pi-firecode 1.0.0 → 1.0.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 +55 -41
- package/README.zh-CN.md +74 -0
- package/config.example.jsonc +21 -22
- package/dist/config.example.jsonc +90 -0
- package/dist/index.js +11464 -0
- package/dist/master/prompts/master.en.md +22 -0
- package/{master → dist/master}/prompts/master.zh.md +3 -3
- package/dist/master/prompts/worker.en.md +1 -0
- package/dist/watcher/prompts/watch.en.md +43 -0
- package/package.json +10 -27
- package/activity.ts +0 -92
- package/busy.ts +0 -188
- package/config.ts +0 -500
- package/deliver.ts +0 -119
- package/flame.ts +0 -149
- package/format.ts +0 -133
- package/header.ts +0 -218
- package/herdr-client.ts +0 -60
- package/index.ts +0 -68
- package/jsonc.ts +0 -34
- package/master/actions.ts +0 -293
- package/master/activity-list.ts +0 -281
- package/master/event-card.ts +0 -87
- package/master/event-format.ts +0 -83
- package/master/guard.ts +0 -46
- package/master/index.ts +0 -252
- package/master/list-view.ts +0 -129
- package/master/outbox.ts +0 -188
- package/master/prompt.ts +0 -26
- package/master/role.ts +0 -18
- package/master/run.ts +0 -315
- package/master/runtime.ts +0 -259
- package/master/spawn.ts +0 -234
- package/master/state.ts +0 -225
- package/master/worker-view.ts +0 -517
- package/provider/claude-sub.ts +0 -151
- package/provider/openai-native/index.ts +0 -8
- package/provider/openai-native/src/compact-client.ts +0 -350
- package/provider/openai-native/src/config.ts +0 -297
- package/provider/openai-native/src/extension.ts +0 -93
- package/provider/openai-native/src/native-compaction.ts +0 -151
- package/provider/openai-native/src/native-details.ts +0 -157
- package/provider/openai-native/src/native-replay.ts +0 -253
- package/provider/openai-native/src/native-runtime.ts +0 -144
- package/provider/openai-native/src/options.ts +0 -87
- package/provider/openai-native/src/request-pipeline.ts +0 -21
- package/provider/openai-native/src/responses-input.ts +0 -433
- package/review/advisor.ts +0 -110
- package/review/card.ts +0 -398
- package/review/checkpoint.ts +0 -378
- package/review/evidence.ts +0 -230
- package/review/index.ts +0 -992
- package/review/occupancy.ts +0 -25
- package/review/outcome.ts +0 -98
- package/review/prompt.ts +0 -209
- package/review/reviewer.ts +0 -353
- package/review/session.ts +0 -110
- package/review/state.ts +0 -877
- package/review/ui.ts +0 -89
- package/round-recorder.ts +0 -18
- package/session/herdr-display.ts +0 -87
- package/session/presets.ts +0 -300
- package/session/quota.ts +0 -123
- package/session/rename.ts +0 -46
- package/session/stats.ts +0 -313
- package/statusbar/index.ts +0 -256
- package/statusbar/render.ts +0 -113
- package/theme.ts +0 -45
- package/tools/actions.ts +0 -53
- package/tools/assistant-view.ts +0 -64
- package/tools/click-anchor.ts +0 -56
- package/tools/group-view.ts +0 -420
- package/tools/grouping.ts +0 -158
- package/tools/host.ts +0 -369
- package/tools/index.ts +0 -186
- package/tools/line.ts +0 -231
- package/tools/machine.ts +0 -78
- package/tools/parts.ts +0 -143
- package/tools/round.ts +0 -95
- package/tools/timing.ts +0 -22
- package/tools/turn-clock.ts +0 -41
- package/tools/turn-summary.ts +0 -109
- package/truncated-write.ts +0 -18
- package/watcher/card.ts +0 -91
- package/watcher/index.ts +0 -176
- package/watcher/observer.ts +0 -75
- package/watcher/transcript.ts +0 -56
- /package/{master → dist/master}/prompts/worker.zh.md +0 -0
- /package/{review → dist/review}/prompts/advisor.en.md +0 -0
- /package/{review → dist/review}/prompts/advisor.zh.md +0 -0
- /package/{review → dist/review}/prompts/review.en.md +0 -0
- /package/{review → dist/review}/prompts/review.zh.md +0 -0
- /package/{watcher → dist/watcher}/prompts/watch.zh.md +0 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
When subagents is active you are the one and only Master: you push the task to delivery yourself; delegation is a tool, not a duty. The final diff review and the delivery verdict always stay in your own hands.
|
|
2
|
+
|
|
3
|
+
- Where to do it yourself: delegation buys several large pieces of work moving at once, and costs a cold start of tens of seconds per Worker, an overall pace set by the slowest piece, and a final merge; your codemode can already call tools in parallel and filter output. Work that splits into several pieces, each of which takes a long time on its own (a dozen minutes or more), goes to parallel Workers; work you can finish in a few minutes with a parallel script in the main session, even research or an audit, you do yourself. Split by non-overlapping scopes (files, modules, objects) of similar size; never have several Workers read the same code by concern, and never split one piece of work into writing code, writing tests and reviewing across different Workers. Do not redo by hand what you have handed out. Use the elapsed time at the end of an event to judge whether parallelism is still worth it.
|
|
4
|
+
- Dispatch: pick the role from the roster by its stated use; start must pass role explicitly, and send passes role when the function needs to change; override thinking only when the user explicitly asks. The delegation carries pointers only: checkout path, ticket number and related commits, constraints specific to this ticket; do not restate the ticket body, the Worker reads the ticket itself. When a Worker moves to another checkout, send with cwd.
|
|
5
|
+
- Parallelism: work with no blocking edge between pieces is dispatched in parallel by default. Before implementing in parallel, freeze the shared boundary: interfaces, shared types, registries and schemas are landed first by you or a single Worker; the other Workers then start at the same time, divided by path, with exactly one writer per shared file, and each delegation names its paths. Integration is judged by the test gate.
|
|
6
|
+
- Tickets: when the project has a tracker, before every dispatch claim the ticket by convention, align with the state of other people's tickets and start from the latest code; for a new problem you decide whether to open a new ticket or hand it to a Worker present with the right context, and Workers never open tickets. Without a tracker, the user's acceptance is the delivery boundary.
|
|
7
|
+
- Implementation standard: Workers implement per the implement skill; at final review you judge by the same standard and check three things: the acceptance tests verify the requirement and not the implementation, the red failure was because the work was not yet implemented, and the implementation commits did not touch the acceptance tests; if any is missing, send it back.
|
|
8
|
+
- Harvest: research and watching Workers are killed as soon as their key points are harvested. When a plan artifact exists, its maintenance moves to you along with command.
|
|
9
|
+
- Research handoff: a focused exploration by an execution-tier model (reading code to locate, designing an approach) is continued by sending to the same Worker to execute, since the scene is the working context. Cheap-tier or high-noise research (lots of searching, logs, dead ends) harvests only structured leads (facts, paths, candidates, evidence sources, open questions); after verifying them, dispatch a separate execution Worker and do not reuse the transcript.
|
|
10
|
+
- Delivery: Worker results, interruptions and review final states arrive automatically (a batch returning one after another is merged into a single wake-up); when you have nothing of your own to do after dispatching, end the turn and wait for delivery; do not poll for results with sleep, tail or subagents_list. Use tail only to read execution details on demand.
|
|
11
|
+
- Waiting: you talk to the user directly, so a single tool call must not block for more than 60 seconds in total of sleep, wait or watch (including chained sleeps and gh run watch), otherwise the user cannot talk to you meanwhile; longer waits go to the role whose stated use covers CI, deployment or long-test waiting, or to the background, and you check the result when it comes back.
|
|
12
|
+
- On receiving results: the UI has already shown each result to the user in one line. When other Workers of the same batch are still running and this result needs no action from you right now, output no text at all this turn (no progress talk such as "X is done" or "still waiting"), just end the turn; give the user one summary when the whole batch has returned. If it needs action from you (a failure, a resume, a decision), act directly and state only the action and the reason, without repeating the result text.
|
|
13
|
+
- A result whose title carries "(you dispatched this directly in the Worker view)" is something the user said to that Worker directly in the Worker view (the "You said:" lines in the body are the verbatim words): you were not waiting for it, so it does not wake you and arrives with the next turn; the user has already seen it, so do not repeat it.
|
|
14
|
+
- A `<firecode_review>` envelope comes from fire-review in your own session (it reviews your changes) and has nothing to do with Workers; a Worker's review final state arrives only through `<firecode_master_event>`.
|
|
15
|
+
- Talk to the user in plain language: do not relay the internal fields and state words in the pool snapshot (disposition, awaiting disposition, handling state, working/idle and the like); say only the result, the risk and the next step.
|
|
16
|
+
- What adversarial review is: the review action starts the built-in fire-review state machine; it is not another Worker reading the code, and you must not simulate it by hand. It runs inside the original Worker session: several independent models read that Worker's full work record in parallel, check files and run read-only verification, and each returns PASS or a FAIL with evidence; when consecutive failures reach the threshold, an advisor model arbitrates whether to continue or stop. FAIL findings flow back to the same Worker for repair automatically, and the next round starts after the fix, until it passes, the advisor stops it or the rounds run out; the final state is delivered automatically. A pass may still carry suggestions that do not block delivery.
|
|
17
|
+
- When and how to review: for complex, high-impact implementations and for tasks that a narrow test cannot reliably verify, pass review:true at start; omit it otherwise. review:true only records a review obligation and does not start a review by itself; it is not lost to send, reload, interruption or failure, and without fulfilling it you cannot ack. After a Worker returns a result and verification is done, you start the review; a ticket without a registered obligation can also be reviewed on your initiative while the Worker is idle; to abandon the whole ticket, kill it directly.
|
|
18
|
+
- Delivery sequence: review passes → if a non-blocking suggestion is really necessary, close it out in the original Worker session → ack → final diff review, and for important changes dogfood it yourself or through a Worker → push, open the PR and merge (with a local tracker, merge back to trunk). Pushing and opening a PR are forbidden before ack, draft included: opening a PR early only buys a few minutes of parallel CI, at the cost of rework, CI re-runs and PR noise. For CI and release waits, dispatch the role whose stated use covers CI, deployment or long-test waiting to run the project's ship-wait entry point (named in the project AGENTS.md), block once until a final state and then relay it. After seeing the release succeed, close the ticket and sync the state.
|
|
19
|
+
- Close-out: clean up debugging artifacts, checkouts and the local stack (use the project's checkout teardown entry point), and the documents and tests made stale by this, except for parallel Masters'; implementation Workers are kept until the user accepts, and if acceptance finds problems you resume them on the spot.
|
|
20
|
+
- Report: the first sentence of the closing report to the user states the delivery status (shipped, awaiting merge or awaiting your acceptance) and what the user must do now, and lists what is not closed out: running or kept Workers, unmerged branches, checkouts not deleted; for verification state only what is not covered and what failed, without repeating commands run and exit codes.
|
|
21
|
+
|
|
22
|
+
Call templates: start {"worker":"fix-auth","role":"<a role from the roster>","prompt":"Check out <path>, implement ticket #<n>; constraints for this ticket: …"}; send {"worker":"fix-auth","prompt":"follow-up instructions"}; resume in a new checkout: send {"worker":"fix-auth","prompt":"…","cwd":"<new checkout path>"}.
|
|
@@ -8,15 +8,15 @@ subagents 激活时,你是唯一的指挥官(Master):亲手把任务推
|
|
|
8
8
|
- 收割:调研与盯守 Worker 收割要点后立即 kill。计划产物存在时,其维护责任随指挥权归你。
|
|
9
9
|
- 调研衔接:执行档模型的聚焦探索(读代码定位、方案设计)直接 send 续派原 Worker 执行,现场就是工作上下文。廉价档或高噪声调研(大量搜索、日志、死路)只收割结构化线索(事实、路径、候选、证据来源、未决问题),核实后另派执行 Worker,不复用 transcript。
|
|
10
10
|
- 投递:Worker 结果、中断与审查终态会自动送达(陆续返回的一批会合并成一次唤醒);派发后手头没有亲手可做的活就结束回合等送达,不用 sleep、tail 或 subagents_list 轮询等结果。tail 仅用于按需读取执行细节。
|
|
11
|
-
- 等待:你直接与用户对话,单次工具调用累计阻塞不超过 60 秒的 sleep、wait 或 watch(含串联多个 sleep、gh run watch
|
|
11
|
+
- 等待:你直接与用户对话,单次工具调用累计阻塞不超过 60 秒的 sleep、wait 或 watch(含串联多个 sleep、gh run watch),否则这段时间用户无法和你沟通;更长的等待派适用场景写着 CI、部署、长测试等待的角色执行,或放后台,回来再查结果。
|
|
12
12
|
- 收到结果:界面已用一行把每个结果给用户看了。同批还有子代理在跑、这条结果不需要你此刻动作时,本回合不输出任何文字(不写“X 已完成”“继续等”之类的进度话),直接结束回合;等这一批全部返回再给用户一次汇总。需要你动作(失败、要续派、要拍板)就直接动作,只说动作和理由,不复述结果原文。
|
|
13
13
|
- 标题带“(你在子代理视图里直接派的)”的结果是用户在子代理视图里直接跟它说的(正文“你说:”是原话):你没在等它,所以它不唤醒你,随下一回合到达;用户已亲眼看过,不复述。
|
|
14
14
|
- `<firecode_review>` 信封来自你自己这个会话的 fire-review(审查的是你的改动),与子代理无关;子代理的审查终态只经 `<firecode_master_event>` 送达。
|
|
15
15
|
- 对用户说人话:不转述池快照里的内部字段与状态词(disposition、待发落、处置状态、working/idle 之类),只说结果、风险和下一步。
|
|
16
16
|
- 对抗性审查是什么:review 动作起的是内建的 fire-review 状态机,不是另派 Worker 看代码,也不要手工模拟。它在原 Worker 会话里跑:多个独立模型并行读该 Worker 的完整工作记录、核对文件、跑只读验证,各自出 PASS 或带证据的 FAIL;连续失败到阈值召顾问模型仲裁继续还是叫停。FAIL 的发现自动回流给同一个 Worker 修复,修完进下一轮,直到通过、顾问叫停或轮数用尽,终态自动送达。通过时仍可能附带不阻塞交付的建议。
|
|
17
17
|
- 何时与怎么审:复杂且影响大的实现,以及无法靠窄测可靠验收的任务,start 时传 review:true;其余省略。review:true 只登记审查义务,不自动开审——它不因 send、reload、中断或失败丢失,未履行就不能 ack。Worker 返回结果并完成验证后由你发起 review;没登记义务的票也可以在 Worker 空闲时主动审;整票放弃直接 kill。
|
|
18
|
-
- 交付:顺序是审查通过 → 非阻塞建议确有必要就在原 Worker 会话收口 → ack → 终审 diff,重要改动亲手或派子代理 dogfood 试用 → 推送、开 PR 与合并(本地工单库则合回主干)。ack 之前禁止推送与开 PR,draft 也不行——提前开 PR 只换来几分钟 CI 并行,代价是打回重修、CI 重跑和 PR 噪音。CI
|
|
18
|
+
- 交付:顺序是审查通过 → 非阻塞建议确有必要就在原 Worker 会话收口 → ack → 终审 diff,重要改动亲手或派子代理 dogfood 试用 → 推送、开 PR 与合并(本地工单库则合回主干)。ack 之前禁止推送与开 PR,draft 也不行——提前开 PR 只换来几分钟 CI 并行,代价是打回重修、CI 重跑和 PR 噪音。CI 与发布等待派适用场景写着 CI、部署、长测试等待的角色,执行项目的上线等待入口(项目 AGENTS.md 指明),一次阻塞到终态后转述。看到发布成功后关闭工单并同步状态。
|
|
19
19
|
- 收口:清理调试产物、检出与本地栈(用项目的拆检出入口)、因此失效的文档和测试,并行指挥官的除外;实现 Worker 保留到用户验收通过,验出问题就地续派。
|
|
20
20
|
- 汇报:给用户的收尾汇报第一句说交付状态(已上线、待合并或待你验收)和用户此刻要做的事,并列出还没收口的:在跑或保留的子代理、未合并的分支、未删的检出;验证只说没覆盖到的和失败的,跑过的命令与退出码不复述。
|
|
21
21
|
|
|
22
|
-
调用样板:start {"worker":"fix-auth","role":"
|
|
22
|
+
调用样板:start {"worker":"fix-auth","role":"<角色表中的角色>","prompt":"检出 <路径>,实现工单 #<n>;本票约束:…"};send {"worker":"fix-auth","prompt":"后续说明"};续派到新检出:send {"worker":"fix-auth","prompt":"…","cwd":"<新检出路径>"}。
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
You are a Worker delegated by the Master and work only inside the current checkout, per the delegation. When the delegation is tied to a ticket or asks for an implementation, read the implement skill before starting and deliver evidence by its steps. Verify your changes and report the result, the evidence and the remaining risks; if you cannot finish or verify, report the blocker and the scene honestly and never fake success. Git operations stay local and cover only the paths you changed. You are a delegated Worker and do not talk to the user directly: long commands the task needs (sleep, long tests, builds, watching) run in the foreground as usual; when waiting for background tests, builds or services, use one blocking wait with a timeout until it ends, without polling in segments; the Master will interrupt you if it needs to. A turn ends only when the deliverable is complete or you are truly blocked: do not end a turn by announcing the next step, asking whether to continue, listing open items that do not block the remaining work, or giving a progress report; a watching task replies only once the final state defined in the delegation is reached.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Watcher
|
|
2
|
+
|
|
3
|
+
You are the FireCode watcher: a pair of eyes that keeps watching the main session from the sidelines. You work in your own read-only session, and what you see is the incremental record the main session appends turn by turn.
|
|
4
|
+
|
|
5
|
+
## Role
|
|
6
|
+
|
|
7
|
+
You are a bystander, not a second commander. You do not take over the task, give orders, plan steps, or make decisions for the main agent.
|
|
8
|
+
|
|
9
|
+
Your suggestions are second opinions for the main agent to weigh, not instructions it must follow — every suggestion you write is presented as "for weighing; don't follow blindly". The main agent has fuller context and the user's direct authorization; when its approach conflicts with your judgment, it has reason to keep going its own way.
|
|
10
|
+
|
|
11
|
+
Your suggestion is delivered to the main agent on the spot: if it is busy, the suggestion is inserted at the next sentence seam; if it is idle, the suggestion wakes it into a new turn. Speak only when delivering the words right now would change the main agent's next action; otherwise stay silent.
|
|
12
|
+
|
|
13
|
+
Most turns need no suggestion at all. Saying nothing is the norm and the correct answer.
|
|
14
|
+
|
|
15
|
+
## Scope
|
|
16
|
+
|
|
17
|
+
Speak only on these kinds of problems:
|
|
18
|
+
|
|
19
|
+
- **Deviation**: what is being done does not match the user's request or the ticket, or has quietly grown into scope nobody asked for.
|
|
20
|
+
- **Over-engineering**: abstractions, configuration, compatibility layers, or defensive branches added for requirements that do not exist.
|
|
21
|
+
- **Missed requirements**: parts the request or ticket explicitly asks for are skipped, forgotten, or downgraded.
|
|
22
|
+
- **Loose ends**: the change leaves dead code, stale docs, an un-updated source of truth, verification that was not run, or a ticket that was not closed out.
|
|
23
|
+
- **Dangerous operations**: destructive commands, irreversible rewrites of data or history, writes outside the current working scope, secrets written into code or commits.
|
|
24
|
+
|
|
25
|
+
Style preferences, refactors that are possible but unnecessary, and more elegant ways you thought of yourself are all outside the scope.
|
|
26
|
+
|
|
27
|
+
## Output contract
|
|
28
|
+
|
|
29
|
+
You can speak only through the `advise` tool. Any other text will be seen by no one.
|
|
30
|
+
|
|
31
|
+
`advise` takes exactly one parameter, `note`: one sentence stating the problem and where it is, plus one more sentence on why if needed.
|
|
32
|
+
|
|
33
|
+
Submit at most one suggestion per evaluation. When there are several problems, raise only the most pressing one and leave the rest for the next evaluation.
|
|
34
|
+
|
|
35
|
+
Do not raise a problem the main agent has already corrected within the same batch of increments: read the whole batch before judging; a successful retry after a failure, or a fix after an error, counts as corrected.
|
|
36
|
+
|
|
37
|
+
Do not submit the same suggestion twice. If things have worsened to the point that you must raise it again, say what changed to make it urgent.
|
|
38
|
+
|
|
39
|
+
## Evidence discipline
|
|
40
|
+
|
|
41
|
+
A problem you point out must come with a location: which file, which function, which tool call, which item of the request. A hunch you cannot locate should not be submitted.
|
|
42
|
+
|
|
43
|
+
The incremental record is trimmed: it omits the reasoning and the diff bodies. For a problem you only guessed from the increments, first verify the real state with the read-only tools (read / grep / find / ls) before deciding whether to speak; if verification shows you simply missed something, do nothing.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-firecode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "A modular Pi extension for terminal UI, session workflows, adversarial review, and delegated agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,37 +17,20 @@
|
|
|
17
17
|
"pi-extension"
|
|
18
18
|
],
|
|
19
19
|
"files": [
|
|
20
|
-
"
|
|
21
|
-
"config.example.jsonc"
|
|
22
|
-
"master/*.ts",
|
|
23
|
-
"master/prompts/*.md",
|
|
24
|
-
"provider/claude-sub.ts",
|
|
25
|
-
"provider/openai-native/index.ts",
|
|
26
|
-
"provider/openai-native/src/compact-client.ts",
|
|
27
|
-
"provider/openai-native/src/config.ts",
|
|
28
|
-
"provider/openai-native/src/extension.ts",
|
|
29
|
-
"provider/openai-native/src/native-compaction.ts",
|
|
30
|
-
"provider/openai-native/src/native-details.ts",
|
|
31
|
-
"provider/openai-native/src/native-replay.ts",
|
|
32
|
-
"provider/openai-native/src/native-runtime.ts",
|
|
33
|
-
"provider/openai-native/src/options.ts",
|
|
34
|
-
"provider/openai-native/src/request-pipeline.ts",
|
|
35
|
-
"provider/openai-native/src/responses-input.ts",
|
|
36
|
-
"!provider/openai-native/src/*.test.ts",
|
|
37
|
-
"review/*.ts",
|
|
38
|
-
"review/prompts/*.md",
|
|
39
|
-
"session/*.ts",
|
|
40
|
-
"statusbar/*.ts",
|
|
41
|
-
"tools/*.ts",
|
|
42
|
-
"watcher/*.ts",
|
|
43
|
-
"watcher/prompts/*.md"
|
|
20
|
+
"dist",
|
|
21
|
+
"config.example.jsonc"
|
|
44
22
|
],
|
|
45
23
|
"scripts": {
|
|
46
|
-
"test": "bun test"
|
|
24
|
+
"test": "bun test",
|
|
25
|
+
"typecheck": "bun scripts/typecheck.ts",
|
|
26
|
+
"build": "bun scripts/build.ts",
|
|
27
|
+
"prepack": "bun scripts/build.ts"
|
|
47
28
|
},
|
|
48
29
|
"pi": {
|
|
30
|
+
"image": "https://raw.githubusercontent.com/Suge8/firecode/main/design/promo/og-1280x640.png",
|
|
31
|
+
"video": "https://raw.githubusercontent.com/Suge8/firecode/main/design/promo/hero.mp4",
|
|
49
32
|
"extensions": [
|
|
50
|
-
"./index.
|
|
33
|
+
"./dist/index.js"
|
|
51
34
|
]
|
|
52
35
|
},
|
|
53
36
|
"peerDependencies": {
|
package/activity.ts
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 活动行:输入框上方子代理活动列表(master/activity-list.ts)的一行布局。
|
|
3
|
-
* 标记 名字 角色 · 当前动作 · 提醒 …… 耗时。宽度不够时的退让顺序:先缩名字列(整表一致,名字截短带 …)、
|
|
4
|
-
* 再丢角色(整表一致),最后截动作文字。卡住行的提醒比动作重要:先保提醒(放不下全写法换短写法),
|
|
5
|
-
* 动作截到看不出是哪条命令(不足 COMFORT_ACTION_WIDTH)就整段不显示,不留“操作 …”这种没信息的残片。
|
|
6
|
-
*/
|
|
7
|
-
import type { Theme, ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
8
|
-
import { visibleWidth } from "@earendil-works/pi-tui";
|
|
9
|
-
import { clip } from "./format.js";
|
|
10
|
-
import { HEAT_COLORS, paint } from "./flame.js";
|
|
11
|
-
|
|
12
|
-
/** 动作文字至少留这么宽才值得显示(一个字加省略号)。 */
|
|
13
|
-
const MIN_ACTION_WIDTH = 4;
|
|
14
|
-
/** 动作文字想要的最小宽度:保得住它才保名字全长与角色(约“操作 $ bun t…”)。 */
|
|
15
|
-
const COMFORT_ACTION_WIDTH = 12;
|
|
16
|
-
/** 名字列再窄就认不出是谁了。 */
|
|
17
|
-
const MIN_NAME_WIDTH = 9;
|
|
18
|
-
const SEP = " · ";
|
|
19
|
-
|
|
20
|
-
export interface ActivityRow {
|
|
21
|
-
/** 已着色的单格标记:火苗、◈、‖、◌、✓、✗。 */
|
|
22
|
-
mark: string;
|
|
23
|
-
name: string;
|
|
24
|
-
role: string;
|
|
25
|
-
/** 当前动作;为空时只显示角色(如空闲行)。 */
|
|
26
|
-
action: string;
|
|
27
|
-
/** 当前动作的语气:审查金色,失败红色,被中断黄色。 */
|
|
28
|
-
tone?: "review" | "failed" | "warning";
|
|
29
|
-
/** 动作后追加的黄色提醒(卡住行的“无输出”):比动作重要,宽度不够先换短写法,动作没信息就整段不显示。 */
|
|
30
|
-
note?: { full: string; short: string };
|
|
31
|
-
elapsed: string;
|
|
32
|
-
/** 已落定的行文字退为暗色,只有标记保留颜色。 */
|
|
33
|
-
settled?: boolean;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const pad = (text: string, width: number) => text + " ".repeat(Math.max(0, width - visibleWidth(text)));
|
|
37
|
-
|
|
38
|
-
/** 缩进、标记、名字列与耗时之外留给“角色 · 动作 · 提醒”的宽度。 */
|
|
39
|
-
function middleRoom(row: ActivityRow, width: number, nameWidth: number): number {
|
|
40
|
-
return width - (2 + 1 + 1 + nameWidth + 2 + visibleWidth(row.elapsed) + 1) - 1;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** 一行至少要给动作和提醒留多少:动作想要的宽度加提醒的短写法。 */
|
|
44
|
-
function wanted(row: ActivityRow): number {
|
|
45
|
-
return (row.action ? COMFORT_ACTION_WIDTH : 0) + (row.note ? visibleWidth(row.note.short) : 0);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** 名字列按剩余空间分配:每行都给动作留够了还有余,名字就不截;不够才缩到下限。 */
|
|
49
|
-
export function nameWidthFor(rows: readonly ActivityRow[], width: number): number {
|
|
50
|
-
const longest = Math.max(0, ...rows.map((row) => visibleWidth(row.name)));
|
|
51
|
-
const room = Math.min(...rows.map((row) => middleRoom(row, width, longest) - wanted(row)));
|
|
52
|
-
return room >= 0 ? longest : Math.min(longest, Math.max(MIN_NAME_WIDTH, longest + room));
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/** 这一行在给定宽度下是否值得保留角色;列表据此整表决定,列才对得齐。 */
|
|
56
|
-
export function roleFits(row: ActivityRow, width: number, nameWidth: number): boolean {
|
|
57
|
-
return middleRoom(row, width, nameWidth) - visibleWidth(row.role) - SEP.length - wanted(row) >= 0;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function renderActivityRow(
|
|
61
|
-
row: ActivityRow,
|
|
62
|
-
width: number,
|
|
63
|
-
nameWidth: number,
|
|
64
|
-
theme: Theme,
|
|
65
|
-
showRole = roleFits(row, width, nameWidth),
|
|
66
|
-
): string {
|
|
67
|
-
const color = (base: ThemeColor) => (row.settled ? "dim" : base);
|
|
68
|
-
const head = ` ${row.mark} ${theme.fg(color("text"), pad(clip(row.name, nameWidth), nameWidth))}`;
|
|
69
|
-
const room = middleRoom(row, width, nameWidth);
|
|
70
|
-
if (room < 0) return clip(head, width, "end", "");
|
|
71
|
-
const paintAction = (text: string) =>
|
|
72
|
-
row.tone === "review" ? paint(HEAT_COLORS.gold, text)
|
|
73
|
-
: row.tone === "failed" ? theme.fg("error", text)
|
|
74
|
-
: row.tone === "warning" ? theme.fg("warning", text)
|
|
75
|
-
: theme.fg(color("muted"), text);
|
|
76
|
-
const actionRoom = showRole ? room - visibleWidth(row.role) - SEP.length : room;
|
|
77
|
-
const action = row.note ? withNote(row, actionRoom, paintAction, theme) : (
|
|
78
|
-
row.action && actionRoom >= MIN_ACTION_WIDTH ? paintAction(clip(row.action, actionRoom)) : "");
|
|
79
|
-
const role = theme.fg(color("muted"), row.role);
|
|
80
|
-
const middle = showRole ? (action ? `${role}${theme.fg("dim", SEP)}${action}` : role) : action;
|
|
81
|
-
return `${head} ${pad(middle, room + 1)}${theme.fg(color("muted"), row.elapsed)} `;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/** “动作 · 提醒”:提醒优先;剩下的位置装得下整条动作或至少能认出命令才带上动作。 */
|
|
85
|
-
function withNote(row: ActivityRow, room: number, paintAction: (text: string) => string, theme: Theme): string {
|
|
86
|
-
const { full, short } = row.note!;
|
|
87
|
-
const note = visibleWidth(full) <= room ? full : clip(short, room);
|
|
88
|
-
const actionRoom = room - visibleWidth(note) - SEP.length;
|
|
89
|
-
const fits = visibleWidth(row.action) <= actionRoom || actionRoom >= COMFORT_ACTION_WIDTH;
|
|
90
|
-
if (!row.action || !fits) return theme.fg("warning", note);
|
|
91
|
-
return `${paintAction(clip(row.action, actionRoom))}${theme.fg("warning", `${SEP}${note}`)}`;
|
|
92
|
-
}
|
package/busy.ts
DELETED
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* “会话进行中”的单一事实:指挥官回合在跑 || 有子代理在飞(定义见 master/outbox.ts)|| 主会话 /fire-review 进行中(review 的占用频道)。
|
|
3
|
-
* 指挥官回合结束不等于歇下:回合结束后仍有子代理在飞、审查在跑,会话照旧进行中。
|
|
4
|
-
* Master 是在飞子代理数的唯一发布者;轮记录器、上边框、本轮摘要与轮次时钟都经 watchBusy 读同一个事实并消费同一个歇下边沿。
|
|
5
|
-
* 本段进行中的起点也只在这里记:首次变忙那一刻起,中途的人类输入与结果唤醒都不重置,歇下边沿报告整段事实:时长、终态、均速。
|
|
6
|
-
* 频道名与 payload 只在本文件定义。
|
|
7
|
-
*/
|
|
8
|
-
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
9
|
-
import { formatDuration } from "./format.js";
|
|
10
|
-
import { OCCUPANCY_CHANNEL, type OccupancyPayload } from "./review/occupancy.js";
|
|
11
|
-
|
|
12
|
-
/** 进程内事件总线:在飞子代理数变化时发布 `{ inFlight }`,激活/停用同步。 */
|
|
13
|
-
export const WORKERS_CHANNEL = "firecode:workers";
|
|
14
|
-
export interface WorkersPayload {
|
|
15
|
-
inFlight: number;
|
|
16
|
-
/** Master 停用遗弃在飞子代理:归零只结束本段,不是歇下。 */
|
|
17
|
-
teardown?: true;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* herdr 通用“进行中”频道,与 herdr:blocked 同构:消费者按 active 的 true/false 做计数配对。
|
|
22
|
-
* Master 只在在飞数 0↔正数跃迁时发布,保证配对;指挥官自己的回合 herdr 已由 agent_start/settled 得知。
|
|
23
|
-
*/
|
|
24
|
-
export const HERDR_WORKING_CHANNEL = "herdr:working";
|
|
25
|
-
export interface HerdrWorkingPayload {
|
|
26
|
-
active: boolean;
|
|
27
|
-
label?: string;
|
|
28
|
-
}
|
|
29
|
-
export const HERDR_WORKING_LABEL = "子代理进行中";
|
|
30
|
-
|
|
31
|
-
export interface BusyView {
|
|
32
|
-
agentRunning: boolean;
|
|
33
|
-
inFlight: number;
|
|
34
|
-
/** 主会话 /fire-review 进行中(含修复与总结回合之间的等待):算会话进行中,审查时长计入这一段。 */
|
|
35
|
-
review: boolean;
|
|
36
|
-
/** 会话进行中 = 指挥官回合在跑 || 有子代理在飞 || 主会话审查进行中。 */
|
|
37
|
-
busy: boolean;
|
|
38
|
-
/** 本段进行中的起点(Date.now);当且仅当 busy 时存在。 */
|
|
39
|
-
since?: number;
|
|
40
|
-
}
|
|
41
|
-
export const IDLE: BusyView = { agentRunning: false, inFlight: 0, review: false, busy: false };
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* 本段最后一个指挥官回合的终态:宿主的回合中断信号(ctx.signal.aborted)为真即“已中断”——工具执行中被 Esc 时
|
|
45
|
-
* 宿主给的终态是 error(“The operation was aborted.”),不能只看 stopReason;其余按最后一条助手消息的 stopReason。
|
|
46
|
-
*/
|
|
47
|
-
export type Outcome = "complete" | "aborted" | "error";
|
|
48
|
-
export interface SettledRound {
|
|
49
|
-
elapsed: number;
|
|
50
|
-
outcome: Outcome;
|
|
51
|
-
/**
|
|
52
|
-
* 均速(token/s):指挥官各回合的输出 token 之和除以模型请求墙钟之和,等子代理与跑工具不算分母。
|
|
53
|
-
* 任一请求失败、中断、未配对或压缩失败则整段不给,不出半截的数。
|
|
54
|
-
*/
|
|
55
|
-
tps?: number;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** 终态字样;完成不写字。 */
|
|
59
|
-
export const OUTCOME_TEXT: Record<Outcome, string> = { complete: "", aborted: "已中断", error: "请求失败" };
|
|
60
|
-
const RATE_FORMAT = new Intl.NumberFormat("en-US", { maximumSignificantDigits: 3, useGrouping: false });
|
|
61
|
-
|
|
62
|
-
/** 落定记录的展示片段(未着色):耗时,有均速再跟一段。上边框与摘要行共用。 */
|
|
63
|
-
export function roundTexts(round: SettledRound): string[] {
|
|
64
|
-
return [formatDuration(round.elapsed), ...(round.tps ? [`${RATE_FORMAT.format(round.tps)} tps`] : [])];
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** 本段的模型请求计时;requestMs 为 undefined 表示本段已无法给出均速。 */
|
|
68
|
-
interface Requests {
|
|
69
|
-
startedAt?: number;
|
|
70
|
-
requestMs?: number;
|
|
71
|
-
outputTokens: number;
|
|
72
|
-
}
|
|
73
|
-
const FRESH: Requests = { requestMs: 0, outputTokens: 0 };
|
|
74
|
-
/** 输出 token 少于这个数时均速没有意义(1 个 token 的快答算出来的 tps 只是噪声)。 */
|
|
75
|
-
const MIN_RATE_TOKENS = 20;
|
|
76
|
-
|
|
77
|
-
function settledRound(elapsed: number, outcome: Outcome, { startedAt, requestMs, outputTokens }: Requests): SettledRound {
|
|
78
|
-
const valid = outcome === "complete" && startedAt === undefined && requestMs && outputTokens >= MIN_RATE_TOKENS;
|
|
79
|
-
return { elapsed, outcome, ...(valid ? { tps: (outputTokens * 1_000) / requestMs } : {}) };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export interface BusyHandlers {
|
|
83
|
-
/** 任一来源变化后调用(含歇下那一次,先于 onSettled)。 */
|
|
84
|
-
onChange?(view: BusyView, ctx: ExtensionContext | undefined): void;
|
|
85
|
-
/** 会话歇下边沿:busy 由真变假时触发一次,带本段进行中的总时长与终态。两个来源——agent_settled 时在飞数为 0,或在飞数归零时指挥官已空闲。 */
|
|
86
|
-
onSettled?(ctx: ExtensionContext | undefined, round: SettledRound): void;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function outcomeOf(messages: readonly { role: string; stopReason?: string }[]): Outcome {
|
|
90
|
-
const stop = messages.findLast((message) => message.role === "assistant")?.stopReason;
|
|
91
|
-
return stop === "aborted" || stop === "error" ? stop : "complete";
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const HUBS = Symbol.for("firecode.busy");
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* 会话进行中的唯一判定与歇下边沿:上边框与轮次时钟都只订阅这里,不各自拼装。
|
|
98
|
-
* 每个 pi 只有一份状态机,首个订阅者安装宿主事件,之后只追加订阅;登记挂在 globalThis 上,
|
|
99
|
-
* 宿主按文件加载模块副本时同一个 pi 仍只命中一份。
|
|
100
|
-
* 指挥官回合以 agent_start → agent_settled(且 ctx.isIdle())为界。在飞数归零与回合落定先后不定
|
|
101
|
-
* (闲时前门投递在宿主记录这条消息后才算送达,见 deliver.ts),歇下必须在两个来源都满足的那一刻触发。
|
|
102
|
-
* 拆会话(session_shutdown)与 Master 停用遗弃子代理只结束本段,不发歇下边沿。
|
|
103
|
-
*/
|
|
104
|
-
export function watchBusy(pi: ExtensionAPI, handlers: BusyHandlers): void {
|
|
105
|
-
const hubs = ((globalThis as Record<symbol, unknown>)[HUBS] ??= new WeakMap()) as WeakMap<ExtensionAPI, BusyHandlers[]>;
|
|
106
|
-
const subscribers = hubs.get(pi);
|
|
107
|
-
if (subscribers) {
|
|
108
|
-
subscribers.push(handlers);
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
const list = [handlers];
|
|
112
|
-
hubs.set(pi, list);
|
|
113
|
-
installBusy(pi, list);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function installBusy(pi: ExtensionAPI, subscribers: readonly BusyHandlers[]): void {
|
|
117
|
-
let agentRunning = false;
|
|
118
|
-
let inFlight = 0;
|
|
119
|
-
let review = false;
|
|
120
|
-
/** 本段起点;有值即进行中。 */
|
|
121
|
-
let since: number | undefined;
|
|
122
|
-
let outcome: Outcome = "complete";
|
|
123
|
-
let requests = FRESH;
|
|
124
|
-
let ctx: ExtensionContext | undefined;
|
|
125
|
-
let closed = false;
|
|
126
|
-
const update = (teardown = false) => {
|
|
127
|
-
if (closed) return;
|
|
128
|
-
const now = Date.now();
|
|
129
|
-
const busy = agentRunning || inFlight > 0 || review;
|
|
130
|
-
if (busy && since === undefined) {
|
|
131
|
-
since = now;
|
|
132
|
-
requests = FRESH;
|
|
133
|
-
}
|
|
134
|
-
const started = since;
|
|
135
|
-
if (!busy) since = undefined;
|
|
136
|
-
const view: BusyView = { agentRunning, inFlight, review, busy, since };
|
|
137
|
-
for (const subscriber of subscribers) subscriber.onChange?.(view, ctx);
|
|
138
|
-
if (busy || started === undefined || teardown) return;
|
|
139
|
-
const round = settledRound(now - started, outcome, requests);
|
|
140
|
-
for (const subscriber of subscribers) subscriber.onSettled?.(ctx, round);
|
|
141
|
-
};
|
|
142
|
-
pi.on("session_shutdown", () => {
|
|
143
|
-
closed = true;
|
|
144
|
-
});
|
|
145
|
-
pi.on("agent_end", (event, context) => {
|
|
146
|
-
outcome = context.signal?.aborted ? "aborted" : outcomeOf(event.messages);
|
|
147
|
-
});
|
|
148
|
-
pi.on("before_provider_request", () => {
|
|
149
|
-
// 上一次请求没有等到助手 message_end 就又发起:起止无法配对。
|
|
150
|
-
requests = { ...requests, startedAt: Date.now(), requestMs: requests.startedAt === undefined ? requests.requestMs : undefined };
|
|
151
|
-
});
|
|
152
|
-
pi.on("message_end", ({ message }) => {
|
|
153
|
-
if (message.role !== "assistant") return;
|
|
154
|
-
const duration = requests.startedAt === undefined ? 0 : Date.now() - requests.startedAt;
|
|
155
|
-
const output = message.usage.output;
|
|
156
|
-
const valid = requests.requestMs !== undefined && duration > 0 && Number.isFinite(output) && output > 0
|
|
157
|
-
&& (message.stopReason === "stop" || message.stopReason === "toolUse");
|
|
158
|
-
requests = valid
|
|
159
|
-
? { requestMs: requests.requestMs! + duration, outputTokens: requests.outputTokens + output }
|
|
160
|
-
: { requestMs: undefined, outputTokens: requests.outputTokens };
|
|
161
|
-
});
|
|
162
|
-
// 压缩的模型调用没有助手 message_end,不把它的起点借给下一条回复;压缩失败则本段不给均速。
|
|
163
|
-
const clearRequest = () => { requests = { ...requests, startedAt: undefined }; };
|
|
164
|
-
pi.on("session_before_compact", clearRequest);
|
|
165
|
-
pi.on("session_compact", clearRequest);
|
|
166
|
-
pi.on("session_compact_failed", () => { requests = { requestMs: undefined, outputTokens: requests.outputTokens }; });
|
|
167
|
-
pi.on("agent_start", (_event, context) => {
|
|
168
|
-
ctx = context;
|
|
169
|
-
agentRunning = true;
|
|
170
|
-
update();
|
|
171
|
-
});
|
|
172
|
-
pi.on("agent_settled", (_event, context) => {
|
|
173
|
-
ctx = context;
|
|
174
|
-
// 宿主在 agent_settled 期间可能已有排队/延后的动作(isIdle 为 false),紧接着会再 agent_start:不算回合结束。
|
|
175
|
-
agentRunning = context.isIdle() !== true;
|
|
176
|
-
update();
|
|
177
|
-
});
|
|
178
|
-
// 主会话审查算会话进行中:审查与修复、总结回合同属这一段,审查时长计入轮记录。
|
|
179
|
-
pi.events.on(OCCUPANCY_CHANNEL, (data) => {
|
|
180
|
-
review = (data as OccupancyPayload).active;
|
|
181
|
-
update();
|
|
182
|
-
});
|
|
183
|
-
pi.events.on(WORKERS_CHANNEL, (data) => {
|
|
184
|
-
const payload = data as WorkersPayload;
|
|
185
|
-
inFlight = payload.inFlight;
|
|
186
|
-
update(payload.teardown === true);
|
|
187
|
-
});
|
|
188
|
-
}
|