u-foo 1.0.6 → 1.1.9

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.
Files changed (149) hide show
  1. package/README.md +44 -4
  2. package/SKILLS/ufoo/SKILL.md +17 -2
  3. package/SKILLS/uinit/SKILL.md +8 -3
  4. package/bin/ucode-core.js +15 -0
  5. package/bin/ucode.js +125 -0
  6. package/bin/ufoo-assistant-agent.js +5 -0
  7. package/bin/ufoo-engine.js +25 -0
  8. package/bin/ufoo.js +4 -0
  9. package/modules/AGENTS.template.md +14 -4
  10. package/modules/bus/README.md +8 -5
  11. package/modules/bus/SKILLS/ubus/SKILL.md +5 -4
  12. package/modules/context/SKILLS/uctx/SKILL.md +3 -1
  13. package/modules/online/SKILLS/ufoo-online/SKILL.md +144 -0
  14. package/package.json +12 -3
  15. package/scripts/import-pi-mono.js +124 -0
  16. package/scripts/postinstall.js +20 -49
  17. package/scripts/sync-claude-skills.sh +21 -0
  18. package/src/agent/cliRunner.js +524 -31
  19. package/src/agent/internalRunner.js +76 -9
  20. package/src/agent/launcher.js +97 -45
  21. package/src/agent/normalizeOutput.js +1 -1
  22. package/src/agent/notifier.js +144 -4
  23. package/src/agent/ptyRunner.js +480 -10
  24. package/src/agent/ptyWrapper.js +28 -3
  25. package/src/agent/readyDetector.js +16 -0
  26. package/src/agent/ucode.js +443 -0
  27. package/src/agent/ucodeBootstrap.js +113 -0
  28. package/src/agent/ucodeBuild.js +67 -0
  29. package/src/agent/ucodeDoctor.js +184 -0
  30. package/src/agent/ucodeRuntimeConfig.js +129 -0
  31. package/src/agent/ufooAgent.js +11 -2
  32. package/src/assistant/agent.js +260 -0
  33. package/src/assistant/bridge.js +172 -0
  34. package/src/assistant/engine.js +252 -0
  35. package/src/assistant/stdio.js +58 -0
  36. package/src/assistant/ufooEngineCli.js +306 -0
  37. package/src/bus/activate.js +27 -11
  38. package/src/bus/daemon.js +133 -5
  39. package/src/bus/index.js +137 -80
  40. package/src/bus/inject.js +47 -17
  41. package/src/bus/message.js +145 -17
  42. package/src/bus/nickname.js +3 -1
  43. package/src/bus/queue.js +6 -1
  44. package/src/bus/store.js +189 -0
  45. package/src/bus/subscriber.js +20 -4
  46. package/src/bus/utils.js +9 -3
  47. package/src/chat/agentBar.js +117 -0
  48. package/src/chat/agentDirectory.js +88 -0
  49. package/src/chat/agentSockets.js +225 -0
  50. package/src/chat/agentViewController.js +298 -0
  51. package/src/chat/chatLogController.js +115 -0
  52. package/src/chat/commandExecutor.js +700 -0
  53. package/src/chat/commands.js +132 -0
  54. package/src/chat/completionController.js +414 -0
  55. package/src/chat/cronScheduler.js +160 -0
  56. package/src/chat/daemonConnection.js +166 -0
  57. package/src/chat/daemonCoordinator.js +64 -0
  58. package/src/chat/daemonMessageRouter.js +257 -0
  59. package/src/chat/daemonReconnect.js +41 -0
  60. package/src/chat/daemonTransport.js +36 -0
  61. package/src/chat/daemonTransportDefaults.js +10 -0
  62. package/src/chat/dashboardKeyController.js +480 -0
  63. package/src/chat/dashboardView.js +154 -0
  64. package/src/chat/index.js +935 -2909
  65. package/src/chat/inputHistoryController.js +105 -0
  66. package/src/chat/inputListenerController.js +304 -0
  67. package/src/chat/inputMath.js +104 -0
  68. package/src/chat/inputSubmitHandler.js +171 -0
  69. package/src/chat/layout.js +165 -0
  70. package/src/chat/pasteController.js +81 -0
  71. package/src/chat/rawKeyMap.js +42 -0
  72. package/src/chat/settingsController.js +132 -0
  73. package/src/chat/statusLineController.js +177 -0
  74. package/src/chat/streamTracker.js +138 -0
  75. package/src/chat/text.js +70 -0
  76. package/src/chat/transport.js +61 -0
  77. package/src/cli/busCoreCommands.js +59 -0
  78. package/src/cli/ctxCoreCommands.js +199 -0
  79. package/src/cli/onlineCoreCommands.js +379 -0
  80. package/src/cli.js +741 -238
  81. package/src/code/README.md +29 -0
  82. package/src/code/UCODE_PROMPT.md +32 -0
  83. package/src/code/agent.js +1651 -0
  84. package/src/code/cli.js +158 -0
  85. package/src/code/config +0 -0
  86. package/src/code/dispatch.js +42 -0
  87. package/src/code/index.js +70 -0
  88. package/src/code/nativeRunner.js +1213 -0
  89. package/src/code/runtime.js +154 -0
  90. package/src/code/sessionStore.js +162 -0
  91. package/src/code/taskDecomposer.js +269 -0
  92. package/src/code/tools/bash.js +53 -0
  93. package/src/code/tools/common.js +42 -0
  94. package/src/code/tools/edit.js +70 -0
  95. package/src/code/tools/read.js +44 -0
  96. package/src/code/tools/write.js +35 -0
  97. package/src/code/tui.js +1580 -0
  98. package/src/config.js +47 -1
  99. package/src/context/decisions.js +12 -2
  100. package/src/context/index.js +18 -1
  101. package/src/context/sync.js +127 -0
  102. package/src/daemon/agentProcessManager.js +74 -0
  103. package/src/daemon/cronOps.js +241 -0
  104. package/src/daemon/index.js +661 -488
  105. package/src/daemon/ipcServer.js +99 -0
  106. package/src/daemon/ops.js +417 -179
  107. package/src/daemon/promptLoop.js +319 -0
  108. package/src/daemon/promptRequest.js +101 -0
  109. package/src/daemon/providerSessions.js +32 -17
  110. package/src/daemon/reporting.js +90 -0
  111. package/src/daemon/run.js +2 -5
  112. package/src/daemon/status.js +24 -1
  113. package/src/init/index.js +68 -14
  114. package/src/online/bridge.js +663 -0
  115. package/src/online/client.js +245 -0
  116. package/src/online/runner.js +253 -0
  117. package/src/online/server.js +992 -0
  118. package/src/online/tokens.js +103 -0
  119. package/src/report/store.js +331 -0
  120. package/src/shared/eventContract.js +35 -0
  121. package/src/shared/ptySocketContract.js +21 -0
  122. package/src/status/index.js +50 -17
  123. package/src/terminal/adapterContract.js +87 -0
  124. package/src/terminal/adapterRouter.js +84 -0
  125. package/src/terminal/adapters/externalAdapter.js +14 -0
  126. package/src/terminal/adapters/internalAdapter.js +13 -0
  127. package/src/terminal/adapters/internalPtyAdapter.js +42 -0
  128. package/src/terminal/adapters/internalQueueAdapter.js +37 -0
  129. package/src/terminal/adapters/terminalAdapter.js +31 -0
  130. package/src/terminal/adapters/tmuxAdapter.js +30 -0
  131. package/src/ufoo/agentsStore.js +69 -3
  132. package/src/utils/banner.js +5 -2
  133. package/scripts/.archived/bash-to-js-migration/README.md +0 -46
  134. package/scripts/.archived/bash-to-js-migration/banner.sh +0 -89
  135. package/scripts/.archived/bash-to-js-migration/bus-alert.sh +0 -6
  136. package/scripts/.archived/bash-to-js-migration/bus-autotrigger.sh +0 -6
  137. package/scripts/.archived/bash-to-js-migration/bus-daemon.sh +0 -231
  138. package/scripts/.archived/bash-to-js-migration/bus-inject.sh +0 -176
  139. package/scripts/.archived/bash-to-js-migration/bus-listen.sh +0 -6
  140. package/scripts/.archived/bash-to-js-migration/bus.sh +0 -986
  141. package/scripts/.archived/bash-to-js-migration/context-decisions.sh +0 -167
  142. package/scripts/.archived/bash-to-js-migration/context-doctor.sh +0 -72
  143. package/scripts/.archived/bash-to-js-migration/context-lint.sh +0 -110
  144. package/scripts/.archived/bash-to-js-migration/doctor.sh +0 -22
  145. package/scripts/.archived/bash-to-js-migration/init.sh +0 -247
  146. package/scripts/.archived/bash-to-js-migration/skills.sh +0 -113
  147. package/scripts/.archived/bash-to-js-migration/status.sh +0 -125
  148. package/scripts/banner.sh +0 -2
  149. package/src/bus/API_DESIGN.md +0 -204
