@pinet/slack-bridge 0.2.2 → 0.2.6
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 +316 -451
- package/dist/activity-log.d.ts +10 -3
- package/dist/agent-event-runtime.d.ts +0 -2
- package/dist/agent-event-runtime.js +0 -1
- package/dist/agent-prompt-guidance.d.ts +1 -6
- package/dist/agent-prompt-guidance.js +1 -6
- package/dist/broker/adapters/slack.d.ts +4 -1
- package/dist/broker/adapters/slack.js +16 -9
- package/dist/broker/client.d.ts +21 -4
- package/dist/broker/client.js +65 -25
- package/dist/broker/hibernation-activation-authority.d.ts +22 -0
- package/dist/broker/hibernation-activation-authority.js +51 -0
- package/dist/broker/hibernation-activation.d.ts +75 -0
- package/dist/broker/hibernation-activation.js +95 -0
- package/dist/broker/hibernation-command-router.d.ts +54 -0
- package/dist/broker/hibernation-command-router.js +124 -0
- package/dist/broker/hibernation-runtime-adapters.d.ts +79 -0
- package/dist/broker/hibernation-runtime-adapters.js +473 -0
- package/dist/broker/hibernation-runtime-helpers.d.ts +146 -0
- package/dist/broker/hibernation-runtime-helpers.js +243 -0
- package/dist/broker/index.d.ts +16 -1
- package/dist/broker/index.js +68 -10
- package/dist/broker/lock-conflict.d.ts +109 -0
- package/dist/broker/lock-conflict.js +409 -0
- package/dist/broker/socket-server.d.ts +26 -0
- package/dist/broker/socket-server.js +287 -4
- package/dist/broker-delivery.d.ts +1 -0
- package/dist/broker-delivery.js +10 -1
- package/dist/broker-runtime.d.ts +14 -2
- package/dist/broker-runtime.js +114 -3
- package/dist/canvases.d.ts +12 -2
- package/dist/canvases.js +1 -3
- package/dist/compaction-gate.d.ts +7 -0
- package/dist/compaction-gate.js +35 -0
- package/dist/deploy-manifest.d.ts +8 -2
- package/dist/deploy-manifest.js +9 -7
- package/dist/follower-runtime.d.ts +5 -2
- package/dist/follower-runtime.js +39 -5
- package/dist/git-metadata.d.ts +5 -9
- package/dist/git-metadata.js +23 -42
- package/dist/guardrails.js +5 -0
- package/dist/helpers.d.ts +77 -27
- package/dist/helpers.js +150 -39
- package/dist/hibernation-config.d.ts +13 -0
- package/dist/hibernation-config.js +14 -0
- package/dist/home-tab.d.ts +13 -2
- package/dist/imessage-tools.d.ts +4 -1
- package/dist/inbox-drain-runtime.d.ts +1 -0
- package/dist/inbox-drain-runtime.js +5 -1
- package/dist/index.js +341 -108
- package/dist/pinet-commands.d.ts +14 -1
- package/dist/pinet-commands.js +93 -22
- package/dist/pinet-mesh-ops.d.ts +6 -4
- package/dist/pinet-mesh-ops.js +9 -1
- package/dist/pinet-runtime-composition.d.ts +2 -1
- package/dist/pinet-runtime-composition.js +14 -3
- package/dist/pinet-session-formatting.d.ts +26 -2
- package/dist/pinet-session-formatting.js +2 -0
- package/dist/pinet-tools.d.ts +14 -0
- package/dist/pinet-tools.js +355 -50
- package/dist/prompts/broker/default.md +3 -3
- package/dist/prompts/broker/tmux.md +3 -3
- package/dist/runtime-agent-context.d.ts +3 -3
- package/dist/runtime-agent-context.js +12 -6
- package/dist/runtime-mode.js +4 -0
- package/dist/session-ui-runtime.js +3 -1
- package/dist/single-player-runtime.d.ts +1 -1
- package/dist/single-player-runtime.js +21 -5
- package/dist/slack-access.d.ts +10 -2
- package/dist/slack-access.js +64 -22
- package/dist/slack-block-kit.d.ts +44 -2
- package/dist/slack-block-kit.js +5 -5
- package/dist/slack-file-access.d.ts +12 -1
- package/dist/slack-file-access.js +4 -4
- package/dist/slack-message-context.d.ts +9 -2
- package/dist/slack-modals.d.ts +4 -1
- package/dist/slack-modals.js +5 -5
- package/dist/slack-pinet-runtime-adapter.d.ts +1 -0
- package/dist/slack-pinet-runtime-adapter.js +10 -0
- package/dist/slack-presence.d.ts +5 -4
- package/dist/slack-request-runtime.d.ts +3 -2
- package/dist/slack-scope-diagnostics.d.ts +27 -2
- package/dist/slack-socket-dedup.d.ts +8 -5
- package/dist/slack-tools.d.ts +10 -0
- package/dist/slack-tools.js +51 -64
- package/dist/slack-upload.d.ts +17 -1
- package/dist/subtree-broker-runtime.d.ts +83 -2
- package/dist/subtree-broker-runtime.js +774 -109
- package/dist/tool-output-limits.d.ts +11 -0
- package/dist/tool-output-limits.js +20 -0
- package/dist/tool-registration-runtime.d.ts +3 -0
- package/dist/tool-registration-runtime.js +27 -0
- package/package.json +8 -8
package/dist/pinet-tools.js
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
|
+
import { mkdtempSync, writeFileSync } from "node:fs";
|
|
1
2
|
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
2
4
|
import { buildCompactPinetReadDetails, formatPinetReadResultCompact, formatPinetReadResultFull, } from "@pinet/pinet-core/pinet-read-formatting";
|
|
3
5
|
import { normalizePinetOutputOptions, } from "@pinet/pinet-core/output-options";
|
|
4
6
|
import { parseScheduledWakeupDelay, resolveScheduledWakeupFireAt, } from "@pinet/pinet-core/scheduled-wakeups";
|
|
5
7
|
import { Text } from "@earendil-works/pi-tui";
|
|
6
8
|
import { Type } from "@sinclair/typebox";
|
|
9
|
+
import { formatAgentLifecycleStatus, formatAgentLifecycleTag, formatHibernationCommandResult, sanitizeOperatorReason, sanitizeOperatorTarget, } from "@pinet/broker-core";
|
|
7
10
|
import { buildAgentDisplayInfo, filterAgentsForMeshVisibility, formatAgentList, rankAgentsForRouting, } from "./helpers.js";
|
|
8
11
|
import { isBroadcastChannelTarget } from "./broker/agent-messaging.js";
|
|
9
12
|
import { DEFAULT_HEARTBEAT_TIMEOUT_MS } from "./broker/socket-server.js";
|
|
10
13
|
import { HEARTBEAT_INTERVAL_MS } from "./broker/client.js";
|
|
14
|
+
import { SubtreeSpawnLaunchError, SubtreeSpawnRegistrationTimeoutError, } from "./subtree-broker-runtime.js";
|
|
15
|
+
import { DEFAULT_MAX_OUTPUT_BYTES, DEFAULT_MAX_OUTPUT_LINES, formatOutputSize, measureOutputLimits, } from "./tool-output-limits.js";
|
|
11
16
|
import { buildPinetSessionCompactDetails, buildPinetSessionFullDetails, getPinetSessionFilename, getPinetSessionPath, summarizePinetStableId, } from "./pinet-session-formatting.js";
|
|
12
17
|
const PINET_DISPATCHER_EXAMPLES = {
|
|
13
|
-
send: [
|
|
18
|
+
send: [
|
|
19
|
+
{ action: "send", args: { to: "@worker", message: "Please review PR #123" } },
|
|
20
|
+
{ action: "send", args: { to: "@worker", message: "/steer stop polling" } },
|
|
21
|
+
],
|
|
14
22
|
read: [
|
|
15
23
|
// Routine inbox drain: defaults are `unread_only: true` + `mark_read: true`,
|
|
16
24
|
// so this returns and consumes only this agent's unread rows. The compact
|
|
@@ -60,6 +68,8 @@ const PINET_DISPATCHER_EXAMPLES = {
|
|
|
60
68
|
],
|
|
61
69
|
reload: [{ action: "reload", args: { target: "@worker" } }],
|
|
62
70
|
exit: [{ action: "exit", args: { target: "@worker" } }],
|
|
71
|
+
hibernate: [{ action: "hibernate", args: { target: "@worker", reason: "idle overnight" } }],
|
|
72
|
+
wake: [{ action: "wake", args: { target: "@worker" } }],
|
|
63
73
|
};
|
|
64
74
|
const PINET_OUTPUT_OPTION_PARAMETERS = {
|
|
65
75
|
format: Type.Optional(Type.String({ description: 'Response presentation format: "cli" (default) or "json".' })),
|
|
@@ -72,7 +82,7 @@ function getRecordString(record, key) {
|
|
|
72
82
|
const value = record?.[key];
|
|
73
83
|
return typeof value === "string" && value.trim() ? value : undefined;
|
|
74
84
|
}
|
|
75
|
-
function
|
|
85
|
+
function isPinetToolObject(value) {
|
|
76
86
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
77
87
|
}
|
|
78
88
|
function normalizeDispatcherAction(value) {
|
|
@@ -100,6 +110,8 @@ function normalizeDispatcherAction(value) {
|
|
|
100
110
|
"spawn",
|
|
101
111
|
"reload",
|
|
102
112
|
"exit",
|
|
113
|
+
"hibernate",
|
|
114
|
+
"wake",
|
|
103
115
|
];
|
|
104
116
|
if (!allowed.includes(normalized)) {
|
|
105
117
|
throw new Error(`Unknown Pinet action: ${normalized}`);
|
|
@@ -118,6 +130,14 @@ function getErrorMessage(error) {
|
|
|
118
130
|
}
|
|
119
131
|
}
|
|
120
132
|
function classifyPinetError(message) {
|
|
133
|
+
if (message.includes("state=launched_unregistered")) {
|
|
134
|
+
return {
|
|
135
|
+
class: "runtime",
|
|
136
|
+
message,
|
|
137
|
+
retryable: true,
|
|
138
|
+
hint: "Retry spawn with retry_handle to clean up the exact recorded worker runtime before relaunching.",
|
|
139
|
+
};
|
|
140
|
+
}
|
|
121
141
|
if (message.includes("requires confirmation for action")) {
|
|
122
142
|
return {
|
|
123
143
|
class: "state",
|
|
@@ -126,7 +146,9 @@ function classifyPinetError(message) {
|
|
|
126
146
|
hint: 'Call slack with action "confirm_action" first in the original thread.',
|
|
127
147
|
};
|
|
128
148
|
}
|
|
129
|
-
if (message.includes("is not running") ||
|
|
149
|
+
if (message.includes("is not running") ||
|
|
150
|
+
message.includes("not initialized") ||
|
|
151
|
+
message.includes("unexpected state")) {
|
|
130
152
|
return {
|
|
131
153
|
class: "state",
|
|
132
154
|
message,
|
|
@@ -173,7 +195,7 @@ function buildPinetDispatcherEnvelope(status, data, errors = [], warnings = [])
|
|
|
173
195
|
function formatPinetHelpCliText(data) {
|
|
174
196
|
const actions = Array.isArray(data.actions)
|
|
175
197
|
? data.actions
|
|
176
|
-
.map((action) =>
|
|
198
|
+
.map((action) => isPinetToolObject(action) && typeof action.action === "string" ? action.action : null)
|
|
177
199
|
.filter((action) => Boolean(action))
|
|
178
200
|
: [];
|
|
179
201
|
if (actions.length > 0) {
|
|
@@ -189,7 +211,7 @@ function formatPinetHelpCliText(data) {
|
|
|
189
211
|
return null;
|
|
190
212
|
}
|
|
191
213
|
function getPinetEnvelopeCliText(envelope) {
|
|
192
|
-
if (envelope.status === "succeeded" &&
|
|
214
|
+
if (envelope.status === "succeeded" && isPinetToolObject(envelope.data)) {
|
|
193
215
|
const text = envelope.data.text;
|
|
194
216
|
if (typeof text === "string" && text.length > 0)
|
|
195
217
|
return text;
|
|
@@ -207,7 +229,7 @@ function getPinetEnvelopeCliText(envelope) {
|
|
|
207
229
|
return `Pinet ${envelope.status}. Use args.format="json" for the compact envelope, or args.full=true for verbose details.`;
|
|
208
230
|
}
|
|
209
231
|
function getTolerantPinetOutputOptions(value) {
|
|
210
|
-
if (!
|
|
232
|
+
if (!isPinetToolObject(value))
|
|
211
233
|
return { format: "cli", full: false };
|
|
212
234
|
const rawFormat = value.format ?? value.f ?? value["-f"];
|
|
213
235
|
const normalizedFormat = rawFormat == null ? "cli" : String(rawFormat).trim().toLowerCase();
|
|
@@ -216,7 +238,7 @@ function getTolerantPinetOutputOptions(value) {
|
|
|
216
238
|
return { format, full: rawFull === true };
|
|
217
239
|
}
|
|
218
240
|
function getOptionalPinetOutputOptions(value) {
|
|
219
|
-
if (!
|
|
241
|
+
if (!isPinetToolObject(value))
|
|
220
242
|
return { format: "cli", full: false };
|
|
221
243
|
try {
|
|
222
244
|
return normalizePinetOutputOptions(value);
|
|
@@ -232,28 +254,134 @@ function shouldRenderStructuredPinetEnvelope(envelope, output) {
|
|
|
232
254
|
return false;
|
|
233
255
|
if (envelope.status === "failed")
|
|
234
256
|
return true;
|
|
235
|
-
if (!
|
|
257
|
+
if (!isPinetToolObject(envelope.data))
|
|
236
258
|
return true;
|
|
237
259
|
const text = envelope.data.text;
|
|
238
260
|
return typeof text !== "string" || text.length === 0;
|
|
239
261
|
}
|
|
240
|
-
function wrapDispatcherEnvelope(envelope, output = { format: "cli", full: false }, expandedText, displayText) {
|
|
262
|
+
function wrapDispatcherEnvelope(envelope, output = { format: "cli", full: false }, expandedText, displayText, compactDetailsForSpill) {
|
|
263
|
+
const structuredEnvelopeText = JSON.stringify(envelope, null, output.full ? 2 : 0);
|
|
264
|
+
const renderStructured = shouldRenderStructuredPinetEnvelope(envelope, output);
|
|
265
|
+
const renderedText = renderStructured
|
|
266
|
+
? structuredEnvelopeText
|
|
267
|
+
: getPinetEnvelopeCliText(envelope);
|
|
268
|
+
const data = typeof envelope.data === "object" && envelope.data !== null && !Array.isArray(envelope.data)
|
|
269
|
+
? envelope.data
|
|
270
|
+
: null;
|
|
271
|
+
const action = typeof data?.action === "string" ? data.action : undefined;
|
|
272
|
+
const fullOutputText = output.format === "json"
|
|
273
|
+
? renderedText
|
|
274
|
+
: `${renderedText}\n\n--- Pinet dispatcher envelope (JSON) ---\n${JSON.stringify(envelope, null, 2)}`;
|
|
275
|
+
const outputMeasurement = measureOutputLimits(fullOutputText);
|
|
276
|
+
let returnedEnvelope = envelope;
|
|
277
|
+
let returnedText = renderedText;
|
|
278
|
+
let returnedDisplayText = displayText;
|
|
279
|
+
if (outputMeasurement.exceedsLimit) {
|
|
280
|
+
const fileToken = (action ?? "result")
|
|
281
|
+
.trim()
|
|
282
|
+
.toLowerCase()
|
|
283
|
+
.replace(/[^a-z0-9_-]+/g, "-")
|
|
284
|
+
.replace(/^-+|-+$/g, "") || "result";
|
|
285
|
+
const dir = mkdtempSync(path.join(os.tmpdir(), "pinet-output-"));
|
|
286
|
+
const extension = output.format === "json" ? "json" : "txt";
|
|
287
|
+
const fullOutputPath = path.join(dir, `${Date.now()}-${fileToken}.${extension}`);
|
|
288
|
+
writeFileSync(fullOutputPath, fullOutputText, "utf8");
|
|
289
|
+
const originalDetails = data?.details;
|
|
290
|
+
let compactDetailRecord = {};
|
|
291
|
+
if (typeof compactDetailsForSpill === "object" &&
|
|
292
|
+
compactDetailsForSpill !== null &&
|
|
293
|
+
!Array.isArray(compactDetailsForSpill)) {
|
|
294
|
+
compactDetailRecord = compactDetailsForSpill;
|
|
295
|
+
}
|
|
296
|
+
else if (compactDetailsForSpill != null) {
|
|
297
|
+
const compactDetailValue = Array.isArray(compactDetailsForSpill)
|
|
298
|
+
? { count: compactDetailsForSpill.length }
|
|
299
|
+
: typeof compactDetailsForSpill === "string" ||
|
|
300
|
+
typeof compactDetailsForSpill === "number" ||
|
|
301
|
+
typeof compactDetailsForSpill === "boolean"
|
|
302
|
+
? compactDetailsForSpill
|
|
303
|
+
: String(compactDetailsForSpill);
|
|
304
|
+
compactDetailRecord = { compactDetails: compactDetailValue };
|
|
305
|
+
}
|
|
306
|
+
else if (typeof originalDetails === "object" &&
|
|
307
|
+
originalDetails !== null &&
|
|
308
|
+
!Array.isArray(originalDetails)) {
|
|
309
|
+
compactDetailRecord = Object.fromEntries(Object.entries(originalDetails)
|
|
310
|
+
.slice(0, PINET_COMPACT_LIST_LIMIT)
|
|
311
|
+
.map(([key, value]) => {
|
|
312
|
+
if (value == null ||
|
|
313
|
+
typeof value === "string" ||
|
|
314
|
+
typeof value === "number" ||
|
|
315
|
+
typeof value === "boolean") {
|
|
316
|
+
return [key, value];
|
|
317
|
+
}
|
|
318
|
+
if (Array.isArray(value))
|
|
319
|
+
return [key, { count: value.length }];
|
|
320
|
+
return [key, typeof value === "object" ? "{...}" : String(value)];
|
|
321
|
+
}));
|
|
322
|
+
}
|
|
323
|
+
const count = compactDetailRecord.laneCount ??
|
|
324
|
+
compactDetailRecord.count ??
|
|
325
|
+
compactDetailRecord.total ??
|
|
326
|
+
compactDetailRecord.messageCount ??
|
|
327
|
+
compactDetailRecord.sessionCount;
|
|
328
|
+
const subject = action ? `Pinet ${action}` : `Pinet ${envelope.status}`;
|
|
329
|
+
let summary;
|
|
330
|
+
if (typeof count === "number" && action === "lanes") {
|
|
331
|
+
summary = `Pinet lanes: ${count} tracked.`;
|
|
332
|
+
}
|
|
333
|
+
else if (typeof count === "number" && action === "agents") {
|
|
334
|
+
summary = `Pinet agents: ${count} visible.`;
|
|
335
|
+
}
|
|
336
|
+
else if (typeof count === "number" && action === "sessions") {
|
|
337
|
+
summary = `Pinet sessions: ${count} found.`;
|
|
338
|
+
}
|
|
339
|
+
else if (typeof count === "number" && action === "ports") {
|
|
340
|
+
summary = `Pinet port leases: ${count}.`;
|
|
341
|
+
}
|
|
342
|
+
else if (typeof count === "number" && action === "read") {
|
|
343
|
+
summary = `Pinet read: ${count} message${count === 1 ? "" : "s"}.`;
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
summary = `${subject} output exceeded the inline limit.`;
|
|
347
|
+
}
|
|
348
|
+
const spillNotice = `[Output truncated: original was ${outputMeasurement.totalLines} line${outputMeasurement.totalLines === 1 ? "" : "s"} (${formatOutputSize(outputMeasurement.totalBytes)}), exceeding the inline limit of ${DEFAULT_MAX_OUTPUT_LINES} lines or ${formatOutputSize(DEFAULT_MAX_OUTPUT_BYTES)}. Full output saved to: ${fullOutputPath}]`;
|
|
349
|
+
const warning = `Pinet output exceeded ${formatOutputSize(DEFAULT_MAX_OUTPUT_BYTES)} / ${DEFAULT_MAX_OUTPUT_LINES} lines; full output saved to ${fullOutputPath}.`;
|
|
350
|
+
returnedEnvelope = buildPinetDispatcherEnvelope(envelope.status, {
|
|
351
|
+
...(action ? { action } : {}),
|
|
352
|
+
text: `${summary}\n\n${spillNotice}`,
|
|
353
|
+
details: {
|
|
354
|
+
...compactDetailRecord,
|
|
355
|
+
fullOutputPath,
|
|
356
|
+
truncation: {
|
|
357
|
+
truncated: true,
|
|
358
|
+
totalLines: outputMeasurement.totalLines,
|
|
359
|
+
totalBytes: outputMeasurement.totalBytes,
|
|
360
|
+
maxLines: DEFAULT_MAX_OUTPUT_LINES,
|
|
361
|
+
maxBytes: DEFAULT_MAX_OUTPUT_BYTES,
|
|
362
|
+
},
|
|
363
|
+
outputFormat: output.format,
|
|
364
|
+
},
|
|
365
|
+
}, envelope.errors, [...envelope.warnings, warning]);
|
|
366
|
+
returnedText = renderStructured
|
|
367
|
+
? JSON.stringify(returnedEnvelope, null, output.full ? 2 : 0)
|
|
368
|
+
: getPinetEnvelopeCliText(returnedEnvelope);
|
|
369
|
+
returnedDisplayText = getPinetEnvelopeCliText(returnedEnvelope);
|
|
370
|
+
}
|
|
241
371
|
return {
|
|
242
372
|
content: [
|
|
243
373
|
{
|
|
244
374
|
type: "text",
|
|
245
|
-
text:
|
|
246
|
-
? JSON.stringify(envelope, null, output.full ? 2 : 0)
|
|
247
|
-
: getPinetEnvelopeCliText(envelope),
|
|
375
|
+
text: returnedText,
|
|
248
376
|
},
|
|
249
377
|
],
|
|
250
|
-
details:
|
|
251
|
-
...(expandedText ? { expandedText } : {}),
|
|
252
|
-
...(
|
|
378
|
+
details: returnedEnvelope,
|
|
379
|
+
...(outputMeasurement.exceedsLimit ? {} : expandedText ? { expandedText } : {}),
|
|
380
|
+
...(returnedDisplayText ? { displayText: returnedDisplayText } : {}),
|
|
253
381
|
};
|
|
254
382
|
}
|
|
255
383
|
function isPinetDispatcherEnvelope(value) {
|
|
256
|
-
if (!
|
|
384
|
+
if (!isPinetToolObject(value))
|
|
257
385
|
return false;
|
|
258
386
|
if (value.status !== "succeeded" && value.status !== "failed")
|
|
259
387
|
return false;
|
|
@@ -355,13 +483,13 @@ function formatPinetEnvelopeExpandedText(envelope, expandedText) {
|
|
|
355
483
|
else if (expandedText && expandedText.trim().length > 0) {
|
|
356
484
|
lines.push(...expandedText.split("\n"));
|
|
357
485
|
}
|
|
358
|
-
else if (
|
|
486
|
+
else if (isPinetToolObject(envelope.data)) {
|
|
359
487
|
lines.push(`status: ${envelope.status}`);
|
|
360
488
|
const action = typeof envelope.data.action === "string" ? envelope.data.action : undefined;
|
|
361
489
|
if (action)
|
|
362
490
|
lines.push(`action: ${action}`);
|
|
363
491
|
const details = envelope.data.details;
|
|
364
|
-
if (
|
|
492
|
+
if (isPinetToolObject(details)) {
|
|
365
493
|
lines.push("details:");
|
|
366
494
|
lines.push(...formatPrimitiveDetails(details).map((line) => ` ${line}`));
|
|
367
495
|
}
|
|
@@ -664,36 +792,59 @@ function runPinetSnoozeAction(params, deps, toolName) {
|
|
|
664
792
|
}
|
|
665
793
|
function runPinetSpawnAction(params, deps, toolName, output) {
|
|
666
794
|
return (async () => {
|
|
667
|
-
const task = getMaybeString(params, "task");
|
|
668
|
-
const repo = getMaybeString(params, "repo");
|
|
669
|
-
const role = getMaybeString(params, "role") ?? "subworker";
|
|
670
|
-
const laneId = getMaybeString(params, "lane_id");
|
|
671
|
-
deps.requireToolPolicy(toolName, undefined, `task=${task ?? ""} | repo=${repo ?? ""} | role=${role} | lane_id=${laneId ?? ""}`);
|
|
672
795
|
if (!deps.pinetEnabled()) {
|
|
673
796
|
throw new Error("Pinet is not running. Use /pinet start or /pinet follow first.");
|
|
674
797
|
}
|
|
675
798
|
if (deps.brokerRole() !== "follower") {
|
|
676
799
|
throw new Error("spawn is worker-only; the broker should launch top-level followers, not own subtrees.");
|
|
677
800
|
}
|
|
801
|
+
if (!deps.spawnSubtreeWorker) {
|
|
802
|
+
throw new Error("subtree worker launcher is unavailable in this runtime. Start a worker subtree with /pinet subtree start and retry.");
|
|
803
|
+
}
|
|
804
|
+
const task = getMaybeString(params, "task");
|
|
805
|
+
const repo = getMaybeString(params, "repo");
|
|
806
|
+
const role = getMaybeString(params, "role") ?? "subworker";
|
|
807
|
+
const laneId = getMaybeString(params, "lane_id");
|
|
808
|
+
const retryHandleValue = params.retry_handle;
|
|
809
|
+
let cleanupHandle;
|
|
810
|
+
if (retryHandleValue !== undefined) {
|
|
811
|
+
if (!isPinetToolObject(retryHandleValue)) {
|
|
812
|
+
throw new Error("retry_handle must be a subtree spawn launch handle");
|
|
813
|
+
}
|
|
814
|
+
const launchId = getRecordString(retryHandleValue, "launchId");
|
|
815
|
+
const tmuxSessionName = getRecordString(retryHandleValue, "tmuxSessionName");
|
|
816
|
+
const socketPath = getRecordString(retryHandleValue, "socketPath");
|
|
817
|
+
if (!launchId ||
|
|
818
|
+
!tmuxSessionName ||
|
|
819
|
+
!socketPath ||
|
|
820
|
+
retryHandleValue.state !== "launched_unregistered") {
|
|
821
|
+
throw new Error('retry_handle requires launchId, tmuxSessionName, socketPath, and state="launched_unregistered"');
|
|
822
|
+
}
|
|
823
|
+
cleanupHandle = {
|
|
824
|
+
launchId,
|
|
825
|
+
tmuxSessionName,
|
|
826
|
+
socketPath,
|
|
827
|
+
state: "launched_unregistered",
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
deps.requireToolPolicy(toolName, undefined, `task=${task ?? ""} | repo=${repo ?? ""} | role=${role} | lane_id=${laneId ?? ""}`);
|
|
678
831
|
if (!task)
|
|
679
832
|
throw new Error("spawn requires task");
|
|
680
833
|
if (!repo)
|
|
681
834
|
throw new Error("spawn requires repo");
|
|
682
|
-
if (!deps.spawnSubtreeWorker) {
|
|
683
|
-
throw new Error("subtree worker launcher is unavailable in this runtime. Start a worker subtree with /pinet subtree start and retry.");
|
|
684
|
-
}
|
|
685
835
|
const result = await deps.spawnSubtreeWorker({
|
|
686
836
|
task,
|
|
687
837
|
repo,
|
|
688
838
|
role,
|
|
689
839
|
...(laneId ? { laneId } : {}),
|
|
840
|
+
...(cleanupHandle ? { cleanupHandle } : {}),
|
|
690
841
|
});
|
|
691
842
|
return {
|
|
692
843
|
content: [
|
|
693
844
|
{
|
|
694
845
|
type: "text",
|
|
695
846
|
text: output.full
|
|
696
|
-
? `Pinet subtree worker started: ${result.agentName} (${result.agentId})
|
|
847
|
+
? `Pinet subtree worker started: ${result.agentName} (${result.agentId}) using ${result.runtimeKind} runtime ${result.sessionName}. Task message ${result.messageId} delivered. Monitor: ${result.monitorCommand}`
|
|
697
848
|
: `Pinet subtree worker started: ${result.agentName} (${result.agentId}). Task message ${result.messageId} delivered.`,
|
|
698
849
|
},
|
|
699
850
|
],
|
|
@@ -728,6 +879,49 @@ function runPinetRemoteControlAction(params, deps, toolName, command) {
|
|
|
728
879
|
};
|
|
729
880
|
})();
|
|
730
881
|
}
|
|
882
|
+
function runPinetHibernationCommandAction(target, reason, deps, toolName, command) {
|
|
883
|
+
return (async () => {
|
|
884
|
+
if (!target) {
|
|
885
|
+
throw new Error("target is required");
|
|
886
|
+
}
|
|
887
|
+
// Sanitize the operator-authored target/reason before they enter the policy
|
|
888
|
+
// context, which can surface verbatim in confirmation prompts and error
|
|
889
|
+
// text. `sanitizeOperatorTarget` fingerprints anything that is not a plain
|
|
890
|
+
// broker-safe identifier slug — a stable-id/session/cwd target such as
|
|
891
|
+
// `host:session:<ref>` (which embeds the session-resume identity), a private
|
|
892
|
+
// worktree path, or a `KEY=secret` shape — to an opaque `target:#<hash>`
|
|
893
|
+
// rather than echoing it. The reason is path/secret-redacted. The raw
|
|
894
|
+
// `target` still flows to the broker runner, which resolves it server-side
|
|
895
|
+
// and redacts any unknown-target echo.
|
|
896
|
+
const safeTarget = sanitizeOperatorTarget(target);
|
|
897
|
+
const safeReason = sanitizeOperatorReason(reason) ?? "";
|
|
898
|
+
deps.requireToolPolicy(toolName, undefined, `command=${command} | target=${safeTarget} | reason=${safeReason}`);
|
|
899
|
+
if (!deps.pinetEnabled()) {
|
|
900
|
+
throw new Error("Pinet is not running. Use /pinet start or /pinet follow first.");
|
|
901
|
+
}
|
|
902
|
+
const runner = command === "hibernate" ? deps.runHibernateCommand : deps.runWakeCommand;
|
|
903
|
+
if (!runner) {
|
|
904
|
+
throw new Error(`pinet ${command} is broker-managed. Connect this session as the broker (/pinet start) to hibernate or wake workers.`);
|
|
905
|
+
}
|
|
906
|
+
const result = await runner({ target, reason });
|
|
907
|
+
const text = formatHibernationCommandResult(result);
|
|
908
|
+
return {
|
|
909
|
+
content: [{ type: "text", text }],
|
|
910
|
+
details: result,
|
|
911
|
+
compactDetails: {
|
|
912
|
+
command: result.command,
|
|
913
|
+
agentId: result.agentId,
|
|
914
|
+
outcome: result.outcome,
|
|
915
|
+
reason: result.reason,
|
|
916
|
+
state: result.state,
|
|
917
|
+
...(result.runtimeGeneration != null
|
|
918
|
+
? { runtimeGeneration: result.runtimeGeneration }
|
|
919
|
+
: {}),
|
|
920
|
+
},
|
|
921
|
+
fullDetails: result,
|
|
922
|
+
};
|
|
923
|
+
})();
|
|
924
|
+
}
|
|
731
925
|
function runPinetScheduleAction(params, deps, toolName, output) {
|
|
732
926
|
return (async () => {
|
|
733
927
|
const delay = typeof params.delay === "string" ? params.delay : undefined;
|
|
@@ -995,7 +1189,7 @@ function getMaybeMetadata(params) {
|
|
|
995
1189
|
const value = params.metadata;
|
|
996
1190
|
if (value === null)
|
|
997
1191
|
return null;
|
|
998
|
-
return
|
|
1192
|
+
return isPinetToolObject(value) ? value : undefined;
|
|
999
1193
|
}
|
|
1000
1194
|
function buildCompactPortLeaseDetails(leases) {
|
|
1001
1195
|
const shownLeases = leases.slice(0, PINET_COMPACT_LIST_LIMIT);
|
|
@@ -1185,7 +1379,12 @@ function runPinetPortsAction(params, deps, toolName, output) {
|
|
|
1185
1379
|
function runPinetLanesAction(params, deps, toolName, output) {
|
|
1186
1380
|
return (async () => {
|
|
1187
1381
|
const op = getMaybeString(params, "op")?.toLowerCase() ?? "list";
|
|
1188
|
-
|
|
1382
|
+
const policyContext = `op=${op} | format=${output.format}`;
|
|
1383
|
+
const threadTs = getMaybeString(params, "thread_ts");
|
|
1384
|
+
deps.requireToolPolicy(toolName, threadTs, policyContext);
|
|
1385
|
+
if (op === "upsert" || op === "participant") {
|
|
1386
|
+
deps.requireToolPolicy(`${toolName}:write`, threadTs, policyContext);
|
|
1387
|
+
}
|
|
1189
1388
|
if (!deps.pinetEnabled()) {
|
|
1190
1389
|
throw new Error("Pinet is not running. Use /pinet start or /pinet follow first.");
|
|
1191
1390
|
}
|
|
@@ -1297,6 +1496,32 @@ function runPinetLanesAction(params, deps, toolName, output) {
|
|
|
1297
1496
|
throw new Error("lanes op must be list, upsert, or participant");
|
|
1298
1497
|
})();
|
|
1299
1498
|
}
|
|
1499
|
+
/**
|
|
1500
|
+
* Render the operator-safe hibernation lifecycle section appended to the
|
|
1501
|
+
* existing `agents`/`sessions` read output. Compact output uses a scannable
|
|
1502
|
+
* per-agent tag (state · gen · queue · checkpoint · refusal/quarantine); full
|
|
1503
|
+
* output uses the complete redacted status block. Returns "" when there is
|
|
1504
|
+
* nothing hibernation-relevant to show, so ordinary output is unchanged.
|
|
1505
|
+
*/
|
|
1506
|
+
function formatHibernationLifecycleSection(statuses, full) {
|
|
1507
|
+
if (statuses.length === 0)
|
|
1508
|
+
return "";
|
|
1509
|
+
const body = full
|
|
1510
|
+
? statuses.map(formatAgentLifecycleStatus).join("\n\n")
|
|
1511
|
+
: statuses
|
|
1512
|
+
.map((status) => ` ${status.agentId}: ${formatAgentLifecycleTag(status)}`)
|
|
1513
|
+
.join("\n");
|
|
1514
|
+
return `\n\nHibernation lifecycle:\n${body}`;
|
|
1515
|
+
}
|
|
1516
|
+
function buildCompactLifecycleDetails(statuses) {
|
|
1517
|
+
return statuses.map((status) => ({
|
|
1518
|
+
agentId: status.agentId,
|
|
1519
|
+
state: status.state,
|
|
1520
|
+
quarantined: status.quarantined,
|
|
1521
|
+
tag: formatAgentLifecycleTag(status),
|
|
1522
|
+
...(status.wake.queued ? { wakeQueuePosition: status.wake.position } : {}),
|
|
1523
|
+
}));
|
|
1524
|
+
}
|
|
1300
1525
|
function runPinetAgentsAction(params, deps, toolName, output) {
|
|
1301
1526
|
return (async () => {
|
|
1302
1527
|
const scope = params.scope === "children" || params.scope === "subtree" || params.scope === "all"
|
|
@@ -1361,14 +1586,26 @@ function runPinetAgentsAction(params, deps, toolName, output) {
|
|
|
1361
1586
|
recentDisconnectWindowMs: recentGhostWindowMs,
|
|
1362
1587
|
}).map(toDisplay);
|
|
1363
1588
|
const agents = rankAgentsForRouting(visibleAgents, hint);
|
|
1589
|
+
// Read-only hibernation lifecycle projection wired into the existing agents
|
|
1590
|
+
// read path. Compact output shows a scannable per-agent tag; full/JSON
|
|
1591
|
+
// exposes the complete redacted lifecycle structure. Broker-only (the dep
|
|
1592
|
+
// is undefined on followers) and sanitized by construction. Called with NO
|
|
1593
|
+
// id filter so durable hibernated/quarantined runtimes — which are
|
|
1594
|
+
// disconnected and therefore absent from the visible roster — are still
|
|
1595
|
+
// surfaced (this is exactly when operators need lifecycle visibility).
|
|
1596
|
+
const lifecycle = deps.getAgentLifecycleProjection ? deps.getAgentLifecycleProjection() : [];
|
|
1364
1597
|
const header = hasHint && output.full ? `${buildPinetAgentsHintText(hint)}\n\n` : "";
|
|
1365
|
-
const text = `${header}${output.full ? formatAgentList(agents, os.homedir()) : formatCompactAgentList(agents, hint)}`;
|
|
1598
|
+
const text = `${header}${output.full ? formatAgentList(agents, os.homedir()) : formatCompactAgentList(agents, hint)}${formatHibernationLifecycleSection(lifecycle, output.full)}`;
|
|
1599
|
+
const expandedText = `${formatPinetAgentsExpanded(agents)}${formatHibernationLifecycleSection(lifecycle, true)}`;
|
|
1366
1600
|
return {
|
|
1367
1601
|
content: [{ type: "text", text }],
|
|
1368
|
-
details: { agents, hint },
|
|
1369
|
-
compactDetails:
|
|
1370
|
-
|
|
1371
|
-
|
|
1602
|
+
details: { agents, hint, ...(lifecycle.length > 0 ? { lifecycle } : {}) },
|
|
1603
|
+
compactDetails: {
|
|
1604
|
+
...buildCompactAgentDetails(agents, hint),
|
|
1605
|
+
...(lifecycle.length > 0 ? { lifecycle: buildCompactLifecycleDetails(lifecycle) } : {}),
|
|
1606
|
+
},
|
|
1607
|
+
fullDetails: { agents, hint, ...(lifecycle.length > 0 ? { lifecycle } : {}) },
|
|
1608
|
+
expandedText,
|
|
1372
1609
|
};
|
|
1373
1610
|
})();
|
|
1374
1611
|
}
|
|
@@ -1385,6 +1622,9 @@ function buildPinetSessionSearchOptions(params) {
|
|
|
1385
1622
|
...(getMaybeString(params, "worktree_path")
|
|
1386
1623
|
? { worktreePath: getMaybeString(params, "worktree_path") }
|
|
1387
1624
|
: {}),
|
|
1625
|
+
...(getMaybeString(params, "runtime_locator")
|
|
1626
|
+
? { runtimeLocator: getMaybeString(params, "runtime_locator") }
|
|
1627
|
+
: {}),
|
|
1388
1628
|
...(getMaybeString(params, "tmux_session")
|
|
1389
1629
|
? { tmuxSession: getMaybeString(params, "tmux_session") }
|
|
1390
1630
|
: {}),
|
|
@@ -1400,6 +1640,7 @@ function formatPinetSessionSearchHeader(sessions, options) {
|
|
|
1400
1640
|
options.threadId ? `thread_id=${options.threadId}` : null,
|
|
1401
1641
|
options.repo ? `repo=${options.repo}` : null,
|
|
1402
1642
|
options.worktreePath ? `worktree=${options.worktreePath}` : null,
|
|
1643
|
+
options.runtimeLocator ? `runtime=${options.runtimeLocator}` : null,
|
|
1403
1644
|
options.tmuxSession ? `tmux=${options.tmuxSession}` : null,
|
|
1404
1645
|
options.since ? `since=${options.since}` : null,
|
|
1405
1646
|
options.until ? `until=${options.until}` : null,
|
|
@@ -1413,7 +1654,7 @@ function formatPinetSessionLine(session, full) {
|
|
|
1413
1654
|
const where = [
|
|
1414
1655
|
session.repo ?? null,
|
|
1415
1656
|
session.branch ? `branch=${session.branch}` : null,
|
|
1416
|
-
session.
|
|
1657
|
+
session.runtimeLocator ? `${session.runtimeKind ?? "runtime"}=${session.runtimeLocator}` : null,
|
|
1417
1658
|
].filter((item) => Boolean(item));
|
|
1418
1659
|
const threads = session.relatedThreadIds.slice(0, full ? 10 : 3).join(", ");
|
|
1419
1660
|
const suffix = session.relatedThreadIds.length > (full ? 10 : 3) ? " …" : "";
|
|
@@ -1466,24 +1707,37 @@ function runPinetSessionsAction(params, deps, toolName, output) {
|
|
|
1466
1707
|
throw new Error("sessions op must be search");
|
|
1467
1708
|
}
|
|
1468
1709
|
const options = buildPinetSessionSearchOptions(params);
|
|
1469
|
-
deps.requireToolPolicy(toolName, undefined, `agent_name=${options.agentName ?? ""} | agent_id=${options.agentId ?? ""} | thread_id=${options.threadId ?? ""} | repo=${options.repo ?? ""} | worktree_path=${options.worktreePath ?? ""} | tmux_session=${options.tmuxSession ?? ""} | since=${options.since ?? ""} | until=${options.until ?? ""} | limit=${options.limit ?? ""} | format=${output.format} | full=${output.full}`);
|
|
1710
|
+
deps.requireToolPolicy(toolName, undefined, `agent_name=${options.agentName ?? ""} | agent_id=${options.agentId ?? ""} | thread_id=${options.threadId ?? ""} | repo=${options.repo ?? ""} | worktree_path=${options.worktreePath ?? ""} | runtime_locator=${options.runtimeLocator ?? ""} | tmux_session=${options.tmuxSession ?? ""} | since=${options.since ?? ""} | until=${options.until ?? ""} | limit=${options.limit ?? ""} | format=${output.format} | full=${output.full}`);
|
|
1470
1711
|
if (!deps.pinetEnabled()) {
|
|
1471
1712
|
throw new Error("Pinet is not running. Use /pinet start or /pinet follow first.");
|
|
1472
1713
|
}
|
|
1473
1714
|
const sessions = await deps.searchPinetSessions(options);
|
|
1474
|
-
|
|
1715
|
+
// Read-only lifecycle projection for the session's live agents, wired into
|
|
1716
|
+
// the existing sessions read path. Broker-only and sanitized by
|
|
1717
|
+
// construction; empty for historical/disconnected agents.
|
|
1718
|
+
const lifecycle = deps.getAgentLifecycleProjection && sessions.length > 0
|
|
1719
|
+
? deps.getAgentLifecycleProjection(sessions.map((session) => session.agentId))
|
|
1720
|
+
: [];
|
|
1721
|
+
const text = `${formatPinetSessionSearch(sessions, options, output.full)}${formatHibernationLifecycleSection(lifecycle, output.full)}`;
|
|
1475
1722
|
return {
|
|
1476
1723
|
content: [{ type: "text", text }],
|
|
1477
|
-
details: {
|
|
1724
|
+
details: {
|
|
1725
|
+
count: sessions.length,
|
|
1726
|
+
sessions,
|
|
1727
|
+
options,
|
|
1728
|
+
...(lifecycle.length > 0 ? { lifecycle } : {}),
|
|
1729
|
+
},
|
|
1478
1730
|
compactDetails: {
|
|
1479
1731
|
count: sessions.length,
|
|
1480
1732
|
options,
|
|
1481
1733
|
sessions: sessions.map(buildPinetSessionCompactDetails),
|
|
1734
|
+
...(lifecycle.length > 0 ? { lifecycle: buildCompactLifecycleDetails(lifecycle) } : {}),
|
|
1482
1735
|
},
|
|
1483
1736
|
fullDetails: {
|
|
1484
1737
|
count: sessions.length,
|
|
1485
1738
|
options,
|
|
1486
1739
|
sessions: sessions.map(buildPinetSessionFullDetails),
|
|
1740
|
+
...(lifecycle.length > 0 ? { lifecycle } : {}),
|
|
1487
1741
|
},
|
|
1488
1742
|
expandedText: text,
|
|
1489
1743
|
};
|
|
@@ -1496,12 +1750,14 @@ export function registerPinetTools(pi, deps) {
|
|
|
1496
1750
|
}
|
|
1497
1751
|
registerAction({
|
|
1498
1752
|
name: "send",
|
|
1499
|
-
description: "Send a message to a connected Pinet agent or broker-only broadcast channel.",
|
|
1753
|
+
description: "Send a queued message to a connected Pinet agent or broker-only broadcast channel. Prefix the body with /steer to interrupt a busy recipient with explicit steering.",
|
|
1500
1754
|
parameters: Type.Object({
|
|
1501
1755
|
to: Type.String({
|
|
1502
1756
|
description: "Target agent name/ID, or a broker-only broadcast channel like #extensions. Avoid #all for repo-specific issue/policy announcements.",
|
|
1503
1757
|
}),
|
|
1504
|
-
message: Type.String({
|
|
1758
|
+
message: Type.String({
|
|
1759
|
+
description: "Message body. Use /steer <instruction> only when you explicitly need to steer a busy recipient immediately; otherwise messages remain queued.",
|
|
1760
|
+
}),
|
|
1505
1761
|
transfer_thread_id: Type.Optional(Type.String({
|
|
1506
1762
|
description: "Broker-only: transfer ownership of this existing Slack thread to the direct recipient after delivery (for example a Slack thread_ts).",
|
|
1507
1763
|
})),
|
|
@@ -1545,12 +1801,18 @@ export function registerPinetTools(pi, deps) {
|
|
|
1545
1801
|
});
|
|
1546
1802
|
registerAction({
|
|
1547
1803
|
name: "spawn",
|
|
1548
|
-
description: "Worker-only: launch a
|
|
1804
|
+
description: "Worker-only: launch a child worker with the configured runtime into this worker's active subtree broker and deliver the task over Pinet.",
|
|
1549
1805
|
parameters: Type.Object({
|
|
1550
1806
|
task: Type.String({ description: "Scoped task prompt for the child worker" }),
|
|
1551
1807
|
repo: Type.String({ description: "Repo/workspace scope for the child worker" }),
|
|
1552
1808
|
role: Type.Optional(Type.String({ description: "Child subtree role, e.g. reviewer" })),
|
|
1553
1809
|
lane_id: Type.Optional(Type.String({ description: "Optional durable Pinet lane id" })),
|
|
1810
|
+
retry_handle: Type.Optional(Type.Object({
|
|
1811
|
+
launchId: Type.String(),
|
|
1812
|
+
tmuxSessionName: Type.String(),
|
|
1813
|
+
socketPath: Type.String(),
|
|
1814
|
+
state: Type.Literal("launched_unregistered"),
|
|
1815
|
+
})),
|
|
1554
1816
|
...PINET_OUTPUT_OPTION_PARAMETERS,
|
|
1555
1817
|
}),
|
|
1556
1818
|
execute: (_id, params, output) => runPinetSpawnAction(params, deps, "pinet:spawn", output),
|
|
@@ -1573,6 +1835,42 @@ export function registerPinetTools(pi, deps) {
|
|
|
1573
1835
|
}),
|
|
1574
1836
|
execute: (_id, params, _output) => runPinetRemoteControlAction(params, deps, "pinet:exit", "/exit"),
|
|
1575
1837
|
});
|
|
1838
|
+
registerAction({
|
|
1839
|
+
name: "hibernate",
|
|
1840
|
+
description: "Broker-managed: checkpoint and hibernate an idle broker-managed worker. Default-off; returns a safe refusal when hibernation is disabled or observe-only.",
|
|
1841
|
+
parameters: Type.Object({
|
|
1842
|
+
target: Type.String({
|
|
1843
|
+
description: "Target worker: display name, agent id, or stable id",
|
|
1844
|
+
}),
|
|
1845
|
+
reason: Type.Optional(Type.String({ description: "Optional operator reason recorded in lifecycle telemetry" })),
|
|
1846
|
+
...PINET_OUTPUT_OPTION_PARAMETERS,
|
|
1847
|
+
}),
|
|
1848
|
+
execute: (_id, params, _output) => {
|
|
1849
|
+
const target = typeof params.target === "string" ? params.target.trim() : "";
|
|
1850
|
+
const reason = typeof params.reason === "string" && params.reason.trim().length > 0
|
|
1851
|
+
? params.reason.trim()
|
|
1852
|
+
: undefined;
|
|
1853
|
+
return runPinetHibernationCommandAction(target, reason, deps, "pinet:hibernate", "hibernate");
|
|
1854
|
+
},
|
|
1855
|
+
});
|
|
1856
|
+
registerAction({
|
|
1857
|
+
name: "wake",
|
|
1858
|
+
description: "Broker-managed: wake a hibernated worker under a fresh fenced runtime and drain its queued messages in order.",
|
|
1859
|
+
parameters: Type.Object({
|
|
1860
|
+
target: Type.String({
|
|
1861
|
+
description: "Target worker: display name, agent id, or stable id",
|
|
1862
|
+
}),
|
|
1863
|
+
reason: Type.Optional(Type.String({ description: "Optional operator reason recorded in lifecycle telemetry" })),
|
|
1864
|
+
...PINET_OUTPUT_OPTION_PARAMETERS,
|
|
1865
|
+
}),
|
|
1866
|
+
execute: (_id, params, _output) => {
|
|
1867
|
+
const target = typeof params.target === "string" ? params.target.trim() : "";
|
|
1868
|
+
const reason = typeof params.reason === "string" && params.reason.trim().length > 0
|
|
1869
|
+
? params.reason.trim()
|
|
1870
|
+
: undefined;
|
|
1871
|
+
return runPinetHibernationCommandAction(target, reason, deps, "pinet:wake", "wake");
|
|
1872
|
+
},
|
|
1873
|
+
});
|
|
1576
1874
|
registerAction({
|
|
1577
1875
|
name: "schedule",
|
|
1578
1876
|
description: "Schedule a future wake-up for this Pinet agent.",
|
|
@@ -1606,7 +1904,7 @@ export function registerPinetTools(pi, deps) {
|
|
|
1606
1904
|
});
|
|
1607
1905
|
registerAction({
|
|
1608
1906
|
name: "sessions",
|
|
1609
|
-
description: "Search live and historical Pinet worker Pi sessions by display name, agent id, thread, repo/worktree, tmux session, or time range.",
|
|
1907
|
+
description: "Search live and historical Pinet worker Pi sessions by display name, agent id, thread, repo/worktree, runtime locator (tmux session or herdr pane), or time range.",
|
|
1610
1908
|
parameters: Type.Object({
|
|
1611
1909
|
op: Type.Optional(Type.String({ description: "Operation: search (default)" })),
|
|
1612
1910
|
agent_name: Type.Optional(Type.String({ description: "Worker display name, e.g. Frozen Hazel Whale" })),
|
|
@@ -1615,6 +1913,7 @@ export function registerPinetTools(pi, deps) {
|
|
|
1615
1913
|
thread_id: Type.Optional(Type.String({ description: "Related Pinet/Slack/A2A thread id" })),
|
|
1616
1914
|
repo: Type.Optional(Type.String({ description: "Repo name or path fragment" })),
|
|
1617
1915
|
worktree_path: Type.Optional(Type.String({ description: "Worktree/cwd path fragment" })),
|
|
1916
|
+
runtime_locator: Type.Optional(Type.String({ description: "Broker-managed runtime locator (tmux session or herdr pane)" })),
|
|
1618
1917
|
tmux_session: Type.Optional(Type.String({ description: "Broker-managed tmux session name" })),
|
|
1619
1918
|
since: Type.Optional(Type.String({ description: "Only sessions active after this ISO time" })),
|
|
1620
1919
|
until: Type.Optional(Type.String({ description: "Only sessions started before this ISO time" })),
|
|
@@ -1645,9 +1944,10 @@ export function registerPinetTools(pi, deps) {
|
|
|
1645
1944
|
});
|
|
1646
1945
|
registerAction({
|
|
1647
1946
|
name: "lanes",
|
|
1648
|
-
description: "List or update durable Pinet lane metadata for PM-mode and complex coordination visibility.",
|
|
1947
|
+
description: "List or update durable Pinet lane metadata for PM-mode and complex coordination visibility. Upsert and participant operations additionally require guardrail policy pinet:lanes:write.",
|
|
1649
1948
|
parameters: Type.Object({
|
|
1650
1949
|
op: Type.Optional(Type.String({ description: "Operation: list, upsert, or participant" })),
|
|
1950
|
+
thread_ts: Type.Optional(Type.String({ description: "Slack thread timestamp for confirmation-required mutations" })),
|
|
1651
1951
|
lane_id: Type.Optional(Type.String({ description: "Stable lane id, e.g. issue-688" })),
|
|
1652
1952
|
name: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "Human-readable lane name" })),
|
|
1653
1953
|
task: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "Short lane task description" })),
|
|
@@ -1694,10 +1994,11 @@ export function registerPinetTools(pi, deps) {
|
|
|
1694
1994
|
name: "pinet",
|
|
1695
1995
|
label: "Pinet Dispatcher",
|
|
1696
1996
|
description: "Dispatch Pinet operations by action with compact help and schema discovery.",
|
|
1697
|
-
promptSnippet: 'Use this compact dispatcher for Pinet actions: send, read, free, snooze, schedule, agents, sessions, lanes, ports, reload, exit, spawn, and help. Use /pinet start, /pinet follow, /pinet unfollow, and /pinet subtree start for TUI lifecycle changes. Defaults to terse CLI text; pass args.format="json" for the compact envelope or args.full=true for verbose/debug detail.',
|
|
1997
|
+
promptSnippet: 'Use this compact dispatcher for Pinet actions: send, read, free, snooze, schedule, agents, sessions, lanes, ports, reload, exit, hibernate, wake, spawn, and help. Use /pinet start, /pinet follow, /pinet unfollow, and /pinet subtree start for TUI lifecycle changes. Defaults to terse CLI text; pass args.format="json" for the compact envelope or args.full=true for verbose/debug detail.',
|
|
1998
|
+
promptGuidelines: deps.promptGuidelines,
|
|
1698
1999
|
parameters: Type.Object({
|
|
1699
2000
|
action: Type.String({
|
|
1700
|
-
description: "Action name: help, send, read, free, snooze, schedule, agents, sessions, lanes, ports, reload, or
|
|
2001
|
+
description: "Action name: help, send, read, free, snooze, schedule, agents, sessions, lanes, ports, reload, exit, hibernate (broker-managed checkpoint+hibernate), or wake (broker-managed fenced wake). Also supports spawn for launching worker-owned subtree children.",
|
|
1701
2002
|
}),
|
|
1702
2003
|
args: Type.Optional(Type.Record(Type.String(), Type.Unknown(), {
|
|
1703
2004
|
description: 'Action arguments. Add format="cli"|"json" (or f/"-f") for presentation, and full=true (or "--full": true) only for verbose/debug details. Default cli and non-full json keep data.details compact.',
|
|
@@ -1706,7 +2007,7 @@ export function registerPinetTools(pi, deps) {
|
|
|
1706
2007
|
renderCall(args, theme) {
|
|
1707
2008
|
const action = typeof args.action === "string" && args.action.trim() ? args.action.trim() : "?";
|
|
1708
2009
|
let suffix = "";
|
|
1709
|
-
if (
|
|
2010
|
+
if (isPinetToolObject(args.args)) {
|
|
1710
2011
|
const topic = typeof args.args.topic === "string" ? args.args.topic.trim() : "";
|
|
1711
2012
|
const op = typeof args.args.op === "string" ? args.args.op.trim() : "";
|
|
1712
2013
|
if (topic)
|
|
@@ -1748,7 +2049,7 @@ export function registerPinetTools(pi, deps) {
|
|
|
1748
2049
|
]), getOptionalPinetOutputOptions(params.args));
|
|
1749
2050
|
}
|
|
1750
2051
|
if (normalizedAction === "help") {
|
|
1751
|
-
const args =
|
|
2052
|
+
const args = isPinetToolObject(params.args) ? params.args : {};
|
|
1752
2053
|
let output;
|
|
1753
2054
|
try {
|
|
1754
2055
|
output = normalizePinetOutputOptions(args);
|
|
@@ -1776,7 +2077,7 @@ export function registerPinetTools(pi, deps) {
|
|
|
1776
2077
|
},
|
|
1777
2078
|
]), getOptionalPinetOutputOptions(params.args));
|
|
1778
2079
|
}
|
|
1779
|
-
if (!
|
|
2080
|
+
if (!isPinetToolObject(params.args)) {
|
|
1780
2081
|
return wrapDispatcherEnvelope(buildPinetDispatcherEnvelope("failed", null, [
|
|
1781
2082
|
{
|
|
1782
2083
|
class: "input",
|
|
@@ -1810,14 +2111,18 @@ export function registerPinetTools(pi, deps) {
|
|
|
1810
2111
|
action: definition.name,
|
|
1811
2112
|
text: output.format === "json" ? (result.machineText ?? displayText) : displayText,
|
|
1812
2113
|
details: selectPinetResultDetails(result, output),
|
|
1813
|
-
}), output, expandedText, displayText);
|
|
2114
|
+
}), output, expandedText, displayText, result.compactDetails);
|
|
1814
2115
|
}
|
|
1815
2116
|
catch (error) {
|
|
1816
2117
|
if (error instanceof Error && error.name === "AbortError") {
|
|
1817
2118
|
throw error;
|
|
1818
2119
|
}
|
|
1819
2120
|
const message = getErrorMessage(error);
|
|
1820
|
-
|
|
2121
|
+
const launchHandle = error instanceof SubtreeSpawnRegistrationTimeoutError ||
|
|
2122
|
+
error instanceof SubtreeSpawnLaunchError
|
|
2123
|
+
? error.handle
|
|
2124
|
+
: null;
|
|
2125
|
+
return wrapDispatcherEnvelope(buildPinetDispatcherEnvelope("failed", launchHandle ? { action: definition.name, details: { launchHandle } } : null, [classifyPinetError(message)]), output);
|
|
1821
2126
|
}
|
|
1822
2127
|
},
|
|
1823
2128
|
});
|