@sayknow-cli/coding-agent 0.3.5 → 0.3.7
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/README.md +76 -0
- package/dist/types/capability/mcp.d.ts +2 -0
- package/dist/types/cli/local-provider-smoke.d.ts +59 -0
- package/dist/types/cli/notify-cli.d.ts +1 -1
- package/dist/types/cli/telegram-cli.d.ts +13 -0
- package/dist/types/cli/update-cli.d.ts +24 -1
- package/dist/types/commands/local-provider.d.ts +34 -0
- package/dist/types/commands/telegram.d.ts +22 -0
- package/dist/types/config/keybindings.d.ts +12 -6
- package/dist/types/config/model-registry.d.ts +5 -0
- package/dist/types/config/models-config-schema.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +284 -4
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/config/telegram-autostart.d.ts +14 -0
- package/dist/types/config/telegram-env-bridge.d.ts +18 -0
- package/dist/types/coordinator-mcp/server.d.ts +8 -0
- package/dist/types/defaults/skc-defaults.d.ts +7 -0
- package/dist/types/discovery/cursor.d.ts +4 -1
- package/dist/types/discovery/index.d.ts +0 -1
- package/dist/types/discovery/windsurf.d.ts +3 -1
- package/dist/types/extensibility/extensions/types.d.ts +11 -0
- package/dist/types/i18n/messages/en.d.ts +1 -0
- package/dist/types/modes/components/custom-editor.d.ts +6 -1
- package/dist/types/modes/components/custom-model-preset-wizard.d.ts +1 -1
- package/dist/types/modes/components/hook-selector.d.ts +5 -0
- package/dist/types/modes/components/model-selector.d.ts +2 -0
- package/dist/types/modes/components/settings-selector.d.ts +5 -1
- package/dist/types/modes/components/skill-message.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +5 -7
- package/dist/types/modes/components/status-line.d.ts +15 -3
- package/dist/types/modes/components/two-column-body.d.ts +9 -0
- package/dist/types/modes/components/welcome.d.ts +9 -1
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/input-controller.d.ts +6 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +10 -1
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/prompt-action-autocomplete.d.ts +3 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +1 -0
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +41 -7
- package/dist/types/modes/slash-command-visibility.d.ts +1 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/tmux-scroll.d.ts +18 -0
- package/dist/types/modes/types.d.ts +5 -1
- package/dist/types/modes/utils/terminal-bell.d.ts +3 -0
- package/dist/types/notifications/daemon-paths.d.ts +9 -0
- package/dist/types/notifications/html-format.d.ts +2 -0
- package/dist/types/notifications/recent-activity.d.ts +5 -1
- package/dist/types/notifications/telegram-daemon-cli.d.ts +21 -4
- package/dist/types/notifications/telegram-daemon-control.d.ts +8 -0
- package/dist/types/notifications/telegram-daemon.d.ts +4 -10
- package/dist/types/notifications/telegram-reference.d.ts +4 -0
- package/dist/types/notifications/threaded-render.d.ts +1 -0
- package/dist/types/runtime-mcp/config-writer.d.ts +9 -0
- package/dist/types/runtime-mcp/config.d.ts +2 -0
- package/dist/types/runtime-mcp/loader.d.ts +2 -0
- package/dist/types/runtime-mcp/manager.d.ts +2 -0
- package/dist/types/runtime-mcp/types.d.ts +6 -0
- package/dist/types/sdk.d.ts +1 -1
- package/dist/types/skc-runtime/launch-tmux.d.ts +2 -2
- package/dist/types/skc-runtime/ralplan-runtime.d.ts +3 -2
- package/dist/types/skc-runtime/restricted-role-agent-bash.d.ts +1 -0
- package/dist/types/skc-runtime/session-state-sidecar.d.ts +5 -0
- package/dist/types/skc-runtime/state-writer.d.ts +1 -0
- package/dist/types/skc-runtime/team-runtime.d.ts +1 -1
- package/dist/types/skc-runtime/tmux-common.d.ts +12 -0
- package/dist/types/skc-runtime/ultragoal-guard.d.ts +7 -1
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +0 -4
- package/dist/types/slash-commands/types.d.ts +2 -0
- package/dist/types/system-prompt.d.ts +7 -0
- package/dist/types/tools/skill.d.ts +1 -1
- package/dist/types/utils/cmux-workspace.d.ts +52 -0
- package/dist/types/web/search/index.d.ts +4 -2
- package/dist/types/web/search/provider.d.ts +14 -0
- package/dist/types/web/search/providers/utils.d.ts +42 -13
- package/dist/types/web/search/render.d.ts +1 -0
- package/dist/types/workflow/workflow-intent-diff.d.ts +19 -0
- package/package.json +8 -8
- package/scripts/build-binary.ts +3 -0
- package/scripts/verify-insane-vendor.ts +0 -5
- package/src/capability/mcp.ts +2 -0
- package/src/cli/local-provider-smoke.ts +632 -0
- package/src/cli/notify-cli.ts +4 -2
- package/src/cli/telegram-cli.ts +156 -0
- package/src/cli/update-cli.ts +179 -13
- package/src/cli/web-search-cli.ts +5 -7
- package/src/cli.ts +20 -1
- package/src/commands/launch.ts +47 -2
- package/src/commands/local-provider.ts +62 -0
- package/src/commands/notify.ts +1 -1
- package/src/commands/ralplan.ts +1 -0
- package/src/commands/telegram.ts +47 -0
- package/src/config/keybindings.ts +15 -6
- package/src/config/mcp-schema.json +4 -0
- package/src/config/model-registry.ts +55 -1
- package/src/config/models-config-schema.ts +9 -0
- package/src/config/settings-schema.ts +256 -4
- package/src/config/settings.ts +11 -0
- package/src/config/telegram-autostart.ts +107 -0
- package/src/config/telegram-env-bridge.ts +92 -0
- package/src/coordinator-mcp/server.ts +276 -34
- package/src/defaults/skc/skills/ralplan/SKILL.md +29 -20
- package/src/defaults/skc/skills/ultragoal/SKILL.md +37 -112
- package/src/defaults/skc-defaults.ts +16 -0
- package/src/discovery/builtin.ts +12 -0
- package/src/discovery/cursor.ts +5 -84
- package/src/discovery/gemini.ts +3 -89
- package/src/discovery/index.ts +4 -1
- package/src/discovery/mcp-json.ts +11 -0
- package/src/discovery/opencode.ts +5 -122
- package/src/discovery/windsurf.ts +4 -82
- package/src/edit/read-file.ts +9 -1
- package/src/extensibility/extensions/runner.ts +2 -1
- package/src/extensibility/extensions/types.ts +11 -0
- package/src/extensibility/skills.ts +30 -2
- package/src/hooks/skill-state.ts +33 -73
- package/src/i18n/messages/de.ts +1 -0
- package/src/i18n/messages/en.ts +1 -0
- package/src/i18n/messages/es.ts +1 -0
- package/src/i18n/messages/fr.ts +1 -0
- package/src/i18n/messages/ja.settings.ts +28 -0
- package/src/i18n/messages/ja.ts +1 -0
- package/src/i18n/messages/ko.settings.ts +28 -0
- package/src/i18n/messages/ko.ts +1 -0
- package/src/i18n/messages/zh.settings.ts +28 -0
- package/src/i18n/messages/zh.ts +1 -0
- package/src/internal-urls/docs-index.generated.ts +12 -7
- package/src/main.ts +5 -2
- package/src/migrate/executor.ts +23 -6
- package/src/modes/acp/acp-agent.ts +7 -6
- package/src/modes/components/agent-dashboard.ts +3 -35
- package/src/modes/components/custom-editor.ts +26 -0
- package/src/modes/components/custom-model-preset-wizard.ts +30 -189
- package/src/modes/components/extensions/extension-dashboard.ts +2 -48
- package/src/modes/components/hook-selector.ts +21 -3
- package/src/modes/components/model-selector.ts +246 -11
- package/src/modes/components/session-observer-overlay.ts +3 -3
- package/src/modes/components/session-selector.ts +19 -9
- package/src/modes/components/settings-selector.ts +551 -11
- package/src/modes/components/skill-message.ts +18 -10
- package/src/modes/components/status-line/presets.ts +11 -0
- package/src/modes/components/status-line/segments.ts +16 -19
- package/src/modes/components/status-line/types.ts +6 -2
- package/src/modes/components/status-line.ts +281 -117
- package/src/modes/components/tree-selector.ts +28 -8
- package/src/modes/components/two-column-body.ts +36 -0
- package/src/modes/components/welcome.ts +165 -30
- package/src/modes/controllers/command-controller.ts +26 -2
- package/src/modes/controllers/event-controller.ts +102 -4
- package/src/modes/controllers/extension-ui-controller.ts +21 -2
- package/src/modes/controllers/input-controller.ts +214 -47
- package/src/modes/controllers/selector-controller.ts +35 -33
- package/src/modes/interactive-mode.ts +36 -30
- package/src/modes/prompt-action-autocomplete.ts +48 -8
- package/src/modes/rpc/rpc-types.ts +1 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +72 -15
- package/src/modes/shared/agent-wire/workflow-gate-schema.ts +20 -0
- package/src/modes/slash-command-visibility.ts +9 -0
- package/src/modes/theme/theme.ts +5 -1
- package/src/modes/tmux-scroll.ts +92 -0
- package/src/modes/types.ts +8 -1
- package/src/modes/utils/hotkeys-markdown.ts +5 -2
- package/src/modes/utils/terminal-bell.ts +48 -0
- package/src/modes/utils/ui-helpers.ts +76 -3
- package/src/notifications/daemon-paths.ts +22 -0
- package/src/notifications/html-format.ts +75 -1
- package/src/notifications/index.ts +32 -2
- package/src/notifications/lifecycle-control-runtime.ts +12 -3
- package/src/notifications/recent-activity.ts +32 -8
- package/src/notifications/telegram-daemon-cli.ts +125 -13
- package/src/notifications/telegram-daemon-control.ts +9 -0
- package/src/notifications/telegram-daemon.ts +88 -46
- package/src/notifications/telegram-reference.ts +36 -14
- package/src/notifications/threaded-render.ts +14 -6
- package/src/prompts/agents/architect.md +7 -4
- package/src/prompts/agents/critic.md +15 -6
- package/src/prompts/agents/planner.md +8 -3
- package/src/prompts/system/system-prompt.md +20 -2
- package/src/prompts/tools/bash.md +1 -1
- package/src/runtime-mcp/config-writer.ts +28 -0
- package/src/runtime-mcp/config.ts +7 -0
- package/src/runtime-mcp/loader.ts +3 -0
- package/src/runtime-mcp/manager.ts +3 -0
- package/src/runtime-mcp/types.ts +6 -0
- package/src/sdk.ts +10 -11
- package/src/session/agent-session.ts +103 -13
- package/src/session/session-manager.ts +58 -18
- package/src/skc-runtime/launch-tmux.ts +34 -8
- package/src/skc-runtime/launch-worktree.ts +16 -2
- package/src/skc-runtime/ralplan-runtime.ts +22 -6
- package/src/skc-runtime/restricted-role-agent-bash.ts +1 -0
- package/src/skc-runtime/session-state-sidecar.ts +167 -24
- package/src/skc-runtime/state-runtime.ts +8 -5
- package/src/skc-runtime/state-writer.ts +5 -5
- package/src/skc-runtime/team-runtime.ts +21 -6
- package/src/skc-runtime/tmux-common.ts +16 -0
- package/src/skc-runtime/tmux-sessions.ts +13 -9
- package/src/skc-runtime/ultragoal-guard.ts +150 -24
- package/src/skc-runtime/ultragoal-runtime.ts +16 -124
- package/src/skc-runtime/workflow-manifest.generated.json +0 -8
- package/src/skc-runtime/workflow-manifest.ts +0 -1
- package/src/slash-commands/builtin-registry.ts +136 -23
- package/src/slash-commands/types.ts +2 -0
- package/src/system-prompt.ts +8 -0
- package/src/task/executor.ts +5 -0
- package/src/tools/ast-grep.ts +10 -7
- package/src/tools/bash.ts +39 -14
- package/src/tools/index.ts +8 -1
- package/src/tools/skill.ts +1 -1
- package/src/utils/cmux-workspace.ts +233 -0
- package/src/utils/title-generator.ts +2 -0
- package/src/web/kagi.ts +1 -1
- package/src/web/parallel.ts +2 -2
- package/src/web/search/index.ts +137 -52
- package/src/web/search/provider.ts +80 -0
- package/src/web/search/providers/anthropic.ts +1 -1
- package/src/web/search/providers/brave.ts +1 -1
- package/src/web/search/providers/codex.ts +1 -1
- package/src/web/search/providers/duckduckgo.ts +1 -1
- package/src/web/search/providers/exa.ts +1 -1
- package/src/web/search/providers/gemini.ts +7 -3
- package/src/web/search/providers/jina.ts +1 -1
- package/src/web/search/providers/kimi.ts +6 -1
- package/src/web/search/providers/openai-compatible.ts +1 -1
- package/src/web/search/providers/parallel.ts +1 -1
- package/src/web/search/providers/perplexity.ts +2 -2
- package/src/web/search/providers/searxng.ts +1 -1
- package/src/web/search/providers/synthetic.ts +1 -1
- package/src/web/search/providers/tavily.ts +1 -1
- package/src/web/search/providers/utils.ts +67 -17
- package/src/web/search/providers/xai.ts +1 -1
- package/src/web/search/providers/zai.ts +1 -1
- package/src/web/search/render.ts +3 -0
- package/src/workflow/workflow-intent-diff.ts +199 -0
- package/vendor/insane-search/MANIFEST.json +1 -3
- package/vendor/insane-search/engine/tests/test_u1.py +0 -28
- package/vendor/insane-search/engine/transport.py +2 -3
- package/vendor/insane-search/engine/validators.py +0 -14
- package/src/defaults/skc/rules/ponytail.md +0 -68
|
@@ -147,6 +147,8 @@ export interface MCPDiscoverOptions {
|
|
|
147
147
|
filterExa?: boolean;
|
|
148
148
|
/** Whether to filter out browser MCP servers when builtin browser tool is enabled (default: false) */
|
|
149
149
|
filterBrowser?: boolean;
|
|
150
|
+
/** Only connect servers with autoload !== false (default: false) */
|
|
151
|
+
autoloadOnly?: boolean;
|
|
150
152
|
/** Called when starting to connect to servers */
|
|
151
153
|
onConnecting?: (serverNames: string[]) => void;
|
|
152
154
|
}
|
|
@@ -325,6 +327,7 @@ export class MCPManager {
|
|
|
325
327
|
enableProjectConfig: options?.enableProjectConfig,
|
|
326
328
|
filterExa: options?.filterExa,
|
|
327
329
|
filterBrowser: options?.filterBrowser,
|
|
330
|
+
autoloadOnly: options?.autoloadOnly,
|
|
328
331
|
});
|
|
329
332
|
const result = await this.connectServers(configs, sources, options?.onConnecting);
|
|
330
333
|
result.exaApiKeys = exaApiKeys;
|
package/src/runtime-mcp/types.ts
CHANGED
|
@@ -61,6 +61,12 @@ export interface MCPAuthConfig {
|
|
|
61
61
|
interface MCPServerConfigBase {
|
|
62
62
|
/** Whether this server is enabled (default: true) */
|
|
63
63
|
enabled?: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Whether to connect this server automatically at session startup (default: true).
|
|
66
|
+
* When false the server stays configured-but-disconnected until connected
|
|
67
|
+
* explicitly (e.g. `/mcp connect <name>`).
|
|
68
|
+
*/
|
|
69
|
+
autoload?: boolean;
|
|
64
70
|
/** Connection timeout in milliseconds (default: 30000) */
|
|
65
71
|
timeout?: number;
|
|
66
72
|
/** Authentication configuration (optional) */
|
package/src/sdk.ts
CHANGED
|
@@ -121,6 +121,7 @@ import { AgentOutputManager } from "./task/output-manager";
|
|
|
121
121
|
import { parseThinkingLevel, resolveThinkingLevelForModel, toReasoningEffort } from "./thinking";
|
|
122
122
|
import { collectDiscoverableTools, type DiscoverableTool } from "./tool-discovery/tool-index";
|
|
123
123
|
import {
|
|
124
|
+
applyConfiguredSearchTimeout,
|
|
124
125
|
BashTool,
|
|
125
126
|
BUILTIN_TOOLS,
|
|
126
127
|
computeEssentialBuiltinNames,
|
|
@@ -129,10 +130,10 @@ import {
|
|
|
129
130
|
EditTool,
|
|
130
131
|
EvalTool,
|
|
131
132
|
FindTool,
|
|
133
|
+
getConfiguredSearchProviderPreference,
|
|
132
134
|
getSearchTools,
|
|
133
135
|
HIDDEN_TOOLS,
|
|
134
136
|
isConfigurableSearchProviderId,
|
|
135
|
-
isSearchProviderPreference,
|
|
136
137
|
type LspStartupServerInfo,
|
|
137
138
|
loadSshTool,
|
|
138
139
|
ReadTool,
|
|
@@ -142,7 +143,6 @@ import {
|
|
|
142
143
|
setPreferredImageProvider,
|
|
143
144
|
setPreferredSearchProvider,
|
|
144
145
|
setSearchFallbackProviders,
|
|
145
|
-
setSearchHardTimeoutMs,
|
|
146
146
|
type Tool,
|
|
147
147
|
type ToolSession,
|
|
148
148
|
WebSearchTool,
|
|
@@ -257,7 +257,7 @@ export interface CreateAgentSessionOptions {
|
|
|
257
257
|
thinkingLevel?: ThinkingLevel;
|
|
258
258
|
/** Runtime substitution metadata for the initial model_change session event. */
|
|
259
259
|
modelSubstitution?: { requestedModel: Model; reason: string };
|
|
260
|
-
/** Models available for cycling (
|
|
260
|
+
/** Models available for cycling (Alt+N in interactive mode) */
|
|
261
261
|
scopedModels?: ScopedModelSelection[];
|
|
262
262
|
|
|
263
263
|
/** System prompt blocks. Array replaces default, function receives default blocks and returns final blocks. */
|
|
@@ -934,20 +934,15 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
934
934
|
slashCommandsPromise.catch(() => {});
|
|
935
935
|
|
|
936
936
|
// Initialize provider preferences from settings
|
|
937
|
-
const webSearchProvider = settings
|
|
938
|
-
|
|
939
|
-
setPreferredSearchProvider(webSearchProvider);
|
|
940
|
-
}
|
|
937
|
+
const webSearchProvider = getConfiguredSearchProviderPreference(settings);
|
|
938
|
+
setPreferredSearchProvider(webSearchProvider);
|
|
941
939
|
const webSearchFallback = settings.get("web_search.fallback");
|
|
942
940
|
if (Array.isArray(webSearchFallback)) {
|
|
943
941
|
setSearchFallbackProviders(
|
|
944
942
|
webSearchFallback.filter(value => typeof value === "string" && isConfigurableSearchProviderId(value)),
|
|
945
943
|
);
|
|
946
944
|
}
|
|
947
|
-
|
|
948
|
-
if (typeof webSearchTimeout === "number" && Number.isFinite(webSearchTimeout) && webSearchTimeout > 0) {
|
|
949
|
-
setSearchHardTimeoutMs(webSearchTimeout * 1000);
|
|
950
|
-
}
|
|
945
|
+
applyConfiguredSearchTimeout(settings);
|
|
951
946
|
|
|
952
947
|
const imageProvider = settings.get("providers.image");
|
|
953
948
|
if (
|
|
@@ -1844,6 +1839,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1844
1839
|
intentField,
|
|
1845
1840
|
mcpDiscoveryMode: false,
|
|
1846
1841
|
mcpDiscoveryServerSummaries: [],
|
|
1842
|
+
toolDiscoveryActive: effectiveDiscoveryMode === "all",
|
|
1843
|
+
discoverableTools: discoverableBuiltinTools
|
|
1844
|
+
.map(tool => ({ name: tool.name, summary: tool.summary }))
|
|
1845
|
+
.sort((a, b) => a.name.localeCompare(b.name)),
|
|
1847
1846
|
eagerTasks,
|
|
1848
1847
|
secretsEnabled,
|
|
1849
1848
|
workspaceTree: workspaceTreePromise,
|
|
@@ -260,6 +260,7 @@ import { type EditMode, resolveEditMode } from "../utils/edit-mode";
|
|
|
260
260
|
import { resolveFileDisplayMode } from "../utils/file-display-mode";
|
|
261
261
|
import { extractFileMentions, generateFileMentionMessages } from "../utils/file-mentions";
|
|
262
262
|
import { buildNamedToolChoice, buildNamedToolChoiceResult } from "../utils/tool-choice";
|
|
263
|
+
import { buildWorkflowIntentDiff, WORKFLOW_INTENT_DIFF_CUSTOM_TYPE } from "../workflow/workflow-intent-diff";
|
|
263
264
|
import type { AuthStorage } from "./auth-storage";
|
|
264
265
|
import type { ClientBridge, ClientBridgePermissionOption, ClientBridgePermissionOutcome } from "./client-bridge";
|
|
265
266
|
import {
|
|
@@ -526,6 +527,15 @@ export interface SessionStats {
|
|
|
526
527
|
type RetryFallbackChains = Record<string, string[]>;
|
|
527
528
|
|
|
528
529
|
type RetryFallbackRevertPolicy = "never" | "cooldown-expiry";
|
|
530
|
+
type RetryErrorClassification =
|
|
531
|
+
| "none"
|
|
532
|
+
| "overflow"
|
|
533
|
+
| "terminal"
|
|
534
|
+
| "usage_limit"
|
|
535
|
+
| "first_event_timeout"
|
|
536
|
+
| "transient"
|
|
537
|
+
| "local_unavailable"
|
|
538
|
+
| "unknown";
|
|
529
539
|
|
|
530
540
|
interface RetryFallbackSelector {
|
|
531
541
|
raw: string;
|
|
@@ -563,6 +573,37 @@ function formatRetryFallbackBaseSelector(selector: RetryFallbackSelector): strin
|
|
|
563
573
|
return `${selector.provider}/${selector.id}`;
|
|
564
574
|
}
|
|
565
575
|
|
|
576
|
+
function isLocalModelEndpoint(model: Model | undefined): boolean {
|
|
577
|
+
if (!model) return false;
|
|
578
|
+
if (model.provider === "ollama" || model.provider === "lm-studio" || model.provider === "llama.cpp") {
|
|
579
|
+
return true;
|
|
580
|
+
}
|
|
581
|
+
try {
|
|
582
|
+
const hostname = new URL(model.baseUrl).hostname.toLowerCase();
|
|
583
|
+
if (
|
|
584
|
+
hostname === "localhost" ||
|
|
585
|
+
hostname === "0.0.0.0" ||
|
|
586
|
+
hostname === "::1" ||
|
|
587
|
+
hostname === "[::1]" ||
|
|
588
|
+
hostname.endsWith(".local")
|
|
589
|
+
) {
|
|
590
|
+
return true;
|
|
591
|
+
}
|
|
592
|
+
if (/^127\./.test(hostname) || /^10\./.test(hostname) || /^192\.168\./.test(hostname)) {
|
|
593
|
+
return true;
|
|
594
|
+
}
|
|
595
|
+
const private172 = /^172\.(\d{1,2})\./.exec(hostname);
|
|
596
|
+
if (private172) {
|
|
597
|
+
const secondOctet = Number(private172[1]);
|
|
598
|
+
return secondOctet >= 16 && secondOctet <= 31;
|
|
599
|
+
}
|
|
600
|
+
} catch {
|
|
601
|
+
// A malformed base URL is configuration, not availability. Keep it visible.
|
|
602
|
+
return false;
|
|
603
|
+
}
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
|
|
566
607
|
const IRC_REPLY_MAX_BYTES = 4096;
|
|
567
608
|
|
|
568
609
|
/**
|
|
@@ -862,7 +903,7 @@ function extractPermissionLocations(
|
|
|
862
903
|
* custom messages queued during streaming) and is matched by the custom-role
|
|
863
904
|
* `message_start` dequeue branch; user-message pushes leave it undefined and
|
|
864
905
|
* rely on the existing text-equality match. */
|
|
865
|
-
type QueuedDisplayEntry = { text: string; tag?: string };
|
|
906
|
+
type QueuedDisplayEntry = { text: string; tag?: string; sequence: number };
|
|
866
907
|
|
|
867
908
|
/** A custom message contributed at the before-agent-start point. */
|
|
868
909
|
export type BeforeAgentStartInternalMessage = Pick<
|
|
@@ -916,6 +957,7 @@ export class AgentSession {
|
|
|
916
957
|
/** Tracks pending follow-up messages for UI display. Removed when delivered.
|
|
917
958
|
* See `#steeringMessages` for entry shape. */
|
|
918
959
|
#followUpMessages: QueuedDisplayEntry[] = [];
|
|
960
|
+
#queuedDisplaySequence = 0;
|
|
919
961
|
/** Messages queued to be included with the next user prompt as context ("asides"). */
|
|
920
962
|
#pendingNextTurnMessages: CustomMessage[] = [];
|
|
921
963
|
#scheduledHiddenNextTurnGeneration: number | undefined = undefined;
|
|
@@ -1585,6 +1627,14 @@ export class AgentSession {
|
|
|
1585
1627
|
this.#planCompactAbortPending = false;
|
|
1586
1628
|
}
|
|
1587
1629
|
|
|
1630
|
+
#createQueuedDisplayEntry(text: string, tag?: string): QueuedDisplayEntry {
|
|
1631
|
+
const entry: QueuedDisplayEntry = { text, sequence: ++this.#queuedDisplaySequence };
|
|
1632
|
+
if (tag !== undefined) {
|
|
1633
|
+
entry.tag = tag;
|
|
1634
|
+
}
|
|
1635
|
+
return entry;
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1588
1638
|
/** Register a compact display string for a custom message that the caller is
|
|
1589
1639
|
* about to dispatch via `promptCustomMessage` / `sendCustomMessage`.
|
|
1590
1640
|
* Returns a stable tag the caller MUST embed in
|
|
@@ -1598,7 +1648,7 @@ export class AgentSession {
|
|
|
1598
1648
|
const tag = `skc-cmd-${Date.now()}-${++this.#customDisplayTagCounter}`;
|
|
1599
1649
|
const displayText = text.trim();
|
|
1600
1650
|
if (!displayText) return tag;
|
|
1601
|
-
const entry
|
|
1651
|
+
const entry = this.#createQueuedDisplayEntry(displayText, tag);
|
|
1602
1652
|
if (mode === "steer") {
|
|
1603
1653
|
this.#steeringMessages.push(entry);
|
|
1604
1654
|
} else {
|
|
@@ -4769,6 +4819,7 @@ export class AgentSession {
|
|
|
4769
4819
|
|
|
4770
4820
|
// Expand file-based prompt templates if requested
|
|
4771
4821
|
const expandedText = expandPromptTemplates ? expandPromptTemplate(text, [...this.#promptTemplates]) : text;
|
|
4822
|
+
const workflowIntentDiff = options?.synthetic ? null : buildWorkflowIntentDiff(expandedText);
|
|
4772
4823
|
|
|
4773
4824
|
// If streaming, queue via steer() or followUp() based on option
|
|
4774
4825
|
if (this.isStreaming) {
|
|
@@ -4780,9 +4831,16 @@ export class AgentSession {
|
|
|
4780
4831
|
} else {
|
|
4781
4832
|
await this.#queueSteer(expandedText, options?.images);
|
|
4782
4833
|
}
|
|
4834
|
+
if (workflowIntentDiff) {
|
|
4835
|
+
this.sessionManager.appendCustomEntry(WORKFLOW_INTENT_DIFF_CUSTOM_TYPE, workflowIntentDiff);
|
|
4836
|
+
}
|
|
4783
4837
|
return;
|
|
4784
4838
|
}
|
|
4785
4839
|
|
|
4840
|
+
if (workflowIntentDiff) {
|
|
4841
|
+
this.sessionManager.appendCustomEntry(WORKFLOW_INTENT_DIFF_CUSTOM_TYPE, workflowIntentDiff);
|
|
4842
|
+
}
|
|
4843
|
+
|
|
4786
4844
|
// Skip eager todo prelude when the user has already queued a directive
|
|
4787
4845
|
const hasPendingUserDirective = this.#toolChoiceQueue.inspect().includes("user-force");
|
|
4788
4846
|
const eagerTodoPrelude =
|
|
@@ -5236,7 +5294,7 @@ export class AgentSession {
|
|
|
5236
5294
|
*/
|
|
5237
5295
|
async #queueSteer(text: string, images?: ImageContent[]): Promise<void> {
|
|
5238
5296
|
const displayText = text || (images && images.length > 0 ? "[Image]" : "");
|
|
5239
|
-
this.#steeringMessages.push(
|
|
5297
|
+
this.#steeringMessages.push(this.#createQueuedDisplayEntry(displayText));
|
|
5240
5298
|
const content: (TextContent | ImageContent)[] = [{ type: "text", text }];
|
|
5241
5299
|
if (images && images.length > 0) {
|
|
5242
5300
|
content.push(...images);
|
|
@@ -5267,7 +5325,7 @@ export class AgentSession {
|
|
|
5267
5325
|
*/
|
|
5268
5326
|
async #queueFollowUp(text: string, images?: ImageContent[]): Promise<void> {
|
|
5269
5327
|
const displayText = text || (images && images.length > 0 ? "[Image]" : "");
|
|
5270
|
-
this.#followUpMessages.push(
|
|
5328
|
+
this.#followUpMessages.push(this.#createQueuedDisplayEntry(displayText));
|
|
5271
5329
|
const content: (TextContent | ImageContent)[] = [{ type: "text", text }];
|
|
5272
5330
|
if (images && images.length > 0) {
|
|
5273
5331
|
content.push(...images);
|
|
@@ -5643,18 +5701,21 @@ export class AgentSession {
|
|
|
5643
5701
|
* record — no orphan state can outlive the queue entry.
|
|
5644
5702
|
*/
|
|
5645
5703
|
popLastQueuedMessage(): string | undefined {
|
|
5646
|
-
|
|
5647
|
-
|
|
5704
|
+
const steeringEntry = this.#steeringMessages.at(-1);
|
|
5705
|
+
const followUpEntry = this.#followUpMessages.at(-1);
|
|
5706
|
+
|
|
5707
|
+
if (steeringEntry && (!followUpEntry || steeringEntry.sequence > followUpEntry.sequence)) {
|
|
5648
5708
|
const entry = this.#steeringMessages.pop();
|
|
5649
5709
|
this.agent.popLastSteer();
|
|
5650
5710
|
return entry?.text;
|
|
5651
5711
|
}
|
|
5652
|
-
|
|
5653
|
-
if (
|
|
5712
|
+
|
|
5713
|
+
if (followUpEntry) {
|
|
5654
5714
|
const entry = this.#followUpMessages.pop();
|
|
5655
5715
|
this.agent.popLastFollowUp();
|
|
5656
5716
|
return entry?.text;
|
|
5657
5717
|
}
|
|
5718
|
+
|
|
5658
5719
|
return undefined;
|
|
5659
5720
|
}
|
|
5660
5721
|
|
|
@@ -8221,6 +8282,12 @@ export class AgentSession {
|
|
|
8221
8282
|
*/
|
|
8222
8283
|
#isRetryableError(message: AssistantMessage): boolean {
|
|
8223
8284
|
const classification = this.#classifyErrorForRetry(message);
|
|
8285
|
+
if (classification === "local_unavailable") {
|
|
8286
|
+
return this.#hasRetryFallbackCandidate({
|
|
8287
|
+
currentSelector: this.model ? formatRetryFallbackSelector(this.model, this.thinkingLevel) : undefined,
|
|
8288
|
+
requireNonLocal: true,
|
|
8289
|
+
});
|
|
8290
|
+
}
|
|
8224
8291
|
return (
|
|
8225
8292
|
classification === "usage_limit" ||
|
|
8226
8293
|
classification === "transient" ||
|
|
@@ -8260,6 +8327,12 @@ export class AgentSession {
|
|
|
8260
8327
|
return /timed?\s*out while waiting for the first event|timeout waiting for first/i.test(errorMessage);
|
|
8261
8328
|
}
|
|
8262
8329
|
|
|
8330
|
+
#isLocalProviderAvailabilityErrorMessage(errorMessage: string): boolean {
|
|
8331
|
+
return /connection.?refused|econnrefused|timed?\s*out|timeout|fetch failed|network.?error|socket hang up|terminated|service.?unavailable|server.?error|internal.?error|503|model_not_found|model not found|no such model|unknown model|model .*not.*(found|available|loaded)|not ready|not.?ready|warming|loading|currently loading|try again|out of memory|\boom\b|memory guard|insufficient memory|not enough memory|failed to allocate|kv.?cache|malformed (stream|streaming|sse)|invalid (stream|streaming|sse)|stream envelope error|unexpected end of (json|input)|unterminated json|no error details in response/i.test(
|
|
8332
|
+
errorMessage,
|
|
8333
|
+
);
|
|
8334
|
+
}
|
|
8335
|
+
|
|
8263
8336
|
/**
|
|
8264
8337
|
* Whether a first-event timeout on the error's provider should fail closed —
|
|
8265
8338
|
* i.e. retry a bounded number of times (capped at retry.maxRetries) and then
|
|
@@ -8303,13 +8376,14 @@ export class AgentSession {
|
|
|
8303
8376
|
* -> usage_limit (rotation) -> first_event_timeout (bounded retry) ->
|
|
8304
8377
|
* transient (retry) -> unknown (retry).
|
|
8305
8378
|
*/
|
|
8306
|
-
#classifyErrorForRetry(
|
|
8307
|
-
message: AssistantMessage,
|
|
8308
|
-
): "none" | "overflow" | "terminal" | "usage_limit" | "first_event_timeout" | "transient" | "unknown" {
|
|
8379
|
+
#classifyErrorForRetry(message: AssistantMessage): RetryErrorClassification {
|
|
8309
8380
|
if (message.stopReason !== "error" || !message.errorMessage) return "none";
|
|
8310
8381
|
const contextWindow = this.model?.contextWindow ?? 0;
|
|
8311
8382
|
if (isContextOverflow(message, contextWindow)) return "overflow";
|
|
8312
8383
|
const err = message.errorMessage;
|
|
8384
|
+
if (isLocalModelEndpoint(this.model) && this.#isLocalProviderAvailabilityErrorMessage(err)) {
|
|
8385
|
+
return "local_unavailable";
|
|
8386
|
+
}
|
|
8313
8387
|
// Stream-envelope errors are only transient in the pre-message_start
|
|
8314
8388
|
// variant; any other envelope failure is structural and must surface.
|
|
8315
8389
|
if (/anthropic stream envelope error:/i.test(err)) {
|
|
@@ -8444,6 +8518,20 @@ export class AgentSession {
|
|
|
8444
8518
|
return chain;
|
|
8445
8519
|
}
|
|
8446
8520
|
|
|
8521
|
+
#hasRetryFallbackCandidate(options: { currentSelector: string | undefined; requireNonLocal?: boolean }): boolean {
|
|
8522
|
+
if (!options.currentSelector) return false;
|
|
8523
|
+
const role = this.#activeRetryFallback?.role ?? this.#resolveRetryFallbackRole(options.currentSelector);
|
|
8524
|
+
if (!role) return false;
|
|
8525
|
+
for (const selector of this.#findRetryFallbackCandidates(role, options.currentSelector)) {
|
|
8526
|
+
if (this.#isRetryFallbackSelectorSuppressed(selector)) continue;
|
|
8527
|
+
const candidate = this.#modelRegistry.find(selector.provider, selector.id);
|
|
8528
|
+
if (!candidate) continue;
|
|
8529
|
+
if (options.requireNonLocal && isLocalModelEndpoint(candidate)) continue;
|
|
8530
|
+
return true;
|
|
8531
|
+
}
|
|
8532
|
+
return false;
|
|
8533
|
+
}
|
|
8534
|
+
|
|
8447
8535
|
#findRetryFallbackCandidates(role: string, currentSelector: string): RetryFallbackSelector[] {
|
|
8448
8536
|
const chain = this.#getRetryFallbackEffectiveChain(role);
|
|
8449
8537
|
if (chain.length <= 1) return [];
|
|
@@ -8497,7 +8585,7 @@ export class AgentSession {
|
|
|
8497
8585
|
});
|
|
8498
8586
|
}
|
|
8499
8587
|
|
|
8500
|
-
async #tryRetryModelFallback(currentSelector: string): Promise<boolean> {
|
|
8588
|
+
async #tryRetryModelFallback(currentSelector: string, options?: { requireNonLocal?: boolean }): Promise<boolean> {
|
|
8501
8589
|
const role = this.#activeRetryFallback?.role ?? this.#resolveRetryFallbackRole(currentSelector);
|
|
8502
8590
|
if (!role) return false;
|
|
8503
8591
|
|
|
@@ -8505,6 +8593,7 @@ export class AgentSession {
|
|
|
8505
8593
|
if (this.#isRetryFallbackSelectorSuppressed(selector)) continue;
|
|
8506
8594
|
const candidate = this.#modelRegistry.find(selector.provider, selector.id);
|
|
8507
8595
|
if (!candidate) continue;
|
|
8596
|
+
if (options?.requireNonLocal && isLocalModelEndpoint(candidate)) continue;
|
|
8508
8597
|
const apiKey = await this.#modelRegistry.getApiKey(candidate, this.sessionId);
|
|
8509
8598
|
if (!apiKey) continue;
|
|
8510
8599
|
await this.#applyRetryFallbackCandidate(role, selector, currentSelector);
|
|
@@ -8615,6 +8704,7 @@ export class AgentSession {
|
|
|
8615
8704
|
if (!retrySettings.enabled) return false;
|
|
8616
8705
|
const retryClassification = this.#classifyErrorForRetry(message);
|
|
8617
8706
|
const unboundedClass = retryClassification === "transient" || retryClassification === "unknown";
|
|
8707
|
+
const localUnavailable = retryClassification === "local_unavailable";
|
|
8618
8708
|
|
|
8619
8709
|
const generation = this.#promptGeneration;
|
|
8620
8710
|
this.#retryAttempt++;
|
|
@@ -8668,7 +8758,7 @@ export class AgentSession {
|
|
|
8668
8758
|
const currentSelector = this.model ? formatRetryFallbackSelector(this.model, this.thinkingLevel) : undefined;
|
|
8669
8759
|
if (!switchedCredential && currentSelector) {
|
|
8670
8760
|
this.#noteRetryFallbackCooldown(currentSelector, parsedRetryAfterMs, errorMessage);
|
|
8671
|
-
switchedModel = await this.#tryRetryModelFallback(currentSelector);
|
|
8761
|
+
switchedModel = await this.#tryRetryModelFallback(currentSelector, { requireNonLocal: localUnavailable });
|
|
8672
8762
|
if (switchedModel) {
|
|
8673
8763
|
delayMs = 0;
|
|
8674
8764
|
} else if (parsedRetryAfterMs && parsedRetryAfterMs > delayMs) {
|
|
@@ -1461,9 +1461,25 @@ function materializeResidentEntrySync<T extends FileEntry | SessionEntry>(
|
|
|
1461
1461
|
function materializeResidentEntriesSync<T extends FileEntry | SessionEntry>(
|
|
1462
1462
|
entries: T[],
|
|
1463
1463
|
stores: ResidentBlobStores,
|
|
1464
|
+
missingPolicy: ResidentBlobMissingPolicy = "throw",
|
|
1464
1465
|
): T[] {
|
|
1465
1466
|
const cache = new Map<string, string>();
|
|
1466
|
-
return entries.map(entry => materializeResidentEntrySync(entry, stores, cache));
|
|
1467
|
+
return entries.map(entry => materializeResidentEntrySync(entry, stores, cache, missingPolicy));
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
function materializeResidentEntryForReadSync<T extends FileEntry | SessionEntry>(
|
|
1471
|
+
entry: T,
|
|
1472
|
+
stores: ResidentBlobStores,
|
|
1473
|
+
cache: Map<string, string>,
|
|
1474
|
+
): T {
|
|
1475
|
+
return materializeResidentEntrySync(entry, stores, cache, "placeholder");
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
function materializeResidentEntriesForReadSync<T extends FileEntry | SessionEntry>(
|
|
1479
|
+
entries: T[],
|
|
1480
|
+
stores: ResidentBlobStores,
|
|
1481
|
+
): T[] {
|
|
1482
|
+
return materializeResidentEntriesSync(entries, stores, "placeholder");
|
|
1467
1483
|
}
|
|
1468
1484
|
|
|
1469
1485
|
function materializeResidentEntryForPersistenceSync<T extends FileEntry | SessionEntry>(
|
|
@@ -1520,12 +1536,24 @@ function cloneSessionEntry(entry: SessionEntry): SessionEntry {
|
|
|
1520
1536
|
function materializeProviderVisibleEntrySync(entry: SessionEntry, stores: ResidentBlobStores): SessionEntry {
|
|
1521
1537
|
if (entry.type === "compaction") {
|
|
1522
1538
|
const cache = new Map<string, string>();
|
|
1523
|
-
const summary = materializeResidentValueSync(entry.summary, stores, "summary", cache);
|
|
1524
|
-
const shortSummary = materializeResidentValueSync(
|
|
1539
|
+
const summary = materializeResidentValueSync(entry.summary, stores, "summary", cache, "placeholder");
|
|
1540
|
+
const shortSummary = materializeResidentValueSync(
|
|
1541
|
+
entry.shortSummary,
|
|
1542
|
+
stores,
|
|
1543
|
+
"shortSummary",
|
|
1544
|
+
cache,
|
|
1545
|
+
"placeholder",
|
|
1546
|
+
);
|
|
1525
1547
|
const remote = entry.preserveData?.openaiRemoteCompaction;
|
|
1526
1548
|
const remoteRecord = isRecord(remote) ? remote : undefined;
|
|
1527
1549
|
const replacementHistory = remoteRecord
|
|
1528
|
-
? materializeResidentValueSync(
|
|
1550
|
+
? materializeResidentValueSync(
|
|
1551
|
+
remoteRecord.replacementHistory,
|
|
1552
|
+
stores,
|
|
1553
|
+
"replacementHistory",
|
|
1554
|
+
cache,
|
|
1555
|
+
"placeholder",
|
|
1556
|
+
)
|
|
1529
1557
|
: undefined;
|
|
1530
1558
|
const preserveData =
|
|
1531
1559
|
remoteRecord && replacementHistory !== undefined && replacementHistory !== remoteRecord.replacementHistory
|
|
@@ -1545,7 +1573,13 @@ function materializeProviderVisibleEntrySync(entry: SessionEntry, stores: Reside
|
|
|
1545
1573
|
};
|
|
1546
1574
|
}
|
|
1547
1575
|
if (entry.type === "branch_summary") {
|
|
1548
|
-
const summary = materializeResidentValueSync(
|
|
1576
|
+
const summary = materializeResidentValueSync(
|
|
1577
|
+
entry.summary,
|
|
1578
|
+
stores,
|
|
1579
|
+
"summary",
|
|
1580
|
+
new Map<string, string>(),
|
|
1581
|
+
"placeholder",
|
|
1582
|
+
);
|
|
1549
1583
|
return typeof summary === "string" ? { ...entry, summary } : { ...entry };
|
|
1550
1584
|
}
|
|
1551
1585
|
return cloneSessionEntry(entry);
|
|
@@ -2751,7 +2785,10 @@ export class SessionManager {
|
|
|
2751
2785
|
}
|
|
2752
2786
|
|
|
2753
2787
|
captureState(): SessionManagerStateSnapshot {
|
|
2754
|
-
const materializedFileEntries =
|
|
2788
|
+
const materializedFileEntries = materializeResidentEntriesForReadSync(
|
|
2789
|
+
this.#fileEntries,
|
|
2790
|
+
this.#residentBlobStores(),
|
|
2791
|
+
);
|
|
2755
2792
|
return {
|
|
2756
2793
|
sessionId: this.#sessionId,
|
|
2757
2794
|
sessionName: this.#sessionName,
|
|
@@ -2875,7 +2912,7 @@ export class SessionManager {
|
|
|
2875
2912
|
|
|
2876
2913
|
const oldSessionFile = this.#sessionFile;
|
|
2877
2914
|
const oldSessionId = this.#sessionId;
|
|
2878
|
-
const materializedEntries =
|
|
2915
|
+
const materializedEntries = materializeResidentEntriesForReadSync(this.#fileEntries, this.#residentBlobStores());
|
|
2879
2916
|
|
|
2880
2917
|
// Close the current writer
|
|
2881
2918
|
await this.#closePersistWriter();
|
|
@@ -2947,7 +2984,10 @@ export class SessionManager {
|
|
|
2947
2984
|
hadSessionFile = this.storage.existsSync(oldSessionFile);
|
|
2948
2985
|
let movedSessionFile = false;
|
|
2949
2986
|
let movedArtifactDir = false;
|
|
2950
|
-
const materializedEntries =
|
|
2987
|
+
const materializedEntries = materializeResidentEntriesForReadSync(
|
|
2988
|
+
this.#fileEntries,
|
|
2989
|
+
this.#residentBlobStores(),
|
|
2990
|
+
);
|
|
2951
2991
|
const restoreResidentStateAfterFailure = (): void => {
|
|
2952
2992
|
this.#fileEntries = materializedEntries;
|
|
2953
2993
|
this.#resetResidentTextBlobStore();
|
|
@@ -4006,7 +4046,7 @@ export class SessionManager {
|
|
|
4006
4046
|
getLeafEntry(): SessionEntry | undefined {
|
|
4007
4047
|
if (!this.#leafId) return undefined;
|
|
4008
4048
|
const entry = this.#byId.get(this.#leafId);
|
|
4009
|
-
return entry ?
|
|
4049
|
+
return entry ? materializeResidentEntryForReadSync(entry, this.#residentBlobStores(), new Map()) : undefined;
|
|
4010
4050
|
}
|
|
4011
4051
|
|
|
4012
4052
|
/**
|
|
@@ -4176,7 +4216,7 @@ export class SessionManager {
|
|
|
4176
4216
|
const entry = this.#byId.get(id);
|
|
4177
4217
|
return entry
|
|
4178
4218
|
? rehydrateColdSpillEntry(
|
|
4179
|
-
|
|
4219
|
+
materializeResidentEntryForReadSync(entry, this.#residentBlobStores(), new Map()),
|
|
4180
4220
|
this.#blobStore,
|
|
4181
4221
|
this.#residentBlobStoresForColdRehydrate(),
|
|
4182
4222
|
)
|
|
@@ -4193,7 +4233,7 @@ export class SessionManager {
|
|
|
4193
4233
|
visited.add(current.id);
|
|
4194
4234
|
path.push(
|
|
4195
4235
|
rehydrateColdSpillEntry(
|
|
4196
|
-
|
|
4236
|
+
materializeResidentEntryForReadSync(current, this.#residentBlobStores(), cache),
|
|
4197
4237
|
this.#blobStore,
|
|
4198
4238
|
this.#residentBlobStoresForColdRehydrate(),
|
|
4199
4239
|
),
|
|
@@ -4233,7 +4273,7 @@ export class SessionManager {
|
|
|
4233
4273
|
.filter((entry): entry is SessionEntry => entry.type !== "session")
|
|
4234
4274
|
.map(entry =>
|
|
4235
4275
|
rehydrateColdSpillEntry(
|
|
4236
|
-
|
|
4276
|
+
materializeResidentEntryForReadSync(entry, this.#residentBlobStores(), cache),
|
|
4237
4277
|
this.#blobStore,
|
|
4238
4278
|
this.#residentBlobStoresForColdRehydrate(),
|
|
4239
4279
|
),
|
|
@@ -4244,7 +4284,7 @@ export class SessionManager {
|
|
|
4244
4284
|
this.#publicMaterializerCallCount++;
|
|
4245
4285
|
this.#getEntryMaterializerCallCount++;
|
|
4246
4286
|
const entry = this.#byId.get(id);
|
|
4247
|
-
return entry ?
|
|
4287
|
+
return entry ? materializeResidentEntryForReadSync(entry, this.#residentBlobStores(), new Map()) : undefined;
|
|
4248
4288
|
}
|
|
4249
4289
|
|
|
4250
4290
|
/**
|
|
@@ -4255,7 +4295,7 @@ export class SessionManager {
|
|
|
4255
4295
|
const children: SessionEntry[] = [];
|
|
4256
4296
|
for (const entry of this.#byId.values()) {
|
|
4257
4297
|
if (entry.parentId === parentId) {
|
|
4258
|
-
children.push(
|
|
4298
|
+
children.push(materializeResidentEntryForReadSync(entry, this.#residentBlobStores(), cache));
|
|
4259
4299
|
}
|
|
4260
4300
|
}
|
|
4261
4301
|
return children;
|
|
@@ -4310,7 +4350,7 @@ export class SessionManager {
|
|
|
4310
4350
|
while (current) {
|
|
4311
4351
|
if (visited.has(current.id)) break;
|
|
4312
4352
|
visited.add(current.id);
|
|
4313
|
-
path.push(
|
|
4353
|
+
path.push(materializeResidentEntryForReadSync(current, this.#residentBlobStores(), cache));
|
|
4314
4354
|
current = current.parentId ? this.#byId.get(current.parentId) : undefined;
|
|
4315
4355
|
}
|
|
4316
4356
|
path.reverse();
|
|
@@ -4375,7 +4415,7 @@ export class SessionManager {
|
|
|
4375
4415
|
}
|
|
4376
4416
|
if (entry.type !== "message" && entry.type !== "custom_message")
|
|
4377
4417
|
return materializeProviderVisibleEntrySync(entry, this.#residentBlobStores());
|
|
4378
|
-
const materialized =
|
|
4418
|
+
const materialized = materializeResidentEntryForReadSync(entry, this.#residentBlobStores(), new Map());
|
|
4379
4419
|
const rehydrated = rehydrateColdSpillEntry(
|
|
4380
4420
|
materialized,
|
|
4381
4421
|
this.#blobStore,
|
|
@@ -4434,7 +4474,7 @@ export class SessionManager {
|
|
|
4434
4474
|
const resolvedTextBlobCache = new Map<string, string>();
|
|
4435
4475
|
const materializedEntries = this.#fileEntries
|
|
4436
4476
|
.filter((e): e is SessionEntry => e.type !== "session")
|
|
4437
|
-
.map(entry =>
|
|
4477
|
+
.map(entry => materializeResidentEntryForReadSync(entry, this.#residentBlobStores(), resolvedTextBlobCache));
|
|
4438
4478
|
this.#materializedEntriesCache = materializedEntries;
|
|
4439
4479
|
this.#materializedEntriesRevision = this.#entryRevision;
|
|
4440
4480
|
return materializedEntries;
|
|
@@ -4591,7 +4631,7 @@ export class SessionManager {
|
|
|
4591
4631
|
|
|
4592
4632
|
// Filter out LabelEntry from path - we'll recreate them from the resolved map
|
|
4593
4633
|
const pathWithoutLabels = branchPath.filter(e => e.type !== "label");
|
|
4594
|
-
const materializedPathWithoutLabels =
|
|
4634
|
+
const materializedPathWithoutLabels = materializeResidentEntriesForReadSync(
|
|
4595
4635
|
pathWithoutLabels,
|
|
4596
4636
|
this.#residentBlobStores(),
|
|
4597
4637
|
);
|
|
@@ -6,6 +6,7 @@ import type { Args } from "../cli/args";
|
|
|
6
6
|
import { tmuxRuntimeSessionPath } from "./session-layout";
|
|
7
7
|
import { SKC_COORDINATOR_SESSION_ID_ENV, SKC_COORDINATOR_SESSION_STATE_FILE_ENV } from "./session-state-sidecar";
|
|
8
8
|
import {
|
|
9
|
+
buildSkcTmuxExactSessionTarget,
|
|
9
10
|
buildSkcTmuxProfileCommands,
|
|
10
11
|
buildSkcTmuxSessionName,
|
|
11
12
|
buildSkcTmuxSessionSlug,
|
|
@@ -21,6 +22,7 @@ import {
|
|
|
21
22
|
import { findSkcTmuxSessionByName, findSkcTmuxSessionByScope, type SkcTmuxSessionStatus } from "./tmux-sessions";
|
|
22
23
|
|
|
23
24
|
export {
|
|
25
|
+
buildSkcTmuxExactSessionTarget,
|
|
24
26
|
buildSkcTmuxProfileCommands,
|
|
25
27
|
SKC_DEFAULT_TMUX_SESSION,
|
|
26
28
|
SKC_TMUX_COMMAND_ENV,
|
|
@@ -201,12 +203,24 @@ function buildWindowsPowerShellInnerCommand(context: CommandResolutionContext, r
|
|
|
201
203
|
const envLines = Object.entries({ [SKC_TMUX_LAUNCHED_ENV]: "1", ...(context.extraEnv ?? {}) }).map(
|
|
202
204
|
([key, value]) => `$env:${key} = ${powershellQuote(value)}`,
|
|
203
205
|
);
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
206
|
+
// The inner `&` invocation must wrap the resolved skc command in a
|
|
207
|
+
// script-block so the trailing PowerShell exec-policy flags from the
|
|
208
|
+
// outer invocation are not forwarded into the bun / node / .bat
|
|
209
|
+
// binary the skc command resolves to. Without the wrapping, the flags
|
|
210
|
+
// reach the runtime binary and cause an immediate failure that closes
|
|
211
|
+
// the psmux pane before the skc --tmux attach can land.
|
|
212
|
+
const resolvedCommand = command.map(powershellQuote).join(" ");
|
|
213
|
+
const innerArgs = stripRootTmuxFlag(rawArgs).map(powershellQuote).join(" ");
|
|
214
|
+
const invocation = `& { ${resolvedCommand} ${innerArgs} }`;
|
|
207
215
|
const exitLine = "if ($null -ne $LASTEXITCODE) { exit $LASTEXITCODE } else { exit 1 }";
|
|
208
216
|
const script = [...envLines, invocation, exitLine].join("\n");
|
|
209
|
-
|
|
217
|
+
// PowerShell -EncodedCommand requires a UTF-16LE BOM (0xFF 0xFE) at the
|
|
218
|
+
// start of the decoded buffer; without it pwsh may misinterpret the
|
|
219
|
+
// leading bytes and reject the script with a parse error, which would
|
|
220
|
+
// kill the psmux pane before the skc --tmux attach could land.
|
|
221
|
+
const bom = Buffer.from([0xff, 0xfe]);
|
|
222
|
+
const body = Buffer.from(script, "utf16le");
|
|
223
|
+
const encodedCommand = Buffer.concat([bom, body]).toString("base64");
|
|
210
224
|
return `pwsh -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand ${encodedCommand}`;
|
|
211
225
|
}
|
|
212
226
|
|
|
@@ -395,7 +409,11 @@ function readCurrentBranch(cwd: string): string | null {
|
|
|
395
409
|
}
|
|
396
410
|
|
|
397
411
|
function cleanupCreatedTmuxSession(plan: TmuxLaunchPlan, spawnSync: TmuxSpawnSync, options: TmuxSpawnOptions): void {
|
|
398
|
-
spawnSync(
|
|
412
|
+
spawnSync(
|
|
413
|
+
plan.tmuxCommand,
|
|
414
|
+
["kill-session", "-t", buildSkcTmuxExactSessionTarget(plan.sessionName, { env: options.env })],
|
|
415
|
+
options,
|
|
416
|
+
);
|
|
399
417
|
}
|
|
400
418
|
function isTmuxAttachDisconnectError(result: TmuxSpawnResult): boolean {
|
|
401
419
|
if (result.signalCode === "SIGHUP") return true;
|
|
@@ -499,7 +517,11 @@ export function launchDefaultTmuxIfNeeded(context: TmuxLaunchContext): boolean {
|
|
|
499
517
|
};
|
|
500
518
|
|
|
501
519
|
if (plan.attachSessionName) {
|
|
502
|
-
const attached = spawnSync(
|
|
520
|
+
const attached = spawnSync(
|
|
521
|
+
plan.tmuxCommand,
|
|
522
|
+
["attach-session", "-t", buildSkcTmuxExactSessionTarget(plan.attachSessionName, { env })],
|
|
523
|
+
options,
|
|
524
|
+
);
|
|
503
525
|
if (attached.exitCode === 0) return true;
|
|
504
526
|
}
|
|
505
527
|
|
|
@@ -510,7 +532,7 @@ export function launchDefaultTmuxIfNeeded(context: TmuxLaunchContext): boolean {
|
|
|
510
532
|
buildSkcTmuxWindowTitle(plan.project ?? plan.cwd, plan.branch),
|
|
511
533
|
spawnSync,
|
|
512
534
|
options,
|
|
513
|
-
|
|
535
|
+
buildSkcTmuxExactSessionTarget(plan.sessionName, { env }),
|
|
514
536
|
);
|
|
515
537
|
|
|
516
538
|
const profile = applySkcTmuxProfile({
|
|
@@ -535,7 +557,11 @@ export function launchDefaultTmuxIfNeeded(context: TmuxLaunchContext): boolean {
|
|
|
535
557
|
}
|
|
536
558
|
}
|
|
537
559
|
if (created.exitCode !== 0) return false;
|
|
538
|
-
const attached = spawnSync(
|
|
560
|
+
const attached = spawnSync(
|
|
561
|
+
plan.tmuxCommand,
|
|
562
|
+
["attach-session", "-t", buildSkcTmuxExactSessionTarget(plan.sessionName, { env })],
|
|
563
|
+
options,
|
|
564
|
+
);
|
|
539
565
|
if (attached.exitCode === 0) return true;
|
|
540
566
|
if (isTmuxAttachDisconnectError(attached)) {
|
|
541
567
|
(context.diagnosticWriter ?? safeStderrWrite)(formatTmuxLaunchDiagnostic("attach disconnected", attached.stderr));
|