u-foo 2.5.14 → 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 (59) hide show
  1. package/package.json +1 -1
  2. package/src/agents/prompts/native/environment.js +20 -8
  3. package/src/code/agent.js +517 -112
  4. package/src/code/commands.js +77 -0
  5. package/src/code/context/artifactGc.js +292 -0
  6. package/src/code/context/artifactIndex.js +161 -0
  7. package/src/code/context/artifacts.js +183 -0
  8. package/src/code/context/assembler.js +703 -0
  9. package/src/code/context/executionSegment.js +292 -0
  10. package/src/code/context/index.js +28 -0
  11. package/src/code/context/planGraph.js +1410 -0
  12. package/src/code/context/planGraphService.js +857 -0
  13. package/src/code/context/planMode.js +398 -0
  14. package/src/code/context/planProjection.js +432 -0
  15. package/src/code/context/projectSnapshot.js +201 -0
  16. package/src/code/context/promptLayers.js +175 -0
  17. package/src/code/context/reducers.js +328 -0
  18. package/src/code/context/stableJson.js +29 -0
  19. package/src/code/context/stateCommit.js +414 -0
  20. package/src/code/context/toolRuntime.js +172 -0
  21. package/src/code/context/transcript.js +182 -0
  22. package/src/code/context/transcriptSync.js +106 -0
  23. package/src/code/context/userInteraction.js +457 -0
  24. package/src/code/context/userNudge.js +116 -0
  25. package/src/code/context/workingSet.js +323 -0
  26. package/src/code/dispatch.js +20 -1
  27. package/src/code/index.js +8 -0
  28. package/src/code/modelCommand.js +87 -0
  29. package/src/code/nativeRunner.js +625 -34
  30. package/src/code/repl.js +196 -50
  31. package/src/code/runtime/agentWakeup.js +58 -0
  32. package/src/code/runtime/graphOwner.js +41 -0
  33. package/src/code/runtime/graphYieldRouter.js +42 -0
  34. package/src/code/runtime/index.js +15 -0
  35. package/src/code/runtime/loopMailbox.js +124 -0
  36. package/src/code/runtime/runtimeEvents.js +39 -0
  37. package/src/code/runtime/taskControl.js +565 -0
  38. package/src/code/runtime/taskFocus.js +165 -0
  39. package/src/code/runtime/taskLoop.js +383 -0
  40. package/src/code/runtime/taskRun.js +187 -0
  41. package/src/code/runtime/toolProvenance.js +70 -0
  42. package/src/code/runtime/workspaceLease.js +208 -0
  43. package/src/code/sessionStore.js +217 -15
  44. package/src/code/skills/index.js +10 -0
  45. package/src/code/skills/injection.js +66 -3
  46. package/src/code/skills/loader.js +21 -0
  47. package/src/code/skills/manifest.js +87 -0
  48. package/src/code/skills/render.js +15 -1
  49. package/src/code/taskDecomposer.js +56 -2
  50. package/src/code/tools/artifactRead.js +40 -0
  51. package/src/code/tools/askUser.js +11 -0
  52. package/src/code/tools/planGraph.js +29 -0
  53. package/src/code/tui.js +2 -0
  54. package/src/code/usageStore.js +15 -0
  55. package/src/ui/format/index.js +285 -45
  56. package/src/ui/format/markdownRenderer.js +436 -71
  57. package/src/ui/ink/ChatApp.js +39 -8
  58. package/src/ui/ink/UcodeApp.js +592 -43
  59. package/src/ui/ink/chatLogModel.js +102 -21
