@akira-tl/forgerelay 0.6.2 → 0.7.1

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 +26 -0
  2. package/capabilities/subagents/GUIDE.md +100 -40
  3. package/dist/activity/lifecycle.js +1 -1
  4. package/dist/capabilities.js +1 -1
  5. package/dist/capability-registry.js +34 -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 +148 -0
  13. package/dist/subagents/providers/adapters/claude.js +75 -0
  14. package/dist/{local-agent-runtime.js → subagents/providers/adapters/codex.js} +10 -4
  15. package/dist/subagents/providers/adapters/opencode.js +137 -0
  16. package/dist/subagents/providers/adapters/pi.js +232 -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 +40 -0
  22. package/dist/subagents/sessions/capability.js +214 -0
  23. package/dist/subagents/sessions/delivery-mailbox.js +115 -0
  24. package/dist/subagents/sessions/execution.js +171 -0
  25. package/dist/subagents/sessions/manager.js +107 -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} +75 -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,19 @@
1
+ import { SubagentSessionCapability } from "../capability.js";
2
+ export function createSubagentMcpRuntime(config, activityLifecycle, options = {}) {
3
+ const capability = config.subagents
4
+ ? new SubagentSessionCapability(config, activityLifecycle, {
5
+ providerRunner: options.subagentProviderRunner,
6
+ })
7
+ : undefined;
8
+ return {
9
+ registryDependencies: capability
10
+ ? {
11
+ subagentSession: {
12
+ available: true,
13
+ run: (input, context, runOptions) => capability.run(input, context, runOptions),
14
+ },
15
+ }
16
+ : {},
17
+ decorateResult: (workspaceId, result) => capability?.decorateResult(workspaceId, result) ?? result,
18
+ };
19
+ }
@@ -1,7 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { resolve } from "node:path";
3
- import { openDatabase } from "./db/client.js";
4
- export class LocalAgentStore {
3
+ import { openDatabase } from "../../db/client.js";
4
+ export class SubagentSessionStore {
5
5
  database;
6
6
  constructor(stateDir) {
7
7
  this.database = openDatabase(stateDir);
@@ -27,7 +27,7 @@ export class LocalAgentStore {
27
27
  .prepare("select * from local_agent_sessions order by updated_at desc")
28
28
  .all();
29
29
  }
30
- return rows.map(rowToLocalAgentRecord);
30
+ return rows.map(rowToSubagentSession);
31
31
  }
32
32
  create(input) {
33
33
  const now = new Date().toISOString();
@@ -39,7 +39,17 @@ export class LocalAgentStore {
39
39
  provider: input.provider,
40
40
  model: input.model,
41
41
  thinking: input.thinking,
42
- status: "starting",
42
+ status: input.activeRun ? "running" : "idle",
43
+ ...(input.activeRun
44
+ ? {
45
+ activeRun: {
46
+ id: input.activeRun.id,
47
+ status: "running",
48
+ ...(input.activeRun.activityId ? { activityId: input.activeRun.activityId } : {}),
49
+ startedAt: input.activeRun.startedAt,
50
+ },
51
+ }
52
+ : {}),
43
53
  createdAt: now,
44
54
  updatedAt: now,
45
55
  };
@@ -52,11 +62,21 @@ export class LocalAgentStore {
52
62
  provider,
53
63
  model,
54
64
  thinking,
65
+ provider_session_id,
55
66
  status,
67
+ active_run_id,
68
+ active_activity_id,
69
+ active_run_started_at,
70
+ latest_run_id,
71
+ latest_run_outcome,
72
+ latest_run_finished_at,
73
+ latest_response,
74
+ error,
75
+ hook_reports_json,
56
76
  created_at,
57
77
  updated_at
58
- ) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
59
- .run(record.id, record.workspaceId ?? null, record.workspaceRoot, record.profileName, record.provider, record.model ?? null, record.thinking ?? null, record.status, record.createdAt, record.updatedAt);
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);
60
80
  return record;
61
81
  }
62
82
  get(idOrPrefix) {
@@ -66,13 +86,23 @@ export class LocalAgentStore {
66
86
  limit 1`)
67
87
  .get(idOrPrefix, idOrPrefix);
68
88
  if (exact)
69
- return rowToLocalAgentRecord(exact);
89
+ return rowToSubagentSession(exact);
70
90
  const matches = this.database.sqlite
71
91
  .prepare(`select * from local_agent_sessions
72
92
  where id like ? escape '\\' or provider_session_id like ? escape '\\'
73
93
  order by updated_at desc`)
74
94
  .all(`${escapeLike(idOrPrefix)}%`, `${escapeLike(idOrPrefix)}%`);
75
- return matches.length === 1 ? rowToLocalAgentRecord(matches[0]) : undefined;
95
+ return matches.length === 1 ? rowToSubagentSession(matches[0]) : undefined;
96
+ }
97
+ getInScope(idOrPrefix, scope) {
98
+ const session = this.get(idOrPrefix);
99
+ if (!session)
100
+ return undefined;
101
+ if (scope.workspaceId && session.workspaceId !== scope.workspaceId)
102
+ return undefined;
103
+ if (scope.workspaceRoot && session.workspaceRoot !== resolve(scope.workspaceRoot))
104
+ return undefined;
105
+ return session;
76
106
  }
77
107
  update(id, patch) {
78
108
  const current = this.getById(id);
@@ -93,12 +123,18 @@ export class LocalAgentStore {
93
123
  thinking = ?,
94
124
  provider_session_id = ?,
95
125
  status = ?,
96
- latest_response = ?,
97
- error = ?,
98
- hook_reports_json = ?,
126
+ active_run_id = ?,
127
+ active_activity_id = ?,
128
+ active_run_started_at = ?,
129
+ latest_run_id = ?,
130
+ latest_run_outcome = ?,
131
+ latest_run_finished_at = ?,
132
+ latest_response = null,
133
+ error = null,
134
+ hook_reports_json = null,
99
135
  updated_at = ?
100
136
  where id = ?`)
101
- .run(updated.workspaceId ?? null, resolve(updated.workspaceRoot), updated.profileName, updated.provider, updated.model ?? null, updated.thinking ?? null, updated.providerSessionId ?? null, updated.status, updated.latestResponse ?? null, updated.error ?? null, updated.hookReports ? JSON.stringify(updated.hookReports) : null, updated.updatedAt, updated.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);
102
138
  return updated;
103
139
  }
104
140
  close() {
@@ -108,13 +144,29 @@ export class LocalAgentStore {
108
144
  const row = this.database.sqlite
109
145
  .prepare("select * from local_agent_sessions where id = ?")
110
146
  .get(id);
111
- return row ? rowToLocalAgentRecord(row) : undefined;
147
+ return row ? rowToSubagentSession(row) : undefined;
112
148
  }
113
149
  }
114
- export function createLocalAgentStore(config) {
115
- return new LocalAgentStore(config.stateDir);
150
+ export function createSubagentSessionStore(config) {
151
+ return new SubagentSessionStore(config.stateDir);
116
152
  }
117
- function rowToLocalAgentRecord(row) {
153
+ function rowToSubagentSession(row) {
154
+ const activeRun = row.active_run_id
155
+ ? {
156
+ id: row.active_run_id,
157
+ status: "running",
158
+ ...(row.active_activity_id ? { activityId: row.active_activity_id } : {}),
159
+ ...(row.active_run_started_at ? { startedAt: row.active_run_started_at } : {}),
160
+ }
161
+ : undefined;
162
+ const latestOutcome = readOutcome(row.latest_run_outcome);
163
+ const latestRun = row.latest_run_id && latestOutcome
164
+ ? {
165
+ id: row.latest_run_id,
166
+ status: latestOutcome,
167
+ ...(row.latest_run_finished_at ? { finishedAt: row.latest_run_finished_at } : {}),
168
+ }
169
+ : undefined;
118
170
  return {
119
171
  id: row.id,
120
172
  workspaceId: row.workspace_id ?? undefined,
@@ -124,34 +176,18 @@ function rowToLocalAgentRecord(row) {
124
176
  model: row.model ?? undefined,
125
177
  thinking: row.thinking ?? undefined,
126
178
  providerSessionId: row.provider_session_id ?? undefined,
127
- status: readStatus(row.status),
128
- latestResponse: row.latest_response ?? undefined,
129
- error: row.error ?? undefined,
130
- hookReports: parseHookReports(row.hook_reports_json),
179
+ status: activeRun || row.status === "starting" || row.status === "running" ? "running" : "idle",
180
+ ...(activeRun ? { activeRun } : {}),
181
+ ...(latestRun ? { latestRun } : {}),
131
182
  createdAt: row.created_at,
132
183
  updatedAt: row.updated_at,
133
184
  };
134
185
  }
135
- function parseHookReports(value) {
136
- if (!value)
137
- return undefined;
138
- try {
139
- const parsed = JSON.parse(value);
140
- return Array.isArray(parsed) ? parsed : undefined;
141
- }
142
- catch {
143
- return undefined;
144
- }
145
- }
146
- function readStatus(status) {
147
- if (status === "starting" ||
148
- status === "running" ||
149
- status === "idle" ||
150
- status === "error" ||
151
- status === "stopped") {
152
- return status;
186
+ function readOutcome(value) {
187
+ if (value === "succeeded" || value === "failed" || value === "cancelled" || value === "interrupted") {
188
+ return value;
153
189
  }
154
- return "error";
190
+ return undefined;
155
191
  }
156
192
  function escapeLike(value) {
157
193
  return value.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_");
@@ -7,7 +7,7 @@ import { HookRunner } from "./hooks.js";
7
7
  import { closeManagedWorktree, createManagedWorktree, resolveManagedWorktreeBase, } from "./git-worktrees.js";
8
8
  import { AccessDeniedError, assertAllowedPath, isPathInsideRoot, resolveAllowedPath, } from "./roots.js";
9
9
  import { loadWorkspaceSkills, markSkillActivated, resolveSkillReadPath, } from "./skills.js";
10
- import { loadLocalAgentProfiles, } from "./local-agent-profiles.js";
10
+ import { loadSubagentProfiles, } from "./subagents/profiles.js";
11
11
  const WORKSPACE_STALE_REMINDER_MS = 2 * 24 * 60 * 60 * 1_000;
12
12
  const WORKSPACE_SESSION_IDLE_TTL_MS = 30 * 24 * 60 * 60 * 1_000;
13
13
  const WORKSPACE_GC_INTERVAL_MS = 60 * 60 * 1_000;
@@ -634,7 +634,7 @@ export class WorkspaceRegistry {
634
634
  async reusedWorkspaceContext(workspace) {
635
635
  Object.assign(workspace, this.loadSkillsForWorkspace(workspace.root));
636
636
  workspace.capabilityGuides = loadCapabilityGuides(this.config);
637
- workspace.agentProfiles = await loadLocalAgentProfiles(this.config, workspace.root);
637
+ workspace.agentProfiles = await loadSubagentProfiles(this.config, workspace.root);
638
638
  workspace.scannedInstructionDirs.clear();
639
639
  workspace.knownInstructionPathsByDir.clear();
640
640
  workspace.loadedInstructionRealPaths.clear();
@@ -800,7 +800,7 @@ export class WorkspaceRegistry {
800
800
  worktree: input.worktree,
801
801
  ...this.loadSkillsForWorkspace(input.root),
802
802
  capabilityGuides: loadCapabilityGuides(this.config),
803
- agentProfiles: await loadLocalAgentProfiles(this.config, input.root),
803
+ agentProfiles: await loadSubagentProfiles(this.config, input.root),
804
804
  activatedSkillDirs: new Set(),
805
805
  activatedCapabilityGuideDirs: new Set(),
806
806
  scannedInstructionDirs: new Set(),
@@ -235,16 +235,9 @@ ForgeRelay-owned `subagents` capability guide when delegation is actually needed
235
235
  new setups. Existing user-authored or previously seeded Skills remain normal
236
236
  user configuration and are not deleted.
237
237
 
238
- The current model-facing delegation workflow is:
238
+ Host 正常委派通过现有 `capability` Gateway 中的 `subagent.session` 完成,不增加新的 Core MCP tool。支持的生命周期操作包括 `start`、`resume`、`status`、`list`、`stop` 和 `delete`;具体参数与 provider continuation 能力以 `subagents` capability guide 为准。
239
239
 
240
- ```bash
241
- forgerelay agents ls
242
- forgerelay agents run <profile-or-provider-or-id> "<prompt>"
243
- forgerelay agents show <id>
244
- ```
245
-
246
- A first-class MCP subagent interface is planned so this CLI indirection can be
247
- removed.
240
+ `forgerelay agents` CLI 继续保留给本地诊断和兼容场景,但 first-class MCP 路径不会通过 `bash -> forgerelay agents ...` 间接执行。Subagent Session 绑定实际 Execution Workspace,provider 原生 session/thread 保存 conversation history,ForgeRelay 只持久化必要的 ownership、continuation 和当前执行协调元数据。
248
241
 
249
242
  ## Tool modes
250
243
 
package/docs/roadmap.md CHANGED
@@ -250,16 +250,51 @@ fake-LSP coverage remains the primary cross-platform protocol/lifecycle gate, wh
250
250
  `rust-analyzer`, `gopls`, and `clangd` only when those external executables are
251
251
  already present and otherwise reports explicit skips without installing them.
252
252
 
253
- ## 0.5 — First-class subagent MCP
253
+ ## 0.5 — Durable Activity and batch execution
254
254
 
255
- ForgeRelay already owns provider adapters and resumable local agent sessions.
256
- The next step is to remove the current `bash -> forgerelay agents ...` indirection
257
- for MCP hosts.
255
+ 0.5 established the execution-history substrate used by the later MCP App and
256
+ multi-workspace features:
257
+
258
+ - immutable local Audit Events and restart-safe Activity Records;
259
+ - durable Bash output plus late `bash_result` delivery;
260
+ - Host Turn snapshots with lazy detail/output queries for MCP Apps;
261
+ - native bulk read/edit/delete and heterogeneous `batch.execute` orchestration;
262
+ - parent/child Activity relationships and bounded capability batch policies;
263
+ - Windows-safe Activity/SQLite cleanup and release-test hardening.
264
+
265
+ Activity remains an observation of semantic ForgeRelay operations rather than a
266
+ second task/session runtime. Files, processes, Git state, and audit truth continue
267
+ to belong to the Workspace that actually executes the operation.
268
+
269
+ ## 0.6 — Host UI, Workspace Relay, and Composite Workspace
270
+
271
+ 0.6 turned the Activity substrate into a Host-facing multi-environment workflow:
258
272
 
259
- First-class subagent operations should reuse the Capability Gateway established in 0.3 rather than add another top-level MCP tool. The exact registered names, action semantics and provider/session contract remain 0.5 design work. The parent agent will continue choosing from available provider/profile metadata while ForgeRelay owns provider-backed worker lifecycle state.
273
+ - **0.6.0** one ForgeRelay Panel per Host Turn, MCP Inspector/debug lifecycle,
274
+ virtual `skills://` loading, bounded process waits, and release-candidate support;
275
+ - **0.6.1** — Workspace Relay, authenticated direct/SSH-routed remote execution,
276
+ restart-safe relay identity/routes, and remote Activity/bootstrap forwarding;
277
+ - **0.6.2** — Composite Workspace as a first-class Workspace kind with named local,
278
+ managed-worktree, and relayed members, explicit member routing, aggregated
279
+ Composite Activity, and `close_workspace` dissolution that preserves members.
280
+
281
+ The 0.6 contract keeps execution ownership explicit: Gateway ForgeRelay presents
282
+ and routes; the selected member Workspace and its Execution ForgeRelay own files,
283
+ Git state, processes, Hooks, Skills, Language services, Activity, and Audit facts.
284
+ Workspace Relay is not file synchronization or failover, and Composite Workspace
285
+ never silently chooses a member.
286
+
287
+ ## Later: first-class subagent MCP
288
+
289
+ ForgeRelay already owns provider adapters and resumable local agent sessions. A
290
+ future release may remove the current `bash -> forgerelay agents ...` indirection
291
+ for MCP hosts.
260
292
 
261
- This is intentionally provider-backed delegation, not an attempt to emulate a
262
- host-native subagent implementation.
293
+ First-class subagent operations should reuse the Capability Gateway rather than
294
+ add another top-level MCP tool. The parent agent should continue choosing from
295
+ available provider/profile metadata while ForgeRelay owns provider-backed worker
296
+ lifecycle state. This remains provider-backed delegation, not an attempt to
297
+ emulate a Host-native subagent implementation.
263
298
 
264
299
  ## Worktree and history refinements
265
300
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akira-tl/forgerelay",
3
- "version": "0.6.2",
3
+ "version": "0.7.1",
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/local-agent-runtime.test.ts && tsx src/local-agent-adapters.test.ts && tsx src/local-agent-availability.test.ts && tsx src/local-agent-profiles.test.ts && tsx src/local-agent-targets.test.ts && tsx src/local-agent-store.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/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",
@@ -79,7 +79,7 @@ test("release runtime and local parity share the checked-in Node contract", asyn
79
79
  test("cloud verification produces one reusable npm package on Linux", async () => {
80
80
  const workflow = await readFile(resolve(repoRoot, ".github/workflows/ci.yml"), "utf8");
81
81
  assert.match(workflow, /if:\s*runner\.os == 'Linux'[\s\S]*run:\s*npm run release:pack/);
82
- assert.match(workflow, /uses:\s*actions\/upload-artifact@v4/);
82
+ assert.match(workflow, /uses:\s*actions\/upload-artifact@v7/);
83
83
  assert.match(workflow, /name:\s*npm-package/);
84
84
  assert.match(workflow, /include-hidden-files:\s*true/);
85
85
  assert.match(workflow, /overwrite:\s*true/);
@@ -89,7 +89,7 @@ test("release workflow is tag-only and promotes the verified npm artifact withou
89
89
  const workflow = await readFile(resolve(repoRoot, ".github/workflows/release.yml"), "utf8");
90
90
  assert.doesNotMatch(workflow, /workflow_dispatch:/);
91
91
  assert.match(workflow, /needs:\s*verify/);
92
- assert.match(workflow, /uses:\s*actions\/download-artifact@v5/);
92
+ assert.match(workflow, /uses:\s*actions\/download-artifact@v7/);
93
93
  assert.match(workflow, /name:\s*npm-package/);
94
94
  assert.match(workflow, /run:\s*npm run release:publish/);
95
95
  assert.match(workflow, /npm install --global npm@11\.19\.1/);