@gajae-code/coding-agent 0.4.4 → 0.5.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/CHANGELOG.md +83 -0
- package/dist/types/cli/fast-help.d.ts +1 -0
- package/dist/types/cli/setup-cli.d.ts +2 -0
- package/dist/types/commands/harness.d.ts +6 -0
- package/dist/types/commands/setup.d.ts +6 -0
- package/dist/types/config/model-profile-activation.d.ts +11 -2
- package/dist/types/config/model-profiles.d.ts +7 -0
- package/dist/types/config/model-registry.d.ts +6 -0
- package/dist/types/config/model-resolver.d.ts +2 -0
- package/dist/types/config/models-config-schema.d.ts +35 -0
- package/dist/types/config/settings-schema.d.ts +4 -3
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/coordinator-mcp/server.d.ts +8 -2
- package/dist/types/gjc-runtime/team-runtime.d.ts +0 -1
- package/dist/types/gjc-runtime/tmux-common.d.ts +3 -0
- package/dist/types/harness-control-plane/finalize.d.ts +5 -0
- package/dist/types/harness-control-plane/owner.d.ts +1 -1
- package/dist/types/harness-control-plane/phase-rollup.d.ts +23 -0
- package/dist/types/harness-control-plane/receipt-ingest.d.ts +19 -0
- package/dist/types/harness-control-plane/receipt-spool.d.ts +19 -0
- package/dist/types/harness-control-plane/receipts.d.ts +46 -0
- package/dist/types/harness-control-plane/rpc-adapter.d.ts +3 -0
- package/dist/types/harness-control-plane/state-machine.d.ts +6 -1
- package/dist/types/harness-control-plane/types.d.ts +13 -1
- package/dist/types/hindsight/mental-models.d.ts +5 -5
- package/dist/types/main.d.ts +2 -2
- package/dist/types/modes/components/model-selector.d.ts +1 -12
- package/dist/types/modes/rpc/rpc-client.d.ts +2 -2
- package/dist/types/modes/rpc/rpc-types.d.ts +4 -1
- package/dist/types/modes/utils/abort-message.d.ts +4 -0
- package/dist/types/sdk.d.ts +5 -0
- package/dist/types/session/agent-session.d.ts +2 -0
- package/dist/types/session/blob-store.d.ts +20 -1
- package/dist/types/session/session-manager.d.ts +32 -6
- package/dist/types/session/streaming-output.d.ts +3 -2
- package/dist/types/session/tool-choice-queue.d.ts +6 -0
- package/dist/types/setup/hermes-setup.d.ts +7 -0
- package/dist/types/task/fork-context-advisory.d.ts +13 -0
- package/dist/types/task/receipt.d.ts +2 -0
- package/dist/types/task/roi-reconciliation.d.ts +27 -0
- package/dist/types/task/types.d.ts +17 -0
- package/dist/types/thinking-metadata.d.ts +16 -0
- package/dist/types/thinking.d.ts +3 -12
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tools/resolve.d.ts +0 -10
- package/dist/types/utils/tool-choice.d.ts +14 -1
- package/package.json +8 -7
- package/scripts/build-binary.ts +4 -0
- package/src/cli/fast-help.ts +80 -0
- package/src/cli/setup-cli.ts +12 -3
- package/src/cli.ts +112 -17
- package/src/commands/coordinator.ts +44 -1
- package/src/commands/harness.ts +128 -11
- package/src/commands/launch.ts +2 -2
- package/src/commands/mcp-serve.ts +3 -2
- package/src/commands/session.ts +3 -1
- package/src/commands/setup.ts +4 -0
- package/src/config/model-profile-activation.ts +15 -3
- package/src/config/model-profiles.ts +255 -56
- package/src/config/model-resolver.ts +9 -6
- package/src/config/models-config-schema.ts +2 -0
- package/src/config/settings-schema.ts +6 -3
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/server.ts +427 -193
- package/src/cursor.ts +46 -4
- package/src/defaults/gjc/skills/team/SKILL.md +3 -2
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +8 -2
- package/src/export/html/index.ts +13 -9
- package/src/gjc-runtime/launch-worktree.ts +12 -1
- package/src/gjc-runtime/session-state-sidecar.ts +38 -0
- package/src/gjc-runtime/team-runtime.ts +33 -7
- package/src/gjc-runtime/tmux-common.ts +15 -0
- package/src/gjc-runtime/tmux-sessions.ts +19 -11
- package/src/gjc-runtime/ultragoal-runtime.ts +505 -41
- package/src/gjc-runtime/workflow-manifest.generated.json +27 -1
- package/src/gjc-runtime/workflow-manifest.ts +16 -1
- package/src/harness-control-plane/finalize.ts +39 -5
- package/src/harness-control-plane/owner.ts +87 -28
- package/src/harness-control-plane/phase-rollup.ts +96 -0
- package/src/harness-control-plane/receipt-ingest.ts +127 -0
- package/src/harness-control-plane/receipt-spool.ts +128 -0
- package/src/harness-control-plane/receipts.ts +229 -1
- package/src/harness-control-plane/rpc-adapter.ts +8 -0
- package/src/harness-control-plane/state-machine.ts +27 -6
- package/src/harness-control-plane/storage.ts +23 -0
- package/src/harness-control-plane/types.ts +33 -1
- package/src/hindsight/mental-models.ts +17 -16
- package/src/internal-urls/docs-index.generated.ts +8 -7
- package/src/main.ts +7 -3
- package/src/modes/components/assistant-message.ts +26 -14
- package/src/modes/components/diff.ts +97 -0
- package/src/modes/components/model-selector.ts +353 -181
- package/src/modes/components/status-line.ts +6 -6
- package/src/modes/components/tool-execution.ts +30 -13
- package/src/modes/controllers/event-controller.ts +5 -4
- package/src/modes/controllers/selector-controller.ts +33 -42
- package/src/modes/interactive-mode.ts +4 -5
- package/src/modes/print-mode.ts +1 -1
- package/src/modes/rpc/rpc-client.ts +3 -2
- package/src/modes/rpc/rpc-mode.ts +44 -14
- package/src/modes/rpc/rpc-types.ts +5 -2
- package/src/modes/shared/agent-wire/command-dispatch.ts +10 -5
- package/src/modes/shared/agent-wire/command-validation.ts +11 -0
- package/src/modes/theme/theme.ts +2 -2
- package/src/modes/utils/abort-message.ts +41 -0
- package/src/modes/utils/context-usage.ts +15 -8
- package/src/modes/utils/ui-helpers.ts +5 -6
- package/src/sdk.ts +38 -6
- package/src/secrets/obfuscator.ts +102 -27
- package/src/session/agent-session.ts +121 -25
- package/src/session/blob-store.ts +89 -3
- package/src/session/session-manager.ts +328 -57
- package/src/session/streaming-output.ts +185 -122
- package/src/session/tool-choice-queue.ts +23 -0
- package/src/setup/hermes/templates/operator-instructions.v1.md +3 -2
- package/src/setup/hermes-setup.ts +63 -8
- package/src/task/executor.ts +69 -6
- package/src/task/fork-context-advisory.ts +99 -0
- package/src/task/index.ts +31 -2
- package/src/task/receipt.ts +7 -0
- package/src/task/render.ts +21 -1
- package/src/task/roi-reconciliation.ts +90 -0
- package/src/task/types.ts +15 -0
- package/src/thinking-metadata.ts +51 -0
- package/src/thinking.ts +26 -46
- package/src/tools/bash.ts +1 -1
- package/src/tools/index.ts +4 -2
- package/src/tools/resolve.ts +93 -18
- package/src/tools/subagent-render.ts +10 -1
- package/src/utils/edit-mode.ts +1 -1
- package/src/utils/title-generator.ts +16 -2
- package/src/utils/tool-choice.ts +45 -16
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
} from "../../session/messages";
|
|
28
28
|
import type { SessionContext } from "../../session/session-manager";
|
|
29
29
|
import { formatBytes, formatDuration } from "../../tools/render-utils";
|
|
30
|
+
import { buildAbortDisplayMessage } from "./abort-message";
|
|
30
31
|
|
|
31
32
|
type TextBlock = { type: "text"; text: string };
|
|
32
33
|
interface RenderInitialMessagesOptions {
|
|
@@ -319,12 +320,10 @@ export class UiHelpers {
|
|
|
319
320
|
!isAbortedSilently && (message.stopReason === "aborted" || message.stopReason === "error");
|
|
320
321
|
const errorMessage = hasErrorStop
|
|
321
322
|
? message.stopReason === "aborted"
|
|
322
|
-
? (
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
: "Operation aborted";
|
|
327
|
-
})()
|
|
323
|
+
? buildAbortDisplayMessage({
|
|
324
|
+
errorMessage: message.errorMessage,
|
|
325
|
+
retryAttempt: this.ctx.session.retryAttempt,
|
|
326
|
+
})
|
|
328
327
|
: message.errorMessage || "Error"
|
|
329
328
|
: null;
|
|
330
329
|
|
package/src/sdk.ts
CHANGED
|
@@ -133,7 +133,7 @@ import { ToolContextStore } from "./tools/context";
|
|
|
133
133
|
import { getImageGenTools } from "./tools/image-gen";
|
|
134
134
|
import { wrapToolWithMetaNotice } from "./tools/output-meta";
|
|
135
135
|
import { EventBus } from "./utils/event-bus";
|
|
136
|
-
import { buildNamedToolChoice } from "./utils/tool-choice";
|
|
136
|
+
import { buildNamedToolChoice, buildNamedToolChoiceResult } from "./utils/tool-choice";
|
|
137
137
|
import { buildWorkspaceTree, type WorkspaceTree } from "./workspace-tree";
|
|
138
138
|
|
|
139
139
|
type AsyncResultEntry = {
|
|
@@ -234,6 +234,8 @@ export interface CreateAgentSessionOptions {
|
|
|
234
234
|
modelPattern?: string;
|
|
235
235
|
/** Thinking selector. Default: from settings, else unset */
|
|
236
236
|
thinkingLevel?: ThinkingLevel;
|
|
237
|
+
/** Runtime substitution metadata for the initial model_change session event. */
|
|
238
|
+
modelSubstitution?: { requestedModel: Model; reason: string };
|
|
237
239
|
/** Models available for cycling (Ctrl+P in interactive mode) */
|
|
238
240
|
scopedModels?: ScopedModelSelection[];
|
|
239
241
|
|
|
@@ -1212,6 +1214,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1212
1214
|
const m = session.model;
|
|
1213
1215
|
return m ? buildNamedToolChoice(name, m) : undefined;
|
|
1214
1216
|
},
|
|
1217
|
+
buildToolChoiceResult: name => buildNamedToolChoiceResult(name, session.model),
|
|
1215
1218
|
steer: msg =>
|
|
1216
1219
|
session.agent.steer({
|
|
1217
1220
|
role: "custom",
|
|
@@ -1622,9 +1625,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1622
1625
|
};
|
|
1623
1626
|
|
|
1624
1627
|
const toolNamesFromRegistry = Array.from(toolRegistry.keys());
|
|
1625
|
-
const requestedToolNames =
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
+
const requestedToolNames = options.toolNames
|
|
1629
|
+
? [
|
|
1630
|
+
...new Set([
|
|
1631
|
+
...options.toolNames.map(name => name.toLowerCase()),
|
|
1632
|
+
...(settings.get("goal.enabled") ? ["goal"] : []),
|
|
1633
|
+
]),
|
|
1634
|
+
]
|
|
1635
|
+
: toolNamesFromRegistry;
|
|
1628
1636
|
const normalizedRequested = requestedToolNames.filter(name => toolRegistry.has(name));
|
|
1629
1637
|
const requestedToolNameSet = new Set(normalizedRequested);
|
|
1630
1638
|
// Effective discovery mode only covers built-in tools; MCP tool discovery
|
|
@@ -1635,7 +1643,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1635
1643
|
const defaultInactiveToolNames = new Set(
|
|
1636
1644
|
registeredTools.filter(tool => tool.definition.defaultInactive).map(tool => tool.definition.name),
|
|
1637
1645
|
);
|
|
1638
|
-
const requestedActiveToolNames = normalizedRequested
|
|
1646
|
+
const requestedActiveToolNames = normalizedRequested;
|
|
1639
1647
|
const initialRequestedActiveToolNames = options.toolNames
|
|
1640
1648
|
? requestedActiveToolNames
|
|
1641
1649
|
: requestedActiveToolNames.filter(name => !defaultInactiveToolNames.has(name));
|
|
@@ -1893,6 +1901,19 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1893
1901
|
}
|
|
1894
1902
|
return result;
|
|
1895
1903
|
},
|
|
1904
|
+
onToolChoiceIncapability: event => {
|
|
1905
|
+
const droppedLabel = session?.toolChoiceQueue.degradeInFlight(event.reason);
|
|
1906
|
+
logger.debug("Dropped in-flight tool choice after runtime incapability", {
|
|
1907
|
+
droppedLabel,
|
|
1908
|
+
api: event.api,
|
|
1909
|
+
provider: event.provider,
|
|
1910
|
+
model: event.model,
|
|
1911
|
+
requestedLevel: event.requestedLevel,
|
|
1912
|
+
resolvedLevel: event.resolvedLevel,
|
|
1913
|
+
reason: event.reason,
|
|
1914
|
+
registryKey: event.registryKey,
|
|
1915
|
+
});
|
|
1916
|
+
},
|
|
1896
1917
|
intentTracing: !!intentField,
|
|
1897
1918
|
getToolChoice: () => session?.nextToolChoice(),
|
|
1898
1919
|
telemetry: options.telemetry,
|
|
@@ -1907,7 +1928,18 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1907
1928
|
} else {
|
|
1908
1929
|
// Save initial model, thinking level, and service tier for new sessions so they can be restored on resume.
|
|
1909
1930
|
if (model) {
|
|
1910
|
-
|
|
1931
|
+
const substitution = options.modelSubstitution;
|
|
1932
|
+
sessionManager.appendModelChange(
|
|
1933
|
+
`${model.provider}/${model.id}`,
|
|
1934
|
+
undefined,
|
|
1935
|
+
substitution
|
|
1936
|
+
? {
|
|
1937
|
+
previousModel: `${substitution.requestedModel.provider}/${substitution.requestedModel.id}`,
|
|
1938
|
+
reason: substitution.reason,
|
|
1939
|
+
thinkingLevel: thinkingLevel ?? null,
|
|
1940
|
+
}
|
|
1941
|
+
: undefined,
|
|
1942
|
+
);
|
|
1911
1943
|
}
|
|
1912
1944
|
sessionManager.appendThinkingLevelChange(thinkingLevel);
|
|
1913
1945
|
if (initialServiceTier) {
|
|
@@ -73,9 +73,24 @@ export class SecretObfuscator {
|
|
|
73
73
|
/** Replace-mode plain mappings: secret → replacement */
|
|
74
74
|
#replaceMappings = new Map<string, string>();
|
|
75
75
|
|
|
76
|
+
/** Replace-mode plain mappings sorted longest-first for deterministic longest-match replacement. */
|
|
77
|
+
#sortedReplaceMappings: Array<{ secret: string; replacement: string }> = [];
|
|
78
|
+
|
|
79
|
+
/** Obfuscate-mode plain and regex-discovered mappings sorted longest-first. */
|
|
80
|
+
#sortedObfuscateMappings: Array<{ secret: string; index: number; placeholder: string }> = [];
|
|
81
|
+
|
|
82
|
+
/** Reverse lookup for obfuscate-mode secrets to avoid scanning mappings. */
|
|
83
|
+
#obfuscateIndexBySecret = new Map<string, number>();
|
|
84
|
+
|
|
76
85
|
/** Reverse lookup for deobfuscation: placeholder → secret */
|
|
77
86
|
#deobfuscateMap = new Map<string, string>();
|
|
78
87
|
|
|
88
|
+
/** Combined plain-secret regex cache for single-pass replacement. */
|
|
89
|
+
#combinedPlainRegex: RegExp | undefined;
|
|
90
|
+
#combinedPlainReplacementBySecret = new Map<string, string>();
|
|
91
|
+
#combinedPlainRegexDirty = true;
|
|
92
|
+
#useSequentialPlainReplacement = false;
|
|
93
|
+
|
|
79
94
|
/** Next available index for regex match discoveries */
|
|
80
95
|
#nextIndex: number;
|
|
81
96
|
|
|
@@ -93,6 +108,7 @@ export class SecretObfuscator {
|
|
|
93
108
|
this.#plainMappings.set(entry.content, index);
|
|
94
109
|
this.#obfuscateMappings.set(index, { secret: entry.content, placeholder });
|
|
95
110
|
this.#deobfuscateMap.set(placeholder, entry.content);
|
|
111
|
+
this.#obfuscateIndexBySecret.set(entry.content, index);
|
|
96
112
|
index++;
|
|
97
113
|
} else {
|
|
98
114
|
// replace mode
|
|
@@ -111,6 +127,16 @@ export class SecretObfuscator {
|
|
|
111
127
|
}
|
|
112
128
|
|
|
113
129
|
this.#nextIndex = index;
|
|
130
|
+
this.#sortedReplaceMappings = [...this.#replaceMappings]
|
|
131
|
+
.sort((a, b) => b[0].length - a[0].length)
|
|
132
|
+
.map(([secret, replacement]) => ({ secret, replacement }));
|
|
133
|
+
this.#sortedObfuscateMappings = [...this.#plainMappings]
|
|
134
|
+
.sort((a, b) => b[0].length - a[0].length)
|
|
135
|
+
.map(([secret, mappingIndex]) => ({
|
|
136
|
+
secret,
|
|
137
|
+
index: mappingIndex,
|
|
138
|
+
placeholder: this.#obfuscateMappings.get(mappingIndex)!.placeholder,
|
|
139
|
+
}));
|
|
114
140
|
this.#hasAny = entries.length > 0;
|
|
115
141
|
}
|
|
116
142
|
|
|
@@ -121,18 +147,7 @@ export class SecretObfuscator {
|
|
|
121
147
|
/** Obfuscate all secrets in text. Bidirectional placeholders for obfuscate mode, one-way for replace. */
|
|
122
148
|
obfuscate(text: string): string {
|
|
123
149
|
if (!this.#hasAny) return text;
|
|
124
|
-
let result = text;
|
|
125
|
-
|
|
126
|
-
// 1. Process replace-mode plain secrets
|
|
127
|
-
for (const [secret, replacement] of [...this.#replaceMappings].sort((a, b) => b[0].length - a[0].length)) {
|
|
128
|
-
result = replaceAll(result, secret, replacement);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// 2. Process obfuscate-mode plain secrets
|
|
132
|
-
for (const [secret, index] of [...this.#plainMappings].sort((a, b) => b[0].length - a[0].length)) {
|
|
133
|
-
const mapping = this.#obfuscateMappings.get(index)!;
|
|
134
|
-
result = replaceAll(result, secret, mapping.placeholder);
|
|
135
|
-
}
|
|
150
|
+
let result = this.#obfuscatePlainMappings(text);
|
|
136
151
|
|
|
137
152
|
// 3. Process regex entries — discover new matches
|
|
138
153
|
for (const entry of this.#regexEntries) {
|
|
@@ -160,6 +175,9 @@ export class SecretObfuscator {
|
|
|
160
175
|
const placeholder = buildPlaceholder(index);
|
|
161
176
|
this.#obfuscateMappings.set(index, { secret: matchValue, placeholder });
|
|
162
177
|
this.#deobfuscateMap.set(placeholder, matchValue);
|
|
178
|
+
this.#obfuscateIndexBySecret.set(matchValue, index);
|
|
179
|
+
this.#insertSortedObfuscateMapping({ secret: matchValue, index, placeholder });
|
|
180
|
+
this.#combinedPlainRegexDirty = true;
|
|
163
181
|
}
|
|
164
182
|
const mapping = this.#obfuscateMappings.get(index)!;
|
|
165
183
|
result = replaceAll(result, matchValue, mapping.placeholder);
|
|
@@ -186,15 +204,74 @@ export class SecretObfuscator {
|
|
|
186
204
|
|
|
187
205
|
/** Find the obfuscate index for a known secret value. */
|
|
188
206
|
#findObfuscateIndex(secret: string): number | undefined {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
207
|
+
return this.#obfuscateIndexBySecret.get(secret);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
#insertSortedObfuscateMapping(mapping: { secret: string; index: number; placeholder: string }): void {
|
|
211
|
+
let lo = 0;
|
|
212
|
+
let hi = this.#sortedObfuscateMappings.length;
|
|
213
|
+
while (lo < hi) {
|
|
214
|
+
const mid = (lo + hi) >> 1;
|
|
215
|
+
if (this.#sortedObfuscateMappings[mid]!.secret.length < mapping.secret.length) {
|
|
216
|
+
hi = mid;
|
|
217
|
+
} else {
|
|
218
|
+
lo = mid + 1;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
this.#sortedObfuscateMappings.splice(lo, 0, mapping);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
#obfuscatePlainMappings(text: string): string {
|
|
225
|
+
this.#ensureCombinedPlainRegex();
|
|
226
|
+
if (this.#useSequentialPlainReplacement) return this.#obfuscatePlainMappingsSequential(text);
|
|
227
|
+
if (!this.#combinedPlainRegex) return text;
|
|
228
|
+
return text.replace(
|
|
229
|
+
this.#combinedPlainRegex,
|
|
230
|
+
match => this.#combinedPlainReplacementBySecret.get(match) ?? match,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
192
233
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
234
|
+
#obfuscatePlainMappingsSequential(text: string): string {
|
|
235
|
+
let result = text;
|
|
236
|
+
for (const mapping of this.#sortedReplaceMappings) {
|
|
237
|
+
result = replaceAll(result, mapping.secret, mapping.replacement);
|
|
238
|
+
}
|
|
239
|
+
for (const mapping of this.#sortedObfuscateMappings) {
|
|
240
|
+
result = replaceAll(result, mapping.secret, mapping.placeholder);
|
|
196
241
|
}
|
|
197
|
-
return
|
|
242
|
+
return result;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
#ensureCombinedPlainRegex(): void {
|
|
246
|
+
if (!this.#combinedPlainRegexDirty) return;
|
|
247
|
+
this.#combinedPlainRegexDirty = false;
|
|
248
|
+
this.#combinedPlainReplacementBySecret = new Map<string, string>();
|
|
249
|
+
|
|
250
|
+
const mappings = [
|
|
251
|
+
...this.#sortedReplaceMappings.map(mapping => ({ secret: mapping.secret, replacement: mapping.replacement })),
|
|
252
|
+
...this.#sortedObfuscateMappings.map(mapping => ({
|
|
253
|
+
secret: mapping.secret,
|
|
254
|
+
replacement: mapping.placeholder,
|
|
255
|
+
})),
|
|
256
|
+
];
|
|
257
|
+
|
|
258
|
+
this.#useSequentialPlainReplacement = mappings.some((mapping, index) =>
|
|
259
|
+
mappings.some(
|
|
260
|
+
(other, otherIndex) =>
|
|
261
|
+
other.secret.length > 0 &&
|
|
262
|
+
(mapping.replacement.includes(other.secret) ||
|
|
263
|
+
(index !== otherIndex &&
|
|
264
|
+
(mapping.secret.includes(other.secret) || other.secret.includes(mapping.secret)))),
|
|
265
|
+
),
|
|
266
|
+
);
|
|
267
|
+
for (const mapping of mappings) {
|
|
268
|
+
if (!this.#combinedPlainReplacementBySecret.has(mapping.secret))
|
|
269
|
+
this.#combinedPlainReplacementBySecret.set(mapping.secret, mapping.replacement);
|
|
270
|
+
}
|
|
271
|
+
this.#combinedPlainRegex =
|
|
272
|
+
mappings.length > 0
|
|
273
|
+
? new RegExp(mappings.map(mapping => escapeRegex(mapping.secret)).join("|"), "g")
|
|
274
|
+
: undefined;
|
|
198
275
|
}
|
|
199
276
|
}
|
|
200
277
|
|
|
@@ -238,14 +315,12 @@ export function obfuscateMessages(obfuscator: SecretObfuscator, messages: Messag
|
|
|
238
315
|
|
|
239
316
|
/** Replace all occurrences of `search` in `text` with `replacement`. */
|
|
240
317
|
function replaceAll(text: string, search: string, replacement: string): string {
|
|
241
|
-
if (search.length === 0) return text;
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
248
|
-
return result;
|
|
318
|
+
if (search.length === 0 || !text.includes(search)) return text;
|
|
319
|
+
return text.split(search).join(replacement);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function escapeRegex(value: string): string {
|
|
323
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
249
324
|
}
|
|
250
325
|
|
|
251
326
|
/** Deep-walk an object, transforming all string values. */
|
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
calculatePromptTokens,
|
|
42
42
|
collectEntriesForBranchSummary,
|
|
43
43
|
compact,
|
|
44
|
+
estimateMessageTokensHeuristic,
|
|
44
45
|
estimateTokens,
|
|
45
46
|
generateBranchSummary,
|
|
46
47
|
generateHandoff,
|
|
@@ -243,7 +244,7 @@ import { parseCommandArgs } from "../utils/command-args";
|
|
|
243
244
|
import { type EditMode, resolveEditMode } from "../utils/edit-mode";
|
|
244
245
|
import { resolveFileDisplayMode } from "../utils/file-display-mode";
|
|
245
246
|
import { extractFileMentions, generateFileMentionMessages } from "../utils/file-mentions";
|
|
246
|
-
import { buildNamedToolChoice } from "../utils/tool-choice";
|
|
247
|
+
import { buildNamedToolChoice, buildNamedToolChoiceResult } from "../utils/tool-choice";
|
|
247
248
|
import type { AuthStorage } from "./auth-storage";
|
|
248
249
|
import type { ClientBridge, ClientBridgePermissionOption, ClientBridgePermissionOutcome } from "./client-bridge";
|
|
249
250
|
import {
|
|
@@ -838,6 +839,8 @@ export type BeforeAgentStartInternalMessage = Pick<
|
|
|
838
839
|
"customType" | "content" | "display" | "details" | "attribution"
|
|
839
840
|
>;
|
|
840
841
|
|
|
842
|
+
type ProviderReplaySourceCacheEntry = { source: string; hash: bigint };
|
|
843
|
+
|
|
841
844
|
/**
|
|
842
845
|
* Internal (first-party, non-user-hook) contributor invoked at the active
|
|
843
846
|
* before-agent-start point alongside the extension runner. Returns an optional
|
|
@@ -862,6 +865,7 @@ export class AgentSession {
|
|
|
862
865
|
|
|
863
866
|
#scopedModels: ScopedModelSelection[];
|
|
864
867
|
#thinkingLevel: ThinkingLevel | undefined;
|
|
868
|
+
#activeModelProfile: string | undefined;
|
|
865
869
|
#promptTemplates: PromptTemplate[];
|
|
866
870
|
#slashCommands: FileSlashCommand[];
|
|
867
871
|
|
|
@@ -1055,6 +1059,7 @@ export class AgentSession {
|
|
|
1055
1059
|
#pendingAgentEndEmit: AgentSessionEvent | undefined;
|
|
1056
1060
|
#obfuscator: SecretObfuscator | undefined;
|
|
1057
1061
|
#checkpointState: CheckpointState | undefined = undefined;
|
|
1062
|
+
#providerReplaySourceCache = new WeakMap<AgentMessage, ProviderReplaySourceCacheEntry>();
|
|
1058
1063
|
#pendingRewindReport: string | undefined = undefined;
|
|
1059
1064
|
#lastSuccessfulYieldToolCallId: string | undefined = undefined;
|
|
1060
1065
|
#promptGeneration = 0;
|
|
@@ -1418,7 +1423,7 @@ export class AgentSession {
|
|
|
1418
1423
|
recordSkip("unsupported-role");
|
|
1419
1424
|
return undefined;
|
|
1420
1425
|
}
|
|
1421
|
-
const cloned =
|
|
1426
|
+
const cloned = cloneJsonValueForForkSeed(message) as Message;
|
|
1422
1427
|
if ("providerPayload" in cloned) {
|
|
1423
1428
|
delete (cloned as { providerPayload?: unknown }).providerPayload;
|
|
1424
1429
|
}
|
|
@@ -1465,7 +1470,7 @@ export class AgentSession {
|
|
|
1465
1470
|
}
|
|
1466
1471
|
return {
|
|
1467
1472
|
messages,
|
|
1468
|
-
agentMessages: messages.map(message =>
|
|
1473
|
+
agentMessages: messages.map(message => cloneJsonValueForForkSeed(message) as AgentMessage),
|
|
1469
1474
|
metadata: {
|
|
1470
1475
|
sourceSessionId: this.sessionId,
|
|
1471
1476
|
parentMessageCount: providerMessages.length,
|
|
@@ -4388,7 +4393,7 @@ export class AgentSession {
|
|
|
4388
4393
|
return false;
|
|
4389
4394
|
}
|
|
4390
4395
|
|
|
4391
|
-
const previousTools = this.getActiveToolNames()
|
|
4396
|
+
const previousTools = this.getActiveToolNames();
|
|
4392
4397
|
const goalTools = [...new Set([...previousTools, "goal"])];
|
|
4393
4398
|
await this.#goalRuntime.createGoal({ objective: pendingGoal.objective });
|
|
4394
4399
|
await this.setActiveToolsByName(goalTools);
|
|
@@ -4587,7 +4592,7 @@ export class AgentSession {
|
|
|
4587
4592
|
: { role: "user" as const, content: userContent, attribution: promptAttribution, timestamp: Date.now() };
|
|
4588
4593
|
await this.refreshGjcSubskillTools();
|
|
4589
4594
|
|
|
4590
|
-
if (eagerTodoPrelude) {
|
|
4595
|
+
if (eagerTodoPrelude?.toolChoice) {
|
|
4591
4596
|
this.#toolChoiceQueue.pushOnce(eagerTodoPrelude.toolChoice, {
|
|
4592
4597
|
label: "eager-todo",
|
|
4593
4598
|
});
|
|
@@ -4748,6 +4753,9 @@ export class AgentSession {
|
|
|
4748
4753
|
if (lastAssistant && !options?.skipCompactionCheck) {
|
|
4749
4754
|
await this.#checkCompaction(lastAssistant, false);
|
|
4750
4755
|
}
|
|
4756
|
+
if (!options?.skipCompactionCheck) {
|
|
4757
|
+
await this.#checkEstimatedContextBeforePrompt();
|
|
4758
|
+
}
|
|
4751
4759
|
|
|
4752
4760
|
// Build messages array (session context, eager todo prelude, then active prompt message)
|
|
4753
4761
|
const messages: AgentMessage[] = [];
|
|
@@ -5727,6 +5735,14 @@ export class AgentSession {
|
|
|
5727
5735
|
await this.#syncEditToolModeAfterModelChange(previousEditMode);
|
|
5728
5736
|
}
|
|
5729
5737
|
|
|
5738
|
+
setActiveModelProfile(name: string | undefined): void {
|
|
5739
|
+
this.#activeModelProfile = name;
|
|
5740
|
+
}
|
|
5741
|
+
|
|
5742
|
+
getActiveModelProfile(): string | undefined {
|
|
5743
|
+
return this.#activeModelProfile;
|
|
5744
|
+
}
|
|
5745
|
+
|
|
5730
5746
|
/**
|
|
5731
5747
|
* Set model temporarily (for this session only).
|
|
5732
5748
|
* Validates API key, saves to session log but NOT to settings.
|
|
@@ -6063,6 +6079,9 @@ export class AgentSession {
|
|
|
6063
6079
|
return undefined;
|
|
6064
6080
|
}
|
|
6065
6081
|
|
|
6082
|
+
// getBranch() returns materialized copies for blob-externalized entries, so
|
|
6083
|
+
// the pruning mutations must be written back into the canonical store.
|
|
6084
|
+
this.sessionManager.applyEntryMessageUpdates(result.prunedEntries);
|
|
6066
6085
|
await this.sessionManager.rewriteEntries();
|
|
6067
6086
|
const sessionContext = this.buildDisplaySessionContext();
|
|
6068
6087
|
this.agent.replaceMessages(sessionContext.messages);
|
|
@@ -6507,12 +6526,18 @@ export class AgentSession {
|
|
|
6507
6526
|
// Case 2: Threshold - turn succeeded but context is getting large
|
|
6508
6527
|
// Skip if this was an error (non-overflow errors don't have usage data)
|
|
6509
6528
|
if (assistantMessage.stopReason === "error") return;
|
|
6510
|
-
const pruneResult = await this.#pruneToolOutputs();
|
|
6511
6529
|
let contextTokens = calculateContextTokens(assistantMessage.usage);
|
|
6530
|
+
const maxOutputTokens = this.model?.maxTokens ?? 0;
|
|
6531
|
+
// Cache-epoch invariant: pruning rewrites already-sent toolResult history,
|
|
6532
|
+
// which breaks the provider prompt-cache prefix mid-epoch. Only prune at a
|
|
6533
|
+
// sanctioned maintenance boundary, i.e. when the un-pruned context already
|
|
6534
|
+
// crosses the compaction threshold. Pruning may then avert full compaction.
|
|
6535
|
+
if (!shouldCompact(contextTokens, contextWindow, compactionSettings, maxOutputTokens)) return;
|
|
6536
|
+
const pruneResult = await this.#pruneToolOutputs();
|
|
6512
6537
|
if (pruneResult) {
|
|
6513
6538
|
contextTokens = Math.max(0, contextTokens - pruneResult.tokensSaved);
|
|
6514
6539
|
}
|
|
6515
|
-
if (shouldCompact(contextTokens, contextWindow, compactionSettings,
|
|
6540
|
+
if (shouldCompact(contextTokens, contextWindow, compactionSettings, maxOutputTokens)) {
|
|
6516
6541
|
// Try promotion first — if a larger model is available, switch instead of compacting
|
|
6517
6542
|
const promoted = await this.#tryContextPromotion(assistantMessage);
|
|
6518
6543
|
if (!promoted) {
|
|
@@ -6520,6 +6545,31 @@ export class AgentSession {
|
|
|
6520
6545
|
}
|
|
6521
6546
|
}
|
|
6522
6547
|
}
|
|
6548
|
+
|
|
6549
|
+
async #checkEstimatedContextBeforePrompt(): Promise<void> {
|
|
6550
|
+
const model = this.model;
|
|
6551
|
+
if (!model) return;
|
|
6552
|
+
const contextWindow = model.contextWindow ?? 0;
|
|
6553
|
+
if (contextWindow <= 0) return;
|
|
6554
|
+
const compactionSettings = this.settings.getGroup("compaction");
|
|
6555
|
+
if (!compactionSettings.enabled || compactionSettings.strategy === "off") return;
|
|
6556
|
+
|
|
6557
|
+
let contextTokens = this.#estimateContextTokens().tokens;
|
|
6558
|
+
const maxOutputTokens = model.maxTokens ?? 0;
|
|
6559
|
+
if (!shouldCompact(contextTokens, contextWindow, compactionSettings, maxOutputTokens)) return;
|
|
6560
|
+
|
|
6561
|
+
const pruneResult = await this.#pruneToolOutputs();
|
|
6562
|
+
if (pruneResult) {
|
|
6563
|
+
contextTokens = Math.max(0, contextTokens - pruneResult.tokensSaved);
|
|
6564
|
+
}
|
|
6565
|
+
if (shouldCompact(contextTokens, contextWindow, compactionSettings, maxOutputTokens)) {
|
|
6566
|
+
await this.#runAutoCompaction("threshold", false, false, {
|
|
6567
|
+
continueAfterMaintenance: false,
|
|
6568
|
+
deferHandoffMaintenance: false,
|
|
6569
|
+
});
|
|
6570
|
+
}
|
|
6571
|
+
}
|
|
6572
|
+
|
|
6523
6573
|
#assistantEndedWithSuccessfulYield(assistantMessage: AssistantMessage): boolean {
|
|
6524
6574
|
const toolCallId = this.#lastSuccessfulYieldToolCallId;
|
|
6525
6575
|
if (!toolCallId) return false;
|
|
@@ -6617,7 +6667,7 @@ export class AgentSession {
|
|
|
6617
6667
|
});
|
|
6618
6668
|
}
|
|
6619
6669
|
|
|
6620
|
-
#createEagerTodoPrelude(promptText: string): { message: AgentMessage; toolChoice
|
|
6670
|
+
#createEagerTodoPrelude(promptText: string): { message: AgentMessage; toolChoice?: ToolChoice } | undefined {
|
|
6621
6671
|
const eagerTodosEnabled = this.settings.get("todo.eager");
|
|
6622
6672
|
const todosEnabled = this.settings.get("todo.enabled");
|
|
6623
6673
|
if (!eagerTodosEnabled || !todosEnabled) {
|
|
@@ -6651,13 +6701,15 @@ export class AgentSession {
|
|
|
6651
6701
|
return undefined;
|
|
6652
6702
|
}
|
|
6653
6703
|
|
|
6654
|
-
const
|
|
6655
|
-
|
|
6656
|
-
|
|
6704
|
+
const todoWriteToolChoiceResult = buildNamedToolChoiceResult("todo_write", this.model);
|
|
6705
|
+
const todoWriteToolChoice = todoWriteToolChoiceResult.exactNamed ? todoWriteToolChoiceResult.choice : undefined;
|
|
6706
|
+
if (!todoWriteToolChoiceResult.exactNamed) {
|
|
6707
|
+
logger.debug("Eager todo enforcement degraded; sending reminder without forced tool choice", {
|
|
6657
6708
|
modelApi: this.model?.api,
|
|
6658
6709
|
modelId: this.model?.id,
|
|
6710
|
+
resolvedLevel: todoWriteToolChoiceResult.resolved?.resolvedLevel,
|
|
6711
|
+
reason: todoWriteToolChoiceResult.resolved?.reason,
|
|
6659
6712
|
});
|
|
6660
|
-
return undefined;
|
|
6661
6713
|
}
|
|
6662
6714
|
|
|
6663
6715
|
const eagerTodoReminder = prompt.render(eagerTodoPrompt);
|
|
@@ -7039,11 +7091,37 @@ export class AgentSession {
|
|
|
7039
7091
|
}
|
|
7040
7092
|
}
|
|
7041
7093
|
|
|
7094
|
+
#getProviderReplaySource(message: AgentMessage): ProviderReplaySourceCacheEntry {
|
|
7095
|
+
const cached = this.#providerReplaySourceCache.get(message);
|
|
7096
|
+
if (cached) return cached;
|
|
7097
|
+
const source = JSON.stringify(this.#normalizeSessionMessageForProviderReplay(message));
|
|
7098
|
+
const hash = this.#hashProviderReplaySource(source);
|
|
7099
|
+
const entry = { source, hash };
|
|
7100
|
+
this.#providerReplaySourceCache.set(message, entry);
|
|
7101
|
+
return entry;
|
|
7102
|
+
}
|
|
7103
|
+
|
|
7104
|
+
#hashProviderReplaySource(source: string): bigint {
|
|
7105
|
+
return Bun.hash.xxHash64(source);
|
|
7106
|
+
}
|
|
7107
|
+
|
|
7042
7108
|
#didSessionMessagesChange(previousMessages: AgentMessage[], nextMessages: AgentMessage[]): boolean {
|
|
7043
|
-
return
|
|
7044
|
-
|
|
7045
|
-
|
|
7046
|
-
|
|
7109
|
+
if (previousMessages.length !== nextMessages.length) return true;
|
|
7110
|
+
|
|
7111
|
+
const previousSources: ProviderReplaySourceCacheEntry[] = [];
|
|
7112
|
+
const nextSources: ProviderReplaySourceCacheEntry[] = [];
|
|
7113
|
+
for (let i = 0; i < previousMessages.length; i++) {
|
|
7114
|
+
const previous = this.#getProviderReplaySource(previousMessages[i]!);
|
|
7115
|
+
const next = this.#getProviderReplaySource(nextMessages[i]!);
|
|
7116
|
+
if (previous.hash !== next.hash) return true;
|
|
7117
|
+
previousSources.push(previous);
|
|
7118
|
+
nextSources.push(next);
|
|
7119
|
+
}
|
|
7120
|
+
|
|
7121
|
+
for (let i = 0; i < previousSources.length; i++) {
|
|
7122
|
+
if (previousSources[i]!.source !== nextSources[i]!.source) return true;
|
|
7123
|
+
}
|
|
7124
|
+
return false;
|
|
7047
7125
|
}
|
|
7048
7126
|
|
|
7049
7127
|
#getModelKey(model: Model): string {
|
|
@@ -7248,17 +7326,24 @@ export class AgentSession {
|
|
|
7248
7326
|
reason: "overflow" | "threshold" | "idle",
|
|
7249
7327
|
willRetry: boolean,
|
|
7250
7328
|
deferred = false,
|
|
7329
|
+
options?: { continueAfterMaintenance?: boolean; deferHandoffMaintenance?: boolean },
|
|
7251
7330
|
): Promise<void> {
|
|
7252
7331
|
const compactionSettings = this.settings.getGroup("compaction");
|
|
7253
7332
|
if (compactionSettings.strategy === "off") return;
|
|
7254
7333
|
if (reason !== "idle" && !compactionSettings.enabled) return;
|
|
7255
7334
|
const generation = this.#promptGeneration;
|
|
7256
|
-
if (
|
|
7335
|
+
if (
|
|
7336
|
+
options?.deferHandoffMaintenance !== false &&
|
|
7337
|
+
!deferred &&
|
|
7338
|
+
reason !== "overflow" &&
|
|
7339
|
+
reason !== "idle" &&
|
|
7340
|
+
compactionSettings.strategy === "handoff"
|
|
7341
|
+
) {
|
|
7257
7342
|
this.#schedulePostPromptTask(
|
|
7258
7343
|
async signal => {
|
|
7259
7344
|
await Promise.resolve();
|
|
7260
7345
|
if (signal.aborted) return;
|
|
7261
|
-
await this.#runAutoCompaction(reason, willRetry, true);
|
|
7346
|
+
await this.#runAutoCompaction(reason, willRetry, true, options);
|
|
7262
7347
|
},
|
|
7263
7348
|
{ generation },
|
|
7264
7349
|
);
|
|
@@ -7267,6 +7352,7 @@ export class AgentSession {
|
|
|
7267
7352
|
|
|
7268
7353
|
let action: "context-full" | "handoff" =
|
|
7269
7354
|
compactionSettings.strategy === "handoff" && reason !== "overflow" ? "handoff" : "context-full";
|
|
7355
|
+
const continueAfterMaintenance = options?.continueAfterMaintenance !== false;
|
|
7270
7356
|
await this.#emitSessionEvent({ type: "auto_compaction_start", reason, action });
|
|
7271
7357
|
// Abort any older auto-compaction before installing this run's controller.
|
|
7272
7358
|
this.#autoCompactionAbortController?.abort();
|
|
@@ -7306,7 +7392,12 @@ export class AgentSession {
|
|
|
7306
7392
|
aborted: false,
|
|
7307
7393
|
willRetry: false,
|
|
7308
7394
|
});
|
|
7309
|
-
if (
|
|
7395
|
+
if (
|
|
7396
|
+
continueAfterMaintenance &&
|
|
7397
|
+
!autoCompactionSignal.aborted &&
|
|
7398
|
+
reason !== "idle" &&
|
|
7399
|
+
compactionSettings.autoContinue !== false
|
|
7400
|
+
) {
|
|
7310
7401
|
this.#scheduleAutoContinuePrompt(generation);
|
|
7311
7402
|
}
|
|
7312
7403
|
return;
|
|
@@ -7368,7 +7459,7 @@ export class AgentSession {
|
|
|
7368
7459
|
stopReason: tail?.stopReason,
|
|
7369
7460
|
});
|
|
7370
7461
|
}
|
|
7371
|
-
} else if (reason !== "idle" && this.agent.hasQueuedMessages()) {
|
|
7462
|
+
} else if (continueAfterMaintenance && reason !== "idle" && this.agent.hasQueuedMessages()) {
|
|
7372
7463
|
this.#scheduleAgentContinue({
|
|
7373
7464
|
delayMs: 100,
|
|
7374
7465
|
generation,
|
|
@@ -7376,7 +7467,7 @@ export class AgentSession {
|
|
|
7376
7467
|
onSkip: skipReason => this.#logCompactionContinuationSkipped("queued_continue", skipReason),
|
|
7377
7468
|
onError: error => this.#logCompactionContinuationError("queued_continue", error),
|
|
7378
7469
|
});
|
|
7379
|
-
} else if (reason !== "idle" && compactionSettings.autoContinue !== false) {
|
|
7470
|
+
} else if (continueAfterMaintenance && reason !== "idle" && compactionSettings.autoContinue !== false) {
|
|
7380
7471
|
this.#scheduleAutoContinuePrompt(generation);
|
|
7381
7472
|
}
|
|
7382
7473
|
return;
|
|
@@ -7597,7 +7688,7 @@ export class AgentSession {
|
|
|
7597
7688
|
onError: error => this.#logCompactionContinuationError("overflow_retry", error),
|
|
7598
7689
|
});
|
|
7599
7690
|
}
|
|
7600
|
-
} else if (reason !== "idle" && this.agent.hasQueuedMessages()) {
|
|
7691
|
+
} else if (continueAfterMaintenance && reason !== "idle" && this.agent.hasQueuedMessages()) {
|
|
7601
7692
|
// Auto-compaction can complete while follow-up/steering/custom messages are waiting.
|
|
7602
7693
|
// Kick the loop so queued messages are actually delivered.
|
|
7603
7694
|
this.#scheduleAgentContinue({
|
|
@@ -7607,7 +7698,7 @@ export class AgentSession {
|
|
|
7607
7698
|
onSkip: reason => this.#logCompactionContinuationSkipped("queued_continue", reason),
|
|
7608
7699
|
onError: error => this.#logCompactionContinuationError("queued_continue", error),
|
|
7609
7700
|
});
|
|
7610
|
-
} else if (reason !== "idle" && compactionSettings.autoContinue !== false) {
|
|
7701
|
+
} else if (continueAfterMaintenance && reason !== "idle" && compactionSettings.autoContinue !== false) {
|
|
7611
7702
|
this.#scheduleAutoContinuePrompt(generation);
|
|
7612
7703
|
}
|
|
7613
7704
|
} catch (error) {
|
|
@@ -8338,6 +8429,7 @@ export class AgentSession {
|
|
|
8338
8429
|
onChunk,
|
|
8339
8430
|
signal: abortController.signal,
|
|
8340
8431
|
sessionKey: this.sessionId,
|
|
8432
|
+
cwd,
|
|
8341
8433
|
timeout: clampTimeout("bash") * 1000,
|
|
8342
8434
|
env: buildGjcRuntimeSessionEnv({
|
|
8343
8435
|
sessionFile: this.sessionManager.getSessionFile(),
|
|
@@ -9527,7 +9619,7 @@ export class AgentSession {
|
|
|
9527
9619
|
// No usage data - estimate all messages
|
|
9528
9620
|
let estimated = 0;
|
|
9529
9621
|
for (const message of messages) {
|
|
9530
|
-
estimated +=
|
|
9622
|
+
estimated += estimateMessageTokensHeuristic(message);
|
|
9531
9623
|
}
|
|
9532
9624
|
return {
|
|
9533
9625
|
tokens: estimated,
|
|
@@ -9537,7 +9629,7 @@ export class AgentSession {
|
|
|
9537
9629
|
const usageTokens = calculatePromptTokens(lastUsage);
|
|
9538
9630
|
let trailingTokens = 0;
|
|
9539
9631
|
for (let i = lastUsageIndex + 1; i < messages.length; i++) {
|
|
9540
|
-
trailingTokens +=
|
|
9632
|
+
trailingTokens += estimateMessageTokensHeuristic(messages[i]);
|
|
9541
9633
|
}
|
|
9542
9634
|
|
|
9543
9635
|
return {
|
|
@@ -9757,3 +9849,7 @@ export class AgentSession {
|
|
|
9757
9849
|
return this.#extensionRunner;
|
|
9758
9850
|
}
|
|
9759
9851
|
}
|
|
9852
|
+
|
|
9853
|
+
function cloneJsonValueForForkSeed<T>(value: T): T {
|
|
9854
|
+
return JSON.parse(JSON.stringify(value)) as T;
|
|
9855
|
+
}
|