u-foo 2.5.15 → 3.0.0

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 (40) hide show
  1. package/package.json +1 -1
  2. package/src/code/agent.js +333 -243
  3. package/src/code/commands.js +16 -0
  4. package/src/code/context/assembler.js +18 -13
  5. package/src/code/context/executionSegment.js +97 -119
  6. package/src/code/context/index.js +11 -1
  7. package/src/code/context/planGraph.js +1410 -0
  8. package/src/code/context/planGraphService.js +857 -0
  9. package/src/code/context/planMode.js +398 -0
  10. package/src/code/context/planProjection.js +432 -0
  11. package/src/code/context/promptLayers.js +21 -5
  12. package/src/code/context/stateCommit.js +2 -0
  13. package/src/code/context/toolRuntime.js +172 -0
  14. package/src/code/context/userInteraction.js +457 -0
  15. package/src/code/context/userNudge.js +116 -0
  16. package/src/code/dispatch.js +17 -1
  17. package/src/code/index.js +2 -0
  18. package/src/code/nativeRunner.js +518 -37
  19. package/src/code/repl.js +160 -18
  20. package/src/code/runtime/agentWakeup.js +58 -0
  21. package/src/code/runtime/graphOwner.js +41 -0
  22. package/src/code/runtime/graphYieldRouter.js +42 -0
  23. package/src/code/runtime/index.js +15 -0
  24. package/src/code/runtime/loopMailbox.js +124 -0
  25. package/src/code/runtime/runtimeEvents.js +39 -0
  26. package/src/code/runtime/taskControl.js +565 -0
  27. package/src/code/runtime/taskFocus.js +165 -0
  28. package/src/code/runtime/taskLoop.js +383 -0
  29. package/src/code/runtime/taskRun.js +187 -0
  30. package/src/code/runtime/toolProvenance.js +70 -0
  31. package/src/code/runtime/workspaceLease.js +208 -0
  32. package/src/code/sessionStore.js +0 -10
  33. package/src/code/skills/injection.js +1 -0
  34. package/src/code/taskDecomposer.js +32 -8
  35. package/src/code/tools/askUser.js +11 -0
  36. package/src/code/tools/planGraph.js +29 -0
  37. package/src/ui/format/index.js +25 -1
  38. package/src/ui/format/markdownRenderer.js +224 -2
  39. package/src/ui/ink/UcodeApp.js +285 -22
  40. package/src/code/context/featureFlag.js +0 -13
