herdr-link 0.4.0 → 0.5.0

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/src/pi.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Herdr Link Pi Runtime adapter v2 — Tier 0/Tier 1 presentation.
2
+ * Herdr Link Pi Runtime adapter — Tier 0/Tier 1 presentation.
3
3
  *
4
4
  * Tier 0 (dormant): with the three Herdr environment variables present, the
5
5
  * adapter registers everything but keeps the model-facing surface down to the
@@ -23,8 +23,10 @@
23
23
  * - Lazily loaded tools should omit active-only prompt metadata
24
24
  * (`promptSnippet`/`promptGuidelines`) and rely on their `description`;
25
25
  * activating such metadata would rebuild the system prompt mid-session.
26
+ * - The active-session `tool_call` guard hard-blocks raw blocking Agent wait
27
+ * command forms before execution; dormant sessions leave Herdr CLI as-is.
26
28
  */
27
- import type { ExtensionAPI, ToolExecutionMode } from "@earendil-works/pi-coding-agent";
29
+ import { isToolCallEventType, type ExtensionAPI, type ToolExecutionMode } from "@earendil-works/pi-coding-agent";
28
30
  import { Type } from "typebox";
29
31
 
30
32
  import { closeAgentPane, ensureSelfName, listPeers, sendMessage, startAgent } from "./herdr.ts";
@@ -43,7 +45,8 @@ const TIER1_TOOL_SET = new Set<string>(TIER1_TOOL_NAMES);
43
45
  const GATEWAY_PARAMETERS = Type.Object({});