@@ -0,0 +1,1213 @@
1
+ const { randomUUID } = require("crypto");
2
+ const { loadConfig } = require("../config");
3
+ const { runToolCall } = require("./dispatch");
4
+
5
+ const CORE_TOOL_NAMES = new Set(["read", "write", "edit", "bash"]);
6
+ const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
7
+ const DEFAULT_ANTHROPIC_BASE_URL = "https://api.anthropic.com/v1";
8
+
9
+ function nowMs() {
10
+ return Date.now();
11
+ }
12
+
13
+ function normalizeTimeoutMs(value) {
14
+ const parsed = Number(value);
15
+ if (!Number.isFinite(parsed)) return 300000;
16
+ return Math.max(1000, Math.floor(parsed));
17
+ }
18
+
19
+ function createGuards({ signal = null, timeoutMs = 300000 } = {}) {
20
+ const startedAt = nowMs();
21
+ const budgetMs = normalizeTimeoutMs(timeoutMs);
22
+
23
+ function ensureActive() {
24
+ if (signal && typeof signal === "object" && signal.aborted) {
25
+ const err = new Error("CLI cancelled");
26
+ err.code = "cancelled";
27
+ throw err;
28
+ }
29
+ if (nowMs() - startedAt > budgetMs) {
30
+ const err = new Error(`CLI timeout (${budgetMs}ms)`);
31
+ err.code = "timeout";
32
+ throw err;
33
+ }
34
+ }
35
+
36
+ return {
37
+ ensureActive,
38
+ budgetMs,
39
+ };
40
+ }
41
+
42
+ function emitToolEvent(callback, event = {}) {
43
+ if (typeof callback !== "function") return;
44
+ try {
45
+ callback(event);
46
+ } catch {
47
+ // ignore callback failures
48
+ }
49
+ }
50
+
51
+ function clipText(value = "", maxChars = 6000) {
52
+ const text = String(value || "");
53
+ if (text.length <= maxChars) return text;
54
+ return `${text.slice(0, maxChars)}\n...[truncated]`;
55
+ }
56
+
57
+ function summarizeFileSnippet(file = "", content = "") {
58
+ const target = String(file || "").trim();
59
+ const body = String(content || "").trim();
60
+ if (!body) return `${target}: empty`;
61
+
62
+ if (target.toLowerCase().endsWith("package.json")) {
63
+ try {
64
+ const parsed = JSON.parse(body);
65
+ const name = String(parsed.name || "").trim() || "(unknown)";
66
+ const version = String(parsed.version || "").trim() || "(unknown)";
67
+ const scripts = parsed.scripts && typeof parsed.scripts === "object"
68
+ ? Object.keys(parsed.scripts).slice(0, 4)
69
+ : [];
70
+ const scriptText = scripts.length > 0 ? ` scripts=${scripts.join(",")}` : "";
71
+ return `${target}: name=${name} version=${version}${scriptText}`;
72
+ } catch {
73
+ // fall through
74
+ }
75
+ }
76
+
77
+ const lines = body
78
+ .split(/\r?\n/)
79
+ .map((line) => line.trim())
80
+ .filter(Boolean)
81
+ .slice(0, 3)
82
+ .map((line) => (line.length > 120 ? `${line.slice(0, 120)}...` : line));
83
+
84
+ if (lines.length === 0) return `${target}: empty`;
85
+ return `${target}: ${lines.join(" | ")}`;
86
+ }
87
+
88
+ function extractPreflightEvidence(systemPrompt = "") {
89
+ const source = String(systemPrompt || "");
90
+ if (!source) return [];
91
+
92
+ const results = [];
93
+ const fileRegex = /File:\s*([^\n]+)\n([\s\S]*?)(?=\n---\n(?:File|Command):|$)/g;
94
+ let match = fileRegex.exec(source);
95
+ while (match) {
96
+ const file = String(match[1] || "").trim();
97
+ const content = String(match[2] || "").trim();
98
+ if (file) {
99
+ results.push({ kind: "file", label: file, summary: summarizeFileSnippet(file, content) });
100
+ }
101
+ match = fileRegex.exec(source);
102
+ }
103
+
104
+ const cmdRegex = /Command:\s*([^\n]+)\n([\s\S]*?)(?=\n---\n(?:File|Command):|$)/g;
105
+ match = cmdRegex.exec(source);
106
+ while (match) {
107
+ const command = String(match[1] || "").trim();
108
+ const output = String(match[2] || "").trim();
109
+ if (command) {
110
+ const clipped = clipText(output, 300).replace(/\s+/g, " ").trim();
111
+ results.push({ kind: "command", label: command, summary: `${command}: ${clipped || "(no output)"}` });
112
+ }
113
+ match = cmdRegex.exec(source);
114
+ }
115
+
116
+ return results;
117
+ }
118
+
119
+ function isAnalysisPrompt(text = "") {
120
+ return /(?:analy[sz]e|analysis|review|audit|status|architecture|codebase|repo|project|现状|架构|审查|分析|项目|代码库)/i.test(text);
121
+ }
122
+
123
+ function parseReadIntent(prompt = "") {
124
+ const text = String(prompt || "");
125
+ const patterns = [
126
+ /(?:\bread\b|\bcat\b|查看|读取)\s+([A-Za-z0-9_./\\-]+(?:\.[A-Za-z0-9._-]+)?)/i,
127
+ /([A-Za-z0-9_./\\-]+\.(?:md|txt|json|js|ts|jsx|tsx|yml|yaml|toml|sh))/i,
128
+ ];
129
+ for (const re of patterns) {
130
+ const match = text.match(re);
131
+ if (!match || !match[1]) continue;
132
+ const candidate = String(match[1]).trim().replace(/[),.;:]+$/, "");
133
+ if (!candidate) continue;
134
+ if (candidate.length > 260) continue;
135
+ return candidate;
136
+ }
137
+ return "";
138
+ }
139
+
140
+ function parseBashIntent(prompt = "") {
141
+ const text = String(prompt || "").trim();
142
+ if (!text) return "";
143
+ if (
144
+ /\b(ls|dir|tree)\b/i.test(text)
145
+ || /\b(list|show)\s+(files|dirs|directories|folders)\b/i.test(text)
146
+ || /列出|目录|文件列表/.test(text)
147
+ ) {
148
+ return "ls -la";
149
+ }
150
+ const cmdMatch = text.match(/(?:运行|执行|run|exec(?:ute)?)\s+`([^`]+)`/i);
151
+ if (cmdMatch && cmdMatch[1]) return String(cmdMatch[1]).trim();
152
+ return "";
153
+ }
154
+
155
+ function normalizeProvider(value = "") {
156
+ const text = String(value || "").trim().toLowerCase();
157
+ if (!text) return "";
158
+ if (text === "codex" || text === "codex-cli" || text === "codex-code") return "openai";
159
+ if (text === "claude" || text === "claude-cli" || text === "claude-code") return "anthropic";
160
+ if (text === "openai" || text === "anthropic") return text;
161
+ return text;
162
+ }
163
+
164
+ function resolveTransport({ provider = "", baseUrl = "" } = {}) {
165
+ const normalizedProvider = normalizeProvider(provider);
166
+ const url = String(baseUrl || "").trim().toLowerCase();
167
+
168
+ if (normalizedProvider === "anthropic") return "anthropic-messages";
169
+ if (url.includes("anthropic.com")) return "anthropic-messages";
170
+ if (/\/messages(?:$|[/?#])/.test(url) && !/\/chat\/completions(?:$|[/?#])/.test(url)) {
171
+ return "anthropic-messages";
172
+ }
173
+
174
+ return "openai-chat";
175
+ }
176
+
177
+ function resolveRuntimeConfig({ workspaceRoot = process.cwd(), provider = "", model = "" } = {}) {
178
+ const config = loadConfig(workspaceRoot);
179
+ const configuredProvider = normalizeProvider(config.ucodeProvider || config.agentProvider || "");
180
+ const selectedProvider = normalizeProvider(
181
+ provider
182
+ || process.env.UFOO_UCODE_PROVIDER
183
+ || configuredProvider
184
+ || "openai"
185
+ ) || "openai";
186
+
187
+ const selectedModel = String(
188
+ model
189
+ || process.env.UFOO_UCODE_MODEL
190
+ || config.ucodeModel
191
+ || config.agentModel
192
+ || ""
193
+ ).trim();
194
+
195
+ const defaultBaseUrl = selectedProvider === "anthropic"
196
+ ? String(process.env.ANTHROPIC_BASE_URL || DEFAULT_ANTHROPIC_BASE_URL)
197
+ : String(process.env.OPENAI_BASE_URL || DEFAULT_OPENAI_BASE_URL);
198
+
199
+ const baseUrl = String(
200
+ process.env.UFOO_UCODE_BASE_URL
201
+ || config.ucodeBaseUrl
202
+ || defaultBaseUrl
203
+ ).trim();
204
+
205
+ const apiKey = String(
206
+ process.env.UFOO_UCODE_API_KEY
207
+ || config.ucodeApiKey
208
+ || (selectedProvider === "openai" ? process.env.OPENAI_API_KEY : "")
209
+ || (selectedProvider === "anthropic" ? process.env.ANTHROPIC_API_KEY : "")
210
+ || ""
211
+ ).trim();
212
+
213
+ return {
214
+ provider: selectedProvider,
215
+ model: selectedModel,
216
+ baseUrl,
217
+ apiKey,
218
+ transport: resolveTransport({ provider: selectedProvider, baseUrl }),
219
+ };
220
+ }
221
+
222
+ function resolveCompletionUrl(baseUrl = "") {
223
+ const raw = String(baseUrl || "").trim();
224
+ if (!raw) return "";
225
+ const normalized = raw.replace(/\/+$/, "");
226
+ if (/\/chat\/completions$/i.test(normalized)) return normalized;
227
+ if (/\/v1$/i.test(normalized)) return `${normalized}/chat/completions`;
228
+ if (/\/api$/i.test(normalized)) return `${normalized}/v1/chat/completions`;
229
+ return `${normalized}/chat/completions`;
230
+ }
231
+
232
+ function resolveAnthropicMessagesUrl(baseUrl = "") {
233
+ const raw = String(baseUrl || "").trim() || DEFAULT_ANTHROPIC_BASE_URL;
234
+ const normalized = raw.replace(/\/+$/, "");
235
+ if (/\/messages$/i.test(normalized)) return normalized;
236
+ if (/\/v1$/i.test(normalized)) return `${normalized}/messages`;
237
+ if (/\/api$/i.test(normalized)) return `${normalized}/v1/messages`;
238
+ return `${normalized}/messages`;
239
+ }
240
+
241
+ function buildCoreToolSpecs() {
242
+ return [
243
+ {
244
+ type: "function",
245
+ function: {
246
+ name: "read",
247
+ description: "Read a text file from workspace.",
248
+ parameters: {
249
+ type: "object",
250
+ properties: {
251
+ path: { type: "string" },
252
+ startLine: { type: "integer" },
253
+ endLine: { type: "integer" },
254
+ maxBytes: { type: "integer" },
255
+ },
256
+ required: ["path"],
257
+ },
258
+ },
259
+ },
260
+ {
261
+ type: "function",
262
+ function: {
263
+ name: "write",
264
+ description: "Write content to a file in workspace.",
265
+ parameters: {
266
+ type: "object",
267
+ properties: {
268
+ path: { type: "string" },
269
+ content: { type: "string" },
270
+ append: { type: "boolean" },
271
+ },
272
+ required: ["path", "content"],
273
+ },
274
+ },
275
+ },
276
+ {
277
+ type: "function",
278
+ function: {
279
+ name: "edit",
280
+ description: "Replace text in a file in workspace.",
281
+ parameters: {
282
+ type: "object",
283
+ properties: {
284
+ path: { type: "string" },
285
+ find: { type: "string" },
286
+ replace: { type: "string" },
287
+ all: { type: "boolean" },
288
+ },
289
+ required: ["path", "find", "replace"],
290
+ },
291
+ },
292
+ },
293
+ {
294
+ type: "function",
295
+ function: {
296
+ name: "bash",
297
+ description: "Run one shell command in workspace.",
298
+ parameters: {
299
+ type: "object",
300
+ properties: {
301
+ command: { type: "string" },
302
+ timeoutMs: { type: "integer" },
303
+ },
304
+ required: ["command"],
305
+ },
306
+ },
307
+ },
308
+ ];
309
+ }
310
+
311
+ function buildAnthropicToolSpecs() {
312
+ return buildCoreToolSpecs().map((spec) => ({
313
+ name: spec.function.name,
314
+ description: spec.function.description,
315
+ input_schema: spec.function.parameters,
316
+ }));
317
+ }
318
+
319
+ function createRequestController({ signal = null, timeoutMs = 300000 } = {}) {
320
+ const controller = new AbortController();
321
+ let timedOut = false;
322
+
323
+ const timer = setTimeout(() => {
324
+ timedOut = true;
325
+ try {
326
+ controller.abort();
327
+ } catch {
328
+ // ignore
329
+ }
330
+ }, normalizeTimeoutMs(timeoutMs));
331
+
332
+ let abortHandler = null;
333
+ if (signal && typeof signal === "object") {
334
+ abortHandler = () => {
335
+ try {
336
+ controller.abort();
337
+ } catch {
338
+ // ignore
339
+ }
340
+ };
341
+ if (signal.aborted) {
342
+ abortHandler();
343
+ } else if (typeof signal.addEventListener === "function") {
344
+ signal.addEventListener("abort", abortHandler, { once: true });
345
+ }
346
+ }
347
+
348
+ return {
349
+ signal: controller.signal,
350
+ timedOut: () => timedOut,
351
+ cleanup: () => {
352
+ clearTimeout(timer);
353
+ if (signal && abortHandler && typeof signal.removeEventListener === "function") {
354
+ signal.removeEventListener("abort", abortHandler);
355
+ }
356
+ },
357
+ };
358
+ }
359
+
360
+ function parseJsonSafe(value = "", fallback = null) {
361
+ try {
362
+ return JSON.parse(String(value || ""));
363
+ } catch {
364
+ return fallback;
365
+ }
366
+ }
367
+
368
+ function cloneMessageList(value = []) {
369
+ const parsed = parseJsonSafe(toJsonString(value), []);
370
+ if (!Array.isArray(parsed)) return [];
371
+ return parsed.filter((entry) => entry && typeof entry === "object" && !Array.isArray(entry));
372
+ }
373
+
374
+ function normalizeToolName(value = "") {
375
+ const name = String(value || "").trim().toLowerCase();
376
+ if (!CORE_TOOL_NAMES.has(name)) return "";
377
+ return name;
378
+ }
379
+
380
+ function toJsonString(value) {
381
+ try {
382
+ return JSON.stringify(value);
383
+ } catch {
384
+ return String(value || "");
385
+ }
386
+ }
387
+
388
+ function parseSseBlocks(text = "") {
389
+ const source = String(text || "");
390
+ const blocks = source.split(/\r?\n\r?\n/);
391
+ if (blocks.length <= 1) {
392
+ return { blocks: [], rest: source };
393
+ }
394
+ const rest = blocks.pop() || "";
395
+ return { blocks, rest };
396
+ }
397
+
398
+ function parseSseEventBlock(block = "") {
399
+ const lines = String(block || "").split(/\r?\n/);
400
+ let event = "message";
401
+ const data = [];
402
+
403
+ for (const line of lines) {
404
+ if (!line) continue;
405
+ if (line.startsWith("event:")) {
406
+ event = line.slice(6).trim() || "message";
407
+ continue;
408
+ }
409
+ if (line.startsWith("data:")) {
410
+ data.push(line.slice(5).trimStart());
411
+ }
412
+ }
413
+
414
+ return {
415
+ event,
416
+ data: data.join("\n"),
417
+ };
418
+ }
419
+
420
+ function parseSseDataBlock(block = "") {
421
+ return parseSseEventBlock(block).data;
422
+ }
423
+
424
+ function normalizeToolCallArgs(raw = "") {
425
+ const text = String(raw || "").trim();
426
+ if (!text) return {};
427
+ const parsed = parseJsonSafe(text, null);
428
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
429
+ return parsed;
430
+ }
431
+ return {};
432
+ }
433
+
434
+ function runCoreTool({ tool = "", args = {}, workspaceRoot = process.cwd(), onToolEvent = null } = {}) {
435
+ const normalizedTool = normalizeToolName(tool);
436
+ if (!normalizedTool) {
437
+ emitToolEvent(onToolEvent, {
438
+ tool: String(tool || "unknown"),
439
+ phase: "error",
440
+ args: args && typeof args === "object" ? { ...args } : {},
441
+ error: `unsupported tool: ${tool}`,
442
+ });
443
+ return {
444
+ ok: false,
445
+ error: `unsupported tool: ${tool}`,
446
+ };
447
+ }
448
+
449
+ const safeArgs = args && typeof args === "object" ? { ...args } : {};
450
+ emitToolEvent(onToolEvent, {
451
+ tool: normalizedTool,
452
+ phase: "start",
453
+ args: safeArgs,
454
+ error: "",
455
+ });
456
+
457
+ const result = runToolCall(
458
+ { tool: normalizedTool, args: safeArgs },
459
+ { workspaceRoot, cwd: workspaceRoot }
460
+ );
461
+
462
+ if (!result || result.ok === false) {
463
+ emitToolEvent(onToolEvent, {
464
+ tool: normalizedTool,
465
+ phase: "error",
466
+ args: safeArgs,
467
+ error: String((result && result.error) || `${normalizedTool} failed`),
468
+ });
469
+ }
470
+
471
+ return result;
472
+ }
473
+
474
+ async function runOpenAiLikeTurn({
475
+ url = "",
476
+ apiKey = "",
477
+ model = "",
478
+ messages = [],
479
+ onTextDelta = null,
480
+ signal = null,
481
+ timeoutMs = 300000,
482
+ } = {}) {
483
+ const payload = {
484
+ model,
485
+ messages,
486
+ tools: buildCoreToolSpecs(),
487
+ tool_choice: "auto",
488
+ stream: true,
489
+ temperature: 0,
490
+ };
491
+
492
+ const headers = {
493
+ "content-type": "application/json",
494
+ };
495
+ if (apiKey) {
496
+ headers.authorization = `Bearer ${apiKey}`;
497
+ }
498
+
499
+ const request = createRequestController({ signal, timeoutMs });
500
+
501
+ try {
502
+ const response = await fetch(url, {
503
+ method: "POST",
504
+ headers,
505
+ body: JSON.stringify(payload),
506
+ signal: request.signal,
507
+ });
508
+
509
+ if (!response.ok) {
510
+ const body = await response.text().catch(() => "");
511
+ throw new Error(`provider request failed (${response.status}): ${clipText(body, 500)}`);
512
+ }
513
+
514
+ if (!response.body || typeof response.body.getReader !== "function") {
515
+ const data = await response.json();
516
+ const message = data && data.choices && data.choices[0] && data.choices[0].message
517
+ ? data.choices[0].message
518
+ : {};
519
+ const text = typeof message.content === "string" ? message.content : "";
520
+ const toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
521
+ if (text && typeof onTextDelta === "function") {
522
+ onTextDelta(text);
523
+ }
524
+ return {
525
+ text,
526
+ toolCalls,
527
+ };
528
+ }
529
+
530
+ const reader = response.body.getReader();
531
+ const decoder = new TextDecoder();
532
+ const toolCallMap = new Map();
533
+ let rawBuffer = "";
534
+ let responseText = "";
535
+
536
+ while (true) {
537
+ const { done, value } = await reader.read();
538
+ if (done) break;
539
+
540
+ rawBuffer += decoder.decode(value, { stream: true });
541
+ const parsed = parseSseBlocks(rawBuffer);
542
+ rawBuffer = parsed.rest;
543
+
544
+ for (const block of parsed.blocks) {
545
+ const payloadText = parseSseDataBlock(block);
546
+ if (!payloadText) continue;
547
+ if (payloadText === "[DONE]") {
548
+ rawBuffer = "";
549
+ break;
550
+ }
551
+
552
+ const chunk = parseJsonSafe(payloadText, null);
553
+ if (!chunk || typeof chunk !== "object") continue;
554
+
555
+ const choice = chunk.choices && chunk.choices[0] ? chunk.choices[0] : null;
556
+ if (!choice || typeof choice !== "object") continue;
557
+
558
+ const delta = choice.delta && typeof choice.delta === "object" ? choice.delta : {};
559
+
560
+ if (typeof delta.content === "string" && delta.content) {
561
+ responseText += delta.content;
562
+ if (typeof onTextDelta === "function") {
563
+ onTextDelta(delta.content);
564
+ }
565
+ }
566
+
567
+ if (Array.isArray(delta.tool_calls)) {
568
+ for (const callPart of delta.tool_calls) {
569
+ const index = Number.isFinite(callPart.index) ? callPart.index : 0;
570
+ const previous = toolCallMap.get(index) || {
571
+ id: "",
572
+ type: "function",
573
+ function: {
574
+ name: "",
575
+ arguments: "",
576
+ },
577
+ };
578
+
579
+ if (typeof callPart.id === "string" && callPart.id) previous.id = callPart.id;
580
+ if (callPart.function && typeof callPart.function === "object") {
581
+ if (typeof callPart.function.name === "string" && callPart.function.name) {
582
+ previous.function.name = callPart.function.name;
583
+ }
584
+ if (typeof callPart.function.arguments === "string" && callPart.function.arguments) {
585
+ previous.function.arguments += callPart.function.arguments;
586
+ }
587
+ }
588
+
589
+ toolCallMap.set(index, previous);
590
+ }
591
+ }
592
+ }
593
+ }
594
+
595
+ if (rawBuffer.trim()) {
596
+ const fallbackBlock = parseSseDataBlock(rawBuffer);
597
+ if (fallbackBlock && fallbackBlock !== "[DONE]") {
598
+ const chunk = parseJsonSafe(fallbackBlock, null);
599
+ const choice = chunk && chunk.choices && chunk.choices[0] ? chunk.choices[0] : null;
600
+ if (choice && choice.delta && typeof choice.delta.content === "string" && choice.delta.content) {
601
+ responseText += choice.delta.content;
602
+ if (typeof onTextDelta === "function") {
603
+ onTextDelta(choice.delta.content);
604
+ }
605
+ }
606
+ }
607
+ }
608
+
609
+ return {
610
+ text: responseText,
611
+ toolCalls: Array.from(toolCallMap.entries())
612
+ .sort((a, b) => a[0] - b[0])
613
+ .map((entry) => entry[1]),
614
+ };
615
+ } catch (err) {
616
+ if (request.timedOut()) {
617
+ const timeoutError = new Error(`CLI timeout (${normalizeTimeoutMs(timeoutMs)}ms)`);
618
+ timeoutError.code = "timeout";
619
+ throw timeoutError;
620
+ }
621
+ if (signal && typeof signal === "object" && signal.aborted) {
622
+ const cancelError = new Error("CLI cancelled");
623
+ cancelError.code = "cancelled";
624
+ throw cancelError;
625
+ }
626
+ throw err;
627
+ } finally {
628
+ request.cleanup();
629
+ }
630
+ }
631
+
632
+ function normalizeAnthropicMessageContent(raw = []) {
633
+ if (!Array.isArray(raw)) return [];
634
+ return raw
635
+ .map((item) => {
636
+ if (!item || typeof item !== "object") return null;
637
+ if (item.type === "text") {
638
+ return {
639
+ type: "text",
640
+ text: String(item.text || ""),
641
+ };
642
+ }
643
+ if (item.type === "tool_use") {
644
+ return {
645
+ type: "tool_use",
646
+ id: String(item.id || ""),
647
+ name: String(item.name || ""),
648
+ input: item.input && typeof item.input === "object" && !Array.isArray(item.input)
649
+ ? item.input
650
+ : {},
651
+ };
652
+ }
653
+ return null;
654
+ })
655
+ .filter(Boolean);
656
+ }
657
+
658
+ function extractAnthropicToolCalls(content = []) {
659
+ return normalizeAnthropicMessageContent(content)
660
+ .filter((item) => item.type === "tool_use")
661
+ .map((item) => ({
662
+ id: String(item.id || `tool_${randomUUID()}`),
663
+ name: String(item.name || ""),
664
+ args: item.input && typeof item.input === "object" && !Array.isArray(item.input)
665
+ ? item.input
666
+ : {},
667
+ }));
668
+ }
669
+
670
+ async function runAnthropicTurn({
671
+ url = "",
672
+ apiKey = "",
673
+ model = "",
674
+ systemPrompt = "",
675
+ messages = [],
676
+ onTextDelta = null,
677
+ signal = null,
678
+ timeoutMs = 300000,
679
+ } = {}) {
680
+ const payload = {
681
+ model,
682
+ max_tokens: 4096,
683
+ messages,
684
+ tools: buildAnthropicToolSpecs(),
685
+ stream: true,
686
+ };
687
+ const systemText = String(systemPrompt || "").trim();
688
+ if (systemText) {
689
+ payload.system = systemText;
690
+ }
691
+
692
+ const headers = {
693
+ "content-type": "application/json",
694
+ "anthropic-version": "2023-06-01",
695
+ };
696
+ if (apiKey) {
697
+ headers["x-api-key"] = apiKey;
698
+ }
699
+
700
+ const request = createRequestController({ signal, timeoutMs });
701
+
702
+ try {
703
+ const response = await fetch(url, {
704
+ method: "POST",
705
+ headers,
706
+ body: JSON.stringify(payload),
707
+ signal: request.signal,
708
+ });
709
+
710
+ if (!response.ok) {
711
+ const body = await response.text().catch(() => "");
712
+ throw new Error(`provider request failed (${response.status}): ${clipText(body, 500)}`);
713
+ }
714
+
715
+ if (!response.body || typeof response.body.getReader !== "function") {
716
+ const data = await response.json();
717
+ const content = normalizeAnthropicMessageContent(data && data.content);
718
+ const text = content
719
+ .filter((item) => item.type === "text")
720
+ .map((item) => item.text)
721
+ .join("");
722
+ if (text && typeof onTextDelta === "function") {
723
+ onTextDelta(text);
724
+ }
725
+ return {
726
+ text,
727
+ assistantContent: content,
728
+ toolCalls: extractAnthropicToolCalls(content),
729
+ };
730
+ }
731
+
732
+ const reader = response.body.getReader();
733
+ const decoder = new TextDecoder();
734
+ const blockMap = new Map();
735
+ let rawBuffer = "";
736
+ let responseText = "";
737
+
738
+ while (true) {
739
+ const { done, value } = await reader.read();
740
+ if (done) break;
741
+
742
+ rawBuffer += decoder.decode(value, { stream: true });
743
+ const parsed = parseSseBlocks(rawBuffer);
744
+ rawBuffer = parsed.rest;
745
+
746
+ for (const rawBlock of parsed.blocks) {
747
+ const { event, data } = parseSseEventBlock(rawBlock);
748
+ if (!data || data === "[DONE]") continue;
749
+
750
+ const payloadChunk = parseJsonSafe(data, null);
751
+ if (!payloadChunk || typeof payloadChunk !== "object") continue;
752
+
753
+ if (event === "error") {
754
+ const errMsg = payloadChunk.error && payloadChunk.error.message
755
+ ? String(payloadChunk.error.message)
756
+ : "anthropic stream error";
757
+ throw new Error(errMsg);
758
+ }
759
+
760
+ if (event === "content_block_start") {
761
+ const index = Number.isFinite(payloadChunk.index) ? payloadChunk.index : 0;
762
+ const contentBlock = payloadChunk.content_block && typeof payloadChunk.content_block === "object"
763
+ ? payloadChunk.content_block
764
+ : {};
765
+
766
+ if (contentBlock.type === "text") {
767
+ blockMap.set(index, {
768
+ order: index,
769
+ type: "text",
770
+ text: String(contentBlock.text || ""),
771
+ });
772
+ } else if (contentBlock.type === "tool_use") {
773
+ blockMap.set(index, {
774
+ order: index,
775
+ type: "tool_use",
776
+ id: String(contentBlock.id || ""),
777
+ name: String(contentBlock.name || ""),
778
+ input: contentBlock.input && typeof contentBlock.input === "object" && !Array.isArray(contentBlock.input)
779
+ ? { ...contentBlock.input }
780
+ : {},
781
+ inputJson: "",
782
+ });
783
+ }
784
+ continue;
785
+ }
786
+
787
+ if (event === "content_block_delta") {
788
+ const index = Number.isFinite(payloadChunk.index) ? payloadChunk.index : 0;
789
+ const delta = payloadChunk.delta && typeof payloadChunk.delta === "object"
790
+ ? payloadChunk.delta
791
+ : {};
792
+ const current = blockMap.get(index) || { order: index, type: "text", text: "" };
793
+
794
+ if (delta.type === "text_delta") {
795
+ const deltaText = String(delta.text || "");
796
+ current.type = "text";
797
+ current.text = `${String(current.text || "")}${deltaText}`;
798
+ blockMap.set(index, current);
799
+ if (deltaText) {
800
+ responseText += deltaText;
801
+ if (typeof onTextDelta === "function") {
802
+ onTextDelta(deltaText);
803
+ }
804
+ }
805
+ continue;
806
+ }
807
+
808
+ if (delta.type === "input_json_delta") {
809
+ current.type = "tool_use";
810
+ current.inputJson = `${String(current.inputJson || "")}${String(delta.partial_json || "")}`;
811
+ blockMap.set(index, current);
812
+ continue;
813
+ }
814
+ }
815
+ }
816
+ }
817
+
818
+ const assistantContent = Array.from(blockMap.values())
819
+ .sort((a, b) => a.order - b.order)
820
+ .map((item) => {
821
+ if (item.type === "text") {
822
+ return {
823
+ type: "text",
824
+ text: String(item.text || ""),
825
+ };
826
+ }
827
+
828
+ const inputFromDelta = normalizeToolCallArgs(item.inputJson || "");
829
+ const mergedInput = {
830
+ ...(item.input && typeof item.input === "object" ? item.input : {}),
831
+ ...(inputFromDelta && typeof inputFromDelta === "object" ? inputFromDelta : {}),
832
+ };
833
+ return {
834
+ type: "tool_use",
835
+ id: String(item.id || `tool_${randomUUID()}`),
836
+ name: String(item.name || ""),
837
+ input: mergedInput,
838
+ };
839
+ });
840
+
841
+ if (!responseText) {
842
+ responseText = assistantContent
843
+ .filter((item) => item.type === "text")
844
+ .map((item) => item.text)
845
+ .join("");
846
+ }
847
+
848
+ return {
849
+ text: responseText,
850
+ assistantContent,
851
+ toolCalls: extractAnthropicToolCalls(assistantContent),
852
+ };
853
+ } catch (err) {
854
+ if (request.timedOut()) {
855
+ const timeoutError = new Error(`CLI timeout (${normalizeTimeoutMs(timeoutMs)}ms)`);
856
+ timeoutError.code = "timeout";
857
+ throw timeoutError;
858
+ }
859
+ if (signal && typeof signal === "object" && signal.aborted) {
860
+ const cancelError = new Error("CLI cancelled");
861
+ cancelError.code = "cancelled";
862
+ throw cancelError;
863
+ }
864
+ throw err;
865
+ } finally {
866
+ request.cleanup();
867
+ }
868
+ }
869
+
870
+ async function runNativeLoopOpenAi({
871
+ workspaceRoot = process.cwd(),
872
+ prompt = "",
873
+ systemPrompt = "",
874
+ historyMessages = [],
875
+ model = "",
876
+ baseUrl = "",
877
+ apiKey = "",
878
+ timeoutMs = 300000,
879
+ onStreamDelta = null,
880
+ onToolEvent = null,
881
+ signal = null,
882
+ guards,
883
+ } = {}) {
884
+ const requestModel = String(model || "").trim();
885
+ if (!requestModel) {
886
+ throw new Error("ucode model is not configured");
887
+ }
888
+
889
+ const requestUrl = resolveCompletionUrl(baseUrl);
890
+ if (!requestUrl) {
891
+ throw new Error("ucode baseUrl is not configured");
892
+ }
893
+
894
+ const messages = cloneMessageList(historyMessages);
895
+ const systemText = String(systemPrompt || "").trim();
896
+ const hasSystem = messages.some((entry) => String(entry.role || "").trim() === "system");
897
+ if (systemText && !hasSystem) {
898
+ messages.unshift({ role: "system", content: systemText });
899
+ }
900
+ messages.push({ role: "user", content: String(prompt || "") });
901
+
902
+ let aggregated = "";
903
+ let streamed = false;
904
+ let toolCallsExecuted = 0;
905
+
906
+ while (true) {
907
+ guards.ensureActive();
908
+
909
+ const turnResult = await runOpenAiLikeTurn({
910
+ url: requestUrl,
911
+ apiKey,
912
+ model: requestModel,
913
+ messages,
914
+ signal,
915
+ timeoutMs,
916
+ onTextDelta: (chunk) => {
917
+ const text = String(chunk || "");
918
+ if (!text) return;
919
+ aggregated += text;
920
+ if (typeof onStreamDelta === "function") {
921
+ streamed = true;
922
+ onStreamDelta(text);
923
+ }
924
+ },
925
+ });
926
+
927
+ const toolCalls = Array.isArray(turnResult.toolCalls)
928
+ ? turnResult.toolCalls.filter((call) => call && call.function && typeof call.function === "object")
929
+ : [];
930
+
931
+ if (toolCalls.length === 0) {
932
+ const text = String(turnResult.text || "").trim();
933
+ if (text) {
934
+ messages.push({
935
+ role: "assistant",
936
+ content: text,
937
+ });
938
+ }
939
+ if (!aggregated.trim() && text) {
940
+ aggregated = text;
941
+ }
942
+ return {
943
+ text: aggregated,
944
+ streamed,
945
+ toolCallsExecuted,
946
+ messages,
947
+ };
948
+ }
949
+
950
+ const assistantToolCalls = [];
951
+ for (const call of toolCalls) {
952
+ const callId = String(call.id || `call_${randomUUID()}`);
953
+ const name = normalizeToolName(call.function.name || "");
954
+ const args = normalizeToolCallArgs(call.function.arguments || "");
955
+
956
+ assistantToolCalls.push({
957
+ id: callId,
958
+ type: "function",
959
+ function: {
960
+ name: name || String(call.function.name || ""),
961
+ arguments: toJsonString(args),
962
+ },
963
+ });
964
+ }
965
+
966
+ if (assistantToolCalls.length === 0) {
967
+ return {
968
+ text: aggregated,
969
+ streamed,
970
+ toolCallsExecuted,
971
+ messages,
972
+ };
973
+ }
974
+
975
+ messages.push({
976
+ role: "assistant",
977
+ content: null,
978
+ tool_calls: assistantToolCalls,
979
+ });
980
+
981
+ for (const toolCall of assistantToolCalls) {
982
+ const toolResult = runCoreTool({
983
+ tool: toolCall.function.name,
984
+ args: normalizeToolCallArgs(toolCall.function.arguments),
985
+ workspaceRoot,
986
+ onToolEvent,
987
+ });
988
+ toolCallsExecuted += 1;
989
+ messages.push({
990
+ role: "tool",
991
+ tool_call_id: toolCall.id,
992
+ content: clipText(toJsonString(toolResult), 12000),
993
+ });
994
+ }
995
+ }
996
+
997
+ }
998
+
999
+ async function runNativeLoopAnthropic({
1000
+ workspaceRoot = process.cwd(),
1001
+ prompt = "",
1002
+ systemPrompt = "",
1003
+ historyMessages = [],
1004
+ model = "",
1005
+ baseUrl = "",
1006
+ apiKey = "",
1007
+ timeoutMs = 300000,
1008
+ onStreamDelta = null,
1009
+ onToolEvent = null,
1010
+ signal = null,
1011
+ guards,
1012
+ } = {}) {
1013
+ const requestModel = String(model || "").trim();
1014
+ if (!requestModel) {
1015
+ throw new Error("ucode model is not configured");
1016
+ }
1017
+
1018
+ const requestUrl = resolveAnthropicMessagesUrl(baseUrl);
1019
+ if (!requestUrl) {
1020
+ throw new Error("ucode baseUrl is not configured");
1021
+ }
1022
+
1023
+ const messages = cloneMessageList(historyMessages);
1024
+ messages.push({
1025
+ role: "user",
1026
+ content: String(prompt || ""),
1027
+ });
1028
+
1029
+ let aggregated = "";
1030
+ let streamed = false;
1031
+ let toolCallsExecuted = 0;
1032
+
1033
+ while (true) {
1034
+ guards.ensureActive();
1035
+
1036
+ const turnResult = await runAnthropicTurn({
1037
+ url: requestUrl,
1038
+ apiKey,
1039
+ model: requestModel,
1040
+ systemPrompt,
1041
+ messages,
1042
+ signal,
1043
+ timeoutMs,
1044
+ onTextDelta: (chunk) => {
1045
+ const text = String(chunk || "");
1046
+ if (!text) return;
1047
+ aggregated += text;
1048
+ if (typeof onStreamDelta === "function") {
1049
+ streamed = true;
1050
+ onStreamDelta(text);
1051
+ }
1052
+ },
1053
+ });
1054
+
1055
+ const toolCalls = Array.isArray(turnResult.toolCalls) ? turnResult.toolCalls : [];
1056
+
1057
+ if (toolCalls.length === 0) {
1058
+ const assistantContent = Array.isArray(turnResult.assistantContent)
1059
+ ? turnResult.assistantContent
1060
+ : [];
1061
+ if (assistantContent.length > 0) {
1062
+ messages.push({
1063
+ role: "assistant",
1064
+ content: assistantContent,
1065
+ });
1066
+ } else if (String(turnResult.text || "").trim()) {
1067
+ messages.push({
1068
+ role: "assistant",
1069
+ content: [
1070
+ {
1071
+ type: "text",
1072
+ text: String(turnResult.text || ""),
1073
+ },
1074
+ ],
1075
+ });
1076
+ }
1077
+ const text = String(turnResult.text || "").trim();
1078
+ if (!aggregated.trim() && text) {
1079
+ aggregated = text;
1080
+ }
1081
+ return {
1082
+ text: aggregated,
1083
+ streamed,
1084
+ toolCallsExecuted,
1085
+ messages,
1086
+ };
1087
+ }
1088
+
1089
+ const assistantContent = Array.isArray(turnResult.assistantContent)
1090
+ ? turnResult.assistantContent
1091
+ : [];
1092
+
1093
+ messages.push({
1094
+ role: "assistant",
1095
+ content: assistantContent,
1096
+ });
1097
+
1098
+ const toolResults = [];
1099
+ for (const call of toolCalls) {
1100
+ const toolResult = runCoreTool({
1101
+ tool: call.name,
1102
+ args: call.args,
1103
+ workspaceRoot,
1104
+ onToolEvent,
1105
+ });
1106
+ toolCallsExecuted += 1;
1107
+ toolResults.push({
1108
+ type: "tool_result",
1109
+ tool_use_id: String(call.id || ""),
1110
+ content: clipText(toJsonString(toolResult), 12000),
1111
+ is_error: Boolean(!toolResult || toolResult.ok === false),
1112
+ });
1113
+ }
1114
+
1115
+ messages.push({
1116
+ role: "user",
1117
+ content: toolResults,
1118
+ });
1119
+ }
1120
+
1121
+ }
1122
+
1123
+ async function runNativeAgentTask({
1124
+ workspaceRoot = process.cwd(),
1125
+ prompt = "",
1126
+ systemPrompt = "",
1127
+ provider = "",
1128
+ model = "",
1129
+ messages = [],
1130
+ sessionId = "",
1131
+ timeoutMs = 300000,
1132
+ onStreamDelta = null,
1133
+ onToolEvent = null,
1134
+ signal = null,
1135
+ } = {}) {
1136
+ const guards = createGuards({ signal, timeoutMs });
1137
+ const nextSessionId = String(sessionId || "").trim() || `native-${randomUUID()}`;
1138
+ const promptText = String(prompt || "").trim();
1139
+
1140
+ try {
1141
+ guards.ensureActive();
1142
+
1143
+ if (!promptText) {
1144
+ return {
1145
+ ok: false,
1146
+ error: "empty task",
1147
+ output: "",
1148
+ sessionId: nextSessionId,
1149
+ streamed: false,
1150
+ };
1151
+ }
1152
+
1153
+ const runtime = resolveRuntimeConfig({
1154
+ workspaceRoot,
1155
+ provider,
1156
+ model,
1157
+ });
1158
+
1159
+ const loopRunner = runtime.transport === "anthropic-messages"
1160
+ ? runNativeLoopAnthropic
1161
+ : runNativeLoopOpenAi;
1162
+
1163
+ const runResult = await loopRunner({
1164
+ workspaceRoot,
1165
+ prompt: promptText,
1166
+ systemPrompt,
1167
+ historyMessages: messages,
1168
+ model: runtime.model,
1169
+ baseUrl: runtime.baseUrl,
1170
+ apiKey: runtime.apiKey,
1171
+ timeoutMs,
1172
+ onStreamDelta,
1173
+ onToolEvent,
1174
+ signal,
1175
+ guards,
1176
+ });
1177
+
1178
+ const outputText = String(runResult.text || "").trim() || (
1179
+ runResult.toolCallsExecuted > 0
1180
+ ? `Completed ${runResult.toolCallsExecuted} tool call${runResult.toolCallsExecuted === 1 ? "" : "s"}.`
1181
+ : ""
1182
+ );
1183
+
1184
+ return {
1185
+ ok: true,
1186
+ error: "",
1187
+ output: outputText,
1188
+ messages: cloneMessageList(runResult.messages),
1189
+ sessionId: nextSessionId,
1190
+ streamed: Boolean(runResult.streamed),
1191
+ };
1192
+ } catch (err) {
1193
+ const message = err && err.message ? err.message : "native runner failed";
1194
+ return {
1195
+ ok: false,
1196
+ error: message,
1197
+ output: "",
1198
+ sessionId: nextSessionId,
1199
+ streamed: false,
1200
+ };
1201
+ }
1202
+ }
1203
+
1204
+ module.exports = {
1205
+ runNativeAgentTask,
1206
+ parseReadIntent,
1207
+ parseBashIntent,
1208
+ extractPreflightEvidence,
1209
+ resolveRuntimeConfig,
1210
+ resolveCompletionUrl,
1211
+ resolveAnthropicMessagesUrl,
1212
+ resolveTransport,
1213
+ };