@wjarka/cezarion 0.14.8 → 0.14.9-dev.829

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 (207) hide show
  1. package/README.md +76 -2
  2. package/dist/application-update/bin-recovery.d.ts +5 -0
  3. package/dist/application-update/bin-recovery.js +96 -0
  4. package/dist/application-update/bin-recovery.js.map +1 -0
  5. package/dist/application-update/discovery.d.ts +22 -0
  6. package/dist/application-update/discovery.js +91 -0
  7. package/dist/application-update/discovery.js.map +1 -0
  8. package/dist/application-update/helper.d.ts +57 -0
  9. package/dist/application-update/helper.js +374 -0
  10. package/dist/application-update/helper.js.map +1 -0
  11. package/dist/application-update/launcher.d.ts +8 -0
  12. package/dist/application-update/launcher.js +53 -0
  13. package/dist/application-update/launcher.js.map +1 -0
  14. package/dist/application-update/lock.d.ts +4 -0
  15. package/dist/application-update/lock.js +70 -0
  16. package/dist/application-update/lock.js.map +1 -0
  17. package/dist/application-update/npm-process.d.ts +17 -0
  18. package/dist/application-update/npm-process.js +133 -0
  19. package/dist/application-update/npm-process.js.map +1 -0
  20. package/dist/application-update/service.d.ts +69 -0
  21. package/dist/application-update/service.js +456 -0
  22. package/dist/application-update/service.js.map +1 -0
  23. package/dist/artifacts/cli.d.ts +1 -0
  24. package/dist/artifacts/cli.js +32 -0
  25. package/dist/artifacts/cli.js.map +1 -0
  26. package/dist/artifacts/lifecycle.d.ts +4 -0
  27. package/dist/artifacts/lifecycle.js +42 -0
  28. package/dist/artifacts/lifecycle.js.map +1 -0
  29. package/dist/artifacts/resolve.d.ts +18 -0
  30. package/dist/artifacts/resolve.js +102 -0
  31. package/dist/artifacts/resolve.js.map +1 -0
  32. package/dist/artifacts/store.d.ts +8 -0
  33. package/dist/artifacts/store.js +323 -0
  34. package/dist/artifacts/store.js.map +1 -0
  35. package/dist/ci-wait/mcp.d.ts +3 -0
  36. package/dist/ci-wait/mcp.js +14 -3
  37. package/dist/ci-wait/mcp.js.map +1 -1
  38. package/dist/contract/application-update.d.ts +32 -0
  39. package/dist/contract/artifacts.d.ts +83 -0
  40. package/dist/contract/ask-schema.d.ts +32 -0
  41. package/dist/contract/ask.d.ts +2 -32
  42. package/dist/contract/conversations.d.ts +165 -0
  43. package/dist/contract/delegation.d.ts +39 -0
  44. package/dist/contract/events.d.ts +49 -0
  45. package/dist/contract/health.d.ts +12 -0
  46. package/dist/contract/index.d.ts +2 -0
  47. package/dist/contract/index.js +1374 -1247
  48. package/dist/contract/runs.d.ts +143 -0
  49. package/dist/contract/skills.d.ts +28 -0
  50. package/dist/core/agent-runner.d.ts +33 -4
  51. package/dist/core/agent-runner.js +5 -0
  52. package/dist/core/agent-runner.js.map +1 -1
  53. package/dist/core/claude-cli-runner.d.ts +6 -12
  54. package/dist/core/claude-cli-runner.js +89 -19
  55. package/dist/core/claude-cli-runner.js.map +1 -1
  56. package/dist/core/codex-app-server-runner.d.ts +2 -1
  57. package/dist/core/codex-app-server-runner.js +327 -92
  58. package/dist/core/codex-app-server-runner.js.map +1 -1
  59. package/dist/core/codex-app-server-transport.d.ts +13 -1
  60. package/dist/core/codex-app-server-transport.js +58 -17
  61. package/dist/core/codex-app-server-transport.js.map +1 -1
  62. package/dist/core/cursor-acp-runner.d.ts +4 -1
  63. package/dist/core/cursor-acp-runner.js +3 -0
  64. package/dist/core/cursor-acp-runner.js.map +1 -1
  65. package/dist/core/cursor-provider-error.js +3 -1
  66. package/dist/core/cursor-provider-error.js.map +1 -1
  67. package/dist/core/input-submissions.d.ts +14 -0
  68. package/dist/core/input-submissions.js +26 -0
  69. package/dist/core/input-submissions.js.map +1 -0
  70. package/dist/core/opencode-server-runner.d.ts +4 -1
  71. package/dist/core/opencode-server-runner.js +144 -21
  72. package/dist/core/opencode-server-runner.js.map +1 -1
  73. package/dist/core/pi-runner.d.ts +2 -1
  74. package/dist/core/pi-runner.js +72 -7
  75. package/dist/core/pi-runner.js.map +1 -1
  76. package/dist/delegation/cli.js +102 -6
  77. package/dist/delegation/cli.js.map +1 -1
  78. package/dist/delegation/conversations.js +13 -5
  79. package/dist/delegation/conversations.js.map +1 -1
  80. package/dist/delegation/input.d.ts +2 -1
  81. package/dist/delegation/input.js +6 -1
  82. package/dist/delegation/input.js.map +1 -1
  83. package/dist/delegation/provision.js +5 -3
  84. package/dist/delegation/provision.js.map +1 -1
  85. package/dist/delegation/questions.d.ts +19 -0
  86. package/dist/delegation/questions.js +45 -0
  87. package/dist/delegation/questions.js.map +1 -0
  88. package/dist/delegation/results.d.ts +1 -1
  89. package/dist/delegation/results.js +23 -13
  90. package/dist/delegation/results.js.map +1 -1
  91. package/dist/delegation/routes.d.ts +142 -15
  92. package/dist/delegation/routes.js +4 -1
  93. package/dist/delegation/routes.js.map +1 -1
  94. package/dist/delegation/service.d.ts +9 -5
  95. package/dist/delegation/service.js +106 -7
  96. package/dist/delegation/service.js.map +1 -1
  97. package/dist/delegation/transport.d.ts +138 -11
  98. package/dist/index.js +72 -26
  99. package/dist/index.js.map +1 -1
  100. package/dist/open-url.d.ts +2 -0
  101. package/dist/open-url.js +20 -0
  102. package/dist/open-url.js.map +1 -0
  103. package/dist/release/snapshot.d.ts +3 -3
  104. package/dist/release/snapshot.js +6 -6
  105. package/dist/release/snapshot.js.map +1 -1
  106. package/dist/runs/client-request.d.ts +11 -0
  107. package/dist/runs/client-request.js +26 -0
  108. package/dist/runs/client-request.js.map +1 -0
  109. package/dist/runs/delegation-state.d.ts +17 -0
  110. package/dist/runs/event-history.js +6 -0
  111. package/dist/runs/event-history.js.map +1 -1
  112. package/dist/runs/store.d.ts +78 -6
  113. package/dist/runs/store.js +163 -1
  114. package/dist/runs/store.js.map +1 -1
  115. package/dist/server/server.d.ts +3761 -1648
  116. package/dist/server/server.js +170 -4
  117. package/dist/server/server.js.map +1 -1
  118. package/dist/task-cli/cli.d.ts +38 -0
  119. package/dist/task-cli/cli.js +463 -0
  120. package/dist/task-cli/cli.js.map +1 -0
  121. package/dist/task-cli/discovery.d.ts +22 -0
  122. package/dist/task-cli/discovery.js +95 -0
  123. package/dist/task-cli/discovery.js.map +1 -0
  124. package/dist/task-cli/http.d.ts +43 -0
  125. package/dist/task-cli/http.js +101 -0
  126. package/dist/task-cli/http.js.map +1 -0
  127. package/dist/task-cli/projections.d.ts +40 -0
  128. package/dist/task-cli/projections.js +45 -0
  129. package/dist/task-cli/projections.js.map +1 -0
  130. package/dist/task-cli/watch.d.ts +78 -0
  131. package/dist/task-cli/watch.js +336 -0
  132. package/dist/task-cli/watch.js.map +1 -0
  133. package/dist/workflows/run.d.ts +93 -0
  134. package/dist/workflows/run.js +816 -81
  135. package/dist/workflows/run.js.map +1 -1
  136. package/dist/workspace/projects-cli.js +1 -1
  137. package/package.json +3 -3
  138. package/scripts/mock-claude.mjs +38 -2
  139. package/scripts/mock-codex-app-server.mjs +98 -1
  140. package/scripts/mock-cursor-acp.mjs +3 -0
  141. package/scripts/mock-opencode-serve.mjs +69 -2
  142. package/scripts/mock-pi-rpc.mjs +63 -2
  143. package/web/dist/assets/agent-accounts-JPfZPgKn.js +1 -0
  144. package/web/dist/assets/centered-state-HwCqQkH3.js +43 -0
  145. package/web/dist/assets/collapsible-CTymFdL7.js +1 -0
  146. package/web/dist/assets/commit-list-BZpYGQl2.js +1 -0
  147. package/web/dist/assets/compare-variants-9VkybaQ7.js +1 -0
  148. package/web/dist/assets/{diff-tRE59UNb2.js → diff-CZcD4D1r2.js} +2 -2
  149. package/web/dist/assets/diff-controls-CpcHG3Mm.js +1 -0
  150. package/web/dist/assets/{diff-stat-Cgnl_hYF.js → diff-stat-Cn7rzy1H.js} +1 -1
  151. package/web/dist/assets/{diff-view-DN1ort7Q.js → diff-view-C46nHiP2.js} +2 -2
  152. package/web/dist/assets/dropdown-menu-_YAzCaoQ.js +1 -0
  153. package/web/dist/assets/git-toolbar-BvKDz6uS.js +1 -0
  154. package/web/dist/assets/github-CgjJCGrD.js +1 -0
  155. package/web/dist/assets/{image-preview-C2JGcBlC.js → image-preview-B618kDBH.js} +1 -1
  156. package/web/dist/assets/index-BJtve0Pm.js +7 -0
  157. package/web/dist/assets/index-D0WAgmTQ.css +1 -0
  158. package/web/dist/assets/markdown-CFWZnvMh.js +2 -0
  159. package/web/dist/assets/pill-QfwkuR50.js +1 -0
  160. package/web/dist/assets/project-router-CkAA39N5.js +1 -0
  161. package/web/dist/assets/prompt-templates-Cfna_S0T.js +15 -0
  162. package/web/dist/assets/repo-git-DgzgJ0cP.js +1 -0
  163. package/web/dist/assets/{run-diff-CyG0Nciv.js → run-diff-YbCEQn5I.js} +2 -2
  164. package/web/dist/assets/run-header-DMgIbr1d.js +1 -0
  165. package/web/dist/assets/skills-BW79kUTd.js +1 -0
  166. package/web/dist/assets/{tab-link-BWNp3mLY.js → tab-link-MIHvczEm.js} +1 -1
  167. package/web/dist/assets/task-changes-C96ekt94.js +1 -0
  168. package/web/dist/assets/task-commits-DBfcMZyG.js +1 -0
  169. package/web/dist/assets/task-files-BKpH8dKQ.js +2 -0
  170. package/web/dist/assets/task-thread-Bw_nQuTx.css +1 -0
  171. package/web/dist/assets/task-thread-Dnq7Ek6W.js +9 -0
  172. package/web/dist/assets/textarea-D2vAVgYW.js +1 -0
  173. package/web/dist/assets/thread-loading-NW8Mthco.js +1 -0
  174. package/web/dist/assets/token-metrics-D188e6_0.js +1 -0
  175. package/web/dist/assets/utils-DKheTxGD.js +64 -0
  176. package/web/dist/assets/workflows-BFY1hgp3.js +11 -0
  177. package/web/dist/assets/{zoomable-image-Z3JDnm0b.js → zoomable-image-YW3Qdrr_.js} +1 -1
  178. package/web/dist/index.html +14 -14
  179. package/web/dist/assets/agent-accounts-BZm_2WAv.js +0 -1
  180. package/web/dist/assets/alert-dialog-B422RI-k.js +0 -43
  181. package/web/dist/assets/collapsible-Cy_Fmkrd.js +0 -1
  182. package/web/dist/assets/commit-list-Dcnk7TnI.js +0 -1
  183. package/web/dist/assets/compare-variants-Ciqwvd_f.js +0 -1
  184. package/web/dist/assets/diff-controls-BGrXGi7O.js +0 -1
  185. package/web/dist/assets/dropdown-menu-dwDGT8MM.js +0 -1
  186. package/web/dist/assets/git-toolbar-UiHnGvE0.js +0 -1
  187. package/web/dist/assets/github-D-MrkbJ7.js +0 -1
  188. package/web/dist/assets/ibm-plex-mono-regular-BF3vfe7_.ttf +0 -0
  189. package/web/dist/assets/index-BszfYvBq.js +0 -7
  190. package/web/dist/assets/index-CWh4pKP3.css +0 -1
  191. package/web/dist/assets/markdown-C_IcKVQd.js +0 -2
  192. package/web/dist/assets/pill-BsbO6G7y.js +0 -1
  193. package/web/dist/assets/project-router-DbLkll25.js +0 -1
  194. package/web/dist/assets/prompt-templates-BlTGJB5W.js +0 -15
  195. package/web/dist/assets/repo-git-Bm3s6Why.js +0 -1
  196. package/web/dist/assets/run-header-IFyIGCey.js +0 -1
  197. package/web/dist/assets/skills-C-C4DVFY.js +0 -1
  198. package/web/dist/assets/task-changes-D2PHtWUW.js +0 -1
  199. package/web/dist/assets/task-commits-N5u9U0_R.js +0 -1
  200. package/web/dist/assets/task-files-BS7c7CFm.js +0 -2
  201. package/web/dist/assets/task-thread-BQFdz1dJ.css +0 -1
  202. package/web/dist/assets/task-thread-DGM2dZV-.js +0 -9
  203. package/web/dist/assets/textarea-FjaU370i.js +0 -1
  204. package/web/dist/assets/thread-loading-77sJ8Yu_.js +0 -1
  205. package/web/dist/assets/token-metrics-Clxh03bE.js +0 -1
  206. package/web/dist/assets/utils-PYYcpCFZ.js +0 -64
  207. package/web/dist/assets/workflows-Czl0ZfXH.js +0 -11
