@code-yeongyu/senpi 2026.8.29 → 2026.8.30
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 +66 -0
- package/dist/core/agent-session.d.ts +45 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +312 -25
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +155 -37
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/credential-accounts.d.ts.map +1 -1
- package/dist/core/credential-accounts.js +23 -9
- package/dist/core/credential-accounts.js.map +1 -1
- package/dist/core/credential-pool/env-slots.d.ts.map +1 -1
- package/dist/core/credential-pool/env-slots.js +2 -0
- package/dist/core/credential-pool/env-slots.js.map +1 -1
- package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts +8 -2
- package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts.map +1 -1
- package/dist/core/extensions/builtin/compaction/deterministic-fallback.js +160 -47
- package/dist/core/extensions/builtin/compaction/deterministic-fallback.js.map +1 -1
- package/dist/core/extensions/builtin/compaction/retained-message-safety.d.ts.map +1 -1
- package/dist/core/extensions/builtin/compaction/retained-message-safety.js +31 -8
- package/dist/core/extensions/builtin/compaction/retained-message-safety.js.map +1 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/stream.d.ts.map +1 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/stream.js +8 -28
- package/dist/core/extensions/builtin/cursor-cli-oauth/stream.js.map +1 -1
- package/dist/core/extensions/builtin/permission-system/external-dir.d.ts.map +1 -1
- package/dist/core/extensions/builtin/permission-system/external-dir.js +15 -5
- package/dist/core/extensions/builtin/permission-system/external-dir.js.map +1 -1
- package/dist/core/extensions/builtin/permission-system/parsers.d.ts.map +1 -1
- package/dist/core/extensions/builtin/permission-system/parsers.js +16 -1
- package/dist/core/extensions/builtin/permission-system/parsers.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/manager.d.ts +5 -0
- package/dist/core/extensions/builtin/terminal/manager.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/manager.js +31 -3
- package/dist/core/extensions/builtin/terminal/manager.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/monitor-permission.d.ts +8 -0
- package/dist/core/extensions/builtin/terminal/monitor-permission.d.ts.map +1 -0
- package/dist/core/extensions/builtin/terminal/monitor-permission.js +16 -0
- package/dist/core/extensions/builtin/terminal/monitor-permission.js.map +1 -0
- package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts +13 -0
- package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/monitor-registry.js +403 -3
- package/dist/core/extensions/builtin/terminal/monitor-registry.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/session-bundle.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/session-bundle.js +1 -0
- package/dist/core/extensions/builtin/terminal/session-bundle.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/kill-bash.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/kill-bash.js +5 -2
- package/dist/core/extensions/builtin/terminal/tools/kill-bash.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/monitor.d.ts +4 -0
- package/dist/core/extensions/builtin/terminal/tools/monitor.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/monitor.js +40 -2
- package/dist/core/extensions/builtin/terminal/tools/monitor.js.map +1 -1
- package/dist/core/model-resolver.js +2 -2
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +10 -1
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +7 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +6 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +97 -15
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/index.d.ts +1 -0
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +8 -0
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/output-accumulator.d.ts +1 -1
- package/dist/core/tools/output-accumulator.d.ts.map +1 -1
- package/dist/core/tools/output-accumulator.js +76 -34
- package/dist/core/tools/output-accumulator.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +7 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +34 -8
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/grok/chrome.d.ts +2 -0
- package/dist/modes/interactive/grok/chrome.d.ts.map +1 -1
- package/dist/modes/interactive/grok/chrome.js.map +1 -1
- package/dist/modes/interactive/interactive-host-runtime.d.ts +51 -1
- package/dist/modes/interactive/interactive-host-runtime.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-host-runtime.js +235 -20
- package/dist/modes/interactive/interactive-host-runtime.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +7 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +114 -2
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/connection-handler.d.ts +8 -1
- package/dist/modes/rpc/connection-handler.d.ts.map +1 -1
- package/dist/modes/rpc/connection-handler.js +145 -49
- package/dist/modes/rpc/connection-handler.js.map +1 -1
- package/dist/modes/rpc/host-ensure.d.ts +2 -0
- package/dist/modes/rpc/host-ensure.d.ts.map +1 -1
- package/dist/modes/rpc/host-ensure.js +15 -2
- package/dist/modes/rpc/host-ensure.js.map +1 -1
- package/dist/modes/rpc/host-lifecycle.d.ts.map +1 -1
- package/dist/modes/rpc/host-lifecycle.js +31 -12
- package/dist/modes/rpc/host-lifecycle.js.map +1 -1
- package/dist/modes/rpc/host-watchdog.d.ts +2 -0
- package/dist/modes/rpc/host-watchdog.d.ts.map +1 -1
- package/dist/modes/rpc/host-watchdog.js +23 -12
- package/dist/modes/rpc/host-watchdog.js.map +1 -1
- package/dist/modes/rpc/multi-session-host.js +6 -4
- package/dist/modes/rpc/multi-session-host.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +10 -4
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +45 -10
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-input-validation.d.ts +1 -0
- package/dist/modes/rpc/rpc-input-validation.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-input-validation.js +66 -0
- package/dist/modes/rpc/rpc-input-validation.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +79 -1
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/modes/rpc/session-binding.d.ts +1 -0
- package/dist/modes/rpc/session-binding.d.ts.map +1 -1
- package/dist/modes/rpc/session-binding.js +21 -1
- package/dist/modes/rpc/session-binding.js.map +1 -1
- package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
- package/dist/modes/rpc/session-command-router.js +1 -0
- package/dist/modes/rpc/session-command-router.js.map +1 -1
- package/dist/modes/rpc/session-event-writer.d.ts +2 -0
- package/dist/modes/rpc/session-event-writer.d.ts.map +1 -1
- package/dist/modes/rpc/session-event-writer.js +54 -7
- package/dist/modes/rpc/session-event-writer.js.map +1 -1
- package/dist/modes/rpc/session-extension-ui-requests.d.ts +1 -0
- package/dist/modes/rpc/session-extension-ui-requests.d.ts.map +1 -1
- package/dist/modes/rpc/session-extension-ui-requests.js +5 -0
- package/dist/modes/rpc/session-extension-ui-requests.js.map +1 -1
- package/dist/modes/rpc/session-registry.d.ts +10 -2
- package/dist/modes/rpc/session-registry.d.ts.map +1 -1
- package/dist/modes/rpc/session-registry.js +58 -4
- package/dist/modes/rpc/session-registry.js.map +1 -1
- package/dist/modes/rpc/socket-event-fanout.d.ts +22 -0
- package/dist/modes/rpc/socket-event-fanout.d.ts.map +1 -0
- package/dist/modes/rpc/socket-event-fanout.js +84 -0
- package/dist/modes/rpc/socket-event-fanout.js.map +1 -0
- package/docs/rpc.md +3 -1
- package/docs/settings.md +34 -0
- package/docs/windows.md +2 -0
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +38 -0
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-indirect-eval.js +139 -90
- package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt.ts +4 -4
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.js +40 -20
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts +3 -3
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.d.ts +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.js +28 -9
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js +58 -31
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.d.ts +20 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.js +443 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts +2 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js +2 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/index.d.ts +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/index.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/index.js +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-ai-gateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-workers-ai.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/together.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai-coding-cn.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai.json +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +2 -2
- package/node_modules/@earendil-works/pi-pty/package.json +1 -1
- package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts +7 -0
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +5 -0
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/mux.d.ts +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/mux.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/mux.js +2 -2
- package/node_modules/@earendil-works/pi-tui/dist/mux.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts +1 -0
- package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal.js +19 -0
- package/node_modules/@earendil-works/pi-tui/dist/terminal.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +7 -7
|
@@ -14,10 +14,11 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { randomUUID } from "node:crypto";
|
|
16
16
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
17
|
+
import { rm } from "node:fs/promises";
|
|
17
18
|
import { basename, dirname } from "node:path";
|
|
18
19
|
import { isDeepStrictEqual } from "node:util";
|
|
19
20
|
import { ProviderRetryWatchdogAbortError, prepareAgentToolCall } from "@earendil-works/pi-agent-core";
|
|
20
|
-
import { contentText, SERVER_FALLBACK_ABORTED_DIAGNOSTIC } from "@earendil-works/pi-ai";
|
|
21
|
+
import { contentText, measureCursorHistorySerializedBytes, SERVER_FALLBACK_ABORTED_DIAGNOSTIC, } from "@earendil-works/pi-ai";
|
|
21
22
|
import { cleanupSessionResources, cursorOverflowCompactionSettings, isClassifierRefusal, isContextOverflow, isCursorPayloadResourceExhausted, isCursorQuotaResourceExhausted, isCursorZeroTokenResourceExhausted, isProviderStreamStallError, isProviderTimeoutError, isRecoverableLength, isRetryableAssistantError, modelsAreEqual, resetApiProviders, shouldRetryOverflowWithoutCompact, streamSimple, } from "@earendil-works/pi-ai/compat";
|
|
22
23
|
import { extract429RetryAfterMs, parseRetryAfterMsMarker } from "@earendil-works/pi-ai/utils/retry-hint";
|
|
23
24
|
import { retryBackoffDelayMs } from "@earendil-works/pi-ai/utils/retry-profile/backoff";
|
|
@@ -47,7 +48,7 @@ import { ExecuteToolError, ExtensionRunner, wrapRegisteredTools, } from "./exten
|
|
|
47
48
|
import { emitSessionShutdownEvent } from "./extensions/runner.js";
|
|
48
49
|
import { normalizeToolExposure, RUNTIME_EXTENSION_PATH } from "./extensions/types.js";
|
|
49
50
|
import { shouldWarnHighReasoning } from "./high-reasoning-warning.js";
|
|
50
|
-
import { filterContextExcludedMessages } from "./messages.js";
|
|
51
|
+
import { convertToLlm, filterContextExcludedMessages, } from "./messages.js";
|
|
51
52
|
import { ModelRegistry } from "./model-registry.js";
|
|
52
53
|
import { getModelNarrowingPatterns, resolveModelScope } from "./model-resolver.js";
|
|
53
54
|
import { PROMPT_CACHE_SAFE_WAIT_ENV, resolvePromptCacheSafeWaitSeconds } from "./prompt-cache-budget.js";
|
|
@@ -70,9 +71,21 @@ import { getSupportedThinkingLevels, supportsMax, supportsXhigh } from "./thinki
|
|
|
70
71
|
import { resetTimings, time } from "./timings.js";
|
|
71
72
|
import { createLocalBashOperations } from "./tools/bash.js";
|
|
72
73
|
import { composeFilesystemPolicies } from "./tools/filesystem-policy.js";
|
|
73
|
-
import { createAllToolDefinitions } from "./tools/index.js";
|
|
74
|
+
import { createAllToolDefinitions, temporarilyDisabledToolNames } from "./tools/index.js";
|
|
74
75
|
import { createToolDefinitionFromAgentTool } from "./tools/tool-definition-wrapper.js";
|
|
75
76
|
import { addUsageToTotals, createUsageTotals } from "./usage-totals.js";
|
|
77
|
+
/** Built-in shell tools routed exclusively through eval when experimental.bashEvalOnly is enabled. */
|
|
78
|
+
const EVAL_ONLY_SHELL_TOOL_NAMES = ["bash", "powershell"];
|
|
79
|
+
/** Workflow tool routed exclusively through eval when experimental.workflowEvalOnly is enabled. */
|
|
80
|
+
const EVAL_ONLY_WORKFLOW_TOOL_NAMES = ["workflow"];
|
|
81
|
+
/** Sample eval-cell call for an eval-only tool, using the argument name that tool actually takes. */
|
|
82
|
+
function evalHelperCall(name) {
|
|
83
|
+
if (EVAL_ONLY_SHELL_TOOL_NAMES.includes(name))
|
|
84
|
+
return `tool.${name}({ command: "..." })`;
|
|
85
|
+
if (EVAL_ONLY_WORKFLOW_TOOL_NAMES.includes(name))
|
|
86
|
+
return `tool.${name}({ action: "..." })`;
|
|
87
|
+
return `tool.${name}({ ... })`;
|
|
88
|
+
}
|
|
76
89
|
const TURN_RETRY_SUPPRESSION_PREFIX = "senpi:no-turn-retry:";
|
|
77
90
|
const DEFERRED_RETRY_QUEUE_OWNERS = new WeakSet();
|
|
78
91
|
/** Format the user-attributed payload for one or more explicit skill invocations. */
|
|
@@ -310,6 +323,121 @@ function isSameOverflowSource(message, model, upstreamModelId) {
|
|
|
310
323
|
const THINKING_LEVELS_WITH_MAX = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
311
324
|
/** Caps explicit skill expansion so one prompt cannot consume unbounded context. */
|
|
312
325
|
export const MAX_SKILL_EXPANSIONS_PER_PROMPT = 5;
|
|
326
|
+
/** Cursor ingest rejects large verbatim tool payloads. The bound is UTF-8 bytes. */
|
|
327
|
+
export const CURSOR_TOOL_RESULT_MAX_CHARS = 2000;
|
|
328
|
+
export const CURSOR_TOOL_RESULT_MAX_BYTES = 50_000;
|
|
329
|
+
const CURSOR_TRUNCATION_MARKER = "\n...[truncated]";
|
|
330
|
+
export function truncateToolResultBodies(messages, maxChars = CURSOR_TOOL_RESULT_MAX_CHARS, maxBytes = CURSOR_TOOL_RESULT_MAX_BYTES, convert = (candidate) => convertToLlm(candidate)) {
|
|
331
|
+
if (!Array.isArray(messages) || messages.length === 0)
|
|
332
|
+
return { messages, changed: false };
|
|
333
|
+
const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
334
|
+
const markerChars = [...segmenter.segment(CURSOR_TRUNCATION_MARKER)].length;
|
|
335
|
+
const result = messages.slice();
|
|
336
|
+
let changed = false;
|
|
337
|
+
// Apply the per-result character cap first, independent of the aggregate wire cap.
|
|
338
|
+
for (let messageIndex = result.length - 1; messageIndex >= 0; messageIndex--) {
|
|
339
|
+
const message = result[messageIndex];
|
|
340
|
+
if (message.role !== "toolResult" || !Array.isArray(message.content))
|
|
341
|
+
continue;
|
|
342
|
+
let content = message.content;
|
|
343
|
+
for (let partIndex = content.length - 1; partIndex >= 0; partIndex--) {
|
|
344
|
+
const part = content[partIndex];
|
|
345
|
+
if (part.type === "image" && typeof part.data === "string")
|
|
346
|
+
continue;
|
|
347
|
+
if (part.type !== "text" || typeof part.text !== "string")
|
|
348
|
+
continue;
|
|
349
|
+
const graphemes = [...segmenter.segment(part.text)].map((item) => item.segment);
|
|
350
|
+
if (graphemes.length <= maxChars)
|
|
351
|
+
continue;
|
|
352
|
+
const kept = graphemes.slice(0, Math.max(0, maxChars - markerChars)).join("");
|
|
353
|
+
const nextText = kept + CURSOR_TRUNCATION_MARKER;
|
|
354
|
+
content = content === message.content ? content.slice() : content;
|
|
355
|
+
content[partIndex] = { ...part, text: nextText };
|
|
356
|
+
result[messageIndex] = { ...message, content };
|
|
357
|
+
changed = true;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
const measure = (candidate) => {
|
|
361
|
+
const converted = convert(candidate);
|
|
362
|
+
const activeUserMessageIndex = converted.at(-1)?.role === "user" ? converted.length - 1 : -1;
|
|
363
|
+
return measureCursorHistorySerializedBytes(converted, activeUserMessageIndex);
|
|
364
|
+
};
|
|
365
|
+
const fits = (candidate = result) => measure(candidate) <= maxBytes;
|
|
366
|
+
if (fits())
|
|
367
|
+
return { messages: changed ? result : messages, changed };
|
|
368
|
+
// Empty the oldest result bodies. Search the monotonic prefix of candidates
|
|
369
|
+
// rather than serializing once for every result (admission must stay bounded).
|
|
370
|
+
const emptyToolResult = (message) => {
|
|
371
|
+
if (message.role !== "toolResult" || !Array.isArray(message.content))
|
|
372
|
+
return message;
|
|
373
|
+
const emptiedContent = message.content.map((part) => part.type === "text" ? { ...part, text: "" } : part.type === "image" ? { ...part, data: "" } : part);
|
|
374
|
+
const content = emptiedContent.filter((part, index) => {
|
|
375
|
+
if (index === 0)
|
|
376
|
+
return true;
|
|
377
|
+
const previous = emptiedContent[index - 1];
|
|
378
|
+
const empty = part.type === "text" ? part.text === "" : part.type === "image" && part.data === "";
|
|
379
|
+
const previousEmpty = previous.type === "text" ? previous.text === "" : previous.type === "image" && previous.data === "";
|
|
380
|
+
return !empty || !previousEmpty;
|
|
381
|
+
});
|
|
382
|
+
return { ...message, content };
|
|
383
|
+
};
|
|
384
|
+
const toolResultIndexes = result.flatMap((message, index) => message.role === "toolResult" && Array.isArray(message.content) ? [index] : []);
|
|
385
|
+
const withEmptyPrefix = (count) => {
|
|
386
|
+
const candidate = result.slice();
|
|
387
|
+
for (let i = 0; i < count; i++)
|
|
388
|
+
candidate[toolResultIndexes[i]] = emptyToolResult(candidate[toolResultIndexes[i]]);
|
|
389
|
+
return candidate;
|
|
390
|
+
};
|
|
391
|
+
let low = 0;
|
|
392
|
+
let high = toolResultIndexes.length;
|
|
393
|
+
while (low < high) {
|
|
394
|
+
const middle = Math.floor((low + high) / 2);
|
|
395
|
+
if (fits(withEmptyPrefix(middle + 1)))
|
|
396
|
+
high = middle;
|
|
397
|
+
else
|
|
398
|
+
low = middle + 1;
|
|
399
|
+
}
|
|
400
|
+
const emptyCount = Math.min(low + 1, toolResultIndexes.length);
|
|
401
|
+
if (emptyCount > 0) {
|
|
402
|
+
result.splice(0, result.length, ...withEmptyPrefix(emptyCount));
|
|
403
|
+
changed = true;
|
|
404
|
+
}
|
|
405
|
+
if (fits())
|
|
406
|
+
return { messages: changed ? result : messages, changed };
|
|
407
|
+
// If metadata alone exceeds the cap, discard the oldest complete turns. This
|
|
408
|
+
// search is also monotonic and avoids quadratic whole-history reserialization.
|
|
409
|
+
const turnRanges = [];
|
|
410
|
+
const isConvertedUser = (message) => convert([message])[0]?.role === "user";
|
|
411
|
+
for (let index = 0; index < result.length; index++) {
|
|
412
|
+
if (!isConvertedUser(result[index]))
|
|
413
|
+
continue;
|
|
414
|
+
const nextUser = result.findIndex((message, nextIndex) => nextIndex > index && isConvertedUser(message));
|
|
415
|
+
turnRanges.push([index, nextUser < 0 ? result.length : nextUser]);
|
|
416
|
+
}
|
|
417
|
+
const withoutTurns = (count) => {
|
|
418
|
+
if (count === 0)
|
|
419
|
+
return result;
|
|
420
|
+
const start = turnRanges[0]?.[0] ?? 0;
|
|
421
|
+
const end = turnRanges[count - 1]?.[1] ?? start;
|
|
422
|
+
return [...result.slice(0, start), ...result.slice(end)];
|
|
423
|
+
};
|
|
424
|
+
low = 0;
|
|
425
|
+
high = turnRanges.length;
|
|
426
|
+
while (low < high) {
|
|
427
|
+
const middle = Math.floor((low + high) / 2);
|
|
428
|
+
if (fits(withoutTurns(middle + 1)))
|
|
429
|
+
high = middle;
|
|
430
|
+
else
|
|
431
|
+
low = middle + 1;
|
|
432
|
+
}
|
|
433
|
+
const turnCount = Math.min(low + 1, turnRanges.length);
|
|
434
|
+
if (turnCount > 0) {
|
|
435
|
+
const next = withoutTurns(turnCount);
|
|
436
|
+
result.splice(0, result.length, ...next);
|
|
437
|
+
changed = true;
|
|
438
|
+
}
|
|
439
|
+
return { messages: changed ? result : messages, changed };
|
|
440
|
+
}
|
|
313
441
|
// ============================================================================
|
|
314
442
|
// AgentSession Class
|
|
315
443
|
// ============================================================================
|
|
@@ -395,6 +523,10 @@ export class AgentSession {
|
|
|
395
523
|
this._pendingBashMessages = [];
|
|
396
524
|
this._turnIndex = 0;
|
|
397
525
|
this._baseToolDefinitions = new Map();
|
|
526
|
+
/** Policy tools withheld from the model, retained so disarming can restore direct access. */
|
|
527
|
+
this._withheldEvalOnlyToolNames = new Set();
|
|
528
|
+
/** Eval-only hint names this session published, so a disarm can withdraw exactly those. */
|
|
529
|
+
this._publishedEvalOnlyHintNames = new Set();
|
|
398
530
|
this._extensionMode = "print";
|
|
399
531
|
// Tool registry for extension getTools/setTools
|
|
400
532
|
this._toolRegistry = new Map();
|
|
@@ -516,6 +648,8 @@ export class AgentSession {
|
|
|
516
648
|
this._extensionRunnerRef = config.extensionRunnerRef;
|
|
517
649
|
this._initialActiveToolNames = config.initialActiveToolNames;
|
|
518
650
|
this._defaultToolNames = config.defaultToolNames ? new Set(config.defaultToolNames) : undefined;
|
|
651
|
+
this._evalOnlyToolNamesOverride = config.evalOnlyToolNames ? new Set(config.evalOnlyToolNames) : undefined;
|
|
652
|
+
this._evalOnlyToolNames = this._resolveEvalOnlyToolNames();
|
|
519
653
|
this._allowedToolNames = config.allowedToolNames ? new Set(config.allowedToolNames) : undefined;
|
|
520
654
|
this._excludedToolNames = config.excludedToolNames ? new Set(config.excludedToolNames) : undefined;
|
|
521
655
|
this._baseToolsOverride = config.baseToolsOverride;
|
|
@@ -727,6 +861,14 @@ export class AgentSession {
|
|
|
727
861
|
(this.agent.prepareNextTurn
|
|
728
862
|
? async (_turn, signal) => await this.agent.prepareNextTurn?.(signal)
|
|
729
863
|
: undefined);
|
|
864
|
+
const previousTransformContext = this.agent.transformContext;
|
|
865
|
+
this.agent.transformContext = async (messages, signal) => {
|
|
866
|
+
const transformed = previousTransformContext ? await previousTransformContext(messages, signal) : messages;
|
|
867
|
+
if (this.model?.provider === "cursor" || this.model?.provider === "cursor-cli-oauth") {
|
|
868
|
+
return ((await truncateToolResultBodies(transformed, CURSOR_TOOL_RESULT_MAX_CHARS, CURSOR_TOOL_RESULT_MAX_BYTES, (candidate) => this.agent.convertToLlm(candidate))).messages ?? transformed);
|
|
869
|
+
}
|
|
870
|
+
return transformed;
|
|
871
|
+
};
|
|
730
872
|
this.agent.prepareNextTurnWithContext = async (turn, signal) => {
|
|
731
873
|
// Enforce compaction only when this prepare precedes an actual provider
|
|
732
874
|
// admission: a tool continuation or queued steer/follow-up messages. A
|
|
@@ -735,7 +877,8 @@ export class AgentSession {
|
|
|
735
877
|
const compactBeforeNextAdmission = async () => {
|
|
736
878
|
const provider = this.model?.provider;
|
|
737
879
|
// Cursor rebuilds the full conversation each hop. Compacting here
|
|
738
|
-
// mutates rootPrompt mid-run and poisons conversationId.
|
|
880
|
+
// mutates rootPrompt mid-run and poisons conversationId (#984).
|
|
881
|
+
// Still truncate verbatim toolResult bodies so the skip cannot send MB-scale payloads (#1043).
|
|
739
882
|
if (provider === "cursor" || provider === "cursor-cli-oauth") {
|
|
740
883
|
return false;
|
|
741
884
|
}
|
|
@@ -816,6 +959,19 @@ export class AgentSession {
|
|
|
816
959
|
// Event Subscription
|
|
817
960
|
// =========================================================================
|
|
818
961
|
/** Emit an event to all listeners */
|
|
962
|
+
_emitEntryAppended(entryId) {
|
|
963
|
+
if (this._extensionMode !== "rpc")
|
|
964
|
+
return;
|
|
965
|
+
const entry = this.sessionManager.getEntry(entryId);
|
|
966
|
+
if (entry)
|
|
967
|
+
this._emit({ type: "entry_appended", entry });
|
|
968
|
+
}
|
|
969
|
+
/** Append a transport-provided entry and publish it on the RPC event stream. */
|
|
970
|
+
appendSessionEntry(entry) {
|
|
971
|
+
this.sessionManager.appendEntry(entry);
|
|
972
|
+
this.agent.state.messages = this.sessionManager.buildSessionContext().messages;
|
|
973
|
+
this._emitEntryAppended(entry.id);
|
|
974
|
+
}
|
|
819
975
|
_emit(event) {
|
|
820
976
|
this._logSessionEvent(event);
|
|
821
977
|
for (const l of this._eventListeners) {
|
|
@@ -976,6 +1132,7 @@ export class AgentSession {
|
|
|
976
1132
|
_invalidateCompactionForModelSelection() {
|
|
977
1133
|
this.abortCompaction();
|
|
978
1134
|
this.abortBranchSummary();
|
|
1135
|
+
this._delegatedCompactionKey = undefined;
|
|
979
1136
|
this._incrementMessageRevision();
|
|
980
1137
|
}
|
|
981
1138
|
_getIdleWaitPromise() {
|
|
@@ -1367,7 +1524,7 @@ export class AgentSession {
|
|
|
1367
1524
|
event.message.role === "assistant" ||
|
|
1368
1525
|
event.message.role === "toolResult") {
|
|
1369
1526
|
// Regular LLM message - persist as SessionMessageEntry
|
|
1370
|
-
this.sessionManager.appendMessage(event.message);
|
|
1527
|
+
this._emitEntryAppended(this.sessionManager.appendMessage(event.message));
|
|
1371
1528
|
this._incrementMessageRevision();
|
|
1372
1529
|
}
|
|
1373
1530
|
// Other message types (bashExecution, compactionSummary, branchSummary) are persisted elsewhere
|
|
@@ -2001,6 +2158,9 @@ export class AgentSession {
|
|
|
2001
2158
|
async executeTool(toolName, params, options) {
|
|
2002
2159
|
let activeTools = this.getActiveToolNames();
|
|
2003
2160
|
let tool = this.agent.state.tools.find((candidate) => candidate.name === toolName);
|
|
2161
|
+
if (!tool && this._isEvalOnlyPolicyArmed() && this._evalOnlyToolNames?.has(toolName)) {
|
|
2162
|
+
tool = this._toolRegistry.get(toolName);
|
|
2163
|
+
}
|
|
2004
2164
|
if (!tool &&
|
|
2005
2165
|
options?.activateInactiveTool === true &&
|
|
2006
2166
|
this._toolDefinitions.has(toolName) &&
|
|
@@ -2075,6 +2235,45 @@ export class AgentSession {
|
|
|
2075
2235
|
return false;
|
|
2076
2236
|
return this._lazyToolActivators.some((activate) => activate(toolName));
|
|
2077
2237
|
}
|
|
2238
|
+
_isEvalOnlyPolicyArmed() {
|
|
2239
|
+
return this._evalOnlyToolNames !== undefined && this._toolRegistry.has("eval");
|
|
2240
|
+
}
|
|
2241
|
+
/**
|
|
2242
|
+
* Resolve the eval-only policy from settings so a reload picks up a flag change.
|
|
2243
|
+
* An explicitly configured set stays authoritative for SDK embedders and tests.
|
|
2244
|
+
*/
|
|
2245
|
+
_resolveEvalOnlyToolNames() {
|
|
2246
|
+
if (this._evalOnlyToolNamesOverride !== undefined)
|
|
2247
|
+
return this._evalOnlyToolNamesOverride;
|
|
2248
|
+
const names = [
|
|
2249
|
+
...(this.settingsManager.getExperimentalBashEvalOnly() ? EVAL_ONLY_SHELL_TOOL_NAMES : []),
|
|
2250
|
+
...(this.settingsManager.getExperimentalWorkflowEvalOnly() ? EVAL_ONLY_WORKFLOW_TOOL_NAMES : []),
|
|
2251
|
+
];
|
|
2252
|
+
return names.length > 0 ? new Set(names) : undefined;
|
|
2253
|
+
}
|
|
2254
|
+
/**
|
|
2255
|
+
* Publish per-tool eval-only redirect hints so a direct call names its own eval helper.
|
|
2256
|
+
*
|
|
2257
|
+
* Hints published here are tracked so a disarm - or a shrinking armed set, e.g. bash+workflow
|
|
2258
|
+
* down to workflow only - withdraws the stale ones. Only tracked names are ever deleted, so
|
|
2259
|
+
* hints owned by other publishers survive.
|
|
2260
|
+
*/
|
|
2261
|
+
_publishEvalOnlyToolHints() {
|
|
2262
|
+
const armed = this._isEvalOnlyPolicyArmed() ? (this._evalOnlyToolNames ?? new Set()) : undefined;
|
|
2263
|
+
for (const name of this._publishedEvalOnlyHintNames) {
|
|
2264
|
+
if (armed?.has(name))
|
|
2265
|
+
continue;
|
|
2266
|
+
delete this.agent.removedToolHints[name];
|
|
2267
|
+
}
|
|
2268
|
+
this._publishedEvalOnlyHintNames.clear();
|
|
2269
|
+
if (!armed)
|
|
2270
|
+
return;
|
|
2271
|
+
for (const name of armed) {
|
|
2272
|
+
this.agent.removedToolHints[name] =
|
|
2273
|
+
`Run ${name} inside an eval cell via ${evalHelperCall(name)}; hooks and permissions still apply.`;
|
|
2274
|
+
this._publishedEvalOnlyHintNames.add(name);
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2078
2277
|
/**
|
|
2079
2278
|
* Set active tools by name.
|
|
2080
2279
|
* Only tools in the registry can be enabled. Unknown tool names are ignored.
|
|
@@ -2102,7 +2301,23 @@ export class AgentSession {
|
|
|
2102
2301
|
setActiveToolsByName(toolNames) {
|
|
2103
2302
|
const tools = [];
|
|
2104
2303
|
const validToolNames = [];
|
|
2105
|
-
|
|
2304
|
+
const policyArmed = this._isEvalOnlyPolicyArmed();
|
|
2305
|
+
this._publishEvalOnlyToolHints();
|
|
2306
|
+
const policyNames = this._evalOnlyToolNames;
|
|
2307
|
+
let filteredToolNames = toolNames;
|
|
2308
|
+
if (policyArmed && policyNames) {
|
|
2309
|
+
for (const name of toolNames) {
|
|
2310
|
+
if (policyNames.has(name))
|
|
2311
|
+
this._withheldEvalOnlyToolNames.add(name);
|
|
2312
|
+
}
|
|
2313
|
+
filteredToolNames = toolNames.filter((name) => !policyNames.has(name));
|
|
2314
|
+
}
|
|
2315
|
+
else {
|
|
2316
|
+
this._withheldEvalOnlyToolNames.clear();
|
|
2317
|
+
}
|
|
2318
|
+
// Remember the unfiltered request so a later disarm can restore withheld shell tools.
|
|
2319
|
+
this._requestedActiveToolNames = [...new Set([...toolNames, ...this._withheldEvalOnlyToolNames])];
|
|
2320
|
+
for (const name of filteredToolNames) {
|
|
2106
2321
|
const tool = this._toolRegistry.get(name);
|
|
2107
2322
|
if (tool) {
|
|
2108
2323
|
tools.push(tool);
|
|
@@ -2259,9 +2474,26 @@ export class AgentSession {
|
|
|
2259
2474
|
appendSystemPrompt: loaderAppendSystemPrompt.length > 0 ? loaderAppendSystemPrompt.join("\n\n") : undefined,
|
|
2260
2475
|
};
|
|
2261
2476
|
const basePrompt = loaderSystemPrompt ?? buildDynamicSystemPrompt(this._baseSystemPromptOptions);
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2477
|
+
const prompt = loaderAppendSystemPrompt.length > 0 ? `${basePrompt}\n\n${loaderAppendSystemPrompt.join("\n\n")}` : basePrompt;
|
|
2478
|
+
if (!this._isEvalOnlyPolicyArmed())
|
|
2479
|
+
return prompt;
|
|
2480
|
+
const armed = this._evalOnlyToolNames ?? new Set();
|
|
2481
|
+
const sentences = [];
|
|
2482
|
+
const shellHelpers = EVAL_ONLY_SHELL_TOOL_NAMES.filter((name) => armed.has(name)).map(evalHelperCall);
|
|
2483
|
+
if (shellHelpers.length > 0) {
|
|
2484
|
+
sentences.push(`Shell commands run ONLY inside eval cells via ${shellHelpers.join(" or ")}; hooks and permissions still apply.`);
|
|
2485
|
+
}
|
|
2486
|
+
if (armed.has("workflow")) {
|
|
2487
|
+
sentences.push(`The workflow tool runs ONLY inside eval cells via ${evalHelperCall("workflow")}; hooks and permissions still apply.`);
|
|
2488
|
+
}
|
|
2489
|
+
// SDK embedders can arm names outside the built-in groups; they still need eval guidance.
|
|
2490
|
+
const otherHelpers = [...armed]
|
|
2491
|
+
.filter((name) => !EVAL_ONLY_SHELL_TOOL_NAMES.includes(name) && name !== "workflow")
|
|
2492
|
+
.map(evalHelperCall);
|
|
2493
|
+
if (otherHelpers.length > 0) {
|
|
2494
|
+
sentences.push(`These tools run ONLY inside eval cells via ${otherHelpers.join(" or ")}; hooks and permissions still apply.`);
|
|
2495
|
+
}
|
|
2496
|
+
return sentences.length > 0 ? `${prompt}\n\n${sentences.join("\n\n")}` : prompt;
|
|
2265
2497
|
}
|
|
2266
2498
|
/**
|
|
2267
2499
|
* Send a prompt to the agent.
|
|
@@ -3053,7 +3285,7 @@ export class AgentSession {
|
|
|
3053
3285
|
}
|
|
3054
3286
|
_appendCustomMessage(appMessage) {
|
|
3055
3287
|
this.agent.state.messages.push(appMessage);
|
|
3056
|
-
this.sessionManager.appendCustomMessageEntry(appMessage.customType, appMessage.content, appMessage.display, appMessage.details);
|
|
3288
|
+
this._emitEntryAppended(this.sessionManager.appendCustomMessageEntry(appMessage.customType, appMessage.content, appMessage.display, appMessage.details));
|
|
3057
3289
|
this._incrementMessageRevision();
|
|
3058
3290
|
this._emit({ type: "message_start", message: appMessage });
|
|
3059
3291
|
this._emit({ type: "message_end", message: appMessage });
|
|
@@ -3336,7 +3568,10 @@ export class AgentSession {
|
|
|
3336
3568
|
}
|
|
3337
3569
|
async _switchActiveModel(model, opts) {
|
|
3338
3570
|
const previousModel = this.model;
|
|
3339
|
-
if (opts.invalidateCompaction &&
|
|
3571
|
+
if (opts.invalidateCompaction &&
|
|
3572
|
+
(this._modelSelectionChangesContext(previousModel, model) ||
|
|
3573
|
+
previousModel?.provider !== model.provider ||
|
|
3574
|
+
previousModel?.id !== model.id)) {
|
|
3340
3575
|
this._invalidateCompactionForModelSelection();
|
|
3341
3576
|
}
|
|
3342
3577
|
const thinking = this._getThinkingForModelSwitch(model, opts.ephemeralThinkingLevel);
|
|
@@ -3408,7 +3643,9 @@ export class AgentSession {
|
|
|
3408
3643
|
nextIndex = direction === "forward" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;
|
|
3409
3644
|
}
|
|
3410
3645
|
const next = favoriteModels[nextIndex];
|
|
3411
|
-
const invalidatesCompaction = this._modelSelectionChangesContext(currentModel, next.model)
|
|
3646
|
+
const invalidatesCompaction = this._modelSelectionChangesContext(currentModel, next.model) ||
|
|
3647
|
+
currentModel?.provider !== next.model.provider ||
|
|
3648
|
+
currentModel?.id !== next.model.id;
|
|
3412
3649
|
if (invalidatesCompaction) {
|
|
3413
3650
|
this._invalidateCompactionForModelSelection();
|
|
3414
3651
|
}
|
|
@@ -3610,6 +3847,7 @@ export class AgentSession {
|
|
|
3610
3847
|
setSteeringMode(mode) {
|
|
3611
3848
|
this.agent.steeringMode = mode;
|
|
3612
3849
|
this.settingsManager.setSteeringMode(mode);
|
|
3850
|
+
this._emitSessionSettingsChanged();
|
|
3613
3851
|
}
|
|
3614
3852
|
/**
|
|
3615
3853
|
* Set follow-up message mode.
|
|
@@ -3618,6 +3856,7 @@ export class AgentSession {
|
|
|
3618
3856
|
setFollowUpMode(mode) {
|
|
3619
3857
|
this.agent.followUpMode = mode;
|
|
3620
3858
|
this.settingsManager.setFollowUpMode(mode);
|
|
3859
|
+
this._emitSessionSettingsChanged();
|
|
3621
3860
|
}
|
|
3622
3861
|
// =========================================================================
|
|
3623
3862
|
// Compaction
|
|
@@ -4100,7 +4339,7 @@ export class AgentSession {
|
|
|
4100
4339
|
if (sourceChanged) {
|
|
4101
4340
|
return await this._rejectCompaction(request, requestId, operationId, "stale-revision", false);
|
|
4102
4341
|
}
|
|
4103
|
-
if (this._wouldCompactionOverflow(pathEntries, compactionResult, fromExtension, model)) {
|
|
4342
|
+
if (await this._wouldCompactionOverflow(pathEntries, compactionResult, fromExtension, model)) {
|
|
4104
4343
|
return await this._rejectCompaction(request, requestId, operationId, "would-overflow", false);
|
|
4105
4344
|
}
|
|
4106
4345
|
const compactionEntryId = this.sessionManager.appendCompaction(compactionResult.summary, compactionResult.firstKeptEntryId, compactionResult.tokensBefore, compactionResult.details, fromExtension, compactionResult.usage);
|
|
@@ -4141,6 +4380,7 @@ export class AgentSession {
|
|
|
4141
4380
|
})) {
|
|
4142
4381
|
throw new CompactionCancelledError();
|
|
4143
4382
|
}
|
|
4383
|
+
this._delegatedCompactionKey = undefined;
|
|
4144
4384
|
if (request.owner === "compaction" && this._compactionAbortController === request.controller) {
|
|
4145
4385
|
this._compactionAbortController = undefined;
|
|
4146
4386
|
}
|
|
@@ -4191,7 +4431,7 @@ export class AgentSession {
|
|
|
4191
4431
|
finishCompactionWork();
|
|
4192
4432
|
}
|
|
4193
4433
|
}
|
|
4194
|
-
_wouldCompactionOverflow(pathEntries, compactionResult, fromExtension, model) {
|
|
4434
|
+
async _wouldCompactionOverflow(pathEntries, compactionResult, fromExtension, model) {
|
|
4195
4435
|
const currentLeaf = pathEntries[pathEntries.length - 1];
|
|
4196
4436
|
if (!currentLeaf)
|
|
4197
4437
|
return false;
|
|
@@ -4206,7 +4446,12 @@ export class AgentSession {
|
|
|
4206
4446
|
details: compactionResult.details,
|
|
4207
4447
|
fromHook: fromExtension,
|
|
4208
4448
|
};
|
|
4209
|
-
|
|
4449
|
+
let simulatedMessages = buildSessionContext([...pathEntries, simulatedCompactionEntry], simulatedCompactionEntry.id).messages;
|
|
4450
|
+
// Size the same retained context that will be admitted to Cursor. Persisted JSONL
|
|
4451
|
+
// remains verbatim, but the in-memory request representation is bounded first.
|
|
4452
|
+
if (model.provider === "cursor" || model.provider === "cursor-cli-oauth") {
|
|
4453
|
+
simulatedMessages = truncateToolResultBodies(simulatedMessages).messages ?? simulatedMessages;
|
|
4454
|
+
}
|
|
4210
4455
|
const contextTokens = estimateMessagesTokens(filterContextExcludedMessages(simulatedMessages));
|
|
4211
4456
|
const settings = this.settingsManager.getCompactionSettings();
|
|
4212
4457
|
return contextTokens > model.contextWindow - settings.reserveTokens;
|
|
@@ -4237,6 +4482,9 @@ export class AgentSession {
|
|
|
4237
4482
|
// also emitted with accepted:false so the compaction extension's circuit-breaker
|
|
4238
4483
|
// bookkeeping stops being dead code; other builtin session_compact handlers guard
|
|
4239
4484
|
// on event.accepted.
|
|
4485
|
+
if (rejectionCause === "external-owner" && this.model) {
|
|
4486
|
+
this._delegatedCompactionKey = { provider: this.model.provider, id: this.model.id };
|
|
4487
|
+
}
|
|
4240
4488
|
const trimmedExtensionReason = extensionReason?.trim();
|
|
4241
4489
|
const detailedMessage = trimmedExtensionReason
|
|
4242
4490
|
? `Compaction rejected: ${trimmedExtensionReason}`
|
|
@@ -4573,15 +4821,15 @@ export class AgentSession {
|
|
|
4573
4821
|
return claimant !== undefined && !claimant.signal.aborted;
|
|
4574
4822
|
}
|
|
4575
4823
|
_isCompactionDelegated() {
|
|
4576
|
-
const state = this._compactionLifecycle.state;
|
|
4577
4824
|
const model = this.model;
|
|
4578
|
-
return (
|
|
4579
|
-
state.rejectionCause === "external-owner" &&
|
|
4580
|
-
state.model !== undefined &&
|
|
4825
|
+
return (this._delegatedCompactionKey !== undefined &&
|
|
4581
4826
|
model !== undefined &&
|
|
4582
|
-
|
|
4827
|
+
this._delegatedCompactionKey.provider === model.provider &&
|
|
4828
|
+
this._delegatedCompactionKey.id === model.id);
|
|
4583
4829
|
}
|
|
4584
4830
|
async _runPrePromptCompaction(lastAssistantMessage, skipAbortedCheck, reason = "pre_prompt", willRetry = false, allowSummaryOnly = false) {
|
|
4831
|
+
if (this._isCompactionDelegated())
|
|
4832
|
+
return false;
|
|
4585
4833
|
const controller = new AbortController();
|
|
4586
4834
|
const requestId = randomUUID();
|
|
4587
4835
|
this._claimCompactionController(controller, "compaction");
|
|
@@ -4754,6 +5002,8 @@ export class AgentSession {
|
|
|
4754
5002
|
* @returns Whether the post-run loop should call `agent.continue()`
|
|
4755
5003
|
*/
|
|
4756
5004
|
async _runAutoCompaction(reason, willRetry) {
|
|
5005
|
+
if (this._isCompactionDelegated())
|
|
5006
|
+
return false;
|
|
4757
5007
|
const finishCompactionWork = this._sessionWorkBarrier.begin();
|
|
4758
5008
|
const agentMessagesAtStart = this.agent.state.messages.slice();
|
|
4759
5009
|
const autoCompactionController = new AbortController();
|
|
@@ -4887,6 +5137,15 @@ export class AgentSession {
|
|
|
4887
5137
|
*/
|
|
4888
5138
|
setAutoCompactionEnabled(enabled) {
|
|
4889
5139
|
this.settingsManager.setCompactionEnabled(enabled);
|
|
5140
|
+
this._emitSessionSettingsChanged();
|
|
5141
|
+
}
|
|
5142
|
+
_emitSessionSettingsChanged() {
|
|
5143
|
+
this._emit({
|
|
5144
|
+
type: "session_settings_changed",
|
|
5145
|
+
steeringMode: this.steeringMode,
|
|
5146
|
+
followUpMode: this.followUpMode,
|
|
5147
|
+
autoCompactionEnabled: this.autoCompactionEnabled,
|
|
5148
|
+
});
|
|
4890
5149
|
}
|
|
4891
5150
|
/** Whether auto-compaction is enabled */
|
|
4892
5151
|
get autoCompactionEnabled() {
|
|
@@ -4984,6 +5243,7 @@ export class AgentSession {
|
|
|
4984
5243
|
: undefined;
|
|
4985
5244
|
}
|
|
4986
5245
|
_refreshCurrentModelFromRegistry() {
|
|
5246
|
+
this._delegatedCompactionKey = undefined;
|
|
4987
5247
|
const currentModel = this.model;
|
|
4988
5248
|
if (!currentModel) {
|
|
4989
5249
|
return;
|
|
@@ -5308,8 +5568,11 @@ export class AgentSession {
|
|
|
5308
5568
|
}),
|
|
5309
5569
|
})),
|
|
5310
5570
|
].filter((tool) => isAllowedTool(tool.definition.name));
|
|
5571
|
+
// Withheld tools stay in _baseToolDefinitions (and therefore in _toolRegistry, which
|
|
5572
|
+
// getRegisteredTool serves to the Cursor exec bridge) but are dropped from the model-facing
|
|
5573
|
+
// definitions so they never reach the prompt. See temporarilyDisabledToolNames.
|
|
5311
5574
|
const definitionRegistry = new Map(Array.from(this._baseToolDefinitions.entries())
|
|
5312
|
-
.filter(([name]) => isAllowedTool(name))
|
|
5575
|
+
.filter(([name]) => isAllowedTool(name) && !temporarilyDisabledToolNames.has(name))
|
|
5313
5576
|
.map(([name, definition]) => [
|
|
5314
5577
|
name,
|
|
5315
5578
|
{
|
|
@@ -5351,13 +5614,22 @@ export class AgentSession {
|
|
|
5351
5614
|
toolRegistry.set(tool.name, tool);
|
|
5352
5615
|
}
|
|
5353
5616
|
this._toolRegistry = toolRegistry;
|
|
5617
|
+
this._publishEvalOnlyToolHints();
|
|
5354
5618
|
const isDirectlyExposed = (name) => {
|
|
5355
5619
|
const entry = this._toolDefinitions.get(name);
|
|
5356
5620
|
return entry !== undefined && normalizeToolExposure(entry.definition).exposure === "direct";
|
|
5357
5621
|
};
|
|
5358
|
-
|
|
5622
|
+
// A withheld tool is dropped from the DEFAULT selection only. An explicit activeToolNames
|
|
5623
|
+
// request names the tool deliberately, and callers that do so (tests, SDK embedders,
|
|
5624
|
+
// filesystem-policy wiring) still expect it to activate.
|
|
5625
|
+
const hasExplicitActiveToolNames = options?.activeToolNames !== undefined;
|
|
5626
|
+
const nextActiveToolNames = (options?.activeToolNames
|
|
5627
|
+
? [...options.activeToolNames]
|
|
5628
|
+
: [...(this._requestedActiveToolNames ?? previousActiveToolNames)]).filter((name) => {
|
|
5359
5629
|
if (!isAllowedTool(name))
|
|
5360
5630
|
return false;
|
|
5631
|
+
if (!hasExplicitActiveToolNames && temporarilyDisabledToolNames.has(name))
|
|
5632
|
+
return false;
|
|
5361
5633
|
const previousRegistrationIds = options?.previousActiveToolRegistrationIds;
|
|
5362
5634
|
if (!previousRegistrationIds)
|
|
5363
5635
|
return true;
|
|
@@ -5388,6 +5660,7 @@ export class AgentSession {
|
|
|
5388
5660
|
this.setActiveToolsByName([...new Set(nextActiveToolNames)]);
|
|
5389
5661
|
}
|
|
5390
5662
|
_buildRuntime(options) {
|
|
5663
|
+
this._delegatedCompactionKey = undefined;
|
|
5391
5664
|
const autoResizeImages = this.settingsManager.getImageAutoResize();
|
|
5392
5665
|
const shellCommandPrefix = this.settingsManager.getShellCommandPrefix();
|
|
5393
5666
|
const shellPath = this.settingsManager.getShellPath();
|
|
@@ -5439,7 +5712,9 @@ export class AgentSession {
|
|
|
5439
5712
|
const oldExtensionIdentities = oldExtensionRunner.getExtensionIdentities();
|
|
5440
5713
|
const previousFlagValues = oldExtensionRunner.getFlagValues();
|
|
5441
5714
|
const previousActiveToolRegistrationIds = new Map();
|
|
5442
|
-
|
|
5715
|
+
// Cover withheld eval-only tools too: the rebuild drops any seeded name missing from this
|
|
5716
|
+
// map, which would strand shell tools when the policy disarms during this reload.
|
|
5717
|
+
for (const name of this._requestedActiveToolNames ?? this.getActiveToolNames()) {
|
|
5443
5718
|
const entry = this._toolDefinitions.get(name);
|
|
5444
5719
|
if (entry)
|
|
5445
5720
|
previousActiveToolRegistrationIds.set(name, deriveExtensionRegistrationId(entry.sourceInfo, name));
|
|
@@ -5450,6 +5725,12 @@ export class AgentSession {
|
|
|
5450
5725
|
});
|
|
5451
5726
|
time("shutdown", "reload");
|
|
5452
5727
|
await this.settingsManager.reload();
|
|
5728
|
+
this._delegatedCompactionKey = undefined;
|
|
5729
|
+
// Capture the unfiltered request BEFORE the rebuild reassigns it, so a policy that
|
|
5730
|
+
// disarms during this reload can still restore its withheld shell tools.
|
|
5731
|
+
const requestedActiveToolNamesBeforeRebuild = [...(this._requestedActiveToolNames ?? this.getActiveToolNames())];
|
|
5732
|
+
// A flag change must take effect on reload, matching documented settings hot-reload behavior.
|
|
5733
|
+
this._evalOnlyToolNames = this._resolveEvalOnlyToolNames();
|
|
5453
5734
|
this.syncQueueModesFromSettings();
|
|
5454
5735
|
resetApiProviders();
|
|
5455
5736
|
time("settings", "reload");
|
|
@@ -5474,7 +5755,7 @@ export class AgentSession {
|
|
|
5474
5755
|
time("resources", "reload");
|
|
5475
5756
|
try {
|
|
5476
5757
|
this._buildRuntime({
|
|
5477
|
-
activeToolNames:
|
|
5758
|
+
activeToolNames: requestedActiveToolNamesBeforeRebuild,
|
|
5478
5759
|
flagValues: previousFlagValues,
|
|
5479
5760
|
includeAllExtensionTools: true,
|
|
5480
5761
|
previousActiveToolRegistrationIds,
|
|
@@ -6179,12 +6460,13 @@ export class AgentSession {
|
|
|
6179
6460
|
try {
|
|
6180
6461
|
const result = await executeBashWithOperations(resolvedCommand, this.sessionManager.getCwd(), options?.operations ?? createLocalBashOperations({ shellPath }), {
|
|
6181
6462
|
onChunk: (delta) => {
|
|
6182
|
-
onChunk?.(delta);
|
|
6463
|
+
const callbackResult = onChunk?.(delta);
|
|
6183
6464
|
this._emit({
|
|
6184
6465
|
type: "bash_execution_update",
|
|
6185
6466
|
id: options?.id,
|
|
6186
6467
|
delta,
|
|
6187
6468
|
});
|
|
6469
|
+
return callbackResult;
|
|
6188
6470
|
},
|
|
6189
6471
|
signal: abortController.signal,
|
|
6190
6472
|
});
|
|
@@ -6232,6 +6514,10 @@ export class AgentSession {
|
|
|
6232
6514
|
abortController.abort();
|
|
6233
6515
|
}
|
|
6234
6516
|
}
|
|
6517
|
+
/** Remove a host-owned full-output spill after a remote observer failed. */
|
|
6518
|
+
async cleanupBashOutput(path) {
|
|
6519
|
+
await rm(path, { force: true });
|
|
6520
|
+
}
|
|
6235
6521
|
/** Whether a bash command is currently running */
|
|
6236
6522
|
get isBashRunning() {
|
|
6237
6523
|
return this._bashAbortControllers.size > 0;
|
|
@@ -6433,6 +6719,7 @@ export class AgentSession {
|
|
|
6433
6719
|
}
|
|
6434
6720
|
// Update agent state (preserving exact messages still awaiting persistence)
|
|
6435
6721
|
this._restoreAgentMessagesFromSession();
|
|
6722
|
+
this._delegatedCompactionKey = undefined;
|
|
6436
6723
|
this._incrementMessageRevision();
|
|
6437
6724
|
// Emit session_tree event
|
|
6438
6725
|
await this._extensionRunner.emit({
|