pilotswarm-sdk 0.1.3

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 (183) hide show
  1. package/dist/agent-loader.d.ts +61 -0
  2. package/dist/agent-loader.d.ts.map +1 -0
  3. package/dist/agent-loader.js +212 -0
  4. package/dist/agent-loader.js.map +1 -0
  5. package/dist/artifact-tools.d.ts +31 -0
  6. package/dist/artifact-tools.d.ts.map +1 -0
  7. package/dist/artifact-tools.js +190 -0
  8. package/dist/artifact-tools.js.map +1 -0
  9. package/dist/blob-store.d.ts +73 -0
  10. package/dist/blob-store.d.ts.map +1 -0
  11. package/dist/blob-store.js +220 -0
  12. package/dist/blob-store.js.map +1 -0
  13. package/dist/client.d.ts +159 -0
  14. package/dist/client.d.ts.map +1 -0
  15. package/dist/client.js +676 -0
  16. package/dist/client.js.map +1 -0
  17. package/dist/cms.d.ts +129 -0
  18. package/dist/cms.d.ts.map +1 -0
  19. package/dist/cms.js +313 -0
  20. package/dist/cms.js.map +1 -0
  21. package/dist/index.d.ts +44 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +42 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/managed-session.d.ts +70 -0
  26. package/dist/managed-session.d.ts.map +1 -0
  27. package/dist/managed-session.js +717 -0
  28. package/dist/managed-session.js.map +1 -0
  29. package/dist/management-client.d.ts +171 -0
  30. package/dist/management-client.d.ts.map +1 -0
  31. package/dist/management-client.js +401 -0
  32. package/dist/management-client.js.map +1 -0
  33. package/dist/mcp-loader.d.ts +50 -0
  34. package/dist/mcp-loader.d.ts.map +1 -0
  35. package/dist/mcp-loader.js +83 -0
  36. package/dist/mcp-loader.js.map +1 -0
  37. package/dist/model-providers.d.ts +143 -0
  38. package/dist/model-providers.d.ts.map +1 -0
  39. package/dist/model-providers.js +228 -0
  40. package/dist/model-providers.js.map +1 -0
  41. package/dist/orchestration-registry.d.ts +7 -0
  42. package/dist/orchestration-registry.d.ts.map +1 -0
  43. package/dist/orchestration-registry.js +49 -0
  44. package/dist/orchestration-registry.js.map +1 -0
  45. package/dist/orchestration.d.ts +36 -0
  46. package/dist/orchestration.d.ts.map +1 -0
  47. package/dist/orchestration.js +1357 -0
  48. package/dist/orchestration.js.map +1 -0
  49. package/dist/orchestration_1_0_0.d.ts +20 -0
  50. package/dist/orchestration_1_0_0.d.ts.map +1 -0
  51. package/dist/orchestration_1_0_0.js +497 -0
  52. package/dist/orchestration_1_0_0.js.map +1 -0
  53. package/dist/orchestration_1_0_1.d.ts +19 -0
  54. package/dist/orchestration_1_0_1.d.ts.map +1 -0
  55. package/dist/orchestration_1_0_1.js +546 -0
  56. package/dist/orchestration_1_0_1.js.map +1 -0
  57. package/dist/orchestration_1_0_10.d.ts +36 -0
  58. package/dist/orchestration_1_0_10.d.ts.map +1 -0
  59. package/dist/orchestration_1_0_10.js +1253 -0
  60. package/dist/orchestration_1_0_10.js.map +1 -0
  61. package/dist/orchestration_1_0_11.d.ts +36 -0
  62. package/dist/orchestration_1_0_11.d.ts.map +1 -0
  63. package/dist/orchestration_1_0_11.js +1255 -0
  64. package/dist/orchestration_1_0_11.js.map +1 -0
  65. package/dist/orchestration_1_0_12.d.ts +36 -0
  66. package/dist/orchestration_1_0_12.d.ts.map +1 -0
  67. package/dist/orchestration_1_0_12.js +1250 -0
  68. package/dist/orchestration_1_0_12.js.map +1 -0
  69. package/dist/orchestration_1_0_13.d.ts +36 -0
  70. package/dist/orchestration_1_0_13.d.ts.map +1 -0
  71. package/dist/orchestration_1_0_13.js +1260 -0
  72. package/dist/orchestration_1_0_13.js.map +1 -0
  73. package/dist/orchestration_1_0_14.d.ts +36 -0
  74. package/dist/orchestration_1_0_14.d.ts.map +1 -0
  75. package/dist/orchestration_1_0_14.js +1258 -0
  76. package/dist/orchestration_1_0_14.js.map +1 -0
  77. package/dist/orchestration_1_0_15.d.ts +36 -0
  78. package/dist/orchestration_1_0_15.d.ts.map +1 -0
  79. package/dist/orchestration_1_0_15.js +1266 -0
  80. package/dist/orchestration_1_0_15.js.map +1 -0
  81. package/dist/orchestration_1_0_16.d.ts +36 -0
  82. package/dist/orchestration_1_0_16.d.ts.map +1 -0
  83. package/dist/orchestration_1_0_16.js +1275 -0
  84. package/dist/orchestration_1_0_16.js.map +1 -0
  85. package/dist/orchestration_1_0_17.d.ts +36 -0
  86. package/dist/orchestration_1_0_17.d.ts.map +1 -0
  87. package/dist/orchestration_1_0_17.js +1314 -0
  88. package/dist/orchestration_1_0_17.js.map +1 -0
  89. package/dist/orchestration_1_0_18.d.ts +36 -0
  90. package/dist/orchestration_1_0_18.d.ts.map +1 -0
  91. package/dist/orchestration_1_0_18.js +1328 -0
  92. package/dist/orchestration_1_0_18.js.map +1 -0
  93. package/dist/orchestration_1_0_19.d.ts +36 -0
  94. package/dist/orchestration_1_0_19.d.ts.map +1 -0
  95. package/dist/orchestration_1_0_19.js +1324 -0
  96. package/dist/orchestration_1_0_19.js.map +1 -0
  97. package/dist/orchestration_1_0_2.d.ts +19 -0
  98. package/dist/orchestration_1_0_2.d.ts.map +1 -0
  99. package/dist/orchestration_1_0_2.js +749 -0
  100. package/dist/orchestration_1_0_2.js.map +1 -0
  101. package/dist/orchestration_1_0_20.d.ts +36 -0
  102. package/dist/orchestration_1_0_20.d.ts.map +1 -0
  103. package/dist/orchestration_1_0_20.js +1347 -0
  104. package/dist/orchestration_1_0_20.js.map +1 -0
  105. package/dist/orchestration_1_0_3.d.ts +19 -0
  106. package/dist/orchestration_1_0_3.d.ts.map +1 -0
  107. package/dist/orchestration_1_0_3.js +826 -0
  108. package/dist/orchestration_1_0_3.js.map +1 -0
  109. package/dist/orchestration_1_0_4.d.ts +19 -0
  110. package/dist/orchestration_1_0_4.d.ts.map +1 -0
  111. package/dist/orchestration_1_0_4.js +1020 -0
  112. package/dist/orchestration_1_0_4.js.map +1 -0
  113. package/dist/orchestration_1_0_5.d.ts +19 -0
  114. package/dist/orchestration_1_0_5.d.ts.map +1 -0
  115. package/dist/orchestration_1_0_5.js +1027 -0
  116. package/dist/orchestration_1_0_5.js.map +1 -0
  117. package/dist/orchestration_1_0_6.d.ts +19 -0
  118. package/dist/orchestration_1_0_6.d.ts.map +1 -0
  119. package/dist/orchestration_1_0_6.js +1034 -0
  120. package/dist/orchestration_1_0_6.js.map +1 -0
  121. package/dist/orchestration_1_0_7.d.ts +19 -0
  122. package/dist/orchestration_1_0_7.d.ts.map +1 -0
  123. package/dist/orchestration_1_0_7.js +1085 -0
  124. package/dist/orchestration_1_0_7.js.map +1 -0
  125. package/dist/orchestration_1_0_8.d.ts +36 -0
  126. package/dist/orchestration_1_0_8.d.ts.map +1 -0
  127. package/dist/orchestration_1_0_8.js +1106 -0
  128. package/dist/orchestration_1_0_8.js.map +1 -0
  129. package/dist/orchestration_1_0_9.d.ts +36 -0
  130. package/dist/orchestration_1_0_9.d.ts.map +1 -0
  131. package/dist/orchestration_1_0_9.js +1207 -0
  132. package/dist/orchestration_1_0_9.js.map +1 -0
  133. package/dist/prompt-layering.d.ts +16 -0
  134. package/dist/prompt-layering.d.ts.map +1 -0
  135. package/dist/prompt-layering.js +60 -0
  136. package/dist/prompt-layering.js.map +1 -0
  137. package/dist/resourcemgr-tools.d.ts +27 -0
  138. package/dist/resourcemgr-tools.d.ts.map +1 -0
  139. package/dist/resourcemgr-tools.js +638 -0
  140. package/dist/resourcemgr-tools.js.map +1 -0
  141. package/dist/session-dumper.d.ts +26 -0
  142. package/dist/session-dumper.d.ts.map +1 -0
  143. package/dist/session-dumper.js +272 -0
  144. package/dist/session-dumper.js.map +1 -0
  145. package/dist/session-manager.d.ts +152 -0
  146. package/dist/session-manager.d.ts.map +1 -0
  147. package/dist/session-manager.js +493 -0
  148. package/dist/session-manager.js.map +1 -0
  149. package/dist/session-proxy.d.ts +68 -0
  150. package/dist/session-proxy.d.ts.map +1 -0
  151. package/dist/session-proxy.js +665 -0
  152. package/dist/session-proxy.js.map +1 -0
  153. package/dist/session-store.d.ts +35 -0
  154. package/dist/session-store.d.ts.map +1 -0
  155. package/dist/session-store.js +88 -0
  156. package/dist/session-store.js.map +1 -0
  157. package/dist/skills.d.ts +31 -0
  158. package/dist/skills.d.ts.map +1 -0
  159. package/dist/skills.js +93 -0
  160. package/dist/skills.js.map +1 -0
  161. package/dist/sweeper-tools.d.ts +28 -0
  162. package/dist/sweeper-tools.d.ts.map +1 -0
  163. package/dist/sweeper-tools.js +332 -0
  164. package/dist/sweeper-tools.js.map +1 -0
  165. package/dist/types.d.ts +498 -0
  166. package/dist/types.d.ts.map +1 -0
  167. package/dist/types.js +9 -0
  168. package/dist/types.js.map +1 -0
  169. package/dist/worker.d.ts +128 -0
  170. package/dist/worker.d.ts.map +1 -0
  171. package/dist/worker.js +562 -0
  172. package/dist/worker.js.map +1 -0
  173. package/package.json +74 -0
  174. package/plugins/mgmt/agents/pilotswarm.agent.md +59 -0
  175. package/plugins/mgmt/agents/resourcemgr.agent.md +111 -0
  176. package/plugins/mgmt/agents/sweeper.agent.md +67 -0
  177. package/plugins/mgmt/skills/resourcemgr/SKILL.md +41 -0
  178. package/plugins/mgmt/skills/resourcemgr/tools.json +1 -0
  179. package/plugins/mgmt/skills/sweeper/SKILL.md +44 -0
  180. package/plugins/mgmt/skills/sweeper/tools.json +1 -0
  181. package/plugins/system/agents/default.agent.md +58 -0
  182. package/plugins/system/skills/durable-timers/SKILL.md +39 -0
  183. package/plugins/system/skills/sub-agents/SKILL.md +75 -0