@@ -1,6 +1,6 @@
1
1
  import { DelegationService } from '../delegation/service.js';
2
2
  import { delegationFailure } from '../delegation/routes.js';
3
- import { workerEmptyRequestSchema, runRelationshipsSchema, runDelegationSummarySchema } from '../contract/index.js';
3
+ import { CLIENT_REQUEST_VARIANTS_ERROR, workerEmptyRequestSchema, runRelationshipsSchema, runDelegationSummarySchema } from '../contract/index.js';
4
4
  import { existsSync, readFileSync, statSync } from 'node:fs';
5
5
  import { randomUUID } from 'node:crypto';
6
6
  import { AutomationStore } from '../automations/store.js';
@@ -41,10 +41,14 @@ import { appendHandoffHeartbeat, handoffProgressExcerpt, readHandoff } from '../
41
41
  import { markStarted, onTodosChanged, readTodos, removeTodo, todoTaskText } from '../todos.js';
42
42
  import { HistoryCursorError, deriveRunContextEvents, readEventsAfterLiveCursor, readRunHistoryPage, validateLiveCursor, } from '../runs/event-history.js';
43
43
  import { readRunIndexFromDisk } from '../runs/run-index.js';
44
+ import { clientRequestHash } from '../runs/client-request.js';
44
45
  import { ColdRepoHandles } from './cold-repo-handles.js';
