@akira-tl/forgerelay 0.6.2 → 0.7.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 (35) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/capabilities/subagents/GUIDE.md +108 -40
  3. package/dist/activity/lifecycle.js +1 -1
  4. package/dist/capabilities.js +1 -1
  5. package/dist/capability-registry.js +42 -0
  6. package/dist/cli.js +80 -165
  7. package/dist/db/migrations.js +14 -0
  8. package/dist/db/schema.js +6 -0
  9. package/dist/server.js +29 -32
  10. package/dist/{local-agent-targets.js → subagents/cli-target.js} +6 -6
  11. package/dist/{local-agent-profiles.js → subagents/profiles.js} +13 -5
  12. package/dist/subagents/providers/adapters/acp.js +150 -0
  13. package/dist/subagents/providers/adapters/claude.js +82 -0
  14. package/dist/{local-agent-runtime.js → subagents/providers/adapters/codex.js} +11 -5
  15. package/dist/subagents/providers/adapters/opencode.js +164 -0
  16. package/dist/subagents/providers/adapters/pi.js +246 -0
  17. package/dist/{local-agent-availability.js → subagents/providers/availability.js} +24 -10
  18. package/dist/subagents/providers/continuation.js +11 -0
  19. package/dist/subagents/providers/contract.js +1 -0
  20. package/dist/subagents/providers/registry.js +26 -0
  21. package/dist/subagents/providers/shared.js +66 -0
  22. package/dist/subagents/sessions/capability.js +260 -0
  23. package/dist/subagents/sessions/delivery-mailbox.js +118 -0
  24. package/dist/subagents/sessions/execution.js +168 -0
  25. package/dist/subagents/sessions/manager.js +118 -0
  26. package/dist/subagents/sessions/mcp/audit.js +85 -0
  27. package/dist/subagents/sessions/mcp/runtime.js +19 -0
  28. package/dist/{local-agent-store.js → subagents/sessions/store.js} +78 -39
  29. package/dist/workspaces.js +3 -3
  30. package/docs/chatgpt-coding-workflow.md +2 -9
  31. package/docs/roadmap.md +42 -7
  32. package/package.json +2 -2
  33. package/scripts/release/release-gate.test.mjs +2 -2
  34. package/dist/local-agent-adapters.js +0 -653
  35. /package/dist/{local-agent-path.js → subagents/providers/path.js} +0 -0
