u-foo 2.4.5 → 2.4.6

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/README.md CHANGED
@@ -322,8 +322,7 @@ src/
322
322
  online/ relay client/server/runner/token helpers
323
323
  ```
324
324
 
325
- See [PROJECT.md](PROJECT.md) for the maintainer-facing map and
326
- [docs/source-structure.md](docs/source-structure.md) for detailed package
325
+ See [PROJECT.md](PROJECT.md) for the maintainer-facing map and detailed package
327
326
  ownership.
328
327
 
329
328
  ## Development
@@ -340,7 +339,6 @@ Useful checks:
340
339
  ```bash
341
340
  npm run test:watch
342
341
  npm run test:coverage
343
- npm run bench:global-switch
344
342
  ```
345
343
 
346
344
  The repository is CommonJS, targets Node.js 18+, and has no build step.
@@ -38,7 +38,7 @@ Versioning is optional but recommended for auditability.
38
38
  ```
39
39
  context/ # This repo
40
40
  ├── README.md # This file
41
- ├── SKILLS/uctx/SKILL.md # Canonical decision format + workflow
41
+ ├── ../../SKILLS/uctx/SKILL.md # Canonical decision format + workflow
42
42
  └── .ufoo/context/ # Local project context for this repo (ignored; not part of protocol distribution)
