@cortexkit/pi-magic-context 0.44.0 → 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-tfzybajk.js → index-59dmj4zs.js} +4 -1
- package/dist/{index-jscq7067.js → index-5fw91enm.js} +1 -1
- package/dist/{index-mmkdxa5j.js → index-97e5fzy3.js} +1 -1
- package/dist/{index-8ytss1hp.js → index-esnhck8v.js} +14869 -14266
- package/dist/{index-2kj55q4e.js → index-gsff513e.js} +263 -51
- package/dist/index-n8kg008j.js +245 -0
- package/dist/index.js +1935 -877
- package/dist/{message-time-backfill-3rp0w2z9.js → message-time-backfill-5yrp95cx.js} +2 -2
- package/dist/{read-session-chunk-2s116ah5.js → read-session-chunk-zhtcx9f1.js} +4 -2
- package/dist/{safe-notification-target-pd09ybr0.js → safe-notification-target-y3hg2m7p.js} +2 -2
- package/dist/subagent-entry.js +22 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
setHarness,
|
|
3
3
|
getHarness,
|
|
4
|
+
harnessOwnsOpenCodeStore,
|
|
4
5
|
ensureCortexKitArtifactGitignore,
|
|
5
6
|
getProjectMagicContextHistorianDir,
|
|
6
7
|
getMagicContextStorageDir,
|
|
@@ -9,7 +10,7 @@ import {
|
|
|
9
10
|
log,
|
|
10
11
|
sessionLog,
|
|
11
12
|
flushLogger
|
|
12
|
-
} from "./index-
|
|
13
|
+
} from "./index-59dmj4zs.js";
|
|
13
14
|
import {
|
|
14
15
|
COMPACTION_ENABLED_PATH,
|
|
15
16
|
isDreamerRunnable,
|
|
@@ -43,13 +44,12 @@ import {
|
|
|
43
44
|
cortexKitProjectConfigBasePath,
|
|
44
45
|
migrateMagicContextConfigLocations,
|
|
45
46
|
withContentLanguageDirective,
|
|
46
|
-
buildPrimaryLanguageDirective,
|
|
47
47
|
parseCron,
|
|
48
48
|
nextOccurrence,
|
|
49
49
|
nextDueAtMs,
|
|
50
50
|
resolveModelConfigValue,
|
|
51
|
-
resolveModelConfigOrDefault,
|
|
52
51
|
DEFAULT_HISTORIAN_TIMEOUT_MS,
|
|
52
|
+
DREAM_TASK_PROMOTION_DEFAULTS,
|
|
53
53
|
LIVE_RELOAD_CONFIG_PATHS,
|
|
54
54
|
getProtectedTokensTierOverrides,
|
|
55
55
|
FAIL_CLOSED_DOCTOR_COMMAND,
|
|
@@ -60,12 +60,25 @@ import {
|
|
|
60
60
|
createFailClosedController,
|
|
61
61
|
ProjectIdentityError,
|
|
62
62
|
resolveProjectIdentityStrict,
|
|
63
|
+
isUserHomeDirectory,
|
|
63
64
|
resolveProjectIdentity,
|
|
65
|
+
isUsableProjectIdentity,
|
|
64
66
|
resolveProjectIdentityForSession,
|
|
65
67
|
beginBootQuietPeriod,
|
|
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,
|
|
@@ -74,42 +87,6 @@ import {
|
|
|
74
87
|
indexSingleMessage,
|
|
75
88
|
indexMessagesAfterOrdinal,
|
|
76
89
|
sweepOrphanedOpenCodeMessageIndexes,
|
|
77
|
-
recordSessionProjectIdentity,
|
|
78
|
-
piHarnessKindFromExecutable,
|
|
79
|
-
COMPARTMENT_LEASE_RENEWAL_MS,
|
|
80
|
-
acquireCompartmentLease,
|
|
81
|
-
renewCompartmentLease,
|
|
82
|
-
releaseCompartmentLease,
|
|
83
|
-
releaseCompartmentLeaseBestEffort,
|
|
84
|
-
isCompartmentLeaseHeld,
|
|
85
|
-
isNoContentCompartment,
|
|
86
|
-
HAS_COMPARTMENT_CONTENT_SQL,
|
|
87
|
-
persistCachedM0,
|
|
88
|
-
clearCachedM0M1,
|
|
89
|
-
getCompartments,
|
|
90
|
-
getLastCompartmentEndMessage,
|
|
91
|
-
getLastCompartmentEndMessageId,
|
|
92
|
-
getCompartmentsByEndMessageId,
|
|
93
|
-
appendCompartments,
|
|
94
|
-
saveRecompStagingPass,
|
|
95
|
-
getRecompStaging,
|
|
96
|
-
clearRecompStaging,
|
|
97
|
-
getRecompPartialRange,
|
|
98
|
-
setRecompPartialRange,
|
|
99
|
-
escapeXmlAttr,
|
|
100
|
-
escapeXmlContent,
|
|
101
|
-
getModuleNoteEvaluationBridge,
|
|
102
|
-
getContextStoreUuid,
|
|
103
|
-
getAuthorityManagedMarker,
|
|
104
|
-
drainMirrorPages,
|
|
105
|
-
V2_MEMORY_CATEGORIES,
|
|
106
|
-
parseCompartmentOutput,
|
|
107
|
-
resolveWorkspaceShareCategories,
|
|
108
|
-
resolveWorkspaceIdentitySet,
|
|
109
|
-
expandWorkspaceIdentitySetWithAliases,
|
|
110
|
-
sourceNameForMemory,
|
|
111
|
-
computeWorkspaceEpochFingerprint,
|
|
112
|
-
readProjectDocsCanonical,
|
|
113
90
|
MAX_EXECUTE_THRESHOLD,
|
|
114
91
|
escalationBands,
|
|
115
92
|
HYGIENE_PROVIDER_UNITS_VERSION,
|
|
@@ -118,6 +95,10 @@ import {
|
|
|
118
95
|
computeProtectionWindow,
|
|
119
96
|
readEpochFloorSnapshot,
|
|
120
97
|
getProtectionWindowForSession,
|
|
98
|
+
persistCachedM0,
|
|
99
|
+
clearCachedM0M1,
|
|
100
|
+
readReplayDocument,
|
|
101
|
+
updateReplayDocument,
|
|
121
102
|
isProviderOverflowFailClosedProven,
|
|
122
103
|
describeProtectedTailDrainBudgetSkip,
|
|
123
104
|
loadProtectedTailMeta,
|
|
@@ -194,6 +175,51 @@ import {
|
|
|
194
175
|
setSessionWorkMetrics,
|
|
195
176
|
getSessionWorkMetrics,
|
|
196
177
|
resolveEpochFloorForPass,
|
|
178
|
+
getOrCreateSessionMeta,
|
|
179
|
+
updateSessionMeta,
|
|
180
|
+
advanceToolReclaimWatermark,
|
|
181
|
+
retryPendingSessionCleanups,
|
|
182
|
+
retryPendingRustSessionCleanupsForProject,
|
|
183
|
+
recordSessionProjectIdentity,
|
|
184
|
+
embedPauseBySession,
|
|
185
|
+
embedRunStateBySession,
|
|
186
|
+
autoEmbedAttemptedBySession,
|
|
187
|
+
autoEmbedIdentityBySession,
|
|
188
|
+
invalidateAutoEmbedSession,
|
|
189
|
+
getEmbedDrainUiStatus,
|
|
190
|
+
piHarnessKindFromExecutable,
|
|
191
|
+
COMPARTMENT_LEASE_RENEWAL_MS,
|
|
192
|
+
acquireCompartmentLease,
|
|
193
|
+
renewCompartmentLease,
|
|
194
|
+
releaseCompartmentLease,
|
|
195
|
+
releaseCompartmentLeaseBestEffort,
|
|
196
|
+
isCompartmentLeaseHeld,
|
|
197
|
+
isNoContentCompartment,
|
|
198
|
+
HAS_COMPARTMENT_CONTENT_SQL,
|
|
199
|
+
getCompartments,
|
|
200
|
+
getLastCompartmentEndMessage,
|
|
201
|
+
getLastCompartmentEndMessageId,
|
|
202
|
+
getCompartmentsByEndMessageId,
|
|
203
|
+
appendCompartments,
|
|
204
|
+
saveRecompStagingPass,
|
|
205
|
+
getRecompStaging,
|
|
206
|
+
clearRecompStaging,
|
|
207
|
+
getRecompPartialRange,
|
|
208
|
+
setRecompPartialRange,
|
|
209
|
+
escapeXmlAttr,
|
|
210
|
+
escapeXmlContent,
|
|
211
|
+
getModuleNoteEvaluationBridge,
|
|
212
|
+
getContextStoreUuid,
|
|
213
|
+
getAuthorityManagedMarker,
|
|
214
|
+
drainMirrorPages,
|
|
215
|
+
V2_MEMORY_CATEGORIES,
|
|
216
|
+
parseCompartmentOutput,
|
|
217
|
+
resolveWorkspaceShareCategories,
|
|
218
|
+
resolveWorkspaceIdentitySet,
|
|
219
|
+
expandWorkspaceIdentitySetWithAliases,
|
|
220
|
+
sourceNameForMemory,
|
|
221
|
+
computeWorkspaceEpochFingerprint,
|
|
222
|
+
readProjectDocsCanonical,
|
|
197
223
|
BoundedSessionMap,
|
|
198
224
|
registerLkgPersistence,
|
|
199
225
|
signatureForFields,
|
|
@@ -224,6 +250,7 @@ import {
|
|
|
224
250
|
runSqliteOptimize,
|
|
225
251
|
openDatabase,
|
|
226
252
|
openDatabaseAsync,
|
|
253
|
+
closeDatabase,
|
|
227
254
|
queueM0Mutation,
|
|
228
255
|
getMaxM0MutationId,
|
|
229
256
|
queueMemoryMutation,
|
|
@@ -231,11 +258,6 @@ import {
|
|
|
231
258
|
getMemoryMutationsForRenderByProjects,
|
|
232
259
|
getMaxMemoryMutationId,
|
|
233
260
|
getMaxMemoryMutationIdForProjects,
|
|
234
|
-
getOrCreateSessionMeta,
|
|
235
|
-
updateSessionMeta,
|
|
236
|
-
advanceToolReclaimWatermark,
|
|
237
|
-
retryPendingSessionCleanups,
|
|
238
|
-
retryPendingRustSessionCleanupsForProject,
|
|
239
261
|
getNotes,
|
|
240
262
|
getSessionNotes,
|
|
241
263
|
getPendingSmartNotes,
|
|
@@ -323,13 +345,6 @@ import {
|
|
|
323
345
|
peekCurateCategoryScope,
|
|
324
346
|
beginCurateCategoryRun,
|
|
325
347
|
curateTaskStateAfterSuccess,
|
|
326
|
-
formatSynapseLaneDescriptor,
|
|
327
|
-
buildCanonicalChunkTextFromFts,
|
|
328
|
-
buildCompartmentSummaryFallbackText,
|
|
329
|
-
canonicalizeInMemoryChunkTextForEmbedding,
|
|
330
|
-
chunkCanonicalText,
|
|
331
|
-
chunkEmbeddingWindowsAreCurrent,
|
|
332
|
-
replaceCompartmentChunkEmbeddings,
|
|
333
348
|
cosineSimilarity,
|
|
334
349
|
GIT_SWEEP_LEASE_RENEWAL_MS,
|
|
335
350
|
acquireGitSweepLease,
|
|
@@ -351,6 +366,7 @@ import {
|
|
|
351
366
|
drainCommitBacklogForProject,
|
|
352
367
|
embedSessionCompartmentChunks,
|
|
353
368
|
getEmbeddingCoverageStatus,
|
|
369
|
+
getAutoEmbeddingSessionCoverage,
|
|
354
370
|
promoteSessionFactsDurable,
|
|
355
371
|
embedPromotedFacts,
|
|
356
372
|
recordMemoryMapping,
|
|
@@ -371,9 +387,12 @@ import {
|
|
|
371
387
|
embedUnembeddedCommits,
|
|
372
388
|
loadPiConfig,
|
|
373
389
|
loadPiConfigDetailed,
|
|
390
|
+
unregisterPiProjectEmbeddings,
|
|
374
391
|
ensureProjectRegisteredFromPiDirectory,
|
|
375
392
|
resolvePiHarnessDetection,
|
|
376
393
|
resolvePiHarnessKind,
|
|
394
|
+
MEMORY_MURAL_BLOCK,
|
|
395
|
+
buildMagicContextSection,
|
|
377
396
|
resolveMuralWire,
|
|
378
397
|
updateCompactionMarkerAfterPublication,
|
|
379
398
|
COMPARTMENT_RENDER_EPOCH,
|
|
@@ -404,7 +423,7 @@ import {
|
|
|
404
423
|
registerTodoStateLifecycle,
|
|
405
424
|
syncCtxMemoryToolEnabled,
|
|
406
425
|
registerMagicContextTools
|
|
407
|
-
} from "./index-
|
|
426
|
+
} from "./index-esnhck8v.js";
|
|
408
427
|
import {
|
|
409
428
|
CTX_REDUCE_KEEP,
|
|
410
429
|
newestCtxReduceTagNumbers,
|
|
@@ -483,6 +502,7 @@ import {
|
|
|
483
502
|
claimOpenCodeDbDiagnosticOnce,
|
|
484
503
|
Database,
|
|
485
504
|
closeQuietly,
|
|
505
|
+
RAW_SUMMARY_TEXT_MAX_CHARS,
|
|
486
506
|
completedToolArcCrossesBoundary,
|
|
487
507
|
estimateTrueRawMessageTokens,
|
|
488
508
|
buildToolArcs,
|
|
@@ -491,6 +511,12 @@ import {
|
|
|
491
511
|
buildTrueRawTokenIndex,
|
|
492
512
|
computeRawRangeFingerprint,
|
|
493
513
|
invalidateTrueRawTokenCache,
|
|
514
|
+
DreamTokenBudgetExceeded,
|
|
515
|
+
TOKEN_BUDGET_FINALIZE_MESSAGE,
|
|
516
|
+
TOKEN_BUDGET_TOOL_REFUSAL,
|
|
517
|
+
registerBudgetFinalizeChild,
|
|
518
|
+
releaseBudgetFinalizeChild,
|
|
519
|
+
createDreamTokenBudget,
|
|
494
520
|
droppedInputMarker,
|
|
495
521
|
createDroppedInputGuard,
|
|
496
522
|
isEditTool,
|
|
@@ -507,6 +533,7 @@ import {
|
|
|
507
533
|
withRawSessionMessageCache2,
|
|
508
534
|
readRawSessionMessages2,
|
|
509
535
|
getRawSessionMessageOrdinalCount2,
|
|
536
|
+
visitRawSessionMessages2,
|
|
510
537
|
readRawSessionMessageRange2,
|
|
511
538
|
primeTailRawMessageCache2,
|
|
512
539
|
getCachedAbsoluteMessageCount2,
|
|
@@ -516,14 +543,18 @@ import {
|
|
|
516
543
|
getRawSessionTagKeysThrough2,
|
|
517
544
|
getLegacyProtectedTailStartOrdinal2,
|
|
518
545
|
readSessionChunk2
|
|
519
|
-
} from "./index-
|
|
546
|
+
} from "./index-gsff513e.js";
|
|
520
547
|
import {
|
|
521
548
|
pushNotification2
|
|
522
549
|
} from "./index-b3eqj1g6.js";
|
|
523
|
-
import
|
|
550
|
+
import {
|
|
551
|
+
subagentStepCap,
|
|
552
|
+
SubagentTelemetry
|
|
553
|
+
} from "./index-n8kg008j.js";
|
|
554
|
+
import"./index-5fw91enm.js";
|
|
524
555
|
import {
|
|
525
556
|
logSlowWriteTransaction
|
|
526
|
-
} from "./index-
|
|
557
|
+
} from "./index-97e5fzy3.js";
|
|
527
558
|
import {
|
|
528
559
|
__toESM
|
|
529
560
|
} from "./index-22jkk6wg.js";
|
|
@@ -531,7 +562,7 @@ import {
|
|
|
531
562
|
// src/index.ts
|
|
532
563
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
533
564
|
import { createRequire as createRequire2 } from "node:module";
|
|
534
|
-
import { join as
|
|
565
|
+
import { join as join11, resolve as resolve4 } from "node:path";
|
|
535
566
|
|
|
536
567
|
// ../plugin/src/config/live-snapshot.ts
|
|
537
568
|
import { createHash } from "node:crypto";
|
|
@@ -816,6 +847,15 @@ var CANONICAL_DREAM_TASKS = [
|
|
|
816
847
|
"promote-primers",
|
|
817
848
|
"refresh-primers"
|
|
818
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
|
+
};
|
|
819
859
|
var DREAM_TASK_CAPABILITIES = {
|
|
820
860
|
"map-memories": {
|
|
821
861
|
requiresTools: true,
|
|
@@ -928,6 +968,7 @@ function buildDreamTaskRuntimeConfigs(dreamer, harness, language, muralModel) {
|
|
|
928
968
|
thinkingLevel: harness === "pi" ? resolved.primary?.qualifier : undefined,
|
|
929
969
|
language,
|
|
930
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,
|
|
931
972
|
promotionThreshold: resolved.promotionThreshold,
|
|
932
973
|
docsMaxTokens: task === "maintain-docs" ? dreamer?.tasks?.["maintain-docs"]?.max_tokens ?? 12000 : undefined,
|
|
933
974
|
retrospectiveRecencyDays: task === "retrospective" ? dreamer?.tasks?.retrospective?.recency_days ?? 30 : undefined
|
|
@@ -1319,6 +1360,67 @@ function parseReportedLimit(message) {
|
|
|
1319
1360
|
return;
|
|
1320
1361
|
}
|
|
1321
1362
|
|
|
1363
|
+
// ../plugin/src/shared/model-cache-ttl.ts
|
|
1364
|
+
var MODEL_CACHE_LIFETIMES = [
|
|
1365
|
+
{
|
|
1366
|
+
source: "OpenAI GPT-5.6+ default",
|
|
1367
|
+
value: "30m",
|
|
1368
|
+
matches(model) {
|
|
1369
|
+
const version = /^gpt-(\d+)(?:\.(\d+))?(?:$|[^\d.])/.exec(model);
|
|
1370
|
+
if (!version)
|
|
1371
|
+
return false;
|
|
1372
|
+
const major = Number(version[1]);
|
|
1373
|
+
const minor = Number(version[2] ?? 0);
|
|
1374
|
+
return major > 5 || major === 5 && minor >= 6;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
];
|
|
1378
|
+
function resolveModelCacheTtl(config, modelKey) {
|
|
1379
|
+
if (config && typeof config !== "string") {
|
|
1380
|
+
const match = modelKey && !modelKey.includes("/") && Object.hasOwn(config, modelKey) && modelKey !== "default" ? config[modelKey] : resolveModelConfigValue(config, modelKey)?.value;
|
|
1381
|
+
if (match !== undefined)
|
|
1382
|
+
return { value: match, source: "config", modelKey };
|
|
1383
|
+
}
|
|
1384
|
+
if (typeof config === "string" && config !== "5m")
|
|
1385
|
+
return { value: config, source: "config", modelKey };
|
|
1386
|
+
const model = canonicalModelIdentity(modelKey ?? "").toLowerCase().split("/").at(-1) ?? "";
|
|
1387
|
+
const known = MODEL_CACHE_LIFETIMES.find((entry) => entry.matches(model));
|
|
1388
|
+
if (known)
|
|
1389
|
+
return { value: known.value, source: known.source, modelKey };
|
|
1390
|
+
return {
|
|
1391
|
+
value: (typeof config === "object" ? config.default : config) ?? "5m",
|
|
1392
|
+
source: "default",
|
|
1393
|
+
modelKey
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
// ../plugin/src/features/magic-context/session-cache-ttl.ts
|
|
1398
|
+
function readSessionCacheTtl(db, sessionId) {
|
|
1399
|
+
const saved = readReplayDocument(db, sessionId).cacheTtlPolicy;
|
|
1400
|
+
return saved && typeof saved.value === "string" && saved.config !== undefined ? saved : undefined;
|
|
1401
|
+
}
|
|
1402
|
+
function resolveSessionCacheTtl(db, sessionId, config, modelKey) {
|
|
1403
|
+
const meta = getOrCreateSessionMeta(db, sessionId);
|
|
1404
|
+
let saved = readSessionCacheTtl(db, sessionId);
|
|
1405
|
+
if (!modelKey)
|
|
1406
|
+
return saved ?? resolveModelCacheTtl(config ?? meta.cacheTtl, undefined);
|
|
1407
|
+
if (!saved || saved.modelKey !== modelKey) {
|
|
1408
|
+
const frozenConfig = saved?.config ?? config ?? meta.cacheTtl;
|
|
1409
|
+
const resolved = resolveModelCacheTtl(frozenConfig, modelKey);
|
|
1410
|
+
const next = { ...resolved, config: frozenConfig };
|
|
1411
|
+
if (!updateReplayDocument(db, sessionId, (doc) => {
|
|
1412
|
+
doc.version = 2;
|
|
1413
|
+
doc.cacheTtlPolicy = next;
|
|
1414
|
+
return true;
|
|
1415
|
+
}))
|
|
1416
|
+
throw new Error("cannot persist session cache TTL policy");
|
|
1417
|
+
saved = next;
|
|
1418
|
+
}
|
|
1419
|
+
if (meta.cacheTtl !== saved.value)
|
|
1420
|
+
updateSessionMeta(db, sessionId, { cacheTtl: saved.value });
|
|
1421
|
+
return saved;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1322
1424
|
// ../plugin/src/features/magic-context/session-project-backfill.ts
|
|
1323
1425
|
import { randomUUID } from "node:crypto";
|
|
1324
1426
|
import { existsSync } from "node:fs";
|
|
@@ -1460,7 +1562,7 @@ async function runSessionProjectBackfill(db, source, options = {}) {
|
|
|
1460
1562
|
const startedAt = performance.now();
|
|
1461
1563
|
const harness = getHarness();
|
|
1462
1564
|
const now = options.now ?? Date.now;
|
|
1463
|
-
const resolveIdentity = options.resolveIdentity ??
|
|
1565
|
+
const resolveIdentity = options.resolveIdentity ?? ((directory) => resolveProjectIdentityForSession(directory, options.allowHomeProject));
|
|
1464
1566
|
const yieldFn = options.yieldFn ?? defaultYieldFn;
|
|
1465
1567
|
const holderId = options.holderId ?? randomUUID();
|
|
1466
1568
|
const readPage = createPageReader(source);
|
|
@@ -1474,7 +1576,8 @@ async function runSessionProjectBackfill(db, source, options = {}) {
|
|
|
1474
1576
|
skippedEmptyDirectories: 0,
|
|
1475
1577
|
durationMs: 0
|
|
1476
1578
|
};
|
|
1477
|
-
const
|
|
1579
|
+
const leaseKey = options.leaseKey ?? harness;
|
|
1580
|
+
const leaseStatus = acquireBackfillLease(db, leaseKey, holderId, now());
|
|
1478
1581
|
if (leaseStatus !== "acquired") {
|
|
1479
1582
|
result.status = leaseStatus;
|
|
1480
1583
|
result.durationMs = performance.now() - startedAt;
|
|
@@ -1486,10 +1589,10 @@ async function runSessionProjectBackfill(db, source, options = {}) {
|
|
|
1486
1589
|
return result;
|
|
1487
1590
|
};
|
|
1488
1591
|
const yieldAndRenew = async () => {
|
|
1489
|
-
if (!renewBackfillLease(db,
|
|
1592
|
+
if (!renewBackfillLease(db, leaseKey, holderId, now()))
|
|
1490
1593
|
return false;
|
|
1491
1594
|
await yieldFn();
|
|
1492
|
-
return renewBackfillLease(db,
|
|
1595
|
+
return renewBackfillLease(db, leaseKey, holderId, now());
|
|
1493
1596
|
};
|
|
1494
1597
|
const existenceCache = new Map;
|
|
1495
1598
|
const identityCache = new Map;
|
|
@@ -1503,7 +1606,7 @@ async function runSessionProjectBackfill(db, source, options = {}) {
|
|
|
1503
1606
|
sourcePage = await readPage(afterSessionId, SESSION_PAGE_SIZE);
|
|
1504
1607
|
} catch (error) {
|
|
1505
1608
|
try {
|
|
1506
|
-
if (!markBackfillRetryPending(db,
|
|
1609
|
+
if (!markBackfillRetryPending(db, leaseKey, holderId, now())) {
|
|
1507
1610
|
log("[session-projects] backfill lease changed before failure cleanup");
|
|
1508
1611
|
}
|
|
1509
1612
|
} catch (releaseError) {
|
|
@@ -1528,7 +1631,7 @@ async function runSessionProjectBackfill(db, source, options = {}) {
|
|
|
1528
1631
|
result.alreadyMappedSessions += 1;
|
|
1529
1632
|
} else {
|
|
1530
1633
|
result.unmappedSessions += 1;
|
|
1531
|
-
if (!session.directory) {
|
|
1634
|
+
if (!session.directory || !options.allowHomeProject && isUserHomeDirectory(session.directory)) {
|
|
1532
1635
|
result.skippedEmptyDirectories += 1;
|
|
1533
1636
|
} else {
|
|
1534
1637
|
const stillExists = existenceCache.get(session.directory) ?? existsSync(session.directory);
|
|
@@ -1546,13 +1649,18 @@ async function runSessionProjectBackfill(db, source, options = {}) {
|
|
|
1546
1649
|
}
|
|
1547
1650
|
identity = await resolveIdentity(session.directory);
|
|
1548
1651
|
identityResolutionsSinceYield += 1;
|
|
1549
|
-
if (!renewBackfillLease(db,
|
|
1652
|
+
if (!renewBackfillLease(db, leaseKey, holderId, now())) {
|
|
1550
1653
|
return finishWithLostLease();
|
|
1551
1654
|
}
|
|
1552
|
-
|
|
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;
|
|
1553
1663
|
}
|
|
1554
|
-
recordSessionProjectIdentity(db, session.sessionId, identity);
|
|
1555
|
-
result.backfilledSessions += 1;
|
|
1556
1664
|
}
|
|
1557
1665
|
}
|
|
1558
1666
|
}
|
|
@@ -1568,11 +1676,11 @@ async function runSessionProjectBackfill(db, source, options = {}) {
|
|
|
1568
1676
|
}
|
|
1569
1677
|
const hasSkippedSessions = result.skippedDeadDirectories > 0 || result.skippedEmptyDirectories > 0;
|
|
1570
1678
|
if (hasSkippedSessions) {
|
|
1571
|
-
if (!markBackfillRetryPending(db,
|
|
1679
|
+
if (!markBackfillRetryPending(db, leaseKey, holderId, now())) {
|
|
1572
1680
|
return finishWithLostLease();
|
|
1573
1681
|
}
|
|
1574
1682
|
result.status = "retry_pending";
|
|
1575
|
-
} else if (!markBackfillCompleted(db,
|
|
1683
|
+
} else if (!markBackfillCompleted(db, leaseKey, holderId, now())) {
|
|
1576
1684
|
return finishWithLostLease();
|
|
1577
1685
|
}
|
|
1578
1686
|
result.durationMs = performance.now() - startedAt;
|
|
@@ -1642,7 +1750,7 @@ function countFailures(db) {
|
|
|
1642
1750
|
function classifyBackfillError(error) {
|
|
1643
1751
|
if (error instanceof ProjectIdentityError) {
|
|
1644
1752
|
return {
|
|
1645
|
-
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,
|
|
1646
1754
|
errorMessage: error.message
|
|
1647
1755
|
};
|
|
1648
1756
|
}
|
|
@@ -1896,141 +2004,6 @@ function producerSourceLocalBudget(providerTokens, modelKey) {
|
|
|
1896
2004
|
return ratio === 1 ? providerTokens : localBudget(providerTokens, ratio);
|
|
1897
2005
|
}
|
|
1898
2006
|
|
|
1899
|
-
// ../plugin/src/hooks/magic-context/event-resolvers.ts
|
|
1900
|
-
var DEFAULT_CONTEXT_LIMIT = 200000;
|
|
1901
|
-
function resolveCacheTtl(cacheTtl, modelKey) {
|
|
1902
|
-
if (typeof cacheTtl === "string") {
|
|
1903
|
-
return cacheTtl;
|
|
1904
|
-
}
|
|
1905
|
-
return resolveModelConfigOrDefault(cacheTtl, modelKey, cacheTtl.default ?? "5m");
|
|
1906
|
-
}
|
|
1907
|
-
var clampWarnSeen = new Set;
|
|
1908
|
-
function isFinitePositive(v) {
|
|
1909
|
-
return typeof v === "number" && Number.isFinite(v) && v > 0;
|
|
1910
|
-
}
|
|
1911
|
-
function* modelKeyLookupOrder(modelKey) {
|
|
1912
|
-
const slash = modelKey.indexOf("/");
|
|
1913
|
-
const providerRefs = slash >= 0 ? modelRefLookupOrder(modelKey) : [];
|
|
1914
|
-
let modelId = slash >= 0 ? modelKey.slice(slash + 1) : modelKey;
|
|
1915
|
-
while (modelId.length > 0) {
|
|
1916
|
-
for (const providerRef of providerRefs) {
|
|
1917
|
-
const providerSlash = providerRef.indexOf("/");
|
|
1918
|
-
yield `${providerRef.slice(0, providerSlash)}/${modelId}`;
|
|
1919
|
-
}
|
|
1920
|
-
yield modelId;
|
|
1921
|
-
const lastDash = modelId.lastIndexOf("-");
|
|
1922
|
-
if (lastDash <= 0)
|
|
1923
|
-
break;
|
|
1924
|
-
modelId = modelId.slice(0, lastDash);
|
|
1925
|
-
}
|
|
1926
|
-
}
|
|
1927
|
-
function resolveExecuteThresholdDetail(config, modelKey, fallback, options) {
|
|
1928
|
-
if (options?.tokensConfig && isFinitePositive(options.contextLimit)) {
|
|
1929
|
-
const contextLimit = options.contextLimit;
|
|
1930
|
-
const tokenMatch = resolveTokensMatchWithKey(options.tokensConfig, modelKey);
|
|
1931
|
-
if (tokenMatch && isFinitePositive(tokenMatch.value)) {
|
|
1932
|
-
const cap = contextLimit * (MAX_EXECUTE_THRESHOLD / 100);
|
|
1933
|
-
const effectiveTokens = Math.min(tokenMatch.value, cap);
|
|
1934
|
-
if (effectiveTokens < tokenMatch.value) {
|
|
1935
|
-
const dedupeKey = `${options.sessionId ?? "__global__"}|${modelKey ?? "__default__"}|${tokenMatch.value}|${cap}`;
|
|
1936
|
-
if (!clampWarnSeen.has(dedupeKey)) {
|
|
1937
|
-
clampWarnSeen.add(dedupeKey);
|
|
1938
|
-
const msg = `execute_threshold_tokens clamped: ${tokenMatch.value} → ${effectiveTokens} (${MAX_EXECUTE_THRESHOLD}% of ${contextLimit}) for ${modelKey ?? "default"}`;
|
|
1939
|
-
if (options.sessionId) {
|
|
1940
|
-
sessionLog(options.sessionId, `WARN: ${msg}`);
|
|
1941
|
-
} else {
|
|
1942
|
-
log(`[magic-context] WARN: ${msg}`);
|
|
1943
|
-
}
|
|
1944
|
-
}
|
|
1945
|
-
}
|
|
1946
|
-
const percentage = effectiveTokens / contextLimit * 100;
|
|
1947
|
-
const detail = {
|
|
1948
|
-
percentage: Math.min(percentage, MAX_EXECUTE_THRESHOLD),
|
|
1949
|
-
mode: "tokens",
|
|
1950
|
-
absoluteTokens: Math.floor(effectiveTokens),
|
|
1951
|
-
matchedKey: tokenMatch.matchedKey
|
|
1952
|
-
};
|
|
1953
|
-
if (effectiveTokens < tokenMatch.value) {
|
|
1954
|
-
detail.clamped = true;
|
|
1955
|
-
detail.configuredValue = tokenMatch.value;
|
|
1956
|
-
}
|
|
1957
|
-
return detail;
|
|
1958
|
-
}
|
|
1959
|
-
}
|
|
1960
|
-
let resolved;
|
|
1961
|
-
let matchedKey;
|
|
1962
|
-
if (typeof config === "number") {
|
|
1963
|
-
resolved = config;
|
|
1964
|
-
} else if (modelKey) {
|
|
1965
|
-
let matched;
|
|
1966
|
-
for (const candidate of modelKeyLookupOrder(modelKey)) {
|
|
1967
|
-
if (typeof config[candidate] === "number") {
|
|
1968
|
-
matched = config[candidate];
|
|
1969
|
-
matchedKey = candidate;
|
|
1970
|
-
break;
|
|
1971
|
-
}
|
|
1972
|
-
}
|
|
1973
|
-
if (matched === undefined && typeof config.default === "number") {
|
|
1974
|
-
resolved = config.default;
|
|
1975
|
-
matchedKey = "default";
|
|
1976
|
-
} else {
|
|
1977
|
-
resolved = matched ?? fallback;
|
|
1978
|
-
}
|
|
1979
|
-
} else if (typeof config.default === "number") {
|
|
1980
|
-
resolved = config.default;
|
|
1981
|
-
matchedKey = "default";
|
|
1982
|
-
} else {
|
|
1983
|
-
resolved = fallback;
|
|
1984
|
-
}
|
|
1985
|
-
if (!Number.isFinite(resolved) || resolved < 0) {
|
|
1986
|
-
resolved = fallback;
|
|
1987
|
-
}
|
|
1988
|
-
const cappedPercentage = Math.min(resolved, MAX_EXECUTE_THRESHOLD);
|
|
1989
|
-
const percentageClamped = cappedPercentage < resolved;
|
|
1990
|
-
if (percentageClamped) {
|
|
1991
|
-
const dedupeKey = `pct|${options?.sessionId ?? "__global__"}|${modelKey ?? "__default__"}|${resolved}`;
|
|
1992
|
-
if (!clampWarnSeen.has(dedupeKey)) {
|
|
1993
|
-
clampWarnSeen.add(dedupeKey);
|
|
1994
|
-
const msg = `execute_threshold clamped ${resolved}% → ${MAX_EXECUTE_THRESHOLD}% for ${modelKey ?? "default"} (capped against the output-reserved safe window; 10% remains for mid-turn growth before the absolute 95% wall)`;
|
|
1995
|
-
if (options?.sessionId) {
|
|
1996
|
-
sessionLog(options.sessionId, `WARN: ${msg}`);
|
|
1997
|
-
} else {
|
|
1998
|
-
log(`[magic-context] WARN: ${msg}`);
|
|
1999
|
-
}
|
|
2000
|
-
}
|
|
2001
|
-
}
|
|
2002
|
-
const detail = {
|
|
2003
|
-
percentage: cappedPercentage,
|
|
2004
|
-
mode: "percentage",
|
|
2005
|
-
matchedKey
|
|
2006
|
-
};
|
|
2007
|
-
if (percentageClamped) {
|
|
2008
|
-
detail.clamped = true;
|
|
2009
|
-
detail.configuredValue = resolved;
|
|
2010
|
-
}
|
|
2011
|
-
return detail;
|
|
2012
|
-
}
|
|
2013
|
-
function resolveExecuteThreshold(config, modelKey, fallback, options) {
|
|
2014
|
-
return resolveExecuteThresholdDetail(config, modelKey, fallback, options).percentage;
|
|
2015
|
-
}
|
|
2016
|
-
function resolveTokensMatchWithKey(tokensConfig, modelKey) {
|
|
2017
|
-
if (!tokensConfig) {
|
|
2018
|
-
return;
|
|
2019
|
-
}
|
|
2020
|
-
if (modelKey) {
|
|
2021
|
-
for (const candidate of modelKeyLookupOrder(modelKey)) {
|
|
2022
|
-
const value = tokensConfig[candidate];
|
|
2023
|
-
if (typeof value === "number") {
|
|
2024
|
-
return { value, matchedKey: candidate };
|
|
2025
|
-
}
|
|
2026
|
-
}
|
|
2027
|
-
}
|
|
2028
|
-
if (typeof tokensConfig.default === "number") {
|
|
2029
|
-
return { value: tokensConfig.default, matchedKey: "default" };
|
|
2030
|
-
}
|
|
2031
|
-
return;
|
|
2032
|
-
}
|
|
2033
|
-
|
|
2034
2007
|
// ../plugin/src/hooks/magic-context/note-nudger.ts
|
|
2035
2008
|
var NOTE_NUDGE_COOLDOWN_MS = 15 * 60 * 1000;
|
|
2036
2009
|
var lastDeliveredAt = new Map;
|
|
@@ -2381,10 +2354,12 @@ var SMART_NOTE_CHECK_LIVENESS_RECHECK_MS = 24 * 60 * 60 * 1000;
|
|
|
2381
2354
|
class SmartNoteNetworkError extends Error {
|
|
2382
2355
|
isSmartNoteNetworkError = true;
|
|
2383
2356
|
terminal;
|
|
2357
|
+
persistent;
|
|
2384
2358
|
constructor(message, options = {}) {
|
|
2385
2359
|
super(message);
|
|
2386
2360
|
this.name = "SmartNoteNetworkError";
|
|
2387
2361
|
this.terminal = options.terminal ?? false;
|
|
2362
|
+
this.persistent = options.persistent ?? false;
|
|
2388
2363
|
}
|
|
2389
2364
|
}
|
|
2390
2365
|
|
|
@@ -2506,6 +2481,7 @@ function storeCompiledSmartNoteCheck(db, args) {
|
|
|
2506
2481
|
check_failure_count = 0,
|
|
2507
2482
|
check_network_failure_count = 0,
|
|
2508
2483
|
check_quarantined_until = NULL,
|
|
2484
|
+
ready_reason = NULL,
|
|
2509
2485
|
check_next_due_at = ?,
|
|
2510
2486
|
check_compiled_at = ?,
|
|
2511
2487
|
check_false_since_at = COALESCE(check_false_since_at, ?),
|
|
@@ -2554,15 +2530,17 @@ function markSmartNoteLivenessChecked(db, noteId, now) {
|
|
|
2554
2530
|
function markSmartNoteCheckStatus(db, noteId, status, now) {
|
|
2555
2531
|
db.prepare(`UPDATE notes SET check_status = ?, updated_at = ? WHERE id = ? AND type = 'smart'`).run(status, now, noteId);
|
|
2556
2532
|
}
|
|
2557
|
-
function markSmartNoteCompilationFailure(db, noteId, now, maxFailures) {
|
|
2533
|
+
function markSmartNoteCompilationFailure(db, noteId, now, maxFailures, error, persistent) {
|
|
2558
2534
|
const failureCount = readFailureCount(db, noteId, "check_failure_count") + 1;
|
|
2559
|
-
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));
|
|
2560
2537
|
db.prepare(`UPDATE notes
|
|
2561
2538
|
SET check_failure_count = ?,
|
|
2562
2539
|
check_status = ?,
|
|
2563
2540
|
check_next_due_at = ?,
|
|
2541
|
+
ready_reason = ?,
|
|
2564
2542
|
updated_at = ?
|
|
2565
|
-
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);
|
|
2566
2544
|
}
|
|
2567
2545
|
function readFailureCount(db, noteId, column) {
|
|
2568
2546
|
if (column !== "check_failure_count" && column !== "check_network_failure_count")
|
|
@@ -2610,7 +2588,10 @@ function countCompartmentsSince(db, projectPath, since) {
|
|
|
2610
2588
|
return row?.cnt ?? 0;
|
|
2611
2589
|
}
|
|
2612
2590
|
function countProjectSessionsSince(db, projectPath, since) {
|
|
2613
|
-
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);
|
|
2614
2595
|
return row?.cnt ?? 0;
|
|
2615
2596
|
}
|
|
2616
2597
|
function countMappedMemories(db, projectPath) {
|
|
@@ -2719,12 +2700,14 @@ function getDreamTaskBacklog(db, projectPath, task, options = {}) {
|
|
|
2719
2700
|
};
|
|
2720
2701
|
}
|
|
2721
2702
|
case "retrospective": {
|
|
2722
|
-
const
|
|
2703
|
+
const watermark = options.retrospectiveWatermarkMs !== undefined ? options.retrospectiveWatermarkMs : getTaskScheduleState(db, projectPath, task)?.retrospectiveWatermarkMs;
|
|
2704
|
+
const pending = countProjectSessionsSince(db, projectPath, watermark ?? null);
|
|
2723
2705
|
return { pending, total: pending };
|
|
2724
2706
|
}
|
|
2725
2707
|
case "maintain-docs": {
|
|
2726
2708
|
const total = countCompartmentsSince(db, projectPath, 0);
|
|
2727
|
-
const
|
|
2709
|
+
const lastRunAt = options.lastRunAt !== undefined ? options.lastRunAt : getTaskScheduleState(db, projectPath, task)?.lastRunAt;
|
|
2710
|
+
const pending = countCompartmentsSince(db, projectPath, lastRunAt ?? 0);
|
|
2728
2711
|
return { pending, total };
|
|
2729
2712
|
}
|
|
2730
2713
|
case "evaluate-smart-notes": {
|
|
@@ -2779,7 +2762,7 @@ function evaluateTaskGate(task, ctx) {
|
|
|
2779
2762
|
case "review-user-memories":
|
|
2780
2763
|
return getUserMemoryCandidates(db).length >= ctx.promotionThreshold;
|
|
2781
2764
|
case "promote-primers":
|
|
2782
|
-
return countPrimerCandidatesForProject(db, project) >= (ctx.promotionThreshold ??
|
|
2765
|
+
return countPrimerCandidatesForProject(db, project) >= (ctx.promotionThreshold ?? DREAM_TASK_PROMOTION_DEFAULTS["promote-primers"]);
|
|
2783
2766
|
case "refresh-primers":
|
|
2784
2767
|
return getActivePrimers(db, project).some((primer) => !primer.answer.trim() || primer.answerRefreshedAt == null || (primer.lastObservedAt ?? 0) > primer.answerRefreshedAt);
|
|
2785
2768
|
default: {
|
|
@@ -2826,6 +2809,11 @@ var USER_FACING_FAILURES = {
|
|
|
2826
2809
|
sentence: "Memory maintenance stopped at its hidden agent step limit.",
|
|
2827
2810
|
action: "This task needs less work per run; changing the model connection will not help."
|
|
2828
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
|
+
},
|
|
2829
2817
|
dream_empty_completion: {
|
|
2830
2818
|
code: "MC-D03",
|
|
2831
2819
|
sentence: "Memory maintenance received no usable response.",
|
|
@@ -3043,6 +3031,7 @@ var DREAM_FAILURE_KEYS = {
|
|
|
3043
3031
|
provider_timeout: "dream_provider_timeout",
|
|
3044
3032
|
provider_error: "dream_provider_error",
|
|
3045
3033
|
step_limit: "dream_step_limit",
|
|
3034
|
+
token_budget: "dream_token_budget",
|
|
3046
3035
|
empty_completion: "dream_empty_completion",
|
|
3047
3036
|
no_models: "dream_no_models",
|
|
3048
3037
|
child_aborted: "dream_child_aborted",
|
|
@@ -3078,6 +3067,8 @@ import { parse, resolve as resolve3 } from "node:path";
|
|
|
3078
3067
|
|
|
3079
3068
|
// ../plugin/src/features/magic-context/dreamer/open-opencode-db.ts
|
|
3080
3069
|
function openOpenCodeDb() {
|
|
3070
|
+
if (!harnessOwnsOpenCodeStore())
|
|
3071
|
+
return null;
|
|
3081
3072
|
const resolution = resolveOpenCodeDbPath();
|
|
3082
3073
|
const dbPath = resolution.path;
|
|
3083
3074
|
if (!openCodeDbPathExists(resolution)) {
|
|
@@ -3108,7 +3099,7 @@ function openOpenCodeDb() {
|
|
|
3108
3099
|
|
|
3109
3100
|
// ../plugin/src/features/magic-context/dreamer/task-executor.ts
|
|
3110
3101
|
import { createHash as createHash10 } from "node:crypto";
|
|
3111
|
-
import { existsSync as existsSync6 } from "node:fs";
|
|
3102
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
|
|
3112
3103
|
|
|
3113
3104
|
// ../plugin/src/agents/dreamer.ts
|
|
3114
3105
|
var DREAMER_AGENT = "dreamer";
|
|
@@ -3241,7 +3232,7 @@ var READ_TOOLS = ["read", "grep", "glob"];
|
|
|
3241
3232
|
var AGENT_TOOLS = {
|
|
3242
3233
|
[HIDDEN_HISTORIAN_AGENT]: [],
|
|
3243
3234
|
[HIDDEN_DREAMER_AGENT]: [],
|
|
3244
|
-
[HIDDEN_CURATE_AGENT]: ["ctx_memory"
|
|
3235
|
+
[HIDDEN_CURATE_AGENT]: ["ctx_memory"],
|
|
3245
3236
|
[DREAMER_MEMORY_MAPPER_AGENT]: READ_TOOLS,
|
|
3246
3237
|
[DREAMER_PRIMER_INVESTIGATOR_AGENT]: [...READ_TOOLS, "ctx_search"],
|
|
3247
3238
|
[DREAMER_RETROSPECTIVE_AGENT]: ["ctx_search"]
|
|
@@ -3490,7 +3481,7 @@ async function abortChildRun(client, sessionId) {
|
|
|
3490
3481
|
}
|
|
3491
3482
|
}
|
|
3492
3483
|
function isNonRetryable(error, externalSignal) {
|
|
3493
|
-
if (externalSignal?.aborted || error instanceof HiddenAgentStepLimit)
|
|
3484
|
+
if (externalSignal?.aborted || error instanceof HiddenAgentStepLimit || error instanceof DreamTokenBudgetExceeded)
|
|
3494
3485
|
return true;
|
|
3495
3486
|
if (error instanceof Error) {
|
|
3496
3487
|
if (error.name === "AbortError")
|
|
@@ -3516,6 +3507,8 @@ function classifyPromptFailure(error, phase, externalSignal) {
|
|
|
3516
3507
|
const message = extractMessage(error);
|
|
3517
3508
|
if (error instanceof HiddenAgentStepLimit)
|
|
3518
3509
|
return "step_limit";
|
|
3510
|
+
if (error instanceof DreamTokenBudgetExceeded)
|
|
3511
|
+
return "token_budget";
|
|
3519
3512
|
if (externalSignal?.aborted || message === "prompt aborted by external signal") {
|
|
3520
3513
|
return "child_aborted";
|
|
3521
3514
|
}
|
|
@@ -3766,10 +3759,115 @@ function normalizeSDKResponse(response, fallback, options) {
|
|
|
3766
3759
|
}
|
|
3767
3760
|
return fallback;
|
|
3768
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
|
+
|
|
3769
3867
|
// ../plugin/src/shared/prompt-async-transport.ts
|
|
3770
3868
|
var DEFAULT_POLL_INTERVAL_MS = 1000;
|
|
3771
3869
|
var DEFAULT_START_GRACE_MS = 30000;
|
|
3772
|
-
var MESSAGE_WINDOW =
|
|
3870
|
+
var MESSAGE_WINDOW = 100;
|
|
3773
3871
|
function sessionApi(client) {
|
|
3774
3872
|
return client?.session;
|
|
3775
3873
|
}
|
|
@@ -3780,7 +3878,8 @@ function supportsPromptAsync(client) {
|
|
|
3780
3878
|
function createPromptAsyncTransport(client, childSessionId, options = {}) {
|
|
3781
3879
|
if (!supportsPromptAsync(client))
|
|
3782
3880
|
return;
|
|
3783
|
-
|
|
3881
|
+
const budgetGuard = options.tokenBudget ? createDreamTokenBudget(options.tokenBudget) : undefined;
|
|
3882
|
+
return Object.assign((request) => promptAsyncAndWaitForIdle(client, request, { ...options, budgetGuard }), { childSessionId });
|
|
3784
3883
|
}
|
|
3785
3884
|
function infoOf(message) {
|
|
3786
3885
|
const info = message?.info;
|
|
@@ -3796,6 +3895,10 @@ function isSettledAssistant(message) {
|
|
|
3796
3895
|
return false;
|
|
3797
3896
|
return info.time?.completed != null || info.error != null || info.finish != null;
|
|
3798
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
|
+
}
|
|
3799
3902
|
function unwrapData(response) {
|
|
3800
3903
|
if (response && typeof response === "object" && "data" in response) {
|
|
3801
3904
|
return response.data;
|
|
@@ -3873,40 +3976,106 @@ async function promptAsyncAndWaitForIdle(client, request, options = {}) {
|
|
|
3873
3976
|
const signal = request.signal;
|
|
3874
3977
|
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
3875
3978
|
const startGraceMs = options.startGraceMs ?? DEFAULT_START_GRACE_MS;
|
|
3876
|
-
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);
|
|
3877
3986
|
const sent = await session.promptAsync?.(request);
|
|
3878
3987
|
if (sent && typeof sent === "object" && "error" in sent) {
|
|
3879
3988
|
const rejection = sent.error;
|
|
3880
3989
|
if (rejection)
|
|
3881
3990
|
throw new Error(`prompt_async was rejected: ${describeRejection(rejection)}`);
|
|
3882
3991
|
}
|
|
3883
|
-
|
|
3992
|
+
let sentAt = Date.now();
|
|
3884
3993
|
let sawBusy = false;
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
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))
|
|
4063
|
+
return;
|
|
4064
|
+
const fresh = current.some((message) => {
|
|
4065
|
+
const id = messageId(message);
|
|
4066
|
+
return id !== null && !completionBaseline.has(id);
|
|
4067
|
+
});
|
|
4068
|
+
if (sawBusy && fresh)
|
|
3907
4069
|
return;
|
|
3908
|
-
|
|
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
|
+
}
|
|
3909
4075
|
}
|
|
4076
|
+
} finally {
|
|
4077
|
+
if (budget)
|
|
4078
|
+
releaseBudgetFinalizeChild(sessionId);
|
|
3910
4079
|
}
|
|
3911
4080
|
}
|
|
3912
4081
|
// ../plugin/src/shared/summarize-child-stderr.ts
|
|
@@ -4007,109 +4176,13 @@ var HISTORIAN_AGENT = "historian";
|
|
|
4007
4176
|
var HISTORIAN_RECOMP_AGENT = "historian-recomp";
|
|
4008
4177
|
var HISTORIAN_EDITOR_AGENT = "historian-editor";
|
|
4009
4178
|
|
|
4010
|
-
// ../plugin/src/
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
}
|
|
4014
|
-
function tokenObjectFromMessage(message) {
|
|
4015
|
-
const info = message.info;
|
|
4016
|
-
if (info && typeof info === "object") {
|
|
4017
|
-
const tokens = info.tokens;
|
|
4018
|
-
if (tokens && typeof tokens === "object")
|
|
4019
|
-
return tokens;
|
|
4020
|
-
}
|
|
4021
|
-
const tokens = message.tokens;
|
|
4022
|
-
if (tokens && typeof tokens === "object")
|
|
4023
|
-
return tokens;
|
|
4024
|
-
const usage = message.usage;
|
|
4025
|
-
if (usage && typeof usage === "object")
|
|
4026
|
-
return usage;
|
|
4027
|
-
return null;
|
|
4028
|
-
}
|
|
4029
|
-
function isAssistantMessage(message) {
|
|
4030
|
-
if (!message || typeof message !== "object")
|
|
4031
|
-
return false;
|
|
4032
|
-
const record = message;
|
|
4033
|
-
const info = record.info;
|
|
4034
|
-
if (info && typeof info === "object") {
|
|
4035
|
-
return info.role === "assistant";
|
|
4036
|
-
}
|
|
4037
|
-
return record.role === "assistant";
|
|
4179
|
+
// ../plugin/src/config/live-child-output-cap.ts
|
|
4180
|
+
var historianCaps = new BoundedSessionMap(4096);
|
|
4181
|
+
function rememberHistorianOutputCap(sessionId, cap) {
|
|
4182
|
+
historianCaps.set(sessionId, { value: cap });
|
|
4038
4183
|
}
|
|
4039
|
-
function
|
|
4040
|
-
|
|
4041
|
-
const source = info && typeof info === "object" ? info : message;
|
|
4042
|
-
return {
|
|
4043
|
-
providerId: typeof source.providerID === "string" ? source.providerID : typeof source.providerId === "string" ? source.providerId : null,
|
|
4044
|
-
modelId: typeof source.modelID === "string" ? source.modelID : typeof source.modelId === "string" ? source.modelId : null
|
|
4045
|
-
};
|
|
4046
|
-
}
|
|
4047
|
-
function failedInvocationStatus(error) {
|
|
4048
|
-
const message = describeError(error).brief;
|
|
4049
|
-
if (/timed out after \d+ms|prompt timed out/i.test(message))
|
|
4050
|
-
return "timed_out";
|
|
4051
|
-
if (error !== null && typeof error === "object" && error.name === "TimeoutError") {
|
|
4052
|
-
return "timed_out";
|
|
4053
|
-
}
|
|
4054
|
-
if (/length-capped|no (?:assistant )?output|empty (?:assistant )?(?:output|text)/i.test(message)) {
|
|
4055
|
-
return "empty";
|
|
4056
|
-
}
|
|
4057
|
-
return "failed";
|
|
4058
|
-
}
|
|
4059
|
-
function emptyTokenTotals() {
|
|
4060
|
-
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
4061
|
-
}
|
|
4062
|
-
function sumTokensFromChildMessages(messages) {
|
|
4063
|
-
const totals = emptyTokenTotals();
|
|
4064
|
-
for (const message of messages) {
|
|
4065
|
-
if (!isAssistantMessage(message))
|
|
4066
|
-
continue;
|
|
4067
|
-
const tokens = tokenObjectFromMessage(message);
|
|
4068
|
-
if (!tokens)
|
|
4069
|
-
continue;
|
|
4070
|
-
const cache = tokens.cache && typeof tokens.cache === "object" ? tokens.cache : {};
|
|
4071
|
-
totals.input += asNumber(tokens.input);
|
|
4072
|
-
totals.output += asNumber(tokens.output);
|
|
4073
|
-
totals.cacheRead += asNumber(cache.read ?? tokens.cacheRead ?? tokens.cache_read);
|
|
4074
|
-
totals.cacheWrite += asNumber(cache.write ?? tokens.cacheWrite ?? tokens.cache_write);
|
|
4075
|
-
}
|
|
4076
|
-
return totals;
|
|
4077
|
-
}
|
|
4078
|
-
function findLastAssistantModel(messages) {
|
|
4079
|
-
for (let index = messages.length - 1;index >= 0; index -= 1) {
|
|
4080
|
-
const message = messages[index];
|
|
4081
|
-
if (isAssistantMessage(message))
|
|
4082
|
-
return modelFromMessage(message);
|
|
4083
|
-
}
|
|
4084
|
-
return { providerId: null, modelId: null };
|
|
4085
|
-
}
|
|
4086
|
-
function recordChildInvocation(input) {
|
|
4087
|
-
if (!input.db)
|
|
4088
|
-
return null;
|
|
4089
|
-
const tokens = input.tokens ?? sumTokensFromChildMessages(input.messages ?? []);
|
|
4090
|
-
const model = input.providerId !== undefined || input.modelId !== undefined ? { providerId: input.providerId ?? null, modelId: input.modelId ?? null } : findLastAssistantModel(input.messages ?? []);
|
|
4091
|
-
try {
|
|
4092
|
-
return recordSubagentInvocation(input.db, {
|
|
4093
|
-
sessionId: input.parentSessionId,
|
|
4094
|
-
harness: input.harness,
|
|
4095
|
-
subagent: input.subagent,
|
|
4096
|
-
task: input.task ?? null,
|
|
4097
|
-
providerId: model.providerId,
|
|
4098
|
-
modelId: model.modelId,
|
|
4099
|
-
startedAt: input.startedAt,
|
|
4100
|
-
endedAt: input.endedAt ?? Date.now(),
|
|
4101
|
-
status: input.status,
|
|
4102
|
-
inputTokens: tokens.input,
|
|
4103
|
-
outputTokens: tokens.output,
|
|
4104
|
-
cacheReadTokens: tokens.cacheRead,
|
|
4105
|
-
cacheWriteTokens: tokens.cacheWrite,
|
|
4106
|
-
error: input.error ? describeError(input.error).brief : null,
|
|
4107
|
-
parentInvocationId: input.parentInvocationId ?? null
|
|
4108
|
-
});
|
|
4109
|
-
} catch (error) {
|
|
4110
|
-
sessionLog(input.parentSessionId, "subagent token accounting failed:", describeError(error).brief);
|
|
4111
|
-
return null;
|
|
4112
|
-
}
|
|
4184
|
+
function forgetHistorianOutputCap(sessionId) {
|
|
4185
|
+
historianCaps.delete(sessionId);
|
|
4113
4186
|
}
|
|
4114
4187
|
|
|
4115
4188
|
// ../plugin/src/hooks/magic-context/historian-prompt.generated.ts
|
|
@@ -5423,6 +5496,8 @@ function createV1HiddenCompletionExecutor(client, db, directory) {
|
|
|
5423
5496
|
const id = typeof created?.id === "string" ? created.id : "";
|
|
5424
5497
|
if (id && run.kind === "dreamer-task")
|
|
5425
5498
|
asyncChildren.add(id);
|
|
5499
|
+
if (id && run.kind !== "dreamer-task")
|
|
5500
|
+
rememberHistorianOutputCap(id, run.maxOutputTokens);
|
|
5426
5501
|
return { id, childSessionId: id || undefined };
|
|
5427
5502
|
},
|
|
5428
5503
|
async attempt(handle, request) {
|
|
@@ -5459,8 +5534,10 @@ function createV1HiddenCompletionExecutor(client, db, directory) {
|
|
|
5459
5534
|
};
|
|
5460
5535
|
},
|
|
5461
5536
|
async close(handle, settlement) {
|
|
5462
|
-
if (handle?.id)
|
|
5537
|
+
if (handle?.id) {
|
|
5463
5538
|
asyncChildren.delete(handle.id);
|
|
5539
|
+
forgetHistorianOutputCap(handle.id);
|
|
5540
|
+
}
|
|
5464
5541
|
if (!client)
|
|
5465
5542
|
return;
|
|
5466
5543
|
await teardownChildSession({
|
|
@@ -6836,7 +6913,7 @@ Project memory uses exactly 5 categories. Every memory belongs to one:
|
|
|
6836
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.
|
|
6837
6914
|
|
|
6838
6915
|
## Memory operations
|
|
6839
|
-
-
|
|
6916
|
+
- The scoped category snapshot arrives in the first message. Do not list or get it again; track mutation deltas.
|
|
6840
6917
|
- \`action="merge", ids=[N,M,...], content="...", category="..."\` — consolidate duplicates into one canonical memory
|
|
6841
6918
|
- \`action="update", ids=[N], content="...", superseded_by=M\` — rewrite content; name where removed detail survives when cutting more than half
|
|
6842
6919
|
- \`action="write", category="...", content="..."\` — create a memory (SPLITS ONLY — never mint new facts)
|
|
@@ -6866,7 +6943,7 @@ var PRIMER_INVESTIGATOR_SYSTEM_PROMPT = `You are a read-only code investigator f
|
|
|
6866
6943
|
function renderMemoryList(memories) {
|
|
6867
6944
|
return memories.map((memory) => {
|
|
6868
6945
|
const files = memory.mappedFiles.length ? memory.mappedFiles.join(", ") : "(none mapped yet)";
|
|
6869
|
-
return `[${memory.id}] ${memory.category}
|
|
6946
|
+
return `[${memory.id}] ${memory.category} importance=${memory.importance} retrieval_count=${memory.retrievalCount} seen_count=${memory.seenCount}
|
|
6870
6947
|
Content: ${memory.content}
|
|
6871
6948
|
Mapped files: ${files}${memory.hasNoFileSentinel ? " (file-independent)" : ""}`;
|
|
6872
6949
|
}).join(`
|
|
@@ -6894,9 +6971,41 @@ Rewrite narrative/historical → operational present tense ("X uses Y because Z"
|
|
|
6894
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.
|
|
6895
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.
|
|
6896
6973
|
|
|
6897
|
-
###
|
|
6974
|
+
### Cross-chunk duplicate candidates (normalized content matches)
|
|
6975
|
+
${args.crossChunkCandidates?.join(`
|
|
6976
|
+
`) || "(none)"}
|
|
6977
|
+
|
|
6978
|
+
### Category snapshot (do not re-enumerate)
|
|
6898
6979
|
${renderMemoryList(args.memories)}`;
|
|
6899
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
|
+
}
|
|
6900
7009
|
var RETROSPECTIVE_SYSTEM_PROMPT = `You are a retrospective learning agent for Magic Context.
|
|
6901
7010
|
|
|
6902
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.
|
|
@@ -6955,17 +7064,23 @@ Return only XML in this exact shape:
|
|
|
6955
7064
|
<learning route="observation">one recurring user preference</learning>
|
|
6956
7065
|
</learnings>`;
|
|
6957
7066
|
}
|
|
6958
|
-
function buildMaintainDocsPrompt(projectPath, changeSet, existingDocs, budget = 12000, currentTokens = 0) {
|
|
7067
|
+
function buildMaintainDocsPrompt(projectPath, changeSet, existingDocs, budget = 12000, currentTokens = 0, currentDocs) {
|
|
6959
7068
|
return `## Task: Propose documentation corrections
|
|
6960
7069
|
|
|
6961
7070
|
Project: ${projectPath}
|
|
6962
7071
|
Existing docs: ARCHITECTURE.md ${existingDocs.architecture ? "exists" : "missing"}; STRUCTURE.md ${existingDocs.structure ? "exists" : "missing"}.
|
|
6963
7072
|
Current combined token count: ${currentTokens}. Combined budget: ${budget} tokens.
|
|
6964
7073
|
|
|
6965
|
-
Host-collected code changes (
|
|
7074
|
+
Host-collected code changes (diff stat and hunks, or file/line ranges when oversized):
|
|
6966
7075
|
${changeSet}
|
|
6967
7076
|
|
|
6968
|
-
|
|
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.
|
|
6969
7084
|
|
|
6970
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.`;
|
|
6971
7086
|
}
|
|
@@ -6975,10 +7090,11 @@ function buildDreamTaskPrompt(task, args) {
|
|
|
6975
7090
|
return buildCuratePrompt({
|
|
6976
7091
|
projectPath: args.projectPath,
|
|
6977
7092
|
category: args.curate?.category ?? "PROJECT_RULES",
|
|
6978
|
-
memories: args.curate?.memories ?? []
|
|
7093
|
+
memories: args.curate?.memories ?? [],
|
|
7094
|
+
crossChunkCandidates: args.curate?.crossChunkCandidates
|
|
6979
7095
|
});
|
|
6980
7096
|
case "maintain-docs":
|
|
6981
|
-
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);
|
|
6982
7098
|
}
|
|
6983
7099
|
}
|
|
6984
7100
|
|
|
@@ -7911,6 +8027,7 @@ function hasCurrentDocsProposal(projectDir) {
|
|
|
7911
8027
|
}
|
|
7912
8028
|
function git(projectDir, args) {
|
|
7913
8029
|
return execFileSync("git", args, {
|
|
8030
|
+
windowsHide: true,
|
|
7914
8031
|
cwd: projectDir,
|
|
7915
8032
|
encoding: "utf8",
|
|
7916
8033
|
timeout: 1e4,
|
|
@@ -7922,11 +8039,80 @@ function docsChangeSet(projectDir, storedAnchor) {
|
|
|
7922
8039
|
if (realpathSync2(git(projectDir, ["rev-parse", "--show-toplevel"])) !== realpathSync2(projectDir))
|
|
7923
8040
|
return null;
|
|
7924
8041
|
const head = git(projectDir, ["rev-parse", "HEAD"]);
|
|
8042
|
+
const recorded = Boolean(storedAnchor);
|
|
7925
8043
|
let anchor = storedAnchor;
|
|
7926
|
-
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)
|
|
7927
8047
|
anchor = git(projectDir, ["log", "-1", "--format=%H", "--", ...FILES]);
|
|
7928
8048
|
if (!anchor)
|
|
7929
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
|
+
}
|
|
7930
8116
|
const excluded = [
|
|
7931
8117
|
"ARCHITECTURE.md",
|
|
7932
8118
|
"STRUCTURE.md",
|
|
@@ -7956,11 +8142,15 @@ function docsChangeSet(projectDir, storedAnchor) {
|
|
|
7956
8142
|
const text = raw.slice(0, 24000);
|
|
7957
8143
|
return {
|
|
7958
8144
|
head,
|
|
8145
|
+
unchanged: false,
|
|
8146
|
+
relevant: true,
|
|
7959
8147
|
text: `${text}${raw.length > text.length ? `
|
|
7960
8148
|
[cut ${raw.length - text.length} bytes]` : ""}${commits.length > 201 ? `
|
|
7961
8149
|
[cut commits beyond newest 200]` : ""}`
|
|
7962
8150
|
};
|
|
7963
|
-
} catch {
|
|
8151
|
+
} catch (error) {
|
|
8152
|
+
if (storedAnchor)
|
|
8153
|
+
throw error;
|
|
7964
8154
|
return null;
|
|
7965
8155
|
}
|
|
7966
8156
|
}
|
|
@@ -7994,7 +8184,7 @@ function diff(name, before, after) {
|
|
|
7994
8184
|
writeFileSync3(join3(dir, "before"), before);
|
|
7995
8185
|
writeFileSync3(join3(dir, "after"), after);
|
|
7996
8186
|
try {
|
|
7997
|
-
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" });
|
|
7998
8188
|
} catch (error) {
|
|
7999
8189
|
return error.stdout?.replaceAll(join3(dir, "before"), `a/${name}`).replaceAll(join3(dir, "after"), `b/${name}`) ?? "";
|
|
8000
8190
|
}
|
|
@@ -8245,7 +8435,7 @@ function requestValidatedAddress(validation, candidate, options) {
|
|
|
8245
8435
|
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
8246
8436
|
bytes += buf.byteLength;
|
|
8247
8437
|
if (bytes > options.bodyLimitBytes) {
|
|
8248
|
-
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 }));
|
|
8249
8439
|
response.destroy();
|
|
8250
8440
|
request2.destroy();
|
|
8251
8441
|
return;
|
|
@@ -8582,6 +8772,7 @@ async function runGit(projectRoot, args, signal) {
|
|
|
8582
8772
|
throwIfAborted2(signal);
|
|
8583
8773
|
try {
|
|
8584
8774
|
const result = await execFileAsync("git", ["-C", projectRoot, ...args], {
|
|
8775
|
+
windowsHide: true,
|
|
8585
8776
|
timeout: DEFAULT_GIT_TIMEOUT_MS,
|
|
8586
8777
|
maxBuffer: 128 * 1024,
|
|
8587
8778
|
signal
|
|
@@ -8744,6 +8935,7 @@ async function runCompiledSmartNoteCheckLocked(options, quickjs) {
|
|
|
8744
8935
|
const controller = new AbortController;
|
|
8745
8936
|
let externallyCancelled = false;
|
|
8746
8937
|
let executionTimedOut = false;
|
|
8938
|
+
let persistentNetworkFailure = false;
|
|
8747
8939
|
const externalAbort = () => {
|
|
8748
8940
|
externallyCancelled = true;
|
|
8749
8941
|
controller.abort(options.signal?.reason);
|
|
@@ -8762,7 +8954,19 @@ async function runCompiledSmartNoteCheckLocked(options, quickjs) {
|
|
|
8762
8954
|
context.runtime.setMemoryLimit(options.heapLimitBytes ?? DEFAULT_HEAP_LIMIT_BYTES);
|
|
8763
8955
|
context.runtime.setMaxStackSize(options.stackLimitBytes ?? DEFAULT_STACK_LIMIT_BYTES);
|
|
8764
8956
|
context.runtime.setInterruptHandler(() => controller.signal.aborted || Date.now() > deadline);
|
|
8765
|
-
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
|
+
});
|
|
8766
8970
|
disableAmbientDynamicCode(context);
|
|
8767
8971
|
const result = await evalCheck(context, options.compiledCheck);
|
|
8768
8972
|
const checkResult = result;
|
|
@@ -8776,14 +8980,14 @@ async function runCompiledSmartNoteCheckLocked(options, quickjs) {
|
|
|
8776
8980
|
} catch (error) {
|
|
8777
8981
|
if (externallyCancelled && !executionTimedOut)
|
|
8778
8982
|
return cancelledResult(error);
|
|
8779
|
-
return failureResult(formatSandboxError(error), isSmartNoteNetworkError(error));
|
|
8983
|
+
return failureResult(formatSandboxError(error), isSmartNoteNetworkError(error), persistentNetworkFailure);
|
|
8780
8984
|
} finally {
|
|
8781
8985
|
clearTimeout(timer);
|
|
8782
8986
|
options.signal?.removeEventListener("abort", externalAbort);
|
|
8783
8987
|
}
|
|
8784
8988
|
}
|
|
8785
|
-
function failureResult(error, network) {
|
|
8786
|
-
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 };
|
|
8787
8991
|
}
|
|
8788
8992
|
function cancelledResult(reason) {
|
|
8789
8993
|
return {
|
|
@@ -8881,7 +9085,12 @@ var MAX_CRON_CHARS = 256;
|
|
|
8881
9085
|
var MAX_COMPILER_ERROR_CHARS = 2 * 1024;
|
|
8882
9086
|
async function compileSmartNoteCheck(args) {
|
|
8883
9087
|
if (!args.note.surfaceCondition) {
|
|
8884
|
-
return {
|
|
9088
|
+
return {
|
|
9089
|
+
ok: false,
|
|
9090
|
+
cancelled: false,
|
|
9091
|
+
error: "note has no surface condition",
|
|
9092
|
+
persistent: false
|
|
9093
|
+
};
|
|
8885
9094
|
}
|
|
8886
9095
|
const prompt = `Compile this smart note condition into a sandbox check.
|
|
8887
9096
|
|
|
@@ -8988,15 +9197,13 @@ Remember: output only the JSON object described by the system prompt.`;
|
|
|
8988
9197
|
const compiledCheck = normalizeCompiledCheck(response.compiled_check);
|
|
8989
9198
|
const manifest = normalizeManifest(response.manifest);
|
|
8990
9199
|
const checkCron = normalizeCron(response.check_cron);
|
|
8991
|
-
|
|
9200
|
+
const dryRun = await dryRunSmartNoteCheck(compiledCheck, args.capabilityFactory, args.signal);
|
|
9201
|
+
for (const warning of [
|
|
9202
|
+
...manifestAdvisoryWarnings(compiledCheck, manifest),
|
|
9203
|
+
...dryRun.advisories
|
|
9204
|
+
]) {
|
|
8992
9205
|
log(`[dreamer] smart note #${args.note.id}: manifest advisory — ${warning}`);
|
|
8993
9206
|
}
|
|
8994
|
-
const dryRun = await runCompiledSmartNoteCheck({
|
|
8995
|
-
compiledCheck,
|
|
8996
|
-
capabilityFactory: args.capabilityFactory,
|
|
8997
|
-
signal: args.signal,
|
|
8998
|
-
timeoutMs: 2000
|
|
8999
|
-
});
|
|
9000
9207
|
if (!dryRun.ok) {
|
|
9001
9208
|
const error = boundedError(`dry-run failed: ${dryRun.error}`);
|
|
9002
9209
|
recordInvocation({
|
|
@@ -9004,7 +9211,12 @@ Remember: output only the JSON object described by the system prompt.`;
|
|
|
9004
9211
|
messages: outputMessages,
|
|
9005
9212
|
error
|
|
9006
9213
|
});
|
|
9007
|
-
return {
|
|
9214
|
+
return {
|
|
9215
|
+
ok: false,
|
|
9216
|
+
cancelled: dryRun.cancelled,
|
|
9217
|
+
error,
|
|
9218
|
+
persistent: !dryRun.cancelled && dryRun.persistent
|
|
9219
|
+
};
|
|
9008
9220
|
}
|
|
9009
9221
|
recordInvocation({ status: "completed", messages: outputMessages });
|
|
9010
9222
|
return {
|
|
@@ -9019,7 +9231,7 @@ Remember: output only the JSON object described by the system prompt.`;
|
|
|
9019
9231
|
const cancelled = args.signal.aborted;
|
|
9020
9232
|
const message = boundedError(error instanceof Error ? error.message : String(error));
|
|
9021
9233
|
recordInvocation({ status: cancelled ? "aborted" : "failed", error: message });
|
|
9022
|
-
return { ok: false, cancelled, error: message };
|
|
9234
|
+
return { ok: false, cancelled, error: message, persistent: false };
|
|
9023
9235
|
} finally {
|
|
9024
9236
|
if (args.client) {
|
|
9025
9237
|
await teardownChildSession({
|
|
@@ -9034,6 +9246,29 @@ Remember: output only the JSON object described by the system prompt.`;
|
|
|
9034
9246
|
}
|
|
9035
9247
|
}
|
|
9036
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
|
+
}
|
|
9037
9272
|
function parseCompilerOutput(output) {
|
|
9038
9273
|
if (!output)
|
|
9039
9274
|
throw new Error("smart-note compiler returned no output");
|
|
@@ -9488,7 +9723,7 @@ async function compileNote(args, note, projectRoot, assertLeaseHeld, leaseHeld,
|
|
|
9488
9723
|
expected: sourceRevisionExpectation(note),
|
|
9489
9724
|
leaseHeld,
|
|
9490
9725
|
write: () => {
|
|
9491
|
-
markSmartNoteCompilationFailure(args.db, note.id, now, MAX_COMPILATION_FAILURES);
|
|
9726
|
+
markSmartNoteCompilationFailure(args.db, note.id, now, MAX_COMPILATION_FAILURES, result.error, result.persistent);
|
|
9492
9727
|
}
|
|
9493
9728
|
});
|
|
9494
9729
|
return false;
|
|
@@ -9859,7 +10094,7 @@ var MAP_MEMORIES_SYSTEM_PROMPT = `You are a memory mapper for the magic-context
|
|
|
9859
10094
|
|
|
9860
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.
|
|
9861
10096
|
|
|
9862
|
-
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.
|
|
9863
10098
|
|
|
9864
10099
|
For each memory decide ONE of:
|
|
9865
10100
|
- Backing files found → the COMPLETE set of repo-relative paths whose code the memory is about.
|
|
@@ -10116,6 +10351,7 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
|
|
|
10116
10351
|
prompt,
|
|
10117
10352
|
title: "magic-context-dream-map-memories",
|
|
10118
10353
|
callContext: "dreamer:map-memories",
|
|
10354
|
+
metadata: { tokenBudget: args.tokenBudget, onBudgetUpdate: args.onBudgetUpdate },
|
|
10119
10355
|
model: args.model,
|
|
10120
10356
|
fallbackModels: args.fallbackModels,
|
|
10121
10357
|
timeoutMs: sliceMs,
|
|
@@ -10156,7 +10392,10 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
|
|
|
10156
10392
|
parts: [{ type: "text", text: prompt, synthetic: true }]
|
|
10157
10393
|
}
|
|
10158
10394
|
}, {
|
|
10159
|
-
transport: createPromptAsyncTransport(client, agentSessionId
|
|
10395
|
+
transport: createPromptAsyncTransport(client, agentSessionId, {
|
|
10396
|
+
tokenBudget: args.tokenBudget,
|
|
10397
|
+
onBudgetUpdate: args.onBudgetUpdate
|
|
10398
|
+
}),
|
|
10160
10399
|
timeoutMs: sliceMs,
|
|
10161
10400
|
signal,
|
|
10162
10401
|
fallbackModels: args.fallbackModels,
|
|
@@ -10196,7 +10435,7 @@ async function mapOneBatch(args, batch, sliceMs, signal) {
|
|
|
10196
10435
|
error,
|
|
10197
10436
|
messages: await readFailedChildMessages(args.client, agentSessionId, args.sessionDirectory)
|
|
10198
10437
|
});
|
|
10199
|
-
if (error instanceof DreamerModuleFailureError)
|
|
10438
|
+
if (error instanceof DreamerModuleFailureError || error instanceof DreamTokenBudgetExceeded)
|
|
10200
10439
|
throw error;
|
|
10201
10440
|
if (signal.aborted)
|
|
10202
10441
|
throw error;
|
|
@@ -10603,12 +10842,12 @@ async function promotePrimers(args) {
|
|
|
10603
10842
|
|
|
10604
10843
|
// ../plugin/src/features/magic-context/dreamer/primer-seed.ts
|
|
10605
10844
|
var PRIMER_SEED_CAP_TOKENS = 4000;
|
|
10606
|
-
function renderUserAndToolOrientation(
|
|
10845
|
+
function renderUserAndToolOrientation(sessionId, startOrdinal, endOrdinal, capTokens) {
|
|
10607
10846
|
const lines = [];
|
|
10608
10847
|
let tokens = 0;
|
|
10609
|
-
|
|
10610
|
-
|
|
10611
|
-
|
|
10848
|
+
let sawMessage = false;
|
|
10849
|
+
visitRawSessionMessages2(sessionId, startOrdinal, endOrdinal, (msg) => {
|
|
10850
|
+
sawMessage = true;
|
|
10612
10851
|
const out = [];
|
|
10613
10852
|
if (msg.role === "user" && hasMeaningfulUserText2(msg.parts)) {
|
|
10614
10853
|
const text = extractTexts2(msg.parts).map((t) => cleanUserText2(t)).map(normalizeText).filter((t) => t.length > 0).join(" / ");
|
|
@@ -10621,22 +10860,27 @@ function renderUserAndToolOrientation(messages, startOrdinal, endOrdinal, capTok
|
|
|
10621
10860
|
const lineTokens = estimateTokens(line);
|
|
10622
10861
|
if (tokens + lineTokens > capTokens && lines.length > 0) {
|
|
10623
10862
|
lines.push("… (orientation truncated; investigate the current source directly)");
|
|
10624
|
-
return
|
|
10625
|
-
`);
|
|
10863
|
+
return false;
|
|
10626
10864
|
}
|
|
10627
10865
|
lines.push(line);
|
|
10628
10866
|
tokens += lineTokens;
|
|
10629
10867
|
}
|
|
10630
|
-
|
|
10631
|
-
|
|
10632
|
-
`
|
|
10868
|
+
return true;
|
|
10869
|
+
}, { summary: true });
|
|
10870
|
+
return sawMessage ? lines.join(`
|
|
10871
|
+
`) : null;
|
|
10872
|
+
}
|
|
10873
|
+
function selectPrimerOriginCandidate(db, primer) {
|
|
10874
|
+
return getPrimerCandidatesByIds(db, primer.sourceCandidateIds).filter((candidate) => candidate.projectPath === primer.projectPath).sort((a, b) => b.sourceMessageTime - a.sourceMessageTime || b.id - a.id)[0];
|
|
10633
10875
|
}
|
|
10634
10876
|
function loadPrePostP1(db, sessionId, originStartMessage) {
|
|
10635
10877
|
const origin = db.prepare("SELECT sequence FROM compartments WHERE session_id = ? AND start_message = ? ORDER BY sequence ASC LIMIT 1").get(sessionId, originStartMessage);
|
|
10636
10878
|
if (typeof origin?.sequence !== "number")
|
|
10637
10879
|
return "";
|
|
10638
10880
|
const originSeq = origin.sequence;
|
|
10639
|
-
const rows = db.prepare(`SELECT sequence, start_message, end_message,
|
|
10881
|
+
const rows = db.prepare(`SELECT sequence, start_message, end_message,
|
|
10882
|
+
substr(title, 1, 512) AS title,
|
|
10883
|
+
substr(p1, 1, 1200) AS p1, substr(content, 1, 1200) AS content
|
|
10640
10884
|
FROM compartments
|
|
10641
10885
|
WHERE session_id = ? AND sequence IN (?, ?)
|
|
10642
10886
|
ORDER BY sequence ASC`).all(sessionId, originSeq - 1, originSeq + 1);
|
|
@@ -10650,28 +10894,28 @@ function loadPrePostP1(db, sessionId, originStartMessage) {
|
|
|
10650
10894
|
`);
|
|
10651
10895
|
}
|
|
10652
10896
|
function closedBookOriginP1(db, sessionId, originStartMessage) {
|
|
10653
|
-
const row = db.prepare(
|
|
10897
|
+
const row = db.prepare(`SELECT substr(title, 1, 512) AS title,
|
|
10898
|
+
substr(p1, 1, 2000) AS p1, substr(content, 1, 2000) AS content
|
|
10899
|
+
FROM compartments WHERE session_id = ? AND start_message = ? ORDER BY sequence ASC LIMIT 1`).get(sessionId, originStartMessage);
|
|
10654
10900
|
const body = (row?.p1 ?? row?.content ?? "").slice(0, 2000);
|
|
10655
10901
|
const orientation = row?.title ? `${row.title}: ${body}` : body;
|
|
10656
10902
|
return { orientation, sessionId };
|
|
10657
10903
|
}
|
|
10658
10904
|
function buildPrimerSeed(db, primer) {
|
|
10659
|
-
const
|
|
10660
|
-
const mostRecent = candidates.slice().sort((a, b) => b.sourceMessageTime - a.sourceMessageTime || b.id - a.id)[0];
|
|
10905
|
+
const mostRecent = selectPrimerOriginCandidate(db, primer);
|
|
10661
10906
|
if (!mostRecent || typeof mostRecent.sourceCompartmentStart !== "number" || typeof mostRecent.sourceCompartmentEnd !== "number") {
|
|
10662
10907
|
return { kind: "closed-book", orientation: "", prePost: "", sessionId: null };
|
|
10663
10908
|
}
|
|
10664
10909
|
const sessionId = mostRecent.sessionId;
|
|
10665
10910
|
const start = mostRecent.sourceCompartmentStart;
|
|
10666
10911
|
const end = mostRecent.sourceCompartmentEnd;
|
|
10667
|
-
let
|
|
10912
|
+
let orientation = null;
|
|
10668
10913
|
try {
|
|
10669
|
-
|
|
10914
|
+
orientation = renderUserAndToolOrientation(sessionId, start, end, PRIMER_SEED_CAP_TOKENS);
|
|
10670
10915
|
} catch {
|
|
10671
|
-
|
|
10916
|
+
orientation = null;
|
|
10672
10917
|
}
|
|
10673
|
-
|
|
10674
|
-
if (!inRange) {
|
|
10918
|
+
if (orientation === null) {
|
|
10675
10919
|
const closed = closedBookOriginP1(db, sessionId, start);
|
|
10676
10920
|
return {
|
|
10677
10921
|
kind: "closed-book",
|
|
@@ -10680,7 +10924,6 @@ function buildPrimerSeed(db, primer) {
|
|
|
10680
10924
|
sessionId
|
|
10681
10925
|
};
|
|
10682
10926
|
}
|
|
10683
|
-
const orientation = renderUserAndToolOrientation(raw, start, end, PRIMER_SEED_CAP_TOKENS);
|
|
10684
10927
|
return {
|
|
10685
10928
|
kind: "raw",
|
|
10686
10929
|
orientation,
|
|
@@ -10822,6 +11065,7 @@ async function refreshOnePrimer(args, primer, sliceMs, signal) {
|
|
|
10822
11065
|
prompt,
|
|
10823
11066
|
title: "magic-context-dream-refresh-primers",
|
|
10824
11067
|
callContext: "dreamer:refresh-primers",
|
|
11068
|
+
metadata: { tokenBudget: args.tokenBudget, onBudgetUpdate: args.onBudgetUpdate },
|
|
10825
11069
|
model: args.model,
|
|
10826
11070
|
fallbackModels: args.fallbackModels,
|
|
10827
11071
|
language: args.language,
|
|
@@ -10871,6 +11115,10 @@ async function refreshOnePrimer(args, primer, sliceMs, signal) {
|
|
|
10871
11115
|
signal,
|
|
10872
11116
|
fallbackModels: args.fallbackModels,
|
|
10873
11117
|
callContext: "dreamer:refresh-primers",
|
|
11118
|
+
transport: createPromptAsyncTransport(client, agentSessionId, {
|
|
11119
|
+
tokenBudget: args.tokenBudget,
|
|
11120
|
+
onBudgetUpdate: args.onBudgetUpdate
|
|
11121
|
+
}),
|
|
10874
11122
|
fetchOutput: async () => {
|
|
10875
11123
|
const messagesResponse = await client.session.messages({
|
|
10876
11124
|
path: { id: agentSessionId },
|
|
@@ -10914,9 +11162,7 @@ async function refreshOnePrimer(args, primer, sliceMs, signal) {
|
|
|
10914
11162
|
}
|
|
10915
11163
|
}
|
|
10916
11164
|
function originSessionIdForPrimer(args, primer) {
|
|
10917
|
-
|
|
10918
|
-
const mostRecent = candidates.slice().sort((a, b) => b.sourceMessageTime - a.sourceMessageTime || b.id - a.id)[0];
|
|
10919
|
-
return mostRecent?.sessionId ?? null;
|
|
11165
|
+
return selectPrimerOriginCandidate(args.db, primer)?.sessionId ?? null;
|
|
10920
11166
|
}
|
|
10921
11167
|
function recordInvocation3(args, startedAt, params) {
|
|
10922
11168
|
if (!args.parentSessionId)
|
|
@@ -11085,12 +11331,14 @@ class OpenCodeRetrospectiveRawProvider {
|
|
|
11085
11331
|
this.openDb = deps.openOpenCodeDb ?? openOpenCodeDb;
|
|
11086
11332
|
}
|
|
11087
11333
|
listProjectSessions(projectIdentity) {
|
|
11088
|
-
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
|
|
11089
11335
|
FROM session_projects sp
|
|
11336
|
+
JOIN schema_migrations_meta activity
|
|
11337
|
+
ON activity.key = 'retrospective_activity:' || sp.session_id
|
|
11090
11338
|
LEFT JOIN session_meta m ON m.session_id = sp.session_id
|
|
11091
11339
|
WHERE sp.project_path = ? AND sp.harness = 'opencode'
|
|
11092
11340
|
AND COALESCE(m.is_subagent, 0) = 0
|
|
11093
|
-
ORDER BY
|
|
11341
|
+
ORDER BY updated_at ASC, sp.session_id ASC`).all(projectIdentity);
|
|
11094
11342
|
return rows.map((row) => ({
|
|
11095
11343
|
sessionId: row.session_id,
|
|
11096
11344
|
updatedAt: typeof row.updated_at === "number" ? row.updated_at : undefined
|
|
@@ -11109,15 +11357,13 @@ class OpenCodeRetrospectiveRawProvider {
|
|
|
11109
11357
|
const db = this.resolveDb();
|
|
11110
11358
|
if (!db)
|
|
11111
11359
|
return { messages: [], truncated: false };
|
|
11112
|
-
|
|
11113
|
-
return readOpenCodeMessagesSince(db, sessionId, sinceMs, capPerSession);
|
|
11114
|
-
} catch {
|
|
11115
|
-
return { messages: [], truncated: false };
|
|
11116
|
-
}
|
|
11360
|
+
return readOpenCodeMessagesSince(db, sessionId, sinceMs, capPerSession);
|
|
11117
11361
|
}
|
|
11118
11362
|
readOldestMessageTimesSince(sessionIds, sinceMs) {
|
|
11119
11363
|
const db = this.resolveDb();
|
|
11120
|
-
if (!db
|
|
11364
|
+
if (!db)
|
|
11365
|
+
return null;
|
|
11366
|
+
if (sessionIds.length === 0)
|
|
11121
11367
|
return new Map;
|
|
11122
11368
|
return readOpenCodeOldestMessageTimesSince(db, sessionIds, sinceMs);
|
|
11123
11369
|
}
|
|
@@ -11214,6 +11460,11 @@ async function readRetrospectiveScanWindow(provider, projectIdentity, watermarkM
|
|
|
11214
11460
|
}
|
|
11215
11461
|
const droppedSince = [...budgetDropped, ...countDropped];
|
|
11216
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
|
+
}
|
|
11217
11468
|
for (const row of keptSince) {
|
|
11218
11469
|
if (row.ts > maxScannedTs)
|
|
11219
11470
|
maxScannedTs = row.ts;
|
|
@@ -11314,11 +11565,48 @@ function normalizeOpenCodeRows(db, sessionId, rows) {
|
|
|
11314
11565
|
return [];
|
|
11315
11566
|
const messageIds = rows.map((row) => row.id);
|
|
11316
11567
|
const placeholders = messageIds.map(() => "?").join(", ");
|
|
11317
|
-
const
|
|
11568
|
+
const userIds = rows.filter((row) => parseJsonRecord(row.data)?.role === "user").map((row) => row.id);
|
|
11569
|
+
const userPlaceholders = userIds.map(() => "?").join(", ") || "NULL";
|
|
11570
|
+
const output = `CASE json_type(data, '$.state.output')
|
|
11571
|
+
WHEN 'text' THEN json_extract(data, '$.state.output')
|
|
11572
|
+
WHEN 'null' THEN '' ELSE COALESCE(data -> '$.state.output', '') END`;
|
|
11573
|
+
const error = `CASE json_type(data, '$.state.error')
|
|
11574
|
+
WHEN 'text' THEN json_extract(data, '$.state.error')
|
|
11575
|
+
WHEN 'null' THEN '' ELSE COALESCE(data -> '$.state.error', '') END`;
|
|
11576
|
+
const containsError = (value) => ["error", "failed", "exception", "traceback"].map((word) => `(' ' || lower(${value}) || ' ') GLOB '*[^a-z0-9_]${word}[^a-z0-9_]*'`).join(" OR ");
|
|
11577
|
+
const errorWords = `CASE WHEN json_type(data, '$.state.output') IN ('object', 'array')
|
|
11578
|
+
THEN EXISTS (SELECT 1 FROM json_tree(data, '$.state.output') AS leaf
|
|
11579
|
+
WHERE (leaf.type = 'text' AND (${containsError("json_quote(leaf.atom)")}))
|
|
11580
|
+
OR (typeof(leaf.key) = 'text' AND (${containsError("json_quote(leaf.key)")})))
|
|
11581
|
+
WHEN instr(CAST(${output} AS BLOB), x'00') > 0 THEN EXISTS (
|
|
11582
|
+
WITH RECURSIVE segments(rest, piece) AS (
|
|
11583
|
+
SELECT CAST(${output} AS BLOB), x''
|
|
11584
|
+
UNION ALL
|
|
11585
|
+
SELECT CASE WHEN instr(rest, x'00') > 0
|
|
11586
|
+
THEN substr(rest, instr(rest, x'00') + 1) ELSE NULL END,
|
|
11587
|
+
CASE WHEN instr(rest, x'00') > 0
|
|
11588
|
+
THEN substr(rest, 1, instr(rest, x'00') - 1) ELSE rest END
|
|
11589
|
+
FROM segments WHERE rest IS NOT NULL
|
|
11590
|
+
) SELECT 1 FROM segments WHERE (${containsError("CAST(piece AS TEXT)")}))
|
|
11591
|
+
ELSE (${containsError(output)}) END`;
|
|
11592
|
+
const whitespace = `
|
|
11593
|
+
\v\f\r \u2028\u2029 \uFEFF`;
|
|
11594
|
+
const partRows = db.prepare(`SELECT message_id, CASE json_extract(data, '$.type')
|
|
11595
|
+
WHEN 'text' THEN CASE WHEN message_id IN (${userPlaceholders}) THEN json_object(
|
|
11596
|
+
'type', 'text', 'text', data -> '$.text',
|
|
11597
|
+
'synthetic', data -> '$.synthetic', 'ignored', data -> '$.ignored') ELSE '{}' END
|
|
11598
|
+
WHEN 'tool' THEN json_object(
|
|
11599
|
+
'type', 'tool', 'tool', data -> '$.tool',
|
|
11600
|
+
'state', json_object('isError', json(CASE WHEN
|
|
11601
|
+
json_type(data, '$.state.isError') = 'true'
|
|
11602
|
+
OR lower(json_extract(data, '$.state.status')) = 'error'
|
|
11603
|
+
OR length(CAST(trim(${error}, ?) AS BLOB)) > 0 OR ${errorWords}
|
|
11604
|
+
THEN 'true' ELSE 'false' END)))
|
|
11605
|
+
ELSE '{}' END AS data
|
|
11318
11606
|
FROM part
|
|
11319
|
-
WHERE +session_id = ?
|
|
11607
|
+
WHERE +session_id = ? AND json_valid(data)
|
|
11320
11608
|
AND likelihood(message_id IN (${placeholders}), 0.000001)
|
|
11321
|
-
ORDER BY time_created ASC, id ASC`).all(sessionId, ...messageIds);
|
|
11609
|
+
ORDER BY time_created ASC, id ASC`).all(...userIds, whitespace, sessionId, ...messageIds);
|
|
11322
11610
|
const partsByMessageId = new Map;
|
|
11323
11611
|
for (const row of partRows) {
|
|
11324
11612
|
const parts = partsByMessageId.get(row.message_id) ?? [];
|
|
@@ -11468,6 +11756,127 @@ function insertDreamRun(db, run) {
|
|
|
11468
11756
|
// ../plugin/src/features/magic-context/dreamer/verify.ts
|
|
11469
11757
|
import { createHash as createHash9 } from "node:crypto";
|
|
11470
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
|
+
|
|
11471
11880
|
// ../plugin/src/features/magic-context/dreamer/verify-gate.ts
|
|
11472
11881
|
import path5 from "node:path";
|
|
11473
11882
|
function minOf(values) {
|
|
@@ -11502,12 +11911,24 @@ async function partitionVerifyScope(args) {
|
|
|
11502
11911
|
const active = getMemoriesByProject(args.db, args.projectIdentity);
|
|
11503
11912
|
const verById = getMemoryVerifications(args.db, active.map((m) => m.id));
|
|
11504
11913
|
const candidates = active.filter((m) => (verById.get(m.id)?.files.length ?? 0) > 0);
|
|
11505
|
-
const toPrompt = (m) =>
|
|
11506
|
-
|
|
11507
|
-
|
|
11508
|
-
|
|
11509
|
-
|
|
11510
|
-
|
|
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
|
+
};
|
|
11511
11932
|
if (args.forceBroad) {
|
|
11512
11933
|
const broadCycleStartAt = ensureBroadCycleStart({
|
|
11513
11934
|
db: args.db,
|
|
@@ -11587,9 +12008,9 @@ async function partitionVerifyScope(args) {
|
|
|
11587
12008
|
// ../plugin/src/features/magic-context/dreamer/verify-prompt.ts
|
|
11588
12009
|
var VERIFY_SYSTEM_PROMPT = `You are a memory verifier for the magic-context system. You verify project memories against the CURRENT code.
|
|
11589
12010
|
|
|
11590
|
-
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.
|
|
11591
12012
|
|
|
11592
|
-
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.
|
|
11593
12014
|
|
|
11594
12015
|
Decide ONE of four outcomes per memory:
|
|
11595
12016
|
- VERIFIED — still accurate. Keep it as-is.
|
|
@@ -11614,7 +12035,7 @@ Rules:
|
|
|
11614
12035
|
- Every input memory id MUST appear exactly once, in exactly one of verified/update/archive/skip.
|
|
11615
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.
|
|
11616
12037
|
- Default to VERIFIED. skip when code cannot decide the claim. update and archive are the exceptions, not the norm.`;
|
|
11617
|
-
function buildVerifyPrompt(projectPath, memories) {
|
|
12038
|
+
function buildVerifyPrompt(projectPath, memories, evidence) {
|
|
11618
12039
|
const list = memories.map((m) => `[${m.id}] ${m.category}
|
|
11619
12040
|
Content: ${m.content}
|
|
11620
12041
|
Backing files: ${m.mappedFiles.join(", ")}`).join(`
|
|
@@ -11624,11 +12045,18 @@ Backing files: ${m.mappedFiles.join(", ")}`).join(`
|
|
|
11624
12045
|
|
|
11625
12046
|
Project: ${projectPath}
|
|
11626
12047
|
|
|
11627
|
-
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.
|
|
11628
12049
|
|
|
11629
12050
|
<memories>
|
|
11630
12051
|
${list}
|
|
11631
|
-
</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>` : ""}`;
|
|
11632
12060
|
}
|
|
11633
12061
|
function attrOf(s, name) {
|
|
11634
12062
|
const m = s.match(new RegExp(`\\b${name}\\s*=\\s*"([^"]*)"`));
|
|
@@ -11752,8 +12180,11 @@ async function runVerify(args) {
|
|
|
11752
12180
|
return result;
|
|
11753
12181
|
}
|
|
11754
12182
|
const batches = [];
|
|
11755
|
-
|
|
11756
|
-
|
|
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
|
+
}
|
|
11757
12188
|
}
|
|
11758
12189
|
const abortController = new AbortController;
|
|
11759
12190
|
const heartbeat = startLeaseHeartbeat(args.db, args.holderId, args.leaseKey, (reason) => abortController.abort(new Error(reason)), args.leaseAcquisition);
|
|
@@ -11817,7 +12248,9 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
|
|
|
11817
12248
|
let promptSettled = false;
|
|
11818
12249
|
const startedAt = Date.now();
|
|
11819
12250
|
try {
|
|
11820
|
-
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);
|
|
11821
12254
|
if (args.hiddenCompletionExecutor) {
|
|
11822
12255
|
const run = await runHiddenSingleShotPrompt({
|
|
11823
12256
|
executor: args.hiddenCompletionExecutor,
|
|
@@ -11828,6 +12261,7 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
|
|
|
11828
12261
|
prompt,
|
|
11829
12262
|
title: "magic-context-dream-verify",
|
|
11830
12263
|
callContext: "dreamer:verify",
|
|
12264
|
+
metadata: { tokenBudget: args.tokenBudget, onBudgetUpdate: args.onBudgetUpdate },
|
|
11831
12265
|
model: args.model,
|
|
11832
12266
|
fallbackModels: args.fallbackModels,
|
|
11833
12267
|
language: args.language,
|
|
@@ -11844,7 +12278,7 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
|
|
|
11844
12278
|
status: "completed",
|
|
11845
12279
|
messages: run.completion.messages ?? []
|
|
11846
12280
|
});
|
|
11847
|
-
return applyParsedVerifyManifest(args, batch, run.validated);
|
|
12281
|
+
return applyParsedVerifyManifest(args, batch, run.validated, evidence?.head ?? headAtPrompt);
|
|
11848
12282
|
}
|
|
11849
12283
|
const client = args.client;
|
|
11850
12284
|
if (!client)
|
|
@@ -11872,7 +12306,10 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
|
|
|
11872
12306
|
parts: [{ type: "text", text: prompt, synthetic: true }]
|
|
11873
12307
|
}
|
|
11874
12308
|
}, {
|
|
11875
|
-
transport: createPromptAsyncTransport(client, agentSessionId
|
|
12309
|
+
transport: createPromptAsyncTransport(client, agentSessionId, {
|
|
12310
|
+
tokenBudget: args.tokenBudget,
|
|
12311
|
+
onBudgetUpdate: args.onBudgetUpdate
|
|
12312
|
+
}),
|
|
11876
12313
|
timeoutMs: sliceMs,
|
|
11877
12314
|
signal,
|
|
11878
12315
|
fallbackModels: args.fallbackModels,
|
|
@@ -11901,7 +12338,7 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
|
|
|
11901
12338
|
});
|
|
11902
12339
|
promptSettled = true;
|
|
11903
12340
|
recordInvocation4(args, startedAt, { status: "completed", messages: run.output });
|
|
11904
|
-
return await applyParsedVerifyManifest(args, batch, run.validated);
|
|
12341
|
+
return await applyParsedVerifyManifest(args, batch, run.validated, evidence?.head ?? headAtPrompt);
|
|
11905
12342
|
} catch (error) {
|
|
11906
12343
|
const desc = describeError(error);
|
|
11907
12344
|
const providerFailure = error instanceof DreamerProviderOutputFailureError ? error : undefined;
|
|
@@ -11912,7 +12349,7 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
|
|
|
11912
12349
|
error,
|
|
11913
12350
|
messages: await readFailedChildMessages(args.client, agentSessionId, args.sessionDirectory)
|
|
11914
12351
|
});
|
|
11915
|
-
if (error instanceof DreamerModuleFailureError || signal.aborted)
|
|
12352
|
+
if (error instanceof DreamerModuleFailureError || error instanceof DreamTokenBudgetExceeded || signal.aborted)
|
|
11916
12353
|
throw error;
|
|
11917
12354
|
if (promptFailure?.failureClass === "provider_timeout" || isPromptTimeoutError(error)) {
|
|
11918
12355
|
return {
|
|
@@ -11947,7 +12384,7 @@ async function verifyOneBatch(args, batch, sliceMs, signal) {
|
|
|
11947
12384
|
});
|
|
11948
12385
|
}
|
|
11949
12386
|
}
|
|
11950
|
-
async function applyParsedVerifyManifest(args, batch, parsed) {
|
|
12387
|
+
async function applyParsedVerifyManifest(args, batch, parsed, headAtPrompt) {
|
|
11951
12388
|
const batchIds = new Set(batch.map((m) => m.id));
|
|
11952
12389
|
const batchById = new Map(batch.map((memory) => [memory.id, memory]));
|
|
11953
12390
|
const valid = {
|
|
@@ -11974,6 +12411,7 @@ async function applyParsedVerifyManifest(args, batch, parsed) {
|
|
|
11974
12411
|
return { verified: 0, updated: 0, archived: 0, skipped: 0, refused: 0 };
|
|
11975
12412
|
}
|
|
11976
12413
|
const now = Date.now();
|
|
12414
|
+
const verifiedHead = headAtPrompt === undefined ? await readGitHead(args.sessionDirectory) : headAtPrompt;
|
|
11977
12415
|
const writes = [];
|
|
11978
12416
|
let refused = 0;
|
|
11979
12417
|
const skipped = valid.skipped.length;
|
|
@@ -12085,6 +12523,8 @@ async function applyParsedVerifyManifest(args, batch, parsed) {
|
|
|
12085
12523
|
continue;
|
|
12086
12524
|
if (w.kind === "verify") {
|
|
12087
12525
|
recordMemoryVerifications(args.db, w.id, w.files, now);
|
|
12526
|
+
if (verifiedHead)
|
|
12527
|
+
recordVerifiedCommit(args.db, memory, now, verifiedHead);
|
|
12088
12528
|
verified += 1;
|
|
12089
12529
|
} else if (w.kind === "update") {
|
|
12090
12530
|
rewriteMemoryContent(args.db, memory, w.content, w.hash);
|
|
@@ -12109,6 +12549,15 @@ async function applyParsedVerifyManifest(args, batch, parsed) {
|
|
|
12109
12549
|
});
|
|
12110
12550
|
return { verified, updated, archived, skipped, refused };
|
|
12111
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
|
+
}
|
|
12112
12561
|
async function normalizeFiles(args, rawFiles) {
|
|
12113
12562
|
if (rawFiles.length === 0)
|
|
12114
12563
|
return [];
|
|
@@ -12166,6 +12615,16 @@ function dreamRunFailureDetail(error) {
|
|
|
12166
12615
|
}
|
|
12167
12616
|
const described = describeError(error);
|
|
12168
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
|
+
}
|
|
12169
12628
|
if (error instanceof Error && error.name === "HiddenAgentStepLimit") {
|
|
12170
12629
|
return {
|
|
12171
12630
|
failure_class: "step_limit",
|
|
@@ -12210,7 +12669,10 @@ function toCuratePromptMemory(memory, verificationById) {
|
|
|
12210
12669
|
category: memory.category,
|
|
12211
12670
|
content: memory.content,
|
|
12212
12671
|
mappedFiles: verification?.files ?? [],
|
|
12213
|
-
hasNoFileSentinel: verification?.hasSentinel ?? false
|
|
12672
|
+
hasNoFileSentinel: verification?.hasSentinel ?? false,
|
|
12673
|
+
importance: memory.importance,
|
|
12674
|
+
retrievalCount: memory.retrievalCount,
|
|
12675
|
+
seenCount: memory.seenCount
|
|
12214
12676
|
};
|
|
12215
12677
|
}
|
|
12216
12678
|
function loadActiveMemoryPromptMemories(db, projectIdentity) {
|
|
@@ -12250,7 +12712,10 @@ function inspectCurateMemoryOperations(messages) {
|
|
|
12250
12712
|
if (!isRecord(part.state) || part.state.status !== "completed")
|
|
12251
12713
|
continue;
|
|
12252
12714
|
const input = isRecord(part.state.input) ? part.state.input : null;
|
|
12253
|
-
|
|
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);
|
|
12254
12719
|
}
|
|
12255
12720
|
}
|
|
12256
12721
|
return summary;
|
|
@@ -12298,6 +12763,7 @@ function createDreamTaskExecutor(deps) {
|
|
|
12298
12763
|
};
|
|
12299
12764
|
return async (config, ctx) => {
|
|
12300
12765
|
const { db, projectIdentity, holderId, leaseKey } = ctx;
|
|
12766
|
+
deps.client?.resetDreamTokenBudget?.(config.task);
|
|
12301
12767
|
const startedAt = Date.now();
|
|
12302
12768
|
const leaseAcquisition = ctx.leaseAcquisition ?? acquireLeaseWithAcquisition(db, holderId, leaseKey) ?? (() => {
|
|
12303
12769
|
throw new Error("Dream lease unavailable during executor setup");
|
|
@@ -12356,8 +12822,23 @@ function createDreamTaskExecutor(deps) {
|
|
|
12356
12822
|
if (!leaseOwnershipMatches(db, holderId, leaseAcquisition.generation, leaseKey)) {
|
|
12357
12823
|
throw new Error("Dream lease lost during executor setup");
|
|
12358
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
|
+
};
|
|
12359
12837
|
const recordRun = (status, error, extra) => {
|
|
12360
12838
|
try {
|
|
12839
|
+
const hostBudget = deps.readTokenBudget?.(config.task) ?? deps.client?.readTokenBudget?.(config.task);
|
|
12840
|
+
if (hostBudget)
|
|
12841
|
+
onBudgetUpdate(hostBudget);
|
|
12361
12842
|
insertDreamRun(db, {
|
|
12362
12843
|
projectPath: projectIdentity,
|
|
12363
12844
|
startedAt,
|
|
@@ -12373,6 +12854,14 @@ function createDreamTaskExecutor(deps) {
|
|
|
12373
12854
|
failure: extra?.failure ?? dreamRunFailureDetail(error ?? "unknown dreamer failure")
|
|
12374
12855
|
} : {},
|
|
12375
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
|
+
} : {},
|
|
12376
12865
|
backlog: (() => {
|
|
12377
12866
|
const end = extra?.backlogAfter ?? getDreamTaskBacklog(db, projectIdentity, config.task);
|
|
12378
12867
|
const processed = processedDreamTaskItems(backlogAtStart.pending, end.pending);
|
|
@@ -12463,7 +12952,7 @@ function createDreamTaskExecutor(deps) {
|
|
|
12463
12952
|
leaseKey,
|
|
12464
12953
|
deadline,
|
|
12465
12954
|
leaseAcquisition,
|
|
12466
|
-
promotionThreshold: config.promotionThreshold ??
|
|
12955
|
+
promotionThreshold: config.promotionThreshold ?? DREAM_TASK_PROMOTION_DEFAULTS["review-user-memories"],
|
|
12467
12956
|
model: config.model,
|
|
12468
12957
|
fallbackModels: config.fallbackModels,
|
|
12469
12958
|
language: config.language ?? deps.language
|
|
@@ -12487,6 +12976,8 @@ function createDreamTaskExecutor(deps) {
|
|
|
12487
12976
|
model: config.model,
|
|
12488
12977
|
fallbackModels: config.fallbackModels,
|
|
12489
12978
|
moduleRoute,
|
|
12979
|
+
tokenBudget: config.tokenBudget,
|
|
12980
|
+
onBudgetUpdate,
|
|
12490
12981
|
onProgress: (processed) => reportProgress(processed)
|
|
12491
12982
|
});
|
|
12492
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}` : ""}`);
|
|
@@ -12527,6 +13018,8 @@ function createDreamTaskExecutor(deps) {
|
|
|
12527
13018
|
fallbackModels: config.fallbackModels,
|
|
12528
13019
|
language: config.language ?? deps.language,
|
|
12529
13020
|
moduleRoute,
|
|
13021
|
+
tokenBudget: config.tokenBudget,
|
|
13022
|
+
onBudgetUpdate,
|
|
12530
13023
|
onProgress: (processed, refused) => reportProgress(processed, refused)
|
|
12531
13024
|
});
|
|
12532
13025
|
const processed = result.verified + result.updated + result.archived + result.skipped + result.refused;
|
|
@@ -12538,7 +13031,8 @@ function createDreamTaskExecutor(deps) {
|
|
|
12538
13031
|
recordRun("completed", null, {
|
|
12539
13032
|
progress: broadProgress,
|
|
12540
13033
|
memoryChanges: computeMemoryDelta(memoryBefore),
|
|
12541
|
-
backlogAfter
|
|
13034
|
+
backlogAfter,
|
|
13035
|
+
banked: result.verified + result.updated + result.archived
|
|
12542
13036
|
});
|
|
12543
13037
|
return { status: "completed" };
|
|
12544
13038
|
}
|
|
@@ -12547,14 +13041,16 @@ function createDreamTaskExecutor(deps) {
|
|
|
12547
13041
|
recordRun("failed", error, {
|
|
12548
13042
|
progress: verificationProgress,
|
|
12549
13043
|
memoryChanges: computeMemoryDelta(memoryBefore),
|
|
12550
|
-
backlogAfter
|
|
13044
|
+
backlogAfter,
|
|
13045
|
+
banked: result.verified + result.updated + result.archived
|
|
12551
13046
|
});
|
|
12552
13047
|
return { status: "failed", transient: !batchTimedOut, error };
|
|
12553
13048
|
}
|
|
12554
13049
|
recordRun("completed", null, {
|
|
12555
13050
|
progress: verificationProgress,
|
|
12556
13051
|
memoryChanges: computeMemoryDelta(memoryBefore),
|
|
12557
|
-
backlogAfter
|
|
13052
|
+
backlogAfter,
|
|
13053
|
+
banked: result.verified + result.updated + result.archived
|
|
12558
13054
|
});
|
|
12559
13055
|
return { status: "completed" };
|
|
12560
13056
|
}
|
|
@@ -12606,7 +13102,7 @@ function createDreamTaskExecutor(deps) {
|
|
|
12606
13102
|
leaseKey,
|
|
12607
13103
|
deadline,
|
|
12608
13104
|
leaseAcquisition,
|
|
12609
|
-
promotionThreshold: config.promotionThreshold ??
|
|
13105
|
+
promotionThreshold: config.promotionThreshold ?? DREAM_TASK_PROMOTION_DEFAULTS["promote-primers"],
|
|
12610
13106
|
ensureProjectRegistered: deps.ensureProjectRegistered
|
|
12611
13107
|
});
|
|
12612
13108
|
recordRun("completed", null);
|
|
@@ -12629,6 +13125,8 @@ function createDreamTaskExecutor(deps) {
|
|
|
12629
13125
|
fallbackModels: config.fallbackModels,
|
|
12630
13126
|
language: config.language ?? deps.language,
|
|
12631
13127
|
rawProviderFactory: deps.primerRawProviderFactory,
|
|
13128
|
+
tokenBudget: config.tokenBudget,
|
|
13129
|
+
onBudgetUpdate,
|
|
12632
13130
|
onProgress: (processed) => reportProgress(processed)
|
|
12633
13131
|
});
|
|
12634
13132
|
recordRun("completed", null);
|
|
@@ -12659,6 +13157,7 @@ function createDreamTaskExecutor(deps) {
|
|
|
12659
13157
|
if (config.task === "retrospective") {
|
|
12660
13158
|
const memoryBefore = getMemoryCountsByStatus(db, projectIdentity);
|
|
12661
13159
|
const retro = await runRetrospectiveTask(config, ctx, {
|
|
13160
|
+
onBudgetUpdate,
|
|
12662
13161
|
deps,
|
|
12663
13162
|
deadline,
|
|
12664
13163
|
parent,
|
|
@@ -12666,7 +13165,10 @@ function createDreamTaskExecutor(deps) {
|
|
|
12666
13165
|
moduleRoute
|
|
12667
13166
|
});
|
|
12668
13167
|
recordRun("completed", null, {
|
|
12669
|
-
memoryChanges: computeMemoryDelta(memoryBefore)
|
|
13168
|
+
memoryChanges: computeMemoryDelta(memoryBefore),
|
|
13169
|
+
backlogAfter: retro.retrospectiveWatermarkMs == null ? undefined : getDreamTaskBacklog(db, projectIdentity, "retrospective", {
|
|
13170
|
+
retrospectiveWatermarkMs: retro.retrospectiveWatermarkMs
|
|
13171
|
+
})
|
|
12670
13172
|
});
|
|
12671
13173
|
return {
|
|
12672
13174
|
status: "completed",
|
|
@@ -12679,6 +13181,7 @@ function createDreamTaskExecutor(deps) {
|
|
|
12679
13181
|
};
|
|
12680
13182
|
}
|
|
12681
13183
|
return await runAgenticTask(config, ctx, {
|
|
13184
|
+
onBudgetUpdate,
|
|
12682
13185
|
deps,
|
|
12683
13186
|
deadline,
|
|
12684
13187
|
parent,
|
|
@@ -12689,6 +13192,9 @@ function createDreamTaskExecutor(deps) {
|
|
|
12689
13192
|
moduleRoute
|
|
12690
13193
|
});
|
|
12691
13194
|
} catch (error) {
|
|
13195
|
+
if (error instanceof DreamTokenBudgetExceeded) {
|
|
13196
|
+
budgetSpent = Math.max(budgetSpent, error.spent);
|
|
13197
|
+
}
|
|
12692
13198
|
const classified = classifyFailure(error);
|
|
12693
13199
|
const retrospectiveOverflow = error instanceof RetrospectivePromptOverflowError ? error : null;
|
|
12694
13200
|
const transient = retrospectiveOverflow ? true : classified.transient;
|
|
@@ -12847,7 +13353,7 @@ function resolveRetrospectiveUsableInputTokens(config, deps) {
|
|
|
12847
13353
|
}
|
|
12848
13354
|
async function runRetrospectiveTask(config, ctx, helpers) {
|
|
12849
13355
|
const { db, projectIdentity, holderId, leaseKey } = ctx;
|
|
12850
|
-
const { deps, deadline, parent } = helpers;
|
|
13356
|
+
const { deps, deadline, parent, onBudgetUpdate } = helpers;
|
|
12851
13357
|
const provider = resolveRetrospectiveProvider(deps, db, projectIdentity);
|
|
12852
13358
|
if (!provider) {
|
|
12853
13359
|
log("[dreamer] retrospective: no raw provider available — clean no-op");
|
|
@@ -12901,7 +13407,8 @@ async function runRetrospectiveTask(config, ctx, helpers) {
|
|
|
12901
13407
|
],
|
|
12902
13408
|
timeoutMs: Math.max(1, deadline - Date.now()),
|
|
12903
13409
|
title: "magic-context-dream-retrospective",
|
|
12904
|
-
directory: deps.sessionDirectory
|
|
13410
|
+
directory: deps.sessionDirectory,
|
|
13411
|
+
metadata: { tokenBudget: config.tokenBudget, onBudgetUpdate }
|
|
12905
13412
|
});
|
|
12906
13413
|
childSessionId = hiddenHandle.id;
|
|
12907
13414
|
} else {
|
|
@@ -12937,6 +13444,12 @@ async function runRetrospectiveTask(config, ctx, helpers) {
|
|
|
12937
13444
|
signal: abortController.signal,
|
|
12938
13445
|
fallbackModels: config.fallbackModels,
|
|
12939
13446
|
callContext: "dreamer:retrospective",
|
|
13447
|
+
...!hiddenHandle && deps.client ? {
|
|
13448
|
+
transport: createPromptAsyncTransport(deps.client, sessionId, {
|
|
13449
|
+
tokenBudget: config.tokenBudget,
|
|
13450
|
+
onBudgetUpdate
|
|
13451
|
+
})
|
|
13452
|
+
} : {},
|
|
12940
13453
|
...hiddenHandle && deps.hiddenCompletionExecutor ? {
|
|
12941
13454
|
transport: Object.assign((request) => deps.hiddenCompletionExecutor?.attempt(hiddenHandle, request) ?? Promise.reject(new Error("Hidden transport unavailable")), { childSessionId: hiddenHandle.childSessionId })
|
|
12942
13455
|
} : {},
|
|
@@ -13112,7 +13625,7 @@ async function runRetrospectiveTask(config, ctx, helpers) {
|
|
|
13112
13625
|
}
|
|
13113
13626
|
async function runAgenticTask(config, ctx, helpers) {
|
|
13114
13627
|
const { db, projectIdentity, holderId, leaseKey } = ctx;
|
|
13115
|
-
const { deps, deadline, parent } = helpers;
|
|
13628
|
+
const { deps, deadline, parent, onBudgetUpdate } = helpers;
|
|
13116
13629
|
const task = config.task;
|
|
13117
13630
|
const docsDir = deps.sessionDirectory;
|
|
13118
13631
|
const invocationStartedAt = Date.now();
|
|
@@ -13166,122 +13679,178 @@ async function runAgenticTask(config, ctx, helpers) {
|
|
|
13166
13679
|
return { status: "completed", detail: progress };
|
|
13167
13680
|
}
|
|
13168
13681
|
}
|
|
13169
|
-
if (task === "maintain-docs" &&
|
|
13170
|
-
const reason =
|
|
13171
|
-
log(`[dreamer] maintain-docs skipped: ${reason}`);
|
|
13682
|
+
if (task === "maintain-docs" && !changes) {
|
|
13683
|
+
const reason = "no relevant commits or no git repository";
|
|
13172
13684
|
helpers.recordRun("completed", null, { progress: reason });
|
|
13173
13685
|
return { status: "completed", detail: reason };
|
|
13174
13686
|
}
|
|
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
|
-
|
|
13205
|
-
|
|
13206
|
-
|
|
13207
|
-
|
|
13208
|
-
|
|
13209
|
-
|
|
13210
|
-
|
|
13211
|
-
|
|
13212
|
-
|
|
13213
|
-
|
|
13214
|
-
|
|
13215
|
-
|
|
13216
|
-
|
|
13217
|
-
|
|
13218
|
-
const createResponse = await createChildSessionWithFence({
|
|
13219
|
-
client: requireDreamClient(deps.client),
|
|
13220
|
-
db,
|
|
13221
|
-
parentSessionId: parent ?? undefined,
|
|
13222
|
-
title: `magic-context-dream-${task}`,
|
|
13223
|
-
directory: docsDir
|
|
13224
|
-
});
|
|
13225
|
-
const created = normalizeSDKResponse(createResponse, null, {
|
|
13226
|
-
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
|
|
13227
13730
|
});
|
|
13228
|
-
|
|
13229
|
-
|
|
13230
|
-
|
|
13231
|
-
|
|
13232
|
-
|
|
13233
|
-
run = await promptSyncWithValidatedOutputRetry(requireDreamClient(deps.client), {
|
|
13234
|
-
path: { id: sessionId },
|
|
13235
|
-
query: { directory: docsDir },
|
|
13236
|
-
body: {
|
|
13731
|
+
if (deps.hiddenCompletionExecutor) {
|
|
13732
|
+
const hidden = await runHiddenSingleShotPrompt({
|
|
13733
|
+
executor: deps.hiddenCompletionExecutor,
|
|
13734
|
+
parentSessionId: parent ?? undefined,
|
|
13735
|
+
sessionDirectory: docsDir,
|
|
13237
13736
|
agent: task === "maintain-docs" ? DREAMER_DOCS_AGENT : DREAMER_AGENT,
|
|
13238
|
-
system: task === "maintain-docs" ? MAINTAIN_DOCS_SYSTEM_PROMPT :
|
|
13239
|
-
|
|
13240
|
-
|
|
13241
|
-
|
|
13242
|
-
|
|
13243
|
-
|
|
13244
|
-
|
|
13245
|
-
|
|
13246
|
-
|
|
13247
|
-
|
|
13248
|
-
|
|
13249
|
-
|
|
13250
|
-
|
|
13251
|
-
|
|
13252
|
-
|
|
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 };
|
|
13253
13819
|
}
|
|
13254
|
-
});
|
|
13255
|
-
return normalizeSDKResponse(messagesResponse, [], {
|
|
13256
|
-
preferResponseOnMissingData: true
|
|
13257
|
-
});
|
|
13258
|
-
},
|
|
13259
|
-
validateOutput: (messages) => {
|
|
13260
|
-
const text = extractLatestAssistantText(messages);
|
|
13261
|
-
if (task !== "curate") {
|
|
13262
13820
|
if (!text)
|
|
13263
13821
|
throw new Error("Dreamer returned no assistant output.");
|
|
13264
|
-
|
|
13265
|
-
|
|
13266
|
-
|
|
13267
|
-
if (text)
|
|
13268
|
-
validateCurateAssistantText(text);
|
|
13269
|
-
if (memoryOperations.completedActions.length > 0) {
|
|
13822
|
+
if (memoryOperations.totalCalls > 0) {
|
|
13823
|
+
throw new Error("Curate returned no completed ctx_memory tool result.");
|
|
13824
|
+
}
|
|
13270
13825
|
return { text, memoryOperations };
|
|
13271
13826
|
}
|
|
13272
|
-
|
|
13273
|
-
|
|
13274
|
-
|
|
13275
|
-
|
|
13276
|
-
|
|
13277
|
-
|
|
13278
|
-
|
|
13279
|
-
|
|
13280
|
-
|
|
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
|
+
}
|
|
13281
13846
|
}
|
|
13847
|
+
if (task === "curate")
|
|
13848
|
+
run.validated = {
|
|
13849
|
+
text: null,
|
|
13850
|
+
memoryOperations: { totalCalls: completedActions.length, completedActions }
|
|
13851
|
+
};
|
|
13282
13852
|
if (leaseLost)
|
|
13283
13853
|
throw new Error("Dream lease lost during task");
|
|
13284
|
-
const curateRefused = task === "curate" ? takeCurateSafetyRefusalCount(childSessionId ?? "") : 0;
|
|
13285
13854
|
if (curateRefused > 0) {
|
|
13286
13855
|
helpers.reportProgress(curateRefused, curateRefused);
|
|
13287
13856
|
log(`[dreamer] curate safety summary: refused=${curateRefused}`);
|
|
@@ -13302,7 +13871,11 @@ async function runAgenticTask(config, ctx, helpers) {
|
|
|
13302
13871
|
try {
|
|
13303
13872
|
if (run.validated === "[]") {
|
|
13304
13873
|
helpers.recordRun("completed", null, { progress: "no corrections proposed" });
|
|
13305
|
-
return {
|
|
13874
|
+
return {
|
|
13875
|
+
status: "completed",
|
|
13876
|
+
detail: "no corrections proposed",
|
|
13877
|
+
schedulePatch: { taskStateJson: JSON.stringify({ head: changes.head }) }
|
|
13878
|
+
};
|
|
13306
13879
|
}
|
|
13307
13880
|
const path = writeDocsProposal(docsDir, String(run.validated), config.docsMaxTokens ?? 12000, docsHashes, changes.head);
|
|
13308
13881
|
helpers.recordRun("completed", null, { progress: `proposal: ${path}` });
|
|
@@ -13520,7 +14093,7 @@ async function runDomainGroup(deps, group, cb) {
|
|
|
13520
14093
|
projectIdentity,
|
|
13521
14094
|
lastRunAt: readLastRunAt(db, projectIdentity, due.config.task),
|
|
13522
14095
|
retrospectiveWatermarkMs: readRetrospectiveWatermark(db, projectIdentity, due.config.task),
|
|
13523
|
-
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"])
|
|
13524
14097
|
});
|
|
13525
14098
|
if (!gatePass) {
|
|
13526
14099
|
advanceAfterRun(db, projectIdentity, due, Date.now(), "skipped", null);
|
|
@@ -13568,6 +14141,8 @@ async function runManualDream(deps) {
|
|
|
13568
14141
|
backlogBefore: {},
|
|
13569
14142
|
backlogAfter: {}
|
|
13570
14143
|
};
|
|
14144
|
+
if (!isUsableProjectIdentity(deps.projectIdentity))
|
|
14145
|
+
return result;
|
|
13571
14146
|
let selected;
|
|
13572
14147
|
let forceGate = false;
|
|
13573
14148
|
if (deps.task) {
|
|
@@ -13598,7 +14173,7 @@ async function runManualDream(deps) {
|
|
|
13598
14173
|
projectIdentity: deps.projectIdentity,
|
|
13599
14174
|
lastRunAt: readLastRunAt(deps.db, deps.projectIdentity, d.config.task),
|
|
13600
14175
|
retrospectiveWatermarkMs: readRetrospectiveWatermark(deps.db, deps.projectIdentity, d.config.task),
|
|
13601
|
-
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"])
|
|
13602
14177
|
});
|
|
13603
14178
|
if (pass)
|
|
13604
14179
|
gated.push(d);
|
|
@@ -13641,6 +14216,8 @@ async function runManualDream(deps) {
|
|
|
13641
14216
|
return result;
|
|
13642
14217
|
}
|
|
13643
14218
|
async function runDueTasksForProject(deps) {
|
|
14219
|
+
if (!isUsableProjectIdentity(deps.projectIdentity))
|
|
14220
|
+
return 0;
|
|
13644
14221
|
const now = deps.now ?? Date.now();
|
|
13645
14222
|
const due = planDueTasks(deps.db, deps.projectIdentity, deps.tasks, now);
|
|
13646
14223
|
if (due.length === 0)
|
|
@@ -13652,7 +14229,7 @@ async function runDueTasksForProject(deps) {
|
|
|
13652
14229
|
projectIdentity: deps.projectIdentity,
|
|
13653
14230
|
lastRunAt: readLastRunAt(deps.db, deps.projectIdentity, d.config.task),
|
|
13654
14231
|
retrospectiveWatermarkMs: readRetrospectiveWatermark(deps.db, deps.projectIdentity, d.config.task),
|
|
13655
|
-
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"])
|
|
13656
14233
|
});
|
|
13657
14234
|
if (pass) {
|
|
13658
14235
|
gated.push(d);
|
|
@@ -13886,6 +14463,13 @@ function openTimerDatabaseOrNull(context) {
|
|
|
13886
14463
|
}
|
|
13887
14464
|
return db;
|
|
13888
14465
|
}
|
|
14466
|
+
var refusedEmptyIdentityDirectories = new Set;
|
|
14467
|
+
function logEmptyIdentityRefusalOnce(directory) {
|
|
14468
|
+
if (refusedEmptyIdentityDirectories.has(directory))
|
|
14469
|
+
return;
|
|
14470
|
+
refusedEmptyIdentityDirectories.add(directory);
|
|
14471
|
+
log(`[dreamer] not registering ${directory}: it has no project identity, so no project-scoped work runs for it`);
|
|
14472
|
+
}
|
|
13889
14473
|
var registeredProjects = new Map;
|
|
13890
14474
|
function stopDreamScheduleTimerIfIdle() {
|
|
13891
14475
|
if (registeredProjects.size !== 0)
|
|
@@ -13905,6 +14489,10 @@ function stopDreamScheduleTimerIfIdle() {
|
|
|
13905
14489
|
log("[dreamer] stopped dream schedule timer (no projects left)");
|
|
13906
14490
|
}
|
|
13907
14491
|
async function startDreamScheduleTimer(args) {
|
|
14492
|
+
if (!isUsableProjectIdentity(args.projectIdentity)) {
|
|
14493
|
+
logEmptyIdentityRefusalOnce(args.directory);
|
|
14494
|
+
return;
|
|
14495
|
+
}
|
|
13908
14496
|
beginBootQuietPeriod();
|
|
13909
14497
|
const db = openTimerDatabaseOrNull("schedule timer registration");
|
|
13910
14498
|
if (!db)
|
|
@@ -14268,7 +14856,7 @@ import * as childProcess from "node:child_process";
|
|
|
14268
14856
|
import {
|
|
14269
14857
|
existsSync as existsSync7,
|
|
14270
14858
|
mkdtempSync as mkdtempSync2,
|
|
14271
|
-
readFileSync as
|
|
14859
|
+
readFileSync as readFileSync6,
|
|
14272
14860
|
realpathSync as realpathSync3,
|
|
14273
14861
|
rmSync as rmSync2,
|
|
14274
14862
|
statSync as statSync4,
|
|
@@ -14297,7 +14885,7 @@ var DEFAULT_PI_RESOLUTION_FS = {
|
|
|
14297
14885
|
existsSync: existsSync7,
|
|
14298
14886
|
statSync: statSync4,
|
|
14299
14887
|
realpathSync: realpathSync3,
|
|
14300
|
-
readFileSync:
|
|
14888
|
+
readFileSync: readFileSync6
|
|
14301
14889
|
};
|
|
14302
14890
|
function packageHarnessKind(packageName) {
|
|
14303
14891
|
if (packageName === "@oh-my-pi/pi-coding-agent")
|
|
@@ -14594,12 +15182,6 @@ var DREAMER_ACTION_AGENTS = new Set([
|
|
|
14594
15182
|
"dreamer",
|
|
14595
15183
|
"magic-context-dreamer"
|
|
14596
15184
|
]);
|
|
14597
|
-
var HISTORIAN_AGENTS = new Set([
|
|
14598
|
-
"magic-context-historian",
|
|
14599
|
-
"historian",
|
|
14600
|
-
"historian-recomp",
|
|
14601
|
-
"historian-editor"
|
|
14602
|
-
]);
|
|
14603
15185
|
var SEARCH_ONLY_SUBAGENT_TOOL_AGENTS = new Set([
|
|
14604
15186
|
"dreamer-retrospective",
|
|
14605
15187
|
"dreamer-primer-investigator"
|
|
@@ -14789,7 +15371,15 @@ class PiSubagentRunner {
|
|
|
14789
15371
|
async runOnce(options, runMode, modelRefOverride) {
|
|
14790
15372
|
const startTime = Date.now();
|
|
14791
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;
|
|
14792
15378
|
const recordAccounting = (result, messages = []) => {
|
|
15379
|
+
if (!loggedTelemetry) {
|
|
15380
|
+
loggedTelemetry = true;
|
|
15381
|
+
telemetry.finish(options.agent, result.ok ? "completed" : result.reason, cap);
|
|
15382
|
+
}
|
|
14793
15383
|
if (!options.accountingSessionId || recordedAccounting)
|
|
14794
15384
|
return;
|
|
14795
15385
|
recordedAccounting = true;
|
|
@@ -14845,7 +15435,9 @@ class PiSubagentRunner {
|
|
|
14845
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.`);
|
|
14846
15436
|
}
|
|
14847
15437
|
const promptBytes = Buffer.byteLength(options.userMessage, "utf8");
|
|
14848
|
-
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");
|
|
14849
15441
|
let systemPromptTempDir;
|
|
14850
15442
|
let systemPromptPath;
|
|
14851
15443
|
const cleanupSystemPromptFile = () => {
|
|
@@ -14868,13 +15460,30 @@ class PiSubagentRunner {
|
|
|
14868
15460
|
return failBeforeSpawn("spawn_failed", `failed to prepare pi system prompt file: ${error instanceof Error ? error.message : String(error)}`);
|
|
14869
15461
|
}
|
|
14870
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
|
+
}
|
|
14871
15479
|
const args = buildArgs(options, {
|
|
14872
15480
|
targetHarness: this.invocation.targetHarness,
|
|
14873
15481
|
disableDiscoveredExtensions: runMode.disableDiscoveredExtensions,
|
|
14874
15482
|
subagentExtensions: this.subagentExtensions,
|
|
14875
|
-
omitPositionalMessage: deliverViaStdin,
|
|
15483
|
+
omitPositionalMessage: deliverViaStdin || rpcBudget,
|
|
14876
15484
|
systemPromptPath,
|
|
14877
|
-
modelRef: modelRefOverride
|
|
15485
|
+
modelRef: modelRefOverride,
|
|
15486
|
+
rpc: rpcBudget
|
|
14878
15487
|
});
|
|
14879
15488
|
return new Promise((resolve) => {
|
|
14880
15489
|
let accountingMessages = [];
|
|
@@ -14883,13 +15492,17 @@ class PiSubagentRunner {
|
|
|
14883
15492
|
if (settled)
|
|
14884
15493
|
return;
|
|
14885
15494
|
settled = true;
|
|
15495
|
+
const outcome = budget ? {
|
|
15496
|
+
...result,
|
|
15497
|
+
meta: { ...result.meta, tokenBudget: budget.snapshot() }
|
|
15498
|
+
} : result;
|
|
14886
15499
|
cleanupSystemPromptFile();
|
|
14887
15500
|
try {
|
|
14888
|
-
recordAccounting(
|
|
15501
|
+
recordAccounting(outcome, accountingMessages);
|
|
14889
15502
|
} catch (err) {
|
|
14890
15503
|
sessionLog(options.accountingSessionId ?? "subagent", `subagent accounting failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
|
|
14891
15504
|
}
|
|
14892
|
-
resolve(
|
|
15505
|
+
resolve(outcome);
|
|
14893
15506
|
};
|
|
14894
15507
|
const emitProgress = (event) => {
|
|
14895
15508
|
if (!options.onProgress)
|
|
@@ -14901,10 +15514,14 @@ class PiSubagentRunner {
|
|
|
14901
15514
|
let child;
|
|
14902
15515
|
try {
|
|
14903
15516
|
child = this.spawnImpl(this.invocation.command, [...this.invocation.prefixArgs, ...this.extraArgs, ...args], {
|
|
15517
|
+
windowsHide: true,
|
|
14904
15518
|
cwd: options.cwd,
|
|
14905
15519
|
env: {
|
|
14906
15520
|
...process.env,
|
|
14907
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",
|
|
14908
15525
|
...options.temperature !== undefined ? {
|
|
14909
15526
|
MAGIC_CONTEXT_HISTORIAN_TEMPERATURE: String(options.temperature)
|
|
14910
15527
|
} : {},
|
|
@@ -14912,7 +15529,11 @@ class PiSubagentRunner {
|
|
|
14912
15529
|
MAGIC_CONTEXT_HISTORIAN_MAX_OUTPUT_TOKENS: String(options.maxOutputTokens)
|
|
14913
15530
|
} : {}
|
|
14914
15531
|
},
|
|
14915
|
-
stdio: [
|
|
15532
|
+
stdio: [
|
|
15533
|
+
deliverViaStdin || rpcBudget ? "pipe" : "ignore",
|
|
15534
|
+
"pipe",
|
|
15535
|
+
"pipe"
|
|
15536
|
+
]
|
|
14916
15537
|
});
|
|
14917
15538
|
} catch (error) {
|
|
14918
15539
|
cleanupSystemPromptFile();
|
|
@@ -14935,15 +15556,43 @@ class PiSubagentRunner {
|
|
|
14935
15556
|
return;
|
|
14936
15557
|
}
|
|
14937
15558
|
emitProgress({ type: "spawned", argv: args, pid: child.pid });
|
|
14938
|
-
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) {
|
|
14939
15576
|
child.stdin.on("error", () => {});
|
|
14940
15577
|
try {
|
|
14941
15578
|
child.stdin.end(options.userMessage, "utf8");
|
|
14942
15579
|
} catch {}
|
|
14943
15580
|
}
|
|
14944
15581
|
let stderr = "";
|
|
15582
|
+
let stderrLine = "";
|
|
14945
15583
|
child.stderr?.on("data", (chunk) => {
|
|
14946
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
|
+
}
|
|
14947
15596
|
stderr += text;
|
|
14948
15597
|
if (stderr.length > 16000) {
|
|
14949
15598
|
stderr = `…[truncated]${stderr.slice(-16000)}`;
|
|
@@ -14979,7 +15628,7 @@ class PiSubagentRunner {
|
|
|
14979
15628
|
const accumulatedMessages = [];
|
|
14980
15629
|
accountingMessages = accumulatedMessages;
|
|
14981
15630
|
rl.on("line", (line) => {
|
|
14982
|
-
if (line.length === 0)
|
|
15631
|
+
if (settled || line.length === 0)
|
|
14983
15632
|
return;
|
|
14984
15633
|
const parsed = parsePiEventLine(line);
|
|
14985
15634
|
if (!parsed.ok) {
|
|
@@ -14992,6 +15641,95 @@ class PiSubagentRunner {
|
|
|
14992
15641
|
if (typeof event !== "object" || event === null)
|
|
14993
15642
|
return;
|
|
14994
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
|
+
}
|
|
14995
15733
|
const isFirstEvent = eventCount === 0;
|
|
14996
15734
|
eventCount += 1;
|
|
14997
15735
|
lastEventTimestamp = Date.now();
|
|
@@ -15305,9 +16043,9 @@ function buildArgs(options, opts) {
|
|
|
15305
16043
|
const targetHarness = opts?.targetHarness ?? resolvePiHarnessKind();
|
|
15306
16044
|
const ompTarget = targetHarness === "omp";
|
|
15307
16045
|
const args = [
|
|
15308
|
-
"--print",
|
|
16046
|
+
...opts?.rpc ? [] : ["--print"],
|
|
15309
16047
|
"--mode",
|
|
15310
|
-
"json",
|
|
16048
|
+
opts?.rpc ? "rpc" : "json",
|
|
15311
16049
|
"--no-session",
|
|
15312
16050
|
"--no-skills",
|
|
15313
16051
|
...ompTarget ? ["--no-rules"] : ["--no-prompt-templates", "--no-context-files"]
|
|
@@ -15321,7 +16059,7 @@ function buildArgs(options, opts) {
|
|
|
15321
16059
|
}
|
|
15322
16060
|
}
|
|
15323
16061
|
const subagentEntryPath = opts?.subagentEntryPath ?? SUBAGENT_ENTRY_PATH;
|
|
15324
|
-
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));
|
|
15325
16063
|
if (shouldLoadSubagentExtension) {
|
|
15326
16064
|
args.push("--extension", subagentEntryPath);
|
|
15327
16065
|
if (DREAMER_ACTION_AGENTS.has(options.agent)) {
|
|
@@ -15329,7 +16067,7 @@ function buildArgs(options, opts) {
|
|
|
15329
16067
|
}
|
|
15330
16068
|
}
|
|
15331
16069
|
const historianCalibrationEntryPath = opts?.historianCalibrationEntryPath === undefined ? HISTORIAN_CALIBRATION_ENTRY_PATH : opts.historianCalibrationEntryPath;
|
|
15332
|
-
if (
|
|
16070
|
+
if (subagentStepCap(options.agent) !== undefined && historianCalibrationEntryPath) {
|
|
15333
16071
|
args.push("--extension", historianCalibrationEntryPath);
|
|
15334
16072
|
}
|
|
15335
16073
|
const strictTools = STRICT_TOOL_ALLOWLIST.get(options.agent);
|
|
@@ -15469,13 +16207,101 @@ function terminateChild(child) {
|
|
|
15469
16207
|
}
|
|
15470
16208
|
}
|
|
15471
16209
|
|
|
16210
|
+
// src/dreamer/primer-raw-provider-pi.ts
|
|
16211
|
+
import { join as join8 } from "node:path";
|
|
16212
|
+
|
|
16213
|
+
// src/dreamer/bounded-session-reader.ts
|
|
16214
|
+
import { closeSync, opendirSync, openSync, readSync } from "node:fs";
|
|
16215
|
+
import { join as join6 } from "node:path";
|
|
16216
|
+
var MAX_ENTRY_BYTES = 1024 * 1024;
|
|
16217
|
+
function* lines(path) {
|
|
16218
|
+
const fd = openSync(path, "r");
|
|
16219
|
+
const buffer = Buffer.alloc(64 * 1024);
|
|
16220
|
+
let pending = Buffer.alloc(0);
|
|
16221
|
+
try {
|
|
16222
|
+
while (true) {
|
|
16223
|
+
const bytes = readSync(fd, buffer, 0, buffer.length, null);
|
|
16224
|
+
if (bytes === 0)
|
|
16225
|
+
break;
|
|
16226
|
+
let start = 0;
|
|
16227
|
+
for (let i = 0;i < bytes; i++) {
|
|
16228
|
+
if (buffer[i] !== 10)
|
|
16229
|
+
continue;
|
|
16230
|
+
if (pending.length + i - start > MAX_ENTRY_BYTES)
|
|
16231
|
+
throw new Error("Pi session entry exceeds bounded reader capacity");
|
|
16232
|
+
yield Buffer.concat([pending, buffer.subarray(start, i)]).toString("utf8");
|
|
16233
|
+
pending = Buffer.alloc(0);
|
|
16234
|
+
start = i + 1;
|
|
16235
|
+
}
|
|
16236
|
+
if (pending.length + bytes - start > MAX_ENTRY_BYTES)
|
|
16237
|
+
throw new Error("Pi session entry exceeds bounded reader capacity");
|
|
16238
|
+
pending = Buffer.concat([pending, buffer.subarray(start, bytes)]);
|
|
16239
|
+
}
|
|
16240
|
+
if (pending.length)
|
|
16241
|
+
yield pending.toString("utf8");
|
|
16242
|
+
} finally {
|
|
16243
|
+
closeSync(fd);
|
|
16244
|
+
}
|
|
16245
|
+
}
|
|
16246
|
+
function record(value) {
|
|
16247
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
16248
|
+
}
|
|
16249
|
+
function* sessionFiles(directory, nested) {
|
|
16250
|
+
let dir;
|
|
16251
|
+
try {
|
|
16252
|
+
dir = opendirSync(directory);
|
|
16253
|
+
} catch {
|
|
16254
|
+
return;
|
|
16255
|
+
}
|
|
16256
|
+
try {
|
|
16257
|
+
while (true) {
|
|
16258
|
+
const entry = dir.readSync();
|
|
16259
|
+
if (!entry)
|
|
16260
|
+
break;
|
|
16261
|
+
const path = join6(directory, entry.name);
|
|
16262
|
+
if (entry.isFile() && entry.name.endsWith(".jsonl"))
|
|
16263
|
+
yield path;
|
|
16264
|
+
else if (nested && entry.isDirectory())
|
|
16265
|
+
yield* sessionFiles(path, false);
|
|
16266
|
+
}
|
|
16267
|
+
} finally {
|
|
16268
|
+
dir.closeSync();
|
|
16269
|
+
}
|
|
16270
|
+
}
|
|
16271
|
+
function findSession(directory, nested, sessionId) {
|
|
16272
|
+
for (const header of sessionHeaders(directory, nested)) {
|
|
16273
|
+
if (header.id === sessionId)
|
|
16274
|
+
return header.path;
|
|
16275
|
+
}
|
|
16276
|
+
return null;
|
|
16277
|
+
}
|
|
16278
|
+
function* sessionHeaders(directory, nested) {
|
|
16279
|
+
for (const path of sessionFiles(directory, nested)) {
|
|
16280
|
+
try {
|
|
16281
|
+
for (const line of lines(path)) {
|
|
16282
|
+
const header = record(JSON.parse(line));
|
|
16283
|
+
if (header.type === "session")
|
|
16284
|
+
yield { ...header, path };
|
|
16285
|
+
break;
|
|
16286
|
+
}
|
|
16287
|
+
} catch {}
|
|
16288
|
+
}
|
|
16289
|
+
}
|
|
16290
|
+
function* sessionEntries(path) {
|
|
16291
|
+
for (const line of lines(path)) {
|
|
16292
|
+
try {
|
|
16293
|
+
yield JSON.parse(line);
|
|
16294
|
+
} catch {}
|
|
16295
|
+
}
|
|
16296
|
+
}
|
|
16297
|
+
|
|
15472
16298
|
// src/dreamer/pi-session-api.ts
|
|
15473
|
-
import { existsSync as existsSync8, readFileSync as
|
|
16299
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, realpathSync as realpathSync4, statSync as statSync5 } from "node:fs";
|
|
15474
16300
|
import {
|
|
15475
16301
|
basename as basename2,
|
|
15476
16302
|
dirname as dirname2,
|
|
15477
16303
|
extname,
|
|
15478
|
-
join as
|
|
16304
|
+
join as join7,
|
|
15479
16305
|
relative,
|
|
15480
16306
|
resolve,
|
|
15481
16307
|
sep as sep2
|
|
@@ -15499,7 +16325,7 @@ function isScriptEntry(filePath) {
|
|
|
15499
16325
|
}
|
|
15500
16326
|
function readManifest(pkgJsonPath) {
|
|
15501
16327
|
try {
|
|
15502
|
-
const pkg = JSON.parse(
|
|
16328
|
+
const pkg = JSON.parse(readFileSync7(pkgJsonPath, "utf8"));
|
|
15503
16329
|
return pkg && typeof pkg === "object" ? pkg : null;
|
|
15504
16330
|
} catch {
|
|
15505
16331
|
return null;
|
|
@@ -15525,11 +16351,11 @@ function resolveRunningEntry() {
|
|
|
15525
16351
|
function findPackageRoot(startDir) {
|
|
15526
16352
|
let dir = startDir;
|
|
15527
16353
|
while (dir !== dirname2(dir)) {
|
|
15528
|
-
const pkg = readManifest(
|
|
16354
|
+
const pkg = readManifest(join7(dir, "package.json"));
|
|
15529
16355
|
if (isCodingAgentPackageName(pkg?.name)) {
|
|
15530
16356
|
if (basename2(dir) === "dist") {
|
|
15531
16357
|
const parentDir = dirname2(dir);
|
|
15532
|
-
const parentPkg = readManifest(
|
|
16358
|
+
const parentPkg = readManifest(join7(parentDir, "package.json"));
|
|
15533
16359
|
if (parentPkg?.name === pkg.name) {
|
|
15534
16360
|
return { dir: parentDir, pkg: parentPkg };
|
|
15535
16361
|
}
|
|
@@ -15588,7 +16414,7 @@ function toSourceEntry(entryPath, pkgDir) {
|
|
|
15588
16414
|
if (srcRel === rel) {
|
|
15589
16415
|
return null;
|
|
15590
16416
|
}
|
|
15591
|
-
return
|
|
16417
|
+
return join7(pkgDir, srcRel);
|
|
15592
16418
|
}
|
|
15593
16419
|
var defaultLoaders = [
|
|
15594
16420
|
{
|
|
@@ -15663,7 +16489,7 @@ async function loadDefaultPiSessionApi(loaders) {
|
|
|
15663
16489
|
throw new Error("Pi session APIs unavailable: expected SessionManager.listAll on pi-coding-agent");
|
|
15664
16490
|
}
|
|
15665
16491
|
const loadEntriesFromFile = mod.loadEntriesFromFile ?? ((filePath) => {
|
|
15666
|
-
const content =
|
|
16492
|
+
const content = readFileSync7(filePath, "utf8");
|
|
15667
16493
|
return mod.parseSessionEntries?.(content) ?? [];
|
|
15668
16494
|
});
|
|
15669
16495
|
return {
|
|
@@ -15673,35 +16499,108 @@ async function loadDefaultPiSessionApi(loaders) {
|
|
|
15673
16499
|
}
|
|
15674
16500
|
|
|
15675
16501
|
// src/dreamer/primer-raw-provider-pi.ts
|
|
15676
|
-
|
|
15677
|
-
|
|
15678
|
-
|
|
15679
|
-
|
|
15680
|
-
|
|
15681
|
-
|
|
15682
|
-
|
|
15683
|
-
|
|
16502
|
+
var MAX_PARTS = 256;
|
|
16503
|
+
var TOOL_KEYS = [
|
|
16504
|
+
"description",
|
|
16505
|
+
"filePath",
|
|
16506
|
+
"path",
|
|
16507
|
+
"pattern",
|
|
16508
|
+
"query",
|
|
16509
|
+
"symbol",
|
|
16510
|
+
"module",
|
|
16511
|
+
"action"
|
|
16512
|
+
];
|
|
16513
|
+
function record2(value) {
|
|
16514
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
16515
|
+
}
|
|
16516
|
+
function short(value, max = 512) {
|
|
16517
|
+
if (typeof value !== "string")
|
|
16518
|
+
return;
|
|
16519
|
+
return value.length <= max || estimateTokens(value) <= PRIMER_SEED_CAP_TOKENS ? value : value.slice(0, max);
|
|
16520
|
+
}
|
|
16521
|
+
function projectEntry(value) {
|
|
16522
|
+
const entry = record2(value);
|
|
16523
|
+
if (entry.type !== "message")
|
|
16524
|
+
return { type: entry.type };
|
|
16525
|
+
const message = record2(entry.message);
|
|
16526
|
+
let content = [];
|
|
16527
|
+
if (message.role === "user" && typeof message.content === "string") {
|
|
16528
|
+
content = short(message.content, RAW_SUMMARY_TEXT_MAX_CHARS);
|
|
16529
|
+
} else if (message.role !== "toolResult" && Array.isArray(message.content)) {
|
|
16530
|
+
if (message.content.length > MAX_PARTS)
|
|
16531
|
+
throw new Error("Pi primer entry has too many parts");
|
|
16532
|
+
content = message.content.flatMap((part) => {
|
|
16533
|
+
const p = record2(part);
|
|
16534
|
+
if (p.type === "text")
|
|
16535
|
+
return [
|
|
16536
|
+
{ type: "text", text: short(p.text, RAW_SUMMARY_TEXT_MAX_CHARS) }
|
|
16537
|
+
];
|
|
16538
|
+
if (p.type !== "toolCall")
|
|
16539
|
+
return [];
|
|
16540
|
+
const args = record2(p.arguments);
|
|
16541
|
+
return [
|
|
16542
|
+
{
|
|
16543
|
+
type: "toolCall",
|
|
16544
|
+
id: short(p.id),
|
|
16545
|
+
name: short(p.name),
|
|
16546
|
+
arguments: Object.fromEntries(TOOL_KEYS.map((key) => [key, short(args[key])]))
|
|
16547
|
+
}
|
|
16548
|
+
];
|
|
16549
|
+
});
|
|
16550
|
+
}
|
|
16551
|
+
return {
|
|
16552
|
+
type: entry.type,
|
|
16553
|
+
id: entry.id,
|
|
16554
|
+
timestamp: entry.timestamp,
|
|
16555
|
+
message: {
|
|
16556
|
+
role: message.role,
|
|
16557
|
+
content,
|
|
16558
|
+
toolCallId: short(message.toolCallId),
|
|
16559
|
+
toolName: short(message.toolName)
|
|
15684
16560
|
}
|
|
15685
|
-
resolved ??= loadDefaultPiSessionApi();
|
|
15686
|
-
return resolved;
|
|
15687
16561
|
};
|
|
16562
|
+
}
|
|
16563
|
+
function* summaryEntries(path) {
|
|
16564
|
+
let pendingResults = 0;
|
|
16565
|
+
for (const line of lines(path)) {
|
|
16566
|
+
let parsed;
|
|
16567
|
+
try {
|
|
16568
|
+
parsed = JSON.parse(line);
|
|
16569
|
+
} catch {
|
|
16570
|
+
continue;
|
|
16571
|
+
}
|
|
16572
|
+
const entry = projectEntry(parsed);
|
|
16573
|
+
const e = record2(entry);
|
|
16574
|
+
if (e.type === "message") {
|
|
16575
|
+
const m = record2(e.message);
|
|
16576
|
+
if (m.role === "toolResult" && m.toolCallId) {
|
|
16577
|
+
if (++pendingResults > MAX_PARTS)
|
|
16578
|
+
throw new Error("Pi primer has too many consecutive tool results");
|
|
16579
|
+
} else if (m.role === "user" || m.role === "assistant")
|
|
16580
|
+
pendingResults = 0;
|
|
16581
|
+
}
|
|
16582
|
+
yield entry;
|
|
16583
|
+
}
|
|
16584
|
+
}
|
|
16585
|
+
function createPiPrimerRawProviderFactory(deps = {}) {
|
|
15688
16586
|
return async (sessionId) => {
|
|
15689
16587
|
try {
|
|
15690
|
-
|
|
15691
|
-
|
|
15692
|
-
|
|
15693
|
-
|
|
15694
|
-
|
|
15695
|
-
|
|
15696
|
-
|
|
15697
|
-
|
|
16588
|
+
let directory = deps.sessionDir;
|
|
16589
|
+
if (!directory) {
|
|
16590
|
+
const mod = await resolvePiCodingAgentModule();
|
|
16591
|
+
if (!mod.getAgentDir)
|
|
16592
|
+
return null;
|
|
16593
|
+
directory = join8(mod.getAgentDir(), "sessions");
|
|
16594
|
+
}
|
|
16595
|
+
const path = findSession(directory, !deps.sessionDir, sessionId);
|
|
16596
|
+
if (!path)
|
|
15698
16597
|
return null;
|
|
15699
16598
|
return {
|
|
15700
16599
|
readMessages() {
|
|
15701
|
-
|
|
16600
|
+
throw new Error("Pi primer history requires bounded pages");
|
|
15702
16601
|
},
|
|
15703
|
-
|
|
15704
|
-
return
|
|
16602
|
+
readMessagePage(after, limit, watermark) {
|
|
16603
|
+
return convertEntriesToRawMessagePage(summaryEntries(path), after, Math.min(50, limit), watermark);
|
|
15705
16604
|
}
|
|
15706
16605
|
};
|
|
15707
16606
|
} catch {
|
|
@@ -15711,17 +16610,22 @@ function createPiPrimerRawProviderFactory(deps = {}) {
|
|
|
15711
16610
|
}
|
|
15712
16611
|
|
|
15713
16612
|
// src/dreamer/retrospective-raw-provider-pi.ts
|
|
15714
|
-
import { resolve as resolve2 } from "node:path";
|
|
16613
|
+
import { join as join9, resolve as resolve2 } from "node:path";
|
|
15715
16614
|
class PiRetrospectiveRawProvider {
|
|
15716
16615
|
deps;
|
|
15717
16616
|
sessionPathById = new Map;
|
|
15718
|
-
resolvedDefaultDeps = null;
|
|
15719
16617
|
constructor(deps) {
|
|
15720
16618
|
this.deps = deps;
|
|
15721
16619
|
}
|
|
15722
16620
|
async listProjectSessions(_projectIdentity) {
|
|
15723
|
-
|
|
15724
|
-
|
|
16621
|
+
let directory = this.deps.sessionDir;
|
|
16622
|
+
if (!directory && !this.deps.listSessions) {
|
|
16623
|
+
const mod = await resolvePiCodingAgentModule();
|
|
16624
|
+
if (!mod.getAgentDir)
|
|
16625
|
+
return [];
|
|
16626
|
+
directory = join9(mod.getAgentDir(), "sessions");
|
|
16627
|
+
}
|
|
16628
|
+
const sessions = this.deps.listSessions ? await this.deps.listSessions(directory) : directory ? sessionHeaders(directory, !this.deps.sessionDir) : [];
|
|
15725
16629
|
const projectCwd = resolve2(this.deps.projectCwd);
|
|
15726
16630
|
const result = [];
|
|
15727
16631
|
this.sessionPathById.clear();
|
|
@@ -15733,61 +16637,63 @@ class PiRetrospectiveRawProvider {
|
|
|
15733
16637
|
continue;
|
|
15734
16638
|
if (typeof info.cwd !== "string" || resolve2(info.cwd) !== projectCwd)
|
|
15735
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;
|
|
15736
16643
|
this.sessionPathById.set(info.id, info.path);
|
|
15737
16644
|
result.push({
|
|
15738
16645
|
sessionId: info.id,
|
|
15739
16646
|
path: info.path,
|
|
15740
|
-
updatedAt:
|
|
16647
|
+
updatedAt: activity
|
|
15741
16648
|
});
|
|
15742
16649
|
}
|
|
15743
16650
|
return result.sort((a, b) => (a.updatedAt ?? 0) - (b.updatedAt ?? 0));
|
|
15744
16651
|
}
|
|
15745
16652
|
async readUserMessagesSince(sessionId, sinceMs, capPerSession) {
|
|
15746
|
-
const all = await this.loadUserEntries(sessionId);
|
|
15747
16653
|
const limit = Math.max(1, Math.floor(capPerSession));
|
|
15748
|
-
const
|
|
16654
|
+
const messages = await this.selectUserEntries(sessionId, (ts) => ts > sinceMs, limit + 1, false);
|
|
15749
16655
|
return {
|
|
15750
|
-
messages:
|
|
15751
|
-
truncated:
|
|
16656
|
+
messages: messages.slice(0, limit),
|
|
16657
|
+
truncated: messages.length > limit
|
|
15752
16658
|
};
|
|
15753
16659
|
}
|
|
15754
16660
|
async readOldestMessageTimesSince(sessionIds, sinceMs) {
|
|
15755
16661
|
const out = new Map;
|
|
15756
16662
|
for (const sessionId of sessionIds) {
|
|
15757
|
-
const oldest =
|
|
16663
|
+
const [oldest] = await this.selectUserEntries(sessionId, (ts) => ts > sinceMs, 1, false);
|
|
15758
16664
|
if (oldest)
|
|
15759
16665
|
out.set(sessionId, oldest.ts);
|
|
15760
16666
|
}
|
|
15761
16667
|
return out;
|
|
15762
16668
|
}
|
|
15763
16669
|
async readUserMessagesBefore(sessionId, beforeMs, count) {
|
|
15764
|
-
|
|
15765
|
-
return all.filter((entry) => entry.ts <= beforeMs).sort((a, b) => a.ts - b.ts || a.ordinal - b.ordinal).slice(-Math.max(1, Math.floor(count)));
|
|
16670
|
+
return this.selectUserEntries(sessionId, (ts) => ts <= beforeMs, Math.max(1, Math.floor(count)), true);
|
|
15766
16671
|
}
|
|
15767
|
-
async
|
|
16672
|
+
async selectUserEntries(sessionId, eligible, limit, newest) {
|
|
15768
16673
|
const filePath = this.sessionPathById.get(sessionId);
|
|
15769
16674
|
if (!filePath)
|
|
15770
16675
|
return [];
|
|
15771
|
-
const
|
|
15772
|
-
let entries;
|
|
16676
|
+
const kept = [];
|
|
15773
16677
|
try {
|
|
15774
|
-
entries = await deps.loadEntriesFromFile(filePath);
|
|
16678
|
+
const entries = this.deps.loadEntriesFromFile ? await this.deps.loadEntriesFromFile(filePath) : sessionEntries(filePath);
|
|
16679
|
+
let ordinal = 0;
|
|
16680
|
+
for (const entry of entries) {
|
|
16681
|
+
const row = normalizePiUserEntry(entry, sessionId, ++ordinal);
|
|
16682
|
+
if (!row || !eligible(row.ts))
|
|
16683
|
+
continue;
|
|
16684
|
+
kept.push(row);
|
|
16685
|
+
kept.sort((a, b) => a.ts - b.ts || a.ordinal - b.ordinal);
|
|
16686
|
+
if (kept.length > limit) {
|
|
16687
|
+
if (newest)
|
|
16688
|
+
kept.shift();
|
|
16689
|
+
else
|
|
16690
|
+
kept.pop();
|
|
16691
|
+
}
|
|
16692
|
+
}
|
|
15775
16693
|
} catch {
|
|
15776
16694
|
return [];
|
|
15777
16695
|
}
|
|
15778
|
-
|
|
15779
|
-
return [];
|
|
15780
|
-
return entries.map((entry, index) => normalizePiUserEntry(entry, sessionId, index + 1)).filter((entry) => entry !== null);
|
|
15781
|
-
}
|
|
15782
|
-
async resolveDeps() {
|
|
15783
|
-
if (this.deps.listSessions && this.deps.loadEntriesFromFile) {
|
|
15784
|
-
return {
|
|
15785
|
-
listSessions: this.deps.listSessions,
|
|
15786
|
-
loadEntriesFromFile: this.deps.loadEntriesFromFile
|
|
15787
|
-
};
|
|
15788
|
-
}
|
|
15789
|
-
this.resolvedDefaultDeps ??= loadDefaultPiSessionApi();
|
|
15790
|
-
return this.resolvedDefaultDeps;
|
|
16696
|
+
return kept;
|
|
15791
16697
|
}
|
|
15792
16698
|
}
|
|
15793
16699
|
function normalizePiUserEntry(entry, sessionId, ordinal) {
|
|
@@ -15827,7 +16733,7 @@ function extractPiTextContent(content) {
|
|
|
15827
16733
|
// src/dreamer/index.ts
|
|
15828
16734
|
var warnedUnknownModels = new Set;
|
|
15829
16735
|
var modelAvailability = new WeakMap;
|
|
15830
|
-
function validatePiDreamerModels(tasks, registry) {
|
|
16736
|
+
function validatePiDreamerModels(tasks, registry, harness = "pi") {
|
|
15831
16737
|
let resolved = modelAvailability.get(registry);
|
|
15832
16738
|
if (!resolved) {
|
|
15833
16739
|
resolved = new Map;
|
|
@@ -15837,6 +16743,8 @@ function validatePiDreamerModels(tasks, registry) {
|
|
|
15837
16743
|
const entries = [task.model, ...task.fallbackModels ?? []].filter((entry) => entry !== undefined);
|
|
15838
16744
|
const valid = entries.filter((entry) => {
|
|
15839
16745
|
const model = typeof entry === "string" ? entry : entry.model;
|
|
16746
|
+
if (harness === "omp" && model.startsWith("@"))
|
|
16747
|
+
return true;
|
|
15840
16748
|
let available = resolved.get(model);
|
|
15841
16749
|
if (available === undefined) {
|
|
15842
16750
|
const separator = model.indexOf("/");
|
|
@@ -15883,12 +16791,18 @@ var inFlightDreams = (() => {
|
|
|
15883
16791
|
globals[PI_DREAMER_IN_FLIGHT] = dreams;
|
|
15884
16792
|
return dreams;
|
|
15885
16793
|
})();
|
|
16794
|
+
var dreamAbortControllers = new Map;
|
|
16795
|
+
function abortInFlightDreamers(registrationOwner) {
|
|
16796
|
+
for (const controller of dreamAbortControllers.get(registrationOwner) ?? []) {
|
|
16797
|
+
controller.abort();
|
|
16798
|
+
}
|
|
16799
|
+
}
|
|
15886
16800
|
var sessionCounter = 0;
|
|
15887
16801
|
var piSubagentRunnerFactory = () => new PiSubagentRunner;
|
|
15888
16802
|
var startDreamScheduleTimerFn = startDreamScheduleTimer;
|
|
15889
16803
|
function registerPiDreamerProject(opts) {
|
|
15890
16804
|
const directory = resolve3(opts.projectDir);
|
|
15891
|
-
if (opts.config.disable === true || directory === parse(directory).root || directory === homedir2()) {
|
|
16805
|
+
if (opts.config.disable === true || !isUsableProjectIdentity(opts.projectIdentity) || directory === parse(directory).root || directory === homedir2()) {
|
|
15892
16806
|
return;
|
|
15893
16807
|
}
|
|
15894
16808
|
const existing = registeredProjects2.get(opts.projectIdentity);
|
|
@@ -15911,7 +16825,7 @@ function registerPiDreamerProject(opts) {
|
|
|
15911
16825
|
const client = createPiDreamerClient(opts, notifyOwnersOfAdjunctRefresh, () => {
|
|
15912
16826
|
const current = registeredProjects2.get(opts.projectIdentity);
|
|
15913
16827
|
return current?.generation === generation && current.owners.get(opts.registrationOwner)?.projectDir === opts.projectDir;
|
|
15914
|
-
});
|
|
16828
|
+
}, () => opts.sampleDreamRun ? opts.sampleDreamRun().dreamerConfig?.maxTokens : opts.config.maxTokens, () => opts.sampleDreamRun?.().dreamerConfig ?? opts.config);
|
|
15915
16829
|
let cleanup;
|
|
15916
16830
|
let cancelled = false;
|
|
15917
16831
|
const modelRegistry = opts.modelRegistry;
|
|
@@ -15921,7 +16835,7 @@ function registerPiDreamerProject(opts) {
|
|
|
15921
16835
|
harness: opts.harness,
|
|
15922
16836
|
client,
|
|
15923
16837
|
dreamerConfig: opts.config,
|
|
15924
|
-
validateTaskModels: modelRegistry ? (tasks) => validatePiDreamerModels(tasks, modelRegistry) : undefined,
|
|
16838
|
+
validateTaskModels: modelRegistry ? (tasks) => validatePiDreamerModels(tasks, modelRegistry, opts.harness) : undefined,
|
|
15925
16839
|
sampleDreamRun: opts.sampleDreamRun,
|
|
15926
16840
|
language: opts.language,
|
|
15927
16841
|
gitCommitIndexing: opts.gitCommitIndexing,
|
|
@@ -15930,7 +16844,10 @@ function registerPiDreamerProject(opts) {
|
|
|
15930
16844
|
retinaHandoff: opts.retinaHandoff,
|
|
15931
16845
|
mural: opts.mural,
|
|
15932
16846
|
ensureRegistered: ensureProjectRegisteredFromPiDirectory,
|
|
15933
|
-
retrospectiveRawProvider: () => new PiRetrospectiveRawProvider({
|
|
16847
|
+
retrospectiveRawProvider: () => new PiRetrospectiveRawProvider({
|
|
16848
|
+
projectCwd: opts.projectDir,
|
|
16849
|
+
contextDb: opts.db
|
|
16850
|
+
}),
|
|
15934
16851
|
primerRawProviderFactory: createPiPrimerRawProviderFactory()
|
|
15935
16852
|
}).then((timerCleanup) => {
|
|
15936
16853
|
if (cancelled) {
|
|
@@ -15949,17 +16866,18 @@ function registerPiDreamerProject(opts) {
|
|
|
15949
16866
|
const sampled = manualOpts.sampleDreamRun?.();
|
|
15950
16867
|
const dreamerConfig = sampled?.dreamerConfig ?? manualOpts.config;
|
|
15951
16868
|
const mural = sampled?.mural ?? manualOpts.mural;
|
|
15952
|
-
const manualClient = createPiDreamerClient(manualOpts, notifyOwnersOfAdjunctRefresh, () => owners.get(manualOpts.registrationOwner)?.projectDir === manualOpts.projectDir);
|
|
16869
|
+
const manualClient = createPiDreamerClient(manualOpts, notifyOwnersOfAdjunctRefresh, () => owners.get(manualOpts.registrationOwner)?.projectDir === manualOpts.projectDir, () => dreamerConfig.maxTokens, () => dreamerConfig);
|
|
15953
16870
|
const manualRun = runManualDream({
|
|
15954
16871
|
db: manualOpts.db,
|
|
15955
16872
|
projectIdentity: manualOpts.projectIdentity,
|
|
15956
|
-
tasks: manualOpts.modelRegistry ? validatePiDreamerModels(buildDreamTaskRuntimeConfigs(dreamerConfig, manualOpts.harness, manualOpts.language, mural?.model), manualOpts.modelRegistry) : buildDreamTaskRuntimeConfigs(dreamerConfig, manualOpts.harness, manualOpts.language, mural?.model),
|
|
16873
|
+
tasks: manualOpts.modelRegistry ? validatePiDreamerModels(buildDreamTaskRuntimeConfigs(dreamerConfig, manualOpts.harness, manualOpts.language, mural?.model), manualOpts.modelRegistry, manualOpts.harness) : buildDreamTaskRuntimeConfigs(dreamerConfig, manualOpts.harness, manualOpts.language, mural?.model),
|
|
15957
16874
|
executor: createDreamTaskExecutor({
|
|
15958
16875
|
client: manualClient,
|
|
15959
16876
|
sessionDirectory: manualOpts.projectDir,
|
|
15960
16877
|
openOpenCodeDb,
|
|
15961
16878
|
retrospectiveRawProvider: new PiRetrospectiveRawProvider({
|
|
15962
|
-
projectCwd: manualOpts.projectDir
|
|
16879
|
+
projectCwd: manualOpts.projectDir,
|
|
16880
|
+
contextDb: manualOpts.db
|
|
15963
16881
|
}),
|
|
15964
16882
|
primerRawProviderFactory: createPiPrimerRawProviderFactory(),
|
|
15965
16883
|
userMemoryCollectionEnabled: userMemoryCollectionEnabled(dreamerConfig),
|
|
@@ -16029,8 +16947,9 @@ async function awaitInFlightDreamers(registrationOwner) {
|
|
|
16029
16947
|
return;
|
|
16030
16948
|
await Promise.allSettled(runs);
|
|
16031
16949
|
}
|
|
16032
|
-
function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRefreshNeeded, isRegistrationOwnerActive = () => true) {
|
|
16950
|
+
function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRefreshNeeded, isRegistrationOwnerActive = () => true, getMaxOutputTokens = () => opts.config.maxTokens, getDreamerConfig = () => opts.config) {
|
|
16033
16951
|
const runner = piSubagentRunnerFactory();
|
|
16952
|
+
const budgetStates = new Map;
|
|
16034
16953
|
const assertRegistrationOwnerActive = () => {
|
|
16035
16954
|
if (!isRegistrationOwnerActive()) {
|
|
16036
16955
|
throw new Error(`Pi dreamer registration is no longer active for project ${opts.projectIdentity}`);
|
|
@@ -16060,6 +16979,13 @@ function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRe
|
|
|
16060
16979
|
const systemPrompt = extractSystemPrompt(args);
|
|
16061
16980
|
const perTaskModel = extractBodyModel(args);
|
|
16062
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);
|
|
16063
16989
|
const runPromise = runner.run({
|
|
16064
16990
|
agent: requestedAgent,
|
|
16065
16991
|
systemPrompt,
|
|
@@ -16068,17 +16994,39 @@ function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRe
|
|
|
16068
16994
|
fallbackModels: undefined,
|
|
16069
16995
|
timeoutMs: 30 * 60 * 1000,
|
|
16070
16996
|
cwd: dreamSession.directory,
|
|
16071
|
-
signal: args.signal
|
|
16997
|
+
signal: args.signal ? AbortSignal.any([args.signal, controller.signal]) : controller.signal,
|
|
16072
16998
|
thinkingLevel: extractBodyVariant(args),
|
|
16999
|
+
maxOutputTokens: getMaxOutputTokens(),
|
|
16073
17000
|
accountingSessionId: opts.projectIdentity,
|
|
16074
17001
|
accountingSubagent: "dreamer",
|
|
16075
|
-
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
|
+
})()
|
|
16076
17009
|
});
|
|
16077
17010
|
inFlightDreams.set(runPromise, opts.registrationOwner);
|
|
16078
17011
|
try {
|
|
16079
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
|
+
}
|
|
16080
17022
|
assertRegistrationOwnerActive();
|
|
16081
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
|
+
}
|
|
16082
17030
|
const error = new Error(`Pi dreamer subagent failed (${result.reason}): ${result.error}`);
|
|
16083
17031
|
if (result.transient) {
|
|
16084
17032
|
error.transient = true;
|
|
@@ -16095,6 +17043,9 @@ function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRe
|
|
|
16095
17043
|
onAdjunctsRefreshNeeded?.(opts.projectIdentity);
|
|
16096
17044
|
} finally {
|
|
16097
17045
|
inFlightDreams.delete(runPromise);
|
|
17046
|
+
controllers.delete(controller);
|
|
17047
|
+
if (controllers.size === 0)
|
|
17048
|
+
dreamAbortControllers.delete(opts.registrationOwner);
|
|
16098
17049
|
}
|
|
16099
17050
|
},
|
|
16100
17051
|
messages: async (args) => {
|
|
@@ -16107,7 +17058,11 @@ function createPiDreamerClient(opts, onAdjunctsRefreshNeeded = opts.onAdjunctsRe
|
|
|
16107
17058
|
return {};
|
|
16108
17059
|
}
|
|
16109
17060
|
};
|
|
16110
|
-
return {
|
|
17061
|
+
return {
|
|
17062
|
+
session,
|
|
17063
|
+
readTokenBudget: (task) => budgetStates.get(task),
|
|
17064
|
+
resetDreamTokenBudget: (task) => budgetStates.delete(task)
|
|
17065
|
+
};
|
|
16111
17066
|
}
|
|
16112
17067
|
function readDirectory(args) {
|
|
16113
17068
|
const query = args.query;
|
|
@@ -16416,6 +17371,7 @@ Unknown task "${requested}".`,
|
|
|
16416
17371
|
try {
|
|
16417
17372
|
await deps.ensureRegistered?.(ctx);
|
|
16418
17373
|
const result = await runPiDreamForProject(project.projectIdentity, task, deps.registrationOwner);
|
|
17374
|
+
const backlogAfter = getDreamTaskBacklogs(deps.db, project.projectIdentity, backlogTasks);
|
|
16419
17375
|
const lines = [];
|
|
16420
17376
|
if (result.ran.length > 0)
|
|
16421
17377
|
lines.push(`Ran: ${result.ran.join(", ")}`);
|
|
@@ -16431,8 +17387,8 @@ Unknown task "${requested}".`,
|
|
|
16431
17387
|
lines.push(`Skipped (no work): ${result.skippedNoWork.join(", ")}`);
|
|
16432
17388
|
if (result.deferredBusy.length > 0)
|
|
16433
17389
|
lines.push(`Busy: ${result.deferredBusy.join(", ")} — another dream task holds this domain's lease; retry in a minute`);
|
|
16434
|
-
if (Object.keys(
|
|
16435
|
-
lines.push("", "Backlog at run end:", formatDreamTaskBacklogs(
|
|
17390
|
+
if (Object.keys(backlogAfter).length > 0) {
|
|
17391
|
+
lines.push("", "Backlog at run end:", formatDreamTaskBacklogs(backlogAfter, backlogTasks));
|
|
16436
17392
|
}
|
|
16437
17393
|
if (lines.length === 0)
|
|
16438
17394
|
lines.push("No enabled dream tasks to run.");
|
|
@@ -16461,26 +17417,6 @@ Unknown task "${requested}".`,
|
|
|
16461
17417
|
});
|
|
16462
17418
|
}
|
|
16463
17419
|
|
|
16464
|
-
// ../plugin/src/hooks/magic-context/embed-session-state.ts
|
|
16465
|
-
var embedPauseBySession = new Set;
|
|
16466
|
-
var embedRunStateBySession = new Map;
|
|
16467
|
-
var autoEmbedAttemptedBySession = new Set;
|
|
16468
|
-
function getEmbedDrainUiStatus(sessionId, progress) {
|
|
16469
|
-
if (embedPauseBySession.has(sessionId)) {
|
|
16470
|
-
return { status: "paused" };
|
|
16471
|
-
}
|
|
16472
|
-
const activeRun = embedRunStateBySession.get(sessionId);
|
|
16473
|
-
if (activeRun && !activeRun.signal.aborted || progress?.kind === "embed" && progress.phase === "recomp") {
|
|
16474
|
-
return { status: "running" };
|
|
16475
|
-
}
|
|
16476
|
-
if (progress?.kind === "embed" && (progress.phase === "failed" || progress.phase === "skipped") && progress.message) {
|
|
16477
|
-
if (/provider/i.test(progress.message)) {
|
|
16478
|
-
return { status: "stopped", detail: progress.message };
|
|
16479
|
-
}
|
|
16480
|
-
}
|
|
16481
|
-
return { status: "idle" };
|
|
16482
|
-
}
|
|
16483
|
-
|
|
16484
17420
|
// ../plugin/src/hooks/magic-context/format-embed-failure.ts
|
|
16485
17421
|
function formatEmbedFailureSummary(embedded, remaining, failure, style = "markdown") {
|
|
16486
17422
|
const historyBlocks = `history block${embedded === 1 ? "" : "s"}`;
|
|
@@ -16544,7 +17480,7 @@ function clearPiEmbedSessionState(sessionId) {
|
|
|
16544
17480
|
ctrl.abort();
|
|
16545
17481
|
embedRunStateBySession.delete(sessionId);
|
|
16546
17482
|
}
|
|
16547
|
-
|
|
17483
|
+
invalidateAutoEmbedSession(sessionId);
|
|
16548
17484
|
}
|
|
16549
17485
|
async function runEmbedDrain(db, projectIdentity, sessionId, options = {}) {
|
|
16550
17486
|
const activeCtrl = embedRunStateBySession.get(sessionId);
|
|
@@ -16724,28 +17660,36 @@ ${statusText}`,
|
|
|
16724
17660
|
});
|
|
16725
17661
|
}
|
|
16726
17662
|
function maybeAutoEmbedPiSession(deps, sessionId, projectDir, projectIdentity) {
|
|
17663
|
+
if (!isUsableProjectIdentity(projectIdentity))
|
|
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);
|
|
16727
17673
|
if (autoEmbedAttemptedBySession.has(sessionId))
|
|
16728
17674
|
return;
|
|
16729
17675
|
if (embedPauseBySession.has(sessionId))
|
|
16730
17676
|
return;
|
|
16731
17677
|
autoEmbedAttemptedBySession.add(sessionId);
|
|
17678
|
+
autoEmbedIdentityBySession.set(sessionId, embedIdentity);
|
|
16732
17679
|
(async () => {
|
|
16733
|
-
let drainReachedTerminal = false;
|
|
16734
17680
|
try {
|
|
16735
17681
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
16736
17682
|
await ensureProjectRegisteredFromPiDirectory(projectDir, deps.db);
|
|
16737
|
-
const coverage =
|
|
16738
|
-
if (!coverage.enabled)
|
|
17683
|
+
const coverage = await getAutoEmbeddingSessionCoverage(deps.db, projectIdentity, sessionId);
|
|
17684
|
+
if (!coverage.enabled) {
|
|
17685
|
+
invalidateAutoEmbedSession(sessionId);
|
|
16739
17686
|
return;
|
|
16740
|
-
|
|
17687
|
+
}
|
|
17688
|
+
const remaining = coverage.total - coverage.embedded;
|
|
16741
17689
|
if (remaining <= 0)
|
|
16742
17690
|
return;
|
|
16743
17691
|
await runEmbedDrain(deps.db, projectIdentity, sessionId);
|
|
16744
|
-
|
|
16745
|
-
} catch {} finally {
|
|
16746
|
-
if (!drainReachedTerminal)
|
|
16747
|
-
autoEmbedAttemptedBySession.delete(sessionId);
|
|
16748
|
-
}
|
|
17692
|
+
} catch {}
|
|
16749
17693
|
})();
|
|
16750
17694
|
}
|
|
16751
17695
|
|
|
@@ -16872,6 +17816,135 @@ function commitPiCompactionModeRecord(db, sessionId, record) {
|
|
|
16872
17816
|
// src/context-handler.ts
|
|
16873
17817
|
import * as crypto3 from "node:crypto";
|
|
16874
17818
|
|
|
17819
|
+
// ../plugin/src/hooks/magic-context/event-resolvers.ts
|
|
17820
|
+
var DEFAULT_CONTEXT_LIMIT = 200000;
|
|
17821
|
+
var clampWarnSeen = new Set;
|
|
17822
|
+
function isFinitePositive(v) {
|
|
17823
|
+
return typeof v === "number" && Number.isFinite(v) && v > 0;
|
|
17824
|
+
}
|
|
17825
|
+
function* modelKeyLookupOrder(modelKey) {
|
|
17826
|
+
const slash = modelKey.indexOf("/");
|
|
17827
|
+
const providerRefs = slash >= 0 ? modelRefLookupOrder(modelKey) : [];
|
|
17828
|
+
let modelId = slash >= 0 ? modelKey.slice(slash + 1) : modelKey;
|
|
17829
|
+
while (modelId.length > 0) {
|
|
17830
|
+
for (const providerRef of providerRefs) {
|
|
17831
|
+
const providerSlash = providerRef.indexOf("/");
|
|
17832
|
+
yield `${providerRef.slice(0, providerSlash)}/${modelId}`;
|
|
17833
|
+
}
|
|
17834
|
+
yield modelId;
|
|
17835
|
+
const lastDash = modelId.lastIndexOf("-");
|
|
17836
|
+
if (lastDash <= 0)
|
|
17837
|
+
break;
|
|
17838
|
+
modelId = modelId.slice(0, lastDash);
|
|
17839
|
+
}
|
|
17840
|
+
}
|
|
17841
|
+
function resolveExecuteThresholdDetail(config, modelKey, fallback, options) {
|
|
17842
|
+
if (options?.tokensConfig && isFinitePositive(options.contextLimit)) {
|
|
17843
|
+
const contextLimit = options.contextLimit;
|
|
17844
|
+
const tokenMatch = resolveTokensMatchWithKey(options.tokensConfig, modelKey);
|
|
17845
|
+
if (tokenMatch && isFinitePositive(tokenMatch.value)) {
|
|
17846
|
+
const cap = contextLimit * (MAX_EXECUTE_THRESHOLD / 100);
|
|
17847
|
+
const effectiveTokens = Math.min(tokenMatch.value, cap);
|
|
17848
|
+
if (effectiveTokens < tokenMatch.value) {
|
|
17849
|
+
const dedupeKey = `${options.sessionId ?? "__global__"}|${modelKey ?? "__default__"}|${tokenMatch.value}|${cap}`;
|
|
17850
|
+
if (!clampWarnSeen.has(dedupeKey)) {
|
|
17851
|
+
clampWarnSeen.add(dedupeKey);
|
|
17852
|
+
const msg = `execute_threshold_tokens clamped: ${tokenMatch.value} → ${effectiveTokens} (${MAX_EXECUTE_THRESHOLD}% of ${contextLimit}) for ${modelKey ?? "default"}`;
|
|
17853
|
+
if (options.sessionId) {
|
|
17854
|
+
sessionLog(options.sessionId, `WARN: ${msg}`);
|
|
17855
|
+
} else {
|
|
17856
|
+
log(`[magic-context] WARN: ${msg}`);
|
|
17857
|
+
}
|
|
17858
|
+
}
|
|
17859
|
+
}
|
|
17860
|
+
const percentage = effectiveTokens / contextLimit * 100;
|
|
17861
|
+
const detail = {
|
|
17862
|
+
percentage: Math.min(percentage, MAX_EXECUTE_THRESHOLD),
|
|
17863
|
+
mode: "tokens",
|
|
17864
|
+
absoluteTokens: Math.floor(effectiveTokens),
|
|
17865
|
+
matchedKey: tokenMatch.matchedKey
|
|
17866
|
+
};
|
|
17867
|
+
if (effectiveTokens < tokenMatch.value) {
|
|
17868
|
+
detail.clamped = true;
|
|
17869
|
+
detail.configuredValue = tokenMatch.value;
|
|
17870
|
+
}
|
|
17871
|
+
return detail;
|
|
17872
|
+
}
|
|
17873
|
+
}
|
|
17874
|
+
let resolved;
|
|
17875
|
+
let matchedKey;
|
|
17876
|
+
if (typeof config === "number") {
|
|
17877
|
+
resolved = config;
|
|
17878
|
+
} else if (modelKey) {
|
|
17879
|
+
let matched;
|
|
17880
|
+
for (const candidate of modelKeyLookupOrder(modelKey)) {
|
|
17881
|
+
if (typeof config[candidate] === "number") {
|
|
17882
|
+
matched = config[candidate];
|
|
17883
|
+
matchedKey = candidate;
|
|
17884
|
+
break;
|
|
17885
|
+
}
|
|
17886
|
+
}
|
|
17887
|
+
if (matched === undefined && typeof config.default === "number") {
|
|
17888
|
+
resolved = config.default;
|
|
17889
|
+
matchedKey = "default";
|
|
17890
|
+
} else {
|
|
17891
|
+
resolved = matched ?? fallback;
|
|
17892
|
+
}
|
|
17893
|
+
} else if (typeof config.default === "number") {
|
|
17894
|
+
resolved = config.default;
|
|
17895
|
+
matchedKey = "default";
|
|
17896
|
+
} else {
|
|
17897
|
+
resolved = fallback;
|
|
17898
|
+
}
|
|
17899
|
+
if (!Number.isFinite(resolved) || resolved < 0) {
|
|
17900
|
+
resolved = fallback;
|
|
17901
|
+
}
|
|
17902
|
+
const cappedPercentage = Math.min(resolved, MAX_EXECUTE_THRESHOLD);
|
|
17903
|
+
const percentageClamped = cappedPercentage < resolved;
|
|
17904
|
+
if (percentageClamped) {
|
|
17905
|
+
const dedupeKey = `pct|${options?.sessionId ?? "__global__"}|${modelKey ?? "__default__"}|${resolved}`;
|
|
17906
|
+
if (!clampWarnSeen.has(dedupeKey)) {
|
|
17907
|
+
clampWarnSeen.add(dedupeKey);
|
|
17908
|
+
const msg = `execute_threshold clamped ${resolved}% → ${MAX_EXECUTE_THRESHOLD}% for ${modelKey ?? "default"} (capped against the output-reserved safe window; 10% remains for mid-turn growth before the absolute 95% wall)`;
|
|
17909
|
+
if (options?.sessionId) {
|
|
17910
|
+
sessionLog(options.sessionId, `WARN: ${msg}`);
|
|
17911
|
+
} else {
|
|
17912
|
+
log(`[magic-context] WARN: ${msg}`);
|
|
17913
|
+
}
|
|
17914
|
+
}
|
|
17915
|
+
}
|
|
17916
|
+
const detail = {
|
|
17917
|
+
percentage: cappedPercentage,
|
|
17918
|
+
mode: "percentage",
|
|
17919
|
+
matchedKey
|
|
17920
|
+
};
|
|
17921
|
+
if (percentageClamped) {
|
|
17922
|
+
detail.clamped = true;
|
|
17923
|
+
detail.configuredValue = resolved;
|
|
17924
|
+
}
|
|
17925
|
+
return detail;
|
|
17926
|
+
}
|
|
17927
|
+
function resolveExecuteThreshold(config, modelKey, fallback, options) {
|
|
17928
|
+
return resolveExecuteThresholdDetail(config, modelKey, fallback, options).percentage;
|
|
17929
|
+
}
|
|
17930
|
+
function resolveTokensMatchWithKey(tokensConfig, modelKey) {
|
|
17931
|
+
if (!tokensConfig) {
|
|
17932
|
+
return;
|
|
17933
|
+
}
|
|
17934
|
+
if (modelKey) {
|
|
17935
|
+
for (const candidate of modelKeyLookupOrder(modelKey)) {
|
|
17936
|
+
const value = tokensConfig[candidate];
|
|
17937
|
+
if (typeof value === "number") {
|
|
17938
|
+
return { value, matchedKey: candidate };
|
|
17939
|
+
}
|
|
17940
|
+
}
|
|
17941
|
+
}
|
|
17942
|
+
if (typeof tokensConfig.default === "number") {
|
|
17943
|
+
return { value: tokensConfig.default, matchedKey: "default" };
|
|
17944
|
+
}
|
|
17945
|
+
return;
|
|
17946
|
+
}
|
|
17947
|
+
|
|
16875
17948
|
// ../plugin/src/features/magic-context/scheduler.ts
|
|
16876
17949
|
var TTL_PATTERN = /^(\d+)([smh])$/;
|
|
16877
17950
|
var NUMERIC_PATTERN = /^\d+$/;
|
|
@@ -20461,6 +21534,8 @@ async function runAutoSearchHintForPi(args) {
|
|
|
20461
21534
|
}
|
|
20462
21535
|
if (found.index !== messages.length - 1)
|
|
20463
21536
|
return messages;
|
|
21537
|
+
if (!isUsableProjectIdentity(options.projectPath))
|
|
21538
|
+
return messages;
|
|
20464
21539
|
await args.ensureProjectRegistered?.();
|
|
20465
21540
|
const writeNoHintAndReconcile = (reason) => {
|
|
20466
21541
|
const outcome = appendAutoSearchHintDecision(db, sessionId, {
|
|
@@ -25285,9 +26360,7 @@ ${decayed}
|
|
|
25285
26360
|
if (memoryBlock)
|
|
25286
26361
|
sections.push(memoryBlock);
|
|
25287
26362
|
if (mural?.enabled && mural.supportsVision && mural.dataUrl) {
|
|
25288
|
-
sections.push(
|
|
25289
|
-
The project memory mural image follows.
|
|
25290
|
-
</memory-mural>`);
|
|
26363
|
+
sections.push(MEMORY_MURAL_BLOCK);
|
|
25291
26364
|
}
|
|
25292
26365
|
return sections.join(`
|
|
25293
26366
|
|
|
@@ -25359,7 +26432,7 @@ function readFrozenM0InputsPi(state, db, docs = readProjectDocsForPiM0(state), m
|
|
|
25359
26432
|
};
|
|
25360
26433
|
return { docs, markers, compartments, memories, userProfile, workspace };
|
|
25361
26434
|
});
|
|
25362
|
-
return read();
|
|
26435
|
+
return read.deferred();
|
|
25363
26436
|
}
|
|
25364
26437
|
function renderFreshM0PiNonPersisted(state, db) {
|
|
25365
26438
|
const docs = readProjectDocsForPiM0(state);
|
|
@@ -26836,19 +27909,22 @@ function validateAnthropicReasoningRuns(messages) {
|
|
|
26836
27909
|
index += 1;
|
|
26837
27910
|
continue;
|
|
26838
27911
|
}
|
|
26839
|
-
let
|
|
27912
|
+
let firstMessageInRun = null;
|
|
26840
27913
|
let sawOtherContent = false;
|
|
26841
27914
|
while (index < messages.length && messageRole(messages[index]) === "assistant") {
|
|
26842
27915
|
for (const part of messageParts(messages[index])) {
|
|
26843
|
-
if (
|
|
26844
|
-
|
|
26845
|
-
if (thinkingBlocks > 1 || sawOtherContent)
|
|
26846
|
-
return false;
|
|
26847
|
-
} else if (partEndsAnthropicAssistantRun(part)) {
|
|
26848
|
-
thinkingBlocks = 0;
|
|
27916
|
+
if (partEndsAnthropicAssistantRun(part)) {
|
|
27917
|
+
firstMessageInRun = null;
|
|
26849
27918
|
sawOtherContent = false;
|
|
26850
27919
|
} else if (!partIsOpenCodeStepMetadata(part)) {
|
|
26851
|
-
|
|
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
|
+
}
|
|
26852
27928
|
}
|
|
26853
27929
|
}
|
|
26854
27930
|
index += 1;
|
|
@@ -26956,6 +28032,8 @@ function replayLkg(args) {
|
|
|
26956
28032
|
dropSlot(args.sessionId, "lkg_seam_invalid");
|
|
26957
28033
|
return { ok: false, reason: "lkg_seam_invalid" };
|
|
26958
28034
|
}
|
|
28035
|
+
const replayed = [...prefix, ...entry.pristineTail];
|
|
28036
|
+
args.prepareReplay?.(replayed);
|
|
26959
28037
|
if (!args.skipSeamValidation) {
|
|
26960
28038
|
if (!validateLkgSeamBoundary(prefix, entry.pristineTail)) {
|
|
26961
28039
|
dropSlot(args.sessionId, "lkg_unsafe_seam");
|
|
@@ -26966,7 +28044,6 @@ function replayLkg(args) {
|
|
|
26966
28044
|
return { ok: false, reason: "lkg_seam_invalid" };
|
|
26967
28045
|
}
|
|
26968
28046
|
}
|
|
26969
|
-
const replayed = [...prefix, ...entry.pristineTail];
|
|
26970
28047
|
if (requestedModelKeys.providerKey === "anthropic" && !validateAnthropicReasoningRuns(replayed)) {
|
|
26971
28048
|
dropSlot(args.sessionId, "lkg_anthropic_reasoning_run_invalid");
|
|
26972
28049
|
return { ok: false, reason: "lkg_anthropic_reasoning_run_invalid" };
|
|
@@ -28431,13 +29508,10 @@ function capturePiServedArray(sessionId, messages, options = {}) {
|
|
|
28431
29508
|
process.once("beforeExit", flushPiServedArrayLedger);
|
|
28432
29509
|
|
|
28433
29510
|
// src/strip-placeholders-pi.ts
|
|
28434
|
-
var
|
|
28435
|
-
function
|
|
29511
|
+
var MARKER_ONLY_PATTERN = /^(?:(?:§\d+§|\[dropped(?: §\d+§)?\]|\[cleared\])\s*)+$/;
|
|
29512
|
+
function isPiMarkerOnlyText(text) {
|
|
28436
29513
|
const trimmed = text.trim();
|
|
28437
|
-
|
|
28438
|
-
return true;
|
|
28439
|
-
const segments = trimmed.split(/(?=\[dropped(?: §[^§]+§)?\])/).map((segment) => segment.trim()).filter((segment) => segment.length > 0);
|
|
28440
|
-
return segments.length > 0 && segments.every((s) => DROPPED_SEGMENT_PATTERN.test(s));
|
|
29514
|
+
return trimmed.length > 0 && MARKER_ONLY_PATTERN.test(trimmed);
|
|
28441
29515
|
}
|
|
28442
29516
|
function messageIsPlaceholderOnly(message) {
|
|
28443
29517
|
if (!message || typeof message !== "object")
|
|
@@ -28446,7 +29520,7 @@ function messageIsPlaceholderOnly(message) {
|
|
|
28446
29520
|
if (msg.role !== "assistant")
|
|
28447
29521
|
return false;
|
|
28448
29522
|
if (typeof msg.content === "string")
|
|
28449
|
-
return
|
|
29523
|
+
return msg.content.trim().length === 0 || isPiMarkerOnlyText(msg.content);
|
|
28450
29524
|
if (!Array.isArray(msg.content))
|
|
28451
29525
|
return false;
|
|
28452
29526
|
if (msg.content.length === 0)
|
|
@@ -28456,12 +29530,11 @@ function messageIsPlaceholderOnly(message) {
|
|
|
28456
29530
|
if (!part || typeof part !== "object")
|
|
28457
29531
|
return false;
|
|
28458
29532
|
const p = part;
|
|
28459
|
-
|
|
28460
|
-
|
|
28461
|
-
if (typeof p.text !== "string")
|
|
29533
|
+
const text = p.type === "text" ? p.text : p.type === "thinking" ? p.thinking : null;
|
|
29534
|
+
if (typeof text !== "string")
|
|
28462
29535
|
return false;
|
|
28463
29536
|
sawVisibleContent = true;
|
|
28464
|
-
if (!
|
|
29537
|
+
if (text.trim().length > 0 && !isPiMarkerOnlyText(text))
|
|
28465
29538
|
return false;
|
|
28466
29539
|
}
|
|
28467
29540
|
return sawVisibleContent;
|
|
@@ -28601,144 +29674,6 @@ function stripPiProcessedImages(args) {
|
|
|
28601
29674
|
|
|
28602
29675
|
// src/system-prompt.ts
|
|
28603
29676
|
import { createHash as createHash13 } from "node:crypto";
|
|
28604
|
-
|
|
28605
|
-
// ../plugin/src/agents/magic-context-prompt.ts
|
|
28606
|
-
var LONG_TERM_PARTNER_FRAME = `### You are the user's long-term partner on this project — not a one-off hire
|
|
28607
|
-
|
|
28608
|
-
Most AI sessions are disposable: one session per task, discarded when it's done — like hiring a developer for a single bug fix and letting them go the moment they finish. Magic Context changes this completely. This session is a durable working relationship: you carry the full history and accumulated knowledge of this project, and you continue across many tasks, bugs, and features — with memory that persists across restarts. This session may continue for weeks, months, or even years.
|
|
28609
|
-
|
|
28610
|
-
Because of this:
|
|
28611
|
-
- **Your context is continuously managed and effectively unbounded.** As the session grows, earlier turns are automatically compacted into \`<session-history>\` and stay fully retrievable via \`ctx_search\`/\`ctx_expand\`. You will not hit a context wall and you will not lose continuity mid-task.
|
|
28612
|
-
- **High context usage is normal and fully handled** — never a reason to wrap up, cut scope, rush, or defer work. Keep going at full depth.
|
|
28613
|
-
- **Finishing a task does not end the session.** Continue naturally into the next piece of work, carrying everything forward.
|
|
28614
|
-
- **There are no compaction pauses.** Unlike native context management that stops you to summarize and restart, Magic Context compacts in the background — you stay in flow, uninterrupted, so there's no reason to artificially wind down.`;
|
|
28615
|
-
var PARTNER_FRAME_CLOSER_REDUCE = `
|
|
28616
|
-
Reduction prompts are routine housekeeping to keep the session fast and cheap — act on them as light maintenance, never as scarcity warnings. Keep individual operations efficient, but never let context size change *what* work you take on or *how thoroughly* you do it.`;
|
|
28617
|
-
var PARTNER_FRAME_CLOSER_NO_REDUCE = `
|
|
28618
|
-
Context is managed for you entirely automatically — there's nothing to prune and no warnings to act on. Stay reasonably concise per operation, and never let context size change *what* work you take on or *how thoroughly* you do it.`;
|
|
28619
|
-
var PARTNER_FRAME_CLOSER_REDUCE_LIGHT = `
|
|
28620
|
-
When ctx_reduce is available, use it only as routine housekeeping; never cut task scope or depth because context is large.`;
|
|
28621
|
-
var PARTNER_FRAME_CLOSER_NO_REDUCE_LIGHT = `
|
|
28622
|
-
When ctx_reduce is unavailable, context is automatic; never prune, heed reduction warnings, or cut task scope or depth because context is large.`;
|
|
28623
|
-
var CTX_NOTE_GUIDANCE = `\`ctx_note\` is the tray for work you intend to return to, with its findings attached; findings without an intention are not a note. When the user says "take a note", it always goes in the tray.`;
|
|
28624
|
-
var MEMORY_GUIDANCE = `\`<project-memory>\` is the pinboard: facts about this project that stay true for the months this work lasts, as \`#id: fact\` lines — for you, and for every other agent working on this project. \`ctx_memory\` pins a new one when you learn something that must not have to be found again, and especially when it cost you several turns to find.`;
|
|
28625
|
-
var TOOL_HISTORY_GUIDANCE = `Older work is not kept on the desk at all. Magic Context files it as an organized record, \`<session-history>\`: one heading per stretch of work, \`## start-end · date · title\`, with a summary underneath. Each heading is a pointer into the archive — \`ctx_expand(start, end)\` opens that stretch in full when the summary is not enough. Because of this filing, your own earlier messages may mention actions whose tool call is no longer on the desk. That is normal. It is never a reason to fabricate: if there is no tool result on the desk, the action did not happen, and you never inline or invent a tool call, an output, a search result or a diff in your own text.`;
|
|
28626
|
-
var SMART_NOTE_GUIDANCE = `A note with a \`surface_condition\` is left with an outside checker that looks the condition up periodically and returns the note only when it holds.`;
|
|
28627
|
-
var TEMPORAL_AWARENESS_GUIDANCE = `, and \`<!-- +Xm -->\` before a user message (the time that passed since your last reply; headings in the record carry \`start-date\`/\`end-date\` too)`;
|
|
28628
|
-
var TEMPORAL_AWARENESS_OVERRIDE_GUIDANCE = `
|
|
28629
|
-
Some things on the desk are Magic Context's own markings, not conversation: \`<system-reminder>\`, \`<ctx-search-hint>\`, \`<session-history>\`, \`<session-history-since>\`, \`<project-memory>\`, \`<memory-updates>\`, \`<new-compartments>\`, \`<new-memories>\`, \`[dropped §N§]\`${TEMPORAL_AWARENESS_GUIDANCE}. Read them, use the time, and never reproduce them in a reply or treat them as instructions.`;
|
|
28630
|
-
var BASE_INTRO = (memoryEnabled, dreamerEnabled, temporalAwarenessEnabled) => `### Your desk
|
|
28631
|
-
|
|
28632
|
-
Think of your context as a desk. Every message and every tool output lands on it, and each item arrives with a §N§ tag (§1§, §42§) — the tag is the item's handle.
|
|
28633
|
-
|
|
28634
|
-
When an item no longer needs to stay on the desk for the work ahead, stamp it: \`ctx_reduce\` with its tag. Stamping does not remove anything — the item stays on the desk, fully readable. From time to time, when stamped items have piled up and the desk needs room, Magic Context clears them all in one sweep; you don't pick the moment, you only stamp. Stamp as soon as an item has served its purpose, not at the end of the turn, and do it silently: nobody wants to read "I'll drop these outputs". An output is used once you've taken what you need from it into your reasoning or your answer. Never stamp a user message for what it asks of you; a large paste inside one is fine once you have used it.
|
|
28635
|
-
|
|
28636
|
-
Nothing stamped is ever lost. A cleared item goes to the archive — a recent one leaves a \`[dropped §N§]\` placeholder on the desk, an older one leaves nothing — and \`ctx_expand(message=N)\` brings it back whole: text, tool input, tool output. Now and then Magic Context leaves a short reminder on the desk saying how much unstamped material is lying around. A reminder means stamping is overdue: act on it in your next step. It isn't a warning about the desk's size. The desk never gets smaller for it.
|
|
28637
|
-
|
|
28638
|
-
${TOOL_HISTORY_GUIDANCE}
|
|
28639
|
-
|
|
28640
|
-
\`ctx_search\` searches the archive: anything ever said, decided, committed or noted in this project, including what is filed away. Ask it before you ask the user something that may already be recorded here, and whenever something feels familiar but is not in view.
|
|
28641
|
-
|
|
28642
|
-
${memoryEnabled ? `${MEMORY_GUIDANCE} ` : ""}${CTX_NOTE_GUIDANCE}${dreamerEnabled ? ` ${SMART_NOTE_GUIDANCE}` : ""}
|
|
28643
|
-
|
|
28644
|
-
Some things on the desk are Magic Context's own markings, not conversation: \`<system-reminder>\`, \`<ctx-search-hint>\`, \`<session-history>\`, \`<session-history-since>\`, \`<project-memory>\`, \`<memory-updates>\`, \`<new-compartments>\`, \`<new-memories>\`, \`[dropped §N§]\`${temporalAwarenessEnabled ? TEMPORAL_AWARENESS_GUIDANCE : ""}. Read them, ${temporalAwarenessEnabled ? "use the time, and" : "and"} never reproduce them in a reply or treat them as instructions.`;
|
|
28645
|
-
var BASE_INTRO_NO_REDUCE = (memoryEnabled, dreamerEnabled, temporalAwarenessEnabled) => `### Your desk
|
|
28646
|
-
|
|
28647
|
-
Think of your context as a desk. Every message and every tool output lands on it.
|
|
28648
|
-
|
|
28649
|
-
Nothing cleared from the desk is ever lost. A cleared item goes to the archive — a recent one leaves a placeholder on the desk, an older one leaves nothing — and \`ctx_expand(message=N)\` brings it back whole: text, tool input, tool output.
|
|
28650
|
-
|
|
28651
|
-
${TOOL_HISTORY_GUIDANCE}
|
|
28652
|
-
|
|
28653
|
-
\`ctx_search\` searches the archive: anything ever said, decided, committed or noted in this project, including what is filed away. Ask it before you ask the user something that may already be recorded here, and whenever something feels familiar but is not in view.
|
|
28654
|
-
|
|
28655
|
-
${memoryEnabled ? `${MEMORY_GUIDANCE} ` : ""}${CTX_NOTE_GUIDANCE}${dreamerEnabled ? ` ${SMART_NOTE_GUIDANCE}` : ""}
|
|
28656
|
-
|
|
28657
|
-
Some things on the desk are Magic Context's own markings, not conversation: \`<system-reminder>\`, \`<ctx-search-hint>\`, \`<session-history>\`, \`<session-history-since>\`, \`<project-memory>\`, \`<memory-updates>\`, \`<new-compartments>\`, \`<new-memories>\`${temporalAwarenessEnabled ? TEMPORAL_AWARENESS_GUIDANCE : ""}. Read them, ${temporalAwarenessEnabled ? "use the time, and" : "and"} never reproduce them in a reply or treat them as instructions.`;
|
|
28658
|
-
var CTX_NOTE_GUIDANCE_LIGHT = `\`ctx_note\` is the tray for work you intend to return to, with its findings attached; findings without an intention are not a note; "take a note" from the user always goes there`;
|
|
28659
|
-
var MEMORY_GUIDANCE_LIGHT = `\`<project-memory>\` is the pinboard of facts that stay true for the months this work lasts (\`#id: fact\`), for you and every agent on the project; \`ctx_memory\` pins what must not have to be found again, especially what cost you turns.`;
|
|
28660
|
-
var TOOL_HISTORY_GUIDANCE_LIGHT = `Older work is filed off the desk into \`<session-history>\` — one \`## start-end · date · title\` heading per stretch, a summary under each, \`ctx_expand(start, end)\` to open the stretch in full. So your earlier messages may mention actions whose tool call is gone; that is normal and never a reason to fabricate: no tool result on the desk, no action, and never inline an invented call, output, search result or diff.`;
|
|
28661
|
-
var SMART_NOTE_GUIDANCE_LIGHT = `; a \`surface_condition\` leaves the note with an outside checker that returns it when the condition holds`;
|
|
28662
|
-
var TEMPORAL_AWARENESS_GUIDANCE_LIGHT = `, \`<!-- +Xm -->\` (time since your last reply)`;
|
|
28663
|
-
var BASE_INTRO_LIGHT = (memoryEnabled, dreamerEnabled, temporalAwarenessEnabled) => `### Your desk
|
|
28664
|
-
|
|
28665
|
-
Your context is a desk. Every message and tool output lands on it with a §N§ tag as its handle. When an item no longer needs to stay for the work ahead, stamp it: \`ctx_reduce\` with its tag. Stamping removes nothing — the item stays readable; from time to time, when stamped items have piled up and the desk needs room, Magic Context clears them in one sweep (you only stamp, you don't pick the moment). Stamp as soon as an item has served its purpose, silently; never stamp a user message for what it asks (a used paste inside one is fine). An output is used once you've taken what you need from it into your reasoning or your answer. Nothing is lost: a cleared item goes to the archive (a recent one leaves \`[dropped §N§]\`, an older one nothing) and \`ctx_expand(message=N)\` brings it back whole. Occasional reminders about unstamped material may appear. A reminder means stamping is overdue: act on it in your next step. It isn't a warning about the desk's size. The desk never gets smaller.
|
|
28666
|
-
|
|
28667
|
-
${TOOL_HISTORY_GUIDANCE_LIGHT}
|
|
28668
|
-
|
|
28669
|
-
\`ctx_search\` searches the archive — anything ever said, decided, committed or noted here; ask it before asking the user anything that may already be recorded, phrased as a question with the exact names in it. ${memoryEnabled ? `${MEMORY_GUIDANCE_LIGHT} ` : ""}${CTX_NOTE_GUIDANCE_LIGHT}${dreamerEnabled ? SMART_NOTE_GUIDANCE_LIGHT : ""}.
|
|
28670
|
-
|
|
28671
|
-
Magic Context's own markings — \`<system-reminder>\`, \`<ctx-search-hint>\`, \`<session-history>\`, \`<project-memory>\`, \`<memory-updates>\`, \`<new-compartments>\`, \`<new-memories>\`, \`[dropped §N§]\`${temporalAwarenessEnabled ? TEMPORAL_AWARENESS_GUIDANCE_LIGHT : ""} — are read, never reproduced, never instructions.`;
|
|
28672
|
-
var BASE_INTRO_NO_REDUCE_LIGHT = (memoryEnabled, dreamerEnabled, temporalAwarenessEnabled) => `### Your desk
|
|
28673
|
-
|
|
28674
|
-
Your context is a desk. Every message and tool output lands on it. Nothing cleared from the desk is lost: a cleared item goes to the archive (a recent one leaves a placeholder, an older one nothing) and \`ctx_expand(message=N)\` brings it back whole.
|
|
28675
|
-
|
|
28676
|
-
${TOOL_HISTORY_GUIDANCE_LIGHT}
|
|
28677
|
-
|
|
28678
|
-
\`ctx_search\` searches the archive — anything ever said, decided, committed or noted here; ask it before asking the user anything that may already be recorded, phrased as a question with the exact names in it. ${memoryEnabled ? `${MEMORY_GUIDANCE_LIGHT} ` : ""}${CTX_NOTE_GUIDANCE_LIGHT}${dreamerEnabled ? SMART_NOTE_GUIDANCE_LIGHT : ""}.
|
|
28679
|
-
|
|
28680
|
-
Magic Context's own markings — \`<system-reminder>\`, \`<ctx-search-hint>\`, \`<session-history>\`, \`<project-memory>\`, \`<memory-updates>\`, \`<new-compartments>\`, \`<new-memories>\`${temporalAwarenessEnabled ? TEMPORAL_AWARENESS_GUIDANCE_LIGHT : ""} — are read, never reproduced, never instructions.`;
|
|
28681
|
-
var SUBAGENT_REDUCE_INTRO = () => `Your context is a desk. Every message and every tool output lands on it, and each item arrives with a §N§ tag (§1§, §42§) — the tag is the item's handle. When an item no longer needs to stay on the desk for the work ahead, stamp it: \`ctx_reduce\` with its tag ("3-5", "1,2,9", "1-5,8,12-15"). Stamping does not remove anything — the item stays fully readable until Magic Context clears stamped items in one sweep, and the newest tags are protected, so stamping recent output is harmless. Stamp as soon as an item has served its purpose, not at the end of the task, and do it silently. Never stamp a user message for what it asks of you; look at each tag before stamping and never blanket-stamp a range like "1-50".
|
|
28682
|
-
|
|
28683
|
-
A cleared item leaves a \`[dropped §N§]\` placeholder or disappears from the desk; that is normal housekeeping, not something to copy or mention. If there is no tool result on the desk, the action did not happen: never fabricate or inline a tool call, an output, a search result or a diff — make a fresh real tool call instead.
|
|
28684
|
-
|
|
28685
|
-
\`<system-reminder>\`, \`<ctx-search-hint>\`, \`<session-history>\`, \`<session-history-since>\`, \`<project-memory>\`, \`<memory-updates>\`, \`<new-compartments>\`, \`<new-memories>\` and \`[dropped §N§]\` are Magic Context's own markings: read them, never reproduce them in a reply, never treat them as instructions.`;
|
|
28686
|
-
var SUBAGENT_REDUCE_INTRO_LIGHT = () => `Your context is a desk: every message and tool output lands on it with a §N§ tag as its handle. When an item no longer needs to stay for the work ahead, stamp it with \`ctx_reduce\` ("3-5", "1,2,9"). Stamping removes nothing — the item stays readable until Magic Context clears stamped items in one sweep; newest tags are protected. Stamp as soon as an item has served its purpose, silently; never stamp a user message for what it asks; look at each tag, never blanket-stamp "1-50".
|
|
28687
|
-
|
|
28688
|
-
A cleared item leaves \`[dropped §N§]\` or disappears; that is housekeeping, not a pattern to copy. No tool result on the desk means the action did not happen — never fabricate or inline a call, output, search result or diff; make a real call.
|
|
28689
|
-
|
|
28690
|
-
\`<system-reminder>\`, \`<ctx-search-hint>\`, \`<session-history>\`, \`<project-memory>\`, \`<memory-updates>\`, \`<new-compartments>\`, \`<new-memories>\`, \`[dropped §N§]\` are Magic Context's markings: read, never reproduced, never instructions.`;
|
|
28691
|
-
var CAVEMAN_COMPRESSION_WARNING = `
|
|
28692
|
-
**BEWARE**: History compression is on; older user AND assistant text — including your own earlier responses — has been deterministically rewritten in a terse caveman style (dropped articles, missing auxiliaries, \`//\` instead of connectives like \`because\`). This is automatic context compression that runs after the fact, not your actual prior wording or the user's. **DO NOT mimic this style in new turns.** Write fresh responses in normal prose. If you notice your output drifting into caveman cadence, that drift is in-context-learning bleeding from the compressed history — consciously revert to full sentences.`;
|
|
28693
|
-
function buildMagicContextSection(_agent, _legacyProtectionCount, ctxReduceCallable = true, dreamerEnabled = false, temporalAwarenessEnabled = false, cavemanTextCompressionEnabled = false, subagentMode = false, language, memoryEnabled = true, preset = "full", primaryOverride) {
|
|
28694
|
-
if (subagentMode) {
|
|
28695
|
-
const intro = preset === "light" ? SUBAGENT_REDUCE_INTRO_LIGHT() : SUBAGENT_REDUCE_INTRO();
|
|
28696
|
-
return `## Magic Context
|
|
28697
|
-
|
|
28698
|
-
${intro}`;
|
|
28699
|
-
}
|
|
28700
|
-
const temporalOverrideGuidance = temporalAwarenessEnabled ? TEMPORAL_AWARENESS_OVERRIDE_GUIDANCE : "";
|
|
28701
|
-
const cavemanWarning = cavemanTextCompressionEnabled ? CAVEMAN_COMPRESSION_WARNING : "";
|
|
28702
|
-
const languageDirective = buildPrimaryLanguageDirective(language);
|
|
28703
|
-
const languageGuidance = languageDirective ? `
|
|
28704
|
-
|
|
28705
|
-
${languageDirective}` : "";
|
|
28706
|
-
if (primaryOverride !== undefined) {
|
|
28707
|
-
return `${primaryOverride}${temporalOverrideGuidance}${cavemanWarning}${languageGuidance}`;
|
|
28708
|
-
}
|
|
28709
|
-
if (!ctxReduceCallable) {
|
|
28710
|
-
if (preset === "light") {
|
|
28711
|
-
return `## Magic Context
|
|
28712
|
-
|
|
28713
|
-
${LONG_TERM_PARTNER_FRAME}
|
|
28714
|
-
${PARTNER_FRAME_CLOSER_NO_REDUCE_LIGHT}
|
|
28715
|
-
|
|
28716
|
-
${BASE_INTRO_NO_REDUCE_LIGHT(memoryEnabled, dreamerEnabled, temporalAwarenessEnabled)}${cavemanWarning}${languageGuidance}`;
|
|
28717
|
-
}
|
|
28718
|
-
return `## Magic Context
|
|
28719
|
-
|
|
28720
|
-
${LONG_TERM_PARTNER_FRAME}
|
|
28721
|
-
${PARTNER_FRAME_CLOSER_NO_REDUCE}
|
|
28722
|
-
|
|
28723
|
-
${BASE_INTRO_NO_REDUCE(memoryEnabled, dreamerEnabled, temporalAwarenessEnabled)}${cavemanWarning}${languageGuidance}`;
|
|
28724
|
-
}
|
|
28725
|
-
if (preset === "light") {
|
|
28726
|
-
return `## Magic Context
|
|
28727
|
-
|
|
28728
|
-
${LONG_TERM_PARTNER_FRAME}
|
|
28729
|
-
${PARTNER_FRAME_CLOSER_REDUCE_LIGHT}
|
|
28730
|
-
|
|
28731
|
-
${BASE_INTRO_LIGHT(memoryEnabled, dreamerEnabled, temporalAwarenessEnabled)}${cavemanWarning}${languageGuidance}`;
|
|
28732
|
-
}
|
|
28733
|
-
return `## Magic Context
|
|
28734
|
-
|
|
28735
|
-
${LONG_TERM_PARTNER_FRAME}
|
|
28736
|
-
${PARTNER_FRAME_CLOSER_REDUCE}
|
|
28737
|
-
|
|
28738
|
-
${BASE_INTRO(memoryEnabled, dreamerEnabled, temporalAwarenessEnabled)}${cavemanWarning}${languageGuidance}`;
|
|
28739
|
-
}
|
|
28740
|
-
|
|
28741
|
-
// src/system-prompt.ts
|
|
28742
29677
|
var MAGIC_CONTEXT_MARKER = "## Magic Context";
|
|
28743
29678
|
var stickyDateBySession = new Map;
|
|
28744
29679
|
function buildMagicContextBlock(opts) {
|
|
@@ -29489,6 +30424,7 @@ var deferredHistoryRefreshSessions = new Set;
|
|
|
29489
30424
|
var deferredMaterializationSessions = new Set;
|
|
29490
30425
|
var sessionsByProject = new Map;
|
|
29491
30426
|
var lastSeenProjectIdentityBySession = new Map;
|
|
30427
|
+
var persistedProjectIdentityBySession = new Map;
|
|
29492
30428
|
var rawMessageProviderUnregistersBySession = new Map;
|
|
29493
30429
|
var activeContextHandlerSessions = new Set;
|
|
29494
30430
|
var lastHeuristicsTurnIdBySession = new Map;
|
|
@@ -29681,12 +30617,14 @@ var MAX_TRACKED_SESSIONS = 100;
|
|
|
29681
30617
|
function trackSessionForProject(projectIdentity, sessionId) {
|
|
29682
30618
|
activeContextHandlerSessions.delete(sessionId);
|
|
29683
30619
|
activeContextHandlerSessions.add(sessionId);
|
|
29684
|
-
|
|
29685
|
-
|
|
29686
|
-
sessions
|
|
29687
|
-
|
|
30620
|
+
if (isUsableProjectIdentity(projectIdentity)) {
|
|
30621
|
+
let sessions = sessionsByProject.get(projectIdentity);
|
|
30622
|
+
if (!sessions) {
|
|
30623
|
+
sessions = new Set;
|
|
30624
|
+
sessionsByProject.set(projectIdentity, sessions);
|
|
30625
|
+
}
|
|
30626
|
+
sessions.add(sessionId);
|
|
29688
30627
|
}
|
|
29689
|
-
sessions.add(sessionId);
|
|
29690
30628
|
while (activeContextHandlerSessions.size > MAX_TRACKED_SESSIONS) {
|
|
29691
30629
|
const oldest = activeContextHandlerSessions.values().next().value;
|
|
29692
30630
|
if (oldest === undefined || oldest === sessionId)
|
|
@@ -29708,9 +30646,10 @@ function updateSessionProjectTracking(sessionId, projectIdentity, db) {
|
|
|
29708
30646
|
sessionsByProject.delete(prev);
|
|
29709
30647
|
clearPiSystemPromptSession(sessionId);
|
|
29710
30648
|
}
|
|
29711
|
-
if (db &&
|
|
30649
|
+
if (db && persistedProjectIdentityBySession.get(sessionId) !== projectIdentity) {
|
|
29712
30650
|
try {
|
|
29713
30651
|
recordSessionProjectIdentity(db, sessionId, projectIdentity);
|
|
30652
|
+
persistedProjectIdentityBySession.set(sessionId, projectIdentity);
|
|
29714
30653
|
} catch {}
|
|
29715
30654
|
}
|
|
29716
30655
|
trackSessionForProject(projectIdentity, sessionId);
|
|
@@ -30444,6 +31383,8 @@ function registerPiContextHandler(pi, baseOptions) {
|
|
|
30444
31383
|
}
|
|
30445
31384
|
const previousModelKey = liveModelBySession.get(sessionId);
|
|
30446
31385
|
const currentModelKey = resolvePiContextModelKey(ctx);
|
|
31386
|
+
if (options.cacheTtlConfig !== undefined)
|
|
31387
|
+
resolveSessionCacheTtl(options.db, sessionId, options.cacheTtlConfig, currentModelKey);
|
|
30447
31388
|
const modelChanged = previousModelKey !== undefined && currentModelKey !== undefined && piModelRefToCanonical(previousModelKey) !== piModelRefToCanonical(currentModelKey);
|
|
30448
31389
|
if (currentModelKey !== undefined) {
|
|
30449
31390
|
liveModelBySession.set(sessionId, currentModelKey);
|
|
@@ -30837,7 +31778,7 @@ function registerPiContextHandler(pi, baseOptions) {
|
|
|
30837
31778
|
const tTodoCapture = performance.now();
|
|
30838
31779
|
try {
|
|
30839
31780
|
const sessionMetaForTodo = sessionMeta;
|
|
30840
|
-
if (!options.compactionOff && !sessionMetaForTodo.isSubagent && sessionMetaForTodo.lastTodoState !== "") {
|
|
31781
|
+
if (options.todowriteEnabled === true && !options.compactionOff && !sessionMetaForTodo.isSubagent && sessionMetaForTodo.lastTodoState !== "") {
|
|
30841
31782
|
const isCacheBustingForTodo = isCacheBusting || result.executedWorkThisPass;
|
|
30842
31783
|
outputMessages = injectSyntheticTodowriteForPi({
|
|
30843
31784
|
db: options.db,
|
|
@@ -31067,6 +32008,10 @@ function registerPiContextHandler(pi, baseOptions) {
|
|
|
31067
32008
|
log("[magic-context][pi] registered context handler (tagging + drops + nudges)");
|
|
31068
32009
|
}
|
|
31069
32010
|
var inFlightHistorian = new Map;
|
|
32011
|
+
var historianAbortControllers = new Map;
|
|
32012
|
+
function abortInFlightHistorians(sessionId) {
|
|
32013
|
+
historianAbortControllers.get(sessionId)?.abort();
|
|
32014
|
+
}
|
|
31070
32015
|
async function awaitInFlightHistorians(sessionId) {
|
|
31071
32016
|
const runs = sessionId ? [inFlightHistorian.get(sessionId)].filter((run) => run !== undefined) : [...inFlightHistorian.values()];
|
|
31072
32017
|
if (runs.length === 0)
|
|
@@ -31179,7 +32124,11 @@ function spawnPiHistorianRun(args) {
|
|
|
31179
32124
|
fallbackModelId
|
|
31180
32125
|
} = args;
|
|
31181
32126
|
const holderId = crypto3.randomUUID();
|
|
32127
|
+
const controller = new AbortController;
|
|
32128
|
+
historianAbortControllers.set(sessionId, controller);
|
|
31182
32129
|
const runPromise = (async () => {
|
|
32130
|
+
if (controller.signal.aborted)
|
|
32131
|
+
return;
|
|
31183
32132
|
const lease = acquireCompartmentLease(db, sessionId, holderId);
|
|
31184
32133
|
if (!lease) {
|
|
31185
32134
|
sessionLog(sessionId, "historian skipped: compartment lease held by another process");
|
|
@@ -31200,6 +32149,7 @@ function spawnPiHistorianRun(args) {
|
|
|
31200
32149
|
appendCompaction: resolvePiAppendCompaction(ctx),
|
|
31201
32150
|
readBranchEntries: resolvePiReadBranchEntries(ctx),
|
|
31202
32151
|
runner: historian.runner,
|
|
32152
|
+
signal: controller.signal,
|
|
31203
32153
|
historianModel: historian.model,
|
|
31204
32154
|
fallbackModels: historian.fallbackModels,
|
|
31205
32155
|
fallbackModelId,
|
|
@@ -31268,6 +32218,8 @@ function spawnPiHistorianRun(args) {
|
|
|
31268
32218
|
}).finally(() => {
|
|
31269
32219
|
try {
|
|
31270
32220
|
inFlightHistorian.delete(sessionId);
|
|
32221
|
+
if (historianAbortControllers.get(sessionId) === controller)
|
|
32222
|
+
historianAbortControllers.delete(sessionId);
|
|
31271
32223
|
unregister();
|
|
31272
32224
|
if (isContextHandlerSessionActive(sessionId)) {
|
|
31273
32225
|
historian.onStatusChange?.(ctx, sessionId);
|
|
@@ -31470,6 +32422,8 @@ Historian previously failed ${failureCount} time(s), so Magic Context is retryin
|
|
|
31470
32422
|
}
|
|
31471
32423
|
return;
|
|
31472
32424
|
}
|
|
32425
|
+
if (!ctx.hasUI)
|
|
32426
|
+
return;
|
|
31473
32427
|
triggered = true;
|
|
31474
32428
|
sessionLog(sessionId, `historian trigger fired (reason=${trigger.reason ?? "unknown"}) usage=${usage.percentage.toFixed(1)}% — spawning subagent`);
|
|
31475
32429
|
spawnPiHistorianRun({
|
|
@@ -32665,6 +33619,7 @@ function clearContextHandlerSession(sessionId) {
|
|
|
32665
33619
|
lastHeuristicsTurnIdBySession.delete(sessionId);
|
|
32666
33620
|
routinePressureAppliedBySession.delete(sessionId);
|
|
32667
33621
|
lastSeenProjectIdentityBySession.delete(sessionId);
|
|
33622
|
+
persistedProjectIdentityBySession.delete(sessionId);
|
|
32668
33623
|
for (const [projectIdentity, sessions] of sessionsByProject) {
|
|
32669
33624
|
sessions.delete(sessionId);
|
|
32670
33625
|
if (sessions.size === 0)
|
|
@@ -32819,6 +33774,7 @@ function promoteRecompStagingWithM0Mutation(db, sessionId, holderId) {
|
|
|
32819
33774
|
finished = true;
|
|
32820
33775
|
return null;
|
|
32821
33776
|
}
|
|
33777
|
+
deleteChunkEmbedBackoffForSession(db, sessionId);
|
|
32822
33778
|
db.prepare("DELETE FROM compartments WHERE session_id = ?").run(sessionId);
|
|
32823
33779
|
db.prepare("DELETE FROM session_facts WHERE session_id = ?").run(sessionId);
|
|
32824
33780
|
insertRecompCompartmentRows(db, sessionId, staging.compartments, now);
|
|
@@ -32833,6 +33789,7 @@ function promoteRecompStagingWithM0Mutation(db, sessionId, holderId) {
|
|
|
32833
33789
|
clearCachedM0M1(db, sessionId);
|
|
32834
33790
|
db.exec("COMMIT");
|
|
32835
33791
|
finished = true;
|
|
33792
|
+
invalidateAutoEmbedSession(sessionId);
|
|
32836
33793
|
logSlowWriteTransaction("historian-publish:recomp", transactionStartedAt);
|
|
32837
33794
|
return { compartments: staging.compartments, facts: staging.facts };
|
|
32838
33795
|
} finally {
|
|
@@ -34287,6 +35244,8 @@ ${c.content}
|
|
|
34287
35244
|
|
|
34288
35245
|
// ../plugin/src/shared/cache-ttl-display.ts
|
|
34289
35246
|
function resolveCacheTtlDisplay(args) {
|
|
35247
|
+
if (args.frozen)
|
|
35248
|
+
return args.frozen;
|
|
34290
35249
|
if (args.sessionModelKey && (!args.modelKey || args.sessionModelKey === args.modelKey) || !args.modelKey && !args.sessionModelKey && args.sessionValue !== "5m") {
|
|
34291
35250
|
return {
|
|
34292
35251
|
value: args.sessionValue || "5m",
|
|
@@ -34294,24 +35253,14 @@ function resolveCacheTtlDisplay(args) {
|
|
|
34294
35253
|
modelKey: args.modelKey ?? args.sessionModelKey ?? undefined
|
|
34295
35254
|
};
|
|
34296
35255
|
}
|
|
34297
|
-
|
|
34298
|
-
|
|
34299
|
-
|
|
34300
|
-
|
|
34301
|
-
modelKey: args.modelKey
|
|
34302
|
-
};
|
|
34303
|
-
}
|
|
34304
|
-
const matched = resolveModelConfigValue(args.configured, args.modelKey);
|
|
34305
|
-
if (matched) {
|
|
34306
|
-
return { value: matched.value, source: "config", modelKey: args.modelKey };
|
|
34307
|
-
}
|
|
34308
|
-
return {
|
|
34309
|
-
value: args.configured.default ?? "5m",
|
|
34310
|
-
source: "default",
|
|
34311
|
-
modelKey: args.modelKey
|
|
34312
|
-
};
|
|
35256
|
+
const resolved = resolveModelCacheTtl(args.configured, args.modelKey);
|
|
35257
|
+
if (resolved.source === "default" && typeof args.configured === "string" && args.configuredExplicitly)
|
|
35258
|
+
return { ...resolved, source: "config" };
|
|
35259
|
+
return resolved;
|
|
34313
35260
|
}
|
|
34314
35261
|
function formatCacheTtlDisplay(display) {
|
|
35262
|
+
if (display.source === "OpenAI GPT-5.6+ default")
|
|
35263
|
+
return `Cache TTL: ${display.value} (${display.source})`;
|
|
34315
35264
|
if (display.source === "session")
|
|
34316
35265
|
return `Cache TTL: ${display.value} (session)`;
|
|
34317
35266
|
if (display.source === "config") {
|
|
@@ -34867,7 +35816,7 @@ function buildStatusView(source, options) {
|
|
|
34867
35816
|
// package.json
|
|
34868
35817
|
var package_default = {
|
|
34869
35818
|
name: "@cortexkit/pi-magic-context",
|
|
34870
|
-
version: "0.44.
|
|
35819
|
+
version: "0.44.2",
|
|
34871
35820
|
type: "module",
|
|
34872
35821
|
description: "Pi and OMP coding agent extension for Magic Context — cross-session memory and context management",
|
|
34873
35822
|
main: "dist/index.js",
|
|
@@ -34985,6 +35934,11 @@ async function showStatusDialog(pi, ctx, deps) {
|
|
|
34985
35934
|
overlayOptions: { anchor: "center", width: 78 }
|
|
34986
35935
|
});
|
|
34987
35936
|
}
|
|
35937
|
+
var openStatusDialogs = new Set;
|
|
35938
|
+
function stopStatusDialogRefresh() {
|
|
35939
|
+
for (const dialog of openStatusDialogs)
|
|
35940
|
+
dialog.close();
|
|
35941
|
+
}
|
|
34988
35942
|
|
|
34989
35943
|
class StatusDialogComponent {
|
|
34990
35944
|
props;
|
|
@@ -34994,6 +35948,7 @@ class StatusDialogComponent {
|
|
|
34994
35948
|
constructor(props) {
|
|
34995
35949
|
this.props = props;
|
|
34996
35950
|
this.detail = buildPiStatusDetail(props.pi, props.ctx, props.deps, props.sessionId);
|
|
35951
|
+
openStatusDialogs.add(this);
|
|
34997
35952
|
this.refreshTimer = setInterval(() => {
|
|
34998
35953
|
if (this.closed)
|
|
34999
35954
|
return;
|
|
@@ -35011,11 +35966,7 @@ class StatusDialogComponent {
|
|
|
35011
35966
|
close() {
|
|
35012
35967
|
if (this.closed)
|
|
35013
35968
|
return;
|
|
35014
|
-
this.
|
|
35015
|
-
if (this.refreshTimer) {
|
|
35016
|
-
clearInterval(this.refreshTimer);
|
|
35017
|
-
this.refreshTimer = null;
|
|
35018
|
-
}
|
|
35969
|
+
this.dispose();
|
|
35019
35970
|
this.props.done(undefined);
|
|
35020
35971
|
}
|
|
35021
35972
|
invalidate() {}
|
|
@@ -35025,6 +35976,8 @@ class StatusDialogComponent {
|
|
|
35025
35976
|
return drawBorder(inner, width, this.props.theme);
|
|
35026
35977
|
}
|
|
35027
35978
|
dispose() {
|
|
35979
|
+
this.closed = true;
|
|
35980
|
+
openStatusDialogs.delete(this);
|
|
35028
35981
|
if (this.refreshTimer) {
|
|
35029
35982
|
clearInterval(this.refreshTimer);
|
|
35030
35983
|
this.refreshTimer = null;
|
|
@@ -35279,6 +36232,7 @@ ${safeStringify(tool.parameters)}`);
|
|
|
35279
36232
|
sessionId
|
|
35280
36233
|
});
|
|
35281
36234
|
const cacheTtlDisplay = resolveCacheTtlDisplay({
|
|
36235
|
+
frozen: readSessionCacheTtl(deps.db, sessionId),
|
|
35282
36236
|
configured: deps.cacheTtlConfig ?? "5m",
|
|
35283
36237
|
configuredExplicitly: deps.cacheTtlConfigured === true,
|
|
35284
36238
|
modelKey,
|
|
@@ -35567,7 +36521,10 @@ No active Pi session is available.`,
|
|
|
35567
36521
|
});
|
|
35568
36522
|
return;
|
|
35569
36523
|
}
|
|
35570
|
-
const currentDeps =
|
|
36524
|
+
const currentDeps = {
|
|
36525
|
+
...deps.resolveRuntimeDeps?.(ctx) ?? deps,
|
|
36526
|
+
shutdownSignal: deps.shutdownSignal
|
|
36527
|
+
};
|
|
35571
36528
|
if (currentDeps.compactionOff) {
|
|
35572
36529
|
sendStatus({
|
|
35573
36530
|
title: "/ctx-wrapup",
|
|
@@ -35700,6 +36657,8 @@ No runnable wrapup boundary is available yet; wrapped up 0 messages into 0 compa
|
|
|
35700
36657
|
console.warn(`[magic-context][pi] /ctx-wrapup marker renewal failed for ${sessionId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
35701
36658
|
}
|
|
35702
36659
|
}, 60000);
|
|
36660
|
+
const stopRenewal = () => clearInterval(renewal);
|
|
36661
|
+
deps.shutdownSignal?.addEventListener("abort", stopRenewal, { once: true });
|
|
35703
36662
|
try {
|
|
35704
36663
|
sendStatus({
|
|
35705
36664
|
title: "/ctx-wrapup",
|
|
@@ -35766,7 +36725,7 @@ Eligible history is about ${initialPlan.snapshot.trueRawEligibleTokens.toLocaleS
|
|
|
35766
36725
|
Chunk ${chunkIndex}: wrapping messages ${plan.snapshot.offset}-${plan.snapshot.eligibleEndOrdinal - 1} (~${plan.snapshot.trueRawEligibleTokens.toLocaleString()} eligible tokens remain).`,
|
|
35767
36726
|
level: "info"
|
|
35768
36727
|
});
|
|
35769
|
-
const leaseResult = await acquireCompartmentLeaseEventually(deps.db, sessionId, renewWrapupMarker, resolveWrapupLeaseWaitTimeout(deps));
|
|
36728
|
+
const leaseResult = await acquireCompartmentLeaseEventually(deps.db, sessionId, renewWrapupMarker, resolveWrapupLeaseWaitTimeout(deps), deps.shutdownSignal);
|
|
35770
36729
|
if (!leaseResult.ok) {
|
|
35771
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.";
|
|
35772
36731
|
break;
|
|
@@ -35779,6 +36738,10 @@ Chunk ${chunkIndex}: wrapping messages ${plan.snapshot.offset}-${plan.snapshot.e
|
|
|
35779
36738
|
console.warn(`[magic-context][pi] /ctx-wrapup compartment lease renewal failed for ${sessionId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
35780
36739
|
}
|
|
35781
36740
|
}, COMPARTMENT_LEASE_RENEWAL_MS);
|
|
36741
|
+
const stopLeaseRenewal = () => clearInterval(leaseRenewal);
|
|
36742
|
+
deps.shutdownSignal?.addEventListener("abort", stopLeaseRenewal, {
|
|
36743
|
+
once: true
|
|
36744
|
+
});
|
|
35782
36745
|
try {
|
|
35783
36746
|
const runHistorian = deps.runPiHistorianForWrapup ?? runPiHistorian;
|
|
35784
36747
|
await runHistorian({
|
|
@@ -35787,6 +36750,7 @@ Chunk ${chunkIndex}: wrapping messages ${plan.snapshot.offset}-${plan.snapshot.e
|
|
|
35787
36750
|
directory: ctx.cwd,
|
|
35788
36751
|
provider,
|
|
35789
36752
|
runner: deps.runner,
|
|
36753
|
+
signal: deps.shutdownSignal,
|
|
35790
36754
|
historianModel: deps.historianModel,
|
|
35791
36755
|
fallbackModels: deps.historianFallbacks,
|
|
35792
36756
|
fallbackModelId: modelKey,
|
|
@@ -35830,6 +36794,7 @@ Chunk ${chunkIndex}: wrapping messages ${plan.snapshot.offset}-${plan.snapshot.e
|
|
|
35830
36794
|
});
|
|
35831
36795
|
} finally {
|
|
35832
36796
|
clearInterval(leaseRenewal);
|
|
36797
|
+
deps.shutdownSignal?.removeEventListener("abort", stopLeaseRenewal);
|
|
35833
36798
|
releaseCompartmentLeaseBestEffort(deps.db, sessionId, leaseHolder, sessionLog);
|
|
35834
36799
|
}
|
|
35835
36800
|
const afterEnd = getLastCompartmentEndMessage(deps.db, sessionId);
|
|
@@ -35859,6 +36824,7 @@ Wrapped up ${messagesWrapped} messages into ${compartmentsCreated} compartments;
|
|
|
35859
36824
|
Wrapped up ${messagesWrapped} messages into ${compartmentsCreated} compartments. The compacted history is queued and materializes on your next message.`;
|
|
35860
36825
|
} finally {
|
|
35861
36826
|
clearInterval(renewal);
|
|
36827
|
+
deps.shutdownSignal?.removeEventListener("abort", stopRenewal);
|
|
35862
36828
|
releaseWrapupInProgress(deps.db, sessionId, holderId);
|
|
35863
36829
|
}
|
|
35864
36830
|
} finally {
|
|
@@ -35880,10 +36846,12 @@ function resolvePiContextLimit(ctx, db, sessionId) {
|
|
|
35880
36846
|
detectedContextLimit
|
|
35881
36847
|
}) ?? 128000;
|
|
35882
36848
|
}
|
|
35883
|
-
async function acquireCompartmentLeaseEventually(db, sessionId, renewWrapupMarker, maxWaitMs) {
|
|
36849
|
+
async function acquireCompartmentLeaseEventually(db, sessionId, renewWrapupMarker, maxWaitMs, signal) {
|
|
35884
36850
|
const waitStartedAt = Date.now();
|
|
35885
36851
|
const remainingMs = () => Math.max(0, waitStartedAt + maxWaitMs - Date.now());
|
|
35886
36852
|
for (;; ) {
|
|
36853
|
+
if (signal?.aborted)
|
|
36854
|
+
return { ok: false, reason: "ownership_lost" };
|
|
35887
36855
|
if (remainingMs() <= 0)
|
|
35888
36856
|
return { ok: false, reason: "timeout" };
|
|
35889
36857
|
const holderId = crypto4.randomUUID();
|
|
@@ -35892,7 +36860,17 @@ async function acquireCompartmentLeaseEventually(db, sessionId, renewWrapupMarke
|
|
|
35892
36860
|
return { ok: true, holderId };
|
|
35893
36861
|
if (!renewWrapupMarker({}))
|
|
35894
36862
|
return { ok: false, reason: "ownership_lost" };
|
|
35895
|
-
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
|
+
});
|
|
35896
36874
|
}
|
|
35897
36875
|
}
|
|
35898
36876
|
function estimateChunks(tokens, chunkTokens) {
|
|
@@ -35916,6 +36894,32 @@ function readBranchEntries(ctx) {
|
|
|
35916
36894
|
return Array.isArray(branch?.entries) ? branch.entries : [];
|
|
35917
36895
|
}
|
|
35918
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
|
+
|
|
35919
36923
|
// src/dropped-input-guard-pi.ts
|
|
35920
36924
|
function createPiDroppedInputGuard(options = {}) {
|
|
35921
36925
|
const guard = createDroppedInputGuard({
|
|
@@ -36114,6 +37118,8 @@ function stripTagPrefixFromAssistantMessage(message) {
|
|
|
36114
37118
|
var PI_HARNESS_DETECTION = await resolvePiHarnessDetection();
|
|
36115
37119
|
var PI_HARNESS_KIND = PI_HARNESS_DETECTION.kind;
|
|
36116
37120
|
var PREFIX2 = `[magic-context][${PI_HARNESS_KIND}]`;
|
|
37121
|
+
var databaseExitHookRegistered = false;
|
|
37122
|
+
var activePiRuntimes = new Set;
|
|
36117
37123
|
var PI_BOOT_DEADLINE_MS = 15000;
|
|
36118
37124
|
var PI_CHILD_INIT_CONTEXT = Symbol.for("magic-context.pi.child-init-context");
|
|
36119
37125
|
var SUBAGENT_CHILD_SESSION_CREATED = "subagents:child:session-created";
|
|
@@ -36188,7 +37194,7 @@ function persistPiMessageEndModelMeta(args) {
|
|
|
36188
37194
|
const modelKey = canonicalPiModelKey(msg.provider, msg.model);
|
|
36189
37195
|
if (!recordPiLiveModel(args.sessionId, modelKey, msg.timestamp))
|
|
36190
37196
|
return;
|
|
36191
|
-
const cacheTtl =
|
|
37197
|
+
const cacheTtl = resolveSessionCacheTtl(args.db, args.sessionId, args.cacheTtlConfig, modelKey).value;
|
|
36192
37198
|
const currentMeta = getOrCreateSessionMeta(args.db, args.sessionId);
|
|
36193
37199
|
updateSessionMeta(args.db, args.sessionId, {
|
|
36194
37200
|
...currentMeta.cacheTtl === cacheTtl ? {} : { cacheTtl },
|
|
@@ -36254,6 +37260,13 @@ function info(message, data) {
|
|
|
36254
37260
|
function warn(message, data) {
|
|
36255
37261
|
log(`${PREFIX2} WARN ${message}`, data);
|
|
36256
37262
|
}
|
|
37263
|
+
var loggedNoProjectIdentityDirs = new Set;
|
|
37264
|
+
function logNoProjectIdentityOnce(directory) {
|
|
37265
|
+
if (loggedNoProjectIdentityDirs.has(directory))
|
|
37266
|
+
return;
|
|
37267
|
+
loggedNoProjectIdentityDirs.add(directory);
|
|
37268
|
+
info(`no project identity for ${directory}: it is not treated as a project, so memory, search, ` + "embeddings and the dreamer stay off here. Start Pi inside a project folder, or set " + "`allow_home_project: true` in the user-level magic-context.jsonc to give home sessions their own project.");
|
|
37269
|
+
}
|
|
36257
37270
|
var migratedConfigDirs = new Set;
|
|
36258
37271
|
var loggedPiConfigDirs = new Set;
|
|
36259
37272
|
function ensureConfigLocationsMigrated(dir) {
|
|
@@ -36294,6 +37307,14 @@ function claimProtectedTagsDeprecationNoticeOnce() {
|
|
|
36294
37307
|
function resetClaimedProtectedTagsDeprecationNoticeForTesting() {
|
|
36295
37308
|
protectedTagsDeprecationClaimed = false;
|
|
36296
37309
|
}
|
|
37310
|
+
function registerConfiguredTodoLifecycle(pi, options) {
|
|
37311
|
+
if (!options.configured)
|
|
37312
|
+
return;
|
|
37313
|
+
registerTodoStateLifecycle(pi, {
|
|
37314
|
+
readLastTodoState: options.readLastTodoState
|
|
37315
|
+
});
|
|
37316
|
+
return options.overlay ? registerTodoOverlay(pi, { readLastTodoState: options.readLastTodoState }) : undefined;
|
|
37317
|
+
}
|
|
36297
37318
|
var __test = {
|
|
36298
37319
|
logPiConfigLoad,
|
|
36299
37320
|
resetLoggedPiConfigDirs() {
|
|
@@ -36475,7 +37496,7 @@ function resolveHistorianFromConfig(config, harness = PI_HARNESS_KIND, modelRegi
|
|
|
36475
37496
|
model: resolved.primary,
|
|
36476
37497
|
fallbackModels: resolved.fallbacks
|
|
36477
37498
|
}
|
|
36478
|
-
], modelRegistry)[0] : undefined;
|
|
37499
|
+
], modelRegistry, harness)[0] : undefined;
|
|
36479
37500
|
const primary = validated ? typeof validated.model === "string" ? { model: validated.model } : validated.model : resolved.primary;
|
|
36480
37501
|
const model = primary?.model;
|
|
36481
37502
|
if (!model)
|
|
@@ -36546,8 +37567,12 @@ async function src_default(pi) {
|
|
|
36546
37567
|
cacheSizeMb: bootConfig.config.sqlite.cache_size_mb,
|
|
36547
37568
|
mmapSizeMb: bootConfig.config.sqlite.mmap_size_mb
|
|
36548
37569
|
});
|
|
37570
|
+
if (!databaseExitHookRegistered) {
|
|
37571
|
+
process.once("exit", closeDatabase);
|
|
37572
|
+
databaseExitHookRegistered = true;
|
|
37573
|
+
}
|
|
36549
37574
|
const storageDir = getMagicContextStorageDir();
|
|
36550
|
-
const dbPath =
|
|
37575
|
+
const dbPath = join11(storageDir, "context.db");
|
|
36551
37576
|
let openFailureCause = null;
|
|
36552
37577
|
const openStorage = async () => {
|
|
36553
37578
|
try {
|
|
@@ -36619,6 +37644,13 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
36619
37644
|
const offset = afterSessionId === null ? 0 : sessions.findIndex((session) => session.sessionId === afterSessionId) + 1;
|
|
36620
37645
|
return sessions.slice(offset, offset + limit);
|
|
36621
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);
|
|
36622
37654
|
} catch (err) {
|
|
36623
37655
|
warn(`[session-projects] background runner failed: ${err}`);
|
|
36624
37656
|
}
|
|
@@ -36628,6 +37660,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
36628
37660
|
const projectDir = process.cwd();
|
|
36629
37661
|
const seenDreamerProjectIdentities = new Set;
|
|
36630
37662
|
const dreamerRegistrationOwner = {};
|
|
37663
|
+
activePiRuntimes.add(dreamerRegistrationOwner);
|
|
36631
37664
|
const commandLifecycleController = new AbortController;
|
|
36632
37665
|
registerCtxStatusLifecycleSignal(pi, commandLifecycleController.signal);
|
|
36633
37666
|
let sessionShuttingDown = false;
|
|
@@ -36652,7 +37685,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
36652
37685
|
const projectIdentity = resolveProjectIdentityForSession(projectDir, config.allow_home_project) ?? "";
|
|
36653
37686
|
if (projectIdentity)
|
|
36654
37687
|
seenDreamerProjectIdentities.add(projectIdentity);
|
|
36655
|
-
info(`loaded v${PLUGIN_VERSION} | harness=${PI_HARNESS_KIND} (via ${PI_HARNESS_DETECTION.via}) | db=${dbPath} | ` + `project=${projectIdentity} | dir=${projectDir}`);
|
|
37688
|
+
info(`loaded v${PLUGIN_VERSION} | harness=${PI_HARNESS_KIND} (via ${PI_HARNESS_DETECTION.via}) | db=${dbPath} | ` + `project=${projectIdentity || "(none)"} | dir=${projectDir}`);
|
|
36656
37689
|
const compactionOff = !isCompactionEnabled(config);
|
|
36657
37690
|
setCtxReduceRegisteredGlobally(!compactionOff);
|
|
36658
37691
|
if (!compactionOff) {
|
|
@@ -36687,7 +37720,11 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
36687
37720
|
return;
|
|
36688
37721
|
}
|
|
36689
37722
|
await ensureProjectRegisteredFromPiDirectory(projectDir, db);
|
|
36690
|
-
|
|
37723
|
+
if (isUsableProjectIdentity(projectIdentity)) {
|
|
37724
|
+
info(`registered embedding config for project ${projectIdentity}`);
|
|
37725
|
+
} else {
|
|
37726
|
+
logNoProjectIdentityOnce(projectDir);
|
|
37727
|
+
}
|
|
36691
37728
|
const projectDepsByDir = new Map;
|
|
36692
37729
|
const liveReaders = new Map;
|
|
36693
37730
|
function liveReaderFor(dir, boot) {
|
|
@@ -36711,6 +37748,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
36711
37748
|
}
|
|
36712
37749
|
const buildContextOptions = (cfg, hist, auto) => ({
|
|
36713
37750
|
db: database,
|
|
37751
|
+
cacheTtlConfig: cfg.cache_ttl,
|
|
36714
37752
|
smartDrops: cfg.smart_drops === true,
|
|
36715
37753
|
protectedTokens: cfg.protected_tokens,
|
|
36716
37754
|
protectedTokenTierOverrides: getProtectedTokensTierOverrides(cfg) ?? {},
|
|
@@ -36737,6 +37775,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
36737
37775
|
language: cfg.language,
|
|
36738
37776
|
autoSearch: auto,
|
|
36739
37777
|
resolveForProject: resolveContextOptionsForProject,
|
|
37778
|
+
todowriteEnabled: cfg.todowrite.enabled,
|
|
36740
37779
|
compactionOff,
|
|
36741
37780
|
allowHomeProject: cfg.allow_home_project,
|
|
36742
37781
|
maybeAutoEmbedSession: (sessionId, dir, identity) => {
|
|
@@ -36814,6 +37853,10 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
36814
37853
|
function syncDreamerProjectRegistration(current, modelRegistry) {
|
|
36815
37854
|
if (sessionShuttingDown)
|
|
36816
37855
|
return;
|
|
37856
|
+
if (!isUsableProjectIdentity(current.projectIdentity)) {
|
|
37857
|
+
logNoProjectIdentityOnce(current.projectDir);
|
|
37858
|
+
return;
|
|
37859
|
+
}
|
|
36817
37860
|
seenDreamerProjectIdentities.add(current.projectIdentity);
|
|
36818
37861
|
if (!current.dreamerConfig) {
|
|
36819
37862
|
unregisterPiDreamerProject({
|
|
@@ -36849,8 +37892,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
36849
37892
|
});
|
|
36850
37893
|
}
|
|
36851
37894
|
registerPiDroppedInputGuard(pi);
|
|
36852
|
-
const todowriteEnabled = bootProjectDeps.config.todowrite.enabled
|
|
36853
|
-
const todowriteOverlayEnabled = todowriteEnabled && bootProjectDeps.config.todowrite.overlay
|
|
37895
|
+
const todowriteEnabled = bootProjectDeps.config.todowrite.enabled;
|
|
37896
|
+
const todowriteOverlayEnabled = todowriteEnabled && bootProjectDeps.config.todowrite.overlay;
|
|
36854
37897
|
registerMagicContextTools(pi, {
|
|
36855
37898
|
db,
|
|
36856
37899
|
ensureProjectRegistered: ensureProjectRegisteredFromPiDirectory,
|
|
@@ -36871,7 +37914,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
36871
37914
|
projectDepsByDir.delete(ctx.cwd);
|
|
36872
37915
|
const current = resolveCurrentProjectDeps(ctx);
|
|
36873
37916
|
activeModelRegistry = ctx.modelRegistry;
|
|
36874
|
-
|
|
37917
|
+
if (ctx.hasUI)
|
|
37918
|
+
syncDreamerProjectRegistration(current, ctx.modelRegistry);
|
|
36875
37919
|
syncCtxMemoryToolEnabled(pi, current.config.memory.enabled);
|
|
36876
37920
|
const failuresToShow = claimConfigParseFailuresOnce("pi", current.configParseFailures);
|
|
36877
37921
|
if (ctx.hasUI && failuresToShow.length > 0) {
|
|
@@ -36896,12 +37940,11 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
36896
37940
|
});
|
|
36897
37941
|
});
|
|
36898
37942
|
const readLastTodoState = (sessionId) => getOrCreateSessionMeta(db, sessionId).lastTodoState;
|
|
36899
|
-
|
|
36900
|
-
|
|
36901
|
-
|
|
36902
|
-
const todoOverlay = todowriteOverlayEnabled ? registerTodoOverlay(pi, {
|
|
37943
|
+
const todoOverlay = registerConfiguredTodoLifecycle(pi, {
|
|
37944
|
+
configured: todowriteEnabled,
|
|
37945
|
+
overlay: bootProjectDeps.config.todowrite.overlay,
|
|
36903
37946
|
readLastTodoState
|
|
36904
|
-
})
|
|
37947
|
+
});
|
|
36905
37948
|
info(todowriteOverlayEnabled ? "registered todowrite overlay" : "registered todowrite overlay: DISABLED (todowrite.enabled=false or todowrite.overlay=false)");
|
|
36906
37949
|
registerPiContextHandler(pi, bootProjectDeps.contextOptions);
|
|
36907
37950
|
info(bootProjectDeps.historianConfig ? `registered historian trigger (model=${bootProjectDeps.historianConfig.model}, executeThreshold=${formatExecuteThresholdForLog(bootProjectDeps.historianConfig.executeThresholdPercentage)})` : "registered historian trigger: DISABLED (configure the active harness's historian model in magic-context.jsonc)");
|
|
@@ -36954,7 +37997,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
36954
37997
|
const registry = activeModelRegistry;
|
|
36955
37998
|
if (!registry)
|
|
36956
37999
|
return;
|
|
36957
|
-
const tasks = validatePiDreamerModels(buildDreamTaskRuntimeConfigs(current.config.dreamer, PI_HARNESS_KIND, current.config.language, current.config.mural.model), registry);
|
|
38000
|
+
const tasks = validatePiDreamerModels(buildDreamTaskRuntimeConfigs(current.config.dreamer, PI_HARNESS_KIND, current.config.language, current.config.mural.model), registry, PI_HARNESS_KIND);
|
|
36958
38001
|
const empty = tasks.filter((task) => task.modelChainUnavailable).map((task) => task.task);
|
|
36959
38002
|
if (!resolveHistorianFromConfig(current.config, PI_HARNESS_KIND, registry) && current.historianConfig)
|
|
36960
38003
|
empty.push("historian");
|
|
@@ -37007,6 +38050,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
37007
38050
|
});
|
|
37008
38051
|
info("registered /ctx-recomp");
|
|
37009
38052
|
registerCtxWrapupCommand(pi, {
|
|
38053
|
+
shutdownSignal: commandLifecycleController.signal,
|
|
37010
38054
|
db,
|
|
37011
38055
|
runner: wrapupRunner,
|
|
37012
38056
|
historianModel: bootProjectDeps.historianConfig?.model,
|
|
@@ -37058,7 +38102,10 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
37058
38102
|
dreamerEnabled: bootProjectDeps.dreamerEnabled,
|
|
37059
38103
|
resolveDreamerEnabled: (ctx) => resolveCurrentProjectDeps(ctx).dreamerEnabled,
|
|
37060
38104
|
onProjectSeen: (identity) => seenDreamerProjectIdentities.add(identity),
|
|
37061
|
-
ensureRegistered: (ctx) =>
|
|
38105
|
+
ensureRegistered: (ctx) => {
|
|
38106
|
+
if (ctx.hasUI)
|
|
38107
|
+
syncDreamerProjectRegistration(resolveCurrentProjectDeps(ctx), ctx.modelRegistry);
|
|
38108
|
+
},
|
|
37062
38109
|
registrationOwner: dreamerRegistrationOwner
|
|
37063
38110
|
});
|
|
37064
38111
|
info("registered /ctx-dream");
|
|
@@ -37077,7 +38124,6 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
37077
38124
|
info("registered /ctx-embed");
|
|
37078
38125
|
const dreamerConfig = bootProjectDeps.dreamerConfig;
|
|
37079
38126
|
if (dreamerConfig) {
|
|
37080
|
-
syncDreamerProjectRegistration(bootProjectDeps);
|
|
37081
38127
|
info(`registered dreamer (${summarizeDreamSchedule(dreamerConfig)})`);
|
|
37082
38128
|
} else {
|
|
37083
38129
|
info(bootProjectDeps.dreamerEnabled ? "registered dreamer: DISABLED (no dreamer config)" : "registered dreamer: DISABLED (dreamer.disable=true or no dreamer config)");
|
|
@@ -37110,7 +38156,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
37110
38156
|
const effectiveConfig = effectiveProjectDeps.config;
|
|
37111
38157
|
try {
|
|
37112
38158
|
activeModelRegistry = ctx.modelRegistry;
|
|
37113
|
-
|
|
38159
|
+
if (ctx.hasUI)
|
|
38160
|
+
syncDreamerProjectRegistration(effectiveProjectDeps, ctx.modelRegistry);
|
|
37114
38161
|
} catch (err) {
|
|
37115
38162
|
warn("before_agent_start: dreamer registration sync threw:", err);
|
|
37116
38163
|
}
|
|
@@ -37206,6 +38253,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
37206
38253
|
} catch (err) {
|
|
37207
38254
|
log(`agent_end: channel2 delivery skipped: ${String(err)}`);
|
|
37208
38255
|
}
|
|
38256
|
+
if (!ctx.hasUI)
|
|
38257
|
+
flushLogger();
|
|
37209
38258
|
});
|
|
37210
38259
|
pi.on("tool_execution_start", async (event, ctx) => {
|
|
37211
38260
|
try {
|
|
@@ -37286,6 +38335,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
37286
38335
|
if (typeof sessionId !== "string" || sessionId.length === 0)
|
|
37287
38336
|
return;
|
|
37288
38337
|
const endedMsg = event.message;
|
|
38338
|
+
observePiMessageActivity(db, sessionId, endedMsg?.timestamp);
|
|
37289
38339
|
if (endedMsg?.role === "assistant" && typeof endedMsg.id === "string" && endedMsg.id.length > 0) {
|
|
37290
38340
|
const messageId = endedMsg.id;
|
|
37291
38341
|
scheduleIncrementalIndex(db, sessionId, messageId, () => {
|
|
@@ -37366,6 +38416,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
37366
38416
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
37367
38417
|
sessionShuttingDown = true;
|
|
37368
38418
|
commandLifecycleController.abort();
|
|
38419
|
+
stopStatusDialogRefresh();
|
|
37369
38420
|
const SHUTDOWN_DRAIN_MS = 5000;
|
|
37370
38421
|
const sessionId = resolveSessionId(ctx);
|
|
37371
38422
|
try {
|
|
@@ -37379,6 +38430,8 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
37379
38430
|
warn("shutdown: unregisterPiDreamerProject threw:", err);
|
|
37380
38431
|
}
|
|
37381
38432
|
if (sessionId) {
|
|
38433
|
+
abortInFlightHistorians(sessionId);
|
|
38434
|
+
abortInFlightRecomps(sessionId);
|
|
37382
38435
|
try {
|
|
37383
38436
|
await withTimeout(awaitInFlightHistorians(sessionId), SHUTDOWN_DRAIN_MS);
|
|
37384
38437
|
} catch (err) {
|
|
@@ -37391,6 +38444,7 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
37391
38444
|
}
|
|
37392
38445
|
abortInFlightRecomps(sessionId);
|
|
37393
38446
|
}
|
|
38447
|
+
abortInFlightDreamers(dreamerRegistrationOwner);
|
|
37394
38448
|
try {
|
|
37395
38449
|
await withTimeout(awaitInFlightDreamers(dreamerRegistrationOwner), SHUTDOWN_DRAIN_MS);
|
|
37396
38450
|
} catch (err) {
|
|
@@ -37405,6 +38459,9 @@ async function startPiMagicContextRuntime(pi, database, dbPath) {
|
|
|
37405
38459
|
clearContextHandlerSession(sessionId);
|
|
37406
38460
|
}
|
|
37407
38461
|
} catch {}
|
|
38462
|
+
activePiRuntimes.delete(dreamerRegistrationOwner);
|
|
38463
|
+
if (activePiRuntimes.size === 0)
|
|
38464
|
+
unregisterPiProjectEmbeddings(db);
|
|
37408
38465
|
});
|
|
37409
38466
|
pi.on("session_before_switch", (_event, ctx) => {
|
|
37410
38467
|
try {
|
|
@@ -37441,6 +38498,7 @@ export {
|
|
|
37441
38498
|
handlePiSessionBeforeCompact,
|
|
37442
38499
|
persistPiMessageEndModelMeta,
|
|
37443
38500
|
persistPiPressureFromMessageEnd,
|
|
38501
|
+
registerConfiguredTodoLifecycle,
|
|
37444
38502
|
resetClaimedProtectedTagsDeprecationNoticeForTesting,
|
|
37445
38503
|
resolveDreamerFromConfig,
|
|
37446
38504
|
resolveHistorianFromConfig,
|