45
46
  import { isV2WireEventType } from '../runs/ui-event-sink.js';
46
47
  import { runEventsQuerySchema, runHistoryQuerySchema, runIdParamSchema, } from '../contract/index.js';
47
48
  import { resolveAttachmentPath } from '../workflows/attachment-path.js';
49
+ import { artifactParamsSchema, fileLinkQuerySchema } from '../contract/index.js';
50
+ import { artifactDirectory, listArtifacts, readArtifact } from '../artifacts/store.js';
51
+ import { artifactPreview, loadFileLink, rasterMime } from '../artifacts/resolve.js';
48
52
  import { isUntouchedCancelledRun, toPastedContent } from '../workflows/run.js';
49
53
  import { removeWorktree, worktreeDiff, worktreeDiffStat, worktreeSizeBytes } from '../git-worktree.js';
50
54
  import { isReclaimable, reclaimWorktrees } from '../runs/retention.js';
@@ -72,6 +76,8 @@ import { reviewGateEnabled } from '../runs/review-gate.js';
72
76
  import { readUiState, uiStatePath } from '../ui-state.js';
73
77
  import { agentHomePaths, expandTilde } from '../paths.js';
74
78
  import { isLoopbackHostHeader, normalizeHostname, resolveCapabilities } from './capabilities.js';
