@paperclipai/server 2026.817.0 → 2026.818.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/hermes-gateway-doc.d.ts +1 -1
- package/dist/adapters/hermes-gateway-doc.d.ts.map +1 -1
- package/dist/adapters/hermes-gateway-doc.js +1 -1
- package/dist/adapters/plugin-loader.d.ts.map +1 -1
- package/dist/adapters/plugin-loader.js +3 -2
- package/dist/adapters/plugin-loader.js.map +1 -1
- package/dist/agent-auth-jwt.d.ts.map +1 -1
- package/dist/agent-auth-jwt.js +8 -2
- package/dist/agent-auth-jwt.js.map +1 -1
- package/dist/app.d.ts +5 -1
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +202 -15
- package/dist/app.js.map +1 -1
- package/dist/built-ins/agents/summarizer/AGENTS.md +2 -2
- package/dist/built-ins/agents/summarizer/routines/refresh-stale-summaries.md +1 -0
- package/dist/db-errors.d.ts +9 -0
- package/dist/db-errors.d.ts.map +1 -1
- package/dist/db-errors.js +19 -0
- package/dist/db-errors.js.map +1 -1
- 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 +162 -38
- package/dist/index.js.map +1 -1
- package/dist/lib/request-promise-memo.d.ts +6 -0
- package/dist/lib/request-promise-memo.d.ts.map +1 -0
- package/dist/lib/request-promise-memo.js +25 -0
- package/dist/lib/request-promise-memo.js.map +1 -0
- package/dist/lib/request-promise-memo.test.d.ts +2 -0
- package/dist/lib/request-promise-memo.test.d.ts.map +1 -0
- package/dist/lib/request-promise-memo.test.js +74 -0
- package/dist/lib/request-promise-memo.test.js.map +1 -0
- package/dist/middleware/auth.d.ts +13 -1
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +92 -41
- package/dist/middleware/auth.js.map +1 -1
- package/dist/middleware/cloud-tenant-actor.test.js +26 -1
- package/dist/middleware/cloud-tenant-actor.test.js.map +1 -1
- package/dist/middleware/error-handler.d.ts.map +1 -1
- package/dist/middleware/error-handler.js +11 -2
- package/dist/middleware/error-handler.js.map +1 -1
- package/dist/middleware/logger.d.ts.map +1 -1
- package/dist/middleware/logger.js +7 -33
- package/dist/middleware/logger.js.map +1 -1
- package/dist/middleware/private-hostname-guard.d.ts.map +1 -1
- package/dist/middleware/private-hostname-guard.js +11 -6
- package/dist/middleware/private-hostname-guard.js.map +1 -1
- package/dist/middleware/private-json-etag.d.ts +3 -0
- package/dist/middleware/private-json-etag.d.ts.map +1 -0
- package/dist/middleware/private-json-etag.js +37 -0
- package/dist/middleware/private-json-etag.js.map +1 -0
- package/dist/middleware/redact-sensitive.d.ts.map +1 -1
- package/dist/middleware/redact-sensitive.js +11 -0
- package/dist/middleware/redact-sensitive.js.map +1 -1
- package/dist/realtime/live-events-ws.d.ts +12 -0
- package/dist/realtime/live-events-ws.d.ts.map +1 -1
- package/dist/realtime/live-events-ws.js +20 -0
- package/dist/realtime/live-events-ws.js.map +1 -1
- package/dist/redaction.js +1 -1
- package/dist/redaction.js.map +1 -1
- package/dist/routes/access.d.ts +2 -0
- package/dist/routes/access.d.ts.map +1 -1
- package/dist/routes/access.js +21 -10
- package/dist/routes/access.js.map +1 -1
- package/dist/routes/agents.d.ts +42 -0
- package/dist/routes/agents.d.ts.map +1 -1
- package/dist/routes/agents.js +875 -8
- package/dist/routes/agents.js.map +1 -1
- package/dist/routes/companies.d.ts +5 -1
- package/dist/routes/companies.d.ts.map +1 -1
- package/dist/routes/companies.js +467 -30
- package/dist/routes/companies.js.map +1 -1
- package/dist/routes/environments.d.ts +12 -3
- package/dist/routes/environments.d.ts.map +1 -1
- package/dist/routes/environments.js +139 -23
- package/dist/routes/environments.js.map +1 -1
- package/dist/routes/execution-workspaces.d.ts.map +1 -1
- package/dist/routes/execution-workspaces.js +240 -70
- package/dist/routes/execution-workspaces.js.map +1 -1
- package/dist/routes/file-resources.d.ts +14 -1
- package/dist/routes/file-resources.d.ts.map +1 -1
- package/dist/routes/file-resources.js +182 -3
- package/dist/routes/file-resources.js.map +1 -1
- package/dist/routes/index.d.ts +2 -1
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +2 -1
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/instance-settings.d.ts.map +1 -1
- package/dist/routes/instance-settings.js +20 -1
- package/dist/routes/instance-settings.js.map +1 -1
- package/dist/routes/issues.d.ts.map +1 -1
- package/dist/routes/issues.js +750 -235
- package/dist/routes/issues.js.map +1 -1
- package/dist/routes/onboarding-seed.d.ts +27 -0
- package/dist/routes/onboarding-seed.d.ts.map +1 -0
- package/dist/routes/onboarding-seed.js +56 -0
- package/dist/routes/onboarding-seed.js.map +1 -0
- package/dist/routes/openapi.d.ts.map +1 -1
- package/dist/routes/openapi.js +316 -2
- package/dist/routes/openapi.js.map +1 -1
- package/dist/routes/secrets.d.ts.map +1 -1
- package/dist/routes/secrets.js +11 -1
- package/dist/routes/secrets.js.map +1 -1
- package/dist/routes/setup-token-route.test.d.ts +2 -0
- package/dist/routes/setup-token-route.test.d.ts.map +1 -0
- package/dist/routes/setup-token-route.test.js +896 -0
- package/dist/routes/setup-token-route.test.js.map +1 -0
- package/dist/routes/tool-access.d.ts.map +1 -1
- package/dist/routes/tool-access.js +87 -52
- package/dist/routes/tool-access.js.map +1 -1
- package/dist/routes/workspace-runtime-service-authz.d.ts +11 -1
- package/dist/routes/workspace-runtime-service-authz.d.ts.map +1 -1
- package/dist/routes/workspace-runtime-service-authz.js +18 -16
- package/dist/routes/workspace-runtime-service-authz.js.map +1 -1
- package/dist/services/access.d.ts.map +1 -1
- package/dist/services/activity.d.ts +1 -1
- package/dist/services/agents.d.ts +21 -0
- package/dist/services/agents.d.ts.map +1 -1
- package/dist/services/agents.js +121 -1
- package/dist/services/agents.js.map +1 -1
- package/dist/services/attention-resolver-audience.test.d.ts +2 -0
- package/dist/services/attention-resolver-audience.test.d.ts.map +1 -0
- package/dist/services/attention-resolver-audience.test.js +81 -0
- package/dist/services/attention-resolver-audience.test.js.map +1 -0
- package/dist/services/attention.d.ts +20 -1
- package/dist/services/attention.d.ts.map +1 -1
- package/dist/services/attention.js +45 -1
- package/dist/services/attention.js.map +1 -1
- package/dist/services/authorization.d.ts +2 -2
- package/dist/services/authorization.d.ts.map +1 -1
- package/dist/services/authorization.js +89 -46
- package/dist/services/authorization.js.map +1 -1
- package/dist/services/board-auth.d.ts.map +1 -1
- package/dist/services/board-auth.js +40 -1
- package/dist/services/board-auth.js.map +1 -1
- package/dist/services/board-auth.test.d.ts +2 -0
- package/dist/services/board-auth.test.d.ts.map +1 -0
- package/dist/services/board-auth.test.js +34 -0
- package/dist/services/board-auth.test.js.map +1 -0
- package/dist/services/built-in-agents.d.ts.map +1 -1
- package/dist/services/built-in-agents.js +20 -4
- package/dist/services/built-in-agents.js.map +1 -1
- package/dist/services/bundled-plugins.d.ts +11 -1
- package/dist/services/bundled-plugins.d.ts.map +1 -1
- package/dist/services/bundled-plugins.js +46 -1
- package/dist/services/bundled-plugins.js.map +1 -1
- package/dist/services/codex-device-login-reaper.d.ts +61 -0
- package/dist/services/codex-device-login-reaper.d.ts.map +1 -0
- package/dist/services/codex-device-login-reaper.js +219 -0
- package/dist/services/codex-device-login-reaper.js.map +1 -0
- package/dist/services/codex-device-login-service.d.ts +355 -0
- package/dist/services/codex-device-login-service.d.ts.map +1 -0
- package/dist/services/codex-device-login-service.js +775 -0
- package/dist/services/codex-device-login-service.js.map +1 -0
- package/dist/services/companies.d.ts.map +1 -1
- package/dist/services/companies.js +7 -0
- package/dist/services/companies.js.map +1 -1
- package/dist/services/company-export-readme.js +1 -1
- package/dist/services/company-export-readme.js.map +1 -1
- package/dist/services/company-import-transfers.d.ts +55 -0
- package/dist/services/company-import-transfers.d.ts.map +1 -0
- package/dist/services/company-import-transfers.js +151 -0
- package/dist/services/company-import-transfers.js.map +1 -0
- package/dist/services/company-portability.d.ts.map +1 -1
- package/dist/services/company-portability.js +128 -3
- package/dist/services/company-portability.js.map +1 -1
- package/dist/services/company-transfer-runs.d.ts +136 -0
- package/dist/services/company-transfer-runs.d.ts.map +1 -0
- package/dist/services/company-transfer-runs.js +243 -0
- package/dist/services/company-transfer-runs.js.map +1 -0
- package/dist/services/cross-issue-influence-limit.d.ts +7 -1
- package/dist/services/cross-issue-influence-limit.d.ts.map +1 -1
- package/dist/services/cross-issue-influence-limit.js.map +1 -1
- package/dist/services/environment-config.d.ts +15 -0
- package/dist/services/environment-config.d.ts.map +1 -1
- package/dist/services/environment-config.js +56 -10
- package/dist/services/environment-config.js.map +1 -1
- package/dist/services/environment-execution-target.d.ts.map +1 -1
- package/dist/services/environment-execution-target.js +61 -11
- package/dist/services/environment-execution-target.js.map +1 -1
- package/dist/services/environment-run-orchestrator.d.ts +16 -1
- package/dist/services/environment-run-orchestrator.d.ts.map +1 -1
- package/dist/services/environment-run-orchestrator.js +1 -1
- package/dist/services/environment-run-orchestrator.js.map +1 -1
- package/dist/services/environment-runtime.d.ts +178 -2
- package/dist/services/environment-runtime.d.ts.map +1 -1
- package/dist/services/environment-runtime.js +1035 -96
- package/dist/services/environment-runtime.js.map +1 -1
- package/dist/services/environments.d.ts +75 -0
- package/dist/services/environments.d.ts.map +1 -1
- package/dist/services/environments.js +199 -8
- package/dist/services/environments.js.map +1 -1
- package/dist/services/execution-allowlist.d.ts +16 -4
- package/dist/services/execution-allowlist.d.ts.map +1 -1
- package/dist/services/execution-allowlist.js +35 -16
- package/dist/services/execution-allowlist.js.map +1 -1
- package/dist/services/execution-allowlist.test.js +33 -0
- package/dist/services/execution-allowlist.test.js.map +1 -1
- package/dist/services/execution-workspace-policy.d.ts +14 -1
- package/dist/services/execution-workspace-policy.d.ts.map +1 -1
- package/dist/services/execution-workspace-policy.js +29 -12
- package/dist/services/execution-workspace-policy.js.map +1 -1
- package/dist/services/execution-workspaces.d.ts +89 -0
- package/dist/services/execution-workspaces.d.ts.map +1 -1
- package/dist/services/execution-workspaces.js +997 -125
- package/dist/services/execution-workspaces.js.map +1 -1
- package/dist/services/heartbeat-run-summary.d.ts +1 -0
- package/dist/services/heartbeat-run-summary.d.ts.map +1 -1
- package/dist/services/heartbeat-run-summary.js +19 -3
- package/dist/services/heartbeat-run-summary.js.map +1 -1
- package/dist/services/heartbeat.d.ts +18 -2
- package/dist/services/heartbeat.d.ts.map +1 -1
- package/dist/services/heartbeat.js +369 -16
- package/dist/services/heartbeat.js.map +1 -1
- package/dist/services/import-write-types.d.ts +12 -0
- package/dist/services/import-write-types.d.ts.map +1 -1
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +2 -0
- package/dist/services/index.js.map +1 -1
- package/dist/services/instance-settings.d.ts +10 -1
- package/dist/services/instance-settings.d.ts.map +1 -1
- package/dist/services/instance-settings.js +16 -9
- package/dist/services/instance-settings.js.map +1 -1
- package/dist/services/issue-review-policy.d.ts +4 -0
- package/dist/services/issue-review-policy.d.ts.map +1 -1
- package/dist/services/issue-review-policy.js +18 -7
- package/dist/services/issue-review-policy.js.map +1 -1
- package/dist/services/issue-thread-interaction-resolution.d.ts +92 -0
- package/dist/services/issue-thread-interaction-resolution.d.ts.map +1 -0
- package/dist/services/issue-thread-interaction-resolution.js +198 -0
- package/dist/services/issue-thread-interaction-resolution.js.map +1 -0
- package/dist/services/issue-thread-interaction-resolution.test.d.ts +2 -0
- package/dist/services/issue-thread-interaction-resolution.test.d.ts.map +1 -0
- package/dist/services/issue-thread-interaction-resolution.test.js +152 -0
- package/dist/services/issue-thread-interaction-resolution.test.js.map +1 -0
- package/dist/services/issue-thread-interactions.d.ts +24 -4
- package/dist/services/issue-thread-interactions.d.ts.map +1 -1
- package/dist/services/issue-thread-interactions.js +360 -145
- package/dist/services/issue-thread-interactions.js.map +1 -1
- package/dist/services/issue-thread-interactions.test.js +18 -11
- package/dist/services/issue-thread-interactions.test.js.map +1 -1
- package/dist/services/issues.d.ts +3 -0
- package/dist/services/issues.d.ts.map +1 -1
- package/dist/services/issues.js +157 -33
- package/dist/services/issues.js.map +1 -1
- package/dist/services/local-service-supervisor.d.ts +2 -0
- package/dist/services/local-service-supervisor.d.ts.map +1 -1
- package/dist/services/local-service-supervisor.js +53 -3
- package/dist/services/local-service-supervisor.js.map +1 -1
- package/dist/services/managed-environments.d.ts +4 -1
- package/dist/services/managed-environments.d.ts.map +1 -1
- package/dist/services/managed-environments.js +86 -2
- package/dist/services/managed-environments.js.map +1 -1
- package/dist/services/managed-environments.test.js +164 -0
- package/dist/services/managed-environments.test.js.map +1 -1
- package/dist/services/onboarding-greeting.d.ts +7 -0
- package/dist/services/onboarding-greeting.d.ts.map +1 -0
- package/dist/services/onboarding-greeting.js +27 -0
- package/dist/services/onboarding-greeting.js.map +1 -0
- package/dist/services/onboarding-greeting.test.d.ts +2 -0
- package/dist/services/onboarding-greeting.test.d.ts.map +1 -0
- package/dist/services/onboarding-greeting.test.js +32 -0
- package/dist/services/onboarding-greeting.test.js.map +1 -0
- package/dist/services/onboarding-seed.d.ts +57 -0
- package/dist/services/onboarding-seed.d.ts.map +1 -0
- package/dist/services/onboarding-seed.js +344 -0
- package/dist/services/onboarding-seed.js.map +1 -0
- package/dist/services/plan-review-context.d.ts +9 -1
- package/dist/services/plan-review-context.d.ts.map +1 -1
- package/dist/services/plan-review-context.js +192 -0
- package/dist/services/plan-review-context.js.map +1 -1
- package/dist/services/plugin-environment-driver.d.ts +46 -0
- package/dist/services/plugin-environment-driver.d.ts.map +1 -1
- package/dist/services/plugin-environment-driver.js +50 -0
- package/dist/services/plugin-environment-driver.js.map +1 -1
- package/dist/services/plugin-host-services.d.ts +1 -0
- package/dist/services/plugin-host-services.d.ts.map +1 -1
- package/dist/services/plugin-host-services.js +51 -28
- package/dist/services/plugin-host-services.js.map +1 -1
- package/dist/services/plugin-loader.d.ts +10 -1
- package/dist/services/plugin-loader.d.ts.map +1 -1
- package/dist/services/plugin-loader.js +43 -2
- package/dist/services/plugin-loader.js.map +1 -1
- package/dist/services/plugin-worker-manager.d.ts +61 -0
- package/dist/services/plugin-worker-manager.d.ts.map +1 -1
- package/dist/services/plugin-worker-manager.js +248 -1
- package/dist/services/plugin-worker-manager.js.map +1 -1
- package/dist/services/productivity-review.d.ts.map +1 -1
- package/dist/services/productivity-review.js +5 -0
- package/dist/services/productivity-review.js.map +1 -1
- package/dist/services/recovery/service.d.ts.map +1 -1
- package/dist/services/recovery/service.js +5 -1
- package/dist/services/recovery/service.js.map +1 -1
- package/dist/services/recovery/successful-run-handoff.d.ts +16 -1
- package/dist/services/recovery/successful-run-handoff.d.ts.map +1 -1
- package/dist/services/recovery/successful-run-handoff.js +32 -0
- package/dist/services/recovery/successful-run-handoff.js.map +1 -1
- package/dist/services/recovery/successful-run-handoff.test.js +51 -1
- package/dist/services/recovery/successful-run-handoff.test.js.map +1 -1
- package/dist/services/routines.d.ts.map +1 -1
- package/dist/services/routines.js +48 -4
- package/dist/services/routines.js.map +1 -1
- package/dist/services/runtime-exposure/broker-client.d.ts +88 -0
- package/dist/services/runtime-exposure/broker-client.d.ts.map +1 -0
- package/dist/services/runtime-exposure/broker-client.js +217 -0
- package/dist/services/runtime-exposure/broker-client.js.map +1 -0
- package/dist/services/runtime-exposure/broker-client.test.d.ts +2 -0
- package/dist/services/runtime-exposure/broker-client.test.d.ts.map +1 -0
- package/dist/services/runtime-exposure/broker-client.test.js +185 -0
- package/dist/services/runtime-exposure/broker-client.test.js.map +1 -0
- package/dist/services/runtime-exposure/exposure-manager.d.ts +135 -0
- package/dist/services/runtime-exposure/exposure-manager.d.ts.map +1 -0
- package/dist/services/runtime-exposure/exposure-manager.js +247 -0
- package/dist/services/runtime-exposure/exposure-manager.js.map +1 -0
- package/dist/services/runtime-exposure/exposure-manager.test.d.ts +2 -0
- package/dist/services/runtime-exposure/exposure-manager.test.d.ts.map +1 -0
- package/dist/services/runtime-exposure/exposure-manager.test.js +384 -0
- package/dist/services/runtime-exposure/exposure-manager.test.js.map +1 -0
- package/dist/services/runtime-exposure/loopback-listener.d.ts +33 -0
- package/dist/services/runtime-exposure/loopback-listener.d.ts.map +1 -0
- package/dist/services/runtime-exposure/loopback-listener.js +146 -0
- package/dist/services/runtime-exposure/loopback-listener.js.map +1 -0
- package/dist/services/runtime-exposure/loopback-listener.test.d.ts +2 -0
- package/dist/services/runtime-exposure/loopback-listener.test.d.ts.map +1 -0
- package/dist/services/runtime-exposure/loopback-listener.test.js +116 -0
- package/dist/services/runtime-exposure/loopback-listener.test.js.map +1 -0
- package/dist/services/runtime-exposure/port-pair.d.ts +47 -0
- package/dist/services/runtime-exposure/port-pair.d.ts.map +1 -0
- package/dist/services/runtime-exposure/port-pair.js +57 -0
- package/dist/services/runtime-exposure/port-pair.js.map +1 -0
- package/dist/services/runtime-exposure/port-pair.test.d.ts +2 -0
- package/dist/services/runtime-exposure/port-pair.test.d.ts.map +1 -0
- package/dist/services/runtime-exposure/port-pair.test.js +92 -0
- package/dist/services/runtime-exposure/port-pair.test.js.map +1 -0
- package/dist/services/runtime-exposure/port-reservation.d.ts +211 -0
- package/dist/services/runtime-exposure/port-reservation.d.ts.map +1 -0
- package/dist/services/runtime-exposure/port-reservation.js +380 -0
- package/dist/services/runtime-exposure/port-reservation.js.map +1 -0
- package/dist/services/runtime-exposure/port-reservation.test.d.ts +2 -0
- package/dist/services/runtime-exposure/port-reservation.test.d.ts.map +1 -0
- package/dist/services/runtime-exposure/port-reservation.test.js +456 -0
- package/dist/services/runtime-exposure/port-reservation.test.js.map +1 -0
- package/dist/services/runtime-exposure/tailscale-hostname.d.ts +11 -0
- package/dist/services/runtime-exposure/tailscale-hostname.d.ts.map +1 -0
- package/dist/services/runtime-exposure/tailscale-hostname.js +36 -0
- package/dist/services/runtime-exposure/tailscale-hostname.js.map +1 -0
- package/dist/services/runtime-exposure/tailscale-hostname.test.d.ts +2 -0
- package/dist/services/runtime-exposure/tailscale-hostname.test.d.ts.map +1 -0
- package/dist/services/runtime-exposure/tailscale-hostname.test.js +14 -0
- package/dist/services/runtime-exposure/tailscale-hostname.test.js.map +1 -0
- package/dist/services/sandbox-orphan-cleanup-spool.d.ts +64 -0
- package/dist/services/sandbox-orphan-cleanup-spool.d.ts.map +1 -0
- package/dist/services/sandbox-orphan-cleanup-spool.js +134 -0
- package/dist/services/sandbox-orphan-cleanup-spool.js.map +1 -0
- package/dist/services/sandbox-provider-runtime.d.ts +15 -0
- package/dist/services/sandbox-provider-runtime.d.ts.map +1 -1
- package/dist/services/sandbox-provider-runtime.js +1 -0
- package/dist/services/sandbox-provider-runtime.js.map +1 -1
- package/dist/services/secrets.d.ts +103 -0
- package/dist/services/secrets.d.ts.map +1 -1
- package/dist/services/secrets.js +432 -10
- package/dist/services/secrets.js.map +1 -1
- package/dist/services/setup-token-session.d.ts +630 -0
- package/dist/services/setup-token-session.d.ts.map +1 -0
- package/dist/services/setup-token-session.js +1005 -0
- package/dist/services/setup-token-session.js.map +1 -0
- package/dist/services/setup-token-session.test.d.ts +2 -0
- package/dist/services/setup-token-session.test.d.ts.map +1 -0
- package/dist/services/setup-token-session.test.js +1143 -0
- package/dist/services/setup-token-session.test.js.map +1 -0
- package/dist/services/setup-token-transport-binding.d.ts +202 -0
- package/dist/services/setup-token-transport-binding.d.ts.map +1 -0
- package/dist/services/setup-token-transport-binding.js +386 -0
- package/dist/services/setup-token-transport-binding.js.map +1 -0
- package/dist/services/setup-token-transport-binding.test.d.ts +2 -0
- package/dist/services/setup-token-transport-binding.test.d.ts.map +1 -0
- package/dist/services/setup-token-transport-binding.test.js +761 -0
- package/dist/services/setup-token-transport-binding.test.js.map +1 -0
- package/dist/services/summary-slots.d.ts.map +1 -1
- package/dist/services/summary-slots.js +40 -6
- package/dist/services/summary-slots.js.map +1 -1
- package/dist/services/task-watchdogs.js +2 -2
- package/dist/services/task-watchdogs.js.map +1 -1
- package/dist/services/tool-access-policy.d.ts +1 -1
- package/dist/services/tool-access.d.ts.map +1 -1
- package/dist/services/tool-access.js +29 -11
- package/dist/services/tool-access.js.map +1 -1
- package/dist/services/tool-gateway.d.ts +1 -1
- package/dist/services/tool-gateway.d.ts.map +1 -1
- package/dist/services/tool-gateway.js +56 -5
- package/dist/services/tool-gateway.js.map +1 -1
- package/dist/services/workspace-file-resources.d.ts +10 -1
- package/dist/services/workspace-file-resources.d.ts.map +1 -1
- package/dist/services/workspace-file-resources.js +258 -6
- package/dist/services/workspace-file-resources.js.map +1 -1
- package/dist/services/workspace-git-operation-scheduler.d.ts +135 -0
- package/dist/services/workspace-git-operation-scheduler.d.ts.map +1 -0
- package/dist/services/workspace-git-operation-scheduler.js +655 -0
- package/dist/services/workspace-git-operation-scheduler.js.map +1 -0
- package/dist/services/workspace-git-operation-scheduler.test.d.ts +2 -0
- package/dist/services/workspace-git-operation-scheduler.test.d.ts.map +1 -0
- package/dist/services/workspace-git-operation-scheduler.test.js +367 -0
- package/dist/services/workspace-git-operation-scheduler.test.js.map +1 -0
- package/dist/services/workspace-operations.d.ts +60 -0
- package/dist/services/workspace-operations.d.ts.map +1 -1
- package/dist/services/workspace-operations.js +384 -19
- package/dist/services/workspace-operations.js.map +1 -1
- package/dist/services/workspace-runtime-exposure-backfill.test.d.ts +2 -0
- package/dist/services/workspace-runtime-exposure-backfill.test.d.ts.map +1 -0
- package/dist/services/workspace-runtime-exposure-backfill.test.js +149 -0
- package/dist/services/workspace-runtime-exposure-backfill.test.js.map +1 -0
- package/dist/services/workspace-runtime-exposure.test.d.ts +2 -0
- package/dist/services/workspace-runtime-exposure.test.d.ts.map +1 -0
- package/dist/services/workspace-runtime-exposure.test.js +517 -0
- package/dist/services/workspace-runtime-exposure.test.js.map +1 -0
- package/dist/services/workspace-runtime-leases.d.ts +90 -0
- package/dist/services/workspace-runtime-leases.d.ts.map +1 -0
- package/dist/services/workspace-runtime-leases.js +252 -0
- package/dist/services/workspace-runtime-leases.js.map +1 -0
- package/dist/services/workspace-runtime-read-model.d.ts +12 -0
- package/dist/services/workspace-runtime-read-model.d.ts.map +1 -1
- package/dist/services/workspace-runtime-read-model.test.js +3 -0
- package/dist/services/workspace-runtime-read-model.test.js.map +1 -1
- package/dist/services/workspace-runtime-ready-comment.test.js +1 -0
- package/dist/services/workspace-runtime-ready-comment.test.js.map +1 -1
- package/dist/services/workspace-runtime.d.ts +160 -2
- package/dist/services/workspace-runtime.d.ts.map +1 -1
- package/dist/services/workspace-runtime.js +1693 -141
- package/dist/services/workspace-runtime.js.map +1 -1
- package/dist/shutdown.d.ts +24 -0
- package/dist/shutdown.d.ts.map +1 -1
- package/dist/shutdown.js +37 -0
- package/dist/shutdown.js.map +1 -1
- package/dist/shutdown.test.js +113 -1
- package/dist/shutdown.test.js.map +1 -1
- package/dist/startup-banner.js +1 -1
- package/dist/startup-banner.js.map +1 -1
- package/dist/url-utils.d.ts +26 -0
- package/dist/url-utils.d.ts.map +1 -0
- package/dist/url-utils.js +59 -0
- package/dist/url-utils.js.map +1 -0
- package/dist/url-utils.test.d.ts +2 -0
- package/dist/url-utils.test.d.ts.map +1 -0
- package/dist/url-utils.test.js +55 -0
- package/dist/url-utils.test.js.map +1 -0
- package/dist/worktree-config.d.ts.map +1 -1
- package/dist/worktree-config.js +3 -17
- package/dist/worktree-config.js.map +1 -1
- package/package.json +20 -20
- package/skills/paperclip/SKILL.md +16 -11
- package/skills/paperclip/references/api-reference.md +8 -5
- package/skills/paperclip-board/SKILL.md +1 -1
- package/ui-dist/assets/{abnfDiagram-VRR7QNED-wvSnvD6-.js → abnfDiagram-N423BO3Z-BvgoV-u5.js} +1 -1
- package/ui-dist/assets/{arc-Bx0V1JsN.js → arc-BInnUBUU.js} +1 -1
- package/ui-dist/assets/architectureDiagram-T3A2C74G-Dg4MziOm.js +36 -0
- package/ui-dist/assets/{blockDiagram-677ZJIJ3-BROcLaUk.js → blockDiagram-VBNYF7ZC-D9kzAkLN.js} +1 -1
- package/ui-dist/assets/{c4Diagram-LMCZKHZV-D-V3kgju.js → c4Diagram-5PPSVZJV-BwzsgV_0.js} +1 -1
- package/ui-dist/assets/channel-qoSYbCn0.js +1 -0
- package/ui-dist/assets/{chunk-32BRIVSS-qillH3H6.js → chunk-2GRJ4B5K-D2EQ9vrl.js} +1 -1
- package/ui-dist/assets/{chunk-2Q5K7J3B-D19cVnii.js → chunk-2Q5K7J3B-DOA_8fSd.js} +1 -1
- package/ui-dist/assets/{chunk-EX3LRPZG-CLUswrZg.js → chunk-5RXB4S5H-CT5ukRiT.js} +1 -1
- package/ui-dist/assets/{chunk-5VM5RSS4-Cwlp02Lx.js → chunk-5VM5RSS4-D2gOlkyB.js} +1 -1
- package/ui-dist/assets/{chunk-MOJQB5TN-yKciyq6E.js → chunk-6Q2QTUOP-DUb6aG8c.js} +1 -1
- package/ui-dist/assets/{chunk-V7JOEXUC-DjJ75g0_.js → chunk-GF5L2VYU-BPabG3Cx.js} +1 -1
- package/ui-dist/assets/{chunk-JWPE2WC7-BjepyzEb.js → chunk-JWPE2WC7-scNfhlZz.js} +1 -1
- package/ui-dist/assets/{chunk-VR4S4FIN-CiEmy8-R.js → chunk-KBJHAD2P-BcRAzRpq.js} +1 -1
- package/ui-dist/assets/{chunk-RYQCIY6F-ed7mQM9j.js → chunk-RYQCIY6F-BiZ0-EGh.js} +1 -1
- package/ui-dist/assets/{chunk-XXDRQBXY-BiI83zoO.js → chunk-XXDRQBXY-lJULxFIE.js} +1 -1
- package/ui-dist/assets/classDiagram-JCYQIIEL-B24u1REs.js +1 -0
- package/ui-dist/assets/classDiagram-v2-OCEON4UE-B24u1REs.js +1 -0
- package/ui-dist/assets/{cose-bilkent-JH36ORCC-DIdQf5ta.js → cose-bilkent-JH36ORCC-Do8pe8JB.js} +1 -1
- package/ui-dist/assets/{cynefin-VYW2F7L2-B2m9xxh8.js → cynefin-VYW2F7L2-B34jJY1x.js} +1 -1
- package/ui-dist/assets/{cynefinDiagram-TSTJHNR4-CGVdB0kX.js → cynefinDiagram-MW4NZA55-DQ8QK8JK.js} +1 -1
- package/ui-dist/assets/cytoscape.esm-DShqj__B.js +321 -0
- package/ui-dist/assets/{dagre-VKFMJZFB-CskKiZwJ.js → dagre-VZM6K2ZE-B7lSKgxH.js} +1 -1
- package/ui-dist/assets/{diagram-OA4YK3LP-D2cn5eIN.js → diagram-7IWD3JNH-B3hLAFGE.js} +1 -1
- package/ui-dist/assets/{diagram-FQU43EPY-oa5Zu0XG.js → diagram-B4RE2ZJO-CH9Ro9e0.js} +1 -1
- package/ui-dist/assets/{diagram-NH7WQ7WH-fymti5Js.js → diagram-LBJQPF4R-DdPVMii7.js} +1 -1
- package/ui-dist/assets/{diagram-G47NLZAW-BWF2ZCsw.js → diagram-Q27KOJAE-f5VXswjc.js} +1 -1
- package/ui-dist/assets/diagram-UB23O5K3-DGqxixsR.js +41 -0
- package/ui-dist/assets/{ebnfDiagram-CCIWWBDH-CJ1d3D5q.js → ebnfDiagram-BXEA7PRR-C-1klg_w.js} +1 -1
- package/ui-dist/assets/{erDiagram-Q63AITRT-BJCPa7kK.js → erDiagram-JOGREHBK-Dt4qK4gv.js} +1 -1
- package/ui-dist/assets/{flowDiagram-23GEKE2U-BWjWuz6r.js → flowDiagram-UKHOOZJN-9ZlrTuET.js} +5 -5
- package/ui-dist/assets/{ganttDiagram-NO4QXBWP-Dv1KKjoM.js → ganttDiagram-PKOTCBZU-CE3lyvFq.js} +1 -1
- package/ui-dist/assets/{gitGraphDiagram-IHSO6WYX-9X7vgL0A.js → gitGraphDiagram-DS77QQ5N-HH2fSgad.js} +1 -1
- package/ui-dist/assets/{index-CpaK48ok.js → index-B61ORkIb.js} +1 -1
- package/ui-dist/assets/index-BHiUiToL.css +1 -0
- package/ui-dist/assets/{index-D2YCrA-D.js → index-BY5ITKmG.js} +1 -1
- package/ui-dist/assets/{index-9JMDmXc5.js → index-BbAhzCq8.js} +1 -1
- package/ui-dist/assets/{index-C5qHatpp.js → index-Bc9g6q63.js} +1 -1
- package/ui-dist/assets/{index-DuI6e5mP.js → index-Bf7tyGxE.js} +1 -1
- package/ui-dist/assets/{index-BahRsj0s.js → index-BvFx-4KW.js} +1 -1
- package/ui-dist/assets/{index-DKlsEDV7.js → index-C1mpWCcn.js} +1 -1
- package/ui-dist/assets/{index-DIXaXZrW.js → index-CHXG4GPL.js} +1 -1
- package/ui-dist/assets/{index-DcfO6NoY.js → index-CLu3Tfpv.js} +1 -1
- package/ui-dist/assets/{index-Cfz6P0Qq.js → index-CQhaM6qd.js} +1 -1
- package/ui-dist/assets/{index-D9d3sl6K.js → index-CVTL3aAL.js} +1 -1
- package/ui-dist/assets/{index-BexA3NAl.js → index-CcRTJ93J.js} +1 -1
- package/ui-dist/assets/{index-CnBrDwGT.js → index-Cm6iEYPt.js} +247 -223
- package/ui-dist/assets/{index-ByGcu-MB.js → index-DQIrrn2L.js} +1 -1
- package/ui-dist/assets/{index-DKEzHO7c.js → index-DU4U1fuv.js} +1 -1
- package/ui-dist/assets/{index-pqJO8FsV.js → index-DV2FOgyD.js} +1 -1
- package/ui-dist/assets/{index-tDuieU3u.js → index-DceuCrr8.js} +1 -1
- package/ui-dist/assets/{index-CjN2lm_w.js → index-DlVBBF8o.js} +1 -1
- package/ui-dist/assets/{index-0yJtUsFh.js → index-DsdVneqe.js} +1 -1
- package/ui-dist/assets/{index-C2a4_IKU.js → index-DvFUXI-Q.js} +1 -1
- package/ui-dist/assets/{index-BTMiDMYv.js → index-FHzrJ7iQ.js} +1 -1
- package/ui-dist/assets/{index-cUQXJtLK.js → index-diFs0CNC.js} +1 -1
- package/ui-dist/assets/{index-C96LHV1A.js → index-hkanj4t6.js} +1 -1
- package/ui-dist/assets/{index-rDI1Q4r9.js → index-yRJnh4vx.js} +1 -1
- package/ui-dist/assets/{infoDiagram-FWYZ7A6U-CHIhutSb.js → infoDiagram-6WML65LV-2jai2fw-.js} +1 -1
- package/ui-dist/assets/{ishikawaDiagram-FXEZZL3T-ChaD7-5_.js → ishikawaDiagram-WSZJBQD7-CUmGhmEb.js} +1 -1
- package/ui-dist/assets/{journeyDiagram-5HDEW3XC-M5T2sMgn.js → journeyDiagram-NVQOT4AX-CGQuPDO1.js} +1 -1
- package/ui-dist/assets/{kanban-definition-HUTT4EX6-B8H6R_Wc.js → kanban-definition-27J2QSJJ-zgEPcbHl.js} +1 -1
- package/ui-dist/assets/{linear-cpPKlVCq.js → linear-BVf5VtLS.js} +1 -1
- package/ui-dist/assets/mermaid.core-D2TFkGfI.js +308 -0
- package/ui-dist/assets/{mindmap-definition-LN4V7U3C-Bf6POzvm.js → mindmap-definition-FAOFIHXS-xoAQ0Lb_.js} +1 -1
- package/ui-dist/assets/{pegDiagram-2B236MQR-BJbGktAV.js → pegDiagram-VL7TDLO6-CBR1PWLr.js} +1 -1
- package/ui-dist/assets/{pieDiagram-ENE6RG2P-BmSN9Bio.js → pieDiagram-7S7Q4E2Y-BMPnzK_e.js} +1 -1
- package/ui-dist/assets/{quadrantDiagram-ABIIQ3AL-JUpqQCx_.js → quadrantDiagram-CIZ2JOQS-CPtVUPkR.js} +1 -1
- package/ui-dist/assets/{railroadDiagram-RFXS5EU6-B8k8T4-2.js → railroadDiagram-AXF67PYL-DC0lsZV1.js} +1 -1
- package/ui-dist/assets/{requirementDiagram-TGXJPOKE-b32heq7Q.js → requirementDiagram-LRYGKXZP-BF6k05SB.js} +1 -1
- package/ui-dist/assets/{sankeyDiagram-HTMAVEWB-DC3s-xgf.js → sankeyDiagram-W5VNT64P-CRN0ibiP.js} +1 -1
- package/ui-dist/assets/{sequenceDiagram-DBY2YBRQ-BsCFyv_E.js → sequenceDiagram-SI44F4Z6-CQtTiPWm.js} +1 -1
- package/ui-dist/assets/{sizeCapture-X5ZJPWSS-S7xeCud3.js → sizeCapture-X5ZJPWSS-BdjuGZd1.js} +1 -1
- package/ui-dist/assets/{stateDiagram-2N3HPSRC-58O3ofoS.js → stateDiagram-OKZ733FA-B99onxyh.js} +1 -1
- package/ui-dist/assets/stateDiagram-v2-UEYNNEHI-bzFlYBZ1.js +1 -0
- package/ui-dist/assets/{swimlanes-5IMT3BWC-DFCVG0iF.js → swimlanes-SLNWSIFB-BIByPbrL.js} +2 -2
- package/ui-dist/assets/swimlanesDiagram-ULZ7WXOC-DgdkBnuK.js +8 -0
- package/ui-dist/assets/{timeline-definition-FHXFAJF6-C0c0SkQu.js → timeline-definition-Z64GVDOM-I1NgOcn9.js} +1 -1
- package/ui-dist/assets/{vennDiagram-L72KCM5P-B1cEx8GQ.js → vennDiagram-T6HMQDX7-D6t2Njcl.js} +1 -1
- package/ui-dist/assets/{wardleyDiagram-EHGQE667-BQhDkOx9.js → wardleyDiagram-T6FBY63Y-BiYKm9gh.js} +1 -1
- package/ui-dist/assets/{xychartDiagram-FW5EYKEG-YSQp1N0i.js → xychartDiagram-ELKLHX3M-BhwXKWIa.js} +4 -4
- package/ui-dist/index.html +3 -3
- package/ui-dist/sw.js +8 -3
- package/ui-dist/assets/architectureDiagram-ZJ3FMSHR-BcJt8Ia1.js +0 -36
- package/ui-dist/assets/channel-ZRpjyNiZ.js +0 -1
- package/ui-dist/assets/classDiagram-OUVF2IWQ-CCzzzCgk.js +0 -1
- package/ui-dist/assets/classDiagram-v2-EOCWNBFH-CCzzzCgk.js +0 -1
- package/ui-dist/assets/cytoscape.esm-D8joxN9f.js +0 -321
- package/ui-dist/assets/diagram-WEI45ONY-M3WOC2tK.js +0 -41
- package/ui-dist/assets/index-BqfqfoQX.css +0 -1
- package/ui-dist/assets/mermaid.core-MCazc_od.js +0 -308
- package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-_VY5dpDG.js +0 -1
- package/ui-dist/assets/swimlanesDiagram-G3AALYLV-BJhUjfns.js +0 -8
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from "node:util";
|
|
2
2
|
import { and, asc, desc, eq, inArray, isNotNull, isNull, ne } from "drizzle-orm";
|
|
3
3
|
import { agents, companies, documents, heartbeatRuns, issueComments, issueDocuments, issueThreadInteractions, issues, toolActionRequests, } from "@paperclipai/db";
|
|
4
|
-
import { trackInteractionResolved } from "@paperclipai/shared/telemetry";
|
|
5
|
-
import { acceptIssueThreadInteractionSchema, askUserQuestionsPayloadSchema, askUserQuestionsResultSchema, cancelIssueThreadInteractionSchema, createIssueThreadInteractionSchema, rejectIssueThreadInteractionSchema, requestCheckboxConfirmationPayloadSchema, requestCheckboxConfirmationResultSchema, requestConfirmationPayloadSchema, requestConfirmationResultSchema, requestItemVerdictsPayloadSchema, requestItemVerdictsResultSchema, suggestTasksPayloadSchema, suggestTasksResultSchema, submitIssueThreadInteractionVerdictsSchema, withdrawIssueThreadInteractionSchema, } from "@paperclipai/shared";
|
|
4
|
+
import { trackInteractionCreated, trackInteractionResolved } from "@paperclipai/shared/telemetry";
|
|
5
|
+
import { acceptIssueThreadInteractionSchema, askUserQuestionsPayloadSchema, askUserQuestionsResultSchema, cancelIssueThreadInteractionSchema, createIssueThreadInteractionSchema, legacyIssueThreadInteractionResolverPolicyAlias, normalizeIssueThreadInteractionResolverPolicy, rejectIssueThreadInteractionSchema, requestCheckboxConfirmationPayloadSchema, requestCheckboxConfirmationResultSchema, requestConfirmationPayloadSchema, requestConfirmationResultSchema, requestItemVerdictsPayloadSchema, requestItemVerdictsResultSchema, suggestTasksPayloadSchema, suggestTasksResultSchema, submitIssueThreadInteractionVerdictsSchema, withdrawIssueThreadInteractionSchema, } from "@paperclipai/shared";
|
|
6
6
|
import { conflict, forbidden, notFound, unprocessable } from "../errors.js";
|
|
7
7
|
import { getTelemetryClient } from "../telemetry.js";
|
|
8
8
|
import { logActivity } from "./activity-log.js";
|
|
9
9
|
import { evaluateAgentInvokabilityFromDb } from "./agent-invokability.js";
|
|
10
|
+
import { assertIssueReviewVerdictActorAllowed, isIssueReviewVerdictInteraction, } from "./issue-review-policy.js";
|
|
10
11
|
import { issueService, runWorkspaceIsFinalized } from "./issues.js";
|
|
12
|
+
import { assertIssueThreadInteractionResolverAudience, canonicalizeStoredResolverPolicy, issueThreadInteractionResolutionError, } from "./issue-thread-interaction-resolution.js";
|
|
11
13
|
import { createPullRequestMergeStateResolver, extractGitHubPullRequestReferences, setBoundedPullRequestCacheEntry, } from "./github-pull-request-merge.js";
|
|
12
14
|
export { extractGitHubPullRequestReferences } from "./github-pull-request-merge.js";
|
|
13
15
|
const GITHUB_PULL_REQUEST_URL_PATTERN = /https:\/\/(?:www\.)?github\.com\/([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)\/pull\/([1-9][0-9]*)/gi;
|
|
@@ -106,54 +108,91 @@ export function getMergeConfirmationPullRequestReferences(row) {
|
|
|
106
108
|
return extractGitHubPullRequestReferences(trustedTextValues);
|
|
107
109
|
}
|
|
108
110
|
const ISSUE_THREAD_INTERACTION_IDEMPOTENCY_CONSTRAINT = "issue_thread_interactions_company_issue_idempotency_uq";
|
|
109
|
-
const DEFAULT_RESOLVER_POLICY_BY_KIND = {
|
|
110
|
-
suggest_tasks: "
|
|
111
|
-
ask_user_questions: "
|
|
112
|
-
request_confirmation: "
|
|
113
|
-
request_checkbox_confirmation: "
|
|
114
|
-
request_item_verdicts: "
|
|
111
|
+
export const DEFAULT_RESOLVER_POLICY_BY_KIND = {
|
|
112
|
+
suggest_tasks: "anyone",
|
|
113
|
+
ask_user_questions: "anyone",
|
|
114
|
+
request_confirmation: "anyone",
|
|
115
|
+
request_checkbox_confirmation: "anyone",
|
|
116
|
+
request_item_verdicts: "anyone",
|
|
117
|
+
};
|
|
118
|
+
const RESOLVER_POLICY_RESTRICTION_RANK = {
|
|
119
|
+
anyone: 0,
|
|
120
|
+
not_creator: 1,
|
|
121
|
+
human_only: 2,
|
|
115
122
|
};
|
|
116
123
|
export function resolveInteractionPolicy(args) {
|
|
117
124
|
const kindGovernance = args.governance[args.kind];
|
|
118
|
-
const
|
|
125
|
+
const requestedPolicyInput = args.requested
|
|
119
126
|
?? kindGovernance?.defaultPolicy
|
|
120
127
|
?? DEFAULT_RESOLVER_POLICY_BY_KIND[args.kind];
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
return;
|
|
129
|
-
if (!actor.runId)
|
|
130
|
-
throw forbidden("Agent run id required to resolve an issue-thread interaction");
|
|
131
|
-
if (current.kind === "request_confirmation"
|
|
132
|
-
&& current.payload
|
|
133
|
-
&& typeof current.payload === "object"
|
|
134
|
-
&& "toolAction" in current.payload
|
|
135
|
-
&& current.payload.toolAction !== undefined) {
|
|
136
|
-
throw forbidden("Tool-action confirmations are always board-only");
|
|
137
|
-
}
|
|
138
|
-
if (actor.reviewVerdictAuthorized && isRequestConfirmationLikeKind(current.kind)) {
|
|
139
|
-
assertAgentInteractionActorAllowed(current, actor);
|
|
140
|
-
return;
|
|
128
|
+
const requestedResolverPolicy = normalizeIssueThreadInteractionResolverPolicy(requestedPolicyInput);
|
|
129
|
+
const resolverPolicyProvenance = args.requested === undefined ? "inherited" : "explicit";
|
|
130
|
+
let effectiveResolverPolicy = requestedResolverPolicy;
|
|
131
|
+
let effectiveResolverPolicySource = "requested";
|
|
132
|
+
if (args.hasToolAction) {
|
|
133
|
+
effectiveResolverPolicy = "human_only";
|
|
134
|
+
effectiveResolverPolicySource = "governed_action";
|
|
141
135
|
}
|
|
142
|
-
if (
|
|
143
|
-
|
|
136
|
+
else if (kindGovernance?.cap) {
|
|
137
|
+
const cap = normalizeIssueThreadInteractionResolverPolicy(kindGovernance.cap);
|
|
138
|
+
if (RESOLVER_POLICY_RESTRICTION_RANK[cap] > RESOLVER_POLICY_RESTRICTION_RANK[effectiveResolverPolicy]) {
|
|
139
|
+
effectiveResolverPolicy = cap;
|
|
140
|
+
effectiveResolverPolicySource = "company_cap";
|
|
141
|
+
}
|
|
144
142
|
}
|
|
145
|
-
|
|
143
|
+
return {
|
|
144
|
+
requestedResolverPolicy,
|
|
145
|
+
effectiveResolverPolicy,
|
|
146
|
+
resolverPolicyProvenance,
|
|
147
|
+
effectiveResolverPolicySource,
|
|
148
|
+
};
|
|
146
149
|
}
|
|
147
|
-
function
|
|
148
|
-
if (
|
|
149
|
-
|
|
150
|
+
function resolverActor(actor) {
|
|
151
|
+
if (actor.systemId)
|
|
152
|
+
return { type: "system", systemId: actor.systemId };
|
|
153
|
+
if (actor.agentId) {
|
|
154
|
+
return { type: "agent", agentId: actor.agentId, runId: actor.runId };
|
|
150
155
|
}
|
|
151
|
-
if (
|
|
152
|
-
|
|
156
|
+
if (actor.userId)
|
|
157
|
+
return { type: "user", userId: actor.userId };
|
|
158
|
+
// Missing principals must fail closed. Internal maintenance paths that are
|
|
159
|
+
// intentionally system-owned provide an explicit systemId.
|
|
160
|
+
return { type: "agent", agentId: null, runId: null };
|
|
161
|
+
}
|
|
162
|
+
function assertInteractionResolutionAllowed(current, actor) {
|
|
163
|
+
return assertIssueThreadInteractionResolverAudience({
|
|
164
|
+
actor: resolverActor(actor),
|
|
165
|
+
interaction: current,
|
|
166
|
+
additionalRestriction: actor.resolverPolicyRestriction,
|
|
167
|
+
governedAction: current.kind === "request_confirmation"
|
|
168
|
+
&& current.payload !== null
|
|
169
|
+
&& typeof current.payload === "object"
|
|
170
|
+
&& "toolAction" in current.payload
|
|
171
|
+
&& current.payload.toolAction !== undefined,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
async function assertRequestConfirmationResolutionAllowedUnderLock(tx, issue, interaction, actor) {
|
|
175
|
+
if (isTerminalIssueStatus(issue.status)) {
|
|
176
|
+
throw conflict("Interaction is no longer actionable because the issue is closed");
|
|
153
177
|
}
|
|
154
|
-
|
|
155
|
-
|
|
178
|
+
const isReviewVerdict = issue.status === "in_review"
|
|
179
|
+
&& isRequestConfirmationLikeKind(interaction.kind)
|
|
180
|
+
&& await isIssueReviewVerdictInteraction(tx, { issue, interaction });
|
|
181
|
+
assertInteractionResolutionAllowed(interaction, actor);
|
|
182
|
+
if (!isReviewVerdict)
|
|
183
|
+
return;
|
|
184
|
+
const verdictActor = actor.agentId
|
|
185
|
+
? { type: "agent", id: actor.agentId }
|
|
186
|
+
: actor.userId
|
|
187
|
+
? { type: "user", id: actor.userId }
|
|
188
|
+
: null;
|
|
189
|
+
if (!verdictActor) {
|
|
190
|
+
throw forbidden("A review verdict requires an authenticated agent or user");
|
|
156
191
|
}
|
|
192
|
+
await assertIssueReviewVerdictActorAllowed(tx, {
|
|
193
|
+
issue,
|
|
194
|
+
actor: verdictActor,
|
|
195
|
+
});
|
|
157
196
|
}
|
|
158
197
|
const REQUEST_CONFIRMATION_INTERACTION_KINDS = [
|
|
159
198
|
"request_confirmation",
|
|
@@ -218,15 +257,30 @@ function parseStoredInteractionResult(schema, raw, row) {
|
|
|
218
257
|
return null;
|
|
219
258
|
}
|
|
220
259
|
function hydrateInteraction(row) {
|
|
260
|
+
const storedRequestedResolverPolicy = row.requestedResolverPolicy;
|
|
261
|
+
const storedEffectiveResolverPolicy = row.effectiveResolverPolicy;
|
|
262
|
+
const resolverPolicyProvenance = row.resolverPolicyProvenance
|
|
263
|
+
?? (storedRequestedResolverPolicy === "board_only" || storedRequestedResolverPolicy === "board_or_agents"
|
|
264
|
+
? "legacy_inherited_restriction"
|
|
265
|
+
: "inherited");
|
|
266
|
+
const canonicalizeStoredPolicy = (policy) => canonicalizeStoredResolverPolicy(policy, resolverPolicyProvenance);
|
|
267
|
+
const requestedResolverPolicy = canonicalizeStoredPolicy(storedRequestedResolverPolicy);
|
|
268
|
+
const effectiveResolverPolicy = canonicalizeStoredPolicy(storedEffectiveResolverPolicy);
|
|
221
269
|
const base = {
|
|
222
270
|
...row,
|
|
223
271
|
idempotencyKey: row.idempotencyKey ?? null,
|
|
224
272
|
addresseeAgentId: row.addresseeAgentId ?? null,
|
|
225
273
|
status: row.status,
|
|
226
274
|
continuationPolicy: row.continuationPolicy,
|
|
227
|
-
resolverPolicy:
|
|
228
|
-
requestedResolverPolicy
|
|
229
|
-
effectiveResolverPolicy
|
|
275
|
+
resolverPolicy: requestedResolverPolicy,
|
|
276
|
+
requestedResolverPolicy,
|
|
277
|
+
effectiveResolverPolicy,
|
|
278
|
+
resolverPolicyProvenance,
|
|
279
|
+
effectiveResolverPolicySource: row.effectiveResolverPolicySource ?? "requested",
|
|
280
|
+
legacyResolverPolicyAliases: {
|
|
281
|
+
requested: legacyIssueThreadInteractionResolverPolicyAlias(requestedResolverPolicy),
|
|
282
|
+
effective: legacyIssueThreadInteractionResolverPolicyAlias(effectiveResolverPolicy),
|
|
283
|
+
},
|
|
230
284
|
};
|
|
231
285
|
switch (row.kind) {
|
|
232
286
|
case "suggest_tasks":
|
|
@@ -277,6 +331,29 @@ async function touchIssue(db, issueId) {
|
|
|
277
331
|
function isTerminalIssueStatus(status) {
|
|
278
332
|
return status === "done" || status === "cancelled";
|
|
279
333
|
}
|
|
334
|
+
function interactionNotFoundError() {
|
|
335
|
+
return notFound("Interaction not found", { code: "interaction_not_found" });
|
|
336
|
+
}
|
|
337
|
+
function interactionIssueClosedError() {
|
|
338
|
+
return issueThreadInteractionResolutionError(409, "interaction_issue_closed", "Interaction is no longer actionable because the issue is closed");
|
|
339
|
+
}
|
|
340
|
+
function interactionAlreadyResolvedError() {
|
|
341
|
+
return issueThreadInteractionResolutionError(409, "interaction_already_resolved", "Interaction has already been resolved");
|
|
342
|
+
}
|
|
343
|
+
function interactionTerminalError(row) {
|
|
344
|
+
const result = row.result && typeof row.result === "object" && !Array.isArray(row.result)
|
|
345
|
+
? row.result
|
|
346
|
+
: null;
|
|
347
|
+
if (result?.outcome === "stale_target") {
|
|
348
|
+
return issueThreadInteractionResolutionError(409, "interaction_stale_target", "Interaction target is stale");
|
|
349
|
+
}
|
|
350
|
+
if (result?.outcome === "superseded_by_comment" || result?.outcome === "superseded_by_newer_request") {
|
|
351
|
+
return issueThreadInteractionResolutionError(409, "interaction_superseded", "Interaction has been superseded");
|
|
352
|
+
}
|
|
353
|
+
if (result?.outcome === "issue_closed")
|
|
354
|
+
return interactionIssueClosedError();
|
|
355
|
+
return issueThreadInteractionResolutionError(409, "interaction_already_resolved", "Interaction has already been resolved");
|
|
356
|
+
}
|
|
280
357
|
function shouldReturnAcceptedConfirmationToCreatorAgent(args) {
|
|
281
358
|
if (!isRequestConfirmationLikeKind(args.current.kind))
|
|
282
359
|
return false;
|
|
@@ -383,6 +460,20 @@ function buildSupersededByNewerRequestResult(replacementInteractionId) {
|
|
|
383
460
|
supersededByInteractionId: replacementInteractionId,
|
|
384
461
|
};
|
|
385
462
|
}
|
|
463
|
+
// An agent that posts a fresh ask_user_questions while its own earlier ones on
|
|
464
|
+
// the same issue are still pending has replaced them — the newer card carries
|
|
465
|
+
// the real ask, so the stale siblings auto-expire (PAP-437). Mirrors the
|
|
466
|
+
// `superseded_by_comment` shape (ask_user_questions results key expiry off
|
|
467
|
+
// `expirationReason`, not `outcome`) so the UI can hide them cleanly.
|
|
468
|
+
function buildSupersededByNewerInteractionResult(replacementInteractionId) {
|
|
469
|
+
return {
|
|
470
|
+
version: 1,
|
|
471
|
+
answers: [],
|
|
472
|
+
expirationReason: "superseded_by_newer_interaction",
|
|
473
|
+
supersededByInteractionId: replacementInteractionId,
|
|
474
|
+
summaryMarkdown: null,
|
|
475
|
+
};
|
|
476
|
+
}
|
|
386
477
|
function buildAdministrativeOutcomeResult(row, outcome, reason = null) {
|
|
387
478
|
if (row.kind === "ask_user_questions") {
|
|
388
479
|
return { version: 1, outcome, reason, answers: [], summaryMarkdown: null };
|
|
@@ -554,12 +645,24 @@ async function emitInteractionResolvedTelemetry(db, interaction, args) {
|
|
|
554
645
|
...buildInteractionResolvedCounts(interaction, {
|
|
555
646
|
createdTaskCount: args?.createdTaskCount,
|
|
556
647
|
}),
|
|
648
|
+
legacyInheritedRestriction: interaction.resolverPolicyProvenance === "legacy_inherited_restriction",
|
|
557
649
|
});
|
|
558
650
|
}
|
|
559
651
|
catch (error) {
|
|
560
652
|
console.error("[paperclip] Failed to emit interaction.resolved telemetry", error);
|
|
561
653
|
}
|
|
562
654
|
}
|
|
655
|
+
function emitInteractionCreatedTelemetry(args) {
|
|
656
|
+
const telemetryClient = getTelemetryClient();
|
|
657
|
+
if (!telemetryClient)
|
|
658
|
+
return;
|
|
659
|
+
try {
|
|
660
|
+
trackInteractionCreated(telemetryClient, args);
|
|
661
|
+
}
|
|
662
|
+
catch (error) {
|
|
663
|
+
console.error("[paperclip] Failed to emit interaction.created telemetry", error);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
563
666
|
async function emitResolvedInteractionsTelemetry(db, interactions) {
|
|
564
667
|
if (interactions.length === 0 || !getTelemetryClient())
|
|
565
668
|
return;
|
|
@@ -606,7 +709,7 @@ function buildTaskCreationOrder(tasks) {
|
|
|
606
709
|
}
|
|
607
710
|
return ordered;
|
|
608
711
|
}
|
|
609
|
-
function resolveSelectedSuggestedTasks(args) {
|
|
712
|
+
export function resolveSelectedSuggestedTasks(args) {
|
|
610
713
|
const taskByClientKey = new Map(args.interaction.payload.tasks.map((task) => [task.clientKey, task]));
|
|
611
714
|
const selectedClientKeys = args.selectedClientKeys ?? args.interaction.payload.tasks.map((task) => task.clientKey);
|
|
612
715
|
const selectedClientKeySet = new Set();
|
|
@@ -660,9 +763,6 @@ function resolveSelectedCheckboxConfirmationOptions(args) {
|
|
|
660
763
|
.map((option) => option.id);
|
|
661
764
|
}
|
|
662
765
|
function resolveRequestItemVerdictSubmissions(args) {
|
|
663
|
-
if (!args.actor.userId) {
|
|
664
|
-
throw unprocessable("request_item_verdicts submissions require a user actor");
|
|
665
|
-
}
|
|
666
766
|
const existingItems = args.interaction.result?.items ?? [];
|
|
667
767
|
const existingById = new Map(existingItems.map((item) => [item.id, item]));
|
|
668
768
|
const payloadItemIds = new Set(args.interaction.payload.items.map((item) => item.id));
|
|
@@ -691,7 +791,9 @@ function resolveRequestItemVerdictSubmissions(args) {
|
|
|
691
791
|
id: submitted.id,
|
|
692
792
|
verdict: submitted.verdict,
|
|
693
793
|
...(reason ? { reason } : {}),
|
|
694
|
-
resolvedByUserId: args.actor.userId,
|
|
794
|
+
...(args.actor.userId ? { resolvedByUserId: args.actor.userId } : {}),
|
|
795
|
+
...(args.actor.agentId ? { resolvedByAgentId: args.actor.agentId } : {}),
|
|
796
|
+
...(args.actor.runId ? { resolvedByRunId: args.actor.runId } : {}),
|
|
695
797
|
resolvedAt: args.now,
|
|
696
798
|
});
|
|
697
799
|
newlyResolvedItemIds.push(submitted.id);
|
|
@@ -859,7 +961,7 @@ async function expireStaleRequestConfirmationTarget(db, args) {
|
|
|
859
961
|
.where(and(eq(issueThreadInteractions.id, args.row.id), eq(issueThreadInteractions.status, "pending")))
|
|
860
962
|
.returning();
|
|
861
963
|
if (!updated) {
|
|
862
|
-
throw
|
|
964
|
+
throw issueThreadInteractionResolutionError(409, "interaction_already_resolved", "Interaction has already been resolved");
|
|
863
965
|
}
|
|
864
966
|
await touchIssue(db, args.row.issueId);
|
|
865
967
|
const expired = hydrateInteraction(updated);
|
|
@@ -906,7 +1008,7 @@ export function issueThreadInteractionService(db, opts = {}) {
|
|
|
906
1008
|
.where(eq(issueThreadInteractions.id, interactionId))
|
|
907
1009
|
.then((rows) => rows[0] ?? null);
|
|
908
1010
|
if (!current || current.companyId !== issue.companyId || current.issueId !== issue.id) {
|
|
909
|
-
throw
|
|
1011
|
+
throw interactionNotFoundError();
|
|
910
1012
|
}
|
|
911
1013
|
return hydrateInteraction(current);
|
|
912
1014
|
}
|
|
@@ -938,32 +1040,75 @@ export function issueThreadInteractionService(db, opts = {}) {
|
|
|
938
1040
|
.where(eq(issueThreadInteractions.id, args.interactionId))
|
|
939
1041
|
.then((rows) => rows[0] ?? null);
|
|
940
1042
|
if (!current)
|
|
941
|
-
throw
|
|
1043
|
+
throw interactionNotFoundError();
|
|
942
1044
|
if (current.companyId !== args.issue.companyId || current.issueId !== args.issue.id) {
|
|
943
|
-
throw
|
|
1045
|
+
throw interactionNotFoundError();
|
|
1046
|
+
}
|
|
1047
|
+
if (args.issue.status && isTerminalIssueStatus(args.issue.status)) {
|
|
1048
|
+
throw interactionIssueClosedError();
|
|
944
1049
|
}
|
|
945
1050
|
if (current.status !== "pending") {
|
|
946
|
-
throw
|
|
1051
|
+
throw interactionTerminalError(current);
|
|
947
1052
|
}
|
|
948
1053
|
return current;
|
|
949
1054
|
}
|
|
1055
|
+
function assertIssueOpenForInteractionResolution(issue) {
|
|
1056
|
+
if (issue.status && isTerminalIssueStatus(issue.status)) {
|
|
1057
|
+
throw interactionIssueClosedError();
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
950
1060
|
async function acceptRequestConfirmation(args) {
|
|
951
1061
|
const expired = await expireStaleRequestConfirmationTarget(db, {
|
|
952
1062
|
row: args.current,
|
|
953
1063
|
actor: args.actor,
|
|
954
1064
|
});
|
|
955
|
-
if (expired)
|
|
956
|
-
|
|
957
|
-
}
|
|
958
|
-
const interaction = hydrateInteraction(args.current);
|
|
959
|
-
const selectedOptionIds = interaction.kind === "request_checkbox_confirmation"
|
|
960
|
-
? resolveSelectedCheckboxConfirmationOptions({
|
|
961
|
-
interaction,
|
|
962
|
-
selectedOptionIds: args.input.selectedOptionIds,
|
|
963
|
-
})
|
|
964
|
-
: undefined;
|
|
1065
|
+
if (expired)
|
|
1066
|
+
throw interactionTerminalError({ status: expired.status, result: expired.result });
|
|
965
1067
|
const now = new Date();
|
|
966
1068
|
const result = await db.transaction(async (tx) => {
|
|
1069
|
+
// Lock the issue before claiming the interaction. Policy mutations and
|
|
1070
|
+
// review transitions use the same issue-row lock, so the authoritative
|
|
1071
|
+
// review policy and requester are stable through the verdict write.
|
|
1072
|
+
const issueContext = await tx
|
|
1073
|
+
.select({
|
|
1074
|
+
id: issues.id,
|
|
1075
|
+
companyId: issues.companyId,
|
|
1076
|
+
status: issues.status,
|
|
1077
|
+
assigneeAgentId: issues.assigneeAgentId,
|
|
1078
|
+
assigneeUserId: issues.assigneeUserId,
|
|
1079
|
+
reviewPolicy: issues.reviewPolicy,
|
|
1080
|
+
createdByAgentId: issues.createdByAgentId,
|
|
1081
|
+
createdByUserId: issues.createdByUserId,
|
|
1082
|
+
})
|
|
1083
|
+
.from(issues)
|
|
1084
|
+
.where(eq(issues.id, args.issue.id))
|
|
1085
|
+
.for("update")
|
|
1086
|
+
.then((rows) => rows[0] ?? null);
|
|
1087
|
+
if (!issueContext || issueContext.companyId !== args.issue.companyId) {
|
|
1088
|
+
throw notFound("Issue not found");
|
|
1089
|
+
}
|
|
1090
|
+
const lockedCurrent = await tx
|
|
1091
|
+
.select()
|
|
1092
|
+
.from(issueThreadInteractions)
|
|
1093
|
+
.where(eq(issueThreadInteractions.id, args.current.id))
|
|
1094
|
+
.for("update")
|
|
1095
|
+
.then((rows) => rows[0] ?? null);
|
|
1096
|
+
if (!lockedCurrent
|
|
1097
|
+
|| lockedCurrent.companyId !== args.issue.companyId
|
|
1098
|
+
|| lockedCurrent.issueId !== args.issue.id) {
|
|
1099
|
+
throw notFound("Interaction not found");
|
|
1100
|
+
}
|
|
1101
|
+
if (lockedCurrent.status !== "pending") {
|
|
1102
|
+
throw issueThreadInteractionResolutionError(409, "interaction_already_resolved", "Interaction has already been resolved");
|
|
1103
|
+
}
|
|
1104
|
+
await assertRequestConfirmationResolutionAllowedUnderLock(tx, issueContext, lockedCurrent, args.actor);
|
|
1105
|
+
const interaction = hydrateInteraction(lockedCurrent);
|
|
1106
|
+
const selectedOptionIds = interaction.kind === "request_checkbox_confirmation"
|
|
1107
|
+
? resolveSelectedCheckboxConfirmationOptions({
|
|
1108
|
+
interaction,
|
|
1109
|
+
selectedOptionIds: args.input.selectedOptionIds,
|
|
1110
|
+
})
|
|
1111
|
+
: undefined;
|
|
967
1112
|
const [updated] = await tx
|
|
968
1113
|
.update(issueThreadInteractions)
|
|
969
1114
|
.set({
|
|
@@ -979,35 +1124,21 @@ export function issueThreadInteractionService(db, opts = {}) {
|
|
|
979
1124
|
resolvedAt: now,
|
|
980
1125
|
updatedAt: now,
|
|
981
1126
|
})
|
|
982
|
-
.where(and(eq(issueThreadInteractions.id,
|
|
1127
|
+
.where(and(eq(issueThreadInteractions.id, lockedCurrent.id), eq(issueThreadInteractions.status, "pending")))
|
|
983
1128
|
.returning();
|
|
984
1129
|
if (!updated) {
|
|
985
|
-
throw
|
|
986
|
-
}
|
|
987
|
-
const issueContext = await tx
|
|
988
|
-
.select({
|
|
989
|
-
id: issues.id,
|
|
990
|
-
companyId: issues.companyId,
|
|
991
|
-
status: issues.status,
|
|
992
|
-
assigneeAgentId: issues.assigneeAgentId,
|
|
993
|
-
assigneeUserId: issues.assigneeUserId,
|
|
994
|
-
})
|
|
995
|
-
.from(issues)
|
|
996
|
-
.where(eq(issues.id, args.issue.id))
|
|
997
|
-
.then((rows) => rows[0] ?? null);
|
|
998
|
-
if (!issueContext || issueContext.companyId !== args.issue.companyId) {
|
|
999
|
-
throw notFound("Issue not found");
|
|
1130
|
+
throw issueThreadInteractionResolutionError(409, "interaction_already_resolved", "Interaction has already been resolved");
|
|
1000
1131
|
}
|
|
1001
1132
|
let continuationIssue = null;
|
|
1002
1133
|
if (shouldReturnAcceptedConfirmationToCreatorAgent({
|
|
1003
1134
|
issue: issueContext,
|
|
1004
|
-
current:
|
|
1135
|
+
current: lockedCurrent,
|
|
1005
1136
|
actor: args.actor,
|
|
1006
1137
|
})) {
|
|
1007
1138
|
const returnStatus = issueContext.status === "blocked" ? "blocked" : "todo";
|
|
1008
1139
|
const returnedIssue = await issueService(db).update(args.issue.id, {
|
|
1009
1140
|
status: returnStatus,
|
|
1010
|
-
assigneeAgentId:
|
|
1141
|
+
assigneeAgentId: lockedCurrent.createdByAgentId,
|
|
1011
1142
|
assigneeUserId: null,
|
|
1012
1143
|
actorAgentId: args.actor.agentId ?? null,
|
|
1013
1144
|
actorUserId: args.actor.userId ?? null,
|
|
@@ -1035,12 +1166,12 @@ export function issueThreadInteractionService(db, opts = {}) {
|
|
|
1035
1166
|
entityType: "issue",
|
|
1036
1167
|
entityId: args.issue.id,
|
|
1037
1168
|
details: {
|
|
1038
|
-
interactionId:
|
|
1039
|
-
interactionKind:
|
|
1169
|
+
interactionId: lockedCurrent.id,
|
|
1170
|
+
interactionKind: lockedCurrent.kind,
|
|
1040
1171
|
interactionStatus: "accepted",
|
|
1041
1172
|
resolutionActorKind: "system",
|
|
1042
|
-
requestedResolverPolicy:
|
|
1043
|
-
effectiveResolverPolicy:
|
|
1173
|
+
requestedResolverPolicy: lockedCurrent.requestedResolverPolicy,
|
|
1174
|
+
effectiveResolverPolicy: lockedCurrent.effectiveResolverPolicy,
|
|
1044
1175
|
...(args.actor.resolutionDetails ?? {}),
|
|
1045
1176
|
},
|
|
1046
1177
|
});
|
|
@@ -1058,36 +1189,71 @@ export function issueThreadInteractionService(db, opts = {}) {
|
|
|
1058
1189
|
row: args.current,
|
|
1059
1190
|
actor: args.actor,
|
|
1060
1191
|
});
|
|
1061
|
-
if (expired)
|
|
1062
|
-
|
|
1063
|
-
}
|
|
1192
|
+
if (expired)
|
|
1193
|
+
throw interactionTerminalError({ status: expired.status, result: expired.result });
|
|
1064
1194
|
const interaction = hydrateInteraction(args.current);
|
|
1065
1195
|
const reason = args.input.reason?.trim() ?? "";
|
|
1066
1196
|
if (interaction.payload.rejectRequiresReason === true && reason.length === 0) {
|
|
1067
1197
|
throw unprocessable("A decline reason is required for this confirmation");
|
|
1068
1198
|
}
|
|
1069
1199
|
const now = new Date();
|
|
1070
|
-
const
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
.
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1200
|
+
const updated = await db.transaction(async (tx) => {
|
|
1201
|
+
const issueContext = await tx
|
|
1202
|
+
.select({
|
|
1203
|
+
id: issues.id,
|
|
1204
|
+
companyId: issues.companyId,
|
|
1205
|
+
status: issues.status,
|
|
1206
|
+
assigneeAgentId: issues.assigneeAgentId,
|
|
1207
|
+
assigneeUserId: issues.assigneeUserId,
|
|
1208
|
+
reviewPolicy: issues.reviewPolicy,
|
|
1209
|
+
createdByAgentId: issues.createdByAgentId,
|
|
1210
|
+
createdByUserId: issues.createdByUserId,
|
|
1211
|
+
})
|
|
1212
|
+
.from(issues)
|
|
1213
|
+
.where(eq(issues.id, args.issue.id))
|
|
1214
|
+
.for("update")
|
|
1215
|
+
.then((rows) => rows[0] ?? null);
|
|
1216
|
+
if (!issueContext || issueContext.companyId !== args.issue.companyId) {
|
|
1217
|
+
throw notFound("Issue not found");
|
|
1218
|
+
}
|
|
1219
|
+
const lockedCurrent = await tx
|
|
1220
|
+
.select()
|
|
1221
|
+
.from(issueThreadInteractions)
|
|
1222
|
+
.where(eq(issueThreadInteractions.id, args.current.id))
|
|
1223
|
+
.for("update")
|
|
1224
|
+
.then((rows) => rows[0] ?? null);
|
|
1225
|
+
if (!lockedCurrent
|
|
1226
|
+
|| lockedCurrent.companyId !== args.issue.companyId
|
|
1227
|
+
|| lockedCurrent.issueId !== args.issue.id) {
|
|
1228
|
+
throw notFound("Interaction not found");
|
|
1229
|
+
}
|
|
1230
|
+
if (lockedCurrent.status !== "pending") {
|
|
1231
|
+
throw issueThreadInteractionResolutionError(409, "interaction_already_resolved", "Interaction has already been resolved");
|
|
1232
|
+
}
|
|
1233
|
+
await assertRequestConfirmationResolutionAllowedUnderLock(tx, issueContext, lockedCurrent, args.actor);
|
|
1234
|
+
const [resolved] = await tx
|
|
1235
|
+
.update(issueThreadInteractions)
|
|
1236
|
+
.set({
|
|
1237
|
+
status: "rejected",
|
|
1238
|
+
result: {
|
|
1239
|
+
version: 1,
|
|
1240
|
+
outcome: "rejected",
|
|
1241
|
+
reason: reason || null,
|
|
1242
|
+
},
|
|
1243
|
+
resolvedByAgentId: args.actor.agentId ?? null,
|
|
1244
|
+
resolvedByRunId: args.actor.runId ?? null,
|
|
1245
|
+
resolvedByUserId: args.actor.userId ?? null,
|
|
1246
|
+
resolvedAt: now,
|
|
1247
|
+
updatedAt: now,
|
|
1248
|
+
})
|
|
1249
|
+
.where(and(eq(issueThreadInteractions.id, lockedCurrent.id), eq(issueThreadInteractions.status, "pending")))
|
|
1250
|
+
.returning();
|
|
1251
|
+
if (!resolved) {
|
|
1252
|
+
throw issueThreadInteractionResolutionError(409, "interaction_already_resolved", "Interaction has already been resolved");
|
|
1253
|
+
}
|
|
1254
|
+
await touchIssue(tx, args.issue.id);
|
|
1255
|
+
return resolved;
|
|
1256
|
+
});
|
|
1091
1257
|
const rejected = hydrateInteraction(updated);
|
|
1092
1258
|
await emitInteractionResolvedTelemetry(db, rejected);
|
|
1093
1259
|
return rejected;
|
|
@@ -1212,12 +1378,26 @@ export function issueThreadInteractionService(db, opts = {}) {
|
|
|
1212
1378
|
return { checked: rows.length, candidates: eligible.length, accepted, woken };
|
|
1213
1379
|
},
|
|
1214
1380
|
listForIssue: async (issueId) => {
|
|
1215
|
-
const rows = await
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1381
|
+
const [rows, issueStatus] = await Promise.all([
|
|
1382
|
+
db
|
|
1383
|
+
.select()
|
|
1384
|
+
.from(issueThreadInteractions)
|
|
1385
|
+
.where(eq(issueThreadInteractions.issueId, issueId))
|
|
1386
|
+
.orderBy(asc(issueThreadInteractions.createdAt), asc(issueThreadInteractions.id)),
|
|
1387
|
+
db
|
|
1388
|
+
.select({ status: issues.status })
|
|
1389
|
+
.from(issues)
|
|
1390
|
+
.where(eq(issues.id, issueId))
|
|
1391
|
+
.then((issueRows) => issueRows[0]?.status ?? null),
|
|
1392
|
+
]);
|
|
1393
|
+
return rows.map((row) => hydrateInteraction(issueStatus && isTerminalIssueStatus(issueStatus) && row.status === "pending"
|
|
1394
|
+
? {
|
|
1395
|
+
...row,
|
|
1396
|
+
status: "expired",
|
|
1397
|
+
result: buildAdministrativeOutcomeResult(row, "issue_closed"),
|
|
1398
|
+
resolvedAt: row.updatedAt,
|
|
1399
|
+
}
|
|
1400
|
+
: row));
|
|
1221
1401
|
},
|
|
1222
1402
|
getById: async (interactionId) => {
|
|
1223
1403
|
const row = await db
|
|
@@ -1320,6 +1500,7 @@ export function issueThreadInteractionService(db, opts = {}) {
|
|
|
1320
1500
|
},
|
|
1321
1501
|
create: async (issue, input, actor) => {
|
|
1322
1502
|
const data = normalizeCreateInteractionInput(createIssueThreadInteractionSchema.parse(input));
|
|
1503
|
+
const usedDeprecatedResolverPolicyAlias = data.resolverPolicy === "board_or_agents" || data.resolverPolicy === "board_only";
|
|
1323
1504
|
const governance = await db
|
|
1324
1505
|
.select({ interactionResolverGovernance: companies.interactionResolverGovernance })
|
|
1325
1506
|
.from(companies)
|
|
@@ -1444,6 +1625,8 @@ export function issueThreadInteractionService(db, opts = {}) {
|
|
|
1444
1625
|
continuationPolicy: data.continuationPolicy,
|
|
1445
1626
|
requestedResolverPolicy: policy.requestedResolverPolicy,
|
|
1446
1627
|
effectiveResolverPolicy: policy.effectiveResolverPolicy,
|
|
1628
|
+
resolverPolicyProvenance: policy.resolverPolicyProvenance,
|
|
1629
|
+
effectiveResolverPolicySource: policy.effectiveResolverPolicySource,
|
|
1447
1630
|
idempotencyKey: data.idempotencyKey ?? null,
|
|
1448
1631
|
sourceCommentId: data.sourceCommentId ?? null,
|
|
1449
1632
|
sourceRunId: data.sourceRunId ?? null,
|
|
@@ -1455,15 +1638,25 @@ export function issueThreadInteractionService(db, opts = {}) {
|
|
|
1455
1638
|
payload: data.payload,
|
|
1456
1639
|
})
|
|
1457
1640
|
.returning();
|
|
1458
|
-
|
|
1641
|
+
// An agent replacing its own still-pending card supersedes the older
|
|
1642
|
+
// one so the thread never accumulates stale sibling cards. This covers
|
|
1643
|
+
// request_confirmation drafts and ask_user_questions (PAP-437: probe
|
|
1644
|
+
// question cards that agents never withdrew). Each kind keeps its own
|
|
1645
|
+
// result shape. Scoped strictly to the same agent + issue + kind, so
|
|
1646
|
+
// other agents' or other kinds' pending cards are untouched.
|
|
1647
|
+
const canSupersedeSiblingCards = data.kind === "request_confirmation" || data.kind === "ask_user_questions";
|
|
1648
|
+
if (!actor.agentId || !canSupersedeSiblingCards) {
|
|
1459
1649
|
return { row, supersededRows: [] };
|
|
1460
1650
|
}
|
|
1461
1651
|
const now = new Date();
|
|
1652
|
+
const supersededResult = data.kind === "ask_user_questions"
|
|
1653
|
+
? buildSupersededByNewerInteractionResult(row.id)
|
|
1654
|
+
: buildSupersededByNewerRequestResult(row.id);
|
|
1462
1655
|
const supersededRows = await tx
|
|
1463
1656
|
.update(issueThreadInteractions)
|
|
1464
1657
|
.set({
|
|
1465
1658
|
status: "expired",
|
|
1466
|
-
result:
|
|
1659
|
+
result: supersededResult,
|
|
1467
1660
|
resolvedByAgentId: actor.agentId,
|
|
1468
1661
|
resolvedByUserId: actor.userId ?? null,
|
|
1469
1662
|
resolvedAt: now,
|
|
@@ -1506,12 +1699,17 @@ export function issueThreadInteractionService(db, opts = {}) {
|
|
|
1506
1699
|
if (superseded.length > 0) {
|
|
1507
1700
|
await emitResolvedInteractionsTelemetry(db, superseded.map(hydrateInteraction));
|
|
1508
1701
|
}
|
|
1509
|
-
|
|
1702
|
+
const interaction = hydrateInteraction(created);
|
|
1703
|
+
emitInteractionCreatedTelemetry({
|
|
1704
|
+
interactionKind: interaction.kind,
|
|
1705
|
+
usedDeprecatedResolverPolicyAlias,
|
|
1706
|
+
});
|
|
1707
|
+
return interaction;
|
|
1510
1708
|
},
|
|
1511
1709
|
acceptInteraction: async (issue, interactionId, input, actor) => {
|
|
1512
1710
|
const data = acceptIssueThreadInteractionSchema.parse(input);
|
|
1513
1711
|
const current = await getPendingInteractionForResolution({ issue, interactionId });
|
|
1514
|
-
|
|
1712
|
+
assertInteractionResolutionAllowed(current, actor);
|
|
1515
1713
|
switch (current.kind) {
|
|
1516
1714
|
case "suggest_tasks":
|
|
1517
1715
|
// Accepting suggest_tasks only creates follow-up issues; it does not
|
|
@@ -1551,22 +1749,26 @@ export function issueThreadInteractionService(db, opts = {}) {
|
|
|
1551
1749
|
}
|
|
1552
1750
|
},
|
|
1553
1751
|
acceptSuggestedTasks: async (issue, interactionId, input, actor) => {
|
|
1752
|
+
assertIssueOpenForInteractionResolution(issue);
|
|
1554
1753
|
const current = await db
|
|
1555
1754
|
.select()
|
|
1556
1755
|
.from(issueThreadInteractions)
|
|
1557
1756
|
.where(eq(issueThreadInteractions.id, interactionId))
|
|
1558
1757
|
.then((rows) => rows[0] ?? null);
|
|
1559
1758
|
if (!current)
|
|
1560
|
-
throw
|
|
1759
|
+
throw interactionNotFoundError();
|
|
1561
1760
|
if (current.companyId !== issue.companyId || current.issueId !== issue.id) {
|
|
1562
|
-
throw
|
|
1761
|
+
throw interactionNotFoundError();
|
|
1563
1762
|
}
|
|
1564
|
-
|
|
1763
|
+
assertInteractionResolutionAllowed(current, actor);
|
|
1565
1764
|
if (current.kind !== "suggest_tasks") {
|
|
1566
1765
|
throw unprocessable("Only suggest_tasks interactions can be accepted");
|
|
1567
1766
|
}
|
|
1568
1767
|
if (current.status !== "pending") {
|
|
1569
|
-
throw
|
|
1768
|
+
throw interactionTerminalError(current);
|
|
1769
|
+
}
|
|
1770
|
+
if (actor.agentId && actor.suggestedTaskEffectsAuthorized !== true) {
|
|
1771
|
+
throw issueThreadInteractionResolutionError(403, "interaction_governed_action_denied", "Suggested-task creation requires independent task-creation authorization");
|
|
1570
1772
|
}
|
|
1571
1773
|
const interaction = hydrateInteraction(current);
|
|
1572
1774
|
const { selectedTasks, skippedClientKeys } = resolveSelectedSuggestedTasks({
|
|
@@ -1612,7 +1814,7 @@ export function issueThreadInteractionService(db, opts = {}) {
|
|
|
1612
1814
|
.where(and(eq(issueThreadInteractions.id, interactionId), eq(issueThreadInteractions.status, "pending")))
|
|
1613
1815
|
.returning();
|
|
1614
1816
|
if (!claimed) {
|
|
1615
|
-
throw
|
|
1817
|
+
throw interactionAlreadyResolvedError();
|
|
1616
1818
|
}
|
|
1617
1819
|
for (const task of orderedTasks) {
|
|
1618
1820
|
const parentIssueId = task.parentClientKey
|
|
@@ -1686,7 +1888,7 @@ export function issueThreadInteractionService(db, opts = {}) {
|
|
|
1686
1888
|
rejectInteraction: async (issue, interactionId, input, actor) => {
|
|
1687
1889
|
const data = rejectIssueThreadInteractionSchema.parse(input);
|
|
1688
1890
|
const current = await getPendingInteractionForResolution({ issue, interactionId });
|
|
1689
|
-
|
|
1891
|
+
assertInteractionResolutionAllowed(current, actor);
|
|
1690
1892
|
switch (current.kind) {
|
|
1691
1893
|
case "suggest_tasks":
|
|
1692
1894
|
return issueThreadInteractionService(db).rejectSuggestedTasks(issue, interactionId, data, actor, current);
|
|
@@ -1703,6 +1905,7 @@ export function issueThreadInteractionService(db, opts = {}) {
|
|
|
1703
1905
|
}
|
|
1704
1906
|
},
|
|
1705
1907
|
submitItemVerdicts: async (issue, interactionId, input, actor) => {
|
|
1908
|
+
assertIssueOpenForInteractionResolution(issue);
|
|
1706
1909
|
const data = submitIssueThreadInteractionVerdictsSchema.parse(input);
|
|
1707
1910
|
const submission = await db.transaction(async (tx) => {
|
|
1708
1911
|
const current = await tx
|
|
@@ -1712,13 +1915,14 @@ export function issueThreadInteractionService(db, opts = {}) {
|
|
|
1712
1915
|
.for("update")
|
|
1713
1916
|
.then((rows) => rows[0] ?? null);
|
|
1714
1917
|
if (!current)
|
|
1715
|
-
throw
|
|
1918
|
+
throw interactionNotFoundError();
|
|
1716
1919
|
if (current.companyId !== issue.companyId || current.issueId !== issue.id) {
|
|
1717
|
-
throw
|
|
1920
|
+
throw interactionNotFoundError();
|
|
1718
1921
|
}
|
|
1719
1922
|
if (current.kind !== "request_item_verdicts") {
|
|
1720
1923
|
throw unprocessable("Only request_item_verdicts interactions can receive item verdicts");
|
|
1721
1924
|
}
|
|
1925
|
+
assertInteractionResolutionAllowed(current, actor);
|
|
1722
1926
|
const interaction = hydrateInteraction(current);
|
|
1723
1927
|
if (current.status !== "pending") {
|
|
1724
1928
|
if (current.status === "answered") {
|
|
@@ -1729,19 +1933,24 @@ export function issueThreadInteractionService(db, opts = {}) {
|
|
|
1729
1933
|
throw unprocessable(`Unknown item verdict id: ${submitted.id}`);
|
|
1730
1934
|
}
|
|
1731
1935
|
if (!resolvedIds.has(submitted.id)) {
|
|
1732
|
-
throw
|
|
1936
|
+
throw interactionTerminalError(current);
|
|
1733
1937
|
}
|
|
1734
1938
|
}
|
|
1735
1939
|
return { interaction, newlyResolvedItemIds: [], resolved: false };
|
|
1736
1940
|
}
|
|
1737
|
-
throw
|
|
1941
|
+
throw interactionTerminalError(current);
|
|
1738
1942
|
}
|
|
1739
1943
|
const expired = await expireStaleRequestConfirmationTarget(tx, {
|
|
1740
1944
|
row: current,
|
|
1741
1945
|
actor,
|
|
1742
1946
|
});
|
|
1743
1947
|
if (expired) {
|
|
1744
|
-
return {
|
|
1948
|
+
return {
|
|
1949
|
+
interaction: expired,
|
|
1950
|
+
newlyResolvedItemIds: [],
|
|
1951
|
+
resolved: false,
|
|
1952
|
+
terminalError: interactionTerminalError({ status: expired.status, result: expired.result }),
|
|
1953
|
+
};
|
|
1745
1954
|
}
|
|
1746
1955
|
const now = new Date();
|
|
1747
1956
|
const { items, complete, newlyResolvedItemIds } = resolveRequestItemVerdictSubmissions({
|
|
@@ -1773,7 +1982,7 @@ export function issueThreadInteractionService(db, opts = {}) {
|
|
|
1773
1982
|
.where(and(eq(issueThreadInteractions.id, interactionId), eq(issueThreadInteractions.status, "pending")))
|
|
1774
1983
|
.returning();
|
|
1775
1984
|
if (!updated) {
|
|
1776
|
-
throw
|
|
1985
|
+
throw interactionAlreadyResolvedError();
|
|
1777
1986
|
}
|
|
1778
1987
|
await touchIssue(tx, issue.id);
|
|
1779
1988
|
return {
|
|
@@ -1782,20 +1991,23 @@ export function issueThreadInteractionService(db, opts = {}) {
|
|
|
1782
1991
|
resolved: complete,
|
|
1783
1992
|
};
|
|
1784
1993
|
});
|
|
1994
|
+
if ("terminalError" in submission)
|
|
1995
|
+
throw submission.terminalError;
|
|
1785
1996
|
if (submission.resolved) {
|
|
1786
1997
|
await emitInteractionResolvedTelemetry(db, submission.interaction);
|
|
1787
1998
|
}
|
|
1788
1999
|
return submission;
|
|
1789
2000
|
},
|
|
1790
2001
|
rejectSuggestedTasks: async (issue, interactionId, input, actor, current) => {
|
|
2002
|
+
assertIssueOpenForInteractionResolution(issue);
|
|
1791
2003
|
if (current.companyId !== issue.companyId || current.issueId !== issue.id) {
|
|
1792
|
-
throw
|
|
2004
|
+
throw interactionNotFoundError();
|
|
1793
2005
|
}
|
|
1794
2006
|
if (current.kind !== "suggest_tasks") {
|
|
1795
2007
|
throw unprocessable("Only suggest_tasks interactions can be rejected");
|
|
1796
2008
|
}
|
|
1797
2009
|
if (current.status !== "pending") {
|
|
1798
|
-
throw
|
|
2010
|
+
throw interactionTerminalError(current);
|
|
1799
2011
|
}
|
|
1800
2012
|
const [updated] = await db
|
|
1801
2013
|
.update(issueThreadInteractions)
|
|
@@ -1814,7 +2026,7 @@ export function issueThreadInteractionService(db, opts = {}) {
|
|
|
1814
2026
|
.where(and(eq(issueThreadInteractions.id, interactionId), eq(issueThreadInteractions.status, "pending")))
|
|
1815
2027
|
.returning();
|
|
1816
2028
|
if (!updated) {
|
|
1817
|
-
throw
|
|
2029
|
+
throw interactionAlreadyResolvedError();
|
|
1818
2030
|
}
|
|
1819
2031
|
await touchIssue(db, issue.id);
|
|
1820
2032
|
const rejected = hydrateInteraction(updated);
|
|
@@ -2099,6 +2311,7 @@ export function issueThreadInteractionService(db, opts = {}) {
|
|
|
2099
2311
|
return expired;
|
|
2100
2312
|
},
|
|
2101
2313
|
withdrawInteraction: async (issue, interactionId, input, actor) => {
|
|
2314
|
+
assertIssueOpenForInteractionResolution(issue);
|
|
2102
2315
|
const data = withdrawIssueThreadInteractionSchema.parse(input);
|
|
2103
2316
|
const current = await db
|
|
2104
2317
|
.select()
|
|
@@ -2106,10 +2319,10 @@ export function issueThreadInteractionService(db, opts = {}) {
|
|
|
2106
2319
|
.where(eq(issueThreadInteractions.id, interactionId))
|
|
2107
2320
|
.then((rows) => rows[0] ?? null);
|
|
2108
2321
|
if (!current || current.companyId !== issue.companyId || current.issueId !== issue.id) {
|
|
2109
|
-
throw
|
|
2322
|
+
throw interactionNotFoundError();
|
|
2110
2323
|
}
|
|
2111
2324
|
if (current.status !== "pending")
|
|
2112
|
-
throw
|
|
2325
|
+
throw interactionTerminalError(current);
|
|
2113
2326
|
const reason = data.reason?.trim() || null;
|
|
2114
2327
|
const now = new Date();
|
|
2115
2328
|
// One transaction, linked tool action first: revoking pending/approved
|
|
@@ -2150,7 +2363,7 @@ export function issueThreadInteractionService(db, opts = {}) {
|
|
|
2150
2363
|
.where(and(eq(issueThreadInteractions.id, interactionId), eq(issueThreadInteractions.status, "pending")))
|
|
2151
2364
|
.returning();
|
|
2152
2365
|
if (!row)
|
|
2153
|
-
throw
|
|
2366
|
+
throw interactionAlreadyResolvedError();
|
|
2154
2367
|
return row;
|
|
2155
2368
|
});
|
|
2156
2369
|
await touchIssue(db, issue.id);
|
|
@@ -2159,22 +2372,23 @@ export function issueThreadInteractionService(db, opts = {}) {
|
|
|
2159
2372
|
return withdrawn;
|
|
2160
2373
|
},
|
|
2161
2374
|
answerQuestions: async (issue, interactionId, input, actor) => {
|
|
2375
|
+
assertIssueOpenForInteractionResolution(issue);
|
|
2162
2376
|
const current = await db
|
|
2163
2377
|
.select()
|
|
2164
2378
|
.from(issueThreadInteractions)
|
|
2165
2379
|
.where(eq(issueThreadInteractions.id, interactionId))
|
|
2166
2380
|
.then((rows) => rows[0] ?? null);
|
|
2167
2381
|
if (!current)
|
|
2168
|
-
throw
|
|
2382
|
+
throw interactionNotFoundError();
|
|
2169
2383
|
if (current.companyId !== issue.companyId || current.issueId !== issue.id) {
|
|
2170
|
-
throw
|
|
2384
|
+
throw interactionNotFoundError();
|
|
2171
2385
|
}
|
|
2172
|
-
|
|
2386
|
+
assertInteractionResolutionAllowed(current, actor);
|
|
2173
2387
|
if (current.kind !== "ask_user_questions") {
|
|
2174
2388
|
throw unprocessable("Only ask_user_questions interactions can be answered");
|
|
2175
2389
|
}
|
|
2176
2390
|
if (current.status !== "pending") {
|
|
2177
|
-
throw
|
|
2391
|
+
throw interactionTerminalError(current);
|
|
2178
2392
|
}
|
|
2179
2393
|
const interaction = hydrateInteraction(current);
|
|
2180
2394
|
const normalizedAnswers = normalizeQuestionAnswers({
|
|
@@ -2199,7 +2413,7 @@ export function issueThreadInteractionService(db, opts = {}) {
|
|
|
2199
2413
|
.where(and(eq(issueThreadInteractions.id, interactionId), eq(issueThreadInteractions.status, "pending")))
|
|
2200
2414
|
.returning();
|
|
2201
2415
|
if (!updated) {
|
|
2202
|
-
throw
|
|
2416
|
+
throw interactionAlreadyResolvedError();
|
|
2203
2417
|
}
|
|
2204
2418
|
await touchIssue(db, issue.id);
|
|
2205
2419
|
const answered = hydrateInteraction(updated);
|
|
@@ -2207,6 +2421,7 @@ export function issueThreadInteractionService(db, opts = {}) {
|
|
|
2207
2421
|
return answered;
|
|
2208
2422
|
},
|
|
2209
2423
|
cancelQuestions: async (issue, interactionId, input, actor) => {
|
|
2424
|
+
assertIssueOpenForInteractionResolution(issue);
|
|
2210
2425
|
const data = cancelIssueThreadInteractionSchema.parse(input);
|
|
2211
2426
|
const current = await db
|
|
2212
2427
|
.select()
|
|
@@ -2214,15 +2429,15 @@ export function issueThreadInteractionService(db, opts = {}) {
|
|
|
2214
2429
|
.where(eq(issueThreadInteractions.id, interactionId))
|
|
2215
2430
|
.then((rows) => rows[0] ?? null);
|
|
2216
2431
|
if (!current)
|
|
2217
|
-
throw
|
|
2432
|
+
throw interactionNotFoundError();
|
|
2218
2433
|
if (current.companyId !== issue.companyId || current.issueId !== issue.id) {
|
|
2219
|
-
throw
|
|
2434
|
+
throw interactionNotFoundError();
|
|
2220
2435
|
}
|
|
2221
2436
|
if (current.kind !== "ask_user_questions") {
|
|
2222
2437
|
throw unprocessable("Only ask_user_questions interactions can be cancelled");
|
|
2223
2438
|
}
|
|
2224
2439
|
if (current.status !== "pending") {
|
|
2225
|
-
throw
|
|
2440
|
+
throw interactionTerminalError(current);
|
|
2226
2441
|
}
|
|
2227
2442
|
const reason = data.reason?.trim() || null;
|
|
2228
2443
|
const [updated] = await db
|
|
@@ -2245,7 +2460,7 @@ export function issueThreadInteractionService(db, opts = {}) {
|
|
|
2245
2460
|
.where(and(eq(issueThreadInteractions.id, interactionId), eq(issueThreadInteractions.status, "pending")))
|
|
2246
2461
|
.returning();
|
|
2247
2462
|
if (!updated) {
|
|
2248
|
-
throw
|
|
2463
|
+
throw interactionAlreadyResolvedError();
|
|
2249
2464
|
}
|
|
2250
2465
|
await touchIssue(db, issue.id);
|
|
2251
2466
|
const cancelled = hydrateInteraction(updated);
|