@@ -0,0 +1,565 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * plan_graph control-plane actions:
5
+ * start_task / cancel_task / fail_task / complete_task / skip_node / cancel_subtree.
6
+ *
7
+ * complete_task:
8
+ * - taskRunId → owning TaskLoop submitting TaskRun result
9
+ * - nodeId → Graph owner completing waiting_llm inline_llm/expand work
10
+ */
11
+
12
+ const { agentLoopOwner } = require("./graphOwner");
13
+ const {
14
+ createTaskRun,
15
+ putTaskRun,
16
+ findActiveTaskRunForNode,
17
+ getTaskRun,
18
+ casTaskRunStatus,
19
+ isTerminalTaskRun,
20
+ getCachedControlCommand,
21
+ cacheControlCommand,
22
+ listActiveWritingTaskRuns,
23
+ } = require("./taskRun");
24
+ const {
25
+ createChildGraphState,
26
+ setGraph,
27
+ getGraph,
28
+ ensureGraphs,
29
+ processTaskRun,
30
+ finalizeFailure,
31
+ syncParentNodeFromRun,
32
+ } = require("./taskLoop");
33
+ const { enqueueTaskEvent } = require("./loopMailbox");
34
+ const { getExecutionKind } = require("./taskFocus");
35
+ const {
36
+ countWriteLeases,
37
+ MAX_CONCURRENT_WRITE_LEASES,
38
+ } = require("./workspaceLease");
39
+ const { applyControlNodeAction } = require("../context/planGraph");
40
+
41
+ function findParentNode(executionState = null, nodeId = "") {
42
+ ensureGraphs(executionState);
43
+ const parent = executionState.planGraph;
44
+ if (!parent || !Array.isArray(parent.nodes)) return { parent: null, node: null };
45
+ const node = parent.nodes.find((n) => n && n.id === nodeId) || null;
46
+ return { parent, node };
47
+ }
48
+
49
+ function dependenciesSatisfied(parent = null, node = null) {
50
+ if (!node) return { ok: false, dependencies: [] };
51
+ const byId = new Map((parent.nodes || []).map((n) => [n.id, n]));
52
+ const unmet = [];
53
+ for (const depId of node.dependsOn || []) {
54
+ const dep = byId.get(depId);
55
+ if (!dep || dep.status !== "succeeded") {
56
+ unmet.push({ id: depId, status: dep ? dep.status : "missing" });
57
+ }
58
+ }
59
+ return { ok: unmet.length === 0, dependencies: unmet };
60
+ }
61
+
62
+ function startTask(executionState = null, {
63
+ nodeId = "",
64
+ commandId = "",
65
+ runTool = null,
66
+ knownTools = null,
67
+ processImmediately = true,
68
+ } = {}) {
69
+ const cached = getCachedControlCommand(executionState, commandId);
70
+ if (cached) return { ...cached, idempotentReplay: true };
71
+
72
+ const id = String(nodeId || "").trim();
73
+ const { parent, node } = findParentNode(executionState, id);
74
+ if (!parent || !node) {
75
+ return {
76
+ status: "rejected",
77
+ ok: false,
78
+ errors: [{ code: "NODE_NOT_FOUND", message: `task node missing: ${id}` }],
79
+ };
80
+ }
81
+ if (node.type !== "task") {
82
+ return {
83
+ status: "rejected",
84
+ ok: false,
85
+ errors: [{ code: "NOT_A_TASK", message: `node ${id} is not a task` }],
86
+ };
87
+ }
88
+ const kind = getExecutionKind(node);
89
+ if (kind !== "task_loop") {
90
+ return {
91
+ status: "rejected",
92
+ ok: false,
93
+ errors: [{
94
+ code: "NOT_TASK_LOOP",
95
+ message: `node ${id} execution.kind must be task_loop (got ${kind})`,
96
+ }],
97
+ };
98
+ }
99
+
100
+ const active = findActiveTaskRunForNode(executionState, id);
101
+ if (active) {
102
+ const payload = {
103
+ status: "already_running",
104
+ ok: true,
105
+ graphId: parent.graphId || "",
106
+ nodeId: id,
107
+ taskRunId: active.id,
108
+ childGraphId: active.childGraphId,
109
+ parentNodeStatus: "running",
110
+ };
111
+ cacheControlCommand(executionState, commandId, payload);
112
+ return payload;
113
+ }
114
+
115
+ const deps = dependenciesSatisfied(parent, node);
116
+ if (!deps.ok) {
117
+ return {
118
+ status: "rejected",
119
+ ok: false,
120
+ errors: [{
121
+ code: "DEPENDENCIES_NOT_SATISFIED",
122
+ message: `dependencies not succeeded for ${id}`,
123
+ dependencies: deps.dependencies,
124
+ }],
125
+ };
126
+ }
127
+
128
+ const activeCount = listActiveWritingTaskRuns(executionState).length;
129
+ const leaseCount = countWriteLeases(executionState);
130
+ if (activeCount >= MAX_CONCURRENT_WRITE_LEASES || leaseCount >= MAX_CONCURRENT_WRITE_LEASES) {
131
+ return {
132
+ status: "rejected",
133
+ ok: false,
134
+ errors: [{
135
+ code: "MAX_CONCURRENT_TASKS",
136
+ message: `At most ${MAX_CONCURRENT_WRITE_LEASES} concurrent writing TaskRuns`,
137
+ max: MAX_CONCURRENT_WRITE_LEASES,
138
+ current: Math.max(activeCount, leaseCount),
139
+ }],
140
+ };
141
+ }
142
+
143
+ // Freeze spec snapshot on node.runtime
144
+ if (!node.runtime || typeof node.runtime !== "object") node.runtime = {};
145
+ node.runtime.specFrozen = {
146
+ objective: node.objective,
147
+ title: node.title,
148
+ dependsOn: (node.dependsOn || []).slice(),
149
+ execution: node.execution && typeof node.execution === "object"
150
+ ? JSON.parse(JSON.stringify(node.execution))
151
+ : { kind: "task_loop" },
152
+ };
153
+
154
+ const run = createTaskRun({
155
+ parentGraphId: parent.graphId || "",
156
+ parentNodeId: id,
157
+ attempt: (Number(node.attempt) || 0) + 1,
158
+ });
159
+ const child = createChildGraphState({
160
+ parentGraphId: parent.graphId || "",
161
+ parentNodeId: id,
162
+ taskRunId: run.id,
163
+ objective: node.objective || node.title || id,
164
+ });
165
+ run.childGraphId = child.graphId;
166
+ putTaskRun(executionState, run);
167
+ setGraph(executionState, child);
168
+
169
+ // Ensure parent has owner
170
+ if (!parent.owner) parent.owner = agentLoopOwner();
171
+ node.status = "running";
172
+ node.attempt = run.attempt;
173
+ syncParentNodeFromRun(executionState, run);
174
+
175
+ const payload = {
176
+ status: "started",
177
+ ok: true,
178
+ graphId: parent.graphId || "",
179
+ nodeId: id,
180
+ taskRunId: run.id,
181
+ childGraphId: child.graphId,
182
+ parentNodeStatus: "running",
183
+ };
184
+ cacheControlCommand(executionState, commandId, payload);
185
+
186
+ enqueueTaskEvent(executionState, run.id, { kind: "advance" });
187
+
188
+ if (processImmediately) {
189
+ processTaskRun(executionState, run.id, { runTool, knownTools });
190
+ }
191
+
192
+ return payload;
193
+ }
194
+
195
+ function cancelTask(executionState = null, {
196
+ nodeId = "",
197
+ reason = "",
198
+ commandId = "",
199
+ } = {}) {
200
+ const cached = getCachedControlCommand(executionState, commandId);
201
+ if (cached) return { ...cached, idempotentReplay: true };
202
+
203
+ const id = String(nodeId || "").trim();
204
+ const active = findActiveTaskRunForNode(executionState, id);
205
+ if (!active) {
206
+ const { node } = findParentNode(executionState, id);
207
+ if (node && (node.status === "succeeded" || node.status === "failed" || node.status === "cancelled")) {
208
+ return {
209
+ status: "rejected",
210
+ ok: false,
211
+ errors: [{
212
+ code: "TASK_ALREADY_TERMINAL",
213
+ message: `task ${id} already ${node.status}`,
214
+ currentStatus: node.status,
215
+ }],
216
+ };
217
+ }
218
+ return {
219
+ status: "rejected",
220
+ ok: false,
221
+ errors: [{ code: "TASK_NOT_RUNNING", message: `no active run for ${id}` }],
222
+ };
223
+ }
224
+
225
+ if (isTerminalTaskRun(active)) {
226
+ return {
227
+ status: "rejected",
228
+ ok: false,
229
+ errors: [{
230
+ code: "TASK_ALREADY_TERMINAL",
231
+ message: `task run already ${active.status}`,
232
+ currentStatus: active.status,
233
+ }],
234
+ };
235
+ }
236
+
237
+ casTaskRunStatus(executionState, active.id, {
238
+ expectedStatus: active.status,
239
+ nextStatus: "cancelling",
240
+ error: { code: "TASK_CANCELLED", message: String(reason || "cancelled") },
241
+ });
242
+ enqueueTaskEvent(executionState, active.id, {
243
+ kind: "control",
244
+ op: "cancel_task",
245
+ reason: String(reason || ""),
246
+ });
247
+ const done = finalizeFailure(executionState, active.id, {
248
+ code: "TASK_CANCELLED",
249
+ message: String(reason || "cancelled"),
250
+ }, "cancelling");
251
+
252
+ const payload = {
253
+ status: "accepted",
254
+ ok: Boolean(done.ok),
255
+ nodeId: id,
256
+ taskRunId: active.id,
257
+ parentNodeStatus: done.run ? done.run.status : "cancelled",
258
+ };
259
+ cacheControlCommand(executionState, commandId, payload);
260
+ return payload;
261
+ }
262
+
263
+ function failTask(executionState = null, {
264
+ nodeId = "",
265
+ reason = "",
266
+ commandId = "",
267
+ } = {}) {
268
+ const cached = getCachedControlCommand(executionState, commandId);
269
+ if (cached) return { ...cached, idempotentReplay: true };
270
+
271
+ const id = String(nodeId || "").trim();
272
+ const active = findActiveTaskRunForNode(executionState, id);
273
+ if (!active) {
274
+ const { node } = findParentNode(executionState, id);
275
+ if (node && (node.status === "succeeded" || node.status === "failed" || node.status === "cancelled")) {
276
+ return {
277
+ status: "rejected",
278
+ ok: false,
279
+ errors: [{
280
+ code: "TASK_ALREADY_TERMINAL",
281
+ message: `task ${id} already ${node.status}`,
282
+ currentStatus: node.status,
283
+ }],
284
+ };
285
+ }
286
+ return {
287
+ status: "rejected",
288
+ ok: false,
289
+ errors: [{ code: "TASK_NOT_RUNNING", message: `no active run for ${id}` }],
290
+ };
291
+ }
292
+ if (isTerminalTaskRun(active)) {
293
+ return {
294
+ status: "rejected",
295
+ ok: false,
296
+ errors: [{
297
+ code: "TASK_ALREADY_TERMINAL",
298
+ currentStatus: active.status,
299
+ }],
300
+ };
301
+ }
302
+
303
+ casTaskRunStatus(executionState, active.id, {
304
+ expectedStatus: active.status === "cancelling" ? "cancelling" : "running",
305
+ nextStatus: "cancelling",
306
+ error: { code: "TASK_FAILED", message: String(reason || "failed") },
307
+ });
308
+ const done = finalizeFailure(executionState, active.id, {
309
+ code: "TASK_FAILED",
310
+ message: String(reason || "failed"),
311
+ }, "cancelling");
312
+
313
+ const payload = {
314
+ status: done.ok ? "accepted" : "rejected",
315
+ ok: Boolean(done.ok),
316
+ nodeId: id,
317
+ taskRunId: active.id,
318
+ parentNodeStatus: done.run ? done.run.status : "failed",
319
+ errors: done.ok ? undefined : [{ code: done.code || "CAS_FAILED", currentStatus: done.currentStatus }],
320
+ };
321
+ cacheControlCommand(executionState, commandId, payload);
322
+ return payload;
323
+ }
324
+
325
+ function completeTaskFromLoop(executionState = null, {
326
+ taskRunId = "",
327
+ result = {},
328
+ commandId = "",
329
+ } = {}) {
330
+ const cached = getCachedControlCommand(executionState, commandId);
331
+ if (cached) return { ...cached, idempotentReplay: true };
332
+
333
+ const run = getTaskRun(executionState, taskRunId);
334
+ if (!run) {
335
+ return {
336
+ status: "rejected",
337
+ ok: false,
338
+ errors: [{ code: "TASK_RUN_NOT_FOUND", message: "task run missing" }],
339
+ };
340
+ }
341
+ enqueueTaskEvent(executionState, run.id, {
342
+ kind: "control",
343
+ op: "complete_task",
344
+ result: result && typeof result === "object" ? result : {},
345
+ });
346
+ const tick = processTaskRun(executionState, run.id, {});
347
+ const live = getTaskRun(executionState, run.id);
348
+ const payload = {
349
+ status: live && live.status === "succeeded" ? "accepted" : "rejected",
350
+ ok: Boolean(live && live.status === "succeeded"),
351
+ taskRunId: run.id,
352
+ parentNodeStatus: live ? live.status : "",
353
+ result: live && live.result,
354
+ tick,
355
+ };
356
+ if (!payload.ok && live && isTerminalTaskRun(live)) {
357
+ payload.errors = [{
358
+ code: "TASK_ALREADY_TERMINAL",
359
+ currentStatus: live.status,
360
+ }];
361
+ }
362
+ cacheControlCommand(executionState, commandId, payload);
363
+ return payload;
364
+ }
365
+
366
+ function completeInlineTask(executionState = null, {
367
+ nodeId = "",
368
+ result = null,
369
+ output = null,
370
+ summary = "",
371
+ commandId = "",
372
+ } = {}) {
373
+ const cached = getCachedControlCommand(executionState, commandId);
374
+ if (cached) return { ...cached, idempotentReplay: true };
375
+
376
+ ensureGraphs(executionState);
377
+ const applied = applyControlNodeAction(executionState.planGraph, {
378
+ op: "complete_task",
379
+ nodeId,
380
+ result,
381
+ output,
382
+ summary,
383
+ });
384
+ if (!applied.ok) {
385
+ return {
386
+ status: "rejected",
387
+ ok: false,
388
+ errors: applied.errors || [{ code: "COMPLETE_REJECTED", message: "complete_task rejected" }],
389
+ };
390
+ }
391
+ const live = (executionState.planGraph.nodes || []).find((n) => n && n.id === nodeId);
392
+ const payload = {
393
+ status: "accepted",
394
+ ok: true,
395
+ nodeId,
396
+ parentNodeStatus: live ? live.status : "succeeded",
397
+ result: live ? live.result : null,
398
+ stateRevision: Number(executionState.planGraph.stateRevision) || 0,
399
+ };
400
+ cacheControlCommand(executionState, commandId, payload);
401
+ return payload;
402
+ }
403
+
404
+ function skipNode(executionState = null, {
405
+ nodeId = "",
406
+ reason = "",
407
+ commandId = "",
408
+ } = {}) {
409
+ const cached = getCachedControlCommand(executionState, commandId);
410
+ if (cached) return { ...cached, idempotentReplay: true };
411
+
412
+ ensureGraphs(executionState);
413
+ const applied = applyControlNodeAction(executionState.planGraph, {
414
+ op: "skip_node",
415
+ nodeId,
416
+ reason,
417
+ });
418
+ if (!applied.ok) {
419
+ return {
420
+ status: "rejected",
421
+ ok: false,
422
+ errors: applied.errors || [{ code: "SKIP_REJECTED", message: "skip_node rejected" }],
423
+ };
424
+ }
425
+ const payload = {
426
+ status: "accepted",
427
+ ok: true,
428
+ nodeId,
429
+ parentNodeStatus: "skipped",
430
+ stateRevision: Number(executionState.planGraph.stateRevision) || 0,
431
+ };
432
+ cacheControlCommand(executionState, commandId, payload);
433
+ return payload;
434
+ }
435
+
436
+ function cancelSubtree(executionState = null, {
437
+ nodeId = "",
438
+ reason = "",
439
+ commandId = "",
440
+ } = {}) {
441
+ const cached = getCachedControlCommand(executionState, commandId);
442
+ if (cached) return { ...cached, idempotentReplay: true };
443
+
444
+ ensureGraphs(executionState);
445
+ const applied = applyControlNodeAction(executionState.planGraph, {
446
+ op: "cancel_subtree",
447
+ nodeId,
448
+ reason,
449
+ });
450
+ if (!applied.ok) {
451
+ return {
452
+ status: "rejected",
453
+ ok: false,
454
+ errors: applied.errors || [{ code: "CANCEL_SUBTREE_REJECTED", message: "cancel_subtree rejected" }],
455
+ };
456
+ }
457
+ const payload = {
458
+ status: "accepted",
459
+ ok: true,
460
+ nodeId,
461
+ parentNodeStatus: "cancelled",
462
+ stateRevision: Number(executionState.planGraph.stateRevision) || 0,
463
+ };
464
+ cacheControlCommand(executionState, commandId, payload);
465
+ return payload;
466
+ }
467
+
468
+ function runControlActions(executionState = null, {
469
+ actions = [],
470
+ commandId = "",
471
+ runTool = null,
472
+ knownTools = null,
473
+ } = {}) {
474
+ const list = Array.isArray(actions) ? actions : [];
475
+ const results = [];
476
+ for (const action of list) {
477
+ const op = String(action && action.op || "").trim().toLowerCase();
478
+ if (op === "start_task") {
479
+ results.push(startTask(executionState, {
480
+ nodeId: action.nodeId,
481
+ commandId: commandId && list.length === 1 ? commandId : `${commandId}:${op}:${action.nodeId}`,
482
+ runTool,
483
+ knownTools,
484
+ }));
485
+ } else if (op === "cancel_task") {
486
+ results.push(cancelTask(executionState, {
487
+ nodeId: action.nodeId,
488
+ reason: action.reason,
489
+ commandId: commandId && list.length === 1 ? commandId : `${commandId}:${op}:${action.nodeId}`,
490
+ }));
491
+ } else if (op === "fail_task" || op === "mark_task_failed") {
492
+ results.push(failTask(executionState, {
493
+ nodeId: action.nodeId,
494
+ reason: action.reason,
495
+ commandId: commandId && list.length === 1 ? commandId : `${commandId}:${op}:${action.nodeId}`,
496
+ }));
497
+ } else if (op === "complete_task") {
498
+ const taskRunId = String(action.taskRunId || "").trim();
499
+ if (taskRunId) {
500
+ results.push(completeTaskFromLoop(executionState, {
501
+ taskRunId,
502
+ result: action.result,
503
+ commandId: commandId && list.length === 1 ? commandId : `${commandId}:${op}:${taskRunId}`,
504
+ }));
505
+ } else {
506
+ results.push(completeInlineTask(executionState, {
507
+ nodeId: action.nodeId,
508
+ result: action.result,
509
+ output: action.output,
510
+ summary: action.summary,
511
+ commandId: commandId && list.length === 1
512
+ ? commandId
513
+ : `${commandId}:${op}:${action.nodeId}`,
514
+ }));
515
+ }
516
+ } else if (op === "skip_node") {
517
+ results.push(skipNode(executionState, {
518
+ nodeId: action.nodeId,
519
+ reason: action.reason,
520
+ commandId: commandId && list.length === 1 ? commandId : `${commandId}:${op}:${action.nodeId}`,
521
+ }));
522
+ } else if (op === "cancel_subtree") {
523
+ results.push(cancelSubtree(executionState, {
524
+ nodeId: action.nodeId,
525
+ reason: action.reason,
526
+ commandId: commandId && list.length === 1 ? commandId : `${commandId}:${op}:${action.nodeId}`,
527
+ }));
528
+ } else if (op === "fail_current_task") {
529
+ results.push(failTask(executionState, {
530
+ nodeId: action.nodeId || (getTaskRun(executionState, action.taskRunId) || {}).parentNodeId,
531
+ reason: action.reason,
532
+ commandId: commandId && list.length === 1 ? commandId : `${commandId}:${op}`,
533
+ }));
534
+ } else {
535
+ results.push({
536
+ status: "rejected",
537
+ ok: false,
538
+ errors: [{ code: "UNKNOWN_CONTROL_OP", message: `unknown control op: ${op}` }],
539
+ });
540
+ }
541
+ }
542
+ const ok = results.every((r) => r && r.ok !== false && r.status !== "rejected");
543
+ const errors = [];
544
+ for (const r of results) {
545
+ if (r && Array.isArray(r.errors)) errors.push(...r.errors);
546
+ }
547
+ return {
548
+ status: ok ? "accepted" : "rejected",
549
+ ok,
550
+ results,
551
+ errors: errors.length ? errors : undefined,
552
+ };
553
+ }
554
+
555
+ module.exports = {
556
+ startTask,
557
+ cancelTask,
558
+ failTask,
559
+ completeTaskFromLoop,
560
+ completeInlineTask,
561
+ skipNode,
562
+ cancelSubtree,
563
+ runControlActions,
564
+ dependenciesSatisfied,
565
+ };