mixdog 0.9.149 → 0.9.151
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -12
- package/package.json +1 -1
- package/src/headless-exec.mjs +40 -4
- package/src/headless-exec.test.mjs +116 -1
- package/src/rules/lead/01-general.md +3 -2
- package/src/rules/shared/00-general.md +1 -0
- package/src/rules/shared/10-tool-workflow.md +11 -12
- package/src/rules/shared/30-exploration.md +4 -3
- package/src/rules/shared/60-verification.md +4 -3
- package/src/runtime/agent/orchestrator/providers/openai-codex-identity.test.mjs +0 -2
- package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +1 -4
- package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +0 -18
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +0 -3
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +0 -1
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +4 -72
- package/src/runtime/agent/orchestrator/session/approval-mode.mjs +0 -8
- package/src/runtime/agent/orchestrator/session/compact/handoff.mjs +2 -4
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +18 -33
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +21 -42
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +20 -125
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +50 -8
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.test.mjs +31 -1
- package/src/runtime/agent/orchestrator/session/store/serialize.mjs +27 -4
- package/src/runtime/agent/orchestrator/session/store/serialize.test.mjs +60 -0
- package/src/runtime/agent/orchestrator/session/task-wait-control.mjs +78 -0
- package/src/runtime/agent/orchestrator/session/task-wait-control.test.mjs +78 -0
- package/src/runtime/agent/orchestrator/tools/builtin/absence-absorption.test.mjs +43 -0
- package/src/runtime/agent/orchestrator/tools/builtin/absolute-glob-expand.test.mjs +63 -0
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +29 -10
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -7
- package/src/runtime/agent/orchestrator/tools/builtin/enoent-outside-project.test.mjs +56 -0
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +50 -23
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.test.mjs +50 -1
- package/src/runtime/agent/orchestrator/tools/builtin/grep-output-budget.test.mjs +31 -0
- package/src/runtime/agent/orchestrator/tools/builtin/grep-single-file-rescue.test.mjs +106 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/absolute-glob-expand.mjs +129 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-context-expander.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-single-file-rescue.mjs +218 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +9 -2
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool-integrity.test.mjs +6 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +50 -2
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/noise-dir-visibility.test.mjs +69 -0
- package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +33 -6
- package/src/runtime/agent/orchestrator/tools/builtin/read-glob-survey.test.mjs +71 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +7 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +54 -8
- package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +90 -51
- package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.test.mjs +56 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +25 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.test.mjs +16 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +30 -1
- package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +60 -5
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +54 -1
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +49 -24
- package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +1 -0
- package/src/runtime/agent/orchestrator/tools/builtin/write-symlink.test.mjs +153 -0
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +11 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/aggregate-anchor-relocation.test.mjs +55 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +54 -3
- package/src/runtime/agent/orchestrator/tools/env-scrub.mjs +16 -0
- package/src/runtime/agent/orchestrator/tools/env-scrub.test.mjs +33 -1
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +9 -1
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +44 -10
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +57 -5
- package/src/runtime/agent/orchestrator/tools/patch/patch-symlink.test.mjs +141 -0
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +7 -3
- package/src/runtime/agent/orchestrator/tools/patch/v4a-pure-move.test.mjs +57 -0
- package/src/runtime/agent/orchestrator/tools/patch/v4a-section-coalesce.test.mjs +81 -0
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +42 -23
- package/src/runtime/media/renditions.mjs +7 -1
- package/src/runtime/memory/index.mjs +2 -4
- package/src/runtime/memory/lib/pg/process.mjs +29 -2
- package/src/runtime/memory/lib/pg/process.test.mjs +36 -0
- package/src/runtime/memory/lib/pg/supervisor.mjs +5 -1
- package/src/runtime/memory/lib/query-handlers.mjs +13 -19
- package/src/runtime/memory/lib/recall-format.mjs +13 -31
- package/src/runtime/memory/tool-defs.mjs +2 -2
- package/src/runtime/shared/memory-snapshot.mjs +57 -4
- package/src/runtime/shared/pristine-execution-contract.json +3 -0
- package/src/runtime/shared/pristine-execution.mjs +6 -3
- package/src/runtime/shared/tool-surface.mjs +6 -0
- package/src/runtime/shared/tool-surface.test.mjs +8 -0
- package/src/session-runtime/lifecycle-api.mjs +1 -0
- package/src/session-runtime/session-turn-api.mjs +5 -0
- package/src/standalone/agent-dispatch-broker.mjs +25 -80
- package/src/standalone/channel-worker-heartbeat.test.mjs +30 -0
- package/src/standalone/channel-worker.mjs +31 -1
- package/src/standalone/daemon.mjs +9 -9
- package/src/standalone/memory-runtime-proxy.mjs +5 -2
- package/src/standalone/session-client.mjs +23 -3
- package/src/standalone/session-runtime-host-health.test.mjs +36 -0
- package/src/standalone/session-runtime-host.mjs +26 -76
- package/src/standalone/session-runtime-worker.mjs +97 -0
- package/src/tui/App.jsx +3 -2
- package/src/tui/app/use-global-key-input.mjs +9 -0
- package/src/tui/app/use-mouse-input.mjs +77 -14
- package/src/tui/components/Spinner.jsx +1 -1
- package/src/tui/dist/index.mjs +83 -23
- package/src/tui/lib/voice-setup.mjs +3 -2
- package/src/tui/session/completion-card-restore.test.mjs +18 -0
- package/src/tui/session/session-api-ext.mjs +2 -1
- package/src/tui/session/session-api.mjs +8 -1
- package/src/tui/session/task-wait-submit.test.mjs +80 -0
- package/src/tui/session/turn.mjs +58 -6
- package/src/tui/spinner-verbs.mjs +1 -0
- package/src/workflows/default/WORKFLOW.md +12 -19
- package/src/workflows/headless/WORKFLOW.md +11 -0
- package/src/workflows/solo/WORKFLOW.md +6 -11
|
@@ -444,7 +444,7 @@ export async function stopPg({ runtimeDir, pgdataDir }) {
|
|
|
444
444
|
})
|
|
445
445
|
|
|
446
446
|
if (r.status !== 0) {
|
|
447
|
-
const msg = r.stderr?.toString() || r.stdout?.toString() || ''
|
|
447
|
+
const msg = r.error?.message || r.stderr?.toString() || r.stdout?.toString() || ''
|
|
448
448
|
// Stale postmaster.pid — PG is already down; clean up and continue.
|
|
449
449
|
if (
|
|
450
450
|
msg.includes('no server running') ||
|
|
@@ -453,10 +453,16 @@ export async function stopPg({ runtimeDir, pgdataDir }) {
|
|
|
453
453
|
) {
|
|
454
454
|
__mixdogMemoryLog(`[pg-process] stopPg: already stopped (${msg.slice(0, 80)})\n`)
|
|
455
455
|
try { rmSync(join(pgdataDir, 'postmaster.pid'), { force: true }) } catch {}
|
|
456
|
+
return
|
|
456
457
|
} else {
|
|
457
458
|
__mixdogMemoryLog(`[pg-process] pg_ctl stop warning: ${msg}\n`)
|
|
459
|
+
throw new Error(`pg_ctl stop failed${msg ? `: ${msg.trim()}` : ''}`)
|
|
458
460
|
}
|
|
459
461
|
}
|
|
462
|
+
const remaining = readPostmasterInfo(pgdataDir)
|
|
463
|
+
if (remaining.pid && isPidAlive(remaining.pid)) {
|
|
464
|
+
throw new Error(`postgres postmaster pid=${remaining.pid} survived pg_ctl stop`)
|
|
465
|
+
}
|
|
460
466
|
}
|
|
461
467
|
|
|
462
468
|
// Synchronous best-effort variant for a process 'exit' hook (only sync work is
|
|
@@ -494,6 +500,7 @@ export function classifyOrphanTempPostmaster({
|
|
|
494
500
|
args,
|
|
495
501
|
uptimeSec,
|
|
496
502
|
tempRoot,
|
|
503
|
+
ownerAlive = false,
|
|
497
504
|
minUptimeSec = ORPHAN_TEMP_PG_MIN_UPTIME_SEC,
|
|
498
505
|
}) {
|
|
499
506
|
const commandLine = String(args || '')
|
|
@@ -506,9 +513,24 @@ export function classifyOrphanTempPostmaster({
|
|
|
506
513
|
const root = normalizePathForMatch(tempRoot).replace(/\/+$/, '')
|
|
507
514
|
if (!root || !dataDir.startsWith(`${root}/`)) return false
|
|
508
515
|
if (!dataDir.includes('mixdog')) return false
|
|
516
|
+
if (ownerAlive) return false
|
|
509
517
|
return Number(uptimeSec) >= Math.max(60, Number(minUptimeSec) || ORPHAN_TEMP_PG_MIN_UPTIME_SEC)
|
|
510
518
|
}
|
|
511
519
|
|
|
520
|
+
function tempPostmasterOwnerAlive(args) {
|
|
521
|
+
const commandLine = String(args || '')
|
|
522
|
+
const dataDirMatch = commandLine.match(/(?:^|\s)-D\s+"?([^"]+?)"?(?:\s|$)/)
|
|
523
|
+
if (!dataDirMatch) return false
|
|
524
|
+
const dataRoot = join(dataDirMatch[1], '..')
|
|
525
|
+
for (const name of ['memory-runtime-owner.json', 'daemon-owner.json']) {
|
|
526
|
+
try {
|
|
527
|
+
const owner = JSON.parse(readFileSync(join(dataRoot, name), 'utf8'))
|
|
528
|
+
if (Number(owner?.pid) > 0 && isPidAlive(Number(owner.pid))) return true
|
|
529
|
+
} catch {}
|
|
530
|
+
}
|
|
531
|
+
return false
|
|
532
|
+
}
|
|
533
|
+
|
|
512
534
|
function listPostmasterRows() {
|
|
513
535
|
if (process.platform === 'win32') {
|
|
514
536
|
const result = spawnSync('powershell', ['-NoProfile', '-Command',
|
|
@@ -545,7 +567,12 @@ export function sweepOrphanTempPostmasters({ tempRoot, minUptimeSec } = {}) {
|
|
|
545
567
|
const root = tempRoot || os.tmpdir()
|
|
546
568
|
for (const row of listPostmasterRows()) {
|
|
547
569
|
if (!row.pid || row.pid === process.pid) continue
|
|
548
|
-
if (!classifyOrphanTempPostmaster({
|
|
570
|
+
if (!classifyOrphanTempPostmaster({
|
|
571
|
+
...row,
|
|
572
|
+
tempRoot: root,
|
|
573
|
+
ownerAlive: tempPostmasterOwnerAlive(row.args),
|
|
574
|
+
minUptimeSec,
|
|
575
|
+
})) continue
|
|
549
576
|
try {
|
|
550
577
|
process.kill(row.pid)
|
|
551
578
|
reaped += 1
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
|
|
4
|
+
import { classifyOrphanTempPostmaster } from './process.mjs';
|
|
5
|
+
|
|
6
|
+
const tempRoot = process.platform === 'win32'
|
|
7
|
+
? 'C:/Users/test/AppData/Local/Temp'
|
|
8
|
+
: '/tmp';
|
|
9
|
+
const args = `"${tempRoot}/mixdog-headless-pristine-test/data/runtime/bin/postgres" `
|
|
10
|
+
+ `-D "${tempRoot}/mixdog-headless-pristine-test/data/pgdata"`;
|
|
11
|
+
|
|
12
|
+
test('old unowned temp postmasters are sweep candidates', () => {
|
|
13
|
+
assert.equal(classifyOrphanTempPostmaster({
|
|
14
|
+
args,
|
|
15
|
+
uptimeSec: 3600,
|
|
16
|
+
tempRoot,
|
|
17
|
+
ownerAlive: false,
|
|
18
|
+
}), true);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('a live owner protects a long-running temp postmaster', () => {
|
|
22
|
+
assert.equal(classifyOrphanTempPostmaster({
|
|
23
|
+
args,
|
|
24
|
+
uptimeSec: 3600,
|
|
25
|
+
tempRoot,
|
|
26
|
+
ownerAlive: true,
|
|
27
|
+
}), false);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('the official data directory is never a sweep candidate', () => {
|
|
31
|
+
assert.equal(classifyOrphanTempPostmaster({
|
|
32
|
+
args: 'postgres -D "/home/test/.mixdog/data/pgdata"',
|
|
33
|
+
uptimeSec: 3600,
|
|
34
|
+
tempRoot,
|
|
35
|
+
}), false);
|
|
36
|
+
});
|
|
@@ -458,6 +458,7 @@ async function tryReusePgInstance({ pgdata, runtimeDir, healthcheckPg, source =
|
|
|
458
458
|
// patchActiveInstance) so a concurrent restart/owner change is not
|
|
459
459
|
// clobbered by this reuse-time snapshot.
|
|
460
460
|
_restampReuseOwner({ pgdata, port: existingPort });
|
|
461
|
+
scheduleOrphanTempPostmasterSweep();
|
|
461
462
|
return { host: '127.0.0.1', port: existingPort, runtimeDir: existingRtDir, pgdataDir: pgdata };
|
|
462
463
|
}
|
|
463
464
|
} catch {}
|
|
@@ -475,6 +476,7 @@ async function tryReusePgInstance({ pgdata, runtimeDir, healthcheckPg, source =
|
|
|
475
476
|
pg_pgdata: pgdata,
|
|
476
477
|
pg_runtime_dir: runtimeDir,
|
|
477
478
|
}, { timeoutMs: 1000, background: true });
|
|
479
|
+
scheduleOrphanTempPostmasterSweep();
|
|
478
480
|
return { host: '127.0.0.1', port: pm.port, runtimeDir, pgdataDir: pgdata };
|
|
479
481
|
}
|
|
480
482
|
} catch {}
|
|
@@ -674,6 +676,7 @@ export async function stopPgForShutdown() {
|
|
|
674
676
|
__mixdogMemoryLog(`[supervisor-pg] PG stopped (via active-instance.json) on shutdown\n`);
|
|
675
677
|
} catch (e) {
|
|
676
678
|
__mixdogMemoryLog(`[supervisor-pg] stopPg error on shutdown (no _live): ${e?.message}\n`);
|
|
679
|
+
throw e;
|
|
677
680
|
}
|
|
678
681
|
// Bounded sync clear (<=4s: 2s + one 2s retry). A residual stale pg_port is
|
|
679
682
|
// tolerated — readers healthcheck-verify the port before reuse.
|
|
@@ -681,14 +684,15 @@ export async function stopPgForShutdown() {
|
|
|
681
684
|
return;
|
|
682
685
|
}
|
|
683
686
|
const snap = _live;
|
|
684
|
-
_live = null;
|
|
685
687
|
_ensureInFlight = null;
|
|
686
688
|
try {
|
|
687
689
|
const { stopPg } = await _getPgProc();
|
|
688
690
|
await stopPg({ runtimeDir: snap.runtimeDir, pgdataDir: snap.pgdata });
|
|
691
|
+
_live = null;
|
|
689
692
|
__mixdogMemoryLog(`[supervisor-pg] PG stopped gracefully on shutdown\n`);
|
|
690
693
|
} catch (e) {
|
|
691
694
|
__mixdogMemoryLog(`[supervisor-pg] stopPg error on shutdown: ${e?.message}\n`);
|
|
695
|
+
throw e;
|
|
692
696
|
}
|
|
693
697
|
// Bounded sync clear (<=4s: 2s + one 2s retry). A residual stale pg_port is
|
|
694
698
|
// tolerated — readers healthcheck-verify the port before reuse.
|
|
@@ -136,7 +136,7 @@ export function createQueryHandlers({
|
|
|
136
136
|
// Over-fetch before compact-only dedupe so duplicated legacy rows cannot
|
|
137
137
|
// consume the requested page and hide distinct older context.
|
|
138
138
|
const fetchLimit = compactHandoff
|
|
139
|
-
?
|
|
139
|
+
? null
|
|
140
140
|
: compactDigest
|
|
141
141
|
? Math.min(100, Math.max(limit, limit * 4))
|
|
142
142
|
: limit
|
|
@@ -189,16 +189,17 @@ export function createQueryHandlers({
|
|
|
189
189
|
})
|
|
190
190
|
where.push(`(${clauses.join(' OR ')})`)
|
|
191
191
|
}
|
|
192
|
-
params.push(fetchLimit)
|
|
192
|
+
if (fetchLimit != null) params.push(fetchLimit)
|
|
193
|
+
const limitClause = fetchLimit == null ? '' : `LIMIT $${params.length}`
|
|
193
194
|
let rows = (await db.query(`
|
|
194
195
|
SELECT id, ts, role, content, source_ref, session_id, source_turn, time_source, chunk_root, is_root,
|
|
195
196
|
element, category, summary, status, score, last_seen_at, project_id
|
|
196
197
|
FROM entries
|
|
197
198
|
WHERE ${where.join(' AND ')}
|
|
198
199
|
ORDER BY ts DESC, source_turn DESC NULLS LAST, id DESC
|
|
199
|
-
|
|
200
|
+
${limitClause}
|
|
200
201
|
`, params)).rows
|
|
201
|
-
if (rows.length < fetchLimit) {
|
|
202
|
+
if (fetchLimit != null && rows.length < fetchLimit) {
|
|
202
203
|
const seen = new Set(rows.map((row) => Number(row.id)).filter((id) => Number.isFinite(id)))
|
|
203
204
|
const fillLimit = Math.max(0, fetchLimit - rows.length)
|
|
204
205
|
const fillWhere = ['session_id = $1', 'id <> ALL($2::bigint[])', '(is_root = 1 OR chunk_root IS NULL OR chunk_root = id)']
|
|
@@ -220,7 +221,7 @@ export function createQueryHandlers({
|
|
|
220
221
|
: []
|
|
221
222
|
if (fillRows.length > 0) rows = [...rows, ...fillRows]
|
|
222
223
|
}
|
|
223
|
-
if (args.includeMembers === true
|
|
224
|
+
if (args.includeMembers === true) {
|
|
224
225
|
const rootIds = rows
|
|
225
226
|
.filter((row) => Number(row.is_root) === 1)
|
|
226
227
|
.map((row) => Number(row.id))
|
|
@@ -243,7 +244,7 @@ export function createQueryHandlers({
|
|
|
243
244
|
}
|
|
244
245
|
}
|
|
245
246
|
}
|
|
246
|
-
if (compactHandoff) rows = compactHandoffRows(rows
|
|
247
|
+
if (compactHandoff) rows = compactHandoffRows(rows)
|
|
247
248
|
else if (compactDigest) rows = compactDigestRows(rows, limit)
|
|
248
249
|
return { text: renderEntryLines(rows, { pendingMarks: !compactDigest && !compactHandoff }) }
|
|
249
250
|
}
|
|
@@ -507,18 +508,11 @@ export function createQueryHandlers({
|
|
|
507
508
|
? args.query.some((v) => String(v || '').trim())
|
|
508
509
|
: String(args.query ?? '').trim() !== ''
|
|
509
510
|
let sort = args.sort != null ? String(args.sort) : (hasQueryForSort ? 'importance' : 'date')
|
|
510
|
-
//
|
|
511
|
-
//
|
|
512
|
-
//
|
|
513
|
-
|
|
514
|
-
const
|
|
515
|
-
// Raw leg defaults ON: recall never drains/chunks inline anymore, so raw
|
|
516
|
-
// (unchunked) rows — embedded by the always-on post-ingest/tick flush —
|
|
517
|
-
// are the only way to see content cycle1 hasn't reached yet, regardless
|
|
518
|
-
// of the recap toggle. The importance/query search leg only ranks chunked
|
|
519
|
-
// entries; readRawRowsInWindow interleaves the raw rows into the hybrid
|
|
520
|
-
// list. Explicit includeRaw:false keeps the chunked-only view.
|
|
521
|
-
const includeRaw = args.includeRaw !== false
|
|
511
|
+
// Root summaries are the compact default recall output. Chunk members and
|
|
512
|
+
// unchunked raw/episode rows are explicit expansion legs for callers that
|
|
513
|
+
// need the underlying transcript evidence.
|
|
514
|
+
const includeMembers = args.includeMembers === true
|
|
515
|
+
const includeRaw = args.includeRaw === true
|
|
522
516
|
const includeArchived = args.includeArchived !== false
|
|
523
517
|
const category = args.category
|
|
524
518
|
const temporal = parsePeriod(period, Boolean(query))
|
|
@@ -1094,7 +1088,7 @@ export function createQueryHandlers({
|
|
|
1094
1088
|
// too and merge chronologically — original text first, no summaries.
|
|
1095
1089
|
// Query-less + includeRaw:false callers keep the roots-only view.
|
|
1096
1090
|
let merged = rows
|
|
1097
|
-
if (sort === 'date' &&
|
|
1091
|
+
if (sort === 'date' && includeRaw) {
|
|
1098
1092
|
const rawRows = await readRawRowsInWindow(
|
|
1099
1093
|
db,
|
|
1100
1094
|
filters.ts_from ?? temporal?.startMs ?? null,
|
|
@@ -399,16 +399,21 @@ export function compactDigestRows(rows, limit = 30) {
|
|
|
399
399
|
.slice(0, cap)
|
|
400
400
|
}
|
|
401
401
|
|
|
402
|
-
export function compactHandoffRows(rows
|
|
403
|
-
const
|
|
404
|
-
const deduped = collapseNearDuplicateRows(collapseExactDuplicateRows(Array.isArray(rows) ? rows : []))
|
|
405
|
-
.filter((row) => !row?._dupStub)
|
|
402
|
+
export function compactHandoffRows(rows) {
|
|
403
|
+
const deduped = Array.isArray(rows) ? rows : []
|
|
406
404
|
const roots = []
|
|
407
405
|
const raw = []
|
|
408
406
|
for (const row of deduped) {
|
|
409
407
|
if (Number(row?.is_root) === 1) {
|
|
410
|
-
|
|
411
|
-
|
|
408
|
+
if (String(row?.summary ?? '').trim()) {
|
|
409
|
+
const { members: _members, ...summaryRow } = row
|
|
410
|
+
roots.push(summaryRow)
|
|
411
|
+
} else if (Array.isArray(row?.members) && row.members.length > 0) {
|
|
412
|
+
raw.push(...row.members)
|
|
413
|
+
} else {
|
|
414
|
+
const { members: _members, ...rawRoot } = row
|
|
415
|
+
raw.push(rawRoot)
|
|
416
|
+
}
|
|
412
417
|
} else if (row?.chunk_root == null || Number(row.chunk_root) === Number(row.id)) {
|
|
413
418
|
raw.push(row)
|
|
414
419
|
}
|
|
@@ -419,32 +424,9 @@ export function compactHandoffRows(rows, limit = 100) {
|
|
|
419
424
|
|| (Number(a?.ts) || 0) - (Number(b?.ts) || 0)
|
|
420
425
|
|| (Number(a?.id) || 0) - (Number(b?.id) || 0)
|
|
421
426
|
))
|
|
422
|
-
const
|
|
423
|
-
let userRow = null
|
|
424
|
-
let assistantRow = null
|
|
425
|
-
let fallbackRow = null
|
|
426
|
-
const flushEvent = () => {
|
|
427
|
-
if (assistantRow || fallbackRow) endpoints.push(assistantRow || fallbackRow)
|
|
428
|
-
if (userRow) endpoints.push(userRow)
|
|
429
|
-
userRow = null
|
|
430
|
-
assistantRow = null
|
|
431
|
-
fallbackRow = null
|
|
432
|
-
}
|
|
433
|
-
for (const row of raw) {
|
|
434
|
-
if (row?.role === 'user') {
|
|
435
|
-
flushEvent()
|
|
436
|
-
userRow = row
|
|
437
|
-
} else if (row?.role === 'assistant') {
|
|
438
|
-
assistantRow = row
|
|
439
|
-
} else {
|
|
440
|
-
fallbackRow = row
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
flushEvent()
|
|
444
|
-
|
|
445
|
-
const selected = [...roots, ...endpoints]
|
|
427
|
+
const selected = [...roots, ...raw]
|
|
446
428
|
selected.sort(compareRecallNewestFirst)
|
|
447
|
-
return
|
|
429
|
+
return selected
|
|
448
430
|
}
|
|
449
431
|
|
|
450
432
|
// Compact session label for group headers: keep short ids verbatim, shorten
|
|
@@ -46,8 +46,8 @@ export const TOOL_DEFS = [
|
|
|
46
46
|
// handler validates against VALID_CATEGORY.
|
|
47
47
|
category: { anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }], description: 'Category filter, string or array: rule|constraint|decision|fact|goal|preference|task|issue.' },
|
|
48
48
|
includeArchived: { type: 'boolean', description: 'Include archived entries; default true.' },
|
|
49
|
-
includeMembers: { type: 'boolean', description: 'Include chunk members; default
|
|
50
|
-
includeRaw: { type: 'boolean', description: 'Include raw/episode rows; default
|
|
49
|
+
includeMembers: { type: 'boolean', default: false, description: 'Include chunk members; default false.' },
|
|
50
|
+
includeRaw: { type: 'boolean', default: false, description: 'Include raw/episode rows; default false.' },
|
|
51
51
|
projectScope: { type: 'string', description: 'Pool: inferred from cwd, common, all, or slug.' },
|
|
52
52
|
},
|
|
53
53
|
additionalProperties: false,
|
|
@@ -2,6 +2,7 @@ import { execFile } from 'node:child_process';
|
|
|
2
2
|
import {
|
|
3
3
|
mkdir,
|
|
4
4
|
open,
|
|
5
|
+
readFile,
|
|
5
6
|
rename,
|
|
6
7
|
stat,
|
|
7
8
|
unlink,
|
|
@@ -59,6 +60,51 @@ function systemMemory() {
|
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Private working set of THIS process. Raw RSS double-counts image pages the
|
|
65
|
+
* runtime binary shares across every mixdog process (~90-100MB each measured
|
|
66
|
+
* on the desktop bundle), so trend analysis needs the private number beside
|
|
67
|
+
* memoryUsage. Best-effort: one bounded probe per sample, null when the
|
|
68
|
+
* platform offers no cheap source.
|
|
69
|
+
*/
|
|
70
|
+
async function selfPrivateMemory() {
|
|
71
|
+
if (process.platform === 'win32') {
|
|
72
|
+
try {
|
|
73
|
+
const { stdout } = await execFileAsync('powershell.exe', [
|
|
74
|
+
'-NoProfile', '-NonInteractive', '-Command',
|
|
75
|
+
`Get-CimInstance Win32_PerfFormattedData_PerfProc_Process -Filter "IDProcess=${process.pid}" | Select-Object -First 1 -ExpandProperty WorkingSetPrivate`,
|
|
76
|
+
], {
|
|
77
|
+
encoding: 'utf8',
|
|
78
|
+
timeout: 3000,
|
|
79
|
+
windowsHide: true,
|
|
80
|
+
});
|
|
81
|
+
const bytes = Number(String(stdout).trim());
|
|
82
|
+
return Number.isFinite(bytes) && bytes >= 0 ? { workingSetPrivateBytes: bytes } : null;
|
|
83
|
+
} catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (process.platform === 'linux') {
|
|
88
|
+
try {
|
|
89
|
+
const rollup = await readFile('/proc/self/smaps_rollup', 'utf8');
|
|
90
|
+
const kb = (field) => {
|
|
91
|
+
const match = rollup.match(new RegExp(`^${field}:\\s+(\\d+) kB$`, 'm'));
|
|
92
|
+
return match ? Number(match[1]) * 1024 : null;
|
|
93
|
+
};
|
|
94
|
+
const privateBytes = (kb('Private_Clean') ?? 0) + (kb('Private_Dirty') ?? 0);
|
|
95
|
+
const pssBytes = kb('Pss');
|
|
96
|
+
if (privateBytes <= 0 && pssBytes === null) return null;
|
|
97
|
+
return {
|
|
98
|
+
workingSetPrivateBytes: privateBytes > 0 ? privateBytes : null,
|
|
99
|
+
proportionalSetSizeBytes: pssBytes,
|
|
100
|
+
};
|
|
101
|
+
} catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
62
108
|
async function pathExists(path) {
|
|
63
109
|
try {
|
|
64
110
|
await stat(path);
|
|
@@ -134,6 +180,9 @@ function parseWindowsProcesses(stdout) {
|
|
|
134
180
|
name: String(row?.Name || ''),
|
|
135
181
|
pid: Number(row?.ProcessId),
|
|
136
182
|
workingSetMb: workingSetMb(row?.WorkingSetSize),
|
|
183
|
+
workingSetPrivateMb: Number.isFinite(Number(row?.WorkingSetPrivate))
|
|
184
|
+
? workingSetMb(row.WorkingSetPrivate)
|
|
185
|
+
: null,
|
|
137
186
|
commandLine: nullableProcessText(row?.CommandLine),
|
|
138
187
|
parentPid: nullablePid(row?.ParentProcessId),
|
|
139
188
|
}))
|
|
@@ -164,10 +213,10 @@ async function topProcesses() {
|
|
|
164
213
|
if (process.platform === 'win32') {
|
|
165
214
|
const { stdout } = await execFileAsync('powershell.exe', [
|
|
166
215
|
'-NoProfile', '-NonInteractive', '-Command',
|
|
167
|
-
'Get-CimInstance Win32_Process | Sort-Object WorkingSetSize -Descending | Select-Object -First 8 ProcessId,Name,WorkingSetSize,CommandLine,ParentProcessId | ConvertTo-Json -Compress',
|
|
216
|
+
'$private = @{}; Get-CimInstance Win32_PerfFormattedData_PerfProc_Process | ForEach-Object { $private[[int64]$_.IDProcess] = $_.WorkingSetPrivate }; Get-CimInstance Win32_Process | Sort-Object WorkingSetSize -Descending | Select-Object -First 8 ProcessId,Name,WorkingSetSize,CommandLine,ParentProcessId,@{n=\'WorkingSetPrivate\';e={$private[[int64]$_.ProcessId]}} | ConvertTo-Json -Compress',
|
|
168
217
|
], {
|
|
169
218
|
encoding: 'utf8',
|
|
170
|
-
timeout:
|
|
219
|
+
timeout: 5000,
|
|
171
220
|
windowsHide: true,
|
|
172
221
|
});
|
|
173
222
|
return parseWindowsProcesses(stdout);
|
|
@@ -200,19 +249,23 @@ function baseSnapshot(reason) {
|
|
|
200
249
|
async function captureMemoryPressureSnapshot(reason = 'memory-pressure') {
|
|
201
250
|
if (!enabled()) return false;
|
|
202
251
|
const entry = baseSnapshot(reason);
|
|
203
|
-
const processes = await topProcesses();
|
|
252
|
+
const [privateMemory, processes] = await Promise.all([selfPrivateMemory(), topProcesses()]);
|
|
253
|
+
if (privateMemory) entry.privateMemory = privateMemory;
|
|
204
254
|
if (processes) entry.topProcesses = processes;
|
|
205
255
|
return appendSnapshot(entry);
|
|
206
256
|
}
|
|
207
257
|
|
|
208
258
|
/**
|
|
209
|
-
* Periodic samples stay
|
|
259
|
+
* Periodic samples stay light — one bounded private-WS probe beside
|
|
260
|
+
* process.memoryUsage() — and once host free memory drops below the
|
|
210
261
|
* attribution threshold the sample upgrades to a full top-process snapshot so
|
|
211
262
|
* post-mortem analysis can name the consumer.
|
|
212
263
|
*/
|
|
213
264
|
async function capturePeriodicMemorySnapshot() {
|
|
214
265
|
if (!enabled()) return false;
|
|
215
266
|
const entry = baseSnapshot('periodic');
|
|
267
|
+
const privateMemory = await selfPrivateMemory();
|
|
268
|
+
if (privateMemory) entry.privateMemory = privateMemory;
|
|
216
269
|
const freeMemoryBytes = Number(entry.systemMemory?.freeMemoryBytes);
|
|
217
270
|
if (Number.isFinite(freeMemoryBytes) && freeMemoryBytes < PERIODIC_ATTRIBUTION_FREE_BYTES) {
|
|
218
271
|
const processes = await topProcesses();
|
|
@@ -5,12 +5,15 @@
|
|
|
5
5
|
"MIXDOG_DISABLE_MCP": "1",
|
|
6
6
|
"MIXDOG_DISABLE_SKILLS": "1",
|
|
7
7
|
"MIXDOG_BOOT_CORE_MEMORY": "0",
|
|
8
|
+
"MIXDOG_DAEMON_SKIP_MEMORY": "1",
|
|
9
|
+
"MIXDOG_DISABLE_MEMORY_INGEST": "1",
|
|
8
10
|
"MIXDOG_DISABLE_CHANNEL_START": "1",
|
|
9
11
|
"MIXDOG_SKIP_USER_DATA_BACKUP": "1",
|
|
10
12
|
"MIXDOG_DISABLE_STAGED_INSTALL": "1",
|
|
11
13
|
"MIXDOG_DISABLE_STAGED_SWAP": "1",
|
|
12
14
|
"MIXDOG_FEATURE_WEB_SEARCH": "0",
|
|
13
15
|
"MIXDOG_FEATURE_MEMORY": "0",
|
|
16
|
+
"MIXDOG_MEMORY_DISABLE_CYCLES": "1",
|
|
14
17
|
"MIXDOG_MEMORY_CLIENT_GRACE_MS": "1"
|
|
15
18
|
},
|
|
16
19
|
"approvedExecutionEnv": [
|
|
@@ -84,7 +84,7 @@ export function buildMinimalPristineConfig({ provider, model, effort, fast } = {
|
|
|
84
84
|
...route,
|
|
85
85
|
}],
|
|
86
86
|
default: 'exec-explicit-route',
|
|
87
|
-
workflow: { active: '
|
|
87
|
+
workflow: { active: 'headless' },
|
|
88
88
|
workflowRoutes: {},
|
|
89
89
|
agents: {},
|
|
90
90
|
modelSettings,
|
|
@@ -244,7 +244,9 @@ export function createPristineExecutionBoundary({
|
|
|
244
244
|
};
|
|
245
245
|
const rootDir = mkdtempSync(join(tmpdir(), 'mixdog-headless-pristine-'));
|
|
246
246
|
const dataDir = join(rootDir, 'data');
|
|
247
|
+
const runtimeRoot = join(rootDir, 'runtime-root');
|
|
247
248
|
mkdirSync(dataDir, { recursive: true, mode: 0o700 });
|
|
249
|
+
mkdirSync(runtimeRoot, { recursive: true, mode: 0o700 });
|
|
248
250
|
let cleaned = false;
|
|
249
251
|
let restoreAuthBindings = () => {};
|
|
250
252
|
const cleanup = ({ preserveRoot = false, tolerateRootRemovalFailure = false } = {}) => {
|
|
@@ -259,7 +261,7 @@ export function createPristineExecutionBoundary({
|
|
|
259
261
|
// process exits; rmSync retries absorb that EBUSY/EPERM window.
|
|
260
262
|
if (!preserveRoot) {
|
|
261
263
|
try {
|
|
262
|
-
rmSync(rootDir, { recursive: true, force: true, maxRetries:
|
|
264
|
+
rmSync(rootDir, { recursive: true, force: true, maxRetries: 50, retryDelay: 100 });
|
|
263
265
|
} catch (error) {
|
|
264
266
|
if (tolerateRootRemovalFailure) return { rootRemovalError: error };
|
|
265
267
|
throw error;
|
|
@@ -306,6 +308,7 @@ export function createPristineExecutionBoundary({
|
|
|
306
308
|
}
|
|
307
309
|
setEnv('MIXDOG_HOME', rootDir);
|
|
308
310
|
setEnv('MIXDOG_DATA_DIR', dataDir);
|
|
311
|
+
setEnv('MIXDOG_RUNTIME_ROOT', runtimeRoot);
|
|
309
312
|
for (const [name, value] of Object.entries({
|
|
310
313
|
...inheritedExecutionEnv,
|
|
311
314
|
...approvedExecutionEnv,
|
|
@@ -358,7 +361,7 @@ export function createPristineExecutionBoundary({
|
|
|
358
361
|
authMode,
|
|
359
362
|
catalogCount,
|
|
360
363
|
});
|
|
361
|
-
return { rootDir, dataDir, config, audit, loadConfig, cleanup };
|
|
364
|
+
return { rootDir, dataDir, runtimeRoot, config, audit, loadConfig, cleanup };
|
|
362
365
|
} catch (error) {
|
|
363
366
|
cleanup();
|
|
364
367
|
throw error;
|
|
@@ -69,6 +69,12 @@ export {
|
|
|
69
69
|
isMemorySurface,
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
+
export function isTaskWaitToolCall(name, args = {}) {
|
|
73
|
+
if (normalizeToolName(name) !== 'task') return false;
|
|
74
|
+
const parsed = parseToolArgs(args);
|
|
75
|
+
return String(parsed?.action || '').trim().toLowerCase() === 'wait';
|
|
76
|
+
}
|
|
77
|
+
|
|
72
78
|
function patchOperationProfile(args = {}) {
|
|
73
79
|
const a = parseToolArgs(args);
|
|
74
80
|
const patchText = String(a.patch ?? '');
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
aggregateDoneCategories,
|
|
6
6
|
formatAggregateHeader,
|
|
7
7
|
formatToolActionHeader,
|
|
8
|
+
isTaskWaitToolCall,
|
|
8
9
|
toolLoadingTargets,
|
|
9
10
|
} from './tool-surface.mjs';
|
|
10
11
|
import { deriveToolCardModel } from './tool-card-model.mjs';
|
|
@@ -77,3 +78,10 @@ test('aggregate loading cards preserve comma-separated tool and skill names', ()
|
|
|
77
78
|
});
|
|
78
79
|
assert.equal(model.labelText, 'Loaded grep, setup, memory');
|
|
79
80
|
});
|
|
81
|
+
|
|
82
|
+
test('task wait display policy is scoped to the wait action', () => {
|
|
83
|
+
assert.equal(isTaskWaitToolCall('task', { action: 'wait', task_id: 'task_1' }), true);
|
|
84
|
+
assert.equal(isTaskWaitToolCall('functions.task', '{"action":"WAIT","task_id":"task_1"}'), true);
|
|
85
|
+
assert.equal(isTaskWaitToolCall('task', { action: 'read', task_id: 'task_1' }), false);
|
|
86
|
+
assert.equal(isTaskWaitToolCall('shell', { action: 'wait' }), false);
|
|
87
|
+
});
|
|
@@ -166,6 +166,7 @@ export function createLifecycleApi(deps) {
|
|
|
166
166
|
// them later). Never loads the memory runtime just for this — a null
|
|
167
167
|
// module promise means memory was never used this run, so skip.
|
|
168
168
|
async function ingestSessionIntoMemory(session) {
|
|
169
|
+
if (process.env.MIXDOG_DISABLE_MEMORY_INGEST === '1') return;
|
|
169
170
|
try {
|
|
170
171
|
const messages = Array.isArray(session?.messages) ? session.messages : [];
|
|
171
172
|
if (!session?.id || messages.length === 0) return;
|
|
@@ -14,6 +14,7 @@ import { traceTurnTiming } from '../runtime/agent/orchestrator/agent-trace.mjs';
|
|
|
14
14
|
import { beginTurnSnapshot, cancelTurnSnapshot, completeTurnSnapshot } from '../runtime/shared/turn-snapshot.mjs';
|
|
15
15
|
import { isVisibleStreamProgress } from '../runtime/shared/stream-progress.mjs';
|
|
16
16
|
import { runAbortable, settleWithin, throwIfAborted } from '../runtime/shared/abort-race.mjs';
|
|
17
|
+
import { interruptTaskWaitForSession } from '../runtime/agent/orchestrator/session/task-wait-control.mjs';
|
|
17
18
|
|
|
18
19
|
export function splitToolStatusCounts(rows) {
|
|
19
20
|
const list = Array.isArray(rows) ? rows : [];
|
|
@@ -500,6 +501,10 @@ export function createSessionTurnApi(deps) {
|
|
|
500
501
|
agentStatus() {
|
|
501
502
|
return agentStatusState();
|
|
502
503
|
},
|
|
504
|
+
interruptTaskWait(reason = 'user-message') {
|
|
505
|
+
const sessionId = getSession()?.id || getReservedSessionId?.() || '';
|
|
506
|
+
return interruptTaskWaitForSession(sessionId, reason);
|
|
507
|
+
},
|
|
503
508
|
// Raw model-message read for LIVE transcript consumers (remote agent
|
|
504
509
|
// handoff, daemon session.read). Answers from the in-memory session so a
|
|
505
510
|
// just-committed final turn is readable immediately — the debounced disk
|