@@ -0,0 +1,457 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * User interaction (approval / choice / chat) for Agent Loop.
5
+ *
6
+ * Continuity rule: the question lives in the prior ask_user tool-call args
7
+ * (or checkpoint waitingFor). The answer is only the payload — never restate
8
+ * the question — and is written as the matching tool_result so it sits
9
+ * immediately after that tool call in the model transcript.
10
+ */
11
+
12
+ const { randomUUID } = require("crypto");
13
+ const { advanceStoredGraph } = require("./planGraphService");
14
+
15
+ const INTERACTION_KINDS = Object.freeze(["approval", "choice", "chat"]);
16
+
17
+ function createInteractionId() {
18
+ return `ui_${Date.now().toString(36)}_${randomUUID().slice(0, 6)}`;
19
+ }
20
+
21
+ function ensureInteractionState(executionState = null) {
22
+ const state = executionState && typeof executionState === "object" ? executionState : {};
23
+ if (state.pendingUserInteraction === undefined) {
24
+ state.pendingUserInteraction = null;
25
+ }
26
+ return state;
27
+ }
28
+
29
+ function getPendingUserInteraction(executionState = null) {
30
+ const state = ensureInteractionState(executionState);
31
+ return state.pendingUserInteraction && typeof state.pendingUserInteraction === "object"
32
+ ? state.pendingUserInteraction
33
+ : null;
34
+ }
35
+
36
+ function hasPendingUserInteraction(executionState = null) {
37
+ return Boolean(getPendingUserInteraction(executionState));
38
+ }
39
+
40
+ function clearPendingUserInteraction(executionState = null) {
41
+ const state = ensureInteractionState(executionState);
42
+ state.pendingUserInteraction = null;
43
+ return state;
44
+ }
45
+
46
+ function normalizeOptions(kind = "chat", options = []) {
47
+ if (kind === "approval") {
48
+ if (Array.isArray(options) && options.length > 0) {
49
+ return options.map((opt, index) => normalizeOption(opt, index)).filter(Boolean);
50
+ }
51
+ return [
52
+ { key: "yes", label: "Yes" },
53
+ { key: "no", label: "No" },
54
+ ];
55
+ }
56
+ if (kind === "choice") {
57
+ const list = Array.isArray(options) ? options : [];
58
+ return list.map((opt, index) => normalizeOption(opt, index)).filter(Boolean);
59
+ }
60
+ return [];
61
+ }
62
+
63
+ function normalizeOption(opt, index = 0) {
64
+ if (typeof opt === "string") {
65
+ const label = String(opt || "").trim();
66
+ if (!label) return null;
67
+ return { key: String(index + 1), label };
68
+ }
69
+ if (!opt || typeof opt !== "object") return null;
70
+ const label = String(opt.label || opt.text || opt.title || "").trim();
71
+ const key = String(opt.key || opt.id || index + 1).trim();
72
+ if (!label && !key) return null;
73
+ return { key: key || String(index + 1), label: label || key };
74
+ }
75
+
76
+ /**
77
+ * Create a pending interaction. Caller must attach resume.call so the answer
78
+ * can be written as the deferred ask_user tool_result.
79
+ */
80
+ function requestUserInteraction(executionState = null, input = {}) {
81
+ const state = ensureInteractionState(executionState);
82
+ if (state.pendingUserInteraction) {
83
+ return {
84
+ ok: false,
85
+ status: "rejected",
86
+ code: "INTERACTION_ALREADY_PENDING",
87
+ error: "Another user interaction is already pending",
88
+ interactionId: state.pendingUserInteraction.id,
89
+ executionState: state,
90
+ };
91
+ }
92
+
93
+ let kind = String(input.kind || input.type || "chat").trim().toLowerCase();
94
+ if (kind === "yes_no" || kind === "yesno" || kind === "confirm") kind = "approval";
95
+ if (kind === "select" || kind === "options") kind = "choice";
96
+ if (!INTERACTION_KINDS.includes(kind)) kind = "chat";
97
+
98
+ const prompt = String(input.prompt || input.question || input.message || "").trim();
99
+ if (!prompt) {
100
+ return {
101
+ ok: false,
102
+ status: "rejected",
103
+ code: "MISSING_PROMPT",
104
+ error: "prompt is required",
105
+ executionState: state,
106
+ };
107
+ }
108
+
109
+ const options = normalizeOptions(kind, input.options);
110
+ if (kind === "choice" && options.length < 2) {
111
+ return {
112
+ ok: false,
113
+ status: "rejected",
114
+ code: "CHOICE_REQUIRES_OPTIONS",
115
+ error: "choice requires at least 2 options",
116
+ executionState: state,
117
+ };
118
+ }
119
+
120
+ const interaction = {
121
+ id: createInteractionId(),
122
+ kind,
123
+ prompt,
124
+ options,
125
+ allowFreeChat: input.allowFreeChat !== false,
126
+ origin: input.origin && typeof input.origin === "object"
127
+ ? { ...input.origin }
128
+ : { type: "ask_user" },
129
+ resume: input.resume && typeof input.resume === "object" ? { ...input.resume } : null,
130
+ createdAt: new Date().toISOString(),
131
+ };
132
+
133
+ state.pendingUserInteraction = interaction;
134
+ return {
135
+ ok: true,
136
+ status: "waiting_user",
137
+ waiting_user: true,
138
+ interactionId: interaction.id,
139
+ kind: interaction.kind,
140
+ // Model-facing wait ack: no answer yet; question stays in tool args only.
141
+ summary: "Waiting for user response",
142
+ executionState: state,
143
+ };
144
+ }
145
+
146
+ /**
147
+ * Sync pending approval UI from plan-graph checkpoint yield.
148
+ * No ask_user tool call — answer will be a short contiguous user message
149
+ * referencing nodeId only (question already in waitingFor / prior tool output).
150
+ */
151
+ function syncInteractionFromPlanGraph(executionState = null) {
152
+ const state = ensureInteractionState(executionState);
153
+ if (state.pendingUserInteraction) return state.pendingUserInteraction;
154
+
155
+ const pg = state.planGraph && typeof state.planGraph === "object" ? state.planGraph : null;
156
+ const waiting = pg && pg.waitingFor && typeof pg.waitingFor === "object" ? pg.waitingFor : null;
157
+ if (!waiting || waiting.type !== "checkpoint") return null;
158
+ if (String(pg.lastYieldReason || "") !== "approval_required" && waiting.mode !== "approval") {
159
+ return null;
160
+ }
161
+
162
+ const reason = String(waiting.reason || "Approval required").trim() || "Approval required";
163
+ const created = requestUserInteraction(state, {
164
+ kind: "approval",
165
+ prompt: reason,
166
+ origin: {
167
+ type: "checkpoint",
168
+ nodeId: waiting.id || "",
169
+ graphId: pg.graphId || "",
170
+ },
171
+ resume: { mode: "checkpoint" },
172
+ });
173
+ return created.ok ? state.pendingUserInteraction : null;
174
+ }
175
+
176
+ function parseUserInteractionInput(pending = null, rawText = "") {
177
+ const text = String(rawText || "").trim();
178
+ if (!pending || !text) {
179
+ return { ok: false, code: "EMPTY", error: "empty answer" };
180
+ }
181
+
182
+ const kind = String(pending.kind || "chat");
183
+ const options = Array.isArray(pending.options) ? pending.options : [];
184
+ const lowered = text.toLowerCase();
185
+
186
+ if (kind === "approval" || kind === "choice") {
187
+ // Exact key match
188
+ const byKey = options.find((opt) => String(opt.key).toLowerCase() === lowered);
189
+ if (byKey) {
190
+ return {
191
+ ok: true,
192
+ answerKind: "option",
193
+ selected: byKey.key,
194
+ label: byKey.label,
195
+ };
196
+ }
197
+ // Approval aliases
198
+ if (kind === "approval") {
199
+ if (["y", "yes", "ok", "okay", "approve", "approved", "是", "好", "同意"].includes(lowered)) {
200
+ const yes = options.find((o) => String(o.key).toLowerCase() === "yes") || options[0];
201
+ return {
202
+ ok: true,
203
+ answerKind: "option",
204
+ selected: yes ? yes.key : "yes",
205
+ label: yes ? yes.label : "Yes",
206
+ };
207
+ }
208
+ if (["n", "no", "reject", "rejected", "cancel", "denied", "否", "不", "取消"].includes(lowered)) {
209
+ const no = options.find((o) => String(o.key).toLowerCase() === "no") || options[1] || options[0];
210
+ return {
211
+ ok: true,
212
+ answerKind: "option",
213
+ selected: no ? no.key : "no",
214
+ label: no ? no.label : "No",
215
+ };
216
+ }
217
+ }
218
+ // Numeric index for choice (1-based)
219
+ if (/^\d+$/.test(text)) {
220
+ const index = Number(text) - 1;
221
+ if (index >= 0 && index < options.length) {
222
+ return {
223
+ ok: true,
224
+ answerKind: "option",
225
+ selected: options[index].key,
226
+ label: options[index].label,
227
+ };
228
+ }
229
+ }
230
+ // Label match (case-insensitive)
231
+ const byLabel = options.find((opt) => String(opt.label).toLowerCase() === lowered);
232
+ if (byLabel) {
233
+ return {
234
+ ok: true,
235
+ answerKind: "option",
236
+ selected: byLabel.key,
237
+ label: byLabel.label,
238
+ };
239
+ }
240
+
241
+ if (pending.allowFreeChat !== false) {
242
+ return { ok: true, answerKind: "chat", text };
243
+ }
244
+ return {
245
+ ok: false,
246
+ code: "INVALID_OPTION",
247
+ error: kind === "approval"
248
+ ? "Reply yes/no, or type a free-text answer"
249
+ : "Reply with an option number/key, or type a free-text answer",
250
+ };
251
+ }
252
+
253
+ // kind === chat
254
+ return { ok: true, answerKind: "chat", text };
255
+ }
256
+
257
+ /**
258
+ * Answer-only payload for the model (no question echo).
259
+ * Stays adjacent to ask_user via tool_result, or as a short follow-up for checkpoint.
260
+ */
261
+ function buildAnswerPayload(pending = null, parsed = {}) {
262
+ const base = {
263
+ type: "user_answer",
264
+ interactionId: pending && pending.id ? pending.id : "",
265
+ kind: pending && pending.kind ? pending.kind : "chat",
266
+ ok: true,
267
+ };
268
+ if (parsed.answerKind === "option") {
269
+ return {
270
+ ...base,
271
+ answerKind: "option",
272
+ selected: parsed.selected,
273
+ label: parsed.label || "",
274
+ };
275
+ }
276
+ return {
277
+ ...base,
278
+ answerKind: "chat",
279
+ text: String(parsed.text || "").trim(),
280
+ };
281
+ }
282
+
283
+ function applyCheckpointDecision(executionState = null, pending = null, parsed = {}) {
284
+ const state = ensureInteractionState(executionState);
285
+ const nodeId = pending && pending.origin && pending.origin.nodeId
286
+ ? String(pending.origin.nodeId)
287
+ : "";
288
+ const pg = state.planGraph;
289
+ if (!pg || !Array.isArray(pg.nodes) || !nodeId) {
290
+ return { ok: false, code: "CHECKPOINT_MISSING" };
291
+ }
292
+ const idx = pg.nodes.findIndex((n) => n && n.id === nodeId);
293
+ if (idx < 0) return { ok: false, code: "CHECKPOINT_MISSING" };
294
+ const node = pg.nodes[idx];
295
+ if (node.type !== "checkpoint") return { ok: false, code: "NOT_CHECKPOINT" };
296
+
297
+ const approved = parsed.answerKind === "option"
298
+ && ["yes", "y", "ok", "approve", "approved"].includes(String(parsed.selected || "").toLowerCase());
299
+ const rejected = parsed.answerKind === "option"
300
+ && ["no", "n", "reject", "rejected", "cancel", "denied"].includes(String(parsed.selected || "").toLowerCase());
301
+
302
+ if (parsed.answerKind === "chat") {
303
+ // Free-text on approval checkpoint: leave node waiting; agent decides via contiguous answer msg.
304
+ return { ok: true, advanced: false, chatOverride: true };
305
+ }
306
+
307
+ if (approved) {
308
+ pg.nodes[idx] = {
309
+ ...node,
310
+ status: "succeeded",
311
+ result: {
312
+ ok: true,
313
+ summary: `approved (${parsed.selected})`,
314
+ output: { selected: parsed.selected, label: parsed.label || "" },
315
+ },
316
+ error: "",
317
+ };
318
+ } else if (rejected) {
319
+ pg.nodes[idx] = {
320
+ ...node,
321
+ status: "cancelled",
322
+ result: {
323
+ ok: false,
324
+ summary: `rejected (${parsed.selected})`,
325
+ output: { selected: parsed.selected, label: parsed.label || "" },
326
+ },
327
+ error: "",
328
+ };
329
+ } else {
330
+ return { ok: false, code: "UNKNOWN_DECISION" };
331
+ }
332
+
333
+ pg.waitingFor = null;
334
+ pg.lastYieldReason = "";
335
+ const advanced = advanceStoredGraph(pg, { autoAdvance: false });
336
+ if (advanced && advanced.planGraph) {
337
+ state.planGraph = {
338
+ ...advanced.planGraph,
339
+ commandLog: pg.commandLog || {},
340
+ };
341
+ if (state.graphs && state.planGraph.graphId) {
342
+ state.graphs[state.planGraph.graphId] = state.planGraph;
343
+ }
344
+ }
345
+ return { ok: true, advanced: true, approved: Boolean(approved) };
346
+ }
347
+
348
+ /**
349
+ * Resolve user text against pending interaction.
350
+ * Returns answer payload + how to continue the agent loop.
351
+ */
352
+ function resolveUserInteraction(executionState = null, rawText = "") {
353
+ const state = ensureInteractionState(executionState);
354
+ const pending = getPendingUserInteraction(state);
355
+ if (!pending) {
356
+ return { ok: false, code: "NO_PENDING", error: "no pending user interaction" };
357
+ }
358
+
359
+ const parsed = parseUserInteractionInput(pending, rawText);
360
+ if (!parsed.ok) {
361
+ return { ok: false, code: parsed.code, error: parsed.error, pending };
362
+ }
363
+
364
+ const answer = buildAnswerPayload(pending, parsed);
365
+ const resume = pending.resume && typeof pending.resume === "object" ? { ...pending.resume } : null;
366
+ const origin = pending.origin || {};
367
+
368
+ let checkpoint = null;
369
+ if (resume && resume.mode === "checkpoint") {
370
+ checkpoint = applyCheckpointDecision(state, pending, parsed);
371
+ }
372
+
373
+ clearPendingUserInteraction(state);
374
+
375
+ return {
376
+ ok: true,
377
+ answer,
378
+ parsed,
379
+ pendingSnapshot: pending,
380
+ resume,
381
+ origin,
382
+ checkpoint,
383
+ // ask_user path: write answer as deferred tool_result (contiguous).
384
+ // checkpoint path without tool: short user message with answer only.
385
+ continueMode: resume && resume.toolCallId ? "tool_result" : "user_message",
386
+ executionState: state,
387
+ };
388
+ }
389
+
390
+ function formatInteractionPromptLines(pending = null) {
391
+ if (!pending) return [];
392
+ const lines = [];
393
+ const kind = pending.kind || "chat";
394
+ if (kind === "approval") {
395
+ lines.push(`Approval: ${pending.prompt}`);
396
+ lines.push(" [yes] Yes [no] No or type a free-text reply");
397
+ } else if (kind === "choice") {
398
+ lines.push(`Choice: ${pending.prompt}`);
399
+ for (const opt of pending.options || []) {
400
+ lines.push(` [${opt.key}] ${opt.label}`);
401
+ }
402
+ if (pending.allowFreeChat !== false) {
403
+ lines.push(" or type a free-text reply");
404
+ }
405
+ } else {
406
+ lines.push(`Question: ${pending.prompt}`);
407
+ lines.push(" (type your reply)");
408
+ }
409
+ return lines;
410
+ }
411
+
412
+ function runAskUserTool(args = {}, options = {}) {
413
+ const executionState = options.executionState;
414
+ const result = requestUserInteraction(executionState, {
415
+ kind: args.kind || args.type || "chat",
416
+ prompt: args.prompt || args.question || args.message,
417
+ options: args.options,
418
+ allowFreeChat: args.allowFreeChat !== false,
419
+ origin: { type: "ask_user" },
420
+ // resume.call filled by nativeRunner after it knows tool_call id
421
+ resume: options.resume || null,
422
+ });
423
+ return {
424
+ ...result,
425
+ // Keep tool wait ack tiny — question is only in args.
426
+ modelPayload: result.ok
427
+ ? {
428
+ ok: true,
429
+ status: "waiting_user",
430
+ interactionId: result.interactionId,
431
+ kind: result.kind,
432
+ }
433
+ : {
434
+ ok: false,
435
+ status: "rejected",
436
+ error: result.error || "ask_user rejected",
437
+ code: result.code || "",
438
+ },
439
+ };
440
+ }
441
+
442
+ module.exports = {
443
+ INTERACTION_KINDS,
444
+ createInteractionId,
445
+ ensureInteractionState,
446
+ getPendingUserInteraction,
447
+ hasPendingUserInteraction,
448
+ clearPendingUserInteraction,
449
+ requestUserInteraction,
450
+ syncInteractionFromPlanGraph,
451
+ parseUserInteractionInput,
452
+ buildAnswerPayload,
453
+ resolveUserInteraction,
454
+ formatInteractionPromptLines,
455
+ runAskUserTool,
456
+ applyCheckpointDecision,
457
+ };
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Unified user interjection (nudge) queue for the native agent loop.
5
+ *
6
+ * While a task is running, user submits enqueue here. nativeRunner drains
7
+ * before each LLM turn and appends a labeled additional user reminder.
8
+ */
9
+
10
+ function ensurePendingUserPrompts(executionState = null) {
11
+ const state = executionState && typeof executionState === "object"
12
+ ? executionState
13
+ : {};
14
+ if (!Array.isArray(state.pendingUserPrompts)) {
15
+ state.pendingUserPrompts = [];
16
+ }
17
+ return state;
18
+ }
19
+
20
+ function enqueueUserPrompt(executionState = null, text = "") {
21
+ const state = ensurePendingUserPrompts(executionState);
22
+ const value = String(text || "").trim();
23
+ if (!value) {
24
+ return { ok: false, enqueued: false, reason: "empty", executionState: state };
25
+ }
26
+ state.pendingUserPrompts.push({
27
+ text: value,
28
+ at: new Date().toISOString(),
29
+ });
30
+ return {
31
+ ok: true,
32
+ enqueued: true,
33
+ count: state.pendingUserPrompts.length,
34
+ executionState: state,
35
+ };
36
+ }
37
+
38
+ /**
39
+ * Atomically take all pending prompts and clear the queue.
40
+ * @returns {string[]}
41
+ */
42
+ function drainUserPrompts(executionState = null) {
43
+ const state = ensurePendingUserPrompts(executionState);
44
+ if (state.pendingUserPrompts.length === 0) return [];
45
+ const texts = state.pendingUserPrompts.map((entry) => String(entry.text || "").trim()).filter(Boolean);
46
+ state.pendingUserPrompts = [];
47
+ return texts;
48
+ }
49
+
50
+ function clearUserPrompts(executionState = null) {
51
+ const state = ensurePendingUserPrompts(executionState);
52
+ state.pendingUserPrompts = [];
53
+ return state;
54
+ }
55
+
56
+ function hasPendingUserPrompts(executionState = null) {
57
+ const state = ensurePendingUserPrompts(executionState);
58
+ return state.pendingUserPrompts.length > 0;
59
+ }
60
+
61
+ function shouldFrameAsUserReminder(executionState = null) {
62
+ if (!executionState || typeof executionState !== "object") return false;
63
+ if (executionState.planMode === true) return true;
64
+ const waiting = executionState.planGraph && executionState.planGraph.waitingFor;
65
+ return Boolean(waiting && waiting.id);
66
+ }
67
+
68
+ function formatUserReminderMessage(texts = [], { waitingFor = null } = {}) {
69
+ const lines = Array.isArray(texts)
70
+ ? texts.map((t) => String(t || "").trim()).filter(Boolean)
71
+ : [String(texts || "").trim()].filter(Boolean);
72
+ if (lines.length === 0) return "";
73
+
74
+ const parts = ["User reminder (additional prompt):"];
75
+ if (lines.length === 1) {
76
+ parts.push(lines[0]);
77
+ } else {
78
+ lines.forEach((line, index) => {
79
+ parts.push(`${index + 1}. ${line}`);
80
+ });
81
+ }
82
+ if (waitingFor && waitingFor.id) {
83
+ const label = waitingFor.title || waitingFor.objective || waitingFor.reason || waitingFor.id;
84
+ parts.push(
85
+ `Prefer serving the current waiting ${waitingFor.type || "node"}: ${waitingFor.id}`
86
+ + (label && label !== waitingFor.id ? ` — ${label}` : "")
87
+ + ". Do not start an unrelated objective.",
88
+ );
89
+ }
90
+ return parts.join("\n");
91
+ }
92
+
93
+ /**
94
+ * Idle + plan waiting: wrap a new user message as continuation reminder.
95
+ */
96
+ function buildContinuationUserPrompt(userText = "", executionState = null) {
97
+ const text = String(userText || "").trim();
98
+ if (!text) return "";
99
+ const waiting = executionState
100
+ && executionState.planGraph
101
+ && executionState.planGraph.waitingFor
102
+ ? executionState.planGraph.waitingFor
103
+ : null;
104
+ return formatUserReminderMessage([text], { waitingFor: waiting });
105
+ }
106
+
107
+ module.exports = {
108
+ ensurePendingUserPrompts,
109
+ enqueueUserPrompt,
110
+ drainUserPrompts,
111
+ clearUserPrompts,
112
+ hasPendingUserPrompts,
113
+ shouldFrameAsUserReminder,
114
+ formatUserReminderMessage,
115
+ buildContinuationUserPrompt,
116
+ };
@@ -1,10 +1,22 @@
1
+ "use strict";
2
+
1
3
  const { runReadTool } = require("./tools/read");
