@akira-tl/forgerelay 0.7.2 → 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,17 @@ All notable ForgeRelay changes are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.7.4] - 2026-08-30
8
+
9
+ ### Added
10
+
11
+ - Subagent Run restart reconciliation now preserves live owners and marks stale active Runs `interrupted` without replaying the old prompt.
12
+ - First-class Subagent Sessions now work through Workspace Relay and Composite explicit-member routing while keeping Session state on the Execution ForgeRelay.
13
+
14
+ ### Fixed
15
+
16
+ - Composite Capability result remapping now preserves the declared output schema during relayed Subagent operations.
17
+
7
18
  ## [0.7.2] - 2026-08-30
8
19
 
9
20
  ### Added
@@ -103,6 +103,8 @@ Run 完成后:
103
103
  - 成功领取后 mailbox 条目立即删除,同一个 completion 不重复交付;
104
104
  - 未领取 completion 可以跨正常 ForgeRelay 进程重启保留。
105
105
 
106
+ ForgeRelay 只为 active Run 持久化最小 execution-owner 元数据(owner identity / PID),不保存 delegated prompt。后续 Session 操作会先做 restart reconciliation:能够证明 owner 仍存活的 Run 保持 `running`;无法证明仍有执行 owner 的 Run 一次性转为 `interrupted`,Session 回到 `idle`。ForgeRelay 绝不自动 replay 被中断 Run 的旧 prompt;如果 provider continuation 仍有效,由 Host 显式 `resume` 新 prompt。
107
+
106
108
  如果需要等待结果,使用 `status` 进行有节制的后续查询;不要高频短轮询。
107
109
 
108
110
  ## 数据所有权
package/dist/cli.js CHANGED
@@ -580,7 +580,8 @@ function createCliSubagentSessionManager(config = loadConfig()) {
580
580
  return new SubagentSessionManager(config, {
581
581
  launch(request) {
582
582
  const promptFile = writeSubagentPromptFile(request.prompt);
583
- spawnSubagentWorker(request.sessionId, promptFile);
583
+ const pid = spawnSubagentWorker(request.sessionId, promptFile);
584
+ return pid === undefined ? undefined : { id: `subagent-worker-${request.runId}`, pid };
584
585
  },
585
586
  });
586
587
  }
@@ -599,6 +600,7 @@ function spawnSubagentWorker(sessionId, promptFile) {
599
600
  env: process.env,
600
601
  });
601
602
  child.unref();
603
+ return child.pid;
602
604
  }
603
605
  function writeSubagentPromptFile(prompt) {
604
606
  const directory = mkdtempSync(join(tmpdir(), "forgerelay-agent-prompt-"));
@@ -69,6 +69,11 @@ const migrations = [
69
69
  name: "subagent-session-coordination",
70
70
  up: migrateSubagentSessionCoordination,
71
71
  },
72
+ {
73
+ version: 15,
74
+ name: "subagent-run-ownership",
75
+ up: migrateSubagentRunOwnership,
76
+ },
72
77
  ];
