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.
- package/package.json +1 -1
- package/src/agents/prompts/native/environment.js +20 -8
- package/src/code/agent.js +517 -112
- package/src/code/commands.js +77 -0
- package/src/code/context/artifactGc.js +292 -0
- package/src/code/context/artifactIndex.js +161 -0
- package/src/code/context/artifacts.js +183 -0
- package/src/code/context/assembler.js +703 -0
- package/src/code/context/executionSegment.js +292 -0
- package/src/code/context/index.js +28 -0
- package/src/code/context/planGraph.js +1410 -0
- package/src/code/context/planGraphService.js +857 -0
- package/src/code/context/planMode.js +398 -0
- package/src/code/context/planProjection.js +432 -0
- package/src/code/context/projectSnapshot.js +201 -0
- package/src/code/context/promptLayers.js +175 -0
- package/src/code/context/reducers.js +328 -0
- package/src/code/context/stableJson.js +29 -0
- package/src/code/context/stateCommit.js +414 -0
- package/src/code/context/toolRuntime.js +172 -0
- package/src/code/context/transcript.js +182 -0
- package/src/code/context/transcriptSync.js +106 -0
- package/src/code/context/userInteraction.js +457 -0
- package/src/code/context/userNudge.js +116 -0
- package/src/code/context/workingSet.js +323 -0
- package/src/code/dispatch.js +20 -1
- package/src/code/index.js +8 -0
- package/src/code/modelCommand.js +87 -0
- package/src/code/nativeRunner.js +625 -34
- package/src/code/repl.js +196 -50
- package/src/code/runtime/agentWakeup.js +58 -0
- package/src/code/runtime/graphOwner.js +41 -0
- package/src/code/runtime/graphYieldRouter.js +42 -0
- package/src/code/runtime/index.js +15 -0
- package/src/code/runtime/loopMailbox.js +124 -0
- package/src/code/runtime/runtimeEvents.js +39 -0
- package/src/code/runtime/taskControl.js +565 -0
- package/src/code/runtime/taskFocus.js +165 -0
- package/src/code/runtime/taskLoop.js +383 -0
- package/src/code/runtime/taskRun.js +187 -0
- package/src/code/runtime/toolProvenance.js +70 -0
- package/src/code/runtime/workspaceLease.js +208 -0
- package/src/code/sessionStore.js +217 -15
- package/src/code/skills/index.js +10 -0
- package/src/code/skills/injection.js +66 -3
- package/src/code/skills/loader.js +21 -0
- package/src/code/skills/manifest.js +87 -0
- package/src/code/skills/render.js +15 -1
- package/src/code/taskDecomposer.js +56 -2
- package/src/code/tools/artifactRead.js +40 -0
- package/src/code/tools/askUser.js +11 -0
- package/src/code/tools/planGraph.js +29 -0
- package/src/code/tui.js +2 -0
- package/src/code/usageStore.js +15 -0
- package/src/ui/format/index.js +285 -45
- package/src/ui/format/markdownRenderer.js +436 -71
- package/src/ui/ink/ChatApp.js +39 -8
- package/src/ui/ink/UcodeApp.js +592 -43
- package/src/ui/ink/chatLogModel.js +102 -21
package/src/code/repl.js
CHANGED
|
@@ -21,18 +21,27 @@ const {
|
|
|
21
21
|
getPendingBusCount,
|
|
22
22
|
shouldAutoConsumeBus,
|
|
23
23
|
} = require("./busConsumer");
|
|
24
|
-
const { summarizeSessionUsage } = require("./usageStore");
|
|
24
|
+
const { summarizeSessionUsage, formatSessionUsageStatus } = require("./usageStore");
|
|
25
|
+
const { listUcodeCommandsForHelp } = require("./commands");
|
|
26
|
+
const { applyUcodeModelCommand, suggestUcodeModels } = require("./modelCommand");
|
|
27
|
+
const { applyUcodePlanCommand } = require("./context/planMode");
|
|
25
28
|
|
|
26
29
|
function printPrompt(stdout = process.stdout) {
|
|
27
30
|
stdout.write("> ");
|
|
28
31
|
}
|
|
29
32
|
|
|
30
|
-
function printUcodeBanner(stdout = process.stdout, {
|
|
33
|
+
function printUcodeBanner(stdout = process.stdout, {
|
|
34
|
+
model = "",
|
|
35
|
+
workspaceRoot = process.cwd(),
|
|
36
|
+
sessionId = "",
|
|
37
|
+
planMode = false,
|
|
38
|
+
} = {}) {
|
|
31
39
|
stdout.write(`${buildUcodeBannerLines({
|
|
32
40
|
model,
|
|
33
41
|
engine: "ufoo-core",
|
|
34
42
|
workspaceRoot,
|
|
35
43
|
sessionId,
|
|
44
|
+
planMode,
|
|
36
45
|
width: (stdout && stdout.columns) || 0,
|
|
37
46
|
}).join("\n")}\n`);
|
|
38
47
|
}
|
|
@@ -75,40 +84,14 @@ function extractAgentNickname(agentId = "") {
|
|
|
75
84
|
return base;
|
|
76
85
|
}
|
|
77
86
|
|
|
78
|
-
function formatSessionUsageStatus(summary = {}) {
|
|
79
|
-
const source = summary && typeof summary === "object" ? summary : {};
|
|
80
|
-
const input = Number(source.input) || 0;
|
|
81
|
-
const output = Number(source.output) || 0;
|
|
82
|
-
const cacheRead = Number(source.cacheRead) || 0;
|
|
83
|
-
const cacheCreation = Number(source.cacheCreation) || 0;
|
|
84
|
-
const denominator = cacheRead + input;
|
|
85
|
-
const hitRate = denominator > 0 ? (cacheRead / denominator) * 100 : 0;
|
|
86
|
-
return [
|
|
87
|
-
`Session tokens: input=${input} output=${output} cache_read=${cacheRead} cache_creation=${cacheCreation}`,
|
|
88
|
-
`Cache hit rate: ${hitRate.toFixed(1)}% (cache_read/(cache_read+input))`,
|
|
89
|
-
].join("\n");
|
|
90
|
-
}
|
|
91
|
-
|
|
92
87
|
function runSingleCommand(line = "", workspaceRoot = process.cwd()) {
|
|
93
88
|
const text = normalizeLine(line);
|
|
94
89
|
if (!text) return { kind: "empty" };
|
|
95
|
-
if (text === "exit" || text === "quit") return { kind: "exit" };
|
|
96
|
-
if (text === "help") {
|
|
90
|
+
if (text === "exit" || text === "quit" || text === "/exit" || text === "/quit") return { kind: "exit" };
|
|
91
|
+
if (text === "help" || text === "/help") {
|
|
97
92
|
return {
|
|
98
93
|
kind: "help",
|
|
99
|
-
output:
|
|
100
|
-
"Commands:",
|
|
101
|
-
" help",
|
|
102
|
-
" exit|quit",
|
|
103
|
-
" ubus|/ubus",
|
|
104
|
-
" status|/status",
|
|
105
|
-
" skills [list]",
|
|
106
|
-
" skills show <name>",
|
|
107
|
-
" bg|/bg <task>",
|
|
108
|
-
" resume <session-id>",
|
|
109
|
-
" tool <read|write|edit|bash> <args-json>",
|
|
110
|
-
" run <read|write|edit|bash> <args-json>",
|
|
111
|
-
].join("\n"),
|
|
94
|
+
output: listUcodeCommandsForHelp(),
|
|
112
95
|
};
|
|
113
96
|
}
|
|
114
97
|
const legacyUfooMarker = parseLegacyUfooMarkerCommand(text);
|
|
@@ -128,6 +111,41 @@ function runSingleCommand(line = "", workspaceRoot = process.cwd()) {
|
|
|
128
111
|
kind: "status",
|
|
129
112
|
};
|
|
130
113
|
}
|
|
114
|
+
const modelMatch = text.match(/^(?:\/model|model)(?:\s+(.*))?$/i);
|
|
115
|
+
if (modelMatch) {
|
|
116
|
+
const nextModel = String(modelMatch[1] || "").trim();
|
|
117
|
+
if (!nextModel) {
|
|
118
|
+
return { kind: "model", action: "show" };
|
|
119
|
+
}
|
|
120
|
+
// Reject accidental multi-token garbage; model ids are single tokens.
|
|
121
|
+
if (/\s/.test(nextModel)) {
|
|
122
|
+
return {
|
|
123
|
+
kind: "error",
|
|
124
|
+
output: "usage: /model [model-id]",
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
kind: "model",
|
|
129
|
+
action: "set",
|
|
130
|
+
model: nextModel,
|
|
131
|
+
};
|
|
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
|
+
}
|
|
131
149
|
const skillsMatch = text.match(/^(?:\/skills|skills)(?:\s+(.*))?$/i);
|
|
132
150
|
if (skillsMatch) {
|
|
133
151
|
const args = String(skillsMatch[1] || "").trim().split(/\s+/).filter(Boolean);
|
|
@@ -187,13 +205,13 @@ function runSingleCommand(line = "", workspaceRoot = process.cwd()) {
|
|
|
187
205
|
task,
|
|
188
206
|
};
|
|
189
207
|
}
|
|
190
|
-
const resumeMatch = text.match(/^resume(?:\s+(.+))?$/i);
|
|
208
|
+
const resumeMatch = text.match(/^(?:\/resume|resume)(?:\s+(.+))?$/i);
|
|
191
209
|
if (resumeMatch) {
|
|
192
210
|
const session = String(resumeMatch[1] || "").trim();
|
|
193
211
|
if (!session) {
|
|
194
212
|
return {
|
|
195
213
|
kind: "error",
|
|
196
|
-
output: "usage: resume <session-id>",
|
|
214
|
+
output: "usage: /resume <session-id>",
|
|
197
215
|
};
|
|
198
216
|
}
|
|
199
217
|
return {
|
|
@@ -264,6 +282,7 @@ async function runUcodeCoreAgent({
|
|
|
264
282
|
resolveNlTaskTimeoutMs,
|
|
265
283
|
resolveUcodeProviderModel,
|
|
266
284
|
runNaturalLanguageTask,
|
|
285
|
+
resumeAfterUserInteraction,
|
|
267
286
|
} = require("./agent");
|
|
268
287
|
const resolvedWorkspaceRoot = resolveUfooProjectRoot(workspaceRoot);
|
|
269
288
|
const resolvedUcode = resolveUcodeProviderModel({
|
|
@@ -320,6 +339,7 @@ async function runUcodeCoreAgent({
|
|
|
320
339
|
model: state.model || "default",
|
|
321
340
|
workspaceRoot: workspaceRoot,
|
|
322
341
|
sessionId: state.sessionId,
|
|
342
|
+
planMode: Boolean(state.executionState && state.executionState.planMode),
|
|
323
343
|
});
|
|
324
344
|
printPrompt(stdout);
|
|
325
345
|
const rl = readline.createInterface({
|
|
@@ -338,6 +358,7 @@ async function runUcodeCoreAgent({
|
|
|
338
358
|
let autoBusQueued = false;
|
|
339
359
|
let autoBusError = "";
|
|
340
360
|
let closing = false;
|
|
361
|
+
let taskInFlight = false;
|
|
341
362
|
|
|
342
363
|
const runAutoBusOnce = async () => {
|
|
343
364
|
if (!autoBusEnabled || closing) return;
|
|
@@ -438,6 +459,22 @@ async function runUcodeCoreAgent({
|
|
|
438
459
|
sessionId: state.sessionId,
|
|
439
460
|
});
|
|
440
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
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if (result.kind === "model") {
|
|
468
|
+
const applied = applyUcodeModelCommand(state, result);
|
|
469
|
+
stdout.write(`${applied.output}\n`);
|
|
470
|
+
if (applied.ok && result.action === "set") {
|
|
471
|
+
persistSessionState(state);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (result.kind === "plan") {
|
|
475
|
+
const applied = applyUcodePlanCommand(state, result);
|
|
476
|
+
stdout.write(`${applied.output}\n`);
|
|
477
|
+
if (applied.ok) persistSessionState(state);
|
|
441
478
|
}
|
|
442
479
|
if (result.kind === "ubus") {
|
|
443
480
|
const ubusResult = await runUbusCommand(state, {
|
|
@@ -486,23 +523,29 @@ async function runUcodeCoreAgent({
|
|
|
486
523
|
});
|
|
487
524
|
}
|
|
488
525
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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
|
+
}
|
|
506
549
|
|
|
507
550
|
if (!state.jsonOutput) {
|
|
508
551
|
const tail = escapeStripper.flush();
|
|
@@ -545,6 +588,106 @@ async function runUcodeCoreAgent({
|
|
|
545
588
|
};
|
|
546
589
|
|
|
547
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
|
+
}
|
|
548
691
|
chain = chain.then(() => handleLine(line)).catch((err) => {
|
|
549
692
|
stdout.write(`${JSON.stringify({ ok: false, error: err && err.message ? err.message : "agent loop failed" })}\n`);
|
|
550
693
|
printPrompt(stdout);
|
|
@@ -637,4 +780,7 @@ module.exports = {
|
|
|
637
780
|
extractAgentNickname,
|
|
638
781
|
parseAgentArgs,
|
|
639
782
|
formatSessionUsageStatus,
|
|
783
|
+
applyUcodeModelCommand,
|
|
784
|
+
applyUcodePlanCommand,
|
|
785
|
+
suggestUcodeModels,
|
|
640
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
|
+
};
|