@pinet/slack-bridge 0.2.4 → 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.
Files changed (93) hide show
  1. package/README.md +26 -1
  2. package/dist/activity-log.d.ts +10 -3
  3. package/dist/agent-event-runtime.d.ts +0 -2
  4. package/dist/agent-event-runtime.js +0 -1
  5. package/dist/agent-prompt-guidance.d.ts +1 -6
  6. package/dist/agent-prompt-guidance.js +1 -6
  7. package/dist/broker/adapters/slack.d.ts +4 -1
  8. package/dist/broker/adapters/slack.js +16 -9
  9. package/dist/broker/client.d.ts +21 -4
  10. package/dist/broker/client.js +65 -25
  11. package/dist/broker/hibernation-activation-authority.d.ts +22 -0
  12. package/dist/broker/hibernation-activation-authority.js +51 -0
  13. package/dist/broker/hibernation-activation.d.ts +75 -0
  14. package/dist/broker/hibernation-activation.js +95 -0
  15. package/dist/broker/hibernation-command-router.d.ts +54 -0
  16. package/dist/broker/hibernation-command-router.js +124 -0
  17. package/dist/broker/hibernation-runtime-adapters.d.ts +79 -0
  18. package/dist/broker/hibernation-runtime-adapters.js +473 -0
  19. package/dist/broker/hibernation-runtime-helpers.d.ts +146 -0
  20. package/dist/broker/hibernation-runtime-helpers.js +243 -0
  21. package/dist/broker/index.d.ts +16 -1
  22. package/dist/broker/index.js +68 -10
  23. package/dist/broker/lock-conflict.d.ts +109 -0
  24. package/dist/broker/lock-conflict.js +409 -0
  25. package/dist/broker/socket-server.d.ts +25 -0
  26. package/dist/broker/socket-server.js +234 -4
  27. package/dist/broker-delivery.d.ts +1 -0
  28. package/dist/broker-delivery.js +10 -1
  29. package/dist/broker-runtime.d.ts +14 -2
  30. package/dist/broker-runtime.js +114 -3
  31. package/dist/canvases.d.ts +12 -2
  32. package/dist/canvases.js +1 -3
  33. package/dist/compaction-gate.d.ts +7 -0
  34. package/dist/compaction-gate.js +35 -0
  35. package/dist/deploy-manifest.d.ts +8 -2
  36. package/dist/deploy-manifest.js +9 -7
  37. package/dist/follower-runtime.d.ts +5 -2
  38. package/dist/follower-runtime.js +39 -5
  39. package/dist/git-metadata.d.ts +5 -9
  40. package/dist/git-metadata.js +23 -42
  41. package/dist/guardrails.js +5 -0
  42. package/dist/helpers.d.ts +77 -27
  43. package/dist/helpers.js +150 -39
  44. package/dist/hibernation-config.d.ts +13 -0
  45. package/dist/hibernation-config.js +14 -0
  46. package/dist/home-tab.d.ts +13 -2
  47. package/dist/imessage-tools.d.ts +4 -1
  48. package/dist/inbox-drain-runtime.d.ts +1 -0
  49. package/dist/inbox-drain-runtime.js +5 -1
  50. package/dist/index.js +340 -108
  51. package/dist/pinet-commands.d.ts +14 -1
  52. package/dist/pinet-commands.js +93 -22
  53. package/dist/pinet-mesh-ops.d.ts +6 -4
  54. package/dist/pinet-mesh-ops.js +8 -1
  55. package/dist/pinet-runtime-composition.d.ts +2 -1
  56. package/dist/pinet-runtime-composition.js +14 -3
  57. package/dist/pinet-session-formatting.d.ts +26 -2
  58. package/dist/pinet-session-formatting.js +2 -0
  59. package/dist/pinet-tools.d.ts +14 -0
  60. package/dist/pinet-tools.js +355 -50
  61. package/dist/prompts/broker/default.md +3 -3
  62. package/dist/prompts/broker/tmux.md +3 -3
  63. package/dist/runtime-agent-context.d.ts +3 -3
  64. package/dist/runtime-agent-context.js +12 -6
  65. package/dist/runtime-mode.js +4 -0
  66. package/dist/session-ui-runtime.js +3 -1
  67. package/dist/single-player-runtime.d.ts +1 -1
  68. package/dist/single-player-runtime.js +21 -5
  69. package/dist/slack-access.d.ts +10 -2
  70. package/dist/slack-access.js +64 -22
  71. package/dist/slack-block-kit.d.ts +44 -2
  72. package/dist/slack-block-kit.js +5 -5
  73. package/dist/slack-file-access.d.ts +12 -1
  74. package/dist/slack-file-access.js +4 -4
  75. package/dist/slack-message-context.d.ts +9 -2
  76. package/dist/slack-modals.d.ts +4 -1
  77. package/dist/slack-modals.js +5 -5
  78. package/dist/slack-pinet-runtime-adapter.d.ts +1 -0
  79. package/dist/slack-pinet-runtime-adapter.js +10 -0
  80. package/dist/slack-presence.d.ts +5 -4
  81. package/dist/slack-request-runtime.d.ts +3 -2
  82. package/dist/slack-scope-diagnostics.d.ts +27 -2
  83. package/dist/slack-socket-dedup.d.ts +8 -5
  84. package/dist/slack-tools.d.ts +3 -1
  85. package/dist/slack-tools.js +26 -16
  86. package/dist/slack-upload.d.ts +17 -1
  87. package/dist/subtree-broker-runtime.d.ts +83 -2
  88. package/dist/subtree-broker-runtime.js +774 -109
  89. package/dist/tool-output-limits.d.ts +11 -0
  90. package/dist/tool-output-limits.js +20 -0
  91. package/dist/tool-registration-runtime.d.ts +3 -0
  92. package/dist/tool-registration-runtime.js +27 -0
  93. package/package.json +8 -8
