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 +1 -3
- package/modules/context/README.md +2 -2
- package/package.json +3 -7
- package/scripts/postinstall.js +1 -21
- package/src/app/chat/commandExecutor.js +11 -6
- package/src/app/chat/daemonCoordinator.js +4 -0
- package/src/app/chat/daemonReconnect.js +17 -7
- package/src/app/cli/features/skills.js +0 -15
- package/src/app/cli/run.js +1 -1
- package/src/code/README.md +4 -4
- package/src/code/cli.js +1 -1
- package/src/code/launcher/ucode.js +18 -45
- package/src/code/skills/loader.js +0 -13
- package/src/coordination/context/doctor.js +2 -4
- package/src/online/server.js +1 -1
- package/src/runtime/daemon/restart.js +293 -0
- package/src/runtime/daemon/run.js +31 -37
- package/src/ui/MIGRATION.md +8 -10
- package/src/ui/ink/ChatApp.js +11 -5
- package/bin/ucode-core.js +0 -15
- package/bin/ufoo +0 -71
- package/scripts/chat-app-smoke.js +0 -30
- package/scripts/global-chat-switch-benchmark.js +0 -406
- package/scripts/ink-demo.js +0 -23
- package/scripts/ink-smoke.js +0 -30
- package/scripts/ucode-app-smoke.js +0 -36
- /package/{modules/bus/SKILLS → SKILLS}/ubus/SKILL.md +0 -0
- /package/{modules/context/SKILLS → SKILLS}/uctx/SKILL.md +0 -0
- /package/{modules/online/SKILLS → SKILLS}/ufoo-online/SKILL.md +0 -0
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
|
|
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.
|
|
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",
|
package/scripts/postinstall.js
CHANGED
|
@@ -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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
package/src/app/cli/run.js
CHANGED
|
@@ -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`
|
package/src/code/README.md
CHANGED
|
@@ -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
|
-
- `
|
|
20
|
-
- `
|
|
21
|
-
- `
|
|
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
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
|
|
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:
|
|
333
|
-
args: [
|
|
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
|
|
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
|
}
|
package/src/online/server.js
CHANGED
|
@@ -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
|
|
13
|
+
* Intended WebSocket path: /ufoo/online.
|
|
14
14
|
*/
|
|
15
15
|
class OnlineServer extends EventEmitter {
|
|
16
16
|
constructor(options = {}) {
|