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/CHANGELOG.md +28 -1
- package/PROTOCOL.md +35 -31
- package/README.md +15 -22
- package/README.zh-CN.md +16 -22
- package/dist/herdr-link.mcp.js +235 -38
- package/dist/herdr-link.opencode.js +228 -31
- package/docs/mcp-wiring.md +11 -14
- package/examples/agent_config.example.json +17 -2
- package/package.json +3 -2
- package/src/herdr.ts +320 -27
- package/src/mcp.ts +17 -18
- package/src/opencode.ts +9 -5
- package/src/pi.ts +34 -7
- package/src/protocol.ts +19 -22
package/src/pi.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Herdr Link Pi Runtime adapter
|
|
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
|
|
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
|
-
|
|
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, {
|
|
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
|
-
"
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
93
|
-
|
|
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;
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
308
|
-
|
|
309
|
-
1.
|
|
310
|
-
2. Use
|
|
311
|
-
3.
|
|
312
|
-
4.
|
|
313
|
-
5.
|
|
314
|
-
6.
|
|
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.`;
|