@gajae-code/coding-agent 0.9.1 → 0.9.3
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 +36 -0
- package/dist/types/capability/mcp.d.ts +3 -1
- package/dist/types/cli/args.d.ts +1 -0
- package/dist/types/cli/notify-cli.d.ts +5 -0
- package/dist/types/cli.d.ts +3 -0
- package/dist/types/commands/launch.d.ts +3 -0
- package/dist/types/commands/mcp.d.ts +1 -1
- package/dist/types/config/model-profile-activation.d.ts +7 -7
- package/dist/types/config/model-registry.d.ts +7 -3
- package/dist/types/config/model-resolver.d.ts +2 -2
- package/dist/types/config/settings-schema.d.ts +1 -1
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +20 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +9 -0
- package/dist/types/modes/components/settings-selector.d.ts +2 -2
- package/dist/types/modes/components/status-line/types.d.ts +6 -0
- package/dist/types/modes/components/status-line.d.ts +1 -0
- package/dist/types/modes/components/thinking-selector.d.ts +6 -1
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +12 -0
- package/dist/types/notifications/config-commands.d.ts +29 -0
- package/dist/types/notifications/index.d.ts +12 -1
- package/dist/types/notifications/telegram-daemon-cli.d.ts +3 -3
- package/dist/types/notifications/threaded-render.d.ts +2 -0
- package/dist/types/notifications/topic-registry.d.ts +4 -5
- package/dist/types/runtime-credential-selector.d.ts +7 -0
- package/dist/types/runtime-mcp/config-writer.d.ts +4 -3
- package/dist/types/runtime-mcp/types.d.ts +5 -4
- package/dist/types/sdk.d.ts +7 -1
- package/dist/types/session/agent-session.d.ts +22 -3
- package/dist/types/session/session-manager.d.ts +1 -0
- package/dist/types/tools/index.d.ts +1 -0
- package/dist/types/tools/skill-discovery.d.ts +40 -0
- package/dist/types/tools/skill.d.ts +9 -8
- package/dist/types/utils/linkedom.d.ts +4 -0
- package/dist/types/utils/pasted-image-path.d.ts +5 -3
- package/package.json +11 -7
- package/scripts/build-binary.ts +2 -40
- package/scripts/compile-args.ts +91 -0
- package/scripts/generate-docs-index.ts +1 -0
- package/src/capability/mcp.ts +3 -1
- package/src/cli/args.ts +8 -0
- package/src/cli/mcp-cli.ts +108 -26
- package/src/cli/notify-cli.ts +27 -1
- package/src/cli.ts +26 -0
- package/src/commands/launch.ts +5 -0
- package/src/commands/mcp.ts +8 -8
- package/src/commands/notify.ts +2 -2
- package/src/commands/stats.ts +6 -2
- package/src/config/mcp-schema.json +21 -17
- package/src/config/model-profile-activation.ts +23 -7
- package/src/config/model-registry.ts +21 -4
- package/src/config/model-resolver.ts +5 -16
- package/src/config/settings-schema.ts +3 -2
- package/src/config/settings.ts +24 -1
- package/src/coordinator-mcp/server.ts +7 -4
- package/src/discovery/builtin.ts +14 -0
- package/src/discovery/mcp-json.ts +14 -0
- package/src/extensibility/runtime-skill-discovery.ts +229 -0
- package/src/gjc-runtime/launch-tmux.ts +72 -12
- package/src/gjc-runtime/psmux-detect.ts +17 -2
- package/src/gjc-runtime/state-runtime.ts +105 -12
- package/src/gjc-runtime/team-runtime.ts +35 -3
- package/src/gjc-runtime/workflow-manifest.generated.json +4 -28
- package/src/gjc-runtime/workflow-manifest.ts +1 -7
- package/src/internal-urls/docs-index.generated.ts +3 -2
- package/src/internal-urls/gjc-protocol.ts +14 -1
- package/src/main.ts +26 -3
- package/src/modes/components/assistant-message.ts +1 -1
- package/src/modes/components/settings-selector.ts +3 -3
- package/src/modes/components/status-line/presets.ts +5 -7
- package/src/modes/components/status-line/segments.ts +14 -0
- package/src/modes/components/status-line/types.ts +6 -0
- package/src/modes/components/status-line.ts +7 -1
- package/src/modes/components/thinking-selector.ts +79 -18
- package/src/modes/components/tool-execution.ts +1 -1
- package/src/modes/controllers/event-controller.ts +9 -9
- package/src/modes/controllers/input-controller.ts +52 -11
- package/src/modes/controllers/selector-controller.ts +7 -5
- package/src/modes/interactive-mode.ts +12 -4
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +202 -47
- package/src/notifications/config-commands.ts +70 -0
- package/src/notifications/index.ts +156 -9
- package/src/notifications/rich-render.ts +1 -0
- package/src/notifications/telegram-daemon-cli.ts +13 -5
- package/src/notifications/telegram-daemon.ts +87 -4
- package/src/notifications/threaded-render.ts +14 -0
- package/src/notifications/topic-registry.ts +9 -8
- package/src/prompts/tools/skill-discovery.md +13 -0
- package/src/prompts/tools/skill.md +2 -2
- package/src/runtime-credential-selector.ts +35 -0
- package/src/runtime-mcp/config-writer.ts +4 -3
- package/src/runtime-mcp/config.ts +1 -0
- package/src/runtime-mcp/types.ts +5 -4
- package/src/sdk.ts +68 -5
- package/src/session/agent-session.ts +159 -21
- package/src/session/session-manager.ts +68 -40
- package/src/system-prompt.ts +4 -6
- package/src/tools/bash-interactive.ts +9 -4
- package/src/tools/browser/readable.ts +2 -2
- package/src/tools/fetch.ts +5 -5
- package/src/tools/index.ts +4 -0
- package/src/tools/skill-discovery.ts +73 -0
- package/src/tools/skill.ts +29 -13
- package/src/utils/linkedom.ts +14 -0
- package/src/utils/pasted-image-path.ts +23 -9
- package/src/web/scrapers/arxiv.ts +2 -2
- package/src/web/scrapers/go-pkg.ts +2 -2
- package/src/web/scrapers/iacr.ts +2 -2
- package/src/web/scrapers/readthedocs.ts +2 -2
- package/src/web/scrapers/twitter.ts +3 -2
- package/src/web/scrapers/wikipedia.ts +2 -2
- package/src/export/html/template.css +0 -1060
- package/src/export/html/template.html +0 -47
- package/src/export/html/template.js +0 -2314
- package/vendor/insane-search/engine/tests/test_hardening.py +0 -57
- package/vendor/insane-search/engine/tests/test_smoke.py +0 -152
- package/vendor/insane-search/engine/tests/test_u1.py +0 -200
- package/vendor/insane-search/engine/tests/test_u4.py +0 -131
- package/vendor/insane-search/engine/tests/test_u5.py +0 -163
- package/vendor/insane-search/engine/tests/test_u7.py +0 -124
- package/vendor/insane-search/engine/tests/test_u8.py +0 -216
package/src/sdk.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
type ThinkingLevel,
|
|
10
10
|
} from "@gajae-code/agent-core";
|
|
11
11
|
import {
|
|
12
|
+
type AuthCredentialSelector,
|
|
12
13
|
type CredentialDisabledEvent,
|
|
13
14
|
type Message,
|
|
14
15
|
type Model,
|
|
@@ -39,7 +40,7 @@ import {
|
|
|
39
40
|
import { type AsyncJob, AsyncJobManager, isBackgroundJobSupportEnabled, jobElapsedMs } from "./async";
|
|
40
41
|
import { loadCapability } from "./capability";
|
|
41
42
|
import { type Rule, ruleCapability, setActiveRules } from "./capability/rule";
|
|
42
|
-
import { ModelRegistry } from "./config/model-registry";
|
|
43
|
+
import { kNoAuth, ModelRegistry } from "./config/model-registry";
|
|
43
44
|
import {
|
|
44
45
|
formatModelString,
|
|
45
46
|
parseModelPattern,
|
|
@@ -272,6 +273,8 @@ export interface CreateAgentSessionOptions {
|
|
|
272
273
|
/** Optional provider-facing session identifier for prompt caches and sticky auth selection.
|
|
273
274
|
* Keeps persisted session files isolated while reusing provider-side caches. */
|
|
274
275
|
providerSessionId?: string;
|
|
276
|
+
/** Runtime credential selector for multi-account auth pools. */
|
|
277
|
+
credentialSelector?: { provider?: string; selector: AuthCredentialSelector; raw: string };
|
|
275
278
|
|
|
276
279
|
/** Custom tools to register (in addition to built-in tools). Accepts both CustomTool and ToolDefinition. */
|
|
277
280
|
customTools?: (CustomTool | ToolDefinition)[];
|
|
@@ -903,10 +906,22 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
903
906
|
startupCredentialDisabledEvents.push(event);
|
|
904
907
|
}
|
|
905
908
|
});
|
|
909
|
+
let runtimeCredentialSelectorInstalled = false;
|
|
910
|
+
const installRuntimeCredentialSelector = (provider: string): void => {
|
|
911
|
+
if (!options.credentialSelector || runtimeCredentialSelectorInstalled) return;
|
|
912
|
+
authStorage.setRuntimeCredentialSelector(provider, options.credentialSelector.selector);
|
|
913
|
+
runtimeCredentialSelectorInstalled = true;
|
|
914
|
+
};
|
|
915
|
+
const earlyCredentialSelectorProvider = options.credentialSelector?.provider ?? options.model?.provider;
|
|
916
|
+
if (earlyCredentialSelectorProvider) {
|
|
917
|
+
installRuntimeCredentialSelector(earlyCredentialSelectorProvider);
|
|
918
|
+
}
|
|
906
919
|
const settings = options.settings ?? (await logger.time("settings", Settings.init, { cwd, agentDir }));
|
|
907
920
|
modelRegistry.applyConfiguredModelBindings(settings);
|
|
908
921
|
logger.time("initializeWithSettings", initializeWithSettings, settings);
|
|
909
|
-
|
|
922
|
+
const canRefreshModelsBeforeCredentialSelector =
|
|
923
|
+
!options.credentialSelector || runtimeCredentialSelectorInstalled || options.modelRegistry !== undefined;
|
|
924
|
+
if (!options.modelRegistry && canRefreshModelsBeforeCredentialSelector) {
|
|
910
925
|
modelRegistry.refreshInBackground();
|
|
911
926
|
}
|
|
912
927
|
// Kick off workspace tree discovery early. The native workspace scan returns
|
|
@@ -972,7 +987,29 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
972
987
|
return cached;
|
|
973
988
|
}
|
|
974
989
|
|
|
975
|
-
const
|
|
990
|
+
const credentialSelector =
|
|
991
|
+
options.credentialSelector && !runtimeCredentialSelectorInstalled
|
|
992
|
+
? options.credentialSelector.provider === undefined ||
|
|
993
|
+
options.credentialSelector.provider === candidate.provider
|
|
994
|
+
? options.credentialSelector.selector
|
|
995
|
+
: undefined
|
|
996
|
+
: undefined;
|
|
997
|
+
if (options.credentialSelector?.provider && options.credentialSelector.provider !== candidate.provider) {
|
|
998
|
+
modelApiKeyAvailability.set(availabilityKey, false);
|
|
999
|
+
return false;
|
|
1000
|
+
}
|
|
1001
|
+
const key = await modelRegistry.getApiKey(candidate, providerSessionId, { credentialSelector }).catch(error => {
|
|
1002
|
+
if (credentialSelector) {
|
|
1003
|
+
logger.debug("Credential selector did not match model availability candidate", {
|
|
1004
|
+
provider: candidate.provider,
|
|
1005
|
+
model: candidate.id,
|
|
1006
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1007
|
+
});
|
|
1008
|
+
return undefined;
|
|
1009
|
+
}
|
|
1010
|
+
throw error;
|
|
1011
|
+
});
|
|
1012
|
+
const hasKey = Boolean(key) && (!credentialSelector || key !== kNoAuth);
|
|
976
1013
|
modelApiKeyAvailability.set(availabilityKey, hasKey);
|
|
977
1014
|
return hasKey;
|
|
978
1015
|
};
|
|
@@ -1047,6 +1084,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1047
1084
|
const taskDepth = options.taskDepth ?? 0;
|
|
1048
1085
|
|
|
1049
1086
|
let thinkingLevel = options.thinkingLevel;
|
|
1087
|
+
const hasExplicitDefaultThinkingLevel = settings.has("defaultThinkingLevel");
|
|
1088
|
+
let thinkingLevelFromSchemaDefault = false;
|
|
1050
1089
|
|
|
1051
1090
|
// If session has data and includes a thinking entry, restore it
|
|
1052
1091
|
if (thinkingLevel === undefined && hasExistingSession && hasThinkingEntry) {
|
|
@@ -1057,13 +1096,20 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1057
1096
|
thinkingLevel = defaultRoleSpec.thinkingLevel;
|
|
1058
1097
|
}
|
|
1059
1098
|
|
|
1060
|
-
//
|
|
1061
|
-
//
|
|
1099
|
+
// An explicit user/project default should win over the model's bundled
|
|
1100
|
+
// defaultLevel. The schema default is only a final fallback so model metadata
|
|
1101
|
+
// can keep driving first-run behavior until the user chooses "Set as default".
|
|
1102
|
+
if (thinkingLevel === undefined && hasExplicitDefaultThinkingLevel) {
|
|
1103
|
+
thinkingLevel = settings.get("defaultThinkingLevel");
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1062
1106
|
if (thinkingLevel === undefined && model?.thinking?.defaultLevel !== undefined) {
|
|
1063
1107
|
thinkingLevel = model.thinking.defaultLevel;
|
|
1064
1108
|
}
|
|
1109
|
+
|
|
1065
1110
|
if (thinkingLevel === undefined) {
|
|
1066
1111
|
thinkingLevel = settings.get("defaultThinkingLevel");
|
|
1112
|
+
thinkingLevelFromSchemaDefault = true;
|
|
1067
1113
|
}
|
|
1068
1114
|
if (model) {
|
|
1069
1115
|
const resolvedModel = model;
|
|
@@ -1608,6 +1654,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1608
1654
|
if (resolved) {
|
|
1609
1655
|
model = resolved;
|
|
1610
1656
|
modelFallbackMessage = undefined;
|
|
1657
|
+
if (thinkingLevelFromSchemaDefault && resolved.thinking?.defaultLevel !== undefined) {
|
|
1658
|
+
thinkingLevel = resolved.thinking.defaultLevel;
|
|
1659
|
+
thinkingLevelFromSchemaDefault = false;
|
|
1660
|
+
}
|
|
1661
|
+
thinkingLevel = resolveThinkingLevelForModel(resolved, thinkingLevel);
|
|
1611
1662
|
} else {
|
|
1612
1663
|
modelFallbackMessage = `Model "${options.modelPattern}" not found`;
|
|
1613
1664
|
}
|
|
@@ -1639,6 +1690,18 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1639
1690
|
}
|
|
1640
1691
|
}
|
|
1641
1692
|
|
|
1693
|
+
if (options.credentialSelector && !runtimeCredentialSelectorInstalled) {
|
|
1694
|
+
const credentialProvider = options.credentialSelector.provider ?? model?.provider;
|
|
1695
|
+
if (!credentialProvider) {
|
|
1696
|
+
throw new Error(
|
|
1697
|
+
`--credential ${options.credentialSelector.raw} requires a resolved model or an explicit provider prefix`,
|
|
1698
|
+
);
|
|
1699
|
+
}
|
|
1700
|
+
installRuntimeCredentialSelector(credentialProvider);
|
|
1701
|
+
if (!options.modelRegistry && !canRefreshModelsBeforeCredentialSelector) {
|
|
1702
|
+
modelRegistry.refreshInBackground();
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1642
1705
|
const customCommandsResult: CustomCommandsLoadResult = { commands: [], errors: [] };
|
|
1643
1706
|
|
|
1644
1707
|
let extensionRunner: ExtensionRunner | undefined;
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
type AgentMessage,
|
|
27
27
|
type AgentState,
|
|
28
28
|
type AgentTool,
|
|
29
|
+
assertImagePlaceholdersHavePayload,
|
|
29
30
|
resolveTelemetry,
|
|
30
31
|
type StablePrefixSnapshot,
|
|
31
32
|
ThinkingLevel,
|
|
@@ -151,6 +152,7 @@ import { onAppendOnlyModeChanged } from "../config/settings";
|
|
|
151
152
|
import { RawSseDebugBuffer } from "../debug/raw-sse-buffer";
|
|
152
153
|
import { loadCapability } from "../discovery";
|
|
153
154
|
import { expandApplyPatchToEntries, normalizeDiff, normalizeToLF, ParseError, previewPatch, stripBom } from "../edit";
|
|
155
|
+
import { MAX_EDIT_FILE_BYTES } from "../edit/read-file";
|
|
154
156
|
import { disposeVmContextsByOwner } from "../eval/js/context-manager";
|
|
155
157
|
import {
|
|
156
158
|
disposeKernelSessionsByOwner,
|
|
@@ -360,6 +362,11 @@ export interface AsyncJobSnapshot {
|
|
|
360
362
|
// Types
|
|
361
363
|
// ============================================================================
|
|
362
364
|
|
|
365
|
+
export interface RetainedMemorySample {
|
|
366
|
+
tuiChatChildren?: number;
|
|
367
|
+
tuiCachedRenderBytes?: number;
|
|
368
|
+
}
|
|
369
|
+
|
|
363
370
|
export interface AgentSessionConfig {
|
|
364
371
|
agent: Agent;
|
|
365
372
|
sessionManager: SessionManager;
|
|
@@ -439,6 +446,8 @@ export interface AgentSessionConfig {
|
|
|
439
446
|
* **MUST NOT** dispose it on their own teardown.
|
|
440
447
|
*/
|
|
441
448
|
ownedAsyncJobManager?: AsyncJobManager;
|
|
449
|
+
/** Cheap TUI retained-memory counters; absent for headless sessions. */
|
|
450
|
+
retainedMemorySampler?: () => RetainedMemorySample;
|
|
442
451
|
/**
|
|
443
452
|
* MCPManager whose lifecycle this session owns (top-level sessions that
|
|
444
453
|
* connected plugin-bundle MCP servers). Only the owned manager is
|
|
@@ -473,6 +482,8 @@ export interface PromptOptions {
|
|
|
473
482
|
images?: ImageContent[];
|
|
474
483
|
/** When streaming, how to queue the message: "steer" (interrupt) or "followUp" (wait). */
|
|
475
484
|
streamingBehavior?: "steer" | "followUp";
|
|
485
|
+
/** When set to "sequential", this follow-up is delivered one prompt at a time even if followUpMode is "all". */
|
|
486
|
+
followUpQueuePolicy?: "respect-mode" | "sequential";
|
|
476
487
|
/** Optional tool choice override for the next LLM call. */
|
|
477
488
|
toolChoice?: ToolChoice;
|
|
478
489
|
/** Send as developer/system message instead of user. Providers that support it use the developer role; others fall back to user. */
|
|
@@ -1082,6 +1093,68 @@ export function buildContextInjectionSignature(kind: string, parts: readonly str
|
|
|
1082
1093
|
}
|
|
1083
1094
|
return hash.digest("base64url");
|
|
1084
1095
|
}
|
|
1096
|
+
|
|
1097
|
+
const STREAMING_EDIT_FILE_CACHE_MAX_ENTRIES = 16;
|
|
1098
|
+
const STREAMING_EDIT_FILE_CACHE_MAX_TOTAL_BYTES = 32 * 1024 * 1024;
|
|
1099
|
+
|
|
1100
|
+
type StreamingEditFileCacheEntry = {
|
|
1101
|
+
content: string;
|
|
1102
|
+
bytes: number;
|
|
1103
|
+
};
|
|
1104
|
+
|
|
1105
|
+
export class StreamingEditFileCache {
|
|
1106
|
+
#entries = new Map<string, StreamingEditFileCacheEntry>();
|
|
1107
|
+
#totalBytes = 0;
|
|
1108
|
+
|
|
1109
|
+
get(path: string): string | undefined {
|
|
1110
|
+
const entry = this.#entries.get(path);
|
|
1111
|
+
if (entry === undefined) return undefined;
|
|
1112
|
+
this.#entries.delete(path);
|
|
1113
|
+
this.#entries.set(path, entry);
|
|
1114
|
+
return entry.content;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
set(path: string, content: string): void {
|
|
1118
|
+
const bytes = Buffer.byteLength(content, "utf8");
|
|
1119
|
+
if (bytes > MAX_EDIT_FILE_BYTES || bytes > STREAMING_EDIT_FILE_CACHE_MAX_TOTAL_BYTES) {
|
|
1120
|
+
this.delete(path);
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
this.delete(path);
|
|
1125
|
+
while (
|
|
1126
|
+
this.#entries.size >= STREAMING_EDIT_FILE_CACHE_MAX_ENTRIES ||
|
|
1127
|
+
this.#totalBytes + bytes > STREAMING_EDIT_FILE_CACHE_MAX_TOTAL_BYTES
|
|
1128
|
+
) {
|
|
1129
|
+
const oldestPath = this.#entries.keys().next().value;
|
|
1130
|
+
if (oldestPath === undefined) break;
|
|
1131
|
+
this.delete(oldestPath);
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
this.#entries.set(path, { content, bytes });
|
|
1135
|
+
this.#totalBytes += bytes;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
delete(path: string): void {
|
|
1139
|
+
const entry = this.#entries.get(path);
|
|
1140
|
+
if (entry === undefined) return;
|
|
1141
|
+
this.#entries.delete(path);
|
|
1142
|
+
this.#totalBytes -= entry.bytes;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
clear(): void {
|
|
1146
|
+
this.#entries.clear();
|
|
1147
|
+
this.#totalBytes = 0;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
has(path: string): boolean {
|
|
1151
|
+
return this.#entries.has(path);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
get totalBytes(): number {
|
|
1155
|
+
return this.#totalBytes;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1085
1158
|
export class AgentSession {
|
|
1086
1159
|
readonly agent: Agent;
|
|
1087
1160
|
readonly sessionManager: SessionManager;
|
|
@@ -1146,6 +1219,7 @@ export class AgentSession {
|
|
|
1146
1219
|
#compactionAbortController: AbortController | undefined = undefined;
|
|
1147
1220
|
#autoCompactionAbortController: AbortController | undefined = undefined;
|
|
1148
1221
|
#resourceSampler: () => EmergencyCompactionSample = () => this.#defaultResourceSample();
|
|
1222
|
+
#retainedMemorySampler: (() => RetainedMemorySample) | undefined;
|
|
1149
1223
|
#prePromptContextCheckPromise: Promise<void> | undefined = undefined;
|
|
1150
1224
|
|
|
1151
1225
|
// Branch summarization state
|
|
@@ -1319,7 +1393,7 @@ export class AgentSession {
|
|
|
1319
1393
|
|
|
1320
1394
|
#streamingEditPrecheckedToolCallIds = new Set<string>();
|
|
1321
1395
|
|
|
1322
|
-
#streamingEditFileCache = new
|
|
1396
|
+
#streamingEditFileCache = new StreamingEditFileCache();
|
|
1323
1397
|
readonly streamingEditDebugCounters = {
|
|
1324
1398
|
guardRuns: 0,
|
|
1325
1399
|
processedChars: 0,
|
|
@@ -1445,6 +1519,7 @@ export class AgentSession {
|
|
|
1445
1519
|
// Power assertions are taken per turn (see #beginInFlight); nothing acquired here.
|
|
1446
1520
|
this.#evalKernelOwnerId = config.evalKernelOwnerId ?? `agent-session:${Snowflake.next()}`;
|
|
1447
1521
|
this.#ownedAsyncJobManager = config.ownedAsyncJobManager;
|
|
1522
|
+
this.#retainedMemorySampler = config.retainedMemorySampler;
|
|
1448
1523
|
this.#ownedMcpManager = config.ownedMcpManager;
|
|
1449
1524
|
this.#scopedModels = config.scopedModels ?? [];
|
|
1450
1525
|
this.#thinkingLevel = config.thinkingLevel;
|
|
@@ -3168,7 +3243,31 @@ export class AgentSession {
|
|
|
3168
3243
|
// which is the optional patch-preview verification gated by
|
|
3169
3244
|
// edit.streamingAbort. Skip the read when the setting is off.
|
|
3170
3245
|
if (this.settings.get("edit.streamingAbort")) {
|
|
3171
|
-
|
|
3246
|
+
// Fire-and-forget async priming: toolcall deltas arrive on the hot
|
|
3247
|
+
// stream path, so never block it on filesystem I/O. The abort-check
|
|
3248
|
+
// path (#maybeAbortStreamingEdit) still falls back to the sync read
|
|
3249
|
+
// if priming hasn't completed by the time removed lines appear.
|
|
3250
|
+
void this.#preCacheFileAsync(streamingEdit.resolvedPath);
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
|
|
3254
|
+
#streamingEditPrecachePending = new Set<string>();
|
|
3255
|
+
async #preCacheFileAsync(resolvedPath: string): Promise<void> {
|
|
3256
|
+
if (this.#streamingEditFileCache.has(resolvedPath)) return;
|
|
3257
|
+
if (this.#streamingEditPrecachePending.has(resolvedPath)) return;
|
|
3258
|
+
this.#streamingEditPrecachePending.add(resolvedPath);
|
|
3259
|
+
try {
|
|
3260
|
+
const stat = await fs.promises.stat(resolvedPath);
|
|
3261
|
+
if (stat.size > MAX_EDIT_FILE_BYTES) return;
|
|
3262
|
+
|
|
3263
|
+
const rawText = await fs.promises.readFile(resolvedPath, "utf-8");
|
|
3264
|
+
if (this.#streamingEditFileCache.has(resolvedPath)) return;
|
|
3265
|
+
const { text } = stripBom(rawText);
|
|
3266
|
+
this.#streamingEditFileCache.set(resolvedPath, normalizeToLF(text));
|
|
3267
|
+
} catch {
|
|
3268
|
+
// Don't cache on read errors (including ENOENT) - let the edit tool handle them
|
|
3269
|
+
} finally {
|
|
3270
|
+
this.#streamingEditPrecachePending.delete(resolvedPath);
|
|
3172
3271
|
}
|
|
3173
3272
|
}
|
|
3174
3273
|
|
|
@@ -3176,6 +3275,9 @@ export class AgentSession {
|
|
|
3176
3275
|
if (this.#streamingEditFileCache.has(resolvedPath)) return;
|
|
3177
3276
|
|
|
3178
3277
|
try {
|
|
3278
|
+
const stat = fs.statSync(resolvedPath);
|
|
3279
|
+
if (stat.size > MAX_EDIT_FILE_BYTES) return;
|
|
3280
|
+
|
|
3179
3281
|
const rawText = fs.readFileSync(resolvedPath, "utf-8");
|
|
3180
3282
|
const { text } = stripBom(rawText);
|
|
3181
3283
|
this.#streamingEditFileCache.set(resolvedPath, normalizeToLF(text));
|
|
@@ -5296,6 +5398,7 @@ export class AgentSession {
|
|
|
5296
5398
|
|
|
5297
5399
|
// Expand file-based prompt templates if requested
|
|
5298
5400
|
const expandedText = expandPromptTemplates ? expandPromptTemplate(text, [...this.#promptTemplates]) : text;
|
|
5401
|
+
assertImagePlaceholdersHavePayload(expandedText, options?.images);
|
|
5299
5402
|
const workflowIntentDiff = options?.synthetic ? null : buildWorkflowIntentDiff(expandedText);
|
|
5300
5403
|
|
|
5301
5404
|
// If streaming, queue via steer() or followUp() based on option
|
|
@@ -5304,7 +5407,9 @@ export class AgentSession {
|
|
|
5304
5407
|
throw new AgentBusyError();
|
|
5305
5408
|
}
|
|
5306
5409
|
if (options.streamingBehavior === "followUp") {
|
|
5307
|
-
await this.#queueFollowUp(expandedText, options?.images
|
|
5410
|
+
await this.#queueFollowUp(expandedText, options?.images, {
|
|
5411
|
+
forceOneAtATime: options.followUpQueuePolicy === "sequential",
|
|
5412
|
+
});
|
|
5308
5413
|
} else {
|
|
5309
5414
|
await this.#queueSteer(expandedText, options?.images);
|
|
5310
5415
|
}
|
|
@@ -5422,7 +5527,7 @@ export class AgentSession {
|
|
|
5422
5527
|
|
|
5423
5528
|
async promptCustomMessage<T = unknown>(
|
|
5424
5529
|
message: Pick<CustomMessage<T>, "customType" | "content" | "display" | "details" | "attribution">,
|
|
5425
|
-
options?: Pick<PromptOptions, "streamingBehavior" | "toolChoice">,
|
|
5530
|
+
options?: Pick<PromptOptions, "streamingBehavior" | "toolChoice" | "followUpQueuePolicy">,
|
|
5426
5531
|
): Promise<void> {
|
|
5427
5532
|
const textContent =
|
|
5428
5533
|
typeof message.content === "string"
|
|
@@ -5436,7 +5541,10 @@ export class AgentSession {
|
|
|
5436
5541
|
if (!options?.streamingBehavior) {
|
|
5437
5542
|
throw new AgentBusyError();
|
|
5438
5543
|
}
|
|
5439
|
-
await this.sendCustomMessage(message, {
|
|
5544
|
+
await this.sendCustomMessage(message, {
|
|
5545
|
+
deliverAs: options.streamingBehavior,
|
|
5546
|
+
followUpQueuePolicy: options.followUpQueuePolicy,
|
|
5547
|
+
});
|
|
5440
5548
|
return;
|
|
5441
5549
|
}
|
|
5442
5550
|
|
|
@@ -5777,25 +5885,34 @@ export class AgentSession {
|
|
|
5777
5885
|
}
|
|
5778
5886
|
|
|
5779
5887
|
const expandedText = expandPromptTemplate(text, [...this.#promptTemplates]);
|
|
5888
|
+
assertImagePlaceholdersHavePayload(expandedText, images);
|
|
5780
5889
|
await this.#queueSteer(expandedText, images);
|
|
5781
5890
|
}
|
|
5782
5891
|
|
|
5783
5892
|
/**
|
|
5784
5893
|
* Queue a follow-up message to process after the agent would otherwise stop.
|
|
5785
5894
|
*/
|
|
5786
|
-
async followUp(
|
|
5895
|
+
async followUp(
|
|
5896
|
+
text: string,
|
|
5897
|
+
images?: ImageContent[],
|
|
5898
|
+
options?: Pick<PromptOptions, "followUpQueuePolicy">,
|
|
5899
|
+
): Promise<void> {
|
|
5787
5900
|
if (text.startsWith("/")) {
|
|
5788
5901
|
this.#throwIfExtensionCommand(text);
|
|
5789
5902
|
}
|
|
5790
5903
|
|
|
5791
5904
|
const expandedText = expandPromptTemplate(text, [...this.#promptTemplates]);
|
|
5792
|
-
|
|
5905
|
+
assertImagePlaceholdersHavePayload(expandedText, images);
|
|
5906
|
+
await this.#queueFollowUp(expandedText, images, {
|
|
5907
|
+
forceOneAtATime: options?.followUpQueuePolicy === "sequential",
|
|
5908
|
+
});
|
|
5793
5909
|
}
|
|
5794
5910
|
|
|
5795
5911
|
/**
|
|
5796
5912
|
* Internal: Queue a steering message (already expanded, no extension command check).
|
|
5797
5913
|
*/
|
|
5798
5914
|
async #queueSteer(text: string, images?: ImageContent[]): Promise<void> {
|
|
5915
|
+
assertImagePlaceholdersHavePayload(text, images);
|
|
5799
5916
|
const displayText = text || (images && images.length > 0 ? "[Image]" : "");
|
|
5800
5917
|
this.#steeringMessages.push(this.#createQueuedDisplayEntry(displayText));
|
|
5801
5918
|
const content: (TextContent | ImageContent)[] = [{ type: "text", text }];
|
|
@@ -5826,19 +5943,23 @@ export class AgentSession {
|
|
|
5826
5943
|
/**
|
|
5827
5944
|
* Internal: Queue a follow-up message (already expanded, no extension command check).
|
|
5828
5945
|
*/
|
|
5829
|
-
async #queueFollowUp(text: string, images?: ImageContent[]): Promise<void> {
|
|
5946
|
+
async #queueFollowUp(text: string, images?: ImageContent[], options?: { forceOneAtATime?: boolean }): Promise<void> {
|
|
5947
|
+
assertImagePlaceholdersHavePayload(text, images);
|
|
5830
5948
|
const displayText = text || (images && images.length > 0 ? "[Image]" : "");
|
|
5831
5949
|
this.#followUpMessages.push(this.#createQueuedDisplayEntry(displayText));
|
|
5832
5950
|
const content: (TextContent | ImageContent)[] = [{ type: "text", text }];
|
|
5833
5951
|
if (images && images.length > 0) {
|
|
5834
5952
|
content.push(...images);
|
|
5835
5953
|
}
|
|
5836
|
-
this.agent.followUp(
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5954
|
+
this.agent.followUp(
|
|
5955
|
+
{
|
|
5956
|
+
role: "user",
|
|
5957
|
+
content,
|
|
5958
|
+
attribution: "user",
|
|
5959
|
+
timestamp: Date.now(),
|
|
5960
|
+
},
|
|
5961
|
+
options?.forceOneAtATime ? { forceOneAtATime: true } : undefined,
|
|
5962
|
+
);
|
|
5842
5963
|
// When fully idle AND the session is in a resumable assistant-ended state,
|
|
5843
5964
|
// schedule an immediate continue so the queued follow-up is delivered
|
|
5844
5965
|
// without waiting for the next user turn. We gate on isStreaming (model
|
|
@@ -5986,7 +6107,11 @@ export class AgentSession {
|
|
|
5986
6107
|
*/
|
|
5987
6108
|
async sendCustomMessage<T = unknown>(
|
|
5988
6109
|
message: Pick<CustomMessage<T>, "customType" | "content" | "display" | "details" | "attribution">,
|
|
5989
|
-
options?: {
|
|
6110
|
+
options?: {
|
|
6111
|
+
triggerTurn?: boolean;
|
|
6112
|
+
deliverAs?: "steer" | "followUp" | "nextTurn";
|
|
6113
|
+
followUpQueuePolicy?: "respect-mode" | "sequential";
|
|
6114
|
+
},
|
|
5990
6115
|
): Promise<void> {
|
|
5991
6116
|
const appMessage: CustomMessage<T> = {
|
|
5992
6117
|
role: "custom",
|
|
@@ -6004,7 +6129,10 @@ export class AgentSession {
|
|
|
6004
6129
|
}
|
|
6005
6130
|
|
|
6006
6131
|
if (options?.deliverAs === "followUp") {
|
|
6007
|
-
this.agent.followUp(
|
|
6132
|
+
this.agent.followUp(
|
|
6133
|
+
appMessage,
|
|
6134
|
+
options.followUpQueuePolicy === "sequential" ? { forceOneAtATime: true } : undefined,
|
|
6135
|
+
);
|
|
6008
6136
|
} else {
|
|
6009
6137
|
this.agent.steer(appMessage);
|
|
6010
6138
|
}
|
|
@@ -6930,7 +7058,7 @@ export class AgentSession {
|
|
|
6930
7058
|
|
|
6931
7059
|
/**
|
|
6932
7060
|
* Set thinking level.
|
|
6933
|
-
* Saves the effective metadata-clamped level to session and settings
|
|
7061
|
+
* Saves the effective metadata-clamped level to the session, and to settings when requested.
|
|
6934
7062
|
*/
|
|
6935
7063
|
setThinkingLevel(level: ThinkingLevel | undefined, persist: boolean = false): void {
|
|
6936
7064
|
const effectiveLevel = resolveThinkingLevelForModel(this.model, level);
|
|
@@ -6939,11 +7067,12 @@ export class AgentSession {
|
|
|
6939
7067
|
this.#thinkingLevel = effectiveLevel;
|
|
6940
7068
|
this.agent.setThinkingLevel(toReasoningEffort(effectiveLevel));
|
|
6941
7069
|
|
|
7070
|
+
if (persist && effectiveLevel !== undefined) {
|
|
7071
|
+
this.settings.set("defaultThinkingLevel", effectiveLevel);
|
|
7072
|
+
}
|
|
7073
|
+
|
|
6942
7074
|
if (isChanging) {
|
|
6943
7075
|
this.sessionManager.appendThinkingLevelChange(effectiveLevel);
|
|
6944
|
-
if (persist && effectiveLevel !== undefined && effectiveLevel !== ThinkingLevel.Off) {
|
|
6945
|
-
this.settings.set("defaultThinkingLevel", effectiveLevel);
|
|
6946
|
-
}
|
|
6947
7076
|
this.#emit({ type: "thinking_level_changed", thinkingLevel: effectiveLevel });
|
|
6948
7077
|
}
|
|
6949
7078
|
}
|
|
@@ -7708,9 +7837,14 @@ export class AgentSession {
|
|
|
7708
7837
|
this.#resourceSampler = sampler;
|
|
7709
7838
|
}
|
|
7710
7839
|
|
|
7840
|
+
setRetainedMemorySampler(sampler: (() => RetainedMemorySample) | undefined): void {
|
|
7841
|
+
this.#retainedMemorySampler = sampler;
|
|
7842
|
+
}
|
|
7843
|
+
|
|
7711
7844
|
#defaultResourceSample(): EmergencyCompactionSample {
|
|
7712
7845
|
let providerBytes = 0;
|
|
7713
7846
|
let imageBytes = 0;
|
|
7847
|
+
const retainedMemory = this.#retainedMemorySampler?.() ?? {};
|
|
7714
7848
|
for (const message of this.state.messages) {
|
|
7715
7849
|
const content = (message as { content?: unknown }).content;
|
|
7716
7850
|
if (typeof content === "string") {
|
|
@@ -7732,6 +7866,10 @@ export class AgentSession {
|
|
|
7732
7866
|
providerBytes,
|
|
7733
7867
|
messageCount: this.state.messages.length,
|
|
7734
7868
|
imageBytes,
|
|
7869
|
+
sessionResidentImageBytes: this.sessionManager.getResidentImageBytes(),
|
|
7870
|
+
materializedResidentBytes: this.#streamingEditFileCache.totalBytes,
|
|
7871
|
+
tuiChatChildren: retainedMemory.tuiChatChildren ?? 0,
|
|
7872
|
+
tuiCachedRenderBytes: retainedMemory.tuiCachedRenderBytes ?? 0,
|
|
7735
7873
|
};
|
|
7736
7874
|
}
|
|
7737
7875
|
|
|
@@ -8047,7 +8185,7 @@ export class AgentSession {
|
|
|
8047
8185
|
attribution: "agent",
|
|
8048
8186
|
timestamp: Date.now(),
|
|
8049
8187
|
});
|
|
8050
|
-
this.#scheduleAgentContinue({
|
|
8188
|
+
this.#scheduleAgentContinue({ skipCompactionCheck: true });
|
|
8051
8189
|
}
|
|
8052
8190
|
|
|
8053
8191
|
/**
|