open-agents-ai 0.11.2 → 0.11.4
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/index.js +625 -70
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1164,7 +1164,7 @@ var init_shell = __esm({
|
|
|
1164
1164
|
const timeout = args["timeout"] ?? this.defaultTimeout;
|
|
1165
1165
|
const stdinInput = args["stdin"];
|
|
1166
1166
|
const start = performance.now();
|
|
1167
|
-
return new Promise((
|
|
1167
|
+
return new Promise((resolve14) => {
|
|
1168
1168
|
const child = spawn("bash", ["-c", command], {
|
|
1169
1169
|
cwd: this.workingDir,
|
|
1170
1170
|
env: {
|
|
@@ -1217,7 +1217,7 @@ var init_shell = __esm({
|
|
|
1217
1217
|
const combined = stdout + stderr;
|
|
1218
1218
|
const looksInteractive = /\? .+[›>]|y\/n|yes\/no|\(Y\/n\)|\[y\/N\]/i.test(combined);
|
|
1219
1219
|
const hint = looksInteractive ? " The command appears to be waiting for interactive input. Use non-interactive flags (e.g., --yes, --no-input) or provide input via the stdin parameter." : "";
|
|
1220
|
-
|
|
1220
|
+
resolve14({
|
|
1221
1221
|
success: false,
|
|
1222
1222
|
output: stdout,
|
|
1223
1223
|
error: `Command timed out after ${timeout}ms.${hint}`,
|
|
@@ -1226,7 +1226,7 @@ var init_shell = __esm({
|
|
|
1226
1226
|
return;
|
|
1227
1227
|
}
|
|
1228
1228
|
const success = code === 0;
|
|
1229
|
-
|
|
1229
|
+
resolve14({
|
|
1230
1230
|
success,
|
|
1231
1231
|
output: stdout + (stderr && success ? `
|
|
1232
1232
|
STDERR:
|
|
@@ -1237,7 +1237,7 @@ ${stderr}` : ""),
|
|
|
1237
1237
|
});
|
|
1238
1238
|
child.on("error", (err) => {
|
|
1239
1239
|
clearTimeout(timer);
|
|
1240
|
-
|
|
1240
|
+
resolve14({
|
|
1241
1241
|
success: false,
|
|
1242
1242
|
output: stdout,
|
|
1243
1243
|
error: err.message,
|
|
@@ -2919,11 +2919,11 @@ var init_diagnostic = __esm({
|
|
|
2919
2919
|
}
|
|
2920
2920
|
return steps;
|
|
2921
2921
|
}
|
|
2922
|
-
runStep(step, command,
|
|
2922
|
+
runStep(step, command, cwd4) {
|
|
2923
2923
|
const start = performance.now();
|
|
2924
2924
|
try {
|
|
2925
2925
|
const output = execSync5(command, {
|
|
2926
|
-
cwd:
|
|
2926
|
+
cwd: cwd4,
|
|
2927
2927
|
encoding: "utf8",
|
|
2928
2928
|
timeout: 12e4,
|
|
2929
2929
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -3066,10 +3066,10 @@ var init_git_info = __esm({
|
|
|
3066
3066
|
durationMs: performance.now() - start
|
|
3067
3067
|
};
|
|
3068
3068
|
}
|
|
3069
|
-
git(
|
|
3069
|
+
git(cwd4, cmd) {
|
|
3070
3070
|
try {
|
|
3071
3071
|
return execSync6(`git ${cmd}`, {
|
|
3072
|
-
cwd:
|
|
3072
|
+
cwd: cwd4,
|
|
3073
3073
|
encoding: "utf8",
|
|
3074
3074
|
timeout: 1e4,
|
|
3075
3075
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -3103,10 +3103,10 @@ var init_background_task = __esm({
|
|
|
3103
3103
|
BackgroundTaskManager = class {
|
|
3104
3104
|
tasks = /* @__PURE__ */ new Map();
|
|
3105
3105
|
nextId = 1;
|
|
3106
|
-
spawn(command,
|
|
3106
|
+
spawn(command, cwd4, timeoutMs = 6e5) {
|
|
3107
3107
|
const id = `task-${this.nextId++}`;
|
|
3108
3108
|
const child = spawn2("bash", ["-c", command], {
|
|
3109
|
-
cwd:
|
|
3109
|
+
cwd: cwd4,
|
|
3110
3110
|
env: { ...process.env, CI: "true", NONINTERACTIVE: "1", NO_COLOR: "1" },
|
|
3111
3111
|
stdio: ["ignore", "pipe", "pipe"],
|
|
3112
3112
|
detached: false
|
|
@@ -6587,7 +6587,8 @@ Commands run non-interactively (CI=true). When running scaffolding tools:
|
|
|
6587
6587
|
requestTimeoutMs: options?.requestTimeoutMs ?? 3e5,
|
|
6588
6588
|
taskTimeoutMs: options?.taskTimeoutMs ?? 12e5,
|
|
6589
6589
|
compactionThreshold: options?.compactionThreshold ?? 4e4,
|
|
6590
|
-
dynamicContext: options?.dynamicContext ?? ""
|
|
6590
|
+
dynamicContext: options?.dynamicContext ?? "",
|
|
6591
|
+
streamEnabled: options?.streamEnabled ?? false
|
|
6591
6592
|
};
|
|
6592
6593
|
}
|
|
6593
6594
|
/** Register a tool for the agent to use */
|
|
@@ -6683,13 +6684,14 @@ Integrate this guidance into your current approach. Continue working on the task
|
|
|
6683
6684
|
});
|
|
6684
6685
|
}
|
|
6685
6686
|
const compacted = this.compactMessages(messages);
|
|
6686
|
-
const
|
|
6687
|
+
const chatRequest = {
|
|
6687
6688
|
messages: compacted,
|
|
6688
6689
|
tools: toolDefs,
|
|
6689
6690
|
temperature: this.options.temperature,
|
|
6690
6691
|
maxTokens: this.options.maxTokens,
|
|
6691
6692
|
timeoutMs: this.options.requestTimeoutMs
|
|
6692
|
-
}
|
|
6693
|
+
};
|
|
6694
|
+
const response = this.options.streamEnabled && this.hasStreamingSupport() ? await this.streamingRequest(chatRequest, turn) : await this.backend.chatCompletion(chatRequest);
|
|
6693
6695
|
totalTokens += response.usage?.totalTokens ?? 0;
|
|
6694
6696
|
const choice = response.choices[0];
|
|
6695
6697
|
if (!choice)
|
|
@@ -6916,6 +6918,90 @@ ${summary}
|
|
|
6916
6918
|
}
|
|
6917
6919
|
}));
|
|
6918
6920
|
}
|
|
6921
|
+
// -------------------------------------------------------------------------
|
|
6922
|
+
// Streaming support — parallel path that emits token events
|
|
6923
|
+
// -------------------------------------------------------------------------
|
|
6924
|
+
/** Check whether the backend supports SSE streaming */
|
|
6925
|
+
hasStreamingSupport() {
|
|
6926
|
+
return typeof this.backend.chatCompletionStream === "function";
|
|
6927
|
+
}
|
|
6928
|
+
/**
|
|
6929
|
+
* Streaming request: calls the SSE endpoint, emits stream events,
|
|
6930
|
+
* assembles and returns the same response format as chatCompletion().
|
|
6931
|
+
* The non-streaming chatCompletion path is NEVER touched by this code.
|
|
6932
|
+
*/
|
|
6933
|
+
async streamingRequest(request, turn) {
|
|
6934
|
+
const backend = this.backend;
|
|
6935
|
+
let content = "";
|
|
6936
|
+
let inThinkTag = false;
|
|
6937
|
+
const toolCallAccumulators = /* @__PURE__ */ new Map();
|
|
6938
|
+
this.emit({ type: "stream_start", turn, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
6939
|
+
for await (const chunk of backend.chatCompletionStream(request)) {
|
|
6940
|
+
if (this.aborted)
|
|
6941
|
+
break;
|
|
6942
|
+
if (chunk.type === "content" && chunk.content) {
|
|
6943
|
+
content += chunk.content;
|
|
6944
|
+
let kind = inThinkTag ? "thinking" : "content";
|
|
6945
|
+
const fragment = chunk.content;
|
|
6946
|
+
if (fragment.includes("<think>")) {
|
|
6947
|
+
inThinkTag = true;
|
|
6948
|
+
kind = "thinking";
|
|
6949
|
+
}
|
|
6950
|
+
if (fragment.includes("</think>")) {
|
|
6951
|
+
inThinkTag = false;
|
|
6952
|
+
kind = "content";
|
|
6953
|
+
}
|
|
6954
|
+
this.emit({
|
|
6955
|
+
type: "stream_token",
|
|
6956
|
+
content: fragment,
|
|
6957
|
+
streamKind: kind,
|
|
6958
|
+
turn,
|
|
6959
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6960
|
+
});
|
|
6961
|
+
}
|
|
6962
|
+
if (chunk.type === "tool_call_delta") {
|
|
6963
|
+
const idx = chunk.toolCallIndex ?? 0;
|
|
6964
|
+
if (!toolCallAccumulators.has(idx)) {
|
|
6965
|
+
toolCallAccumulators.set(idx, {
|
|
6966
|
+
id: chunk.toolCallId ?? crypto.randomUUID(),
|
|
6967
|
+
name: chunk.toolCallName ?? "",
|
|
6968
|
+
args: ""
|
|
6969
|
+
});
|
|
6970
|
+
}
|
|
6971
|
+
const acc = toolCallAccumulators.get(idx);
|
|
6972
|
+
if (chunk.toolCallName)
|
|
6973
|
+
acc.name = chunk.toolCallName;
|
|
6974
|
+
if (chunk.toolCallId)
|
|
6975
|
+
acc.id = chunk.toolCallId;
|
|
6976
|
+
if (chunk.toolCallArgs) {
|
|
6977
|
+
acc.args += chunk.toolCallArgs;
|
|
6978
|
+
this.emit({
|
|
6979
|
+
type: "stream_token",
|
|
6980
|
+
content: chunk.toolCallArgs,
|
|
6981
|
+
streamKind: "tool_args",
|
|
6982
|
+
turn,
|
|
6983
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6984
|
+
});
|
|
6985
|
+
}
|
|
6986
|
+
}
|
|
6987
|
+
}
|
|
6988
|
+
this.emit({ type: "stream_end", content, turn, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
6989
|
+
const cleanContent = content.replace(/<think>[\s\S]*?<\/think>/g, "").trim();
|
|
6990
|
+
const toolCalls = toolCallAccumulators.size > 0 ? Array.from(toolCallAccumulators.values()).map((tc) => {
|
|
6991
|
+
let args;
|
|
6992
|
+
try {
|
|
6993
|
+
args = JSON.parse(tc.args);
|
|
6994
|
+
} catch {
|
|
6995
|
+
args = { _raw: tc.args };
|
|
6996
|
+
}
|
|
6997
|
+
return { id: tc.id, name: tc.name, arguments: args };
|
|
6998
|
+
}) : void 0;
|
|
6999
|
+
return {
|
|
7000
|
+
choices: [{ message: { content: cleanContent || null, toolCalls } }],
|
|
7001
|
+
usage: void 0
|
|
7002
|
+
// SSE responses typically don't include usage in chunks
|
|
7003
|
+
};
|
|
7004
|
+
}
|
|
6919
7005
|
};
|
|
6920
7006
|
OllamaAgenticBackend = class {
|
|
6921
7007
|
baseUrl;
|
|
@@ -6941,7 +7027,9 @@ ${summary}
|
|
|
6941
7027
|
});
|
|
6942
7028
|
if (!resp.ok) {
|
|
6943
7029
|
const text = await resp.text().catch(() => "");
|
|
6944
|
-
|
|
7030
|
+
const isHtml = text.trimStart().startsWith("<!") || text.trimStart().startsWith("<html");
|
|
7031
|
+
const detail = isHtml ? `(received HTML error page \u2014 backend may be behind a proxy/CDN that is timing out)` : text.slice(0, 200);
|
|
7032
|
+
throw new Error(`Backend HTTP ${resp.status}: ${detail}`);
|
|
6945
7033
|
}
|
|
6946
7034
|
const data = await resp.json();
|
|
6947
7035
|
const choices = data.choices ?? [];
|
|
@@ -6973,6 +7061,79 @@ ${summary}
|
|
|
6973
7061
|
usage: usage ? { totalTokens: usage.total_tokens ?? 0 } : void 0
|
|
6974
7062
|
};
|
|
6975
7063
|
}
|
|
7064
|
+
/**
|
|
7065
|
+
* SSE streaming variant — yields StreamChunks as tokens arrive.
|
|
7066
|
+
* Uses `stream: true` and `think: true` so thinking tokens are visible.
|
|
7067
|
+
* The existing chatCompletion() method is completely unmodified.
|
|
7068
|
+
*/
|
|
7069
|
+
async *chatCompletionStream(request) {
|
|
7070
|
+
const body = {
|
|
7071
|
+
model: this.model,
|
|
7072
|
+
messages: request.messages,
|
|
7073
|
+
tools: request.tools,
|
|
7074
|
+
temperature: request.temperature,
|
|
7075
|
+
max_tokens: request.maxTokens,
|
|
7076
|
+
stream: true,
|
|
7077
|
+
think: true
|
|
7078
|
+
};
|
|
7079
|
+
const resp = await fetch(`${this.baseUrl}/v1/chat/completions`, {
|
|
7080
|
+
method: "POST",
|
|
7081
|
+
headers: { "Content-Type": "application/json" },
|
|
7082
|
+
body: JSON.stringify(body),
|
|
7083
|
+
signal: AbortSignal.timeout(request.timeoutMs)
|
|
7084
|
+
});
|
|
7085
|
+
if (!resp.ok) {
|
|
7086
|
+
const text = await resp.text().catch(() => "");
|
|
7087
|
+
const isHtml = text.trimStart().startsWith("<!") || text.trimStart().startsWith("<html");
|
|
7088
|
+
const detail = isHtml ? `(received HTML error page \u2014 backend may be behind a proxy/CDN that is timing out)` : text.slice(0, 200);
|
|
7089
|
+
throw new Error(`Backend HTTP ${resp.status}: ${detail}`);
|
|
7090
|
+
}
|
|
7091
|
+
let sseBuffer = "";
|
|
7092
|
+
const decoder = new TextDecoder();
|
|
7093
|
+
for await (const rawChunk of resp.body) {
|
|
7094
|
+
sseBuffer += decoder.decode(rawChunk, { stream: true });
|
|
7095
|
+
const parts = sseBuffer.split("\n\n");
|
|
7096
|
+
sseBuffer = parts.pop();
|
|
7097
|
+
for (const part of parts) {
|
|
7098
|
+
const line = part.trim();
|
|
7099
|
+
if (!line)
|
|
7100
|
+
continue;
|
|
7101
|
+
if (line === "data: [DONE]")
|
|
7102
|
+
return;
|
|
7103
|
+
if (!line.startsWith("data: "))
|
|
7104
|
+
continue;
|
|
7105
|
+
try {
|
|
7106
|
+
const data = JSON.parse(line.slice(6));
|
|
7107
|
+
const choices = data.choices ?? [];
|
|
7108
|
+
const choice = choices[0];
|
|
7109
|
+
if (!choice)
|
|
7110
|
+
continue;
|
|
7111
|
+
const delta = choice.delta;
|
|
7112
|
+
const finishReason = choice.finish_reason;
|
|
7113
|
+
if (delta?.content) {
|
|
7114
|
+
yield { type: "content", content: delta.content };
|
|
7115
|
+
}
|
|
7116
|
+
const tcDeltas = delta?.tool_calls;
|
|
7117
|
+
if (tcDeltas) {
|
|
7118
|
+
for (const tcd of tcDeltas) {
|
|
7119
|
+
const fn = tcd.function;
|
|
7120
|
+
yield {
|
|
7121
|
+
type: "tool_call_delta",
|
|
7122
|
+
toolCallIndex: tcd.index ?? 0,
|
|
7123
|
+
toolCallId: tcd.id || void 0,
|
|
7124
|
+
toolCallName: fn?.name || void 0,
|
|
7125
|
+
toolCallArgs: fn?.arguments || void 0
|
|
7126
|
+
};
|
|
7127
|
+
}
|
|
7128
|
+
}
|
|
7129
|
+
if (finishReason) {
|
|
7130
|
+
yield { type: "finish", finishReason };
|
|
7131
|
+
}
|
|
7132
|
+
} catch {
|
|
7133
|
+
}
|
|
7134
|
+
}
|
|
7135
|
+
}
|
|
7136
|
+
}
|
|
6976
7137
|
};
|
|
6977
7138
|
}
|
|
6978
7139
|
});
|
|
@@ -7230,6 +7391,7 @@ function renderSlashHelp() {
|
|
|
7230
7391
|
["/update", "Check for updates and auto-install"],
|
|
7231
7392
|
["/voice", "Toggle TTS voice feedback (GLaDOS)"],
|
|
7232
7393
|
["/voice <model>", "Set voice: glados, overwatch"],
|
|
7394
|
+
["/stream", "Toggle real-time token streaming (pastel syntax highlighting)"],
|
|
7233
7395
|
["/verbose", "Toggle verbose mode"],
|
|
7234
7396
|
["/clear", "Clear the screen"],
|
|
7235
7397
|
["/help", "Show this help"],
|
|
@@ -7243,6 +7405,14 @@ function renderSlashHelp() {
|
|
|
7243
7405
|
process.stdout.write(` ${c2.cyan(cmd.padEnd(30))} ${c2.dim(desc)}
|
|
7244
7406
|
`);
|
|
7245
7407
|
}
|
|
7408
|
+
process.stdout.write(`
|
|
7409
|
+
${c2.bold("Project-local overrides:")}
|
|
7410
|
+
|
|
7411
|
+
`);
|
|
7412
|
+
process.stdout.write(` ${c2.dim("Append")} ${c2.yellow("--local")} ${c2.dim("to save settings to .oa/settings.json (this project only).")}
|
|
7413
|
+
`);
|
|
7414
|
+
process.stdout.write(` ${c2.dim("Example:")} ${c2.cyan("/model qwen3:32b --local")} ${c2.dim("/endpoint http://remote:8000/v1 --local")}
|
|
7415
|
+
`);
|
|
7246
7416
|
process.stdout.write(`
|
|
7247
7417
|
${c2.bold("Mid-task steering:")}
|
|
7248
7418
|
|
|
@@ -7499,6 +7669,7 @@ var init_render = __esm({
|
|
|
7499
7669
|
"/config",
|
|
7500
7670
|
"/update",
|
|
7501
7671
|
"/voice",
|
|
7672
|
+
"/stream",
|
|
7502
7673
|
"/verbose",
|
|
7503
7674
|
"/clear",
|
|
7504
7675
|
"/help",
|
|
@@ -7513,7 +7684,9 @@ async function handleSlashCommand(input, ctx) {
|
|
|
7513
7684
|
if (!trimmed.startsWith("/"))
|
|
7514
7685
|
return "not_a_command";
|
|
7515
7686
|
const [cmd, ...rest] = trimmed.slice(1).split(/\s+/);
|
|
7516
|
-
const
|
|
7687
|
+
const hasLocal = rest.includes("--local");
|
|
7688
|
+
const filteredRest = rest.filter((r) => r !== "--local");
|
|
7689
|
+
const arg = filteredRest.join(" ").trim();
|
|
7517
7690
|
switch (cmd) {
|
|
7518
7691
|
case "help":
|
|
7519
7692
|
case "h":
|
|
@@ -7531,8 +7704,13 @@ async function handleSlashCommand(input, ctx) {
|
|
|
7531
7704
|
case "verbose":
|
|
7532
7705
|
case "v":
|
|
7533
7706
|
ctx.setVerbose(!ctx.config.verbose);
|
|
7534
|
-
|
|
7535
|
-
|
|
7707
|
+
if (hasLocal) {
|
|
7708
|
+
ctx.saveLocalSettings({ verbose: ctx.config.verbose });
|
|
7709
|
+
renderInfo(`Verbose mode: ${ctx.config.verbose ? "on" : "off"} (project-local)`);
|
|
7710
|
+
} else {
|
|
7711
|
+
ctx.saveSettings({ verbose: ctx.config.verbose });
|
|
7712
|
+
renderInfo(`Verbose mode: ${ctx.config.verbose ? "on" : "off"}`);
|
|
7713
|
+
}
|
|
7536
7714
|
return "handled";
|
|
7537
7715
|
case "config":
|
|
7538
7716
|
case "cfg":
|
|
@@ -7548,7 +7726,7 @@ async function handleSlashCommand(input, ctx) {
|
|
|
7548
7726
|
return "handled";
|
|
7549
7727
|
case "model":
|
|
7550
7728
|
if (arg) {
|
|
7551
|
-
await switchModel(arg, ctx);
|
|
7729
|
+
await switchModel(arg, ctx, hasLocal);
|
|
7552
7730
|
} else {
|
|
7553
7731
|
await showModelPicker(ctx);
|
|
7554
7732
|
}
|
|
@@ -7558,25 +7736,33 @@ async function handleSlashCommand(input, ctx) {
|
|
|
7558
7736
|
return "handled";
|
|
7559
7737
|
case "endpoint":
|
|
7560
7738
|
case "ep":
|
|
7561
|
-
await handleEndpoint(arg, ctx);
|
|
7739
|
+
await handleEndpoint(arg, ctx, hasLocal);
|
|
7562
7740
|
return "handled";
|
|
7563
7741
|
case "update":
|
|
7564
7742
|
case "upgrade":
|
|
7565
7743
|
await handleUpdate();
|
|
7566
7744
|
return "handled";
|
|
7567
7745
|
case "voice": {
|
|
7746
|
+
const save = hasLocal ? ctx.saveLocalSettings.bind(ctx) : ctx.saveSettings.bind(ctx);
|
|
7568
7747
|
if (arg) {
|
|
7569
7748
|
const msg = await ctx.voiceSetModel(arg);
|
|
7570
|
-
|
|
7571
|
-
renderInfo(msg);
|
|
7749
|
+
save({ voice: true, voiceModel: arg });
|
|
7750
|
+
renderInfo(msg + (hasLocal ? " (project-local)" : ""));
|
|
7572
7751
|
} else {
|
|
7573
7752
|
const msg = await ctx.voiceToggle();
|
|
7574
7753
|
const isOn = msg.toLowerCase().includes("enabled") || msg.toLowerCase().includes("on");
|
|
7575
|
-
|
|
7576
|
-
renderInfo(msg);
|
|
7754
|
+
save({ voice: isOn });
|
|
7755
|
+
renderInfo(msg + (hasLocal ? " (project-local)" : ""));
|
|
7577
7756
|
}
|
|
7578
7757
|
return "handled";
|
|
7579
7758
|
}
|
|
7759
|
+
case "stream": {
|
|
7760
|
+
const isOn = ctx.streamToggle();
|
|
7761
|
+
const save = hasLocal ? ctx.saveLocalSettings.bind(ctx) : ctx.saveSettings.bind(ctx);
|
|
7762
|
+
save({ stream: isOn });
|
|
7763
|
+
renderInfo(`Token streaming: ${isOn ? "on" : "off"}${hasLocal ? " (project-local)" : ""}` + (isOn ? " \u2014 thinking tokens in grey italics, responses with pastel syntax highlighting" : ""));
|
|
7764
|
+
return "handled";
|
|
7765
|
+
}
|
|
7580
7766
|
default:
|
|
7581
7767
|
renderWarning(`Unknown command: /${cmd}. Type /help for available commands.`);
|
|
7582
7768
|
return "handled";
|
|
@@ -7602,7 +7788,7 @@ async function showModelPicker(ctx) {
|
|
|
7602
7788
|
renderError(`Failed to fetch models: ${err instanceof Error ? err.message : String(err)}`);
|
|
7603
7789
|
}
|
|
7604
7790
|
}
|
|
7605
|
-
async function handleEndpoint(arg, ctx) {
|
|
7791
|
+
async function handleEndpoint(arg, ctx, local = false) {
|
|
7606
7792
|
if (!arg) {
|
|
7607
7793
|
process.stdout.write(`
|
|
7608
7794
|
${c2.bold("Current endpoint:")}
|
|
@@ -7665,14 +7851,19 @@ async function handleEndpoint(arg, ctx) {
|
|
|
7665
7851
|
renderInfo("Setting endpoint anyway \u2014 it may come online later.");
|
|
7666
7852
|
}
|
|
7667
7853
|
ctx.setEndpoint(url, backendType, apiKey);
|
|
7668
|
-
|
|
7669
|
-
|
|
7670
|
-
|
|
7671
|
-
|
|
7854
|
+
const endpointSettings = { backendUrl: url, backendType, ...apiKey ? { apiKey } : {} };
|
|
7855
|
+
if (local) {
|
|
7856
|
+
ctx.saveLocalSettings(endpointSettings);
|
|
7857
|
+
} else {
|
|
7858
|
+
setConfigValue("backendUrl", url);
|
|
7859
|
+
setConfigValue("backendType", backendType);
|
|
7860
|
+
if (apiKey) {
|
|
7861
|
+
setConfigValue("apiKey", apiKey);
|
|
7862
|
+
}
|
|
7863
|
+
ctx.saveSettings(endpointSettings);
|
|
7672
7864
|
}
|
|
7673
|
-
ctx.saveSettings({ backendUrl: url, backendType, ...apiKey ? { apiKey } : {} });
|
|
7674
7865
|
process.stdout.write(`
|
|
7675
|
-
${c2.green("\u2714")} Endpoint updated and saved:
|
|
7866
|
+
${c2.green("\u2714")} Endpoint updated and saved${local ? " (project-local)" : ""}:
|
|
7676
7867
|
`);
|
|
7677
7868
|
process.stdout.write(` ${c2.cyan("URL".padEnd(8))} ${url}
|
|
7678
7869
|
`);
|
|
@@ -7738,7 +7929,7 @@ async function handleUpdate() {
|
|
|
7738
7929
|
`);
|
|
7739
7930
|
restartProcess();
|
|
7740
7931
|
}
|
|
7741
|
-
async function switchModel(query, ctx) {
|
|
7932
|
+
async function switchModel(query, ctx, local = false) {
|
|
7742
7933
|
try {
|
|
7743
7934
|
const models = await fetchOllamaModels(ctx.config.backendUrl);
|
|
7744
7935
|
const match = findModel(models, query);
|
|
@@ -7752,8 +7943,15 @@ async function switchModel(query, ctx) {
|
|
|
7752
7943
|
}
|
|
7753
7944
|
const oldModel = ctx.config.model;
|
|
7754
7945
|
ctx.setModel(match.name);
|
|
7755
|
-
|
|
7946
|
+
if (local) {
|
|
7947
|
+
ctx.saveLocalSettings({ model: match.name });
|
|
7948
|
+
} else {
|
|
7949
|
+
ctx.saveSettings({ model: match.name });
|
|
7950
|
+
}
|
|
7756
7951
|
renderModelSwitch(oldModel, match.name);
|
|
7952
|
+
if (local) {
|
|
7953
|
+
renderInfo("Saved as project-local override.");
|
|
7954
|
+
}
|
|
7757
7955
|
} catch (err) {
|
|
7758
7956
|
renderError(`Failed to switch model: ${err instanceof Error ? err.message : String(err)}`);
|
|
7759
7957
|
}
|
|
@@ -7856,8 +8054,8 @@ function modelSupportsToolCalling(modelName) {
|
|
|
7856
8054
|
return false;
|
|
7857
8055
|
}
|
|
7858
8056
|
function ask(rl, question) {
|
|
7859
|
-
return new Promise((
|
|
7860
|
-
rl.question(question, (answer) =>
|
|
8057
|
+
return new Promise((resolve14) => {
|
|
8058
|
+
rl.question(question, (answer) => resolve14(answer.trim()));
|
|
7861
8059
|
});
|
|
7862
8060
|
}
|
|
7863
8061
|
function pullModelWithAutoUpdate(tag) {
|
|
@@ -9284,7 +9482,7 @@ var init_voice = __esm({
|
|
|
9284
9482
|
const cmd = this.getPlayCommand(path);
|
|
9285
9483
|
if (!cmd)
|
|
9286
9484
|
return;
|
|
9287
|
-
return new Promise((
|
|
9485
|
+
return new Promise((resolve14) => {
|
|
9288
9486
|
const child = nodeSpawn(cmd[0], cmd.slice(1), {
|
|
9289
9487
|
stdio: "ignore",
|
|
9290
9488
|
detached: false
|
|
@@ -9293,16 +9491,16 @@ var init_voice = __esm({
|
|
|
9293
9491
|
child.on("close", () => {
|
|
9294
9492
|
if (this.currentPlayback === child)
|
|
9295
9493
|
this.currentPlayback = null;
|
|
9296
|
-
|
|
9494
|
+
resolve14();
|
|
9297
9495
|
});
|
|
9298
9496
|
child.on("error", () => {
|
|
9299
9497
|
if (this.currentPlayback === child)
|
|
9300
9498
|
this.currentPlayback = null;
|
|
9301
|
-
|
|
9499
|
+
resolve14();
|
|
9302
9500
|
});
|
|
9303
9501
|
setTimeout(() => {
|
|
9304
9502
|
this.killPlayback();
|
|
9305
|
-
|
|
9503
|
+
resolve14();
|
|
9306
9504
|
}, 15e3);
|
|
9307
9505
|
});
|
|
9308
9506
|
}
|
|
@@ -9472,6 +9670,266 @@ Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
|
9472
9670
|
}
|
|
9473
9671
|
});
|
|
9474
9672
|
|
|
9673
|
+
// packages/cli/dist/tui/stream-renderer.js
|
|
9674
|
+
function fg256(code, text) {
|
|
9675
|
+
return isTTY4 ? `\x1B[38;5;${code}m${text}\x1B[0m` : text;
|
|
9676
|
+
}
|
|
9677
|
+
function dimText(text) {
|
|
9678
|
+
return isTTY4 ? `\x1B[2m${text}\x1B[0m` : text;
|
|
9679
|
+
}
|
|
9680
|
+
function dimItalic(text) {
|
|
9681
|
+
return isTTY4 ? `\x1B[2;3m${text}\x1B[0m` : text;
|
|
9682
|
+
}
|
|
9683
|
+
var isTTY4, PASTEL, StreamRenderer;
|
|
9684
|
+
var init_stream_renderer = __esm({
|
|
9685
|
+
"packages/cli/dist/tui/stream-renderer.js"() {
|
|
9686
|
+
"use strict";
|
|
9687
|
+
isTTY4 = process.stdout.isTTY ?? false;
|
|
9688
|
+
PASTEL = {
|
|
9689
|
+
key: 222,
|
|
9690
|
+
// light gold — JSON keys
|
|
9691
|
+
string: 183,
|
|
9692
|
+
// light lavender — "string values"
|
|
9693
|
+
number: 156,
|
|
9694
|
+
// soft green — 42, 3.14
|
|
9695
|
+
boolean: 114,
|
|
9696
|
+
// mint green — true, false
|
|
9697
|
+
null: 109,
|
|
9698
|
+
// grey-blue — null
|
|
9699
|
+
bracket: 75,
|
|
9700
|
+
// soft blue — { } [ ]
|
|
9701
|
+
colon: 245,
|
|
9702
|
+
// neutral grey — : ,
|
|
9703
|
+
keyword: 117,
|
|
9704
|
+
// sky blue — function, return, if, else
|
|
9705
|
+
comment: 243,
|
|
9706
|
+
// dim grey — // comments
|
|
9707
|
+
thinking: 245,
|
|
9708
|
+
// neutral grey for thinking tokens
|
|
9709
|
+
toolArg: 111
|
|
9710
|
+
// dim periwinkle for tool arg tokens
|
|
9711
|
+
};
|
|
9712
|
+
StreamRenderer = class {
|
|
9713
|
+
lineBuffer = "";
|
|
9714
|
+
inThinkBlock = false;
|
|
9715
|
+
inCodeBlock = false;
|
|
9716
|
+
codeLang = "";
|
|
9717
|
+
lineStarted = false;
|
|
9718
|
+
flushTimer = null;
|
|
9719
|
+
enabled = false;
|
|
9720
|
+
tokenCount = 0;
|
|
9721
|
+
startTime = 0;
|
|
9722
|
+
/** Track if we're mid-tool-arg display */
|
|
9723
|
+
inToolArgs = false;
|
|
9724
|
+
/** Called when a new model response starts streaming */
|
|
9725
|
+
onStreamStart() {
|
|
9726
|
+
this.lineBuffer = "";
|
|
9727
|
+
this.inThinkBlock = false;
|
|
9728
|
+
this.inCodeBlock = false;
|
|
9729
|
+
this.codeLang = "";
|
|
9730
|
+
this.lineStarted = false;
|
|
9731
|
+
this.inToolArgs = false;
|
|
9732
|
+
this.enabled = true;
|
|
9733
|
+
this.tokenCount = 0;
|
|
9734
|
+
this.startTime = Date.now();
|
|
9735
|
+
this.cancelFlush();
|
|
9736
|
+
}
|
|
9737
|
+
/**
|
|
9738
|
+
* Feed a streamed token into the renderer.
|
|
9739
|
+
* Tokens are buffered per-line and flushed with syntax highlighting.
|
|
9740
|
+
*/
|
|
9741
|
+
write(token, kind) {
|
|
9742
|
+
if (!this.enabled)
|
|
9743
|
+
return;
|
|
9744
|
+
this.tokenCount++;
|
|
9745
|
+
if (kind === "tool_args" && !this.inToolArgs) {
|
|
9746
|
+
this.flushPartial(kind);
|
|
9747
|
+
this.inToolArgs = true;
|
|
9748
|
+
} else if (kind !== "tool_args" && this.inToolArgs) {
|
|
9749
|
+
this.flushPartial(kind);
|
|
9750
|
+
this.inToolArgs = false;
|
|
9751
|
+
}
|
|
9752
|
+
for (const char of token) {
|
|
9753
|
+
this.lineBuffer += char;
|
|
9754
|
+
if (char === "\n") {
|
|
9755
|
+
this.flushLine(kind);
|
|
9756
|
+
}
|
|
9757
|
+
}
|
|
9758
|
+
this.scheduleFlush(kind);
|
|
9759
|
+
}
|
|
9760
|
+
/** Called when streaming ends for this response */
|
|
9761
|
+
onStreamEnd() {
|
|
9762
|
+
if (!this.enabled)
|
|
9763
|
+
return;
|
|
9764
|
+
this.cancelFlush();
|
|
9765
|
+
if (this.lineBuffer.length > 0) {
|
|
9766
|
+
const kind = this.inThinkBlock ? "thinking" : this.inToolArgs ? "tool_args" : "content";
|
|
9767
|
+
this.writeHighlighted(this.lineBuffer, kind);
|
|
9768
|
+
this.lineBuffer = "";
|
|
9769
|
+
}
|
|
9770
|
+
if (this.lineStarted) {
|
|
9771
|
+
process.stdout.write("\n");
|
|
9772
|
+
this.lineStarted = false;
|
|
9773
|
+
}
|
|
9774
|
+
this.enabled = false;
|
|
9775
|
+
}
|
|
9776
|
+
/** Get streaming stats */
|
|
9777
|
+
getStats() {
|
|
9778
|
+
return {
|
|
9779
|
+
tokens: this.tokenCount,
|
|
9780
|
+
durationMs: Date.now() - this.startTime
|
|
9781
|
+
};
|
|
9782
|
+
}
|
|
9783
|
+
// -------------------------------------------------------------------------
|
|
9784
|
+
// Internal rendering
|
|
9785
|
+
// -------------------------------------------------------------------------
|
|
9786
|
+
/**
|
|
9787
|
+
* Flush a complete line (ending with \n) with full syntax highlighting.
|
|
9788
|
+
*/
|
|
9789
|
+
flushLine(kind) {
|
|
9790
|
+
const line = this.lineBuffer;
|
|
9791
|
+
this.lineBuffer = "";
|
|
9792
|
+
if (!line || line === "\n") {
|
|
9793
|
+
if (this.lineStarted) {
|
|
9794
|
+
process.stdout.write("\n");
|
|
9795
|
+
this.lineStarted = false;
|
|
9796
|
+
} else {
|
|
9797
|
+
process.stdout.write("\n");
|
|
9798
|
+
}
|
|
9799
|
+
return;
|
|
9800
|
+
}
|
|
9801
|
+
if (line.includes("<think>")) {
|
|
9802
|
+
this.inThinkBlock = true;
|
|
9803
|
+
const after = line.replace(/<think>/g, "");
|
|
9804
|
+
if (after.trim()) {
|
|
9805
|
+
this.writeHighlighted(after, "thinking");
|
|
9806
|
+
}
|
|
9807
|
+
return;
|
|
9808
|
+
}
|
|
9809
|
+
if (line.includes("</think>")) {
|
|
9810
|
+
this.inThinkBlock = false;
|
|
9811
|
+
const after = line.replace(/<\/think>/g, "");
|
|
9812
|
+
if (after.trim()) {
|
|
9813
|
+
this.writeHighlighted(after, "content");
|
|
9814
|
+
}
|
|
9815
|
+
return;
|
|
9816
|
+
}
|
|
9817
|
+
const trimmedLine = line.replace(/\n$/, "");
|
|
9818
|
+
if (trimmedLine.trimStart().startsWith("```")) {
|
|
9819
|
+
if (this.inCodeBlock) {
|
|
9820
|
+
this.writeRaw(dimText(" \u23BF ") + dimText("```") + "\n");
|
|
9821
|
+
this.inCodeBlock = false;
|
|
9822
|
+
this.codeLang = "";
|
|
9823
|
+
this.lineStarted = false;
|
|
9824
|
+
} else {
|
|
9825
|
+
this.codeLang = trimmedLine.replace(/```/g, "").trim();
|
|
9826
|
+
this.writeRaw(dimText(" \u23BF ") + dimText("```" + this.codeLang) + "\n");
|
|
9827
|
+
this.inCodeBlock = true;
|
|
9828
|
+
this.lineStarted = false;
|
|
9829
|
+
}
|
|
9830
|
+
return;
|
|
9831
|
+
}
|
|
9832
|
+
const effectiveKind = this.inThinkBlock ? "thinking" : kind;
|
|
9833
|
+
this.writeHighlighted(line, effectiveKind);
|
|
9834
|
+
}
|
|
9835
|
+
/**
|
|
9836
|
+
* Write a highlighted line/fragment to stdout.
|
|
9837
|
+
*/
|
|
9838
|
+
writeHighlighted(text, kind) {
|
|
9839
|
+
const raw = text.replace(/\n$/, "");
|
|
9840
|
+
if (!raw)
|
|
9841
|
+
return;
|
|
9842
|
+
const prefix = this.lineStarted ? "" : " \u23BF ";
|
|
9843
|
+
let rendered;
|
|
9844
|
+
switch (kind) {
|
|
9845
|
+
case "thinking":
|
|
9846
|
+
rendered = dimItalic(raw);
|
|
9847
|
+
break;
|
|
9848
|
+
case "tool_args":
|
|
9849
|
+
rendered = this.highlightJson(raw, true);
|
|
9850
|
+
break;
|
|
9851
|
+
case "content":
|
|
9852
|
+
if (this.inCodeBlock) {
|
|
9853
|
+
rendered = this.highlightCode(raw);
|
|
9854
|
+
} else if (this.looksLikeJson(raw)) {
|
|
9855
|
+
rendered = this.highlightJson(raw, false);
|
|
9856
|
+
} else {
|
|
9857
|
+
rendered = raw;
|
|
9858
|
+
}
|
|
9859
|
+
break;
|
|
9860
|
+
}
|
|
9861
|
+
const hasNewline = text.endsWith("\n");
|
|
9862
|
+
this.writeRaw(dimText(prefix) + rendered + (hasNewline ? "\n" : ""));
|
|
9863
|
+
this.lineStarted = !hasNewline;
|
|
9864
|
+
}
|
|
9865
|
+
/** Write raw ANSI text to stdout */
|
|
9866
|
+
writeRaw(text) {
|
|
9867
|
+
process.stdout.write(text);
|
|
9868
|
+
}
|
|
9869
|
+
/** Flush partial buffer (non-newline-terminated tokens) */
|
|
9870
|
+
flushPartial(kind) {
|
|
9871
|
+
if (this.lineBuffer.length === 0)
|
|
9872
|
+
return;
|
|
9873
|
+
const effectiveKind = this.inThinkBlock ? "thinking" : kind;
|
|
9874
|
+
this.writeHighlighted(this.lineBuffer, effectiveKind);
|
|
9875
|
+
this.lineBuffer = "";
|
|
9876
|
+
}
|
|
9877
|
+
/** Schedule a timer to flush partial buffer (for streaming smoothness) */
|
|
9878
|
+
scheduleFlush(kind) {
|
|
9879
|
+
this.cancelFlush();
|
|
9880
|
+
this.flushTimer = setTimeout(() => {
|
|
9881
|
+
if (this.lineBuffer.length > 0) {
|
|
9882
|
+
this.flushPartial(kind);
|
|
9883
|
+
}
|
|
9884
|
+
}, 80);
|
|
9885
|
+
}
|
|
9886
|
+
cancelFlush() {
|
|
9887
|
+
if (this.flushTimer) {
|
|
9888
|
+
clearTimeout(this.flushTimer);
|
|
9889
|
+
this.flushTimer = null;
|
|
9890
|
+
}
|
|
9891
|
+
}
|
|
9892
|
+
// -------------------------------------------------------------------------
|
|
9893
|
+
// Syntax highlighting — pastel palette
|
|
9894
|
+
// -------------------------------------------------------------------------
|
|
9895
|
+
/** Check if a string looks like JSON (starts with { [ " or has key: patterns) */
|
|
9896
|
+
looksLikeJson(text) {
|
|
9897
|
+
const trimmed = text.trimStart();
|
|
9898
|
+
return trimmed.startsWith("{") || trimmed.startsWith("[") || trimmed.startsWith("}") || trimmed.startsWith("]") || /^\s*"[^"]+"\s*:/.test(trimmed);
|
|
9899
|
+
}
|
|
9900
|
+
/**
|
|
9901
|
+
* Highlight a JSON line with pastel colors.
|
|
9902
|
+
* @param dim If true, apply dimmer colors (for tool args)
|
|
9903
|
+
*/
|
|
9904
|
+
highlightJson(line, dim) {
|
|
9905
|
+
const colorKey = dim ? PASTEL.toolArg : PASTEL.key;
|
|
9906
|
+
const colorStr = dim ? PASTEL.toolArg : PASTEL.string;
|
|
9907
|
+
let result = line;
|
|
9908
|
+
result = result.replace(/"([^"]*)"(\s*:)/g, (_m, key, colon) => fg256(colorKey, `"${key}"`) + fg256(PASTEL.colon, colon));
|
|
9909
|
+
result = result.replace(/(:\s*)"([^"]*)"/g, (_m, prefix, val) => fg256(PASTEL.colon, prefix) + fg256(colorStr, `"${val}"`));
|
|
9910
|
+
result = result.replace(/(:\s*)(\d+\.?\d*)/g, (_m, prefix, num) => fg256(PASTEL.colon, prefix) + fg256(PASTEL.number, num));
|
|
9911
|
+
result = result.replace(/(:\s*)(true|false)/g, (_m, prefix, bool) => fg256(PASTEL.colon, prefix) + fg256(PASTEL.boolean, bool));
|
|
9912
|
+
result = result.replace(/(:\s*)(null)/g, (_m, prefix, n) => fg256(PASTEL.colon, prefix) + fg256(PASTEL.null, n));
|
|
9913
|
+
result = result.replace(/([{}[\]])/g, (_m, b) => fg256(PASTEL.bracket, b));
|
|
9914
|
+
return dim ? dimText(result) : result;
|
|
9915
|
+
}
|
|
9916
|
+
/**
|
|
9917
|
+
* Highlight a code line with basic pastel syntax coloring.
|
|
9918
|
+
*/
|
|
9919
|
+
highlightCode(line) {
|
|
9920
|
+
let result = line;
|
|
9921
|
+
result = result.replace(/"([^"]*)"/g, (_m, s) => fg256(PASTEL.string, `"${s}"`));
|
|
9922
|
+
result = result.replace(/'([^']*)'/g, (_m, s) => fg256(PASTEL.string, `'${s}'`));
|
|
9923
|
+
result = result.replace(/\b(\d+\.?\d*)\b/g, (_m, n) => fg256(PASTEL.number, n));
|
|
9924
|
+
result = result.replace(/\b(true|false|null|undefined|None|True|False)\b/g, (_m, kw) => fg256(PASTEL.boolean, kw));
|
|
9925
|
+
result = result.replace(/\b(function|const|let|var|return|if|else|for|while|import|export|from|class|async|await|def|self|try|catch|finally|throw|new|typeof|instanceof)\b/g, (_m, kw) => fg256(PASTEL.keyword, kw));
|
|
9926
|
+
result = result.replace(/(\/\/.*$|#.*$)/gm, (_m, c3) => fg256(PASTEL.comment, c3));
|
|
9927
|
+
return result;
|
|
9928
|
+
}
|
|
9929
|
+
};
|
|
9930
|
+
}
|
|
9931
|
+
});
|
|
9932
|
+
|
|
9475
9933
|
// packages/cli/dist/tui/interactive.js
|
|
9476
9934
|
import * as readline2 from "node:readline";
|
|
9477
9935
|
import { cwd } from "node:process";
|
|
@@ -9639,7 +10097,7 @@ Use task_status("${taskId}") or task_output("${taskId}") to check progress.`
|
|
|
9639
10097
|
}
|
|
9640
10098
|
};
|
|
9641
10099
|
}
|
|
9642
|
-
function startTask(task, config, repoRoot, voice) {
|
|
10100
|
+
function startTask(task, config, repoRoot, voice, stream) {
|
|
9643
10101
|
const projectCtx = buildProjectContext(repoRoot);
|
|
9644
10102
|
const dynamicContext = formatContextForPrompt(projectCtx);
|
|
9645
10103
|
const backend = new OllamaAgenticBackend(config.backendUrl.replace(/\/$/, ""), config.model);
|
|
@@ -9650,7 +10108,8 @@ function startTask(task, config, repoRoot, voice) {
|
|
|
9650
10108
|
requestTimeoutMs: config.timeoutMs,
|
|
9651
10109
|
taskTimeoutMs: config.timeoutMs * 4,
|
|
9652
10110
|
compactionThreshold: 4e4,
|
|
9653
|
-
dynamicContext
|
|
10111
|
+
dynamicContext,
|
|
10112
|
+
streamEnabled: stream?.enabled ?? false
|
|
9654
10113
|
});
|
|
9655
10114
|
runner.registerTools(buildTools(repoRoot, config));
|
|
9656
10115
|
runner.onEvent((event) => {
|
|
@@ -9674,10 +10133,23 @@ function startTask(task, config, repoRoot, voice) {
|
|
|
9674
10133
|
}
|
|
9675
10134
|
break;
|
|
9676
10135
|
case "model_response":
|
|
9677
|
-
if (config.verbose && event.content) {
|
|
10136
|
+
if (config.verbose && !stream?.enabled && event.content) {
|
|
9678
10137
|
renderAssistantText(event.content);
|
|
9679
10138
|
}
|
|
9680
10139
|
break;
|
|
10140
|
+
case "stream_start":
|
|
10141
|
+
if (stream?.enabled)
|
|
10142
|
+
stream.renderer.onStreamStart();
|
|
10143
|
+
break;
|
|
10144
|
+
case "stream_token":
|
|
10145
|
+
if (stream?.enabled) {
|
|
10146
|
+
stream.renderer.write(event.content ?? "", event.streamKind ?? "content");
|
|
10147
|
+
}
|
|
10148
|
+
break;
|
|
10149
|
+
case "stream_end":
|
|
10150
|
+
if (stream?.enabled)
|
|
10151
|
+
stream.renderer.onStreamEnd();
|
|
10152
|
+
break;
|
|
9681
10153
|
case "user_interrupt":
|
|
9682
10154
|
break;
|
|
9683
10155
|
case "compaction":
|
|
@@ -9739,6 +10211,15 @@ async function startInteractive(config, repoPath) {
|
|
|
9739
10211
|
config = { ...config, apiKey: savedSettings.apiKey };
|
|
9740
10212
|
if (savedSettings.verbose !== void 0)
|
|
9741
10213
|
config = { ...config, verbose: savedSettings.verbose };
|
|
10214
|
+
if (savedSettings.maxRetries !== void 0)
|
|
10215
|
+
config = { ...config, maxRetries: savedSettings.maxRetries };
|
|
10216
|
+
if (savedSettings.timeoutMs !== void 0)
|
|
10217
|
+
config = { ...config, timeoutMs: savedSettings.timeoutMs };
|
|
10218
|
+
if (savedSettings.dryRun !== void 0)
|
|
10219
|
+
config = { ...config, dryRun: savedSettings.dryRun };
|
|
10220
|
+
if (savedSettings.dbPath)
|
|
10221
|
+
config = { ...config, dbPath: savedSettings.dbPath };
|
|
10222
|
+
let streamEnabled = savedSettings.stream ?? false;
|
|
9742
10223
|
if (!isResumed) {
|
|
9743
10224
|
const needsSetup = isFirstRun() || !await isModelAvailable(config);
|
|
9744
10225
|
if (needsSetup && config.backendType === "ollama") {
|
|
@@ -9782,6 +10263,7 @@ async function startInteractive(config, repoPath) {
|
|
|
9782
10263
|
`);
|
|
9783
10264
|
}
|
|
9784
10265
|
const voiceEngine = new VoiceEngine();
|
|
10266
|
+
const streamRenderer = new StreamRenderer();
|
|
9785
10267
|
if (savedSettings.voice) {
|
|
9786
10268
|
voiceEngine.toggle().catch(() => {
|
|
9787
10269
|
});
|
|
@@ -9856,12 +10338,22 @@ async function startInteractive(config, repoPath) {
|
|
|
9856
10338
|
async voiceSetModel(id) {
|
|
9857
10339
|
return voiceEngine.setModel(id);
|
|
9858
10340
|
},
|
|
10341
|
+
streamToggle() {
|
|
10342
|
+
streamEnabled = !streamEnabled;
|
|
10343
|
+
return streamEnabled;
|
|
10344
|
+
},
|
|
9859
10345
|
saveSettings(settings) {
|
|
9860
10346
|
try {
|
|
9861
10347
|
saveProjectSettings(repoRoot, settings);
|
|
9862
10348
|
saveGlobalSettings(settings);
|
|
9863
10349
|
} catch {
|
|
9864
10350
|
}
|
|
10351
|
+
},
|
|
10352
|
+
saveLocalSettings(settings) {
|
|
10353
|
+
try {
|
|
10354
|
+
saveProjectSettings(repoRoot, settings);
|
|
10355
|
+
} catch {
|
|
10356
|
+
}
|
|
9865
10357
|
}
|
|
9866
10358
|
};
|
|
9867
10359
|
showPrompt();
|
|
@@ -9926,7 +10418,10 @@ ${c2.dim("Goodbye!")}
|
|
|
9926
10418
|
}
|
|
9927
10419
|
renderUserMessage(isImage ? `[Image: ${cleanPath}]` : fullInput);
|
|
9928
10420
|
try {
|
|
9929
|
-
const task = startTask(fullInput, currentConfig, repoRoot, voiceEngine
|
|
10421
|
+
const task = startTask(fullInput, currentConfig, repoRoot, voiceEngine, {
|
|
10422
|
+
enabled: streamEnabled,
|
|
10423
|
+
renderer: streamRenderer
|
|
10424
|
+
});
|
|
9930
10425
|
activeTask = task;
|
|
9931
10426
|
showPrompt();
|
|
9932
10427
|
await task.promise;
|
|
@@ -10022,6 +10517,7 @@ var init_interactive = __esm({
|
|
|
10022
10517
|
init_render();
|
|
10023
10518
|
init_carousel();
|
|
10024
10519
|
init_voice();
|
|
10520
|
+
init_stream_renderer();
|
|
10025
10521
|
taskManager = new BackgroundTaskManager();
|
|
10026
10522
|
}
|
|
10027
10523
|
});
|
|
@@ -10489,8 +10985,17 @@ var config_exports = {};
|
|
|
10489
10985
|
__export(config_exports, {
|
|
10490
10986
|
configCommand: () => configCommand
|
|
10491
10987
|
});
|
|
10492
|
-
import { join as join19 } from "node:path";
|
|
10988
|
+
import { join as join19, resolve as resolve13 } from "node:path";
|
|
10493
10989
|
import { homedir as homedir7 } from "node:os";
|
|
10990
|
+
import { cwd as cwd3 } from "node:process";
|
|
10991
|
+
function coerceForSettings(key, value) {
|
|
10992
|
+
if (INT_KEYS.has(key))
|
|
10993
|
+
return parseInt(value, 10);
|
|
10994
|
+
if (BOOL_KEYS.has(key)) {
|
|
10995
|
+
return value === "1" || value.toLowerCase() === "true" || value.toLowerCase() === "yes";
|
|
10996
|
+
}
|
|
10997
|
+
return value;
|
|
10998
|
+
}
|
|
10494
10999
|
async function configCommand(opts, config) {
|
|
10495
11000
|
if (opts.subCommand === "set") {
|
|
10496
11001
|
return handleSet(opts, config);
|
|
@@ -10501,9 +11006,11 @@ async function configCommand(opts, config) {
|
|
|
10501
11006
|
return handleShow(opts, config);
|
|
10502
11007
|
}
|
|
10503
11008
|
function handleShow(opts, config) {
|
|
11009
|
+
const repoRoot = resolve13(opts.repoPath ?? cwd3());
|
|
10504
11010
|
printHeader("Configuration");
|
|
10505
|
-
printSection("Active Settings");
|
|
11011
|
+
printSection("Active Settings (merged)");
|
|
10506
11012
|
printKeyValue("backendUrl", config.backendUrl, 2);
|
|
11013
|
+
printKeyValue("backendType", config.backendType, 2);
|
|
10507
11014
|
printKeyValue("model", config.model, 2);
|
|
10508
11015
|
printKeyValue("apiKey", config.apiKey ? "[set]" : "[not set]", 2);
|
|
10509
11016
|
printKeyValue("maxRetries", String(config.maxRetries), 2);
|
|
@@ -10511,18 +11018,34 @@ function handleShow(opts, config) {
|
|
|
10511
11018
|
printKeyValue("dryRun", String(config.dryRun), 2);
|
|
10512
11019
|
printKeyValue("verbose", String(config.verbose), 2);
|
|
10513
11020
|
printKeyValue("dbPath", config.dbPath, 2);
|
|
11021
|
+
const projectSettings = loadProjectSettings(repoRoot);
|
|
11022
|
+
const projectKeys = Object.entries(projectSettings).filter(([, v]) => v !== void 0);
|
|
11023
|
+
if (projectKeys.length > 0) {
|
|
11024
|
+
printSection(`Project Overrides (.oa/settings.json)`);
|
|
11025
|
+
for (const [k, v] of projectKeys) {
|
|
11026
|
+
printKeyValue(k, String(v), 2);
|
|
11027
|
+
}
|
|
11028
|
+
} else {
|
|
11029
|
+
printSection("Project Overrides");
|
|
11030
|
+
printInfo(" (none \u2014 use 'config set KEY VALUE --local' to add)");
|
|
11031
|
+
}
|
|
11032
|
+
const globalSettings = loadGlobalSettings();
|
|
11033
|
+
const globalKeys = Object.entries(globalSettings).filter(([, v]) => v !== void 0);
|
|
11034
|
+
if (globalKeys.length > 0) {
|
|
11035
|
+
printSection("Global Settings (~/.open-agents/settings.json)");
|
|
11036
|
+
for (const [k, v] of globalKeys) {
|
|
11037
|
+
printKeyValue(k, String(v), 2);
|
|
11038
|
+
}
|
|
11039
|
+
}
|
|
10514
11040
|
printSection("Config File");
|
|
10515
11041
|
printInfo(`~/.open-agents/config.json (${join19(homedir7(), ".open-agents", "config.json")})`);
|
|
10516
|
-
printSection("
|
|
10517
|
-
printInfo("
|
|
10518
|
-
printInfo("
|
|
10519
|
-
printInfo("
|
|
10520
|
-
printInfo("
|
|
10521
|
-
printInfo("
|
|
10522
|
-
printInfo("
|
|
10523
|
-
printInfo("OPEN_AGENTS_VERBOSE \u2014 override verbose (true/false)");
|
|
10524
|
-
printInfo("OPEN_AGENTS_DB_PATH \u2014 override dbPath");
|
|
10525
|
-
printInfo("VLLM_BASE_URL \u2014 fallback for backendUrl");
|
|
11042
|
+
printSection("Priority Chain");
|
|
11043
|
+
printInfo(" 1. CLI flags (--model, --backend-url, etc.)");
|
|
11044
|
+
printInfo(" 2. Project .oa/settings.json (--local)");
|
|
11045
|
+
printInfo(" 3. Global ~/.open-agents/settings.json");
|
|
11046
|
+
printInfo(" 4. Environment variables (OPEN_AGENTS_*)");
|
|
11047
|
+
printInfo(" 5. Global ~/.open-agents/config.json");
|
|
11048
|
+
printInfo(" 6. Built-in defaults");
|
|
10526
11049
|
if (opts.verbose) {
|
|
10527
11050
|
printSection("All Settable Keys");
|
|
10528
11051
|
for (const [key, desc] of Object.entries(CONFIG_KEYS)) {
|
|
@@ -10533,7 +11056,7 @@ function handleShow(opts, config) {
|
|
|
10533
11056
|
function handleSet(opts, _config) {
|
|
10534
11057
|
const { key, value } = opts;
|
|
10535
11058
|
if (!key) {
|
|
10536
|
-
printError("Usage: open-agents config set KEY VALUE");
|
|
11059
|
+
printError("Usage: open-agents config set KEY VALUE [--local]");
|
|
10537
11060
|
printInfo("Run 'open-agents config keys' to see available keys");
|
|
10538
11061
|
process.exit(1);
|
|
10539
11062
|
}
|
|
@@ -10547,37 +11070,64 @@ function handleSet(opts, _config) {
|
|
|
10547
11070
|
printInfo("Run 'open-agents config keys' to see available keys");
|
|
10548
11071
|
process.exit(1);
|
|
10549
11072
|
}
|
|
10550
|
-
|
|
10551
|
-
|
|
10552
|
-
|
|
10553
|
-
|
|
10554
|
-
|
|
10555
|
-
|
|
10556
|
-
|
|
11073
|
+
if (opts.local) {
|
|
11074
|
+
const repoRoot = resolve13(opts.repoPath ?? cwd3());
|
|
11075
|
+
try {
|
|
11076
|
+
initOaDirectory(repoRoot);
|
|
11077
|
+
const coerced = coerceForSettings(key, value);
|
|
11078
|
+
saveProjectSettings(repoRoot, { [key]: coerced });
|
|
11079
|
+
printSuccess(`Project override set: ${key} = ${value}`);
|
|
11080
|
+
printInfo(`Saved to ${join19(repoRoot, ".oa", "settings.json")}`);
|
|
11081
|
+
printInfo("This override applies only when running in this workspace.");
|
|
11082
|
+
} catch (err) {
|
|
11083
|
+
printError(`Failed to save: ${err instanceof Error ? err.message : String(err)}`);
|
|
11084
|
+
process.exit(1);
|
|
11085
|
+
}
|
|
11086
|
+
} else {
|
|
11087
|
+
try {
|
|
11088
|
+
setConfigValue(key, value);
|
|
11089
|
+
printSuccess(`Config updated: ${key} = ${value}`);
|
|
11090
|
+
printInfo(`Saved to ~/.open-agents/config.json`);
|
|
11091
|
+
printInfo("Tip: Use --local to set project-specific overrides.");
|
|
11092
|
+
} catch (err) {
|
|
11093
|
+
printError(`Failed to save config: ${err instanceof Error ? err.message : String(err)}`);
|
|
11094
|
+
process.exit(1);
|
|
11095
|
+
}
|
|
10557
11096
|
}
|
|
10558
11097
|
}
|
|
10559
11098
|
function handleKeys() {
|
|
10560
11099
|
printHeader("Config Keys");
|
|
11100
|
+
printInfo("All keys can be set globally or per-project (--local):\n");
|
|
10561
11101
|
for (const [key, desc] of Object.entries(CONFIG_KEYS)) {
|
|
10562
11102
|
printKeyValue(key, desc, 2);
|
|
10563
11103
|
}
|
|
11104
|
+
printInfo("\nUsage:");
|
|
11105
|
+
printInfo(" oa config set model qwen3.5:122b # global default");
|
|
11106
|
+
printInfo(" oa config set model qwen3.5:122b --local # this project only");
|
|
10564
11107
|
}
|
|
10565
|
-
var CONFIG_KEYS;
|
|
11108
|
+
var CONFIG_KEYS, INT_KEYS, BOOL_KEYS;
|
|
10566
11109
|
var init_config3 = __esm({
|
|
10567
11110
|
"packages/cli/dist/commands/config.js"() {
|
|
10568
11111
|
"use strict";
|
|
10569
11112
|
init_config();
|
|
11113
|
+
init_oa_directory();
|
|
10570
11114
|
init_output();
|
|
10571
11115
|
CONFIG_KEYS = {
|
|
10572
|
-
backendUrl: "
|
|
10573
|
-
|
|
11116
|
+
backendUrl: "Backend base URL (Ollama or OpenAI-compatible)",
|
|
11117
|
+
backendType: "Backend type: ollama, vllm, fake",
|
|
11118
|
+
model: "Model name to use",
|
|
10574
11119
|
apiKey: "Bearer token for authenticated deployments",
|
|
10575
11120
|
maxRetries: "Maximum HTTP retries (integer)",
|
|
10576
11121
|
timeoutMs: "Per-request timeout in milliseconds (integer)",
|
|
10577
11122
|
dryRun: "Dry-run mode - patches not written (true/false)",
|
|
10578
11123
|
verbose: "Verbose output (true/false)",
|
|
10579
|
-
dbPath: "Path to SQLite memory database"
|
|
11124
|
+
dbPath: "Path to SQLite memory database",
|
|
11125
|
+
voice: "Enable TTS voice feedback (true/false)",
|
|
11126
|
+
voiceModel: "TTS voice model: glados, overwatch",
|
|
11127
|
+
stream: "Enable real-time token streaming with pastel syntax highlighting (true/false)"
|
|
10580
11128
|
};
|
|
11129
|
+
INT_KEYS = /* @__PURE__ */ new Set(["maxRetries", "timeoutMs"]);
|
|
11130
|
+
BOOL_KEYS = /* @__PURE__ */ new Set(["dryRun", "verbose", "voice", "stream"]);
|
|
10581
11131
|
}
|
|
10582
11132
|
});
|
|
10583
11133
|
|
|
@@ -10678,7 +11228,7 @@ async function serveVllm(opts, config) {
|
|
|
10678
11228
|
await runVllmServer(args, opts.verbose ?? false);
|
|
10679
11229
|
}
|
|
10680
11230
|
async function runVllmServer(args, verbose) {
|
|
10681
|
-
return new Promise((
|
|
11231
|
+
return new Promise((resolve14, reject) => {
|
|
10682
11232
|
const child = spawn3("python", args, {
|
|
10683
11233
|
stdio: verbose ? "inherit" : ["ignore", "pipe", "pipe"],
|
|
10684
11234
|
env: { ...process.env }
|
|
@@ -10713,10 +11263,10 @@ async function runVllmServer(args, verbose) {
|
|
|
10713
11263
|
child.once("exit", (code, signal) => {
|
|
10714
11264
|
if (signal) {
|
|
10715
11265
|
printInfo(`vLLM server stopped by signal ${signal}`);
|
|
10716
|
-
|
|
11266
|
+
resolve14();
|
|
10717
11267
|
} else if (code === 0) {
|
|
10718
11268
|
printSuccess("vLLM server exited cleanly");
|
|
10719
|
-
|
|
11269
|
+
resolve14();
|
|
10720
11270
|
} else {
|
|
10721
11271
|
printError(`vLLM server exited with code ${code}`);
|
|
10722
11272
|
reject(new Error(`vLLM exited with code ${code}`));
|
|
@@ -11060,6 +11610,7 @@ function parseCliArgs(argv) {
|
|
|
11060
11610
|
"max-retries": { type: "string" },
|
|
11061
11611
|
"timeout-ms": { type: "string" },
|
|
11062
11612
|
offline: { type: "boolean" },
|
|
11613
|
+
local: { type: "boolean", short: "l" },
|
|
11063
11614
|
port: { type: "string" },
|
|
11064
11615
|
suite: { type: "string" },
|
|
11065
11616
|
help: { type: "boolean", short: "h" },
|
|
@@ -11081,6 +11632,7 @@ function parseCliArgs(argv) {
|
|
|
11081
11632
|
maxRetries: typeof values["max-retries"] === "string" ? parseInt(values["max-retries"], 10) : void 0,
|
|
11082
11633
|
timeoutMs: typeof values["timeout-ms"] === "string" ? parseInt(values["timeout-ms"], 10) : void 0,
|
|
11083
11634
|
offline: values.offline === true,
|
|
11635
|
+
local: values.local === true,
|
|
11084
11636
|
help: values.help === true,
|
|
11085
11637
|
version: values.version === true
|
|
11086
11638
|
};
|
|
@@ -11145,6 +11697,7 @@ Flags:
|
|
|
11145
11697
|
-r, --repo <path> Repository root (default: cwd)
|
|
11146
11698
|
--dry-run Validate patches, don't write to disk
|
|
11147
11699
|
--offline Use FakeBackend, no backend connection needed
|
|
11700
|
+
-l, --local Save settings to .oa/settings.json (project-local)
|
|
11148
11701
|
-v, --verbose Verbose output
|
|
11149
11702
|
--max-retries <n> Max retries per model request
|
|
11150
11703
|
--timeout-ms <ms> Overall task timeout
|
|
@@ -11239,7 +11792,9 @@ async function main() {
|
|
|
11239
11792
|
subCommand: parsed.configSubCommand,
|
|
11240
11793
|
key: parsed.configKey,
|
|
11241
11794
|
value: parsed.configValue,
|
|
11242
|
-
verbose: parsed.verbose
|
|
11795
|
+
verbose: parsed.verbose,
|
|
11796
|
+
local: parsed.local,
|
|
11797
|
+
repoPath: parsed.repoPath
|
|
11243
11798
|
}, config);
|
|
11244
11799
|
break;
|
|
11245
11800
|
}
|
package/package.json
CHANGED