@code-yeongyu/senpi 2026.8.28 → 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 +140 -0
- package/dist/cli/args.d.ts +2 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +4 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +2 -0
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +50 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +313 -25
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +1 -0
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +3 -0
- package/dist/core/auth-storage.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 +45 -21
- package/dist/core/credential-accounts.js.map +1 -1
- package/dist/core/credential-pool/classify.d.ts +6 -1
- package/dist/core/credential-pool/classify.d.ts.map +1 -1
- package/dist/core/credential-pool/classify.js +22 -5
- package/dist/core/credential-pool/classify.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/credential-pool/failover.d.ts +8 -0
- package/dist/core/credential-pool/failover.d.ts.map +1 -1
- package/dist/core/credential-pool/failover.js +24 -6
- package/dist/core/credential-pool/failover.js.map +1 -1
- package/dist/core/credential-pool/rotation-stream.d.ts +13 -2
- package/dist/core/credential-pool/rotation-stream.d.ts.map +1 -1
- package/dist/core/credential-pool/rotation-stream.js +97 -10
- package/dist/core/credential-pool/rotation-stream.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/goal/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/index.js +7 -0
- package/dist/core/extensions/builtin/goal/index.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/service-tier.d.ts.map +1 -1
- package/dist/core/extensions/builtin/service-tier.js +13 -0
- package/dist/core/extensions/builtin/service-tier.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/extension.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/extension.js +4 -2
- package/dist/core/extensions/builtin/terminal/extension.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-config-schema.d.ts +4 -4
- package/dist/core/model-resolver.js +2 -2
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/model-runtime.d.ts +2 -0
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +74 -37
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/retry-fallback/chains.d.ts +23 -1
- package/dist/core/retry-fallback/chains.d.ts.map +1 -1
- package/dist/core/retry-fallback/chains.js +59 -3
- package/dist/core/retry-fallback/chains.js.map +1 -1
- package/dist/core/retry-fallback/controller.d.ts.map +1 -1
- package/dist/core/retry-fallback/controller.js +9 -2
- package/dist/core/retry-fallback/controller.js.map +1 -1
- package/dist/core/retry-fallback/settings.d.ts.map +1 -1
- package/dist/core/retry-fallback/settings.js +9 -0
- package/dist/core/retry-fallback/settings.js.map +1 -1
- package/dist/core/retry-fallback/validate.js +1 -1
- package/dist/core/retry-fallback/validate.js.map +1 -1
- package/dist/core/sdk.js +1 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +9 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +40 -0
- 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/main.d.ts +7 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +10 -1
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/favorite-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/favorite-models-selector.js +2 -2
- package/dist/modes/interactive/components/favorite-models-selector.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 +545 -40
- 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 +390 -239
- 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 +268 -63
- 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 +20 -8
- package/dist/modes/rpc/multi-session-host.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +91 -16
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +129 -37
- 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-mode.d.ts +3 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +3 -1
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +241 -3
- 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 +22 -0
- package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
- package/dist/modes/rpc/session-command-router.js +148 -16
- package/dist/modes/rpc/session-command-router.js.map +1 -1
- package/dist/modes/rpc/session-event-writer.d.ts +4 -0
- package/dist/modes/rpc/session-event-writer.d.ts.map +1 -1
- package/dist/modes/rpc/session-event-writer.js +58 -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 +17 -2
- package/dist/modes/rpc/session-registry.d.ts.map +1 -1
- package/dist/modes/rpc/session-registry.js +67 -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 +40 -1
- package/docs/settings.md +34 -0
- package/docs/windows.md +2 -0
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +62 -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/agent-loop.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +6 -3
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
- 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/amazon-bedrock.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/baseten.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/huggingface.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/nvidia.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/vercel-ai-gateway.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) {
|
|
@@ -934,6 +1090,7 @@ export class AgentSession {
|
|
|
934
1090
|
type: "queue_update",
|
|
935
1091
|
steering: [...this._steeringMessages],
|
|
936
1092
|
followUp: [...this._followUpMessages],
|
|
1093
|
+
ordered: [...this._queuedInputOrder].sort((a, b) => a.enqueueOrder - b.enqueueOrder),
|
|
937
1094
|
});
|
|
938
1095
|
}
|
|
939
1096
|
_incrementMessageRevision() {
|
|
@@ -975,6 +1132,7 @@ export class AgentSession {
|
|
|
975
1132
|
_invalidateCompactionForModelSelection() {
|
|
976
1133
|
this.abortCompaction();
|
|
977
1134
|
this.abortBranchSummary();
|
|
1135
|
+
this._delegatedCompactionKey = undefined;
|
|
978
1136
|
this._incrementMessageRevision();
|
|
979
1137
|
}
|
|
980
1138
|
_getIdleWaitPromise() {
|
|
@@ -1366,7 +1524,7 @@ export class AgentSession {
|
|
|
1366
1524
|
event.message.role === "assistant" ||
|
|
1367
1525
|
event.message.role === "toolResult") {
|
|
1368
1526
|
// Regular LLM message - persist as SessionMessageEntry
|
|
1369
|
-
this.sessionManager.appendMessage(event.message);
|
|
1527
|
+
this._emitEntryAppended(this.sessionManager.appendMessage(event.message));
|
|
1370
1528
|
this._incrementMessageRevision();
|
|
1371
1529
|
}
|
|
1372
1530
|
// Other message types (bashExecution, compactionSummary, branchSummary) are persisted elsewhere
|
|
@@ -2000,6 +2158,9 @@ export class AgentSession {
|
|
|
2000
2158
|
async executeTool(toolName, params, options) {
|
|
2001
2159
|
let activeTools = this.getActiveToolNames();
|
|
2002
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
|
+
}
|
|
2003
2164
|
if (!tool &&
|
|
2004
2165
|
options?.activateInactiveTool === true &&
|
|
2005
2166
|
this._toolDefinitions.has(toolName) &&
|
|
@@ -2074,6 +2235,45 @@ export class AgentSession {
|
|
|
2074
2235
|
return false;
|
|
2075
2236
|
return this._lazyToolActivators.some((activate) => activate(toolName));
|
|
2076
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
|
+
}
|
|
2077
2277
|
/**
|
|
2078
2278
|
* Set active tools by name.
|
|
2079
2279
|
* Only tools in the registry can be enabled. Unknown tool names are ignored.
|
|
@@ -2101,7 +2301,23 @@ export class AgentSession {
|
|
|
2101
2301
|
setActiveToolsByName(toolNames) {
|
|
2102
2302
|
const tools = [];
|
|
2103
2303
|
const validToolNames = [];
|
|
2104
|
-
|
|
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) {
|
|
2105
2321
|
const tool = this._toolRegistry.get(name);
|
|
2106
2322
|
if (tool) {
|
|
2107
2323
|
tools.push(tool);
|
|
@@ -2258,9 +2474,26 @@ export class AgentSession {
|
|
|
2258
2474
|
appendSystemPrompt: loaderAppendSystemPrompt.length > 0 ? loaderAppendSystemPrompt.join("\n\n") : undefined,
|
|
2259
2475
|
};
|
|
2260
2476
|
const basePrompt = loaderSystemPrompt ?? buildDynamicSystemPrompt(this._baseSystemPromptOptions);
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
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;
|
|
2264
2497
|
}
|
|
2265
2498
|
/**
|
|
2266
2499
|
* Send a prompt to the agent.
|
|
@@ -3052,7 +3285,7 @@ export class AgentSession {
|
|
|
3052
3285
|
}
|
|
3053
3286
|
_appendCustomMessage(appMessage) {
|
|
3054
3287
|
this.agent.state.messages.push(appMessage);
|
|
3055
|
-
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));
|
|
3056
3289
|
this._incrementMessageRevision();
|
|
3057
3290
|
this._emit({ type: "message_start", message: appMessage });
|
|
3058
3291
|
this._emit({ type: "message_end", message: appMessage });
|
|
@@ -3335,7 +3568,10 @@ export class AgentSession {
|
|
|
3335
3568
|
}
|
|
3336
3569
|
async _switchActiveModel(model, opts) {
|
|
3337
3570
|
const previousModel = this.model;
|
|
3338
|
-
if (opts.invalidateCompaction &&
|
|
3571
|
+
if (opts.invalidateCompaction &&
|
|
3572
|
+
(this._modelSelectionChangesContext(previousModel, model) ||
|
|
3573
|
+
previousModel?.provider !== model.provider ||
|
|
3574
|
+
previousModel?.id !== model.id)) {
|
|
3339
3575
|
this._invalidateCompactionForModelSelection();
|
|
3340
3576
|
}
|
|
3341
3577
|
const thinking = this._getThinkingForModelSwitch(model, opts.ephemeralThinkingLevel);
|
|
@@ -3407,7 +3643,9 @@ export class AgentSession {
|
|
|
3407
3643
|
nextIndex = direction === "forward" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;
|
|
3408
3644
|
}
|
|
3409
3645
|
const next = favoriteModels[nextIndex];
|
|
3410
|
-
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;
|
|
3411
3649
|
if (invalidatesCompaction) {
|
|
3412
3650
|
this._invalidateCompactionForModelSelection();
|
|
3413
3651
|
}
|
|
@@ -3609,6 +3847,7 @@ export class AgentSession {
|
|
|
3609
3847
|
setSteeringMode(mode) {
|
|
3610
3848
|
this.agent.steeringMode = mode;
|
|
3611
3849
|
this.settingsManager.setSteeringMode(mode);
|
|
3850
|
+
this._emitSessionSettingsChanged();
|
|
3612
3851
|
}
|
|
3613
3852
|
/**
|
|
3614
3853
|
* Set follow-up message mode.
|
|
@@ -3617,6 +3856,7 @@ export class AgentSession {
|
|
|
3617
3856
|
setFollowUpMode(mode) {
|
|
3618
3857
|
this.agent.followUpMode = mode;
|
|
3619
3858
|
this.settingsManager.setFollowUpMode(mode);
|
|
3859
|
+
this._emitSessionSettingsChanged();
|
|
3620
3860
|
}
|
|
3621
3861
|
// =========================================================================
|
|
3622
3862
|
// Compaction
|
|
@@ -4099,7 +4339,7 @@ export class AgentSession {
|
|
|
4099
4339
|
if (sourceChanged) {
|
|
4100
4340
|
return await this._rejectCompaction(request, requestId, operationId, "stale-revision", false);
|
|
4101
4341
|
}
|
|
4102
|
-
if (this._wouldCompactionOverflow(pathEntries, compactionResult, fromExtension, model)) {
|
|
4342
|
+
if (await this._wouldCompactionOverflow(pathEntries, compactionResult, fromExtension, model)) {
|
|
4103
4343
|
return await this._rejectCompaction(request, requestId, operationId, "would-overflow", false);
|
|
4104
4344
|
}
|
|
4105
4345
|
const compactionEntryId = this.sessionManager.appendCompaction(compactionResult.summary, compactionResult.firstKeptEntryId, compactionResult.tokensBefore, compactionResult.details, fromExtension, compactionResult.usage);
|
|
@@ -4140,6 +4380,7 @@ export class AgentSession {
|
|
|
4140
4380
|
})) {
|
|
4141
4381
|
throw new CompactionCancelledError();
|
|
4142
4382
|
}
|
|
4383
|
+
this._delegatedCompactionKey = undefined;
|
|
4143
4384
|
if (request.owner === "compaction" && this._compactionAbortController === request.controller) {
|
|
4144
4385
|
this._compactionAbortController = undefined;
|
|
4145
4386
|
}
|
|
@@ -4190,7 +4431,7 @@ export class AgentSession {
|
|
|
4190
4431
|
finishCompactionWork();
|
|
4191
4432
|
}
|
|
4192
4433
|
}
|
|
4193
|
-
_wouldCompactionOverflow(pathEntries, compactionResult, fromExtension, model) {
|
|
4434
|
+
async _wouldCompactionOverflow(pathEntries, compactionResult, fromExtension, model) {
|
|
4194
4435
|
const currentLeaf = pathEntries[pathEntries.length - 1];
|
|
4195
4436
|
if (!currentLeaf)
|
|
4196
4437
|
return false;
|
|
@@ -4205,7 +4446,12 @@ export class AgentSession {
|
|
|
4205
4446
|
details: compactionResult.details,
|
|
4206
4447
|
fromHook: fromExtension,
|
|
4207
4448
|
};
|
|
4208
|
-
|
|
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
|
+
}
|
|
4209
4455
|
const contextTokens = estimateMessagesTokens(filterContextExcludedMessages(simulatedMessages));
|
|
4210
4456
|
const settings = this.settingsManager.getCompactionSettings();
|
|
4211
4457
|
return contextTokens > model.contextWindow - settings.reserveTokens;
|
|
@@ -4236,6 +4482,9 @@ export class AgentSession {
|
|
|
4236
4482
|
// also emitted with accepted:false so the compaction extension's circuit-breaker
|
|
4237
4483
|
// bookkeeping stops being dead code; other builtin session_compact handlers guard
|
|
4238
4484
|
// on event.accepted.
|
|
4485
|
+
if (rejectionCause === "external-owner" && this.model) {
|
|
4486
|
+
this._delegatedCompactionKey = { provider: this.model.provider, id: this.model.id };
|
|
4487
|
+
}
|
|
4239
4488
|
const trimmedExtensionReason = extensionReason?.trim();
|
|
4240
4489
|
const detailedMessage = trimmedExtensionReason
|
|
4241
4490
|
? `Compaction rejected: ${trimmedExtensionReason}`
|
|
@@ -4572,15 +4821,15 @@ export class AgentSession {
|
|
|
4572
4821
|
return claimant !== undefined && !claimant.signal.aborted;
|
|
4573
4822
|
}
|
|
4574
4823
|
_isCompactionDelegated() {
|
|
4575
|
-
const state = this._compactionLifecycle.state;
|
|
4576
4824
|
const model = this.model;
|
|
4577
|
-
return (
|
|
4578
|
-
state.rejectionCause === "external-owner" &&
|
|
4579
|
-
state.model !== undefined &&
|
|
4825
|
+
return (this._delegatedCompactionKey !== undefined &&
|
|
4580
4826
|
model !== undefined &&
|
|
4581
|
-
|
|
4827
|
+
this._delegatedCompactionKey.provider === model.provider &&
|
|
4828
|
+
this._delegatedCompactionKey.id === model.id);
|
|
4582
4829
|
}
|
|
4583
4830
|
async _runPrePromptCompaction(lastAssistantMessage, skipAbortedCheck, reason = "pre_prompt", willRetry = false, allowSummaryOnly = false) {
|
|
4831
|
+
if (this._isCompactionDelegated())
|
|
4832
|
+
return false;
|
|
4584
4833
|
const controller = new AbortController();
|
|
4585
4834
|
const requestId = randomUUID();
|
|
4586
4835
|
this._claimCompactionController(controller, "compaction");
|
|
@@ -4753,6 +5002,8 @@ export class AgentSession {
|
|
|
4753
5002
|
* @returns Whether the post-run loop should call `agent.continue()`
|
|
4754
5003
|
*/
|
|
4755
5004
|
async _runAutoCompaction(reason, willRetry) {
|
|
5005
|
+
if (this._isCompactionDelegated())
|
|
5006
|
+
return false;
|
|
4756
5007
|
const finishCompactionWork = this._sessionWorkBarrier.begin();
|
|
4757
5008
|
const agentMessagesAtStart = this.agent.state.messages.slice();
|
|
4758
5009
|
const autoCompactionController = new AbortController();
|
|
@@ -4886,6 +5137,15 @@ export class AgentSession {
|
|
|
4886
5137
|
*/
|
|
4887
5138
|
setAutoCompactionEnabled(enabled) {
|
|
4888
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
|
+
});
|
|
4889
5149
|
}
|
|
4890
5150
|
/** Whether auto-compaction is enabled */
|
|
4891
5151
|
get autoCompactionEnabled() {
|
|
@@ -4983,6 +5243,7 @@ export class AgentSession {
|
|
|
4983
5243
|
: undefined;
|
|
4984
5244
|
}
|
|
4985
5245
|
_refreshCurrentModelFromRegistry() {
|
|
5246
|
+
this._delegatedCompactionKey = undefined;
|
|
4986
5247
|
const currentModel = this.model;
|
|
4987
5248
|
if (!currentModel) {
|
|
4988
5249
|
return;
|
|
@@ -5307,8 +5568,11 @@ export class AgentSession {
|
|
|
5307
5568
|
}),
|
|
5308
5569
|
})),
|
|
5309
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.
|
|
5310
5574
|
const definitionRegistry = new Map(Array.from(this._baseToolDefinitions.entries())
|
|
5311
|
-
.filter(([name]) => isAllowedTool(name))
|
|
5575
|
+
.filter(([name]) => isAllowedTool(name) && !temporarilyDisabledToolNames.has(name))
|
|
5312
5576
|
.map(([name, definition]) => [
|
|
5313
5577
|
name,
|
|
5314
5578
|
{
|
|
@@ -5350,13 +5614,22 @@ export class AgentSession {
|
|
|
5350
5614
|
toolRegistry.set(tool.name, tool);
|
|
5351
5615
|
}
|
|
5352
5616
|
this._toolRegistry = toolRegistry;
|
|
5617
|
+
this._publishEvalOnlyToolHints();
|
|
5353
5618
|
const isDirectlyExposed = (name) => {
|
|
5354
5619
|
const entry = this._toolDefinitions.get(name);
|
|
5355
5620
|
return entry !== undefined && normalizeToolExposure(entry.definition).exposure === "direct";
|
|
5356
5621
|
};
|
|
5357
|
-
|
|
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) => {
|
|
5358
5629
|
if (!isAllowedTool(name))
|
|
5359
5630
|
return false;
|
|
5631
|
+
if (!hasExplicitActiveToolNames && temporarilyDisabledToolNames.has(name))
|
|
5632
|
+
return false;
|
|
5360
5633
|
const previousRegistrationIds = options?.previousActiveToolRegistrationIds;
|
|
5361
5634
|
if (!previousRegistrationIds)
|
|
5362
5635
|
return true;
|
|
@@ -5387,6 +5660,7 @@ export class AgentSession {
|
|
|
5387
5660
|
this.setActiveToolsByName([...new Set(nextActiveToolNames)]);
|
|
5388
5661
|
}
|
|
5389
5662
|
_buildRuntime(options) {
|
|
5663
|
+
this._delegatedCompactionKey = undefined;
|
|
5390
5664
|
const autoResizeImages = this.settingsManager.getImageAutoResize();
|
|
5391
5665
|
const shellCommandPrefix = this.settingsManager.getShellCommandPrefix();
|
|
5392
5666
|
const shellPath = this.settingsManager.getShellPath();
|
|
@@ -5438,7 +5712,9 @@ export class AgentSession {
|
|
|
5438
5712
|
const oldExtensionIdentities = oldExtensionRunner.getExtensionIdentities();
|
|
5439
5713
|
const previousFlagValues = oldExtensionRunner.getFlagValues();
|
|
5440
5714
|
const previousActiveToolRegistrationIds = new Map();
|
|
5441
|
-
|
|
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()) {
|
|
5442
5718
|
const entry = this._toolDefinitions.get(name);
|
|
5443
5719
|
if (entry)
|
|
5444
5720
|
previousActiveToolRegistrationIds.set(name, deriveExtensionRegistrationId(entry.sourceInfo, name));
|
|
@@ -5449,6 +5725,12 @@ export class AgentSession {
|
|
|
5449
5725
|
});
|
|
5450
5726
|
time("shutdown", "reload");
|
|
5451
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();
|
|
5452
5734
|
this.syncQueueModesFromSettings();
|
|
5453
5735
|
resetApiProviders();
|
|
5454
5736
|
time("settings", "reload");
|
|
@@ -5473,7 +5755,7 @@ export class AgentSession {
|
|
|
5473
5755
|
time("resources", "reload");
|
|
5474
5756
|
try {
|
|
5475
5757
|
this._buildRuntime({
|
|
5476
|
-
activeToolNames:
|
|
5758
|
+
activeToolNames: requestedActiveToolNamesBeforeRebuild,
|
|
5477
5759
|
flagValues: previousFlagValues,
|
|
5478
5760
|
includeAllExtensionTools: true,
|
|
5479
5761
|
previousActiveToolRegistrationIds,
|
|
@@ -6178,12 +6460,13 @@ export class AgentSession {
|
|
|
6178
6460
|
try {
|
|
6179
6461
|
const result = await executeBashWithOperations(resolvedCommand, this.sessionManager.getCwd(), options?.operations ?? createLocalBashOperations({ shellPath }), {
|
|
6180
6462
|
onChunk: (delta) => {
|
|
6181
|
-
onChunk?.(delta);
|
|
6463
|
+
const callbackResult = onChunk?.(delta);
|
|
6182
6464
|
this._emit({
|
|
6183
6465
|
type: "bash_execution_update",
|
|
6184
6466
|
id: options?.id,
|
|
6185
6467
|
delta,
|
|
6186
6468
|
});
|
|
6469
|
+
return callbackResult;
|
|
6187
6470
|
},
|
|
6188
6471
|
signal: abortController.signal,
|
|
6189
6472
|
});
|
|
@@ -6231,6 +6514,10 @@ export class AgentSession {
|
|
|
6231
6514
|
abortController.abort();
|
|
6232
6515
|
}
|
|
6233
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
|
+
}
|
|
6234
6521
|
/** Whether a bash command is currently running */
|
|
6235
6522
|
get isBashRunning() {
|
|
6236
6523
|
return this._bashAbortControllers.size > 0;
|
|
@@ -6432,6 +6719,7 @@ export class AgentSession {
|
|
|
6432
6719
|
}
|
|
6433
6720
|
// Update agent state (preserving exact messages still awaiting persistence)
|
|
6434
6721
|
this._restoreAgentMessagesFromSession();
|
|
6722
|
+
this._delegatedCompactionKey = undefined;
|
|
6435
6723
|
this._incrementMessageRevision();
|
|
6436
6724
|
// Emit session_tree event
|
|
6437
6725
|
await this._extensionRunner.emit({
|