73
78
  export function migrateDatabase(sqlite) {
74
79
  const migrate = sqlite.transaction(() => {
@@ -358,6 +363,11 @@ function migrateSubagentSessionCoordination(sqlite) {
358
363
  addColumnIfMissing(sqlite, "local_agent_sessions", "latest_run_outcome", "text");
359
364
  addColumnIfMissing(sqlite, "local_agent_sessions", "latest_run_finished_at", "text");
360
365
  }
366
+ function migrateSubagentRunOwnership(sqlite) {
367
+ migrateSubagentSessionCoordination(sqlite);
368
+ addColumnIfMissing(sqlite, "local_agent_sessions", "active_owner_id", "text");
369
+ addColumnIfMissing(sqlite, "local_agent_sessions", "active_owner_pid", "integer");
370
+ }
361
371
  function migrateActivityHostTurnWorkspace(sqlite) {
362
372
  migrateActivityHostTurns(sqlite);
363
373
  addColumnIfMissing(sqlite, "activity_host_turns", "workspace_id", "text");
package/dist/db/schema.js CHANGED
@@ -155,6 +155,8 @@ export const localAgentSessions = sqliteTable("local_agent_sessions", {
155
155
  activeRunId: text("active_run_id"),
156
156
  activeActivityId: text("active_activity_id"),
157
157
  activeRunStartedAt: text("active_run_started_at"),
158
+ activeOwnerId: text("active_owner_id"),
159
+ activeOwnerPid: integer("active_owner_pid"),
158
160
  latestRunId: text("latest_run_id"),
159
161
  latestRunOutcome: text("latest_run_outcome"),
160
162
  latestRunFinishedAt: text("latest_run_finished_at"),
package/dist/server.js CHANGED
@@ -2764,6 +2764,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
2764
2764
  outputSchema: {
2765
2765
  name: z.string(),
2766
2766
  action: z.enum(["describe", "run"]),
2767
+ member: z.string().optional(),
2767
2768
  capability: z.unknown().optional(),
2768
2769
  result: z.unknown().optional(),
2769
2770
  error: capabilityErrorOutputSchema.optional(),
@@ -9,18 +9,23 @@ export class SubagentSessionCapability {
9
9
  activityLifecycle;
10
10
  mailbox;
11
11
  providerRunner;
12
+ ownerAliveOverride;
12
13
  activeRuns = new Map();
13
14
  constructor(config, activityLifecycle, options = {}) {
14
15
  this.config = config;
15
16
  this.activityLifecycle = activityLifecycle;
16
17
  this.mailbox = new SubagentDeliveryMailbox(config.stateDir);
17
18
  this.providerRunner = options.providerRunner ?? defaultProviderRunner;
19
+ this.ownerAliveOverride = options.ownerAlive;
18
20
  }
19
21
  async run(input, context, options) {
20
22
  const manager = new SubagentSessionManager(this.config, {
21
23
  launch: (request) => this.launch(request),
22
24
  });
23
25
  try {
26
+ const reconciled = manager.reconcile({ workspaceId: context.workspaceId }, (run) => this.ownerAlive(run));
27
+ for (const entry of reconciled)
28
+ this.recordInterruption(entry, options.activityId);
24
29
  switch (input.operation) {
25
30
  case "start": {
26
31
  const started = await manager.start({
@@ -118,17 +123,19 @@ export class SubagentSessionCapability {
118
123
  };
119
124
  }
120
125
  launch(request) {
126
+ const ownerId = `subagent-owner-${process.pid}-${request.runId}`;
121
127
  const controller = new AbortController();
122
128
  const completion = executeSubagentRun(this.config, { ...request, signal: controller.signal }, this.providerRunner).then((result) => {
123
129
  this.recordCompletion(result);
124
130
  return result;
125
131
  });
126
- this.activeRuns.set(request.runId, { controller, completion });
132
+ this.activeRuns.set(request.runId, { ownerId, controller, completion });
127
133
  void completion.finally(() => {
128
134
  this.activeRuns.delete(request.runId);
129
135
  }).catch(() => {
130
136
  // Unexpected orchestration failures surface through later reconciliation.
131
137
  });
138
+ return { id: ownerId, pid: process.pid };
132
139
  }
133
140
  async stop(manager, sessionId, workspaceId) {
134
141
  let session = manager.get(sessionId, { workspaceId });
@@ -158,6 +165,58 @@ export class SubagentSessionCapability {
158
165
  ...(session.latestRun?.id === activeRun.id ? { run: publicRun(session.latestRun) } : {}),
159
166
  };
160
167
  }
168
+ ownerAlive(run) {
169
+ if (this.ownerAliveOverride)
170
+ return this.ownerAliveOverride(run);
171
+ if (!run.ownerId || run.ownerPid === undefined)
172
+ return false;
173
+ const active = this.activeRuns.get(run.id);
174
+ if (run.ownerPid === process.pid)
175
+ return active?.ownerId === run.ownerId;
176
+ try {
177
+ process.kill(run.ownerPid, 0);
178
+ return true;
179
+ }
180
+ catch (error) {
181
+ return error.code === "EPERM";
182
+ }
183
+ }
184
+ recordInterruption(entry, fallbackActivityId) {
185
+ const sourceActivityId = entry.run.activityId ?? fallbackActivityId;
186
+ if (!sourceActivityId)
187
+ return;
188
+ const record = () => this.activityLifecycle.recordLinked({
189
+ sourceActivityId,
190
+ tool: "subagent_result",
191
+ request: { sessionId: entry.session.id, runId: entry.run.id },
192
+ result: {
193
+ sessionId: entry.session.id,
194
+ runId: entry.run.id,
195
+ provider: entry.session.provider,
196
+ status: "interrupted",
197
+ },
198
+ outcome: { type: "failed", error: "Subagent Run interrupted." },
199
+ });
200
+ try {
201
+ record();
202
+ }
203
+ catch {
204
+ if (!fallbackActivityId || fallbackActivityId === sourceActivityId)
205
+ return;
206
+ this.activityLifecycle.recordLinked({
207
+ sourceActivityId: fallbackActivityId,
208
+ tool: "subagent_result",
209
+ request: { sessionId: entry.session.id, runId: entry.run.id },
210
+ result: {
211
+ sessionId: entry.session.id,
212
+ runId: entry.run.id,
213
+ provider: entry.session.provider,
214
+ status: "interrupted",
215
+ },
216
+ outcome: { type: "failed", error: "Subagent Run interrupted." },
217
+ });
218
+ }
219
+ }
161
220
  recordCompletion(completion) {
162
221
  if (!completion.activityId)
163
222
  return;
@@ -52,13 +52,16 @@ export class SubagentSessionManager {
52
52
  const run = session.activeRun;
53
53
  if (!run)
54
54
  throw new Error(`Subagent Session ${session.id} did not create an active Run.`);
55
- this.launcher.launch({
55
+ const owned = this.assignOwner(session, run, this.launcher.launch({
56
56
  sessionId: session.id,
57
57
  runId,
58
58
  ...(input.activityId ? { activityId: input.activityId } : {}),
59
59
  prompt: input.prompt,
60
- });
61
- return { session, run };
60
+ }));
61
+ return {
62
+ session: owned,
63
+ run: owned.activeRun ?? (owned.latestRun?.id === run.id ? owned.latestRun : run),
64
+ };
62
65
  }
63
66
  resume(input, scope = {}) {
64
67
  const existing = this.store.getInScope(input.sessionId, scope);
@@ -90,13 +93,38 @@ export class SubagentSessionManager {
90
93
  status: "running",
91
94
  activeRun: run,
92
95
  });
93
- this.launcher.launch({
96
+ const owned = this.assignOwner(session, run, this.launcher.launch({
94
97
  sessionId: session.id,
95
98
  runId,
96
99
  ...(input.activityId ? { activityId: input.activityId } : {}),
97
100
  prompt: input.prompt,
98
- });
99
- return { session, run };
101
+ }));
102
+ return {
103
+ session: owned,
104
+ run: owned.activeRun ?? (owned.latestRun?.id === run.id ? owned.latestRun : run),
105
+ };
106
+ }
107
+ reconcile(scope, ownerAlive) {
108
+ const reconciled = [];
109
+ for (const session of this.store.list(scope)) {
110
+ const run = session.activeRun;
111
+ if (!run || ownerAlive(run))
112
+ continue;
113
+ const interrupted = {
114
+ id: run.id,
115
+ status: "interrupted",
116
+ ...(run.activityId ? { activityId: run.activityId } : {}),
117
+ ...(run.startedAt ? { startedAt: run.startedAt } : {}),
118
+ finishedAt: new Date().toISOString(),
119
+ };
120
+ const updated = this.store.update(session.id, {
121
+ status: "idle",
122
+ activeRun: undefined,
123
+ latestRun: interrupted,
124
+ });
125
+ reconciled.push({ session: updated, run: interrupted });
126
+ }
127
+ return reconciled;
100
128
  }
101
129
  delete(sessionId, scope = {}) {
102
130
  const session = this.store.getInScope(sessionId, scope);
@@ -112,6 +140,11 @@ export class SubagentSessionManager {
112
140
  close() {
113
141
  this.store.close();
114
142
  }
143
+ assignOwner(session, run, owner) {
144
+ if (!owner)
145
+ return session;
146
+ return this.store.assignActiveRunOwner(session.id, run.id, owner);
147
+ }
115
148
  }
116
149
  function newRunId() {
117
150
  return `run_${randomUUID().replaceAll("-", "").slice(0, 12)}`;
@@ -3,6 +3,7 @@ export function createSubagentMcpRuntime(config, activityLifecycle, options = {}
3
3
  const capability = config.subagents
4
4
  ? new SubagentSessionCapability(config, activityLifecycle, {
5
5
  providerRunner: options.subagentProviderRunner,
6
+ ownerAlive: options.subagentOwnerAlive,
6
7
  })
7
8
  : undefined;
8
9
  return {
@@ -47,6 +47,8 @@ export class SubagentSessionStore {
47
47
  status: "running",
48
48
  ...(input.activeRun.activityId ? { activityId: input.activeRun.activityId } : {}),
49
49
  startedAt: input.activeRun.startedAt,
50
+ ...(input.activeRun.ownerId ? { ownerId: input.activeRun.ownerId } : {}),
51
+ ...(input.activeRun.ownerPid !== undefined ? { ownerPid: input.activeRun.ownerPid } : {}),
50
52
  },
51
53
  }
52
54
  : {}),
@@ -67,6 +69,8 @@ export class SubagentSessionStore {
67
69
  active_run_id,
68
70
  active_activity_id,
69
71
  active_run_started_at,
72
+ active_owner_id,
73
+ active_owner_pid,
70
74
  latest_run_id,
71
75
  latest_run_outcome,
72
76
  latest_run_finished_at,
@@ -75,8 +79,8 @@ export class SubagentSessionStore {
75
79
  hook_reports_json,
76
80
  created_at,
77
81
  updated_at
78
- ) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, null, null, null, ?, ?)`)
79
- .run(record.id, record.workspaceId ?? null, record.workspaceRoot, record.profileName, record.provider, record.model ?? null, record.thinking ?? null, null, record.status, record.activeRun?.id ?? null, record.activeRun?.activityId ?? null, record.activeRun?.startedAt ?? null, null, null, null, record.createdAt, record.updatedAt);
82
+ ) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, null, null, null, ?, ?)`)
83
+ .run(record.id, record.workspaceId ?? null, record.workspaceRoot, record.profileName, record.provider, record.model ?? null, record.thinking ?? null, null, record.status, record.activeRun?.id ?? null, record.activeRun?.activityId ?? null, record.activeRun?.startedAt ?? null, record.activeRun?.ownerId ?? null, record.activeRun?.ownerPid ?? null, null, null, null, record.createdAt, record.updatedAt);
80
84
  return record;
81
85
  }
82
86
  get(idOrPrefix) {
@@ -126,6 +130,8 @@ export class SubagentSessionStore {
126
130
  active_run_id = ?,
127
131
  active_activity_id = ?,
128
132
  active_run_started_at = ?,
133
+ active_owner_id = ?,
134
+ active_owner_pid = ?,
129
135
  latest_run_id = ?,
130
136
  latest_run_outcome = ?,
131
137
  latest_run_finished_at = ?,
@@ -134,9 +140,18 @@ export class SubagentSessionStore {
134
140
  hook_reports_json = null,
135
141
  updated_at = ?
136
142
  where id = ?`)
137
- .run(updated.workspaceId ?? null, resolve(updated.workspaceRoot), updated.profileName, updated.provider, updated.model ?? null, updated.thinking ?? null, updated.providerSessionId ?? null, updated.status, updated.activeRun?.id ?? null, updated.activeRun?.activityId ?? null, updated.activeRun?.startedAt ?? null, updated.latestRun?.id ?? null, updated.latestRun && updated.latestRun.status !== "running" ? updated.latestRun.status : null, updated.latestRun?.finishedAt ?? null, updated.updatedAt, updated.id);
143
+ .run(updated.workspaceId ?? null, resolve(updated.workspaceRoot), updated.profileName, updated.provider, updated.model ?? null, updated.thinking ?? null, updated.providerSessionId ?? null, updated.status, updated.activeRun?.id ?? null, updated.activeRun?.activityId ?? null, updated.activeRun?.startedAt ?? null, updated.activeRun?.ownerId ?? null, updated.activeRun?.ownerPid ?? null, updated.latestRun?.id ?? null, updated.latestRun && updated.latestRun.status !== "running" ? updated.latestRun.status : null, updated.latestRun?.finishedAt ?? null, updated.updatedAt, updated.id);
138
144
  return updated;
139
145
  }
146
+ assignActiveRunOwner(id, runId, owner) {
147
+ this.database.sqlite.prepare(`update local_agent_sessions
148
+ set active_owner_id = ?, active_owner_pid = ?, updated_at = ?
149
+ where id = ? and active_run_id = ?`).run(owner.id, owner.pid ?? null, new Date().toISOString(), id, runId);
150
+ const current = this.getById(id);
151
+ if (!current)
152
+ throw new Error(`Unknown subagent id: ${id}`);
153
+ return current;
154
+ }
140
155
  delete(id) {
141
156
  this.database.sqlite.prepare("delete from local_agent_sessions where id = ?").run(id);
142
157
  }
@@ -160,6 +175,8 @@ function rowToSubagentSession(row) {
160
175
  status: "running",
161
176
  ...(row.active_activity_id ? { activityId: row.active_activity_id } : {}),
162
177
  ...(row.active_run_started_at ? { startedAt: row.active_run_started_at } : {}),
178
+ ...(row.active_owner_id ? { ownerId: row.active_owner_id } : {}),
179
+ ...(row.active_owner_pid !== null ? { ownerPid: row.active_owner_pid } : {}),
163
180
  }
164
181
  : undefined;
165
182
  const latestOutcome = readOutcome(row.latest_run_outcome);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akira-tl/forgerelay",
3
- "version": "0.7.2",
3
+ "version": "0.7.4",
4
4
  "description": "Local development control plane for MCP coding agents.",
5
5
  "type": "module",
6
6
  "homepage": "https://github.com/Akira-TL/forgerelay#readme",
@@ -47,7 +47,7 @@
47
47
  "release:publish": "node scripts/release/publish.mjs",
48
48
  "postinstall": "node scripts/fix-node-pty-permissions.mjs",
49
49
  "start": "node dist/cli.js serve",
50
- "test": "node --test scripts/debug/runtime.test.mjs scripts/release-proof.test.mjs scripts/release/release-gate.test.mjs scripts/release/release-version.test.mjs && tsx src/oauth/router.test.ts && tsx src/remote-auth-cli.test.ts && tsx src/remote-ssh-auth-cli.test.ts && tsx src/remote-workspace-relay.test.ts && tsx src/remote-workspace-relay-process.test.ts && tsx src/config.test.ts && tsx src/lsp/language-server-config.test.ts && tsx src/lsp/normalization/hover.test.ts && tsx src/lsp/references.server.test.ts && tsx src/lsp/operations/document-symbols.server.test.ts && tsx src/lsp/operations/workspace-symbols.server.test.ts && tsx src/lsp/operations/diagnostics-push.server.test.ts && tsx src/lsp/operations/diagnostics-pull.server.test.ts && tsx src/lsp/operations/request-hardening.server.test.ts && tsx src/lsp/operations/recovery.server.test.ts && tsx src/lsp/operations/lifecycle.server.test.ts && tsx src/lsp/runtime/semantic-requests.test.ts && tsx src/logger.test.ts && tsx src/proxy-trust.test.ts && tsx src/mcp-app-template.test.ts && tsx src/hooks.test.ts && tsx src/capability-registry.test.ts && tsx src/mcp/server-instructions.test.ts && tsx src/request-meta.test.ts && tsx src/incoming-artifacts.test.ts && tsx src/artifact-download.test.ts && tsx src/ui/card-types.test.ts && tsx src/ui/workspace-lifecycle-app.test.ts && tsx src/ui/activity/model.test.ts && tsx src/ui/patch-display.test.ts && tsx src/ui/tool-display.test.ts && tsx src/apply-patch.test.ts && tsx src/process-platform.test.ts && tsx src/process-sessions.test.ts && tsx src/mcp-sessions.test.ts && tsx src/server-shutdown.test.ts && tsx src/subagents/providers/adapters/codex.test.ts && tsx src/subagents/providers/registry.test.ts && tsx src/subagents/providers/availability.test.ts && tsx src/subagents/profiles.test.ts && tsx src/subagents/cli-target.test.ts && tsx src/subagents/sessions/store.test.ts && tsx src/subagents/sessions/manager.test.ts && tsx src/subagents/sessions/mcp/capability.server.test.ts && tsx src/subagents/sessions/mcp/continuation.server.test.ts && tsx src/subagents/sessions/mcp/lifecycle.server.test.ts && tsx src/roots.test.ts && tsx src/file-mutations.test.ts && tsx src/operations/edit-preflight.test.ts && tsx src/skills.test.ts && tsx src/db/migrations.test.ts && tsx src/workspace-store.test.ts && tsx src/activity/audit-store.test.ts && tsx src/activity/bash-output-store.test.ts && tsx src/activity/lifecycle.test.ts && tsx src/activity/query-service.test.ts && tsx src/operations/core-operation-executor.test.ts && tsx src/operations/bulk-mutation.test.ts && tsx src/operations/batch/scheduler.test.ts && tsx src/operations/batch/executor-policy.test.ts && tsx src/workspaces.test.ts && tsx src/workspace-conversation.test.ts && tsx src/review-checkpoints.test.ts && tsx src/lsp/code-intelligence.server.test.ts && npm run build:app && tsx src/server.test.ts && tsx src/oauth-store.test.ts && tsx src/cli.test.ts",
50
+ "test": "node --test scripts/debug/runtime.test.mjs scripts/release-proof.test.mjs scripts/release/release-gate.test.mjs scripts/release/release-version.test.mjs && tsx src/oauth/router.test.ts && tsx src/remote-auth-cli.test.ts && tsx src/remote-ssh-auth-cli.test.ts && tsx src/remote-workspace-relay.test.ts && tsx src/remote-workspace-relay-process.test.ts && tsx src/config.test.ts && tsx src/lsp/language-server-config.test.ts && tsx src/lsp/normalization/hover.test.ts && tsx src/lsp/references.server.test.ts && tsx src/lsp/operations/document-symbols.server.test.ts && tsx src/lsp/operations/workspace-symbols.server.test.ts && tsx src/lsp/operations/diagnostics-push.server.test.ts && tsx src/lsp/operations/diagnostics-pull.server.test.ts && tsx src/lsp/operations/request-hardening.server.test.ts && tsx src/lsp/operations/recovery.server.test.ts && tsx src/lsp/operations/lifecycle.server.test.ts && tsx src/lsp/runtime/semantic-requests.test.ts && tsx src/logger.test.ts && tsx src/proxy-trust.test.ts && tsx src/mcp-app-template.test.ts && tsx src/hooks.test.ts && tsx src/capability-registry.test.ts && tsx src/mcp/server-instructions.test.ts && tsx src/request-meta.test.ts && tsx src/incoming-artifacts.test.ts && tsx src/artifact-download.test.ts && tsx src/ui/card-types.test.ts && tsx src/ui/workspace-lifecycle-app.test.ts && tsx src/ui/activity/model.test.ts && tsx src/ui/patch-display.test.ts && tsx src/ui/tool-display.test.ts && tsx src/apply-patch.test.ts && tsx src/process-platform.test.ts && tsx src/process-sessions.test.ts && tsx src/mcp-sessions.test.ts && tsx src/server-shutdown.test.ts && tsx src/subagents/providers/adapters/codex.test.ts && tsx src/subagents/providers/registry.test.ts && tsx src/subagents/providers/availability.test.ts && tsx src/subagents/profiles.test.ts && tsx src/subagents/cli-target.test.ts && tsx src/subagents/sessions/store.test.ts && tsx src/subagents/sessions/manager.test.ts && tsx src/subagents/sessions/mcp/capability.server.test.ts && tsx src/subagents/sessions/mcp/continuation.server.test.ts && tsx src/subagents/sessions/mcp/lifecycle.server.test.ts && tsx src/subagents/sessions/mcp/reconciliation.server.test.ts && tsx src/subagents/sessions/mcp/routing.server.test.ts && tsx src/roots.test.ts && tsx src/file-mutations.test.ts && tsx src/operations/edit-preflight.test.ts && tsx src/skills.test.ts && tsx src/db/migrations.test.ts && tsx src/workspace-store.test.ts && tsx src/activity/audit-store.test.ts && tsx src/activity/bash-output-store.test.ts && tsx src/activity/lifecycle.test.ts && tsx src/activity/query-service.test.ts && tsx src/operations/core-operation-executor.test.ts && tsx src/operations/bulk-mutation.test.ts && tsx src/operations/batch/scheduler.test.ts && tsx src/operations/batch/executor-policy.test.ts && tsx src/workspaces.test.ts && tsx src/workspace-conversation.test.ts && tsx src/review-checkpoints.test.ts && tsx src/lsp/code-intelligence.server.test.ts && npm run build:app && tsx src/server.test.ts && tsx src/oauth-store.test.ts && tsx src/cli.test.ts",
51
51
  "typecheck": "tsc -p tsconfig.json --noEmit",
52
52
  "release:check": "node scripts/release-version.mjs check",
53
53
  "release:tag-check": "node scripts/release-version.mjs tag",