2
4
  const { runWriteTool } = require("./tools/write");
3
5
  const { runEditTool } = require("./tools/edit");
4
6
  const { runBashTool } = require("./tools/bash");
5
7
  const { runArtifactReadTool } = require("./tools/artifactRead");
8
+ const { runPlanGraphTool } = require("./tools/planGraph");
9
+ const { runAskUserTool } = require("./tools/askUser");
6
10
 
7
- const TOOL_NAMES = ["read", "write", "edit", "bash", "artifact_read"];
11
+ const TOOL_NAMES = [
12
+ "read",
13
+ "write",
14
+ "edit",
15
+ "bash",
16
+ "artifact_read",
17
+ "plan_graph",
18
+ "ask_user",
19
+ ];
8
20
 
9
21
  function normalizeToolName(value = "") {
10
22
  const text = String(value || "").trim().toLowerCase();
@@ -13,6 +25,8 @@ function normalizeToolName(value = "") {
13
25
  if (text === "edit") return "edit";
14
26
  if (text === "bash") return "bash";
15
27
  if (text === "artifact_read" || text === "artifact-read" || text === "artifactread") return "artifact_read";
28
+ if (text === "plan_graph" || text === "plan-graph" || text === "plangraph") return "plan_graph";
29
+ if (text === "ask_user" || text === "ask-user" || text === "askuser") return "ask_user";
16
30
  return "";
17
31
  }
18
32
 
@@ -30,6 +44,8 @@ function runToolCall(input = {}, options = {}) {
30
44
  if (tool === "write") return runWriteTool(args, options);
31
45
  if (tool === "edit") return runEditTool(args, options);
32
46
  if (tool === "artifact_read") return runArtifactReadTool(args, options);
47
+ if (tool === "plan_graph") return runPlanGraphTool(args, options);
48
+ if (tool === "ask_user") return runAskUserTool(args, options);
33
49
  return runBashTool(args, options);
34
50
  }
35
51
 
package/src/code/index.js CHANGED
@@ -18,6 +18,7 @@ const {
18
18
  runUcodeCoreAgent,
19
19
  runSingleCommand,
20
20
  runNaturalLanguageTask,
21
+ resumeAfterUserInteraction,
21
22
  formatNlResult,
22
23
  resolvePlannerProvider,
23
24
  parseAgentArgs,
@@ -60,6 +61,7 @@ module.exports = {
60
61
  runUcodeCoreAgent,
61
62
  runSingleCommand,
62
63
  runNaturalLanguageTask,
64
+ resumeAfterUserInteraction,
63
65
  formatNlResult,
64
66
  resolvePlannerProvider,
65
67
  parseAgentArgs,