@cortexkit/opencode-magic-context 0.49.6 → 0.49.7
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/auto-search-worker.js +10 -7
- package/dist/{compaction-marker-candidates-cz72pm1t.js → compaction-marker-candidates-a0edbqrr.js} +2 -2
- package/dist/compaction-marker-candidates-worker.js +2 -2
- package/dist/{conflict-warning-hook-1jq1rxx7.js → conflict-warning-hook-myk5hbt9.js} +5 -5
- package/dist/{context-ae3gxdye.js → context-ffr7e0g7.js} +46 -34
- package/dist/{embedding-backfill-dtjmyktb.js → embedding-backfill-re16xncd.js} +6 -6
- package/dist/features/magic-context/compaction-marker.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/hidden-single-shot.d.ts +6 -3
- package/dist/features/magic-context/dreamer/hidden-single-shot.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/map-memories-prompt.d.ts +7 -1
- package/dist/features/magic-context/dreamer/map-memories-prompt.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/map-memories.d.ts +2 -2
- package/dist/features/magic-context/dreamer/map-memories.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/primer-seed.d.ts +2 -0
- package/dist/features/magic-context/dreamer/primer-seed.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/refresh-primers.d.ts +2 -2
- package/dist/features/magic-context/storage-clone.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-persisted.d.ts +10 -3
- package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
- package/dist/features/magic-context/storage-replay-document.d.ts +4 -0
- package/dist/features/magic-context/storage-replay-document.d.ts.map +1 -1
- package/dist/hooks/magic-context/apply-operations.d.ts +7 -0
- package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
- package/dist/hooks/magic-context/heuristic-cleanup.d.ts.map +1 -1
- package/dist/hooks/magic-context/history-boundary-repair.d.ts.map +1 -1
- package/dist/hooks/magic-context/lkg-persist.d.ts.map +1 -1
- package/dist/hooks/magic-context/note-nudger.d.ts +5 -3
- package/dist/hooks/magic-context/note-nudger.d.ts.map +1 -1
- package/dist/hooks/magic-context/postprocess-read-cache.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-chunk.d.ts +7 -0
- package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
- package/dist/hooks/magic-context/replay-committed-drops.d.ts +9 -0
- package/dist/hooks/magic-context/replay-committed-drops.d.ts.map +1 -0
- package/dist/hooks/magic-context/rust-lkg-freeze-registry.d.ts +2 -2
- package/dist/hooks/magic-context/rust-lkg-freeze-registry.d.ts.map +1 -1
- package/dist/hooks/magic-context/rust-mode-transform.d.ts +3 -0
- package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
- package/dist/hooks/magic-context/rust-served-raw.d.ts +29 -0
- package/dist/hooks/magic-context/rust-served-raw.d.ts.map +1 -0
- package/dist/hooks/magic-context/strip-content.d.ts +23 -24
- package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
- package/dist/hooks/magic-context/tag-messages.d.ts +2 -0
- package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
- package/dist/hooks/magic-context/tool-drop-target.d.ts +2 -0
- package/dist/hooks/magic-context/tool-drop-target.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
- package/dist/{index-6q5h70h2.js → index-0gyq3djm.js} +1 -1
- package/dist/{index-vnjhmkwk.js → index-37q80veb.js} +1 -1
- package/dist/{index-besxhcsh.js → index-4btknbqh.js} +9 -8
- package/dist/{index-vh3e2mvr.js → index-5tbvdff4.js} +59 -28
- package/dist/{index-cm4hmxyk.js → index-9yyqq3mm.js} +1 -1
- package/dist/{index-ab3bgm2m.js → index-a8acx4gc.js} +2 -2
- package/dist/{index-j55vn694.js → index-ax2jhztn.js} +1 -1
- package/dist/{index-zgzdm6dg.js → index-ebnngykq.js} +145 -94
- package/dist/{index-g9exahnx.js → index-g8qbdb03.js} +1 -1
- package/dist/{index-ch08qwgd.js → index-j58gqrn2.js} +65 -65
- package/dist/{index-xqyj5c85.js → index-j5hce1ab.js} +2 -2
- package/dist/{index-0nnd4bry.js → index-j8epsc0g.js} +34 -13
- package/dist/{index-vztbgcdk.js → index-n5a170gs.js} +227 -38
- package/dist/{index-s0gsmthp.js → index-n7kwfw2r.js} +4 -4
- package/dist/{index-ck6y3td2.js → index-rwmpkss5.js} +3 -3
- package/dist/{index-bg5qqene.js → index-rzmjkdd6.js} +1 -1
- package/dist/{index-9s58tq95.js → index-sf94at64.js} +3 -3
- package/dist/{index-f3dnvnw6.js → index-sh91t19a.js} +1 -1
- package/dist/{index-mj1fgfrk.js → index-tagss15p.js} +857 -125
- package/dist/{index-1j8jcjpa.js → index-tmtty2x5.js} +1 -1
- package/dist/{index-hr64sth2.js → index-vt4sf4ep.js} +1 -1
- package/dist/{index-9m7bn21d.js → index-w4mw92w3.js} +1 -1
- package/dist/{index-7q1m1qjk.js → index-w6bd03dc.js} +1 -1
- package/dist/{index-a796ct3s.js → index-w6vr754t.js} +4 -4
- package/dist/{index-1xaw2pq8.js → index-wq16ahsn.js} +5 -5
- package/dist/index.js +36 -36
- package/dist/{message-index-bounded-630j53ps.js → message-index-bounded-04rycpx4.js} +9 -9
- package/dist/{message-index-c84wm2g0.js → message-index-rqtse28y.js} +9 -9
- package/dist/{message-time-backfill-8kj57qe3.js → message-time-backfill-a5ytnp53.js} +1 -1
- package/dist/migration-worker.js +6 -6
- package/dist/plugin/messages-transform.d.ts.map +1 -1
- package/dist/{project-embedding-registry-vv59t7ah.js → project-embedding-registry-k0sd9k7v.js} +5 -5
- package/dist/raw-ordinal-warmup-worker.js +17 -15
- package/dist/{read-session-chunk-t3vjd0h0.js → read-session-chunk-h1btx938.js} +7 -5
- package/dist/{read-session-raw-gf7eytnm.js → read-session-raw-vdf4wh78.js} +2 -2
- package/dist/{recomp-orchestrator-1d08g57a.js → recomp-orchestrator-trcgmm23.js} +20 -20
- package/dist/{search-kth2p6cc.js → search-3rbdx6hw.js} +16 -16
- package/dist/{send-session-notification-3j6krd4w.js → send-session-notification-2tp44tfa.js} +4 -4
- package/dist/shared/processed-image-eligibility.d.ts +22 -0
- package/dist/shared/processed-image-eligibility.d.ts.map +1 -0
- package/dist/shared/recovery-reasoning-prefix.d.ts +9 -0
- package/dist/shared/recovery-reasoning-prefix.d.ts.map +1 -0
- package/dist/shared/sqlite.d.ts +1 -1
- package/dist/shared/sqlite.d.ts.map +1 -1
- package/dist/shared/tag-transcript.d.ts +9 -0
- package/dist/shared/tag-transcript.d.ts.map +1 -1
- package/dist/{sqlite-d7edbk1s.js → sqlite-j3tvg8r0.js} +1 -1
- package/dist/{storage-memory-af5zdz98.js → storage-memory-8168bgef.js} +2 -2
- package/dist/{storage-tags-dyj7mmbq.js → storage-tags-61z5j2f9.js} +2 -2
- package/dist/tools/ctx-expand/search-archive.d.ts +4 -0
- package/dist/tools/ctx-expand/search-archive.d.ts.map +1 -0
- package/dist/tools/ctx-expand/tools.d.ts.map +1 -1
- package/dist/{transform-postprocess-phase-bv3kb5s1.js → transform-postprocess-phase-7qts7h54.js} +20 -20
- package/dist/v2/auto-search-worker.js +10 -7
- package/dist/v2/{compaction-marker-candidates-6gqp5wrc.js → compaction-marker-candidates-6nypjkpf.js} +2 -2
- package/dist/v2/compaction-marker-candidates-worker.js +2 -2
- package/dist/v2/{context-p8f2shp1.js → context-xq4rtywr.js} +1019 -233
- package/dist/v2/{embedding-backfill-cg4rv90f.js → embedding-backfill-xd9ygzgd.js} +6 -6
- package/dist/v2/hooks/payload.d.ts.map +1 -1
- package/dist/v2/{message-index-3ws17x1g.js → message-index-8e5z2xx8.js} +9 -9
- package/dist/v2/{message-index-bounded-yzxmf6vp.js → message-index-bounded-5vh1h2g7.js} +9 -9
- package/dist/v2/{message-time-backfill-5nf10c65.js → message-time-backfill-2z365239.js} +1 -1
- package/dist/v2/migration-worker.js +6 -6
- package/dist/v2/{project-embedding-registry-rsnf2263.js → project-embedding-registry-b77awytr.js} +5 -5
- package/dist/v2/raw-ordinal-warmup-worker.js +17 -15
- package/dist/v2/{read-session-chunk-vtb0w2kd.js → read-session-chunk-4sat83cx.js} +7 -5
- package/dist/v2/{read-session-raw-gpcddemt.js → read-session-raw-a3hw9jsp.js} +2 -2
- package/dist/v2/{recomp-orchestrator-v64r4s4r.js → recomp-orchestrator-n11zv1f1.js} +19 -19
- package/dist/v2/{search-bpa4fxq1.js → search-mvgqtkkz.js} +16 -16
- package/dist/v2/{send-session-notification-5aj2j7vf.js → send-session-notification-ppgc8qdz.js} +4 -4
- package/dist/v2/{server-wrem2nmz.js → server-1rfgbjfn.js} +1 -1
- package/dist/v2/{server-qn23p4h7.js → server-7srtwsa4.js} +5 -5
- package/dist/v2/{server-cm4hmxyk.js → server-9yyqq3mm.js} +1 -1
- package/dist/v2/{server-yezz0cp2.js → server-brhj8qtd.js} +1 -1
- package/dist/v2/{server-387vkhjq.js → server-dd0nja67.js} +1 -1
- package/dist/v2/{server-keswwkvv.js → server-fpqe39as.js} +1 -1
- package/dist/v2/{server-158ehmk2.js → server-fw31krgq.js} +3 -3
- package/dist/v2/{server-fby0cxbq.js → server-gtv9wbhq.js} +227 -38
- package/dist/v2/{server-ww9nr8pe.js → server-hcvcxzx7.js} +2 -2
- package/dist/v2/{server-0nnd4bry.js → server-j8epsc0g.js} +34 -13
- package/dist/v2/{server-x2r63c0x.js → server-jdwd03ep.js} +4 -4
- package/dist/v2/{server-btgnsv7w.js → server-kdrezdy0.js} +1 -1
- package/dist/v2/{server-7skbfb81.js → server-mv1w7t6z.js} +61 -30
- package/dist/v2/{server-aa9w15cs.js → server-nfan6zb6.js} +65 -65
- package/dist/v2/{server-9zqewm6g.js → server-qcw43339.js} +1 -1
- package/dist/v2/{server-f9py7s7a.js → server-sy4b1sra.js} +1 -1
- package/dist/v2/{server-09g9pkvx.js → server-v4fwha43.js} +9 -8
- package/dist/v2/{server-yd01snxq.js → server-w3zeywp1.js} +1 -1
- package/dist/v2/{server-4tn95znb.js → server-wkdgjjch.js} +1 -1
- package/dist/v2/{server-6a9mxnb2.js → server-x08d1xf3.js} +1 -1
- package/dist/v2/server.js +4 -4
- package/dist/v2/{sqlite-d7edbk1s.js → sqlite-j3tvg8r0.js} +1 -1
- package/dist/v2/{storage-memory-af5zdz98.js → storage-memory-8168bgef.js} +2 -2
- package/dist/v2/{storage-tags-d47bpaqa.js → storage-tags-j2fzq3sw.js} +2 -2
- package/dist/v2/{wrapup-orchestrator-cahk37n7.js → wrapup-orchestrator-s92ebfzv.js} +19 -19
- package/dist/{wrapup-orchestrator-mqp0v195.js → wrapup-orchestrator-df61jmz9.js} +21 -21
- package/package.json +3 -3
- package/src/shared/processed-image-eligibility.test.ts +68 -0
- package/src/shared/processed-image-eligibility.ts +41 -0
- package/src/shared/processed-image-position-review.json +20 -0
- package/src/shared/processed-image-position-review.test.ts +19 -0
- package/src/shared/recovery-reasoning-prefix.test.ts +34 -0
- package/src/shared/recovery-reasoning-prefix.ts +35 -0
- package/src/shared/sqlite-writer-diagnostics.test.ts +31 -0
- package/src/shared/sqlite.ts +63 -22
- package/src/shared/tag-transcript.ts +118 -0
package/src/shared/sqlite.ts
CHANGED
|
@@ -72,14 +72,21 @@ export function registerSlowWriteReporter(reporter: SlowWriteReporter): void {
|
|
|
72
72
|
*/
|
|
73
73
|
type SqliteDiagnosticSink = (message: string) => void;
|
|
74
74
|
let sqliteDiagnosticSink: SqliteDiagnosticSink | undefined;
|
|
75
|
+
let sqliteDiagnosticHost: () => string = () => "unknown";
|
|
75
76
|
|
|
76
|
-
export function registerSqliteDiagnosticSink(
|
|
77
|
+
export function registerSqliteDiagnosticSink(
|
|
78
|
+
sink: SqliteDiagnosticSink,
|
|
79
|
+
host?: () => string,
|
|
80
|
+
): void {
|
|
77
81
|
sqliteDiagnosticSink = sink;
|
|
82
|
+
if (host) sqliteDiagnosticHost = host;
|
|
78
83
|
}
|
|
79
84
|
|
|
80
85
|
function reportSqliteDiagnostic(message: string): void {
|
|
81
86
|
try {
|
|
82
|
-
sqliteDiagnosticSink?.(
|
|
87
|
+
sqliteDiagnosticSink?.(
|
|
88
|
+
`[magic-context] ${message} pid=${process.pid} host=${sqliteDiagnosticHost()}`,
|
|
89
|
+
);
|
|
83
90
|
} catch {
|
|
84
91
|
// Diagnostics must never change the outcome of the write they describe.
|
|
85
92
|
}
|
|
@@ -393,9 +400,18 @@ function installTransactionRouting(db: BetterSqlite3.Database, readonly: boolean
|
|
|
393
400
|
// A transaction that ended without passing through exec (for
|
|
394
401
|
// example an automatic rollback) must not be timed as this one.
|
|
395
402
|
openWriterTransactions.delete(db);
|
|
396
|
-
if (transformPassScope.getStore()?.active || backgroundWriterScope.getStore())
|
|
403
|
+
if (transformPassScope.getStore()?.active || backgroundWriterScope.getStore()) {
|
|
397
404
|
acquireShort(db, () => nativeExec(sql), sql.trim());
|
|
398
|
-
else
|
|
405
|
+
} else {
|
|
406
|
+
nativeExec(sql);
|
|
407
|
+
openWriterTransactions.set(db, {
|
|
408
|
+
site: sql.trim(),
|
|
409
|
+
lane: "background",
|
|
410
|
+
acquireMs: 0,
|
|
411
|
+
attempts: 1,
|
|
412
|
+
acquiredAt: performance.now(),
|
|
413
|
+
});
|
|
414
|
+
}
|
|
399
415
|
return db;
|
|
400
416
|
}
|
|
401
417
|
if (WRITER_TRANSACTION_END.test(sql) && openWriterTransactions.has(db))
|
|
@@ -943,7 +959,7 @@ const SHORT_BUSY_TIMEOUT_MS = 25;
|
|
|
943
959
|
const FOREGROUND_IN_PASS_BUSY_TIMEOUT_MS = 2_000;
|
|
944
960
|
const FOREGROUND_ACQUISITION_BUDGET_MS = 16_500;
|
|
945
961
|
/** Waiting this long for the writer lock, or holding it this long, is logged. */
|
|
946
|
-
const SLOW_WRITER_LOG_MS =
|
|
962
|
+
const SLOW_WRITER_LOG_MS = 100;
|
|
947
963
|
const WRITER_TRANSACTION_END = /^\s*(?:COMMIT|END|ROLLBACK)(?:\s+TRANSACTION)?\s*;?\s*$/i;
|
|
948
964
|
|
|
949
965
|
type WriterLane = "foreground" | "background";
|
|
@@ -991,25 +1007,33 @@ function foregroundWaitLease() {
|
|
|
991
1007
|
function withAutocommitTimeout<T>(db: Database, operation: () => T): T {
|
|
992
1008
|
assertTransformWrite();
|
|
993
1009
|
const lease = foregroundWaitLease();
|
|
994
|
-
if (isInTransaction(db)
|
|
995
|
-
const
|
|
996
|
-
db
|
|
997
|
-
|
|
998
|
-
|
|
1010
|
+
if (isInTransaction(db)) return operation();
|
|
1011
|
+
const scoped = lease || backgroundWriterScope.getStore();
|
|
1012
|
+
const previous = scoped ? pragmaNumber(db, "busy_timeout") : null;
|
|
1013
|
+
if (scoped)
|
|
1014
|
+
db.exec(
|
|
1015
|
+
`PRAGMA busy_timeout=${lease ? Math.floor(Math.min(lease.remainingWaitMs, lease.guard?.remainingMs() ?? Infinity)) : SHORT_BUSY_TIMEOUT_MS}`,
|
|
1016
|
+
);
|
|
999
1017
|
const started = performance.now();
|
|
1018
|
+
let completed = false;
|
|
1000
1019
|
try {
|
|
1001
|
-
|
|
1020
|
+
const result = operation();
|
|
1021
|
+
completed = true;
|
|
1022
|
+
return result;
|
|
1002
1023
|
} catch (error) {
|
|
1003
1024
|
if (lease && isTransientSqliteError(error))
|
|
1004
1025
|
throw new SqliteAcquisitionBusyError(error, "autocommit");
|
|
1005
1026
|
throw error;
|
|
1006
1027
|
} finally {
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
0,
|
|
1010
|
-
lease.remainingWaitMs - (performance.now() - started),
|
|
1011
|
-
);
|
|
1028
|
+
const elapsed = performance.now() - started;
|
|
1029
|
+
if (lease) lease.remainingWaitMs = Math.max(0, lease.remainingWaitMs - elapsed);
|
|
1012
1030
|
if (previous !== null) db.exec(`PRAGMA busy_timeout=${previous}`);
|
|
1031
|
+
// Native autocommit does not expose the instant its lock was acquired.
|
|
1032
|
+
// Label the upper bound so a waiting statement is not blamed as a holder.
|
|
1033
|
+
if (elapsed >= SLOW_WRITER_LOG_MS)
|
|
1034
|
+
reportSqliteDiagnostic(
|
|
1035
|
+
`sqlite writer site=autocommit lane=${lease ? "foreground" : "background"} elapsed_ms=${Math.round(elapsed)} timing=statement_including_acquisition outcome=${completed ? "completed" : "failed"} caller=${writerCaller()}`,
|
|
1036
|
+
);
|
|
1013
1037
|
}
|
|
1014
1038
|
}
|
|
1015
1039
|
|
|
@@ -1071,20 +1095,18 @@ function acquireShort(db: Database, acquire: () => unknown, site: string): void
|
|
|
1071
1095
|
* `acquireShort`, then report how long the writer waited for the lock
|
|
1072
1096
|
* (`acquire_ms`) and how long it then held it (`hold_ms`) when either crosses
|
|
1073
1097
|
* the threshold. A long hold blocks every other process's writer, so it is
|
|
1074
|
-
* worth seeing even when the lock itself came quickly.
|
|
1075
|
-
*
|
|
1076
|
-
* is not
|
|
1098
|
+
* worth seeing even when the lock itself came quickly. This interval includes
|
|
1099
|
+
* COMMIT: an automatic PASSIVE checkpoint can run after writer-lock release,
|
|
1100
|
+
* so hold_ms is a conservative upper bound, not proof of continuous ownership.
|
|
1077
1101
|
*/
|
|
1078
1102
|
function endWriterTransaction(db: Database, end: () => unknown, sql: string): unknown {
|
|
1079
1103
|
let ended = false;
|
|
1080
|
-
const endStarted = performance.now();
|
|
1081
1104
|
try {
|
|
1082
1105
|
const result = end();
|
|
1083
1106
|
ended = true;
|
|
1084
1107
|
return result;
|
|
1085
1108
|
} finally {
|
|
1086
1109
|
const open = openWriterTransactions.get(db);
|
|
1087
|
-
const endMs = performance.now() - endStarted;
|
|
1088
1110
|
// A COMMIT that failed with the transaction still open has not ended it.
|
|
1089
1111
|
if (open && !isInTransaction(db)) {
|
|
1090
1112
|
openWriterTransactions.delete(db);
|
|
@@ -1096,13 +1118,32 @@ function endWriterTransaction(db: Database, end: () => unknown, sql: string): un
|
|
|
1096
1118
|
? "committed"
|
|
1097
1119
|
: "failed";
|
|
1098
1120
|
reportSqliteDiagnostic(
|
|
1099
|
-
`sqlite writer site=${open.site} lane=${open.lane} acquire_ms=${Math.round(open.acquireMs)} hold_ms=${Math.round(holdMs)}
|
|
1121
|
+
`sqlite writer site=${open.site} lane=${open.lane} acquire_ms=${Math.round(open.acquireMs)} hold_ms=${Math.round(holdMs)} attempts=${open.attempts} outcome=${outcome} timing=through_commit_upper_bound caller=${writerCaller()}`,
|
|
1100
1122
|
);
|
|
1101
1123
|
}
|
|
1102
1124
|
}
|
|
1103
1125
|
}
|
|
1104
1126
|
}
|
|
1105
1127
|
|
|
1128
|
+
// Capture a stack only for slow writes. Synchronous COMMIT retains the writer's
|
|
1129
|
+
// caller, so fast transactions need neither stack allocation nor source mapping.
|
|
1130
|
+
function writerCaller(): string {
|
|
1131
|
+
return (
|
|
1132
|
+
new Error().stack
|
|
1133
|
+
?.split("\n")
|
|
1134
|
+
.find(
|
|
1135
|
+
(line) =>
|
|
1136
|
+
line.includes(" at ") &&
|
|
1137
|
+
!/[/\\]sqlite\.[tj]s[:)]/.test(line) &&
|
|
1138
|
+
!/\b(?:writerCaller|endWriterTransaction|withAutocommitTimeout|value)\b/.test(
|
|
1139
|
+
line,
|
|
1140
|
+
),
|
|
1141
|
+
)
|
|
1142
|
+
?.trim()
|
|
1143
|
+
.replace(/\s+/g, "_") ?? "unknown"
|
|
1144
|
+
);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1106
1147
|
/** Begin an immediate transaction with bounded asynchronous retries on writer contention.
|
|
1107
1148
|
* Callers must recheck any lease or source snapshot after this function returns.
|
|
1108
1149
|
* Resolves with the `performance.now()` time at which BEGIN IMMEDIATE succeeded,
|
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
updateTagTokenCount,
|
|
56
56
|
} from "../features/magic-context/storage-tags";
|
|
57
57
|
import { makeToolCompositeKey, type Tagger } from "../features/magic-context/tagger";
|
|
58
|
+
import type { TagEntry } from "../features/magic-context/types";
|
|
58
59
|
import { droppedInputMarker } from "../hooks/magic-context/dropped-input-guard";
|
|
59
60
|
import { applyEditMarkerToInput } from "../hooks/magic-context/edit-marker";
|
|
60
61
|
import { estimateImageTokensFromDataUrl } from "../hooks/magic-context/image-token-estimate";
|
|
@@ -121,6 +122,122 @@ export interface TagTranscriptResult {
|
|
|
121
122
|
targets: Map<number, TagTarget>;
|
|
122
123
|
}
|
|
123
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Bind durable drops to a saved request without tagging it again. Recovery must
|
|
127
|
+
* not allocate identities, inject prefixes, or write accounting while the store
|
|
128
|
+
* is failing. Reuse the normal targets so persisted modes render identically.
|
|
129
|
+
*/
|
|
130
|
+
export function buildPersistedDropTargets(
|
|
131
|
+
transcript: Transcript,
|
|
132
|
+
tags: readonly TagEntry[],
|
|
133
|
+
options: { allowReasoningPrefixRewrite?: boolean } = {},
|
|
134
|
+
): Map<number, TagTarget> {
|
|
135
|
+
const targets = new Map<number, TagTarget>();
|
|
136
|
+
const tools = new Map(
|
|
137
|
+
tags
|
|
138
|
+
.filter((tag) => tag.type === "tool")
|
|
139
|
+
.map((tag) => [makeToolCompositeKey(tag.toolOwnerMessageId ?? "", tag.messageId), tag]),
|
|
140
|
+
);
|
|
141
|
+
const textTags = new Map(
|
|
142
|
+
tags.filter((tag) => tag.type !== "tool").map((tag) => [tag.messageId, tag]),
|
|
143
|
+
);
|
|
144
|
+
const byNumber = new Map(tags.map((tag) => [tag.tagNumber, tag]));
|
|
145
|
+
const aggregates = new Map<
|
|
146
|
+
string,
|
|
147
|
+
{ tag?: TagEntry; occurrences: ToolOccurrence[]; reasoning: boolean }
|
|
148
|
+
>();
|
|
149
|
+
const pending = new Map<string, string[]>();
|
|
150
|
+
const resolved = new Map<string, string>();
|
|
151
|
+
const ambiguousCalls = new Set<string>();
|
|
152
|
+
const end = new ConversationEndGuard(transcript.messages);
|
|
153
|
+
for (const message of transcript.messages) {
|
|
154
|
+
let ordinal = 0;
|
|
155
|
+
let resultRun: { callId: string; key: string } | undefined;
|
|
156
|
+
const parts = message.parts;
|
|
157
|
+
const reasoning =
|
|
158
|
+
message.requiresToolArcSkeleton ??
|
|
159
|
+
(message.info.role === "assistant" && parts.some((part) => part.kind === "thinking"));
|
|
160
|
+
for (const part of parts) {
|
|
161
|
+
if (part.kind !== "tool_result") resultRun = undefined;
|
|
162
|
+
if (part.kind === "text") {
|
|
163
|
+
const prefix = /^§(\d+)§\s/.exec(part.getText() ?? "");
|
|
164
|
+
const tag =
|
|
165
|
+
(prefix ? byNumber.get(Number(prefix[1])) : undefined) ??
|
|
166
|
+
textTags.get(`${message.info.id}:p${ordinal}`);
|
|
167
|
+
ordinal++;
|
|
168
|
+
if (tag && tag.type !== "tool")
|
|
169
|
+
targets.set(tag.tagNumber, buildTextTarget(part, message, ""));
|
|
170
|
+
} else if ((part.kind === "tool_use" || part.kind === "tool_result") && part.id) {
|
|
171
|
+
const open = pending.get(part.id) ?? [];
|
|
172
|
+
if (open.length > 1) ambiguousCalls.add(part.id);
|
|
173
|
+
const key =
|
|
174
|
+
(part.kind === "tool_result"
|
|
175
|
+
? resultRun?.callId === part.id
|
|
176
|
+
? resultRun.key
|
|
177
|
+
: (open.at(-1) ?? resolved.get(part.id))
|
|
178
|
+
: undefined) ?? makeToolCompositeKey(message.info.id ?? "", part.id);
|
|
179
|
+
if (part.kind === "tool_use") open.push(key);
|
|
180
|
+
else {
|
|
181
|
+
const position = open.lastIndexOf(key);
|
|
182
|
+
if (position >= 0) open.splice(position, 1);
|
|
183
|
+
resultRun = { callId: part.id, key };
|
|
184
|
+
resolved.set(part.id, key);
|
|
185
|
+
}
|
|
186
|
+
pending.set(part.id, open);
|
|
187
|
+
let aggregate = aggregates.get(key);
|
|
188
|
+
if (!aggregate) {
|
|
189
|
+
aggregate = {
|
|
190
|
+
tag: tools.get(key),
|
|
191
|
+
occurrences: [],
|
|
192
|
+
reasoning: false,
|
|
193
|
+
};
|
|
194
|
+
aggregates.set(key, aggregate);
|
|
195
|
+
}
|
|
196
|
+
aggregate.occurrences.push({ message, part, kind: part.kind });
|
|
197
|
+
aggregate.reasoning ||= reasoning;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const arcsByCall = new Map<string, number>();
|
|
202
|
+
for (const { occurrences } of aggregates.values()) {
|
|
203
|
+
const callId = occurrences[0]?.part.id ?? "";
|
|
204
|
+
arcsByCall.set(callId, (arcsByCall.get(callId) ?? 0) + 1);
|
|
205
|
+
}
|
|
206
|
+
for (const aggregate of aggregates.values()) {
|
|
207
|
+
const { occurrences, reasoning } = aggregate;
|
|
208
|
+
const callId = occurrences[0]?.part.id ?? "";
|
|
209
|
+
if (ambiguousCalls.has(callId)) continue;
|
|
210
|
+
const tag =
|
|
211
|
+
aggregate.tag ??
|
|
212
|
+
(arcsByCall.get(callId) === 1
|
|
213
|
+
? tools.get(makeToolCompositeKey("", callId))
|
|
214
|
+
: undefined);
|
|
215
|
+
if (tag)
|
|
216
|
+
targets.set(
|
|
217
|
+
tag.tagNumber,
|
|
218
|
+
buildAggregateTarget(tag.tagNumber, occurrences, reasoning, end),
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
if (!options.allowReasoningPrefixRewrite) {
|
|
222
|
+
// This target builder cannot persist removal of thinking. By default,
|
|
223
|
+
// withhold edits before reasoning rather than invalidate its signature.
|
|
224
|
+
// The Pi recovery handler opts in only to test edits on a saved copy;
|
|
225
|
+
// it must compare final request prefixes and refuse unsafe changes.
|
|
226
|
+
let lastThinking = -1;
|
|
227
|
+
transcript.messages.forEach((message, index) => {
|
|
228
|
+
if (message.parts.some((part) => part.kind === "thinking")) lastThinking = index;
|
|
229
|
+
});
|
|
230
|
+
const protectedIds = new Set(
|
|
231
|
+
transcript.messages.slice(0, lastThinking + 1).map((message) => message.info.id),
|
|
232
|
+
);
|
|
233
|
+
for (const [number, target] of targets) {
|
|
234
|
+
if (target.mutationParts?.some(({ message }) => protectedIds.has(message.info.id)))
|
|
235
|
+
targets.delete(number);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return targets;
|
|
239
|
+
}
|
|
240
|
+
|
|
124
241
|
/**
|
|
125
242
|
* Tag eligible parts of a transcript and build TagTargets for them.
|
|
126
243
|
*
|
|
@@ -1289,6 +1406,7 @@ function buildAggregateTarget(
|
|
|
1289
1406
|
return any ? "truncated" : "absent";
|
|
1290
1407
|
},
|
|
1291
1408
|
// Open invocations still belong to the current turn; only complete arcs reclaim.
|
|
1409
|
+
prepareDrop: () => !occurrences.some((occ) => occ.part.canRemove?.() === "defer"),
|
|
1292
1410
|
canDrop: () => complete() && !occurrences.some((occ) => occ.part.hasUserAnswer?.()),
|
|
1293
1411
|
requiresToolArcSkeleton,
|
|
1294
1412
|
// Non-mutating read of the invocation input (the tool_use occurrence
|