43
43
  ```
44
44
 
@@ -47,7 +47,7 @@ context/ # This repo
47
47
  1. Read installed module from `~/.ufoo/modules/context/`
48
48
  2. Read/write decisions in `<project>/.ufoo/context/decisions/`
49
49
  3. **Never write to global** — only to project
50
- 4. Follow the decision format in `SKILLS/uctx/SKILL.md`
50
+ 4. Follow the decision format in the package-level `SKILLS/uctx/SKILL.md`
51
51
 
52
52
  ## Validate
53
53
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "u-foo",
3
- "version": "2.4.5",
3
+ "version": "2.4.6",
4
4
  "description": "Multi-Agent Workspace Protocol. Just add u. claude → uclaude, codex → ucodex.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://ufoo.dev",
@@ -20,8 +20,7 @@
20
20
  "uclaude": "bin/uclaude.js",
21
21
  "ucodex": "bin/ucodex.js",
22
22
  "uagy": "bin/uagy.js",
23
- "ucode": "bin/ucode.js",
24
- "ucode-core": "bin/ucode-core.js"
23
+ "ucode": "bin/ucode.js"
25
24
  },
26
25
  "files": [
27
26
  "bin/",
@@ -42,10 +41,7 @@
42
41
  "postinstall": "node scripts/postinstall.js",
43
42
  "test": "jest",
44
43
  "test:watch": "jest --watch",
45
- "test:coverage": "jest --coverage",
46
- "ink:demo": "node scripts/ink-demo.js",
47
- "ink:smoke": "node scripts/ink-smoke.js",
48
- "bench:global-switch": "node scripts/global-chat-switch-benchmark.js"
44
+ "test:coverage": "jest --coverage"
49
45
  },
50
46
  "dependencies": {
51
47
  "@anthropic-ai/claude-agent-sdk": "^0.2.138",
@@ -30,11 +30,9 @@ for (const platform of platforms) {
30
30
  }
31
31
  }
32
32
 
33
- // Collect all skill sources from package
33
+ // Collect all skill sources from the package-level SKILLS directory.
34
34
  function collectSkillSources(pkgRoot) {
35
35
  const sources = [];
36
-
37
- // Top-level SKILLS/
38
36
  const topSkills = path.join(pkgRoot, "SKILLS");
39
37
  if (fs.existsSync(topSkills)) {
40
38
  for (const entry of fs.readdirSync(topSkills, { withFileTypes: true })) {
@@ -46,24 +44,6 @@ function collectSkillSources(pkgRoot) {
46
44
  }
47
45
  }
48
46
  }
49
-
50
- // modules/*/SKILLS/
51
- const modulesDir = path.join(pkgRoot, "modules");
52
- if (fs.existsSync(modulesDir)) {
53
- for (const mod of fs.readdirSync(modulesDir, { withFileTypes: true })) {
54
- if (!mod.isDirectory()) continue;
55
- const modSkills = path.join(modulesDir, mod.name, "SKILLS");
56
- if (!fs.existsSync(modSkills)) continue;
57
- for (const entry of fs.readdirSync(modSkills, { withFileTypes: true })) {
58
- if (!entry.isDirectory()) continue;
59
- const skillMd = path.join(modSkills, entry.name, "SKILL.md");
60
- if (fs.existsSync(skillMd)) {
61
- sources.push({ name: entry.name, dir: path.join(modSkills, entry.name), md: skillMd });
62
- }
63
- }
64
- }
65
- }
66
-
67
47
  return sources;
68
48
  }
69
49
 
@@ -19,6 +19,7 @@ const { parseIntervalMs, formatIntervalMs } = require("./cronScheduler");
19
19
  const { isGlobalControllerProjectRoot, resolveGlobalControllerUfooDir } = require("../../runtime/projects");
20
20
  const { loadPromptProfileRegistry } = require("../../orchestration/groups/promptProfiles");
21
21
  const { resolveSoloAgentType } = require("../../orchestration/solo/commands");
22
+ const { restartDaemonLifecycle } = require("../../runtime/daemon/restart");
22
23
  const {
23
24
  inspectDirectAuthStatus,
24
25
  formatDirectAuthStatus,
@@ -269,15 +270,19 @@ function createCommandExecutor(options = {}) {
269
270
  }
270
271
 
271
272
  statusMsg("{gray-fg}⚙{/gray-fg} Restarting daemon...");
272
- stopDaemon(targetRoot, { source: "chat-command:/daemon restart" });
273
- await sleep(500);
274
- if (isDaemonRunning(targetRoot)) {
273
+ const result = await restartDaemonLifecycle({
274
+ projectRoot: targetRoot,
275
+ isRunning: isDaemonRunning,
276
+ stopDaemon,
277
+ startDaemon,
278
+ stopOptions: { source: "chat-command:/daemon restart" },
279
+ sleep,
280
+ });
281
+ if (result.error === "failed_to_stop") {
275
282
  statusMsg("{gray-fg}✗{/gray-fg} Failed to stop daemon");
276
283
  return;
277
284
  }
278
- startDaemon(targetRoot);
279
- await sleep(1000);
280
- if (isDaemonRunning(targetRoot)) {
285
+ if (result.ok) {
281
286
  statusMsg("{gray-fg}✓{/gray-fg} Daemon restarted");
282
287
  } else {
283
288
  statusMsg("{gray-fg}✗{/gray-fg} Failed to restart daemon");
@@ -12,8 +12,10 @@ function createDaemonCoordinator(options = {}) {
12
12
  logMessage,
13
13
  stopDaemon,
14
14
  startDaemon,
15
+ isDaemonRunning,
15
16
  daemonConnection,
16
17
  restartDaemon,
18
+ sleep,
17
19
  } = options;
18
20
 
19
21
  const connectClientFn = connectClient
@@ -37,9 +39,11 @@ function createDaemonCoordinator(options = {}) {
37
39
  projectRoot,
38
40
  stopDaemon,
39
41
  startDaemon,
42
+ isDaemonRunning,
40
43
  daemonConnection: connection,
41
44
  logMessage,
42
45
  resolveStatusLine,
46
+ sleep,
43
47
  });
44
48
  let switchProjectChain = Promise.resolve();
45
49
 
@@ -1,4 +1,5 @@
1
1
  const { restartLocks } = require("./daemonTransport");
2
+ const { restartDaemonLifecycle } = require("../../runtime/daemon/restart");
2
3
 
3
4
  function resolveDaemonConnection(daemonConnection) {
4
5
  return typeof daemonConnection === "function" ? daemonConnection() : daemonConnection;
@@ -9,9 +10,11 @@ function restartDaemonFlow(options = {}) {
9
10
  projectRoot,
10
11
  stopDaemon,
11
12
  startDaemon,
13
+ isDaemonRunning,
12
14
  daemonConnection,
13
15
  logMessage,
14
16
  resolveStatusLine = null,
17
+ sleep,
15
18
  } = options;
16
19
 
17
20
  const statusMsg = resolveStatusLine || ((text) => logMessage("status", text));
@@ -26,14 +29,21 @@ function restartDaemonFlow(options = {}) {
26
29
  if (connection) {
27
30
  connection.close();
28
31
  }
29
- stopDaemon(projectRoot);
30
- startDaemon(projectRoot);
31
- const connected = connection ? await connection.connect() : false;
32
- if (connected) {
33
- if (typeof connection.requestStatus === "function") {
34
- connection.requestStatus();
35
- }
32
+ const result = await restartDaemonLifecycle({
33
+ projectRoot,
34
+ isRunning: isDaemonRunning,
35
+ stopDaemon,
36
+ startDaemon,
37
+ connect: connection ? () => connection.connect() : null,
38
+ requestStatus: connection && typeof connection.requestStatus === "function"
39
+ ? () => connection.requestStatus()
40
+ : null,
41
+ sleep,
42
+ });
43
+ if (result.ok) {
36
44
  statusMsg("{gray-fg}✓{/gray-fg} Daemon reconnected");
45
+ } else if (result.error === "failed_to_stop") {
46
+ statusMsg("{gray-fg}✗{/gray-fg} Failed to stop daemon");
37
47
  } else {
38
48
  statusMsg("{gray-fg}✗{/gray-fg} Failed to reconnect to daemon");
39
49
  }
@@ -15,25 +15,10 @@ class SkillsManager {
15
15
  */
16
16
  findSkillRoots() {
17
17
  const roots = [];
18
-
19
- // 检查 SKILLS 目录
20
18
  const mainSkills = path.join(this.repoRoot, "SKILLS");
21
19
  if (fs.existsSync(mainSkills)) {
22
20
  roots.push(mainSkills);
23
21
  }
24
-
25
- // 检查 modules 中的 SKILLS
26
- const modulesDir = path.join(this.repoRoot, "modules");
27
- if (fs.existsSync(modulesDir)) {
28
- const modules = fs.readdirSync(modulesDir);
29
- for (const module of modules) {
30
- const moduleSkills = path.join(modulesDir, module, "SKILLS");
31
- if (fs.existsSync(moduleSkills)) {
32
- roots.push(moduleSkills);
33
- }
34
- }
35
- }
36
-
37
22
  return roots;
38
23
  }
39
24
 
@@ -1675,7 +1675,7 @@ async function runCli(argv) {
1675
1675
  program.addHelpText(
1676
1676
  "after",
1677
1677
  `\nNotes:\n - If 'ufoo' isn't in PATH, run it via ${chalk.cyan(
