u-foo 2.3.18 → 2.3.20
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 +8 -2
- package/README.zh-CN.md +8 -2
- package/package.json +1 -1
- package/src/chat/agentViewController.js +98 -11
- package/src/chat/commandExecutor.js +3 -3
- package/src/chat/commands.js +3 -3
- package/src/chat/daemonMessageRouter.js +18 -2
- package/src/chat/index.js +23 -0
- package/src/code/agent.js +118 -4
- package/src/code/cli.js +47 -0
- package/src/code/prompts/index.js +9 -0
- package/src/code/skills/index.js +74 -0
- package/src/code/skills/injection.js +120 -0
- package/src/code/skills/loader.js +218 -0
- package/src/code/skills/render.js +46 -0
- package/src/daemon/index.js +196 -12
- package/src/shared/eventContract.js +1 -0
package/README.md
CHANGED
|
@@ -143,7 +143,9 @@ ufoo resume <ucode|uclaude|ucodex|nickname>
|
|
|
143
143
|
ufoo recover list
|
|
144
144
|
```
|
|
145
145
|
|
|
146
|
-
Common chat commands include `/status`, `/bus list`, `/bus status`, `/settings`, `/project list`, `/project switch <index|path>`, `/open <path>`, `/resume list`, `/group status`, and `@nickname <message>`.
|
|
146
|
+
Common chat commands include `/status`, `/bus list`, `/bus status`, `/settings`, `/project list`, `/project switch <index|path>`, `/open <path>`, `/resume list`, `/group status`, `/skills`, and `@nickname <message>`.
|
|
147
|
+
|
|
148
|
+
In `ufoo chat`, `/skills` lists ufoo's built-in available skills and preset workflow capabilities so users can discover and choose them. It does not execute a task by itself, and it is not a private capability list for any one agent.
|
|
147
149
|
|
|
148
150
|
### Event Bus
|
|
149
151
|
|
|
@@ -197,7 +199,7 @@ Use decisions sparingly for plan-level constraints. Durable project facts belong
|
|
|
197
199
|
| Online | `ufoo online server|token|room|channel|connect|send|inbox` |
|
|
198
200
|
| History | `ufoo history build|show|prompt` |
|
|
199
201
|
| Skills | `ufoo skills list|install` |
|
|
200
|
-
| Chat
|
|
202
|
+
| Chat commands | `/skills`, `/settings`, `/settings agent`, `/settings router`, `/settings ucode` |
|
|
201
203
|
|
|
202
204
|
### Groups
|
|
203
205
|
|
|
@@ -246,8 +248,12 @@ Use the low-level queue runtime:
|
|
|
246
248
|
ucode-core submit --tool read --args-json '{"path":"README.md"}' --json
|
|
247
249
|
ucode-core run-once --json
|
|
248
250
|
ucode-core list --json
|
|
251
|
+
ucode-core skills list --json
|
|
252
|
+
ucode-core skills show <name>
|
|
249
253
|
```
|
|
250
254
|
|
|
255
|
+
`ucode-core skills list` discovers ufoo/ucode built-in and local `SKILL.md` preset workflow capabilities for selection. It lists metadata only; full skill bodies are loaded by ucode only when the user explicitly references a skill such as `$demo` or a direct `SKILL.md` link.
|
|
256
|
+
|
|
251
257
|
## Configuration
|
|
252
258
|
|
|
253
259
|
Project configuration is stored in `.ufoo/config.json`. `ucode` provider credentials are stored globally in `~/.ufoo/config.json` and merged into project config at load time.
|
package/README.zh-CN.md
CHANGED
|
@@ -133,7 +133,9 @@ ufoo resume <ucode|uclaude|ucodex|nickname>
|
|
|
133
133
|
ufoo recover list
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
-
常见 chat 命令包括 `/status`、`/bus list`、`/bus status`、`/settings`、`/project list`、`/project switch <index|path>`、`/open <path>`、`/resume list`、`/group status` 和 `@nickname <message>`。
|
|
136
|
+
常见 chat 命令包括 `/status`、`/bus list`、`/bus status`、`/settings`、`/project list`、`/project switch <index|path>`、`/open <path>`、`/resume list`、`/group status`、`/skills` 和 `@nickname <message>`。
|
|
137
|
+
|
|
138
|
+
在 `ufoo chat` 中,`/skills` 用于列出 ufoo 内置的可用 skills 和预设工作流能力,方便用户发现和选择。它本身不等于执行任务,也不表示某个 agent 的私有能力列表。
|
|
137
139
|
|
|
138
140
|
### 事件总线
|
|
139
141
|
|
|
@@ -211,8 +213,12 @@ ufoo ucode build
|
|
|
211
213
|
ucode-core submit --tool read --args-json '{"path":"README.md"}' --json
|
|
212
214
|
ucode-core run-once --json
|
|
213
215
|
ucode-core list --json
|
|
216
|
+
ucode-core skills list --json
|
|
217
|
+
ucode-core skills show <name>
|
|
214
218
|
```
|
|
215
219
|
|
|
220
|
+
`ucode-core skills list` 用于发现 ufoo/ucode 内置和本地 `SKILL.md` 预设工作流能力,输出的是供选择的元数据;完整 skill 内容只会在用户显式引用 `$demo` 或直接链接某个 `SKILL.md` 时由 ucode 加载。
|
|
221
|
+
|
|
216
222
|
## 命令参考
|
|
217
223
|
|
|
218
224
|
| 范围 | 命令 |
|
|
@@ -228,7 +234,7 @@ ucode-core list --json
|
|
|
228
234
|
| Online | `ufoo online server|token|room|channel|connect|send|inbox` |
|
|
229
235
|
| History | `ufoo history build|show|prompt` |
|
|
230
236
|
| Skills | `ufoo skills list|install` |
|
|
231
|
-
| Chat
|
|
237
|
+
| Chat 命令 | `/skills`, `/settings`, `/settings agent`, `/settings router`, `/settings ucode` |
|
|
232
238
|
|
|
233
239
|
## 配置
|
|
234
240
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
const os = require("os");
|
|
2
2
|
const { version: packageVersion } = require("../../package.json");
|
|
3
3
|
|
|
4
|
+
const ANSI_RESET = "\x1b[0m";
|
|
5
|
+
const CLAUDE_ORANGE = "\x1b[38;2;217;119;87m";
|
|
6
|
+
|
|
4
7
|
function createAgentViewController(options = {}) {
|
|
5
8
|
const {
|
|
6
9
|
screen,
|
|
@@ -39,6 +42,7 @@ function createAgentViewController(options = {}) {
|
|
|
39
42
|
sendBusMessage = () => {},
|
|
40
43
|
sendResize = () => {},
|
|
41
44
|
requestScreenSnapshot = () => {},
|
|
45
|
+
sendBusWatch = () => {},
|
|
42
46
|
} = options;
|
|
43
47
|
|
|
44
48
|
if (!screen || typeof screen.render !== "function") {
|
|
@@ -55,6 +59,8 @@ function createAgentViewController(options = {}) {
|
|
|
55
59
|
let busInputValue = "";
|
|
56
60
|
let busInputCursor = 0;
|
|
57
61
|
let busLogLines = [];
|
|
62
|
+
let busStartupAgentId = "";
|
|
63
|
+
let busStartupLineCount = 0;
|
|
58
64
|
const originalRender = screen.render.bind(screen);
|
|
59
65
|
let renderFrozen = false;
|
|
60
66
|
|
|
@@ -64,10 +70,14 @@ function createAgentViewController(options = {}) {
|
|
|
64
70
|
};
|
|
65
71
|
|
|
66
72
|
function getRows() {
|
|
73
|
+
if (Number.isFinite(screen.height) && screen.height > 0) return screen.height;
|
|
74
|
+
if (Number.isFinite(screen.rows) && screen.rows > 0) return screen.rows;
|
|
67
75
|
return processStdout.rows || 24;
|
|
68
76
|
}
|
|
69
77
|
|
|
70
78
|
function getCols() {
|
|
79
|
+
if (Number.isFinite(screen.width) && screen.width > 0) return screen.width;
|
|
80
|
+
if (Number.isFinite(screen.cols) && screen.cols > 0) return screen.cols;
|
|
71
81
|
return processStdout.columns || 80;
|
|
72
82
|
}
|
|
73
83
|
|
|
@@ -76,6 +86,10 @@ function createAgentViewController(options = {}) {
|
|
|
76
86
|
.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "");
|
|
77
87
|
}
|
|
78
88
|
|
|
89
|
+
function hasAnsi(text = "") {
|
|
90
|
+
return /\x1b(?:\][^\x07\x1b]*(?:\x07|\x1b\\)|\[[0-9;?]*[ -/]*[@-~])/.test(String(text || ""));
|
|
91
|
+
}
|
|
92
|
+
|
|
79
93
|
function clamp(value, min, max) {
|
|
80
94
|
const normalized = Number.isFinite(value) ? Math.floor(value) : min;
|
|
81
95
|
return Math.max(min, Math.min(max, normalized));
|
|
@@ -141,6 +155,39 @@ function createAgentViewController(options = {}) {
|
|
|
141
155
|
return `${truncateToWidth(clean, normalizedWidth - 1).trimEnd()}…`;
|
|
142
156
|
}
|
|
143
157
|
|
|
158
|
+
function fitAnsiText(text = "", width = 1) {
|
|
159
|
+
const normalizedWidth = Math.max(1, width);
|
|
160
|
+
const raw = String(text || "").replace(/\r/g, "");
|
|
161
|
+
if (!hasAnsi(raw)) return fitText(raw, normalizedWidth);
|
|
162
|
+
if (displayWidth(raw) <= normalizedWidth) {
|
|
163
|
+
return padToWidth(raw, normalizedWidth);
|
|
164
|
+
}
|
|
165
|
+
if (normalizedWidth <= 1) return "…";
|
|
166
|
+
|
|
167
|
+
const ansiPattern = /\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b\[[0-9;?]*[ -/]*[@-~]/g;
|
|
168
|
+
let out = "";
|
|
169
|
+
let cells = 0;
|
|
170
|
+
let index = 0;
|
|
171
|
+
while (index < raw.length && cells < normalizedWidth - 1) {
|
|
172
|
+
ansiPattern.lastIndex = index;
|
|
173
|
+
const match = ansiPattern.exec(raw);
|
|
174
|
+
if (match && match.index === index) {
|
|
175
|
+
out += match[0];
|
|
176
|
+
index += match[0].length;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const char = Array.from(raw.slice(index))[0] || "";
|
|
180
|
+
if (!char) break;
|
|
181
|
+
const charWidth = charDisplayWidth(char);
|
|
182
|
+
if (cells + charWidth > normalizedWidth - 1) break;
|
|
183
|
+
out += char;
|
|
184
|
+
cells += charWidth;
|
|
185
|
+
index += char.length;
|
|
186
|
+
}
|
|
187
|
+
const suffix = `${ANSI_RESET}…`;
|
|
188
|
+
return padToWidth(`${out}${suffix}`, normalizedWidth);
|
|
189
|
+
}
|
|
190
|
+
|
|
144
191
|
function horizontalLine(width = 80) {
|
|
145
192
|
return "─".repeat(Math.max(1, width));
|
|
146
193
|
}
|
|
@@ -149,6 +196,11 @@ function createAgentViewController(options = {}) {
|
|
|
149
196
|
return fitText(text, Math.max(1, width));
|
|
150
197
|
}
|
|
151
198
|
|
|
199
|
+
function logLine(text = "", width = 80) {
|
|
200
|
+
const normalizedWidth = Math.max(1, width);
|
|
201
|
+
return hasAnsi(text) ? fitAnsiText(text, normalizedWidth) : plainLine(text, normalizedWidth);
|
|
202
|
+
}
|
|
203
|
+
|
|
152
204
|
function sliceDisplayCells(text = "", startCell = 0, maxCells = 1) {
|
|
153
205
|
const targetStart = Math.max(0, startCell);
|
|
154
206
|
const targetWidth = Math.max(1, maxCells);
|
|
@@ -174,6 +226,7 @@ function createAgentViewController(options = {}) {
|
|
|
174
226
|
|
|
175
227
|
function wrapTextLine(text = "", width = 80) {
|
|
176
228
|
const inner = Math.max(1, width);
|
|
229
|
+
if (hasAnsi(text)) return [String(text || "")];
|
|
177
230
|
const clean = stripAnsi(String(text || ""));
|
|
178
231
|
if (!clean) return [""];
|
|
179
232
|
const lines = [];
|
|
@@ -209,11 +262,14 @@ function createAgentViewController(options = {}) {
|
|
|
209
262
|
function forceScreenRepaint() {
|
|
210
263
|
if (typeof screen.realloc === "function") {
|
|
211
264
|
screen.realloc();
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
if (typeof screen.alloc === "function") {
|
|
265
|
+
} else if (typeof screen.alloc === "function") {
|
|
215
266
|
screen.alloc(true);
|
|
216
267
|
}
|
|
268
|
+
try {
|
|
269
|
+
originalRender();
|
|
270
|
+
} catch {
|
|
271
|
+
// Ignore repaint failures while restoring from raw agent view.
|
|
272
|
+
}
|
|
217
273
|
}
|
|
218
274
|
|
|
219
275
|
function compactProjectPath(projectRoot = "") {
|
|
@@ -247,16 +303,21 @@ function createAgentViewController(options = {}) {
|
|
|
247
303
|
function buildClaudeStartupLines(agentLabel = "", width = 80) {
|
|
248
304
|
const label = String(agentLabel || "").trim();
|
|
249
305
|
const projectPath = compactProjectPath(getProjectRoot());
|
|
250
|
-
const product = "
|
|
306
|
+
const product = "ClaudeCode";
|
|
251
307
|
const detail = label ? `${label} · managed headless` : "managed headless";
|
|
308
|
+
const iconWidth = 9;
|
|
309
|
+
const iconLine = (icon = "", text = "") => {
|
|
310
|
+
const pad = " ".repeat(Math.max(0, iconWidth - displayWidth(icon)));
|
|
311
|
+
return `${CLAUDE_ORANGE}${icon}${ANSI_RESET}${pad}${text}`;
|
|
312
|
+
};
|
|
252
313
|
const lines = [
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
314
|
+
iconLine("▐▛███▜▌", `${product}v${packageVersion}`),
|
|
315
|
+
iconLine("▝▜█████▛▘", detail),
|
|
316
|
+
iconLine(" ▘▘▝▝", projectPath),
|
|
256
317
|
"",
|
|
257
318
|
];
|
|
258
319
|
if (width < 44) return lines;
|
|
259
|
-
return lines.map((line) =>
|
|
320
|
+
return lines.map((line) => fitAnsiText(line, Math.min(58, Math.max(1, width))));
|
|
260
321
|
}
|
|
261
322
|
|
|
262
323
|
function buildCodexStartupLines(agentLabel = "", width = 80) {
|
|
@@ -288,11 +349,31 @@ function createAgentViewController(options = {}) {
|
|
|
288
349
|
return buildClaudeStartupLines(agentLabel || agentId, width);
|
|
289
350
|
}
|
|
290
351
|
|
|
352
|
+
function staticStartupLines(agentId = "", agentLabel = "", width = 80) {
|
|
353
|
+
const lines = buildInternalStartupLines(agentId, agentLabel, width);
|
|
354
|
+
if (lines.length > 0 && String(lines[lines.length - 1] || "") === "") {
|
|
355
|
+
return lines.slice(0, -1);
|
|
356
|
+
}
|
|
357
|
+
return lines;
|
|
358
|
+
}
|
|
359
|
+
|
|
291
360
|
function resetBusView(agentId) {
|
|
292
361
|
busInputValue = "";
|
|
293
362
|
busInputCursor = 0;
|
|
363
|
+
busStartupAgentId = agentId || "";
|
|
294
364
|
const label = getAgentLabel(agentId);
|
|
295
|
-
|
|
365
|
+
const startupLines = staticStartupLines(agentId, label, getCols());
|
|
366
|
+
busLogLines = startupLines.concat("");
|
|
367
|
+
busStartupLineCount = startupLines.length;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function refreshBusStartupLines(width = getCols()) {
|
|
371
|
+
if (!busStartupAgentId || busStartupLineCount <= 0) return;
|
|
372
|
+
const label = getAgentLabel(busStartupAgentId);
|
|
373
|
+
const startupLines = staticStartupLines(busStartupAgentId, label, width);
|
|
374
|
+
const tailLines = busLogLines.slice(busStartupLineCount);
|
|
375
|
+
busLogLines = startupLines.concat(tailLines.length > 0 ? tailLines : [""]);
|
|
376
|
+
busStartupLineCount = startupLines.length;
|
|
296
377
|
}
|
|
297
378
|
|
|
298
379
|
function appendBusLog(text = "") {
|
|
@@ -331,6 +412,7 @@ function createAgentViewController(options = {}) {
|
|
|
331
412
|
const rows = getRows();
|
|
332
413
|
const cols = getCols();
|
|
333
414
|
const width = Math.max(20, cols);
|
|
415
|
+
refreshBusStartupLines(width);
|
|
334
416
|
const inputTop = Math.max(4, rows - 3);
|
|
335
417
|
const logContentTop = 1;
|
|
336
418
|
const logContentBottom = Math.max(logContentTop, inputTop - 1);
|
|
@@ -339,7 +421,7 @@ function createAgentViewController(options = {}) {
|
|
|
339
421
|
processStdout.write("\x1b[?25l");
|
|
340
422
|
const visibleLines = getWrappedBusLogLines(width).slice(-logContentHeight);
|
|
341
423
|
for (let i = 0; i < logContentHeight; i += 1) {
|
|
342
|
-
writeAt(logContentTop + i,
|
|
424
|
+
writeAt(logContentTop + i, logLine(visibleLines[i] || "", width));
|
|
343
425
|
}
|
|
344
426
|
|
|
345
427
|
writeAt(inputTop, horizontalLine(width));
|
|
@@ -392,6 +474,7 @@ function createAgentViewController(options = {}) {
|
|
|
392
474
|
function enterAgentView(agentId, options = {}) {
|
|
393
475
|
if (currentView === "agent" && viewingAgent === agentId) return;
|
|
394
476
|
if (currentView === "agent") {
|
|
477
|
+
if (agentViewUsesBus && viewingAgent) sendBusWatch(viewingAgent, false);
|
|
395
478
|
disconnectAgentOutput();
|
|
396
479
|
disconnectAgentInput();
|
|
397
480
|
}
|
|
@@ -416,6 +499,7 @@ function createAgentViewController(options = {}) {
|
|
|
416
499
|
agentInputSuppressUntil = now() + 300;
|
|
417
500
|
agentViewUsesBus = Boolean(options.useBus);
|
|
418
501
|
if (agentViewUsesBus) {
|
|
502
|
+
sendBusWatch(agentId, true);
|
|
419
503
|
resetBusView(agentId);
|
|
420
504
|
renderBusView();
|
|
421
505
|
} else {
|
|
@@ -439,12 +523,15 @@ function createAgentViewController(options = {}) {
|
|
|
439
523
|
|
|
440
524
|
disconnectAgentOutput();
|
|
441
525
|
disconnectAgentInput();
|
|
526
|
+
if (agentViewUsesBus && viewingAgent) sendBusWatch(viewingAgent, false);
|
|
442
527
|
agentViewUsesBus = false;
|
|
443
528
|
agentOutputSuppressed = false;
|
|
444
529
|
agentBarVisible = false;
|
|
445
530
|
busInputValue = "";
|
|
446
531
|
busInputCursor = 0;
|
|
447
532
|
busLogLines = [];
|
|
533
|
+
busStartupAgentId = "";
|
|
534
|
+
busStartupLineCount = 0;
|
|
448
535
|
|
|
449
536
|
currentView = "main";
|
|
450
537
|
viewingAgent = null;
|
|
@@ -462,11 +549,11 @@ function createAgentViewController(options = {}) {
|
|
|
462
549
|
setDashboardView("agents");
|
|
463
550
|
setSelectedAgentIndex(-1);
|
|
464
551
|
setScreenGrabKeys(false);
|
|
465
|
-
forceScreenRepaint();
|
|
466
552
|
clearTargetAgent();
|
|
467
553
|
renderDashboard();
|
|
468
554
|
focusInput();
|
|
469
555
|
resizeInput();
|
|
556
|
+
forceScreenRepaint();
|
|
470
557
|
try {
|
|
471
558
|
if (screen.program && typeof screen.program.showCursor === "function") {
|
|
472
559
|
screen.program.showCursor();
|
|
@@ -448,12 +448,12 @@ function createCommandExecutor(options = {}) {
|
|
|
448
448
|
try {
|
|
449
449
|
const skills = createSkills(projectRoot);
|
|
450
450
|
|
|
451
|
-
if (subcommand === "list") {
|
|
451
|
+
if (!subcommand || subcommand === "list") {
|
|
452
452
|
const skillList = skills.list();
|
|
453
453
|
if (skillList.length === 0) {
|
|
454
|
-
logMessage("system", "{white-fg}No skills found{/white-fg}");
|
|
454
|
+
logMessage("system", "{white-fg}No built-in ufoo skills found{/white-fg}");
|
|
455
455
|
} else {
|
|
456
|
-
logMessage("system", `{cyan-fg}Available skills:{/cyan-fg} ${skillList.length}`);
|
|
456
|
+
logMessage("system", `{cyan-fg}Available built-in ufoo skills:{/cyan-fg} ${skillList.length}`);
|
|
457
457
|
for (const skill of skillList) {
|
|
458
458
|
logMessage("system", ` • ${skill}`);
|
|
459
459
|
}
|
package/src/chat/commands.js
CHANGED
|
@@ -129,10 +129,10 @@ const COMMAND_TREE = {
|
|
|
129
129
|
},
|
|
130
130
|
},
|
|
131
131
|
"/skills": {
|
|
132
|
-
desc: "
|
|
132
|
+
desc: "List ufoo built-in skills and preset workflows",
|
|
133
133
|
children: {
|
|
134
|
-
install: { desc: "Install skills (use: all or name)" },
|
|
135
|
-
list: { desc: "List
|
|
134
|
+
install: { desc: "Install built-in skills (use: all or name)" },
|
|
135
|
+
list: { desc: "List built-in skills for discovery" },
|
|
136
136
|
},
|
|
137
137
|
},
|
|
138
138
|
"/status": { desc: "Status display" },
|
|
@@ -19,6 +19,8 @@ function createDaemonMessageRouter(options = {}) {
|
|
|
19
19
|
getCurrentView = () => "main",
|
|
20
20
|
isAgentViewUsesBus = () => false,
|
|
21
21
|
getViewingAgent = () => "",
|
|
22
|
+
isAgentEventForViewingAgent = (data, viewingAgent, publisher) =>
|
|
23
|
+
Boolean(viewingAgent && (publisher === viewingAgent || data?.target === viewingAgent)),
|
|
22
24
|
writeToAgentTerm = () => {},
|
|
23
25
|
consumePendingDelivery = () => false,
|
|
24
26
|
getPendingState = () => null,
|
|
@@ -352,6 +354,19 @@ function createDaemonMessageRouter(options = {}) {
|
|
|
352
354
|
function handleBusMessage(msg) {
|
|
353
355
|
const data = msg.data || {};
|
|
354
356
|
if (data.event === "activity_state_changed") {
|
|
357
|
+
const publisher = data.publisher && data.publisher !== "unknown"
|
|
358
|
+
? data.publisher
|
|
359
|
+
: (data.subscriber || "bus");
|
|
360
|
+
const viewingAgent = getViewingAgent();
|
|
361
|
+
if (
|
|
362
|
+
getCurrentView() === "agent" &&
|
|
363
|
+
isAgentViewUsesBus() &&
|
|
364
|
+
viewingAgent &&
|
|
365
|
+
isAgentEventForViewingAgent(data, viewingAgent, publisher)
|
|
366
|
+
) {
|
|
367
|
+
const state = data.state || (data.data && data.data.state) || "";
|
|
368
|
+
if (state) writeToAgentTerm(`[state] ${state}\r\n`);
|
|
369
|
+
}
|
|
355
370
|
requestStatus();
|
|
356
371
|
return true;
|
|
357
372
|
}
|
|
@@ -375,11 +390,12 @@ function createDaemonMessageRouter(options = {}) {
|
|
|
375
390
|
if (data.silent && !streamPayload) return true;
|
|
376
391
|
if (!displayMessage && !streamPayload) return true;
|
|
377
392
|
|
|
393
|
+
const viewingAgent = getViewingAgent();
|
|
378
394
|
const isAgentViewTarget =
|
|
379
395
|
getCurrentView() === "agent" &&
|
|
380
396
|
isAgentViewUsesBus() &&
|
|
381
|
-
|
|
382
|
-
|
|
397
|
+
viewingAgent &&
|
|
398
|
+
isAgentEventForViewingAgent(data, viewingAgent, publisher);
|
|
383
399
|
|
|
384
400
|
const displayName = resolveAgentDisplayName(publisher);
|
|
385
401
|
|
package/src/chat/index.js
CHANGED
|
@@ -1581,6 +1581,14 @@ async function runChat(projectRoot, options = {}) {
|
|
|
1581
1581
|
source: "chat-internal-agent-view",
|
|
1582
1582
|
});
|
|
1583
1583
|
},
|
|
1584
|
+
sendBusWatch: (agentId, enabled) => {
|
|
1585
|
+
if (!agentId) return;
|
|
1586
|
+
send({
|
|
1587
|
+
type: IPC_REQUEST_TYPES.BUS_WATCH,
|
|
1588
|
+
agent_id: agentId,
|
|
1589
|
+
enabled: enabled !== false,
|
|
1590
|
+
});
|
|
1591
|
+
},
|
|
1584
1592
|
sendResize: (cols, rows) => {
|
|
1585
1593
|
sendResizeWithCapabilities(cols, rows);
|
|
1586
1594
|
},
|
|
@@ -1612,6 +1620,21 @@ async function runChat(projectRoot, options = {}) {
|
|
|
1612
1620
|
getCurrentView: () => getCurrentView(),
|
|
1613
1621
|
isAgentViewUsesBus: () => isAgentViewUsesBus(),
|
|
1614
1622
|
getViewingAgent: () => getViewingAgent(),
|
|
1623
|
+
isAgentEventForViewingAgent: (data, viewingAgent, publisher) => {
|
|
1624
|
+
if (!viewingAgent) return false;
|
|
1625
|
+
const label = getAgentLabel(viewingAgent);
|
|
1626
|
+
const candidates = [
|
|
1627
|
+
publisher,
|
|
1628
|
+
data && data.publisher,
|
|
1629
|
+
data && data.target,
|
|
1630
|
+
data && data.subscriber,
|
|
1631
|
+
].filter(Boolean);
|
|
1632
|
+
return candidates.some((value) => (
|
|
1633
|
+
value === viewingAgent ||
|
|
1634
|
+
value === label ||
|
|
1635
|
+
resolveAgentId(value) === viewingAgent
|
|
1636
|
+
));
|
|
1637
|
+
},
|
|
1615
1638
|
writeToAgentTerm,
|
|
1616
1639
|
consumePendingDelivery,
|
|
1617
1640
|
getPendingState,
|
package/src/code/agent.js
CHANGED
|
@@ -25,6 +25,12 @@ const {
|
|
|
25
25
|
loadSessionSnapshot,
|
|
26
26
|
} = require("./sessionStore");
|
|
27
27
|
const { buildPromptContext } = require("./prompts");
|
|
28
|
+
const {
|
|
29
|
+
buildSkillInjections,
|
|
30
|
+
formatSkillsList,
|
|
31
|
+
listUcodeSkills,
|
|
32
|
+
showSkill,
|
|
33
|
+
} = require("./skills");
|
|
28
34
|
|
|
29
35
|
function printPrompt() {
|
|
30
36
|
process.stdout.write("> ");
|
|
@@ -243,6 +249,61 @@ function createToolLogCollector(logs = [], onToolLog = null) {
|
|
|
243
249
|
};
|
|
244
250
|
}
|
|
245
251
|
|
|
252
|
+
function pushSkillWarning(logs = [], onToolLog = null, warning = "") {
|
|
253
|
+
const text = String(warning || "").trim();
|
|
254
|
+
if (!text) return null;
|
|
255
|
+
const entry = {
|
|
256
|
+
type: "skills",
|
|
257
|
+
phase: "warning",
|
|
258
|
+
message: text,
|
|
259
|
+
error: text,
|
|
260
|
+
};
|
|
261
|
+
if (Array.isArray(logs)) logs.push(entry);
|
|
262
|
+
if (typeof onToolLog === "function") {
|
|
263
|
+
try {
|
|
264
|
+
onToolLog(entry);
|
|
265
|
+
} catch {
|
|
266
|
+
// ignore callback failures
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return entry;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function stripSkillBlocksFromText(value = "") {
|
|
273
|
+
return String(value || "")
|
|
274
|
+
.replace(/<skill>\s*[\s\S]*?<\/skill>\s*/g, "")
|
|
275
|
+
.trim();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function stripSkillBlocksFromMessages(messages = []) {
|
|
279
|
+
if (!Array.isArray(messages)) return [];
|
|
280
|
+
return messages.map((message) => {
|
|
281
|
+
if (!message || typeof message !== "object" || Array.isArray(message)) return message;
|
|
282
|
+
if (typeof message.content === "string") {
|
|
283
|
+
return {
|
|
284
|
+
...message,
|
|
285
|
+
content: stripSkillBlocksFromText(message.content),
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
if (Array.isArray(message.content)) {
|
|
289
|
+
return {
|
|
290
|
+
...message,
|
|
291
|
+
content: message.content.map((item) => {
|
|
292
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return item;
|
|
293
|
+
if (typeof item.text === "string") {
|
|
294
|
+
return {
|
|
295
|
+
...item,
|
|
296
|
+
text: stripSkillBlocksFromText(item.text),
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
return item;
|
|
300
|
+
}),
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
return message;
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
246
307
|
function isProjectAnalysisTask(task = "") {
|
|
247
308
|
const text = String(task || "").trim().toLowerCase();
|
|
248
309
|
if (!text) return false;
|
|
@@ -398,6 +459,16 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
398
459
|
const taskPrompt = analysisTask
|
|
399
460
|
? `${taskText}\n\nAnalysis requirements:\n- Inspect repository evidence before concluding.\n- Cite concrete file observations.\n- Keep findings concise and actionable.`
|
|
400
461
|
: taskText;
|
|
462
|
+
const skillInjections = buildSkillInjections({
|
|
463
|
+
prompt: taskPrompt,
|
|
464
|
+
workspaceRoot,
|
|
465
|
+
});
|
|
466
|
+
for (const warning of skillInjections.warnings || []) {
|
|
467
|
+
pushSkillWarning(logs, onToolLog, warning);
|
|
468
|
+
}
|
|
469
|
+
const effectiveTaskPrompt = Array.isArray(skillInjections.blocks) && skillInjections.blocks.length > 0
|
|
470
|
+
? `${skillInjections.blocks.join("\n\n")}\n\n${taskPrompt}`
|
|
471
|
+
: taskPrompt;
|
|
401
472
|
const systemContext = [String(state.context || "").trim(), preflightContext]
|
|
402
473
|
.filter(Boolean)
|
|
403
474
|
.join("\n\n");
|
|
@@ -422,7 +493,7 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
422
493
|
workspaceRoot,
|
|
423
494
|
provider,
|
|
424
495
|
model,
|
|
425
|
-
prompt:
|
|
496
|
+
prompt: effectiveTaskPrompt,
|
|
426
497
|
systemPrompt: systemContext,
|
|
427
498
|
messages: Array.isArray(state.nlMessages) ? state.nlMessages : [],
|
|
428
499
|
sessionId: String(sessionIdValue || ""),
|
|
@@ -440,7 +511,7 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
440
511
|
// Use decomposed runner for bug fix tasks
|
|
441
512
|
if (useDecomposition) {
|
|
442
513
|
const decomposedResult = await runDecomposedTask({
|
|
443
|
-
task:
|
|
514
|
+
task: effectiveTaskPrompt,
|
|
444
515
|
state,
|
|
445
516
|
onProgress: options.onProgress,
|
|
446
517
|
onToolEvent: pushToolLog,
|
|
@@ -497,7 +568,7 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
497
568
|
state.sessionId = cliRes.sessionId.trim();
|
|
498
569
|
}
|
|
499
570
|
if (cliRes && Array.isArray(cliRes.messages)) {
|
|
500
|
-
state.nlMessages = cliRes.messages;
|
|
571
|
+
state.nlMessages = stripSkillBlocksFromMessages(cliRes.messages);
|
|
501
572
|
}
|
|
502
573
|
const normalized = String(cliRes.output || "").trim();
|
|
503
574
|
const summary = extractJsonSummary(normalized);
|
|
@@ -1235,6 +1306,8 @@ function runSingleCommand(line = "", workspaceRoot = process.cwd()) {
|
|
|
1235
1306
|
" help",
|
|
1236
1307
|
" exit|quit",
|
|
1237
1308
|
" ubus|/ubus",
|
|
1309
|
+
" skills [list]",
|
|
1310
|
+
" skills show <name>",
|
|
1238
1311
|
" bg|/bg <task>",
|
|
1239
1312
|
" resume <session-id>",
|
|
1240
1313
|
" tool <read|write|edit|bash> <args-json>",
|
|
@@ -1254,6 +1327,45 @@ function runSingleCommand(line = "", workspaceRoot = process.cwd()) {
|
|
|
1254
1327
|
kind: "ubus",
|
|
1255
1328
|
};
|
|
1256
1329
|
}
|
|
1330
|
+
const skillsMatch = text.match(/^(?:\/skills|skills)(?:\s+(.*))?$/i);
|
|
1331
|
+
if (skillsMatch) {
|
|
1332
|
+
const args = String(skillsMatch[1] || "").trim().split(/\s+/).filter(Boolean);
|
|
1333
|
+
const action = String(args[0] || "list").toLowerCase();
|
|
1334
|
+
if (action === "list" || action === "ls") {
|
|
1335
|
+
const outcome = listUcodeSkills({ workspaceRoot });
|
|
1336
|
+
return {
|
|
1337
|
+
kind: "skills",
|
|
1338
|
+
output: formatSkillsList(outcome),
|
|
1339
|
+
skills: outcome.skills,
|
|
1340
|
+
errors: outcome.errors,
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
if (action === "show") {
|
|
1344
|
+
const name = String(args[1] || "").trim();
|
|
1345
|
+
if (!name) {
|
|
1346
|
+
return {
|
|
1347
|
+
kind: "error",
|
|
1348
|
+
output: "usage: skills show <name>",
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
1351
|
+
const result = showSkill({ name, workspaceRoot });
|
|
1352
|
+
if (!result.ok) {
|
|
1353
|
+
return {
|
|
1354
|
+
kind: "error",
|
|
1355
|
+
output: result.error,
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
return {
|
|
1359
|
+
kind: "skills",
|
|
1360
|
+
output: result.output,
|
|
1361
|
+
skill: result.skill,
|
|
1362
|
+
};
|
|
1363
|
+
}
|
|
1364
|
+
return {
|
|
1365
|
+
kind: "error",
|
|
1366
|
+
output: "usage: skills [list] | skills show <name>",
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1257
1369
|
if (text === "bg" || text === "/bg") {
|
|
1258
1370
|
return {
|
|
1259
1371
|
kind: "error",
|
|
@@ -1498,7 +1610,7 @@ async function runUcodeCoreAgent({
|
|
|
1498
1610
|
if (result.kind === "probe") {
|
|
1499
1611
|
return;
|
|
1500
1612
|
}
|
|
1501
|
-
if (result.kind === "help" || result.kind === "tool" || result.kind === "error") {
|
|
1613
|
+
if (result.kind === "help" || result.kind === "tool" || result.kind === "skills" || result.kind === "error") {
|
|
1502
1614
|
stdout.write(`${result.output}\n`);
|
|
1503
1615
|
}
|
|
1504
1616
|
if (result.kind === "ubus") {
|
|
@@ -1701,6 +1813,8 @@ module.exports = {
|
|
|
1701
1813
|
normalizeToolLogEvent,
|
|
1702
1814
|
isProjectAnalysisTask,
|
|
1703
1815
|
buildNlFallbackSummary,
|
|
1816
|
+
buildNlContext,
|
|
1817
|
+
stripSkillBlocksFromMessages,
|
|
1704
1818
|
resolvePlannerProvider,
|
|
1705
1819
|
extractJsonSummary,
|
|
1706
1820
|
enrichNativeError,
|