@pinet/slack-bridge 0.2.4 → 0.2.7

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
package/dist/helpers.js CHANGED
@@ -3,6 +3,7 @@ import * as os from "node:os";
3
3
  import * as path from "node:path";
4
4
  import { classifyPinetMail, formatPinetMailClassLabel, } from "@pinet/broker-core/mail-classification";
5
5
  import { buildCompatibilityInstanceScope, buildCompatibilityWorkspaceScope, buildRuntimeScopeCarrier, } from "@pinet/transport-core";
6
+ import { createAbortError, isAbortError, sleep } from "@pinet/transport-core/async";
6
7
  import { buildPinetReadPointer } from "./broker-inbound-persistence.js";
7
8
  import { matchesToolPattern } from "./guardrails.js";
8
9
  import { getPinetSessionPath, summarizePinetStableId } from "./pinet-session-formatting.js";
@@ -421,6 +422,33 @@ export async function reloadPinetRuntimeSafely(reloader) {
421
422
  throw new Error(`Reload failed: ${reloadMessage}. Restored the previous runtime.`);
422
423
  }
423
424
  }
425
+ export async function reloadPinetRuntimeInPlaceSafely(input) {
426
+ const snapshot = input.snapshotState();
427
+ try {
428
+ input.refreshState();
429
+ await input.validateRefreshedState();
430
+ }
431
+ catch (validationErr) {
432
+ input.restoreState(snapshot);
433
+ throw validationErr;
434
+ }
435
+ try {
436
+ await input.reloadRuntime();
437
+ }
438
+ catch (reloadErr) {
439
+ input.restoreState(snapshot);
440
+ try {
441
+ await input.reloadRuntime();
442
+ }
443
+ catch (rollbackErr) {
444
+ const reloadMessage = reloadErr instanceof Error ? reloadErr.message : String(reloadErr);
445
+ const rollbackMessage = rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr);
446
+ throw new Error(`Reload failed: ${reloadMessage}. Rollback to the previous runtime also failed: ${rollbackMessage}`);
447
+ }
448
+ const reloadMessage = reloadErr instanceof Error ? reloadErr.message : String(reloadErr);
449
+ throw new Error(`Reload failed: ${reloadMessage}. Restored the previous runtime.`);
450
+ }
451
+ }
424
452
  function parsePinetControlEnvelope(value) {
425
453
  if (typeof value !== "object" || value === null)
426
454
  return null;
@@ -471,6 +499,62 @@ export function normalizeOutgoingPinetControlMessage(body, metadata) {
471
499
  },
472
500
  };
473
501
  }