79
+ import { applicationUpdateInputSchema } from '../contract/index.js';
80
+ import { ApplicationUpdateConflictError, ApplicationUpdateFailureError } from '../application-update/service.js';
75
81
  import { createSocketHub } from './ws.js';
76
82
  import { browseDirectory, isInsideBrowseRoot, isLexicallyInsideBrowseRoot, resolveBrowseRoot } from './fs-browse.js';
77
83
  import { parseRemote, resolveForge } from './forge/index.js';
@@ -304,9 +310,14 @@ const startRunSchema = z
304
310
  // audit trail survives the composer detour. Bounded like every other
305
311
  // string here; a todo id is a short generated key.
306
312
  todoId: z.string().min(1).max(200, 'must be at most 200 characters').optional(),
313
+ // Idempotent start (#504): the same id and payload answer 200 with the existing run.
314
+ clientRequestId: z.string().uuid().optional(),
307
315
  })
308
316
  .refine((b) => Boolean(b.workflow) !== Boolean(b.steps), {
309
317
  message: 'provide either "workflow" or "steps", not both',
318
+ })
319
+ .refine((b) => !(b.clientRequestId && (b.variants ?? 1) > 1), {
320
+ message: CLIENT_REQUEST_VARIANTS_ERROR,
310
321
  });
