@muzi3/herdr 0.3.0 → 0.3.2
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
|
@@ -30,7 +30,7 @@ extensions/
|
|
|
30
30
|
|
|
31
31
|
每个扩展目录的入口都叫 `index.ts`,与 pi 自己的目录式扩展约定一致:扩展目录内可以再放内部模块,`pi.extensions` 只指向入口,不会误加载旁支文件。
|
|
32
32
|
|
|
33
|
-
`herdr-agent-state/index.ts` 的目录名与 herdr 集成注入的文件同名,对齐上游时可以直接对比。内容分两段:官方集成正文(一直到文件末尾之前)与之后的「本包新增」段落。本包对官方正文只有**一处**改动:官方 `export default` 开头插入一行 `registerBlockedReporting(pi);` —— 该 default export 就是本扩展的唯一入口,blocked 上报只能挂在那里。重新同步上游时(`HERDR_INTEGRATION_VERSION` 从
|
|
33
|
+
`herdr-agent-state/index.ts` 的目录名与 herdr 集成注入的文件同名,对齐上游时可以直接对比。内容分两段:官方集成正文(一直到文件末尾之前)与之后的「本包新增」段落。本包对官方正文只有**一处**改动:官方 `export default` 开头插入一行 `registerBlockedReporting(pi);` —— 该 default export 就是本扩展的唯一入口,blocked 上报只能挂在那里。重新同步上游时(`HERDR_INTEGRATION_VERSION` 从 9 往上升)重取注入文件替换官方正文并补回那一行;`diff` 应只有三处新增、零删除,漏帖那一行会被 `tests/herdr-agent-state.test.ts` 拦下。
|
|
34
34
|
|
|
35
35
|
## 来源与许可
|
|
36
36
|
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
// managed by herdr; reinstalling or updating the integration overwrites this file.
|
|
3
3
|
// add custom hooks/plugins beside this file instead of editing it.
|
|
4
4
|
// HERDR_INTEGRATION_ID=pi
|
|
5
|
-
// HERDR_INTEGRATION_VERSION=
|
|
5
|
+
// HERDR_INTEGRATION_VERSION=9
|
|
6
6
|
// @ts-nocheck
|
|
7
7
|
//
|
|
8
8
|
// ---------------------------------------------------------------------------
|
|
9
9
|
// 以下为本包(@muzi3/herdr)的说明,不属于 herdr 官方正文:
|
|
10
10
|
//
|
|
11
11
|
// 1. 本文件上方内容是 herdr 官方 pi 集成正文的副本,保留是为了将来能直接与上游重新同步:
|
|
12
|
-
// 上游 `HERDR_INTEGRATION_VERSION` 从当前的
|
|
12
|
+
// 上游 `HERDR_INTEGRATION_VERSION` 从当前的 9 往上升时,重取 `herdr integration install pi`
|
|
13
13
|
// 注入的文件并替换本文件的官方段落即可(文件末尾的「本包新增」段落不受影响)。
|
|
14
14
|
// 本包对官方段落只有**一处**改动:`export default` 开头插入一行
|
|
15
15
|
// `registerBlockedReporting(pi);`(带「本包新增」标记)。因为官方的 default export 就是本
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
// ---------------------------------------------------------------------------
|
|
28
28
|
|
|
29
29
|
import net from "node:net";
|
|
30
|
+
import path from "node:path";
|
|
30
31
|
|
|
31
32
|
const HERDR_ENV = process.env.HERDR_ENV;
|
|
32
33
|
const socketPath = process.env.HERDR_SOCKET_PATH;
|
|
@@ -67,11 +68,11 @@ function sendRequestAttempt(request: unknown, timeoutMs: number): Promise<boolea
|
|
|
67
68
|
});
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
async function sendRequest(request: unknown): Promise<
|
|
71
|
+
async function sendRequest(request: unknown): Promise<boolean> {
|
|
71
72
|
if (await sendRequestAttempt(request, 500)) {
|
|
72
|
-
return;
|
|
73
|
+
return true;
|
|
73
74
|
}
|
|
74
|
-
|
|
75
|
+
return sendRequestAttempt(request, 1500);
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
type AgentState = "working" | "blocked" | "idle";
|
|
@@ -95,7 +96,10 @@ function updateSessionRef(ctx: any): void {
|
|
|
95
96
|
try {
|
|
96
97
|
const file = ctx?.sessionManager?.getSessionFile?.();
|
|
97
98
|
currentAgentSessionPath =
|
|
98
|
-
typeof file === "string" &&
|
|
99
|
+
typeof file === "string" &&
|
|
100
|
+
(path.posix.isAbsolute(file) || path.win32.isAbsolute(file))
|
|
101
|
+
? file
|
|
102
|
+
: undefined;
|
|
99
103
|
} catch {
|
|
100
104
|
currentAgentSessionPath = undefined;
|
|
101
105
|
}
|
|
@@ -165,6 +169,7 @@ function sendState(state: AgentState, message?: string, seq = nextReportSeq()):
|
|
|
165
169
|
|
|
166
170
|
let sendInFlight = false;
|
|
167
171
|
let queuedState: QueuedState | undefined;
|
|
172
|
+
let retryTimer: ReturnType<typeof setTimeout> | undefined;
|
|
168
173
|
|
|
169
174
|
function queueState(state: AgentState, message?: string): void {
|
|
170
175
|
queuedState = { state, message, seq: nextReportSeq() };
|
|
@@ -173,6 +178,17 @@ function queueState(state: AgentState, message?: string): void {
|
|
|
173
178
|
}
|
|
174
179
|
}
|
|
175
180
|
|
|
181
|
+
function scheduleStateRetry(): void {
|
|
182
|
+
if (retryTimer) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
retryTimer = setTimeout(() => {
|
|
186
|
+
retryTimer = undefined;
|
|
187
|
+
void drainStateQueue();
|
|
188
|
+
}, 1000);
|
|
189
|
+
retryTimer.unref?.();
|
|
190
|
+
}
|
|
191
|
+
|
|
176
192
|
async function drainStateQueue(): Promise<void> {
|
|
177
193
|
if (sendInFlight) {
|
|
178
194
|
return;
|
|
@@ -183,11 +199,18 @@ async function drainStateQueue(): Promise<void> {
|
|
|
183
199
|
while (queuedState) {
|
|
184
200
|
const next = queuedState;
|
|
185
201
|
queuedState = undefined;
|
|
186
|
-
await sendState(next.state, next.message, next.seq)
|
|
202
|
+
if (await sendState(next.state, next.message, next.seq)) {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (!queuedState || queuedState.seq < next.seq) {
|
|
206
|
+
queuedState = next;
|
|
207
|
+
}
|
|
208
|
+
scheduleStateRetry();
|
|
209
|
+
break;
|
|
187
210
|
}
|
|
188
211
|
} finally {
|
|
189
212
|
sendInFlight = false;
|
|
190
|
-
if (queuedState) {
|
|
213
|
+
if (queuedState && !retryTimer) {
|
|
191
214
|
void drainStateQueue();
|
|
192
215
|
}
|
|
193
216
|
}
|
|
@@ -128,6 +128,19 @@ const ReadModeEnum = StringEnum(["digest", "raw"] as const, {
|
|
|
128
128
|
description: "默认 digest:压成「前段信号行 + 尾部」;要整段原文传 raw",
|
|
129
129
|
});
|
|
130
130
|
|
|
131
|
+
/**
|
|
132
|
+
* 八个工具共用的结构化结果:codemode 脚本拿它做过滤,模型仍看 content 里的渲染文本。
|
|
133
|
+
* 与 Envelope 同形,免得多出一套真值。
|
|
134
|
+
*/
|
|
135
|
+
const envelopeOutputSchema = Type.Object({
|
|
136
|
+
ok: Type.Boolean({ description: "这条 herdr 命令是否成功" }),
|
|
137
|
+
exitCode: Type.Number({ description: "退出码;-1 表示本地截止时间到期" }),
|
|
138
|
+
timedOut: Type.Optional(Type.Boolean({ description: "本地截止时间是否到期" })),
|
|
139
|
+
output: Type.String({ description: "正文:digest 压缩后的信号行 + 尾部,或 raw 全文" }),
|
|
140
|
+
note: Type.Optional(Type.String({ description: "判据提示,或失败时的说明" })),
|
|
141
|
+
via: Type.Optional(Type.String({ description: "实际走了 agent 还是 pane 视图" })),
|
|
142
|
+
});
|
|
143
|
+
|
|
131
144
|
const CreateKindEnum = StringEnum(["workspace", "tab", "pane"] as const, {
|
|
132
145
|
description: "建 workspace、建 tab,还是切一个兄弟 pane",
|
|
133
146
|
});
|
|
@@ -367,7 +380,12 @@ function renderEnvelope(value: Envelope): string {
|
|
|
367
380
|
}
|
|
368
381
|
|
|
369
382
|
function envelopeResult(value: Envelope) {
|
|
370
|
-
return {
|
|
383
|
+
return {
|
|
384
|
+
content: [{ type: "text" as const, text: renderEnvelope(value) }],
|
|
385
|
+
details: value,
|
|
386
|
+
// 声明了 outputSchema 的工具:脚本收结构化值,模型仍收上面的渲染文本。
|
|
387
|
+
structuredContent: value,
|
|
388
|
+
};
|
|
371
389
|
}
|
|
372
390
|
|
|
373
391
|
/** 自己拒绝的决定,发生在任何 herdr 调用之前。 */
|
|
@@ -546,6 +564,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
546
564
|
pi.registerTool({
|
|
547
565
|
name: "herdr_overview",
|
|
548
566
|
label: "Herdr Overview",
|
|
567
|
+
outputSchema: envelopeOutputSchema,
|
|
549
568
|
description:
|
|
550
569
|
"盘点:一次拿到自己的位置 + 全机 workspace 拓扑 + 全机 agent 状态(名称/状态/cwd/pane id)。管理其他 agent 的第一步几乎总是它 —— 先看有哪些 agent、谁在忙、谁空着。只读。",
|
|
551
570
|
promptSnippet: "一次看清 herdr 里有哪些 agent、谁在忙、各自在哪个 pane",
|
|
@@ -624,6 +643,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
624
643
|
pi.registerTool({
|
|
625
644
|
name: "herdr_read",
|
|
626
645
|
label: "Herdr Read",
|
|
646
|
+
outputSchema: envelopeOutputSchema,
|
|
627
647
|
description:
|
|
628
648
|
"读屏:看某个 pane(或它里面的 agent)当前输出。盯梢一个 agent 在干什么、卡在哪,用它。target 用完整限定 pane id(如 wC:p77);scope=agent 走 agent 语义视图,pane 走裸终端视图。只读。默认 digest 模式:只回「前段命中的审批/报错行 + 尾部 30 行」,省上下文;要看整段传 mode=\"raw\"。",
|
|
629
649
|
promptSnippet: "读取某个 pane 或 agent 的当前输出(默认压缩成信号行 + 尾部)",
|
|
@@ -683,6 +703,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
683
703
|
pi.registerTool({
|
|
684
704
|
name: "herdr_wait",
|
|
685
705
|
label: "Herdr Wait",
|
|
706
|
+
outputSchema: envelopeOutputSchema,
|
|
686
707
|
description:
|
|
687
708
|
"等某个 agent 进入指定状态(默认 idle/done/blocked)。盯梢的关键原语:下发任务后等它落定。注意超时只表示「没观测到」,不是失败 —— 不要据此重发任务,先 herdr_read 看实际情况。",
|
|
688
709
|
promptSnippet: "等待某个 agent 进入 idle/done/blocked",
|
|
@@ -735,6 +756,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
735
756
|
pi.registerTool({
|
|
736
757
|
name: "herdr_prompt",
|
|
737
758
|
label: "Herdr Prompt",
|
|
759
|
+
outputSchema: envelopeOutputSchema,
|
|
738
760
|
description:
|
|
739
761
|
"给另一个 agent 下发一段 prompt(调度)。先试 agent prompt(语义路径);若对方是自报状态的 dsh-tui,herdr 会回 agent_not_ready,此时自动回退成 pane run 直接键入。返回的 via 会说明走了哪条路。「已送出」不等于「已开始干活」—— 要确认请配 herdr_wait 或 herdr_read。",
|
|
740
762
|
promptSnippet: "给另一个 agent 下发任务(自动回退到直接键入)",
|
|
@@ -858,6 +880,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
858
880
|
pi.registerTool({
|
|
859
881
|
name: "herdr_run",
|
|
860
882
|
label: "Herdr Run",
|
|
883
|
+
outputSchema: envelopeOutputSchema,
|
|
861
884
|
description:
|
|
862
885
|
"在指定 pane 里跑一条命令(裸终端,原子下发:文本 + Enter)。用来在别的 pane 起服务、跑脚本。成功 = 退出 0 且 stdout 为空,别期待回显信封;要确认结果用 herdr_read 或 herdr_wait。",
|
|
863
886
|
promptSnippet: "在另一个 pane 里原子下发一条命令",
|
|
@@ -889,6 +912,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
889
912
|
pi.registerTool({
|
|
890
913
|
name: "herdr_create",
|
|
891
914
|
label: "Herdr Create",
|
|
915
|
+
outputSchema: envelopeOutputSchema,
|
|
892
916
|
description:
|
|
893
917
|
"开新位置:workspace / tab / 兄弟 pane。默认不动焦点(不加 --focus),避免把用户正在看的上下文切走。返回的新 pane id 才是后续操作要用的句柄。",
|
|
894
918
|
promptSnippet: "创建 workspace / tab / 兄弟 pane,并拿回新 pane id",
|
|
@@ -963,6 +987,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
963
987
|
pi.registerTool({
|
|
964
988
|
name: "herdr_agent_start",
|
|
965
989
|
label: "Herdr Agent Start",
|
|
990
|
+
outputSchema: envelopeOutputSchema,
|
|
966
991
|
description:
|
|
967
992
|
"在一个已经停在交互提示符的 shell pane 里启动 coding agent。它不创建 pane、不移动布局 —— 要新位置先用 herdr_create。注意 kind 列表里没有 dsh-tui(dsh-tui 靠自报被识别,不由 agent start 起出来)。",
|
|
968
993
|
promptSnippet: "在空闲 shell pane 里启动一个 coding agent",
|
|
@@ -1015,6 +1040,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1015
1040
|
pi.registerTool({
|
|
1016
1041
|
name: "herdr_close",
|
|
1017
1042
|
label: "Herdr Close",
|
|
1043
|
+
outputSchema: envelopeOutputSchema,
|
|
1018
1044
|
description:
|
|
1019
1045
|
"关 pane(会杀掉里面的进程)。默认 dryRun=true 只预览目标、不真关;确认目标无误后再显式传 dryRun=false。工具拒绝关闭自己所在的 pane,也绝不要关不是你创建的 pane。",
|
|
1020
1046
|
promptSnippet: "预览或关闭一个 pane(默认只预览)",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@muzi3/herdr",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Herdr for pi: three independent extensions (agent + blocked state reporting, /pi-hot-restart, and the herdr_overview / herdr_read / herdr_wait / herdr_prompt / herdr_run / herdr_create / herdr_agent_start / herdr_close manager tools aligned with dsh's herdr-tools plugin) plus the herdr-guide skill.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|