@gjczone/pi-swarm 0.1.2

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 (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +124 -0
  3. package/dist/index.d.ts +13 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +99 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/shared/controller.d.ts +86 -0
  8. package/dist/shared/controller.d.ts.map +1 -0
  9. package/dist/shared/controller.js +662 -0
  10. package/dist/shared/controller.js.map +1 -0
  11. package/dist/shared/pi-invoke.d.ts +31 -0
  12. package/dist/shared/pi-invoke.d.ts.map +1 -0
  13. package/dist/shared/pi-invoke.js +54 -0
  14. package/dist/shared/pi-invoke.js.map +1 -0
  15. package/dist/shared/render.d.ts +44 -0
  16. package/dist/shared/render.d.ts.map +1 -0
  17. package/dist/shared/render.js +116 -0
  18. package/dist/shared/render.js.map +1 -0
  19. package/dist/shared/spawner.d.ts +26 -0
  20. package/dist/shared/spawner.d.ts.map +1 -0
  21. package/dist/shared/spawner.js +226 -0
  22. package/dist/shared/spawner.js.map +1 -0
  23. package/dist/shared/types.d.ts +182 -0
  24. package/dist/shared/types.d.ts.map +1 -0
  25. package/dist/shared/types.js +8 -0
  26. package/dist/shared/types.js.map +1 -0
  27. package/dist/state/persistence.d.ts +83 -0
  28. package/dist/state/persistence.d.ts.map +1 -0
  29. package/dist/state/persistence.js +215 -0
  30. package/dist/state/persistence.js.map +1 -0
  31. package/dist/state/recovery.d.ts +35 -0
  32. package/dist/state/recovery.d.ts.map +1 -0
  33. package/dist/state/recovery.js +149 -0
  34. package/dist/state/recovery.js.map +1 -0
  35. package/dist/swarm/command.d.ts +36 -0
  36. package/dist/swarm/command.d.ts.map +1 -0
  37. package/dist/swarm/command.js +113 -0
  38. package/dist/swarm/command.js.map +1 -0
  39. package/dist/swarm/mode.d.ts +58 -0
  40. package/dist/swarm/mode.d.ts.map +1 -0
  41. package/dist/swarm/mode.js +87 -0
  42. package/dist/swarm/mode.js.map +1 -0
  43. package/dist/swarm/tool.d.ts +11 -0
  44. package/dist/swarm/tool.d.ts.map +1 -0
  45. package/dist/swarm/tool.js +190 -0
  46. package/dist/swarm/tool.js.map +1 -0
  47. package/dist/team/command.d.ts +11 -0
  48. package/dist/team/command.d.ts.map +1 -0
  49. package/dist/team/command.js +32 -0
  50. package/dist/team/command.js.map +1 -0
  51. package/dist/team/mailbox.d.ts +61 -0
  52. package/dist/team/mailbox.d.ts.map +1 -0
  53. package/dist/team/mailbox.js +160 -0
  54. package/dist/team/mailbox.js.map +1 -0
  55. package/dist/team/supervisor.d.ts +77 -0
  56. package/dist/team/supervisor.d.ts.map +1 -0
  57. package/dist/team/supervisor.js +195 -0
  58. package/dist/team/supervisor.js.map +1 -0
  59. package/dist/team/task-graph.d.ts +61 -0
  60. package/dist/team/task-graph.d.ts.map +1 -0
  61. package/dist/team/task-graph.js +193 -0
  62. package/dist/team/task-graph.js.map +1 -0
  63. package/dist/team/tool.d.ts +11 -0
  64. package/dist/team/tool.d.ts.map +1 -0
  65. package/dist/team/tool.js +210 -0
  66. package/dist/team/tool.js.map +1 -0
  67. package/dist/tui/permission-prompt.d.ts +26 -0
  68. package/dist/tui/permission-prompt.d.ts.map +1 -0
  69. package/dist/tui/permission-prompt.js +94 -0
  70. package/dist/tui/permission-prompt.js.map +1 -0
  71. package/dist/tui/progress.d.ts +64 -0
  72. package/dist/tui/progress.d.ts.map +1 -0
  73. package/dist/tui/progress.js +260 -0
  74. package/dist/tui/progress.js.map +1 -0
  75. package/dist/tui/swarm-markers.d.ts +20 -0
  76. package/dist/tui/swarm-markers.d.ts.map +1 -0
  77. package/dist/tui/swarm-markers.js +61 -0
  78. package/dist/tui/swarm-markers.js.map +1 -0
  79. package/package.json +58 -0
@@ -0,0 +1,149 @@
1
+ /**
2
+ * state/recovery — crash recovery and stale run detection.
3
+ *
4
+ * On session start, scans for incomplete runs. If the parent Pi
5
+ * process is no longer alive, marks the run as abandoned. Completed
6
+ * runs older than 7 days are cleaned up automatically.
7
+ */
8
+ import * as fs from "node:fs";
9
+ import * as path from "node:path";
10
+ import { resolveCrewRoot, listActiveRuns, readManifest, updateManifest, deleteRunState, appendEvent, } from "./persistence.js";
11
+ // ---------------------------------------------------------------------------
12
+ // Constants
13
+ // ---------------------------------------------------------------------------
14
+ /** Runs completed more than this many ms ago are eligible for cleanup. */
15
+ const COMPLETED_RUN_RETENTION_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
16
+ /** Runs with status "running" older than this without a heartbeat are stale. */
17
+ const STALE_RUN_THRESHOLD_MS = 30 * 60 * 1000; // 30 minutes
18
+ /**
19
+ * Run recovery on session start.
20
+ *
21
+ * - Marks stale "running" runs as abandoned.
22
+ * - Cleans up expired completed runs.
23
+ * - Returns lists of resumable and abandoned runs.
24
+ */
25
+ export function recoverRuns(cwd) {
26
+ const crewRoot = resolveCrewRoot(cwd);
27
+ const runsDir = path.join(crewRoot, "state", "runs");
28
+ if (!fs.existsSync(runsDir)) {
29
+ return { resumable: [], abandoned: [], cleanedUp: [] };
30
+ }
31
+ const runIds = listActiveRuns(crewRoot);
32
+ const now = Date.now();
33
+ const resumable = [];
34
+ const abandoned = [];
35
+ const cleanedUp = [];
36
+ for (const runId of runIds) {
37
+ const manifest = readManifest(crewRoot, runId);
38
+ if (!manifest) {
39
+ // Orphaned directory — clean up
40
+ try {
41
+ deleteRunState(crewRoot, runId);
42
+ cleanedUp.push(runId);
43
+ }
44
+ catch {
45
+ // Best effort
46
+ }
47
+ continue;
48
+ }
49
+ switch (manifest.status) {
50
+ case "running": {
51
+ // Check for staleness
52
+ const age = now - manifest.startedAt;
53
+ if (age > STALE_RUN_THRESHOLD_MS) {
54
+ // Mark as abandoned
55
+ const updated = {
56
+ ...manifest,
57
+ status: "abandoned",
58
+ completedAt: now,
59
+ };
60
+ updateManifest(crewRoot, updated);
61
+ appendEvent(crewRoot, runId, {
62
+ type: "run.abandoned",
63
+ reason: "stale",
64
+ timestamp: new Date().toISOString(),
65
+ });
66
+ abandoned.push(updated);
67
+ }
68
+ else {
69
+ // Still potentially resumable
70
+ resumable.push(manifest);
71
+ }
72
+ break;
73
+ }
74
+ case "completed":
75
+ case "failed": {
76
+ // Clean up expired runs
77
+ const completedAt = manifest.completedAt ?? manifest.startedAt;
78
+ const age = now - completedAt;
79
+ if (age > COMPLETED_RUN_RETENTION_MS) {
80
+ try {
81
+ deleteRunState(crewRoot, runId);
82
+ cleanedUp.push(runId);
83
+ }
84
+ catch {
85
+ // Best effort
86
+ }
87
+ }
88
+ break;
89
+ }
90
+ case "abandoned":
91
+ // Already abandoned — check if we should clean up
92
+ {
93
+ const abandonedAt = manifest.completedAt ?? manifest.startedAt;
94
+ if (now - abandonedAt > COMPLETED_RUN_RETENTION_MS) {
95
+ try {
96
+ deleteRunState(crewRoot, runId);
97
+ cleanedUp.push(runId);
98
+ }
99
+ catch {
100
+ // Best effort
101
+ }
102
+ }
103
+ }
104
+ break;
105
+ }
106
+ }
107
+ return { resumable, abandoned, cleanedUp };
108
+ }
109
+ /**
110
+ * Check whether a run has unfinished tasks.
111
+ * Used to decide whether to offer resume or start fresh.
112
+ */
113
+ export function hasUnfinishedTasks(crewRoot, runId) {
114
+ const manifest = readManifest(crewRoot, runId);
115
+ if (!manifest)
116
+ return false;
117
+ if (manifest.status === "completed")
118
+ return false;
119
+ // Check if any agent status files indicate unfinished work
120
+ const agentsDir = path.join(crewRoot, "state", "runs", runId, "agents");
121
+ if (!fs.existsSync(agentsDir))
122
+ return manifest.status === "running";
123
+ // If there are agent IDs in the manifest, all are accounted for
124
+ // If fewer agents ran than expected, there is unfinished work
125
+ return true;
126
+ }
127
+ /**
128
+ * Get resumable agent IDs from a run.
129
+ * Returns a map of agentId → last known status.
130
+ */
131
+ export function getResumableAgents(crewRoot, runId) {
132
+ const manifest = readManifest(crewRoot, runId);
133
+ const result = new Map();
134
+ if (!manifest)
135
+ return result;
136
+ for (const agentId of manifest.agentIds) {
137
+ const statusDir = path.join(crewRoot, "state", "runs", runId, "agents", agentId);
138
+ if (fs.existsSync(statusDir)) {
139
+ // Agent has state on disk — it can be resumed
140
+ result.set(agentId, "resumable");
141
+ }
142
+ else {
143
+ // Agent was never started
144
+ result.set(agentId, "not_started");
145
+ }
146
+ }
147
+ return result;
148
+ }
149
+ //# sourceMappingURL=recovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recovery.js","sourceRoot":"","sources":["../../src/state/recovery.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * swarm/command — /swarm slash command handler.
3
+ *
4
+ * Supports:
5
+ * /swarm on — enable swarm mode (manual trigger)
6
+ * /swarm off — disable swarm mode
7
+ * /swarm — toggle swarm mode
8
+ * /swarm <task> — enable swarm mode + send task (one-shot)
9
+ *
10
+ * Ported from MoonshotAI/kimi-code's swarm command.
11
+ */
12
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
13
+ import type { SwarmModeTrigger } from "./mode.js";
14
+ export interface SwarmCommandHost {
15
+ /** The pi extension API. */
16
+ readonly pi: ExtensionAPI;
17
+ /** Current swarm mode state. */
18
+ swarmActive: boolean;
19
+ /** Set swarm mode state. */
20
+ setSwarmActive(active: boolean, trigger: SwarmModeTrigger): void;
21
+ /** Log / status helpers. */
22
+ /** Send a normal user input prompt. */
23
+ sendNormalUserInput(prompt: string): void;
24
+ /** Show a status message to the user. */
25
+ showStatus(message: string): void;
26
+ /** Show an error message to the user. */
27
+ showError(message: string): void;
28
+ /** Check if a model is configured. */
29
+ hasModel(): boolean;
30
+ /** Get the current permission mode. */
31
+ getPermissionMode(): string;
32
+ /** Set the permission mode. */
33
+ setPermissionMode(mode: string): Promise<void>;
34
+ }
35
+ export declare function registerSwarmCommand(pi: ExtensionAPI, host: SwarmCommandHost): void;
36
+ //# sourceMappingURL=command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/swarm/command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,YAAY,EAA2B,MAAM,iCAAiC,CAAC;AAC7F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAMlD,MAAM,WAAW,gBAAgB;IAC/B,4BAA4B;IAC5B,QAAQ,CAAC,EAAE,EAAE,YAAY,CAAC;IAC1B,gCAAgC;IAChC,WAAW,EAAE,OAAO,CAAC;IACrB,4BAA4B;IAC5B,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACjE,4BAA4B;IAC5B,uCAAuC;IACvC,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,yCAAyC;IACzC,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,yCAAyC;IACzC,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,sCAAsC;IACtC,QAAQ,IAAI,OAAO,CAAC;IACpB,uCAAuC;IACvC,iBAAiB,IAAI,MAAM,CAAC;IAC5B,+BAA+B;IAC/B,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD;AAED,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,YAAY,EAChB,IAAI,EAAE,gBAAgB,GACrB,IAAI,CAmCN"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * swarm/command — /swarm slash command handler.
3
+ *
4
+ * Supports:
5
+ * /swarm on — enable swarm mode (manual trigger)
6
+ * /swarm off — disable swarm mode
7
+ * /swarm — toggle swarm mode
8
+ * /swarm <task> — enable swarm mode + send task (one-shot)
9
+ *
10
+ * Ported from MoonshotAI/kimi-code's swarm command.
11
+ */
12
+ export function registerSwarmCommand(pi, host) {
13
+ pi.registerCommand("swarm", {
14
+ description: "Turn swarm mode on/off, or run a one-shot swarm task. " +
15
+ "Usage: /swarm on|off, or /swarm <task>",
16
+ async handler(args, ctx) {
17
+ const prompt = args.trim();
18
+ const mode = swarmModeSubcommand(prompt);
19
+ // Subcommand: on / off
20
+ if (mode !== undefined) {
21
+ await applySwarmMode(host, mode, `/swarm ${prompt}`, ctx);
22
+ return;
23
+ }
24
+ // No args: toggle
25
+ if (prompt.length === 0) {
26
+ await applySwarmMode(host, !host.swarmActive, "/swarm", ctx);
27
+ return;
28
+ }
29
+ // Task mode: enable swarm + send prompt
30
+ if (!host.hasModel()) {
31
+ host.showError("No model configured. Please set a model first.");
32
+ return;
33
+ }
34
+ await startSwarmTask(host, prompt, ctx);
35
+ },
36
+ });
37
+ }
38
+ // ---------------------------------------------------------------------------
39
+ // Command logic
40
+ // ---------------------------------------------------------------------------
41
+ async function applySwarmMode(host, enabled, commandText, ctx) {
42
+ if (enabled && host.swarmActive) {
43
+ host.showStatus("Swarm mode is already on.");
44
+ return;
45
+ }
46
+ if (!enabled && !host.swarmActive) {
47
+ host.showStatus("Swarm mode is already off.");
48
+ return;
49
+ }
50
+ if (enabled) {
51
+ // If in manual permission mode, ask for confirmation
52
+ if (host.getPermissionMode() === "manual") {
53
+ const confirmed = await ctx.ui?.confirm("Swarm Mode", "Swarm mode works best with auto or yolo permission. " +
54
+ "Switch to auto permission mode and enable swarm?");
55
+ if (!confirmed) {
56
+ host.showStatus("Swarm mode not enabled.");
57
+ return;
58
+ }
59
+ await host.setPermissionMode("auto");
60
+ }
61
+ host.setSwarmActive(true, "manual");
62
+ host.showStatus("Swarm mode enabled. AgentSwarm tool is now auto-approved.");
63
+ // Insert swarm mode marker (via pi.sendMessage for TUI rendering)
64
+ host.pi.sendMessage?.({
65
+ customType: "swarm:marker",
66
+ content: "active",
67
+ display: true,
68
+ });
69
+ }
70
+ else {
71
+ host.setSwarmActive(false, "manual");
72
+ host.showStatus("Swarm mode disabled.");
73
+ host.pi.sendMessage?.({
74
+ customType: "swarm:marker",
75
+ content: "inactive",
76
+ display: true,
77
+ });
78
+ }
79
+ }
80
+ async function startSwarmTask(host, prompt, ctx) {
81
+ // Enable swarm mode if not already active
82
+ if (!host.swarmActive) {
83
+ if (host.getPermissionMode() === "manual") {
84
+ const confirmed = await ctx.ui?.confirm("Swarm Task", "Starting a swarm task. Switch to auto permission mode?");
85
+ if (!confirmed) {
86
+ host.showStatus("Swarm task cancelled.");
87
+ return;
88
+ }
89
+ await host.setPermissionMode("auto");
90
+ }
91
+ host.setSwarmActive(true, "task");
92
+ }
93
+ // TUI marker
94
+ host.pi.sendMessage?.({
95
+ customType: "swarm:marker",
96
+ content: "active",
97
+ display: true,
98
+ });
99
+ // Send the prompt
100
+ host.sendNormalUserInput(prompt);
101
+ }
102
+ // ---------------------------------------------------------------------------
103
+ // Helpers
104
+ // ---------------------------------------------------------------------------
105
+ function swarmModeSubcommand(input) {
106
+ const command = input.toLowerCase();
107
+ if (command === "on")
108
+ return true;
109
+ if (command === "off")
110
+ return false;
111
+ return undefined;
112
+ }
113
+ //# sourceMappingURL=command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.js","sourceRoot":"","sources":["../../src/swarm/command.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * swarm/mode — SwarmMode state machine.
3
+ *
4
+ * Tracks whether swarm mode is active and manages the lifecycle
5
+ * of entering/exiting swarm mode with system reminders.
6
+ *
7
+ * Ported from MoonshotAI/kimi-code's SwarmMode.
8
+ */
9
+ /** How swarm mode was triggered. */
10
+ export type SwarmModeTrigger = "manual" | "task" | "tool";
11
+ /**
12
+ * Minimal interface for the agent object that SwarmMode needs.
13
+ * The real agent is provided by the extension context.
14
+ */
15
+ export interface SwarmModeAgent {
16
+ readonly context: {
17
+ /**
18
+ * Append a system reminder to the agent context.
19
+ * The reminder is injected into the model's context window.
20
+ */
21
+ appendSystemReminder(text: string, origin: {
22
+ kind: string;
23
+ variant: string;
24
+ }): void;
25
+ /**
26
+ * Pop a matched message from the context.
27
+ * Returns true if a message was removed.
28
+ */
29
+ popMatchedMessage(predicate: (origin: unknown) => boolean): boolean;
30
+ };
31
+ /** Emit a status update event. */
32
+ emitStatusUpdated(): void;
33
+ /** Log a record to the agent's event log. */
34
+ records: {
35
+ logRecord(entry: {
36
+ type: string;
37
+ trigger?: string;
38
+ }): void;
39
+ };
40
+ }
41
+ export declare class SwarmMode {
42
+ private readonly agent;
43
+ private active;
44
+ constructor(agent: SwarmModeAgent);
45
+ /** Enter swarm mode with the given trigger. No-op if already active. */
46
+ enter(trigger: SwarmModeTrigger): void;
47
+ /** Restore a previously saved swarm mode trigger (e.g., on session resume). */
48
+ restoreEnter(trigger: SwarmModeTrigger): void;
49
+ /** Exit swarm mode. No-op if not active. */
50
+ exit(): void;
51
+ /** Whether swarm mode is currently active. */
52
+ get isActive(): boolean;
53
+ /** Whether swarm mode should auto-exit after the current operation. */
54
+ get shouldAutoExit(): boolean;
55
+ /** The current trigger, or null if not active. */
56
+ get currentTrigger(): SwarmModeTrigger | null;
57
+ }
58
+ //# sourceMappingURL=mode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mode.d.ts","sourceRoot":"","sources":["../../src/swarm/mode.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,oCAAoC;AACpC,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAE1D;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE;QAChB;;;WAGG;QACH,oBAAoB,CAClB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE,GACxC,IAAI,CAAC;QACR;;;WAGG;QACH,iBAAiB,CACf,SAAS,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,GACtC,OAAO,CAAC;KACZ,CAAC;IACF,kCAAkC;IAClC,iBAAiB,IAAI,IAAI,CAAC;IAC1B,6CAA6C;IAC7C,OAAO,EAAE;QACP,SAAS,CAAC,KAAK,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,CAAC,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAC;KAC5D,CAAC;CACH;AA0BD,qBAAa,SAAS;IAGR,OAAO,CAAC,QAAQ,CAAC,KAAK;IAFlC,OAAO,CAAC,MAAM,CAAiC;gBAElB,KAAK,EAAE,cAAc;IAElD,wEAAwE;IACxE,KAAK,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAmBtC,+EAA+E;IAC/E,YAAY,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAI7C,4CAA4C;IAC5C,IAAI,IAAI,IAAI;IAgCZ,8CAA8C;IAC9C,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,uEAAuE;IACvE,IAAI,cAAc,IAAI,OAAO,CAE5B;IAED,kDAAkD;IAClD,IAAI,cAAc,IAAI,gBAAgB,GAAG,IAAI,CAE5C;CACF"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * swarm/mode — SwarmMode state machine.
3
+ *
4
+ * Tracks whether swarm mode is active and manages the lifecycle
5
+ * of entering/exiting swarm mode with system reminders.
6
+ *
7
+ * Ported from MoonshotAI/kimi-code's SwarmMode.
8
+ */
9
+ // ---------------------------------------------------------------------------
10
+ // System reminder templates (from kimi-code)
11
+ // ---------------------------------------------------------------------------
12
+ const SWARM_MODE_ENTER_REMINDER = `
13
+ Swarm mode is now active. While swarm mode is active:
14
+ - The AgentSwarm tool is auto-approved (no permission required).
15
+ - You may call AgentSwarm freely to parallelise work.
16
+ - When you finish the swarm work, call AgentSwarm with
17
+ resume_agent_ids to continue unfinished subagents.
18
+ - Prefer splitting large tasks into many small, independent items
19
+ so the swarm stays efficient.
20
+ `.trim();
21
+ const SWARM_MODE_EXIT_REMINDER = `
22
+ Swarm mode has been deactivated.
23
+ - The AgentSwarm tool now requires permission approval again.
24
+ - Continue working normally.
25
+ `.trim();
26
+ // ---------------------------------------------------------------------------
27
+ // SwarmMode
28
+ // ---------------------------------------------------------------------------
29
+ export class SwarmMode {
30
+ agent;
31
+ active = null;
32
+ constructor(agent) {
33
+ this.agent = agent;
34
+ }
35
+ /** Enter swarm mode with the given trigger. No-op if already active. */
36
+ enter(trigger) {
37
+ if (this.active !== null)
38
+ return;
39
+ this.agent.records.logRecord({
40
+ type: "swarm_mode.enter",
41
+ trigger,
42
+ });
43
+ this.active = trigger;
44
+ if (trigger !== "tool") {
45
+ this.agent.context.appendSystemReminder(SWARM_MODE_ENTER_REMINDER, { kind: "injection", variant: "swarm_mode" });
46
+ }
47
+ this.agent.emitStatusUpdated();
48
+ }
49
+ /** Restore a previously saved swarm mode trigger (e.g., on session resume). */
50
+ restoreEnter(trigger) {
51
+ this.active = trigger;
52
+ }
53
+ /** Exit swarm mode. No-op if not active. */
54
+ exit() {
55
+ if (this.active === null)
56
+ return;
57
+ this.agent.records.logRecord({ type: "swarm_mode.exit" });
58
+ const trigger = this.active;
59
+ this.active = null;
60
+ this.agent.emitStatusUpdated();
61
+ if (trigger === "tool")
62
+ return;
63
+ // Try to pop the enter reminder first
64
+ if (this.agent.context.popMatchedMessage((origin) => {
65
+ const o = origin;
66
+ return (o?.kind === "injection" &&
67
+ o?.variant === "swarm_mode");
68
+ })) {
69
+ return;
70
+ }
71
+ // If we couldn't pop it, inject an exit reminder
72
+ this.agent.context.appendSystemReminder(SWARM_MODE_EXIT_REMINDER, { kind: "injection", variant: "swarm_mode_exit" });
73
+ }
74
+ /** Whether swarm mode is currently active. */
75
+ get isActive() {
76
+ return this.active !== null;
77
+ }
78
+ /** Whether swarm mode should auto-exit after the current operation. */
79
+ get shouldAutoExit() {
80
+ return this.active === "task" || this.active === "tool";
81
+ }
82
+ /** The current trigger, or null if not active. */
83
+ get currentTrigger() {
84
+ return this.active;
85
+ }
86
+ }
87
+ //# sourceMappingURL=mode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mode.js","sourceRoot":"","sources":["../../src/swarm/mode.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAmCH,8EAA8E;AAC9E,6CAA6C;AAC7C,8EAA8E;AAE9E,MAAM,yBAAyB,GAAG;;;;;;;;CAQjC,CAAC,IAAI,EAAE,CAAC;AAET,MAAM,wBAAwB,GAAG;;;;CAIhC,CAAC,IAAI,EAAE,CAAC;AAET,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E,MAAM,OAAO,SAAS;IAGS;IAFrB,MAAM,GAA4B,IAAI,CAAC;IAE/C,YAA6B,KAAqB;QAArB,UAAK,GAAL,KAAK,CAAgB;IAAG,CAAC;IAEtD,wEAAwE;IACxE,KAAK,CAAC,OAAyB;QAC7B,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI;YAAE,OAAO;QAEjC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC;YAC3B,IAAI,EAAE,kBAAkB;YACxB,OAAO;SACR,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC;QAEtB,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;YACvB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,oBAAoB,CACrC,yBAAyB,EACzB,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,EAAE,CAC7C,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,KAAK,CAAC,iBAAiB,EAAE,CAAC;IACjC,CAAC;IAED,+EAA+E;IAC/E,YAAY,CAAC,OAAyB;QACpC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC;IACxB,CAAC;IAED,4CAA4C;IAC5C,IAAI;QACF,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI;YAAE,OAAO;QAEjC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC,CAAC;QAC1D,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC;QAC5B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,IAAI,CAAC,KAAK,CAAC,iBAAiB,EAAE,CAAC;QAE/B,IAAI,OAAO,KAAK,MAAM;YAAE,OAAO;QAE/B,sCAAsC;QACtC,IACE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,iBAAiB,CAClC,CAAC,MAAe,EAAE,EAAE;YAClB,MAAM,CAAC,GAAG,MAAoD,CAAC;YAC/D,OAAO,CACL,CAAC,EAAE,IAAI,KAAK,WAAW;gBACvB,CAAC,EAAE,OAAO,KAAK,YAAY,CAC5B,CAAC;QACJ,CAAC,CACF,EACD,CAAC;YACD,OAAO;QACT,CAAC;QAED,iDAAiD;QACjD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,oBAAoB,CACrC,wBAAwB,EACxB,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,iBAAiB,EAAE,CAClD,CAAC;IACJ,CAAC;IAED,8CAA8C;IAC9C,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC;IAC9B,CAAC;IAED,uEAAuE;IACvE,IAAI,cAAc;QAChB,OAAO,IAAI,CAAC,MAAM,KAAK,MAAM,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,CAAC;IAC1D,CAAC;IAED,kDAAkD;IAClD,IAAI,cAAc;QAChB,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;CACF"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * swarm/tool — AgentSwarm tool registration.
3
+ *
4
+ * Registers the `AgentSwarm` tool that the LLM can call to launch
5
+ * multiple subagents from a shared prompt template.
6
+ *
7
+ * Ported from MoonshotAI/kimi-code's AgentSwarmTool.
8
+ */
9
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
10
+ export declare function registerAgentSwarmTool(pi: ExtensionAPI): void;
11
+ //# sourceMappingURL=tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../../src/swarm/tool.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AA0CpE,wBAAgB,sBAAsB,CACpC,EAAE,EAAE,YAAY,GACf,IAAI,CAoIN"}