@pinet/slack-bridge 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -32
- package/dist/broker/adapters/slack.d.ts +19 -1
- package/dist/broker/adapters/slack.js +111 -22
- package/dist/broker/client.d.ts +2 -1
- package/dist/broker/client.js +1 -0
- package/dist/broker/socket-server.js +18 -0
- package/dist/deploy-manifest.d.ts +5 -0
- package/dist/deploy-manifest.js +30 -1
- package/dist/follower-runtime.js +5 -1
- package/dist/helpers.d.ts +14 -0
- package/dist/helpers.js +45 -21
- package/dist/index.js +60 -0
- package/dist/pinet-commands.d.ts +6 -1
- package/dist/pinet-commands.js +166 -1
- package/dist/pinet-mesh-ops.d.ts +11 -0
- package/dist/pinet-mesh-ops.js +17 -0
- package/dist/pinet-tools.d.ts +47 -0
- package/dist/pinet-tools.js +506 -38
- package/dist/prompts/broker/tmux.md +2 -2
- package/dist/reaction-triggers.d.ts +1 -0
- package/dist/reaction-triggers.js +26 -15
- package/dist/runtime-agent-context.js +19 -0
- package/dist/runtime-mode.js +7 -1
- package/dist/single-player-runtime.js +22 -26
- package/dist/slack-access.d.ts +11 -0
- package/dist/slack-access.js +30 -0
- package/dist/slack-agents-command.d.ts +19 -0
- package/dist/slack-agents-command.js +90 -0
- package/dist/slack-export.d.ts +1 -1
- package/dist/slack-export.js +6 -4
- package/dist/slack-file-access.d.ts +34 -0
- package/dist/slack-file-access.js +209 -0
- package/dist/slack-message-context.d.ts +0 -1
- package/dist/slack-message-context.js +1 -6
- package/dist/slack-pinet-runtime-adapter.d.ts +4 -2
- package/dist/slack-pinet-runtime-adapter.js +12 -0
- package/dist/slack-tools.d.ts +6 -0
- package/dist/slack-tools.js +290 -36
- package/dist/slack-upload.d.ts +13 -1
- package/dist/slack-upload.js +29 -2
- package/dist/stale-slack-messages.d.ts +12 -0
- package/dist/stale-slack-messages.js +29 -0
- package/dist/subtree-broker-runtime.d.ts +109 -0
- package/dist/subtree-broker-runtime.js +558 -0
- package/manifest.yaml +9 -0
- package/package.json +9 -7
- package/skills/slack-bridge/SKILL.md +60 -1
package/dist/helpers.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export interface SlackBridgeSettings {
|
|
|
23
23
|
ralphSnoozeAfterEmptyCycles?: number;
|
|
24
24
|
ralphSnoozeDurationMs?: number;
|
|
25
25
|
skinTheme?: string;
|
|
26
|
+
slackCommandName?: string;
|
|
27
|
+
slackCommandNames?: string[];
|
|
26
28
|
agentName?: string;
|
|
27
29
|
agentEmoji?: string;
|
|
28
30
|
meshSecret?: string;
|
|
@@ -173,6 +175,12 @@ export interface AgentDisplayInfo {
|
|
|
173
175
|
launchSource?: string;
|
|
174
176
|
tmuxSession?: string;
|
|
175
177
|
brokerManagedAt?: string;
|
|
178
|
+
parentAgentId?: string;
|
|
179
|
+
rootAgentId?: string;
|
|
180
|
+
treeDepth?: number;
|
|
181
|
+
supervisionState?: string;
|
|
182
|
+
subtreeRole?: string;
|
|
183
|
+
laneId?: string;
|
|
176
184
|
skinTheme?: string;
|
|
177
185
|
personality?: string;
|
|
178
186
|
skinStatusVocabulary?: PinetSkinStatusVocabulary;
|
|
@@ -212,6 +220,12 @@ export interface AgentVisibilityInput {
|
|
|
212
220
|
lastActivity?: string | null;
|
|
213
221
|
outboundCount?: number | null;
|
|
214
222
|
pendingInboxCount?: number | null;
|
|
223
|
+
parentAgentId?: string | null;
|
|
224
|
+
rootAgentId?: string | null;
|
|
225
|
+
treeDepth?: number;
|
|
226
|
+
supervisionState?: string;
|
|
227
|
+
subtreeRole?: string | null;
|
|
228
|
+
laneId?: string | null;
|
|
215
229
|
}
|
|
216
230
|
export interface AgentVisibilityOptions {
|
|
217
231
|
now?: number;
|
package/dist/helpers.js
CHANGED
|
@@ -837,8 +837,15 @@ export function buildAgentDisplayInfo(agent, options = {}) {
|
|
|
837
837
|
health = "stale";
|
|
838
838
|
}
|
|
839
839
|
const metadata = asRecord(agent.metadata);
|
|
840
|
+
const hasHierarchyMetadata = Boolean(agent.parentAgentId ||
|
|
841
|
+
agent.rootAgentId ||
|
|
842
|
+
typeof agent.treeDepth === "number" ||
|
|
843
|
+
agent.supervisionState ||
|
|
844
|
+
agent.subtreeRole ||
|
|
845
|
+
agent.laneId);
|
|
840
846
|
const capabilities = extractAgentCapabilities(metadata);
|
|
841
847
|
const capabilityTags = buildAgentCapabilityTags(capabilities);
|
|
848
|
+
const displayMetadata = metadata ?? {};
|
|
842
849
|
const idleSinceMs = parseIsoMs(agent.idleSince);
|
|
843
850
|
const lastActivityMs = parseIsoMs(agent.lastActivity);
|
|
844
851
|
const idleDurationMs = idleSinceMs == null ? null : Math.max(0, nowMs - idleSinceMs);
|
|
@@ -849,31 +856,37 @@ export function buildAgentDisplayInfo(agent, options = {}) {
|
|
|
849
856
|
id: agent.id,
|
|
850
857
|
...(agent.pid != null ? { pid: agent.pid } : {}),
|
|
851
858
|
status: agent.status,
|
|
852
|
-
metadata: metadata
|
|
859
|
+
metadata: metadata || hasHierarchyMetadata
|
|
853
860
|
? {
|
|
854
|
-
cwd: asString(
|
|
855
|
-
branch: asString(
|
|
856
|
-
...(
|
|
857
|
-
...(typeof
|
|
858
|
-
? { workdirDirtyFileCount:
|
|
861
|
+
cwd: asString(displayMetadata.cwd),
|
|
862
|
+
branch: asString(displayMetadata.branch),
|
|
863
|
+
...(displayMetadata.workdirDirty === true ? { workdirDirty: true } : {}),
|
|
864
|
+
...(typeof displayMetadata.workdirDirtyFileCount === "number"
|
|
865
|
+
? { workdirDirtyFileCount: displayMetadata.workdirDirtyFileCount }
|
|
859
866
|
: {}),
|
|
860
|
-
...(
|
|
861
|
-
...(asString(
|
|
862
|
-
? { gitProbedAt: asString(
|
|
867
|
+
...(displayMetadata.gitProbeFailed === true ? { gitProbeFailed: true } : {}),
|
|
868
|
+
...(asString(displayMetadata.gitProbedAt)
|
|
869
|
+
? { gitProbedAt: asString(displayMetadata.gitProbedAt) }
|
|
863
870
|
: {}),
|
|
864
|
-
host: asString(
|
|
865
|
-
repo: asString(
|
|
866
|
-
role: asString(
|
|
867
|
-
brokerManaged:
|
|
868
|
-
brokerManagedBy: asString(
|
|
869
|
-
launchSource: asString(
|
|
870
|
-
tmuxSession: asString(
|
|
871
|
-
brokerManagedAt: asString(
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
...(
|
|
871
|
+
host: asString(displayMetadata.host),
|
|
872
|
+
repo: asString(displayMetadata.repo) ?? capabilities.repo,
|
|
873
|
+
role: asString(displayMetadata.role) ?? capabilities.role,
|
|
874
|
+
brokerManaged: displayMetadata.brokerManaged === true,
|
|
875
|
+
brokerManagedBy: asString(displayMetadata.brokerManagedBy),
|
|
876
|
+
launchSource: asString(displayMetadata.launchSource),
|
|
877
|
+
tmuxSession: asString(displayMetadata.tmuxSession),
|
|
878
|
+
brokerManagedAt: asString(displayMetadata.brokerManagedAt),
|
|
879
|
+
parentAgentId: agent.parentAgentId ?? asString(displayMetadata.parentAgentId),
|
|
880
|
+
rootAgentId: agent.rootAgentId ?? asString(displayMetadata.rootAgentId),
|
|
881
|
+
...(typeof agent.treeDepth === "number" ? { treeDepth: agent.treeDepth } : {}),
|
|
882
|
+
supervisionState: agent.supervisionState,
|
|
883
|
+
subtreeRole: agent.subtreeRole ?? asString(displayMetadata.subtreeRole),
|
|
884
|
+
laneId: agent.laneId ?? asString(displayMetadata.laneId),
|
|
885
|
+
skinTheme: asString(displayMetadata.skinTheme),
|
|
886
|
+
personality: asString(displayMetadata.personality),
|
|
887
|
+
...(extractPinetSkinStatusVocabulary(displayMetadata.skinStatusVocabulary)
|
|
875
888
|
? {
|
|
876
|
-
skinStatusVocabulary: extractPinetSkinStatusVocabulary(
|
|
889
|
+
skinStatusVocabulary: extractPinetSkinStatusVocabulary(displayMetadata.skinStatusVocabulary),
|
|
877
890
|
}
|
|
878
891
|
: {}),
|
|
879
892
|
...(capabilities.scope ? { scope: capabilities.scope } : {}),
|
|
@@ -1729,6 +1742,17 @@ export function formatAgentList(agents, homedir) {
|
|
|
1729
1742
|
const probe = meta.gitProbeFailed ? " [git probe failed]" : "";
|
|
1730
1743
|
line += `\n ${cwd}${branch}${host}${probe}`;
|
|
1731
1744
|
}
|
|
1745
|
+
if (meta?.parentAgentId || meta?.supervisionState === "orphaned") {
|
|
1746
|
+
const hierarchy = [
|
|
1747
|
+
meta.parentAgentId ? `parent=${meta.parentAgentId}` : null,
|
|
1748
|
+
meta.rootAgentId ? `root=${meta.rootAgentId}` : null,
|
|
1749
|
+
typeof meta.treeDepth === "number" ? `depth=${meta.treeDepth}` : null,
|
|
1750
|
+
meta.supervisionState ? `state=${meta.supervisionState}` : null,
|
|
1751
|
+
meta.subtreeRole ? `role=${meta.subtreeRole}` : null,
|
|
1752
|
+
meta.laneId ? `lane=${meta.laneId}` : null,
|
|
1753
|
+
].filter((item) => Boolean(item));
|
|
1754
|
+
line += `\n subtree: ${hierarchy.join(" · ")}`;
|
|
1755
|
+
}
|
|
1732
1756
|
if (meta?.brokerManaged) {
|
|
1733
1757
|
const managed = [
|
|
1734
1758
|
meta.launchSource ? `source=${meta.launchSource}` : "source=broker",
|
package/dist/index.js
CHANGED
|
@@ -37,9 +37,12 @@ import { createAgentPromptGuidance } from "./agent-prompt-guidance.js";
|
|
|
37
37
|
import { createAgentEventRuntime } from "./agent-event-runtime.js";
|
|
38
38
|
import { createSessionUiRuntime } from "./session-ui-runtime.js";
|
|
39
39
|
import { createSlackRequestRuntime } from "./slack-request-runtime.js";
|
|
40
|
+
import { getSlackMessageAgeMs, isStaleSlackMessage } from "./stale-slack-messages.js";
|
|
41
|
+
import { formatSlackAgentsDashboard, formatSlackAgentsUsage, isSlackAgentCommand, isSlackAgentsListCommand, resolveSlackAgentCommandNames, shouldIncludeSlackAgentsGhosts, } from "./slack-agents-command.js";
|
|
40
42
|
import { createPinetRegistrationGate } from "./pinet-registration-gate.js";
|
|
41
43
|
import { createBrokerRuntimeAccess } from "./broker-runtime-access.js";
|
|
42
44
|
import { createInboxDrainRuntime } from "./inbox-drain-runtime.js";
|
|
45
|
+
import { createSubtreeBrokerRuntime } from "./subtree-broker-runtime.js";
|
|
43
46
|
import { createAgentCompletionRuntime } from "./agent-completion-runtime.js";
|
|
44
47
|
import { sendBrokerMessage } from "./broker/message-send.js";
|
|
45
48
|
import { SlackThreadStatusManager } from "./slack-thread-status.js";
|
|
@@ -406,6 +409,23 @@ export default function (pi) {
|
|
|
406
409
|
formatError: msg,
|
|
407
410
|
});
|
|
408
411
|
const { requestRemoteControl, runRemoteControl, resetRemoteControlState } = pinetRemoteControl;
|
|
412
|
+
const subtreeBrokerRuntime = createSubtreeBrokerRuntime({
|
|
413
|
+
cwd: process.cwd(),
|
|
414
|
+
getSettings: () => settings,
|
|
415
|
+
getAgentStableId: () => agentStableId,
|
|
416
|
+
getCentralAgentId: () => brokerClient?.client.getRegisteredIdentity()?.agentId ?? null,
|
|
417
|
+
getAgentIdentity: () => ({ name: agentName, emoji: agentEmoji }),
|
|
418
|
+
getAgentMetadata,
|
|
419
|
+
getMeshRoleFromMetadata: (metadata, fallbackRole) => getMeshRoleFromMetadata(metadata, fallbackRole),
|
|
420
|
+
pushInboxMessages: (messages) => {
|
|
421
|
+
inbox.push(...messages);
|
|
422
|
+
},
|
|
423
|
+
updateBadge,
|
|
424
|
+
maybeDrainInboxIfIdle,
|
|
425
|
+
requestRemoteControl,
|
|
426
|
+
runRemoteControl,
|
|
427
|
+
formatError: msg,
|
|
428
|
+
});
|
|
409
429
|
const pinetActivityFormatting = createPinetActivityFormatting({
|
|
410
430
|
getActiveBrokerDb,
|
|
411
431
|
});
|
|
@@ -491,6 +511,20 @@ export default function (pi) {
|
|
|
491
511
|
const handleBrokerAppHomeOpened = async (userId, ctx) => {
|
|
492
512
|
await pinetHomeTabs.publishCurrentPinetHomeTabSafely(userId, ctx, new Date().toISOString());
|
|
493
513
|
};
|
|
514
|
+
const handleBrokerSlackSlashCommand = async (event) => {
|
|
515
|
+
const commandNames = resolveSlackAgentCommandNames(settings, activeSkinTheme);
|
|
516
|
+
if (!isSlackAgentCommand(event.command, commandNames)) {
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
if (!isSlackAgentsListCommand(event.command, event.text, commandNames)) {
|
|
520
|
+
return formatSlackAgentsUsage(commandNames);
|
|
521
|
+
}
|
|
522
|
+
const snapshot = await buildCurrentBrokerControlPlaneDashboardSnapshot();
|
|
523
|
+
if (!snapshot) {
|
|
524
|
+
return "Pinet broker dashboard data is not available yet. Try again after the broker completes a maintenance cycle.";
|
|
525
|
+
}
|
|
526
|
+
return formatSlackAgentsDashboard(snapshot, shouldIncludeSlackAgentsGhosts(event.text));
|
|
527
|
+
};
|
|
494
528
|
const slackPinetAdapterFactory = createSlackPinetRuntimeAdapterFactory({
|
|
495
529
|
getSettings: () => settings,
|
|
496
530
|
getBotToken: () => botToken,
|
|
@@ -498,6 +532,7 @@ export default function (pi) {
|
|
|
498
532
|
getAllowedUsers: () => allowedUsers,
|
|
499
533
|
shouldAllowAllWorkspaceUsers: () => resolveAllowAllWorkspaceUsers(settings, process.env.SLACK_ALLOW_ALL_WORKSPACE_USERS),
|
|
500
534
|
onAppHomeOpened: handleBrokerAppHomeOpened,
|
|
535
|
+
onSlashCommand: handleBrokerSlackSlashCommand,
|
|
501
536
|
});
|
|
502
537
|
const brokerRuntime = createBrokerRuntime({
|
|
503
538
|
getSettings: () => settings,
|
|
@@ -519,6 +554,11 @@ export default function (pi) {
|
|
|
519
554
|
getMeshRoleFromMetadata: (metadata, fallbackRole) => getMeshRoleFromMetadata(metadata ?? undefined, fallbackRole),
|
|
520
555
|
handleInboundMessage: async ({ message, broker, router, selfId, ctx }) => {
|
|
521
556
|
try {
|
|
557
|
+
if (isStaleSlackMessage(message)) {
|
|
558
|
+
const ageMs = getSlackMessageAgeMs(message);
|
|
559
|
+
console.info(`[slack-bridge] skipped stale Slack inbound message older than 15m: channel=${message.channel} thread=${message.threadId} ts=${message.timestamp} age_ms=${ageMs ?? "unknown"}`);
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
522
562
|
const ownerHint = message.source === "slack" && message.threadId && message.channel
|
|
523
563
|
? await resolveBrokerThreadOwnerHint(message.channel, message.threadId)
|
|
524
564
|
: null;
|
|
@@ -687,6 +727,7 @@ export default function (pi) {
|
|
|
687
727
|
getActiveBrokerSelfId,
|
|
688
728
|
getAgentName: () => agentName,
|
|
689
729
|
getFollowerClient: () => brokerClient?.client ?? null,
|
|
730
|
+
sendSubtreeAgentMessage: (target, body, metadata) => subtreeBrokerRuntime.sendMessage(target, body, metadata),
|
|
690
731
|
formatTrackedAgent,
|
|
691
732
|
logActivity: (entry) => {
|
|
692
733
|
brokerRuntime.logActivity(entry);
|
|
@@ -830,6 +871,10 @@ export default function (pi) {
|
|
|
830
871
|
}, consumeConfirmationReply);
|
|
831
872
|
}
|
|
832
873
|
if (brokerRole === "follower" && brokerClient?.client) {
|
|
874
|
+
const subtreeResult = subtreeBrokerRuntime.readInbox(options);
|
|
875
|
+
if (subtreeResult && (subtreeResult.messages.length > 0 || Boolean(options.threadId))) {
|
|
876
|
+
return consumePinetReadConfirmationReplies(subtreeResult, consumeConfirmationReply);
|
|
877
|
+
}
|
|
833
878
|
const result = await brokerClient.client.readInbox(options);
|
|
834
879
|
return consumePinetReadConfirmationReplies(result, consumeConfirmationReply);
|
|
835
880
|
}
|
|
@@ -872,6 +917,7 @@ export default function (pi) {
|
|
|
872
917
|
}
|
|
873
918
|
async function stopPinetRuntime(ctx, options) {
|
|
874
919
|
flushPersist();
|
|
920
|
+
await subtreeBrokerRuntime.stop({ releaseIdentity: options.releaseIdentity });
|
|
875
921
|
await brokerRuntime.disconnect({ releaseIdentity: options.releaseIdentity });
|
|
876
922
|
if (brokerClient) {
|
|
877
923
|
if (options.releaseIdentity) {
|
|
@@ -980,6 +1026,7 @@ export default function (pi) {
|
|
|
980
1026
|
channel: input.channel,
|
|
981
1027
|
content,
|
|
982
1028
|
...(input.blocks && input.blocks.length > 0 ? { blocks: input.blocks } : {}),
|
|
1029
|
+
...(input.files && input.files.length > 0 ? { files: input.files } : {}),
|
|
983
1030
|
agentName,
|
|
984
1031
|
agentEmoji,
|
|
985
1032
|
agentOwnerToken,
|
|
@@ -1003,6 +1050,7 @@ export default function (pi) {
|
|
|
1003
1050
|
channel: input.channel,
|
|
1004
1051
|
content,
|
|
1005
1052
|
...(input.blocks && input.blocks.length > 0 ? { blocks: input.blocks } : {}),
|
|
1053
|
+
...(input.files && input.files.length > 0 ? { files: input.files } : {}),
|
|
1006
1054
|
agentName,
|
|
1007
1055
|
agentEmoji,
|
|
1008
1056
|
agentOwnerToken,
|
|
@@ -1024,6 +1072,14 @@ export default function (pi) {
|
|
|
1024
1072
|
readPinetInbox,
|
|
1025
1073
|
listBrokerAgents,
|
|
1026
1074
|
listFollowerAgents,
|
|
1075
|
+
listSubtreeAgents: (includeGhosts) => subtreeBrokerRuntime.listAgents(includeGhosts),
|
|
1076
|
+
getSubtreeSelfAgentId: () => subtreeBrokerRuntime.getStatus().selfAgentId,
|
|
1077
|
+
spawnSubtreeWorker: async (input) => {
|
|
1078
|
+
const activeCtx = sessionUiRuntime.getExtensionContext();
|
|
1079
|
+
if (!activeCtx)
|
|
1080
|
+
throw new Error("No active Pi extension context for subtree spawn.");
|
|
1081
|
+
return await subtreeBrokerRuntime.spawnWorker(activeCtx, input);
|
|
1082
|
+
},
|
|
1027
1083
|
listPinetLanes,
|
|
1028
1084
|
upsertPinetLane,
|
|
1029
1085
|
setPinetLaneParticipant,
|
|
@@ -1234,11 +1290,15 @@ export default function (pi) {
|
|
|
1234
1290
|
getBrokerControlPlaneHomeTabViewerIds,
|
|
1235
1291
|
lastBrokerControlPlaneHomeTabRefreshAt: () => brokerRuntime.getLastHomeTabRefreshAt(),
|
|
1236
1292
|
lastBrokerControlPlaneHomeTabError: () => brokerRuntime.getLastHomeTabError(),
|
|
1293
|
+
subtreeBrokerStatus: () => subtreeBrokerRuntime.getStatus(),
|
|
1237
1294
|
getPinetRegistrationBlockReason: pinetRegistrationGate.getBlockReason,
|
|
1238
1295
|
connectAsBroker: (ctx) => transitionToRuntimeMode(ctx, "broker"),
|
|
1239
1296
|
connectAsFollower: (ctx) => transitionToRuntimeMode(ctx, "follower"),
|
|
1240
1297
|
reloadPinetRuntime,
|
|
1241
1298
|
disconnectFollower,
|
|
1299
|
+
startSubtreeBroker: (ctx) => subtreeBrokerRuntime.start(ctx),
|
|
1300
|
+
stopSubtreeBroker: () => subtreeBrokerRuntime.stop({ releaseIdentity: true }),
|
|
1301
|
+
spawnSubtreeWorker: (ctx, input) => subtreeBrokerRuntime.spawnWorker(ctx, input),
|
|
1242
1302
|
sendPinetAgentMessage,
|
|
1243
1303
|
signalAgentFree,
|
|
1244
1304
|
applyLocalAgentIdentity,
|
package/dist/pinet-commands.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { type LoggedActivityLogEntry } from "./activity-log.js";
|
|
|
4
4
|
import { type SlackScopeDiagnostics } from "./slack-scope-diagnostics.js";
|
|
5
5
|
import type { SlackBridgeRuntimeMode } from "./runtime-mode.js";
|
|
6
6
|
import type { RalphSnoozeStatus } from "./ralph-loop.js";
|
|
7
|
+
import type { SubtreeBrokerStatus, SubtreeSpawnInput, SubtreeSpawnResult } from "./subtree-broker-runtime.js";
|
|
7
8
|
export interface PinetCommandsDeps {
|
|
8
9
|
pinetEnabled: () => boolean;
|
|
9
10
|
pinetRegistrationBlocked: () => boolean;
|
|
@@ -43,6 +44,7 @@ export interface PinetCommandsDeps {
|
|
|
43
44
|
getBrokerControlPlaneHomeTabViewerIds: () => string[];
|
|
44
45
|
lastBrokerControlPlaneHomeTabRefreshAt: () => string | null;
|
|
45
46
|
lastBrokerControlPlaneHomeTabError: () => string | null;
|
|
47
|
+
subtreeBrokerStatus: () => SubtreeBrokerStatus;
|
|
46
48
|
getPinetRegistrationBlockReason: () => string;
|
|
47
49
|
connectAsBroker: (ctx: ExtensionContext) => Promise<void>;
|
|
48
50
|
connectAsFollower: (ctx: ExtensionContext) => Promise<void>;
|
|
@@ -50,6 +52,9 @@ export interface PinetCommandsDeps {
|
|
|
50
52
|
disconnectFollower: (ctx: ExtensionContext) => Promise<{
|
|
51
53
|
unregisterError: string | null;
|
|
52
54
|
}>;
|
|
55
|
+
startSubtreeBroker: (ctx: ExtensionContext) => Promise<SubtreeBrokerStatus>;
|
|
56
|
+
stopSubtreeBroker: () => Promise<void>;
|
|
57
|
+
spawnSubtreeWorker: (ctx: ExtensionContext, input: SubtreeSpawnInput) => Promise<SubtreeSpawnResult>;
|
|
53
58
|
sendPinetAgentMessage: (target: string, body: string) => Promise<{
|
|
54
59
|
messageId: number;
|
|
55
60
|
target: string;
|
|
@@ -64,7 +69,7 @@ export interface PinetCommandsDeps {
|
|
|
64
69
|
setExtStatus: (ctx: ExtensionContext, state: "ok" | "reconnecting" | "error" | "off") => void;
|
|
65
70
|
setExtCtx: (ctx: ExtensionContext) => void;
|
|
66
71
|
}
|
|
67
|
-
export type PinetCommandAction = "start" | "follow" | "unfollow" | "reload" | "exit" | "free" | "status" | "logs" | "rename" | "snooze";
|
|
72
|
+
export type PinetCommandAction = "start" | "follow" | "unfollow" | "reload" | "exit" | "free" | "status" | "logs" | "rename" | "snooze" | "subtree";
|
|
68
73
|
export declare function formatPinetCommandHelp(): string;
|
|
69
74
|
export declare function runPinetCommandAction(deps: PinetCommandsDeps, action: PinetCommandAction, args: string, ctx: ExtensionContext, usageCommand?: string): Promise<void>;
|
|
70
75
|
export declare function registerPinetCommands(pi: ExtensionAPI, deps: PinetCommandsDeps): void;
|
package/dist/pinet-commands.js
CHANGED
|
@@ -11,6 +11,11 @@ const PINET_PRIMARY_COMMANDS = [
|
|
|
11
11
|
{ action: "exit", args: "<agent>", description: "Ask another agent to exit" },
|
|
12
12
|
{ action: "free", args: "", description: "Mark this agent as idle" },
|
|
13
13
|
{ action: "snooze", args: "[duration|off|status]", description: "Quiet empty RALPH cycles" },
|
|
14
|
+
{
|
|
15
|
+
action: "subtree",
|
|
16
|
+
args: "[start|status|spawn|stop]",
|
|
17
|
+
description: "Run this worker as a subtree broker for child followers",
|
|
18
|
+
},
|
|
14
19
|
];
|
|
15
20
|
const PINET_SECONDARY_COMMANDS = [
|
|
16
21
|
{ action: "status", args: "", description: "Show Pinet status" },
|
|
@@ -89,6 +94,9 @@ function normalizePinetCommandAction(rawAction) {
|
|
|
89
94
|
case "snooze":
|
|
90
95
|
case "quiet":
|
|
91
96
|
return "snooze";
|
|
97
|
+
case "subtree":
|
|
98
|
+
case "subbroker":
|
|
99
|
+
return "subtree";
|
|
92
100
|
case "help":
|
|
93
101
|
return null;
|
|
94
102
|
default:
|
|
@@ -127,11 +135,14 @@ export async function runPinetCommandAction(deps, action, args, ctx, usageComman
|
|
|
127
135
|
case "snooze":
|
|
128
136
|
runPinetSnooze(deps, args, ctx);
|
|
129
137
|
return;
|
|
138
|
+
case "subtree":
|
|
139
|
+
await runPinetSubtree(deps, args, ctx);
|
|
140
|
+
return;
|
|
130
141
|
}
|
|
131
142
|
}
|
|
132
143
|
export function registerPinetCommands(pi, deps) {
|
|
133
144
|
pi.registerCommand("pinet", {
|
|
134
|
-
description: "Unified Pinet command surface: start, follow, unfollow, reload, exit, free, snooze, status, logs, rename",
|
|
145
|
+
description: "Unified Pinet command surface: start, follow, unfollow, reload, exit, free, snooze, subtree, status, logs, rename",
|
|
135
146
|
handler: async (args, ctx) => {
|
|
136
147
|
const parsed = parsePinetCommandAction(args);
|
|
137
148
|
if (!parsed) {
|
|
@@ -318,6 +329,152 @@ function runPinetSnooze(deps, args, ctx) {
|
|
|
318
329
|
});
|
|
319
330
|
ctx.ui.notify(formatRalphSnoozeStatus(status), "info");
|
|
320
331
|
}
|
|
332
|
+
function formatSubtreeBrokerStatus(status) {
|
|
333
|
+
if (!status.active || !status.paths || !status.selfAgentId) {
|
|
334
|
+
return "Subtree broker: off\nUse /pinet subtree start from a worker that is already following the central broker.";
|
|
335
|
+
}
|
|
336
|
+
const envLines = Object.entries(status.childLaunchEnv).map(([key, value]) => `${key}=${value}`);
|
|
337
|
+
return [
|
|
338
|
+
"Subtree broker: running",
|
|
339
|
+
`Self agent: ${status.selfAgentId}`,
|
|
340
|
+
`Started: ${status.startedAt ?? "unknown"}`,
|
|
341
|
+
`Socket: ${status.paths.socketPath}`,
|
|
342
|
+
`Database: ${status.paths.dbPath}`,
|
|
343
|
+
`Lock: ${status.paths.lockPath}`,
|
|
344
|
+
`Children: ${status.childCount}`,
|
|
345
|
+
...(status.spawnedWorkers.length > 0
|
|
346
|
+
? [
|
|
347
|
+
"Spawned workers:",
|
|
348
|
+
...status.spawnedWorkers.map((worker) => `- ${worker.sessionName} role=${worker.role} agent=${worker.agentId ?? "pending"} repo=${worker.repoPath}`),
|
|
349
|
+
]
|
|
350
|
+
: []),
|
|
351
|
+
"Child follower environment:",
|
|
352
|
+
...envLines,
|
|
353
|
+
...(status.childLaunchHint ? ["Child launch hint:", status.childLaunchHint] : []),
|
|
354
|
+
].join("\n");
|
|
355
|
+
}
|
|
356
|
+
function parseSubtreeSpawnArgs(args) {
|
|
357
|
+
const tokens = args
|
|
358
|
+
.trim()
|
|
359
|
+
.split(/\s+/)
|
|
360
|
+
.map((token) => token.trim())
|
|
361
|
+
.filter(Boolean);
|
|
362
|
+
let repo = null;
|
|
363
|
+
let role;
|
|
364
|
+
let laneId;
|
|
365
|
+
const taskParts = [];
|
|
366
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
367
|
+
const token = tokens[index];
|
|
368
|
+
const next = tokens[index + 1];
|
|
369
|
+
if (token === "--") {
|
|
370
|
+
taskParts.push(...tokens.slice(index + 1));
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
if (token === "--repo" || token === "repo") {
|
|
374
|
+
if (!next)
|
|
375
|
+
return null;
|
|
376
|
+
repo = next;
|
|
377
|
+
index += 1;
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
if (token === "--role" || token === "role") {
|
|
381
|
+
if (!next)
|
|
382
|
+
return null;
|
|
383
|
+
role = next;
|
|
384
|
+
index += 1;
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
if (token === "--lane" || token === "--lane-id" || token === "lane" || token === "lane_id") {
|
|
388
|
+
if (!next)
|
|
389
|
+
return null;
|
|
390
|
+
laneId = next;
|
|
391
|
+
index += 1;
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
if (token.startsWith("repo=")) {
|
|
395
|
+
repo = token.slice("repo=".length);
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
if (token.startsWith("role=")) {
|
|
399
|
+
role = token.slice("role=".length);
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
if (token.startsWith("lane=")) {
|
|
403
|
+
laneId = token.slice("lane=".length);
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
if (token.startsWith("lane_id=")) {
|
|
407
|
+
laneId = token.slice("lane_id=".length);
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
taskParts.push(token);
|
|
411
|
+
}
|
|
412
|
+
const task = taskParts.join(" ").trim();
|
|
413
|
+
if (!repo || !task)
|
|
414
|
+
return null;
|
|
415
|
+
return {
|
|
416
|
+
repo,
|
|
417
|
+
task,
|
|
418
|
+
...(role ? { role } : {}),
|
|
419
|
+
...(laneId ? { laneId } : {}),
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
function formatSubtreeSpawnResult(result) {
|
|
423
|
+
return [
|
|
424
|
+
"Subtree worker started",
|
|
425
|
+
`Agent: ${result.agentName} (${result.agentId})`,
|
|
426
|
+
`Session: ${result.sessionName}`,
|
|
427
|
+
`Repo: ${result.repoPath}`,
|
|
428
|
+
`Role: ${result.role}`,
|
|
429
|
+
...(result.laneId ? [`Lane: ${result.laneId}`] : []),
|
|
430
|
+
`Task message: ${result.messageId}`,
|
|
431
|
+
`Thread: ${result.threadId}`,
|
|
432
|
+
`Monitor: ${result.monitorCommand}`,
|
|
433
|
+
].join("\n");
|
|
434
|
+
}
|
|
435
|
+
async function runPinetSubtree(deps, args, ctx) {
|
|
436
|
+
const [rawSubcommand = "status"] = args.trim().split(/\s+/);
|
|
437
|
+
const subcommand = rawSubcommand.toLowerCase();
|
|
438
|
+
if (["status", "show", "info", ""].includes(subcommand)) {
|
|
439
|
+
ctx.ui.notify(formatSubtreeBrokerStatus(deps.subtreeBrokerStatus()), "info");
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
if (["stop", "off", "down"].includes(subcommand)) {
|
|
443
|
+
await deps.stopSubtreeBroker();
|
|
444
|
+
ctx.ui.notify("Subtree broker stopped. Spawned child followers were asked to exit.", "info");
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
if (deps.runtimeMode() !== "follower" || deps.brokerRole() !== "follower") {
|
|
448
|
+
ctx.ui.notify("Subtree broker operations require this session to be running as a Pinet worker/follower. Run /pinet follow first.", "warning");
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
if (["spawn", "child", "worker"].includes(subcommand)) {
|
|
452
|
+
const spawnInput = parseSubtreeSpawnArgs(args.trim().slice(rawSubcommand.length));
|
|
453
|
+
if (!spawnInput) {
|
|
454
|
+
ctx.ui.notify("Usage: /pinet subtree spawn repo=<repo-or-path> [role=<role>] [lane=<lane>] <task>", "warning");
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
try {
|
|
458
|
+
const result = await deps.spawnSubtreeWorker(ctx, spawnInput);
|
|
459
|
+
ctx.ui.notify(formatSubtreeSpawnResult(result), "info");
|
|
460
|
+
}
|
|
461
|
+
catch (err) {
|
|
462
|
+
ctx.ui.notify(`Subtree worker spawn failed: ${errorMsg(err)}`, "error");
|
|
463
|
+
}
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
if (!["start", "on", "broker", "promote"].includes(subcommand)) {
|
|
467
|
+
ctx.ui.notify("Usage: /pinet subtree [start|status|spawn|stop]", "warning");
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
try {
|
|
471
|
+
const status = await deps.startSubtreeBroker(ctx);
|
|
472
|
+
ctx.ui.notify(formatSubtreeBrokerStatus(status), "info");
|
|
473
|
+
}
|
|
474
|
+
catch (err) {
|
|
475
|
+
ctx.ui.notify(`Subtree broker start failed: ${errorMsg(err)}`, "error");
|
|
476
|
+
}
|
|
477
|
+
}
|
|
321
478
|
function runPinetStatus(deps, ctx) {
|
|
322
479
|
const mode = deps.runtimeMode();
|
|
323
480
|
const ownedCount = [...deps.threads().values()].filter((t) => agentOwnsThread(t.owner, deps.agentName(), deps.agentAliases(), deps.agentOwnerToken())).length;
|
|
@@ -357,6 +514,13 @@ function runPinetStatus(deps, ctx) {
|
|
|
357
514
|
: []),
|
|
358
515
|
]
|
|
359
516
|
: [];
|
|
517
|
+
const subtreeStatus = deps.subtreeBrokerStatus();
|
|
518
|
+
const subtreeBrokerInfo = subtreeStatus.active
|
|
519
|
+
? [
|
|
520
|
+
`Subtree broker: running (${subtreeStatus.selfAgentId ?? "unknown"})`,
|
|
521
|
+
...(subtreeStatus.paths ? [`Subtree socket: ${subtreeStatus.paths.socketPath}`] : []),
|
|
522
|
+
]
|
|
523
|
+
: [];
|
|
360
524
|
ctx.ui.notify([
|
|
361
525
|
`Mode: ${mode}`,
|
|
362
526
|
`Agent: ${deps.agentEmoji()} ${deps.agentName()}`,
|
|
@@ -376,6 +540,7 @@ function runPinetStatus(deps, ctx) {
|
|
|
376
540
|
...brokerHealthInfo,
|
|
377
541
|
...ralphSnoozeInfo,
|
|
378
542
|
...brokerHomeTabInfo,
|
|
543
|
+
...subtreeBrokerInfo,
|
|
379
544
|
].join("\n"), "info");
|
|
380
545
|
}
|
|
381
546
|
function runPinetLogs(deps, ctx) {
|
package/dist/pinet-mesh-ops.d.ts
CHANGED
|
@@ -14,6 +14,12 @@ export interface PinetMeshOpsAgentRecord {
|
|
|
14
14
|
resumableUntil?: string | null;
|
|
15
15
|
outboundCount?: number;
|
|
16
16
|
pendingInboxCount?: number;
|
|
17
|
+
parentAgentId?: string | null;
|
|
18
|
+
rootAgentId?: string | null;
|
|
19
|
+
treeDepth?: number;
|
|
20
|
+
supervisionState?: string;
|
|
21
|
+
subtreeRole?: string | null;
|
|
22
|
+
laneId?: string | null;
|
|
17
23
|
}
|
|
18
24
|
export interface PinetMeshOpsRecordedAssignment {
|
|
19
25
|
issueNumber: number;
|
|
@@ -64,6 +70,11 @@ export interface PinetMeshOpsDeps {
|
|
|
64
70
|
getActiveBrokerSelfId: () => string | null;
|
|
65
71
|
getAgentName: () => string;
|
|
66
72
|
getFollowerClient: () => PinetMeshOpsFollowerClientPort | null;
|
|
73
|
+
sendSubtreeAgentMessage?: (target: string, body: string, metadata?: Record<string, unknown>) => Promise<{
|
|
74
|
+
messageId: number;
|
|
75
|
+
target: string;
|
|
76
|
+
threadId: string;
|
|
77
|
+
} | null>;
|
|
67
78
|
formatTrackedAgent: (agentId: string) => string;
|
|
68
79
|
logActivity: (entry: ActivityLogEntry) => void;
|
|
69
80
|
}
|
package/dist/pinet-mesh-ops.js
CHANGED
|
@@ -109,6 +109,7 @@ export function createPinetMeshOps(deps) {
|
|
|
109
109
|
target: targetRef,
|
|
110
110
|
body: dispatchBody,
|
|
111
111
|
metadata: dispatchMetadata,
|
|
112
|
+
trustedBrokerAgentId: selfId,
|
|
112
113
|
});
|
|
113
114
|
if (transferThreadId) {
|
|
114
115
|
const transfer = db.transferThreadOwnership(transferThreadId, result.target.id);
|
|
@@ -171,6 +172,10 @@ export function createPinetMeshOps(deps) {
|
|
|
171
172
|
};
|
|
172
173
|
}
|
|
173
174
|
if (deps.getBrokerRole() === "follower") {
|
|
175
|
+
const subtreeResult = await deps.sendSubtreeAgentMessage?.(targetRef, finalBody, finalMetadata);
|
|
176
|
+
if (subtreeResult) {
|
|
177
|
+
return { messageId: subtreeResult.messageId, target: subtreeResult.target };
|
|
178
|
+
}
|
|
174
179
|
const client = deps.getFollowerClient();
|
|
175
180
|
if (!client) {
|
|
176
181
|
throw new Error("Pinet is in an unexpected state.");
|
|
@@ -233,6 +238,12 @@ export function createPinetMeshOps(deps) {
|
|
|
233
238
|
resumableUntil: agent.resumableUntil,
|
|
234
239
|
outboundCount: agent.outboundCount,
|
|
235
240
|
pendingInboxCount: db.getPendingInboxCount(agent.id),
|
|
241
|
+
parentAgentId: agent.parentAgentId,
|
|
242
|
+
rootAgentId: agent.rootAgentId,
|
|
243
|
+
treeDepth: agent.treeDepth,
|
|
244
|
+
supervisionState: agent.supervisionState,
|
|
245
|
+
subtreeRole: agent.subtreeRole,
|
|
246
|
+
laneId: agent.laneId,
|
|
236
247
|
}));
|
|
237
248
|
}
|
|
238
249
|
async function listFollowerAgents(includeGhosts) {
|
|
@@ -253,6 +264,12 @@ export function createPinetMeshOps(deps) {
|
|
|
253
264
|
resumableUntil: agent.resumableUntil,
|
|
254
265
|
outboundCount: agent.outboundCount,
|
|
255
266
|
pendingInboxCount: agent.pendingInboxCount,
|
|
267
|
+
parentAgentId: agent.parentAgentId,
|
|
268
|
+
rootAgentId: agent.rootAgentId,
|
|
269
|
+
treeDepth: agent.treeDepth,
|
|
270
|
+
supervisionState: agent.supervisionState,
|
|
271
|
+
subtreeRole: agent.subtreeRole,
|
|
272
|
+
laneId: agent.laneId,
|
|
256
273
|
}));
|
|
257
274
|
}
|
|
258
275
|
return {
|
package/dist/pinet-tools.d.ts
CHANGED
|
@@ -15,6 +15,34 @@ export interface PinetToolsAgentRecord {
|
|
|
15
15
|
resumableUntil?: string | null;
|
|
16
16
|
outboundCount?: number;
|
|
17
17
|
pendingInboxCount?: number;
|
|
18
|
+
parentAgentId?: string | null;
|
|
19
|
+
rootAgentId?: string | null;
|
|
20
|
+
treeDepth?: number;
|
|
21
|
+
supervisionState?: string;
|
|
22
|
+
subtreeRole?: string | null;
|
|
23
|
+
laneId?: string | null;
|
|
24
|
+
}
|
|
25
|
+
export interface PinetSubtreeSpawnInput {
|
|
26
|
+
task: string;
|
|
27
|
+
repo: string;
|
|
28
|
+
role?: string;
|
|
29
|
+
laneId?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface PinetSubtreeSpawnResult {
|
|
32
|
+
status: "started";
|
|
33
|
+
launchId: string;
|
|
34
|
+
sessionName: string;
|
|
35
|
+
repoPath: string;
|
|
36
|
+
role: string;
|
|
37
|
+
laneId: string | null;
|
|
38
|
+
agentId: string;
|
|
39
|
+
agentName: string;
|
|
40
|
+
messageId: number;
|
|
41
|
+
threadId: string;
|
|
42
|
+
monitorCommand: string;
|
|
43
|
+
socketPath: string;
|
|
44
|
+
dbPath: string;
|
|
45
|
+
childLaunchEnv: Record<string, string>;
|
|
18
46
|
}
|
|
19
47
|
export interface RegisterPinetToolsDeps {
|
|
20
48
|
pinetEnabled: () => boolean;
|
|
@@ -48,6 +76,9 @@ export interface RegisterPinetToolsDeps {
|
|
|
48
76
|
readPinetInbox: (options: PinetReadOptions) => Promise<PinetReadResult>;
|
|
49
77
|
listBrokerAgents: () => PinetToolsAgentRecord[];
|
|
50
78
|
listFollowerAgents: (includeGhosts: boolean) => Promise<PinetToolsAgentRecord[]>;
|
|
79
|
+
listSubtreeAgents?: (includeGhosts: boolean) => PinetToolsAgentRecord[] | null;
|
|
80
|
+
getSubtreeSelfAgentId?: () => string | null;
|
|
81
|
+
spawnSubtreeWorker?: (input: PinetSubtreeSpawnInput) => Promise<PinetSubtreeSpawnResult>;
|
|
51
82
|
listPinetLanes: (options: PinetLaneListOptions) => Promise<PinetLaneInfo[]>;
|
|
52
83
|
upsertPinetLane: (input: PinetLaneUpsertInput) => Promise<PinetLaneInfo>;
|
|
53
84
|
setPinetLaneParticipant: (input: PinetLaneParticipantUpsertInput) => Promise<PinetLaneParticipantInfo>;
|
|
@@ -64,4 +95,20 @@ export interface RegisterPinetToolsDeps {
|
|
|
64
95
|
}) => RalphSnoozeStatus;
|
|
65
96
|
clearRalphSnooze?: () => RalphSnoozeStatus;
|
|
66
97
|
}
|
|
98
|
+
type PinetDispatcherStatus = "succeeded" | "failed";
|
|
99
|
+
interface PinetRenderContentBlock {
|
|
100
|
+
type: string;
|
|
101
|
+
text?: string;
|
|
102
|
+
}
|
|
103
|
+
interface PinetRenderResultInput {
|
|
104
|
+
content?: PinetRenderContentBlock[];
|
|
105
|
+
details?: unknown;
|
|
106
|
+
expandedText?: string;
|
|
107
|
+
displayText?: string;
|
|
108
|
+
}
|
|
109
|
+
export declare function formatPinetDispatcherResultForDisplay(result: PinetRenderResultInput, expanded: boolean): {
|
|
110
|
+
status: PinetDispatcherStatus | "unknown";
|
|
111
|
+
text: string;
|
|
112
|
+
};
|
|
67
113
|
export declare function registerPinetTools(pi: ExtensionAPI, deps: RegisterPinetToolsDeps): void;
|
|
114
|
+
export {};
|