@wjarka/cezarion 0.14.8 → 0.14.9-dev.1009
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/README.md +131 -2
- package/dist/application-update/bin-recovery.d.ts +5 -0
- package/dist/application-update/bin-recovery.js +96 -0
- package/dist/application-update/bin-recovery.js.map +1 -0
- package/dist/application-update/discovery.d.ts +22 -0
- package/dist/application-update/discovery.js +91 -0
- package/dist/application-update/discovery.js.map +1 -0
- package/dist/application-update/helper.d.ts +57 -0
- package/dist/application-update/helper.js +374 -0
- package/dist/application-update/helper.js.map +1 -0
- package/dist/application-update/launcher.d.ts +8 -0
- package/dist/application-update/launcher.js +53 -0
- package/dist/application-update/launcher.js.map +1 -0
- package/dist/application-update/lock.d.ts +4 -0
- package/dist/application-update/lock.js +70 -0
- package/dist/application-update/lock.js.map +1 -0
- package/dist/application-update/npm-process.d.ts +17 -0
- package/dist/application-update/npm-process.js +133 -0
- package/dist/application-update/npm-process.js.map +1 -0
- package/dist/application-update/service.d.ts +69 -0
- package/dist/application-update/service.js +456 -0
- package/dist/application-update/service.js.map +1 -0
- package/dist/artifacts/cli.d.ts +1 -0
- package/dist/artifacts/cli.js +32 -0
- package/dist/artifacts/cli.js.map +1 -0
- package/dist/artifacts/lifecycle.d.ts +4 -0
- package/dist/artifacts/lifecycle.js +42 -0
- package/dist/artifacts/lifecycle.js.map +1 -0
- package/dist/artifacts/resolve.d.ts +18 -0
- package/dist/artifacts/resolve.js +102 -0
- package/dist/artifacts/resolve.js.map +1 -0
- package/dist/artifacts/store.d.ts +8 -0
- package/dist/artifacts/store.js +323 -0
- package/dist/artifacts/store.js.map +1 -0
- package/dist/automations/scheduler.d.ts +6 -0
- package/dist/automations/scheduler.js +45 -17
- package/dist/automations/scheduler.js.map +1 -1
- package/dist/automations/store.d.ts +12 -4
- package/dist/automations/store.js +199 -22
- package/dist/automations/store.js.map +1 -1
- package/dist/automations/types.d.ts +2 -0
- package/dist/automations/types.js +1 -0
- package/dist/automations/types.js.map +1 -1
- package/dist/ci-wait/mcp.d.ts +3 -0
- package/dist/ci-wait/mcp.js +14 -3
- package/dist/ci-wait/mcp.js.map +1 -1
- package/dist/contract/application-update.d.ts +32 -0
- package/dist/contract/artifacts.d.ts +83 -0
- package/dist/contract/ask-schema.d.ts +32 -0
- package/dist/contract/ask.d.ts +2 -32
- package/dist/contract/automations.d.ts +6 -0
- package/dist/contract/conversations.d.ts +165 -0
- package/dist/contract/delegation.d.ts +68 -0
- package/dist/contract/discovery.d.ts +134 -0
- package/dist/contract/events.d.ts +49 -0
- package/dist/contract/health.d.ts +12 -0
- package/dist/contract/index.d.ts +3 -0
- package/dist/contract/index.js +1654 -1454
- package/dist/contract/projects.d.ts +40 -0
- package/dist/contract/runs.d.ts +186 -0
- package/dist/contract/skills.d.ts +33 -0
- package/dist/core/agent-runner.d.ts +33 -4
- package/dist/core/agent-runner.js +5 -0
- package/dist/core/agent-runner.js.map +1 -1
- package/dist/core/claude-cli-runner.d.ts +6 -12
- package/dist/core/claude-cli-runner.js +90 -20
- package/dist/core/claude-cli-runner.js.map +1 -1
- package/dist/core/codex-app-server-runner.d.ts +2 -1
- package/dist/core/codex-app-server-runner.js +327 -92
- package/dist/core/codex-app-server-runner.js.map +1 -1
- package/dist/core/codex-app-server-transport.d.ts +13 -1
- package/dist/core/codex-app-server-transport.js +58 -17
- package/dist/core/codex-app-server-transport.js.map +1 -1
- package/dist/core/cursor-acp-runner.d.ts +6 -1
- package/dist/core/cursor-acp-runner.js +162 -26
- package/dist/core/cursor-acp-runner.js.map +1 -1
- package/dist/core/cursor-provider-error.d.ts +3 -0
- package/dist/core/cursor-provider-error.js +16 -1
- package/dist/core/cursor-provider-error.js.map +1 -1
- package/dist/core/input-submissions.d.ts +14 -0
- package/dist/core/input-submissions.js +26 -0
- package/dist/core/input-submissions.js.map +1 -0
- package/dist/core/opencode-server-runner.d.ts +4 -1
- package/dist/core/opencode-server-runner.js +156 -26
- package/dist/core/opencode-server-runner.js.map +1 -1
- package/dist/core/pi-runner.d.ts +2 -1
- package/dist/core/pi-runner.js +88 -10
- package/dist/core/pi-runner.js.map +1 -1
- package/dist/core/provider-auth.d.ts +99 -1
- package/dist/core/provider-auth.js +203 -2
- package/dist/core/provider-auth.js.map +1 -1
- package/dist/delegation/cli.d.ts +1 -0
- package/dist/delegation/cli.js +103 -7
- package/dist/delegation/cli.js.map +1 -1
- package/dist/delegation/conversations.js +13 -5
- package/dist/delegation/conversations.js.map +1 -1
- package/dist/delegation/input.d.ts +2 -1
- package/dist/delegation/input.js +6 -1
- package/dist/delegation/input.js.map +1 -1
- package/dist/delegation/policy.d.ts +3 -2
- package/dist/delegation/policy.js +3 -1
- package/dist/delegation/policy.js.map +1 -1
- package/dist/delegation/process-liveness.d.ts +66 -0
- package/dist/delegation/process-liveness.js +215 -0
- package/dist/delegation/process-liveness.js.map +1 -0
- package/dist/delegation/provision.d.ts +1 -1
- package/dist/delegation/provision.js +10 -5
- package/dist/delegation/provision.js.map +1 -1
- package/dist/delegation/questions.d.ts +19 -0
- package/dist/delegation/questions.js +45 -0
- package/dist/delegation/questions.js.map +1 -0
- package/dist/delegation/results.d.ts +1 -1
- package/dist/delegation/results.js +33 -19
- package/dist/delegation/results.js.map +1 -1
- package/dist/delegation/routes.d.ts +185 -11
- package/dist/delegation/routes.js +6 -2
- package/dist/delegation/routes.js.map +1 -1
- package/dist/delegation/service.d.ts +23 -5
- package/dist/delegation/service.js +240 -17
- package/dist/delegation/service.js.map +1 -1
- package/dist/delegation/transport.d.ts +178 -15
- package/dist/delegation/workspace.d.ts +1 -1
- package/dist/delegation/workspace.js +13 -6
- package/dist/delegation/workspace.js.map +1 -1
- package/dist/discovery/catalog.d.ts +14 -0
- package/dist/discovery/catalog.js +31 -0
- package/dist/discovery/catalog.js.map +1 -0
- package/dist/discovery/cli.d.ts +8 -0
- package/dist/discovery/cli.js +104 -0
- package/dist/discovery/cli.js.map +1 -0
- package/dist/git-worktree.d.ts +3 -1
- package/dist/git-worktree.js +26 -4
- package/dist/git-worktree.js.map +1 -1
- package/dist/index.js +93 -26
- package/dist/index.js.map +1 -1
- package/dist/open-url.d.ts +2 -0
- package/dist/open-url.js +20 -0
- package/dist/open-url.js.map +1 -0
- package/dist/release/snapshot.d.ts +3 -3
- package/dist/release/snapshot.js +6 -6
- package/dist/release/snapshot.js.map +1 -1
- package/dist/runs/agent-tmpdir.d.ts +2 -0
- package/dist/runs/agent-tmpdir.js +5 -2
- package/dist/runs/agent-tmpdir.js.map +1 -1
- package/dist/runs/client-request.d.ts +11 -0
- package/dist/runs/client-request.js +26 -0
- package/dist/runs/client-request.js.map +1 -0
- package/dist/runs/delegation-state.d.ts +17 -0
- package/dist/runs/event-history.js +6 -0
- package/dist/runs/event-history.js.map +1 -1
- package/dist/runs/retention.d.ts +7 -2
- package/dist/runs/retention.js +39 -7
- package/dist/runs/retention.js.map +1 -1
- package/dist/runs/store.d.ts +100 -6
- package/dist/runs/store.js +245 -2
- package/dist/runs/store.js.map +1 -1
- package/dist/runs/webhook.d.ts +125 -0
- package/dist/runs/webhook.js +256 -0
- package/dist/runs/webhook.js.map +1 -0
- package/dist/server/project-context.d.ts +2 -0
- package/dist/server/project-context.js +1 -0
- package/dist/server/project-context.js.map +1 -1
- package/dist/server/provider-auth-runtime.d.ts +30 -4
- package/dist/server/provider-auth-runtime.js +71 -6
- package/dist/server/provider-auth-runtime.js.map +1 -1
- package/dist/server/server.d.ts +8226 -4860
- package/dist/server/server.js +311 -15
- package/dist/server/server.js.map +1 -1
- package/dist/server-install/platforms/ubuntu-vps.d.ts +5 -1
- package/dist/server-install/platforms/ubuntu-vps.js +160 -18
- package/dist/server-install/platforms/ubuntu-vps.js.map +1 -1
- package/dist/server-install/steps.d.ts +4 -2
- package/dist/server-install/steps.js +5 -4
- package/dist/server-install/steps.js.map +1 -1
- package/dist/skills-remote.d.ts +30 -5
- package/dist/skills-remote.js +122 -32
- package/dist/skills-remote.js.map +1 -1
- package/dist/skills.js +15 -6
- package/dist/skills.js.map +1 -1
- package/dist/task-cli/cli.d.ts +38 -0
- package/dist/task-cli/cli.js +580 -0
- package/dist/task-cli/cli.js.map +1 -0
- package/dist/task-cli/discovery.d.ts +22 -0
- package/dist/task-cli/discovery.js +96 -0
- package/dist/task-cli/discovery.js.map +1 -0
- package/dist/task-cli/http.d.ts +46 -0
- package/dist/task-cli/http.js +101 -0
- package/dist/task-cli/http.js.map +1 -0
- package/dist/task-cli/projections.d.ts +40 -0
- package/dist/task-cli/projections.js +45 -0
- package/dist/task-cli/projections.js.map +1 -0
- package/dist/task-cli/watch.d.ts +78 -0
- package/dist/task-cli/watch.js +336 -0
- package/dist/task-cli/watch.js.map +1 -0
- package/dist/workflows/run.d.ts +160 -2
- package/dist/workflows/run.js +1141 -108
- package/dist/workflows/run.js.map +1 -1
- package/dist/workspace/agent-profiles.d.ts +22 -0
- package/dist/workspace/agent-profiles.js +15 -0
- package/dist/workspace/agent-profiles.js.map +1 -1
- package/dist/workspace/config.d.ts +9 -0
- package/dist/workspace/config.js +13 -1
- package/dist/workspace/config.js.map +1 -1
- package/dist/workspace/projects-cli.js +1 -1
- package/dist/workspace/projects.d.ts +24 -1
- package/dist/workspace/projects.js +14 -1
- package/dist/workspace/projects.js.map +1 -1
- package/package.json +5 -5
- package/scripts/mock-claude.mjs +64 -2
- package/scripts/mock-codex-app-server.mjs +124 -1
- package/scripts/mock-cursor-acp.mjs +54 -3
- package/scripts/mock-cursor-hang-stdin-late.mjs +35 -0
- package/scripts/mock-cursor-hang-stdin.mjs +30 -0
- package/scripts/mock-opencode-serve.mjs +102 -2
- package/scripts/mock-pi-rpc.mjs +82 -3
- package/web/dist/assets/agent-accounts-CerclGiO.js +1 -0
- package/web/dist/assets/centered-state-DH8qtCz-.js +43 -0
- package/web/dist/assets/collapsible-De2G0xqQ.js +1 -0
- package/web/dist/assets/commit-list-s6JPY3qv.js +1 -0
- package/web/dist/assets/compare-variants-BQu_t-_g.js +1 -0
- package/web/dist/assets/{diff-tRE59UNb2.js → diff-8M5HlDxq2.js} +2 -2
- package/web/dist/assets/diff-controls-B6ok0TtQ.js +1 -0
- package/web/dist/assets/{diff-stat-Cgnl_hYF.js → diff-stat-D1AODN2a.js} +1 -1
- package/web/dist/assets/{diff-view-DN1ort7Q.js → diff-view-Bs5Gh7UF.js} +2 -2
- package/web/dist/assets/dropdown-menu-7KGQMoFj.js +1 -0
- package/web/dist/assets/git-toolbar-DqbapMXr.js +1 -0
- package/web/dist/assets/github-DCkk6sBm.js +1 -0
- package/web/dist/assets/{image-preview-C2JGcBlC.js → image-preview-jzHoEyLM.js} +1 -1
- package/web/dist/assets/index-Bnva-ttR.js +7 -0
- package/web/dist/assets/index-D5NkwqY7.css +1 -0
- package/web/dist/assets/markdown-DvbymqXz.js +2 -0
- package/web/dist/assets/pill-BL6ugwXo.js +1 -0
- package/web/dist/assets/project-router-ChC5Rb-Y.js +1 -0
- package/web/dist/assets/prompt-templates-j8qlK7c-.js +15 -0
- package/web/dist/assets/repo-git-_h1NjOVt.js +1 -0
- package/web/dist/assets/{run-diff-CyG0Nciv.js → run-diff-BuATgZpx.js} +2 -2
- package/web/dist/assets/run-header-DuAWswYM.js +1 -0
- package/web/dist/assets/skills-C-fq5BrZ.js +1 -0
- package/web/dist/assets/{tab-link-BWNp3mLY.js → tab-link-BmGKj9Rt.js} +1 -1
- package/web/dist/assets/task-changes-CHa6JWOS.js +1 -0
- package/web/dist/assets/task-commits-BqCqyWm1.js +1 -0
- package/web/dist/assets/task-files-BSA0l-YP.js +2 -0
- package/web/dist/assets/task-thread-Bw_nQuTx.css +1 -0
- package/web/dist/assets/task-thread-Q7Nj33DH.js +9 -0
- package/web/dist/assets/textarea-ByZ-txS7.js +1 -0
- package/web/dist/assets/thread-loading-DHT9p5o5.js +1 -0
- package/web/dist/assets/token-metrics-k4XoovDS.js +1 -0
- package/web/dist/assets/utils-BTFBhZ55.js +64 -0
- package/web/dist/assets/workflows-HJOd3lVi.js +11 -0
- package/web/dist/assets/{zoomable-image-Z3JDnm0b.js → zoomable-image-Ba6y6Wyl.js} +1 -1
- package/web/dist/index.html +14 -14
- package/web/dist/assets/agent-accounts-BZm_2WAv.js +0 -1
- package/web/dist/assets/alert-dialog-B422RI-k.js +0 -43
- package/web/dist/assets/collapsible-Cy_Fmkrd.js +0 -1
- package/web/dist/assets/commit-list-Dcnk7TnI.js +0 -1
- package/web/dist/assets/compare-variants-Ciqwvd_f.js +0 -1
- package/web/dist/assets/diff-controls-BGrXGi7O.js +0 -1
- package/web/dist/assets/dropdown-menu-dwDGT8MM.js +0 -1
- package/web/dist/assets/git-toolbar-UiHnGvE0.js +0 -1
- package/web/dist/assets/github-D-MrkbJ7.js +0 -1
- package/web/dist/assets/ibm-plex-mono-regular-BF3vfe7_.ttf +0 -0
- package/web/dist/assets/index-BszfYvBq.js +0 -7
- package/web/dist/assets/index-CWh4pKP3.css +0 -1
- package/web/dist/assets/markdown-C_IcKVQd.js +0 -2
- package/web/dist/assets/pill-BsbO6G7y.js +0 -1
- package/web/dist/assets/project-router-DbLkll25.js +0 -1
- package/web/dist/assets/prompt-templates-BlTGJB5W.js +0 -15
- package/web/dist/assets/repo-git-Bm3s6Why.js +0 -1
- package/web/dist/assets/run-header-IFyIGCey.js +0 -1
- package/web/dist/assets/skills-C-C4DVFY.js +0 -1
- package/web/dist/assets/task-changes-D2PHtWUW.js +0 -1
- package/web/dist/assets/task-commits-N5u9U0_R.js +0 -1
- package/web/dist/assets/task-files-BS7c7CFm.js +0 -2
- package/web/dist/assets/task-thread-BQFdz1dJ.css +0 -1
- package/web/dist/assets/task-thread-DGM2dZV-.js +0 -9
- package/web/dist/assets/textarea-FjaU370i.js +0 -1
- package/web/dist/assets/thread-loading-77sJ8Yu_.js +0 -1
- package/web/dist/assets/token-metrics-Clxh03bE.js +0 -1
- package/web/dist/assets/utils-PYYcpCFZ.js +0 -64
- package/web/dist/assets/workflows-Czl0ZfXH.js +0 -11
package/dist/server/server.js
CHANGED
|
@@ -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';
|
|
@@ -17,9 +17,10 @@ import { serve } from '@hono/node-server';
|
|
|
17
17
|
import { bodyLimit } from 'hono/body-limit';
|
|
18
18
|
import { streamSSE } from 'hono/streaming';
|
|
19
19
|
import { jsonZodValidator, paramZodValidator, queryZodValidator } from './validators.js';
|
|
20
|
+
import { deliverOnce, TaskWebhooks } from '../runs/webhook.js';
|
|
20
21
|
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
21
22
|
import { z } from 'zod';
|
|
22
|
-
import { repoPullInputSchema, pinRunInputSchema, githubSearchQuerySchema, setWorkspaceUiStateInputSchema, } from '../contract/index.js';
|
|
23
|
+
import { repoPullInputSchema, pinRunInputSchema, notifyRunInputSchema, githubSearchQuerySchema, setWorkspaceUiStateInputSchema, } from '../contract/index.js';
|
|
23
24
|
// A contract VALUE, like `workspaceUiStateSchema` in workspace/migrations.ts — the request
|
|
24
25
|
// schema this route validates with is the same one the client compiles against.
|
|
25
26
|
import { MODEL_DISCOVERY_RUNNERS, attachmentInputSchema, attachmentFileParamSchema, modelDiscoveryRunnerSchema, openProjectInSchema, runnerDiscoversModels, updateProjectInputSchema, } from '../contract/index.js';
|
|
@@ -41,10 +42,14 @@ import { appendHandoffHeartbeat, handoffProgressExcerpt, readHandoff } from '../
|
|
|
41
42
|
import { markStarted, onTodosChanged, readTodos, removeTodo, todoTaskText } from '../todos.js';
|
|
42
43
|
import { HistoryCursorError, deriveRunContextEvents, readEventsAfterLiveCursor, readRunHistoryPage, validateLiveCursor, } from '../runs/event-history.js';
|
|
43
44
|
import { readRunIndexFromDisk } from '../runs/run-index.js';
|
|
45
|
+
import { clientRequestHash } from '../runs/client-request.js';
|
|
44
46
|
import { ColdRepoHandles } from './cold-repo-handles.js';
|
|
45
47
|
import { isV2WireEventType } from '../runs/ui-event-sink.js';
|
|
46
48
|
import { runEventsQuerySchema, runHistoryQuerySchema, runIdParamSchema, } from '../contract/index.js';
|
|
47
49
|
import { resolveAttachmentPath } from '../workflows/attachment-path.js';
|
|
50
|
+
import { artifactParamsSchema, fileLinkQuerySchema } from '../contract/index.js';
|
|
51
|
+
import { artifactDirectory, listArtifacts, readArtifact } from '../artifacts/store.js';
|
|
52
|
+
import { artifactPreview, loadFileLink, rasterMime } from '../artifacts/resolve.js';
|
|
48
53
|
import { isUntouchedCancelledRun, toPastedContent } from '../workflows/run.js';
|
|
49
54
|
import { removeWorktree, worktreeDiff, worktreeDiffStat, worktreeSizeBytes } from '../git-worktree.js';
|
|
50
55
|
import { isReclaimable, reclaimWorktrees } from '../runs/retention.js';
|
|
@@ -64,7 +69,7 @@ import { defaultAgentProfile, listAgentProfiles, profileDirState, resolveProfile
|
|
|
64
69
|
import { PROFILE_CAPABLE_PROVIDERS, profileEnv, supportsProfiles } from '../core/agent-profiles.js';
|
|
65
70
|
import { quoteExecutable, withEnvPrefix } from '../core/shell-env.js';
|
|
66
71
|
import { resolveCursorExecutable } from '../core/cursor-model-catalog.js';
|
|
67
|
-
import { allocateProjectSlug, listProjects, normalizeProjectTags, probeProjectStatus, registerProject, removeProject, shouldRegisterProject, } from '../workspace/projects.js';
|
|
72
|
+
import { allocateProjectSlug, listProjects, normalizeProjectTags, probeProjectStatus, toProjectListEntry, registerProject, removeProject, shouldRegisterProject, } from '../workspace/projects.js';
|
|
68
73
|
import { mergeWriteWorkspaceUiState, readWorkspaceUiState } from '../workspace/ui-state.js';
|
|
69
74
|
import { checkoutRepo } from './checkout.js';
|
|
70
75
|
import { ProjectContextError, ProjectContexts } from './project-context.js';
|
|
@@ -72,6 +77,8 @@ import { reviewGateEnabled } from '../runs/review-gate.js';
|
|
|
72
77
|
import { readUiState, uiStatePath } from '../ui-state.js';
|
|
73
78
|
import { agentHomePaths, expandTilde } from '../paths.js';
|
|
74
79
|
import { isLoopbackHostHeader, normalizeHostname, resolveCapabilities } from './capabilities.js';
|
|
80
|
+
import { applicationUpdateInputSchema } from '../contract/index.js';
|
|
81
|
+
import { ApplicationUpdateConflictError, ApplicationUpdateFailureError } from '../application-update/service.js';
|
|
75
82
|
import { createSocketHub } from './ws.js';
|
|
76
83
|
import { browseDirectory, isInsideBrowseRoot, isLexicallyInsideBrowseRoot, resolveBrowseRoot } from './fs-browse.js';
|
|
77
84
|
import { parseRemote, resolveForge } from './forge/index.js';
|
|
@@ -304,9 +311,16 @@ const startRunSchema = z
|
|
|
304
311
|
// audit trail survives the composer detour. Bounded like every other
|
|
305
312
|
// string here; a todo id is a short generated key.
|
|
306
313
|
todoId: z.string().min(1).max(200, 'must be at most 200 characters').optional(),
|
|
314
|
+
// Idempotent start (#504): the same id and payload answer 200 with the existing run.
|
|
315
|
+
clientRequestId: z.string().uuid().optional(),
|
|
316
|
+
// Task webhook opt-in (#589). Absent = off; not part of the idempotency hash.
|
|
317
|
+
notify: z.boolean().optional(),
|
|
307
318
|
})
|
|
308
319
|
.refine((b) => Boolean(b.workflow) !== Boolean(b.steps), {
|
|
309
320
|
message: 'provide either "workflow" or "steps", not both',
|
|
321
|
+
})
|
|
322
|
+
.refine((b) => !(b.clientRequestId && (b.variants ?? 1) > 1), {
|
|
323
|
+
message: CLIENT_REQUEST_VARIANTS_ERROR,
|
|
310
324
|
});
|
|
311
325
|
const pickSchema = z.object({
|
|
312
326
|
runId: z.string().min(1),
|
|
@@ -811,6 +825,11 @@ export function createApp(deps) {
|
|
|
811
825
|
automationStore: deps.automationStore ?? AutomationStore.open(bootDataDir),
|
|
812
826
|
launchKey: ensureLaunchKey(bootDataDir), // bookmarklet auto-start secret (spec 011)
|
|
813
827
|
};
|
|
828
|
+
// The task webhook (#589): attached before any recovery — the boot store by `index.ts`, lazy
|
|
829
|
+
// projects through `prepareManager` below. Attaching is idempotent, so the calls here only
|
|
830
|
+
// cover an app built without boot's wiring (tests, embedded servers).
|
|
831
|
+
const taskWebhooks = deps.taskWebhooks ?? new TaskWebhooks({ fetch: deps.taskWebhookFetch, origin: '' });
|
|
832
|
+
taskWebhooks.attach(bootContext);
|
|
814
833
|
// Non-boot projects build lazily on first scoped request; their managers
|
|
815
834
|
// count against the same workspace semaphore as the boot manager (step 2.5).
|
|
816
835
|
const contexts = deps.contexts ?? new ProjectContexts({
|
|
@@ -821,7 +840,11 @@ export function createApp(deps) {
|
|
|
821
840
|
return listProjects(selector);
|
|
822
841
|
},
|
|
823
842
|
semaphore: deps.semaphore,
|
|
824
|
-
prepareManager: project =>
|
|
843
|
+
prepareManager: (project) => {
|
|
844
|
+
taskWebhooks.attach(project);
|
|
845
|
+
return deps.delegation?.attachProject(project);
|
|
846
|
+
},
|
|
847
|
+
afterRecover: (project) => deps.delegation?.service.armDestroyRetries(project.id),
|
|
825
848
|
});
|
|
826
849
|
// Workspace-level SSE bus (step 2.8) — the registry mutators and the
|
|
827
850
|
// checkout flow (Phase 4) emit here; /api/workspace/events relays.
|
|
@@ -845,6 +868,17 @@ export function createApp(deps) {
|
|
|
845
868
|
}
|
|
846
869
|
contexts.onStoreCreated((store) => providerRuntimeAuth.watch(store));
|
|
847
870
|
contexts.onContextBuilt((ctx) => providerRuntimeAuth.watch(ctx.store));
|
|
871
|
+
// Injected contexts (tests) skip `prepareManager`; attach those when they are built.
|
|
872
|
+
for (const id of contexts.ids()) {
|
|
873
|
+
const ctx = contexts.peek(id);
|
|
874
|
+
if (ctx)
|
|
875
|
+
taskWebhooks.attach(ctx);
|
|
876
|
+
}
|
|
877
|
+
contexts.onContextBuilt((ctx) => taskWebhooks.attach(ctx));
|
|
878
|
+
const projectRegistryEntry = async (projectId) => {
|
|
879
|
+
const id = projectId === 'default' ? await resolveBootProject() : projectId;
|
|
880
|
+
return (await loadWorkspaceConfig()).projects.find((entry) => entry.id === id);
|
|
881
|
+
};
|
|
848
882
|
const app = new Hono();
|
|
849
883
|
// Reject oversized request bodies before they reach any handler (#429). GETs
|
|
850
884
|
// and SSE carry no body, so this only ever gates the mutating routes.
|
|
@@ -1088,6 +1122,9 @@ export function createApp(deps) {
|
|
|
1088
1122
|
// the key as always-present, which is a shape no client ever receives. The contract schema
|
|
1089
1123
|
// says `.optional()`, and contract-parity.test.ts holds the two together.
|
|
1090
1124
|
...(update?.latest !== undefined ? { latestVersion: update.latest } : {}),
|
|
1125
|
+
...(deps.applicationUpdate ? { applicationUpdate: caps.localHandoff
|
|
1126
|
+
? deps.applicationUpdate.snapshot()
|
|
1127
|
+
: { status: 'idle', supported: false, message: 'Update this installation manually.' } } : {}),
|
|
1091
1128
|
// Health is CORS-open and, in hosted mode, reachable off the loopback —
|
|
1092
1129
|
// so any site/host that reads it would learn the developer's absolute
|
|
1093
1130
|
// checkout path and username (#431). Local mode keeps the full path (the
|
|
@@ -1163,6 +1200,10 @@ export function createApp(deps) {
|
|
|
1163
1200
|
})();
|
|
1164
1201
|
return healthInFlight;
|
|
1165
1202
|
};
|
|
1203
|
+
deps.applicationUpdate?.onStateChange?.(() => {
|
|
1204
|
+
healthCache = undefined;
|
|
1205
|
+
void refreshHealth();
|
|
1206
|
+
});
|
|
1166
1207
|
// The read the GET and the topic snapshot share. On the live server (a hub is
|
|
1167
1208
|
// injected) it answers from cache instantly and revalidates behind the
|
|
1168
1209
|
// response; without a hub — a bare app in tests — there is no refresher
|
|
@@ -2039,7 +2080,7 @@ export function createApp(deps) {
|
|
|
2039
2080
|
const parsed = { data: c.req.valid('json') };
|
|
2040
2081
|
// `default` is the boot alias the cockpit is allowed to use everywhere else.
|
|
2041
2082
|
const id = raw === 'default' ? await resolveBootProject() : raw;
|
|
2042
|
-
const { maxParallel, tags } = parsed.data;
|
|
2083
|
+
const { maxParallel, tags, webhook } = parsed.data;
|
|
2043
2084
|
// Read-first (mirroring DELETE, server.ts:1252-1258): a well-formed but
|
|
2044
2085
|
// unknown id must 404 WITHOUT rewriting the config — otherwise it would both
|
|
2045
2086
|
// do a needless full-config tmp+rename and, on a read-only home, surface the
|
|
@@ -2081,6 +2122,16 @@ export function createApp(deps) {
|
|
|
2081
2122
|
else
|
|
2082
2123
|
entry.tags = normalized;
|
|
2083
2124
|
}
|
|
2125
|
+
if (webhook !== undefined) {
|
|
2126
|
+
// The token is write-only (#589): the form never has it back, so an omitted token
|
|
2127
|
+
// keeps the stored one and only `''` clears it.
|
|
2128
|
+
if (webhook === null)
|
|
2129
|
+
delete entry.webhook;
|
|
2130
|
+
else {
|
|
2131
|
+
const token = webhook.token === undefined ? entry.webhook?.token : webhook.token || undefined;
|
|
2132
|
+
entry.webhook = { url: webhook.url, ...(token ? { token } : {}) };
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2084
2135
|
updated = entry;
|
|
2085
2136
|
});
|
|
2086
2137
|
}
|
|
@@ -2096,7 +2147,41 @@ export function createApp(deps) {
|
|
|
2096
2147
|
// `PUT /api/workspace/config` fires for a workspace-cap change.
|
|
2097
2148
|
await deps.semaphore?.refresh();
|
|
2098
2149
|
const body = {
|
|
2099
|
-
project:
|
|
2150
|
+
project: toProjectListEntry(updated, await probeProjectStatus(updated.root)),
|
|
2151
|
+
};
|
|
2152
|
+
return c.json(body);
|
|
2153
|
+
})
|
|
2154
|
+
// "Send test" (#589): one `task.test` delivery to the STORED webhook, now, with the timeout
|
|
2155
|
+
// the real deliveries use and no retries — the settings page wants the answer, not a queue.
|
|
2156
|
+
// Refused in single-project mode like every other registry edit route.
|
|
2157
|
+
.post('/projects/:projectId/webhook/test', async (c) => {
|
|
2158
|
+
if (capabilities().singleProject) {
|
|
2159
|
+
return c.json(singleProjectRefusal('editing projects'), 409);
|
|
2160
|
+
}
|
|
2161
|
+
const raw = c.req.param('projectId');
|
|
2162
|
+
if (!projectIdSchema.safeParse(raw).success) {
|
|
2163
|
+
return c.json({ error: `unknown project: ${raw}` }, 404);
|
|
2164
|
+
}
|
|
2165
|
+
const entry = await projectRegistryEntry(raw).catch(() => undefined);
|
|
2166
|
+
if (!entry)
|
|
2167
|
+
return c.json({ error: `unknown project: ${raw}` }, 404);
|
|
2168
|
+
if (!entry.webhook)
|
|
2169
|
+
return c.json({ error: NO_WEBHOOK_ERROR }, 400);
|
|
2170
|
+
const payload = {
|
|
2171
|
+
event: 'task.test',
|
|
2172
|
+
deliveryId: randomUUID(),
|
|
2173
|
+
projectId: entry.id,
|
|
2174
|
+
occurredAt: new Date().toISOString(),
|
|
2175
|
+
};
|
|
2176
|
+
if (process.env.CEZ_DRY_RUN === '1') {
|
|
2177
|
+
const body = { ok: true, dryRun: true };
|
|
2178
|
+
return c.json(body);
|
|
2179
|
+
}
|
|
2180
|
+
const result = await deliverOnce(entry.webhook, payload, { fetch: deps.taskWebhookFetch });
|
|
2181
|
+
const body = {
|
|
2182
|
+
ok: result.ok,
|
|
2183
|
+
...(result.status === undefined ? {} : { status: result.status }),
|
|
2184
|
+
...(result.error === undefined ? {} : { error: result.error }),
|
|
2100
2185
|
};
|
|
2101
2186
|
return c.json(body);
|
|
2102
2187
|
})
|
|
@@ -2232,7 +2317,7 @@ export function createApp(deps) {
|
|
|
2232
2317
|
let project;
|
|
2233
2318
|
try {
|
|
2234
2319
|
const entry = await registerProject(requested, source);
|
|
2235
|
-
project =
|
|
2320
|
+
project = toProjectListEntry(entry, await probeProjectStatus(entry.root));
|
|
2236
2321
|
}
|
|
2237
2322
|
catch (err) {
|
|
2238
2323
|
// e.g. a read-only home — nothing was persisted (atomic tmp+rename).
|
|
@@ -2342,6 +2427,32 @@ export function createApp(deps) {
|
|
|
2342
2427
|
throw error;
|
|
2343
2428
|
}
|
|
2344
2429
|
});
|
|
2430
|
+
// Workspace-level, server-selected target and executable. The empty JSON body is strict.
|
|
2431
|
+
const applicationUpdateRoutes = new Hono()
|
|
2432
|
+
.post('/workspace/application-update/apply', jsonZodValidator(applicationUpdateInputSchema), async (c) => {
|
|
2433
|
+
if (!capabilities().localHandoff)
|
|
2434
|
+
return c.json({ error: 'Application updates require a local cockpit.' }, 409);
|
|
2435
|
+
if (!deps.applicationUpdate)
|
|
2436
|
+
return c.json({ error: 'Application update is unavailable.' }, 409);
|
|
2437
|
+
try {
|
|
2438
|
+
return c.json({ state: await deps.applicationUpdate.apply() });
|
|
2439
|
+
}
|
|
2440
|
+
catch (error) {
|
|
2441
|
+
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);
|
|
2442
|
+
}
|
|
2443
|
+
})
|
|
2444
|
+
.post('/workspace/application-update/restart', jsonZodValidator(applicationUpdateInputSchema), async (c) => {
|
|
2445
|
+
if (!capabilities().localHandoff)
|
|
2446
|
+
return c.json({ error: 'Application updates require a local cockpit.' }, 409);
|
|
2447
|
+
if (!deps.applicationUpdate)
|
|
2448
|
+
return c.json({ error: 'Application update is unavailable.' }, 409);
|
|
2449
|
+
try {
|
|
2450
|
+
return c.json({ state: await deps.applicationUpdate.restart() });
|
|
2451
|
+
}
|
|
2452
|
+
catch (error) {
|
|
2453
|
+
return c.json({ error: error instanceof ApplicationUpdateConflictError ? error.message : 'Application restart is unavailable.' }, error instanceof ApplicationUpdateConflictError ? 409 : 500);
|
|
2454
|
+
}
|
|
2455
|
+
});
|
|
2345
2456
|
// ---- GUI clone (multi-project spec, step 4.3) ----------------------------
|
|
2346
2457
|
// "Add project → Clone from GitHub": clone into the checkout root, then
|
|
2347
2458
|
// register the result through `registerFolder` above (same guards, same
|
|
@@ -2891,7 +3002,7 @@ export function createApp(deps) {
|
|
|
2891
3002
|
automationsChanged();
|
|
2892
3003
|
return c.body(null, 204);
|
|
2893
3004
|
})
|
|
2894
|
-
.post('/automations/:id/enable', (c) => {
|
|
3005
|
+
.post('/automations/:id/enable', async (c) => {
|
|
2895
3006
|
const store = c.get('project').automationStore;
|
|
2896
3007
|
const current = store.get(c.req.param('id'));
|
|
2897
3008
|
if (!current)
|
|
@@ -2905,7 +3016,7 @@ export function createApp(deps) {
|
|
|
2905
3016
|
cursor: { timestamp: baselineAt },
|
|
2906
3017
|
nextCheckAt: new Date(Date.now() + automation.intervalSeconds * 1_000).toISOString(),
|
|
2907
3018
|
});
|
|
2908
|
-
store.appendLog({ automationId: automation.id, revision: automation.revision, result: 'baseline', reason: 'Enabled from a current-time baseline; existing records were not launched.' });
|
|
3019
|
+
await store.appendLog({ automationId: automation.id, revision: automation.revision, result: 'baseline', reason: 'Enabled from a current-time baseline; existing records were not launched.' });
|
|
2909
3020
|
emitAutomationChange(c.get('project'), automation.id, automation.revision);
|
|
2910
3021
|
automationsChanged();
|
|
2911
3022
|
return c.json({ automation });
|
|
@@ -3044,6 +3155,7 @@ export function createApp(deps) {
|
|
|
3044
3155
|
};
|
|
3045
3156
|
// ---- chained family: runs lifecycle + artifacts (project-scoped) ----
|
|
3046
3157
|
const delegationService = deps.delegation?.service ?? new DelegationService();
|
|
3158
|
+
delegationService.setDiscovery({ models: modelCatalog, providers: providerStatus });
|
|
3047
3159
|
const runsRoutes = new Hono()
|
|
3048
3160
|
.get('/runs', (c) => c.json(c.get('project').store.listRuns().map(run => withUsage(run))))
|
|
3049
3161
|
.get('/runs/:id/relationships', paramZodValidator(runIdParamSchema), queryZodValidator(workerEmptyRequestSchema), (c) => {
|
|
@@ -3105,6 +3217,34 @@ export function createApp(deps) {
|
|
|
3105
3217
|
const { store } = c.get('project');
|
|
3106
3218
|
const run = store.setPinned(c.req.valid('param').id, c.req.valid('json').pinned !== false);
|
|
3107
3219
|
return run ? c.json(run) : c.json({ error: 'not found' }, 404);
|
|
3220
|
+
})
|
|
3221
|
+
// Task webhook opt-in for one run (#589), valid in every state — handing a finished task to a
|
|
3222
|
+
// bot for follow-up is a real case. Turning it on sends one `task.subscribed` delivery with
|
|
3223
|
+
// the note; the note never reaches the agent session. Either direction leaves a durable
|
|
3224
|
+
// `handoff` event so the thread shows when and why. Idempotent: repeating `true` with a
|
|
3225
|
+
// new note is how "Send a note…" works.
|
|
3226
|
+
.post('/runs/:id/notify', paramZodValidator(runIdParamSchema), jsonZodValidator(notifyRunInputSchema), async (c) => {
|
|
3227
|
+
const project = c.get('project');
|
|
3228
|
+
const { id } = c.req.valid('param');
|
|
3229
|
+
const { notify, message } = c.req.valid('json');
|
|
3230
|
+
if (!project.store.getRun(id))
|
|
3231
|
+
return c.json({ error: 'not found' }, 404);
|
|
3232
|
+
if (notify) {
|
|
3233
|
+
const entry = await projectRegistryEntry(project.id).catch(() => undefined);
|
|
3234
|
+
if (!entry?.webhook)
|
|
3235
|
+
return c.json({ error: NO_WEBHOOK_ERROR }, 400);
|
|
3236
|
+
}
|
|
3237
|
+
const run = project.store.updateRun(id, { notify: notify ? true : undefined });
|
|
3238
|
+
if (!run)
|
|
3239
|
+
return c.json({ error: 'not found' }, 404);
|
|
3240
|
+
project.store.appendEvent(id, {
|
|
3241
|
+
type: 'handoff',
|
|
3242
|
+
notify,
|
|
3243
|
+
...(message === undefined ? {} : { message }),
|
|
3244
|
+
});
|
|
3245
|
+
if (notify)
|
|
3246
|
+
taskWebhooks.attach(project).subscribed(id, message);
|
|
3247
|
+
return c.json(run);
|
|
3108
3248
|
})
|
|
3109
3249
|
// The per-task off switch for that resume (the workspace setting is Settings → Resources).
|
|
3110
3250
|
// Idempotent: a run with nothing pending answers 200 too, because "this task will not
|
|
@@ -3131,8 +3271,21 @@ export function createApp(deps) {
|
|
|
3131
3271
|
return run ? c.json(run) : c.json({ error: 'not found' }, 404);
|
|
3132
3272
|
})
|
|
3133
3273
|
.post('/runs', jsonZodValidator(startRunSchema), async (c) => {
|
|
3134
|
-
const { root: repoRoot, dataDir, manager } = c.get('project');
|
|
3274
|
+
const { root: repoRoot, dataDir, manager, store } = c.get('project');
|
|
3135
3275
|
const parsed = { data: c.req.valid('json') };
|
|
3276
|
+
const clientRequest = parsed.data.clientRequestId
|
|
3277
|
+
? { id: parsed.data.clientRequestId, hash: clientRequestHash(parsed.data) }
|
|
3278
|
+
: undefined;
|
|
3279
|
+
// A retry only retrieves a persisted result: mutable start prerequisites (workflow,
|
|
3280
|
+
// provider, account and model policy) must not invalidate an already accepted request.
|
|
3281
|
+
if (clientRequest) {
|
|
3282
|
+
const existing = store.findRunByClientRequestId(clientRequest.id);
|
|
3283
|
+
if (existing) {
|
|
3284
|
+
if (existing.clientRequestHash !== clientRequest.hash)
|
|
3285
|
+
return c.json({ error: 'request id payload conflict' }, 409);
|
|
3286
|
+
return c.json(existing, 200);
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3136
3289
|
if (agentModelsLocked(repoRoot) &&
|
|
3137
3290
|
(parsed.data.model?.trim() || parsed.data.effort?.trim())) {
|
|
3138
3291
|
return c.json({ error: AGENT_MODELS_LOCKED_ERROR }, 409);
|
|
@@ -3168,9 +3321,15 @@ export function createApp(deps) {
|
|
|
3168
3321
|
if ('error' in account)
|
|
3169
3322
|
return c.json({ error: account.error }, 400);
|
|
3170
3323
|
}
|
|
3324
|
+
// Task webhook (#589): an explicit `true` on a project with no webhook is refused, so a
|
|
3325
|
+
// bot learns in one round trip. `cez task start`'s implicit default never sends it there.
|
|
3326
|
+
if (parsed.data.notify === true && !(await projectRegistryEntry(c.get('project').id).catch(() => undefined))?.webhook) {
|
|
3327
|
+
return c.json({ error: NO_WEBHOOK_ERROR }, 400);
|
|
3328
|
+
}
|
|
3171
3329
|
const images = parsed.data.images?.map(toPastedContent);
|
|
3172
3330
|
const input = {
|
|
3173
3331
|
task: parsed.data.task,
|
|
3332
|
+
notify: parsed.data.notify,
|
|
3174
3333
|
model: parsed.data.model,
|
|
3175
3334
|
effort: parsed.data.effort,
|
|
3176
3335
|
runner: parsed.data.runner,
|
|
@@ -3186,6 +3345,18 @@ export function createApp(deps) {
|
|
|
3186
3345
|
// CEZ_TODOS_FILE alike (RunManager.agentEnv).
|
|
3187
3346
|
generateFollowups: capabilities().followups ? parsed.data.generateFollowups : false,
|
|
3188
3347
|
};
|
|
3348
|
+
if (clientRequest) {
|
|
3349
|
+
// Recheck after every await: concurrent first requests may both miss the early lookup.
|
|
3350
|
+
// The manager's lookup and create are synchronous, so exactly one can create (#504).
|
|
3351
|
+
const result = manager.startRunIdempotent(workflow, input, clientRequest);
|
|
3352
|
+
if ('conflict' in result)
|
|
3353
|
+
return c.json({ error: 'request id payload conflict' }, 409);
|
|
3354
|
+
if (!result.created)
|
|
3355
|
+
return c.json(result.run, 200);
|
|
3356
|
+
if (parsed.data.todoId)
|
|
3357
|
+
await noteTodoStarted(dataDir, parsed.data.todoId, result.run.id);
|
|
3358
|
+
return c.json(result.run, 201);
|
|
3359
|
+
}
|
|
3189
3360
|
const variants = parsed.data.variants ?? 1;
|
|
3190
3361
|
if (variants > 1) {
|
|
3191
3362
|
// Variants live in worktrees — without git there's nothing to isolate
|
|
@@ -3703,6 +3874,70 @@ export function createApp(deps) {
|
|
|
3703
3874
|
if (!result.ok)
|
|
3704
3875
|
return c.json({ error: result.error }, 409);
|
|
3705
3876
|
return c.json(result.commit);
|
|
3877
|
+
})
|
|
3878
|
+
.get('/runs/:id/file-link', paramZodValidator(artifactParamsSchema.omit({ artifactId: true })), queryZodValidator(fileLinkQuerySchema), async (c) => {
|
|
3879
|
+
const { root, dataDir, store } = c.get('project');
|
|
3880
|
+
const run = store.getRun(c.req.valid('param').id);
|
|
3881
|
+
if (!run)
|
|
3882
|
+
return c.json({ error: 'not found' }, 404);
|
|
3883
|
+
const query = c.req.valid('query');
|
|
3884
|
+
const loaded = await loadFileLink({ dataDir, runId: run.id, projectRoot: root, workingDirectory: run.worktreePath ?? root, path: query.path });
|
|
3885
|
+
if (query.raw === '1') {
|
|
3886
|
+
if (!loaded.imageBytes || loaded.preview.type !== 'file')
|
|
3887
|
+
return c.json({ error: 'Only bounded raster images can be served inline.' }, 409);
|
|
3888
|
+
return c.body(new Uint8Array(loaded.imageBytes).buffer, 200, {
|
|
3889
|
+
'content-type': rasterMime(loaded.preview.path), 'x-content-type-options': 'nosniff',
|
|
3890
|
+
'content-security-policy': "default-src 'none'; sandbox", 'cache-control': 'private, no-store',
|
|
3891
|
+
});
|
|
3892
|
+
}
|
|
3893
|
+
return c.json(loaded.preview);
|
|
3894
|
+
})
|
|
3895
|
+
.get('/runs/:id/artifacts', paramZodValidator(artifactParamsSchema.omit({ artifactId: true })), async (c) => {
|
|
3896
|
+
const { dataDir, store } = c.get('project');
|
|
3897
|
+
const id = c.req.valid('param').id;
|
|
3898
|
+
if (!store.getRun(id))
|
|
3899
|
+
return c.json({ error: 'not found' }, 404);
|
|
3900
|
+
return c.json({ artifacts: await listArtifacts(artifactDirectory(dataDir, id), id) });
|
|
3901
|
+
})
|
|
3902
|
+
.get('/runs/:id/artifacts/:artifactId', paramZodValidator(artifactParamsSchema), async (c) => {
|
|
3903
|
+
const { dataDir, store } = c.get('project');
|
|
3904
|
+
const { id, artifactId } = c.req.valid('param');
|
|
3905
|
+
if (!store.getRun(id))
|
|
3906
|
+
return c.json({ error: 'not found' }, 404);
|
|
3907
|
+
const entry = await readArtifact(artifactDirectory(dataDir, id), id, artifactId);
|
|
3908
|
+
if (!entry)
|
|
3909
|
+
return c.json({ error: 'Published file is missing or unavailable.' }, 404);
|
|
3910
|
+
return c.json(artifactPreview(entry.metadata, entry.bytes));
|
|
3911
|
+
})
|
|
3912
|
+
.get('/runs/:id/artifacts/:artifactId/download', paramZodValidator(artifactParamsSchema), async (c) => {
|
|
3913
|
+
const { dataDir, store } = c.get('project');
|
|
3914
|
+
const { id, artifactId } = c.req.valid('param');
|
|
3915
|
+
if (!store.getRun(id))
|
|
3916
|
+
return c.json({ error: 'not found' }, 404);
|
|
3917
|
+
const entry = await readArtifact(artifactDirectory(dataDir, id), id, artifactId);
|
|
3918
|
+
if (!entry)
|
|
3919
|
+
return c.json({ error: 'Published file is missing or unavailable.' }, 404);
|
|
3920
|
+
return c.body(new Uint8Array(entry.bytes).buffer, 200, {
|
|
3921
|
+
'content-type': 'application/octet-stream', 'x-content-type-options': 'nosniff',
|
|
3922
|
+
'content-disposition': `attachment; filename="${entry.metadata.name.replace(/[^A-Za-z0-9._-]/g, '_')}"`,
|
|
3923
|
+
'content-security-policy': "default-src 'none'; sandbox", 'cache-control': 'private, no-store',
|
|
3924
|
+
});
|
|
3925
|
+
})
|
|
3926
|
+
.get('/runs/:id/artifacts/:artifactId/image', paramZodValidator(artifactParamsSchema), async (c) => {
|
|
3927
|
+
const { dataDir, store } = c.get('project');
|
|
3928
|
+
const { id, artifactId } = c.req.valid('param');
|
|
3929
|
+
if (!store.getRun(id))
|
|
3930
|
+
return c.json({ error: 'not found' }, 404);
|
|
3931
|
+
const entry = await readArtifact(artifactDirectory(dataDir, id), id, artifactId);
|
|
3932
|
+
if (!entry)
|
|
3933
|
+
return c.json({ error: 'Published file is missing or unavailable.' }, 404);
|
|
3934
|
+
const mime = rasterMime(entry.metadata.name);
|
|
3935
|
+
if (!mime)
|
|
3936
|
+
return c.json({ error: 'Only raster images can be served inline.' }, 409);
|
|
3937
|
+
return c.body(new Uint8Array(entry.bytes).buffer, 200, {
|
|
3938
|
+
'content-type': mime, 'x-content-type-options': 'nosniff',
|
|
3939
|
+
'content-security-policy': "default-src 'none'; sandbox", 'cache-control': 'private, no-store',
|
|
3940
|
+
});
|
|
3706
3941
|
})
|
|
3707
3942
|
// Files tab: directory listing (path omitted or a dir) or file content
|
|
3708
3943
|
// (size-capped, binary flagged). Traversal-safe — readWorktreePath rejects
|
|
@@ -4052,7 +4287,10 @@ export function createApp(deps) {
|
|
|
4052
4287
|
// The keep-limit the panel reports is the one the enforcer will actually
|
|
4053
4288
|
// apply — inherited from the workspace default when this repo sets none.
|
|
4054
4289
|
const keep = await resolveWorktreeRetention(repoRoot);
|
|
4055
|
-
const
|
|
4290
|
+
const allRuns = store.listRuns();
|
|
4291
|
+
// Listing is on-disk dirs only; parent liveness for #575 uses the full store
|
|
4292
|
+
// so a live `worktree: false` parent is not treated as gone (#570 honesty).
|
|
4293
|
+
const runs = allRuns.filter((r) => r.worktreePath && existsSync(r.worktreePath));
|
|
4056
4294
|
const worktrees = await Promise.all(runs.map(async (r) => ({
|
|
4057
4295
|
runId: r.id,
|
|
4058
4296
|
title: r.title ?? r.id,
|
|
@@ -4061,7 +4299,7 @@ export function createApp(deps) {
|
|
|
4061
4299
|
// POSIX `du` — degrades to null (Windows / du missing / error); never blocks.
|
|
4062
4300
|
sizeBytes: await worktreeSizeBytes(r.worktreePath),
|
|
4063
4301
|
finishedAt: r.finishedAt ?? null,
|
|
4064
|
-
reclaimable: isReclaimable(r),
|
|
4302
|
+
reclaimable: isReclaimable(r, allRuns),
|
|
4065
4303
|
})));
|
|
4066
4304
|
// Total is null when any size degraded, so the panel never shows a wrong sum.
|
|
4067
4305
|
const totalBytes = worktrees.some((w) => w.sizeBytes === null)
|
|
@@ -5080,6 +5318,7 @@ export function createApp(deps) {
|
|
|
5080
5318
|
.route('/', projectsRoutes)
|
|
5081
5319
|
.route('/', agentProfilesRoutes)
|
|
5082
5320
|
.route('/', skillsUpdateRoutes)
|
|
5321
|
+
.route('/', applicationUpdateRoutes)
|
|
5083
5322
|
.route('/', workspaceConfigRoutes)
|
|
5084
5323
|
.route('/', fsBrowseRoutes)
|
|
5085
5324
|
.route('/', automationChecksRoutes)
|
|
@@ -5110,6 +5349,13 @@ export function createApp(deps) {
|
|
|
5110
5349
|
routed.get('*', (c) => serveShell(c) ?? c.notFound());
|
|
5111
5350
|
return routed;
|
|
5112
5351
|
}
|
|
5352
|
+
/** The host a thread link names (#589): the bound one, or loopback for a wildcard bind. */
|
|
5353
|
+
function taskWebhookHost(bindHost) {
|
|
5354
|
+
if (!bindHost || bindHost === '0.0.0.0' || bindHost === '::')
|
|
5355
|
+
return '127.0.0.1';
|
|
5356
|
+
return bindHost.includes(':') ? `[${bindHost}]` : bindHost;
|
|
5357
|
+
}
|
|
5358
|
+
const NO_WEBHOOK_ERROR = 'this project has no task webhook — set one in Settings → General → Task webhook, or start without notify';
|
|
5113
5359
|
export function startServer(deps, port) {
|
|
5114
5360
|
const workspaceEvents = deps.workspaceEvents ?? new WorkspaceEventBus();
|
|
5115
5361
|
const skillsUpdate = deps.skillsUpdate ?? new SkillsUpdateService({ invalidateCatalog: refreshTeamSkills });
|
|
@@ -5119,11 +5365,16 @@ export function startServer(deps, port) {
|
|
|
5119
5365
|
const automationCoordinator = new AutomationCoordinator({ listProjects });
|
|
5120
5366
|
const bootProjectId = deps.bootProjectId ?? 'default';
|
|
5121
5367
|
const bootAutomationStore = automationCoordinator.store(bootProjectId, deps.repoRoot);
|
|
5368
|
+
const taskWebhooks = deps.taskWebhooks ?? new TaskWebhooks({ fetch: deps.taskWebhookFetch });
|
|
5122
5369
|
const sharedContexts = deps.contexts ?? new ProjectContexts({
|
|
5123
5370
|
listProjects,
|
|
5124
5371
|
semaphore: deps.semaphore,
|
|
5125
5372
|
automationStore: (projectId, root) => automationCoordinator.store(projectId, root),
|
|
5126
|
-
prepareManager: project =>
|
|
5373
|
+
prepareManager: (project) => {
|
|
5374
|
+
taskWebhooks.attach(project);
|
|
5375
|
+
return deps.delegation?.attachProject(project);
|
|
5376
|
+
},
|
|
5377
|
+
afterRecover: (project) => deps.delegation?.service.armDestroyRetries(project.id),
|
|
5127
5378
|
});
|
|
5128
5379
|
// #801: GitHub automations are opt-in. Off, the flag must remove the BEHAVIOR and not merely
|
|
5129
5380
|
// the UI — no scheduler, no GitHub polling, no launched runs — so every entry point into the
|
|
@@ -5133,6 +5384,7 @@ export function startServer(deps, port) {
|
|
|
5133
5384
|
let rescheduleAutomations = () => { };
|
|
5134
5385
|
const app = createApp({
|
|
5135
5386
|
...deps,
|
|
5387
|
+
taskWebhooks,
|
|
5136
5388
|
contexts: sharedContexts,
|
|
5137
5389
|
automationStore: bootAutomationStore,
|
|
5138
5390
|
workspaceEvents,
|
|
@@ -5146,7 +5398,28 @@ export function startServer(deps, port) {
|
|
|
5146
5398
|
// hosted/VPS deployment (which also flips CEZ_REMOTE to gate the local-handoff endpoints) —
|
|
5147
5399
|
// src/index.ts never passes it, so the loopback guarantee holds for the normal CLI.
|
|
5148
5400
|
const server = serve({
|
|
5149
|
-
fetch:
|
|
5401
|
+
fetch: async (request, env) => {
|
|
5402
|
+
const result = await app.fetch(request, env);
|
|
5403
|
+
if (request.method === 'POST' && new URL(request.url).pathname === '/api/v1/workspace/application-update/restart'
|
|
5404
|
+
&& result.status === 200) {
|
|
5405
|
+
// Arming is server-owned. A connected caller must receive the complete
|
|
5406
|
+
// acknowledgement before shutdown; a caller that already disconnected
|
|
5407
|
+
// cannot leave its explicitly requested, armed restart stranded.
|
|
5408
|
+
const response = env.outgoing;
|
|
5409
|
+
const handoff = () => {
|
|
5410
|
+
response.off('finish', handoff);
|
|
5411
|
+
response.off('close', handoff);
|
|
5412
|
+
deps.applicationUpdate?.afterResponse?.();
|
|
5413
|
+
};
|
|
5414
|
+
if (response.destroyed)
|
|
5415
|
+
handoff();
|
|
5416
|
+
else {
|
|
5417
|
+
response.once('finish', handoff);
|
|
5418
|
+
response.once('close', handoff);
|
|
5419
|
+
}
|
|
5420
|
+
}
|
|
5421
|
+
return result;
|
|
5422
|
+
},
|
|
5150
5423
|
port,
|
|
5151
5424
|
hostname: deps.bindHost ?? '127.0.0.1',
|
|
5152
5425
|
});
|
|
@@ -5239,7 +5512,30 @@ export function startServer(deps, port) {
|
|
|
5239
5512
|
});
|
|
5240
5513
|
server.once('close', () => { unsubscribe(); coordinator.stop(); automationScheduler.stop(); sharedContexts.disposeAll(); void deps.delegation?.close(); });
|
|
5241
5514
|
socketHub.attach(server, (req) => verifyWsUpgrade(req, deps.bindHost));
|
|
5242
|
-
|
|
5515
|
+
const shutdownForRestart = async () => {
|
|
5516
|
+
sharedContexts.disposeAll(); // flush every built secondary project before old process exits
|
|
5517
|
+
deps.store.flush();
|
|
5518
|
+
socketHub.close();
|
|
5519
|
+
await deps.delegation?.close();
|
|
5520
|
+
await new Promise((resolve) => {
|
|
5521
|
+
server.close(() => resolve());
|
|
5522
|
+
if ('closeAllConnections' in server)
|
|
5523
|
+
server.closeAllConnections();
|
|
5524
|
+
});
|
|
5525
|
+
};
|
|
5526
|
+
// The task webhook's thread links name the port the listener BOUND (#594 review): `--port 0`
|
|
5527
|
+
// asks for an ephemeral one, so the requested number is not an address. Deliveries queue
|
|
5528
|
+
// behind this, so none goes out before it is set.
|
|
5529
|
+
const setTaskWebhookOrigin = () => {
|
|
5530
|
+
const address = server.address();
|
|
5531
|
+
const bound = address && typeof address === 'object' ? address.port : port;
|
|
5532
|
+
taskWebhooks.setOrigin(`http://${taskWebhookHost(deps.bindHost)}:${bound}`);
|
|
5533
|
+
};
|
|
5534
|
+
if (server.listening)
|
|
5535
|
+
setTaskWebhookOrigin();
|
|
5536
|
+
else
|
|
5537
|
+
server.once('listening', setTaskWebhookOrigin);
|
|
5538
|
+
return Object.assign(server, { shutdownForRestart });
|
|
5243
5539
|
}
|
|
5244
5540
|
/**
|
|
5245
5541
|
* The WebSocket twin of the `/api/*` request-origin guard (#426), applied
|