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
package/src/code/repl.js CHANGED
@@ -24,17 +24,24 @@ const {
24
24
  const { summarizeSessionUsage, formatSessionUsageStatus } = require("./usageStore");
25
25
  const { listUcodeCommandsForHelp } = require("./commands");
26
26
  const { applyUcodeModelCommand, suggestUcodeModels } = require("./modelCommand");
27
+ const { applyUcodePlanCommand } = require("./context/planMode");
27
28
 
28
29
  function printPrompt(stdout = process.stdout) {
29
30
  stdout.write("> ");
30
31
  }
31
32
 
32
- function printUcodeBanner(stdout = process.stdout, { model = "", workspaceRoot = process.cwd(), sessionId = "" } = {}) {
33
+ function printUcodeBanner(stdout = process.stdout, {
34
+ model = "",
35
+ workspaceRoot = process.cwd(),
36
+ sessionId = "",
37
+ planMode = false,
38
+ } = {}) {
33
39
  stdout.write(`${buildUcodeBannerLines({
34
40
  model,
35
41
  engine: "ufoo-core",
36
42
  workspaceRoot,
37
43
  sessionId,
44
+ planMode,
38
45
  width: (stdout && stdout.columns) || 0,
39
46
  }).join("\n")}\n`);
40
47
  }
@@ -123,6 +130,22 @@ function runSingleCommand(line = "", workspaceRoot = process.cwd()) {
123
130
  model: nextModel,
124
131
  };
125
132
  }
133
+ const planMatch = text.match(/^(?:\/plan|plan)(?:\s+(.*))?$/i);
134
+ if (planMatch) {
135
+ const arg = String(planMatch[1] || "").trim().toLowerCase();
136
+ if (!arg || arg === "show" || arg === "status") return { kind: "plan", action: "show" };
137
+ if (arg === "on" || arg === "enable") return { kind: "plan", action: "on" };
138
+ if (arg === "off" || arg === "disable") return { kind: "plan", action: "off" };
139
+ if (arg === "clear") return { kind: "plan", action: "clear" };
140
+ if (arg === "hide") return { kind: "plan", action: "hide" };
141
+ if (arg === "focus") return { kind: "plan", action: "focus" };
142
+ if (arg === "debug") return { kind: "plan", action: "debug" };
143
+ if (arg === "toggle") return { kind: "plan", action: "toggle" };
144
+ return {
145
+ kind: "error",
146
+ output: "usage: /plan [on|off|show|hide|focus|debug|clear]",
147
+ };
148
+ }
126
149
  const skillsMatch = text.match(/^(?:\/skills|skills)(?:\s+(.*))?$/i);
127
150
  if (skillsMatch) {
128
151
  const args = String(skillsMatch[1] || "").trim().split(/\s+/).filter(Boolean);
@@ -259,6 +282,7 @@ async function runUcodeCoreAgent({
259
282
  resolveNlTaskTimeoutMs,
260
283
  resolveUcodeProviderModel,
261
284
  runNaturalLanguageTask,
285
+ resumeAfterUserInteraction,
262
286
  } = require("./agent");
263
287
  const resolvedWorkspaceRoot = resolveUfooProjectRoot(workspaceRoot);
264
288
  const resolvedUcode = resolveUcodeProviderModel({
@@ -315,6 +339,7 @@ async function runUcodeCoreAgent({
315
339
  model: state.model || "default",
316
340
  workspaceRoot: workspaceRoot,
317
341
  sessionId: state.sessionId,
342
+ planMode: Boolean(state.executionState && state.executionState.planMode),
318
343
  });
319
344
  printPrompt(stdout);
320
345
  const rl = readline.createInterface({
@@ -333,6 +358,7 @@ async function runUcodeCoreAgent({
333
358
  let autoBusQueued = false;
334
359
  let autoBusError = "";
335
360
  let closing = false;
361
+ let taskInFlight = false;
336
362
 
337
363
  const runAutoBusOnce = async () => {
338
364
  if (!autoBusEnabled || closing) return;
@@ -433,6 +459,10 @@ async function runUcodeCoreAgent({
433
459
  sessionId: state.sessionId,
434
460
  });
435
461
  stdout.write(`${formatSessionUsageStatus(usageSummary)}\n`);
462
+ const { formatPlanModeStatus } = require("./context/planMode");
463
+ if (state.executionState) {
464
+ stdout.write(`${formatPlanModeStatus(state.executionState).split("\n").slice(0, 5).join("\n")}\n`);
465
+ }
436
466
  }
437
467
  if (result.kind === "model") {
438
468
  const applied = applyUcodeModelCommand(state, result);
@@ -441,6 +471,11 @@ async function runUcodeCoreAgent({
441
471
  persistSessionState(state);
442
472
  }
443
473
  }
474
+ if (result.kind === "plan") {
475
+ const applied = applyUcodePlanCommand(state, result);
476
+ stdout.write(`${applied.output}\n`);
477
+ if (applied.ok) persistSessionState(state);
478
+ }
444
479
  if (result.kind === "ubus") {
445
480
  const ubusResult = await runUbusCommand(state, {
446
481
  workspaceRoot: runtimeWorkspace,
@@ -488,23 +523,29 @@ async function runUcodeCoreAgent({
488
523
  });
489
524
  }
490
525
 
491
- const nlResult = await runNaturalLanguageTask(result.task, state, {
492
- onDelta: state.jsonOutput
493
- ? null
494
- : async (delta) => {
495
- const text = escapeStripper.write(String(delta || ""));
496
- const safeText = stripBlessedTags(stripLeakedEscapeTags(text));
497
- if (!safeText) return;
498
- if (/[^\s]/.test(safeText)) {
499
- streamedVisible = true;
500
- }
501
- if (streamBuffer) {
502
- await streamBuffer.write(safeText);
503
- } else {
504
- stdout.write(safeText);
505
- }
506
- },
507
- });
526
+ taskInFlight = true;
527
+ let nlResult;
528
+ try {
529
+ nlResult = await runNaturalLanguageTask(result.task, state, {
530
+ onDelta: state.jsonOutput
531
+ ? null
532
+ : async (delta) => {
533
+ const text = escapeStripper.write(String(delta || ""));
534
+ const safeText = stripBlessedTags(stripLeakedEscapeTags(text));
535
+ if (!safeText) return;
536
+ if (/[^\s]/.test(safeText)) {
537
+ streamedVisible = true;
538
+ }
539
+ if (streamBuffer) {
540
+ await streamBuffer.write(safeText);
541
+ } else {
542
+ stdout.write(safeText);
543
+ }
544
+ },
545
+ });
546
+ } finally {
547
+ taskInFlight = false;
548
+ }
508
549
 
509
550
  if (!state.jsonOutput) {
510
551
  const tail = escapeStripper.flush();
@@ -547,6 +588,106 @@ async function runUcodeCoreAgent({
547
588
  };
548
589
 
549
590
  rl.on("line", (line) => {
591
+ const trimmed = normalizeLine(line);
592
+
593
+ // Pending approval/choice/chat takes priority over nudge / new NL.
594
+ try {
595
+ const {
596
+ hasPendingUserInteraction,
597
+ parseUserInteractionInput,
598
+ getPendingUserInteraction,
599
+ } = require("./context/userInteraction");
600
+ if (
601
+ trimmed
602
+ && state.executionState
603
+ && hasPendingUserInteraction(state.executionState)
604
+ ) {
605
+ const pending = getPendingUserInteraction(state.executionState);
606
+ const parsed = parseUserInteractionInput(pending, trimmed);
607
+ if (!parsed.ok) {
608
+ stdout.write(`${parsed.error || "Invalid reply"}\n`);
609
+ printPrompt(stdout);
610
+ return;
611
+ }
612
+ chain = chain.then(async () => {
613
+ let streamBuffer = null;
614
+ let streamedVisible = false;
615
+ const escapeStripper = createEscapeTagStripper();
616
+ if (!state.jsonOutput) {
617
+ streamBuffer = new StreamBuffer(stdout.write.bind(stdout), {
618
+ delay: 10,
619
+ chunkSize: 4,
620
+ });
621
+ }
622
+ taskInFlight = true;
623
+ let resumeResult;
624
+ try {
625
+ resumeResult = await resumeAfterUserInteraction(trimmed, state, {
626
+ onDelta: state.jsonOutput
627
+ ? null
628
+ : async (delta) => {
629
+ const text = escapeStripper.write(String(delta || ""));
630
+ const safeText = stripBlessedTags(stripLeakedEscapeTags(text));
631
+ if (!safeText) return;
632
+ if (/[^\s]/.test(safeText)) {
633
+ streamedVisible = true;
634
+ }
635
+ if (streamBuffer) {
636
+ await streamBuffer.write(safeText);
637
+ } else {
638
+ stdout.write(safeText);
639
+ }
640
+ },
641
+ });
642
+ } finally {
643
+ taskInFlight = false;
644
+ }
645
+ if (streamBuffer) {
646
+ await streamBuffer.finish();
647
+ }
648
+ const streamed = !state.jsonOutput && Boolean(resumeResult && resumeResult.streamed);
649
+ if (streamed && streamedVisible && resumeResult && resumeResult.streamLastChar !== "\n") {
650
+ stdout.write("\n");
651
+ }
652
+ if (resumeResult && resumeResult.waitingUserInteraction) {
653
+ stdout.write("Still waiting for your reply.\n");
654
+ } else if (!resumeResult || resumeResult.ok === false) {
655
+ stdout.write(`Error: ${(resumeResult && resumeResult.error) || "resume failed"}\n`);
656
+ } else {
657
+ const shouldSkipSummary = Boolean(streamed && resumeResult.ok && streamedVisible);
658
+ if (!shouldSkipSummary && resumeResult.summary) {
659
+ stdout.write(`${resumeResult.summary}\n`);
660
+ }
661
+ }
662
+ const persisted = persistSessionState(state);
663
+ if (!state.jsonOutput && (!persisted || persisted.ok === false)) {
664
+ stdout.write(`Warning: failed to persist session ${state.sessionId}: ${(persisted && persisted.error) || "unknown error"}\n`);
665
+ }
666
+ printPrompt(stdout);
667
+ }).catch((err) => {
668
+ stdout.write(`${JSON.stringify({ ok: false, error: err && err.message ? err.message : "resume failed" })}\n`);
669
+ printPrompt(stdout);
670
+ });
671
+ return;
672
+ }
673
+ } catch (err) {
674
+ stdout.write(`Error: ${err && err.message ? err.message : "interaction failed"}\n`);
675
+ printPrompt(stdout);
676
+ return;
677
+ }
678
+
679
+ // Mid-task NL input becomes a pending user reminder for the next LLM turn.
680
+ if (taskInFlight && trimmed && !/^\//.test(trimmed)) {
681
+ const { enqueueUserPrompt } = require("./context/userNudge");
682
+ const { emptyExecutionState } = require("./context/executionSegment");
683
+ if (!state.executionState || typeof state.executionState !== "object") {
684
+ state.executionState = emptyExecutionState();
685
+ }
686
+ enqueueUserPrompt(state.executionState, trimmed);
687
+ stdout.write("Queued user reminder for next model turn.\n");
688
+ printPrompt(stdout);
689
+ return;
690
+ }
550
691
  chain = chain.then(() => handleLine(line)).catch((err) => {
551
692
  stdout.write(`${JSON.stringify({ ok: false, error: err && err.message ? err.message : "agent loop failed" })}\n`);
552
693
  printPrompt(stdout);
@@ -640,5 +781,6 @@ module.exports = {
640
781
  parseAgentArgs,
641
782
  formatSessionUsageStatus,
642
783
  applyUcodeModelCommand,
784
+ applyUcodePlanCommand,
643
785
  suggestUcodeModels,
644
786
  };
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Drain Agent Loop mailbox into a turnDynamic block (never as user role).
5
+ */
6
+
7
+ const { drainAgentMailbox, ensureMailbox } = require("./loopMailbox");
8
+
9
+ function formatAgentRuntimeEvents(events = []) {
10
+ const list = Array.isArray(events) ? events : [];
11
+ if (list.length === 0) return "";
12
+ const lines = ["Runtime events (Agent Loop mailbox; not user messages):"];
13
+ for (const entry of list) {
14
+ if (!entry) continue;
15
+ if (entry.kind === "user") {
16
+ lines.push(`- user_nudge: ${String(entry.text || "").slice(0, 400)}`);
17
+ continue;
18
+ }
19
+ if (entry.kind === "runtime" && entry.event) {
20
+ const ev = entry.event;
21
+ const bits = [`type=${ev.type}`];
22
+ if (ev.taskId) bits.push(`taskId=${ev.taskId}`);
23
+ if (ev.taskRunId) bits.push(`taskRunId=${ev.taskRunId}`);
24
+ if (ev.result && ev.result.summary) bits.push(`summary=${String(ev.result.summary).slice(0, 200)}`);
25
+ if (ev.error && (ev.error.message || ev.error)) {
26
+ bits.push(`error=${String(ev.error.message || ev.error).slice(0, 200)}`);
27
+ }
28
+ if (Array.isArray(ev.readyNodes) && ev.readyNodes.length) {
29
+ bits.push(`readyNodes=[${ev.readyNodes.join(",")}]`);
30
+ }
31
+ lines.push(`- ${bits.join(" ")}`);
32
+ }
33
+ }
34
+ return lines.join("\n");
35
+ }
36
+
37
+ /**
38
+ * Peek without drain — for inspect. Prefer drainForAgentTurn for consumption.
39
+ */
40
+ function peekAgentMailboxText(executionState = null) {
41
+ const state = executionState && typeof executionState === "object" ? executionState : {};
42
+ const box = ensureMailbox(state, "agentMailbox");
43
+ return formatAgentRuntimeEvents(box.queue || []);
44
+ }
45
+
46
+ function drainAgentMailboxForTurn(executionState = null) {
47
+ const events = drainAgentMailbox(executionState);
48
+ return {
49
+ events,
50
+ text: formatAgentRuntimeEvents(events),
51
+ };
52
+ }
53
+
54
+ module.exports = {
55
+ formatAgentRuntimeEvents,
56
+ peekAgentMailboxText,
57
+ drainAgentMailboxForTurn,
58
+ };
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Graph ownership: which loop consumes waiting_llm / yields.
5
+ */
6
+
7
+ function agentLoopOwner(agentLoopId = "agent") {
8
+ return {
9
+ kind: "agent_loop",
10
+ agentLoopId: String(agentLoopId || "agent").trim() || "agent",
11
+ };
12
+ }
13
+
14
+ function taskLoopOwner(taskRunId = "") {
15
+ return {
16
+ kind: "task_loop",
17
+ taskRunId: String(taskRunId || "").trim(),
18
+ };
19
+ }
20
+
21
+ function normalizeGraphOwner(source = null) {
22
+ if (!source || typeof source !== "object") return agentLoopOwner();
23
+ const kind = String(source.kind || "").trim();
24
+ if (kind === "task_loop") {
25
+ const taskRunId = String(source.taskRunId || "").trim();
26
+ if (!taskRunId) return agentLoopOwner();
27
+ return taskLoopOwner(taskRunId);
28
+ }
29
+ return agentLoopOwner(source.agentLoopId);
30
+ }
31
+
32
+ function isTaskLoopOwner(owner = null) {
33
+ return Boolean(owner && owner.kind === "task_loop" && owner.taskRunId);
34
+ }
35
+
36
+ module.exports = {
37
+ agentLoopOwner,
38
+ taskLoopOwner,
39
+ normalizeGraphOwner,
40
+ isTaskLoopOwner,
41
+ };
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Route graph yields to the owning loop mailbox.
5
+ */
6
+
7
+ const { isTaskLoopOwner } = require("./graphOwner");
8
+ const { enqueueAgentRuntime, enqueueTaskEvent } = require("./loopMailbox");
9
+ const { createRuntimeEvent } = require("./runtimeEvents");
10
+
11
+ function routeGraphYield(executionState = null, {
12
+ graph = null,
13
+ reason = "",
14
+ waitingFor = null,
15
+ } = {}) {
16
+ const owner = graph && graph.owner ? graph.owner : null;
17
+ const graphId = graph && graph.graphId ? graph.graphId : "";
18
+ const payload = {
19
+ graphId,
20
+ reason: String(reason || "").trim() || "llm_required",
21
+ waitingFor: waitingFor || null,
22
+ };
23
+
24
+ if (isTaskLoopOwner(owner)) {
25
+ return enqueueTaskEvent(executionState, owner.taskRunId, {
26
+ kind: "graph_yield",
27
+ ...payload,
28
+ });
29
+ }
30
+
31
+ // Parent / agent-owned graph: surface as runtime event (not user message).
32
+ return enqueueAgentRuntime(executionState, createRuntimeEvent("parent_graph_ready_changed", {
33
+ readyNodes: waitingFor && waitingFor.id ? [waitingFor.id] : [],
34
+ graphId,
35
+ yieldReason: payload.reason,
36
+ waitingFor,
37
+ }));
38
+ }
39
+
40
+ module.exports = {
41
+ routeGraphYield,
42
+ };
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+
3
+ module.exports = {
4
+ ...require("./runtimeEvents"),
5
+ ...require("./graphOwner"),
6
+ ...require("./loopMailbox"),
7
+ ...require("./taskRun"),
8
+ ...require("./workspaceLease"),
9
+ ...require("./taskFocus"),
10
+ ...require("./taskLoop"),
11
+ ...require("./taskControl"),
12
+ ...require("./toolProvenance"),
13
+ ...require("./graphYieldRouter"),
14
+ ...require("./agentWakeup"),
15
+ };
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Typed mailboxes for Agent Loop vs TaskLoop.
5
+ * Task mailbox schema has no user-event type (runtime isolation).
6
+ */
7
+
8
+ const { isRuntimeEvent } = require("./runtimeEvents");
9
+
10
+ const AGENT_EVENT_KINDS = Object.freeze(["user", "runtime"]);
11
+ const TASK_EVENT_KINDS = Object.freeze([
12
+ "graph_yield",
13
+ "tool_result",
14
+ "control",
15
+ "advance",
16
+ "model_turn",
17
+ ]);
18
+
19
+ function emptyMailbox() {
20
+ return { queue: [], seq: 0 };
21
+ }
22
+
23
+ function ensureMailbox(store = null, key = "mailbox") {
24
+ const target = store && typeof store === "object" ? store : {};
25
+ if (!target[key] || typeof target[key] !== "object") {
26
+ target[key] = emptyMailbox();
27
+ }
28
+ if (!Array.isArray(target[key].queue)) target[key].queue = [];
29
+ if (!Number.isFinite(target[key].seq)) target[key].seq = 0;
30
+ return target[key];
31
+ }
32
+
33
+ function enqueue(mailbox = null, event = {}) {
34
+ const box = mailbox && typeof mailbox === "object" ? mailbox : emptyMailbox();
35
+ if (!Array.isArray(box.queue)) box.queue = [];
36
+ box.seq = (Number(box.seq) || 0) + 1;
37
+ const entry = {
38
+ id: `evt_${box.seq}`,
39
+ enqueuedAt: new Date().toISOString(),
40
+ ...event,
41
+ };
42
+ box.queue.push(entry);
43
+ return entry;
44
+ }
45
+
46
+ function drain(mailbox = null, { max = 0 } = {}) {
47
+ const box = mailbox && typeof mailbox === "object" ? mailbox : emptyMailbox();
48
+ if (!Array.isArray(box.queue) || box.queue.length === 0) return [];
49
+ if (!max || max >= box.queue.length) {
50
+ const all = box.queue.slice();
51
+ box.queue = [];
52
+ return all;
53
+ }
54
+ const taken = box.queue.splice(0, Math.max(1, Math.floor(max)));
55
+ return taken;
56
+ }
57
+
58
+ function peek(mailbox = null) {
59
+ const box = mailbox && typeof mailbox === "object" ? mailbox : emptyMailbox();
60
+ return Array.isArray(box.queue) && box.queue.length > 0 ? box.queue[0] : null;
61
+ }
62
+
63
+ function enqueueAgentUser(executionState = null, text = "") {
64
+ const state = executionState && typeof executionState === "object" ? executionState : {};
65
+ const mailbox = ensureMailbox(state, "agentMailbox");
66
+ return enqueue(mailbox, {
67
+ kind: "user",
68
+ text: String(text || "").trim(),
69
+ });
70
+ }
71
+
72
+ function enqueueAgentRuntime(executionState = null, runtimeEvent = {}) {
73
+ if (!isRuntimeEvent(runtimeEvent)) {
74
+ throw new Error("enqueueAgentRuntime requires a runtime event");
75
+ }
76
+ const state = executionState && typeof executionState === "object" ? executionState : {};
77
+ const mailbox = ensureMailbox(state, "agentMailbox");
78
+ return enqueue(mailbox, {
79
+ kind: "runtime",
80
+ event: runtimeEvent,
81
+ });
82
+ }
83
+
84
+ function enqueueTaskEvent(executionState = null, taskRunId = "", event = {}) {
85
+ const state = executionState && typeof executionState === "object" ? executionState : {};
86
+ if (!state.taskMailboxes || typeof state.taskMailboxes !== "object") {
87
+ state.taskMailboxes = {};
88
+ }
89
+ const id = String(taskRunId || "").trim();
90
+ if (!id) throw new Error("taskRunId required");
91
+ const kind = String(event.kind || "").trim();
92
+ if (!TASK_EVENT_KINDS.includes(kind)) {
93
+ throw new Error(`invalid task mailbox event kind: ${kind}`);
94
+ }
95
+ if (!state.taskMailboxes[id]) state.taskMailboxes[id] = emptyMailbox();
96
+ return enqueue(state.taskMailboxes[id], event);
97
+ }
98
+
99
+ function drainAgentMailbox(executionState = null, options = {}) {
100
+ const state = executionState && typeof executionState === "object" ? executionState : {};
101
+ return drain(ensureMailbox(state, "agentMailbox"), options);
102
+ }
103
+
104
+ function drainTaskMailbox(executionState = null, taskRunId = "", options = {}) {
105
+ const state = executionState && typeof executionState === "object" ? executionState : {};
106
+ const id = String(taskRunId || "").trim();
107
+ if (!state.taskMailboxes || !state.taskMailboxes[id]) return [];
108
+ return drain(state.taskMailboxes[id], options);
109
+ }
110
+
111
+ module.exports = {
112
+ AGENT_EVENT_KINDS,
113
+ TASK_EVENT_KINDS,
114
+ emptyMailbox,
115
+ ensureMailbox,
116
+ enqueue,
117
+ drain,
118
+ peek,
119
+ enqueueAgentUser,
120
+ enqueueAgentRuntime,
121
+ enqueueTaskEvent,
122
+ drainAgentMailbox,
123
+ drainTaskMailbox,
124
+ };
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Runtime event types for Agent Loop wakeup (never user-role messages).
5
+ */
6
+
7
+ const RUNTIME_EVENT_TYPES = Object.freeze([
8
+ "task_started",
9
+ "task_succeeded",
10
+ "task_failed",
11
+ "task_cancelled",
12
+ "parent_graph_ready_changed",
13
+ ]);
14
+
15
+ function createRuntimeEvent(type = "", payload = {}) {
16
+ const eventType = String(type || "").trim();
17
+ if (!RUNTIME_EVENT_TYPES.includes(eventType)) {
18
+ throw new Error(`unknown runtime event type: ${type}`);
19
+ }
20
+ return {
21
+ type: eventType,
22
+ at: new Date().toISOString(),
23
+ ...payload,
24
+ };
25
+ }
26
+
27
+ function isRuntimeEvent(value = null) {
28
+ return Boolean(
29
+ value
30
+ && typeof value === "object"
31
+ && RUNTIME_EVENT_TYPES.includes(String(value.type || "")),
32
+ );
33
+ }
34
+
35
+ module.exports = {
36
+ RUNTIME_EVENT_TYPES,
37
+ createRuntimeEvent,
38
+ isRuntimeEvent,
39
+ };