@aiden-ade/sandbox-agent 0.1.13 → 0.1.15
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.cjs +1408 -982
- package/package.json +1 -1
- package/dist/core-agent.d.ts +0 -33
- package/dist/core-agent.d.ts.map +0 -1
- package/dist/core-agent.js +0 -218
- package/dist/core-agent.js.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -53
- package/dist/index.js.map +0 -1
- package/dist/runtime-backends.d.ts +0 -2
- package/dist/runtime-backends.d.ts.map +0 -1
- package/dist/runtime-backends.js +0 -2
- package/dist/runtime-backends.js.map +0 -1
- package/dist/sandbox.d.ts +0 -25
- package/dist/sandbox.d.ts.map +0 -1
- package/dist/sandbox.js +0 -185
- package/dist/sandbox.js.map +0 -1
- package/dist/types.d.ts +0 -6
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -6
- package/dist/types.js.map +0 -1
- package/dist/updater.d.ts +0 -15
- package/dist/updater.d.ts.map +0 -1
- package/dist/updater.js +0 -54
- package/dist/updater.js.map +0 -1
- package/dist/version.d.ts +0 -2
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js +0 -2
- package/dist/version.js.map +0 -1
- package/dist/web-presenter.d.ts +0 -33
- package/dist/web-presenter.d.ts.map +0 -1
- package/dist/web-presenter.js +0 -100
- package/dist/web-presenter.js.map +0 -1
- package/dist/ws-client.d.ts +0 -79
- package/dist/ws-client.d.ts.map +0 -1
- package/dist/ws-client.js +0 -74
- package/dist/ws-client.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -4812,11 +4812,6 @@ var require_websocket_server = __commonJS({
|
|
|
4812
4812
|
|
|
4813
4813
|
// ../agent-core/dist/index.js
|
|
4814
4814
|
var import_child_process = require("child_process");
|
|
4815
|
-
var import_crypto = require("crypto");
|
|
4816
|
-
var import_fs = require("fs");
|
|
4817
|
-
var import_os = require("os");
|
|
4818
|
-
var import_path = require("path");
|
|
4819
|
-
var import_readline = require("readline");
|
|
4820
4815
|
|
|
4821
4816
|
// ../shared/dist/agent/agent-definitions.js
|
|
4822
4817
|
var COORDINATOR_PROMPT = `## You are the Coordinator
|
|
@@ -4940,7 +4935,7 @@ var GENERAL_PROMPT = `## You are the General Agent
|
|
|
4940
4935
|
|
|
4941
4936
|
You are a flexible general-purpose agent (an Aiden agent profile) for work that does not need a specialist profile. Wear whatever hat the moment demands \u2014 product thinker, planner, programmer, debugger, tester. Read the room and pick the hat.
|
|
4942
4937
|
|
|
4943
|
-
|
|
4938
|
+
Your name is Aiden, General Agent.
|
|
4944
4939
|
|
|
4945
4940
|
Clarify the goal only when required, inspect the relevant context first, then complete the requested work directly. Keep changes focused, avoid unrelated refactors, and validate the result before reporting back.
|
|
4946
4941
|
|
|
@@ -5175,8 +5170,8 @@ var PLAN_MODE_PROMPT = [
|
|
|
5175
5170
|
"Include dependencies, ordering, risks, key files/functions, and decision points where relevant.",
|
|
5176
5171
|
"When the plan is ready, you MUST persist it by calling the create_plan MCP tool.",
|
|
5177
5172
|
"Pass title, content, summary, taskId, and conversationId. Include teamId too when available.",
|
|
5178
|
-
"If create_plan fails, retry it. Do not
|
|
5179
|
-
"After create_plan succeeds,
|
|
5173
|
+
"If create_plan fails, retry it. Do not finish plan mode until the plan is persisted successfully.",
|
|
5174
|
+
"After create_plan succeeds, present the plan title and artifact link to the user for approval."
|
|
5180
5175
|
].join("\n");
|
|
5181
5176
|
var TEST_MODE_PROMPT = [
|
|
5182
5177
|
"You are in test mode. Verify the implemented feature carefully.",
|
|
@@ -5206,6 +5201,17 @@ function getAidenAgentPrompt(agentId) {
|
|
|
5206
5201
|
// ../shared/dist/agent/session-result.js
|
|
5207
5202
|
var SESSION_RESUME_FAILED_MESSAGE = "Session resume failed. The previous session may have expired or is no longer available. Please start a new session.";
|
|
5208
5203
|
|
|
5204
|
+
// ../agent-core/dist/index.js
|
|
5205
|
+
var import_fs = require("fs");
|
|
5206
|
+
var import_os = require("os");
|
|
5207
|
+
var import_path = require("path");
|
|
5208
|
+
var import_crypto = require("crypto");
|
|
5209
|
+
var import_fs2 = require("fs");
|
|
5210
|
+
var import_fs3 = require("fs");
|
|
5211
|
+
var import_os2 = require("os");
|
|
5212
|
+
var import_path2 = require("path");
|
|
5213
|
+
var import_readline = require("readline");
|
|
5214
|
+
|
|
5209
5215
|
// ../shared/dist/constants/agent.js
|
|
5210
5216
|
var INTERACTIVE_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
5211
5217
|
"askuserquestion",
|
|
@@ -5232,17 +5238,9 @@ var DEFAULT_TEAM_STATUSES = STATUS_OPTIONS.map((o) => ({
|
|
|
5232
5238
|
}));
|
|
5233
5239
|
|
|
5234
5240
|
// ../agent-core/dist/index.js
|
|
5235
|
-
var
|
|
5236
|
-
var
|
|
5237
|
-
var
|
|
5238
|
-
function formatCliSpawnError(command, error) {
|
|
5239
|
-
if (error.code === "ENOENT") {
|
|
5240
|
-
return new Error(
|
|
5241
|
-
`CLI command not found: ${command}. Install it or configure this provider with the full executable path.`
|
|
5242
|
-
);
|
|
5243
|
-
}
|
|
5244
|
-
return error;
|
|
5245
|
-
}
|
|
5241
|
+
var import_fs4 = require("fs");
|
|
5242
|
+
var import_os3 = require("os");
|
|
5243
|
+
var import_path3 = require("path");
|
|
5246
5244
|
function isLikelyProviderAuthError(stderr) {
|
|
5247
5245
|
const lower = stderr.toLowerCase();
|
|
5248
5246
|
return lower.includes("api key") || lower.includes("api_key") || lower.includes("invalid api key") || lower.includes("api key invalid") || lower.includes("unauthorized") || lower.includes("authentication failed") || lower.includes("authentication error") || lower.includes("provider settings") || lower.includes("invalid token") || lower.includes("token expired");
|
|
@@ -5287,23 +5285,6 @@ function isTransientError(errorMessage) {
|
|
|
5287
5285
|
const lower = errorMessage.toLowerCase();
|
|
5288
5286
|
return lower.includes("rate limit") || lower.includes("overloaded") || lower.includes("503") || lower.includes("429") || lower.includes("service unavailable") || lower.includes("etimedout") || lower.includes("econnreset") || lower.includes("econnrefused") || lower.includes("epipe");
|
|
5289
5287
|
}
|
|
5290
|
-
function buildPromptWithSystem(config, promptText) {
|
|
5291
|
-
const parts2 = [
|
|
5292
|
-
config.systemPrompt?.trim(),
|
|
5293
|
-
config.systemPromptAppend?.trim(),
|
|
5294
|
-
promptText.trim()
|
|
5295
|
-
].filter((value2) => Boolean(value2 && value2.length > 0));
|
|
5296
|
-
return parts2.join("\n\n");
|
|
5297
|
-
}
|
|
5298
|
-
function buildPlanModePrefix(promptText) {
|
|
5299
|
-
return [
|
|
5300
|
-
"You are in plan-only mode.",
|
|
5301
|
-
"Do not make code changes or execute write operations.",
|
|
5302
|
-
"Focus on analysis, planning, and explaining the next steps.",
|
|
5303
|
-
"",
|
|
5304
|
-
promptText
|
|
5305
|
-
].join("\n");
|
|
5306
|
-
}
|
|
5307
5288
|
function planCliSpawn(command, args, env = process.env) {
|
|
5308
5289
|
const isWindows = process.platform === "win32";
|
|
5309
5290
|
const comSpec = env.ComSpec ?? process.env.ComSpec ?? "cmd.exe";
|
|
@@ -5339,48 +5320,334 @@ function spawnCli(command, args, context) {
|
|
|
5339
5320
|
detached: process.platform !== "win32"
|
|
5340
5321
|
});
|
|
5341
5322
|
}
|
|
5342
|
-
function
|
|
5343
|
-
return
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5323
|
+
function shouldUseReadOnlyRuntimePermissions(config) {
|
|
5324
|
+
if (isReadOnlyAgent(config.agentId)) return true;
|
|
5325
|
+
switch (config.mode) {
|
|
5326
|
+
case "plan":
|
|
5327
|
+
case "ask":
|
|
5328
|
+
case "review":
|
|
5329
|
+
return true;
|
|
5330
|
+
default:
|
|
5331
|
+
return false;
|
|
5332
|
+
}
|
|
5333
|
+
}
|
|
5334
|
+
function getClaudePermissionMode(config) {
|
|
5335
|
+
switch (config.mode) {
|
|
5336
|
+
case "plan":
|
|
5337
|
+
case "ask":
|
|
5338
|
+
case "review":
|
|
5339
|
+
return "plan";
|
|
5340
|
+
default:
|
|
5341
|
+
return "bypassPermissions";
|
|
5342
|
+
}
|
|
5343
|
+
}
|
|
5344
|
+
function getClaudeDisallowedTools(config) {
|
|
5345
|
+
return isReadOnlyAgent(config.agentId) ? ["Edit", "Write", "MultiEdit", "NotebookEdit"] : [];
|
|
5346
|
+
}
|
|
5347
|
+
function parseJsonObject(value2) {
|
|
5348
|
+
try {
|
|
5349
|
+
const parsed = JSON.parse(value2);
|
|
5350
|
+
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
5351
|
+
} catch {
|
|
5352
|
+
return null;
|
|
5353
|
+
}
|
|
5354
|
+
}
|
|
5355
|
+
function parseMaybeJson(value2) {
|
|
5356
|
+
if (typeof value2 !== "string") return value2;
|
|
5357
|
+
return parseJsonObject(value2) ?? value2;
|
|
5358
|
+
}
|
|
5359
|
+
function normalizeCodexMcpToolResult(output) {
|
|
5360
|
+
if (typeof output !== "string") return JSON.stringify(output ?? {}, null, 2);
|
|
5361
|
+
const outputMarker = "\nOutput:\n";
|
|
5362
|
+
const raw = output.includes(outputMarker) ? output.slice(output.indexOf(outputMarker) + outputMarker.length).trim() : output.trim();
|
|
5363
|
+
let parsed;
|
|
5364
|
+
try {
|
|
5365
|
+
parsed = JSON.parse(raw);
|
|
5366
|
+
} catch {
|
|
5367
|
+
return raw;
|
|
5368
|
+
}
|
|
5369
|
+
if (Array.isArray(parsed)) {
|
|
5370
|
+
const textBlocks = parsed.filter(
|
|
5371
|
+
(entry) => typeof entry === "object" && entry !== null && "text" in entry
|
|
5372
|
+
).map((entry) => typeof entry.text === "string" ? entry.text : "").filter(Boolean);
|
|
5373
|
+
if (textBlocks.length > 0) return textBlocks.join("\n");
|
|
5374
|
+
}
|
|
5375
|
+
return raw;
|
|
5376
|
+
}
|
|
5377
|
+
function findCodexSessionLog(runtimeSessionId, codexHome) {
|
|
5378
|
+
if (!runtimeSessionId || !(0, import_fs.existsSync)(codexHome)) return null;
|
|
5379
|
+
const root = (0, import_path.join)(codexHome, "sessions");
|
|
5380
|
+
if (!(0, import_fs.existsSync)(root)) return null;
|
|
5381
|
+
const matches = [];
|
|
5382
|
+
const stack = [root];
|
|
5383
|
+
while (stack.length > 0) {
|
|
5384
|
+
const dir = stack.pop();
|
|
5385
|
+
let entries;
|
|
5386
|
+
try {
|
|
5387
|
+
entries = (0, import_fs.readdirSync)(dir, { withFileTypes: true });
|
|
5388
|
+
} catch {
|
|
5389
|
+
continue;
|
|
5390
|
+
}
|
|
5391
|
+
for (const entry of entries) {
|
|
5392
|
+
const path = (0, import_path.join)(dir, entry.name);
|
|
5393
|
+
if (entry.isDirectory()) {
|
|
5394
|
+
stack.push(path);
|
|
5395
|
+
} else if (entry.isFile() && entry.name.includes(runtimeSessionId) && entry.name.endsWith(".jsonl")) {
|
|
5396
|
+
try {
|
|
5397
|
+
matches.push({ path, mtimeMs: (0, import_fs.statSync)(path).mtimeMs });
|
|
5398
|
+
} catch {
|
|
5399
|
+
matches.push({ path, mtimeMs: 0 });
|
|
5369
5400
|
}
|
|
5370
|
-
});
|
|
5371
|
-
if (options.stdinWriter) {
|
|
5372
|
-
options.stdinWriter(child.stdin, context);
|
|
5373
|
-
} else if (options.promptViaStdin) {
|
|
5374
|
-
child.stdin.write(prompt);
|
|
5375
|
-
child.stdin.end();
|
|
5376
|
-
} else {
|
|
5377
|
-
child.stdin.end();
|
|
5378
5401
|
}
|
|
5379
|
-
|
|
5380
|
-
|
|
5402
|
+
}
|
|
5403
|
+
}
|
|
5404
|
+
matches.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
5405
|
+
return matches[0]?.path ?? null;
|
|
5406
|
+
}
|
|
5407
|
+
async function replayCodexMcpToolEventsFromSessionLog(context, state) {
|
|
5408
|
+
const runtimeSessionId = state.runtimeSessionId;
|
|
5409
|
+
if (!runtimeSessionId) return;
|
|
5410
|
+
const codexHome = context.env.CODEX_HOME || process.env.CODEX_HOME || (0, import_path.join)((0, import_os.homedir)(), ".codex");
|
|
5411
|
+
const logPath = findCodexSessionLog(runtimeSessionId, codexHome);
|
|
5412
|
+
if (!logPath) return;
|
|
5413
|
+
const emittedToolIds = /* @__PURE__ */ new Set();
|
|
5414
|
+
const lines = (0, import_fs.readFileSync)(logPath, "utf8").split(/\r?\n/).filter(Boolean);
|
|
5415
|
+
for (const line of lines) {
|
|
5416
|
+
const entry = parseJsonObject(line);
|
|
5417
|
+
const payload = entry && typeof entry.payload === "object" && entry.payload !== null ? entry.payload : null;
|
|
5418
|
+
if (!payload) continue;
|
|
5419
|
+
if (payload.type === "function_call") {
|
|
5420
|
+
const callId = typeof payload.call_id === "string" ? payload.call_id : "";
|
|
5421
|
+
const name = typeof payload.name === "string" ? payload.name : "";
|
|
5422
|
+
const namespace = typeof payload.namespace === "string" ? payload.namespace : "";
|
|
5423
|
+
if (!callId || !name || !namespace.startsWith("mcp__")) continue;
|
|
5424
|
+
const toolName = `${namespace}${name}`;
|
|
5425
|
+
context.presenter.onToolUse(toolName, parseMaybeJson(payload.arguments), callId);
|
|
5426
|
+
emittedToolIds.add(callId);
|
|
5427
|
+
continue;
|
|
5428
|
+
}
|
|
5429
|
+
if (payload.type === "tool_search_call") {
|
|
5430
|
+
const callId = typeof payload.call_id === "string" ? payload.call_id : "";
|
|
5431
|
+
if (!callId) continue;
|
|
5432
|
+
context.presenter.onToolUse("tool_search", payload.arguments ?? {}, callId);
|
|
5433
|
+
emittedToolIds.add(callId);
|
|
5434
|
+
}
|
|
5435
|
+
}
|
|
5436
|
+
if (emittedToolIds.size === 0) return;
|
|
5437
|
+
for (const line of lines) {
|
|
5438
|
+
const entry = parseJsonObject(line);
|
|
5439
|
+
const payload = entry && typeof entry.payload === "object" && entry.payload !== null ? entry.payload : null;
|
|
5440
|
+
if (!payload || payload.type !== "function_call_output" && payload.type !== "tool_search_output") continue;
|
|
5441
|
+
const callId = typeof payload.call_id === "string" ? payload.call_id : "";
|
|
5442
|
+
if (!emittedToolIds.has(callId)) continue;
|
|
5443
|
+
try {
|
|
5444
|
+
const result = payload.type === "tool_search_output" ? JSON.stringify(payload.tools ?? [], null, 2) : normalizeCodexMcpToolResult(payload.output);
|
|
5445
|
+
context.presenter.onToolResult?.(callId, result);
|
|
5446
|
+
} catch {
|
|
5447
|
+
context.presenter.onToolResult?.(callId, String(payload.output ?? ""));
|
|
5448
|
+
}
|
|
5449
|
+
}
|
|
5450
|
+
}
|
|
5451
|
+
function kimiSessionId(cwd) {
|
|
5452
|
+
let resolved = cwd;
|
|
5453
|
+
try {
|
|
5454
|
+
resolved = (0, import_fs2.realpathSync)(cwd);
|
|
5455
|
+
} catch {
|
|
5456
|
+
}
|
|
5457
|
+
return (0, import_crypto.createHash)("md5").update(resolved).digest("hex");
|
|
5458
|
+
}
|
|
5459
|
+
function parseKimiStructuredLine(line, context, state) {
|
|
5460
|
+
const presenter = context.presenter;
|
|
5461
|
+
if (!state.runtimeSessionId) {
|
|
5462
|
+
state.runtimeSessionId = kimiSessionId(context.cwd);
|
|
5463
|
+
}
|
|
5464
|
+
let parsed;
|
|
5465
|
+
try {
|
|
5466
|
+
parsed = JSON.parse(line);
|
|
5467
|
+
} catch {
|
|
5468
|
+
const text = line.trim();
|
|
5469
|
+
if (text.length > 0) {
|
|
5470
|
+
if (text.toLowerCase().includes("llm not set")) state.error = text;
|
|
5471
|
+
void presenter.onLog(`[kimi_cli] ${line}`);
|
|
5472
|
+
}
|
|
5473
|
+
return;
|
|
5474
|
+
}
|
|
5475
|
+
const role = typeof parsed.role === "string" ? parsed.role : "";
|
|
5476
|
+
const contentBlocks = Array.isArray(parsed.content) ? parsed.content : [];
|
|
5477
|
+
const toolCalls = Array.isArray(parsed.tool_calls) ? parsed.tool_calls : [];
|
|
5478
|
+
if (role === "assistant") {
|
|
5479
|
+
const thinking = contentBlocks.filter((b) => b.type === "think" && typeof b.think === "string").map((b) => b.think).join("");
|
|
5480
|
+
if (thinking) {
|
|
5481
|
+
state.iterations = Math.max(state.iterations, 1);
|
|
5482
|
+
void presenter.onThinking(thinking);
|
|
5483
|
+
}
|
|
5484
|
+
if (toolCalls.length > 0) {
|
|
5485
|
+
state.iterations = Math.max(state.iterations, 1);
|
|
5486
|
+
for (const tc of toolCalls) {
|
|
5487
|
+
const fn = typeof tc.function === "object" && tc.function !== null ? tc.function : {};
|
|
5488
|
+
const toolId = typeof tc.id === "string" ? tc.id : `kimi-tool-${Date.now()}`;
|
|
5489
|
+
const toolName = typeof fn.name === "string" ? fn.name : "Tool";
|
|
5490
|
+
let args = {};
|
|
5491
|
+
if (typeof fn.arguments === "string") {
|
|
5492
|
+
try {
|
|
5493
|
+
args = JSON.parse(fn.arguments);
|
|
5494
|
+
} catch {
|
|
5495
|
+
}
|
|
5496
|
+
} else if (typeof fn.arguments === "object" && fn.arguments !== null) {
|
|
5497
|
+
args = fn.arguments;
|
|
5498
|
+
}
|
|
5499
|
+
void presenter.onToolUse(toolName, args, toolId);
|
|
5381
5500
|
}
|
|
5382
|
-
|
|
5383
|
-
const
|
|
5501
|
+
} else {
|
|
5502
|
+
const text = contentBlocks.filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("");
|
|
5503
|
+
if (text) {
|
|
5504
|
+
state.iterations = Math.max(state.iterations, 1);
|
|
5505
|
+
state.summary += text;
|
|
5506
|
+
void presenter.onAssistantText(text);
|
|
5507
|
+
}
|
|
5508
|
+
}
|
|
5509
|
+
return;
|
|
5510
|
+
}
|
|
5511
|
+
if (role === "tool") {
|
|
5512
|
+
const toolCallId = typeof parsed.tool_call_id === "string" ? parsed.tool_call_id : `kimi-tool-${Date.now()}`;
|
|
5513
|
+
const allText = contentBlocks.filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text);
|
|
5514
|
+
const userText = allText.filter((t) => !t.startsWith("<system>")).join("\n");
|
|
5515
|
+
void presenter.onToolResult?.(toolCallId, userText || allText.join("\n"));
|
|
5516
|
+
return;
|
|
5517
|
+
}
|
|
5518
|
+
}
|
|
5519
|
+
function buildPromptWithSystem(config, promptText) {
|
|
5520
|
+
const parts2 = [
|
|
5521
|
+
config.systemPrompt?.trim(),
|
|
5522
|
+
config.systemPromptAppend?.trim(),
|
|
5523
|
+
promptText.trim()
|
|
5524
|
+
].filter((value2) => Boolean(value2 && value2.length > 0));
|
|
5525
|
+
return parts2.join("\n\n");
|
|
5526
|
+
}
|
|
5527
|
+
function buildPlanModePrefix(promptText) {
|
|
5528
|
+
return [
|
|
5529
|
+
"You are in plan-only mode.",
|
|
5530
|
+
"Do not make code changes or execute write operations.",
|
|
5531
|
+
"Focus on analysis, planning, and explaining the next steps.",
|
|
5532
|
+
"",
|
|
5533
|
+
promptText
|
|
5534
|
+
].join("\n");
|
|
5535
|
+
}
|
|
5536
|
+
function imageFileExtension(mimeType) {
|
|
5537
|
+
const subtype = mimeType.split("/")[1]?.split(/[+;]/)[0]?.trim();
|
|
5538
|
+
if (!subtype) return "png";
|
|
5539
|
+
return subtype === "jpeg" ? "jpg" : subtype;
|
|
5540
|
+
}
|
|
5541
|
+
function createImageTempDirectory(cwd) {
|
|
5542
|
+
const candidates = [
|
|
5543
|
+
cwd ? (0, import_path2.join)(cwd, ".aiden-images-") : null,
|
|
5544
|
+
(0, import_path2.join)((0, import_os2.tmpdir)(), "aiden-images-")
|
|
5545
|
+
].filter((value2) => Boolean(value2));
|
|
5546
|
+
for (const candidate of candidates) {
|
|
5547
|
+
try {
|
|
5548
|
+
return (0, import_fs3.mkdtempSync)(candidate);
|
|
5549
|
+
} catch {
|
|
5550
|
+
}
|
|
5551
|
+
}
|
|
5552
|
+
return (0, import_fs3.mkdtempSync)((0, import_path2.join)((0, import_os2.tmpdir)(), "aiden-images-"));
|
|
5553
|
+
}
|
|
5554
|
+
function writeImagesToTempFiles(images, cwd) {
|
|
5555
|
+
if (!images?.length) return { paths: [], files: [], cleanup: () => {
|
|
5556
|
+
} };
|
|
5557
|
+
const directory = createImageTempDirectory(cwd);
|
|
5558
|
+
const paths = [];
|
|
5559
|
+
const files = [];
|
|
5560
|
+
for (const img of images) {
|
|
5561
|
+
const ext = imageFileExtension(img.mimeType);
|
|
5562
|
+
const filePath = (0, import_path2.join)(directory, `aiden-img-${img.id}.${ext}`);
|
|
5563
|
+
(0, import_fs3.writeFileSync)(filePath, Buffer.from(img.data, "base64"));
|
|
5564
|
+
paths.push(filePath);
|
|
5565
|
+
files.push({
|
|
5566
|
+
filename: img.filename,
|
|
5567
|
+
mimeType: img.mimeType,
|
|
5568
|
+
width: img.width,
|
|
5569
|
+
height: img.height,
|
|
5570
|
+
path: filePath
|
|
5571
|
+
});
|
|
5572
|
+
}
|
|
5573
|
+
return {
|
|
5574
|
+
paths,
|
|
5575
|
+
files,
|
|
5576
|
+
cleanup: () => {
|
|
5577
|
+
try {
|
|
5578
|
+
(0, import_fs3.rmSync)(directory, { recursive: true, force: true });
|
|
5579
|
+
} catch {
|
|
5580
|
+
}
|
|
5581
|
+
}
|
|
5582
|
+
};
|
|
5583
|
+
}
|
|
5584
|
+
function appendImagePathReferences(prompt, images) {
|
|
5585
|
+
if (images.length === 0) return prompt;
|
|
5586
|
+
const imageLines = images.map((img) => {
|
|
5587
|
+
const dimensions = img.width > 0 && img.height > 0 ? `, ${img.width}x${img.height}` : "";
|
|
5588
|
+
return `- ${img.filename} (${img.mimeType}${dimensions}): @${img.path} (path: ${img.path})`;
|
|
5589
|
+
});
|
|
5590
|
+
return [
|
|
5591
|
+
prompt,
|
|
5592
|
+
"Attached images are available as local files. Use the @path references below when this CLI supports file references, or read the plain paths with filesystem tools:",
|
|
5593
|
+
...imageLines
|
|
5594
|
+
].join("\n\n");
|
|
5595
|
+
}
|
|
5596
|
+
function buildPromptWithImagePathReferences(context, images) {
|
|
5597
|
+
const base = buildPromptWithSystem(context.config, context.promptText);
|
|
5598
|
+
const prompt = context.config.mode === "plan" ? buildPlanModePrefix(base) : base;
|
|
5599
|
+
return appendImagePathReferences(prompt, images);
|
|
5600
|
+
}
|
|
5601
|
+
function formatCliSpawnError(command, error) {
|
|
5602
|
+
if (error.code === "ENOENT") {
|
|
5603
|
+
return new Error(
|
|
5604
|
+
`CLI command not found: ${command}. Install it or configure this provider with the full executable path.`
|
|
5605
|
+
);
|
|
5606
|
+
}
|
|
5607
|
+
return error;
|
|
5608
|
+
}
|
|
5609
|
+
function createGenericCliBackend(options) {
|
|
5610
|
+
return {
|
|
5611
|
+
kind: options.kind,
|
|
5612
|
+
supportTier: options.supportTier,
|
|
5613
|
+
async run(context) {
|
|
5614
|
+
const presenter = context.presenter;
|
|
5615
|
+
const state = {
|
|
5616
|
+
process: null,
|
|
5617
|
+
iterations: 0,
|
|
5618
|
+
summary: "",
|
|
5619
|
+
allowContinuation: options.allowContinuation ?? false
|
|
5620
|
+
};
|
|
5621
|
+
const prompt = options.augmentPrompt?.(context) ?? (context.config.mode === "plan" ? buildPlanModePrefix(buildPromptWithSystem(context.config, context.promptText)) : buildPromptWithSystem(context.config, context.promptText));
|
|
5622
|
+
const args = options.buildArgs?.(context, prompt) ?? options.args.map((arg) => arg === "{{prompt}}" ? prompt : arg);
|
|
5623
|
+
const child = spawnCli(options.command, args, context);
|
|
5624
|
+
state.process = child;
|
|
5625
|
+
context.onProcessSpawned?.(child);
|
|
5626
|
+
const safeArgs = args.map(
|
|
5627
|
+
(a, i) => i > 0 && args[i - 1] === "--system-prompt" ? `"<system-prompt ${a.length} chars>"` : a
|
|
5628
|
+
);
|
|
5629
|
+
console.info(`[${options.kind}] Spawning: ${options.command} ${safeArgs.join(" ")}`);
|
|
5630
|
+
presenter.recordRawTranscript?.("system", `${options.command} ${args.join(" ")}`, {
|
|
5631
|
+
backendKind: options.kind
|
|
5632
|
+
});
|
|
5633
|
+
child.stdin.on("error", (err) => {
|
|
5634
|
+
if (err.code !== "EPIPE") {
|
|
5635
|
+
console.error(`[${options.kind}] stdin error:`, err);
|
|
5636
|
+
}
|
|
5637
|
+
});
|
|
5638
|
+
if (options.stdinWriter) {
|
|
5639
|
+
options.stdinWriter(child.stdin, context);
|
|
5640
|
+
} else if (options.promptViaStdin) {
|
|
5641
|
+
child.stdin.write(prompt);
|
|
5642
|
+
if (!options.keepStdinOpen) child.stdin.end();
|
|
5643
|
+
} else if (!options.keepStdinOpen) {
|
|
5644
|
+
child.stdin.end();
|
|
5645
|
+
}
|
|
5646
|
+
if (options.keepStdinOpen) {
|
|
5647
|
+
state.stdin = child.stdin;
|
|
5648
|
+
}
|
|
5649
|
+
const stdoutRl = (0, import_readline.createInterface)({ input: child.stdout });
|
|
5650
|
+
const stderrRl = (0, import_readline.createInterface)({ input: child.stderr });
|
|
5384
5651
|
const stderrLines = [];
|
|
5385
5652
|
stdoutRl.on("line", (line) => {
|
|
5386
5653
|
presenter.recordRawTranscript?.("stdout", line);
|
|
@@ -5708,155 +5975,281 @@ function parseClaudeStructuredLine(line, context, state) {
|
|
|
5708
5975
|
break;
|
|
5709
5976
|
}
|
|
5710
5977
|
}
|
|
5711
|
-
function
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
return parsedValue.detail.trim();
|
|
5733
|
-
}
|
|
5734
|
-
if (typeof parsedValue.message === "string" && parsedValue.message.trim().length > 0) {
|
|
5735
|
-
return parsedValue.message.trim();
|
|
5978
|
+
function buildClaudeModelArg(baseModel, selectedContextWindow) {
|
|
5979
|
+
if (selectedContextWindow === "1m") return `${baseModel}[1m]`;
|
|
5980
|
+
return baseModel;
|
|
5981
|
+
}
|
|
5982
|
+
function createClaudeCliBackend(command = "claude", defaultArgs = []) {
|
|
5983
|
+
return {
|
|
5984
|
+
kind: "claude_cli",
|
|
5985
|
+
supportTier: "structured",
|
|
5986
|
+
async run(context) {
|
|
5987
|
+
const args = [
|
|
5988
|
+
"--verbose",
|
|
5989
|
+
"--output-format",
|
|
5990
|
+
"stream-json",
|
|
5991
|
+
"--input-format",
|
|
5992
|
+
"stream-json",
|
|
5993
|
+
"--permission-mode",
|
|
5994
|
+
getClaudePermissionMode(context.config)
|
|
5995
|
+
];
|
|
5996
|
+
const disallowedTools = getClaudeDisallowedTools(context.config);
|
|
5997
|
+
if (disallowedTools.length > 0) {
|
|
5998
|
+
args.push("--disallowedTools", disallowedTools.join(","));
|
|
5736
5999
|
}
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
state.iterations += 1;
|
|
5746
|
-
break;
|
|
5747
|
-
case "session_configured":
|
|
5748
|
-
if (typeof parsed.session_id === "string") {
|
|
5749
|
-
state.runtimeSessionId = parsed.session_id;
|
|
6000
|
+
const resumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
|
|
6001
|
+
if (!resumeId) {
|
|
6002
|
+
if (context.config.systemPrompt?.trim()) {
|
|
6003
|
+
args.push("--system-prompt", context.config.systemPrompt.trim());
|
|
6004
|
+
}
|
|
6005
|
+
if (context.config.systemPromptAppend?.trim()) {
|
|
6006
|
+
args.push("--append-system-prompt", context.config.systemPromptAppend.trim());
|
|
6007
|
+
}
|
|
5750
6008
|
}
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
break;
|
|
5755
|
-
case "item.started": {
|
|
5756
|
-
const item = typeof parsed.item === "object" && parsed.item !== null ? parsed.item : null;
|
|
5757
|
-
if (!item || item.type !== "command_execution" || typeof item.id !== "string") break;
|
|
5758
|
-
const command = typeof item.command === "string" ? item.command : "";
|
|
5759
|
-
void presenter.onToolUse("Bash", { command, cwd: context.cwd }, item.id);
|
|
5760
|
-
break;
|
|
5761
|
-
}
|
|
5762
|
-
case "item.completed": {
|
|
5763
|
-
const item = typeof parsed.item === "object" && parsed.item !== null ? parsed.item : null;
|
|
5764
|
-
if (!item || typeof item.type !== "string") break;
|
|
5765
|
-
if (item.type === "reasoning" && typeof item.text === "string") {
|
|
5766
|
-
void presenter.onThinking(item.text);
|
|
5767
|
-
break;
|
|
6009
|
+
const baseModel = context.config.selectedModel?.trim();
|
|
6010
|
+
if (baseModel) {
|
|
6011
|
+
args.push("--model", buildClaudeModelArg(baseModel, context.config.selectedContextWindow));
|
|
5768
6012
|
}
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
void presenter.onAssistantText(item.text);
|
|
5773
|
-
break;
|
|
6013
|
+
const claudeEffort = context.config.selectedEffortLevel?.trim();
|
|
6014
|
+
if (claudeEffort) {
|
|
6015
|
+
args.push("--effort", claudeEffort);
|
|
5774
6016
|
}
|
|
5775
|
-
if (
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
const resultText = output.trim().length > 0 ? output : itemExitCode === null ? "" : `Exit code: ${itemExitCode}`;
|
|
5779
|
-
void presenter.onToolResult?.(item.id, resultText);
|
|
5780
|
-
break;
|
|
6017
|
+
if (resumeId) {
|
|
6018
|
+
args.push("--resume", resumeId);
|
|
6019
|
+
console.info("[claude_cli] Resuming session", { resumeId });
|
|
5781
6020
|
}
|
|
5782
|
-
|
|
6021
|
+
args.push("--chrome");
|
|
6022
|
+
console.info("[claude_cli] Chrome flag added \u2014 final args:", args.join(" "));
|
|
6023
|
+
args.push(...defaultArgs);
|
|
6024
|
+
return createGenericCliBackend({
|
|
6025
|
+
kind: "claude_cli",
|
|
6026
|
+
supportTier: "structured",
|
|
6027
|
+
command,
|
|
6028
|
+
args,
|
|
6029
|
+
parseStructuredLine: parseClaudeStructuredLine,
|
|
6030
|
+
stdinWriter: (stdin, ctx) => {
|
|
6031
|
+
const contentBlocks = [];
|
|
6032
|
+
if (ctx.config.images?.length) {
|
|
6033
|
+
for (const img of ctx.config.images) {
|
|
6034
|
+
contentBlocks.push({
|
|
6035
|
+
type: "image",
|
|
6036
|
+
source: { type: "base64", media_type: img.mimeType, data: img.data }
|
|
6037
|
+
});
|
|
6038
|
+
}
|
|
6039
|
+
}
|
|
6040
|
+
const promptText = ctx.config.mode === "plan" ? buildPlanModePrefix(buildPromptWithSystem(ctx.config, ctx.promptText)) : buildPromptWithSystem(ctx.config, ctx.promptText);
|
|
6041
|
+
if (promptText.trim()) {
|
|
6042
|
+
contentBlocks.push({ type: "text", text: promptText });
|
|
6043
|
+
}
|
|
6044
|
+
console.info(`[claude_cli] stdinWriter: ${contentBlocks.length} content blocks (${ctx.config.images?.length ?? 0} images)`);
|
|
6045
|
+
const message = JSON.stringify({
|
|
6046
|
+
type: "user",
|
|
6047
|
+
message: { role: "user", content: contentBlocks },
|
|
6048
|
+
session_id: "default",
|
|
6049
|
+
parent_tool_use_id: null
|
|
6050
|
+
});
|
|
6051
|
+
stdin.write(message + "\n");
|
|
6052
|
+
},
|
|
6053
|
+
keepStdinOpen: true,
|
|
6054
|
+
allowContinuation: true,
|
|
6055
|
+
continuationIdleMs: 10 * 60 * 1e3
|
|
6056
|
+
// 10 minutes
|
|
6057
|
+
}).run(context);
|
|
5783
6058
|
}
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
6059
|
+
};
|
|
6060
|
+
}
|
|
6061
|
+
function createSupatestCliBackend(command = "supatest", defaultArgs = []) {
|
|
6062
|
+
return {
|
|
6063
|
+
kind: "supatest_cli",
|
|
6064
|
+
supportTier: "structured",
|
|
6065
|
+
async run(context) {
|
|
6066
|
+
const args = [
|
|
6067
|
+
"--cwd",
|
|
6068
|
+
context.cwd,
|
|
6069
|
+
"--output-format",
|
|
6070
|
+
"stream-json",
|
|
6071
|
+
"--input-format",
|
|
6072
|
+
"stream-json",
|
|
6073
|
+
"--permission-mode",
|
|
6074
|
+
getClaudePermissionMode(context.config)
|
|
6075
|
+
];
|
|
6076
|
+
const resumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
|
|
6077
|
+
if (!resumeId) {
|
|
6078
|
+
if (context.config.systemPrompt?.trim()) {
|
|
6079
|
+
args.push("--system-prompt", context.config.systemPrompt.trim());
|
|
6080
|
+
}
|
|
6081
|
+
if (context.config.systemPromptAppend?.trim()) {
|
|
6082
|
+
args.push("--append-system-prompt", context.config.systemPromptAppend.trim());
|
|
6083
|
+
}
|
|
6084
|
+
}
|
|
6085
|
+
const baseModel = context.config.selectedModel?.trim();
|
|
6086
|
+
if (baseModel) {
|
|
6087
|
+
args.push("--model", buildClaudeModelArg(baseModel, context.config.selectedContextWindow));
|
|
6088
|
+
}
|
|
6089
|
+
if (resumeId) {
|
|
6090
|
+
args.push("--resume", resumeId);
|
|
6091
|
+
console.info("[supatest_cli] Resuming session", { resumeId });
|
|
6092
|
+
}
|
|
6093
|
+
args.push(...defaultArgs);
|
|
6094
|
+
return createGenericCliBackend({
|
|
6095
|
+
kind: "supatest_cli",
|
|
6096
|
+
supportTier: "structured",
|
|
6097
|
+
command,
|
|
6098
|
+
args,
|
|
6099
|
+
parseStructuredLine: parseClaudeStructuredLine,
|
|
6100
|
+
stdinWriter: (stdin, ctx) => {
|
|
6101
|
+
const contentBlocks = [];
|
|
6102
|
+
if (ctx.config.images?.length) {
|
|
6103
|
+
for (const img of ctx.config.images) {
|
|
6104
|
+
contentBlocks.push({
|
|
6105
|
+
type: "image",
|
|
6106
|
+
source: { type: "base64", media_type: img.mimeType, data: img.data }
|
|
6107
|
+
});
|
|
6108
|
+
}
|
|
6109
|
+
}
|
|
6110
|
+
const promptText = ctx.config.mode === "plan" ? buildPlanModePrefix(buildPromptWithSystem(ctx.config, ctx.promptText)) : buildPromptWithSystem(ctx.config, ctx.promptText);
|
|
6111
|
+
if (promptText.trim()) {
|
|
6112
|
+
contentBlocks.push({ type: "text", text: promptText });
|
|
6113
|
+
}
|
|
6114
|
+
const message = JSON.stringify({
|
|
6115
|
+
type: "user",
|
|
6116
|
+
message: { role: "user", content: contentBlocks },
|
|
6117
|
+
session_id: "default",
|
|
6118
|
+
parent_tool_use_id: null
|
|
6119
|
+
});
|
|
6120
|
+
stdin.write(message + "\n");
|
|
6121
|
+
},
|
|
6122
|
+
keepStdinOpen: true
|
|
6123
|
+
}).run(context);
|
|
5799
6124
|
}
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
6125
|
+
};
|
|
6126
|
+
}
|
|
6127
|
+
function shouldEnableKimiCliThinking(model) {
|
|
6128
|
+
return model?.trim() === "kimi-k2.6-thinking";
|
|
6129
|
+
}
|
|
6130
|
+
function resolveKimiCliModelArg(model) {
|
|
6131
|
+
const trimmed = model?.trim();
|
|
6132
|
+
if (!trimmed) return void 0;
|
|
6133
|
+
if (trimmed === "kimi-latest" || trimmed === "kimi-k2.5" || trimmed === "kimi-k2.6" || shouldEnableKimiCliThinking(trimmed)) {
|
|
6134
|
+
return void 0;
|
|
6135
|
+
}
|
|
6136
|
+
if (trimmed.startsWith("claude-")) return void 0;
|
|
6137
|
+
return trimmed;
|
|
6138
|
+
}
|
|
6139
|
+
function createKimiCliBackend(command = "kimi", defaultArgs = []) {
|
|
6140
|
+
return {
|
|
6141
|
+
kind: "kimi_cli",
|
|
6142
|
+
supportTier: "structured",
|
|
6143
|
+
async run(context) {
|
|
6144
|
+
const { files: imageFiles, cleanup } = writeImagesToTempFiles(context.config.images, context.cwd);
|
|
6145
|
+
try {
|
|
6146
|
+
return await createGenericCliBackend({
|
|
6147
|
+
kind: "kimi_cli",
|
|
6148
|
+
supportTier: "structured",
|
|
6149
|
+
command,
|
|
6150
|
+
args: [],
|
|
6151
|
+
buildArgs: (ctx, prompt) => {
|
|
6152
|
+
const args = [
|
|
6153
|
+
"--print",
|
|
6154
|
+
"--output-format",
|
|
6155
|
+
"stream-json",
|
|
6156
|
+
"--work-dir",
|
|
6157
|
+
ctx.cwd
|
|
6158
|
+
];
|
|
6159
|
+
const resumeId = ctx.config.runtimeSessionId?.trim() || ctx.config.providerSessionId?.trim();
|
|
6160
|
+
if (resumeId) args.push("--session", resumeId);
|
|
6161
|
+
if (shouldEnableKimiCliThinking(ctx.config.selectedModel)) {
|
|
6162
|
+
args.push("--thinking");
|
|
6163
|
+
}
|
|
6164
|
+
const model = resolveKimiCliModelArg(ctx.config.selectedModel);
|
|
6165
|
+
if (model) args.push("--model", model);
|
|
6166
|
+
return [...args, ...defaultArgs, "--prompt", prompt];
|
|
6167
|
+
},
|
|
6168
|
+
augmentPrompt: (ctx) => buildPromptWithImagePathReferences(ctx, imageFiles),
|
|
6169
|
+
parseStructuredLine: parseKimiStructuredLine
|
|
6170
|
+
}).run(context);
|
|
6171
|
+
} finally {
|
|
6172
|
+
cleanup();
|
|
6173
|
+
}
|
|
5806
6174
|
}
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
6175
|
+
};
|
|
6176
|
+
}
|
|
6177
|
+
function buildGeminiToolResultText(parsed) {
|
|
6178
|
+
const output = typeof parsed.output === "string" ? parsed.output : "";
|
|
6179
|
+
if (output.trim().length > 0) return output;
|
|
6180
|
+
const error = typeof parsed.error === "object" && parsed.error !== null ? parsed.error : null;
|
|
6181
|
+
const message = typeof error?.message === "string" ? error.message.trim() : "";
|
|
6182
|
+
const errorType = typeof error?.type === "string" ? error.type.trim() : "";
|
|
6183
|
+
if (message && errorType) return `${errorType}: ${message}`;
|
|
6184
|
+
if (message) return message;
|
|
6185
|
+
const status = typeof parsed.status === "string" ? parsed.status.trim() : "";
|
|
6186
|
+
return status || "Tool completed";
|
|
6187
|
+
}
|
|
6188
|
+
function parseGeminiStructuredLine(line, context, state) {
|
|
6189
|
+
const presenter = context.presenter;
|
|
6190
|
+
let parsed = null;
|
|
6191
|
+
try {
|
|
6192
|
+
parsed = JSON.parse(line);
|
|
6193
|
+
} catch {
|
|
6194
|
+
void presenter.onLog(`[gemini_cli] ${line}`);
|
|
6195
|
+
return;
|
|
6196
|
+
}
|
|
6197
|
+
const type = typeof parsed.type === "string" ? parsed.type : "";
|
|
6198
|
+
if (!type) return;
|
|
6199
|
+
if (typeof parsed.session_id === "string") state.runtimeSessionId = parsed.session_id;
|
|
6200
|
+
if (typeof parsed.model === "string") state.activeModel = parsed.model;
|
|
6201
|
+
switch (type) {
|
|
6202
|
+
case "init": {
|
|
6203
|
+
const model = state.activeModel || context.config.selectedModel || "gemini";
|
|
6204
|
+
void presenter.onLog(`[gemini_cli] Session initialized with ${model}`);
|
|
5811
6205
|
break;
|
|
5812
6206
|
}
|
|
5813
|
-
case "
|
|
5814
|
-
|
|
5815
|
-
const
|
|
5816
|
-
|
|
5817
|
-
|
|
6207
|
+
case "message": {
|
|
6208
|
+
if (typeof parsed.role !== "string" || parsed.role !== "assistant") break;
|
|
6209
|
+
const content = typeof parsed.content === "string" ? parsed.content : "";
|
|
6210
|
+
if (!content) break;
|
|
6211
|
+
state.iterations = Math.max(state.iterations, 1);
|
|
6212
|
+
state.summary += content;
|
|
6213
|
+
void presenter.onAssistantText(content);
|
|
5818
6214
|
break;
|
|
5819
6215
|
}
|
|
5820
|
-
case "
|
|
5821
|
-
const
|
|
5822
|
-
|
|
6216
|
+
case "tool_use": {
|
|
6217
|
+
const toolName = typeof parsed.tool_name === "string" ? parsed.tool_name : "Gemini Tool";
|
|
6218
|
+
const toolId = typeof parsed.tool_id === "string" ? parsed.tool_id : `gemini-tool-${Date.now()}`;
|
|
6219
|
+
state.iterations = Math.max(state.iterations, 1);
|
|
6220
|
+
void presenter.onToolUse(toolName, parsed.parameters ?? {}, toolId);
|
|
5823
6221
|
break;
|
|
5824
6222
|
}
|
|
5825
|
-
case "
|
|
5826
|
-
const
|
|
5827
|
-
state.
|
|
5828
|
-
|
|
5829
|
-
numTurns: Math.max(state.iterations, 1),
|
|
5830
|
-
durationMs: 0,
|
|
5831
|
-
inputTokens: typeof info.input_tokens === "number" ? info.input_tokens : 0,
|
|
5832
|
-
outputTokens: typeof info.output_tokens === "number" ? info.output_tokens : 0,
|
|
5833
|
-
cacheReadTokens: typeof info.cached_input_tokens === "number" ? info.cached_input_tokens : 0,
|
|
5834
|
-
cacheCreationTokens: 0,
|
|
5835
|
-
costUsd: typeof info.total_cost_usd === "number" ? info.total_cost_usd : 0
|
|
5836
|
-
};
|
|
5837
|
-
void presenter.onUsageUpdate?.({
|
|
5838
|
-
model: state.usage.model,
|
|
5839
|
-
inputTokens: state.usage.inputTokens,
|
|
5840
|
-
outputTokens: state.usage.outputTokens,
|
|
5841
|
-
cacheReadTokens: state.usage.cacheReadTokens,
|
|
5842
|
-
cacheCreationTokens: state.usage.cacheCreationTokens
|
|
5843
|
-
});
|
|
6223
|
+
case "tool_result": {
|
|
6224
|
+
const toolId = typeof parsed.tool_id === "string" ? parsed.tool_id : `gemini-tool-${Date.now()}`;
|
|
6225
|
+
state.iterations = Math.max(state.iterations, 1);
|
|
6226
|
+
void presenter.onToolResult?.(toolId, buildGeminiToolResultText(parsed));
|
|
5844
6227
|
break;
|
|
5845
6228
|
}
|
|
5846
|
-
case "
|
|
5847
|
-
const
|
|
5848
|
-
|
|
6229
|
+
case "error": {
|
|
6230
|
+
const errObj = typeof parsed.error === "object" && parsed.error !== null ? parsed.error : null;
|
|
6231
|
+
const message = typeof parsed.message === "string" && parsed.message.trim() || errObj && typeof errObj.message === "string" && errObj.message.trim() || typeof parsed.error === "string" && parsed.error.trim() || typeof parsed.error_message === "string" && parsed.error_message.trim() || "";
|
|
6232
|
+
const severity = typeof parsed.severity === "string" ? parsed.severity : "error";
|
|
6233
|
+
if (message) {
|
|
6234
|
+
if (severity === "error") {
|
|
6235
|
+
void presenter.onError(message);
|
|
6236
|
+
state.error = message;
|
|
6237
|
+
} else {
|
|
6238
|
+
void presenter.onLog(`[gemini_cli/${severity}] ${message}`);
|
|
6239
|
+
}
|
|
6240
|
+
}
|
|
5849
6241
|
break;
|
|
5850
6242
|
}
|
|
5851
|
-
case "
|
|
5852
|
-
const
|
|
6243
|
+
case "result": {
|
|
6244
|
+
const stats = typeof parsed.stats === "object" && parsed.stats !== null ? parsed.stats : {};
|
|
6245
|
+
state.iterations = Math.max(state.iterations, 1);
|
|
5853
6246
|
state.usage = {
|
|
5854
|
-
model: typeof context.config.selectedModel === "string" && context.config.selectedModel.trim() ? context.config.selectedModel : "
|
|
6247
|
+
model: state.activeModel || (typeof context.config.selectedModel === "string" && context.config.selectedModel.trim() ? context.config.selectedModel : "gemini"),
|
|
5855
6248
|
numTurns: Math.max(state.iterations, 1),
|
|
5856
|
-
durationMs: 0,
|
|
5857
|
-
inputTokens: typeof
|
|
5858
|
-
outputTokens: typeof
|
|
5859
|
-
cacheReadTokens: typeof
|
|
6249
|
+
durationMs: typeof stats.duration_ms === "number" ? stats.duration_ms : 0,
|
|
6250
|
+
inputTokens: typeof stats.input_tokens === "number" ? stats.input_tokens : typeof stats.input === "number" ? stats.input : 0,
|
|
6251
|
+
outputTokens: typeof stats.output_tokens === "number" ? stats.output_tokens : 0,
|
|
6252
|
+
cacheReadTokens: typeof stats.cached === "number" ? stats.cached : 0,
|
|
5860
6253
|
cacheCreationTokens: 0,
|
|
5861
6254
|
costUsd: 0
|
|
5862
6255
|
};
|
|
@@ -5867,24 +6260,15 @@ function parseCodexStructuredLine(line, context, state) {
|
|
|
5867
6260
|
cacheReadTokens: state.usage.cacheReadTokens,
|
|
5868
6261
|
cacheCreationTokens: state.usage.cacheCreationTokens
|
|
5869
6262
|
});
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
break;
|
|
5880
|
-
}
|
|
5881
|
-
case "turn.failed": {
|
|
5882
|
-
const error = typeof parsed.error === "object" && parsed.error !== null ? parsed.error : {};
|
|
5883
|
-
const message = extractCodexErrorMessage(error.message) || extractCodexErrorMessage(error.detail) || typeof parsed.message === "string" && parsed.message.trim() || "";
|
|
5884
|
-
if (message) {
|
|
5885
|
-
const normalizedMessage = normalizeCodexCliErrorMessage(message);
|
|
5886
|
-
void presenter.onError(normalizedMessage);
|
|
5887
|
-
state.error = normalizedMessage;
|
|
6263
|
+
if (typeof parsed.status === "string" && parsed.status !== "success" && !state.error) {
|
|
6264
|
+
const errObj = typeof parsed.error === "object" && parsed.error !== null ? parsed.error : null;
|
|
6265
|
+
const errMsg = typeof parsed.error === "string" && parsed.error.trim() || errObj && typeof errObj.message === "string" && errObj.message.trim() || typeof parsed.error_message === "string" && parsed.error_message.trim() || typeof parsed.message === "string" && parsed.message.trim() || typeof parsed.result === "string" && parsed.result.trim() || parsed.status;
|
|
6266
|
+
state.error = errMsg;
|
|
6267
|
+
console.error("[gemini_cli] result status=", parsed.status, "error:", JSON.stringify({
|
|
6268
|
+
error: parsed.error,
|
|
6269
|
+
error_message: parsed.error_message,
|
|
6270
|
+
message: parsed.message
|
|
6271
|
+
}));
|
|
5888
6272
|
}
|
|
5889
6273
|
break;
|
|
5890
6274
|
}
|
|
@@ -5892,109 +6276,36 @@ function parseCodexStructuredLine(line, context, state) {
|
|
|
5892
6276
|
break;
|
|
5893
6277
|
}
|
|
5894
6278
|
}
|
|
5895
|
-
function
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
}
|
|
5923
|
-
return raw;
|
|
5924
|
-
}
|
|
5925
|
-
function findCodexSessionLog(runtimeSessionId, codexHome) {
|
|
5926
|
-
if (!runtimeSessionId || !(0, import_fs.existsSync)(codexHome)) return null;
|
|
5927
|
-
const root = (0, import_path.join)(codexHome, "sessions");
|
|
5928
|
-
if (!(0, import_fs.existsSync)(root)) return null;
|
|
5929
|
-
const matches = [];
|
|
5930
|
-
const stack = [root];
|
|
5931
|
-
while (stack.length > 0) {
|
|
5932
|
-
const dir = stack.pop();
|
|
5933
|
-
let entries;
|
|
5934
|
-
try {
|
|
5935
|
-
entries = (0, import_fs.readdirSync)(dir, { withFileTypes: true });
|
|
5936
|
-
} catch {
|
|
5937
|
-
continue;
|
|
5938
|
-
}
|
|
5939
|
-
for (const entry of entries) {
|
|
5940
|
-
const path = (0, import_path.join)(dir, entry.name);
|
|
5941
|
-
if (entry.isDirectory()) {
|
|
5942
|
-
stack.push(path);
|
|
5943
|
-
} else if (entry.isFile() && entry.name.includes(runtimeSessionId) && entry.name.endsWith(".jsonl")) {
|
|
5944
|
-
try {
|
|
5945
|
-
matches.push({ path, mtimeMs: (0, import_fs.statSync)(path).mtimeMs });
|
|
5946
|
-
} catch {
|
|
5947
|
-
matches.push({ path, mtimeMs: 0 });
|
|
5948
|
-
}
|
|
6279
|
+
function createGeminiCliBackend(command = "gemini", defaultArgs = []) {
|
|
6280
|
+
return {
|
|
6281
|
+
kind: "gemini_cli",
|
|
6282
|
+
supportTier: "structured",
|
|
6283
|
+
async run(context) {
|
|
6284
|
+
const { files: imageFiles, cleanup } = writeImagesToTempFiles(context.config.images, context.cwd);
|
|
6285
|
+
try {
|
|
6286
|
+
return await createGenericCliBackend({
|
|
6287
|
+
kind: "gemini_cli",
|
|
6288
|
+
supportTier: "structured",
|
|
6289
|
+
command,
|
|
6290
|
+
args: [],
|
|
6291
|
+
buildArgs: (ctx) => {
|
|
6292
|
+
const args = ["-p", "", "--output-format", "stream-json"];
|
|
6293
|
+
const resumeId = ctx.config.runtimeSessionId?.trim() || ctx.config.providerSessionId?.trim();
|
|
6294
|
+
const approvalMode = ctx.config.mode === "plan" || ctx.config.mode === "ask" || ctx.config.mode === "review" ? "plan" : "yolo";
|
|
6295
|
+
args.push("--approval-mode", approvalMode);
|
|
6296
|
+
if (resumeId) args.push("--resume", resumeId);
|
|
6297
|
+
if (ctx.config.selectedModel?.trim()) args.push("--model", ctx.config.selectedModel.trim());
|
|
6298
|
+
return [...args, ...defaultArgs];
|
|
6299
|
+
},
|
|
6300
|
+
promptViaStdin: true,
|
|
6301
|
+
augmentPrompt: (ctx) => buildPromptWithImagePathReferences(ctx, imageFiles),
|
|
6302
|
+
parseStructuredLine: parseGeminiStructuredLine
|
|
6303
|
+
}).run(context);
|
|
6304
|
+
} finally {
|
|
6305
|
+
cleanup();
|
|
5949
6306
|
}
|
|
5950
6307
|
}
|
|
5951
|
-
}
|
|
5952
|
-
matches.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
5953
|
-
return matches[0]?.path ?? null;
|
|
5954
|
-
}
|
|
5955
|
-
async function replayCodexMcpToolEventsFromSessionLog(context, state) {
|
|
5956
|
-
const runtimeSessionId = state.runtimeSessionId;
|
|
5957
|
-
if (!runtimeSessionId) return;
|
|
5958
|
-
const codexHome = context.env.CODEX_HOME || process.env.CODEX_HOME || (0, import_path.join)((0, import_os.homedir)(), ".codex");
|
|
5959
|
-
const logPath = findCodexSessionLog(runtimeSessionId, codexHome);
|
|
5960
|
-
if (!logPath) return;
|
|
5961
|
-
const emittedToolIds = /* @__PURE__ */ new Set();
|
|
5962
|
-
const lines = (0, import_fs.readFileSync)(logPath, "utf8").split(/\r?\n/).filter(Boolean);
|
|
5963
|
-
for (const line of lines) {
|
|
5964
|
-
const entry = parseJsonObject(line);
|
|
5965
|
-
const payload = entry && typeof entry.payload === "object" && entry.payload !== null ? entry.payload : null;
|
|
5966
|
-
if (!payload) continue;
|
|
5967
|
-
if (payload.type === "function_call") {
|
|
5968
|
-
const callId = typeof payload.call_id === "string" ? payload.call_id : "";
|
|
5969
|
-
const name = typeof payload.name === "string" ? payload.name : "";
|
|
5970
|
-
const namespace = typeof payload.namespace === "string" ? payload.namespace : "";
|
|
5971
|
-
if (!callId || !name || !namespace.startsWith("mcp__")) continue;
|
|
5972
|
-
const toolName = `${namespace}${name}`;
|
|
5973
|
-
context.presenter.onToolUse(toolName, parseMaybeJson(payload.arguments), callId);
|
|
5974
|
-
emittedToolIds.add(callId);
|
|
5975
|
-
continue;
|
|
5976
|
-
}
|
|
5977
|
-
if (payload.type === "tool_search_call") {
|
|
5978
|
-
const callId = typeof payload.call_id === "string" ? payload.call_id : "";
|
|
5979
|
-
if (!callId) continue;
|
|
5980
|
-
context.presenter.onToolUse("tool_search", payload.arguments ?? {}, callId);
|
|
5981
|
-
emittedToolIds.add(callId);
|
|
5982
|
-
}
|
|
5983
|
-
}
|
|
5984
|
-
if (emittedToolIds.size === 0) return;
|
|
5985
|
-
for (const line of lines) {
|
|
5986
|
-
const entry = parseJsonObject(line);
|
|
5987
|
-
const payload = entry && typeof entry.payload === "object" && entry.payload !== null ? entry.payload : null;
|
|
5988
|
-
if (!payload || payload.type !== "function_call_output" && payload.type !== "tool_search_output") continue;
|
|
5989
|
-
const callId = typeof payload.call_id === "string" ? payload.call_id : "";
|
|
5990
|
-
if (!emittedToolIds.has(callId)) continue;
|
|
5991
|
-
try {
|
|
5992
|
-
const result = payload.type === "tool_search_output" ? JSON.stringify(payload.tools ?? [], null, 2) : normalizeCodexMcpToolResult(payload.output);
|
|
5993
|
-
context.presenter.onToolResult?.(callId, result);
|
|
5994
|
-
} catch {
|
|
5995
|
-
context.presenter.onToolResult?.(callId, String(payload.output ?? ""));
|
|
5996
|
-
}
|
|
5997
|
-
}
|
|
6308
|
+
};
|
|
5998
6309
|
}
|
|
5999
6310
|
function extractCursorToolEntry(toolCall) {
|
|
6000
6311
|
const [rawName, rawPayload] = Object.entries(toolCall)[0] ?? [];
|
|
@@ -6106,30 +6417,81 @@ function parseCursorStructuredLine(line, context, state) {
|
|
|
6106
6417
|
state.error = resultText.trim() || "Cursor Agent run failed";
|
|
6107
6418
|
void presenter.onError(state.error);
|
|
6108
6419
|
}
|
|
6420
|
+
state.resultReceived = true;
|
|
6421
|
+
state.onResultReceived?.();
|
|
6422
|
+
if (state.stdin && !state.stdin.destroyed) {
|
|
6423
|
+
state.stdin.end();
|
|
6424
|
+
}
|
|
6109
6425
|
break;
|
|
6110
6426
|
}
|
|
6111
6427
|
default:
|
|
6112
6428
|
break;
|
|
6113
6429
|
}
|
|
6114
6430
|
}
|
|
6115
|
-
function
|
|
6116
|
-
|
|
6117
|
-
if (output.trim().length > 0) return output;
|
|
6118
|
-
const error = typeof parsed.error === "object" && parsed.error !== null ? parsed.error : null;
|
|
6119
|
-
const message = typeof error?.message === "string" ? error.message.trim() : "";
|
|
6120
|
-
const errorType = typeof error?.type === "string" ? error.type.trim() : "";
|
|
6121
|
-
if (message && errorType) return `${errorType}: ${message}`;
|
|
6122
|
-
if (message) return message;
|
|
6123
|
-
const status = typeof parsed.status === "string" ? parsed.status.trim() : "";
|
|
6124
|
-
return status || "Tool completed";
|
|
6431
|
+
function isCursorAgentCliModelId(model) {
|
|
6432
|
+
return model === "auto" || /^composer-/.test(model) || /^gpt-\d/.test(model) || /^claude-\d/.test(model) || /^gemini-\d/.test(model) || /^grok-\d/.test(model) || /^kimi-/.test(model);
|
|
6125
6433
|
}
|
|
6126
|
-
function
|
|
6434
|
+
function shouldForceCursorAgent(config) {
|
|
6435
|
+
if (shouldUseReadOnlyRuntimePermissions(config)) return false;
|
|
6436
|
+
switch (config.mode) {
|
|
6437
|
+
case "plan":
|
|
6438
|
+
case "ask":
|
|
6439
|
+
case "review":
|
|
6440
|
+
return false;
|
|
6441
|
+
default:
|
|
6442
|
+
return true;
|
|
6443
|
+
}
|
|
6444
|
+
}
|
|
6445
|
+
function createCursorAgentCliBackend(command = "cursor-agent", defaultArgs = []) {
|
|
6446
|
+
return {
|
|
6447
|
+
kind: "cursor_agent_cli",
|
|
6448
|
+
supportTier: "structured",
|
|
6449
|
+
async run(context) {
|
|
6450
|
+
const { files: imageFiles, cleanup } = writeImagesToTempFiles(context.config.images, context.cwd);
|
|
6451
|
+
try {
|
|
6452
|
+
return await createGenericCliBackend({
|
|
6453
|
+
kind: "cursor_agent_cli",
|
|
6454
|
+
supportTier: "structured",
|
|
6455
|
+
command,
|
|
6456
|
+
args: [],
|
|
6457
|
+
buildArgs: (ctx, prompt) => {
|
|
6458
|
+
const args = [
|
|
6459
|
+
"--print",
|
|
6460
|
+
"--output-format",
|
|
6461
|
+
"stream-json",
|
|
6462
|
+
"--trust",
|
|
6463
|
+
"--workspace",
|
|
6464
|
+
ctx.cwd
|
|
6465
|
+
];
|
|
6466
|
+
const resumeId = ctx.config.runtimeSessionId?.trim() || ctx.config.providerSessionId?.trim();
|
|
6467
|
+
if (shouldForceCursorAgent(ctx.config)) args.push("--force");
|
|
6468
|
+
if (ctx.config.mode === "plan" || ctx.config.mode === "review") {
|
|
6469
|
+
args.push("--mode", "plan");
|
|
6470
|
+
} else if (ctx.config.mode === "ask") {
|
|
6471
|
+
args.push("--mode", "ask");
|
|
6472
|
+
}
|
|
6473
|
+
if (resumeId) args.push("--resume", resumeId);
|
|
6474
|
+
const model = ctx.config.selectedModel?.trim();
|
|
6475
|
+
if (model && isCursorAgentCliModelId(model)) args.push("--model", model);
|
|
6476
|
+
return [...args, ...defaultArgs, prompt];
|
|
6477
|
+
},
|
|
6478
|
+
keepStdinOpen: true,
|
|
6479
|
+
augmentPrompt: (ctx) => buildPromptWithImagePathReferences(ctx, imageFiles),
|
|
6480
|
+
parseStructuredLine: parseCursorStructuredLine
|
|
6481
|
+
}).run(context);
|
|
6482
|
+
} finally {
|
|
6483
|
+
cleanup();
|
|
6484
|
+
}
|
|
6485
|
+
}
|
|
6486
|
+
};
|
|
6487
|
+
}
|
|
6488
|
+
function parseDroidStructuredLine(line, context, state) {
|
|
6127
6489
|
const presenter = context.presenter;
|
|
6128
6490
|
let parsed = null;
|
|
6129
6491
|
try {
|
|
6130
6492
|
parsed = JSON.parse(line);
|
|
6131
6493
|
} catch {
|
|
6132
|
-
void presenter.onLog(`[
|
|
6494
|
+
if (line.trim().length > 0) void presenter.onLog(`[droid_cli] ${line}`);
|
|
6133
6495
|
return;
|
|
6134
6496
|
}
|
|
6135
6497
|
const type = typeof parsed.type === "string" ? parsed.type : "";
|
|
@@ -6137,581 +6499,441 @@ function parseGeminiStructuredLine(line, context, state) {
|
|
|
6137
6499
|
if (typeof parsed.session_id === "string") state.runtimeSessionId = parsed.session_id;
|
|
6138
6500
|
if (typeof parsed.model === "string") state.activeModel = parsed.model;
|
|
6139
6501
|
switch (type) {
|
|
6140
|
-
case "
|
|
6141
|
-
const
|
|
6142
|
-
|
|
6502
|
+
case "system": {
|
|
6503
|
+
const subtype = typeof parsed.subtype === "string" ? parsed.subtype : "";
|
|
6504
|
+
if (subtype === "init") {
|
|
6505
|
+
if (typeof parsed.session_id === "string") state.runtimeSessionId = parsed.session_id;
|
|
6506
|
+
if (typeof parsed.model === "string") state.activeModel = parsed.model;
|
|
6507
|
+
}
|
|
6143
6508
|
break;
|
|
6144
6509
|
}
|
|
6145
6510
|
case "message": {
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6511
|
+
const role = typeof parsed.role === "string" ? parsed.role : "";
|
|
6512
|
+
if (role !== "assistant") break;
|
|
6513
|
+
const text = typeof parsed.text === "string" ? parsed.text : "";
|
|
6514
|
+
if (!text) break;
|
|
6149
6515
|
state.iterations = Math.max(state.iterations, 1);
|
|
6150
|
-
state.summary +=
|
|
6151
|
-
void presenter.onAssistantText(
|
|
6516
|
+
state.summary += text;
|
|
6517
|
+
void presenter.onAssistantText(text);
|
|
6152
6518
|
break;
|
|
6153
6519
|
}
|
|
6154
|
-
case "
|
|
6155
|
-
const
|
|
6156
|
-
const
|
|
6520
|
+
case "tool_call": {
|
|
6521
|
+
const toolId = typeof parsed.id === "string" ? parsed.id : `droid-tool-${Date.now()}`;
|
|
6522
|
+
const toolName = typeof parsed.toolName === "string" ? parsed.toolName : "Tool";
|
|
6523
|
+
const parameters = typeof parsed.parameters === "object" && parsed.parameters !== null ? parsed.parameters : {};
|
|
6157
6524
|
state.iterations = Math.max(state.iterations, 1);
|
|
6158
|
-
void presenter.onToolUse(toolName,
|
|
6525
|
+
void presenter.onToolUse(toolName, parameters, toolId);
|
|
6159
6526
|
break;
|
|
6160
6527
|
}
|
|
6161
6528
|
case "tool_result": {
|
|
6162
|
-
const toolId = typeof parsed.
|
|
6163
|
-
|
|
6164
|
-
void
|
|
6529
|
+
const toolId = typeof parsed.id === "string" ? parsed.id : `droid-tool-${Date.now()}`;
|
|
6530
|
+
const value2 = parsed.value;
|
|
6531
|
+
const content = typeof value2 === "string" ? value2 : value2 !== void 0 ? JSON.stringify(value2) : "";
|
|
6532
|
+
const isError = parsed.isError === true;
|
|
6533
|
+
void presenter.onToolResult?.(toolId, content, isError);
|
|
6165
6534
|
break;
|
|
6166
6535
|
}
|
|
6167
|
-
case "
|
|
6168
|
-
const
|
|
6169
|
-
const
|
|
6170
|
-
const
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
} else {
|
|
6176
|
-
void presenter.onLog(`[gemini_cli/${severity}] ${message}`);
|
|
6177
|
-
}
|
|
6536
|
+
case "completion": {
|
|
6537
|
+
const finalText = typeof parsed.finalText === "string" ? parsed.finalText : "";
|
|
6538
|
+
const numTurns = typeof parsed.numTurns === "number" ? parsed.numTurns : 1;
|
|
6539
|
+
const durationMs = typeof parsed.durationMs === "number" ? parsed.durationMs : 0;
|
|
6540
|
+
state.iterations = Math.max(state.iterations, numTurns, finalText ? 1 : 0);
|
|
6541
|
+
if (finalText.trim() && !state.summary.trim()) {
|
|
6542
|
+
state.summary = finalText;
|
|
6543
|
+
void presenter.onAssistantText(finalText);
|
|
6178
6544
|
}
|
|
6179
|
-
break;
|
|
6180
|
-
}
|
|
6181
|
-
case "result": {
|
|
6182
|
-
const stats = typeof parsed.stats === "object" && parsed.stats !== null ? parsed.stats : {};
|
|
6183
|
-
state.iterations = Math.max(state.iterations, 1);
|
|
6184
6545
|
state.usage = {
|
|
6185
|
-
model: state.activeModel || (typeof context.config.selectedModel === "string" && context.config.selectedModel.trim() ? context.config.selectedModel : "
|
|
6186
|
-
numTurns: Math.max(
|
|
6187
|
-
durationMs
|
|
6188
|
-
inputTokens:
|
|
6189
|
-
outputTokens:
|
|
6190
|
-
cacheReadTokens:
|
|
6546
|
+
model: state.activeModel || (typeof context.config.selectedModel === "string" && context.config.selectedModel.trim() ? context.config.selectedModel : "droid"),
|
|
6547
|
+
numTurns: Math.max(numTurns, 1),
|
|
6548
|
+
durationMs,
|
|
6549
|
+
inputTokens: 0,
|
|
6550
|
+
outputTokens: 0,
|
|
6551
|
+
cacheReadTokens: 0,
|
|
6191
6552
|
cacheCreationTokens: 0,
|
|
6192
6553
|
costUsd: 0
|
|
6193
6554
|
};
|
|
6194
6555
|
void presenter.onUsageUpdate?.({
|
|
6195
6556
|
model: state.usage.model,
|
|
6196
|
-
inputTokens:
|
|
6197
|
-
outputTokens:
|
|
6198
|
-
cacheReadTokens:
|
|
6199
|
-
cacheCreationTokens:
|
|
6557
|
+
inputTokens: 0,
|
|
6558
|
+
outputTokens: 0,
|
|
6559
|
+
cacheReadTokens: 0,
|
|
6560
|
+
cacheCreationTokens: 0
|
|
6200
6561
|
});
|
|
6201
|
-
if (typeof parsed.status === "string" && parsed.status !== "success" && !state.error) {
|
|
6202
|
-
const errObj = typeof parsed.error === "object" && parsed.error !== null ? parsed.error : null;
|
|
6203
|
-
const errMsg = typeof parsed.error === "string" && parsed.error.trim() || errObj && typeof errObj.message === "string" && errObj.message.trim() || typeof parsed.error_message === "string" && parsed.error_message.trim() || typeof parsed.message === "string" && parsed.message.trim() || typeof parsed.result === "string" && parsed.result.trim() || parsed.status;
|
|
6204
|
-
state.error = errMsg;
|
|
6205
|
-
console.error("[gemini_cli] result status=", parsed.status, "error:", JSON.stringify({
|
|
6206
|
-
error: parsed.error,
|
|
6207
|
-
error_message: parsed.error_message,
|
|
6208
|
-
message: parsed.message
|
|
6209
|
-
}));
|
|
6210
|
-
}
|
|
6211
6562
|
break;
|
|
6212
6563
|
}
|
|
6213
6564
|
default:
|
|
6214
6565
|
break;
|
|
6215
6566
|
}
|
|
6216
6567
|
}
|
|
6217
|
-
function
|
|
6218
|
-
if (
|
|
6219
|
-
|
|
6220
|
-
case "plan":
|
|
6221
|
-
case "ask":
|
|
6222
|
-
case "review":
|
|
6223
|
-
return true;
|
|
6224
|
-
default:
|
|
6225
|
-
return false;
|
|
6226
|
-
}
|
|
6227
|
-
}
|
|
6228
|
-
function getClaudePermissionMode(config) {
|
|
6229
|
-
switch (config.mode) {
|
|
6230
|
-
case "plan":
|
|
6231
|
-
case "ask":
|
|
6232
|
-
case "review":
|
|
6233
|
-
return "plan";
|
|
6234
|
-
default:
|
|
6235
|
-
return "bypassPermissions";
|
|
6236
|
-
}
|
|
6237
|
-
}
|
|
6238
|
-
function getClaudeDisallowedTools(config) {
|
|
6239
|
-
return isReadOnlyAgent(config.agentId) ? ["Edit", "Write", "MultiEdit", "NotebookEdit"] : [];
|
|
6240
|
-
}
|
|
6241
|
-
function shouldForceCursorAgent(config) {
|
|
6242
|
-
if (shouldUseReadOnlyRuntimePermissions(config)) return false;
|
|
6243
|
-
switch (config.mode) {
|
|
6244
|
-
case "plan":
|
|
6245
|
-
case "ask":
|
|
6246
|
-
case "review":
|
|
6247
|
-
return false;
|
|
6248
|
-
default:
|
|
6249
|
-
return true;
|
|
6250
|
-
}
|
|
6251
|
-
}
|
|
6252
|
-
function writeImagesToTempFiles(images) {
|
|
6253
|
-
if (!images?.length) return { paths: [], cleanup: () => {
|
|
6254
|
-
} };
|
|
6255
|
-
const paths = [];
|
|
6256
|
-
for (const img of images) {
|
|
6257
|
-
const ext = img.mimeType.split("/")[1]?.replace("jpeg", "jpg") ?? "png";
|
|
6258
|
-
const filePath = (0, import_path.join)((0, import_os.tmpdir)(), `aiden-img-${img.id}.${ext}`);
|
|
6259
|
-
(0, import_fs.writeFileSync)(filePath, Buffer.from(img.data, "base64"));
|
|
6260
|
-
paths.push(filePath);
|
|
6261
|
-
}
|
|
6262
|
-
return {
|
|
6263
|
-
paths,
|
|
6264
|
-
cleanup: () => {
|
|
6265
|
-
for (const p of paths) {
|
|
6266
|
-
try {
|
|
6267
|
-
(0, import_fs.rmSync)(p, { force: true });
|
|
6268
|
-
} catch {
|
|
6269
|
-
}
|
|
6270
|
-
}
|
|
6271
|
-
}
|
|
6272
|
-
};
|
|
6273
|
-
}
|
|
6274
|
-
function buildClaudeModelArg(baseModel, selectedContextWindow) {
|
|
6275
|
-
if (selectedContextWindow === "1m") return `${baseModel}[1m]`;
|
|
6276
|
-
return baseModel;
|
|
6568
|
+
function droidExecPermissionArgs(mode) {
|
|
6569
|
+
if (mode === "plan" || mode === "ask" || mode === "review") return [];
|
|
6570
|
+
return ["--auto", "high"];
|
|
6277
6571
|
}
|
|
6278
|
-
function
|
|
6572
|
+
function createDroidCliBackend(command = "droid", defaultArgs = []) {
|
|
6279
6573
|
return {
|
|
6280
|
-
kind: "
|
|
6574
|
+
kind: "droid_cli",
|
|
6281
6575
|
supportTier: "structured",
|
|
6282
6576
|
async run(context) {
|
|
6283
|
-
const
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
|
|
6307
|
-
|
|
6308
|
-
}
|
|
6309
|
-
|
|
6310
|
-
if (claudeEffort) {
|
|
6311
|
-
args.push("--effort", claudeEffort);
|
|
6312
|
-
}
|
|
6313
|
-
if (resumeId) {
|
|
6314
|
-
args.push("--resume", resumeId);
|
|
6315
|
-
console.info("[claude_cli] Resuming session", { resumeId });
|
|
6577
|
+
const { files: imageFiles, cleanup } = writeImagesToTempFiles(context.config.images, context.cwd);
|
|
6578
|
+
try {
|
|
6579
|
+
return await createGenericCliBackend({
|
|
6580
|
+
kind: "droid_cli",
|
|
6581
|
+
supportTier: "structured",
|
|
6582
|
+
command,
|
|
6583
|
+
args: [],
|
|
6584
|
+
buildArgs: (ctx, prompt) => {
|
|
6585
|
+
const args = [
|
|
6586
|
+
"exec",
|
|
6587
|
+
"--output-format",
|
|
6588
|
+
"stream-json",
|
|
6589
|
+
...droidExecPermissionArgs(ctx.config.mode),
|
|
6590
|
+
...defaultArgs
|
|
6591
|
+
];
|
|
6592
|
+
const resumeId = ctx.config.runtimeSessionId?.trim() || ctx.config.providerSessionId?.trim();
|
|
6593
|
+
if (resumeId) args.push("--session-id", resumeId);
|
|
6594
|
+
const model = ctx.config.selectedModel?.trim();
|
|
6595
|
+
if (model) args.push("-m", model);
|
|
6596
|
+
args.push(prompt);
|
|
6597
|
+
return args;
|
|
6598
|
+
},
|
|
6599
|
+
augmentPrompt: (ctx) => buildPromptWithImagePathReferences(ctx, imageFiles),
|
|
6600
|
+
parseStructuredLine: parseDroidStructuredLine
|
|
6601
|
+
}).run(context);
|
|
6602
|
+
} finally {
|
|
6603
|
+
cleanup();
|
|
6316
6604
|
}
|
|
6317
|
-
args.push("--chrome");
|
|
6318
|
-
console.info("[claude_cli] Chrome flag added \u2014 final args:", args.join(" "));
|
|
6319
|
-
args.push(...defaultArgs);
|
|
6320
|
-
return createGenericCliBackend({
|
|
6321
|
-
kind: "claude_cli",
|
|
6322
|
-
supportTier: "structured",
|
|
6323
|
-
command,
|
|
6324
|
-
args,
|
|
6325
|
-
parseStructuredLine: parseClaudeStructuredLine,
|
|
6326
|
-
stdinWriter: (stdin, ctx) => {
|
|
6327
|
-
const contentBlocks = [];
|
|
6328
|
-
if (ctx.config.images?.length) {
|
|
6329
|
-
for (const img of ctx.config.images) {
|
|
6330
|
-
contentBlocks.push({
|
|
6331
|
-
type: "image",
|
|
6332
|
-
source: { type: "base64", media_type: img.mimeType, data: img.data }
|
|
6333
|
-
});
|
|
6334
|
-
}
|
|
6335
|
-
}
|
|
6336
|
-
const promptText = ctx.config.mode === "plan" ? buildPlanModePrefix(buildPromptWithSystem(ctx.config, ctx.promptText)) : buildPromptWithSystem(ctx.config, ctx.promptText);
|
|
6337
|
-
if (promptText.trim()) {
|
|
6338
|
-
contentBlocks.push({ type: "text", text: promptText });
|
|
6339
|
-
}
|
|
6340
|
-
console.info(`[claude_cli] stdinWriter: ${contentBlocks.length} content blocks (${ctx.config.images?.length ?? 0} images)`);
|
|
6341
|
-
const message = JSON.stringify({
|
|
6342
|
-
type: "user",
|
|
6343
|
-
message: { role: "user", content: contentBlocks },
|
|
6344
|
-
session_id: "default",
|
|
6345
|
-
parent_tool_use_id: null
|
|
6346
|
-
});
|
|
6347
|
-
stdin.write(message + "\n");
|
|
6348
|
-
},
|
|
6349
|
-
keepStdinOpen: true,
|
|
6350
|
-
allowContinuation: true,
|
|
6351
|
-
continuationIdleMs: 10 * 60 * 1e3
|
|
6352
|
-
// 10 minutes
|
|
6353
|
-
}).run(context);
|
|
6354
6605
|
}
|
|
6355
6606
|
};
|
|
6356
6607
|
}
|
|
6357
|
-
function
|
|
6358
|
-
let resolved = cwd;
|
|
6359
|
-
try {
|
|
6360
|
-
resolved = (0, import_fs.realpathSync)(cwd);
|
|
6361
|
-
} catch {
|
|
6362
|
-
}
|
|
6363
|
-
return (0, import_crypto.createHash)("md5").update(resolved).digest("hex");
|
|
6364
|
-
}
|
|
6365
|
-
function shouldEnableKimiCliThinking(model) {
|
|
6366
|
-
return model?.trim() === "kimi-k2.6-thinking";
|
|
6367
|
-
}
|
|
6368
|
-
function resolveKimiCliModelArg(model) {
|
|
6369
|
-
const trimmed = model?.trim();
|
|
6370
|
-
if (!trimmed) return void 0;
|
|
6371
|
-
if (trimmed === "kimi-latest" || trimmed === "kimi-k2.5" || trimmed === "kimi-k2.6" || shouldEnableKimiCliThinking(trimmed)) {
|
|
6372
|
-
return void 0;
|
|
6373
|
-
}
|
|
6374
|
-
if (trimmed.startsWith("claude-")) return void 0;
|
|
6375
|
-
return trimmed;
|
|
6376
|
-
}
|
|
6377
|
-
function parseKimiStructuredLine(line, context, state) {
|
|
6608
|
+
function parseOpencodeStructuredLine(line, context, state) {
|
|
6378
6609
|
const presenter = context.presenter;
|
|
6379
|
-
|
|
6380
|
-
state.runtimeSessionId = kimiSessionId(context.cwd);
|
|
6381
|
-
}
|
|
6382
|
-
let parsed;
|
|
6610
|
+
let parsed = null;
|
|
6383
6611
|
try {
|
|
6384
6612
|
parsed = JSON.parse(line);
|
|
6385
6613
|
} catch {
|
|
6386
|
-
|
|
6387
|
-
if (text.length > 0) {
|
|
6388
|
-
if (text.toLowerCase().includes("llm not set")) state.error = text;
|
|
6389
|
-
void presenter.onLog(`[kimi_cli] ${line}`);
|
|
6390
|
-
}
|
|
6614
|
+
if (line.trim().length > 0) void presenter.onLog(`[opencode] ${line}`);
|
|
6391
6615
|
return;
|
|
6392
6616
|
}
|
|
6393
|
-
const
|
|
6394
|
-
|
|
6395
|
-
const
|
|
6396
|
-
if (
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
|
|
6617
|
+
const type = typeof parsed.type === "string" ? parsed.type : "";
|
|
6618
|
+
if (!type) return;
|
|
6619
|
+
const sessionID = typeof parsed.sessionID === "string" ? parsed.sessionID : void 0;
|
|
6620
|
+
if (sessionID) state.runtimeSessionId = sessionID;
|
|
6621
|
+
const part = typeof parsed.part === "object" && parsed.part !== null ? parsed.part : null;
|
|
6622
|
+
switch (type) {
|
|
6623
|
+
case "step_start": {
|
|
6624
|
+
state.iterations += 1;
|
|
6625
|
+
void presenter.onLog(`[opencode] Step ${state.iterations} started`);
|
|
6626
|
+
break;
|
|
6401
6627
|
}
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6628
|
+
case "text": {
|
|
6629
|
+
const text = part && typeof part.text === "string" ? part.text : "";
|
|
6630
|
+
if (text) {
|
|
6631
|
+
state.summary += text;
|
|
6632
|
+
void presenter.onAssistantText(text);
|
|
6633
|
+
}
|
|
6634
|
+
break;
|
|
6635
|
+
}
|
|
6636
|
+
case "tool_use":
|
|
6637
|
+
case "tool.execute": {
|
|
6638
|
+
const toolName = part && typeof part.name === "string" ? part.name : "OpenCode Tool";
|
|
6639
|
+
const toolId = part && typeof part.id === "string" ? part.id : `opencode-tool-${Date.now()}`;
|
|
6640
|
+
void presenter.onToolUse(toolName, part ?? {}, toolId);
|
|
6641
|
+
break;
|
|
6642
|
+
}
|
|
6643
|
+
case "tool_result":
|
|
6644
|
+
case "tool.result": {
|
|
6645
|
+
const toolId = part && typeof part.id === "string" ? part.id : `opencode-tool-${Date.now()}`;
|
|
6646
|
+
const output = part && typeof part.output === "string" ? part.output : JSON.stringify(part);
|
|
6647
|
+
void presenter.onToolResult?.(toolId, output);
|
|
6648
|
+
break;
|
|
6649
|
+
}
|
|
6650
|
+
case "step_finish": {
|
|
6651
|
+
if (part && typeof part.tokens === "object" && part.tokens !== null) {
|
|
6652
|
+
const tokens = part.tokens;
|
|
6653
|
+
state.usage = {
|
|
6654
|
+
model: typeof context.config.selectedModel === "string" && context.config.selectedModel.trim() || "opencode",
|
|
6655
|
+
numTurns: Math.max(state.iterations, 1),
|
|
6656
|
+
durationMs: 0,
|
|
6657
|
+
inputTokens: tokens.input ?? 0,
|
|
6658
|
+
outputTokens: tokens.output ?? 0,
|
|
6659
|
+
cacheReadTokens: tokens.cache_read ?? 0,
|
|
6660
|
+
cacheCreationTokens: tokens.cache_creation ?? 0,
|
|
6661
|
+
costUsd: 0
|
|
6662
|
+
};
|
|
6663
|
+
void presenter.onUsageUpdate?.({
|
|
6664
|
+
model: state.usage.model,
|
|
6665
|
+
inputTokens: state.usage.inputTokens,
|
|
6666
|
+
outputTokens: state.usage.outputTokens,
|
|
6667
|
+
cacheReadTokens: state.usage.cacheReadTokens,
|
|
6668
|
+
cacheCreationTokens: state.usage.cacheCreationTokens
|
|
6669
|
+
});
|
|
6670
|
+
}
|
|
6671
|
+
break;
|
|
6672
|
+
}
|
|
6673
|
+
case "session.error":
|
|
6674
|
+
case "error": {
|
|
6675
|
+
const errObj = typeof parsed.error === "object" && parsed.error !== null ? parsed.error : null;
|
|
6676
|
+
if (!state.error) {
|
|
6677
|
+
console.error("[opencode] error event (raw):", JSON.stringify(parsed).slice(0, 500));
|
|
6678
|
+
}
|
|
6679
|
+
const rawMessage = typeof parsed.message === "string" && parsed.message.trim() || part && typeof part.message === "string" && part.message.trim() || typeof parsed.error === "string" && parsed.error.trim() || errObj && typeof errObj.message === "string" && errObj.message.trim() || part && typeof part.error === "string" && part.error.trim() || // Also try data.message — some opencode versions nest the message here
|
|
6680
|
+
(typeof parsed.data?.message === "string" ? parsed.data.message.trim() : "") || "";
|
|
6681
|
+
const message = rawMessage || "OpenCode encountered an error. Check your provider and model settings.";
|
|
6682
|
+
if (!state.error) {
|
|
6683
|
+
void presenter.onError(message);
|
|
6684
|
+
state.error = message;
|
|
6685
|
+
}
|
|
6686
|
+
const pid = state.process?.pid;
|
|
6687
|
+
if (pid) {
|
|
6688
|
+
try {
|
|
6689
|
+
process.kill(-pid, "SIGTERM");
|
|
6690
|
+
} catch {
|
|
6410
6691
|
try {
|
|
6411
|
-
|
|
6692
|
+
state.process?.kill("SIGTERM");
|
|
6412
6693
|
} catch {
|
|
6413
6694
|
}
|
|
6414
|
-
} else if (typeof fn.arguments === "object" && fn.arguments !== null) {
|
|
6415
|
-
args = fn.arguments;
|
|
6416
6695
|
}
|
|
6417
|
-
void presenter.onToolUse(toolName, args, toolId);
|
|
6418
6696
|
}
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6697
|
+
break;
|
|
6698
|
+
}
|
|
6699
|
+
default:
|
|
6700
|
+
break;
|
|
6701
|
+
}
|
|
6702
|
+
}
|
|
6703
|
+
function createOpencodeCliBackend(command = "opencode", defaultArgs = []) {
|
|
6704
|
+
return {
|
|
6705
|
+
kind: "opencode_cli",
|
|
6706
|
+
supportTier: "structured",
|
|
6707
|
+
async run(context) {
|
|
6708
|
+
const { paths: imagePaths, cleanup } = writeImagesToTempFiles(context.config.images, context.cwd);
|
|
6709
|
+
try {
|
|
6710
|
+
return await createGenericCliBackend({
|
|
6711
|
+
kind: "opencode_cli",
|
|
6712
|
+
supportTier: "structured",
|
|
6713
|
+
command,
|
|
6714
|
+
args: [],
|
|
6715
|
+
buildArgs: (ctx, prompt) => {
|
|
6716
|
+
const args = ["run", "--format", "json"];
|
|
6717
|
+
const resumeId = ctx.config.runtimeSessionId?.trim() || ctx.config.providerSessionId?.trim();
|
|
6718
|
+
if (resumeId) args.push("--session", resumeId);
|
|
6719
|
+
const model = ctx.config.selectedModel?.trim();
|
|
6720
|
+
if (model && model.includes("/")) args.push("--model", model);
|
|
6721
|
+
const fileArgs = imagePaths.flatMap((p) => ["--file", p]);
|
|
6722
|
+
if (fileArgs.length > 0) {
|
|
6723
|
+
return [...args, ...fileArgs, ...defaultArgs, "--", prompt];
|
|
6724
|
+
}
|
|
6725
|
+
return [...args, ...defaultArgs, prompt];
|
|
6726
|
+
},
|
|
6727
|
+
augmentPrompt: (ctx) => {
|
|
6728
|
+
const base = buildPromptWithSystem(ctx.config, ctx.promptText);
|
|
6729
|
+
return ctx.config.mode === "plan" ? buildPlanModePrefix(base) : base;
|
|
6730
|
+
},
|
|
6731
|
+
parseStructuredLine: parseOpencodeStructuredLine
|
|
6732
|
+
}).run(context);
|
|
6733
|
+
} finally {
|
|
6734
|
+
cleanup();
|
|
6425
6735
|
}
|
|
6426
6736
|
}
|
|
6427
|
-
|
|
6428
|
-
}
|
|
6429
|
-
if (role === "tool") {
|
|
6430
|
-
const toolCallId = typeof parsed.tool_call_id === "string" ? parsed.tool_call_id : `kimi-tool-${Date.now()}`;
|
|
6431
|
-
const allText = contentBlocks.filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text);
|
|
6432
|
-
const userText = allText.filter((t) => !t.startsWith("<system>")).join("\n");
|
|
6433
|
-
void presenter.onToolResult?.(toolCallId, userText || allText.join("\n"));
|
|
6434
|
-
return;
|
|
6435
|
-
}
|
|
6737
|
+
};
|
|
6436
6738
|
}
|
|
6437
|
-
function
|
|
6438
|
-
return
|
|
6439
|
-
kind: "
|
|
6440
|
-
supportTier: "
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
|
|
6452
|
-
|
|
6453
|
-
|
|
6454
|
-
args.push("--thinking");
|
|
6739
|
+
function createGenericCliPassthroughBackend(command, defaultArgs = []) {
|
|
6740
|
+
return {
|
|
6741
|
+
kind: "generic_cli",
|
|
6742
|
+
supportTier: "text",
|
|
6743
|
+
async run(context) {
|
|
6744
|
+
const { files: imageFiles, cleanup } = writeImagesToTempFiles(context.config.images, context.cwd);
|
|
6745
|
+
try {
|
|
6746
|
+
return await createGenericCliBackend({
|
|
6747
|
+
kind: "generic_cli",
|
|
6748
|
+
supportTier: "text",
|
|
6749
|
+
command,
|
|
6750
|
+
args: defaultArgs,
|
|
6751
|
+
promptViaStdin: true,
|
|
6752
|
+
augmentPrompt: (ctx) => buildPromptWithImagePathReferences(ctx, imageFiles)
|
|
6753
|
+
}).run(context);
|
|
6754
|
+
} finally {
|
|
6755
|
+
cleanup();
|
|
6455
6756
|
}
|
|
6456
|
-
|
|
6457
|
-
|
|
6458
|
-
return [...args, ...defaultArgs, "--prompt", prompt];
|
|
6459
|
-
},
|
|
6460
|
-
augmentPrompt: (context) => {
|
|
6461
|
-
const base = buildPromptWithSystem(context.config, context.promptText);
|
|
6462
|
-
return context.config.mode === "plan" ? buildPlanModePrefix(base) : base;
|
|
6463
|
-
},
|
|
6464
|
-
parseStructuredLine: parseKimiStructuredLine
|
|
6465
|
-
});
|
|
6466
|
-
}
|
|
6467
|
-
function createGeminiCliBackend(command = "gemini", defaultArgs = []) {
|
|
6468
|
-
return createGenericCliBackend({
|
|
6469
|
-
kind: "gemini_cli",
|
|
6470
|
-
supportTier: "structured",
|
|
6471
|
-
command,
|
|
6472
|
-
args: [],
|
|
6473
|
-
buildArgs: (context) => {
|
|
6474
|
-
const args = ["-p", "", "--output-format", "stream-json"];
|
|
6475
|
-
const resumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
|
|
6476
|
-
const approvalMode = context.config.mode === "plan" || context.config.mode === "ask" || context.config.mode === "review" ? "plan" : "yolo";
|
|
6477
|
-
args.push("--approval-mode", approvalMode);
|
|
6478
|
-
if (resumeId) args.push("--resume", resumeId);
|
|
6479
|
-
if (context.config.selectedModel?.trim()) args.push("--model", context.config.selectedModel.trim());
|
|
6480
|
-
return [...args, ...defaultArgs];
|
|
6481
|
-
},
|
|
6482
|
-
promptViaStdin: true,
|
|
6483
|
-
augmentPrompt: (context) => {
|
|
6484
|
-
const basePrompt = buildPromptWithSystem(context.config, context.promptText);
|
|
6485
|
-
return context.config.mode === "plan" ? buildPlanModePrefix(basePrompt) : basePrompt;
|
|
6486
|
-
},
|
|
6487
|
-
parseStructuredLine: parseGeminiStructuredLine
|
|
6488
|
-
});
|
|
6489
|
-
}
|
|
6490
|
-
function isCursorAgentCliModelId(model) {
|
|
6491
|
-
return model === "auto" || /^composer-/.test(model) || /^gpt-\d/.test(model) || /^claude-\d/.test(model) || /^gemini-\d/.test(model) || /^grok-\d/.test(model) || /^kimi-/.test(model);
|
|
6492
|
-
}
|
|
6493
|
-
function createCursorAgentCliBackend(command = "cursor-agent", defaultArgs = []) {
|
|
6494
|
-
return createGenericCliBackend({
|
|
6495
|
-
kind: "cursor_agent_cli",
|
|
6496
|
-
supportTier: "structured",
|
|
6497
|
-
command,
|
|
6498
|
-
args: [],
|
|
6499
|
-
buildArgs: (context, prompt) => {
|
|
6500
|
-
const args = ["--print", "--output-format", "stream-json"];
|
|
6501
|
-
const resumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
|
|
6502
|
-
if (shouldForceCursorAgent(context.config)) args.push("--force");
|
|
6503
|
-
if (resumeId) args.push("--resume", resumeId);
|
|
6504
|
-
const model = context.config.selectedModel?.trim();
|
|
6505
|
-
if (model && isCursorAgentCliModelId(model)) args.push("--model", model);
|
|
6506
|
-
return [...args, ...defaultArgs, prompt];
|
|
6507
|
-
},
|
|
6508
|
-
augmentPrompt: (context) => {
|
|
6509
|
-
const base = buildPromptWithSystem(context.config, context.promptText);
|
|
6510
|
-
return context.config.mode === "plan" ? buildPlanModePrefix(base) : base;
|
|
6511
|
-
},
|
|
6512
|
-
parseStructuredLine: parseCursorStructuredLine
|
|
6513
|
-
});
|
|
6514
|
-
}
|
|
6515
|
-
function droidExecPermissionArgs(mode) {
|
|
6516
|
-
if (mode === "plan" || mode === "ask" || mode === "review") return [];
|
|
6517
|
-
return ["--auto", "high"];
|
|
6757
|
+
}
|
|
6758
|
+
};
|
|
6518
6759
|
}
|
|
6519
|
-
function
|
|
6760
|
+
function parseCodexStructuredLine(line, context, state) {
|
|
6520
6761
|
const presenter = context.presenter;
|
|
6521
6762
|
let parsed = null;
|
|
6522
6763
|
try {
|
|
6523
6764
|
parsed = JSON.parse(line);
|
|
6524
6765
|
} catch {
|
|
6525
|
-
|
|
6766
|
+
void presenter.onLog(`[codex] ${line}`);
|
|
6526
6767
|
return;
|
|
6527
6768
|
}
|
|
6528
6769
|
const type = typeof parsed.type === "string" ? parsed.type : "";
|
|
6529
6770
|
if (!type) return;
|
|
6530
|
-
if (typeof parsed.
|
|
6531
|
-
|
|
6771
|
+
if (typeof parsed.thread_id === "string") {
|
|
6772
|
+
state.runtimeSessionId = parsed.thread_id;
|
|
6773
|
+
}
|
|
6774
|
+
const extractCodexErrorMessage = (value2) => {
|
|
6775
|
+
if (typeof value2 !== "string") return "";
|
|
6776
|
+
const trimmed = value2.trim();
|
|
6777
|
+
if (!trimmed) return "";
|
|
6778
|
+
try {
|
|
6779
|
+
const parsedValue = JSON.parse(trimmed);
|
|
6780
|
+
if (typeof parsedValue.detail === "string" && parsedValue.detail.trim().length > 0) {
|
|
6781
|
+
return parsedValue.detail.trim();
|
|
6782
|
+
}
|
|
6783
|
+
if (typeof parsedValue.message === "string" && parsedValue.message.trim().length > 0) {
|
|
6784
|
+
return parsedValue.message.trim();
|
|
6785
|
+
}
|
|
6786
|
+
} catch {
|
|
6787
|
+
}
|
|
6788
|
+
return trimmed;
|
|
6789
|
+
};
|
|
6532
6790
|
switch (type) {
|
|
6533
|
-
case "
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
6791
|
+
case "thread.started":
|
|
6792
|
+
break;
|
|
6793
|
+
case "turn.started":
|
|
6794
|
+
state.iterations += 1;
|
|
6795
|
+
break;
|
|
6796
|
+
case "session_configured":
|
|
6797
|
+
if (typeof parsed.session_id === "string") {
|
|
6798
|
+
state.runtimeSessionId = parsed.session_id;
|
|
6538
6799
|
}
|
|
6539
6800
|
break;
|
|
6801
|
+
case "task_started":
|
|
6802
|
+
state.iterations += 1;
|
|
6803
|
+
break;
|
|
6804
|
+
case "item.started": {
|
|
6805
|
+
const item = typeof parsed.item === "object" && parsed.item !== null ? parsed.item : null;
|
|
6806
|
+
if (!item || item.type !== "command_execution" || typeof item.id !== "string") break;
|
|
6807
|
+
const command = typeof item.command === "string" ? item.command : "";
|
|
6808
|
+
void presenter.onToolUse("Bash", { command, cwd: context.cwd }, item.id);
|
|
6809
|
+
break;
|
|
6540
6810
|
}
|
|
6541
|
-
case "
|
|
6542
|
-
const
|
|
6543
|
-
if (
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
|
|
6811
|
+
case "item.completed": {
|
|
6812
|
+
const item = typeof parsed.item === "object" && parsed.item !== null ? parsed.item : null;
|
|
6813
|
+
if (!item || typeof item.type !== "string") break;
|
|
6814
|
+
if (item.type === "reasoning" && typeof item.text === "string") {
|
|
6815
|
+
void presenter.onThinking(item.text);
|
|
6816
|
+
break;
|
|
6817
|
+
}
|
|
6818
|
+
if (item.type === "agent_message" && typeof item.text === "string") {
|
|
6819
|
+
state.summary += `${item.text}
|
|
6820
|
+
`;
|
|
6821
|
+
void presenter.onAssistantText(item.text);
|
|
6822
|
+
break;
|
|
6823
|
+
}
|
|
6824
|
+
if (item.type === "command_execution" && typeof item.id === "string") {
|
|
6825
|
+
const output = typeof item.aggregated_output === "string" ? item.aggregated_output : "";
|
|
6826
|
+
const itemExitCode = typeof item.exit_code === "number" ? item.exit_code : null;
|
|
6827
|
+
const resultText = output.trim().length > 0 ? output : itemExitCode === null ? "" : `Exit code: ${itemExitCode}`;
|
|
6828
|
+
void presenter.onToolResult?.(item.id, resultText);
|
|
6829
|
+
break;
|
|
6830
|
+
}
|
|
6549
6831
|
break;
|
|
6550
6832
|
}
|
|
6551
|
-
case "
|
|
6552
|
-
|
|
6553
|
-
const
|
|
6554
|
-
|
|
6555
|
-
state.
|
|
6556
|
-
void presenter.
|
|
6833
|
+
case "agent_message_delta":
|
|
6834
|
+
case "agent_message_content_delta": {
|
|
6835
|
+
const delta = typeof parsed.delta === "string" ? parsed.delta : "";
|
|
6836
|
+
if (!delta) return;
|
|
6837
|
+
state.summary += delta;
|
|
6838
|
+
void presenter.onAssistantText(delta);
|
|
6557
6839
|
break;
|
|
6558
6840
|
}
|
|
6559
|
-
case "
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
const
|
|
6563
|
-
|
|
6564
|
-
void presenter.
|
|
6841
|
+
case "agent_reasoning_delta":
|
|
6842
|
+
case "reasoning_content_delta":
|
|
6843
|
+
case "agent_reasoning_raw_content_delta": {
|
|
6844
|
+
const delta = typeof parsed.delta === "string" ? parsed.delta : "";
|
|
6845
|
+
if (!delta) return;
|
|
6846
|
+
void presenter.onThinking(delta);
|
|
6565
6847
|
break;
|
|
6566
6848
|
}
|
|
6567
|
-
case "
|
|
6568
|
-
const
|
|
6569
|
-
const
|
|
6570
|
-
const
|
|
6571
|
-
|
|
6572
|
-
if (finalText.trim() && !state.summary.trim()) {
|
|
6573
|
-
state.summary = finalText;
|
|
6574
|
-
void presenter.onAssistantText(finalText);
|
|
6575
|
-
}
|
|
6576
|
-
state.usage = {
|
|
6577
|
-
model: state.activeModel || (typeof context.config.selectedModel === "string" && context.config.selectedModel.trim() ? context.config.selectedModel : "droid"),
|
|
6578
|
-
numTurns: Math.max(numTurns, 1),
|
|
6579
|
-
durationMs,
|
|
6580
|
-
inputTokens: 0,
|
|
6581
|
-
outputTokens: 0,
|
|
6582
|
-
cacheReadTokens: 0,
|
|
6583
|
-
cacheCreationTokens: 0,
|
|
6584
|
-
costUsd: 0
|
|
6585
|
-
};
|
|
6586
|
-
void presenter.onUsageUpdate?.({
|
|
6587
|
-
model: state.usage.model,
|
|
6588
|
-
inputTokens: 0,
|
|
6589
|
-
outputTokens: 0,
|
|
6590
|
-
cacheReadTokens: 0,
|
|
6591
|
-
cacheCreationTokens: 0
|
|
6592
|
-
});
|
|
6849
|
+
case "exec_command_begin": {
|
|
6850
|
+
const toolId = typeof parsed.call_id === "string" ? parsed.call_id : `exec-${Date.now()}`;
|
|
6851
|
+
const command = Array.isArray(parsed.command) ? parsed.command.join(" ") : "";
|
|
6852
|
+
const cwd = typeof parsed.cwd === "string" ? parsed.cwd : context.cwd;
|
|
6853
|
+
void presenter.onToolUse("Bash", { command, cwd }, toolId);
|
|
6593
6854
|
break;
|
|
6594
6855
|
}
|
|
6595
|
-
|
|
6856
|
+
case "exec_command_end": {
|
|
6857
|
+
const toolId = typeof parsed.call_id === "string" ? parsed.call_id : `exec-${Date.now()}`;
|
|
6858
|
+
const output = typeof parsed.formatted_output === "string" ? parsed.formatted_output : typeof parsed.aggregated_output === "string" ? parsed.aggregated_output : "";
|
|
6859
|
+
void presenter.onToolResult?.(toolId, output);
|
|
6596
6860
|
break;
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
command,
|
|
6604
|
-
args: [],
|
|
6605
|
-
buildArgs: (context, prompt) => {
|
|
6606
|
-
const args = [
|
|
6607
|
-
"exec",
|
|
6608
|
-
"--output-format",
|
|
6609
|
-
"stream-json",
|
|
6610
|
-
...droidExecPermissionArgs(context.config.mode),
|
|
6611
|
-
...defaultArgs
|
|
6612
|
-
];
|
|
6613
|
-
const resumeId = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
|
|
6614
|
-
if (resumeId) args.push("--session-id", resumeId);
|
|
6615
|
-
const model = context.config.selectedModel?.trim();
|
|
6616
|
-
if (model) args.push("-m", model);
|
|
6617
|
-
args.push(prompt);
|
|
6618
|
-
return args;
|
|
6619
|
-
},
|
|
6620
|
-
augmentPrompt: (context) => {
|
|
6621
|
-
const base = buildPromptWithSystem(context.config, context.promptText);
|
|
6622
|
-
return context.config.mode === "plan" ? buildPlanModePrefix(base) : base;
|
|
6623
|
-
},
|
|
6624
|
-
parseStructuredLine: parseDroidStructuredLine
|
|
6625
|
-
});
|
|
6626
|
-
}
|
|
6627
|
-
function parseOpencodeStructuredLine(line, context, state) {
|
|
6628
|
-
const presenter = context.presenter;
|
|
6629
|
-
let parsed = null;
|
|
6630
|
-
try {
|
|
6631
|
-
parsed = JSON.parse(line);
|
|
6632
|
-
} catch {
|
|
6633
|
-
if (line.trim().length > 0) void presenter.onLog(`[opencode] ${line}`);
|
|
6634
|
-
return;
|
|
6635
|
-
}
|
|
6636
|
-
const type = typeof parsed.type === "string" ? parsed.type : "";
|
|
6637
|
-
if (!type) return;
|
|
6638
|
-
const sessionID = typeof parsed.sessionID === "string" ? parsed.sessionID : void 0;
|
|
6639
|
-
if (sessionID) state.runtimeSessionId = sessionID;
|
|
6640
|
-
const part = typeof parsed.part === "object" && parsed.part !== null ? parsed.part : null;
|
|
6641
|
-
switch (type) {
|
|
6642
|
-
case "step_start": {
|
|
6643
|
-
state.iterations += 1;
|
|
6644
|
-
void presenter.onLog(`[opencode] Step ${state.iterations} started`);
|
|
6861
|
+
}
|
|
6862
|
+
case "mcp_tool_call_begin": {
|
|
6863
|
+
const toolId = typeof parsed.call_id === "string" ? parsed.call_id : `mcp-${Date.now()}`;
|
|
6864
|
+
const invocation = typeof parsed.invocation === "object" && parsed.invocation !== null ? parsed.invocation : {};
|
|
6865
|
+
const tool = typeof invocation.tool_name === "string" ? invocation.tool_name : typeof invocation.tool === "string" ? invocation.tool : "MCP Tool";
|
|
6866
|
+
void presenter.onToolUse(tool, invocation, toolId);
|
|
6645
6867
|
break;
|
|
6646
6868
|
}
|
|
6647
|
-
case "
|
|
6648
|
-
const
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6869
|
+
case "mcp_tool_call_end": {
|
|
6870
|
+
const toolId = typeof parsed.call_id === "string" ? parsed.call_id : `mcp-${Date.now()}`;
|
|
6871
|
+
void presenter.onToolResult?.(toolId, JSON.stringify(parsed.result ?? {}, null, 2));
|
|
6872
|
+
break;
|
|
6873
|
+
}
|
|
6874
|
+
case "token_count": {
|
|
6875
|
+
const info = typeof parsed.info === "object" && parsed.info !== null ? parsed.info : {};
|
|
6876
|
+
state.usage = {
|
|
6877
|
+
model: typeof info.model === "string" ? info.model : "codex",
|
|
6878
|
+
numTurns: Math.max(state.iterations, 1),
|
|
6879
|
+
durationMs: 0,
|
|
6880
|
+
inputTokens: typeof info.input_tokens === "number" ? info.input_tokens : 0,
|
|
6881
|
+
outputTokens: typeof info.output_tokens === "number" ? info.output_tokens : 0,
|
|
6882
|
+
cacheReadTokens: typeof info.cached_input_tokens === "number" ? info.cached_input_tokens : 0,
|
|
6883
|
+
cacheCreationTokens: 0,
|
|
6884
|
+
costUsd: typeof info.total_cost_usd === "number" ? info.total_cost_usd : 0
|
|
6885
|
+
};
|
|
6886
|
+
void presenter.onUsageUpdate?.({
|
|
6887
|
+
model: state.usage.model,
|
|
6888
|
+
inputTokens: state.usage.inputTokens,
|
|
6889
|
+
outputTokens: state.usage.outputTokens,
|
|
6890
|
+
cacheReadTokens: state.usage.cacheReadTokens,
|
|
6891
|
+
cacheCreationTokens: state.usage.cacheCreationTokens
|
|
6892
|
+
});
|
|
6653
6893
|
break;
|
|
6654
6894
|
}
|
|
6655
|
-
case "
|
|
6656
|
-
|
|
6657
|
-
|
|
6658
|
-
const toolId = part && typeof part.id === "string" ? part.id : `opencode-tool-${Date.now()}`;
|
|
6659
|
-
void presenter.onToolUse(toolName, part ?? {}, toolId);
|
|
6895
|
+
case "task_complete": {
|
|
6896
|
+
const lastMessage = typeof parsed.last_agent_message === "string" ? parsed.last_agent_message : "";
|
|
6897
|
+
if (lastMessage.length > 0) state.summary = lastMessage;
|
|
6660
6898
|
break;
|
|
6661
6899
|
}
|
|
6662
|
-
case "
|
|
6663
|
-
|
|
6664
|
-
|
|
6665
|
-
|
|
6666
|
-
|
|
6900
|
+
case "turn.completed": {
|
|
6901
|
+
const usage = typeof parsed.usage === "object" && parsed.usage !== null ? parsed.usage : {};
|
|
6902
|
+
state.usage = {
|
|
6903
|
+
model: typeof context.config.selectedModel === "string" && context.config.selectedModel.trim() ? context.config.selectedModel : "codex",
|
|
6904
|
+
numTurns: Math.max(state.iterations, 1),
|
|
6905
|
+
durationMs: 0,
|
|
6906
|
+
inputTokens: typeof usage.input_tokens === "number" ? usage.input_tokens : 0,
|
|
6907
|
+
outputTokens: typeof usage.output_tokens === "number" ? usage.output_tokens : 0,
|
|
6908
|
+
cacheReadTokens: typeof usage.cached_input_tokens === "number" ? usage.cached_input_tokens : 0,
|
|
6909
|
+
cacheCreationTokens: 0,
|
|
6910
|
+
costUsd: 0
|
|
6911
|
+
};
|
|
6912
|
+
void presenter.onUsageUpdate?.({
|
|
6913
|
+
model: state.usage.model,
|
|
6914
|
+
inputTokens: state.usage.inputTokens,
|
|
6915
|
+
outputTokens: state.usage.outputTokens,
|
|
6916
|
+
cacheReadTokens: state.usage.cacheReadTokens,
|
|
6917
|
+
cacheCreationTokens: state.usage.cacheCreationTokens
|
|
6918
|
+
});
|
|
6667
6919
|
break;
|
|
6668
6920
|
}
|
|
6669
|
-
case "
|
|
6670
|
-
|
|
6671
|
-
|
|
6672
|
-
|
|
6673
|
-
|
|
6674
|
-
|
|
6675
|
-
durationMs: 0,
|
|
6676
|
-
inputTokens: tokens.input ?? 0,
|
|
6677
|
-
outputTokens: tokens.output ?? 0,
|
|
6678
|
-
cacheReadTokens: tokens.cache_read ?? 0,
|
|
6679
|
-
cacheCreationTokens: tokens.cache_creation ?? 0,
|
|
6680
|
-
costUsd: 0
|
|
6681
|
-
};
|
|
6682
|
-
void presenter.onUsageUpdate?.({
|
|
6683
|
-
model: state.usage.model,
|
|
6684
|
-
inputTokens: state.usage.inputTokens,
|
|
6685
|
-
outputTokens: state.usage.outputTokens,
|
|
6686
|
-
cacheReadTokens: state.usage.cacheReadTokens,
|
|
6687
|
-
cacheCreationTokens: state.usage.cacheCreationTokens
|
|
6688
|
-
});
|
|
6921
|
+
case "error": {
|
|
6922
|
+
const message = extractCodexErrorMessage(parsed.message) || extractCodexErrorMessage(parsed.error) || typeof parsed.detail === "string" && parsed.detail.trim() || "";
|
|
6923
|
+
if (message) {
|
|
6924
|
+
const normalizedMessage = normalizeCodexCliErrorMessage(message);
|
|
6925
|
+
void presenter.onError(normalizedMessage);
|
|
6926
|
+
state.error = normalizedMessage;
|
|
6689
6927
|
}
|
|
6690
6928
|
break;
|
|
6691
6929
|
}
|
|
6692
|
-
case "
|
|
6693
|
-
|
|
6694
|
-
const
|
|
6695
|
-
if (
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
(typeof parsed.data?.message === "string" ? parsed.data.message.trim() : "") || "";
|
|
6700
|
-
const message = rawMessage || "OpenCode encountered an error. Check your provider and model settings.";
|
|
6701
|
-
if (!state.error) {
|
|
6702
|
-
void presenter.onError(message);
|
|
6703
|
-
state.error = message;
|
|
6704
|
-
}
|
|
6705
|
-
const pid = state.process?.pid;
|
|
6706
|
-
if (pid) {
|
|
6707
|
-
try {
|
|
6708
|
-
process.kill(-pid, "SIGTERM");
|
|
6709
|
-
} catch {
|
|
6710
|
-
try {
|
|
6711
|
-
state.process?.kill("SIGTERM");
|
|
6712
|
-
} catch {
|
|
6713
|
-
}
|
|
6714
|
-
}
|
|
6930
|
+
case "turn.failed": {
|
|
6931
|
+
const error = typeof parsed.error === "object" && parsed.error !== null ? parsed.error : {};
|
|
6932
|
+
const message = extractCodexErrorMessage(error.message) || extractCodexErrorMessage(error.detail) || typeof parsed.message === "string" && parsed.message.trim() || "";
|
|
6933
|
+
if (message) {
|
|
6934
|
+
const normalizedMessage = normalizeCodexCliErrorMessage(message);
|
|
6935
|
+
void presenter.onError(normalizedMessage);
|
|
6936
|
+
state.error = normalizedMessage;
|
|
6715
6937
|
}
|
|
6716
6938
|
break;
|
|
6717
6939
|
}
|
|
@@ -6719,51 +6941,6 @@ function parseOpencodeStructuredLine(line, context, state) {
|
|
|
6719
6941
|
break;
|
|
6720
6942
|
}
|
|
6721
6943
|
}
|
|
6722
|
-
function createOpencodeCliBackend(command = "opencode", defaultArgs = []) {
|
|
6723
|
-
return {
|
|
6724
|
-
kind: "opencode_cli",
|
|
6725
|
-
supportTier: "structured",
|
|
6726
|
-
async run(context) {
|
|
6727
|
-
const { paths: imagePaths, cleanup } = writeImagesToTempFiles(context.config.images);
|
|
6728
|
-
try {
|
|
6729
|
-
return await createGenericCliBackend({
|
|
6730
|
-
kind: "opencode_cli",
|
|
6731
|
-
supportTier: "structured",
|
|
6732
|
-
command,
|
|
6733
|
-
args: [],
|
|
6734
|
-
buildArgs: (ctx, prompt) => {
|
|
6735
|
-
const args = ["run", "--format", "json"];
|
|
6736
|
-
const resumeId = ctx.config.runtimeSessionId?.trim() || ctx.config.providerSessionId?.trim();
|
|
6737
|
-
if (resumeId) args.push("--session", resumeId);
|
|
6738
|
-
const model = ctx.config.selectedModel?.trim();
|
|
6739
|
-
if (model && model.includes("/")) args.push("--model", model);
|
|
6740
|
-
const fileArgs = imagePaths.flatMap((p) => ["--file", p]);
|
|
6741
|
-
if (fileArgs.length > 0) {
|
|
6742
|
-
return [...args, ...fileArgs, ...defaultArgs, "--", prompt];
|
|
6743
|
-
}
|
|
6744
|
-
return [...args, ...defaultArgs, prompt];
|
|
6745
|
-
},
|
|
6746
|
-
augmentPrompt: (ctx) => {
|
|
6747
|
-
const base = buildPromptWithSystem(ctx.config, ctx.promptText);
|
|
6748
|
-
return ctx.config.mode === "plan" ? buildPlanModePrefix(base) : base;
|
|
6749
|
-
},
|
|
6750
|
-
parseStructuredLine: parseOpencodeStructuredLine
|
|
6751
|
-
}).run(context);
|
|
6752
|
-
} finally {
|
|
6753
|
-
cleanup();
|
|
6754
|
-
}
|
|
6755
|
-
}
|
|
6756
|
-
};
|
|
6757
|
-
}
|
|
6758
|
-
function createGenericCliPassthroughBackend(command, defaultArgs = []) {
|
|
6759
|
-
return createGenericCliBackend({
|
|
6760
|
-
kind: "generic_cli",
|
|
6761
|
-
supportTier: "text",
|
|
6762
|
-
command,
|
|
6763
|
-
args: defaultArgs,
|
|
6764
|
-
promptViaStdin: true
|
|
6765
|
-
});
|
|
6766
|
-
}
|
|
6767
6944
|
function buildCodexEffortArgs(selectedEffortLevel) {
|
|
6768
6945
|
const level = selectedEffortLevel?.trim();
|
|
6769
6946
|
if (!level) return [];
|
|
@@ -6774,7 +6951,7 @@ function createCodexRuntimeBackend(command = "codex", defaultArgs = []) {
|
|
|
6774
6951
|
kind: "codex_app_server",
|
|
6775
6952
|
supportTier: "structured",
|
|
6776
6953
|
async run(context) {
|
|
6777
|
-
const { paths: imagePaths, cleanup } = writeImagesToTempFiles(context.config.images);
|
|
6954
|
+
const { paths: imagePaths, cleanup } = writeImagesToTempFiles(context.config.images, context.cwd);
|
|
6778
6955
|
try {
|
|
6779
6956
|
return await createGenericCliBackend({
|
|
6780
6957
|
kind: "codex_app_server",
|
|
@@ -6785,7 +6962,7 @@ function createCodexRuntimeBackend(command = "codex", defaultArgs = []) {
|
|
|
6785
6962
|
const resumeId = ctx.config.runtimeSessionId?.trim() || ctx.config.providerSessionId?.trim();
|
|
6786
6963
|
const modelArgs = ctx.config.selectedModel?.trim() ? ["--model", ctx.config.selectedModel.trim()] : [];
|
|
6787
6964
|
const effortArgs = buildCodexEffortArgs(ctx.config.selectedEffortLevel);
|
|
6788
|
-
const permissionArgs =
|
|
6965
|
+
const permissionArgs = ["--dangerously-bypass-approvals-and-sandbox"];
|
|
6789
6966
|
const imageArgs = imagePaths.flatMap((p) => ["--image", p]);
|
|
6790
6967
|
const baseArgs = resumeId ? [...permissionArgs, "exec", "resume", "--json", "--skip-git-repo-check", ...modelArgs, ...effortArgs, ...imageArgs, resumeId, "-"] : [...permissionArgs, "exec", "--json", "--skip-git-repo-check", ...modelArgs, ...effortArgs, ...imageArgs, "-"];
|
|
6791
6968
|
return [...baseArgs, ...defaultArgs];
|
|
@@ -6881,51 +7058,67 @@ function parseCopilotStructuredLine(line, context, state) {
|
|
|
6881
7058
|
}
|
|
6882
7059
|
}
|
|
6883
7060
|
function createCopilotCliBackend(command = "copilot", defaultArgs = []) {
|
|
6884
|
-
return
|
|
7061
|
+
return {
|
|
6885
7062
|
kind: "copilot_cli",
|
|
6886
7063
|
supportTier: "structured",
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
|
|
7064
|
+
async run(context) {
|
|
7065
|
+
const { files: imageFiles, cleanup } = writeImagesToTempFiles(context.config.images, context.cwd);
|
|
7066
|
+
try {
|
|
7067
|
+
return await createGenericCliBackend({
|
|
7068
|
+
kind: "copilot_cli",
|
|
7069
|
+
supportTier: "structured",
|
|
7070
|
+
command,
|
|
7071
|
+
args: [],
|
|
7072
|
+
buildArgs: (ctx, prompt) => {
|
|
7073
|
+
const args = [
|
|
7074
|
+
"--autopilot",
|
|
7075
|
+
"--yolo",
|
|
7076
|
+
"--max-autopilot-continues",
|
|
7077
|
+
"20",
|
|
7078
|
+
"-s",
|
|
7079
|
+
"--stream",
|
|
7080
|
+
"on",
|
|
7081
|
+
"--output-format",
|
|
7082
|
+
"json",
|
|
7083
|
+
"-p",
|
|
7084
|
+
prompt
|
|
7085
|
+
];
|
|
7086
|
+
if (ctx.config.selectedModel?.trim()) {
|
|
7087
|
+
args.push("--model", ctx.config.selectedModel.trim());
|
|
7088
|
+
}
|
|
7089
|
+
return [...args, ...defaultArgs];
|
|
7090
|
+
},
|
|
7091
|
+
augmentPrompt: (ctx) => buildPromptWithImagePathReferences(ctx, imageFiles),
|
|
7092
|
+
parseStructuredLine: parseCopilotStructuredLine
|
|
7093
|
+
}).run(context);
|
|
7094
|
+
} finally {
|
|
7095
|
+
cleanup();
|
|
6905
7096
|
}
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
parseStructuredLine: parseCopilotStructuredLine
|
|
6909
|
-
});
|
|
7097
|
+
}
|
|
7098
|
+
};
|
|
6910
7099
|
}
|
|
6911
7100
|
var DEFAULT_MODEL = "claude-sonnet-4-6";
|
|
6912
7101
|
var DROID_DEFAULT_MODEL = "claude-opus-4-7";
|
|
6913
7102
|
function defaultSelectedModelForBackend(backendKind) {
|
|
6914
7103
|
switch (backendKind) {
|
|
7104
|
+
case "claude_cli":
|
|
7105
|
+
return void 0;
|
|
6915
7106
|
case "droid_cli":
|
|
6916
7107
|
return DROID_DEFAULT_MODEL;
|
|
6917
7108
|
case "copilot_cli":
|
|
6918
7109
|
return "claude-sonnet-4.5";
|
|
6919
7110
|
case "kimi_cli":
|
|
6920
7111
|
return void 0;
|
|
7112
|
+
case "supatest_cli":
|
|
7113
|
+
return "medium";
|
|
6921
7114
|
default:
|
|
6922
7115
|
return DEFAULT_MODEL;
|
|
6923
7116
|
}
|
|
6924
7117
|
}
|
|
6925
7118
|
function canonicalizePath(inputPath) {
|
|
6926
7119
|
try {
|
|
6927
|
-
const resolved = (0,
|
|
6928
|
-
return (0,
|
|
7120
|
+
const resolved = (0, import_path3.resolve)(inputPath);
|
|
7121
|
+
return (0, import_fs4.existsSync)(resolved) ? (0, import_fs4.realpathSync)(resolved) : resolved;
|
|
6929
7122
|
} catch {
|
|
6930
7123
|
return null;
|
|
6931
7124
|
}
|
|
@@ -6938,6 +7131,34 @@ function buildPromptWithFiles(task, files) {
|
|
|
6938
7131
|
The user has attached the following files. You can read them using your available tools:
|
|
6939
7132
|
${fileList}`;
|
|
6940
7133
|
}
|
|
7134
|
+
function buildTaskLifecyclePrompt(config) {
|
|
7135
|
+
if (config.mode === "plan" || config.mode === "ask" || config.mode === "review") {
|
|
7136
|
+
return void 0;
|
|
7137
|
+
}
|
|
7138
|
+
const taskId = config.taskId ?? config.taskMeta?.id;
|
|
7139
|
+
if (!taskId) return void 0;
|
|
7140
|
+
const statusLines = config.taskMeta?.statusOptions?.length ? [
|
|
7141
|
+
"",
|
|
7142
|
+
"Available task statuses for this task, in board order:",
|
|
7143
|
+
...config.taskMeta.statusOptions.map(
|
|
7144
|
+
(status) => `- ${status.id}: ${status.label} (${status.group})`
|
|
7145
|
+
)
|
|
7146
|
+
] : [];
|
|
7147
|
+
return [
|
|
7148
|
+
"## Task Lifecycle",
|
|
7149
|
+
`This session is attached to taskId "${taskId}". Keep the task status current by calling the Aiden MCP update_task tool as you move through work phases.`,
|
|
7150
|
+
...statusLines,
|
|
7151
|
+
"",
|
|
7152
|
+
"Use exact status IDs from the available statuses when provided. Do not invent status IDs.",
|
|
7153
|
+
"Lifecycle mapping:",
|
|
7154
|
+
"- Research / discovery / code reading / planning: use the best matching status such as research, discovery, planning, or the first active status.",
|
|
7155
|
+
"- Implementation / coding / file edits: use the best matching status such as implementing, in_progress, working, or running.",
|
|
7156
|
+
"- Local validation / tests / QA: use the best matching status such as localtesting, local_testing, testing, qa, or validation.",
|
|
7157
|
+
"- Ready for human review: use the best matching review status such as in_review or review.",
|
|
7158
|
+
"- Done: use only when the requested work is fully complete and validated; otherwise leave it in the review/testing phase.",
|
|
7159
|
+
"If no suitable status exists for a phase, leave the current status unchanged rather than forcing an unrelated one."
|
|
7160
|
+
].join("\n");
|
|
7161
|
+
}
|
|
6941
7162
|
var BaseMachineAgent = class _BaseMachineAgent {
|
|
6942
7163
|
static MAX_RETRIES = 2;
|
|
6943
7164
|
static RETRY_BASE_DELAY_MS = 3e3;
|
|
@@ -7018,6 +7239,8 @@ var BaseMachineAgent = class _BaseMachineAgent {
|
|
|
7018
7239
|
case "gemini_cli":
|
|
7019
7240
|
case "opencode_cli":
|
|
7020
7241
|
case "copilot_cli":
|
|
7242
|
+
case "supatest_cli":
|
|
7243
|
+
case "kimi_cli":
|
|
7021
7244
|
return "structured";
|
|
7022
7245
|
case "generic_cli":
|
|
7023
7246
|
default:
|
|
@@ -7042,6 +7265,8 @@ var BaseMachineAgent = class _BaseMachineAgent {
|
|
|
7042
7265
|
Prefer relative paths and keep your work scoped to this project.`
|
|
7043
7266
|
);
|
|
7044
7267
|
}
|
|
7268
|
+
const lifecyclePrompt = buildTaskLifecyclePrompt(config);
|
|
7269
|
+
if (lifecyclePrompt) systemPromptParts.push(lifecyclePrompt);
|
|
7045
7270
|
const extra = this.buildExtraSystemPromptParts(config);
|
|
7046
7271
|
systemPromptParts.push(...extra);
|
|
7047
7272
|
return {
|
|
@@ -7056,9 +7281,9 @@ Prefer relative paths and keep your work scoped to this project.`
|
|
|
7056
7281
|
}
|
|
7057
7282
|
async runCliBackend(config, safeProjectPath) {
|
|
7058
7283
|
const backendKind = this.resolveBackendKind(config);
|
|
7059
|
-
const defaultCwd = (0,
|
|
7060
|
-
if (!(0,
|
|
7061
|
-
(0,
|
|
7284
|
+
const defaultCwd = (0, import_path3.join)((0, import_os3.homedir)(), "Documents", "aiden");
|
|
7285
|
+
if (!(0, import_fs4.existsSync)(defaultCwd)) {
|
|
7286
|
+
(0, import_fs4.mkdirSync)(defaultCwd, { recursive: true });
|
|
7062
7287
|
}
|
|
7063
7288
|
let runtimeConfig = this.buildRuntimeConfig(config, safeProjectPath);
|
|
7064
7289
|
const cwd = runtimeConfig.worktreePath || safeProjectPath || runtimeConfig.cwd || defaultCwd;
|
|
@@ -7066,7 +7291,7 @@ Prefer relative paths and keep your work scoped to this project.`
|
|
|
7066
7291
|
const promptText = this.buildPromptText(runtimeConfig);
|
|
7067
7292
|
const runtimeCommand = runtimeConfig.runtimeCommand || this.runtime.runtimeCommand;
|
|
7068
7293
|
const runtimeArgs = runtimeConfig.runtimeArgs || this.runtime.runtimeArgs || [];
|
|
7069
|
-
const backend = backendKind === "claude_cli" ? createClaudeCliBackend(runtimeCommand || "claude", runtimeArgs) : backendKind === "codex_app_server" ? createCodexRuntimeBackend(runtimeCommand || "codex", runtimeArgs) : backendKind === "copilot_cli" ? createCopilotCliBackend(runtimeCommand || "copilot", runtimeArgs) : backendKind === "cursor_agent_cli" ? createCursorAgentCliBackend(runtimeCommand || "cursor-agent", runtimeArgs) : backendKind === "gemini_cli" ? createGeminiCliBackend(runtimeCommand || "gemini", runtimeArgs) : backendKind === "droid_cli" ? createDroidCliBackend(runtimeCommand || "droid", runtimeArgs) : backendKind === "opencode_cli" ? createOpencodeCliBackend(runtimeCommand || "opencode", runtimeArgs) : backendKind === "kimi_cli" ? createKimiCliBackend(runtimeCommand || "kimi", runtimeArgs) : createGenericCliPassthroughBackend(runtimeCommand || "generic-cli", runtimeArgs);
|
|
7294
|
+
const backend = backendKind === "claude_cli" ? createClaudeCliBackend(runtimeCommand || "claude", runtimeArgs) : backendKind === "codex_app_server" ? createCodexRuntimeBackend(runtimeCommand || "codex", runtimeArgs) : backendKind === "copilot_cli" ? createCopilotCliBackend(runtimeCommand || "copilot", runtimeArgs) : backendKind === "cursor_agent_cli" ? createCursorAgentCliBackend(runtimeCommand || "cursor-agent", runtimeArgs) : backendKind === "gemini_cli" ? createGeminiCliBackend(runtimeCommand || "gemini", runtimeArgs) : backendKind === "droid_cli" ? createDroidCliBackend(runtimeCommand || "droid", runtimeArgs) : backendKind === "opencode_cli" ? createOpencodeCliBackend(runtimeCommand || "opencode", runtimeArgs) : backendKind === "kimi_cli" ? createKimiCliBackend(runtimeCommand || "kimi", runtimeArgs) : backendKind === "supatest_cli" ? createSupatestCliBackend(runtimeCommand || "supatest", runtimeArgs) : createGenericCliPassthroughBackend(runtimeCommand || "generic-cli", runtimeArgs);
|
|
7070
7295
|
let lastResult = null;
|
|
7071
7296
|
for (let attempt = 0; attempt <= _BaseMachineAgent.MAX_RETRIES; attempt++) {
|
|
7072
7297
|
if (this.abortController?.signal.aborted) break;
|
|
@@ -7162,7 +7387,7 @@ Prefer relative paths and keep your work scoped to this project.`
|
|
|
7162
7387
|
const canonicalized = canonicalizePath(initialConfig.teamPath);
|
|
7163
7388
|
if (canonicalized) safeProjectPath = canonicalized;
|
|
7164
7389
|
}
|
|
7165
|
-
if (safeProjectPath && !(0,
|
|
7390
|
+
if (safeProjectPath && !(0, import_fs4.existsSync)(safeProjectPath)) {
|
|
7166
7391
|
const message = `Local path no longer exists: "${safeProjectPath}". Please update the team's local path in team settings before running the agent.`;
|
|
7167
7392
|
this.presenter.onError(message, true);
|
|
7168
7393
|
const result = {
|
|
@@ -7207,25 +7432,78 @@ Prefer relative paths and keep your work scoped to this project.`
|
|
|
7207
7432
|
var import_node_os2 = require("os");
|
|
7208
7433
|
|
|
7209
7434
|
// src/cli-executable.ts
|
|
7210
|
-
var import_node_fs = require("fs");
|
|
7211
7435
|
var import_node_child_process = require("child_process");
|
|
7436
|
+
var import_node_fs = require("fs");
|
|
7437
|
+
var import_node_child_process2 = require("child_process");
|
|
7212
7438
|
var import_node_os = require("os");
|
|
7213
7439
|
var import_node_path = require("path");
|
|
7214
7440
|
var PROVIDER_CLI_COMMANDS = {
|
|
7215
7441
|
claude_cli: { provider: "claude_cli", command: "claude", envVar: "AIDEN_CLAUDE_PATH" },
|
|
7216
|
-
|
|
7217
|
-
|
|
7442
|
+
codex_app_server: { provider: "codex_app_server", command: "codex", envVar: "AIDEN_CODEX_PATH" },
|
|
7443
|
+
copilot_cli: { provider: "copilot_cli", command: "copilot", envVar: "AIDEN_COPILOT_PATH" },
|
|
7444
|
+
cursor_agent_cli: { provider: "cursor_agent_cli", command: "cursor-agent", envVar: "AIDEN_CURSOR_AGENT_PATH" },
|
|
7445
|
+
gemini_cli: { provider: "gemini_cli", command: "gemini", envVar: "AIDEN_GEMINI_PATH" },
|
|
7446
|
+
kimi_cli: { provider: "kimi_cli", command: "kimi", envVar: "AIDEN_KIMI_PATH" },
|
|
7447
|
+
opencode_cli: { provider: "opencode_cli", command: "opencode", envVar: "AIDEN_OPENCODE_PATH" },
|
|
7448
|
+
droid_cli: { provider: "droid_cli", command: "droid", envVar: "AIDEN_DROID_PATH" },
|
|
7449
|
+
supatest_cli: { provider: "supatest_cli", command: "supatest", envVar: "AIDEN_SUPATEST_PATH" }
|
|
7450
|
+
};
|
|
7451
|
+
var DISCOVERABLE_PROVIDER_KINDS = Object.keys(PROVIDER_CLI_COMMANDS);
|
|
7452
|
+
var PROVIDER_ALIASES = {
|
|
7453
|
+
codex: "codex_app_server",
|
|
7454
|
+
gemini: "gemini_cli"
|
|
7218
7455
|
};
|
|
7219
7456
|
var BACKEND_CLI_COMMANDS = {
|
|
7220
7457
|
claude_cli: "claude",
|
|
7221
7458
|
codex: "codex",
|
|
7222
7459
|
codex_app_server: "codex",
|
|
7460
|
+
copilot_cli: "copilot",
|
|
7461
|
+
cursor_agent_cli: "cursor-agent",
|
|
7223
7462
|
gemini_cli: "gemini",
|
|
7224
|
-
gemini: "gemini"
|
|
7463
|
+
gemini: "gemini",
|
|
7464
|
+
kimi_cli: "kimi",
|
|
7465
|
+
opencode_cli: "opencode",
|
|
7466
|
+
droid_cli: "droid",
|
|
7467
|
+
supatest_cli: "supatest"
|
|
7225
7468
|
};
|
|
7226
7469
|
var resolvedCliCache = /* @__PURE__ */ new Map();
|
|
7470
|
+
var cachedLoginShellEnv = null;
|
|
7471
|
+
function parseEnvOutput(output) {
|
|
7472
|
+
const loginEnv = {};
|
|
7473
|
+
for (const line of output.split("\n")) {
|
|
7474
|
+
if (!line.trim()) continue;
|
|
7475
|
+
const eqIdx = line.indexOf("=");
|
|
7476
|
+
if (eqIdx <= 0) continue;
|
|
7477
|
+
const key = line.slice(0, eqIdx);
|
|
7478
|
+
if (/\s/.test(key)) continue;
|
|
7479
|
+
loginEnv[key] = line.slice(eqIdx + 1);
|
|
7480
|
+
}
|
|
7481
|
+
if (!loginEnv.HOME) loginEnv.HOME = process.env.HOME ?? (0, import_node_os.homedir)();
|
|
7482
|
+
if (!loginEnv.SHELL) loginEnv.SHELL = process.env.SHELL ?? "/bin/zsh";
|
|
7483
|
+
return loginEnv;
|
|
7484
|
+
}
|
|
7485
|
+
function loadLoginShellEnvironment() {
|
|
7486
|
+
if (cachedLoginShellEnv) return cachedLoginShellEnv;
|
|
7487
|
+
if ((0, import_node_os.platform)() === "win32") {
|
|
7488
|
+
cachedLoginShellEnv = process.env;
|
|
7489
|
+
return cachedLoginShellEnv;
|
|
7490
|
+
}
|
|
7491
|
+
try {
|
|
7492
|
+
const shell = (process.env.SHELL || "/bin/bash").replace(/'/g, "'\\''");
|
|
7493
|
+
const envOutput = (0, import_node_child_process.execSync)(`'${shell}' -ilc 'env'`, {
|
|
7494
|
+
encoding: "utf8",
|
|
7495
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
7496
|
+
timeout: 5e3
|
|
7497
|
+
});
|
|
7498
|
+
cachedLoginShellEnv = parseEnvOutput(envOutput);
|
|
7499
|
+
} catch {
|
|
7500
|
+
cachedLoginShellEnv = process.env;
|
|
7501
|
+
}
|
|
7502
|
+
return cachedLoginShellEnv;
|
|
7503
|
+
}
|
|
7227
7504
|
function getDaemonCliEnvironment() {
|
|
7228
|
-
|
|
7505
|
+
const base = (0, import_node_os.platform)() === "win32" ? process.env : loadLoginShellEnvironment();
|
|
7506
|
+
return augmentCliPath(base);
|
|
7229
7507
|
}
|
|
7230
7508
|
function cacheResolvedCli(command, resolvedPath) {
|
|
7231
7509
|
resolvedCliCache.set(command, resolvedPath);
|
|
@@ -7237,7 +7515,7 @@ function resolveViaWhere(command, env) {
|
|
|
7237
7515
|
if ((0, import_node_os.platform)() !== "win32") return null;
|
|
7238
7516
|
const whereExe = (0, import_node_path.join)(env.SystemRoot ?? process.env.SystemRoot ?? "C:\\Windows", "System32", "where.exe");
|
|
7239
7517
|
if (!(0, import_node_fs.existsSync)(whereExe)) return null;
|
|
7240
|
-
const result = (0,
|
|
7518
|
+
const result = (0, import_node_child_process2.spawnSync)(whereExe, [command], {
|
|
7241
7519
|
encoding: "utf8",
|
|
7242
7520
|
env,
|
|
7243
7521
|
windowsHide: true,
|
|
@@ -7251,26 +7529,6 @@ function resolveViaWhere(command, env) {
|
|
|
7251
7529
|
}
|
|
7252
7530
|
return null;
|
|
7253
7531
|
}
|
|
7254
|
-
function runCliVersionProbe(executable, env, args = ["--version"]) {
|
|
7255
|
-
const isWin = (0, import_node_os.platform)() === "win32";
|
|
7256
|
-
if (isWin && /\.(cmd|bat)$/i.test(executable)) {
|
|
7257
|
-
const comSpec = env.ComSpec ?? process.env.ComSpec ?? "cmd.exe";
|
|
7258
|
-
return (0, import_node_child_process.spawnSync)(comSpec, ["/d", "/s", "/c", executable, ...args], {
|
|
7259
|
-
encoding: "utf8",
|
|
7260
|
-
timeout: 3e3,
|
|
7261
|
-
env,
|
|
7262
|
-
windowsHide: true
|
|
7263
|
-
});
|
|
7264
|
-
}
|
|
7265
|
-
const useShell = isWin && !/[\\/]/.test(executable);
|
|
7266
|
-
return (0, import_node_child_process.spawnSync)(executable, args, {
|
|
7267
|
-
encoding: "utf8",
|
|
7268
|
-
timeout: 3e3,
|
|
7269
|
-
env,
|
|
7270
|
-
shell: useShell,
|
|
7271
|
-
windowsHide: isWin ? true : void 0
|
|
7272
|
-
});
|
|
7273
|
-
}
|
|
7274
7532
|
function pathSeparator() {
|
|
7275
7533
|
return (0, import_node_os.platform)() === "win32" ? ";" : ":";
|
|
7276
7534
|
}
|
|
@@ -7371,8 +7629,18 @@ function resolveCliExecutable(command, env = getDaemonCliEnvironment()) {
|
|
|
7371
7629
|
}
|
|
7372
7630
|
return null;
|
|
7373
7631
|
}
|
|
7632
|
+
function normalizeDiscoverableProvider(provider) {
|
|
7633
|
+
const alias = PROVIDER_ALIASES[provider];
|
|
7634
|
+
if (alias) return alias;
|
|
7635
|
+
if (provider in PROVIDER_CLI_COMMANDS) {
|
|
7636
|
+
return provider;
|
|
7637
|
+
}
|
|
7638
|
+
return null;
|
|
7639
|
+
}
|
|
7374
7640
|
function resolveProviderCliCommand(provider, env = getDaemonCliEnvironment()) {
|
|
7375
|
-
const
|
|
7641
|
+
const normalized = normalizeDiscoverableProvider(provider);
|
|
7642
|
+
if (!normalized) return null;
|
|
7643
|
+
const spec = PROVIDER_CLI_COMMANDS[normalized];
|
|
7376
7644
|
const override = process.env[spec.envVar]?.trim();
|
|
7377
7645
|
if (override) {
|
|
7378
7646
|
const resolved2 = resolveCliExecutable(override, env) ?? (isRunnableFile(override) ? override : null);
|
|
@@ -7389,7 +7657,8 @@ function resolveBackendRuntimeCommand(backendKind, env = getDaemonCliEnvironment
|
|
|
7389
7657
|
if (!command) return void 0;
|
|
7390
7658
|
const cached = getCachedResolvedCli(command);
|
|
7391
7659
|
if (cached && isRunnableFile(cached)) return cached;
|
|
7392
|
-
const
|
|
7660
|
+
const spec = Object.values(PROVIDER_CLI_COMMANDS).find((entry) => entry.command === command);
|
|
7661
|
+
const envOverride = spec ? env[spec.envVar] : void 0;
|
|
7393
7662
|
const resolved = resolveCliExecutable(envOverride?.trim() || command, env);
|
|
7394
7663
|
if (resolved) cacheResolvedCli(command, resolved);
|
|
7395
7664
|
return resolved ?? void 0;
|
|
@@ -7399,10 +7668,12 @@ function resolveBackendRuntimeCommand(backendKind, env = getDaemonCliEnvironment
|
|
|
7399
7668
|
var CoreAgent = class _CoreAgent extends BaseMachineAgent {
|
|
7400
7669
|
childProcess = null;
|
|
7401
7670
|
_aborted = false;
|
|
7671
|
+
providerApiKey;
|
|
7402
7672
|
/** How long to wait after SIGTERM before escalating to SIGKILL. */
|
|
7403
7673
|
static SIGKILL_DELAY_MS = 3e3;
|
|
7404
7674
|
constructor(presenter, runtime) {
|
|
7405
7675
|
super(presenter, runtime);
|
|
7676
|
+
this.providerApiKey = runtime.providerApiKey;
|
|
7406
7677
|
}
|
|
7407
7678
|
abort() {
|
|
7408
7679
|
this._aborted = true;
|
|
@@ -7527,6 +7798,9 @@ var CoreAgent = class _CoreAgent extends BaseMachineAgent {
|
|
|
7527
7798
|
if (value2 !== void 0) env[key] = value2;
|
|
7528
7799
|
}
|
|
7529
7800
|
const augmented = getDaemonCliEnvironment();
|
|
7801
|
+
if (this.runtime.backendKind === "supatest_cli" && this.providerApiKey) {
|
|
7802
|
+
augmented.SUPATEST_API_KEY = this.providerApiKey;
|
|
7803
|
+
}
|
|
7530
7804
|
if ((0, import_node_os2.platform)() !== "win32") {
|
|
7531
7805
|
const home = augmented.HOME ?? "/home/user";
|
|
7532
7806
|
const extraPaths = [`${home}/.local/node/bin`, `${home}/.local/bin`];
|
|
@@ -11361,7 +11635,7 @@ var WSClient = class {
|
|
|
11361
11635
|
};
|
|
11362
11636
|
|
|
11363
11637
|
// src/version.ts
|
|
11364
|
-
var AGENT_VERSION = "0.1.
|
|
11638
|
+
var AGENT_VERSION = "0.1.15";
|
|
11365
11639
|
|
|
11366
11640
|
// src/sandbox.ts
|
|
11367
11641
|
async function runSandbox(config) {
|
|
@@ -11377,6 +11651,8 @@ async function runSandbox(config) {
|
|
|
11377
11651
|
let currentConversationId = config.sessionId;
|
|
11378
11652
|
let currentTeamId = config.teamId;
|
|
11379
11653
|
let currentUser = config.currentUser;
|
|
11654
|
+
let currentTerminalContextRefs;
|
|
11655
|
+
let currentTerminalSnapshots;
|
|
11380
11656
|
console.info("[sandbox] Connecting to WS server", { wsUrl: config.wsUrl, sessionId: config.sessionId });
|
|
11381
11657
|
const wsClient = new WSClient(
|
|
11382
11658
|
config.wsUrl,
|
|
@@ -11458,6 +11734,8 @@ async function runSandbox(config) {
|
|
|
11458
11734
|
teamId: currentTeamId,
|
|
11459
11735
|
currentUser,
|
|
11460
11736
|
workflowTools: currentWorkflowTools,
|
|
11737
|
+
terminalContextRefs: currentTerminalContextRefs,
|
|
11738
|
+
terminalSnapshots: currentTerminalSnapshots,
|
|
11461
11739
|
images: currentImages.length > 0 ? currentImages.map((img) => ({
|
|
11462
11740
|
id: img.id,
|
|
11463
11741
|
data: img.data,
|
|
@@ -11498,8 +11776,8 @@ async function runSandbox(config) {
|
|
|
11498
11776
|
if (nextPayload.agentPrompt !== void 0) {
|
|
11499
11777
|
activeAgentPrompt = nextPayload.agentPrompt;
|
|
11500
11778
|
}
|
|
11501
|
-
if (nextPayload.selectedModel) {
|
|
11502
|
-
activeModel = nextPayload.selectedModel;
|
|
11779
|
+
if (nextPayload.selectedModel !== void 0) {
|
|
11780
|
+
activeModel = nextPayload.selectedModel.trim() || void 0;
|
|
11503
11781
|
}
|
|
11504
11782
|
if (nextPayload.selectedContextWindow !== void 0) {
|
|
11505
11783
|
activeContextWindow = nextPayload.selectedContextWindow;
|
|
@@ -11531,6 +11809,12 @@ async function runSandbox(config) {
|
|
|
11531
11809
|
if (nextPayload.teamId !== void 0) {
|
|
11532
11810
|
currentTeamId = nextPayload.teamId;
|
|
11533
11811
|
}
|
|
11812
|
+
if (nextPayload.terminalContextRefs !== void 0) {
|
|
11813
|
+
currentTerminalContextRefs = nextPayload.terminalContextRefs;
|
|
11814
|
+
}
|
|
11815
|
+
if (nextPayload.terminalSnapshots !== void 0) {
|
|
11816
|
+
currentTerminalSnapshots = nextPayload.terminalSnapshots;
|
|
11817
|
+
}
|
|
11534
11818
|
}
|
|
11535
11819
|
presenter.setSuppressSessionLifecycle(false);
|
|
11536
11820
|
wsClient.close();
|
|
@@ -11546,6 +11830,26 @@ var PRODUCTION_API_URL = "https://api.aiden-platform.com";
|
|
|
11546
11830
|
var PRODUCTION_WS_URL = "wss://ws.aiden-platform.com";
|
|
11547
11831
|
var LOCAL_API_URL = "http://localhost:8400";
|
|
11548
11832
|
var LOCAL_WS_URL = "ws://localhost:8401";
|
|
11833
|
+
async function probeLocalDaemonEndpoint(port, token, timeoutMs = 750) {
|
|
11834
|
+
try {
|
|
11835
|
+
const headers = {};
|
|
11836
|
+
if (token) headers.authorization = `Bearer ${token}`;
|
|
11837
|
+
const response = await fetch(`http://127.0.0.1:${port}/status`, {
|
|
11838
|
+
headers,
|
|
11839
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
11840
|
+
});
|
|
11841
|
+
if (response.status === 401) return { state: "listening_unauthorized" };
|
|
11842
|
+
if (!response.ok) return { state: "absent" };
|
|
11843
|
+
const body = await response.json();
|
|
11844
|
+
if (!body.runtimeId) return { state: "absent" };
|
|
11845
|
+
return { state: "running", status: body };
|
|
11846
|
+
} catch {
|
|
11847
|
+
return { state: "absent" };
|
|
11848
|
+
}
|
|
11849
|
+
}
|
|
11850
|
+
function describeOccupiedLocalDaemon(port) {
|
|
11851
|
+
return `local daemon API port ${port} is already in use. If Aiden desktop is open, it manages the daemon automatically. Quit Aiden desktop or stop the existing daemon before starting another.`;
|
|
11852
|
+
}
|
|
11549
11853
|
function getConfigPath() {
|
|
11550
11854
|
return process.env.AIDEN_AGENT_CONFIG_PATH ?? (0, import_node_path2.join)((0, import_node_os3.homedir)(), ".aiden", "agent", "config.json");
|
|
11551
11855
|
}
|
|
@@ -11603,44 +11907,70 @@ function isLocalEndpoint(url2) {
|
|
|
11603
11907
|
function getLocalApiPort(args, stored) {
|
|
11604
11908
|
const raw = argValue(args, "--local-api-port") ?? process.env.AIDEN_AGENT_LOCAL_API_PORT;
|
|
11605
11909
|
if (raw) return Number.parseInt(raw, 10);
|
|
11606
|
-
|
|
11910
|
+
const storedPort = stored.localApiPort;
|
|
11911
|
+
if (storedPort && storedPort > 0) return storedPort;
|
|
11912
|
+
return 47831;
|
|
11607
11913
|
}
|
|
11608
11914
|
function sleep(ms) {
|
|
11609
11915
|
if (ms <= 0) return Promise.resolve();
|
|
11610
11916
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
11611
11917
|
}
|
|
11612
|
-
function commandVersion(command, args = ["--version"]) {
|
|
11613
|
-
const env = getDaemonCliEnvironment();
|
|
11614
|
-
const executable = command === "claude" ? resolveProviderCliCommand("claude_cli", env) : command === "codex" ? resolveProviderCliCommand("codex", env) : command === "gemini" ? resolveProviderCliCommand("gemini", env) : resolveCliExecutable(command, env);
|
|
11615
|
-
if (!executable) return void 0;
|
|
11616
|
-
const result = runCliVersionProbe(executable, env, args);
|
|
11617
|
-
if (result.error || result.status !== 0) return void 0;
|
|
11618
|
-
return (result.stdout || result.stderr).split("\n")[0]?.trim() || void 0;
|
|
11619
|
-
}
|
|
11620
11918
|
function normalizeBackendKind(backendKind) {
|
|
11621
11919
|
if (backendKind === "codex") return "codex_app_server";
|
|
11622
11920
|
return backendKind;
|
|
11623
11921
|
}
|
|
11624
11922
|
function discoverCapabilities() {
|
|
11625
11923
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
11626
|
-
const
|
|
11924
|
+
const env = getDaemonCliEnvironment();
|
|
11627
11925
|
return {
|
|
11628
|
-
agents:
|
|
11629
|
-
const executable = resolveProviderCliCommand(provider);
|
|
11630
|
-
const version = executable ? commandVersion(executable) : void 0;
|
|
11926
|
+
agents: DISCOVERABLE_PROVIDER_KINDS.map((provider) => {
|
|
11927
|
+
const executable = resolveProviderCliCommand(provider, env);
|
|
11631
11928
|
return {
|
|
11632
11929
|
provider,
|
|
11633
|
-
available: Boolean(
|
|
11634
|
-
...version ? { version } : {},
|
|
11930
|
+
available: Boolean(executable),
|
|
11635
11931
|
models: [],
|
|
11636
11932
|
lastCheckedAt: now
|
|
11637
11933
|
};
|
|
11638
11934
|
}),
|
|
11639
|
-
hasGit: Boolean(
|
|
11935
|
+
hasGit: Boolean(resolveCliExecutable("git", env)),
|
|
11640
11936
|
hasTerminal: true,
|
|
11641
11937
|
supportsFilesystem: true
|
|
11642
11938
|
};
|
|
11643
11939
|
}
|
|
11940
|
+
var USER_INTERRUPTED_RESULT = {
|
|
11941
|
+
success: false,
|
|
11942
|
+
summary: "Interrupted by user",
|
|
11943
|
+
filesModified: [],
|
|
11944
|
+
planFilesCreated: [],
|
|
11945
|
+
iterations: 0,
|
|
11946
|
+
error: "Interrupted by user"
|
|
11947
|
+
};
|
|
11948
|
+
function abortActiveAgent(entry) {
|
|
11949
|
+
entry.presenter.onComplete(USER_INTERRUPTED_RESULT);
|
|
11950
|
+
entry.agent.kill();
|
|
11951
|
+
}
|
|
11952
|
+
function collectRuntimeMetadata() {
|
|
11953
|
+
const cpuList = (0, import_node_os3.cpus)();
|
|
11954
|
+
const metadata = {
|
|
11955
|
+
platform: (0, import_node_os3.platform)(),
|
|
11956
|
+
arch: (0, import_node_os3.arch)(),
|
|
11957
|
+
osVersion: (0, import_node_os3.release)(),
|
|
11958
|
+
agentVersion: AGENT_VERSION,
|
|
11959
|
+
memoryBytes: (0, import_node_os3.totalmem)(),
|
|
11960
|
+
memoryFreeBytes: (0, import_node_os3.freemem)()
|
|
11961
|
+
};
|
|
11962
|
+
if (cpuList.length > 0) {
|
|
11963
|
+
metadata.cpuModel = cpuList[0]?.model;
|
|
11964
|
+
metadata.cpuCores = cpuList.length;
|
|
11965
|
+
}
|
|
11966
|
+
try {
|
|
11967
|
+
const disk = (0, import_node_fs2.statfsSync)((0, import_node_os3.homedir)());
|
|
11968
|
+
metadata.diskFreeBytes = disk.bavail * disk.bsize;
|
|
11969
|
+
metadata.diskTotalBytes = disk.blocks * disk.bsize;
|
|
11970
|
+
} catch {
|
|
11971
|
+
}
|
|
11972
|
+
return metadata;
|
|
11973
|
+
}
|
|
11644
11974
|
var RuntimePresenter = class {
|
|
11645
11975
|
constructor(socket, conversationId, runId) {
|
|
11646
11976
|
this.socket = socket;
|
|
@@ -11746,11 +12076,7 @@ async function setupDaemon(args) {
|
|
|
11746
12076
|
ownerType: scope,
|
|
11747
12077
|
visibility: scope,
|
|
11748
12078
|
capabilities: discoverCapabilities(),
|
|
11749
|
-
metadata:
|
|
11750
|
-
platform: (0, import_node_os3.platform)(),
|
|
11751
|
-
arch: (0, import_node_os3.arch)(),
|
|
11752
|
-
agentVersion: AGENT_VERSION
|
|
11753
|
-
}
|
|
12079
|
+
metadata: collectRuntimeMetadata()
|
|
11754
12080
|
})
|
|
11755
12081
|
});
|
|
11756
12082
|
if (!response.ok) {
|
|
@@ -11781,11 +12107,7 @@ async function loginWithDeviceCode(args) {
|
|
|
11781
12107
|
displayName,
|
|
11782
12108
|
hostname: (0, import_node_os3.hostname)(),
|
|
11783
12109
|
capabilities: discoverCapabilities(),
|
|
11784
|
-
metadata:
|
|
11785
|
-
platform: (0, import_node_os3.platform)(),
|
|
11786
|
-
arch: (0, import_node_os3.arch)(),
|
|
11787
|
-
agentVersion: AGENT_VERSION
|
|
11788
|
-
}
|
|
12110
|
+
metadata: collectRuntimeMetadata()
|
|
11789
12111
|
})
|
|
11790
12112
|
});
|
|
11791
12113
|
if (!start.ok) {
|
|
@@ -11833,8 +12155,9 @@ async function startDaemon(args) {
|
|
|
11833
12155
|
if (!runtimeId || !runtimeToken || !wsUrl) {
|
|
11834
12156
|
throw new Error("daemon requires runtimeId, runtime token, and wsUrl. Run setup first or pass --runtime-id/--token/--ws-url.");
|
|
11835
12157
|
}
|
|
11836
|
-
const capabilities = discoverCapabilities();
|
|
11837
12158
|
const activeAgents = /* @__PURE__ */ new Map();
|
|
12159
|
+
const currentCapabilities = () => discoverCapabilities();
|
|
12160
|
+
const currentMetadata = () => collectRuntimeMetadata();
|
|
11838
12161
|
const recentLogs = [];
|
|
11839
12162
|
const pushLog = (message) => {
|
|
11840
12163
|
recentLogs.push(`${(/* @__PURE__ */ new Date()).toISOString()} ${message}`);
|
|
@@ -11856,16 +12179,21 @@ async function startDaemon(args) {
|
|
|
11856
12179
|
extraHeaders: { "ngrok-skip-browser-warning": "1" }
|
|
11857
12180
|
});
|
|
11858
12181
|
const heartbeat = setInterval(() => {
|
|
11859
|
-
if (socket.connected) socket.emit("runtime.heartbeat", { capabilities });
|
|
12182
|
+
if (socket.connected) socket.emit("runtime.heartbeat", { capabilities: currentCapabilities(), metadata: currentMetadata() });
|
|
11860
12183
|
}, 15e3);
|
|
11861
12184
|
socket.on("connect", () => {
|
|
11862
12185
|
pushLog(`connected runtime=${runtimeId}`);
|
|
11863
12186
|
console.info("[aiden-agent] Daemon connected", { runtimeId, wsUrl, version: AGENT_VERSION });
|
|
11864
|
-
socket.emit("runtime.hello", { capabilities });
|
|
12187
|
+
socket.emit("runtime.hello", { capabilities: currentCapabilities(), metadata: currentMetadata() });
|
|
11865
12188
|
});
|
|
12189
|
+
let lastConnectErrorLogAt = 0;
|
|
11866
12190
|
socket.on("connect_error", (error) => {
|
|
11867
12191
|
pushLog(`connect_error ${error.message}`);
|
|
11868
|
-
|
|
12192
|
+
const now = Date.now();
|
|
12193
|
+
if (now - lastConnectErrorLogAt < 5e3) return;
|
|
12194
|
+
lastConnectErrorLogAt = now;
|
|
12195
|
+
const wsHint = isLocalEndpoint(wsUrl) ? ` Nothing is listening at ${wsUrl}. For local dev run setup with --profile local and start the stack (WS on ws://localhost:8401).` : ` Verify wsUrl in ${getConfigPath()} and that you can reach ${wsUrl}.`;
|
|
12196
|
+
console.warn("[aiden-agent] Daemon connection error:", `${error.message}.${wsHint}`);
|
|
11869
12197
|
});
|
|
11870
12198
|
socket.on("disconnect", (reason) => {
|
|
11871
12199
|
pushLog(`disconnected ${reason}`);
|
|
@@ -11893,10 +12221,11 @@ async function startDaemon(args) {
|
|
|
11893
12221
|
const presenter = new RuntimePresenter(socket, payload.conversationId, payload.runId);
|
|
11894
12222
|
const agent = new CoreAgent(presenter, {
|
|
11895
12223
|
backendKind,
|
|
11896
|
-
runtimeCommand
|
|
12224
|
+
runtimeCommand,
|
|
12225
|
+
providerApiKey: payload.providerApiKey
|
|
11897
12226
|
});
|
|
11898
|
-
activeAgents.set(payload.runId, agent);
|
|
11899
12227
|
const cwd = payload.projectPath ?? process.cwd();
|
|
12228
|
+
activeAgents.set(payload.runId, { agent, presenter });
|
|
11900
12229
|
void agent.run({
|
|
11901
12230
|
task: payload.content,
|
|
11902
12231
|
maxIterations: payload.maxIterations ?? 50,
|
|
@@ -11932,21 +12261,21 @@ async function startDaemon(args) {
|
|
|
11932
12261
|
});
|
|
11933
12262
|
socket.on("agent.abort", (payload) => {
|
|
11934
12263
|
if (!payload?.runId) return;
|
|
11935
|
-
const
|
|
11936
|
-
if (!
|
|
11937
|
-
|
|
12264
|
+
const entry = activeAgents.get(payload.runId);
|
|
12265
|
+
if (!entry) return;
|
|
12266
|
+
abortActiveAgent(entry);
|
|
11938
12267
|
activeAgents.delete(payload.runId);
|
|
11939
12268
|
pushLog(`aborted run=${payload.runId}`);
|
|
11940
12269
|
});
|
|
11941
12270
|
socket.on("agent.tool_response", (payload) => {
|
|
11942
12271
|
if (!payload?.toolId || typeof payload.response !== "string") return;
|
|
11943
|
-
const agents = payload.runId ? [activeAgents.get(payload.runId)] : [...activeAgents.values()];
|
|
12272
|
+
const agents = payload.runId ? [activeAgents.get(payload.runId)?.agent] : [...activeAgents.values()].map((entry) => entry.agent);
|
|
11944
12273
|
const delivered = agents.some((agent) => agent?.sendToolResponse(payload.toolId, payload.response) === true);
|
|
11945
12274
|
if (!delivered) pushLog(`tool_response missed tool=${payload.toolId}`);
|
|
11946
12275
|
});
|
|
11947
12276
|
socket.on("agent.append_message", (payload) => {
|
|
11948
12277
|
if (!payload?.runId || typeof payload.text !== "string") return;
|
|
11949
|
-
const agent = activeAgents.get(payload.runId);
|
|
12278
|
+
const agent = activeAgents.get(payload.runId)?.agent;
|
|
11950
12279
|
const delivered = agent?.sendUserMessage(payload.text) === true;
|
|
11951
12280
|
socket.emit(delivered ? "agent.accepted" : "agent.rejected", {
|
|
11952
12281
|
runId: payload.runId,
|
|
@@ -11980,21 +12309,47 @@ async function startDaemon(args) {
|
|
|
11980
12309
|
return;
|
|
11981
12310
|
}
|
|
11982
12311
|
if (req.method === "POST" && req.url === "/stop") {
|
|
11983
|
-
for (const
|
|
12312
|
+
for (const entry of activeAgents.values()) abortActiveAgent(entry);
|
|
11984
12313
|
activeAgents.clear();
|
|
11985
12314
|
res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ ok: true }));
|
|
11986
12315
|
return;
|
|
11987
12316
|
}
|
|
12317
|
+
if (req.method === "POST" && req.url === "/shutdown") {
|
|
12318
|
+
res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ ok: true }));
|
|
12319
|
+
setImmediate(queueFullShutdown);
|
|
12320
|
+
return;
|
|
12321
|
+
}
|
|
11988
12322
|
res.writeHead(404, { "content-type": "application/json" }).end(JSON.stringify({ error: "Not found" }));
|
|
11989
12323
|
});
|
|
12324
|
+
const occupiedProbe = await probeLocalDaemonEndpoint(localApiPort, localApiToken);
|
|
12325
|
+
if (occupiedProbe.state === "running") {
|
|
12326
|
+
clearInterval(heartbeat);
|
|
12327
|
+
socket.disconnect();
|
|
12328
|
+
throw new Error(
|
|
12329
|
+
`${describeOccupiedLocalDaemon(localApiPort)} (runtime ${occupiedProbe.status.runtimeId})`
|
|
12330
|
+
);
|
|
12331
|
+
}
|
|
12332
|
+
if (occupiedProbe.state === "listening_unauthorized") {
|
|
12333
|
+
clearInterval(heartbeat);
|
|
12334
|
+
socket.disconnect();
|
|
12335
|
+
throw new Error(
|
|
12336
|
+
`${describeOccupiedLocalDaemon(localApiPort)} The token in ${getConfigPath()} no longer matches the running daemon; quit Aiden desktop or run setup again.`
|
|
12337
|
+
);
|
|
12338
|
+
}
|
|
11990
12339
|
await new Promise((resolve2, reject) => {
|
|
11991
12340
|
const onError = (error) => {
|
|
11992
12341
|
clearInterval(heartbeat);
|
|
11993
12342
|
socket.disconnect();
|
|
11994
12343
|
if (error.code === "EADDRINUSE") {
|
|
11995
|
-
|
|
11996
|
-
|
|
11997
|
-
|
|
12344
|
+
void probeLocalDaemonEndpoint(localApiPort).then((probe) => {
|
|
12345
|
+
if (probe.state !== "absent") {
|
|
12346
|
+
reject(new Error(describeOccupiedLocalDaemon(localApiPort)));
|
|
12347
|
+
return;
|
|
12348
|
+
}
|
|
12349
|
+
reject(new Error(
|
|
12350
|
+
`local daemon API port ${localApiPort} is already in use by another process. Otherwise free the port or pass \`--local-api-port\` with a different value.`
|
|
12351
|
+
));
|
|
12352
|
+
});
|
|
11998
12353
|
return;
|
|
11999
12354
|
}
|
|
12000
12355
|
reject(error);
|
|
@@ -12014,8 +12369,8 @@ async function startDaemon(args) {
|
|
|
12014
12369
|
if (stopped) return;
|
|
12015
12370
|
stopped = true;
|
|
12016
12371
|
clearInterval(heartbeat);
|
|
12017
|
-
for (const
|
|
12018
|
-
|
|
12372
|
+
for (const entry of activeAgents.values()) {
|
|
12373
|
+
abortActiveAgent(entry);
|
|
12019
12374
|
}
|
|
12020
12375
|
activeAgents.clear();
|
|
12021
12376
|
socket.disconnect();
|
|
@@ -12023,13 +12378,74 @@ async function startDaemon(args) {
|
|
|
12023
12378
|
localServer.close(() => resolve2());
|
|
12024
12379
|
});
|
|
12025
12380
|
};
|
|
12026
|
-
|
|
12381
|
+
const controller = {
|
|
12382
|
+
runtimeId,
|
|
12383
|
+
wsUrl,
|
|
12384
|
+
localApiPort,
|
|
12385
|
+
localApiToken,
|
|
12386
|
+
stop
|
|
12387
|
+
};
|
|
12388
|
+
const queueFullShutdown = () => {
|
|
12389
|
+
void stop().then(() => controller.whenExternallyStopped?.());
|
|
12390
|
+
};
|
|
12391
|
+
return controller;
|
|
12392
|
+
}
|
|
12393
|
+
async function stopDaemon(args) {
|
|
12394
|
+
const stored = readConfig();
|
|
12395
|
+
const localApiPort = getLocalApiPort(args, stored);
|
|
12396
|
+
const localApiToken = stored.localApiToken;
|
|
12397
|
+
if (!localApiToken) {
|
|
12398
|
+
throw new Error("No local API token in config. Run setup or login first.");
|
|
12399
|
+
}
|
|
12400
|
+
const probe = await probeLocalDaemonEndpoint(localApiPort, localApiToken);
|
|
12401
|
+
if (probe.state === "listening_unauthorized") {
|
|
12402
|
+
throw new Error(
|
|
12403
|
+
`Daemon is listening on port ${localApiPort} but rejected this config token. Quit Aiden desktop or rerun setup/login to refresh config.`
|
|
12404
|
+
);
|
|
12405
|
+
}
|
|
12406
|
+
if (probe.state === "absent") {
|
|
12407
|
+
console.info("[aiden-agent] No daemon listening", { port: localApiPort });
|
|
12408
|
+
return;
|
|
12409
|
+
}
|
|
12410
|
+
const response = await fetch(`http://127.0.0.1:${localApiPort}/shutdown`, {
|
|
12411
|
+
method: "POST",
|
|
12412
|
+
headers: { authorization: `Bearer ${localApiToken}` },
|
|
12413
|
+
signal: AbortSignal.timeout(5e3)
|
|
12414
|
+
});
|
|
12415
|
+
if (!response.ok) {
|
|
12416
|
+
throw new Error(`Failed to stop daemon: HTTP ${response.status}`);
|
|
12417
|
+
}
|
|
12418
|
+
console.info("[aiden-agent] Daemon stopped", {
|
|
12419
|
+
port: localApiPort,
|
|
12420
|
+
runtimeId: probe.status.runtimeId
|
|
12421
|
+
});
|
|
12027
12422
|
}
|
|
12028
12423
|
async function runDaemon(args) {
|
|
12424
|
+
const stored = readConfig();
|
|
12425
|
+
const localApiPort = getLocalApiPort(args, stored);
|
|
12426
|
+
const localApiToken = stored.localApiToken;
|
|
12427
|
+
const existingProbe = await probeLocalDaemonEndpoint(localApiPort, localApiToken);
|
|
12428
|
+
if (existingProbe.state === "running") {
|
|
12429
|
+
console.info("[aiden-agent] Daemon already running", {
|
|
12430
|
+
port: localApiPort,
|
|
12431
|
+
runtimeId: existingProbe.status.runtimeId,
|
|
12432
|
+
connected: existingProbe.status.connected,
|
|
12433
|
+
hint: "If Aiden desktop is open, it manages the daemon automatically."
|
|
12434
|
+
});
|
|
12435
|
+
return;
|
|
12436
|
+
}
|
|
12437
|
+
if (existingProbe.state === "listening_unauthorized") {
|
|
12438
|
+
console.info("[aiden-agent] Daemon already running", {
|
|
12439
|
+
port: localApiPort,
|
|
12440
|
+
hint: "Aiden desktop is using a different local API token than config.json. Quit the desktop app or rerun setup if you need a fresh daemon."
|
|
12441
|
+
});
|
|
12442
|
+
return;
|
|
12443
|
+
}
|
|
12029
12444
|
const controller = await startDaemon(args);
|
|
12030
12445
|
const shutdown = () => {
|
|
12031
12446
|
void controller.stop().finally(() => process.exit(0));
|
|
12032
12447
|
};
|
|
12448
|
+
controller.whenExternallyStopped = shutdown;
|
|
12033
12449
|
process.once("SIGINT", shutdown);
|
|
12034
12450
|
process.once("SIGTERM", shutdown);
|
|
12035
12451
|
await new Promise(() => {
|
|
@@ -12076,13 +12492,16 @@ function logoutDaemon() {
|
|
|
12076
12492
|
removeConfig();
|
|
12077
12493
|
console.info("[aiden-agent] Runtime config removed", { configPath: getConfigPath() });
|
|
12078
12494
|
}
|
|
12079
|
-
function printStatus() {
|
|
12495
|
+
async function printStatus() {
|
|
12080
12496
|
const config = readConfig();
|
|
12081
12497
|
const configured = Boolean(config.runtimeId && config.runtimeToken && config.wsUrl);
|
|
12082
12498
|
const warnings = [];
|
|
12083
12499
|
if ((isLocalEndpoint(config.apiUrl) || isLocalEndpoint(config.wsUrl)) && config.endpointProfile !== "local") {
|
|
12084
12500
|
warnings.push("Configured endpoint points at localhost. Use --profile local only for local development.");
|
|
12085
12501
|
}
|
|
12502
|
+
const localApiPort = config.localApiPort && config.localApiPort > 0 ? config.localApiPort : getLocalApiPort([], config);
|
|
12503
|
+
const localDaemonProbe = config.localApiToken ? await probeLocalDaemonEndpoint(localApiPort, config.localApiToken) : { state: "absent" };
|
|
12504
|
+
const localDaemonRunning = localDaemonProbe.state === "running";
|
|
12086
12505
|
console.info(JSON.stringify({
|
|
12087
12506
|
mode: "runtime",
|
|
12088
12507
|
configured,
|
|
@@ -12091,9 +12510,10 @@ function printStatus() {
|
|
|
12091
12510
|
wsUrl: config.wsUrl ?? null,
|
|
12092
12511
|
endpointProfile: config.endpointProfile ?? null,
|
|
12093
12512
|
localApiPort: config.localApiPort ?? null,
|
|
12513
|
+
localDaemonRunning,
|
|
12094
12514
|
hasLocalApiToken: Boolean(config.localApiToken),
|
|
12095
12515
|
configPath: getConfigPath(),
|
|
12096
|
-
note: configured ? "Durable runtime config is present. Start it with: aiden-agent daemon" : "No durable runtime configured. Cloud sandbox sessions are launched by Aiden with session env vars and do not appear in this local config.",
|
|
12516
|
+
note: configured ? localDaemonRunning ? "Local daemon is running. Stop it with: aiden-agent stop" : "Durable runtime config is present. Start it with: aiden-agent daemon" : "No durable runtime configured. Cloud sandbox sessions are launched by Aiden with session env vars and do not appear in this local config.",
|
|
12097
12517
|
warnings
|
|
12098
12518
|
}, null, 2));
|
|
12099
12519
|
}
|
|
@@ -12112,7 +12532,7 @@ async function printDoctor(args = []) {
|
|
|
12112
12532
|
authorization: `Bearer ${config.runtimeToken}`,
|
|
12113
12533
|
"content-type": "application/json"
|
|
12114
12534
|
},
|
|
12115
|
-
body: JSON.stringify({ capabilities })
|
|
12535
|
+
body: JSON.stringify({ capabilities, metadata: collectRuntimeMetadata() })
|
|
12116
12536
|
});
|
|
12117
12537
|
if (!response.ok) {
|
|
12118
12538
|
syncError = `${response.status} ${await response.text()}`;
|
|
@@ -12145,6 +12565,7 @@ Commands:
|
|
|
12145
12565
|
setup Register this computer with a setup token from Aiden
|
|
12146
12566
|
login Register this computer through browser authorization
|
|
12147
12567
|
daemon Start the durable runtime daemon
|
|
12568
|
+
stop Stop the local daemon (or active runs only via the local API /stop)
|
|
12148
12569
|
status Show local runtime configuration without secrets
|
|
12149
12570
|
doctor Check local CLI capabilities
|
|
12150
12571
|
logout Remove local runtime configuration
|
|
@@ -12156,6 +12577,7 @@ Common flows:
|
|
|
12156
12577
|
aiden-agent setup --setup-token <token>
|
|
12157
12578
|
aiden-agent setup --setup-token <token> --scope user
|
|
12158
12579
|
aiden-agent daemon
|
|
12580
|
+
aiden-agent stop
|
|
12159
12581
|
|
|
12160
12582
|
Local development:
|
|
12161
12583
|
aiden-agent login --profile local
|
|
@@ -12242,8 +12664,12 @@ async function main() {
|
|
|
12242
12664
|
await runDaemon(commandArgs);
|
|
12243
12665
|
return;
|
|
12244
12666
|
}
|
|
12667
|
+
if (command === "stop") {
|
|
12668
|
+
await stopDaemon(commandArgs);
|
|
12669
|
+
return;
|
|
12670
|
+
}
|
|
12245
12671
|
if (command === "status") {
|
|
12246
|
-
printStatus();
|
|
12672
|
+
await printStatus();
|
|
12247
12673
|
return;
|
|
12248
12674
|
}
|
|
12249
12675
|
if (command === "doctor") {
|