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
package/dist/client.js ADDED
@@ -0,0 +1,676 @@
1
+ import { RESPONSE_LATEST_KEY, } from "./types.js";
2
+ import { DURABLE_SESSION_LATEST_VERSION, DURABLE_SESSION_ORCHESTRATION_NAME, } from "./orchestration-registry.js";
3
+ import { PgSessionCatalogProvider } from "./cms.js";
4
+ // duroxide is CommonJS — use createRequire for ESM compatibility
5
+ import { createRequire } from "node:module";
6
+ const require = createRequire(import.meta.url);
7
+ const { SqliteProvider, PostgresProvider, Client } = require("duroxide");
8
+ const DEFAULT_DUROXIDE_SCHEMA = "duroxide";
9
+ /**
10
+ * PilotSwarmClient — pure client-side session handle.
11
+ *
12
+ * Talks to duroxide only through the Client API (startOrchestration,
13
+ * enqueueEvent, waitForStatusChange, getStatus). Does NOT own
14
+ * SessionManager, Runtime, or CopilotSession.
15
+ *
16
+ * Creates its own duroxide Client and CMS catalog from the store URL.
17
+ * Completely independent of PilotSwarmWorker.
18
+ */
19
+ export class PilotSwarmClient {
20
+ config;
21
+ _catalog;
22
+ duroxideClient = null;
23
+ sessionConfigs = new Map();
24
+ /** parentSessionId for sub-agent sessions. */
25
+ parentSessionIds = new Map();
26
+ /** nestingLevel for sub-agent sessions. */
27
+ nestingLevels = new Map();
28
+ /** System session flag. */
29
+ systemSessions = new Set();
30
+ activeOrchestrations = new Map();
31
+ lastSeenStatusVersion = new Map();
32
+ lastSeenIteration = new Map();
33
+ lastSeenResponseVersion = new Map();
34
+ started = false;
35
+ /** Tracks agentId bound to each session (for policy and title prefixing). */
36
+ sessionAgentIds = new Map();
37
+ /** Effective session policy (set via config from worker). */
38
+ get _sessionPolicy() {
39
+ return this.config.sessionPolicy ?? null;
40
+ }
41
+ /** Allowed agent names (set via config from worker). */
42
+ get _allowedAgentNames() {
43
+ return this.config.allowedAgentNames ?? [];
44
+ }
45
+ constructor(options) {
46
+ this.config = {
47
+ ...options,
48
+ waitThreshold: options.waitThreshold ?? 30,
49
+ };
50
+ }
51
+ // ─── Session Management ──────────────────────────────────
52
+ async createSession(config) {
53
+ // ── Policy enforcement (client-side) ─────────────────
54
+ const policy = this._sessionPolicy;
55
+ const isSubAgent = !!config?.parentSessionId;
56
+ if (policy && policy.creation?.mode === "allowlist" && !isSubAgent) {
57
+ const agentId = config?.agentId;
58
+ if (!agentId && !policy.creation.allowGeneric) {
59
+ throw new Error("Session creation policy violation: generic sessions are not allowed. " +
60
+ "Use createSessionForAgent() to specify an agent.");
61
+ }
62
+ if (agentId && !this._allowedAgentNames.includes(agentId)) {
63
+ throw new Error(`Session creation policy violation: agent "${agentId}" is not in the allowed agent list.`);
64
+ }
65
+ }
66
+ const sessionId = config?.sessionId ?? crypto.randomUUID();
67
+ if (config) {
68
+ const fullConfig = {
69
+ model: config.model,
70
+ systemMessage: config.systemMessage,
71
+ boundAgentName: config.boundAgentName,
72
+ promptLayering: config.promptLayering,
73
+ tools: config.tools,
74
+ workingDirectory: config.workingDirectory,
75
+ hooks: config.hooks,
76
+ waitThreshold: config.waitThreshold ?? this.config.waitThreshold,
77
+ toolNames: config.toolNames,
78
+ };
79
+ this.sessionConfigs.set(sessionId, fullConfig);
80
+ }
81
+ // CMS: write session record (state=pending, no orchestration yet)
82
+ await this._catalog.createSession(sessionId, {
83
+ model: config?.model,
84
+ parentSessionId: config?.parentSessionId,
85
+ });
86
+ // Track parentSessionId for sub-agent orchestration input
87
+ if (config?.parentSessionId) {
88
+ this.parentSessionIds.set(sessionId, config.parentSessionId);
89
+ }
90
+ // Track nestingLevel for sub-agent depth enforcement
91
+ if (config?.nestingLevel != null) {
92
+ this.nestingLevels.set(sessionId, config.nestingLevel);
93
+ }
94
+ // Track agentId for orchestration input
95
+ if (config?.agentId) {
96
+ this.sessionAgentIds.set(sessionId, config.agentId);
97
+ }
98
+ return new PilotSwarmSession(sessionId, this, config?.onUserInputRequest);
99
+ }
100
+ /**
101
+ * Create a session bound to a named agent.
102
+ *
103
+ * Validates that the agent exists in the loaded (non-system) agent list.
104
+ * Sets the agentId on the session and applies a prefixed title:
105
+ * `"Agent Title: <shortId>"`.
106
+ *
107
+ * @throws If the agent is not found, is a system agent, or policy rejects it.
108
+ */
109
+ async createSessionForAgent(agentName, opts) {
110
+ // Validate the agent exists and is non-system
111
+ const allowed = this._allowedAgentNames;
112
+ if (!allowed.includes(agentName)) {
113
+ throw new Error(`Cannot create session for agent "${agentName}": not found in loaded agents or is a system agent.`);
114
+ }
115
+ const session = await this.createSession({
116
+ model: opts?.model,
117
+ toolNames: opts?.toolNames,
118
+ onUserInputRequest: opts?.onUserInputRequest,
119
+ agentId: agentName,
120
+ boundAgentName: agentName,
121
+ promptLayering: { kind: "app-agent" },
122
+ });
123
+ // Set agent metadata in CMS (agentId + prefixed title)
124
+ const shortId = session.sessionId.slice(0, 8);
125
+ const agentTitle = opts?.title || (agentName.charAt(0).toUpperCase() + agentName.slice(1));
126
+ await this._catalog.updateSession(session.sessionId, {
127
+ agentId: agentName,
128
+ title: `${agentTitle}: ${shortId}`,
129
+ ...(opts?.splash ? { splash: opts.splash } : {}),
130
+ });
131
+ if (opts?.initialPrompt) {
132
+ await session.send(opts.initialPrompt, { bootstrap: true });
133
+ }
134
+ return session;
135
+ }
136
+ /**
137
+ * Create a system session (e.g. Sweeper Agent).
138
+ *
139
+ * System sessions are protected from deletion and appear with distinct
140
+ * styling in the TUI. They use the same orchestration as regular sessions.
141
+ * Idempotent: if a system session already exists, it is resumed.
142
+ */
143
+ async createSystemSession(config) {
144
+ // Check if a system session already exists — resume it
145
+ const existingSessions = await this._catalog.listSessions();
146
+ const existing = existingSessions.find(s => s.isSystem);
147
+ if (existing) {
148
+ this.systemSessions.add(existing.sessionId);
149
+ return this.resumeSession(existing.sessionId, {
150
+ model: config.model,
151
+ systemMessage: config.systemMessage,
152
+ toolNames: config.toolNames,
153
+ onUserInputRequest: config.onUserInputRequest,
154
+ });
155
+ }
156
+ const sessionId = crypto.randomUUID();
157
+ this.systemSessions.add(sessionId);
158
+ const fullConfig = {
159
+ model: config.model,
160
+ systemMessage: config.systemMessage,
161
+ toolNames: config.toolNames,
162
+ };
163
+ this.sessionConfigs.set(sessionId, fullConfig);
164
+ // CMS: create with is_system = true
165
+ await this._catalog.createSession(sessionId, {
166
+ model: config.model,
167
+ isSystem: true,
168
+ });
169
+ // Set a fixed title immediately
170
+ if (config.title) {
171
+ await this._catalog.updateSession(sessionId, { title: config.title });
172
+ }
173
+ return new PilotSwarmSession(sessionId, this, config.onUserInputRequest);
174
+ }
175
+ async resumeSession(sessionId, config) {
176
+ if (config) {
177
+ this.sessionConfigs.set(sessionId, config);
178
+ }
179
+ // Mark orchestration as active so _ensureOrchestrationAndSend skips creation.
180
+ // The orchestration should already be running for resumed sessions.
181
+ const orchestrationId = `session-${sessionId}`;
182
+ this.activeOrchestrations.set(sessionId, orchestrationId);
183
+ // Sync tracking state from the live orchestration so the client
184
+ // doesn't mistake pre-existing KV data (from prior turns) as new.
185
+ // Without this, sendAndWait returns stale responses after worker restarts.
186
+ try {
187
+ const orchStatus = await this.duroxideClient.getStatus(orchestrationId);
188
+ if (orchStatus.customStatusVersion) {
189
+ this.lastSeenStatusVersion.set(orchestrationId, orchStatus.customStatusVersion);
190
+ }
191
+ if (orchStatus.customStatus) {
192
+ const cs = typeof orchStatus.customStatus === "string"
193
+ ? JSON.parse(orchStatus.customStatus) : orchStatus.customStatus;
194
+ if (cs.iteration != null) {
195
+ this.lastSeenIteration.set(orchestrationId, cs.iteration);
196
+ }
197
+ if (cs.responseVersion != null) {
198
+ this.lastSeenResponseVersion.set(orchestrationId, cs.responseVersion);
199
+ }
200
+ }
201
+ }
202
+ catch {
203
+ // Best-effort — if getStatus fails, we'll still work (may see a stale response on first poll)
204
+ }
205
+ return new PilotSwarmSession(sessionId, this, config?.onUserInputRequest);
206
+ }
207
+ async listSessions() {
208
+ const rows = await this._catalog.listSessions();
209
+ return rows.map(row => ({
210
+ sessionId: row.sessionId,
211
+ status: row.state ?? "pending",
212
+ title: row.title ?? undefined,
213
+ createdAt: row.createdAt,
214
+ updatedAt: row.updatedAt,
215
+ iterations: row.currentIteration,
216
+ error: row.lastError ?? undefined,
217
+ parentSessionId: row.parentSessionId ?? undefined,
218
+ isSystem: row.isSystem || undefined,
219
+ agentId: row.agentId ?? undefined,
220
+ splash: row.splash ?? undefined,
221
+ }));
222
+ }
223
+ async deleteSession(sessionId) {
224
+ // Guard: refuse to delete system sessions (CMS will also throw)
225
+ const session = await this._catalog.getSession(sessionId);
226
+ if (session?.isSystem) {
227
+ throw new Error("Cannot delete system session");
228
+ }
229
+ this.sessionConfigs.delete(sessionId);
230
+ this.parentSessionIds.delete(sessionId);
231
+ this.nestingLevels.delete(sessionId);
232
+ // CMS: soft-delete (source of truth)
233
+ await this._catalog.softDeleteSession(sessionId);
234
+ // Duroxide: cancel orchestration (best effort)
235
+ const orchestrationId = `session-${sessionId}`;
236
+ if (this.duroxideClient) {
237
+ try {
238
+ await this.duroxideClient.cancelInstance(orchestrationId, "Session deleted");
239
+ }
240
+ catch { }
241
+ }
242
+ this.activeOrchestrations.delete(sessionId);
243
+ }
244
+ // ─── Lifecycle ───────────────────────────────────────────
245
+ async start() {
246
+ if (this.started)
247
+ return;
248
+ const store = this.config.store;
249
+ const _trace = this.config.traceWriter ?? (() => { });
250
+ // Create duroxide client
251
+ let provider;
252
+ if (store === "sqlite::memory:")
253
+ provider = SqliteProvider.inMemory();
254
+ else if (store.startsWith("sqlite://"))
255
+ provider = SqliteProvider.open(store);
256
+ else if (store.startsWith("postgres://") || store.startsWith("postgresql://")) {
257
+ _trace("[client] connectWithSchema start...");
258
+ provider = await PostgresProvider.connectWithSchema(store, this.config.duroxideSchema ?? DEFAULT_DUROXIDE_SCHEMA);
259
+ _trace("[client] connectWithSchema done");
260
+ }
261
+ else {
262
+ throw new Error(`Unsupported store URL: ${store}`);
263
+ }
264
+ this.duroxideClient = new Client(provider);
265
+ // Create CMS catalog
266
+ if (store.startsWith("postgres://") || store.startsWith("postgresql://")) {
267
+ _trace("[client] CMS create start...");
268
+ this._catalog = await PgSessionCatalogProvider.create(store, this.config.cmsSchema);
269
+ _trace("[client] CMS initialize start...");
270
+ await this._catalog.initialize();
271
+ _trace("[client] CMS initialize done");
272
+ }
273
+ this.started = true;
274
+ }
275
+ async stop() {
276
+ this.duroxideClient = null;
277
+ this.started = false;
278
+ }
279
+ // ─── Internal ────────────────────────────────────────────
280
+ get _blobEnabled() {
281
+ return this.config.blobEnabled ?? false;
282
+ }
283
+ /** @internal — ensure orchestration exists, update CMS, enqueue prompt. */
284
+ async _ensureOrchestrationAndSend(sessionId, prompt, opts) {
285
+ if (!this.duroxideClient)
286
+ throw new Error("Not started.");
287
+ const orchestrationId = `session-${sessionId}`;
288
+ const fullConfig = this.sessionConfigs.get(sessionId);
289
+ // Build toolNames: merge explicit toolNames with names extracted from Tool objects.
290
+ const explicitNames = fullConfig?.toolNames ?? [];
291
+ const objectNames = (fullConfig?.tools ?? [])
292
+ .map((t) => typeof t === "string" ? t : t.name)
293
+ .filter((n) => n && n !== "wait" && n !== "ask_user");
294
+ const allNames = [...new Set([...explicitNames, ...objectNames])];
295
+ const serializableConfig = {
296
+ model: fullConfig?.model,
297
+ systemMessage: fullConfig?.systemMessage,
298
+ workingDirectory: fullConfig?.workingDirectory,
299
+ waitThreshold: fullConfig?.waitThreshold ?? this.config.waitThreshold,
300
+ boundAgentName: fullConfig?.boundAgentName,
301
+ promptLayering: fullConfig?.promptLayering,
302
+ toolNames: allNames.length ? allNames : undefined,
303
+ };
304
+ if (!this.activeOrchestrations.has(sessionId)) {
305
+ const parentSessionId = this.parentSessionIds.get(sessionId);
306
+ const nestingLevel = this.nestingLevels.get(sessionId);
307
+ const input = {
308
+ sessionId,
309
+ config: serializableConfig,
310
+ iteration: 0,
311
+ blobEnabled: this._blobEnabled,
312
+ dehydrateThreshold: this.config.dehydrateThreshold ?? 30,
313
+ idleTimeout: parentSessionId ? -1 : (this.config.dehydrateOnIdle ?? 30),
314
+ inputGracePeriod: parentSessionId ? -1 : (this.config.dehydrateOnInputRequired ?? 30),
315
+ checkpointInterval: this.config.checkpointInterval ?? -1,
316
+ rehydrationMessage: this.config.rehydrationMessage,
317
+ ...(parentSessionId ? { parentSessionId } : {}),
318
+ ...(nestingLevel != null ? { nestingLevel } : {}),
319
+ ...(this.systemSessions.has(sessionId) ? { isSystem: true } : {}),
320
+ ...(this.sessionAgentIds.has(sessionId) ? { agentId: this.sessionAgentIds.get(sessionId) } : {}),
321
+ ...(this._sessionPolicy ? { sessionPolicy: this._sessionPolicy } : {}),
322
+ ...(this._allowedAgentNames.length > 0 ? { allowedAgentNames: this._allowedAgentNames } : {}),
323
+ };
324
+ await this.duroxideClient.startOrchestrationVersioned(orchestrationId, DURABLE_SESSION_ORCHESTRATION_NAME, input, DURABLE_SESSION_LATEST_VERSION);
325
+ this.activeOrchestrations.set(sessionId, orchestrationId);
326
+ }
327
+ // CMS: update state + orchestration ID
328
+ await this._catalog.updateSession(sessionId, {
329
+ orchestrationId,
330
+ state: "running",
331
+ lastActiveAt: new Date(),
332
+ });
333
+ await this.duroxideClient.enqueueEvent(orchestrationId, "messages", JSON.stringify({ prompt, ...(opts?.bootstrap ? { bootstrap: true } : {}) }));
334
+ return orchestrationId;
335
+ }
336
+ /** @internal */
337
+ async _startAndWait(sessionId, prompt, onUserInput, timeout, onIntermediateContent, opts) {
338
+ const orchestrationId = await this._ensureOrchestrationAndSend(sessionId, prompt, opts);
339
+ return this._waitForTurnResult(orchestrationId, sessionId, onUserInput, timeout ?? 300_000, onIntermediateContent);
340
+ }
341
+ /** @internal */
342
+ async _startTurn(sessionId, prompt, opts) {
343
+ return this._ensureOrchestrationAndSend(sessionId, prompt, opts);
344
+ }
345
+ /** @internal */
346
+ _getDuroxideClient() {
347
+ return this.duroxideClient;
348
+ }
349
+ /** @internal */
350
+ _getCatalog() {
351
+ return this._catalog;
352
+ }
353
+ /** @internal — exposed for PilotSwarmSession.wait() */
354
+ async _waitForTurnResult_external(orchestrationId, sessionId, onUserInput, timeout) {
355
+ return this._waitForTurnResult(orchestrationId, sessionId, onUserInput, timeout);
356
+ }
357
+ /** @internal */
358
+ async _getLatestResponse(orchestrationId) {
359
+ if (!this.duroxideClient)
360
+ return null;
361
+ try {
362
+ const raw = await this.duroxideClient.getValue(orchestrationId, RESPONSE_LATEST_KEY);
363
+ if (!raw)
364
+ return null;
365
+ const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
366
+ return parsed ?? null;
367
+ }
368
+ catch {
369
+ return null;
370
+ }
371
+ }
372
+ /** @internal */
373
+ async _getSessionInfo(sessionId) {
374
+ const cmsRow = await this._catalog.getSession(sessionId);
375
+ // Merge with live customStatus for real-time fields
376
+ const orchestrationId = `session-${sessionId}`;
377
+ let customStatus = {};
378
+ let orchStatus = {};
379
+ if (this.duroxideClient) {
380
+ try {
381
+ orchStatus = await this.duroxideClient.getStatus(orchestrationId);
382
+ if (orchStatus.customStatus) {
383
+ try {
384
+ customStatus = typeof orchStatus.customStatus === "string"
385
+ ? JSON.parse(orchStatus.customStatus) : orchStatus.customStatus;
386
+ }
387
+ catch { }
388
+ }
389
+ }
390
+ catch { }
391
+ }
392
+ const latestResponse = customStatus?.responseVersion
393
+ ? await this._getLatestResponse(orchestrationId)
394
+ : null;
395
+ let status = customStatus.status
396
+ ?? cmsRow?.state
397
+ ?? "pending";
398
+ if (orchStatus.status === "Completed")
399
+ status = "completed";
400
+ if (orchStatus.status === "Failed")
401
+ status = "failed";
402
+ return {
403
+ sessionId,
404
+ status,
405
+ model: cmsRow?.model ?? undefined,
406
+ title: cmsRow?.title ?? undefined,
407
+ agentId: cmsRow?.agentId ?? undefined,
408
+ createdAt: cmsRow?.createdAt ?? new Date(),
409
+ updatedAt: cmsRow?.updatedAt ?? new Date(),
410
+ iterations: customStatus.iteration ?? cmsRow?.currentIteration ?? 0,
411
+ pendingQuestion: customStatus.pendingQuestion
412
+ ? { question: customStatus.pendingQuestion, choices: customStatus.choices, allowFreeform: customStatus.allowFreeform }
413
+ : latestResponse?.type === "input_required" && latestResponse.question
414
+ ? {
415
+ question: latestResponse.question,
416
+ choices: latestResponse.choices,
417
+ allowFreeform: latestResponse.allowFreeform,
418
+ }
419
+ : undefined,
420
+ waitingUntil: customStatus.waitSeconds
421
+ ? new Date(Date.now() + customStatus.waitSeconds * 1000)
422
+ : undefined,
423
+ waitReason: customStatus.waitReason,
424
+ result: customStatus.turnResult?.type === "completed"
425
+ ? customStatus.turnResult.content
426
+ : latestResponse?.type === "completed"
427
+ ? latestResponse.content
428
+ : (orchStatus.status === "Completed" ? orchStatus.output : undefined),
429
+ error: orchStatus.status === "Failed" ? orchStatus.error : (cmsRow?.lastError ?? undefined),
430
+ };
431
+ }
432
+ /** @internal */
433
+ async _waitForTurnResult(orchestrationId, sessionId, onUserInput, timeout, onIntermediateContent) {
434
+ const deadline = timeout > 0 ? Date.now() + timeout : Infinity;
435
+ let lastSeenVersion = this.lastSeenStatusVersion.get(orchestrationId) ?? 0;
436
+ let lastSeenIteration = this.lastSeenIteration.get(orchestrationId) ?? -1;
437
+ let lastSeenResponseVersion = this.lastSeenResponseVersion.get(orchestrationId) ?? 0;
438
+ while (Date.now() < deadline) {
439
+ const remaining = deadline === Infinity ? 30_000 : Math.min(deadline - Date.now(), 30_000);
440
+ if (remaining <= 0)
441
+ break;
442
+ let statusResult;
443
+ try {
444
+ statusResult = await this.duroxideClient.waitForStatusChange(orchestrationId, lastSeenVersion, 200, remaining);
445
+ }
446
+ catch {
447
+ await new Promise(r => setTimeout(r, 200));
448
+ const orchStatus = await this.duroxideClient.getStatus(orchestrationId);
449
+ if (orchStatus.status === "Failed")
450
+ throw new Error(orchStatus.error ?? "Orchestration failed");
451
+ if (orchStatus.status === "Completed")
452
+ return orchStatus.output;
453
+ const currentVersion = orchStatus.customStatusVersion || 0;
454
+ if (currentVersion < lastSeenVersion) {
455
+ lastSeenVersion = 0;
456
+ lastSeenIteration = -1;
457
+ }
458
+ continue;
459
+ }
460
+ if (statusResult.customStatusVersion > lastSeenVersion) {
461
+ lastSeenVersion = statusResult.customStatusVersion;
462
+ }
463
+ else if (statusResult.customStatusVersion < lastSeenVersion) {
464
+ lastSeenVersion = statusResult.customStatusVersion;
465
+ lastSeenIteration = -1;
466
+ }
467
+ let customStatus = null;
468
+ if (statusResult.customStatus) {
469
+ try {
470
+ customStatus = typeof statusResult.customStatus === "string"
471
+ ? JSON.parse(statusResult.customStatus) : statusResult.customStatus;
472
+ }
473
+ catch { }
474
+ }
475
+ if (customStatus) {
476
+ if (customStatus.intermediateContent && onIntermediateContent) {
477
+ onIntermediateContent(customStatus.intermediateContent);
478
+ }
479
+ if (customStatus.turnResult && customStatus.iteration > lastSeenIteration) {
480
+ lastSeenIteration = customStatus.iteration;
481
+ const result = customStatus.turnResult;
482
+ if (result.type === "completed") {
483
+ if (customStatus.status === "idle") {
484
+ if (onIntermediateContent)
485
+ onIntermediateContent(result.content);
486
+ this.lastSeenStatusVersion.set(orchestrationId, lastSeenVersion);
487
+ this.lastSeenIteration.set(orchestrationId, lastSeenIteration);
488
+ return result.content;
489
+ }
490
+ else {
491
+ if (onIntermediateContent)
492
+ onIntermediateContent(result.content);
493
+ }
494
+ }
495
+ if (result.type === "input_required" && onUserInput) {
496
+ const response = await onUserInput({
497
+ question: result.question,
498
+ choices: result.choices,
499
+ allowFreeform: result.allowFreeform,
500
+ }, { sessionId });
501
+ await this.duroxideClient.enqueueEvent(orchestrationId, "messages", JSON.stringify(response));
502
+ continue;
503
+ }
504
+ }
505
+ if (customStatus.responseVersion && customStatus.responseVersion > lastSeenResponseVersion) {
506
+ const response = await this._getLatestResponse(orchestrationId);
507
+ lastSeenResponseVersion = Math.max(lastSeenResponseVersion, response?.version ?? customStatus.responseVersion);
508
+ if (response?.type === "completed" && response.content) {
509
+ if (customStatus.status === "idle" || customStatus.status === "completed") {
510
+ if (onIntermediateContent)
511
+ onIntermediateContent(response.content);
512
+ this.lastSeenStatusVersion.set(orchestrationId, lastSeenVersion);
513
+ this.lastSeenIteration.set(orchestrationId, lastSeenIteration);
514
+ this.lastSeenResponseVersion.set(orchestrationId, lastSeenResponseVersion);
515
+ return response.content;
516
+ }
517
+ if (onIntermediateContent)
518
+ onIntermediateContent(response.content);
519
+ }
520
+ if (response?.type === "wait" && response.content && onIntermediateContent) {
521
+ onIntermediateContent(response.content);
522
+ }
523
+ if (response?.type === "input_required" && response.question && onUserInput) {
524
+ const responseInput = await onUserInput({
525
+ question: response.question,
526
+ choices: response.choices,
527
+ allowFreeform: response.allowFreeform,
528
+ }, { sessionId });
529
+ await this.duroxideClient.enqueueEvent(orchestrationId, "messages", JSON.stringify(responseInput));
530
+ continue;
531
+ }
532
+ }
533
+ }
534
+ const orchStatus = await this.duroxideClient.getStatus(orchestrationId);
535
+ if (orchStatus.status === "Failed")
536
+ throw new Error(orchStatus.error ?? "Orchestration failed");
537
+ if (orchStatus.status === "Completed")
538
+ return orchStatus.output;
539
+ }
540
+ this.lastSeenResponseVersion.set(orchestrationId, lastSeenResponseVersion);
541
+ throw new Error(`Timeout waiting for response (${timeout}ms)`);
542
+ }
543
+ }
544
+ export class PilotSwarmSession {
545
+ sessionId;
546
+ client;
547
+ onUserInput;
548
+ lastOrchestrationId;
549
+ // Event subscription state
550
+ handlers = new Map();
551
+ lastSeenSeq = 0;
552
+ pollTimer = null;
553
+ polling = false;
554
+ static POLL_INTERVAL = 500; // ms
555
+ /** @internal */
556
+ constructor(sessionId, client, onUserInput) {
557
+ this.sessionId = sessionId;
558
+ this.client = client;
559
+ this.onUserInput = onUserInput;
560
+ }
561
+ async sendAndWait(prompt, timeout, onIntermediateContent) {
562
+ return this.client._startAndWait(this.sessionId, prompt, this.onUserInput, timeout, onIntermediateContent);
563
+ }
564
+ async send(prompt, opts) {
565
+ this.lastOrchestrationId = await this.client._startTurn(this.sessionId, prompt, opts);
566
+ }
567
+ async wait(timeout) {
568
+ if (!this.lastOrchestrationId)
569
+ throw new Error("No pending turn. Call send() first.");
570
+ return this.client._waitForTurnResult_external(this.lastOrchestrationId, this.sessionId, this.onUserInput, timeout ?? 300_000);
571
+ }
572
+ on(eventTypeOrHandler, handler) {
573
+ let key;
574
+ let fn;
575
+ if (typeof eventTypeOrHandler === "function") {
576
+ key = null;
577
+ fn = eventTypeOrHandler;
578
+ }
579
+ else {
580
+ key = eventTypeOrHandler;
581
+ fn = handler;
582
+ }
583
+ if (!this.handlers.has(key)) {
584
+ this.handlers.set(key, new Set());
585
+ }
586
+ this.handlers.get(key).add(fn);
587
+ // Start polling if not already running
588
+ this._startPolling();
589
+ return () => {
590
+ const set = this.handlers.get(key);
591
+ if (set) {
592
+ set.delete(fn);
593
+ if (set.size === 0)
594
+ this.handlers.delete(key);
595
+ }
596
+ // Stop polling if no handlers left
597
+ if (this.handlers.size === 0) {
598
+ this._stopPolling();
599
+ }
600
+ };
601
+ }
602
+ async sendEvent(eventName, data) {
603
+ const duroxideClient = this.client._getDuroxideClient();
604
+ const orchestrationId = this.lastOrchestrationId ?? `session-${this.sessionId}`;
605
+ if (duroxideClient) {
606
+ await duroxideClient.enqueueEvent(orchestrationId, "messages", JSON.stringify(data));
607
+ }
608
+ }
609
+ async abort() {
610
+ const duroxideClient = this.client._getDuroxideClient();
611
+ const orchestrationId = this.lastOrchestrationId ?? `session-${this.sessionId}`;
612
+ if (duroxideClient) {
613
+ await duroxideClient.cancelInstance(orchestrationId, "User abort");
614
+ }
615
+ }
616
+ async destroy() {
617
+ this._stopPolling();
618
+ await this.client.deleteSession(this.sessionId);
619
+ }
620
+ /** Get all persisted events for this session from CMS. */
621
+ async getMessages(limit) {
622
+ const catalog = this.client._getCatalog();
623
+ return catalog.getSessionEvents(this.sessionId, undefined, limit);
624
+ }
625
+ async getInfo() {
626
+ return this.client._getSessionInfo(this.sessionId);
627
+ }
628
+ // ─── Private: event polling ──────────────────────────────
629
+ _startPolling() {
630
+ if (this.pollTimer)
631
+ return;
632
+ this.pollTimer = setInterval(() => this._poll(), PilotSwarmSession.POLL_INTERVAL);
633
+ // Fire immediately too
634
+ this._poll();
635
+ }
636
+ _stopPolling() {
637
+ if (this.pollTimer) {
638
+ clearInterval(this.pollTimer);
639
+ this.pollTimer = null;
640
+ }
641
+ }
642
+ async _poll() {
643
+ if (this.polling)
644
+ return; // prevent overlapping polls
645
+ this.polling = true;
646
+ try {
647
+ const catalog = this.client._getCatalog();
648
+ const events = await catalog.getSessionEvents(this.sessionId, this.lastSeenSeq, 200);
649
+ for (const event of events) {
650
+ this.lastSeenSeq = event.seq;
651
+ this._dispatch(event);
652
+ }
653
+ }
654
+ catch {
655
+ // Swallow — will retry on next poll
656
+ }
657
+ finally {
658
+ this.polling = false;
659
+ }
660
+ }
661
+ _dispatch(event) {
662
+ // Typed handlers
663
+ const typed = this.handlers.get(event.eventType);
664
+ if (typed) {
665
+ for (const fn of typed)
666
+ fn(event);
667
+ }
668
+ // Catch-all handlers
669
+ const catchAll = this.handlers.get(null);
670
+ if (catchAll) {
671
+ for (const fn of catchAll)
672
+ fn(event);
673
+ }
674
+ }
675
+ }
676
+ //# sourceMappingURL=client.js.map