@leanandmean/coding-agent 0.74.1-scramjet.7 → 0.74.1-scramjet.71
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 +32 -0
- package/LICENSE +202 -0
- package/README.md +37 -9
- package/dist/cli/args.d.ts +2 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +45 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/initial-message.d.ts +1 -0
- package/dist/cli/initial-message.d.ts.map +1 -1
- package/dist/cli/initial-message.js +3 -0
- package/dist/cli/initial-message.js.map +1 -1
- package/dist/cli/list-models.d.ts +2 -0
- package/dist/cli/list-models.d.ts.map +1 -1
- package/dist/cli/list-models.js +4 -1
- package/dist/cli/list-models.js.map +1 -1
- package/dist/config.d.ts +9 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +33 -22
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts +12 -3
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +64 -38
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +2 -0
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +3 -0
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +84 -17
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +1082 -209
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +21 -17
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +3 -5
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +43 -10
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/exec.d.ts +12 -0
- package/dist/core/exec.d.ts.map +1 -1
- package/dist/core/exec.js +74 -28
- package/dist/core/exec.js.map +1 -1
- package/dist/core/export-html/template.js +4 -0
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +9 -5
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +160 -3
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +85 -8
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/keybindings.d.ts +8 -0
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/model-registry.d.ts +103 -0
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +272 -28
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +24 -6
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/output-throughput-history.d.ts +25 -0
- package/dist/core/output-throughput-history.d.ts.map +1 -0
- package/dist/core/output-throughput-history.js +240 -0
- package/dist/core/output-throughput-history.js.map +1 -0
- package/dist/core/output-throughput.d.ts +43 -0
- package/dist/core/output-throughput.d.ts.map +1 -0
- package/dist/core/output-throughput.js +161 -0
- package/dist/core/output-throughput.js.map +1 -0
- package/dist/core/resource-loader.d.ts +6 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +76 -21
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/scramjet-command-parser.d.ts +4 -0
- package/dist/core/scramjet-command-parser.d.ts.map +1 -1
- package/dist/core/scramjet-command-parser.js +72 -1
- package/dist/core/scramjet-command-parser.js.map +1 -1
- package/dist/core/sdk.d.ts +4 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +70 -11
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +11 -3
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +46 -11
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +19 -6
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +131 -17
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +6 -2
- package/dist/core/skills.js.map +1 -1
- package/dist/core/system-prompt.d.ts +2 -0
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +5 -2
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +4 -3
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +119 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +113 -11
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/armin.d.ts +4 -2
- package/dist/modes/interactive/components/armin.d.ts.map +1 -1
- package/dist/modes/interactive/components/armin.js +14 -8
- package/dist/modes/interactive/components/armin.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts +3 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +19 -8
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/daxnuts.d.ts +4 -1
- package/dist/modes/interactive/components/daxnuts.d.ts.map +1 -1
- package/dist/modes/interactive/components/daxnuts.js +14 -6
- package/dist/modes/interactive/components/daxnuts.js.map +1 -1
- package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-editor.js +5 -3
- package/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-selector.js +4 -3
- package/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +4 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +24 -20
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts +3 -3
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +10 -7
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts +3 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +8 -2
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +17 -3
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +87 -16
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +2 -1
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +19 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +129 -36
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +3 -2
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/components/visual-truncate.d.ts.map +1 -1
- package/dist/modes/interactive/components/visual-truncate.js +2 -2
- package/dist/modes/interactive/components/visual-truncate.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +37 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +902 -177
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/spellcheck.d.ts +26 -0
- package/dist/modes/interactive/spellcheck.d.ts.map +1 -0
- package/dist/modes/interactive/spellcheck.js +144 -0
- package/dist/modes/interactive/spellcheck.js.map +1 -0
- package/dist/modes/interactive/theme/{dark.json → pi-dark.json} +3 -2
- package/dist/modes/interactive/theme/{light.json → pi-light.json} +27 -26
- package/dist/modes/interactive/theme/theme-schema.json +6 -1
- package/dist/modes/interactive/theme/theme.d.ts +24 -4
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +123 -22
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +5 -0
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +3 -1
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +6 -1
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +33 -14
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/utils/clipboard-native.d.ts +1 -0
- package/dist/utils/clipboard-native.d.ts.map +1 -1
- package/dist/utils/clipboard-native.js.map +1 -1
- package/dist/utils/clipboard.d.ts +6 -0
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +278 -10
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/tools-manager.d.ts +1 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +7 -6
- package/dist/utils/tools-manager.js.map +1 -1
- package/dist/utils/version-check.d.ts +17 -5
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +88 -21
- package/dist/utils/version-check.js.map +1 -1
- package/docs/compaction.md +21 -3
- package/docs/custom-provider.md +33 -4
- package/docs/extensions.md +150 -24
- package/docs/json.md +8 -0
- package/docs/keybindings.md +25 -1
- package/docs/models.md +103 -4
- package/docs/packages.md +2 -0
- package/docs/prompt-templates.md +2 -0
- package/docs/quickstart.md +1 -4
- package/docs/rpc.md +16 -6
- package/docs/sdk.md +48 -3
- package/docs/session-format.md +5 -1
- package/docs/settings.md +19 -5
- package/docs/skills.md +7 -5
- package/docs/terminal-setup.md +79 -2
- package/docs/terminal-verification.md +71 -0
- package/docs/themes.md +63 -10
- package/docs/tui.md +120 -25
- package/docs/usage.md +6 -4
- package/examples/extensions/border-status-editor.ts +2 -3
- package/examples/extensions/doom-overlay/doom-component.ts +3 -3
- package/examples/extensions/mac-system-theme.ts +3 -2
- package/examples/extensions/modal-editor.ts +13 -8
- package/examples/extensions/overlay-qa-tests.ts +22 -9
- package/examples/extensions/overlay-test.ts +5 -3
- package/examples/extensions/snake.ts +3 -1
- package/examples/extensions/space-invaders.ts +6 -3
- package/examples/extensions/subagent/index.ts +20 -17
- package/package.json +69 -58
package/dist/main.d.ts
CHANGED
|
@@ -4,7 +4,126 @@
|
|
|
4
4
|
* This file handles CLI argument parsing and translates them into
|
|
5
5
|
* createAgentSession() options. The SDK does the heavy lifting.
|
|
6
6
|
*/
|
|
7
|
+
import type { ThinkingLevel } from "@leanandmean/agent";
|
|
8
|
+
import { type Model } from "@leanandmean/ai";
|
|
9
|
+
import { type Args } from "./cli/args.js";
|
|
10
|
+
import { type AgentSessionRuntimeDiagnostic } from "./core/agent-session-services.js";
|
|
11
|
+
import { AuthStorage } from "./core/auth-storage.js";
|
|
7
12
|
import type { ExtensionAPI, ExtensionFactory } from "./core/extensions/types.js";
|
|
13
|
+
import { type ModelRegistry } from "./core/model-registry.js";
|
|
14
|
+
import { type ScopedModel } from "./core/model-resolver.js";
|
|
15
|
+
import type { CreateAgentSessionOptions } from "./core/sdk.js";
|
|
16
|
+
import { SettingsManager } from "./core/settings-manager.js";
|
|
17
|
+
export declare function resolveInvocationModelDefinition(parsed: Args, stdinIsTTY: boolean, env?: NodeJS.ProcessEnv): {
|
|
18
|
+
definition: {
|
|
19
|
+
api: "anthropic-messages" | "google-generative-ai" | "openai-completions" | "openai-responses";
|
|
20
|
+
baseUrl: string;
|
|
21
|
+
apiKeyEnv: string;
|
|
22
|
+
contextWindow: number;
|
|
23
|
+
maxTokens: number;
|
|
24
|
+
name: string;
|
|
25
|
+
reasoning: boolean;
|
|
26
|
+
input: ("image" | "text")[];
|
|
27
|
+
cost: {
|
|
28
|
+
input: number;
|
|
29
|
+
output: number;
|
|
30
|
+
cacheRead: number;
|
|
31
|
+
cacheWrite: number;
|
|
32
|
+
};
|
|
33
|
+
compat?: {
|
|
34
|
+
supportsStore?: boolean | undefined;
|
|
35
|
+
supportsDeveloperRole?: boolean | undefined;
|
|
36
|
+
supportsReasoningEffort?: boolean | undefined;
|
|
37
|
+
supportsUsageInStreaming?: boolean | undefined;
|
|
38
|
+
maxTokensField?: "max_completion_tokens" | "max_tokens" | undefined;
|
|
39
|
+
requiresToolResultName?: boolean | undefined;
|
|
40
|
+
requiresAssistantAfterToolResult?: boolean | undefined;
|
|
41
|
+
requiresThinkingAsText?: boolean | undefined;
|
|
42
|
+
requiresReasoningContentOnAssistantMessages?: boolean | undefined;
|
|
43
|
+
thinkingFormat?: "deepseek" | "openai" | "openrouter" | "qwen" | "qwen-chat-template" | "together" | "zai" | undefined;
|
|
44
|
+
cacheControlFormat?: "anthropic" | undefined;
|
|
45
|
+
openRouterRouting?: {
|
|
46
|
+
allow_fallbacks?: boolean | undefined;
|
|
47
|
+
require_parameters?: boolean | undefined;
|
|
48
|
+
data_collection?: "allow" | "deny" | undefined;
|
|
49
|
+
zdr?: boolean | undefined;
|
|
50
|
+
enforce_distillable_text?: boolean | undefined;
|
|
51
|
+
order?: string[] | undefined;
|
|
52
|
+
only?: string[] | undefined;
|
|
53
|
+
ignore?: string[] | undefined;
|
|
54
|
+
quantizations?: string[] | undefined;
|
|
55
|
+
sort?: string | {
|
|
56
|
+
by?: string | undefined;
|
|
57
|
+
partition?: string | null | undefined;
|
|
58
|
+
} | undefined;
|
|
59
|
+
max_price?: {
|
|
60
|
+
prompt?: string | number | undefined;
|
|
61
|
+
completion?: string | number | undefined;
|
|
62
|
+
image?: string | number | undefined;
|
|
63
|
+
audio?: string | number | undefined;
|
|
64
|
+
request?: string | number | undefined;
|
|
65
|
+
} | undefined;
|
|
66
|
+
preferred_min_throughput?: number | {
|
|
67
|
+
p50?: number | undefined;
|
|
68
|
+
p75?: number | undefined;
|
|
69
|
+
p90?: number | undefined;
|
|
70
|
+
p99?: number | undefined;
|
|
71
|
+
} | undefined;
|
|
72
|
+
preferred_max_latency?: number | {
|
|
73
|
+
p50?: number | undefined;
|
|
74
|
+
p75?: number | undefined;
|
|
75
|
+
p90?: number | undefined;
|
|
76
|
+
p99?: number | undefined;
|
|
77
|
+
} | undefined;
|
|
78
|
+
} | undefined;
|
|
79
|
+
vercelGatewayRouting?: {
|
|
80
|
+
only?: string[] | undefined;
|
|
81
|
+
order?: string[] | undefined;
|
|
82
|
+
} | undefined;
|
|
83
|
+
zaiToolStream?: boolean | undefined;
|
|
84
|
+
supportsStrictMode?: boolean | undefined;
|
|
85
|
+
sendSessionAffinityHeaders?: boolean | undefined;
|
|
86
|
+
supportsLongCacheRetention?: boolean | undefined;
|
|
87
|
+
} | {
|
|
88
|
+
sendSessionIdHeader?: boolean | undefined;
|
|
89
|
+
supportsLongCacheRetention?: boolean | undefined;
|
|
90
|
+
} | {
|
|
91
|
+
supportsEagerToolInputStreaming?: boolean | undefined;
|
|
92
|
+
supportsLongCacheRetention?: boolean | undefined;
|
|
93
|
+
sendSessionAffinityHeaders?: boolean | undefined;
|
|
94
|
+
supportsCacheControlOnTools?: boolean | undefined;
|
|
95
|
+
supportsTemperature?: boolean | undefined;
|
|
96
|
+
forceAdaptiveThinking?: boolean | undefined;
|
|
97
|
+
} | undefined;
|
|
98
|
+
id: string;
|
|
99
|
+
maxInputTokens?: number | undefined;
|
|
100
|
+
requestLimits?: {
|
|
101
|
+
maxTotalTokens: number;
|
|
102
|
+
maxInputTokens?: number | undefined;
|
|
103
|
+
maxOutputTokens?: number | undefined;
|
|
104
|
+
supportsTools: boolean;
|
|
105
|
+
}[] | undefined;
|
|
106
|
+
thinkingLevelMap?: {
|
|
107
|
+
off?: string | null | undefined;
|
|
108
|
+
minimal?: string | null | undefined;
|
|
109
|
+
low?: string | null | undefined;
|
|
110
|
+
medium?: string | null | undefined;
|
|
111
|
+
high?: string | null | undefined;
|
|
112
|
+
xhigh?: string | null | undefined;
|
|
113
|
+
max?: string | null | undefined;
|
|
114
|
+
} | undefined;
|
|
115
|
+
};
|
|
116
|
+
apiKey: string | undefined;
|
|
117
|
+
} | undefined;
|
|
118
|
+
export declare function registerInvocationModel(invocation: NonNullable<ReturnType<typeof resolveInvocationModelDefinition>>, provider: string, modelRegistry: ModelRegistry, authStorage: AuthStorage): Model<any>;
|
|
119
|
+
export declare function buildSessionOptions(parsed: Args, scopedModels: ScopedModel[], hasExistingSession: boolean, modelRegistry: ModelRegistry, settingsManager: SettingsManager, inherited?: {
|
|
120
|
+
model: Model<any>;
|
|
121
|
+
thinkingLevel: ThinkingLevel;
|
|
122
|
+
}, invocationModel?: Model<any>): {
|
|
123
|
+
options: CreateAgentSessionOptions;
|
|
124
|
+
cliThinkingFromModel: boolean;
|
|
125
|
+
diagnostics: AgentSessionRuntimeDiagnostic[];
|
|
126
|
+
};
|
|
8
127
|
export interface MainOptions {
|
|
9
128
|
extensionFactories?: ExtensionFactory[];
|
|
10
129
|
builtinInit?: (pi: ExtensionAPI) => void | Promise<void>;
|
package/dist/main.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAsBH,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AA6YjF,MAAM,WAAW,WAAW;IAC3B,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACxC,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzD;AAED,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,WAAW,iBA4S/D","sourcesContent":["/**\n * Main entry point for the coding agent CLI.\n *\n * This file handles CLI argument parsing and translates them into\n * createAgentSession() options. The SDK does the heavy lifting.\n */\n\nimport { resolve } from \"node:path\";\nimport { createInterface } from \"node:readline\";\nimport { type ImageContent, modelsAreEqual } from \"@leanandmean/ai\";\nimport { ProcessTerminal, setKeybindings, TUI } from \"@leanandmean/tui\";\nimport chalk from \"chalk\";\nimport { type Args, type Mode, parseArgs, printHelp } from \"./cli/args.js\";\nimport { processFileArguments } from \"./cli/file-processor.js\";\nimport { buildInitialMessage } from \"./cli/initial-message.js\";\nimport { listModels } from \"./cli/list-models.js\";\nimport { selectSession } from \"./cli/session-picker.js\";\nimport { ENV_SESSION_DIR, expandTildePath, getAgentDir, VERSION } from \"./config.js\";\nimport { type CreateAgentSessionRuntimeFactory, createAgentSessionRuntime } from \"./core/agent-session-runtime.js\";\nimport {\n\ttype AgentSessionRuntimeDiagnostic,\n\tcreateAgentSessionFromServices,\n\tcreateAgentSessionServices,\n} from \"./core/agent-session-services.js\";\nimport { formatNoModelsAvailableMessage } from \"./core/auth-guidance.js\";\nimport { AuthStorage } from \"./core/auth-storage.js\";\nimport { exportFromFile } from \"./core/export-html/index.js\";\nimport type { ExtensionAPI, ExtensionFactory } from \"./core/extensions/types.js\";\nimport { KeybindingsManager } from \"./core/keybindings.js\";\nimport type { ModelRegistry } from \"./core/model-registry.js\";\nimport { resolveCliModel, resolveModelScope, type ScopedModel } from \"./core/model-resolver.js\";\nimport { restoreStdout, takeOverStdout } from \"./core/output-guard.js\";\nimport type { CreateAgentSessionOptions } from \"./core/sdk.js\";\nimport {\n\tformatMissingSessionCwdPrompt,\n\tgetMissingSessionCwdIssue,\n\tMissingSessionCwdError,\n\ttype SessionCwdIssue,\n} from \"./core/session-cwd.js\";\nimport { SessionManager } from \"./core/session-manager.js\";\nimport { SettingsManager } from \"./core/settings-manager.js\";\nimport { printTimings, resetTimings, time } from \"./core/timings.js\";\nimport { runMigrations, showDeprecationWarnings } from \"./migrations.js\";\nimport { InteractiveMode, runPrintMode, runRpcMode } from \"./modes/index.js\";\nimport { ExtensionSelectorComponent } from \"./modes/interactive/components/extension-selector.js\";\nimport { initTheme, stopThemeWatcher } from \"./modes/interactive/theme/theme.js\";\nimport { handleConfigCommand, handlePackageCommand } from \"./package-manager-cli.js\";\nimport { isLocalPath } from \"./utils/paths.js\";\n\n/**\n * Read all content from piped stdin.\n * Returns undefined if stdin is a TTY (interactive terminal).\n */\nasync function readPipedStdin(): Promise<string | undefined> {\n\t// If stdin is a TTY, we're running interactively - don't read stdin\n\tif (process.stdin.isTTY) {\n\t\treturn undefined;\n\t}\n\n\treturn new Promise((resolve) => {\n\t\tlet data = \"\";\n\t\tprocess.stdin.setEncoding(\"utf8\");\n\t\tprocess.stdin.on(\"data\", (chunk) => {\n\t\t\tdata += chunk;\n\t\t});\n\t\tprocess.stdin.on(\"end\", () => {\n\t\t\tresolve(data.trim() || undefined);\n\t\t});\n\t\tprocess.stdin.resume();\n\t});\n}\n\nfunction collectSettingsDiagnostics(\n\tsettingsManager: SettingsManager,\n\tcontext: string,\n): AgentSessionRuntimeDiagnostic[] {\n\treturn settingsManager.drainErrors().map(({ scope, error }) => ({\n\t\ttype: \"warning\",\n\t\tmessage: `(${context}, ${scope} settings) ${error.message}`,\n\t}));\n}\n\nfunction reportDiagnostics(diagnostics: readonly AgentSessionRuntimeDiagnostic[]): void {\n\tfor (const diagnostic of diagnostics) {\n\t\tconst color = diagnostic.type === \"error\" ? chalk.red : diagnostic.type === \"warning\" ? chalk.yellow : chalk.dim;\n\t\tconst prefix = diagnostic.type === \"error\" ? \"Error: \" : diagnostic.type === \"warning\" ? \"Warning: \" : \"\";\n\t\tconsole.error(color(`${prefix}${diagnostic.message}`));\n\t}\n}\n\nfunction isTruthyEnvFlag(value: string | undefined): boolean {\n\tif (!value) return false;\n\treturn value === \"1\" || value.toLowerCase() === \"true\" || value.toLowerCase() === \"yes\";\n}\n\ntype AppMode = \"interactive\" | \"print\" | \"json\" | \"rpc\";\n\nfunction resolveAppMode(parsed: Args, stdinIsTTY: boolean): AppMode {\n\tif (parsed.mode === \"rpc\") {\n\t\treturn \"rpc\";\n\t}\n\tif (parsed.mode === \"json\") {\n\t\treturn \"json\";\n\t}\n\tif (parsed.print || !stdinIsTTY) {\n\t\treturn \"print\";\n\t}\n\treturn \"interactive\";\n}\n\nfunction toPrintOutputMode(appMode: AppMode): Exclude<Mode, \"rpc\"> {\n\treturn appMode === \"json\" ? \"json\" : \"text\";\n}\n\nasync function prepareInitialMessage(\n\tparsed: Args,\n\tautoResizeImages: boolean,\n\tstdinContent?: string,\n): Promise<{\n\tinitialMessage?: string;\n\tinitialImages?: ImageContent[];\n}> {\n\tif (parsed.fileArgs.length === 0) {\n\t\treturn buildInitialMessage({ parsed, stdinContent });\n\t}\n\n\tconst { text, images } = await processFileArguments(parsed.fileArgs, { autoResizeImages });\n\treturn buildInitialMessage({\n\t\tparsed,\n\t\tfileText: text,\n\t\tfileImages: images,\n\t\tstdinContent,\n\t});\n}\n\n/** Result from resolving a session argument */\ntype ResolvedSession =\n\t| { type: \"path\"; path: string } // Direct file path\n\t| { type: \"local\"; path: string } // Found in current project\n\t| { type: \"global\"; path: string; cwd: string } // Found in different project\n\t| { type: \"not_found\"; arg: string }; // Not found anywhere\n\n/**\n * Resolve a session argument to a file path.\n * If it looks like a path, use as-is. Otherwise try to match as session ID prefix.\n */\nasync function resolveSessionPath(sessionArg: string, cwd: string, sessionDir?: string): Promise<ResolvedSession> {\n\t// If it looks like a file path, use as-is\n\tif (sessionArg.includes(\"/\") || sessionArg.includes(\"\\\\\") || sessionArg.endsWith(\".jsonl\")) {\n\t\treturn { type: \"path\", path: sessionArg };\n\t}\n\n\t// Try to match as session ID in current project first\n\tconst localSessions = await SessionManager.list(cwd, sessionDir);\n\tconst localMatches = localSessions.filter((s) => s.id.startsWith(sessionArg));\n\n\tif (localMatches.length >= 1) {\n\t\treturn { type: \"local\", path: localMatches[0].path };\n\t}\n\n\t// Try global search across all projects\n\tconst allSessions = await SessionManager.listAll();\n\tconst globalMatches = allSessions.filter((s) => s.id.startsWith(sessionArg));\n\n\tif (globalMatches.length >= 1) {\n\t\tconst match = globalMatches[0];\n\t\treturn { type: \"global\", path: match.path, cwd: match.cwd };\n\t}\n\n\t// Not found anywhere\n\treturn { type: \"not_found\", arg: sessionArg };\n}\n\n/** Prompt user for yes/no confirmation */\nasync function promptConfirm(message: string): Promise<boolean> {\n\treturn new Promise((resolve) => {\n\t\tconst rl = createInterface({\n\t\t\tinput: process.stdin,\n\t\t\toutput: process.stdout,\n\t\t});\n\t\trl.question(`${message} [y/N] `, (answer) => {\n\t\t\trl.close();\n\t\t\tresolve(answer.toLowerCase() === \"y\" || answer.toLowerCase() === \"yes\");\n\t\t});\n\t});\n}\n\nfunction validateForkFlags(parsed: Args): void {\n\tif (!parsed.fork) return;\n\n\tconst conflictingFlags = [\n\t\tparsed.session ? \"--session\" : undefined,\n\t\tparsed.continue ? \"--continue\" : undefined,\n\t\tparsed.resume ? \"--resume\" : undefined,\n\t\tparsed.noSession ? \"--no-session\" : undefined,\n\t].filter((flag): flag is string => flag !== undefined);\n\n\tif (conflictingFlags.length > 0) {\n\t\tconsole.error(chalk.red(`Error: --fork cannot be combined with ${conflictingFlags.join(\", \")}`));\n\t\tprocess.exit(1);\n\t}\n}\n\nfunction forkSessionOrExit(sourcePath: string, cwd: string, sessionDir?: string): SessionManager {\n\ttry {\n\t\treturn SessionManager.forkFrom(sourcePath, cwd, sessionDir);\n\t} catch (error: unknown) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tconsole.error(chalk.red(`Error: ${message}`));\n\t\tprocess.exit(1);\n\t}\n}\n\nasync function createSessionManager(\n\tparsed: Args,\n\tcwd: string,\n\tsessionDir: string | undefined,\n\tsettingsManager: SettingsManager,\n): Promise<SessionManager> {\n\tif (parsed.noSession) {\n\t\treturn SessionManager.inMemory();\n\t}\n\n\tif (parsed.fork) {\n\t\tconst resolved = await resolveSessionPath(parsed.fork, cwd, sessionDir);\n\n\t\tswitch (resolved.type) {\n\t\t\tcase \"path\":\n\t\t\tcase \"local\":\n\t\t\tcase \"global\":\n\t\t\t\treturn forkSessionOrExit(resolved.path, cwd, sessionDir);\n\n\t\t\tcase \"not_found\":\n\t\t\t\tconsole.error(chalk.red(`No session found matching '${resolved.arg}'`));\n\t\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\tif (parsed.session) {\n\t\tconst resolved = await resolveSessionPath(parsed.session, cwd, sessionDir);\n\n\t\tswitch (resolved.type) {\n\t\t\tcase \"path\":\n\t\t\tcase \"local\":\n\t\t\t\treturn SessionManager.open(resolved.path, sessionDir);\n\n\t\t\tcase \"global\": {\n\t\t\t\tconsole.log(chalk.yellow(`Session found in different project: ${resolved.cwd}`));\n\t\t\t\tconst shouldFork = await promptConfirm(\"Fork this session into current directory?\");\n\t\t\t\tif (!shouldFork) {\n\t\t\t\t\tconsole.log(chalk.dim(\"Aborted.\"));\n\t\t\t\t\tprocess.exit(0);\n\t\t\t\t}\n\t\t\t\treturn forkSessionOrExit(resolved.path, cwd, sessionDir);\n\t\t\t}\n\n\t\t\tcase \"not_found\":\n\t\t\t\tconsole.error(chalk.red(`No session found matching '${resolved.arg}'`));\n\t\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\tif (parsed.resume) {\n\t\tinitTheme(settingsManager.getTheme(), true);\n\t\ttry {\n\t\t\tconst selectedPath = await selectSession(\n\t\t\t\t(onProgress) => SessionManager.list(cwd, sessionDir, onProgress),\n\t\t\t\tSessionManager.listAll,\n\t\t\t);\n\t\t\tif (!selectedPath) {\n\t\t\t\tconsole.log(chalk.dim(\"No session selected\"));\n\t\t\t\tprocess.exit(0);\n\t\t\t}\n\t\t\treturn SessionManager.open(selectedPath, sessionDir);\n\t\t} finally {\n\t\t\tstopThemeWatcher();\n\t\t}\n\t}\n\n\tif (parsed.continue) {\n\t\treturn SessionManager.continueRecent(cwd, sessionDir);\n\t}\n\n\treturn SessionManager.create(cwd, sessionDir);\n}\n\nfunction buildSessionOptions(\n\tparsed: Args,\n\tscopedModels: ScopedModel[],\n\thasExistingSession: boolean,\n\tmodelRegistry: ModelRegistry,\n\tsettingsManager: SettingsManager,\n): {\n\toptions: CreateAgentSessionOptions;\n\tcliThinkingFromModel: boolean;\n\tdiagnostics: AgentSessionRuntimeDiagnostic[];\n} {\n\tconst options: CreateAgentSessionOptions = {};\n\tconst diagnostics: AgentSessionRuntimeDiagnostic[] = [];\n\tlet cliThinkingFromModel = false;\n\n\t// Model from CLI\n\t// - supports --provider <name> --model <pattern>\n\t// - supports --model <provider>/<pattern>\n\tif (parsed.model) {\n\t\tconst resolved = resolveCliModel({\n\t\t\tcliProvider: parsed.provider,\n\t\t\tcliModel: parsed.model,\n\t\t\tmodelRegistry,\n\t\t});\n\t\tif (resolved.warning) {\n\t\t\tdiagnostics.push({ type: \"warning\", message: resolved.warning });\n\t\t}\n\t\tif (resolved.error) {\n\t\t\tdiagnostics.push({ type: \"error\", message: resolved.error });\n\t\t}\n\t\tif (resolved.model) {\n\t\t\toptions.model = resolved.model;\n\t\t\t// Allow \"--model <pattern>:<thinking>\" as a shorthand.\n\t\t\t// Explicit --thinking still takes precedence (applied later).\n\t\t\tif (!parsed.thinking && resolved.thinkingLevel) {\n\t\t\t\toptions.thinkingLevel = resolved.thinkingLevel;\n\t\t\t\tcliThinkingFromModel = true;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!options.model && scopedModels.length > 0 && !hasExistingSession) {\n\t\t// Check if saved default is in scoped models - use it if so, otherwise first scoped model\n\t\tconst savedProvider = settingsManager.getDefaultProvider();\n\t\tconst savedModelId = settingsManager.getDefaultModel();\n\t\tconst savedModel = savedProvider && savedModelId ? modelRegistry.find(savedProvider, savedModelId) : undefined;\n\t\tconst savedInScope = savedModel ? scopedModels.find((sm) => modelsAreEqual(sm.model, savedModel)) : undefined;\n\n\t\tif (savedInScope) {\n\t\t\toptions.model = savedInScope.model;\n\t\t\t// Use thinking level from scoped model config if explicitly set\n\t\t\tif (!parsed.thinking && savedInScope.thinkingLevel) {\n\t\t\t\toptions.thinkingLevel = savedInScope.thinkingLevel;\n\t\t\t}\n\t\t} else {\n\t\t\toptions.model = scopedModels[0].model;\n\t\t\t// Use thinking level from first scoped model if explicitly set\n\t\t\tif (!parsed.thinking && scopedModels[0].thinkingLevel) {\n\t\t\t\toptions.thinkingLevel = scopedModels[0].thinkingLevel;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Thinking level from CLI (takes precedence over scoped model thinking levels set above)\n\tif (parsed.thinking) {\n\t\toptions.thinkingLevel = parsed.thinking;\n\t}\n\n\t// Cache retention from CLI (PI_CACHE_RETENTION and the default are resolved by the SDK)\n\tif (parsed.cacheRetention) {\n\t\toptions.cacheRetention = parsed.cacheRetention;\n\t}\n\n\t// Scoped models for Ctrl+P cycling\n\t// Keep thinking level undefined when not explicitly set in the model pattern.\n\t// Undefined means \"inherit current session thinking level\" during cycling.\n\tif (scopedModels.length > 0) {\n\t\toptions.scopedModels = scopedModels.map((sm) => ({\n\t\t\tmodel: sm.model,\n\t\t\tthinkingLevel: sm.thinkingLevel,\n\t\t}));\n\t}\n\n\t// API key from CLI - set in authStorage\n\t// (handled by caller before createAgentSession)\n\n\t// Tools\n\tif (parsed.noTools) {\n\t\toptions.noTools = \"all\";\n\t} else if (parsed.noBuiltinTools) {\n\t\toptions.noTools = \"builtin\";\n\t}\n\tif (parsed.tools) {\n\t\toptions.tools = [...parsed.tools];\n\t}\n\n\treturn { options, cliThinkingFromModel, diagnostics };\n}\n\nfunction resolveCliPaths(cwd: string, paths: string[] | undefined): string[] | undefined {\n\treturn paths?.map((value) => (isLocalPath(value) ? resolve(cwd, value) : value));\n}\n\nasync function promptForMissingSessionCwd(\n\tissue: SessionCwdIssue,\n\tsettingsManager: SettingsManager,\n): Promise<string | undefined> {\n\tinitTheme(settingsManager.getTheme());\n\tsetKeybindings(KeybindingsManager.create());\n\n\treturn new Promise((resolve) => {\n\t\tconst ui = new TUI(new ProcessTerminal(), settingsManager.getShowHardwareCursor());\n\t\tui.setClearOnShrink(settingsManager.getClearOnShrink());\n\n\t\tlet settled = false;\n\t\tconst finish = (result: string | undefined) => {\n\t\t\tif (settled) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsettled = true;\n\t\t\tui.stop();\n\t\t\tresolve(result);\n\t\t};\n\n\t\tconst selector = new ExtensionSelectorComponent(\n\t\t\tformatMissingSessionCwdPrompt(issue),\n\t\t\t[\"Continue\", \"Cancel\"],\n\t\t\t(option) => finish(option === \"Continue\" ? issue.fallbackCwd : undefined),\n\t\t\t() => finish(undefined),\n\t\t\t{ tui: ui },\n\t\t);\n\t\tui.addChild(selector);\n\t\tui.setFocus(selector);\n\t\tui.start();\n\t});\n}\n\n// SCRAMJET-DIVERGENCE: added builtinInit for direct product wiring without extension discovery.\nexport interface MainOptions {\n\textensionFactories?: ExtensionFactory[];\n\tbuiltinInit?: (pi: ExtensionAPI) => void | Promise<void>;\n}\n\nexport async function main(args: string[], options?: MainOptions) {\n\tresetTimings();\n\t// SCRAMJET-DIVERGENCE: prefer SCRAMJET_OFFLINE, fall back to PI_OFFLINE.\n\tconst offlineMode =\n\t\targs.includes(\"--offline\") ||\n\t\tisTruthyEnvFlag(process.env.SCRAMJET_OFFLINE) ||\n\t\tisTruthyEnvFlag(process.env.PI_OFFLINE);\n\tif (offlineMode) {\n\t\tprocess.env.SCRAMJET_OFFLINE = \"1\";\n\t\tprocess.env.PI_OFFLINE = \"1\";\n\t\tprocess.env.PI_SKIP_VERSION_CHECK = \"1\";\n\t}\n\n\tif (await handlePackageCommand(args)) {\n\t\treturn;\n\t}\n\n\tif (await handleConfigCommand(args)) {\n\t\treturn;\n\t}\n\n\tconst parsed = parseArgs(args);\n\tif (parsed.diagnostics.length > 0) {\n\t\tfor (const d of parsed.diagnostics) {\n\t\t\tconst color = d.type === \"error\" ? chalk.red : chalk.yellow;\n\t\t\tconsole.error(color(`${d.type === \"error\" ? \"Error\" : \"Warning\"}: ${d.message}`));\n\t\t}\n\t\tif (parsed.diagnostics.some((d) => d.type === \"error\")) {\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\ttime(\"parseArgs\");\n\tlet appMode = resolveAppMode(parsed, process.stdin.isTTY);\n\tconst shouldTakeOverStdout = appMode !== \"interactive\";\n\tif (shouldTakeOverStdout) {\n\t\ttakeOverStdout();\n\t}\n\n\tif (parsed.version) {\n\t\tconsole.log(VERSION);\n\t\tprocess.exit(0);\n\t}\n\n\tif (parsed.export) {\n\t\tlet result: string;\n\t\ttry {\n\t\t\tconst outputPath = parsed.messages.length > 0 ? parsed.messages[0] : undefined;\n\t\t\tresult = await exportFromFile(parsed.export, outputPath);\n\t\t} catch (error: unknown) {\n\t\t\tconst message = error instanceof Error ? error.message : \"Failed to export session\";\n\t\t\tconsole.error(chalk.red(`Error: ${message}`));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tconsole.log(`Exported to: ${result}`);\n\t\tprocess.exit(0);\n\t}\n\n\tif (parsed.mode === \"rpc\" && parsed.fileArgs.length > 0) {\n\t\tconsole.error(chalk.red(\"Error: @file arguments are not supported in RPC mode\"));\n\t\tprocess.exit(1);\n\t}\n\n\tvalidateForkFlags(parsed);\n\n\t// Run migrations (pass cwd for project-local migrations)\n\tconst { migratedAuthProviders: migratedProviders, deprecationWarnings } = runMigrations(process.cwd());\n\ttime(\"runMigrations\");\n\n\tconst cwd = process.cwd();\n\tconst agentDir = getAgentDir();\n\tconst startupSettingsManager = SettingsManager.create(cwd, agentDir);\n\treportDiagnostics(collectSettingsDiagnostics(startupSettingsManager, \"startup session lookup\"));\n\n\t// Decide the final runtime cwd before creating cwd-bound runtime services.\n\t// --session and --resume may select a session from another project, so project-local\n\t// settings, resources, provider registrations, and models must be resolved only after\n\t// the target session cwd is known. The startup-cwd settings manager is used only for\n\t// sessionDir lookup during session selection.\n\tconst envSessionDir = process.env[ENV_SESSION_DIR];\n\tconst sessionDir =\n\t\tparsed.sessionDir ??\n\t\t(envSessionDir ? expandTildePath(envSessionDir) : undefined) ??\n\t\tstartupSettingsManager.getSessionDir();\n\tlet sessionManager = await createSessionManager(parsed, cwd, sessionDir, startupSettingsManager);\n\tconst missingSessionCwdIssue = getMissingSessionCwdIssue(sessionManager, cwd);\n\tif (missingSessionCwdIssue) {\n\t\tif (appMode === \"interactive\") {\n\t\t\tconst selectedCwd = await promptForMissingSessionCwd(missingSessionCwdIssue, startupSettingsManager);\n\t\t\tif (!selectedCwd) {\n\t\t\t\tprocess.exit(0);\n\t\t\t}\n\t\t\tsessionManager = SessionManager.open(missingSessionCwdIssue.sessionFile!, sessionDir, selectedCwd);\n\t\t} else {\n\t\t\tconsole.error(chalk.red(new MissingSessionCwdError(missingSessionCwdIssue).message));\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\ttime(\"createSessionManager\");\n\n\tconst resolvedExtensionPaths = resolveCliPaths(cwd, parsed.extensions);\n\tconst resolvedSkillPaths = resolveCliPaths(cwd, parsed.skills);\n\tconst resolvedPromptTemplatePaths = resolveCliPaths(cwd, parsed.promptTemplates);\n\tconst resolvedThemePaths = resolveCliPaths(cwd, parsed.themes);\n\tconst authStorage = AuthStorage.create();\n\tconst createRuntime: CreateAgentSessionRuntimeFactory = async ({\n\t\tcwd,\n\t\tagentDir,\n\t\tsessionManager,\n\t\tsessionStartEvent,\n\t}) => {\n\t\tconst services = await createAgentSessionServices({\n\t\t\tcwd,\n\t\t\tagentDir,\n\t\t\tauthStorage,\n\t\t\textensionFlagValues: parsed.unknownFlags,\n\t\t\tresourceLoaderOptions: {\n\t\t\t\tadditionalExtensionPaths: resolvedExtensionPaths,\n\t\t\t\tadditionalSkillPaths: resolvedSkillPaths,\n\t\t\t\tadditionalPromptTemplatePaths: resolvedPromptTemplatePaths,\n\t\t\t\tadditionalThemePaths: resolvedThemePaths,\n\t\t\t\tnoExtensions: parsed.noExtensions,\n\t\t\t\tnoSkills: parsed.noSkills,\n\t\t\t\tnoPromptTemplates: parsed.noPromptTemplates,\n\t\t\t\tnoThemes: parsed.noThemes,\n\t\t\t\tnoContextFiles: parsed.noContextFiles,\n\t\t\t\tsystemPrompt: parsed.systemPrompt,\n\t\t\t\tappendSystemPrompt: parsed.appendSystemPrompt,\n\t\t\t\textensionFactories: options?.extensionFactories,\n\t\t\t\tbuiltinInit: options?.builtinInit,\n\t\t\t},\n\t\t});\n\t\tconst { settingsManager, modelRegistry, resourceLoader } = services;\n\t\tconst diagnostics: AgentSessionRuntimeDiagnostic[] = [\n\t\t\t...services.diagnostics,\n\t\t\t...collectSettingsDiagnostics(settingsManager, \"runtime creation\"),\n\t\t\t...resourceLoader.getExtensions().errors.map(({ path, error }) => ({\n\t\t\t\ttype: \"error\" as const,\n\t\t\t\tmessage: `Failed to load extension \"${path}\": ${error}`,\n\t\t\t})),\n\t\t];\n\n\t\tconst modelPatterns = parsed.models ?? settingsManager.getEnabledModels();\n\t\tconst scopedModels =\n\t\t\tmodelPatterns && modelPatterns.length > 0 ? await resolveModelScope(modelPatterns, modelRegistry) : [];\n\t\tconst {\n\t\t\toptions: sessionOptions,\n\t\t\tcliThinkingFromModel,\n\t\t\tdiagnostics: sessionOptionDiagnostics,\n\t\t} = buildSessionOptions(\n\t\t\tparsed,\n\t\t\tscopedModels,\n\t\t\tsessionManager.buildSessionContext().messages.length > 0,\n\t\t\tmodelRegistry,\n\t\t\tsettingsManager,\n\t\t);\n\t\tdiagnostics.push(...sessionOptionDiagnostics);\n\n\t\tif (parsed.apiKey) {\n\t\t\tif (!sessionOptions.model) {\n\t\t\t\tdiagnostics.push({\n\t\t\t\t\ttype: \"error\",\n\t\t\t\t\tmessage: \"--api-key requires a model to be specified via --model, --provider/--model, or --models\",\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tauthStorage.setRuntimeApiKey(sessionOptions.model.provider, parsed.apiKey);\n\t\t\t}\n\t\t}\n\n\t\tconst created = await createAgentSessionFromServices({\n\t\t\tservices,\n\t\t\tsessionManager,\n\t\t\tsessionStartEvent,\n\t\t\tmodel: sessionOptions.model,\n\t\t\tthinkingLevel: sessionOptions.thinkingLevel,\n\t\t\tscopedModels: sessionOptions.scopedModels,\n\t\t\tcacheRetention: sessionOptions.cacheRetention,\n\t\t\ttools: sessionOptions.tools,\n\t\t\tnoTools: sessionOptions.noTools,\n\t\t\tcustomTools: sessionOptions.customTools,\n\t\t});\n\t\tconst cliThinkingOverride = parsed.thinking !== undefined || cliThinkingFromModel;\n\t\tif (created.session.model && cliThinkingOverride) {\n\t\t\tcreated.session.setThinkingLevel(created.session.thinkingLevel);\n\t\t}\n\n\t\treturn {\n\t\t\t...created,\n\t\t\tservices,\n\t\t\tdiagnostics,\n\t\t};\n\t};\n\ttime(\"createRuntime\");\n\tconst runtime = await createAgentSessionRuntime(createRuntime, {\n\t\tcwd: sessionManager.getCwd(),\n\t\tagentDir,\n\t\tsessionManager,\n\t});\n\tconst { services, session, modelFallbackMessage } = runtime;\n\tconst { settingsManager, modelRegistry, resourceLoader } = services;\n\n\tif (parsed.help) {\n\t\tconst extensionFlags = resourceLoader\n\t\t\t.getExtensions()\n\t\t\t.extensions.flatMap((extension) => Array.from(extension.flags.values()));\n\t\tprintHelp(extensionFlags);\n\t\tprocess.exit(0);\n\t}\n\n\tif (parsed.listModels !== undefined) {\n\t\tconst searchPattern = typeof parsed.listModels === \"string\" ? parsed.listModels : undefined;\n\t\tawait listModels(modelRegistry, searchPattern);\n\t\tprocess.exit(0);\n\t}\n\n\t// Read piped stdin content (if any) - skip for RPC mode which uses stdin for JSON-RPC\n\tlet stdinContent: string | undefined;\n\tif (appMode !== \"rpc\") {\n\t\tstdinContent = await readPipedStdin();\n\t\tif (stdinContent !== undefined && appMode === \"interactive\") {\n\t\t\tappMode = \"print\";\n\t\t}\n\t}\n\ttime(\"readPipedStdin\");\n\n\tconst { initialMessage, initialImages } = await prepareInitialMessage(\n\t\tparsed,\n\t\tsettingsManager.getImageAutoResize(),\n\t\tstdinContent,\n\t);\n\ttime(\"prepareInitialMessage\");\n\tinitTheme(settingsManager.getTheme(), appMode === \"interactive\");\n\ttime(\"initTheme\");\n\n\t// Show deprecation warnings in interactive mode\n\tif (appMode === \"interactive\" && deprecationWarnings.length > 0) {\n\t\tawait showDeprecationWarnings(deprecationWarnings);\n\t}\n\n\ttime(\"resolveModelScope\");\n\treportDiagnostics(runtime.diagnostics);\n\tif (runtime.diagnostics.some((diagnostic) => diagnostic.type === \"error\")) {\n\t\tprocess.exit(1);\n\t}\n\ttime(\"createAgentSession\");\n\n\tif (appMode !== \"interactive\" && !session.model) {\n\t\tconsole.error(chalk.red(formatNoModelsAvailableMessage()));\n\t\tprocess.exit(1);\n\t}\n\n\tconst startupBenchmark = isTruthyEnvFlag(process.env.PI_STARTUP_BENCHMARK);\n\tif (startupBenchmark && appMode !== \"interactive\") {\n\t\tconsole.error(chalk.red(\"Error: PI_STARTUP_BENCHMARK only supports interactive mode\"));\n\t\tprocess.exit(1);\n\t}\n\n\tif (appMode === \"rpc\") {\n\t\tprintTimings();\n\t\tawait runRpcMode(runtime);\n\t} else if (appMode === \"interactive\") {\n\t\tconst interactiveMode = new InteractiveMode(runtime, {\n\t\t\tmigratedProviders,\n\t\t\tmodelFallbackMessage,\n\t\t\tinitialMessage,\n\t\t\tinitialImages,\n\t\t\tinitialMessages: parsed.messages,\n\t\t\tverbose: parsed.verbose,\n\t\t});\n\t\tif (startupBenchmark) {\n\t\t\tawait interactiveMode.init();\n\t\t\ttime(\"interactiveMode.init\");\n\t\t\tprintTimings();\n\t\t\tinteractiveMode.stop();\n\t\t\tstopThemeWatcher();\n\t\t\tif (process.stdout.writableLength > 0) {\n\t\t\t\tawait new Promise<void>((resolve) => process.stdout.once(\"drain\", resolve));\n\t\t\t}\n\t\t\tif (process.stderr.writableLength > 0) {\n\t\t\t\tawait new Promise<void>((resolve) => process.stderr.once(\"drain\", resolve));\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tprintTimings();\n\t\tawait interactiveMode.run();\n\t} else {\n\t\tprintTimings();\n\t\tconst exitCode = await runPrintMode(runtime, {\n\t\t\tmode: toPrintOutputMode(appMode),\n\t\t\tmessages: parsed.messages,\n\t\t\tinitialMessage,\n\t\t\tinitialImages,\n\t\t});\n\t\tstopThemeWatcher();\n\t\trestoreStdout();\n\t\tif (exitCode !== 0) {\n\t\t\tprocess.exitCode = exitCode;\n\t\t}\n\t\treturn;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAqB,KAAK,KAAK,EAAkB,MAAM,iBAAiB,CAAC;AAGhF,OAAO,EAAE,KAAK,IAAI,EAAmC,MAAM,eAAe,CAAC;AAO3E,OAAO,EACN,KAAK,6BAA6B,EAGlC,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAErD,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAEjF,OAAO,EAAE,KAAK,aAAa,EAAqC,MAAM,0BAA0B,CAAC;AACjG,OAAO,EAAsC,KAAK,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGhG,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAQ/D,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AA0D7D,wBAAgB,gCAAgC,CAC/C,MAAM,EAAE,IAAI,EACZ,UAAU,EAAE,OAAO,EACnB,GAAG,GAAE,MAAM,CAAC,UAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAqCpC;AAGD,wBAAgB,uBAAuB,CACtC,UAAU,EAAE,WAAW,CAAC,UAAU,CAAC,OAAO,gCAAgC,CAAC,CAAC,EAC5E,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,aAAa,EAC5B,WAAW,EAAE,WAAW,GACtB,KAAK,CAAC,GAAG,CAAC,CAUZ;AAgMD,wBAAgB,mBAAmB,CAClC,MAAM,EAAE,IAAI,EACZ,YAAY,EAAE,WAAW,EAAE,EAC3B,kBAAkB,EAAE,OAAO,EAC3B,aAAa,EAAE,aAAa,EAC5B,eAAe,EAAE,eAAe,EAChC,SAAS,CAAC,EAAE;IAAE,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAAC,aAAa,EAAE,aAAa,CAAA;CAAE,EAC/D,eAAe,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,GAC1B;IACF,OAAO,EAAE,yBAAyB,CAAC;IACnC,oBAAoB,EAAE,OAAO,CAAC;IAC9B,WAAW,EAAE,6BAA6B,EAAE,CAAC;CAC7C,CAqGA;AAyCD,MAAM,WAAW,WAAW;IAC3B,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACxC,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzD;AAED,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,WAAW,iBA8U/D","sourcesContent":["/**\n * Main entry point for the coding agent CLI.\n *\n * This file handles CLI argument parsing and translates them into\n * createAgentSession() options. The SDK does the heavy lifting.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport { resolve } from \"node:path\";\nimport { createInterface } from \"node:readline\";\nimport type { ThinkingLevel } from \"@leanandmean/agent\";\nimport { type ImageContent, type Model, modelsAreEqual } from \"@leanandmean/ai\";\nimport { ProcessTerminal, setKeybindings, TUI } from \"@leanandmean/tui\";\nimport chalk from \"chalk\";\nimport { type Args, type Mode, parseArgs, printHelp } from \"./cli/args.js\";\nimport { processFileArguments } from \"./cli/file-processor.js\";\nimport { buildInitialMessage, normalizePipedStdinContent } from \"./cli/initial-message.js\";\nimport { listModels } from \"./cli/list-models.js\";\nimport { selectSession } from \"./cli/session-picker.js\";\nimport { ENV_SESSION_DIR, expandTildePath, getAgentDir, VERSION } from \"./config.js\";\nimport { type CreateAgentSessionRuntimeFactory, createAgentSessionRuntime } from \"./core/agent-session-runtime.js\";\nimport {\n\ttype AgentSessionRuntimeDiagnostic,\n\tcreateAgentSessionFromServices,\n\tcreateAgentSessionServices,\n} from \"./core/agent-session-services.js\";\nimport { formatNoModelsAvailableMessage } from \"./core/auth-guidance.js\";\nimport { AuthStorage } from \"./core/auth-storage.js\";\nimport { exportFromFile } from \"./core/export-html/index.js\";\nimport type { ExtensionAPI, ExtensionFactory } from \"./core/extensions/types.js\";\nimport { KeybindingsManager } from \"./core/keybindings.js\";\nimport { type ModelRegistry, validateInvocationModelDefinition } from \"./core/model-registry.js\";\nimport { resolveCliModel, resolveModelScope, type ScopedModel } from \"./core/model-resolver.js\";\nimport { restoreStdout, takeOverStdout } from \"./core/output-guard.js\";\nimport { isRequiredBuiltinInitError, renderRequiredBuiltinInitCause } from \"./core/resource-loader.js\";\nimport type { CreateAgentSessionOptions } from \"./core/sdk.js\";\nimport {\n\tformatMissingSessionCwdPrompt,\n\tgetMissingSessionCwdIssue,\n\tMissingSessionCwdError,\n\ttype SessionCwdIssue,\n} from \"./core/session-cwd.js\";\nimport { SessionManager } from \"./core/session-manager.js\";\nimport { SettingsManager } from \"./core/settings-manager.js\";\nimport { printTimings, resetTimings, time } from \"./core/timings.js\";\nimport { runMigrations, showDeprecationWarnings } from \"./migrations.js\";\nimport { InteractiveMode, runPrintMode, runRpcMode } from \"./modes/index.js\";\nimport { ExtensionSelectorComponent } from \"./modes/interactive/components/extension-selector.js\";\nimport { initTheme, stopThemeWatcher } from \"./modes/interactive/theme/theme.js\";\nimport { handleConfigCommand, handlePackageCommand } from \"./package-manager-cli.js\";\nimport { isLocalPath } from \"./utils/paths.js\";\n\n/**\n * Read all content from piped stdin.\n * Returns undefined if stdin is a TTY (interactive terminal).\n */\nasync function readPipedStdin(): Promise<string | undefined> {\n\t// If stdin is a TTY, we're running interactively - don't read stdin\n\tif (process.stdin.isTTY) {\n\t\treturn undefined;\n\t}\n\n\treturn new Promise((resolve) => {\n\t\tlet data = \"\";\n\t\tprocess.stdin.setEncoding(\"utf8\");\n\t\tprocess.stdin.on(\"data\", (chunk) => {\n\t\t\tdata += chunk;\n\t\t});\n\t\tprocess.stdin.on(\"end\", () => {\n\t\t\tresolve(normalizePipedStdinContent(data));\n\t\t});\n\t\tprocess.stdin.resume();\n\t});\n}\n\nfunction collectSettingsDiagnostics(\n\tsettingsManager: SettingsManager,\n\tcontext: string,\n): AgentSessionRuntimeDiagnostic[] {\n\treturn settingsManager.drainErrors().map(({ scope, error }) => ({\n\t\ttype: \"warning\",\n\t\tmessage: `(${context}, ${scope} settings) ${error.message}`,\n\t}));\n}\n\nfunction reportDiagnostics(diagnostics: readonly AgentSessionRuntimeDiagnostic[]): void {\n\tfor (const diagnostic of diagnostics) {\n\t\tconst color = diagnostic.type === \"error\" ? chalk.red : diagnostic.type === \"warning\" ? chalk.yellow : chalk.dim;\n\t\tconst prefix = diagnostic.type === \"error\" ? \"Error: \" : diagnostic.type === \"warning\" ? \"Warning: \" : \"\";\n\t\tconsole.error(color(`${prefix}${diagnostic.message}`));\n\t}\n}\n\nfunction isTruthyEnvFlag(value: string | undefined): boolean {\n\tif (!value) return false;\n\treturn value === \"1\" || value.toLowerCase() === \"true\" || value.toLowerCase() === \"yes\";\n}\n\ntype AppMode = \"interactive\" | \"print\" | \"json\" | \"rpc\";\n\n// SCRAMJET-DIVERGENCE: Resolve strict invocation inputs before runtime construction, without secret-bearing errors.\nexport function resolveInvocationModelDefinition(\n\tparsed: Args,\n\tstdinIsTTY: boolean,\n\tenv: NodeJS.ProcessEnv = process.env,\n) {\n\tif (parsed.help || parsed.version) return undefined;\n\tif (parsed.diagnostics.some((diagnostic) => diagnostic.type === \"error\")) {\n\t\tthrow new Error(\"--model-definition/--model-definition-env: correct the selector diagnostics before continuing.\");\n\t}\n\tif (parsed.modelDefinition === undefined && parsed.modelDefinitionEnv === undefined) return undefined;\n\tif (parsed.listModels === undefined && resolveAppMode(parsed, stdinIsTTY) === \"interactive\") {\n\t\tthrow new Error(\n\t\t\t\"--model-definition/--model-definition-env requires headless execution; use --print, --mode json, --mode rpc or redirected stdin.\",\n\t\t);\n\t}\n\tfunction readEnvironment(name: string, field: string): string {\n\t\tif (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {\n\t\t\tthrow new Error(`${field}: supply a valid environment variable identifier.`);\n\t\t}\n\t\tconst value = env[name];\n\t\tif (!value?.trim()) throw new Error(`${field}: set the named environment variable to a nonblank value.`);\n\t\treturn value;\n\t}\n\tconst json = parsed.modelDefinition ?? readEnvironment(parsed.modelDefinitionEnv!, \"--model-definition-env\");\n\tlet value: unknown;\n\ttry {\n\t\tvalue = JSON.parse(json);\n\t} catch {\n\t\tthrow new Error(\n\t\t\t\"--model-definition/--model-definition-env: supply strict JSON without comments or trailing commas.\",\n\t\t);\n\t}\n\tlet definition: ReturnType<typeof validateInvocationModelDefinition>;\n\ttry {\n\t\tdefinition = validateInvocationModelDefinition(value);\n\t} catch (error) {\n\t\tthrow new Error(`--model-definition/--model-definition-env: ${(error as Error).message}`);\n\t}\n\tconst apiKey = parsed.listModels === undefined ? readEnvironment(definition.apiKeyEnv, \"apiKeyEnv\") : undefined;\n\treturn { definition, apiKey };\n}\n\n// SCRAMJET-DIVERGENCE: Install captured invocation routes after extension registrations, without storing secrets in models.\nexport function registerInvocationModel(\n\tinvocation: NonNullable<ReturnType<typeof resolveInvocationModelDefinition>>,\n\tprovider: string,\n\tmodelRegistry: ModelRegistry,\n\tauthStorage: AuthStorage,\n): Model<any> {\n\tif (modelRegistry.getAll().some((model) => model.provider === provider)) {\n\t\tthrow new Error(\"--model-definition: generated provider identity collided; retry the invocation.\");\n\t}\n\tconst { api, baseUrl, apiKeyEnv, ...model } = invocation.definition;\n\tmodelRegistry.registerProvider(provider, { api, baseUrl, apiKey: apiKeyEnv, models: [model] });\n\tif (invocation.apiKey !== undefined) authStorage.setRuntimeApiKey(provider, invocation.apiKey);\n\tconst registered = modelRegistry.find(provider, model.id);\n\tif (!registered) throw new Error(\"--model-definition: registration failed; correct the definition before retrying.\");\n\treturn registered;\n}\n\nfunction resolveAppMode(parsed: Args, stdinIsTTY: boolean): AppMode {\n\tif (parsed.mode === \"rpc\") {\n\t\treturn \"rpc\";\n\t}\n\tif (parsed.mode === \"json\") {\n\t\treturn \"json\";\n\t}\n\tif (parsed.print || !stdinIsTTY) {\n\t\treturn \"print\";\n\t}\n\treturn \"interactive\";\n}\n\nfunction toPrintOutputMode(appMode: AppMode): Exclude<Mode, \"rpc\"> {\n\treturn appMode === \"json\" ? \"json\" : \"text\";\n}\n\nasync function prepareInitialMessage(\n\tparsed: Args,\n\tautoResizeImages: boolean,\n\tstdinContent?: string,\n): Promise<{\n\tinitialMessage?: string;\n\tinitialImages?: ImageContent[];\n}> {\n\tif (parsed.fileArgs.length === 0) {\n\t\treturn buildInitialMessage({ parsed, stdinContent });\n\t}\n\n\tconst { text, images } = await processFileArguments(parsed.fileArgs, { autoResizeImages });\n\treturn buildInitialMessage({\n\t\tparsed,\n\t\tfileText: text,\n\t\tfileImages: images,\n\t\tstdinContent,\n\t});\n}\n\n/** Result from resolving a session argument */\ntype ResolvedSession =\n\t| { type: \"path\"; path: string } // Direct file path\n\t| { type: \"local\"; path: string } // Found in current project\n\t| { type: \"global\"; path: string; cwd: string } // Found in different project\n\t| { type: \"not_found\"; arg: string }; // Not found anywhere\n\n/**\n * Resolve a session argument to a file path.\n * If it looks like a path, use as-is. Otherwise try to match as session ID prefix.\n */\nasync function resolveSessionPath(sessionArg: string, cwd: string, sessionDir?: string): Promise<ResolvedSession> {\n\t// If it looks like a file path, use as-is\n\tif (sessionArg.includes(\"/\") || sessionArg.includes(\"\\\\\") || sessionArg.endsWith(\".jsonl\")) {\n\t\treturn { type: \"path\", path: sessionArg };\n\t}\n\n\t// Try to match as session ID in current project first\n\tconst localSessions = await SessionManager.list(cwd, sessionDir);\n\tconst localMatches = localSessions.filter((s) => s.id.startsWith(sessionArg));\n\n\tif (localMatches.length >= 1) {\n\t\treturn { type: \"local\", path: localMatches[0].path };\n\t}\n\n\t// Try global search across all projects\n\tconst allSessions = await SessionManager.listAll();\n\tconst globalMatches = allSessions.filter((s) => s.id.startsWith(sessionArg));\n\n\tif (globalMatches.length >= 1) {\n\t\tconst match = globalMatches[0];\n\t\treturn { type: \"global\", path: match.path, cwd: match.cwd };\n\t}\n\n\t// Not found anywhere\n\treturn { type: \"not_found\", arg: sessionArg };\n}\n\n/** Prompt user for yes/no confirmation */\nasync function promptConfirm(message: string): Promise<boolean> {\n\treturn new Promise((resolve) => {\n\t\tconst rl = createInterface({\n\t\t\tinput: process.stdin,\n\t\t\toutput: process.stdout,\n\t\t});\n\t\trl.question(`${message} [y/N] `, (answer) => {\n\t\t\trl.close();\n\t\t\tresolve(answer.toLowerCase() === \"y\" || answer.toLowerCase() === \"yes\");\n\t\t});\n\t});\n}\n\nfunction validateForkFlags(parsed: Args): void {\n\tif (!parsed.fork) return;\n\n\tconst conflictingFlags = [\n\t\tparsed.session ? \"--session\" : undefined,\n\t\tparsed.continue ? \"--continue\" : undefined,\n\t\tparsed.resume ? \"--resume\" : undefined,\n\t\tparsed.noSession ? \"--no-session\" : undefined,\n\t].filter((flag): flag is string => flag !== undefined);\n\n\tif (conflictingFlags.length > 0) {\n\t\tconsole.error(chalk.red(`Error: --fork cannot be combined with ${conflictingFlags.join(\", \")}`));\n\t\tprocess.exit(1);\n\t}\n}\n\nfunction forkSessionOrExit(sourcePath: string, cwd: string, sessionDir?: string): SessionManager {\n\ttry {\n\t\treturn SessionManager.forkFrom(sourcePath, cwd, sessionDir);\n\t} catch (error: unknown) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tconsole.error(chalk.red(`Error: ${message}`));\n\t\tprocess.exit(1);\n\t}\n}\n\nasync function createSessionManager(\n\tparsed: Args,\n\tcwd: string,\n\tsessionDir: string | undefined,\n\tsettingsManager: SettingsManager,\n): Promise<SessionManager> {\n\tif (parsed.noSession) {\n\t\treturn SessionManager.inMemory();\n\t}\n\n\tif (parsed.fork) {\n\t\tconst resolved = await resolveSessionPath(parsed.fork, cwd, sessionDir);\n\n\t\tswitch (resolved.type) {\n\t\t\tcase \"path\":\n\t\t\tcase \"local\":\n\t\t\tcase \"global\":\n\t\t\t\treturn forkSessionOrExit(resolved.path, cwd, sessionDir);\n\n\t\t\tcase \"not_found\":\n\t\t\t\tconsole.error(chalk.red(`No session found matching '${resolved.arg}'`));\n\t\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\tif (parsed.session) {\n\t\tconst resolved = await resolveSessionPath(parsed.session, cwd, sessionDir);\n\n\t\tswitch (resolved.type) {\n\t\t\tcase \"path\":\n\t\t\tcase \"local\":\n\t\t\t\treturn SessionManager.open(resolved.path, sessionDir);\n\n\t\t\tcase \"global\": {\n\t\t\t\tconsole.log(chalk.yellow(`Session found in different project: ${resolved.cwd}`));\n\t\t\t\tconst shouldFork = await promptConfirm(\"Fork this session into current directory?\");\n\t\t\t\tif (!shouldFork) {\n\t\t\t\t\tconsole.log(chalk.dim(\"Aborted.\"));\n\t\t\t\t\tprocess.exit(0);\n\t\t\t\t}\n\t\t\t\treturn forkSessionOrExit(resolved.path, cwd, sessionDir);\n\t\t\t}\n\n\t\t\tcase \"not_found\":\n\t\t\t\tconsole.error(chalk.red(`No session found matching '${resolved.arg}'`));\n\t\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\tif (parsed.resume) {\n\t\tinitTheme(settingsManager.getTheme(), true);\n\t\ttry {\n\t\t\tconst selectedPath = await selectSession(\n\t\t\t\t(onProgress) => SessionManager.list(cwd, sessionDir, onProgress),\n\t\t\t\tSessionManager.listAll,\n\t\t\t);\n\t\t\tif (!selectedPath) {\n\t\t\t\tconsole.log(chalk.dim(\"No session selected\"));\n\t\t\t\tprocess.exit(0);\n\t\t\t}\n\t\t\treturn SessionManager.open(selectedPath, sessionDir);\n\t\t} finally {\n\t\t\tstopThemeWatcher();\n\t\t}\n\t}\n\n\tif (parsed.continue) {\n\t\treturn SessionManager.continueRecent(cwd, sessionDir);\n\t}\n\n\treturn SessionManager.create(cwd, sessionDir);\n}\n\n// SCRAMJET-DIVERGENCE: exported for direct unit testing (issue 186).\nexport function buildSessionOptions(\n\tparsed: Args,\n\tscopedModels: ScopedModel[],\n\thasExistingSession: boolean,\n\tmodelRegistry: ModelRegistry,\n\tsettingsManager: SettingsManager,\n\tinherited?: { model: Model<any>; thinkingLevel: ThinkingLevel },\n\tinvocationModel?: Model<any>,\n): {\n\toptions: CreateAgentSessionOptions;\n\tcliThinkingFromModel: boolean;\n\tdiagnostics: AgentSessionRuntimeDiagnostic[];\n} {\n\tconst options: CreateAgentSessionOptions = {};\n\tconst diagnostics: AgentSessionRuntimeDiagnostic[] = [];\n\tlet cliThinkingFromModel = false;\n\n\t// SCRAMJET-DIVERGENCE: inherited model/thinkingLevel from the previous session takes highest\n\t// precedence, above CLI --model and scoped models. See issue 186.\n\tif (inherited) {\n\t\toptions.model = inherited.model;\n\t\toptions.thinkingLevel = inherited.thinkingLevel;\n\t}\n\n\t// SCRAMJET-DIVERGENCE: Invocation selection bypasses ordinary resolution and saved startup scopes.\n\tif (invocationModel) {\n\t\toptions.model ??= invocationModel;\n\t\toptions.persistModelPreferences = false;\n\t}\n\n\t// Model from CLI\n\t// - supports --provider <name> --model <pattern>\n\t// - supports --model <provider>/<pattern>\n\tif (!options.model && parsed.model) {\n\t\tconst resolved = resolveCliModel({\n\t\t\tcliProvider: parsed.provider,\n\t\t\tcliModel: parsed.model,\n\t\t\tmodelRegistry,\n\t\t});\n\t\tif (resolved.warning) {\n\t\t\tdiagnostics.push({ type: \"warning\", message: resolved.warning });\n\t\t}\n\t\tif (resolved.error) {\n\t\t\tdiagnostics.push({ type: \"error\", message: resolved.error });\n\t\t}\n\t\tif (resolved.model) {\n\t\t\toptions.model = resolved.model;\n\t\t\t// Allow \"--model <pattern>:<thinking>\" as a shorthand.\n\t\t\t// Explicit --thinking still takes precedence (applied later).\n\t\t\tif (!parsed.thinking && resolved.thinkingLevel) {\n\t\t\t\toptions.thinkingLevel = resolved.thinkingLevel;\n\t\t\t\tcliThinkingFromModel = true;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!options.model && scopedModels.length > 0 && !hasExistingSession) {\n\t\t// Check if saved default is in scoped models - use it if so, otherwise first scoped model\n\t\tconst savedProvider = settingsManager.getDefaultProvider();\n\t\tconst savedModelId = settingsManager.getDefaultModel();\n\t\tconst savedModel = savedProvider && savedModelId ? modelRegistry.find(savedProvider, savedModelId) : undefined;\n\t\tconst savedInScope = savedModel ? scopedModels.find((sm) => modelsAreEqual(sm.model, savedModel)) : undefined;\n\n\t\tif (savedInScope) {\n\t\t\toptions.model = savedInScope.model;\n\t\t\t// Use thinking level from scoped model config if explicitly set\n\t\t\tif (!parsed.thinking && savedInScope.thinkingLevel) {\n\t\t\t\toptions.thinkingLevel = savedInScope.thinkingLevel;\n\t\t\t}\n\t\t} else {\n\t\t\toptions.model = scopedModels[0].model;\n\t\t\t// Use thinking level from first scoped model if explicitly set\n\t\t\tif (!parsed.thinking && scopedModels[0].thinkingLevel) {\n\t\t\t\toptions.thinkingLevel = scopedModels[0].thinkingLevel;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Thinking level from CLI (takes precedence over scoped model thinking levels set above,\n\t// but NOT over inherited thinking level — SCRAMJET-DIVERGENCE: issue 186)\n\tif (options.thinkingLevel === undefined && parsed.thinking) {\n\t\toptions.thinkingLevel = parsed.thinking;\n\t}\n\n\t// Cache retention from CLI (PI_CACHE_RETENTION and the default are resolved by the SDK)\n\tif (parsed.cacheRetention) {\n\t\toptions.cacheRetention = parsed.cacheRetention;\n\t}\n\n\t// Scoped models for Ctrl+P cycling\n\t// Keep thinking level undefined when not explicitly set in the model pattern.\n\t// Undefined means \"inherit current session thinking level\" during cycling.\n\tif (scopedModels.length > 0) {\n\t\toptions.scopedModels = scopedModels.map((sm) => ({\n\t\t\tmodel: sm.model,\n\t\t\tthinkingLevel: sm.thinkingLevel,\n\t\t}));\n\t}\n\n\t// API key from CLI - set in authStorage\n\t// (handled by caller before createAgentSession)\n\n\t// Tools\n\tif (parsed.noTools) {\n\t\toptions.noTools = \"all\";\n\t} else if (parsed.noBuiltinTools) {\n\t\toptions.noTools = \"builtin\";\n\t}\n\tif (parsed.tools) {\n\t\toptions.tools = [...parsed.tools];\n\t}\n\n\treturn { options, cliThinkingFromModel, diagnostics };\n}\n\nfunction resolveCliPaths(cwd: string, paths: string[] | undefined): string[] | undefined {\n\treturn paths?.map((value) => (isLocalPath(value) ? resolve(cwd, value) : value));\n}\n\nasync function promptForMissingSessionCwd(\n\tissue: SessionCwdIssue,\n\tsettingsManager: SettingsManager,\n): Promise<string | undefined> {\n\tinitTheme(settingsManager.getTheme());\n\tsetKeybindings(KeybindingsManager.create());\n\n\treturn new Promise((resolve) => {\n\t\t// SCRAMJET-DIVERGENCE: shrink recovery is mandatory renderer behavior; no legacy toggle is applied (#389).\n\t\tconst ui = new TUI(new ProcessTerminal(), settingsManager.getShowHardwareCursor());\n\n\t\tlet settled = false;\n\t\tconst finish = (result: string | undefined) => {\n\t\t\tif (settled) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsettled = true;\n\t\t\tui.stop();\n\t\t\tresolve(result);\n\t\t};\n\n\t\tconst selector = new ExtensionSelectorComponent(\n\t\t\tformatMissingSessionCwdPrompt(issue),\n\t\t\t[\"Continue\", \"Cancel\"],\n\t\t\t(option) => finish(option === \"Continue\" ? issue.fallbackCwd : undefined),\n\t\t\t() => finish(undefined),\n\t\t\t{ tui: ui },\n\t\t);\n\t\tui.addChild(selector);\n\t\tui.setFocus(selector);\n\t\tui.start();\n\t});\n}\n\n// SCRAMJET-DIVERGENCE: added builtinInit for direct product wiring without extension discovery.\nexport interface MainOptions {\n\textensionFactories?: ExtensionFactory[];\n\tbuiltinInit?: (pi: ExtensionAPI) => void | Promise<void>;\n}\n\nexport async function main(args: string[], options?: MainOptions) {\n\tresetTimings();\n\t// SCRAMJET-DIVERGENCE: prefer SCRAMJET_OFFLINE, fall back to PI_OFFLINE.\n\tconst offlineMode =\n\t\targs.includes(\"--offline\") ||\n\t\tisTruthyEnvFlag(process.env.SCRAMJET_OFFLINE) ||\n\t\tisTruthyEnvFlag(process.env.PI_OFFLINE);\n\tif (offlineMode) {\n\t\tprocess.env.SCRAMJET_OFFLINE = \"1\";\n\t\tprocess.env.PI_OFFLINE = \"1\";\n\t\tprocess.env.PI_SKIP_VERSION_CHECK = \"1\";\n\t}\n\n\tif (await handlePackageCommand(args)) {\n\t\treturn;\n\t}\n\n\tif (await handleConfigCommand(args)) {\n\t\treturn;\n\t}\n\n\tconst parsed = parseArgs(args);\n\tif (parsed.diagnostics.length > 0) {\n\t\tfor (const d of parsed.diagnostics) {\n\t\t\tconst color = d.type === \"error\" ? chalk.red : chalk.yellow;\n\t\t\tconsole.error(color(`${d.type === \"error\" ? \"Error\" : \"Warning\"}: ${d.message}`));\n\t\t}\n\t\tif (parsed.diagnostics.some((d) => d.type === \"error\")) {\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\ttime(\"parseArgs\");\n\tlet appMode = resolveAppMode(parsed, process.stdin.isTTY);\n\tconst shouldTakeOverStdout = appMode !== \"interactive\";\n\tif (shouldTakeOverStdout) {\n\t\ttakeOverStdout();\n\t}\n\n\tif (parsed.version) {\n\t\tconsole.log(VERSION);\n\t\tprocess.exit(0);\n\t}\n\n\tlet invocation: ReturnType<typeof resolveInvocationModelDefinition>;\n\ttry {\n\t\tinvocation = resolveInvocationModelDefinition(parsed, process.stdin.isTTY);\n\t} catch (error) {\n\t\tconsole.error(chalk.red(`Error: ${(error as Error).message}`));\n\t\tprocess.exit(1);\n\t}\n\n\tif (parsed.export) {\n\t\tlet result: string;\n\t\ttry {\n\t\t\tconst outputPath = parsed.messages.length > 0 ? parsed.messages[0] : undefined;\n\t\t\tresult = await exportFromFile(parsed.export, outputPath);\n\t\t} catch (error: unknown) {\n\t\t\tconst message = error instanceof Error ? error.message : \"Failed to export session\";\n\t\t\tconsole.error(chalk.red(`Error: ${message}`));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tconsole.log(`Exported to: ${result}`);\n\t\tprocess.exit(0);\n\t}\n\n\tif (parsed.mode === \"rpc\" && parsed.fileArgs.length > 0) {\n\t\tconsole.error(chalk.red(\"Error: @file arguments are not supported in RPC mode\"));\n\t\tprocess.exit(1);\n\t}\n\n\tvalidateForkFlags(parsed);\n\n\t// Run migrations (pass cwd for project-local migrations)\n\tconst { migratedAuthProviders: migratedProviders, deprecationWarnings } = runMigrations(process.cwd());\n\ttime(\"runMigrations\");\n\n\tconst cwd = process.cwd();\n\tconst agentDir = getAgentDir();\n\tconst startupSettingsManager = SettingsManager.create(cwd, agentDir);\n\treportDiagnostics(collectSettingsDiagnostics(startupSettingsManager, \"startup session lookup\"));\n\n\t// Decide the final runtime cwd before creating cwd-bound runtime services.\n\t// --session and --resume may select a session from another project, so project-local\n\t// settings, resources, provider registrations, and models must be resolved only after\n\t// the target session cwd is known. The startup-cwd settings manager is used only for\n\t// sessionDir lookup during session selection.\n\tconst envSessionDir = process.env[ENV_SESSION_DIR];\n\tconst sessionDir =\n\t\tparsed.sessionDir ??\n\t\t(envSessionDir ? expandTildePath(envSessionDir) : undefined) ??\n\t\tstartupSettingsManager.getSessionDir();\n\tlet sessionManager = await createSessionManager(parsed, cwd, sessionDir, startupSettingsManager);\n\tconst missingSessionCwdIssue = getMissingSessionCwdIssue(sessionManager, cwd);\n\tif (missingSessionCwdIssue) {\n\t\tif (appMode === \"interactive\") {\n\t\t\tconst selectedCwd = await promptForMissingSessionCwd(missingSessionCwdIssue, startupSettingsManager);\n\t\t\tif (!selectedCwd) {\n\t\t\t\tprocess.exit(0);\n\t\t\t}\n\t\t\tsessionManager = SessionManager.open(missingSessionCwdIssue.sessionFile!, sessionDir, selectedCwd);\n\t\t} else {\n\t\t\tconsole.error(chalk.red(new MissingSessionCwdError(missingSessionCwdIssue).message));\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\ttime(\"createSessionManager\");\n\n\tconst resolvedExtensionPaths = resolveCliPaths(cwd, parsed.extensions);\n\tconst resolvedSkillPaths = resolveCliPaths(cwd, parsed.skills);\n\tconst resolvedPromptTemplatePaths = resolveCliPaths(cwd, parsed.promptTemplates);\n\tconst resolvedThemePaths = resolveCliPaths(cwd, parsed.themes);\n\tconst authStorage = AuthStorage.create();\n\tconst invocationProvider = invocation ? `invocation-${randomUUID()}` : undefined;\n\tconst createRuntime: CreateAgentSessionRuntimeFactory = async ({\n\t\tcwd,\n\t\tagentDir,\n\t\tsessionManager,\n\t\tsessionStartEvent,\n\t\tinherited, // SCRAMJET-DIVERGENCE: forward inherited model/thinkingLevel (issue 186)\n\t}) => {\n\t\tconst services = await createAgentSessionServices({\n\t\t\tcwd,\n\t\t\tagentDir,\n\t\t\tauthStorage,\n\t\t\textensionFlagValues: parsed.unknownFlags,\n\t\t\tresourceLoaderOptions: {\n\t\t\t\tadditionalExtensionPaths: resolvedExtensionPaths,\n\t\t\t\tadditionalSkillPaths: resolvedSkillPaths,\n\t\t\t\tadditionalPromptTemplatePaths: resolvedPromptTemplatePaths,\n\t\t\t\tadditionalThemePaths: resolvedThemePaths,\n\t\t\t\tnoExtensions: parsed.noExtensions,\n\t\t\t\tnoSkills: parsed.noSkills,\n\t\t\t\tnoPromptTemplates: parsed.noPromptTemplates,\n\t\t\t\tnoThemes: parsed.noThemes,\n\t\t\t\tnoContextFiles: parsed.noContextFiles,\n\t\t\t\tsystemPrompt: parsed.systemPrompt,\n\t\t\t\tappendSystemPrompt: parsed.appendSystemPrompt,\n\t\t\t\textensionFactories: options?.extensionFactories,\n\t\t\t\tbuiltinInit: options?.builtinInit,\n\t\t\t},\n\t\t});\n\t\tconst { settingsManager, modelRegistry, resourceLoader } = services;\n\t\tconst diagnostics: AgentSessionRuntimeDiagnostic[] = [\n\t\t\t...services.diagnostics,\n\t\t\t...collectSettingsDiagnostics(settingsManager, \"runtime creation\"),\n\t\t\t...resourceLoader.getExtensions().errors.map(({ path, error }) => ({\n\t\t\t\ttype: \"error\" as const,\n\t\t\t\tmessage: `Failed to load extension \"${path}\": ${error}`,\n\t\t\t})),\n\t\t];\n\n\t\tconst invocationModel = invocation\n\t\t\t? registerInvocationModel(invocation, invocationProvider!, modelRegistry, authStorage)\n\t\t\t: undefined;\n\t\tconst modelPatterns = invocation ? undefined : (parsed.models ?? settingsManager.getEnabledModels());\n\t\tconst scopedModels =\n\t\t\tmodelPatterns && modelPatterns.length > 0 ? await resolveModelScope(modelPatterns, modelRegistry) : [];\n\t\tconst {\n\t\t\toptions: sessionOptions,\n\t\t\tcliThinkingFromModel,\n\t\t\tdiagnostics: sessionOptionDiagnostics,\n\t\t} = buildSessionOptions(\n\t\t\tparsed,\n\t\t\tscopedModels,\n\t\t\tsessionManager.buildSessionContext().messages.length > 0,\n\t\t\tmodelRegistry,\n\t\t\tsettingsManager,\n\t\t\tinherited, // SCRAMJET-DIVERGENCE: pass inherited model/thinkingLevel (issue 186)\n\t\t\tinvocationModel,\n\t\t);\n\t\tdiagnostics.push(...sessionOptionDiagnostics);\n\n\t\tif (parsed.apiKey) {\n\t\t\tif (!sessionOptions.model) {\n\t\t\t\tdiagnostics.push({\n\t\t\t\t\ttype: \"error\",\n\t\t\t\t\tmessage: \"--api-key requires a model to be specified via --model, --provider/--model, or --models\",\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tauthStorage.setRuntimeApiKey(sessionOptions.model.provider, parsed.apiKey);\n\t\t\t}\n\t\t}\n\n\t\tconst created = await createAgentSessionFromServices({\n\t\t\tservices,\n\t\t\tsessionManager,\n\t\t\tsessionStartEvent,\n\t\t\tmodel: sessionOptions.model,\n\t\t\tpersistModelPreferences: sessionOptions.persistModelPreferences,\n\t\t\tthinkingLevel: sessionOptions.thinkingLevel,\n\t\t\tscopedModels: sessionOptions.scopedModels,\n\t\t\tcacheRetention: sessionOptions.cacheRetention,\n\t\t\ttools: sessionOptions.tools,\n\t\t\tnoTools: sessionOptions.noTools,\n\t\t\tcustomTools: sessionOptions.customTools,\n\t\t});\n\t\tconst cliThinkingOverride = parsed.thinking !== undefined || cliThinkingFromModel;\n\t\tif (created.session.model && cliThinkingOverride) {\n\t\t\tcreated.session.setThinkingLevel(created.session.thinkingLevel);\n\t\t}\n\n\t\treturn {\n\t\t\t...created,\n\t\t\tservices,\n\t\t\tdiagnostics,\n\t\t};\n\t};\n\ttime(\"createRuntime\");\n\tconst runtime = await createAgentSessionRuntime(createRuntime, {\n\t\tcwd: sessionManager.getCwd(),\n\t\tagentDir,\n\t\tsessionManager,\n\t}).catch((error: unknown) => {\n\t\t// SCRAMJET-DIVERGENCE: a required Scramjet builtin failure during candidate preparation aborts startup\n\t\t// before any productless AgentSession or session_start. Surface it as a fatal, product-attributed error\n\t\t// (retaining the original cause) with a non-zero exit, never an anonymous crash or the optional-extension\n\t\t// diagnostic path.\n\t\tif (isRequiredBuiltinInitError(error)) {\n\t\t\tconsole.error(chalk.red(`Error: ${error.message}`));\n\t\t\tconst detail = renderRequiredBuiltinInitCause(error);\n\t\t\tif (detail !== undefined) {\n\t\t\t\tconsole.error(chalk.dim(detail));\n\t\t\t}\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tthrow error;\n\t});\n\tconst { services, session, modelFallbackMessage } = runtime;\n\tconst { settingsManager, modelRegistry, resourceLoader } = services;\n\n\tif (parsed.help) {\n\t\tconst extensionFlags = resourceLoader\n\t\t\t.getExtensions()\n\t\t\t.extensions.flatMap((extension) => Array.from(extension.flags.values()));\n\t\tprintHelp(extensionFlags);\n\t\tprocess.exit(0);\n\t}\n\n\tif (parsed.listModels !== undefined) {\n\t\tconst searchPattern = typeof parsed.listModels === \"string\" ? parsed.listModels : undefined;\n\t\tawait listModels(modelRegistry, searchPattern);\n\t\tprocess.exit(0);\n\t}\n\n\t// Read piped stdin content (if any) - skip for RPC mode which uses stdin for JSON-RPC\n\tlet stdinContent: string | undefined;\n\tif (appMode !== \"rpc\") {\n\t\tstdinContent = await readPipedStdin();\n\t\tif (stdinContent !== undefined && appMode === \"interactive\") {\n\t\t\tappMode = \"print\";\n\t\t}\n\t}\n\ttime(\"readPipedStdin\");\n\n\tconst { initialMessage, initialImages } = await prepareInitialMessage(\n\t\tparsed,\n\t\tsettingsManager.getImageAutoResize(),\n\t\tstdinContent,\n\t);\n\ttime(\"prepareInitialMessage\");\n\tinitTheme(settingsManager.getTheme(), appMode === \"interactive\");\n\ttime(\"initTheme\");\n\n\t// Show deprecation warnings in interactive mode\n\tif (appMode === \"interactive\" && deprecationWarnings.length > 0) {\n\t\tawait showDeprecationWarnings(deprecationWarnings);\n\t}\n\n\ttime(\"resolveModelScope\");\n\treportDiagnostics(runtime.diagnostics);\n\tif (runtime.diagnostics.some((diagnostic) => diagnostic.type === \"error\")) {\n\t\tprocess.exit(1);\n\t}\n\ttime(\"createAgentSession\");\n\n\t// SCRAMJET-DIVERGENCE: Headless fallback warnings go to stderr, never JSON/RPC stdout.\n\tif (appMode !== \"interactive\" && !session.model) {\n\t\tconsole.error(chalk.red(modelFallbackMessage ?? formatNoModelsAvailableMessage()));\n\t\tprocess.exit(1);\n\t}\n\tif (appMode !== \"interactive\" && modelFallbackMessage) {\n\t\tconsole.error(chalk.yellow(`Warning: ${modelFallbackMessage}`));\n\t}\n\n\tconst startupBenchmark = isTruthyEnvFlag(process.env.PI_STARTUP_BENCHMARK);\n\tif (startupBenchmark && appMode !== \"interactive\") {\n\t\tconsole.error(chalk.red(\"Error: PI_STARTUP_BENCHMARK only supports interactive mode\"));\n\t\tprocess.exit(1);\n\t}\n\n\tif (appMode === \"rpc\") {\n\t\tprintTimings();\n\t\tawait runRpcMode(runtime);\n\t} else if (appMode === \"interactive\") {\n\t\tconst interactiveMode = new InteractiveMode(runtime, {\n\t\t\tmigratedProviders,\n\t\t\tmodelFallbackMessage,\n\t\t\tinitialMessage,\n\t\t\tinitialImages,\n\t\t\tinitialMessages: parsed.messages,\n\t\t\tverbose: parsed.verbose,\n\t\t});\n\t\tif (startupBenchmark) {\n\t\t\tawait interactiveMode.init();\n\t\t\ttime(\"interactiveMode.init\");\n\t\t\tprintTimings();\n\t\t\tinteractiveMode.stop();\n\t\t\tstopThemeWatcher();\n\t\t\tif (process.stdout.writableLength > 0) {\n\t\t\t\tawait new Promise<void>((resolve) => process.stdout.once(\"drain\", resolve));\n\t\t\t}\n\t\t\tif (process.stderr.writableLength > 0) {\n\t\t\t\tawait new Promise<void>((resolve) => process.stderr.once(\"drain\", resolve));\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tprintTimings();\n\t\tawait interactiveMode.run();\n\t} else {\n\t\tprintTimings();\n\t\tconst exitCode = await runPrintMode(runtime, {\n\t\t\tmode: toPrintOutputMode(appMode),\n\t\t\tmessages: parsed.messages,\n\t\t\tinitialMessage,\n\t\t\tinitialImages,\n\t\t});\n\t\tstopThemeWatcher();\n\t\trestoreStdout();\n\t\tif (exitCode !== 0) {\n\t\t\tprocess.exitCode = exitCode;\n\t\t}\n\t\treturn;\n\t}\n}\n"]}
|
package/dist/main.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* This file handles CLI argument parsing and translates them into
|
|
5
5
|
* createAgentSession() options. The SDK does the heavy lifting.
|
|
6
6
|
*/
|
|
7
|
+
import { randomUUID } from "node:crypto";
|
|
7
8
|
import { resolve } from "node:path";
|
|
8
9
|
import { createInterface } from "node:readline";
|
|
9
10
|
import { modelsAreEqual } from "@leanandmean/ai";
|
|
@@ -11,7 +12,7 @@ import { ProcessTerminal, setKeybindings, TUI } from "@leanandmean/tui";
|
|
|
11
12
|
import chalk from "chalk";
|
|
12
13
|
import { parseArgs, printHelp } from "./cli/args.js";
|
|
13
14
|
import { processFileArguments } from "./cli/file-processor.js";
|
|
14
|
-
import { buildInitialMessage } from "./cli/initial-message.js";
|
|
15
|
+
import { buildInitialMessage, normalizePipedStdinContent } from "./cli/initial-message.js";
|
|
15
16
|
import { listModels } from "./cli/list-models.js";
|
|
16
17
|
import { selectSession } from "./cli/session-picker.js";
|
|
17
18
|
import { ENV_SESSION_DIR, expandTildePath, getAgentDir, VERSION } from "./config.js";
|
|
@@ -21,8 +22,10 @@ import { formatNoModelsAvailableMessage } from "./core/auth-guidance.js";
|
|
|
21
22
|
import { AuthStorage } from "./core/auth-storage.js";
|
|
22
23
|
import { exportFromFile } from "./core/export-html/index.js";
|
|
23
24
|
import { KeybindingsManager } from "./core/keybindings.js";
|
|
25
|
+
import { validateInvocationModelDefinition } from "./core/model-registry.js";
|
|
24
26
|
import { resolveCliModel, resolveModelScope } from "./core/model-resolver.js";
|
|
25
27
|
import { restoreStdout, takeOverStdout } from "./core/output-guard.js";
|
|
28
|
+
import { isRequiredBuiltinInitError, renderRequiredBuiltinInitCause } from "./core/resource-loader.js";
|
|
26
29
|
import { formatMissingSessionCwdPrompt, getMissingSessionCwdIssue, MissingSessionCwdError, } from "./core/session-cwd.js";
|
|
27
30
|
import { SessionManager } from "./core/session-manager.js";
|
|
28
31
|
import { SettingsManager } from "./core/settings-manager.js";
|
|
@@ -49,7 +52,7 @@ async function readPipedStdin() {
|
|
|
49
52
|
data += chunk;
|
|
50
53
|
});
|
|
51
54
|
process.stdin.on("end", () => {
|
|
52
|
-
resolve(data
|
|
55
|
+
resolve(normalizePipedStdinContent(data));
|
|
53
56
|
});
|
|
54
57
|
process.stdin.resume();
|
|
55
58
|
});
|
|
@@ -72,6 +75,59 @@ function isTruthyEnvFlag(value) {
|
|
|
72
75
|
return false;
|
|
73
76
|
return value === "1" || value.toLowerCase() === "true" || value.toLowerCase() === "yes";
|
|
74
77
|
}
|
|
78
|
+
// SCRAMJET-DIVERGENCE: Resolve strict invocation inputs before runtime construction, without secret-bearing errors.
|
|
79
|
+
export function resolveInvocationModelDefinition(parsed, stdinIsTTY, env = process.env) {
|
|
80
|
+
if (parsed.help || parsed.version)
|
|
81
|
+
return undefined;
|
|
82
|
+
if (parsed.diagnostics.some((diagnostic) => diagnostic.type === "error")) {
|
|
83
|
+
throw new Error("--model-definition/--model-definition-env: correct the selector diagnostics before continuing.");
|
|
84
|
+
}
|
|
85
|
+
if (parsed.modelDefinition === undefined && parsed.modelDefinitionEnv === undefined)
|
|
86
|
+
return undefined;
|
|
87
|
+
if (parsed.listModels === undefined && resolveAppMode(parsed, stdinIsTTY) === "interactive") {
|
|
88
|
+
throw new Error("--model-definition/--model-definition-env requires headless execution; use --print, --mode json, --mode rpc or redirected stdin.");
|
|
89
|
+
}
|
|
90
|
+
function readEnvironment(name, field) {
|
|
91
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
92
|
+
throw new Error(`${field}: supply a valid environment variable identifier.`);
|
|
93
|
+
}
|
|
94
|
+
const value = env[name];
|
|
95
|
+
if (!value?.trim())
|
|
96
|
+
throw new Error(`${field}: set the named environment variable to a nonblank value.`);
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
const json = parsed.modelDefinition ?? readEnvironment(parsed.modelDefinitionEnv, "--model-definition-env");
|
|
100
|
+
let value;
|
|
101
|
+
try {
|
|
102
|
+
value = JSON.parse(json);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
throw new Error("--model-definition/--model-definition-env: supply strict JSON without comments or trailing commas.");
|
|
106
|
+
}
|
|
107
|
+
let definition;
|
|
108
|
+
try {
|
|
109
|
+
definition = validateInvocationModelDefinition(value);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
throw new Error(`--model-definition/--model-definition-env: ${error.message}`);
|
|
113
|
+
}
|
|
114
|
+
const apiKey = parsed.listModels === undefined ? readEnvironment(definition.apiKeyEnv, "apiKeyEnv") : undefined;
|
|
115
|
+
return { definition, apiKey };
|
|
116
|
+
}
|
|
117
|
+
// SCRAMJET-DIVERGENCE: Install captured invocation routes after extension registrations, without storing secrets in models.
|
|
118
|
+
export function registerInvocationModel(invocation, provider, modelRegistry, authStorage) {
|
|
119
|
+
if (modelRegistry.getAll().some((model) => model.provider === provider)) {
|
|
120
|
+
throw new Error("--model-definition: generated provider identity collided; retry the invocation.");
|
|
121
|
+
}
|
|
122
|
+
const { api, baseUrl, apiKeyEnv, ...model } = invocation.definition;
|
|
123
|
+
modelRegistry.registerProvider(provider, { api, baseUrl, apiKey: apiKeyEnv, models: [model] });
|
|
124
|
+
if (invocation.apiKey !== undefined)
|
|
125
|
+
authStorage.setRuntimeApiKey(provider, invocation.apiKey);
|
|
126
|
+
const registered = modelRegistry.find(provider, model.id);
|
|
127
|
+
if (!registered)
|
|
128
|
+
throw new Error("--model-definition: registration failed; correct the definition before retrying.");
|
|
129
|
+
return registered;
|
|
130
|
+
}
|
|
75
131
|
function resolveAppMode(parsed, stdinIsTTY) {
|
|
76
132
|
if (parsed.mode === "rpc") {
|
|
77
133
|
return "rpc";
|
|
@@ -216,14 +272,26 @@ async function createSessionManager(parsed, cwd, sessionDir, settingsManager) {
|
|
|
216
272
|
}
|
|
217
273
|
return SessionManager.create(cwd, sessionDir);
|
|
218
274
|
}
|
|
219
|
-
|
|
275
|
+
// SCRAMJET-DIVERGENCE: exported for direct unit testing (issue 186).
|
|
276
|
+
export function buildSessionOptions(parsed, scopedModels, hasExistingSession, modelRegistry, settingsManager, inherited, invocationModel) {
|
|
220
277
|
const options = {};
|
|
221
278
|
const diagnostics = [];
|
|
222
279
|
let cliThinkingFromModel = false;
|
|
280
|
+
// SCRAMJET-DIVERGENCE: inherited model/thinkingLevel from the previous session takes highest
|
|
281
|
+
// precedence, above CLI --model and scoped models. See issue 186.
|
|
282
|
+
if (inherited) {
|
|
283
|
+
options.model = inherited.model;
|
|
284
|
+
options.thinkingLevel = inherited.thinkingLevel;
|
|
285
|
+
}
|
|
286
|
+
// SCRAMJET-DIVERGENCE: Invocation selection bypasses ordinary resolution and saved startup scopes.
|
|
287
|
+
if (invocationModel) {
|
|
288
|
+
options.model ??= invocationModel;
|
|
289
|
+
options.persistModelPreferences = false;
|
|
290
|
+
}
|
|
223
291
|
// Model from CLI
|
|
224
292
|
// - supports --provider <name> --model <pattern>
|
|
225
293
|
// - supports --model <provider>/<pattern>
|
|
226
|
-
if (parsed.model) {
|
|
294
|
+
if (!options.model && parsed.model) {
|
|
227
295
|
const resolved = resolveCliModel({
|
|
228
296
|
cliProvider: parsed.provider,
|
|
229
297
|
cliModel: parsed.model,
|
|
@@ -266,8 +334,9 @@ function buildSessionOptions(parsed, scopedModels, hasExistingSession, modelRegi
|
|
|
266
334
|
}
|
|
267
335
|
}
|
|
268
336
|
}
|
|
269
|
-
// Thinking level from CLI (takes precedence over scoped model thinking levels set above
|
|
270
|
-
|
|
337
|
+
// Thinking level from CLI (takes precedence over scoped model thinking levels set above,
|
|
338
|
+
// but NOT over inherited thinking level — SCRAMJET-DIVERGENCE: issue 186)
|
|
339
|
+
if (options.thinkingLevel === undefined && parsed.thinking) {
|
|
271
340
|
options.thinkingLevel = parsed.thinking;
|
|
272
341
|
}
|
|
273
342
|
// Cache retention from CLI (PI_CACHE_RETENTION and the default are resolved by the SDK)
|
|
@@ -304,8 +373,8 @@ async function promptForMissingSessionCwd(issue, settingsManager) {
|
|
|
304
373
|
initTheme(settingsManager.getTheme());
|
|
305
374
|
setKeybindings(KeybindingsManager.create());
|
|
306
375
|
return new Promise((resolve) => {
|
|
376
|
+
// SCRAMJET-DIVERGENCE: shrink recovery is mandatory renderer behavior; no legacy toggle is applied (#389).
|
|
307
377
|
const ui = new TUI(new ProcessTerminal(), settingsManager.getShowHardwareCursor());
|
|
308
|
-
ui.setClearOnShrink(settingsManager.getClearOnShrink());
|
|
309
378
|
let settled = false;
|
|
310
379
|
const finish = (result) => {
|
|
311
380
|
if (settled) {
|
|
@@ -358,6 +427,14 @@ export async function main(args, options) {
|
|
|
358
427
|
console.log(VERSION);
|
|
359
428
|
process.exit(0);
|
|
360
429
|
}
|
|
430
|
+
let invocation;
|
|
431
|
+
try {
|
|
432
|
+
invocation = resolveInvocationModelDefinition(parsed, process.stdin.isTTY);
|
|
433
|
+
}
|
|
434
|
+
catch (error) {
|
|
435
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
|
436
|
+
process.exit(1);
|
|
437
|
+
}
|
|
361
438
|
if (parsed.export) {
|
|
362
439
|
let result;
|
|
363
440
|
try {
|
|
@@ -414,7 +491,9 @@ export async function main(args, options) {
|
|
|
414
491
|
const resolvedPromptTemplatePaths = resolveCliPaths(cwd, parsed.promptTemplates);
|
|
415
492
|
const resolvedThemePaths = resolveCliPaths(cwd, parsed.themes);
|
|
416
493
|
const authStorage = AuthStorage.create();
|
|
417
|
-
const
|
|
494
|
+
const invocationProvider = invocation ? `invocation-${randomUUID()}` : undefined;
|
|
495
|
+
const createRuntime = async ({ cwd, agentDir, sessionManager, sessionStartEvent, inherited, // SCRAMJET-DIVERGENCE: forward inherited model/thinkingLevel (issue 186)
|
|
496
|
+
}) => {
|
|
418
497
|
const services = await createAgentSessionServices({
|
|
419
498
|
cwd,
|
|
420
499
|
agentDir,
|
|
@@ -445,9 +524,13 @@ export async function main(args, options) {
|
|
|
445
524
|
message: `Failed to load extension "${path}": ${error}`,
|
|
446
525
|
})),
|
|
447
526
|
];
|
|
448
|
-
const
|
|
527
|
+
const invocationModel = invocation
|
|
528
|
+
? registerInvocationModel(invocation, invocationProvider, modelRegistry, authStorage)
|
|
529
|
+
: undefined;
|
|
530
|
+
const modelPatterns = invocation ? undefined : (parsed.models ?? settingsManager.getEnabledModels());
|
|
449
531
|
const scopedModels = modelPatterns && modelPatterns.length > 0 ? await resolveModelScope(modelPatterns, modelRegistry) : [];
|
|
450
|
-
const { options: sessionOptions, cliThinkingFromModel, diagnostics: sessionOptionDiagnostics, } = buildSessionOptions(parsed, scopedModels, sessionManager.buildSessionContext().messages.length > 0, modelRegistry, settingsManager)
|
|
532
|
+
const { options: sessionOptions, cliThinkingFromModel, diagnostics: sessionOptionDiagnostics, } = buildSessionOptions(parsed, scopedModels, sessionManager.buildSessionContext().messages.length > 0, modelRegistry, settingsManager, inherited, // SCRAMJET-DIVERGENCE: pass inherited model/thinkingLevel (issue 186)
|
|
533
|
+
invocationModel);
|
|
451
534
|
diagnostics.push(...sessionOptionDiagnostics);
|
|
452
535
|
if (parsed.apiKey) {
|
|
453
536
|
if (!sessionOptions.model) {
|
|
@@ -465,6 +548,7 @@ export async function main(args, options) {
|
|
|
465
548
|
sessionManager,
|
|
466
549
|
sessionStartEvent,
|
|
467
550
|
model: sessionOptions.model,
|
|
551
|
+
persistModelPreferences: sessionOptions.persistModelPreferences,
|
|
468
552
|
thinkingLevel: sessionOptions.thinkingLevel,
|
|
469
553
|
scopedModels: sessionOptions.scopedModels,
|
|
470
554
|
cacheRetention: sessionOptions.cacheRetention,
|
|
@@ -487,6 +571,20 @@ export async function main(args, options) {
|
|
|
487
571
|
cwd: sessionManager.getCwd(),
|
|
488
572
|
agentDir,
|
|
489
573
|
sessionManager,
|
|
574
|
+
}).catch((error) => {
|
|
575
|
+
// SCRAMJET-DIVERGENCE: a required Scramjet builtin failure during candidate preparation aborts startup
|
|
576
|
+
// before any productless AgentSession or session_start. Surface it as a fatal, product-attributed error
|
|
577
|
+
// (retaining the original cause) with a non-zero exit, never an anonymous crash or the optional-extension
|
|
578
|
+
// diagnostic path.
|
|
579
|
+
if (isRequiredBuiltinInitError(error)) {
|
|
580
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
|
581
|
+
const detail = renderRequiredBuiltinInitCause(error);
|
|
582
|
+
if (detail !== undefined) {
|
|
583
|
+
console.error(chalk.dim(detail));
|
|
584
|
+
}
|
|
585
|
+
process.exit(1);
|
|
586
|
+
}
|
|
587
|
+
throw error;
|
|
490
588
|
});
|
|
491
589
|
const { services, session, modelFallbackMessage } = runtime;
|
|
492
590
|
const { settingsManager, modelRegistry, resourceLoader } = services;
|
|
@@ -525,10 +623,14 @@ export async function main(args, options) {
|
|
|
525
623
|
process.exit(1);
|
|
526
624
|
}
|
|
527
625
|
time("createAgentSession");
|
|
626
|
+
// SCRAMJET-DIVERGENCE: Headless fallback warnings go to stderr, never JSON/RPC stdout.
|
|
528
627
|
if (appMode !== "interactive" && !session.model) {
|
|
529
|
-
console.error(chalk.red(formatNoModelsAvailableMessage()));
|
|
628
|
+
console.error(chalk.red(modelFallbackMessage ?? formatNoModelsAvailableMessage()));
|
|
530
629
|
process.exit(1);
|
|
531
630
|
}
|
|
631
|
+
if (appMode !== "interactive" && modelFallbackMessage) {
|
|
632
|
+
console.error(chalk.yellow(`Warning: ${modelFallbackMessage}`));
|
|
633
|
+
}
|
|
532
634
|
const startupBenchmark = isTruthyEnvFlag(process.env.PI_STARTUP_BENCHMARK);
|
|
533
635
|
if (startupBenchmark && appMode !== "interactive") {
|
|
534
636
|
console.error(chalk.red("Error: PI_STARTUP_BENCHMARK only supports interactive mode"));
|