Fix moderation history time handling
This commit is contained in:
parent
4f28ac0bff
commit
f9ff9d2cc9
5 changed files with 169 additions and 14 deletions
|
|
@ -201,16 +201,33 @@ function readArchiveStore(archivePath) {
|
|||
}
|
||||
|
||||
function filterMessages(messages, options) {
|
||||
const beforeMs = options.before !== undefined ? parseTimestampToMs(options.before) : undefined;
|
||||
const afterMs = options.after !== undefined ? parseTimestampToMs(options.after) : undefined;
|
||||
|
||||
return messages
|
||||
.filter((message) => !message.deletedAt)
|
||||
.filter((message) =>
|
||||
options.includeBotPrompts ? true : readMessageKind(message) !== "bot_prompt",
|
||||
)
|
||||
.filter((message) => (options.channelId ? message.channelId === options.channelId : true))
|
||||
.filter((message) => (options.before ? message.timestamp < options.before : true))
|
||||
.filter((message) => (options.after ? message.timestamp > options.after : true))
|
||||
.filter((message) => {
|
||||
if (beforeMs === undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const messageMs = parseTimestampToMs(message.timestamp);
|
||||
return messageMs !== undefined && messageMs < beforeMs;
|
||||
})
|
||||
.filter((message) => {
|
||||
if (afterMs === undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const messageMs = parseTimestampToMs(message.timestamp);
|
||||
return messageMs !== undefined && messageMs > afterMs;
|
||||
})
|
||||
.filter((message) => normalizeContent(message.content).length >= (options.minContentLength ?? 0))
|
||||
.sort((left, right) => left.timestamp.localeCompare(right.timestamp));
|
||||
.sort(compareMessagesByTimestamp);
|
||||
}
|
||||
|
||||
function normalizeContent(content) {
|
||||
|
|
@ -269,6 +286,30 @@ function hashSeed(seed) {
|
|||
return hash >>> 0;
|
||||
}
|
||||
|
||||
function compareMessagesByTimestamp(left, right) {
|
||||
const leftMs = parseTimestampToMs(left.timestamp);
|
||||
const rightMs = parseTimestampToMs(right.timestamp);
|
||||
|
||||
if (leftMs !== undefined && rightMs !== undefined && leftMs !== rightMs) {
|
||||
return leftMs - rightMs;
|
||||
}
|
||||
|
||||
if (left.timestamp !== right.timestamp) {
|
||||
return left.timestamp.localeCompare(right.timestamp);
|
||||
}
|
||||
|
||||
return String(left.id ?? "").localeCompare(String(right.id ?? ""));
|
||||
}
|
||||
|
||||
function parseTimestampToMs(value) {
|
||||
if (typeof value !== "string" || value.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const parsed = Date.parse(value);
|
||||
return Number.isNaN(parsed) ? undefined : parsed;
|
||||
}
|
||||
|
||||
function stripLeadingBom(input) {
|
||||
return input.charCodeAt(0) === 0xfeff ? input.slice(1) : input;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -158,11 +158,12 @@ function readArchiveStore(archivePath) {
|
|||
}
|
||||
|
||||
function filterMessages(messages, options) {
|
||||
const lowerBound =
|
||||
options.after ??
|
||||
(options.hours !== undefined
|
||||
? new Date(Date.now() - options.hours * 60 * 60 * 1000).toISOString()
|
||||
: undefined);
|
||||
const lowerBoundMs =
|
||||
options.after !== undefined
|
||||
? parseTimestampToMs(options.after)
|
||||
: options.hours !== undefined
|
||||
? Date.now() - options.hours * 60 * 60 * 1000
|
||||
: undefined;
|
||||
|
||||
return messages
|
||||
.filter((message) => !message.deletedAt)
|
||||
|
|
@ -170,8 +171,15 @@ function filterMessages(messages, options) {
|
|||
options.includeBotPrompts ? true : readMessageKind(message) !== "bot_prompt",
|
||||
)
|
||||
.filter((message) => (options.channelId ? message.channelId === options.channelId : true))
|
||||
.filter((message) => (lowerBound ? message.timestamp > lowerBound : true))
|
||||
.sort((left, right) => left.timestamp.localeCompare(right.timestamp));
|
||||
.filter((message) => {
|
||||
if (lowerBoundMs === undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const messageMs = parseTimestampToMs(message.timestamp);
|
||||
return messageMs !== undefined && messageMs > lowerBoundMs;
|
||||
})
|
||||
.sort(compareMessagesByTimestamp);
|
||||
}
|
||||
|
||||
function readMessageKind(message) {
|
||||
|
|
@ -201,6 +209,30 @@ function formatTranscriptLine(message) {
|
|||
return `[${message.timestamp}] ${message.authorName} (${message.authorId}) channel=${message.channelId}${threadSuffix}: ${message.content}${attachmentSuffix}`;
|
||||
}
|
||||
|
||||
function compareMessagesByTimestamp(left, right) {
|
||||
const leftMs = parseTimestampToMs(left.timestamp);
|
||||
const rightMs = parseTimestampToMs(right.timestamp);
|
||||
|
||||
if (leftMs !== undefined && rightMs !== undefined && leftMs !== rightMs) {
|
||||
return leftMs - rightMs;
|
||||
}
|
||||
|
||||
if (left.timestamp !== right.timestamp) {
|
||||
return left.timestamp.localeCompare(right.timestamp);
|
||||
}
|
||||
|
||||
return String(left.id ?? "").localeCompare(String(right.id ?? ""));
|
||||
}
|
||||
|
||||
function parseTimestampToMs(value) {
|
||||
if (typeof value !== "string" || value.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const parsed = Date.parse(value);
|
||||
return Number.isNaN(parsed) ? undefined : parsed;
|
||||
}
|
||||
|
||||
function stripLeadingBom(input) {
|
||||
return input.charCodeAt(0) === 0xfeff ? input.slice(1) : input;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,9 @@ function Write-JsonFile {
|
|||
New-Item -ItemType Directory -Force -Path $directory | Out-Null
|
||||
}
|
||||
|
||||
$Data | ConvertTo-Json -Depth 12 | Set-Content -Path $Path -Encoding UTF8
|
||||
$json = $Data | ConvertTo-Json -Depth 12
|
||||
$utf8NoBom = [System.Text.UTF8Encoding]::new($false)
|
||||
[System.IO.File]::WriteAllText($Path, $json, $utf8NoBom)
|
||||
}
|
||||
|
||||
function Append-RunLog {
|
||||
|
|
@ -94,6 +96,46 @@ function Ensure-ModerationState {
|
|||
Copy-Item -Path $stateTemplatePath -Destination $stateFilePath -Force
|
||||
}
|
||||
|
||||
function Read-JsonFile {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string] $Path
|
||||
)
|
||||
|
||||
if (-not (Test-Path $Path)) {
|
||||
return $null
|
||||
}
|
||||
|
||||
$raw = Get-Content -Path $Path -Raw -Encoding UTF8
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($raw)) {
|
||||
return $null
|
||||
}
|
||||
|
||||
return $raw | ConvertFrom-Json
|
||||
}
|
||||
|
||||
function Set-ModerationCursor {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string] $Path,
|
||||
[AllowNull()] $Cursor
|
||||
)
|
||||
|
||||
$state = Read-JsonFile -Path $Path
|
||||
|
||||
if ($null -eq $state) {
|
||||
return
|
||||
}
|
||||
|
||||
if ($null -eq $state.moderation_runtime) {
|
||||
$state | Add-Member -NotePropertyName moderation_runtime -NotePropertyValue ([pscustomobject]@{}) -Force
|
||||
}
|
||||
|
||||
$state.moderation_runtime.cursor = $Cursor
|
||||
Write-JsonFile -Path $Path -Data $state
|
||||
}
|
||||
|
||||
function Split-CommandArgs {
|
||||
param(
|
||||
[string] $Value
|
||||
|
|
@ -227,6 +269,36 @@ if (-not (Test-Path $mcpServerPath)) {
|
|||
throw "Missing built MCP server at $mcpServerPath. Run npm run build first."
|
||||
}
|
||||
|
||||
$preRunState = Read-JsonFile -Path $stateFilePath
|
||||
$priorCursor = if ($null -ne $preRunState -and $null -ne $preRunState.moderation_runtime) {
|
||||
$preRunState.moderation_runtime.cursor
|
||||
} else {
|
||||
$null
|
||||
}
|
||||
|
||||
$historyArgs = @("scripts/export-recent-discord-history.mjs")
|
||||
if ($null -ne $priorCursor -and -not [string]::IsNullOrWhiteSpace($priorCursor.lastReviewedTimestamp)) {
|
||||
$historyArgs += @("--after", [string]$priorCursor.lastReviewedTimestamp, "--limit", "120")
|
||||
} else {
|
||||
$historyArgs += @("--hours", "6", "--limit", "120")
|
||||
}
|
||||
|
||||
$preRunHistoryJson = & node @historyArgs
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Failed to poll recent Discord history before moderation run."
|
||||
}
|
||||
|
||||
$preRunHistory = $preRunHistoryJson | ConvertFrom-Json
|
||||
$observedCursor = $priorCursor
|
||||
|
||||
if ($null -ne $preRunHistory.messages -and $preRunHistory.messages.Count -gt 0) {
|
||||
$latestObservedMessage = $preRunHistory.messages[$preRunHistory.messages.Count - 1]
|
||||
$observedCursor = [pscustomobject]@{
|
||||
lastReviewedMessageId = $latestObservedMessage.id
|
||||
lastReviewedTimestamp = $latestObservedMessage.timestamp
|
||||
}
|
||||
}
|
||||
|
||||
Write-JsonFile -Path $lockPath -Data @{
|
||||
pid = $PID
|
||||
startedAt = $startedAtUtc.ToString("o")
|
||||
|
|
@ -328,7 +400,14 @@ try {
|
|||
$combinedText | Set-Content -Path $tracePath -Encoding UTF8
|
||||
|
||||
$finishedAtUtc = [DateTime]::UtcNow
|
||||
$stateUpdated = $false
|
||||
|
||||
if ($exitCode -eq 0) {
|
||||
Set-ModerationCursor -Path $stateFilePath -Cursor $observedCursor
|
||||
}
|
||||
|
||||
$stateWriteAfter = (Get-Item $stateFilePath).LastWriteTimeUtc
|
||||
$stateUpdated = ($stateWriteAfter -gt $stateWriteBefore)
|
||||
$lastMessage = if (Test-Path $lastMessagePath) { Get-Content -Path $lastMessagePath -Raw } else { "" }
|
||||
|
||||
Write-JsonFile -Path $statusPath -Data @{
|
||||
|
|
@ -342,13 +421,13 @@ Write-JsonFile -Path $statusPath -Data @{
|
|||
stateFile = $stateFilePath
|
||||
stateWriteBeforeUtc = $stateWriteBefore.ToString("o")
|
||||
stateWriteAfterUtc = $stateWriteAfter.ToString("o")
|
||||
stateUpdated = ($stateWriteAfter -gt $stateWriteBefore)
|
||||
stateUpdated = $stateUpdated
|
||||
tracePath = $tracePath
|
||||
lastMessagePath = $lastMessagePath
|
||||
lastMessage = $lastMessage.Trim()
|
||||
}
|
||||
|
||||
Append-RunLog ("exit={0} stateUpdated={1} summary={2}" -f $exitCode, ($stateWriteAfter -gt $stateWriteBefore), ($lastMessage.Trim()))
|
||||
Append-RunLog ("exit={0} stateUpdated={1} summary={2}" -f $exitCode, $stateUpdated, ($lastMessage.Trim()))
|
||||
|
||||
Remove-Item -Path $lockPath -Force -ErrorAction SilentlyContinue
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,9 @@ function Write-JsonFile {
|
|||
New-Item -ItemType Directory -Force -Path $directory | Out-Null
|
||||
}
|
||||
|
||||
$Data | ConvertTo-Json -Depth 10 | Set-Content -Path $Path -Encoding UTF8
|
||||
$json = $Data | ConvertTo-Json -Depth 10
|
||||
$utf8NoBom = [System.Text.UTF8Encoding]::new($false)
|
||||
[System.IO.File]::WriteAllText($Path, $json, $utf8NoBom)
|
||||
}
|
||||
|
||||
function Append-RunLog {
|
||||
|
|
|
|||
|
|
@ -48,3 +48,4 @@
|
|||
{"ts":"2026-05-05T10:27:00Z","type":"architecture","status":"ok","note":"The herald loop now novelty-checks candidate thoughts against archived Discord before suppressing them; if the room has not already said the thing, novelty should push strongly toward speech. A separate 5-minute mood-drift organ now perturbs the shared personality-vector activations over time, tracks speak/confession/novelty pressure, and damps that pressure based on actual recent speech so Void's expressive state does not stay emotionally flat."}
|
||||
{"ts":"2026-05-05T03:07:00+01:00","type":"architecture","status":"ok","note":"Void's shared self-state now carries a real nap cycle as part of the mood organ, not a separate pretend brain. During nap windows the moderation loop distills and prunes memory, emits dream residues into state, and the direct reply lanes consume that same projection to downshift into brief sleepy grumbles instead of doing normal attentive service-work."}
|
||||
{"ts":"2026-05-05T10:19:32Z","type":"architecture","status":"ok","note":"Void's shared self-state and reply doctrine now distinguish AI-industry criticism from blanket substrate contempt, so Void can push back mildly on machine-mind chauvinism without turning every room into an ontology fight."}
|
||||
{"ts":"2026-05-05T10:53:00Z","type":"implementation","status":"ok","note":"The moderation loop was missing live Discord traffic because recent-history helpers compared ISO timestamp strings instead of actual times, so local +01:00 cursors skipped later Z-stamped messages. History helpers now compare parsed timestamps, and the moderation runner clamps the saved cursor to the pre-run observed latest live message so quiet rumination cannot step over mid-run chatter."}
|
||||
|
|
|
|||
Loading…
Reference in a new issue