311
322
  const pickSchema = z.object({
312
323
  runId: z.string().min(1),
@@ -1088,6 +1099,9 @@ export function createApp(deps) {
1088
1099
  // the key as always-present, which is a shape no client ever receives. The contract schema
1089
1100
  // says `.optional()`, and contract-parity.test.ts holds the two together.
1090
1101
  ...(update?.latest !== undefined ? { latestVersion: update.latest } : {}),
1102
+ ...(deps.applicationUpdate ? { applicationUpdate: caps.localHandoff
1103
+ ? deps.applicationUpdate.snapshot()
1104
+ : { status: 'idle', supported: false, message: 'Update this installation manually.' } } : {}),
1091
1105
  // Health is CORS-open and, in hosted mode, reachable off the loopback —
1092
1106
  // so any site/host that reads it would learn the developer's absolute
1093
1107
  // checkout path and username (#431). Local mode keeps the full path (the
@@ -1163,6 +1177,10 @@ export function createApp(deps) {
1163
1177
  })();
1164
1178
  return healthInFlight;
1165
1179
  };
1180
+ deps.applicationUpdate?.onStateChange?.(() => {
1181
+ healthCache = undefined;
1182
+ void refreshHealth();
1183
+ });
1166
1184
  // The read the GET and the topic snapshot share. On the live server (a hub is
1167
1185
  // injected) it answers from cache instantly and revalidates behind the
1168
1186
  // response; without a hub — a bare app in tests — there is no refresher
@@ -2342,6 +2360,32 @@ export function createApp(deps) {
2342
2360
  throw error;
2343
2361
  }
2344
2362
  });
