pzero-operator 0.1.7 → 0.1.8
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/dist/core/agent-session.js +3 -3
- package/dist/core/keybindings.js +1 -0
- package/dist/core/sdk.js +3 -3
- package/dist/core/system-prompt.js +5 -1
- package/dist/core/tools/index.js +12 -2
- package/dist/core/tools/web-search.js +162 -0
- package/dist/migrations.js +32 -5
- package/dist/modes/interactive/components/footer.js +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +5 -6
- package/dist/modes/interactive/interactive-mode.js +351 -72
- package/node_modules/@operator/ai/dist/providers/openai-completions.js +6 -1
- package/package.json +1 -1
|
@@ -52,9 +52,9 @@ export function parseSkillBlock(text) {
|
|
|
52
52
|
// Constants
|
|
53
53
|
// ============================================================================
|
|
54
54
|
/** Standard thinking levels */
|
|
55
|
-
const THINKING_LEVELS = ["off", "
|
|
55
|
+
const THINKING_LEVELS = ["off", "low", "medium", "high"];
|
|
56
56
|
/** Thinking levels including xhigh (for supported models) */
|
|
57
|
-
const THINKING_LEVELS_WITH_XHIGH = ["off", "
|
|
57
|
+
const THINKING_LEVELS_WITH_XHIGH = ["off", "low", "medium", "high", "xhigh"];
|
|
58
58
|
// ============================================================================
|
|
59
59
|
// AgentSession Class
|
|
60
60
|
// ============================================================================
|
|
@@ -1887,7 +1887,7 @@ export class AgentSession {
|
|
|
1887
1887
|
this._applyExtensionBindings(this._extensionRunner);
|
|
1888
1888
|
const defaultActiveToolNames = this._baseToolsOverride
|
|
1889
1889
|
? Object.keys(this._baseToolsOverride)
|
|
1890
|
-
: ["read", "bash", "edit", "write"];
|
|
1890
|
+
: ["read", "bash", "edit", "write", "web_search"];
|
|
1891
1891
|
const baseActiveToolNames = options.activeToolNames ?? defaultActiveToolNames;
|
|
1892
1892
|
this._refreshToolRegistry({
|
|
1893
1893
|
activeToolNames: baseActiveToolNames,
|
package/dist/core/keybindings.js
CHANGED
|
@@ -25,6 +25,7 @@ export const KEYBINDINGS = {
|
|
|
25
25
|
},
|
|
26
26
|
"app.model.select": { defaultKeys: "ctrl+l", description: "Open model selector" },
|
|
27
27
|
"app.tools.expand": { defaultKeys: "ctrl+o", description: "Toggle tool output" },
|
|
28
|
+
"app.history.full": { defaultKeys: "ctrl+r", description: "Toggle full conversation view" },
|
|
28
29
|
"app.thinking.toggle": {
|
|
29
30
|
defaultKeys: "ctrl+t",
|
|
30
31
|
description: "Toggle thinking blocks",
|
package/dist/core/sdk.js
CHANGED
|
@@ -13,12 +13,12 @@ import { getDefaultSessionDir, SessionManager } from "./session-manager.js";
|
|
|
13
13
|
import { SettingsManager } from "./settings-manager.js";
|
|
14
14
|
import { isInstallTelemetryEnabled } from "./telemetry.js";
|
|
15
15
|
import { time } from "./timings.js";
|
|
16
|
-
import { createBashTool, createCodingTools, createEditTool, createFindTool, createGrepTool, createLsTool, createReadOnlyTools, createReadTool, createWriteTool, withFileMutationQueue, } from "./tools/index.js";
|
|
16
|
+
import { createBashTool, createCodingTools, createEditTool, createFindTool, createGrepTool, createLsTool, createReadOnlyTools, createReadTool, createWebSearchTool, createWriteTool, withFileMutationQueue, } from "./tools/index.js";
|
|
17
17
|
// Re-exports
|
|
18
18
|
export * from "./agent-session-runtime.js";
|
|
19
19
|
export { withFileMutationQueue,
|
|
20
20
|
// Tool factories (for custom cwd)
|
|
21
|
-
createCodingTools, createReadOnlyTools, createReadTool, createBashTool, createEditTool, createWriteTool, createGrepTool, createFindTool, createLsTool, };
|
|
21
|
+
createCodingTools, createReadOnlyTools, createReadTool, createBashTool, createEditTool, createWriteTool, createGrepTool, createFindTool, createLsTool, createWebSearchTool, };
|
|
22
22
|
// Helper Functions
|
|
23
23
|
function getDefaultAgentDir() {
|
|
24
24
|
return getAgentDir();
|
|
@@ -135,7 +135,7 @@ export async function createAgentSession(options = {}) {
|
|
|
135
135
|
if (!model || !model.reasoning) {
|
|
136
136
|
thinkingLevel = "off";
|
|
137
137
|
}
|
|
138
|
-
const defaultActiveToolNames = ["read", "bash", "edit", "write"];
|
|
138
|
+
const defaultActiveToolNames = ["read", "bash", "edit", "write", "web_search"];
|
|
139
139
|
const initialActiveToolNames = options.tools ? [...options.tools] : defaultActiveToolNames;
|
|
140
140
|
let agent;
|
|
141
141
|
// Create convertToLlm wrapper that filters images if blockImages is enabled (defense-in-depth)
|
|
@@ -45,7 +45,7 @@ export function buildSystemPrompt(options) {
|
|
|
45
45
|
const examplesPath = getExamplesPath();
|
|
46
46
|
// Build tools list based on selected tools.
|
|
47
47
|
// A tool appears in Available tools only when the caller provides a one-line snippet.
|
|
48
|
-
const tools = selectedTools || ["read", "bash", "edit", "write"];
|
|
48
|
+
const tools = selectedTools || ["read", "bash", "edit", "write", "web_search"];
|
|
49
49
|
const visibleTools = tools.filter((name) => !!toolSnippets?.[name]);
|
|
50
50
|
const toolsList = visibleTools.length > 0 ? visibleTools.map((name) => `- ${name}: ${toolSnippets[name]}`).join("\n") : "(none)";
|
|
51
51
|
// Build guidelines based on which tools are actually available
|
|
@@ -63,6 +63,7 @@ export function buildSystemPrompt(options) {
|
|
|
63
63
|
const hasFind = tools.includes("find");
|
|
64
64
|
const hasLs = tools.includes("ls");
|
|
65
65
|
const hasRead = tools.includes("read");
|
|
66
|
+
const hasWebSearch = tools.includes("web_search");
|
|
66
67
|
// File exploration guidelines
|
|
67
68
|
if (hasBash && !hasGrep && !hasFind && !hasLs) {
|
|
68
69
|
addGuideline("Use bash for file operations like ls, rg, find");
|
|
@@ -70,6 +71,9 @@ export function buildSystemPrompt(options) {
|
|
|
70
71
|
else if (hasBash && (hasGrep || hasFind || hasLs)) {
|
|
71
72
|
addGuideline("Prefer grep/find/ls tools over bash for file exploration (faster, respects .gitignore)");
|
|
72
73
|
}
|
|
74
|
+
if (hasWebSearch) {
|
|
75
|
+
addGuideline("Use web_search for current external information, public docs, and recent web content.");
|
|
76
|
+
}
|
|
73
77
|
for (const guideline of promptGuidelines ?? []) {
|
|
74
78
|
const normalized = guideline.trim();
|
|
75
79
|
if (normalized.length > 0) {
|
package/dist/core/tools/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export { createGrepTool, createGrepToolDefinition, } from "./grep.js";
|
|
|
6
6
|
export { createLsTool, createLsToolDefinition, } from "./ls.js";
|
|
7
7
|
export { createReadTool, createReadToolDefinition, } from "./read.js";
|
|
8
8
|
export { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, truncateHead, truncateLine, truncateTail, } from "./truncate.js";
|
|
9
|
+
export { createWebSearchTool, createWebSearchToolDefinition, } from "./web-search.js";
|
|
9
10
|
export { createWriteTool, createWriteToolDefinition, } from "./write.js";
|
|
10
11
|
import { createBashTool, createBashToolDefinition } from "./bash.js";
|
|
11
12
|
import { createEditTool, createEditToolDefinition } from "./edit.js";
|
|
@@ -13,8 +14,9 @@ import { createFindTool, createFindToolDefinition } from "./find.js";
|
|
|
13
14
|
import { createGrepTool, createGrepToolDefinition } from "./grep.js";
|
|
14
15
|
import { createLsTool, createLsToolDefinition } from "./ls.js";
|
|
15
16
|
import { createReadTool, createReadToolDefinition } from "./read.js";
|
|
17
|
+
import { createWebSearchTool, createWebSearchToolDefinition } from "./web-search.js";
|
|
16
18
|
import { createWriteTool, createWriteToolDefinition } from "./write.js";
|
|
17
|
-
export const allToolNames = new Set(["read", "bash", "edit", "write", "grep", "find", "ls"]);
|
|
19
|
+
export const allToolNames = new Set(["read", "bash", "edit", "write", "grep", "find", "ls", "web_search"]);
|
|
18
20
|
export function createToolDefinition(toolName, cwd, options) {
|
|
19
21
|
switch (toolName) {
|
|
20
22
|
case "read":
|
|
@@ -31,6 +33,8 @@ export function createToolDefinition(toolName, cwd, options) {
|
|
|
31
33
|
return createFindToolDefinition(cwd, options?.find);
|
|
32
34
|
case "ls":
|
|
33
35
|
return createLsToolDefinition(cwd, options?.ls);
|
|
36
|
+
case "web_search":
|
|
37
|
+
return createWebSearchToolDefinition(cwd, options?.web_search);
|
|
34
38
|
default:
|
|
35
39
|
throw new Error(`Unknown tool name: ${toolName}`);
|
|
36
40
|
}
|
|
@@ -51,6 +55,8 @@ export function createTool(toolName, cwd, options) {
|
|
|
51
55
|
return createFindTool(cwd, options?.find);
|
|
52
56
|
case "ls":
|
|
53
57
|
return createLsTool(cwd, options?.ls);
|
|
58
|
+
case "web_search":
|
|
59
|
+
return createWebSearchTool(cwd, options?.web_search);
|
|
54
60
|
default:
|
|
55
61
|
throw new Error(`Unknown tool name: ${toolName}`);
|
|
56
62
|
}
|
|
@@ -61,6 +67,7 @@ export function createCodingToolDefinitions(cwd, options) {
|
|
|
61
67
|
createBashToolDefinition(cwd, options?.bash),
|
|
62
68
|
createEditToolDefinition(cwd, options?.edit),
|
|
63
69
|
createWriteToolDefinition(cwd, options?.write),
|
|
70
|
+
createWebSearchToolDefinition(cwd, options?.web_search),
|
|
64
71
|
];
|
|
65
72
|
}
|
|
66
73
|
export function createReadOnlyToolDefinitions(cwd, options) {
|
|
@@ -80,6 +87,7 @@ export function createAllToolDefinitions(cwd, options) {
|
|
|
80
87
|
grep: createGrepToolDefinition(cwd, options?.grep),
|
|
81
88
|
find: createFindToolDefinition(cwd, options?.find),
|
|
82
89
|
ls: createLsToolDefinition(cwd, options?.ls),
|
|
90
|
+
web_search: createWebSearchToolDefinition(cwd, options?.web_search),
|
|
83
91
|
};
|
|
84
92
|
}
|
|
85
93
|
export function createCodingTools(cwd, options) {
|
|
@@ -88,6 +96,7 @@ export function createCodingTools(cwd, options) {
|
|
|
88
96
|
createBashTool(cwd, options?.bash),
|
|
89
97
|
createEditTool(cwd, options?.edit),
|
|
90
98
|
createWriteTool(cwd, options?.write),
|
|
99
|
+
createWebSearchTool(cwd, options?.web_search),
|
|
91
100
|
];
|
|
92
101
|
}
|
|
93
102
|
export function createReadOnlyTools(cwd, options) {
|
|
@@ -107,6 +116,7 @@ export function createAllTools(cwd, options) {
|
|
|
107
116
|
grep: createGrepTool(cwd, options?.grep),
|
|
108
117
|
find: createFindTool(cwd, options?.find),
|
|
109
118
|
ls: createLsTool(cwd, options?.ls),
|
|
119
|
+
web_search: createWebSearchTool(cwd, options?.web_search),
|
|
110
120
|
};
|
|
111
121
|
}
|
|
112
|
-
//# sourceMappingURL=index.js.map
|
|
122
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { Text } from "@operator/tui";
|
|
2
|
+
import { spawn } from "child_process";
|
|
3
|
+
import { existsSync } from "fs";
|
|
4
|
+
import { Type } from "typebox";
|
|
5
|
+
import { keyHint } from "../../modes/interactive/components/keybinding-hints.js";
|
|
6
|
+
import { getTextOutput, invalidArgText, str } from "./render-utils.js";
|
|
7
|
+
import { wrapToolDefinition } from "./tool-definition-wrapper.js";
|
|
8
|
+
import { DEFAULT_MAX_BYTES, formatSize, truncateHead } from "./truncate.js";
|
|
9
|
+
const WEB_SEARCH_HELPER = `${process.env.HOME || ""}/.tools/web_search/search.cjs`;
|
|
10
|
+
const webSearchSchema = Type.Object({
|
|
11
|
+
query: Type.Optional(Type.String({ description: "Search query for full, ddg, instant, or wiki modes" })),
|
|
12
|
+
url: Type.Optional(Type.String({ description: "Page URL to extract when mode is 'page'" })),
|
|
13
|
+
mode: Type.Optional(Type.Union([
|
|
14
|
+
Type.Literal("full"),
|
|
15
|
+
Type.Literal("ddg"),
|
|
16
|
+
Type.Literal("instant"),
|
|
17
|
+
Type.Literal("wiki"),
|
|
18
|
+
Type.Literal("page"),
|
|
19
|
+
], { description: "Search mode (default: full)" })),
|
|
20
|
+
limit: Type.Optional(Type.Number({ description: "Maximum number of results for ddg/wiki/full (default: 5)" })),
|
|
21
|
+
});
|
|
22
|
+
function formatWebSearchCall(args, theme) {
|
|
23
|
+
const mode = str(args?.mode) || "full";
|
|
24
|
+
const query = str(args?.query);
|
|
25
|
+
const url = str(args?.url);
|
|
26
|
+
const target = mode === "page" ? url : query;
|
|
27
|
+
const invalidArg = invalidArgText(theme);
|
|
28
|
+
return `${theme.fg("toolTitle", theme.bold("web.search"))} ${theme.fg("toolOutput", `[${mode}] `)}${target === null ? invalidArg : theme.fg("accent", target || "...")}`;
|
|
29
|
+
}
|
|
30
|
+
function formatWebSearchResult(result, options, theme, showImages) {
|
|
31
|
+
const output = getTextOutput(result, showImages).trim();
|
|
32
|
+
if (!output) {
|
|
33
|
+
return "";
|
|
34
|
+
}
|
|
35
|
+
const lines = output.split("\n");
|
|
36
|
+
const maxLines = options.expanded ? lines.length : 20;
|
|
37
|
+
const displayLines = lines.slice(0, maxLines);
|
|
38
|
+
const remaining = lines.length - maxLines;
|
|
39
|
+
let text = `\n${displayLines.map((line) => theme.fg("toolOutput", line)).join("\n")}`;
|
|
40
|
+
if (remaining > 0) {
|
|
41
|
+
text += `${theme.fg("muted", `\n... (${remaining} more lines,`)} ${keyHint("app.tools.expand", "to expand")})`;
|
|
42
|
+
}
|
|
43
|
+
const truncation = result.details?.truncation;
|
|
44
|
+
if (truncation?.truncated) {
|
|
45
|
+
text += `\n${theme.fg("warning", `[Truncated: ${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit]`)}`;
|
|
46
|
+
}
|
|
47
|
+
return text;
|
|
48
|
+
}
|
|
49
|
+
export function createWebSearchToolDefinition(_cwd, _options) {
|
|
50
|
+
return {
|
|
51
|
+
name: "web_search",
|
|
52
|
+
label: "web.search",
|
|
53
|
+
description: `Search the web using DuckDuckGo and Wikipedia, or extract readable text from a web page. Best for current external information, documentation, and quick validation. Output is truncated to ${DEFAULT_MAX_BYTES / 1024}KB if needed.`,
|
|
54
|
+
promptSnippet: "Search the web for current external information or extract text from a URL",
|
|
55
|
+
promptGuidelines: [
|
|
56
|
+
"Use web_search when the user asks for current external information, recent updates, or public web content.",
|
|
57
|
+
],
|
|
58
|
+
parameters: webSearchSchema,
|
|
59
|
+
async execute(_toolCallId, { query, url, mode, limit }, signal, _onUpdate, _ctx) {
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
if (signal?.aborted) {
|
|
62
|
+
reject(new Error("Operation aborted"));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (!existsSync(WEB_SEARCH_HELPER)) {
|
|
66
|
+
reject(new Error(`Web search helper not found: ${WEB_SEARCH_HELPER}`));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const effectiveMode = mode || "full";
|
|
70
|
+
const target = effectiveMode === "page" ? url : query;
|
|
71
|
+
if (!target) {
|
|
72
|
+
reject(new Error(effectiveMode === "page" ? "url is required for page mode" : "query is required for search mode"));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const args = [WEB_SEARCH_HELPER, "--json"];
|
|
76
|
+
if (effectiveMode === "page") {
|
|
77
|
+
args.push("--page", target);
|
|
78
|
+
}
|
|
79
|
+
else if (effectiveMode === "ddg") {
|
|
80
|
+
args.push("--ddg", target);
|
|
81
|
+
}
|
|
82
|
+
else if (effectiveMode === "instant") {
|
|
83
|
+
args.push("--instant", target);
|
|
84
|
+
}
|
|
85
|
+
else if (effectiveMode === "wiki") {
|
|
86
|
+
args.push("--wiki", target);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
args.push(target);
|
|
90
|
+
}
|
|
91
|
+
if (limit !== undefined && effectiveMode !== "page" && effectiveMode !== "instant") {
|
|
92
|
+
args.push("--limit", String(limit));
|
|
93
|
+
}
|
|
94
|
+
const child = spawn(process.execPath, args, {
|
|
95
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
96
|
+
});
|
|
97
|
+
let stdout = "";
|
|
98
|
+
let stderr = "";
|
|
99
|
+
let settled = false;
|
|
100
|
+
const settle = (fn) => {
|
|
101
|
+
if (settled)
|
|
102
|
+
return;
|
|
103
|
+
settled = true;
|
|
104
|
+
signal?.removeEventListener("abort", onAbort);
|
|
105
|
+
fn();
|
|
106
|
+
};
|
|
107
|
+
const onAbort = () => {
|
|
108
|
+
if (!child.killed) {
|
|
109
|
+
child.kill();
|
|
110
|
+
}
|
|
111
|
+
settle(() => reject(new Error("Operation aborted")));
|
|
112
|
+
};
|
|
113
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
114
|
+
child.stdout?.on("data", (chunk) => {
|
|
115
|
+
stdout += chunk.toString();
|
|
116
|
+
});
|
|
117
|
+
child.stderr?.on("data", (chunk) => {
|
|
118
|
+
stderr += chunk.toString();
|
|
119
|
+
});
|
|
120
|
+
child.on("error", (error) => {
|
|
121
|
+
settle(() => reject(new Error(`Failed to run web search helper: ${error.message}`)));
|
|
122
|
+
});
|
|
123
|
+
child.on("close", (code) => {
|
|
124
|
+
if (signal?.aborted) {
|
|
125
|
+
settle(() => reject(new Error("Operation aborted")));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (code !== 0) {
|
|
129
|
+
settle(() => reject(new Error(stderr.trim() || `web_search exited with code ${code}`)));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const output = stdout.trim();
|
|
133
|
+
if (!output) {
|
|
134
|
+
settle(() => resolve({
|
|
135
|
+
content: [{ type: "text", text: "No web search results returned" }],
|
|
136
|
+
details: undefined,
|
|
137
|
+
}));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const truncation = truncateHead(output, { maxLines: Number.MAX_SAFE_INTEGER });
|
|
141
|
+
settle(() => resolve({
|
|
142
|
+
content: [{ type: "text", text: truncation.content }],
|
|
143
|
+
details: truncation.truncated ? { truncation } : undefined,
|
|
144
|
+
}));
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
renderCall(args, theme, context) {
|
|
149
|
+
const text = context.lastComponent ?? new Text("", 0, 0);
|
|
150
|
+
text.setText(formatWebSearchCall(args, theme));
|
|
151
|
+
return text;
|
|
152
|
+
},
|
|
153
|
+
renderResult(result, options, theme, context) {
|
|
154
|
+
const text = context.lastComponent ?? new Text("", 0, 0);
|
|
155
|
+
text.setText(formatWebSearchResult(result, options, theme, context.showImages));
|
|
156
|
+
return text;
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
export function createWebSearchTool(cwd, options) {
|
|
161
|
+
return wrapToolDefinition(createWebSearchToolDefinition(cwd, options));
|
|
162
|
+
}
|
package/dist/migrations.js
CHANGED
|
@@ -16,7 +16,7 @@ const DEFAULT_MODELS_CONFIG = {
|
|
|
16
16
|
authHeader: true,
|
|
17
17
|
compat: {
|
|
18
18
|
supportsDeveloperRole: true,
|
|
19
|
-
supportsReasoningEffort:
|
|
19
|
+
supportsReasoningEffort: true,
|
|
20
20
|
},
|
|
21
21
|
models: [
|
|
22
22
|
{
|
|
@@ -32,13 +32,14 @@ const DEFAULT_MODELS_CONFIG = {
|
|
|
32
32
|
authHeader: true,
|
|
33
33
|
compat: {
|
|
34
34
|
supportsDeveloperRole: true,
|
|
35
|
-
supportsReasoningEffort:
|
|
35
|
+
supportsReasoningEffort: true,
|
|
36
36
|
},
|
|
37
37
|
models: [
|
|
38
38
|
{
|
|
39
39
|
id: "qwen3-6",
|
|
40
40
|
name: "qwen3.6 27b",
|
|
41
41
|
reasoning: true,
|
|
42
|
+
input: ["text", "image"],
|
|
42
43
|
},
|
|
43
44
|
],
|
|
44
45
|
},
|
|
@@ -48,13 +49,14 @@ const DEFAULT_MODELS_CONFIG = {
|
|
|
48
49
|
authHeader: true,
|
|
49
50
|
compat: {
|
|
50
51
|
supportsDeveloperRole: true,
|
|
51
|
-
supportsReasoningEffort:
|
|
52
|
+
supportsReasoningEffort: true,
|
|
52
53
|
},
|
|
53
54
|
models: [
|
|
54
55
|
{
|
|
55
56
|
id: "gemma4",
|
|
56
57
|
name: "gemma4",
|
|
57
58
|
reasoning: true,
|
|
59
|
+
input: ["text", "image"],
|
|
58
60
|
},
|
|
59
61
|
],
|
|
60
62
|
},
|
|
@@ -110,8 +112,8 @@ function migrateAlemBaseUrls() {
|
|
|
110
112
|
if (!gptOssProvider.compat || typeof gptOssProvider.compat !== "object") {
|
|
111
113
|
gptOssProvider.compat = {};
|
|
112
114
|
}
|
|
113
|
-
if (gptOssProvider.compat.supportsReasoningEffort !==
|
|
114
|
-
gptOssProvider.compat.supportsReasoningEffort =
|
|
115
|
+
if (gptOssProvider.compat.supportsReasoningEffort !== true) {
|
|
116
|
+
gptOssProvider.compat.supportsReasoningEffort = true;
|
|
115
117
|
changed = true;
|
|
116
118
|
}
|
|
117
119
|
}
|
|
@@ -126,6 +128,31 @@ function migrateAlemBaseUrls() {
|
|
|
126
128
|
provider.baseUrl = "https://llm.alem.ai/v1";
|
|
127
129
|
changed = true;
|
|
128
130
|
}
|
|
131
|
+
if (!provider.compat || typeof provider.compat !== "object") {
|
|
132
|
+
provider.compat = {};
|
|
133
|
+
}
|
|
134
|
+
if (provider.compat.supportsReasoningEffort !== true) {
|
|
135
|
+
provider.compat.supportsReasoningEffort = true;
|
|
136
|
+
changed = true;
|
|
137
|
+
}
|
|
138
|
+
if (Array.isArray(provider.models)) {
|
|
139
|
+
for (const model of provider.models) {
|
|
140
|
+
if (!model || typeof model !== "object") {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (!model.compat || typeof model.compat !== "object") {
|
|
144
|
+
model.compat = {};
|
|
145
|
+
}
|
|
146
|
+
if (model.compat.supportsReasoningEffort !== true) {
|
|
147
|
+
model.compat.supportsReasoningEffort = true;
|
|
148
|
+
changed = true;
|
|
149
|
+
}
|
|
150
|
+
if (model.reasoning !== true) {
|
|
151
|
+
model.reasoning = true;
|
|
152
|
+
changed = true;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
129
156
|
}
|
|
130
157
|
if (changed) {
|
|
131
158
|
writeFileSync(modelsPath, `${JSON.stringify(parsed, null, 2)}\n`, "utf-8");
|
|
@@ -134,7 +134,7 @@ export class FooterComponent {
|
|
|
134
134
|
if (state.model?.reasoning) {
|
|
135
135
|
const thinkingLevel = state.thinkingLevel || "off";
|
|
136
136
|
rightSideWithoutProvider =
|
|
137
|
-
thinkingLevel === "off" ? `${modelName} | reasoning
|
|
137
|
+
thinkingLevel === "off" ? `${modelName} | reasoning fast` : `${modelName} | reasoning ${thinkingLevel}`;
|
|
138
138
|
}
|
|
139
139
|
const providerName = state.model?.provider || "none";
|
|
140
140
|
const sessionValue = sessionName || "unnamed";
|
|
@@ -6,11 +6,10 @@ const THINKING_SELECT_LIST_LAYOUT = {
|
|
|
6
6
|
maxPrimaryColumnWidth: 32,
|
|
7
7
|
};
|
|
8
8
|
const LEVEL_DESCRIPTIONS = {
|
|
9
|
-
off: "
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
high: "Deep analysis pass (~16k tokens)",
|
|
9
|
+
off: "Fast mode. Direct output, lowest latency.",
|
|
10
|
+
low: "Light analysis. Quick check before answering.",
|
|
11
|
+
medium: "Balanced analysis. Best default for most tasks.",
|
|
12
|
+
high: "Deep analysis. Slower, but stronger reasoning.",
|
|
14
13
|
xhigh: "Maximum analysis pass (~32k tokens)",
|
|
15
14
|
};
|
|
16
15
|
/**
|
|
@@ -22,7 +21,7 @@ export class ThinkingSelectorComponent extends Container {
|
|
|
22
21
|
super();
|
|
23
22
|
const thinkingLevels = availableLevels.map((level) => ({
|
|
24
23
|
value: level,
|
|
25
|
-
label: level,
|
|
24
|
+
label: level === "off" ? "fast" : level,
|
|
26
25
|
description: LEVEL_DESCRIPTIONS[level],
|
|
27
26
|
}));
|
|
28
27
|
// Add top border
|
|
@@ -6,7 +6,7 @@ import * as crypto from "node:crypto";
|
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
7
|
import * as os from "node:os";
|
|
8
8
|
import * as path from "node:path";
|
|
9
|
-
import { CombinedAutocompleteProvider, Container, fuzzyFilter, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, visibleWidth, } from "@operator/tui";
|
|
9
|
+
import { CombinedAutocompleteProvider, Container, fuzzyFilter, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, truncateToWidth, visibleWidth, } from "@operator/tui";
|
|
10
10
|
import { spawn, spawnSync } from "child_process";
|
|
11
11
|
import { APP_NAME, getAgentDir, getDebugLogPath, getShareViewerUrl, getUpdateInstruction, VERSION, } from "../../config.js";
|
|
12
12
|
import { parseSkillBlock } from "../../core/agent-session.js";
|
|
@@ -24,6 +24,7 @@ import { getChangelogPath, getNewEntries, parseChangelog } from "../../utils/cha
|
|
|
24
24
|
import { copyToClipboard } from "../../utils/clipboard.js";
|
|
25
25
|
import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
|
|
26
26
|
import { parseGitUrl } from "../../utils/git.js";
|
|
27
|
+
import { detectSupportedImageMimeTypeFromFile } from "../../utils/mime.js";
|
|
27
28
|
import { killTrackedDetachedChildren } from "../../utils/shell.js";
|
|
28
29
|
import { ensureTool } from "../../utils/tools-manager.js";
|
|
29
30
|
import { ArminComponent } from "./components/armin.js";
|
|
@@ -89,6 +90,133 @@ class StartupHeroText extends Text {
|
|
|
89
90
|
clearInterval(this.interval);
|
|
90
91
|
}
|
|
91
92
|
}
|
|
93
|
+
function fitLine(line, width) {
|
|
94
|
+
const fitted = truncateToWidth(line ?? "", Math.max(0, width), theme.fg("dim", "..."));
|
|
95
|
+
const padding = Math.max(0, width - visibleWidth(fitted));
|
|
96
|
+
return fitted + " ".repeat(padding);
|
|
97
|
+
}
|
|
98
|
+
function framedLines(lines, width, title = " OPERATOR ") {
|
|
99
|
+
if (width < 8)
|
|
100
|
+
return lines;
|
|
101
|
+
const innerWidth = Math.max(1, width - 4);
|
|
102
|
+
const titleText = truncateToWidth(title, Math.max(0, innerWidth - 2), "");
|
|
103
|
+
const topFill = Math.max(0, width - 3 - visibleWidth(titleText));
|
|
104
|
+
const top = theme.fg("borderAccent", "┌") + theme.bold(theme.fg("accent", titleText)) + theme.fg("borderAccent", "─".repeat(topFill) + "┐");
|
|
105
|
+
const bottom = theme.fg("borderAccent", "└" + "─".repeat(width - 2) + "┘");
|
|
106
|
+
return [
|
|
107
|
+
top,
|
|
108
|
+
...lines.map((line) => theme.fg("borderAccent", "│ ") + fitLine(line, innerWidth) + theme.fg("borderAccent", " │")),
|
|
109
|
+
bottom,
|
|
110
|
+
];
|
|
111
|
+
}
|
|
112
|
+
function formatTokensCompact(count) {
|
|
113
|
+
if (!count)
|
|
114
|
+
return "0";
|
|
115
|
+
if (count < 1000)
|
|
116
|
+
return String(count);
|
|
117
|
+
if (count < 10000)
|
|
118
|
+
return `${(count / 1000).toFixed(1)}k`;
|
|
119
|
+
if (count < 1000000)
|
|
120
|
+
return `${Math.round(count / 1000)}k`;
|
|
121
|
+
return `${(count / 1000000).toFixed(1)}M`;
|
|
122
|
+
}
|
|
123
|
+
function renderGauge(value, width) {
|
|
124
|
+
const pct = Number.isFinite(value) ? Math.max(0, Math.min(100, value)) : 0;
|
|
125
|
+
const slots = Math.max(6, Math.min(18, width - 10));
|
|
126
|
+
const filled = Math.round((slots * pct) / 100);
|
|
127
|
+
return `[${theme.fg("borderAccent", "█".repeat(filled))}${theme.fg("dim", "░".repeat(slots - filled))}] ${pct.toFixed(1)}%`;
|
|
128
|
+
}
|
|
129
|
+
function getSpinnerFrame(frame) {
|
|
130
|
+
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
131
|
+
return frames[frame % frames.length] || "•";
|
|
132
|
+
}
|
|
133
|
+
function formatReasoningLevel(level) {
|
|
134
|
+
const normalized = level === "low" || level === "medium" || level === "high" ? level : "off";
|
|
135
|
+
if (normalized === "low")
|
|
136
|
+
return `${theme.fg("success", "[LOW]")} ${theme.fg("success", "•")} ${theme.fg("dim", "light analysis")}`;
|
|
137
|
+
if (normalized === "medium")
|
|
138
|
+
return `${theme.fg("warning", "[MEDIUM]")} ${theme.fg("warning", "••")} ${theme.fg("dim", "balanced depth")}`;
|
|
139
|
+
if (normalized === "high")
|
|
140
|
+
return `${theme.fg("error", "[HIGH]")} ${theme.fg("error", "•••")} ${theme.fg("dim", "deep reasoning")}`;
|
|
141
|
+
return `${theme.fg("accent", "[FAST]")} ${theme.fg("accent", "⚡")} ${theme.fg("dim", "direct output")}`;
|
|
142
|
+
}
|
|
143
|
+
function formatReasoningLegend() {
|
|
144
|
+
return [
|
|
145
|
+
`${theme.fg("accent", "FAST")} ${theme.fg("dim", "⚡")}`,
|
|
146
|
+
`${theme.fg("success", "LOW")} ${theme.fg("success", "•")}`,
|
|
147
|
+
`${theme.fg("warning", "MED")} ${theme.fg("warning", "••")}`,
|
|
148
|
+
`${theme.fg("error", "HIGH")} ${theme.fg("error", "•••")}`,
|
|
149
|
+
].join(theme.fg("dim", " "));
|
|
150
|
+
}
|
|
151
|
+
function displayThinkingLevel(level) {
|
|
152
|
+
return level === "off" ? "fast" : level;
|
|
153
|
+
}
|
|
154
|
+
class OperatorWorkspaceLayout {
|
|
155
|
+
mode;
|
|
156
|
+
frame = 0;
|
|
157
|
+
interval;
|
|
158
|
+
constructor(mode) {
|
|
159
|
+
this.mode = mode;
|
|
160
|
+
this.interval = setInterval(() => {
|
|
161
|
+
this.frame = (this.frame + 1) % 24;
|
|
162
|
+
this.mode.ui.requestRender();
|
|
163
|
+
}, 380);
|
|
164
|
+
this.interval.unref?.();
|
|
165
|
+
}
|
|
166
|
+
dispose() {
|
|
167
|
+
clearInterval(this.interval);
|
|
168
|
+
}
|
|
169
|
+
render(width) {
|
|
170
|
+
const terminalHeight = this.mode.ui.terminal.rows || 30;
|
|
171
|
+
const headerHeight = this.mode.headerContainer.render(width).length;
|
|
172
|
+
const outerFrame = width >= 72;
|
|
173
|
+
const contentWidth = outerFrame ? Math.max(1, width - 4) : width;
|
|
174
|
+
const maxRows = Math.max(8, terminalHeight - headerHeight - (outerFrame ? 2 : 0));
|
|
175
|
+
const renderBody = (bodyLines) => outerFrame ? framedLines(bodyLines, width, this.mode.fullHistoryView ? " OPERATOR / FULL CHAT " : " OPERATOR / WORKSPACE ") : bodyLines;
|
|
176
|
+
if (contentWidth < 96) {
|
|
177
|
+
const leftLines = this.clampLeftLines(this.renderLeft(contentWidth), maxRows);
|
|
178
|
+
return renderBody([
|
|
179
|
+
...leftLines,
|
|
180
|
+
theme.fg("borderMuted", "─".repeat(Math.max(0, contentWidth))),
|
|
181
|
+
...this.mode.renderOperatorSidebar(contentWidth).slice(0, Math.max(0, maxRows - leftLines.length - 1)),
|
|
182
|
+
]);
|
|
183
|
+
}
|
|
184
|
+
const gutter = 3;
|
|
185
|
+
const leftWidth = Math.max(64, Math.floor(contentWidth * 0.65) - 1);
|
|
186
|
+
const rightWidth = Math.max(28, contentWidth - leftWidth - gutter);
|
|
187
|
+
const separator = theme.fg("borderMuted", " │ ");
|
|
188
|
+
const leftLines = this.clampLeftLines(this.renderLeft(leftWidth), maxRows);
|
|
189
|
+
const rightLines = this.mode.renderOperatorSidebar(rightWidth).slice(0, maxRows);
|
|
190
|
+
const rowCount = this.mode.fullHistoryView ? Math.max(maxRows, leftLines.length) : maxRows;
|
|
191
|
+
const rightStart = Math.max(0, rowCount - maxRows);
|
|
192
|
+
const lines = [];
|
|
193
|
+
for (let i = 0; i < rowCount; i++) {
|
|
194
|
+
const rightIndex = this.mode.fullHistoryView ? i - rightStart : i;
|
|
195
|
+
lines.push(fitLine(leftLines[i] ?? "", leftWidth) + separator + fitLine(rightLines[rightIndex] ?? "", rightWidth));
|
|
196
|
+
}
|
|
197
|
+
return renderBody(lines);
|
|
198
|
+
}
|
|
199
|
+
renderLeft(width) {
|
|
200
|
+
const components = [
|
|
201
|
+
this.mode.chatContainer,
|
|
202
|
+
this.mode.pendingMessagesContainer,
|
|
203
|
+
this.mode.statusContainer,
|
|
204
|
+
this.mode.widgetContainerAbove,
|
|
205
|
+
this.mode.editorDeckContainer,
|
|
206
|
+
this.mode.editorContainer,
|
|
207
|
+
this.mode.widgetContainerBelow,
|
|
208
|
+
];
|
|
209
|
+
return components.flatMap((component) => component.render(width));
|
|
210
|
+
}
|
|
211
|
+
clampLeftLines(lines, maxRows) {
|
|
212
|
+
if (this.mode.fullHistoryView)
|
|
213
|
+
return lines;
|
|
214
|
+
if (lines.length <= maxRows)
|
|
215
|
+
return lines;
|
|
216
|
+
const marker = theme.fg("dim", `... ${lines.length - maxRows + 1} earlier lines hidden in current view ...`);
|
|
217
|
+
return [marker, ...lines.slice(-(maxRows - 1))];
|
|
218
|
+
}
|
|
219
|
+
}
|
|
92
220
|
function isUnknownModel(model) {
|
|
93
221
|
return !!model && model.provider === "unknown" && model.id === "unknown" && model.api === "unknown";
|
|
94
222
|
}
|
|
@@ -126,6 +254,7 @@ export class InteractiveMode {
|
|
|
126
254
|
fdPath;
|
|
127
255
|
editorDeckContainer;
|
|
128
256
|
editorContainer;
|
|
257
|
+
workspaceLayout;
|
|
129
258
|
footer;
|
|
130
259
|
footerDataProvider;
|
|
131
260
|
// Stored so the same manager can be injected into custom editors, selectors, and extension UI.
|
|
@@ -155,6 +284,7 @@ export class InteractiveMode {
|
|
|
155
284
|
isFirstUserMessage = true;
|
|
156
285
|
// Tool output expansion state
|
|
157
286
|
toolOutputExpanded = false;
|
|
287
|
+
fullHistoryView = false;
|
|
158
288
|
// Thinking block visibility state
|
|
159
289
|
hideThinkingBlock = false;
|
|
160
290
|
// Skill commands: command name -> skill file path
|
|
@@ -177,6 +307,7 @@ export class InteractiveMode {
|
|
|
177
307
|
retryEscapeHandler;
|
|
178
308
|
// Messages queued while compaction is running
|
|
179
309
|
compactionQueuedMessages = [];
|
|
310
|
+
pendingImageAttachments = [];
|
|
180
311
|
// Shutdown state
|
|
181
312
|
shutdownRequested = false;
|
|
182
313
|
// Extension UI state
|
|
@@ -238,6 +369,7 @@ export class InteractiveMode {
|
|
|
238
369
|
this.editorDeckContainer = new Container();
|
|
239
370
|
this.editorContainer = new Container();
|
|
240
371
|
this.editorContainer.addChild(this.editor);
|
|
372
|
+
this.workspaceLayout = new OperatorWorkspaceLayout(this);
|
|
241
373
|
this.footerDataProvider = new FooterDataProvider(this.sessionManager.getCwd());
|
|
242
374
|
this.footer = new FooterComponent(this.session, this.footerDataProvider);
|
|
243
375
|
this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
|
|
@@ -472,15 +604,8 @@ export class InteractiveMode {
|
|
|
472
604
|
this.builtInHeader = new Text("", 0, 0);
|
|
473
605
|
this.headerContainer.addChild(this.builtInHeader);
|
|
474
606
|
}
|
|
475
|
-
this.ui.addChild(this.chatContainer);
|
|
476
|
-
this.ui.addChild(this.pendingMessagesContainer);
|
|
477
|
-
this.ui.addChild(this.statusContainer);
|
|
478
607
|
this.renderWidgets(); // Initialize with default spacer
|
|
479
|
-
this.ui.addChild(this.
|
|
480
|
-
this.ui.addChild(this.editorDeckContainer);
|
|
481
|
-
this.ui.addChild(this.editorContainer);
|
|
482
|
-
this.ui.addChild(this.widgetContainerBelow);
|
|
483
|
-
this.ui.addChild(this.footer);
|
|
608
|
+
this.ui.addChild(this.workspaceLayout);
|
|
484
609
|
this.ui.setFocus(this.editor);
|
|
485
610
|
this.setupKeyHandlers();
|
|
486
611
|
this.setupEditorSubmitHandler();
|
|
@@ -591,7 +716,9 @@ export class InteractiveMode {
|
|
|
591
716
|
continue;
|
|
592
717
|
}
|
|
593
718
|
this.dismissStartupHero();
|
|
594
|
-
|
|
719
|
+
const text = typeof userInput === "string" ? userInput : userInput.text;
|
|
720
|
+
const images = typeof userInput === "string" ? undefined : userInput.images;
|
|
721
|
+
await this.session.prompt(text, { images });
|
|
595
722
|
}
|
|
596
723
|
catch (error) {
|
|
597
724
|
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
@@ -1512,22 +1639,12 @@ export class InteractiveMode {
|
|
|
1512
1639
|
if (this.customFooter?.dispose) {
|
|
1513
1640
|
this.customFooter.dispose();
|
|
1514
1641
|
}
|
|
1515
|
-
// Remove current footer from UI
|
|
1516
|
-
if (this.customFooter) {
|
|
1517
|
-
this.ui.removeChild(this.customFooter);
|
|
1518
|
-
}
|
|
1519
|
-
else {
|
|
1520
|
-
this.ui.removeChild(this.footer);
|
|
1521
|
-
}
|
|
1522
1642
|
if (factory) {
|
|
1523
|
-
//
|
|
1643
|
+
// Custom footer is rendered inside the right sidebar.
|
|
1524
1644
|
this.customFooter = factory(this.ui, theme, this.footerDataProvider);
|
|
1525
|
-
this.ui.addChild(this.customFooter);
|
|
1526
1645
|
}
|
|
1527
1646
|
else {
|
|
1528
|
-
// Restore built-in footer
|
|
1529
1647
|
this.customFooter = undefined;
|
|
1530
|
-
this.ui.addChild(this.footer);
|
|
1531
1648
|
}
|
|
1532
1649
|
this.ui.requestRender();
|
|
1533
1650
|
}
|
|
@@ -1981,6 +2098,7 @@ export class InteractiveMode {
|
|
|
1981
2098
|
this.ui.onDebug = () => this.handleDebugCommand();
|
|
1982
2099
|
this.defaultEditor.onAction("app.model.select", () => this.showModelSelector());
|
|
1983
2100
|
this.defaultEditor.onAction("app.tools.expand", () => this.toggleToolOutputExpansion());
|
|
2101
|
+
this.defaultEditor.onAction("app.history.full", () => this.toggleFullHistoryView());
|
|
1984
2102
|
this.defaultEditor.onAction("app.thinking.toggle", () => this.toggleThinkingBlockVisibility());
|
|
1985
2103
|
this.defaultEditor.onAction("app.editor.external", () => this.openExternalEditor());
|
|
1986
2104
|
this.defaultEditor.onAction("app.message.followUp", () => this.handleFollowUp());
|
|
@@ -2007,14 +2125,15 @@ export class InteractiveMode {
|
|
|
2007
2125
|
if (!image) {
|
|
2008
2126
|
return;
|
|
2009
2127
|
}
|
|
2010
|
-
// Write to temp file
|
|
2011
|
-
const tmpDir = os.tmpdir();
|
|
2012
2128
|
const ext = extensionForImageMimeType(image.mimeType) ?? "png";
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2129
|
+
this.pendingImageAttachments.push({
|
|
2130
|
+
type: "image",
|
|
2131
|
+
data: Buffer.from(image.bytes).toString("base64"),
|
|
2132
|
+
mimeType: image.mimeType,
|
|
2133
|
+
filename: `clipboard-${crypto.randomUUID()}.${ext}`,
|
|
2134
|
+
});
|
|
2135
|
+
this.showStatus(`Image attached (${this.pendingImageAttachments.length})`);
|
|
2136
|
+
this.updateEditorDeck();
|
|
2018
2137
|
this.ui.requestRender();
|
|
2019
2138
|
}
|
|
2020
2139
|
catch {
|
|
@@ -2024,7 +2143,9 @@ export class InteractiveMode {
|
|
|
2024
2143
|
setupEditorSubmitHandler() {
|
|
2025
2144
|
this.defaultEditor.onSubmit = async (text) => {
|
|
2026
2145
|
text = text.trim();
|
|
2027
|
-
|
|
2146
|
+
const { text: normalizedText, images } = await this.preparePromptPayload(text);
|
|
2147
|
+
text = normalizedText;
|
|
2148
|
+
if (!text && !images?.length)
|
|
2028
2149
|
return;
|
|
2029
2150
|
if (DISABLED_SLASH_COMMANDS.has(text) || Array.from(DISABLED_SLASH_COMMANDS).some((command) => text.startsWith(`${command} `))) {
|
|
2030
2151
|
this.editor.setText("");
|
|
@@ -2059,28 +2180,30 @@ export class InteractiveMode {
|
|
|
2059
2180
|
}
|
|
2060
2181
|
const value = text.startsWith("/thinking ") ? text.slice(10).trim().toLowerCase() : "";
|
|
2061
2182
|
if (!value) {
|
|
2062
|
-
const selected = await this.showExtensionSelector("Select analysis depth", ["low", "medium", "high"]);
|
|
2183
|
+
const selected = await this.showExtensionSelector("Select analysis depth", ["fast", "low", "medium", "high"]);
|
|
2063
2184
|
if (!selected) {
|
|
2064
2185
|
this.showStatus("Analysis depth selection cancelled");
|
|
2065
2186
|
return;
|
|
2066
2187
|
}
|
|
2067
|
-
this.session.setThinkingLevel(selected);
|
|
2188
|
+
this.session.setThinkingLevel(selected === "fast" ? "off" : selected);
|
|
2068
2189
|
const after = this.session.thinkingLevel || "off";
|
|
2069
2190
|
this.footer.invalidate();
|
|
2070
2191
|
this.updateEditorBorderColor();
|
|
2071
|
-
this.showStatus(`Analysis depth: ${after}`);
|
|
2192
|
+
this.showStatus(`Analysis depth: ${displayThinkingLevel(after)}`);
|
|
2072
2193
|
return;
|
|
2073
2194
|
}
|
|
2074
|
-
if (!["low", "medium", "high"].includes(value)) {
|
|
2075
|
-
this.showStatus("Use /thinking low, /thinking medium, or /thinking high");
|
|
2195
|
+
if (!["fast", "low", "medium", "high"].includes(value)) {
|
|
2196
|
+
this.showStatus("Use /thinking fast, /thinking low, /thinking medium, or /thinking high");
|
|
2076
2197
|
return;
|
|
2077
2198
|
}
|
|
2078
2199
|
const before = this.session.thinkingLevel || "off";
|
|
2079
|
-
this.session.setThinkingLevel(value);
|
|
2200
|
+
this.session.setThinkingLevel(value === "fast" ? "off" : value);
|
|
2080
2201
|
const after = this.session.thinkingLevel || "off";
|
|
2081
2202
|
this.footer.invalidate();
|
|
2082
2203
|
this.updateEditorBorderColor();
|
|
2083
|
-
this.showStatus(before === after
|
|
2204
|
+
this.showStatus(before === after
|
|
2205
|
+
? `Analysis depth unchanged: ${displayThinkingLevel(after)}`
|
|
2206
|
+
: `Analysis depth: ${displayThinkingLevel(after)}`);
|
|
2084
2207
|
return;
|
|
2085
2208
|
}
|
|
2086
2209
|
if (text === "/copy") {
|
|
@@ -2167,10 +2290,10 @@ export class InteractiveMode {
|
|
|
2167
2290
|
if (this.isExtensionCommand(text)) {
|
|
2168
2291
|
this.editor.addToHistory?.(text);
|
|
2169
2292
|
this.editor.setText("");
|
|
2170
|
-
await this.session.prompt(text);
|
|
2293
|
+
await this.session.prompt(text, { images });
|
|
2171
2294
|
}
|
|
2172
2295
|
else {
|
|
2173
|
-
this.queueCompactionMessage(text, "steer");
|
|
2296
|
+
this.queueCompactionMessage(text, "steer", images);
|
|
2174
2297
|
}
|
|
2175
2298
|
return;
|
|
2176
2299
|
}
|
|
@@ -2179,7 +2302,7 @@ export class InteractiveMode {
|
|
|
2179
2302
|
if (this.session.isStreaming) {
|
|
2180
2303
|
this.editor.addToHistory?.(text);
|
|
2181
2304
|
this.editor.setText("");
|
|
2182
|
-
await this.session.prompt(text, { streamingBehavior: "steer" });
|
|
2305
|
+
await this.session.prompt(text, { streamingBehavior: "steer", images });
|
|
2183
2306
|
this.updatePendingMessagesDisplay();
|
|
2184
2307
|
this.ui.requestRender();
|
|
2185
2308
|
return;
|
|
@@ -2188,9 +2311,11 @@ export class InteractiveMode {
|
|
|
2188
2311
|
// First, move any pending bash components to chat
|
|
2189
2312
|
this.flushPendingBashComponents();
|
|
2190
2313
|
if (this.onInputCallback) {
|
|
2191
|
-
this.onInputCallback(text);
|
|
2314
|
+
this.onInputCallback({ text, images });
|
|
2192
2315
|
}
|
|
2193
2316
|
this.editor.addToHistory?.(text);
|
|
2317
|
+
this.pendingImageAttachments = [];
|
|
2318
|
+
this.updateEditorDeck();
|
|
2194
2319
|
};
|
|
2195
2320
|
}
|
|
2196
2321
|
subscribeToAgent() {
|
|
@@ -2206,6 +2331,7 @@ export class InteractiveMode {
|
|
|
2206
2331
|
switch (event.type) {
|
|
2207
2332
|
case "agent_start":
|
|
2208
2333
|
this.ui.terminal.setProgress(true);
|
|
2334
|
+
this.fullHistoryView = false;
|
|
2209
2335
|
// Restore main escape handler if retry handler is still active
|
|
2210
2336
|
// (retry success event fires later, but we need main handler now)
|
|
2211
2337
|
if (this.retryEscapeHandler) {
|
|
@@ -2483,7 +2609,11 @@ export class InteractiveMode {
|
|
|
2483
2609
|
const textBlocks = typeof message.content === "string"
|
|
2484
2610
|
? [{ type: "text", text: message.content }]
|
|
2485
2611
|
: message.content.filter((c) => c.type === "text");
|
|
2486
|
-
|
|
2612
|
+
const imageCount = typeof message.content === "string"
|
|
2613
|
+
? 0
|
|
2614
|
+
: message.content.filter((c) => c.type === "image").length;
|
|
2615
|
+
const text = textBlocks.map((c) => c.text).join("");
|
|
2616
|
+
return imageCount > 0 ? `${text}\n\n[Attached images: ${imageCount}]` : text;
|
|
2487
2617
|
}
|
|
2488
2618
|
/**
|
|
2489
2619
|
* Show a status message in the chat.
|
|
@@ -2670,6 +2800,50 @@ export class InteractiveMode {
|
|
|
2670
2800
|
};
|
|
2671
2801
|
});
|
|
2672
2802
|
}
|
|
2803
|
+
async buildImageAttachmentsFromPaths(text) {
|
|
2804
|
+
const attachments = [];
|
|
2805
|
+
const keptLines = [];
|
|
2806
|
+
const lines = text.split("\n");
|
|
2807
|
+
for (const line of lines) {
|
|
2808
|
+
const candidate = line.trim();
|
|
2809
|
+
if (!candidate) {
|
|
2810
|
+
keptLines.push(line);
|
|
2811
|
+
continue;
|
|
2812
|
+
}
|
|
2813
|
+
const resolvedPath = path.isAbsolute(candidate)
|
|
2814
|
+
? candidate
|
|
2815
|
+
: path.resolve(this.sessionManager.getCwd(), candidate);
|
|
2816
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
2817
|
+
keptLines.push(line);
|
|
2818
|
+
continue;
|
|
2819
|
+
}
|
|
2820
|
+
const mimeType = await detectSupportedImageMimeTypeFromFile(resolvedPath).catch(() => null);
|
|
2821
|
+
if (!mimeType) {
|
|
2822
|
+
keptLines.push(line);
|
|
2823
|
+
continue;
|
|
2824
|
+
}
|
|
2825
|
+
attachments.push({
|
|
2826
|
+
type: "image",
|
|
2827
|
+
data: fs.readFileSync(resolvedPath).toString("base64"),
|
|
2828
|
+
mimeType,
|
|
2829
|
+
filename: path.basename(resolvedPath),
|
|
2830
|
+
});
|
|
2831
|
+
}
|
|
2832
|
+
return {
|
|
2833
|
+
text: keptLines.join("\n").trim(),
|
|
2834
|
+
images: attachments,
|
|
2835
|
+
};
|
|
2836
|
+
}
|
|
2837
|
+
async preparePromptPayload(text) {
|
|
2838
|
+
const pathAttachments = await this.buildImageAttachmentsFromPaths(text);
|
|
2839
|
+
const images = [...this.pendingImageAttachments, ...pathAttachments.images];
|
|
2840
|
+
this.pendingImageAttachments = [];
|
|
2841
|
+
this.updateEditorDeck();
|
|
2842
|
+
return {
|
|
2843
|
+
text: pathAttachments.text || text,
|
|
2844
|
+
images: images.length > 0 ? images : undefined,
|
|
2845
|
+
};
|
|
2846
|
+
}
|
|
2673
2847
|
rebuildChatFromMessages() {
|
|
2674
2848
|
this.chatContainer.clear();
|
|
2675
2849
|
const context = this.sessionManager.buildSessionContext();
|
|
@@ -2779,33 +2953,36 @@ export class InteractiveMode {
|
|
|
2779
2953
|
}
|
|
2780
2954
|
async handleFollowUp() {
|
|
2781
2955
|
const text = (this.editor.getExpandedText?.() ?? this.editor.getText()).trim();
|
|
2782
|
-
if (!text)
|
|
2956
|
+
if (!text && this.pendingImageAttachments.length === 0)
|
|
2957
|
+
return;
|
|
2958
|
+
if (!this.session.isStreaming && !this.session.isCompacting) {
|
|
2959
|
+
if (this.editor.onSubmit) {
|
|
2960
|
+
this.editor.onSubmit(text);
|
|
2961
|
+
}
|
|
2783
2962
|
return;
|
|
2963
|
+
}
|
|
2964
|
+
const payload = await this.preparePromptPayload(text);
|
|
2784
2965
|
// Queue input during compaction (extension commands execute immediately)
|
|
2785
2966
|
if (this.session.isCompacting) {
|
|
2786
|
-
if (this.isExtensionCommand(text)) {
|
|
2787
|
-
this.editor.addToHistory?.(text);
|
|
2967
|
+
if (this.isExtensionCommand(payload.text)) {
|
|
2968
|
+
this.editor.addToHistory?.(payload.text);
|
|
2788
2969
|
this.editor.setText("");
|
|
2789
|
-
await this.session.prompt(text);
|
|
2970
|
+
await this.session.prompt(payload.text, { images: payload.images });
|
|
2790
2971
|
}
|
|
2791
2972
|
else {
|
|
2792
|
-
this.queueCompactionMessage(text, "followUp");
|
|
2973
|
+
this.queueCompactionMessage(payload.text, "followUp", payload.images);
|
|
2793
2974
|
}
|
|
2794
2975
|
return;
|
|
2795
2976
|
}
|
|
2796
2977
|
// Alt+Enter queues a follow-up message (waits until agent finishes)
|
|
2797
2978
|
// This handles extension commands (execute immediately), prompt template expansion, and queueing
|
|
2798
2979
|
if (this.session.isStreaming) {
|
|
2799
|
-
this.editor.addToHistory?.(text);
|
|
2980
|
+
this.editor.addToHistory?.(payload.text);
|
|
2800
2981
|
this.editor.setText("");
|
|
2801
|
-
await this.session.prompt(text, { streamingBehavior: "followUp" });
|
|
2982
|
+
await this.session.prompt(payload.text, { streamingBehavior: "followUp", images: payload.images });
|
|
2802
2983
|
this.updatePendingMessagesDisplay();
|
|
2803
2984
|
this.ui.requestRender();
|
|
2804
2985
|
}
|
|
2805
|
-
// If not streaming, Alt+Enter acts like regular Enter (trigger onSubmit)
|
|
2806
|
-
else if (this.editor.onSubmit) {
|
|
2807
|
-
this.editor.onSubmit(text);
|
|
2808
|
-
}
|
|
2809
2986
|
}
|
|
2810
2987
|
handleDequeue() {
|
|
2811
2988
|
const restored = this.restoreQueuedMessagesToEditor();
|
|
@@ -2829,17 +3006,105 @@ export class InteractiveMode {
|
|
|
2829
3006
|
}
|
|
2830
3007
|
updateEditorDeck() {
|
|
2831
3008
|
this.editorDeckContainer.clear();
|
|
2832
|
-
const modeLabel = this.isBashMode ? "shell
|
|
2833
|
-
const stateLabel = this.isInterruptibleWorkActive()
|
|
2834
|
-
|
|
2835
|
-
|
|
3009
|
+
const modeLabel = this.isBashMode ? "shell" : "prompt";
|
|
3010
|
+
const stateLabel = this.isInterruptibleWorkActive() ? "active" : "ready";
|
|
3011
|
+
const imageLabel = this.pendingImageAttachments.length > 0
|
|
3012
|
+
? theme.fg("warning", `${this.pendingImageAttachments.length} image${this.pendingImageAttachments.length > 1 ? "s" : ""}`)
|
|
3013
|
+
: "";
|
|
2836
3014
|
const hintLabel = this.isBashMode
|
|
2837
|
-
? `! shell
|
|
2838
|
-
:
|
|
2839
|
-
|
|
2840
|
-
|
|
3015
|
+
? theme.fg("dim", `! shell | ${keyText("app.clear")} stop`)
|
|
3016
|
+
: theme.fg("dim", `/ cmd | ${keyText("app.history.full")} full | ${keyText("app.thinking.cycle")} `) +
|
|
3017
|
+
formatReasoningLegend() +
|
|
3018
|
+
theme.fg("dim", ` | ${keyText("app.model.select")} model`);
|
|
3019
|
+
this.editorDeckContainer.addChild(new TruncatedText(theme.bold(theme.fg(this.isBashMode ? "warning" : "accent", `INPUT ${modeLabel}`)) +
|
|
3020
|
+
theme.fg("dim", " | ") +
|
|
3021
|
+
theme.fg(this.isInterruptibleWorkActive() ? "warning" : "success", stateLabel) +
|
|
3022
|
+
(imageLabel ? theme.fg("dim", " | ") + imageLabel : "") +
|
|
3023
|
+
theme.fg("dim", " | ") +
|
|
3024
|
+
hintLabel, 0, 0));
|
|
2841
3025
|
this.editorDeckContainer.addChild(new Spacer(1));
|
|
2842
3026
|
}
|
|
3027
|
+
renderOperatorSidebar(width) {
|
|
3028
|
+
const state = this.session.state;
|
|
3029
|
+
const contextUsage = this.session.getContextUsage();
|
|
3030
|
+
const contextWindow = contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;
|
|
3031
|
+
const contextPercent = contextUsage?.percent ?? 0;
|
|
3032
|
+
let totalInput = 0;
|
|
3033
|
+
let totalOutput = 0;
|
|
3034
|
+
let totalCacheRead = 0;
|
|
3035
|
+
for (const entry of this.session.sessionManager.getEntries()) {
|
|
3036
|
+
if (entry.type === "message" && entry.message.role === "assistant") {
|
|
3037
|
+
totalInput += entry.message.usage.input;
|
|
3038
|
+
totalOutput += entry.message.usage.output;
|
|
3039
|
+
totalCacheRead += entry.message.usage.cacheRead;
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
const cwd = this.session.sessionManager.getCwd().replace(process.env.HOME || "", "~");
|
|
3043
|
+
const sessionName = this.session.sessionManager.getSessionName() || "unnamed";
|
|
3044
|
+
const modelName = state.model?.id || "no-model";
|
|
3045
|
+
const providerName = state.model?.provider || "none";
|
|
3046
|
+
const thinking = state.model?.reasoning ? this.session.thinkingLevel || "off" : "off";
|
|
3047
|
+
const reasoningText = formatReasoningLevel(thinking);
|
|
3048
|
+
const active = this.isInterruptibleWorkActive();
|
|
3049
|
+
const activeTools = this.pendingTools.size;
|
|
3050
|
+
const queued = this.session.pendingMessageCount || 0;
|
|
3051
|
+
const activeToolNames = this.session.getActiveToolNames?.() || [];
|
|
3052
|
+
const toolText = activeToolNames.length ? activeToolNames.slice(0, 6).join(", ") : "read, bash, edit, write";
|
|
3053
|
+
const pulse = active ? ["●", "●", "●", "○"][Math.floor((this.workspaceLayout.frame % 24) / 6)] : "●";
|
|
3054
|
+
const loadingMessage = this.loadingAnimation?.message || this.defaultWorkingMessage;
|
|
3055
|
+
const spinner = getSpinnerFrame(this.workspaceLayout.frame);
|
|
3056
|
+
const rule = theme.fg("borderMuted", "─".repeat(Math.max(0, width)));
|
|
3057
|
+
const section = (name) => fitLine(theme.bold(theme.fg("accent", `▌ ${name}`)) + theme.fg("borderMuted", " " + "─".repeat(Math.max(0, width - visibleWidth(name) - 4))), width);
|
|
3058
|
+
const label = (name, value, color = "text") => fitLine(theme.fg("dim", `${name.padEnd(10)} `) + theme.fg(color, value), width);
|
|
3059
|
+
const rawLabel = (name, value) => fitLine(theme.fg("dim", `${name.padEnd(10)} `) + value, width);
|
|
3060
|
+
const lines = [
|
|
3061
|
+
theme.bold(fitLine(theme.fg("accent", "OPERATOR CONTROL"), width)),
|
|
3062
|
+
fitLine(theme.fg("dim", "agent loop dashboard"), width),
|
|
3063
|
+
rule,
|
|
3064
|
+
section("RUN"),
|
|
3065
|
+
label("state", `${pulse} ${active ? "RUNNING" : "READY"}`, active ? "warning" : "success"),
|
|
3066
|
+
label("phase", active ? "working / verify" : "waiting input", active ? "accent" : "muted"),
|
|
3067
|
+
label("queue", `${queued} pending`, queued ? "warning" : "text"),
|
|
3068
|
+
...(active
|
|
3069
|
+
? [
|
|
3070
|
+
rawLabel("activity", `${theme.fg("accent", spinner)} ${theme.fg("text", loadingMessage)}`),
|
|
3071
|
+
"",
|
|
3072
|
+
]
|
|
3073
|
+
: []),
|
|
3074
|
+
...(!active ? [""] : []),
|
|
3075
|
+
section("CONTEXT"),
|
|
3076
|
+
fitLine(renderGauge(contextPercent, width), width),
|
|
3077
|
+
label("window", `${formatTokensCompact(contextWindow)} tokens`),
|
|
3078
|
+
label("usage", `↑${formatTokensCompact(totalInput)} ↓${formatTokensCompact(totalOutput)} R${formatTokensCompact(totalCacheRead)}`),
|
|
3079
|
+
label("history", this.fullHistoryView ? "full view" : "compact", this.fullHistoryView ? "warning" : "text"),
|
|
3080
|
+
"",
|
|
3081
|
+
section("ENGINE"),
|
|
3082
|
+
label("model", modelName),
|
|
3083
|
+
rawLabel("reason", reasoningText),
|
|
3084
|
+
label("provider", providerName),
|
|
3085
|
+
"",
|
|
3086
|
+
section("WORKSPACE"),
|
|
3087
|
+
label("path", cwd),
|
|
3088
|
+
label("thread", sessionName),
|
|
3089
|
+
"",
|
|
3090
|
+
section("TOOLS"),
|
|
3091
|
+
label("active", `${activeTools} running`, activeTools ? "warning" : "text"),
|
|
3092
|
+
fitLine(theme.fg("text", toolText), width),
|
|
3093
|
+
"",
|
|
3094
|
+
rule,
|
|
3095
|
+
fitLine(theme.fg("dim", `${keyText("app.clear")} stop ${keyText("app.history.full")} full chat`), width),
|
|
3096
|
+
fitLine(theme.fg("dim", `${keyText("app.thinking.cycle")} reason: `) + formatReasoningLegend(), width),
|
|
3097
|
+
fitLine(theme.fg("dim", `${keyText("app.model.select")} model ${keyText("app.editor.external")} editor`), width),
|
|
3098
|
+
];
|
|
3099
|
+
const extensionStatuses = this.footerDataProvider.getExtensionStatuses();
|
|
3100
|
+
if (extensionStatuses.size > 0) {
|
|
3101
|
+
lines.splice(lines.length - 3, 0, "", fitLine(theme.fg("accent", "EXTENSIONS"), width), ...Array.from(extensionStatuses.values()).slice(0, 4).map((value) => fitLine(theme.fg("text", String(value).replace(/[\r\n\t]/g, " ")), width)));
|
|
3102
|
+
}
|
|
3103
|
+
if (this.customFooter) {
|
|
3104
|
+
lines.splice(lines.length - 3, 0, "", ...this.customFooter.render(width).map((line) => fitLine(line, width)));
|
|
3105
|
+
}
|
|
3106
|
+
return lines;
|
|
3107
|
+
}
|
|
2843
3108
|
cycleThinkingLevel() {
|
|
2844
3109
|
if (this.isBusyForModelOrThinkingChange()) {
|
|
2845
3110
|
this.showBusyModelOrThinkingStatus();
|
|
@@ -2852,7 +3117,7 @@ export class InteractiveMode {
|
|
|
2852
3117
|
else {
|
|
2853
3118
|
this.footer.invalidate();
|
|
2854
3119
|
this.updateEditorBorderColor();
|
|
2855
|
-
this.showStatus(`Analysis depth: ${newLevel}`);
|
|
3120
|
+
this.showStatus(`Analysis depth: ${displayThinkingLevel(newLevel)}`);
|
|
2856
3121
|
}
|
|
2857
3122
|
}
|
|
2858
3123
|
async cycleModel(direction) {
|
|
@@ -2880,6 +3145,19 @@ export class InteractiveMode {
|
|
|
2880
3145
|
toggleToolOutputExpansion() {
|
|
2881
3146
|
this.setToolsExpanded(!this.toolOutputExpanded);
|
|
2882
3147
|
}
|
|
3148
|
+
toggleFullHistoryView() {
|
|
3149
|
+
if (this.isInterruptibleWorkActive()) {
|
|
3150
|
+
this.fullHistoryView = false;
|
|
3151
|
+
this.showStatus("Full conversation view is available after the current run finishes");
|
|
3152
|
+
this.ui.requestRender();
|
|
3153
|
+
return;
|
|
3154
|
+
}
|
|
3155
|
+
this.fullHistoryView = !this.fullHistoryView;
|
|
3156
|
+
this.showStatus(this.fullHistoryView
|
|
3157
|
+
? "Full conversation view enabled. Press Ctrl+R again to return to compact view."
|
|
3158
|
+
: "Compact conversation view enabled.");
|
|
3159
|
+
this.ui.requestRender();
|
|
3160
|
+
}
|
|
2883
3161
|
setToolsExpanded(expanded) {
|
|
2884
3162
|
this.toolOutputExpanded = expanded;
|
|
2885
3163
|
const activeHeader = this.customHeader ?? this.builtInHeader;
|
|
@@ -3044,8 +3322,8 @@ export class InteractiveMode {
|
|
|
3044
3322
|
}
|
|
3045
3323
|
return allQueued.length;
|
|
3046
3324
|
}
|
|
3047
|
-
queueCompactionMessage(text, mode) {
|
|
3048
|
-
this.compactionQueuedMessages.push({ text, mode });
|
|
3325
|
+
queueCompactionMessage(text, mode, images) {
|
|
3326
|
+
this.compactionQueuedMessages.push({ text, mode, images });
|
|
3049
3327
|
this.editor.addToHistory?.(text);
|
|
3050
3328
|
this.editor.setText("");
|
|
3051
3329
|
this.updatePendingMessagesDisplay();
|
|
@@ -3077,13 +3355,13 @@ export class InteractiveMode {
|
|
|
3077
3355
|
// When retry is pending, queue messages for the retry turn
|
|
3078
3356
|
for (const message of queuedMessages) {
|
|
3079
3357
|
if (this.isExtensionCommand(message.text)) {
|
|
3080
|
-
await this.session.prompt(message.text);
|
|
3358
|
+
await this.session.prompt(message.text, { images: message.images });
|
|
3081
3359
|
}
|
|
3082
3360
|
else if (message.mode === "followUp") {
|
|
3083
|
-
await this.session.followUp(message.text);
|
|
3361
|
+
await this.session.followUp(message.text, message.images);
|
|
3084
3362
|
}
|
|
3085
3363
|
else {
|
|
3086
|
-
await this.session.steer(message.text);
|
|
3364
|
+
await this.session.steer(message.text, message.images);
|
|
3087
3365
|
}
|
|
3088
3366
|
}
|
|
3089
3367
|
this.updatePendingMessagesDisplay();
|
|
@@ -3094,7 +3372,7 @@ export class InteractiveMode {
|
|
|
3094
3372
|
if (firstPromptIndex === -1) {
|
|
3095
3373
|
// All extension commands - execute them all
|
|
3096
3374
|
for (const message of queuedMessages) {
|
|
3097
|
-
await this.session.prompt(message.text);
|
|
3375
|
+
await this.session.prompt(message.text, { images: message.images });
|
|
3098
3376
|
}
|
|
3099
3377
|
return;
|
|
3100
3378
|
}
|
|
@@ -3103,22 +3381,22 @@ export class InteractiveMode {
|
|
|
3103
3381
|
const firstPrompt = queuedMessages[firstPromptIndex];
|
|
3104
3382
|
const rest = queuedMessages.slice(firstPromptIndex + 1);
|
|
3105
3383
|
for (const message of preCommands) {
|
|
3106
|
-
await this.session.prompt(message.text);
|
|
3384
|
+
await this.session.prompt(message.text, { images: message.images });
|
|
3107
3385
|
}
|
|
3108
3386
|
// Send first prompt (starts streaming)
|
|
3109
|
-
const promptPromise = this.session.prompt(firstPrompt.text).catch((error) => {
|
|
3387
|
+
const promptPromise = this.session.prompt(firstPrompt.text, { images: firstPrompt.images }).catch((error) => {
|
|
3110
3388
|
restoreQueue(error);
|
|
3111
3389
|
});
|
|
3112
3390
|
// Queue remaining messages
|
|
3113
3391
|
for (const message of rest) {
|
|
3114
3392
|
if (this.isExtensionCommand(message.text)) {
|
|
3115
|
-
await this.session.prompt(message.text);
|
|
3393
|
+
await this.session.prompt(message.text, { images: message.images });
|
|
3116
3394
|
}
|
|
3117
3395
|
else if (message.mode === "followUp") {
|
|
3118
|
-
await this.session.followUp(message.text);
|
|
3396
|
+
await this.session.followUp(message.text, message.images);
|
|
3119
3397
|
}
|
|
3120
3398
|
else {
|
|
3121
|
-
await this.session.steer(message.text);
|
|
3399
|
+
await this.session.steer(message.text, message.images);
|
|
3122
3400
|
}
|
|
3123
3401
|
}
|
|
3124
3402
|
this.updatePendingMessagesDisplay();
|
|
@@ -4351,6 +4629,7 @@ export class InteractiveMode {
|
|
|
4351
4629
|
this.loadingAnimation.stop();
|
|
4352
4630
|
this.loadingAnimation = undefined;
|
|
4353
4631
|
}
|
|
4632
|
+
this.workspaceLayout.dispose();
|
|
4354
4633
|
this.clearExtensionTerminalInputListeners();
|
|
4355
4634
|
this.footer.dispose();
|
|
4356
4635
|
this.footerDataProvider.dispose();
|
|
@@ -396,6 +396,11 @@ function buildParams(model, context, options, compat = getCompat(model), cacheRe
|
|
|
396
396
|
else if (options?.reasoningEffort && model.reasoning && compat.supportsReasoningEffort) {
|
|
397
397
|
// OpenAI-style reasoning_effort
|
|
398
398
|
params.reasoning_effort = mapReasoningEffort(options.reasoningEffort, compat.reasoningEffortMap);
|
|
399
|
+
// LiteLLM proxies may reject OpenAI-only params unless they are explicitly allow-listed.
|
|
400
|
+
// Alem AI Plus sits behind LiteLLM and returns UnsupportedParamsError without this field.
|
|
401
|
+
if (model.baseUrl.includes("llm.alem.ai")) {
|
|
402
|
+
params.allowed_openai_params = ["reasoning_effort"];
|
|
403
|
+
}
|
|
399
404
|
}
|
|
400
405
|
// OpenRouter provider routing preferences
|
|
401
406
|
if (model.baseUrl.includes("openrouter.ai") && model.compat?.openRouterRouting) {
|
|
@@ -851,4 +856,4 @@ function getCompat(model) {
|
|
|
851
856
|
sendSessionAffinityHeaders: model.compat.sendSessionAffinityHeaders ?? detected.sendSessionAffinityHeaders,
|
|
852
857
|
};
|
|
853
858
|
}
|
|
854
|
-
//# sourceMappingURL=openai-completions.js.map
|
|
859
|
+
//# sourceMappingURL=openai-completions.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pzero-operator",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Operator is a coding-first terminal AI agent from ProjectZero for software development, shell execution, local project workflows, and broader device-level operator control.",
|