502
+ function normalizePinetSteeringText(value) {
503
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
504
+ }
505
+ function parsePinetSteeringEnvelope(value) {
506
+ if (typeof value !== "object" || value === null)
507
+ return null;
508
+ const record = value;
509
+ if (record.type !== "pinet:steer")
510
+ return null;
511
+ return (normalizePinetSteeringText(record.message) ??
512
+ normalizePinetSteeringText(record.text) ??
513
+ normalizePinetSteeringText(record.body));
514
+ }
515
+ function parseStructuredPinetSteeringMessageFromText(text) {
516
+ const trimmed = text?.trim();
517
+ if (!trimmed)
518
+ return null;
519
+ try {
520
+ return parsePinetSteeringEnvelope(JSON.parse(trimmed));
521
+ }
522
+ catch {
523
+ return null;
524
+ }
525
+ }
526
+ function parseLegacyPinetSteeringMessageFromText(text) {
527
+ const trimmed = text?.trim();
528
+ if (!trimmed?.startsWith("/steer"))
529
+ return null;
530
+ const nextChar = trimmed.charAt("/steer".length);
531
+ if (nextChar && !/\s/.test(nextChar))
532
+ return null;
533
+ return normalizePinetSteeringText(trimmed.slice("/steer".length));
534
+ }
535
+ export function getPinetSteeringMessageFromText(text) {
536
+ return (parseStructuredPinetSteeringMessageFromText(text) ??
537
+ parseLegacyPinetSteeringMessageFromText(text));
538
+ }
539
+ export function buildPinetSteeringMetadata(message) {
540
+ return { type: "pinet:steer", message };
541
+ }
542
+ export function buildPinetSteeringMessage(message) {
543
+ return JSON.stringify(buildPinetSteeringMetadata(message));
544
+ }
545
+ export function normalizeOutgoingPinetSteeringMessage(body, metadata) {
546
+ const message = getPinetSteeringMessageFromText(body);
547
+ if (!message)
548
+ return null;
549
+ return {
550
+ body: buildPinetSteeringMessage(message),
551
+ metadata: {
552
+ ...(metadata ?? {}),
553
+ ...buildPinetSteeringMetadata(message),
554
+ kind: "pinet_steer",
555
+ },
556
+ };
557
+ }
474
558
  function extractPinetSkinStatusVocabulary(value) {
475
559
  const record = asRecord(value);
476
560
  if (!record)
@@ -503,6 +587,23 @@ export function extractPinetControlCommand(message) {
503
587
  // Backward-compatible fallback for structured JSON or exact slash commands sent over a2a flows.
504
588
  return getPinetControlCommandFromText(message.body);
505
589
  }
590
+ export function extractPinetSteeringMessage(message) {
591
+ const metadata = message.metadata ?? {};
592
+ if (metadata.scheduledWakeup === true)
593
+ return null;
594
+ const metadataMessage = parsePinetSteeringEnvelope(metadata) ??
595
+ (metadata.kind === "pinet_steer" || metadata.kind === "pinet_steering"
596
+ ? (normalizePinetSteeringText(metadata.message) ??
597
+ normalizePinetSteeringText(metadata.text) ??
598
+ normalizePinetSteeringText(metadata.body))
599
+ : null);
600
+ if (metadataMessage)
601
+ return metadataMessage;
602
+ if (metadata.reactionTrigger === true && metadata.reactionAction === "steer") {
603
+ return normalizePinetSteeringText(message.body);
604
+ }
605
+ return getPinetSteeringMessageFromText(message.body);
606
+ }
506
607
  // ─── Slack API encoding ──────────────────────────────────
507
608
  export const FORM_METHODS = new Set([
508
609
  "auth.test",
@@ -549,30 +650,9 @@ export function buildSlackRequest(method, token, body) {
549
650
  },
550
651
  };
551
652
  }
552
- export function createAbortError(message = "Operation aborted") {
553
- const error = new Error(message);
554
- error.name = "AbortError";
555
- return error;
556
- }
557
- export function isAbortError(error) {
558
- return error instanceof Error && error.name === "AbortError";
559
- }
653
+ export { createAbortError, isAbortError };
560
654
  export function abortableDelay(ms, signal) {
561
- if (signal.aborted) {
562
- return Promise.reject(createAbortError());
563
- }
564
- return new Promise((resolve, reject) => {
565
- const timer = setTimeout(() => {
566
- signal.removeEventListener("abort", onAbort);
567
- resolve();
568
- }, ms);
569
- function onAbort() {
570
- clearTimeout(timer);
571
- signal.removeEventListener("abort", onAbort);
572
- reject(createAbortError());
573
- }
574
- signal.addEventListener("abort", onAbort, { once: true });
575
- });
655
+ return sleep(ms, { signal });
576
656
  }
577
657
  export function createAbortableOperationTracker() {
578
658
  let aborting = false;
@@ -1266,8 +1346,8 @@ export function buildWorkerPromptGuidelines() {
1266
1346
  "- In PM mode, remain accountable: nominate an implementation lead, delegate implementation, coordinate blockers/status, act as second-pass reviewer, and coordinate merge readiness.",
1267
1347
  "- Before delegating through Pinet, call `pinet action=agents` with the target repo to find a suitable connected worker in the same repo/worktree, then delegate via `pinet action=send`.",
1268
1348
  "- Keep delegation inside the Pinet or Slack thread so ACKs, blockers, status updates, and final results flow back to the original sender.",
1269
- "- Do not start or delegate extension changes unless the request names a GitHub issue/PR with clear maintainer priority/approval; if missing or unclear, ask first.",
1270
- "- When delegating, include the workflow (`ack/work/ask/report`), the task, issue/PR numbers, maintainer approval, repo/branch/worktree setup, important files, acceptance criteria, and where to reply.",
1349
+ "- For work in `gugu91/pinet`, do not start or delegate changes unless the request names a GitHub issue/PR with clear maintainer priority/approval; if missing or unclear, ask first. This gate is specific to `gugu91/pinet`; unrelated repositories, including `tmustier/pi-extensions`, follow their own instructions and normal requester authority.",
1350
+ "- When delegating, include the workflow (`ack/work/ask/report`), the task, repo/branch/worktree setup, important files, acceptance criteria, where to reply, and any issue/PR numbers or maintainer approval required by the target repository's instructions.",
1271
1351
  "- Use `pinet action=lanes` to keep PM-mode or complex coordination lane metadata durable when the broker asks you to track lane state/roles.",
1272
1352
  ];
1273
1353
  }
@@ -1517,8 +1597,39 @@ export function syncFollowerInboxEntries(entries, existingThreads, agentOwner, l
1517
1597
  changed,
1518
1598
  };
1519
1599
  }
