@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.
- package/LICENSE +21 -0
- package/README.md +570 -0
- package/dist/activity-log.d.ts +62 -0
- package/dist/activity-log.js +293 -0
- package/dist/agent-completion-runtime.d.ts +17 -0
- package/dist/agent-completion-runtime.js +26 -0
- package/dist/agent-event-runtime.d.ts +13 -0
- package/dist/agent-event-runtime.js +27 -0
- package/dist/agent-prompt-guidance.d.ts +22 -0
- package/dist/agent-prompt-guidance.js +46 -0
- package/dist/broker/adapters/slack.d.ts +74 -0
- package/dist/broker/adapters/slack.js +563 -0
- package/dist/broker/adapters/types.d.ts +1 -0
- package/dist/broker/adapters/types.js +1 -0
- package/dist/broker/agent-messaging.d.ts +1 -0
- package/dist/broker/agent-messaging.js +1 -0
- package/dist/broker/auth.d.ts +1 -0
- package/dist/broker/auth.js +1 -0
- package/dist/broker/client.d.ts +167 -0
- package/dist/broker/client.js +624 -0
- package/dist/broker/control-plane-dashboard.d.ts +98 -0
- package/dist/broker/control-plane-dashboard.js +213 -0
- package/dist/broker/ghost-reaper.d.ts +49 -0
- package/dist/broker/ghost-reaper.js +209 -0
- package/dist/broker/index.d.ts +36 -0
- package/dist/broker/index.js +90 -0
- package/dist/broker/leader.d.ts +1 -0
- package/dist/broker/leader.js +1 -0
- package/dist/broker/maintenance.d.ts +1 -0
- package/dist/broker/maintenance.js +1 -0
- package/dist/broker/message-send.d.ts +1 -0
- package/dist/broker/message-send.js +1 -0
- package/dist/broker/paths.d.ts +1 -0
- package/dist/broker/paths.js +1 -0
- package/dist/broker/raw-tcp-loopback.d.ts +1 -0
- package/dist/broker/raw-tcp-loopback.js +1 -0
- package/dist/broker/router.d.ts +1 -0
- package/dist/broker/router.js +1 -0
- package/dist/broker/schema.d.ts +9 -0
- package/dist/broker/schema.js +63 -0
- package/dist/broker/socket-server.d.ts +120 -0
- package/dist/broker/socket-server.js +1087 -0
- package/dist/broker/types.d.ts +1 -0
- package/dist/broker/types.js +1 -0
- package/dist/broker-delivery.d.ts +10 -0
- package/dist/broker-delivery.js +26 -0
- package/dist/broker-inbound-persistence.d.ts +11 -0
- package/dist/broker-inbound-persistence.js +22 -0
- package/dist/broker-prompt-loader.d.ts +34 -0
- package/dist/broker-prompt-loader.js +185 -0
- package/dist/broker-runtime-access.d.ts +14 -0
- package/dist/broker-runtime-access.js +20 -0
- package/dist/broker-runtime.d.ts +100 -0
- package/dist/broker-runtime.js +533 -0
- package/dist/broker-thread-owner-hints.d.ts +10 -0
- package/dist/broker-thread-owner-hints.js +14 -0
- package/dist/canvases.d.ts +80 -0
- package/dist/canvases.js +221 -0
- package/dist/command-registration-runtime.d.ts +9 -0
- package/dist/command-registration-runtime.js +9 -0
- package/dist/core-tool-guardrails.d.ts +24 -0
- package/dist/core-tool-guardrails.js +66 -0
- package/dist/deploy-manifest.d.ts +27 -0
- package/dist/deploy-manifest.js +197 -0
- package/dist/follower-delivery.d.ts +16 -0
- package/dist/follower-delivery.js +70 -0
- package/dist/follower-runtime.d.ts +61 -0
- package/dist/follower-runtime.js +350 -0
- package/dist/git-metadata.d.ts +35 -0
- package/dist/git-metadata.js +88 -0
- package/dist/guardrails.d.ts +69 -0
- package/dist/guardrails.js +283 -0
- package/dist/helpers.d.ts +550 -0
- package/dist/helpers.js +2711 -0
- package/dist/home-tab.d.ts +27 -0
- package/dist/home-tab.js +242 -0
- package/dist/imessage-tools.d.ts +33 -0
- package/dist/imessage-tools.js +96 -0
- package/dist/inbox-drain-runtime.d.ts +30 -0
- package/dist/inbox-drain-runtime.js +71 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1330 -0
- package/dist/persisted-runtime-state.d.ts +52 -0
- package/dist/persisted-runtime-state.js +98 -0
- package/dist/pinet-activity-formatting.d.ts +20 -0
- package/dist/pinet-activity-formatting.js +43 -0
- package/dist/pinet-agent-status.d.ts +36 -0
- package/dist/pinet-agent-status.js +48 -0
- package/dist/pinet-commands.d.ts +70 -0
- package/dist/pinet-commands.js +402 -0
- package/dist/pinet-confirmation-replies.d.ts +7 -0
- package/dist/pinet-confirmation-replies.js +30 -0
- package/dist/pinet-control-plane-dashboard.d.ts +34 -0
- package/dist/pinet-control-plane-dashboard.js +86 -0
- package/dist/pinet-home-tabs.d.ts +36 -0
- package/dist/pinet-home-tabs.js +101 -0
- package/dist/pinet-maintenance-delivery.d.ts +22 -0
- package/dist/pinet-maintenance-delivery.js +45 -0
- package/dist/pinet-mesh-ops.d.ts +97 -0
- package/dist/pinet-mesh-ops.js +266 -0
- package/dist/pinet-registration-gate.d.ts +14 -0
- package/dist/pinet-registration-gate.js +42 -0
- package/dist/pinet-remote-control-acks.d.ts +16 -0
- package/dist/pinet-remote-control-acks.js +46 -0
- package/dist/pinet-remote-control.d.ts +17 -0
- package/dist/pinet-remote-control.js +74 -0
- package/dist/pinet-runtime-composition.d.ts +27 -0
- package/dist/pinet-runtime-composition.js +20 -0
- package/dist/pinet-tools.d.ts +67 -0
- package/dist/pinet-tools.js +1211 -0
- package/dist/prompts/broker/default.md +41 -0
- package/dist/prompts/broker/tmux.md +49 -0
- package/dist/ralph-loop.d.ts +76 -0
- package/dist/ralph-loop.js +524 -0
- package/dist/reaction-triggers.d.ts +25 -0
- package/dist/reaction-triggers.js +171 -0
- package/dist/repo-tool-guardrails.d.ts +20 -0
- package/dist/repo-tool-guardrails.js +81 -0
- package/dist/runtime-agent-context.d.ts +93 -0
- package/dist/runtime-agent-context.js +302 -0
- package/dist/runtime-mode.d.ts +10 -0
- package/dist/runtime-mode.js +39 -0
- package/dist/scheduled-wakeups.d.ts +1 -0
- package/dist/scheduled-wakeups.js +1 -0
- package/dist/session-ui-runtime.d.ts +20 -0
- package/dist/session-ui-runtime.js +163 -0
- package/dist/single-player-runtime.d.ts +71 -0
- package/dist/single-player-runtime.js +436 -0
- package/dist/skins/cosmere.json +1571 -0
- package/dist/skins/foundation.json +1304 -0
- package/dist/slack-access.d.ts +186 -0
- package/dist/slack-access.js +462 -0
- package/dist/slack-api.d.ts +2 -0
- package/dist/slack-api.js +1 -0
- package/dist/slack-block-kit.d.ts +36 -0
- package/dist/slack-block-kit.js +242 -0
- package/dist/slack-export.d.ts +31 -0
- package/dist/slack-export.js +206 -0
- package/dist/slack-message-context.d.ts +15 -0
- package/dist/slack-message-context.js +207 -0
- package/dist/slack-modals.d.ts +13 -0
- package/dist/slack-modals.js +79 -0
- package/dist/slack-pinet-runtime-adapter.d.ts +16 -0
- package/dist/slack-pinet-runtime-adapter.js +72 -0
- package/dist/slack-presence.d.ts +36 -0
- package/dist/slack-presence.js +89 -0
- package/dist/slack-request-runtime.d.ts +8 -0
- package/dist/slack-request-runtime.js +19 -0
- package/dist/slack-runtime-access.d.ts +28 -0
- package/dist/slack-runtime-access.js +107 -0
- package/dist/slack-scope-diagnostics.d.ts +31 -0
- package/dist/slack-scope-diagnostics.js +235 -0
- package/dist/slack-socket-dedup.d.ts +7 -0
- package/dist/slack-socket-dedup.js +135 -0
- package/dist/slack-thread-status.d.ts +41 -0
- package/dist/slack-thread-status.js +156 -0
- package/dist/slack-tool-policy-runtime.d.ts +36 -0
- package/dist/slack-tool-policy-runtime.js +96 -0
- package/dist/slack-tools.d.ts +52 -0
- package/dist/slack-tools.js +2688 -0
- package/dist/slack-turn-guardrails.d.ts +19 -0
- package/dist/slack-turn-guardrails.js +36 -0
- package/dist/slack-upload.d.ts +49 -0
- package/dist/slack-upload.js +224 -0
- package/dist/task-assignments.d.ts +45 -0
- package/dist/task-assignments.js +527 -0
- package/dist/thread-confirmations.d.ts +22 -0
- package/dist/thread-confirmations.js +139 -0
- package/dist/tool-registration-runtime.d.ts +13 -0
- package/dist/tool-registration-runtime.js +13 -0
- package/dist/ttl-cache.d.ts +47 -0
- package/dist/ttl-cache.js +105 -0
- package/manifest.yaml +57 -0
- package/package.json +60 -0
- package/skills/pinet-skin-creator/SKILL.md +109 -0
- package/skills/pinet-skin-creator/references/descriptor-format.md +99 -0
- package/skills/pinet-skin-creator/references/safety-checklist.md +56 -0
- package/skills/pinet-skin-creator/templates/pinet-skin-descriptor.json +77 -0
- package/skills/slack-bridge/SKILL.md +360 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { BrokerControlPlaneDashboardSnapshot } from "./broker/control-plane-dashboard.js";
|
|
2
|
+
import type { SlackBridgeRuntimeMode } from "./runtime-mode.js";
|
|
3
|
+
export type SlackBlock = Record<string, unknown>;
|
|
4
|
+
export interface SlackHomeView {
|
|
5
|
+
type: "home";
|
|
6
|
+
blocks: SlackBlock[];
|
|
7
|
+
}
|
|
8
|
+
export interface PublishSlackHomeTabInput {
|
|
9
|
+
slack: (method: string, token: string, body?: Record<string, unknown>) => Promise<Record<string, unknown>>;
|
|
10
|
+
token: string;
|
|
11
|
+
userId: string;
|
|
12
|
+
view: SlackHomeView;
|
|
13
|
+
}
|
|
14
|
+
export interface StandalonePinetHomeTabInput {
|
|
15
|
+
agentName: string;
|
|
16
|
+
agentEmoji: string;
|
|
17
|
+
connected: boolean;
|
|
18
|
+
mode: SlackBridgeRuntimeMode;
|
|
19
|
+
activeThreads: number;
|
|
20
|
+
pendingInbox: number;
|
|
21
|
+
currentBranch?: string | null;
|
|
22
|
+
defaultChannel?: string | null;
|
|
23
|
+
}
|
|
24
|
+
export declare function renderBrokerControlPlaneHomeTabView(snapshot: BrokerControlPlaneDashboardSnapshot): SlackHomeView;
|
|
25
|
+
export declare function renderStandalonePinetHomeTabView(input: StandalonePinetHomeTabInput): SlackHomeView;
|
|
26
|
+
export declare function buildSlackHomeTabPublishRequest(userId: string, view: SlackHomeView): Record<string, unknown>;
|
|
27
|
+
export declare function publishSlackHomeTab(input: PublishSlackHomeTabInput): Promise<void>;
|
package/dist/home-tab.js
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
const MAX_SECTION_TEXT_LENGTH = 2800;
|
|
2
|
+
const MAX_ROSTER_ENTRIES = 20;
|
|
3
|
+
function asNonEmptyString(value) {
|
|
4
|
+
const trimmed = value?.trim();
|
|
5
|
+
return trimmed && trimmed.length > 0 ? trimmed : null;
|
|
6
|
+
}
|
|
7
|
+
function formatTimestamp(value) {
|
|
8
|
+
const parsed = Date.parse(value);
|
|
9
|
+
if (Number.isNaN(parsed)) {
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
return new Date(parsed)
|
|
13
|
+
.toISOString()
|
|
14
|
+
.replace("T", " ")
|
|
15
|
+
.replace(".000", "")
|
|
16
|
+
.replace(/:\d\dZ$/, "Z");
|
|
17
|
+
}
|
|
18
|
+
function formatDuration(ms) {
|
|
19
|
+
if (ms == null || !Number.isFinite(ms))
|
|
20
|
+
return "n/a";
|
|
21
|
+
if (ms < 1_000)
|
|
22
|
+
return `${Math.round(ms)}ms`;
|
|
23
|
+
const seconds = ms / 1_000;
|
|
24
|
+
if (seconds < 60)
|
|
25
|
+
return `${seconds.toFixed(seconds < 10 ? 1 : 0)}s`;
|
|
26
|
+
const minutes = seconds / 60;
|
|
27
|
+
return `${minutes.toFixed(minutes < 10 ? 1 : 0)}m`;
|
|
28
|
+
}
|
|
29
|
+
function truncate(value, maxLength) {
|
|
30
|
+
if (value.length <= maxLength)
|
|
31
|
+
return value;
|
|
32
|
+
return `${value.slice(0, Math.max(0, maxLength - 3))}...`;
|
|
33
|
+
}
|
|
34
|
+
function plainText(text) {
|
|
35
|
+
return {
|
|
36
|
+
type: "plain_text",
|
|
37
|
+
text: truncate(text, 150),
|
|
38
|
+
emoji: true,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function mrkdwn(text) {
|
|
42
|
+
return {
|
|
43
|
+
type: "mrkdwn",
|
|
44
|
+
text,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function headerBlock(text) {
|
|
48
|
+
return {
|
|
49
|
+
type: "header",
|
|
50
|
+
text: plainText(text),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function contextBlock(lines) {
|
|
54
|
+
return {
|
|
55
|
+
type: "context",
|
|
56
|
+
elements: lines.map((line) => mrkdwn(line)),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function dividerBlock() {
|
|
60
|
+
return { type: "divider" };
|
|
61
|
+
}
|
|
62
|
+
function sectionBlock(options) {
|
|
63
|
+
return {
|
|
64
|
+
type: "section",
|
|
65
|
+
...(options.text ? { text: mrkdwn(options.text) } : {}),
|
|
66
|
+
...(options.fields && options.fields.length > 0
|
|
67
|
+
? { fields: options.fields.slice(0, 10).map((field) => mrkdwn(field)) }
|
|
68
|
+
: {}),
|
|
69
|
+
...(options.accessory ? { accessory: options.accessory } : {}),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function buildLineSections(lines, emptyState) {
|
|
73
|
+
if (lines.length === 0) {
|
|
74
|
+
return [sectionBlock({ text: emptyState })];
|
|
75
|
+
}
|
|
76
|
+
const chunks = [];
|
|
77
|
+
let current = "";
|
|
78
|
+
for (const line of lines) {
|
|
79
|
+
const candidate = current.length > 0 ? `${current}\n${line}` : line;
|
|
80
|
+
if (candidate.length > MAX_SECTION_TEXT_LENGTH && current.length > 0) {
|
|
81
|
+
chunks.push(current);
|
|
82
|
+
current = line;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (line.length > MAX_SECTION_TEXT_LENGTH) {
|
|
86
|
+
if (current.length > 0) {
|
|
87
|
+
chunks.push(current);
|
|
88
|
+
current = "";
|
|
89
|
+
}
|
|
90
|
+
chunks.push(truncate(line, MAX_SECTION_TEXT_LENGTH));
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
current = candidate;
|
|
94
|
+
}
|
|
95
|
+
if (current.length > 0) {
|
|
96
|
+
chunks.push(current);
|
|
97
|
+
}
|
|
98
|
+
return chunks.map((chunk) => sectionBlock({ text: chunk }));
|
|
99
|
+
}
|
|
100
|
+
function buildRosterBlocks(snapshot) {
|
|
101
|
+
const visibleRoster = snapshot.roster.slice(0, MAX_ROSTER_ENTRIES);
|
|
102
|
+
const blocks = visibleRoster.length > 0
|
|
103
|
+
? visibleRoster.map((row) => sectionBlock({
|
|
104
|
+
text: [
|
|
105
|
+
`*${row.label}* · \`${row.role}\``,
|
|
106
|
+
`Health: ${row.health} · Status: ${row.status}`,
|
|
107
|
+
`Workload: ${row.workload}`,
|
|
108
|
+
`Task: ${truncate(row.taskSummary, 120)}`,
|
|
109
|
+
`Heartbeat: ${row.heartbeat}`,
|
|
110
|
+
`Branch: ${row.branch} · Worktree: ${row.worktree}`,
|
|
111
|
+
].join("\n"),
|
|
112
|
+
}))
|
|
113
|
+
: [sectionBlock({ text: "No agents are currently registered." })];
|
|
114
|
+
if (snapshot.roster.length > visibleRoster.length) {
|
|
115
|
+
blocks.push(contextBlock([
|
|
116
|
+
`_Showing ${visibleRoster.length} of ${snapshot.roster.length} agents on the Home tab._`,
|
|
117
|
+
]));
|
|
118
|
+
}
|
|
119
|
+
return blocks;
|
|
120
|
+
}
|
|
121
|
+
export function renderBrokerControlPlaneHomeTabView(snapshot) {
|
|
122
|
+
const anomalyLines = snapshot.anomalies.length > 0
|
|
123
|
+
? snapshot.anomalies.map((anomaly) => `• ${anomaly}`)
|
|
124
|
+
: ["• Healthy ✅"];
|
|
125
|
+
const maintenanceLines = snapshot.maintenanceAnomalies.map((anomaly) => `• ${anomaly}`);
|
|
126
|
+
const snoozeLine = snapshot.ralphSnooze?.active
|
|
127
|
+
? `active until ${snapshot.ralphSnooze.until ?? "unknown"}${snapshot.ralphSnooze.reason ? ` — ${snapshot.ralphSnooze.reason}` : ""}`
|
|
128
|
+
: `off${snapshot.ralphSnooze ? ` · ${snapshot.ralphSnooze.emptyCycleCount} empty cycles` : ""}`;
|
|
129
|
+
const cycleLines = snapshot.recentCycles.map((cycle) => `• *${cycle.startedAt}* — ${cycle.duration} · ${cycle.agentCount} agents · backlog ${cycle.backlogCount} · ghosts ${cycle.ghostCount} · stuck ${cycle.stuckCount} · follow-up ${cycle.followUpDelivered ? "yes" : "no"}\n ${cycle.anomalySummary}`);
|
|
130
|
+
const blocks = [
|
|
131
|
+
headerBlock("Pinet Broker Control Plane"),
|
|
132
|
+
contextBlock([
|
|
133
|
+
`_Updated ${formatTimestamp(snapshot.cycleStartedAt)} · cycle ${formatDuration(snapshot.cycleDurationMs)}_`,
|
|
134
|
+
]),
|
|
135
|
+
sectionBlock({
|
|
136
|
+
text: "This Home tab mirrors the broker control-plane dashboard with live mesh, task, and RALPH loop status.",
|
|
137
|
+
}),
|
|
138
|
+
dividerBlock(),
|
|
139
|
+
headerBlock("Mesh summary"),
|
|
140
|
+
sectionBlock({
|
|
141
|
+
fields: [
|
|
142
|
+
`*Main checkout*\n\`${snapshot.currentBranch ?? "unknown"}\``,
|
|
143
|
+
`*Agents*\n${snapshot.liveAgents} live / ${snapshot.totalAgents} total`,
|
|
144
|
+
`*Workers*\n${snapshot.workingWorkers} working · ${snapshot.idleWorkers} idle`,
|
|
145
|
+
`*Backlog*\n${snapshot.pendingBacklogCount} pending · ${snapshot.assignedBacklogCount} assigned`,
|
|
146
|
+
`*RALPH nudges*\n${snapshot.nudgesThisCycle} nudges · ${snapshot.idleDrainCandidates} idle drains`,
|
|
147
|
+
`*RALPH snooze*\n${snoozeLine}`,
|
|
148
|
+
`*Maintenance*\n${snapshot.reapedAgents} reaped · ${snapshot.repairedThreadClaims} repaired claims`,
|
|
149
|
+
],
|
|
150
|
+
}),
|
|
151
|
+
headerBlock("Active anomalies"),
|
|
152
|
+
...buildLineSections(anomalyLines, "Healthy ✅"),
|
|
153
|
+
...(maintenanceLines.length > 0
|
|
154
|
+
? [headerBlock("Maintenance anomalies"), ...buildLineSections(maintenanceLines, "None.")]
|
|
155
|
+
: []),
|
|
156
|
+
headerBlock("Agent roster"),
|
|
157
|
+
...buildRosterBlocks(snapshot),
|
|
158
|
+
headerBlock("Task / PR status"),
|
|
159
|
+
sectionBlock({
|
|
160
|
+
fields: [
|
|
161
|
+
`*Assigned*\n${snapshot.taskCounts.assigned}`,
|
|
162
|
+
`*Branch pushed*\n${snapshot.taskCounts.branchPushed}`,
|
|
163
|
+
`*Open PRs*\n${snapshot.taskCounts.openPrs}`,
|
|
164
|
+
`*Merged PRs*\n${snapshot.taskCounts.mergedPrs}`,
|
|
165
|
+
`*Closed PRs*\n${snapshot.taskCounts.closedPrs}`,
|
|
166
|
+
],
|
|
167
|
+
}),
|
|
168
|
+
headerBlock("Active tasks"),
|
|
169
|
+
...buildLineSections(snapshot.activeTasks.map((task) => `• ${task}`), "No active tracked tasks."),
|
|
170
|
+
headerBlock("Recent outcomes"),
|
|
171
|
+
...buildLineSections(snapshot.recentOutcomes.map((task) => `• ${task}`), "No merged or closed PR outcomes tracked yet."),
|
|
172
|
+
headerBlock("Lane metadata"),
|
|
173
|
+
headerBlock("Active/managed lanes"),
|
|
174
|
+
...buildLineSections(snapshot.activeLanes.map((lane) => `• ${lane}`), "No active managed lanes tracked."),
|
|
175
|
+
headerBlock("Detached/manual-supervision lanes"),
|
|
176
|
+
...buildLineSections(snapshot.detachedLanes.map((lane) => `• ${lane}`), "No detached lanes tracked."),
|
|
177
|
+
headerBlock("Recent RALPH cycles"),
|
|
178
|
+
...buildLineSections(cycleLines, "No recorded RALPH cycles yet."),
|
|
179
|
+
dividerBlock(),
|
|
180
|
+
headerBlock("Quick actions"),
|
|
181
|
+
sectionBlock({
|
|
182
|
+
text: [
|
|
183
|
+
"• Open a DM with Pinet in the *Messages* tab to assign or follow up on work.",
|
|
184
|
+
"• Mention Pinet in a channel thread to route a task into the broker mesh.",
|
|
185
|
+
].join("\n"),
|
|
186
|
+
}),
|
|
187
|
+
];
|
|
188
|
+
return {
|
|
189
|
+
type: "home",
|
|
190
|
+
blocks,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
export function renderStandalonePinetHomeTabView(input) {
|
|
194
|
+
const modeLabel = input.mode;
|
|
195
|
+
const branch = asNonEmptyString(input.currentBranch) ?? "unknown";
|
|
196
|
+
const defaultChannel = asNonEmptyString(input.defaultChannel) ?? "not configured";
|
|
197
|
+
return {
|
|
198
|
+
type: "home",
|
|
199
|
+
blocks: [
|
|
200
|
+
headerBlock("Pinet Home"),
|
|
201
|
+
contextBlock([
|
|
202
|
+
`${input.agentEmoji} *${input.agentName}* · ${input.connected ? "connected" : "disconnected"}`,
|
|
203
|
+
]),
|
|
204
|
+
sectionBlock({
|
|
205
|
+
text: "Pinet is available from Slack’s Home tab. When the broker mesh is running, this surface upgrades into the full control-plane dashboard.",
|
|
206
|
+
}),
|
|
207
|
+
dividerBlock(),
|
|
208
|
+
headerBlock("Current runtime"),
|
|
209
|
+
sectionBlock({
|
|
210
|
+
fields: [
|
|
211
|
+
`*Mode*\n${modeLabel}`,
|
|
212
|
+
`*Connection*\n${input.connected ? "connected" : "disconnected"}`,
|
|
213
|
+
`*Branch*\n\`${branch}\``,
|
|
214
|
+
`*Active threads*\n${input.activeThreads}`,
|
|
215
|
+
`*Pending inbox*\n${input.pendingInbox}`,
|
|
216
|
+
`*Default channel*\n${defaultChannel}`,
|
|
217
|
+
],
|
|
218
|
+
}),
|
|
219
|
+
headerBlock("Getting started"),
|
|
220
|
+
sectionBlock({
|
|
221
|
+
text: [
|
|
222
|
+
"• Open the *Messages* tab and start a conversation with Pinet.",
|
|
223
|
+
"• Mention Pinet in a channel to continue work in-thread.",
|
|
224
|
+
'• Use `runtimeMode: "single"` for local Slack-only mode, or `/pinet start` and `/pinet follow` for mesh runtimes.',
|
|
225
|
+
].join("\n"),
|
|
226
|
+
}),
|
|
227
|
+
],
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
export function buildSlackHomeTabPublishRequest(userId, view) {
|
|
231
|
+
const normalizedUserId = asNonEmptyString(userId);
|
|
232
|
+
if (!normalizedUserId) {
|
|
233
|
+
throw new Error("Home tab publish requires a user ID.");
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
user_id: normalizedUserId,
|
|
237
|
+
view,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
export async function publishSlackHomeTab(input) {
|
|
241
|
+
await input.slack("views.publish", input.token, buildSlackHomeTabPublishRequest(input.userId, input.view));
|
|
242
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { NormalizedMessageContent } from "@pinet/transport-core";
|
|
3
|
+
import type { Broker } from "./broker/index.js";
|
|
4
|
+
export interface IMessageToolSendInput {
|
|
5
|
+
threadId: string;
|
|
6
|
+
body: string;
|
|
7
|
+
source: "imessage";
|
|
8
|
+
channel: string;
|
|
9
|
+
content: NormalizedMessageContent;
|
|
10
|
+
agentName: string;
|
|
11
|
+
agentEmoji: string;
|
|
12
|
+
agentOwnerToken: string;
|
|
13
|
+
metadata: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
export interface IMessageToolSendResult {
|
|
16
|
+
adapter: string;
|
|
17
|
+
messageId: number;
|
|
18
|
+
}
|
|
19
|
+
export interface RegisterIMessageToolsDeps {
|
|
20
|
+
pinetEnabled: () => boolean;
|
|
21
|
+
brokerRole: () => "broker" | "follower" | null;
|
|
22
|
+
requireToolPolicy: (toolName: string, threadTs: string | undefined, action: string) => void;
|
|
23
|
+
getActiveBroker: () => Broker | null;
|
|
24
|
+
getActiveBrokerSelfId: () => string | null;
|
|
25
|
+
sendFollowerIMessage: (input: IMessageToolSendInput) => Promise<IMessageToolSendResult>;
|
|
26
|
+
getAgentIdentity: () => {
|
|
27
|
+
name: string;
|
|
28
|
+
emoji: string;
|
|
29
|
+
ownerToken: string;
|
|
30
|
+
};
|
|
31
|
+
trackOwnedThread: (threadId: string, channel: string, source: "imessage") => void;
|
|
32
|
+
}
|
|
33
|
+
export declare function registerIMessageTools(pi: ExtensionAPI, deps: RegisterIMessageToolsDeps): void;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { getDefaultIMessageThreadId, normalizeIMessageRecipient } from "@pinet/imessage-bridge";
|
|
3
|
+
import { sendBrokerMessage } from "./broker/message-send.js";
|
|
4
|
+
export function registerIMessageTools(pi, deps) {
|
|
5
|
+
pi.registerTool({
|
|
6
|
+
name: "imessage_send",
|
|
7
|
+
label: "iMessage Send",
|
|
8
|
+
description: "Send a message through the local send-first iMessage adapter on the active broker.",
|
|
9
|
+
promptSnippet: "Send a message through the local send-first iMessage adapter on the active Pinet broker. Use when a task needs a narrow macOS iMessage send path.",
|
|
10
|
+
parameters: Type.Object({
|
|
11
|
+
to: Type.String({
|
|
12
|
+
description: "Recipient handle, phone number, email, or local chat identifier",
|
|
13
|
+
}),
|
|
14
|
+
text: Type.String({ description: "Message body" }),
|
|
15
|
+
thread_id: Type.Optional(Type.String({
|
|
16
|
+
description: "Optional transport thread id. Defaults to a stable iMessage thread id derived from the recipient.",
|
|
17
|
+
})),
|
|
18
|
+
}),
|
|
19
|
+
async execute(_id, params) {
|
|
20
|
+
deps.requireToolPolicy("imessage_send", undefined, `to=${params.to} | thread_id=${params.thread_id ?? ""} | text=${params.text}`);
|
|
21
|
+
if (!deps.pinetEnabled()) {
|
|
22
|
+
throw new Error("Pinet is not running. Use /pinet start or /pinet follow first.");
|
|
23
|
+
}
|
|
24
|
+
const recipient = normalizeIMessageRecipient(params.to);
|
|
25
|
+
const text = params.text.trim();
|
|
26
|
+
if (!text) {
|
|
27
|
+
throw new Error("text is required");
|
|
28
|
+
}
|
|
29
|
+
const threadId = params.thread_id?.trim() || getDefaultIMessageThreadId(recipient);
|
|
30
|
+
const { name, emoji, ownerToken } = deps.getAgentIdentity();
|
|
31
|
+
const request = {
|
|
32
|
+
threadId,
|
|
33
|
+
body: text,
|
|
34
|
+
source: "imessage",
|
|
35
|
+
channel: recipient,
|
|
36
|
+
content: { text },
|
|
37
|
+
agentName: name,
|
|
38
|
+
agentEmoji: emoji,
|
|
39
|
+
agentOwnerToken: ownerToken,
|
|
40
|
+
metadata: { recipient },
|
|
41
|
+
};
|
|
42
|
+
let result;
|
|
43
|
+
if (deps.brokerRole() === "broker") {
|
|
44
|
+
const broker = deps.getActiveBroker();
|
|
45
|
+
const selfId = deps.getActiveBrokerSelfId();
|
|
46
|
+
if (!broker || !selfId) {
|
|
47
|
+
throw new Error("Broker agent identity is unavailable.");
|
|
48
|
+
}
|
|
49
|
+
if (!broker.adapters.some((candidate) => candidate.name === "imessage")) {
|
|
50
|
+
throw new Error("iMessage adapter is not enabled or not ready on the active broker. Set slack-bridge.imessage.enabled: true and restart /pinet start.");
|
|
51
|
+
}
|
|
52
|
+
const brokerResult = await sendBrokerMessage({
|
|
53
|
+
db: broker.db,
|
|
54
|
+
adapters: broker.adapters,
|
|
55
|
+
}, {
|
|
56
|
+
threadId,
|
|
57
|
+
body: text,
|
|
58
|
+
senderAgentId: selfId,
|
|
59
|
+
source: request.source,
|
|
60
|
+
channel: request.channel,
|
|
61
|
+
content: request.content,
|
|
62
|
+
agentName: request.agentName,
|
|
63
|
+
agentEmoji: request.agentEmoji,
|
|
64
|
+
agentOwnerToken: request.agentOwnerToken,
|
|
65
|
+
metadata: request.metadata,
|
|
66
|
+
});
|
|
67
|
+
result = {
|
|
68
|
+
adapter: brokerResult.adapter,
|
|
69
|
+
messageId: brokerResult.message.id,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
else if (deps.brokerRole() === "follower") {
|
|
73
|
+
result = await deps.sendFollowerIMessage(request);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
throw new Error("Pinet is in an unexpected state.");
|
|
77
|
+
}
|
|
78
|
+
deps.trackOwnedThread(threadId, recipient, "imessage");
|
|
79
|
+
return {
|
|
80
|
+
content: [
|
|
81
|
+
{
|
|
82
|
+
type: "text",
|
|
83
|
+
text: `Sent iMessage to ${recipient} (thread_id: ${threadId}).`,
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
details: {
|
|
87
|
+
threadId,
|
|
88
|
+
channel: recipient,
|
|
89
|
+
source: "imessage",
|
|
90
|
+
adapter: result.adapter,
|
|
91
|
+
messageId: result.messageId,
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type FollowerDeliveryState } from "./follower-delivery.js";
|
|
2
|
+
import { type InboxMessage } from "./helpers.js";
|
|
3
|
+
export interface InboxDrainRuntimeDeps {
|
|
4
|
+
sendUserMessage: (text: string) => void;
|
|
5
|
+
isIdle: () => boolean;
|
|
6
|
+
takeInboxMessages: (maxMessages?: number) => InboxMessage[];
|
|
7
|
+
restoreInboxMessages: (messages: InboxMessage[]) => void;
|
|
8
|
+
updateBadge: () => void;
|
|
9
|
+
reportStatus: (status: "working" | "idle") => Promise<void>;
|
|
10
|
+
userNames: {
|
|
11
|
+
get: (key: string) => string | undefined;
|
|
12
|
+
};
|
|
13
|
+
getSecurityPrompt: () => string;
|
|
14
|
+
deliverTrackedSlackFollowUpMessage: (options: {
|
|
15
|
+
prompt: string;
|
|
16
|
+
messages: Pick<InboxMessage, "threadTs">[];
|
|
17
|
+
}) => boolean;
|
|
18
|
+
getBrokerRole: () => "broker" | "follower" | null;
|
|
19
|
+
hasFollowerClient: () => boolean;
|
|
20
|
+
flushFollowerDeliveredAcks: () => Promise<void>;
|
|
21
|
+
markBrokerInboxIdsDelivered: (inboxIds: number[]) => void;
|
|
22
|
+
getFollowerDeliveryState: () => FollowerDeliveryState;
|
|
23
|
+
maxMessagesPerDrain?: number;
|
|
24
|
+
}
|
|
25
|
+
export interface InboxDrainRuntime {
|
|
26
|
+
deliverFollowUpMessage: (text: string) => boolean;
|
|
27
|
+
flushDeliveredFollowerAcks: () => Promise<void>;
|
|
28
|
+
drainInbox: () => void;
|
|
29
|
+
}
|
|
30
|
+
export declare function createInboxDrainRuntime(deps: InboxDrainRuntimeDeps): InboxDrainRuntime;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { getBrokerInboxIds } from "./broker-delivery.js";
|
|
2
|
+
import { markFollowerInboxIdsDelivered } from "./follower-delivery.js";
|
|
3
|
+
import { formatInboxMessages } from "./helpers.js";
|
|
4
|
+
const DEFAULT_MAX_MESSAGES_PER_DRAIN = 5;
|
|
5
|
+
export function createInboxDrainRuntime(deps) {
|
|
6
|
+
function deliverFollowUpMessage(text) {
|
|
7
|
+
if (!deps.isIdle()) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
deps.sendUserMessage(text);
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function flushDeliveredFollowerAcks() {
|
|
19
|
+
if (deps.getBrokerRole() !== "follower" || !deps.hasFollowerClient()) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
await deps.flushFollowerDeliveredAcks();
|
|
23
|
+
}
|
|
24
|
+
function drainInbox() {
|
|
25
|
+
if (!deps.isIdle()) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const maxMessages = deps.maxMessagesPerDrain ?? DEFAULT_MAX_MESSAGES_PER_DRAIN;
|
|
29
|
+
const pending = deps.takeInboxMessages(maxMessages);
|
|
30
|
+
if (pending.length === 0) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const brokerInboxIds = getBrokerInboxIds(pending);
|
|
34
|
+
deps.updateBadge();
|
|
35
|
+
void deps.reportStatus("working").catch(() => {
|
|
36
|
+
/* best effort */
|
|
37
|
+
});
|
|
38
|
+
let prompt = formatInboxMessages(pending, deps.userNames);
|
|
39
|
+
const securityPrompt = deps.getSecurityPrompt();
|
|
40
|
+
if (securityPrompt) {
|
|
41
|
+
prompt = `${securityPrompt}\n\n${prompt}`;
|
|
42
|
+
}
|
|
43
|
+
if (deps.deliverTrackedSlackFollowUpMessage({
|
|
44
|
+
prompt,
|
|
45
|
+
messages: pending,
|
|
46
|
+
})) {
|
|
47
|
+
if (brokerInboxIds.length > 0) {
|
|
48
|
+
if (deps.getBrokerRole() === "follower") {
|
|
49
|
+
markFollowerInboxIdsDelivered(deps.getFollowerDeliveryState(), brokerInboxIds);
|
|
50
|
+
void flushDeliveredFollowerAcks();
|
|
51
|
+
}
|
|
52
|
+
else if (deps.getBrokerRole() === "broker") {
|
|
53
|
+
try {
|
|
54
|
+
deps.markBrokerInboxIdsDelivered(brokerInboxIds);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
/* best effort */
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
deps.restoreInboxMessages(pending);
|
|
64
|
+
deps.updateBadge();
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
deliverFollowUpMessage,
|
|
68
|
+
flushDeliveredFollowerAcks,
|
|
69
|
+
drainInbox,
|
|
70
|
+
};
|
|
71
|
+
}
|
package/dist/index.d.ts
ADDED