@penclipai/server 2026.704.0 → 2026.714.0-canary.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/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/process/execute.d.ts.map +1 -1
- package/dist/adapters/process/execute.js +1 -0
- package/dist/adapters/process/execute.js.map +1 -1
- package/dist/adapters/registry.d.ts.map +1 -1
- package/dist/adapters/registry.js +79 -51
- package/dist/adapters/registry.js.map +1 -1
- package/dist/adapters/utils.d.ts +5 -0
- package/dist/adapters/utils.d.ts.map +1 -1
- package/dist/adapters/utils.js.map +1 -1
- package/dist/agent-auth-jwt.d.ts +5 -1
- package/dist/agent-auth-jwt.d.ts.map +1 -1
- package/dist/agent-auth-jwt.js +72 -20
- package/dist/agent-auth-jwt.js.map +1 -1
- package/dist/app.d.ts +2 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +10 -0
- package/dist/app.js.map +1 -1
- package/dist/built-ins/agents/reflection-coach/AGENTS.md +47 -0
- package/dist/built-ins/agents/reflection-coach/routines/recent-agent-reflection.md +77 -0
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +2 -2
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +208 -116
- package/dist/index.js.map +1 -1
- package/dist/middleware/api-compression.d.ts +8 -0
- package/dist/middleware/api-compression.d.ts.map +1 -0
- package/dist/middleware/api-compression.js +207 -0
- package/dist/middleware/api-compression.js.map +1 -0
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +130 -3
- package/dist/middleware/auth.js.map +1 -1
- package/dist/middleware/error-handler.d.ts.map +1 -1
- package/dist/middleware/error-handler.js +27 -0
- package/dist/middleware/error-handler.js.map +1 -1
- package/dist/onboarding-assets/default/AGENTS.md +1 -0
- package/dist/redaction.d.ts.map +1 -1
- package/dist/redaction.js +11 -0
- package/dist/redaction.js.map +1 -1
- package/dist/routes/access.d.ts.map +1 -1
- package/dist/routes/access.js +4 -3
- package/dist/routes/access.js.map +1 -1
- package/dist/routes/adapters.d.ts.map +1 -1
- package/dist/routes/adapters.js +2 -0
- package/dist/routes/adapters.js.map +1 -1
- package/dist/routes/agents.d.ts.map +1 -1
- package/dist/routes/agents.js +222 -87
- package/dist/routes/agents.js.map +1 -1
- package/dist/routes/attention.d.ts +3 -0
- package/dist/routes/attention.d.ts.map +1 -0
- package/dist/routes/attention.js +24 -0
- package/dist/routes/attention.js.map +1 -0
- package/dist/routes/authz.d.ts.map +1 -1
- package/dist/routes/authz.js +29 -1
- package/dist/routes/authz.js.map +1 -1
- package/dist/routes/board-chat.d.ts.map +1 -1
- package/dist/routes/board-chat.js +4 -1
- package/dist/routes/board-chat.js.map +1 -1
- package/dist/routes/built-in-agents.d.ts +3 -0
- package/dist/routes/built-in-agents.d.ts.map +1 -0
- package/dist/routes/built-in-agents.js +267 -0
- package/dist/routes/built-in-agents.js.map +1 -0
- package/dist/routes/cases.d.ts +46 -0
- package/dist/routes/cases.d.ts.map +1 -0
- package/dist/routes/cases.js +1395 -0
- package/dist/routes/cases.js.map +1 -0
- package/dist/routes/companies.d.ts.map +1 -1
- package/dist/routes/companies.js +4 -1
- package/dist/routes/companies.js.map +1 -1
- package/dist/routes/company-skills.d.ts.map +1 -1
- package/dist/routes/company-skills.js +453 -33
- package/dist/routes/company-skills.js.map +1 -1
- package/dist/routes/environments.d.ts.map +1 -1
- package/dist/routes/environments.js +71 -8
- package/dist/routes/environments.js.map +1 -1
- package/dist/routes/execution-workspaces.d.ts.map +1 -1
- package/dist/routes/execution-workspaces.js +89 -3
- package/dist/routes/execution-workspaces.js.map +1 -1
- package/dist/routes/health.d.ts +2 -0
- package/dist/routes/health.d.ts.map +1 -1
- package/dist/routes/health.js +30 -0
- package/dist/routes/health.js.map +1 -1
- package/dist/routes/inbox-dismissals.d.ts.map +1 -1
- package/dist/routes/inbox-dismissals.js +80 -19
- package/dist/routes/inbox-dismissals.js.map +1 -1
- package/dist/routes/index.d.ts +2 -0
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +2 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/issues.d.ts +25 -0
- package/dist/routes/issues.d.ts.map +1 -1
- package/dist/routes/issues.js +1455 -46
- package/dist/routes/issues.js.map +1 -1
- package/dist/routes/openapi.d.ts.map +1 -1
- package/dist/routes/openapi.js +448 -11
- package/dist/routes/openapi.js.map +1 -1
- package/dist/routes/pipelines.js +2 -2
- package/dist/routes/pipelines.js.map +1 -1
- package/dist/routes/resource-memberships.d.ts.map +1 -1
- package/dist/routes/resource-memberships.js +13 -5
- package/dist/routes/resource-memberships.js.map +1 -1
- package/dist/routes/secrets.d.ts.map +1 -1
- package/dist/routes/secrets.js +261 -1
- package/dist/routes/secrets.js.map +1 -1
- package/dist/routes/sidebar-badges.d.ts.map +1 -1
- package/dist/routes/sidebar-badges.js +18 -2
- package/dist/routes/sidebar-badges.js.map +1 -1
- package/dist/routes/user-profiles.d.ts.map +1 -1
- package/dist/routes/user-profiles.js +5 -4
- package/dist/routes/user-profiles.js.map +1 -1
- package/dist/server-info.d.ts +2 -0
- package/dist/server-info.d.ts.map +1 -1
- package/dist/server-info.js +20 -5
- package/dist/server-info.js.map +1 -1
- package/dist/services/access.d.ts +4 -4
- package/dist/services/access.d.ts.map +1 -1
- package/dist/services/activity.d.ts +3 -3
- package/dist/services/activity.d.ts.map +1 -1
- package/dist/services/activity.js +5 -4
- package/dist/services/activity.js.map +1 -1
- package/dist/services/adapter-registry-bootstrap.reconcile.test.js +1 -0
- package/dist/services/adapter-registry-bootstrap.reconcile.test.js.map +1 -1
- package/dist/services/agent-secret-bindings.d.ts +15 -0
- package/dist/services/agent-secret-bindings.d.ts.map +1 -1
- package/dist/services/agent-secret-bindings.js +43 -0
- package/dist/services/agent-secret-bindings.js.map +1 -1
- package/dist/services/agents.d.ts +194 -133
- package/dist/services/agents.d.ts.map +1 -1
- package/dist/services/agents.js +107 -4
- package/dist/services/agents.js.map +1 -1
- package/dist/services/approvals.d.ts +10 -10
- package/dist/services/approvals.d.ts.map +1 -1
- package/dist/services/approvals.js +9 -1
- package/dist/services/approvals.js.map +1 -1
- package/dist/services/assets.d.ts +5 -5
- package/dist/services/attention.d.ts +11 -0
- package/dist/services/attention.d.ts.map +1 -0
- package/dist/services/attention.js +1024 -0
- package/dist/services/attention.js.map +1 -0
- package/dist/services/authorization.d.ts +15 -2
- package/dist/services/authorization.d.ts.map +1 -1
- package/dist/services/authorization.js +324 -8
- package/dist/services/authorization.js.map +1 -1
- package/dist/services/board-auth.d.ts +2 -2
- package/dist/services/budgets.d.ts.map +1 -1
- package/dist/services/budgets.js +8 -7
- package/dist/services/budgets.js.map +1 -1
- package/dist/services/built-in-agent-metadata.d.ts +9 -0
- package/dist/services/built-in-agent-metadata.d.ts.map +1 -0
- package/dist/services/built-in-agent-metadata.js +39 -0
- package/dist/services/built-in-agent-metadata.js.map +1 -0
- package/dist/services/built-in-agents.d.ts +394 -0
- package/dist/services/built-in-agents.d.ts.map +1 -0
- package/dist/services/built-in-agents.js +1426 -0
- package/dist/services/built-in-agents.js.map +1 -0
- package/dist/services/change-consent-gate.d.ts +18 -0
- package/dist/services/change-consent-gate.d.ts.map +1 -0
- package/dist/services/change-consent-gate.js +170 -0
- package/dist/services/change-consent-gate.js.map +1 -0
- package/dist/services/companies.d.ts +14 -8
- package/dist/services/companies.d.ts.map +1 -1
- package/dist/services/companies.js +6 -1
- package/dist/services/companies.js.map +1 -1
- package/dist/services/company-artifacts.d.ts +4 -1
- package/dist/services/company-artifacts.d.ts.map +1 -1
- package/dist/services/company-artifacts.js +9 -1
- package/dist/services/company-artifacts.js.map +1 -1
- package/dist/services/company-member-roles.d.ts.map +1 -1
- package/dist/services/company-member-roles.js +2 -0
- package/dist/services/company-member-roles.js.map +1 -1
- package/dist/services/company-portability.d.ts +1 -1
- package/dist/services/company-portability.d.ts.map +1 -1
- package/dist/services/company-portability.js +113 -8
- package/dist/services/company-portability.js.map +1 -1
- package/dist/services/company-search.d.ts.map +1 -1
- package/dist/services/company-search.js +843 -294
- package/dist/services/company-search.js.map +1 -1
- package/dist/services/company-skills.d.ts +64 -3
- package/dist/services/company-skills.d.ts.map +1 -1
- package/dist/services/company-skills.js +1205 -20
- package/dist/services/company-skills.js.map +1 -1
- package/dist/services/costs.d.ts +9 -8
- package/dist/services/costs.d.ts.map +1 -1
- package/dist/services/costs.js +9 -2
- package/dist/services/costs.js.map +1 -1
- package/dist/services/dashboard.d.ts +2 -0
- package/dist/services/dashboard.d.ts.map +1 -1
- package/dist/services/dashboard.js +60 -17
- package/dist/services/dashboard.js.map +1 -1
- package/dist/services/database-backup-health.d.ts +34 -0
- package/dist/services/database-backup-health.d.ts.map +1 -0
- package/dist/services/database-backup-health.js +104 -0
- package/dist/services/database-backup-health.js.map +1 -0
- package/dist/services/document-annotations.d.ts +169 -1
- package/dist/services/document-annotations.d.ts.map +1 -1
- package/dist/services/document-annotations.js +272 -1
- package/dist/services/document-annotations.js.map +1 -1
- package/dist/services/documents.d.ts +368 -0
- package/dist/services/documents.d.ts.map +1 -1
- package/dist/services/documents.js +2 -2
- package/dist/services/documents.js.map +1 -1
- package/dist/services/environment-config.d.ts.map +1 -1
- package/dist/services/environment-config.js +6 -0
- package/dist/services/environment-config.js.map +1 -1
- package/dist/services/environment-custom-image-runtime.d.ts +28 -0
- package/dist/services/environment-custom-image-runtime.d.ts.map +1 -1
- package/dist/services/environment-custom-image-runtime.js +106 -7
- package/dist/services/environment-custom-image-runtime.js.map +1 -1
- package/dist/services/environment-custom-images.d.ts +31 -1
- package/dist/services/environment-custom-images.d.ts.map +1 -1
- package/dist/services/environment-custom-images.js +110 -4
- package/dist/services/environment-custom-images.js.map +1 -1
- package/dist/services/environment-execution-target.d.ts.map +1 -1
- package/dist/services/environment-execution-target.js +1 -3
- package/dist/services/environment-execution-target.js.map +1 -1
- package/dist/services/environment-probe.d.ts +1 -0
- package/dist/services/environment-probe.d.ts.map +1 -1
- package/dist/services/environment-probe.js +99 -5
- package/dist/services/environment-probe.js.map +1 -1
- package/dist/services/environments.d.ts +3 -1
- package/dist/services/environments.d.ts.map +1 -1
- package/dist/services/environments.js +98 -3
- package/dist/services/environments.js.map +1 -1
- package/dist/services/execution-workspace-policy.d.ts +29 -3
- package/dist/services/execution-workspace-policy.d.ts.map +1 -1
- package/dist/services/execution-workspace-policy.js +44 -1
- package/dist/services/execution-workspace-policy.js.map +1 -1
- package/dist/services/execution-workspaces.d.ts +64 -1
- package/dist/services/execution-workspaces.d.ts.map +1 -1
- package/dist/services/execution-workspaces.js +634 -3
- package/dist/services/execution-workspaces.js.map +1 -1
- package/dist/services/external-objects.d.ts +26 -26
- package/dist/services/feedback.d.ts +2 -2
- package/dist/services/finance.d.ts +5 -5
- package/dist/services/goals.d.ts +8 -8
- package/dist/services/heartbeat-stop-metadata.d.ts +2 -2
- package/dist/services/heartbeat-stop-metadata.d.ts.map +1 -1
- package/dist/services/heartbeat-stop-metadata.js +4 -0
- package/dist/services/heartbeat-stop-metadata.js.map +1 -1
- package/dist/services/heartbeat-stop-metadata.test.js +9 -0
- package/dist/services/heartbeat-stop-metadata.test.js.map +1 -1
- package/dist/services/heartbeat.d.ts +133 -28
- package/dist/services/heartbeat.d.ts.map +1 -1
- package/dist/services/heartbeat.js +1963 -239
- package/dist/services/heartbeat.js.map +1 -1
- package/dist/services/inbox-dismissals.d.ts +26 -0
- package/dist/services/inbox-dismissals.d.ts.map +1 -1
- package/dist/services/inbox-dismissals.js +33 -18
- package/dist/services/inbox-dismissals.js.map +1 -1
- package/dist/services/index.d.ts +3 -1
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +3 -1
- package/dist/services/index.js.map +1 -1
- package/dist/services/instance-settings.d.ts +25 -1
- package/dist/services/instance-settings.d.ts.map +1 -1
- package/dist/services/instance-settings.js +103 -5
- package/dist/services/instance-settings.js.map +1 -1
- package/dist/services/issue-continuation-summary.js +1 -1
- package/dist/services/issue-continuation-summary.js.map +1 -1
- package/dist/services/issue-recovery-actions.d.ts +3 -0
- package/dist/services/issue-recovery-actions.d.ts.map +1 -1
- package/dist/services/issue-recovery-actions.js +9 -0
- package/dist/services/issue-recovery-actions.js.map +1 -1
- package/dist/services/issue-rewake-throttle.d.ts +92 -0
- package/dist/services/issue-rewake-throttle.d.ts.map +1 -0
- package/dist/services/issue-rewake-throttle.js +139 -0
- package/dist/services/issue-rewake-throttle.js.map +1 -0
- package/dist/services/issue-thread-interactions.d.ts +8 -1
- package/dist/services/issue-thread-interactions.d.ts.map +1 -1
- package/dist/services/issue-thread-interactions.js +231 -16
- package/dist/services/issue-thread-interactions.js.map +1 -1
- package/dist/services/issue-visibility.d.ts +4 -0
- package/dist/services/issue-visibility.d.ts.map +1 -0
- package/dist/services/issue-visibility.js +9 -0
- package/dist/services/issue-visibility.js.map +1 -0
- package/dist/services/issues.d.ts +225 -105
- package/dist/services/issues.d.ts.map +1 -1
- package/dist/services/issues.js +540 -18
- package/dist/services/issues.js.map +1 -1
- package/dist/services/local-service-supervisor.d.ts +3 -0
- package/dist/services/local-service-supervisor.d.ts.map +1 -1
- package/dist/services/local-service-supervisor.js +51 -0
- package/dist/services/local-service-supervisor.js.map +1 -1
- package/dist/services/pipeline-case-outputs.d.ts.map +1 -1
- package/dist/services/pipeline-case-outputs.js +2 -1
- package/dist/services/pipeline-case-outputs.js.map +1 -1
- package/dist/services/pipelines-aggregation.d.ts.map +1 -1
- package/dist/services/pipelines-aggregation.js +3 -2
- package/dist/services/pipelines-aggregation.js.map +1 -1
- package/dist/services/pipelines.d.ts +165 -165
- package/dist/services/pipelines.d.ts.map +1 -1
- package/dist/services/pipelines.js +6 -4
- package/dist/services/pipelines.js.map +1 -1
- package/dist/services/plugin-database.d.ts +2 -2
- package/dist/services/plugin-environment-driver.d.ts +1 -1
- package/dist/services/plugin-host-services.d.ts.map +1 -1
- package/dist/services/plugin-host-services.js +3 -1
- package/dist/services/plugin-host-services.js.map +1 -1
- package/dist/services/plugin-managed-routines.d.ts +1 -0
- package/dist/services/plugin-managed-routines.d.ts.map +1 -1
- package/dist/services/plugin-registry.d.ts +21 -21
- package/dist/services/productivity-review.d.ts +1 -0
- package/dist/services/productivity-review.d.ts.map +1 -1
- package/dist/services/productivity-review.js +6 -5
- package/dist/services/productivity-review.js.map +1 -1
- package/dist/services/projects.d.ts +9 -9
- package/dist/services/recovery/service.d.ts +82 -54
- package/dist/services/recovery/service.d.ts.map +1 -1
- package/dist/services/recovery/service.js +200 -35
- package/dist/services/recovery/service.js.map +1 -1
- package/dist/services/recovery/successful-run-handoff.d.ts +1 -0
- package/dist/services/recovery/successful-run-handoff.d.ts.map +1 -1
- package/dist/services/recovery/successful-run-handoff.js +2 -0
- package/dist/services/recovery/successful-run-handoff.js.map +1 -1
- package/dist/services/recovery/successful-run-handoff.test.js +20 -0
- package/dist/services/recovery/successful-run-handoff.test.js.map +1 -1
- package/dist/services/resource-memberships.d.ts +13 -10
- package/dist/services/resource-memberships.d.ts.map +1 -1
- package/dist/services/resource-memberships.js +89 -20
- package/dist/services/resource-memberships.js.map +1 -1
- package/dist/services/responsible-user-denial-run-outcomes.d.ts +60 -0
- package/dist/services/responsible-user-denial-run-outcomes.d.ts.map +1 -0
- package/dist/services/responsible-user-denial-run-outcomes.js +56 -0
- package/dist/services/responsible-user-denial-run-outcomes.js.map +1 -0
- package/dist/services/responsible-user-denial-run-outcomes.test.d.ts +2 -0
- package/dist/services/responsible-user-denial-run-outcomes.test.d.ts.map +1 -0
- package/dist/services/responsible-user-denial-run-outcomes.test.js +75 -0
- package/dist/services/responsible-user-denial-run-outcomes.test.js.map +1 -0
- package/dist/services/routines.d.ts +30 -8
- package/dist/services/routines.d.ts.map +1 -1
- package/dist/services/routines.js +241 -18
- package/dist/services/routines.js.map +1 -1
- package/dist/services/run-liveness.d.ts.map +1 -1
- package/dist/services/run-liveness.js +21 -0
- package/dist/services/run-liveness.js.map +1 -1
- package/dist/services/run-log-store.d.ts +1 -0
- package/dist/services/run-log-store.d.ts.map +1 -1
- package/dist/services/run-log-store.js +4 -0
- package/dist/services/run-log-store.js.map +1 -1
- package/dist/services/run-scratch.d.ts +42 -0
- package/dist/services/run-scratch.d.ts.map +1 -0
- package/dist/services/run-scratch.js +111 -0
- package/dist/services/run-scratch.js.map +1 -0
- package/dist/services/run-scratch.test.d.ts +2 -0
- package/dist/services/run-scratch.test.d.ts.map +1 -0
- package/dist/services/run-scratch.test.js +98 -0
- package/dist/services/run-scratch.test.js.map +1 -0
- package/dist/services/secrets.d.ts +1261 -64
- package/dist/services/secrets.d.ts.map +1 -1
- package/dist/services/secrets.js +1089 -96
- package/dist/services/secrets.js.map +1 -1
- package/dist/services/task-watchdogs.d.ts +1 -0
- package/dist/services/task-watchdogs.d.ts.map +1 -1
- package/dist/services/task-watchdogs.js +78 -9
- package/dist/services/task-watchdogs.js.map +1 -1
- package/dist/services/work-timeline.d.ts.map +1 -1
- package/dist/services/work-timeline.js +34 -2
- package/dist/services/work-timeline.js.map +1 -1
- package/dist/services/workspace-runtime-read-model.d.ts +30 -29
- package/dist/services/workspace-runtime-read-model.d.ts.map +1 -1
- package/dist/services/workspace-runtime-read-model.js.map +1 -1
- package/dist/services/workspace-runtime.d.ts +59 -13
- package/dist/services/workspace-runtime.d.ts.map +1 -1
- package/dist/services/workspace-runtime.js +1121 -102
- package/dist/services/workspace-runtime.js.map +1 -1
- package/dist/skills-catalog/catalog/bundled/paperclip-operations/reflection-coach/SKILL.md +202 -0
- package/dist/skills-catalog/catalog/bundled/product/paperclip-capsules/SKILL.md +1 -1
- package/dist/skills-catalog/catalog/bundled/product/wireframe/SKILL.md +1 -1
- package/dist/skills-catalog/catalog/optional/content/release-announcement/SKILL.md +90 -0
- package/dist/skills-catalog/catalog/optional/finance/ramp/SKILL.md +98 -0
- package/dist/skills-catalog/generated/catalog.json +84 -12
- package/dist/ui-branding.d.ts +7 -0
- package/dist/ui-branding.d.ts.map +1 -1
- package/dist/ui-branding.js +8 -2
- package/dist/ui-branding.js.map +1 -1
- package/dist/version.d.ts +14 -0
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +114 -3
- package/dist/version.js.map +1 -1
- package/package.json +19 -20
- package/skills/paperclip/SKILL.md +45 -10
- package/skills/paperclip/references/api-reference.md +246 -1
- package/skills/paperclip/references/cases.md +295 -0
- package/skills/paperclip/references/company-skills.md +1 -1
- package/skills/paperclip-board/SKILL.md +3 -4
- package/skills/paperclip-converting-plans-to-tasks/SKILL.md +3 -7
- package/skills/para-memory-files/SKILL.md +3 -7
- package/ui-dist/assets/{abnfDiagram-VRR7QNED-BsFz2IiG.js → abnfDiagram-VRR7QNED-Cy91Tlk9.js} +1 -1
- package/ui-dist/assets/{arc-HZhAwbOs.js → arc-BBAQu311.js} +1 -1
- package/ui-dist/assets/{architectureDiagram-ZJ3FMSHR-BteMjuOn.js → architectureDiagram-ZJ3FMSHR-BrTSDi88.js} +1 -1
- package/ui-dist/assets/{blockDiagram-677ZJIJ3-Iq66uZjf.js → blockDiagram-677ZJIJ3-e5uziFO5.js} +1 -1
- package/ui-dist/assets/{browser-ponyfill-BBsuyZJt.js → browser-ponyfill-D8l5pq-s.js} +1 -1
- package/ui-dist/assets/{c4Diagram-LMCZKHZV-_nlglj7I.js → c4Diagram-LMCZKHZV-DQjqZvNw.js} +1 -1
- package/ui-dist/assets/channel-FQ5LRr4r.js +1 -0
- package/ui-dist/assets/{chunk-2Q5K7J3B-Cvz7WwSQ.js → chunk-2Q5K7J3B-ff7RVJIE.js} +1 -1
- package/ui-dist/assets/{chunk-32BRIVSS-LLubniCS.js → chunk-32BRIVSS-DUJjWWOm.js} +1 -1
- package/ui-dist/assets/{chunk-5VM5RSS4-BOst5hcr.js → chunk-5VM5RSS4-0Yi4VBgb.js} +1 -1
- package/ui-dist/assets/{chunk-EX3LRPZG-D5WNShVm.js → chunk-EX3LRPZG-NKvQ4wVr.js} +1 -1
- package/ui-dist/assets/{chunk-JWPE2WC7-CA07x4av.js → chunk-JWPE2WC7-C9fJ6g-i.js} +1 -1
- package/ui-dist/assets/{chunk-MOJQB5TN-Clfi83rK.js → chunk-MOJQB5TN-DEFFsbL_.js} +1 -1
- package/ui-dist/assets/{chunk-RYQCIY6F-BhZ6p7YJ.js → chunk-RYQCIY6F-B3v41_7G.js} +1 -1
- package/ui-dist/assets/{chunk-V7JOEXUC-CdexJGoV.js → chunk-V7JOEXUC-DVUU3xAk.js} +1 -1
- package/ui-dist/assets/{chunk-VR4S4FIN-8vjmBhr1.js → chunk-VR4S4FIN-bemyw0k2.js} +1 -1
- package/ui-dist/assets/{chunk-XXDRQBXY-CqmCaplj.js → chunk-XXDRQBXY-CWvhgyeb.js} +1 -1
- package/ui-dist/assets/classDiagram-OUVF2IWQ-QaL1os8Q.js +1 -0
- package/ui-dist/assets/classDiagram-v2-EOCWNBFH-QaL1os8Q.js +1 -0
- package/ui-dist/assets/{cose-bilkent-JH36ORCC-CY-zAygY.js → cose-bilkent-JH36ORCC-E-2cLOc1.js} +1 -1
- package/ui-dist/assets/{cynefin-VYW2F7L2-BQSQ1HKr.js → cynefin-VYW2F7L2-CxotRukv.js} +1 -1
- package/ui-dist/assets/{cynefinDiagram-TSTJHNR4-NJCuFrZL.js → cynefinDiagram-TSTJHNR4-AGQOPMl5.js} +1 -1
- package/ui-dist/assets/{dagre-VKFMJZFB-DAUPIc8g.js → dagre-VKFMJZFB-BxLGfVbk.js} +1 -1
- package/ui-dist/assets/{diagram-FQU43EPY-DvMwJlCi.js → diagram-FQU43EPY-DWXO_O6m.js} +1 -1
- package/ui-dist/assets/{diagram-G47NLZAW-DPY0Jz_y.js → diagram-G47NLZAW-C3WsiNNv.js} +1 -1
- package/ui-dist/assets/{diagram-NH7WQ7WH-BpopeEhp.js → diagram-NH7WQ7WH-CbLSRXTf.js} +1 -1
- package/ui-dist/assets/{diagram-OA4YK3LP-Qpu05z9X.js → diagram-OA4YK3LP-hwvxJr04.js} +1 -1
- package/ui-dist/assets/{diagram-WEI45ONY-DHnvw9XU.js → diagram-WEI45ONY-xwXsnT5Q.js} +1 -1
- package/ui-dist/assets/{ebnfDiagram-CCIWWBDH-D61S54_7.js → ebnfDiagram-CCIWWBDH-G311pLCP.js} +1 -1
- package/ui-dist/assets/{erDiagram-Q63AITRT-pplAN_x2.js → erDiagram-Q63AITRT-BnnuR_2P.js} +1 -1
- package/ui-dist/assets/{flowDiagram-23GEKE2U-BaBcbyDq.js → flowDiagram-23GEKE2U-CSHgefz-.js} +1 -1
- package/ui-dist/assets/{ganttDiagram-NO4QXBWP-BOJSII2b.js → ganttDiagram-NO4QXBWP-CERr9vTh.js} +1 -1
- package/ui-dist/assets/{gitGraphDiagram-IHSO6WYX-aYAaO4a2.js → gitGraphDiagram-IHSO6WYX-DHFHVLdn.js} +1 -1
- package/ui-dist/assets/{index-TmpY6Xh_.js → index-B-quLPan.js} +1 -1
- package/ui-dist/assets/{index-LHkZdu0h.js → index-B6QznFjN.js} +1 -1
- package/ui-dist/assets/{index-3EKATXgc.js → index-B7BUwUuV.js} +1 -1
- package/ui-dist/assets/{index-CvPPuNs1.js → index-BDiZss3L.js} +1 -1
- package/ui-dist/assets/{index-BaiskB3o.js → index-BHdxZ8mW.js} +1 -1
- package/ui-dist/assets/{index-CZxYajxF.js → index-BP3GWFFq.js} +1 -1
- package/ui-dist/assets/{index-B5YenxAQ.js → index-BPjK1nba.js} +1 -1
- package/ui-dist/assets/{index-BKVeJ3Jh.js → index-BUGIBR28.js} +1 -1
- package/ui-dist/assets/{index-BRgdHUNR.js → index-BVe5Mpsj.js} +1 -1
- package/ui-dist/assets/{index-Dceamza_.js → index-B_vsK1nr.js} +1 -1
- package/ui-dist/assets/{index-DrL6pHSJ.js → index-BbtsvYoS.js} +1 -1
- package/ui-dist/assets/{index-CemenVOf.js → index-CCptD5rH.js} +1 -1
- package/ui-dist/assets/{index-C-nKjFZh.js → index-CPd0SXS5.js} +1 -1
- package/ui-dist/assets/index-C_R8L9b5.js +676 -0
- package/ui-dist/assets/{index-B8H0SHw1.js → index-CuR0ldHA.js} +1 -1
- package/ui-dist/assets/{index-CKKytnOd.js → index-CvKZQ7p2.js} +1 -1
- package/ui-dist/assets/index-CyuYtn7U.css +1 -0
- package/ui-dist/assets/{index-D3ODiUup.js → index-DSwKPLnt.js} +1 -1
- package/ui-dist/assets/{index-CJlw4l9L.js → index-Daq0pXqe.js} +1 -1
- package/ui-dist/assets/{index-bUFCSEgv.js → index-DmsO--MD.js} +1 -1
- package/ui-dist/assets/{index-Da2kqT1M.js → index-DozP52ue.js} +1 -1
- package/ui-dist/assets/{index-C2JgdjPa.js → index-DxjbpC1V.js} +1 -1
- package/ui-dist/assets/{index-By0oeEvz.js → index-H1y6lR4h.js} +1 -1
- package/ui-dist/assets/{index-BT8b79Bd.js → index-ICaKahDe.js} +1 -1
- package/ui-dist/assets/{index-CjDqB1SX.js → index-q2ZXRiiu.js} +1 -1
- package/ui-dist/assets/{infoDiagram-FWYZ7A6U-C7_90qGq.js → infoDiagram-FWYZ7A6U-BX-kzcCv.js} +1 -1
- package/ui-dist/assets/{ishikawaDiagram-FXEZZL3T-T-nPPdTt.js → ishikawaDiagram-FXEZZL3T-CRO2OGCv.js} +1 -1
- package/ui-dist/assets/{journeyDiagram-5HDEW3XC-C3hqQN0R.js → journeyDiagram-5HDEW3XC-Di6QSKTh.js} +1 -1
- package/ui-dist/assets/{kanban-definition-HUTT4EX6-CPoD9DUm.js → kanban-definition-HUTT4EX6-D9TB3XsU.js} +1 -1
- package/ui-dist/assets/{linear-D-S8yr3Z.js → linear-DNuyBs03.js} +1 -1
- package/ui-dist/assets/{mermaid.core-DhvH8shK.js → mermaid.core-CRXbfGc8.js} +4 -4
- package/ui-dist/assets/{mindmap-definition-LN4V7U3C-CxCIKrKo.js → mindmap-definition-LN4V7U3C-BVxJgBfB.js} +1 -1
- package/ui-dist/assets/{pegDiagram-2B236MQR-D4G-PC6N.js → pegDiagram-2B236MQR-BuMRgd7M.js} +1 -1
- package/ui-dist/assets/{pieDiagram-ENE6RG2P-N4X8UvqY.js → pieDiagram-ENE6RG2P-DFT7WuwK.js} +1 -1
- package/ui-dist/assets/{quadrantDiagram-ABIIQ3AL-CZohoDln.js → quadrantDiagram-ABIIQ3AL-HOLkwSzD.js} +1 -1
- package/ui-dist/assets/{railroadDiagram-RFXS5EU6-fMGQVhwF.js → railroadDiagram-RFXS5EU6-BND-mOri.js} +1 -1
- package/ui-dist/assets/{requirementDiagram-TGXJPOKE-CK0IuUmk.js → requirementDiagram-TGXJPOKE-BOa8A9lE.js} +1 -1
- package/ui-dist/assets/{sankeyDiagram-HTMAVEWB-pSfb3CqG.js → sankeyDiagram-HTMAVEWB-B7cM3qvd.js} +1 -1
- package/ui-dist/assets/{sequenceDiagram-DBY2YBRQ-CbhkqIeq.js → sequenceDiagram-DBY2YBRQ-ClQvkzoo.js} +1 -1
- package/ui-dist/assets/{sizeCapture-X5ZJPWSS-B6vqqPx-.js → sizeCapture-X5ZJPWSS-D4r_s7na.js} +1 -1
- package/ui-dist/assets/{stateDiagram-2N3HPSRC-DIJf9h5C.js → stateDiagram-2N3HPSRC-Cx0UQpxD.js} +1 -1
- package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-C8L1NW8Q.js +1 -0
- package/ui-dist/assets/{swimlanes-5IMT3BWC-DIX_LV8O.js → swimlanes-5IMT3BWC-cGIUWMIm.js} +2 -2
- package/ui-dist/assets/swimlanesDiagram-G3AALYLV-Dj9aJ1a_.js +8 -0
- package/ui-dist/assets/{timeline-definition-FHXFAJF6-CD-Pxb_g.js → timeline-definition-FHXFAJF6-BUHQ5Auj.js} +1 -1
- package/ui-dist/assets/{vennDiagram-L72KCM5P-BAZsWL0R.js → vennDiagram-L72KCM5P-TvbS6VWx.js} +1 -1
- package/ui-dist/assets/{wardleyDiagram-EHGQE667-CnNyfwX5.js → wardleyDiagram-EHGQE667-VkQDXzb8.js} +1 -1
- package/ui-dist/assets/{xychartDiagram-FW5EYKEG-j9YgsD3Y.js → xychartDiagram-FW5EYKEG-ButPn9aQ.js} +1 -1
- package/ui-dist/fonts/InterVariable-Italic.woff2 +0 -0
- package/ui-dist/fonts/InterVariable.woff2 +0 -0
- package/ui-dist/fonts/NOTICE.md +17 -0
- package/ui-dist/index.html +2 -2
- package/ui-dist/locales/en/common.json +1002 -2
- package/ui-dist/locales/zh-CN/common.json +1003 -3
- package/ui-dist/assets/channel-CsiI2LYw.js +0 -1
- package/ui-dist/assets/classDiagram-OUVF2IWQ-CBn1Jqo8.js +0 -1
- package/ui-dist/assets/classDiagram-v2-EOCWNBFH-CBn1Jqo8.js +0 -1
- package/ui-dist/assets/index-BsL8K1vL.css +0 -1
- package/ui-dist/assets/index-D__fTsb2.js +0 -643
- package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-DpwLD7YL.js +0 -1
- package/ui-dist/assets/swimlanesDiagram-G3AALYLV-Cklauutg.js +0 -8
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import fs from "node:fs/promises";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { promisify } from "node:util";
|
|
5
|
-
import { and, asc, desc, eq, inArray, isNull, ne, sql } from "drizzle-orm";
|
|
6
|
-
import { executionWorkspaces, issues, projects, projectWorkspaces } from "@penclipai/db";
|
|
6
|
+
import { and, asc, desc, eq, inArray, isNull, ne, or, sql } from "drizzle-orm";
|
|
7
|
+
import { executionWorkspaces, heartbeatRuns, issueComments, issues, projects, projectWorkspaces, workspaceRuntimeServices } from "@penclipai/db";
|
|
7
8
|
import { deriveProjectUrlKey, WORKSPACE_OVERVIEW_LINKED_ISSUE_LIMIT } from "@penclipai/shared";
|
|
9
|
+
import { conflict, notFound, unprocessable } from "../errors.js";
|
|
10
|
+
import { applyIssueExecutionPolicyTransition, normalizeIssueExecutionPolicy, parseIssueExecutionState, } from "./issue-execution-policy.js";
|
|
8
11
|
import { parseProjectExecutionWorkspacePolicy } from "./execution-workspace-policy.js";
|
|
12
|
+
import { issueRecoveryActionService } from "./issue-recovery-actions.js";
|
|
13
|
+
import { visibleIssueCondition } from "./issue-visibility.js";
|
|
9
14
|
import { readProjectWorkspaceRuntimeConfig } from "./project-workspace-runtime-config.js";
|
|
10
15
|
import { listCurrentRuntimeServicesForExecutionWorkspaces, listCurrentRuntimeServicesForProjectWorkspaces, } from "./workspace-runtime-read-model.js";
|
|
11
16
|
const execFileAsync = promisify(execFile);
|
|
12
17
|
const TERMINAL_ISSUE_STATUSES = new Set(["done", "cancelled"]);
|
|
18
|
+
const WORKSPACE_BRANCH_INCOHERENCE_REASON = "git_worktree_branch_incoherence";
|
|
19
|
+
const WORKSPACE_VALIDATION_RECOVERY_CAUSE = "workspace_validation_failed";
|
|
13
20
|
function isRecord(value) {
|
|
14
21
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15
22
|
}
|
|
@@ -24,6 +31,26 @@ function cloneRecord(value) {
|
|
|
24
31
|
return null;
|
|
25
32
|
return { ...value };
|
|
26
33
|
}
|
|
34
|
+
function assigneeMatchesExecutionPrincipal(input, principal) {
|
|
35
|
+
if (!principal)
|
|
36
|
+
return false;
|
|
37
|
+
if (principal.type === "agent") {
|
|
38
|
+
return input.assigneeAgentId === principal.agentId && input.assigneeUserId === null;
|
|
39
|
+
}
|
|
40
|
+
if (principal.type === "user") {
|
|
41
|
+
return input.assigneeAgentId === null && input.assigneeUserId === principal.userId;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
function quarantineRestoreRequestedSourceStatus(input) {
|
|
46
|
+
const state = parseIssueExecutionState(input.executionState);
|
|
47
|
+
if (state?.status === "pending" &&
|
|
48
|
+
input.status === "in_review" &&
|
|
49
|
+
assigneeMatchesExecutionPrincipal(input, state.currentParticipant)) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
return "todo";
|
|
53
|
+
}
|
|
27
54
|
function readDesiredState(value) {
|
|
28
55
|
return value === "running" || value === "stopped" || value === "manual" ? value : null;
|
|
29
56
|
}
|
|
@@ -49,6 +76,274 @@ async function pathExists(value) {
|
|
|
49
76
|
async function runGit(args, cwd) {
|
|
50
77
|
return await execFileAsync("git", ["-C", cwd, ...args], { cwd });
|
|
51
78
|
}
|
|
79
|
+
async function readGitStdout(args, cwd) {
|
|
80
|
+
const output = await runGit(args, cwd);
|
|
81
|
+
return output.stdout.trim() || null;
|
|
82
|
+
}
|
|
83
|
+
function stableStringify(value) {
|
|
84
|
+
if (Array.isArray(value)) {
|
|
85
|
+
return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
|
|
86
|
+
}
|
|
87
|
+
if (value && typeof value === "object") {
|
|
88
|
+
const rec = value;
|
|
89
|
+
return `{${Object.keys(rec).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(rec[key])}`).join(",")}}`;
|
|
90
|
+
}
|
|
91
|
+
return JSON.stringify(value);
|
|
92
|
+
}
|
|
93
|
+
function formatBranchForMessage(branch) {
|
|
94
|
+
return branch && branch.length > 0 ? branch : "<detached>";
|
|
95
|
+
}
|
|
96
|
+
function fingerprintWorkspaceBranchIncoherence(input) {
|
|
97
|
+
const digest = createHash("sha256")
|
|
98
|
+
.update(stableStringify({
|
|
99
|
+
version: 1,
|
|
100
|
+
reason: WORKSPACE_BRANCH_INCOHERENCE_REASON,
|
|
101
|
+
sourceIssueId: input.sourceIssueId,
|
|
102
|
+
executionWorkspaceId: input.executionWorkspaceId,
|
|
103
|
+
worktreePath: path.resolve(input.worktreePath),
|
|
104
|
+
expectedBranch: input.expectedBranch,
|
|
105
|
+
actualBranch: input.actualBranch,
|
|
106
|
+
cleanliness: input.cleanliness,
|
|
107
|
+
expectedHeadSha: input.expectedHeadSha,
|
|
108
|
+
actualHeadSha: input.actualHeadSha,
|
|
109
|
+
}))
|
|
110
|
+
.digest("hex");
|
|
111
|
+
return `workspace_incoherence:v1:sha256:${digest}`;
|
|
112
|
+
}
|
|
113
|
+
async function getGitWorktreeBranchAncestryVerdict(input) {
|
|
114
|
+
if (!input.expectedHeadSha || !input.actualHeadSha)
|
|
115
|
+
return "unknown";
|
|
116
|
+
try {
|
|
117
|
+
await runGit(["merge-base", "--is-ancestor", input.expectedHeadSha, input.actualHeadSha], input.repoRoot);
|
|
118
|
+
return "ancestor";
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
const code = typeof error === "object" && error && "code" in error
|
|
122
|
+
? error.code
|
|
123
|
+
: null;
|
|
124
|
+
return code === 1 ? "diverged" : "unknown";
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function explainGitWorktreeBranchReconcileInspection(input) {
|
|
128
|
+
if (!input.fromSha || !input.toSha) {
|
|
129
|
+
return `Paperclip could not determine branch ancestry because "${input.fromBranch}" or "${input.toBranch}" is missing a resolvable HEAD commit.`;
|
|
130
|
+
}
|
|
131
|
+
if (input.fromSha === input.toSha) {
|
|
132
|
+
return `The recorded branch "${input.fromBranch}" and checked-out branch "${input.toBranch}" resolve to the same commit.`;
|
|
133
|
+
}
|
|
134
|
+
if (input.ancestryVerdict === "ancestor") {
|
|
135
|
+
return `The recorded branch "${input.fromBranch}" is an ancestor of the checked-out branch "${input.toBranch}".`;
|
|
136
|
+
}
|
|
137
|
+
if (input.ancestryVerdict === "diverged") {
|
|
138
|
+
return `The recorded branch "${input.fromBranch}" is not an ancestor of the checked-out branch "${input.toBranch}".`;
|
|
139
|
+
}
|
|
140
|
+
return `Paperclip could not determine whether "${input.toBranch}" is forward of "${input.fromBranch}".`;
|
|
141
|
+
}
|
|
142
|
+
async function inspectExecutionWorkspaceBranchForReconcile(workspace) {
|
|
143
|
+
const fromBranch = readNullableString(workspace.branchName);
|
|
144
|
+
if (!fromBranch) {
|
|
145
|
+
throw unprocessable("Execution workspace has no recorded branch to reconcile");
|
|
146
|
+
}
|
|
147
|
+
const worktreePath = readNullableString(workspace.providerRef) ?? readNullableString(workspace.cwd);
|
|
148
|
+
if (!worktreePath) {
|
|
149
|
+
throw unprocessable("Execution workspace needs a local worktree path before Paperclip can reconcile its branch record");
|
|
150
|
+
}
|
|
151
|
+
const repoRoot = await readGitStdout(["rev-parse", "--show-toplevel"], worktreePath).catch(() => null);
|
|
152
|
+
if (!repoRoot) {
|
|
153
|
+
throw unprocessable("Execution workspace path is not inside a git repository");
|
|
154
|
+
}
|
|
155
|
+
const toBranch = await readGitStdout(["symbolic-ref", "--quiet", "--short", "HEAD"], worktreePath).catch(() => null);
|
|
156
|
+
if (!toBranch) {
|
|
157
|
+
throw unprocessable("Execution workspace is detached; Paperclip cannot reconcile it to a branch name");
|
|
158
|
+
}
|
|
159
|
+
const status = await runGit(["status", "--porcelain", "--untracked-files=all"], worktreePath)
|
|
160
|
+
.then((output) => output.stdout)
|
|
161
|
+
.catch(() => null);
|
|
162
|
+
const statusLines = status === null
|
|
163
|
+
? null
|
|
164
|
+
: status.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
165
|
+
const cleanliness = status === null ? "unknown" : status.trim().length > 0 ? "dirty" : "clean";
|
|
166
|
+
const fromSha = await readGitStdout(["rev-parse", "--verify", `refs/heads/${fromBranch}^{commit}`], repoRoot)
|
|
167
|
+
.catch(() => null);
|
|
168
|
+
const toSha = await readGitStdout(["rev-parse", "HEAD"], worktreePath).catch(() => null);
|
|
169
|
+
const ancestryVerdict = await getGitWorktreeBranchAncestryVerdict({
|
|
170
|
+
repoRoot,
|
|
171
|
+
expectedHeadSha: fromSha,
|
|
172
|
+
actualHeadSha: toSha,
|
|
173
|
+
});
|
|
174
|
+
return {
|
|
175
|
+
fingerprint: fingerprintWorkspaceBranchIncoherence({
|
|
176
|
+
sourceIssueId: workspace.sourceIssueId ?? null,
|
|
177
|
+
executionWorkspaceId: workspace.id,
|
|
178
|
+
worktreePath,
|
|
179
|
+
expectedBranch: fromBranch,
|
|
180
|
+
actualBranch: toBranch,
|
|
181
|
+
cleanliness,
|
|
182
|
+
expectedHeadSha: fromSha,
|
|
183
|
+
actualHeadSha: toSha,
|
|
184
|
+
}),
|
|
185
|
+
worktreePath: path.resolve(worktreePath),
|
|
186
|
+
repoRoot: path.resolve(repoRoot),
|
|
187
|
+
fromBranch,
|
|
188
|
+
toBranch,
|
|
189
|
+
fromSha,
|
|
190
|
+
toSha,
|
|
191
|
+
ancestryVerdict,
|
|
192
|
+
cleanliness,
|
|
193
|
+
statusEntryCount: statusLines?.length ?? null,
|
|
194
|
+
plainLanguageReason: explainGitWorktreeBranchReconcileInspection({
|
|
195
|
+
fromBranch,
|
|
196
|
+
toBranch,
|
|
197
|
+
fromSha,
|
|
198
|
+
toSha,
|
|
199
|
+
ancestryVerdict,
|
|
200
|
+
}),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function formatBranchReconcileAuditComment(input) {
|
|
204
|
+
return [
|
|
205
|
+
"Execution workspace branch reconciled.",
|
|
206
|
+
"",
|
|
207
|
+
`- Workspace: \`${input.workspaceId}\``,
|
|
208
|
+
`- Mode: \`${input.mode}\``,
|
|
209
|
+
`- From branch: \`${formatBranchForMessage(input.inspection.fromBranch)}\``,
|
|
210
|
+
`- To branch: \`${formatBranchForMessage(input.inspection.toBranch)}\``,
|
|
211
|
+
`- From SHA: \`${input.inspection.fromSha ?? "unknown"}\``,
|
|
212
|
+
`- To SHA: \`${input.inspection.toSha ?? "unknown"}\``,
|
|
213
|
+
`- Verdict: \`${input.inspection.ancestryVerdict}\``,
|
|
214
|
+
`- Fingerprint: \`${input.inspection.fingerprint}\``,
|
|
215
|
+
`- Recovery action: ${input.recoveryActionId ? `\`${input.recoveryActionId}\`` : "none matched"}`,
|
|
216
|
+
...(input.rescueRef
|
|
217
|
+
? [
|
|
218
|
+
`- Rescue ref: \`${input.rescueRef.branchName}\``,
|
|
219
|
+
`- Rescue commit: \`${input.rescueRef.commitSha}\``,
|
|
220
|
+
`- Rescued file count: \`${input.rescueRef.fileCount}\``,
|
|
221
|
+
]
|
|
222
|
+
: []),
|
|
223
|
+
...(input.reason ? [`- Operator reason: ${input.reason}`] : []),
|
|
224
|
+
].join("\n");
|
|
225
|
+
}
|
|
226
|
+
function isWorkspaceRuntimeValidationFailure(error) {
|
|
227
|
+
if (!error || typeof error !== "object")
|
|
228
|
+
return false;
|
|
229
|
+
const maybe = error;
|
|
230
|
+
return maybe.code === "workspace_validation_failed" &&
|
|
231
|
+
typeof maybe.message === "string" &&
|
|
232
|
+
Boolean(maybe.resultJson) &&
|
|
233
|
+
typeof maybe.resultJson === "object" &&
|
|
234
|
+
!Array.isArray(maybe.resultJson);
|
|
235
|
+
}
|
|
236
|
+
function assertBranchReconcileWorkspaceIsSafe(input) {
|
|
237
|
+
const allowedStatuses = input.allowActiveWorkspace ? ["idle", "active"] : ["idle"];
|
|
238
|
+
if (!allowedStatuses.includes(input.workspaceStatus)) {
|
|
239
|
+
throw unprocessable("Execution workspace branch reconciliation requires the workspace to be idle", {
|
|
240
|
+
workspaceStatus: input.workspaceStatus,
|
|
241
|
+
inspection: input.inspection,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
if (input.inspection.cleanliness !== "clean") {
|
|
245
|
+
throw unprocessable("Execution workspace branch reconciliation requires a clean worktree", {
|
|
246
|
+
inspection: input.inspection,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
assertBranchReconcileRuntimeServicesStopped({
|
|
250
|
+
inspection: input.inspection,
|
|
251
|
+
runtimeServices: input.runtimeServices,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
function assertBranchReconcileRuntimeServicesStopped(input) {
|
|
255
|
+
const activeRuntimeServices = input.runtimeServices.filter((service) => service.status !== "stopped");
|
|
256
|
+
if (activeRuntimeServices.length > 0) {
|
|
257
|
+
throw unprocessable("Execution workspace branch reconciliation requires all runtime services to be stopped", {
|
|
258
|
+
inspection: input.inspection,
|
|
259
|
+
runtimeServices: activeRuntimeServices.map((service) => ({
|
|
260
|
+
id: service.id,
|
|
261
|
+
serviceName: service.serviceName,
|
|
262
|
+
status: service.status,
|
|
263
|
+
})),
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function assertLockedBranchReconcileWorkspaceStillMatchesInspection(input) {
|
|
268
|
+
const lockedPath = readNullableString(input.lockedRow.providerRef) ?? readNullableString(input.lockedRow.cwd);
|
|
269
|
+
const lockedBranch = readNullableString(input.lockedRow.branchName);
|
|
270
|
+
const currentPath = lockedPath ? path.resolve(lockedPath) : null;
|
|
271
|
+
if (input.lockedRow.sourceIssueId !== input.inspectedRow.sourceIssueId ||
|
|
272
|
+
input.lockedRow.projectWorkspaceId !== input.inspectedRow.projectWorkspaceId ||
|
|
273
|
+
lockedBranch !== input.inspection.fromBranch ||
|
|
274
|
+
currentPath !== input.inspection.worktreePath) {
|
|
275
|
+
throw conflict("Execution workspace changed during branch reconciliation; retry with the latest workspace state", {
|
|
276
|
+
workspaceId: input.lockedRow.id,
|
|
277
|
+
expected: {
|
|
278
|
+
status: input.inspectedRow.status,
|
|
279
|
+
sourceIssueId: input.inspectedRow.sourceIssueId,
|
|
280
|
+
projectWorkspaceId: input.inspectedRow.projectWorkspaceId,
|
|
281
|
+
branchName: input.inspection.fromBranch,
|
|
282
|
+
worktreePath: input.inspection.worktreePath,
|
|
283
|
+
},
|
|
284
|
+
current: {
|
|
285
|
+
status: input.lockedRow.status,
|
|
286
|
+
sourceIssueId: input.lockedRow.sourceIssueId,
|
|
287
|
+
projectWorkspaceId: input.lockedRow.projectWorkspaceId,
|
|
288
|
+
branchName: lockedBranch,
|
|
289
|
+
worktreePath: currentPath,
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
async function quarantineRestoreDirtyWorkspaceBranch(input) {
|
|
295
|
+
const sourceIssue = await input.db
|
|
296
|
+
.select({
|
|
297
|
+
id: issues.id,
|
|
298
|
+
identifier: issues.identifier,
|
|
299
|
+
title: issues.title,
|
|
300
|
+
workMode: issues.workMode,
|
|
301
|
+
})
|
|
302
|
+
.from(issues)
|
|
303
|
+
.where(eq(issues.id, input.workspace.sourceIssueId))
|
|
304
|
+
.then((rows) => rows[0] ?? null);
|
|
305
|
+
if (!sourceIssue)
|
|
306
|
+
throw notFound("Source issue not found");
|
|
307
|
+
const { ensureGitWorktreeBranchCoherent } = await import("./workspace-runtime.js");
|
|
308
|
+
try {
|
|
309
|
+
const result = await ensureGitWorktreeBranchCoherent({
|
|
310
|
+
db: input.db,
|
|
311
|
+
repoRoot: input.inspection.repoRoot,
|
|
312
|
+
worktreePath: input.inspection.worktreePath,
|
|
313
|
+
expectedBranchName: input.inspection.fromBranch,
|
|
314
|
+
actualBranchName: input.inspection.toBranch,
|
|
315
|
+
sourceIssue,
|
|
316
|
+
executionWorkspaceId: input.workspace.id,
|
|
317
|
+
heartbeatRunId: input.actor.runId,
|
|
318
|
+
enableWorkspaceBranchReconcileForward: false,
|
|
319
|
+
enableWorkspaceDirtyQuarantineRepair: true,
|
|
320
|
+
persistForwardReconcile: false,
|
|
321
|
+
reconcileOperationPhase: "worktree_prepare",
|
|
322
|
+
recorder: null,
|
|
323
|
+
});
|
|
324
|
+
if (!result.dirtyQuarantineRepair) {
|
|
325
|
+
throw unprocessable("Quarantine restore requires a dirty foreign-branch worktree to repair", {
|
|
326
|
+
inspection: input.inspection,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
return {
|
|
330
|
+
branchName: result.dirtyQuarantineRepair.rescueBranch,
|
|
331
|
+
commitSha: result.dirtyQuarantineRepair.rescueCommitSha,
|
|
332
|
+
fileCount: result.dirtyQuarantineRepair.fileCount,
|
|
333
|
+
sourceAuditCommentId: result.dirtyQuarantineRepair.sourceAuditCommentId,
|
|
334
|
+
claimantAuditCommentId: result.dirtyQuarantineRepair.claimantAuditCommentId,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
if (isWorkspaceRuntimeValidationFailure(error)) {
|
|
339
|
+
throw unprocessable(error.message, {
|
|
340
|
+
code: error.code,
|
|
341
|
+
...error.resultJson,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
throw error;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
52
347
|
async function inspectGitCloseReadiness(workspace) {
|
|
53
348
|
const warnings = [];
|
|
54
349
|
const workspacePath = readNullableString(workspace.providerRef) ?? readNullableString(workspace.cwd);
|
|
@@ -342,6 +637,13 @@ function usesInheritedProjectRuntimeServices(row) {
|
|
|
342
637
|
return false;
|
|
343
638
|
return !readExecutionWorkspaceConfig(row.metadata ?? null)?.workspaceRuntime;
|
|
344
639
|
}
|
|
640
|
+
function noActiveRuntimeServicesForWorkspaceCondition(row) {
|
|
641
|
+
const inheritedProjectWorkspaceId = usesInheritedProjectRuntimeServices(row) ? row.projectWorkspaceId : null;
|
|
642
|
+
const activeServiceConditions = inheritedProjectWorkspaceId
|
|
643
|
+
? and(eq(workspaceRuntimeServices.companyId, row.companyId), eq(workspaceRuntimeServices.projectWorkspaceId, inheritedProjectWorkspaceId), eq(workspaceRuntimeServices.scopeType, "project_workspace"), ne(workspaceRuntimeServices.status, "stopped"))
|
|
644
|
+
: and(eq(workspaceRuntimeServices.companyId, row.companyId), eq(workspaceRuntimeServices.executionWorkspaceId, row.id), ne(workspaceRuntimeServices.status, "stopped"));
|
|
645
|
+
return sql `not exists (select 1 from ${workspaceRuntimeServices} where ${activeServiceConditions})`;
|
|
646
|
+
}
|
|
345
647
|
async function loadEffectiveRuntimeServicesByExecutionWorkspace(db, companyId, rows) {
|
|
346
648
|
const executionRuntimeServices = await listCurrentRuntimeServicesForExecutionWorkspaces(db, companyId, rows.map((row) => row.id));
|
|
347
649
|
const projectWorkspaceIds = rows
|
|
@@ -357,6 +659,7 @@ async function loadEffectiveRuntimeServicesByExecutionWorkspace(db, companyId, r
|
|
|
357
659
|
]));
|
|
358
660
|
}
|
|
359
661
|
export function executionWorkspaceService(db) {
|
|
662
|
+
const recoveryActionsSvc = issueRecoveryActionService(db);
|
|
360
663
|
function buildListConditions(companyId, filters) {
|
|
361
664
|
const conditions = [eq(executionWorkspaces.companyId, companyId)];
|
|
362
665
|
if (filters?.projectId)
|
|
@@ -463,7 +766,7 @@ export function executionWorkspaceService(db) {
|
|
|
463
766
|
count: sql `count(*)::int`,
|
|
464
767
|
})
|
|
465
768
|
.from(issues)
|
|
466
|
-
.where(and(eq(issues.companyId, companyId),
|
|
769
|
+
.where(and(eq(issues.companyId, companyId), visibleIssueCondition(), inArray(issues.executionWorkspaceId, workspaceIds)))
|
|
467
770
|
.groupBy(issues.executionWorkspaceId),
|
|
468
771
|
db.execute(sql `
|
|
469
772
|
select
|
|
@@ -587,6 +890,92 @@ export function executionWorkspaceService(db) {
|
|
|
587
890
|
.orderBy(desc(executionWorkspaces.lastUsedAt), desc(executionWorkspaces.createdAt));
|
|
588
891
|
return rows.map((row) => toExecutionWorkspaceSummary(row));
|
|
589
892
|
},
|
|
893
|
+
findGitWorktreeContention: async (input) => {
|
|
894
|
+
const resolvedWorktreePath = path.resolve(input.worktreePath);
|
|
895
|
+
const pathOrBranchConditions = [
|
|
896
|
+
eq(executionWorkspaces.providerRef, input.worktreePath),
|
|
897
|
+
eq(executionWorkspaces.cwd, input.worktreePath),
|
|
898
|
+
];
|
|
899
|
+
if (input.liveBranchName) {
|
|
900
|
+
pathOrBranchConditions.push(eq(executionWorkspaces.branchName, input.liveBranchName));
|
|
901
|
+
}
|
|
902
|
+
const candidates = await db
|
|
903
|
+
.select({
|
|
904
|
+
id: executionWorkspaces.id,
|
|
905
|
+
cwd: executionWorkspaces.cwd,
|
|
906
|
+
providerRef: executionWorkspaces.providerRef,
|
|
907
|
+
branchName: executionWorkspaces.branchName,
|
|
908
|
+
sourceIssueId: executionWorkspaces.sourceIssueId,
|
|
909
|
+
sourceIssueIdentifier: issues.identifier,
|
|
910
|
+
})
|
|
911
|
+
.from(executionWorkspaces)
|
|
912
|
+
.leftJoin(issues, and(eq(issues.companyId, executionWorkspaces.companyId), eq(issues.id, executionWorkspaces.sourceIssueId)))
|
|
913
|
+
.where(and(eq(executionWorkspaces.companyId, input.companyId), isNull(executionWorkspaces.closedAt), ne(executionWorkspaces.status, "archived"), input.excludingExecutionWorkspaceId
|
|
914
|
+
? ne(executionWorkspaces.id, input.excludingExecutionWorkspaceId)
|
|
915
|
+
: sql `true`, or(...pathOrBranchConditions)))
|
|
916
|
+
.orderBy(desc(executionWorkspaces.lastUsedAt), desc(executionWorkspaces.updatedAt))
|
|
917
|
+
.limit(20);
|
|
918
|
+
for (const candidate of candidates) {
|
|
919
|
+
const candidatePath = readNullableString(candidate.providerRef) ?? readNullableString(candidate.cwd);
|
|
920
|
+
const matchesPath = candidatePath ? path.resolve(candidatePath) === resolvedWorktreePath : false;
|
|
921
|
+
const matchesBranch = Boolean(input.liveBranchName && candidate.branchName === input.liveBranchName);
|
|
922
|
+
if (!matchesPath && !matchesBranch)
|
|
923
|
+
continue;
|
|
924
|
+
const linkedIssueConditions = [eq(issues.executionWorkspaceId, candidate.id)];
|
|
925
|
+
if (candidate.sourceIssueId)
|
|
926
|
+
linkedIssueConditions.push(eq(issues.id, candidate.sourceIssueId));
|
|
927
|
+
const linkedIssueRows = await db
|
|
928
|
+
.select({
|
|
929
|
+
id: issues.id,
|
|
930
|
+
identifier: issues.identifier,
|
|
931
|
+
checkoutRunId: issues.checkoutRunId,
|
|
932
|
+
executionRunId: issues.executionRunId,
|
|
933
|
+
})
|
|
934
|
+
.from(issues)
|
|
935
|
+
.where(and(eq(issues.companyId, input.companyId), isNull(issues.hiddenAt), linkedIssueConditions.length === 1 ? linkedIssueConditions[0] : or(...linkedIssueConditions)))
|
|
936
|
+
.orderBy(desc(issues.updatedAt))
|
|
937
|
+
.limit(20);
|
|
938
|
+
const runToIssue = new Map();
|
|
939
|
+
for (const issue of linkedIssueRows) {
|
|
940
|
+
if (issue.executionRunId)
|
|
941
|
+
runToIssue.set(issue.executionRunId, { id: issue.id, identifier: issue.identifier ?? null });
|
|
942
|
+
if (issue.checkoutRunId)
|
|
943
|
+
runToIssue.set(issue.checkoutRunId, { id: issue.id, identifier: issue.identifier ?? null });
|
|
944
|
+
}
|
|
945
|
+
let activeRun = null;
|
|
946
|
+
const runIds = [...runToIssue.keys()];
|
|
947
|
+
if (runIds.length > 0) {
|
|
948
|
+
const [row] = await db
|
|
949
|
+
.select({
|
|
950
|
+
id: heartbeatRuns.id,
|
|
951
|
+
status: heartbeatRuns.status,
|
|
952
|
+
})
|
|
953
|
+
.from(heartbeatRuns)
|
|
954
|
+
.where(and(eq(heartbeatRuns.companyId, input.companyId), inArray(heartbeatRuns.id, runIds), inArray(heartbeatRuns.status, ["queued", "running"])))
|
|
955
|
+
.orderBy(desc(heartbeatRuns.startedAt), desc(heartbeatRuns.createdAt))
|
|
956
|
+
.limit(1);
|
|
957
|
+
if (row && (row.status === "queued" || row.status === "running")) {
|
|
958
|
+
const issue = runToIssue.get(row.id) ?? null;
|
|
959
|
+
activeRun = {
|
|
960
|
+
id: row.id,
|
|
961
|
+
status: row.status,
|
|
962
|
+
issueId: issue?.id ?? null,
|
|
963
|
+
issueIdentifier: issue?.identifier ?? null,
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
const claimedIssue = linkedIssueRows.find((issue) => issue.id === candidate.sourceIssueId)
|
|
968
|
+
?? linkedIssueRows[0]
|
|
969
|
+
?? null;
|
|
970
|
+
return {
|
|
971
|
+
claimedByWorkspaceId: candidate.id,
|
|
972
|
+
claimedByIssueId: claimedIssue?.id ?? candidate.sourceIssueId ?? null,
|
|
973
|
+
claimedByIssueIdentifier: claimedIssue?.identifier ?? candidate.sourceIssueIdentifier ?? null,
|
|
974
|
+
activeRun,
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
return null;
|
|
978
|
+
},
|
|
590
979
|
getById: async (id) => {
|
|
591
980
|
const row = await db
|
|
592
981
|
.select()
|
|
@@ -822,6 +1211,248 @@ export function executionWorkspaceService(db) {
|
|
|
822
1211
|
.then((rows) => rows[0] ?? null);
|
|
823
1212
|
return row ? toExecutionWorkspace(row) : null;
|
|
824
1213
|
},
|
|
1214
|
+
reconcileExecutionWorkspaceBranch: async (id, input) => {
|
|
1215
|
+
const existingRow = await db
|
|
1216
|
+
.select()
|
|
1217
|
+
.from(executionWorkspaces)
|
|
1218
|
+
.where(eq(executionWorkspaces.id, id))
|
|
1219
|
+
.then((rows) => rows[0] ?? null);
|
|
1220
|
+
if (!existingRow)
|
|
1221
|
+
throw notFound("Execution workspace not found");
|
|
1222
|
+
const existing = toExecutionWorkspace(existingRow);
|
|
1223
|
+
if (!existing.sourceIssueId) {
|
|
1224
|
+
throw unprocessable("Execution workspace needs a source issue before Paperclip can audit branch reconciliation");
|
|
1225
|
+
}
|
|
1226
|
+
const inspection = await inspectExecutionWorkspaceBranchForReconcile(existing);
|
|
1227
|
+
if (input.mode === "forward" && inspection.ancestryVerdict !== "ancestor") {
|
|
1228
|
+
throw unprocessable("Forward branch reconciliation requires the recorded branch to be an ancestor of the checked-out branch", { inspection });
|
|
1229
|
+
}
|
|
1230
|
+
const reason = readNullableString(input.reason);
|
|
1231
|
+
const rescueRef = input.mode === "quarantine_restore"
|
|
1232
|
+
? await (async () => {
|
|
1233
|
+
const runtimeServicesByWorkspaceId = await loadEffectiveRuntimeServicesByExecutionWorkspace(db, existing.companyId, [existingRow]);
|
|
1234
|
+
assertBranchReconcileRuntimeServicesStopped({
|
|
1235
|
+
inspection,
|
|
1236
|
+
runtimeServices: (runtimeServicesByWorkspaceId.get(existing.id) ?? []).map(toRuntimeService),
|
|
1237
|
+
});
|
|
1238
|
+
// The git rescue has to happen before the DB transaction because the
|
|
1239
|
+
// transaction may be retried/rolled back, while git side effects cannot.
|
|
1240
|
+
// The preflight runtime-service guard above keeps known local services
|
|
1241
|
+
// from holding files open during the non-transactional git sequence.
|
|
1242
|
+
return quarantineRestoreDirtyWorkspaceBranch({
|
|
1243
|
+
db,
|
|
1244
|
+
workspace: existing,
|
|
1245
|
+
inspection,
|
|
1246
|
+
actor: input.actor,
|
|
1247
|
+
});
|
|
1248
|
+
})()
|
|
1249
|
+
: null;
|
|
1250
|
+
const now = new Date();
|
|
1251
|
+
const allowActiveWorkspace = input.mode === "forward" &&
|
|
1252
|
+
input.actor.actorType === "system" &&
|
|
1253
|
+
input.actor.actorId === "workspace_runtime" &&
|
|
1254
|
+
Boolean(input.actor.runId);
|
|
1255
|
+
return db.transaction(async (tx) => {
|
|
1256
|
+
const txDb = tx;
|
|
1257
|
+
// Runtime-service activation takes this same row lock before spawning
|
|
1258
|
+
// local services and persists a `starting` row before releasing it.
|
|
1259
|
+
const lockedRow = await tx
|
|
1260
|
+
.select()
|
|
1261
|
+
.from(executionWorkspaces)
|
|
1262
|
+
.where(eq(executionWorkspaces.id, existing.id))
|
|
1263
|
+
.for("update")
|
|
1264
|
+
.then((rows) => rows[0] ?? null);
|
|
1265
|
+
if (!lockedRow)
|
|
1266
|
+
throw notFound("Execution workspace not found");
|
|
1267
|
+
assertLockedBranchReconcileWorkspaceStillMatchesInspection({
|
|
1268
|
+
lockedRow,
|
|
1269
|
+
inspectedRow: existingRow,
|
|
1270
|
+
inspection,
|
|
1271
|
+
});
|
|
1272
|
+
if (usesInheritedProjectRuntimeServices(lockedRow)) {
|
|
1273
|
+
await tx
|
|
1274
|
+
.select({ id: projectWorkspaces.id })
|
|
1275
|
+
.from(projectWorkspaces)
|
|
1276
|
+
.where(and(eq(projectWorkspaces.companyId, lockedRow.companyId), eq(projectWorkspaces.id, lockedRow.projectWorkspaceId)))
|
|
1277
|
+
.for("update");
|
|
1278
|
+
}
|
|
1279
|
+
await tx
|
|
1280
|
+
.select({ id: workspaceRuntimeServices.id })
|
|
1281
|
+
.from(workspaceRuntimeServices)
|
|
1282
|
+
.where(usesInheritedProjectRuntimeServices(lockedRow)
|
|
1283
|
+
? and(eq(workspaceRuntimeServices.companyId, lockedRow.companyId), eq(workspaceRuntimeServices.projectWorkspaceId, lockedRow.projectWorkspaceId), eq(workspaceRuntimeServices.scopeType, "project_workspace"))
|
|
1284
|
+
: and(eq(workspaceRuntimeServices.companyId, lockedRow.companyId), eq(workspaceRuntimeServices.executionWorkspaceId, lockedRow.id)))
|
|
1285
|
+
.for("update");
|
|
1286
|
+
const lockedRuntimeServicesByWorkspaceId = await loadEffectiveRuntimeServicesByExecutionWorkspace(txDb, lockedRow.companyId, [lockedRow]);
|
|
1287
|
+
const lockedRuntimeServices = (lockedRuntimeServicesByWorkspaceId.get(lockedRow.id) ?? []).map(toRuntimeService);
|
|
1288
|
+
const lockedWorkspace = toExecutionWorkspace(lockedRow, lockedRuntimeServices);
|
|
1289
|
+
if (!lockedWorkspace.sourceIssueId) {
|
|
1290
|
+
throw unprocessable("Execution workspace needs a source issue before Paperclip can audit branch reconciliation");
|
|
1291
|
+
}
|
|
1292
|
+
let updatedRow = lockedRow;
|
|
1293
|
+
if (input.mode !== "quarantine_restore") {
|
|
1294
|
+
assertBranchReconcileWorkspaceIsSafe({
|
|
1295
|
+
workspaceStatus: lockedWorkspace.status,
|
|
1296
|
+
inspection,
|
|
1297
|
+
runtimeServices: lockedRuntimeServices,
|
|
1298
|
+
allowActiveWorkspace,
|
|
1299
|
+
});
|
|
1300
|
+
if (lockedWorkspace.branchName !== inspection.fromBranch) {
|
|
1301
|
+
throw unprocessable("Execution workspace branch changed during reconciliation; retry with a fresh inspection", {
|
|
1302
|
+
workspaceBranch: lockedWorkspace.branchName,
|
|
1303
|
+
inspection,
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
const updatePatch = {
|
|
1307
|
+
branchName: inspection.toBranch,
|
|
1308
|
+
updatedAt: now,
|
|
1309
|
+
};
|
|
1310
|
+
if (lockedWorkspace.name === inspection.fromBranch) {
|
|
1311
|
+
updatePatch.name = inspection.toBranch;
|
|
1312
|
+
}
|
|
1313
|
+
const [branchUpdatedRow] = await tx
|
|
1314
|
+
.update(executionWorkspaces)
|
|
1315
|
+
.set(updatePatch)
|
|
1316
|
+
.where(and(eq(executionWorkspaces.id, lockedWorkspace.id), allowActiveWorkspace
|
|
1317
|
+
? inArray(executionWorkspaces.status, ["idle", "active"])
|
|
1318
|
+
: eq(executionWorkspaces.status, "idle"), eq(executionWorkspaces.branchName, inspection.fromBranch), noActiveRuntimeServicesForWorkspaceCondition(lockedRow)))
|
|
1319
|
+
.returning();
|
|
1320
|
+
if (!branchUpdatedRow) {
|
|
1321
|
+
const latestRuntimeServicesByWorkspaceId = await loadEffectiveRuntimeServicesByExecutionWorkspace(txDb, lockedRow.companyId, [lockedRow]);
|
|
1322
|
+
const latestRuntimeServices = (latestRuntimeServicesByWorkspaceId.get(lockedRow.id) ?? []).map(toRuntimeService);
|
|
1323
|
+
assertBranchReconcileWorkspaceIsSafe({
|
|
1324
|
+
workspaceStatus: lockedWorkspace.status,
|
|
1325
|
+
inspection,
|
|
1326
|
+
runtimeServices: latestRuntimeServices,
|
|
1327
|
+
allowActiveWorkspace,
|
|
1328
|
+
});
|
|
1329
|
+
throw unprocessable("Execution workspace branch reconciliation requires the workspace to stay idle with stopped runtime services during the update", {
|
|
1330
|
+
inspection,
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
updatedRow = branchUpdatedRow;
|
|
1334
|
+
}
|
|
1335
|
+
let recoveryAction = await recoveryActionsSvc.resolveActiveForIssue({
|
|
1336
|
+
companyId: lockedWorkspace.companyId,
|
|
1337
|
+
sourceIssueId: lockedWorkspace.sourceIssueId,
|
|
1338
|
+
kind: "workspace_validation",
|
|
1339
|
+
cause: WORKSPACE_VALIDATION_RECOVERY_CAUSE,
|
|
1340
|
+
fingerprint: inspection.fingerprint,
|
|
1341
|
+
status: "resolved",
|
|
1342
|
+
outcome: "restored",
|
|
1343
|
+
resolutionNote: input.mode === "quarantine_restore" && rescueRef
|
|
1344
|
+
? `Execution workspace dirty worktree quarantined on "${rescueRef.branchName}" and restored recorded branch "${inspection.fromBranch}".`
|
|
1345
|
+
: `Execution workspace branch record reconciled from "${inspection.fromBranch}" to "${inspection.toBranch}".`,
|
|
1346
|
+
}, tx);
|
|
1347
|
+
if (!recoveryAction) {
|
|
1348
|
+
for (const alternateFingerprint of input.alternateRecoveryFingerprints ?? []) {
|
|
1349
|
+
if (!alternateFingerprint || alternateFingerprint === inspection.fingerprint)
|
|
1350
|
+
continue;
|
|
1351
|
+
recoveryAction = await recoveryActionsSvc.resolveActiveForIssue({
|
|
1352
|
+
companyId: existing.companyId,
|
|
1353
|
+
sourceIssueId: existing.sourceIssueId,
|
|
1354
|
+
kind: "workspace_validation",
|
|
1355
|
+
cause: WORKSPACE_VALIDATION_RECOVERY_CAUSE,
|
|
1356
|
+
fingerprint: alternateFingerprint,
|
|
1357
|
+
status: "resolved",
|
|
1358
|
+
outcome: "restored",
|
|
1359
|
+
resolutionNote: input.mode === "quarantine_restore" && rescueRef
|
|
1360
|
+
? `Execution workspace dirty worktree quarantined on "${rescueRef.branchName}" and restored recorded branch "${inspection.fromBranch}".`
|
|
1361
|
+
: `Execution workspace branch record reconciled from "${inspection.fromBranch}" to "${inspection.toBranch}".`,
|
|
1362
|
+
}, tx);
|
|
1363
|
+
if (recoveryAction)
|
|
1364
|
+
break;
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
let restoredSourceIssue = null;
|
|
1368
|
+
let sourceIssueStatusChanged = false;
|
|
1369
|
+
if (input.mode === "quarantine_restore") {
|
|
1370
|
+
const [sourceBefore] = await tx
|
|
1371
|
+
.select({
|
|
1372
|
+
id: issues.id,
|
|
1373
|
+
companyId: issues.companyId,
|
|
1374
|
+
status: issues.status,
|
|
1375
|
+
assigneeAgentId: issues.assigneeAgentId,
|
|
1376
|
+
assigneeUserId: issues.assigneeUserId,
|
|
1377
|
+
executionPolicy: issues.executionPolicy,
|
|
1378
|
+
executionState: issues.executionState,
|
|
1379
|
+
monitorNextCheckAt: issues.monitorNextCheckAt,
|
|
1380
|
+
monitorWakeRequestedAt: issues.monitorWakeRequestedAt,
|
|
1381
|
+
monitorLastTriggeredAt: issues.monitorLastTriggeredAt,
|
|
1382
|
+
monitorAttemptCount: issues.monitorAttemptCount,
|
|
1383
|
+
monitorNotes: issues.monitorNotes,
|
|
1384
|
+
monitorScheduledBy: issues.monitorScheduledBy,
|
|
1385
|
+
})
|
|
1386
|
+
.from(issues)
|
|
1387
|
+
.where(eq(issues.id, lockedWorkspace.sourceIssueId))
|
|
1388
|
+
.for("update");
|
|
1389
|
+
if (!sourceBefore)
|
|
1390
|
+
throw notFound("Source issue not found");
|
|
1391
|
+
const requestedStatus = quarantineRestoreRequestedSourceStatus(sourceBefore);
|
|
1392
|
+
const policy = normalizeIssueExecutionPolicy(sourceBefore.executionPolicy ?? null);
|
|
1393
|
+
const transition = applyIssueExecutionPolicyTransition({
|
|
1394
|
+
issue: sourceBefore,
|
|
1395
|
+
policy,
|
|
1396
|
+
previousPolicy: policy,
|
|
1397
|
+
requestedStatus,
|
|
1398
|
+
requestedAssigneePatch: {},
|
|
1399
|
+
actor: {
|
|
1400
|
+
agentId: input.actor.agentId ?? null,
|
|
1401
|
+
userId: input.actor.actorType === "user" ? input.actor.actorId : null,
|
|
1402
|
+
},
|
|
1403
|
+
commentBody: null,
|
|
1404
|
+
});
|
|
1405
|
+
const { issueService } = await import("./issues.js");
|
|
1406
|
+
const updatedIssue = await issueService(db).update(lockedWorkspace.sourceIssueId, {
|
|
1407
|
+
...(requestedStatus ? { status: requestedStatus } : {}),
|
|
1408
|
+
...transition.patch,
|
|
1409
|
+
actorAgentId: input.actor.agentId ?? null,
|
|
1410
|
+
actorUserId: input.actor.actorType === "user" ? input.actor.actorId : null,
|
|
1411
|
+
}, tx);
|
|
1412
|
+
if (!updatedIssue)
|
|
1413
|
+
throw notFound("Source issue not found");
|
|
1414
|
+
restoredSourceIssue = {
|
|
1415
|
+
id: updatedIssue.id,
|
|
1416
|
+
companyId: updatedIssue.companyId,
|
|
1417
|
+
status: updatedIssue.status,
|
|
1418
|
+
assigneeAgentId: updatedIssue.assigneeAgentId,
|
|
1419
|
+
};
|
|
1420
|
+
sourceIssueStatusChanged = sourceBefore.status !== updatedIssue.status;
|
|
1421
|
+
}
|
|
1422
|
+
const [auditComment] = await tx
|
|
1423
|
+
.insert(issueComments)
|
|
1424
|
+
.values({
|
|
1425
|
+
companyId: lockedWorkspace.companyId,
|
|
1426
|
+
issueId: lockedWorkspace.sourceIssueId,
|
|
1427
|
+
authorAgentId: input.actor.actorType === "agent" ? input.actor.agentId : null,
|
|
1428
|
+
authorUserId: input.actor.actorType === "user" ? input.actor.actorId : null,
|
|
1429
|
+
authorType: input.actor.actorType,
|
|
1430
|
+
createdByRunId: input.actor.runId,
|
|
1431
|
+
body: formatBranchReconcileAuditComment({
|
|
1432
|
+
mode: input.mode,
|
|
1433
|
+
reason,
|
|
1434
|
+
workspaceId: existing.id,
|
|
1435
|
+
inspection,
|
|
1436
|
+
recoveryActionId: recoveryAction?.id ?? null,
|
|
1437
|
+
rescueRef,
|
|
1438
|
+
}),
|
|
1439
|
+
})
|
|
1440
|
+
.returning({ id: issueComments.id });
|
|
1441
|
+
await tx
|
|
1442
|
+
.update(issues)
|
|
1443
|
+
.set({ updatedAt: now })
|
|
1444
|
+
.where(eq(issues.id, lockedWorkspace.sourceIssueId));
|
|
1445
|
+
return {
|
|
1446
|
+
workspace: toExecutionWorkspace(updatedRow, lockedRuntimeServices),
|
|
1447
|
+
inspection,
|
|
1448
|
+
recoveryAction,
|
|
1449
|
+
auditCommentId: auditComment?.id ?? null,
|
|
1450
|
+
rescueRef,
|
|
1451
|
+
restoredSourceIssue,
|
|
1452
|
+
sourceIssueStatusChanged,
|
|
1453
|
+
};
|
|
1454
|
+
});
|
|
1455
|
+
},
|
|
825
1456
|
clearEnvironmentSelection: async (companyId, environmentId) => {
|
|
826
1457
|
return db.transaction(async (tx) => {
|
|
827
1458
|
const rows = await tx
|