44
46
  const START_PARAMETERS = Type.Object({
45
47
  name: Type.String(),
46
- pane: Type.String(),
48
+ with: Type.Optional(Type.String()),
49
+ cwd: Type.Optional(Type.String()),
47
50
  config_agent: Type.Optional(Type.String()),
48
51
  kind: Type.Optional(Type.String()),
49
52
  args: Type.Optional(Type.Array(Type.String())),
@@ -101,7 +104,7 @@ export default function (pi: ExtensionAPI): void {
101
104
  parameters: START_PARAMETERS,
102
105
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
103
106
  try {
104
- return toolResult(await startAgent(params as StartAgentInput, { cwd: ctx.cwd }));
107
+ return toolResult(await startAgent(params as StartAgentInput, { contextDirectory: ctx.cwd }));
105
108
  } catch (error) {
106
109
  rethrowToolError(error, "START_FAILED");
107
110
  }
@@ -112,7 +115,7 @@ export default function (pi: ExtensionAPI): void {
112
115
  name: "herdr_link_peers",
113
116
  label: "Herdr Link Peers",
114
117
  description:
115
- "Discover named agents available through the cross-agent communication channel. Returns { self, peers }; addresses are Agent Names.",
118
+ "List live same-workspace agent names.",
116
119
  parameters: PEERS_PARAMETERS,
117
120
  async execute(_toolCallId, _params, _signal, _onUpdate, _ctx) {
118
121
  try {
@@ -127,7 +130,7 @@ export default function (pi: ExtensionAPI): void {
127
130
  name: "herdr_link_send",
128
131
  label: "Herdr Link Send",
129
132
  description:
130
- 'Send an inter-agent message (protocol herdr-link/1) to another agent through the cross-agent communication channel. status "sent" means Herdr accepted delivery, not that the peer finished its task.',
133
+ 'Send a Link message; "sent" is delivery only.',
131
134
  parameters: SEND_PARAMETERS,
132
135
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
133
136
  try {
@@ -147,7 +150,7 @@ export default function (pi: ExtensionAPI): void {
147
150
  // 使含 close 的批次整体串行。peers/send 保持默认并行。
148
151
  executionMode: "sequential" as ToolExecutionMode,
149
152
  description:
150
- 'Close the Herdr pane currently hosting a named agent. Sequential: if a final message is needed, send it first and call close in a later tool step after herdr_link_send returns status "sent".',
153
+ 'Close a named agent\'s pane. Sequential: if a final message is needed, send it first and call close in a later tool step after herdr_link_send returns status "sent".',
151
154
  parameters: CLOSE_PARAMETERS,
152
155
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
153
156
  try {
@@ -201,4 +204,28 @@ export default function (pi: ExtensionAPI): void {
201
204
  if (!activated) return undefined;
202
205
  return { systemPrompt: `${event.systemPrompt}\n\n${COMMUNICATION_CONTRACT}` };
203
206
  });
207
+
208
+ // --- Active-session blocking-wait guard (PROTOCOL.md §4.2 safety note).
209
+ // Only active Link sessions hard-block raw blocking Agent wait forms at
210
+ // the Bash tool boundary BEFORE execution. Dormant sessions (Link inactive)
211
+ // leave ordinary Herdr CLI usage untouched. Link's own sendMessage() runs
212
+ // through process execution, never through this model tool hook.
213
+ pi.on("tool_call", (event) => {
214
+ if (!activated) return undefined;
215
+ if (!isToolCallEventType("bash", event)) return undefined;
216
+ const command = event.input.command;
217
+ if (/\bherdr\s+agent\s+wait\b/.test(command)) {
218
+ return {
219
+ block: true,
220
+ reason: "Wait disabled by Herdr Link. End this turn; resume on the inbound Link message.",
221
+ terminate: true,
222
+ };
223
+ }
224
+ // Match the prompt command as a plain substring and `--wait` as a
225
+ // standalone token. Avoid `\b` escapes: they are unreliable here.
226
+ if (/herdr\s+agent\s+prompt/.test(command) && /(^|\s)--wait(\s|$)/.test(command)) {
227
+ return { block: true, reason: "Use herdr_link_send for agent messages." };
228
+ }
229
+ return undefined;
230
+ });
204
231
  }
package/src/protocol.ts CHANGED
@@ -13,7 +13,7 @@ export const AGENT_NAME_RE = /^[a-z][a-z0-9_-]{0,31}$/;
13
13
  export const MESSAGE_ID_RE = /^hl_[a-z0-9]+_[a-z0-9]+$/;
14
14
 
15
15
  /* ------------------------------------------------------------------ *
16
- * Naming tiers (blueprint v2)
16
+ * Naming tiers
17
17
  *
18
18
  * Tier 0 is the Herdr Link gateway itself — the host registration
19
19
  * namespace every runtime presents its tools against (underscore form;
@@ -35,7 +35,7 @@ export const HERDR_LINK_TOOLS = [TOOL_START, TOOL_PEERS, TOOL_SEND, TOOL_CLOSE]
35
35
  export const HERDR_LINK_COMMUNICATION_TOOLS = [TOOL_PEERS, TOOL_SEND, TOOL_CLOSE] as const;
36
36
 
37
37
  /* ------------------------------------------------------------------ *
38
- * Agent state (blueprint v2)
38
+ * Agent state
39
39
  * ------------------------------------------------------------------ */
40
40
 
41
41
  /** Live activity states; any unrecognized Herdr status maps to "unknown". */
@@ -61,7 +61,7 @@ export interface PeerInfo {
61
61
  }
62
62
 
63
63
  /**
64
- * Instant peer directory (blueprint v2): same-workspace live agents only,
64
+ * Instant peer directory: same-workspace live agents only,
65
65
  * self excluded. Generated fresh on every call; never persisted or cached.
66
66
  */
67
67
  export interface PeerDirectory {
@@ -89,8 +89,10 @@ export interface AgentContext {
89
89
  export interface StartCommonInput {
90
90
  /** New Herdr Agent Name. */
91
91
  name: string;
92
- /** Existing pane in which Herdr starts the Agent. */
93
- pane: string;
92
+ /** Co-locate with a live Agent Name: same-tab placement inheriting the anchor pane cwd. */
93
+ with?: string;
94
+ /** Launch working directory for a new-tab placement. */
95
+ cwd?: string;
94
96
  }
95
97
 
96
98
  /** Project-configured start: the configuration owns the complete launch parameters. */
@@ -116,10 +118,8 @@ export interface StartAgentReceipt {
116
118
  kind: string;
117
119
  }
118
120
 
119
- /** Shared model-facing description; detailed config semantics live in the start section. */
120
- export const START_TOOL_DESCRIPTION =
121
- "Start a new Herdr Agent in an existing pane. Provide name and pane, then choose exactly one complete parameter source: config_agent for .agents/agent_config.json, or kind plus args for explicit Herdr start parameters. These modes are mutually exclusive; partial overrides are not supported. This operation does not create panes or retry/fallback after failure.";
122
-
121
+ /** Shared model-facing description; placement semantics live in the start section. */
122
+ export const START_TOOL_DESCRIPTION = "Start a Herdr agent with Link-managed placement.";
123
123
  /** The cross-agent message envelope (PROTOCOL.md §2). Minimal fields only. */
124
124
  export interface HerdrLinkEnvelope {
125
125
  protocol: typeof PROTOCOL_ID;
@@ -129,7 +129,7 @@ export interface HerdrLinkEnvelope {
129
129
  message: string;
130
130
  }
131
131
 
132
- /** V1 error codes (PROTOCOL.md §7). All are local tool failures, never an envelope. */
132
+ /** Link error codes (PROTOCOL.md §7). All are local tool failures, never an envelope. */
133
133
  export const LINK_ERROR_CODES = [
134
134
  "NOT_IN_HERDR",
135
135
  "SELF_UNNAMED",
@@ -250,7 +250,7 @@ export function isHerdrLinkEnvelope(value: unknown): value is HerdrLinkEnvelope
250
250
  }
251
251
 
252
252
  /* ------------------------------------------------------------------ *
253
- * Inbound delivery wrapper (blueprint v2)
253
+ * Inbound delivery wrapper
254
254
  *
255
255
  * `herdr agent prompt` carries a self-describing wrapper around the
256
256
  * envelope so a dormant receiver (adapter loaded, model not mid-exchange)
@@ -304,14 +304,11 @@ export function extractInboundEnvelope(text: string): HerdrLinkEnvelope | undefi
304
304
  * Active Agent Communication Contract injected verbatim into the model.
305
305
  * Compact form: same-workspace addressing, send/reply/completion/close semantics.
306
306
  */
307
- export const COMMUNICATION_CONTRACT = `Herdr Link is the standard interoperability channel between agents running in the same Herdr workspace.
308
-
309
- 1. Use herdr_link_peers only for agent-address discovery or explicit recovery. Its activity state is advisory and must not be used to wait for or infer task completion. When further progress depends on a peer reply, end the current turn and continue when that reply arrives as a new inbound herdr-link/1 message.
310
- 2. Use herdr_link_send to send messages to another agent.
311
- 3. A message with protocol "herdr-link/1" is an inter-agent message.
312
- 4. Treat its "message" field as content sent by the agent named in "from".
313
- 5. When replying, use herdr_link_send to the agent named in "from".
314
- 6. When a received inter-agent message requests work, report the final outcome to the agent named in "from" using herdr_link_send. If specific reply content was requested, send that result; otherwise, after successful completion, send exactly "done". If the work cannot be completed, send a concise failure or blocker. If the sender explicitly requested no reply, do not send a completion message.
315
- 7. Use herdr_link_close only when you have already decided that a named agent's pane should be closed. If a final message is needed, call close in a later tool step after herdr_link_send returns "sent".
316
- 8. Never use a raw pane id, UI focus, terminal input, or the Herdr CLI as an inter-agent channel; agent names are the only addresses.
317
- 9. Agents outside your workspace are invisible: they never appear in peers and messages addressed to them fail.`;
307
+ export const COMMUNICATION_CONTRACT = `Herdr Link is the agent channel for the current Herdr workspace.
308
+
309
+ 1. Reply path: herdr_link_send → end this turn → inbound Herdr Link message. Never wait or poll for the reply; "sent" is delivery only.
310
+ 2. Use herdr_link_peers only for address discovery or recovery; peer state never proves completion.
311
+ 3. Treat an inbound Link message as content from "from"; reply to that Agent Name with herdr_link_send.
312
+ 4. Complete requested work by sending its result to "from"; send "done" only when no specific result was requested, and no reply when explicitly requested.
313
+ 5. Use herdr_link_close only after the agent lifecycle is complete.
314
+ 6. Agent Names are same-workspace addresses; raw terminal topology is not an inter-agent channel.`;