@@ -79,6 +79,16 @@ export function createSlackPinetRuntimeAdapterFactory(deps) {
79
79
  onAppHomeOpened: async ({ userId }) => {
80
80
  await deps.onAppHomeOpened(userId, ctx);
81
81
  },
82
+ onSocketOpen: () => deps.setExtStatus(ctx, "ok"),
83
+ onSocketReconnectScheduled: () => deps.setExtStatus(ctx, "reconnecting"),
84
+ onSocketError: (message, source) => {
85
+ if (source === "connection") {
86
+ deps.setExtStatus(ctx, "error");
87
+ }
88
+ else {
89
+ ctx.ui.notify(`Slack event failed: ${message}`, "error");
90
+ }
91
+ },
82
92
  onSlashCommand: deps.onSlashCommand
83
93
  ? (event) => deps.onSlashCommand?.(event, ctx) ?? null
84
94
  : undefined,
@@ -21,11 +21,12 @@ export interface SlackPresenceSnapshot {
21
21
  lastActivity?: number;
22
22
  online?: boolean;
23
23
  }
24
+ export type SlackPresenceTimestampValue = string | number | null | undefined;
24
25
  export interface SlackDndInfoLike {
25
- dnd_enabled?: unknown;
26
- next_dnd_end_ts?: unknown;
27
- snooze_enabled?: unknown;
28
- snooze_endtime?: unknown;
26
+ dnd_enabled?: boolean | null;
27
+ next_dnd_end_ts?: SlackPresenceTimestampValue;
28
+ snooze_enabled?: boolean | null;
29
+ snooze_endtime?: SlackPresenceTimestampValue;
29
30
  }
30
31
  export declare function stripSlackUserReference(value: string): string;
31
32
  export declare function isSlackUserId(value: string): boolean;
@@ -1,5 +1,6 @@
1
- import { type SlackResult } from "./slack-api.js";
2
- export type SlackRequestCall = (method: string, token: string, body?: Record<string, unknown>) => Promise<SlackResult>;
1
+ import { callSlackApi, type SlackResult } from "./slack-api.js";
2
+ export type SlackRequestBody = NonNullable<Parameters<typeof callSlackApi>[2]>;
3
+ export type SlackRequestCall = (method: string, token: string, body?: SlackRequestBody) => Promise<SlackResult>;
3
4
  export interface SlackRequestRuntime {
4
5
  slack: SlackRequestCall;
5
6
  reset: () => void;
@@ -1,7 +1,31 @@
1
+ export type SlackScopeDiagnosticsSurface = "files" | "bookmarks" | "pins";
2
+ export type SlackScopeProbeBody = Record<string, unknown>;
3
+ interface SlackFileInfoScopeProbeBody extends SlackScopeProbeBody {
4
+ file: string;
5
+ }
6
+ interface SlackCompleteUploadScopeProbeFile {
7
+ id: string;
8
+ title: string;
9
+ }
10
+ interface SlackCompleteUploadScopeProbeBody extends SlackScopeProbeBody {
11
+ files: SlackCompleteUploadScopeProbeFile[];
12
+ channel_id: string;
13
+ }
14
+ interface SlackChannelScopeProbeBody extends SlackScopeProbeBody {
15
+ channel_id: string;
16
+ }
17
+ interface SlackBookmarkRemoveScopeProbeBody extends SlackChannelScopeProbeBody {
18
+ bookmark_id: string;
19
+ }
20
+ interface SlackPinnedItemScopeProbeBody extends SlackScopeProbeBody {
21
+ channel: string;
22
+ timestamp?: string;
23
+ }
24
+ export type SlackScopeProbeRequestBody = SlackFileInfoScopeProbeBody | SlackCompleteUploadScopeProbeBody | SlackChannelScopeProbeBody | SlackBookmarkRemoveScopeProbeBody | SlackPinnedItemScopeProbeBody;
1
25
  export type SlackScopeDiagnosticsStatus = "not_checked" | "pending" | "healthy" | "drift" | "unavailable";
2
26
  export interface SlackScopeProbeResult {
3
27
  key: string;
4
- surface: "files" | "bookmarks" | "pins";
28
+ surface: SlackScopeDiagnosticsSurface;
5
29
  method: string;
6
30
  expectedScopes: string[];
7
31
  status: "ok" | "missing" | "unavailable";
@@ -14,7 +38,7 @@ export interface SlackScopeDiagnostics {
14
38
  status: SlackScopeDiagnosticsStatus;
15
39
  checkedAt: string | null;
16
40
  summary: string;
17
- surfaces: Array<"files" | "bookmarks" | "pins">;
41
+ surfaces: SlackScopeDiagnosticsSurface[];
18
42
  missingScopes: string[];
19
43
  results: SlackScopeProbeResult[];
20
44
  error?: string;
@@ -29,3 +53,4 @@ export declare function createPendingSlackScopeDiagnostics(): SlackScopeDiagnost
29
53
  export declare function detectSlackScopeDiagnostics(options: DetectSlackScopeDiagnosticsOptions): Promise<SlackScopeDiagnostics>;
30
54
  export declare function formatSlackScopeDiagnosticsStatus(diagnostics: SlackScopeDiagnostics): string;
31
55
  export declare function buildSlackScopeDriftWarning(diagnostics: SlackScopeDiagnostics): string | null;
56
+ export {};
@@ -1,7 +1,10 @@
1
1
  export declare const SLACK_SOCKET_DELIVERY_DEDUP_TTL_MS: number;
2
2
  export declare const SLACK_SOCKET_DELIVERY_DEDUP_MAX_SIZE = 10000;
3
- export declare function extractSlackEventDedupKey(evt: Record<string, unknown>): string | null;
4
- export declare function extractSlackBlockActionDedupKey(payload: Record<string, unknown>): string | null;
5
- export declare function extractSlackViewSubmissionDedupKey(payload: Record<string, unknown>): string | null;
6
- export declare function extractSlackInteractiveDedupKey(payload: Record<string, unknown>): string | null;
7
- export declare function extractSlackSocketDedupKey(frame: Record<string, unknown>): string | null;
3
+ export type SlackDedupEventPayload = Record<string, unknown>;
4
+ export type SlackInteractiveDedupPayload = Record<string, unknown>;
5
+ export type SlackSocketDedupFrame = Record<string, unknown>;
6
+ export declare function extractSlackEventDedupKey(evt: SlackDedupEventPayload): string | null;
7
+ export declare function extractSlackBlockActionDedupKey(payload: SlackInteractiveDedupPayload): string | null;
8
+ export declare function extractSlackViewSubmissionDedupKey(payload: SlackInteractiveDedupPayload): string | null;
9
+ export declare function extractSlackInteractiveDedupKey(payload: SlackInteractiveDedupPayload): string | null;
10
+ export declare function extractSlackSocketDedupKey(frame: SlackSocketDedupFrame): string | null;
@@ -31,13 +31,14 @@ export interface SlackPinetDeliveryPort {
31
31
  * live broker connectivity. When true, threaded Slack replies MUST route
32
32
  * through the broker; direct Slack fallback is refused even if the broker
33
33
  * is momentarily unavailable, to preserve broker-enforced thread
34
- * ownership (see gugu91/extensions#855).
34
+ * ownership (see gugu91/pinet#855).
35
35
  */
36
36
  isEnabled: () => boolean;
37
37
  isAvailable: () => boolean;
38
38
  sendSlackMessage: (input: SlackPinetDeliveryInput) => Promise<SlackPinetDeliveryResult>;
39
39
  }
40
40
  export interface RegisterSlackToolsDeps {
41
+ additionalSendPromptGuidelines?: string[];
41
42
  getBotToken: () => string;
42
43
  getDefaultChannel: () => string | undefined;
43
44
  getSecurityPrompt: () => string;
@@ -48,6 +49,7 @@ export interface RegisterSlackToolsDeps {
48
49
  getAgentOwnerToken: () => string;
49
50
  getLastDmChannel: () => string | null;
50
51
  updateBadge: () => void;
52
+ markSubtreeInboxIdsDelivered: (inboxIds: number[]) => void;
51
53
  resolveUser: (userId: string) => Promise<string>;
52
54
  threadContext: SlackToolsThreadContextPort;
53
55
  resolveChannel: (nameOrId: string) => Promise<string>;
@@ -1,5 +1,6 @@
1
1
  import os from "node:os";
2
2
  import { Type } from "@sinclair/typebox";
3
+ import { getSubtreeInboxIds } from "./broker-delivery.js";
3
4
  import { buildSlackCanvasCreateRequest, buildSlackCanvasEditRequest, buildSlackCanvasSectionsLookupRequest, extractSlackCanvasCommentsPage, extractSlackChannelCanvasId, normalizeSlackCanvasCommentsLimit, normalizeSlackCanvasCreateKind, normalizeSlackCanvasUpdateMode, pickSlackCanvasSectionId, } from "./canvases.js";
4
5
  import { normalizeSlackBlocksInput, summarizeSlackBlocksForPolicy } from "./slack-block-kit.js";
5
6
  import { encodeSlackModalPrivateMetadata, normalizeSlackModalViewInput } from "./slack-modals.js";
@@ -146,7 +147,7 @@ const SLACK_DISPATCHER_EXAMPLES = {
146
147
  },
147
148
  ],
148
149
  };
149
- function isRecord(value) {
150
+ function isSlackToolObject(value) {
150
151
  return typeof value === "object" && value !== null && !Array.isArray(value);
151
152
  }
152
153
  function normalizeSlackDispatcherActionName(value) {
@@ -292,11 +293,11 @@ function classifySlackDispatcherError(error) {
292
293
  };
293
294
  }
294
295
  function extractToolResponseText(response) {
295
- if (!isRecord(response) || !Array.isArray(response.content))
296
+ if (!isSlackToolObject(response) || !Array.isArray(response.content))
296
297
  return undefined;
297
298
  const lines = response.content
298
299
  .map((item) => {
299
- if (!isRecord(item))
300
+ if (!isSlackToolObject(item))
300
301
  return undefined;
301
302
  return typeof item.text === "string" ? item.text : undefined;
302
303
  })
@@ -304,18 +305,20 @@ function extractToolResponseText(response) {
304
305
  return lines.length > 0 ? lines.join("\n") : undefined;
305
306
  }
306
307
  function extractToolResponseDetails(response) {
307
- return isRecord(response) && "details" in response ? response.details : undefined;
308
+ return isSlackToolObject(response) && "details" in response ? response.details : undefined;
308
309
  }
309
310
  function extractToolResponseFullDetails(response) {
310
- return isRecord(response) && "fullDetails" in response ? response.fullDetails : undefined;
311
+ return isSlackToolObject(response) && "fullDetails" in response
312
+ ? response.fullDetails
313
+ : undefined;
311
314
  }
312
315
  function normalizeSlackOutputOptions(action, args) {
313
- if (args != null && !isRecord(args)) {
316
+ if (args != null && !isSlackToolObject(args)) {
314
317
  throw new Error("slack args must be an object.");
315
318
  }
316
319
  const ownsFormatArg = SLACK_ACTIONS_WITH_FORMAT_ARG.has(action);
317
- const rawResponseFormat = isRecord(args) ? args.response_format : undefined;
318
- const rawFormat = rawResponseFormat ?? (isRecord(args) && !ownsFormatArg ? args.format : undefined);
320
+ const rawResponseFormat = isSlackToolObject(args) ? args.response_format : undefined;
321
+ const rawFormat = rawResponseFormat ?? (isSlackToolObject(args) && !ownsFormatArg ? args.format : undefined);
319
322
  const normalizedFormat = rawFormat == null ? "cli" : String(rawFormat).trim().toLowerCase();
320
323
  if (normalizedFormat !== "cli" && normalizedFormat !== "json") {
321
324
  throw new Error(ownsFormatArg
@@ -323,14 +326,14 @@ function normalizeSlackOutputOptions(action, args) {
323
326
  : 'format must be "cli" or "json" when provided.');
324
327
  }
325
328
  const format = normalizedFormat;
326
- const rawFull = isRecord(args) ? args.full : undefined;
329
+ const rawFull = isSlackToolObject(args) ? args.full : undefined;
327
330
  if (rawFull != null && typeof rawFull !== "boolean") {
328
331
  throw new Error("full must be a boolean when provided.");
329
332
  }
330
333
  return { format, full: rawFull === true };
331
334
  }
332
335
  function getSlackEnvelopeCliText(envelope) {
333
- if (envelope.status === "succeeded" && isRecord(envelope.data)) {
336
+ if (envelope.status === "succeeded" && isSlackToolObject(envelope.data)) {
334
337
  const text = envelope.data.text;
335
338
  if (typeof text === "string" && text.length > 0)
336
339
  return text;
@@ -610,7 +613,7 @@ export function registerSlackTools(pi, deps) {
610
613
  // Slack when the broker is unavailable or the delivery errors: direct
611
614
  // Slack posting bypasses broker thread-ownership enforcement and lets any
612
615
  // worker with a valid bot token take over a Slack thread it does not own.
613
- // See gugu91/extensions#855. Single-player mode (isEnabled === false) and
616
+ // See gugu91/pinet#855. Single-player mode (isEnabled === false) and
614
617
  // top-level channel posts (no threadTs) remain unaffected.
615
618
  if (input.threadTs && pinetDelivery?.isEnabled()) {
616
619
  if (!pinetDelivery.isAvailable()) {
@@ -673,7 +676,7 @@ export function registerSlackTools(pi, deps) {
673
676
  if (input.threadTs)
674
677
  body.thread_ts = input.threadTs;
675
678
  const response = await slack("chat.postMessage", getBotToken(), body);
676
- const message = isRecord(response.message) ? response.message : null;
679
+ const message = isSlackToolObject(response.message) ? response.message : null;
677
680
  const ts = typeof message?.ts === "string" ? message.ts : undefined;
678
681
  return {
679
682
  ...(ts ? { ts } : {}),
@@ -691,10 +694,10 @@ export function registerSlackTools(pi, deps) {
691
694
  slackActionRegistry.set(action, definition);
692
695
  }
693
696
  function buildSlackDispatcherHelpEnvelope(args) {
694
- if (args != null && !isRecord(args)) {
697
+ if (args != null && !isSlackToolObject(args)) {
695
698
  return buildSlackActionFailureEnvelope(new Error("slack help args must be an object."));
696
699
  }
697
- const topic = isRecord(args) && typeof args.topic === "string" ? args.topic : undefined;
700
+ const topic = isSlackToolObject(args) && typeof args.topic === "string" ? args.topic : undefined;
698
701
  if (topic) {
699
702
  const action = normalizeSlackDispatcherActionName(topic);
700
703
  const definition = slackActionRegistry.get(action);
@@ -769,7 +772,7 @@ export function registerSlackTools(pi, deps) {
769
772
  if (action === "help") {
770
773
  return buildSlackDispatcherResponse(buildSlackDispatcherHelpEnvelope(args), output);
771
774
  }
772
- if (!isRecord(args)) {
775
+ if (!isSlackToolObject(args)) {
773
776
  return buildSlackDispatcherResponse(buildSlackActionFailureEnvelope(new Error("slack args must be an object.")), output);
774
777
  }
775
778
  const definition = slackActionRegistry.get(action);
@@ -1247,6 +1250,10 @@ export function registerSlackTools(pi, deps) {
1247
1250
  : "";
1248
1251
  lines.push(`${prefix} (${message.timestamp}): ${message.text}${metadataSuffix}`);
1249
1252
  }
1253
+ const subtreeInboxIds = getSubtreeInboxIds(pending);
1254
+ if (subtreeInboxIds.length > 0) {
1255
+ deps.markSubtreeInboxIdsDelivered(subtreeInboxIds);
1256
+ }
1250
1257
  return {
1251
1258
  content: [
1252
1259
  {
@@ -1404,7 +1411,10 @@ export function registerSlackTools(pi, deps) {
1404
1411
  label: "Slack Send",
1405
1412
  description: "Send a message in a Slack assistant thread.",
1406
1413
  promptSnippet: "Reply in a Slack assistant thread. When you receive a task: ACK briefly, do the work, report blockers immediately, report the outcome when done. Always reply where the task came from.",
1407
- promptGuidelines: buildSlackSendPromptGuidelines(),
1414
+ promptGuidelines: [
1415
+ ...buildSlackSendPromptGuidelines(),
1416
+ ...(deps.additionalSendPromptGuidelines ?? []),
1417
+ ],
1408
1418
  parameters: Type.Object({
1409
1419
  text: Type.String({ description: "Message text (Slack markdown)" }),
1410
1420
  thread_ts: Type.Optional(Type.String({
@@ -17,8 +17,24 @@ export interface PreparedSlackUpload {
17
17
  source: "content" | "path";
18
18
  resolvedPath?: string;
19
19
  }
20
+ export interface SlackUploadMetadataPayload extends Record<string, unknown> {
21
+ filename: string;
22
+ length: number;
23
+ snippet_type?: string;
24
+ }
25
+ export interface SlackCompleteUploadFilePayload {
26
+ id: string;
27
+ title: string;
28
+ }
29
+ export interface SlackCompleteUploadPayload extends Record<string, unknown> {
30
+ files: SlackCompleteUploadFilePayload[];
31
+ channel_id: string;
32
+ thread_ts?: string;
33
+ initial_comment?: string;
34
+ }
35
+ export type SlackUploadApiBody = SlackUploadMetadataPayload | SlackCompleteUploadPayload;
20
36
  export interface SlackUploadDeps {
21
- slack: (method: string, token: string, body?: Record<string, unknown>) => Promise<SlackResult>;
37
+ slack: (method: string, token: string, body?: SlackUploadApiBody) => Promise<SlackResult>;
22
38
  token: string;
23
39
  fetchImpl?: (url: string, init: RequestInit) => Promise<Pick<Response, "ok" | "status" | "statusText" | "text">>;
24
40
  }
@@ -2,21 +2,51 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import type { AgentSessionSummary } from "./broker/types.js";
3
3
  import type { PinetReadOptions, PinetReadResult } from "@pinet/pinet-core/pinet-read-formatting";
4
4
  import { type InboxMessage, type PinetControlCommand, type PinetRemoteControlRequestResult, type SlackBridgeSettings } from "./helpers.js";
5
+ import type { BrokerDB } from "./broker/schema.js";
5
6
  export interface SubtreeBrokerPaths {
6
7
  rootDir: string;
7
8
  socketPath: string;
8
9
  dbPath: string;
9
10
  lockPath: string;
10
11
  }
11
- export interface SubtreeWorkerRecord {
12
- launchId: string;
12
+ export type WorkerRuntimeSpec = {
13
+ runtimeKind: "tmux";
14
+ sessionName: string;
15
+ tmuxSocketPath: string | null;
16
+ } | {
17
+ runtimeKind: "herdr";
13
18
  sessionName: string;
19
+ herdrSession: string;
20
+ herdrConfigDir: string;
21
+ herdrPaneId: string | null;
22
+ herdrShellPid: number | null;
23
+ };
24
+ type HerdrWorkerRuntimeSpec = Extract<WorkerRuntimeSpec, {
25
+ runtimeKind: "herdr";
26
+ }>;
27
+ export type SubtreeWorkerRecord = WorkerRuntimeSpec & {
28
+ launchId: string;
14
29
  repoPath: string;
15
30
  role: string;
16
31
  laneId: string | null;
17
32
  agentId: string | null;
18
33
  startedAt: string;
19
34
  monitorCommand: string;
35
+ };
36
+ interface WorkerRuntimeController<T extends WorkerRuntimeSpec> {
37
+ createLaunchSpec: (sessionName: string) => T;
38
+ monitorCommand: (spec: T) => string;
39
+ launch: (spec: T, launcherPath: string, launchEnv: Record<string, string>, onRuntimeOwned?: (spec: T) => void) => Promise<void>;
40
+ cleanup: (spec: T) => Promise<void>;
41
+ }
42
+ export interface HerdrCommandOptions {
43
+ env: NodeJS.ProcessEnv;
44
+ detached?: boolean;
45
+ }
46
+ export type HerdrCommandRunner = (args: string[], options: HerdrCommandOptions) => Promise<string>;
47
+ export interface HerdrWorkerRuntimeControllerOptions {
48
+ herdrSession: string;
49
+ herdrConfigDir: string;
20
50
  }
21
51
  export interface SubtreeBrokerStatus {
22
52
  active: boolean;
@@ -50,16 +80,34 @@ export interface SubtreeAgentRecord {
50
80
  subtreeRole?: string | null;
51
81
  laneId?: string | null;
52
82
  }
83
+ export interface SubtreeSpawnLaunchHandle {
84
+ launchId: string;
85
+ tmuxSessionName: string;
86
+ socketPath: string;
87
+ state: "launched_unregistered";
88
+ runtimeKind?: WorkerRuntimeSpec["runtimeKind"];
89
+ monitorCommand?: string;
90
+ }
91
+ export declare class SubtreeSpawnRegistrationTimeoutError extends Error {
92
+ readonly handle: SubtreeSpawnLaunchHandle;
93
+ constructor(timeoutMs: number, handle: SubtreeSpawnLaunchHandle);
94
+ }
95
+ export declare class SubtreeSpawnLaunchError extends Error {
96
+ readonly handle: SubtreeSpawnLaunchHandle;
97
+ constructor(error: Error, handle: SubtreeSpawnLaunchHandle, runtimeKind?: WorkerRuntimeSpec["runtimeKind"]);
98
+ }
53
99
  export interface SubtreeSpawnInput {
54
100
  task: string;
55
101
  repo: string;
56
102
  role?: string;
57
103
  laneId?: string;
58
104
  waitForRegistrationMs?: number;
105
+ cleanupHandle?: SubtreeSpawnLaunchHandle;
59
106
  }
60
107
  export interface SubtreeSpawnResult {
61
108
  status: "started";
62
109
  launchId: string;
110
+ runtimeKind: WorkerRuntimeSpec["runtimeKind"];
63
111
  sessionName: string;
64
112
  repoPath: string;
65
113
  role: string;
@@ -85,19 +133,43 @@ export interface SubtreeBrokerRuntimeDeps {
85
133
  getAgentMetadata: (role: "broker" | "worker") => Promise<Record<string, unknown>>;
86
134
  getMeshRoleFromMetadata: (metadata: Record<string, unknown> | undefined, fallback?: "broker" | "worker") => "broker" | "worker";
87
135
  pushInboxMessages: (messages: InboxMessage[]) => void;
136
+ discardQueuedInboxMessages: () => void;
88
137
  updateBadge: () => void;
89
138
  maybeDrainInboxIfIdle: (ctx: ExtensionContext) => boolean;
139
+ deliverSteeringMessage: (text: string, ctx: ExtensionContext) => boolean;
90
140
  requestRemoteControl: (command: PinetControlCommand, ctx: ExtensionContext) => PinetRemoteControlRequestResult;
91
141
  runRemoteControl: (command: PinetControlCommand, ctx: ExtensionContext) => void;
92
142
  formatError: (error: unknown) => string;
143
+ runTmuxCommand?: (args: string[]) => Promise<void>;
144
+ runHerdrCommand?: HerdrCommandRunner;
145
+ }
146
+ /**
147
+ * The authoritative hibernation runtime surface for this process's subtree
148
+ * broker. The subtree broker is the authority that spawns and OWNS its workers
149
+ * and authors their durable runtime specs, so hibernate/wake must resolve the
150
+ * target, read its authz spec, and drive the orchestrator against THIS same
151
+ * authoritative DB end to end. The explicit trust boundary: the operator command
152
+ * may only address workers this subtree broker owns. Null when no subtree broker
153
+ * is running (nothing is command-addressable).
154
+ */
155
+ export interface SubtreeHibernationRuntimeControl {
156
+ /** The single authoritative DB that owns the spawned workers + their specs. */
157
+ db: BrokerDB;
158
+ /** Broker instance id recorded on lifecycle leases; matches startup recovery. */
159
+ brokerInstanceId: string;
160
+ /** Base PINET_* env re-establishing the mesh connection for a woken worker. */
161
+ baseLaunchEnv: Record<string, string>;
93
162
  }
94
163
  export interface SubtreeBrokerRuntime {
95
164
  start: (ctx: ExtensionContext) => Promise<SubtreeBrokerStatus>;
165
+ getHibernationRuntimeControl: () => SubtreeHibernationRuntimeControl | null;
96
166
  stop: (options?: {
97
167
  releaseIdentity?: boolean;
98
168
  stopChildren?: boolean;
99
169
  }) => Promise<void>;
100
170
  getStatus: () => SubtreeBrokerStatus;
171
+ drainInbox: (ctx: ExtensionContext) => void;
172
+ markDelivered: (inboxIds: number[]) => void;
101
173
  readInbox: (options?: PinetReadOptions) => PinetReadResult | null;
102
174
  sendMessage: (target: string, body: string, metadata?: Record<string, unknown>) => Promise<{
103
175
  messageId: number;
@@ -109,4 +181,13 @@ export interface SubtreeBrokerRuntime {
109
181
  isActive: () => boolean;
110
182
  }
111
183
  export declare function buildSubtreeBrokerPaths(stableId: string): SubtreeBrokerPaths;
184
+ export declare function createHerdrWorkerRuntimeController(runHerdrCommand: HerdrCommandRunner, options: HerdrWorkerRuntimeControllerOptions): WorkerRuntimeController<HerdrWorkerRuntimeSpec>;
185
+ export declare function getExtensionEntryPath(): string;
186
+ /**
187
+ * Broker env var NAMES (never values) a spawned — or later WOKEN — worker
188
+ * re-exports to re-establish itself. Single source of truth shared by the
189
+ * ordinary spawn launcher and the Phase B wake path so both stay in lockstep.
190
+ */
191
+ export declare const SUBTREE_INHERITED_ENV_KEYS: string[];
112
192
  export declare function createSubtreeBrokerRuntime(deps: SubtreeBrokerRuntimeDeps): SubtreeBrokerRuntime;
193
+ export {};