1600
+ export function extractPinetSteeringInboxEntry(entry) {
1601
+ if (entry.inboxId == null)
1602
+ return null;
1603
+ const steeringText = extractPinetSteeringMessage({
1604
+ threadId: entry.message.threadId,
1605
+ body: entry.message.body,
1606
+ metadata: entry.message.metadata,
1607
+ });
1608
+ if (!steeringText)
1609
+ return null;
1610
+ return {
1611
+ inboxId: entry.inboxId,
1612
+ message: entry.message,
1613
+ steeringText,
1614
+ };
1615
+ }
1616
+ export function formatPinetSteeringMessage(entry) {
1617
+ const threadTs = entry.message.threadId ?? "";
1618
+ const sender = getPinetSenderLabel(entry.message);
1619
+ const transferSuffix = formatPinetThreadTransferSuffix(entry);
1620
+ const pointer = formatPinetInboxPointer(entry);
1621
+ return [
1622
+ "Pinet steering message:",
1623
+ `[thread ${threadTs}] ${sender}: inbox_id=${entry.inboxId}${transferSuffix} ${pointer}`,
1624
+ "",
1625
+ entry.steeringText,
1626
+ "",
1627
+ "This message was sent with explicit steering semantics. Incorporate it now, even if it interrupts the active turn. Reply via pinet action=send when needed.",
1628
+ ].join("\n");
1629
+ }
1520
1630
  export function syncBrokerInboxEntries(entries) {
1521
1631
  const controlEntries = [];
1632
+ const steeringEntries = [];
1522
1633
  const inboxMessages = [];
1523
1634
  for (const entry of entries) {
1524
1635
  const meta = entry.message.metadata ?? {};
@@ -1536,6 +1647,11 @@ export function syncBrokerInboxEntries(entries) {
1536
1647
  controlEntries.push({ inboxId, command: control });
1537
1648
  continue;
1538
1649
  }
1650
+ const steering = extractPinetSteeringInboxEntry(entry);
1651
+ if (steering) {
1652
+ steeringEntries.push(steering);
1653
+ continue;
1654
+ }
1539
1655
  const scope = extractRuntimeScopeCarrier(meta.scope);
1540
1656
  inboxMessages.push({
1541
1657
  channel: "",
@@ -1550,6 +1666,7 @@ export function syncBrokerInboxEntries(entries) {
1550
1666
  }
1551
1667
  return {
1552
1668
  controlEntries,
1669
+ steeringEntries,
1553
1670
  inboxMessages,
1554
1671
  };
1555
1672
  }
@@ -2030,7 +2147,7 @@ const MAX_PINET_SKIN_PERSONALITY_LENGTH = 260;
2030
2147
  const MAX_PINET_SKIN_PROMPT_GUIDELINE_LENGTH = 460;
2031
2148
  const PINET_BUILT_IN_SKIN_FILES = ["foundation", "cosmere"];
2032
2149
  let cachedBuiltInPinetSkinDescriptors = null;
2033
- function isRecord(value) {
2150
+ function isPinetSkinObject(value) {
2034
2151
  return typeof value === "object" && value !== null && !Array.isArray(value);
2035
2152
  }
2036
2153
  function readStringProperty(value, key) {
@@ -2055,13 +2172,13 @@ function validatePinetSkinTheme(value) {
2055
2172
  return null;
2056
2173
  }
2057
2174
  function validatePinetSkinStatusVocabulary(value) {
2058
- if (!isRecord(value)) {
2175
+ if (!isPinetSkinObject(value)) {
2059
2176
  return null;
2060
2177
  }
2061
2178
  return extractPinetSkinStatusVocabulary(value) ?? {};
2062
2179
  }
2063
2180
  function validateBuiltInPinetSkinCharacter(id, value) {
2064
- if (!isRecord(value)) {
2181
+ if (!isPinetSkinObject(value)) {
2065
2182
  return null;
2066
2183
  }
2067
2184
  const name = readStringProperty(value, "name");
@@ -2075,7 +2192,7 @@ function validateBuiltInPinetSkinCharacter(id, value) {
2075
2192
  return { id, name, ...(aliases.length > 0 ? { aliases } : {}), emoji, persona, style };
2076
2193
  }
2077
2194
  function validateBuiltInPinetSkinRoleDescriptor(value) {
2078
- if (!isRecord(value)) {
2195
+ if (!isPinetSkinObject(value)) {
2079
2196
  return null;
2080
2197
  }
2081
2198
  const characterPool = readStringArrayProperty(value, "characterPool");
@@ -2093,7 +2210,7 @@ function validateBuiltInPinetSkinRoleDescriptor(value) {
2093
2210
  };
2094
2211
  }
2095
2212
  function validateBuiltInPinetSkinDescriptor(value) {
2096
- if (!isRecord(value)) {
2213
+ if (!isPinetSkinObject(value)) {
2097
2214
  throw new Error("Pinet skin descriptor must be an object.");
2098
2215
  }
2099
2216
  const keyValue = readStringProperty(value, "key");
@@ -2105,7 +2222,7 @@ function validateBuiltInPinetSkinDescriptor(value) {
2105
2222
  if (!key || !aliases || !displayName || !intent || !statusVocabulary) {
2106
2223
  throw new Error("Pinet skin descriptor is missing required top-level fields.");
2107
2224
  }
2108
- if (!isRecord(value.roles)) {
2225
+ if (!isPinetSkinObject(value.roles)) {
2109
2226
  throw new Error(`Pinet skin descriptor ${key} must define roles.`);
2110
2227
  }
2111
2228
  const brokerRole = validateBuiltInPinetSkinRoleDescriptor(value.roles.broker);
@@ -2113,7 +2230,7 @@ function validateBuiltInPinetSkinDescriptor(value) {
2113
2230
  if (!brokerRole || !workerRole) {
2114
2231
  throw new Error(`Pinet skin descriptor ${key} must define broker and worker roles.`);
2115
2232
  }
2116
- if (!isRecord(value.characters)) {
2233
+ if (!isPinetSkinObject(value.characters)) {
2117
2234
  throw new Error(`Pinet skin descriptor ${key} must define characters.`);
2118
2235
  }
2119
2236
  const characters = {};
@@ -2704,12 +2821,7 @@ export async function callSlackAPI(method, token, body, options = {}) {
2704
2821
  throw new Error(`Slack ${method}: rate limited after ${maxRetries} retries`);
2705
2822
  }
2706
2823
  const wait = Number(res.headers.get("retry-after") ?? "3");
2707
- if (signal) {
2708
- await abortableDelay(wait * 1000, signal);
2709
- }
2710
- else {
2711
- await new Promise((resolve) => setTimeout(resolve, wait * 1000));
2712
- }
2824
+ await sleep(wait * 1000, { signal });
2713
2825
  return callSlackAPI(method, token, body, { signal, retryCount: retryCount + 1 });
2714
2826
  }
2715
2827
  const data = (await res.json());
@@ -2718,7 +2830,6 @@ export async function callSlackAPI(method, token, body, options = {}) {
2718
2830
  }
2719
2831
  return data;
2720
2832
  }
2721
- // ─── Follower inbox partitioning (#102, #175) ───────────
2722
2833
  export function isRalphNudgeEntry(entry) {
2723
2834
  return entry.message.metadata?.kind === "ralph_loop_nudge";
2724
2835
  }
@@ -0,0 +1,13 @@
1
+ import type { SlackBridgeSettings } from "./helpers.js";
2
+ export interface ResolvedHibernationSettings {
3
+ enabled: boolean;
4
+ mode: "observe" | "manual" | "auto";
5
+ allowedRepos: string[];
6
+ graceMs: number;
7
+ idleDebounceMs: number;
8
+ handshakeTimeoutMs: number;
9
+ wakeLeaseMs: number;
10
+ maxConcurrentWakes: number;
11
+ maxConcurrentWakesPerRepo: number;
12
+ }
13
+ export declare function resolveHibernationSettings(settings: Pick<SlackBridgeSettings, "hibernation">): ResolvedHibernationSettings;
@@ -0,0 +1,14 @@
1
+ export function resolveHibernationSettings(settings) {
2
+ const value = settings.hibernation;
3
+ return {
4
+ enabled: value?.enabled === true,
5
+ mode: value?.mode ?? "observe",
6
+ allowedRepos: [...(value?.allowedRepos ?? [])],
7
+ graceMs: value?.graceMs ?? 60 * 60_000,
8
+ idleDebounceMs: value?.idleDebounceMs ?? 2 * 60_000,
9
+ handshakeTimeoutMs: value?.handshakeTimeoutMs ?? 30_000,
10
+ wakeLeaseMs: value?.wakeLeaseMs ?? 90_000,
11
+ maxConcurrentWakes: value?.maxConcurrentWakes ?? 2,
12
+ maxConcurrentWakesPerRepo: value?.maxConcurrentWakesPerRepo ?? 1,
13
+ };
14
+ }
@@ -1,12 +1,23 @@
1
1
  import type { BrokerControlPlaneDashboardSnapshot } from "./broker/control-plane-dashboard.js";
2
2
  import type { SlackBridgeRuntimeMode } from "./runtime-mode.js";
3
3
  export type SlackBlock = Record<string, unknown>;
4
+ export type SlackSectionAccessory = Record<string, unknown>;
5
+ export type SlackHomeTabPublishResponse = Record<string, unknown>;
6
+ export interface SlackTextObject extends Record<string, unknown> {
7
+ type: "plain_text" | "mrkdwn";
8
+ text: string;
9
+ emoji?: boolean;
10
+ }
4
11
  export interface SlackHomeView {
5
12
  type: "home";
6
13
  blocks: SlackBlock[];
7
14
  }
15
+ export interface SlackHomeTabPublishRequest extends Record<string, unknown> {
16
+ user_id: string;
17
+ view: SlackHomeView;
18
+ }
8
19
  export interface PublishSlackHomeTabInput {
9
- slack: (method: string, token: string, body?: Record<string, unknown>) => Promise<Record<string, unknown>>;
20
+ slack: (method: string, token: string, body?: SlackHomeTabPublishRequest) => Promise<SlackHomeTabPublishResponse>;
10
21
  token: string;
11
22
  userId: string;
12
23
  view: SlackHomeView;
@@ -23,5 +34,5 @@ export interface StandalonePinetHomeTabInput {
23
34
  }
24
35
  export declare function renderBrokerControlPlaneHomeTabView(snapshot: BrokerControlPlaneDashboardSnapshot): SlackHomeView;
25
36
  export declare function renderStandalonePinetHomeTabView(input: StandalonePinetHomeTabInput): SlackHomeView;
26
- export declare function buildSlackHomeTabPublishRequest(userId: string, view: SlackHomeView): Record<string, unknown>;
37
+ export declare function buildSlackHomeTabPublishRequest(userId: string, view: SlackHomeView): SlackHomeTabPublishRequest;
27
38
  export declare function publishSlackHomeTab(input: PublishSlackHomeTabInput): Promise<void>;
@@ -1,6 +1,9 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { NormalizedMessageContent } from "@pinet/transport-core";
3
3
  import type { Broker } from "./broker/index.js";
4
+ export interface IMessageToolMetadata extends Record<string, unknown> {
5
+ recipient: string;
6
+ }
4
7
  export interface IMessageToolSendInput {
5
8
  threadId: string;
6
9
  body: string;
@@ -10,7 +13,7 @@ export interface IMessageToolSendInput {
10
13
  agentName: string;
11
14
  agentEmoji: string;
12
15
  agentOwnerToken: string;
13
- metadata: Record<string, unknown>;
16
+ metadata: IMessageToolMetadata;
14
17
  }
15
18
  export interface IMessageToolSendResult {
16
19
  adapter: string;
@@ -19,6 +19,7 @@ export interface InboxDrainRuntimeDeps {
19
19
  hasFollowerClient: () => boolean;
20
20
  flushFollowerDeliveredAcks: () => Promise<void>;
21
21
  markBrokerInboxIdsDelivered: (inboxIds: number[]) => void;
22
+ markSubtreeInboxIdsDelivered: (inboxIds: number[]) => void;
22
23
  getFollowerDeliveryState: () => FollowerDeliveryState;
23
24
  maxMessagesPerDrain?: number;
24
25
  }
@@ -1,4 +1,4 @@
1
- import { getBrokerInboxIds } from "./broker-delivery.js";
1
+ import { getBrokerInboxIds, getSubtreeInboxIds } from "./broker-delivery.js";
2
2
  import { markFollowerInboxIdsDelivered } from "./follower-delivery.js";
3
3
  import { formatInboxMessages } from "./helpers.js";
4
4
  const DEFAULT_MAX_MESSAGES_PER_DRAIN = 5;
@@ -31,6 +31,7 @@ export function createInboxDrainRuntime(deps) {
31
31
  return;
32
32
  }
33
33
  const brokerInboxIds = getBrokerInboxIds(pending);
34
+ const subtreeInboxIds = getSubtreeInboxIds(pending);
34
35
  deps.updateBadge();
35
36
  void deps.reportStatus("working").catch(() => {
36
37
  /* best effort */
@@ -58,6 +59,9 @@ export function createInboxDrainRuntime(deps) {
58
59
  }
59
60
  }
60
61
  }
62
+ if (subtreeInboxIds.length > 0) {
63
+ deps.markSubtreeInboxIdsDelivered(subtreeInboxIds);
64
+ }
61
65
  return;
62
66
  }
63
67
  deps.restoreInboxMessages(pending);