u-foo 2.2.4 → 2.3.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/SKILLS/ufoo/SKILL.md +56 -12
- package/SKILLS/uinit/SKILL.md +3 -2
- package/modules/AGENTS.template.md +2 -1
- package/modules/bus/README.md +1 -1
- package/modules/context/SKILLS/uctx/SKILL.md +6 -4
- package/package.json +1 -1
- package/src/agent/codexThreadProvider.js +2 -2
- package/src/agent/controllerToolExecutor.js +24 -1
- package/src/agent/credentials/claude.js +85 -16
- package/src/agent/credentials/codex.js +251 -23
- package/src/agent/defaultBootstrap.js +3 -1
- package/src/agent/directAuthStatus.js +264 -0
- package/src/agent/internalRunner.js +18 -12
- package/src/agent/loopObservability.js +10 -0
- package/src/agent/loopRuntime.js +19 -0
- package/src/agent/ufooAgent.js +43 -13
- package/src/agent/upstreamTransport.js +23 -8
- package/src/bus/index.js +6 -1
- package/src/bus/message.js +156 -8
- package/src/chat/commandExecutor.js +187 -7
- package/src/chat/commands.js +23 -4
- package/src/chat/completionController.js +30 -7
- package/src/chat/index.js +3 -5
- package/src/cli/groupCoreCommands.js +5 -0
- package/src/cli.js +309 -0
- package/src/code/UCODE_PROMPT.md +3 -2
- package/src/code/prompts/ufoo.js +3 -2
- package/src/config.js +16 -3
- package/src/context/doctor.js +1 -1
- package/src/daemon/groupOrchestrator.js +13 -9
- package/src/daemon/promptRequest.js +11 -2
- package/src/daemon/soloBootstrap.js +2 -0
- package/src/group/bootstrap.js +1 -1
- package/src/group/promptProfiles.js +106 -22
- package/src/group/templates.js +1 -0
- package/src/init/index.js +4 -0
- package/src/memory/historySearch.js +308 -0
- package/src/memory/index.js +653 -8
- package/src/providerapi/redactor.js +4 -1
- package/src/status/index.js +24 -1
- package/src/tools/handlers/memory.js +168 -0
- package/src/tools/index.js +12 -0
- package/src/tools/registry.js +12 -0
- package/src/tools/schemaFixtures.js +213 -0
- package/src/tools/tier1/editMemory.js +14 -0
- package/src/tools/tier1/forget.js +14 -0
- package/src/tools/tier1/recall.js +14 -0
- package/src/tools/tier1/remember.js +14 -0
- package/src/tools/tier1/searchHistory.js +14 -0
- package/src/tools/tier1/searchMemory.js +14 -0
- package/templates/groups/build-lane.json +44 -6
- package/templates/groups/build-ultra.json +6 -5
- package/templates/groups/design-system.json +84 -0
- package/templates/groups/product-discovery.json +9 -4
- package/templates/groups/ui-plan-review.json +84 -0
- package/templates/groups/ui-polish.json +6 -2
- package/templates/groups/verify-ship.json +9 -4
package/src/cli.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
|
+
const os = require("os");
|
|
2
3
|
const { spawnSync } = require("child_process");
|
|
3
4
|
const net = require("net");
|
|
4
5
|
const fs = require("fs");
|
|
@@ -244,6 +245,179 @@ function projectSwitchV1Error() {
|
|
|
244
245
|
return err;
|
|
245
246
|
}
|
|
246
247
|
|
|
248
|
+
function parseMemoryTags(value = "") {
|
|
249
|
+
return String(value || "")
|
|
250
|
+
.split(",")
|
|
251
|
+
.map((item) => item.trim())
|
|
252
|
+
.filter(Boolean);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function readTextFile(filePath = "") {
|
|
256
|
+
const target = String(filePath || "").trim();
|
|
257
|
+
if (!target) return "";
|
|
258
|
+
return fs.readFileSync(path.resolve(target), "utf8");
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function formatMemoryLine(entry = {}) {
|
|
262
|
+
const tags = Array.isArray(entry.tags) && entry.tags.length
|
|
263
|
+
? `[${entry.tags.join(",")}]`
|
|
264
|
+
: "[]";
|
|
265
|
+
const status = entry.status && entry.status !== "active" ? ` (${entry.status})` : "";
|
|
266
|
+
return `${entry.id} ${tags} ${entry.title}${status}`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function printMemoryEntry(entry = {}, write = (line) => console.log(line)) {
|
|
270
|
+
write(`${entry.id} ${entry.status || "active"}`);
|
|
271
|
+
write(`title: ${entry.title || ""}`);
|
|
272
|
+
write(`tags: ${(entry.tags || []).join(",")}`);
|
|
273
|
+
write(`source: ${entry.source || ""}`);
|
|
274
|
+
write(`created_at: ${entry.created_at || ""}`);
|
|
275
|
+
write(`updated_at: ${entry.updated_at || ""}`);
|
|
276
|
+
write("");
|
|
277
|
+
write(entry.body || "");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function runMemoryCommand({
|
|
281
|
+
subcommand = "list",
|
|
282
|
+
args = [],
|
|
283
|
+
opts = {},
|
|
284
|
+
cwd = process.cwd(),
|
|
285
|
+
write = (line) => console.log(line),
|
|
286
|
+
writeError = (line) => console.error(line),
|
|
287
|
+
} = {}) {
|
|
288
|
+
const MemoryManager = require("./memory");
|
|
289
|
+
const manager = new MemoryManager(cwd);
|
|
290
|
+
const sub = String(subcommand || "list").trim().toLowerCase();
|
|
291
|
+
const outputJson = opts.json === true;
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
if (sub === "add") {
|
|
295
|
+
const title = String(args[0] || opts.title || "").trim();
|
|
296
|
+
const body = opts.bodyFile ? readTextFile(opts.bodyFile) : String(opts.body || "").trim();
|
|
297
|
+
if (!body) throw new Error("memory add requires --body or --body-file");
|
|
298
|
+
const entry = manager.add({
|
|
299
|
+
title,
|
|
300
|
+
body,
|
|
301
|
+
tags: parseMemoryTags(opts.tags),
|
|
302
|
+
source: "user",
|
|
303
|
+
}, {
|
|
304
|
+
source: "cli",
|
|
305
|
+
actor: process.env.UFOO_SUBSCRIBER_ID || process.env.USER || "user",
|
|
306
|
+
});
|
|
307
|
+
if (outputJson) write(JSON.stringify(entry, null, 2));
|
|
308
|
+
else write(formatMemoryLine(entry));
|
|
309
|
+
return 0;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (sub === "list" || sub === "ls") {
|
|
313
|
+
const entries = manager.list({
|
|
314
|
+
tag: opts.tag || "",
|
|
315
|
+
all: opts.all === true,
|
|
316
|
+
limit: parseInt(opts.limit, 10) || 0,
|
|
317
|
+
});
|
|
318
|
+
if (outputJson) {
|
|
319
|
+
write(JSON.stringify(entries, null, 2));
|
|
320
|
+
return 0;
|
|
321
|
+
}
|
|
322
|
+
if (entries.length === 0) {
|
|
323
|
+
write("No memory entries found.");
|
|
324
|
+
return 0;
|
|
325
|
+
}
|
|
326
|
+
entries.forEach((entry) => write(formatMemoryLine(entry)));
|
|
327
|
+
return 0;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (sub === "show") {
|
|
331
|
+
const id = String(args[0] || "").trim();
|
|
332
|
+
if (!id) throw new Error("memory show requires <id>");
|
|
333
|
+
const entry = manager.get(id, { includeArchived: opts.all === true });
|
|
334
|
+
if (outputJson) write(JSON.stringify(entry, null, 2));
|
|
335
|
+
else printMemoryEntry(entry, write);
|
|
336
|
+
return 0;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (sub === "edit") {
|
|
340
|
+
const id = String(args[0] || "").trim();
|
|
341
|
+
if (!id) throw new Error("memory edit requires <id>");
|
|
342
|
+
const patch = {};
|
|
343
|
+
if (opts.title) patch.title = opts.title;
|
|
344
|
+
if (opts.tags !== undefined) patch.tags = parseMemoryTags(opts.tags);
|
|
345
|
+
if (opts.bodyFile) patch.body = readTextFile(opts.bodyFile);
|
|
346
|
+
if (opts.body) patch.body = opts.body;
|
|
347
|
+
if (Object.keys(patch).length === 0) {
|
|
348
|
+
const entry = manager.get(id);
|
|
349
|
+
const editor = process.env.EDITOR || "vi";
|
|
350
|
+
const tmp = path.join(os.tmpdir(), `ufoo-memory-${entry.id}-${Date.now()}.md`);
|
|
351
|
+
fs.writeFileSync(tmp, `# ${entry.title}\n\n${entry.body || ""}\n`, "utf8");
|
|
352
|
+
const res = spawnSync(editor, [tmp], { stdio: "inherit" });
|
|
353
|
+
if (res.error) throw res.error;
|
|
354
|
+
if (typeof res.status === "number" && res.status !== 0) {
|
|
355
|
+
throw new Error(`${editor} exited with code ${res.status}`);
|
|
356
|
+
}
|
|
357
|
+
const parsed = String(fs.readFileSync(tmp, "utf8"));
|
|
358
|
+
fs.rmSync(tmp, { force: true });
|
|
359
|
+
const lines = parsed.replace(/\r\n/g, "\n").split("\n");
|
|
360
|
+
const first = lines.findIndex((line) => line.trim());
|
|
361
|
+
if (first !== -1 && /^#\s+/.test(lines[first])) {
|
|
362
|
+
patch.title = lines[first].replace(/^#\s+/, "").trim();
|
|
363
|
+
patch.body = lines.slice(first + 1).join("\n").trim();
|
|
364
|
+
} else {
|
|
365
|
+
patch.body = parsed.trim();
|
|
366
|
+
}
|
|
367
|
+
patch.expected_updated_at = entry.updated_at;
|
|
368
|
+
}
|
|
369
|
+
const updated = manager.update(id, patch, {
|
|
370
|
+
source: "cli",
|
|
371
|
+
actor: process.env.UFOO_SUBSCRIBER_ID || process.env.USER || "user",
|
|
372
|
+
});
|
|
373
|
+
if (outputJson) write(JSON.stringify(updated, null, 2));
|
|
374
|
+
else write(formatMemoryLine(updated));
|
|
375
|
+
return 0;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (sub === "forget" || sub === "archive") {
|
|
379
|
+
const id = String(args[0] || "").trim();
|
|
380
|
+
if (!id) throw new Error("memory forget requires <id>");
|
|
381
|
+
const entry = manager.archive(id, {
|
|
382
|
+
source: "cli",
|
|
383
|
+
actor: process.env.UFOO_SUBSCRIBER_ID || process.env.USER || "user",
|
|
384
|
+
});
|
|
385
|
+
if (outputJson) write(JSON.stringify(entry, null, 2));
|
|
386
|
+
else write(formatMemoryLine(entry));
|
|
387
|
+
return 0;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (sub === "rebuild-index") {
|
|
391
|
+
const entries = manager.rebuildIndex();
|
|
392
|
+
if (outputJson) write(JSON.stringify({ count: entries.length, index_file: manager.indexFile }, null, 2));
|
|
393
|
+
else write(`Rebuilt ${manager.indexFile} (${entries.length} active entries)`);
|
|
394
|
+
return 0;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (sub === "audit") {
|
|
398
|
+
const rows = manager.readAudit(args[0] || "");
|
|
399
|
+
if (outputJson) {
|
|
400
|
+
write(JSON.stringify(rows, null, 2));
|
|
401
|
+
return 0;
|
|
402
|
+
}
|
|
403
|
+
if (rows.length === 0) {
|
|
404
|
+
write("No memory audit entries found.");
|
|
405
|
+
return 0;
|
|
406
|
+
}
|
|
407
|
+
rows.forEach((row) => {
|
|
408
|
+
write(`${row.ts || "-"} ${row.action || "-"} ${row.id || ""} ${row.title || ""}`.trim());
|
|
409
|
+
});
|
|
410
|
+
return 0;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
writeError("memory requires add|list|show|edit|forget|rebuild-index|audit subcommand");
|
|
414
|
+
return 1;
|
|
415
|
+
} catch (err) {
|
|
416
|
+
writeError(err.message || String(err));
|
|
417
|
+
return err.exitCode || 1;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
247
421
|
function runProjectCommand({
|
|
248
422
|
subcommand = "list",
|
|
249
423
|
outputJson = false,
|
|
@@ -409,6 +583,108 @@ async function runCli(argv) {
|
|
|
409
583
|
cwd: process.cwd(),
|
|
410
584
|
});
|
|
411
585
|
});
|
|
586
|
+
|
|
587
|
+
const memory = program.command("memory").description("Project shared memory commands");
|
|
588
|
+
memory
|
|
589
|
+
.command("add")
|
|
590
|
+
.description("Add a project memory entry")
|
|
591
|
+
.argument("<title>", "Memory title")
|
|
592
|
+
.option("--body <text>", "Memory body")
|
|
593
|
+
.option("--body-file <path>", "Read memory body from file")
|
|
594
|
+
.option("--tags <tags>", "Comma-separated tags")
|
|
595
|
+
.option("--json", "Output as JSON")
|
|
596
|
+
.action((title, opts) => {
|
|
597
|
+
process.exitCode = runMemoryCommand({
|
|
598
|
+
subcommand: "add",
|
|
599
|
+
args: [title],
|
|
600
|
+
opts,
|
|
601
|
+
cwd: process.cwd(),
|
|
602
|
+
});
|
|
603
|
+
});
|
|
604
|
+
memory
|
|
605
|
+
.command("list")
|
|
606
|
+
.alias("ls")
|
|
607
|
+
.description("List project memory entries")
|
|
608
|
+
.option("--tag <tag>", "Filter by tag")
|
|
609
|
+
.option("--all", "Include archived entries")
|
|
610
|
+
.option("--limit <n>", "Limit result count")
|
|
611
|
+
.option("--json", "Output as JSON")
|
|
612
|
+
.action((opts) => {
|
|
613
|
+
process.exitCode = runMemoryCommand({
|
|
614
|
+
subcommand: "list",
|
|
615
|
+
opts,
|
|
616
|
+
cwd: process.cwd(),
|
|
617
|
+
});
|
|
618
|
+
});
|
|
619
|
+
memory
|
|
620
|
+
.command("show")
|
|
621
|
+
.description("Show one memory entry")
|
|
622
|
+
.argument("<id>", "Memory id")
|
|
623
|
+
.option("--all", "Allow archived entries")
|
|
624
|
+
.option("--json", "Output as JSON")
|
|
625
|
+
.action((id, opts) => {
|
|
626
|
+
process.exitCode = runMemoryCommand({
|
|
627
|
+
subcommand: "show",
|
|
628
|
+
args: [id],
|
|
629
|
+
opts,
|
|
630
|
+
cwd: process.cwd(),
|
|
631
|
+
});
|
|
632
|
+
});
|
|
633
|
+
memory
|
|
634
|
+
.command("edit")
|
|
635
|
+
.description("Edit one memory entry")
|
|
636
|
+
.argument("<id>", "Memory id")
|
|
637
|
+
.option("--title <title>", "Replace title")
|
|
638
|
+
.option("--body <text>", "Replace body")
|
|
639
|
+
.option("--body-file <path>", "Replace body from file")
|
|
640
|
+
.option("--tags <tags>", "Replace tags with comma-separated tags")
|
|
641
|
+
.option("--json", "Output as JSON")
|
|
642
|
+
.action((id, opts) => {
|
|
643
|
+
process.exitCode = runMemoryCommand({
|
|
644
|
+
subcommand: "edit",
|
|
645
|
+
args: [id],
|
|
646
|
+
opts,
|
|
647
|
+
cwd: process.cwd(),
|
|
648
|
+
});
|
|
649
|
+
});
|
|
650
|
+
memory
|
|
651
|
+
.command("forget")
|
|
652
|
+
.description("Archive one memory entry")
|
|
653
|
+
.argument("<id>", "Memory id")
|
|
654
|
+
.option("--json", "Output as JSON")
|
|
655
|
+
.action((id, opts) => {
|
|
656
|
+
process.exitCode = runMemoryCommand({
|
|
657
|
+
subcommand: "forget",
|
|
658
|
+
args: [id],
|
|
659
|
+
opts,
|
|
660
|
+
cwd: process.cwd(),
|
|
661
|
+
});
|
|
662
|
+
});
|
|
663
|
+
memory
|
|
664
|
+
.command("rebuild-index")
|
|
665
|
+
.description("Rebuild memory INDEX.md")
|
|
666
|
+
.option("--json", "Output as JSON")
|
|
667
|
+
.action((opts) => {
|
|
668
|
+
process.exitCode = runMemoryCommand({
|
|
669
|
+
subcommand: "rebuild-index",
|
|
670
|
+
opts,
|
|
671
|
+
cwd: process.cwd(),
|
|
672
|
+
});
|
|
673
|
+
});
|
|
674
|
+
memory
|
|
675
|
+
.command("audit")
|
|
676
|
+
.description("Show memory audit log")
|
|
677
|
+
.argument("[id]", "Optional memory id")
|
|
678
|
+
.option("--json", "Output as JSON")
|
|
679
|
+
.action((id, opts) => {
|
|
680
|
+
process.exitCode = runMemoryCommand({
|
|
681
|
+
subcommand: "audit",
|
|
682
|
+
args: id ? [id] : [],
|
|
683
|
+
opts,
|
|
684
|
+
cwd: process.cwd(),
|
|
685
|
+
});
|
|
686
|
+
});
|
|
687
|
+
|
|
412
688
|
program
|
|
413
689
|
.command("launch")
|
|
414
690
|
.description("Launch an agent (ucode, uclaude, ucodex)")
|
|
@@ -1705,6 +1981,39 @@ async function runCli(argv) {
|
|
|
1705
1981
|
}
|
|
1706
1982
|
return;
|
|
1707
1983
|
}
|
|
1984
|
+
if (cmd === "memory") {
|
|
1985
|
+
const sub = rest[0] || "list";
|
|
1986
|
+
const args = rest.slice(1).filter((token, index, array) => {
|
|
1987
|
+
const prev = array[index - 1] || "";
|
|
1988
|
+
if (prev === "--body" || prev === "--body-file" || prev === "--tags" || prev === "--tag" || prev === "--limit" || prev === "--title") {
|
|
1989
|
+
return false;
|
|
1990
|
+
}
|
|
1991
|
+
return !token.startsWith("--");
|
|
1992
|
+
});
|
|
1993
|
+
const getOpt = (name, fallback = "") => {
|
|
1994
|
+
const idx = rest.indexOf(name);
|
|
1995
|
+
if (idx === -1 || idx + 1 >= rest.length) return fallback;
|
|
1996
|
+
const value = rest[idx + 1];
|
|
1997
|
+
if (!value || value.startsWith("--")) return fallback;
|
|
1998
|
+
return value;
|
|
1999
|
+
};
|
|
2000
|
+
process.exitCode = runMemoryCommand({
|
|
2001
|
+
subcommand: sub,
|
|
2002
|
+
args,
|
|
2003
|
+
opts: {
|
|
2004
|
+
title: getOpt("--title", ""),
|
|
2005
|
+
body: getOpt("--body", ""),
|
|
2006
|
+
bodyFile: getOpt("--body-file", ""),
|
|
2007
|
+
tags: getOpt("--tags", ""),
|
|
2008
|
+
tag: getOpt("--tag", ""),
|
|
2009
|
+
limit: getOpt("--limit", ""),
|
|
2010
|
+
all: rest.includes("--all"),
|
|
2011
|
+
json: rest.includes("--json"),
|
|
2012
|
+
},
|
|
2013
|
+
cwd: process.cwd(),
|
|
2014
|
+
});
|
|
2015
|
+
return;
|
|
2016
|
+
}
|
|
1708
2017
|
if (cmd === "init") {
|
|
1709
2018
|
const UfooInit = require("./init");
|
|
1710
2019
|
const init = new UfooInit(repoRoot);
|
package/src/code/UCODE_PROMPT.md
CHANGED
|
@@ -13,9 +13,10 @@ Operational constraints:
|
|
|
13
13
|
|
|
14
14
|
ufoo integration requirements:
|
|
15
15
|
- Participate in multi-agent coordination through ufoo bus/context.
|
|
16
|
-
- Respect shared context decisions
|
|
16
|
+
- Respect shared context decisions. The default is no new decision; only append one for important, plan-level choices that constrain future work, and keep durable project facts out of decisions.
|
|
17
|
+
- Use shared memory for durable project facts. Read existing memory before writing new memory; do not use it for transient task state.
|
|
17
18
|
- Support launch/close/resume/inject flows managed by ufoo daemon.
|
|
18
|
-
- Prefer canonical ufoo commands (`ufoo ctx`, `ufoo bus`, `ufoo report`) for coordination and status sync.
|
|
19
|
+
- Prefer canonical ufoo commands (`ufoo ctx`, `ufoo bus`, `ufoo memory`, `ufoo report`) for coordination and status sync.
|
|
19
20
|
|
|
20
21
|
Execution protocol:
|
|
21
22
|
- On session start, check context quickly:
|
package/src/code/prompts/ufoo.js
CHANGED
|
@@ -4,9 +4,10 @@ function getUfooIntegrationSection() {
|
|
|
4
4
|
return `# ufoo integration
|
|
5
5
|
|
|
6
6
|
Participate in multi-agent coordination through the ufoo bus/context system:
|
|
7
|
-
- Respect shared context decisions
|
|
7
|
+
- Respect shared context decisions. The default is no new decision; only append one for important, plan-level choices that constrain future work, and keep durable project facts out of decisions.
|
|
8
|
+
- Use shared memory for durable project facts. Read existing memory before writing new memory; do not use it for transient task state.
|
|
8
9
|
- Support launch/close/resume/inject flows managed by ufoo daemon.
|
|
9
|
-
- Prefer canonical ufoo commands (\`ufoo ctx\`, \`ufoo bus\`, \`ufoo report\`) for coordination and status sync.
|
|
10
|
+
- Prefer canonical ufoo commands (\`ufoo ctx\`, \`ufoo bus\`, \`ufoo memory\`, \`ufoo report\`) for coordination and status sync.
|
|
10
11
|
|
|
11
12
|
Execution protocol:
|
|
12
13
|
- On session start, check context quickly:
|
package/src/config.js
CHANGED
|
@@ -8,8 +8,9 @@ const DEFAULT_CONFIG = {
|
|
|
8
8
|
launchMode: "auto",
|
|
9
9
|
agentProvider: "codex-cli",
|
|
10
10
|
controllerMode: "main",
|
|
11
|
-
codexInternalThreadMode: "
|
|
11
|
+
codexInternalThreadMode: "api",
|
|
12
12
|
codexAuthPath: "",
|
|
13
|
+
codexOauthRefreshWindowSec: 300,
|
|
13
14
|
claudeOauthProfile: "",
|
|
14
15
|
claudeOauthTokenPath: "",
|
|
15
16
|
claudeOauthRefreshWindowSec: 300,
|
|
@@ -50,7 +51,8 @@ function normalizeControllerMode(value) {
|
|
|
50
51
|
|
|
51
52
|
function normalizeCodexInternalThreadMode(value) {
|
|
52
53
|
const raw = String(value || "").trim().toLowerCase();
|
|
53
|
-
if (raw === "
|
|
54
|
+
if (raw === "api" || raw === "direct" || raw === "direct-api" || raw === "upstream") return "api";
|
|
55
|
+
if (raw === "sdk") return "api";
|
|
54
56
|
return "legacy";
|
|
55
57
|
}
|
|
56
58
|
|
|
@@ -58,6 +60,12 @@ function normalizeCodexAuthPath(value) {
|
|
|
58
60
|
return typeof value === "string" ? value.trim() : "";
|
|
59
61
|
}
|
|
60
62
|
|
|
63
|
+
function normalizeCodexOauthRefreshWindowSec(value) {
|
|
64
|
+
const num = Number(value);
|
|
65
|
+
if (!Number.isFinite(num) || num < 0) return 300;
|
|
66
|
+
return Math.floor(num);
|
|
67
|
+
}
|
|
68
|
+
|
|
61
69
|
function normalizeClaudeOauthProfile(value) {
|
|
62
70
|
return typeof value === "string" ? value.trim() : "";
|
|
63
71
|
}
|
|
@@ -99,8 +107,11 @@ function loadConfig(projectRoot) {
|
|
|
99
107
|
controllerMode: Object.prototype.hasOwnProperty.call(raw, "controllerMode")
|
|
100
108
|
? normalizeControllerMode(raw.controllerMode)
|
|
101
109
|
: DEFAULT_CONFIG.controllerMode,
|
|
102
|
-
codexInternalThreadMode:
|
|
110
|
+
codexInternalThreadMode: Object.prototype.hasOwnProperty.call(raw, "codexInternalThreadMode")
|
|
111
|
+
? normalizeCodexInternalThreadMode(raw.codexInternalThreadMode)
|
|
112
|
+
: DEFAULT_CONFIG.codexInternalThreadMode,
|
|
103
113
|
codexAuthPath: normalizeCodexAuthPath(raw.codexAuthPath),
|
|
114
|
+
codexOauthRefreshWindowSec: normalizeCodexOauthRefreshWindowSec(raw.codexOauthRefreshWindowSec),
|
|
104
115
|
claudeOauthProfile: normalizeClaudeOauthProfile(raw.claudeOauthProfile),
|
|
105
116
|
claudeOauthTokenPath: normalizeClaudeOauthTokenPath(raw.claudeOauthTokenPath),
|
|
106
117
|
claudeOauthRefreshWindowSec: normalizeClaudeOauthRefreshWindowSec(raw.claudeOauthRefreshWindowSec),
|
|
@@ -143,6 +154,7 @@ function saveConfig(projectRoot, config) {
|
|
|
143
154
|
merged.controllerMode = normalizeControllerMode(merged.controllerMode);
|
|
144
155
|
merged.codexInternalThreadMode = normalizeCodexInternalThreadMode(merged.codexInternalThreadMode);
|
|
145
156
|
merged.codexAuthPath = normalizeCodexAuthPath(merged.codexAuthPath);
|
|
157
|
+
merged.codexOauthRefreshWindowSec = normalizeCodexOauthRefreshWindowSec(merged.codexOauthRefreshWindowSec);
|
|
146
158
|
merged.claudeOauthProfile = normalizeClaudeOauthProfile(merged.claudeOauthProfile);
|
|
147
159
|
merged.claudeOauthTokenPath = normalizeClaudeOauthTokenPath(merged.claudeOauthTokenPath);
|
|
148
160
|
merged.claudeOauthRefreshWindowSec = normalizeClaudeOauthRefreshWindowSec(merged.claudeOauthRefreshWindowSec);
|
|
@@ -186,6 +198,7 @@ module.exports = {
|
|
|
186
198
|
normalizeControllerMode,
|
|
187
199
|
normalizeCodexInternalThreadMode,
|
|
188
200
|
normalizeCodexAuthPath,
|
|
201
|
+
normalizeCodexOauthRefreshWindowSec,
|
|
189
202
|
normalizeClaudeOauthProfile,
|
|
190
203
|
normalizeClaudeOauthTokenPath,
|
|
191
204
|
normalizeClaudeOauthRefreshWindowSec,
|
package/src/context/doctor.js
CHANGED
|
@@ -113,7 +113,7 @@ class ContextDoctor {
|
|
|
113
113
|
|
|
114
114
|
console.log("=== context doctor ===");
|
|
115
115
|
console.log(
|
|
116
|
-
"Reminder:
|
|
116
|
+
"Reminder: default to no new decision; only record important, plan-level choices that constrain future work."
|
|
117
117
|
);
|
|
118
118
|
console.log("");
|
|
119
119
|
|
|
@@ -73,6 +73,12 @@ function buildRuntimeNickname(projectPrefix, nickname) {
|
|
|
73
73
|
return `${prefix}-${logicalName}`;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
function resolveMemberScopedNickname(member = {}) {
|
|
77
|
+
return asTrimmedString(member.scoped_nickname)
|
|
78
|
+
|| asTrimmedString(member.runtime_nickname)
|
|
79
|
+
|| asTrimmedString(member.nickname);
|
|
80
|
+
}
|
|
81
|
+
|
|
76
82
|
function buildLaunchPlan(templateDoc = {}) {
|
|
77
83
|
const agents = Array.isArray(templateDoc.agents) ? templateDoc.agents : [];
|
|
78
84
|
const remaining = new Map();
|
|
@@ -325,7 +331,6 @@ function buildExecutionPlan({
|
|
|
325
331
|
const runtimeNickname = buildRuntimeNickname(projectNicknamePrefix, item.nickname);
|
|
326
332
|
return {
|
|
327
333
|
nickname: item.nickname,
|
|
328
|
-
runtime_nickname: runtimeNickname,
|
|
329
334
|
requested_type: item.requested_type || item.type,
|
|
330
335
|
type: resolvedType,
|
|
331
336
|
role: item.role,
|
|
@@ -401,7 +406,7 @@ function buildExecutionPlan({
|
|
|
401
406
|
...item,
|
|
402
407
|
requested_type: item.requested_type || item.type,
|
|
403
408
|
type: resolvedType,
|
|
404
|
-
|
|
409
|
+
scoped_nickname: runtimeNickname,
|
|
405
410
|
resolved_profile: profile ? profile.resolved_profile : "",
|
|
406
411
|
display_name: profile ? profile.display_name : "",
|
|
407
412
|
short_name: profile ? profile.short_name : "",
|
|
@@ -459,7 +464,7 @@ function buildDefaultRuntime({
|
|
|
459
464
|
index: idx,
|
|
460
465
|
template_agent_id: item.id || "",
|
|
461
466
|
nickname: item.nickname,
|
|
462
|
-
|
|
467
|
+
scoped_nickname: item.scoped_nickname || item.runtime_nickname || "",
|
|
463
468
|
requested_type: item.requested_type || item.type,
|
|
464
469
|
type: item.type,
|
|
465
470
|
role: item.role || "",
|
|
@@ -795,7 +800,7 @@ function createGroupOrchestrator(options = {}) {
|
|
|
795
800
|
roster_version: compiled.rosterVersion,
|
|
796
801
|
members: compiled.executionPlan.map((item) => ({
|
|
797
802
|
nickname: item.nickname,
|
|
798
|
-
|
|
803
|
+
scoped_nickname: item.scoped_nickname,
|
|
799
804
|
type: item.type,
|
|
800
805
|
role: item.role,
|
|
801
806
|
startup_order: item.startup_order,
|
|
@@ -915,7 +920,7 @@ function createGroupOrchestrator(options = {}) {
|
|
|
915
920
|
action: "launch",
|
|
916
921
|
agent: item.type,
|
|
917
922
|
count: 1,
|
|
918
|
-
nickname: item.runtime_nickname,
|
|
923
|
+
nickname: item.scoped_nickname || item.runtime_nickname,
|
|
919
924
|
require_activity_monitor: true,
|
|
920
925
|
tmux_layout_context: tmuxLayoutContext,
|
|
921
926
|
...launchHostContext,
|
|
@@ -948,7 +953,7 @@ function createGroupOrchestrator(options = {}) {
|
|
|
948
953
|
}
|
|
949
954
|
|
|
950
955
|
const reused = Boolean(launchResult.skipped);
|
|
951
|
-
const subscriberId = pickLaunchSubscriber(projectRoot, launchResult, item.runtime_nickname);
|
|
956
|
+
const subscriberId = pickLaunchSubscriber(projectRoot, launchResult, item.scoped_nickname || item.runtime_nickname);
|
|
952
957
|
member.status = reused ? "reused" : "active";
|
|
953
958
|
member.managed = !reused;
|
|
954
959
|
member.subscriber_id = subscriberId || "";
|
|
@@ -960,7 +965,7 @@ function createGroupOrchestrator(options = {}) {
|
|
|
960
965
|
if (!reused) {
|
|
961
966
|
rollbackTargets.push({
|
|
962
967
|
memberIndex: i,
|
|
963
|
-
target: subscriberId || item.runtime_nickname,
|
|
968
|
+
target: subscriberId || item.scoped_nickname || item.runtime_nickname,
|
|
964
969
|
});
|
|
965
970
|
} else if (!canReuseBootstrappedMember(member, item, subscriberId)) {
|
|
966
971
|
const priorBootstrap = findAppliedBootstrapRecord(
|
|
@@ -1124,8 +1129,7 @@ function createGroupOrchestrator(options = {}) {
|
|
|
1124
1129
|
if (!member || member.managed === false) continue;
|
|
1125
1130
|
if (member.status !== "active") continue;
|
|
1126
1131
|
const target = asTrimmedString(member.subscriber_id)
|
|
1127
|
-
||
|
|
1128
|
-
|| asTrimmedString(member.nickname);
|
|
1132
|
+
|| resolveMemberScopedNickname(member);
|
|
1129
1133
|
if (!target) continue;
|
|
1130
1134
|
activeMembers.push({ index: i, target });
|
|
1131
1135
|
}
|
|
@@ -212,15 +212,16 @@ async function handlePromptRequest(options = {}) {
|
|
|
212
212
|
|
|
213
213
|
const privateReports = listControllerInboxEntries(projectRoot, "ufoo-agent", { num: 100 });
|
|
214
214
|
const useGlobalProjectRouter = isGlobalController;
|
|
215
|
-
const promptRunner = runPromptWithAssistant;
|
|
216
215
|
const ufooAgentOptions = useGlobalProjectRouter ? { routingMode: "global-router" } : { controllerMode };
|
|
217
216
|
let nextRequestMeta = requestMeta;
|
|
218
|
-
|
|
217
|
+
const hasExplicitRequestMeta = Object.keys(nextRequestMeta).length > 0;
|
|
218
|
+
if (hasExplicitRequestMeta && !Object.prototype.hasOwnProperty.call(nextRequestMeta, "agent_execution_path") && controllerMode !== CONTROLLER_MODES.LEGACY) {
|
|
219
219
|
nextRequestMeta = {
|
|
220
220
|
...nextRequestMeta,
|
|
221
221
|
agent_execution_path: controllerMode,
|
|
222
222
|
};
|
|
223
223
|
}
|
|
224
|
+
let forceMainRouterFallback = false;
|
|
224
225
|
|
|
225
226
|
const logGateRouterEvent = (event, details = {}) => {
|
|
226
227
|
controllerObserver.emit(event, details);
|
|
@@ -275,6 +276,7 @@ async function handlePromptRequest(options = {}) {
|
|
|
275
276
|
attachGateRouterMeta("provider_error", {
|
|
276
277
|
error: routed && routed.error ? routed.error : "route_agent_failed",
|
|
277
278
|
});
|
|
279
|
+
forceMainRouterFallback = true;
|
|
278
280
|
logGateRouterEvent("controller.gate_router_upgraded", {
|
|
279
281
|
reason: "provider_error",
|
|
280
282
|
fallback_used: "main_router",
|
|
@@ -295,6 +297,7 @@ async function handlePromptRequest(options = {}) {
|
|
|
295
297
|
confidence: Number(route.confidence || 0),
|
|
296
298
|
route_reason: route.reason || "",
|
|
297
299
|
});
|
|
300
|
+
forceMainRouterFallback = true;
|
|
298
301
|
logGateRouterEvent("controller.gate_router_upgraded", {
|
|
299
302
|
reason: upgradeReason,
|
|
300
303
|
decision: route.decision || "",
|
|
@@ -339,6 +342,7 @@ async function handlePromptRequest(options = {}) {
|
|
|
339
342
|
route_reason: route.reason || "",
|
|
340
343
|
error: err && err.message ? err.message : String(err),
|
|
341
344
|
});
|
|
345
|
+
forceMainRouterFallback = true;
|
|
342
346
|
logGateRouterEvent("controller.gate_router_upgraded", {
|
|
343
347
|
reason: "dispatch_failed",
|
|
344
348
|
target: route.target,
|
|
@@ -351,6 +355,11 @@ async function handlePromptRequest(options = {}) {
|
|
|
351
355
|
}
|
|
352
356
|
|
|
353
357
|
const promptText = buildPromptWithPrivateReports(req.text || "", privateReports, nextRequestMeta);
|
|
358
|
+
const promptRunner = loopRuntime.enabled
|
|
359
|
+
&& !forceMainRouterFallback
|
|
360
|
+
&& typeof injectedLoopRunner === "function"
|
|
361
|
+
? injectedLoopRunner
|
|
362
|
+
: runPromptWithAssistant;
|
|
354
363
|
|
|
355
364
|
try {
|
|
356
365
|
const handled = await promptRunner({
|
|
@@ -253,6 +253,8 @@ function findOwningGroup(projectRoot, subscriberId = "") {
|
|
|
253
253
|
&& (
|
|
254
254
|
!liveNickname
|
|
255
255
|
|| asTrimmedString(member.nickname) === liveNickname
|
|
256
|
+
|| asTrimmedString(member.scoped_nickname) === liveNickname
|
|
257
|
+
|| asTrimmedString(member.scoped_nickname) === liveScopedNickname
|
|
256
258
|
|| asTrimmedString(member.runtime_nickname) === liveNickname
|
|
257
259
|
|| asTrimmedString(member.runtime_nickname) === liveScopedNickname
|
|
258
260
|
)
|
package/src/group/bootstrap.js
CHANGED
|
@@ -5,7 +5,7 @@ const crypto = require("crypto");
|
|
|
5
5
|
const SHARED_UFOO_PROTOCOL = [
|
|
6
6
|
"ufoo protocol:",
|
|
7
7
|
"- At session start, sync shared context with `ufoo ctx decisions -l` and `ufoo ctx decisions -n 1`.",
|
|
8
|
-
"-
|
|
8
|
+
"- Default to no new decision. Record one ONLY for important, plan-level knowledge: architectural choices, multi-option trade-off analysis, cross-agent coordination decisions, or plans that affect other agents. Do NOT record routine findings, simple bug fixes, trivial observations, or generic plan/evaluation/recommendation requests. Durable project facts belong in shared memory, not decisions. Use `ufoo ctx decisions new \"Title\"` BEFORE acting only when that bar is met.",
|
|
9
9
|
"- Use `ufoo bus send <target-nickname> \"<message>\"` for agent-to-agent handoffs.",
|
|
10
10
|
"- If you receive pending bus work, execute it immediately, reply to the sender, then `ufoo bus ack \"$UFOO_SUBSCRIBER_ID\"`.",
|
|
11
11
|
"- Use `ufoo report` for controller/runtime status updates, not as a substitute for direct handoffs.",
|