@ouro.bot/cli 0.1.0-alpha.59 → 0.1.0-alpha.590
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 +127 -23
- 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/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +4 -2
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +2 -2
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +1 -1
- package/changelog.json +3805 -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 +237 -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 +837 -26
- 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/background-operations.js +281 -0
- package/dist/heart/bundle-state.js +168 -0
- package/dist/heart/commitments.js +111 -0
- package/dist/heart/config-registry.js +322 -0
- package/dist/heart/config.js +114 -118
- package/dist/heart/core.js +909 -246
- package/dist/heart/cross-chat-delivery.js +3 -18
- package/dist/heart/daemon/agent-config-check.js +419 -0
- package/dist/heart/daemon/agent-discovery.js +102 -3
- 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 +7559 -0
- package/dist/heart/daemon/cli-help.js +498 -0
- package/dist/heart/daemon/cli-parse.js +1592 -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 +29 -1703
- package/dist/heart/daemon/daemon-entry.js +387 -2
- 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 +88 -13
- package/dist/heart/daemon/daemon-tombstone.js +236 -0
- package/dist/heart/daemon/daemon.js +815 -70
- 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 +115 -1
- 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 +37 -8
- package/dist/heart/daemon/log-tailer.js +78 -9
- package/dist/heart/daemon/logs-prune.js +110 -0
- package/dist/heart/daemon/mcp-canary.js +297 -0
- 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 +375 -33
- 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 +2 -0
- package/dist/heart/daemon/runtime-logging.js +10 -2
- package/dist/heart/daemon/runtime-metadata.js +2 -30
- package/dist/heart/daemon/safe-mode.js +161 -0
- package/dist/heart/daemon/sense-manager.js +462 -38
- 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 +158 -11
- 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 +162 -17
- package/dist/heart/daemon/up-progress.js +366 -0
- package/dist/heart/daemon/vault-items.js +56 -0
- package/dist/heart/delegation.js +1 -1
- 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-flow.js +32 -56
- package/dist/heart/{daemon → hatch}/hatch-specialist.js +6 -8
- package/dist/heart/{daemon → hatch}/specialist-prompt.js +12 -9
- package/dist/heart/{daemon → hatch}/specialist-tools.js +35 -12
- package/dist/heart/identity.js +166 -55
- package/dist/heart/kept-notes.js +357 -0
- package/dist/heart/kicks.js +1 -1
- 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 +362 -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 +19 -0
- package/dist/heart/platform.js +81 -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 +139 -52
- package/dist/heart/providers/azure.js +97 -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 +26 -8
- package/dist/heart/providers/openai-codex.js +55 -40
- 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 +13 -4
- package/dist/heart/session-activity.js +43 -22
- 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 +44 -27
- package/dist/heart/sync-classification.js +176 -0
- package/dist/heart/sync.js +449 -0
- package/dist/heart/target-resolution.js +9 -5
- 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/{daemon → versioning}/ouro-bot-global-installer.js +6 -5
- package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
- package/dist/heart/versioning/ouro-path-installer.js +426 -0
- package/dist/heart/versioning/ouro-version-manager.js +295 -0
- package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
- package/dist/heart/{daemon → versioning}/update-checker.js +6 -1
- package/dist/heart/{daemon → versioning}/update-hooks.js +63 -59
- 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 +674 -0
- package/dist/mailroom/body-cache.js +61 -0
- package/dist/mailroom/core.js +720 -0
- package/dist/mailroom/entry.js +160 -0
- package/dist/mailroom/file-store.js +430 -0
- package/dist/mailroom/mbox-import.js +383 -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 +7 -1
- package/dist/mind/context.js +165 -101
- package/dist/mind/diary-integrity.js +60 -0
- package/dist/mind/{memory.js → diary.js} +62 -75
- package/dist/mind/embedding-provider.js +60 -0
- package/dist/mind/file-state.js +179 -0
- package/dist/mind/friends/channel.js +39 -0
- package/dist/mind/friends/resolver.js +54 -2
- package/dist/mind/friends/store-file.js +39 -3
- package/dist/mind/friends/types.js +2 -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 +4 -0
- package/dist/mind/prompt-refresh.js +3 -2
- package/dist/mind/prompt.js +1039 -135
- package/dist/mind/provenance-trust.js +26 -0
- package/dist/mind/scrutiny.js +173 -0
- package/dist/nerves/cli-logging.js +7 -1
- 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/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 +15 -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 +197 -30
- package/dist/repertoire/coding/manager.js +158 -9
- package/dist/repertoire/coding/spawner.js +55 -9
- package/dist/repertoire/coding/tools.js +170 -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/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 +31 -5
- package/dist/repertoire/tasks/fix.js +182 -0
- package/dist/repertoire/tasks/index.js +16 -4
- 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 -78
- package/dist/repertoire/tool-results.js +29 -0
- package/dist/repertoire/tools-attachments.js +317 -0
- package/dist/repertoire/tools-base.js +47 -1082
- package/dist/repertoire/tools-bluebubbles.js +1 -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 +1911 -0
- package/dist/repertoire/tools-notes.js +421 -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 +9 -39
- 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 +115 -103
- 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/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 → bluebubbles/client.js} +219 -18
- package/dist/senses/bluebubbles/entry.js +77 -0
- package/dist/senses/{bluebubbles-inbound-log.js → bluebubbles/inbound-log.js} +20 -3
- package/dist/senses/bluebubbles/index.js +2487 -0
- package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -71
- package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +33 -12
- package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +3 -3
- package/dist/senses/bluebubbles/processed-log.js +133 -0
- package/dist/senses/bluebubbles/replay.js +137 -0
- package/dist/senses/{bluebubbles-runtime-state.js → bluebubbles/runtime-state.js} +30 -2
- package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
- 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 +100 -0
- package/dist/senses/cli.js +516 -204
- package/dist/senses/commands.js +66 -3
- package/dist/senses/habit-turn-message.js +108 -0
- package/dist/senses/inner-dialog-worker.js +175 -21
- package/dist/senses/inner-dialog.js +330 -27
- package/dist/senses/mail-entry.js +66 -0
- package/dist/senses/mail.js +379 -0
- package/dist/senses/pipeline.js +654 -181
- 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 +387 -98
- package/dist/senses/trust-gate.js +100 -5
- 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 +398 -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 +794 -0
- package/dist/senses/voice/twilio-phone.js +4995 -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 +42 -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/dist/heart/daemon/auth-flow.js +0 -351
- package/dist/heart/daemon/ouro-path-installer.js +0 -178
- package/dist/heart/daemon/subagent-installer.js +0 -166
- package/dist/heart/safe-workspace.js +0 -228
- package/dist/heart/session-recall.js +0 -116
- package/dist/mind/associative-recall.js +0 -209
- package/dist/senses/bluebubbles-entry.js +0 -13
- package/dist/senses/bluebubbles.js +0 -1177
- package/dist/senses/debug-activity.js +0 -148
- package/subagents/README.md +0 -86
- package/subagents/work-doer.md +0 -237
- package/subagents/work-merger.md +0 -618
- package/subagents/work-planner.md +0 -390
- /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/monty.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/heart/{daemon → hatch}/hatch-animation.js +0 -0
- /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
- /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
- /package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +0 -0
package/dist/senses/cli.js
CHANGED
|
@@ -33,9 +33,14 @@ 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 = exports.wrapCliText = exports.formatEchoedInputSummary = void 0;
|
|
36
|
+
exports.InputQueue = exports.MarkdownStreamer = exports.InputController = exports.Spinner = exports.StreamingWordWrapper = exports.wrapCliText = exports.formatEchoedInputSummary = void 0;
|
|
37
37
|
exports.formatPendingPrefix = formatPendingPrefix;
|
|
38
38
|
exports.getCliContinuityIngressTexts = getCliContinuityIngressTexts;
|
|
39
|
+
exports.formatTimeAgo = formatTimeAgo;
|
|
40
|
+
exports.writeCliAsyncAssistantMessage = writeCliAsyncAssistantMessage;
|
|
41
|
+
exports.pauseActiveSpinner = pauseActiveSpinner;
|
|
42
|
+
exports.resumeActiveSpinner = resumeActiveSpinner;
|
|
43
|
+
exports.setActiveSpinner = setActiveSpinner;
|
|
39
44
|
exports.handleSigint = handleSigint;
|
|
40
45
|
exports.addHistory = addHistory;
|
|
41
46
|
exports.renderMarkdown = renderMarkdown;
|
|
@@ -51,10 +56,12 @@ const prompt_1 = require("../mind/prompt");
|
|
|
51
56
|
const phrases_1 = require("../mind/phrases");
|
|
52
57
|
const format_1 = require("../mind/format");
|
|
53
58
|
const config_1 = require("../heart/config");
|
|
59
|
+
const session_events_1 = require("../heart/session-events");
|
|
54
60
|
const context_1 = require("../mind/context");
|
|
55
61
|
const pending_1 = require("../mind/pending");
|
|
56
62
|
const commands_1 = require("./commands");
|
|
57
63
|
const identity_1 = require("../heart/identity");
|
|
64
|
+
const mcp_manager_1 = require("../repertoire/mcp-manager");
|
|
58
65
|
const nerves_1 = require("../nerves");
|
|
59
66
|
const store_file_1 = require("../mind/friends/store-file");
|
|
60
67
|
const resolver_1 = require("../mind/friends/resolver");
|
|
@@ -65,13 +72,18 @@ const trust_gate_1 = require("./trust-gate");
|
|
|
65
72
|
const pipeline_1 = require("./pipeline");
|
|
66
73
|
const channel_1 = require("../mind/friends/channel");
|
|
67
74
|
const session_lock_1 = require("./session-lock");
|
|
68
|
-
const update_hooks_1 = require("../heart/
|
|
75
|
+
const update_hooks_1 = require("../heart/versioning/update-hooks");
|
|
69
76
|
const bundle_meta_1 = require("../heart/daemon/hooks/bundle-meta");
|
|
77
|
+
const agent_config_v2_1 = require("../heart/daemon/hooks/agent-config-v2");
|
|
70
78
|
const bundle_manifest_1 = require("../mind/bundle-manifest");
|
|
71
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");
|
|
72
83
|
var cli_layout_2 = require("./cli-layout");
|
|
73
84
|
Object.defineProperty(exports, "formatEchoedInputSummary", { enumerable: true, get: function () { return cli_layout_2.formatEchoedInputSummary; } });
|
|
74
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; } });
|
|
75
87
|
/**
|
|
76
88
|
* Format pending messages as content-prefix strings for injection into
|
|
77
89
|
* the next user message. Self-messages (from === agentName) become
|
|
@@ -89,68 +101,46 @@ function getCliContinuityIngressTexts(input) {
|
|
|
89
101
|
const trimmed = input.trim();
|
|
90
102
|
return trimmed ? [trimmed] : [];
|
|
91
103
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
/* v8 ignore next -- race guard: timer callback fires after stop() @preserve */
|
|
120
|
-
if (this.stopped)
|
|
121
|
-
return;
|
|
122
|
-
process.stderr.write(`\r\x1b[K${this.frames[this.i]} ${this.msg}... `);
|
|
123
|
-
this.i = (this.i + 1) % this.frames.length;
|
|
124
|
-
}
|
|
125
|
-
rotatePhrase() {
|
|
126
|
-
/* v8 ignore next -- race guard: timer callback fires after stop() @preserve */
|
|
127
|
-
if (this.stopped)
|
|
128
|
-
return;
|
|
129
|
-
const next = (0, phrases_1.pickPhrase)(this.phrases, this.lastPhrase);
|
|
130
|
-
this.lastPhrase = next;
|
|
131
|
-
this.msg = next;
|
|
132
|
-
}
|
|
133
|
-
stop(ok) {
|
|
134
|
-
this.stopped = true;
|
|
135
|
-
if (this.iv) {
|
|
136
|
-
clearInterval(this.iv);
|
|
137
|
-
this.iv = null;
|
|
138
|
-
}
|
|
139
|
-
if (this.piv) {
|
|
140
|
-
clearInterval(this.piv);
|
|
141
|
-
this.piv = null;
|
|
142
|
-
}
|
|
143
|
-
process.stderr.write("\r\x1b[K");
|
|
144
|
-
/* v8 ignore next -- ok parameter currently unused by callers @preserve */
|
|
145
|
-
if (ok)
|
|
146
|
-
process.stderr.write(`\x1b[32m\u2713\x1b[0m ${ok}\n`);
|
|
147
|
-
}
|
|
148
|
-
fail(msg) {
|
|
149
|
-
this.stop();
|
|
150
|
-
process.stderr.write(`\x1b[31m\u2717\x1b[0m ${msg}\n`);
|
|
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);
|
|
151
131
|
}
|
|
152
132
|
}
|
|
153
|
-
|
|
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; } });
|
|
154
144
|
// Input controller: pauses readline during model/tool execution.
|
|
155
145
|
// Does NOT touch raw mode — readline with terminal:true manages raw mode
|
|
156
146
|
// internally. Touching it causes ^C to be echoed by the terminal driver.
|
|
@@ -318,60 +308,65 @@ function createCliCallbacks() {
|
|
|
318
308
|
meta: {},
|
|
319
309
|
});
|
|
320
310
|
let currentSpinner = null;
|
|
321
|
-
|
|
311
|
+
function setSpinner(s) { currentSpinner = s; setActiveSpinner(s); }
|
|
322
312
|
let hadToolRun = false;
|
|
323
313
|
let textDirty = false; // true when text/reasoning was written without a trailing newline
|
|
324
314
|
const streamer = new MarkdownStreamer();
|
|
315
|
+
const wrapper = new cli_layout_1.StreamingWordWrapper();
|
|
325
316
|
return {
|
|
326
317
|
onModelStart: () => {
|
|
327
318
|
currentSpinner?.stop();
|
|
328
|
-
|
|
329
|
-
hadReasoning = false;
|
|
319
|
+
setSpinner(null);
|
|
330
320
|
textDirty = false;
|
|
331
321
|
streamer.reset();
|
|
322
|
+
wrapper.reset();
|
|
332
323
|
const phrases = (0, phrases_1.getPhrases)();
|
|
333
324
|
const pool = hadToolRun ? phrases.followup : phrases.thinking;
|
|
334
325
|
const first = (0, phrases_1.pickPhrase)(pool);
|
|
335
|
-
|
|
326
|
+
setSpinner(new spinner_imperative_1.ImperativeSpinner(first, pool));
|
|
336
327
|
currentSpinner.start();
|
|
337
328
|
},
|
|
338
329
|
onModelStreamStart: () => {
|
|
339
330
|
// No-op: content callbacks (onTextChunk, onReasoningChunk) handle
|
|
340
331
|
// stopping the spinner. onModelStreamStart fires too early and
|
|
341
|
-
// doesn't fire at all for
|
|
332
|
+
// doesn't fire at all for settle tool streaming.
|
|
342
333
|
},
|
|
343
334
|
onClearText: () => {
|
|
344
335
|
streamer.reset();
|
|
336
|
+
wrapper.reset();
|
|
345
337
|
},
|
|
346
338
|
onTextChunk: (text) => {
|
|
347
|
-
// Stop spinner if still running —
|
|
339
|
+
// Stop spinner if still running — settle streaming and Anthropic
|
|
348
340
|
// tool-only responses bypass onModelStreamStart, so the spinner would
|
|
349
341
|
// otherwise keep running (and its \r writes overwrite response text).
|
|
350
342
|
if (currentSpinner) {
|
|
351
343
|
currentSpinner.stop();
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
if (hadReasoning) {
|
|
355
|
-
// Single newline to separate reasoning from reply — reasoning
|
|
356
|
-
// output often ends with its own trailing newline(s)
|
|
357
|
-
process.stdout.write("\n");
|
|
358
|
-
hadReasoning = false;
|
|
344
|
+
setSpinner(null);
|
|
359
345
|
}
|
|
360
346
|
const rendered = streamer.push(text);
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
if (currentSpinner) {
|
|
367
|
-
currentSpinner.stop();
|
|
368
|
-
currentSpinner = null;
|
|
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);
|
|
369
352
|
}
|
|
370
|
-
|
|
371
|
-
process.stdout.write(`\x1b[2m${text}\x1b[0m`);
|
|
353
|
+
/* v8 ignore stop */
|
|
372
354
|
textDirty = text.length > 0 && !text.endsWith("\n");
|
|
373
355
|
},
|
|
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.
|
|
362
|
+
},
|
|
374
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;
|
|
375
370
|
// Stop the model-start spinner: when the model returns only tool calls
|
|
376
371
|
// (no content/reasoning), onModelStreamStart never fires, so the old
|
|
377
372
|
// spinner's intervals would leak.
|
|
@@ -384,31 +379,29 @@ function createCliCallbacks() {
|
|
|
384
379
|
}
|
|
385
380
|
const toolPhrases = (0, phrases_1.getPhrases)().tool;
|
|
386
381
|
const first = (0, phrases_1.pickPhrase)(toolPhrases);
|
|
387
|
-
|
|
382
|
+
setSpinner(new spinner_imperative_1.ImperativeSpinner(first, toolPhrases));
|
|
388
383
|
currentSpinner.start();
|
|
389
384
|
hadToolRun = true;
|
|
390
385
|
},
|
|
391
386
|
onToolEnd: (name, argSummary, success) => {
|
|
392
387
|
currentSpinner?.stop();
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
const color = success ? "\x1b[32m" : "\x1b[31m";
|
|
396
|
-
process.stderr.write(`${color}${msg}\x1b[0m\n`);
|
|
388
|
+
setSpinner(null);
|
|
389
|
+
(0, tool_display_1.writeToolEnd)(name, argSummary, success);
|
|
397
390
|
},
|
|
398
391
|
onError: (error, severity) => {
|
|
399
392
|
if (severity === "transient") {
|
|
400
393
|
currentSpinner?.fail(error.message);
|
|
401
|
-
|
|
394
|
+
setSpinner(null);
|
|
402
395
|
}
|
|
403
396
|
else {
|
|
404
397
|
currentSpinner?.stop();
|
|
405
|
-
|
|
398
|
+
setSpinner(null);
|
|
406
399
|
process.stderr.write(`\x1b[31m${(0, format_1.formatError)(error)}\x1b[0m\n`);
|
|
407
400
|
}
|
|
408
401
|
},
|
|
409
402
|
onKick: () => {
|
|
410
403
|
currentSpinner?.stop();
|
|
411
|
-
|
|
404
|
+
setSpinner(null);
|
|
412
405
|
if (textDirty) {
|
|
413
406
|
process.stdout.write("\n");
|
|
414
407
|
textDirty = false;
|
|
@@ -417,10 +410,18 @@ function createCliCallbacks() {
|
|
|
417
410
|
},
|
|
418
411
|
flushMarkdown: () => {
|
|
419
412
|
currentSpinner?.stop();
|
|
420
|
-
|
|
413
|
+
setSpinner(null);
|
|
414
|
+
/* v8 ignore start -- wrapper flush: tested via cli.test.ts flushMarkdown tests @preserve */
|
|
421
415
|
const remaining = streamer.flush();
|
|
422
|
-
if (remaining)
|
|
423
|
-
|
|
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 */
|
|
424
425
|
},
|
|
425
426
|
};
|
|
426
427
|
}
|
|
@@ -461,29 +462,243 @@ async function* createDebouncedLines(source, debounceMs) {
|
|
|
461
462
|
yield lines.join("\n");
|
|
462
463
|
}
|
|
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;
|
|
464
516
|
async function runCliSession(options) {
|
|
465
517
|
/* v8 ignore start -- integration: runCliSession is interactive, tested via E2E @preserve */
|
|
466
|
-
const pasteDebounceMs = options.pasteDebounceMs ?? 50;
|
|
467
518
|
const registry = (0, commands_1.createCommandRegistry)();
|
|
468
519
|
if (!options.disableCommands) {
|
|
469
520
|
(0, commands_1.registerDefaultCommands)(registry);
|
|
470
521
|
}
|
|
471
522
|
const messages = options.messages
|
|
472
|
-
?? [{ role: "system", content: await (0, prompt_1.buildSystem)("cli") }];
|
|
473
|
-
|
|
474
|
-
const
|
|
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;
|
|
475
526
|
let currentAbort = null;
|
|
476
|
-
const history = [];
|
|
477
527
|
let closed = false;
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
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
|
+
}
|
|
485
656
|
}
|
|
486
|
-
|
|
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
|
+
};
|
|
487
702
|
// exitOnToolCall machinery: wrap execTool to detect target tool
|
|
488
703
|
let exitToolResult;
|
|
489
704
|
let exitToolFired = false;
|
|
@@ -494,8 +709,6 @@ async function runCliSession(options) {
|
|
|
494
709
|
if (name === options.exitOnToolCall) {
|
|
495
710
|
exitToolResult = result;
|
|
496
711
|
exitToolFired = true;
|
|
497
|
-
// Abort immediately so the model doesn't generate more output
|
|
498
|
-
// (e.g. reasoning about calling final_answer after complete_adoption)
|
|
499
712
|
currentAbort?.abort();
|
|
500
713
|
}
|
|
501
714
|
return result;
|
|
@@ -503,28 +716,6 @@ async function runCliSession(options) {
|
|
|
503
716
|
: resolvedExecTool;
|
|
504
717
|
// Resolve toolChoiceRequired: use explicit option if set, else fall back to toggle
|
|
505
718
|
const getEffectiveToolChoiceRequired = () => options.toolChoiceRequired !== undefined ? options.toolChoiceRequired : (0, commands_1.getToolChoiceRequired)();
|
|
506
|
-
// Ctrl-C at the input prompt: clear line or warn/exit
|
|
507
|
-
rl.on("SIGINT", () => {
|
|
508
|
-
const rlInt = rl;
|
|
509
|
-
const currentLine = rlInt.line || "";
|
|
510
|
-
const result = handleSigint(rl, currentLine);
|
|
511
|
-
if (result === "clear") {
|
|
512
|
-
rlInt.line = "";
|
|
513
|
-
rlInt.cursor = 0;
|
|
514
|
-
process.stdout.write("\r\x1b[K\x1b[36m> \x1b[0m");
|
|
515
|
-
}
|
|
516
|
-
else if (result === "warn") {
|
|
517
|
-
rlInt.line = "";
|
|
518
|
-
rlInt.cursor = 0;
|
|
519
|
-
process.stdout.write("\r\x1b[K");
|
|
520
|
-
process.stderr.write("press Ctrl-C again to exit\n");
|
|
521
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
522
|
-
}
|
|
523
|
-
else {
|
|
524
|
-
rl.close();
|
|
525
|
-
}
|
|
526
|
-
});
|
|
527
|
-
const debouncedLines = (source) => createDebouncedLines(source, pasteDebounceMs);
|
|
528
719
|
(0, runtime_1.emitNervesEvent)({
|
|
529
720
|
component: "senses",
|
|
530
721
|
event: "senses.cli_session_start",
|
|
@@ -537,7 +728,7 @@ async function runCliSession(options) {
|
|
|
537
728
|
if (options.autoFirstTurn && messages.length > 0 && messages[messages.length - 1]?.role === "user") {
|
|
538
729
|
currentAbort = new AbortController();
|
|
539
730
|
const traceId = (0, nerves_1.createTraceId)();
|
|
540
|
-
|
|
731
|
+
display.suppressInput();
|
|
541
732
|
let result;
|
|
542
733
|
try {
|
|
543
734
|
result = await (0, core_1.runAgent)(messages, cliCallbacks, options.skipSystemPromptRefresh ? undefined : "cli", currentAbort.signal, {
|
|
@@ -545,119 +736,145 @@ async function runCliSession(options) {
|
|
|
545
736
|
traceId,
|
|
546
737
|
tools: options.tools,
|
|
547
738
|
execTool: wrappedExecTool,
|
|
548
|
-
toolContext:
|
|
739
|
+
toolContext: effectiveToolContext,
|
|
549
740
|
});
|
|
550
741
|
}
|
|
551
742
|
catch (err) {
|
|
552
|
-
// AbortError (Ctrl-C) -- silently continue to prompt
|
|
553
|
-
// All other errors: show the user what happened
|
|
554
743
|
if (!(err instanceof DOMException && err.name === "AbortError")) {
|
|
555
|
-
|
|
744
|
+
display.error(err instanceof Error ? err.message : String(err));
|
|
556
745
|
}
|
|
557
746
|
}
|
|
558
747
|
cliCallbacks.flushMarkdown();
|
|
559
|
-
|
|
748
|
+
display.restoreInput();
|
|
560
749
|
currentAbort = null;
|
|
561
750
|
if (exitToolFired) {
|
|
562
751
|
exitReason = "tool_exit";
|
|
563
|
-
|
|
752
|
+
closed = true;
|
|
564
753
|
}
|
|
565
754
|
else {
|
|
566
755
|
const lastMsg = messages[messages.length - 1];
|
|
567
756
|
if (lastMsg?.role === "assistant" && !(typeof lastMsg.content === "string" ? lastMsg.content : "").trim()) {
|
|
568
|
-
|
|
757
|
+
display.warn("(empty response)");
|
|
569
758
|
}
|
|
570
|
-
process.stdout.write("\n\n");
|
|
571
759
|
if (options.onTurnEnd) {
|
|
572
760
|
await options.onTurnEnd(messages, result ?? { usage: undefined });
|
|
573
761
|
}
|
|
574
762
|
}
|
|
575
763
|
}
|
|
576
|
-
if (!exitToolFired) {
|
|
577
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
578
|
-
}
|
|
579
764
|
try {
|
|
580
|
-
for
|
|
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) {
|
|
581
784
|
if (closed)
|
|
582
785
|
break;
|
|
583
|
-
if (!input.trim())
|
|
584
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
786
|
+
if (!input.trim())
|
|
585
787
|
continue;
|
|
586
|
-
|
|
788
|
+
// Remove from TUI queue display as the agent picks up this message
|
|
789
|
+
tuiStore?.dequeueInput(input);
|
|
587
790
|
// Optional input gate (e.g. trust gate in main)
|
|
588
791
|
if (options.onInput) {
|
|
589
792
|
const gate = options.onInput(input);
|
|
590
793
|
if (!gate.allowed) {
|
|
591
794
|
if (gate.reply) {
|
|
592
|
-
|
|
795
|
+
display.text(gate.reply);
|
|
593
796
|
}
|
|
594
797
|
if (closed)
|
|
595
798
|
break;
|
|
596
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
597
799
|
continue;
|
|
598
800
|
}
|
|
599
801
|
}
|
|
600
|
-
// Check for slash commands
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
if (dispatchResult.
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
console.log("session cleared");
|
|
614
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
615
|
-
continue;
|
|
616
|
-
}
|
|
617
|
-
else if (dispatchResult.result.action === "response") {
|
|
618
|
-
// eslint-disable-next-line no-console -- terminal UX: command dispatch result
|
|
619
|
-
console.log(dispatchResult.result.message || "");
|
|
620
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
621
|
-
continue;
|
|
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
|
+
}
|
|
622
815
|
}
|
|
623
816
|
}
|
|
624
817
|
}
|
|
625
|
-
//
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
addHistory(history, input);
|
|
818
|
+
// Track user message in TUI for display
|
|
819
|
+
if (tuiStore)
|
|
820
|
+
tuiStore.addUserMessage(input);
|
|
629
821
|
currentAbort = new AbortController();
|
|
630
|
-
|
|
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
|
+
}
|
|
631
832
|
let result;
|
|
632
833
|
try {
|
|
633
834
|
if (options.runTurn) {
|
|
634
835
|
// Pipeline-based turn: the runTurn callback handles user message assembly,
|
|
635
836
|
// pending drain, trust gate, runAgent, postTurn, and token accumulation.
|
|
636
|
-
result = await options.runTurn(messages, input, cliCallbacks, currentAbort.signal);
|
|
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
|
+
}
|
|
637
847
|
}
|
|
638
848
|
else {
|
|
639
|
-
// Legacy path: inline runAgent (used by
|
|
849
|
+
// Legacy path: inline runAgent (used by serpent guide and tests)
|
|
640
850
|
const prefix = options.getContentPrefix?.();
|
|
641
|
-
|
|
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);
|
|
642
857
|
const traceId = (0, nerves_1.createTraceId)();
|
|
643
858
|
result = await (0, core_1.runAgent)(messages, cliCallbacks, options.skipSystemPromptRefresh ? undefined : "cli", currentAbort.signal, {
|
|
644
859
|
toolChoiceRequired: getEffectiveToolChoiceRequired(),
|
|
645
860
|
traceId,
|
|
646
861
|
tools: options.tools,
|
|
647
862
|
execTool: wrappedExecTool,
|
|
648
|
-
toolContext:
|
|
863
|
+
toolContext: effectiveToolContext,
|
|
649
864
|
});
|
|
650
865
|
}
|
|
651
866
|
}
|
|
652
867
|
catch (err) {
|
|
653
|
-
// AbortError (Ctrl-C) -- silently return to prompt
|
|
654
|
-
// All other errors: show the user what happened
|
|
655
868
|
if (!(err instanceof DOMException && err.name === "AbortError")) {
|
|
656
|
-
|
|
869
|
+
display.error(err instanceof Error ? err.message : String(err));
|
|
657
870
|
}
|
|
658
871
|
}
|
|
659
872
|
cliCallbacks.flushMarkdown();
|
|
660
|
-
|
|
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();
|
|
661
878
|
currentAbort = null;
|
|
662
879
|
// Check if exit tool was fired during this turn
|
|
663
880
|
if (exitToolFired) {
|
|
@@ -667,20 +884,35 @@ async function runCliSession(options) {
|
|
|
667
884
|
// Safety net: never silently swallow an empty response
|
|
668
885
|
const lastMsg = messages[messages.length - 1];
|
|
669
886
|
if (lastMsg?.role === "assistant" && !(typeof lastMsg.content === "string" ? lastMsg.content : "").trim()) {
|
|
670
|
-
|
|
887
|
+
display.warn("(empty response)");
|
|
671
888
|
}
|
|
672
|
-
process.stdout.write("\n\n");
|
|
673
889
|
// Post-turn hook (session persistence, pending drain, prompt refresh, etc.)
|
|
674
890
|
if (options.onTurnEnd) {
|
|
675
891
|
await options.onTurnEnd(messages, result ?? { usage: undefined });
|
|
676
892
|
}
|
|
677
893
|
if (closed)
|
|
678
894
|
break;
|
|
679
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
680
895
|
}
|
|
681
896
|
}
|
|
682
897
|
finally {
|
|
683
|
-
rl
|
|
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
|
+
}
|
|
684
916
|
if (options.banner !== false) {
|
|
685
917
|
// eslint-disable-next-line no-console -- terminal UX: goodbye
|
|
686
918
|
console.log("bye");
|
|
@@ -693,17 +925,32 @@ async function main(agentName, options) {
|
|
|
693
925
|
if (agentName)
|
|
694
926
|
(0, identity_1.setAgentName)(agentName);
|
|
695
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);
|
|
696
943
|
// Fallback: apply pending updates for daemon-less direct CLI usage
|
|
697
944
|
(0, update_hooks_1.registerUpdateHook)(bundle_meta_1.bundleMetaHook);
|
|
945
|
+
(0, update_hooks_1.registerUpdateHook)(agent_config_v2_1.agentConfigV2Hook);
|
|
698
946
|
await (0, update_hooks_1.applyPendingUpdates)((0, identity_1.getAgentBundlesRoot)(), (0, bundle_manifest_1.getPackageVersion)());
|
|
699
947
|
// Fail fast if provider is misconfigured (triggers human-readable error + exit)
|
|
700
|
-
(0, core_1.getProvider)();
|
|
948
|
+
(0, core_1.getProvider)("human");
|
|
701
949
|
// Resolve context kernel (identity + channel) for CLI
|
|
702
950
|
const friendsPath = path.join((0, identity_1.getAgentRoot)(), "friends");
|
|
703
951
|
const friendStore = new store_file_1.FileFriendStore(friendsPath);
|
|
704
952
|
const username = os.userInfo().username;
|
|
705
|
-
const
|
|
706
|
-
const localExternalId = `${username}@${hostname}`;
|
|
953
|
+
const localExternalId = username;
|
|
707
954
|
const resolver = new resolver_1.FriendResolver(friendStore, {
|
|
708
955
|
provider: "local",
|
|
709
956
|
externalId: localExternalId,
|
|
@@ -734,31 +981,73 @@ async function main(agentName, options) {
|
|
|
734
981
|
// Load existing session or start fresh
|
|
735
982
|
const existing = (0, context_1.loadSession)(sessPath);
|
|
736
983
|
let sessionState = existing?.state;
|
|
984
|
+
let sessionEvents = existing?.events ?? [];
|
|
985
|
+
const mcpManager = await (0, mcp_manager_1.getSharedMcpManager)() ?? undefined;
|
|
737
986
|
const sessionMessages = existing?.messages && existing.messages.length > 0
|
|
738
987
|
? existing.messages
|
|
739
|
-
: [{ role: "system", content: await (0, prompt_1.buildSystem)("cli",
|
|
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);
|
|
740
991
|
// Per-turn pipeline input: CLI capabilities and pending dir
|
|
741
992
|
const cliCapabilities = (0, channel_1.getChannelCapabilities)("cli");
|
|
742
993
|
const currentAgentName = (0, identity_1.getAgentName)();
|
|
743
994
|
const pendingDir = (0, pending_1.getPendingDir)(currentAgentName, friendId, "cli", "session");
|
|
744
|
-
const summarize = (0, core_1.createSummarize)();
|
|
995
|
+
const summarize = (0, core_1.createSummarize)("human");
|
|
996
|
+
const cliFailoverState = { pending: null };
|
|
745
997
|
try {
|
|
746
998
|
await runCliSession({
|
|
747
999
|
agentName: currentAgentName,
|
|
748
1000
|
pasteDebounceMs,
|
|
749
1001
|
messages: sessionMessages,
|
|
750
|
-
|
|
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) => {
|
|
751
1011
|
// Run the full per-turn pipeline: resolve -> gate -> session -> drain -> runAgent -> postTurn -> tokens
|
|
752
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 */
|
|
753
1035
|
const result = await (0, pipeline_1.handleInboundTurn)({
|
|
754
1036
|
channel: "cli",
|
|
755
1037
|
sessionKey: "session",
|
|
756
1038
|
capabilities: cliCapabilities,
|
|
757
|
-
messages: [{ role: "user", content: userInput }],
|
|
1039
|
+
messages: [{ role: "user", content: userContent ?? userInput }],
|
|
758
1040
|
continuityIngressTexts: getCliContinuityIngressTexts(userInput),
|
|
759
|
-
callbacks,
|
|
1041
|
+
callbacks: failoverAwareCallbacks,
|
|
760
1042
|
friendResolver: { resolve: () => Promise.resolve(resolvedContext) },
|
|
761
|
-
sessionLoader: {
|
|
1043
|
+
sessionLoader: {
|
|
1044
|
+
loadOrCreate: () => Promise.resolve({
|
|
1045
|
+
messages,
|
|
1046
|
+
sessionPath: sessPath,
|
|
1047
|
+
state: sessionState,
|
|
1048
|
+
events: sessionEvents,
|
|
1049
|
+
}),
|
|
1050
|
+
},
|
|
762
1051
|
pendingDir,
|
|
763
1052
|
friendStore,
|
|
764
1053
|
provider: "local",
|
|
@@ -776,30 +1065,53 @@ async function main(agentName, options) {
|
|
|
776
1065
|
},
|
|
777
1066
|
}),
|
|
778
1067
|
postTurn: (turnMessages, sessionPathArg, usage, hooks, state) => {
|
|
779
|
-
(
|
|
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);
|
|
780
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
|
+
});
|
|
781
1076
|
},
|
|
782
1077
|
accumulateFriendTokens: tokens_1.accumulateFriendTokens,
|
|
783
1078
|
signal,
|
|
784
1079
|
runAgentOptions: {
|
|
785
1080
|
toolChoiceRequired: (0, commands_1.getToolChoiceRequired)(),
|
|
786
1081
|
traceId: (0, nerves_1.createTraceId)(),
|
|
1082
|
+
mcpManager,
|
|
1083
|
+
toolContext,
|
|
787
1084
|
},
|
|
1085
|
+
failoverState,
|
|
788
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`);
|
|
1091
|
+
}
|
|
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 */
|
|
789
1097
|
// Handle gate rejection: display auto-reply if present
|
|
790
1098
|
if (!result.gateResult.allowed) {
|
|
791
1099
|
if ("autoReply" in result.gateResult && result.gateResult.autoReply) {
|
|
792
1100
|
process.stdout.write(`${result.gateResult.autoReply}\n`);
|
|
793
1101
|
}
|
|
794
1102
|
}
|
|
795
|
-
return { usage: result.usage };
|
|
796
|
-
},
|
|
797
|
-
onNewSession: () => {
|
|
798
|
-
(0, context_1.deleteSession)(sessPath);
|
|
1103
|
+
return { usage: result.usage, turnOutcome: result.turnOutcome, commandAction: result.commandAction };
|
|
799
1104
|
},
|
|
800
1105
|
});
|
|
801
1106
|
}
|
|
802
1107
|
finally {
|
|
803
1108
|
sessionLock?.release();
|
|
804
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);
|
|
805
1116
|
}
|
|
1117
|
+
// CI trigger
|