u-foo 2.4.4 → 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 +18 -4
- package/README.zh-CN.md +16 -1
- package/bin/ufoo.js +7 -0
- 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 +17 -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/contracts/mcpContract.js +39 -0
- package/src/runtime/daemon/mcpServer.js +786 -0
- 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
|
@@ -27,6 +27,7 @@ Package: [u-foo on npm](https://www.npmjs.com/package/u-foo)
|
|
|
27
27
|
- Launch modes for internal, tmux, host, Terminal.app, and iTerm2 workflows.
|
|
28
28
|
- Built-in group templates for launching and orchestrating multi-agent workflows.
|
|
29
29
|
- `ucode`, a native ufoo coding-agent runtime.
|
|
30
|
+
- `ufoo mcp`, a local global MCP bridge for external MCP-capable agents.
|
|
30
31
|
|
|
31
32
|
## Requirements
|
|
32
33
|
|
|
@@ -56,7 +57,7 @@ Installed binaries:
|
|
|
56
57
|
|
|
57
58
|
| Binary | Purpose |
|
|
58
59
|
|---|---|
|
|
59
|
-
| `ufoo` | Main CLI, chat dashboard, daemon, groups, bus, context, memory, reports, and online helpers. |
|
|
60
|
+
| `ufoo` | Main CLI, chat dashboard, daemon, local global MCP bridge, groups, bus, context, memory, reports, and online helpers. |
|
|
60
61
|
| `uclaude` | Claude Code wrapper with ufoo bootstrap and bus identity. |
|
|
61
62
|
| `ucodex` | Codex wrapper with ufoo bootstrap and bus identity. |
|
|
62
63
|
| `uagy` | Antigravity wrapper with ufoo bootstrap and bus identity. |
|
|
@@ -96,6 +97,16 @@ Use global chat mode to switch between registered projects:
|
|
|
96
97
|
ufoo -g
|
|
97
98
|
```
|
|
98
99
|
|
|
100
|
+
For MCP-capable clients, configure the global stdio bridge once:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
ufoo mcp
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The MCP bridge connects to the home-scoped global controller daemon and routes
|
|
107
|
+
project-scoped tools through the global project registry. It is not a separate
|
|
108
|
+
per-project MCP server mode.
|
|
109
|
+
|
|
99
110
|
## Runtime Model
|
|
100
111
|
|
|
101
112
|
```text
|
|
@@ -107,6 +118,11 @@ ufoo / ufoo chat
|
|
|
107
118
|
-> agents launch/providers/internal/controller/activity
|
|
108
119
|
-> coordination bus/context/memory/history/report/state/status
|
|
109
120
|
-> shared controller/worker tools and native ucode tools
|
|
121
|
+
|
|
122
|
+
ufoo mcp
|
|
123
|
+
-> home-scoped global controller daemon
|
|
124
|
+
-> ~/.ufoo/projects/runtime
|
|
125
|
+
-> selected project daemon for bus/report/activity state
|
|
110
126
|
```
|
|
111
127
|
|
|
112
128
|
Chat is a UI client. The daemon owns project runtime state. Agents communicate
|
|
@@ -306,8 +322,7 @@ src/
|
|
|
306
322
|
online/ relay client/server/runner/token helpers
|
|
307
323
|
```
|
|
308
324
|
|
|
309
|
-
See [PROJECT.md](PROJECT.md) for the maintainer-facing map and
|
|
310
|
-
[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
|
|
311
326
|
ownership.
|
|
312
327
|
|
|
313
328
|
## Development
|
|
@@ -324,7 +339,6 @@ Useful checks:
|
|
|
324
339
|
```bash
|
|
325
340
|
npm run test:watch
|
|
326
341
|
npm run test:coverage
|
|
327
|
-
npm run bench:global-switch
|
|
328
342
|
```
|
|
329
343
|
|
|
330
344
|
The repository is CommonJS, targets Node.js 18+, and has no build step.
|
package/README.zh-CN.md
CHANGED
|
@@ -25,6 +25,7 @@ npm 包:[u-foo](https://www.npmjs.com/package/u-foo)
|
|
|
25
25
|
- 支持 internal、tmux、host、Terminal.app、iTerm2 等启动模式。
|
|
26
26
|
- 内置 group 模板,用于启动和编排多 Agent 工作流。
|
|
27
27
|
- 提供原生 ufoo coding-agent 运行时 `ucode`。
|
|
28
|
+
- 提供 `ufoo mcp` 本机 global MCP bridge,供支持 MCP 的外部 Agent 接入。
|
|
28
29
|
|
|
29
30
|
## 环境要求
|
|
30
31
|
|
|
@@ -54,7 +55,7 @@ npm link
|
|
|
54
55
|
|
|
55
56
|
| 命令 | 用途 |
|
|
56
57
|
|---|---|
|
|
57
|
-
| `ufoo` | 主 CLI、chat 仪表盘、daemon、group、bus、context、memory、report 和 online helper。 |
|
|
58
|
+
| `ufoo` | 主 CLI、chat 仪表盘、daemon、本机 global MCP bridge、group、bus、context、memory、report 和 online helper。 |
|
|
58
59
|
| `uclaude` | Claude Code 包装器,注入 ufoo bootstrap 和 bus 身份。 |
|
|
59
60
|
| `ucodex` | Codex 包装器,注入 ufoo bootstrap 和 bus 身份。 |
|
|
60
61
|
| `uagy` | Antigravity 包装器,注入 ufoo bootstrap 和 bus 身份。 |
|
|
@@ -94,6 +95,15 @@ ucode
|
|
|
94
95
|
ufoo -g
|
|
95
96
|
```
|
|
96
97
|
|
|
98
|
+
给支持 MCP 的客户端使用时,只需要配置一次全局 stdio bridge:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
ufoo mcp
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
MCP bridge 会连接 home 级 global controller daemon,并通过全局项目 registry
|
|
105
|
+
把项目级工具路由到对应项目 daemon。它不是每个项目单独部署一个 MCP server 的模式。
|
|
106
|
+
|
|
97
107
|
## 运行模型
|
|
98
108
|
|
|
99
109
|
```text
|
|
@@ -105,6 +115,11 @@ ufoo / ufoo chat
|
|
|
105
115
|
-> agents launch/providers/internal/controller/activity
|
|
106
116
|
-> coordination bus/context/memory/history/report/state/status
|
|
107
117
|
-> shared controller/worker tools and native ucode tools
|
|
118
|
+
|
|
119
|
+
ufoo mcp
|
|
120
|
+
-> home-scoped global controller daemon
|
|
121
|
+
-> ~/.ufoo/projects/runtime
|
|
122
|
+
-> selected project daemon for bus/report/activity state
|
|
108
123
|
```
|
|
109
124
|
|
|
110
125
|
Chat 是 UI client。daemon 拥有项目运行态。Agent 通过 bus queue、prompt
|
package/bin/ufoo.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
/* eslint-disable no-console */
|
|
3
3
|
const { runCli } = require("../src/app/cli/run");
|
|
4
4
|
const { runDaemonCli } = require("../src/runtime/daemon/run");
|
|
5
|
+
const { runMcpServer } = require("../src/runtime/daemon/mcpServer");
|
|
5
6
|
const { runChat } = require("../src/app/chat");
|
|
6
7
|
const { runInternalRunner } = require("../src/agents/internal/internalRunner");
|
|
7
8
|
const { resolveGlobalControllerProjectRoot } = require("../src/runtime/projects");
|
|
@@ -26,6 +27,12 @@ async function main() {
|
|
|
26
27
|
runDaemonCli(["daemon", ...argv.slice(1)]);
|
|
27
28
|
return;
|
|
28
29
|
}
|
|
30
|
+
if (cmd === "mcp") {
|
|
31
|
+
await runMcpServer({
|
|
32
|
+
autoStart: !argv.includes("--no-auto-start"),
|
|
33
|
+
});
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
29
36
|
if (cmd === "agent-runner") {
|
|
30
37
|
const agentType = argv[1] || "codex";
|
|
31
38
|
const extraArgs = argv.slice(2);
|
|
@@ -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
|
@@ -585,6 +585,16 @@ async function runCli(argv) {
|
|
|
585
585
|
if (opts.global === true) args.push("-g");
|
|
586
586
|
run(process.execPath, [path.join(repoRoot, "bin", "ufoo.js"), ...args]);
|
|
587
587
|
});
|
|
588
|
+
program
|
|
589
|
+
.command("mcp")
|
|
590
|
+
.description("Run the local global ufoo MCP bridge over stdio")
|
|
591
|
+
.option("--no-auto-start", "Do not auto-start the home-scoped global controller daemon")
|
|
592
|
+
.action((opts) => {
|
|
593
|
+
const repoRoot = getPackageRoot();
|
|
594
|
+
const args = ["mcp"];
|
|
595
|
+
if (opts.autoStart === false) args.push("--no-auto-start");
|
|
596
|
+
run(process.execPath, [path.join(repoRoot, "bin", "ufoo.js"), ...args]);
|
|
597
|
+
});
|
|
588
598
|
const project = program.command("project").description("Project runtime commands");
|
|
589
599
|
project
|
|
590
600
|
.command("list")
|
|
@@ -1665,7 +1675,7 @@ async function runCli(argv) {
|
|
|
1665
1675
|
program.addHelpText(
|
|
1666
1676
|
"after",
|
|
1667
1677
|
`\nNotes:\n - If 'ufoo' isn't in PATH, run it via ${chalk.cyan(
|
|
1668
|
-
"./bin/ufoo"
|
|
1678
|
+
"./bin/ufoo.js"
|
|
1669
1679
|
)} (repo) or install globally via npm.\n - For bus notifications inside Codex, prefer ${chalk.cyan(
|
|
1670
1680
|
"ufoo bus alert"
|
|
1671
1681
|
)} / ${chalk.cyan("ufoo bus listen")} (no IME issues).\n`
|
|
@@ -1771,6 +1781,12 @@ async function runCli(argv) {
|
|
|
1771
1781
|
run(resolveNodeExecutable(), [path.join(repoRoot, "bin", "ufoo.js"), ...chatArgs]);
|
|
1772
1782
|
return;
|
|
1773
1783
|
}
|
|
1784
|
+
if (cmd === "mcp") {
|
|
1785
|
+
const mcpArgs = ["mcp"];
|
|
1786
|
+
if (rest.includes("--no-auto-start")) mcpArgs.push("--no-auto-start");
|
|
1787
|
+
run(resolveNodeExecutable(), [path.join(repoRoot, "bin", "ufoo.js"), ...mcpArgs]);
|
|
1788
|
+
return;
|
|
1789
|
+
}
|
|
1774
1790
|
if (cmd === "project") {
|
|
1775
1791
|
const sub = String(rest[0] || "list").trim().toLowerCase();
|
|
1776
1792
|
const outputJson = rest.includes("--json");
|
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 = {}) {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const MCP_PROTOCOL_VERSION = "2024-11-05";
|
|
4
|
+
|
|
5
|
+
const MCP_ERROR_CODES = Object.freeze({
|
|
6
|
+
PARSE_ERROR: -32700,
|
|
7
|
+
INVALID_REQUEST: -32600,
|
|
8
|
+
METHOD_NOT_FOUND: -32601,
|
|
9
|
+
INVALID_PARAMS: -32602,
|
|
10
|
+
INTERNAL_ERROR: -32603,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
function createJsonRpcResult(id, result) {
|
|
14
|
+
return {
|
|
15
|
+
jsonrpc: "2.0",
|
|
16
|
+
id,
|
|
17
|
+
result,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function createJsonRpcError(id, code, message, data = undefined) {
|
|
22
|
+
const error = {
|
|
23
|
+
code,
|
|
24
|
+
message: String(message || "MCP request failed"),
|
|
25
|
+
};
|
|
26
|
+
if (data !== undefined) error.data = data;
|
|
27
|
+
return {
|
|
28
|
+
jsonrpc: "2.0",
|
|
29
|
+
id: id === undefined ? null : id,
|
|
30
|
+
error,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = {
|
|
35
|
+
MCP_PROTOCOL_VERSION,
|
|
36
|
+
MCP_ERROR_CODES,
|
|
37
|
+
createJsonRpcResult,
|
|
38
|
+
createJsonRpcError,
|
|
39
|
+
};
|