@pinet/slack-bridge 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +570 -0
  3. package/dist/activity-log.d.ts +62 -0
  4. package/dist/activity-log.js +293 -0
  5. package/dist/agent-completion-runtime.d.ts +17 -0
  6. package/dist/agent-completion-runtime.js +26 -0
  7. package/dist/agent-event-runtime.d.ts +13 -0
  8. package/dist/agent-event-runtime.js +27 -0
  9. package/dist/agent-prompt-guidance.d.ts +22 -0
  10. package/dist/agent-prompt-guidance.js +46 -0
  11. package/dist/broker/adapters/slack.d.ts +74 -0
  12. package/dist/broker/adapters/slack.js +563 -0
  13. package/dist/broker/adapters/types.d.ts +1 -0
  14. package/dist/broker/adapters/types.js +1 -0
  15. package/dist/broker/agent-messaging.d.ts +1 -0
  16. package/dist/broker/agent-messaging.js +1 -0
  17. package/dist/broker/auth.d.ts +1 -0
  18. package/dist/broker/auth.js +1 -0
  19. package/dist/broker/client.d.ts +167 -0
  20. package/dist/broker/client.js +624 -0
  21. package/dist/broker/control-plane-dashboard.d.ts +98 -0
  22. package/dist/broker/control-plane-dashboard.js +213 -0
  23. package/dist/broker/ghost-reaper.d.ts +49 -0
  24. package/dist/broker/ghost-reaper.js +209 -0
  25. package/dist/broker/index.d.ts +36 -0
  26. package/dist/broker/index.js +90 -0
  27. package/dist/broker/leader.d.ts +1 -0
  28. package/dist/broker/leader.js +1 -0
  29. package/dist/broker/maintenance.d.ts +1 -0
  30. package/dist/broker/maintenance.js +1 -0
  31. package/dist/broker/message-send.d.ts +1 -0
  32. package/dist/broker/message-send.js +1 -0
  33. package/dist/broker/paths.d.ts +1 -0
  34. package/dist/broker/paths.js +1 -0
  35. package/dist/broker/raw-tcp-loopback.d.ts +1 -0
  36. package/dist/broker/raw-tcp-loopback.js +1 -0
  37. package/dist/broker/router.d.ts +1 -0
  38. package/dist/broker/router.js +1 -0
  39. package/dist/broker/schema.d.ts +9 -0
  40. package/dist/broker/schema.js +63 -0
  41. package/dist/broker/socket-server.d.ts +120 -0
  42. package/dist/broker/socket-server.js +1087 -0
  43. package/dist/broker/types.d.ts +1 -0
  44. package/dist/broker/types.js +1 -0
  45. package/dist/broker-delivery.d.ts +10 -0
  46. package/dist/broker-delivery.js +26 -0
  47. package/dist/broker-inbound-persistence.d.ts +11 -0
  48. package/dist/broker-inbound-persistence.js +22 -0
  49. package/dist/broker-prompt-loader.d.ts +34 -0
  50. package/dist/broker-prompt-loader.js +185 -0
  51. package/dist/broker-runtime-access.d.ts +14 -0
  52. package/dist/broker-runtime-access.js +20 -0
  53. package/dist/broker-runtime.d.ts +100 -0
  54. package/dist/broker-runtime.js +533 -0
  55. package/dist/broker-thread-owner-hints.d.ts +10 -0
  56. package/dist/broker-thread-owner-hints.js +14 -0
  57. package/dist/canvases.d.ts +80 -0
  58. package/dist/canvases.js +221 -0
  59. package/dist/command-registration-runtime.d.ts +9 -0
  60. package/dist/command-registration-runtime.js +9 -0
  61. package/dist/core-tool-guardrails.d.ts +24 -0
  62. package/dist/core-tool-guardrails.js +66 -0
  63. package/dist/deploy-manifest.d.ts +27 -0
  64. package/dist/deploy-manifest.js +197 -0
  65. package/dist/follower-delivery.d.ts +16 -0
  66. package/dist/follower-delivery.js +70 -0
  67. package/dist/follower-runtime.d.ts +61 -0
  68. package/dist/follower-runtime.js +350 -0
  69. package/dist/git-metadata.d.ts +35 -0
  70. package/dist/git-metadata.js +88 -0
  71. package/dist/guardrails.d.ts +69 -0
  72. package/dist/guardrails.js +283 -0
  73. package/dist/helpers.d.ts +550 -0
  74. package/dist/helpers.js +2711 -0
  75. package/dist/home-tab.d.ts +27 -0
  76. package/dist/home-tab.js +242 -0
  77. package/dist/imessage-tools.d.ts +33 -0
  78. package/dist/imessage-tools.js +96 -0
  79. package/dist/inbox-drain-runtime.d.ts +30 -0
  80. package/dist/inbox-drain-runtime.js +71 -0
  81. package/dist/index.d.ts +2 -0
  82. package/dist/index.js +1330 -0
  83. package/dist/persisted-runtime-state.d.ts +52 -0
  84. package/dist/persisted-runtime-state.js +98 -0
  85. package/dist/pinet-activity-formatting.d.ts +20 -0
  86. package/dist/pinet-activity-formatting.js +43 -0
  87. package/dist/pinet-agent-status.d.ts +36 -0
  88. package/dist/pinet-agent-status.js +48 -0
  89. package/dist/pinet-commands.d.ts +70 -0
  90. package/dist/pinet-commands.js +402 -0
  91. package/dist/pinet-confirmation-replies.d.ts +7 -0
  92. package/dist/pinet-confirmation-replies.js +30 -0
  93. package/dist/pinet-control-plane-dashboard.d.ts +34 -0
  94. package/dist/pinet-control-plane-dashboard.js +86 -0
  95. package/dist/pinet-home-tabs.d.ts +36 -0
  96. package/dist/pinet-home-tabs.js +101 -0
  97. package/dist/pinet-maintenance-delivery.d.ts +22 -0
  98. package/dist/pinet-maintenance-delivery.js +45 -0
  99. package/dist/pinet-mesh-ops.d.ts +97 -0
  100. package/dist/pinet-mesh-ops.js +266 -0
  101. package/dist/pinet-registration-gate.d.ts +14 -0
  102. package/dist/pinet-registration-gate.js +42 -0
  103. package/dist/pinet-remote-control-acks.d.ts +16 -0
  104. package/dist/pinet-remote-control-acks.js +46 -0
  105. package/dist/pinet-remote-control.d.ts +17 -0
  106. package/dist/pinet-remote-control.js +74 -0
  107. package/dist/pinet-runtime-composition.d.ts +27 -0
  108. package/dist/pinet-runtime-composition.js +20 -0
  109. package/dist/pinet-tools.d.ts +67 -0
  110. package/dist/pinet-tools.js +1211 -0
  111. package/dist/prompts/broker/default.md +41 -0
  112. package/dist/prompts/broker/tmux.md +49 -0
  113. package/dist/ralph-loop.d.ts +76 -0
  114. package/dist/ralph-loop.js +524 -0
  115. package/dist/reaction-triggers.d.ts +25 -0
  116. package/dist/reaction-triggers.js +171 -0
  117. package/dist/repo-tool-guardrails.d.ts +20 -0
  118. package/dist/repo-tool-guardrails.js +81 -0
  119. package/dist/runtime-agent-context.d.ts +93 -0
  120. package/dist/runtime-agent-context.js +302 -0
  121. package/dist/runtime-mode.d.ts +10 -0
  122. package/dist/runtime-mode.js +39 -0
  123. package/dist/scheduled-wakeups.d.ts +1 -0
  124. package/dist/scheduled-wakeups.js +1 -0
  125. package/dist/session-ui-runtime.d.ts +20 -0
  126. package/dist/session-ui-runtime.js +163 -0
  127. package/dist/single-player-runtime.d.ts +71 -0
  128. package/dist/single-player-runtime.js +436 -0
  129. package/dist/skins/cosmere.json +1571 -0
  130. package/dist/skins/foundation.json +1304 -0
  131. package/dist/slack-access.d.ts +186 -0
  132. package/dist/slack-access.js +462 -0
  133. package/dist/slack-api.d.ts +2 -0
  134. package/dist/slack-api.js +1 -0
  135. package/dist/slack-block-kit.d.ts +36 -0
  136. package/dist/slack-block-kit.js +242 -0
  137. package/dist/slack-export.d.ts +31 -0
  138. package/dist/slack-export.js +206 -0
  139. package/dist/slack-message-context.d.ts +15 -0
  140. package/dist/slack-message-context.js +207 -0
  141. package/dist/slack-modals.d.ts +13 -0
  142. package/dist/slack-modals.js +79 -0
  143. package/dist/slack-pinet-runtime-adapter.d.ts +16 -0
  144. package/dist/slack-pinet-runtime-adapter.js +72 -0
  145. package/dist/slack-presence.d.ts +36 -0
  146. package/dist/slack-presence.js +89 -0
  147. package/dist/slack-request-runtime.d.ts +8 -0
  148. package/dist/slack-request-runtime.js +19 -0
  149. package/dist/slack-runtime-access.d.ts +28 -0
  150. package/dist/slack-runtime-access.js +107 -0
  151. package/dist/slack-scope-diagnostics.d.ts +31 -0
  152. package/dist/slack-scope-diagnostics.js +235 -0
  153. package/dist/slack-socket-dedup.d.ts +7 -0
  154. package/dist/slack-socket-dedup.js +135 -0
  155. package/dist/slack-thread-status.d.ts +41 -0
  156. package/dist/slack-thread-status.js +156 -0
  157. package/dist/slack-tool-policy-runtime.d.ts +36 -0
  158. package/dist/slack-tool-policy-runtime.js +96 -0
  159. package/dist/slack-tools.d.ts +52 -0
  160. package/dist/slack-tools.js +2688 -0
  161. package/dist/slack-turn-guardrails.d.ts +19 -0
  162. package/dist/slack-turn-guardrails.js +36 -0
  163. package/dist/slack-upload.d.ts +49 -0
  164. package/dist/slack-upload.js +224 -0
  165. package/dist/task-assignments.d.ts +45 -0
  166. package/dist/task-assignments.js +527 -0
  167. package/dist/thread-confirmations.d.ts +22 -0
  168. package/dist/thread-confirmations.js +139 -0
  169. package/dist/tool-registration-runtime.d.ts +13 -0
  170. package/dist/tool-registration-runtime.js +13 -0
  171. package/dist/ttl-cache.d.ts +47 -0
  172. package/dist/ttl-cache.js +105 -0
  173. package/manifest.yaml +57 -0
  174. package/package.json +60 -0
  175. package/skills/pinet-skin-creator/SKILL.md +109 -0
  176. package/skills/pinet-skin-creator/references/descriptor-format.md +99 -0
  177. package/skills/pinet-skin-creator/references/safety-checklist.md +56 -0
  178. package/skills/pinet-skin-creator/templates/pinet-skin-descriptor.json +77 -0
  179. package/skills/slack-bridge/SKILL.md +360 -0