@@ -0,0 +1,665 @@
1
+ import { SESSION_STATE_MISSING_PREFIX } from "./types.js";
2
+ import { systemChildAgentUUID } from "./agent-loader.js";
3
+ import { PilotSwarmClient } from "./client.js";
4
+ import os from "node:os";
5
+ // ─── SessionProxy ────────────────────────────────────────────────
6
+ // The orchestration's view of a specific ManagedSession.
7
+ // Each method maps 1:1 to an activity dispatched to the session's worker node.
8
+ export function createSessionProxy(ctx, sessionId, affinityKey, config) {
9
+ return {
10
+ runTurn(prompt, bootstrap, turnIndex) {
11
+ return ctx.scheduleActivityOnSession("runTurn", { sessionId, prompt, config, ...(bootstrap ? { bootstrap: true } : {}), ...(turnIndex != null ? { turnIndex } : {}) }, affinityKey);
12
+ },
13
+ dehydrate(reason) {
14
+ return ctx.scheduleActivityOnSession("dehydrateSession", { sessionId, reason }, affinityKey);
15
+ },
16
+ hydrate() {
17
+ return ctx.scheduleActivityOnSession("hydrateSession", { sessionId }, affinityKey);
18
+ },
19
+ needsHydration() {
20
+ return ctx.scheduleActivityOnSession("needsHydrationSession", { sessionId }, affinityKey);
21
+ },
22
+ destroy() {
23
+ return ctx.scheduleActivityOnSession("destroySession", { sessionId }, affinityKey);
24
+ },
25
+ checkpoint() {
26
+ return ctx.scheduleActivityOnSession("checkpointSession", { sessionId }, affinityKey);
27
+ },
28
+ };
29
+ }
30
+ // ─── SessionManagerProxy ─────────────────────────────────────────
31
+ // The orchestration's view of the SessionManager singleton.
32
+ // Operations that don't require session affinity.
33
+ export function createSessionManagerProxy(ctx) {
34
+ return {
35
+ listModels() {
36
+ return ctx.scheduleActivity("listModels", {});
37
+ },
38
+ summarizeSession(sessionId) {
39
+ return ctx.scheduleActivity("summarizeSession", { sessionId });
40
+ },
41
+ /** Spawn a child session via the PilotSwarmClient SDK. Returns the generated child session ID. */
42
+ spawnChildSession(parentSessionId, config, task, nestingLevel, isSystem, title, agentId, splash) {
43
+ return ctx.scheduleActivity("spawnChildSession", { parentSessionId, config, task, nestingLevel, isSystem, title, agentId, splash });
44
+ },
45
+ /** Resolve a loaded agent config by name. Returns null if not found. */
46
+ resolveAgentConfig(agentName) {
47
+ return ctx.scheduleActivity("resolveAgentConfig", { agentName });
48
+ },
49
+ /** Send a message to a session via the PilotSwarmClient SDK. */
50
+ sendToSession(sessionId, message) {
51
+ return ctx.scheduleActivity("sendToSession", { sessionId, message });
52
+ },
53
+ /** Send a raw command (JSON) directly to a session's event queue. */
54
+ sendCommandToSession(sessionId, command) {
55
+ return ctx.scheduleActivity("sendCommandToSession", { sessionId, command });
56
+ },
57
+ /** Get the status of a session via the PilotSwarmClient SDK. */
58
+ getSessionStatus(sessionId) {
59
+ return ctx.scheduleActivity("getSessionStatus", { sessionId });
60
+ },
61
+ /** List all sessions via the PilotSwarmClient SDK. */
62
+ listSessions() {
63
+ return ctx.scheduleActivity("listSessions", {});
64
+ },
65
+ /** @deprecated Send a child_updates event to a parent orchestration. Use sendToSession instead. */
66
+ notifyParent(parentOrchId, childOrchId, childSessionId, update) {
67
+ return ctx.scheduleActivity("notifyParent", { parentOrchId, childOrchId, childSessionId, update });
68
+ },
69
+ /** Get all descendant session IDs of a session (children, grandchildren, etc.). */
70
+ getDescendantSessionIds(sessionId) {
71
+ return ctx.scheduleActivity("getDescendantSessionIds", { sessionId });
72
+ },
73
+ /** Cancel a session's orchestration (terminates immediately). */
74
+ cancelSession(sessionId, reason) {
75
+ return ctx.scheduleActivity("cancelSession", { sessionId, reason });
76
+ },
77
+ /** Cancel a session's orchestration and delete it from CMS. */
78
+ deleteSession(sessionId, reason) {
79
+ return ctx.scheduleActivity("deleteSession", { sessionId, reason });
80
+ },
81
+ /** Update a session's CMS state (e.g. "rejected" for policy violations). */
82
+ updateCmsState(sessionId, state, lastError) {
83
+ return ctx.scheduleActivity("updateCmsState", { sessionId, state, ...(lastError ? { lastError } : {}) });
84
+ },
85
+ /** Get the worker's authoritative session policy + allowed agent names. */
86
+ getWorkerSessionPolicy() {
87
+ return ctx.scheduleActivity("getWorkerSessionPolicy", {});
88
+ },
89
+ };
90
+ }
91
+ // ─── Activity Registration ───────────────────────────────────────
92
+ // Thin dispatchers — each is a one-liner that calls the corresponding
93
+ // SessionManager or ManagedSession method.
94
+ export function registerActivities(runtime, sessionManager, _sessionStore, githubToken, catalog, provider, storeUrl, cmsSchema,
95
+ /** Client-level config forwarded to ephemeral PilotSwarmClient instances (e.g. spawnChildSession). */
96
+ clientConfig,
97
+ /** Loaded system agents — used by resolveAgentConfig activity. */
98
+ systemAgents,
99
+ /** Worker-level session policy — used by getWorkerSessionPolicy activity. */
100
+ workerSessionPolicy,
101
+ /** Names of loaded non-system agents — used by getWorkerSessionPolicy activity. */
102
+ workerAllowedAgentNames,
103
+ /** Loaded user-creatable agents — used by resolveAgentConfig activity. */
104
+ userAgents) {
105
+ // ── runTurn ──────────────────────────────────────────────
106
+ runtime.registerActivity("runTurn", async (activityCtx, input) => {
107
+ activityCtx.traceInfo(`[runTurn] session=${input.sessionId}`);
108
+ let session;
109
+ try {
110
+ session = await sessionManager.getOrCreate(input.sessionId, input.config, {
111
+ turnIndex: input.turnIndex,
112
+ });
113
+ }
114
+ catch (err) {
115
+ const message = err?.message || String(err);
116
+ if (message.includes(SESSION_STATE_MISSING_PREFIX)) {
117
+ if (catalog) {
118
+ await catalog.updateSession(input.sessionId, {
119
+ state: "failed",
120
+ lastError: message,
121
+ }).catch(() => { });
122
+ }
123
+ return { type: "error", message };
124
+ }
125
+ throw err;
126
+ }
127
+ // Cooperative cancellation: poll for lock steal
128
+ let cancelled = false;
129
+ const cancelPoll = setInterval(() => {
130
+ if (activityCtx.isCancelled()) {
131
+ cancelled = true;
132
+ session.abort();
133
+ clearInterval(cancelPoll);
134
+ }
135
+ }, 2_000);
136
+ try {
137
+ // Inject host info so LLM knows which worker it's on
138
+ const hostname = os.hostname();
139
+ const enrichedPrompt = `[SYSTEM: Running on host "${hostname}".]\n\n${input.prompt}`;
140
+ // Build onEvent callback: write each non-ephemeral event to CMS as it fires
141
+ const EPHEMERAL_TYPES = new Set([
142
+ "assistant.message_delta",
143
+ "assistant.reasoning_delta",
144
+ "user.message", // Already recorded explicitly above — skip the SDK's duplicate
145
+ ]);
146
+ const onEvent = catalog
147
+ ? (event) => {
148
+ if (EPHEMERAL_TYPES.has(event.eventType))
149
+ return;
150
+ catalog.recordEvents(input.sessionId, [event]).catch((err) => {
151
+ activityCtx.traceInfo(`[runTurn] CMS recordEvent failed: ${err}`);
152
+ });
153
+ }
154
+ : undefined;
155
+ // Record the user prompt as a CMS event before running the turn.
156
+ // Skip internal timer continuation prompts — they're system-generated, not user input.
157
+ const isTimerPrompt = /^The \d+ second wait is now complete\./i.test(input.prompt);
158
+ if (catalog && !isTimerPrompt && !input.bootstrap) {
159
+ catalog.recordEvents(input.sessionId, [{
160
+ eventType: "user.message",
161
+ data: { content: input.prompt },
162
+ }]).catch((err) => {
163
+ activityCtx.traceInfo(`[runTurn] CMS recordEvent (user) failed: ${err}`);
164
+ });
165
+ }
166
+ // Mark session as "running" in CMS before the turn
167
+ if (catalog) {
168
+ await catalog.updateSession(input.sessionId, {
169
+ state: "running",
170
+ lastActiveAt: new Date(),
171
+ }).catch((err) => {
172
+ activityCtx.traceInfo(`[runTurn] CMS pre-turn status update failed: ${err}`);
173
+ });
174
+ }
175
+ activityCtx.traceInfo(`[runTurn] invoking ManagedSession.runTurn for ${input.sessionId}`);
176
+ const result = await session.runTurn(enrichedPrompt, {
177
+ onEvent,
178
+ modelSummary: sessionManager.getModelSummary(),
179
+ bootstrap: input.bootstrap,
180
+ });
181
+ activityCtx.traceInfo(`[runTurn] ManagedSession.runTurn completed for ${input.sessionId} type=${result.type}`);
182
+ if (cancelled)
183
+ return { type: "cancelled" };
184
+ // ── Activity-level writeback: sync turn result → CMS ──
185
+ // This lets listSessions() read entirely from CMS without
186
+ // hitting duroxide for every session's customStatus.
187
+ if (catalog) {
188
+ const statusMap = {
189
+ completed: "idle", // orchestration decides idle vs completed; default to idle
190
+ wait: "waiting",
191
+ input_required: "input_required",
192
+ error: "error",
193
+ cancelled: "idle",
194
+ spawn_agent: "running",
195
+ message_agent: "running",
196
+ check_agents: "running",
197
+ wait_for_agents: "waiting",
198
+ list_sessions: "running",
199
+ complete_agent: "running",
200
+ cancel_agent: "running",
201
+ delete_agent: "running",
202
+ };
203
+ const liveStatus = statusMap[result.type] ?? "idle";
204
+ const updates = {
205
+ state: liveStatus,
206
+ lastActiveAt: new Date(),
207
+ };
208
+ if (result.type === "error") {
209
+ updates.lastError = result.message ?? null;
210
+ updates.waitReason = null;
211
+ }
212
+ else if (result.type === "wait") {
213
+ updates.waitReason = result.reason ?? null;
214
+ updates.lastError = null;
215
+ }
216
+ else if (result.type === "input_required") {
217
+ updates.waitReason = result.question ?? null;
218
+ updates.lastError = null;
219
+ }
220
+ else {
221
+ updates.waitReason = null;
222
+ updates.lastError = null;
223
+ }
224
+ await catalog.updateSession(input.sessionId, updates).catch((err) => {
225
+ activityCtx.traceInfo(`[runTurn] CMS post-turn status writeback failed: ${err}`);
226
+ });
227
+ }
228
+ return result;
229
+ }
230
+ finally {
231
+ clearInterval(cancelPoll);
232
+ }
233
+ });
234
+ // ── dehydrateSession ────────────────────────────────────
235
+ runtime.registerActivity("dehydrateSession", async (_ctx, input) => {
236
+ await sessionManager.dehydrate(input.sessionId, input.reason ?? "unknown");
237
+ });
238
+ runtime.registerActivity("needsHydrationSession", async (_activityCtx, input) => {
239
+ return sessionManager.needsHydration(input.sessionId);
240
+ });
241
+ // ── hydrateSession ──────────────────────────────────────
242
+ runtime.registerActivity("hydrateSession", async (_ctx, input) => {
243
+ await sessionManager.hydrate(input.sessionId);
244
+ });
245
+ // ── destroySession ──────────────────────────────────────
246
+ runtime.registerActivity("destroySession", async (_ctx, input) => {
247
+ await sessionManager.destroySession(input.sessionId);
248
+ });
249
+ // ── checkpointSession ───────────────────────────────────
250
+ runtime.registerActivity("checkpointSession", async (_ctx, input) => {
251
+ await sessionManager.checkpoint(input.sessionId);
252
+ });
253
+ // ── listModels ──────────────────────────────────────────
254
+ if (githubToken) {
255
+ runtime.registerActivity("listModels", async (activityCtx, _input) => {
256
+ activityCtx.traceInfo("[listModels] fetching");
257
+ const { CopilotClient } = await import("@github/copilot-sdk");
258
+ const sdk = new CopilotClient({ githubToken });
259
+ try {
260
+ await sdk.start();
261
+ const models = await sdk.listModels();
262
+ return JSON.stringify(models.map((m) => ({ id: m.id })));
263
+ }
264
+ finally {
265
+ try {
266
+ await sdk.stop();
267
+ }
268
+ catch { }
269
+ }
270
+ });
271
+ }
272
+ // ── summarizeSession ────────────────────────────────────
273
+ // Fetches recent conversation from CMS, asks a lightweight LLM
274
+ // for a 3-5 word title, and writes it back to CMS.
275
+ if (githubToken && catalog) {
276
+ runtime.registerActivity("summarizeSession", async (activityCtx, input) => {
277
+ activityCtx.traceInfo(`[summarizeSession] session=${input.sessionId}`);
278
+ // Never overwrite system session titles (e.g. "Sweeper Agent")
279
+ const session = await catalog.getSession(input.sessionId);
280
+ if (session?.isSystem) {
281
+ activityCtx.traceInfo(`[summarizeSession] skipping system session`);
282
+ return session.title || "";
283
+ }
284
+ // Named agent sessions have a title prefix (e.g. "Alpha Agent: <shortId>").
285
+ // Detect this so we can preserve the prefix after summarization.
286
+ const agentTitlePrefix = session?.agentId && session?.title?.includes(": ")
287
+ ? session.title.split(": ")[0]
288
+ : null;
289
+ const events = await catalog.getSessionEvents(input.sessionId, undefined, 50);
290
+ if (!events || events.length === 0)
291
+ return "";
292
+ // Build a condensed conversation transcript
293
+ const lines = [];
294
+ for (const evt of events) {
295
+ if (evt.eventType === "user.message") {
296
+ const content = evt.data?.content;
297
+ if (content)
298
+ lines.push(`User: ${content.slice(0, 200)}`);
299
+ }
300
+ else if (evt.eventType === "assistant.message") {
301
+ const content = evt.data?.content;
302
+ if (content)
303
+ lines.push(`Assistant: ${content.slice(0, 200)}`);
304
+ }
305
+ }
306
+ if (lines.length === 0)
307
+ return "";
308
+ const transcript = lines.join("\n");
309
+ const summaryPrompt = "Summarize the following conversation in exactly 3-5 words. " +
310
+ "Return ONLY the summary, nothing else. No quotes, no punctuation at the end.\n\n" +
311
+ transcript;
312
+ // Use a one-shot CopilotSession to generate the title
313
+ const { CopilotClient: SdkClient } = await import("@github/copilot-sdk");
314
+ const sdk = new SdkClient({ githubToken });
315
+ try {
316
+ await sdk.start();
317
+ const tempSession = await sdk.createSession({ model: "gpt-4o-mini", onPermissionRequest: async () => ({ kind: "approved" }) });
318
+ let title = "";
319
+ await new Promise((resolve, reject) => {
320
+ tempSession.on("assistant.message", (event) => {
321
+ title = (event.data?.content || "").trim();
322
+ });
323
+ tempSession.on("session.idle", () => resolve());
324
+ tempSession.on("session.error", (event) => reject(new Error(event.data?.message || "session error")));
325
+ tempSession.send({ prompt: summaryPrompt });
326
+ });
327
+ await sdk.stop();
328
+ // Truncate to 60 chars max
329
+ title = title.slice(0, 60);
330
+ if (title) {
331
+ // Preserve named agent prefix: "Alpha Agent: <summary>"
332
+ const finalTitle = agentTitlePrefix ? `${agentTitlePrefix}: ${title}` : title;
333
+ await catalog.updateSession(input.sessionId, { title: finalTitle });
334
+ activityCtx.traceInfo(`[summarizeSession] title="${finalTitle}"`);
335
+ }
336
+ return title;
337
+ }
338
+ catch (err) {
339
+ activityCtx.traceInfo(`[summarizeSession] failed: ${err.message}`);
340
+ try {
341
+ await sdk.stop();
342
+ }
343
+ catch { }
344
+ return "";
345
+ }
346
+ });
347
+ }
348
+ // ── resolveAgentConfig ────────────────────────────────────
349
+ // Resolves a loaded agent definition by name. Used by spawn_agent
350
+ // with agent_name to look up the agent's prompt, tools, and initial prompt.
351
+ runtime.registerActivity("resolveAgentConfig", async (_activityCtx, input) => {
352
+ const agents = [...(systemAgents ?? []), ...(userAgents ?? []).map(a => ({ ...a, system: false }))];
353
+ const normalize = (value) => (value || "").toLowerCase().replace(/[^a-z0-9]+/g, "");
354
+ // Support qualified names: "smelter:supervisor" → namespace="smelter", name="supervisor"
355
+ let lookupNamespace;
356
+ let rawName = input.agentName;
357
+ if (input.agentName.includes(":")) {
358
+ const parts = input.agentName.split(":");
359
+ lookupNamespace = parts[0];
360
+ rawName = parts.slice(1).join(":");
361
+ }
362
+ const lookup = normalize(rawName);
363
+ // Also try without trailing "agent" suffix for fuzzy matching
364
+ // (LLM often says "Sweeper agent" which normalizes to "sweeperagent", but id is "sweeper")
365
+ const lookupBase = lookup.replace(/agent$/, "");
366
+ const agent = agents.find(a => {
367
+ // If namespace qualifier provided, check it matches
368
+ if (lookupNamespace && normalize(a.namespace) !== normalize(lookupNamespace))
369
+ return false;
370
+ const candidates = [a.name, a.id, a.title].map(normalize).filter(Boolean);
371
+ return candidates.includes(lookup) || (lookupBase && candidates.includes(lookupBase));
372
+ });
373
+ if (!agent)
374
+ return null;
375
+ return {
376
+ name: agent.name,
377
+ prompt: agent.prompt,
378
+ tools: agent.tools ?? undefined,
379
+ initialPrompt: agent.initialPrompt ?? undefined,
380
+ title: agent.title ?? undefined,
381
+ system: agent.system ?? undefined,
382
+ id: agent.id ?? undefined,
383
+ parent: agent.parent ?? undefined,
384
+ splash: agent.splash ?? undefined,
385
+ namespace: agent.namespace ?? undefined,
386
+ promptLayerKind: agent.promptLayerKind ?? undefined,
387
+ };
388
+ });
389
+ // ── spawnChildSession ─────────────────────────────────────
390
+ // Creates a child session via the PilotSwarmClient SDK.
391
+ // System child agents with a stable agentId use a deterministic UUID.
392
+ // Other child sessions use a random UUID.
393
+ // Goes through the full SDK path: CMS registration + orchestration startup.
394
+ runtime.registerActivity("spawnChildSession", async (activityCtx, input) => {
395
+ const isDeterministicSystemChild = Boolean(input.isSystem && input.agentId);
396
+ const childSessionId = isDeterministicSystemChild
397
+ ? systemChildAgentUUID(input.parentSessionId, input.agentId)
398
+ : crypto.randomUUID();
399
+ activityCtx.traceInfo(`[spawnChildSession] child=${childSessionId} parent=${input.parentSessionId} nesting=${input.nestingLevel ?? 0} isSystem=${input.isSystem ?? false} agent=${input.agentId ?? "custom"}`);
400
+ if (!storeUrl)
401
+ throw new Error("No storeUrl — cannot create PilotSwarmClient");
402
+ const sdkClient = new PilotSwarmClient({
403
+ store: storeUrl,
404
+ cmsSchema,
405
+ // Forward blob/dehydration config so child orchestrations inherit the parent's settings
406
+ ...(clientConfig?.blobEnabled != null && { blobEnabled: clientConfig.blobEnabled }),
407
+ ...(clientConfig?.duroxideSchema != null && { duroxideSchema: clientConfig.duroxideSchema }),
408
+ });
409
+ try {
410
+ await sdkClient.start();
411
+ if (isDeterministicSystemChild && catalog) {
412
+ const existing = await catalog.getSession(childSessionId);
413
+ if (existing && !["completed", "failed", "terminated"].includes(existing.state)) {
414
+ activityCtx.traceInfo(`[spawnChildSession] reusing existing live system child: ${childSessionId}`);
415
+ return childSessionId;
416
+ }
417
+ }
418
+ // Mark as system session BEFORE createSession so OrchestrationInput gets isSystem=true
419
+ if (input.isSystem) {
420
+ sdkClient.systemSessions.add(childSessionId);
421
+ }
422
+ // Child sessions may inherit a parent model that was created with a
423
+ // bare alias such as "gpt-4.1". Normalize it here, but do not
424
+ // require that the stored value is already provider-qualified.
425
+ const normalizedModel = sessionManager.normalizeModelRef(input.config.model);
426
+ if (normalizedModel) {
427
+ input.config.model = normalizedModel;
428
+ }
429
+ // Create the child session via the SDK — handles CMS row + orchestration start
430
+ const session = await sdkClient.createSession({
431
+ sessionId: childSessionId,
432
+ parentSessionId: input.parentSessionId,
433
+ nestingLevel: input.nestingLevel,
434
+ model: input.config.model,
435
+ systemMessage: input.config.systemMessage,
436
+ boundAgentName: input.config.boundAgentName,
437
+ promptLayering: input.config.promptLayering,
438
+ toolNames: input.config.toolNames,
439
+ waitThreshold: input.config.waitThreshold,
440
+ });
441
+ // One-time metadata write: isSystem, title, agentId, splash
442
+ const meta = {};
443
+ if (input.isSystem)
444
+ meta.isSystem = true;
445
+ // Named agents get a prefixed title: "Agent Title: <shortId>"
446
+ // System agents keep their fixed title as-is.
447
+ if (input.title && !input.isSystem) {
448
+ meta.title = `${input.title}: ${childSessionId.slice(0, 8)}`;
449
+ }
450
+ else if (input.title) {
451
+ meta.title = input.title;
452
+ }
453
+ if (input.agentId)
454
+ meta.agentId = input.agentId;
455
+ if (input.splash)
456
+ meta.splash = input.splash;
457
+ if (Object.keys(meta).length > 0 && catalog) {
458
+ await catalog.updateSession(childSessionId, meta);
459
+ }
460
+ // Fire the initial task prompt (non-blocking: just enqueues).
461
+ // This prompt is orchestration-generated bootstrap state for the child
462
+ // session, not an actual user-authored message inside that child chat.
463
+ await session.send(input.task, { bootstrap: true });
464
+ activityCtx.traceInfo(`[spawnChildSession] session created and task sent: ${childSessionId}`);
465
+ return childSessionId;
466
+ }
467
+ finally {
468
+ await sdkClient.stop();
469
+ }
470
+ });
471
+ // ── sendToSession ───────────────────────────────────────
472
+ // Sends a message to any session's orchestration event queue directly.
473
+ // Does NOT call session.send() (which tries to start/resume the orchestration).
474
+ // Instead, enqueues directly to the existing orchestration's "messages" queue.
475
+ runtime.registerActivity("sendToSession", async (activityCtx, input) => {
476
+ activityCtx.traceInfo(`[sendToSession] session=${input.sessionId} msg="${input.message.slice(0, 60)}"`);
477
+ if (!storeUrl)
478
+ throw new Error("No storeUrl — cannot create PilotSwarmClient");
479
+ const sdkClient = new PilotSwarmClient({
480
+ store: storeUrl,
481
+ cmsSchema,
482
+ });
483
+ try {
484
+ await sdkClient.start();
485
+ // Enqueue directly to the orchestration's event queue
486
+ const orchestrationId = `session-${input.sessionId}`;
487
+ await sdkClient.duroxideClient.enqueueEvent(orchestrationId, "messages", JSON.stringify({ prompt: input.message }));
488
+ activityCtx.traceInfo(`[sendToSession] enqueued to ${orchestrationId}`);
489
+ }
490
+ finally {
491
+ await sdkClient.stop();
492
+ }
493
+ });
494
+ // ── sendCommandToSession ────────────────────────────────
495
+ // Sends a raw JSON command directly to a session's orchestration event queue.
496
+ // Unlike sendToSession, this does NOT wrap the payload in { prompt: ... }.
497
+ runtime.registerActivity("sendCommandToSession", async (activityCtx, input) => {
498
+ activityCtx.traceInfo(`[sendCommandToSession] session=${input.sessionId} cmd=${input.command?.cmd}`);
499
+ if (!storeUrl)
500
+ throw new Error("No storeUrl — cannot create PilotSwarmClient");
501
+ const sdkClient = new PilotSwarmClient({
502
+ store: storeUrl,
503
+ cmsSchema,
504
+ });
505
+ try {
506
+ await sdkClient.start();
507
+ const orchestrationId = `session-${input.sessionId}`;
508
+ await sdkClient.duroxideClient.enqueueEvent(orchestrationId, "messages", JSON.stringify(input.command));
509
+ activityCtx.traceInfo(`[sendCommandToSession] enqueued to ${orchestrationId}`);
510
+ }
511
+ finally {
512
+ await sdkClient.stop();
513
+ }
514
+ });
515
+ // ── getSessionStatus ────────────────────────────────────
516
+ // Gets the status of a session via the PilotSwarmClient SDK.
517
+ runtime.registerActivity("getSessionStatus", async (activityCtx, input) => {
518
+ activityCtx.traceInfo(`[getSessionStatus] session=${input.sessionId}`);
519
+ if (!storeUrl)
520
+ throw new Error("No storeUrl — cannot create PilotSwarmClient");
521
+ const sdkClient = new PilotSwarmClient({
522
+ store: storeUrl,
523
+ cmsSchema,
524
+ });
525
+ try {
526
+ await sdkClient.start();
527
+ const info = await sdkClient._getSessionInfo(input.sessionId);
528
+ return JSON.stringify({
529
+ sessionId: info.sessionId,
530
+ status: info.status,
531
+ title: info.title,
532
+ iterations: info.iterations,
533
+ result: info.result,
534
+ error: info.error,
535
+ });
536
+ }
537
+ finally {
538
+ await sdkClient.stop();
539
+ }
540
+ });
541
+ // ── listSessions ────────────────────────────────────────
542
+ // Lists all sessions via the PilotSwarmClient SDK.
543
+ runtime.registerActivity("listSessions", async (activityCtx, _input) => {
544
+ activityCtx.traceInfo(`[listSessions]`);
545
+ if (!storeUrl)
546
+ throw new Error("No storeUrl — cannot create PilotSwarmClient");
547
+ const sdkClient = new PilotSwarmClient({
548
+ store: storeUrl,
549
+ cmsSchema,
550
+ });
551
+ try {
552
+ await sdkClient.start();
553
+ const sessions = await sdkClient.listSessions();
554
+ return JSON.stringify(sessions.map(s => ({
555
+ sessionId: s.sessionId,
556
+ title: s.title,
557
+ status: s.status,
558
+ iterations: s.iterations,
559
+ parentSessionId: s.parentSessionId,
560
+ error: s.error,
561
+ })));
562
+ }
563
+ finally {
564
+ await sdkClient.stop();
565
+ }
566
+ });
567
+ // ── notifyParent ────────────────────────────────────────
568
+ // Sends a child_updates event to the parent orchestration so it can
569
+ // wake up from durable sleep and process the child's result.
570
+ // Uses raw enqueueEvent because it targets the "child_updates" queue,
571
+ // not the standard "messages" queue that session.send() uses.
572
+ runtime.registerActivity("notifyParent", async (activityCtx, input) => {
573
+ activityCtx.traceInfo(`[notifyParent] parent=${input.parentOrchId} child=${input.childOrchId} type=${input.update?.type}`);
574
+ if (!provider)
575
+ throw new Error("No provider available");
576
+ const { Client } = (await import("node:module")).createRequire(import.meta.url)("duroxide");
577
+ const client = new Client(provider);
578
+ await client.enqueueEvent(input.parentOrchId, "child_updates", JSON.stringify({
579
+ childOrchId: input.childOrchId,
580
+ childSessionId: input.childSessionId,
581
+ ...input.update,
582
+ }));
583
+ });
584
+ // ── getDescendantSessionIds ──────────────────────────────
585
+ // Returns all descendant session IDs (children, grandchildren, etc.)
586
+ // Used by cancel/delete to cascade to grandchildren.
587
+ runtime.registerActivity("getDescendantSessionIds", async (activityCtx, input) => {
588
+ activityCtx.traceInfo(`[getDescendantSessionIds] session=${input.sessionId}`);
589
+ if (!catalog)
590
+ return [];
591
+ const descendants = await catalog.getDescendantSessionIds(input.sessionId);
592
+ activityCtx.traceInfo(`[getDescendantSessionIds] found ${descendants.length} descendants`);
593
+ return descendants;
594
+ });
595
+ // ── cancelSession ───────────────────────────────────────
596
+ // Cancels a session's orchestration (terminates immediately).
597
+ runtime.registerActivity("cancelSession", async (activityCtx, input) => {
598
+ activityCtx.traceInfo(`[cancelSession] session=${input.sessionId} reason=${input.reason ?? "none"}`);
599
+ if (!storeUrl)
600
+ throw new Error("No storeUrl — cannot create PilotSwarmClient");
601
+ const sdkClient = new PilotSwarmClient({
602
+ store: storeUrl,
603
+ cmsSchema,
604
+ });
605
+ try {
606
+ await sdkClient.start();
607
+ const orchestrationId = `session-${input.sessionId}`;
608
+ // Cancel the orchestration via duroxide
609
+ await sdkClient.duroxideClient.cancelInstance(orchestrationId, input.reason ?? "Cancelled by parent");
610
+ // Update CMS status
611
+ if (catalog) {
612
+ await catalog.updateSession(input.sessionId, {
613
+ state: "completed",
614
+ lastError: input.reason ? `Cancelled: ${input.reason}` : "Cancelled",
615
+ });
616
+ }
617
+ activityCtx.traceInfo(`[cancelSession] cancelled ${orchestrationId}`);
618
+ }
619
+ finally {
620
+ await sdkClient.stop();
621
+ }
622
+ });
623
+ // ── deleteSession ───────────────────────────────────────
624
+ // Cancels a session's orchestration AND removes it from CMS.
625
+ runtime.registerActivity("deleteSession", async (activityCtx, input) => {
626
+ activityCtx.traceInfo(`[deleteSession] session=${input.sessionId} reason=${input.reason ?? "none"}`);
627
+ if (!storeUrl)
628
+ throw new Error("No storeUrl — cannot create PilotSwarmClient");
629
+ const sdkClient = new PilotSwarmClient({
630
+ store: storeUrl,
631
+ cmsSchema,
632
+ });
633
+ try {
634
+ await sdkClient.start();
635
+ // This does both: CMS soft-delete + duroxide cancel
636
+ await sdkClient.deleteSession(input.sessionId);
637
+ activityCtx.traceInfo(`[deleteSession] deleted session-${input.sessionId}`);
638
+ }
639
+ finally {
640
+ await sdkClient.stop();
641
+ }
642
+ });
643
+ // ── updateCmsState ─────────────────────────────────────
644
+ // Updates a session's state in CMS (e.g. "rejected" for policy violations).
645
+ if (catalog) {
646
+ runtime.registerActivity("updateCmsState", async (activityCtx, input) => {
647
+ activityCtx.traceInfo(`[updateCmsState] session=${input.sessionId} state=${input.state}`);
648
+ await catalog.updateSession(input.sessionId, {
649
+ state: input.state,
650
+ ...(input.lastError ? { lastError: input.lastError } : {}),
651
+ });
652
+ });
653
+ }
654
+ // ── getWorkerSessionPolicy ──────────────────────────────
655
+ // Returns the worker's session policy and allowed agent names.
656
+ // This is the authoritative source — even if a rogue client omits policy
657
+ // from the OrchestrationInput, the orchestration can fetch it from the worker.
658
+ runtime.registerActivity("getWorkerSessionPolicy", async (_activityCtx, _input) => {
659
+ return {
660
+ policy: workerSessionPolicy ?? null,
661
+ allowedAgentNames: workerAllowedAgentNames ?? [],
662
+ };
663
+ });
664
+ }
665
+ //# sourceMappingURL=session-proxy.js.map