@gajae-code/coding-agent 0.12.0 → 0.12.1
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/CHANGELOG.md +82 -1
- package/dist/types/async/job-manager.d.ts +17 -3
- package/dist/types/config/settings-schema.d.ts +54 -0
- package/dist/types/exa/mcp-client.d.ts +10 -1
- package/dist/types/exec/bash-executor.d.ts +45 -12
- package/dist/types/extensibility/extensions/types.d.ts +16 -2
- package/dist/types/extensibility/gjc-plugins/index.d.ts +12 -2
- package/dist/types/extensibility/gjc-plugins/installer.d.ts +63 -7
- package/dist/types/extensibility/gjc-plugins/lifecycle-reconciliation.d.ts +55 -0
- package/dist/types/extensibility/gjc-plugins/lifecycle.d.ts +64 -0
- package/dist/types/extensibility/gjc-plugins/runtime-quarantine.d.ts +67 -0
- package/dist/types/extensibility/gjc-plugins/session-validation.d.ts +3 -1
- package/dist/types/extensibility/gjc-plugins/types.d.ts +124 -0
- package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +18 -0
- package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
- package/dist/types/gjc-runtime/team-runtime.d.ts +25 -0
- package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +43 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +10 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +31 -80
- package/dist/types/modes/acp/acp-agent.d.ts +2 -19
- package/dist/types/modes/components/gjc-bundle-settings.d.ts +34 -0
- package/dist/types/modes/components/notifications-settings-editor.d.ts +1 -0
- package/dist/types/modes/components/session-observer-overlay.d.ts +18 -0
- package/dist/types/modes/components/settings-selector.d.ts +9 -0
- package/dist/types/modes/components/status-line/gh.d.ts +5 -0
- package/dist/types/modes/components/transcript-viewer-overlay.d.ts +18 -1
- package/dist/types/reminders/star-reminder.d.ts +3 -13
- package/dist/types/runtime-mcp/manager.d.ts +1 -0
- package/dist/types/sdk/acp/adapter.d.ts +3 -1
- package/dist/types/sdk/broker/transport.d.ts +2 -0
- package/dist/types/sdk/bus/attachment-registry.d.ts +1 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +4 -1
- package/dist/types/sdk/bus/config.d.ts +6 -2
- package/dist/types/sdk/bus/index.d.ts +29 -0
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +8 -1
- package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
- package/dist/types/sdk/bus/reconciliation-store.d.ts +7 -3
- package/dist/types/sdk/bus/rich-draft.d.ts +1 -0
- package/dist/types/sdk/bus/rich-render.d.ts +2 -0
- package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +165 -0
- package/dist/types/sdk/bus/telegram-cli.d.ts +12 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +11 -4
- package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -1
- package/dist/types/sdk/bus/telegram-reference.d.ts +11 -1
- package/dist/types/sdk/bus/threaded-render.d.ts +3 -2
- package/dist/types/sdk/bus/topic-registry.d.ts +11 -1
- package/dist/types/sdk/host/query/revision-store.d.ts +2 -0
- package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
- package/dist/types/sdk/prompt-status.d.ts +23 -3
- package/dist/types/sdk/session.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +38 -2
- package/dist/types/session/artifacts.d.ts +8 -0
- package/dist/types/session/blob-store.d.ts +57 -4
- package/dist/types/session/internal/managed-session-storage.d.ts +8 -1
- package/dist/types/session/session-manager.d.ts +23 -6
- package/dist/types/session/session-storage.d.ts +4 -0
- package/dist/types/session/streaming-output.d.ts +36 -7
- package/dist/types/setup/credential-import.d.ts +12 -0
- package/dist/types/skill-state/active-state.d.ts +12 -0
- package/dist/types/skill-state/workflow-hud.d.ts +6 -0
- package/dist/types/task/executor.d.ts +3 -2
- package/dist/types/task/index.d.ts +3 -1
- package/dist/types/task/provider-retry-status.d.ts +8 -0
- package/dist/types/task/receipt.d.ts +7 -1
- package/dist/types/task/types.d.ts +23 -0
- package/dist/types/tools/ask-answer-registry.d.ts +2 -2
- package/dist/types/tools/bash-interactive.d.ts +4 -1
- package/dist/types/tools/bash.d.ts +3 -1
- package/dist/types/tools/image-gen.d.ts +4 -0
- package/dist/types/tools/index.d.ts +4 -0
- package/dist/types/tools/output-meta.d.ts +23 -2
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/read.d.ts +0 -6
- package/dist/types/tools/review.d.ts +5 -7
- package/dist/types/tools/subagent.d.ts +2 -0
- package/dist/types/utils/gh.d.ts +13 -0
- package/dist/types/utils/image-resize.d.ts +1 -0
- package/dist/types/web/search/providers/kimi.d.ts +2 -0
- package/dist/types/web/search/providers/searxng.d.ts +7 -0
- package/dist/types/web/search/providers/xai.d.ts +2 -0
- package/package.json +10 -8
- package/scripts/acp-conformance-agent.ts +218 -0
- package/scripts/capture-gjc-bundle-settings.ts +306 -0
- package/scripts/generate-sdk-operation-inventory.ts +3 -0
- package/scripts/resident-memory-bench.ts +1341 -0
- package/scripts/run-acp-conformance.ts +285 -0
- package/src/async/job-manager.ts +78 -20
- package/src/cli/fast-help.ts +1 -1
- package/src/cli/plugin-cli.ts +176 -18
- package/src/commands/team.ts +19 -1
- package/src/config/file-lock.ts +11 -2
- package/src/config/settings-schema.ts +51 -0
- package/src/coordinator-mcp/question-gate-codec.ts +1 -1
- package/src/coordinator-mcp/server.ts +5 -12
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -0
- package/src/defaults/gjc/skills/ralplan/SKILL.md +19 -16
- package/src/defaults/gjc/skills/team/SKILL.md +2 -0
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +37 -24
- package/src/defaults/gjc/skills/ultragoal/validation-batch-contracts.md +12 -0
- package/src/defaults/gjc-defaults.ts +4 -4
- package/src/exa/mcp-client.ts +12 -3
- package/src/exec/bash-executor.ts +144 -27
- package/src/extensibility/extensions/runner.ts +15 -0
- package/src/extensibility/extensions/types.ts +18 -1
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +8 -2
- package/src/extensibility/gjc-plugins/index.ts +19 -2
- package/src/extensibility/gjc-plugins/installer.ts +189 -50
- package/src/extensibility/gjc-plugins/lifecycle-reconciliation.ts +199 -0
- package/src/extensibility/gjc-plugins/lifecycle.ts +626 -0
- package/src/extensibility/gjc-plugins/observability.ts +3 -3
- package/src/extensibility/gjc-plugins/prompt-appendix.ts +5 -1
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +12 -2
- package/src/extensibility/gjc-plugins/runtime-quarantine.ts +121 -0
- package/src/extensibility/gjc-plugins/schema.ts +84 -12
- package/src/extensibility/gjc-plugins/session-validation.ts +9 -3
- package/src/extensibility/gjc-plugins/types.ts +141 -0
- package/src/extensibility/gjc-plugins/validation.ts +9 -0
- package/src/gjc-runtime/launch-tmux.ts +25 -6
- package/src/gjc-runtime/managed-owner-admission.ts +1 -1
- package/src/gjc-runtime/ralplan-runtime.ts +411 -17
- package/src/gjc-runtime/team-launch.ts +8 -0
- package/src/gjc-runtime/team-runtime.ts +140 -32
- package/src/gjc-runtime/team-worker-heartbeat.ts +149 -0
- package/src/gjc-runtime/tmux-owner-isolation.ts +10 -0
- package/src/gjc-runtime/tmux-sessions.ts +58 -13
- package/src/gjc-runtime/ultragoal-runtime.ts +401 -611
- package/src/gjc-runtime/workflow-manifest.generated.json +8 -99
- package/src/gjc-runtime/workflow-manifest.ts +4 -68
- package/src/internal-urls/docs-index.generated.ts +15 -15
- package/src/modes/DESIGN.md +51 -0
- package/src/modes/acp/acp-agent.ts +298 -186
- package/src/modes/components/gjc-bundle-settings.ts +508 -0
- package/src/modes/components/notifications-settings-editor.ts +12 -1
- package/src/modes/components/oauth-selector.ts +68 -30
- package/src/modes/components/session-observer-overlay.ts +305 -86
- package/src/modes/components/settings-selector.ts +56 -5
- package/src/modes/components/status-line/gh.ts +16 -0
- package/src/modes/components/tool-status-header.ts +3 -14
- package/src/modes/components/transcript-viewer-overlay.ts +99 -24
- package/src/modes/controllers/command-controller.ts +4 -0
- package/src/modes/controllers/extension-ui-controller.ts +7 -0
- package/src/modes/controllers/selector-controller.ts +45 -0
- package/src/modes/runtime-init.ts +3 -0
- package/src/prompts/agent-fragments/ralplan-persistence.md +4 -3
- package/src/prompts/system/eager-todo.md +8 -4
- package/src/prompts/system/system-prompt.md +11 -4
- package/src/prompts/tools/bash.md +5 -5
- package/src/prompts/tools/search-tool-bm25.md +5 -0
- package/src/prompts/tools/task.md +1 -1
- package/src/reminders/star-reminder.ts +4 -42
- package/src/runtime-mcp/manager.ts +19 -4
- package/src/sdk/acp/adapter.ts +22 -6
- package/src/sdk/broker/transport.ts +35 -3
- package/src/sdk/bus/attachment-registry.ts +5 -1
- package/src/sdk/bus/chat-daemon-cli.ts +4 -39
- package/src/sdk/bus/chat-daemon-control.ts +6 -3
- package/src/sdk/bus/config.ts +18 -2
- package/src/sdk/bus/index.ts +661 -126
- package/src/sdk/bus/kind-aware-reconciliation.ts +148 -73
- package/src/sdk/bus/lifecycle-commands.ts +4 -1
- package/src/sdk/bus/lifecycle-control-runtime.ts +403 -88
- package/src/sdk/bus/reconciliation-store.ts +95 -16
- package/src/sdk/bus/rich-draft.ts +1 -1
- package/src/sdk/bus/rich-render.ts +2 -2
- package/src/sdk/bus/telegram-adoption-intent.ts +574 -0
- package/src/sdk/bus/telegram-cli.ts +33 -8
- package/src/sdk/bus/telegram-daemon-cli.ts +1 -0
- package/src/sdk/bus/telegram-daemon-contract.ts +11 -4
- package/src/sdk/bus/telegram-daemon.ts +916 -23
- package/src/sdk/bus/telegram-reference.ts +40 -3
- package/src/sdk/bus/threaded-render.ts +48 -3
- package/src/sdk/bus/topic-registry.ts +20 -0
- package/src/sdk/host/query/revision-store.ts +36 -2
- package/src/sdk/host/reverse-leases.ts +44 -7
- package/src/sdk/prompt-status.ts +16 -3
- package/src/sdk/protocol/operation-inventory.generated.json +22 -0
- package/src/sdk/session.ts +88 -9
- package/src/session/agent-session.ts +822 -172
- package/src/session/artifacts.ts +22 -0
- package/src/session/auth-broker-config.ts +10 -3
- package/src/session/blob-store.ts +915 -24
- package/src/session/internal/managed-session-scope.ts +44 -6
- package/src/session/internal/managed-session-storage.ts +48 -6
- package/src/session/session-manager.ts +1614 -435
- package/src/session/session-storage.ts +9 -1
- package/src/session/streaming-output.ts +238 -68
- package/src/setup/credential-import.ts +75 -13
- package/src/skill-state/active-state.ts +32 -0
- package/src/skill-state/workflow-hud.ts +17 -0
- package/src/skill-state/workflow-mutation-guard.ts +496 -2
- package/src/task/executor.ts +122 -39
- package/src/task/index.ts +23 -14
- package/src/task/provider-retry-status.ts +16 -0
- package/src/task/receipt.ts +23 -7
- package/src/task/render.ts +6 -2
- package/src/task/types.ts +108 -0
- package/src/tools/ask-answer-registry.ts +12 -6
- package/src/tools/ask.ts +86 -55
- package/src/tools/ast-edit.ts +1 -0
- package/src/tools/ast-grep.ts +1 -0
- package/src/tools/bash-interactive.ts +8 -3
- package/src/tools/bash.ts +503 -60
- package/src/tools/find.ts +1 -0
- package/src/tools/image-gen.ts +29 -5
- package/src/tools/index.ts +4 -0
- package/src/tools/output-meta.ts +108 -14
- package/src/tools/path-utils.ts +3 -2
- package/src/tools/read.ts +18 -0
- package/src/tools/review.ts +5 -7
- package/src/tools/search.ts +1 -0
- package/src/tools/subagent-render.ts +6 -2
- package/src/tools/subagent.ts +6 -0
- package/src/tools/telegram-send.ts +6 -3
- package/src/utils/gh.ts +39 -0
- package/src/utils/image-resize.ts +3 -0
- package/src/web/search/providers/anthropic.ts +4 -4
- package/src/web/search/providers/kimi.ts +15 -5
- package/src/web/search/providers/searxng.ts +47 -4
- package/src/web/search/providers/xai.ts +7 -2
- package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +0 -23
- package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +0 -30
- package/src/gjc-runtime/ultragoal-pipeline.ts +0 -306
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as crypto from "node:crypto";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
|
-
import type { ToolResultMessage } from "@gajae-code/ai";
|
|
3
|
+
import type { ToolCall, ToolResultMessage } from "@gajae-code/ai";
|
|
4
4
|
import { matchesKey } from "@gajae-code/tui";
|
|
5
5
|
import { formatDuration, formatNumber } from "@gajae-code/utils";
|
|
6
6
|
import type { KeyId } from "../../config/keybindings";
|
|
@@ -19,7 +19,19 @@ import { type TranscriptViewerEntry, TranscriptViewerOverlay } from "./transcrip
|
|
|
19
19
|
const FATAL_UTF8_DECODER = new TextDecoder("utf-8", { fatal: true });
|
|
20
20
|
|
|
21
21
|
type FileIdentity = { dev: bigint; ino: bigint };
|
|
22
|
-
type StableSnapshot = {
|
|
22
|
+
type StableSnapshot = {
|
|
23
|
+
identity: FileIdentity;
|
|
24
|
+
size: number;
|
|
25
|
+
prefix: Buffer;
|
|
26
|
+
tail: Buffer;
|
|
27
|
+
offsetReset: boolean;
|
|
28
|
+
};
|
|
29
|
+
type SnapshotReadResult =
|
|
30
|
+
| { status: "stable"; snapshot: StableSnapshot }
|
|
31
|
+
| { status: "unstable" }
|
|
32
|
+
| { status: "unavailable" };
|
|
33
|
+
|
|
34
|
+
class UnstableSnapshotError extends Error {}
|
|
23
35
|
type ObserverCache = {
|
|
24
36
|
path: string;
|
|
25
37
|
identity: FileIdentity;
|
|
@@ -27,8 +39,56 @@ type ObserverCache = {
|
|
|
27
39
|
prefixDigest: string;
|
|
28
40
|
entries: SessionMessageEntry[];
|
|
29
41
|
model?: string;
|
|
42
|
+
projectedCount: number;
|
|
43
|
+
projectedOutput: TranscriptViewerEntry[];
|
|
44
|
+
toolCallOutputIndex: Map<string, number>;
|
|
45
|
+
projectionSafe: boolean;
|
|
30
46
|
};
|
|
31
47
|
|
|
48
|
+
type ProjectionState = Pick<
|
|
49
|
+
ObserverCache,
|
|
50
|
+
"projectedCount" | "projectedOutput" | "toolCallOutputIndex" | "projectionSafe"
|
|
51
|
+
>;
|
|
52
|
+
type ProjectionUpdate = { state: ProjectionState; preserveLayoutCache: boolean };
|
|
53
|
+
|
|
54
|
+
export const __sessionObserverProjectionCounters = {
|
|
55
|
+
enabled: false,
|
|
56
|
+
projectionFullRuns: 0,
|
|
57
|
+
projectionIncrementalRuns: 0,
|
|
58
|
+
sourceMessagesProjected: 0,
|
|
59
|
+
projectedEntriesAppended: 0,
|
|
60
|
+
resultPatchCount: 0,
|
|
61
|
+
snapshot() {
|
|
62
|
+
return {
|
|
63
|
+
projectionFullRuns: this.projectionFullRuns,
|
|
64
|
+
projectionIncrementalRuns: this.projectionIncrementalRuns,
|
|
65
|
+
sourceMessagesProjected: this.sourceMessagesProjected,
|
|
66
|
+
projectedEntriesAppended: this.projectedEntriesAppended,
|
|
67
|
+
resultPatchCount: this.resultPatchCount,
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
enable(): void {
|
|
71
|
+
this.enabled = true;
|
|
72
|
+
},
|
|
73
|
+
disable(): void {
|
|
74
|
+
this.enabled = false;
|
|
75
|
+
},
|
|
76
|
+
reset(): void {
|
|
77
|
+
this.projectionFullRuns = 0;
|
|
78
|
+
this.projectionIncrementalRuns = 0;
|
|
79
|
+
this.sourceMessagesProjected = 0;
|
|
80
|
+
this.projectedEntriesAppended = 0;
|
|
81
|
+
this.resultPatchCount = 0;
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
function recordProjectionCounter(
|
|
86
|
+
counter: keyof ReturnType<typeof __sessionObserverProjectionCounters.snapshot>,
|
|
87
|
+
amount = 1,
|
|
88
|
+
): void {
|
|
89
|
+
if (__sessionObserverProjectionCounters.enabled) __sessionObserverProjectionCounters[counter] += amount;
|
|
90
|
+
}
|
|
91
|
+
|
|
32
92
|
/** Session-observer adapter. The shared viewer owns navigation and fold state. */
|
|
33
93
|
export class SessionObserverOverlayComponent extends TranscriptViewerOverlay {
|
|
34
94
|
#registry: SessionObserverRegistry;
|
|
@@ -62,11 +122,21 @@ export class SessionObserverOverlayComponent extends TranscriptViewerOverlay {
|
|
|
62
122
|
this.#observeKeys = observeKeys;
|
|
63
123
|
this.#selectedSessionId = this.#mostRecent()?.id;
|
|
64
124
|
if (!this.#selectedSessionId) queueMicrotask(onDone);
|
|
65
|
-
this
|
|
125
|
+
this.#refreshCurrentSource();
|
|
66
126
|
}
|
|
67
127
|
|
|
68
128
|
refreshFromRegistry(): void {
|
|
69
|
-
this
|
|
129
|
+
this.#refreshCurrentSource();
|
|
130
|
+
}
|
|
131
|
+
#refreshCurrentSource(): void {
|
|
132
|
+
const session = this.#registry.getSessions().find(candidate => candidate.id === this.#selectedSessionId);
|
|
133
|
+
if (!session?.sessionFile) {
|
|
134
|
+
this.#clearSource();
|
|
135
|
+
this.refresh(undefined, false);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const preserveLayoutCache = this.#load(session.sessionFile);
|
|
139
|
+
this.refresh(undefined, preserveLayoutCache);
|
|
70
140
|
}
|
|
71
141
|
override handleInput(keyData: string): void {
|
|
72
142
|
if (this.#observeKeys.some(key => matchesKey(keyData, key))) {
|
|
@@ -100,12 +170,10 @@ export class SessionObserverOverlayComponent extends TranscriptViewerOverlay {
|
|
|
100
170
|
this.#selectedSessionId = ids[(current + direction + ids.length) % ids.length];
|
|
101
171
|
this.#cache = undefined;
|
|
102
172
|
this.resetSourceState();
|
|
103
|
-
this
|
|
173
|
+
this.#refreshCurrentSource();
|
|
104
174
|
}
|
|
105
175
|
#entries(): readonly TranscriptViewerEntry[] {
|
|
106
|
-
|
|
107
|
-
if (!session?.sessionFile) return [];
|
|
108
|
-
return entriesFromMessages(this.#load(session.sessionFile));
|
|
176
|
+
return this.#cache?.projectedOutput ?? [];
|
|
109
177
|
}
|
|
110
178
|
#headerLines(): string[] {
|
|
111
179
|
const session = this.#registry.getSessions().find(candidate => candidate.id === this.#selectedSessionId);
|
|
@@ -139,71 +207,146 @@ export class SessionObserverOverlayComponent extends TranscriptViewerOverlay {
|
|
|
139
207
|
if (progress.cost > 0) stats.push(`$${progress.cost.toFixed(2)}`);
|
|
140
208
|
return stats.length ? [theme.fg("dim", stats.join(theme.sep.dot))] : [];
|
|
141
209
|
}
|
|
142
|
-
#load(filePath: string):
|
|
210
|
+
#load(filePath: string): boolean {
|
|
143
211
|
if (this.#cache && this.#cache.path !== filePath) {
|
|
144
212
|
this.#cache = undefined;
|
|
145
213
|
this.resetSourceState();
|
|
146
214
|
}
|
|
147
215
|
|
|
148
216
|
const cache = this.#cache;
|
|
149
|
-
const
|
|
150
|
-
if (
|
|
151
|
-
|
|
217
|
+
const read = readStableSnapshot(filePath, cache?.completeOffset ?? 0);
|
|
218
|
+
if (read.status === "unstable") return cache !== undefined;
|
|
219
|
+
if (read.status === "unavailable") {
|
|
152
220
|
if (cache) this.#clearSource();
|
|
153
|
-
return
|
|
221
|
+
return false;
|
|
154
222
|
}
|
|
155
|
-
|
|
223
|
+
const snapshot = read.snapshot;
|
|
156
224
|
const canAppend =
|
|
157
225
|
cache &&
|
|
226
|
+
!snapshot.offsetReset &&
|
|
158
227
|
sameIdentity(cache.identity, snapshot.identity) &&
|
|
159
228
|
snapshot.size >= cache.completeOffset &&
|
|
160
229
|
digest(snapshot.prefix) === cache.prefixDigest;
|
|
161
230
|
if (canAppend) return this.#appendStable(cache, snapshot);
|
|
162
231
|
|
|
163
|
-
|
|
164
|
-
// Validate the entire candidate before it replaces the visible transcript.
|
|
165
|
-
const replacement = readStableSnapshot(filePath, 0);
|
|
166
|
-
if (!replacement) {
|
|
167
|
-
this.#clearSource();
|
|
168
|
-
return [];
|
|
169
|
-
}
|
|
170
|
-
const candidate = cacheEntries(filePath, replacement);
|
|
232
|
+
const candidate = cacheEntries(filePath, asFullSnapshot(snapshot));
|
|
171
233
|
if (!candidate) {
|
|
172
234
|
this.#clearSource();
|
|
173
|
-
return
|
|
235
|
+
return false;
|
|
174
236
|
}
|
|
175
237
|
this.#cache = candidate;
|
|
176
238
|
if (cache) this.resetSourceState();
|
|
177
|
-
return
|
|
239
|
+
return false;
|
|
178
240
|
}
|
|
179
|
-
#appendStable(cache: ObserverCache, snapshot: StableSnapshot):
|
|
241
|
+
#appendStable(cache: ObserverCache, snapshot: StableSnapshot): boolean {
|
|
180
242
|
const complete = completePrefix(snapshot.tail);
|
|
181
243
|
if (!complete) {
|
|
182
244
|
if (!isValidUtf8Prefix(snapshot.tail)) {
|
|
183
245
|
this.#clearSource();
|
|
184
|
-
return
|
|
246
|
+
return false;
|
|
185
247
|
}
|
|
186
|
-
return
|
|
248
|
+
return true;
|
|
187
249
|
}
|
|
188
250
|
const remainder = snapshot.tail.subarray(complete.bytes.length);
|
|
189
251
|
if (!isValidUtf8Prefix(remainder)) {
|
|
190
252
|
this.#clearSource();
|
|
191
|
-
return
|
|
253
|
+
return false;
|
|
192
254
|
}
|
|
193
255
|
const parsed = parseCompleteEntries(complete.bytes);
|
|
194
256
|
if (!parsed) {
|
|
195
257
|
this.#clearSource();
|
|
196
|
-
return
|
|
258
|
+
return false;
|
|
197
259
|
}
|
|
260
|
+
if (parsed.hasPatchRecords) {
|
|
261
|
+
const candidate = cacheEntries(cache.path, asFullSnapshot(snapshot));
|
|
262
|
+
if (!candidate) {
|
|
263
|
+
this.#clearSource();
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
this.#cache = candidate;
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
|
|
198
270
|
const entries = [...cache.entries, ...parsed.entries];
|
|
271
|
+
const projection = this.#appendProjection(cache, parsed.entries);
|
|
199
272
|
this.#cache = {
|
|
200
273
|
...cache,
|
|
201
274
|
completeOffset: cache.completeOffset + complete.bytes.length,
|
|
202
275
|
prefixDigest: digest(Buffer.concat([snapshot.prefix, complete.bytes])),
|
|
203
276
|
entries,
|
|
204
277
|
model: parsed.model ?? cache.model,
|
|
278
|
+
...projection.state,
|
|
279
|
+
};
|
|
280
|
+
return projection.preserveLayoutCache;
|
|
281
|
+
}
|
|
282
|
+
#appendProjection(cache: ObserverCache, suffix: readonly SessionMessageEntry[]): ProjectionUpdate {
|
|
283
|
+
if (suffix.length === 0) {
|
|
284
|
+
return {
|
|
285
|
+
state: projectionState(cache),
|
|
286
|
+
preserveLayoutCache: true,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
if (!cache.projectionSafe || cache.projectedCount !== cache.entries.length) {
|
|
290
|
+
return {
|
|
291
|
+
state: fullProjection([...cache.entries, ...suffix]),
|
|
292
|
+
preserveLayoutCache: false,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const projectedOutput = [...cache.projectedOutput];
|
|
297
|
+
const toolCallOutputIndex = new Map(cache.toolCallOutputIndex);
|
|
298
|
+
const patchedIds: string[] = [];
|
|
299
|
+
let appendedCount = 0;
|
|
300
|
+
let patchCount = 0;
|
|
301
|
+
for (const entry of suffix) {
|
|
302
|
+
const message = entry.message;
|
|
303
|
+
if (message.role === "toolResult") {
|
|
304
|
+
const index = toolCallOutputIndex.get(message.toolCallId);
|
|
305
|
+
const existing = index === undefined ? undefined : projectedOutput[index];
|
|
306
|
+
const source = existing?.payload.source;
|
|
307
|
+
const call = isToolProjectionSource(source) ? source.call : undefined;
|
|
308
|
+
if (index === undefined || !call) {
|
|
309
|
+
return {
|
|
310
|
+
state: fullProjection([...cache.entries, ...suffix]),
|
|
311
|
+
preserveLayoutCache: false,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
projectedOutput[index] = toolEntry(call, message);
|
|
315
|
+
patchedIds.push(projectedOutput[index].id);
|
|
316
|
+
patchCount++;
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const appended = entriesFromMessages([entry]);
|
|
321
|
+
for (const outputEntry of appended) {
|
|
322
|
+
if (outputEntry.id.startsWith("tool:")) {
|
|
323
|
+
const callId = outputEntry.id.slice("tool:".length);
|
|
324
|
+
if (toolCallOutputIndex.has(callId)) {
|
|
325
|
+
return {
|
|
326
|
+
state: fullProjection([...cache.entries, ...suffix]),
|
|
327
|
+
preserveLayoutCache: false,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
toolCallOutputIndex.set(callId, projectedOutput.length);
|
|
331
|
+
}
|
|
332
|
+
projectedOutput.push(outputEntry);
|
|
333
|
+
}
|
|
334
|
+
appendedCount += appended.length;
|
|
335
|
+
}
|
|
336
|
+
if (patchedIds.length > 0) this.invalidateLayoutEntries(patchedIds);
|
|
337
|
+
recordProjectionCounter("projectionIncrementalRuns");
|
|
338
|
+
recordProjectionCounter("sourceMessagesProjected", suffix.length);
|
|
339
|
+
recordProjectionCounter("projectedEntriesAppended", appendedCount);
|
|
340
|
+
recordProjectionCounter("resultPatchCount", patchCount);
|
|
341
|
+
return {
|
|
342
|
+
state: {
|
|
343
|
+
projectedCount: cache.projectedCount + suffix.length,
|
|
344
|
+
projectedOutput,
|
|
345
|
+
toolCallOutputIndex,
|
|
346
|
+
projectionSafe: true,
|
|
347
|
+
},
|
|
348
|
+
preserveLayoutCache: true,
|
|
205
349
|
};
|
|
206
|
-
return entries;
|
|
207
350
|
}
|
|
208
351
|
#clearSource(): void {
|
|
209
352
|
if (this.#cache) this.resetSourceState();
|
|
@@ -211,6 +354,95 @@ export class SessionObserverOverlayComponent extends TranscriptViewerOverlay {
|
|
|
211
354
|
}
|
|
212
355
|
}
|
|
213
356
|
|
|
357
|
+
type ToolProjectionSource = { call: ToolCall; result?: ToolResultMessage };
|
|
358
|
+
|
|
359
|
+
function isToolProjectionSource(source: unknown): source is ToolProjectionSource {
|
|
360
|
+
if (!source || typeof source !== "object" || !("call" in source)) return false;
|
|
361
|
+
const call = source.call;
|
|
362
|
+
return Boolean(call && typeof call === "object" && "type" in call && call.type === "toolCall");
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function projectionState(cache: ObserverCache): ProjectionState {
|
|
366
|
+
return {
|
|
367
|
+
projectedCount: cache.projectedCount,
|
|
368
|
+
projectedOutput: cache.projectedOutput,
|
|
369
|
+
toolCallOutputIndex: cache.toolCallOutputIndex,
|
|
370
|
+
projectionSafe: cache.projectionSafe,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function fullProjection(
|
|
375
|
+
entries: readonly SessionMessageEntry[],
|
|
376
|
+
): Pick<ObserverCache, "projectedCount" | "projectedOutput" | "toolCallOutputIndex" | "projectionSafe"> {
|
|
377
|
+
const projectedOutput = entriesFromMessages(entries);
|
|
378
|
+
const toolCallOutputIndex = new Map<string, number>();
|
|
379
|
+
let projectionSafe = true;
|
|
380
|
+
const knownToolCallIds = new Set<string>();
|
|
381
|
+
const unresolvedToolResultIds = new Set<string>();
|
|
382
|
+
for (const entry of entries) {
|
|
383
|
+
const message = entry.message;
|
|
384
|
+
if (message.role === "assistant") {
|
|
385
|
+
for (const content of message.content) if (content.type === "toolCall") knownToolCallIds.add(content.id);
|
|
386
|
+
} else if (message.role === "toolResult") unresolvedToolResultIds.add(message.toolCallId);
|
|
387
|
+
}
|
|
388
|
+
for (const callId of knownToolCallIds) unresolvedToolResultIds.delete(callId);
|
|
389
|
+
if (unresolvedToolResultIds.size > 0) projectionSafe = false;
|
|
390
|
+
for (let index = 0; index < projectedOutput.length; index++) {
|
|
391
|
+
const id = projectedOutput[index].id;
|
|
392
|
+
if (!id.startsWith("tool:")) continue;
|
|
393
|
+
const callId = id.slice("tool:".length);
|
|
394
|
+
if (toolCallOutputIndex.has(callId)) projectionSafe = false;
|
|
395
|
+
else toolCallOutputIndex.set(callId, index);
|
|
396
|
+
}
|
|
397
|
+
recordProjectionCounter("projectionFullRuns");
|
|
398
|
+
recordProjectionCounter("sourceMessagesProjected", entries.length);
|
|
399
|
+
return { projectedCount: entries.length, projectedOutput, toolCallOutputIndex, projectionSafe };
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function toolEntry(call: ToolCall, result?: ToolResultMessage): TranscriptViewerEntry {
|
|
403
|
+
const resultText =
|
|
404
|
+
result?.content
|
|
405
|
+
.filter(part => part.type === "text")
|
|
406
|
+
.map(part => part.text)
|
|
407
|
+
.join("\n")
|
|
408
|
+
.trim() ?? "";
|
|
409
|
+
const hasResult = result !== undefined;
|
|
410
|
+
const canonicalPayload = {
|
|
411
|
+
text: composeToolText({
|
|
412
|
+
name: call.name,
|
|
413
|
+
args: call.arguments,
|
|
414
|
+
intent: call.intent,
|
|
415
|
+
resultText,
|
|
416
|
+
isError: result?.isError ?? false,
|
|
417
|
+
hasResult,
|
|
418
|
+
}),
|
|
419
|
+
metadata: {
|
|
420
|
+
name: call.name,
|
|
421
|
+
arguments: call.arguments,
|
|
422
|
+
intent: call.intent,
|
|
423
|
+
resultText,
|
|
424
|
+
isError: result?.isError ?? false,
|
|
425
|
+
hasResult,
|
|
426
|
+
detailsData: result?.details,
|
|
427
|
+
},
|
|
428
|
+
source: { call, result },
|
|
429
|
+
};
|
|
430
|
+
return buildToolTranscriptEntry({
|
|
431
|
+
canonicalPayload,
|
|
432
|
+
renderDescriptor: createToolTranscriptRenderDescriptor({
|
|
433
|
+
name: call.name,
|
|
434
|
+
args: call.arguments,
|
|
435
|
+
intent: call.intent,
|
|
436
|
+
resultContent: resultText,
|
|
437
|
+
isError: result?.isError,
|
|
438
|
+
hasResult,
|
|
439
|
+
detailsData: result?.details,
|
|
440
|
+
}),
|
|
441
|
+
capabilities: { copyable: true, foldable: true, rawViewable: true },
|
|
442
|
+
identity: { id: `tool:${call.id}`, label: call.name, display: "full" },
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
214
446
|
/**
|
|
215
447
|
* This deliberately remains the eager full-history projection. PR2 owns projection
|
|
216
448
|
* virtualization/incrementalization; source acquisition above only controls snapshot safety.
|
|
@@ -249,51 +481,7 @@ export function entriesFromMessages(entries: readonly SessionMessageEntry[]): Tr
|
|
|
249
481
|
payload: { text: content.text, metadata: {}, source: content },
|
|
250
482
|
foldable: true,
|
|
251
483
|
});
|
|
252
|
-
if (content.type === "toolCall")
|
|
253
|
-
const result = results.get(content.id);
|
|
254
|
-
const resultText =
|
|
255
|
-
result?.content
|
|
256
|
-
.filter(part => part.type === "text")
|
|
257
|
-
.map(part => part.text)
|
|
258
|
-
.join("\n")
|
|
259
|
-
.trim() ?? "";
|
|
260
|
-
const canonicalPayload = {
|
|
261
|
-
text: composeToolText({
|
|
262
|
-
name: content.name,
|
|
263
|
-
args: content.arguments,
|
|
264
|
-
intent: content.intent,
|
|
265
|
-
resultText,
|
|
266
|
-
isError: result?.isError ?? false,
|
|
267
|
-
hasResult: results.has(content.id),
|
|
268
|
-
}),
|
|
269
|
-
metadata: {
|
|
270
|
-
name: content.name,
|
|
271
|
-
arguments: content.arguments,
|
|
272
|
-
intent: content.intent,
|
|
273
|
-
resultText,
|
|
274
|
-
isError: result?.isError ?? false,
|
|
275
|
-
hasResult: results.has(content.id),
|
|
276
|
-
detailsData: result?.details,
|
|
277
|
-
},
|
|
278
|
-
source: { call: content, result },
|
|
279
|
-
};
|
|
280
|
-
output.push(
|
|
281
|
-
buildToolTranscriptEntry({
|
|
282
|
-
canonicalPayload,
|
|
283
|
-
renderDescriptor: createToolTranscriptRenderDescriptor({
|
|
284
|
-
name: content.name,
|
|
285
|
-
args: content.arguments,
|
|
286
|
-
intent: content.intent,
|
|
287
|
-
resultContent: resultText,
|
|
288
|
-
isError: result?.isError,
|
|
289
|
-
hasResult: results.has(content.id),
|
|
290
|
-
detailsData: result?.details,
|
|
291
|
-
}),
|
|
292
|
-
capabilities: { copyable: true, foldable: true, rawViewable: true },
|
|
293
|
-
identity: { id: `tool:${content.id}`, label: content.name, display: "full" },
|
|
294
|
-
}),
|
|
295
|
-
);
|
|
296
|
-
}
|
|
484
|
+
if (content.type === "toolCall") output.push(toolEntry(content, results.get(content.id)));
|
|
297
485
|
});
|
|
298
486
|
}
|
|
299
487
|
if (message.role === "user" || message.role === "developer") {
|
|
@@ -322,15 +510,17 @@ function truncateThinking(text: string, expanded: boolean): string {
|
|
|
322
510
|
return text.length > limit ? `${text.slice(0, limit)}...` : text;
|
|
323
511
|
}
|
|
324
512
|
|
|
325
|
-
function readStableSnapshot(filePath: string, completeOffset: number):
|
|
513
|
+
function readStableSnapshot(filePath: string, completeOffset: number): SnapshotReadResult {
|
|
326
514
|
let fd: number | undefined;
|
|
327
515
|
try {
|
|
328
516
|
fd = fs.openSync(filePath, "r");
|
|
329
517
|
const before = fs.fstatSync(fd, { bigint: true });
|
|
330
|
-
if (before.size > BigInt(Number.MAX_SAFE_INTEGER)
|
|
518
|
+
if (before.size > BigInt(Number.MAX_SAFE_INTEGER)) return { status: "unavailable" };
|
|
331
519
|
const size = Number(before.size);
|
|
332
|
-
const
|
|
333
|
-
const
|
|
520
|
+
const offsetReset = completeOffset > size;
|
|
521
|
+
const offset = offsetReset ? 0 : completeOffset;
|
|
522
|
+
const prefix = readExactly(fd, 0, offset);
|
|
523
|
+
const tail = readExactly(fd, offset, size - offset);
|
|
334
524
|
const after = fs.fstatSync(fd, { bigint: true });
|
|
335
525
|
if (
|
|
336
526
|
before.dev !== after.dev ||
|
|
@@ -339,21 +529,34 @@ function readStableSnapshot(filePath: string, completeOffset: number): StableSna
|
|
|
339
529
|
before.mtimeNs !== after.mtimeNs ||
|
|
340
530
|
before.ctimeNs !== after.ctimeNs
|
|
341
531
|
)
|
|
342
|
-
return
|
|
343
|
-
return {
|
|
344
|
-
|
|
345
|
-
|
|
532
|
+
return { status: "unstable" };
|
|
533
|
+
return {
|
|
534
|
+
status: "stable",
|
|
535
|
+
snapshot: { identity: { dev: before.dev, ino: before.ino }, size, prefix, tail, offsetReset },
|
|
536
|
+
};
|
|
537
|
+
} catch (err) {
|
|
538
|
+
return err instanceof UnstableSnapshotError ? { status: "unstable" } : { status: "unavailable" };
|
|
346
539
|
} finally {
|
|
347
540
|
if (fd !== undefined) fs.closeSync(fd);
|
|
348
541
|
}
|
|
349
542
|
}
|
|
350
543
|
|
|
544
|
+
function asFullSnapshot(snapshot: StableSnapshot): StableSnapshot {
|
|
545
|
+
if (snapshot.prefix.length === 0) return snapshot;
|
|
546
|
+
return {
|
|
547
|
+
...snapshot,
|
|
548
|
+
prefix: Buffer.alloc(0),
|
|
549
|
+
tail: Buffer.concat([snapshot.prefix, snapshot.tail]),
|
|
550
|
+
offsetReset: true,
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
|
|
351
554
|
function readExactly(fd: number, position: number, length: number): Buffer {
|
|
352
555
|
const buffer = Buffer.alloc(length);
|
|
353
556
|
let offset = 0;
|
|
354
557
|
while (offset < length) {
|
|
355
558
|
const bytesRead = fs.readSync(fd, buffer, offset, length - offset, position + offset);
|
|
356
|
-
if (bytesRead === 0) throw new
|
|
559
|
+
if (bytesRead === 0) throw new UnstableSnapshotError("Short session file read");
|
|
357
560
|
offset += bytesRead;
|
|
358
561
|
}
|
|
359
562
|
return buffer;
|
|
@@ -372,14 +575,28 @@ function completePrefix(bytes: Buffer): { bytes: Buffer } | undefined {
|
|
|
372
575
|
return newline < 0 ? undefined : { bytes: bytes.subarray(0, newline + 1) };
|
|
373
576
|
}
|
|
374
577
|
|
|
375
|
-
function parseCompleteEntries(
|
|
578
|
+
function parseCompleteEntries(
|
|
579
|
+
bytes: Buffer,
|
|
580
|
+
): { entries: SessionMessageEntry[]; model?: string; hasPatchRecords: boolean } | null {
|
|
376
581
|
try {
|
|
377
582
|
const text = FATAL_UTF8_DECODER.decode(bytes);
|
|
378
|
-
|
|
583
|
+
let hasPatchRecords = false;
|
|
584
|
+
for (const line of text.split("\n")) {
|
|
585
|
+
if (!line.trim()) continue;
|
|
586
|
+
const record: unknown = JSON.parse(line);
|
|
587
|
+
if (
|
|
588
|
+
record !== null &&
|
|
589
|
+
typeof record === "object" &&
|
|
590
|
+
"type" in record &&
|
|
591
|
+
(record.type === "entry_patch" || record.type === "header_patch")
|
|
592
|
+
)
|
|
593
|
+
hasPatchRecords = true;
|
|
594
|
+
}
|
|
379
595
|
const parsed = parseSessionEntries(text);
|
|
380
596
|
return {
|
|
381
597
|
entries: parsed.filter((entry): entry is SessionMessageEntry => entry.type === "message"),
|
|
382
598
|
model: modelFromEntries(parsed),
|
|
599
|
+
hasPatchRecords,
|
|
383
600
|
};
|
|
384
601
|
} catch {
|
|
385
602
|
return null;
|
|
@@ -415,6 +632,7 @@ function cacheEntries(filePath: string, snapshot: StableSnapshot): ObserverCache
|
|
|
415
632
|
completeOffset: 0,
|
|
416
633
|
prefixDigest: digest(Buffer.alloc(0)),
|
|
417
634
|
entries: [],
|
|
635
|
+
...fullProjection([]),
|
|
418
636
|
};
|
|
419
637
|
}
|
|
420
638
|
const remainder = snapshot.tail.subarray(complete.bytes.length);
|
|
@@ -428,5 +646,6 @@ function cacheEntries(filePath: string, snapshot: StableSnapshot): ObserverCache
|
|
|
428
646
|
prefixDigest: digest(complete.bytes),
|
|
429
647
|
entries: parsed.entries,
|
|
430
648
|
model: parsed.model,
|
|
649
|
+
...fullProjection(parsed.entries),
|
|
431
650
|
};
|
|
432
651
|
}
|