2363
+ // Workspace-level, server-selected target and executable. The empty JSON body is strict.
2364
+ const applicationUpdateRoutes = new Hono()
2365
+ .post('/workspace/application-update/apply', jsonZodValidator(applicationUpdateInputSchema), async (c) => {
2366
+ if (!capabilities().localHandoff)
2367
+ return c.json({ error: 'Application updates require a local cockpit.' }, 409);
2368
+ if (!deps.applicationUpdate)
2369
+ return c.json({ error: 'Application update is unavailable.' }, 409);
2370
+ try {
2371
+ return c.json({ state: await deps.applicationUpdate.apply() });
2372
+ }
2373
+ catch (error) {
2374
+ return c.json({ error: error instanceof ApplicationUpdateConflictError || error instanceof ApplicationUpdateFailureError ? error.message : 'Application update preparation failed.' }, error instanceof ApplicationUpdateConflictError ? 409 : error instanceof ApplicationUpdateFailureError ? error.status : 500);
2375
+ }
2376
+ })
2377
+ .post('/workspace/application-update/restart', jsonZodValidator(applicationUpdateInputSchema), async (c) => {
2378
+ if (!capabilities().localHandoff)
2379
+ return c.json({ error: 'Application updates require a local cockpit.' }, 409);
2380
+ if (!deps.applicationUpdate)
2381
+ return c.json({ error: 'Application update is unavailable.' }, 409);
2382
+ try {
2383
+ return c.json({ state: await deps.applicationUpdate.restart() });
2384
+ }
2385
+ catch (error) {
2386
+ return c.json({ error: error instanceof ApplicationUpdateConflictError ? error.message : 'Application restart is unavailable.' }, error instanceof ApplicationUpdateConflictError ? 409 : 500);
2387
+ }
2388
+ });
2345
2389
  // ---- GUI clone (multi-project spec, step 4.3) ----------------------------
2346
2390
  // "Add project → Clone from GitHub": clone into the checkout root, then
2347
2391
  // register the result through `registerFolder` above (same guards, same
@@ -3131,8 +3175,21 @@ export function createApp(deps) {
3131
3175
  return run ? c.json(run) : c.json({ error: 'not found' }, 404);
3132
3176
  })
