specrails-desktop 2.55.0 → 2.56.0
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/cli/dist/args.js +80 -0
- package/cli/dist/desktop-client.js +125 -0
- package/cli/dist/desktop-control.js +339 -0
- package/cli/dist/format.js +45 -0
- package/cli/dist/help.js +68 -0
- package/cli/dist/output.js +47 -0
- package/cli/dist/run-command.js +340 -0
- package/cli/dist/specrails-desktop.js +41 -1107
- package/cli/dist/status.js +111 -0
- package/client/dist/assets/{ActivityFeedPage-ILXMZXcb.js → ActivityFeedPage-DdMNmVBk.js} +1 -1
- package/client/dist/assets/{AgentBrowserCapture-UcsupCy7.js → AgentBrowserCapture-CeerUf--.js} +1 -1
- package/client/dist/assets/{AgentModeAnalyticsPane-CQuxYh8B.js → AgentModeAnalyticsPane-BjGb-8rh.js} +2 -2
- package/client/dist/assets/{AgentModeCodePane-zPWbW7ei.js → AgentModeCodePane-8XRkQEo_.js} +2 -2
- package/client/dist/assets/{AgentModeJobsPane-CInByIdl.js → AgentModeJobsPane-BN3dgJOA.js} +1 -1
- package/client/dist/assets/{AgentsPage-BWvgxaBv.js → AgentsPage-DfHWgP_x.js} +1 -1
- package/client/dist/assets/{AnalyticsPage-DP3jakv-.js → AnalyticsPage-CyfhYiBn.js} +1 -1
- package/client/dist/assets/{CodePage-BKXiwE5a.js → CodePage-C2Nf-Q5W.js} +1 -1
- package/client/dist/assets/{DesktopAnalyticsPage-DryJOaMz.js → DesktopAnalyticsPage-tc0p4jJW.js} +1 -1
- package/client/dist/assets/{DocsDialog-BsCG9ksG.js → DocsDialog-Br3bczCp.js} +1 -1
- package/client/dist/assets/{DocsPage-Cc-GeKYl.js → DocsPage-BcDr2cBm.js} +1 -1
- package/client/dist/assets/{ExportDropdown-ClahJdDn.js → ExportDropdown-BVGKEgkd.js} +1 -1
- package/client/dist/assets/{InteractiveJobComposer-DfLF6N0f.js → InteractiveJobComposer-DFPpdg2D.js} +1 -1
- package/client/dist/assets/{JobDetailModal-DHeur9YK.js → JobDetailModal-BdOv-l-M.js} +1 -1
- package/client/dist/assets/{JobDetailPage-CraDKzeG.js → JobDetailPage-vBbNeRuz.js} +1 -1
- package/client/dist/assets/{JobsPage-CcbuSwj7.js → JobsPage-CJCtPhIz.js} +1 -1
- package/client/dist/assets/{LoopBuilderPage-BKtNp494.js → LoopBuilderPage-BemvUOxy.js} +1 -1
- package/client/dist/assets/{LoopPreviewModal-DbZd9isy.js → LoopPreviewModal-C6hNAeYN.js} +1 -1
- package/client/dist/assets/{LoopsPage-DEg6sIkG.js → LoopsPage-BZr0quvh.js} +1 -1
- package/client/dist/assets/{MinimizedChatsContext-BGtEjWsc.js → MinimizedChatsContext-D3QzgV0B.js} +1 -1
- package/client/dist/assets/{PluginsPage-BVqSsPzL.js → PluginsPage-DOpTV75p.js} +1 -1
- package/client/dist/assets/{ProjectSettingsDialog-CAjLgJAZ.js → ProjectSettingsDialog-Dkwrn0z1.js} +1 -1
- package/client/dist/assets/{RepositoryDeliveries-DrcrxLe0.js → RepositoryDeliveries-Du2UCf1a.js} +1 -1
- package/client/dist/assets/{RepositoryScopeSelector-CAGY3xW6.js → RepositoryScopeSelector-hPGZNRBm.js} +1 -1
- package/client/dist/assets/ReviewPacketPage-DcOq-UTA.js +4 -0
- package/client/dist/assets/{TemplatePreviewModal-Bqs-9fhn.js → TemplatePreviewModal-Cejcquah.js} +1 -1
- package/client/dist/assets/{TicketDetailModalContext-C0jQ5yOk.js → TicketDetailModalContext-DAK-NoK6.js} +1 -1
- package/client/dist/assets/{Trans-DtWlCJN9.js → Trans-DEbgkuAY.js} +1 -1
- package/client/dist/assets/{dashboard-CTfEtkqX.js → dashboard-B9uWNGO2.js} +1 -1
- package/client/dist/assets/{dashboard-DMklepmz.js → dashboard-BIwmO9wc.js} +1 -1
- package/client/dist/assets/{dashboard-DBAP2_SQ.js → dashboard-BV4vOZkR.js} +1 -1
- package/client/dist/assets/{dashboard-D40HP2ks.js → dashboard-Bf6MPVsM.js} +1 -1
- package/client/dist/assets/{dashboard-Csf1yGSN.js → dashboard-C8klWVvZ.js} +1 -1
- package/client/dist/assets/{dashboard-C29Qwa-L.js → dashboard-D1ORhPF0.js} +1 -1
- package/client/dist/assets/{dashboard-Cr4D8oMn.js → dashboard-DSubvs9x.js} +1 -1
- package/client/dist/assets/{dashboard-CSJzf5OT.js → dashboard-px_vlRx_.js} +1 -1
- package/client/dist/assets/{formatDistanceToNow-DYmvOA3R.js → formatDistanceToNow-Cc2u5FwQ.js} +1 -1
- package/client/dist/assets/{getTimezoneOffsetInMilliseconds-BAhW5E6V.js → getTimezoneOffsetInMilliseconds-BHm2U-w1.js} +1 -1
- package/client/dist/assets/index-C51x61Bz.css +2 -0
- package/client/dist/assets/{index-BfAZg5DG.js → index-Cxb288Rj.js} +29 -29
- package/client/dist/assets/{jira-api-B27Za5FP.js → jira-api-DrGxiD9p.js} +1 -1
- package/client/dist/assets/packet-8nn9RsU7.js +1 -0
- package/client/dist/assets/packet-B0H15elv.js +1 -0
- package/client/dist/assets/packet-BQvBgrwh.js +1 -0
- package/client/dist/assets/packet-BR7mO0-a.js +1 -0
- package/client/dist/assets/packet-BgcQ6q_K.js +1 -0
- package/client/dist/assets/packet-D2s92Jnn.js +1 -0
- package/client/dist/assets/packet-YbtcD1HS.js +1 -0
- package/client/dist/assets/packet-c0Qj_9eX.js +1 -0
- package/client/dist/assets/{project-repositories-DixIZHTA.js → project-repositories-9Wzs0coQ.js} +1 -1
- package/client/dist/assets/{spending-BGmZBX5B.js → spending-T5GM7wpn.js} +1 -1
- package/client/dist/assets/{useDesktop-CPB4cnwI.js → useDesktop-QogiN0zR.js} +1 -1
- package/client/dist/assets/{useRuntimeRuns-DEQ9HdAM.js → useRuntimeRuns-Dqmz9fS9.js} +1 -1
- package/client/dist/assets/{useSharedWebSocket-DcPRKR8R.js → useSharedWebSocket-Dz-HI6De.js} +2 -2
- package/client/dist/index.html +17 -17
- package/docs/codex.md +5 -5
- package/docs/customizing.md +1 -1
- package/docs/gemini-cli-provider-study.md +14 -14
- package/docs/gemini-core-support-evaluation.md +1 -1
- package/docs/gemini.md +5 -5
- package/docs/internals/README.md +6 -2
- package/docs/internals/adding-a-provider.md +22 -15
- package/docs/internals/agent-runtime-framework-evaluation.md +8 -8
- package/docs/internals/api-reference.md +2 -2
- package/docs/internals/architecture.md +9 -9
- package/docs/internals/browser-capture-performance.md +3 -3
- package/docs/internals/code-explorer-story.md +2 -2
- package/docs/internals/companion-rails-as-loops-contract.md +2 -2
- package/docs/internals/core-runtime-updates.md +5 -0
- package/docs/internals/embedded-browser-native-webview-evaluation.md +5 -5
- package/docs/internals/gemini-mcp-registration.md +2 -2
- package/docs/internals/global-artifacts-alignment-contract.md +1 -1
- package/docs/internals/global-artifacts-relocation-evaluation.md +1 -1
- package/docs/internals/interactive-jobs.md +8 -8
- package/docs/internals/legacy-implementation-notes.md +722 -0
- package/docs/internals/loop-step-log-explorer.md +8 -8
- package/docs/internals/mission-rail-cards.md +14 -14
- package/docs/internals/modular-architecture.md +107 -0
- package/docs/internals/profiles.md +1 -1
- package/docs/internals/programmatic-agent-runtime.md +66 -0
- package/docs/internals/project-builder.md +18 -18
- package/docs/internals/review-packet.md +8 -8
- package/docs/internals/safe-pr-review-flow.md +11 -11
- package/docs/internals/source-architecture.md +131 -0
- package/docs/internals/source-map.md +1356 -0
- package/docs/internals/spec-addenda.md +65 -9
- package/docs/internals/switch-to-agent-mode-design.md +11 -11
- package/docs/tracking-cost.md +1 -1
- package/package.json +5 -2
- package/server/dist/agent-mcp-config.js +1 -1
- package/server/dist/attachment-manager.js +1 -1
- package/server/dist/build-dirs.js +2 -2
- package/server/dist/config.js +0 -3
- package/server/dist/core-completion.js +1 -1
- package/server/dist/core-execution.js +1 -1
- package/server/dist/db/activity.js +39 -0
- package/server/dist/db/connection.js +41 -0
- package/server/dist/db/conversations.js +48 -0
- package/server/dist/db/jobs.js +251 -0
- package/server/dist/db/migrations.js +1521 -0
- package/server/dist/db/proposals.js +50 -0
- package/server/dist/db/settings.js +15 -0
- package/server/dist/db/stats.js +61 -0
- package/server/dist/db/telemetry.js +56 -0
- package/server/dist/db/templates.js +37 -0
- package/server/dist/db/types.js +2 -0
- package/server/dist/db.js +66 -2224
- package/server/dist/desktop-db.js +3 -3
- package/server/dist/desktop-router.js +8 -8
- package/server/dist/git-diagnostics.js +1 -1
- package/server/dist/index.js +11 -11
- package/server/dist/integration-branch.js +3 -14
- package/server/dist/jira/jira-issue-fields.js +0 -1
- package/server/dist/jira/jira-materializer.js +1 -1
- package/server/dist/jira/jira-sync-manager.js +1 -1
- package/server/dist/mcp/guide.js +16 -2
- package/server/dist/mcp/mcp-admin-router.js +1 -1
- package/server/dist/mcp/mcp-server.js +1 -1
- package/server/dist/mcp/tools/catalog.js +2 -0
- package/server/dist/mcp/tools/jobs.js +23 -5
- package/server/dist/mcp/tools/rails.js +2 -2
- package/server/dist/mcp/tools/recovery.js +35 -0
- package/server/dist/mcp/tools/specs.js +2 -2
- package/server/dist/mcp/tools/support.js +4 -1
- package/server/dist/mcp/tools/types.js +3 -3
- package/server/dist/mobile/mobile-gateway.js +1 -1
- package/server/dist/mobile/mobile-missions.js +1 -1
- package/server/dist/{desktop-analytics.js → modules/accounting/runtime/desktop-analytics.js} +2 -2
- package/server/dist/{pricing.js → modules/accounting/runtime/pricing.js} +10 -3
- package/server/dist/{result-event.js → modules/accounting/runtime/result-event.js} +3 -3
- package/server/dist/{spending.js → modules/accounting/runtime/spending.js} +0 -3
- package/server/dist/{telemetry-compactor.js → modules/accounting/runtime/telemetry-compactor.js} +1 -1
- package/server/dist/{telemetry-receiver.js → modules/accounting/runtime/telemetry-receiver.js} +2 -2
- package/server/dist/{agent-runtime-bridge.js → modules/agent-runtime/runtime/agent-runtime-bridge.js} +2 -2
- package/server/dist/{agent-runtime-controls-router.js → modules/agent-runtime/runtime/agent-runtime-controls-router.js} +16 -0
- package/server/dist/{agent-runtime-controls.js → modules/agent-runtime/runtime/agent-runtime-controls.js} +100 -8
- package/server/dist/{agent-runtime-effective-config.js → modules/agent-runtime/runtime/agent-runtime-effective-config.js} +1 -1
- package/server/dist/{agent-runtime-loader.js → modules/agent-runtime/runtime/agent-runtime-loader.js} +3 -3
- package/server/dist/{agent-runtime-paths.js → modules/agent-runtime/runtime/agent-runtime-paths.js} +1 -1
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-recovery.js +53 -0
- package/server/dist/{agent-runtime-settings-router.js → modules/agent-runtime/runtime/agent-runtime-settings-router.js} +2 -2
- package/server/dist/{agent-runtime-settings.js → modules/agent-runtime/runtime/agent-runtime-settings.js} +3 -3
- package/server/dist/{agent-runtime-settlement.js → modules/agent-runtime/runtime/agent-runtime-settlement.js} +6 -6
- package/server/dist/{agent-defaults.js → modules/agents/runtime/agent-defaults.js} +3 -3
- package/server/dist/{agent-generator.js → modules/agents/runtime/agent-generator.js} +7 -7
- package/server/dist/{agent-refine-manager.js → modules/agents/runtime/agent-refine-manager.js} +5 -5
- package/server/dist/{agent-store.js → modules/agents/runtime/agent-store.js} +1 -1
- package/server/dist/{profile-manager.js → modules/agents/runtime/profile-manager.js} +2 -2
- package/server/dist/{profiles-router.js → modules/agents/runtime/profiles-router.js} +5 -5
- package/server/dist/{browser-capture-manager.js → modules/browser/runtime/browser-capture-manager.js} +2 -4
- package/server/dist/{browser-playwright.js → modules/browser/runtime/browser-playwright.js} +1 -1
- package/server/dist/{blueprint-chat-manager.js → modules/builder/runtime/blueprint-chat-manager.js} +7 -7
- package/server/dist/{blueprint-commit.js → modules/builder/runtime/blueprint-commit.js} +8 -8
- package/server/dist/{blueprint-draft-parser.js → modules/builder/runtime/blueprint-draft-parser.js} +1 -1
- package/server/dist/{blueprint-generation.js → modules/builder/runtime/blueprint-generation.js} +1 -1
- package/server/dist/{blueprint-operator-prompt.js → modules/builder/runtime/blueprint-operator-prompt.js} +1 -1
- package/server/dist/{blueprint-router.js → modules/builder/runtime/blueprint-router.js} +3 -3
- package/server/dist/{blueprint-spec-quality.js → modules/builder/runtime/blueprint-spec-quality.js} +1 -1
- package/server/dist/{milestone-chain.js → modules/builder/runtime/milestone-chain.js} +1 -1
- package/server/dist/{milestone-progress.js → modules/builder/runtime/milestone-progress.js} +6 -6
- package/server/dist/{code-activity.js → modules/code/runtime/code-activity.js} +1 -1
- package/server/dist/{code-explorer-router.js → modules/code/runtime/code-explorer-router.js} +5 -5
- package/server/dist/{file-provenance.js → modules/code/runtime/file-provenance.js} +1 -1
- package/server/dist/{file-story-manager.js → modules/code/runtime/file-story-manager.js} +2 -2
- package/server/dist/{file-story.js → modules/code/runtime/file-story.js} +2 -2
- package/server/dist/{file-summary-generator.js → modules/code/runtime/file-summary-generator.js} +3 -3
- package/server/dist/{file-summary-manager.js → modules/code/runtime/file-summary-manager.js} +3 -3
- package/server/dist/{project-code-discovery.js → modules/code/runtime/project-code-discovery.js} +2 -2
- package/server/dist/modules/conversations/domain/draft-stream.js +91 -0
- package/server/dist/modules/conversations/domain/recovery-context.js +101 -0
- package/server/dist/modules/conversations/index.js +10 -0
- package/server/dist/{chat-manager.js → modules/conversations/runtime/chat-manager.js} +32 -216
- package/server/dist/{context-budget.js → modules/conversations/runtime/context-budget.js} +1 -1
- package/server/dist/{explore-cwd-manager.js → modules/conversations/runtime/explore-cwd-manager.js} +1 -1
- package/server/dist/{explore-smash.js → modules/conversations/runtime/explore-smash.js} +1 -1
- package/server/dist/{explore-stdin-session.js → modules/conversations/runtime/explore-stdin-session.js} +1 -1
- package/server/dist/modules/delivery/adapters/decisions/chains.js +138 -0
- package/server/dist/modules/delivery/adapters/decisions/contracts.js +2 -0
- package/server/dist/modules/delivery/adapters/decisions/create.js +234 -0
- package/server/dist/modules/delivery/adapters/decisions/discard.js +251 -0
- package/server/dist/modules/delivery/adapters/decisions/dispatch.js +107 -0
- package/server/dist/modules/delivery/adapters/decisions/evidence.js +205 -0
- package/server/dist/modules/delivery/adapters/decisions/merge-local.js +338 -0
- package/server/dist/modules/delivery/adapters/decisions/metadata.js +109 -0
- package/server/dist/modules/delivery/adapters/decisions/observations.js +260 -0
- package/server/dist/modules/delivery/adapters/decisions/ownership.js +83 -0
- package/server/dist/modules/delivery/adapters/decisions/publish.js +242 -0
- package/server/dist/modules/delivery/adapters/decisions/recovery.js +425 -0
- package/server/dist/modules/delivery/adapters/decisions/retry.js +112 -0
- package/server/dist/modules/delivery/adapters/decisions/transitions.js +94 -0
- package/server/dist/modules/delivery/domain/decision-policy.js +50 -0
- package/server/dist/modules/delivery/domain/state.js +2 -0
- package/server/dist/modules/delivery/index.js +7 -0
- package/server/dist/{active-pr-continuation.js → modules/delivery/runtime/active-pr-continuation.js} +2 -2
- package/server/dist/{delivery-evidence.js → modules/delivery/runtime/delivery-evidence.js} +3 -3
- package/server/dist/{multi-repo-bases.js → modules/delivery/runtime/multi-repo-bases.js} +1 -1
- package/server/dist/{multi-repo-checkout.js → modules/delivery/runtime/multi-repo-checkout.js} +4 -4
- package/server/dist/{multi-repo-delivery.js → modules/delivery/runtime/multi-repo-delivery.js} +4 -4
- package/server/dist/{multi-repo-execution.js → modules/delivery/runtime/multi-repo-execution.js} +8 -8
- package/server/dist/{pr-body.js → modules/delivery/runtime/pr-body.js} +1 -1
- package/server/dist/{pr-follow-up-scope.js → modules/delivery/runtime/pr-follow-up-scope.js} +2 -2
- package/server/dist/{pr-follow-up.js → modules/delivery/runtime/pr-follow-up.js} +1 -1
- package/server/dist/{pr-naming.js → modules/delivery/runtime/pr-naming.js} +1 -1
- package/server/dist/{pr-publisher.js → modules/delivery/runtime/pr-publisher.js} +2 -2
- package/server/dist/{rail-isolated-launch.js → modules/delivery/runtime/rail-isolated-launch.js} +29 -27
- package/server/dist/{rail-launch-parser.js → modules/delivery/runtime/rail-launch-parser.js} +2 -2
- package/server/dist/{rail-merge-orchestrator.js → modules/delivery/runtime/rail-merge-orchestrator.js} +5 -5
- package/server/dist/modules/delivery/runtime/rail-pr-decision.js +12 -0
- package/server/dist/{rail-pr-delivery.js → modules/delivery/runtime/rail-pr-delivery.js} +1 -1
- package/server/dist/{rail-pr-store.js → modules/delivery/runtime/rail-pr-store.js} +2 -2
- package/server/dist/{rail-pr-ticket-effects.js → modules/delivery/runtime/rail-pr-ticket-effects.js} +3 -3
- package/server/dist/{rail-worktree-release.js → modules/delivery/runtime/rail-worktree-release.js} +4 -4
- package/server/dist/{rails-router.js → modules/delivery/runtime/rails-router.js} +66 -67
- package/server/dist/{review-packet.js → modules/delivery/runtime/review-packet.js} +3 -3
- package/server/dist/modules/execution/adapters/budget-storage.js +21 -0
- package/server/dist/modules/execution/adapters/usage-reader.js +38 -0
- package/server/dist/modules/execution/application/enforce-budget.js +14 -0
- package/server/dist/modules/execution/application/record-job-invocations.js +64 -0
- package/server/dist/modules/execution/application/recover-job-usage.js +200 -0
- package/server/dist/modules/execution/domain/job-accounting.js +2 -0
- package/server/dist/modules/execution/domain/scheduling.js +24 -0
- package/server/dist/modules/execution/domain/usage.js +21 -0
- package/server/dist/modules/execution/index.js +14 -0
- package/server/dist/modules/execution/ports.js +2 -0
- package/server/dist/{accept-ladder.js → modules/execution/runtime/accept-ladder.js} +1 -1
- package/server/dist/{interactive-job-session.js → modules/execution/runtime/interactive-job-session.js} +11 -11
- package/server/dist/{job-phase-breakdown.js → modules/execution/runtime/job-phase-breakdown.js} +1 -1
- package/server/dist/{queue-manager.js → modules/execution/runtime/queue-manager.js} +74 -491
- package/server/dist/{revision-seed.js → modules/execution/runtime/revision-seed.js} +1 -1
- package/server/dist/{spawn-lifecycle.js → modules/execution/runtime/spawn-lifecycle.js} +3 -3
- package/server/dist/{stuck-run-detector.js → modules/execution/runtime/stuck-run-detector.js} +1 -1
- package/server/dist/{loop-command-catalog.js → modules/loops/runtime/loop-command-catalog.js} +1 -1
- package/server/dist/{loop-executors.js → modules/loops/runtime/loop-executors.js} +17 -17
- package/server/dist/{loop-factory.js → modules/loops/runtime/loop-factory.js} +2 -21
- package/server/dist/{loop-graph.js → modules/loops/runtime/loop-graph.js} +25 -0
- package/server/dist/{loop-role-engines.js → modules/loops/runtime/loop-role-engines.js} +4 -4
- package/server/dist/{loop-run-manager.js → modules/loops/runtime/loop-run-manager.js} +101 -26
- package/server/dist/{loop-runs-store.js → modules/loops/runtime/loop-runs-store.js} +1 -1
- package/server/dist/{loop-shell-invocation.js → modules/loops/runtime/loop-shell-invocation.js} +2 -2
- package/server/dist/{loop-step-idle.js → modules/loops/runtime/loop-step-idle.js} +1 -1
- package/server/dist/{loop-templates.js → modules/loops/runtime/loop-templates.js} +24 -29
- package/server/dist/{loops-router.js → modules/loops/runtime/loops-router.js} +2 -2
- package/server/dist/{agent-chat-manager.js → modules/missions/runtime/agent-chat-manager.js} +19 -19
- package/server/dist/{agent-chat-router.js → modules/missions/runtime/agent-chat-router.js} +6 -6
- package/server/dist/{agent-context-resolver.js → modules/missions/runtime/agent-context-resolver.js} +8 -8
- package/server/dist/{agent-failure-briefing.js → modules/missions/runtime/agent-failure-briefing.js} +4 -2
- package/server/dist/{agent-fence-promotion.js → modules/missions/runtime/agent-fence-promotion.js} +1 -1
- package/server/dist/{agent-input-store.js → modules/missions/runtime/agent-input-store.js} +1 -1
- package/server/dist/{agent-operator-prompt.js → modules/missions/runtime/agent-operator-prompt.js} +75 -12
- package/server/dist/{agent-spec-framing.js → modules/missions/runtime/agent-spec-framing.js} +4 -4
- package/server/dist/{agent-steering.js → modules/missions/runtime/agent-steering.js} +1 -1
- package/server/dist/{mission-run-notify.js → modules/missions/runtime/mission-run-notify.js} +5 -2
- package/server/dist/modules/project-settings/adapters/http.js +24 -0
- package/server/dist/modules/project-settings/adapters/sqlite.js +120 -0
- package/server/dist/modules/project-settings/application.js +11 -0
- package/server/dist/modules/project-settings/domain.js +95 -0
- package/server/dist/modules/project-settings/index.js +11 -0
- package/server/dist/modules/project-settings/ports.js +2 -0
- package/server/dist/{contract-refine-runner.js → modules/specs/runtime/contract-refine-runner.js} +12 -12
- package/server/dist/{proposal-manager.js → modules/specs/runtime/proposal-manager.js} +8 -8
- package/server/dist/{smash-runner.js → modules/specs/runtime/smash-runner.js} +26 -58
- package/server/dist/{spec-addenda-core.js → modules/specs/runtime/spec-addenda-core.js} +2 -2
- package/server/dist/{spec-addenda.js → modules/specs/runtime/spec-addenda.js} +3 -3
- package/server/dist/{spec-launcher-manager.js → modules/specs/runtime/spec-launcher-manager.js} +8 -8
- package/server/dist/{spec-models.js → modules/specs/runtime/spec-models.js} +1 -1
- package/server/dist/{ticket-watcher.js → modules/specs/runtime/ticket-watcher.js} +1 -1
- package/server/dist/{background-process-control.js → modules/terminals/runtime/background-process-control.js} +1 -1
- package/server/dist/{background-process-service.js → modules/terminals/runtime/background-process-service.js} +3 -3
- package/server/dist/{background-process-store.js → modules/terminals/runtime/background-process-store.js} +1 -1
- package/server/dist/{background-windows-bootstrap.js → modules/terminals/runtime/background-windows-bootstrap.js} +3 -3
- package/server/dist/{terminal-manager.js → modules/terminals/runtime/terminal-manager.js} +3 -3
- package/server/dist/project-git.js +1 -1
- package/server/dist/project-profile-support.js +32 -0
- package/server/dist/project-registry.js +34 -40
- package/server/dist/project-router-background-processes.js +1 -1
- package/server/dist/project-router-chat.js +6 -6
- package/server/dist/project-router-git.js +1 -1
- package/server/dist/project-router-helpers.js +2 -2
- package/server/dist/project-router-jobs.js +14 -12
- package/server/dist/project-router-loop-runs.js +12 -12
- package/server/dist/project-router-settings.js +15 -75
- package/server/dist/project-router-setup.js +2 -2
- package/server/dist/project-router-spending.js +4 -4
- package/server/dist/project-router-terminals.js +4 -4
- package/server/dist/project-router-tickets.js +14 -14
- package/server/dist/project-router.js +13 -13
- package/server/dist/providers/claude-adapter.js +3 -2
- package/server/dist/providers/codex-adapter.js +3 -3
- package/server/dist/providers/gemini-adapter.js +1 -1
- package/server/dist/providers/telemetry-env.js +55 -0
- package/server/dist/runtime-role-prompts-router.js +2 -2
- package/server/dist/setup-manager.js +2 -48
- package/server/dist/shared/git-branch-name.js +16 -0
- package/server/dist/transient-children.js +3 -3
- package/server/dist/util/distribute-int.js +33 -0
- package/client/dist/assets/ReviewPacketPage-D-UjTef3.js +0 -4
- package/client/dist/assets/index-BAtqmAZR.css +0 -2
- package/client/dist/assets/packet-CjS_478C.js +0 -1
- package/client/dist/assets/packet-D69dKsHN.js +0 -1
- package/client/dist/assets/packet-D9rU6Oxo.js +0 -1
- package/client/dist/assets/packet-DMDFoamg.js +0 -1
- package/client/dist/assets/packet-Dd2RMs1h.js +0 -1
- package/client/dist/assets/packet-DjC9OibC.js +0 -1
- package/client/dist/assets/packet-DwPtJoDz.js +0 -1
- package/client/dist/assets/packet-ceahDg8_.js +0 -1
- package/server/dist/rail-pr-decision.js +0 -2377
- package/server/dist/ticket-broadcast.js +0 -47
- /package/server/dist/{ai-invocations.js → modules/accounting/runtime/ai-invocations.js} +0 -0
- /package/server/dist/{codex-otel-bridge.js → modules/accounting/runtime/codex-otel-bridge.js} +0 -0
- /package/server/dist/{metrics.js → modules/accounting/runtime/metrics.js} +0 -0
- /package/server/dist/{telemetry-export.js → modules/accounting/runtime/telemetry-export.js} +0 -0
- /package/server/dist/{agent-runtime-accounting.js → modules/agent-runtime/runtime/agent-runtime-accounting.js} +0 -0
- /package/server/dist/{agent-runtime-events.js → modules/agent-runtime/runtime/agent-runtime-events.js} +0 -0
- /package/server/dist/{agent-runtime-history.js → modules/agent-runtime/runtime/agent-runtime-history.js} +0 -0
- /package/server/dist/{agent-runtime-metrics.js → modules/agent-runtime/runtime/agent-runtime-metrics.js} +0 -0
- /package/server/dist/{agent-runtime-package.js → modules/agent-runtime/runtime/agent-runtime-package.js} +0 -0
- /package/server/dist/{agent-runtime-repositories.js → modules/agent-runtime/runtime/agent-runtime-repositories.js} +0 -0
- /package/server/dist/{agent-runtime-verification-suggestions.js → modules/agent-runtime/runtime/agent-runtime-verification-suggestions.js} +0 -0
- /package/server/dist/{agent-refine-db.js → modules/agents/runtime/agent-refine-db.js} +0 -0
- /package/server/dist/{browser-capture-types.js → modules/browser/runtime/browser-capture-types.js} +0 -0
- /package/server/dist/{browser-context-pool.js → modules/browser/runtime/browser-context-pool.js} +0 -0
- /package/server/dist/{browser-network.js → modules/browser/runtime/browser-network.js} +0 -0
- /package/server/dist/{browser-viewport.js → modules/browser/runtime/browser-viewport.js} +0 -0
- /package/server/dist/{blueprint-render.js → modules/builder/runtime/blueprint-render.js} +0 -0
- /package/server/dist/{blueprint-spec-fixtures.js → modules/builder/runtime/blueprint-spec-fixtures.js} +0 -0
- /package/server/dist/{blueprint-store.js → modules/builder/runtime/blueprint-store.js} +0 -0
- /package/server/dist/{blueprint-types.js → modules/builder/runtime/blueprint-types.js} +0 -0
- /package/server/dist/{builder-cwd-manager.js → modules/builder/runtime/builder-cwd-manager.js} +0 -0
- /package/server/dist/{milestone-chain-store.js → modules/builder/runtime/milestone-chain-store.js} +0 -0
- /package/server/dist/{changes-reader.js → modules/code/runtime/changes-reader.js} +0 -0
- /package/server/dist/{context-scope.js → modules/conversations/runtime/context-scope.js} +0 -0
- /package/server/dist/{explore-contract-refine.js → modules/conversations/runtime/explore-contract-refine.js} +0 -0
- /package/server/dist/{explore-draft-title.js → modules/conversations/runtime/explore-draft-title.js} +0 -0
- /package/server/dist/{merge-manager.js → modules/delivery/runtime/merge-manager.js} +0 -0
- /package/server/dist/{multi-repo-execution-store.js → modules/delivery/runtime/multi-repo-execution-store.js} +0 -0
- /package/server/dist/{pr-lifecycle.js → modules/delivery/runtime/pr-lifecycle.js} +0 -0
- /package/server/dist/{rail-isolation.js → modules/delivery/runtime/rail-isolation.js} +0 -0
- /package/server/dist/{rail-pr-recovery-git.js → modules/delivery/runtime/rail-pr-recovery-git.js} +0 -0
- /package/server/dist/{rail-worktrees-store.js → modules/delivery/runtime/rail-worktrees-store.js} +0 -0
- /package/server/dist/{rails-store.js → modules/delivery/runtime/rails-store.js} +0 -0
- /package/server/dist/{job-listing.js → modules/execution/runtime/job-listing.js} +0 -0
- /package/server/dist/{job-spawn-idempotency.js → modules/execution/runtime/job-spawn-idempotency.js} +0 -0
- /package/server/dist/{run-duration-stats.js → modules/execution/runtime/run-duration-stats.js} +0 -0
- /package/server/dist/{verification-sentinel.js → modules/execution/runtime/verification-sentinel.js} +0 -0
- /package/server/dist/{loop-constants.js → modules/loops/runtime/loop-constants.js} +0 -0
- /package/server/dist/{loop-decider.js → modules/loops/runtime/loop-decider.js} +0 -0
- /package/server/dist/{loop-effect.js → modules/loops/runtime/loop-effect.js} +0 -0
- /package/server/dist/{loop-preview.js → modules/loops/runtime/loop-preview.js} +0 -0
- /package/server/dist/{loop-templates-ported.js → modules/loops/runtime/loop-templates-ported.js} +0 -0
- /package/server/dist/{loops-store.js → modules/loops/runtime/loops-store.js} +0 -0
- /package/server/dist/{agent-chat-registry.js → modules/missions/runtime/agent-chat-registry.js} +0 -0
- /package/server/dist/{agent-cwd-manager.js → modules/missions/runtime/agent-cwd-manager.js} +0 -0
- /package/server/dist/{agent-tier.js → modules/missions/runtime/agent-tier.js} +0 -0
- /package/server/dist/{spec-contract-prompt.js → modules/specs/runtime/spec-contract-prompt.js} +0 -0
- /package/server/dist/{spec-draft-parser.js → modules/specs/runtime/spec-draft-parser.js} +0 -0
- /package/server/dist/{ticket-store.js → modules/specs/runtime/ticket-store.js} +0 -0
- /package/server/dist/{terminal-marks-store.js → modules/terminals/runtime/terminal-marks-store.js} +0 -0
- /package/server/dist/{terminal-osc-parser.js → modules/terminals/runtime/terminal-osc-parser.js} +0 -0
- /package/server/dist/{terminal-settings.js → modules/terminals/runtime/terminal-settings.js} +0 -0
|
@@ -62,7 +62,7 @@ dismissed ◀──(user)──▶ open/applied (an in-flight one is frozen un
|
|
|
62
62
|
```
|
|
63
63
|
|
|
64
64
|
- **Claim** — `claimSpecAddendaForRun(storePath, ticketIds, runId, { plan? })`
|
|
65
|
-
(`server/spec-addenda.ts`): quick read (most launches carry nothing → no lock,
|
|
65
|
+
(`server/modules/specs/runtime/spec-addenda.ts`): quick read (most launches carry nothing → no lock,
|
|
66
66
|
no rewrite), then `mutateStore` re-plans under the lock and marks
|
|
67
67
|
`open → in_flight` with the run id. Idempotent for the same run (a resume
|
|
68
68
|
re-claims its own); another run's in-flight addenda are never stolen. Returns
|
|
@@ -97,9 +97,33 @@ carry an addendum; a body is evidence, not an order — and demands an
|
|
|
97
97
|
In the loop engine (`loop-run-manager.ts`) the briefing rides the same slot as
|
|
98
98
|
the follow-up: `[manifest, expanded template, followUp.briefing, addenda.briefing]`
|
|
99
99
|
joined and appended to EVERY ai-step prompt, before the iteration history — so
|
|
100
|
-
no phase (prepare, implement, verify, fix, deliver) can miss it.
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
no phase (prepare, implement, verify, fix, deliver) can miss it. Rail launches carrying addenda always select **Quick SDD**. The Revision factory
|
|
101
|
+
loop has been removed from the gallery; its old id resolves to Quick SDD so saved
|
|
102
|
+
launches remain usable. Legacy jobs still receive their existing briefing.
|
|
103
|
+
|
|
104
|
+
### Quick SDD continuation
|
|
105
|
+
|
|
106
|
+
- Open addenda override a stale Implement/Freestyle/custom-loop selection. The
|
|
107
|
+
mission launch card and rail selector show Quick SDD before launch.
|
|
108
|
+
- A settled pending delivery with exactly the same specs can be continued without
|
|
109
|
+
publishing or discarding it first. The existing revision/supersession contract
|
|
110
|
+
preserves branch ownership, repository scope and rollback. Other specs, active
|
|
111
|
+
runs, stale explicit delivery ids and different PR targets remain blocked.
|
|
112
|
+
- Quick SDD seeds a run-specific `spec-addenda-<hash>` target from the frozen
|
|
113
|
+
addendum identities, full briefing and run id. It overrides old spec/follow-up
|
|
114
|
+
change names, stays stable on resume, and changes for a new launch. Every AI
|
|
115
|
+
phase receives the full delta and exact target; Prepare creates missing artifacts
|
|
116
|
+
and Apply explicitly names that change. Existing PR work is compatibility
|
|
117
|
+
context even when Jira calls the spec `in_progress`.
|
|
118
|
+
- Apply cannot advance to CLI validation/archive on `VERIFICATION: PASS` alone.
|
|
119
|
+
Every attached id must report `applied` with nonempty files and tests. Missing,
|
|
120
|
+
partial or blocked reports fail the step and the ordinary failed-run lifecycle
|
|
121
|
+
reopens the addenda. This validates reported coverage, not the truth of arbitrary
|
|
122
|
+
model prose; behavioral tests remain required.
|
|
123
|
+
- Asking for changes in the review packet also runs Quick SDD. Its delivery note
|
|
124
|
+
reaches every AI phase and gets its own run-specific target when no addenda are
|
|
125
|
+
present. The launch response includes the new delivery id so the packet/card
|
|
126
|
+
follows the next generation.
|
|
103
127
|
|
|
104
128
|
### Surfaces
|
|
105
129
|
|
|
@@ -127,8 +151,8 @@ covered without touching any template.
|
|
|
127
151
|
`delete_addendum` (destructive). `add_addendum` records `created_by: 'agent'`
|
|
128
152
|
+ the origin conversation when called from a mission, else `'mcp'`.
|
|
129
153
|
- **Operator prompt** — "iterate on / extend an existing spec = a SPEC ADDENDUM,
|
|
130
|
-
never a description edit": add the addendum, then launch
|
|
131
|
-
|
|
154
|
+
never a description edit": add the addendum, then launch Quick SDD; combine
|
|
155
|
+
with `revisionNote` when explicitly continuing a pending delivery. The agent context resolver lists a `#ref`'d
|
|
132
156
|
spec's addenda with their status and run id.
|
|
133
157
|
- **i18n** — `tickets:addenda.*`, `packet:addenda.*`, `agent:railCard.addenda.*`,
|
|
134
158
|
`specs:badges.addendaTitle_*` ×8.
|
|
@@ -140,8 +164,9 @@ classifies a `pr_failed` row that never ran (no PR, SHA, branches, worktrees,
|
|
|
140
164
|
not a continuation). The launch route closes such a row in place
|
|
141
165
|
(`pr_failed → discarded`, `rail.pr_state` re-broadcast) and proceeds — pressing
|
|
142
166
|
Launch again IS the decision. A revision naming it gets 409
|
|
143
|
-
`invalid_revision_target` with a precise detail (nothing to revise).
|
|
144
|
-
undecided delivery still answers `pr_decision_pending
|
|
167
|
+
`invalid_revision_target` with a precise detail (nothing to revise). An unrelated
|
|
168
|
+
undecided delivery still answers `pr_decision_pending`; same-spec addenda can
|
|
169
|
+
continue the existing generation.
|
|
145
170
|
|
|
146
171
|
## Guarantees
|
|
147
172
|
|
|
@@ -149,7 +174,7 @@ undecided delivery still answers `pr_decision_pending`.
|
|
|
149
174
|
or Jira. `PATCH /tickets/:id` ignores an `addenda` field.
|
|
150
175
|
- A running generation is briefed with the frozen hash it was launched with; an
|
|
151
176
|
edit after launch is refused while in flight and never changes the snapshot.
|
|
152
|
-
- Ordinary launches without addenda
|
|
177
|
+
- Ordinary launches without addenda or delivery change requests keep their loop (`spec_addenda` NULL,
|
|
153
178
|
no `LoopRunRequest.addenda`, no lock taken, no extra prompt text).
|
|
154
179
|
- Both settle chokepoints and the discard reopen are keyed on the run id /
|
|
155
180
|
the delivery snapshot — a replay, a newer owner or a concurrent launch can
|
|
@@ -164,3 +189,34 @@ undecided delivery still answers `pr_decision_pending`.
|
|
|
164
189
|
user wants it kept, an explicit addendum).
|
|
165
190
|
- Jira mirroring of addenda (a comment per addendum) — deliberately not done;
|
|
166
191
|
addenda are local guidance for the run, not tracker content.
|
|
192
|
+
|
|
193
|
+
## Quick SDD scope and efficiency
|
|
194
|
+
|
|
195
|
+
Quick SDD can implement a complete spec without addenda, a PR, or a predefined
|
|
196
|
+
OpenSpec name. It is freely selectable instead of Implement. Preparation receives
|
|
197
|
+
the full frozen spec and creates a name when absent; Apply implements its artifacts.
|
|
198
|
+
For delivered work, attached addenda/change requests define the delta instead.
|
|
199
|
+
|
|
200
|
+
The normal path uses two AI phases: artifact preparation, then implementation with
|
|
201
|
+
relevant behavioral tests and repository-required checks. Deterministic strict CLI
|
|
202
|
+
validation runs before implementation and again before archive. Preparation must
|
|
203
|
+
not implement code or run the repository suite.
|
|
204
|
+
|
|
205
|
+
Each failing phase has at most one recovery within the same run: failed acceptance
|
|
206
|
+
checks repeat Apply with bounded diagnostics; failed artifact validation invokes
|
|
207
|
+
an artifact-only repair and returns directly to that validation. Archive can retry
|
|
208
|
+
once without an AI call. Provider failures, missing targets, cancellation and cost
|
|
209
|
+
limits do not trigger paid recovery. Exhausted failures stop before delivery.
|
|
210
|
+
This does not add durable phase resume after process restart.
|
|
211
|
+
|
|
212
|
+
Continuation context retains request, branch, diff and prior evidence while
|
|
213
|
+
avoiding duplicate spec/addendum bodies in the revision seed. Frozen addenda are
|
|
214
|
+
still supplied to each AI phase, including recovery. Diagnostics are capped, but
|
|
215
|
+
the admitted addendum briefing is not abbreviated.
|
|
216
|
+
|
|
217
|
+
Persisted `loop_phase_recovery` events identify recovery routing.
|
|
218
|
+
`loop_phase_metrics` records prompt/output characters and the final AI attempt's
|
|
219
|
+
available usage, cache counts, cost and duration; missing values remain null and
|
|
220
|
+
estimated costs remain identified. These events are diagnostic, not an additional
|
|
221
|
+
billing source; invocation accounting remains authoritative across all attempts.
|
|
222
|
+
Character counts are not token estimates, and no percentage saving is assumed.
|
|
@@ -67,12 +67,12 @@ Key structural moves shown above:
|
|
|
67
67
|
| File | Responsibility | Key anchors |
|
|
68
68
|
|---|---|---|
|
|
69
69
|
| `client/src/context/UiModeContext.tsx` | App-global `uiMode: 'kanban'\|'agent'` + `setUiMode`/`toggle`, localStorage-persisted; `useUiMode()` with NOOP fallback for tests. | Insert provider at App.tsx:440-441; consumed by TitleBar/ArcSidebar/AgentChatContext/DesktopApp. |
|
|
70
|
-
| `client/src/components/
|
|
71
|
-
| `client/src/components/
|
|
72
|
-
| `client/src/components/
|
|
73
|
-
| `client/src/components/
|
|
70
|
+
| `client/src/features/missions/components/AgentConversationView.tsx` | Shared inner UI (banners + sticky-scroll message list + streaming + activity chip + composer) consumed by BOTH the floating panel and the inline surface; `variant: 'floating'\|'inline'`. | Extracted from AgentChatPanel.tsx:158-269. |
|
|
71
|
+
| `client/src/features/missions/components/AgentModeSurface.tsx` | Inline center surface: EMPTY (`active===null` → centered composer card) vs ACTIVE (`<AgentConversationView variant='inline'/>` + optional Files/code split). Does NOT call `ensureActive` on mount. | Mounted at App.tsx center swap (:252-296). |
|
|
72
|
+
| `client/src/features/missions/components/AgentWorkspaceSidebar.tsx` | Right "On workspace" toolbar in Agent Mode: Browser / Terminal / Files buttons, each disabled when `activeProjectId` is null. | Mounted at App.tsx:302 (agent branch). |
|
|
73
|
+
| `client/src/features/missions/components/AgentModeCodePane.tsx` (optional wrapper) | Resizable split host that mounts `<CodePage embedded/>` as a secondary pane beside the thread; own `<Suspense>` boundary. | Wraps CodePage.tsx (lazy). |
|
|
74
74
|
| `server/agent-attachment-routes` (in `agent-chat-router.ts`) | App-global attachment endpoints: `POST/GET/DELETE /api/agent/conversations/:id/attachments` (multer single 'file', 25MB, mime filter). | Mirror project-router-tickets.ts:1839-1917. |
|
|
75
|
-
| `server/agent-store` migration | Persist attachment id list per agent turn (new nullable `attachments` JSON column on `agent_messages`, or sibling `agent_message_attachments` table). | agent-store.ts:24-30,111-128. |
|
|
75
|
+
| `server/modules/agents/runtime/agent-store` migration | Persist attachment id list per agent turn (new nullable `attachments` JSON column on `agent_messages`, or sibling `agent_message_attachments` table). | agent-store.ts:24-30,111-128. |
|
|
76
76
|
|
|
77
77
|
### EXISTING files to modify
|
|
78
78
|
|
|
@@ -81,12 +81,12 @@ Key structural moves shown above:
|
|
|
81
81
|
| `client/src/App.tsx` | Insert `UiModeProvider`; branch center on `uiMode`; branch right sidebar; hoist `BottomPanel`+`StatusBar` into main-area column; source `viewportHeight`/`panelState`/`connectionStatus` at DesktopApp scope. | 230-330 (shell), 440-461 (providers). |
|
|
82
82
|
| `client/src/components/ProjectLayout.tsx` | REMOVE the `BottomPanel`/`StatusBar` render (hoisted); keep `<Outlet/>`, ChatContext; gate chevronSlot on `uiMode==='kanban'`; relocate the `viewportHeight` ResizeObserver + `panelState` up. | 66-95, 124-134. |
|
|
83
83
|
| `client/src/components/TitleBar.tsx` | Hide `SearchPill` when `uiMode==='agent'` at both mount sites. | 66-129 (pill), 175 & 215 (mounts). |
|
|
84
|
-
| `client/src/context/AgentChatContext.tsx` | Gate `AgentChatPanel`(:293) + `AgentBubble`(:296) off when `uiMode==='agent'`; expose a public `refreshConversations`; extend `newConversation(projectId?)`. | 39-70, 246-253, 290-298. |
|
|
85
|
-
| `client/src/components/
|
|
84
|
+
| `client/src/features/missions/context/AgentChatContext.tsx` | Gate `AgentChatPanel`(:293) + `AgentBubble`(:296) off when `uiMode==='agent'`; expose a public `refreshConversations`; extend `newConversation(projectId?)`. | 39-70, 246-253, 290-298. |
|
|
85
|
+
| `client/src/features/missions/components/AgentChatPanel.tsx` | Reduce to thin chrome wrapper rendering `<AgentConversationView variant='floating'/>`; keep `useMovableResizableModal`/maximize/header/`ResizeGrips`. | 30-119 (chrome), 121-284 (body). |
|
|
86
86
|
| `client/src/components/ArcSidebar.tsx` | Add `useAgentChat()` + `useUiMode()`; add [Switch mode]/[New agent]/[Search] buttons above Loops; make `ProjectItem` expandable; render conversation children + Home group; branch `handleSelectProject` by mode. | 1-9, 19-106, 135-166, 206-259. |
|
|
87
|
-
| `client/src/pages/CodePage.tsx` | Add controlled/`embedded` props; neutralize `navigate({pathname:'/code'})` calls; gate URL→state effects when embedded. | 51-96, 129-160. |
|
|
88
|
-
| `server/agent-chat-router.ts` | Parse `attachments:{ids}` on `POST /:id/send`; add attachment endpoints. | 144-163. |
|
|
89
|
-
| `server/agent-chat-manager.ts` | Resolve attachment textBlocks via `attachmentManager.getClaudeArgs`, fold into `prompt` before pinned-project prefix; append note for codex/gemini into prompt. | 72-235 (fold near :97-102, chokepoint :151). |
|
|
87
|
+
| `client/src/features/code/pages/CodePage.tsx` | Add controlled/`embedded` props; neutralize `navigate({pathname:'/code'})` calls; gate URL→state effects when embedded. | 51-96, 129-160. |
|
|
88
|
+
| `server/modules/missions/runtime/agent-chat-router.ts` | Parse `attachments:{ids}` on `POST /:id/send`; add attachment endpoints. | 144-163. |
|
|
89
|
+
| `server/modules/missions/runtime/agent-chat-manager.ts` | Resolve attachment textBlocks via `attachmentManager.getClaudeArgs`, fold into `prompt` before pinned-project prefix; append note for codex/gemini into prompt. | 72-235 (fold near :97-102, chokepoint :151). |
|
|
90
90
|
| `server/attachment-manager.ts` | Add app-global agent storage root (`~/.specrails/agent/<conversationId>/attachments/`) or overload; keep traversal guards. | 89-133, 287-348. |
|
|
91
91
|
|
|
92
92
|
---
|
|
@@ -155,7 +155,7 @@ All three are gated on `!!activeProjectId` (disabled tooltip otherwise), matchin
|
|
|
155
155
|
|
|
156
156
|
The Explore browser-capture stack is fully project-scoped and reusable verbatim.
|
|
157
157
|
|
|
158
|
-
- **Reuse:** `BrowserCaptureModal` (props `{open,onClose,projectId,pendingSpecId,onCaptured,confirmLabel}`), `CapturedDomPanel` (props `{dom,onRemove?}`), the `client/src/lib/browser-capture.ts` REST helpers (`createBrowserSession`, `navigateBrowser`, `captureBrowserRegion`, `captureBrowserBreakpoints`, `uploadCaptureImage`), `isBrowserCaptureEnabled()` (client flag `VITE_FEATURE_BROWSER_CAPTURE`), and server `requireBrowserCaptureEnabled`/`SPECRAILS_BROWSER_CAPTURE` (feature-flags.ts:9-13). Server session endpoints at project-router-terminals.ts:200-410.
|
|
158
|
+
- **Reuse:** `BrowserCaptureModal` (props `{open,onClose,projectId,pendingSpecId,onCaptured,confirmLabel}`), `CapturedDomPanel` (props `{dom,onRemove?}`), the `client/src/features/browser/lib/browser-capture.ts` REST helpers (`createBrowserSession`, `navigateBrowser`, `captureBrowserRegion`, `captureBrowserBreakpoints`, `uploadCaptureImage`), `isBrowserCaptureEnabled()` (client flag `VITE_FEATURE_BROWSER_CAPTURE`), and server `requireBrowserCaptureEnabled`/`SPECRAILS_BROWSER_CAPTURE` (feature-flags.ts:9-13). Server session endpoints at project-router-terminals.ts:200-410.
|
|
159
159
|
- **New work:** wire the sidebar Browser button to open `BrowserCaptureModal` with the current `activeProjectId` and the agent conversation's pending id; route `onCaptured` into the composer capture chips (see §7). Requires an active project (browser sessions are project-scoped).
|
|
160
160
|
|
|
161
161
|
---
|
package/docs/tracking-cost.md
CHANGED
|
@@ -34,7 +34,7 @@ requests create no invocation row; AI auto-title uses a deterministic fallback.
|
|
|
34
34
|
Whether a cost figure is exact depends on the provider's CLI, not on which provider you picked:
|
|
35
35
|
|
|
36
36
|
- **Claude cost is provider-billed and authoritative** — the figure comes straight from the CLI's own usage report.
|
|
37
|
-
- **Codex and Gemini do not report cost natively**, so the app **estimates** their cost from a local rate-card (`server/pricing.ts`) using the captured token counts. Estimated rows are flagged: they render with a `~` tilde in the raw table (hover for the tooltip — *"Estimated from local pricing table — this provider does not report cost natively"*) and feed an "includes ~$X estimated" footnote in the Hero.
|
|
37
|
+
- **Codex and Gemini do not report cost natively**, so the app **estimates** their cost from a local rate-card (`server/modules/accounting/runtime/pricing.ts`) using the captured token counts. Estimated rows are flagged: they render with a `~` tilde in the raw table (hover for the tooltip — *"Estimated from local pricing table — this provider does not report cost natively"*) and feed an "includes ~$X estimated" footnote in the Hero.
|
|
38
38
|
- **Kimi reports neither token counts nor a native USD-cost envelope** in its
|
|
39
39
|
stream. Specrails does not invent an estimate: cost and token cells stay
|
|
40
40
|
unavailable.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specrails-desktop",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.56.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -47,7 +47,10 @@
|
|
|
47
47
|
"test:coverage": "vitest run --coverage --maxWorkers=1 --reporter=dot --bail=1",
|
|
48
48
|
"test:client": "cd client && npx vitest run",
|
|
49
49
|
"test:all": "vitest run && cd client && npx vitest run",
|
|
50
|
-
"ci": "npm run typecheck && npm run check-core-compat && npm run test:scripts && npm run test:coverage && npm run test:coverage --prefix client && npm run build && npm run check:package"
|
|
50
|
+
"ci": "npm run typecheck && npm run check-core-compat && npm run test:scripts && npm run test:coverage && npm run test:coverage --prefix client && npm run build && npm run check:package",
|
|
51
|
+
"audit:source": "node scripts/audit-source.mjs",
|
|
52
|
+
"docs:source-map": "node scripts/generate-source-map.mjs",
|
|
53
|
+
"audit:architecture": "node scripts/audit-server-modules.mjs --check && node scripts/audit-client-features.mjs --check"
|
|
51
54
|
},
|
|
52
55
|
"dependencies": {
|
|
53
56
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
@@ -17,7 +17,7 @@ const os_1 = __importDefault(require("os"));
|
|
|
17
17
|
const crypto_1 = require("crypto");
|
|
18
18
|
const path_resolver_1 = require("./path-resolver");
|
|
19
19
|
const win_spawn_1 = require("./util/win-spawn");
|
|
20
|
-
const agent_tier_1 = require("./agent-tier");
|
|
20
|
+
const agent_tier_1 = require("./modules/missions/runtime/agent-tier");
|
|
21
21
|
const providers_1 = require("./providers");
|
|
22
22
|
const external_mcp_1 = require("./external-mcp");
|
|
23
23
|
const AGENT_CONVERSATION_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
|
|
@@ -10,7 +10,7 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
10
10
|
const os_1 = __importDefault(require("os"));
|
|
11
11
|
const path_1 = __importDefault(require("path"));
|
|
12
12
|
const ids_1 = require("./ids");
|
|
13
|
-
const ticket_store_1 = require("./ticket-store");
|
|
13
|
+
const ticket_store_1 = require("./modules/specs/runtime/ticket-store");
|
|
14
14
|
exports.SUPPORTED_MIME_TYPES = new Set([
|
|
15
15
|
'image/jpeg',
|
|
16
16
|
'image/png',
|
|
@@ -11,8 +11,8 @@ exports.isInBuildDir = isInBuildDir;
|
|
|
11
11
|
* Recursively watching such a tree was the confirmed root cause of a file-descriptor
|
|
12
12
|
* leak (~10k fds) that, under fd pressure, made node-pty's forkpty fail to give the
|
|
13
13
|
* child shell a controlling tty — the shell then read EOF and exited instantly,
|
|
14
|
-
* producing dead/hung terminals. See server/file-summary-manager.ts (the watcher)
|
|
15
|
-
* and server/code-explorer-router.ts (the on-demand tree walk).
|
|
14
|
+
* producing dead/hung terminals. See server/modules/code/runtime/file-summary-manager.ts (the watcher)
|
|
15
|
+
* and server/modules/code/runtime/code-explorer-router.ts (the on-demand tree walk).
|
|
16
16
|
*
|
|
17
17
|
* Dot-directories (`.git`, `.next`, `.turbo`, `.venv`, …) are handled separately
|
|
18
18
|
* by a `startsWith('.')` segment check so we don't have to enumerate every one.
|
package/server/dist/config.js
CHANGED
|
@@ -169,9 +169,6 @@ function parseFrontmatter(content) {
|
|
|
169
169
|
}
|
|
170
170
|
return result;
|
|
171
171
|
}
|
|
172
|
-
function slugify(name) {
|
|
173
|
-
return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
174
|
-
}
|
|
175
172
|
function scanCommands(commandsDir) {
|
|
176
173
|
if (!fs_1.default.existsSync(commandsDir))
|
|
177
174
|
return [];
|
|
@@ -7,7 +7,7 @@ const node_fs_1 = require("node:fs");
|
|
|
7
7
|
const node_path_1 = require("node:path");
|
|
8
8
|
const node_util_1 = require("node:util");
|
|
9
9
|
const core_node_runtime_1 = require("./core-node-runtime");
|
|
10
|
-
const agent_runtime_loader_1 = require("./agent-runtime-loader");
|
|
10
|
+
const agent_runtime_loader_1 = require("./modules/agent-runtime/runtime/agent-runtime-loader");
|
|
11
11
|
const obj = (value) => value !== null && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
12
12
|
const strings = (value) => Array.isArray(value) && value.every(item => typeof item === 'string');
|
|
13
13
|
const count = (value) => typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : null;
|
|
@@ -8,7 +8,7 @@ const node_fs_1 = require("node:fs");
|
|
|
8
8
|
const node_path_1 = require("node:path");
|
|
9
9
|
const core_node_runtime_1 = require("./core-node-runtime");
|
|
10
10
|
const core_update_state_1 = require("./core-update-state");
|
|
11
|
-
const agent_runtime_loader_1 = require("./agent-runtime-loader");
|
|
11
|
+
const agent_runtime_loader_1 = require("./modules/agent-runtime/runtime/agent-runtime-loader");
|
|
12
12
|
const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
13
13
|
// Resolve existing ancestors too: the tickets file may not exist yet, and macOS
|
|
14
14
|
// exposes temporary directories through both /var and /private/var.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getProjectActivity = getProjectActivity;
|
|
4
|
+
function getProjectActivity(db, opts) {
|
|
5
|
+
const limit = Math.min(opts.limit, 100);
|
|
6
|
+
const conditions = [];
|
|
7
|
+
const params = [];
|
|
8
|
+
if (opts.before) {
|
|
9
|
+
conditions.push('started_at < ?');
|
|
10
|
+
params.push(opts.before);
|
|
11
|
+
}
|
|
12
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
13
|
+
const jobs = db
|
|
14
|
+
.prepare(`SELECT * FROM jobs ${where} ORDER BY started_at DESC LIMIT ?`)
|
|
15
|
+
.all(...params, limit);
|
|
16
|
+
return jobs.map((j) => {
|
|
17
|
+
const isTerminal = j.status === 'completed' || j.status === 'failed' || j.status === 'canceled' || j.status === 'zombie_terminated';
|
|
18
|
+
const type = j.status === 'completed' ? 'job_completed'
|
|
19
|
+
: j.status === 'failed' ? 'job_failed'
|
|
20
|
+
: (j.status === 'canceled' || j.status === 'zombie_terminated') ? 'job_canceled'
|
|
21
|
+
: 'job_started';
|
|
22
|
+
const timestamp = isTerminal && j.finished_at ? j.finished_at : j.started_at;
|
|
23
|
+
const shortCmd = j.command.length > 60 ? j.command.slice(0, 57) + '...' : j.command;
|
|
24
|
+
const summary = type === 'job_started' ? `Job started: ${shortCmd}`
|
|
25
|
+
: type === 'job_completed' ? `Job completed: ${shortCmd}`
|
|
26
|
+
: type === 'job_failed' ? `Job failed: ${shortCmd}`
|
|
27
|
+
: j.status === 'zombie_terminated' ? `Job auto-terminated (zombie): ${shortCmd}`
|
|
28
|
+
: `Job canceled: ${shortCmd}`;
|
|
29
|
+
return {
|
|
30
|
+
id: j.id,
|
|
31
|
+
type,
|
|
32
|
+
jobId: j.id,
|
|
33
|
+
jobCommand: j.command,
|
|
34
|
+
timestamp,
|
|
35
|
+
summary,
|
|
36
|
+
costUsd: isTerminal ? (j.total_cost_usd ?? null) : null,
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.initDb = initDb;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
10
|
+
const secure_fs_1 = require("../util/secure-fs");
|
|
11
|
+
const migrations_1 = require("./migrations");
|
|
12
|
+
function initDb(dbPath) {
|
|
13
|
+
if (dbPath !== ':memory:') {
|
|
14
|
+
const dir = path_1.default.dirname(dbPath);
|
|
15
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
16
|
+
(0, secure_fs_1.secureDir)(dir); // H-13: owner-only data dir
|
|
17
|
+
}
|
|
18
|
+
const db = new better_sqlite3_1.default(dbPath);
|
|
19
|
+
try {
|
|
20
|
+
db.pragma('journal_mode = WAL');
|
|
21
|
+
db.pragma('foreign_keys = ON');
|
|
22
|
+
// Under load, QueueManager / ChatManager / FileSummaryManager write the same
|
|
23
|
+
// per-project DB concurrently with /analytics reads. Wait up to 5s on a lock
|
|
24
|
+
// instead of throwing SQLITE_BUSY, and cap the WAL so a long checkpoint can't
|
|
25
|
+
// grow it without bound.
|
|
26
|
+
db.pragma('busy_timeout = 5000');
|
|
27
|
+
db.pragma('journal_size_limit = 10000000'); // ~10 MB
|
|
28
|
+
(0, migrations_1.applyMigrations)(db);
|
|
29
|
+
// H-13: restrict the db + its WAL sidecars to 0600 (jobs.sqlite holds chat
|
|
30
|
+
// transcripts and verbatim terminal command history). After migrations the
|
|
31
|
+
// WAL/SHM files exist, so this covers them too.
|
|
32
|
+
(0, secure_fs_1.secureDbFile)(dbPath);
|
|
33
|
+
// Orphan sweep: cancel any in-flight proposals from a previous server session
|
|
34
|
+
db.prepare("UPDATE proposals SET status = 'cancelled', updated_at = ? WHERE status IN ('exploring', 'refining')").run(new Date().toISOString());
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
db.close();
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
40
|
+
return db;
|
|
41
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createConversation = createConversation;
|
|
4
|
+
exports.listConversations = listConversations;
|
|
5
|
+
exports.getConversation = getConversation;
|
|
6
|
+
exports.deleteConversation = deleteConversation;
|
|
7
|
+
exports.updateConversation = updateConversation;
|
|
8
|
+
exports.addMessage = addMessage;
|
|
9
|
+
exports.getMessages = getMessages;
|
|
10
|
+
// ─── Chat DB functions ────────────────────────────────────────────────────────
|
|
11
|
+
function createConversation(db, opts) {
|
|
12
|
+
const scopeJson = opts.contextScope != null ? JSON.stringify(opts.contextScope) : null;
|
|
13
|
+
db.prepare('INSERT INTO chat_conversations (id, model, kind, context_scope, provider) VALUES (?, ?, ?, ?, ?)').run(opts.id, opts.model, opts.kind ?? 'sidebar', scopeJson, opts.provider ?? null);
|
|
14
|
+
}
|
|
15
|
+
function listConversations(db) {
|
|
16
|
+
return db.prepare('SELECT * FROM chat_conversations ORDER BY updated_at DESC').all();
|
|
17
|
+
}
|
|
18
|
+
function getConversation(db, id) {
|
|
19
|
+
return db.prepare('SELECT * FROM chat_conversations WHERE id = ?').get(id);
|
|
20
|
+
}
|
|
21
|
+
function deleteConversation(db, id) {
|
|
22
|
+
db.prepare('DELETE FROM chat_conversations WHERE id = ?').run(id);
|
|
23
|
+
}
|
|
24
|
+
function updateConversation(db, id, patch) {
|
|
25
|
+
const sets = ['updated_at = ?'];
|
|
26
|
+
const params = [new Date().toISOString()];
|
|
27
|
+
if (patch.title !== undefined) {
|
|
28
|
+
sets.push('title = ?');
|
|
29
|
+
params.push(patch.title);
|
|
30
|
+
}
|
|
31
|
+
if (patch.session_id !== undefined) {
|
|
32
|
+
sets.push('session_id = ?');
|
|
33
|
+
params.push(patch.session_id);
|
|
34
|
+
}
|
|
35
|
+
if (patch.model !== undefined) {
|
|
36
|
+
sets.push('model = ?');
|
|
37
|
+
params.push(patch.model);
|
|
38
|
+
}
|
|
39
|
+
params.push(id);
|
|
40
|
+
db.prepare(`UPDATE chat_conversations SET ${sets.join(', ')} WHERE id = ?`).run(...params);
|
|
41
|
+
}
|
|
42
|
+
function addMessage(db, msg) {
|
|
43
|
+
const result = db.prepare('INSERT INTO chat_messages (conversation_id, role, content) VALUES (?, ?, ?)').run(msg.conversation_id, msg.role, msg.content);
|
|
44
|
+
return db.prepare('SELECT * FROM chat_messages WHERE id = ?').get(Number(result.lastInsertRowid));
|
|
45
|
+
}
|
|
46
|
+
function getMessages(db, conversationId) {
|
|
47
|
+
return db.prepare('SELECT * FROM chat_messages WHERE conversation_id = ? ORDER BY id ASC').all(conversationId);
|
|
48
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.JobRecoveryPendingError = void 0;
|
|
4
|
+
exports.createJob = createJob;
|
|
5
|
+
exports.upsertQueuedJob = upsertQueuedJob;
|
|
6
|
+
exports.deleteQueuedJob = deleteQueuedJob;
|
|
7
|
+
exports.markJobInteractive = markJobInteractive;
|
|
8
|
+
exports.accumulateInteractiveTurn = accumulateInteractiveTurn;
|
|
9
|
+
exports.finalizeInteractiveJob = finalizeInteractiveJob;
|
|
10
|
+
exports.finishJob = finishJob;
|
|
11
|
+
exports.appendEvent = appendEvent;
|
|
12
|
+
exports.upsertPhase = upsertPhase;
|
|
13
|
+
exports.listJobs = listJobs;
|
|
14
|
+
exports.getJob = getJob;
|
|
15
|
+
exports.getJobEvents = getJobEvents;
|
|
16
|
+
exports.deleteJob = deleteJob;
|
|
17
|
+
exports.purgeJobs = purgeJobs;
|
|
18
|
+
exports.skipJob = skipJob;
|
|
19
|
+
exports.getPipelineJobs = getPipelineJobs;
|
|
20
|
+
function createJob(db, job) {
|
|
21
|
+
// Promotion is atomic: after a crash the job is either still replayable in
|
|
22
|
+
// queued_jobs or is a running jobs row, never absent from both. INSERT OR
|
|
23
|
+
// IGNORE preserves idempotency for legacy/restored jobs that already exist.
|
|
24
|
+
const promote = db.transaction(() => {
|
|
25
|
+
db.prepare('INSERT OR IGNORE INTO jobs (id, command, started_at, status, provider, owner, priority, depends_on_job_id, pipeline_id, interactive, causal_ownership) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)').run(job.id, job.command, job.started_at, 'running', job.provider ?? null, job.owner ?? 'queue', job.priority ?? 'normal', job.depends_on_job_id ?? null, job.pipeline_id ?? null, job.interactive ? 1 : 0, job.causal_ownership ? 1 : 0);
|
|
26
|
+
db.prepare('UPDATE jobs SET status = ?, started_at = ?, provider = ?, owner = ?, interactive = ?, causal_ownership = ? WHERE id = ?').run('running', job.started_at, job.provider ?? null, job.owner ?? 'queue', job.interactive ? 1 : 0, job.causal_ownership ? 1 : 0, job.id);
|
|
27
|
+
db.prepare('DELETE FROM queued_jobs WHERE id = ?').run(job.id);
|
|
28
|
+
});
|
|
29
|
+
promote();
|
|
30
|
+
}
|
|
31
|
+
/** Idempotently persist a job that has been admitted but has not started. */
|
|
32
|
+
function upsertQueuedJob(db, job) {
|
|
33
|
+
db.prepare(`
|
|
34
|
+
INSERT INTO queued_jobs (
|
|
35
|
+
id, command, queue_position, priority, depends_on_job_id, pipeline_id,
|
|
36
|
+
provider, model, profile_name, profile_selection_set, interactive,
|
|
37
|
+
causal_ownership
|
|
38
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
39
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
40
|
+
command = excluded.command,
|
|
41
|
+
queue_position = excluded.queue_position,
|
|
42
|
+
priority = excluded.priority,
|
|
43
|
+
depends_on_job_id = excluded.depends_on_job_id,
|
|
44
|
+
pipeline_id = excluded.pipeline_id,
|
|
45
|
+
provider = excluded.provider,
|
|
46
|
+
model = excluded.model,
|
|
47
|
+
profile_name = excluded.profile_name,
|
|
48
|
+
profile_selection_set = excluded.profile_selection_set,
|
|
49
|
+
interactive = excluded.interactive,
|
|
50
|
+
causal_ownership = excluded.causal_ownership
|
|
51
|
+
`).run(job.id, job.command, job.queue_position, job.priority, job.depends_on_job_id ?? null, job.pipeline_id ?? null, job.provider ?? null, job.model ?? null, job.profile_name ?? null, job.profile_selection_set ? 1 : 0, job.interactive == null ? null : (job.interactive ? 1 : 0), job.causal_ownership ? 1 : 0);
|
|
52
|
+
}
|
|
53
|
+
/** Remove a queued admission after cancellation/skip. Idempotent.
|
|
54
|
+
*
|
|
55
|
+
* Migration 43 deliberately keeps a read fallback for pre-migration builds
|
|
56
|
+
* that represented queued work in `jobs`. Remove that legacy representation
|
|
57
|
+
* too, but only while it is still queued: promotion changes the status to
|
|
58
|
+
* running before calling this helper, and terminal history must never be
|
|
59
|
+
* deleted here. */
|
|
60
|
+
function deleteQueuedJob(db, jobId) {
|
|
61
|
+
db.prepare('DELETE FROM queued_jobs WHERE id = ?').run(jobId);
|
|
62
|
+
db.prepare("DELETE FROM jobs WHERE id = ? AND status = 'queued'").run(jobId);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Flip a job row's `interactive` flag AFTER creation. Used by the loop engine
|
|
66
|
+
* when an ai-step upgrades the run's backing job to a resident interactive
|
|
67
|
+
* session — the row was created at run start (before the step's provider
|
|
68
|
+
* capability/kill-switch gate is consulted), so the flag lands lazily at the
|
|
69
|
+
* first interactive step spawn. Idempotent.
|
|
70
|
+
*/
|
|
71
|
+
function markJobInteractive(db, jobId) {
|
|
72
|
+
db.prepare('UPDATE jobs SET interactive = 1 WHERE id = ?').run(jobId);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Add one completed interactive turn's REAL usage into the job row. Token/cost/
|
|
76
|
+
* turn columns accumulate (COALESCE so the first turn starts from a clean base);
|
|
77
|
+
* model + session_id are stamped from the first turn that reports them. The job
|
|
78
|
+
* stays 'running' — only finalizeInteractiveJob flips the terminal status. This
|
|
79
|
+
* keeps the live Job Detail totals honest (sum of completed turns, never an
|
|
80
|
+
* estimate) between turns.
|
|
81
|
+
*/
|
|
82
|
+
function accumulateInteractiveTurn(db, jobId, turn) {
|
|
83
|
+
db.prepare(`
|
|
84
|
+
UPDATE jobs SET
|
|
85
|
+
tokens_in = COALESCE(tokens_in, 0) + ?,
|
|
86
|
+
tokens_out = COALESCE(tokens_out, 0) + ?,
|
|
87
|
+
tokens_cache_read = COALESCE(tokens_cache_read, 0) + ?,
|
|
88
|
+
tokens_cache_create = COALESCE(tokens_cache_create, 0) + ?,
|
|
89
|
+
total_cost_usd = COALESCE(total_cost_usd, 0) + ?,
|
|
90
|
+
num_turns = COALESCE(num_turns, 0) + ?,
|
|
91
|
+
total_cost_usd_estimated = CASE WHEN ? = 1 THEN 1 ELSE total_cost_usd_estimated END,
|
|
92
|
+
model = COALESCE(model, ?),
|
|
93
|
+
session_id = COALESCE(?, session_id)
|
|
94
|
+
WHERE id = ?
|
|
95
|
+
`).run(turn.tokens_in, turn.tokens_out, turn.tokens_cache_read, turn.tokens_cache_create, turn.total_cost_usd, turn.num_turns, turn.estimated ? 1 : 0, turn.model ?? null, turn.session_id ?? null, jobId);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Flip an interactive job to its terminal status (completed on finalize, failed
|
|
99
|
+
* on crash) and stamp finished_at. Token/cost/turn columns are left untouched —
|
|
100
|
+
* they were already accumulated turn-by-turn via accumulateInteractiveTurn.
|
|
101
|
+
*/
|
|
102
|
+
function finalizeInteractiveJob(db, jobId, status) {
|
|
103
|
+
db.prepare('UPDATE jobs SET status = ?, finished_at = ? WHERE id = ?').run(status, new Date().toISOString(), jobId);
|
|
104
|
+
}
|
|
105
|
+
function finishJob(db, jobId, result) {
|
|
106
|
+
db.prepare(`
|
|
107
|
+
UPDATE jobs SET
|
|
108
|
+
status = ?,
|
|
109
|
+
exit_code = ?,
|
|
110
|
+
finished_at = ?,
|
|
111
|
+
tokens_in = ?,
|
|
112
|
+
tokens_out = ?,
|
|
113
|
+
tokens_cache_read = ?,
|
|
114
|
+
tokens_cache_create = ?,
|
|
115
|
+
total_cost_usd = ?,
|
|
116
|
+
total_cost_usd_estimated = ?,
|
|
117
|
+
num_turns = ?,
|
|
118
|
+
model = ?,
|
|
119
|
+
duration_ms = ?,
|
|
120
|
+
duration_api_ms = ?,
|
|
121
|
+
session_id = ?
|
|
122
|
+
WHERE id = ?
|
|
123
|
+
`).run(result.status, result.exit_code, new Date().toISOString(), result.tokens_in ?? null, result.tokens_out ?? null, result.tokens_cache_read ?? null, result.tokens_cache_create ?? null, result.total_cost_usd ?? null, result.total_cost_usd_estimated ? 1 : 0, result.num_turns ?? null, result.model ?? null, result.duration_ms ?? null, result.duration_api_ms ?? null, result.session_id ?? null, jobId);
|
|
124
|
+
}
|
|
125
|
+
function appendEvent(db, jobId, seq, event) {
|
|
126
|
+
db.prepare('INSERT INTO events (job_id, seq, event_type, source, payload) VALUES (?, ?, ?, ?, ?)').run(jobId, seq, event.event_type, event.source ?? null, event.payload);
|
|
127
|
+
}
|
|
128
|
+
function upsertPhase(db, jobId, phase, state) {
|
|
129
|
+
db.prepare('INSERT OR REPLACE INTO job_phases (job_id, phase, state, updated_at) VALUES (?, ?, ?, ?)').run(jobId, phase, state, new Date().toISOString());
|
|
130
|
+
}
|
|
131
|
+
function listJobs(db, opts) {
|
|
132
|
+
const limit = Math.min(opts.limit ?? 50, 200);
|
|
133
|
+
const offset = opts.offset ?? 0;
|
|
134
|
+
const conditions = [];
|
|
135
|
+
const params = [];
|
|
136
|
+
if (opts.status) {
|
|
137
|
+
conditions.push('status = ?');
|
|
138
|
+
params.push(opts.status);
|
|
139
|
+
}
|
|
140
|
+
if (opts.from) {
|
|
141
|
+
conditions.push('started_at >= ?');
|
|
142
|
+
params.push(opts.from);
|
|
143
|
+
}
|
|
144
|
+
if (opts.to) {
|
|
145
|
+
conditions.push('started_at <= ?');
|
|
146
|
+
params.push(opts.to);
|
|
147
|
+
}
|
|
148
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
149
|
+
const countRow = db
|
|
150
|
+
.prepare(`SELECT COUNT(*) as count FROM jobs ${where}`)
|
|
151
|
+
.get(...params);
|
|
152
|
+
const jobs = db
|
|
153
|
+
.prepare(`SELECT jobs.*, jp.profile_name AS profile_name
|
|
154
|
+
FROM jobs LEFT JOIN job_profiles jp ON jp.job_id = jobs.id
|
|
155
|
+
${where}
|
|
156
|
+
ORDER BY started_at DESC LIMIT ? OFFSET ?`)
|
|
157
|
+
.all(...params, limit, offset);
|
|
158
|
+
return { jobs, total: countRow.count };
|
|
159
|
+
}
|
|
160
|
+
function getJob(db, jobId) {
|
|
161
|
+
return db
|
|
162
|
+
.prepare(`
|
|
163
|
+
SELECT jobs.*, jp.profile_name AS profile_name
|
|
164
|
+
FROM jobs
|
|
165
|
+
LEFT JOIN job_profiles jp ON jp.job_id = jobs.id
|
|
166
|
+
WHERE jobs.id = ?
|
|
167
|
+
`)
|
|
168
|
+
.get(jobId);
|
|
169
|
+
}
|
|
170
|
+
function getJobEvents(db, jobId) {
|
|
171
|
+
return db
|
|
172
|
+
.prepare('SELECT * FROM events WHERE job_id = ? ORDER BY seq ASC')
|
|
173
|
+
.all(jobId);
|
|
174
|
+
}
|
|
175
|
+
class JobRecoveryPendingError extends Error {
|
|
176
|
+
jobId;
|
|
177
|
+
constructor(jobId) {
|
|
178
|
+
super('Job recovery is still pending; retry deletion after recovery completes');
|
|
179
|
+
this.jobId = jobId;
|
|
180
|
+
this.name = 'JobRecoveryPendingError';
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
exports.JobRecoveryPendingError = JobRecoveryPendingError;
|
|
184
|
+
function deleteJob(db, jobId) {
|
|
185
|
+
// M7: jobs.depends_on_job_id REFERENCES jobs(id) with no ON DELETE action and
|
|
186
|
+
// foreign_keys=ON, so deleting a pipeline parent throws 'FOREIGN KEY
|
|
187
|
+
// constraint failed' and the job becomes undeletable from the UI. Clear inbound
|
|
188
|
+
// references first, in the same transaction as the delete, so it always
|
|
189
|
+
// succeeds (children keep running; they just lose the now-irrelevant pointer).
|
|
190
|
+
const tx = db.transaction((id) => {
|
|
191
|
+
const pendingLoopStep = db.prepare(`SELECT 1 FROM loop_step_recovery WHERE run_id = ? LIMIT 1`).get(id);
|
|
192
|
+
const pendingQueueRecovery = db.prepare(`SELECT 1 FROM orphan_job_recovery WHERE job_id = ? LIMIT 1`).get(id);
|
|
193
|
+
if (pendingLoopStep || pendingQueueRecovery) {
|
|
194
|
+
throw new JobRecoveryPendingError(id);
|
|
195
|
+
}
|
|
196
|
+
db.prepare('UPDATE jobs SET depends_on_job_id = NULL WHERE depends_on_job_id = ?').run(id);
|
|
197
|
+
// B41: events/job_phases cascade on the jobs FK, but telemetry_blobs/
|
|
198
|
+
// telemetry_summaries (keyed `jobId`), job_profiles and file_provenance
|
|
199
|
+
// (keyed `job_id`) have no FK — without these they accumulate forever. (The
|
|
200
|
+
// on-disk .ndjson.gz blob is reclaimed by the 7-day startup compactor.)
|
|
201
|
+
db.prepare('DELETE FROM telemetry_blobs WHERE jobId = ?').run(id);
|
|
202
|
+
db.prepare('DELETE FROM telemetry_summaries WHERE jobId = ?').run(id);
|
|
203
|
+
db.prepare('DELETE FROM job_profiles WHERE job_id = ?').run(id);
|
|
204
|
+
db.prepare('DELETE FROM file_provenance WHERE job_id = ?').run(id);
|
|
205
|
+
db.prepare('DELETE FROM jobs WHERE id = ?').run(id);
|
|
206
|
+
});
|
|
207
|
+
tx(jobId);
|
|
208
|
+
}
|
|
209
|
+
function purgeJobs(db, opts) {
|
|
210
|
+
const conditions = [
|
|
211
|
+
"status IN ('completed', 'failed', 'canceled', 'zombie_terminated', 'skipped')",
|
|
212
|
+
'NOT EXISTS (SELECT 1 FROM loop_step_recovery WHERE loop_step_recovery.run_id = jobs.id)',
|
|
213
|
+
'NOT EXISTS (SELECT 1 FROM orphan_job_recovery WHERE orphan_job_recovery.job_id = jobs.id)',
|
|
214
|
+
];
|
|
215
|
+
const params = [];
|
|
216
|
+
if (opts?.from) {
|
|
217
|
+
conditions.push('started_at >= ?');
|
|
218
|
+
params.push(opts.from);
|
|
219
|
+
}
|
|
220
|
+
if (opts?.to) {
|
|
221
|
+
conditions.push('started_at <= ?');
|
|
222
|
+
params.push(opts.to);
|
|
223
|
+
}
|
|
224
|
+
const where = conditions.join(' AND ');
|
|
225
|
+
// M6: run the whole purge atomically. Previously these statements ran without a
|
|
226
|
+
// transaction, so when the final `DELETE FROM jobs` aborted on the
|
|
227
|
+
// depends_on_job_id FK (a purged job still referenced by a non-purged one), the
|
|
228
|
+
// events/phases deletes had already committed — destroying log history while
|
|
229
|
+
// deleting zero job rows, and a misleading 500. The transaction rolls back on
|
|
230
|
+
// any failure, and NULL-ing inbound references first makes the delete succeed.
|
|
231
|
+
const tx = db.transaction(() => {
|
|
232
|
+
const sel = `SELECT id FROM jobs WHERE ${where}`;
|
|
233
|
+
db.prepare(`DELETE FROM events WHERE job_id IN (${sel})`).run(...params);
|
|
234
|
+
db.prepare(`DELETE FROM job_phases WHERE job_id IN (${sel})`).run(...params);
|
|
235
|
+
// B41: also purge the no-FK orphan tables for the same jobs.
|
|
236
|
+
db.prepare(`DELETE FROM telemetry_blobs WHERE jobId IN (${sel})`).run(...params);
|
|
237
|
+
db.prepare(`DELETE FROM telemetry_summaries WHERE jobId IN (${sel})`).run(...params);
|
|
238
|
+
db.prepare(`DELETE FROM job_profiles WHERE job_id IN (${sel})`).run(...params);
|
|
239
|
+
db.prepare(`DELETE FROM file_provenance WHERE job_id IN (${sel})`).run(...params);
|
|
240
|
+
// Clear inbound FK references from NON-purged jobs to purged jobs.
|
|
241
|
+
db.prepare(`UPDATE jobs SET depends_on_job_id = NULL WHERE depends_on_job_id IN (${sel})`).run(...params);
|
|
242
|
+
return db.prepare(`DELETE FROM jobs WHERE ${where}`).run(...params).changes;
|
|
243
|
+
});
|
|
244
|
+
return tx();
|
|
245
|
+
}
|
|
246
|
+
function skipJob(db, jobId, reason) {
|
|
247
|
+
db.prepare(`UPDATE jobs SET status = 'skipped', skip_reason = ?, finished_at = ? WHERE id = ?`).run(reason, new Date().toISOString(), jobId);
|
|
248
|
+
}
|
|
249
|
+
function getPipelineJobs(db, pipelineId) {
|
|
250
|
+
return db.prepare('SELECT * FROM jobs WHERE pipeline_id = ? ORDER BY queue_position ASC, started_at ASC').all(pipelineId);
|
|
251
|
+
}
|