@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` 从 8 往上升)重取注入文件替换官方正文并补回那一行;`diff` 应只有三处新增、零删除,漏帖那一行会被 `tests/herdr-agent-state.test.ts` 拦下。
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=8
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` 从当前的 8 往上升时,重取 `herdr integration install pi`
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<void> {
71
+ async function sendRequest(request: unknown): Promise<boolean> {
71
72
  if (await sendRequestAttempt(request, 500)) {
72
- return;
73
+ return true;
73
74
  }
74
- await sendRequestAttempt(request, 1500);
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" && file.startsWith("/") ? file : undefined;
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 { content: [{ type: "text" as const, text: renderEnvelope(value) }], details: value };
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.0",
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",