@@ -0,0 +1,402 @@
1
+ import { parseScheduledWakeupDelay } from "@pinet/pinet-core/scheduled-wakeups";
2
+ import { generateAgentName, agentOwnsThread, describeSlackUserAccess, formatFollowerRuntimeDiagnosticHealth, formatFollowerRuntimeDiagnosticNextStep, resolveAllowAllWorkspaceUsers, } from "./helpers.js";
3
+ import { formatRecentActivityLogEntries } from "./activity-log.js";
4
+ import { formatRuntimeGuardrailsPosture } from "./guardrails.js";
5
+ import { formatSlackScopeDiagnosticsStatus, } from "./slack-scope-diagnostics.js";
6
+ const PINET_PRIMARY_COMMANDS = [
7
+ { action: "start", args: "", description: "Start as the mesh broker" },
8
+ { action: "follow", args: "", description: "Connect as a follower worker" },
9
+ { action: "unfollow", args: "", description: "Disconnect from the broker" },
10
+ { action: "reload", args: "<agent>", description: "Ask another agent to reload" },
11
+ { action: "exit", args: "<agent>", description: "Ask another agent to exit" },
12
+ { action: "free", args: "", description: "Mark this agent as idle" },
13
+ { action: "snooze", args: "[duration|off|status]", description: "Quiet empty RALPH cycles" },
14
+ ];
15
+ const PINET_SECONDARY_COMMANDS = [
16
+ { action: "status", args: "", description: "Show Pinet status" },
17
+ { action: "logs", args: "", description: "Show recent broker activity logs" },
18
+ { action: "rename", args: "[name]", description: "Rename this Pinet agent" },
19
+ ];
20
+ // ─── Registration ────────────────────────────────────────
21
+ function abortCurrentTurnBeforeBrokerReload(ctx) {
22
+ if (ctx.isIdle?.() ?? true) {
23
+ return;
24
+ }
25
+ try {
26
+ ctx.abort?.();
27
+ }
28
+ catch {
29
+ /* best effort */
30
+ }
31
+ }
32
+ export function formatPinetCommandHelp() {
33
+ const lines = [
34
+ "Usage: /pinet <action> [args]",
35
+ "",
36
+ "Primary actions:",
37
+ ...PINET_PRIMARY_COMMANDS.map((command) => formatPinetCommandHelpLine(command.action, command.args, command.description)),
38
+ "",
39
+ "Other actions:",
40
+ ...PINET_SECONDARY_COMMANDS.map((command) => formatPinetCommandHelpLine(command.action, command.args, command.description)),
41
+ ];
42
+ return lines.join("\n");
43
+ }
44
+ function formatPinetCommandHelpLine(action, args, description) {
45
+ const command = args ? `/pinet ${action} ${args}` : `/pinet ${action}`;
46
+ return `• ${command} — ${description}`;
47
+ }
48
+ function parsePinetCommandAction(args) {
49
+ const trimmed = args.trim();
50
+ if (!trimmed || trimmed === "?" || trimmed === "-h" || trimmed === "--help") {
51
+ return null;
52
+ }
53
+ const [rawAction = "", ...rest] = trimmed.split(/\s+/);
54
+ const action = normalizePinetCommandAction(rawAction);
55
+ if (!action) {
56
+ return null;
57
+ }
58
+ return {
59
+ action,
60
+ args: rest.join(" ").trim(),
61
+ };
62
+ }
63
+ function normalizePinetCommandAction(rawAction) {
64
+ const normalized = rawAction.trim().replace(/^\//, "").toLowerCase();
65
+ switch (normalized) {
66
+ case "start":
67
+ case "broker":
68
+ return "start";
69
+ case "follow":
70
+ case "worker":
71
+ return "follow";
72
+ case "unfollow":
73
+ case "disconnect":
74
+ return "unfollow";
75
+ case "reload":
76
+ return "reload";
77
+ case "exit":
78
+ return "exit";
79
+ case "free":
80
+ case "idle":
81
+ return "free";
82
+ case "status":
83
+ return "status";
84
+ case "logs":
85
+ case "log":
86
+ return "logs";
87
+ case "rename":
88
+ return "rename";
89
+ case "snooze":
90
+ case "quiet":
91
+ return "snooze";
92
+ case "help":
93
+ return null;
94
+ default:
95
+ return null;
96
+ }
97
+ }
98
+ export async function runPinetCommandAction(deps, action, args, ctx, usageCommand = `/pinet ${action}`) {
99
+ switch (action) {
100
+ case "start":
101
+ await runPinetStart(deps, ctx);
102
+ return;
103
+ case "follow":
104
+ await runPinetFollow(deps, ctx);
105
+ return;
106
+ case "unfollow":
107
+ await runPinetUnfollow(deps, ctx);
108
+ return;
109
+ case "reload":
110
+ await runPinetReload(deps, args, ctx, usageCommand);
111
+ return;
112
+ case "exit":
113
+ await runPinetExit(deps, args, ctx, usageCommand);
114
+ return;
115
+ case "free":
116
+ await runPinetFree(deps, ctx);
117
+ return;
118
+ case "status":
119
+ runPinetStatus(deps, ctx);
120
+ return;
121
+ case "logs":
122
+ runPinetLogs(deps, ctx);
123
+ return;
124
+ case "rename":
125
+ runPinetRename(deps, args, ctx);
126
+ return;
127
+ case "snooze":
128
+ runPinetSnooze(deps, args, ctx);
129
+ return;
130
+ }
131
+ }
132
+ export function registerPinetCommands(pi, deps) {
133
+ pi.registerCommand("pinet", {
134
+ description: "Unified Pinet command surface: start, follow, unfollow, reload, exit, free, snooze, status, logs, rename",
135
+ handler: async (args, ctx) => {
136
+ const parsed = parsePinetCommandAction(args);
137
+ if (!parsed) {
138
+ const trimmed = args.trim();
139
+ const tone = trimmed && !["help", "?", "-h", "--help"].includes(trimmed.toLowerCase())
140
+ ? "warning"
141
+ : "info";
142
+ const prefix = tone === "warning" ? `Unknown Pinet action: ${trimmed}\n\n` : "";
143
+ ctx.ui.notify(`${prefix}${formatPinetCommandHelp()}`, tone);
144
+ return;
145
+ }
146
+ await runPinetCommandAction(deps, parsed.action, parsed.args, ctx);
147
+ },
148
+ });
149
+ }
150
+ async function runPinetStart(deps, ctx) {
151
+ if (deps.pinetRegistrationBlocked()) {
152
+ ctx.ui.notify(deps.getPinetRegistrationBlockReason(), "warning");
153
+ return;
154
+ }
155
+ deps.setExtCtx(ctx);
156
+ if (deps.runtimeMode() === "broker") {
157
+ try {
158
+ abortCurrentTurnBeforeBrokerReload(ctx);
159
+ ctx.ui.notify("Pinet broker already running — reloading current runtime", "info");
160
+ await deps.reloadPinetRuntime(ctx);
161
+ }
162
+ catch (err) {
163
+ ctx.ui.notify(`Pinet broker reload failed: ${errorMsg(err)}`, "error");
164
+ deps.setExtStatus(ctx, "error");
165
+ }
166
+ return;
167
+ }
168
+ try {
169
+ await deps.connectAsBroker(ctx);
170
+ }
171
+ catch (err) {
172
+ ctx.ui.notify(`Pinet broker failed: ${errorMsg(err)}`, "error");
173
+ deps.setExtStatus(ctx, "error");
174
+ }
175
+ }
176
+ async function runPinetFollow(deps, ctx) {
177
+ if (deps.pinetRegistrationBlocked()) {
178
+ ctx.ui.notify(deps.getPinetRegistrationBlockReason(), "warning");
179
+ return;
180
+ }
181
+ if (deps.runtimeMode() === "follower") {
182
+ ctx.ui.notify("Pinet already running (follower)", "info");
183
+ return;
184
+ }
185
+ deps.setExtCtx(ctx);
186
+ try {
187
+ await deps.connectAsFollower(ctx);
188
+ ctx.ui.notify(`${deps.agentEmoji()} ${deps.agentName()} — following broker`, "info");
189
+ }
190
+ catch (err) {
191
+ ctx.ui.notify(`Pinet follow failed: ${errorMsg(err)}`, "error");
192
+ deps.setExtStatus(ctx, "error");
193
+ }
194
+ }
195
+ async function runPinetUnfollow(deps, ctx) {
196
+ if (deps.runtimeMode() !== "follower" || deps.brokerRole() == null) {
197
+ ctx.ui.notify("Pinet is not running as a follower.", "info");
198
+ return;
199
+ }
200
+ if (deps.brokerRole() !== "follower") {
201
+ ctx.ui.notify("Pinet is running as broker; /pinet unfollow only applies to followers.", "warning");
202
+ return;
203
+ }
204
+ const { unregisterError } = await deps.disconnectFollower(ctx);
205
+ if (unregisterError) {
206
+ ctx.ui.notify(`Pinet follower disconnected locally, but broker deregistration failed: ${unregisterError}`, "warning");
207
+ return;
208
+ }
209
+ ctx.ui.notify(`${deps.agentEmoji()} ${deps.agentName()} — disconnected from broker; local session still running`, "info");
210
+ }
211
+ async function runPinetReload(deps, args, ctx, usageCommand) {
212
+ const target = args.trim();
213
+ if (!target) {
214
+ ctx.ui.notify(`Usage: ${usageCommand} <agent-name-or-id>`, "warning");
215
+ return;
216
+ }
217
+ try {
218
+ const result = await deps.sendPinetAgentMessage(target, "/reload");
219
+ ctx.ui.notify(`Sent /reload to ${result.target}`, "info");
220
+ }
221
+ catch (err) {
222
+ ctx.ui.notify(`Pinet reload failed: ${errorMsg(err)}`, "error");
223
+ }
224
+ }
225
+ async function runPinetExit(deps, args, ctx, usageCommand) {
226
+ const target = args.trim();
227
+ if (!target) {
228
+ ctx.ui.notify(`Usage: ${usageCommand} <agent-name-or-id>`, "warning");
229
+ return;
230
+ }
231
+ try {
232
+ const result = await deps.sendPinetAgentMessage(target, "/exit");
233
+ ctx.ui.notify(`Sent /exit to ${result.target}`, "info");
234
+ }
235
+ catch (err) {
236
+ ctx.ui.notify(`Pinet exit failed: ${errorMsg(err)}`, "error");
237
+ }
238
+ }
239
+ function formatDurationMs(ms) {
240
+ if (ms <= 0)
241
+ return "0s";
242
+ const totalSeconds = Math.ceil(ms / 1000);
243
+ const hours = Math.floor(totalSeconds / 3600);
244
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
245
+ const seconds = totalSeconds % 60;
246
+ const parts = [
247
+ hours > 0 ? `${hours}h` : null,
248
+ minutes > 0 ? `${minutes}m` : null,
249
+ seconds > 0 && hours === 0 ? `${seconds}s` : null,
250
+ ].filter((part) => part !== null);
251
+ return parts.join(" ") || "0s";
252
+ }
253
+ function formatRalphSnoozeStatus(status) {
254
+ if (!status) {
255
+ return "RALPH snooze: unavailable outside broker mode";
256
+ }
257
+ if (!status.active) {
258
+ return `RALPH snooze: off (${status.emptyCycleCount} empty cycle${status.emptyCycleCount === 1 ? "" : "s"})`;
259
+ }
260
+ return [
261
+ `RALPH snooze: active for ${formatDurationMs(status.remainingMs)}`,
262
+ `Until: ${status.until ?? "unknown"}`,
263
+ `Source: ${status.source ?? "unknown"}`,
264
+ `Reason: ${status.reason ?? "none"}`,
265
+ `Empty cycles: ${status.emptyCycleCount}`,
266
+ ].join("\n");
267
+ }
268
+ async function runPinetFree(deps, ctx) {
269
+ if (!deps.pinetEnabled()) {
270
+ ctx.ui.notify("Pinet mesh runtime is not active. Use /pinet start or /pinet follow.", "info");
271
+ return;
272
+ }
273
+ try {
274
+ const result = await deps.signalAgentFree(ctx, { requirePinet: true });
275
+ const suffix = result.drainedQueuedInbox
276
+ ? ` Processing ${result.queuedInboxCount} queued inbox item${result.queuedInboxCount === 1 ? "" : "s"} now.`
277
+ : result.queuedInboxCount > 0
278
+ ? ` ${result.queuedInboxCount} queued inbox item${result.queuedInboxCount === 1 ? " remains" : "s remain"}.`
279
+ : "";
280
+ ctx.ui.notify(`Marked ${deps.agentEmoji()} ${deps.agentName()} idle/free for new work.${suffix}`, "info");
281
+ }
282
+ catch (err) {
283
+ ctx.ui.notify(`Pinet free failed: ${errorMsg(err)}`, "error");
284
+ }
285
+ }
286
+ function runPinetSnooze(deps, args, ctx) {
287
+ if (deps.runtimeMode() !== "broker") {
288
+ ctx.ui.notify("RALPH snooze is only available while running as the Pinet broker.", "warning");
289
+ return;
290
+ }
291
+ const trimmed = args.trim();
292
+ const normalized = trimmed.toLowerCase();
293
+ if (!trimmed || normalized === "status") {
294
+ ctx.ui.notify(formatRalphSnoozeStatus(deps.ralphSnoozeStatus?.() ?? null), "info");
295
+ return;
296
+ }
297
+ if (["off", "clear", "wake", "resume", "cancel"].includes(normalized)) {
298
+ if (!deps.clearRalphSnooze) {
299
+ ctx.ui.notify("RALPH snooze is unavailable in this runtime.", "warning");
300
+ return;
301
+ }
302
+ ctx.ui.notify(formatRalphSnoozeStatus(deps.clearRalphSnooze()), "info");
303
+ return;
304
+ }
305
+ const [durationToken = "", ...reasonParts] = trimmed.split(/\s+/);
306
+ const durationMs = parseScheduledWakeupDelay(durationToken);
307
+ if (durationMs == null) {
308
+ ctx.ui.notify("Usage: /pinet snooze <duration|off|status> [reason]. Example: /pinet snooze 30m no work available", "warning");
309
+ return;
310
+ }
311
+ if (!deps.snoozeRalphLoop) {
312
+ ctx.ui.notify("RALPH snooze is unavailable in this runtime.", "warning");
313
+ return;
314
+ }
315
+ const status = deps.snoozeRalphLoop({
316
+ durationMs,
317
+ reason: reasonParts.join(" ").trim() || "manual command",
318
+ });
319
+ ctx.ui.notify(formatRalphSnoozeStatus(status), "info");
320
+ }
321
+ function runPinetStatus(deps, ctx) {
322
+ const mode = deps.runtimeMode();
323
+ const ownedCount = [...deps.threads().values()].filter((t) => agentOwnsThread(t.owner, deps.agentName(), deps.agentAliases(), deps.agentOwnerToken())).length;
324
+ const users = deps.allowedUsers();
325
+ const s = deps.settings();
326
+ const allowlistInfo = describeSlackUserAccess(users, {
327
+ allowAllWorkspaceUsers: resolveAllowAllWorkspaceUsers(s, process.env.SLACK_ALLOW_ALL_WORKSPACE_USERS),
328
+ });
329
+ const defaultChInfo = s.defaultChannel
330
+ ? `Default channel: ${s.defaultChannel}`
331
+ : "Default channel: none";
332
+ const activityLogInfo = s.logChannel
333
+ ? `Activity log: ${s.logChannel} (${s.logLevel ?? "actions"})`
334
+ : "Activity log: disabled";
335
+ const guardrailsInfo = `Guardrails: ${formatRuntimeGuardrailsPosture(s.security ?? {})}`;
336
+ const runtimeDiagnostic = deps.followerRuntimeDiagnostic();
337
+ const runtimeHealthInfo = `Runtime health: ${formatFollowerRuntimeDiagnosticHealth(runtimeDiagnostic)}`;
338
+ const runtimeNextStepInfo = `Next step: ${formatFollowerRuntimeDiagnosticNextStep(runtimeDiagnostic)}`;
339
+ const slackToolHealthInfo = `Slack tool health: ${formatSlackScopeDiagnosticsStatus(deps.slackScopeDiagnostics())}`;
340
+ const lbm = deps.lastBrokerMaintenance();
341
+ const brokerHealthInfo = mode === "broker" && lbm
342
+ ? [
343
+ `Pending backlog: ${lbm.pendingBacklogCount}`,
344
+ `Last maintenance: assigned ${lbm.assignedBacklogCount}, reaped ${lbm.reapedAgentIds.length}, repaired ${lbm.repairedThreadClaims}`,
345
+ ...(lbm.anomalies.length > 0 ? [`Health: ${lbm.anomalies.join("; ")}`] : []),
346
+ ]
347
+ : [];
348
+ const ralphSnoozeInfo = mode === "broker" ? [formatRalphSnoozeStatus(deps.ralphSnoozeStatus?.() ?? null)] : [];
349
+ const brokerHomeTabInfo = mode === "broker"
350
+ ? [
351
+ `Home tab viewers: ${deps.getBrokerControlPlaneHomeTabViewerIds().length}`,
352
+ ...(deps.lastBrokerControlPlaneHomeTabRefreshAt()
353
+ ? [`Home tab refreshed: ${deps.lastBrokerControlPlaneHomeTabRefreshAt()}`]
354
+ : []),
355
+ ...(deps.lastBrokerControlPlaneHomeTabError()
356
+ ? [`Home tab status: ${deps.lastBrokerControlPlaneHomeTabError()}`]
357
+ : []),
358
+ ]
359
+ : [];
360
+ ctx.ui.notify([
361
+ `Mode: ${mode}`,
362
+ `Agent: ${deps.agentEmoji()} ${deps.agentName()}`,
363
+ `Bot: ${deps.botUserId() ?? "unknown"}`,
364
+ `Connection: ${deps.runtimeConnected() ? "connected" : "disconnected"}`,
365
+ runtimeHealthInfo,
366
+ runtimeNextStepInfo,
367
+ `Skin: ${deps.activeSkinTheme() ?? "(legacy/manual)"}`,
368
+ ...(deps.agentPersonality() ? [`Persona: ${deps.agentPersonality()}`] : []),
369
+ `Threads: ${deps.threads().size} (${ownedCount} owned by ${deps.agentName()})`,
370
+ `DM channel: ${deps.lastDmChannel() ?? "none yet"}`,
371
+ allowlistInfo,
372
+ guardrailsInfo,
373
+ defaultChInfo,
374
+ activityLogInfo,
375
+ slackToolHealthInfo,
376
+ ...brokerHealthInfo,
377
+ ...ralphSnoozeInfo,
378
+ ...brokerHomeTabInfo,
379
+ ].join("\n"), "info");
380
+ }
381
+ function runPinetLogs(deps, ctx) {
382
+ const s = deps.settings();
383
+ const channelInfo = s.logChannel ? `${s.logChannel} (${s.logLevel ?? "actions"})` : "disabled";
384
+ ctx.ui.notify([
385
+ `Activity log channel: ${channelInfo}`,
386
+ formatRecentActivityLogEntries(deps.recentActivityLogEntries(10)),
387
+ ].join("\n\n"), s.logChannel ? "info" : "warning");
388
+ }
389
+ function runPinetRename(deps, args, ctx) {
390
+ const newName = args.trim();
391
+ if (!newName) {
392
+ const fresh = generateAgentName(undefined, deps.runtimeMode() === "broker" ? "broker" : "worker");
393
+ deps.applyLocalAgentIdentity(fresh.name, fresh.emoji, deps.agentPersonality());
394
+ }
395
+ else {
396
+ deps.applyLocalAgentIdentity(newName, deps.agentEmoji(), deps.agentPersonality());
397
+ }
398
+ ctx.ui.notify(`${deps.agentEmoji()} Agent renamed to: ${deps.agentName()}`, "info");
399
+ }
400
+ function errorMsg(err) {
401
+ return err instanceof Error ? err.message : String(err);
402
+ }
@@ -0,0 +1,7 @@
1
+ import type { PinetReadResult } from "@pinet/pinet-core/pinet-read-formatting";
2
+ export type ConsumePinetConfirmationReply = (threadTs: string, text: string, options?: {
3
+ receivedAt?: string | number | Date;
4
+ }) => {
5
+ approved: boolean;
6
+ } | null;
7
+ export declare function consumePinetReadConfirmationReplies(result: PinetReadResult, consumeReply: ConsumePinetConfirmationReply): PinetReadResult;
@@ -0,0 +1,30 @@
1
+ export function consumePinetReadConfirmationReplies(result, consumeReply) {
2
+ if (result.markedReadIds.length === 0)
3
+ return result;
4
+ const markedReadIds = new Set(result.markedReadIds);
5
+ return {
6
+ ...result,
7
+ messages: result.messages.map((item) => {
8
+ if (!markedReadIds.has(item.inboxId) ||
9
+ item.message.source !== "slack" ||
10
+ item.message.direction !== "inbound") {
11
+ return item;
12
+ }
13
+ const confirmationResult = consumeReply(item.message.threadId, item.message.body, {
14
+ receivedAt: item.message.createdAt,
15
+ });
16
+ if (confirmationResult === null)
17
+ return item;
18
+ const suffix = confirmationResult.approved
19
+ ? "✅ User approved security confirmation request in this thread."
20
+ : "❌ User denied security confirmation request in this thread.";
21
+ return {
22
+ ...item,
23
+ message: {
24
+ ...item.message,
25
+ body: `${item.message.body}\n\n${suffix}`,
26
+ },
27
+ };
28
+ }),
29
+ };
30
+ }
@@ -0,0 +1,34 @@
1
+ import { type RalphLoopAgentWorkload } from "./helpers.js";
2
+ import type { BrokerMaintenanceResult } from "./broker/maintenance.js";
3
+ import type { RalphSnoozeStatus } from "./ralph-loop.js";
4
+ import type { PinetLaneInfo, TaskAssignmentInfo } from "./broker/types.js";
5
+ import { type BrokerControlPlaneDashboardSnapshot, type BrokerControlPlaneRecentCycle } from "./broker/control-plane-dashboard.js";
6
+ export type PinetControlPlaneDashboardAgentRecord = Omit<RalphLoopAgentWorkload, "pendingInboxCount" | "ownedThreadCount">;
7
+ export interface PinetControlPlaneDashboardMessageRecord {
8
+ id: number;
9
+ body: string;
10
+ }
11
+ export interface PinetControlPlaneDashboardBrokerDbPort {
12
+ getAllAgents: () => PinetControlPlaneDashboardAgentRecord[];
13
+ getPendingInboxCount: (agentId: string) => number;
14
+ getOwnedThreadCount: (agentId: string) => number;
15
+ getBacklogCount: (status: "pending") => number;
16
+ listTaskAssignments: () => TaskAssignmentInfo[];
17
+ listPinetLanes: (options?: {
18
+ includeDone?: boolean;
19
+ }) => PinetLaneInfo[];
20
+ getMessagesByIds: (ids: number[]) => PinetControlPlaneDashboardMessageRecord[];
21
+ getRecentRalphCycles: (limit: number) => BrokerControlPlaneRecentCycle[];
22
+ }
23
+ export interface PinetControlPlaneDashboardDeps {
24
+ getActiveBrokerDb: () => PinetControlPlaneDashboardBrokerDbPort | null;
25
+ getActiveBrokerSelfId: () => string | null;
26
+ heartbeatTimerActive: () => boolean;
27
+ maintenanceTimerActive: () => boolean;
28
+ getLastMaintenance: () => BrokerMaintenanceResult | null;
29
+ getRalphSnoozeStatus?: () => RalphSnoozeStatus | null;
30
+ }
31
+ export interface PinetControlPlaneDashboard {
32
+ buildCurrentBrokerControlPlaneDashboardSnapshot: (cycleStartedAt?: string) => Promise<BrokerControlPlaneDashboardSnapshot | null>;
33
+ }
34
+ export declare function createPinetControlPlaneDashboard(deps: PinetControlPlaneDashboardDeps): PinetControlPlaneDashboard;
@@ -0,0 +1,86 @@
1
+ import * as os from "node:os";
2
+ import { probeGitBranch } from "./git-metadata.js";
3
+ import { DEFAULT_RALPH_LOOP_STUCK_WORKING_THRESHOLD_MS, filterAgentsForMeshVisibility, evaluateRalphLoopCycle, } from "./helpers.js";
4
+ import { DEFAULT_HEARTBEAT_TIMEOUT_MS } from "./broker/socket-server.js";
5
+ import { HEARTBEAT_INTERVAL_MS } from "./broker/client.js";
6
+ import { buildBrokerControlPlaneDashboardSnapshot, } from "./broker/control-plane-dashboard.js";
7
+ import { normalizeTrackedTaskAssignments, resolveTaskAssignments, } from "./task-assignments.js";
8
+ export function createPinetControlPlaneDashboard(deps) {
9
+ async function buildCurrentBrokerControlPlaneDashboardSnapshot(cycleStartedAt = new Date().toISOString()) {
10
+ const db = deps.getActiveBrokerDb();
11
+ if (!db) {
12
+ return null;
13
+ }
14
+ const currentBranch = (await probeGitBranch(process.cwd())) ?? null;
15
+ const nowMs = Date.now();
16
+ const recentGhostWindowMs = DEFAULT_HEARTBEAT_TIMEOUT_MS * 2;
17
+ const workloads = filterAgentsForMeshVisibility(db.getAllAgents(), {
18
+ now: nowMs,
19
+ includeGhosts: true,
20
+ recentDisconnectWindowMs: recentGhostWindowMs,
21
+ }).map((agent) => ({
22
+ ...agent,
23
+ pendingInboxCount: db.getPendingInboxCount(agent.id),
24
+ ownedThreadCount: db.getOwnedThreadCount(agent.id),
25
+ }));
26
+ const pendingBacklogCount = db.getBacklogCount("pending");
27
+ const evaluationOptions = {
28
+ now: nowMs,
29
+ heartbeatTimeoutMs: DEFAULT_HEARTBEAT_TIMEOUT_MS,
30
+ heartbeatIntervalMs: HEARTBEAT_INTERVAL_MS,
31
+ stuckWorkingThresholdMs: DEFAULT_RALPH_LOOP_STUCK_WORKING_THRESHOLD_MS,
32
+ pendingBacklogCount,
33
+ currentBranch,
34
+ brokerHeartbeatActive: deps.heartbeatTimerActive(),
35
+ brokerMaintenanceActive: deps.maintenanceTimerActive(),
36
+ brokerAgentId: deps.getActiveBrokerSelfId() ?? undefined,
37
+ };
38
+ const evaluation = evaluateRalphLoopCycle(workloads, evaluationOptions);
39
+ const rawTrackedAssignments = db.listTaskAssignments();
40
+ const trackedAssignmentSourceIds = [
41
+ ...new Set(rawTrackedAssignments
42
+ .map((assignment) => assignment.sourceMessageId)
43
+ .filter((messageId) => messageId != null)),
44
+ ];
45
+ const trackedAssignments = normalizeTrackedTaskAssignments(rawTrackedAssignments, new Map(db
46
+ .getMessagesByIds(trackedAssignmentSourceIds)
47
+ .map((message) => [message.id, message.body])));
48
+ let projectedAssignments = [];
49
+ if (trackedAssignments.length > 0) {
50
+ const resolvedAssignments = await resolveTaskAssignments(trackedAssignments, process.cwd());
51
+ projectedAssignments = resolvedAssignments.map((assignment) => ({
52
+ ...assignment,
53
+ status: assignment.nextStatus,
54
+ prNumber: assignment.nextPrNumber,
55
+ }));
56
+ }
57
+ const recentRalphCycles = db.getRecentRalphCycles(5).map((cycle) => ({
58
+ startedAt: cycle.startedAt,
59
+ completedAt: cycle.completedAt,
60
+ durationMs: cycle.durationMs,
61
+ ghostAgentIds: cycle.ghostAgentIds,
62
+ stuckAgentIds: cycle.stuckAgentIds,
63
+ anomalies: cycle.anomalies,
64
+ followUpDelivered: cycle.followUpDelivered,
65
+ agentCount: cycle.agentCount,
66
+ backlogCount: cycle.backlogCount,
67
+ }));
68
+ return buildBrokerControlPlaneDashboardSnapshot({
69
+ workloads,
70
+ evaluation,
71
+ evaluationOptions,
72
+ maintenance: deps.getLastMaintenance(),
73
+ assignments: projectedAssignments,
74
+ lanes: db.listPinetLanes({ includeDone: true }),
75
+ recentCycles: recentRalphCycles,
76
+ cycleStartedAt,
77
+ cycleDurationMs: 0,
78
+ currentBranch,
79
+ homedir: os.homedir(),
80
+ snooze: deps.getRalphSnoozeStatus?.() ?? null,
81
+ });
82
+ }
83
+ return {
84
+ buildCurrentBrokerControlPlaneDashboardSnapshot,
85
+ };
86
+ }
@@ -0,0 +1,36 @@
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import type { BrokerControlPlaneDashboardSnapshot } from "./broker/control-plane-dashboard.js";
3
+ import { type PublishSlackHomeTabInput } from "./home-tab.js";
4
+ import type { SlackBridgeRuntimeMode } from "./runtime-mode.js";
5
+ export interface PinetHomeTabsBrokerPort {
6
+ isConnected: () => boolean;
7
+ publishCurrentHomeTabSafely: (userId: string, ctx: ExtensionContext, openedAt?: string) => Promise<boolean>;
8
+ getHomeTabViewerIds: () => string[];
9
+ getLastHomeTabError: () => string | null;
10
+ setLastHomeTabSnapshot: (snapshot: BrokerControlPlaneDashboardSnapshot | null) => void;
11
+ setLastHomeTabRefreshAt: (value: string | null) => void;
12
+ setLastHomeTabError: (value: string | null) => void;
13
+ }
14
+ export interface PinetHomeTabsDeps {
15
+ slack: PublishSlackHomeTabInput["slack"];
16
+ getBotToken: () => string | undefined;
17
+ formatError: (error: unknown) => string;
18
+ getAgentName: () => string;
19
+ getAgentEmoji: () => string;
20
+ getBrokerRole: () => "broker" | "follower" | null;
21
+ getRuntimeMode: () => SlackBridgeRuntimeMode;
22
+ isFollowerConnected: () => boolean;
23
+ isSinglePlayerConnected: () => boolean;
24
+ getActiveThreads: () => number;
25
+ getPendingInboxCount: () => number;
26
+ getDefaultChannel: () => string | null | undefined;
27
+ getBrokerHomeTabs: () => PinetHomeTabsBrokerPort;
28
+ getCurrentBranch?: () => Promise<string | null>;
29
+ }
30
+ export interface PinetHomeTabs {
31
+ refreshBrokerControlPlaneHomeTabs: (ctx: ExtensionContext, snapshot: BrokerControlPlaneDashboardSnapshot, refreshedAt: string, userIds?: string[]) => Promise<void>;
32
+ reportHomeTabPublishFailure: (ctx: ExtensionContext, err: unknown) => void;
33
+ publishCurrentPinetHomeTab: (userId: string, ctx: ExtensionContext, openedAt?: string) => Promise<void>;
34
+ publishCurrentPinetHomeTabSafely: (userId: string, ctx: ExtensionContext, openedAt?: string) => Promise<void>;
35
+ }
36
+ export declare function createPinetHomeTabs(deps: PinetHomeTabsDeps): PinetHomeTabs;