@opengsd/gsd-pi 1.5.0 → 1.6.0-dev.aa7f94e8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -10
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/ask-user-questions.js +1 -1
- package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +114 -36
- package/dist/resources/extensions/gsd/activity-log.js +1 -1
- package/dist/resources/extensions/gsd/auto/closeout.js +97 -5
- package/dist/resources/extensions/gsd/auto/orchestrator.js +20 -13
- package/dist/resources/extensions/gsd/auto/pre-dispatch.js +6 -3
- package/dist/resources/extensions/gsd/auto/unit-phase.js +5 -9
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +14 -5
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +11 -7
- package/dist/resources/extensions/gsd/auto-dispatch.js +69 -17
- package/dist/resources/extensions/gsd/auto-model-selection.js +28 -1
- package/dist/resources/extensions/gsd/auto-post-unit.js +23 -6
- package/dist/resources/extensions/gsd/auto-prompts.js +49 -117
- package/dist/resources/extensions/gsd/auto-recovery.js +50 -6
- package/dist/resources/extensions/gsd/auto-start.js +57 -33
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +12 -2
- package/dist/resources/extensions/gsd/auto-timers.js +9 -2
- package/dist/resources/extensions/gsd/auto-worktree-branch-lifecycle.js +189 -0
- package/dist/resources/extensions/gsd/auto-worktree-cleanup.js +133 -0
- package/dist/resources/extensions/gsd/auto-worktree-conflict-auto-resolve.js +35 -0
- package/dist/resources/extensions/gsd/auto-worktree-creation.js +130 -0
- package/dist/resources/extensions/gsd/auto-worktree-entry.js +118 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-already-merged.js +98 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-branch-head.js +52 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-cleanup.js +87 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-code-changes.js +46 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-db-ready.js +68 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-integration-branch.js +51 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-message.js +106 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-pre-teardown.js +64 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-stash.js +168 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge.js +302 -0
- package/dist/resources/extensions/gsd/auto-worktree-milestone-shelter.js +119 -0
- package/dist/resources/extensions/gsd/auto-worktree-path-resolution.js +5 -0
- package/dist/resources/extensions/gsd/auto-worktree-path-utils.js +5 -0
- package/dist/resources/extensions/gsd/auto-worktree-resource-version.js +41 -0
- package/dist/resources/extensions/gsd/auto-worktree-runtime-cleanup.js +99 -0
- package/dist/resources/extensions/gsd/auto-worktree-session-registry.js +57 -0
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +249 -0
- package/dist/resources/extensions/gsd/auto-worktree-teardown.js +120 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +20 -2073
- package/dist/resources/extensions/gsd/auto.js +118 -34
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +20 -2
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +185 -27
- package/dist/resources/extensions/gsd/browser-daemon-auto-prep.js +68 -2
- package/dist/resources/extensions/gsd/codebase-generator.js +7 -1
- package/dist/resources/extensions/gsd/commands-handlers.js +1 -1
- package/dist/resources/extensions/gsd/commands-workflow-templates.js +3 -3
- package/dist/resources/extensions/gsd/compat/compat-marker.js +44 -0
- package/dist/resources/extensions/gsd/config-overlay.js +7 -2
- package/dist/resources/extensions/gsd/db/engine.js +33 -0
- package/dist/resources/extensions/gsd/db/queries.js +9 -9
- package/dist/resources/extensions/gsd/db/writers/cascades.js +20 -2
- package/dist/resources/extensions/gsd/db-adapter.js +30 -3
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +3 -3
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +93 -11
- package/dist/resources/extensions/gsd/flat-phase-migration.js +32 -3
- package/dist/resources/extensions/gsd/forensics.js +1 -1
- package/dist/resources/extensions/gsd/git-conflict-resolve.js +2 -2
- package/dist/resources/extensions/gsd/git-service.js +45 -0
- package/dist/resources/extensions/gsd/gsd-db.js +23 -11
- package/dist/resources/extensions/gsd/guided-flow.js +22 -17
- package/dist/resources/extensions/gsd/journal.js +1 -1
- package/dist/resources/extensions/gsd/markdown-renderer.js +34 -79
- package/dist/resources/extensions/gsd/md-importer.js +89 -23
- package/dist/resources/extensions/gsd/metrics.js +2 -2
- package/dist/resources/extensions/gsd/migration-auto-check.js +30 -3
- package/dist/resources/extensions/gsd/milestone-merge-transaction.js +11 -0
- package/dist/resources/extensions/gsd/milestone-settlement.js +1 -1
- package/dist/resources/extensions/gsd/parallel-merge.js +1 -1
- package/dist/resources/extensions/gsd/parallel-orchestrator.js +3 -1
- package/dist/resources/extensions/gsd/paths.js +87 -52
- package/dist/resources/extensions/gsd/post-unit-hooks.js +27 -0
- package/dist/resources/extensions/gsd/preferences-models.js +66 -15
- package/dist/resources/extensions/gsd/preferences-skills.js +10 -16
- package/dist/resources/extensions/gsd/preferences-types.js +1 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +148 -10
- package/dist/resources/extensions/gsd/preferences.js +20 -0
- package/dist/resources/extensions/gsd/prompts/system.md +6 -6
- package/dist/resources/extensions/gsd/quick.js +12 -3
- package/dist/resources/extensions/gsd/reopen-reason.js +98 -0
- package/dist/resources/extensions/gsd/rule-registry.js +106 -10
- package/dist/resources/extensions/gsd/session-status-io.js +31 -0
- package/dist/resources/extensions/gsd/skill-discovery.js +36 -23
- package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +2 -1
- package/dist/resources/extensions/gsd/state/derive/from-db.js +88 -153
- package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +1 -10
- package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +3 -24
- package/dist/resources/extensions/gsd/state-reconciliation/index.js +10 -0
- package/dist/resources/extensions/gsd/tool-contract.js +14 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -5
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +16 -10
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +5 -0
- package/dist/resources/extensions/gsd/tools/reopen-task.js +28 -5
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +3 -8
- package/dist/resources/extensions/gsd/uat-dispatch.js +94 -0
- package/dist/resources/extensions/gsd/unit-registry.js +12 -2
- package/dist/resources/extensions/gsd/unmerged-milestone-guard.js +1 -1
- package/dist/resources/extensions/gsd/uok/audit-toggle.js +31 -2
- package/dist/resources/extensions/gsd/uok/audit.js +3 -0
- package/dist/resources/extensions/gsd/uok/gate-runner.js +22 -30
- package/dist/resources/extensions/gsd/uok/gitops.js +18 -15
- package/dist/resources/extensions/gsd/uok/kernel.js +120 -46
- package/dist/resources/extensions/gsd/uok/model-policy.js +17 -14
- package/dist/resources/extensions/gsd/validation-block-guard.js +2 -1
- package/dist/resources/extensions/gsd/workflow-event-ledger.js +1 -1
- package/dist/resources/extensions/gsd/workflow-logger.js +1 -1
- package/dist/resources/extensions/gsd/workflow-projections.js +9 -26
- package/dist/resources/extensions/gsd/workspace-git-preflight.js +2 -2
- package/dist/resources/extensions/gsd/workspace.js +5 -14
- package/dist/resources/extensions/gsd/worktree-command.js +1 -1
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +14 -9
- package/dist/resources/extensions/gsd/worktree-reentry.js +2 -1
- package/dist/resources/extensions/shared/gsd-browser-cli.js +51 -5
- package/dist/resources/extensions/shared/interview-ui.js +62 -9
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +6 -6
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/required-server-files.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/mcp-connections/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/page.js +2 -2
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +6 -6
- package/dist/web/standalone/.next/server/chunks/{1128.js → 1991.js} +1 -1
- package/dist/web/standalone/.next/server/chunks/2842.js +4 -4
- package/dist/web/standalone/.next/server/chunks/8357.js +3 -3
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware.js +3 -3
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/app/_not-found/page-a6fb1847f67f167c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/{layout-b35cbfff38aaf4cf.js → layout-4ae2d68984392bbf.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/page-72a856634ad14c10.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{main-app-590a74400e35f685.js → main-app-90d1d8d5e5d2dc6b.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-1115d46ac61b9823.js +1 -0
- package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/web/standalone/server.js +1 -1
- package/dist/worktree-cli-create.d.ts +10 -0
- package/dist/worktree-cli-create.js +11 -0
- package/dist/worktree-cli-format.d.ts +12 -0
- package/dist/worktree-cli-format.js +38 -0
- package/dist/worktree-cli-plan.d.ts +16 -0
- package/dist/worktree-cli-plan.js +19 -0
- package/dist/worktree-cli-session.d.ts +13 -0
- package/dist/worktree-cli-session.js +18 -0
- package/dist/worktree-cli-status.d.ts +35 -0
- package/dist/worktree-cli-status.js +54 -0
- package/dist/worktree-cli.d.ts +1 -16
- package/dist/worktree-cli.js +28 -122
- package/integrations/hermes/open_gsd_hermes/gsd_client.py +25 -36
- package/integrations/hermes/pyproject.toml +1 -1
- package/integrations/hermes/tests/test_gsd_client_timeout.py +4 -4
- package/package.json +6 -6
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/dist/cloud-cli.d.ts +1 -1
- package/packages/daemon/dist/cloud-cli.d.ts.map +1 -1
- package/packages/daemon/dist/cloud-cli.js +42 -2
- package/packages/daemon/dist/cloud-cli.js.map +1 -1
- package/packages/daemon/dist/cloud-config.d.ts +1 -0
- package/packages/daemon/dist/cloud-config.d.ts.map +1 -1
- package/packages/daemon/dist/cloud-config.js +1 -1
- package/packages/daemon/dist/cloud-config.js.map +1 -1
- package/packages/daemon/dist/device-flow.d.ts +24 -0
- package/packages/daemon/dist/device-flow.d.ts.map +1 -0
- package/packages/daemon/dist/device-flow.js +128 -0
- package/packages/daemon/dist/device-flow.js.map +1 -0
- package/packages/daemon/dist/device-flow.test.d.ts +2 -0
- package/packages/daemon/dist/device-flow.test.d.ts.map +1 -0
- package/packages/daemon/dist/device-flow.test.js +172 -0
- package/packages/daemon/dist/device-flow.test.js.map +1 -0
- package/packages/daemon/dist/orchestrator-agent.d.ts +21 -0
- package/packages/daemon/dist/orchestrator-agent.d.ts.map +1 -0
- package/packages/daemon/dist/orchestrator-agent.js +113 -0
- package/packages/daemon/dist/orchestrator-agent.js.map +1 -0
- package/packages/daemon/dist/orchestrator-tools.d.ts +12 -0
- package/packages/daemon/dist/orchestrator-tools.d.ts.map +1 -0
- package/packages/daemon/dist/orchestrator-tools.js +137 -0
- package/packages/daemon/dist/orchestrator-tools.js.map +1 -0
- package/packages/daemon/dist/orchestrator.d.ts +4 -56
- package/packages/daemon/dist/orchestrator.d.ts.map +1 -1
- package/packages/daemon/dist/orchestrator.js +13 -303
- package/packages/daemon/dist/orchestrator.js.map +1 -1
- package/packages/daemon/dist/orchestrator.test.js +11 -1
- package/packages/daemon/dist/orchestrator.test.js.map +1 -1
- package/packages/daemon/package.json +5 -5
- package/packages/gsd-agent-core/dist/export-html/template.js +53 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +3 -2
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/gsd-cloud/README.md +34 -0
- package/packages/gsd-cloud/bin/gsd-cloud.js +27 -0
- package/packages/gsd-cloud/package.json +38 -0
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +14 -9
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/index.d.ts +1 -0
- package/packages/pi-ai/dist/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/index.js +1 -0
- package/packages/pi-ai/dist/index.js.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.js +7 -3
- package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
- package/packages/pi-ai/dist/tool-result-content.d.ts +3 -0
- package/packages/pi-ai/dist/tool-result-content.d.ts.map +1 -0
- package/packages/pi-ai/dist/tool-result-content.js +62 -0
- package/packages/pi-ai/dist/tool-result-content.js.map +1 -0
- package/packages/pi-ai/dist/types.d.ts +8 -20
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts +9 -0
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.js +74 -53
- package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +6 -0
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.js +30 -2
- package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/skill-directories.d.ts +66 -0
- package/packages/pi-coding-agent/dist/core/skill-directories.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/skill-directories.js +138 -0
- package/packages/pi-coding-agent/dist/core/skill-directories.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/skills.d.ts +4 -1
- package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.js +4 -1
- package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +1 -0
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -0
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/dist/core/export-html/template.js +53 -4
- package/pkg/package.json +1 -1
- package/src/resources/extensions/ask-user-questions.ts +1 -1
- package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +146 -33
- package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +44 -0
- package/src/resources/extensions/browser-tools/tests/managed-gsd-browser-tools.test.mjs +93 -0
- package/src/resources/extensions/gsd/activity-log.ts +1 -1
- package/src/resources/extensions/gsd/auto/closeout.ts +114 -7
- package/src/resources/extensions/gsd/auto/orchestrator.ts +21 -14
- package/src/resources/extensions/gsd/auto/pre-dispatch.ts +6 -3
- package/src/resources/extensions/gsd/auto/unit-phase.ts +5 -9
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +14 -4
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +13 -7
- package/src/resources/extensions/gsd/auto-dispatch.ts +77 -16
- package/src/resources/extensions/gsd/auto-model-selection.ts +33 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +26 -5
- package/src/resources/extensions/gsd/auto-prompts.ts +73 -113
- package/src/resources/extensions/gsd/auto-recovery.ts +50 -6
- package/src/resources/extensions/gsd/auto-start.ts +62 -31
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +12 -2
- package/src/resources/extensions/gsd/auto-timers.ts +9 -2
- package/src/resources/extensions/gsd/auto-worktree-branch-lifecycle.ts +237 -0
- package/src/resources/extensions/gsd/auto-worktree-cleanup.ts +166 -0
- package/src/resources/extensions/gsd/auto-worktree-conflict-auto-resolve.ts +37 -0
- package/src/resources/extensions/gsd/auto-worktree-creation.ts +171 -0
- package/src/resources/extensions/gsd/auto-worktree-entry.ts +154 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-already-merged.ts +161 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-branch-head.ts +83 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-cleanup.ts +132 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-code-changes.ts +71 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-db-ready.ts +112 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-integration-branch.ts +99 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-message.ts +166 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-pre-teardown.ts +93 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-stash.ts +213 -0
- package/src/resources/extensions/gsd/auto-worktree-merge.ts +391 -0
- package/src/resources/extensions/gsd/auto-worktree-milestone-shelter.ts +159 -0
- package/src/resources/extensions/gsd/auto-worktree-path-resolution.ts +6 -0
- package/src/resources/extensions/gsd/auto-worktree-path-utils.ts +10 -0
- package/src/resources/extensions/gsd/auto-worktree-resource-version.ts +44 -0
- package/src/resources/extensions/gsd/auto-worktree-runtime-cleanup.ts +119 -0
- package/src/resources/extensions/gsd/auto-worktree-session-registry.ts +71 -0
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +370 -0
- package/src/resources/extensions/gsd/auto-worktree-teardown.ts +161 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +69 -2429
- package/src/resources/extensions/gsd/auto.ts +126 -47
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +20 -2
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +218 -27
- package/src/resources/extensions/gsd/browser-daemon-auto-prep.ts +77 -1
- package/src/resources/extensions/gsd/codebase-generator.ts +7 -1
- package/src/resources/extensions/gsd/commands-handlers.ts +1 -1
- package/src/resources/extensions/gsd/commands-workflow-templates.ts +3 -3
- package/src/resources/extensions/gsd/compat/compat-marker.ts +47 -0
- package/src/resources/extensions/gsd/config-overlay.ts +7 -1
- package/src/resources/extensions/gsd/db/engine.ts +36 -0
- package/src/resources/extensions/gsd/db/queries.ts +9 -9
- package/src/resources/extensions/gsd/db/writers/cascades.ts +23 -1
- package/src/resources/extensions/gsd/db-adapter.ts +29 -3
- package/src/resources/extensions/gsd/docs/preferences-reference.md +3 -3
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +109 -10
- package/src/resources/extensions/gsd/flat-phase-migration.ts +34 -3
- package/src/resources/extensions/gsd/forensics.ts +1 -1
- package/src/resources/extensions/gsd/git-conflict-resolve.ts +2 -2
- package/src/resources/extensions/gsd/git-service.ts +53 -0
- package/src/resources/extensions/gsd/gsd-db.ts +33 -11
- package/src/resources/extensions/gsd/guided-flow.ts +26 -18
- package/src/resources/extensions/gsd/journal.ts +1 -1
- package/src/resources/extensions/gsd/markdown-renderer.ts +52 -91
- package/src/resources/extensions/gsd/md-importer.ts +93 -22
- package/src/resources/extensions/gsd/metrics.ts +2 -2
- package/src/resources/extensions/gsd/migration-auto-check.ts +32 -3
- package/src/resources/extensions/gsd/milestone-merge-transaction.ts +15 -0
- package/src/resources/extensions/gsd/milestone-settlement.ts +1 -1
- package/src/resources/extensions/gsd/parallel-merge.ts +1 -1
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +3 -1
- package/src/resources/extensions/gsd/paths.ts +108 -51
- package/src/resources/extensions/gsd/post-unit-hooks.ts +32 -0
- package/src/resources/extensions/gsd/preferences-models.ts +74 -20
- package/src/resources/extensions/gsd/preferences-skills.ts +10 -16
- package/src/resources/extensions/gsd/preferences-types.ts +54 -1
- package/src/resources/extensions/gsd/preferences-validation.ts +146 -9
- package/src/resources/extensions/gsd/preferences.ts +20 -0
- package/src/resources/extensions/gsd/prompts/system.md +6 -6
- package/src/resources/extensions/gsd/quick.ts +11 -3
- package/src/resources/extensions/gsd/reopen-reason.ts +109 -0
- package/src/resources/extensions/gsd/rule-registry.ts +111 -9
- package/src/resources/extensions/gsd/session-status-io.ts +34 -0
- package/src/resources/extensions/gsd/skill-discovery.ts +47 -23
- package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +3 -1
- package/src/resources/extensions/gsd/state/derive/from-db.ts +201 -157
- package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +1 -9
- package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +2 -22
- package/src/resources/extensions/gsd/state-reconciliation/index.ts +14 -0
- package/src/resources/extensions/gsd/tests/auto-artifact-paths.test.ts +169 -1
- package/src/resources/extensions/gsd/tests/auto-loop-no-copy-artifacts.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +41 -2
- package/src/resources/extensions/gsd/tests/auto-post-unit-artifact-diagnostic.test.ts +48 -1
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +57 -0
- package/src/resources/extensions/gsd/tests/auto-start-clean-runtime-db-gated.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-start-project-milestone-reconcile.test.ts +29 -1
- package/src/resources/extensions/gsd/tests/auto-timeout-recovery-supervisor-inflight-guard.test.ts +159 -0
- package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/auto-worktree-auto-resolve.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-already-merged.test.ts +177 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-branch-head.test.ts +118 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-cleanup.test.ts +115 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-code-changes.test.ts +76 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-db-ready.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-integration-branch.test.ts +123 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-message.test.ts +114 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-pre-teardown.test.ts +84 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-stash.test.ts +70 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-shelter.test.ts +52 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +6 -6
- package/src/resources/extensions/gsd/tests/auto-worktree-resource-version.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-untracked-content.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/browser-daemon-auto-prep.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/compat-marker.test.ts +85 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +45 -0
- package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/db-adapter.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/db-writable-probe.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +174 -0
- package/src/resources/extensions/gsd/tests/fast-forward-reused-milestone-branch.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +71 -1
- package/src/resources/extensions/gsd/tests/flat-phase-renderer.test.ts +46 -1
- package/src/resources/extensions/gsd/tests/flat-phase-validation-path.test.ts +72 -32
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/import-completion-lost-update.test.ts +196 -0
- package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts +4 -6
- package/src/resources/extensions/gsd/tests/integration/auto-stash-merge.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +7 -8
- package/src/resources/extensions/gsd/tests/integration/feature-branch-lifecycle-integration.test.ts +2 -5
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +76 -0
- package/src/resources/extensions/gsd/tests/integration/merge-cwd-restore.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/integration/merge-preserve-worktree-on-preteardown-error.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/integration/merge-strategy-regular.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/integration/milestone-transition-worktree.test.ts +5 -7
- package/src/resources/extensions/gsd/tests/integration/paths.test.ts +88 -1
- package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +2 -4
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +16 -15
- package/src/resources/extensions/gsd/tests/merge-closeout-consistency-gate.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/merge-self-branch-guard.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +91 -0
- package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +439 -0
- package/src/resources/extensions/gsd/tests/milestone-merge-transaction.test.ts +24 -0
- package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/parallel-worker-lock-contention.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +269 -0
- package/src/resources/extensions/gsd/tests/post-unit-retry-on-orchestrator-bridge.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +1 -4
- package/src/resources/extensions/gsd/tests/preferences.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +5 -0
- package/src/resources/extensions/gsd/tests/read-uat-gate-verdict.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/reassess-handler.test.ts +49 -1
- package/src/resources/extensions/gsd/tests/reopen-reason.test.ts +117 -0
- package/src/resources/extensions/gsd/tests/reopen-slice.test.ts +52 -0
- package/src/resources/extensions/gsd/tests/rule-registry.test.ts +95 -1
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/sibling-model-fallbacks.test.ts +189 -0
- package/src/resources/extensions/gsd/tests/single-field-model-thinking.test.ts +156 -0
- package/src/resources/extensions/gsd/tests/skill-discovery.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +3 -5
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/steer-worktree-path.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/supervisor-model-swap-inflight-guard.test.ts +181 -0
- package/src/resources/extensions/gsd/tests/supervisor-model-swap-thinking-clamp.test.ts +122 -0
- package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/system-flat-phase-layout.test.ts +33 -0
- package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +196 -0
- package/src/resources/extensions/gsd/tests/uat-dispatch.test.ts +211 -0
- package/src/resources/extensions/gsd/tests/uat-stuck-loop-orphaned-worktree.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/unit-registry.test.ts +26 -1
- package/src/resources/extensions/gsd/tests/uok-gate-runner.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/uok-kernel-path.test.ts +226 -2
- package/src/resources/extensions/gsd/tests/uok-model-policy.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/worker-resume-wait.test.ts +73 -0
- package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +213 -2
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-db-respawn-truncation.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-db-same-file.test.ts +1 -3
- package/src/resources/extensions/gsd/tests/worktree-expected-warnings.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-git-pathspec.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-main-branch.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-preferences-sync.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +5 -2
- package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-sync-tasks.test.ts +1 -1
- package/src/resources/extensions/gsd/tool-contract.ts +29 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +9 -5
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +19 -8
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +5 -0
- package/src/resources/extensions/gsd/tools/reopen-task.ts +28 -5
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +13 -9
- package/src/resources/extensions/gsd/types.ts +23 -4
- package/src/resources/extensions/gsd/uat-dispatch.ts +176 -0
- package/src/resources/extensions/gsd/unit-registry.ts +16 -2
- package/src/resources/extensions/gsd/unmerged-milestone-guard.ts +1 -1
- package/src/resources/extensions/gsd/uok/audit-toggle.ts +32 -2
- package/src/resources/extensions/gsd/uok/audit.ts +2 -0
- package/src/resources/extensions/gsd/uok/gate-runner.ts +26 -36
- package/src/resources/extensions/gsd/uok/gitops.ts +21 -18
- package/src/resources/extensions/gsd/uok/kernel.ts +169 -48
- package/src/resources/extensions/gsd/uok/model-policy.ts +20 -17
- package/src/resources/extensions/gsd/validation-block-guard.ts +2 -1
- package/src/resources/extensions/gsd/workflow-event-ledger.ts +1 -1
- package/src/resources/extensions/gsd/workflow-logger.ts +1 -1
- package/src/resources/extensions/gsd/workflow-projections.ts +9 -24
- package/src/resources/extensions/gsd/workspace-git-preflight.ts +2 -2
- package/src/resources/extensions/gsd/workspace.ts +5 -21
- package/src/resources/extensions/gsd/worktree-command.ts +1 -1
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +19 -15
- package/src/resources/extensions/gsd/worktree-reentry.ts +2 -1
- package/src/resources/extensions/shared/gsd-browser-cli.ts +64 -7
- package/src/resources/extensions/shared/interview-ui.ts +58 -11
- package/src/resources/extensions/shared/tests/interview-ui-border.test.ts +32 -0
- package/dist/web/standalone/.next/static/chunks/app/_not-found/page-49f565245e1e4afe.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/page-bc4c723c6a19c6f0.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ec3eaffc9785ba48.js +0 -1
- /package/dist/web/standalone/.next/static/{bCg3aYrFN1DKZr2dqgE7L → XFuMvz26Zb_lrrR-8dvkd}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{bCg3aYrFN1DKZr2dqgE7L → XFuMvz26Zb_lrrR-8dvkd}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface DeviceFlowResult {
|
|
2
|
+
deviceToken: string;
|
|
3
|
+
runtimeId: string;
|
|
4
|
+
/**
|
|
5
|
+
* Resolved relay/gateway URL: the server-returned `gateway_url` from the approved
|
|
6
|
+
* token-poll response when present AND valid, else the `gatewayUrl` the caller passed
|
|
7
|
+
* (D-02 backward-compat — existing single-host `--gateway` users are unaffected).
|
|
8
|
+
*/
|
|
9
|
+
gatewayUrl: string;
|
|
10
|
+
}
|
|
11
|
+
export interface DeviceFlowParams {
|
|
12
|
+
gatewayUrl: string;
|
|
13
|
+
configPath: string;
|
|
14
|
+
runtimeName?: string;
|
|
15
|
+
binaryName: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Run the full RFC 8628 device authorization flow.
|
|
19
|
+
* Requests a device code, displays the verification URL and user code in the terminal,
|
|
20
|
+
* polls for approval with a spinner, and returns the device token on success.
|
|
21
|
+
* Calls process.exit(1) on denial (D-12) or timeout (D-11).
|
|
22
|
+
*/
|
|
23
|
+
export declare function runDeviceFlow(params: DeviceFlowParams): Promise<DeviceFlowResult>;
|
|
24
|
+
//# sourceMappingURL=device-flow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-flow.d.ts","sourceRoot":"","sources":["../src/device-flow.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB;AAKD;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA+HvF"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// Device flow handler: code request, terminal display, polling loop, config save.
|
|
2
|
+
// Implements RFC 8628 device authorization grant for the daemon's `gsd cloud login` command.
|
|
3
|
+
import { hostname as osHostname } from "node:os";
|
|
4
|
+
import { cursorTo, clearLine } from "node:readline";
|
|
5
|
+
import { parseCloudGatewayUrl, postJsonToValidatedGateway, validateGatewayNetworkTarget } from "./cloud-config.js";
|
|
6
|
+
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
7
|
+
const DEFAULT_POLL_INTERVAL_MS = 5_000;
|
|
8
|
+
const SLOW_DOWN_INCREMENT_MS = 5_000;
|
|
9
|
+
/** Simple sleep helper. */
|
|
10
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
11
|
+
/**
|
|
12
|
+
* Run the full RFC 8628 device authorization flow.
|
|
13
|
+
* Requests a device code, displays the verification URL and user code in the terminal,
|
|
14
|
+
* polls for approval with a spinner, and returns the device token on success.
|
|
15
|
+
* Calls process.exit(1) on denial (D-12) or timeout (D-11).
|
|
16
|
+
*/
|
|
17
|
+
export async function runDeviceFlow(params) {
|
|
18
|
+
const base = parseCloudGatewayUrl(params.gatewayUrl);
|
|
19
|
+
// ---- Step 1: Request device code (D-01, DEVF-01) ----
|
|
20
|
+
const codeUrl = new URL(base.pathname.replace(/\/+$/, "") + "/api/device/code", base);
|
|
21
|
+
const codeResponse = await postJsonToValidatedGateway(codeUrl, {
|
|
22
|
+
hostname: osHostname(),
|
|
23
|
+
os: process.platform,
|
|
24
|
+
...(params.runtimeName !== undefined ? { runtimeName: params.runtimeName } : {}),
|
|
25
|
+
});
|
|
26
|
+
const userCode = typeof codeResponse["userCode"] === "string" ? codeResponse["userCode"] : "";
|
|
27
|
+
const deviceCode = typeof codeResponse["deviceCode"] === "string" ? codeResponse["deviceCode"] : "";
|
|
28
|
+
const verificationUriComplete = typeof codeResponse["verificationUriComplete"] === "string"
|
|
29
|
+
? codeResponse["verificationUriComplete"]
|
|
30
|
+
: "";
|
|
31
|
+
const expiresIn = typeof codeResponse["expiresIn"] === "number" ? codeResponse["expiresIn"] : 600;
|
|
32
|
+
if (!userCode || !deviceCode) {
|
|
33
|
+
throw new Error("Device code response missing userCode or deviceCode");
|
|
34
|
+
}
|
|
35
|
+
// ---- Step 2: Display to user (D-02) ----
|
|
36
|
+
process.stdout.write(`\n${params.binaryName}: Cloud Login\n`);
|
|
37
|
+
process.stdout.write(`\nTo authorize this machine, open the following URL in your browser:\n`);
|
|
38
|
+
process.stdout.write(`\n ${verificationUriComplete}\n`);
|
|
39
|
+
process.stdout.write(`\nOr visit the gateway and enter the code manually:\n`);
|
|
40
|
+
process.stdout.write(`\n ${base.origin}\n`);
|
|
41
|
+
process.stdout.write(`\nYour code: ${userCode}\n\n`);
|
|
42
|
+
// ---- Step 3: Polling loop (D-10, D-11, D-12) ----
|
|
43
|
+
const tokenUrl = new URL(base.pathname.replace(/\/+$/, "") + "/api/device/token", base);
|
|
44
|
+
const expiresAt = Date.now() + expiresIn * 1_000;
|
|
45
|
+
let pollIntervalMs = DEFAULT_POLL_INTERVAL_MS;
|
|
46
|
+
let spinnerIndex = 0;
|
|
47
|
+
const spinnerInterval = setInterval(() => {
|
|
48
|
+
const frame = SPINNER_FRAMES[spinnerIndex % SPINNER_FRAMES.length] ?? "⠋";
|
|
49
|
+
spinnerIndex++;
|
|
50
|
+
const remaining = Math.max(0, Math.ceil((expiresAt - Date.now()) / 1_000));
|
|
51
|
+
cursorTo(process.stdout, 0);
|
|
52
|
+
clearLine(process.stdout, 0);
|
|
53
|
+
process.stdout.write(` ${frame} Waiting for approval... (expires in ${remaining}s)`);
|
|
54
|
+
}, 100);
|
|
55
|
+
try {
|
|
56
|
+
while (Date.now() < expiresAt) {
|
|
57
|
+
await sleep(pollIntervalMs);
|
|
58
|
+
if (Date.now() >= expiresAt)
|
|
59
|
+
break;
|
|
60
|
+
let tokenResponse;
|
|
61
|
+
try {
|
|
62
|
+
tokenResponse = await postJsonToValidatedGateway(tokenUrl, { deviceCode });
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// Network errors during polling are transient — keep trying until expiry.
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const status = typeof tokenResponse["status"] === "string" ? tokenResponse["status"] : "";
|
|
69
|
+
if (status === "approved") {
|
|
70
|
+
clearInterval(spinnerInterval);
|
|
71
|
+
cursorTo(process.stdout, 0);
|
|
72
|
+
clearLine(process.stdout, 0);
|
|
73
|
+
process.stdout.write(`${params.binaryName}: Authorization approved!\n`);
|
|
74
|
+
const deviceToken = typeof tokenResponse["token"] === "string" ? tokenResponse["token"] : "";
|
|
75
|
+
const runtimeId = typeof tokenResponse["runtimeId"] === "string" ? tokenResponse["runtimeId"] : "";
|
|
76
|
+
if (!deviceToken || !runtimeId) {
|
|
77
|
+
throw new Error("Approval response missing token or runtimeId");
|
|
78
|
+
}
|
|
79
|
+
// Resolve the relay/gateway URL in exactly ONE place (D-02, D-03).
|
|
80
|
+
// Default to the caller-supplied gateway; if the server sent a valid
|
|
81
|
+
// `gateway_url`, use that instead. The server value is UNTRUSTED input —
|
|
82
|
+
// re-validate it through the same SSRF guards applied to `--gateway`.
|
|
83
|
+
let gatewayUrl = params.gatewayUrl;
|
|
84
|
+
const serverGatewayUrl = typeof tokenResponse["gateway_url"] === "string"
|
|
85
|
+
? tokenResponse["gateway_url"]
|
|
86
|
+
: undefined;
|
|
87
|
+
if (serverGatewayUrl) {
|
|
88
|
+
try {
|
|
89
|
+
const parsed = parseCloudGatewayUrl(serverGatewayUrl);
|
|
90
|
+
validateGatewayNetworkTarget(parsed);
|
|
91
|
+
gatewayUrl = parsed.toString();
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
// Fail loud-but-non-fatal: a bad server-supplied relay URL must not
|
|
95
|
+
// crash login. Warn and fall back to the configured `--gateway`.
|
|
96
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
97
|
+
process.stderr.write(`${params.binaryName}: ignoring invalid server-supplied relay URL (${reason}); using configured --gateway instead.\n`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return { deviceToken, runtimeId, gatewayUrl };
|
|
101
|
+
}
|
|
102
|
+
if (status === "denied") {
|
|
103
|
+
clearInterval(spinnerInterval);
|
|
104
|
+
cursorTo(process.stdout, 0);
|
|
105
|
+
clearLine(process.stdout, 0);
|
|
106
|
+
process.stdout.write(`${params.binaryName}: Device request denied by user.\n`);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
if (status === "expired") {
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
if (status === "slow_down") {
|
|
113
|
+
pollIntervalMs += SLOW_DOWN_INCREMENT_MS;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
// status === "pending" or unknown — keep polling
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
clearInterval(spinnerInterval);
|
|
121
|
+
}
|
|
122
|
+
// Timeout path (D-11)
|
|
123
|
+
cursorTo(process.stdout, 0);
|
|
124
|
+
clearLine(process.stdout, 0);
|
|
125
|
+
process.stdout.write(`${params.binaryName}: Authorization request timed out. Please run login again.\n`);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=device-flow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-flow.js","sourceRoot":"","sources":["../src/device-flow.ts"],"names":[],"mappings":"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","sourcesContent":["// Device flow handler: code request, terminal display, polling loop, config save.\n// Implements RFC 8628 device authorization grant for the daemon's `gsd cloud login` command.\nimport { hostname as osHostname } from \"node:os\";\nimport { cursorTo, clearLine } from \"node:readline\";\nimport { parseCloudGatewayUrl, postJsonToValidatedGateway, validateGatewayNetworkTarget } from \"./cloud-config.js\";\n\nconst SPINNER_FRAMES = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"];\nconst DEFAULT_POLL_INTERVAL_MS = 5_000;\nconst SLOW_DOWN_INCREMENT_MS = 5_000;\n\nexport interface DeviceFlowResult {\n deviceToken: string;\n runtimeId: string;\n /**\n * Resolved relay/gateway URL: the server-returned `gateway_url` from the approved\n * token-poll response when present AND valid, else the `gatewayUrl` the caller passed\n * (D-02 backward-compat — existing single-host `--gateway` users are unaffected).\n */\n gatewayUrl: string;\n}\n\nexport interface DeviceFlowParams {\n gatewayUrl: string;\n configPath: string;\n runtimeName?: string;\n binaryName: string;\n}\n\n/** Simple sleep helper. */\nconst sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms));\n\n/**\n * Run the full RFC 8628 device authorization flow.\n * Requests a device code, displays the verification URL and user code in the terminal,\n * polls for approval with a spinner, and returns the device token on success.\n * Calls process.exit(1) on denial (D-12) or timeout (D-11).\n */\nexport async function runDeviceFlow(params: DeviceFlowParams): Promise<DeviceFlowResult> {\n const base = parseCloudGatewayUrl(params.gatewayUrl);\n\n // ---- Step 1: Request device code (D-01, DEVF-01) ----\n const codeUrl = new URL(base.pathname.replace(/\\/+$/, \"\") + \"/api/device/code\", base);\n const codeResponse = await postJsonToValidatedGateway(codeUrl, {\n hostname: osHostname(),\n os: process.platform,\n ...(params.runtimeName !== undefined ? { runtimeName: params.runtimeName } : {}),\n });\n\n const userCode = typeof codeResponse[\"userCode\"] === \"string\" ? codeResponse[\"userCode\"] : \"\";\n const deviceCode = typeof codeResponse[\"deviceCode\"] === \"string\" ? codeResponse[\"deviceCode\"] : \"\";\n const verificationUriComplete = typeof codeResponse[\"verificationUriComplete\"] === \"string\"\n ? codeResponse[\"verificationUriComplete\"]\n : \"\";\n const expiresIn = typeof codeResponse[\"expiresIn\"] === \"number\" ? codeResponse[\"expiresIn\"] : 600;\n\n if (!userCode || !deviceCode) {\n throw new Error(\"Device code response missing userCode or deviceCode\");\n }\n\n // ---- Step 2: Display to user (D-02) ----\n process.stdout.write(`\\n${params.binaryName}: Cloud Login\\n`);\n process.stdout.write(`\\nTo authorize this machine, open the following URL in your browser:\\n`);\n process.stdout.write(`\\n ${verificationUriComplete}\\n`);\n process.stdout.write(`\\nOr visit the gateway and enter the code manually:\\n`);\n process.stdout.write(`\\n ${base.origin}\\n`);\n process.stdout.write(`\\nYour code: ${userCode}\\n\\n`);\n\n // ---- Step 3: Polling loop (D-10, D-11, D-12) ----\n const tokenUrl = new URL(base.pathname.replace(/\\/+$/, \"\") + \"/api/device/token\", base);\n const expiresAt = Date.now() + expiresIn * 1_000;\n let pollIntervalMs = DEFAULT_POLL_INTERVAL_MS;\n let spinnerIndex = 0;\n\n const spinnerInterval = setInterval(() => {\n const frame = SPINNER_FRAMES[spinnerIndex % SPINNER_FRAMES.length] ?? \"⠋\";\n spinnerIndex++;\n const remaining = Math.max(0, Math.ceil((expiresAt - Date.now()) / 1_000));\n cursorTo(process.stdout, 0);\n clearLine(process.stdout, 0);\n process.stdout.write(` ${frame} Waiting for approval... (expires in ${remaining}s)`);\n }, 100);\n\n try {\n while (Date.now() < expiresAt) {\n await sleep(pollIntervalMs);\n\n if (Date.now() >= expiresAt) break;\n\n let tokenResponse: Record<string, unknown>;\n try {\n tokenResponse = await postJsonToValidatedGateway(tokenUrl, { deviceCode });\n } catch {\n // Network errors during polling are transient — keep trying until expiry.\n continue;\n }\n\n const status = typeof tokenResponse[\"status\"] === \"string\" ? tokenResponse[\"status\"] : \"\";\n\n if (status === \"approved\") {\n clearInterval(spinnerInterval);\n cursorTo(process.stdout, 0);\n clearLine(process.stdout, 0);\n process.stdout.write(`${params.binaryName}: Authorization approved!\\n`);\n\n const deviceToken = typeof tokenResponse[\"token\"] === \"string\" ? tokenResponse[\"token\"] : \"\";\n const runtimeId = typeof tokenResponse[\"runtimeId\"] === \"string\" ? tokenResponse[\"runtimeId\"] : \"\";\n if (!deviceToken || !runtimeId) {\n throw new Error(\"Approval response missing token or runtimeId\");\n }\n\n // Resolve the relay/gateway URL in exactly ONE place (D-02, D-03).\n // Default to the caller-supplied gateway; if the server sent a valid\n // `gateway_url`, use that instead. The server value is UNTRUSTED input —\n // re-validate it through the same SSRF guards applied to `--gateway`.\n let gatewayUrl = params.gatewayUrl;\n const serverGatewayUrl = typeof tokenResponse[\"gateway_url\"] === \"string\"\n ? tokenResponse[\"gateway_url\"]\n : undefined;\n if (serverGatewayUrl) {\n try {\n const parsed = parseCloudGatewayUrl(serverGatewayUrl);\n validateGatewayNetworkTarget(parsed);\n gatewayUrl = parsed.toString();\n } catch (err) {\n // Fail loud-but-non-fatal: a bad server-supplied relay URL must not\n // crash login. Warn and fall back to the configured `--gateway`.\n const reason = err instanceof Error ? err.message : String(err);\n process.stderr.write(\n `${params.binaryName}: ignoring invalid server-supplied relay URL (${reason}); using configured --gateway instead.\\n`,\n );\n }\n }\n\n return { deviceToken, runtimeId, gatewayUrl };\n }\n\n if (status === \"denied\") {\n clearInterval(spinnerInterval);\n cursorTo(process.stdout, 0);\n clearLine(process.stdout, 0);\n process.stdout.write(`${params.binaryName}: Device request denied by user.\\n`);\n process.exit(1);\n }\n\n if (status === \"expired\") {\n break;\n }\n\n if (status === \"slow_down\") {\n pollIntervalMs += SLOW_DOWN_INCREMENT_MS;\n continue;\n }\n\n // status === \"pending\" or unknown — keep polling\n }\n } finally {\n clearInterval(spinnerInterval);\n }\n\n // Timeout path (D-11)\n cursorTo(process.stdout, 0);\n clearLine(process.stdout, 0);\n process.stdout.write(`${params.binaryName}: Authorization request timed out. Please run login again.\\n`);\n process.exit(1);\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-flow.test.d.ts","sourceRoot":"","sources":["../src/device-flow.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// Device-flow gateway_url resolution tests: server-valid-wins, absent-falls-back,
|
|
2
|
+
// invalid-falls-back-no-throw. Drives runDeviceFlow against a local loopback HTTP server.
|
|
3
|
+
import assert from "node:assert/strict";
|
|
4
|
+
import { createServer } from "node:http";
|
|
5
|
+
import { mkdtempSync } from "node:fs";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { test } from "node:test";
|
|
9
|
+
import { runDeviceFlow } from "./device-flow.js";
|
|
10
|
+
/**
|
|
11
|
+
* Stand up a loopback HTTP server implementing both device-flow endpoints.
|
|
12
|
+
* `/api/device/code` returns a code payload; `/api/device/token` returns an
|
|
13
|
+
* approved response whose `gateway_url` field is shaped per `gatewayCase`.
|
|
14
|
+
*/
|
|
15
|
+
function startDeviceServer(gatewayCase) {
|
|
16
|
+
const server = createServer((req, res) => {
|
|
17
|
+
let body = "";
|
|
18
|
+
req.setEncoding("utf8");
|
|
19
|
+
req.on("data", (chunk) => {
|
|
20
|
+
body += chunk;
|
|
21
|
+
});
|
|
22
|
+
req.on("end", () => {
|
|
23
|
+
if (req.url === "/api/device/code") {
|
|
24
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
25
|
+
res.end(JSON.stringify({
|
|
26
|
+
userCode: "ABCD-1234",
|
|
27
|
+
deviceCode: "device-code-fixture",
|
|
28
|
+
verificationUriComplete: "https://example.test/verify?code=ABCD-1234",
|
|
29
|
+
expiresIn: 600,
|
|
30
|
+
}));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (req.url === "/api/device/token") {
|
|
34
|
+
const approved = {
|
|
35
|
+
status: "approved",
|
|
36
|
+
token: "device-token-fixture",
|
|
37
|
+
runtimeId: "rt-fixture",
|
|
38
|
+
};
|
|
39
|
+
if (gatewayCase.kind === "value") {
|
|
40
|
+
approved["gateway_url"] = gatewayCase.value;
|
|
41
|
+
}
|
|
42
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
43
|
+
res.end(JSON.stringify(approved));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
res.writeHead(404, { "content-type": "application/json" });
|
|
47
|
+
res.end(JSON.stringify({ error: "not found" }));
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
server.once("error", reject);
|
|
52
|
+
server.listen(0, "127.0.0.1", () => {
|
|
53
|
+
const address = server.address();
|
|
54
|
+
if (!address || typeof address !== "object") {
|
|
55
|
+
reject(new Error("failed to bind loopback test server"));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
resolve({ server, baseUrl: `http://127.0.0.1:${address.port}` });
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function closeServer(server) {
|
|
63
|
+
return new Promise((resolve, reject) => server.close((err) => (err ? reject(err) : resolve())));
|
|
64
|
+
}
|
|
65
|
+
/** Capture stderr writes so the invalid-URL warning can be asserted. */
|
|
66
|
+
function captureStderr() {
|
|
67
|
+
const original = process.stderr.write.bind(process.stderr);
|
|
68
|
+
let captured = "";
|
|
69
|
+
process.stderr.write = ((chunk) => {
|
|
70
|
+
captured += typeof chunk === "string" ? chunk : String(chunk);
|
|
71
|
+
return true;
|
|
72
|
+
});
|
|
73
|
+
return {
|
|
74
|
+
restore: () => {
|
|
75
|
+
process.stderr.write = original;
|
|
76
|
+
},
|
|
77
|
+
output: () => captured,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function configPathFixture() {
|
|
81
|
+
return join(mkdtempSync(join(tmpdir(), "gsd-device-flow-")), "daemon.yaml");
|
|
82
|
+
}
|
|
83
|
+
test("device flow uses a valid server gateway_url over the passed --gateway", async (t) => {
|
|
84
|
+
let started;
|
|
85
|
+
try {
|
|
86
|
+
// A different, valid loopback URL than the login base — server value should win.
|
|
87
|
+
const serverGateway = "https://relay.example/gateway";
|
|
88
|
+
started = await startDeviceServer({ kind: "value", value: serverGateway });
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
if (err.code === "EPERM") {
|
|
92
|
+
t.skip("loopback listen is blocked in this sandbox");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
throw err;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const result = await runDeviceFlow({
|
|
99
|
+
gatewayUrl: started.baseUrl,
|
|
100
|
+
configPath: configPathFixture(),
|
|
101
|
+
binaryName: "gsd",
|
|
102
|
+
});
|
|
103
|
+
// Server-returned value wins, normalized (trailing slash stripped from path).
|
|
104
|
+
assert.equal(result.gatewayUrl, "https://relay.example/gateway");
|
|
105
|
+
assert.notEqual(result.gatewayUrl, started.baseUrl);
|
|
106
|
+
assert.equal(result.deviceToken, "device-token-fixture");
|
|
107
|
+
assert.equal(result.runtimeId, "rt-fixture");
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
await closeServer(started.server);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
test("device flow falls back to --gateway when server omits gateway_url (backward-compat)", async (t) => {
|
|
114
|
+
let started;
|
|
115
|
+
try {
|
|
116
|
+
started = await startDeviceServer({ kind: "absent" });
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
if (err.code === "EPERM") {
|
|
120
|
+
t.skip("loopback listen is blocked in this sandbox");
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
throw err;
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
const result = await runDeviceFlow({
|
|
127
|
+
gatewayUrl: started.baseUrl,
|
|
128
|
+
configPath: configPathFixture(),
|
|
129
|
+
binaryName: "gsd",
|
|
130
|
+
});
|
|
131
|
+
// Load-bearing invariant: with no server gateway_url, the passed --gateway is preserved.
|
|
132
|
+
assert.equal(result.gatewayUrl, started.baseUrl);
|
|
133
|
+
assert.equal(result.deviceToken, "device-token-fixture");
|
|
134
|
+
assert.equal(result.runtimeId, "rt-fixture");
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
await closeServer(started.server);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
test("device flow falls back to --gateway when server gateway_url is invalid (no throw)", async (t) => {
|
|
141
|
+
let started;
|
|
142
|
+
try {
|
|
143
|
+
// A private-IP https URL fails validateGatewayNetworkTarget — must fall back, not crash.
|
|
144
|
+
started = await startDeviceServer({ kind: "value", value: "https://10.0.0.5/gateway" });
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
if (err.code === "EPERM") {
|
|
148
|
+
t.skip("loopback listen is blocked in this sandbox");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
throw err;
|
|
152
|
+
}
|
|
153
|
+
const stderr = captureStderr();
|
|
154
|
+
let capturedWarning = "";
|
|
155
|
+
try {
|
|
156
|
+
const result = await runDeviceFlow({
|
|
157
|
+
gatewayUrl: started.baseUrl,
|
|
158
|
+
configPath: configPathFixture(),
|
|
159
|
+
binaryName: "gsd",
|
|
160
|
+
});
|
|
161
|
+
capturedWarning = stderr.output();
|
|
162
|
+
stderr.restore();
|
|
163
|
+
// Invalid server value is ignored; passed --gateway is used; login does not throw.
|
|
164
|
+
assert.equal(result.gatewayUrl, started.baseUrl);
|
|
165
|
+
assert.match(capturedWarning, /ignoring invalid server-supplied relay URL/);
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
stderr.restore();
|
|
169
|
+
await closeServer(started.server);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
//# sourceMappingURL=device-flow.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-flow.test.js","sourceRoot":"","sources":["../src/device-flow.test.ts"],"names":[],"mappings":"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","sourcesContent":["// Device-flow gateway_url resolution tests: server-valid-wins, absent-falls-back,\n// invalid-falls-back-no-throw. Drives runDeviceFlow against a local loopback HTTP server.\nimport assert from \"node:assert/strict\";\nimport { createServer, type Server } from \"node:http\";\nimport { mkdtempSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { test } from \"node:test\";\nimport { runDeviceFlow } from \"./device-flow.js\";\n\n/** Server-side gateway_url behaviour for a single test case. */\ntype GatewayCase =\n | { kind: \"value\"; value: string }\n | { kind: \"absent\" };\n\n/**\n * Stand up a loopback HTTP server implementing both device-flow endpoints.\n * `/api/device/code` returns a code payload; `/api/device/token` returns an\n * approved response whose `gateway_url` field is shaped per `gatewayCase`.\n */\nfunction startDeviceServer(gatewayCase: GatewayCase): Promise<{ server: Server; baseUrl: string }> {\n const server = createServer((req, res) => {\n let body = \"\";\n req.setEncoding(\"utf8\");\n req.on(\"data\", (chunk) => {\n body += chunk;\n });\n req.on(\"end\", () => {\n if (req.url === \"/api/device/code\") {\n res.writeHead(200, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify({\n userCode: \"ABCD-1234\",\n deviceCode: \"device-code-fixture\",\n verificationUriComplete: \"https://example.test/verify?code=ABCD-1234\",\n expiresIn: 600,\n }));\n return;\n }\n if (req.url === \"/api/device/token\") {\n const approved: Record<string, unknown> = {\n status: \"approved\",\n token: \"device-token-fixture\",\n runtimeId: \"rt-fixture\",\n };\n if (gatewayCase.kind === \"value\") {\n approved[\"gateway_url\"] = gatewayCase.value;\n }\n res.writeHead(200, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(approved));\n return;\n }\n res.writeHead(404, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"not found\" }));\n });\n });\n\n return new Promise((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(0, \"127.0.0.1\", () => {\n const address = server.address();\n if (!address || typeof address !== \"object\") {\n reject(new Error(\"failed to bind loopback test server\"));\n return;\n }\n resolve({ server, baseUrl: `http://127.0.0.1:${address.port}` });\n });\n });\n}\n\nfunction closeServer(server: Server): Promise<void> {\n return new Promise((resolve, reject) => server.close((err) => (err ? reject(err) : resolve())));\n}\n\n/** Capture stderr writes so the invalid-URL warning can be asserted. */\nfunction captureStderr(): { restore: () => void; output: () => string } {\n const original = process.stderr.write.bind(process.stderr);\n let captured = \"\";\n (process.stderr as unknown as { write: typeof process.stderr.write }).write = ((chunk: unknown) => {\n captured += typeof chunk === \"string\" ? chunk : String(chunk);\n return true;\n }) as typeof process.stderr.write;\n return {\n restore: () => {\n (process.stderr as unknown as { write: typeof process.stderr.write }).write = original;\n },\n output: () => captured,\n };\n}\n\nfunction configPathFixture(): string {\n return join(mkdtempSync(join(tmpdir(), \"gsd-device-flow-\")), \"daemon.yaml\");\n}\n\ntest(\"device flow uses a valid server gateway_url over the passed --gateway\", async (t) => {\n let started: { server: Server; baseUrl: string };\n try {\n // A different, valid loopback URL than the login base — server value should win.\n const serverGateway = \"https://relay.example/gateway\";\n started = await startDeviceServer({ kind: \"value\", value: serverGateway });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"EPERM\") {\n t.skip(\"loopback listen is blocked in this sandbox\");\n return;\n }\n throw err;\n }\n try {\n const result = await runDeviceFlow({\n gatewayUrl: started.baseUrl,\n configPath: configPathFixture(),\n binaryName: \"gsd\",\n });\n // Server-returned value wins, normalized (trailing slash stripped from path).\n assert.equal(result.gatewayUrl, \"https://relay.example/gateway\");\n assert.notEqual(result.gatewayUrl, started.baseUrl);\n assert.equal(result.deviceToken, \"device-token-fixture\");\n assert.equal(result.runtimeId, \"rt-fixture\");\n } finally {\n await closeServer(started.server);\n }\n});\n\ntest(\"device flow falls back to --gateway when server omits gateway_url (backward-compat)\", async (t) => {\n let started: { server: Server; baseUrl: string };\n try {\n started = await startDeviceServer({ kind: \"absent\" });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"EPERM\") {\n t.skip(\"loopback listen is blocked in this sandbox\");\n return;\n }\n throw err;\n }\n try {\n const result = await runDeviceFlow({\n gatewayUrl: started.baseUrl,\n configPath: configPathFixture(),\n binaryName: \"gsd\",\n });\n // Load-bearing invariant: with no server gateway_url, the passed --gateway is preserved.\n assert.equal(result.gatewayUrl, started.baseUrl);\n assert.equal(result.deviceToken, \"device-token-fixture\");\n assert.equal(result.runtimeId, \"rt-fixture\");\n } finally {\n await closeServer(started.server);\n }\n});\n\ntest(\"device flow falls back to --gateway when server gateway_url is invalid (no throw)\", async (t) => {\n let started: { server: Server; baseUrl: string };\n try {\n // A private-IP https URL fails validateGatewayNetworkTarget — must fall back, not crash.\n started = await startDeviceServer({ kind: \"value\", value: \"https://10.0.0.5/gateway\" });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"EPERM\") {\n t.skip(\"loopback listen is blocked in this sandbox\");\n return;\n }\n throw err;\n }\n const stderr = captureStderr();\n let capturedWarning = \"\";\n try {\n const result = await runDeviceFlow({\n gatewayUrl: started.baseUrl,\n configPath: configPathFixture(),\n binaryName: \"gsd\",\n });\n capturedWarning = stderr.output();\n stderr.restore();\n // Invalid server value is ignored; passed --gateway is used; login does not throw.\n assert.equal(result.gatewayUrl, started.baseUrl);\n assert.match(capturedWarning, /ignoring invalid server-supplied relay URL/);\n } finally {\n stderr.restore();\n await closeServer(started.server);\n }\n});\n"]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type Anthropic from '@anthropic-ai/sdk';
|
|
2
|
+
import type { MessageParam } from '@anthropic-ai/sdk/resources/messages/messages';
|
|
3
|
+
import { type OrchestratorToolContext } from './orchestrator-tools.js';
|
|
4
|
+
export interface OrchestratorAgentConfig {
|
|
5
|
+
model: string;
|
|
6
|
+
max_tokens: number;
|
|
7
|
+
}
|
|
8
|
+
export declare class OrchestratorAgent {
|
|
9
|
+
private readonly config;
|
|
10
|
+
private readonly toolContext;
|
|
11
|
+
private client;
|
|
12
|
+
private history;
|
|
13
|
+
constructor(config: OrchestratorAgentConfig, toolContext: OrchestratorToolContext, client?: Anthropic);
|
|
14
|
+
run(content: string): Promise<string>;
|
|
15
|
+
getHistory(): MessageParam[];
|
|
16
|
+
stop(): void;
|
|
17
|
+
private getClient;
|
|
18
|
+
private runToolLoop;
|
|
19
|
+
private trimHistory;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=orchestrator-agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orchestrator-agent.d.ts","sourceRoot":"","sources":["../src/orchestrator-agent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,mBAAmB,CAAC;AAC/C,OAAO,KAAK,EACV,YAAY,EAKb,MAAM,+CAA+C,CAAC;AACvD,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,yBAAyB,CAAC;AAEjC,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAqCD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0B;IACjD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA0B;IACtD,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,OAAO,CAAsB;gBAGnC,MAAM,EAAE,uBAAuB,EAC/B,WAAW,EAAE,uBAAuB,EACpC,MAAM,CAAC,EAAE,SAAS;IAOd,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAmB3C,UAAU,IAAI,YAAY,EAAE;IAI5B,IAAI,IAAI,IAAI;YAKE,SAAS;YAQT,WAAW;IAkDzB,OAAO,CAAC,WAAW;CAKpB"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { ORCHESTRATOR_TOOLS, executeOrchestratorTool, } from './orchestrator-tools.js';
|
|
2
|
+
const SYSTEM_PROMPT = `You are GSD Control — a concise, capable orchestrator for managing GSD (Git Ship Done) coding agent sessions via Discord.
|
|
3
|
+
|
|
4
|
+
You have tools to list projects, start sessions, get status, stop sessions, and inspect session details. Use them to fulfill the user's requests.
|
|
5
|
+
|
|
6
|
+
Response guidelines:
|
|
7
|
+
- Be terse and direct. No filler, no performed enthusiasm.
|
|
8
|
+
- When reporting status, use bullet points with project name, status, duration, and cost.
|
|
9
|
+
- When starting a session, confirm with the project name and session ID.
|
|
10
|
+
- When stopping a session, confirm which session was stopped.
|
|
11
|
+
- If something fails, say what went wrong plainly.
|
|
12
|
+
- Use Discord markdown formatting (bold, code blocks) for readability.
|
|
13
|
+
- Never expose internal error stack traces to the user — summarize the issue.`;
|
|
14
|
+
const MAX_HISTORY = 30;
|
|
15
|
+
const MAX_TOOL_ITERATIONS = 10;
|
|
16
|
+
function resolveAnthropicApiKey() {
|
|
17
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
18
|
+
if (!apiKey) {
|
|
19
|
+
throw new Error('ANTHROPIC_API_KEY is required. Set it in your environment or run `gsd config`.');
|
|
20
|
+
}
|
|
21
|
+
return apiKey;
|
|
22
|
+
}
|
|
23
|
+
function isAuthError(message) {
|
|
24
|
+
return (message.includes('authentication') ||
|
|
25
|
+
message.includes('apiKey') ||
|
|
26
|
+
message.includes('authToken') ||
|
|
27
|
+
message.includes('401'));
|
|
28
|
+
}
|
|
29
|
+
export class OrchestratorAgent {
|
|
30
|
+
config;
|
|
31
|
+
toolContext;
|
|
32
|
+
client;
|
|
33
|
+
history = [];
|
|
34
|
+
constructor(config, toolContext, client) {
|
|
35
|
+
this.config = config;
|
|
36
|
+
this.toolContext = toolContext;
|
|
37
|
+
this.client = client ?? null;
|
|
38
|
+
}
|
|
39
|
+
async run(content) {
|
|
40
|
+
this.history.push({ role: 'user', content });
|
|
41
|
+
try {
|
|
42
|
+
const responseText = await this.runToolLoop();
|
|
43
|
+
this.history.push({ role: 'assistant', content: responseText });
|
|
44
|
+
return responseText;
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
48
|
+
if (isAuthError(errorMsg)) {
|
|
49
|
+
this.client = null;
|
|
50
|
+
}
|
|
51
|
+
this.history.push({ role: 'assistant', content: '[error — see logs]' });
|
|
52
|
+
throw err;
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
this.trimHistory();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
getHistory() {
|
|
59
|
+
return [...this.history];
|
|
60
|
+
}
|
|
61
|
+
stop() {
|
|
62
|
+
this.history = [];
|
|
63
|
+
this.client = null;
|
|
64
|
+
}
|
|
65
|
+
async getClient() {
|
|
66
|
+
if (this.client)
|
|
67
|
+
return this.client;
|
|
68
|
+
const apiKey = resolveAnthropicApiKey();
|
|
69
|
+
const { default: AnthropicSDK } = await import('@anthropic-ai/sdk');
|
|
70
|
+
this.client = new AnthropicSDK({ apiKey });
|
|
71
|
+
return this.client;
|
|
72
|
+
}
|
|
73
|
+
async runToolLoop() {
|
|
74
|
+
const client = await this.getClient();
|
|
75
|
+
const { model, max_tokens } = this.config;
|
|
76
|
+
let loopMessages = [...this.history];
|
|
77
|
+
for (let i = 0; i < MAX_TOOL_ITERATIONS; i++) {
|
|
78
|
+
const response = await client.messages.create({
|
|
79
|
+
model,
|
|
80
|
+
max_tokens,
|
|
81
|
+
system: SYSTEM_PROMPT,
|
|
82
|
+
tools: ORCHESTRATOR_TOOLS,
|
|
83
|
+
messages: loopMessages,
|
|
84
|
+
});
|
|
85
|
+
if (response.stop_reason !== 'tool_use') {
|
|
86
|
+
const textBlocks = response.content.filter((b) => b.type === 'text');
|
|
87
|
+
return textBlocks.map((b) => b.text).join('\n') || '(No response)';
|
|
88
|
+
}
|
|
89
|
+
const toolUseBlocks = response.content.filter((b) => b.type === 'tool_use');
|
|
90
|
+
const toolResults = [];
|
|
91
|
+
for (const toolUse of toolUseBlocks) {
|
|
92
|
+
const result = await executeOrchestratorTool(this.toolContext, toolUse.name, toolUse.input);
|
|
93
|
+
toolResults.push({
|
|
94
|
+
type: 'tool_result',
|
|
95
|
+
tool_use_id: toolUse.id,
|
|
96
|
+
content: result,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
loopMessages = [
|
|
100
|
+
...loopMessages,
|
|
101
|
+
{ role: 'assistant', content: response.content },
|
|
102
|
+
{ role: 'user', content: toolResults },
|
|
103
|
+
];
|
|
104
|
+
}
|
|
105
|
+
return 'I hit the maximum number of tool iterations. Please try a simpler request.';
|
|
106
|
+
}
|
|
107
|
+
trimHistory() {
|
|
108
|
+
while (this.history.length > MAX_HISTORY) {
|
|
109
|
+
this.history.splice(0, 2);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=orchestrator-agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orchestrator-agent.js","sourceRoot":"","sources":["../src/orchestrator-agent.ts"],"names":[],"mappings":"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","sourcesContent":["import type Anthropic from '@anthropic-ai/sdk';\nimport type {\n MessageParam,\n ContentBlockParam,\n ToolResultBlockParam,\n ToolUseBlock,\n TextBlock,\n} from '@anthropic-ai/sdk/resources/messages/messages';\nimport {\n ORCHESTRATOR_TOOLS,\n executeOrchestratorTool,\n type OrchestratorToolContext,\n} from './orchestrator-tools.js';\n\nexport interface OrchestratorAgentConfig {\n model: string;\n max_tokens: number;\n}\n\nconst SYSTEM_PROMPT = `You are GSD Control — a concise, capable orchestrator for managing GSD (Git Ship Done) coding agent sessions via Discord.\n\nYou have tools to list projects, start sessions, get status, stop sessions, and inspect session details. Use them to fulfill the user's requests.\n\nResponse guidelines:\n- Be terse and direct. No filler, no performed enthusiasm.\n- When reporting status, use bullet points with project name, status, duration, and cost.\n- When starting a session, confirm with the project name and session ID.\n- When stopping a session, confirm which session was stopped.\n- If something fails, say what went wrong plainly.\n- Use Discord markdown formatting (bold, code blocks) for readability.\n- Never expose internal error stack traces to the user — summarize the issue.`;\n\nconst MAX_HISTORY = 30;\nconst MAX_TOOL_ITERATIONS = 10;\n\nfunction resolveAnthropicApiKey(): string {\n const apiKey = process.env.ANTHROPIC_API_KEY;\n if (!apiKey) {\n throw new Error(\n 'ANTHROPIC_API_KEY is required. Set it in your environment or run `gsd config`.',\n );\n }\n return apiKey;\n}\n\nfunction isAuthError(message: string): boolean {\n return (\n message.includes('authentication') ||\n message.includes('apiKey') ||\n message.includes('authToken') ||\n message.includes('401')\n );\n}\n\nexport class OrchestratorAgent {\n private readonly config: OrchestratorAgentConfig;\n private readonly toolContext: OrchestratorToolContext;\n private client: Anthropic | null;\n private history: MessageParam[] = [];\n\n constructor(\n config: OrchestratorAgentConfig,\n toolContext: OrchestratorToolContext,\n client?: Anthropic,\n ) {\n this.config = config;\n this.toolContext = toolContext;\n this.client = client ?? null;\n }\n\n async run(content: string): Promise<string> {\n this.history.push({ role: 'user', content });\n\n try {\n const responseText = await this.runToolLoop();\n this.history.push({ role: 'assistant', content: responseText });\n return responseText;\n } catch (err) {\n const errorMsg = err instanceof Error ? err.message : String(err);\n if (isAuthError(errorMsg)) {\n this.client = null;\n }\n this.history.push({ role: 'assistant', content: '[error — see logs]' });\n throw err;\n } finally {\n this.trimHistory();\n }\n }\n\n getHistory(): MessageParam[] {\n return [...this.history];\n }\n\n stop(): void {\n this.history = [];\n this.client = null;\n }\n\n private async getClient(): Promise<Anthropic> {\n if (this.client) return this.client;\n const apiKey = resolveAnthropicApiKey();\n const { default: AnthropicSDK } = await import('@anthropic-ai/sdk');\n this.client = new AnthropicSDK({ apiKey });\n return this.client;\n }\n\n private async runToolLoop(): Promise<string> {\n const client = await this.getClient();\n const { model, max_tokens } = this.config;\n\n let loopMessages: MessageParam[] = [...this.history];\n\n for (let i = 0; i < MAX_TOOL_ITERATIONS; i++) {\n const response = await client.messages.create({\n model,\n max_tokens,\n system: SYSTEM_PROMPT,\n tools: ORCHESTRATOR_TOOLS,\n messages: loopMessages,\n });\n\n if (response.stop_reason !== 'tool_use') {\n const textBlocks = response.content.filter(\n (b): b is TextBlock => b.type === 'text',\n );\n return textBlocks.map((b) => b.text).join('\\n') || '(No response)';\n }\n\n const toolUseBlocks = response.content.filter(\n (b): b is ToolUseBlock => b.type === 'tool_use',\n );\n\n const toolResults: ToolResultBlockParam[] = [];\n for (const toolUse of toolUseBlocks) {\n const result = await executeOrchestratorTool(\n this.toolContext,\n toolUse.name,\n toolUse.input as Record<string, unknown>,\n );\n toolResults.push({\n type: 'tool_result',\n tool_use_id: toolUse.id,\n content: result,\n });\n }\n\n loopMessages = [\n ...loopMessages,\n { role: 'assistant', content: response.content as ContentBlockParam[] },\n { role: 'user', content: toolResults },\n ];\n }\n\n return 'I hit the maximum number of tool iterations. Please try a simpler request.';\n }\n\n private trimHistory(): void {\n while (this.history.length > MAX_HISTORY) {\n this.history.splice(0, 2);\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Tool } from '@anthropic-ai/sdk/resources/messages/messages';
|
|
2
|
+
import type { SessionManager } from './session-manager.js';
|
|
3
|
+
import type { ProjectInfo } from './types.js';
|
|
4
|
+
import type { Logger } from './logger.js';
|
|
5
|
+
export interface OrchestratorToolContext {
|
|
6
|
+
sessionManager: SessionManager;
|
|
7
|
+
scanProjects: () => Promise<ProjectInfo[]>;
|
|
8
|
+
logger: Logger;
|
|
9
|
+
}
|
|
10
|
+
export declare const ORCHESTRATOR_TOOLS: Tool[];
|
|
11
|
+
export declare function executeOrchestratorTool(context: OrchestratorToolContext, name: string, input: Record<string, unknown>): Promise<string>;
|
|
12
|
+
//# sourceMappingURL=orchestrator-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orchestrator-tools.d.ts","sourceRoot":"","sources":["../src/orchestrator-tools.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,+CAA+C,CAAC;AAC1E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,WAAW,EAAkB,MAAM,YAAY,CAAC;AAC9D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,WAAW,uBAAuB;IACtC,cAAc,EAAE,cAAc,CAAC;IAC/B,YAAY,EAAE,MAAM,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAC3C,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,kBAAkB,EAAE,IAAI,EAqDpC,CAAC;AAeF,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,uBAAuB,EAChC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,CAAC,MAAM,CAAC,CAqBjB"}
|