@cortexkit/opencode-magic-context 0.21.7 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +116 -325
- package/dist/agents/magic-context-prompt.d.ts.map +1 -1
- package/dist/agents/permissions.d.ts +29 -14
- package/dist/agents/permissions.d.ts.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/migrate-experimental.d.ts +29 -0
- package/dist/config/migrate-experimental.d.ts.map +1 -0
- package/dist/config/schema/magic-context.d.ts +80 -104
- package/dist/config/schema/magic-context.d.ts.map +1 -1
- package/dist/features/builtin-commands/commands.d.ts.map +1 -1
- package/dist/features/magic-context/compartment-embedding.d.ts +34 -0
- package/dist/features/magic-context/compartment-embedding.d.ts.map +1 -0
- package/dist/features/magic-context/compartment-events.d.ts +50 -0
- package/dist/features/magic-context/compartment-events.d.ts.map +1 -0
- package/dist/features/magic-context/compartment-storage.d.ts +22 -0
- package/dist/features/magic-context/compartment-storage.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/lease.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/queue.d.ts +13 -2
- package/dist/features/magic-context/dreamer/queue.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/runner.d.ts +11 -0
- package/dist/features/magic-context/dreamer/runner.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-prompts.d.ts +1 -1
- package/dist/features/magic-context/dreamer/task-prompts.d.ts.map +1 -1
- package/dist/features/magic-context/git-commits/git-log-reader.d.ts.map +1 -1
- package/dist/features/magic-context/key-files/identify-key-files.d.ts +1 -1
- package/dist/features/magic-context/key-files/identify-key-files.d.ts.map +1 -1
- package/dist/features/magic-context/key-files/project-key-files.d.ts.map +1 -1
- package/dist/features/magic-context/key-files/read-stats.d.ts +1 -1
- package/dist/features/magic-context/key-files/read-stats.d.ts.map +1 -1
- package/dist/features/magic-context/memory/constants.d.ts +4 -0
- package/dist/features/magic-context/memory/constants.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
- package/dist/features/magic-context/memory/index.d.ts +1 -1
- package/dist/features/magic-context/memory/index.d.ts.map +1 -1
- package/dist/features/magic-context/memory/memory-migration.d.ts +133 -0
- package/dist/features/magic-context/memory/memory-migration.d.ts.map +1 -0
- package/dist/features/magic-context/memory/project-identity.d.ts +38 -7
- package/dist/features/magic-context/memory/project-identity.d.ts.map +1 -1
- package/dist/features/magic-context/memory/storage-memory-fts.d.ts.map +1 -1
- package/dist/features/magic-context/memory/storage-memory.d.ts +15 -1
- package/dist/features/magic-context/memory/storage-memory.d.ts.map +1 -1
- package/dist/features/magic-context/memory/types.d.ts +3 -1
- package/dist/features/magic-context/memory/types.d.ts.map +1 -1
- package/dist/features/magic-context/message-index.d.ts.map +1 -1
- package/dist/features/magic-context/migrations.d.ts +7 -0
- package/dist/features/magic-context/migrations.d.ts.map +1 -1
- package/dist/features/magic-context/project-docs-hash.d.ts +6 -0
- package/dist/features/magic-context/project-docs-hash.d.ts.map +1 -0
- package/dist/features/magic-context/project-identity.d.ts +2 -0
- package/dist/features/magic-context/project-identity.d.ts.map +1 -0
- package/dist/features/magic-context/sidekick/agent.d.ts.map +1 -1
- package/dist/features/magic-context/storage-db.d.ts +51 -7
- package/dist/features/magic-context/storage-db.d.ts.map +1 -1
- package/dist/features/magic-context/storage-historian-runs.d.ts +73 -0
- package/dist/features/magic-context/storage-historian-runs.d.ts.map +1 -0
- package/dist/features/magic-context/storage-identity-rekey-map.d.ts +11 -0
- package/dist/features/magic-context/storage-identity-rekey-map.d.ts.map +1 -0
- package/dist/features/magic-context/storage-m0-mutation-log.d.ts +22 -0
- package/dist/features/magic-context/storage-m0-mutation-log.d.ts.map +1 -0
- package/dist/features/magic-context/storage-memory-mutation-log.d.ts +25 -0
- package/dist/features/magic-context/storage-memory-mutation-log.d.ts.map +1 -0
- package/dist/features/magic-context/storage-meta-persisted.d.ts +5 -0
- 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-meta-shared.d.ts +44 -0
- package/dist/features/magic-context/storage-meta-shared.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta.d.ts +2 -1
- package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
- package/dist/features/magic-context/storage-project-state.d.ts +19 -0
- package/dist/features/magic-context/storage-project-state.d.ts.map +1 -0
- package/dist/features/magic-context/storage-subagent-invocations.d.ts +61 -0
- package/dist/features/magic-context/storage-subagent-invocations.d.ts.map +1 -0
- package/dist/features/magic-context/storage-tags.d.ts +21 -1
- package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
- package/dist/features/magic-context/storage-v22-backfill-failures.d.ts +24 -0
- package/dist/features/magic-context/storage-v22-backfill-failures.d.ts.map +1 -0
- package/dist/features/magic-context/storage.d.ts +13 -3
- package/dist/features/magic-context/storage.d.ts.map +1 -1
- package/dist/features/magic-context/subagent-token-capture.d.ts +33 -0
- package/dist/features/magic-context/subagent-token-capture.d.ts.map +1 -0
- package/dist/features/magic-context/tagger.d.ts +15 -1
- package/dist/features/magic-context/tagger.d.ts.map +1 -1
- package/dist/features/magic-context/types.d.ts +21 -0
- package/dist/features/magic-context/types.d.ts.map +1 -1
- package/dist/features/magic-context/user-memory/review-user-memories.d.ts.map +1 -1
- package/dist/features/magic-context/user-memory/storage-user-memory.d.ts.map +1 -1
- package/dist/features/magic-context/v22-deferred-backfill.d.ts +46 -0
- package/dist/features/magic-context/v22-deferred-backfill.d.ts.map +1 -0
- package/dist/features/magic-context/work-metrics.d.ts +79 -0
- package/dist/features/magic-context/work-metrics.d.ts.map +1 -0
- package/dist/hooks/auto-update-checker/cache.d.ts.map +1 -1
- package/dist/hooks/auto-update-checker/checker.d.ts.map +1 -1
- package/dist/hooks/magic-context/cache-busting-signals.d.ts +9 -0
- package/dist/hooks/magic-context/cache-busting-signals.d.ts.map +1 -1
- package/dist/hooks/magic-context/command-handler.d.ts +13 -1
- package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-parser.d.ts +25 -0
- package/dist/hooks/magic-context/compartment-parser.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-prompt.d.ts +27 -16
- package/dist/hooks/magic-context/compartment-prompt.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-historian.d.ts +2 -0
- package/dist/hooks/magic-context/compartment-runner-historian.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-mapping.d.ts +6 -2
- package/dist/hooks/magic-context/compartment-runner-mapping.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-recomp.d.ts +9 -1
- package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-types.d.ts +68 -4
- package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-validation.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
- package/dist/hooks/magic-context/decay-curve.d.ts +78 -0
- package/dist/hooks/magic-context/decay-curve.d.ts.map +1 -0
- package/dist/hooks/magic-context/decay-render.d.ts +67 -0
- package/dist/hooks/magic-context/decay-render.d.ts.map +1 -0
- package/dist/hooks/magic-context/event-handler.d.ts +1 -1
- package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/event-resolvers.d.ts +17 -0
- package/dist/hooks/magic-context/event-resolvers.d.ts.map +1 -1
- package/dist/hooks/magic-context/execute-status.d.ts.map +1 -1
- package/dist/hooks/magic-context/historian-prompt.generated.d.ts +2 -0
- package/dist/hooks/magic-context/historian-prompt.generated.d.ts.map +1 -0
- package/dist/hooks/magic-context/hook-handlers.d.ts +3 -0
- package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook.d.ts +9 -21
- package/dist/hooks/magic-context/hook.d.ts.map +1 -1
- package/dist/hooks/magic-context/inject-compartments.d.ts +126 -0
- package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
- package/dist/hooks/magic-context/key-files-block.d.ts.map +1 -1
- package/dist/hooks/magic-context/live-session-state.d.ts +9 -0
- package/dist/hooks/magic-context/live-session-state.d.ts.map +1 -1
- package/dist/hooks/magic-context/m0-token-breakdown.d.ts +35 -0
- package/dist/hooks/magic-context/m0-token-breakdown.d.ts.map +1 -0
- package/dist/hooks/magic-context/read-session-chunk.d.ts +9 -0
- package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-db.d.ts +7 -0
- package/dist/hooks/magic-context/read-session-db.d.ts.map +1 -1
- package/dist/hooks/magic-context/recomp-orchestrator.d.ts +104 -0
- package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -0
- package/dist/hooks/magic-context/reference-retrieval.d.ts +61 -0
- package/dist/hooks/magic-context/reference-retrieval.d.ts.map +1 -0
- package/dist/hooks/magic-context/reference-seeds.generated.d.ts +8 -0
- package/dist/hooks/magic-context/reference-seeds.generated.d.ts.map +1 -0
- package/dist/hooks/magic-context/send-session-notification.d.ts +1 -1
- package/dist/hooks/magic-context/send-session-notification.d.ts.map +1 -1
- package/dist/hooks/magic-context/system-prompt-hash.d.ts +5 -6
- package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
- package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
- package/dist/hooks/magic-context/tokenizer-calibration.d.ts +6 -0
- package/dist/hooks/magic-context/tokenizer-calibration.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-compartment-phase.d.ts +0 -7
- package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +18 -0
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts +9 -7
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/hooks/magic-context/upgrade-reminder.d.ts +73 -0
- package/dist/hooks/magic-context/upgrade-reminder.d.ts.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22111 -16352
- package/dist/plugin/conflict-warning-hook.d.ts +13 -0
- package/dist/plugin/conflict-warning-hook.d.ts.map +1 -1
- package/dist/plugin/dream-timer.d.ts.map +1 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
- package/dist/plugin/messages-transform.d.ts.map +1 -1
- package/dist/plugin/rpc-handlers.d.ts.map +1 -1
- package/dist/plugin/tool-registry.d.ts.map +1 -1
- package/dist/shared/announcement.d.ts +1 -1
- package/dist/shared/announcement.d.ts.map +1 -1
- package/dist/shared/rpc-client.d.ts +1 -0
- package/dist/shared/rpc-client.d.ts.map +1 -1
- package/dist/shared/rpc-notifications.d.ts +27 -5
- package/dist/shared/rpc-notifications.d.ts.map +1 -1
- package/dist/shared/rpc-server.d.ts +1 -0
- package/dist/shared/rpc-server.d.ts.map +1 -1
- package/dist/shared/rpc-types.d.ts +32 -2
- package/dist/shared/rpc-types.d.ts.map +1 -1
- package/dist/shared/rpc-utils.d.ts +9 -0
- package/dist/shared/rpc-utils.d.ts.map +1 -1
- package/dist/shared/sqlite-helpers.d.ts +7 -7
- package/dist/shared/sqlite.d.ts +23 -14
- package/dist/shared/sqlite.d.ts.map +1 -1
- package/dist/shared/subagent-runner.d.ts +5 -0
- package/dist/shared/subagent-runner.d.ts.map +1 -1
- package/dist/shared/tag-transcript.d.ts +10 -1
- package/dist/shared/tag-transcript.d.ts.map +1 -1
- package/dist/tools/ctx-expand/tools.d.ts +5 -1
- package/dist/tools/ctx-expand/tools.d.ts.map +1 -1
- package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
- package/dist/tui/data/context-db.d.ts +16 -1
- package/dist/tui/data/context-db.d.ts.map +1 -1
- package/package.json +2 -4
- package/src/shared/announcement.ts +6 -7
- package/src/shared/rpc-client.test.ts +49 -2
- package/src/shared/rpc-client.ts +19 -9
- package/src/shared/rpc-notifications.test.ts +54 -1
- package/src/shared/rpc-notifications.ts +82 -13
- package/src/shared/rpc-server.ts +33 -4
- package/src/shared/rpc-types.ts +32 -2
- package/src/shared/rpc-utils.ts +10 -0
- package/src/shared/sqlite-helpers.ts +9 -9
- package/src/shared/sqlite.ts +99 -80
- package/src/shared/subagent-runner.ts +14 -0
- package/src/shared/tag-transcript.test.ts +280 -0
- package/src/shared/tag-transcript.ts +162 -33
- package/src/tui/data/context-db.ts +77 -11
- package/src/tui/index.tsx +240 -57
- package/src/tui/slots/sidebar-content.tsx +415 -101
- package/dist/hooks/magic-context/compartment-runner-compressor.d.ts +0 -87
- package/dist/hooks/magic-context/compartment-runner-compressor.d.ts.map +0 -1
- package/dist/shared/native-binding.d.ts +0 -87
- package/dist/shared/native-binding.d.ts.map +0 -1
- package/src/shared/native-binding.ts +0 -311
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
import type { ContextDatabase } from "../features/magic-context/storage";
|
|
49
49
|
import { saveSourceContent } from "../features/magic-context/storage-source";
|
|
50
50
|
import { updateTagByteSize, updateTagInputByteSize } from "../features/magic-context/storage-tags";
|
|
51
|
-
import type
|
|
51
|
+
import { makeToolCompositeKey, type Tagger } from "../features/magic-context/tagger";
|
|
52
52
|
import {
|
|
53
53
|
byteSize,
|
|
54
54
|
prependTag,
|
|
@@ -67,6 +67,15 @@ export interface TagTranscriptOptions {
|
|
|
67
67
|
* consistent across passes.
|
|
68
68
|
*/
|
|
69
69
|
skipPrefixInjection?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Pi-only: map of messageId → raw-message fingerprint. When a NEW message
|
|
72
|
+
* text tag is created, its fingerprint is persisted on the tag row so a
|
|
73
|
+
* later pass can adopt the fallback-id tag onto the real SessionEntry id
|
|
74
|
+
* (keeping tag_number/§N§ stable). OpenCode omits this → tags store NULL
|
|
75
|
+
* → adoption never fires. Keyed by the bare messageId (not the `:pN`
|
|
76
|
+
* contentId) since all parts of a message share one fingerprint.
|
|
77
|
+
*/
|
|
78
|
+
entryFingerprintByMessageId?: ReadonlyMap<string, string>;
|
|
70
79
|
}
|
|
71
80
|
|
|
72
81
|
export interface TagTranscriptResult {
|
|
@@ -138,13 +147,13 @@ export function tagTranscript(
|
|
|
138
147
|
const skipPrefixInjection = options.skipPrefixInjection === true;
|
|
139
148
|
const targets = new Map<number, TagTarget>();
|
|
140
149
|
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
// chronological tag numbering while still aggregating drop behavior
|
|
146
|
-
// across both invocation and result.
|
|
150
|
+
// Tool aggregation is keyed by the same owner+callId identity used by
|
|
151
|
+
// assignToolTag. OpenCode/Pi callId counters can repeat across turns, so
|
|
152
|
+
// a bare callId key can merge distinct invocations and replay drops/status
|
|
153
|
+
// changes against the wrong tool pair.
|
|
147
154
|
const toolAggregates = new Map<string, ToolAggregate & { tagId: number }>();
|
|
155
|
+
const openToolAggregateKeysByCallId = new Map<string, string[]>();
|
|
156
|
+
let activeToolResultRun: { callId: string; aggregateKey: string } | undefined;
|
|
148
157
|
|
|
149
158
|
// v3.3.1 Layer C (plan v3.3.1 Finding #16): the previous outer
|
|
150
159
|
// db.transaction() wrapper rolled back EVERY tag insert + savedSource
|
|
@@ -156,6 +165,7 @@ export function tagTranscript(
|
|
|
156
165
|
for (let msgIndex = 0; msgIndex < transcript.messages.length; msgIndex += 1) {
|
|
157
166
|
const message = transcript.messages[msgIndex];
|
|
158
167
|
if (message === undefined) continue;
|
|
168
|
+
activeToolResultRun = undefined;
|
|
159
169
|
const messageId = message.info.id;
|
|
160
170
|
|
|
161
171
|
let textOrdinal = 0;
|
|
@@ -165,6 +175,10 @@ export function tagTranscript(
|
|
|
165
175
|
const part = parts[partIndex];
|
|
166
176
|
if (part === undefined) continue;
|
|
167
177
|
|
|
178
|
+
if (part.kind !== "tool_result") {
|
|
179
|
+
activeToolResultRun = undefined;
|
|
180
|
+
}
|
|
181
|
+
|
|
168
182
|
if (part.kind === "text") {
|
|
169
183
|
// Synthetic message ids (Pi tail synthetic user with
|
|
170
184
|
// no id) cannot be tagged — there's no stable handle
|
|
@@ -186,19 +200,25 @@ export function tagTranscript(
|
|
|
186
200
|
db,
|
|
187
201
|
targets,
|
|
188
202
|
skipPrefixInjection,
|
|
203
|
+
entryFingerprint: options.entryFingerprintByMessageId?.get(messageId) ?? null,
|
|
189
204
|
});
|
|
190
205
|
textOrdinal += 1;
|
|
191
206
|
continue;
|
|
192
207
|
}
|
|
193
208
|
|
|
194
209
|
if (part.kind === "tool_use" || part.kind === "tool_result") {
|
|
195
|
-
if (messageId === undefined)
|
|
210
|
+
if (messageId === undefined) {
|
|
211
|
+
activeToolResultRun = undefined;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
196
214
|
|
|
197
215
|
const callId = part.id;
|
|
198
216
|
const text = part.getText() ?? "";
|
|
217
|
+
const toolByteSize = getToolPartByteSize(part, text);
|
|
199
218
|
const meta = part.getToolMetadata();
|
|
200
219
|
|
|
201
220
|
if (typeof callId !== "string" || callId.length === 0) {
|
|
221
|
+
activeToolResultRun = undefined;
|
|
202
222
|
// No stable callId to aggregate on. Tag independently.
|
|
203
223
|
tagToolPart({
|
|
204
224
|
sessionId,
|
|
@@ -215,21 +235,35 @@ export function tagTranscript(
|
|
|
215
235
|
continue;
|
|
216
236
|
}
|
|
217
237
|
|
|
218
|
-
const
|
|
238
|
+
const pendingKeys = openToolAggregateKeysByCallId.get(callId) ?? [];
|
|
239
|
+
let existingKey: string | undefined;
|
|
240
|
+
if (part.kind === "tool_result") {
|
|
241
|
+
if (
|
|
242
|
+
activeToolResultRun !== undefined &&
|
|
243
|
+
activeToolResultRun.callId === callId
|
|
244
|
+
) {
|
|
245
|
+
existingKey = activeToolResultRun.aggregateKey;
|
|
246
|
+
} else {
|
|
247
|
+
existingKey = findLastUnresolvedToolAggregateKey(
|
|
248
|
+
pendingKeys,
|
|
249
|
+
toolAggregates,
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
const aggregateKey: string = existingKey ?? makeToolCompositeKey(messageId, callId);
|
|
254
|
+
const existing = toolAggregates.get(aggregateKey);
|
|
219
255
|
if (existing) {
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
// closures over `occurrences` see all parts.
|
|
256
|
+
// Later occurrence for this owner+callId pair. Merge into the
|
|
257
|
+
// aggregate, update byte accounting if larger, and rebuild the
|
|
258
|
+
// TagTarget so drops mutate both invocation and result.
|
|
224
259
|
existing.occurrences.push({
|
|
225
260
|
message,
|
|
226
261
|
part,
|
|
227
262
|
kind: part.kind,
|
|
228
263
|
});
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
existing.
|
|
232
|
-
updateTagByteSize(db, sessionId, existing.tagId, newByteSize);
|
|
264
|
+
if (toolByteSize > existing.maxByteSize) {
|
|
265
|
+
existing.maxByteSize = toolByteSize;
|
|
266
|
+
updateTagByteSize(db, sessionId, existing.tagId, toolByteSize);
|
|
233
267
|
}
|
|
234
268
|
if (existing.toolName === null && meta.toolName) {
|
|
235
269
|
existing.toolName = meta.toolName;
|
|
@@ -253,18 +287,23 @@ export function tagTranscript(
|
|
|
253
287
|
existing.tagId,
|
|
254
288
|
buildAggregateTarget(existing.tagId, existing.occurrences),
|
|
255
289
|
);
|
|
290
|
+
if (part.kind === "tool_result") {
|
|
291
|
+
markToolAggregateResolved(
|
|
292
|
+
callId,
|
|
293
|
+
aggregateKey,
|
|
294
|
+
openToolAggregateKeysByCallId,
|
|
295
|
+
);
|
|
296
|
+
activeToolResultRun = { callId, aggregateKey };
|
|
297
|
+
}
|
|
256
298
|
} else {
|
|
257
|
-
// First occurrence
|
|
258
|
-
//
|
|
259
|
-
//
|
|
260
|
-
// Owner stays stable across passes because Pi
|
|
261
|
-
// re-emits the full transcript each time and
|
|
262
|
-
// message ids are durable.
|
|
299
|
+
// First occurrence for this owner+callId identity — reserve
|
|
300
|
+
// the tag number. Owner stays stable across passes because
|
|
301
|
+
// transcript message ids are durable.
|
|
263
302
|
const tagId = tagger.assignToolTag(
|
|
264
303
|
sessionId,
|
|
265
304
|
callId,
|
|
266
305
|
messageId,
|
|
267
|
-
|
|
306
|
+
toolByteSize,
|
|
268
307
|
db,
|
|
269
308
|
0,
|
|
270
309
|
meta.toolName ?? null,
|
|
@@ -280,11 +319,14 @@ export function tagTranscript(
|
|
|
280
319
|
kind: part.kind,
|
|
281
320
|
},
|
|
282
321
|
],
|
|
283
|
-
maxByteSize:
|
|
322
|
+
maxByteSize: toolByteSize,
|
|
284
323
|
toolName: meta.toolName ?? null,
|
|
285
324
|
inputByteSize: part.kind === "tool_use" ? meta.inputByteSize : 0,
|
|
286
325
|
};
|
|
287
|
-
toolAggregates.set(
|
|
326
|
+
toolAggregates.set(aggregateKey, aggregate);
|
|
327
|
+
if (part.kind === "tool_use") {
|
|
328
|
+
openToolAggregateKeysByCallId.set(callId, [...pendingKeys, aggregateKey]);
|
|
329
|
+
}
|
|
288
330
|
// Inject §N§ prefix into this occurrence's visible text
|
|
289
331
|
// when it's a tool_result. (OpenCode parity: prefix
|
|
290
332
|
// only goes on the result, not the invocation.)
|
|
@@ -292,6 +334,14 @@ export function tagTranscript(
|
|
|
292
334
|
part.setText(prependTag(tagId, text));
|
|
293
335
|
}
|
|
294
336
|
targets.set(tagId, buildAggregateTarget(tagId, aggregate.occurrences));
|
|
337
|
+
if (part.kind === "tool_result") {
|
|
338
|
+
markToolAggregateResolved(
|
|
339
|
+
callId,
|
|
340
|
+
aggregateKey,
|
|
341
|
+
openToolAggregateKeysByCallId,
|
|
342
|
+
);
|
|
343
|
+
activeToolResultRun = { callId, aggregateKey };
|
|
344
|
+
}
|
|
295
345
|
}
|
|
296
346
|
}
|
|
297
347
|
// thinking, image, file, structural, unknown → skip.
|
|
@@ -301,6 +351,69 @@ export function tagTranscript(
|
|
|
301
351
|
return { targets };
|
|
302
352
|
}
|
|
303
353
|
|
|
354
|
+
function findLastUnresolvedToolAggregateKey(
|
|
355
|
+
pendingKeys: string[],
|
|
356
|
+
toolAggregates: Map<string, ToolAggregate & { tagId: number }>,
|
|
357
|
+
): string | undefined {
|
|
358
|
+
for (let i = pendingKeys.length - 1; i >= 0; i -= 1) {
|
|
359
|
+
const key = pendingKeys[i];
|
|
360
|
+
if (key === undefined) continue;
|
|
361
|
+
const aggregate = toolAggregates.get(key);
|
|
362
|
+
if (aggregate === undefined) continue;
|
|
363
|
+
if (!aggregate.occurrences.some((occ) => occ.kind === "tool_result")) {
|
|
364
|
+
return key;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return undefined;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function markToolAggregateResolved(
|
|
371
|
+
callId: string,
|
|
372
|
+
aggregateKey: string,
|
|
373
|
+
openToolAggregateKeysByCallId: Map<string, string[]>,
|
|
374
|
+
): void {
|
|
375
|
+
const pendingKeys = openToolAggregateKeysByCallId.get(callId);
|
|
376
|
+
if (pendingKeys === undefined) return;
|
|
377
|
+
const nextPendingKeys = pendingKeys.filter((key) => key !== aggregateKey);
|
|
378
|
+
if (nextPendingKeys.length === 0) {
|
|
379
|
+
openToolAggregateKeysByCallId.delete(callId);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
openToolAggregateKeysByCallId.set(callId, nextPendingKeys);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function getToolPartByteSize(part: TranscriptPart, text: string): number {
|
|
386
|
+
const textByteSize = byteSize(text);
|
|
387
|
+
if (textByteSize > 0 || part.kind !== "tool_result") return textByteSize;
|
|
388
|
+
return getNonTextToolResultByteSize(part);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function getNonTextToolResultByteSize(part: TranscriptPart): number {
|
|
392
|
+
const record = isRecord(part) ? part : undefined;
|
|
393
|
+
const content =
|
|
394
|
+
record?.content ??
|
|
395
|
+
record?.rawContent ??
|
|
396
|
+
record?.rawPart ??
|
|
397
|
+
record?.part ??
|
|
398
|
+
record?.data ??
|
|
399
|
+
record?.image ??
|
|
400
|
+
record?.source;
|
|
401
|
+
const serialized = safeJsonStringify(content ?? part);
|
|
402
|
+
return serialized === undefined ? 0 : byteSize(serialized);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function safeJsonStringify(value: unknown): string | undefined {
|
|
406
|
+
try {
|
|
407
|
+
return JSON.stringify(value);
|
|
408
|
+
} catch {
|
|
409
|
+
return undefined;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
414
|
+
return typeof value === "object" && value !== null;
|
|
415
|
+
}
|
|
416
|
+
|
|
304
417
|
interface TagTextPartArgs {
|
|
305
418
|
sessionId: string;
|
|
306
419
|
message: { info: { id?: string; role: string } };
|
|
@@ -312,6 +425,7 @@ interface TagTextPartArgs {
|
|
|
312
425
|
db: ContextDatabase;
|
|
313
426
|
targets: Map<number, TagTarget>;
|
|
314
427
|
skipPrefixInjection: boolean;
|
|
428
|
+
entryFingerprint: string | null;
|
|
315
429
|
}
|
|
316
430
|
|
|
317
431
|
function tagTextPart(args: TagTextPartArgs): void {
|
|
@@ -323,6 +437,10 @@ function tagTextPart(args: TagTextPartArgs): void {
|
|
|
323
437
|
"message",
|
|
324
438
|
byteSize(text),
|
|
325
439
|
args.db,
|
|
440
|
+
0,
|
|
441
|
+
null,
|
|
442
|
+
0,
|
|
443
|
+
args.entryFingerprint,
|
|
326
444
|
);
|
|
327
445
|
|
|
328
446
|
// Persist the original (pre-tagged) source content so caveman
|
|
@@ -371,6 +489,7 @@ function tagToolPart(args: TagToolPartArgs): void {
|
|
|
371
489
|
const stableId = args.part.id;
|
|
372
490
|
const contentId = stableId ?? `${args.messageId}:t${args.partIndex}`;
|
|
373
491
|
const text = args.part.getText() ?? "";
|
|
492
|
+
const toolByteSize = getToolPartByteSize(args.part, text);
|
|
374
493
|
const meta = args.part.getToolMetadata();
|
|
375
494
|
// v3.3.1 Layer C: synthetic ownership for the no-callId Pi
|
|
376
495
|
// fallback. Owner == callId == contentId. The composite key
|
|
@@ -382,7 +501,7 @@ function tagToolPart(args: TagToolPartArgs): void {
|
|
|
382
501
|
args.sessionId,
|
|
383
502
|
contentId,
|
|
384
503
|
contentId,
|
|
385
|
-
|
|
504
|
+
toolByteSize,
|
|
386
505
|
args.db,
|
|
387
506
|
0,
|
|
388
507
|
meta.toolName ?? null,
|
|
@@ -400,6 +519,18 @@ function tagToolPart(args: TagToolPartArgs): void {
|
|
|
400
519
|
args.targets.set(tagId, buildToolTarget(args.part, args.message));
|
|
401
520
|
}
|
|
402
521
|
|
|
522
|
+
function setToolContentOrText(part: TranscriptPart, content: string): boolean {
|
|
523
|
+
try {
|
|
524
|
+
if (part.setToolOutput(content)) return true;
|
|
525
|
+
} catch {
|
|
526
|
+
// Pi assistant tool_use parts deliberately assert if callers try
|
|
527
|
+
// to write a nonexistent output slot. Truncated-mode drops still
|
|
528
|
+
// need to shrink the invocation, so fall back to visible text/args
|
|
529
|
+
// replacement while preserving the adapter-level invariant.
|
|
530
|
+
}
|
|
531
|
+
return part.setText(content);
|
|
532
|
+
}
|
|
533
|
+
|
|
403
534
|
/**
|
|
404
535
|
* Build a TagTarget that walks ALL occurrences of a tool call (invocation
|
|
405
536
|
* + result) when mutating. This is the per-callId aggregate target used
|
|
@@ -428,9 +559,7 @@ function buildAggregateTarget(tagId: number, occurrences: ToolOccurrence[]): Tag
|
|
|
428
559
|
for (const occ of occurrences) {
|
|
429
560
|
// Try setToolOutput first (works on tool_result-shaped parts);
|
|
430
561
|
// fall back to setText so tool_use parts also get sentinelized.
|
|
431
|
-
if (occ.part
|
|
432
|
-
changed = true;
|
|
433
|
-
} else if (occ.part.setText(content)) {
|
|
562
|
+
if (setToolContentOrText(occ.part, content)) {
|
|
434
563
|
changed = true;
|
|
435
564
|
}
|
|
436
565
|
}
|
|
@@ -461,7 +590,7 @@ function buildAggregateTarget(tagId: number, occurrences: ToolOccurrence[]): Tag
|
|
|
461
590
|
const sentinel = "[truncated]";
|
|
462
591
|
let any = false;
|
|
463
592
|
for (const occ of occurrences) {
|
|
464
|
-
if (occ.part
|
|
593
|
+
if (setToolContentOrText(occ.part, sentinel)) {
|
|
465
594
|
any = true;
|
|
466
595
|
}
|
|
467
596
|
}
|
|
@@ -521,7 +650,7 @@ function buildToolTarget(
|
|
|
521
650
|
): TagTarget {
|
|
522
651
|
return {
|
|
523
652
|
setContent(content: string): boolean {
|
|
524
|
-
return part
|
|
653
|
+
return setToolContentOrText(part, content);
|
|
525
654
|
},
|
|
526
655
|
getContent(): string | null {
|
|
527
656
|
return part.getText() ?? null;
|
|
@@ -542,7 +671,7 @@ function buildToolTarget(
|
|
|
542
671
|
// via setToolOutput so the underlying tool_result content
|
|
543
672
|
// gets the truncation; falls back to setText for cases
|
|
544
673
|
// where the part type doesn't support setToolOutput.
|
|
545
|
-
const ok = part
|
|
674
|
+
const ok = setToolContentOrText(part, "[truncated]");
|
|
546
675
|
return ok ? "truncated" : "absent";
|
|
547
676
|
},
|
|
548
677
|
message: {
|
|
@@ -10,7 +10,8 @@ import type { RpcNotificationMessage, SidebarSnapshot, StatusDetail } from "../.
|
|
|
10
10
|
export type { SidebarSnapshot, StatusDetail };
|
|
11
11
|
|
|
12
12
|
let rpcClient: MagicContextRpcClient | null = null;
|
|
13
|
-
let
|
|
13
|
+
let rpcGeneration = 0;
|
|
14
|
+
const lastReceivedNotificationIdBySession = new Map<string, number>();
|
|
14
15
|
|
|
15
16
|
function getStorageDir(): string {
|
|
16
17
|
// Plugin v0.16+ uses the shared cortexkit/magic-context path so OpenCode
|
|
@@ -24,14 +25,25 @@ function getStorageDir(): string {
|
|
|
24
25
|
/** Initialize the RPC client. Call once on TUI startup. */
|
|
25
26
|
export function initRpcClient(directory: string): void {
|
|
26
27
|
const storageDir = getStorageDir();
|
|
28
|
+
// Bump the generation before replacing the client so late notification
|
|
29
|
+
// responses from a disposed client cannot repopulate cleared cursors.
|
|
30
|
+
rpcGeneration += 1;
|
|
31
|
+
lastReceivedNotificationIdBySession.clear();
|
|
27
32
|
rpcClient = new MagicContextRpcClient(storageDir, directory);
|
|
28
33
|
}
|
|
29
34
|
|
|
35
|
+
export function getRpcGeneration(): number {
|
|
36
|
+
return rpcGeneration;
|
|
37
|
+
}
|
|
38
|
+
|
|
30
39
|
/** Clean up the RPC client. */
|
|
31
40
|
export function closeRpc(): void {
|
|
41
|
+
// Closing invalidates any already-issued RPC calls; their callbacks must
|
|
42
|
+
// observe the new generation and avoid advancing stale notification cursors.
|
|
43
|
+
rpcGeneration += 1;
|
|
32
44
|
rpcClient?.reset();
|
|
33
45
|
rpcClient = null;
|
|
34
|
-
|
|
46
|
+
lastReceivedNotificationIdBySession.clear();
|
|
35
47
|
}
|
|
36
48
|
|
|
37
49
|
const EMPTY_SNAPSHOT: SidebarSnapshot = {
|
|
@@ -55,10 +67,14 @@ const EMPTY_SNAPSHOT: SidebarSnapshot = {
|
|
|
55
67
|
compartmentTokens: 0,
|
|
56
68
|
factTokens: 0,
|
|
57
69
|
memoryTokens: 0,
|
|
70
|
+
docsTokens: 0,
|
|
71
|
+
profileTokens: 0,
|
|
58
72
|
conversationTokens: 0,
|
|
59
73
|
toolCallTokens: 0,
|
|
60
74
|
toolDefinitionTokens: 0,
|
|
61
75
|
executeThreshold: 65,
|
|
76
|
+
newWorkTokens: null,
|
|
77
|
+
totalInputTokens: null,
|
|
62
78
|
};
|
|
63
79
|
|
|
64
80
|
/**
|
|
@@ -227,7 +243,35 @@ export async function requestRecomp(sessionId: string): Promise<boolean> {
|
|
|
227
243
|
}
|
|
228
244
|
}
|
|
229
245
|
|
|
246
|
+
/** Run `/ctx-session-upgrade` for the session (full recomp + once-per-project
|
|
247
|
+
* memory migration). Fired from the upgrade dialog's "Run upgrade now" action. */
|
|
248
|
+
export async function requestUpgrade(sessionId: string): Promise<boolean> {
|
|
249
|
+
if (!rpcClient) return false;
|
|
250
|
+
try {
|
|
251
|
+
const result = await rpcClient.call<{ ok: boolean }>("upgrade", { sessionId });
|
|
252
|
+
return result.ok ?? false;
|
|
253
|
+
} catch {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** Mark the upgrade reminder dismissed (the user made an explicit Confirm/Cancel
|
|
259
|
+
* choice), setting the durable stamp so the FRESH dialog won't re-show. Resume
|
|
260
|
+
* prompts are staging-driven and unaffected. */
|
|
261
|
+
export async function dismissUpgradeReminder(sessionId: string): Promise<boolean> {
|
|
262
|
+
if (!rpcClient) return false;
|
|
263
|
+
try {
|
|
264
|
+
const result = await rpcClient.call<{ ok: boolean }>("dismiss-upgrade-reminder", {
|
|
265
|
+
sessionId,
|
|
266
|
+
});
|
|
267
|
+
return result.ok ?? false;
|
|
268
|
+
} catch {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
230
273
|
export interface TuiMessage {
|
|
274
|
+
id: number;
|
|
231
275
|
type: string;
|
|
232
276
|
payload: Record<string, unknown>;
|
|
233
277
|
sessionId?: string;
|
|
@@ -277,20 +321,24 @@ export async function markAnnounced(): Promise<boolean> {
|
|
|
277
321
|
}
|
|
278
322
|
|
|
279
323
|
/** Poll for pending server→TUI notifications via RPC. */
|
|
280
|
-
export async function consumeTuiMessages(): Promise<TuiMessage[]> {
|
|
324
|
+
export async function consumeTuiMessages(sessionId: string): Promise<TuiMessage[]> {
|
|
281
325
|
if (!rpcClient) return [];
|
|
282
326
|
try {
|
|
283
327
|
const result = await rpcClient.call<{ messages: RpcNotificationMessage[] }>(
|
|
284
328
|
"pending-notifications",
|
|
285
|
-
|
|
329
|
+
// Pass the TUI's active session so the server only drains
|
|
330
|
+
// notifications scoped to it (or global ones). Without this, a
|
|
331
|
+
// notification for another session served by the same process (e.g.
|
|
332
|
+
// OpenCode Desktop on the same project) could surface here. The
|
|
333
|
+
// cursor is per-session and is advanced by the poller only after it
|
|
334
|
+
// has delivered the returned batch.
|
|
335
|
+
{
|
|
336
|
+
lastReceivedId: lastReceivedNotificationIdBySession.get(sessionId) ?? 0,
|
|
337
|
+
sessionId,
|
|
338
|
+
},
|
|
286
339
|
);
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if (message.id > lastReceivedNotificationId) {
|
|
290
|
-
lastReceivedNotificationId = message.id;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
return messages.map((m) => ({
|
|
340
|
+
return (result.messages ?? []).map((m) => ({
|
|
341
|
+
id: m.id,
|
|
294
342
|
type: m.type,
|
|
295
343
|
payload: m.payload,
|
|
296
344
|
sessionId: m.sessionId,
|
|
@@ -299,3 +347,21 @@ export async function consumeTuiMessages(): Promise<TuiMessage[]> {
|
|
|
299
347
|
return [];
|
|
300
348
|
}
|
|
301
349
|
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Advance the delivered-message cursor for one active TUI session.
|
|
353
|
+
* Callers must pass only the contiguous handled prefix of the drained batch;
|
|
354
|
+
* this helper remains empty-safe and monotonic for that prefix.
|
|
355
|
+
*/
|
|
356
|
+
export function markTuiMessagesHandled(sessionId: string, messages: TuiMessage[]): void {
|
|
357
|
+
const previous = lastReceivedNotificationIdBySession.get(sessionId) ?? 0;
|
|
358
|
+
let next = previous;
|
|
359
|
+
for (const message of messages) {
|
|
360
|
+
if (message.id > next) {
|
|
361
|
+
next = message.id;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (next > previous) {
|
|
365
|
+
lastReceivedNotificationIdBySession.set(sessionId, next);
|
|
366
|
+
}
|
|
367
|
+
}
|