@@ -0,0 +1,260 @@
1
+ import { CapabilityError, } from "../../capability-registry.js";
2
+ import { isSubagentProvider } from "../profiles.js";
3
+ import { subagentProviderContinuationSupported } from "../providers/continuation.js";
4
+ import { SubagentDeliveryMailbox } from "./delivery-mailbox.js";
5
+ import { executeSubagentRun, } from "./execution.js";
6
+ import { SubagentSessionError, SubagentSessionManager, } from "./manager.js";
7
+ export class SubagentSessionCapability {
8
+ config;
9
+ activityLifecycle;
10
+ mailbox;
11
+ providerRunner;
12
+ activeRuns = new Map();
13
+ constructor(config, activityLifecycle, options = {}) {
14
+ this.config = config;
15
+ this.activityLifecycle = activityLifecycle;
16
+ this.mailbox = new SubagentDeliveryMailbox(config.stateDir);
17
+ this.providerRunner = options.providerRunner ?? defaultProviderRunner;
18
+ }
19
+ async run(input, context, options) {
20
+ const manager = new SubagentSessionManager(this.config, {
21
+ launch: (request) => this.launch(request),
22
+ });
23
+ try {
24
+ switch (input.operation) {
25
+ case "start": {
26
+ const started = await manager.start({
27
+ workspaceId: context.workspaceId,
28
+ workspaceRoot: context.workspaceRoot,
29
+ target: input.target,
30
+ prompt: input.prompt,
31
+ model: input.model,
32
+ thinking: input.thinking,
33
+ activityId: options.activityId,
34
+ });
35
+ return {
36
+ value: {
37
+ operation: "start",
38
+ session: publicSession(started.session),
39
+ run: publicRun(started.run),
40
+ },
41
+ };
42
+ }
43
+ case "resume": {
44
+ const resumed = manager.resume({
45
+ sessionId: input.sessionId,
46
+ prompt: input.prompt,
47
+ activityId: options.activityId,
48
+ }, { workspaceId: context.workspaceId });
49
+ return {
50
+ value: {
51
+ operation: "resume",
52
+ session: publicSession(resumed.session),
53
+ run: publicRun(resumed.run),
54
+ },
55
+ };
56
+ }
57
+ case "status": {
58
+ const session = manager.get(input.sessionId, { workspaceId: context.workspaceId });
59
+ if (!session) {
60
+ throw new SubagentSessionError("subagent.session_not_found", `Unknown Subagent Session in this Workspace: ${input.sessionId}`);
61
+ }
62
+ return {
63
+ value: {
64
+ operation: "status",
65
+ session: publicSession(session),
66
+ ...(session.activeRun ? { activeRun: publicRun(session.activeRun) } : {}),
67
+ ...(session.latestRun ? { latestRun: publicRun(session.latestRun) } : {}),
68
+ },
69
+ };
70
+ }
71
+ case "stop":
72
+ return { value: await this.stop(manager, input.sessionId, context.workspaceId) };
73
+ case "delete": {
74
+ const deleted = manager.delete(input.sessionId, { workspaceId: context.workspaceId });
75
+ this.mailbox.discardSession(deleted.id);
76
+ return {
77
+ value: {
78
+ operation: "delete",
79
+ deletedSessionId: deleted.id,
80
+ },
81
+ };
82
+ }
83
+ case "list":
84
+ return {
85
+ value: {
86
+ operation: "list",
87
+ sessions: manager.list({ workspaceId: context.workspaceId }).map(publicSessionSummary),
88
+ },
89
+ };
90
+ }
91
+ }
92
+ catch (error) {
93
+ if (error instanceof SubagentSessionError) {
94
+ throw new CapabilityError(error.code, error.message);
95
+ }
96
+ throw error;
97
+ }
98
+ finally {
99
+ manager.close();
100
+ }
101
+ }
102
+ decorateResult(workspaceId, result) {
103
+ if (typeof result !== "object" || result === null)
104
+ return result;
105
+ const content = result.content;
106
+ if (!Array.isArray(content))
107
+ return result;
108
+ const excludeRunId = currentRunId(result);
109
+ const deliveries = this.mailbox.claimWorkspace(workspaceId, excludeRunId);
110
+ if (deliveries.length === 0)
111
+ return result;
112
+ return {
113
+ ...result,
114
+ content: [
115
+ ...content,
116
+ ...deliveries.map((delivery) => ({ type: "text", text: deliveryText(delivery) })),
117
+ ],
118
+ };
119
+ }
120
+ launch(request) {
121
+ const controller = new AbortController();
122
+ const completion = executeSubagentRun(this.config, { ...request, signal: controller.signal }, this.providerRunner).then((result) => {
123
+ this.recordCompletion(result);
124
+ return result;
125
+ });
126
+ this.activeRuns.set(request.runId, { controller, completion });
127
+ void completion.finally(() => {
128
+ this.activeRuns.delete(request.runId);
129
+ }).catch(() => {
130
+ // Unexpected orchestration failures surface through later reconciliation.
131
+ });
132
+ }
133
+ async stop(manager, sessionId, workspaceId) {
134
+ let session = manager.get(sessionId, { workspaceId });
135
+ if (!session) {
136
+ throw new SubagentSessionError("subagent.session_not_found", `Unknown Subagent Session in this Workspace: ${sessionId}`);
137
+ }
138
+ const activeRun = session.activeRun;
139
+ if (!activeRun) {
140
+ return { operation: "stop", session: publicSession(session) };
141
+ }
142
+ const handle = this.activeRuns.get(activeRun.id);
143
+ if (!handle) {
144
+ session = manager.get(session.id, { workspaceId }) ?? session;
145
+ if (!session.activeRun)
146
+ return { operation: "stop", session: publicSession(session) };
147
+ throw new SubagentSessionError("subagent.cancel_unavailable", `Subagent Run ${activeRun.id} has no live cancellation owner.`);
148
+ }
149
+ handle.controller.abort(new Error(`Subagent Run ${activeRun.id} cancelled by stop.`));
150
+ await handle.completion;
151
+ session = manager.get(session.id, { workspaceId });
152
+ if (!session) {
153
+ throw new SubagentSessionError("subagent.session_not_found", `Unknown Subagent Session in this Workspace: ${sessionId}`);
154
+ }
155
+ return {
156
+ operation: "stop",
157
+ session: publicSession(session),
158
+ ...(session.latestRun?.id === activeRun.id ? { run: publicRun(session.latestRun) } : {}),
159
+ };
160
+ }
161
+ recordCompletion(completion) {
162
+ if (!completion.activityId)
163
+ return;
164
+ this.activityLifecycle.recordLinked({
165
+ sourceActivityId: completion.activityId,
166
+ tool: "subagent_result",
167
+ request: {
168
+ sessionId: completion.sessionId,
169
+ runId: completion.runId,
170
+ },
171
+ result: {
172
+ sessionId: completion.sessionId,
173
+ runId: completion.runId,
174
+ provider: completion.provider,
175
+ status: completion.outcome,
176
+ },
177
+ outcome: completion.outcome === "failed"
178
+ ? { type: "failed", error: "Subagent Run failed." }
179
+ : { type: "succeeded" },
180
+ });
181
+ }
182
+ }
183
+ function publicSession(session) {
184
+ const continuationSupported = sessionContinuationSupported(session);
185
+ return {
186
+ id: session.id,
187
+ status: session.status,
188
+ profileName: session.profileName,
189
+ provider: session.provider,
190
+ continuationSupported,
191
+ resumable: continuationSupported && session.status === "idle" && Boolean(session.providerSessionId),
192
+ ...(session.model ? { model: session.model } : {}),
193
+ ...(session.thinking ? { thinking: session.thinking } : {}),
194
+ ...(session.activeRun ? { activeRun: publicRun(session.activeRun) } : {}),
195
+ ...(session.latestRun ? { latestRun: publicRun(session.latestRun) } : {}),
196
+ createdAt: session.createdAt,
197
+ updatedAt: session.updatedAt,
198
+ };
199
+ }
200
+ function publicSessionSummary(session) {
201
+ const continuationSupported = sessionContinuationSupported(session);
202
+ return {
203
+ id: session.id,
204
+ status: session.status,
205
+ profileName: session.profileName,
206
+ provider: session.provider,
207
+ continuationSupported,
208
+ resumable: continuationSupported && session.status === "idle" && Boolean(session.providerSessionId),
209
+ ...(session.model ? { model: session.model } : {}),
210
+ ...(session.thinking ? { thinking: session.thinking } : {}),
211
+ ...(session.activeRun ? { activeRunId: session.activeRun.id } : {}),
212
+ ...(session.latestRun ? {
213
+ latestRun: {
214
+ id: session.latestRun.id,
215
+ status: session.latestRun.status,
216
+ },
217
+ } : {}),
218
+ updatedAt: session.updatedAt,
219
+ };
220
+ }
221
+ function sessionContinuationSupported(session) {
222
+ return isSubagentProvider(session.provider)
223
+ ? subagentProviderContinuationSupported(session.provider)
224
+ : false;
225
+ }
226
+ function publicRun(run) {
227
+ return {
228
+ id: run.id,
229
+ status: run.status,
230
+ ...(run.startedAt ? { startedAt: run.startedAt } : {}),
231
+ ...(run.finishedAt ? { finishedAt: run.finishedAt } : {}),
232
+ };
233
+ }
234
+ function currentRunId(result) {
235
+ if (typeof result !== "object" || result === null)
236
+ return undefined;
237
+ const structured = result.structuredContent;
238
+ if (typeof structured !== "object" || structured === null)
239
+ return undefined;
240
+ const capabilityResult = structured.result;
241
+ if (typeof capabilityResult !== "object" || capabilityResult === null)
242
+ return undefined;
243
+ const run = capabilityResult.run;
244
+ if (typeof run !== "object" || run === null)
245
+ return undefined;
246
+ const id = run.id;
247
+ return typeof id === "string" ? id : undefined;
248
+ }
249
+ function deliveryText(delivery) {
250
+ const header = `Subagent ${delivery.sessionId} Run ${delivery.runId} ${delivery.outcome}.`;
251
+ const body = delivery.outcome === "succeeded"
252
+ ? delivery.finalResponse
253
+ : delivery.error;
254
+ const suffix = delivery.truncated ? "\n[Subagent result truncated for delivery.]" : "";
255
+ return body ? `${header}\n${body}${suffix}` : `${header}${suffix}`;
256
+ }
257
+ async function defaultProviderRunner(provider, input) {
258
+ const { runSubagentProvider } = await import("../providers/registry.js");
259
+ return runSubagentProvider(provider, input);
260
+ }
@@ -0,0 +1,118 @@
1
+ import { mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync, } from "node:fs";
2
+ import { join } from "node:path";
3
+ const MAX_DELIVERY_TEXT_BYTES = 64 * 1024;
4
+ const MAX_DELIVERIES_PER_RESPONSE = 4;
5
+ export class SubagentDeliveryMailbox {
6
+ directory;
7
+ constructor(stateDir) {
8
+ this.directory = join(stateDir, "subagent-delivery");
9
+ }
10
+ write(input) {
11
+ const response = boundText(input.finalResponse);
12
+ const error = boundText(input.error);
13
+ const delivery = {
14
+ sessionId: input.sessionId,
15
+ runId: input.runId,
16
+ workspaceId: input.workspaceId,
17
+ ...(input.activityId ? { activityId: input.activityId } : {}),
18
+ provider: input.provider,
19
+ outcome: input.outcome,
20
+ ...(response.text !== undefined ? { finalResponse: response.text } : {}),
21
+ ...(error.text !== undefined ? { error: error.text } : {}),
22
+ truncated: response.truncated || error.truncated,
23
+ createdAt: new Date().toISOString(),
24
+ };
25
+ mkdirSync(this.directory, { recursive: true, mode: 0o700 });
26
+ const path = this.pathFor(delivery.sessionId);
27
+ const temporary = `${path}.${process.pid}.tmp`;
28
+ writeFileSync(temporary, `${JSON.stringify(delivery)}\n`, { mode: 0o600 });
29
+ rmSync(path, { force: true });
30
+ renameSync(temporary, path);
31
+ return delivery;
32
+ }
33
+ claimWorkspace(workspaceId, excludeRunId) {
34
+ return this.claim((delivery) => delivery.workspaceId === workspaceId && delivery.runId !== excludeRunId);
35
+ }
36
+ claimSession(workspaceId, sessionId) {
37
+ return this.claim((delivery) => delivery.workspaceId === workspaceId && delivery.sessionId === sessionId);
38
+ }
39
+ hasSession(sessionId) {
40
+ return this.files().includes(`${sessionId}.json`);
41
+ }
42
+ discardSession(sessionId) {
43
+ rmSync(this.pathFor(sessionId), { force: true });
44
+ }
45
+ claim(predicate) {
46
+ const deliveries = [];
47
+ for (const file of this.files()) {
48
+ if (deliveries.length >= MAX_DELIVERIES_PER_RESPONSE)
49
+ break;
50
+ const path = join(this.directory, file);
51
+ const delivery = readDelivery(path);
52
+ if (!delivery || !predicate(delivery))
53
+ continue;
54
+ rmSync(path, { force: true });
55
+ deliveries.push(delivery);
56
+ }
57
+ return deliveries.sort((left, right) => left.createdAt.localeCompare(right.createdAt));
58
+ }
59
+ files() {
60
+ try {
61
+ return readdirSync(this.directory)
62
+ .filter((file) => /^agt_[a-z0-9]+\.json$/i.test(file))
63
+ .sort();
64
+ }
65
+ catch (error) {
66
+ if (error.code === "ENOENT")
67
+ return [];
68
+ throw error;
69
+ }
70
+ }
71
+ pathFor(sessionId) {
72
+ if (!/^agt_[a-z0-9]+$/i.test(sessionId))
73
+ throw new Error(`Invalid Subagent Session id: ${sessionId}`);
74
+ return join(this.directory, `${sessionId}.json`);
75
+ }
76
+ }
77
+ function readDelivery(path) {
78
+ try {
79
+ const value = JSON.parse(readFileSync(path, "utf8"));
80
+ if (typeof value.sessionId !== "string" ||
81
+ typeof value.runId !== "string" ||
82
+ typeof value.workspaceId !== "string" ||
83
+ typeof value.provider !== "string" ||
84
+ !isOutcome(value.outcome) ||
85
+ typeof value.createdAt !== "string") {
86
+ return undefined;
87
+ }
88
+ return {
89
+ sessionId: value.sessionId,
90
+ runId: value.runId,
91
+ workspaceId: value.workspaceId,
92
+ ...(typeof value.activityId === "string" ? { activityId: value.activityId } : {}),
93
+ provider: value.provider,
94
+ outcome: value.outcome,
95
+ ...(typeof value.finalResponse === "string" ? { finalResponse: value.finalResponse } : {}),
96
+ ...(typeof value.error === "string" ? { error: value.error } : {}),
97
+ truncated: value.truncated === true,
98
+ createdAt: value.createdAt,
99
+ };
100
+ }
101
+ catch {
102
+ return undefined;
103
+ }
104
+ }
105
+ function boundText(value) {
106
+ if (value === undefined)
107
+ return { truncated: false };
108
+ const bytes = Buffer.from(value, "utf8");
109
+ if (bytes.length <= MAX_DELIVERY_TEXT_BYTES)
110
+ return { text: value, truncated: false };
111
+ return {
112
+ text: bytes.subarray(0, MAX_DELIVERY_TEXT_BYTES).toString("utf8"),
113
+ truncated: true,
114
+ };
115
+ }
116
+ function isOutcome(value) {
117
+ return value === "succeeded" || value === "failed" || value === "cancelled" || value === "interrupted";
118
+ }
@@ -0,0 +1,168 @@
1
+ import { HookRunner } from "../../hooks.js";
2
+ import { isSubagentProvider, loadSubagentProfiles, } from "../profiles.js";
3
+ import { runSubagentProvider } from "../providers/registry.js";
4
+ import { SubagentDeliveryMailbox } from "./delivery-mailbox.js";
5
+ import { createSubagentSessionStore, } from "./store.js";
6
+ export async function executeSubagentRun(config, input, providerRunner = runSubagentProvider) {
7
+ const store = createSubagentSessionStore(config);
8
+ const mailbox = new SubagentDeliveryMailbox(config.stateDir);
9
+ try {
10
+ const record = store.get(input.sessionId);
11
+ if (!record)
12
+ throw new Error(`Unknown subagent id: ${input.sessionId}`);
13
+ if (record.activeRun?.id !== input.runId) {
14
+ throw new Error(`Subagent Run ${input.runId} is not active for Session ${record.id}.`);
15
+ }
16
+ const hooks = new HookRunner(config.hooks, config.logging);
17
+ const hookInvocation = {
18
+ workspaceId: record.workspaceId,
19
+ workspaceRoot: record.workspaceRoot,
20
+ payload: {
21
+ agentId: record.id,
22
+ sessionId: record.id,
23
+ runId: input.runId,
24
+ profile: record.profileName,
25
+ provider: record.provider,
26
+ model: record.model,
27
+ thinking: record.thinking,
28
+ },
29
+ };
30
+ let outcome = "succeeded";
31
+ let result;
32
+ let errorMessage;
33
+ try {
34
+ await hooks.run("SubagentStart", hookInvocation);
35
+ input.signal?.throwIfAborted();
36
+ result = await runSessionProvider(config, record, input.prompt, providerRunner, input.signal);
37
+ input.signal?.throwIfAborted();
38
+ }
39
+ catch (error) {
40
+ if (isCancelled(error, input.signal)) {
41
+ outcome = "cancelled";
42
+ errorMessage = "Subagent Run cancelled.";
43
+ }
44
+ else {
45
+ outcome = "failed";
46
+ errorMessage = error instanceof Error ? error.message : String(error);
47
+ }
48
+ }
49
+ try {
50
+ await hooks.run("SubagentStop", {
51
+ ...hookInvocation,
52
+ payload: {
53
+ ...hookInvocation.payload,
54
+ status: outcome,
55
+ },
56
+ });
57
+ }
58
+ catch (error) {
59
+ if (outcome === "succeeded") {
60
+ outcome = "failed";
61
+ errorMessage = error instanceof Error ? error.message : String(error);
62
+ }
63
+ }
64
+ const finishedAt = new Date().toISOString();
65
+ store.update(record.id, {
66
+ ...(result?.providerSessionId ? { providerSessionId: result.providerSessionId } : {}),
67
+ status: "idle",
68
+ activeRun: undefined,
69
+ latestRun: {
70
+ id: input.runId,
71
+ status: outcome,
72
+ finishedAt,
73
+ },
74
+ });
75
+ if (record.workspaceId) {
76
+ mailbox.write({
77
+ sessionId: record.id,
78
+ runId: input.runId,
79
+ workspaceId: record.workspaceId,
80
+ ...(input.activityId ? { activityId: input.activityId } : {}),
81
+ provider: record.provider,
82
+ outcome,
83
+ ...(outcome === "succeeded" && result ? { finalResponse: result.finalResponse } : {}),
84
+ ...(outcome !== "succeeded" && errorMessage ? { error: errorMessage } : {}),
85
+ });
86
+ }
87
+ return completion(record, input, outcome, errorMessage);
88
+ }
89
+ finally {
90
+ store.close();
91
+ }
92
+ }
93
+ export async function executeSubagentSession(config, sessionId, prompt) {
94
+ const store = createSubagentSessionStore(config);
95
+ try {
96
+ const record = store.get(sessionId);
97
+ if (!record)
98
+ throw new Error(`Unknown subagent id: ${sessionId}`);
99
+ if (!record.activeRun)
100
+ throw new Error(`Subagent Session ${sessionId} has no active Run.`);
101
+ return executeSubagentRun(config, {
102
+ sessionId,
103
+ runId: record.activeRun.id,
104
+ ...(record.activeRun.activityId ? { activityId: record.activeRun.activityId } : {}),
105
+ prompt,
106
+ });
107
+ }
108
+ finally {
109
+ store.close();
110
+ }
111
+ }
112
+ async function runSessionProvider(config, session, prompt, providerRunner, signal) {
113
+ if (!isSubagentProvider(session.provider)) {
114
+ throw new Error(`Unknown subagent provider for Session ${session.id}: ${session.provider}`);
115
+ }
116
+ if (session.providerSessionId) {
117
+ return providerRunner(session.provider, {
118
+ prompt,
119
+ workspace: session.workspaceRoot,
120
+ providerSessionId: session.providerSessionId,
121
+ writeMode: "allowed",
122
+ model: session.model,
123
+ thinking: session.thinking,
124
+ signal,
125
+ });
126
+ }
127
+ if (session.profileName === session.provider) {
128
+ return providerRunner(session.provider, {
129
+ prompt,
130
+ workspace: session.workspaceRoot,
131
+ writeMode: "allowed",
132
+ model: session.model,
133
+ thinking: session.thinking,
134
+ signal,
135
+ });
136
+ }
137
+ const profiles = await loadSubagentProfiles(config, session.workspaceRoot);
138
+ const profile = profiles.find((candidate) => candidate.name === session.profileName);
139
+ if (!profile)
140
+ throw new Error(`Subagent profile not found: ${session.profileName}`);
141
+ return runSubagentProfile(profile, session, prompt, providerRunner, signal);
142
+ }
143
+ async function runSubagentProfile(profile, session, prompt, providerRunner, signal) {
144
+ const body = profile.body.trim();
145
+ const firstPrompt = body ? `${body}\n\nTask:\n${prompt}` : prompt;
146
+ return providerRunner(session.provider, {
147
+ prompt: firstPrompt,
148
+ workspace: session.workspaceRoot,
149
+ writeMode: "allowed",
150
+ model: session.model,
151
+ thinking: session.thinking,
152
+ signal,
153
+ });
154
+ }
155
+ function isCancelled(error, signal) {
156
+ return signal?.aborted === true || (error instanceof Error && error.name === "AbortError");
157
+ }
158
+ function completion(session, input, outcome, error) {
159
+ return {
160
+ sessionId: session.id,
161
+ runId: input.runId,
162
+ workspaceId: session.workspaceId,
163
+ activityId: input.activityId,
164
+ provider: session.provider,
165
+ outcome,
166
+ ...(error ? { error } : {}),
167
+ };
168
+ }
@@ -0,0 +1,118 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { formatAvailableSubagentTargets, resolveSubagentTarget, } from "../cli-target.js";
3
+ import { isSubagentProvider, loadSubagentProfiles, } from "../profiles.js";
4
+ import { assertSubagentProviderAvailable } from "../providers/availability.js";
5
+ import { subagentProviderContinuationSupported } from "../providers/continuation.js";
6
+ import { createSubagentSessionStore, } from "./store.js";
7
+ export class SubagentSessionError extends Error {
8
+ code;
9
+ constructor(code, message) {
10
+ super(message);
11
+ this.code = code;
12
+ this.name = "SubagentSessionError";
13
+ }
14
+ }
15
+ export class SubagentSessionManager {
16
+ config;
17
+ launcher;
18
+ store;
19
+ constructor(config, launcher) {
20
+ this.config = config;
21
+ this.launcher = launcher;
22
+ this.store = createSubagentSessionStore(config);
23
+ }
24
+ list(scope = {}) {
25
+ return this.store.list(scope);
26
+ }
27
+ get(idOrPrefix, scope) {
28
+ return scope ? this.store.getInScope(idOrPrefix, scope) : this.store.get(idOrPrefix);
29
+ }
30
+ async start(input) {
31
+ const profiles = await loadSubagentProfiles(this.config, input.workspaceRoot);
32
+ const target = resolveSubagentTarget(input.target, profiles, input.model, input.thinking);
33
+ if (!target) {
34
+ throw new Error(`Unknown subagent profile or provider: ${input.target}. Available ${formatAvailableSubagentTargets(profiles)}`);
35
+ }
36
+ assertSubagentProviderAvailable(target.provider);
37
+ const runId = newRunId();
38
+ const startedAt = new Date().toISOString();
39
+ const session = this.store.create({
40
+ workspaceId: input.workspaceId,
41
+ workspaceRoot: input.workspaceRoot,
42
+ profileName: target.name,
43
+ provider: target.provider,
44
+ model: target.model,
45
+ thinking: target.thinking,
46
+ activeRun: {
47
+ id: runId,
48
+ ...(input.activityId ? { activityId: input.activityId } : {}),
49
+ startedAt,
50
+ },
51
+ });
52
+ const run = session.activeRun;
53
+ if (!run)
54
+ throw new Error(`Subagent Session ${session.id} did not create an active Run.`);
55
+ this.launcher.launch({
56
+ sessionId: session.id,
57
+ runId,
58
+ ...(input.activityId ? { activityId: input.activityId } : {}),
59
+ prompt: input.prompt,
60
+ });
61
+ return { session, run };
62
+ }
63
+ resume(input, scope = {}) {
64
+ const existing = this.store.getInScope(input.sessionId, scope);
65
+ if (!existing) {
66
+ throw new SubagentSessionError("subagent.session_not_found", `Unknown Subagent Session in this Workspace: ${input.sessionId}`);
67
+ }
68
+ if (existing.activeRun) {
69
+ throw new SubagentSessionError("subagent.busy", `Subagent Session ${existing.id} already has active Run ${existing.activeRun.id}.`);
70
+ }
71
+ if (!isSubagentProvider(existing.provider)) {
72
+ throw new Error(`Unknown subagent provider for existing session: ${existing.provider}`);
73
+ }
74
+ if (!subagentProviderContinuationSupported(existing.provider)) {
75
+ throw new SubagentSessionError("subagent.continuation_unsupported", `${existing.provider} does not support true Subagent Session continuation.`);
76
+ }
77
+ if (!existing.providerSessionId) {
78
+ throw new SubagentSessionError("subagent.continuation_unavailable", `Subagent Session ${existing.id} has no provider continuation identity.`);
79
+ }
80
+ assertSubagentProviderAvailable(existing.provider);
81
+ const runId = newRunId();
82
+ const startedAt = new Date().toISOString();
83
+ const run = {
84
+ id: runId,
85
+ status: "running",
86
+ ...(input.activityId ? { activityId: input.activityId } : {}),
87
+ startedAt,
88
+ };
89
+ const session = this.store.update(existing.id, {
90
+ status: "running",
91
+ activeRun: run,
92
+ });
93
+ this.launcher.launch({
94
+ sessionId: session.id,
95
+ runId,
96
+ ...(input.activityId ? { activityId: input.activityId } : {}),
97
+ prompt: input.prompt,
98
+ });
99
+ return { session, run };
100
+ }
101
+ delete(sessionId, scope = {}) {
102
+ const session = this.store.getInScope(sessionId, scope);
103
+ if (!session) {
104
+ throw new SubagentSessionError("subagent.session_not_found", `Unknown Subagent Session in this Workspace: ${sessionId}`);
105
+ }
106
+ if (session.activeRun) {
107
+ throw new SubagentSessionError("subagent.busy", `Subagent Session ${session.id} already has active Run ${session.activeRun.id}.`);
108
+ }
109
+ this.store.delete(session.id);
110
+ return session;
111
+ }
112
+ close() {
113
+ this.store.close();
114
+ }
115
+ }
116
+ function newRunId() {
117
+ return `run_${randomUUID().replaceAll("-", "").slice(0, 12)}`;
118
+ }