@comchatx/cx-subagents 0.1.1 → 0.2.1

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
@@ -1,43 +1,56 @@
1
- When a task will take long, such as a research across several data sources or a look through many listings, cx hands it to a helper that runs in the background, tells you it has, and keeps answering you. The helper's result comes back into the conversation by itself.
1
+ When cx hands work to the background, a card for it appears under To do on the board. It holds what cx asked for. When the work is done, the same card shows its result. If it is not done after 30 minutes, the card says 超时 and shows what was done so far.
2
2
 
3
- ## When the result comes back
3
+ It needs [`pi-subagents`](https://www.npmjs.com/package/pi-subagents), which runs the work in the background, and [`@comchatx/cx-board`](https://www.npmjs.com/package/@comchatx/cx-board), the board. The Plugins page installs both with it. With pi-subagents alone, cx still works in the background, and no card is written.
4
4
 
5
- A helper runs for at most 30 minutes. When it is done, cx tells you what it found in the conversation; if it is still at work after 30 minutes, cx says it ran out. On cx's computer, at most two helpers run in the background at a time in one conversation: that is ComChatX's setting on the computer, not this package's, and a computer without that setting sets no cap.
6
-
7
- ## What it costs
5
+ ## When cx's computer stops
8
6
 
9
- A helper uses the same model as cx and is paid from the same balance. cx's computer stays on while a helper runs, and that time is billed.
7
+ If cx's computer stops before the work is done, its card stays as it was when the work started.
10
8
 
11
- ## When cx's computer stops
9
+ ## What it costs
12
10
 
13
- If cx's computer stops before a result is handed back, that result is lost and the work has to run again.
11
+ The work in the background uses the same model as cx and is paid from the same balance. cx's computer stays on while it runs, and that time is billed.
14
12
 
15
13
  ## How it is built
16
14
 
17
- - **pi-subagents, unchanged.** The helper is [`pi-subagents`](https://www.npmjs.com/package/pi-subagents) and its `subagent` tool. This package names it in its `comchatx` field's `builds`, so the Plugins page installs it with this one; it does not carry it. Its tests run against `pi-subagents` 0.83.0 on pi 1.3.0.
18
- - **One skill, `cx-subagents`.** It says when cx hands a task to the background (a long research across several sources; a look through many listings), that cx then hands the conversation back and says so, and that the result returns by itself. pi-subagents' own tool asks for an instruction before it delegates; in a ComChatX box this skill is that instruction. Its description says what it needs: pi installs nothing for a package.
19
- - **pi-subagents' config, carried by the box.** pi-subagents reads `extensions/subagent/config.json` in pi's agent directory once, when it loads, and the Plugins page installs it before this package, so a box's image carries the file instead of this package writing it:
20
-
21
- ```json
22
- {
23
- "asyncByDefault": true,
24
- "toolActivation": "eager",
25
- "maxActiveAsyncRunsPerSession": 2
26
- }
27
- ```
15
+ - **One extension, `extensions/board-cards.ts`.** pi loads it as TypeScript; there is no build. It ships no skill. When cx hands work to the background is cx's own judgement with pi-subagents' `subagent` tool.
16
+ - **What it listens to.** pi-subagents emits `subagent:async-started` and `subagent:async-complete` on the session's event bus. The start carries the run id and the `subagent` call's id, and redacts the task. The extension takes the task from pi's own `tool_call` event for that call. The end carries the result.
17
+ - **The card.** It goes in `board/<person>/todo/` under the session's directory, which in cx's computer is `/tenant`. The person is the one directory under `board/`; a board with none, or with more than one, gets no card. This is the board's shape as `cx-board` describes it. The extension imports nothing from `cx-board` and calls nothing of the board's.
18
+ - The name is `<YYYYMMDD-HHMMSS>-subagent-<run id>.md`, the start's time in UTC.
19
+ - The front matter is `kind: finding`, a `title` and an `at`. The title is the task's first line, at most 200 characters. `at` is the start.
20
+ - The words are the task as given. They are cut where the whole file would pass the 32 KiB the board reads.
21
+ - **At the end** the same file in `todo/` is rewritten, its name, title and `at` kept. The words become:
22
+ - a single run's own output;
23
+ - each run's output under its agent's name, for several;
24
+ - at the deadline, 超时, then what the run had written. pi-subagents' own note on the stop, its "Recovery summary", is left out.
28
25
 
29
- A run goes to the background unless a call says otherwise; the `subagent` tool is offered from a session's first request; and two background runs at a time in one session, where pi-subagents' own default has no cap. The 30-minute limit is pi-subagents' own default.
30
- - **What the box does for it.** A background run is a detached Node process of the model's user, so the box counts it as work and stays awake while it runs. Its state lives under `/tmp`, which a stop clears. The box drops the widget pi-subagents sets once a second while a run is in the background, since the page draws none. The notice that wakes cx when a run ends is not drawn; cx's own answer after it is.
26
+ Nothing is moved. A card the seller has already marked read stays in `done/`, and the result is a card again in `todo/` under the same name. A second start of the same run id overwrites its card. A run that ends after cx's computer stopped leaves its card as it started.
27
+ - **pi-subagents' config is carried by the box,** not this package. `asyncByDefault` sends a run to the background. `toolActivation: "eager"` offers the `subagent` tool from the first request. `maxActiveAsyncRunsPerSession: 2` caps the runs in one conversation at two. The 30 minutes is pi-subagents' own default for a background run.
31
28
 
32
29
  ## Tests
33
30
 
34
- - `test/package.test.ts`: the pinned pi lists `cx-subagents` beside `pi-subagents`' own skill, with what it needs in its description; the `comchatx` field names the same skills pi lists from this package, with their words, for the Plugins page; the package ships the skill alone and names `pi-subagents` for the page.
35
- - `test/background.test.ts`, on the pinned pi in RPC mode with a scripted model on loopback, as a box starts pi:
36
- - the `subagent` call comes back at once with the run detached;
37
- - a second message is answered while the helper is still at work;
38
- - the helper's result arrives as pi-subagents' notice, not displayed, and starts the turn in which cx relays it;
39
- - with the box's config the session holds `0/2` background runs, and without it `0/unlimited`.
31
+ The tests run against `pi-subagents` 0.85.0 on pi 1.4.0, the versions cx's computer runs.
32
+
33
+ - **`test/board.test.ts`**, on the pinned pi in RPC mode with a scripted model on loopback, as a box starts pi. Each card is read by the board's own reader, copied into `test/fixtures/board-reader.ts` with its source lines named.
34
+ - A `subagent` call writes a started card whose title is the task's first line. The result rewrites the same file.
35
+ - A run stopped at its deadline writes 超时 with what it had.
36
+ - Two runs write two files.
37
+ - The same run id started again overwrites its card.
38
+ - **`test/board-cards.test.ts`**, the extension on a bus of its own:
39
+ - the name and the front matter;
40
+ - a card found again after a reload;
41
+ - nothing moved;
42
+ - the task of a chain or a parallel run;
43
+ - no card where the board is not one person's;
44
+ - the 32 KiB cut;
45
+ - the end's words.
46
+ - **`test/package.test.ts`:**
47
+ - pi lists no skill from the package and loads its one extension;
48
+ - the package ships the extension alone;
49
+ - the package names `pi-subagents` and `@comchatx/cx-board` in `comchatx.builds`.
50
+ - **`test/background.test.ts`:**
51
+ - a run goes to the background and its result comes back into the conversation;
52
+ - the box's config caps the runs at two.
40
53
 
41
54
  ## Release
42
55
 
43
- A maintainer publishes the first version by hand, `npm publish --access public` from a checkout of the merged tag. Each later version is published by `.github/workflows/release.yml` on a `v<version>` tag, once this repository is the package's trusted publisher on npm.
56
+ `.github/workflows/release.yml` publishes each version on a `v<version>` tag, once this repository is the package's trusted publisher on npm. Until then a maintainer publishes by hand, `npm publish --access public` from a checkout of the merged tag.
@@ -0,0 +1,56 @@
1
+ cx 把工作交给后台时,看板的「待处理」下会出现一张对应的卡片,上面写着 cx 交代的内容。工作完成后,同一张卡片会显示结果。如果 30 分钟后仍未完成,卡片会显示「超时」和目前已完成的部分。
2
+
3
+ 它需要在后台运行工作的 [`pi-subagents`](https://www.npmjs.com/package/pi-subagents),以及看板 [`@comchatx/cx-board`](https://www.npmjs.com/package/@comchatx/cx-board)。「插件」页会一并安装这两个插件。只安装 pi-subagents 时,cx 仍会在后台工作,但不会写卡片。
4
+
5
+ ## cx 的电脑关机时
6
+
7
+ 如果工作完成前 cx 的电脑关机,卡片会保持工作开始时的样子。
8
+
9
+ ## 费用
10
+
11
+ 后台工作和 cx 使用同一个模型,从同一份余额付费。后台工作运行时,cx 的电脑保持开机,开机时长照常计费。
12
+
13
+ ## 实现方式
14
+
15
+ - **一个扩展:`extensions/board-cards.ts`。** pi 以 TypeScript 直接加载它,不需要构建。它不带任何技能。什么时候把工作交给后台,由 cx 借助 pi-subagents 的 `subagent` 工具自行判断。
16
+ - **它监听什么。** pi-subagents 会在会话的事件总线上发出 `subagent:async-started` 和 `subagent:async-complete`。开始事件带有运行 id 和这次 `subagent` 调用的 id,但隐去了任务内容;扩展从 pi 自己为这次调用发出的 `tool_call` 事件中取得任务。结束事件带有结果。
17
+ - **卡片。** 卡片写在会话文件夹下的 `board/<person>/todo/` 中;在 cx 的电脑上,会话文件夹就是 `/tenant`。这个人就是 `board/` 下唯一的那个文件夹;一个都没有或不止一个时,不写卡片。这是 `cx-board` 所描述的看板结构。扩展不从 `cx-board` 导入任何内容,也不调用看板的任何功能。
18
+ - 文件名是 `<YYYYMMDD-HHMMSS>-subagent-<run id>.md`,时间是开始时的 UTC 时间。
19
+ - front matter 包含 `kind: finding`、`title` 和 `at`。标题是任务的第一行,最多 200 个字符;`at` 是开始时间。
20
+ - 正文是原样的任务内容。整个文件超过看板读取的上限 32 KiB 时,正文会在超出处截断。
21
+ - **结束时**,`todo/` 里的同一个文件会被重写,文件名、标题和 `at` 保持不变。正文变为:
22
+ - 只有一个运行时,是它自己的输出;
23
+ - 有多个运行时,是每个运行的输出,分别放在各自智能体的名称下;
24
+ - 到达时限时,是「超时」,然后是运行已写出的内容;pi-subagents 自己关于这次停止的说明,即它的「Recovery summary」,不会写入。
25
+
26
+ 不移动任何文件。卖家已标为已读的卡片留在 `done/` 中,结果会以同一个文件名,重新作为卡片出现在 `todo/` 中。同一个运行 id 再次开始时,会覆盖它的卡片。在 cx 的电脑关机后才结束的运行,卡片保持开始时的样子。
27
+ - **pi-subagents 的配置由 cx 的电脑提供**,不在这个包里。`asyncByDefault` 让运行进入后台。`toolActivation: "eager"` 从第一次请求起就提供 `subagent` 工具。`maxActiveAsyncRunsPerSession: 2` 把一个对话中的运行限制为两个。30 分钟是 pi-subagents 对后台运行的默认时限。
28
+
29
+ ## 测试
30
+
31
+ 测试针对 pi 1.4.0 上的 `pi-subagents` 0.85.0 运行,也就是 cx 的电脑上运行的版本。
32
+
33
+ - **`test/board.test.ts`**:在锁定版本的 pi 上以 RPC 模式运行,使用本机回环地址上的脚本模型,与 cx 的电脑启动 pi 的方式相同。每张卡片都由看板自己的读取器读取,读取器复制在 `test/fixtures/board-reader.ts` 中,并注明了来源行。
34
+ - 一次 `subagent` 调用会写一张已开始的卡片,标题是任务的第一行;结果会重写同一个文件。
35
+ - 到时限被停止的运行,写入「超时」和已有的内容。
36
+ - 两个运行写两个文件。
37
+ - 同一个运行 id 再次开始,会覆盖它的卡片。
38
+ - **`test/board-cards.test.ts`**:扩展在独立的事件总线上:
39
+ - 文件名和 front matter;
40
+ - 重新加载后再次找到卡片;
41
+ - 不移动任何文件;
42
+ - 链式运行或并行运行的任务;
43
+ - 看板不属于单独一个人时,不写卡片;
44
+ - 32 KiB 截断;
45
+ - 结束时的正文。
46
+ - **`test/package.test.ts`**:
47
+ - pi 不会从这个包列出任何技能,并加载它唯一的扩展;
48
+ - 这个包只发布这个扩展;
49
+ - 这个包在 `comchatx.builds` 中写明 `pi-subagents` 和 `@comchatx/cx-board`。
50
+ - **`test/background.test.ts`**:
51
+ - 运行进入后台,结果回到对话中;
52
+ - cx 的电脑上的配置把运行限制为两个。
53
+
54
+ ## 发布
55
+
56
+ 本仓库成为这个包在 npm 上的可信发布者后,每个版本会在打上 `v<version>` 标签时由 `.github/workflows/release.yml` 发布。在此之前,由维护者从合并后的标签检出代码,手动运行 `npm publish --access public` 发布。
@@ -0,0 +1,162 @@
1
+ import { mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
4
+
5
+ /** pi-subagents' events for a background run, on the session's bus (`pi-subagents` `src/shared/types.js`). */
6
+ export const STARTED = "subagent:async-started";
7
+ export const COMPLETE = "subagent:async-complete";
8
+ export const TIMED_OUT_WORDS = "超时";
9
+
10
+ /** The board's shape (`parts/cx-board.md` §1): one directory per person under `board/`, cards waiting in its `todo/`. */
11
+ const BOARD = "board";
12
+ const TODO = "todo";
13
+ const DONE = "done";
14
+ const PERSON = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
15
+ /** The most of one file the board reads; a larger one is drawn as its name alone. */
16
+ export const CARD_MAX_BYTES = 32 * 1024;
17
+ const TITLE_MAX = 200;
18
+ /** The `subagent` calls whose words are kept, the latest, for a start that names its call after the call's own result. */
19
+ const ASKED_KEPT = 64;
20
+
21
+ type Card = { readonly name: string; readonly title: string; readonly at: number };
22
+ type Options = { readonly now?: () => number };
23
+
24
+ const text = (value: unknown): string | undefined => (typeof value === "string" ? value : undefined);
25
+ const record = (value: unknown): Record<string, unknown> => (typeof value === "object" && value !== null ? (value as Record<string, unknown>) : {});
26
+
27
+ /** What a `subagent` call asked: its task, or each step's of a chain or a parallel run, as given. */
28
+ function taskOf(input: Record<string, unknown>): string {
29
+ const own = text(input["task"]);
30
+ if (own !== undefined && own.trim() !== "") return own;
31
+ const steps = [input["tasks"], input["chain"]].flatMap((list) => (Array.isArray(list) ? list : [])).map((step) => text(record(step)["task"]) ?? "").filter((one) => one.trim() !== "");
32
+ return steps.join("\n\n");
33
+ }
34
+
35
+ const firstLine = (task: string): string => {
36
+ const line = (task.split(/\r?\n/).find((one) => one.trim() !== "") ?? "").replace(/\s+/g, " ").trim();
37
+ return line.length > TITLE_MAX ? `${line.slice(0, TITLE_MAX - 1)}…` : line;
38
+ };
39
+
40
+ const stamp = (ms: number): string => new Date(ms).toISOString().replace(/[-:]/g, "").replace("T", "-").slice(0, 15);
41
+ const idPart = (id: string): string => id.replace(/[^A-Za-z0-9_-]/g, "-").slice(0, 80);
42
+ const suffix = (id: string): string => `-subagent-${idPart(id)}.md`;
43
+
44
+ /** A card as the board reads it: front matter with its kind, title and time, then the words, cut to what the board reads whole. */
45
+ export function cardText(card: Card, words: string): string {
46
+ const head = `---\nkind: finding\ntitle: ${JSON.stringify(card.title)}\nat: ${new Date(card.at).toISOString()}\n---\n\n`;
47
+ const room = CARD_MAX_BYTES - Buffer.byteLength(head) - 1;
48
+ const body = Buffer.byteLength(words) <= room ? words : `${Buffer.from(words).subarray(0, room - 3).toString().replace(/�+$/, "")}…`;
49
+ return `${head}${body}\n`;
50
+ }
51
+
52
+ /** The one person's board under the session's directory, or none when there is not exactly one. */
53
+ function personDir(cwd: string): string | undefined {
54
+ let people: string[];
55
+ try {
56
+ people = readdirSync(join(cwd, BOARD), { withFileTypes: true }).filter((one) => one.isDirectory()).map((one) => one.name);
57
+ } catch {
58
+ return undefined;
59
+ }
60
+ return people.length === 1 && PERSON.test(people[0]!) ? join(cwd, BOARD, people[0]!) : undefined;
61
+ }
62
+
63
+ /** A run's card already on the board, by its run id: in `todo/`, else where the seller moved it, with its title and time read back. */
64
+ function found(person: string, id: string, now: number): Card | undefined {
65
+ for (const place of [TODO, DONE]) {
66
+ let names: string[];
67
+ try { names = readdirSync(join(person, place)); } catch { continue; }
68
+ const name = names.filter((one) => one.endsWith(suffix(id)) && !one.startsWith(".")).sort().at(-1);
69
+ if (name === undefined) continue;
70
+ let lines: string[] = [];
71
+ try { lines = readFileSync(join(person, place, name), "utf8").split(/\r?\n/); } catch { /* the name alone */ }
72
+ const field = (key: string): string | undefined => lines.find((line) => line.startsWith(`${key}: `))?.slice(key.length + 2);
73
+ let title = field("title") ?? "";
74
+ try { title = JSON.parse(title) as string; } catch { /* as it stands */ }
75
+ const at = Date.parse(field("at") ?? "");
76
+ return { name, title, at: Number.isFinite(at) ? at : now };
77
+ }
78
+ return undefined;
79
+ }
80
+
81
+ /** Writes a card into `todo/` whole, so the board never reads half of one. */
82
+ function write(person: string, card: Card, words: string): void {
83
+ const dir = join(person, TODO);
84
+ mkdirSync(dir, { recursive: true });
85
+ const temp = join(dir, `.${card.name}.${process.pid}.tmp`);
86
+ writeFileSync(temp, cardText(card, words));
87
+ renameSync(temp, join(dir, card.name));
88
+ }
89
+
90
+ /** Where pi-subagents' own note on a stopped run begins, after what the helper had written: paths and checks, not the helper's words. */
91
+ const RECOVERY = "\n\nRecovery summary:";
92
+ const ownWords = (result: Record<string, unknown>): string => {
93
+ const output = text(result["output"]) ?? "";
94
+ const cut = output.indexOf(RECOVERY);
95
+ return (cut < 0 ? output : output.slice(0, cut)).trim();
96
+ };
97
+
98
+ /**
99
+ * A run's end as its card's words: a single helper's own output, or each helper's of several under its agent's name; at the deadline,
100
+ * 「超时」 and what the run had when pi-subagents stopped it.
101
+ */
102
+ export function endWords(event: Record<string, unknown>): string {
103
+ const timedOut = event["timedOut"] === true;
104
+ const results = Array.isArray(event["results"]) ? event["results"].map(record) : [];
105
+ const each = results.map((result) => ({ agent: text(result["agent"]) ?? "", words: ownWords(result) || (timedOut ? "" : (text(result["error"]) ?? "").trim()) }));
106
+ const words = each.length === 1 ? each[0]!.words : each.filter((one) => one.words !== "").map((one) => `${one.agent}:\n${one.words}`).join("\n\n");
107
+ if (timedOut) return words === "" ? TIMED_OUT_WORDS : `${TIMED_OUT_WORDS}\n\n${words}`;
108
+ return words !== "" ? words : (text(event["summary"]) ?? text(event["error"]) ?? "").trim();
109
+ }
110
+
111
+ /**
112
+ * Each background run of pi-subagents is a card on the board: at its start, the task's first line as the title, the start as its time and
113
+ * the task as its words; at its end, the same file in `todo/` with the result as its words. Nothing is moved; a card the seller already
114
+ * moved to `done/` stays there, and the result is a card again in `todo/` under the same name.
115
+ */
116
+ export function boardCards(pi: ExtensionAPI, options: Options = {}): void {
117
+ const now = options.now ?? Date.now;
118
+ const asked = new Map<string, string>();
119
+ const running = new Set<string>();
120
+ const cards = new Map<string, Card>();
121
+ let cwd = process.cwd();
122
+
123
+ pi.on("session_start", (_event, ctx) => { cwd = ctx.cwd; });
124
+ pi.on("tool_call", (event, ctx) => {
125
+ cwd = ctx.cwd;
126
+ if (event.toolName !== "subagent") return;
127
+ asked.set(event.toolCallId, taskOf(record(event.input)));
128
+ running.add(event.toolCallId);
129
+ for (const old of asked.keys()) if (asked.size > ASKED_KEPT) asked.delete(old);
130
+ });
131
+ pi.on("tool_result", (event) => { running.delete(event.toolCallId); });
132
+
133
+ pi.events.on(STARTED, (data) => {
134
+ const event = record(data);
135
+ const id = text(event["id"]);
136
+ const person = personDir(cwd);
137
+ if (id === undefined || person === undefined) return;
138
+ const toolCallId = text(event["toolCallId"]);
139
+ const own = toolCallId === undefined ? undefined : asked.get(toolCallId);
140
+ const task = own ?? (running.size === 1 ? asked.get([...running][0]!) : undefined) ?? "";
141
+ const at = now();
142
+ const before = cards.get(id) ?? found(person, id, at);
143
+ const card = { name: before?.name ?? `${stamp(at)}${suffix(id)}`, title: firstLine(task) || (text(event["agent"]) ?? "subagent"), at };
144
+ cards.set(id, card);
145
+ write(person, card, task);
146
+ });
147
+
148
+ pi.events.on(COMPLETE, (data) => {
149
+ const event = record(data);
150
+ const id = text(event["id"]) ?? text(event["runId"]);
151
+ const person = personDir(cwd);
152
+ if (id === undefined || person === undefined) return;
153
+ const at = now();
154
+ const card = cards.get(id) ?? found(person, id, at) ?? { name: `${stamp(at)}${suffix(id)}`, title: "", at };
155
+ cards.delete(id);
156
+ write(person, { ...card, title: card.title || (text(event["agent"]) ?? "subagent") }, endWords(event));
157
+ });
158
+ }
159
+
160
+ export default function (pi: ExtensionAPI): void {
161
+ boardCards(pi);
162
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@comchatx/cx-subagents",
3
- "version": "0.1.1",
4
- "description": "Lets cx hand a long task to a helper in the background and keep answering you; the result comes back into the conversation by itself.",
3
+ "version": "0.2.1",
4
+ "description": "Puts what cx does in the background on the board.",
5
5
  "license": "MIT",
6
6
  "author": "ComChatX",
7
7
  "homepage": "https://github.com/comchatx/cx-subagents#readme",
@@ -15,40 +15,34 @@
15
15
  ],
16
16
  "type": "module",
17
17
  "pi": {
18
- "skills": [
19
- "./skills"
18
+ "extensions": [
19
+ "./extensions/board-cards.ts"
20
20
  ]
21
21
  },
22
22
  "files": [
23
- "skills"
23
+ "extensions",
24
+ "README.zh-CN.md"
24
25
  ],
25
26
  "scripts": {
26
27
  "typecheck": "tsc --noEmit",
27
28
  "test": "vitest run"
28
29
  },
29
30
  "comchatx": {
30
- "about": "让 cx 把耗时的任务交给后台助手,自己继续回答你。",
31
- "description": "任务要花很长时间时,比如跨几个数据源做调研、逐个查看大量 listing,cx 会把它交给一个在后台运行的助手,告诉你它这样做了,然后继续回答你。助手的结果会自己回到对话里。\n\n## 结果什么时候回来\n\n助手最多运行 30 分钟。做完时,cx 会在对话里告诉你结果;到 30 分钟还没做完,cx 会说它超时了。在 cx 的电脑上,同一个对话里最多同时有两个助手在后台运行:这是 ComChatX 在电脑上的设置,不是这个插件的;没有这项设置的电脑不限个数。\n\n## 费用\n\n助手和 cx 用同一个模型,由同一份余额付费。助手运行时,cx 的电脑保持开机,开机时长也要付费。\n\n## 电脑关机时\n\n如果 cx 的电脑在结果交回之前关机,这个结果就丢了,需要重新运行。",
31
+ "about": "把 cx 在后台做的事放到看板上。",
32
32
  "mark": {
33
33
  "line": "rocket"
34
34
  },
35
35
  "section": "extensions",
36
36
  "builds": [
37
- "pi-subagents"
38
- ],
39
- "skills": {
40
- "cx-subagents": {
41
- "about": {
42
- "en": "Lets cx hand a long task to a helper in the background and keep answering; the result comes back into the conversation.",
43
- "zh": "让 cx 把耗时的任务交给后台助手并继续回答;结果会回到对话里。"
44
- }
45
- }
46
- }
37
+ "pi-subagents",
38
+ "@comchatx/cx-board"
39
+ ]
47
40
  },
48
41
  "devDependencies": {
49
- "@earendil-works/pi-coding-agent": "1.3.0",
42
+ "@earendil-works/pi-coding-agent": "1.4.0",
50
43
  "@types/node": "26.2.0",
51
- "pi-subagents": "0.83.0",
44
+ "@vitest/coverage-v8": "4.1.11",
45
+ "pi-subagents": "0.85.0",
52
46
  "typescript": "7.0.2",
53
47
  "vitest": "4.1.11"
54
48
  }
@@ -1,23 +0,0 @@
1
- ---
2
- name: cx-subagents
3
- description: "When a task will take long - a research across several data sources, a look through many listings - cx hands it to a helper in the background with pi-subagents' `subagent` tool, tells the seller it has, and keeps answering; the helper's result comes back into the conversation on its own. Needs pi-subagents; if no `subagent` tool is offered, it is not installed, and the seller installs it in Plugins."
4
- ---
5
-
6
- ## When a task goes to the background
7
-
8
- - A research across several data sources, or a look through many listings: work that would hold the conversation for minutes.
9
- - The seller asked for the work. In this box that is the instruction to hand a long piece of it to a helper; ask nothing more first.
10
- - Not for a quick answer, a single read, or a step whose result the next answer needs at once: do those in the conversation.
11
-
12
- ## How
13
-
14
- - Call `subagent` with one agent and the whole task in your own words: what to look at, what to bring back. Leave `async` out: a run goes to the background here.
15
- - Then hand the conversation back: tell the seller in one sentence that the work runs in the background and that its result will come here, and answer whatever they ask next.
16
- - On cx's computer at most two helpers run at a time in one conversation, by ComChatX's setting on the computer, and a third is refused until one ends; a computer without that setting sets no cap.
17
-
18
- ## When it comes back
19
-
20
- - The result arrives by itself, as a message that starts a turn of yours. Tell the seller what the helper found, in your words, as the answer to what they asked for.
21
- - A helper stops at 30 minutes, and its message says so: tell the seller it ran out, and offer to split the work.
22
- - If cx's computer stops before a result is handed back, that result is lost. If the seller asks after a stop, say so and offer to run it again.
23
- - A helper uses the same model as you, paid from the same balance, and cx's computer stays on while it runs.