1678
- "./bin/ufoo"
1678
+ "./bin/ufoo.js"
1679
1679
  )} (repo) or install globally via npm.\n - For bus notifications inside Codex, prefer ${chalk.cyan(
1680
1680
  "ufoo bus alert"
1681
1681
  )} / ${chalk.cyan("ufoo bus listen")} (no IME issues).\n`
@@ -15,7 +15,7 @@
15
15
  - `ucode> ...` free-form task input routes to model-backed planner path
16
16
  - `tool/run` commands stay available as deterministic fallback
17
17
  - Dispatcher: `runToolCall({ tool, args }, { workspaceRoot })`
18
- - Queue runtime:
19
- - `ucode-core submit`
20
- - `ucode-core run-once`
21
- - `ucode-core list`
18
+ - Queue runtime internals:
19
+ - `submitTask`
20
+ - `runOnce`
21
+ - `listResults`
package/src/code/cli.js CHANGED
@@ -78,7 +78,7 @@ function parseArgs(argv = []) {
78
78
 
79
79
  function usage() {
80
80
  return [
81
- "ucode-core native runtime CLI",
81
+ "ucode native runtime internals",
82
82
  "",
83
83
  "Commands:",
84
84
  " submit --tool <read|write|edit|bash> --args-json <json> [--workspace <path>] [--task-id <id>]",
@@ -206,13 +206,11 @@ function isLikelyPiCoreCommand(command = "", args = []) {
206
206
  const cmdText = String(command || "").trim();
207
207
  const cmdBase = path.basename(cmdText).toLowerCase();
208
208
  if (cmdBase === "ucode" || cmdBase === "ucode.exe") return true;
209
- if (cmdBase === "ucode-core" || cmdBase === "ucode-core.exe") return true;
210
209
 
211
210
  const joined = [cmdText, ...(Array.isArray(args) ? args : [])]
212
211
  .map((part) => String(part || "").toLowerCase())
213
212
  .join(" ");
214
213
  if (!joined) return false;
215
- if (joined.includes("ucode-core")) return true;
216
214
  if (joined.includes("/src/code/agent.js")) return true;
217
215
  if (joined.includes("\\src\\code\\agent.js")) return true;
218
216
  return false;
@@ -287,55 +285,30 @@ function resolveCandidateCoreRoot({
287
285
  }
288
286
 
289
287
  function resolveNativeFallbackCommand({ env = process.env } = {}) {
290
- const candidates = [
291
- path.resolve(__dirname, "..", "agent.js"),
292
- path.resolve(__dirname, "..", "..", "..", "bin", "ucode-core.js"),
293
- ];
294
- for (const entry of candidates) {
295
- try {
296
- if (isReadableFile(entry)) {
297
- if (entry.endsWith("agent.js")) {
298
- return {
299
- command: process.execPath,
300
- args: [entry],
301
- root: path.resolve(__dirname, ".."),
302
- kind: "native",
303
- available: true,
304
- resolvedPath: entry,
305
- };
306
- }
307
- return {
308
- command: process.execPath,
309
- args: [entry, "agent"],
310
- root: path.resolve(__dirname, ".."),
311
- kind: "native",
312
- available: true,
313
- resolvedPath: entry,
314
- };
315
- }
316
- } catch {
317
- // ignore
288
+ void env;
289
+ const entry = path.resolve(__dirname, "..", "agent.js");
290
+ try {
291
+ if (isReadableFile(entry)) {
292
+ return {
293
+ command: process.execPath,
294
+ args: [entry],
295
+ root: path.resolve(__dirname, ".."),
296
+ kind: "native",
297
+ available: true,
298
+ resolvedPath: entry,
299
+ };
318
300
  }
319
- }
320
- const resolvedCommand = resolveExecutableFromPath("ucode-core", env);
321
- if (resolvedCommand) {
322
- return {
323
- command: "ucode-core",
324
- args: ["agent"],
325
- root: "",
326
- kind: "native",
327
- available: true,
328
- resolvedPath: resolvedCommand,
329
- };
301
+ } catch {
302
+ // ignore
330
303
  }
331
304
  return {
332
- command: "ucode-core",
333
- args: ["agent"],
334
- root: "",
305
+ command: process.execPath,
306
+ args: [entry],
307
+ root: path.resolve(__dirname, ".."),
335
308
  kind: "native",
336
309
  available: false,
337
310
  resolvedPath: "",
338
- missingReason: "src/code/agent.js not found and ucode-core is not available on PATH",
311
+ missingReason: "src/code/agent.js not found",
339
312
  };
340
313
  }
341
314
 
@@ -63,19 +63,6 @@ function defaultSkillRoots({
63
63
 
64
64
  const root = path.resolve(String(repoRoot || repoRootFromHere()));
65
65
  roots.push({ path: path.join(root, "SKILLS"), scope: "builtin", source: "ufoo" });
66
- const modulesDir = path.join(root, "modules");
67
- try {
68
- for (const entry of fs.readdirSync(modulesDir, { withFileTypes: true })) {
69
- if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
70
- roots.push({
71
- path: path.join(modulesDir, entry.name, "SKILLS"),
72
- scope: "builtin",
73
- source: `ufoo-module:${entry.name}`,
74
- });
75
- }
76
- } catch {
77
- // Modules are optional in tests and local installs.
78
- }
79
66
 
80
67
  const seen = new Set();
81
68
  return roots
@@ -87,6 +87,7 @@ class ContextDoctor {
87
87
  */
88
88
  lintProtocol() {
89
89
  const moduleRoot = path.join(this.projectRoot, "modules", "context");
90
+ const repoSkill = path.join(this.projectRoot, "SKILLS", "uctx", "SKILL.md");
90
91
 
91
92
  if (!fs.existsSync(moduleRoot)) {
92
93
  console.log("No protocol module found (skipping protocol lint)");
@@ -97,10 +98,7 @@ class ContextDoctor {
97
98
 
98
99
  // Check minimal module files
99
100
  this.checkFile(path.join(moduleRoot, "README.md"), "README.md");
100
- this.checkFile(
101
- path.join(moduleRoot, "SKILLS", "uctx", "SKILL.md"),
102
- "SKILLS/uctx/SKILL.md"
103
- );
101
+ this.checkFile(repoSkill, "SKILLS/uctx/SKILL.md");
104
102
 
105
103
  return !this.failed;
106
104
  }
@@ -10,7 +10,7 @@ const WebSocket = require("ws");
10
10
  * ufoo-online (Phase 1)
11
11
  *
12
12
  * Minimal WebSocket relay implementing hello/auth + join/leave + event routing.
13
- * Intended WebSocket path: /ufoo/online (see docs/ufoo-online/PROTOCOL.md)
13
+ * Intended WebSocket path: /ufoo/online.
14
14
  */
15
15
  class OnlineServer extends EventEmitter {
16
16
  constructor(options = {}) {