@indigoai-us/hq-cli 5.314.0 → 5.315.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4311 -1
- package/README.md +113 -0
- package/assets/bot-workers/setup/context/USER-GUIDE.md +404 -0
- package/assets/bot-workers/setup/context/quick-reference.md +199 -0
- package/assets/bot-workers/setup/skills/agents-and-team.md +167 -0
- package/assets/bot-workers/setup/skills/explain-hq.md +35 -0
- package/assets/bot-workers/setup/skills/first-company.md +74 -0
- package/assets/bot-workers/setup/skills/joining-a-team.md +125 -0
- package/assets/bot-workers/setup/skills/pricing.md +92 -0
- package/assets/bot-workers/setup/skills/standing-help.md +81 -0
- package/assets/bot-workers/setup/skills/your-business.md +281 -0
- package/assets/bot-workers/setup/worker.yaml +791 -0
- package/assets/lanes-hooks/hq-lanes-drain.json +15 -0
- package/assets/lanes-hooks/hq-lanes-drain.mjs +275 -0
- package/assets/lanes-hooks/hq-lanes-drain.sh +21 -0
- package/assets/lanes-reconcile/hq-lanes-reconcile.service +11 -0
- package/assets/lanes-reconcile/hq-lanes-reconcile.timer +12 -0
- package/assets/scaffold/.claude/hooks/validate-policy-frontmatter.sh +485 -0
- package/assets/scaffold/core/scripts/audit-log.sh +384 -0
- package/assets/scaffold/core/scripts/checkpoint-stop-gate.sh +281 -43
- package/assets/scaffold/core/scripts/clean-worktree-reconcile-handoff.sh +171 -0
- package/assets/scaffold/core/scripts/eval-trigger.sh +193 -0
- package/assets/scaffold/core/scripts/git-pack-extension-check.sh +164 -0
- package/assets/scaffold/core/scripts/handoff-finalize.sh +688 -0
- package/assets/scaffold/core/scripts/handoff-open-steps.sh +81 -0
- package/assets/scaffold/core/scripts/handoff-post.sh +252 -0
- package/assets/scaffold/core/scripts/hook-lib.sh +345 -50
- package/assets/scaffold/core/scripts/hq-delegate-bundle.sh +307 -0
- package/assets/scaffold/core/scripts/hq-delegate-grant.sh +252 -0
- package/assets/scaffold/core/scripts/hq-delegate-pickup.sh +99 -0
- package/assets/scaffold/core/scripts/hq-delegate-publish.sh +91 -0
- package/assets/scaffold/core/scripts/hq-delegate-repo.sh +158 -0
- package/assets/scaffold/core/scripts/hq-delegate-resolve.sh +145 -0
- package/assets/scaffold/core/scripts/hq-delegate-secrets.sh +129 -0
- package/assets/scaffold/core/scripts/hq-delegate-send.sh +259 -0
- package/assets/scaffold/core/scripts/hq-delegate-transfer.sh +175 -0
- package/assets/scaffold/core/scripts/hq-delegate-verify.sh +238 -0
- package/assets/scaffold/core/scripts/hq-dm-bind.sh +585 -0
- package/assets/scaffold/core/scripts/hq-job-notify.sh +389 -0
- package/assets/scaffold/core/scripts/hq-job-probe.sh +835 -0
- package/assets/scaffold/core/scripts/hq-job-remediate.sh +359 -0
- package/assets/scaffold/core/scripts/hq-job-run.sh +1025 -0
- package/assets/scaffold/core/scripts/hq-pin.sh +118 -0
- package/assets/scaffold/core/scripts/hq-session.sh +184 -23
- package/assets/scaffold/core/scripts/jobs-schedule-parse.sh +310 -0
- package/assets/scaffold/core/scripts/jobs-validate.sh +451 -0
- package/assets/scaffold/core/scripts/knowledge-prefs.sh +55 -0
- package/assets/scaffold/core/scripts/lib/portable.sh +211 -0
- package/assets/scaffold/core/scripts/lib/secret-patterns.sh +58 -0
- package/assets/scaffold/core/scripts/lib/session-id.sh +3 -2
- package/assets/scaffold/core/scripts/lib/session-scope-capability.sh +1 -1
- package/assets/scaffold/core/scripts/lib/work-mesh-enqueue.sh +2 -1
- package/assets/scaffold/core/scripts/lib/work-mesh-live-hook.sh +378 -0
- package/assets/scaffold/core/scripts/lib/work-mesh-live-rebind.sh +141 -0
- package/assets/scaffold/core/scripts/migrate-policy-triggers.sh +378 -0
- package/assets/scaffold/core/scripts/ontology-audience-grant.sh +81 -0
- package/assets/scaffold/core/scripts/ontology-brief.mjs +93 -0
- package/assets/scaffold/core/scripts/ontology-candidate.sh +100 -0
- package/assets/scaffold/core/scripts/ontology-garden.mjs +238 -0
- package/assets/scaffold/core/scripts/ontology-source-worklist.sh +79 -0
- package/assets/scaffold/core/scripts/outpost-jobs-reconcile.sh +834 -0
- package/assets/scaffold/core/scripts/policy-age-report.sh +156 -0
- package/assets/scaffold/core/scripts/policy-retire.sh +199 -0
- package/assets/scaffold/core/scripts/qmd-reindex-after-sync.sh +10 -87
- package/assets/scaffold/core/scripts/qmd-reindex-bg.sh +40 -0
- package/assets/scaffold/core/scripts/read-policy-frontmatter.sh +32 -0
- package/assets/scaffold/core/scripts/rebuild-company-knowledge-index.sh +36 -3
- package/assets/scaffold/core/scripts/rebuild-projects-index.sh +99 -12
- package/assets/scaffold/core/scripts/refresh-vault-access.sh +188 -0
- package/assets/scaffold/core/scripts/register-project.sh +262 -0
- package/assets/scaffold/core/scripts/resolve-company.sh +199 -0
- package/assets/scaffold/core/scripts/resume-thread-lock.sh +285 -0
- package/assets/scaffold/core/scripts/source-yaml-validate.sh +36 -0
- package/assets/scaffold/core/scripts/token-usage-report.sh +81 -20
- package/assets/scaffold/core/scripts/verify-story-deliverables.sh +148 -0
- package/assets/scaffold/core/scripts/work-mesh-live-bind-trusted.sh +152 -0
- package/assets/scaffold/core/scripts/work-mesh-live-rebind.sh +59 -0
- package/assets/scaffold/core/scripts/worktree-gc.sh +362 -0
- package/contracts/preflight/v1/fixtures.json +87 -0
- package/contracts/preflight/v1/schema.json +58 -0
- package/dist/bin/hq-auth-refresh.d.ts +2 -0
- package/dist/bin/hq-auth-refresh.js +12 -6
- package/dist/command-catalog.generated.d.ts +10753 -0
- package/dist/command-catalog.generated.js +13859 -0
- package/dist/command-registration-plan.d.ts +474 -0
- package/dist/command-registration-plan.js +136 -0
- package/dist/commands/__fixtures__/access-vault.d.ts +2 -0
- package/dist/commands/__fixtures__/access-vault.js +1 -1
- package/dist/commands/_patterns.d.ts +16 -0
- package/dist/commands/_patterns.js +20 -3
- package/dist/commands/access.d.ts +2 -1
- package/dist/commands/access.js +58 -49
- package/dist/commands/agent-enroll.d.ts +146 -0
- package/dist/commands/agent-enroll.js +401 -0
- package/dist/commands/agent-inbox.d.ts +9 -0
- package/dist/commands/agent-inbox.js +102 -0
- package/dist/commands/agent-kit.d.ts +87 -0
- package/dist/commands/agent-kit.js +480 -0
- package/dist/commands/agent-mcp.d.ts +24 -0
- package/dist/commands/agent-mcp.js +127 -0
- package/dist/commands/agent-probe.d.ts +77 -0
- package/dist/commands/agent-probe.js +335 -0
- package/dist/commands/agent.d.ts +13 -0
- package/dist/commands/agent.js +40 -0
- package/dist/commands/agents.d.ts +106 -4
- package/dist/commands/agents.js +924 -45
- package/dist/commands/auth.d.ts +1 -0
- package/dist/commands/auth.js +189 -19
- package/dist/commands/billing-sponsor.d.ts +83 -0
- package/dist/commands/billing-sponsor.js +344 -0
- package/dist/commands/billing.d.ts +3 -0
- package/dist/commands/billing.js +6 -1
- package/dist/commands/bot-companies.d.ts +40 -0
- package/dist/commands/bot-companies.js +64 -0
- package/dist/commands/bot-continuity.d.ts +28 -0
- package/dist/commands/bot-continuity.js +68 -0
- package/dist/commands/bot-jobs.d.ts +127 -0
- package/dist/commands/bot-jobs.js +454 -0
- package/dist/commands/bot.d.ts +278 -0
- package/dist/commands/bot.js +1982 -0
- package/dist/commands/channels.js +56 -1
- package/dist/commands/cloud-demote.js +3 -2
- package/dist/commands/cloud-provision.d.ts +14 -0
- package/dist/commands/cloud-provision.js +232 -58
- package/dist/commands/cloud-retire.d.ts +51 -0
- package/dist/commands/cloud-retire.js +154 -0
- package/dist/commands/cloud.d.ts +49 -15
- package/dist/commands/cloud.js +357 -122
- package/dist/commands/company-rename-flag.d.ts +4 -0
- package/dist/commands/company-rename-flag.js +7 -0
- package/dist/commands/company-rename.d.ts +4 -0
- package/dist/commands/company-rename.js +90 -0
- package/dist/commands/company-transfer.d.ts +4 -28
- package/dist/commands/company-transfer.js +5 -49
- package/dist/commands/company.js +68 -3
- package/dist/commands/core-audit-log-native.d.ts +9 -0
- package/dist/commands/core-audit-log-native.js +257 -0
- package/dist/commands/core-checkpoint-gate-setting.d.ts +59 -0
- package/dist/commands/core-checkpoint-gate-setting.js +163 -0
- package/dist/commands/core-checkpoint-stop-gate-native.d.ts +34 -0
- package/dist/commands/core-checkpoint-stop-gate-native.js +725 -0
- package/dist/commands/core-checkpoint.d.ts +19 -9
- package/dist/commands/core-checkpoint.js +301 -37
- package/dist/commands/core-command-catalog.d.ts +34 -0
- package/dist/commands/core-command-catalog.js +62 -0
- package/dist/commands/core-company-index-scope-flag.d.ts +3 -0
- package/dist/commands/core-company-index-scope-flag.js +6 -0
- package/dist/commands/core-delegate-w1b.d.ts +10 -0
- package/dist/commands/core-delegate-w1b.js +1219 -0
- package/dist/commands/core-delegate.d.ts +46 -0
- package/dist/commands/core-delegate.js +1040 -0
- package/dist/commands/core-dm-bind-native.d.ts +32 -0
- package/dist/commands/core-dm-bind-native.js +668 -0
- package/dist/commands/core-eval-trigger-native.d.ts +4 -0
- package/dist/commands/core-eval-trigger-native.js +12 -0
- package/dist/commands/core-git-pack-extension-check-native.d.ts +22 -0
- package/dist/commands/core-git-pack-extension-check-native.js +190 -0
- package/dist/commands/core-handoff-fast.d.ts +18 -0
- package/dist/commands/core-handoff-fast.js +104 -0
- package/dist/commands/core-handoff-open-steps.d.ts +21 -0
- package/dist/commands/core-handoff-open-steps.js +264 -0
- package/dist/commands/core-handoff.d.ts +24 -0
- package/dist/commands/core-handoff.js +1144 -0
- package/dist/commands/core-hq-session-native.d.ts +39 -0
- package/dist/commands/core-hq-session-native.js +579 -0
- package/dist/commands/core-job-native-utils.d.ts +6 -0
- package/dist/commands/core-job-native-utils.js +80 -0
- package/dist/commands/core-job-native.d.ts +5 -0
- package/dist/commands/core-job-native.js +16 -0
- package/dist/commands/core-job-notify-native.d.ts +36 -0
- package/dist/commands/core-job-notify-native.js +359 -0
- package/dist/commands/core-job-probe-native.d.ts +45 -0
- package/dist/commands/core-job-probe-native.js +564 -0
- package/dist/commands/core-job-remediate-native.d.ts +26 -0
- package/dist/commands/core-job-remediate-native.js +374 -0
- package/dist/commands/core-job-remote-launcher.d.ts +7 -0
- package/dist/commands/core-job-remote-launcher.js +51 -0
- package/dist/commands/core-job-run-native.d.ts +47 -0
- package/dist/commands/core-job-run-native.js +1215 -0
- package/dist/commands/core-jobs-native.d.ts +25 -0
- package/dist/commands/core-jobs-native.js +1339 -0
- package/dist/commands/core-knowledge-prefs.d.ts +11 -0
- package/dist/commands/core-knowledge-prefs.js +137 -0
- package/dist/commands/core-migrate-policy-triggers-native.d.ts +21 -0
- package/dist/commands/core-migrate-policy-triggers-native.js +374 -0
- package/dist/commands/core-native-handoff-flag.d.ts +4 -0
- package/dist/commands/core-native-handoff-flag.js +7 -0
- package/dist/commands/core-native-hook-helper-commands.d.ts +4 -0
- package/dist/commands/core-native-hook-helper-commands.js +56 -0
- package/dist/commands/core-native-hook-helpers-flag.d.ts +4 -0
- package/dist/commands/core-native-hook-helpers-flag.js +7 -0
- package/dist/commands/core-native-jobs-flag.d.ts +4 -0
- package/dist/commands/core-native-jobs-flag.js +7 -0
- package/dist/commands/core-native-policy-flag.d.ts +9 -0
- package/dist/commands/core-native-policy-flag.js +12 -0
- package/dist/commands/core-native-session-flag.d.ts +4 -0
- package/dist/commands/core-native-session-flag.js +7 -0
- package/dist/commands/core-native-utilities-flag.d.ts +4 -0
- package/dist/commands/core-native-utilities-flag.js +7 -0
- package/dist/commands/core-native-utilities.d.ts +29 -0
- package/dist/commands/core-native-utilities.js +54 -0
- package/dist/commands/core-native-worker-skills-flag.d.ts +4 -0
- package/dist/commands/core-native-worker-skills-flag.js +7 -0
- package/dist/commands/core-ontology-native.d.ts +17 -0
- package/dist/commands/core-ontology-native.js +916 -0
- package/dist/commands/core-ontology.d.ts +66 -0
- package/dist/commands/core-ontology.js +430 -0
- package/dist/commands/core-pin-native.d.ts +14 -0
- package/dist/commands/core-pin-native.js +236 -0
- package/dist/commands/core-policy-frontmatter.d.ts +8 -0
- package/dist/commands/core-policy-frontmatter.js +52 -0
- package/dist/commands/core-policy.d.ts +14 -0
- package/dist/commands/core-policy.js +1018 -0
- package/dist/commands/core-refresh-vault-access-native.d.ts +20 -0
- package/dist/commands/core-refresh-vault-access-native.js +229 -0
- package/dist/commands/core-register-project-native.d.ts +30 -0
- package/dist/commands/core-register-project-native.js +347 -0
- package/dist/commands/core-resolve-company-command.d.ts +4 -0
- package/dist/commands/core-resolve-company-command.js +42 -0
- package/dist/commands/core-resolve-company-native.d.ts +28 -0
- package/dist/commands/core-resolve-company-native.js +408 -0
- package/dist/commands/core-work-mesh-bind-trusted-native.d.ts +32 -0
- package/dist/commands/core-work-mesh-bind-trusted-native.js +190 -0
- package/dist/commands/core-work-mesh-live-rebind-native.d.ts +18 -0
- package/dist/commands/core-work-mesh-live-rebind-native.js +262 -0
- package/dist/commands/core-worker-lint-native.d.ts +4 -0
- package/dist/commands/core-worker-lint-native.js +160 -0
- package/dist/commands/core-worker-share-native.d.ts +4 -0
- package/dist/commands/core-worker-share-native.js +260 -0
- package/dist/commands/core-worker-skills-command.d.ts +8 -0
- package/dist/commands/core-worker-skills-command.js +33 -0
- package/dist/commands/core-worker-skills-runtime.d.ts +33 -0
- package/dist/commands/core-worker-skills-runtime.js +86 -0
- package/dist/commands/core.d.ts +4 -0
- package/dist/commands/core.js +277 -80
- package/dist/commands/crm.js +52 -3
- package/dist/commands/daemon.d.ts +63 -0
- package/dist/commands/daemon.js +877 -0
- package/dist/commands/db-provision.js +4 -4
- package/dist/commands/db-sql.js +7 -21
- package/dist/commands/deploy-comments.d.ts +3 -0
- package/dist/commands/deploy-comments.js +162 -0
- package/dist/commands/dm.d.ts +119 -12
- package/dist/commands/dm.js +388 -38
- package/dist/commands/doctor.d.ts +12 -0
- package/dist/commands/doctor.js +158 -12
- package/dist/commands/feedback.d.ts +6 -0
- package/dist/commands/feedback.js +86 -37
- package/dist/commands/files-browse.d.ts +32 -12
- package/dist/commands/files-browse.js +176 -60
- package/dist/commands/files.d.ts +17 -0
- package/dist/commands/files.js +300 -47
- package/dist/commands/group-grants.js +3 -2
- package/dist/commands/groups.js +6 -5
- package/dist/commands/index-cmd.d.ts +19 -1
- package/dist/commands/index-cmd.js +74 -7
- package/dist/commands/integrations-api.d.ts +15 -10
- package/dist/commands/integrations-api.js +27 -22
- package/dist/commands/integrations-connect.d.ts +51 -13
- package/dist/commands/integrations-connect.js +86 -71
- package/dist/commands/integrations-core.d.ts +6 -6
- package/dist/commands/integrations-core.js +25 -11
- package/dist/commands/integrations-import.d.ts +1 -1
- package/dist/commands/integrations-import.js +43 -24
- package/dist/commands/integrations-manage.js +20 -13
- package/dist/commands/integrations.d.ts +8 -0
- package/dist/commands/integrations.js +94 -7
- package/dist/commands/lanes-accounts.d.ts +28 -0
- package/dist/commands/lanes-accounts.js +595 -0
- package/dist/commands/lanes-autopilot.d.ts +11 -0
- package/dist/commands/lanes-autopilot.js +336 -0
- package/dist/commands/lanes-checks.d.ts +51 -0
- package/dist/commands/lanes-checks.js +941 -0
- package/dist/commands/lanes-hold.d.ts +43 -0
- package/dist/commands/lanes-hold.js +655 -0
- package/dist/commands/lanes-keep-alive-audit.d.ts +10 -0
- package/dist/commands/lanes-keep-alive-audit.js +19 -0
- package/dist/commands/lanes-kpis.d.ts +11 -0
- package/dist/commands/lanes-kpis.js +34 -0
- package/dist/commands/lanes-message.d.ts +9 -0
- package/dist/commands/lanes-message.js +946 -0
- package/dist/commands/lanes-move.d.ts +12 -0
- package/dist/commands/lanes-move.js +397 -0
- package/dist/commands/lanes-questions.d.ts +36 -0
- package/dist/commands/lanes-questions.js +1005 -0
- package/dist/commands/lanes-report.d.ts +11 -0
- package/dist/commands/lanes-report.js +333 -0
- package/dist/commands/lanes-resume.d.ts +6 -0
- package/dist/commands/lanes-resume.js +564 -0
- package/dist/commands/lanes-review.d.ts +10 -0
- package/dist/commands/lanes-review.js +56 -0
- package/dist/commands/lanes-shared.d.ts +449 -0
- package/dist/commands/lanes-shared.js +170 -0
- package/dist/commands/lanes-stories.d.ts +15 -0
- package/dist/commands/lanes-stories.js +347 -0
- package/dist/commands/lanes-watch.d.ts +7 -0
- package/dist/commands/lanes-watch.js +216 -0
- package/dist/commands/lanes.d.ts +149 -0
- package/dist/commands/lanes.js +8983 -0
- package/dist/commands/login.js +49 -6
- package/dist/commands/mcp-registration.d.ts +32 -25
- package/dist/commands/mcp-registration.js +256 -23
- package/dist/commands/mcp-status.js +1 -0
- package/dist/commands/meetings.js +55 -34
- package/dist/commands/members.js +5 -1
- package/dist/commands/mesh.d.ts +11 -0
- package/dist/commands/mesh.js +1086 -76
- package/dist/commands/monitor.d.ts +58 -0
- package/dist/commands/monitor.js +980 -0
- package/dist/commands/onboard-bot-membership.d.ts +32 -0
- package/dist/commands/onboard-bot-membership.js +40 -0
- package/dist/commands/onboard-create-identity.d.ts +26 -0
- package/dist/commands/onboard-create-identity.js +23 -0
- package/dist/commands/onboard-identity-guard.d.ts +1 -1
- package/dist/commands/onboard.js +24 -5
- package/dist/commands/outpost-pruner.d.ts +3 -0
- package/dist/commands/outpost-pruner.js +125 -0
- package/dist/commands/pack-install.js +3 -1
- package/dist/commands/packs.js +11 -1
- package/dist/commands/people.d.ts +4 -0
- package/dist/commands/people.js +37 -3
- package/dist/commands/personal-connector-settings-flag.d.ts +4 -0
- package/dist/commands/personal-connector-settings-flag.js +7 -0
- package/dist/commands/personal-targeted-push-flag.d.ts +4 -0
- package/dist/commands/personal-targeted-push-flag.js +7 -0
- package/dist/commands/personal-targeted-push-paths.d.ts +3 -0
- package/dist/commands/personal-targeted-push-paths.js +67 -0
- package/dist/commands/publish.d.ts +8 -1
- package/dist/commands/publish.js +17 -4
- package/dist/commands/reindex.js +56 -6
- package/dist/commands/report.d.ts +19 -0
- package/dist/commands/report.js +537 -0
- package/dist/commands/rescue.d.ts +4 -0
- package/dist/commands/rescue.js +27 -17
- package/dist/commands/run-feature-telemetry.d.ts +3 -0
- package/dist/commands/run-feature-telemetry.js +6 -0
- package/dist/commands/run.js +18 -4
- package/dist/commands/safe-extract.d.ts +11 -2
- package/dist/commands/safe-extract.js +36 -8
- package/dist/commands/secrets.d.ts +20 -2
- package/dist/commands/secrets.js +175 -71
- package/dist/commands/skill.d.ts +35 -4
- package/dist/commands/skill.js +357 -18
- package/dist/commands/staff/reset-test-account.d.ts +110 -0
- package/dist/commands/staff/reset-test-account.js +268 -0
- package/dist/commands/staff/test-account.d.ts +51 -0
- package/dist/commands/staff/test-account.js +196 -0
- package/dist/commands/sync-mode.d.ts +11 -0
- package/dist/commands/sync-mode.js +30 -0
- package/dist/commands/sync-narrow.js +36 -5
- package/dist/commands/team-sync.js +71 -10
- package/dist/commands/update.js +6 -0
- package/dist/commands/vault-retention-flag.d.ts +3 -0
- package/dist/commands/vault-retention-flag.js +9 -0
- package/dist/commands/vault-retention.d.ts +11 -0
- package/dist/commands/vault-retention.js +69 -0
- package/dist/commands/version.d.ts +10 -0
- package/dist/commands/version.js +11 -0
- package/dist/commands/whoami.d.ts +22 -0
- package/dist/commands/whoami.js +137 -4
- package/dist/commands/workers.d.ts +2 -14
- package/dist/commands/workers.js +7 -10
- package/dist/fast-core.d.ts +6 -7
- package/dist/fast-core.js +87 -24
- package/dist/fast-scaffold.d.ts +86 -0
- package/dist/fast-scaffold.js +364 -0
- package/dist/index.d.ts +3 -4
- package/dist/index.js +59 -9
- package/dist/lanes-drain-hook.d.ts +14 -0
- package/dist/lanes-drain-hook.js +203 -0
- package/dist/lazy-commands.d.ts +20 -44
- package/dist/lazy-commands.js +60 -58
- package/dist/lib/agent-kit/adopt-identity.d.ts +27 -0
- package/dist/lib/agent-kit/adopt-identity.js +60 -0
- package/dist/lib/agent-kit/attachment-download.d.ts +24 -0
- package/dist/lib/agent-kit/attachment-download.js +47 -0
- package/dist/lib/agent-kit/creds.d.ts +106 -0
- package/dist/lib/agent-kit/creds.js +163 -0
- package/dist/lib/agent-kit/fallback.d.ts +78 -0
- package/dist/lib/agent-kit/fallback.js +210 -0
- package/dist/lib/agent-kit/inbox-state.d.ts +83 -0
- package/dist/lib/agent-kit/inbox-state.js +159 -0
- package/dist/lib/agent-kit/kit-config.d.ts +56 -0
- package/dist/lib/agent-kit/kit-config.js +65 -0
- package/dist/lib/agent-kit/log.d.ts +16 -0
- package/dist/lib/agent-kit/log.js +46 -0
- package/dist/lib/agent-kit/mcp/attachments.d.ts +126 -0
- package/dist/lib/agent-kit/mcp/attachments.js +222 -0
- package/dist/lib/agent-kit/mcp/jsonrpc.d.ts +75 -0
- package/dist/lib/agent-kit/mcp/jsonrpc.js +164 -0
- package/dist/lib/agent-kit/mcp/tools.d.ts +64 -0
- package/dist/lib/agent-kit/mcp/tools.js +397 -0
- package/dist/lib/agent-kit/paths.d.ts +65 -0
- package/dist/lib/agent-kit/paths.js +118 -0
- package/dist/lib/agent-kit/private-file.d.ts +29 -0
- package/dist/lib/agent-kit/private-file.js +91 -0
- package/dist/lib/agent-kit/run/heartbeat.d.ts +72 -0
- package/dist/lib/agent-kit/run/heartbeat.js +128 -0
- package/dist/lib/agent-kit/run/inbox.d.ts +76 -0
- package/dist/lib/agent-kit/run/inbox.js +205 -0
- package/dist/lib/agent-kit/run/supervisor.d.ts +35 -0
- package/dist/lib/agent-kit/run/supervisor.js +85 -0
- package/dist/lib/agent-kit/run/sync.d.ts +33 -0
- package/dist/lib/agent-kit/run/sync.js +58 -0
- package/dist/lib/agent-kit/services.d.ts +31 -0
- package/dist/lib/agent-kit/services.js +55 -0
- package/dist/lib/agent-kit/skills.d.ts +18 -0
- package/dist/lib/agent-kit/skills.js +151 -0
- package/dist/lib/agent-kit/wake.d.ts +83 -0
- package/dist/lib/agent-kit/wake.js +243 -0
- package/dist/lib/agents/model-catalog.d.ts +62 -0
- package/dist/lib/agents/model-catalog.js +275 -0
- package/dist/lib/auth-login-provider-state.d.ts +8 -0
- package/dist/lib/auth-login-provider-state.js +63 -0
- package/dist/lib/aws-sdk-node-warning-output.d.ts +12 -0
- package/dist/lib/aws-sdk-node-warning-output.js +22 -0
- package/dist/lib/billing/free-month.d.ts +18 -0
- package/dist/lib/billing/free-month.js +31 -0
- package/dist/lib/billing/plan-limit-refusals.d.ts +69 -0
- package/dist/lib/billing/plan-limit-refusals.js +187 -0
- package/dist/lib/billing/plan-limit-upgrade-url.d.ts +11 -0
- package/dist/lib/billing/plan-limit-upgrade-url.js +66 -0
- package/dist/lib/billing/plan-lock.d.ts +143 -0
- package/dist/lib/billing/plan-lock.js +439 -0
- package/dist/lib/bot/api.d.ts +349 -0
- package/dist/lib/bot/api.js +316 -0
- package/dist/lib/bot/attachments.d.ts +45 -0
- package/dist/lib/bot/attachments.js +89 -0
- package/dist/lib/bot/cli-update.d.ts +138 -0
- package/dist/lib/bot/cli-update.js +278 -0
- package/dist/lib/bot/company-bind.d.ts +27 -0
- package/dist/lib/bot/company-bind.js +62 -0
- package/dist/lib/bot/config.d.ts +153 -0
- package/dist/lib/bot/config.js +233 -0
- package/dist/lib/bot/continuity-download.d.ts +28 -0
- package/dist/lib/bot/continuity-download.js +75 -0
- package/dist/lib/bot/continuity-install.d.ts +14 -0
- package/dist/lib/bot/continuity-install.js +101 -0
- package/dist/lib/bot/continuity.d.ts +67 -0
- package/dist/lib/bot/continuity.js +301 -0
- package/dist/lib/bot/creds.d.ts +24 -0
- package/dist/lib/bot/creds.js +51 -0
- package/dist/lib/bot/daemon.d.ts +173 -0
- package/dist/lib/bot/daemon.js +552 -0
- package/dist/lib/bot/inbox-poll-backoff-flag.d.ts +24 -0
- package/dist/lib/bot/inbox-poll-backoff-flag.js +135 -0
- package/dist/lib/bot/inbox-state.d.ts +18 -0
- package/dist/lib/bot/inbox-state.js +51 -0
- package/dist/lib/bot/index.d.ts +23 -0
- package/dist/lib/bot/index.js +23 -0
- package/dist/lib/bot/inflight.d.ts +119 -0
- package/dist/lib/bot/inflight.js +83 -0
- package/dist/lib/bot/job-completions.d.ts +89 -0
- package/dist/lib/bot/job-completions.js +132 -0
- package/dist/lib/bot/job-run.d.ts +109 -0
- package/dist/lib/bot/job-run.js +246 -0
- package/dist/lib/bot/local-config.d.ts +61 -0
- package/dist/lib/bot/local-config.js +145 -0
- package/dist/lib/bot/local-jobs.d.ts +89 -0
- package/dist/lib/bot/local-jobs.js +459 -0
- package/dist/lib/bot/local-name.d.ts +54 -0
- package/dist/lib/bot/local-name.js +114 -0
- package/dist/lib/bot/log.d.ts +13 -0
- package/dist/lib/bot/log.js +59 -0
- package/dist/lib/bot/owner-context.d.ts +79 -0
- package/dist/lib/bot/owner-context.js +175 -0
- package/dist/lib/bot/paths.d.ts +76 -0
- package/dist/lib/bot/paths.js +131 -0
- package/dist/lib/bot/progress.d.ts +132 -0
- package/dist/lib/bot/progress.js +289 -0
- package/dist/lib/bot/promote.d.ts +16 -0
- package/dist/lib/bot/promote.js +106 -0
- package/dist/lib/bot/promotion-hold.d.ts +24 -0
- package/dist/lib/bot/promotion-hold.js +103 -0
- package/dist/lib/bot/promotion-receipt.d.ts +9 -0
- package/dist/lib/bot/promotion-receipt.js +56 -0
- package/dist/lib/bot/promotion-upload.d.ts +16 -0
- package/dist/lib/bot/promotion-upload.js +80 -0
- package/dist/lib/bot/prompt.d.ts +136 -0
- package/dist/lib/bot/prompt.js +433 -0
- package/dist/lib/bot/room-policy.d.ts +63 -0
- package/dist/lib/bot/room-policy.js +91 -0
- package/dist/lib/bot/run.d.ts +217 -0
- package/dist/lib/bot/run.js +1869 -0
- package/dist/lib/bot/runnable.d.ts +51 -0
- package/dist/lib/bot/runnable.js +65 -0
- package/dist/lib/bot/runtime/claude.d.ts +50 -0
- package/dist/lib/bot/runtime/claude.js +170 -0
- package/dist/lib/bot/runtime/codex.d.ts +28 -0
- package/dist/lib/bot/runtime/codex.js +153 -0
- package/dist/lib/bot/runtime/grok-acp.d.ts +40 -0
- package/dist/lib/bot/runtime/grok-acp.js +265 -0
- package/dist/lib/bot/runtime/grok.d.ts +41 -0
- package/dist/lib/bot/runtime/grok.js +169 -0
- package/dist/lib/bot/runtime/index.d.ts +36 -0
- package/dist/lib/bot/runtime/index.js +417 -0
- package/dist/lib/bot/runtime/messages-stream.d.ts +56 -0
- package/dist/lib/bot/runtime/messages-stream.js +194 -0
- package/dist/lib/bot/runtime/sanitize.d.ts +18 -0
- package/dist/lib/bot/runtime/sanitize.js +28 -0
- package/dist/lib/bot/runtime/types.d.ts +159 -0
- package/dist/lib/bot/runtime/types.js +51 -0
- package/dist/lib/bot/runtime/windows.d.ts +10 -0
- package/dist/lib/bot/runtime/windows.js +53 -0
- package/dist/lib/bot/runtime-sign-in.d.ts +24 -0
- package/dist/lib/bot/runtime-sign-in.js +31 -0
- package/dist/lib/bot/scaffold.d.ts +38 -0
- package/dist/lib/bot/scaffold.js +94 -0
- package/dist/lib/bot/self-heal.d.ts +52 -0
- package/dist/lib/bot/self-heal.js +79 -0
- package/dist/lib/bot/self-process.d.ts +19 -0
- package/dist/lib/bot/self-process.js +35 -0
- package/dist/lib/bot/session.d.ts +19 -0
- package/dist/lib/bot/session.js +39 -0
- package/dist/lib/bot/setup-explain.d.ts +64 -0
- package/dist/lib/bot/setup-explain.js +141 -0
- package/dist/lib/bot/split.d.ts +32 -0
- package/dist/lib/bot/split.js +241 -0
- package/dist/lib/bot/status.d.ts +57 -0
- package/dist/lib/bot/status.js +66 -0
- package/dist/lib/bot/worker-source.d.ts +66 -0
- package/dist/lib/bot/worker-source.js +283 -0
- package/dist/lib/checkpoint-ontology/aliases.d.ts +17 -0
- package/dist/lib/checkpoint-ontology/aliases.js +44 -0
- package/dist/lib/checkpoint-ontology/apply.d.ts +110 -0
- package/dist/lib/checkpoint-ontology/apply.js +777 -0
- package/dist/lib/checkpoint-ontology/company-brief.d.ts +18 -0
- package/dist/lib/checkpoint-ontology/company-brief.js +337 -0
- package/dist/lib/checkpoint-ontology/company.d.ts +44 -0
- package/dist/lib/checkpoint-ontology/company.js +118 -0
- package/dist/lib/checkpoint-ontology/drop-aliases.d.ts +41 -0
- package/dist/lib/checkpoint-ontology/drop-aliases.js +76 -0
- package/dist/lib/checkpoint-ontology/entity-file.d.ts +109 -0
- package/dist/lib/checkpoint-ontology/entity-file.js +371 -0
- package/dist/lib/checkpoint-ontology/identity.d.ts +38 -0
- package/dist/lib/checkpoint-ontology/identity.js +95 -0
- package/dist/lib/checkpoint-ontology/merge-copies.d.ts +199 -0
- package/dist/lib/checkpoint-ontology/merge-copies.js +1086 -0
- package/dist/lib/checkpoint-ontology/multi-writer.d.ts +101 -0
- package/dist/lib/checkpoint-ontology/multi-writer.js +394 -0
- package/dist/lib/checkpoint-ontology/plan.d.ts +193 -0
- package/dist/lib/checkpoint-ontology/plan.js +504 -0
- package/dist/lib/checkpoint-ontology/queued-touches.d.ts +29 -0
- package/dist/lib/checkpoint-ontology/queued-touches.js +193 -0
- package/dist/lib/checkpoint-ontology/sync-client.d.ts +38 -0
- package/dist/lib/checkpoint-ontology/sync-client.js +84 -0
- package/dist/lib/checkpoint-ontology/tree.d.ts +55 -0
- package/dist/lib/checkpoint-ontology/tree.js +198 -0
- package/dist/lib/company-errors-sdk-warning-flag.d.ts +5 -0
- package/dist/lib/company-errors-sdk-warning-flag.js +8 -0
- package/dist/lib/company-skill-wrapper.d.ts +1 -0
- package/dist/lib/company-skill-wrapper.js +121 -29
- package/dist/lib/core-utils/codex-skill-bridge.d.ts +2 -1
- package/dist/lib/core-utils/hook-timeout-debug-context-flag.d.ts +20 -0
- package/dist/lib/core-utils/hook-timeout-debug-context-flag.js +81 -0
- package/dist/lib/core-utils/jq-json-serializer.d.ts +5 -0
- package/dist/lib/core-utils/jq-json-serializer.js +10 -0
- package/dist/lib/core-utils/qmd-reindex-after-sync.d.ts +14 -1
- package/dist/lib/core-utils/qmd-reindex-after-sync.js +101 -17
- package/dist/lib/core-utils/secret-patterns.d.ts +43 -0
- package/dist/lib/core-utils/secret-patterns.js +34 -0
- package/dist/lib/core-utils/sentry-report.d.ts +114 -0
- package/dist/lib/core-utils/sentry-report.js +1055 -0
- package/dist/lib/core-utils/soft-timeout.js +1 -1
- package/dist/lib/core-utils/timeout-guard-native.d.ts +7 -0
- package/dist/lib/core-utils/timeout-guard-native.js +40 -0
- package/dist/lib/core-utils/timeout-guard.d.ts +3 -3
- package/dist/lib/core-utils/timeout-guard.js +28 -13
- package/dist/lib/core-utils/token-usage-report.js +17 -3
- package/dist/lib/core-utils/worktree-resume.d.ts +20 -0
- package/dist/lib/core-utils/worktree-resume.js +1198 -0
- package/dist/lib/daemon/catalog.d.ts +134 -0
- package/dist/lib/daemon/catalog.js +358 -0
- package/dist/lib/daemon/config.d.ts +35 -0
- package/dist/lib/daemon/config.js +108 -0
- package/dist/lib/daemon/flag.d.ts +25 -0
- package/dist/lib/daemon/flag.js +59 -0
- package/dist/lib/daemon/handoff.d.ts +56 -0
- package/dist/lib/daemon/handoff.js +161 -0
- package/dist/lib/daemon/hq-cloud.d.ts +79 -0
- package/dist/lib/daemon/hq-cloud.js +183 -0
- package/dist/lib/daemon/install.d.ts +85 -0
- package/dist/lib/daemon/install.js +320 -0
- package/dist/lib/daemon/legacy-sync-env.d.ts +10 -0
- package/dist/lib/daemon/legacy-sync-env.js +58 -0
- package/dist/lib/daemon/legacy.d.ts +45 -0
- package/dist/lib/daemon/legacy.js +133 -0
- package/dist/lib/daemon/lock.d.ts +47 -0
- package/dist/lib/daemon/lock.js +138 -0
- package/dist/lib/daemon/machine-type.d.ts +50 -0
- package/dist/lib/daemon/machine-type.js +159 -0
- package/dist/lib/daemon/migrate.d.ts +77 -0
- package/dist/lib/daemon/migrate.js +265 -0
- package/dist/lib/daemon/paths.d.ts +24 -0
- package/dist/lib/daemon/paths.js +43 -0
- package/dist/lib/daemon/protocol-tail.d.ts +33 -0
- package/dist/lib/daemon/protocol-tail.js +131 -0
- package/dist/lib/daemon/remote-control.d.ts +74 -0
- package/dist/lib/daemon/remote-control.js +153 -0
- package/dist/lib/daemon/requests.d.ts +28 -0
- package/dist/lib/daemon/requests.js +65 -0
- package/dist/lib/daemon/run.d.ts +79 -0
- package/dist/lib/daemon/run.js +755 -0
- package/dist/lib/daemon/succession.d.ts +118 -0
- package/dist/lib/daemon/succession.js +263 -0
- package/dist/lib/daemon/supervisor.d.ts +132 -0
- package/dist/lib/daemon/supervisor.js +836 -0
- package/dist/lib/daemon/sync-runner.d.ts +58 -0
- package/dist/lib/daemon/sync-runner.js +277 -0
- package/dist/lib/daemon/tools.d.ts +52 -0
- package/dist/lib/daemon/tools.js +95 -0
- package/dist/lib/daemon/updater.d.ts +57 -0
- package/dist/lib/daemon/updater.js +163 -0
- package/dist/lib/db/control-plane.js +6 -0
- package/dist/lib/db/local.d.ts +0 -5
- package/dist/lib/db/local.js +1 -1
- package/dist/lib/db/migrate.d.ts +0 -12
- package/dist/lib/db/migrate.js +5 -5
- package/dist/lib/db/remote-sql.d.ts +2 -2
- package/dist/lib/db/remote-sql.js +3 -3
- package/dist/lib/db/sql.d.ts +0 -10
- package/dist/lib/db/sql.js +4 -8
- package/dist/lib/deploy-api.d.ts +5 -0
- package/dist/lib/deploy-api.js +21 -0
- package/dist/lib/deploy-apps.d.ts +13 -0
- package/dist/lib/deploy-apps.js +29 -0
- package/dist/lib/doctor/__testing__/fake-hq-tree.d.ts +1 -1
- package/dist/lib/doctor/__testing__/fake-hq-tree.js +1 -1
- package/dist/lib/doctor/__testing__/run-doctor.d.ts +8 -0
- package/dist/lib/doctor/__testing__/run-doctor.js +52 -0
- package/dist/lib/doctor/checks/bots.d.ts +13 -0
- package/dist/lib/doctor/checks/bots.js +189 -0
- package/dist/lib/doctor/checks/claude-wiring.js +70 -1
- package/dist/lib/doctor/checks/codex-wiring.d.ts +0 -4
- package/dist/lib/doctor/checks/codex-wiring.js +3 -35
- package/dist/lib/doctor/checks/grok-wiring.js +1 -34
- package/dist/lib/doctor/checks/integrations.js +11 -0
- package/dist/lib/doctor/checks/runtime-health.d.ts +4 -17
- package/dist/lib/doctor/checks/runtime-health.js +20 -7
- package/dist/lib/doctor/checks/scaffold-cli-floor.d.ts +6 -0
- package/dist/lib/doctor/checks/scaffold-cli-floor.js +73 -0
- package/dist/lib/doctor/checks/sync-health.d.ts +21 -0
- package/dist/lib/doctor/checks/sync-health.js +72 -2
- package/dist/lib/doctor/checks/sync-runtime.d.ts +11 -0
- package/dist/lib/doctor/checks/sync-runtime.js +216 -0
- package/dist/lib/doctor/checks/work-context.js +20 -6
- package/dist/lib/doctor/client-health-flag-token.d.ts +15 -0
- package/dist/lib/doctor/client-health-flag-token.js +66 -0
- package/dist/lib/doctor/client-health-reason-codes-flag.d.ts +27 -0
- package/dist/lib/doctor/client-health-reason-codes-flag.js +106 -0
- package/dist/lib/doctor/compat.d.ts +0 -16
- package/dist/lib/doctor/compat.js +4 -3
- package/dist/lib/doctor/deep/effects.js +1 -3
- package/dist/lib/doctor/deep/executor.d.ts +4 -10
- package/dist/lib/doctor/deep/executor.js +1 -1
- package/dist/lib/doctor/deep/parity.d.ts +2 -29
- package/dist/lib/doctor/deep/parity.js +7 -7
- package/dist/lib/doctor/deep/sandbox.d.ts +7 -25
- package/dist/lib/doctor/deep/sandbox.js +5 -7
- package/dist/lib/doctor/fix/apply.d.ts +79 -8
- package/dist/lib/doctor/fix/apply.js +292 -80
- package/dist/lib/doctor/fix/json-output.d.ts +28 -0
- package/dist/lib/doctor/fix/json-output.js +33 -0
- package/dist/lib/doctor/fix/refresh-sync-outcome.d.ts +12 -0
- package/dist/lib/doctor/fix/refresh-sync-outcome.js +25 -0
- package/dist/lib/doctor/fix/remediation.d.ts +14 -6
- package/dist/lib/doctor/fix/remediation.js +49 -9
- package/dist/lib/doctor/fixtures/schema.js +1 -3
- package/dist/lib/doctor/json-output.d.ts +17 -1
- package/dist/lib/doctor/json-output.js +7 -2
- package/dist/lib/doctor/platform.d.ts +0 -26
- package/dist/lib/doctor/platform.js +5 -5
- package/dist/lib/doctor/registry.d.ts +14 -0
- package/dist/lib/doctor/registry.js +71 -0
- package/dist/lib/doctor/report.js +1 -1
- package/dist/lib/doctor/shared.d.ts +8 -0
- package/dist/lib/doctor/shared.js +38 -0
- package/dist/lib/doctor/stray-gate-entries.d.ts +35 -0
- package/dist/lib/doctor/stray-gate-entries.js +83 -0
- package/dist/lib/doctor/types.d.ts +5 -0
- package/dist/lib/doctor/types.js +20 -0
- package/dist/lib/first-sync-next-step.d.ts +34 -0
- package/dist/lib/first-sync-next-step.js +179 -0
- package/dist/lib/flag-registry-endpoint.d.ts +5 -0
- package/dist/lib/flag-registry-endpoint.js +9 -0
- package/dist/lib/flag-registry-worker-source.generated.d.ts +2 -0
- package/dist/lib/flag-registry-worker-source.generated.js +3 -0
- package/dist/lib/flag-registry-worker.js +12 -1
- package/dist/lib/flag-registry.d.ts +26 -6
- package/dist/lib/flag-registry.js +133 -16
- package/dist/lib/flag-snapshot-file.d.ts +20 -0
- package/dist/lib/flag-snapshot-file.js +91 -0
- package/dist/lib/heartbeat-identity.d.ts +30 -0
- package/dist/lib/heartbeat-identity.js +76 -0
- package/dist/lib/index-render/companies.d.ts +4 -1
- package/dist/lib/index-render/companies.js +66 -3
- package/dist/lib/index-render/company-knowledge.js +80 -17
- package/dist/lib/index-render/index.d.ts +1 -0
- package/dist/lib/index-render/index.js +2 -1
- package/dist/lib/index-render/projects.js +82 -5
- package/dist/lib/index-render/shared.d.ts +13 -1
- package/dist/lib/index-render/shared.js +41 -2
- package/dist/lib/lanes/account-home.d.ts +8 -0
- package/dist/lib/lanes/account-home.js +10 -0
- package/dist/lib/lanes/accounts.d.ts +117 -0
- package/dist/lib/lanes/accounts.js +366 -0
- package/dist/lib/lanes/admission-host.d.ts +22 -0
- package/dist/lib/lanes/admission-host.js +37 -0
- package/dist/lib/lanes/admission.d.ts +26 -0
- package/dist/lib/lanes/admission.js +210 -0
- package/dist/lib/lanes/approval-gate.d.ts +8 -0
- package/dist/lib/lanes/approval-gate.js +90 -0
- package/dist/lib/lanes/autopilot-metrics.d.ts +99 -0
- package/dist/lib/lanes/autopilot-metrics.js +301 -0
- package/dist/lib/lanes/autopilot-registry.d.ts +62 -0
- package/dist/lib/lanes/autopilot-registry.js +192 -0
- package/dist/lib/lanes/autopilot.d.ts +133 -0
- package/dist/lib/lanes/autopilot.js +1222 -0
- package/dist/lib/lanes/blocker-freshness.d.ts +31 -0
- package/dist/lib/lanes/blocker-freshness.js +168 -0
- package/dist/lib/lanes/checked-company-path.d.ts +3 -0
- package/dist/lib/lanes/checked-company-path.js +39 -0
- package/dist/lib/lanes/claude-balance.d.ts +97 -0
- package/dist/lib/lanes/claude-balance.js +319 -0
- package/dist/lib/lanes/compaction.d.ts +110 -0
- package/dist/lib/lanes/compaction.js +293 -0
- package/dist/lib/lanes/config.d.ts +94 -0
- package/dist/lib/lanes/config.js +606 -0
- package/dist/lib/lanes/constants.d.ts +3 -0
- package/dist/lib/lanes/constants.js +3 -0
- package/dist/lib/lanes/delivery-core.d.ts +15 -0
- package/dist/lib/lanes/delivery-core.js +21 -0
- package/dist/lib/lanes/delivery.d.ts +180 -0
- package/dist/lib/lanes/delivery.js +443 -0
- package/dist/lib/lanes/disk-guard.d.ts +66 -0
- package/dist/lib/lanes/disk-guard.js +172 -0
- package/dist/lib/lanes/dropbox.d.ts +129 -0
- package/dist/lib/lanes/dropbox.js +601 -0
- package/dist/lib/lanes/effort.d.ts +51 -0
- package/dist/lib/lanes/effort.js +113 -0
- package/dist/lib/lanes/envelope-repair-dispatch.d.ts +26 -0
- package/dist/lib/lanes/envelope-repair-dispatch.js +74 -0
- package/dist/lib/lanes/errors.d.ts +39 -0
- package/dist/lib/lanes/errors.js +72 -0
- package/dist/lib/lanes/flag-keys.d.ts +22 -0
- package/dist/lib/lanes/flag-keys.js +22 -0
- package/dist/lib/lanes/grants.d.ts +30 -0
- package/dist/lib/lanes/grants.js +171 -0
- package/dist/lib/lanes/hire-grant-dispatch.d.ts +29 -0
- package/dist/lib/lanes/hire-grant-dispatch.js +315 -0
- package/dist/lib/lanes/hooks-install.d.ts +82 -0
- package/dist/lib/lanes/hooks-install.js +479 -0
- package/dist/lib/lanes/host-capacity.d.ts +69 -0
- package/dist/lib/lanes/host-capacity.js +254 -0
- package/dist/lib/lanes/hq-root.d.ts +19 -0
- package/dist/lib/lanes/hq-root.js +28 -0
- package/dist/lib/lanes/index.d.ts +76 -0
- package/dist/lib/lanes/index.js +42 -0
- package/dist/lib/lanes/keep-alive-audit.d.ts +46 -0
- package/dist/lib/lanes/keep-alive-audit.js +370 -0
- package/dist/lib/lanes/keep-alive.d.ts +68 -0
- package/dist/lib/lanes/keep-alive.js +343 -0
- package/dist/lib/lanes/kr-freshness.d.ts +76 -0
- package/dist/lib/lanes/kr-freshness.js +413 -0
- package/dist/lib/lanes/lane-company.d.ts +10 -0
- package/dist/lib/lanes/lane-company.js +41 -0
- package/dist/lib/lanes/liveness.d.ts +108 -0
- package/dist/lib/lanes/liveness.js +396 -0
- package/dist/lib/lanes/machine-identity.d.ts +34 -0
- package/dist/lib/lanes/machine-identity.js +92 -0
- package/dist/lib/lanes/manager-status-gate.d.ts +17 -0
- package/dist/lib/lanes/manager-status-gate.js +133 -0
- package/dist/lib/lanes/merge-command.d.ts +15 -0
- package/dist/lib/lanes/merge-command.js +629 -0
- package/dist/lib/lanes/mesh-events.d.ts +127 -0
- package/dist/lib/lanes/mesh-events.js +564 -0
- package/dist/lib/lanes/message-replay.d.ts +21 -0
- package/dist/lib/lanes/message-replay.js +122 -0
- package/dist/lib/lanes/models.d.ts +86 -0
- package/dist/lib/lanes/models.js +234 -0
- package/dist/lib/lanes/monitor.d.ts +48 -0
- package/dist/lib/lanes/monitor.js +428 -0
- package/dist/lib/lanes/move-account.d.ts +56 -0
- package/dist/lib/lanes/move-account.js +193 -0
- package/dist/lib/lanes/orphans.d.ts +25 -0
- package/dist/lib/lanes/orphans.js +102 -0
- package/dist/lib/lanes/outpost-dispatch.d.ts +147 -0
- package/dist/lib/lanes/outpost-dispatch.js +271 -0
- package/dist/lib/lanes/outpost-hire-cache.d.ts +22 -0
- package/dist/lib/lanes/outpost-hire-cache.js +153 -0
- package/dist/lib/lanes/outpost-identity.d.ts +33 -0
- package/dist/lib/lanes/outpost-identity.js +59 -0
- package/dist/lib/lanes/outpost-profile-repin.d.ts +17 -0
- package/dist/lib/lanes/outpost-profile-repin.js +84 -0
- package/dist/lib/lanes/outpost-pruner-install.d.ts +37 -0
- package/dist/lib/lanes/outpost-pruner-install.js +238 -0
- package/dist/lib/lanes/outpost-registry-worker-approval.d.ts +18 -0
- package/dist/lib/lanes/outpost-registry-worker-approval.js +109 -0
- package/dist/lib/lanes/outpost-snapshot-memo.d.ts +6 -0
- package/dist/lib/lanes/outpost-snapshot-memo.js +14 -0
- package/dist/lib/lanes/outpost-worktree-pruner-flag.d.ts +19 -0
- package/dist/lib/lanes/outpost-worktree-pruner-flag.js +86 -0
- package/dist/lib/lanes/outpost-worktree-pruner.d.ts +62 -0
- package/dist/lib/lanes/outpost-worktree-pruner.js +1201 -0
- package/dist/lib/lanes/profile.d.ts +69 -0
- package/dist/lib/lanes/profile.js +552 -0
- package/dist/lib/lanes/provider-rejection.d.ts +86 -0
- package/dist/lib/lanes/provider-rejection.js +286 -0
- package/dist/lib/lanes/questions.d.ts +90 -0
- package/dist/lib/lanes/questions.js +384 -0
- package/dist/lib/lanes/quota.d.ts +134 -0
- package/dist/lib/lanes/quota.js +471 -0
- package/dist/lib/lanes/reconcile-install.d.ts +90 -0
- package/dist/lib/lanes/reconcile-install.js +257 -0
- package/dist/lib/lanes/reconcile.d.ts +161 -0
- package/dist/lib/lanes/reconcile.js +3343 -0
- package/dist/lib/lanes/registry-flag.d.ts +35 -0
- package/dist/lib/lanes/registry-flag.js +164 -0
- package/dist/lib/lanes/registry-persona.d.ts +20 -0
- package/dist/lib/lanes/registry-persona.js +329 -0
- package/dist/lib/lanes/registry.d.ts +122 -0
- package/dist/lib/lanes/registry.js +570 -0
- package/dist/lib/lanes/reviews.d.ts +38 -0
- package/dist/lib/lanes/reviews.js +395 -0
- package/dist/lib/lanes/runaway-tool-guard.d.ts +38 -0
- package/dist/lib/lanes/runaway-tool-guard.js +284 -0
- package/dist/lib/lanes/session-relink.d.ts +25 -0
- package/dist/lib/lanes/session-relink.js +216 -0
- package/dist/lib/lanes/spawn.d.ts +355 -0
- package/dist/lib/lanes/spawn.js +2668 -0
- package/dist/lib/lanes/ssm-command-context.d.ts +12 -0
- package/dist/lib/lanes/ssm-command-context.js +52 -0
- package/dist/lib/lanes/store.d.ts +91 -0
- package/dist/lib/lanes/store.js +1822 -0
- package/dist/lib/lanes/stories.d.ts +23 -0
- package/dist/lib/lanes/stories.js +92 -0
- package/dist/lib/lanes/story-ledger.d.ts +80 -0
- package/dist/lib/lanes/story-ledger.js +815 -0
- package/dist/lib/lanes/story-switch.d.ts +21 -0
- package/dist/lib/lanes/story-switch.js +75 -0
- package/dist/lib/lanes/text-scan.d.ts +11 -0
- package/dist/lib/lanes/text-scan.js +103 -0
- package/dist/lib/lanes/throttle.d.ts +116 -0
- package/dist/lib/lanes/throttle.js +437 -0
- package/dist/lib/lanes/timestamp.d.ts +3 -0
- package/dist/lib/lanes/timestamp.js +26 -0
- package/dist/lib/lanes/tracker-checks.d.ts +77 -0
- package/dist/lib/lanes/tracker-checks.js +134 -0
- package/dist/lib/lanes/tracker-report-state.d.ts +10 -0
- package/dist/lib/lanes/tracker-report-state.js +124 -0
- package/dist/lib/lanes/tracker-report.d.ts +203 -0
- package/dist/lib/lanes/tracker-report.js +522 -0
- package/dist/lib/lanes/tracker-store.d.ts +18 -0
- package/dist/lib/lanes/tracker-store.js +106 -0
- package/dist/lib/lanes/types.d.ts +1030 -0
- package/dist/lib/lanes/types.js +418 -0
- package/dist/lib/lanes/wait.d.ts +39 -0
- package/dist/lib/lanes/wait.js +159 -0
- package/dist/lib/lanes/watch.d.ts +39 -0
- package/dist/lib/lanes/watch.js +249 -0
- package/dist/lib/lanes/watcher-main.d.ts +11 -0
- package/dist/lib/lanes/watcher-main.js +108 -0
- package/dist/lib/lanes/watcher.d.ts +287 -0
- package/dist/lib/lanes/watcher.js +1972 -0
- package/dist/lib/mesh/api.d.ts +103 -10
- package/dist/lib/mesh/api.js +334 -64
- package/dist/lib/mesh/client.d.ts +3 -2
- package/dist/lib/mesh/client.js +26 -7
- package/dist/lib/mesh/denial-codes.d.ts +5 -0
- package/dist/lib/mesh/denial-codes.js +14 -0
- package/dist/lib/mesh/live/backfill-held.d.ts +67 -16
- package/dist/lib/mesh/live/backfill-held.js +415 -61
- package/dist/lib/mesh/live/backoff.d.ts +2 -2
- package/dist/lib/mesh/live/backoff.js +16 -3
- package/dist/lib/mesh/live/daemon/board-refresh.d.ts +7 -1
- package/dist/lib/mesh/live/daemon/board-refresh.js +78 -30
- package/dist/lib/mesh/live/daemon/config.d.ts +36 -0
- package/dist/lib/mesh/live/daemon/config.js +120 -0
- package/dist/lib/mesh/live/daemon/credentials.d.ts +64 -10
- package/dist/lib/mesh/live/daemon/credentials.js +270 -20
- package/dist/lib/mesh/live/daemon/doctor.d.ts +53 -0
- package/dist/lib/mesh/live/daemon/doctor.js +190 -6
- package/dist/lib/mesh/live/daemon/index.d.ts +13 -10
- package/dist/lib/mesh/live/daemon/index.js +7 -6
- package/dist/lib/mesh/live/daemon/install.d.ts +255 -1
- package/dist/lib/mesh/live/daemon/install.js +811 -13
- package/dist/lib/mesh/live/daemon/legacy-listen.d.ts +43 -0
- package/dist/lib/mesh/live/daemon/legacy-listen.js +89 -0
- package/dist/lib/mesh/live/daemon/live-cache.d.ts +26 -0
- package/dist/lib/mesh/live/daemon/live-cache.js +116 -0
- package/dist/lib/mesh/live/daemon/mqtt-events.d.ts +7 -0
- package/dist/lib/mesh/live/daemon/mqtt-events.js +2 -0
- package/dist/lib/mesh/live/daemon/mqtt-events.test-d.d.ts +2 -0
- package/dist/lib/mesh/live/daemon/mqtt-events.test-d.js +10 -0
- package/dist/lib/mesh/live/daemon/paths.d.ts +2 -0
- package/dist/lib/mesh/live/daemon/paths.js +2 -0
- package/dist/lib/mesh/live/daemon/presence.d.ts +24 -5
- package/dist/lib/mesh/live/daemon/presence.js +79 -10
- package/dist/lib/mesh/live/daemon/run.d.ts +48 -2
- package/dist/lib/mesh/live/daemon/run.js +485 -33
- package/dist/lib/mesh/live/daemon/state.d.ts +19 -2
- package/dist/lib/mesh/live/daemon/state.js +34 -2
- package/dist/lib/mesh/live/daemon/transcript-watch.d.ts +45 -9
- package/dist/lib/mesh/live/daemon/transcript-watch.js +227 -31
- package/dist/lib/mesh/live/daemon/version-drift.d.ts +5 -0
- package/dist/lib/mesh/live/daemon/version-drift.js +47 -0
- package/dist/lib/mesh/live/daemon/work-subscribe.d.ts +32 -0
- package/dist/lib/mesh/live/daemon/work-subscribe.js +262 -0
- package/dist/lib/mesh/live/emit-client.d.ts +8 -0
- package/dist/lib/mesh/live/emit-client.js +23 -8
- package/dist/lib/mesh/live/emit-evidence.d.ts +19 -0
- package/dist/lib/mesh/live/emit-evidence.js +20 -2
- package/dist/lib/mesh/live/emit.d.ts +9 -0
- package/dist/lib/mesh/live/emit.js +28 -2
- package/dist/lib/mesh/live/enqueue.d.ts +5 -0
- package/dist/lib/mesh/live/enqueue.js +19 -0
- package/dist/lib/mesh/live/flush.d.ts +17 -5
- package/dist/lib/mesh/live/flush.js +391 -38
- package/dist/lib/mesh/live/format-spool-line.d.ts +5 -2
- package/dist/lib/mesh/live/format-spool-line.js +10 -0
- package/dist/lib/mesh/live/paths.d.ts +2 -0
- package/dist/lib/mesh/live/paths.js +4 -0
- package/dist/lib/mesh/live/report-summary.d.ts +45 -0
- package/dist/lib/mesh/live/report-summary.js +234 -0
- package/dist/lib/mesh/live/session-event.schema.json +24 -1
- package/dist/lib/mesh/live/session-events-client.d.ts +2 -0
- package/dist/lib/mesh/live/session-events-client.js +26 -0
- package/dist/lib/mesh/live/session-seq.d.ts +4 -0
- package/dist/lib/mesh/live/session-seq.js +33 -0
- package/dist/lib/mesh/live/spool.d.ts +19 -0
- package/dist/lib/mesh/live/spool.js +269 -3
- package/dist/lib/mesh/live/validate-session-event.d.ts +1 -1
- package/dist/lib/mesh/live/validate-session-event.js +1 -1
- package/dist/lib/mesh/project-board.d.ts +65 -0
- package/dist/lib/mesh/project-board.js +181 -0
- package/dist/lib/mesh/story-status.d.ts +22 -0
- package/dist/lib/mesh/story-status.js +49 -0
- package/dist/lib/monitor.d.ts +149 -0
- package/dist/lib/monitor.js +519 -0
- package/dist/lib/node-binary.d.ts +50 -0
- package/dist/lib/node-binary.js +133 -0
- package/dist/lib/notify-recipient.d.ts +7 -0
- package/dist/lib/notify-recipient.js +11 -0
- package/dist/lib/onboarding/checkpoint.d.ts +13 -0
- package/dist/lib/onboarding/checkpoint.js +44 -0
- package/dist/lib/onboarding/cli/onboard.d.ts +53 -0
- package/dist/lib/onboarding/cli/onboard.js +135 -0
- package/dist/lib/onboarding/cli/prompts.d.ts +28 -0
- package/dist/lib/onboarding/cli/prompts.js +83 -0
- package/dist/lib/onboarding/errors.d.ts +34 -0
- package/dist/lib/onboarding/errors.js +59 -0
- package/dist/lib/onboarding/index.d.ts +13 -0
- package/dist/lib/onboarding/index.js +13 -0
- package/dist/lib/onboarding/orchestrator.d.ts +47 -0
- package/dist/lib/onboarding/orchestrator.js +569 -0
- package/dist/lib/onboarding/types.d.ts +79 -0
- package/dist/lib/onboarding/types.js +8 -0
- package/dist/lib/plan-limit-nag.d.ts +102 -6
- package/dist/lib/plan-limit-nag.js +348 -43
- package/dist/lib/policy-validator.d.ts +7 -0
- package/dist/lib/policy-validator.js +187 -0
- package/dist/lib/process-flag-company-context.d.ts +8 -0
- package/dist/lib/process-flag-company-context.js +67 -0
- package/dist/lib/report-publish.d.ts +175 -0
- package/dist/lib/report-publish.js +421 -0
- package/dist/lib/report-run.d.ts +62 -0
- package/dist/lib/report-run.js +123 -0
- package/dist/lib/report-snapshot.d.ts +12 -0
- package/dist/lib/report-snapshot.js +135 -0
- package/dist/lib/report-templates.d.ts +9 -0
- package/dist/lib/report-templates.js +21 -0
- package/dist/lib/scaffold-cli-floor.d.ts +8 -0
- package/dist/lib/scaffold-cli-floor.js +50 -0
- package/dist/lib/scan-packages/index.d.ts +2 -0
- package/dist/lib/scan-packages/index.js +168 -6
- package/dist/lib/search-index/background.d.ts +8 -1
- package/dist/lib/search-index/background.js +55 -200
- package/dist/lib/search-index/collection-path-repair-flag.d.ts +4 -0
- package/dist/lib/search-index/collection-path-repair-flag.js +7 -0
- package/dist/lib/search-index/embed-lock.d.ts +34 -0
- package/dist/lib/search-index/embed-lock.js +295 -0
- package/dist/lib/search-index/index.d.ts +12 -0
- package/dist/lib/search-index/index.js +153 -7
- package/dist/lib/search-index/load-gate.d.ts +57 -0
- package/dist/lib/search-index/load-gate.js +270 -0
- package/dist/lib/search-index/max-doc-bytes.d.ts +219 -0
- package/dist/lib/search-index/max-doc-bytes.js +463 -0
- package/dist/lib/search-index/qmd-exit-diagnostics.d.ts +5 -0
- package/dist/lib/search-index/qmd-exit-diagnostics.js +40 -0
- package/dist/lib/search-index/reindex-single-flight-flag.d.ts +5 -0
- package/dist/lib/search-index/reindex-single-flight-flag.js +8 -0
- package/dist/lib/service-manager/index.d.ts +42 -0
- package/dist/lib/service-manager/index.js +105 -0
- package/dist/lib/service-manager/launchd.d.ts +81 -0
- package/dist/lib/service-manager/launchd.js +212 -0
- package/dist/lib/service-manager/systemd.d.ts +19 -0
- package/dist/lib/service-manager/systemd.js +72 -0
- package/dist/lib/service-manager/types.d.ts +34 -0
- package/dist/lib/service-manager/types.js +26 -0
- package/dist/lib/test-utils/isolated-flag-company-context.d.ts +3 -0
- package/dist/lib/test-utils/isolated-flag-company-context.js +18 -0
- package/dist/lib/work-context/bind-best.d.ts +66 -0
- package/dist/lib/work-context/bind-best.js +344 -0
- package/dist/lib/work-context/bind-calling-session.d.ts +26 -0
- package/dist/lib/work-context/bind-calling-session.js +63 -0
- package/dist/lib/work-context/company-rebind.d.ts +23 -0
- package/dist/lib/work-context/company-rebind.js +59 -0
- package/dist/lib/work-context/company.d.ts +22 -20
- package/dist/lib/work-context/company.js +124 -40
- package/dist/lib/work-context/config.d.ts +58 -11
- package/dist/lib/work-context/config.js +334 -83
- package/dist/lib/work-context/contract.d.ts +13 -0
- package/dist/lib/work-context/contract.schema.json +3 -1
- package/dist/lib/work-context/default-company.d.ts +43 -0
- package/dist/lib/work-context/default-company.js +83 -0
- package/dist/lib/work-context/outbox-quarantine-status-flag.d.ts +4 -0
- package/dist/lib/work-context/outbox-quarantine-status-flag.js +7 -0
- package/dist/lib/work-context/outbox.d.ts +27 -5
- package/dist/lib/work-context/outbox.js +135 -6
- package/dist/lib/work-context/prd-sync.d.ts +56 -0
- package/dist/lib/work-context/prd-sync.js +197 -0
- package/dist/lib/work-context/preflight-contract.d.ts +28 -0
- package/dist/lib/work-context/preflight-contract.js +85 -0
- package/dist/lib/work-context/prompt-rebind.d.ts +25 -0
- package/dist/lib/work-context/prompt-rebind.js +85 -0
- package/dist/lib/work-context/reconcile.d.ts +15 -0
- package/dist/lib/work-context/reconcile.js +405 -29
- package/dist/lib/work-context/state.d.ts +93 -10
- package/dist/lib/work-context/state.js +342 -29
- package/dist/lib/work-context/windows-acl.d.ts +123 -0
- package/dist/lib/work-context/windows-acl.js +425 -0
- package/dist/lib/workers-registry/index.js +62 -8
- package/dist/lib/workers-registry/read.d.ts +15 -0
- package/dist/lib/workers-registry/read.js +17 -0
- package/dist/main.d.ts +32 -0
- package/dist/main.js +273 -73
- package/dist/native-fast-route.d.ts +7 -0
- package/dist/native-fast-route.js +45 -0
- package/dist/node-network-compat-core.d.ts +12 -0
- package/dist/node-network-compat-core.js +41 -0
- package/dist/node-network-compat-fast.d.ts +7 -0
- package/dist/node-network-compat-fast.js +23 -0
- package/dist/node-network-compat.d.ts +2 -30
- package/dist/node-network-compat.js +4 -51
- package/dist/node-preflight.d.ts +7 -1
- package/dist/node-preflight.js +5 -5
- package/dist/register-all.d.ts +4 -29
- package/dist/register-all.js +4 -233
- package/dist/run/discover-schemas.d.ts +2 -1
- package/dist/run/env-graph-guard.d.ts +2 -2
- package/dist/run/env-graph-guard.js +5 -2
- package/dist/run/hq-plugin.d.ts +4 -0
- package/dist/run/hq-plugin.js +36 -8
- package/dist/secrets-env-fast-path.d.ts +5 -0
- package/dist/secrets-env-fast-path.js +16 -0
- package/dist/secrets-env-fast-route.d.ts +18 -0
- package/dist/secrets-env-fast-route.js +54 -0
- package/dist/sentry-before-send.js +80 -0
- package/dist/sentry.d.ts +16 -4
- package/dist/sentry.js +179 -18
- package/dist/startup-registration.d.ts +26 -2
- package/dist/startup-registration.js +116 -15
- package/dist/utils/checkpoint-stamp-write-error.d.ts +28 -0
- package/dist/utils/checkpoint-stamp-write-error.js +99 -0
- package/dist/utils/cli-network-lifecycle.d.ts +4 -0
- package/dist/utils/cli-network-lifecycle.js +11 -0
- package/dist/utils/cli-telemetry.d.ts +75 -1
- package/dist/utils/cli-telemetry.js +433 -7
- package/dist/utils/client-health.d.ts +0 -4
- package/dist/utils/client-health.js +2 -2
- package/dist/utils/cognito-refresh-sentry.d.ts +3 -0
- package/dist/utils/cognito-refresh-sentry.js +49 -0
- package/dist/utils/cognito-session.d.ts +71 -0
- package/dist/utils/cognito-session.js +481 -27
- package/dist/utils/console-origin.d.ts +9 -0
- package/dist/utils/console-origin.js +28 -0
- package/dist/utils/contribution-table.d.ts +1 -1
- package/dist/utils/core-eval-trigger.d.ts +4 -0
- package/dist/utils/core-eval-trigger.js +88 -0
- package/dist/utils/environmental-error.d.ts +18 -0
- package/dist/utils/environmental-error.js +240 -7
- package/dist/utils/exclusive-lock.d.ts +32 -0
- package/dist/utils/exclusive-lock.js +75 -0
- package/dist/utils/expected-cli-error.d.ts +2 -0
- package/dist/utils/expected-cli-error.js +4 -0
- package/dist/utils/fast-stream-errors.d.ts +12 -0
- package/dist/utils/fast-stream-errors.js +18 -0
- package/dist/utils/feedback-diagnostics.d.ts +2 -1
- package/dist/utils/feedback-log-bundle.d.ts +51 -26
- package/dist/utils/feedback-log-bundle.js +211 -31
- package/dist/utils/feedback-logs.d.ts +18 -47
- package/dist/utils/feedback-logs.js +50 -21
- package/dist/utils/feedback-screenshots.d.ts +0 -1
- package/dist/utils/feedback-screenshots.js +1 -1
- package/dist/utils/files-browse-acl.d.ts +5 -0
- package/dist/utils/files-browse-acl.js +11 -0
- package/dist/utils/hq-core-yaml.d.ts +10 -0
- package/dist/utils/hq-core-yaml.js +40 -0
- package/dist/utils/hq-root-resolver.d.ts +13 -0
- package/dist/utils/hq-root-resolver.js +103 -0
- package/dist/utils/hq-roots.d.ts +25 -2
- package/dist/utils/hq-roots.js +67 -7
- package/dist/utils/incomplete-install-error.d.ts +87 -19
- package/dist/utils/incomplete-install-error.js +319 -68
- package/dist/utils/install-tree-torn.d.ts +260 -24
- package/dist/utils/install-tree-torn.js +998 -51
- package/dist/utils/large-file-guard.d.ts +0 -10
- package/dist/utils/large-file-guard.js +1 -1
- package/dist/utils/local-files-overview.d.ts +4 -8
- package/dist/utils/local-files-overview.js +48 -7
- package/dist/utils/login-provider.d.ts +7 -0
- package/dist/utils/login-provider.js +34 -0
- package/dist/utils/pack-contributions.d.ts +2 -14
- package/dist/utils/pack-contributions.js +7 -44
- package/dist/utils/package-root-diagnostics.d.ts +1 -1
- package/dist/utils/people.d.ts +7 -11
- package/dist/utils/people.js +5 -20
- package/dist/utils/plan-gate-error.d.ts +52 -10
- package/dist/utils/plan-gate-error.js +364 -41
- package/dist/utils/qmd-model-download-error.d.ts +0 -8
- package/dist/utils/qmd-model-download-error.js +1 -1
- package/dist/utils/qmd-query-document-error.d.ts +0 -6
- package/dist/utils/qmd-query-document-error.js +1 -1
- package/dist/utils/redact-error-text.d.ts +0 -2
- package/dist/utils/redact-error-text.js +3 -2
- package/dist/utils/redact-url.d.ts +14 -0
- package/dist/utils/redact-url.js +218 -0
- package/dist/utils/run-bundled-script.d.ts +6 -0
- package/dist/utils/run-bundled-script.js +117 -4
- package/dist/utils/sandbox-runner-client.d.ts +2 -1
- package/dist/utils/scaffold-fast-import-guard.d.ts +2 -0
- package/dist/utils/scaffold-fast-import-guard.js +40 -0
- package/dist/utils/scaffold-packlist-guard.d.ts +7 -0
- package/dist/utils/scaffold-packlist-guard.js +23 -0
- package/dist/utils/scaffold-sibling-references.d.ts +22 -0
- package/dist/utils/scaffold-sibling-references.js +156 -0
- package/dist/utils/secret-load-availability.d.ts +8 -0
- package/dist/utils/secret-load-availability.js +54 -0
- package/dist/utils/secrets-cache.d.ts +7 -3
- package/dist/utils/secrets-cache.js +162 -14
- package/dist/utils/self-update.d.ts +34 -1
- package/dist/utils/self-update.js +660 -42
- package/dist/utils/sentry-fingerprint.d.ts +23 -1
- package/dist/utils/sentry-fingerprint.js +223 -31
- package/dist/utils/staged-install.d.ts +34 -0
- package/dist/utils/staged-install.js +346 -0
- package/dist/utils/staged-npm-install.d.ts +16 -0
- package/dist/utils/staged-npm-install.js +264 -0
- package/dist/utils/sync-log-sources.d.ts +15 -0
- package/dist/utils/sync-log-sources.js +36 -0
- package/dist/utils/table-width.d.ts +7 -0
- package/dist/utils/table-width.js +15 -0
- package/dist/utils/team-upgrade.d.ts +1 -1
- package/dist/utils/team-upgrade.js +4 -3
- package/dist/utils/update-command-supervisor.cjs +247 -0
- package/dist/utils/upload-author.d.ts +15 -0
- package/dist/utils/upload-author.js +39 -0
- package/dist/utils/vault-api.d.ts +21 -1
- package/dist/utils/vault-api.js +251 -84
- package/dist/utils/version-check.d.ts +11 -1
- package/dist/utils/version-check.js +32 -3
- package/dist/utils/version-gate.d.ts +101 -17
- package/dist/utils/version-gate.js +645 -70
- package/dist/utils/version-report.d.ts +27 -0
- package/dist/utils/version-report.js +105 -0
- package/dist/utils/windows-spawn.d.ts +6 -0
- package/dist/utils/windows-spawn.js +37 -0
- package/dist/version-request.d.ts +14 -0
- package/dist/version-request.js +36 -0
- package/package.json +20 -8
- package/scripts/mesh-daemon-if-stale.mjs +20 -0
- package/templates/reports/blank.md +3 -0
- package/templates/reports/daily-digest.md +11 -0
- package/templates/runbooks/update-report.md +21 -0
- package/dist/commands/scaffold-fast.d.ts +0 -41
- package/dist/commands/scaffold-fast.js +0 -57
- package/dist/lib/work-context/index.d.ts +0 -25
- package/dist/lib/work-context/index.js +0 -21
|
@@ -0,0 +1,1982 @@
|
|
|
1
|
+
import { withLocalJobs } from "../lib/bot/local-jobs.js";
|
|
2
|
+
import { OwnerContextCache, lookupOwnerContext } from "../lib/bot/owner-context.js";
|
|
3
|
+
import { hasActivePromotion } from "../lib/bot/promotion-receipt.js";
|
|
4
|
+
import { readInflightTurns } from "../lib/bot/inflight.js";
|
|
5
|
+
import { hasPromotionHold, promotionHoldSummary } from "../lib/bot/promotion-hold.js";
|
|
6
|
+
/**
|
|
7
|
+
* `hq bot` — personal local bots (local-bots US-003/US-004/US-005).
|
|
8
|
+
*
|
|
9
|
+
* hq bot create <name> [--kind personal|company] [--company slug]... [--runtime claude|codex|grok] [--model m] [--effort level] [--worker id] [--intro text] [--kickoff prompt] [--memory synced|local] [--no-daemon]
|
|
10
|
+
* hq bot companies <name> [--add slug]... [--remove slug]... (a company bot's memberships)
|
|
11
|
+
* hq bot workers [--json] (company + core workers a bot can be created from)
|
|
12
|
+
* hq bot list [--remote] [--json]
|
|
13
|
+
* hq bot adopt <name> [--runtime r] [--model m] (a bot you own, back on this computer)
|
|
14
|
+
* hq bot restore [--all] [--dry-run] (every bot you own that is missing here)
|
|
15
|
+
* hq bot start|stop|restart <name>
|
|
16
|
+
* hq bot status <name> [--json]
|
|
17
|
+
* hq bot logs <name> [--follow] [-n N]
|
|
18
|
+
* hq bot run <name> (the resident process; launchd runs this)
|
|
19
|
+
* hq bot daemon install|uninstall|status <name>
|
|
20
|
+
* hq bot rm <name> [--yes]
|
|
21
|
+
* hq bot intro <name> (re-send the introduction DM)
|
|
22
|
+
* hq bot jobs <name> [--add "…" --every "…"] [--rm <jobId>] [--json]
|
|
23
|
+
*
|
|
24
|
+
* The bot's own identity (agt_) authenticates with 0600 machine creds under
|
|
25
|
+
* ~/.hq/bots/<name>/; the OWNER's cached session is used only for create,
|
|
26
|
+
* list (online flag), and rm. Secrets are never printed.
|
|
27
|
+
*/
|
|
28
|
+
import { promoteLocalBot } from "../lib/bot/promote.js";
|
|
29
|
+
import { CLI_UPDATE_DISABLE_ENV, createCliUpdateWatcher, handOffToUpdatedCli } from "../lib/bot/cli-update.js";
|
|
30
|
+
import { createVaultObjectDownloader } from "../lib/agent-kit/attachment-download.js";
|
|
31
|
+
import { addBotToCompanies, describeCompanyFailures, removeBotFromCompanies } from "./bot-companies.js";
|
|
32
|
+
import { registerBotContinuityCommand } from "./bot-continuity.js";
|
|
33
|
+
import { registerBotJobsCommand } from "./bot-jobs.js";
|
|
34
|
+
import chalk from "chalk";
|
|
35
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
36
|
+
import * as fs from "node:fs";
|
|
37
|
+
import * as os from "node:os";
|
|
38
|
+
import * as path from "node:path";
|
|
39
|
+
import * as readline from "node:readline";
|
|
40
|
+
import { DEFAULT_COGNITO, ensureCognitoToken, resolveDefaultHqRoot, } from "../utils/cognito-session.js";
|
|
41
|
+
import { peekHqApiKey } from "../utils/resolve-vault-credential.js";
|
|
42
|
+
import { getCompanyUid, resolveCallerPersonUid } from "../utils/vault-api.js";
|
|
43
|
+
import { resolveHqBinary } from "../lib/mesh/live/daemon/install.js";
|
|
44
|
+
import { askDaemonToStart, askDaemonToStop, daemonOwnsProcess, daemonSupervises, daemonWillSupervise, handOffInstallHere, } from "../lib/daemon/handoff.js";
|
|
45
|
+
import { daemonPaths } from "../lib/daemon/paths.js";
|
|
46
|
+
import { launchdLabel } from "../lib/bot/paths.js";
|
|
47
|
+
import { isAncestorProcess } from "../lib/bot/self-process.js";
|
|
48
|
+
import { runtimeDisplayName } from "../lib/bot/runtime-sign-in.js";
|
|
49
|
+
import { BotApi, BotApiError, BOT_RESTORE_DEFAULTS, buildBotLocalConfig, describeBotLocalConfig, orphanBotRunMessage, parseBotLocalConfig, removeOrphanBotDaemon, botDaemonStatus, botDir, botCredsPath, botLogPath, botsRoot, botTokenStateDir, botWorkerRelDir, buildBotDaemonPaths, createBotLogger, deleteBotCreds, installBotDaemon, repairBotDaemonIfStale, healBotDaemonNodeFromJob, introDmText, isBotMemoryMode, botLocalMemoryDir, botMemoryMode, resolveBotMemoryDir, validateBotIntro, validateBotDisplayName, validateBotKickoff, validateBotEffort, validateBotModel, effectiveBotEffort, BOT_EFFORT_LEVELS, DEFAULT_BOT_EFFORT, BOT_MEMORY_MODES, BOT_KINDS, isBotKind, effectiveBotKind, effectiveBotCompanies, normalizeBotCompanies, SETUP_BOT_WORKER_ID, isBotRuntimeId, localNameForRemoteBot, remoteBotRunnability, isPidAlive, listBotWorkerOptions, patchBotConfig, readBotConfig, readBotCredsIdentity, readBotStatus, resolveBotWorker, runBot, runtimeFor, scaffoldBotMemory, scaffoldBotWorker, startBotDaemon, stopBotDaemon, uninstallBotDaemon, validateBotName, writeBotConfig, writeBotCreds, BOT_RUNTIMES, BOT_INTRO_MAX_CHARS, BOT_DISPLAY_NAME_MAX_CHARS, BOT_KICKOFF_MAX_CHARS, detectBotPlatform, } from "../lib/bot/index.js";
|
|
50
|
+
function collectRepeated(value, previous) {
|
|
51
|
+
return [...previous, value];
|
|
52
|
+
}
|
|
53
|
+
function fail(message) {
|
|
54
|
+
console.error(chalk.red(message));
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
function wrap(action) {
|
|
58
|
+
return async () => {
|
|
59
|
+
try {
|
|
60
|
+
await action();
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** Real launchctl; every failure is a non-ok result, never a throw. */
|
|
68
|
+
export const defaultLaunchctl = (args) => {
|
|
69
|
+
try {
|
|
70
|
+
const stdout = execFileSync("launchctl", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
71
|
+
return { ok: true, stdout, stderr: "" };
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
const e = err;
|
|
75
|
+
return { ok: false, stdout: String(e.stdout ?? ""), stderr: String(e.stderr ?? e.message ?? "") };
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
/** The desktop app creates HQ's setup bot under this name, always from the `setup` worker. */
|
|
79
|
+
export const RESERVED_SETUP_BOT_NAME = "setup";
|
|
80
|
+
function daemonDeps() {
|
|
81
|
+
return process.platform === "darwin" ? { launchctl: defaultLaunchctl } : {};
|
|
82
|
+
}
|
|
83
|
+
/** Install the bot's LaunchAgent, unless `hq daemon` runs local bots on this machine. */
|
|
84
|
+
function installBotAgent(p) {
|
|
85
|
+
const handedOff = handOffInstallHere(["bots"]);
|
|
86
|
+
if (handedOff)
|
|
87
|
+
return { platform: detectBotPlatform(), action: "install", installed: true, loaded: true, message: handedOff };
|
|
88
|
+
return installBotDaemon(p, daemonDeps());
|
|
89
|
+
}
|
|
90
|
+
export function listBotNames(root) {
|
|
91
|
+
if (!fs.existsSync(root))
|
|
92
|
+
return [];
|
|
93
|
+
return fs
|
|
94
|
+
.readdirSync(root, { withFileTypes: true })
|
|
95
|
+
.filter((d) => d.isDirectory() && readBotConfig(path.join(root, d.name)) !== null)
|
|
96
|
+
.map((d) => d.name)
|
|
97
|
+
.sort();
|
|
98
|
+
}
|
|
99
|
+
function requireBot(name) {
|
|
100
|
+
const valid = validateBotName(name);
|
|
101
|
+
const dir = botDir(valid);
|
|
102
|
+
const config = readBotConfig(dir);
|
|
103
|
+
if (!config) {
|
|
104
|
+
// Never point at `create` alone: after a wiped ~/.hq the cloud still has
|
|
105
|
+
// the bot, so creating it refuses with a 409 and sends the person straight
|
|
106
|
+
// back here. Adopting is the move that works.
|
|
107
|
+
throw Object.assign(new Error(`No bot named "${valid}" is set up on this computer. Bring it back with: hq bot adopt ${valid} (or make a new one: hq bot create ${valid})`), { expected: true });
|
|
108
|
+
}
|
|
109
|
+
return { name: valid, dir, config };
|
|
110
|
+
}
|
|
111
|
+
/** Bot-identity token supplier: per-bot creds + per-bot token cache dir. */
|
|
112
|
+
export function botTokenSupplier(dir) {
|
|
113
|
+
process.env.HQ_MACHINE_CREDS_FILE = botCredsPath(dir);
|
|
114
|
+
process.env.HQ_MACHINE_TOKEN_STATE_DIR = botTokenStateDir(dir);
|
|
115
|
+
return () => ensureCognitoToken({ tokenSource: "machine", interactive: false });
|
|
116
|
+
}
|
|
117
|
+
async function ownerToken(interactive = process.stdin.isTTY === true) {
|
|
118
|
+
if (peekHqApiKey()) {
|
|
119
|
+
throw Object.assign(new Error("hq bot needs your HQ login session (HQ_API_KEY is not supported here). Run `hq login`."), {
|
|
120
|
+
expected: true,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
// Bots belong to a person, so `hq bot` always acts as the signed-in person,
|
|
124
|
+
// even when a bot runs it: a bot's own process exports its machine
|
|
125
|
+
// credentials, and a machine identity can own no bot ("No person entity
|
|
126
|
+
// found"), which left a setup bot unable to create its owner's first bot.
|
|
127
|
+
// Under the desktop app (no TTY) never open a browser login; the app owns
|
|
128
|
+
// sign-in and shares the same cached session file.
|
|
129
|
+
return ensureCognitoToken({ tokenSource: "person", interactive });
|
|
130
|
+
}
|
|
131
|
+
function rowFor(name, opts = {}) {
|
|
132
|
+
const dir = botDir(name);
|
|
133
|
+
const config = readBotConfig(dir);
|
|
134
|
+
const status = readBotStatus(dir);
|
|
135
|
+
const pid = status?.pid ?? null;
|
|
136
|
+
const processAlive = pid !== null && isPidAlive(pid) && status?.state === "running";
|
|
137
|
+
// Turns this bot has taken and not finished: what "thinking" means.
|
|
138
|
+
const inflightTurns = readInflightTurns(dir);
|
|
139
|
+
let daemonInstalled = false;
|
|
140
|
+
let daemonLoaded = false;
|
|
141
|
+
let daemonNodeMissing = null;
|
|
142
|
+
if (opts.withDaemon !== false) {
|
|
143
|
+
try {
|
|
144
|
+
const d = botDaemonStatus(buildBotDaemonPaths({ name, botDir: dir, hqRoot: config.hqRoot }), daemonDeps());
|
|
145
|
+
daemonInstalled = d.installed;
|
|
146
|
+
daemonLoaded = d.loaded;
|
|
147
|
+
if (d.nodeProblem === "missing" && d.nodeBinary)
|
|
148
|
+
daemonNodeMissing = d.nodeBinary;
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
/* hq binary unresolvable in odd environments — daemon status unknown */
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
name,
|
|
156
|
+
agentUid: config.agentUid,
|
|
157
|
+
ownerUid: config.ownerUid,
|
|
158
|
+
runtime: config.runtime,
|
|
159
|
+
model: config.model,
|
|
160
|
+
effort: effectiveBotEffort(config),
|
|
161
|
+
effortIsDefault: !config.effort,
|
|
162
|
+
state: processAlive ? "running" : status?.state === "failed" ? "failed" : "stopped",
|
|
163
|
+
pid: processAlive ? pid : null,
|
|
164
|
+
processAlive,
|
|
165
|
+
hosting: hasActivePromotion(dir, config.agentUid, config.ownerUid) ? "cloud" : "local",
|
|
166
|
+
promotionHold: promotionHoldSummary(dir, config.agentUid, config.ownerUid),
|
|
167
|
+
online: null,
|
|
168
|
+
lastHeartbeatAt: status?.lastHeartbeatAt ?? null,
|
|
169
|
+
lastHeartbeatOk: status?.lastHeartbeatOk ?? null,
|
|
170
|
+
lastInboxPollAt: status?.lastInboxPollAt ?? null,
|
|
171
|
+
lastInboxPollOk: status?.lastInboxPollOk ?? null,
|
|
172
|
+
busy: processAlive && inflightTurns.length > 0,
|
|
173
|
+
busySince: processAlive ? (inflightTurns[0]?.startedAt ?? null) : null,
|
|
174
|
+
runtimeSignIn: status?.runtimeSignIn ?? null,
|
|
175
|
+
daemonInstalled,
|
|
176
|
+
daemonLoaded,
|
|
177
|
+
daemonNodeMissing,
|
|
178
|
+
dir,
|
|
179
|
+
introSentAt: config.introSentAt,
|
|
180
|
+
kind: effectiveBotKind(config),
|
|
181
|
+
companies: effectiveBotCompanies(config),
|
|
182
|
+
workerSource: config.workerSource ?? "scaffold",
|
|
183
|
+
workerDir: config.workerDir,
|
|
184
|
+
...(config.workerId ? { workerId: config.workerId } : {}),
|
|
185
|
+
...(config.companySlug ? { companySlug: config.companySlug } : {}),
|
|
186
|
+
memory: botMemoryMode(config),
|
|
187
|
+
memoryDir: resolveBotMemoryDir(config),
|
|
188
|
+
...(config.displayName ? { displayName: config.displayName } : {}),
|
|
189
|
+
...(config.intro ? { intro: config.intro } : {}),
|
|
190
|
+
...(config.kickoff ? { kickoff: config.kickoff } : {}),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
async function decorateOnline(rows) {
|
|
194
|
+
if (rows.length === 0)
|
|
195
|
+
return;
|
|
196
|
+
let token;
|
|
197
|
+
try {
|
|
198
|
+
token = await ownerToken(false);
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const api = new BotApi({ token: async () => token });
|
|
204
|
+
await Promise.all(rows.map(async (row) => {
|
|
205
|
+
try {
|
|
206
|
+
const rec = await api.getAgent(row.agentUid);
|
|
207
|
+
row.online = rec.online === true;
|
|
208
|
+
row.lastHeartbeatAt = rec.runtime?.lastHeartbeat?.at ?? row.lastHeartbeatAt;
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
row.online = null;
|
|
212
|
+
}
|
|
213
|
+
}));
|
|
214
|
+
}
|
|
215
|
+
function humanOnline(row) {
|
|
216
|
+
if (row.lastHeartbeatOk === false)
|
|
217
|
+
return chalk.red("unreachable");
|
|
218
|
+
if (row.online === true)
|
|
219
|
+
return chalk.green("online");
|
|
220
|
+
if (row.online === false)
|
|
221
|
+
return chalk.gray("offline");
|
|
222
|
+
return chalk.gray("unknown");
|
|
223
|
+
}
|
|
224
|
+
/** Spawn `hq bot run <name>` detached (no launchd). */
|
|
225
|
+
export function spawnDetachedRun(name, dir) {
|
|
226
|
+
const entry = process.argv[1] && fs.existsSync(process.argv[1]) ? process.argv[1] : resolveHqBinary();
|
|
227
|
+
fs.mkdirSync(path.dirname(botLogPath(dir)), { recursive: true, mode: 0o700 });
|
|
228
|
+
const fd = fs.openSync(botLogPath(dir), "a");
|
|
229
|
+
const child = spawn(process.execPath, [entry, "bot", "run", name], {
|
|
230
|
+
detached: true,
|
|
231
|
+
windowsHide: true,
|
|
232
|
+
stdio: ["ignore", fd, fd],
|
|
233
|
+
env: { ...process.env, HQ_NO_UPDATE_CHECK: "1" },
|
|
234
|
+
});
|
|
235
|
+
fs.closeSync(fd);
|
|
236
|
+
child.unref();
|
|
237
|
+
if (!child.pid)
|
|
238
|
+
throw new Error("Unable to start the bot process");
|
|
239
|
+
return child.pid;
|
|
240
|
+
}
|
|
241
|
+
async function waitFor(pred, timeoutMs, stepMs = 250) {
|
|
242
|
+
const until = Date.now() + timeoutMs;
|
|
243
|
+
while (Date.now() < until) {
|
|
244
|
+
if (pred())
|
|
245
|
+
return true;
|
|
246
|
+
await new Promise((r) => setTimeout(r, stepMs));
|
|
247
|
+
}
|
|
248
|
+
return pred();
|
|
249
|
+
}
|
|
250
|
+
function stopProcess(dir) {
|
|
251
|
+
const status = readBotStatus(dir);
|
|
252
|
+
if (!status || !isPidAlive(status.pid))
|
|
253
|
+
return false;
|
|
254
|
+
try {
|
|
255
|
+
process.kill(status.pid, "SIGTERM");
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* `output`: "json" | "text" print the result; "silent" prints nothing — a
|
|
264
|
+
* restore brings several bots back and its own JSON must be the only document
|
|
265
|
+
* on stdout (the desktop app parses it whole).
|
|
266
|
+
*/
|
|
267
|
+
async function startBot(name, dir, config, output, extra = {}) {
|
|
268
|
+
if (hasPromotionHold(dir))
|
|
269
|
+
throw new Error("This bot is held for cloud promotion. Continue its promotion before changing local settings or lifecycle.");
|
|
270
|
+
if (config.enabled === false)
|
|
271
|
+
patchBotConfig(dir, { enabled: true });
|
|
272
|
+
const paths = buildBotDaemonPaths({ name, botDir: dir, hqRoot: config.hqRoot });
|
|
273
|
+
const d = botDaemonStatus(paths, daemonDeps());
|
|
274
|
+
const hqDaemon = daemonPaths();
|
|
275
|
+
let how;
|
|
276
|
+
// A bot created since the daemon last read its plan is the daemon's too,
|
|
277
|
+
// unless its own LaunchAgent runs it.
|
|
278
|
+
if (daemonSupervises(hqDaemon, `bots:${name}`) || (!d.installed && daemonWillSupervise(hqDaemon, `bots:${name}`))) {
|
|
279
|
+
// `hq daemon` runs this bot; starting a copy here would run it twice.
|
|
280
|
+
const before = readBotStatus(dir);
|
|
281
|
+
if (before && isPidAlive(before.pid) && before.state === "running") {
|
|
282
|
+
how = `already running under hq daemon (pid ${before.pid})`;
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
askDaemonToStart(hqDaemon, `bots:${name}`);
|
|
286
|
+
how = "started by hq daemon";
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
else if (d.installed && process.platform === "darwin") {
|
|
290
|
+
// A repair reloads the job; from inside one of the bot's own turns that
|
|
291
|
+
// bootout would kill the turn and this command with it.
|
|
292
|
+
const self = readBotStatus(dir);
|
|
293
|
+
if (!(self && isPidAlive(self.pid) && selfStopProbe(self.pid)))
|
|
294
|
+
repairBotDaemonIfStale(paths, daemonDeps());
|
|
295
|
+
const r = startBotDaemon(paths, daemonDeps());
|
|
296
|
+
how = r.message;
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
const before = readBotStatus(dir);
|
|
300
|
+
if (before && isPidAlive(before.pid) && before.state === "running") {
|
|
301
|
+
how = `already running (pid ${before.pid})`;
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
how = `started pid ${spawnDetachedRun(name, dir)}`;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
const running = await waitFor(() => {
|
|
308
|
+
const s = readBotStatus(dir);
|
|
309
|
+
return !!s && s.state === "running" && isPidAlive(s.pid);
|
|
310
|
+
}, 15_000);
|
|
311
|
+
const status = readBotStatus(dir);
|
|
312
|
+
if (output === "silent")
|
|
313
|
+
return { running, how };
|
|
314
|
+
if (output === "json") {
|
|
315
|
+
console.log(JSON.stringify({ ok: running, name, ...extra, how, state: status?.state ?? "unknown", pid: status?.pid ?? null, lastError: status?.lastError ?? null }, null, 2));
|
|
316
|
+
return { running, how };
|
|
317
|
+
}
|
|
318
|
+
if (running)
|
|
319
|
+
console.log(chalk.green(`${name} is running`) + chalk.dim(` (${how})`));
|
|
320
|
+
else {
|
|
321
|
+
console.log(chalk.yellow(`${name} did not report running within 15s`) + chalk.dim(` (${how})`));
|
|
322
|
+
if (status?.lastError)
|
|
323
|
+
console.log(chalk.dim(` last error: ${status.lastError}`));
|
|
324
|
+
console.log(chalk.dim(` logs: hq bot logs ${name}`));
|
|
325
|
+
}
|
|
326
|
+
return { running, how };
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* `output`: "json" | "text" print a result; "silent" prints nothing — used
|
|
330
|
+
* when stop is a step inside rm/restart, whose own JSON must be the only
|
|
331
|
+
* document on stdout (the desktop app parses it; a second object made it
|
|
332
|
+
* "unreadable output: trailing characters").
|
|
333
|
+
*/
|
|
334
|
+
/** Seams for the self-stop path, replaced in tests. */
|
|
335
|
+
export const botStopSeams = {
|
|
336
|
+
/** True when this process runs inside the bot process `botPid` (one of its turns). */
|
|
337
|
+
isInsideBot: (botPid) => isAncestorProcess(botPid),
|
|
338
|
+
/** Ask the bot to finish its running turns and exit 0 (the drain signal). */
|
|
339
|
+
signalDrain: (botPid) => {
|
|
340
|
+
try {
|
|
341
|
+
process.kill(botPid, "SIGUSR2");
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
catch {
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
};
|
|
349
|
+
function selfStopProbe(botPid) {
|
|
350
|
+
try {
|
|
351
|
+
return botStopSeams.isInsideBot(botPid);
|
|
352
|
+
}
|
|
353
|
+
catch {
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
function requestSelfStopDrain(botPid) {
|
|
358
|
+
return botStopSeams.signalDrain(botPid);
|
|
359
|
+
}
|
|
360
|
+
async function stopBot(name, dir, config, output) {
|
|
361
|
+
if (hasPromotionHold(dir))
|
|
362
|
+
throw new Error("This bot is held for cloud promotion. Continue its promotion before changing local settings or lifecycle.");
|
|
363
|
+
const paths = buildBotDaemonPaths({ name, botDir: dir, hqRoot: config.hqRoot });
|
|
364
|
+
const hqDaemon = daemonPaths();
|
|
365
|
+
const before = readBotStatus(dir);
|
|
366
|
+
const wasRunning = !!before && isPidAlive(before.pid);
|
|
367
|
+
const supervised = daemonSupervises(hqDaemon, `bots:${name}`);
|
|
368
|
+
// Stopping itself from inside one of its own turns: a bootout or a kill would
|
|
369
|
+
// take down this command and the turn with it, so the reply is lost and the
|
|
370
|
+
// job is left unloaded. Ask the bot to drain instead — finish the running
|
|
371
|
+
// turn (reply and ack included), then exit 0, which launchd's
|
|
372
|
+
// KeepAlive{SuccessfulExit:false} leaves down. `hq bot start` brings it back.
|
|
373
|
+
if (!supervised && wasRunning && selfStopProbe(before.pid)) {
|
|
374
|
+
const drained = requestSelfStopDrain(before.pid);
|
|
375
|
+
if (output === "silent")
|
|
376
|
+
return;
|
|
377
|
+
const how = drained ? "stops after this turn finishes" : "could not signal the bot";
|
|
378
|
+
if (output === "json") {
|
|
379
|
+
console.log(JSON.stringify({ ok: drained, name, signalled: drained, stopped: false, how, self: true }, null, 2));
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
console.log(drained
|
|
383
|
+
? chalk.green(`${name} will stop once this turn finishes`) + chalk.dim(` (stopping itself; start again with: hq bot start ${name})`)
|
|
384
|
+
: chalk.yellow(`${name} could not be asked to stop (pid ${before.pid})`));
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
// Keep the daemon's copy down either way, so it does not start one as soon as this bot is gone.
|
|
388
|
+
if (supervised)
|
|
389
|
+
askDaemonToStop(hqDaemon, `bots:${name}`);
|
|
390
|
+
// A bot started before the daemon is not the daemon's child; stop it directly below.
|
|
391
|
+
if (supervised && (!wasRunning || daemonOwnsProcess(hqDaemon, `bots:${name}`, before.pid))) {
|
|
392
|
+
// The daemon stops it (escalating to SIGKILL itself) and keeps it down; a
|
|
393
|
+
// kill from here would look like a crash and be restarted.
|
|
394
|
+
const gone = await waitFor(() => {
|
|
395
|
+
const s = readBotStatus(dir);
|
|
396
|
+
return !s || !isPidAlive(s.pid);
|
|
397
|
+
}, 20_000);
|
|
398
|
+
if (output === "silent")
|
|
399
|
+
return;
|
|
400
|
+
if (output === "json") {
|
|
401
|
+
console.log(JSON.stringify({ ok: gone, name, signalled: wasRunning, stopped: gone, how: "stopped by hq daemon" }, null, 2));
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
if (gone)
|
|
405
|
+
console.log(chalk.green(`${name} stopped`) + chalk.dim(" (by hq daemon)"));
|
|
406
|
+
else
|
|
407
|
+
console.log(chalk.yellow(`${name} did not stop within 20s`) + chalk.dim(` (logs: hq daemon logs bots:${name})`));
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
if (process.platform === "darwin")
|
|
411
|
+
stopBotDaemon(paths, daemonDeps());
|
|
412
|
+
const signalled = stopProcess(dir);
|
|
413
|
+
const gone = await waitFor(() => {
|
|
414
|
+
const s = readBotStatus(dir);
|
|
415
|
+
return !s || !isPidAlive(s.pid);
|
|
416
|
+
}, 10_000);
|
|
417
|
+
if (!gone) {
|
|
418
|
+
const s = readBotStatus(dir);
|
|
419
|
+
if (s) {
|
|
420
|
+
try {
|
|
421
|
+
process.kill(s.pid, "SIGKILL");
|
|
422
|
+
}
|
|
423
|
+
catch {
|
|
424
|
+
/* ignore */
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if (output === "silent")
|
|
429
|
+
return;
|
|
430
|
+
if (output === "json") {
|
|
431
|
+
console.log(JSON.stringify({ ok: true, name, signalled, stopped: gone }, null, 2));
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
console.log(signalled || gone ? chalk.green(`${name} stopped`) : chalk.dim(`${name} was not running`));
|
|
435
|
+
}
|
|
436
|
+
// ── create ───────────────────────────────────────────────────────────────────
|
|
437
|
+
/** "personal (acts as you)" / "company (acts as itself; member of a, b)". */
|
|
438
|
+
function describeKind(kind, companies) {
|
|
439
|
+
return kind === "company"
|
|
440
|
+
? `company (acts as itself; member of ${companies.length ? companies.join(", ") : "no company"})`
|
|
441
|
+
: "personal (acts as you)";
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* The kind a new bot gets, before anything is provisioned. Personal unless
|
|
445
|
+
* `--kind company` is given (the desktop app always passes --kind). A company
|
|
446
|
+
* bot needs at least one company: `--company`, or the company worker's own
|
|
447
|
+
* company when created from one. A personal bot takes no `--company`, and
|
|
448
|
+
* HQ's setup bot is always personal.
|
|
449
|
+
*/
|
|
450
|
+
export function resolveCreateKind(opts, worker) {
|
|
451
|
+
const given = opts.kind?.trim().toLowerCase();
|
|
452
|
+
if (given !== undefined && given !== "" && !isBotKind(given)) {
|
|
453
|
+
throw Object.assign(new Error(`--kind must be one of ${BOT_KINDS.join(", ")}`), { expected: true });
|
|
454
|
+
}
|
|
455
|
+
const requested = normalizeBotCompanies(opts.company);
|
|
456
|
+
const isSetup = worker?.id === SETUP_BOT_WORKER_ID;
|
|
457
|
+
const kind = isBotKind(given) ? given : "personal";
|
|
458
|
+
if (kind === "personal") {
|
|
459
|
+
if (requested.length > 0) {
|
|
460
|
+
throw Object.assign(new Error("--company only applies to a company bot (a personal bot acts as you, in every company you belong to). Add --kind company, or drop --company."), { expected: true });
|
|
461
|
+
}
|
|
462
|
+
return { kind, companies: [] };
|
|
463
|
+
}
|
|
464
|
+
if (isSetup) {
|
|
465
|
+
throw Object.assign(new Error("HQ's setup bot is always a personal bot; drop --kind company."), { expected: true });
|
|
466
|
+
}
|
|
467
|
+
const companies = requested.length > 0 ? requested : normalizeBotCompanies(worker?.company ? [worker.company] : []);
|
|
468
|
+
if (companies.length === 0) {
|
|
469
|
+
throw Object.assign(new Error("A company bot needs at least one company: add --company <slug> (repeat it for several), or create the bot from a company worker."), { expected: true });
|
|
470
|
+
}
|
|
471
|
+
return { kind, companies };
|
|
472
|
+
}
|
|
473
|
+
export async function runBotCreate(nameArg, opts) {
|
|
474
|
+
const name = validateBotName(nameArg);
|
|
475
|
+
const runtime = opts.runtime ?? "claude";
|
|
476
|
+
if (!isBotRuntimeId(runtime)) {
|
|
477
|
+
throw Object.assign(new Error(`--runtime must be one of ${BOT_RUNTIMES.join(", ")}`), { expected: true });
|
|
478
|
+
}
|
|
479
|
+
const memoryMode = opts.memory ?? "synced";
|
|
480
|
+
if (!isBotMemoryMode(memoryMode)) {
|
|
481
|
+
throw Object.assign(new Error(`--memory must be one of ${BOT_MEMORY_MODES.join(", ")}`), { expected: true });
|
|
482
|
+
}
|
|
483
|
+
const intro = opts.intro !== undefined ? validateBotIntro(opts.intro) : undefined;
|
|
484
|
+
const displayName = opts.displayName !== undefined ? validateBotDisplayName(opts.displayName) : undefined;
|
|
485
|
+
const kickoff = opts.kickoff !== undefined ? validateBotKickoff(opts.kickoff) : undefined;
|
|
486
|
+
const model = opts.model !== undefined ? validateBotModel(opts.model) : undefined;
|
|
487
|
+
const effort = opts.effort !== undefined ? validateBotEffort(runtime, opts.effort) : undefined;
|
|
488
|
+
const dir = botDir(name);
|
|
489
|
+
if (readBotConfig(dir)) {
|
|
490
|
+
throw Object.assign(new Error(`Bot "${name}" already exists (${dir}). Use hq bot start ${name} or hq bot rm ${name}.`), {
|
|
491
|
+
expected: true,
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
const hqRoot = resolveDefaultHqRoot({ onMissing: "throw" });
|
|
495
|
+
// Resolve the company worker before provisioning anything (a typo must not
|
|
496
|
+
// cost an identity).
|
|
497
|
+
const worker = opts.worker?.trim() ? resolveBotWorker(hqRoot, opts.worker) : null;
|
|
498
|
+
const { kind, companies } = resolveCreateKind(opts, worker);
|
|
499
|
+
const isSetupBot = name === RESERVED_SETUP_BOT_NAME && kind === "personal" && worker?.id === SETUP_BOT_WORKER_ID;
|
|
500
|
+
if (name === RESERVED_SETUP_BOT_NAME && kind === "personal" && !isSetupBot) {
|
|
501
|
+
throw Object.assign(new Error(`The name ${RESERVED_SETUP_BOT_NAME} is reserved for HQ's setup bot; create it with --worker ${RESERVED_SETUP_BOT_NAME}.`), { expected: true });
|
|
502
|
+
}
|
|
503
|
+
const token = await ownerToken();
|
|
504
|
+
const owner = await resolveCallerPersonUid(token);
|
|
505
|
+
if (!owner)
|
|
506
|
+
throw Object.assign(new Error("Your HQ login has no person entity yet; open the HQ desktop app once, then retry."), { expected: true });
|
|
507
|
+
const api = new BotApi({ token: async () => token });
|
|
508
|
+
const adoptOwnedSetupBot = async (allowUnsupportedListing) => {
|
|
509
|
+
let mine;
|
|
510
|
+
try {
|
|
511
|
+
mine = await api.listMine({ local: true });
|
|
512
|
+
}
|
|
513
|
+
catch (err) {
|
|
514
|
+
const failure = remoteBotsUnavailable(err);
|
|
515
|
+
if (allowUnsupportedListing && failure.reason === "server-unsupported")
|
|
516
|
+
return false;
|
|
517
|
+
if (opts.json)
|
|
518
|
+
console.log(JSON.stringify({ ok: false, ...failure }, null, 2));
|
|
519
|
+
throw Object.assign(new Error(failure.message), { expected: true });
|
|
520
|
+
}
|
|
521
|
+
const matches = matchRemoteBots(mine, name);
|
|
522
|
+
if (matches.length > 1) {
|
|
523
|
+
throw Object.assign(new Error(`You own more than one bot called "${name}": ${matches.map((bot) => bot.agentUid).join(", ")}. Name the one you mean by its id.`), { expected: true });
|
|
524
|
+
}
|
|
525
|
+
const setupSlug = `${RESERVED_SETUP_BOT_NAME}-${owner.slice(-8).toLowerCase()}`;
|
|
526
|
+
const slugMatches = mine.filter((bot) => (bot.slug ?? "").trim().toLowerCase() === setupSlug);
|
|
527
|
+
if (slugMatches.length > 1) {
|
|
528
|
+
throw Object.assign(new Error(`HQ returned more than one agent with the setup slug "${setupSlug}". The CLI cannot safely choose one; ask your HQ admin to inspect the duplicate records.`), { expected: true });
|
|
529
|
+
}
|
|
530
|
+
if (slugMatches.length === 0)
|
|
531
|
+
return false;
|
|
532
|
+
const remote = slugMatches[0];
|
|
533
|
+
if (!isReservedSetupBotRecord(remote, owner)) {
|
|
534
|
+
throw Object.assign(new Error(`HQ lists an agent with the setup slug "${setupSlug}", but its record does not identify it as your setup bot. The CLI cannot safely adopt it; check it with your HQ admin before retrying.`), { expected: true });
|
|
535
|
+
}
|
|
536
|
+
const { result, config } = await adoptRemoteBot({
|
|
537
|
+
bot: remote,
|
|
538
|
+
name,
|
|
539
|
+
api,
|
|
540
|
+
hqRoot,
|
|
541
|
+
overrides: { ...(model ? { model } : {}) },
|
|
542
|
+
daemon: opts.daemon !== false,
|
|
543
|
+
preserveFirstStart: true,
|
|
544
|
+
});
|
|
545
|
+
const recovered = {
|
|
546
|
+
agentUid: result.agentUid,
|
|
547
|
+
kind: effectiveBotKind(config),
|
|
548
|
+
companies: effectiveBotCompanies(config),
|
|
549
|
+
runtime: config.runtime,
|
|
550
|
+
dir: result.dir,
|
|
551
|
+
worker: config.workerSource === "worker" ? path.join(hqRoot, config.workerDir) : result.worker,
|
|
552
|
+
workerId: config.workerId ?? null,
|
|
553
|
+
companySlug: config.companySlug ?? null,
|
|
554
|
+
memory: botMemoryMode(config),
|
|
555
|
+
memoryDir: result.memoryDir,
|
|
556
|
+
displayName: config.displayName ?? null,
|
|
557
|
+
intro: config.intro ?? null,
|
|
558
|
+
kickoff: config.kickoff ?? null,
|
|
559
|
+
model: config.model ?? null,
|
|
560
|
+
effort: config.effort ?? DEFAULT_BOT_EFFORT,
|
|
561
|
+
};
|
|
562
|
+
if (!opts.json) {
|
|
563
|
+
console.log(chalk.green(`Created bot ${chalk.bold(name)}`) + chalk.dim(` (${result.agentUid}, ${result.runtime})`));
|
|
564
|
+
console.log(chalk.dim(` kind: ${describeKind(recovered.kind, recovered.companies)}`));
|
|
565
|
+
console.log(chalk.dim(` worker: ${result.worker}`));
|
|
566
|
+
console.log(chalk.dim(` memory: ${result.memoryDir} (${result.memory})`));
|
|
567
|
+
console.log(chalk.dim(` state: ${result.dir}`));
|
|
568
|
+
if (result.daemon)
|
|
569
|
+
console.log(chalk.dim(` ${result.daemon}`));
|
|
570
|
+
}
|
|
571
|
+
if (opts.start !== false) {
|
|
572
|
+
await startBot(name, result.dir, config, opts.json ? "json" : "text", { ...recovered, daemon: result.daemon });
|
|
573
|
+
return true;
|
|
574
|
+
}
|
|
575
|
+
if (opts.json)
|
|
576
|
+
console.log(JSON.stringify({ ok: true, name, ...recovered, daemon: result.daemon, running: false }, null, 2));
|
|
577
|
+
return true;
|
|
578
|
+
};
|
|
579
|
+
// HQ adds the owner suffix to the setup bot slug. If a create reached the
|
|
580
|
+
// server but its response was lost, recover the caller-owned bot instead
|
|
581
|
+
// of sending another create that the entity store will reject.
|
|
582
|
+
if (isSetupBot && (await adoptOwnedSetupBot(true)))
|
|
583
|
+
return;
|
|
584
|
+
// A company bot's companies are resolved first (a typo must not cost an
|
|
585
|
+
// identity) and sent with the create: the server makes the bot a member of
|
|
586
|
+
// each and refuses the whole create if the owner cannot add agents there.
|
|
587
|
+
const companyUids = [];
|
|
588
|
+
for (const slug of companies) {
|
|
589
|
+
try {
|
|
590
|
+
companyUids.push(await getCompanyUid(token, slug));
|
|
591
|
+
}
|
|
592
|
+
catch (err) {
|
|
593
|
+
throw Object.assign(new Error(`Cannot create ${name}: ${err instanceof Error ? err.message : String(err)} (company "${slug}").`), { expected: true });
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
// The bot's non-secret settings travel with its cloud identity, so a
|
|
597
|
+
// reinstall (or a second Mac) can rebuild the same bot with `hq bot adopt`
|
|
598
|
+
// instead of creating a new one. The machine secret never travels.
|
|
599
|
+
const localConfig = buildBotLocalConfig({
|
|
600
|
+
runtime,
|
|
601
|
+
kind,
|
|
602
|
+
...(kind === "company" ? { companies } : {}),
|
|
603
|
+
...(model ? { model } : {}),
|
|
604
|
+
...(effort ? { effort } : {}),
|
|
605
|
+
...(opts.autoApprove === false ? { autoApprove: false } : {}),
|
|
606
|
+
...(worker
|
|
607
|
+
? { workerSource: "worker", workerId: worker.id, ...(worker.company ? { companySlug: worker.company } : {}) }
|
|
608
|
+
: { workerSource: "scaffold" }),
|
|
609
|
+
...(memoryMode === "local" ? { memoryDir: botLocalMemoryDir(dir) } : {}),
|
|
610
|
+
...(displayName ? { displayName } : {}),
|
|
611
|
+
});
|
|
612
|
+
let created;
|
|
613
|
+
try {
|
|
614
|
+
created = await api.createLocalBot({
|
|
615
|
+
// HQ shows the entity's name, so a display name goes there. The slug is
|
|
616
|
+
// pinned to the handle the way the server would derive it, so every
|
|
617
|
+
// slug-keyed lookup (the reserved setup bot, adopt, restore) is unchanged.
|
|
618
|
+
name: displayName ?? name,
|
|
619
|
+
...(displayName ? { slug: `${name}-${owner.slice(-8).toLowerCase()}` } : {}),
|
|
620
|
+
kind,
|
|
621
|
+
...(kind === "company" ? { companyUid: companyUids[0], companyMemberships: companyUids.slice(1) } : {}),
|
|
622
|
+
localConfig,
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
catch (err) {
|
|
626
|
+
if (err instanceof BotApiError && err.code === "COMPANY_BOT_CREATE_NOT_ALLOWED") {
|
|
627
|
+
throw Object.assign(new Error(`The server refused to create ${name}: ${err.message} Only an owner or admin of each company can create a company bot there.`), {
|
|
628
|
+
expected: true,
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
if (err instanceof BotApiError && err.code === "LOCAL_BOT_CAP_REACHED") {
|
|
632
|
+
// Only an older server still enforces a cap; current hq-pro-agents has none.
|
|
633
|
+
throw Object.assign(new Error("The server refused another bot (its bot limit is reached). Remove one with hq bot rm <name>, or update the server."), {
|
|
634
|
+
expected: true,
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
if (err instanceof BotApiError && err.status === 409) {
|
|
638
|
+
if (isSetupBot) {
|
|
639
|
+
if (await adoptOwnedSetupBot(false))
|
|
640
|
+
return;
|
|
641
|
+
throw Object.assign(new Error(`HQ could not create the reserved ${name} bot. If you created it on another computer, bring it back with: ` +
|
|
642
|
+
`hq bot adopt ${name}. Otherwise, choose a different name or ask your HQ admin.`), { expected: true });
|
|
643
|
+
}
|
|
644
|
+
// The cloud already has this bot; it is simply not set up on this
|
|
645
|
+
// computer (a reinstall, or a second Mac). Re-creating it is the wrong
|
|
646
|
+
// move — adopting brings the existing one back, memory and all.
|
|
647
|
+
throw Object.assign(new Error(`You already have a bot named "${name}" in HQ — it is just not set up on this computer. ` +
|
|
648
|
+
`Bring it back with: hq bot adopt ${name} (or restore every bot you own: hq bot restore)`), { expected: true });
|
|
649
|
+
}
|
|
650
|
+
throw err;
|
|
651
|
+
}
|
|
652
|
+
const agentUid = created.agent.uid;
|
|
653
|
+
writeBotCreds(dir, {
|
|
654
|
+
username: created.identity.cognitoUsername,
|
|
655
|
+
secret: created.identity.secret,
|
|
656
|
+
entityType: "agent",
|
|
657
|
+
entityUid: agentUid,
|
|
658
|
+
clientId: DEFAULT_COGNITO.clientId,
|
|
659
|
+
region: DEFAULT_COGNITO.region,
|
|
660
|
+
});
|
|
661
|
+
const config = {
|
|
662
|
+
v: 1,
|
|
663
|
+
name,
|
|
664
|
+
agentUid,
|
|
665
|
+
ownerUid: created.agent.ownerUid ?? owner,
|
|
666
|
+
runtime,
|
|
667
|
+
kind,
|
|
668
|
+
// A company bot's list is filled in below, with the invites that went through.
|
|
669
|
+
...(kind === "company" ? { companies: [] } : {}),
|
|
670
|
+
...(model ? { model } : {}),
|
|
671
|
+
...(effort ? { effort } : {}),
|
|
672
|
+
...(opts.autoApprove === false ? { autoApprove: false } : {}),
|
|
673
|
+
hqRoot,
|
|
674
|
+
...(worker
|
|
675
|
+
? {
|
|
676
|
+
workerDir: worker.path,
|
|
677
|
+
workerSource: "worker",
|
|
678
|
+
workerId: worker.id,
|
|
679
|
+
...(worker.company ? { companySlug: worker.company } : {}),
|
|
680
|
+
}
|
|
681
|
+
: { workerDir: botWorkerRelDir(name) }),
|
|
682
|
+
// "local" memory is an absolute path outside the synced HQ tree; "synced"
|
|
683
|
+
// memory is hqRoot-relative (and, for a worker-sourced bot, the bot's own
|
|
684
|
+
// personal/workers/<name>/memory so the shared company tree is never
|
|
685
|
+
// written to).
|
|
686
|
+
...(memoryMode === "local"
|
|
687
|
+
? { memoryDir: botLocalMemoryDir(dir) }
|
|
688
|
+
: worker
|
|
689
|
+
? { memoryDir: path.join(botWorkerRelDir(name), "memory") }
|
|
690
|
+
: {}),
|
|
691
|
+
...(displayName ? { displayName } : {}),
|
|
692
|
+
...(intro ? { intro } : {}),
|
|
693
|
+
...(kickoff ? { kickoff } : {}),
|
|
694
|
+
createdAt: new Date().toISOString(),
|
|
695
|
+
enabled: true,
|
|
696
|
+
};
|
|
697
|
+
writeBotConfig(dir, config);
|
|
698
|
+
// A company bot acts as itself, so it must be a member of each of its
|
|
699
|
+
// companies. Only the companies it actually joined are recorded, so after a failed invite the identity
|
|
700
|
+
// and bot.json stay on disk and `hq bot companies <name> --add <slug>`
|
|
701
|
+
// retries exactly the missing ones (or `hq bot rm <name>` undoes it all).
|
|
702
|
+
if (kind === "company") {
|
|
703
|
+
// The server already made it a member of the companies it reports back;
|
|
704
|
+
// only the rest (an older server reports none) are invited here.
|
|
705
|
+
const joined = new Set(created.agent.companyMemberships ?? []);
|
|
706
|
+
const already = companies.filter((_, i) => joined.has(companyUids[i]));
|
|
707
|
+
const missing = companies.filter((_, i) => !joined.has(companyUids[i]));
|
|
708
|
+
const outcome = await addBotToCompanies({ agentUid, companies: missing, token });
|
|
709
|
+
config.companies = [...already, ...outcome.done];
|
|
710
|
+
writeBotConfig(dir, config);
|
|
711
|
+
if (outcome.failed.length > 0) {
|
|
712
|
+
createBotLogger(dir)("error", `created ${name} (${agentUid}) but could not add it to: ${describeCompanyFailures(outcome.failed)}`);
|
|
713
|
+
throw Object.assign(new Error(`Created the bot identity ${agentUid} but could not add it to ${outcome.failed.length === 1 ? "a company" : `${outcome.failed.length} companies`}: ${describeCompanyFailures(outcome.failed)}. ` +
|
|
714
|
+
`Fix the cause and run \`hq bot companies ${name} --add ${outcome.failed.map((f) => f.slug).join(" --add ")}\`, or remove the bot with \`hq bot rm ${name}\`.`), { expected: true });
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
// A worker-sourced bot never writes into the company tree: only its own
|
|
718
|
+
// memory folder is scaffolded.
|
|
719
|
+
const scaffoldOpts = { memoryDir: config.memoryDir };
|
|
720
|
+
const scaffold = worker
|
|
721
|
+
? scaffoldBotMemory(hqRoot, name, undefined, scaffoldOpts)
|
|
722
|
+
: scaffoldBotWorker(hqRoot, name, agentUid, runtime, undefined, scaffoldOpts);
|
|
723
|
+
createBotLogger(dir)("info", `created ${name} (${agentUid}) kind=${kind}${companies.length ? ` companies=${companies.join(",")}` : ""} runtime=${runtime}${worker ? ` worker=${worker.id} company=${worker.company ?? "-"}` : ""} memory=${memoryMode}`);
|
|
724
|
+
const created_ = {
|
|
725
|
+
agentUid,
|
|
726
|
+
kind,
|
|
727
|
+
companies,
|
|
728
|
+
runtime,
|
|
729
|
+
dir,
|
|
730
|
+
worker: worker ? path.join(hqRoot, worker.path) : scaffold.dir,
|
|
731
|
+
workerId: worker?.id ?? null,
|
|
732
|
+
companySlug: worker?.company ?? null,
|
|
733
|
+
memory: memoryMode,
|
|
734
|
+
memoryDir: scaffold.memoryDir,
|
|
735
|
+
displayName: displayName ?? null,
|
|
736
|
+
intro: intro ?? null,
|
|
737
|
+
kickoff: kickoff ?? null,
|
|
738
|
+
model: model ?? null,
|
|
739
|
+
effort: effort ?? DEFAULT_BOT_EFFORT,
|
|
740
|
+
};
|
|
741
|
+
let daemonMessage = null;
|
|
742
|
+
if (opts.daemon !== false && process.platform === "darwin") {
|
|
743
|
+
const r = installBotAgent(buildBotDaemonPaths({ name, botDir: dir, hqRoot }));
|
|
744
|
+
daemonMessage = r.message;
|
|
745
|
+
}
|
|
746
|
+
if (!opts.json) {
|
|
747
|
+
console.log(chalk.green(`Created bot ${chalk.bold(name)}`) + chalk.dim(` (${agentUid}, ${runtime})`));
|
|
748
|
+
console.log(chalk.dim(` kind: ${describeKind(kind, companies)}`));
|
|
749
|
+
if (worker)
|
|
750
|
+
console.log(chalk.dim(` worker: ${worker.id}${worker.company ? ` (${worker.company})` : ""} — ${path.join(hqRoot, worker.path)}`));
|
|
751
|
+
if (!worker)
|
|
752
|
+
console.log(chalk.dim(` worker: ${scaffold.dir}`));
|
|
753
|
+
console.log(chalk.dim(` memory: ${scaffold.memoryDir} (${memoryMode})`));
|
|
754
|
+
console.log(chalk.dim(` state: ${dir}`));
|
|
755
|
+
if (daemonMessage)
|
|
756
|
+
console.log(chalk.dim(` ${daemonMessage}`));
|
|
757
|
+
}
|
|
758
|
+
if (opts.start !== false) {
|
|
759
|
+
if (daemonMessage && /loaded\)/.test(daemonMessage)) {
|
|
760
|
+
// RunAtLoad already started it.
|
|
761
|
+
const running = await waitFor(() => readBotStatus(dir)?.state === "running", 20_000);
|
|
762
|
+
if (opts.json) {
|
|
763
|
+
console.log(JSON.stringify({ ok: true, name, ...created_, daemon: daemonMessage, running }, null, 2));
|
|
764
|
+
}
|
|
765
|
+
else if (running)
|
|
766
|
+
console.log(chalk.green(`${name} is online — it will DM you an introduction shortly.`));
|
|
767
|
+
else
|
|
768
|
+
console.log(chalk.yellow(`${name} has not reported running yet; check hq bot logs ${name}`));
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
await startBot(name, dir, config, opts.json ? "json" : "text", { ...created_, daemon: daemonMessage });
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
if (opts.json) {
|
|
775
|
+
console.log(JSON.stringify({ ok: true, name, ...created_, daemon: daemonMessage, running: false }, null, 2));
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
// ── companies ────────────────────────────────────────────────────────────────
|
|
779
|
+
export async function runBotCompanies(nameArg, opts) {
|
|
780
|
+
const { name, dir, config } = requireBot(nameArg);
|
|
781
|
+
const kind = effectiveBotKind(config);
|
|
782
|
+
const add = normalizeBotCompanies(opts.add);
|
|
783
|
+
const remove = normalizeBotCompanies(opts.remove);
|
|
784
|
+
if (kind !== "company") {
|
|
785
|
+
throw Object.assign(new Error(`${name} is a personal bot: it acts as you, in every company you belong to, so it has no company list of its own. Create a company bot with: hq bot create <name> --kind company --company <slug>`), { expected: true });
|
|
786
|
+
}
|
|
787
|
+
const current = effectiveBotCompanies(config);
|
|
788
|
+
const both = add.filter((c) => remove.includes(c));
|
|
789
|
+
if (both.length > 0) {
|
|
790
|
+
throw Object.assign(new Error(`--add and --remove name the same company: ${both.join(", ")}`), { expected: true });
|
|
791
|
+
}
|
|
792
|
+
const toAdd = add.filter((c) => !current.includes(c));
|
|
793
|
+
const toRemove = remove.filter((c) => current.includes(c));
|
|
794
|
+
const unknown = remove.filter((c) => !current.includes(c));
|
|
795
|
+
if (unknown.length > 0) {
|
|
796
|
+
throw Object.assign(new Error(`${name} is not in ${unknown.join(", ")} (its companies: ${current.join(", ") || "none"}).`), { expected: true });
|
|
797
|
+
}
|
|
798
|
+
const next = [...current.filter((c) => !toRemove.includes(c)), ...toAdd];
|
|
799
|
+
if (next.length === 0) {
|
|
800
|
+
throw Object.assign(new Error(`A company bot needs at least one company; ${name} would be left with none. Remove the bot instead with: hq bot rm ${name}`), { expected: true });
|
|
801
|
+
}
|
|
802
|
+
const failed = [];
|
|
803
|
+
let added = [];
|
|
804
|
+
let removed = [];
|
|
805
|
+
if (toAdd.length > 0 || toRemove.length > 0) {
|
|
806
|
+
const token = await ownerToken();
|
|
807
|
+
if (toAdd.length > 0) {
|
|
808
|
+
const r = await addBotToCompanies({ agentUid: config.agentUid, companies: toAdd, token });
|
|
809
|
+
added = r.done;
|
|
810
|
+
failed.push(...r.failed);
|
|
811
|
+
}
|
|
812
|
+
if (toRemove.length > 0) {
|
|
813
|
+
const r = await removeBotFromCompanies({ agentUid: config.agentUid, companies: toRemove, token });
|
|
814
|
+
removed = r.done;
|
|
815
|
+
failed.push(...r.failed);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
const companies = [...current.filter((c) => !removed.includes(c)), ...added];
|
|
819
|
+
if (added.length > 0 || removed.length > 0)
|
|
820
|
+
patchBotConfig(dir, { kind: "company", companies });
|
|
821
|
+
if (opts.json) {
|
|
822
|
+
console.log(JSON.stringify({ ok: failed.length === 0, name, kind, companies, added, removed, failed }, null, 2));
|
|
823
|
+
}
|
|
824
|
+
else {
|
|
825
|
+
console.log(`${chalk.bold(name)} company bot, member of: ${companies.join(", ")}`);
|
|
826
|
+
if (added.length > 0)
|
|
827
|
+
console.log(chalk.green(` added: ${added.join(", ")}`));
|
|
828
|
+
if (removed.length > 0)
|
|
829
|
+
console.log(chalk.green(` removed: ${removed.join(", ")}`));
|
|
830
|
+
if (companies.length > 0 && (added.length > 0 || removed.length > 0)) {
|
|
831
|
+
console.log(chalk.dim(` restart the bot so its instructions pick this up: hq bot restart ${name}`));
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
if (failed.length > 0) {
|
|
835
|
+
throw Object.assign(new Error(`Could not change ${name}'s membership in: ${describeCompanyFailures(failed)}`), { expected: true });
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
// ── workers ──────────────────────────────────────────────────────────────────
|
|
839
|
+
export async function runBotWorkers(opts) {
|
|
840
|
+
const hqRoot = resolveDefaultHqRoot({ onMissing: "throw" });
|
|
841
|
+
const workers = listBotWorkerOptions(hqRoot);
|
|
842
|
+
if (opts.json) {
|
|
843
|
+
console.log(JSON.stringify({ workers }, null, 2));
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
if (workers.length === 0) {
|
|
847
|
+
console.log(chalk.gray("No workers in this HQ (a worker needs a worker.yaml under companies/<co>/workers/<id>/ or core/workers/)."));
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
for (const w of workers) {
|
|
851
|
+
console.log(`${chalk.bold(w.id.padEnd(28))} ${(w.company ?? (w.source === "core" ? "core" : "-")).padEnd(14)} ${w.type.padEnd(12)} ${chalk.dim(w.summary.slice(0, 80))}`);
|
|
852
|
+
}
|
|
853
|
+
console.log(chalk.dim(`\nCreate a bot from one: hq bot create <name> --worker <id>`));
|
|
854
|
+
}
|
|
855
|
+
// ── run (resident) ───────────────────────────────────────────────────────────
|
|
856
|
+
async function runBotResident(nameArg) {
|
|
857
|
+
const name = validateBotName(nameArg);
|
|
858
|
+
const dir = botDir(name);
|
|
859
|
+
const config = readBotConfig(dir);
|
|
860
|
+
if (!config) {
|
|
861
|
+
// A LaunchAgent that outlived its bot: exiting non-zero here made launchd
|
|
862
|
+
// relaunch this every five seconds forever. Say so once, take the stale
|
|
863
|
+
// registration away, and exit 0 (KeepAlive{SuccessfulExit:false} leaves a
|
|
864
|
+
// clean exit alone).
|
|
865
|
+
//
|
|
866
|
+
// Under launchd this process IS the job, so the order is load-bearing: the
|
|
867
|
+
// line lands in bot.log and the plist is unlinked while we are still
|
|
868
|
+
// alive, and only then is the `bootout` — which kills us — asked for, in a
|
|
869
|
+
// detached session that outlives us. Printing after the removal, as this
|
|
870
|
+
// did, produced an empty log and a surviving plist on a real Mac.
|
|
871
|
+
let said = false;
|
|
872
|
+
const outcome = removeOrphanBotDaemon({
|
|
873
|
+
name,
|
|
874
|
+
botDir: dir,
|
|
875
|
+
announce: (o) => {
|
|
876
|
+
said = true;
|
|
877
|
+
console.log(orphanBotRunMessage(name, o));
|
|
878
|
+
},
|
|
879
|
+
}, daemonDeps());
|
|
880
|
+
if (!said)
|
|
881
|
+
console.log(orphanBotRunMessage(name, outcome));
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
if (healNodePathFromJob(name, dir, config))
|
|
885
|
+
return;
|
|
886
|
+
const token = botTokenSupplier(dir);
|
|
887
|
+
// The bot's own schedule lives in its folder and fires from this loop.
|
|
888
|
+
const api = withLocalJobs(new BotApi({ token }), dir);
|
|
889
|
+
const owner = new OwnerContextCache(() => lookupOwnerContext(config.ownerUid), Date.now, undefined, config.ownerUid);
|
|
890
|
+
const handle = await runBot({
|
|
891
|
+
dir,
|
|
892
|
+
config,
|
|
893
|
+
api,
|
|
894
|
+
runtime: runtimeFor(config.runtime),
|
|
895
|
+
ownerContext: () => owner.get(),
|
|
896
|
+
// Chat attachments are fetched with the bot's own machine identity against
|
|
897
|
+
// the same API its inbox and posts use (BotApi has no baseUrl override either).
|
|
898
|
+
downloadAttachment: createVaultObjectDownloader({ getToken: token }),
|
|
899
|
+
// Reload onto an updated hq-cli: the watched path is the script this
|
|
900
|
+
// process was launched with, which is what its supervisor launches next.
|
|
901
|
+
...(process.env[CLI_UPDATE_DISABLE_ENV] === "1"
|
|
902
|
+
? {}
|
|
903
|
+
: { cliUpdate: createCliUpdateWatcher({ dir, entry: process.argv[1] ?? null }) }),
|
|
904
|
+
onCliUpdateRestart: (_update, log) => {
|
|
905
|
+
handOffToUpdatedCli(name, log, { spawnReplacement: () => spawnDetachedRun(name, dir) });
|
|
906
|
+
},
|
|
907
|
+
});
|
|
908
|
+
await handle.done;
|
|
909
|
+
void name;
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* When this run IS the bot's launchd job and its plist names node by a
|
|
913
|
+
* versioned path (/opt/homebrew/Cellar/node/<v>/bin/node, removed by the next
|
|
914
|
+
* `brew upgrade node`), rewrite it to the stable alias and reload the job from
|
|
915
|
+
* a detached shell. Returns true when the caller should exit now (0, so
|
|
916
|
+
* KeepAlive leaves it alone) and let the reloaded job take over.
|
|
917
|
+
*/
|
|
918
|
+
function healNodePathFromJob(name, dir, config) {
|
|
919
|
+
// Only the job's own main process (launchd, pid 1, is its parent): a copy
|
|
920
|
+
// spawned by the bot inherits XPC_SERVICE_NAME but is not the job.
|
|
921
|
+
if (process.platform !== "darwin" || process.env.XPC_SERVICE_NAME !== launchdLabel(name) || process.ppid !== 1)
|
|
922
|
+
return false;
|
|
923
|
+
try {
|
|
924
|
+
const healed = healBotDaemonNodeFromJob(buildBotDaemonPaths({ name, botDir: dir, hqRoot: config.hqRoot }), daemonDeps());
|
|
925
|
+
if (!healed)
|
|
926
|
+
return false;
|
|
927
|
+
createBotLogger(dir)("info", `startup agent ran node from ${healed.from}, which the next node upgrade removes; rewrote it to ${healed.to} and reloading`);
|
|
928
|
+
return true;
|
|
929
|
+
}
|
|
930
|
+
catch (err) {
|
|
931
|
+
createBotLogger(dir)("warn", `could not rewrite the startup agent's node path: ${err instanceof Error ? err.message : String(err)}`);
|
|
932
|
+
return false;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
// ── list / status / logs ─────────────────────────────────────────────────────
|
|
936
|
+
async function runBotList(opts) {
|
|
937
|
+
if (opts.remote) {
|
|
938
|
+
await runBotListRemote(opts);
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
const rows = listBotNames(botsRoot()).map((n) => rowFor(n));
|
|
942
|
+
await decorateOnline(rows);
|
|
943
|
+
if (opts.json) {
|
|
944
|
+
console.log(JSON.stringify({ bots: rows }, null, 2));
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
if (rows.length === 0) {
|
|
948
|
+
console.log(chalk.gray("No local bots yet. Create one with: hq bot create assistant"));
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
for (const r of rows) {
|
|
952
|
+
console.log(`${chalk.bold(r.name.padEnd(16))} ${r.agentUid} ${r.runtime.padEnd(6)} ${humanOnline(r).padEnd(8)} ${r.processAlive ? chalk.green(`pid ${r.pid}`) : chalk.gray(r.state)}${chalk.dim(` ${r.kind === "company" ? `company bot: ${r.companies.join(", ") || "no company"}` : "personal"}`)}${r.daemonInstalled ? chalk.dim(" launchd") : ""}${r.daemonNodeMissing ? chalk.red(` node missing (hq bot start ${r.name})`) : ""}${r.workerId ? chalk.dim(` worker ${r.workerId}${r.companySlug ? ` (${r.companySlug})` : ""}`) : ""}`);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
async function runBotStatus(nameArg, opts) {
|
|
956
|
+
const { name, dir } = requireBot(nameArg);
|
|
957
|
+
const row = rowFor(name);
|
|
958
|
+
await decorateOnline([row]);
|
|
959
|
+
const status = readBotStatus(dir);
|
|
960
|
+
const creds = readBotCredsIdentity(dir);
|
|
961
|
+
if (opts.json) {
|
|
962
|
+
console.log(JSON.stringify({ ...row, credsPresent: !!creds, status }, null, 2));
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
console.log(`${chalk.bold(name)} ${row.agentUid}`);
|
|
966
|
+
console.log(` kind: ${describeKind(row.kind, row.companies)}`);
|
|
967
|
+
console.log(` runtime: ${row.runtime} model ${row.model ?? "default"} thinking ${row.effort}${row.effortIsDefault ? " (default)" : ""}`);
|
|
968
|
+
if (row.workerId)
|
|
969
|
+
console.log(` worker: ${row.workerId}${row.companySlug ? ` (company ${row.companySlug})` : ""} ${chalk.dim(row.workerDir)}`);
|
|
970
|
+
console.log(` memory: ${row.memory} ${chalk.dim(row.memoryDir)}`);
|
|
971
|
+
console.log(` process: ${row.processAlive ? chalk.green(`running (pid ${row.pid})`) : chalk.gray(row.state)}`);
|
|
972
|
+
console.log(` HQ online: ${humanOnline(row)}${row.lastHeartbeatAt ? chalk.dim(` last heartbeat ${row.lastHeartbeatAt}`) : ""}${row.lastHeartbeatOk === false && status?.lastHeartbeatError ? chalk.dim(` (${status.lastHeartbeatError})`) : ""}`);
|
|
973
|
+
if (status?.lastInboxPollOk === false) {
|
|
974
|
+
console.log(` inbox: ${chalk.red("cannot receive")}${status.lastInboxError ? chalk.dim(` ${status.lastInboxError}`) : ""}`);
|
|
975
|
+
}
|
|
976
|
+
else if (status?.lastInboxPollAt) {
|
|
977
|
+
console.log(` inbox: last poll ${status.lastInboxPollAt}`);
|
|
978
|
+
}
|
|
979
|
+
console.log(` answering: ${row.busy ? chalk.green(`yes${row.busySince ? `, since ${row.busySince}` : ""}`) : chalk.gray("no")}`);
|
|
980
|
+
console.log(` launchd: ${row.daemonInstalled ? (row.daemonLoaded ? "installed, loaded" : "installed, not loaded") : "not installed"}`);
|
|
981
|
+
if (row.daemonNodeMissing) {
|
|
982
|
+
console.log(` ${chalk.red(`node missing: ${row.daemonNodeMissing}`)} — launchd cannot start ${name}; fix with: hq bot start ${name}`);
|
|
983
|
+
}
|
|
984
|
+
console.log(` creds: ${creds ? `present for ${creds.entityUid}` : chalk.red("missing")}`);
|
|
985
|
+
if (status) {
|
|
986
|
+
console.log(` started: ${status.startedAt} restarts: ${status.restarts} replies: ${status.repliesSent}`);
|
|
987
|
+
if (status.cliVersion)
|
|
988
|
+
console.log(` hq-cli: ${status.cliVersion}`);
|
|
989
|
+
if (status.runtimeSignIn?.state === "expired") {
|
|
990
|
+
const tool = runtimeDisplayName(status.runtimeSignIn.runtime);
|
|
991
|
+
console.log(` ${chalk.yellow(`${tool} needs to sign in again`)} — sign in from HQ, or run \`${status.runtimeSignIn.runtime} ${status.runtimeSignIn.runtime === "claude" ? "auth login" : "login"}\`, then \`hq bot restart ${name}\``);
|
|
992
|
+
}
|
|
993
|
+
if (status.lastError)
|
|
994
|
+
console.log(` last error: ${chalk.yellow(status.lastError)}`);
|
|
995
|
+
}
|
|
996
|
+
console.log(chalk.dim(` state dir: ${dir}`));
|
|
997
|
+
}
|
|
998
|
+
async function runBotLogs(nameArg, opts) {
|
|
999
|
+
const { dir } = requireBot(nameArg);
|
|
1000
|
+
const file = botLogPath(dir);
|
|
1001
|
+
const n = Math.max(1, Number.parseInt(opts.lines ?? "50", 10) || 50);
|
|
1002
|
+
if (!fs.existsSync(file)) {
|
|
1003
|
+
console.log(chalk.gray("No log yet."));
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
const initial = fs.readFileSync(file, "utf8").split("\n").filter(Boolean);
|
|
1007
|
+
for (const line of initial.slice(-n))
|
|
1008
|
+
console.log(line);
|
|
1009
|
+
if (!opts.follow)
|
|
1010
|
+
return;
|
|
1011
|
+
let size = fs.statSync(file).size;
|
|
1012
|
+
await new Promise(() => {
|
|
1013
|
+
fs.watchFile(file, { interval: 500 }, (curr) => {
|
|
1014
|
+
if (curr.size < size)
|
|
1015
|
+
size = 0;
|
|
1016
|
+
if (curr.size > size) {
|
|
1017
|
+
const fd = fs.openSync(file, "r");
|
|
1018
|
+
const buf = Buffer.alloc(curr.size - size);
|
|
1019
|
+
fs.readSync(fd, buf, 0, buf.length, size);
|
|
1020
|
+
fs.closeSync(fd);
|
|
1021
|
+
process.stdout.write(buf.toString());
|
|
1022
|
+
size = curr.size;
|
|
1023
|
+
}
|
|
1024
|
+
});
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
// ── rm ───────────────────────────────────────────────────────────────────────
|
|
1028
|
+
async function confirm(question) {
|
|
1029
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1030
|
+
return new Promise((resolve) => {
|
|
1031
|
+
rl.question(question, (answer) => {
|
|
1032
|
+
rl.close();
|
|
1033
|
+
resolve(/^y(es)?$/i.test(answer.trim()));
|
|
1034
|
+
});
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* `hq bot rm <name>` for a bot whose bot.json is gone — a wiped ~/.hq, a
|
|
1039
|
+
* half-finished reinstall, a folder deleted by hand. Without this the
|
|
1040
|
+
* leftovers were unreachable: `rm` refused because there was no config, and
|
|
1041
|
+
* the message it gave pointed at `create`, which refuses in turn because the
|
|
1042
|
+
* cloud still has the bot. Remove what is actually on this computer, and with
|
|
1043
|
+
* --remote the bot HQ still holds under that name.
|
|
1044
|
+
*/
|
|
1045
|
+
async function runBotRmOrphan(name, opts) {
|
|
1046
|
+
const dir = botDir(name);
|
|
1047
|
+
const hadDir = fs.existsSync(dir);
|
|
1048
|
+
let plistPath = null;
|
|
1049
|
+
try {
|
|
1050
|
+
plistPath = buildBotDaemonPaths({ name, botDir: dir, hqBinary: "hq", nodeBinary: process.execPath }).plistPath;
|
|
1051
|
+
}
|
|
1052
|
+
catch {
|
|
1053
|
+
/* no resolvable home: there is nothing to say about a startup agent */
|
|
1054
|
+
}
|
|
1055
|
+
const hadDaemon = plistPath !== null && fs.existsSync(plistPath);
|
|
1056
|
+
// Ask HQ before touching anything local, so a server that cannot answer does
|
|
1057
|
+
// not leave the removal half done.
|
|
1058
|
+
let api = null;
|
|
1059
|
+
let remote = null;
|
|
1060
|
+
if (opts.remote) {
|
|
1061
|
+
const token = await ownerToken();
|
|
1062
|
+
api = new BotApi({ token: async () => token });
|
|
1063
|
+
const mine = await listMyBotsOrExplain(api, {
|
|
1064
|
+
json: opts.json,
|
|
1065
|
+
emptyResult: { name, orphan: true, localStateRemoved: false, remoteRemoved: false },
|
|
1066
|
+
});
|
|
1067
|
+
const hits = matchRemoteBots(mine, name);
|
|
1068
|
+
if (hits.length > 1) {
|
|
1069
|
+
throw Object.assign(new Error(`You own more than one bot called "${name}": ${hits.map((h) => h.agentUid).join(", ")}. Name the one you mean by its id.`), {
|
|
1070
|
+
expected: true,
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
remote = hits[0] ?? null;
|
|
1074
|
+
}
|
|
1075
|
+
if (!hadDir && !hadDaemon && !remote) {
|
|
1076
|
+
throw Object.assign(new Error(opts.remote
|
|
1077
|
+
? `Nothing named "${name}" is left on this computer, and HQ has no bot by that name either.`
|
|
1078
|
+
: `Nothing named "${name}" is left on this computer. If HQ still has a bot by that name, remove that too with: hq bot rm ${name} --remote`), { expected: true });
|
|
1079
|
+
}
|
|
1080
|
+
if (!opts.yes) {
|
|
1081
|
+
const ok = await confirm(`"${name}" has no settings left on this computer. Remove what is left of it${remote ? `, and the bot HQ still has by that name (${remote.agentUid})` : ""}? [y/N] `);
|
|
1082
|
+
if (!ok) {
|
|
1083
|
+
console.log(chalk.dim("Cancelled."));
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
// Inline bootout: unlike the self-heal, this process is not the launchd job.
|
|
1088
|
+
const daemon = removeOrphanBotDaemon({ name, botDir: dir }, { ...daemonDeps(), bootout: "inline" });
|
|
1089
|
+
if (hadDir)
|
|
1090
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
1091
|
+
let remoteRemoved = false;
|
|
1092
|
+
let remoteDetail = null;
|
|
1093
|
+
let remoteSteps = [];
|
|
1094
|
+
let failure = null;
|
|
1095
|
+
if (remote && api) {
|
|
1096
|
+
try {
|
|
1097
|
+
const identity = await api.deleteLocalBot(remote.agentUid);
|
|
1098
|
+
remoteRemoved = identity.terminal;
|
|
1099
|
+
if (identity.terminal) {
|
|
1100
|
+
remoteDetail = "removed from HQ";
|
|
1101
|
+
}
|
|
1102
|
+
else {
|
|
1103
|
+
const incomplete = botRemovalIncomplete(identity.failures);
|
|
1104
|
+
remoteDetail = incomplete.detail;
|
|
1105
|
+
remoteSteps = incomplete.steps;
|
|
1106
|
+
failure = {
|
|
1107
|
+
reason: incomplete.reason,
|
|
1108
|
+
message: `"${name}" is gone from this computer, but ${incomplete.detail}.` +
|
|
1109
|
+
(incomplete.retryable ? ` Run the same command again to finish clearing it from HQ.` : ` The leftover is HQ's to clear.`),
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
catch (err) {
|
|
1114
|
+
if (err instanceof BotApiError && err.status === 404) {
|
|
1115
|
+
remoteRemoved = true;
|
|
1116
|
+
remoteDetail = "it was already gone from HQ";
|
|
1117
|
+
}
|
|
1118
|
+
else {
|
|
1119
|
+
// Never the transport string: one plain sentence, and the reason the
|
|
1120
|
+
// desktop switches on.
|
|
1121
|
+
const refused = botRemoteDeleteRefused(name, err);
|
|
1122
|
+
remoteDetail = refused.detail;
|
|
1123
|
+
failure = { reason: refused.reason, message: refused.message };
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
if (opts.json) {
|
|
1128
|
+
console.log(JSON.stringify({
|
|
1129
|
+
ok: remote === null || remoteRemoved,
|
|
1130
|
+
...(failure ? { reason: failure.reason, message: failure.message } : {}),
|
|
1131
|
+
name,
|
|
1132
|
+
orphan: true,
|
|
1133
|
+
agentUid: remote?.agentUid ?? null,
|
|
1134
|
+
daemon: daemon.message,
|
|
1135
|
+
localStateRemoved: hadDir,
|
|
1136
|
+
remoteRemoved,
|
|
1137
|
+
remoteDetail,
|
|
1138
|
+
remoteStepsUnfinished: remoteSteps,
|
|
1139
|
+
}, null, 2));
|
|
1140
|
+
if (failure)
|
|
1141
|
+
throw Object.assign(new Error(failure.message), { expected: true });
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
console.log(chalk.green(`Removed what was left of "${name}" on this computer`) + chalk.dim(" (its settings were already gone)."));
|
|
1145
|
+
console.log(chalk.dim(` startup agent: ${daemon.message}`));
|
|
1146
|
+
console.log(chalk.dim(` local folder: ${hadDir ? `removed ${dir}` : "there was none left"}`));
|
|
1147
|
+
if (failure) {
|
|
1148
|
+
if (remoteSteps.length > 0)
|
|
1149
|
+
console.log(chalk.dim(` in HQ: did not finish ${remoteSteps.join(", ")}`));
|
|
1150
|
+
throw Object.assign(new Error(failure.message), { expected: true });
|
|
1151
|
+
}
|
|
1152
|
+
if (remote)
|
|
1153
|
+
console.log(chalk.green(` in HQ: ${remoteDetail}`));
|
|
1154
|
+
else if (opts.remote)
|
|
1155
|
+
console.log(chalk.dim(` in HQ: HQ has no bot named "${name}", so there was nothing to remove there.`));
|
|
1156
|
+
else
|
|
1157
|
+
console.log(chalk.dim(` HQ may still have a bot named "${name}". Remove that too with: hq bot rm ${name} --remote`));
|
|
1158
|
+
}
|
|
1159
|
+
async function runBotRm(nameArg, opts) {
|
|
1160
|
+
const validated = validateBotName(nameArg);
|
|
1161
|
+
if (readBotConfig(botDir(validated)) === null) {
|
|
1162
|
+
await runBotRmOrphan(validated, opts);
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1165
|
+
const { name, dir, config } = requireBot(nameArg);
|
|
1166
|
+
if (hasPromotionHold(dir))
|
|
1167
|
+
throw new Error("This bot is held for cloud promotion. Continue its promotion before changing local settings or lifecycle.");
|
|
1168
|
+
if (!opts.yes) {
|
|
1169
|
+
const ok = await confirm(`Remove bot "${name}" (${config.agentUid}), its identity, and its local state? [y/N] `);
|
|
1170
|
+
if (!ok) {
|
|
1171
|
+
console.log(chalk.dim("Cancelled."));
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
await stopBot(name, dir, config, "silent").catch(() => { });
|
|
1176
|
+
const paths = buildBotDaemonPaths({ name, botDir: dir, hqRoot: config.hqRoot });
|
|
1177
|
+
const daemon = uninstallBotDaemon(paths, daemonDeps());
|
|
1178
|
+
const token = await ownerToken();
|
|
1179
|
+
const api = new BotApi({ token: async () => token });
|
|
1180
|
+
let identity;
|
|
1181
|
+
try {
|
|
1182
|
+
identity = await api.deleteLocalBot(config.agentUid);
|
|
1183
|
+
}
|
|
1184
|
+
catch (err) {
|
|
1185
|
+
if (err instanceof BotApiError && err.status === 404)
|
|
1186
|
+
identity = { alreadyGone: true };
|
|
1187
|
+
else
|
|
1188
|
+
throw err;
|
|
1189
|
+
}
|
|
1190
|
+
const terminal = "alreadyGone" in identity ? true : identity.terminal;
|
|
1191
|
+
// A teardown HQ could only half-finish. Classified here so neither branch
|
|
1192
|
+
// below ever repeats what the server said: one real answer carried the AWS
|
|
1193
|
+
// account id, an assumed-role ARN and a DynamoDB table ARN.
|
|
1194
|
+
const incomplete = terminal ? null : botRemovalIncomplete("failures" in identity ? identity.failures : undefined);
|
|
1195
|
+
// A company bot is a member of its companies: leave them too. Best effort —
|
|
1196
|
+
// a stale membership row must not keep the bot's local state around.
|
|
1197
|
+
const memberships = effectiveBotCompanies(config);
|
|
1198
|
+
const left = memberships.length > 0 ? await removeBotFromCompanies({ agentUid: config.agentUid, companies: memberships, token }) : { done: [], failed: [] };
|
|
1199
|
+
deleteBotCreds(dir);
|
|
1200
|
+
// The local-state decision. A refusal, or a step whose target was already
|
|
1201
|
+
// gone, answers the same way forever — and by then the steps that did
|
|
1202
|
+
// succeed have left a bot that can no longer sign in, so keeping its folder
|
|
1203
|
+
// only strands the person with something that cannot run and cannot be
|
|
1204
|
+
// re-created under the same name. Everything else keeps the folder, so the
|
|
1205
|
+
// same command can pick the job back up.
|
|
1206
|
+
const localStateRemoved = terminal || incomplete.retryable === false;
|
|
1207
|
+
if (localStateRemoved)
|
|
1208
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
1209
|
+
const message = incomplete
|
|
1210
|
+
? `HQ removed what it could of "${name}", but ${incomplete.detail}.` +
|
|
1211
|
+
(incomplete.retryable
|
|
1212
|
+
? ` Its settings are kept on this computer, so run the same command again to finish it.`
|
|
1213
|
+
: ` It is gone from this computer; the leftover is HQ's to clear.`)
|
|
1214
|
+
: null;
|
|
1215
|
+
if (opts.json) {
|
|
1216
|
+
console.log(JSON.stringify({
|
|
1217
|
+
ok: terminal,
|
|
1218
|
+
...(incomplete && message ? { reason: incomplete.reason, message } : {}),
|
|
1219
|
+
name,
|
|
1220
|
+
orphan: false,
|
|
1221
|
+
agentUid: config.agentUid,
|
|
1222
|
+
daemon: daemon.message,
|
|
1223
|
+
companiesLeft: left.done,
|
|
1224
|
+
companiesNotLeft: left.failed,
|
|
1225
|
+
localStateRemoved,
|
|
1226
|
+
remoteRemoved: terminal,
|
|
1227
|
+
remoteDetail: incomplete ? incomplete.detail : "alreadyGone" in identity ? "it was already gone from HQ" : "removed from HQ",
|
|
1228
|
+
remoteStepsUnfinished: incomplete?.steps ?? [],
|
|
1229
|
+
workerKept: path.join(config.hqRoot, config.workerDir),
|
|
1230
|
+
}, null, 2));
|
|
1231
|
+
if (message)
|
|
1232
|
+
throw Object.assign(new Error(message), { expected: true });
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
for (const f of left.failed) {
|
|
1236
|
+
console.log(chalk.yellow(` could not remove ${name} from ${f.slug}: ${f.reason}`) + chalk.dim(` — remove it by hand: hq members --company ${f.slug} revoke ${config.agentUid}`));
|
|
1237
|
+
}
|
|
1238
|
+
if (terminal) {
|
|
1239
|
+
console.log(chalk.green(`Removed bot ${name}`) + chalk.dim(` (${config.agentUid}${left.done.length ? `; left ${left.done.join(", ")}` : ""}); worker folder kept at ${path.join(config.hqRoot, config.workerDir)}`));
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
if (incomplete.steps.length > 0)
|
|
1243
|
+
console.log(chalk.dim(` HQ did not finish: ${incomplete.steps.join(", ")}`));
|
|
1244
|
+
throw Object.assign(new Error(message), { expected: true });
|
|
1245
|
+
}
|
|
1246
|
+
/**
|
|
1247
|
+
* `hq bot set <name> --model <id|default> --effort <level|default>`: change
|
|
1248
|
+
* what the bot thinks with. bot.json is re-read on every turn, so the next
|
|
1249
|
+
* message uses it; no restart.
|
|
1250
|
+
*/
|
|
1251
|
+
export async function runBotSet(nameArg, opts) {
|
|
1252
|
+
const { name, dir, config } = requireBot(nameArg);
|
|
1253
|
+
if (hasPromotionHold(dir))
|
|
1254
|
+
throw new Error("This bot is held for cloud promotion. Continue its promotion before changing local settings or lifecycle.");
|
|
1255
|
+
if (opts.model === undefined && opts.effort === undefined) {
|
|
1256
|
+
throw Object.assign(new Error("Nothing to change: pass --model <id|default> and/or --effort <level|default>."), { expected: true });
|
|
1257
|
+
}
|
|
1258
|
+
const next = { ...config };
|
|
1259
|
+
if (opts.model !== undefined) {
|
|
1260
|
+
const model = validateBotModel(opts.model);
|
|
1261
|
+
if (model)
|
|
1262
|
+
next.model = model;
|
|
1263
|
+
else
|
|
1264
|
+
delete next.model;
|
|
1265
|
+
}
|
|
1266
|
+
if (opts.effort !== undefined) {
|
|
1267
|
+
const effort = validateBotEffort(config.runtime, opts.effort);
|
|
1268
|
+
if (effort)
|
|
1269
|
+
next.effort = effort;
|
|
1270
|
+
else
|
|
1271
|
+
delete next.effort;
|
|
1272
|
+
}
|
|
1273
|
+
writeBotConfig(dir, next);
|
|
1274
|
+
createBotLogger(dir)("info", `settings: model=${next.model ?? "default"} effort=${effectiveBotEffort(next)}${next.effort ? "" : " (default)"}`);
|
|
1275
|
+
const result = {
|
|
1276
|
+
ok: true,
|
|
1277
|
+
name,
|
|
1278
|
+
runtime: config.runtime,
|
|
1279
|
+
model: next.model ?? null,
|
|
1280
|
+
effort: effectiveBotEffort(next),
|
|
1281
|
+
effortIsDefault: !next.effort,
|
|
1282
|
+
effortLevels: BOT_EFFORT_LEVELS[config.runtime],
|
|
1283
|
+
};
|
|
1284
|
+
if (opts.json) {
|
|
1285
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
console.log(chalk.green(`${name} now thinks with ${result.model ?? "the default model"} at ${result.effort} effort (from its next message).`));
|
|
1289
|
+
}
|
|
1290
|
+
async function runBotIntro(nameArg) {
|
|
1291
|
+
const { name, dir, config } = requireBot(nameArg);
|
|
1292
|
+
if (hasPromotionHold(dir))
|
|
1293
|
+
throw new Error("This bot is held for cloud promotion. Continue its promotion before changing local settings or lifecycle.");
|
|
1294
|
+
const api = new BotApi({ token: botTokenSupplier(dir) });
|
|
1295
|
+
await api.sendDm({ toPersonUid: config.ownerUid, body: introDmText(name, config.runtime, config.intro, { kind: effectiveBotKind(config), companies: effectiveBotCompanies(config) }) });
|
|
1296
|
+
patchBotConfig(dir, { introSentAt: new Date().toISOString() });
|
|
1297
|
+
console.log(chalk.green(`${name} introduced itself to you.`));
|
|
1298
|
+
}
|
|
1299
|
+
// ── adopt / restore (bring a bot you own back to this computer) ──────────────
|
|
1300
|
+
/**
|
|
1301
|
+
* Which computer a re-issue is for. Never a secret and never an identifier the
|
|
1302
|
+
* server needs to trust — it only shows up in the bot's audit trail so its
|
|
1303
|
+
* owner can see where it was last brought back.
|
|
1304
|
+
*/
|
|
1305
|
+
function localMachineId() {
|
|
1306
|
+
const host = os.hostname().trim();
|
|
1307
|
+
return host || "unknown-machine";
|
|
1308
|
+
}
|
|
1309
|
+
/**
|
|
1310
|
+
* The one bot the caller means. Matches the local folder name, the cloud slug,
|
|
1311
|
+
* the display name, or the agt_ uid; an ambiguous name is refused rather than
|
|
1312
|
+
* guessed at.
|
|
1313
|
+
*/
|
|
1314
|
+
export function matchRemoteBots(bots, wanted) {
|
|
1315
|
+
const needle = wanted.trim().toLowerCase();
|
|
1316
|
+
return bots.filter((b) => b.agentUid === wanted.trim() ||
|
|
1317
|
+
(b.slug ?? "").trim().toLowerCase() === needle ||
|
|
1318
|
+
(b.name ?? "").trim().toLowerCase() === needle ||
|
|
1319
|
+
localNameForRemoteBot(b) === needle);
|
|
1320
|
+
}
|
|
1321
|
+
/** A name match alone does not prove that a remote record is HQ's setup bot. */
|
|
1322
|
+
function isReservedSetupBotRecord(bot, ownerUid) {
|
|
1323
|
+
const owner = ownerUid.trim();
|
|
1324
|
+
const local = parseBotLocalConfig(bot.localConfig);
|
|
1325
|
+
return (!!owner &&
|
|
1326
|
+
bot.ownerUid === owner &&
|
|
1327
|
+
bot.slug === `${RESERVED_SETUP_BOT_NAME}-${owner.slice(-8).toLowerCase()}` &&
|
|
1328
|
+
bot.botKind === "personal" &&
|
|
1329
|
+
bot.computeMode === "local" &&
|
|
1330
|
+
local?.workerSource === "worker" &&
|
|
1331
|
+
local.workerId === SETUP_BOT_WORKER_ID);
|
|
1332
|
+
}
|
|
1333
|
+
export function resolveRemoteBot(bots, wanted) {
|
|
1334
|
+
const hits = matchRemoteBots(bots, wanted);
|
|
1335
|
+
if (hits.length === 1)
|
|
1336
|
+
return hits[0];
|
|
1337
|
+
if (hits.length > 1) {
|
|
1338
|
+
throw Object.assign(new Error(`You own more than one bot called "${wanted}": ${hits.map((h) => h.agentUid).join(", ")}. Name the one you mean by its id.`), { expected: true });
|
|
1339
|
+
}
|
|
1340
|
+
const known = bots.map((b) => localNameForRemoteBot(b) ?? b.agentUid);
|
|
1341
|
+
throw Object.assign(new Error(known.length > 0
|
|
1342
|
+
? `You have no bot called "${wanted}" in HQ. Bots you own: ${known.join(", ")}.`
|
|
1343
|
+
: `You have no local bots in HQ yet. Create one with: hq bot create ${wanted}`), { expected: true });
|
|
1344
|
+
}
|
|
1345
|
+
/** Turn a BotApiError from the credential route into a sentence a person can act on. */
|
|
1346
|
+
function credentialError(err, name) {
|
|
1347
|
+
if (err instanceof BotApiError && err.status === 403) {
|
|
1348
|
+
return Object.assign(new Error(`HQ will not hand out credentials for "${name}": it is either not yours, or it is a cloud agent rather than a local bot.`), { expected: true });
|
|
1349
|
+
}
|
|
1350
|
+
if (err instanceof BotApiError && err.status === 404) {
|
|
1351
|
+
return Object.assign(new Error(`HQ no longer has a bot called "${name}". Create a new one with: hq bot create ${name}`), {
|
|
1352
|
+
expected: true,
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
return err;
|
|
1356
|
+
}
|
|
1357
|
+
/**
|
|
1358
|
+
* Turn any failure of `GET /v1/agents/mine` into one plain sentence plus a
|
|
1359
|
+
* stable reason. Production HQ Cloud does not serve that route yet, and an
|
|
1360
|
+
* uncaught 404 reached the person verbatim as "HQ API /v1/agents/mine → 404:
|
|
1361
|
+
* Not found" — and, under --json, as no document at all.
|
|
1362
|
+
*/
|
|
1363
|
+
export function remoteBotsUnavailable(err) {
|
|
1364
|
+
if (err instanceof BotApiError) {
|
|
1365
|
+
if (err.status === 404 || err.status === 501) {
|
|
1366
|
+
return {
|
|
1367
|
+
reason: "server-unsupported",
|
|
1368
|
+
message: "Your HQ Cloud cannot list the bots you own yet, so there is nothing to bring back from here. Update HQ Cloud, or try again later.",
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
if (err.status === 401 || err.status === 403) {
|
|
1372
|
+
return { reason: "auth", message: "HQ did not accept your sign-in, so it would not say which bots you own. Sign in again with: hq login" };
|
|
1373
|
+
}
|
|
1374
|
+
if (err.status >= 500) {
|
|
1375
|
+
return { reason: "server-error", message: "HQ Cloud had trouble listing the bots you own. Try again in a minute." };
|
|
1376
|
+
}
|
|
1377
|
+
return { reason: "error", message: "HQ could not say which bots you own, so there is nothing to bring back from here. Try again in a minute." };
|
|
1378
|
+
}
|
|
1379
|
+
const text = err instanceof Error ? err.message : String(err);
|
|
1380
|
+
if (/fetch failed|ENOTFOUND|ECONNREFUSED|ECONNRESET|EAI_AGAIN|EHOSTUNREACH|ETIMEDOUT|network|socket hang up|timed? ?out/i.test(text)) {
|
|
1381
|
+
return { reason: "network", message: "HQ could not be reached, so it could not say which bots you own. Check your connection and try again." };
|
|
1382
|
+
}
|
|
1383
|
+
return { reason: "error", message: "HQ could not say which bots you own, so there is nothing to bring back from here. Try again in a minute." };
|
|
1384
|
+
}
|
|
1385
|
+
const REFUSED_TEARDOWN_STEP = /access\s?denied|not authori[sz]ed|unauthori[sz]ed|forbidden|identity-based policy|permission/i;
|
|
1386
|
+
const ALREADY_GONE_TEARDOWN_STEP = /not\s?found|no such|does not exist|already (?:gone|deleted|removed)/i;
|
|
1387
|
+
/**
|
|
1388
|
+
* Step names are fixed server vocabulary (`delete-creator-owner`). Anything
|
|
1389
|
+
* that is not plainly one is dropped rather than echoed.
|
|
1390
|
+
*/
|
|
1391
|
+
function safeTeardownSteps(failures) {
|
|
1392
|
+
return Object.keys(failures ?? {}).filter((step) => /^[a-z0-9][a-z0-9._-]{0,39}$/i.test(step));
|
|
1393
|
+
}
|
|
1394
|
+
export function botRemovalIncomplete(failures) {
|
|
1395
|
+
const steps = safeTeardownSteps(failures);
|
|
1396
|
+
const texts = Object.values(failures ?? {});
|
|
1397
|
+
const refused = texts.some((text) => REFUSED_TEARDOWN_STEP.test(text));
|
|
1398
|
+
const permanent = texts.length > 0 && texts.every((text) => REFUSED_TEARDOWN_STEP.test(text) || ALREADY_GONE_TEARDOWN_STEP.test(text));
|
|
1399
|
+
if (permanent) {
|
|
1400
|
+
return refused
|
|
1401
|
+
? { reason: "auth", detail: "HQ was not allowed to finish clearing its cloud identity, and would be refused the same way next time", retryable: false, steps }
|
|
1402
|
+
: { reason: "error", detail: "HQ found part of its cloud identity already gone, and would find the same next time", retryable: false, steps };
|
|
1403
|
+
}
|
|
1404
|
+
return { reason: "server-error", detail: "HQ has not finished removing it from the cloud", retryable: true, steps };
|
|
1405
|
+
}
|
|
1406
|
+
/**
|
|
1407
|
+
* A cloud delete that threw. Reuses {@link remoteBotsUnavailable}'s
|
|
1408
|
+
* classification of the transport, with a sentence about removal rather than
|
|
1409
|
+
* listing — and, as there, the transport string never reaches the person.
|
|
1410
|
+
* A 404 is not routed here: for a delete it means the bot was already gone.
|
|
1411
|
+
*/
|
|
1412
|
+
function botRemoteDeleteRefused(name, err) {
|
|
1413
|
+
const { reason } = remoteBotsUnavailable(err);
|
|
1414
|
+
const next = reason === "auth" ? "Sign in again with: hq login, then run this again." : reason === "network" ? "Check your connection and run this again." : "Try again in a minute.";
|
|
1415
|
+
return {
|
|
1416
|
+
reason,
|
|
1417
|
+
detail: reason === "auth" ? "HQ did not accept your sign-in" : reason === "network" ? "HQ could not be reached" : "HQ could not remove it just now",
|
|
1418
|
+
message: `"${name}" is gone from this computer, but HQ could not remove the bot it still has under that name. ${next}`,
|
|
1419
|
+
};
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* The account's own local bots, or a refusal shaped for both audiences: one
|
|
1423
|
+
* plain sentence for a person (via the thrown expected error, exit 1), and —
|
|
1424
|
+
* under --json — a document on stdout that always parses, carrying the reason
|
|
1425
|
+
* the desktop keys off plus whatever empty fields its success shape promises.
|
|
1426
|
+
*/
|
|
1427
|
+
async function listMyBotsOrExplain(api, opts) {
|
|
1428
|
+
try {
|
|
1429
|
+
return await api.listMine({ local: true });
|
|
1430
|
+
}
|
|
1431
|
+
catch (err) {
|
|
1432
|
+
const failure = remoteBotsUnavailable(err);
|
|
1433
|
+
if (opts.json)
|
|
1434
|
+
console.log(JSON.stringify({ ok: false, ...failure, ...(opts.emptyResult ?? {}) }, null, 2));
|
|
1435
|
+
throw Object.assign(new Error(failure.message), { expected: true });
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
/**
|
|
1439
|
+
* Rebuild a bot.json from the settings the cloud kept for it. A bot created
|
|
1440
|
+
* before HQ saved settings comes back on defaults, and anything this HQ can no
|
|
1441
|
+
* longer provide (a worker that is not here, a thinking level the chosen
|
|
1442
|
+
* runtime does not accept) is reported rather than silently kept.
|
|
1443
|
+
*/
|
|
1444
|
+
export function configFromLocalConfig(input) {
|
|
1445
|
+
const { name, local } = input;
|
|
1446
|
+
const notes = [];
|
|
1447
|
+
if (!local) {
|
|
1448
|
+
notes.push(`${name} was created before HQ kept a bot's settings in the cloud, so it comes back on defaults (${describeBotLocalConfig(null)}). Change them with: hq bot set ${name} --model <id> --effort <level>`);
|
|
1449
|
+
}
|
|
1450
|
+
const runtime = input.overrides?.runtime ?? local?.runtime ?? BOT_RESTORE_DEFAULTS.runtime;
|
|
1451
|
+
const kind = local?.kind ?? BOT_RESTORE_DEFAULTS.kind;
|
|
1452
|
+
const memory = local?.memory ?? BOT_RESTORE_DEFAULTS.memory;
|
|
1453
|
+
const model = input.overrides?.model ?? local?.model;
|
|
1454
|
+
let effort = local?.effort;
|
|
1455
|
+
if (effort && !BOT_EFFORT_LEVELS[runtime].includes(effort)) {
|
|
1456
|
+
notes.push(`${runtime} has no "${effort}" thinking level, so ${name} comes back on the default (${DEFAULT_BOT_EFFORT}).`);
|
|
1457
|
+
effort = undefined;
|
|
1458
|
+
}
|
|
1459
|
+
let workerSource = "scaffold";
|
|
1460
|
+
let workerDir = botWorkerRelDir(name);
|
|
1461
|
+
let workerId;
|
|
1462
|
+
let companySlug = local?.companySlug;
|
|
1463
|
+
if (local?.workerSource === "worker" && local.workerId) {
|
|
1464
|
+
try {
|
|
1465
|
+
const worker = resolveBotWorker(input.hqRoot, local.workerId);
|
|
1466
|
+
workerSource = "worker";
|
|
1467
|
+
workerDir = worker.path;
|
|
1468
|
+
workerId = worker.id;
|
|
1469
|
+
companySlug = worker.company ?? undefined;
|
|
1470
|
+
}
|
|
1471
|
+
catch {
|
|
1472
|
+
notes.push(`the worker "${local.workerId}" is not in this HQ, so ${name} comes back with its own scaffolded worker folder instead.`);
|
|
1473
|
+
companySlug = undefined;
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
const config = {
|
|
1477
|
+
v: 1,
|
|
1478
|
+
name,
|
|
1479
|
+
agentUid: input.agentUid,
|
|
1480
|
+
ownerUid: input.ownerUid,
|
|
1481
|
+
runtime,
|
|
1482
|
+
kind,
|
|
1483
|
+
...(kind === "company" ? { companies: local?.companies ?? [] } : {}),
|
|
1484
|
+
...(model ? { model } : {}),
|
|
1485
|
+
...(effort ? { effort } : {}),
|
|
1486
|
+
...(local?.autoApprove === false ? { autoApprove: false } : {}),
|
|
1487
|
+
hqRoot: input.hqRoot,
|
|
1488
|
+
workerDir,
|
|
1489
|
+
...(workerSource === "worker" && workerId ? { workerSource, workerId } : {}),
|
|
1490
|
+
...(companySlug ? { companySlug } : {}),
|
|
1491
|
+
...(memory === "local"
|
|
1492
|
+
? { memoryDir: botLocalMemoryDir(input.dir) }
|
|
1493
|
+
: workerSource === "worker"
|
|
1494
|
+
? { memoryDir: path.join(botWorkerRelDir(name), "memory") }
|
|
1495
|
+
: {}),
|
|
1496
|
+
...(local?.displayName ? { displayName: local.displayName } : {}),
|
|
1497
|
+
...(local?.intro ? { intro: local.intro } : {}),
|
|
1498
|
+
...(local?.kickoff ? { kickoff: local.kickoff } : {}),
|
|
1499
|
+
createdAt: (input.now ?? (() => new Date()))().toISOString(),
|
|
1500
|
+
enabled: true,
|
|
1501
|
+
// A restore is not a first start: the bot's one-off kickoff task already
|
|
1502
|
+
// ran on the machine it was created on, and `runs once` has to keep
|
|
1503
|
+
// meaning once. Recovery after a create response was lost is different:
|
|
1504
|
+
// this machine has not started the bot yet, so its first intro/kickoff
|
|
1505
|
+
// must still run.
|
|
1506
|
+
...(input.preserveFirstStart ? {} : { introSentAt: (input.now ?? (() => new Date()))().toISOString() }),
|
|
1507
|
+
};
|
|
1508
|
+
return { config, notes };
|
|
1509
|
+
}
|
|
1510
|
+
/**
|
|
1511
|
+
* The tail of the create path, run against a bot that already exists in the
|
|
1512
|
+
* cloud: fresh machine credentials, a rebuilt bot.json, its worker/memory
|
|
1513
|
+
* folders, and its launchd agent.
|
|
1514
|
+
*/
|
|
1515
|
+
async function adoptRemoteBot(input) {
|
|
1516
|
+
const { bot, name } = input;
|
|
1517
|
+
const dir = botDir(name);
|
|
1518
|
+
if (readBotConfig(dir)) {
|
|
1519
|
+
throw Object.assign(new Error(`Bot "${name}" is already set up on this computer (${dir}). Start it with: hq bot start ${name}`), {
|
|
1520
|
+
expected: true,
|
|
1521
|
+
});
|
|
1522
|
+
}
|
|
1523
|
+
// The last gate before anything is written or any secret is rotated: the
|
|
1524
|
+
// runtime's own rule, so nothing is ever adopted that could not then start.
|
|
1525
|
+
const runnable = remoteBotRunnability(bot, name);
|
|
1526
|
+
if (!runnable.runnable) {
|
|
1527
|
+
throw Object.assign(new Error(runnable.message), { expected: true });
|
|
1528
|
+
}
|
|
1529
|
+
let reissued;
|
|
1530
|
+
try {
|
|
1531
|
+
reissued = await input.api.reissueCredentials(bot.agentUid, { machineId: localMachineId(), reason: "reinstall" });
|
|
1532
|
+
}
|
|
1533
|
+
catch (err) {
|
|
1534
|
+
throw credentialError(err, name);
|
|
1535
|
+
}
|
|
1536
|
+
writeBotCreds(dir, {
|
|
1537
|
+
username: reissued.identity.cognitoUsername,
|
|
1538
|
+
secret: reissued.identity.secret,
|
|
1539
|
+
entityType: "agent",
|
|
1540
|
+
entityUid: bot.agentUid,
|
|
1541
|
+
clientId: DEFAULT_COGNITO.clientId,
|
|
1542
|
+
region: DEFAULT_COGNITO.region,
|
|
1543
|
+
});
|
|
1544
|
+
const local = parseBotLocalConfig(reissued.localConfig ?? bot.localConfig);
|
|
1545
|
+
const { config, notes } = configFromLocalConfig({
|
|
1546
|
+
name,
|
|
1547
|
+
agentUid: bot.agentUid,
|
|
1548
|
+
ownerUid: reissued.agent.ownerUid ?? bot.ownerUid ?? "",
|
|
1549
|
+
hqRoot: input.hqRoot,
|
|
1550
|
+
dir,
|
|
1551
|
+
local,
|
|
1552
|
+
...(input.overrides ? { overrides: input.overrides } : {}),
|
|
1553
|
+
...(input.preserveFirstStart ? { preserveFirstStart: true } : {}),
|
|
1554
|
+
});
|
|
1555
|
+
writeBotConfig(dir, config);
|
|
1556
|
+
const scaffoldOpts = { memoryDir: config.memoryDir };
|
|
1557
|
+
const scaffold = config.workerSource === "worker"
|
|
1558
|
+
? scaffoldBotMemory(input.hqRoot, name, undefined, scaffoldOpts)
|
|
1559
|
+
: scaffoldBotWorker(input.hqRoot, name, bot.agentUid, config.runtime, undefined, scaffoldOpts);
|
|
1560
|
+
createBotLogger(dir)("info", `adopted ${name} (${bot.agentUid}) on ${localMachineId()} kind=${config.kind}${local ? "" : " (no saved settings; defaults)"} runtime=${config.runtime}`);
|
|
1561
|
+
let daemonMessage = null;
|
|
1562
|
+
if (input.daemon && process.platform === "darwin") {
|
|
1563
|
+
daemonMessage = installBotAgent(buildBotDaemonPaths({ name, botDir: dir, hqRoot: input.hqRoot })).message;
|
|
1564
|
+
}
|
|
1565
|
+
return {
|
|
1566
|
+
config,
|
|
1567
|
+
dir,
|
|
1568
|
+
result: {
|
|
1569
|
+
name,
|
|
1570
|
+
agentUid: bot.agentUid,
|
|
1571
|
+
runtime: config.runtime,
|
|
1572
|
+
kind: effectiveBotKind(config),
|
|
1573
|
+
companies: effectiveBotCompanies(config),
|
|
1574
|
+
dir,
|
|
1575
|
+
worker: scaffold.dir,
|
|
1576
|
+
memory: botMemoryMode(config),
|
|
1577
|
+
memoryDir: scaffold.memoryDir,
|
|
1578
|
+
defaulted: local === null,
|
|
1579
|
+
notes,
|
|
1580
|
+
daemon: daemonMessage,
|
|
1581
|
+
},
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
export async function runBotAdopt(nameArg, opts) {
|
|
1585
|
+
const asked = validateBotName(nameArg);
|
|
1586
|
+
if (readBotConfig(botDir(asked))) {
|
|
1587
|
+
throw Object.assign(new Error(`Bot "${asked}" is already set up on this computer (${botDir(asked)}). Start it with: hq bot start ${asked}`), {
|
|
1588
|
+
expected: true,
|
|
1589
|
+
});
|
|
1590
|
+
}
|
|
1591
|
+
if (opts.runtime !== undefined && !isBotRuntimeId(opts.runtime)) {
|
|
1592
|
+
throw Object.assign(new Error(`--runtime must be one of ${BOT_RUNTIMES.join(", ")}`), { expected: true });
|
|
1593
|
+
}
|
|
1594
|
+
const model = opts.model !== undefined ? validateBotModel(opts.model) : undefined;
|
|
1595
|
+
const hqRoot = resolveDefaultHqRoot({ onMissing: "throw" });
|
|
1596
|
+
const token = await ownerToken();
|
|
1597
|
+
const api = new BotApi({ token: async () => token });
|
|
1598
|
+
const bot = resolveRemoteBot(await listMyBotsOrExplain(api, { json: opts.json }), asked);
|
|
1599
|
+
// One bot, one directory: a bot created here as `qa-x` and slugged
|
|
1600
|
+
// `qa-x-<owner>` in the cloud comes back to ~/.hq/bots/qa-x/, not beside it.
|
|
1601
|
+
const name = localNameForRemoteBot(bot) ?? asked;
|
|
1602
|
+
const dir = botDir(name);
|
|
1603
|
+
const runnable = remoteBotRunnability(bot, name);
|
|
1604
|
+
if (!runnable.runnable) {
|
|
1605
|
+
if (opts.json) {
|
|
1606
|
+
console.log(JSON.stringify({ ok: false, reason: "not-runnable-here", notRunnable: runnable.reason, name, agentUid: bot.agentUid, message: runnable.message }, null, 2));
|
|
1607
|
+
}
|
|
1608
|
+
throw Object.assign(new Error(runnable.message), { expected: true });
|
|
1609
|
+
}
|
|
1610
|
+
if (readBotConfig(dir)) {
|
|
1611
|
+
throw Object.assign(new Error(`Bot "${name}" is already set up on this computer (${dir}). Start it with: hq bot start ${name}`), {
|
|
1612
|
+
expected: true,
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1615
|
+
const { result, config } = await adoptRemoteBot({
|
|
1616
|
+
bot,
|
|
1617
|
+
name,
|
|
1618
|
+
api,
|
|
1619
|
+
hqRoot,
|
|
1620
|
+
overrides: {
|
|
1621
|
+
...(isBotRuntimeId(opts.runtime) ? { runtime: opts.runtime } : {}),
|
|
1622
|
+
...(model ? { model } : {}),
|
|
1623
|
+
},
|
|
1624
|
+
daemon: opts.daemon !== false,
|
|
1625
|
+
});
|
|
1626
|
+
if (!opts.json) {
|
|
1627
|
+
console.log(chalk.green(`Brought back bot ${chalk.bold(name)}`) + chalk.dim(` (${result.agentUid}, ${result.runtime})`));
|
|
1628
|
+
console.log(chalk.dim(` kind: ${describeKind(result.kind, result.companies)}`));
|
|
1629
|
+
console.log(chalk.dim(` worker: ${result.worker}`));
|
|
1630
|
+
console.log(chalk.dim(` memory: ${result.memoryDir} (${result.memory})`));
|
|
1631
|
+
console.log(chalk.dim(` state: ${result.dir}`));
|
|
1632
|
+
if (result.daemon)
|
|
1633
|
+
console.log(chalk.dim(` ${result.daemon}`));
|
|
1634
|
+
for (const note of result.notes)
|
|
1635
|
+
console.log(chalk.yellow(` ${note}`));
|
|
1636
|
+
console.log(chalk.dim(` Have it say hello again with: hq bot intro ${name}`));
|
|
1637
|
+
}
|
|
1638
|
+
if (opts.start !== false) {
|
|
1639
|
+
await startBot(name, result.dir, config, opts.json ? "json" : "text", { ...result });
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
if (opts.json)
|
|
1643
|
+
console.log(JSON.stringify({ ok: true, ...result, running: false }, null, 2));
|
|
1644
|
+
}
|
|
1645
|
+
/** New credentials + a reinstalled startup agent for a bot that IS already here. */
|
|
1646
|
+
async function repairLocalBot(name, api, bot, daemon) {
|
|
1647
|
+
const dir = botDir(name);
|
|
1648
|
+
const config = readBotConfig(dir);
|
|
1649
|
+
let reissued;
|
|
1650
|
+
try {
|
|
1651
|
+
reissued = await api.reissueCredentials(bot.agentUid, { machineId: localMachineId(), reason: "reinstall" });
|
|
1652
|
+
}
|
|
1653
|
+
catch (err) {
|
|
1654
|
+
throw credentialError(err, name);
|
|
1655
|
+
}
|
|
1656
|
+
writeBotCreds(dir, {
|
|
1657
|
+
username: reissued.identity.cognitoUsername,
|
|
1658
|
+
secret: reissued.identity.secret,
|
|
1659
|
+
entityType: "agent",
|
|
1660
|
+
entityUid: bot.agentUid,
|
|
1661
|
+
clientId: DEFAULT_COGNITO.clientId,
|
|
1662
|
+
region: DEFAULT_COGNITO.region,
|
|
1663
|
+
});
|
|
1664
|
+
createBotLogger(dir)("info", `re-issued machine credentials for ${name} (${bot.agentUid}) on ${localMachineId()}`);
|
|
1665
|
+
if (daemon && process.platform === "darwin") {
|
|
1666
|
+
installBotAgent(buildBotDaemonPaths({ name, botDir: dir, hqRoot: config.hqRoot }));
|
|
1667
|
+
}
|
|
1668
|
+
return "new credentials, startup agent reinstalled";
|
|
1669
|
+
}
|
|
1670
|
+
/**
|
|
1671
|
+
* `hq bot restore` — every local bot this account owns that is not set up on
|
|
1672
|
+
* this computer comes back. This is what the desktop app and a post-install
|
|
1673
|
+
* hook call after a reinstall or on a new Mac.
|
|
1674
|
+
*/
|
|
1675
|
+
export async function runBotRestore(opts) {
|
|
1676
|
+
const hqRoot = resolveDefaultHqRoot({ onMissing: "throw" });
|
|
1677
|
+
const token = await ownerToken();
|
|
1678
|
+
const api = new BotApi({ token: async () => token });
|
|
1679
|
+
const bots = (await listMyBotsOrExplain(api, {
|
|
1680
|
+
json: opts.json,
|
|
1681
|
+
emptyResult: { dryRun: opts.dryRun === true, restored: 0, repaired: 0, skipped: 0, failed: 0, bots: [] },
|
|
1682
|
+
}));
|
|
1683
|
+
const rows = [];
|
|
1684
|
+
for (const bot of bots) {
|
|
1685
|
+
const name = localNameForRemoteBot(bot);
|
|
1686
|
+
if (!name) {
|
|
1687
|
+
rows.push({ name: bot.name ?? bot.agentUid, agentUid: bot.agentUid, action: "failed", detail: "its name cannot be a folder name here; rename it in HQ" });
|
|
1688
|
+
continue;
|
|
1689
|
+
}
|
|
1690
|
+
// A bot this computer cannot run is never brought back: it would get
|
|
1691
|
+
// credentials, a folder and a startup agent, and then fail at every start.
|
|
1692
|
+
// It is counted as skipped and says why, rather than being silently absent.
|
|
1693
|
+
const runnable = remoteBotRunnability(bot, name);
|
|
1694
|
+
if (!runnable.runnable) {
|
|
1695
|
+
rows.push({
|
|
1696
|
+
name,
|
|
1697
|
+
agentUid: bot.agentUid,
|
|
1698
|
+
action: opts.dryRun ? "would-skip" : "skipped",
|
|
1699
|
+
reason: runnable.reason,
|
|
1700
|
+
detail: runnable.message,
|
|
1701
|
+
});
|
|
1702
|
+
continue;
|
|
1703
|
+
}
|
|
1704
|
+
const here = readBotConfig(botDir(name)) !== null;
|
|
1705
|
+
const settings = describeBotLocalConfig(parseBotLocalConfig(bot.localConfig));
|
|
1706
|
+
if (here && hasPromotionHold(botDir(name))) {
|
|
1707
|
+
rows.push({ name, agentUid: bot.agentUid, action: opts.dryRun ? "would-skip" : "skipped", detail: "held for cloud promotion" });
|
|
1708
|
+
continue;
|
|
1709
|
+
}
|
|
1710
|
+
if (here && !opts.all) {
|
|
1711
|
+
rows.push({ name, agentUid: bot.agentUid, action: opts.dryRun ? "would-skip" : "skipped", detail: "already set up here" });
|
|
1712
|
+
continue;
|
|
1713
|
+
}
|
|
1714
|
+
if (opts.dryRun) {
|
|
1715
|
+
rows.push({ name, agentUid: bot.agentUid, action: here ? "would-repair" : "would-restore", detail: here ? "new credentials" : settings });
|
|
1716
|
+
continue;
|
|
1717
|
+
}
|
|
1718
|
+
try {
|
|
1719
|
+
if (here) {
|
|
1720
|
+
rows.push({ name, agentUid: bot.agentUid, action: "repaired", detail: await repairLocalBot(name, api, bot, opts.daemon !== false) });
|
|
1721
|
+
}
|
|
1722
|
+
else {
|
|
1723
|
+
const { result, config } = await adoptRemoteBot({ bot, name, api, hqRoot, daemon: opts.daemon !== false });
|
|
1724
|
+
// Silent: this command's own JSON has to be the only document on stdout.
|
|
1725
|
+
const started = opts.start === false ? null : await startBot(name, result.dir, config, "silent").catch(() => null);
|
|
1726
|
+
rows.push({
|
|
1727
|
+
name,
|
|
1728
|
+
agentUid: bot.agentUid,
|
|
1729
|
+
action: "restored",
|
|
1730
|
+
detail: [settings, ...result.notes, ...(started ? [started.running ? "started" : "not running yet — hq bot logs " + name] : [])].join("; "),
|
|
1731
|
+
});
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
catch (err) {
|
|
1735
|
+
rows.push({ name, agentUid: bot.agentUid, action: "failed", detail: err instanceof Error ? err.message : String(err) });
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
const counted = (action) => rows.filter((r) => r.action === action).length;
|
|
1739
|
+
if (opts.json) {
|
|
1740
|
+
console.log(JSON.stringify({
|
|
1741
|
+
ok: counted("failed") === 0,
|
|
1742
|
+
dryRun: opts.dryRun === true,
|
|
1743
|
+
restored: counted("restored") + counted("would-restore"),
|
|
1744
|
+
repaired: counted("repaired") + counted("would-repair"),
|
|
1745
|
+
skipped: counted("skipped") + counted("would-skip"),
|
|
1746
|
+
failed: counted("failed"),
|
|
1747
|
+
bots: rows,
|
|
1748
|
+
}, null, 2));
|
|
1749
|
+
return;
|
|
1750
|
+
}
|
|
1751
|
+
if (rows.length === 0) {
|
|
1752
|
+
console.log(chalk.gray("You have no local bots in HQ. Create one with: hq bot create assistant"));
|
|
1753
|
+
return;
|
|
1754
|
+
}
|
|
1755
|
+
for (const row of rows) {
|
|
1756
|
+
const colour = row.action === "failed" ? chalk.red : row.action.startsWith("would") || row.action === "skipped" ? chalk.gray : chalk.green;
|
|
1757
|
+
console.log(`${chalk.bold(row.name.padEnd(16))} ${colour(row.action.padEnd(14))} ${chalk.dim(row.detail)}`);
|
|
1758
|
+
}
|
|
1759
|
+
const summary = `${counted("restored") + counted("would-restore")} to bring back, ${counted("repaired") + counted("would-repair")} to repair, ${counted("skipped") + counted("would-skip")} already here, ${counted("failed")} failed`;
|
|
1760
|
+
console.log(chalk.dim(opts.dryRun ? `\nDry run — nothing changed. ${summary}.` : `\n${summary}.`));
|
|
1761
|
+
if (counted("failed") > 0) {
|
|
1762
|
+
throw Object.assign(new Error(`Could not bring back ${counted("failed")} of your bots (see the list above).`), { expected: true });
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
/** The one clause the table shows instead of an adopt command it must not offer. */
|
|
1766
|
+
function notRunnableHint(reason) {
|
|
1767
|
+
if (reason === "cloud-compute")
|
|
1768
|
+
return "runs in the cloud, not on a computer";
|
|
1769
|
+
if (reason === "company-bot")
|
|
1770
|
+
return "a company bot: runs in HQ Cloud, not on this Mac";
|
|
1771
|
+
return "not a bot this computer can run";
|
|
1772
|
+
}
|
|
1773
|
+
/** `hq bot list --remote` — the local bots this account owns, wherever they live. */
|
|
1774
|
+
async function runBotListRemote(opts) {
|
|
1775
|
+
const token = await ownerToken();
|
|
1776
|
+
const api = new BotApi({ token: async () => token });
|
|
1777
|
+
const rows = (await listMyBotsOrExplain(api, { json: opts.json, emptyResult: { bots: [] } })).map((bot) => {
|
|
1778
|
+
const name = localNameForRemoteBot(bot);
|
|
1779
|
+
const runnable = remoteBotRunnability(bot);
|
|
1780
|
+
return {
|
|
1781
|
+
name: name ?? bot.name ?? bot.agentUid,
|
|
1782
|
+
agentUid: bot.agentUid,
|
|
1783
|
+
kind: bot.botKind ?? "personal",
|
|
1784
|
+
online: bot.online === true,
|
|
1785
|
+
lastHeartbeatAt: bot.lastHeartbeatAt ?? null,
|
|
1786
|
+
here: name !== null && readBotConfig(botDir(name)) !== null,
|
|
1787
|
+
settings: describeBotLocalConfig(parseBotLocalConfig(bot.localConfig)),
|
|
1788
|
+
runnable: runnable.runnable,
|
|
1789
|
+
reason: runnable.reason,
|
|
1790
|
+
};
|
|
1791
|
+
});
|
|
1792
|
+
if (opts.json) {
|
|
1793
|
+
// `ok` on success too: the failure document carries `ok: false`, and a
|
|
1794
|
+
// consumer that learned to branch on it read a good listing as a failure.
|
|
1795
|
+
console.log(JSON.stringify({ ok: true, bots: rows }, null, 2));
|
|
1796
|
+
return;
|
|
1797
|
+
}
|
|
1798
|
+
if (rows.length === 0) {
|
|
1799
|
+
console.log(chalk.gray("You have no local bots in HQ. Create one with: hq bot create assistant"));
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1802
|
+
console.log(chalk.dim(`${"NAME".padEnd(16)} ${"AGENT".padEnd(26)} ${"KIND".padEnd(9)} ${"ONLINE".padEnd(8)} HERE`));
|
|
1803
|
+
for (const row of rows) {
|
|
1804
|
+
console.log(`${chalk.bold(row.name.padEnd(16))} ${row.agentUid.padEnd(26)} ${row.kind.padEnd(9)} ${row.online ? chalk.green("online".padEnd(8)) : chalk.gray("offline".padEnd(8))} ${row.here ? chalk.green("yes") : chalk.yellow("no")}${row.here ? "" : row.runnable ? chalk.dim(` — hq bot adopt ${row.name}`) : chalk.dim(` — ${notRunnableHint(row.reason)}`)}`);
|
|
1805
|
+
}
|
|
1806
|
+
if (rows.some((r) => !r.here && r.runnable))
|
|
1807
|
+
console.log(chalk.dim(`\nBring them all back with: hq bot restore`));
|
|
1808
|
+
}
|
|
1809
|
+
// ── registration ─────────────────────────────────────────────────────────────
|
|
1810
|
+
export function registerBotCommand(program) {
|
|
1811
|
+
const bot = program
|
|
1812
|
+
.command("bot")
|
|
1813
|
+
.description("Personal local bots that run on this computer with your own model login");
|
|
1814
|
+
registerBotContinuityCommand(bot);
|
|
1815
|
+
registerBotJobsCommand(bot, { requireBot, botTokenSupplier });
|
|
1816
|
+
bot.command("promote <name>")
|
|
1817
|
+
.description("Move this local bot to cloud compute, preserving its identity and private context")
|
|
1818
|
+
.requiredOption("--company <uid>", "Destination company identity")
|
|
1819
|
+
.option("--json", "Print promotion progress as JSON")
|
|
1820
|
+
.action((name, opts) => wrap(async () => {
|
|
1821
|
+
const { dir, config } = requireBot(name);
|
|
1822
|
+
const token = await ownerToken(false);
|
|
1823
|
+
const api = new BotApi({ token: async () => token });
|
|
1824
|
+
const result = await promoteLocalBot({ dir, config, companyUid: opts.company, api });
|
|
1825
|
+
const details = result.pending ? await api.promotionDetails(config.agentUid, opts.company) : null;
|
|
1826
|
+
if (opts.json)
|
|
1827
|
+
console.log(JSON.stringify({ ...result, pairing: details?.pairing ?? null, ...(details?.failure ? { failure: details.failure } : {}) }));
|
|
1828
|
+
else if (details?.failure)
|
|
1829
|
+
console.log(`${name}: ${details.failure}`);
|
|
1830
|
+
else if (details?.pairing)
|
|
1831
|
+
console.log(`${name}: connect the cloud bot to your ChatGPT subscription at ${details.pairing.url}, using code ${details.pairing.code}. Then run the same command to continue.`);
|
|
1832
|
+
else
|
|
1833
|
+
console.log(`${name}: ${result.promotion.phase}.${result.pending ? " Run the same command to continue promotion." : ""}`);
|
|
1834
|
+
})());
|
|
1835
|
+
bot
|
|
1836
|
+
.command("create <name>")
|
|
1837
|
+
.description("Provision a local bot identity, scaffold its worker folder, install its launchd agent, and start it")
|
|
1838
|
+
.option("--kind <kind>", `personal (acts as you, everywhere you are) or company (acts as itself, as a member of its companies). Default: personal, or company when created from a company worker`)
|
|
1839
|
+
.option("--company <slug>", "Company bots: a company the bot is a member of (repeat for several); the owner invites it on creation", collectRepeated, [])
|
|
1840
|
+
.option("--runtime <runtime>", `Model CLI to run the bot with: ${BOT_RUNTIMES.join("|")}`, "claude")
|
|
1841
|
+
.option("--model <model>", "Model override passed to the runtime CLI")
|
|
1842
|
+
.option("--effort <level>", `Thinking level: claude ${BOT_EFFORT_LEVELS.claude.join("|")}; codex ${BOT_EFFORT_LEVELS.codex.join("|")}; grok ${BOT_EFFORT_LEVELS.grok.join("|")} (default ${DEFAULT_BOT_EFFORT})`)
|
|
1843
|
+
.option("--no-auto-approve", "Keep the runtime's permission gates (default: everything pre-approved; a headless bot cannot answer prompts)")
|
|
1844
|
+
.option("--worker <workerId>", "Run an existing company or core worker (see hq bot workers): its instructions, skills, context, and company policies; a company worker binds the bot to that company on every turn")
|
|
1845
|
+
.option("--display-name <name>", `The human name people see for the bot in HQ (e.g. Maya; up to ${BOT_DISPLAY_NAME_MAX_CHARS} chars). The handle stays <name>`)
|
|
1846
|
+
.option("--intro <text>", `The hello the bot DMs you when it first comes online (verbatim, up to ${BOT_INTRO_MAX_CHARS} chars)`)
|
|
1847
|
+
.option("--kickoff <prompt>", `A first task the bot starts on by itself right after its hello: one model turn run as if you had sent it, answered in your DM, once (up to ${BOT_KICKOFF_MAX_CHARS} chars)`)
|
|
1848
|
+
.option("--memory <where>", "Where the bot keeps its memory: synced (personal/workers/<name>/memory in HQ) or local (~/.hq/bots/<name>/memory, this computer only)", "synced")
|
|
1849
|
+
.option("--no-daemon", "Do not install the launchd agent (macOS)")
|
|
1850
|
+
.option("--no-start", "Create without starting")
|
|
1851
|
+
.option("--json", "Print machine-readable JSON")
|
|
1852
|
+
.action((name, opts) => wrap(() => runBotCreate(name, opts))());
|
|
1853
|
+
bot
|
|
1854
|
+
.command("companies <name>")
|
|
1855
|
+
.description("Show or change which companies a company bot is a member of (you invite or remove it; a personal bot has no list of its own)")
|
|
1856
|
+
.option("--add <slug>", "Add the bot to this company (repeatable)", collectRepeated, [])
|
|
1857
|
+
.option("--remove <slug>", "Remove the bot from this company (repeatable)", collectRepeated, [])
|
|
1858
|
+
.option("--json", "Print machine-readable JSON")
|
|
1859
|
+
.action((name, opts) => wrap(() => runBotCompanies(name, opts))());
|
|
1860
|
+
bot
|
|
1861
|
+
.command("workers")
|
|
1862
|
+
.description("List the workers (templates) a bot can be created from (hq bot create <name> --worker <id>)")
|
|
1863
|
+
.option("--json", "Print machine-readable JSON")
|
|
1864
|
+
.action((opts) => wrap(() => runBotWorkers(opts))());
|
|
1865
|
+
bot
|
|
1866
|
+
.command("list")
|
|
1867
|
+
.description("List local bots with runtime, online state, and pid")
|
|
1868
|
+
.option("--remote", "List the local bots your HQ account owns instead, with a here column saying which are set up on this computer")
|
|
1869
|
+
.option("--json", "Print machine-readable JSON")
|
|
1870
|
+
.action((opts) => wrap(() => runBotList(opts))());
|
|
1871
|
+
bot
|
|
1872
|
+
.command("adopt <name>")
|
|
1873
|
+
.description("Bring a bot you already own back to this computer: new machine credentials, its saved settings, worker folder, and startup agent")
|
|
1874
|
+
.option("--runtime <runtime>", `Model CLI to run it with, overriding its saved one: ${BOT_RUNTIMES.join("|")}`)
|
|
1875
|
+
.option("--model <model>", "Model override passed to the runtime CLI, replacing its saved one")
|
|
1876
|
+
.option("--no-daemon", "Do not install the launchd agent (macOS)")
|
|
1877
|
+
.option("--no-start", "Bring it back without starting it")
|
|
1878
|
+
.option("--json", "Print machine-readable JSON")
|
|
1879
|
+
.action((name, opts) => wrap(() => runBotAdopt(name, opts))());
|
|
1880
|
+
bot
|
|
1881
|
+
.command("restore")
|
|
1882
|
+
.description("Bring back every local bot your HQ account owns that is not set up on this computer (after a reinstall, or on a new Mac)")
|
|
1883
|
+
.option("--all", "Also re-issue credentials and reinstall the startup agent for the bots already set up here")
|
|
1884
|
+
.option("--dry-run", "Show what would be brought back, change nothing")
|
|
1885
|
+
.option("--no-daemon", "Do not install the launchd agents (macOS)")
|
|
1886
|
+
.option("--no-start", "Bring them back without starting them")
|
|
1887
|
+
.option("--json", "Print machine-readable JSON")
|
|
1888
|
+
.action((opts) => wrap(() => runBotRestore(opts))());
|
|
1889
|
+
bot
|
|
1890
|
+
.command("start <name>")
|
|
1891
|
+
.description("Start the bot (via launchd when installed, else a detached process)")
|
|
1892
|
+
.option("--json", "Print machine-readable JSON")
|
|
1893
|
+
.action((name, opts) => wrap(async () => {
|
|
1894
|
+
const b = requireBot(name);
|
|
1895
|
+
await startBot(b.name, b.dir, b.config, opts.json ? "json" : "text");
|
|
1896
|
+
})());
|
|
1897
|
+
bot
|
|
1898
|
+
.command("stop <name>")
|
|
1899
|
+
.description("Stop the bot process (it starts again at next login while its launchd agent is installed)")
|
|
1900
|
+
.option("--json", "Print machine-readable JSON")
|
|
1901
|
+
.action((name, opts) => wrap(async () => {
|
|
1902
|
+
const b = requireBot(name);
|
|
1903
|
+
await stopBot(b.name, b.dir, b.config, opts.json ? "json" : "text");
|
|
1904
|
+
})());
|
|
1905
|
+
bot
|
|
1906
|
+
.command("restart <name>")
|
|
1907
|
+
.description("Stop then start the bot")
|
|
1908
|
+
.option("--json", "Print machine-readable JSON")
|
|
1909
|
+
.action((name, opts) => wrap(async () => {
|
|
1910
|
+
const b = requireBot(name);
|
|
1911
|
+
const running = readBotStatus(b.dir);
|
|
1912
|
+
if (running && isPidAlive(running.pid) && selfStopProbe(running.pid)) {
|
|
1913
|
+
// The stop half would end this turn (and this command) before the
|
|
1914
|
+
// start half ran, leaving the bot down. Say so instead.
|
|
1915
|
+
throw new Error(`${b.name} cannot restart itself from inside one of its own turns: the stop would end this turn before the start ran. Run \`hq bot restart ${b.name}\` from outside the bot, or \`hq bot stop ${b.name}\` to stop it after this turn.`);
|
|
1916
|
+
}
|
|
1917
|
+
await stopBot(b.name, b.dir, b.config, "silent");
|
|
1918
|
+
await startBot(b.name, b.dir, b.config, opts.json ? "json" : "text");
|
|
1919
|
+
})());
|
|
1920
|
+
bot
|
|
1921
|
+
.command("status <name>")
|
|
1922
|
+
.description("Show the bot's process, heartbeat, launchd, and credential state")
|
|
1923
|
+
.option("--json", "Print machine-readable JSON")
|
|
1924
|
+
.action((name, opts) => wrap(() => runBotStatus(name, opts))());
|
|
1925
|
+
bot
|
|
1926
|
+
.command("logs <name>")
|
|
1927
|
+
.description("Show the bot's log")
|
|
1928
|
+
.option("-f, --follow", "Keep printing new lines")
|
|
1929
|
+
.option("-n, --lines <n>", "Number of trailing lines", "50")
|
|
1930
|
+
.action((name, opts) => wrap(() => runBotLogs(name, opts))());
|
|
1931
|
+
bot
|
|
1932
|
+
.command("run <name>")
|
|
1933
|
+
.description("Run the bot in the foreground (this is what launchd runs)")
|
|
1934
|
+
.action((name) => wrap(() => runBotResident(name))());
|
|
1935
|
+
bot
|
|
1936
|
+
.command("rm <name>")
|
|
1937
|
+
.description("Stop the bot, remove its launchd agent, delete its cloud identity and local state")
|
|
1938
|
+
.option("-y, --yes", "Skip the confirmation prompt")
|
|
1939
|
+
.option("--remote", "When the bot's local settings are already gone, also delete the bot HQ still has under that name")
|
|
1940
|
+
.option("--json", "Print machine-readable JSON")
|
|
1941
|
+
.action((name, opts) => wrap(() => runBotRm(name, opts))());
|
|
1942
|
+
bot
|
|
1943
|
+
.command("set <name>")
|
|
1944
|
+
.description("Change the model and thinking level a bot uses (applies from its next message)")
|
|
1945
|
+
.option("--model <model>", "Model id for the runtime CLI, or default for the CLI's own default")
|
|
1946
|
+
.option("--effort <level>", `Thinking level (claude ${BOT_EFFORT_LEVELS.claude.join("|")}; codex ${BOT_EFFORT_LEVELS.codex.join("|")}; grok ${BOT_EFFORT_LEVELS.grok.join("|")}), or default (${DEFAULT_BOT_EFFORT})`)
|
|
1947
|
+
.option("--json", "Print machine-readable JSON")
|
|
1948
|
+
.action((name, opts) => wrap(() => runBotSet(name, opts))());
|
|
1949
|
+
bot
|
|
1950
|
+
.command("intro <name>")
|
|
1951
|
+
.description("Have the bot re-send its introduction DM to you")
|
|
1952
|
+
.action((name) => wrap(() => runBotIntro(name))());
|
|
1953
|
+
const daemon = bot.command("daemon").description("Manage the bot's launchd user agent (macOS)");
|
|
1954
|
+
daemon
|
|
1955
|
+
.command("install <name>")
|
|
1956
|
+
.description("Install and load the LaunchAgent so the bot starts at login and restarts on crash")
|
|
1957
|
+
.option("--json", "Print machine-readable JSON")
|
|
1958
|
+
.action((name, opts) => wrap(async () => {
|
|
1959
|
+
const b = requireBot(name);
|
|
1960
|
+
const r = installBotAgent(buildBotDaemonPaths({ name: b.name, botDir: b.dir, hqRoot: b.config.hqRoot }));
|
|
1961
|
+
console.log(opts.json ? JSON.stringify({ ok: r.installed, ...r }, null, 2) : r.message);
|
|
1962
|
+
})());
|
|
1963
|
+
daemon
|
|
1964
|
+
.command("uninstall <name>")
|
|
1965
|
+
.description("Unload and remove the LaunchAgent")
|
|
1966
|
+
.option("--json", "Print machine-readable JSON")
|
|
1967
|
+
.action((name, opts) => wrap(async () => {
|
|
1968
|
+
const b = requireBot(name);
|
|
1969
|
+
const r = uninstallBotDaemon(buildBotDaemonPaths({ name: b.name, botDir: b.dir, hqRoot: b.config.hqRoot }), daemonDeps());
|
|
1970
|
+
console.log(opts.json ? JSON.stringify({ ok: true, ...r }, null, 2) : r.message);
|
|
1971
|
+
})());
|
|
1972
|
+
daemon
|
|
1973
|
+
.command("status <name>")
|
|
1974
|
+
.description("Show whether the LaunchAgent is installed and loaded")
|
|
1975
|
+
.option("--json", "Print machine-readable JSON")
|
|
1976
|
+
.action((name, opts) => wrap(async () => {
|
|
1977
|
+
const b = requireBot(name);
|
|
1978
|
+
const r = botDaemonStatus(buildBotDaemonPaths({ name: b.name, botDir: b.dir, hqRoot: b.config.hqRoot }), daemonDeps());
|
|
1979
|
+
console.log(opts.json ? JSON.stringify({ ok: true, ...r }, null, 2) : r.message);
|
|
1980
|
+
})());
|
|
1981
|
+
}
|
|
1982
|
+
//# sourceMappingURL=bot.js.map
|