@cortexkit/pi-magic-context 0.44.1 → 0.44.2
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/dist/historian-calibration-extension.js +17 -0
- package/dist/{index-mwpk0wn9.js → index-esnhck8v.js} +14695 -14254
- package/dist/{index-nm3c0sn8.js → index-gsff513e.js} +159 -39
- package/dist/index-n8kg008j.js +245 -0
- package/dist/index.js +1313 -439
- package/dist/{read-session-chunk-ka7n4kah.js → read-session-chunk-zhtcx9f1.js} +1 -1
- package/dist/subagent-entry.js +18 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -49,6 +49,7 @@ import {
|
|
|
49
49
|
nextDueAtMs,
|
|
50
50
|
resolveModelConfigValue,
|
|
51
51
|
DEFAULT_HISTORIAN_TIMEOUT_MS,
|
|
52
|
+
DREAM_TASK_PROMOTION_DEFAULTS,
|
|
52
53
|
LIVE_RELOAD_CONFIG_PATHS,
|
|
53
54
|
getProtectedTokensTierOverrides,
|
|
54
55
|
FAIL_CLOSED_DOCTOR_COMMAND,
|
|
@@ -59,6 +60,7 @@ import {
|
|
|
59
60
|
createFailClosedController,
|
|
60
61
|
ProjectIdentityError,
|
|
61
62
|
resolveProjectIdentityStrict,
|
|
63
|
+
isUserHomeDirectory,
|
|
62
64
|
resolveProjectIdentity,
|
|
63
65
|
isUsableProjectIdentity,
|
|
64
66
|
resolveProjectIdentityForSession,
|
|
@@ -66,6 +68,17 @@ import {
|
|
|
66
68
|
scheduleAfterBootQuiet,
|
|
67
69
|
clearCompressionDepth,
|
|
68
70
|
clearCompressionDepthRange,
|
|
71
|
+
formatSynapseLaneDescriptor,
|
|
72
|
+
buildCanonicalChunkTextFromFts,
|
|
73
|
+
buildCompartmentSummaryFallbackText,
|
|
74
|
+
canonicalizeInMemoryChunkTextForEmbedding,
|
|
75
|
+
chunkCanonicalText,
|
|
76
|
+
chunkEmbeddingWindowsAreCurrent,
|
|
77
|
+
replaceCompartmentChunkEmbeddings,
|
|
78
|
+
deleteChunkEmbedBackoffForSession,
|
|
79
|
+
readSessionActivity,
|
|
80
|
+
observeSessionActivity,
|
|
81
|
+
backfillSessionActivity,
|
|
69
82
|
getMessageIndexSourceIdentity,
|
|
70
83
|
isMessageIndexSourceCurrent,
|
|
71
84
|
getLastIndexedOrdinal,
|
|
@@ -168,6 +181,12 @@ import {
|
|
|
168
181
|
retryPendingSessionCleanups,
|
|
169
182
|
retryPendingRustSessionCleanupsForProject,
|
|
170
183
|
recordSessionProjectIdentity,
|
|
184
|
+
embedPauseBySession,
|
|
185
|
+
embedRunStateBySession,
|
|
186
|
+
autoEmbedAttemptedBySession,
|
|
187
|
+
autoEmbedIdentityBySession,
|
|
188
|
+
invalidateAutoEmbedSession,
|
|
189
|
+
getEmbedDrainUiStatus,
|
|
171
190
|
piHarnessKindFromExecutable,
|
|
172
191
|
COMPARTMENT_LEASE_RENEWAL_MS,
|
|
173
192
|
acquireCompartmentLease,
|
|
@@ -231,6 +250,7 @@ import {
|
|
|
231
250
|
runSqliteOptimize,
|
|
232
251
|
openDatabase,
|
|
233
252
|
openDatabaseAsync,
|
|
253
|
+
closeDatabase,
|
|
234
254
|
queueM0Mutation,
|
|
235
255
|
getMaxM0MutationId,
|
|
236
256
|
queueMemoryMutation,
|
|
@@ -325,13 +345,6 @@ import {
|
|
|
325
345
|
peekCurateCategoryScope,
|
|
326
346
|
beginCurateCategoryRun,
|
|
327
347
|
curateTaskStateAfterSuccess,
|
|
328
|
-
formatSynapseLaneDescriptor,
|
|
329
|
-
buildCanonicalChunkTextFromFts,
|
|
330
|
-
buildCompartmentSummaryFallbackText,
|
|
331
|
-
canonicalizeInMemoryChunkTextForEmbedding,
|
|
332
|
-
chunkCanonicalText,
|
|
333
|
-
chunkEmbeddingWindowsAreCurrent,
|
|
334
|
-
replaceCompartmentChunkEmbeddings,
|
|
335
348
|
cosineSimilarity,
|
|
336
349
|
GIT_SWEEP_LEASE_RENEWAL_MS,
|
|
337
350
|
acquireGitSweepLease,
|
|
@@ -353,6 +366,7 @@ import {
|
|
|
353
366
|
drainCommitBacklogForProject,
|
|
354
367
|
embedSessionCompartmentChunks,
|
|
355
368
|
getEmbeddingCoverageStatus,
|
|
369
|
+
getAutoEmbeddingSessionCoverage,
|
|
356
370
|
promoteSessionFactsDurable,
|
|
357
371
|
embedPromotedFacts,
|
|
358
372
|
recordMemoryMapping,
|
|
@@ -373,6 +387,7 @@ import {
|
|
|
373
387
|
embedUnembeddedCommits,
|
|
374
388
|
loadPiConfig,
|
|
375
389
|
loadPiConfigDetailed,
|
|
390
|
+
unregisterPiProjectEmbeddings,
|
|
376
391
|
ensureProjectRegisteredFromPiDirectory,
|
|
377
392
|
resolvePiHarnessDetection,
|
|
378
393
|
resolvePiHarnessKind,
|
|
@@ -408,7 +423,7 @@ import {
|
|
|
408
423
|
registerTodoStateLifecycle,
|
|
409
424
|
syncCtxMemoryToolEnabled,
|
|
410
425
|
registerMagicContextTools
|
|
411
|
-
} from "./index-
|
|
426
|
+
} from "./index-esnhck8v.js";
|
|
412
427
|
import {
|
|
413
428
|
CTX_REDUCE_KEEP,
|
|
414
429
|
newestCtxReduceTagNumbers,
|
|
@@ -496,6 +511,12 @@ import {
|
|
|
496
511
|
buildTrueRawTokenIndex,
|
|
497
512
|
computeRawRangeFingerprint,
|
|
498
513
|
invalidateTrueRawTokenCache,
|
|
514
|
+
DreamTokenBudgetExceeded,
|
|
515
|
+
TOKEN_BUDGET_FINALIZE_MESSAGE,
|
|
516
|
+
TOKEN_BUDGET_TOOL_REFUSAL,
|
|
517
|
+
registerBudgetFinalizeChild,
|
|
518
|
+
releaseBudgetFinalizeChild,
|
|
519
|
+
createDreamTokenBudget,
|
|
499
520
|
droppedInputMarker,
|
|
500
521
|
createDroppedInputGuard,
|
|
501
522
|
isEditTool,
|
|
@@ -522,10 +543,14 @@ import {
|
|
|
522
543
|
getRawSessionTagKeysThrough2,
|
|
523
544
|
getLegacyProtectedTailStartOrdinal2,
|
|
524
545
|
readSessionChunk2
|
|
525
|
-
} from "./index-
|
|
546
|
+
} from "./index-gsff513e.js";
|
|
526
547
|
import {
|
|
527
548
|
pushNotification2
|
|
528
549
|
} from "./index-b3eqj1g6.js";
|
|
550
|
+
import {
|
|
551
|
+
subagentStepCap,
|
|
552
|
+
SubagentTelemetry
|
|
553
|
+
} from "./index-n8kg008j.js";
|
|
529
554
|
import"./index-5fw91enm.js";
|
|
530
555
|
import {
|
|
531
556
|
logSlowWriteTransaction
|
|
@@ -822,6 +847,15 @@ var CANONICAL_DREAM_TASKS = [
|
|
|
822
847
|
"promote-primers",
|
|
823
848
|
"refresh-primers"
|
|
824
849
|
];
|
|
850
|
+
var DREAM_TOOL_LOOP_TOKEN_BUDGETS = {
|
|
851
|
+
"map-memories": 1500000,
|
|
852
|
+
verify: 1700000,
|
|
853
|
+
"verify-broad": 1700000,
|
|
854
|
+
curate: 1500000,
|
|
855
|
+
retrospective: 300000,
|
|
856
|
+
"maintain-docs": 1600000,
|
|
857
|
+
"refresh-primers": 350000
|
|
858
|
+
};
|
|
825
859
|
var DREAM_TASK_CAPABILITIES = {
|
|
826
860
|
"map-memories": {
|
|
827
861
|
requiresTools: true,
|
|
@@ -934,6 +968,7 @@ function buildDreamTaskRuntimeConfigs(dreamer, harness, language, muralModel) {
|
|
|
934
968
|
thinkingLevel: harness === "pi" ? resolved.primary?.qualifier : undefined,
|
|
935
969
|
language,
|
|
936
970
|
timeoutMinutes: resolved.timeoutMinutes ?? 20,
|
|
971
|
+
tokenBudget: task in DREAM_TOOL_LOOP_TOKEN_BUDGETS ? dreamer?.tasks?.[task]?.token_budget ?? DREAM_TOOL_LOOP_TOKEN_BUDGETS[task] : undefined,
|
|
937
972
|
promotionThreshold: resolved.promotionThreshold,
|
|
938
973
|
docsMaxTokens: task === "maintain-docs" ? dreamer?.tasks?.["maintain-docs"]?.max_tokens ?? 12000 : undefined,
|
|
939
974
|
retrospectiveRecencyDays: task === "retrospective" ? dreamer?.tasks?.retrospective?.recency_days ?? 30 : undefined
|
|
@@ -1527,7 +1562,7 @@ async function runSessionProjectBackfill(db, source, options = {}) {
|
|
|
1527
1562
|
const startedAt = performance.now();
|
|
1528
1563
|
const harness = getHarness();
|
|
1529
1564
|
const now = options.now ?? Date.now;
|
|
1530
|
-
const resolveIdentity = options.resolveIdentity ??
|
|
1565
|
+
const resolveIdentity = options.resolveIdentity ?? ((directory) => resolveProjectIdentityForSession(directory, options.allowHomeProject));
|
|
1531
1566
|
const yieldFn = options.yieldFn ?? defaultYieldFn;
|
|
1532
1567
|
const holderId = options.holderId ?? randomUUID();
|
|
1533
1568
|
const readPage = createPageReader(source);
|
|
@@ -1541,7 +1576,8 @@ async function runSessionProjectBackfill(db, source, options = {}) {
|
|
|
1541
1576
|
skippedEmptyDirectories: 0,
|
|
1542
1577
|
durationMs: 0
|
|
1543
1578
|
};
|
|
1544
|
-
const
|
|
1579
|
+
const leaseKey = options.leaseKey ?? harness;
|
|
1580
|
+
const leaseStatus = acquireBackfillLease(db, leaseKey, holderId, now());
|
|
1545
1581
|
if (leaseStatus !== "acquired") {
|
|
1546
1582
|
result.status = leaseStatus;
|
|
1547
1583
|
result.durationMs = performance.now() - startedAt;
|
|
@@ -1553,10 +1589,10 @@ async function runSessionProjectBackfill(db, source, options = {}) {
|
|
|
1553
1589
|
return result;
|
|
1554
1590
|
};
|
|
1555
1591
|
const yieldAndRenew = async () => {
|
|
1556
|
-
if (!renewBackfillLease(db,
|
|
1592
|
+
if (!renewBackfillLease(db, leaseKey, holderId, now()))
|
|
1557
1593
|
return false;
|
|
1558
1594
|
await yieldFn();
|
|
1559
|
-
return renewBackfillLease(db,
|
|
1595
|
+
return renewBackfillLease(db, leaseKey, holderId, now());
|
|
1560
1596
|
};
|
|
1561
1597
|
const existenceCache = new Map;
|
|
1562
1598
|
const identityCache = new Map;
|
|
@@ -1570,7 +1606,7 @@ async function runSessionProjectBackfill(db, source, options = {}) {
|
|
|
1570
1606
|
sourcePage = await readPage(afterSessionId, SESSION_PAGE_SIZE);
|
|
1571
1607
|
} catch (error) {
|
|
1572
1608
|
try {
|
|
1573
|
-
if (!markBackfillRetryPending(db,
|
|
1609
|
+
if (!markBackfillRetryPending(db, leaseKey, holderId, now())) {
|
|
1574
1610
|
log("[session-projects] backfill lease changed before failure cleanup");
|
|
1575
1611
|
}
|
|
1576
1612
|
} catch (releaseError) {
|
|
@@ -1595,7 +1631,7 @@ async function runSessionProjectBackfill(db, source, options = {}) {
|
|
|
1595
1631
|
result.alreadyMappedSessions += 1;
|
|
1596
1632
|
} else {
|
|
1597
1633
|
result.unmappedSessions += 1;
|
|
1598
|
-
if (!session.directory) {
|
|
1634
|
+
if (!session.directory || !options.allowHomeProject && isUserHomeDirectory(session.directory)) {
|
|
1599
1635
|
result.skippedEmptyDirectories += 1;
|
|
1600
1636
|
} else {
|
|
1601
1637
|
const stillExists = existenceCache.get(session.directory) ?? existsSync(session.directory);
|
|
@@ -1613,13 +1649,18 @@ async function runSessionProjectBackfill(db, source, options = {}) {
|
|
|
1613
1649
|
}
|
|
1614
1650
|
identity = await resolveIdentity(session.directory);
|
|
1615
1651
|
identityResolutionsSinceYield += 1;
|
|
1616
|
-
if (!renewBackfillLease(db,
|
|
1652
|
+
if (!renewBackfillLease(db, leaseKey, holderId, now())) {
|
|
1617
1653
|
return finishWithLostLease();
|
|
1618
1654
|
}
|
|
1619
|
-
|
|
1655
|
+
if (identity)
|
|
1656
|
+
identityCache.set(session.directory, identity);
|
|
1657
|
+
}
|
|
1658
|
+
if (identity) {
|
|
1659
|
+
recordSessionProjectIdentity(db, session.sessionId, identity);
|
|
1660
|
+
result.backfilledSessions += 1;
|
|
1661
|
+
} else {
|
|
1662
|
+
result.skippedEmptyDirectories += 1;
|
|
1620
1663
|
}
|
|
1621
|
-
recordSessionProjectIdentity(db, session.sessionId, identity);
|
|
1622
|
-
result.backfilledSessions += 1;
|
|
1623
1664
|
}
|
|
1624
1665
|
}
|
|
1625
1666
|
}
|
|
@@ -1635,11 +1676,11 @@ async function runSessionProjectBackfill(db, source, options = {}) {
|
|
|
1635
1676
|
}
|
|
1636
1677
|
const hasSkippedSessions = result.skippedDeadDirectories > 0 || result.skippedEmptyDirectories > 0;
|
|
1637
1678
|
if (hasSkippedSessions) {
|
|
1638
|
-
if (!markBackfillRetryPending(db,
|
|
1679
|
+
if (!markBackfillRetryPending(db, leaseKey, holderId, now())) {
|
|
1639
1680
|
return finishWithLostLease();
|
|
1640
1681
|
}
|
|
1641
1682
|
result.status = "retry_pending";
|
|
1642
|
-
} else if (!markBackfillCompleted(db,
|
|
1683
|
+
} else if (!markBackfillCompleted(db, leaseKey, holderId, now())) {
|
|
1643
1684
|
return finishWithLostLease();
|
|
1644
1685
|
}
|
|
1645
1686
|
result.durationMs = performance.now() - startedAt;
|
|
@@ -1709,7 +1750,7 @@ function countFailures(db) {
|
|
|
1709
1750
|
function classifyBackfillError(error) {
|
|
1710
1751
|
if (error instanceof ProjectIdentityError) {
|
|
1711
1752
|
return {
|
|
1712
|
-
errorClass: error.errorClass === "dubious_ownership" ? "unknown" : error.errorClass,
|
|
1753
|
+
errorClass: error.errorClass === "dubious_ownership" || error.errorClass === "home_project_disabled" || error.errorClass === "git_identity_unavailable" ? "unknown" : error.errorClass,
|
|
1713
1754
|
errorMessage: error.message
|
|
1714
1755
|
};
|
|
1715
1756
|
}
|
|
@@ -2313,10 +2354,12 @@ var SMART_NOTE_CHECK_LIVENESS_RECHECK_MS = 24 * 60 * 60 * 1000;
|
|
|
2313
2354
|
class SmartNoteNetworkError extends Error {
|
|
2314
2355
|
isSmartNoteNetworkError = true;
|
|
2315
2356
|
terminal;
|
|
2357
|
+
persistent;
|
|
2316
2358
|
constructor(message, options = {}) {
|
|
2317
2359
|
super(message);
|
|
2318
2360
|
this.name = "SmartNoteNetworkError";
|
|
2319
2361
|
this.terminal = options.terminal ?? false;
|
|
2362
|
+
this.persistent = options.persistent ?? false;
|
|
2320
2363
|
}
|
|
2321
2364
|
}
|
|
2322
2365
|
|
|
@@ -2438,6 +2481,7 @@ function storeCompiledSmartNoteCheck(db, args) {
|
|
|
2438
2481
|
check_failure_count = 0,
|
|
2439
2482
|
check_network_failure_count = 0,
|
|
2440
2483
|
check_quarantined_until = NULL,
|
|
2484
|
+
ready_reason = NULL,
|
|
2441
2485
|
check_next_due_at = ?,
|
|
2442
2486
|
check_compiled_at = ?,
|
|
2443
2487
|
check_false_since_at = COALESCE(check_false_since_at, ?),
|
|
@@ -2486,15 +2530,17 @@ function markSmartNoteLivenessChecked(db, noteId, now) {
|
|
|
2486
2530
|
function markSmartNoteCheckStatus(db, noteId, status, now) {
|
|
2487
2531
|
db.prepare(`UPDATE notes SET check_status = ?, updated_at = ? WHERE id = ? AND type = 'smart'`).run(status, now, noteId);
|
|
2488
2532
|
}
|
|
2489
|
-
function markSmartNoteCompilationFailure(db, noteId, now, maxFailures) {
|
|
2533
|
+
function markSmartNoteCompilationFailure(db, noteId, now, maxFailures, error, persistent) {
|
|
2490
2534
|
const failureCount = readFailureCount(db, noteId, "check_failure_count") + 1;
|
|
2491
|
-
const status = failureCount >= maxFailures ? "fallback" : "uncompiled";
|
|
2535
|
+
const status = persistent ? "uncompiled" : failureCount >= maxFailures ? "fallback" : "uncompiled";
|
|
2536
|
+
const nextDueAt = now + (persistent ? 7 * 24 * 60 * 60 * 1000 : backoffMs(failureCount));
|
|
2492
2537
|
db.prepare(`UPDATE notes
|
|
2493
2538
|
SET check_failure_count = ?,
|
|
2494
2539
|
check_status = ?,
|
|
2495
2540
|
check_next_due_at = ?,
|
|
2541
|
+
ready_reason = ?,
|
|
2496
2542
|
updated_at = ?
|
|
2497
|
-
WHERE id = ? AND type = 'smart'`).run(failureCount, status,
|
|
2543
|
+
WHERE id = ? AND type = 'smart'`).run(failureCount, status, nextDueAt, persistent ? `Condition can't be checked: ${error}; rewrite it` : null, now, noteId);
|
|
2498
2544
|
}
|
|
2499
2545
|
function readFailureCount(db, noteId, column) {
|
|
2500
2546
|
if (column !== "check_failure_count" && column !== "check_network_failure_count")
|
|
@@ -2542,7 +2588,10 @@ function countCompartmentsSince(db, projectPath, since) {
|
|
|
2542
2588
|
return row?.cnt ?? 0;
|
|
2543
2589
|
}
|
|
2544
2590
|
function countProjectSessionsSince(db, projectPath, since) {
|
|
2545
|
-
const row = since === null ? db.prepare("SELECT COUNT(*) AS cnt FROM session_projects WHERE project_path = ?").get(projectPath) : db.prepare(
|
|
2591
|
+
const row = since === null ? db.prepare("SELECT COUNT(*) AS cnt FROM session_projects WHERE project_path = ?").get(projectPath) : db.prepare(`SELECT COUNT(*) AS cnt FROM session_projects sp
|
|
2592
|
+
JOIN schema_migrations_meta activity
|
|
2593
|
+
ON activity.key = 'retrospective_activity:' || sp.session_id
|
|
2594
|
+
WHERE sp.project_path = ? AND CAST(activity.value AS INTEGER) > ?`).get(projectPath, since);
|
|
2546
2595
|
return row?.cnt ?? 0;
|
|
2547
2596
|
}
|
|
2548
2597
|
function countMappedMemories(db, projectPath) {
|
|
@@ -2651,12 +2700,14 @@ function getDreamTaskBacklog(db, projectPath, task, options = {}) {
|
|
|
2651
2700
|
};
|
|
2652
2701
|
}
|
|
2653
2702
|
case "retrospective": {
|
|
2654
|
-
const
|
|
2703
|
+
const watermark = options.retrospectiveWatermarkMs !== undefined ? options.retrospectiveWatermarkMs : getTaskScheduleState(db, projectPath, task)?.retrospectiveWatermarkMs;
|
|
2704
|
+
const pending = countProjectSessionsSince(db, projectPath, watermark ?? null);
|
|
2655
2705
|
return { pending, total: pending };
|
|
2656
2706
|
}
|
|
2657
2707
|
case "maintain-docs": {
|
|
2658
2708
|
const total = countCompartmentsSince(db, projectPath, 0);
|
|
2659
|
-
const
|
|
2709
|
+
const lastRunAt = options.lastRunAt !== undefined ? options.lastRunAt : getTaskScheduleState(db, projectPath, task)?.lastRunAt;
|
|
2710
|
+
const pending = countCompartmentsSince(db, projectPath, lastRunAt ?? 0);
|
|
2660
2711
|
return { pending, total };
|
|
2661
2712
|
}
|
|
2662
2713
|
case "evaluate-smart-notes": {
|
|
@@ -2711,7 +2762,7 @@ function evaluateTaskGate(task, ctx) {
|
|
|
2711
2762
|
case "review-user-memories":
|
|
2712
2763
|
return getUserMemoryCandidates(db).length >= ctx.promotionThreshold;
|
|
2713
2764
|
case "promote-primers":
|
|
2714
|
-
return countPrimerCandidatesForProject(db, project) >= (ctx.promotionThreshold ??
|
|
2765
|
+
return countPrimerCandidatesForProject(db, project) >= (ctx.promotionThreshold ?? DREAM_TASK_PROMOTION_DEFAULTS["promote-primers"]);
|
|
2715
2766
|
case "refresh-primers":
|
|
2716
2767
|
return getActivePrimers(db, project).some((primer) => !primer.answer.trim() || primer.answerRefreshedAt == null || (primer.lastObservedAt ?? 0) > primer.answerRefreshedAt);
|
|
2717
2768
|
default: {
|
|
@@ -2758,6 +2809,11 @@ var USER_FACING_FAILURES = {
|
|
|
2758
2809
|
sentence: "Memory maintenance stopped at its hidden agent step limit.",
|
|
2759
2810
|
action: "This task needs less work per run; changing the model connection will not help."
|
|
2760
2811
|
},
|
|
2812
|
+
dream_token_budget: {
|
|
2813
|
+
code: "MC-D11",
|
|
2814
|
+
sentence: "Memory maintenance reached its prompt-token budget.",
|
|
2815
|
+
action: "The unfinished items will be retried on the next run."
|
|
2816
|
+
},
|
|
2761
2817
|
dream_empty_completion: {
|
|
2762
2818
|
code: "MC-D03",
|
|
2763
2819
|
sentence: "Memory maintenance received no usable response.",
|
|
@@ -2975,6 +3031,7 @@ var DREAM_FAILURE_KEYS = {
|
|
|
2975
3031
|
provider_timeout: "dream_provider_timeout",
|
|
2976
3032
|
provider_error: "dream_provider_error",
|
|
2977
3033
|
step_limit: "dream_step_limit",
|
|
3034
|
+
token_budget: "dream_token_budget",
|
|
2978
3035
|
empty_completion: "dream_empty_completion",
|
|
2979
3036
|
no_models: "dream_no_models",
|
|
2980
3037
|
child_aborted: "dream_child_aborted",
|
|
@@ -3042,7 +3099,7 @@ function openOpenCodeDb() {
|
|
|
3042
3099
|
|
|
3043
3100
|
// ../plugin/src/features/magic-context/dreamer/task-executor.ts
|
|
3044
3101
|
import { createHash as createHash10 } from "node:crypto";
|
|
3045
|
-
import { existsSync as existsSync6 } from "node:fs";
|
|
3102
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
|
|
3046
3103
|
|
|
3047
3104
|
// ../plugin/src/agents/dreamer.ts
|
|
3048
3105
|
var DREAMER_AGENT = "dreamer";
|
|
@@ -3175,7 +3232,7 @@ var READ_TOOLS = ["read", "grep", "glob"];
|
|
|
3175
3232
|
var AGENT_TOOLS = {
|
|
3176
3233
|
[HIDDEN_HISTORIAN_AGENT]: [],
|
|
3177
3234
|
[HIDDEN_DREAMER_AGENT]: [],
|
|
3178
|
-
[HIDDEN_CURATE_AGENT]: ["ctx_memory"
|
|
3235
|
+
[HIDDEN_CURATE_AGENT]: ["ctx_memory"],
|
|
3179
3236
|
[DREAMER_MEMORY_MAPPER_AGENT]: READ_TOOLS,
|
|
3180
3237
|
[DREAMER_PRIMER_INVESTIGATOR_AGENT]: [...READ_TOOLS, "ctx_search"],
|
|
3181
3238
|
[DREAMER_RETROSPECTIVE_AGENT]: ["ctx_search"]
|
|
@@ -3424,7 +3481,7 @@ async function abortChildRun(client, sessionId) {
|
|
|
3424
3481
|
}
|
|
3425
3482
|
}
|
|
3426
3483
|
function isNonRetryable(error, externalSignal) {
|
|
3427
|
-
if (externalSignal?.aborted || error instanceof HiddenAgentStepLimit)
|
|
3484
|
+
if (externalSignal?.aborted || error instanceof HiddenAgentStepLimit || error instanceof DreamTokenBudgetExceeded)
|
|
3428
3485
|
return true;
|
|
3429
3486
|
if (error instanceof Error) {
|
|
3430
3487
|
if (error.name === "AbortError")
|
|
@@ -3450,6 +3507,8 @@ function classifyPromptFailure(error, phase, externalSignal) {
|
|
|
3450
3507
|
const message = extractMessage(error);
|
|
3451
3508
|
if (error instanceof HiddenAgentStepLimit)
|
|
3452
3509
|
return "step_limit";
|
|
3510
|
+
if (error instanceof DreamTokenBudgetExceeded)
|
|
3511
|
+
return "token_budget";
|
|
3453
3512
|
if (externalSignal?.aborted || message === "prompt aborted by external signal") {
|
|
3454
3513
|
return "child_aborted";
|
|
3455
3514
|
}
|
|
@@ -3700,10 +3759,115 @@ function normalizeSDKResponse(response, fallback, options) {
|
|
|
3700
3759
|
}
|
|
3701
3760
|
return fallback;
|
|
3702
3761
|
}
|
|
3762
|
+
// ../plugin/src/features/magic-context/subagent-token-capture.ts
|
|
3763
|
+
function asNumber(value) {
|
|
3764
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
3765
|
+
}
|
|
3766
|
+
function tokenObjectFromMessage(message) {
|
|
3767
|
+
const info = message.info;
|
|
3768
|
+
if (info && typeof info === "object") {
|
|
3769
|
+
const tokens = info.tokens;
|
|
3770
|
+
if (tokens && typeof tokens === "object")
|
|
3771
|
+
return tokens;
|
|
3772
|
+
}
|
|
3773
|
+
const tokens = message.tokens;
|
|
3774
|
+
if (tokens && typeof tokens === "object")
|
|
3775
|
+
return tokens;
|
|
3776
|
+
const usage = message.usage;
|
|
3777
|
+
if (usage && typeof usage === "object")
|
|
3778
|
+
return usage;
|
|
3779
|
+
return null;
|
|
3780
|
+
}
|
|
3781
|
+
function isAssistantMessage(message) {
|
|
3782
|
+
if (!message || typeof message !== "object")
|
|
3783
|
+
return false;
|
|
3784
|
+
const record = message;
|
|
3785
|
+
const info = record.info;
|
|
3786
|
+
if (info && typeof info === "object") {
|
|
3787
|
+
return info.role === "assistant";
|
|
3788
|
+
}
|
|
3789
|
+
return record.role === "assistant";
|
|
3790
|
+
}
|
|
3791
|
+
function modelFromMessage(message) {
|
|
3792
|
+
const info = message.info;
|
|
3793
|
+
const source = info && typeof info === "object" ? info : message;
|
|
3794
|
+
return {
|
|
3795
|
+
providerId: typeof source.providerID === "string" ? source.providerID : typeof source.providerId === "string" ? source.providerId : null,
|
|
3796
|
+
modelId: typeof source.modelID === "string" ? source.modelID : typeof source.modelId === "string" ? source.modelId : null
|
|
3797
|
+
};
|
|
3798
|
+
}
|
|
3799
|
+
function failedInvocationStatus(error) {
|
|
3800
|
+
const message = describeError(error).brief;
|
|
3801
|
+
if (/timed out after \d+ms|prompt timed out/i.test(message))
|
|
3802
|
+
return "timed_out";
|
|
3803
|
+
if (error !== null && typeof error === "object" && error.name === "TimeoutError") {
|
|
3804
|
+
return "timed_out";
|
|
3805
|
+
}
|
|
3806
|
+
if (/length-capped|no (?:assistant )?output|empty (?:assistant )?(?:output|text)/i.test(message)) {
|
|
3807
|
+
return "empty";
|
|
3808
|
+
}
|
|
3809
|
+
return "failed";
|
|
3810
|
+
}
|
|
3811
|
+
function emptyTokenTotals() {
|
|
3812
|
+
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
3813
|
+
}
|
|
3814
|
+
function sumTokensFromChildMessages(messages) {
|
|
3815
|
+
const totals = emptyTokenTotals();
|
|
3816
|
+
for (const message of messages) {
|
|
3817
|
+
if (!isAssistantMessage(message))
|
|
3818
|
+
continue;
|
|
3819
|
+
const tokens = tokenObjectFromMessage(message);
|
|
3820
|
+
if (!tokens)
|
|
3821
|
+
continue;
|
|
3822
|
+
const cache = tokens.cache && typeof tokens.cache === "object" ? tokens.cache : {};
|
|
3823
|
+
totals.input += asNumber(tokens.input);
|
|
3824
|
+
totals.output += asNumber(tokens.output);
|
|
3825
|
+
totals.cacheRead += asNumber(cache.read ?? tokens.cacheRead ?? tokens.cache_read);
|
|
3826
|
+
totals.cacheWrite += asNumber(cache.write ?? tokens.cacheWrite ?? tokens.cache_write);
|
|
3827
|
+
}
|
|
3828
|
+
return totals;
|
|
3829
|
+
}
|
|
3830
|
+
function findLastAssistantModel(messages) {
|
|
3831
|
+
for (let index = messages.length - 1;index >= 0; index -= 1) {
|
|
3832
|
+
const message = messages[index];
|
|
3833
|
+
if (isAssistantMessage(message))
|
|
3834
|
+
return modelFromMessage(message);
|
|
3835
|
+
}
|
|
3836
|
+
return { providerId: null, modelId: null };
|
|
3837
|
+
}
|
|
3838
|
+
function recordChildInvocation(input) {
|
|
3839
|
+
if (!input.db)
|
|
3840
|
+
return null;
|
|
3841
|
+
const tokens = input.tokens ?? sumTokensFromChildMessages(input.messages ?? []);
|
|
3842
|
+
const model = input.providerId !== undefined || input.modelId !== undefined ? { providerId: input.providerId ?? null, modelId: input.modelId ?? null } : findLastAssistantModel(input.messages ?? []);
|
|
3843
|
+
try {
|
|
3844
|
+
return recordSubagentInvocation(input.db, {
|
|
3845
|
+
sessionId: input.parentSessionId,
|
|
3846
|
+
harness: input.harness,
|
|
3847
|
+
subagent: input.subagent,
|
|
3848
|
+
task: input.task ?? null,
|
|
3849
|
+
providerId: model.providerId,
|
|
3850
|
+
modelId: model.modelId,
|
|
3851
|
+
startedAt: input.startedAt,
|
|
3852
|
+
endedAt: input.endedAt ?? Date.now(),
|
|
3853
|
+
status: input.status,
|
|
3854
|
+
inputTokens: tokens.input,
|
|
3855
|
+
outputTokens: tokens.output,
|
|
3856
|
+
cacheReadTokens: tokens.cacheRead,
|
|
3857
|
+
cacheWriteTokens: tokens.cacheWrite,
|
|
3858
|
+
error: input.error ? describeError(input.error).brief : null,
|
|
3859
|
+
parentInvocationId: input.parentInvocationId ?? null
|
|
3860
|
+
});
|
|
3861
|
+
} catch (error) {
|
|
3862
|
+
sessionLog(input.parentSessionId, "subagent token accounting failed:", describeError(error).brief);
|
|
3863
|
+
return null;
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3866
|
+
|
|
3703
3867
|
// ../plugin/src/shared/prompt-async-transport.ts
|
|
3704
3868
|
var DEFAULT_POLL_INTERVAL_MS = 1000;
|
|
3705
3869
|
var DEFAULT_START_GRACE_MS = 30000;
|
|
3706
|
-
var MESSAGE_WINDOW =
|
|
3870
|
+
var MESSAGE_WINDOW = 100;
|
|
3707
3871
|
function sessionApi(client) {
|
|
3708
3872
|
return client?.session;
|
|
3709
3873
|
}
|
|
@@ -3714,7 +3878,8 @@ function supportsPromptAsync(client) {
|
|
|
3714
3878
|
function createPromptAsyncTransport(client, childSessionId, options = {}) {
|
|
3715
3879
|
if (!supportsPromptAsync(client))
|
|
3716
3880
|
return;
|
|
3717
|
-
|
|
3881
|
+
const budgetGuard = options.tokenBudget ? createDreamTokenBudget(options.tokenBudget) : undefined;
|
|
3882
|
+
return Object.assign((request) => promptAsyncAndWaitForIdle(client, request, { ...options, budgetGuard }), { childSessionId });
|
|
3718
3883
|
}
|
|
3719
3884
|
function infoOf(message) {
|
|
3720
3885
|
const info = message?.info;
|
|
@@ -3730,6 +3895,10 @@ function isSettledAssistant(message) {
|
|
|
3730
3895
|
return false;
|
|
3731
3896
|
return info.time?.completed != null || info.error != null || info.finish != null;
|
|
3732
3897
|
}
|
|
3898
|
+
function isTerminalAssistant(message) {
|
|
3899
|
+
const info = infoOf(message);
|
|
3900
|
+
return isSettledAssistant(message) && info.finish !== "tool-calls" && info.finish !== "tool_use" && !(message && typeof message === "object" && Array.isArray(message.parts) && message.parts.some((part) => part.type === "tool"));
|
|
3901
|
+
}
|
|
3733
3902
|
function unwrapData(response) {
|
|
3734
3903
|
if (response && typeof response === "object" && "data" in response) {
|
|
3735
3904
|
return response.data;
|
|
@@ -3807,40 +3976,106 @@ async function promptAsyncAndWaitForIdle(client, request, options = {}) {
|
|
|
3807
3976
|
const signal = request.signal;
|
|
3808
3977
|
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
3809
3978
|
const startGraceMs = options.startGraceMs ?? DEFAULT_START_GRACE_MS;
|
|
3810
|
-
const
|
|
3979
|
+
const budget = options.budgetGuard ?? (options.tokenBudget ? createDreamTokenBudget(options.tokenBudget) : undefined);
|
|
3980
|
+
if (budget?.snapshot().finalizeFired)
|
|
3981
|
+
throw new DreamTokenBudgetExceeded(sessionId, budget.snapshot().spent);
|
|
3982
|
+
const seenUsage = new Set;
|
|
3983
|
+
let finalizing = false;
|
|
3984
|
+
let completionBaseline = new Set((await readMessages(session, sessionId, dir, signal)).map(messageId).filter((id) => id !== null));
|
|
3985
|
+
const initialBaseline = new Set(completionBaseline);
|
|
3811
3986
|
const sent = await session.promptAsync?.(request);
|
|
3812
3987
|
if (sent && typeof sent === "object" && "error" in sent) {
|
|
3813
3988
|
const rejection = sent.error;
|
|
3814
3989
|
if (rejection)
|
|
3815
3990
|
throw new Error(`prompt_async was rejected: ${describeRejection(rejection)}`);
|
|
3816
3991
|
}
|
|
3817
|
-
|
|
3992
|
+
let sentAt = Date.now();
|
|
3818
3993
|
let sawBusy = false;
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3994
|
+
try {
|
|
3995
|
+
for (;; ) {
|
|
3996
|
+
await sleep(pollIntervalMs, signal);
|
|
3997
|
+
const status = await readStatus(session, sessionId, dir, signal);
|
|
3998
|
+
const messages = await readMessages(session, sessionId, dir, signal);
|
|
3999
|
+
if (budget) {
|
|
4000
|
+
for (const message of messages) {
|
|
4001
|
+
const id = messageId(message);
|
|
4002
|
+
if (!id || seenUsage.has(id) || infoOf(message).role !== "assistant")
|
|
4003
|
+
continue;
|
|
4004
|
+
if (initialBaseline.has(id))
|
|
4005
|
+
continue;
|
|
4006
|
+
const tokens = sumTokensFromChildMessages([message]);
|
|
4007
|
+
if (tokens.input + tokens.cacheRead + tokens.cacheWrite === 0)
|
|
4008
|
+
continue;
|
|
4009
|
+
seenUsage.add(id);
|
|
4010
|
+
const decision = budget.charge(tokens.input, tokens.cacheRead, tokens.cacheWrite, isTerminalAssistant(message));
|
|
4011
|
+
options.onBudgetUpdate?.({ ...budget.snapshot(), sessionId });
|
|
4012
|
+
if (decision === "stop") {
|
|
4013
|
+
await session.abort?.({ path: { id: sessionId } });
|
|
4014
|
+
throw new DreamTokenBudgetExceeded(sessionId, budget.snapshot().spent);
|
|
4015
|
+
}
|
|
4016
|
+
if (decision === "finalize") {
|
|
4017
|
+
finalizing = true;
|
|
4018
|
+
registerBudgetFinalizeChild(sessionId, budget);
|
|
4019
|
+
if (!session.abort)
|
|
4020
|
+
throw new DreamTokenBudgetExceeded(sessionId, budget.snapshot().spent);
|
|
4021
|
+
await session.abort({ path: { id: sessionId } });
|
|
4022
|
+
const abortDeadline = Date.now() + startGraceMs;
|
|
4023
|
+
while (Date.now() < abortDeadline) {
|
|
4024
|
+
const state = await readStatus(session, sessionId, dir, signal);
|
|
4025
|
+
if (state === "idle")
|
|
4026
|
+
break;
|
|
4027
|
+
await sleep(pollIntervalMs, signal);
|
|
4028
|
+
}
|
|
4029
|
+
if (await readStatus(session, sessionId, dir, signal) !== "idle") {
|
|
4030
|
+
throw new DreamTokenBudgetExceeded(sessionId, budget.snapshot().spent);
|
|
4031
|
+
}
|
|
4032
|
+
completionBaseline = new Set((await readMessages(session, sessionId, dir, signal)).map(messageId).filter((value) => value !== null));
|
|
4033
|
+
const finalizeResponse = await session.promptAsync?.({
|
|
4034
|
+
...request,
|
|
4035
|
+
body: {
|
|
4036
|
+
...request.body,
|
|
4037
|
+
parts: [{ type: "text", text: TOKEN_BUDGET_FINALIZE_MESSAGE }]
|
|
4038
|
+
}
|
|
4039
|
+
});
|
|
4040
|
+
if (finalizeResponse && typeof finalizeResponse === "object" && "error" in finalizeResponse && finalizeResponse.error) {
|
|
4041
|
+
throw new DreamTokenBudgetExceeded(sessionId, budget.snapshot().spent);
|
|
4042
|
+
}
|
|
4043
|
+
sentAt = Date.now();
|
|
4044
|
+
sawBusy = false;
|
|
4045
|
+
break;
|
|
4046
|
+
}
|
|
4047
|
+
}
|
|
4048
|
+
if (budget.snapshot().hardStopped) {
|
|
4049
|
+
await session.abort?.({ path: { id: sessionId } });
|
|
4050
|
+
throw new DreamTokenBudgetExceeded(sessionId, budget.snapshot().spent);
|
|
4051
|
+
}
|
|
4052
|
+
}
|
|
4053
|
+
if (status === "busy" || status === "retry") {
|
|
4054
|
+
sawBusy = true;
|
|
4055
|
+
continue;
|
|
4056
|
+
}
|
|
4057
|
+
if (status === null)
|
|
4058
|
+
continue;
|
|
4059
|
+
const current = finalizing ? await readMessages(session, sessionId, dir, signal) : messages;
|
|
4060
|
+
const last = current.at(-1);
|
|
4061
|
+
const lastId = messageId(last);
|
|
4062
|
+
if (last && lastId !== null && !completionBaseline.has(lastId) && isSettledAssistant(last))
|
|
3841
4063
|
return;
|
|
3842
|
-
|
|
4064
|
+
const fresh = current.some((message) => {
|
|
4065
|
+
const id = messageId(message);
|
|
4066
|
+
return id !== null && !completionBaseline.has(id);
|
|
4067
|
+
});
|
|
4068
|
+
if (sawBusy && fresh)
|
|
4069
|
+
return;
|
|
4070
|
+
if (Date.now() - sentAt >= startGraceMs) {
|
|
4071
|
+
if (fresh)
|
|
4072
|
+
return;
|
|
4073
|
+
throw new Error(`prompt_async did not start a run in child session ${sessionId} within ${startGraceMs}ms`);
|
|
4074
|
+
}
|
|
3843
4075
|
}
|
|
4076
|
+
} finally {
|
|
4077
|
+
if (budget)
|
|
4078
|
+
releaseBudgetFinalizeChild(sessionId);
|
|
3844
4079
|
}
|
|
3845
4080
|
}
|
|
3846
4081
|
// ../plugin/src/shared/summarize-child-stderr.ts
|
|
@@ -3950,111 +4185,6 @@ function forgetHistorianOutputCap(sessionId) {
|
|
|
3950
4185
|
historianCaps.delete(sessionId);
|
|
3951
4186
|
}
|
|
3952
4187
|
|
|
3953
|
-
// ../plugin/src/features/magic-context/subagent-token-capture.ts
|
|
3954
|
-
function asNumber(value) {
|
|
3955
|
-
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
3956
|
-
}
|
|
3957
|
-
function tokenObjectFromMessage(message) {
|
|
3958
|
-
const info = message.info;
|
|
3959
|
-
if (info && typeof info === "object") {
|
|
3960
|
-
const tokens = info.tokens;
|
|
3961
|
-
if (tokens && typeof tokens === "object")
|
|
3962
|
-
return tokens;
|
|
3963
|
-
}
|
|
3964
|
-
const tokens = message.tokens;
|
|
3965
|
-
if (tokens && typeof tokens === "object")
|
|
3966
|
-
return tokens;
|
|
3967
|
-
const usage = message.usage;
|
|
3968
|
-
if (usage && typeof usage === "object")
|
|
3969
|
-
return usage;
|
|
3970
|
-
return null;
|
|
3971
|
-
}
|
|
3972
|
-
function isAssistantMessage(message) {
|
|
3973
|
-
if (!message || typeof message !== "object")
|
|
3974
|
-
return false;
|
|
3975
|
-
const record = message;
|
|
3976
|
-
const info = record.info;
|
|
3977
|
-
if (info && typeof info === "object") {
|
|
3978
|
-
return info.role === "assistant";
|
|
3979
|
-
}
|
|
3980
|
-
return record.role === "assistant";
|
|
3981
|
-
}
|
|
3982
|
-
function modelFromMessage(message) {
|
|
3983
|
-
const info = message.info;
|
|
3984
|
-
const source = info && typeof info === "object" ? info : message;
|
|
3985
|
-
return {
|
|
3986
|
-
providerId: typeof source.providerID === "string" ? source.providerID : typeof source.providerId === "string" ? source.providerId : null,
|
|
3987
|
-
modelId: typeof source.modelID === "string" ? source.modelID : typeof source.modelId === "string" ? source.modelId : null
|
|
3988
|
-
};
|
|
3989
|
-
}
|
|
3990
|
-
function failedInvocationStatus(error) {
|
|
3991
|
-
const message = describeError(error).brief;
|
|
3992
|
-
if (/timed out after \d+ms|prompt timed out/i.test(message))
|
|
3993
|
-
return "timed_out";
|
|
3994
|
-
if (error !== null && typeof error === "object" && error.name === "TimeoutError") {
|
|
3995
|
-
return "timed_out";
|
|
3996
|
-
}
|
|
3997
|
-
if (/length-capped|no (?:assistant )?output|empty (?:assistant )?(?:output|text)/i.test(message)) {
|
|
3998
|
-
return "empty";
|
|
3999
|
-
}
|
|
4000
|
-
return "failed";
|
|
4001
|
-
}
|
|
4002
|
-
function emptyTokenTotals() {
|
|
4003
|
-
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
4004
|
-
}
|
|
4005
|
-
function sumTokensFromChildMessages(messages) {
|
|
4006
|
-
const totals = emptyTokenTotals();
|
|
4007
|
-
for (const message of messages) {
|
|
4008
|
-
if (!isAssistantMessage(message))
|
|
4009
|
-
continue;
|
|
4010
|
-
const tokens = tokenObjectFromMessage(message);
|
|
4011
|
-
if (!tokens)
|
|
4012
|
-
continue;
|
|
4013
|
-
const cache = tokens.cache && typeof tokens.cache === "object" ? tokens.cache : {};
|
|
4014
|
-
totals.input += asNumber(tokens.input);
|
|
4015
|
-
totals.output += asNumber(tokens.output);
|
|
4016
|
-
totals.cacheRead += asNumber(cache.read ?? tokens.cacheRead ?? tokens.cache_read);
|
|
4017
|
-
totals.cacheWrite += asNumber(cache.write ?? tokens.cacheWrite ?? tokens.cache_write);
|
|
4018
|
-
}
|
|
4019
|
-
return totals;
|
|
4020
|
-
}
|
|
4021
|
-
function findLastAssistantModel(messages) {
|
|
4022
|
-
for (let index = messages.length - 1;index >= 0; index -= 1) {
|
|
4023
|
-
const message = messages[index];
|
|
4024
|
-
if (isAssistantMessage(message))
|
|
4025
|
-
return modelFromMessage(message);
|
|
4026
|
-
}
|
|
4027
|
-
return { providerId: null, modelId: null };
|
|
4028
|
-
}
|
|
4029
|
-
function recordChildInvocation(input) {
|
|
4030
|
-
if (!input.db)
|
|
4031
|
-
return null;
|
|
4032
|
-
const tokens = input.tokens ?? sumTokensFromChildMessages(input.messages ?? []);
|
|
4033
|
-
const model = input.providerId !== undefined || input.modelId !== undefined ? { providerId: input.providerId ?? null, modelId: input.modelId ?? null } : findLastAssistantModel(input.messages ?? []);
|
|
4034
|
-
try {
|
|
4035
|
-
return recordSubagentInvocation(input.db, {
|
|
4036
|
-
sessionId: input.parentSessionId,
|
|
4037
|
-
harness: input.harness,
|
|
4038
|
-
subagent: input.subagent,
|
|
4039
|
-
task: input.task ?? null,
|
|
4040
|
-
providerId: model.providerId,
|
|
4041
|
-
modelId: model.modelId,
|
|
4042
|
-
startedAt: input.startedAt,
|
|
4043
|
-
endedAt: input.endedAt ?? Date.now(),
|
|
4044
|
-
status: input.status,
|
|
4045
|
-
inputTokens: tokens.input,
|
|
4046
|
-
outputTokens: tokens.output,
|
|
4047
|
-
cacheReadTokens: tokens.cacheRead,
|
|
4048
|
-
cacheWriteTokens: tokens.cacheWrite,
|
|
4049
|
-
error: input.error ? describeError(input.error).brief : null,
|
|
4050
|
-
parentInvocationId: input.parentInvocationId ?? null
|
|
4051
|
-
});
|
|
4052
|
-
} catch (error) {
|
|
4053
|
-
sessionLog(input.parentSessionId, "subagent token accounting failed:", describeError(error).brief);
|
|
4054
|
-
return null;
|
|
4055
|
-
}
|
|
4056
|
-
}
|
|
4057
|
-
|
|
4058
4188
|
// ../plugin/src/hooks/magic-context/historian-prompt.generated.ts
|
|
4059
4189
|
var COMPARTMENT_AGENT_SYSTEM_PROMPT = `# Historian
|
|
4060
4190
|
|
|
@@ -6783,7 +6913,7 @@ Project memory uses exactly 5 categories. Every memory belongs to one:
|
|
|
6783
6913
|
var CURATE_SYSTEM_PROMPT = `You are a memory-pool curator for the magic-context system. You run during a scheduled dream window to keep a project's cross-session memory store lean and well-formed.
|
|
6784
6914
|
|
|
6785
6915
|
## Memory operations
|
|
6786
|
-
-
|
|
6916
|
+
- The scoped category snapshot arrives in the first message. Do not list or get it again; track mutation deltas.
|
|
6787
6917
|
- \`action="merge", ids=[N,M,...], content="...", category="..."\` — consolidate duplicates into one canonical memory
|
|
6788
6918
|
- \`action="update", ids=[N], content="...", superseded_by=M\` — rewrite content; name where removed detail survives when cutting more than half
|
|
6789
6919
|
- \`action="write", category="...", content="..."\` — create a memory (SPLITS ONLY — never mint new facts)
|
|
@@ -6813,7 +6943,7 @@ var PRIMER_INVESTIGATOR_SYSTEM_PROMPT = `You are a read-only code investigator f
|
|
|
6813
6943
|
function renderMemoryList(memories) {
|
|
6814
6944
|
return memories.map((memory) => {
|
|
6815
6945
|
const files = memory.mappedFiles.length ? memory.mappedFiles.join(", ") : "(none mapped yet)";
|
|
6816
|
-
return `[${memory.id}] ${memory.category}
|
|
6946
|
+
return `[${memory.id}] ${memory.category} importance=${memory.importance} retrieval_count=${memory.retrievalCount} seen_count=${memory.seenCount}
|
|
6817
6947
|
Content: ${memory.content}
|
|
6818
6948
|
Mapped files: ${files}${memory.hasNoFileSentinel ? " (file-independent)" : ""}`;
|
|
6819
6949
|
}).join(`
|
|
@@ -6841,9 +6971,41 @@ Rewrite narrative/historical → operational present tense ("X uses Y because Z"
|
|
|
6841
6971
|
Archive a redundant memory only when a better ACTIVE memory in the same project and category preserves its information; name that survivor with \`superseded_by\`. A bare "redundant" verdict is deletion and will be refused. Leave standalone low-value or stale entries unchanged for a human to review. The global user profile describes the operator and is never a substitute for project knowledge, so it cannot justify an archive.
|
|
6842
6972
|
KEEP (overrides archive): constraint/rule language (must/never/always) · explains WHY (because/so that/to prevent) · EXTERNAL-system limit (CONSTRAINTS: archive only if word-for-word duplicated) · path/config WITH context · retrieval_count>0 · priority/philosophy.
|
|
6843
6973
|
|
|
6844
|
-
###
|
|
6974
|
+
### Cross-chunk duplicate candidates (normalized content matches)
|
|
6975
|
+
${args.crossChunkCandidates?.join(`
|
|
6976
|
+
`) || "(none)"}
|
|
6977
|
+
|
|
6978
|
+
### Category snapshot (do not re-enumerate)
|
|
6845
6979
|
${renderMemoryList(args.memories)}`;
|
|
6846
6980
|
}
|
|
6981
|
+
function chunkCurateMemories(memories, maxCharacters) {
|
|
6982
|
+
const chunks = [];
|
|
6983
|
+
let current = [];
|
|
6984
|
+
let size = 0;
|
|
6985
|
+
for (const memory of memories) {
|
|
6986
|
+
const length = renderMemoryList([memory]).length + 2;
|
|
6987
|
+
if (current.length && size + length > maxCharacters) {
|
|
6988
|
+
chunks.push(current);
|
|
6989
|
+
current = [];
|
|
6990
|
+
size = 0;
|
|
6991
|
+
}
|
|
6992
|
+
current.push(memory);
|
|
6993
|
+
size += length;
|
|
6994
|
+
}
|
|
6995
|
+
if (current.length)
|
|
6996
|
+
chunks.push(current);
|
|
6997
|
+
const keys = new Map;
|
|
6998
|
+
chunks.forEach((chunk, index) => {
|
|
6999
|
+
chunk.forEach((memory) => {
|
|
7000
|
+
const key = memory.content.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim();
|
|
7001
|
+
keys.set(key, [...keys.get(key) ?? [], { id: memory.id, chunk: index }]);
|
|
7002
|
+
});
|
|
7003
|
+
});
|
|
7004
|
+
return chunks.map((chunk, index) => ({
|
|
7005
|
+
memories: chunk,
|
|
7006
|
+
crossChunkCandidates: [...keys.values()].filter((matches) => matches.some((match) => match.chunk === index) && matches.some((match) => match.chunk !== index)).map((matches) => `IDs ${matches.map((match) => match.id).join(", ")}`)
|
|
7007
|
+
}));
|
|
7008
|
+
}
|
|
6847
7009
|
var RETROSPECTIVE_SYSTEM_PROMPT = `You are a retrospective learning agent for Magic Context.
|
|
6848
7010
|
|
|
6849
7011
|
You learn only from recurring user-friction moments where the user had to correct, re-explain, or recover from the assistant's repeated behavior. You receive a pre-rendered friction window from the host and may use ctx_search to look for corroborating prior patterns.
|
|
@@ -6902,17 +7064,23 @@ Return only XML in this exact shape:
|
|
|
6902
7064
|
<learning route="observation">one recurring user preference</learning>
|
|
6903
7065
|
</learnings>`;
|
|
6904
7066
|
}
|
|
6905
|
-
function buildMaintainDocsPrompt(projectPath, changeSet, existingDocs, budget = 12000, currentTokens = 0) {
|
|
7067
|
+
function buildMaintainDocsPrompt(projectPath, changeSet, existingDocs, budget = 12000, currentTokens = 0, currentDocs) {
|
|
6906
7068
|
return `## Task: Propose documentation corrections
|
|
6907
7069
|
|
|
6908
7070
|
Project: ${projectPath}
|
|
6909
7071
|
Existing docs: ARCHITECTURE.md ${existingDocs.architecture ? "exists" : "missing"}; STRUCTURE.md ${existingDocs.structure ? "exists" : "missing"}.
|
|
6910
7072
|
Current combined token count: ${currentTokens}. Combined budget: ${budget} tokens.
|
|
6911
7073
|
|
|
6912
|
-
Host-collected code changes (
|
|
7074
|
+
Host-collected code changes (diff stat and hunks, or file/line ranges when oversized):
|
|
6913
7075
|
${changeSet}
|
|
6914
7076
|
|
|
6915
|
-
|
|
7077
|
+
Current ARCHITECTURE.md:
|
|
7078
|
+
${currentDocs?.architecture ?? "(read from project)"}
|
|
7079
|
+
|
|
7080
|
+
Current STRUCTURE.md:
|
|
7081
|
+
${currentDocs?.structure ?? "(read from project)"}
|
|
7082
|
+
|
|
7083
|
+
Find claims in these sections that the diff makes wrong or incomplete. Propose one focused change per claim or section. Do not investigate unrelated areas. Read the docs and relevant source with read-only tools. These files are short maps read by every agent in every session. Describe how the system works now, never what changed or when: no change log, dates, or commit lists. One short paragraph per subsystem at most. Mechanism detail belongs in docs/architecture/; you may name a docs page for it but never move that detail into these two files. Propose a change only when code contradicts the docs or a major piece is missing. Prefer rewriting a stale sentence to adding one. Propose nothing when nothing is wrong. Keep both files within the combined budget. Never touch protected regions (<!-- mc:protected START ... --> through <!-- mc:protected END -->); preserve their bytes.
|
|
6916
7084
|
|
|
6917
7085
|
Return ONLY a JSON array (or []), with each entry {"file":"ARCHITECTURE.md"|"STRUCTURE.md","action":"replace"|"add"|"remove","heading":"## Exact section heading","text":"## Full replacement section including heading and body (empty for remove)","reason":"one-line reason"}. A replacement includes the entire section, including its heading. Additions are appended at the end of the file. Do not include any unmodified sections. No file writes.`;
|
|
6918
7086
|
}
|
|
@@ -6922,10 +7090,11 @@ function buildDreamTaskPrompt(task, args) {
|
|
|
6922
7090
|
return buildCuratePrompt({
|
|
6923
7091
|
projectPath: args.projectPath,
|
|
6924
7092
|
category: args.curate?.category ?? "PROJECT_RULES",
|
|
6925
|
-
memories: args.curate?.memories ?? []
|
|
7093
|
+
memories: args.curate?.memories ?? [],
|
|
7094
|
+
crossChunkCandidates: args.curate?.crossChunkCandidates
|
|
6926
7095
|
});
|
|
6927
7096
|
case "maintain-docs":
|
|
6928
|
-
return buildMaintainDocsPrompt(args.projectPath, args.docsChangeSet ?? "", args.existingDocs ?? { architecture: false, structure: false }, args.docsBudget, args.docsCurrentTokens);
|
|
7097
|
+
return buildMaintainDocsPrompt(args.projectPath, args.docsChangeSet ?? "", args.existingDocs ?? { architecture: false, structure: false }, args.docsBudget, args.docsCurrentTokens, args.docsContents);
|
|
6929
7098
|
}
|
|
6930
7099
|
}
|
|
6931
7100
|
|
|
@@ -7858,6 +8027,7 @@ function hasCurrentDocsProposal(projectDir) {
|
|
|
7858
8027
|
}
|
|
7859
8028
|
function git(projectDir, args) {
|
|
7860
8029
|
return execFileSync("git", args, {
|
|
8030
|
+
windowsHide: true,
|
|
7861
8031
|
cwd: projectDir,
|
|
7862
8032
|
encoding: "utf8",
|
|
7863
8033
|
timeout: 1e4,
|
|
@@ -7869,11 +8039,80 @@ function docsChangeSet(projectDir, storedAnchor) {
|
|
|
7869
8039
|
if (realpathSync2(git(projectDir, ["rev-parse", "--show-toplevel"])) !== realpathSync2(projectDir))
|
|
7870
8040
|
return null;
|
|
7871
8041
|
const head = git(projectDir, ["rev-parse", "HEAD"]);
|
|
8042
|
+
const recorded = Boolean(storedAnchor);
|
|
7872
8043
|
let anchor = storedAnchor;
|
|
7873
|
-
if (
|
|
8044
|
+
if (anchor && (!/^[a-f0-9]{40}$/.test(anchor) || git(projectDir, ["cat-file", "-t", anchor]) !== "commit"))
|
|
8045
|
+
throw new Error("maintain-docs checkpoint is not a git commit");
|
|
8046
|
+
if (!anchor)
|
|
7874
8047
|
anchor = git(projectDir, ["log", "-1", "--format=%H", "--", ...FILES]);
|
|
7875
8048
|
if (!anchor)
|
|
7876
8049
|
return null;
|
|
8050
|
+
if (recorded && anchor === head)
|
|
8051
|
+
return { head, text: "", unchanged: true, relevant: false };
|
|
8052
|
+
if (recorded) {
|
|
8053
|
+
const changed = git(projectDir, [
|
|
8054
|
+
"diff",
|
|
8055
|
+
"-M",
|
|
8056
|
+
"--name-status",
|
|
8057
|
+
`${anchor}..HEAD`,
|
|
8058
|
+
"--",
|
|
8059
|
+
"."
|
|
8060
|
+
]).split(`
|
|
8061
|
+
`).filter(Boolean).map((line) => line.split("\t").slice(1));
|
|
8062
|
+
const files = [
|
|
8063
|
+
...new Set(changed.flat().filter((file) => file !== "ARCHITECTURE.md" && file !== "STRUCTURE.md" && !/(?:^|[./-])(test|spec)(?:[./-]|$)/.test(file) && !file.endsWith(".lock")))
|
|
8064
|
+
];
|
|
8065
|
+
const docs = FILES.map((file) => existsSync3(join3(projectDir, file)) ? readFileSync3(join3(projectDir, file), "utf8") : "").join(`
|
|
8066
|
+
`);
|
|
8067
|
+
const relevant = files.some((file) => {
|
|
8068
|
+
const directory = file.split("/").slice(0, -1).join("/");
|
|
8069
|
+
return docs.includes(file) || directory.length > 2 && docs.includes(directory);
|
|
8070
|
+
});
|
|
8071
|
+
if (!relevant)
|
|
8072
|
+
return { head, text: "", unchanged: false, relevant: false };
|
|
8073
|
+
const stat = git(projectDir, [
|
|
8074
|
+
"diff",
|
|
8075
|
+
"-M",
|
|
8076
|
+
"--stat",
|
|
8077
|
+
`${anchor}..HEAD`,
|
|
8078
|
+
"--",
|
|
8079
|
+
...files
|
|
8080
|
+
]);
|
|
8081
|
+
const patch = git(projectDir, [
|
|
8082
|
+
"diff",
|
|
8083
|
+
"-M",
|
|
8084
|
+
"--unified=3",
|
|
8085
|
+
`${anchor}..HEAD`,
|
|
8086
|
+
"--",
|
|
8087
|
+
...files
|
|
8088
|
+
]);
|
|
8089
|
+
const budget = 32000;
|
|
8090
|
+
const text = `Changed files:
|
|
8091
|
+
${files.join(`
|
|
8092
|
+
`)}
|
|
8093
|
+
|
|
8094
|
+
Stat:
|
|
8095
|
+
${stat}
|
|
8096
|
+
|
|
8097
|
+
Hunks:
|
|
8098
|
+
${patch}`;
|
|
8099
|
+
if (text.length <= budget)
|
|
8100
|
+
return { head, text, unchanged: false, relevant: true };
|
|
8101
|
+
const ranges = [...patch.matchAll(/^\+\+\+ b\/(.+)$|^@@ .* \+(\d+)(?:,(\d+))? @@/gm)];
|
|
8102
|
+
return {
|
|
8103
|
+
head,
|
|
8104
|
+
unchanged: false,
|
|
8105
|
+
relevant: true,
|
|
8106
|
+
text: `Changed files and ranges (diff exceeds prompt budget):
|
|
8107
|
+
${files.join(`
|
|
8108
|
+
`)}
|
|
8109
|
+
${ranges.map((match) => match[1] ? `file: ${match[1]}` : `line: ${match[2]} +${match[3] ?? 1}`).join(`
|
|
8110
|
+
`).slice(0, budget)}
|
|
8111
|
+
|
|
8112
|
+
Stat:
|
|
8113
|
+
${stat}`
|
|
8114
|
+
};
|
|
8115
|
+
}
|
|
7877
8116
|
const excluded = [
|
|
7878
8117
|
"ARCHITECTURE.md",
|
|
7879
8118
|
"STRUCTURE.md",
|
|
@@ -7903,11 +8142,15 @@ function docsChangeSet(projectDir, storedAnchor) {
|
|
|
7903
8142
|
const text = raw.slice(0, 24000);
|
|
7904
8143
|
return {
|
|
7905
8144
|
head,
|
|
8145
|
+
unchanged: false,
|
|
8146
|
+
relevant: true,
|
|
7906
8147
|
text: `${text}${raw.length > text.length ? `
|
|
7907
8148
|
[cut ${raw.length - text.length} bytes]` : ""}${commits.length > 201 ? `
|
|
7908
8149
|
[cut commits beyond newest 200]` : ""}`
|
|
7909
8150
|
};
|
|
7910
|
-
} catch {
|
|
8151
|
+
} catch (error) {
|
|
8152
|
+
if (storedAnchor)
|
|
8153
|
+
throw error;
|
|
7911
8154
|
return null;
|
|
7912
8155
|
}
|
|
7913
8156
|
}
|
|
@@ -7941,7 +8184,7 @@ function diff(name, before, after) {
|
|
|
7941
8184
|
writeFileSync3(join3(dir, "before"), before);
|
|
7942
8185
|
writeFileSync3(join3(dir, "after"), after);
|
|
7943
8186
|
try {
|
|
7944
|
-
return execFileSync("git", ["diff", "--no-index", "--", join3(dir, "before"), join3(dir, "after")], { encoding: "utf8" });
|
|
8187
|
+
return execFileSync("git", ["diff", "--no-index", "--", join3(dir, "before"), join3(dir, "after")], { windowsHide: true, encoding: "utf8" });
|
|
7945
8188
|
} catch (error) {
|
|
7946
8189
|
return error.stdout?.replaceAll(join3(dir, "before"), `a/${name}`).replaceAll(join3(dir, "after"), `b/${name}`) ?? "";
|
|
7947
8190
|
}
|
|
@@ -8192,7 +8435,7 @@ function requestValidatedAddress(validation, candidate, options) {
|
|
|
8192
8435
|
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
8193
8436
|
bytes += buf.byteLength;
|
|
8194
8437
|
if (bytes > options.bodyLimitBytes) {
|
|
8195
|
-
reject(new SmartNoteNetworkError(
|
|
8438
|
+
reject(new SmartNoteNetworkError(`SMART_NOTE_NETWORK: response body too large at ${url.href} (received at least ${bytes} bytes; limit ${options.bodyLimitBytes})`, { terminal: true, persistent: true }));
|
|
8196
8439
|
response.destroy();
|
|
8197
8440
|
request2.destroy();
|
|
8198
8441
|
return;
|
|
@@ -8529,6 +8772,7 @@ async function runGit(projectRoot, args, signal) {
|
|
|
8529
8772
|
throwIfAborted2(signal);
|
|
8530
8773
|
try {
|
|
8531
8774
|
const result = await execFileAsync("git", ["-C", projectRoot, ...args], {
|
|
8775
|
+
windowsHide: true,
|
|
8532
8776
|
timeout: DEFAULT_GIT_TIMEOUT_MS,
|
|
8533
8777
|
maxBuffer: 128 * 1024,
|
|
8534
8778
|
signal
|
|
@@ -8691,6 +8935,7 @@ async function runCompiledSmartNoteCheckLocked(options, quickjs) {
|
|
|
8691
8935
|
const controller = new AbortController;
|
|
8692
8936
|
let externallyCancelled = false;
|
|
8693
8937
|
let executionTimedOut = false;
|
|
8938
|
+
let persistentNetworkFailure = false;
|
|
8694
8939
|
const externalAbort = () => {
|
|
8695
8940
|
externallyCancelled = true;
|
|
8696
8941
|
controller.abort(options.signal?.reason);
|
|
@@ -8709,7 +8954,19 @@ async function runCompiledSmartNoteCheckLocked(options, quickjs) {
|
|
|
8709
8954
|
context.runtime.setMemoryLimit(options.heapLimitBytes ?? DEFAULT_HEAP_LIMIT_BYTES);
|
|
8710
8955
|
context.runtime.setMaxStackSize(options.stackLimitBytes ?? DEFAULT_STACK_LIMIT_BYTES);
|
|
8711
8956
|
context.runtime.setInterruptHandler(() => controller.signal.aborted || Date.now() > deadline);
|
|
8712
|
-
installCapabilityObject(context,
|
|
8957
|
+
installCapabilityObject(context, {
|
|
8958
|
+
...capabilities,
|
|
8959
|
+
httpGet: async (url) => {
|
|
8960
|
+
try {
|
|
8961
|
+
return await capabilities.httpGet(url);
|
|
8962
|
+
} catch (error) {
|
|
8963
|
+
if (error instanceof SmartNoteNetworkError && error.persistent) {
|
|
8964
|
+
persistentNetworkFailure = true;
|
|
8965
|
+
}
|
|
8966
|
+
throw error;
|
|
8967
|
+
}
|
|
8968
|
+
}
|
|
8969
|
+
});
|
|
8713
8970
|
disableAmbientDynamicCode(context);
|
|
8714
8971
|
const result = await evalCheck(context, options.compiledCheck);
|
|
8715
8972
|
const checkResult = result;
|
|
@@ -8723,14 +8980,14 @@ async function runCompiledSmartNoteCheckLocked(options, quickjs) {
|
|
|
8723
8980
|
} catch (error) {
|
|
8724
8981
|
if (externallyCancelled && !executionTimedOut)
|
|
8725
8982
|
return cancelledResult(error);
|
|
8726
|
-
return failureResult(formatSandboxError(error), isSmartNoteNetworkError(error));
|
|
8983
|
+
return failureResult(formatSandboxError(error), isSmartNoteNetworkError(error), persistentNetworkFailure);
|
|
8727
8984
|
} finally {
|
|
8728
8985
|
clearTimeout(timer);
|
|
8729
8986
|
options.signal?.removeEventListener("abort", externalAbort);
|
|
8730
8987
|
}
|
|
8731
8988
|
}
|
|
8732
|
-
function failureResult(error, network) {
|
|
8733
|
-
return { ok: false, cancelled: false, error: truncate(error), network };
|
|
8989
|
+
function failureResult(error, network, persistent = false) {
|
|
8990
|
+
return { ok: false, cancelled: false, error: truncate(error), network, persistent };
|
|
8734
8991
|
}
|
|
8735
8992
|
function cancelledResult(reason) {
|
|
8736
8993
|
return {
|
|
@@ -8828,7 +9085,12 @@ var MAX_CRON_CHARS = 256;
|
|
|
8828
9085
|
var MAX_COMPILER_ERROR_CHARS = 2 * 1024;
|
|
8829
9086
|
async function compileSmartNoteCheck(args) {
|
|
8830
9087
|
if (!args.note.surfaceCondition) {
|
|
8831
|
-
return {
|
|
9088
|
+
return {
|
|
9089
|
+
ok: false,
|
|
9090
|
+
cancelled: false,
|
|
9091
|
+
error: "note has no surface condition",
|
|
9092
|
+
persistent: false
|
|
9093
|
+
};
|
|
8832
9094
|
}
|
|
8833
9095
|
const prompt = `Compile this smart note condition into a sandbox check.
|
|
8834
9096
|
|
|
@@ -8935,15 +9197,13 @@ Remember: output only the JSON object described by the system prompt.`;
|
|
|
8935
9197
|
const compiledCheck = normalizeCompiledCheck(response.compiled_check);
|
|
8936
9198
|
const manifest = normalizeManifest(response.manifest);
|
|
8937
9199
|
const checkCron = normalizeCron(response.check_cron);
|
|
8938
|
-
|
|
9200
|
+
const dryRun = await dryRunSmartNoteCheck(compiledCheck, args.capabilityFactory, args.signal);
|
|
9201
|
+
for (const warning of [
|
|
9202
|
+
...manifestAdvisoryWarnings(compiledCheck, manifest),
|
|
9203
|
+
...dryRun.advisories
|
|
9204
|
+
]) {
|
|
8939
9205
|
log(`[dreamer] smart note #${args.note.id}: manifest advisory — ${warning}`);
|
|
8940
9206
|
}
|
|
8941
|
-
const dryRun = await runCompiledSmartNoteCheck({
|
|
8942
|
-
compiledCheck,
|
|
8943
|
-
capabilityFactory: args.capabilityFactory,
|
|
8944
|
-
signal: args.signal,
|
|
8945
|
-
timeoutMs: 2000
|
|
8946
|
-
});
|
|
8947
9207
|
if (!dryRun.ok) {
|
|
8948
9208
|
const error = boundedError(`dry-run failed: ${dryRun.error}`);
|
|
8949
9209
|
recordInvocation({
|
|
@@ -8951,7 +9211,12 @@ Remember: output only the JSON object described by the system prompt.`;
|
|
|
8951
9211
|
messages: outputMessages,
|
|
8952
9212
|
error
|
|
8953
9213
|
});
|
|
8954
|
-
return {
|
|
9214
|
+
return {
|
|
9215
|
+
ok: false,
|
|
9216
|
+
cancelled: dryRun.cancelled,
|
|
9217
|
+
error,
|
|
9218
|
+
persistent: !dryRun.cancelled && dryRun.persistent
|
|
9219
|
+
};
|
|
8955
9220
|
}
|
|
8956
9221
|
recordInvocation({ status: "completed", messages: outputMessages });
|
|
8957
9222
|
return {
|
|
@@ -8966,7 +9231,7 @@ Remember: output only the JSON object described by the system prompt.`;
|
|
|
8966
9231
|
const cancelled = args.signal.aborted;
|
|
8967
9232
|
const message = boundedError(error instanceof Error ? error.message : String(error));
|
|
8968
9233
|
recordInvocation({ status: cancelled ? "aborted" : "failed", error: message });
|
|
8969
|
-
return { ok: false, cancelled, error: message };
|
|
9234
|
+
return { ok: false, cancelled, error: message, persistent: false };
|
|
8970
9235
|
} finally {
|
|
8971
9236
|
if (args.client) {
|
|
8972
9237
|
await teardownChildSession({
|
|
@@ -8981,6 +9246,29 @@ Remember: output only the JSON object described by the system prompt.`;
|
|
|
8981
9246
|
}
|
|
8982
9247
|
}
|
|
8983
9248
|
}
|
|
9249
|
+
async function dryRunSmartNoteCheck(compiledCheck, capabilityFactory, signal) {
|
|
9250
|
+
const responses = [];
|
|
9251
|
+
const dryRun = await runCompiledSmartNoteCheck({
|
|
9252
|
+
compiledCheck,
|
|
9253
|
+
capabilityFactory: (runSignal) => {
|
|
9254
|
+
const capabilities = capabilityFactory(runSignal);
|
|
9255
|
+
return {
|
|
9256
|
+
...capabilities,
|
|
9257
|
+
httpGet: async (url) => {
|
|
9258
|
+
const response = await capabilities.httpGet(url);
|
|
9259
|
+
responses.push({ url, status: response.status });
|
|
9260
|
+
return response;
|
|
9261
|
+
}
|
|
9262
|
+
};
|
|
9263
|
+
},
|
|
9264
|
+
signal,
|
|
9265
|
+
timeoutMs: 2000
|
|
9266
|
+
});
|
|
9267
|
+
const advisories = dryRun.ok && responses.length > 0 && responses.every(({ status }) => status === 401 || status === 403 || status === 404) ? [
|
|
9268
|
+
`all HTTP sources currently return 401/403/404: ${responses.map(({ url, status }) => `${url} (HTTP ${status})`).join(", ")}`
|
|
9269
|
+
] : [];
|
|
9270
|
+
return { ...dryRun, advisories };
|
|
9271
|
+
}
|
|
8984
9272
|
function parseCompilerOutput(output) {
|
|
8985
9273
|
if (!output)
|
|
8986
9274
|
throw new Error("smart-note compiler returned no output");
|
|
@@ -9435,7 +9723,7 @@ async function compileNote(args, note, projectRoot, assertLeaseHeld, leaseHeld,
|
|
|
9435
9723
|
expected: sourceRevisionExpectation(note),
|
|
9436
9724
|
leaseHeld,
|
|
9437
9725
|
write: () => {
|
|
9438
|
-
markSmartNoteCompilationFailure(args.db, note.id, now, MAX_COMPILATION_FAILURES);
|
|
9726
|
+
markSmartNoteCompilationFailure(args.db, note.id, now, MAX_COMPILATION_FAILURES, result.error, result.persistent);
|
|
9439
9727
|
}
|
|
9440
9728
|
});
|
|
9441
9729
|
return false;
|
|
@@ -9806,7 +10094,7 @@ var MAP_MEMORIES_SYSTEM_PROMPT = `You are a memory mapper for the magic-context
|
|
|
9806
10094
|
|
|
9807
10095
|
A memory's BACKING FILES are the file(s) whose code the memory makes a claim about — the files you would open to check whether the memory is accurate. You do NOT judge accuracy, rewrite, or remove anything. You only LOCATE backing files.
|
|
9808
10096
|
|
|
9809
|
-
Tools (read-only): read, grep, glob
|
|
10097
|
+
Tools (read-only): read, grep, glob. Request line ranges rather than full files; use offset/limit for longer files. Each memory may come with "Likely files" already named in it and confirmed to exist — confirm those FIRST (cheap) instead of searching. Use search/grep to FIND code only when no likely files are given. Do not guess — confirm a file exists and genuinely backs the memory before listing it. Keep reads minimal: you do not need to read a whole file to confirm it backs a one-line claim.
|
|
9810
10098
|
|
|
9811
10099
|
For each memory decide ONE of:
|
|
9812
10100
|
- Backing files found → the COMPLETE set of repo-relative paths whose code the memory is about.
|
|
@@ -10063,6 +10351,7 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
|
|
|
10063
10351
|
prompt,
|
|
10064
10352
|
title: "magic-context-dream-map-memories",
|
|
10065
10353
|
callContext: "dreamer:map-memories",
|
|
10354
|
+
metadata: { tokenBudget: args.tokenBudget, onBudgetUpdate: args.onBudgetUpdate },
|
|
10066
10355
|
model: args.model,
|
|
10067
10356
|
fallbackModels: args.fallbackModels,
|
|
10068
10357
|
timeoutMs: sliceMs,
|
|
@@ -10103,7 +10392,10 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
|
|
|
10103
10392
|
parts: [{ type: "text", text: prompt, synthetic: true }]
|
|
10104
10393
|
}
|
|
10105
10394
|
}, {
|
|
10106
|
-
transport: createPromptAsyncTransport(client, agentSessionId
|
|
10395
|
+
transport: createPromptAsyncTransport(client, agentSessionId, {
|
|
10396
|
+
tokenBudget: args.tokenBudget,
|
|
10397
|
+
onBudgetUpdate: args.onBudgetUpdate
|
|
10398
|
+
}),
|
|
10107
10399
|
timeoutMs: sliceMs,
|
|
10108
10400
|
signal,
|
|
10109
10401
|
fallbackModels: args.fallbackModels,
|
|
@@ -10143,7 +10435,7 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
|
|
|
10143
10435
|
error,
|
|
10144
10436
|
messages: await readFailedChildMessages(args.client, agentSessionId, args.sessionDirectory)
|
|
10145
10437
|
});
|
|
10146
|
-
if (error instanceof DreamerModuleFailureError)
|
|
10438
|
+
if (error instanceof DreamerModuleFailureError || error instanceof DreamTokenBudgetExceeded)
|
|
10147
10439
|
throw error;
|
|
10148
10440
|
if (signal.aborted)
|
|
10149
10441
|
throw error;
|
|
@@ -10773,6 +11065,7 @@ async function refreshOnePrimer(args, primer, sliceMs, signal) {
|
|
|
10773
11065
|
prompt,
|
|
10774
11066
|
title: "magic-context-dream-refresh-primers",
|
|
10775
11067
|
callContext: "dreamer:refresh-primers",
|
|
11068
|
+
metadata: { tokenBudget: args.tokenBudget, onBudgetUpdate: args.onBudgetUpdate },
|
|
10776
11069
|
model: args.model,
|
|
10777
11070
|
fallbackModels: args.fallbackModels,
|
|
10778
11071
|
language: args.language,
|
|
@@ -10822,6 +11115,10 @@ async function refreshOnePrimer(args, primer, sliceMs, signal) {
|
|
|
10822
11115
|
signal,
|
|
10823
11116
|
fallbackModels: args.fallbackModels,
|
|
10824
11117
|
callContext: "dreamer:refresh-primers",
|
|
11118
|
+
transport: createPromptAsyncTransport(client, agentSessionId, {
|
|
11119
|
+
tokenBudget: args.tokenBudget,
|
|
11120
|
+
onBudgetUpdate: args.onBudgetUpdate
|
|
11121
|
+
}),
|
|
10825
11122
|
fetchOutput: async () => {
|
|
10826
11123
|
const messagesResponse = await client.session.messages({
|
|
10827
11124
|
path: { id: agentSessionId },
|
|
@@ -11034,12 +11331,14 @@ class OpenCodeRetrospectiveRawProvider {
|
|
|
11034
11331
|
this.openDb = deps.openOpenCodeDb ?? openOpenCodeDb;
|
|
11035
11332
|
}
|
|
11036
11333
|
listProjectSessions(projectIdentity) {
|
|
11037
|
-
const rows = this.deps.contextDb.prepare(`SELECT sp.session_id,
|
|
11334
|
+
const rows = this.deps.contextDb.prepare(`SELECT sp.session_id, CAST(activity.value AS INTEGER) AS updated_at
|
|
11038
11335
|
FROM session_projects sp
|
|
11336
|
+
JOIN schema_migrations_meta activity
|
|
11337
|
+
ON activity.key = 'retrospective_activity:' || sp.session_id
|
|
11039
11338
|
LEFT JOIN session_meta m ON m.session_id = sp.session_id
|
|
11040
11339
|
WHERE sp.project_path = ? AND sp.harness = 'opencode'
|
|
11041
11340
|
AND COALESCE(m.is_subagent, 0) = 0
|
|
11042
|
-
ORDER BY
|
|
11341
|
+
ORDER BY updated_at ASC, sp.session_id ASC`).all(projectIdentity);
|
|
11043
11342
|
return rows.map((row) => ({
|
|
11044
11343
|
sessionId: row.session_id,
|
|
11045
11344
|
updatedAt: typeof row.updated_at === "number" ? row.updated_at : undefined
|
|
@@ -11058,15 +11357,13 @@ class OpenCodeRetrospectiveRawProvider {
|
|
|
11058
11357
|
const db = this.resolveDb();
|
|
11059
11358
|
if (!db)
|
|
11060
11359
|
return { messages: [], truncated: false };
|
|
11061
|
-
|
|
11062
|
-
return readOpenCodeMessagesSince(db, sessionId, sinceMs, capPerSession);
|
|
11063
|
-
} catch {
|
|
11064
|
-
return { messages: [], truncated: false };
|
|
11065
|
-
}
|
|
11360
|
+
return readOpenCodeMessagesSince(db, sessionId, sinceMs, capPerSession);
|
|
11066
11361
|
}
|
|
11067
11362
|
readOldestMessageTimesSince(sessionIds, sinceMs) {
|
|
11068
11363
|
const db = this.resolveDb();
|
|
11069
|
-
if (!db
|
|
11364
|
+
if (!db)
|
|
11365
|
+
return null;
|
|
11366
|
+
if (sessionIds.length === 0)
|
|
11070
11367
|
return new Map;
|
|
11071
11368
|
return readOpenCodeOldestMessageTimesSince(db, sessionIds, sinceMs);
|
|
11072
11369
|
}
|
|
@@ -11163,6 +11460,11 @@ async function readRetrospectiveScanWindow(provider, projectIdentity, watermarkM
|
|
|
11163
11460
|
}
|
|
11164
11461
|
const droppedSince = [...budgetDropped, ...countDropped];
|
|
11165
11462
|
let maxScannedTs = scanSinceMs;
|
|
11463
|
+
if (oldestBySession && sinceReads.every((read) => read.messages.length === 0)) {
|
|
11464
|
+
for (const { session } of eligibleSessions) {
|
|
11465
|
+
maxScannedTs = Math.max(maxScannedTs, session.updatedAt ?? scanSinceMs);
|
|
11466
|
+
}
|
|
11467
|
+
}
|
|
11166
11468
|
for (const row of keptSince) {
|
|
11167
11469
|
if (row.ts > maxScannedTs)
|
|
11168
11470
|
maxScannedTs = row.ts;
|
|
@@ -11454,6 +11756,127 @@ function insertDreamRun(db, run) {
|
|
|
11454
11756
|
// ../plugin/src/features/magic-context/dreamer/verify.ts
|
|
11455
11757
|
import { createHash as createHash9 } from "node:crypto";
|
|
11456
11758
|
|
|
11759
|
+
// ../plugin/src/features/magic-context/dreamer/verify-diff.ts
|
|
11760
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
11761
|
+
import { promisify as promisify2 } from "node:util";
|
|
11762
|
+
var exec = promisify2(execFile2);
|
|
11763
|
+
var VERIFY_DIFF_BUDGET_TOKENS = 12000;
|
|
11764
|
+
var MAX_DIFF_BYTES = 4 * 1024 * 1024;
|
|
11765
|
+
async function git2(root, args) {
|
|
11766
|
+
const { stdout } = await exec("git", args, {
|
|
11767
|
+
cwd: root,
|
|
11768
|
+
timeout: 1e4,
|
|
11769
|
+
maxBuffer: MAX_DIFF_BYTES,
|
|
11770
|
+
encoding: "utf8"
|
|
11771
|
+
});
|
|
11772
|
+
return stdout;
|
|
11773
|
+
}
|
|
11774
|
+
async function buildVerifyDiffEvidence(directory, memories) {
|
|
11775
|
+
const root = await resolveGitTopLevel(directory);
|
|
11776
|
+
const head = root && await readGitHead(root);
|
|
11777
|
+
if (!root || !head || memories.some((m) => !m.verifiedAt))
|
|
11778
|
+
return null;
|
|
11779
|
+
try {
|
|
11780
|
+
const bases = new Map;
|
|
11781
|
+
for (const memory of memories) {
|
|
11782
|
+
const at = memory.verifiedAt;
|
|
11783
|
+
if (!at)
|
|
11784
|
+
return null;
|
|
11785
|
+
const base = memory.verifiedCommit ?? (await git2(root, [
|
|
11786
|
+
"rev-list",
|
|
11787
|
+
"-1",
|
|
11788
|
+
`--before=${new Date(at).toISOString()}`,
|
|
11789
|
+
head
|
|
11790
|
+
])).trim();
|
|
11791
|
+
if (!/^[0-9a-f]{40}$/i.test(base))
|
|
11792
|
+
return null;
|
|
11793
|
+
bases.set(memory.id, base);
|
|
11794
|
+
}
|
|
11795
|
+
const grouped = new Map;
|
|
11796
|
+
for (const memory of memories) {
|
|
11797
|
+
const at = memory.verifiedAt;
|
|
11798
|
+
const base = bases.get(memory.id);
|
|
11799
|
+
if (!at || !base)
|
|
11800
|
+
return null;
|
|
11801
|
+
for (const file of memory.mappedFiles) {
|
|
11802
|
+
if (!file || file.startsWith("/") || file.split("/").includes(".."))
|
|
11803
|
+
return null;
|
|
11804
|
+
const previous = grouped.get(file);
|
|
11805
|
+
if (!previous)
|
|
11806
|
+
grouped.set(file, {
|
|
11807
|
+
base,
|
|
11808
|
+
time: at,
|
|
11809
|
+
ids: [memory.id]
|
|
11810
|
+
});
|
|
11811
|
+
else {
|
|
11812
|
+
previous.ids.push(memory.id);
|
|
11813
|
+
if (at < previous.time) {
|
|
11814
|
+
previous.time = at;
|
|
11815
|
+
previous.base = base;
|
|
11816
|
+
}
|
|
11817
|
+
}
|
|
11818
|
+
}
|
|
11819
|
+
}
|
|
11820
|
+
const files = [];
|
|
11821
|
+
const namesByBase = new Map;
|
|
11822
|
+
for (const [file, group] of grouped) {
|
|
11823
|
+
let names = namesByBase.get(group.base);
|
|
11824
|
+
if (names === undefined) {
|
|
11825
|
+
names = await git2(root, ["diff", "-M", "--name-status", group.base, head]);
|
|
11826
|
+
namesByBase.set(group.base, names);
|
|
11827
|
+
}
|
|
11828
|
+
const rename = names.split(`
|
|
11829
|
+
`).map((line) => line.split("\t")).find((parts) => parts[0]?.startsWith("R") && parts[1] === file);
|
|
11830
|
+
const paths = rename ? [file, rename[2]] : [file];
|
|
11831
|
+
const committed = await git2(root, [
|
|
11832
|
+
"diff",
|
|
11833
|
+
"-M",
|
|
11834
|
+
"--no-ext-diff",
|
|
11835
|
+
group.base,
|
|
11836
|
+
head,
|
|
11837
|
+
"--",
|
|
11838
|
+
...paths
|
|
11839
|
+
]);
|
|
11840
|
+
const pending = await git2(root, ["diff", "-M", "--no-ext-diff", head, "--", ...paths]);
|
|
11841
|
+
const status = rename ? `RENAMED ${file} -> ${rename[2]}
|
|
11842
|
+
` : names.split(`
|
|
11843
|
+
`).some((line) => line === `D ${file}`) ? `DELETED ${file}
|
|
11844
|
+
` : "";
|
|
11845
|
+
files.push({
|
|
11846
|
+
path: file,
|
|
11847
|
+
memoryIds: group.ids,
|
|
11848
|
+
text: `${status}${committed}${pending ? `
|
|
11849
|
+
Uncommitted changes:
|
|
11850
|
+
${pending}` : ""}` || "No changed hunks in mapped file."
|
|
11851
|
+
});
|
|
11852
|
+
}
|
|
11853
|
+
const size = files.reduce((sum, file) => sum + file.text.length, 0);
|
|
11854
|
+
if (size <= VERIFY_DIFF_BUDGET_TOKENS * 3)
|
|
11855
|
+
return { head, mode: "diff", files };
|
|
11856
|
+
return {
|
|
11857
|
+
head,
|
|
11858
|
+
mode: "ranges",
|
|
11859
|
+
files: files.map((file) => {
|
|
11860
|
+
const headers = file.text.split(`
|
|
11861
|
+
`).filter((line) => /^(?:@@|diff --git|rename |deleted file mode|new file mode|RENAMED|DELETED|Uncommitted changes:)/.test(line));
|
|
11862
|
+
const lines = file.text.split(`
|
|
11863
|
+
`);
|
|
11864
|
+
const added = lines.filter((line) => line.startsWith("+") && !line.startsWith("+++")).length;
|
|
11865
|
+
const removed = lines.filter((line) => line.startsWith("-") && !line.startsWith("---")).length;
|
|
11866
|
+
return {
|
|
11867
|
+
...file,
|
|
11868
|
+
text: `Changed lines: +${added}/-${removed}.
|
|
11869
|
+
${headers.join(`
|
|
11870
|
+
`)}
|
|
11871
|
+
Read only these changed line ranges if they may affect the claim.`
|
|
11872
|
+
};
|
|
11873
|
+
})
|
|
11874
|
+
};
|
|
11875
|
+
} catch {
|
|
11876
|
+
return null;
|
|
11877
|
+
}
|
|
11878
|
+
}
|
|
11879
|
+
|
|
11457
11880
|
// ../plugin/src/features/magic-context/dreamer/verify-gate.ts
|
|
11458
11881
|
import path5 from "node:path";
|
|
11459
11882
|
function minOf(values) {
|
|
@@ -11488,12 +11911,24 @@ async function partitionVerifyScope(args) {
|
|
|
11488
11911
|
const active = getMemoriesByProject(args.db, args.projectIdentity);
|
|
11489
11912
|
const verById = getMemoryVerifications(args.db, active.map((m) => m.id));
|
|
11490
11913
|
const candidates = active.filter((m) => (verById.get(m.id)?.files.length ?? 0) > 0);
|
|
11491
|
-
const toPrompt = (m) =>
|
|
11492
|
-
|
|
11493
|
-
|
|
11494
|
-
|
|
11495
|
-
|
|
11496
|
-
|
|
11914
|
+
const toPrompt = (m) => {
|
|
11915
|
+
const verifiedAt = verById.get(m.id)?.verifiedAt ?? 0;
|
|
11916
|
+
let verifiedCommit = null;
|
|
11917
|
+
try {
|
|
11918
|
+
const metadata = JSON.parse(m.metadataJson ?? "{}");
|
|
11919
|
+
if (metadata.dreamerVerifiedAt === verifiedAt && typeof metadata.dreamerVerifiedCommit === "string" && /^[0-9a-f]{40}$/i.test(metadata.dreamerVerifiedCommit)) {
|
|
11920
|
+
verifiedCommit = metadata.dreamerVerifiedCommit;
|
|
11921
|
+
}
|
|
11922
|
+
} catch {}
|
|
11923
|
+
return {
|
|
11924
|
+
id: m.id,
|
|
11925
|
+
category: m.category,
|
|
11926
|
+
content: m.content,
|
|
11927
|
+
mappedFiles: verById.get(m.id)?.files ?? [],
|
|
11928
|
+
verifiedAt,
|
|
11929
|
+
verifiedCommit
|
|
11930
|
+
};
|
|
11931
|
+
};
|
|
11497
11932
|
if (args.forceBroad) {
|
|
11498
11933
|
const broadCycleStartAt = ensureBroadCycleStart({
|
|
11499
11934
|
db: args.db,
|
|
@@ -11573,9 +12008,9 @@ async function partitionVerifyScope(args) {
|
|
|
11573
12008
|
// ../plugin/src/features/magic-context/dreamer/verify-prompt.ts
|
|
11574
12009
|
var VERIFY_SYSTEM_PROMPT = `You are a memory verifier for the magic-context system. You verify project memories against the CURRENT code.
|
|
11575
12010
|
|
|
11576
|
-
Each memory below comes with its backing file(s) — the code it makes a claim about. For EACH memory: read its backing files (you may read more if needed) and decide whether the memory is still accurate.
|
|
12011
|
+
Each memory below comes with its backing file(s) — the code it makes a claim about. For EACH memory: use the provided git diff as evidence when present. If no diff is supplied, read its backing files (you may read more if needed) and decide whether the memory is still accurate.
|
|
11577
12012
|
|
|
11578
|
-
Tools (read-only): read, grep, glob
|
|
12013
|
+
Tools (read-only): read, grep, glob. Request line ranges and keep reads short. You read code only when the evidence requires it; you change nothing.
|
|
11579
12014
|
|
|
11580
12015
|
Decide ONE of four outcomes per memory:
|
|
11581
12016
|
- VERIFIED — still accurate. Keep it as-is.
|
|
@@ -11600,7 +12035,7 @@ Rules:
|
|
|
11600
12035
|
- Every input memory id MUST appear exactly once, in exactly one of verified/update/archive/skip.
|
|
11601
12036
|
- files = the COMPLETE current backing set (repo-relative, comma-separated). It may differ from the given mapping if a file moved — record what you actually verified against.
|
|
11602
12037
|
- Default to VERIFIED. skip when code cannot decide the claim. update and archive are the exceptions, not the norm.`;
|
|
11603
|
-
function buildVerifyPrompt(projectPath, memories) {
|
|
12038
|
+
function buildVerifyPrompt(projectPath, memories, evidence) {
|
|
11604
12039
|
const list = memories.map((m) => `[${m.id}] ${m.category}
|
|
11605
12040
|
Content: ${m.content}
|
|
11606
12041
|
Backing files: ${m.mappedFiles.join(", ")}`).join(`
|
|
@@ -11610,11 +12045,18 @@ Backing files: ${m.mappedFiles.join(", ")}`).join(`
|
|
|
11610
12045
|
|
|
11611
12046
|
Project: ${projectPath}
|
|
11612
12047
|
|
|
11613
|
-
Read each memory's backing files
|
|
12048
|
+
${evidence ? "The git diff below is the evidence. If changed hunks do not touch a claim, mark it verified without opening files. Open files only when a diff suggests the claim may have changed. A rename or deletion requires an update or archive when positively supported; do not archive on uncertainty." : "Read each memory's backing files to check its claims."} Decide verified / update / archive / skip (default verified; behavioral directives only permit verified or skip), then output ONE <verify> manifest covering every id.
|
|
11614
12049
|
|
|
11615
12050
|
<memories>
|
|
11616
12051
|
${list}
|
|
11617
|
-
</memories
|
|
12052
|
+
</memories>${evidence ? `
|
|
12053
|
+
|
|
12054
|
+
<changed-files mode="${evidence.mode}">
|
|
12055
|
+
${evidence.files.map((file) => `File: ${file.path} (memories: ${file.memoryIds.join(", ")})
|
|
12056
|
+
${file.text}`).join(`
|
|
12057
|
+
|
|
12058
|
+
`)}
|
|
12059
|
+
</changed-files>` : ""}`;
|
|
11618
12060
|
}
|
|
11619
12061
|
function attrOf(s, name) {
|
|
11620
12062
|
const m = s.match(new RegExp(`\\b${name}\\s*=\\s*"([^"]*)"`));
|
|
@@ -11738,8 +12180,11 @@ async function runVerify(args) {
|
|
|
11738
12180
|
return result;
|
|
11739
12181
|
}
|
|
11740
12182
|
const batches = [];
|
|
11741
|
-
|
|
11742
|
-
|
|
12183
|
+
const groups = !args.forceBroad && gate.mode === "incremental" ? [gate.inScope.filter((m) => m.verifiedAt), gate.inScope.filter((m) => !m.verifiedAt)] : [gate.inScope];
|
|
12184
|
+
for (const group of groups) {
|
|
12185
|
+
for (let i = 0;i < group.length; i += VERIFY_BATCH_SIZE) {
|
|
12186
|
+
batches.push(group.slice(i, i + VERIFY_BATCH_SIZE));
|
|
12187
|
+
}
|
|
11743
12188
|
}
|
|
11744
12189
|
const abortController = new AbortController;
|
|
11745
12190
|
const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, (reason) => abortController.abort(new Error(reason)), args.leaseAcquisition);
|
|
@@ -11803,7 +12248,9 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
|
|
|
11803
12248
|
let promptSettled = false;
|
|
11804
12249
|
const startedAt = Date.now();
|
|
11805
12250
|
try {
|
|
11806
|
-
const
|
|
12251
|
+
const headAtPrompt = await readGitHead(args.sessionDirectory);
|
|
12252
|
+
const evidence = !args.forceBroad && batch.every((m) => m.verifiedAt) ? await buildVerifyDiffEvidence(args.sessionDirectory, batch) : null;
|
|
12253
|
+
const prompt = buildVerifyPrompt(args.projectIdentity, batch, evidence);
|
|
11807
12254
|
if (args.hiddenCompletionExecutor) {
|
|
11808
12255
|
const run = await runHiddenSingleShotPrompt({
|
|
11809
12256
|
executor: args.hiddenCompletionExecutor,
|
|
@@ -11814,6 +12261,7 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
|
|
|
11814
12261
|
prompt,
|
|
11815
12262
|
title: "magic-context-dream-verify",
|
|
11816
12263
|
callContext: "dreamer:verify",
|
|
12264
|
+
metadata: { tokenBudget: args.tokenBudget, onBudgetUpdate: args.onBudgetUpdate },
|
|
11817
12265
|
model: args.model,
|
|
11818
12266
|
fallbackModels: args.fallbackModels,
|
|
11819
12267
|
language: args.language,
|
|
@@ -11830,7 +12278,7 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
|
|
|
11830
12278
|
status: "completed",
|
|
11831
12279
|
messages: run.completion.messages ?? []
|
|
11832
12280
|
});
|
|
11833
|
-
return applyParsedVerifyManifest(args, batch, run.validated);
|
|
12281
|
+
return applyParsedVerifyManifest(args, batch, run.validated, evidence?.head ?? headAtPrompt);
|
|
11834
12282
|
}
|
|
11835
12283
|
const client = args.client;
|
|
11836
12284
|
if (!client)
|
|
@@ -11858,7 +12306,10 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
|
|
|
11858
12306
|
parts: [{ type: "text", text: prompt, synthetic: true }]
|
|
11859
12307
|
}
|
|
11860
12308
|
}, {
|
|
11861
|
-
transport: createPromptAsyncTransport(client, agentSessionId
|
|
12309
|
+
transport: createPromptAsyncTransport(client, agentSessionId, {
|
|
12310
|
+
tokenBudget: args.tokenBudget,
|
|
12311
|
+
onBudgetUpdate: args.onBudgetUpdate
|
|
12312
|
+
}),
|
|
11862
12313
|
timeoutMs: sliceMs,
|
|
11863
12314
|
signal,
|
|
11864
12315
|
fallbackModels: args.fallbackModels,
|
|
@@ -11887,7 +12338,7 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
|
|
|
11887
12338
|
});
|
|
11888
12339
|
promptSettled = true;
|
|
11889
12340
|
recordInvocation4(args, startedAt, { status: "completed", messages: run.output });
|
|
11890
|
-
return await applyParsedVerifyManifest(args, batch, run.validated);
|
|
12341
|
+
return await applyParsedVerifyManifest(args, batch, run.validated, evidence?.head ?? headAtPrompt);
|
|
11891
12342
|
} catch (error) {
|
|
11892
12343
|
const desc = describeError(error);
|
|
11893
12344
|
const providerFailure = error instanceof DreamerProviderOutputFailureError ? error : undefined;
|
|
@@ -11898,7 +12349,7 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
|
|
|
11898
12349
|
error,
|
|
11899
12350
|
messages: await readFailedChildMessages(args.client, agentSessionId, args.sessionDirectory)
|
|
11900
12351
|
});
|
|
11901
|
-
if (error instanceof DreamerModuleFailureError || signal.aborted)
|
|
12352
|
+
if (error instanceof DreamerModuleFailureError || error instanceof DreamTokenBudgetExceeded || signal.aborted)
|
|
11902
12353
|
throw error;
|
|
11903
12354
|
if (promptFailure?.failureClass === "provider_timeout" || isPromptTimeoutError(error)) {
|
|
11904
12355
|
return {
|
|
@@ -11933,7 +12384,7 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
|
|
|
11933
12384
|
});
|
|
11934
12385
|
}
|
|
11935
12386
|
}
|
|
11936
|
-
async function applyParsedVerifyManifest(args, batch, parsed) {
|
|
12387
|
+
async function applyParsedVerifyManifest(args, batch, parsed, headAtPrompt) {
|
|
11937
12388
|
const batchIds = new Set(batch.map((m) => m.id));
|
|
11938
12389
|
const batchById = new Map(batch.map((memory) => [memory.id, memory]));
|
|
11939
12390
|
const valid = {
|
|
@@ -11960,6 +12411,7 @@ async function applyParsedVerifyManifest(args, batch, parsed) {
|
|
|
11960
12411
|
return { verified: 0, updated: 0, archived: 0, skipped: 0, refused: 0 };
|
|
11961
12412
|
}
|
|
11962
12413
|
const now = Date.now();
|
|
12414
|
+
const verifiedHead = headAtPrompt === undefined ? await readGitHead(args.sessionDirectory) : headAtPrompt;
|
|
11963
12415
|
const writes = [];
|
|
11964
12416
|
let refused = 0;
|
|
11965
12417
|
const skipped = valid.skipped.length;
|
|
@@ -12071,6 +12523,8 @@ async function applyParsedVerifyManifest(args, batch, parsed) {
|
|
|
12071
12523
|
continue;
|
|
12072
12524
|
if (w.kind === "verify") {
|
|
12073
12525
|
recordMemoryVerifications(args.db, w.id, w.files, now);
|
|
12526
|
+
if (verifiedHead)
|
|
12527
|
+
recordVerifiedCommit(args.db, memory, now, verifiedHead);
|
|
12074
12528
|
verified += 1;
|
|
12075
12529
|
} else if (w.kind === "update") {
|
|
12076
12530
|
rewriteMemoryContent(args.db, memory, w.content, w.hash);
|
|
@@ -12095,6 +12549,15 @@ async function applyParsedVerifyManifest(args, batch, parsed) {
|
|
|
12095
12549
|
});
|
|
12096
12550
|
return { verified, updated, archived, skipped, refused };
|
|
12097
12551
|
}
|
|
12552
|
+
function recordVerifiedCommit(db, memory, at, head) {
|
|
12553
|
+
let metadata = {};
|
|
12554
|
+
try {
|
|
12555
|
+
const parsed = JSON.parse(memory.metadataJson ?? "{}");
|
|
12556
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
|
|
12557
|
+
metadata = parsed;
|
|
12558
|
+
} catch {}
|
|
12559
|
+
db.prepare("UPDATE memories SET metadata_json = ? WHERE id = ?").run(JSON.stringify({ ...metadata, dreamerVerifiedAt: at, dreamerVerifiedCommit: head }), memory.id);
|
|
12560
|
+
}
|
|
12098
12561
|
async function normalizeFiles(args, rawFiles) {
|
|
12099
12562
|
if (rawFiles.length === 0)
|
|
12100
12563
|
return [];
|
|
@@ -12152,6 +12615,16 @@ function dreamRunFailureDetail(error) {
|
|
|
12152
12615
|
}
|
|
12153
12616
|
const described = describeError(error);
|
|
12154
12617
|
const message = described.brief;
|
|
12618
|
+
if (error instanceof DreamTokenBudgetExceeded) {
|
|
12619
|
+
return {
|
|
12620
|
+
failure_class: "token_budget",
|
|
12621
|
+
model_attempted: null,
|
|
12622
|
+
models_tried: [],
|
|
12623
|
+
provider_error: null,
|
|
12624
|
+
timeout_ms: null,
|
|
12625
|
+
child_session_id: error.sessionId
|
|
12626
|
+
};
|
|
12627
|
+
}
|
|
12155
12628
|
if (error instanceof Error && error.name === "HiddenAgentStepLimit") {
|
|
12156
12629
|
return {
|
|
12157
12630
|
failure_class: "step_limit",
|
|
@@ -12196,7 +12669,10 @@ function toCuratePromptMemory(memory, verificationById) {
|
|
|
12196
12669
|
category: memory.category,
|
|
12197
12670
|
content: memory.content,
|
|
12198
12671
|
mappedFiles: verification?.files ?? [],
|
|
12199
|
-
hasNoFileSentinel: verification?.hasSentinel ?? false
|
|
12672
|
+
hasNoFileSentinel: verification?.hasSentinel ?? false,
|
|
12673
|
+
importance: memory.importance,
|
|
12674
|
+
retrievalCount: memory.retrievalCount,
|
|
12675
|
+
seenCount: memory.seenCount
|
|
12200
12676
|
};
|
|
12201
12677
|
}
|
|
12202
12678
|
function loadActiveMemoryPromptMemories(db, projectIdentity) {
|
|
@@ -12236,7 +12712,10 @@ function inspectCurateMemoryOperations(messages) {
|
|
|
12236
12712
|
if (!isRecord(part.state) || part.state.status !== "completed")
|
|
12237
12713
|
continue;
|
|
12238
12714
|
const input = isRecord(part.state.input) ? part.state.input : null;
|
|
12239
|
-
|
|
12715
|
+
const action = input?.action;
|
|
12716
|
+
const output = typeof part.state.output === "string" ? part.state.output : "";
|
|
12717
|
+
if (output === "completed" && (action === "merge" || action === "archive" || action === "update") || action === "merge" && output.startsWith("Merged memories") || action === "archive" && output.startsWith("Archived memor") || action === "update" && output.startsWith("Updated memory"))
|
|
12718
|
+
summary.completedActions.push(action);
|
|
12240
12719
|
}
|
|
12241
12720
|
}
|
|
12242
12721
|
return summary;
|
|
@@ -12284,6 +12763,7 @@ function createDreamTaskExecutor(deps) {
|
|
|
12284
12763
|
};
|
|
12285
12764
|
return async (config, ctx) => {
|
|
12286
12765
|
const { db, projectIdentity, holderId, leaseKey } = ctx;
|
|
12766
|
+
deps.client?.resetDreamTokenBudget?.(config.task);
|
|
12287
12767
|
const startedAt = Date.now();
|
|
12288
12768
|
const leaseAcquisition = ctx.leaseAcquisition ?? acquireLeaseWithAcquisition(db, holderId, leaseKey) ?? (() => {
|
|
12289
12769
|
throw new Error("Dream lease unavailable during executor setup");
|
|
@@ -12342,8 +12822,23 @@ function createDreamTaskExecutor(deps) {
|
|
|
12342
12822
|
if (!leaseOwnershipMatches(db, holderId, leaseAcquisition.generation, leaseKey)) {
|
|
12343
12823
|
throw new Error("Dream lease lost during executor setup");
|
|
12344
12824
|
}
|
|
12825
|
+
let budgetSpent = 0;
|
|
12826
|
+
let budgetFinalized = false;
|
|
12827
|
+
const childSpending = new Map;
|
|
12828
|
+
const onBudgetUpdate = (state) => {
|
|
12829
|
+
if (state.sessionId) {
|
|
12830
|
+
const previous = childSpending.get(state.sessionId) ?? 0;
|
|
12831
|
+
budgetSpent += Math.max(0, state.spent - previous);
|
|
12832
|
+
childSpending.set(state.sessionId, state.spent);
|
|
12833
|
+
} else
|
|
12834
|
+
budgetSpent = Math.max(budgetSpent, state.spent);
|
|
12835
|
+
budgetFinalized ||= state.finalizeFired;
|
|
12836
|
+
};
|
|
12345
12837
|
const recordRun = (status, error, extra) => {
|
|
12346
12838
|
try {
|
|
12839
|
+
const hostBudget = deps.readTokenBudget?.(config.task) ?? deps.client?.readTokenBudget?.(config.task);
|
|
12840
|
+
if (hostBudget)
|
|
12841
|
+
onBudgetUpdate(hostBudget);
|
|
12347
12842
|
insertDreamRun(db, {
|
|
12348
12843
|
projectPath: projectIdentity,
|
|
12349
12844
|
startedAt,
|
|
@@ -12359,6 +12854,14 @@ function createDreamTaskExecutor(deps) {
|
|
|
12359
12854
|
failure: extra?.failure ?? dreamRunFailureDetail(error ?? "unknown dreamer failure")
|
|
12360
12855
|
} : {},
|
|
12361
12856
|
...extra?.progress ? { progress: extra.progress } : {},
|
|
12857
|
+
...config.tokenBudget ? {
|
|
12858
|
+
tokenBudget: {
|
|
12859
|
+
budget: config.tokenBudget,
|
|
12860
|
+
spent: budgetSpent,
|
|
12861
|
+
finalizeFired: budgetFinalized,
|
|
12862
|
+
banked: extra?.banked ?? processedDreamTaskItems(backlogAtStart.pending, (extra?.backlogAfter ?? getDreamTaskBacklog(db, projectIdentity, config.task)).pending)
|
|
12863
|
+
}
|
|
12864
|
+
} : {},
|
|
12362
12865
|
backlog: (() => {
|
|
12363
12866
|
const end = extra?.backlogAfter ?? getDreamTaskBacklog(db, projectIdentity, config.task);
|
|
12364
12867
|
const processed = processedDreamTaskItems(backlogAtStart.pending, end.pending);
|
|
@@ -12449,7 +12952,7 @@ function createDreamTaskExecutor(deps) {
|
|
|
12449
12952
|
leaseKey,
|
|
12450
12953
|
deadline,
|
|
12451
12954
|
leaseAcquisition,
|
|
12452
|
-
promotionThreshold: config.promotionThreshold ??
|
|
12955
|
+
promotionThreshold: config.promotionThreshold ?? DREAM_TASK_PROMOTION_DEFAULTS["review-user-memories"],
|
|
12453
12956
|
model: config.model,
|
|
12454
12957
|
fallbackModels: config.fallbackModels,
|
|
12455
12958
|
language: config.language ?? deps.language
|
|
@@ -12473,6 +12976,8 @@ function createDreamTaskExecutor(deps) {
|
|
|
12473
12976
|
model: config.model,
|
|
12474
12977
|
fallbackModels: config.fallbackModels,
|
|
12475
12978
|
moduleRoute,
|
|
12979
|
+
tokenBudget: config.tokenBudget,
|
|
12980
|
+
onBudgetUpdate,
|
|
12476
12981
|
onProgress: (processed) => reportProgress(processed)
|
|
12477
12982
|
});
|
|
12478
12983
|
log(`[dreamer] map-memories: committed=${result.mapped + result.independent} mapped=${result.mapped} independent=${result.independent} batches=${result.batches} remaining=${result.remaining} complete=${result.complete}${result.stopReason ? ` stop_reason=${result.stopReason}` : ""}`);
|
|
@@ -12513,6 +13018,8 @@ function createDreamTaskExecutor(deps) {
|
|
|
12513
13018
|
fallbackModels: config.fallbackModels,
|
|
12514
13019
|
language: config.language ?? deps.language,
|
|
12515
13020
|
moduleRoute,
|
|
13021
|
+
tokenBudget: config.tokenBudget,
|
|
13022
|
+
onBudgetUpdate,
|
|
12516
13023
|
onProgress: (processed, refused) => reportProgress(processed, refused)
|
|
12517
13024
|
});
|
|
12518
13025
|
const processed = result.verified + result.updated + result.archived + result.skipped + result.refused;
|
|
@@ -12524,7 +13031,8 @@ function createDreamTaskExecutor(deps) {
|
|
|
12524
13031
|
recordRun("completed", null, {
|
|
12525
13032
|
progress: broadProgress,
|
|
12526
13033
|
memoryChanges: computeMemoryDelta(memoryBefore),
|
|
12527
|
-
backlogAfter
|
|
13034
|
+
backlogAfter,
|
|
13035
|
+
banked: result.verified + result.updated + result.archived
|
|
12528
13036
|
});
|
|
12529
13037
|
return { status: "completed" };
|
|
12530
13038
|
}
|
|
@@ -12533,14 +13041,16 @@ function createDreamTaskExecutor(deps) {
|
|
|
12533
13041
|
recordRun("failed", error, {
|
|
12534
13042
|
progress: verificationProgress,
|
|
12535
13043
|
memoryChanges: computeMemoryDelta(memoryBefore),
|
|
12536
|
-
backlogAfter
|
|
13044
|
+
backlogAfter,
|
|
13045
|
+
banked: result.verified + result.updated + result.archived
|
|
12537
13046
|
});
|
|
12538
13047
|
return { status: "failed", transient: !batchTimedOut, error };
|
|
12539
13048
|
}
|
|
12540
13049
|
recordRun("completed", null, {
|
|
12541
13050
|
progress: verificationProgress,
|
|
12542
13051
|
memoryChanges: computeMemoryDelta(memoryBefore),
|
|
12543
|
-
backlogAfter
|
|
13052
|
+
backlogAfter,
|
|
13053
|
+
banked: result.verified + result.updated + result.archived
|
|
12544
13054
|
});
|
|
12545
13055
|
return { status: "completed" };
|
|
12546
13056
|
}
|
|
@@ -12592,7 +13102,7 @@ function createDreamTaskExecutor(deps) {
|
|
|
12592
13102
|
leaseKey,
|
|
12593
13103
|
deadline,
|
|
12594
13104
|
leaseAcquisition,
|
|
12595
|
-
promotionThreshold: config.promotionThreshold ??
|
|
13105
|
+
promotionThreshold: config.promotionThreshold ?? DREAM_TASK_PROMOTION_DEFAULTS["promote-primers"],
|
|
12596
13106
|
ensureProjectRegistered: deps.ensureProjectRegistered
|
|
12597
13107
|
});
|
|
12598
13108
|
recordRun("completed", null);
|
|
@@ -12615,6 +13125,8 @@ function createDreamTaskExecutor(deps) {
|
|
|
12615
13125
|
fallbackModels: config.fallbackModels,
|
|
12616
13126
|
language: config.language ?? deps.language,
|
|
12617
13127
|
rawProviderFactory: deps.primerRawProviderFactory,
|
|
13128
|
+
tokenBudget: config.tokenBudget,
|
|
13129
|
+
onBudgetUpdate,
|
|
12618
13130
|
onProgress: (processed) => reportProgress(processed)
|
|
12619
13131
|
});
|
|
12620
13132
|
recordRun("completed", null);
|
|
@@ -12645,6 +13157,7 @@ function createDreamTaskExecutor(deps) {
|
|
|
12645
13157
|
if (config.task === "retrospective") {
|
|
12646
13158
|
const memoryBefore = getMemoryCountsByStatus(db, projectIdentity);
|
|
12647
13159
|
const retro = await runRetrospectiveTask(config, ctx, {
|
|
13160
|
+
onBudgetUpdate,
|
|
12648
13161
|
deps,
|
|
12649
13162
|
deadline,
|
|
12650
13163
|
parent,
|
|
@@ -12652,7 +13165,10 @@ function createDreamTaskExecutor(deps) {
|
|
|
12652
13165
|
moduleRoute
|
|
12653
13166
|
});
|
|
12654
13167
|
recordRun("completed", null, {
|
|
12655
|
-
memoryChanges: computeMemoryDelta(memoryBefore)
|
|
13168
|
+
memoryChanges: computeMemoryDelta(memoryBefore),
|
|
13169
|
+
backlogAfter: retro.retrospectiveWatermarkMs == null ? undefined : getDreamTaskBacklog(db, projectIdentity, "retrospective", {
|
|
13170
|
+
retrospectiveWatermarkMs: retro.retrospectiveWatermarkMs
|
|
13171
|
+
})
|
|
12656
13172
|
});
|
|
12657
13173
|
return {
|
|
12658
13174
|
status: "completed",
|
|
@@ -12665,6 +13181,7 @@ function createDreamTaskExecutor(deps) {
|
|
|
12665
13181
|
};
|
|
12666
13182
|
}
|
|
12667
13183
|
return await runAgenticTask(config, ctx, {
|
|
13184
|
+
onBudgetUpdate,
|
|
12668
13185
|
deps,
|
|
12669
13186
|
deadline,
|
|
12670
13187
|
parent,
|
|
@@ -12675,6 +13192,9 @@ function createDreamTaskExecutor(deps) {
|
|
|
12675
13192
|
moduleRoute
|
|
12676
13193
|
});
|
|
12677
13194
|
} catch (error) {
|
|
13195
|
+
if (error instanceof DreamTokenBudgetExceeded) {
|
|
13196
|
+
budgetSpent = Math.max(budgetSpent, error.spent);
|
|
13197
|
+
}
|
|
12678
13198
|
const classified = classifyFailure(error);
|
|
12679
13199
|
const retrospectiveOverflow = error instanceof RetrospectivePromptOverflowError ? error : null;
|
|
12680
13200
|
const transient = retrospectiveOverflow ? true : classified.transient;
|
|
@@ -12833,7 +13353,7 @@ function resolveRetrospectiveUsableInputTokens(config, deps) {
|
|
|
12833
13353
|
}
|
|
12834
13354
|
async function runRetrospectiveTask(config, ctx, helpers) {
|
|
12835
13355
|
const { db, projectIdentity, holderId, leaseKey } = ctx;
|
|
12836
|
-
const { deps, deadline, parent } = helpers;
|
|
13356
|
+
const { deps, deadline, parent, onBudgetUpdate } = helpers;
|
|
12837
13357
|
const provider = resolveRetrospectiveProvider(deps, db, projectIdentity);
|
|
12838
13358
|
if (!provider) {
|
|
12839
13359
|
log("[dreamer] retrospective: no raw provider available — clean no-op");
|
|
@@ -12887,7 +13407,8 @@ async function runRetrospectiveTask(config, ctx, helpers) {
|
|
|
12887
13407
|
],
|
|
12888
13408
|
timeoutMs: Math.max(1, deadline - Date.now()),
|
|
12889
13409
|
title: "magic-context-dream-retrospective",
|
|
12890
|
-
directory: deps.sessionDirectory
|
|
13410
|
+
directory: deps.sessionDirectory,
|
|
13411
|
+
metadata: { tokenBudget: config.tokenBudget, onBudgetUpdate }
|
|
12891
13412
|
});
|
|
12892
13413
|
childSessionId = hiddenHandle.id;
|
|
12893
13414
|
} else {
|
|
@@ -12923,6 +13444,12 @@ async function runRetrospectiveTask(config, ctx, helpers) {
|
|
|
12923
13444
|
signal: abortController.signal,
|
|
12924
13445
|
fallbackModels: config.fallbackModels,
|
|
12925
13446
|
callContext: "dreamer:retrospective",
|
|
13447
|
+
...!hiddenHandle && deps.client ? {
|
|
13448
|
+
transport: createPromptAsyncTransport(deps.client, sessionId, {
|
|
13449
|
+
tokenBudget: config.tokenBudget,
|
|
13450
|
+
onBudgetUpdate
|
|
13451
|
+
})
|
|
13452
|
+
} : {},
|
|
12926
13453
|
...hiddenHandle && deps.hiddenCompletionExecutor ? {
|
|
12927
13454
|
transport: Object.assign((request) => deps.hiddenCompletionExecutor?.attempt(hiddenHandle, request) ?? Promise.reject(new Error("Hidden transport unavailable")), { childSessionId: hiddenHandle.childSessionId })
|
|
12928
13455
|
} : {},
|
|
@@ -13098,7 +13625,7 @@ async function runRetrospectiveTask(config, ctx, helpers) {
|
|
|
13098
13625
|
}
|
|
13099
13626
|
async function runAgenticTask(config, ctx, helpers) {
|
|
13100
13627
|
const { db, projectIdentity, holderId, leaseKey } = ctx;
|
|
13101
|
-
const { deps, deadline, parent } = helpers;
|
|
13628
|
+
const { deps, deadline, parent, onBudgetUpdate } = helpers;
|
|
13102
13629
|
const task = config.task;
|
|
13103
13630
|
const docsDir = deps.sessionDirectory;
|
|
13104
13631
|
const invocationStartedAt = Date.now();
|
|
@@ -13152,122 +13679,178 @@ async function runAgenticTask(config, ctx, helpers) {
|
|
|
13152
13679
|
return { status: "completed", detail: progress };
|
|
13153
13680
|
}
|
|
13154
13681
|
}
|
|
13155
|
-
if (task === "maintain-docs" &&
|
|
13156
|
-
const reason =
|
|
13157
|
-
log(`[dreamer] maintain-docs skipped: ${reason}`);
|
|
13682
|
+
if (task === "maintain-docs" && !changes) {
|
|
13683
|
+
const reason = "no relevant commits or no git repository";
|
|
13158
13684
|
helpers.recordRun("completed", null, { progress: reason });
|
|
13159
13685
|
return { status: "completed", detail: reason };
|
|
13160
13686
|
}
|
|
13161
|
-
|
|
13162
|
-
|
|
13163
|
-
|
|
13164
|
-
|
|
13165
|
-
|
|
13166
|
-
|
|
13167
|
-
|
|
13168
|
-
|
|
13169
|
-
}
|
|
13170
|
-
|
|
13171
|
-
|
|
13172
|
-
|
|
13173
|
-
|
|
13174
|
-
|
|
13175
|
-
|
|
13176
|
-
|
|
13177
|
-
|
|
13178
|
-
|
|
13179
|
-
|
|
13180
|
-
|
|
13181
|
-
|
|
13182
|
-
|
|
13183
|
-
|
|
13184
|
-
|
|
13185
|
-
|
|
13186
|
-
|
|
13187
|
-
|
|
13188
|
-
|
|
13189
|
-
|
|
13190
|
-
|
|
13191
|
-
|
|
13192
|
-
|
|
13193
|
-
|
|
13194
|
-
|
|
13195
|
-
|
|
13196
|
-
|
|
13197
|
-
|
|
13198
|
-
|
|
13199
|
-
|
|
13200
|
-
|
|
13201
|
-
|
|
13202
|
-
|
|
13203
|
-
|
|
13204
|
-
const createResponse = await createChildSessionWithFence({
|
|
13205
|
-
client: requireDreamClient(deps.client),
|
|
13206
|
-
db,
|
|
13207
|
-
parentSessionId: parent ?? undefined,
|
|
13208
|
-
title: `magic-context-dream-${task}`,
|
|
13209
|
-
directory: docsDir
|
|
13210
|
-
});
|
|
13211
|
-
const created = normalizeSDKResponse(createResponse, null, {
|
|
13212
|
-
preferResponseOnMissingData: true
|
|
13687
|
+
if (task === "maintain-docs" && changes && (changes.unchanged || !changes.relevant)) {
|
|
13688
|
+
const reason = changes.unchanged ? "no commits since last run" : "diff does not describe a documented area";
|
|
13689
|
+
helpers.recordRun("completed", null, { progress: reason });
|
|
13690
|
+
return {
|
|
13691
|
+
status: "completed",
|
|
13692
|
+
detail: reason,
|
|
13693
|
+
schedulePatch: { taskStateJson: JSON.stringify({ head: changes.head }) }
|
|
13694
|
+
};
|
|
13695
|
+
}
|
|
13696
|
+
if (task === "maintain-docs" && hasCurrentDocsProposal(docsDir)) {
|
|
13697
|
+
const reason = "pending proposal matches current docs";
|
|
13698
|
+
helpers.recordRun("completed", null, { progress: reason });
|
|
13699
|
+
return {
|
|
13700
|
+
status: "completed",
|
|
13701
|
+
detail: reason,
|
|
13702
|
+
...changes ? { schedulePatch: { taskStateJson: JSON.stringify({ head: changes.head }) } } : {}
|
|
13703
|
+
};
|
|
13704
|
+
}
|
|
13705
|
+
const curateChunks = curateMemories ? chunkCurateMemories(curateMemories, Math.max(4000, Math.floor(resolveRetrospectiveUsableInputTokens(config, deps) * 2))) : [];
|
|
13706
|
+
const batches = task === "curate" ? curateChunks : [{ memories: [], crossChunkCandidates: [] }];
|
|
13707
|
+
const completedActions = [];
|
|
13708
|
+
let curateRefused = 0;
|
|
13709
|
+
let run = {
|
|
13710
|
+
output: [],
|
|
13711
|
+
validated: ""
|
|
13712
|
+
};
|
|
13713
|
+
for (const batch of batches) {
|
|
13714
|
+
const taskPrompt = buildDreamTaskPrompt(task, {
|
|
13715
|
+
projectPath: projectIdentity,
|
|
13716
|
+
lastDreamAt: lastRunAt ? String(lastRunAt) : null,
|
|
13717
|
+
existingDocs,
|
|
13718
|
+
docsChangeSet: changes?.text,
|
|
13719
|
+
docsContents: task === "maintain-docs" ? {
|
|
13720
|
+
architecture: existingDocs?.architecture ? readFileSync5(`${docsDir}/ARCHITECTURE.md`, "utf8") : "(missing)",
|
|
13721
|
+
structure: existingDocs?.structure ? readFileSync5(`${docsDir}/STRUCTURE.md`, "utf8") : "(missing)"
|
|
13722
|
+
} : undefined,
|
|
13723
|
+
docsBudget: config.docsMaxTokens ?? 12000,
|
|
13724
|
+
docsCurrentTokens: maintainDocsSnapshot ? Math.ceil([...maintainDocsSnapshot.values()].join("").length / 3.5) : 0,
|
|
13725
|
+
curate: curateMemories && curateCategory ? {
|
|
13726
|
+
category: curateCategory,
|
|
13727
|
+
memories: batch.memories,
|
|
13728
|
+
crossChunkCandidates: batch.crossChunkCandidates
|
|
13729
|
+
} : undefined
|
|
13213
13730
|
});
|
|
13214
|
-
|
|
13215
|
-
|
|
13216
|
-
|
|
13217
|
-
|
|
13218
|
-
|
|
13219
|
-
run = await promptSyncWithValidatedOutputRetry(requireDreamClient(deps.client), {
|
|
13220
|
-
path: { id: sessionId },
|
|
13221
|
-
query: { directory: docsDir },
|
|
13222
|
-
body: {
|
|
13731
|
+
if (deps.hiddenCompletionExecutor) {
|
|
13732
|
+
const hidden = await runHiddenSingleShotPrompt({
|
|
13733
|
+
executor: deps.hiddenCompletionExecutor,
|
|
13734
|
+
parentSessionId: parent ?? undefined,
|
|
13735
|
+
sessionDirectory: docsDir,
|
|
13223
13736
|
agent: task === "maintain-docs" ? DREAMER_DOCS_AGENT : DREAMER_AGENT,
|
|
13224
|
-
system: task === "maintain-docs" ? MAINTAIN_DOCS_SYSTEM_PROMPT :
|
|
13225
|
-
|
|
13226
|
-
|
|
13227
|
-
|
|
13228
|
-
|
|
13229
|
-
|
|
13230
|
-
|
|
13231
|
-
|
|
13232
|
-
|
|
13233
|
-
|
|
13234
|
-
|
|
13235
|
-
|
|
13236
|
-
|
|
13237
|
-
|
|
13238
|
-
|
|
13737
|
+
system: task === "maintain-docs" ? MAINTAIN_DOCS_SYSTEM_PROMPT : CURATE_SYSTEM_PROMPT,
|
|
13738
|
+
prompt: taskPrompt,
|
|
13739
|
+
title: `magic-context-dream-${task}`,
|
|
13740
|
+
callContext: `dreamer:${task}`,
|
|
13741
|
+
metadata: { tokenBudget: config.tokenBudget, onBudgetUpdate },
|
|
13742
|
+
allowEmpty: task === "curate",
|
|
13743
|
+
model: config.model,
|
|
13744
|
+
fallbackModels: config.fallbackModels,
|
|
13745
|
+
language: task === "curate" ? config.language ?? deps.language : undefined,
|
|
13746
|
+
timeoutMs: Math.min(Math.max(0, deadline - Date.now()), config.timeoutMinutes * 60 * 1000),
|
|
13747
|
+
signal: abortController.signal,
|
|
13748
|
+
parse: (text, completion) => {
|
|
13749
|
+
if (task !== "curate")
|
|
13750
|
+
return text;
|
|
13751
|
+
const memoryOperations = inspectCurateMemoryOperations(completion.messages);
|
|
13752
|
+
if (text)
|
|
13753
|
+
validateCurateAssistantText(text);
|
|
13754
|
+
if (memoryOperations.totalCalls > 0 && memoryOperations.completedActions.length === 0)
|
|
13755
|
+
throw new Error("Curate returned no completed ctx_memory tool result.");
|
|
13756
|
+
if (!text && memoryOperations.completedActions.length === 0)
|
|
13757
|
+
throw new Error("Dreamer returned no assistant output.");
|
|
13758
|
+
return { text: text || null, memoryOperations };
|
|
13759
|
+
}
|
|
13760
|
+
});
|
|
13761
|
+
childSessionId = hidden.childSessionId;
|
|
13762
|
+
run = { output: hidden.completion.messages ?? [], validated: hidden.validated };
|
|
13763
|
+
promptSettled = true;
|
|
13764
|
+
} else {
|
|
13765
|
+
const createResponse = await createChildSessionWithFence({
|
|
13766
|
+
client: requireDreamClient(deps.client),
|
|
13767
|
+
db,
|
|
13768
|
+
parentSessionId: parent ?? undefined,
|
|
13769
|
+
title: `magic-context-dream-${task}`,
|
|
13770
|
+
directory: docsDir
|
|
13771
|
+
});
|
|
13772
|
+
const created = normalizeSDKResponse(createResponse, null, {
|
|
13773
|
+
preferResponseOnMissingData: true
|
|
13774
|
+
});
|
|
13775
|
+
childSessionId = typeof created?.id === "string" ? created.id : null;
|
|
13776
|
+
if (!childSessionId)
|
|
13777
|
+
throw new Error("Dreamer could not create its child session.");
|
|
13778
|
+
const sessionId = childSessionId;
|
|
13779
|
+
const remainingMs = Math.max(0, deadline - Date.now());
|
|
13780
|
+
run = await promptSyncWithValidatedOutputRetry(requireDreamClient(deps.client), {
|
|
13781
|
+
path: { id: sessionId },
|
|
13782
|
+
query: { directory: docsDir },
|
|
13783
|
+
body: {
|
|
13784
|
+
agent: task === "maintain-docs" ? DREAMER_DOCS_AGENT : DREAMER_AGENT,
|
|
13785
|
+
system: task === "maintain-docs" ? MAINTAIN_DOCS_SYSTEM_PROMPT : withContentLanguageDirective(CURATE_SYSTEM_PROMPT, config.language ?? deps.language),
|
|
13786
|
+
...modelBodyField(config.model),
|
|
13787
|
+
parts: [{ type: "text", text: taskPrompt, synthetic: true }]
|
|
13788
|
+
}
|
|
13789
|
+
}, {
|
|
13790
|
+
timeoutMs: Math.min(remainingMs, config.timeoutMinutes * 60 * 1000),
|
|
13791
|
+
signal: abortController.signal,
|
|
13792
|
+
fallbackModels: config.fallbackModels,
|
|
13793
|
+
callContext: `dreamer:${task}`,
|
|
13794
|
+
transport: createPromptAsyncTransport(requireDreamClient(deps.client), sessionId, { tokenBudget: config.tokenBudget, onBudgetUpdate }),
|
|
13795
|
+
fetchOutput: async () => {
|
|
13796
|
+
const messagesResponse = await requireDreamClient(deps.client).session.messages({
|
|
13797
|
+
path: { id: sessionId },
|
|
13798
|
+
query: {
|
|
13799
|
+
directory: docsDir,
|
|
13800
|
+
...task === "curate" ? {} : { limit: 50 }
|
|
13801
|
+
}
|
|
13802
|
+
});
|
|
13803
|
+
return normalizeSDKResponse(messagesResponse, [], {
|
|
13804
|
+
preferResponseOnMissingData: true
|
|
13805
|
+
});
|
|
13806
|
+
},
|
|
13807
|
+
validateOutput: (messages) => {
|
|
13808
|
+
const text = extractLatestAssistantText(messages);
|
|
13809
|
+
if (task !== "curate") {
|
|
13810
|
+
if (!text)
|
|
13811
|
+
throw new Error("Dreamer returned no assistant output.");
|
|
13812
|
+
return text;
|
|
13813
|
+
}
|
|
13814
|
+
const memoryOperations = inspectCurateMemoryOperations(messages);
|
|
13815
|
+
if (text)
|
|
13816
|
+
validateCurateAssistantText(text);
|
|
13817
|
+
if (memoryOperations.completedActions.length > 0) {
|
|
13818
|
+
return { text, memoryOperations };
|
|
13239
13819
|
}
|
|
13240
|
-
});
|
|
13241
|
-
return normalizeSDKResponse(messagesResponse, [], {
|
|
13242
|
-
preferResponseOnMissingData: true
|
|
13243
|
-
});
|
|
13244
|
-
},
|
|
13245
|
-
validateOutput: (messages) => {
|
|
13246
|
-
const text = extractLatestAssistantText(messages);
|
|
13247
|
-
if (task !== "curate") {
|
|
13248
13820
|
if (!text)
|
|
13249
13821
|
throw new Error("Dreamer returned no assistant output.");
|
|
13250
|
-
|
|
13251
|
-
|
|
13252
|
-
|
|
13253
|
-
if (text)
|
|
13254
|
-
validateCurateAssistantText(text);
|
|
13255
|
-
if (memoryOperations.completedActions.length > 0) {
|
|
13822
|
+
if (memoryOperations.totalCalls > 0) {
|
|
13823
|
+
throw new Error("Curate returned no completed ctx_memory tool result.");
|
|
13824
|
+
}
|
|
13256
13825
|
return { text, memoryOperations };
|
|
13257
13826
|
}
|
|
13258
|
-
|
|
13259
|
-
|
|
13260
|
-
|
|
13261
|
-
|
|
13262
|
-
|
|
13263
|
-
|
|
13264
|
-
|
|
13265
|
-
|
|
13266
|
-
|
|
13827
|
+
});
|
|
13828
|
+
promptSettled = true;
|
|
13829
|
+
}
|
|
13830
|
+
if (task === "curate") {
|
|
13831
|
+
completedActions.push(...run.validated.memoryOperations.completedActions);
|
|
13832
|
+
curateRefused += takeCurateSafetyRefusalCount(childSessionId ?? "");
|
|
13833
|
+
}
|
|
13834
|
+
if (childSessionId && !deps.hiddenCompletionExecutor) {
|
|
13835
|
+
await teardownChildSession({
|
|
13836
|
+
client: requireDreamClient(deps.client),
|
|
13837
|
+
sessionId: childSessionId,
|
|
13838
|
+
sessionDirectory: docsDir,
|
|
13839
|
+
promptSettled,
|
|
13840
|
+
privacySensitive: true,
|
|
13841
|
+
context: `[dreamer] ${task}`,
|
|
13842
|
+
log
|
|
13843
|
+
});
|
|
13844
|
+
childSessionId = null;
|
|
13845
|
+
}
|
|
13267
13846
|
}
|
|
13847
|
+
if (task === "curate")
|
|
13848
|
+
run.validated = {
|
|
13849
|
+
text: null,
|
|
13850
|
+
memoryOperations: { totalCalls: completedActions.length, completedActions }
|
|
13851
|
+
};
|
|
13268
13852
|
if (leaseLost)
|
|
13269
13853
|
throw new Error("Dream lease lost during task");
|
|
13270
|
-
const curateRefused = task === "curate" ? takeCurateSafetyRefusalCount(childSessionId ?? "") : 0;
|
|
13271
13854
|
if (curateRefused > 0) {
|
|
13272
13855
|
helpers.reportProgress(curateRefused, curateRefused);
|
|
13273
13856
|
log(`[dreamer] curate safety summary: refused=${curateRefused}`);
|
|
@@ -13288,7 +13871,11 @@ async function runAgenticTask(config, ctx, helpers) {
|
|
|
13288
13871
|
try {
|
|
13289
13872
|
if (run.validated === "[]") {
|
|
13290
13873
|
helpers.recordRun("completed", null, { progress: "no corrections proposed" });
|
|
13291
|
-
return {
|
|
13874
|
+
return {
|
|
13875
|
+
status: "completed",
|
|
13876
|
+
detail: "no corrections proposed",
|
|
13877
|
+
schedulePatch: { taskStateJson: JSON.stringify({ head: changes.head }) }
|
|
13878
|
+
};
|
|
13292
13879
|
}
|
|
13293
13880
|
const path = writeDocsProposal(docsDir, String(run.validated), config.docsMaxTokens ?? 12000, docsHashes, changes.head);
|
|
13294
13881
|
helpers.recordRun("completed", null, { progress: `proposal: ${path}` });
|
|
@@ -13506,7 +14093,7 @@ async function runDomainGroup(deps, group, cb) {
|
|
|
13506
14093
|
projectIdentity,
|
|
13507
14094
|
lastRunAt: readLastRunAt(db, projectIdentity, due.config.task),
|
|
13508
14095
|
retrospectiveWatermarkMs: readRetrospectiveWatermark(db, projectIdentity, due.config.task),
|
|
13509
|
-
promotionThreshold: due.config.promotionThreshold ??
|
|
14096
|
+
promotionThreshold: due.config.promotionThreshold ?? (due.config.task === "promote-primers" ? DREAM_TASK_PROMOTION_DEFAULTS["promote-primers"] : DREAM_TASK_PROMOTION_DEFAULTS["review-user-memories"])
|
|
13510
14097
|
});
|
|
13511
14098
|
if (!gatePass) {
|
|
13512
14099
|
advanceAfterRun(db, projectIdentity, due, Date.now(), "skipped", null);
|
|
@@ -13586,7 +14173,7 @@ async function runManualDream(deps) {
|
|
|
13586
14173
|
projectIdentity: deps.projectIdentity,
|
|
13587
14174
|
lastRunAt: readLastRunAt(deps.db, deps.projectIdentity, d.config.task),
|
|
13588
14175
|
retrospectiveWatermarkMs: readRetrospectiveWatermark(deps.db, deps.projectIdentity, d.config.task),
|
|
13589
|
-
promotionThreshold: d.config.promotionThreshold ??
|
|
14176
|
+
promotionThreshold: d.config.promotionThreshold ?? (d.config.task === "promote-primers" ? DREAM_TASK_PROMOTION_DEFAULTS["promote-primers"] : DREAM_TASK_PROMOTION_DEFAULTS["review-user-memories"])
|
|
13590
14177
|
});
|
|
13591
14178
|
if (pass)
|
|
13592
14179
|
gated.push(d);
|
|
@@ -13642,7 +14229,7 @@ async function runDueTasksForProject(deps) {
|
|
|
13642
14229
|
projectIdentity: deps.projectIdentity,
|
|
13643
14230
|
lastRunAt: readLastRunAt(deps.db, deps.projectIdentity, d.config.task),
|
|
13644
14231
|
retrospectiveWatermarkMs: readRetrospectiveWatermark(deps.db, deps.projectIdentity, d.config.task),
|
|
13645
|
-
promotionThreshold: d.config.promotionThreshold ??
|
|
14232
|
+
promotionThreshold: d.config.promotionThreshold ?? (d.config.task === "promote-primers" ? DREAM_TASK_PROMOTION_DEFAULTS["promote-primers"] : DREAM_TASK_PROMOTION_DEFAULTS["review-user-memories"])
|
|
13646
14233
|
});
|
|
13647
14234
|
if (pass) {
|
|
13648
14235
|
gated.push(d);
|
|
@@ -14269,7 +14856,7 @@ import * as childProcess from "node:child_process";
|
|
|
14269
14856
|
import {
|
|
14270
14857
|
existsSync as existsSync7,
|
|
14271
14858
|
mkdtempSync as mkdtempSync2,
|
|
14272
|
-
readFileSync as
|
|
14859
|
+
readFileSync as readFileSync6,
|
|
14273
14860
|
realpathSync as realpathSync3,
|
|
14274
14861
|
rmSync as rmSync2,
|
|
14275
14862
|
statSync as statSync4,
|
|
@@ -14298,7 +14885,7 @@ var DEFAULT_PI_RESOLUTION_FS = {
|
|
|
14298
14885
|
existsSync: existsSync7,
|
|
14299
14886
|
statSync: statSync4,
|
|
14300
14887
|
realpathSync: realpathSync3,
|
|
14301
|
-
readFileSync:
|
|
14888
|
+
readFileSync: readFileSync6
|
|
14302
14889
|
};
|
|
14303
14890
|
function packageHarnessKind(packageName) {
|
|
14304
14891
|
if (packageName === "@oh-my-pi/pi-coding-agent")
|
|
@@ -14595,12 +15182,6 @@ var DREAMER_ACTION_AGENTS = new Set([
|
|
|
14595
15182
|
"dreamer",
|
|
14596
15183
|
"magic-context-dreamer"
|
|
14597
15184
|
]);
|
|
14598
|
-
var HISTORIAN_AGENTS = new Set([
|
|
14599
|
-
"magic-context-historian",
|
|
14600
|
-
"historian",
|
|
14601
|
-
"historian-recomp",
|
|
14602
|
-
"historian-editor"
|
|
14603
|
-
]);
|
|
14604
15185
|
var SEARCH_ONLY_SUBAGENT_TOOL_AGENTS = new Set([
|
|
14605
15186
|
"dreamer-retrospective",
|
|
14606
15187
|
"dreamer-primer-investigator"
|
|
@@ -14790,7 +15371,15 @@ class PiSubagentRunner {
|
|
|
14790
15371
|
async runOnce(options, runMode, modelRefOverride) {
|
|
14791
15372
|
const startTime = Date.now();
|
|
14792
15373
|
let recordedAccounting = false;
|
|
15374
|
+
const cap = subagentStepCap(options.agent);
|
|
15375
|
+
const budget = options.tokenBudget ? createDreamTokenBudget(options.tokenBudget) : undefined;
|
|
15376
|
+
const telemetry = new SubagentTelemetry(options.systemPrompt, (line) => sessionLog(options.accountingSessionId ?? "subagent", line));
|
|
15377
|
+
let loggedTelemetry = false;
|
|
14793
15378
|
const recordAccounting = (result, messages = []) => {
|
|
15379
|
+
if (!loggedTelemetry) {
|
|
15380
|
+
loggedTelemetry = true;
|
|
15381
|
+
telemetry.finish(options.agent, result.ok ? "completed" : result.reason, cap);
|
|
15382
|
+
}
|
|
14794
15383
|
if (!options.accountingSessionId || recordedAccounting)
|
|
14795
15384
|
return;
|
|
14796
15385
|
recordedAccounting = true;
|
|
@@ -14846,7 +15435,9 @@ class PiSubagentRunner {
|
|
|
14846
15435
|
return failBeforeSpawn("spawn_failed", `Magic Context: plugin build is older than its database (database=v${fence.failure.persistedVersion}, supported_fence=v${fence.failure.supportedVersion}) — restart Pi.`);
|
|
14847
15436
|
}
|
|
14848
15437
|
const promptBytes = Buffer.byteLength(options.userMessage, "utf8");
|
|
14849
|
-
const
|
|
15438
|
+
const budgetToolGuardAvailable = SUBAGENT_ENTRY_PATH !== undefined || this.subagentExtensions?.some((extension) => basename(extension) === "subagent-entry.js") === true;
|
|
15439
|
+
const rpcBudget = options.tokenBudget !== undefined && this.invocation.targetHarness === "pi" && budgetToolGuardAvailable;
|
|
15440
|
+
const deliverViaStdin = !rpcBudget && (promptBytes > PROMPT_ARGV_MAX_BYTES || this.platform === "win32");
|
|
14850
15441
|
let systemPromptTempDir;
|
|
14851
15442
|
let systemPromptPath;
|
|
14852
15443
|
const cleanupSystemPromptFile = () => {
|
|
@@ -14869,13 +15460,30 @@ class PiSubagentRunner {
|
|
|
14869
15460
|
return failBeforeSpawn("spawn_failed", `failed to prepare pi system prompt file: ${error instanceof Error ? error.message : String(error)}`);
|
|
14870
15461
|
}
|
|
14871
15462
|
}
|
|
15463
|
+
if (rpcBudget && !systemPromptTempDir) {
|
|
15464
|
+
try {
|
|
15465
|
+
systemPromptTempDir = mkdtempSync2(join5(tmpdir2(), "mc-pi-prompt-"));
|
|
15466
|
+
} catch (error) {
|
|
15467
|
+
return failBeforeSpawn("spawn_failed", `failed to prepare pi budget guard: ${error instanceof Error ? error.message : String(error)}`);
|
|
15468
|
+
}
|
|
15469
|
+
}
|
|
15470
|
+
const budgetGuardFile = rpcBudget && systemPromptTempDir ? join5(systemPromptTempDir, "budget-guard") : undefined;
|
|
15471
|
+
if (budgetGuardFile) {
|
|
15472
|
+
try {
|
|
15473
|
+
writeFileSync5(budgetGuardFile, "", "utf8");
|
|
15474
|
+
} catch (error) {
|
|
15475
|
+
cleanupSystemPromptFile();
|
|
15476
|
+
return failBeforeSpawn("spawn_failed", `failed to write pi budget guard: ${error instanceof Error ? error.message : String(error)}`);
|
|
15477
|
+
}
|
|
15478
|
+
}
|
|
14872
15479
|
const args = buildArgs(options, {
|
|
14873
15480
|
targetHarness: this.invocation.targetHarness,
|
|
14874
15481
|
disableDiscoveredExtensions: runMode.disableDiscoveredExtensions,
|
|
14875
15482
|
subagentExtensions: this.subagentExtensions,
|
|
14876
|
-
omitPositionalMessage: deliverViaStdin,
|
|
15483
|
+
omitPositionalMessage: deliverViaStdin || rpcBudget,
|
|
14877
15484
|
systemPromptPath,
|
|
14878
|
-
modelRef: modelRefOverride
|
|
15485
|
+
modelRef: modelRefOverride,
|
|
15486
|
+
rpc: rpcBudget
|
|
14879
15487
|
});
|
|
14880
15488
|
return new Promise((resolve) => {
|
|
14881
15489
|
let accountingMessages = [];
|
|
@@ -14884,13 +15492,17 @@ class PiSubagentRunner {
|
|
|
14884
15492
|
if (settled)
|
|
14885
15493
|
return;
|
|
14886
15494
|
settled = true;
|
|
15495
|
+
const outcome = budget ? {
|
|
15496
|
+
...result,
|
|
15497
|
+
meta: { ...result.meta, tokenBudget: budget.snapshot() }
|
|
15498
|
+
} : result;
|
|
14887
15499
|
cleanupSystemPromptFile();
|
|
14888
15500
|
try {
|
|
14889
|
-
recordAccounting(
|
|
15501
|
+
recordAccounting(outcome, accountingMessages);
|
|
14890
15502
|
} catch (err) {
|
|
14891
15503
|
sessionLog(options.accountingSessionId ?? "subagent", `subagent accounting failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
|
|
14892
15504
|
}
|
|
14893
|
-
resolve(
|
|
15505
|
+
resolve(outcome);
|
|
14894
15506
|
};
|
|
14895
15507
|
const emitProgress = (event) => {
|
|
14896
15508
|
if (!options.onProgress)
|
|
@@ -14902,10 +15514,14 @@ class PiSubagentRunner {
|
|
|
14902
15514
|
let child;
|
|
14903
15515
|
try {
|
|
14904
15516
|
child = this.spawnImpl(this.invocation.command, [...this.invocation.prefixArgs, ...this.extraArgs, ...args], {
|
|
15517
|
+
windowsHide: true,
|
|
14905
15518
|
cwd: options.cwd,
|
|
14906
15519
|
env: {
|
|
14907
15520
|
...process.env,
|
|
14908
15521
|
[MAGIC_CONTEXT_PI_SUBAGENT_ENV]: "1",
|
|
15522
|
+
MAGIC_CONTEXT_SUBAGENT_PROMPT_FILE: systemPromptPath,
|
|
15523
|
+
MAGIC_CONTEXT_SUBAGENT_BUDGET_GUARD_FILE: budgetGuardFile,
|
|
15524
|
+
MAGIC_CONTEXT_SUBAGENT_PROVENANCE_ONLY: /^(magic-context-)?historian(?:-recomp|-editor)?$/.test(options.agent) ? undefined : "1",
|
|
14909
15525
|
...options.temperature !== undefined ? {
|
|
14910
15526
|
MAGIC_CONTEXT_HISTORIAN_TEMPERATURE: String(options.temperature)
|
|
14911
15527
|
} : {},
|
|
@@ -14913,7 +15529,11 @@ class PiSubagentRunner {
|
|
|
14913
15529
|
MAGIC_CONTEXT_HISTORIAN_MAX_OUTPUT_TOKENS: String(options.maxOutputTokens)
|
|
14914
15530
|
} : {}
|
|
14915
15531
|
},
|
|
14916
|
-
stdio: [
|
|
15532
|
+
stdio: [
|
|
15533
|
+
deliverViaStdin || rpcBudget ? "pipe" : "ignore",
|
|
15534
|
+
"pipe",
|
|
15535
|
+
"pipe"
|
|
15536
|
+
]
|
|
14917
15537
|
});
|
|
14918
15538
|
} catch (error) {
|
|
14919
15539
|
cleanupSystemPromptFile();
|
|
@@ -14936,15 +15556,43 @@ class PiSubagentRunner {
|
|
|
14936
15556
|
return;
|
|
14937
15557
|
}
|
|
14938
15558
|
emitProgress({ type: "spawned", argv: args, pid: child.pid });
|
|
14939
|
-
if (
|
|
15559
|
+
if (rpcBudget && child.stdin) {
|
|
15560
|
+
child.stdin.on("error", (error) => {
|
|
15561
|
+
terminateChild(child);
|
|
15562
|
+
settle({
|
|
15563
|
+
ok: false,
|
|
15564
|
+
reason: budget?.snapshot().finalizeFired ? "token_budget" : "spawn_failed",
|
|
15565
|
+
error: `Pi RPC input failed: ${error.message}`,
|
|
15566
|
+
durationMs: Date.now() - startTime
|
|
15567
|
+
});
|
|
15568
|
+
});
|
|
15569
|
+
child.stdin.write(`${JSON.stringify({
|
|
15570
|
+
type: "prompt",
|
|
15571
|
+
message: options.userMessage,
|
|
15572
|
+
id: "dreamer-initial"
|
|
15573
|
+
})}
|
|
15574
|
+
`);
|
|
15575
|
+
} else if (deliverViaStdin && child.stdin) {
|
|
14940
15576
|
child.stdin.on("error", () => {});
|
|
14941
15577
|
try {
|
|
14942
15578
|
child.stdin.end(options.userMessage, "utf8");
|
|
14943
15579
|
} catch {}
|
|
14944
15580
|
}
|
|
14945
15581
|
let stderr = "";
|
|
15582
|
+
let stderrLine = "";
|
|
14946
15583
|
child.stderr?.on("data", (chunk) => {
|
|
14947
15584
|
const text = chunk.toString("utf8");
|
|
15585
|
+
stderrLine += text;
|
|
15586
|
+
const lines = stderrLine.split(`
|
|
15587
|
+
`);
|
|
15588
|
+
stderrLine = (lines.pop() ?? "").slice(-4096);
|
|
15589
|
+
for (const line of lines) {
|
|
15590
|
+
if (settled || !line.startsWith('{"type":"mc_system_prompt"'))
|
|
15591
|
+
continue;
|
|
15592
|
+
const parsed = parsePiEventLine(line);
|
|
15593
|
+
if (parsed.ok)
|
|
15594
|
+
telemetry.observe(parsed.event);
|
|
15595
|
+
}
|
|
14948
15596
|
stderr += text;
|
|
14949
15597
|
if (stderr.length > 16000) {
|
|
14950
15598
|
stderr = `…[truncated]${stderr.slice(-16000)}`;
|
|
@@ -14980,7 +15628,7 @@ class PiSubagentRunner {
|
|
|
14980
15628
|
const accumulatedMessages = [];
|
|
14981
15629
|
accountingMessages = accumulatedMessages;
|
|
14982
15630
|
rl.on("line", (line) => {
|
|
14983
|
-
if (line.length === 0)
|
|
15631
|
+
if (settled || line.length === 0)
|
|
14984
15632
|
return;
|
|
14985
15633
|
const parsed = parsePiEventLine(line);
|
|
14986
15634
|
if (!parsed.ok) {
|
|
@@ -14993,6 +15641,95 @@ class PiSubagentRunner {
|
|
|
14993
15641
|
if (typeof event !== "object" || event === null)
|
|
14994
15642
|
return;
|
|
14995
15643
|
const e = event;
|
|
15644
|
+
if (rpcBudget && e.type === "response" && event.success === false) {
|
|
15645
|
+
const reply = event;
|
|
15646
|
+
if (reply.command === "prompt" || reply.command === "steer" && !sawAgentEnd) {
|
|
15647
|
+
terminateChild(child);
|
|
15648
|
+
settle({
|
|
15649
|
+
ok: false,
|
|
15650
|
+
reason: reply.command === "steer" ? "token_budget" : "model_failed",
|
|
15651
|
+
error: reply.error ?? `Pi RPC ${reply.command} failed`,
|
|
15652
|
+
durationMs: Date.now() - startTime
|
|
15653
|
+
});
|
|
15654
|
+
return;
|
|
15655
|
+
}
|
|
15656
|
+
}
|
|
15657
|
+
telemetry.observe(event);
|
|
15658
|
+
if (budget && e.type === "message_end") {
|
|
15659
|
+
const message = e.message;
|
|
15660
|
+
if (message?.role === "assistant") {
|
|
15661
|
+
const usage = message.usage;
|
|
15662
|
+
const finished = message.stopReason === "stop" && !(Array.isArray(message.content) && message.content.some((part) => typeof part === "object" && part !== null && part.type === "toolCall"));
|
|
15663
|
+
const decision = budget.charge(usage?.input ?? 0, usage?.cacheRead ?? 0, usage?.cacheWrite ?? 0, finished, rpcBudget);
|
|
15664
|
+
if (decision === "stop" || decision === "finalize" && !rpcBudget) {
|
|
15665
|
+
terminateChild(child);
|
|
15666
|
+
settle({
|
|
15667
|
+
ok: false,
|
|
15668
|
+
reason: "token_budget",
|
|
15669
|
+
error: "MC-D11: Pi dreamer exceeded its prompt-token budget",
|
|
15670
|
+
durationMs: Date.now() - startTime,
|
|
15671
|
+
meta: { tokenBudget: budget.snapshot() }
|
|
15672
|
+
});
|
|
15673
|
+
return;
|
|
15674
|
+
}
|
|
15675
|
+
if (decision === "finalize" && rpcBudget) {
|
|
15676
|
+
try {
|
|
15677
|
+
if (!budgetGuardFile)
|
|
15678
|
+
throw new Error("missing budget guard file");
|
|
15679
|
+
writeFileSync5(budgetGuardFile, "finalize", "utf8");
|
|
15680
|
+
} catch {
|
|
15681
|
+
terminateChild(child);
|
|
15682
|
+
settle({
|
|
15683
|
+
ok: false,
|
|
15684
|
+
reason: "token_budget",
|
|
15685
|
+
error: "MC-D11: Pi could not activate its tool guard",
|
|
15686
|
+
durationMs: Date.now() - startTime
|
|
15687
|
+
});
|
|
15688
|
+
return;
|
|
15689
|
+
}
|
|
15690
|
+
child.stdin?.write(`${JSON.stringify({
|
|
15691
|
+
type: "steer",
|
|
15692
|
+
message: TOKEN_BUDGET_FINALIZE_MESSAGE,
|
|
15693
|
+
id: "dreamer-finalize"
|
|
15694
|
+
})}
|
|
15695
|
+
`);
|
|
15696
|
+
}
|
|
15697
|
+
}
|
|
15698
|
+
}
|
|
15699
|
+
const resultParts = event.result?.content;
|
|
15700
|
+
if (budget?.snapshot().finalizeFired && e.type === "tool_execution_end" && resultParts?.some((part) => part.text === TOKEN_BUDGET_TOOL_REFUSAL)) {
|
|
15701
|
+
const refusal = budget.refuseTool();
|
|
15702
|
+
if (refusal?.hardStopped) {
|
|
15703
|
+
terminateChild(child);
|
|
15704
|
+
settle({
|
|
15705
|
+
ok: false,
|
|
15706
|
+
reason: "token_budget",
|
|
15707
|
+
error: "MC-D11: Pi dreamer made two calls after finalize",
|
|
15708
|
+
durationMs: Date.now() - startTime,
|
|
15709
|
+
meta: { tokenBudget: budget.snapshot() }
|
|
15710
|
+
});
|
|
15711
|
+
return;
|
|
15712
|
+
}
|
|
15713
|
+
}
|
|
15714
|
+
if (cap !== undefined && telemetry.steps > cap) {
|
|
15715
|
+
if (e.type === "message_end")
|
|
15716
|
+
accumulatedMessages.push(e.message);
|
|
15717
|
+
if (e.type === "agent_end" && Array.isArray(e.messages))
|
|
15718
|
+
accountingMessages = e.messages;
|
|
15719
|
+
if (timeoutHandle)
|
|
15720
|
+
clearTimeout(timeoutHandle);
|
|
15721
|
+
if (drainTimerHandle)
|
|
15722
|
+
clearTimeout(drainTimerHandle);
|
|
15723
|
+
terminateChild(child);
|
|
15724
|
+
settle({
|
|
15725
|
+
ok: false,
|
|
15726
|
+
reason: "step_limit",
|
|
15727
|
+
error: `MC-D10: Pi subagent ${options.agent} exceeded its ${cap}-step limit`,
|
|
15728
|
+
durationMs: Date.now() - startTime,
|
|
15729
|
+
meta: { cap, steps: telemetry.steps }
|
|
15730
|
+
});
|
|
15731
|
+
return;
|
|
15732
|
+
}
|
|
14996
15733
|
const isFirstEvent = eventCount === 0;
|
|
14997
15734
|
eventCount += 1;
|
|
14998
15735
|
lastEventTimestamp = Date.now();
|
|
@@ -15306,9 +16043,9 @@ function buildArgs(options, opts) {
|
|
|
15306
16043
|
const targetHarness = opts?.targetHarness ?? resolvePiHarnessKind();
|
|
15307
16044
|
const ompTarget = targetHarness === "omp";
|
|
15308
16045
|
const args = [
|
|
15309
|
-
"--print",
|
|
16046
|
+
...opts?.rpc ? [] : ["--print"],
|
|
15310
16047
|
"--mode",
|
|
15311
|
-
"json",
|
|
16048
|
+
opts?.rpc ? "rpc" : "json",
|
|
15312
16049
|
"--no-session",
|
|
15313
16050
|
"--no-skills",
|
|
15314
16051
|
...ompTarget ? ["--no-rules"] : ["--no-prompt-templates", "--no-context-files"]
|
|
@@ -15322,7 +16059,7 @@ function buildArgs(options, opts) {
|
|
|
15322
16059
|
}
|
|
15323
16060
|
}
|
|
15324
16061
|
const subagentEntryPath = opts?.subagentEntryPath ?? SUBAGENT_ENTRY_PATH;
|
|
15325
|
-
const shouldLoadSubagentExtension = subagentEntryPath && (SEARCH_ONLY_SUBAGENT_TOOL_AGENTS.has(options.agent) || DREAMER_ACTION_AGENTS.has(options.agent));
|
|
16062
|
+
const shouldLoadSubagentExtension = subagentEntryPath && (opts?.rpc || SEARCH_ONLY_SUBAGENT_TOOL_AGENTS.has(options.agent) || DREAMER_ACTION_AGENTS.has(options.agent));
|
|
15326
16063
|
if (shouldLoadSubagentExtension) {
|
|
15327
16064
|
args.push("--extension", subagentEntryPath);
|
|
15328
16065
|
if (DREAMER_ACTION_AGENTS.has(options.agent)) {
|
|
@@ -15330,7 +16067,7 @@ function buildArgs(options, opts) {
|
|
|
15330
16067
|
}
|
|
15331
16068
|
}
|
|
15332
16069
|
const historianCalibrationEntryPath = opts?.historianCalibrationEntryPath === undefined ? HISTORIAN_CALIBRATION_ENTRY_PATH : opts.historianCalibrationEntryPath;
|
|
15333
|
-
if (
|
|
16070
|
+
if (subagentStepCap(options.agent) !== undefined && historianCalibrationEntryPath) {
|
|
15334
16071
|
args.push("--extension", historianCalibrationEntryPath);
|
|
15335
16072
|
}
|
|
15336
16073
|
const strictTools = STRICT_TOOL_ALLOWLIST.get(options.agent);
|
|
@@ -15559,7 +16296,7 @@ function* sessionEntries(path) {
|
|
|
15559
16296
|
}
|
|
15560
16297
|
|
|
15561
16298
|
// src/dreamer/pi-session-api.ts
|
|
15562
|
-
import { existsSync as existsSync8, readFileSync as
|
|
16299
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, realpathSync as realpathSync4, statSync as statSync5 } from "node:fs";
|
|
15563
16300
|
import {
|
|
15564
16301
|
basename as basename2,
|
|
15565
16302
|
dirname as dirname2,
|
|
@@ -15588,7 +16325,7 @@ function isScriptEntry(filePath) {
|
|
|
15588
16325
|
}
|
|
15589
16326
|
function readManifest(pkgJsonPath) {
|
|
15590
16327
|
try {
|
|
15591
|
-
const pkg = JSON.parse(
|
|
16328
|
+
const pkg = JSON.parse(readFileSync7(pkgJsonPath, "utf8"));
|
|
15592
16329
|
return pkg && typeof pkg === "object" ? pkg : null;
|
|
15593
16330
|
} catch {
|
|
15594
16331
|
return null;
|
|
@@ -15752,7 +16489,7 @@ async function loadDefaultPiSessionApi(loaders) {
|
|
|
15752
16489
|
throw new Error("Pi session APIs unavailable: expected SessionManager.listAll on pi-coding-agent");
|
|
15753
16490
|
}
|
|
15754
16491
|
const loadEntriesFromFile = mod.loadEntriesFromFile ?? ((filePath) => {
|
|
15755
|
-
const content =
|
|
16492
|
+
const content = readFileSync7(filePath, "utf8");
|
|
15756
16493
|
return mod.parseSessionEntries?.(content) ?? [];
|
|
15757
16494
|
});
|
|
15758
16495
|
return {
|
|
@@ -15900,11 +16637,14 @@ class PiRetrospectiveRawProvider {
|
|
|
15900
16637
|
continue;
|
|
15901
16638
|
if (typeof info.cwd !== "string" || resolve2(info.cwd) !== projectCwd)
|
|
15902
16639
|
continue;
|
|
16640
|
+
const activity = this.deps.contextDb ? readSessionActivity(this.deps.contextDb, info.id) : typeof info.modified === "number" ? info.modified : undefined;
|
|
16641
|
+
if (this.deps.contextDb && activity === undefined)
|
|
16642
|
+
continue;
|
|
15903
16643
|
this.sessionPathById.set(info.id, info.path);
|
|
15904
16644
|
result.push({
|
|
15905
16645
|
sessionId: info.id,
|
|
15906
16646
|
path: info.path,
|
|
15907
|
-
updatedAt:
|
|
16647
|
+
updatedAt: activity
|
|
15908
16648
|
});
|
|
15909
16649
|
}
|
|
15910
16650
|
return result.sort((a, b) => (a.updatedAt ?? 0) - (b.updatedAt ?? 0));
|
|
@@ -16051,6 +16791,12 @@ var inFlightDreams = (() => {
|
|
|
16051
16791
|
globals[PI_DREAMER_IN_FLIGHT] = dreams;
|
|
16052
16792
|
return dreams;
|
|
16053
16793
|
})();
|
|
16794
|
+
var dreamAbortControllers = new Map;
|
|
16795
|
+
function abortInFlightDreamers(registrationOwner) {
|
|
16796
|
+
for (const controller of dreamAbortControllers.get(registrationOwner) ?? []) {
|
|
16797
|
+
controller.abort();
|
|
16798
|
+
}
|
|
16799
|
+
}
|
|
16054
16800
|
var sessionCounter = 0;
|
|
16055
16801
|
var piSubagentRunnerFactory = () => new PiSubagentRunner;
|
|
16056
16802
|
var startDreamScheduleTimerFn = startDreamScheduleTimer;
|
|
@@ -16079,7 +16825,7 @@ function registerPiDreamerProject(opts) {
|
|
|
16079
16825
|
const client = createPiDreamerClient(opts, notifyOwnersOfAdjunctRefresh, () => {
|
|
16080
16826
|
const current = registeredProjects2.get(opts.projectIdentity);
|
|
16081
16827
|
return current?.generation === generation && current.owners.get(opts.registrationOwner)?.projectDir === opts.projectDir;
|
|
16082
|
-
}, () => opts.sampleDreamRun ? opts.sampleDreamRun().dreamerConfig?.maxTokens : opts.config.maxTokens);
|
|
16828
|
+
}, () => opts.sampleDreamRun ? opts.sampleDreamRun().dreamerConfig?.maxTokens : opts.config.maxTokens, () => opts.sampleDreamRun?.().dreamerConfig ?? opts.config);
|
|
16083
16829
|
let cleanup;
|
|
16084
16830
|
let cancelled = false;
|
|
16085
16831
|
const modelRegistry = opts.modelRegistry;
|
|
@@ -16098,7 +16844,10 @@ function registerPiDreamerProject(opts) {
|
|
|
16098
16844
|
retinaHandoff: opts.retinaHandoff,
|
|
16099
16845
|
mural: opts.mural,
|
|
16100
16846
|
ensureRegistered: ensureProjectRegisteredFromPiDirectory,
|
|
16101
|
-
retrospectiveRawProvider: () => new PiRetrospectiveRawProvider({
|
|
16847
|
+
retrospectiveRawProvider: () => new PiRetrospectiveRawProvider({
|
|
16848
|
+
projectCwd: opts.projectDir,
|
|
16849
|
+
contextDb: opts.db
|
|
16850
|
+
}),
|
|
16102
16851
|
primerRawProviderFactory: createPiPrimerRawProviderFactory()
|
|
16103
16852
|
}).then((timerCleanup) => {
|
|
16104
16853
|
if (cancelled) {
|
|
@@ -16117,7 +16866,7 @@ function registerPiDreamerProject(opts) {
|
|
|
16117
16866
|
const sampled = manualOpts.sampleDreamRun?.();
|
|
16118
16867
|
const dreamerConfig = sampled?.dreamerConfig ?? manualOpts.config;
|
|
16119
16868
|
const mural = sampled?.mural ?? manualOpts.mural;
|
|
16120
|
-
const manualClient = createPiDreamerClient(manualOpts, notifyOwnersOfAdjunctRefresh, () => owners.get(manualOpts.registrationOwner)?.projectDir === manualOpts.projectDir, () => dreamerConfig.maxTokens);
|
|
16869
|
+
const manualClient = createPiDreamerClient(manualOpts, notifyOwnersOfAdjunctRefresh, () => owners.get(manualOpts.registrationOwner)?.projectDir === manualOpts.projectDir, () => dreamerConfig.maxTokens, () => dreamerConfig);
|
|
16121
16870
|
const manualRun = runManualDream({
|
|
16122
16871
|
db: manualOpts.db,
|
|
16123
16872
|
projectIdentity: manualOpts.projectIdentity,
|
|
@@ -16127,7 +16876,8 @@ function registerPiDreamerProject(opts) {
|
|
|
16127
16876
|
sessionDirectory: manualOpts.projectDir,
|
|
16128
16877
|
openOpenCodeDb,
|
|
16129
16878
|
retrospectiveRawProvider: new PiRetrospectiveRawProvider({
|
|
16130
|
-
projectCwd: manualOpts.projectDir
|
|
16879
|
+
projectCwd: manualOpts.projectDir,
|
|
16880
|
+
contextDb: manualOpts.db
|
|
16131
16881
|
}),
|
|
16132
16882
|
primerRawProviderFactory: createPiPrimerRawProviderFactory(),
|
|
16133
16883
|
userMemoryCollectionEnabled: userMemoryCollectionEnabled(dreamerConfig),
|
|
@@ -16197,8 +16947,9 @@ async function awaitInFlightDreamers(registrationOwner) {
|
|
|
16197
16947
|
return;
|
|
16198
16948
|
await Promise.allSettled(runs);
|
|
16199
16949
|
}
|
|
16200
|
-
function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRefreshNeeded, isRegistrationOwnerActive = () => true, getMaxOutputTokens = () => opts.config.maxTokens) {
|
|
16950
|
+
function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRefreshNeeded, isRegistrationOwnerActive = () => true, getMaxOutputTokens = () => opts.config.maxTokens, getDreamerConfig = () => opts.config) {
|
|
16201
16951
|
const runner = piSubagentRunnerFactory();
|
|
16952
|
+
const budgetStates = new Map;
|
|
16202
16953
|
const assertRegistrationOwnerActive = () => {
|
|
16203
16954
|
if (!isRegistrationOwnerActive()) {
|
|
16204
16955
|
throw new Error(`Pi dreamer registration is no longer active for project ${opts.projectIdentity}`);
|
|
@@ -16228,6 +16979,13 @@ function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRe
|
|
|
16228
16979
|
const systemPrompt = extractSystemPrompt(args);
|
|
16229
16980
|
const perTaskModel = extractBodyModel(args);
|
|
16230
16981
|
const requestedAgent = extractBodyAgent(args) ?? "magic-context-dreamer";
|
|
16982
|
+
const controller = new AbortController;
|
|
16983
|
+
let controllers = dreamAbortControllers.get(opts.registrationOwner);
|
|
16984
|
+
if (!controllers) {
|
|
16985
|
+
controllers = new Set;
|
|
16986
|
+
dreamAbortControllers.set(opts.registrationOwner, controllers);
|
|
16987
|
+
}
|
|
16988
|
+
controllers.add(controller);
|
|
16231
16989
|
const runPromise = runner.run({
|
|
16232
16990
|
agent: requestedAgent,
|
|
16233
16991
|
systemPrompt,
|
|
@@ -16236,18 +16994,39 @@ function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRe
|
|
|
16236
16994
|
fallbackModels: undefined,
|
|
16237
16995
|
timeoutMs: 30 * 60 * 1000,
|
|
16238
16996
|
cwd: dreamSession.directory,
|
|
16239
|
-
signal: args.signal
|
|
16997
|
+
signal: args.signal ? AbortSignal.any([args.signal, controller.signal]) : controller.signal,
|
|
16240
16998
|
thinkingLevel: extractBodyVariant(args),
|
|
16241
16999
|
maxOutputTokens: getMaxOutputTokens(),
|
|
16242
17000
|
accountingSessionId: opts.projectIdentity,
|
|
16243
17001
|
accountingSubagent: "dreamer",
|
|
16244
|
-
accountingTask: accountingTaskFromTitle(dreamSession.title)
|
|
17002
|
+
accountingTask: accountingTaskFromTitle(dreamSession.title),
|
|
17003
|
+
tokenBudget: (() => {
|
|
17004
|
+
const task = accountingTaskFromTitle(dreamSession.title);
|
|
17005
|
+
if (!task)
|
|
17006
|
+
return;
|
|
17007
|
+
return buildDreamTaskRuntimeConfigs(getDreamerConfig(), opts.harness).find((entry) => entry.task === task)?.tokenBudget;
|
|
17008
|
+
})()
|
|
16245
17009
|
});
|
|
16246
17010
|
inFlightDreams.set(runPromise, opts.registrationOwner);
|
|
16247
17011
|
try {
|
|
16248
17012
|
const result = await runPromise;
|
|
17013
|
+
const budget = result.meta?.tokenBudget;
|
|
17014
|
+
const budgetTask = accountingTaskFromTitle(dreamSession.title);
|
|
17015
|
+
if (budgetTask && budget && typeof budget.spent === "number") {
|
|
17016
|
+
const prior = budgetStates.get(budgetTask);
|
|
17017
|
+
budgetStates.set(budgetTask, {
|
|
17018
|
+
spent: (prior?.spent ?? 0) + budget.spent,
|
|
17019
|
+
finalizeFired: (prior?.finalizeFired ?? false) || budget.finalizeFired === true
|
|
17020
|
+
});
|
|
17021
|
+
}
|
|
16249
17022
|
assertRegistrationOwnerActive();
|
|
16250
17023
|
if (!result.ok) {
|
|
17024
|
+
if (result.reason === "token_budget") {
|
|
17025
|
+
throw new DreamTokenBudgetExceeded(dreamSession.id, Number(result.meta?.tokenBudget?.spent ?? 0));
|
|
17026
|
+
}
|
|
17027
|
+
if (result.reason === "step_limit") {
|
|
17028
|
+
throw new HiddenAgentStepLimit("pi-dreamer", Number(result.meta?.cap));
|
|
17029
|
+
}
|
|
16251
17030
|
const error = new Error(`Pi dreamer subagent failed (${result.reason}): ${result.error}`);
|
|
16252
17031
|
if (result.transient) {
|
|
16253
17032
|
error.transient = true;
|
|
@@ -16264,6 +17043,9 @@ function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRe
|
|
|
16264
17043
|
onAdjunctsRefreshNeeded?.(opts.projectIdentity);
|
|
16265
17044
|
} finally {
|
|
16266
17045
|
inFlightDreams.delete(runPromise);
|
|
17046
|
+
controllers.delete(controller);
|
|
17047
|
+
if (controllers.size === 0)
|
|
17048
|
+
dreamAbortControllers.delete(opts.registrationOwner);
|
|
16267
17049
|
}
|
|
16268
17050
|
},
|
|
16269
17051
|
messages: async (args) => {
|
|
@@ -16276,7 +17058,11 @@ function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRe
|
|
|
16276
17058
|
return {};
|
|
16277
17059
|
}
|
|
16278
17060
|
};
|
|
16279
|
-
return {
|
|
17061
|
+
return {
|
|
17062
|
+
session,
|
|
17063
|
+
readTokenBudget: (task) => budgetStates.get(task),
|
|
17064
|
+
resetDreamTokenBudget: (task) => budgetStates.delete(task)
|
|
17065
|
+
};
|
|
16280
17066
|
}
|
|
16281
17067
|
function readDirectory(args) {
|
|
16282
17068
|
const query = args.query;
|
|
@@ -16585,6 +17371,7 @@ Unknown task "${requested}".`,
|
|
|
16585
17371
|
try {
|
|
16586
17372
|
await deps.ensureRegistered?.(ctx);
|
|
16587
17373
|
const result = await runPiDreamForProject(project.projectIdentity, task, deps.registrationOwner);
|
|
17374
|
+
const backlogAfter = getDreamTaskBacklogs(deps.db, project.projectIdentity, backlogTasks);
|
|
16588
17375
|
const lines = [];
|
|
16589
17376
|
if (result.ran.length > 0)
|
|
16590
17377
|
lines.push(`Ran: ${result.ran.join(", ")}`);
|
|
@@ -16600,8 +17387,8 @@ Unknown task "${requested}".`,
|
|
|
16600
17387
|
lines.push(`Skipped (no work): ${result.skippedNoWork.join(", ")}`);
|
|
16601
17388
|
if (result.deferredBusy.length > 0)
|
|
16602
17389
|
lines.push(`Busy: ${result.deferredBusy.join(", ")} — another dream task holds this domain's lease; retry in a minute`);
|
|
16603
|
-
if (Object.keys(
|
|
16604
|
-
lines.push("", "Backlog at run end:", formatDreamTaskBacklogs(
|
|
17390
|
+
if (Object.keys(backlogAfter).length > 0) {
|
|
17391
|
+
lines.push("", "Backlog at run end:", formatDreamTaskBacklogs(backlogAfter, backlogTasks));
|
|
16605
17392
|
}
|
|
16606
17393
|
if (lines.length === 0)
|
|
16607
17394
|
lines.push("No enabled dream tasks to run.");
|
|
@@ -16630,26 +17417,6 @@ Unknown task "${requested}".`,
|
|
|
16630
17417
|
});
|
|
16631
17418
|
}
|
|
16632
17419
|
|
|
16633
|
-
// ../plugin/src/hooks/magic-context/embed-session-state.ts
|
|
16634
|
-
var embedPauseBySession = new Set;
|
|
16635
|
-
var embedRunStateBySession = new Map;
|
|
16636
|
-
var autoEmbedAttemptedBySession = new Set;
|
|
16637
|
-
function getEmbedDrainUiStatus(sessionId, progress) {
|
|
16638
|
-
if (embedPauseBySession.has(sessionId)) {
|
|
16639
|
-
return { status: "paused" };
|
|
16640
|
-
}
|
|
16641
|
-
const activeRun = embedRunStateBySession.get(sessionId);
|
|
16642
|
-
if (activeRun && !activeRun.signal.aborted || progress?.kind === "embed" && progress.phase === "recomp") {
|
|
16643
|
-
return { status: "running" };
|
|
16644
|
-
}
|
|
16645
|
-
if (progress?.kind === "embed" && (progress.phase === "failed" || progress.phase === "skipped") && progress.message) {
|
|
16646
|
-
if (/provider/i.test(progress.message)) {
|
|
16647
|
-
return { status: "stopped", detail: progress.message };
|
|
16648
|
-
}
|
|
16649
|
-
}
|
|
16650
|
-
return { status: "idle" };
|
|
16651
|
-
}
|
|
16652
|
-
|
|
16653
17420
|
// ../plugin/src/hooks/magic-context/format-embed-failure.ts
|
|
16654
17421
|
function formatEmbedFailureSummary(embedded, remaining, failure, style = "markdown") {
|
|
16655
17422
|
const historyBlocks = `history block${embedded === 1 ? "" : "s"}`;
|
|
@@ -16713,7 +17480,7 @@ function clearPiEmbedSessionState(sessionId) {
|
|
|
16713
17480
|
ctrl.abort();
|
|
16714
17481
|
embedRunStateBySession.delete(sessionId);
|
|
16715
17482
|
}
|
|
16716
|
-
|
|
17483
|
+
invalidateAutoEmbedSession(sessionId);
|
|
16717
17484
|
}
|
|
16718
17485
|
async function runEmbedDrain(db, projectIdentity, sessionId, options = {}) {
|
|
16719
17486
|
const activeCtrl = embedRunStateBySession.get(sessionId);
|
|
@@ -16895,28 +17662,34 @@ ${statusText}`,
|
|
|
16895
17662
|
function maybeAutoEmbedPiSession(deps, sessionId, projectDir, projectIdentity) {
|
|
16896
17663
|
if (!isUsableProjectIdentity(projectIdentity))
|
|
16897
17664
|
return;
|
|
17665
|
+
const snapshot = getProjectEmbeddingSnapshot(projectIdentity);
|
|
17666
|
+
const embedIdentity = snapshot ? JSON.stringify([
|
|
17667
|
+
snapshot.providerIdentity,
|
|
17668
|
+
snapshot.chunkModelId,
|
|
17669
|
+
snapshot.runtimeFingerprint
|
|
17670
|
+
]) : "off";
|
|
17671
|
+
if (autoEmbedIdentityBySession.get(sessionId) !== embedIdentity)
|
|
17672
|
+
invalidateAutoEmbedSession(sessionId);
|
|
16898
17673
|
if (autoEmbedAttemptedBySession.has(sessionId))
|
|
16899
17674
|
return;
|
|
16900
17675
|
if (embedPauseBySession.has(sessionId))
|
|
16901
17676
|
return;
|
|
16902
17677
|
autoEmbedAttemptedBySession.add(sessionId);
|
|
17678
|
+
autoEmbedIdentityBySession.set(sessionId, embedIdentity);
|
|
16903
17679
|
(async () => {
|
|
16904
|
-
let drainReachedTerminal = false;
|
|
16905
17680
|
try {
|
|
16906
17681
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
16907
17682
|
await ensureProjectRegisteredFromPiDirectory(projectDir, deps.db);
|
|
16908
|
-
const coverage =
|
|
16909
|
-
if (!coverage.enabled)
|
|
17683
|
+
const coverage = await getAutoEmbeddingSessionCoverage(deps.db, projectIdentity, sessionId);
|
|
17684
|
+
if (!coverage.enabled) {
|
|
17685
|
+
invalidateAutoEmbedSession(sessionId);
|
|
16910
17686
|
return;
|
|
16911
|
-
|
|
17687
|
+
}
|
|
17688
|
+
const remaining = coverage.total - coverage.embedded;
|
|
16912
17689
|
if (remaining <= 0)
|
|
16913
17690
|
return;
|
|
16914
17691
|
await runEmbedDrain(deps.db, projectIdentity, sessionId);
|
|
16915
|
-
|
|
16916
|
-
} catch {} finally {
|
|
16917
|
-
if (!drainReachedTerminal)
|
|
16918
|
-
autoEmbedAttemptedBySession.delete(sessionId);
|
|
16919
|
-
}
|
|
17692
|
+
} catch {}
|
|
16920
17693
|
})();
|
|
16921
17694
|
}
|
|
16922
17695
|
|
|
@@ -25659,7 +26432,7 @@ function readFrozenM0InputsPi(state, db, docs = readProjectDocsForPiM0(state), m
|
|
|
25659
26432
|
};
|
|
25660
26433
|
return { docs, markers, compartments, memories, userProfile, workspace };
|
|
25661
26434
|
});
|
|
25662
|
-
return read();
|
|
26435
|
+
return read.deferred();
|
|
25663
26436
|
}
|
|
25664
26437
|
function renderFreshM0PiNonPersisted(state, db) {
|
|
25665
26438
|
const docs = readProjectDocsForPiM0(state);
|
|
@@ -27136,19 +27909,22 @@ function validateAnthropicReasoningRuns(messages) {
|
|
|
27136
27909
|
index += 1;
|
|
27137
27910
|
continue;
|
|
27138
27911
|
}
|
|
27139
|
-
let
|
|
27912
|
+
let firstMessageInRun = null;
|
|
27140
27913
|
let sawOtherContent = false;
|
|
27141
27914
|
while (index < messages.length && messageRole(messages[index]) === "assistant") {
|
|
27142
27915
|
for (const part of messageParts(messages[index])) {
|
|
27143
|
-
if (
|
|
27144
|
-
|
|
27145
|
-
if (thinkingBlocks > 1 || sawOtherContent)
|
|
27146
|
-
return false;
|
|
27147
|
-
} else if (partEndsAnthropicAssistantRun(part)) {
|
|
27148
|
-
thinkingBlocks = 0;
|
|
27916
|
+
if (partEndsAnthropicAssistantRun(part)) {
|
|
27917
|
+
firstMessageInRun = null;
|
|
27149
27918
|
sawOtherContent = false;
|
|
27150
27919
|
} else if (!partIsOpenCodeStepMetadata(part)) {
|
|
27151
|
-
|
|
27920
|
+
if (firstMessageInRun === null)
|
|
27921
|
+
firstMessageInRun = index;
|
|
27922
|
+
if (partIsAnthropicThinking(part)) {
|
|
27923
|
+
if (sawOtherContent || index !== firstMessageInRun)
|
|
27924
|
+
return false;
|
|
27925
|
+
} else {
|
|
27926
|
+
sawOtherContent = true;
|
|
27927
|
+
}
|
|
27152
27928
|
}
|
|
27153
27929
|
}
|
|
27154
27930
|
index += 1;
|
|
@@ -27256,6 +28032,8 @@ function replayLkg(args) {
|
|
|
27256
28032
|
dropSlot(args.sessionId, "lkg_seam_invalid");
|
|
27257
28033
|
return { ok: false, reason: "lkg_seam_invalid" };
|
|
27258
28034
|
}
|
|
28035
|
+
const replayed = [...prefix, ...entry.pristineTail];
|
|
28036
|
+
args.prepareReplay?.(replayed);
|
|
27259
28037
|
if (!args.skipSeamValidation) {
|
|
27260
28038
|
if (!validateLkgSeamBoundary(prefix, entry.pristineTail)) {
|
|
27261
28039
|
dropSlot(args.sessionId, "lkg_unsafe_seam");
|
|
@@ -27266,7 +28044,6 @@ function replayLkg(args) {
|
|
|
27266
28044
|
return { ok: false, reason: "lkg_seam_invalid" };
|
|
27267
28045
|
}
|
|
27268
28046
|
}
|
|
27269
|
-
const replayed = [...prefix, ...entry.pristineTail];
|
|
27270
28047
|
if (requestedModelKeys.providerKey === "anthropic" && !validateAnthropicReasoningRuns(replayed)) {
|
|
27271
28048
|
dropSlot(args.sessionId, "lkg_anthropic_reasoning_run_invalid");
|
|
27272
28049
|
return { ok: false, reason: "lkg_anthropic_reasoning_run_invalid" };
|
|
@@ -28731,13 +29508,10 @@ function capturePiServedArray(sessionId, messages, options = {}) {
|
|
|
28731
29508
|
process.once("beforeExit", flushPiServedArrayLedger);
|
|
28732
29509
|
|
|
28733
29510
|
// src/strip-placeholders-pi.ts
|
|
28734
|
-
var
|
|
28735
|
-
function
|
|
29511
|
+
var MARKER_ONLY_PATTERN = /^(?:(?:§\d+§|\[dropped(?: §\d+§)?\]|\[cleared\])\s*)+$/;
|
|
29512
|
+
function isPiMarkerOnlyText(text) {
|
|
28736
29513
|
const trimmed = text.trim();
|
|
28737
|
-
|
|
28738
|
-
return true;
|
|
28739
|
-
const segments = trimmed.split(/(?=\[dropped(?: §[^§]+§)?\])/).map((segment) => segment.trim()).filter((segment) => segment.length > 0);
|
|
28740
|
-
return segments.length > 0 && segments.every((s) => DROPPED_SEGMENT_PATTERN.test(s));
|
|
29514
|
+
return trimmed.length > 0 && MARKER_ONLY_PATTERN.test(trimmed);
|
|
28741
29515
|
}
|
|
28742
29516
|
function messageIsPlaceholderOnly(message) {
|
|
28743
29517
|
if (!message || typeof message !== "object")
|
|
@@ -28746,7 +29520,7 @@ function messageIsPlaceholderOnly(message) {
|
|
|
28746
29520
|
if (msg.role !== "assistant")
|
|
28747
29521
|
return false;
|
|
28748
29522
|
if (typeof msg.content === "string")
|
|
28749
|
-
return
|
|
29523
|
+
return msg.content.trim().length === 0 || isPiMarkerOnlyText(msg.content);
|
|
28750
29524
|
if (!Array.isArray(msg.content))
|
|
28751
29525
|
return false;
|
|
28752
29526
|
if (msg.content.length === 0)
|
|
@@ -28756,12 +29530,11 @@ function messageIsPlaceholderOnly(message) {
|
|
|
28756
29530
|
if (!part || typeof part !== "object")
|
|
28757
29531
|
return false;
|
|
28758
29532
|
const p = part;
|
|
28759
|
-
|
|
28760
|
-
|
|
28761
|
-
if (typeof p.text !== "string")
|
|
29533
|
+
const text = p.type === "text" ? p.text : p.type === "thinking" ? p.thinking : null;
|
|
29534
|
+
if (typeof text !== "string")
|
|
28762
29535
|
return false;
|
|
28763
29536
|
sawVisibleContent = true;
|
|
28764
|
-
if (!
|
|
29537
|
+
if (text.trim().length > 0 && !isPiMarkerOnlyText(text))
|
|
28765
29538
|
return false;
|
|
28766
29539
|
}
|
|
28767
29540
|
return sawVisibleContent;
|
|
@@ -29651,6 +30424,7 @@ var deferredHistoryRefreshSessions = new Set;
|
|
|
29651
30424
|
var deferredMaterializationSessions = new Set;
|
|
29652
30425
|
var sessionsByProject = new Map;
|
|
29653
30426
|
var lastSeenProjectIdentityBySession = new Map;
|
|
30427
|
+
var persistedProjectIdentityBySession = new Map;
|
|
29654
30428
|
var rawMessageProviderUnregistersBySession = new Map;
|
|
29655
30429
|
var activeContextHandlerSessions = new Set;
|
|
29656
30430
|
var lastHeuristicsTurnIdBySession = new Map;
|
|
@@ -29872,9 +30646,10 @@ function updateSessionProjectTracking(sessionId, projectIdentity, db) {
|
|
|
29872
30646
|
sessionsByProject.delete(prev);
|
|
29873
30647
|
clearPiSystemPromptSession(sessionId);
|
|
29874
30648
|
}
|
|
29875
|
-
if (db &&
|
|
30649
|
+
if (db && persistedProjectIdentityBySession.get(sessionId) !== projectIdentity) {
|
|
29876
30650
|
try {
|
|
29877
30651
|
recordSessionProjectIdentity(db, sessionId, projectIdentity);
|
|
30652
|
+
persistedProjectIdentityBySession.set(sessionId, projectIdentity);
|
|
29878
30653
|
} catch {}
|
|
29879
30654
|
}
|
|
29880
30655
|
trackSessionForProject(projectIdentity, sessionId);
|
|
@@ -31233,6 +32008,10 @@ function registerPiContextHandler(pi, baseOptions) {
|
|
|
31233
32008
|
log("[magic-context][pi] registered context handler (tagging + drops + nudges)");
|
|
31234
32009
|
}
|
|
31235
32010
|
var inFlightHistorian = new Map;
|
|
32011
|
+
var historianAbortControllers = new Map;
|
|
32012
|
+
function abortInFlightHistorians(sessionId) {
|
|
32013
|
+
historianAbortControllers.get(sessionId)?.abort();
|
|
32014
|
+
}
|
|
31236
32015
|
async function awaitInFlightHistorians(sessionId) {
|
|
31237
32016
|
const runs = sessionId ? [inFlightHistorian.get(sessionId)].filter((run) => run !== undefined) : [...inFlightHistorian.values()];
|
|
31238
32017
|
if (runs.length === 0)
|
|
@@ -31345,7 +32124,11 @@ function spawnPiHistorianRun(args) {
|
|
|
31345
32124
|
fallbackModelId
|
|
31346
32125
|
} = args;
|
|
31347
32126
|
const holderId = crypto3.randomUUID();
|
|
32127
|
+
const controller = new AbortController;
|
|
32128
|
+
historianAbortControllers.set(sessionId, controller);
|
|
31348
32129
|
const runPromise = (async () => {
|
|
32130
|
+
if (controller.signal.aborted)
|
|
32131
|
+
return;
|
|
31349
32132
|
const lease = acquireCompartmentLease(db, sessionId, holderId);
|
|
31350
32133
|
if (!lease) {
|
|
31351
32134
|
sessionLog(sessionId, "historian skipped: compartment lease held by another process");
|
|
@@ -31366,6 +32149,7 @@ function spawnPiHistorianRun(args) {
|
|
|
31366
32149
|
appendCompaction: resolvePiAppendCompaction(ctx),
|
|
31367
32150
|
readBranchEntries: resolvePiReadBranchEntries(ctx),
|
|
31368
32151
|
runner: historian.runner,
|
|
32152
|
+
signal: controller.signal,
|
|
31369
32153
|
historianModel: historian.model,
|
|
31370
32154
|
fallbackModels: historian.fallbackModels,
|
|
31371
32155
|
fallbackModelId,
|
|
@@ -31434,6 +32218,8 @@ function spawnPiHistorianRun(args) {
|
|
|
31434
32218
|
}).finally(() => {
|
|
31435
32219
|
try {
|
|
31436
32220
|
inFlightHistorian.delete(sessionId);
|
|
32221
|
+
if (historianAbortControllers.get(sessionId) === controller)
|
|
32222
|
+
historianAbortControllers.delete(sessionId);
|
|
31437
32223
|
unregister();
|
|
31438
32224
|
if (isContextHandlerSessionActive(sessionId)) {
|
|
31439
32225
|
historian.onStatusChange?.(ctx, sessionId);
|
|
@@ -31636,6 +32422,8 @@ Historian previously failed ${failureCount} time(s), so Magic Context is retryin
|
|
|
31636
32422
|
}
|
|
31637
32423
|
return;
|
|
31638
32424
|
}
|
|
32425
|
+
if (!ctx.hasUI)
|
|
32426
|
+
return;
|
|
31639
32427
|
triggered = true;
|
|
31640
32428
|
sessionLog(sessionId, `historian trigger fired (reason=${trigger.reason ?? "unknown"}) usage=${usage.percentage.toFixed(1)}% — spawning subagent`);
|
|
31641
32429
|
spawnPiHistorianRun({
|
|
@@ -32831,6 +33619,7 @@ function clearContextHandlerSession(sessionId) {
|
|
|
32831
33619
|
lastHeuristicsTurnIdBySession.delete(sessionId);
|
|
32832
33620
|
routinePressureAppliedBySession.delete(sessionId);
|
|
32833
33621
|
lastSeenProjectIdentityBySession.delete(sessionId);
|
|
33622
|
+
persistedProjectIdentityBySession.delete(sessionId);
|
|
32834
33623
|
for (const [projectIdentity, sessions] of sessionsByProject) {
|
|
32835
33624
|
sessions.delete(sessionId);
|
|
32836
33625
|
if (sessions.size === 0)
|
|
@@ -32985,6 +33774,7 @@ function promoteRecompStagingWithM0Mutation(db, sessionId, holderId) {
|
|
|
32985
33774
|
finished = true;
|
|
32986
33775
|
return null;
|
|
32987
33776
|
}
|
|
33777
|
+
deleteChunkEmbedBackoffForSession(db, sessionId);
|
|
32988
33778
|
db.prepare("DELETE FROM compartments WHERE session_id = ?").run(sessionId);
|
|
32989
33779
|
db.prepare("DELETE FROM session_facts WHERE session_id = ?").run(sessionId);
|
|
32990
33780
|
insertRecompCompartmentRows(db, sessionId, staging.compartments, now);
|
|
@@ -32999,6 +33789,7 @@ function promoteRecompStagingWithM0Mutation(db, sessionId, holderId) {
|
|
|
32999
33789
|
clearCachedM0M1(db, sessionId);
|
|
33000
33790
|
db.exec("COMMIT");
|
|
33001
33791
|
finished = true;
|
|
33792
|
+
invalidateAutoEmbedSession(sessionId);
|
|
33002
33793
|
logSlowWriteTransaction("historian-publish:recomp", transactionStartedAt);
|
|
33003
33794
|
return { compartments: staging.compartments, facts: staging.facts };
|
|
33004
33795
|
} finally {
|
|
@@ -35025,7 +35816,7 @@ function buildStatusView(source, options) {
|
|
|
35025
35816
|
// package.json
|
|
35026
35817
|
var package_default = {
|
|
35027
35818
|
name: "@cortexkit/pi-magic-context",
|
|
35028
|
-
version: "0.44.
|
|
35819
|
+
version: "0.44.2",
|
|
35029
35820
|
type: "module",
|
|
35030
35821
|
description: "Pi and OMP coding agent extension for Magic Context — cross-session memory and context management",
|
|
35031
35822
|
main: "dist/index.js",
|
|
@@ -35143,6 +35934,11 @@ async function showStatusDialog(pi, ctx, deps) {
|
|
|
35143
35934
|
overlayOptions: { anchor: "center", width: 78 }
|
|
35144
35935
|
});
|
|
35145
35936
|
}
|
|
35937
|
+
var openStatusDialogs = new Set;
|
|
35938
|
+
function stopStatusDialogRefresh() {
|
|
35939
|
+
for (const dialog of openStatusDialogs)
|
|
35940
|
+
dialog.close();
|
|
35941
|
+
}
|
|
35146
35942
|
|
|
35147
35943
|
class StatusDialogComponent {
|
|
35148
35944
|
props;
|
|
@@ -35152,6 +35948,7 @@ class StatusDialogComponent {
|
|
|
35152
35948
|
constructor(props) {
|
|
35153
35949
|
this.props = props;
|
|
35154
35950
|
this.detail = buildPiStatusDetail(props.pi, props.ctx, props.deps, props.sessionId);
|
|
35951
|
+
openStatusDialogs.add(this);
|
|
35155
35952
|
this.refreshTimer = setInterval(() => {
|
|
35156
35953
|
if (this.closed)
|
|
35157
35954
|
return;
|
|
@@ -35169,11 +35966,7 @@ class StatusDialogComponent {
|
|
|
35169
35966
|
close() {
|
|
35170
35967
|
if (this.closed)
|
|
35171
35968
|
return;
|
|
35172
|
-
this.
|
|
35173
|
-
if (this.refreshTimer) {
|
|
35174
|
-
clearInterval(this.refreshTimer);
|
|
35175
|
-
this.refreshTimer = null;
|
|
35176
|
-
}
|
|
35969
|
+
this.dispose();
|
|
35177
35970
|
this.props.done(undefined);
|
|
35178
35971
|
}
|
|
35179
35972
|
invalidate() {}
|
|
@@ -35183,6 +35976,8 @@ class StatusDialogComponent {
|
|
|
35183
35976
|
return drawBorder(inner, width, this.props.theme);
|
|
35184
35977
|
}
|
|
35185
35978
|
dispose() {
|
|
35979
|
+
this.closed = true;
|
|
35980
|
+
openStatusDialogs.delete(this);
|
|
35186
35981
|
if (this.refreshTimer) {
|
|
35187
35982
|
clearInterval(this.refreshTimer);
|
|
35188
35983
|
this.refreshTimer = null;
|
|
@@ -35726,7 +36521,10 @@ No active Pi session is available.`,
|
|
|
35726
36521
|
});
|
|
35727
36522
|
return;
|
|
35728
36523
|
}
|
|
35729
|
-
const currentDeps =
|
|
36524
|
+
const currentDeps = {
|
|
36525
|
+
...deps.resolveRuntimeDeps?.(ctx) ?? deps,
|
|
36526
|
+
shutdownSignal: deps.shutdownSignal
|
|
36527
|
+
};
|
|
35730
36528
|
if (currentDeps.compactionOff) {
|
|
35731
36529
|
sendStatus({
|
|
35732
36530
|
title: "/ctx-wrapup",
|
|
@@ -35859,6 +36657,8 @@ No runnable wrapup boundary is available yet; wrapped up 0 messages into 0 compa
|
|
|
35859
36657
|
console.warn(`[magic-context][pi] /ctx-wrapup marker renewal failed for ${sessionId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
35860
36658
|
}
|
|
35861
36659
|
}, 60000);
|
|
36660
|
+
const stopRenewal = () => clearInterval(renewal);
|
|
36661
|
+
deps.shutdownSignal?.addEventListener("abort", stopRenewal, { once: true });
|
|
35862
36662
|
try {
|
|
35863
36663
|
sendStatus({
|
|
35864
36664
|
title: "/ctx-wrapup",
|
|
@@ -35925,7 +36725,7 @@ Eligible history is about ${initialPlan.snapshot.trueRawEligibleTokens.toLocaleS
|
|
|
35925
36725
|
Chunk ${chunkIndex}: wrapping messages ${plan.snapshot.offset}-${plan.snapshot.eligibleEndOrdinal - 1} (~${plan.snapshot.trueRawEligibleTokens.toLocaleString()} eligible tokens remain).`,
|
|
35926
36726
|
level: "info"
|
|
35927
36727
|
});
|
|
35928
|
-
const leaseResult = await acquireCompartmentLeaseEventually(deps.db, sessionId, renewWrapupMarker, resolveWrapupLeaseWaitTimeout(deps));
|
|
36728
|
+
const leaseResult = await acquireCompartmentLeaseEventually(deps.db, sessionId, renewWrapupMarker, resolveWrapupLeaseWaitTimeout(deps), deps.shutdownSignal);
|
|
35929
36729
|
if (!leaseResult.ok) {
|
|
35930
36730
|
failure = ownershipLost ? `${ownershipLostReason}; wrapped up through message ${lastEnd}. Run /ctx-wrapup again to continue.` : leaseResult.reason === "timeout" ? `Timed out waiting for another process to release the compartment-state lease; wrapped up through message ${lastEnd}. Run /ctx-wrapup again to continue.` : "Another Magic Context rebuild started while wrapup was waiting. Run /ctx-wrapup again to continue.";
|
|
35931
36731
|
break;
|
|
@@ -35938,6 +36738,10 @@ Chunk ${chunkIndex}: wrapping messages ${plan.snapshot.offset}-${plan.snapshot.e
|
|
|
35938
36738
|
console.warn(`[magic-context][pi] /ctx-wrapup compartment lease renewal failed for ${sessionId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
35939
36739
|
}
|
|
35940
36740
|
}, COMPARTMENT_LEASE_RENEWAL_MS);
|
|
36741
|
+
const stopLeaseRenewal = () => clearInterval(leaseRenewal);
|
|
36742
|
+
deps.shutdownSignal?.addEventListener("abort", stopLeaseRenewal, {
|
|
36743
|
+
once: true
|
|
36744
|
+
});
|
|
35941
36745
|
try {
|
|
35942
36746
|
const runHistorian = deps.runPiHistorianForWrapup ?? runPiHistorian;
|
|
35943
36747
|
await runHistorian({
|
|
@@ -35946,6 +36750,7 @@ Chunk ${chunkIndex}: wrapping messages ${plan.snapshot.offset}-${plan.snapshot.e
|
|
|
35946
36750
|
directory: ctx.cwd,
|
|
35947
36751
|
provider,
|
|
35948
36752
|
runner: deps.runner,
|
|
36753
|
+
signal: deps.shutdownSignal,
|
|
35949
36754
|
historianModel: deps.historianModel,
|
|
35950
36755
|
fallbackModels: deps.historianFallbacks,
|
|
35951
36756
|
fallbackModelId: modelKey,
|
|
@@ -35989,6 +36794,7 @@ Chunk ${chunkIndex}: wrapping messages ${plan.snapshot.offset}-${plan.snapshot.e
|
|
|
35989
36794
|
});
|
|
35990
36795
|
} finally {
|
|
35991
36796
|
clearInterval(leaseRenewal);
|
|
36797
|
+
deps.shutdownSignal?.removeEventListener("abort", stopLeaseRenewal);
|
|
35992
36798
|
releaseCompartmentLeaseBestEffort(deps.db, sessionId, leaseHolder, sessionLog);
|
|
35993
36799
|
}
|
|
35994
36800
|
const afterEnd = getLastCompartmentEndMessage(deps.db, sessionId);
|
|
@@ -36018,6 +36824,7 @@ Wrapped up ${messagesWrapped} messages into ${compartmentsCreated} compartments;
|
|
|
36018
36824
|
Wrapped up ${messagesWrapped} messages into ${compartmentsCreated} compartments. The compacted history is queued and materializes on your next message.`;
|
|
36019
36825
|
} finally {
|
|
36020
36826
|
clearInterval(renewal);
|
|
36827
|
+
deps.shutdownSignal?.removeEventListener("abort", stopRenewal);
|
|
36021
36828
|
releaseWrapupInProgress(deps.db, sessionId, holderId);
|
|
36022
36829
|
}
|
|
36023
36830
|
} finally {
|
|
@@ -36039,10 +36846,12 @@ function resolvePiContextLimit(ctx, db, sessionId) {
|
|
|
36039
36846
|
detectedContextLimit
|
|
36040
36847
|
}) ?? 128000;
|
|
36041
36848
|
}
|
|
36042
|
-
async function acquireCompartmentLeaseEventually(db, sessionId, renewWrapupMarker, maxWaitMs) {
|
|
36849
|
+
async function acquireCompartmentLeaseEventually(db, sessionId, renewWrapupMarker, maxWaitMs, signal) {
|
|
36043
36850
|
const waitStartedAt = Date.now();
|
|
36044
36851
|
const remainingMs = () => Math.max(0, waitStartedAt + maxWaitMs - Date.now());
|
|
36045
36852
|
for (;; ) {
|
|
36853
|
+
if (signal?.aborted)
|
|
36854
|
+
return { ok: false, reason: "ownership_lost" };
|
|
36046
36855
|
if (remainingMs() <= 0)
|
|
36047
36856
|
return { ok: false, reason: "timeout" };
|
|
36048
36857
|
const holderId = crypto4.randomUUID();
|
|
@@ -36051,7 +36860,17 @@ async function acquireCompartmentLeaseEventually(db, sessionId, renewWrapupMarke
|
|
|
36051
36860
|
return { ok: true, holderId };
|
|
36052
36861
|
if (!renewWrapupMarker({}))
|
|
36053
36862
|
return { ok: false, reason: "ownership_lost" };
|
|
36054
|
-
await new Promise((resolve) =>
|
|
36863
|
+
await new Promise((resolve) => {
|
|
36864
|
+
const finish = () => {
|
|
36865
|
+
clearTimeout(timer);
|
|
36866
|
+
signal?.removeEventListener("abort", finish);
|
|
36867
|
+
resolve();
|
|
36868
|
+
};
|
|
36869
|
+
const timer = setTimeout(finish, Math.min(LEASE_WAIT_MS, remainingMs()));
|
|
36870
|
+
signal?.addEventListener("abort", finish, { once: true });
|
|
36871
|
+
if (signal?.aborted)
|
|
36872
|
+
finish();
|
|
36873
|
+
});
|
|
36055
36874
|
}
|
|
36056
36875
|
}
|
|
36057
36876
|
function estimateChunks(tokens, chunkTokens) {
|
|
@@ -36075,6 +36894,32 @@ function readBranchEntries(ctx) {
|
|
|
36075
36894
|
return Array.isArray(branch?.entries) ? branch.entries : [];
|
|
36076
36895
|
}
|
|
36077
36896
|
|
|
36897
|
+
// src/dreamer/session-activity-pi.ts
|
|
36898
|
+
function observePiMessageActivity(db, sessionId, timestamp) {
|
|
36899
|
+
observeSessionActivity(db, sessionId, timestamp);
|
|
36900
|
+
}
|
|
36901
|
+
function latestPiMessageTime(path) {
|
|
36902
|
+
let latest = 0;
|
|
36903
|
+
for (const entry of sessionEntries(path)) {
|
|
36904
|
+
const row = entry;
|
|
36905
|
+
if (row.type === "message" && typeof row.message?.timestamp === "number")
|
|
36906
|
+
latest = Math.max(latest, row.message.timestamp);
|
|
36907
|
+
}
|
|
36908
|
+
return latest || undefined;
|
|
36909
|
+
}
|
|
36910
|
+
async function backfillPiSessionActivity(db, harness, paths) {
|
|
36911
|
+
await backfillSessionActivity(db, harness, (sessionId) => {
|
|
36912
|
+
const path = paths.get(sessionId);
|
|
36913
|
+
if (!path)
|
|
36914
|
+
return;
|
|
36915
|
+
try {
|
|
36916
|
+
return latestPiMessageTime(path);
|
|
36917
|
+
} catch {
|
|
36918
|
+
return;
|
|
36919
|
+
}
|
|
36920
|
+
});
|
|
36921
|
+
}
|
|
36922
|
+
|
|
36078
36923
|
// src/dropped-input-guard-pi.ts
|
|
36079
36924
|
function createPiDroppedInputGuard(options = {}) {
|
|
36080
36925
|
const guard = createDroppedInputGuard({
|
|
@@ -36273,6 +37118,8 @@ function stripTagPrefixFromAssistantMessage(message) {
|
|
|
36273
37118
|
var PI_HARNESS_DETECTION = await resolvePiHarnessDetection();
|
|
36274
37119
|
var PI_HARNESS_KIND = PI_HARNESS_DETECTION.kind;
|
|
36275
37120
|
var PREFIX2 = `[magic-context][${PI_HARNESS_KIND}]`;
|
|
37121
|
+
var databaseExitHookRegistered = false;
|
|
37122
|
+
var activePiRuntimes = new Set;
|
|
36276
37123
|
var PI_BOOT_DEADLINE_MS = 15000;
|
|
36277
37124
|
var PI_CHILD_INIT_CONTEXT = Symbol.for("magic-context.pi.child-init-context");
|
|
36278
37125
|
var SUBAGENT_CHILD_SESSION_CREATED = "subagents:child:session-created";
|
|
@@ -36720,6 +37567,10 @@ async function src_default(pi) {
|
|
|
36720
37567
|
cacheSizeMb: bootConfig.config.sqlite.cache_size_mb,
|
|
36721
37568
|
mmapSizeMb: bootConfig.config.sqlite.mmap_size_mb
|
|
36722
37569
|
});
|
|
37570
|
+
if (!databaseExitHookRegistered) {
|
|
37571
|
+
process.once("exit", closeDatabase);
|
|
37572
|
+
databaseExitHookRegistered = true;
|
|
37573
|
+
}
|
|
36723
37574
|
const storageDir = getMagicContextStorageDir();
|
|
36724
37575
|
const dbPath = join11(storageDir, "context.db");
|
|
36725
37576
|
let openFailureCause = null;
|
|
@@ -36793,6 +37644,13 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
36793
37644
|
const offset = afterSessionId === null ? 0 : sessions.findIndex((session) => session.sessionId === afterSessionId) + 1;
|
|
36794
37645
|
return sessions.slice(offset, offset + limit);
|
|
36795
37646
|
});
|
|
37647
|
+
const api = await loadDefaultPiSessionApi();
|
|
37648
|
+
const paths = new Map;
|
|
37649
|
+
for (const session of await api.listSessions()) {
|
|
37650
|
+
if (typeof session.id === "string" && typeof session.path === "string")
|
|
37651
|
+
paths.set(session.id, session.path);
|
|
37652
|
+
}
|
|
37653
|
+
await backfillPiSessionActivity(database, PI_HARNESS_KIND, paths);
|
|
36796
37654
|
} catch (err) {
|
|
36797
37655
|
warn(`[session-projects] background runner failed: ${err}`);
|
|
36798
37656
|
}
|
|
@@ -36802,6 +37660,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
36802
37660
|
const projectDir = process.cwd();
|
|
36803
37661
|
const seenDreamerProjectIdentities = new Set;
|
|
36804
37662
|
const dreamerRegistrationOwner = {};
|
|
37663
|
+
activePiRuntimes.add(dreamerRegistrationOwner);
|
|
36805
37664
|
const commandLifecycleController = new AbortController;
|
|
36806
37665
|
registerCtxStatusLifecycleSignal(pi, commandLifecycleController.signal);
|
|
36807
37666
|
let sessionShuttingDown = false;
|
|
@@ -37055,7 +37914,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
37055
37914
|
projectDepsByDir.delete(ctx.cwd);
|
|
37056
37915
|
const current = resolveCurrentProjectDeps(ctx);
|
|
37057
37916
|
activeModelRegistry = ctx.modelRegistry;
|
|
37058
|
-
|
|
37917
|
+
if (ctx.hasUI)
|
|
37918
|
+
syncDreamerProjectRegistration(current, ctx.modelRegistry);
|
|
37059
37919
|
syncCtxMemoryToolEnabled(pi, current.config.memory.enabled);
|
|
37060
37920
|
const failuresToShow = claimConfigParseFailuresOnce("pi", current.configParseFailures);
|
|
37061
37921
|
if (ctx.hasUI && failuresToShow.length > 0) {
|
|
@@ -37190,6 +38050,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
37190
38050
|
});
|
|
37191
38051
|
info("registered /ctx-recomp");
|
|
37192
38052
|
registerCtxWrapupCommand(pi, {
|
|
38053
|
+
shutdownSignal: commandLifecycleController.signal,
|
|
37193
38054
|
db,
|
|
37194
38055
|
runner: wrapupRunner,
|
|
37195
38056
|
historianModel: bootProjectDeps.historianConfig?.model,
|
|
@@ -37241,7 +38102,10 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
37241
38102
|
dreamerEnabled: bootProjectDeps.dreamerEnabled,
|
|
37242
38103
|
resolveDreamerEnabled: (ctx) => resolveCurrentProjectDeps(ctx).dreamerEnabled,
|
|
37243
38104
|
onProjectSeen: (identity) => seenDreamerProjectIdentities.add(identity),
|
|
37244
|
-
ensureRegistered: (ctx) =>
|
|
38105
|
+
ensureRegistered: (ctx) => {
|
|
38106
|
+
if (ctx.hasUI)
|
|
38107
|
+
syncDreamerProjectRegistration(resolveCurrentProjectDeps(ctx), ctx.modelRegistry);
|
|
38108
|
+
},
|
|
37245
38109
|
registrationOwner: dreamerRegistrationOwner
|
|
37246
38110
|
});
|
|
37247
38111
|
info("registered /ctx-dream");
|
|
@@ -37260,7 +38124,6 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
37260
38124
|
info("registered /ctx-embed");
|
|
37261
38125
|
const dreamerConfig = bootProjectDeps.dreamerConfig;
|
|
37262
38126
|
if (dreamerConfig) {
|
|
37263
|
-
syncDreamerProjectRegistration(bootProjectDeps);
|
|
37264
38127
|
info(`registered dreamer (${summarizeDreamSchedule(dreamerConfig)})`);
|
|
37265
38128
|
} else {
|
|
37266
38129
|
info(bootProjectDeps.dreamerEnabled ? "registered dreamer: DISABLED (no dreamer config)" : "registered dreamer: DISABLED (dreamer.disable=true or no dreamer config)");
|
|
@@ -37293,7 +38156,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
37293
38156
|
const effectiveConfig = effectiveProjectDeps.config;
|
|
37294
38157
|
try {
|
|
37295
38158
|
activeModelRegistry = ctx.modelRegistry;
|
|
37296
|
-
|
|
38159
|
+
if (ctx.hasUI)
|
|
38160
|
+
syncDreamerProjectRegistration(effectiveProjectDeps, ctx.modelRegistry);
|
|
37297
38161
|
} catch (err) {
|
|
37298
38162
|
warn("before_agent_start: dreamer registration sync threw:", err);
|
|
37299
38163
|
}
|
|
@@ -37389,6 +38253,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
37389
38253
|
} catch (err) {
|
|
37390
38254
|
log(`agent_end: channel2 delivery skipped: ${String(err)}`);
|
|
37391
38255
|
}
|
|
38256
|
+
if (!ctx.hasUI)
|
|
38257
|
+
flushLogger();
|
|
37392
38258
|
});
|
|
37393
38259
|
pi.on("tool_execution_start", async (event, ctx) => {
|
|
37394
38260
|
try {
|
|
@@ -37469,6 +38335,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
37469
38335
|
if (typeof sessionId !== "string" || sessionId.length === 0)
|
|
37470
38336
|
return;
|
|
37471
38337
|
const endedMsg = event.message;
|
|
38338
|
+
observePiMessageActivity(db, sessionId, endedMsg?.timestamp);
|
|
37472
38339
|
if (endedMsg?.role === "assistant" && typeof endedMsg.id === "string" && endedMsg.id.length > 0) {
|
|
37473
38340
|
const messageId = endedMsg.id;
|
|
37474
38341
|
scheduleIncrementalIndex(db, sessionId, messageId, () => {
|
|
@@ -37549,6 +38416,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
37549
38416
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
37550
38417
|
sessionShuttingDown = true;
|
|
37551
38418
|
commandLifecycleController.abort();
|
|
38419
|
+
stopStatusDialogRefresh();
|
|
37552
38420
|
const SHUTDOWN_DRAIN_MS = 5000;
|
|
37553
38421
|
const sessionId = resolveSessionId(ctx);
|
|
37554
38422
|
try {
|
|
@@ -37562,6 +38430,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
37562
38430
|
warn("shutdown: unregisterPiDreamerProject threw:", err);
|
|
37563
38431
|
}
|
|
37564
38432
|
if (sessionId) {
|
|
38433
|
+
abortInFlightHistorians(sessionId);
|
|
38434
|
+
abortInFlightRecomps(sessionId);
|
|
37565
38435
|
try {
|
|
37566
38436
|
await withTimeout(awaitInFlightHistorians(sessionId), SHUTDOWN_DRAIN_MS);
|
|
37567
38437
|
} catch (err) {
|
|
@@ -37574,6 +38444,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
37574
38444
|
}
|
|
37575
38445
|
abortInFlightRecomps(sessionId);
|
|
37576
38446
|
}
|
|
38447
|
+
abortInFlightDreamers(dreamerRegistrationOwner);
|
|
37577
38448
|
try {
|
|
37578
38449
|
await withTimeout(awaitInFlightDreamers(dreamerRegistrationOwner), SHUTDOWN_DRAIN_MS);
|
|
37579
38450
|
} catch (err) {
|
|
@@ -37588,6 +38459,9 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
37588
38459
|
clearContextHandlerSession(sessionId);
|
|
37589
38460
|
}
|
|
37590
38461
|
} catch {}
|
|
38462
|
+
activePiRuntimes.delete(dreamerRegistrationOwner);
|
|
38463
|
+
if (activePiRuntimes.size === 0)
|
|
38464
|
+
unregisterPiProjectEmbeddings(db);
|
|
37591
38465
|
});
|
|
37592
38466
|
pi.on("session_before_switch", (_event, ctx) => {
|
|
37593
38467
|
try {
|