@ouro.bot/cli 0.1.0-alpha.6 → 0.1.0-alpha.600
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +229 -183
- package/RepairGuide.ouro/agent.json +5 -0
- package/RepairGuide.ouro/psyche/IDENTITY.md +19 -0
- package/RepairGuide.ouro/psyche/SOUL.md +55 -0
- package/RepairGuide.ouro/skills/diagnose-broken-remote.md +63 -0
- package/RepairGuide.ouro/skills/diagnose-stacked-typed-issues.md +35 -0
- package/RepairGuide.ouro/skills/diagnose-sync-blocked.md +54 -0
- package/RepairGuide.ouro/skills/diagnose-vault-expired.md +60 -0
- package/SerpentGuide.ouro/agent.json +83 -0
- package/SerpentGuide.ouro/psyche/SOUL.md +25 -0
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +2 -2
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +1 -1
- package/assets/ouroboros.png +0 -0
- package/changelog.json +4182 -0
- package/dist/arc/attention-types.js +8 -0
- package/dist/arc/cares.js +140 -0
- package/dist/arc/episodes.js +117 -0
- package/dist/arc/intentions.js +133 -0
- package/dist/arc/json-store.js +117 -0
- package/dist/arc/obligations.js +254 -0
- package/dist/arc/packets.js +193 -0
- package/dist/arc/presence.js +185 -0
- package/dist/arc/task-lifecycle.js +65 -0
- package/dist/heart/active-work.js +989 -0
- package/dist/heart/agent-entry.js +69 -3
- package/dist/heart/attachments/image-normalize.js +194 -0
- package/dist/heart/attachments/materialize.js +97 -0
- package/dist/heart/attachments/originals.js +88 -0
- package/dist/heart/attachments/render.js +29 -0
- package/dist/heart/attachments/sources/adapter.js +2 -0
- package/dist/heart/attachments/sources/bluebubbles.js +156 -0
- package/dist/heart/attachments/sources/cli-local-file.js +78 -0
- package/dist/heart/attachments/sources/index.js +16 -0
- package/dist/heart/attachments/store.js +103 -0
- package/dist/heart/attachments/types.js +93 -0
- package/dist/heart/auth/auth-flow.js +479 -0
- package/dist/heart/awaiting/await-alert.js +146 -0
- package/dist/heart/awaiting/await-expiry.js +108 -0
- package/dist/heart/awaiting/await-loader.js +91 -0
- package/dist/heart/awaiting/await-parser.js +141 -0
- package/dist/heart/awaiting/await-runtime-state.js +97 -0
- package/dist/heart/awaiting/await-scheduler.js +377 -0
- package/dist/heart/background-operations.js +281 -0
- package/dist/heart/bridges/manager.js +358 -0
- package/dist/heart/bridges/state-machine.js +135 -0
- package/dist/heart/bridges/store.js +123 -0
- package/dist/heart/bundle-state.js +168 -0
- package/dist/heart/commitments.js +142 -0
- package/dist/heart/config-registry.js +322 -0
- package/dist/heart/config.js +164 -135
- package/dist/heart/core.js +1069 -260
- package/dist/heart/cross-chat-delivery.js +131 -0
- package/dist/heart/daemon/agent-config-check.js +419 -0
- package/dist/heart/daemon/agent-discovery.js +180 -0
- package/dist/heart/daemon/agent-service.js +522 -0
- package/dist/heart/daemon/agentic-repair.js +547 -0
- package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
- package/dist/heart/daemon/boot-sync-probe.js +197 -0
- package/dist/heart/daemon/cadence.js +70 -0
- package/dist/heart/daemon/cli-defaults.js +776 -0
- package/dist/heart/daemon/cli-exec.js +7571 -0
- package/dist/heart/daemon/cli-help.js +498 -0
- package/dist/heart/daemon/cli-parse.js +1599 -0
- package/dist/heart/daemon/cli-render-doctor.js +57 -0
- package/dist/heart/daemon/cli-render.js +763 -0
- package/dist/heart/daemon/cli-types.js +8 -0
- package/dist/heart/daemon/connect-bay.js +323 -0
- package/dist/heart/daemon/daemon-cli.js +30 -758
- package/dist/heart/daemon/daemon-entry.js +540 -8
- package/dist/heart/daemon/daemon-health.js +176 -0
- package/dist/heart/daemon/daemon-rollup.js +57 -0
- package/dist/heart/daemon/daemon-runtime-sync.js +287 -0
- package/dist/heart/daemon/daemon-tombstone.js +236 -0
- package/dist/heart/daemon/daemon.js +972 -20
- package/dist/heart/daemon/dns-workflow.js +394 -0
- package/dist/heart/daemon/doctor-types.js +8 -0
- package/dist/heart/daemon/doctor.js +873 -0
- package/dist/heart/daemon/health-monitor.js +122 -1
- package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
- package/dist/heart/daemon/hooks/bundle-meta.js +206 -0
- package/dist/heart/daemon/http-health-probe.js +80 -0
- package/dist/heart/daemon/human-command-screens.js +234 -0
- package/dist/heart/daemon/human-readiness.js +114 -0
- package/dist/heart/daemon/inner-status.js +89 -0
- package/dist/heart/daemon/interactive-repair.js +394 -0
- package/dist/heart/daemon/launchd.js +188 -0
- package/dist/heart/daemon/log-tailer.js +82 -12
- package/dist/heart/daemon/logs-prune.js +110 -0
- package/dist/heart/daemon/mcp-canary.js +297 -0
- package/dist/heart/daemon/message-router.js +17 -8
- package/dist/heart/daemon/os-cron-deps.js +135 -0
- package/dist/heart/daemon/os-cron.js +14 -12
- package/dist/heart/daemon/ouro-bot-entry.js +4 -2
- package/dist/heart/daemon/ouro-entry.js +3 -1
- package/dist/heart/daemon/process-manager.js +381 -26
- package/dist/heart/daemon/provider-discovery.js +137 -0
- package/dist/heart/daemon/provider-ping-progress.js +83 -0
- package/dist/heart/daemon/pulse.js +475 -0
- package/dist/heart/daemon/readiness-repair.js +365 -0
- package/dist/heart/daemon/run-hooks.js +39 -0
- package/dist/heart/daemon/runtime-logging.js +67 -16
- package/dist/heart/daemon/runtime-metadata.js +191 -0
- package/dist/heart/daemon/runtime-mode.js +67 -0
- package/dist/heart/daemon/safe-mode.js +161 -0
- package/dist/heart/daemon/sense-manager.js +731 -0
- package/dist/heart/daemon/session-id-resolver.js +131 -0
- package/dist/heart/daemon/skill-management-installer.js +94 -0
- package/dist/heart/daemon/socket-client.js +349 -0
- package/dist/heart/daemon/stale-bundle-prune.js +96 -0
- package/dist/heart/daemon/startup-tui.js +330 -0
- package/dist/heart/daemon/task-scheduler.js +3 -25
- package/dist/heart/daemon/terminal-ui.js +499 -0
- package/dist/heart/daemon/thoughts.js +524 -0
- package/dist/heart/daemon/up-progress.js +366 -0
- package/dist/heart/daemon/vault-items.js +56 -0
- package/dist/heart/delegation.js +62 -0
- package/dist/heart/habits/habit-migration.js +189 -0
- package/dist/heart/habits/habit-parser.js +140 -0
- package/dist/heart/habits/habit-runtime-state.js +100 -0
- package/dist/heart/habits/habit-scheduler.js +372 -0
- package/dist/heart/{daemon → hatch}/hatch-animation.js +10 -3
- package/dist/heart/{daemon → hatch}/hatch-flow.js +34 -136
- package/dist/heart/{daemon → hatch}/hatch-specialist.js +6 -8
- package/dist/heart/hatch/specialist-orchestrator.js +129 -0
- package/dist/heart/hatch/specialist-prompt.js +102 -0
- package/dist/heart/hatch/specialist-tools.js +306 -0
- package/dist/heart/identity.js +281 -67
- package/dist/heart/kept-notes.js +357 -0
- package/dist/heart/kicks.js +2 -20
- package/dist/heart/machine-identity.js +161 -0
- package/dist/heart/mail-import-discovery.js +353 -0
- package/dist/heart/mailbox/mailbox-http-hooks.js +66 -0
- package/dist/heart/mailbox/mailbox-http-response.js +7 -0
- package/dist/heart/mailbox/mailbox-http-routes.js +246 -0
- package/dist/heart/mailbox/mailbox-http-static.js +103 -0
- package/dist/heart/mailbox/mailbox-http-transport.js +116 -0
- package/dist/heart/mailbox/mailbox-http.js +99 -0
- package/dist/heart/mailbox/mailbox-read.js +31 -0
- package/dist/heart/mailbox/mailbox-types.js +27 -0
- package/dist/heart/mailbox/mailbox-view.js +195 -0
- package/dist/heart/mailbox/readers/agent-machine.js +382 -0
- package/dist/heart/mailbox/readers/continuity-readers.js +338 -0
- package/dist/heart/mailbox/readers/mail.js +367 -0
- package/dist/heart/mailbox/readers/runtime-readers.js +651 -0
- package/dist/heart/mailbox/readers/sessions.js +232 -0
- package/dist/heart/mailbox/readers/shared.js +111 -0
- package/dist/heart/mcp/mcp-server.js +656 -0
- package/dist/heart/migrate-config.js +100 -0
- package/dist/heart/model-capabilities.js +59 -0
- package/dist/heart/platform.js +81 -0
- package/dist/heart/progress-story.js +42 -0
- package/dist/heart/provider-attempt.js +134 -0
- package/dist/heart/provider-binding-resolver.js +267 -0
- package/dist/heart/provider-credentials.js +425 -0
- package/dist/heart/provider-failover.js +301 -0
- package/dist/heart/provider-models.js +81 -0
- package/dist/heart/provider-ping.js +262 -0
- package/dist/heart/provider-readiness-cache.js +40 -0
- package/dist/heart/provider-visibility.js +188 -0
- package/dist/heart/providers/anthropic-token.js +131 -0
- package/dist/heart/providers/anthropic.js +202 -50
- package/dist/heart/providers/azure.js +104 -13
- package/dist/heart/providers/error-classification.js +127 -0
- package/dist/heart/providers/github-copilot.js +145 -0
- package/dist/heart/providers/minimax-vlm.js +189 -0
- package/dist/heart/providers/minimax.js +29 -7
- package/dist/heart/providers/openai-codex.js +63 -39
- package/dist/heart/runtime-capability-check.js +170 -0
- package/dist/heart/runtime-credentials.js +367 -0
- package/dist/heart/runtime-cwd.js +87 -0
- package/dist/heart/sense-truth.js +70 -0
- package/dist/heart/session-activity.js +190 -0
- package/dist/heart/session-events.js +1149 -0
- package/dist/heart/session-playback-cli-main.js +5 -0
- package/dist/heart/session-playback-cli.js +36 -0
- package/dist/heart/session-playback.js +231 -0
- package/dist/heart/session-stats-cli-main.js +5 -0
- package/dist/heart/session-stats.js +182 -0
- package/dist/heart/session-transcript.js +243 -0
- package/dist/heart/start-of-turn-packet.js +345 -0
- package/dist/heart/streaming.js +129 -34
- package/dist/heart/sync-classification.js +176 -0
- package/dist/heart/sync.js +449 -0
- package/dist/heart/target-resolution.js +127 -0
- package/dist/heart/tempo.js +93 -0
- package/dist/heart/temporal-view.js +41 -0
- package/dist/heart/timeouts.js +101 -0
- package/dist/heart/tool-activity-callbacks.js +59 -0
- package/dist/heart/tool-description.js +143 -0
- package/dist/heart/tool-friction.js +55 -0
- package/dist/heart/tool-loop.js +200 -0
- package/dist/heart/turn-context.js +421 -0
- package/dist/heart/turn-coordinator.js +28 -0
- package/dist/heart/versioning/ouro-bot-global-installer.js +129 -0
- package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
- package/dist/heart/versioning/ouro-path-installer.js +426 -0
- package/dist/heart/{daemon → versioning}/ouro-uti.js +11 -2
- package/dist/heart/versioning/ouro-version-manager.js +295 -0
- package/dist/heart/versioning/staged-restart.js +146 -0
- package/dist/heart/versioning/update-checker.js +116 -0
- package/dist/heart/versioning/update-hooks.js +142 -0
- package/dist/heart/versioning/wrapper-publish-guard.js +86 -0
- package/dist/mailbox-ui/assets/index-B-461hes.js +61 -0
- package/dist/mailbox-ui/assets/index-BPr5vNuM.css +1 -0
- package/dist/mailbox-ui/index.html +15 -0
- package/dist/mailroom/attention.js +167 -0
- package/dist/mailroom/autonomy.js +209 -0
- package/dist/mailroom/blob-store.js +700 -0
- package/dist/mailroom/body-cache.js +61 -0
- package/dist/mailroom/core.js +788 -0
- package/dist/mailroom/entry.js +160 -0
- package/dist/mailroom/file-store.js +457 -0
- package/dist/mailroom/mbox-import.js +393 -0
- package/dist/mailroom/migration.js +164 -0
- package/dist/mailroom/outbound.js +380 -0
- package/dist/mailroom/policy.js +263 -0
- package/dist/mailroom/reader.js +233 -0
- package/dist/mailroom/search-cache.js +268 -0
- package/dist/mailroom/search-relevance.js +319 -0
- package/dist/mailroom/smtp-ingress.js +176 -0
- package/dist/mailroom/source-state.js +176 -0
- package/dist/mailroom/thread.js +109 -0
- package/dist/mailroom/travel-extract.js +89 -0
- package/dist/mind/bundle-manifest.js +77 -1
- package/dist/mind/context.js +174 -94
- package/dist/mind/diary-integrity.js +60 -0
- package/dist/mind/{memory.js → diary.js} +84 -96
- package/dist/mind/embedding-provider.js +60 -0
- package/dist/mind/file-state.js +179 -0
- package/dist/mind/first-impressions.js +16 -2
- package/dist/mind/friends/channel.js +74 -0
- package/dist/mind/friends/group-context.js +144 -0
- package/dist/mind/friends/resolver.js +54 -2
- package/dist/mind/friends/store-file.js +58 -3
- package/dist/mind/friends/trust-explanation.js +74 -0
- package/dist/mind/friends/types.js +10 -2
- package/dist/mind/journal-index.js +161 -0
- package/dist/mind/note-search.js +268 -0
- package/dist/mind/obligation-steering.js +221 -0
- package/dist/mind/pending.js +76 -9
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt-refresh.js +3 -2
- package/dist/mind/prompt.js +1267 -130
- package/dist/mind/provenance-trust.js +26 -0
- package/dist/mind/scrutiny.js +173 -0
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/cli-logging.js +22 -3
- package/dist/nerves/coverage/audit-rules.js +15 -6
- package/dist/nerves/coverage/audit.js +28 -2
- package/dist/nerves/coverage/cli.js +1 -1
- package/dist/nerves/coverage/contract.js +5 -5
- package/dist/nerves/coverage/file-completeness.js +129 -5
- package/dist/nerves/coverage/run-artifacts.js +1 -1
- package/dist/nerves/event-buffer.js +111 -0
- package/dist/nerves/index.js +224 -4
- package/dist/nerves/observation.js +20 -0
- package/dist/nerves/redact.js +79 -0
- package/dist/nerves/review/cli-main.js +5 -0
- package/dist/nerves/review/cli.js +156 -0
- package/dist/nerves/review/core.js +152 -0
- package/dist/nerves/runtime.js +5 -1
- package/dist/repertoire/ado-client.js +17 -56
- package/dist/repertoire/ado-semantic.js +11 -10
- package/dist/repertoire/api-client.js +97 -0
- package/dist/repertoire/bitwarden-store.js +997 -0
- package/dist/repertoire/bundle-templates.js +72 -0
- package/dist/repertoire/bw-installer.js +180 -0
- package/dist/repertoire/coding/codex-jsonl.js +64 -0
- package/dist/repertoire/coding/context-pack.js +330 -0
- package/dist/repertoire/coding/feedback.js +301 -0
- package/dist/repertoire/coding/index.js +4 -1
- package/dist/repertoire/coding/manager.js +220 -13
- package/dist/repertoire/coding/spawner.js +58 -12
- package/dist/repertoire/coding/tools.js +209 -7
- package/dist/repertoire/commerce-errors.js +109 -0
- package/dist/repertoire/commerce-self-test.js +156 -0
- package/dist/repertoire/credential-access.js +178 -0
- package/dist/repertoire/data/ado-endpoints.json +188 -0
- package/dist/repertoire/duffel-client.js +185 -0
- package/dist/repertoire/github-client.js +14 -55
- package/dist/repertoire/graph-client.js +11 -52
- package/dist/repertoire/guardrails.js +396 -0
- package/dist/repertoire/mcp-client.js +295 -0
- package/dist/repertoire/mcp-manager.js +362 -0
- package/dist/repertoire/mcp-tools.js +63 -0
- package/dist/repertoire/shell-sessions.js +133 -0
- package/dist/repertoire/skills.js +15 -24
- package/dist/repertoire/stripe-client.js +131 -0
- package/dist/repertoire/tasks/board.js +43 -5
- package/dist/repertoire/tasks/fix.js +182 -0
- package/dist/repertoire/tasks/index.js +39 -13
- package/dist/repertoire/tasks/lifecycle.js +2 -2
- package/dist/repertoire/tasks/parser.js +3 -2
- package/dist/repertoire/tasks/scanner.js +194 -37
- package/dist/repertoire/tasks/transitions.js +16 -79
- package/dist/repertoire/tool-results.js +29 -0
- package/dist/repertoire/tools-attachments.js +317 -0
- package/dist/repertoire/tools-awaiting.js +360 -0
- package/dist/repertoire/tools-base.js +56 -707
- package/dist/repertoire/tools-bluebubbles.js +94 -0
- package/dist/repertoire/tools-bridge.js +142 -0
- package/dist/repertoire/tools-bundle.js +984 -0
- package/dist/repertoire/tools-config.js +185 -0
- package/dist/repertoire/tools-continuity.js +248 -0
- package/dist/repertoire/tools-credential.js +381 -0
- package/dist/repertoire/tools-files.js +342 -0
- package/dist/repertoire/tools-flight.js +224 -0
- package/dist/repertoire/tools-flow.js +119 -0
- package/dist/repertoire/tools-github.js +1 -7
- package/dist/repertoire/tools-mail.js +1916 -0
- package/dist/repertoire/tools-notes.js +421 -0
- package/dist/repertoire/tools-obligations.js +142 -0
- package/dist/repertoire/tools-runtime.js +61 -0
- package/dist/repertoire/tools-session.js +809 -0
- package/dist/repertoire/tools-shell.js +120 -0
- package/dist/repertoire/tools-stripe.js +180 -0
- package/dist/repertoire/tools-surface.js +345 -0
- package/dist/repertoire/tools-teams.js +64 -61
- package/dist/repertoire/tools-travel.js +125 -0
- package/dist/repertoire/tools-trip.js +604 -0
- package/dist/repertoire/tools-user-profile.js +144 -0
- package/dist/repertoire/tools-vault.js +40 -0
- package/dist/repertoire/tools-voice.js +144 -0
- package/dist/repertoire/tools.js +154 -98
- package/dist/repertoire/travel-api-client.js +360 -0
- package/dist/repertoire/user-profile.js +131 -0
- package/dist/repertoire/vault-setup.js +246 -0
- package/dist/repertoire/vault-unlock.js +594 -0
- package/dist/scripts/claude-code-hook.js +41 -0
- package/dist/scripts/claude-code-stop-hook.js +47 -0
- package/dist/senses/attention-queue.js +116 -0
- package/dist/senses/await-turn-message.js +58 -0
- package/dist/senses/bluebubbles/active-turns.js +216 -0
- package/dist/senses/bluebubbles/attachment-cache.js +53 -0
- package/dist/senses/bluebubbles/attachment-download.js +137 -0
- package/dist/senses/bluebubbles/client.js +685 -0
- package/dist/senses/bluebubbles/entry.js +77 -0
- package/dist/senses/bluebubbles/inbound-log.js +126 -0
- package/dist/senses/bluebubbles/index.js +2548 -0
- package/dist/senses/bluebubbles/media.js +389 -0
- package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +45 -16
- package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +46 -6
- package/dist/senses/bluebubbles/processed-log.js +133 -0
- package/dist/senses/bluebubbles/replay.js +137 -0
- package/dist/senses/bluebubbles/runtime-state.js +137 -0
- package/dist/senses/bluebubbles/session-cleanup.js +72 -0
- package/dist/senses/bluebubbles-meta-guard.js +40 -0
- package/dist/senses/cli/bracketed-paste.js +82 -0
- package/dist/senses/cli/image-paste.js +287 -0
- package/dist/senses/cli/image-ref-navigation.js +75 -0
- package/dist/senses/cli/ink-app.js +156 -0
- package/dist/senses/cli/inline-diff.js +64 -0
- package/dist/senses/cli/input-keys.js +174 -0
- package/dist/senses/cli/kill-ring.js +86 -0
- package/dist/senses/cli/message-list.js +51 -0
- package/dist/senses/cli/ouro-tui.js +607 -0
- package/dist/senses/cli/spinner-imperative.js +135 -0
- package/dist/senses/cli/spinner.js +101 -0
- package/dist/senses/cli/status-line.js +60 -0
- package/dist/senses/cli/streaming-markdown.js +526 -0
- package/dist/senses/cli/tool-display.js +85 -0
- package/dist/senses/cli/tool-render.js +85 -0
- package/dist/senses/cli/tui-store.js +240 -0
- package/dist/senses/cli/virtual-list.js +35 -0
- package/dist/senses/cli-entry.js +60 -8
- package/dist/senses/cli-layout.js +187 -0
- package/dist/senses/cli.js +777 -264
- package/dist/senses/commands.js +66 -3
- package/dist/senses/continuity.js +94 -0
- package/dist/senses/habit-turn-message.js +108 -0
- package/dist/senses/inner-dialog-worker.js +209 -16
- package/dist/senses/inner-dialog.js +682 -91
- package/dist/senses/mail-entry.js +66 -0
- package/dist/senses/mail.js +379 -0
- package/dist/senses/pipeline.js +751 -0
- package/dist/senses/proactive-content-guard.js +51 -0
- package/dist/senses/shared-turn.js +392 -0
- package/dist/senses/surface-tool.js +70 -0
- package/dist/senses/teams-entry.js +60 -8
- package/dist/senses/teams.js +925 -195
- package/dist/senses/trust-gate.js +207 -2
- package/dist/senses/voice/audio-playback.js +237 -0
- package/dist/senses/voice/audio-routing.js +119 -0
- package/dist/senses/voice/elevenlabs.js +202 -0
- package/dist/senses/voice/floor-control.js +431 -0
- package/dist/senses/voice/floor-controller.js +115 -0
- package/dist/senses/voice/golden-path.js +116 -0
- package/dist/senses/voice/index.js +29 -0
- package/dist/senses/voice/meeting.js +113 -0
- package/dist/senses/voice/outbound.js +190 -0
- package/dist/senses/voice/phone.js +33 -0
- package/dist/senses/voice/playback.js +139 -0
- package/dist/senses/voice/realtime-eval.js +496 -0
- package/dist/senses/voice/realtime-trace.js +531 -0
- package/dist/senses/voice/transcript.js +70 -0
- package/dist/senses/voice/turn.js +191 -0
- package/dist/senses/voice/twilio-phone-runtime.js +807 -0
- package/dist/senses/voice/twilio-phone.js +5077 -0
- package/dist/senses/voice/types.js +2 -0
- package/dist/senses/voice/whisper.js +161 -0
- package/dist/senses/voice-entry.js +81 -0
- package/dist/senses/voice-realtime-eval-command.js +99 -0
- package/dist/senses/voice-realtime-eval-entry.js +21 -0
- package/dist/senses/voice-twilio-entry.js +87 -0
- package/dist/trips/core.js +138 -0
- package/dist/trips/store.js +265 -0
- package/package.json +52 -7
- package/skills/agent-commerce.md +106 -0
- package/skills/browser-navigation.md +117 -0
- package/skills/commerce-setup-guide.md +116 -0
- package/skills/commerce-setup.md +84 -0
- package/skills/configure-dev-tools.md +99 -0
- package/skills/travel-planning.md +138 -0
- package/AdoptionSpecialist.ouro/agent.json +0 -20
- package/AdoptionSpecialist.ouro/psyche/SOUL.md +0 -22
- package/dist/heart/daemon/specialist-orchestrator.js +0 -160
- package/dist/heart/daemon/specialist-prompt.js +0 -40
- package/dist/heart/daemon/specialist-session.js +0 -142
- package/dist/heart/daemon/specialist-tools.js +0 -128
- package/dist/heart/daemon/subagent-installer.js +0 -125
- package/dist/inner-worker-entry.js +0 -4
- package/dist/mind/associative-recall.js +0 -197
- package/dist/senses/bluebubbles-client.js +0 -279
- package/dist/senses/bluebubbles-entry.js +0 -11
- package/dist/senses/bluebubbles.js +0 -332
- package/subagents/README.md +0 -73
- package/subagents/work-doer.md +0 -233
- package/subagents/work-merger.md +0 -624
- package/subagents/work-planner.md +0 -373
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
package/dist/senses/cli.js
CHANGED
|
@@ -33,11 +33,20 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.MarkdownStreamer = exports.InputController = exports.Spinner = void 0;
|
|
36
|
+
exports.InputQueue = exports.MarkdownStreamer = exports.InputController = exports.Spinner = exports.StreamingWordWrapper = exports.wrapCliText = exports.formatEchoedInputSummary = void 0;
|
|
37
|
+
exports.formatPendingPrefix = formatPendingPrefix;
|
|
38
|
+
exports.getCliContinuityIngressTexts = getCliContinuityIngressTexts;
|
|
39
|
+
exports.formatTimeAgo = formatTimeAgo;
|
|
40
|
+
exports.writeCliAsyncAssistantMessage = writeCliAsyncAssistantMessage;
|
|
41
|
+
exports.pauseActiveSpinner = pauseActiveSpinner;
|
|
42
|
+
exports.resumeActiveSpinner = resumeActiveSpinner;
|
|
43
|
+
exports.setActiveSpinner = setActiveSpinner;
|
|
37
44
|
exports.handleSigint = handleSigint;
|
|
38
45
|
exports.addHistory = addHistory;
|
|
39
46
|
exports.renderMarkdown = renderMarkdown;
|
|
40
47
|
exports.createCliCallbacks = createCliCallbacks;
|
|
48
|
+
exports.createDebouncedLines = createDebouncedLines;
|
|
49
|
+
exports.runCliSession = runCliSession;
|
|
41
50
|
exports.main = main;
|
|
42
51
|
const readline = __importStar(require("readline"));
|
|
43
52
|
const os = __importStar(require("os"));
|
|
@@ -47,11 +56,12 @@ const prompt_1 = require("../mind/prompt");
|
|
|
47
56
|
const phrases_1 = require("../mind/phrases");
|
|
48
57
|
const format_1 = require("../mind/format");
|
|
49
58
|
const config_1 = require("../heart/config");
|
|
59
|
+
const session_events_1 = require("../heart/session-events");
|
|
50
60
|
const context_1 = require("../mind/context");
|
|
51
61
|
const pending_1 = require("../mind/pending");
|
|
52
|
-
const prompt_refresh_1 = require("../mind/prompt-refresh");
|
|
53
62
|
const commands_1 = require("./commands");
|
|
54
63
|
const identity_1 = require("../heart/identity");
|
|
64
|
+
const mcp_manager_1 = require("../repertoire/mcp-manager");
|
|
55
65
|
const nerves_1 = require("../nerves");
|
|
56
66
|
const store_file_1 = require("../mind/friends/store-file");
|
|
57
67
|
const resolver_1 = require("../mind/friends/resolver");
|
|
@@ -59,59 +69,78 @@ const tokens_1 = require("../mind/friends/tokens");
|
|
|
59
69
|
const cli_logging_1 = require("../nerves/cli-logging");
|
|
60
70
|
const runtime_1 = require("../nerves/runtime");
|
|
61
71
|
const trust_gate_1 = require("./trust-gate");
|
|
72
|
+
const pipeline_1 = require("./pipeline");
|
|
73
|
+
const channel_1 = require("../mind/friends/channel");
|
|
62
74
|
const session_lock_1 = require("./session-lock");
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
75
|
+
const update_hooks_1 = require("../heart/versioning/update-hooks");
|
|
76
|
+
const bundle_meta_1 = require("../heart/daemon/hooks/bundle-meta");
|
|
77
|
+
const agent_config_v2_1 = require("../heart/daemon/hooks/agent-config-v2");
|
|
78
|
+
const bundle_manifest_1 = require("../mind/bundle-manifest");
|
|
79
|
+
const cli_layout_1 = require("./cli-layout");
|
|
80
|
+
const image_paste_1 = require("./cli/image-paste");
|
|
81
|
+
const spinner_imperative_1 = require("./cli/spinner-imperative");
|
|
82
|
+
const tool_display_1 = require("./cli/tool-display");
|
|
83
|
+
var cli_layout_2 = require("./cli-layout");
|
|
84
|
+
Object.defineProperty(exports, "formatEchoedInputSummary", { enumerable: true, get: function () { return cli_layout_2.formatEchoedInputSummary; } });
|
|
85
|
+
Object.defineProperty(exports, "wrapCliText", { enumerable: true, get: function () { return cli_layout_2.wrapCliText; } });
|
|
86
|
+
Object.defineProperty(exports, "StreamingWordWrapper", { enumerable: true, get: function () { return cli_layout_2.StreamingWordWrapper; } });
|
|
87
|
+
/**
|
|
88
|
+
* Format pending messages as content-prefix strings for injection into
|
|
89
|
+
* the next user message. Self-messages (from === agentName) become
|
|
90
|
+
* `[inner thought: {content}]`, inter-agent messages become
|
|
91
|
+
* `[message from {name}: {content}]`.
|
|
92
|
+
*/
|
|
93
|
+
function formatPendingPrefix(messages, agentName) {
|
|
94
|
+
return messages
|
|
95
|
+
.map((msg) => msg.from === agentName
|
|
96
|
+
? `[inner thought: ${msg.content}]`
|
|
97
|
+
: `[message from ${msg.from}: ${msg.content}]`)
|
|
98
|
+
.join("\n");
|
|
99
|
+
}
|
|
100
|
+
function getCliContinuityIngressTexts(input) {
|
|
101
|
+
const trimmed = input.trim();
|
|
102
|
+
return trimmed ? [trimmed] : [];
|
|
103
|
+
}
|
|
104
|
+
/* v8 ignore start -- cosmetic time formatting @preserve */
|
|
105
|
+
function formatTimeAgo(date) {
|
|
106
|
+
const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
|
|
107
|
+
if (seconds < 60)
|
|
108
|
+
return "just now";
|
|
109
|
+
const minutes = Math.floor(seconds / 60);
|
|
110
|
+
if (minutes < 60)
|
|
111
|
+
return `${minutes}m ago`;
|
|
112
|
+
const hours = Math.floor(minutes / 60);
|
|
113
|
+
if (hours < 24)
|
|
114
|
+
return `${hours}h ago`;
|
|
115
|
+
const days = Math.floor(hours / 24);
|
|
116
|
+
return `${days}d ago`;
|
|
117
|
+
}
|
|
118
|
+
const CLI_PROMPT = "\x1b[36m) \x1b[0m";
|
|
119
|
+
function writeCliAsyncAssistantMessage(rl, message, stdout = process.stdout) {
|
|
120
|
+
const rlInt = rl;
|
|
121
|
+
const currentLine = rlInt.line ?? "";
|
|
122
|
+
const currentCursor = rlInt.cursor ?? currentLine.length;
|
|
123
|
+
stdout.write("\r\x1b[K");
|
|
124
|
+
stdout.write(`${renderMarkdown(message)}\n`);
|
|
125
|
+
stdout.write(CLI_PROMPT);
|
|
126
|
+
if (!currentLine)
|
|
127
|
+
return;
|
|
128
|
+
stdout.write(currentLine);
|
|
129
|
+
if (currentCursor < currentLine.length) {
|
|
130
|
+
readline.cursorTo(process.stdout, 2 + currentCursor);
|
|
112
131
|
}
|
|
113
132
|
}
|
|
114
|
-
|
|
133
|
+
// Module-level active spinner for log coordination.
|
|
134
|
+
// The terminal log sink calls these to avoid interleaving with spinner output.
|
|
135
|
+
let _activeSpinner = null;
|
|
136
|
+
/* v8 ignore start -- spinner coordination: exercised at runtime, not unit-testable without real terminal @preserve */
|
|
137
|
+
function pauseActiveSpinner() { _activeSpinner?.pause(); }
|
|
138
|
+
function resumeActiveSpinner() { _activeSpinner?.resume(); }
|
|
139
|
+
/* v8 ignore stop */
|
|
140
|
+
function setActiveSpinner(s) { _activeSpinner = s; }
|
|
141
|
+
// Re-export ImperativeSpinner as Spinner for backward compatibility
|
|
142
|
+
var spinner_imperative_2 = require("./cli/spinner-imperative");
|
|
143
|
+
Object.defineProperty(exports, "Spinner", { enumerable: true, get: function () { return spinner_imperative_2.ImperativeSpinner; } });
|
|
115
144
|
// Input controller: pauses readline during model/tool execution.
|
|
116
145
|
// Does NOT touch raw mode — readline with terminal:true manages raw mode
|
|
117
146
|
// internally. Touching it causes ^C to be echoed by the terminal driver.
|
|
@@ -279,43 +308,65 @@ function createCliCallbacks() {
|
|
|
279
308
|
meta: {},
|
|
280
309
|
});
|
|
281
310
|
let currentSpinner = null;
|
|
282
|
-
|
|
311
|
+
function setSpinner(s) { currentSpinner = s; setActiveSpinner(s); }
|
|
283
312
|
let hadToolRun = false;
|
|
284
313
|
let textDirty = false; // true when text/reasoning was written without a trailing newline
|
|
285
314
|
const streamer = new MarkdownStreamer();
|
|
315
|
+
const wrapper = new cli_layout_1.StreamingWordWrapper();
|
|
286
316
|
return {
|
|
287
317
|
onModelStart: () => {
|
|
288
318
|
currentSpinner?.stop();
|
|
289
|
-
|
|
290
|
-
hadReasoning = false;
|
|
319
|
+
setSpinner(null);
|
|
291
320
|
textDirty = false;
|
|
292
321
|
streamer.reset();
|
|
322
|
+
wrapper.reset();
|
|
293
323
|
const phrases = (0, phrases_1.getPhrases)();
|
|
294
324
|
const pool = hadToolRun ? phrases.followup : phrases.thinking;
|
|
295
325
|
const first = (0, phrases_1.pickPhrase)(pool);
|
|
296
|
-
|
|
326
|
+
setSpinner(new spinner_imperative_1.ImperativeSpinner(first, pool));
|
|
297
327
|
currentSpinner.start();
|
|
298
328
|
},
|
|
299
329
|
onModelStreamStart: () => {
|
|
300
|
-
|
|
301
|
-
|
|
330
|
+
// No-op: content callbacks (onTextChunk, onReasoningChunk) handle
|
|
331
|
+
// stopping the spinner. onModelStreamStart fires too early and
|
|
332
|
+
// doesn't fire at all for settle tool streaming.
|
|
333
|
+
},
|
|
334
|
+
onClearText: () => {
|
|
335
|
+
streamer.reset();
|
|
336
|
+
wrapper.reset();
|
|
302
337
|
},
|
|
303
338
|
onTextChunk: (text) => {
|
|
304
|
-
if
|
|
305
|
-
|
|
306
|
-
|
|
339
|
+
// Stop spinner if still running — settle streaming and Anthropic
|
|
340
|
+
// tool-only responses bypass onModelStreamStart, so the spinner would
|
|
341
|
+
// otherwise keep running (and its \r writes overwrite response text).
|
|
342
|
+
if (currentSpinner) {
|
|
343
|
+
currentSpinner.stop();
|
|
344
|
+
setSpinner(null);
|
|
307
345
|
}
|
|
308
346
|
const rendered = streamer.push(text);
|
|
309
|
-
|
|
310
|
-
|
|
347
|
+
/* v8 ignore start -- wrapper integration: tested via cli.test.ts onTextChunk tests @preserve */
|
|
348
|
+
if (rendered) {
|
|
349
|
+
const wrapped = wrapper.push(rendered);
|
|
350
|
+
if (wrapped)
|
|
351
|
+
process.stdout.write(wrapped);
|
|
352
|
+
}
|
|
353
|
+
/* v8 ignore stop */
|
|
311
354
|
textDirty = text.length > 0 && !text.endsWith("\n");
|
|
312
355
|
},
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
356
|
+
flushNow: () => {
|
|
357
|
+
// CLI flushes immediately on each onTextChunk; nothing buffered to push.
|
|
358
|
+
},
|
|
359
|
+
onReasoningChunk: (_text) => {
|
|
360
|
+
// Keep reasoning private in the CLI surface. The spinner continues to
|
|
361
|
+
// represent active thinking until actual tool or answer output arrives.
|
|
317
362
|
},
|
|
318
363
|
onToolStart: (_name, _args) => {
|
|
364
|
+
// speak is flow-control: its visible output is the message itself (delivered
|
|
365
|
+
// via onTextChunk/flushNow). Do NOT start a tool spinner — that would write
|
|
366
|
+
// a "running speak..." phrase to stderr right before the actual message
|
|
367
|
+
// arrives, which is the visual churn the user explicitly does not want.
|
|
368
|
+
if (_name === "speak")
|
|
369
|
+
return;
|
|
319
370
|
// Stop the model-start spinner: when the model returns only tool calls
|
|
320
371
|
// (no content/reasoning), onModelStreamStart never fires, so the old
|
|
321
372
|
// spinner's intervals would leak.
|
|
@@ -328,31 +379,29 @@ function createCliCallbacks() {
|
|
|
328
379
|
}
|
|
329
380
|
const toolPhrases = (0, phrases_1.getPhrases)().tool;
|
|
330
381
|
const first = (0, phrases_1.pickPhrase)(toolPhrases);
|
|
331
|
-
|
|
382
|
+
setSpinner(new spinner_imperative_1.ImperativeSpinner(first, toolPhrases));
|
|
332
383
|
currentSpinner.start();
|
|
333
384
|
hadToolRun = true;
|
|
334
385
|
},
|
|
335
386
|
onToolEnd: (name, argSummary, success) => {
|
|
336
387
|
currentSpinner?.stop();
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
const color = success ? "\x1b[32m" : "\x1b[31m";
|
|
340
|
-
process.stderr.write(`${color}${msg}\x1b[0m\n`);
|
|
388
|
+
setSpinner(null);
|
|
389
|
+
(0, tool_display_1.writeToolEnd)(name, argSummary, success);
|
|
341
390
|
},
|
|
342
391
|
onError: (error, severity) => {
|
|
343
392
|
if (severity === "transient") {
|
|
344
393
|
currentSpinner?.fail(error.message);
|
|
345
|
-
|
|
394
|
+
setSpinner(null);
|
|
346
395
|
}
|
|
347
396
|
else {
|
|
348
397
|
currentSpinner?.stop();
|
|
349
|
-
|
|
398
|
+
setSpinner(null);
|
|
350
399
|
process.stderr.write(`\x1b[31m${(0, format_1.formatError)(error)}\x1b[0m\n`);
|
|
351
400
|
}
|
|
352
401
|
},
|
|
353
402
|
onKick: () => {
|
|
354
403
|
currentSpinner?.stop();
|
|
355
|
-
|
|
404
|
+
setSpinner(null);
|
|
356
405
|
if (textDirty) {
|
|
357
406
|
process.stdout.write("\n");
|
|
358
407
|
textDirty = false;
|
|
@@ -361,27 +410,547 @@ function createCliCallbacks() {
|
|
|
361
410
|
},
|
|
362
411
|
flushMarkdown: () => {
|
|
363
412
|
currentSpinner?.stop();
|
|
364
|
-
|
|
413
|
+
setSpinner(null);
|
|
414
|
+
/* v8 ignore start -- wrapper flush: tested via cli.test.ts flushMarkdown tests @preserve */
|
|
365
415
|
const remaining = streamer.flush();
|
|
366
|
-
if (remaining)
|
|
367
|
-
|
|
416
|
+
if (remaining) {
|
|
417
|
+
const wrapped = wrapper.push(remaining);
|
|
418
|
+
if (wrapped)
|
|
419
|
+
process.stdout.write(wrapped);
|
|
420
|
+
}
|
|
421
|
+
const tail = wrapper.flush();
|
|
422
|
+
if (tail)
|
|
423
|
+
process.stdout.write(tail);
|
|
424
|
+
/* v8 ignore stop */
|
|
368
425
|
},
|
|
369
426
|
};
|
|
370
427
|
}
|
|
428
|
+
// Debounced line iterator: collects rapid-fire lines (paste) into a single input.
|
|
429
|
+
// When the debounce timeout wins the race, the pending iter.next() is saved
|
|
430
|
+
// and reused in the next iteration to prevent it from silently consuming input.
|
|
431
|
+
async function* createDebouncedLines(source, debounceMs) {
|
|
432
|
+
if (debounceMs <= 0) {
|
|
433
|
+
yield* source;
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
const iter = source[Symbol.asyncIterator]();
|
|
437
|
+
let pending = null;
|
|
438
|
+
while (true) {
|
|
439
|
+
const first = pending ? await pending : await iter.next();
|
|
440
|
+
pending = null;
|
|
441
|
+
if (first.done)
|
|
442
|
+
break;
|
|
443
|
+
const lines = [first.value];
|
|
444
|
+
let more = true;
|
|
445
|
+
while (more) {
|
|
446
|
+
const nextPromise = iter.next();
|
|
447
|
+
const raced = await Promise.race([
|
|
448
|
+
nextPromise.then((r) => ({ kind: "line", result: r })),
|
|
449
|
+
new Promise((r) => setTimeout(() => r({ kind: "timeout" }), debounceMs)),
|
|
450
|
+
]);
|
|
451
|
+
if (raced.kind === "timeout") {
|
|
452
|
+
pending = nextPromise;
|
|
453
|
+
more = false;
|
|
454
|
+
}
|
|
455
|
+
else if (raced.result.done) {
|
|
456
|
+
more = false;
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
lines.push(raced.result.value);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
yield lines.join("\n");
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Async queue that bridges push-based Ink input to pull-based async iteration.
|
|
467
|
+
* Input from Ink's onSubmit callback is pushed; the business logic loop awaits via for-await.
|
|
468
|
+
*/
|
|
469
|
+
class InputQueue {
|
|
470
|
+
queue = [];
|
|
471
|
+
resolve = null;
|
|
472
|
+
done = false;
|
|
473
|
+
push(input) {
|
|
474
|
+
if (this.done)
|
|
475
|
+
return;
|
|
476
|
+
if (this.resolve) {
|
|
477
|
+
const r = this.resolve;
|
|
478
|
+
this.resolve = null;
|
|
479
|
+
r({ value: input, done: false });
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
this.queue.push(input);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
close() {
|
|
486
|
+
this.done = true;
|
|
487
|
+
if (this.resolve) {
|
|
488
|
+
const r = this.resolve;
|
|
489
|
+
this.resolve = null;
|
|
490
|
+
r({ value: undefined, done: true });
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
/** Drain all buffered items, leaving any pending async awaiter untouched. */
|
|
494
|
+
drainAll() {
|
|
495
|
+
const items = [...this.queue];
|
|
496
|
+
this.queue = [];
|
|
497
|
+
return items;
|
|
498
|
+
}
|
|
499
|
+
[Symbol.asyncIterator]() {
|
|
500
|
+
return {
|
|
501
|
+
next: () => {
|
|
502
|
+
if (this.queue.length > 0) {
|
|
503
|
+
return Promise.resolve({ value: this.queue.shift(), done: false });
|
|
504
|
+
}
|
|
505
|
+
if (this.done) {
|
|
506
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
507
|
+
}
|
|
508
|
+
return new Promise((resolve) => {
|
|
509
|
+
this.resolve = resolve;
|
|
510
|
+
});
|
|
511
|
+
},
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
exports.InputQueue = InputQueue;
|
|
516
|
+
async function runCliSession(options) {
|
|
517
|
+
/* v8 ignore start -- integration: runCliSession is interactive, tested via E2E @preserve */
|
|
518
|
+
const registry = (0, commands_1.createCommandRegistry)();
|
|
519
|
+
if (!options.disableCommands) {
|
|
520
|
+
(0, commands_1.registerDefaultCommands)(registry);
|
|
521
|
+
}
|
|
522
|
+
const messages = options.messages
|
|
523
|
+
?? [{ role: "system", content: (0, prompt_1.flattenSystemPrompt)(await (0, prompt_1.buildSystem)("cli")) }];
|
|
524
|
+
// ─── Rendering: TUI (Ink + Static) for TTY, imperative for tests/pipes ───
|
|
525
|
+
const useTui = !options._testInputSource && process.stdin.isTTY === true;
|
|
526
|
+
let currentAbort = null;
|
|
527
|
+
let closed = false;
|
|
528
|
+
// eslint-disable-next-line prefer-const -- set by onImageMap callback during input
|
|
529
|
+
let pendingImages = null;
|
|
530
|
+
let cliCallbacks;
|
|
531
|
+
let tuiStore = null;
|
|
532
|
+
let inkRef = null;
|
|
533
|
+
const inputQueue = useTui ? new InputQueue() : null;
|
|
534
|
+
let rl = null;
|
|
535
|
+
if (useTui) {
|
|
536
|
+
try {
|
|
537
|
+
const [ink, React, tuiMod, storeMod] = await Promise.all([
|
|
538
|
+
Promise.resolve().then(() => __importStar(require("ink"))),
|
|
539
|
+
Promise.resolve().then(() => __importStar(require("react"))),
|
|
540
|
+
Promise.resolve().then(() => __importStar(require("./cli/ouro-tui"))),
|
|
541
|
+
Promise.resolve().then(() => __importStar(require("./cli/tui-store"))),
|
|
542
|
+
]);
|
|
543
|
+
const { OuroTui } = tuiMod;
|
|
544
|
+
const { TuiStore, createTuiCallbacks } = storeMod;
|
|
545
|
+
tuiStore = new TuiStore();
|
|
546
|
+
cliCallbacks = createTuiCallbacks(tuiStore);
|
|
547
|
+
// Seed input history from previous session (for up/down arrows) — NOT display
|
|
548
|
+
const prevUserMsgs = messages
|
|
549
|
+
.filter((msg) => msg.role === "user" && typeof msg.content === "string")
|
|
550
|
+
.map(msg => msg.content);
|
|
551
|
+
tuiStore.seedHistory(prevUserMsgs);
|
|
552
|
+
// Show session resume context: last 2 exchanges as normal messages
|
|
553
|
+
if (messages.length > 1) {
|
|
554
|
+
const userAssistantMsgs = messages.filter((m) => (m.role === "user" || m.role === "assistant") && typeof m.content === "string" && m.content.trim().length > 0);
|
|
555
|
+
// Extract last 2 exchanges (up to 4 messages)
|
|
556
|
+
const lastExchanges = [];
|
|
557
|
+
for (let i = userAssistantMsgs.length - 1; i >= 0 && lastExchanges.length < 4; i--) {
|
|
558
|
+
lastExchanges.unshift({ role: userAssistantMsgs[i].role, content: userAssistantMsgs[i].content });
|
|
559
|
+
}
|
|
560
|
+
tuiStore.addResumeMessages(lastExchanges);
|
|
561
|
+
}
|
|
562
|
+
// Compute resumeInfo for header banner
|
|
563
|
+
const resumeInfo = messages.length > 1
|
|
564
|
+
? {
|
|
565
|
+
messageCount: messages.filter(m => m.role === "user" || m.role === "assistant").length,
|
|
566
|
+
timeAgo: options.lastActivityAt ? formatTimeAgo(new Date(options.lastActivityAt)) : "unknown",
|
|
567
|
+
}
|
|
568
|
+
: undefined;
|
|
569
|
+
// Ctrl-C state machine (Claude Code behavior):
|
|
570
|
+
// During generation: abort current request
|
|
571
|
+
// Idle with text: clear input
|
|
572
|
+
// Idle empty (first): warn
|
|
573
|
+
// Idle empty (second): exit
|
|
574
|
+
let ctrlCWarned = false;
|
|
575
|
+
let ctrlCTimer = null;
|
|
576
|
+
const handleCtrlC = (hasInput) => {
|
|
577
|
+
if (currentAbort) {
|
|
578
|
+
currentAbort.abort();
|
|
579
|
+
ctrlCWarned = false;
|
|
580
|
+
return "abort";
|
|
581
|
+
}
|
|
582
|
+
if (hasInput) {
|
|
583
|
+
ctrlCWarned = false;
|
|
584
|
+
return "clear";
|
|
585
|
+
}
|
|
586
|
+
if (ctrlCWarned) {
|
|
587
|
+
ctrlCWarned = false;
|
|
588
|
+
if (ctrlCTimer) {
|
|
589
|
+
clearTimeout(ctrlCTimer);
|
|
590
|
+
ctrlCTimer = null;
|
|
591
|
+
}
|
|
592
|
+
closed = true;
|
|
593
|
+
inputQueue.close();
|
|
594
|
+
return "exit";
|
|
595
|
+
}
|
|
596
|
+
ctrlCWarned = true;
|
|
597
|
+
// Reset after 2 seconds — must press twice within window
|
|
598
|
+
ctrlCTimer = setTimeout(() => { ctrlCWarned = false; }, 2000);
|
|
599
|
+
return "warn";
|
|
600
|
+
};
|
|
601
|
+
// TUI root: subscribes to store, passes props to OuroTui
|
|
602
|
+
// Elapsed timer is local React state (no store.notify overhead)
|
|
603
|
+
const storeRef = tuiStore;
|
|
604
|
+
function TuiRoot() {
|
|
605
|
+
const [, forceUpdate] = React.useState(0);
|
|
606
|
+
const [elapsed, setElapsed] = React.useState(0);
|
|
607
|
+
React.useEffect(() => storeRef.subscribe(() => {
|
|
608
|
+
forceUpdate((n) => n + 1);
|
|
609
|
+
// Reset ctrlC warning on any state change (new turn, etc.)
|
|
610
|
+
}), []);
|
|
611
|
+
React.useEffect(() => {
|
|
612
|
+
const iv = setInterval(() => setElapsed(storeRef.getElapsed()), 1000);
|
|
613
|
+
return () => clearInterval(iv);
|
|
614
|
+
}, []);
|
|
615
|
+
return React.createElement(OuroTui, {
|
|
616
|
+
agentName: options.agentName,
|
|
617
|
+
model: (0, identity_1.loadAgentConfig)().humanFacing?.model ?? "",
|
|
618
|
+
completedMessages: storeRef.completedMessages,
|
|
619
|
+
inputHistory: storeRef.inputHistory,
|
|
620
|
+
queuedInputs: storeRef.queuedInputs,
|
|
621
|
+
live: storeRef.live,
|
|
622
|
+
elapsedSeconds: elapsed,
|
|
623
|
+
contextPercent: 0,
|
|
624
|
+
onSubmit: (text) => { ctrlCWarned = false; inputQueue.push(text); storeRef.enqueueInput(text); },
|
|
625
|
+
onCtrlC: handleCtrlC,
|
|
626
|
+
onPopQueue: () => { const items = storeRef.popAllQueuedForEditing(); inputQueue.drainAll(); return items; },
|
|
627
|
+
headerShown: storeRef.headerShown,
|
|
628
|
+
cwd: process.cwd().replace(process.env.HOME ?? "", "~"),
|
|
629
|
+
resumeInfo,
|
|
630
|
+
onImageMap: (images) => { pendingImages = images; },
|
|
631
|
+
onHistoryAdd: (text) => { storeRef.addToHistoryOnly(text); },
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
inkRef = ink.render(React.createElement(TuiRoot), { exitOnCtrlC: false, patchConsole: false });
|
|
635
|
+
}
|
|
636
|
+
catch (err) {
|
|
637
|
+
// Ink failed to load (CJS compat, missing deps, etc.) — fall through to imperative
|
|
638
|
+
(0, runtime_1.emitNervesEvent)({
|
|
639
|
+
component: "senses",
|
|
640
|
+
event: "senses.tui_fallback",
|
|
641
|
+
message: `TUI failed to load, falling back to imperative: ${err instanceof Error ? err.message : String(err)}`,
|
|
642
|
+
meta: {},
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
// Fallback to imperative callbacks if TUI didn't initialize
|
|
647
|
+
if (!tuiStore) {
|
|
648
|
+
cliCallbacks = createCliCallbacks();
|
|
649
|
+
if (options.banner !== false) {
|
|
650
|
+
const bannerText = typeof options.banner === "string"
|
|
651
|
+
? options.banner
|
|
652
|
+
: `${options.agentName} (type /commands for help)`;
|
|
653
|
+
// eslint-disable-next-line no-console -- terminal UX: startup banner
|
|
654
|
+
console.log(`\n${bannerText}\n`);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
// Display helpers: route to TUI store or imperative stderr/stdout
|
|
658
|
+
const display = {
|
|
659
|
+
error: (msg) => {
|
|
660
|
+
if (tuiStore)
|
|
661
|
+
tuiStore.setError(msg);
|
|
662
|
+
else
|
|
663
|
+
process.stderr.write(`\x1b[31m${msg}\x1b[0m\n`);
|
|
664
|
+
},
|
|
665
|
+
warn: (msg) => {
|
|
666
|
+
if (tuiStore)
|
|
667
|
+
tuiStore.setError(msg); // TUI shows warnings as errors (amber color handled in component)
|
|
668
|
+
else
|
|
669
|
+
process.stderr.write(`\x1b[33m${msg}\x1b[0m\n`);
|
|
670
|
+
},
|
|
671
|
+
text: (msg) => {
|
|
672
|
+
if (tuiStore)
|
|
673
|
+
tuiStore.appendText(msg);
|
|
674
|
+
else
|
|
675
|
+
process.stdout.write(`${msg}\n`);
|
|
676
|
+
},
|
|
677
|
+
suppressInput: () => {
|
|
678
|
+
if (tuiStore)
|
|
679
|
+
tuiStore.suppressInput();
|
|
680
|
+
},
|
|
681
|
+
restoreInput: () => {
|
|
682
|
+
if (tuiStore)
|
|
683
|
+
tuiStore.restoreInput();
|
|
684
|
+
},
|
|
685
|
+
};
|
|
686
|
+
const effectiveToolContext = {
|
|
687
|
+
signin: options.toolContext?.signin ?? (async () => undefined),
|
|
688
|
+
...options.toolContext,
|
|
689
|
+
codingFeedback: {
|
|
690
|
+
send: async (message) => {
|
|
691
|
+
const assistantMessage = {
|
|
692
|
+
role: "assistant",
|
|
693
|
+
content: message,
|
|
694
|
+
};
|
|
695
|
+
messages.push(assistantMessage);
|
|
696
|
+
await options.onAsyncAssistantMessage?.(messages, assistantMessage);
|
|
697
|
+
display.text(message);
|
|
698
|
+
await options.toolContext?.codingFeedback?.send(message);
|
|
699
|
+
},
|
|
700
|
+
},
|
|
701
|
+
};
|
|
702
|
+
// exitOnToolCall machinery: wrap execTool to detect target tool
|
|
703
|
+
let exitToolResult;
|
|
704
|
+
let exitToolFired = false;
|
|
705
|
+
const resolvedExecTool = options.execTool;
|
|
706
|
+
const wrappedExecTool = options.exitOnToolCall && resolvedExecTool
|
|
707
|
+
? async (name, args, ctx) => {
|
|
708
|
+
const result = await resolvedExecTool(name, args, ctx);
|
|
709
|
+
if (name === options.exitOnToolCall) {
|
|
710
|
+
exitToolResult = result;
|
|
711
|
+
exitToolFired = true;
|
|
712
|
+
currentAbort?.abort();
|
|
713
|
+
}
|
|
714
|
+
return result;
|
|
715
|
+
}
|
|
716
|
+
: resolvedExecTool;
|
|
717
|
+
// Resolve toolChoiceRequired: use explicit option if set, else fall back to toggle
|
|
718
|
+
const getEffectiveToolChoiceRequired = () => options.toolChoiceRequired !== undefined ? options.toolChoiceRequired : (0, commands_1.getToolChoiceRequired)();
|
|
719
|
+
(0, runtime_1.emitNervesEvent)({
|
|
720
|
+
component: "senses",
|
|
721
|
+
event: "senses.cli_session_start",
|
|
722
|
+
message: "runCliSession started",
|
|
723
|
+
meta: { agentName: options.agentName, hasExitOnToolCall: !!options.exitOnToolCall },
|
|
724
|
+
});
|
|
725
|
+
let exitReason = "user_quit";
|
|
726
|
+
// Auto-first-turn: process the last user message immediately so the agent
|
|
727
|
+
// speaks first (e.g. specialist greeting). Only triggers when explicitly opted in.
|
|
728
|
+
if (options.autoFirstTurn && messages.length > 0 && messages[messages.length - 1]?.role === "user") {
|
|
729
|
+
currentAbort = new AbortController();
|
|
730
|
+
const traceId = (0, nerves_1.createTraceId)();
|
|
731
|
+
display.suppressInput();
|
|
732
|
+
let result;
|
|
733
|
+
try {
|
|
734
|
+
result = await (0, core_1.runAgent)(messages, cliCallbacks, options.skipSystemPromptRefresh ? undefined : "cli", currentAbort.signal, {
|
|
735
|
+
toolChoiceRequired: getEffectiveToolChoiceRequired(),
|
|
736
|
+
traceId,
|
|
737
|
+
tools: options.tools,
|
|
738
|
+
execTool: wrappedExecTool,
|
|
739
|
+
toolContext: effectiveToolContext,
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
catch (err) {
|
|
743
|
+
if (!(err instanceof DOMException && err.name === "AbortError")) {
|
|
744
|
+
display.error(err instanceof Error ? err.message : String(err));
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
cliCallbacks.flushMarkdown();
|
|
748
|
+
display.restoreInput();
|
|
749
|
+
currentAbort = null;
|
|
750
|
+
if (exitToolFired) {
|
|
751
|
+
exitReason = "tool_exit";
|
|
752
|
+
closed = true;
|
|
753
|
+
}
|
|
754
|
+
else {
|
|
755
|
+
const lastMsg = messages[messages.length - 1];
|
|
756
|
+
if (lastMsg?.role === "assistant" && !(typeof lastMsg.content === "string" ? lastMsg.content : "").trim()) {
|
|
757
|
+
display.warn("(empty response)");
|
|
758
|
+
}
|
|
759
|
+
if (options.onTurnEnd) {
|
|
760
|
+
await options.onTurnEnd(messages, result ?? { usage: undefined });
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
try {
|
|
765
|
+
// Input source: TUI queue for Ink, test source for tests, readline fallback
|
|
766
|
+
let inputSource;
|
|
767
|
+
let inputCtrl = null;
|
|
768
|
+
if (tuiStore && inputQueue) {
|
|
769
|
+
// TUI path: input comes from Ink's onSubmit → InputQueue
|
|
770
|
+
inputSource = inputQueue;
|
|
771
|
+
}
|
|
772
|
+
else if (options._testInputSource) {
|
|
773
|
+
inputSource = options._testInputSource;
|
|
774
|
+
}
|
|
775
|
+
else {
|
|
776
|
+
// Imperative fallback: readline
|
|
777
|
+
const isTTY = process.stdin.isTTY === true;
|
|
778
|
+
rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: isTTY });
|
|
779
|
+
inputCtrl = new InputController(rl);
|
|
780
|
+
inputSource = rl;
|
|
781
|
+
process.stdout.write(CLI_PROMPT);
|
|
782
|
+
}
|
|
783
|
+
for await (const input of inputSource) {
|
|
784
|
+
if (closed)
|
|
785
|
+
break;
|
|
786
|
+
if (!input.trim())
|
|
787
|
+
continue;
|
|
788
|
+
// Remove from TUI queue display as the agent picks up this message
|
|
789
|
+
tuiStore?.dequeueInput(input);
|
|
790
|
+
// Optional input gate (e.g. trust gate in main)
|
|
791
|
+
if (options.onInput) {
|
|
792
|
+
const gate = options.onInput(input);
|
|
793
|
+
if (!gate.allowed) {
|
|
794
|
+
if (gate.reply) {
|
|
795
|
+
display.text(gate.reply);
|
|
796
|
+
}
|
|
797
|
+
if (closed)
|
|
798
|
+
break;
|
|
799
|
+
continue;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
// Check for slash commands (legacy path only — pipeline handles commands for runTurn path)
|
|
803
|
+
if (!options.runTurn) {
|
|
804
|
+
const parsed = (0, commands_1.parseSlashCommand)(input);
|
|
805
|
+
if (parsed) {
|
|
806
|
+
const dispatchResult = registry.dispatch(parsed.command, { channel: "cli" });
|
|
807
|
+
if (dispatchResult.handled && dispatchResult.result) {
|
|
808
|
+
if (dispatchResult.result.action === "exit") {
|
|
809
|
+
break;
|
|
810
|
+
}
|
|
811
|
+
else if (dispatchResult.result.action === "response") {
|
|
812
|
+
display.text(dispatchResult.result.message || "");
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
// Track user message in TUI for display
|
|
819
|
+
if (tuiStore)
|
|
820
|
+
tuiStore.addUserMessage(input);
|
|
821
|
+
currentAbort = new AbortController();
|
|
822
|
+
if (tuiStore)
|
|
823
|
+
tuiStore.suppressInput();
|
|
824
|
+
inputCtrl?.suppress(() => { currentAbort?.abort(); });
|
|
825
|
+
// Resolve pending image content before the turn executes
|
|
826
|
+
let contentParts = null;
|
|
827
|
+
const currentImages = pendingImages;
|
|
828
|
+
if (currentImages && currentImages.size > 0) {
|
|
829
|
+
contentParts = await (0, image_paste_1.resolveImageContent)(input, currentImages);
|
|
830
|
+
pendingImages = null;
|
|
831
|
+
}
|
|
832
|
+
let result;
|
|
833
|
+
try {
|
|
834
|
+
if (options.runTurn) {
|
|
835
|
+
// Pipeline-based turn: the runTurn callback handles user message assembly,
|
|
836
|
+
// pending drain, trust gate, runAgent, postTurn, and token accumulation.
|
|
837
|
+
result = await options.runTurn(messages, input, cliCallbacks, currentAbort.signal, effectiveToolContext, contentParts ?? undefined);
|
|
838
|
+
// Handle pipeline-intercepted commands with loop-control side effects
|
|
839
|
+
if (result?.turnOutcome === "command") {
|
|
840
|
+
if (result.commandAction === "exit") {
|
|
841
|
+
break;
|
|
842
|
+
}
|
|
843
|
+
// For "response" commands: the pipeline already emitted the response via onTextChunk
|
|
844
|
+
cliCallbacks.flushMarkdown();
|
|
845
|
+
continue;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
else {
|
|
849
|
+
// Legacy path: inline runAgent (used by serpent guide and tests)
|
|
850
|
+
const prefix = options.getContentPrefix?.();
|
|
851
|
+
const userContent = contentParts
|
|
852
|
+
? contentParts
|
|
853
|
+
: (prefix ? `${prefix}\n\n${input}` : input);
|
|
854
|
+
const userMsg = { role: "user", content: userContent };
|
|
855
|
+
(0, session_events_1.stampIngressTime)(userMsg);
|
|
856
|
+
messages.push(userMsg);
|
|
857
|
+
const traceId = (0, nerves_1.createTraceId)();
|
|
858
|
+
result = await (0, core_1.runAgent)(messages, cliCallbacks, options.skipSystemPromptRefresh ? undefined : "cli", currentAbort.signal, {
|
|
859
|
+
toolChoiceRequired: getEffectiveToolChoiceRequired(),
|
|
860
|
+
traceId,
|
|
861
|
+
tools: options.tools,
|
|
862
|
+
execTool: wrappedExecTool,
|
|
863
|
+
toolContext: effectiveToolContext,
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
catch (err) {
|
|
868
|
+
if (!(err instanceof DOMException && err.name === "AbortError")) {
|
|
869
|
+
display.error(err instanceof Error ? err.message : String(err));
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
cliCallbacks.flushMarkdown();
|
|
873
|
+
if (!tuiStore)
|
|
874
|
+
process.stdout.write("\n"); // ensure response ends with newline before prompt (imperative only)
|
|
875
|
+
if (tuiStore)
|
|
876
|
+
tuiStore.restoreInput();
|
|
877
|
+
inputCtrl?.restore();
|
|
878
|
+
currentAbort = null;
|
|
879
|
+
// Check if exit tool was fired during this turn
|
|
880
|
+
if (exitToolFired) {
|
|
881
|
+
exitReason = "tool_exit";
|
|
882
|
+
break;
|
|
883
|
+
}
|
|
884
|
+
// Safety net: never silently swallow an empty response
|
|
885
|
+
const lastMsg = messages[messages.length - 1];
|
|
886
|
+
if (lastMsg?.role === "assistant" && !(typeof lastMsg.content === "string" ? lastMsg.content : "").trim()) {
|
|
887
|
+
display.warn("(empty response)");
|
|
888
|
+
}
|
|
889
|
+
// Post-turn hook (session persistence, pending drain, prompt refresh, etc.)
|
|
890
|
+
if (options.onTurnEnd) {
|
|
891
|
+
await options.onTurnEnd(messages, result ?? { usage: undefined });
|
|
892
|
+
}
|
|
893
|
+
if (closed)
|
|
894
|
+
break;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
finally {
|
|
898
|
+
rl?.close();
|
|
899
|
+
if (inkRef) {
|
|
900
|
+
// Suppress React "state update on unmounted component" warnings during cleanup.
|
|
901
|
+
// Ink's useInput hook fires after unmount — this is harmless but noisy.
|
|
902
|
+
// This also covers the SerpentGuide exitOnToolCall path, which aborts the
|
|
903
|
+
// current request and breaks out of the loop into this finally block.
|
|
904
|
+
// eslint-disable-next-line no-console -- intentional console.warn/error override for cleanup
|
|
905
|
+
const origWarn = console.warn;
|
|
906
|
+
const origError = console.error; // eslint-disable-line no-console
|
|
907
|
+
// eslint-disable-next-line no-console -- suppress React unmount warnings
|
|
908
|
+
console.warn = (...args) => { if (typeof args[0] === "string" && args[0].includes("Can't perform a React state update"))
|
|
909
|
+
return; origWarn.apply(console, args); };
|
|
910
|
+
// eslint-disable-next-line no-console -- suppress React unmount warnings
|
|
911
|
+
console.error = (...args) => { if (typeof args[0] === "string" && args[0].includes("Can't perform a React state update"))
|
|
912
|
+
return; origError.apply(console, args); };
|
|
913
|
+
inkRef.unmount();
|
|
914
|
+
setTimeout(() => { console.warn = origWarn; console.error = origError; }, 100); // eslint-disable-line no-console
|
|
915
|
+
}
|
|
916
|
+
if (options.banner !== false) {
|
|
917
|
+
// eslint-disable-next-line no-console -- terminal UX: goodbye
|
|
918
|
+
console.log("bye");
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
/* v8 ignore stop */
|
|
922
|
+
return { exitReason, toolResult: exitToolResult };
|
|
923
|
+
}
|
|
371
924
|
async function main(agentName, options) {
|
|
372
925
|
if (agentName)
|
|
373
926
|
(0, identity_1.setAgentName)(agentName);
|
|
374
927
|
const pasteDebounceMs = options?.pasteDebounceMs ?? 50;
|
|
928
|
+
// Safety net: process-level SIGINT handler ensures Ctrl+C always exits,
|
|
929
|
+
// even when Ink's event loop is blocked by expensive renders.
|
|
930
|
+
/* v8 ignore start -- process signal handler @preserve */
|
|
931
|
+
let sigintCount = 0;
|
|
932
|
+
const sigintHandler = () => {
|
|
933
|
+
sigintCount++;
|
|
934
|
+
if (sigintCount >= 2)
|
|
935
|
+
process.exit(1);
|
|
936
|
+
};
|
|
937
|
+
if (!options?._testInputSource) {
|
|
938
|
+
process.on("SIGINT", sigintHandler);
|
|
939
|
+
}
|
|
940
|
+
/* v8 ignore stop */
|
|
941
|
+
// Register spinner hooks so log output clears the spinner before printing
|
|
942
|
+
(0, nerves_1.registerSpinnerHooks)(pauseActiveSpinner, resumeActiveSpinner);
|
|
943
|
+
// Fallback: apply pending updates for daemon-less direct CLI usage
|
|
944
|
+
(0, update_hooks_1.registerUpdateHook)(bundle_meta_1.bundleMetaHook);
|
|
945
|
+
(0, update_hooks_1.registerUpdateHook)(agent_config_v2_1.agentConfigV2Hook);
|
|
946
|
+
await (0, update_hooks_1.applyPendingUpdates)((0, identity_1.getAgentBundlesRoot)(), (0, bundle_manifest_1.getPackageVersion)());
|
|
375
947
|
// Fail fast if provider is misconfigured (triggers human-readable error + exit)
|
|
376
|
-
(0, core_1.getProvider)();
|
|
377
|
-
const registry = (0, commands_1.createCommandRegistry)();
|
|
378
|
-
(0, commands_1.registerDefaultCommands)(registry);
|
|
948
|
+
(0, core_1.getProvider)("human");
|
|
379
949
|
// Resolve context kernel (identity + channel) for CLI
|
|
380
950
|
const friendsPath = path.join((0, identity_1.getAgentRoot)(), "friends");
|
|
381
951
|
const friendStore = new store_file_1.FileFriendStore(friendsPath);
|
|
382
952
|
const username = os.userInfo().username;
|
|
383
|
-
const
|
|
384
|
-
const localExternalId = `${username}@${hostname}`;
|
|
953
|
+
const localExternalId = username;
|
|
385
954
|
const resolver = new resolver_1.FriendResolver(friendStore, {
|
|
386
955
|
provider: "local",
|
|
387
956
|
externalId: localExternalId,
|
|
@@ -389,13 +958,6 @@ async function main(agentName, options) {
|
|
|
389
958
|
channel: "cli",
|
|
390
959
|
});
|
|
391
960
|
const resolvedContext = await resolver.resolve();
|
|
392
|
-
const cliToolContext = {
|
|
393
|
-
/* v8 ignore next -- CLI has no OAuth sign-in; this no-op satisfies the interface @preserve */
|
|
394
|
-
signin: async () => undefined,
|
|
395
|
-
context: resolvedContext,
|
|
396
|
-
friendStore,
|
|
397
|
-
summarize: (0, core_1.createSummarize)(),
|
|
398
|
-
};
|
|
399
961
|
const friendId = resolvedContext.friend.id;
|
|
400
962
|
const agentConfig = (0, identity_1.loadAgentConfig)();
|
|
401
963
|
(0, cli_logging_1.configureCliRuntimeLogger)(friendId, {
|
|
@@ -418,187 +980,138 @@ async function main(agentName, options) {
|
|
|
418
980
|
}
|
|
419
981
|
// Load existing session or start fresh
|
|
420
982
|
const existing = (0, context_1.loadSession)(sessPath);
|
|
421
|
-
|
|
983
|
+
let sessionState = existing?.state;
|
|
984
|
+
let sessionEvents = existing?.events ?? [];
|
|
985
|
+
const mcpManager = await (0, mcp_manager_1.getSharedMcpManager)() ?? undefined;
|
|
986
|
+
const sessionMessages = existing?.messages && existing.messages.length > 0
|
|
422
987
|
? existing.messages
|
|
423
|
-
: [{ role: "system", content: await (0, prompt_1.buildSystem)("cli",
|
|
424
|
-
//
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
messages.push({ role: "assistant", content: msg.content });
|
|
433
|
-
}
|
|
434
|
-
return pending.length;
|
|
435
|
-
};
|
|
436
|
-
// Startup drain: deliver offline messages
|
|
437
|
-
const startupCount = drainToMessages();
|
|
438
|
-
if (startupCount > 0) {
|
|
439
|
-
(0, context_1.saveSession)(sessPath, messages);
|
|
440
|
-
}
|
|
441
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
442
|
-
const ctrl = new InputController(rl);
|
|
443
|
-
let currentAbort = null;
|
|
444
|
-
const history = [];
|
|
445
|
-
let closed = false;
|
|
446
|
-
rl.on("close", () => { closed = true; });
|
|
447
|
-
// eslint-disable-next-line no-console -- terminal UX: startup banner
|
|
448
|
-
console.log(`\n${(0, identity_1.getAgentName)()} (type /commands for help)\n`);
|
|
449
|
-
const cliCallbacks = createCliCallbacks();
|
|
450
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
451
|
-
// Ctrl-C at the input prompt: clear line or warn/exit
|
|
452
|
-
// readline with terminal:true catches Ctrl-C in raw mode (no ^C echo)
|
|
453
|
-
rl.on("SIGINT", () => {
|
|
454
|
-
const rlInt = rl;
|
|
455
|
-
const currentLine = rlInt.line || "";
|
|
456
|
-
const result = handleSigint(rl, currentLine);
|
|
457
|
-
if (result === "clear") {
|
|
458
|
-
rlInt.line = "";
|
|
459
|
-
rlInt.cursor = 0;
|
|
460
|
-
process.stdout.write("\r\x1b[K\x1b[36m> \x1b[0m");
|
|
461
|
-
}
|
|
462
|
-
else if (result === "warn") {
|
|
463
|
-
rlInt.line = "";
|
|
464
|
-
rlInt.cursor = 0;
|
|
465
|
-
process.stdout.write("\r\x1b[K");
|
|
466
|
-
process.stderr.write("press Ctrl-C again to exit\n");
|
|
467
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
468
|
-
}
|
|
469
|
-
else {
|
|
470
|
-
rl.close();
|
|
471
|
-
}
|
|
472
|
-
});
|
|
473
|
-
// Debounced line iterator: collects rapid-fire lines (paste) into a single input
|
|
474
|
-
async function* debouncedLines(source) {
|
|
475
|
-
if (pasteDebounceMs <= 0) {
|
|
476
|
-
yield* source;
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
const iter = source[Symbol.asyncIterator]();
|
|
480
|
-
while (true) {
|
|
481
|
-
const first = await iter.next();
|
|
482
|
-
if (first.done)
|
|
483
|
-
break;
|
|
484
|
-
// Collect any lines that arrive within the debounce window (paste detection)
|
|
485
|
-
const lines = [first.value];
|
|
486
|
-
let more = true;
|
|
487
|
-
while (more) {
|
|
488
|
-
const raced = await Promise.race([
|
|
489
|
-
iter.next().then((r) => ({ kind: "line", result: r })),
|
|
490
|
-
new Promise((r) => setTimeout(() => r({ kind: "timeout" }), pasteDebounceMs)),
|
|
491
|
-
]);
|
|
492
|
-
if (raced.kind === "timeout") {
|
|
493
|
-
more = false;
|
|
494
|
-
}
|
|
495
|
-
else if (raced.result.done) {
|
|
496
|
-
more = false;
|
|
497
|
-
}
|
|
498
|
-
else {
|
|
499
|
-
lines.push(raced.result.value);
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
yield lines.join("\n");
|
|
503
|
-
}
|
|
504
|
-
}
|
|
988
|
+
: [{ role: "system", content: (0, prompt_1.flattenSystemPrompt)(await (0, prompt_1.buildSystem)("cli", {}, resolvedContext)) }];
|
|
989
|
+
// Repair any orphaned tool calls from a crash mid-turn
|
|
990
|
+
(0, core_1.repairOrphanedToolCalls)(sessionMessages);
|
|
991
|
+
// Per-turn pipeline input: CLI capabilities and pending dir
|
|
992
|
+
const cliCapabilities = (0, channel_1.getChannelCapabilities)("cli");
|
|
993
|
+
const currentAgentName = (0, identity_1.getAgentName)();
|
|
994
|
+
const pendingDir = (0, pending_1.getPendingDir)(currentAgentName, friendId, "cli", "session");
|
|
995
|
+
const summarize = (0, core_1.createSummarize)("human");
|
|
996
|
+
const cliFailoverState = { pending: null };
|
|
505
997
|
try {
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
998
|
+
await runCliSession({
|
|
999
|
+
agentName: currentAgentName,
|
|
1000
|
+
pasteDebounceMs,
|
|
1001
|
+
messages: sessionMessages,
|
|
1002
|
+
lastActivityAt: sessionState?.lastFriendActivityAt,
|
|
1003
|
+
_testInputSource: options?._testInputSource,
|
|
1004
|
+
onAsyncAssistantMessage: async (messages, _assistantMessage) => {
|
|
1005
|
+
const prepared = (0, context_1.postTurnTrim)(messages);
|
|
1006
|
+
const events = (0, context_1.postTurnPersist)(sessPath, prepared, undefined, sessionState);
|
|
1007
|
+
/* v8 ignore next -- defensive: postTurnPersist always returns events in practice @preserve */
|
|
1008
|
+
sessionEvents = events.length > 0 ? events : sessionEvents;
|
|
1009
|
+
},
|
|
1010
|
+
runTurn: async (messages, userInput, callbacks, signal, toolContext, userContent) => {
|
|
1011
|
+
// Run the full per-turn pipeline: resolve -> gate -> session -> drain -> runAgent -> postTurn -> tokens
|
|
1012
|
+
// User message passed via input.messages so the pipeline can prepend pending messages to it.
|
|
1013
|
+
const failoverState = cliFailoverState;
|
|
1014
|
+
// Capture terminal errors instead of displaying immediately — the failover
|
|
1015
|
+
// message replaces the raw error if failover triggers successfully.
|
|
1016
|
+
let capturedTerminalError = null;
|
|
1017
|
+
/* v8 ignore start -- failover-aware callback wrapper: tested via pipeline integration @preserve */
|
|
1018
|
+
const failoverAwareCallbacks = {
|
|
1019
|
+
...callbacks,
|
|
1020
|
+
// Save session after each tool result for crash recovery (deferred to avoid blocking)
|
|
1021
|
+
onToolResult: (turnMessages) => {
|
|
1022
|
+
const prepared = (0, context_1.postTurnTrim)(turnMessages);
|
|
1023
|
+
(0, context_1.deferPostTurnPersist)(sessPath, prepared, undefined, sessionState);
|
|
1024
|
+
},
|
|
1025
|
+
onError: (error, severity) => {
|
|
1026
|
+
if (severity === "terminal" && failoverState) {
|
|
1027
|
+
capturedTerminalError = error;
|
|
1028
|
+
callbacks.onError(new Error(""), "transient");
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
callbacks.onError(error, severity);
|
|
1032
|
+
},
|
|
1033
|
+
};
|
|
1034
|
+
/* v8 ignore stop */
|
|
1035
|
+
const result = await (0, pipeline_1.handleInboundTurn)({
|
|
1036
|
+
channel: "cli",
|
|
1037
|
+
sessionKey: "session",
|
|
1038
|
+
capabilities: cliCapabilities,
|
|
1039
|
+
messages: [{ role: "user", content: userContent ?? userInput }],
|
|
1040
|
+
continuityIngressTexts: getCliContinuityIngressTexts(userInput),
|
|
1041
|
+
callbacks: failoverAwareCallbacks,
|
|
1042
|
+
friendResolver: { resolve: () => Promise.resolve(resolvedContext) },
|
|
1043
|
+
sessionLoader: {
|
|
1044
|
+
loadOrCreate: () => Promise.resolve({
|
|
1045
|
+
messages,
|
|
1046
|
+
sessionPath: sessPath,
|
|
1047
|
+
state: sessionState,
|
|
1048
|
+
events: sessionEvents,
|
|
1049
|
+
}),
|
|
1050
|
+
},
|
|
1051
|
+
pendingDir,
|
|
1052
|
+
friendStore,
|
|
1053
|
+
provider: "local",
|
|
1054
|
+
externalId: localExternalId,
|
|
1055
|
+
enforceTrustGate: trust_gate_1.enforceTrustGate,
|
|
1056
|
+
drainPending: pending_1.drainPending,
|
|
1057
|
+
drainDeferredReturns: (deferredFriendId) => (0, pending_1.drainDeferredReturns)(currentAgentName, deferredFriendId),
|
|
1058
|
+
runAgent: (msgs, cb, channel, sig, opts) => (0, core_1.runAgent)(msgs, cb, channel, sig, {
|
|
1059
|
+
...opts,
|
|
1060
|
+
toolContext: {
|
|
1061
|
+
/* v8 ignore next -- default no-op signin; pipeline provides the real one @preserve */
|
|
1062
|
+
signin: async () => undefined,
|
|
1063
|
+
...opts?.toolContext,
|
|
1064
|
+
summarize,
|
|
1065
|
+
},
|
|
1066
|
+
}),
|
|
1067
|
+
postTurn: (turnMessages, sessionPathArg, usage, hooks, state) => {
|
|
1068
|
+
// Trim synchronously (mutates turnMessages for next turn),
|
|
1069
|
+
// then defer envelope build + disk I/O to avoid blocking the TUI.
|
|
1070
|
+
const prepared = (0, context_1.postTurnTrim)(turnMessages, usage, hooks);
|
|
1071
|
+
sessionState = state;
|
|
1072
|
+
(0, context_1.deferPostTurnPersist)(sessionPathArg, prepared, usage, state).then((events) => {
|
|
1073
|
+
/* v8 ignore next -- defensive: deferPostTurnPersist always resolves events in practice @preserve */
|
|
1074
|
+
sessionEvents = events.length > 0 ? events : sessionEvents;
|
|
1075
|
+
});
|
|
1076
|
+
},
|
|
1077
|
+
accumulateFriendTokens: tokens_1.accumulateFriendTokens,
|
|
1078
|
+
signal,
|
|
1079
|
+
runAgentOptions: {
|
|
1080
|
+
toolChoiceRequired: (0, commands_1.getToolChoiceRequired)(),
|
|
1081
|
+
traceId: (0, nerves_1.createTraceId)(),
|
|
1082
|
+
mcpManager,
|
|
1083
|
+
toolContext,
|
|
1084
|
+
},
|
|
1085
|
+
failoverState,
|
|
1086
|
+
});
|
|
1087
|
+
/* v8 ignore start -- failover display: tested via pipeline integration tests @preserve */
|
|
1088
|
+
if (result.failoverMessage) {
|
|
1089
|
+
// Failover handled it — show the actionable message instead of the raw error
|
|
1090
|
+
process.stdout.write(`\x1b[33m${result.failoverMessage}\x1b[0m\n`);
|
|
522
1091
|
}
|
|
523
|
-
if (
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
if (dispatchResult.handled && dispatchResult.result) {
|
|
533
|
-
if (dispatchResult.result.action === "exit") {
|
|
534
|
-
break;
|
|
535
|
-
}
|
|
536
|
-
else if (dispatchResult.result.action === "new") {
|
|
537
|
-
messages.length = 0;
|
|
538
|
-
messages.push({ role: "system", content: await (0, prompt_1.buildSystem)("cli") });
|
|
539
|
-
(0, context_1.deleteSession)(sessPath);
|
|
540
|
-
// eslint-disable-next-line no-console -- terminal UX: session cleared
|
|
541
|
-
console.log("session cleared");
|
|
542
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
543
|
-
continue;
|
|
544
|
-
}
|
|
545
|
-
else if (dispatchResult.result.action === "response") {
|
|
546
|
-
// eslint-disable-next-line no-console -- terminal UX: command dispatch result
|
|
547
|
-
console.log(dispatchResult.result.message || "");
|
|
548
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
549
|
-
continue;
|
|
1092
|
+
else if (capturedTerminalError) {
|
|
1093
|
+
// Failover didn't trigger (no failoverState, or sequence failed) — show the raw error
|
|
1094
|
+
process.stderr.write(`\x1b[31m${(0, format_1.formatError)(capturedTerminalError)}\x1b[0m\n`);
|
|
1095
|
+
}
|
|
1096
|
+
/* v8 ignore stop */
|
|
1097
|
+
// Handle gate rejection: display auto-reply if present
|
|
1098
|
+
if (!result.gateResult.allowed) {
|
|
1099
|
+
if ("autoReply" in result.gateResult && result.gateResult.autoReply) {
|
|
1100
|
+
process.stdout.write(`${result.gateResult.autoReply}\n`);
|
|
550
1101
|
}
|
|
551
1102
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
const cols = process.stdout.columns || 80;
|
|
556
|
-
const inputLines = input.split("\n");
|
|
557
|
-
let echoRows = 0;
|
|
558
|
-
for (const line of inputLines) {
|
|
559
|
-
echoRows += Math.ceil((2 + line.length) / cols); // "> " prefix + line content
|
|
560
|
-
}
|
|
561
|
-
process.stdout.write(`\x1b[${echoRows}A\x1b[K` + `\x1b[1m> ${inputLines[0]}${inputLines.length > 1 ? ` (+${inputLines.length - 1} lines)` : ""}\x1b[0m\n\n`);
|
|
562
|
-
messages.push({ role: "user", content: input });
|
|
563
|
-
addHistory(history, input);
|
|
564
|
-
currentAbort = new AbortController();
|
|
565
|
-
const traceId = (0, nerves_1.createTraceId)();
|
|
566
|
-
ctrl.suppress(() => currentAbort.abort());
|
|
567
|
-
let result;
|
|
568
|
-
try {
|
|
569
|
-
result = await (0, core_1.runAgent)(messages, cliCallbacks, "cli", currentAbort.signal, {
|
|
570
|
-
toolChoiceRequired: (0, commands_1.getToolChoiceRequired)(),
|
|
571
|
-
toolContext: cliToolContext,
|
|
572
|
-
traceId,
|
|
573
|
-
});
|
|
574
|
-
}
|
|
575
|
-
catch {
|
|
576
|
-
// AbortError — silently return to prompt
|
|
577
|
-
}
|
|
578
|
-
cliCallbacks.flushMarkdown();
|
|
579
|
-
ctrl.restore();
|
|
580
|
-
currentAbort = null;
|
|
581
|
-
// Safety net: never silently swallow an empty response
|
|
582
|
-
const lastMsg = messages[messages.length - 1];
|
|
583
|
-
if (lastMsg?.role === "assistant" && !(typeof lastMsg.content === "string" ? lastMsg.content : "").trim()) {
|
|
584
|
-
process.stderr.write("\x1b[33m(empty response)\x1b[0m\n");
|
|
585
|
-
}
|
|
586
|
-
process.stdout.write("\n\n");
|
|
587
|
-
(0, context_1.postTurn)(messages, sessPath, result?.usage);
|
|
588
|
-
await (0, tokens_1.accumulateFriendTokens)(friendStore, resolvedContext.friend.id, result?.usage);
|
|
589
|
-
// Post-turn: drain any pending messages that arrived during runAgent
|
|
590
|
-
drainToMessages();
|
|
591
|
-
// Post-turn: refresh system prompt so active sessions metadata is current
|
|
592
|
-
await (0, prompt_refresh_1.refreshSystemPrompt)(messages, "cli", undefined, resolvedContext);
|
|
593
|
-
if (closed)
|
|
594
|
-
break;
|
|
595
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
596
|
-
}
|
|
1103
|
+
return { usage: result.usage, turnOutcome: result.turnOutcome, commandAction: result.commandAction };
|
|
1104
|
+
},
|
|
1105
|
+
});
|
|
597
1106
|
}
|
|
598
1107
|
finally {
|
|
599
1108
|
sessionLock?.release();
|
|
600
|
-
rl.close();
|
|
601
|
-
// eslint-disable-next-line no-console -- terminal UX: goodbye
|
|
602
|
-
console.log("bye");
|
|
603
1109
|
}
|
|
1110
|
+
// Force exit: lingering handles (Ink cleanup timers, MCP connections) keep the
|
|
1111
|
+
// event loop alive after the interactive session ends. This is safe because all
|
|
1112
|
+
// session persistence has already completed in the finally block above.
|
|
1113
|
+
/* v8 ignore next -- process.exit not callable in vitest @preserve */
|
|
1114
|
+
if (!options?._testInputSource)
|
|
1115
|
+
process.exit(0);
|
|
604
1116
|
}
|
|
1117
|
+
// CI trigger
|