3133
3177
  .post('/runs', jsonZodValidator(startRunSchema), async (c) => {
3134
- const { root: repoRoot, dataDir, manager } = c.get('project');
3178
+ const { root: repoRoot, dataDir, manager, store } = c.get('project');
3135
3179
  const parsed = { data: c.req.valid('json') };
3180
+ const clientRequest = parsed.data.clientRequestId
3181
+ ? { id: parsed.data.clientRequestId, hash: clientRequestHash(parsed.data) }
3182
+ : undefined;
3183
+ // A retry only retrieves a persisted result: mutable start prerequisites (workflow,
3184
+ // provider, account and model policy) must not invalidate an already accepted request.
3185
+ if (clientRequest) {
3186
+ const existing = store.findRunByClientRequestId(clientRequest.id);
3187
+ if (existing) {
3188
+ if (existing.clientRequestHash !== clientRequest.hash)
3189
+ return c.json({ error: 'request id payload conflict' }, 409);
3190
+ return c.json(existing, 200);
3191
+ }
3192
+ }
3136
3193
  if (agentModelsLocked(repoRoot) &&
3137
3194
  (parsed.data.model?.trim() || parsed.data.effort?.trim())) {
3138
3195
  return c.json({ error: AGENT_MODELS_LOCKED_ERROR }, 409);
@@ -3186,6 +3243,18 @@ export function createApp(deps) {
3186
3243
  // CEZ_TODOS_FILE alike (RunManager.agentEnv).
3187
3244
  generateFollowups: capabilities().followups ? parsed.data.generateFollowups : false,
3188
3245
  };
3246
+ if (clientRequest) {
3247
+ // Recheck after every await: concurrent first requests may both miss the early lookup.
3248
+ // The manager's lookup and create are synchronous, so exactly one can create (#504).
3249
+ const result = manager.startRunIdempotent(workflow, input, clientRequest);
3250
+ if ('conflict' in result)
3251
+ return c.json({ error: 'request id payload conflict' }, 409);
3252
+ if (!result.created)
3253
+ return c.json(result.run, 200);
3254
+ if (parsed.data.todoId)
3255
+ await noteTodoStarted(dataDir, parsed.data.todoId, result.run.id);
3256
+ return c.json(result.run, 201);
3257
+ }
3189
3258
  const variants = parsed.data.variants ?? 1;
3190
3259
  if (variants > 1) {
3191
3260
  // Variants live in worktrees — without git there's nothing to isolate
@@ -3703,6 +3772,70 @@ export function createApp(deps) {
3703
3772
  if (!result.ok)
3704
3773
  return c.json({ error: result.error }, 409);
3705
3774
  return c.json(result.commit);
3775
+ })
3776
+ .get('/runs/:id/file-link', paramZodValidator(artifactParamsSchema.omit({ artifactId: true })), queryZodValidator(fileLinkQuerySchema), async (c) => {
3777
+ const { root, dataDir, store } = c.get('project');
3778
+ const run = store.getRun(c.req.valid('param').id);
3779
+ if (!run)
3780
+ return c.json({ error: 'not found' }, 404);
3781
+ const query = c.req.valid('query');
3782
+ const loaded = await loadFileLink({ dataDir, runId: run.id, projectRoot: root, workingDirectory: run.worktreePath ?? root, path: query.path });
3783
+ if (query.raw === '1') {
3784
+ if (!loaded.imageBytes || loaded.preview.type !== 'file')
3785
+ return c.json({ error: 'Only bounded raster images can be served inline.' }, 409);
3786
+ return c.body(new Uint8Array(loaded.imageBytes).buffer, 200, {
3787
+ 'content-type': rasterMime(loaded.preview.path), 'x-content-type-options': 'nosniff',
3788
+ 'content-security-policy': "default-src 'none'; sandbox", 'cache-control': 'private, no-store',
3789
+ });
3790
+ }
3791
+ return c.json(loaded.preview);
3792
+ })
3793
+ .get('/runs/:id/artifacts', paramZodValidator(artifactParamsSchema.omit({ artifactId: true })), async (c) => {
3794
+ const { dataDir, store } = c.get('project');
3795
+ const id = c.req.valid('param').id;
3796
+ if (!store.getRun(id))
3797
+ return c.json({ error: 'not found' }, 404);
3798
+ return c.json({ artifacts: await listArtifacts(artifactDirectory(dataDir, id), id) });
3799
+ })
3800
+ .get('/runs/:id/artifacts/:artifactId', paramZodValidator(artifactParamsSchema), async (c) => {
3801
+ const { dataDir, store } = c.get('project');
3802
+ const { id, artifactId } = c.req.valid('param');
3803
+ if (!store.getRun(id))
3804
+ return c.json({ error: 'not found' }, 404);
3805
+ const entry = await readArtifact(artifactDirectory(dataDir, id), id, artifactId);
3806
+ if (!entry)
3807
+ return c.json({ error: 'Published file is missing or unavailable.' }, 404);
3808
+ return c.json(artifactPreview(entry.metadata, entry.bytes));
3809
+ })
3810
+ .get('/runs/:id/artifacts/:artifactId/download', paramZodValidator(artifactParamsSchema), async (c) => {
3811
+ const { dataDir, store } = c.get('project');
3812
+ const { id, artifactId } = c.req.valid('param');
3813
+ if (!store.getRun(id))
3814
+ return c.json({ error: 'not found' }, 404);
3815
+ const entry = await readArtifact(artifactDirectory(dataDir, id), id, artifactId);
3816
+ if (!entry)
3817
+ return c.json({ error: 'Published file is missing or unavailable.' }, 404);
3818
+ return c.body(new Uint8Array(entry.bytes).buffer, 200, {
3819
+ 'content-type': 'application/octet-stream', 'x-content-type-options': 'nosniff',
3820
+ 'content-disposition': `attachment; filename="${entry.metadata.name.replace(/[^A-Za-z0-9._-]/g, '_')}"`,
3821
+ 'content-security-policy': "default-src 'none'; sandbox", 'cache-control': 'private, no-store',
3822
+ });
3823
+ })
3824
+ .get('/runs/:id/artifacts/:artifactId/image', paramZodValidator(artifactParamsSchema), async (c) => {
3825
+ const { dataDir, store } = c.get('project');
3826
+ const { id, artifactId } = c.req.valid('param');
3827
+ if (!store.getRun(id))
3828
+ return c.json({ error: 'not found' }, 404);
3829
+ const entry = await readArtifact(artifactDirectory(dataDir, id), id, artifactId);
3830
+ if (!entry)
3831
+ return c.json({ error: 'Published file is missing or unavailable.' }, 404);
3832
+ const mime = rasterMime(entry.metadata.name);
3833
+ if (!mime)
3834
+ return c.json({ error: 'Only raster images can be served inline.' }, 409);
3835
+ return c.body(new Uint8Array(entry.bytes).buffer, 200, {
3836
+ 'content-type': mime, 'x-content-type-options': 'nosniff',
3837
+ 'content-security-policy': "default-src 'none'; sandbox", 'cache-control': 'private, no-store',
3838
+ });
3706
3839
  })
3707
3840
  // Files tab: directory listing (path omitted or a dir) or file content
3708
3841
  // (size-capped, binary flagged). Traversal-safe — readWorktreePath rejects
@@ -5080,6 +5213,7 @@ export function createApp(deps) {
5080
5213
  .route('/', projectsRoutes)
5081
5214
  .route('/', agentProfilesRoutes)
5082
5215
  .route('/', skillsUpdateRoutes)
5216
+ .route('/', applicationUpdateRoutes)
5083
5217
  .route('/', workspaceConfigRoutes)
5084
5218
  .route('/', fsBrowseRoutes)
5085
5219
  .route('/', automationChecksRoutes)
@@ -5146,7 +5280,28 @@ export function startServer(deps, port) {
5146
5280
  // hosted/VPS deployment (which also flips CEZ_REMOTE to gate the local-handoff endpoints) —
5147
5281
  // src/index.ts never passes it, so the loopback guarantee holds for the normal CLI.
5148
5282
  const server = serve({
5149
- fetch: app.fetch,
5283
+ fetch: async (request, env) => {
5284
+ const result = await app.fetch(request, env);
5285
+ if (request.method === 'POST' && new URL(request.url).pathname === '/api/v1/workspace/application-update/restart'
5286
+ && result.status === 200) {
5287
+ // Arming is server-owned. A connected caller must receive the complete
5288
+ // acknowledgement before shutdown; a caller that already disconnected
5289
+ // cannot leave its explicitly requested, armed restart stranded.
5290
+ const response = env.outgoing;
5291
+ const handoff = () => {
5292
+ response.off('finish', handoff);
5293
+ response.off('close', handoff);
5294
+ deps.applicationUpdate?.afterResponse?.();
5295
+ };
5296
+ if (response.destroyed)
5297
+ handoff();
5298
+ else {
5299
+ response.once('finish', handoff);
5300
+ response.once('close', handoff);
5301
+ }
5302
+ }
5303
+ return result;
5304
+ },
5150
5305
  port,
5151
5306
  hostname: deps.bindHost ?? '127.0.0.1',
5152
5307
  });
@@ -5239,7 +5394,18 @@ export function startServer(deps, port) {
5239
5394
  });
5240
5395
  server.once('close', () => { unsubscribe(); coordinator.stop(); automationScheduler.stop(); sharedContexts.disposeAll(); void deps.delegation?.close(); });
5241
5396
  socketHub.attach(server, (req) => verifyWsUpgrade(req, deps.bindHost));
5242
- return server;
5397
+ const shutdownForRestart = async () => {
5398
+ sharedContexts.disposeAll(); // flush every built secondary project before old process exits
5399
+ deps.store.flush();
5400
+ socketHub.close();
5401
+ await deps.delegation?.close();
5402
+ await new Promise((resolve) => {
5403
+ server.close(() => resolve());
5404
+ if ('closeAllConnections' in server)
5405
+ server.closeAllConnections();
5406
+ });
5407
+ };
5408
+ return Object.assign(server, { shutdownForRestart });
5243
5409
  }
5244
5410
  /**
5245
5411
  * The WebSocket twin of the `/api/*` request-origin guard (#426), applied