neondeck 1.0.0-beta.4
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/CHANGELOG.md +22 -0
- package/DEVELOPMENT.md +310 -0
- package/LICENSE +21 -0
- package/README.md +128 -0
- package/SOUL.md +8 -0
- package/bin/neondeck.mjs +27 -0
- package/config/dashboard.json +208 -0
- package/config/dashboard.schema.json +207 -0
- package/dist/SOUL.md +8 -0
- package/dist/assets/app-BuuvLo98.js +3620 -0
- package/dist/assets/app-BuuvLo98.js.map +1 -0
- package/dist/assets/autopilot-CKgEx8yQ.js +68025 -0
- package/dist/assets/autopilot-CKgEx8yQ.js.map +1 -0
- package/dist/assets/briefing-DYVbJaqk.js +2 -0
- package/dist/assets/briefing-DoO-fJr1.js +22 -0
- package/dist/assets/briefing-DoO-fJr1.js.map +1 -0
- package/dist/assets/command-run-B2TWLpLL.js +20 -0
- package/dist/assets/command-run-B2TWLpLL.js.map +1 -0
- package/dist/assets/command-run-CzQra_Y7.js +2 -0
- package/dist/assets/display-assistant-CbCRBSYM.js +389 -0
- package/dist/assets/display-assistant-CbCRBSYM.js.map +1 -0
- package/dist/assets/fix-pr-ci-BlUl-2ht.js +46 -0
- package/dist/assets/fix-pr-ci-BlUl-2ht.js.map +1 -0
- package/dist/assets/fix-pr-ci-Dl1wpKXp.js +2 -0
- package/dist/assets/migrations/20260710061339_neondeck_pre_1_0_baseline/migration.sql +748 -0
- package/dist/assets/migrations/20260710061339_neondeck_pre_1_0_baseline/snapshot.json +7240 -0
- package/dist/assets/prepare-pr-worktree-CLzccRYh.js +2 -0
- package/dist/assets/prepare-pr-worktree-DxyTygiU.js +17 -0
- package/dist/assets/prepare-pr-worktree-DxyTygiU.js.map +1 -0
- package/dist/assets/push-pr-autofix-BFNSEASU.js +2 -0
- package/dist/assets/push-pr-autofix-DlVjw1IN.js +17 -0
- package/dist/assets/push-pr-autofix-DlVjw1IN.js.map +1 -0
- package/dist/assets/review-pr-for-human-B2j8wz2L.js +2 -0
- package/dist/assets/review-pr-for-human-DcGE-7wb.js +708 -0
- package/dist/assets/review-pr-for-human-DcGE-7wb.js.map +1 -0
- package/dist/assets/safety-CawD9GxD.js +2499 -0
- package/dist/assets/safety-CawD9GxD.js.map +1 -0
- package/dist/assets/scheduled-agent-instruction-CQgogBv3.js +22 -0
- package/dist/assets/scheduled-agent-instruction-CQgogBv3.js.map +1 -0
- package/dist/assets/scheduled-agent-instruction-Dr2ebZRD.js +2 -0
- package/dist/assets/scheduler-tick-Cy26qBld.js +105 -0
- package/dist/assets/scheduler-tick-Cy26qBld.js.map +1 -0
- package/dist/assets/skills/github-gh/SKILL.md +56 -0
- package/dist/assets/skills/neon-ci-fix/SKILL.md +15 -0
- package/dist/assets/skills/neon-docs-fix/SKILL.md +15 -0
- package/dist/assets/skills/neon-issue-triage/SKILL.md +13 -0
- package/dist/assets/skills/neon-pr-review/SKILL.md +19 -0
- package/dist/assets/skills/neondeck/SKILL.md +123 -0
- package/dist/assets/skills/neondeck-handoff/SKILL.md +31 -0
- package/dist/assets/triage-pr-event-BlE3GKNR.js +17 -0
- package/dist/assets/triage-pr-event-BlE3GKNR.js.map +1 -0
- package/dist/assets/triage-pr-event-hxUP587F.js +2 -0
- package/dist/assets/verify-then-push-pr-autofix-CIl5ZwZm.js +2 -0
- package/dist/assets/verify-then-push-pr-autofix-Djy0UFUv.js +75 -0
- package/dist/assets/verify-then-push-pr-autofix-Djy0UFUv.js.map +1 -0
- package/dist/config/dashboard.json +208 -0
- package/dist/config/dashboard.schema.json +207 -0
- package/dist/server.mjs +582 -0
- package/dist/server.mjs.map +1 -0
- package/dist/skills/github-gh/SKILL.md +56 -0
- package/dist/skills/neon-ci-fix/SKILL.md +15 -0
- package/dist/skills/neon-docs-fix/SKILL.md +15 -0
- package/dist/skills/neon-issue-triage/SKILL.md +13 -0
- package/dist/skills/neon-pr-review/SKILL.md +19 -0
- package/dist/skills/neondeck/SKILL.md +123 -0
- package/dist/skills/neondeck-handoff/SKILL.md +31 -0
- package/drizzle.config.ts +7 -0
- package/package.json +155 -0
- package/shared/patch-anchors.ts +78 -0
- package/src/agents/busywork-workflow.ts +24 -0
- package/src/agents/display-assistant.ts +134 -0
- package/src/agents/pr-review-assistant.ts +25 -0
- package/src/agents/scheduler-workflow.ts +20 -0
- package/src/agents/support/tools.ts +346 -0
- package/src/app.ts +1 -0
- package/src/cli/index.ts +560 -0
- package/src/cli/learning.ts +44 -0
- package/src/cli/mcp.ts +496 -0
- package/src/cli/modules.ts +107 -0
- package/src/cli/onboarding.ts +667 -0
- package/src/cli/options.ts +103 -0
- package/src/cli/output.ts +399 -0
- package/src/cli/preapprovals.ts +204 -0
- package/src/cli/prompts.ts +108 -0
- package/src/cli/types.ts +59 -0
- package/src/db.ts +41 -0
- package/src/desktop/open.ts +698 -0
- package/src/desktop/service.ts +863 -0
- package/src/domains/mcp/actions.ts +420 -0
- package/src/domains/mcp/config.ts +501 -0
- package/src/domains/mcp/format.ts +35 -0
- package/src/domains/mcp/gate.ts +188 -0
- package/src/domains/mcp/index.ts +9 -0
- package/src/domains/mcp/instructions.ts +18 -0
- package/src/domains/mcp/oauth.ts +1063 -0
- package/src/domains/mcp/policy.ts +15 -0
- package/src/domains/mcp/registry.ts +461 -0
- package/src/domains/mcp/schemas.ts +171 -0
- package/src/domains/mcp/stdio.ts +451 -0
- package/src/domains/mcp/store.ts +905 -0
- package/src/domains/mcp/tools.ts +119 -0
- package/src/lib/action-result.ts +51 -0
- package/src/lib/exec.ts +118 -0
- package/src/lib/git.ts +20 -0
- package/src/lib/report-html.ts +193 -0
- package/src/lib/sqlite.ts +51 -0
- package/src/lib/valibot.ts +33 -0
- package/src/modules/app-state/index.ts +5 -0
- package/src/modules/app-state/native-notifications.ts +76 -0
- package/src/modules/app-state/notification-events.ts +45 -0
- package/src/modules/app-state/notifications.ts +276 -0
- package/src/modules/app-state/types.ts +42 -0
- package/src/modules/app-state/workflow-summaries.ts +201 -0
- package/src/modules/autonomous-audit/index.ts +225 -0
- package/src/modules/autopilot/actions.ts +165 -0
- package/src/modules/autopilot/admissions.ts +865 -0
- package/src/modules/autopilot/approvals.ts +59 -0
- package/src/modules/autopilot/ci-fix-run.ts +1332 -0
- package/src/modules/autopilot/ci-fix.ts +692 -0
- package/src/modules/autopilot/comments.ts +426 -0
- package/src/modules/autopilot/fixtures.ts +344 -0
- package/src/modules/autopilot/github-facts.ts +303 -0
- package/src/modules/autopilot/index.ts +64 -0
- package/src/modules/autopilot/notifications.ts +271 -0
- package/src/modules/autopilot/push-support.ts +288 -0
- package/src/modules/autopilot/push.ts +661 -0
- package/src/modules/autopilot/recovery.ts +656 -0
- package/src/modules/autopilot/review-feedback.ts +651 -0
- package/src/modules/autopilot/review-support.ts +524 -0
- package/src/modules/autopilot/revision-run.ts +445 -0
- package/src/modules/autopilot/schemas.ts +485 -0
- package/src/modules/autopilot/state-mappers.ts +512 -0
- package/src/modules/autopilot/state-schemas.ts +451 -0
- package/src/modules/autopilot/state-store.ts +183 -0
- package/src/modules/autopilot/state.ts +255 -0
- package/src/modules/autopilot/triage-support.ts +234 -0
- package/src/modules/autopilot/triage.ts +156 -0
- package/src/modules/autopilot/utils.ts +145 -0
- package/src/modules/autopilot/worktree.ts +622 -0
- package/src/modules/autopilot-policy/config.ts +110 -0
- package/src/modules/autopilot-policy/index.ts +4 -0
- package/src/modules/autopilot-policy/risk.ts +359 -0
- package/src/modules/autopilot-policy/schemas.ts +284 -0
- package/src/modules/autopilot-policy/service.ts +304 -0
- package/src/modules/commands/actions.ts +112 -0
- package/src/modules/commands/handlers/index.ts +3 -0
- package/src/modules/commands/handlers/misc.ts +441 -0
- package/src/modules/commands/handlers/queue.ts +694 -0
- package/src/modules/commands/handlers/repo.ts +339 -0
- package/src/modules/commands/index.ts +5 -0
- package/src/modules/commands/registry.ts +157 -0
- package/src/modules/commands/runner.ts +152 -0
- package/src/modules/commands/schemas.ts +107 -0
- package/src/modules/commands/summaries.ts +92 -0
- package/src/modules/commands/utils.ts +113 -0
- package/src/modules/config/actions.ts +253 -0
- package/src/modules/config/events.ts +80 -0
- package/src/modules/config/files.ts +9 -0
- package/src/modules/config/history.ts +74 -0
- package/src/modules/config/index.ts +9 -0
- package/src/modules/config/mutations/dashboard.ts +319 -0
- package/src/modules/config/mutations/execution.ts +89 -0
- package/src/modules/config/mutations/models.ts +467 -0
- package/src/modules/config/mutations/providers.ts +163 -0
- package/src/modules/config/mutations/repos.ts +620 -0
- package/src/modules/config/read.ts +135 -0
- package/src/modules/config/result.ts +66 -0
- package/src/modules/config/schemas.ts +181 -0
- package/src/modules/docs-drift/index.ts +1 -0
- package/src/modules/docs-drift/service.ts +863 -0
- package/src/modules/execution/actions.ts +35 -0
- package/src/modules/execution/approvals.ts +322 -0
- package/src/modules/execution/exedev/checkouts.ts +356 -0
- package/src/modules/execution/exedev/context.ts +286 -0
- package/src/modules/execution/index.ts +7 -0
- package/src/modules/execution/policy.ts +102 -0
- package/src/modules/execution/run.ts +589 -0
- package/src/modules/execution/schemas.ts +116 -0
- package/src/modules/execution/scope.ts +137 -0
- package/src/modules/execution/store.ts +320 -0
- package/src/modules/execution/utils.ts +105 -0
- package/src/modules/execution-policy/index.ts +424 -0
- package/src/modules/flue/execution-context.ts +41 -0
- package/src/modules/github/actions.ts +334 -0
- package/src/modules/github/checks.ts +484 -0
- package/src/modules/github/client.ts +110 -0
- package/src/modules/github/comments.ts +204 -0
- package/src/modules/github/errors.ts +50 -0
- package/src/modules/github/index.ts +92 -0
- package/src/modules/github/issues.ts +116 -0
- package/src/modules/github/pr-file-cache.ts +264 -0
- package/src/modules/github/pull-requests.ts +384 -0
- package/src/modules/github/queue.ts +342 -0
- package/src/modules/github/reviews.ts +1421 -0
- package/src/modules/github/schemas.ts +794 -0
- package/src/modules/github/state-truncation.ts +31 -0
- package/src/modules/handoff/index.ts +2 -0
- package/src/modules/handoff/schemas.ts +61 -0
- package/src/modules/handoff/service.ts +587 -0
- package/src/modules/hygiene/index.ts +1 -0
- package/src/modules/hygiene/service.ts +513 -0
- package/src/modules/kilo/actions.ts +229 -0
- package/src/modules/kilo/ci-fix-run-reconcile.ts +299 -0
- package/src/modules/kilo/docs-drift-boundary.ts +178 -0
- package/src/modules/kilo/index.ts +8 -0
- package/src/modules/kilo/notifications.ts +295 -0
- package/src/modules/kilo/process.ts +637 -0
- package/src/modules/kilo/results/actions.ts +66 -0
- package/src/modules/kilo/results/gates.ts +227 -0
- package/src/modules/kilo/results/index.ts +23 -0
- package/src/modules/kilo/results/schemas.ts +112 -0
- package/src/modules/kilo/results/service.ts +512 -0
- package/src/modules/kilo/results/state.ts +508 -0
- package/src/modules/kilo/results/verify.ts +328 -0
- package/src/modules/kilo/revision-reconcile.ts +187 -0
- package/src/modules/kilo/runtime-facts.ts +128 -0
- package/src/modules/kilo/schemas.ts +265 -0
- package/src/modules/kilo/service.ts +714 -0
- package/src/modules/kilo/sessions-adapters.ts +343 -0
- package/src/modules/kilo/sessions.ts +446 -0
- package/src/modules/kilo/store.ts +689 -0
- package/src/modules/kilo/utils.ts +166 -0
- package/src/modules/learning/automation-health.ts +441 -0
- package/src/modules/learning/automation-memory.ts +179 -0
- package/src/modules/learning/index.ts +6 -0
- package/src/modules/learning/observability.ts +616 -0
- package/src/modules/learning/operator.ts +404 -0
- package/src/modules/learning/reviews/agents.ts +33 -0
- package/src/modules/learning/reviews/complete.ts +321 -0
- package/src/modules/learning/reviews/context.ts +145 -0
- package/src/modules/learning/reviews/events.ts +306 -0
- package/src/modules/learning/reviews/index.ts +6 -0
- package/src/modules/learning/reviews/pr-cadence.ts +157 -0
- package/src/modules/learning/reviews/pr-context.ts +749 -0
- package/src/modules/learning/reviews/prepare.ts +387 -0
- package/src/modules/learning/reviews/schemas.ts +184 -0
- package/src/modules/learning/reviews/store.ts +405 -0
- package/src/modules/learning/skill-patches/actions.ts +86 -0
- package/src/modules/learning/skill-patches/index.ts +3 -0
- package/src/modules/learning/skill-patches/schemas.ts +60 -0
- package/src/modules/learning/skill-patches/service.ts +571 -0
- package/src/modules/learning/skill-patches/support.ts +417 -0
- package/src/modules/memory/actions.ts +183 -0
- package/src/modules/memory/candidates.ts +525 -0
- package/src/modules/memory/events.ts +2 -0
- package/src/modules/memory/index.ts +6 -0
- package/src/modules/memory/prompt.ts +214 -0
- package/src/modules/memory/schemas.ts +170 -0
- package/src/modules/memory/service.ts +578 -0
- package/src/modules/memory/store.ts +489 -0
- package/src/modules/pr-events/actions.ts +191 -0
- package/src/modules/pr-events/index.ts +45 -0
- package/src/modules/pr-events/schemas.ts +186 -0
- package/src/modules/pr-events/service.ts +1414 -0
- package/src/modules/pr-events/target.ts +222 -0
- package/src/modules/pr-events/utils.ts +85 -0
- package/src/modules/pr-events/watermarks.ts +308 -0
- package/src/modules/pr-local-diffs/index.ts +334 -0
- package/src/modules/pr-review-assist/actions.ts +199 -0
- package/src/modules/pr-review-assist/index.ts +3 -0
- package/src/modules/pr-review-assist/schemas.ts +74 -0
- package/src/modules/pr-review-assist/service.ts +656 -0
- package/src/modules/prepared-diffs/actions.ts +132 -0
- package/src/modules/prepared-diffs/index.ts +53 -0
- package/src/modules/prepared-diffs/schemas.ts +255 -0
- package/src/modules/prepared-diffs/service.ts +777 -0
- package/src/modules/prepared-diffs/store.ts +606 -0
- package/src/modules/reports/index.ts +1 -0
- package/src/modules/reports/service.ts +322 -0
- package/src/modules/repos/index.ts +3 -0
- package/src/modules/repos/model-discovery.ts +233 -0
- package/src/modules/repos/providers.ts +194 -0
- package/src/modules/repos/registry.ts +232 -0
- package/src/modules/runtime/agent-config.ts +167 -0
- package/src/modules/runtime/doctor.ts +528 -0
- package/src/modules/runtime/env.ts +123 -0
- package/src/modules/runtime/index.ts +12 -0
- package/src/modules/runtime/local-api-auth.ts +43 -0
- package/src/modules/runtime/metrics.ts +211 -0
- package/src/modules/runtime/skills-actions.ts +141 -0
- package/src/modules/runtime/skills.ts +689 -0
- package/src/modules/runtime/soul.ts +68 -0
- package/src/modules/runtime/status-database.ts +252 -0
- package/src/modules/runtime/status-schema.ts +149 -0
- package/src/modules/runtime/status.ts +482 -0
- package/src/modules/runtime/subagents.ts +37 -0
- package/src/modules/runtime/utility-model.ts +47 -0
- package/src/modules/safety/index.ts +3 -0
- package/src/modules/safety/policy-entries.ts +2432 -0
- package/src/modules/safety/schemas.ts +63 -0
- package/src/modules/safety/service.ts +58 -0
- package/src/modules/safety/tools.ts +15 -0
- package/src/modules/scheduled-tasks/actions.ts +113 -0
- package/src/modules/scheduled-tasks/dispatch.ts +105 -0
- package/src/modules/scheduled-tasks/index.ts +6 -0
- package/src/modules/scheduled-tasks/schemas.ts +91 -0
- package/src/modules/scheduled-tasks/service.ts +296 -0
- package/src/modules/scheduled-tasks/store.ts +814 -0
- package/src/modules/scheduled-tasks/triggers.ts +87 -0
- package/src/modules/scheduler/actions.ts +37 -0
- package/src/modules/scheduler/dispatch.ts +126 -0
- package/src/modules/scheduler/index.ts +4 -0
- package/src/modules/scheduler/lease.ts +247 -0
- package/src/modules/scheduler/pr-watch-event-deltas.ts +367 -0
- package/src/modules/scheduler/pr-watch-events.ts +834 -0
- package/src/modules/scheduler/schemas.ts +79 -0
- package/src/modules/scheduler/service.ts +228 -0
- package/src/modules/scheduler/utils.ts +106 -0
- package/src/modules/scheduler/workflow-invocation.ts +40 -0
- package/src/modules/sessions/actions.ts +213 -0
- package/src/modules/sessions/active-session.ts +46 -0
- package/src/modules/sessions/agent-context.ts +146 -0
- package/src/modules/sessions/approval-nudges.ts +156 -0
- package/src/modules/sessions/events.ts +58 -0
- package/src/modules/sessions/index.ts +10 -0
- package/src/modules/sessions/queries.ts +252 -0
- package/src/modules/sessions/references.ts +113 -0
- package/src/modules/sessions/schemas.ts +263 -0
- package/src/modules/sessions/service.ts +845 -0
- package/src/modules/sessions/store.ts +559 -0
- package/src/modules/sessions/summaries.ts +129 -0
- package/src/modules/sessions/utils.ts +18 -0
- package/src/modules/watches/actions.ts +115 -0
- package/src/modules/watches/index.ts +6 -0
- package/src/modules/watches/polling.ts +245 -0
- package/src/modules/watches/references.ts +293 -0
- package/src/modules/watches/schemas.ts +156 -0
- package/src/modules/watches/service.ts +558 -0
- package/src/modules/watches/store.ts +398 -0
- package/src/modules/watches/utils.ts +51 -0
- package/src/modules/worktree-verification/index.ts +256 -0
- package/src/modules/worktrees/access.ts +129 -0
- package/src/modules/worktrees/actions.ts +108 -0
- package/src/modules/worktrees/cleanup.ts +98 -0
- package/src/modules/worktrees/errors.ts +38 -0
- package/src/modules/worktrees/git.ts +48 -0
- package/src/modules/worktrees/index.ts +5 -0
- package/src/modules/worktrees/locks.ts +192 -0
- package/src/modules/worktrees/paths.ts +209 -0
- package/src/modules/worktrees/queries.ts +31 -0
- package/src/modules/worktrees/schemas.ts +234 -0
- package/src/modules/worktrees/service.ts +657 -0
- package/src/modules/worktrees/store.ts +414 -0
- package/src/repo-edit/actions.ts +129 -0
- package/src/repo-edit/audit.ts +180 -0
- package/src/repo-edit/fuzzy-replace.ts +256 -0
- package/src/repo-edit/git.ts +784 -0
- package/src/repo-edit/index.ts +4 -0
- package/src/repo-edit/locks.ts +27 -0
- package/src/repo-edit/patch-parser.ts +212 -0
- package/src/repo-edit/patch-service.ts +572 -0
- package/src/repo-edit/path-safety.ts +294 -0
- package/src/repo-edit/schemas.ts +220 -0
- package/src/repo-edit/service.ts +488 -0
- package/src/repo-edit/support.ts +459 -0
- package/src/runtime-home/app-db/index.ts +73 -0
- package/src/runtime-home/app-db/migrate.ts +491 -0
- package/src/runtime-home/app-db/migrations/20260710061339_neondeck_pre_1_0_baseline/migration.sql +748 -0
- package/src/runtime-home/app-db/migrations/20260710061339_neondeck_pre_1_0_baseline/snapshot.json +7240 -0
- package/src/runtime-home/app-db/reconcile.ts +155 -0
- package/src/runtime-home/app-db/schema.ts +1118 -0
- package/src/runtime-home/bootstrap.ts +171 -0
- package/src/runtime-home/defaults.ts +42 -0
- package/src/runtime-home/files.ts +193 -0
- package/src/runtime-home/index.ts +68 -0
- package/src/runtime-home/paths.ts +75 -0
- package/src/runtime-home/schemas.ts +485 -0
- package/src/sandboxes/exedev.ts +676 -0
- package/src/server/app.ts +5 -0
- package/src/server/autopilot-push-dispatch.ts +326 -0
- package/src/server/create-app.ts +160 -0
- package/src/server/events/config-stream.ts +67 -0
- package/src/server/events/notification-stream.ts +49 -0
- package/src/server/events/session-stream.ts +49 -0
- package/src/server/http.ts +56 -0
- package/src/server/learning-hooks.ts +430 -0
- package/src/server/middleware.ts +81 -0
- package/src/server/routes/autopilot.ts +318 -0
- package/src/server/routes/commands.ts +12 -0
- package/src/server/routes/config.ts +124 -0
- package/src/server/routes/execution.ts +71 -0
- package/src/server/routes/github.ts +372 -0
- package/src/server/routes/handoff.ts +69 -0
- package/src/server/routes/kilo.ts +179 -0
- package/src/server/routes/learning.ts +384 -0
- package/src/server/routes/mcp.ts +301 -0
- package/src/server/routes/memory.ts +99 -0
- package/src/server/routes/metrics.ts +12 -0
- package/src/server/routes/notifications.ts +48 -0
- package/src/server/routes/repo-edit.ts +115 -0
- package/src/server/routes/reports.ts +127 -0
- package/src/server/routes/repos.ts +50 -0
- package/src/server/routes/runtime.ts +48 -0
- package/src/server/routes/safety.ts +13 -0
- package/src/server/routes/scheduled-tasks.ts +58 -0
- package/src/server/routes/scheduler.ts +13 -0
- package/src/server/routes/sessions.ts +216 -0
- package/src/server/routes/skills.ts +103 -0
- package/src/server/routes/watches.ts +81 -0
- package/src/server/routes/workflows.ts +21 -0
- package/src/server/routes/worktrees.ts +64 -0
- package/src/server/scheduler-workflow.ts +541 -0
- package/src/server/serve.ts +100 -0
- package/src/setup.ts +162 -0
- package/src/skills/github-gh/SKILL.md +56 -0
- package/src/skills/neon-ci-fix/SKILL.md +15 -0
- package/src/skills/neon-docs-fix/SKILL.md +15 -0
- package/src/skills/neon-issue-triage/SKILL.md +13 -0
- package/src/skills/neon-pr-review/SKILL.md +19 -0
- package/src/skills/neondeck/SKILL.md +123 -0
- package/src/skills/neondeck-handoff/SKILL.md +31 -0
- package/src/workflows/briefing.ts +14 -0
- package/src/workflows/command-run.ts +15 -0
- package/src/workflows/comment-pr-autofix-result.ts +10 -0
- package/src/workflows/curate_learning_store.ts +37 -0
- package/src/workflows/dev-doctor.ts +10 -0
- package/src/workflows/fix-pr-ci-failure.ts +10 -0
- package/src/workflows/fix-pr-ci.ts +15 -0
- package/src/workflows/fix-pr-review-feedback.ts +10 -0
- package/src/workflows/handoff_to_kilo.ts +10 -0
- package/src/workflows/prepare-pr-worktree.ts +10 -0
- package/src/workflows/promote_kilo_result.ts +10 -0
- package/src/workflows/push-pr-autofix.ts +10 -0
- package/src/workflows/reconcile_kilo_task.ts +10 -0
- package/src/workflows/review-pr-for-human.ts +15 -0
- package/src/workflows/review_conversation_for_learning.ts +37 -0
- package/src/workflows/review_kilo_result.ts +10 -0
- package/src/workflows/review_pr_batch_for_learning.ts +37 -0
- package/src/workflows/scheduled-agent-instruction.ts +16 -0
- package/src/workflows/scheduler-tick.ts +10 -0
- package/src/workflows/summarize_kilo_session.ts +25 -0
- package/src/workflows/triage-pr-event.ts +10 -0
- package/src/workflows/verify-pr-worktree.ts +10 -0
- package/src/workflows/verify-then-push-pr-autofix.ts +82 -0
- package/src/workflows/verify_kilo_result.ts +10 -0
- package/src/workflows/watch-pr.ts +10 -0
- package/web/dist/assets/GitHubPrReview-QK9oqeDs.js +2 -0
- package/web/dist/assets/MultiFileView-CMm6tA9f.js +2970 -0
- package/web/dist/assets/abap-CLvhMVsD.js +1 -0
- package/web/dist/assets/actionscript-3--17pq3dv.js +1 -0
- package/web/dist/assets/ada-C5qYipkI.js +1 -0
- package/web/dist/assets/andromeeda-vGVdxbeo.js +1 -0
- package/web/dist/assets/angular-html-BVBpGdXr.js +1 -0
- package/web/dist/assets/angular-ts-BfdufMKP.js +1 -0
- package/web/dist/assets/apache-U0d_L8uA.js +1 -0
- package/web/dist/assets/apex-VAyPSnFM.js +1 -0
- package/web/dist/assets/apl-DWBSSoBH.js +1 -0
- package/web/dist/assets/applescript-CCn79oCD.js +1 -0
- package/web/dist/assets/ara-4CJ0cIlV.js +1 -0
- package/web/dist/assets/asciidoc-DE70LPWp.js +1 -0
- package/web/dist/assets/asm-Cmm7eHzH.js +1 -0
- package/web/dist/assets/astro-3LtMP0Sq.js +1 -0
- package/web/dist/assets/aurora-x-CDeNXAV0.js +1 -0
- package/web/dist/assets/awk-BWXHIvNe.js +1 -0
- package/web/dist/assets/ayu-dark-DluEY0Gj.js +1 -0
- package/web/dist/assets/ayu-light-C3h-C4tm.js +1 -0
- package/web/dist/assets/ayu-mirage-Bqwy1Gya.js +1 -0
- package/web/dist/assets/ballerina-B7ZEbQpA.js +1 -0
- package/web/dist/assets/bat-Bo4NYOV-.js +1 -0
- package/web/dist/assets/beancount-D-usSTwE.js +1 -0
- package/web/dist/assets/berry-DKpUyyne.js +1 -0
- package/web/dist/assets/bibtex-Ci_nEsc7.js +1 -0
- package/web/dist/assets/bicep-CUHmPFLl.js +1 -0
- package/web/dist/assets/bird2-C6vDhewU.js +1 -0
- package/web/dist/assets/blade-llJRbbtR.js +1 -0
- package/web/dist/assets/bsl-BkkzgIyY.js +1 -0
- package/web/dist/assets/c-Wt1voDr2.js +1 -0
- package/web/dist/assets/c3-BFHwR3_K.js +1 -0
- package/web/dist/assets/cadence-CQ2zXKGN.js +1 -0
- package/web/dist/assets/cairo-DLTphjLi.js +1 -0
- package/web/dist/assets/catppuccin-frappe-3VR1Za6u.js +1 -0
- package/web/dist/assets/catppuccin-latte-DwIHMF0Q.js +1 -0
- package/web/dist/assets/catppuccin-macchiato-DYnBP6_5.js +1 -0
- package/web/dist/assets/catppuccin-mocha-DYhrFGRu.js +1 -0
- package/web/dist/assets/clarity-SemFz856.js +1 -0
- package/web/dist/assets/clojure-DqKBuwfJ.js +1 -0
- package/web/dist/assets/cmake-Bj61d0ZC.js +1 -0
- package/web/dist/assets/cobol-x_HIyl2P.js +1 -0
- package/web/dist/assets/codeowners-C8r90Shi.js +1 -0
- package/web/dist/assets/codeql-oeQT6MSM.js +1 -0
- package/web/dist/assets/coffee-CThvmt4R.js +1 -0
- package/web/dist/assets/common-lisp-Cv5bFMCO.js +1 -0
- package/web/dist/assets/coq-BrsZFFmf.js +1 -0
- package/web/dist/assets/cpp-NtAeskI3.js +1 -0
- package/web/dist/assets/crystal-DNu_sX0G.js +1 -0
- package/web/dist/assets/csharp-oqKa8noW.js +1 -0
- package/web/dist/assets/css-DJp_X0uY.js +1 -0
- package/web/dist/assets/csv-Dx-8-gkx.js +1 -0
- package/web/dist/assets/cue-CE9AQfxI.js +1 -0
- package/web/dist/assets/cypher-ClKdZ_lG.js +1 -0
- package/web/dist/assets/d-qD-0Kul2.js +1 -0
- package/web/dist/assets/dark-plus-Cs2F2srj.js +1 -0
- package/web/dist/assets/dart-CnvKMtbv.js +1 -0
- package/web/dist/assets/dax-BkyTk9wS.js +1 -0
- package/web/dist/assets/desktop-Dlh5hvp9.js +1 -0
- package/web/dist/assets/diff-woXpYk--.js +1 -0
- package/web/dist/assets/docker-IyjqRm3v.js +1 -0
- package/web/dist/assets/dotenv-_5a1GRtc.js +1 -0
- package/web/dist/assets/dracula-BHWKrbxM.js +1 -0
- package/web/dist/assets/dracula-soft-5eyTD99u.js +1 -0
- package/web/dist/assets/dream-maker-DW3nJb8Q.js +1 -0
- package/web/dist/assets/edge-ozw5tpLl.js +1 -0
- package/web/dist/assets/elixir-VhA6FeZt.js +1 -0
- package/web/dist/assets/elm-dREJmIFz.js +1 -0
- package/web/dist/assets/emacs-lisp-B4R74twV.js +1 -0
- package/web/dist/assets/erb-CIg6G69l.js +1 -0
- package/web/dist/assets/erlang-Cphh6RMH.js +1 -0
- package/web/dist/assets/everforest-dark-sB-x3p7T.js +1 -0
- package/web/dist/assets/everforest-light-Df2xbC6M.js +1 -0
- package/web/dist/assets/fennel-DQxkIbk2.js +1 -0
- package/web/dist/assets/fish-BJitypiv.js +1 -0
- package/web/dist/assets/fluent-C03EYrpw.js +1 -0
- package/web/dist/assets/fortran-fixed-form-DEKoE2YW.js +1 -0
- package/web/dist/assets/fortran-free-form-CYNrtFtB.js +1 -0
- package/web/dist/assets/fsharp-D13ZGOAj.js +1 -0
- package/web/dist/assets/gdresource-C0sCabJj.js +1 -0
- package/web/dist/assets/gdscript-Cp2uCuqX.js +1 -0
- package/web/dist/assets/gdshader-CBce3t8t.js +1 -0
- package/web/dist/assets/genie-CV2tkWYe.js +1 -0
- package/web/dist/assets/gherkin-DExj1W_8.js +1 -0
- package/web/dist/assets/git-commit-BSykSTBG.js +1 -0
- package/web/dist/assets/git-rebase-B44mJPta.js +1 -0
- package/web/dist/assets/github-dark-C-LZuMrd.js +1 -0
- package/web/dist/assets/github-dark-default-DXG-b-1a.js +1 -0
- package/web/dist/assets/github-dark-dimmed-Bx1FflLF.js +1 -0
- package/web/dist/assets/github-dark-high-contrast-B_tTalzw.js +1 -0
- package/web/dist/assets/github-light-EUqPIrTm.js +1 -0
- package/web/dist/assets/github-light-default-BXViO-2h.js +1 -0
- package/web/dist/assets/github-light-high-contrast-B68TUdTA.js +1 -0
- package/web/dist/assets/gleam-CSRkHgEL.js +1 -0
- package/web/dist/assets/glimmer-js-vH_gHG0-.js +1 -0
- package/web/dist/assets/glimmer-ts--abOzSAQ.js +1 -0
- package/web/dist/assets/glsl-Dv5r7kPw.js +1 -0
- package/web/dist/assets/gn-ilITqXS6.js +1 -0
- package/web/dist/assets/gnuplot-7GGW24-e.js +1 -0
- package/web/dist/assets/go-rLFTqkRN.js +1 -0
- package/web/dist/assets/graphql-CB4jsw2E.js +1 -0
- package/web/dist/assets/groovy-CacY0gHj.js +1 -0
- package/web/dist/assets/gruvbox-dark-hard-C820rvS2.js +1 -0
- package/web/dist/assets/gruvbox-dark-medium-BPjhmG05.js +1 -0
- package/web/dist/assets/gruvbox-dark-soft-MrdJrrXF.js +1 -0
- package/web/dist/assets/gruvbox-light-hard-BC_s9l72.js +1 -0
- package/web/dist/assets/gruvbox-light-medium-BAWPOn9u.js +1 -0
- package/web/dist/assets/gruvbox-light-soft-BSMLrYjP.js +1 -0
- package/web/dist/assets/hack-DvEYX148.js +1 -0
- package/web/dist/assets/haml-zE6W3STP.js +1 -0
- package/web/dist/assets/handlebars-CzBR2SDs.js +1 -0
- package/web/dist/assets/haskell-D8IpX4py.js +1 -0
- package/web/dist/assets/haxe-OTjmBuCE.js +1 -0
- package/web/dist/assets/hcl-Dh228itO.js +1 -0
- package/web/dist/assets/hjson-CxZEssPk.js +1 -0
- package/web/dist/assets/hlsl-Cvrh5tZx.js +1 -0
- package/web/dist/assets/horizon-CE9ld1lL.js +1 -0
- package/web/dist/assets/horizon-bright-DSNQnXHK.js +1 -0
- package/web/dist/assets/houston-CsvMBhTu.js +1 -0
- package/web/dist/assets/html-C8UlPnhE.js +1 -0
- package/web/dist/assets/html-derivative-CY6NRz-J.js +1 -0
- package/web/dist/assets/http-Cyd7bS_S.js +1 -0
- package/web/dist/assets/hurl-CWPsiEpf.js +1 -0
- package/web/dist/assets/hxml-B0Qn7Nwc.js +1 -0
- package/web/dist/assets/hy-CZbG8q4J.js +1 -0
- package/web/dist/assets/imba-DsUTQ-LC.js +1 -0
- package/web/dist/assets/index-DqZpo0Jd.js +44 -0
- package/web/dist/assets/index-_jKCE4ks.css +2 -0
- package/web/dist/assets/ini-B5eOa1yu.js +1 -0
- package/web/dist/assets/java-CGc3VwQr.js +1 -0
- package/web/dist/assets/javascript-CUt1pgmJ.js +1 -0
- package/web/dist/assets/jinja-CD-Z-FLd.js +1 -0
- package/web/dist/assets/jison-imPNup1l.js +1 -0
- package/web/dist/assets/json-Bg9ijW3F.js +1 -0
- package/web/dist/assets/json5-BR5RXkoi.js +1 -0
- package/web/dist/assets/jsonc-CYpm1nAK.js +1 -0
- package/web/dist/assets/jsonl-CmCQp5Yx.js +1 -0
- package/web/dist/assets/jsonnet-CJTPZ8u_.js +1 -0
- package/web/dist/assets/jssm-DXw9l8Rf.js +1 -0
- package/web/dist/assets/jsx-CY6oMTks.js +1 -0
- package/web/dist/assets/julia-Dc3O-irA.js +1 -0
- package/web/dist/assets/just-BhOq_Kbv.js +1 -0
- package/web/dist/assets/kanagawa-dragon-CXtmUGW6.js +1 -0
- package/web/dist/assets/kanagawa-lotus-BN08jTvb.js +1 -0
- package/web/dist/assets/kanagawa-wave-CTweb8Dz.js +1 -0
- package/web/dist/assets/kdl-CsD5j6eV.js +1 -0
- package/web/dist/assets/kotlin-DhhofPvG.js +1 -0
- package/web/dist/assets/kusto-C7mF5XQf.js +1 -0
- package/web/dist/assets/laserwave-C_8bwKvT.js +1 -0
- package/web/dist/assets/latex-Cu4Y1d5w.js +1 -0
- package/web/dist/assets/lean-CewbzKMR.js +1 -0
- package/web/dist/assets/less-DVTAwKKz.js +1 -0
- package/web/dist/assets/light-plus-DVQuIRkW.js +1 -0
- package/web/dist/assets/liquid-CvXMrjlQ.js +1 -0
- package/web/dist/assets/llvm-Cm23YOpf.js +1 -0
- package/web/dist/assets/log-BNLmms1o.js +1 -0
- package/web/dist/assets/logo-Cluzi2Zq.js +1 -0
- package/web/dist/assets/lua-BjLEUjKY.js +1 -0
- package/web/dist/assets/luau-FMPmPwt6.js +1 -0
- package/web/dist/assets/make-Dixweg8N.js +1 -0
- package/web/dist/assets/markdown-BYOwaDjH.js +1 -0
- package/web/dist/assets/marko-DvhNOisQ.js +1 -0
- package/web/dist/assets/material-theme-Bm3Qr25_.js +1 -0
- package/web/dist/assets/material-theme-darker-2IIEA8gg.js +1 -0
- package/web/dist/assets/material-theme-lighter-uhdI0v04.js +1 -0
- package/web/dist/assets/material-theme-ocean-CHQ94UKr.js +1 -0
- package/web/dist/assets/material-theme-palenight-B5W6OYN7.js +1 -0
- package/web/dist/assets/matlab-D7qyCx1q.js +1 -0
- package/web/dist/assets/mdc-Bm9TpL1X.js +1 -0
- package/web/dist/assets/mdx-DQZ5AkYe.js +1 -0
- package/web/dist/assets/mermaid-Bk4SNUv9.js +1 -0
- package/web/dist/assets/min-dark-BSWPekZh.js +1 -0
- package/web/dist/assets/min-light-DDpmG2fV.js +1 -0
- package/web/dist/assets/mipsasm-BMqwQI7S.js +1 -0
- package/web/dist/assets/mojo-BgCJLMeH.js +1 -0
- package/web/dist/assets/monokai-CdkpiU2Y.js +1 -0
- package/web/dist/assets/moonbit-CaWjb8XO.js +1 -0
- package/web/dist/assets/move-B1IS1UjX.js +1 -0
- package/web/dist/assets/narrat-_X_XdTYD.js +1 -0
- package/web/dist/assets/nextflow-BJtWHP5T.js +1 -0
- package/web/dist/assets/nextflow-groovy-DJMQeKeT.js +1 -0
- package/web/dist/assets/nginx-Bhc82uuv.js +1 -0
- package/web/dist/assets/night-owl-DhmEMT88.js +1 -0
- package/web/dist/assets/night-owl-light-eJ-hLW7d.js +1 -0
- package/web/dist/assets/nim-DXTVBFnF.js +1 -0
- package/web/dist/assets/nix-IvuFDN5E.js +1 -0
- package/web/dist/assets/nord-Cb4Vim4T.js +1 -0
- package/web/dist/assets/nushell-DcLAeLz5.js +1 -0
- package/web/dist/assets/objective-c-D1A_Heim.js +1 -0
- package/web/dist/assets/objective-cpp-BsSzOQcm.js +1 -0
- package/web/dist/assets/ocaml-O90oeIOV.js +1 -0
- package/web/dist/assets/odin-B1RWQWA5.js +1 -0
- package/web/dist/assets/one-dark-pro-CLwyXe_n.js +1 -0
- package/web/dist/assets/one-light-D7Lr4KcI.js +1 -0
- package/web/dist/assets/openscad-BUDT5pXO.js +1 -0
- package/web/dist/assets/pascal-4ZHwLPI5.js +1 -0
- package/web/dist/assets/perl-C7veXV9z.js +1 -0
- package/web/dist/assets/php-BRiuMnnr.js +1 -0
- package/web/dist/assets/pierre-dark-CpLgRqie.js +1 -0
- package/web/dist/assets/pierre-dark-protanopia-deuteranopia-B35FxJx-.js +1 -0
- package/web/dist/assets/pierre-dark-soft-kZQmAZld.js +1 -0
- package/web/dist/assets/pierre-dark-tritanopia-CpjhbsIL.js +1 -0
- package/web/dist/assets/pierre-dark-vibrant-CpQYzh95.js +1 -0
- package/web/dist/assets/pierre-light-CoaEpmwp.js +1 -0
- package/web/dist/assets/pierre-light-protanopia-deuteranopia-0fSaH845.js +1 -0
- package/web/dist/assets/pierre-light-soft-lWLdNTOI.js +1 -0
- package/web/dist/assets/pierre-light-tritanopia-CEbqgOJL.js +1 -0
- package/web/dist/assets/pierre-light-vibrant-D80Fkn33.js +1 -0
- package/web/dist/assets/pkl-ot-7Btpt.js +1 -0
- package/web/dist/assets/plastic-DQwYfKfQ.js +1 -0
- package/web/dist/assets/plsql-DGHpHOYJ.js +1 -0
- package/web/dist/assets/po-BiJDBrnU.js +1 -0
- package/web/dist/assets/poimandres-DRFjx7u4.js +1 -0
- package/web/dist/assets/polar-C7UOKdEL.js +1 -0
- package/web/dist/assets/postcss-BXeXVLqQ.js +1 -0
- package/web/dist/assets/powerquery-DNMTfnFr.js +1 -0
- package/web/dist/assets/powershell-DshXNtvi.js +1 -0
- package/web/dist/assets/prisma-BsRQq5mF.js +1 -0
- package/web/dist/assets/prolog-iXnhIJG7.js +1 -0
- package/web/dist/assets/proto-DB4EqR-F.js +1 -0
- package/web/dist/assets/pug-C5hz5LQ7.js +1 -0
- package/web/dist/assets/puppet-CDv2pdJW.js +1 -0
- package/web/dist/assets/purescript-9MfHhQsQ.js +1 -0
- package/web/dist/assets/python-gzcpVVnB.js +1 -0
- package/web/dist/assets/qml-BdUV3aTS.js +1 -0
- package/web/dist/assets/qmldir-DCQb3MpD.js +1 -0
- package/web/dist/assets/qss-Fe1Jh2GI.js +1 -0
- package/web/dist/assets/r-8R7vtdQc.js +1 -0
- package/web/dist/assets/racket-DcIDlBhZ.js +1 -0
- package/web/dist/assets/raku-B3gFvitq.js +1 -0
- package/web/dist/assets/razor-DRL52XO2.js +1 -0
- package/web/dist/assets/red-CJ3rzSJv.js +1 -0
- package/web/dist/assets/reg-CRGYupPL.js +1 -0
- package/web/dist/assets/regexp-Omp9DhTb.js +1 -0
- package/web/dist/assets/rel-BtDbiS_P.js +1 -0
- package/web/dist/assets/riscv-Ckw8ddFX.js +1 -0
- package/web/dist/assets/rolldown-runtime-QTnfLwEv.js +1 -0
- package/web/dist/assets/ron-VUp2lXgN.js +1 -0
- package/web/dist/assets/rose-pine-BthvhNj6.js +1 -0
- package/web/dist/assets/rose-pine-dawn-Dg85fqjY.js +1 -0
- package/web/dist/assets/rose-pine-moon-hon4tzzS.js +1 -0
- package/web/dist/assets/rosmsg-CAekHB0j.js +1 -0
- package/web/dist/assets/rst-BHX71KW9.js +1 -0
- package/web/dist/assets/ruby-B--HzjGU.js +1 -0
- package/web/dist/assets/rust-Cfkwpbl8.js +1 -0
- package/web/dist/assets/sas-DrLaYOK_.js +1 -0
- package/web/dist/assets/sass-DXrisJhu.js +1 -0
- package/web/dist/assets/scala-DKOlJaKm.js +1 -0
- package/web/dist/assets/scheme-DQCgrYNe.js +1 -0
- package/web/dist/assets/scss-DdSxiZKl.js +1 -0
- package/web/dist/assets/sdbl-bTVj8UrX.js +1 -0
- package/web/dist/assets/shaderlab-TOUzSsQk.js +1 -0
- package/web/dist/assets/shellscript-DwcUjJBL.js +1 -0
- package/web/dist/assets/shellsession-CPZkydE6.js +1 -0
- package/web/dist/assets/slack-dark-DnToyrRv.js +1 -0
- package/web/dist/assets/slack-ochin-B2OO5cIa.js +1 -0
- package/web/dist/assets/smalltalk-B16xEiuN.js +1 -0
- package/web/dist/assets/snazzy-light-4G7pJPwS.js +1 -0
- package/web/dist/assets/solarized-dark-DV17i1UV.js +1 -0
- package/web/dist/assets/solarized-light-DSh2HLQt.js +1 -0
- package/web/dist/assets/solidity-CKzVLygQ.js +1 -0
- package/web/dist/assets/soy-Br5FhD7c.js +1 -0
- package/web/dist/assets/sparql-D_iOobhT.js +1 -0
- package/web/dist/assets/splunk-BC2Px7Mm.js +1 -0
- package/web/dist/assets/sql-DNssxck8.js +1 -0
- package/web/dist/assets/ssh-config-BgfXC-Er.js +1 -0
- package/web/dist/assets/stata-DXn1tqOr.js +1 -0
- package/web/dist/assets/stylus-B6D30XZt.js +1 -0
- package/web/dist/assets/surfaces-B8Ko8AJ3.js +1 -0
- package/web/dist/assets/surrealql-IeLNQw0f.js +1 -0
- package/web/dist/assets/svelte-DOdLCIlh.js +1 -0
- package/web/dist/assets/swift-DonLKvLd.js +1 -0
- package/web/dist/assets/synthwave-84-nFMaYfgc.js +1 -0
- package/web/dist/assets/system-verilog-DJ5XKQeo.js +1 -0
- package/web/dist/assets/systemd-BxMlprV5.js +1 -0
- package/web/dist/assets/talonscript-CohzipZa.js +1 -0
- package/web/dist/assets/tasl-DMoTqEGO.js +1 -0
- package/web/dist/assets/tcl-CZd0xW_V.js +1 -0
- package/web/dist/assets/templ-CIwIngms.js +1 -0
- package/web/dist/assets/terraform-DswuEJGm.js +1 -0
- package/web/dist/assets/tex-D8QMumu5.js +1 -0
- package/web/dist/assets/tokyo-night-oM2G3aXe.js +1 -0
- package/web/dist/assets/toml-CcmNWLt0.js +1 -0
- package/web/dist/assets/ts-tags-BMVY4q-l.js +1 -0
- package/web/dist/assets/tsv-sltzmVWM.js +1 -0
- package/web/dist/assets/tsx-5Eka4NBX.js +1 -0
- package/web/dist/assets/turtle-ByJddavk.js +1 -0
- package/web/dist/assets/twig-C8o_5mgw.js +1 -0
- package/web/dist/assets/typescript-DOu2WMV5.js +1 -0
- package/web/dist/assets/typespec-BRdr0IET.js +1 -0
- package/web/dist/assets/typst-DI99ib-x.js +1 -0
- package/web/dist/assets/v-DETTlOr0.js +1 -0
- package/web/dist/assets/vala-zf12oZj6.js +1 -0
- package/web/dist/assets/vb-Djn5o6TS.js +1 -0
- package/web/dist/assets/verilog-CiiDBU1e.js +1 -0
- package/web/dist/assets/vesper-DdrHHSXu.js +1 -0
- package/web/dist/assets/vhdl-BroJfC0k.js +1 -0
- package/web/dist/assets/viml-DvXPmvsu.js +1 -0
- package/web/dist/assets/vitesse-black-fwtXNY1n.js +1 -0
- package/web/dist/assets/vitesse-dark-BZCL-v6S.js +1 -0
- package/web/dist/assets/vitesse-light-VbXTXTou.js +1 -0
- package/web/dist/assets/vue-BU18DNDL.js +1 -0
- package/web/dist/assets/vue-html-BeluIYX0.js +1 -0
- package/web/dist/assets/vue-vine-DGUAbOCX.js +1 -0
- package/web/dist/assets/vyper-CgoNMtux.js +1 -0
- package/web/dist/assets/wasm-BnjxR4X6.js +1 -0
- package/web/dist/assets/wasm-ByWQv1Qj.js +1 -0
- package/web/dist/assets/wenyan-C8pVoKbM.js +1 -0
- package/web/dist/assets/wgsl-BsKzXJz4.js +1 -0
- package/web/dist/assets/wikitext-ClFFjSW2.js +1 -0
- package/web/dist/assets/wit-DdvCle-K.js +1 -0
- package/web/dist/assets/wolfram-DLL8P-h_.js +1 -0
- package/web/dist/assets/worker-CBKQOXEl.js +160 -0
- package/web/dist/assets/xml-DIqSwXR3.js +1 -0
- package/web/dist/assets/xsl-Ct_-YIAy.js +1 -0
- package/web/dist/assets/yaml-DTtCYNlS.js +1 -0
- package/web/dist/assets/zenscript-BnlCZFoB.js +1 -0
- package/web/dist/assets/zig-CMLA9XwU.js +1 -0
- package/web/dist/icons/neondeck-icon-192.png +0 -0
- package/web/dist/icons/neondeck-icon-512.png +0 -0
- package/web/dist/icons/neondeck-icon.svg +41 -0
- package/web/dist/icons/neondeck-maskable-512.png +0 -0
- package/web/dist/index.html +24 -0
- package/web/dist/manifest.webmanifest +28 -0
|
@@ -0,0 +1,3620 @@
|
|
|
1
|
+
import { $ as readLearningOperatorState, $i as lockWorktree, $r as installFlueExecutionContextTracker, $t as getGitHubPrFiles, A as readReport, Ai as subscribeChatSessionEvents, An as listTaskRecords, At as reviewKiloResult, Ba as asJsonValue, Bi as formatConfigServerSentEvent, Bt as readRepoFile, Ca as nonEmptyStringSchema, Ci as listChatSessionCommandEvents, Cn as resolvePrReference, Cr as applyDashboardPreset, D as fixPrReviewFeedback, Da as readProviderConfigSync, Di as switchChatSession, E as commentPrAutofixResult, Ea as providerRuntimeRegistrations, Ei as restoreChatSession, Er as triagePrEvent, Fa as ConfigValidationError, Fi as archiveMemory, Fr as localApiTokenQueryParam, Gi as readRepoHealthSnapshot, Gn as verifyPrWorktree, Gt as pushPrAutofix, Ha as invalidInputAction, Hi as subscribeConfigEvents, Hr as listRuntimeSkills, Ht as searchRepoFiles, I as recordConversationTurnAndMaybeQueueLearning, Ia as parseAppConfig, Ii as listMemories, Ir as readLocalApiToken, J as rejectSkillPatchCandidate, Jn as getGitHubPullRequest, Jt as deleteGitHubPrReviewDraft, K as applySkillPatchCandidate, Ka as __require, Ki as readRepoRegistrySnapshot, L as recordHandledPrFromWorkflowResult, La as parseDashboardConfig, Li as listMemoryEvents, Lt as patchRepoFiles, Ma as ensureRuntimeHomeSync, Mn as removeTask, Mr as bearerToken, Mt as recordDocsDriftFixTaskBoundary, Na as readRuntimeJson, Ni as decideMemoryCandidate, Nn as setTaskEnabled, Nr as localApiAuthHeader, Oa as loadNeondeckEnv, Oi as updateChatSessionCommandEvent, On as createAgentInstructionTask, Ot as listKiloResultStates, Pi as listMemoryCandidates, Pr as localApiTokenMatches, Pt as fixPrCiFailure, Qi as createWorktree, Qt as getGitHubPrFileDiff, Ra as runtimePaths, Ri as upsertMemory, Rt as readRepoCheckoutStatus, Sa as subscribeNotificationEvents, Si as linkChatSessionContext, Sn as setPrWatchPolling, Sr as updateExecutionPolicy, St as searchKiloSessions, T as approvePreparedDiffPushWithPolicy, Ta as isRegisteredProvider, Ti as renameChatSession, Tr as reloadConfig, Ua as okAction, Un as settleScheduledTaskWorkflowRun, Ur as loadRuntimeSkill, Ut as writeRepoFile, Va as failedAction, Vi as replayConfigEventsAfter, Vr as readRuntimeStatus, Vt as replaceRepoFile, Wa as __commonJSMin, Wn as preparePrWorktree, Wr as reloadRuntimeSkills, Wt as listRepoEditEvents, Xi as listWorktrees, Xn as listGitHubPrQueue, Xt as getGitHubPrBranchPermissions, Y as restoreSkillPatchCandidate, Yt as deleteGitHubPrReviewDraftComment, Zi as cleanupWorktrees, Zr as getMcpRegistry, Zt as getGitHubPrEventState, _a as addNotification, _i as readChatSessionMessages, _n as addRefWatch, _r as updateRepoAutopilotPolicy, _t as readKiloSession, aa as listPreparedDiffs, ai as removeMcpServer, an as postGitHubPrComment, ba as resolveNotification, bi as createChatSession, bn as listRefWatches, br as updateAgentModels, bt as readKiloSessionMessages, c as abandonPreparedDiffWithRevisionAbort, ca as readPreparedDiffChangedFiles, ci as listMcpApprovals, cn as postGitHubPrThreadReply, cr as runApprovedExecution, d as failAutopilotAdmission, da as requestPreparedDiffRevision, dn as refreshPrWatchEventState, dr as resolveExecutionApproval, dt as readKiloTaskDiff, ea as readWorktreeStatus, ei as completeMcpOAuthCallback, en as getGitHubPrRequestedChanges, et as readWorkflowObservability, f as recordAutopilotAdmissionRun, fa as runPreparedDiffVerification, fr as checkExecutionPolicy, ft as readKiloTaskEvents, ga as setWorkflowSummaryRunId, gi as readChatSession, gn as addPrWatch, h as settleAutopilotAdmissionTriage, ha as listWorkflowSummaries, hi as listChatSessions, hr as readExecutionPolicy, ht as startKiloTask, i as runAutopilotRecoveryAction, ii as addMcpServer, in as patchGitHubPrReviewDraftComment, it as listLearningReviews, j as readReportHtml, ja as ensureRuntimeHome, jn as readTaskRecord, jr as readHostMetrics, jt as verifyKiloResult, k as listReports, ki as formatChatSessionServerSentEvent, kn as createBriefingTask, kt as promoteKiloResult, l as runPreparedDiffRevision, la as readPreparedDiffFileDiff, li as listMcpAudit, ln as postGitHubPrThreadResolution, lr as listExecutionApprovals, lt as abortKiloTask, m as settleAutopilotAdmissionPrepare, ma as addWorkflowSummary, mi as refreshChatSessionSummary, mt as readKiloTaskStatus, na as syncWorktree, ni as readPublicMcpOAuthLogin, nn as getGitHubPrReviewThreads, nt as loadAutomationLearningMemoryContext, o as readAutopilotState, oa as openPreparedDiffWorktree, oi as setMcpServerEnabled, on as postGitHubPrReview, or as syncExeDevCheckout, p as recordAutopilotAdmissionTerminalFact, pa as readApprovalRecord, pi as referenceChatSession, pt as readKiloTaskSessions, q as listSkillPatchCandidates, qi as repoFullName, r as readAutopilotRecoveryOptions, ri as startMcpOAuthLogin, rn as listPrWatchEventWatermarks, rt as attachLearningReviewRunId, s as autopilotStateSchema, si as updateMcpServer, sn as postGitHubPrReviewDraftComment, ta as releaseWorktreeLock, ti as logoutMcpOAuthServer, tn as getGitHubPrReviewDraft, tt as recordFlueObservation, u as beginAutopilotAdmissionPrepare, ua as readPreparedDiffSummary, ui as resolveMcpApprovalWithPaths, un as putGitHubPrReviewDraft, ur as requestExecutionApproval, ut as listKiloTasks, va as listNotifications, vi as searchChatSessions, vr as readProviderConfig, vt as readKiloSessionChildren, wa as parseInput, wi as pinChatSession, wr as updateDashboardLayout, xa as formatNotificationServerSentEvent, xi as createChatSessionCommandEvent, xn as removePrWatch, xr as updateWorktreePolicy, xt as readUnavailableSessionAdapter, ya as markNotificationRead, yi as archiveChatSession, yn as listPrWatches, yr as updateProviderConfig, yt as readKiloSessionDiff, zi as readNeonSessionState, zt as readRepoDiff } from "./autopilot-CKgEx8yQ.js";
|
|
2
|
+
import { a as runSchedulerTick, c as rollbackQuietly, f as supportedCommands, n as readSafetyPolicy, o as isSqliteBusy, s as readMetadataValue } from "./safety-CawD9GxD.js";
|
|
3
|
+
import { a as review_conversation_for_learning_default, r as review_pr_batch_for_learning_default, s as curate_learning_store_default, t as scheduler_tick_default } from "./scheduler-tick-Cy26qBld.js";
|
|
4
|
+
import { getRun, invoke, listRuns, observe, registerProvider } from "@flue/runtime";
|
|
5
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
7
|
+
import { readFile } from "node:fs/promises";
|
|
8
|
+
import "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { DatabaseSync } from "node:sqlite";
|
|
11
|
+
import * as v from "valibot";
|
|
12
|
+
import { flue } from "@flue/runtime/routing";
|
|
13
|
+
import { Hono } from "hono";
|
|
14
|
+
//#region node_modules/@hono/node-server/dist/utils/stream.cjs
|
|
15
|
+
var require_stream = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
16
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
17
|
+
var node_stream = __require("node:stream");
|
|
18
|
+
var node_process = __require("node:process");
|
|
19
|
+
var pr54206Applied = () => {
|
|
20
|
+
const [major, minor] = node_process.versions.node.split(".").map((component) => parseInt(component));
|
|
21
|
+
return major >= 23 || major === 22 && minor >= 7 || major === 20 && minor >= 18;
|
|
22
|
+
};
|
|
23
|
+
var useReadableToWeb = pr54206Applied();
|
|
24
|
+
var createStreamBody = (stream, useNativeReadableToWeb = useReadableToWeb) => {
|
|
25
|
+
if (useNativeReadableToWeb) return node_stream.Readable.toWeb(stream);
|
|
26
|
+
let controller;
|
|
27
|
+
let settled = false;
|
|
28
|
+
const cleanup = () => {
|
|
29
|
+
stream.off("data", onData);
|
|
30
|
+
stream.off("error", onError);
|
|
31
|
+
stream.off("end", onTerminate);
|
|
32
|
+
stream.off("close", onTerminate);
|
|
33
|
+
};
|
|
34
|
+
const settle = (callback) => {
|
|
35
|
+
if (settled) return;
|
|
36
|
+
settled = true;
|
|
37
|
+
cleanup();
|
|
38
|
+
callback?.();
|
|
39
|
+
};
|
|
40
|
+
const onData = (chunk) => {
|
|
41
|
+
if (settled || !controller) return;
|
|
42
|
+
controller.enqueue(chunk);
|
|
43
|
+
if ((controller.desiredSize ?? 0) <= 0) stream.pause();
|
|
44
|
+
};
|
|
45
|
+
const onError = (error) => {
|
|
46
|
+
settle(() => {
|
|
47
|
+
controller?.error(error);
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
const onTerminate = () => {
|
|
51
|
+
settle(() => {
|
|
52
|
+
controller?.close();
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
return new ReadableStream({
|
|
56
|
+
start(streamController) {
|
|
57
|
+
controller = streamController;
|
|
58
|
+
stream.on("data", onData);
|
|
59
|
+
stream.on("error", onError);
|
|
60
|
+
stream.on("end", onTerminate);
|
|
61
|
+
stream.on("close", onTerminate);
|
|
62
|
+
stream.pause();
|
|
63
|
+
},
|
|
64
|
+
pull() {
|
|
65
|
+
if (!settled) stream.resume();
|
|
66
|
+
},
|
|
67
|
+
cancel() {
|
|
68
|
+
settle();
|
|
69
|
+
const ignoreError = () => {};
|
|
70
|
+
stream.on("error", ignoreError);
|
|
71
|
+
stream.once("close", () => stream.off("error", ignoreError));
|
|
72
|
+
stream.destroy();
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
exports.createStreamBody = createStreamBody;
|
|
77
|
+
}));
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region node_modules/hono/dist/cjs/utils/mime.js
|
|
80
|
+
var require_mime = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
81
|
+
var __defProp = Object.defineProperty;
|
|
82
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
83
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
84
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
85
|
+
var __export = (target, all) => {
|
|
86
|
+
for (var name in all) __defProp(target, name, {
|
|
87
|
+
get: all[name],
|
|
88
|
+
enumerable: true
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
var __copyProps = (to, from, except, desc) => {
|
|
92
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
93
|
+
for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
94
|
+
get: () => from[key],
|
|
95
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return to;
|
|
99
|
+
};
|
|
100
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
101
|
+
var mime_exports = {};
|
|
102
|
+
__export(mime_exports, {
|
|
103
|
+
getExtension: () => getExtension,
|
|
104
|
+
getMimeType: () => getMimeType,
|
|
105
|
+
mimes: () => baseMimes
|
|
106
|
+
});
|
|
107
|
+
module.exports = __toCommonJS(mime_exports);
|
|
108
|
+
var getMimeType = (filename, mimes = baseMimes) => {
|
|
109
|
+
const match = filename.match(/\.([a-zA-Z0-9]+?)$/);
|
|
110
|
+
if (!match) return;
|
|
111
|
+
return mimes[match[1].toLowerCase()];
|
|
112
|
+
};
|
|
113
|
+
var getExtension = (mimeType) => {
|
|
114
|
+
const baseType = mimeType.split(";", 1)[0].trim();
|
|
115
|
+
for (const ext in baseMimes) {
|
|
116
|
+
const stored = baseMimes[ext];
|
|
117
|
+
if (stored === mimeType || stored.split(";", 1)[0].trim() === baseType) return ext;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
var baseMimes = {
|
|
121
|
+
aac: "audio/aac",
|
|
122
|
+
avi: "video/x-msvideo",
|
|
123
|
+
avif: "image/avif",
|
|
124
|
+
av1: "video/av1",
|
|
125
|
+
bin: "application/octet-stream",
|
|
126
|
+
bmp: "image/bmp",
|
|
127
|
+
css: "text/css; charset=utf-8",
|
|
128
|
+
csv: "text/csv; charset=utf-8",
|
|
129
|
+
eot: "application/vnd.ms-fontobject",
|
|
130
|
+
epub: "application/epub+zip",
|
|
131
|
+
gif: "image/gif",
|
|
132
|
+
gz: "application/gzip",
|
|
133
|
+
htm: "text/html; charset=utf-8",
|
|
134
|
+
html: "text/html; charset=utf-8",
|
|
135
|
+
ico: "image/x-icon",
|
|
136
|
+
ics: "text/calendar; charset=utf-8",
|
|
137
|
+
jpeg: "image/jpeg",
|
|
138
|
+
jpg: "image/jpeg",
|
|
139
|
+
js: "text/javascript; charset=utf-8",
|
|
140
|
+
json: "application/json",
|
|
141
|
+
jsonld: "application/ld+json",
|
|
142
|
+
map: "application/json",
|
|
143
|
+
mid: "audio/x-midi",
|
|
144
|
+
midi: "audio/x-midi",
|
|
145
|
+
mjs: "text/javascript; charset=utf-8",
|
|
146
|
+
mp3: "audio/mpeg",
|
|
147
|
+
mp4: "video/mp4",
|
|
148
|
+
mpeg: "video/mpeg",
|
|
149
|
+
oga: "audio/ogg",
|
|
150
|
+
ogv: "video/ogg",
|
|
151
|
+
ogx: "application/ogg",
|
|
152
|
+
opus: "audio/opus",
|
|
153
|
+
otf: "font/otf",
|
|
154
|
+
pdf: "application/pdf",
|
|
155
|
+
png: "image/png",
|
|
156
|
+
rtf: "application/rtf",
|
|
157
|
+
svg: "image/svg+xml; charset=utf-8",
|
|
158
|
+
tif: "image/tiff",
|
|
159
|
+
tiff: "image/tiff",
|
|
160
|
+
ts: "video/mp2t",
|
|
161
|
+
ttf: "font/ttf",
|
|
162
|
+
txt: "text/plain; charset=utf-8",
|
|
163
|
+
wasm: "application/wasm",
|
|
164
|
+
webm: "video/webm",
|
|
165
|
+
weba: "audio/webm",
|
|
166
|
+
webmanifest: "application/manifest+json",
|
|
167
|
+
webp: "image/webp",
|
|
168
|
+
woff: "font/woff",
|
|
169
|
+
woff2: "font/woff2",
|
|
170
|
+
xhtml: "application/xhtml+xml; charset=utf-8",
|
|
171
|
+
xml: "application/xml; charset=utf-8",
|
|
172
|
+
zip: "application/zip",
|
|
173
|
+
"3gp": "video/3gpp",
|
|
174
|
+
"3g2": "video/3gpp2",
|
|
175
|
+
gltf: "model/gltf+json",
|
|
176
|
+
glb: "model/gltf-binary"
|
|
177
|
+
};
|
|
178
|
+
0 && (module.exports = {
|
|
179
|
+
getExtension,
|
|
180
|
+
getMimeType,
|
|
181
|
+
mimes
|
|
182
|
+
});
|
|
183
|
+
}));
|
|
184
|
+
//#endregion
|
|
185
|
+
//#region src/server/events/config-stream.ts
|
|
186
|
+
var import_serve_static = (/* @__PURE__ */ __commonJSMin(((exports) => {
|
|
187
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
188
|
+
var require_utils_stream = require_stream();
|
|
189
|
+
var hono_utils_mime = require_mime();
|
|
190
|
+
var node_fs = __require("node:fs");
|
|
191
|
+
var node_path = __require("node:path");
|
|
192
|
+
var COMPRESSIBLE_CONTENT_TYPE_REGEX = /^\s*(?:text\/[^;\s]+|application\/(?:javascript|json|xml|xml-dtd|ecmascript|dart|postscript|rtf|tar|toml|vnd\.dart|vnd\.ms-fontobject|vnd\.ms-opentype|wasm|x-httpd-php|x-javascript|x-ns-proxy-autoconfig|x-sh|x-tar|x-virtualbox-hdd|x-virtualbox-ova|x-virtualbox-ovf|x-virtualbox-vbox|x-virtualbox-vdi|x-virtualbox-vhd|x-virtualbox-vmdk|x-www-form-urlencoded)|font\/(?:otf|ttf)|image\/(?:bmp|vnd\.adobe\.photoshop|vnd\.microsoft\.icon|vnd\.ms-dds|x-icon|x-ms-bmp)|message\/rfc822|model\/gltf-binary|x-shader\/x-fragment|x-shader\/x-vertex|[^;\s]+?\+(?:json|text|xml|yaml))(?:[;\s]|$)/i;
|
|
193
|
+
var ENCODINGS = {
|
|
194
|
+
br: ".br",
|
|
195
|
+
zstd: ".zst",
|
|
196
|
+
gzip: ".gz"
|
|
197
|
+
};
|
|
198
|
+
var ENCODINGS_ORDERED_KEYS = Object.keys(ENCODINGS);
|
|
199
|
+
var getStats = (path) => {
|
|
200
|
+
let stats;
|
|
201
|
+
try {
|
|
202
|
+
stats = (0, node_fs.statSync)(path);
|
|
203
|
+
} catch {}
|
|
204
|
+
return stats;
|
|
205
|
+
};
|
|
206
|
+
var tryDecode = (str, decoder) => {
|
|
207
|
+
try {
|
|
208
|
+
return decoder(str);
|
|
209
|
+
} catch {
|
|
210
|
+
return str.replace(/(?:%[0-9A-Fa-f]{2})+/g, (match) => {
|
|
211
|
+
try {
|
|
212
|
+
return decoder(match);
|
|
213
|
+
} catch {
|
|
214
|
+
return match;
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
var tryDecodeURI = (str) => tryDecode(str, decodeURI);
|
|
220
|
+
var serveStatic = (options = { root: "" }) => {
|
|
221
|
+
const root = options.root || "";
|
|
222
|
+
const optionPath = options.path;
|
|
223
|
+
if (root !== "" && !(0, node_fs.existsSync)(root)) console.error(`serveStatic: root path '${root}' is not found, are you sure it's correct?`);
|
|
224
|
+
return async (c, next) => {
|
|
225
|
+
if (c.finalized) return next();
|
|
226
|
+
let filename;
|
|
227
|
+
if (optionPath) filename = optionPath;
|
|
228
|
+
else try {
|
|
229
|
+
filename = tryDecodeURI(c.req.path);
|
|
230
|
+
if (/(?:^|[\/\\])\.{1,2}(?:$|[\/\\])|[\/\\]{2,}|\\/.test(filename)) throw new Error();
|
|
231
|
+
} catch {
|
|
232
|
+
await options.onNotFound?.(c.req.path, c);
|
|
233
|
+
return next();
|
|
234
|
+
}
|
|
235
|
+
let path = (0, node_path.join)(root, !optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename, c) : filename);
|
|
236
|
+
let stats = getStats(path);
|
|
237
|
+
if (stats && stats.isDirectory()) {
|
|
238
|
+
const indexFile = options.index ?? "index.html";
|
|
239
|
+
path = (0, node_path.join)(path, indexFile);
|
|
240
|
+
stats = getStats(path);
|
|
241
|
+
}
|
|
242
|
+
if (!stats) {
|
|
243
|
+
await options.onNotFound?.(path, c);
|
|
244
|
+
return next();
|
|
245
|
+
}
|
|
246
|
+
const mimeType = (0, hono_utils_mime.getMimeType)(path);
|
|
247
|
+
c.header("Content-Type", mimeType || "application/octet-stream");
|
|
248
|
+
if (options.precompressed && (!mimeType || mimeType === "application/octet-stream" || COMPRESSIBLE_CONTENT_TYPE_REGEX.test(mimeType))) {
|
|
249
|
+
const acceptEncodingSet = new Set(c.req.header("Accept-Encoding")?.split(",").map((encoding) => encoding.trim()));
|
|
250
|
+
for (const encoding of ENCODINGS_ORDERED_KEYS) {
|
|
251
|
+
if (!acceptEncodingSet.has(encoding)) continue;
|
|
252
|
+
const precompressedStats = getStats(path + ENCODINGS[encoding]);
|
|
253
|
+
if (precompressedStats) {
|
|
254
|
+
c.header("Content-Encoding", encoding);
|
|
255
|
+
c.header("Vary", "Accept-Encoding", { append: true });
|
|
256
|
+
stats = precompressedStats;
|
|
257
|
+
path = path + ENCODINGS[encoding];
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
let result;
|
|
263
|
+
const size = stats.size;
|
|
264
|
+
const range = c.req.header("range") || "";
|
|
265
|
+
c.header("Last-Modified", stats.mtime.toUTCString());
|
|
266
|
+
if (c.req.method == "HEAD" || c.req.method == "OPTIONS") {
|
|
267
|
+
c.header("Content-Length", size.toString());
|
|
268
|
+
c.status(200);
|
|
269
|
+
result = c.body(null);
|
|
270
|
+
} else if (!range) {
|
|
271
|
+
c.header("Content-Length", size.toString());
|
|
272
|
+
result = c.body(require_utils_stream.createStreamBody((0, node_fs.createReadStream)(path)), 200);
|
|
273
|
+
} else {
|
|
274
|
+
c.header("Accept-Ranges", "bytes");
|
|
275
|
+
const parts = range.replace(/bytes=/, "").split("-", 2);
|
|
276
|
+
const start = parseInt(parts[0], 10) || 0;
|
|
277
|
+
let end = parseInt(parts[1], 10) || size - 1;
|
|
278
|
+
if (size < end - start + 1) end = size - 1;
|
|
279
|
+
const chunksize = end - start + 1;
|
|
280
|
+
const stream = (0, node_fs.createReadStream)(path, {
|
|
281
|
+
start,
|
|
282
|
+
end
|
|
283
|
+
});
|
|
284
|
+
c.header("Content-Length", chunksize.toString());
|
|
285
|
+
c.header("Content-Range", `bytes ${start}-${end}/${stats.size}`);
|
|
286
|
+
result = c.body(require_utils_stream.createStreamBody(stream), 206);
|
|
287
|
+
}
|
|
288
|
+
await options.onFound?.(path, c);
|
|
289
|
+
return result;
|
|
290
|
+
};
|
|
291
|
+
};
|
|
292
|
+
exports.serveStatic = serveStatic;
|
|
293
|
+
})))();
|
|
294
|
+
var configEventHeartbeatMs = 1e4;
|
|
295
|
+
var configEventStreamMaxAgeMs = 2e4;
|
|
296
|
+
function createConfigEventRoutes() {
|
|
297
|
+
const routes = new Hono();
|
|
298
|
+
routes.get("/config", (c) => {
|
|
299
|
+
const lastEventId = c.req.header("last-event-id");
|
|
300
|
+
const encoder = new TextEncoder();
|
|
301
|
+
let cleanup = () => {};
|
|
302
|
+
const stream = new ReadableStream({
|
|
303
|
+
start(controller) {
|
|
304
|
+
let active = true;
|
|
305
|
+
function send(value) {
|
|
306
|
+
if (!active) return;
|
|
307
|
+
controller.enqueue(encoder.encode(value));
|
|
308
|
+
}
|
|
309
|
+
send("retry: 3000\n: connected\n\n");
|
|
310
|
+
const unsubscribe = subscribeConfigEvents((event) => {
|
|
311
|
+
send(formatConfigServerSentEvent(event));
|
|
312
|
+
});
|
|
313
|
+
for (const event of replayConfigEventsAfter(lastEventId)) send(formatConfigServerSentEvent(event));
|
|
314
|
+
const heartbeat = setInterval(() => {
|
|
315
|
+
send(`: heartbeat ${Date.now()}\n\n`);
|
|
316
|
+
}, configEventHeartbeatMs);
|
|
317
|
+
const maxAge = setTimeout(() => {
|
|
318
|
+
send(": reconnecting\n\n");
|
|
319
|
+
cleanup();
|
|
320
|
+
controller.close();
|
|
321
|
+
}, configEventStreamMaxAgeMs);
|
|
322
|
+
cleanup = () => {
|
|
323
|
+
if (!active) return;
|
|
324
|
+
active = false;
|
|
325
|
+
clearInterval(heartbeat);
|
|
326
|
+
clearTimeout(maxAge);
|
|
327
|
+
unsubscribe();
|
|
328
|
+
};
|
|
329
|
+
},
|
|
330
|
+
cancel() {
|
|
331
|
+
cleanup();
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
return new Response(stream, { headers: {
|
|
335
|
+
"cache-control": "no-cache, no-transform",
|
|
336
|
+
connection: "keep-alive",
|
|
337
|
+
"content-type": "text/event-stream; charset=utf-8",
|
|
338
|
+
"x-accel-buffering": "no"
|
|
339
|
+
} });
|
|
340
|
+
});
|
|
341
|
+
return routes;
|
|
342
|
+
}
|
|
343
|
+
//#endregion
|
|
344
|
+
//#region src/server/events/notification-stream.ts
|
|
345
|
+
function createNotificationEventRoutes() {
|
|
346
|
+
const routes = new Hono();
|
|
347
|
+
routes.get("/notifications", () => {
|
|
348
|
+
const encoder = new TextEncoder();
|
|
349
|
+
let cleanup = () => {};
|
|
350
|
+
const stream = new ReadableStream({
|
|
351
|
+
start(controller) {
|
|
352
|
+
function send(value) {
|
|
353
|
+
controller.enqueue(encoder.encode(value));
|
|
354
|
+
}
|
|
355
|
+
send(": connected\n\n");
|
|
356
|
+
const unsubscribe = subscribeNotificationEvents((event) => {
|
|
357
|
+
send(formatNotificationServerSentEvent(event));
|
|
358
|
+
});
|
|
359
|
+
const heartbeat = setInterval(() => {
|
|
360
|
+
send(`: heartbeat ${Date.now()}\n\n`);
|
|
361
|
+
}, 25e3);
|
|
362
|
+
cleanup = () => {
|
|
363
|
+
clearInterval(heartbeat);
|
|
364
|
+
unsubscribe();
|
|
365
|
+
};
|
|
366
|
+
},
|
|
367
|
+
cancel() {
|
|
368
|
+
cleanup();
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
return new Response(stream, { headers: {
|
|
372
|
+
"cache-control": "no-cache, no-transform",
|
|
373
|
+
connection: "keep-alive",
|
|
374
|
+
"content-type": "text/event-stream; charset=utf-8",
|
|
375
|
+
"x-accel-buffering": "no"
|
|
376
|
+
} });
|
|
377
|
+
});
|
|
378
|
+
return routes;
|
|
379
|
+
}
|
|
380
|
+
//#endregion
|
|
381
|
+
//#region src/server/events/session-stream.ts
|
|
382
|
+
function createSessionEventRoutes() {
|
|
383
|
+
const routes = new Hono();
|
|
384
|
+
routes.get("/sessions", () => {
|
|
385
|
+
const encoder = new TextEncoder();
|
|
386
|
+
let cleanup = () => {};
|
|
387
|
+
const stream = new ReadableStream({
|
|
388
|
+
start(controller) {
|
|
389
|
+
function send(value) {
|
|
390
|
+
controller.enqueue(encoder.encode(value));
|
|
391
|
+
}
|
|
392
|
+
send(": connected\n\n");
|
|
393
|
+
const unsubscribe = subscribeChatSessionEvents((event) => {
|
|
394
|
+
send(formatChatSessionServerSentEvent(event));
|
|
395
|
+
});
|
|
396
|
+
const heartbeat = setInterval(() => {
|
|
397
|
+
send(`: heartbeat ${Date.now()}\n\n`);
|
|
398
|
+
}, 25e3);
|
|
399
|
+
cleanup = () => {
|
|
400
|
+
clearInterval(heartbeat);
|
|
401
|
+
unsubscribe();
|
|
402
|
+
};
|
|
403
|
+
},
|
|
404
|
+
cancel() {
|
|
405
|
+
cleanup();
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
return new Response(stream, { headers: {
|
|
409
|
+
"cache-control": "no-cache, no-transform",
|
|
410
|
+
connection: "keep-alive",
|
|
411
|
+
"content-type": "text/event-stream; charset=utf-8",
|
|
412
|
+
"x-accel-buffering": "no"
|
|
413
|
+
} });
|
|
414
|
+
});
|
|
415
|
+
return routes;
|
|
416
|
+
}
|
|
417
|
+
//#endregion
|
|
418
|
+
//#region src/server/learning-hooks.ts
|
|
419
|
+
var observationHandlerUnsubscribers = /* @__PURE__ */ new Map();
|
|
420
|
+
function installFlueObservationHandlers(paths, dependencies = {}) {
|
|
421
|
+
if (observationHandlerUnsubscribers.has(paths.home)) return;
|
|
422
|
+
const unsubscribe = (dependencies.observe ?? observe)((event, context) => {
|
|
423
|
+
const contextHome = flueContextRuntimeHome(context);
|
|
424
|
+
if (contextHome && contextHome !== paths.home) return;
|
|
425
|
+
if (event.type === "run_end") {
|
|
426
|
+
recordFlueObservation(event, paths).then(async () => {
|
|
427
|
+
const terminalFact = autopilotTerminalFact(event);
|
|
428
|
+
if (terminalFact) await recordAutopilotAdmissionTerminalFact({
|
|
429
|
+
runId: event.runId,
|
|
430
|
+
fact: terminalFact
|
|
431
|
+
}, paths);
|
|
432
|
+
}).then(() => Promise.all([
|
|
433
|
+
settleScheduledTaskWorkflowRun({
|
|
434
|
+
workflowRunId: event.runId,
|
|
435
|
+
failed: event.isError
|
|
436
|
+
}, paths),
|
|
437
|
+
settleAutopilotAdmissionTriage({
|
|
438
|
+
runId: event.runId,
|
|
439
|
+
failed: terminalActionFailed(event),
|
|
440
|
+
shouldPrepare: triageRequestsPrepare(event)
|
|
441
|
+
}, paths),
|
|
442
|
+
settleAutopilotAdmissionPrepare({
|
|
443
|
+
runId: event.runId,
|
|
444
|
+
failed: terminalActionFailed(event),
|
|
445
|
+
worktreeId: prepareWorktreeId(event)
|
|
446
|
+
}, paths)
|
|
447
|
+
])).then(() => startPrepareAfterTriage(event, paths)).catch((error) => {
|
|
448
|
+
console.error("[neondeck] failed to record or settle Flue observation", error);
|
|
449
|
+
});
|
|
450
|
+
attachCommandRunSummaryRunId(event, paths).catch((error) => {
|
|
451
|
+
console.error("[neondeck] failed to attach Flue run id", error);
|
|
452
|
+
});
|
|
453
|
+
const learningReviewId = learningReviewResultId(event);
|
|
454
|
+
if (learningReviewId) Promise.resolve().then(() => attachLearningReviewRunId({
|
|
455
|
+
reviewId: learningReviewId,
|
|
456
|
+
runId: event.runId
|
|
457
|
+
}, paths)).catch((error) => {
|
|
458
|
+
console.error("[neondeck] failed to attach learning review run id", error);
|
|
459
|
+
});
|
|
460
|
+
if (!learningReviewId && !event.isError && "result" in event) Promise.resolve().then(() => recordHandledPrFromWorkflowResult({
|
|
461
|
+
workflow: workflowLabel(event),
|
|
462
|
+
runId: event.runId,
|
|
463
|
+
result: event.result
|
|
464
|
+
}, paths, { async invokePrBatchReview(input) {
|
|
465
|
+
return invoke(review_pr_batch_for_learning_default, { input });
|
|
466
|
+
} })).catch((error) => {
|
|
467
|
+
console.error("[neondeck] failed to record handled PR learning event", error);
|
|
468
|
+
});
|
|
469
|
+
if (event.isError) addNotification({
|
|
470
|
+
level: "attention",
|
|
471
|
+
title: "Workflow failed",
|
|
472
|
+
message: `${workflowLabel(event)} failed.`,
|
|
473
|
+
source: "flue",
|
|
474
|
+
sourceId: event.runId,
|
|
475
|
+
data: {
|
|
476
|
+
runId: event.runId,
|
|
477
|
+
workflow: workflowLabel(event),
|
|
478
|
+
error: "See guarded Flue run inspection for error details."
|
|
479
|
+
}
|
|
480
|
+
}, paths).catch((error) => {
|
|
481
|
+
console.error("[neondeck] failed to record Flue failure", error);
|
|
482
|
+
});
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
recordFlueObservation(event, paths).catch((error) => {
|
|
486
|
+
console.error("[neondeck] failed to record Flue observation", error);
|
|
487
|
+
});
|
|
488
|
+
if (event.type === "operation" && event.durationMs > 15e3 && event.isError) addNotification({
|
|
489
|
+
level: "attention",
|
|
490
|
+
title: "Slow Flue operation failed",
|
|
491
|
+
message: `${event.operationKind} failed after ${Math.round(event.durationMs / 1e3)}s.`,
|
|
492
|
+
source: "flue",
|
|
493
|
+
sourceId: event.operationId,
|
|
494
|
+
data: {
|
|
495
|
+
operationKind: event.operationKind,
|
|
496
|
+
durationMs: event.durationMs,
|
|
497
|
+
error: "See workflow observability for error details."
|
|
498
|
+
}
|
|
499
|
+
}, paths).catch((error) => {
|
|
500
|
+
console.error("[neondeck] failed to record Flue operation", error);
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
observationHandlerUnsubscribers.set(paths.home, unsubscribe);
|
|
504
|
+
}
|
|
505
|
+
function flueContextRuntimeHome(context) {
|
|
506
|
+
const value = context?.env?.NEONDECK_HOME;
|
|
507
|
+
return typeof value === "string" && value ? value : void 0;
|
|
508
|
+
}
|
|
509
|
+
async function startPrepareAfterTriage(event, paths) {
|
|
510
|
+
if (event.isError || !triageRequestsPrepare(event)) return;
|
|
511
|
+
const admission = await beginAutopilotAdmissionPrepare({ triageRunId: event.runId }, paths);
|
|
512
|
+
if (!admission) return;
|
|
513
|
+
try {
|
|
514
|
+
const { runId } = await invoke((await import("./prepare-pr-worktree-CLzccRYh.js")).default, { input: {
|
|
515
|
+
repoId: admission.repoId,
|
|
516
|
+
prNumber: admission.prNumber,
|
|
517
|
+
eventId: admission.eventFingerprint,
|
|
518
|
+
lock: false
|
|
519
|
+
} });
|
|
520
|
+
await recordAutopilotAdmissionRun({
|
|
521
|
+
id: admission.id,
|
|
522
|
+
runId
|
|
523
|
+
}, paths);
|
|
524
|
+
} catch (error) {
|
|
525
|
+
await failAutopilotAdmission({
|
|
526
|
+
id: admission.id,
|
|
527
|
+
error: error instanceof Error ? error.message : String(error)
|
|
528
|
+
}, paths);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
function triageRequestsPrepare(event) {
|
|
532
|
+
if (workflowLabel(event) !== "triage-pr-event" || !("result" in event)) return false;
|
|
533
|
+
const result = event.result;
|
|
534
|
+
if (!result || typeof result !== "object") return false;
|
|
535
|
+
const data = result.data;
|
|
536
|
+
return Boolean(data && typeof data === "object" && data.shouldPrepareWorktree === true);
|
|
537
|
+
}
|
|
538
|
+
function prepareWorktreeId(event) {
|
|
539
|
+
if (!("result" in event) || !event.result || typeof event.result !== "object") return;
|
|
540
|
+
const data = event.result.data;
|
|
541
|
+
if (!data || typeof data !== "object") return void 0;
|
|
542
|
+
const worktree = data.worktree;
|
|
543
|
+
if (!worktree || typeof worktree !== "object") return void 0;
|
|
544
|
+
const id = worktree.id;
|
|
545
|
+
return typeof id === "string" ? id : void 0;
|
|
546
|
+
}
|
|
547
|
+
function autopilotTerminalFact(event) {
|
|
548
|
+
const workflow = workflowLabel(event);
|
|
549
|
+
const failed = terminalActionFailed(event);
|
|
550
|
+
if (workflow === "triage-pr-event") return {
|
|
551
|
+
workflow,
|
|
552
|
+
failed,
|
|
553
|
+
shouldPrepare: !failed && triageRequestsPrepare(event)
|
|
554
|
+
};
|
|
555
|
+
if (workflow === "prepare-pr-worktree") return {
|
|
556
|
+
workflow,
|
|
557
|
+
failed,
|
|
558
|
+
worktreeId: failed ? void 0 : prepareWorktreeId(event)
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
function terminalActionFailed(event) {
|
|
562
|
+
if (event.isError || !("result" in event)) return event.isError;
|
|
563
|
+
const result = event.result;
|
|
564
|
+
return Boolean(result && typeof result === "object" && result.ok === false);
|
|
565
|
+
}
|
|
566
|
+
function recordHandledPrApiResult(paths, workflow, result) {
|
|
567
|
+
Promise.resolve().then(() => recordHandledPrFromWorkflowResult({
|
|
568
|
+
workflow,
|
|
569
|
+
result
|
|
570
|
+
}, paths, { async invokePrBatchReview(input) {
|
|
571
|
+
return invoke(review_pr_batch_for_learning_default, { input });
|
|
572
|
+
} })).catch((error) => {
|
|
573
|
+
console.error("[neondeck] failed to record handled PR learning event", error);
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
function displayAssistantLearningMiddleware(paths) {
|
|
577
|
+
return async (c, next) => {
|
|
578
|
+
const method = c.req.method.toUpperCase();
|
|
579
|
+
const sessionId = displayAssistantSessionId(c.req.path);
|
|
580
|
+
await next();
|
|
581
|
+
if (!sessionId || method !== "POST" || c.res.status >= 400) return;
|
|
582
|
+
recordConversationTurnAndMaybeQueueLearning(sessionId, paths, {
|
|
583
|
+
async invokeConversationReview(input) {
|
|
584
|
+
return invoke(review_conversation_for_learning_default, { input });
|
|
585
|
+
},
|
|
586
|
+
async invokeCurationReview(input) {
|
|
587
|
+
return invoke(curate_learning_store_default, { input });
|
|
588
|
+
}
|
|
589
|
+
}).catch((error) => {
|
|
590
|
+
console.error("[neondeck] failed to queue learning review", error);
|
|
591
|
+
});
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
async function attachCommandRunSummaryRunId(event, paths) {
|
|
595
|
+
const link = commandRunSummaryLink(event);
|
|
596
|
+
if (!link) return;
|
|
597
|
+
await setWorkflowSummaryRunId(link.summaryId, link.runId, paths);
|
|
598
|
+
}
|
|
599
|
+
function commandRunSummaryLink(event) {
|
|
600
|
+
if (!("result" in event)) return void 0;
|
|
601
|
+
const result = event.result;
|
|
602
|
+
if (!result || typeof result !== "object") return void 0;
|
|
603
|
+
const summary = result.workflowSummary;
|
|
604
|
+
if (!summary || typeof summary !== "object") return void 0;
|
|
605
|
+
const id = summary.id;
|
|
606
|
+
if (typeof id !== "string") return void 0;
|
|
607
|
+
const runId = stringField(summary, "workflow") === "ci_fix_run" ? stringField(event, "runId") : objectStringField(result, "data", "runId") ?? stringField(summary, "runId") ?? stringField(event, "runId");
|
|
608
|
+
if (!runId) return void 0;
|
|
609
|
+
return {
|
|
610
|
+
summaryId: id,
|
|
611
|
+
runId
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
function learningReviewResultId(event) {
|
|
615
|
+
if (!("result" in event)) return void 0;
|
|
616
|
+
const result = event.result;
|
|
617
|
+
if (!result || typeof result !== "object") return void 0;
|
|
618
|
+
const action = result.action;
|
|
619
|
+
if (action !== "learning_review_conversation" && action !== "learning_curate" && action !== "learning_review_pr_batch") return;
|
|
620
|
+
const reviewId = result.reviewId;
|
|
621
|
+
return typeof reviewId === "string" ? reviewId : void 0;
|
|
622
|
+
}
|
|
623
|
+
function workflowLabel(event) {
|
|
624
|
+
if ("workflow" in event && typeof event.workflow === "string") return event.workflow;
|
|
625
|
+
return `Workflow run ${event.runId ?? "unknown"}`;
|
|
626
|
+
}
|
|
627
|
+
function objectStringField(value, objectKey, stringKey) {
|
|
628
|
+
if (!(objectKey in value)) return void 0;
|
|
629
|
+
const nested = value[objectKey];
|
|
630
|
+
if (!nested || typeof nested !== "object") return void 0;
|
|
631
|
+
return stringField(nested, stringKey);
|
|
632
|
+
}
|
|
633
|
+
function stringField(value, key) {
|
|
634
|
+
if (!(key in value)) return void 0;
|
|
635
|
+
const field = value[key];
|
|
636
|
+
return typeof field === "string" && field.trim() ? field : void 0;
|
|
637
|
+
}
|
|
638
|
+
function displayAssistantSessionId(path) {
|
|
639
|
+
if (!path.startsWith("/api/flue/agents/display-assistant/")) return void 0;
|
|
640
|
+
const remainder = path.slice(35);
|
|
641
|
+
if (!remainder || remainder.includes("/")) return void 0;
|
|
642
|
+
return decodeURIComponent(remainder);
|
|
643
|
+
}
|
|
644
|
+
//#endregion
|
|
645
|
+
//#region src/server/middleware.ts
|
|
646
|
+
var localHosts = /* @__PURE__ */ new Set([
|
|
647
|
+
"127.0.0.1",
|
|
648
|
+
"localhost",
|
|
649
|
+
"[::1]",
|
|
650
|
+
"::1"
|
|
651
|
+
]);
|
|
652
|
+
var requireLocalApiAccess = async (c, next) => {
|
|
653
|
+
const host = hostName(c.req.header("host"));
|
|
654
|
+
if (host && localHosts.has(host)) {
|
|
655
|
+
if (!isSafeMethod(c.req.method) && !isAllowedBrowserOrigin(c.req.raw)) return c.json({ error: "Not found" }, 404);
|
|
656
|
+
await next();
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
return c.json({ error: "Not found" }, 404);
|
|
660
|
+
};
|
|
661
|
+
function requireFlueRunInspectionToken(paths) {
|
|
662
|
+
return async (c, next) => {
|
|
663
|
+
const expected = await readLocalApiToken(paths);
|
|
664
|
+
if (!localApiTokenMatches(c.req.header("x-neondeck-api-token") ?? bearerToken(c.req.header("authorization")) ?? c.req.query("neondeckApiToken"), expected)) return c.json({ error: "Not found" }, 404);
|
|
665
|
+
await next();
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
function hostName(host) {
|
|
669
|
+
if (!host) return void 0;
|
|
670
|
+
const lower = host.toLowerCase();
|
|
671
|
+
if (lower.startsWith("[")) return lower.slice(0, lower.indexOf("]") + 1);
|
|
672
|
+
return lower.split(":")[0];
|
|
673
|
+
}
|
|
674
|
+
function isSafeMethod(method) {
|
|
675
|
+
return method === "GET" || method === "HEAD" || method === "OPTIONS";
|
|
676
|
+
}
|
|
677
|
+
function isAllowedBrowserOrigin(request) {
|
|
678
|
+
const fetchSite = request.headers.get("sec-fetch-site");
|
|
679
|
+
if (fetchSite && fetchSite !== "same-origin" && fetchSite !== "none") return false;
|
|
680
|
+
const origin = request.headers.get("origin");
|
|
681
|
+
if (origin) return isLocalUrl(origin);
|
|
682
|
+
const referer = request.headers.get("referer");
|
|
683
|
+
if (referer) return isLocalUrl(referer);
|
|
684
|
+
return true;
|
|
685
|
+
}
|
|
686
|
+
function isLocalUrl(value) {
|
|
687
|
+
try {
|
|
688
|
+
const url = new URL(value);
|
|
689
|
+
return localHosts.has(url.hostname.toLowerCase());
|
|
690
|
+
} catch {
|
|
691
|
+
return false;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
//#endregion
|
|
695
|
+
//#region src/server/autopilot-push-dispatch.ts
|
|
696
|
+
async function approvePreparedDiffPushWithDispatch(rawInput, paths = runtimePaths(), dependencies = {}) {
|
|
697
|
+
const approval = await approvePreparedDiffPushWithPolicy(rawInput, paths);
|
|
698
|
+
if (!approval.ok || !approval.preparedDiff) return approval;
|
|
699
|
+
return dispatchApprovedPreparedDiffPush(approval, paths, dependencies);
|
|
700
|
+
}
|
|
701
|
+
async function dispatchApprovedPreparedDiffPush(approval, paths, dependencies) {
|
|
702
|
+
const preparedDiff = approval.preparedDiff;
|
|
703
|
+
if (!preparedDiff) return approval;
|
|
704
|
+
const mode = await readPushOnApprovalMode(paths);
|
|
705
|
+
if (mode === "off") return withDispatchData(approval, {
|
|
706
|
+
mode,
|
|
707
|
+
status: "off",
|
|
708
|
+
message: "Push dispatch is disabled by autopilot.pushOnApproval=off."
|
|
709
|
+
}, "Recorded prepared diff push approval. Push dispatch is disabled by autopilot.pushOnApproval=off.");
|
|
710
|
+
const dispatch = mode === "verify-then-push" && preparedDiff.verificationStatus !== "passed" ? verifyThenPushDispatch(preparedDiff) : pushDispatch(preparedDiff);
|
|
711
|
+
const invokeWorkflow = dependencies.invokeWorkflow ?? invokeAutopilotWorkflow;
|
|
712
|
+
const approvalId = approval.approvals?.[0]?.id ?? null;
|
|
713
|
+
let receipt;
|
|
714
|
+
try {
|
|
715
|
+
receipt = await invokeWorkflowForRuntimeHome(paths, dispatch, invokeWorkflow);
|
|
716
|
+
} catch (error) {
|
|
717
|
+
const message = errorMessage$3(error);
|
|
718
|
+
await recordDispatchFailure(preparedDiff, mode, dispatch, approvalId, message, paths);
|
|
719
|
+
return {
|
|
720
|
+
...withDispatchData(approval, {
|
|
721
|
+
mode,
|
|
722
|
+
status: "dispatch-failed",
|
|
723
|
+
workflow: dispatch.workflow,
|
|
724
|
+
message
|
|
725
|
+
}, `Recorded prepared diff push approval, but workflow dispatch failed: ${message}`),
|
|
726
|
+
requires: ["workflowDispatch"],
|
|
727
|
+
errors: [message]
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
const workflowSummary = await addWorkflowSummary({
|
|
731
|
+
workflow: dispatch.workflow.replaceAll("-", "_"),
|
|
732
|
+
runId: receipt.runId,
|
|
733
|
+
status: "pending",
|
|
734
|
+
summary: {
|
|
735
|
+
event: "prepared_diff_push_approval_dispatch",
|
|
736
|
+
mode,
|
|
737
|
+
approvalId,
|
|
738
|
+
preparedDiffId: preparedDiff.id,
|
|
739
|
+
worktreeId: preparedDiff.worktreeId,
|
|
740
|
+
repoId: preparedDiff.repoId,
|
|
741
|
+
repoFullName: preparedDiff.repoFullName,
|
|
742
|
+
prNumber: preparedDiff.prNumber,
|
|
743
|
+
workflow: dispatch.workflow,
|
|
744
|
+
input: dispatch.input,
|
|
745
|
+
dispatchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
746
|
+
}
|
|
747
|
+
}, paths).catch(async (error) => {
|
|
748
|
+
const message = errorMessage$3(error);
|
|
749
|
+
await recordDispatchSummaryFailure(preparedDiff, mode, dispatch, approvalId, receipt.runId, message, paths);
|
|
750
|
+
return null;
|
|
751
|
+
});
|
|
752
|
+
const summaryFailedMessage = workflowSummary ? void 0 : " Workflow summary recording failed; the workflow was still admitted.";
|
|
753
|
+
return {
|
|
754
|
+
...withDispatchData(approval, {
|
|
755
|
+
mode,
|
|
756
|
+
status: "dispatched",
|
|
757
|
+
workflow: dispatch.workflow,
|
|
758
|
+
runId: receipt.runId,
|
|
759
|
+
workflowSummaryId: workflowSummary?.id ?? null,
|
|
760
|
+
message: `${dispatch.message}${summaryFailedMessage ?? ""}`
|
|
761
|
+
}, `${dispatch.message}${summaryFailedMessage ?? ""}`),
|
|
762
|
+
...workflowSummary ? {} : {
|
|
763
|
+
requires: ["workflowSummary"],
|
|
764
|
+
errors: [summaryFailedMessage.trim()]
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
function pushDispatch(preparedDiff) {
|
|
769
|
+
return {
|
|
770
|
+
workflow: "push-pr-autofix",
|
|
771
|
+
input: {
|
|
772
|
+
preparedDiffId: preparedDiff.id,
|
|
773
|
+
lockOwner: "approval_push_pr_autofix"
|
|
774
|
+
},
|
|
775
|
+
message: `Approved prepared diff ${preparedDiff.id}; dispatched push-pr-autofix workflow.`
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
function verifyThenPushDispatch(preparedDiff) {
|
|
779
|
+
return {
|
|
780
|
+
workflow: "verify-then-push-pr-autofix",
|
|
781
|
+
input: {
|
|
782
|
+
preparedDiffId: preparedDiff.id,
|
|
783
|
+
worktreeId: preparedDiff.worktreeId
|
|
784
|
+
},
|
|
785
|
+
message: `Approved prepared diff ${preparedDiff.id}; dispatched verify-then-push-pr-autofix workflow.`
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
async function readPushOnApprovalMode(paths) {
|
|
789
|
+
const value = (await readRuntimeJson(paths.config, parseAppConfig)).autopilot?.pushOnApproval;
|
|
790
|
+
return value === "push" || value === "off" ? value : "verify-then-push";
|
|
791
|
+
}
|
|
792
|
+
async function invokeWorkflowForRuntimeHome(paths, dispatch, invokeWorkflow) {
|
|
793
|
+
process.env.NEONDECK_HOME = paths.home;
|
|
794
|
+
return invokeWorkflow(dispatch.workflow, dispatch.input);
|
|
795
|
+
}
|
|
796
|
+
async function invokeAutopilotWorkflow(workflow, input) {
|
|
797
|
+
if (workflow === "push-pr-autofix") return invokeWorkflow((await import("./push-pr-autofix-BFNSEASU.js")).default, { input });
|
|
798
|
+
return invokeWorkflow((await import("./verify-then-push-pr-autofix-CIl5ZwZm.js")).default, { input });
|
|
799
|
+
}
|
|
800
|
+
async function recordDispatchFailure(preparedDiff, mode, dispatch, approvalId, error, paths) {
|
|
801
|
+
await addWorkflowSummary({
|
|
802
|
+
workflow: dispatch.workflow.replaceAll("-", "_"),
|
|
803
|
+
status: "failed",
|
|
804
|
+
summary: {
|
|
805
|
+
event: "prepared_diff_push_approval_dispatch",
|
|
806
|
+
mode,
|
|
807
|
+
approvalId,
|
|
808
|
+
preparedDiffId: preparedDiff.id,
|
|
809
|
+
worktreeId: preparedDiff.worktreeId,
|
|
810
|
+
repoId: preparedDiff.repoId,
|
|
811
|
+
repoFullName: preparedDiff.repoFullName,
|
|
812
|
+
prNumber: preparedDiff.prNumber,
|
|
813
|
+
workflow: dispatch.workflow,
|
|
814
|
+
input: dispatch.input,
|
|
815
|
+
error,
|
|
816
|
+
dispatchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
817
|
+
}
|
|
818
|
+
}, paths).catch(() => void 0);
|
|
819
|
+
await addNotification({
|
|
820
|
+
level: "attention",
|
|
821
|
+
title: "Push dispatch failed",
|
|
822
|
+
message: `Prepared diff ${preparedDiff.id} was approved, but workflow dispatch failed: ${error}`,
|
|
823
|
+
source: "autopilot",
|
|
824
|
+
sourceId: `prepared-diff:${preparedDiff.id}:push-dispatch-failed`,
|
|
825
|
+
data: {
|
|
826
|
+
approvalId,
|
|
827
|
+
preparedDiffId: preparedDiff.id,
|
|
828
|
+
worktreeId: preparedDiff.worktreeId,
|
|
829
|
+
workflow: dispatch.workflow,
|
|
830
|
+
error
|
|
831
|
+
}
|
|
832
|
+
}, paths).catch(() => void 0);
|
|
833
|
+
}
|
|
834
|
+
async function recordDispatchSummaryFailure(preparedDiff, mode, dispatch, approvalId, runId, error, paths) {
|
|
835
|
+
await addNotification({
|
|
836
|
+
level: "attention",
|
|
837
|
+
title: "Push dispatch audit failed",
|
|
838
|
+
message: `Prepared diff ${preparedDiff.id} dispatched ${dispatch.workflow} (${runId}), but workflow summary recording failed: ${error}`,
|
|
839
|
+
source: "autopilot",
|
|
840
|
+
sourceId: `prepared-diff:${preparedDiff.id}:push-dispatch-summary-failed:${runId}`,
|
|
841
|
+
data: {
|
|
842
|
+
mode,
|
|
843
|
+
approvalId,
|
|
844
|
+
preparedDiffId: preparedDiff.id,
|
|
845
|
+
worktreeId: preparedDiff.worktreeId,
|
|
846
|
+
workflow: dispatch.workflow,
|
|
847
|
+
runId,
|
|
848
|
+
error
|
|
849
|
+
}
|
|
850
|
+
}, paths).catch(() => void 0);
|
|
851
|
+
}
|
|
852
|
+
async function invokeWorkflow(workflow, request) {
|
|
853
|
+
return invoke(workflow, request);
|
|
854
|
+
}
|
|
855
|
+
function errorMessage$3(error) {
|
|
856
|
+
return error instanceof Error ? error.message : String(error);
|
|
857
|
+
}
|
|
858
|
+
function withDispatchData(approval, dispatch, message) {
|
|
859
|
+
const existing = approval.data && typeof approval.data === "object" && !Array.isArray(approval.data) ? approval.data : {};
|
|
860
|
+
return {
|
|
861
|
+
...approval,
|
|
862
|
+
message,
|
|
863
|
+
data: asJsonValue({
|
|
864
|
+
...existing,
|
|
865
|
+
pushApprovalDispatch: dispatch,
|
|
866
|
+
dispatchedPushRunId: typeof dispatch.runId === "string" ? dispatch.runId : null,
|
|
867
|
+
workflowSummaryId: typeof dispatch.workflowSummaryId === "string" ? dispatch.workflowSummaryId : null
|
|
868
|
+
})
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
//#endregion
|
|
872
|
+
//#region src/server/http.ts
|
|
873
|
+
async function safeJsonBody(c) {
|
|
874
|
+
return c.req.json().catch(() => ({}));
|
|
875
|
+
}
|
|
876
|
+
async function safeJsonObject(c) {
|
|
877
|
+
const body = await safeJsonBody(c);
|
|
878
|
+
return body && typeof body === "object" && !Array.isArray(body) ? body : {};
|
|
879
|
+
}
|
|
880
|
+
function boundedQueryLimit(value, fallback) {
|
|
881
|
+
if (!value) return fallback;
|
|
882
|
+
const limit = Number(value);
|
|
883
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100) return void 0;
|
|
884
|
+
return limit;
|
|
885
|
+
}
|
|
886
|
+
function queryNumber(value) {
|
|
887
|
+
if (!value) return void 0;
|
|
888
|
+
const parsed = Number(value);
|
|
889
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
890
|
+
}
|
|
891
|
+
function queryBoolean(value) {
|
|
892
|
+
if (!value) return void 0;
|
|
893
|
+
if (value === "1" || value === "true") return true;
|
|
894
|
+
if (value === "0" || value === "false") return false;
|
|
895
|
+
}
|
|
896
|
+
function kiloSessionQuery(c) {
|
|
897
|
+
return {
|
|
898
|
+
limit: queryNumber(c.req.query("limit")),
|
|
899
|
+
offset: queryNumber(c.req.query("offset")),
|
|
900
|
+
maxBytes: queryNumber(c.req.query("maxBytes")),
|
|
901
|
+
includeFullTranscript: queryBoolean(c.req.query("includeFullTranscript")),
|
|
902
|
+
includeToolOutput: queryBoolean(c.req.query("includeToolOutput")),
|
|
903
|
+
includeDiff: queryBoolean(c.req.query("includeDiff")),
|
|
904
|
+
requesterSurface: c.req.query("requesterSurface") ?? "dashboard",
|
|
905
|
+
readReason: c.req.query("readReason") ?? "dashboard-kilo-session-read"
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
function preparedDiffHttpStatus(result) {
|
|
909
|
+
if (result.ok) return 200;
|
|
910
|
+
if (result.error?.code === "PREPARED_DIFF_NOT_FOUND") return 404;
|
|
911
|
+
return 400;
|
|
912
|
+
}
|
|
913
|
+
//#endregion
|
|
914
|
+
//#region src/server/routes/autopilot.ts
|
|
915
|
+
function createAutopilotRoutes(paths) {
|
|
916
|
+
const routes = new Hono();
|
|
917
|
+
routes.post("/autopilot/triage-pr-event", async (c) => {
|
|
918
|
+
const result = await triagePrEvent(await safeJsonBody(c));
|
|
919
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
920
|
+
});
|
|
921
|
+
routes.post("/autopilot/prepare-pr-worktree", async (c) => {
|
|
922
|
+
const result = await preparePrWorktree(await safeJsonBody(c), paths);
|
|
923
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
924
|
+
});
|
|
925
|
+
routes.post("/autopilot/fix-pr-ci-failure", async (c) => {
|
|
926
|
+
const result = await fixPrCiFailure(await safeJsonBody(c), paths);
|
|
927
|
+
recordHandledPrApiResult(paths, "api:fix_pr_ci_failure", result);
|
|
928
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
929
|
+
});
|
|
930
|
+
routes.post("/autopilot/fix-pr-review-feedback", async (c) => {
|
|
931
|
+
const result = await fixPrReviewFeedback(await safeJsonBody(c), paths);
|
|
932
|
+
recordHandledPrApiResult(paths, "api:fix_pr_review_feedback", result);
|
|
933
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
934
|
+
});
|
|
935
|
+
routes.post("/autopilot/comment-pr-autofix-result", async (c) => {
|
|
936
|
+
const result = await commentPrAutofixResult(await safeJsonBody(c), paths);
|
|
937
|
+
recordHandledPrApiResult(paths, "api:comment_pr_autofix_result", result);
|
|
938
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
939
|
+
});
|
|
940
|
+
routes.post("/autopilot/approvals/:id/resolve", async (c) => {
|
|
941
|
+
const input = await safeJsonObject(c);
|
|
942
|
+
const decision = input.decision;
|
|
943
|
+
if (decision !== "approve" && decision !== "deny") return c.json({
|
|
944
|
+
ok: false,
|
|
945
|
+
action: "autopilot_approval_resolve",
|
|
946
|
+
changed: false,
|
|
947
|
+
message: "Decision must be approve or deny.",
|
|
948
|
+
errors: ["decision is required."]
|
|
949
|
+
}, 400);
|
|
950
|
+
const executionResult = await resolveExecutionApproval({
|
|
951
|
+
id: c.req.param("id"),
|
|
952
|
+
decision: decision === "approve" ? "allow-once" : "deny",
|
|
953
|
+
approverSurface: "dashboard"
|
|
954
|
+
}, paths);
|
|
955
|
+
if (executionResult.ok || !approvalNotFound(executionResult.message)) return c.json(executionResult, executionResult.ok ? 200 : 400);
|
|
956
|
+
const approval = readApprovalRecord(c.req.param("id"), paths);
|
|
957
|
+
if (!approval) return c.json({
|
|
958
|
+
ok: false,
|
|
959
|
+
action: "autopilot_approval_resolve",
|
|
960
|
+
changed: false,
|
|
961
|
+
message: `Autopilot approval "${c.req.param("id")}" was not found.`,
|
|
962
|
+
errors: ["approval not found."]
|
|
963
|
+
}, 404);
|
|
964
|
+
const result = decision === "approve" ? await approvePreparedDiffPushWithDispatch({
|
|
965
|
+
preparedDiffId: approval.preparedDiffId,
|
|
966
|
+
confirm: true,
|
|
967
|
+
reason: "Approved from dashboard Autopilot panel.",
|
|
968
|
+
approverSurface: "dashboard"
|
|
969
|
+
}, paths) : await resolvePreparedDiffRevisionFromRoute(approval.preparedDiffId, input, paths);
|
|
970
|
+
return c.json(result, preparedDiffHttpStatus(result));
|
|
971
|
+
});
|
|
972
|
+
routes.get("/prepared-diffs", async (c) => {
|
|
973
|
+
const result = await listPreparedDiffs({
|
|
974
|
+
status: c.req.query("status") || void 0,
|
|
975
|
+
includeTerminal: queryBoolean(c.req.query("includeTerminal")),
|
|
976
|
+
repoId: c.req.query("repoId") || void 0
|
|
977
|
+
}, paths);
|
|
978
|
+
return c.json(result, preparedDiffHttpStatus(result));
|
|
979
|
+
});
|
|
980
|
+
routes.get("/prepared-diffs/:id/summary", async (c) => {
|
|
981
|
+
const result = await readPreparedDiffSummary({ preparedDiffId: c.req.param("id") }, paths);
|
|
982
|
+
return c.json(result, preparedDiffHttpStatus(result));
|
|
983
|
+
});
|
|
984
|
+
routes.get("/prepared-diffs/:id/files", async (c) => {
|
|
985
|
+
const result = await readPreparedDiffChangedFiles({ preparedDiffId: c.req.param("id") }, paths);
|
|
986
|
+
return c.json(result, preparedDiffHttpStatus(result));
|
|
987
|
+
});
|
|
988
|
+
routes.get("/prepared-diffs/:id/files/diff", async (c) => {
|
|
989
|
+
const result = await readPreparedDiffFileDiff({
|
|
990
|
+
preparedDiffId: c.req.param("id"),
|
|
991
|
+
path: c.req.query("path"),
|
|
992
|
+
maxPatchBytes: queryNumber(c.req.query("maxPatchBytes"))
|
|
993
|
+
}, paths);
|
|
994
|
+
return c.json(result, preparedDiffHttpStatus(result));
|
|
995
|
+
});
|
|
996
|
+
routes.post("/prepared-diffs/:id/approve-push", async (c) => {
|
|
997
|
+
const result = await approvePreparedDiffPushWithDispatch({
|
|
998
|
+
...await safeJsonObject(c),
|
|
999
|
+
preparedDiffId: c.req.param("id")
|
|
1000
|
+
}, paths);
|
|
1001
|
+
return c.json(result, preparedDiffHttpStatus(result));
|
|
1002
|
+
});
|
|
1003
|
+
routes.post("/prepared-diffs/:id/request-revision", async (c) => {
|
|
1004
|
+
const body = await safeJsonObject(c);
|
|
1005
|
+
const requested = await requestPreparedDiffRevision({
|
|
1006
|
+
...body,
|
|
1007
|
+
preparedDiffId: c.req.param("id")
|
|
1008
|
+
}, paths);
|
|
1009
|
+
const result = requested.ok && (body.runRevisionNow === true || body.runNow === true) ? await runPreparedDiffRevision({
|
|
1010
|
+
preparedDiffId: c.req.param("id"),
|
|
1011
|
+
reason: typeof body.reason === "string" ? body.reason : void 0,
|
|
1012
|
+
approverSurface: typeof body.approverSurface === "string" ? body.approverSurface : "dashboard"
|
|
1013
|
+
}, paths) : requested;
|
|
1014
|
+
return c.json(result, preparedDiffHttpStatus(result));
|
|
1015
|
+
});
|
|
1016
|
+
routes.post("/prepared-diffs/:id/run-revision", async (c) => {
|
|
1017
|
+
const result = await runPreparedDiffRevision({
|
|
1018
|
+
...await safeJsonObject(c),
|
|
1019
|
+
preparedDiffId: c.req.param("id")
|
|
1020
|
+
}, paths);
|
|
1021
|
+
return c.json(result, preparedDiffHttpStatus(result));
|
|
1022
|
+
});
|
|
1023
|
+
routes.post("/prepared-diffs/:id/abandon", async (c) => {
|
|
1024
|
+
const result = await abandonPreparedDiffWithRevisionAbort({
|
|
1025
|
+
...await safeJsonObject(c),
|
|
1026
|
+
preparedDiffId: c.req.param("id")
|
|
1027
|
+
}, paths);
|
|
1028
|
+
return c.json(result, preparedDiffHttpStatus(result));
|
|
1029
|
+
});
|
|
1030
|
+
routes.get("/prepared-diffs/:id/worktree-path", async (c) => {
|
|
1031
|
+
const result = await openPreparedDiffWorktree({ preparedDiffId: c.req.param("id") }, paths);
|
|
1032
|
+
return c.json(result, preparedDiffHttpStatus(result));
|
|
1033
|
+
});
|
|
1034
|
+
routes.post("/prepared-diffs/:id/verify", async (c) => {
|
|
1035
|
+
const result = await runPreparedDiffVerification({
|
|
1036
|
+
...await safeJsonObject(c),
|
|
1037
|
+
preparedDiffId: c.req.param("id")
|
|
1038
|
+
}, paths);
|
|
1039
|
+
return c.json(result, preparedDiffHttpStatus(result));
|
|
1040
|
+
});
|
|
1041
|
+
routes.get("/prepared-diffs/:id/recovery", async (c) => {
|
|
1042
|
+
const result = await readAutopilotRecoveryOptions({ preparedDiffId: c.req.param("id") }, paths);
|
|
1043
|
+
return c.json(result, preparedDiffHttpStatus(result));
|
|
1044
|
+
});
|
|
1045
|
+
routes.post("/prepared-diffs/:id/recovery/run", async (c) => {
|
|
1046
|
+
const result = await runAutopilotRecoveryAction({
|
|
1047
|
+
...await safeJsonObject(c),
|
|
1048
|
+
preparedDiffId: c.req.param("id")
|
|
1049
|
+
}, paths, { allowRevisionDispatch: true });
|
|
1050
|
+
recordHandledPrApiResult(paths, "api:autopilot_recovery", result);
|
|
1051
|
+
return c.json(result, preparedDiffHttpStatus(result));
|
|
1052
|
+
});
|
|
1053
|
+
routes.post("/autopilot/verify-pr-worktree", async (c) => {
|
|
1054
|
+
const result = await verifyPrWorktree(await safeJsonBody(c), paths);
|
|
1055
|
+
recordHandledPrApiResult(paths, "api:verify_pr_worktree", result);
|
|
1056
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1057
|
+
});
|
|
1058
|
+
routes.post("/autopilot/push-pr-autofix", async (c) => {
|
|
1059
|
+
const result = await pushPrAutofix(await safeJsonBody(c), paths);
|
|
1060
|
+
recordHandledPrApiResult(paths, "api:push_pr_autofix", result);
|
|
1061
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1062
|
+
});
|
|
1063
|
+
routes.post("/prepared-diffs/:id/push", async (c) => {
|
|
1064
|
+
const result = await pushPrAutofix({
|
|
1065
|
+
...await safeJsonObject(c),
|
|
1066
|
+
preparedDiffId: c.req.param("id")
|
|
1067
|
+
}, paths);
|
|
1068
|
+
recordHandledPrApiResult(paths, "api:push_pr_autofix", result);
|
|
1069
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1070
|
+
});
|
|
1071
|
+
return routes;
|
|
1072
|
+
}
|
|
1073
|
+
function approvalNotFound(message) {
|
|
1074
|
+
return Boolean(message?.includes("was not found"));
|
|
1075
|
+
}
|
|
1076
|
+
async function resolvePreparedDiffRevisionFromRoute(preparedDiffId, input, paths) {
|
|
1077
|
+
const reason = typeof input.reason === "string" ? input.reason : void 0;
|
|
1078
|
+
const runRevisionNow = input.runRevisionNow !== false;
|
|
1079
|
+
if (runRevisionNow && !reason?.trim()) return {
|
|
1080
|
+
ok: false,
|
|
1081
|
+
action: "prepared_diff_request_revision",
|
|
1082
|
+
changed: false,
|
|
1083
|
+
message: "Running a prepared-diff revision requires a revision note.",
|
|
1084
|
+
requires: ["reason"],
|
|
1085
|
+
errors: ["reason is required."]
|
|
1086
|
+
};
|
|
1087
|
+
const requested = await requestPreparedDiffRevision({
|
|
1088
|
+
preparedDiffId,
|
|
1089
|
+
reason,
|
|
1090
|
+
approverSurface: "dashboard"
|
|
1091
|
+
}, paths);
|
|
1092
|
+
if (!requested.ok || !runRevisionNow) return requested;
|
|
1093
|
+
const run = await runPreparedDiffRevision({
|
|
1094
|
+
preparedDiffId,
|
|
1095
|
+
reason,
|
|
1096
|
+
approverSurface: "dashboard"
|
|
1097
|
+
}, paths);
|
|
1098
|
+
return {
|
|
1099
|
+
...run,
|
|
1100
|
+
data: {
|
|
1101
|
+
request: requested,
|
|
1102
|
+
run
|
|
1103
|
+
}
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
//#endregion
|
|
1107
|
+
//#region src/server/routes/commands.ts
|
|
1108
|
+
function createCommandRoutes() {
|
|
1109
|
+
const routes = new Hono();
|
|
1110
|
+
routes.get("/", (c) => {
|
|
1111
|
+
return c.json({ items: supportedCommands() });
|
|
1112
|
+
});
|
|
1113
|
+
return routes;
|
|
1114
|
+
}
|
|
1115
|
+
//#endregion
|
|
1116
|
+
//#region src/server/routes/config.ts
|
|
1117
|
+
function createConfigRoutes(paths) {
|
|
1118
|
+
const routes = new Hono();
|
|
1119
|
+
routes.get("/providers", async (c) => {
|
|
1120
|
+
return c.json(await readProviderConfig(paths));
|
|
1121
|
+
});
|
|
1122
|
+
routes.post("/config/reload", async (c) => {
|
|
1123
|
+
return c.json(await reloadConfig(paths));
|
|
1124
|
+
});
|
|
1125
|
+
routes.post("/models", async (c) => {
|
|
1126
|
+
return c.json(await updateAgentModels(await c.req.json(), paths));
|
|
1127
|
+
});
|
|
1128
|
+
routes.post("/providers/kilocode", async (c) => {
|
|
1129
|
+
const input = await c.req.json();
|
|
1130
|
+
return c.json(await updateProviderConfig({
|
|
1131
|
+
...input,
|
|
1132
|
+
provider: "kilocode"
|
|
1133
|
+
}, paths));
|
|
1134
|
+
});
|
|
1135
|
+
routes.post("/providers/:provider", async (c) => {
|
|
1136
|
+
const input = await safeJsonObject(c);
|
|
1137
|
+
const provider = c.req.param("provider");
|
|
1138
|
+
if (!isRegisteredProvider(provider)) return c.json({
|
|
1139
|
+
ok: false,
|
|
1140
|
+
changed: false,
|
|
1141
|
+
action: "config_update_provider",
|
|
1142
|
+
message: `Unsupported provider "${provider}".`
|
|
1143
|
+
}, 400);
|
|
1144
|
+
return c.json(await updateProviderConfig({
|
|
1145
|
+
...input,
|
|
1146
|
+
provider
|
|
1147
|
+
}, paths));
|
|
1148
|
+
});
|
|
1149
|
+
routes.post("/worktrees/policy", async (c) => {
|
|
1150
|
+
const result = await updateWorktreePolicy(await safeJsonBody(c), paths);
|
|
1151
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1152
|
+
});
|
|
1153
|
+
routes.post("/repos/:repoId/autopilot-policy", async (c) => {
|
|
1154
|
+
const result = await updateRepoAutopilotPolicy({
|
|
1155
|
+
...await safeJsonObject(c),
|
|
1156
|
+
repoId: c.req.param("repoId")
|
|
1157
|
+
}, paths);
|
|
1158
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1159
|
+
});
|
|
1160
|
+
routes.get("/dashboard/config", async (c) => {
|
|
1161
|
+
try {
|
|
1162
|
+
return c.json(await readRuntimeJson(paths.dashboard, parseDashboardConfig));
|
|
1163
|
+
} catch (error) {
|
|
1164
|
+
if (error instanceof ConfigValidationError) return c.json({
|
|
1165
|
+
error: "Invalid dashboard config",
|
|
1166
|
+
message: error.message,
|
|
1167
|
+
path: error.path
|
|
1168
|
+
}, 500);
|
|
1169
|
+
throw error;
|
|
1170
|
+
}
|
|
1171
|
+
});
|
|
1172
|
+
routes.post("/dashboard/config", async (c) => {
|
|
1173
|
+
const result = await updateDashboardLayout(await safeJsonBody(c), paths);
|
|
1174
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1175
|
+
});
|
|
1176
|
+
routes.post("/dashboard/preset", async (c) => {
|
|
1177
|
+
const result = await applyDashboardPreset(await safeJsonBody(c), paths);
|
|
1178
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1179
|
+
});
|
|
1180
|
+
return routes;
|
|
1181
|
+
}
|
|
1182
|
+
//#endregion
|
|
1183
|
+
//#region src/server/routes/execution.ts
|
|
1184
|
+
function createExecutionRoutes(paths) {
|
|
1185
|
+
const routes = new Hono();
|
|
1186
|
+
routes.get("/policy", async (c) => {
|
|
1187
|
+
return c.json(await readExecutionPolicy(paths));
|
|
1188
|
+
});
|
|
1189
|
+
routes.post("/policy", async (c) => {
|
|
1190
|
+
const input = await safeJsonBody(c);
|
|
1191
|
+
return c.json(await updateExecutionPolicy(input, paths));
|
|
1192
|
+
});
|
|
1193
|
+
routes.post("/check", async (c) => {
|
|
1194
|
+
const input = await safeJsonBody(c);
|
|
1195
|
+
return c.json(await checkExecutionPolicy(input, paths));
|
|
1196
|
+
});
|
|
1197
|
+
routes.get("/approvals", async (c) => {
|
|
1198
|
+
const includeResolved = c.req.query("includeResolved") === "1";
|
|
1199
|
+
return c.json(await listExecutionApprovals(paths, { includeResolved }));
|
|
1200
|
+
});
|
|
1201
|
+
routes.post("/approvals", async (c) => {
|
|
1202
|
+
const result = await requestExecutionApproval(await safeJsonBody(c), paths);
|
|
1203
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1204
|
+
});
|
|
1205
|
+
routes.post("/approvals/:id/resolve", async (c) => {
|
|
1206
|
+
const result = await resolveExecutionApproval({
|
|
1207
|
+
...await c.req.json().catch(() => ({})),
|
|
1208
|
+
id: c.req.param("id")
|
|
1209
|
+
}, paths);
|
|
1210
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1211
|
+
});
|
|
1212
|
+
routes.post("/run", async (c) => {
|
|
1213
|
+
const result = await runApprovedExecution(await safeJsonBody(c), paths);
|
|
1214
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1215
|
+
});
|
|
1216
|
+
routes.post("/exedev/sync-checkout", async (c) => {
|
|
1217
|
+
const result = await syncExeDevCheckout(await safeJsonBody(c), paths);
|
|
1218
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1219
|
+
});
|
|
1220
|
+
return routes;
|
|
1221
|
+
}
|
|
1222
|
+
//#endregion
|
|
1223
|
+
//#region src/server/routes/github.ts
|
|
1224
|
+
function createGitHubRoutes(paths) {
|
|
1225
|
+
const routes = new Hono();
|
|
1226
|
+
routes.get("/prs", async (c) => {
|
|
1227
|
+
const result = await listGitHubPrQueue(paths);
|
|
1228
|
+
const queue = result.ok && result.data && typeof result.data === "object" ? result.data.queue : void 0;
|
|
1229
|
+
if (queue && typeof queue === "object") return c.json(queue);
|
|
1230
|
+
return c.json({
|
|
1231
|
+
error: result.message,
|
|
1232
|
+
items: [],
|
|
1233
|
+
issues: (result.errors ?? [result.message]).map((message) => ({
|
|
1234
|
+
type: "search-error",
|
|
1235
|
+
message
|
|
1236
|
+
}))
|
|
1237
|
+
}, result.requires?.includes("GITHUB_TOKEN") ? 503 : 502);
|
|
1238
|
+
});
|
|
1239
|
+
routes.get("/prs/:owner/:repo/:number", async (c) => {
|
|
1240
|
+
const target = prTargetFromParams(c.req.param("owner"), c.req.param("repo"), c.req.param("number"));
|
|
1241
|
+
if (!target.ok) return c.json(target.result, 400);
|
|
1242
|
+
const result = await getGitHubPullRequest({
|
|
1243
|
+
repo: target.input.repo,
|
|
1244
|
+
number: target.input.prNumber
|
|
1245
|
+
}, paths);
|
|
1246
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1247
|
+
});
|
|
1248
|
+
routes.get("/prs/:owner/:repo/:number/files/diff", async (c) => {
|
|
1249
|
+
const owner = c.req.param("owner");
|
|
1250
|
+
const repo = c.req.param("repo");
|
|
1251
|
+
const number = Number(c.req.param("number"));
|
|
1252
|
+
if (!Number.isInteger(number) || number <= 0) return c.json({
|
|
1253
|
+
ok: false,
|
|
1254
|
+
action: "github_pr_file_diff_get",
|
|
1255
|
+
changed: false,
|
|
1256
|
+
message: "Invalid PR number.",
|
|
1257
|
+
requires: ["prNumber"]
|
|
1258
|
+
}, 400);
|
|
1259
|
+
const result = await getGitHubPrFileDiff({
|
|
1260
|
+
repo: `${owner}/${repo}`,
|
|
1261
|
+
prNumber: number,
|
|
1262
|
+
path: c.req.query("path") ?? "",
|
|
1263
|
+
headSha: c.req.query("head")?.trim() || void 0,
|
|
1264
|
+
baseSha: c.req.query("base")?.trim() || void 0,
|
|
1265
|
+
baseRef: c.req.query("baseRef")?.trim() || void 0,
|
|
1266
|
+
source: sourceQuery(c.req.query("source")),
|
|
1267
|
+
maxPatchBytes: queryNumber(c.req.query("maxPatchBytes"))
|
|
1268
|
+
}, paths);
|
|
1269
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1270
|
+
});
|
|
1271
|
+
routes.get("/prs/:owner/:repo/:number/files", async (c) => {
|
|
1272
|
+
const owner = c.req.param("owner");
|
|
1273
|
+
const repo = c.req.param("repo");
|
|
1274
|
+
const number = Number(c.req.param("number"));
|
|
1275
|
+
if (!Number.isInteger(number) || number <= 0) return c.json({
|
|
1276
|
+
ok: false,
|
|
1277
|
+
action: "github_pr_files_get",
|
|
1278
|
+
changed: false,
|
|
1279
|
+
message: "Invalid PR number.",
|
|
1280
|
+
requires: ["prNumber"]
|
|
1281
|
+
}, 400);
|
|
1282
|
+
const result = await getGitHubPrFiles({
|
|
1283
|
+
repo: `${owner}/${repo}`,
|
|
1284
|
+
prNumber: number,
|
|
1285
|
+
headSha: c.req.query("head")?.trim() || void 0,
|
|
1286
|
+
baseSha: c.req.query("base")?.trim() || void 0,
|
|
1287
|
+
baseRef: c.req.query("baseRef")?.trim() || void 0,
|
|
1288
|
+
patches: patchesQuery(c.req.query("patches")),
|
|
1289
|
+
source: sourceQuery(c.req.query("source"))
|
|
1290
|
+
}, paths);
|
|
1291
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1292
|
+
});
|
|
1293
|
+
routes.get("/prs/:owner/:repo/:number/review-draft", async (c) => {
|
|
1294
|
+
const target = prTargetFromParams(c.req.param("owner"), c.req.param("repo"), c.req.param("number"));
|
|
1295
|
+
if (!target.ok) return c.json(target.result, 400);
|
|
1296
|
+
const result = await getGitHubPrReviewDraft(target.input, paths);
|
|
1297
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1298
|
+
});
|
|
1299
|
+
routes.put("/prs/:owner/:repo/:number/review-draft", async (c) => {
|
|
1300
|
+
const target = prTargetFromParams(c.req.param("owner"), c.req.param("repo"), c.req.param("number"));
|
|
1301
|
+
if (!target.ok) return c.json(target.result, 400);
|
|
1302
|
+
const result = await putGitHubPrReviewDraft(target.input, await safeJsonBody(c), paths);
|
|
1303
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1304
|
+
});
|
|
1305
|
+
routes.post("/prs/:owner/:repo/:number/review-draft/comments", async (c) => {
|
|
1306
|
+
const target = prTargetFromParams(c.req.param("owner"), c.req.param("repo"), c.req.param("number"));
|
|
1307
|
+
if (!target.ok) return c.json(target.result, 400);
|
|
1308
|
+
const result = await postGitHubPrReviewDraftComment(target.input, await safeJsonBody(c), paths);
|
|
1309
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1310
|
+
});
|
|
1311
|
+
routes.patch("/prs/:owner/:repo/:number/review-draft/comments/:id", async (c) => {
|
|
1312
|
+
const target = prTargetFromParams(c.req.param("owner"), c.req.param("repo"), c.req.param("number"));
|
|
1313
|
+
if (!target.ok) return c.json(target.result, 400);
|
|
1314
|
+
const result = await patchGitHubPrReviewDraftComment(target.input, c.req.param("id"), await safeJsonBody(c), paths);
|
|
1315
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1316
|
+
});
|
|
1317
|
+
routes.delete("/prs/:owner/:repo/:number/review-draft/comments/:id", async (c) => {
|
|
1318
|
+
const target = prTargetFromParams(c.req.param("owner"), c.req.param("repo"), c.req.param("number"));
|
|
1319
|
+
if (!target.ok) return c.json(target.result, 400);
|
|
1320
|
+
const result = await deleteGitHubPrReviewDraftComment(target.input, c.req.param("id"), paths);
|
|
1321
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1322
|
+
});
|
|
1323
|
+
routes.delete("/prs/:owner/:repo/:number/review-draft", async (c) => {
|
|
1324
|
+
const target = prTargetFromParams(c.req.param("owner"), c.req.param("repo"), c.req.param("number"));
|
|
1325
|
+
if (!target.ok) return c.json(target.result, 400);
|
|
1326
|
+
const result = await deleteGitHubPrReviewDraft(target.input, paths);
|
|
1327
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1328
|
+
});
|
|
1329
|
+
routes.post("/prs/:owner/:repo/:number/reviews", async (c) => {
|
|
1330
|
+
const target = prTargetFromParams(c.req.param("owner"), c.req.param("repo"), c.req.param("number"));
|
|
1331
|
+
if (!target.ok) return c.json(target.result, 400);
|
|
1332
|
+
const result = await postGitHubPrReview(target.input, await safeJsonBody(c), paths);
|
|
1333
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1334
|
+
});
|
|
1335
|
+
routes.post("/prs/:owner/:repo/:number/review-threads/:threadId/reply", async (c) => {
|
|
1336
|
+
const target = prTargetFromParams(c.req.param("owner"), c.req.param("repo"), c.req.param("number"));
|
|
1337
|
+
if (!target.ok) return c.json(target.result, 400);
|
|
1338
|
+
const result = await postGitHubPrThreadReply(target.input, c.req.param("threadId"), await safeJsonBody(c), paths);
|
|
1339
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1340
|
+
});
|
|
1341
|
+
routes.post("/prs/:owner/:repo/:number/review-threads/:threadId/resolve", async (c) => {
|
|
1342
|
+
const target = prTargetFromParams(c.req.param("owner"), c.req.param("repo"), c.req.param("number"));
|
|
1343
|
+
if (!target.ok) return c.json(target.result, 400);
|
|
1344
|
+
const result = await postGitHubPrThreadResolution(target.input, c.req.param("threadId"), true, paths);
|
|
1345
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1346
|
+
});
|
|
1347
|
+
routes.post("/prs/:owner/:repo/:number/review-threads/:threadId/unresolve", async (c) => {
|
|
1348
|
+
const target = prTargetFromParams(c.req.param("owner"), c.req.param("repo"), c.req.param("number"));
|
|
1349
|
+
if (!target.ok) return c.json(target.result, 400);
|
|
1350
|
+
const result = await postGitHubPrThreadResolution(target.input, c.req.param("threadId"), false, paths);
|
|
1351
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1352
|
+
});
|
|
1353
|
+
routes.post("/prs/event-state", async (c) => {
|
|
1354
|
+
const result = await getGitHubPrEventState(await safeJsonBody(c), paths);
|
|
1355
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1356
|
+
});
|
|
1357
|
+
routes.post("/prs/review-threads", async (c) => {
|
|
1358
|
+
const result = await getGitHubPrReviewThreads(await safeJsonBody(c), paths);
|
|
1359
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1360
|
+
});
|
|
1361
|
+
routes.post("/prs/requested-changes", async (c) => {
|
|
1362
|
+
const result = await getGitHubPrRequestedChanges(await safeJsonBody(c), paths);
|
|
1363
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1364
|
+
});
|
|
1365
|
+
routes.post("/prs/branch-permissions", async (c) => {
|
|
1366
|
+
const result = await getGitHubPrBranchPermissions(await safeJsonBody(c), paths);
|
|
1367
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1368
|
+
});
|
|
1369
|
+
routes.post("/prs/comment", async (c) => {
|
|
1370
|
+
const result = await postGitHubPrComment(await safeJsonBody(c), paths);
|
|
1371
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1372
|
+
});
|
|
1373
|
+
return routes;
|
|
1374
|
+
}
|
|
1375
|
+
function prTargetFromParams(owner, repo, numberText) {
|
|
1376
|
+
const number = Number(numberText);
|
|
1377
|
+
if (!Number.isInteger(number) || number <= 0) return {
|
|
1378
|
+
ok: false,
|
|
1379
|
+
result: {
|
|
1380
|
+
ok: false,
|
|
1381
|
+
action: "github_pr_review_target",
|
|
1382
|
+
changed: false,
|
|
1383
|
+
message: "Invalid PR number.",
|
|
1384
|
+
requires: ["prNumber"]
|
|
1385
|
+
}
|
|
1386
|
+
};
|
|
1387
|
+
return {
|
|
1388
|
+
ok: true,
|
|
1389
|
+
input: {
|
|
1390
|
+
repo: `${owner}/${repo}`,
|
|
1391
|
+
prNumber: number
|
|
1392
|
+
}
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
function patchesQuery(value) {
|
|
1396
|
+
return value === "none" || value === "all" ? value : void 0;
|
|
1397
|
+
}
|
|
1398
|
+
function sourceQuery(value) {
|
|
1399
|
+
return value === "local" || value === "github" || value === "auto" ? value : void 0;
|
|
1400
|
+
}
|
|
1401
|
+
//#endregion
|
|
1402
|
+
//#region src/modules/handoff/schemas.ts
|
|
1403
|
+
var handoffSourceSchema = v.pipe(v.string(), v.minLength(1), v.maxLength(120));
|
|
1404
|
+
var handoffNoteLevelSchema = v.optional(v.picklist([
|
|
1405
|
+
"info",
|
|
1406
|
+
"ready",
|
|
1407
|
+
"attention",
|
|
1408
|
+
"urgent"
|
|
1409
|
+
]), "info");
|
|
1410
|
+
var handoffNoteInputSchema = v.strictObject({
|
|
1411
|
+
text: v.pipe(v.string(), v.minLength(1), v.maxLength(4096)),
|
|
1412
|
+
source: v.optional(handoffSourceSchema),
|
|
1413
|
+
repo: v.optional(v.pipe(v.string(), v.minLength(1))),
|
|
1414
|
+
pr: v.optional(v.pipe(v.string(), v.minLength(1))),
|
|
1415
|
+
level: handoffNoteLevelSchema
|
|
1416
|
+
});
|
|
1417
|
+
var handoffRegisterPrInputSchema = v.strictObject({
|
|
1418
|
+
ref: v.pipe(v.string(), v.minLength(1)),
|
|
1419
|
+
source: v.optional(handoffSourceSchema),
|
|
1420
|
+
watch: v.optional(v.boolean()),
|
|
1421
|
+
review: v.optional(v.boolean()),
|
|
1422
|
+
note: v.optional(v.pipe(v.string(), v.minLength(1), v.maxLength(4096)))
|
|
1423
|
+
});
|
|
1424
|
+
var handoffWatchPrInputSchema = v.strictObject({
|
|
1425
|
+
ref: v.pipe(v.string(), v.minLength(1)),
|
|
1426
|
+
source: v.optional(handoffSourceSchema),
|
|
1427
|
+
desiredTerminalState: v.optional(v.picklist(["checks", "merged"])),
|
|
1428
|
+
intervalSeconds: v.optional(v.pipe(v.number(), v.integer(), v.minValue(60)))
|
|
1429
|
+
});
|
|
1430
|
+
v.looseObject({
|
|
1431
|
+
ok: v.boolean(),
|
|
1432
|
+
action: v.string(),
|
|
1433
|
+
changed: v.boolean(),
|
|
1434
|
+
message: v.string(),
|
|
1435
|
+
id: v.optional(v.string()),
|
|
1436
|
+
deckUrl: v.optional(v.string())
|
|
1437
|
+
});
|
|
1438
|
+
//#endregion
|
|
1439
|
+
//#region src/modules/handoff/service.ts
|
|
1440
|
+
var deckUrl = "/";
|
|
1441
|
+
async function registerHandoffWatchPr(rawInput, paths = runtimePaths(), dependencies = {}) {
|
|
1442
|
+
await ensureRuntimeHome(paths);
|
|
1443
|
+
const parsed = parseHandoffInput(handoffWatchPrInputSchema, rawInput, "handoff_pr_watch");
|
|
1444
|
+
if (!parsed.ok) return parsed.result;
|
|
1445
|
+
const source = normalizeHandoffSource(parsed.input.source);
|
|
1446
|
+
const prLink = await resolveRegisteredPrLink(parsed.input.ref, paths);
|
|
1447
|
+
if (!prLink.ok) return prLink.result;
|
|
1448
|
+
const desiredTerminalState = parsed.input.desiredTerminalState ?? "checks";
|
|
1449
|
+
const watchResult = await (dependencies.addPrWatch ?? addPrWatch)({
|
|
1450
|
+
ref: prLink.link.ref.id,
|
|
1451
|
+
desiredTerminalState,
|
|
1452
|
+
...parsed.input.intervalSeconds !== void 0 ? { intervalSeconds: parsed.input.intervalSeconds } : {},
|
|
1453
|
+
createdBy: source
|
|
1454
|
+
}, paths);
|
|
1455
|
+
if (!watchResult.ok) return failResult("handoff_pr_watch", watchResult.message, {
|
|
1456
|
+
errors: watchResult.errors,
|
|
1457
|
+
requires: watchResult.requires
|
|
1458
|
+
});
|
|
1459
|
+
const watchId = watchIdFromValue(watchResult.watch);
|
|
1460
|
+
const audit = await addHandoffAudit({
|
|
1461
|
+
event: "watch-pr",
|
|
1462
|
+
source,
|
|
1463
|
+
ref: prLink.link.ref.id,
|
|
1464
|
+
desiredTerminalState,
|
|
1465
|
+
intervalSeconds: parsed.input.intervalSeconds ?? null,
|
|
1466
|
+
watchId: watchId ?? null
|
|
1467
|
+
}, paths);
|
|
1468
|
+
return okResult("handoff_pr_watch", watchResult.changed, watchResult.changed ? `Watching ${prLink.link.ref.id} from ${displaySource(source)}.` : `Watch for ${prLink.link.ref.id} was already current.`, {
|
|
1469
|
+
id: watchId,
|
|
1470
|
+
watch: watchResult.watch,
|
|
1471
|
+
audit
|
|
1472
|
+
});
|
|
1473
|
+
}
|
|
1474
|
+
async function createHandoffNote(rawInput, paths = runtimePaths(), dependencies = {}) {
|
|
1475
|
+
await ensureRuntimeHome(paths);
|
|
1476
|
+
const parsed = parseHandoffInput(handoffNoteInputSchema, rawInput, "handoff_note_create");
|
|
1477
|
+
if (!parsed.ok) return parsed.result;
|
|
1478
|
+
const source = normalizeHandoffSource(parsed.input.source);
|
|
1479
|
+
const prLink = parsed.input.pr ? await resolveRegisteredPrLink(parsed.input.pr, paths) : null;
|
|
1480
|
+
if (prLink && !prLink.ok) return prLink.result;
|
|
1481
|
+
const repoResult = parsed.input.repo ? await resolveRegisteredRepo(parsed.input.repo, paths) : null;
|
|
1482
|
+
if (repoResult && !repoResult.ok) return repoResult.result;
|
|
1483
|
+
const repo = prLink?.link.repo ?? repoResult?.repo ?? null;
|
|
1484
|
+
if (prLink?.link.repo && repoResult?.repo) {
|
|
1485
|
+
if (repoFullName(prLink.link.repo).toLowerCase() !== repoFullName(repoResult.repo).toLowerCase()) return failResult("handoff_note_create", `Note repo "${repoFullName(repoResult.repo)}" does not match PR repo "${repoFullName(prLink.link.repo)}".`, { requires: ["repo"] });
|
|
1486
|
+
}
|
|
1487
|
+
const level = parsed.input.level === "urgent" ? "attention" : parsed.input.level;
|
|
1488
|
+
const sourceId = noteSourceId({
|
|
1489
|
+
source,
|
|
1490
|
+
text: parsed.input.text,
|
|
1491
|
+
repoFullName: repo ? repoFullName(repo) : null,
|
|
1492
|
+
prId: prLink?.link.ref.id ?? null
|
|
1493
|
+
});
|
|
1494
|
+
const notification = await (dependencies.createNotification ?? addNotification)({
|
|
1495
|
+
level,
|
|
1496
|
+
title: `Note from ${displaySource(source)}`,
|
|
1497
|
+
message: parsed.input.text,
|
|
1498
|
+
source,
|
|
1499
|
+
sourceId,
|
|
1500
|
+
data: {
|
|
1501
|
+
kind: "external-note",
|
|
1502
|
+
source,
|
|
1503
|
+
repoId: repo?.id ?? null,
|
|
1504
|
+
repoFullName: repo ? repoFullName(repo) : null,
|
|
1505
|
+
prNumber: prLink?.link.ref.prNumber ?? null,
|
|
1506
|
+
prRef: prLink?.link.ref.id ?? null
|
|
1507
|
+
}
|
|
1508
|
+
}, paths);
|
|
1509
|
+
const audit = await addHandoffAudit({
|
|
1510
|
+
event: "note",
|
|
1511
|
+
source,
|
|
1512
|
+
notificationId: notification.id,
|
|
1513
|
+
repoId: repo?.id ?? null,
|
|
1514
|
+
repoFullName: repo ? repoFullName(repo) : null,
|
|
1515
|
+
prRef: prLink?.link.ref.id ?? null,
|
|
1516
|
+
level
|
|
1517
|
+
}, paths);
|
|
1518
|
+
return okResult("handoff_note_create", true, "Created handoff note.", {
|
|
1519
|
+
id: notification.id,
|
|
1520
|
+
notification,
|
|
1521
|
+
audit
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
async function registerHandoffPr(rawInput, paths = runtimePaths(), dependencies = {}) {
|
|
1525
|
+
await ensureRuntimeHome(paths);
|
|
1526
|
+
const parsed = parseHandoffInput(handoffRegisterPrInputSchema, rawInput, "handoff_pr_register");
|
|
1527
|
+
if (!parsed.ok) return parsed.result;
|
|
1528
|
+
const input = parsed.input;
|
|
1529
|
+
const source = normalizeHandoffSource(input.source);
|
|
1530
|
+
if (input.review === true) {
|
|
1531
|
+
if (!await externalReviewQueueAllowed(paths)) return failResult("handoff_pr_register", "External PR review queueing is disabled by handoff.allowExternalReviewQueue.", { requires: ["handoff.allowExternalReviewQueue"] });
|
|
1532
|
+
}
|
|
1533
|
+
const prLink = await resolveRegisteredPrLink(input.ref, paths);
|
|
1534
|
+
if (!prLink.ok) return prLink.result;
|
|
1535
|
+
const ref = prLink.link.ref.id;
|
|
1536
|
+
const watchEnabled = input.watch ?? true;
|
|
1537
|
+
let watch;
|
|
1538
|
+
let watchChanged = false;
|
|
1539
|
+
let watchId;
|
|
1540
|
+
if (watchEnabled) {
|
|
1541
|
+
const watchResult = await (dependencies.addPrWatch ?? addPrWatch)({
|
|
1542
|
+
ref,
|
|
1543
|
+
desiredTerminalState: "checks",
|
|
1544
|
+
createdBy: source
|
|
1545
|
+
}, paths);
|
|
1546
|
+
if (!watchResult.ok) return failResult("handoff_pr_register", watchResult.message, {
|
|
1547
|
+
errors: watchResult.errors,
|
|
1548
|
+
requires: watchResult.requires
|
|
1549
|
+
});
|
|
1550
|
+
watch = watchResult.watch;
|
|
1551
|
+
watchChanged = watchResult.changed;
|
|
1552
|
+
watchId = watchIdFromValue(watch);
|
|
1553
|
+
}
|
|
1554
|
+
let notification;
|
|
1555
|
+
if (input.note) {
|
|
1556
|
+
const noteResult = await createHandoffNote({
|
|
1557
|
+
text: input.note,
|
|
1558
|
+
source,
|
|
1559
|
+
pr: ref,
|
|
1560
|
+
level: "ready"
|
|
1561
|
+
}, paths, dependencies);
|
|
1562
|
+
if (!noteResult.ok) return noteResult;
|
|
1563
|
+
notification = noteResult.notification;
|
|
1564
|
+
}
|
|
1565
|
+
let review;
|
|
1566
|
+
if (input.review === true) {
|
|
1567
|
+
const invokeReview = dependencies.invokeReviewPrWorkflow ?? invokeReviewPrWorkflow;
|
|
1568
|
+
try {
|
|
1569
|
+
const { runId } = await invokeReview({ ref });
|
|
1570
|
+
review = asJsonValue({
|
|
1571
|
+
workflow: "review-pr-for-human",
|
|
1572
|
+
runId,
|
|
1573
|
+
ref,
|
|
1574
|
+
queued: true
|
|
1575
|
+
});
|
|
1576
|
+
} catch (error) {
|
|
1577
|
+
const message = errorMessage$2(error);
|
|
1578
|
+
const audit = await addHandoffAudit({
|
|
1579
|
+
event: "register-pr",
|
|
1580
|
+
source,
|
|
1581
|
+
ref,
|
|
1582
|
+
watch: watchEnabled,
|
|
1583
|
+
watchId: watchId ?? null,
|
|
1584
|
+
note: Boolean(input.note),
|
|
1585
|
+
notificationId: notificationIdFromValue(notification),
|
|
1586
|
+
review: true,
|
|
1587
|
+
error: message
|
|
1588
|
+
}, paths, { status: "failed" });
|
|
1589
|
+
return failResult("handoff_pr_register", `Review workflow dispatch failed for ${ref}.`, {
|
|
1590
|
+
changed: watchChanged || Boolean(notification),
|
|
1591
|
+
id: watchId ?? notificationIdFromValue(notification),
|
|
1592
|
+
watch,
|
|
1593
|
+
notification,
|
|
1594
|
+
audit,
|
|
1595
|
+
errors: [message],
|
|
1596
|
+
requires: ["workflowDispatch"]
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
const reviewRunId = reviewRunIdFromValue(review);
|
|
1601
|
+
const audit = await addHandoffAudit({
|
|
1602
|
+
event: "register-pr",
|
|
1603
|
+
source,
|
|
1604
|
+
ref,
|
|
1605
|
+
watch: watchEnabled,
|
|
1606
|
+
watchId: watchId ?? null,
|
|
1607
|
+
note: Boolean(input.note),
|
|
1608
|
+
notificationId: notificationIdFromValue(notification),
|
|
1609
|
+
review: input.review === true,
|
|
1610
|
+
reviewRunId
|
|
1611
|
+
}, paths, reviewRunId ? { runId: reviewRunId } : {});
|
|
1612
|
+
const changed = watchChanged || Boolean(notification) || Boolean(review);
|
|
1613
|
+
return okResult("handoff_pr_register", changed, changed ? `Registered ${ref} from ${displaySource(source)}.` : `Registration for ${ref} was already current.`, {
|
|
1614
|
+
id: watchId ?? notificationIdFromValue(notification),
|
|
1615
|
+
watch,
|
|
1616
|
+
notification,
|
|
1617
|
+
review,
|
|
1618
|
+
audit
|
|
1619
|
+
});
|
|
1620
|
+
}
|
|
1621
|
+
function normalizeHandoffSource(value, fallback = "external:cli") {
|
|
1622
|
+
const raw = (value?.trim() || fallback).replace(/\s+/g, "-");
|
|
1623
|
+
return (/^(?:external|ci):/i.test(raw) ? raw : `external:${raw}`).slice(0, 120);
|
|
1624
|
+
}
|
|
1625
|
+
function parseHandoffInput(schema, input, action) {
|
|
1626
|
+
return parseInput(schema, input, (message) => failResult(action, "Invalid handoff input.", { errors: [message] }));
|
|
1627
|
+
}
|
|
1628
|
+
async function resolveRegisteredPrLink(ref, paths) {
|
|
1629
|
+
const registry = await readRepoRegistrySnapshot(paths);
|
|
1630
|
+
const resolved = resolvePrReference(ref, registry);
|
|
1631
|
+
if (!resolved.ok) return {
|
|
1632
|
+
ok: false,
|
|
1633
|
+
result: failResult("handoff_pr_reference", resolved.result.message, {
|
|
1634
|
+
errors: resolved.result.errors,
|
|
1635
|
+
requires: resolved.result.requires
|
|
1636
|
+
})
|
|
1637
|
+
};
|
|
1638
|
+
const repo = registry.repos.find((item) => repoFullName(item).toLowerCase() === resolved.reference.repoFullName.toLowerCase() || item.id === resolved.reference.repoId);
|
|
1639
|
+
if (!repo) return {
|
|
1640
|
+
ok: false,
|
|
1641
|
+
result: failResult("handoff_pr_reference", `Repository "${resolved.reference.repoFullName}" is not configured.`, { requires: ["repo"] })
|
|
1642
|
+
};
|
|
1643
|
+
return {
|
|
1644
|
+
ok: true,
|
|
1645
|
+
link: {
|
|
1646
|
+
repo,
|
|
1647
|
+
ref: resolved.reference
|
|
1648
|
+
}
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
async function resolveRegisteredRepo(ref, paths) {
|
|
1652
|
+
const matches = (await readRepoRegistrySnapshot(paths)).repos.filter((repo) => repo.id === ref || repo.github.name === ref || repoFullName(repo).toLowerCase() === ref.toLowerCase());
|
|
1653
|
+
if (matches.length === 1) return {
|
|
1654
|
+
ok: true,
|
|
1655
|
+
repo: matches[0]
|
|
1656
|
+
};
|
|
1657
|
+
if (matches.length > 1) return {
|
|
1658
|
+
ok: false,
|
|
1659
|
+
result: failResult("handoff_repo_reference", `Repository "${ref}" is ambiguous.`, { requires: ["repo"] })
|
|
1660
|
+
};
|
|
1661
|
+
return {
|
|
1662
|
+
ok: false,
|
|
1663
|
+
result: failResult("handoff_repo_reference", `Repository "${ref}" is not configured.`, { requires: ["repo"] })
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
async function externalReviewQueueAllowed(paths) {
|
|
1667
|
+
return (await readRuntimeJson(paths.config, parseAppConfig)).handoff?.allowExternalReviewQueue ?? true;
|
|
1668
|
+
}
|
|
1669
|
+
async function invokeReviewPrWorkflow(input) {
|
|
1670
|
+
const { invoke } = await import("@flue/runtime");
|
|
1671
|
+
return invoke((await import("./review-pr-for-human-B2j8wz2L.js")).default, { input });
|
|
1672
|
+
}
|
|
1673
|
+
async function addHandoffAudit(summary, paths, options = {}) {
|
|
1674
|
+
return addWorkflowSummary({
|
|
1675
|
+
workflow: "agent_handoff",
|
|
1676
|
+
status: options.status ?? "completed",
|
|
1677
|
+
...options.runId ? { runId: options.runId } : {},
|
|
1678
|
+
summary
|
|
1679
|
+
}, paths);
|
|
1680
|
+
}
|
|
1681
|
+
function okResult(action, changed, message, data = {}) {
|
|
1682
|
+
return {
|
|
1683
|
+
ok: true,
|
|
1684
|
+
action,
|
|
1685
|
+
changed,
|
|
1686
|
+
message,
|
|
1687
|
+
...data.id ? { id: data.id } : {},
|
|
1688
|
+
deckUrl,
|
|
1689
|
+
...data.watch ? { watch: asJsonValue(data.watch) } : {},
|
|
1690
|
+
...data.notification ? { notification: asJsonValue(data.notification) } : {},
|
|
1691
|
+
...data.release ? { release: asJsonValue(data.release) } : {},
|
|
1692
|
+
...data.review ? { review: asJsonValue(data.review) } : {},
|
|
1693
|
+
...data.audit ? { audit: asJsonValue(data.audit) } : {}
|
|
1694
|
+
};
|
|
1695
|
+
}
|
|
1696
|
+
function failResult(action, message, data = {}) {
|
|
1697
|
+
return {
|
|
1698
|
+
ok: false,
|
|
1699
|
+
action,
|
|
1700
|
+
changed: data.changed ?? false,
|
|
1701
|
+
message,
|
|
1702
|
+
...data.id ? { id: data.id } : {},
|
|
1703
|
+
deckUrl,
|
|
1704
|
+
...data.watch ? { watch: asJsonValue(data.watch) } : {},
|
|
1705
|
+
...data.notification ? { notification: asJsonValue(data.notification) } : {},
|
|
1706
|
+
...data.release ? { release: asJsonValue(data.release) } : {},
|
|
1707
|
+
...data.review ? { review: asJsonValue(data.review) } : {},
|
|
1708
|
+
...data.audit ? { audit: asJsonValue(data.audit) } : {},
|
|
1709
|
+
...data.errors ? { errors: data.errors } : {},
|
|
1710
|
+
...data.requires ? { requires: data.requires } : {}
|
|
1711
|
+
};
|
|
1712
|
+
}
|
|
1713
|
+
function displaySource(source) {
|
|
1714
|
+
return source.replace(/^(?:external|ci):/, "");
|
|
1715
|
+
}
|
|
1716
|
+
function errorMessage$2(error) {
|
|
1717
|
+
if (error instanceof Error) return error.message;
|
|
1718
|
+
return String(error);
|
|
1719
|
+
}
|
|
1720
|
+
function noteSourceId(input) {
|
|
1721
|
+
return createHash("sha256").update([
|
|
1722
|
+
input.source,
|
|
1723
|
+
input.text,
|
|
1724
|
+
input.repoFullName ?? "",
|
|
1725
|
+
input.prId ?? ""
|
|
1726
|
+
].join("\0")).digest("hex").slice(0, 32);
|
|
1727
|
+
}
|
|
1728
|
+
function watchIdFromValue(value) {
|
|
1729
|
+
if (!value || typeof value !== "object") return void 0;
|
|
1730
|
+
const id = value.id;
|
|
1731
|
+
return typeof id === "string" ? id : void 0;
|
|
1732
|
+
}
|
|
1733
|
+
function notificationIdFromValue(value) {
|
|
1734
|
+
if (!value || typeof value !== "object") return void 0;
|
|
1735
|
+
const id = value.id;
|
|
1736
|
+
return typeof id === "string" ? id : void 0;
|
|
1737
|
+
}
|
|
1738
|
+
function reviewRunIdFromValue(value) {
|
|
1739
|
+
if (!value || typeof value !== "object") return void 0;
|
|
1740
|
+
const runId = value.runId;
|
|
1741
|
+
return typeof runId === "string" ? runId : void 0;
|
|
1742
|
+
}
|
|
1743
|
+
//#endregion
|
|
1744
|
+
//#region src/server/routes/handoff.ts
|
|
1745
|
+
function createHandoffRoutes(paths) {
|
|
1746
|
+
const routes = new Hono();
|
|
1747
|
+
routes.post("/handoff/watch-pr", async (c) => {
|
|
1748
|
+
const input = await handoffBody(c);
|
|
1749
|
+
if (!input.ok) return c.json(input.result, 400);
|
|
1750
|
+
const body = input.body;
|
|
1751
|
+
const result = await registerHandoffWatchPr({
|
|
1752
|
+
ref: body.ref,
|
|
1753
|
+
source: body.source,
|
|
1754
|
+
desiredTerminalState: body.desiredTerminalState,
|
|
1755
|
+
intervalSeconds: body.intervalSeconds
|
|
1756
|
+
}, paths);
|
|
1757
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1758
|
+
});
|
|
1759
|
+
routes.post("/handoff/note", async (c) => {
|
|
1760
|
+
const input = await handoffBody(c);
|
|
1761
|
+
if (!input.ok) return c.json(input.result, 400);
|
|
1762
|
+
const result = await createHandoffNote(input.body, paths);
|
|
1763
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1764
|
+
});
|
|
1765
|
+
routes.post("/handoff/register-pr", async (c) => {
|
|
1766
|
+
const input = await handoffBody(c);
|
|
1767
|
+
if (!input.ok) return c.json(input.result, 400);
|
|
1768
|
+
const result = await registerHandoffPr(input.body, paths);
|
|
1769
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1770
|
+
});
|
|
1771
|
+
return routes;
|
|
1772
|
+
}
|
|
1773
|
+
async function handoffBody(c) {
|
|
1774
|
+
const body = await safeJsonObject(c);
|
|
1775
|
+
if (typeof body.source !== "string" || !body.source.trim()) return {
|
|
1776
|
+
ok: false,
|
|
1777
|
+
result: {
|
|
1778
|
+
ok: false,
|
|
1779
|
+
action: "handoff_request",
|
|
1780
|
+
changed: false,
|
|
1781
|
+
message: "Handoff API requests require a source field.",
|
|
1782
|
+
deckUrl: "/",
|
|
1783
|
+
requires: ["source"]
|
|
1784
|
+
}
|
|
1785
|
+
};
|
|
1786
|
+
return {
|
|
1787
|
+
ok: true,
|
|
1788
|
+
body
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
//#endregion
|
|
1792
|
+
//#region src/server/routes/kilo.ts
|
|
1793
|
+
function createKiloRoutes(paths) {
|
|
1794
|
+
const routes = new Hono();
|
|
1795
|
+
routes.get("/tasks", async (c) => {
|
|
1796
|
+
return c.json(await listKiloTasks({
|
|
1797
|
+
status: c.req.query("status"),
|
|
1798
|
+
repoId: c.req.query("repoId"),
|
|
1799
|
+
limit: queryNumber(c.req.query("limit")),
|
|
1800
|
+
includeDiff: queryBoolean(c.req.query("includeDiff"))
|
|
1801
|
+
}, paths));
|
|
1802
|
+
});
|
|
1803
|
+
routes.get("/results", async (c) => {
|
|
1804
|
+
const result = await listKiloResultStates({
|
|
1805
|
+
taskId: c.req.query("taskId") || void 0,
|
|
1806
|
+
limit: queryNumber(c.req.query("limit"))
|
|
1807
|
+
}, paths);
|
|
1808
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1809
|
+
});
|
|
1810
|
+
routes.post("/tasks", async (c) => {
|
|
1811
|
+
const result = await startKiloTask(await safeJsonBody(c), paths);
|
|
1812
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1813
|
+
});
|
|
1814
|
+
routes.get("/tasks/:id", async (c) => {
|
|
1815
|
+
const result = await readKiloTaskStatus({ taskId: c.req.param("id") }, paths);
|
|
1816
|
+
return c.json(result, result.ok ? 200 : 404);
|
|
1817
|
+
});
|
|
1818
|
+
routes.get("/tasks/:id/events", async (c) => {
|
|
1819
|
+
const result = await readKiloTaskEvents({
|
|
1820
|
+
taskId: c.req.param("id"),
|
|
1821
|
+
limit: queryNumber(c.req.query("limit"))
|
|
1822
|
+
}, paths);
|
|
1823
|
+
return c.json(result, result.ok ? 200 : 404);
|
|
1824
|
+
});
|
|
1825
|
+
routes.post("/tasks/:id/abort", async (c) => {
|
|
1826
|
+
const result = await abortKiloTask({ taskId: c.req.param("id") }, paths);
|
|
1827
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1828
|
+
});
|
|
1829
|
+
routes.get("/tasks/:id/sessions", async (c) => {
|
|
1830
|
+
const result = await readKiloTaskSessions({ taskId: c.req.param("id") }, paths);
|
|
1831
|
+
return c.json(result, result.ok ? 200 : 404);
|
|
1832
|
+
});
|
|
1833
|
+
routes.get("/tasks/:id/diff", async (c) => {
|
|
1834
|
+
const result = await readKiloTaskDiff({ taskId: c.req.param("id") }, paths);
|
|
1835
|
+
return c.json(result, result.ok ? 200 : 404);
|
|
1836
|
+
});
|
|
1837
|
+
routes.get("/tasks/:id/result", async (c) => {
|
|
1838
|
+
const result = await listKiloResultStates({ taskId: c.req.param("id") }, paths);
|
|
1839
|
+
return c.json(result, result.ok ? 200 : 404);
|
|
1840
|
+
});
|
|
1841
|
+
routes.post("/tasks/:id/review", async (c) => {
|
|
1842
|
+
const result = await reviewKiloResult({ taskId: c.req.param("id") }, paths);
|
|
1843
|
+
recordHandledPrApiResult(paths, "api:kilo_result_review", result);
|
|
1844
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1845
|
+
});
|
|
1846
|
+
routes.post("/tasks/:id/verify", async (c) => {
|
|
1847
|
+
const result = await verifyKiloResult({
|
|
1848
|
+
...await safeJsonObject(c),
|
|
1849
|
+
taskId: c.req.param("id")
|
|
1850
|
+
}, paths);
|
|
1851
|
+
recordHandledPrApiResult(paths, "api:kilo_result_verify", result);
|
|
1852
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1853
|
+
});
|
|
1854
|
+
routes.post("/tasks/:id/promote", async (c) => {
|
|
1855
|
+
const result = await promoteKiloResult({ taskId: c.req.param("id") }, paths);
|
|
1856
|
+
recordHandledPrApiResult(paths, "api:kilo_result_promote", result);
|
|
1857
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1858
|
+
});
|
|
1859
|
+
routes.post("/sessions/search", async (c) => {
|
|
1860
|
+
const result = await searchKiloSessions(await safeJsonBody(c), paths);
|
|
1861
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1862
|
+
});
|
|
1863
|
+
routes.get("/sessions/:id", async (c) => {
|
|
1864
|
+
const result = await readKiloSession({
|
|
1865
|
+
sessionId: c.req.param("id"),
|
|
1866
|
+
...kiloSessionQuery(c)
|
|
1867
|
+
}, paths);
|
|
1868
|
+
return c.json(result, result.ok ? 200 : 404);
|
|
1869
|
+
});
|
|
1870
|
+
routes.get("/sessions/:id/messages", async (c) => {
|
|
1871
|
+
const result = await readKiloSessionMessages({
|
|
1872
|
+
sessionId: c.req.param("id"),
|
|
1873
|
+
...kiloSessionQuery(c)
|
|
1874
|
+
}, paths);
|
|
1875
|
+
return c.json(result, result.ok ? 200 : 404);
|
|
1876
|
+
});
|
|
1877
|
+
routes.get("/sessions/:id/children", async (c) => {
|
|
1878
|
+
const result = await readKiloSessionChildren({
|
|
1879
|
+
sessionId: c.req.param("id"),
|
|
1880
|
+
...kiloSessionQuery(c)
|
|
1881
|
+
}, paths);
|
|
1882
|
+
return c.json(result, result.ok ? 200 : 404);
|
|
1883
|
+
});
|
|
1884
|
+
routes.get("/sessions/:id/todos", async (c) => {
|
|
1885
|
+
const result = await readUnavailableSessionAdapter({
|
|
1886
|
+
sessionId: c.req.param("id"),
|
|
1887
|
+
...kiloSessionQuery(c)
|
|
1888
|
+
}, "todos", paths);
|
|
1889
|
+
return c.json(result, result.ok ? 200 : 404);
|
|
1890
|
+
});
|
|
1891
|
+
routes.get("/sessions/:id/diff", async (c) => {
|
|
1892
|
+
const result = await readKiloSessionDiff({
|
|
1893
|
+
sessionId: c.req.param("id"),
|
|
1894
|
+
...kiloSessionQuery(c)
|
|
1895
|
+
}, paths);
|
|
1896
|
+
return c.json(result, result.ok ? 200 : 404);
|
|
1897
|
+
});
|
|
1898
|
+
return routes;
|
|
1899
|
+
}
|
|
1900
|
+
//#endregion
|
|
1901
|
+
//#region src/server/routes/learning.ts
|
|
1902
|
+
function createLearningRoutes(paths) {
|
|
1903
|
+
const routes = new Hono();
|
|
1904
|
+
routes.post("/curate", async (c) => {
|
|
1905
|
+
const parsed = v.safeParse(v.object({
|
|
1906
|
+
mode: v.optional(v.picklist([
|
|
1907
|
+
"off",
|
|
1908
|
+
"review",
|
|
1909
|
+
"auto"
|
|
1910
|
+
])),
|
|
1911
|
+
reason: v.optional(v.string())
|
|
1912
|
+
}), await safeJsonBody(c));
|
|
1913
|
+
if (!parsed.success) return c.json({
|
|
1914
|
+
ok: false,
|
|
1915
|
+
action: "learning_curate",
|
|
1916
|
+
changed: false,
|
|
1917
|
+
message: v.summarize(parsed.issues)
|
|
1918
|
+
}, 400);
|
|
1919
|
+
const receipt = await invoke(curate_learning_store_default, { input: {
|
|
1920
|
+
...parsed.output,
|
|
1921
|
+
trigger: "manual"
|
|
1922
|
+
} });
|
|
1923
|
+
return c.json({
|
|
1924
|
+
ok: true,
|
|
1925
|
+
action: "learning_curate",
|
|
1926
|
+
changed: true,
|
|
1927
|
+
runId: receipt.runId,
|
|
1928
|
+
message: "Queued memory curation learning workflow."
|
|
1929
|
+
});
|
|
1930
|
+
});
|
|
1931
|
+
routes.get("/state", async (c) => {
|
|
1932
|
+
const input = {
|
|
1933
|
+
limit: boundedQueryLimit(c.req.query("limit"), 25),
|
|
1934
|
+
reviewKind: learningReviewKind(c.req.query("reviewKind")),
|
|
1935
|
+
reviewStatus: learningReviewStatus(c.req.query("reviewStatus")),
|
|
1936
|
+
candidateStatus: learningCandidateStatus(c.req.query("candidateStatus")),
|
|
1937
|
+
candidateTarget: learningCandidateTarget(c.req.query("candidateTarget")),
|
|
1938
|
+
memoryId: c.req.query("memoryId") || void 0
|
|
1939
|
+
};
|
|
1940
|
+
if (c.req.query("limit") && input.limit === void 0) return c.json({
|
|
1941
|
+
ok: false,
|
|
1942
|
+
action: "learning_operator_state",
|
|
1943
|
+
changed: false,
|
|
1944
|
+
message: `Invalid learning state limit "${c.req.query("limit")}".`
|
|
1945
|
+
}, 400);
|
|
1946
|
+
if (c.req.query("reviewKind") && !input.reviewKind) return c.json({
|
|
1947
|
+
ok: false,
|
|
1948
|
+
action: "learning_operator_state",
|
|
1949
|
+
changed: false,
|
|
1950
|
+
message: `Invalid review kind "${c.req.query("reviewKind")}".`
|
|
1951
|
+
}, 400);
|
|
1952
|
+
if (c.req.query("reviewStatus") && !input.reviewStatus) return c.json({
|
|
1953
|
+
ok: false,
|
|
1954
|
+
action: "learning_operator_state",
|
|
1955
|
+
changed: false,
|
|
1956
|
+
message: `Invalid review status "${c.req.query("reviewStatus")}".`
|
|
1957
|
+
}, 400);
|
|
1958
|
+
if (c.req.query("candidateStatus") && !input.candidateStatus) return c.json({
|
|
1959
|
+
ok: false,
|
|
1960
|
+
action: "learning_operator_state",
|
|
1961
|
+
changed: false,
|
|
1962
|
+
message: `Invalid candidate status "${c.req.query("candidateStatus")}".`
|
|
1963
|
+
}, 400);
|
|
1964
|
+
if (c.req.query("candidateTarget") && !input.candidateTarget) return c.json({
|
|
1965
|
+
ok: false,
|
|
1966
|
+
action: "learning_operator_state",
|
|
1967
|
+
changed: false,
|
|
1968
|
+
message: `Invalid candidate target "${c.req.query("candidateTarget")}".`
|
|
1969
|
+
}, 400);
|
|
1970
|
+
const result = await readLearningOperatorState(input, paths);
|
|
1971
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
1972
|
+
});
|
|
1973
|
+
routes.get("/reviews", (c) => {
|
|
1974
|
+
const kind = learningReviewKind(c.req.query("kind"));
|
|
1975
|
+
const status = learningReviewStatus(c.req.query("status"));
|
|
1976
|
+
const limit = boundedQueryLimit(c.req.query("limit"), 50);
|
|
1977
|
+
if (c.req.query("kind") && !kind) return c.json({
|
|
1978
|
+
ok: false,
|
|
1979
|
+
action: "learning_review_list",
|
|
1980
|
+
changed: false,
|
|
1981
|
+
message: `Invalid learning review kind "${c.req.query("kind")}".`
|
|
1982
|
+
}, 400);
|
|
1983
|
+
if (c.req.query("status") && !status) return c.json({
|
|
1984
|
+
ok: false,
|
|
1985
|
+
action: "learning_review_list",
|
|
1986
|
+
changed: false,
|
|
1987
|
+
message: `Invalid learning review status "${c.req.query("status")}".`
|
|
1988
|
+
}, 400);
|
|
1989
|
+
if (c.req.query("limit") && limit === void 0) return c.json({
|
|
1990
|
+
ok: false,
|
|
1991
|
+
action: "learning_review_list",
|
|
1992
|
+
changed: false,
|
|
1993
|
+
message: `Invalid review limit "${c.req.query("limit")}".`
|
|
1994
|
+
}, 400);
|
|
1995
|
+
return c.json(listLearningReviews({
|
|
1996
|
+
kind,
|
|
1997
|
+
status,
|
|
1998
|
+
limit
|
|
1999
|
+
}, paths));
|
|
2000
|
+
});
|
|
2001
|
+
routes.post("/reviews/conversation", async (c) => {
|
|
2002
|
+
const parsed = v.safeParse(v.object({
|
|
2003
|
+
sessionId: v.optional(v.pipe(v.string(), v.minLength(1))),
|
|
2004
|
+
reason: v.optional(v.string())
|
|
2005
|
+
}), await safeJsonBody(c));
|
|
2006
|
+
if (!parsed.success) return c.json({
|
|
2007
|
+
ok: false,
|
|
2008
|
+
action: "learning_review_conversation",
|
|
2009
|
+
changed: false,
|
|
2010
|
+
message: v.summarize(parsed.issues)
|
|
2011
|
+
}, 400);
|
|
2012
|
+
if (parsed.output.sessionId) {
|
|
2013
|
+
const session = await readChatSession({
|
|
2014
|
+
id: parsed.output.sessionId,
|
|
2015
|
+
reason: "manual-conversation-learning-review",
|
|
2016
|
+
surface: "learning"
|
|
2017
|
+
}, paths);
|
|
2018
|
+
if (!session.ok) return c.json(session, 400);
|
|
2019
|
+
}
|
|
2020
|
+
const receipt = await invoke(review_conversation_for_learning_default, { input: {
|
|
2021
|
+
...parsed.output,
|
|
2022
|
+
trigger: "manual"
|
|
2023
|
+
} });
|
|
2024
|
+
return c.json({
|
|
2025
|
+
ok: true,
|
|
2026
|
+
action: "learning_review_conversation",
|
|
2027
|
+
changed: true,
|
|
2028
|
+
runId: receipt.runId,
|
|
2029
|
+
message: "Queued conversation learning review workflow."
|
|
2030
|
+
});
|
|
2031
|
+
});
|
|
2032
|
+
routes.post("/reviews/prs", async (c) => {
|
|
2033
|
+
const parsed = v.safeParse(v.object({
|
|
2034
|
+
repoId: v.optional(v.pipe(v.string(), v.minLength(1))),
|
|
2035
|
+
reason: v.optional(v.string()),
|
|
2036
|
+
limit: v.optional(v.pipe(v.number(), v.integer(), v.minValue(1)))
|
|
2037
|
+
}), await safeJsonBody(c));
|
|
2038
|
+
if (!parsed.success) return c.json({
|
|
2039
|
+
ok: false,
|
|
2040
|
+
action: "learning_review_pr_batch",
|
|
2041
|
+
changed: false,
|
|
2042
|
+
message: v.summarize(parsed.issues)
|
|
2043
|
+
}, 400);
|
|
2044
|
+
const receipt = await invoke(review_pr_batch_for_learning_default, { input: {
|
|
2045
|
+
...parsed.output,
|
|
2046
|
+
trigger: "manual"
|
|
2047
|
+
} });
|
|
2048
|
+
return c.json({
|
|
2049
|
+
ok: true,
|
|
2050
|
+
action: "learning_review_pr_batch",
|
|
2051
|
+
changed: true,
|
|
2052
|
+
runId: receipt.runId,
|
|
2053
|
+
message: "Queued PR/autopilot learning retrospective workflow."
|
|
2054
|
+
});
|
|
2055
|
+
});
|
|
2056
|
+
routes.get("/candidates", async (c) => {
|
|
2057
|
+
const status = learningCandidateStatus(c.req.query("status"));
|
|
2058
|
+
if (c.req.query("status") && !status) return c.json({
|
|
2059
|
+
ok: false,
|
|
2060
|
+
action: "memory_candidate_list",
|
|
2061
|
+
changed: false,
|
|
2062
|
+
message: `Invalid candidate status "${c.req.query("status")}".`
|
|
2063
|
+
}, 400);
|
|
2064
|
+
const limit = Number(c.req.query("limit") ?? "100");
|
|
2065
|
+
if (c.req.query("limit") && !boundedQueryLimit(c.req.query("limit"), 100)) return c.json({
|
|
2066
|
+
ok: false,
|
|
2067
|
+
action: "learning_candidate_list",
|
|
2068
|
+
changed: false,
|
|
2069
|
+
message: `Invalid candidate limit "${c.req.query("limit")}".`
|
|
2070
|
+
}, 400);
|
|
2071
|
+
const boundedLimit = Number.isFinite(limit) ? limit : void 0;
|
|
2072
|
+
const [memoryCandidates, skillCandidates] = await Promise.all([listMemoryCandidates({
|
|
2073
|
+
status,
|
|
2074
|
+
limit: boundedLimit
|
|
2075
|
+
}, paths), listSkillPatchCandidates({
|
|
2076
|
+
status,
|
|
2077
|
+
limit: boundedLimit
|
|
2078
|
+
}, paths)]);
|
|
2079
|
+
return c.json({
|
|
2080
|
+
ok: memoryCandidates.ok && skillCandidates.ok,
|
|
2081
|
+
action: "learning_candidate_list",
|
|
2082
|
+
changed: false,
|
|
2083
|
+
candidates: [...memoryCandidates.candidates ?? [], ...skillCandidates.candidates ?? []],
|
|
2084
|
+
memoryCandidates: memoryCandidates.candidates ?? [],
|
|
2085
|
+
skillCandidates: skillCandidates.candidates ?? [],
|
|
2086
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2087
|
+
});
|
|
2088
|
+
});
|
|
2089
|
+
routes.post("/candidates/:id/approve", async (c) => {
|
|
2090
|
+
const body = await safeJsonBody(c);
|
|
2091
|
+
const result = await decideMemoryCandidate({
|
|
2092
|
+
...body,
|
|
2093
|
+
id: c.req.param("id"),
|
|
2094
|
+
decision: "apply"
|
|
2095
|
+
}, paths);
|
|
2096
|
+
if (result.ok) return c.json(result);
|
|
2097
|
+
if (!memoryCandidateWasNotFound(result)) return c.json(result, 400);
|
|
2098
|
+
const skillResult = await applySkillPatchCandidate({
|
|
2099
|
+
...body,
|
|
2100
|
+
id: c.req.param("id")
|
|
2101
|
+
}, paths);
|
|
2102
|
+
return c.json(skillResult, skillResult.ok ? 200 : 400);
|
|
2103
|
+
});
|
|
2104
|
+
routes.post("/candidates/:id/reject", async (c) => {
|
|
2105
|
+
const body = await safeJsonBody(c);
|
|
2106
|
+
const result = await decideMemoryCandidate({
|
|
2107
|
+
...body,
|
|
2108
|
+
id: c.req.param("id"),
|
|
2109
|
+
decision: "reject"
|
|
2110
|
+
}, paths);
|
|
2111
|
+
if (result.ok) return c.json(result);
|
|
2112
|
+
if (!memoryCandidateWasNotFound(result)) return c.json(result, 400);
|
|
2113
|
+
const skillResult = await rejectSkillPatchCandidate({
|
|
2114
|
+
...body,
|
|
2115
|
+
id: c.req.param("id")
|
|
2116
|
+
}, paths);
|
|
2117
|
+
return c.json(skillResult, skillResult.ok ? 200 : 400);
|
|
2118
|
+
});
|
|
2119
|
+
return routes;
|
|
2120
|
+
}
|
|
2121
|
+
function learningCandidateStatus(value) {
|
|
2122
|
+
if (value === "proposed" || value === "applied" || value === "rejected" || value === "archived") return value;
|
|
2123
|
+
}
|
|
2124
|
+
function learningCandidateTarget(value) {
|
|
2125
|
+
if (value === "memory" || value === "skill") return value;
|
|
2126
|
+
}
|
|
2127
|
+
function learningReviewKind(value) {
|
|
2128
|
+
if (value === "conversation" || value === "curation" || value === "pr-batch") return value;
|
|
2129
|
+
}
|
|
2130
|
+
function learningReviewStatus(value) {
|
|
2131
|
+
if (value === "running" || value === "completed" || value === "failed") return value;
|
|
2132
|
+
}
|
|
2133
|
+
function memoryCandidateWasNotFound(result) {
|
|
2134
|
+
if (!result || typeof result !== "object") return false;
|
|
2135
|
+
const record = result;
|
|
2136
|
+
return record.message === "Memory candidate was not found." && Array.isArray(record.requires) && record.requires.includes("id");
|
|
2137
|
+
}
|
|
2138
|
+
//#endregion
|
|
2139
|
+
//#region src/server/routes/memory.ts
|
|
2140
|
+
function createMemoryRoutes(paths) {
|
|
2141
|
+
const routes = new Hono();
|
|
2142
|
+
routes.get("/memories", async (c) => {
|
|
2143
|
+
const rawScope = c.req.query("scope");
|
|
2144
|
+
const scope = memoryScope(rawScope);
|
|
2145
|
+
const key = c.req.query("key");
|
|
2146
|
+
const rawStatus = c.req.query("status");
|
|
2147
|
+
const status = memoryStatus(rawStatus);
|
|
2148
|
+
if (rawScope && !scope) return c.json({
|
|
2149
|
+
ok: false,
|
|
2150
|
+
action: "memory_list",
|
|
2151
|
+
changed: false,
|
|
2152
|
+
message: `Invalid memory scope "${rawScope}".`
|
|
2153
|
+
}, 400);
|
|
2154
|
+
if (rawStatus && !status) return c.json({
|
|
2155
|
+
ok: false,
|
|
2156
|
+
action: "memory_list",
|
|
2157
|
+
changed: false,
|
|
2158
|
+
message: `Invalid memory status "${rawStatus}".`
|
|
2159
|
+
}, 400);
|
|
2160
|
+
return c.json(await listMemories({
|
|
2161
|
+
scope,
|
|
2162
|
+
key: key || void 0,
|
|
2163
|
+
status,
|
|
2164
|
+
includeArchived: c.req.query("includeArchived") === "true",
|
|
2165
|
+
repoId: c.req.query("repoId") || void 0
|
|
2166
|
+
}, paths));
|
|
2167
|
+
});
|
|
2168
|
+
routes.post("/memories", async (c) => {
|
|
2169
|
+
return c.json(await upsertMemory(await c.req.json(), paths));
|
|
2170
|
+
});
|
|
2171
|
+
routes.post("/memories/:id/archive", async (c) => {
|
|
2172
|
+
const result = await archiveMemory({
|
|
2173
|
+
...await safeJsonBody(c),
|
|
2174
|
+
id: c.req.param("id")
|
|
2175
|
+
}, paths);
|
|
2176
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
2177
|
+
});
|
|
2178
|
+
routes.get("/memory-events", async (c) => {
|
|
2179
|
+
const limit = Number(c.req.query("limit") ?? "100");
|
|
2180
|
+
return c.json(await listMemoryEvents({
|
|
2181
|
+
memoryId: c.req.query("memoryId") || void 0,
|
|
2182
|
+
limit: Number.isFinite(limit) ? limit : void 0
|
|
2183
|
+
}, paths));
|
|
2184
|
+
});
|
|
2185
|
+
return routes;
|
|
2186
|
+
}
|
|
2187
|
+
function memoryScope(value) {
|
|
2188
|
+
if (value === "user" || value === "local" || value === "project") return value;
|
|
2189
|
+
}
|
|
2190
|
+
function memoryStatus(value) {
|
|
2191
|
+
if (value === "active" || value === "archived") return value;
|
|
2192
|
+
}
|
|
2193
|
+
//#endregion
|
|
2194
|
+
//#region src/server/routes/metrics.ts
|
|
2195
|
+
function createMetricsRoutes() {
|
|
2196
|
+
const routes = new Hono();
|
|
2197
|
+
routes.get("/host", async (c) => {
|
|
2198
|
+
return c.json(await readHostMetrics());
|
|
2199
|
+
});
|
|
2200
|
+
return routes;
|
|
2201
|
+
}
|
|
2202
|
+
//#endregion
|
|
2203
|
+
//#region src/server/routes/mcp.ts
|
|
2204
|
+
function createMcpRoutes(paths) {
|
|
2205
|
+
const routes = new Hono();
|
|
2206
|
+
routes.get("/servers", async (c) => {
|
|
2207
|
+
return c.json({
|
|
2208
|
+
ok: true,
|
|
2209
|
+
action: "mcp_servers_list",
|
|
2210
|
+
changed: false,
|
|
2211
|
+
message: "Read MCP server statuses.",
|
|
2212
|
+
servers: await getMcpRegistry(paths).status()
|
|
2213
|
+
});
|
|
2214
|
+
});
|
|
2215
|
+
routes.post("/servers", async (c) => {
|
|
2216
|
+
const result = await addMcpServer(await safeJsonBody(c), paths);
|
|
2217
|
+
if (result.ok) {
|
|
2218
|
+
const id = readIdFromResultData(result.data);
|
|
2219
|
+
await getMcpRegistry(paths).refresh(id);
|
|
2220
|
+
}
|
|
2221
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
2222
|
+
});
|
|
2223
|
+
routes.patch("/servers/:id", async (c) => {
|
|
2224
|
+
const id = c.req.param("id");
|
|
2225
|
+
const result = await updateMcpServer({
|
|
2226
|
+
...await safeJsonObject(c),
|
|
2227
|
+
id
|
|
2228
|
+
}, paths);
|
|
2229
|
+
if (result.ok) await getMcpRegistry(paths).refresh(id);
|
|
2230
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
2231
|
+
});
|
|
2232
|
+
routes.delete("/servers/:id", async (c) => {
|
|
2233
|
+
const body = await safeJsonObject(c);
|
|
2234
|
+
const id = c.req.param("id");
|
|
2235
|
+
const result = await removeMcpServer({
|
|
2236
|
+
...body,
|
|
2237
|
+
id,
|
|
2238
|
+
confirm: body.confirm === true || c.req.query("confirm") === "true"
|
|
2239
|
+
}, paths);
|
|
2240
|
+
if (result.ok) await getMcpRegistry(paths).refresh(id);
|
|
2241
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
2242
|
+
});
|
|
2243
|
+
routes.post("/servers/:id/enable", async (c) => {
|
|
2244
|
+
const id = c.req.param("id");
|
|
2245
|
+
const result = await setMcpServerEnabled({ id }, true, paths);
|
|
2246
|
+
if (result.ok) await getMcpRegistry(paths).refresh(id);
|
|
2247
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
2248
|
+
});
|
|
2249
|
+
routes.post("/servers/:id/disable", async (c) => {
|
|
2250
|
+
const id = c.req.param("id");
|
|
2251
|
+
const result = await setMcpServerEnabled({ id }, false, paths);
|
|
2252
|
+
if (result.ok) await getMcpRegistry(paths).refresh(id);
|
|
2253
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
2254
|
+
});
|
|
2255
|
+
routes.get("/servers/:id/tools", async (c) => {
|
|
2256
|
+
return c.json({
|
|
2257
|
+
ok: true,
|
|
2258
|
+
action: "mcp_tools_list",
|
|
2259
|
+
changed: false,
|
|
2260
|
+
message: "Read cached MCP tools.",
|
|
2261
|
+
tools: await getMcpRegistry(paths).listTools(c.req.param("id"))
|
|
2262
|
+
});
|
|
2263
|
+
});
|
|
2264
|
+
routes.post("/servers/:id/refresh", async (c) => {
|
|
2265
|
+
const id = c.req.param("id");
|
|
2266
|
+
if (!await getMcpRegistry(paths).refresh(id)) return c.json({
|
|
2267
|
+
ok: false,
|
|
2268
|
+
action: "mcp_server_refresh",
|
|
2269
|
+
changed: false,
|
|
2270
|
+
message: `MCP server "${id}" was not found.`,
|
|
2271
|
+
requires: ["id"]
|
|
2272
|
+
}, 404);
|
|
2273
|
+
return c.json({
|
|
2274
|
+
ok: true,
|
|
2275
|
+
action: "mcp_server_refresh",
|
|
2276
|
+
changed: false,
|
|
2277
|
+
message: `Refreshed MCP server "${id}".`
|
|
2278
|
+
});
|
|
2279
|
+
});
|
|
2280
|
+
routes.post("/servers/:id/login", async (c) => {
|
|
2281
|
+
const body = await safeJsonObject(c);
|
|
2282
|
+
const redirectUrl = typeof body.redirectUrl === "string" ? body.redirectUrl : `${requestOrigin(c)}/api/mcp/oauth/callback`;
|
|
2283
|
+
const result = await startMcpOAuthLogin({
|
|
2284
|
+
id: c.req.param("id"),
|
|
2285
|
+
redirectUrl
|
|
2286
|
+
}, paths);
|
|
2287
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
2288
|
+
});
|
|
2289
|
+
routes.get("/logins/:id", async (c) => {
|
|
2290
|
+
const login = await readPublicMcpOAuthLogin(c.req.param("id"), paths);
|
|
2291
|
+
if (!login) return c.json({
|
|
2292
|
+
ok: false,
|
|
2293
|
+
action: "mcp_login_read",
|
|
2294
|
+
changed: false,
|
|
2295
|
+
message: "MCP OAuth login was not found."
|
|
2296
|
+
}, 404);
|
|
2297
|
+
return c.json({
|
|
2298
|
+
ok: true,
|
|
2299
|
+
action: "mcp_login_read",
|
|
2300
|
+
changed: false,
|
|
2301
|
+
message: `Read MCP OAuth login "${login.id}".`,
|
|
2302
|
+
login
|
|
2303
|
+
});
|
|
2304
|
+
});
|
|
2305
|
+
routes.get("/oauth/callback", async (c) => {
|
|
2306
|
+
const result = await completeMcpOAuthCallback({
|
|
2307
|
+
state: c.req.query("state"),
|
|
2308
|
+
code: c.req.query("code"),
|
|
2309
|
+
error: c.req.query("error")
|
|
2310
|
+
}, paths);
|
|
2311
|
+
const login = "login" in result && isRecord(result.login) ? result.login : null;
|
|
2312
|
+
const serverId = login && typeof login.serverId === "string" ? login.serverId : void 0;
|
|
2313
|
+
if (result.ok && serverId) await getMcpRegistry(paths).refresh(serverId);
|
|
2314
|
+
return c.html(oauthCallbackHtml(result.ok, result.message), result.ok ? 200 : 400);
|
|
2315
|
+
});
|
|
2316
|
+
routes.post("/servers/:id/logout", async (c) => {
|
|
2317
|
+
const body = await safeJsonObject(c);
|
|
2318
|
+
const id = c.req.param("id");
|
|
2319
|
+
const result = await logoutMcpOAuthServer({
|
|
2320
|
+
id,
|
|
2321
|
+
confirm: body.confirm === true
|
|
2322
|
+
}, paths);
|
|
2323
|
+
if (result.ok) await getMcpRegistry(paths).refresh(id);
|
|
2324
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
2325
|
+
});
|
|
2326
|
+
routes.get("/approvals", async (c) => {
|
|
2327
|
+
return c.json({
|
|
2328
|
+
ok: true,
|
|
2329
|
+
action: "mcp_approvals_list",
|
|
2330
|
+
changed: false,
|
|
2331
|
+
message: "Read MCP approvals.",
|
|
2332
|
+
approvals: await listMcpApprovals(paths, { includeResolved: c.req.query("includeResolved") === "1" })
|
|
2333
|
+
});
|
|
2334
|
+
});
|
|
2335
|
+
routes.post("/approvals/:id/resolve", async (c) => {
|
|
2336
|
+
const result = await resolveMcpApprovalWithPaths({
|
|
2337
|
+
...await safeJsonObject(c),
|
|
2338
|
+
id: c.req.param("id")
|
|
2339
|
+
}, paths);
|
|
2340
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
2341
|
+
});
|
|
2342
|
+
routes.get("/audit", async (c) => {
|
|
2343
|
+
return c.json({
|
|
2344
|
+
ok: true,
|
|
2345
|
+
action: "mcp_audit_list",
|
|
2346
|
+
changed: false,
|
|
2347
|
+
message: "Read MCP audit rows.",
|
|
2348
|
+
audit: await listMcpAudit(paths, {
|
|
2349
|
+
serverId: c.req.query("serverId") || void 0,
|
|
2350
|
+
limit: queryNumber(c.req.query("limit"))
|
|
2351
|
+
})
|
|
2352
|
+
});
|
|
2353
|
+
});
|
|
2354
|
+
return routes;
|
|
2355
|
+
}
|
|
2356
|
+
function requestOrigin(c) {
|
|
2357
|
+
return new URL(c.req.url).origin;
|
|
2358
|
+
}
|
|
2359
|
+
function readIdFromResultData(data) {
|
|
2360
|
+
if (!isRecord(data)) return void 0;
|
|
2361
|
+
const id = data.id ?? data.serverId;
|
|
2362
|
+
if (typeof id === "string") return id;
|
|
2363
|
+
const server = data.server;
|
|
2364
|
+
return isRecord(server) && typeof server.id === "string" ? server.id : void 0;
|
|
2365
|
+
}
|
|
2366
|
+
function isRecord(value) {
|
|
2367
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2368
|
+
}
|
|
2369
|
+
function oauthCallbackHtml(ok, message) {
|
|
2370
|
+
const title = ok ? "MCP login complete" : "MCP login failed";
|
|
2371
|
+
return `<!doctype html>
|
|
2372
|
+
<html lang="en">
|
|
2373
|
+
<head>
|
|
2374
|
+
<meta charset="utf-8" />
|
|
2375
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
2376
|
+
<title>${escapeHtml(title)}</title>
|
|
2377
|
+
<style>
|
|
2378
|
+
body {
|
|
2379
|
+
margin: 0;
|
|
2380
|
+
min-height: 100vh;
|
|
2381
|
+
display: grid;
|
|
2382
|
+
place-items: center;
|
|
2383
|
+
background: #0a0b10;
|
|
2384
|
+
color: #d7f7ff;
|
|
2385
|
+
font: 14px/1.5 system-ui, sans-serif;
|
|
2386
|
+
}
|
|
2387
|
+
main {
|
|
2388
|
+
max-width: 36rem;
|
|
2389
|
+
border: 1px solid #ffffff1f;
|
|
2390
|
+
padding: 24px;
|
|
2391
|
+
}
|
|
2392
|
+
h1 {
|
|
2393
|
+
margin: 0 0 8px;
|
|
2394
|
+
color: ${ok ? "#69e6ff" : "#ff4fb8"};
|
|
2395
|
+
font: 600 16px/1.2 ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
2396
|
+
}
|
|
2397
|
+
p {
|
|
2398
|
+
margin: 0;
|
|
2399
|
+
color: #d7f7ffb3;
|
|
2400
|
+
}
|
|
2401
|
+
</style>
|
|
2402
|
+
</head>
|
|
2403
|
+
<body>
|
|
2404
|
+
<main>
|
|
2405
|
+
<h1>${escapeHtml(title)}</h1>
|
|
2406
|
+
<p>${escapeHtml(message)}</p>
|
|
2407
|
+
</main>
|
|
2408
|
+
</body>
|
|
2409
|
+
</html>`;
|
|
2410
|
+
}
|
|
2411
|
+
function escapeHtml(value) {
|
|
2412
|
+
return value.replace(/[&<>"']/g, (char) => {
|
|
2413
|
+
switch (char) {
|
|
2414
|
+
case "&": return "&";
|
|
2415
|
+
case "<": return "<";
|
|
2416
|
+
case ">": return ">";
|
|
2417
|
+
case "\"": return """;
|
|
2418
|
+
default: return "'";
|
|
2419
|
+
}
|
|
2420
|
+
});
|
|
2421
|
+
}
|
|
2422
|
+
//#endregion
|
|
2423
|
+
//#region src/server/routes/notifications.ts
|
|
2424
|
+
function createNotificationRoutes(paths) {
|
|
2425
|
+
const routes = new Hono();
|
|
2426
|
+
routes.get("/", async (c) => {
|
|
2427
|
+
const includeResolved = c.req.query("includeResolved") === "1";
|
|
2428
|
+
return c.json({
|
|
2429
|
+
items: await listNotifications(paths, { includeResolved }),
|
|
2430
|
+
policy: notificationPolicy(),
|
|
2431
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2432
|
+
});
|
|
2433
|
+
});
|
|
2434
|
+
routes.post("/:id/read", async (c) => {
|
|
2435
|
+
await markNotificationRead(c.req.param("id"), paths);
|
|
2436
|
+
return c.json({ ok: true });
|
|
2437
|
+
});
|
|
2438
|
+
routes.post("/:id/resolve", async (c) => {
|
|
2439
|
+
await resolveNotification(c.req.param("id"), paths);
|
|
2440
|
+
return c.json({ ok: true });
|
|
2441
|
+
});
|
|
2442
|
+
return routes;
|
|
2443
|
+
}
|
|
2444
|
+
function notificationPolicy() {
|
|
2445
|
+
return {
|
|
2446
|
+
info: "Passive updates such as queued scheduled work.",
|
|
2447
|
+
ready: "Completed work or green checks that can be glanced at and cleared.",
|
|
2448
|
+
attention: "Actionable failures, missing configuration, blocked watches, or failed Flue work.",
|
|
2449
|
+
urgent: "Release or production-facing failures that should interrupt passive viewing.",
|
|
2450
|
+
reconcile: "Unresolved notifications with the same source and source id are updated in place and counted.",
|
|
2451
|
+
autopilot: "Autopilot notifications use deterministic source ids by prepared diff/workflow and state: review-fix, ci-fix, verify, push-blocked, pushed, comment-result, and failed-workflow. Repeated retries reconcile in place; state changes create separate actionable records with recovery metadata.",
|
|
2452
|
+
kilo: "Kilo notifications reconcile by delegated task and state: started, progress, waiting-approval, completed, failed, timed-out, needs-review, verified, promote-blocked, and promoted. Progress updates replace one task progress row; result workflow states create separate actionable records linked to the task."
|
|
2453
|
+
};
|
|
2454
|
+
}
|
|
2455
|
+
//#endregion
|
|
2456
|
+
//#region src/server/routes/repo-edit.ts
|
|
2457
|
+
function createRepoEditRoutes(paths) {
|
|
2458
|
+
const routes = new Hono();
|
|
2459
|
+
routes.get("/repo-edits", async (c) => {
|
|
2460
|
+
return c.json(await listRepoEditEvents(paths));
|
|
2461
|
+
});
|
|
2462
|
+
routes.get("/repos/:repoId/status", async (c) => {
|
|
2463
|
+
const result = await readRepoCheckoutStatus({ repoId: c.req.param("repoId") }, paths);
|
|
2464
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
2465
|
+
});
|
|
2466
|
+
routes.post("/repos/:repoId/files/read", async (c) => {
|
|
2467
|
+
const result = await readRepoFile({
|
|
2468
|
+
...await safeJsonObject(c),
|
|
2469
|
+
repoId: c.req.param("repoId")
|
|
2470
|
+
}, paths);
|
|
2471
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
2472
|
+
});
|
|
2473
|
+
routes.post("/repos/:repoId/files/search", async (c) => {
|
|
2474
|
+
const result = await searchRepoFiles({
|
|
2475
|
+
...await safeJsonObject(c),
|
|
2476
|
+
repoId: c.req.param("repoId")
|
|
2477
|
+
}, paths);
|
|
2478
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
2479
|
+
});
|
|
2480
|
+
routes.post("/repos/:repoId/files/write/preview", async (c) => {
|
|
2481
|
+
const result = await writeRepoFile({
|
|
2482
|
+
...await safeJsonObject(c),
|
|
2483
|
+
repoId: c.req.param("repoId"),
|
|
2484
|
+
dryRun: true
|
|
2485
|
+
}, paths);
|
|
2486
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
2487
|
+
});
|
|
2488
|
+
routes.post("/repos/:repoId/files/write", async (c) => {
|
|
2489
|
+
const result = await writeRepoFile({
|
|
2490
|
+
...await safeJsonObject(c),
|
|
2491
|
+
repoId: c.req.param("repoId")
|
|
2492
|
+
}, paths);
|
|
2493
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
2494
|
+
});
|
|
2495
|
+
routes.post("/repos/:repoId/files/replace/preview", async (c) => {
|
|
2496
|
+
const result = await replaceRepoFile({
|
|
2497
|
+
...await safeJsonObject(c),
|
|
2498
|
+
repoId: c.req.param("repoId"),
|
|
2499
|
+
dryRun: true
|
|
2500
|
+
}, paths);
|
|
2501
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
2502
|
+
});
|
|
2503
|
+
routes.post("/repos/:repoId/files/replace", async (c) => {
|
|
2504
|
+
const result = await replaceRepoFile({
|
|
2505
|
+
...await safeJsonObject(c),
|
|
2506
|
+
repoId: c.req.param("repoId")
|
|
2507
|
+
}, paths);
|
|
2508
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
2509
|
+
});
|
|
2510
|
+
routes.post("/repos/:repoId/files/patch/preview", async (c) => {
|
|
2511
|
+
const result = await patchRepoFiles({
|
|
2512
|
+
...await safeJsonObject(c),
|
|
2513
|
+
repoId: c.req.param("repoId"),
|
|
2514
|
+
dryRun: true
|
|
2515
|
+
}, paths);
|
|
2516
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
2517
|
+
});
|
|
2518
|
+
routes.post("/repos/:repoId/files/patch", async (c) => {
|
|
2519
|
+
const result = await patchRepoFiles({
|
|
2520
|
+
...await safeJsonObject(c),
|
|
2521
|
+
repoId: c.req.param("repoId")
|
|
2522
|
+
}, paths);
|
|
2523
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
2524
|
+
});
|
|
2525
|
+
routes.post("/repos/:repoId/diff", async (c) => {
|
|
2526
|
+
const result = await readRepoDiff({
|
|
2527
|
+
...await safeJsonObject(c),
|
|
2528
|
+
repoId: c.req.param("repoId")
|
|
2529
|
+
}, paths);
|
|
2530
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
2531
|
+
});
|
|
2532
|
+
return routes;
|
|
2533
|
+
}
|
|
2534
|
+
//#endregion
|
|
2535
|
+
//#region src/modules/docs-drift/service.ts
|
|
2536
|
+
var neonDocsFixSkillPath = fileURLToPath(new URL("../../skills/neon-docs-fix/SKILL.md", import.meta.url));
|
|
2537
|
+
var docsDriftStageFixInputSchema = v.object({ reportId: nonEmptyStringSchema });
|
|
2538
|
+
async function stageDocsDriftFix(rawInput, paths = runtimePaths(), dependencies = {}) {
|
|
2539
|
+
const parsed = parseInput(docsDriftStageFixInputSchema, rawInput, (message) => invalidInputAction("docs_drift_stage_fix", message));
|
|
2540
|
+
if (!parsed.ok) return parsed.result;
|
|
2541
|
+
const report = await (dependencies.readReport ?? readReport)(parsed.input.reportId, paths);
|
|
2542
|
+
if (!report) return failedAction("docs_drift_stage_fix", "Docs drift report not found.", { requires: ["reportId"] });
|
|
2543
|
+
if (report.kind !== "docs-drift") return failedAction("docs_drift_stage_fix", "Only docs-drift reports can stage docs fixes.", { requires: ["docsDriftReport"] });
|
|
2544
|
+
const summary = docsDriftSummary(report);
|
|
2545
|
+
if (!summary || summary.hits.length === 0) return failedAction("docs_drift_stage_fix", "Docs drift report has no fixable drift hits.", { requires: ["driftHits"] });
|
|
2546
|
+
const repo = (await readRepoRegistrySnapshot(paths)).repos.find((candidate) => candidate.id === summary.repo);
|
|
2547
|
+
if (!repo) return failedAction("docs_drift_stage_fix", `Repository "${summary.repo}" is not configured.`, { requires: ["repo"] });
|
|
2548
|
+
const created = await (dependencies.createWorktree ?? createWorktree)({
|
|
2549
|
+
repoId: repo.id,
|
|
2550
|
+
baseRef: docsFixHeadSha(summary),
|
|
2551
|
+
headSha: docsFixHeadSha(summary),
|
|
2552
|
+
createdBy: "neondeck"
|
|
2553
|
+
}, paths);
|
|
2554
|
+
if (!created.ok || !("worktree" in created)) {
|
|
2555
|
+
const result = objectConfig(created);
|
|
2556
|
+
return failedAction("docs_drift_stage_fix", created.message, {
|
|
2557
|
+
errors: stringArrayConfig(result.errors),
|
|
2558
|
+
requires: stringArrayConfig(result.requires)
|
|
2559
|
+
});
|
|
2560
|
+
}
|
|
2561
|
+
const taskId = `docs-drift-${randomUUID()}`;
|
|
2562
|
+
try {
|
|
2563
|
+
await recordDocsDriftFixTaskBoundary({
|
|
2564
|
+
taskId,
|
|
2565
|
+
reportId: report.id,
|
|
2566
|
+
repoId: repo.id,
|
|
2567
|
+
repoFullName: summary.repoFullName,
|
|
2568
|
+
worktreeId: created.worktree.id,
|
|
2569
|
+
allowedDocsPaths: docsFixAllowedDocsPaths(summary)
|
|
2570
|
+
}, paths);
|
|
2571
|
+
} catch (error) {
|
|
2572
|
+
return failedAction("docs_drift_stage_fix", `Could not record docs drift fix boundary: ${errorMessage$1(error)}.`, {
|
|
2573
|
+
requires: ["docsDriftBoundary"],
|
|
2574
|
+
errors: [errorMessage$1(error)]
|
|
2575
|
+
});
|
|
2576
|
+
}
|
|
2577
|
+
const learningMemoryContext = await loadAutomationLearningMemoryContext(paths, {
|
|
2578
|
+
repoId: repo.id,
|
|
2579
|
+
includeGlobal: true
|
|
2580
|
+
});
|
|
2581
|
+
const kilo = await (dependencies.startKiloTask ?? startKiloTask)({
|
|
2582
|
+
taskId,
|
|
2583
|
+
worktreeId: created.worktree.id,
|
|
2584
|
+
title: `Docs drift fix: ${summary.repoFullName}`,
|
|
2585
|
+
prompt: await docsFixPrompt(summary, report, paths, learningMemoryContext),
|
|
2586
|
+
mode: "draft-fix",
|
|
2587
|
+
allowAuto: true,
|
|
2588
|
+
confirmAuto: true,
|
|
2589
|
+
explicitUserRequest: true
|
|
2590
|
+
}, paths);
|
|
2591
|
+
if (!kilo.ok) {
|
|
2592
|
+
const result = objectConfig(kilo);
|
|
2593
|
+
return failedAction("docs_drift_stage_fix", kilo.message, {
|
|
2594
|
+
errors: stringArrayConfig(result.errors),
|
|
2595
|
+
requires: stringArrayConfig(result.requires)
|
|
2596
|
+
});
|
|
2597
|
+
}
|
|
2598
|
+
const startedTaskId = "task" in kilo ? kilo.task.id : taskId;
|
|
2599
|
+
await addWorkflowSummary({
|
|
2600
|
+
workflow: "docs_drift_stage_fix",
|
|
2601
|
+
status: "started",
|
|
2602
|
+
summary: {
|
|
2603
|
+
outcome: "kilo-started",
|
|
2604
|
+
reportId: report.id,
|
|
2605
|
+
repoId: repo.id,
|
|
2606
|
+
repoFullName: summary.repoFullName,
|
|
2607
|
+
kiloTaskId: startedTaskId,
|
|
2608
|
+
worktreeId: created.worktree.id,
|
|
2609
|
+
memoryIds: learningMemoryContext.memoryIds
|
|
2610
|
+
}
|
|
2611
|
+
}, paths);
|
|
2612
|
+
return okAction("docs_drift_stage_fix", true, `Started docs drift fix task for ${summary.repoFullName}.`, { data: asJsonValue({
|
|
2613
|
+
report: reportLink(report),
|
|
2614
|
+
worktree: {
|
|
2615
|
+
id: created.worktree.id,
|
|
2616
|
+
path: created.worktree.localPath,
|
|
2617
|
+
headSha: created.worktree.headSha,
|
|
2618
|
+
targetSha: docsFixHeadSha(summary)
|
|
2619
|
+
},
|
|
2620
|
+
kiloTask: { id: startedTaskId }
|
|
2621
|
+
}) });
|
|
2622
|
+
}
|
|
2623
|
+
function docsDriftSummary(report) {
|
|
2624
|
+
const summary = objectConfig(report.summary);
|
|
2625
|
+
const repo = stringConfig(summary.repo);
|
|
2626
|
+
const repoFullName = stringConfig(summary.repoFullName);
|
|
2627
|
+
const scannedCommit = stringConfig(summary.scannedCommit);
|
|
2628
|
+
if (!repo || !repoFullName || !scannedCommit) return null;
|
|
2629
|
+
const hits = Array.isArray(summary.hits) ? summary.hits.flatMap((hit) => {
|
|
2630
|
+
const record = objectConfig(hit);
|
|
2631
|
+
const docPath = stringConfig(record.docPath);
|
|
2632
|
+
const changedPath = stringConfig(record.changedPath);
|
|
2633
|
+
const status = stringConfig(record.status);
|
|
2634
|
+
const excerpt = stringConfig(record.excerpt);
|
|
2635
|
+
const line = typeof record.line === "number" && Number.isInteger(record.line) ? record.line : null;
|
|
2636
|
+
if (!docPath || !changedPath || !status || !excerpt || line === null) return [];
|
|
2637
|
+
return [{
|
|
2638
|
+
docPath,
|
|
2639
|
+
changedPath,
|
|
2640
|
+
previousPath: stringConfig(record.previousPath),
|
|
2641
|
+
status,
|
|
2642
|
+
line,
|
|
2643
|
+
excerpt
|
|
2644
|
+
}];
|
|
2645
|
+
}) : [];
|
|
2646
|
+
return {
|
|
2647
|
+
repo,
|
|
2648
|
+
repoFullName,
|
|
2649
|
+
base: stringConfig(summary.base),
|
|
2650
|
+
scannedCommit,
|
|
2651
|
+
attemptedCommit: stringConfig(summary.attemptedCommit),
|
|
2652
|
+
truncated: Boolean(summary.truncated),
|
|
2653
|
+
hits
|
|
2654
|
+
};
|
|
2655
|
+
}
|
|
2656
|
+
async function docsFixPrompt(summary, report, paths, learningMemoryContext) {
|
|
2657
|
+
const skill = await runtimeSkillContent("neon-docs-fix", neonDocsFixSkillPath, paths);
|
|
2658
|
+
const docPaths = docsFixAllowedDocsPaths(summary);
|
|
2659
|
+
return [
|
|
2660
|
+
skill.trim() ? `Runtime skill neon-docs-fix:\n${skill.trim()}` : "Runtime skill neon-docs-fix was unavailable; follow the task bounds below.",
|
|
2661
|
+
"",
|
|
2662
|
+
`Task: update stale documentation references for ${summary.repoFullName}.`,
|
|
2663
|
+
`Report: /reports/${report.id}`,
|
|
2664
|
+
`Scanned commit: ${summary.scannedCommit}`,
|
|
2665
|
+
summary.attemptedCommit ? `Attempted commit: ${summary.attemptedCommit}` : null,
|
|
2666
|
+
summary.truncated ? "The drift scan was truncated. Fix only the listed, evidence-backed hits." : null,
|
|
2667
|
+
"",
|
|
2668
|
+
"Bounds:",
|
|
2669
|
+
"- Edit only documentation files listed under allowedDocsPaths.",
|
|
2670
|
+
"- Do not change source code, tests, generated files, package metadata, lockfiles, or runtime config.",
|
|
2671
|
+
"- Keep documentation edits minimal and fact-backed by the drift hits.",
|
|
2672
|
+
"- Commit local changes in this managed worktree when you make a fix.",
|
|
2673
|
+
"- Never push, open a pull request, post comments, submit reviews, or mutate external systems.",
|
|
2674
|
+
"",
|
|
2675
|
+
learningMemoryContext.text,
|
|
2676
|
+
"",
|
|
2677
|
+
"Drift facts:",
|
|
2678
|
+
JSON.stringify({
|
|
2679
|
+
report: reportLink(report),
|
|
2680
|
+
repo: summary.repo,
|
|
2681
|
+
repoFullName: summary.repoFullName,
|
|
2682
|
+
base: summary.base,
|
|
2683
|
+
scannedCommit: summary.scannedCommit,
|
|
2684
|
+
attemptedCommit: summary.attemptedCommit,
|
|
2685
|
+
targetCommit: docsFixHeadSha(summary),
|
|
2686
|
+
allowedDocsPaths: docPaths,
|
|
2687
|
+
hits: summary.hits.slice(0, 100)
|
|
2688
|
+
}, null, 2)
|
|
2689
|
+
].filter((line) => line !== null).join("\n");
|
|
2690
|
+
}
|
|
2691
|
+
async function runtimeSkillContent(id, fallbackPath, paths) {
|
|
2692
|
+
const loaded = await loadRuntimeSkill({ id }, paths);
|
|
2693
|
+
if (loaded.ok) return loaded.skill.content;
|
|
2694
|
+
return readFile(fallbackPath, "utf8").catch(() => "");
|
|
2695
|
+
}
|
|
2696
|
+
function docsFixAllowedDocsPaths(summary) {
|
|
2697
|
+
return [...new Set(summary.hits.map((hit) => hit.docPath))].sort();
|
|
2698
|
+
}
|
|
2699
|
+
function docsFixHeadSha(summary) {
|
|
2700
|
+
return summary.attemptedCommit ?? summary.scannedCommit;
|
|
2701
|
+
}
|
|
2702
|
+
function reportLink(report) {
|
|
2703
|
+
return {
|
|
2704
|
+
id: report.id,
|
|
2705
|
+
title: report.title,
|
|
2706
|
+
url: `/reports/${report.id}`
|
|
2707
|
+
};
|
|
2708
|
+
}
|
|
2709
|
+
function stringConfig(value) {
|
|
2710
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
2711
|
+
}
|
|
2712
|
+
function stringArrayConfig(value) {
|
|
2713
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
2714
|
+
}
|
|
2715
|
+
function objectConfig(value) {
|
|
2716
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
2717
|
+
}
|
|
2718
|
+
function errorMessage$1(error) {
|
|
2719
|
+
return error instanceof Error ? error.message : String(error);
|
|
2720
|
+
}
|
|
2721
|
+
//#endregion
|
|
2722
|
+
//#region src/server/routes/reports.ts
|
|
2723
|
+
function createReportApiRoutes(paths) {
|
|
2724
|
+
const routes = new Hono();
|
|
2725
|
+
routes.get("/reports", async (c) => {
|
|
2726
|
+
const kind = c.req.query("kind")?.trim() || void 0;
|
|
2727
|
+
const limitText = c.req.query("limit");
|
|
2728
|
+
const limit = limitText ? Number(limitText) : void 0;
|
|
2729
|
+
if (limitText && (limit === void 0 || !Number.isFinite(limit) || limit <= 0)) return c.json({
|
|
2730
|
+
ok: false,
|
|
2731
|
+
action: "reports_list",
|
|
2732
|
+
message: "Report limit must be a positive number.",
|
|
2733
|
+
items: []
|
|
2734
|
+
}, 400);
|
|
2735
|
+
try {
|
|
2736
|
+
return c.json({
|
|
2737
|
+
ok: true,
|
|
2738
|
+
action: "reports_list",
|
|
2739
|
+
items: await listReports(paths, {
|
|
2740
|
+
kind,
|
|
2741
|
+
limit
|
|
2742
|
+
}),
|
|
2743
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2744
|
+
});
|
|
2745
|
+
} catch (error) {
|
|
2746
|
+
return c.json({
|
|
2747
|
+
ok: false,
|
|
2748
|
+
action: "reports_list",
|
|
2749
|
+
message: error instanceof Error ? error.message : String(error),
|
|
2750
|
+
items: []
|
|
2751
|
+
}, 400);
|
|
2752
|
+
}
|
|
2753
|
+
});
|
|
2754
|
+
routes.get("/reports/:id", async (c) => {
|
|
2755
|
+
const id = c.req.param("id")?.trim();
|
|
2756
|
+
if (!id) return c.json({
|
|
2757
|
+
ok: false,
|
|
2758
|
+
action: "reports_read",
|
|
2759
|
+
message: "Report id is required.",
|
|
2760
|
+
item: null
|
|
2761
|
+
}, 400);
|
|
2762
|
+
try {
|
|
2763
|
+
const item = await readReport(id, paths);
|
|
2764
|
+
if (!item) return c.json({
|
|
2765
|
+
ok: false,
|
|
2766
|
+
action: "reports_read",
|
|
2767
|
+
message: "Report not found.",
|
|
2768
|
+
item: null
|
|
2769
|
+
}, 404);
|
|
2770
|
+
return c.json({
|
|
2771
|
+
ok: true,
|
|
2772
|
+
action: "reports_read",
|
|
2773
|
+
item,
|
|
2774
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2775
|
+
});
|
|
2776
|
+
} catch (error) {
|
|
2777
|
+
return c.json({
|
|
2778
|
+
ok: false,
|
|
2779
|
+
action: "reports_read",
|
|
2780
|
+
message: error instanceof Error ? error.message : String(error),
|
|
2781
|
+
item: null
|
|
2782
|
+
}, 400);
|
|
2783
|
+
}
|
|
2784
|
+
});
|
|
2785
|
+
routes.post("/reports/:id/stage-docs-fix", async (c) => {
|
|
2786
|
+
const result = await stageDocsDriftFix({
|
|
2787
|
+
...await safeJsonObject(c),
|
|
2788
|
+
reportId: c.req.param("id")
|
|
2789
|
+
}, paths);
|
|
2790
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
2791
|
+
});
|
|
2792
|
+
return routes;
|
|
2793
|
+
}
|
|
2794
|
+
function createReportFileRoutes(paths) {
|
|
2795
|
+
const routes = new Hono();
|
|
2796
|
+
routes.get("/:id", async (c) => {
|
|
2797
|
+
const id = c.req.param("id")?.trim();
|
|
2798
|
+
if (!id) return c.text("Not found", 404);
|
|
2799
|
+
try {
|
|
2800
|
+
const result = await readReportHtml(id, paths);
|
|
2801
|
+
if (!result) return c.text("Not found", 404);
|
|
2802
|
+
return c.html(result.html, 200, {
|
|
2803
|
+
"content-security-policy": "default-src 'none'; style-src 'unsafe-inline';",
|
|
2804
|
+
"x-content-type-options": "nosniff"
|
|
2805
|
+
});
|
|
2806
|
+
} catch {
|
|
2807
|
+
return c.text("Not found", 404);
|
|
2808
|
+
}
|
|
2809
|
+
});
|
|
2810
|
+
return routes;
|
|
2811
|
+
}
|
|
2812
|
+
//#endregion
|
|
2813
|
+
//#region src/server/routes/repos.ts
|
|
2814
|
+
function createReposRoutes(paths) {
|
|
2815
|
+
const routes = new Hono();
|
|
2816
|
+
routes.get("/", async (c) => {
|
|
2817
|
+
try {
|
|
2818
|
+
return c.json(await readRepoRegistrySnapshot(paths));
|
|
2819
|
+
} catch (error) {
|
|
2820
|
+
if (error instanceof ConfigValidationError) return c.json({
|
|
2821
|
+
error: "Invalid repo registry",
|
|
2822
|
+
message: error.message,
|
|
2823
|
+
path: error.path
|
|
2824
|
+
}, 500);
|
|
2825
|
+
throw error;
|
|
2826
|
+
}
|
|
2827
|
+
});
|
|
2828
|
+
routes.get("/health", async (c) => {
|
|
2829
|
+
try {
|
|
2830
|
+
return c.json(await readRepoHealthSnapshot(paths));
|
|
2831
|
+
} catch (error) {
|
|
2832
|
+
if (error instanceof ConfigValidationError) return c.json({
|
|
2833
|
+
error: "Invalid repo registry",
|
|
2834
|
+
message: error.message,
|
|
2835
|
+
path: error.path
|
|
2836
|
+
}, 500);
|
|
2837
|
+
throw error;
|
|
2838
|
+
}
|
|
2839
|
+
});
|
|
2840
|
+
return routes;
|
|
2841
|
+
}
|
|
2842
|
+
//#endregion
|
|
2843
|
+
//#region src/server/routes/scheduled-tasks.ts
|
|
2844
|
+
function createScheduledTaskRoutes(paths) {
|
|
2845
|
+
const routes = new Hono();
|
|
2846
|
+
routes.get("/scheduled-tasks", async (c) => c.json(await listTaskRecords(paths)));
|
|
2847
|
+
routes.post("/scheduled-tasks/briefings", async (c) => {
|
|
2848
|
+
const result = await createBriefingTask(await safeJsonObject(c), paths);
|
|
2849
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
2850
|
+
});
|
|
2851
|
+
routes.post("/scheduled-tasks/instructions", async (c) => {
|
|
2852
|
+
const result = await createAgentInstructionTask(await safeJsonObject(c), paths);
|
|
2853
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
2854
|
+
});
|
|
2855
|
+
routes.get("/scheduled-tasks/:id", async (c) => {
|
|
2856
|
+
const result = await readTaskRecord(c.req.param("id"), paths);
|
|
2857
|
+
return c.json(result, result.ok ? 200 : 404);
|
|
2858
|
+
});
|
|
2859
|
+
routes.post("/scheduled-tasks/:id/pause", async (c) => {
|
|
2860
|
+
const result = await setTaskEnabled(c.req.param("id"), false, paths);
|
|
2861
|
+
return c.json(result, result.ok ? 200 : 404);
|
|
2862
|
+
});
|
|
2863
|
+
routes.post("/scheduled-tasks/:id/resume", async (c) => {
|
|
2864
|
+
const result = await setTaskEnabled(c.req.param("id"), true, paths);
|
|
2865
|
+
return c.json(result, result.ok ? 200 : 404);
|
|
2866
|
+
});
|
|
2867
|
+
routes.delete("/scheduled-tasks/:id", async (c) => {
|
|
2868
|
+
if (c.req.query("confirm") !== "true") return c.json({
|
|
2869
|
+
ok: false,
|
|
2870
|
+
action: "scheduled_task_delete",
|
|
2871
|
+
changed: false,
|
|
2872
|
+
message: "Deleting a scheduled task requires confirm=true.",
|
|
2873
|
+
requires: ["confirm"]
|
|
2874
|
+
}, 400);
|
|
2875
|
+
const result = await removeTask(c.req.param("id"), paths);
|
|
2876
|
+
return c.json(result, result.ok ? 200 : 404);
|
|
2877
|
+
});
|
|
2878
|
+
return routes;
|
|
2879
|
+
}
|
|
2880
|
+
//#endregion
|
|
2881
|
+
//#region src/server/routes/runtime.ts
|
|
2882
|
+
function createRuntimeRoutes(paths) {
|
|
2883
|
+
const routes = new Hono();
|
|
2884
|
+
routes.get("/health", (c) => c.json({
|
|
2885
|
+
ok: true,
|
|
2886
|
+
service: "neondeck",
|
|
2887
|
+
home: paths.home,
|
|
2888
|
+
uptimeSeconds: Math.round(process.uptime())
|
|
2889
|
+
}));
|
|
2890
|
+
routes.get("/runtime/status", async (c) => {
|
|
2891
|
+
return c.json(await readRuntimeStatus(paths));
|
|
2892
|
+
});
|
|
2893
|
+
routes.get("/local-api/session", async (c) => {
|
|
2894
|
+
return c.json({
|
|
2895
|
+
ok: true,
|
|
2896
|
+
action: "local_api_session_read",
|
|
2897
|
+
token: await readLocalApiToken(paths),
|
|
2898
|
+
header: localApiAuthHeader,
|
|
2899
|
+
queryParam: localApiTokenQueryParam
|
|
2900
|
+
});
|
|
2901
|
+
});
|
|
2902
|
+
routes.get("/autopilot/state", async (c) => {
|
|
2903
|
+
return c.json(v.parse(autopilotStateSchema, await readAutopilotState(paths)));
|
|
2904
|
+
});
|
|
2905
|
+
return routes;
|
|
2906
|
+
}
|
|
2907
|
+
//#endregion
|
|
2908
|
+
//#region src/server/routes/safety.ts
|
|
2909
|
+
function createSafetyRoutes(paths) {
|
|
2910
|
+
const routes = new Hono();
|
|
2911
|
+
routes.get("/policy", (c) => {
|
|
2912
|
+
return c.json(readSafetyPolicy(paths));
|
|
2913
|
+
});
|
|
2914
|
+
return routes;
|
|
2915
|
+
}
|
|
2916
|
+
//#endregion
|
|
2917
|
+
//#region src/server/scheduler-workflow.ts
|
|
2918
|
+
var schedulerWorkflowName = "scheduler-tick";
|
|
2919
|
+
var schedulerWorkflowAdmissionLeaseKey = "scheduler.tick.workflow.admission.lease";
|
|
2920
|
+
var defaultWorkflowWaitTimeoutMs = 300 * 1e3;
|
|
2921
|
+
var defaultWorkflowAdmissionLeaseTtlMs = 36e4;
|
|
2922
|
+
var defaultWorkflowPollMs = 250;
|
|
2923
|
+
var schedulerTickInFlight = /* @__PURE__ */ new Map();
|
|
2924
|
+
async function runObservedSchedulerTick(paths, dependencies = {}) {
|
|
2925
|
+
const key = paths.home;
|
|
2926
|
+
const inFlight = schedulerTickInFlight.get(key);
|
|
2927
|
+
if (inFlight) return inFlight;
|
|
2928
|
+
const next = runObservedSchedulerTickOnce(paths, dependencies).finally(() => {
|
|
2929
|
+
schedulerTickInFlight.delete(key);
|
|
2930
|
+
});
|
|
2931
|
+
schedulerTickInFlight.set(key, next);
|
|
2932
|
+
return next;
|
|
2933
|
+
}
|
|
2934
|
+
async function runObservedSchedulerTickOnce(paths, dependencies) {
|
|
2935
|
+
await ensureRuntimeHome(paths);
|
|
2936
|
+
const now = currentDate(dependencies);
|
|
2937
|
+
let activeRun;
|
|
2938
|
+
try {
|
|
2939
|
+
activeRun = await activeSchedulerTickRun(paths, dependencies, now);
|
|
2940
|
+
} catch (error) {
|
|
2941
|
+
return runDirectSchedulerTickFallback(paths, error, "inspection");
|
|
2942
|
+
}
|
|
2943
|
+
if (activeRun) return resultFromObservedRun(paths, activeRun.runId, dependencies);
|
|
2944
|
+
const admission = acquireSchedulerWorkflowAdmissionLease(paths, now, schedulerWorkflowAdmissionLeaseTtlMs(dependencies));
|
|
2945
|
+
if (!admission.acquired) {
|
|
2946
|
+
if (admission.lease?.runId) return resultFromObservedRun(paths, admission.lease.runId, dependencies);
|
|
2947
|
+
return {
|
|
2948
|
+
ok: true,
|
|
2949
|
+
action: "scheduler_tick",
|
|
2950
|
+
changed: false,
|
|
2951
|
+
outcome: "silent",
|
|
2952
|
+
message: "Scheduler tick skipped because another process is admitting the observed scheduler workflow.",
|
|
2953
|
+
extra: { admissionLease: admission.lease ? "active" : "busy" }
|
|
2954
|
+
};
|
|
2955
|
+
}
|
|
2956
|
+
try {
|
|
2957
|
+
let receipt;
|
|
2958
|
+
try {
|
|
2959
|
+
receipt = await invokeSchedulerTickWorkflow(paths, dependencies);
|
|
2960
|
+
} catch (error) {
|
|
2961
|
+
return runDirectSchedulerTickFallback(paths, error, "admission");
|
|
2962
|
+
}
|
|
2963
|
+
recordSchedulerWorkflowAdmissionRunId(paths, admission.lease.owner, receipt.runId, currentDate(dependencies), schedulerWorkflowAdmissionLeaseTtlMs(dependencies));
|
|
2964
|
+
return resultFromObservedRun(paths, receipt.runId, dependencies);
|
|
2965
|
+
} finally {
|
|
2966
|
+
await releaseSchedulerWorkflowAdmissionLease(paths, admission.lease.owner);
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
async function resultFromObservedRun(paths, runId, dependencies) {
|
|
2970
|
+
try {
|
|
2971
|
+
return resultFromTerminalRun(runId, await waitForRun(runId, dependencies));
|
|
2972
|
+
} catch (error) {
|
|
2973
|
+
return runDirectSchedulerTickFallback(paths, error, "inspection");
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
async function runDirectSchedulerTickFallback(paths, error, phase) {
|
|
2977
|
+
const message = errorMessage(error);
|
|
2978
|
+
const fallback = await runSchedulerTick(paths);
|
|
2979
|
+
await addNotification({
|
|
2980
|
+
level: "attention",
|
|
2981
|
+
title: "Scheduler workflow observation failed",
|
|
2982
|
+
message: `Scheduler tick workflow ${phase} failed; ran the direct scheduler tick instead. ${message}`,
|
|
2983
|
+
source: "scheduler",
|
|
2984
|
+
sourceId: "scheduler-tick:workflow-observation-fallback",
|
|
2985
|
+
data: {
|
|
2986
|
+
phase,
|
|
2987
|
+
error: message,
|
|
2988
|
+
fallbackOutcome: fallback.outcome ?? null
|
|
2989
|
+
}
|
|
2990
|
+
}, paths);
|
|
2991
|
+
return {
|
|
2992
|
+
...fallback,
|
|
2993
|
+
changed: true,
|
|
2994
|
+
message: `Scheduler tick workflow ${phase} failed; ran direct scheduler tick instead. ${fallback.message}`,
|
|
2995
|
+
extra: {
|
|
2996
|
+
...objectField(fallback.extra),
|
|
2997
|
+
workflowObservationFallback: true,
|
|
2998
|
+
workflowObservationPhase: phase,
|
|
2999
|
+
...phase === "admission" ? {
|
|
3000
|
+
workflowAdmissionFailed: true,
|
|
3001
|
+
workflowAdmissionError: message
|
|
3002
|
+
} : {
|
|
3003
|
+
workflowInspectionFailed: true,
|
|
3004
|
+
workflowInspectionError: message
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
};
|
|
3008
|
+
}
|
|
3009
|
+
function resultFromTerminalRun(runId, run) {
|
|
3010
|
+
if (!run || run.status === "active") return {
|
|
3011
|
+
ok: false,
|
|
3012
|
+
action: "scheduler_tick",
|
|
3013
|
+
changed: false,
|
|
3014
|
+
outcome: "failed",
|
|
3015
|
+
message: `Scheduler tick workflow ${runId} did not complete within ${defaultWorkflowWaitTimeoutMs}ms.`,
|
|
3016
|
+
errors: ["scheduler-tick workflow timed out"],
|
|
3017
|
+
extra: {
|
|
3018
|
+
runId,
|
|
3019
|
+
activeWorkflow: true
|
|
3020
|
+
},
|
|
3021
|
+
runId
|
|
3022
|
+
};
|
|
3023
|
+
if (run.status === "errored") return {
|
|
3024
|
+
ok: false,
|
|
3025
|
+
action: "scheduler_tick",
|
|
3026
|
+
changed: false,
|
|
3027
|
+
outcome: "failed",
|
|
3028
|
+
message: `Scheduler tick workflow ${runId} failed: ${errorMessage(run.error)}.`,
|
|
3029
|
+
errors: [errorMessage(run.error)],
|
|
3030
|
+
extra: { runId },
|
|
3031
|
+
runId
|
|
3032
|
+
};
|
|
3033
|
+
return {
|
|
3034
|
+
...schedulerResultFromRun(run.result),
|
|
3035
|
+
runId
|
|
3036
|
+
};
|
|
3037
|
+
}
|
|
3038
|
+
function startSchedulerObservedLoop(paths, intervalMs = 6e4, runTick = runObservedSchedulerTick) {
|
|
3039
|
+
let tickInFlight = false;
|
|
3040
|
+
const timer = setInterval(() => {
|
|
3041
|
+
if (tickInFlight) return;
|
|
3042
|
+
tickInFlight = true;
|
|
3043
|
+
runTick(paths).catch((error) => {
|
|
3044
|
+
console.error("[neondeck] scheduler observed tick failed", error);
|
|
3045
|
+
}).finally(() => {
|
|
3046
|
+
tickInFlight = false;
|
|
3047
|
+
});
|
|
3048
|
+
}, intervalMs);
|
|
3049
|
+
timer.unref?.();
|
|
3050
|
+
return timer;
|
|
3051
|
+
}
|
|
3052
|
+
async function invokeSchedulerTickWorkflow(paths, dependencies) {
|
|
3053
|
+
if (dependencies.invokeWorkflow) return dependencies.invokeWorkflow(paths);
|
|
3054
|
+
return invoke(scheduler_tick_default, { input: { runtimeHome: paths.home } });
|
|
3055
|
+
}
|
|
3056
|
+
async function activeSchedulerTickRun(paths, dependencies, now) {
|
|
3057
|
+
const active = await (dependencies.listRuns ?? listRuns)({
|
|
3058
|
+
workflowName: schedulerWorkflowName,
|
|
3059
|
+
status: "active",
|
|
3060
|
+
limit: 50
|
|
3061
|
+
});
|
|
3062
|
+
for (const pointer of active.runs) {
|
|
3063
|
+
const run = await (dependencies.getRun ?? getRun)(pointer.runId);
|
|
3064
|
+
if (!run || run.status !== "active") continue;
|
|
3065
|
+
if (runRuntimeHome(run) !== paths.home) continue;
|
|
3066
|
+
if (isStaleActiveRun(run, now, activeRunTtlMs(dependencies))) continue;
|
|
3067
|
+
return run;
|
|
3068
|
+
}
|
|
3069
|
+
return null;
|
|
3070
|
+
}
|
|
3071
|
+
async function waitForRun(runId, dependencies) {
|
|
3072
|
+
const deadline = Date.now() + (dependencies.waitTimeoutMs ?? defaultWorkflowWaitTimeoutMs);
|
|
3073
|
+
let run = null;
|
|
3074
|
+
while (Date.now() < deadline) {
|
|
3075
|
+
run = await (dependencies.getRun ?? getRun)(runId);
|
|
3076
|
+
if (run && run.status !== "active") return run;
|
|
3077
|
+
await (dependencies.sleep ?? sleep)(dependencies.pollMs ?? defaultWorkflowPollMs);
|
|
3078
|
+
}
|
|
3079
|
+
return run;
|
|
3080
|
+
}
|
|
3081
|
+
function schedulerResultFromRun(result) {
|
|
3082
|
+
const record = objectField(result);
|
|
3083
|
+
if (typeof record.ok === "boolean" && typeof record.message === "string") return record;
|
|
3084
|
+
return {
|
|
3085
|
+
ok: false,
|
|
3086
|
+
action: "scheduler_tick",
|
|
3087
|
+
changed: false,
|
|
3088
|
+
outcome: "failed",
|
|
3089
|
+
message: "Scheduler tick workflow returned an invalid result.",
|
|
3090
|
+
errors: ["invalid scheduler-tick workflow result"]
|
|
3091
|
+
};
|
|
3092
|
+
}
|
|
3093
|
+
function objectField(value) {
|
|
3094
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
3095
|
+
}
|
|
3096
|
+
function runRuntimeHome(run) {
|
|
3097
|
+
const input = objectField(run.input);
|
|
3098
|
+
return typeof input.runtimeHome === "string" && input.runtimeHome ? input.runtimeHome : null;
|
|
3099
|
+
}
|
|
3100
|
+
function acquireSchedulerWorkflowAdmissionLease(paths, now, ttlMs) {
|
|
3101
|
+
const database = new DatabaseSync(paths.neondeckDatabase);
|
|
3102
|
+
try {
|
|
3103
|
+
database.exec("BEGIN IMMEDIATE;");
|
|
3104
|
+
const existingLease = parseSchedulerWorkflowAdmissionLease(readMetadataValue(database.prepare("SELECT value FROM app_metadata WHERE key = ?;").get(schedulerWorkflowAdmissionLeaseKey)));
|
|
3105
|
+
if (existingLease && existingLease.runtimeHome === paths.home && Date.parse(existingLease.expiresAt) > now.getTime()) {
|
|
3106
|
+
database.exec("COMMIT;");
|
|
3107
|
+
return {
|
|
3108
|
+
acquired: false,
|
|
3109
|
+
lease: existingLease
|
|
3110
|
+
};
|
|
3111
|
+
}
|
|
3112
|
+
const acquiredAt = now.toISOString();
|
|
3113
|
+
const lease = {
|
|
3114
|
+
owner: `pid-${process.pid}-${randomUUID()}`,
|
|
3115
|
+
runtimeHome: paths.home,
|
|
3116
|
+
acquiredAt,
|
|
3117
|
+
expiresAt: new Date(now.getTime() + ttlMs).toISOString(),
|
|
3118
|
+
runId: null
|
|
3119
|
+
};
|
|
3120
|
+
database.prepare(`
|
|
3121
|
+
INSERT INTO app_metadata (key, value, updated_at)
|
|
3122
|
+
VALUES (?, ?, ?)
|
|
3123
|
+
ON CONFLICT(key) DO UPDATE SET
|
|
3124
|
+
value = excluded.value,
|
|
3125
|
+
updated_at = excluded.updated_at;
|
|
3126
|
+
`).run(schedulerWorkflowAdmissionLeaseKey, JSON.stringify(lease), acquiredAt);
|
|
3127
|
+
database.exec("COMMIT;");
|
|
3128
|
+
return {
|
|
3129
|
+
acquired: true,
|
|
3130
|
+
lease
|
|
3131
|
+
};
|
|
3132
|
+
} catch (error) {
|
|
3133
|
+
rollbackQuietly(database);
|
|
3134
|
+
if (isSqliteBusy(error)) return {
|
|
3135
|
+
acquired: false,
|
|
3136
|
+
lease: null
|
|
3137
|
+
};
|
|
3138
|
+
throw error;
|
|
3139
|
+
} finally {
|
|
3140
|
+
database.close();
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
function recordSchedulerWorkflowAdmissionRunId(paths, owner, runId, now, ttlMs) {
|
|
3144
|
+
const database = new DatabaseSync(paths.neondeckDatabase);
|
|
3145
|
+
try {
|
|
3146
|
+
database.exec("BEGIN IMMEDIATE;");
|
|
3147
|
+
const existingLease = parseSchedulerWorkflowAdmissionLease(readMetadataValue(database.prepare("SELECT value FROM app_metadata WHERE key = ?;").get(schedulerWorkflowAdmissionLeaseKey)));
|
|
3148
|
+
if (existingLease?.owner !== owner) {
|
|
3149
|
+
database.exec("COMMIT;");
|
|
3150
|
+
return false;
|
|
3151
|
+
}
|
|
3152
|
+
const updatedLease = {
|
|
3153
|
+
...existingLease,
|
|
3154
|
+
runId,
|
|
3155
|
+
expiresAt: new Date(now.getTime() + ttlMs).toISOString()
|
|
3156
|
+
};
|
|
3157
|
+
database.prepare(`
|
|
3158
|
+
UPDATE app_metadata
|
|
3159
|
+
SET value = ?, updated_at = ?
|
|
3160
|
+
WHERE key = ?;
|
|
3161
|
+
`).run(JSON.stringify(updatedLease), now.toISOString(), schedulerWorkflowAdmissionLeaseKey);
|
|
3162
|
+
database.exec("COMMIT;");
|
|
3163
|
+
return true;
|
|
3164
|
+
} catch (error) {
|
|
3165
|
+
rollbackQuietly(database);
|
|
3166
|
+
if (isSqliteBusy(error)) return false;
|
|
3167
|
+
throw error;
|
|
3168
|
+
} finally {
|
|
3169
|
+
database.close();
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
async function releaseSchedulerWorkflowAdmissionLease(paths, owner) {
|
|
3173
|
+
const maxAttempts = 5;
|
|
3174
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
3175
|
+
if (releaseSchedulerWorkflowAdmissionLeaseOnce(paths, owner)) return;
|
|
3176
|
+
if (attempt < maxAttempts) await sleep(25 * attempt);
|
|
3177
|
+
}
|
|
3178
|
+
console.warn("[neondeck] scheduler workflow admission lease release was blocked by SQLite; lease will expire automatically.");
|
|
3179
|
+
try {
|
|
3180
|
+
await addNotification({
|
|
3181
|
+
level: "attention",
|
|
3182
|
+
title: "Scheduler workflow lease release blocked",
|
|
3183
|
+
message: "Scheduler workflow admission lease release was blocked by SQLite; future observed ticks may wait for the lease TTL before retrying.",
|
|
3184
|
+
source: "scheduler",
|
|
3185
|
+
sourceId: "scheduler-tick:workflow-admission-lease-release",
|
|
3186
|
+
data: { owner }
|
|
3187
|
+
}, paths);
|
|
3188
|
+
} catch (error) {
|
|
3189
|
+
console.warn("[neondeck] failed to persist scheduler workflow lease release warning", error);
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
function releaseSchedulerWorkflowAdmissionLeaseOnce(paths, owner) {
|
|
3193
|
+
const database = new DatabaseSync(paths.neondeckDatabase);
|
|
3194
|
+
try {
|
|
3195
|
+
database.exec("BEGIN IMMEDIATE;");
|
|
3196
|
+
if (parseSchedulerWorkflowAdmissionLease(readMetadataValue(database.prepare("SELECT value FROM app_metadata WHERE key = ?;").get(schedulerWorkflowAdmissionLeaseKey)))?.owner === owner) database.prepare("DELETE FROM app_metadata WHERE key = ?;").run(schedulerWorkflowAdmissionLeaseKey);
|
|
3197
|
+
database.exec("COMMIT;");
|
|
3198
|
+
return true;
|
|
3199
|
+
} catch (error) {
|
|
3200
|
+
rollbackQuietly(database);
|
|
3201
|
+
if (isSqliteBusy(error)) return false;
|
|
3202
|
+
throw error;
|
|
3203
|
+
} finally {
|
|
3204
|
+
database.close();
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
function parseSchedulerWorkflowAdmissionLease(value) {
|
|
3208
|
+
if (!value) return;
|
|
3209
|
+
try {
|
|
3210
|
+
const parsed = objectField(JSON.parse(value));
|
|
3211
|
+
if (typeof parsed.owner === "string" && typeof parsed.runtimeHome === "string" && typeof parsed.acquiredAt === "string" && typeof parsed.expiresAt === "string") return {
|
|
3212
|
+
owner: parsed.owner,
|
|
3213
|
+
runtimeHome: parsed.runtimeHome,
|
|
3214
|
+
acquiredAt: parsed.acquiredAt,
|
|
3215
|
+
expiresAt: parsed.expiresAt,
|
|
3216
|
+
runId: typeof parsed.runId === "string" ? parsed.runId : null
|
|
3217
|
+
};
|
|
3218
|
+
} catch {
|
|
3219
|
+
return;
|
|
3220
|
+
}
|
|
3221
|
+
}
|
|
3222
|
+
function isStaleActiveRun(run, now, ttlMs) {
|
|
3223
|
+
const startedAt = Date.parse(run.startedAt);
|
|
3224
|
+
return Number.isFinite(startedAt) && startedAt + ttlMs <= now.getTime();
|
|
3225
|
+
}
|
|
3226
|
+
function schedulerWorkflowAdmissionLeaseTtlMs(dependencies) {
|
|
3227
|
+
return dependencies.admissionLeaseTtlMs ?? defaultWorkflowAdmissionLeaseTtlMs;
|
|
3228
|
+
}
|
|
3229
|
+
function activeRunTtlMs(dependencies) {
|
|
3230
|
+
return dependencies.activeRunTtlMs ?? defaultWorkflowWaitTimeoutMs;
|
|
3231
|
+
}
|
|
3232
|
+
function currentDate(dependencies) {
|
|
3233
|
+
return dependencies.now?.() ?? /* @__PURE__ */ new Date();
|
|
3234
|
+
}
|
|
3235
|
+
function errorMessage(error) {
|
|
3236
|
+
return error instanceof Error ? error.message : String(error);
|
|
3237
|
+
}
|
|
3238
|
+
function sleep(milliseconds) {
|
|
3239
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
3240
|
+
}
|
|
3241
|
+
//#endregion
|
|
3242
|
+
//#region src/server/routes/scheduler.ts
|
|
3243
|
+
function createSchedulerRoutes(paths) {
|
|
3244
|
+
const routes = new Hono();
|
|
3245
|
+
routes.post("/scheduler/tick", async (c) => {
|
|
3246
|
+
return c.json(await runObservedSchedulerTick(paths));
|
|
3247
|
+
});
|
|
3248
|
+
return routes;
|
|
3249
|
+
}
|
|
3250
|
+
//#endregion
|
|
3251
|
+
//#region src/server/routes/sessions.ts
|
|
3252
|
+
function createSessionRoutes(paths) {
|
|
3253
|
+
const routes = new Hono();
|
|
3254
|
+
routes.get("/session", async (c) => {
|
|
3255
|
+
return c.json(await readNeonSessionState(paths, c.req.query("surface")));
|
|
3256
|
+
});
|
|
3257
|
+
routes.get("/sessions", async (c) => {
|
|
3258
|
+
return c.json(await listChatSessions({
|
|
3259
|
+
includeArchived: c.req.query("includeArchived") === "1",
|
|
3260
|
+
kind: sessionKind(c.req.query("kind")),
|
|
3261
|
+
surface: c.req.query("surface") || void 0
|
|
3262
|
+
}, paths));
|
|
3263
|
+
});
|
|
3264
|
+
routes.post("/sessions/search", async (c) => {
|
|
3265
|
+
const result = await searchChatSessions(await safeJsonBody(c), paths);
|
|
3266
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
3267
|
+
});
|
|
3268
|
+
routes.post("/sessions", async (c) => {
|
|
3269
|
+
const result = await createChatSession(await safeJsonBody(c), paths);
|
|
3270
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
3271
|
+
});
|
|
3272
|
+
routes.get("/sessions/:id", async (c) => {
|
|
3273
|
+
const result = await readChatSession({
|
|
3274
|
+
id: c.req.param("id"),
|
|
3275
|
+
surface: c.req.query("surface") || void 0,
|
|
3276
|
+
reason: c.req.query("reason") || void 0
|
|
3277
|
+
}, paths);
|
|
3278
|
+
return c.json(result, result.ok ? 200 : 404);
|
|
3279
|
+
});
|
|
3280
|
+
routes.get("/sessions/:id/messages", async (c) => {
|
|
3281
|
+
const rawLimit = Number(c.req.query("limit"));
|
|
3282
|
+
const result = await readChatSessionMessages({
|
|
3283
|
+
id: c.req.param("id"),
|
|
3284
|
+
cursor: c.req.query("cursor") || void 0,
|
|
3285
|
+
limit: Number.isFinite(rawLimit) ? rawLimit : void 0,
|
|
3286
|
+
surface: c.req.query("surface") || void 0,
|
|
3287
|
+
reason: c.req.query("reason") || void 0,
|
|
3288
|
+
explicitUserRequest: c.req.query("explicitUserRequest") === "1"
|
|
3289
|
+
}, paths);
|
|
3290
|
+
return c.json(result, result.ok ? 200 : "requires" in result ? 400 : 404);
|
|
3291
|
+
});
|
|
3292
|
+
routes.get("/sessions/:id/command-events", async (c) => {
|
|
3293
|
+
const rawLimit = Number(c.req.query("limit"));
|
|
3294
|
+
const result = await listChatSessionCommandEvents({
|
|
3295
|
+
sessionId: c.req.param("id"),
|
|
3296
|
+
limit: Number.isFinite(rawLimit) ? rawLimit : void 0
|
|
3297
|
+
}, paths);
|
|
3298
|
+
return c.json(result, result.ok ? 200 : 404);
|
|
3299
|
+
});
|
|
3300
|
+
routes.post("/sessions/:id/command-events", async (c) => {
|
|
3301
|
+
const result = await createChatSessionCommandEvent({
|
|
3302
|
+
...await safeJsonObject(c),
|
|
3303
|
+
sessionId: c.req.param("id")
|
|
3304
|
+
}, paths);
|
|
3305
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
3306
|
+
});
|
|
3307
|
+
routes.post("/sessions/:id/command-events/:eventId", async (c) => {
|
|
3308
|
+
const result = await updateChatSessionCommandEvent({
|
|
3309
|
+
...await safeJsonObject(c),
|
|
3310
|
+
sessionId: c.req.param("id"),
|
|
3311
|
+
eventId: c.req.param("eventId")
|
|
3312
|
+
}, paths);
|
|
3313
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
3314
|
+
});
|
|
3315
|
+
routes.post("/sessions/:id/summary/refresh", async (c) => {
|
|
3316
|
+
const result = await refreshChatSessionSummary({
|
|
3317
|
+
...await safeJsonObject(c),
|
|
3318
|
+
id: c.req.param("id")
|
|
3319
|
+
}, paths);
|
|
3320
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
3321
|
+
});
|
|
3322
|
+
routes.post("/sessions/:id/reference", async (c) => {
|
|
3323
|
+
const result = await referenceChatSession({
|
|
3324
|
+
...await safeJsonObject(c),
|
|
3325
|
+
id: c.req.param("id")
|
|
3326
|
+
}, paths);
|
|
3327
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
3328
|
+
});
|
|
3329
|
+
routes.post("/sessions/:id/switch", async (c) => {
|
|
3330
|
+
const result = await switchChatSession({
|
|
3331
|
+
...await safeJsonObject(c),
|
|
3332
|
+
id: c.req.param("id")
|
|
3333
|
+
}, paths);
|
|
3334
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
3335
|
+
});
|
|
3336
|
+
routes.post("/sessions/:id/rename", async (c) => {
|
|
3337
|
+
const result = await renameChatSession({
|
|
3338
|
+
...await safeJsonObject(c),
|
|
3339
|
+
id: c.req.param("id")
|
|
3340
|
+
}, paths);
|
|
3341
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
3342
|
+
});
|
|
3343
|
+
routes.post("/sessions/:id/pin", async (c) => {
|
|
3344
|
+
const result = await pinChatSession({
|
|
3345
|
+
...await safeJsonObject(c),
|
|
3346
|
+
id: c.req.param("id")
|
|
3347
|
+
}, paths);
|
|
3348
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
3349
|
+
});
|
|
3350
|
+
routes.post("/sessions/:id/archive", async (c) => {
|
|
3351
|
+
const result = await archiveChatSession({
|
|
3352
|
+
...await safeJsonObject(c),
|
|
3353
|
+
id: c.req.param("id")
|
|
3354
|
+
}, paths);
|
|
3355
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
3356
|
+
});
|
|
3357
|
+
routes.post("/sessions/:id/restore", async (c) => {
|
|
3358
|
+
const result = await restoreChatSession({
|
|
3359
|
+
...await safeJsonObject(c),
|
|
3360
|
+
id: c.req.param("id")
|
|
3361
|
+
}, paths);
|
|
3362
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
3363
|
+
});
|
|
3364
|
+
routes.post("/sessions/:id/link-context", async (c) => {
|
|
3365
|
+
const result = await linkChatSessionContext({
|
|
3366
|
+
...await safeJsonObject(c),
|
|
3367
|
+
id: c.req.param("id")
|
|
3368
|
+
}, paths);
|
|
3369
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
3370
|
+
});
|
|
3371
|
+
return routes;
|
|
3372
|
+
}
|
|
3373
|
+
function sessionKind(value) {
|
|
3374
|
+
if (value === "main" || value === "scratch" || value === "general" || value === "repo" || value === "watch" || value === "task" || value === "briefing") return value;
|
|
3375
|
+
}
|
|
3376
|
+
//#endregion
|
|
3377
|
+
//#region src/server/routes/skills.ts
|
|
3378
|
+
function createSkillRoutes(paths) {
|
|
3379
|
+
const routes = new Hono();
|
|
3380
|
+
routes.get("/patches", async (c) => {
|
|
3381
|
+
const status = learningCandidateStatus(c.req.query("status"));
|
|
3382
|
+
if (c.req.query("status") && !status) return c.json({
|
|
3383
|
+
ok: false,
|
|
3384
|
+
action: "skill_patch_list",
|
|
3385
|
+
changed: false,
|
|
3386
|
+
message: `Invalid candidate status "${c.req.query("status")}".`
|
|
3387
|
+
}, 400);
|
|
3388
|
+
if (c.req.query("limit") && !boundedQueryLimit(c.req.query("limit"), 100)) return c.json({
|
|
3389
|
+
ok: false,
|
|
3390
|
+
action: "skill_patch_list",
|
|
3391
|
+
changed: false,
|
|
3392
|
+
message: `Invalid patch limit "${c.req.query("limit")}".`
|
|
3393
|
+
}, 400);
|
|
3394
|
+
return c.json(await listSkillPatchCandidates({
|
|
3395
|
+
status,
|
|
3396
|
+
skillId: c.req.query("skillId") || void 0,
|
|
3397
|
+
limit: boundedQueryLimit(c.req.query("limit"), 100)
|
|
3398
|
+
}, paths));
|
|
3399
|
+
});
|
|
3400
|
+
routes.post("/patches/:id/apply", async (c) => {
|
|
3401
|
+
const result = await applySkillPatchCandidate({
|
|
3402
|
+
...await safeJsonObject(c),
|
|
3403
|
+
id: c.req.param("id")
|
|
3404
|
+
}, paths);
|
|
3405
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
3406
|
+
});
|
|
3407
|
+
routes.post("/patches/:id/reject", async (c) => {
|
|
3408
|
+
const result = await rejectSkillPatchCandidate({
|
|
3409
|
+
...await safeJsonObject(c),
|
|
3410
|
+
id: c.req.param("id")
|
|
3411
|
+
}, paths);
|
|
3412
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
3413
|
+
});
|
|
3414
|
+
routes.post("/patches/:id/restore", async (c) => {
|
|
3415
|
+
const result = await restoreSkillPatchCandidate({
|
|
3416
|
+
...await safeJsonObject(c),
|
|
3417
|
+
id: c.req.param("id")
|
|
3418
|
+
}, paths);
|
|
3419
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
3420
|
+
});
|
|
3421
|
+
routes.get("/", async (c) => {
|
|
3422
|
+
return c.json(await listRuntimeSkills(paths));
|
|
3423
|
+
});
|
|
3424
|
+
routes.get("/:id", async (c) => {
|
|
3425
|
+
const result = await loadRuntimeSkill({ id: c.req.param("id") }, paths);
|
|
3426
|
+
if (!result.ok) return c.json(result, 404);
|
|
3427
|
+
return c.json(result);
|
|
3428
|
+
});
|
|
3429
|
+
routes.post("/reload", async (c) => {
|
|
3430
|
+
return c.json(await reloadRuntimeSkills(paths));
|
|
3431
|
+
});
|
|
3432
|
+
return routes;
|
|
3433
|
+
}
|
|
3434
|
+
//#endregion
|
|
3435
|
+
//#region src/server/routes/watches.ts
|
|
3436
|
+
function createWatchRoutes(paths) {
|
|
3437
|
+
const routes = new Hono();
|
|
3438
|
+
routes.get("/watches", async (c) => {
|
|
3439
|
+
return c.json(await listPrWatches(paths));
|
|
3440
|
+
});
|
|
3441
|
+
routes.post("/watches", async (c) => {
|
|
3442
|
+
const result = await addPrWatch(await safeJsonBody(c), paths);
|
|
3443
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
3444
|
+
});
|
|
3445
|
+
routes.get("/watches/events/watermarks", async (c) => {
|
|
3446
|
+
return c.json(await listPrWatchEventWatermarks({ watchId: c.req.query("watchId") || void 0 }, paths));
|
|
3447
|
+
});
|
|
3448
|
+
routes.get("/watches/:id/events/watermarks", async (c) => {
|
|
3449
|
+
return c.json(await listPrWatchEventWatermarks({ watchId: c.req.param("id") }, paths));
|
|
3450
|
+
});
|
|
3451
|
+
routes.post("/watches/:id/events/refresh", async (c) => {
|
|
3452
|
+
const result = await refreshPrWatchEventState({
|
|
3453
|
+
...await safeJsonObject(c),
|
|
3454
|
+
watchId: c.req.param("id")
|
|
3455
|
+
}, paths);
|
|
3456
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
3457
|
+
});
|
|
3458
|
+
routes.get("/watches/ref", async (c) => {
|
|
3459
|
+
return c.json(await listRefWatches(paths));
|
|
3460
|
+
});
|
|
3461
|
+
routes.post("/watches/ref", async (c) => {
|
|
3462
|
+
const result = await addRefWatch(await safeJsonBody(c), paths);
|
|
3463
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
3464
|
+
});
|
|
3465
|
+
routes.post("/watches/:id/polling", async (c) => {
|
|
3466
|
+
const result = await setPrWatchPolling({
|
|
3467
|
+
...await safeJsonObject(c),
|
|
3468
|
+
id: c.req.param("id")
|
|
3469
|
+
}, paths);
|
|
3470
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
3471
|
+
});
|
|
3472
|
+
routes.post("/watches/:id", async (c) => {
|
|
3473
|
+
const result = await removePrWatch({
|
|
3474
|
+
...await safeJsonObject(c),
|
|
3475
|
+
id: c.req.param("id")
|
|
3476
|
+
}, paths);
|
|
3477
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
3478
|
+
});
|
|
3479
|
+
return routes;
|
|
3480
|
+
}
|
|
3481
|
+
//#endregion
|
|
3482
|
+
//#region src/server/routes/workflows.ts
|
|
3483
|
+
function createWorkflowRoutes(paths) {
|
|
3484
|
+
const routes = new Hono();
|
|
3485
|
+
routes.get("/summaries", async (c) => {
|
|
3486
|
+
return c.json({
|
|
3487
|
+
items: await listWorkflowSummaries(paths),
|
|
3488
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3489
|
+
});
|
|
3490
|
+
});
|
|
3491
|
+
routes.get("/observability", async (c) => {
|
|
3492
|
+
return c.json(await readWorkflowObservability(paths));
|
|
3493
|
+
});
|
|
3494
|
+
return routes;
|
|
3495
|
+
}
|
|
3496
|
+
//#endregion
|
|
3497
|
+
//#region src/server/routes/worktrees.ts
|
|
3498
|
+
function createWorktreeRoutes(paths) {
|
|
3499
|
+
const routes = new Hono();
|
|
3500
|
+
routes.get("/worktrees", async (c) => {
|
|
3501
|
+
return c.json(await listWorktrees(paths));
|
|
3502
|
+
});
|
|
3503
|
+
routes.post("/worktrees", async (c) => {
|
|
3504
|
+
const result = await createWorktree(await safeJsonBody(c), paths);
|
|
3505
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
3506
|
+
});
|
|
3507
|
+
routes.get("/worktrees/:id/status", async (c) => {
|
|
3508
|
+
const result = await readWorktreeStatus({ worktreeId: c.req.param("id") }, paths);
|
|
3509
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
3510
|
+
});
|
|
3511
|
+
routes.post("/worktrees/:id/sync", async (c) => {
|
|
3512
|
+
const result = await syncWorktree({
|
|
3513
|
+
...await safeJsonObject(c),
|
|
3514
|
+
worktreeId: c.req.param("id")
|
|
3515
|
+
}, paths);
|
|
3516
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
3517
|
+
});
|
|
3518
|
+
routes.post("/worktrees/:id/lock", async (c) => {
|
|
3519
|
+
const result = await lockWorktree({
|
|
3520
|
+
...await safeJsonObject(c),
|
|
3521
|
+
worktreeId: c.req.param("id")
|
|
3522
|
+
}, paths);
|
|
3523
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
3524
|
+
});
|
|
3525
|
+
routes.post("/worktree-locks/:id/release", async (c) => {
|
|
3526
|
+
const result = await releaseWorktreeLock({
|
|
3527
|
+
...await safeJsonObject(c),
|
|
3528
|
+
lockId: c.req.param("id")
|
|
3529
|
+
}, paths);
|
|
3530
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
3531
|
+
});
|
|
3532
|
+
routes.post("/worktrees/cleanup", async (c) => {
|
|
3533
|
+
const result = await cleanupWorktrees(await safeJsonBody(c), paths);
|
|
3534
|
+
return c.json(result, result.ok ? 200 : 400);
|
|
3535
|
+
});
|
|
3536
|
+
return routes;
|
|
3537
|
+
}
|
|
3538
|
+
//#endregion
|
|
3539
|
+
//#region src/server/create-app.ts
|
|
3540
|
+
async function createApp(options = {}) {
|
|
3541
|
+
const paths = options.paths ?? runtimePaths();
|
|
3542
|
+
process.env.NEONDECK_HOME = paths.home;
|
|
3543
|
+
ensureRuntimeHomeSync(paths);
|
|
3544
|
+
loadNeondeckEnv(paths);
|
|
3545
|
+
const providerConfig = readProviderConfigSync(paths);
|
|
3546
|
+
for (const provider of providerRuntimeRegistrations(process.env, providerConfig)) registerProvider(provider.id, provider.registration);
|
|
3547
|
+
const app = new Hono();
|
|
3548
|
+
const staticRoot = options.staticRoot ?? resolveStaticRoot();
|
|
3549
|
+
await ensureRuntimeHome(paths);
|
|
3550
|
+
installFlueExecutionContextTracker();
|
|
3551
|
+
installFlueObservationHandlers(paths);
|
|
3552
|
+
if (options.scheduler !== false && process.env.NEONDECK_DISABLE_SCHEDULER !== "1") startSchedulerObservedLoop(paths);
|
|
3553
|
+
await getMcpRegistry(paths).start();
|
|
3554
|
+
app.use("/api/*", requireLocalApiAccess);
|
|
3555
|
+
app.use("/reports/*", requireLocalApiAccess);
|
|
3556
|
+
app.route("/api", createRuntimeRoutes(paths));
|
|
3557
|
+
app.route("/api/events", createConfigEventRoutes());
|
|
3558
|
+
app.route("/api/events", createNotificationEventRoutes());
|
|
3559
|
+
app.route("/api/events", createSessionEventRoutes());
|
|
3560
|
+
app.route("/api/safety", createSafetyRoutes(paths));
|
|
3561
|
+
app.route("/api/execution", createExecutionRoutes(paths));
|
|
3562
|
+
app.route("/api", createSessionRoutes(paths));
|
|
3563
|
+
app.route("/api", createConfigRoutes(paths));
|
|
3564
|
+
app.route("/api/metrics", createMetricsRoutes());
|
|
3565
|
+
app.route("/api/mcp", createMcpRoutes(paths));
|
|
3566
|
+
app.route("/api/repos", createReposRoutes(paths));
|
|
3567
|
+
app.route("/api", createRepoEditRoutes(paths));
|
|
3568
|
+
app.route("/api", createWorktreeRoutes(paths));
|
|
3569
|
+
app.route("/api/kilo", createKiloRoutes(paths));
|
|
3570
|
+
app.route("/api", createAutopilotRoutes(paths));
|
|
3571
|
+
app.route("/api", createHandoffRoutes(paths));
|
|
3572
|
+
app.route("/api", createWatchRoutes(paths));
|
|
3573
|
+
app.route("/api", createSchedulerRoutes(paths));
|
|
3574
|
+
app.route("/api", createScheduledTaskRoutes(paths));
|
|
3575
|
+
app.route("/api/notifications", createNotificationRoutes(paths));
|
|
3576
|
+
app.route("/api", createMemoryRoutes(paths));
|
|
3577
|
+
app.route("/api/learning", createLearningRoutes(paths));
|
|
3578
|
+
app.route("/api/skills", createSkillRoutes(paths));
|
|
3579
|
+
app.route("/api/commands", createCommandRoutes());
|
|
3580
|
+
app.route("/api", createReportApiRoutes(paths));
|
|
3581
|
+
app.route("/api/workflows", createWorkflowRoutes(paths));
|
|
3582
|
+
app.route("/api/github", createGitHubRoutes(paths));
|
|
3583
|
+
app.use("/api/flue/agents/display-assistant/*", displayAssistantLearningMiddleware(paths));
|
|
3584
|
+
const requireRunInspection = requireFlueRunInspectionToken(paths);
|
|
3585
|
+
app.use("/api/flue/runs", requireRunInspection);
|
|
3586
|
+
app.use("/api/flue/runs/*", requireRunInspection);
|
|
3587
|
+
app.route("/api/flue", flue());
|
|
3588
|
+
app.route("/reports", createReportFileRoutes(paths));
|
|
3589
|
+
app.use("/assets/*", (0, import_serve_static.serveStatic)({ root: staticRoot }));
|
|
3590
|
+
app.get("/manifest.webmanifest", (0, import_serve_static.serveStatic)({
|
|
3591
|
+
root: staticRoot,
|
|
3592
|
+
path: "manifest.webmanifest"
|
|
3593
|
+
}));
|
|
3594
|
+
app.use("/icons/*", (0, import_serve_static.serveStatic)({ root: staticRoot }));
|
|
3595
|
+
app.get("/favicon.svg", (0, import_serve_static.serveStatic)({
|
|
3596
|
+
root: staticRoot,
|
|
3597
|
+
path: "favicon.svg"
|
|
3598
|
+
}));
|
|
3599
|
+
app.get("*", (0, import_serve_static.serveStatic)({
|
|
3600
|
+
root: staticRoot,
|
|
3601
|
+
path: "index.html"
|
|
3602
|
+
}));
|
|
3603
|
+
return app;
|
|
3604
|
+
}
|
|
3605
|
+
function resolveStaticRoot(env = process.env) {
|
|
3606
|
+
const candidates = [
|
|
3607
|
+
env.NEONDECK_STATIC_ROOT,
|
|
3608
|
+
fileURLToPath(new URL("../../web/dist", import.meta.url)),
|
|
3609
|
+
fileURLToPath(new URL("../web/dist", import.meta.url)),
|
|
3610
|
+
"./web/dist"
|
|
3611
|
+
].filter((value) => Boolean(value));
|
|
3612
|
+
return candidates.find((candidate) => existsSync(candidate)) ?? candidates[0];
|
|
3613
|
+
}
|
|
3614
|
+
//#endregion
|
|
3615
|
+
//#region src/server/app.ts
|
|
3616
|
+
var app_default = await createApp();
|
|
3617
|
+
//#endregion
|
|
3618
|
+
export { app_default as default };
|
|
3619
|
+
|
|
3620
|
+
//# sourceMappingURL=app-BuuvLo98.js.map
|