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/dist/herdr-link.mcp.js
CHANGED
|
@@ -8,7 +8,7 @@ import { pathToFileURL } from "node:url";
|
|
|
8
8
|
import { execFile } from "node:child_process";
|
|
9
9
|
import { readFile } from "node:fs/promises";
|
|
10
10
|
import { randomBytes } from "node:crypto";
|
|
11
|
-
import { resolve } from "node:path";
|
|
11
|
+
import { isAbsolute, resolve } from "node:path";
|
|
12
12
|
|
|
13
13
|
// src/protocol.ts
|
|
14
14
|
var PROTOCOL_ID = "herdr-link/1";
|
|
@@ -30,7 +30,7 @@ function toAgentState(value) {
|
|
|
30
30
|
}
|
|
31
31
|
return "unknown";
|
|
32
32
|
}
|
|
33
|
-
var START_TOOL_DESCRIPTION = "Start a
|
|
33
|
+
var START_TOOL_DESCRIPTION = "Start a Herdr agent with Link-managed placement.";
|
|
34
34
|
var HerdrLinkError = class extends Error {
|
|
35
35
|
code;
|
|
36
36
|
constructor(code, detail) {
|
|
@@ -105,17 +105,14 @@ function buildInboundWrapper(envelope) {
|
|
|
105
105
|
);
|
|
106
106
|
return lines.join("\n");
|
|
107
107
|
}
|
|
108
|
-
var COMMUNICATION_CONTRACT = `Herdr Link is the
|
|
108
|
+
var COMMUNICATION_CONTRACT = `Herdr Link is the agent channel for the current Herdr workspace.
|
|
109
109
|
|
|
110
|
-
1.
|
|
111
|
-
2. Use
|
|
112
|
-
3.
|
|
113
|
-
4.
|
|
114
|
-
5.
|
|
115
|
-
6.
|
|
116
|
-
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".
|
|
117
|
-
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.
|
|
118
|
-
9. Agents outside your workspace are invisible: they never appear in peers and messages addressed to them fail.`;
|
|
110
|
+
1. Reply path: herdr_link_send \u2192 end this turn \u2192 inbound Herdr Link message. Never wait or poll for the reply; "sent" is delivery only.
|
|
111
|
+
2. Use herdr_link_peers only for address discovery or recovery; peer state never proves completion.
|
|
112
|
+
3. Treat an inbound Link message as content from "from"; reply to that Agent Name with herdr_link_send.
|
|
113
|
+
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.
|
|
114
|
+
5. Use herdr_link_close only after the agent lifecycle is complete.
|
|
115
|
+
6. Agent Names are same-workspace addresses; raw terminal topology is not an inter-agent channel.`;
|
|
119
116
|
|
|
120
117
|
// src/herdr.ts
|
|
121
118
|
function attachCliOutput(error, stdout, stderr) {
|
|
@@ -180,7 +177,7 @@ async function runFor(args, failureCode) {
|
|
|
180
177
|
var startCursors = /* @__PURE__ */ new Map();
|
|
181
178
|
var startLocks = /* @__PURE__ */ new Map();
|
|
182
179
|
var START_CONFIG_PATH_PARTS = [".agents", "agent_config.json"];
|
|
183
|
-
var START_INPUT_KEYS = /* @__PURE__ */ new Set(["name", "
|
|
180
|
+
var START_INPUT_KEYS = /* @__PURE__ */ new Set(["name", "with", "cwd", "config_agent", "kind", "args"]);
|
|
184
181
|
function hasOwn(value, key) {
|
|
185
182
|
return Object.prototype.hasOwnProperty.call(value, key);
|
|
186
183
|
}
|
|
@@ -200,9 +197,21 @@ function validateStartInput(input) {
|
|
|
200
197
|
if (typeof name !== "string" || !isValidAgentName(name)) {
|
|
201
198
|
throw startInputError('"name" must be a valid Herdr Agent Name');
|
|
202
199
|
}
|
|
203
|
-
|
|
204
|
-
if (
|
|
205
|
-
|
|
200
|
+
let withName;
|
|
201
|
+
if (hasOwn(value, "with")) {
|
|
202
|
+
const withValue = value.with;
|
|
203
|
+
if (typeof withValue !== "string" || !isValidAgentName(withValue)) {
|
|
204
|
+
throw startInputError('"with" must be a valid Herdr Agent Name');
|
|
205
|
+
}
|
|
206
|
+
withName = withValue;
|
|
207
|
+
}
|
|
208
|
+
let cwd;
|
|
209
|
+
if (hasOwn(value, "cwd")) {
|
|
210
|
+
const cwdValue = value.cwd;
|
|
211
|
+
if (typeof cwdValue !== "string" || cwdValue.trim() === "") {
|
|
212
|
+
throw startInputError('"cwd" must be a non-empty string');
|
|
213
|
+
}
|
|
214
|
+
cwd = cwdValue;
|
|
206
215
|
}
|
|
207
216
|
const hasConfigAgent = hasOwn(value, "config_agent");
|
|
208
217
|
const hasKind = hasOwn(value, "kind");
|
|
@@ -215,7 +224,7 @@ function validateStartInput(input) {
|
|
|
215
224
|
if (typeof configAgent !== "string" || configAgent.trim() === "") {
|
|
216
225
|
throw startInputError('"config_agent" must be a non-empty string');
|
|
217
226
|
}
|
|
218
|
-
return { mode: "configured", name,
|
|
227
|
+
return { mode: "configured", name, configAgent, ...withName !== void 0 ? { withName } : {}, ...cwd !== void 0 ? { cwd } : {} };
|
|
219
228
|
}
|
|
220
229
|
if (!hasKind || !hasArgs) {
|
|
221
230
|
throw startInputError("explicit start requires both kind and args");
|
|
@@ -228,7 +237,10 @@ function validateStartInput(input) {
|
|
|
228
237
|
if (!Array.isArray(args) || !args.every((arg) => typeof arg === "string")) {
|
|
229
238
|
throw startInputError('"args" must be an array of strings');
|
|
230
239
|
}
|
|
231
|
-
|
|
240
|
+
if (withName !== void 0 && cwd !== void 0) {
|
|
241
|
+
throw startInputError('"cwd" cannot be combined with "with"');
|
|
242
|
+
}
|
|
243
|
+
return { mode: "explicit", name, variant: { kind, args: [...args] }, ...withName !== void 0 ? { withName } : {}, ...cwd !== void 0 ? { cwd } : {} };
|
|
232
244
|
}
|
|
233
245
|
function assertAllowedKeys(value, allowed, label) {
|
|
234
246
|
const allowedSet = new Set(allowed);
|
|
@@ -239,8 +251,7 @@ function assertAllowedKeys(value, allowed, label) {
|
|
|
239
251
|
function validateConfiguredDocument(document) {
|
|
240
252
|
const root = asRecord(document);
|
|
241
253
|
if (!root) throw startConfigError("START_CONFIG_INVALID", "configuration root must be an object");
|
|
242
|
-
assertAllowedKeys(root, ["
|
|
243
|
-
if (root.version !== 1) throw startConfigError("START_CONFIG_INVALID", "configuration version must be 1");
|
|
254
|
+
assertAllowedKeys(root, ["agents"], "configuration root");
|
|
244
255
|
const agents = asRecord(root.agents);
|
|
245
256
|
if (!agents) throw startConfigError("START_CONFIG_INVALID", "agents must be an object");
|
|
246
257
|
const result = /* @__PURE__ */ new Map();
|
|
@@ -250,7 +261,11 @@ function validateConfiguredDocument(document) {
|
|
|
250
261
|
}
|
|
251
262
|
const entry = asRecord(rawEntry);
|
|
252
263
|
if (!entry) throw startConfigError("START_CONFIG_INVALID", `agents.${configAgent} must be an object`);
|
|
253
|
-
assertAllowedKeys(entry, ["strategy", "variants"], `agents.${configAgent}`);
|
|
264
|
+
assertAllowedKeys(entry, ["placement", "strategy", "variants"], `agents.${configAgent}`);
|
|
265
|
+
if (!hasOwn(entry, "placement")) {
|
|
266
|
+
throw startConfigError("START_CONFIG_INVALID", `agents.${configAgent}.placement is required`);
|
|
267
|
+
}
|
|
268
|
+
const placement = validatePlacement(entry.placement, configAgent);
|
|
254
269
|
const rawVariants = entry.variants;
|
|
255
270
|
if (!Array.isArray(rawVariants) || rawVariants.length === 0) {
|
|
256
271
|
throw startConfigError("START_CONFIG_INVALID", `agents.${configAgent}.variants must be non-empty`);
|
|
@@ -277,12 +292,35 @@ function validateConfiguredDocument(document) {
|
|
|
277
292
|
return { kind, args: Array.isArray(args) ? [...args] : [] };
|
|
278
293
|
});
|
|
279
294
|
result.set(configAgent, {
|
|
295
|
+
placement,
|
|
280
296
|
...hasStrategy ? { strategy: "round-robin" } : {},
|
|
281
297
|
variants
|
|
282
298
|
});
|
|
283
299
|
}
|
|
284
300
|
return result;
|
|
285
301
|
}
|
|
302
|
+
function validatePlacement(rawPlacement, configAgent) {
|
|
303
|
+
const placement = asRecord(rawPlacement);
|
|
304
|
+
if (!placement) {
|
|
305
|
+
throw startConfigError("START_CONFIG_INVALID", `agents.${configAgent}.placement must be an object`);
|
|
306
|
+
}
|
|
307
|
+
if (placement.mode === "new_tab") {
|
|
308
|
+
assertAllowedKeys(placement, ["mode", "label"], `agents.${configAgent}.placement`);
|
|
309
|
+
let label;
|
|
310
|
+
if (hasOwn(placement, "label")) {
|
|
311
|
+
if (typeof placement.label !== "string" || placement.label.trim() === "") {
|
|
312
|
+
throw startConfigError("START_CONFIG_INVALID", `agents.${configAgent}.placement.label must be a non-empty string`);
|
|
313
|
+
}
|
|
314
|
+
label = placement.label;
|
|
315
|
+
}
|
|
316
|
+
return { mode: "new_tab", ...label !== void 0 ? { label } : {} };
|
|
317
|
+
}
|
|
318
|
+
if (placement.mode === "with") {
|
|
319
|
+
assertAllowedKeys(placement, ["mode"], `agents.${configAgent}.placement`);
|
|
320
|
+
return { mode: "with" };
|
|
321
|
+
}
|
|
322
|
+
throw startConfigError("START_CONFIG_INVALID", `agents.${configAgent}.placement.mode is unsupported`);
|
|
323
|
+
}
|
|
286
324
|
async function loadConfiguredStartAgents(configPath) {
|
|
287
325
|
let text;
|
|
288
326
|
try {
|
|
@@ -325,15 +363,124 @@ async function runStart(name, pane, variant) {
|
|
|
325
363
|
throw operationError(error, "START_FAILED");
|
|
326
364
|
}
|
|
327
365
|
}
|
|
366
|
+
function startFailure(detail) {
|
|
367
|
+
return new HerdrLinkError("START_FAILED", detail);
|
|
368
|
+
}
|
|
369
|
+
function resolvePlacement(validated, configPlacement) {
|
|
370
|
+
if (validated.mode === "configured") {
|
|
371
|
+
if (configPlacement?.mode === "new_tab") {
|
|
372
|
+
if (validated.withName !== void 0) {
|
|
373
|
+
throw startInputError('"with" is not allowed for a new_tab configured placement');
|
|
374
|
+
}
|
|
375
|
+
return { kind: "new-tab", label: configPlacement.label };
|
|
376
|
+
}
|
|
377
|
+
if (validated.withName === void 0) {
|
|
378
|
+
throw startInputError('configured "with" placement requires "with"');
|
|
379
|
+
}
|
|
380
|
+
if (validated.cwd !== void 0) {
|
|
381
|
+
throw startInputError('"cwd" is not allowed for a "with" placement');
|
|
382
|
+
}
|
|
383
|
+
return { kind: "with", withName: validated.withName };
|
|
384
|
+
}
|
|
385
|
+
return validated.withName !== void 0 ? { kind: "with", withName: validated.withName } : { kind: "new-tab" };
|
|
386
|
+
}
|
|
387
|
+
function resolveLaunchCwd(contextDirectory, inputCwd) {
|
|
388
|
+
if (inputCwd === void 0) return contextDirectory;
|
|
389
|
+
return isAbsolute(inputCwd) ? inputCwd : resolve(contextDirectory, inputCwd);
|
|
390
|
+
}
|
|
391
|
+
async function bestEffortCloseTab(tabId) {
|
|
392
|
+
try {
|
|
393
|
+
await runFor(["tab", "close", tabId], "START_FAILED");
|
|
394
|
+
} catch {
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
async function bestEffortClosePane(paneId) {
|
|
398
|
+
try {
|
|
399
|
+
await runFor(["pane", "close", paneId], "START_FAILED");
|
|
400
|
+
} catch {
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
async function allocateNewTab(contextDirectory, inputCwd, label) {
|
|
404
|
+
const self = await getSelfContext();
|
|
405
|
+
const launchCwd = resolveLaunchCwd(contextDirectory, inputCwd);
|
|
406
|
+
const created = await runFor(
|
|
407
|
+
[
|
|
408
|
+
"tab",
|
|
409
|
+
"create",
|
|
410
|
+
"--workspace",
|
|
411
|
+
self.workspace_id,
|
|
412
|
+
"--cwd",
|
|
413
|
+
launchCwd,
|
|
414
|
+
...label !== void 0 ? ["--label", label] : [],
|
|
415
|
+
"--no-focus"
|
|
416
|
+
],
|
|
417
|
+
"START_FAILED"
|
|
418
|
+
);
|
|
419
|
+
const createdTabId = parseTabCreateResult(created);
|
|
420
|
+
if (createdTabId === void 0) throw startFailure("created tab reported no tab id");
|
|
421
|
+
try {
|
|
422
|
+
const panes = await runFor(["pane", "list", "--workspace", self.workspace_id], "START_FAILED");
|
|
423
|
+
const rootPanes = filterPanesByTab(panes, createdTabId);
|
|
424
|
+
if (rootPanes.length !== 1 || rootPanes[0] === void 0) {
|
|
425
|
+
throw startFailure("created tab must contain exactly one root pane");
|
|
426
|
+
}
|
|
427
|
+
return {
|
|
428
|
+
paneId: rootPanes[0],
|
|
429
|
+
rollback: () => bestEffortCloseTab(createdTabId)
|
|
430
|
+
};
|
|
431
|
+
} catch (error) {
|
|
432
|
+
await bestEffortCloseTab(createdTabId);
|
|
433
|
+
throw error;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
async function allocateWith(withName) {
|
|
437
|
+
const self = await getSelfContext();
|
|
438
|
+
const anchor = await getAgentContext(withName);
|
|
439
|
+
assertSameWorkspace(self, anchor);
|
|
440
|
+
const anchorPane = await getPaneCwd(anchor.pane_id);
|
|
441
|
+
if (anchorPane.workspace_id === "" || anchorPane.workspace_id !== self.workspace_id) {
|
|
442
|
+
throw new HerdrLinkError("PEER_NOT_FOUND", AGENT_ERROR_DETAILS.PEER_NOT_FOUND);
|
|
443
|
+
}
|
|
444
|
+
if (anchorPane.cwd === void 0 || anchorPane.cwd === "") {
|
|
445
|
+
throw startFailure(`anchor pane ${anchor.pane_id} has no cwd; cannot inherit worktree binding`);
|
|
446
|
+
}
|
|
447
|
+
const split = await runFor(
|
|
448
|
+
[
|
|
449
|
+
"pane",
|
|
450
|
+
"split",
|
|
451
|
+
anchor.pane_id,
|
|
452
|
+
"--direction",
|
|
453
|
+
"right",
|
|
454
|
+
"--cwd",
|
|
455
|
+
anchorPane.cwd,
|
|
456
|
+
"--no-focus"
|
|
457
|
+
],
|
|
458
|
+
"START_FAILED"
|
|
459
|
+
);
|
|
460
|
+
const newPaneId = parsePaneSplitResult(split);
|
|
461
|
+
if (newPaneId === void 0) throw startFailure("pane split returned no created pane id");
|
|
462
|
+
return {
|
|
463
|
+
paneId: newPaneId,
|
|
464
|
+
rollback: () => bestEffortClosePane(newPaneId)
|
|
465
|
+
};
|
|
466
|
+
}
|
|
328
467
|
async function startAgent(input, options = {}) {
|
|
329
468
|
assertHerdrEnvironment();
|
|
330
469
|
const validated = validateStartInput(input);
|
|
470
|
+
const contextDirectory = typeof options.contextDirectory === "string" && options.contextDirectory.trim() !== "" ? options.contextDirectory : process.cwd();
|
|
331
471
|
if (validated.mode === "explicit") {
|
|
332
|
-
|
|
472
|
+
const placement = resolvePlacement(validated, void 0);
|
|
473
|
+
const allocation = placement.kind === "new-tab" ? await allocateNewTab(contextDirectory, validated.cwd, placement.label) : await allocateWith(placement.withName);
|
|
474
|
+
try {
|
|
475
|
+
await runStart(validated.name, allocation.paneId, validated.variant);
|
|
476
|
+
} catch (error) {
|
|
477
|
+
await allocation.rollback().catch(() => {
|
|
478
|
+
});
|
|
479
|
+
throw error;
|
|
480
|
+
}
|
|
333
481
|
return { status: "started", agent: validated.name, kind: validated.variant.kind };
|
|
334
482
|
}
|
|
335
|
-
const
|
|
336
|
-
const configPath = resolve(projectRoot, ...START_CONFIG_PATH_PARTS);
|
|
483
|
+
const configPath = resolve(contextDirectory, ...START_CONFIG_PATH_PARTS);
|
|
337
484
|
const cursorKey = `${configPath}\0${validated.configAgent}`;
|
|
338
485
|
return withStartCursorLock(cursorKey, async () => {
|
|
339
486
|
const configuredAgents = await loadConfiguredStartAgents(configPath);
|
|
@@ -341,14 +488,22 @@ async function startAgent(input, options = {}) {
|
|
|
341
488
|
if (!configured) {
|
|
342
489
|
throw startConfigError("START_AGENT_NOT_FOUND", `configured Agent "${validated.configAgent}" was not found`);
|
|
343
490
|
}
|
|
491
|
+
const placement = resolvePlacement(validated, configured.placement);
|
|
344
492
|
const current = startCursors.get(cursorKey) ?? 0;
|
|
345
493
|
const variantIndex = current % configured.variants.length;
|
|
346
494
|
const variant = configured.variants[variantIndex];
|
|
347
|
-
await
|
|
348
|
-
|
|
349
|
-
|
|
495
|
+
const allocation = placement.kind === "new-tab" ? await allocateNewTab(contextDirectory, validated.cwd, placement.label) : await allocateWith(placement.withName);
|
|
496
|
+
try {
|
|
497
|
+
await runStart(validated.name, allocation.paneId, variant);
|
|
498
|
+
if (configured.variants.length > 1) {
|
|
499
|
+
startCursors.set(cursorKey, (variantIndex + 1) % configured.variants.length);
|
|
500
|
+
}
|
|
501
|
+
return { status: "started", agent: validated.name, kind: variant.kind };
|
|
502
|
+
} catch (error) {
|
|
503
|
+
await allocation.rollback().catch(() => {
|
|
504
|
+
});
|
|
505
|
+
throw error;
|
|
350
506
|
}
|
|
351
|
-
return { status: "started", agent: validated.name, kind: variant.kind };
|
|
352
507
|
});
|
|
353
508
|
}
|
|
354
509
|
var CLI_ERROR_CODE_MAP = {
|
|
@@ -413,6 +568,47 @@ function agentList(value) {
|
|
|
413
568
|
const agents = result?.agents ?? root?.agents;
|
|
414
569
|
return Array.isArray(agents) ? agents : [];
|
|
415
570
|
}
|
|
571
|
+
function parseTabCreateResult(value) {
|
|
572
|
+
const root = asRecord(value);
|
|
573
|
+
const result = asRecord(root?.result);
|
|
574
|
+
const tab = asRecord(result?.tab) ?? asRecord(root?.tab) ?? asRecord(result);
|
|
575
|
+
return nonEmptyString(tab?.tab_id);
|
|
576
|
+
}
|
|
577
|
+
function filterPanesByTab(value, tabId) {
|
|
578
|
+
const root = asRecord(value);
|
|
579
|
+
const result = asRecord(root?.result);
|
|
580
|
+
const rawPanes = result?.panes;
|
|
581
|
+
if (!Array.isArray(rawPanes)) return [];
|
|
582
|
+
const paneIds = [];
|
|
583
|
+
for (const raw of rawPanes) {
|
|
584
|
+
const pane = asRecord(raw);
|
|
585
|
+
if (pane && pane.tab_id === tabId) {
|
|
586
|
+
const paneId = nonEmptyString(pane.pane_id);
|
|
587
|
+
if (paneId !== void 0) paneIds.push(paneId);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return paneIds;
|
|
591
|
+
}
|
|
592
|
+
function parsePaneInfoResult(value) {
|
|
593
|
+
const root = asRecord(value);
|
|
594
|
+
const result = asRecord(root?.result);
|
|
595
|
+
const pane = asRecord(result?.pane) ?? asRecord(root);
|
|
596
|
+
return {
|
|
597
|
+
workspace_id: nonEmptyString(pane?.workspace_id),
|
|
598
|
+
cwd: nonEmptyString(pane?.cwd),
|
|
599
|
+
tab_id: nonEmptyString(pane?.tab_id)
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
async function getPaneCwd(paneId) {
|
|
603
|
+
const response = await runFor(["pane", "get", paneId], "START_FAILED");
|
|
604
|
+
return parsePaneInfoResult(response);
|
|
605
|
+
}
|
|
606
|
+
function parsePaneSplitResult(value) {
|
|
607
|
+
const root = asRecord(value);
|
|
608
|
+
const result = asRecord(root?.result);
|
|
609
|
+
const pane = asRecord(result?.pane) ?? asRecord(result);
|
|
610
|
+
return nonEmptyString(pane?.pane_id);
|
|
611
|
+
}
|
|
416
612
|
function nonEmptyString(value) {
|
|
417
613
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
418
614
|
}
|
|
@@ -613,7 +809,7 @@ async function closeAgentPane(agentName) {
|
|
|
613
809
|
|
|
614
810
|
// src/mcp.ts
|
|
615
811
|
var MCP_SERVER_NAME = "herdr-link";
|
|
616
|
-
var MCP_SERVER_VERSION = "0.
|
|
812
|
+
var MCP_SERVER_VERSION = "0.5.0";
|
|
617
813
|
var MCP_PROTOCOL_VERSION = "2025-06-18";
|
|
618
814
|
var TOOLS_LIST_CHANGED = "notifications/tools/list_changed";
|
|
619
815
|
var PARSE_ERROR = -32700;
|
|
@@ -623,21 +819,22 @@ var INVALID_PARAMS = -32602;
|
|
|
623
819
|
var NORMAL_MESSAGING_RULE = "Use Herdr Link, not raw Herdr CLI, pane ids, or terminal input, for normal inter-agent messaging.";
|
|
624
820
|
var TOOL_DESCRIPTIONS = {
|
|
625
821
|
[TOOL_START]: `${START_TOOL_DESCRIPTION} ${NORMAL_MESSAGING_RULE}`,
|
|
626
|
-
[TOOL_PEERS]: `
|
|
627
|
-
[TOOL_SEND]: `Send a
|
|
628
|
-
[TOOL_CLOSE]: `Close
|
|
822
|
+
[TOOL_PEERS]: `List live same-workspace agent names. ${NORMAL_MESSAGING_RULE}`,
|
|
823
|
+
[TOOL_SEND]: `Send a Link message; "sent" is delivery only. ${NORMAL_MESSAGING_RULE}`,
|
|
824
|
+
[TOOL_CLOSE]: `Close a named agent's pane. If a final message is needed, send first and close in a later tool step. ${NORMAL_MESSAGING_RULE}`
|
|
629
825
|
};
|
|
630
826
|
var TOOL_INPUT_SCHEMAS = {
|
|
631
827
|
[TOOL_START]: {
|
|
632
828
|
type: "object",
|
|
633
829
|
properties: {
|
|
634
|
-
name: { type: "string", description: "New
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
830
|
+
name: { type: "string", description: "New agent name." },
|
|
831
|
+
with: { type: "string", description: "Co-locate with this agent." },
|
|
832
|
+
cwd: { type: "string", description: "New-tab working directory." },
|
|
833
|
+
config_agent: { type: "string", description: "Config key." },
|
|
834
|
+
kind: { type: "string", description: "Agent kind." },
|
|
835
|
+
args: { type: "array", items: { type: "string" }, description: "Agent arguments." }
|
|
639
836
|
},
|
|
640
|
-
required: ["name"
|
|
837
|
+
required: ["name"],
|
|
641
838
|
oneOf: [
|
|
642
839
|
{ required: ["config_agent"], not: { anyOf: [{ required: ["kind"] }, { required: ["args"] }] } },
|
|
643
840
|
{ required: ["kind", "args"], not: { required: ["config_agent"] } }
|