@ouro.bot/cli 0.1.0-alpha.51 → 0.1.0-alpha.511
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 +133 -19
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +3 -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 +3253 -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 +867 -35
- package/dist/heart/agent-entry.js +58 -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 +426 -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 +304 -0
- package/dist/heart/config.js +119 -129
- package/dist/heart/core.js +878 -244
- package/dist/heart/cross-chat-delivery.js +131 -0
- package/dist/heart/daemon/agent-config-check.js +490 -0
- package/dist/heart/daemon/agent-discovery.js +79 -3
- package/dist/heart/daemon/agent-service.js +360 -0
- package/dist/heart/daemon/agentic-repair.js +216 -0
- package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
- package/dist/heart/daemon/cadence.js +70 -0
- package/dist/heart/daemon/cli-defaults.js +640 -0
- package/dist/heart/daemon/cli-exec.js +7241 -0
- package/dist/heart/daemon/cli-help.js +493 -0
- package/dist/heart/daemon/cli-parse.js +1536 -0
- package/dist/heart/daemon/cli-render-doctor.js +57 -0
- package/dist/heart/daemon/cli-render.js +561 -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 -1631
- package/dist/heart/daemon/daemon-entry.js +345 -3
- package/dist/heart/daemon/daemon-health.js +141 -0
- package/dist/heart/daemon/daemon-runtime-sync.js +190 -12
- package/dist/heart/daemon/daemon-tombstone.js +236 -0
- package/dist/heart/daemon/daemon.js +677 -58
- package/dist/heart/daemon/dns-workflow.js +394 -0
- package/dist/heart/daemon/doctor-types.js +8 -0
- package/dist/heart/daemon/doctor.js +750 -0
- package/dist/heart/daemon/health-monitor.js +92 -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 +25 -5
- package/dist/heart/daemon/log-tailer.js +82 -12
- package/dist/heart/daemon/logs-prune.js +110 -0
- package/dist/heart/daemon/message-router.js +2 -2
- package/dist/heart/daemon/os-cron-deps.js +134 -0
- 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 +214 -0
- 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 +67 -16
- package/dist/heart/daemon/runtime-metadata.js +73 -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 +178 -37
- 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 +109 -4
- package/dist/heart/daemon/stale-bundle-prune.js +96 -0
- package/dist/heart/daemon/startup-tui.js +264 -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 +52 -117
- package/dist/heart/{daemon → hatch}/hatch-specialist.js +3 -3
- 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 +205 -66
- 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/mcp/mcp-server.js +653 -0
- package/dist/heart/migrate-config.js +100 -0
- package/dist/heart/model-capabilities.js +19 -0
- package/dist/heart/outlook/outlook-http-hooks.js +66 -0
- package/dist/heart/outlook/outlook-http-response.js +7 -0
- package/dist/heart/outlook/outlook-http-routes.js +244 -0
- package/dist/heart/outlook/outlook-http-static.js +103 -0
- package/dist/heart/outlook/outlook-http-transport.js +116 -0
- package/dist/heart/outlook/outlook-http.js +99 -0
- package/dist/heart/outlook/outlook-read.js +31 -0
- package/dist/heart/outlook/outlook-types.js +27 -0
- package/dist/heart/outlook/outlook-view.js +195 -0
- package/dist/heart/outlook/readers/agent-machine.js +382 -0
- package/dist/heart/outlook/readers/continuity-readers.js +336 -0
- package/dist/heart/outlook/readers/mail.js +362 -0
- package/dist/heart/outlook/readers/runtime-readers.js +644 -0
- package/dist/heart/outlook/readers/sessions.js +232 -0
- package/dist/heart/outlook/readers/shared.js +111 -0
- package/dist/heart/platform.js +81 -0
- package/dist/heart/provider-attempt.js +134 -0
- package/dist/heart/provider-binding-resolver.js +255 -0
- package/dist/heart/provider-credentials.js +424 -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-state.js +216 -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 +260 -0
- package/dist/heart/sense-truth.js +11 -4
- package/dist/heart/session-activity.js +43 -22
- package/dist/heart/session-events.js +1150 -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 +167 -0
- package/dist/heart/start-of-turn-packet.js +345 -0
- package/dist/heart/streaming.js +44 -27
- package/dist/heart/sync.js +332 -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/tool-activity-callbacks.js +36 -0
- package/dist/heart/tool-description.js +135 -0
- package/dist/heart/tool-friction.js +55 -0
- package/dist/heart/tool-loop.js +200 -0
- package/dist/heart/turn-context.js +372 -0
- package/dist/heart/{daemon → versioning}/ouro-bot-global-installer.js +1 -1
- package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
- package/dist/heart/versioning/ouro-path-installer.js +425 -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 +5 -1
- package/dist/heart/{daemon → versioning}/update-hooks.js +63 -59
- package/dist/mailroom/attention.js +167 -0
- package/dist/mailroom/autonomy.js +209 -0
- package/dist/mailroom/blob-store.js +606 -0
- package/dist/mailroom/body-cache.js +61 -0
- package/dist/mailroom/core.js +672 -0
- package/dist/mailroom/entry.js +160 -0
- package/dist/mailroom/file-store.js +426 -0
- package/dist/mailroom/mbox-import.js +382 -0
- package/dist/mailroom/outbound.js +380 -0
- package/dist/mailroom/policy.js +263 -0
- package/dist/mailroom/reader.js +219 -0
- package/dist/mailroom/search-cache.js +182 -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} +74 -93
- package/dist/mind/embedding-provider.js +60 -0
- package/dist/mind/file-state.js +179 -0
- package/dist/mind/friends/channel.js +30 -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 +39 -3
- package/dist/mind/friends/trust-explanation.js +74 -0
- 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 +940 -111
- 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 +114 -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/outlook-ui/assets/index-BPr5vNuM.css +1 -0
- package/dist/outlook-ui/assets/index-Cm51CY9W.js +61 -0
- package/dist/outlook-ui/index.html +15 -0
- 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 +774 -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 +111 -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 +255 -0
- package/dist/repertoire/mcp-manager.js +305 -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 +46 -955
- package/dist/repertoire/tools-bluebubbles.js +1 -0
- package/dist/repertoire/tools-bridge.js +141 -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 +105 -0
- package/dist/repertoire/tools-github.js +1 -7
- package/dist/repertoire/tools-mail.js +1477 -0
- package/dist/repertoire/tools-notes.js +376 -0
- package/dist/repertoire/tools-session.js +749 -0
- package/dist/repertoire/tools-shell.js +120 -0
- package/dist/repertoire/tools-stripe.js +180 -0
- package/dist/repertoire/tools-surface.js +243 -0
- package/dist/repertoire/tools-teams.js +9 -39
- package/dist/repertoire/tools-travel.js +125 -0
- package/dist/repertoire/tools-trip.js +422 -0
- package/dist/repertoire/tools-user-profile.js +144 -0
- package/dist/repertoire/tools-vault.js +40 -0
- package/dist/repertoire/tools.js +107 -100
- 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 +561 -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/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 +73 -0
- package/dist/senses/{bluebubbles-inbound-log.js → bluebubbles/inbound-log.js} +20 -3
- package/dist/senses/bluebubbles/index.js +1881 -0
- package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -70
- 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 +111 -0
- package/dist/senses/bluebubbles/replay.js +129 -0
- package/dist/senses/{bluebubbles-runtime-state.js → bluebubbles/runtime-state.js} +2 -2
- package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
- 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 +605 -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 +83 -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 +511 -209
- 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 +573 -164
- package/dist/senses/proactive-content-guard.js +51 -0
- package/dist/senses/shared-turn.js +248 -0
- package/dist/senses/surface-tool.js +68 -0
- package/dist/senses/teams-entry.js +60 -8
- package/dist/senses/teams.js +405 -170
- package/dist/senses/trust-gate.js +100 -5
- package/dist/trips/core.js +138 -0
- package/dist/trips/store.js +146 -0
- package/package.json +37 -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 +101 -0
- package/skills/travel-planning.md +138 -0
- package/dist/heart/daemon/ouro-path-installer.js +0 -178
- package/dist/heart/daemon/subagent-installer.js +0 -166
- 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 -1142
- 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 = 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,9 +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");
|
|
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; } });
|
|
71
87
|
/**
|
|
72
88
|
* Format pending messages as content-prefix strings for injection into
|
|
73
89
|
* the next user message. Self-messages (from === agentName) become
|
|
@@ -85,68 +101,46 @@ function getCliContinuityIngressTexts(input) {
|
|
|
85
101
|
const trimmed = input.trim();
|
|
86
102
|
return trimmed ? [trimmed] : [];
|
|
87
103
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
/* v8 ignore next -- race guard: timer callback fires after stop() @preserve */
|
|
116
|
-
if (this.stopped)
|
|
117
|
-
return;
|
|
118
|
-
process.stderr.write(`\r\x1b[K${this.frames[this.i]} ${this.msg}... `);
|
|
119
|
-
this.i = (this.i + 1) % this.frames.length;
|
|
120
|
-
}
|
|
121
|
-
rotatePhrase() {
|
|
122
|
-
/* v8 ignore next -- race guard: timer callback fires after stop() @preserve */
|
|
123
|
-
if (this.stopped)
|
|
124
|
-
return;
|
|
125
|
-
const next = (0, phrases_1.pickPhrase)(this.phrases, this.lastPhrase);
|
|
126
|
-
this.lastPhrase = next;
|
|
127
|
-
this.msg = next;
|
|
128
|
-
}
|
|
129
|
-
stop(ok) {
|
|
130
|
-
this.stopped = true;
|
|
131
|
-
if (this.iv) {
|
|
132
|
-
clearInterval(this.iv);
|
|
133
|
-
this.iv = null;
|
|
134
|
-
}
|
|
135
|
-
if (this.piv) {
|
|
136
|
-
clearInterval(this.piv);
|
|
137
|
-
this.piv = null;
|
|
138
|
-
}
|
|
139
|
-
process.stderr.write("\r\x1b[K");
|
|
140
|
-
/* v8 ignore next -- ok parameter currently unused by callers @preserve */
|
|
141
|
-
if (ok)
|
|
142
|
-
process.stderr.write(`\x1b[32m\u2713\x1b[0m ${ok}\n`);
|
|
143
|
-
}
|
|
144
|
-
fail(msg) {
|
|
145
|
-
this.stop();
|
|
146
|
-
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);
|
|
147
131
|
}
|
|
148
132
|
}
|
|
149
|
-
|
|
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; } });
|
|
150
144
|
// Input controller: pauses readline during model/tool execution.
|
|
151
145
|
// Does NOT touch raw mode — readline with terminal:true manages raw mode
|
|
152
146
|
// internally. Touching it causes ^C to be echoed by the terminal driver.
|
|
@@ -314,59 +308,55 @@ function createCliCallbacks() {
|
|
|
314
308
|
meta: {},
|
|
315
309
|
});
|
|
316
310
|
let currentSpinner = null;
|
|
317
|
-
|
|
311
|
+
function setSpinner(s) { currentSpinner = s; setActiveSpinner(s); }
|
|
318
312
|
let hadToolRun = false;
|
|
319
313
|
let textDirty = false; // true when text/reasoning was written without a trailing newline
|
|
320
314
|
const streamer = new MarkdownStreamer();
|
|
315
|
+
const wrapper = new cli_layout_1.StreamingWordWrapper();
|
|
321
316
|
return {
|
|
322
317
|
onModelStart: () => {
|
|
323
318
|
currentSpinner?.stop();
|
|
324
|
-
|
|
325
|
-
hadReasoning = false;
|
|
319
|
+
setSpinner(null);
|
|
326
320
|
textDirty = false;
|
|
327
321
|
streamer.reset();
|
|
322
|
+
wrapper.reset();
|
|
328
323
|
const phrases = (0, phrases_1.getPhrases)();
|
|
329
324
|
const pool = hadToolRun ? phrases.followup : phrases.thinking;
|
|
330
325
|
const first = (0, phrases_1.pickPhrase)(pool);
|
|
331
|
-
|
|
326
|
+
setSpinner(new spinner_imperative_1.ImperativeSpinner(first, pool));
|
|
332
327
|
currentSpinner.start();
|
|
333
328
|
},
|
|
334
329
|
onModelStreamStart: () => {
|
|
335
330
|
// No-op: content callbacks (onTextChunk, onReasoningChunk) handle
|
|
336
331
|
// stopping the spinner. onModelStreamStart fires too early and
|
|
337
|
-
// doesn't fire at all for
|
|
332
|
+
// doesn't fire at all for settle tool streaming.
|
|
338
333
|
},
|
|
339
334
|
onClearText: () => {
|
|
340
335
|
streamer.reset();
|
|
336
|
+
wrapper.reset();
|
|
341
337
|
},
|
|
342
338
|
onTextChunk: (text) => {
|
|
343
|
-
// Stop spinner if still running —
|
|
339
|
+
// Stop spinner if still running — settle streaming and Anthropic
|
|
344
340
|
// tool-only responses bypass onModelStreamStart, so the spinner would
|
|
345
341
|
// otherwise keep running (and its \r writes overwrite response text).
|
|
346
342
|
if (currentSpinner) {
|
|
347
343
|
currentSpinner.stop();
|
|
348
|
-
|
|
349
|
-
}
|
|
350
|
-
if (hadReasoning) {
|
|
351
|
-
// Single newline to separate reasoning from reply — reasoning
|
|
352
|
-
// output often ends with its own trailing newline(s)
|
|
353
|
-
process.stdout.write("\n");
|
|
354
|
-
hadReasoning = false;
|
|
344
|
+
setSpinner(null);
|
|
355
345
|
}
|
|
356
346
|
const rendered = streamer.push(text);
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
if (currentSpinner) {
|
|
363
|
-
currentSpinner.stop();
|
|
364
|
-
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);
|
|
365
352
|
}
|
|
366
|
-
|
|
367
|
-
process.stdout.write(`\x1b[2m${text}\x1b[0m`);
|
|
353
|
+
/* v8 ignore stop */
|
|
368
354
|
textDirty = text.length > 0 && !text.endsWith("\n");
|
|
369
355
|
},
|
|
356
|
+
onReasoningChunk: (_text) => {
|
|
357
|
+
// Keep reasoning private in the CLI surface. The spinner continues to
|
|
358
|
+
// represent active thinking until actual tool or answer output arrives.
|
|
359
|
+
},
|
|
370
360
|
onToolStart: (_name, _args) => {
|
|
371
361
|
// Stop the model-start spinner: when the model returns only tool calls
|
|
372
362
|
// (no content/reasoning), onModelStreamStart never fires, so the old
|
|
@@ -380,31 +370,29 @@ function createCliCallbacks() {
|
|
|
380
370
|
}
|
|
381
371
|
const toolPhrases = (0, phrases_1.getPhrases)().tool;
|
|
382
372
|
const first = (0, phrases_1.pickPhrase)(toolPhrases);
|
|
383
|
-
|
|
373
|
+
setSpinner(new spinner_imperative_1.ImperativeSpinner(first, toolPhrases));
|
|
384
374
|
currentSpinner.start();
|
|
385
375
|
hadToolRun = true;
|
|
386
376
|
},
|
|
387
377
|
onToolEnd: (name, argSummary, success) => {
|
|
388
378
|
currentSpinner?.stop();
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
const color = success ? "\x1b[32m" : "\x1b[31m";
|
|
392
|
-
process.stderr.write(`${color}${msg}\x1b[0m\n`);
|
|
379
|
+
setSpinner(null);
|
|
380
|
+
(0, tool_display_1.writeToolEnd)(name, argSummary, success);
|
|
393
381
|
},
|
|
394
382
|
onError: (error, severity) => {
|
|
395
383
|
if (severity === "transient") {
|
|
396
384
|
currentSpinner?.fail(error.message);
|
|
397
|
-
|
|
385
|
+
setSpinner(null);
|
|
398
386
|
}
|
|
399
387
|
else {
|
|
400
388
|
currentSpinner?.stop();
|
|
401
|
-
|
|
389
|
+
setSpinner(null);
|
|
402
390
|
process.stderr.write(`\x1b[31m${(0, format_1.formatError)(error)}\x1b[0m\n`);
|
|
403
391
|
}
|
|
404
392
|
},
|
|
405
393
|
onKick: () => {
|
|
406
394
|
currentSpinner?.stop();
|
|
407
|
-
|
|
395
|
+
setSpinner(null);
|
|
408
396
|
if (textDirty) {
|
|
409
397
|
process.stdout.write("\n");
|
|
410
398
|
textDirty = false;
|
|
@@ -413,10 +401,18 @@ function createCliCallbacks() {
|
|
|
413
401
|
},
|
|
414
402
|
flushMarkdown: () => {
|
|
415
403
|
currentSpinner?.stop();
|
|
416
|
-
|
|
404
|
+
setSpinner(null);
|
|
405
|
+
/* v8 ignore start -- wrapper flush: tested via cli.test.ts flushMarkdown tests @preserve */
|
|
417
406
|
const remaining = streamer.flush();
|
|
418
|
-
if (remaining)
|
|
419
|
-
|
|
407
|
+
if (remaining) {
|
|
408
|
+
const wrapped = wrapper.push(remaining);
|
|
409
|
+
if (wrapped)
|
|
410
|
+
process.stdout.write(wrapped);
|
|
411
|
+
}
|
|
412
|
+
const tail = wrapper.flush();
|
|
413
|
+
if (tail)
|
|
414
|
+
process.stdout.write(tail);
|
|
415
|
+
/* v8 ignore stop */
|
|
420
416
|
},
|
|
421
417
|
};
|
|
422
418
|
}
|
|
@@ -457,29 +453,243 @@ async function* createDebouncedLines(source, debounceMs) {
|
|
|
457
453
|
yield lines.join("\n");
|
|
458
454
|
}
|
|
459
455
|
}
|
|
456
|
+
/**
|
|
457
|
+
* Async queue that bridges push-based Ink input to pull-based async iteration.
|
|
458
|
+
* Input from Ink's onSubmit callback is pushed; the business logic loop awaits via for-await.
|
|
459
|
+
*/
|
|
460
|
+
class InputQueue {
|
|
461
|
+
queue = [];
|
|
462
|
+
resolve = null;
|
|
463
|
+
done = false;
|
|
464
|
+
push(input) {
|
|
465
|
+
if (this.done)
|
|
466
|
+
return;
|
|
467
|
+
if (this.resolve) {
|
|
468
|
+
const r = this.resolve;
|
|
469
|
+
this.resolve = null;
|
|
470
|
+
r({ value: input, done: false });
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
this.queue.push(input);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
close() {
|
|
477
|
+
this.done = true;
|
|
478
|
+
if (this.resolve) {
|
|
479
|
+
const r = this.resolve;
|
|
480
|
+
this.resolve = null;
|
|
481
|
+
r({ value: undefined, done: true });
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
/** Drain all buffered items, leaving any pending async awaiter untouched. */
|
|
485
|
+
drainAll() {
|
|
486
|
+
const items = [...this.queue];
|
|
487
|
+
this.queue = [];
|
|
488
|
+
return items;
|
|
489
|
+
}
|
|
490
|
+
[Symbol.asyncIterator]() {
|
|
491
|
+
return {
|
|
492
|
+
next: () => {
|
|
493
|
+
if (this.queue.length > 0) {
|
|
494
|
+
return Promise.resolve({ value: this.queue.shift(), done: false });
|
|
495
|
+
}
|
|
496
|
+
if (this.done) {
|
|
497
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
498
|
+
}
|
|
499
|
+
return new Promise((resolve) => {
|
|
500
|
+
this.resolve = resolve;
|
|
501
|
+
});
|
|
502
|
+
},
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
exports.InputQueue = InputQueue;
|
|
460
507
|
async function runCliSession(options) {
|
|
461
508
|
/* v8 ignore start -- integration: runCliSession is interactive, tested via E2E @preserve */
|
|
462
|
-
const pasteDebounceMs = options.pasteDebounceMs ?? 50;
|
|
463
509
|
const registry = (0, commands_1.createCommandRegistry)();
|
|
464
510
|
if (!options.disableCommands) {
|
|
465
511
|
(0, commands_1.registerDefaultCommands)(registry);
|
|
466
512
|
}
|
|
467
513
|
const messages = options.messages
|
|
468
|
-
?? [{ role: "system", content: await (0, prompt_1.buildSystem)("cli") }];
|
|
469
|
-
|
|
470
|
-
const
|
|
514
|
+
?? [{ role: "system", content: (0, prompt_1.flattenSystemPrompt)(await (0, prompt_1.buildSystem)("cli")) }];
|
|
515
|
+
// ─── Rendering: TUI (Ink + Static) for TTY, imperative for tests/pipes ───
|
|
516
|
+
const useTui = !options._testInputSource && process.stdin.isTTY === true;
|
|
471
517
|
let currentAbort = null;
|
|
472
|
-
const history = [];
|
|
473
518
|
let closed = false;
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
519
|
+
// eslint-disable-next-line prefer-const -- set by onImageMap callback during input
|
|
520
|
+
let pendingImages = null;
|
|
521
|
+
let cliCallbacks;
|
|
522
|
+
let tuiStore = null;
|
|
523
|
+
let inkRef = null;
|
|
524
|
+
const inputQueue = useTui ? new InputQueue() : null;
|
|
525
|
+
let rl = null;
|
|
526
|
+
if (useTui) {
|
|
527
|
+
try {
|
|
528
|
+
const [ink, React, tuiMod, storeMod] = await Promise.all([
|
|
529
|
+
Promise.resolve().then(() => __importStar(require("ink"))),
|
|
530
|
+
Promise.resolve().then(() => __importStar(require("react"))),
|
|
531
|
+
Promise.resolve().then(() => __importStar(require("./cli/ouro-tui"))),
|
|
532
|
+
Promise.resolve().then(() => __importStar(require("./cli/tui-store"))),
|
|
533
|
+
]);
|
|
534
|
+
const { OuroTui } = tuiMod;
|
|
535
|
+
const { TuiStore, createTuiCallbacks } = storeMod;
|
|
536
|
+
tuiStore = new TuiStore();
|
|
537
|
+
cliCallbacks = createTuiCallbacks(tuiStore);
|
|
538
|
+
// Seed input history from previous session (for up/down arrows) — NOT display
|
|
539
|
+
const prevUserMsgs = messages
|
|
540
|
+
.filter((msg) => msg.role === "user" && typeof msg.content === "string")
|
|
541
|
+
.map(msg => msg.content);
|
|
542
|
+
tuiStore.seedHistory(prevUserMsgs);
|
|
543
|
+
// Show session resume context: last 2 exchanges as normal messages
|
|
544
|
+
if (messages.length > 1) {
|
|
545
|
+
const userAssistantMsgs = messages.filter((m) => (m.role === "user" || m.role === "assistant") && typeof m.content === "string" && m.content.trim().length > 0);
|
|
546
|
+
// Extract last 2 exchanges (up to 4 messages)
|
|
547
|
+
const lastExchanges = [];
|
|
548
|
+
for (let i = userAssistantMsgs.length - 1; i >= 0 && lastExchanges.length < 4; i--) {
|
|
549
|
+
lastExchanges.unshift({ role: userAssistantMsgs[i].role, content: userAssistantMsgs[i].content });
|
|
550
|
+
}
|
|
551
|
+
tuiStore.addResumeMessages(lastExchanges);
|
|
552
|
+
}
|
|
553
|
+
// Compute resumeInfo for header banner
|
|
554
|
+
const resumeInfo = messages.length > 1
|
|
555
|
+
? {
|
|
556
|
+
messageCount: messages.filter(m => m.role === "user" || m.role === "assistant").length,
|
|
557
|
+
timeAgo: options.lastActivityAt ? formatTimeAgo(new Date(options.lastActivityAt)) : "unknown",
|
|
558
|
+
}
|
|
559
|
+
: undefined;
|
|
560
|
+
// Ctrl-C state machine (Claude Code behavior):
|
|
561
|
+
// During generation: abort current request
|
|
562
|
+
// Idle with text: clear input
|
|
563
|
+
// Idle empty (first): warn
|
|
564
|
+
// Idle empty (second): exit
|
|
565
|
+
let ctrlCWarned = false;
|
|
566
|
+
let ctrlCTimer = null;
|
|
567
|
+
const handleCtrlC = (hasInput) => {
|
|
568
|
+
if (currentAbort) {
|
|
569
|
+
currentAbort.abort();
|
|
570
|
+
ctrlCWarned = false;
|
|
571
|
+
return "abort";
|
|
572
|
+
}
|
|
573
|
+
if (hasInput) {
|
|
574
|
+
ctrlCWarned = false;
|
|
575
|
+
return "clear";
|
|
576
|
+
}
|
|
577
|
+
if (ctrlCWarned) {
|
|
578
|
+
ctrlCWarned = false;
|
|
579
|
+
if (ctrlCTimer) {
|
|
580
|
+
clearTimeout(ctrlCTimer);
|
|
581
|
+
ctrlCTimer = null;
|
|
582
|
+
}
|
|
583
|
+
closed = true;
|
|
584
|
+
inputQueue.close();
|
|
585
|
+
return "exit";
|
|
586
|
+
}
|
|
587
|
+
ctrlCWarned = true;
|
|
588
|
+
// Reset after 2 seconds — must press twice within window
|
|
589
|
+
ctrlCTimer = setTimeout(() => { ctrlCWarned = false; }, 2000);
|
|
590
|
+
return "warn";
|
|
591
|
+
};
|
|
592
|
+
// TUI root: subscribes to store, passes props to OuroTui
|
|
593
|
+
// Elapsed timer is local React state (no store.notify overhead)
|
|
594
|
+
const storeRef = tuiStore;
|
|
595
|
+
function TuiRoot() {
|
|
596
|
+
const [, forceUpdate] = React.useState(0);
|
|
597
|
+
const [elapsed, setElapsed] = React.useState(0);
|
|
598
|
+
React.useEffect(() => storeRef.subscribe(() => {
|
|
599
|
+
forceUpdate((n) => n + 1);
|
|
600
|
+
// Reset ctrlC warning on any state change (new turn, etc.)
|
|
601
|
+
}), []);
|
|
602
|
+
React.useEffect(() => {
|
|
603
|
+
const iv = setInterval(() => setElapsed(storeRef.getElapsed()), 1000);
|
|
604
|
+
return () => clearInterval(iv);
|
|
605
|
+
}, []);
|
|
606
|
+
return React.createElement(OuroTui, {
|
|
607
|
+
agentName: options.agentName,
|
|
608
|
+
model: (0, identity_1.loadAgentConfig)().humanFacing?.model ?? "",
|
|
609
|
+
completedMessages: storeRef.completedMessages,
|
|
610
|
+
inputHistory: storeRef.inputHistory,
|
|
611
|
+
queuedInputs: storeRef.queuedInputs,
|
|
612
|
+
live: storeRef.live,
|
|
613
|
+
elapsedSeconds: elapsed,
|
|
614
|
+
contextPercent: 0,
|
|
615
|
+
onSubmit: (text) => { ctrlCWarned = false; inputQueue.push(text); storeRef.enqueueInput(text); },
|
|
616
|
+
onCtrlC: handleCtrlC,
|
|
617
|
+
onPopQueue: () => { const items = storeRef.popAllQueuedForEditing(); inputQueue.drainAll(); return items; },
|
|
618
|
+
headerShown: storeRef.headerShown,
|
|
619
|
+
cwd: process.cwd().replace(process.env.HOME ?? "", "~"),
|
|
620
|
+
resumeInfo,
|
|
621
|
+
onImageMap: (images) => { pendingImages = images; },
|
|
622
|
+
onHistoryAdd: (text) => { storeRef.addToHistoryOnly(text); },
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
inkRef = ink.render(React.createElement(TuiRoot), { exitOnCtrlC: false, patchConsole: false });
|
|
626
|
+
}
|
|
627
|
+
catch (err) {
|
|
628
|
+
// Ink failed to load (CJS compat, missing deps, etc.) — fall through to imperative
|
|
629
|
+
(0, runtime_1.emitNervesEvent)({
|
|
630
|
+
component: "senses",
|
|
631
|
+
event: "senses.tui_fallback",
|
|
632
|
+
message: `TUI failed to load, falling back to imperative: ${err instanceof Error ? err.message : String(err)}`,
|
|
633
|
+
meta: {},
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
// Fallback to imperative callbacks if TUI didn't initialize
|
|
638
|
+
if (!tuiStore) {
|
|
639
|
+
cliCallbacks = createCliCallbacks();
|
|
640
|
+
if (options.banner !== false) {
|
|
641
|
+
const bannerText = typeof options.banner === "string"
|
|
642
|
+
? options.banner
|
|
643
|
+
: `${options.agentName} (type /commands for help)`;
|
|
644
|
+
// eslint-disable-next-line no-console -- terminal UX: startup banner
|
|
645
|
+
console.log(`\n${bannerText}\n`);
|
|
646
|
+
}
|
|
481
647
|
}
|
|
482
|
-
|
|
648
|
+
// Display helpers: route to TUI store or imperative stderr/stdout
|
|
649
|
+
const display = {
|
|
650
|
+
error: (msg) => {
|
|
651
|
+
if (tuiStore)
|
|
652
|
+
tuiStore.setError(msg);
|
|
653
|
+
else
|
|
654
|
+
process.stderr.write(`\x1b[31m${msg}\x1b[0m\n`);
|
|
655
|
+
},
|
|
656
|
+
warn: (msg) => {
|
|
657
|
+
if (tuiStore)
|
|
658
|
+
tuiStore.setError(msg); // TUI shows warnings as errors (amber color handled in component)
|
|
659
|
+
else
|
|
660
|
+
process.stderr.write(`\x1b[33m${msg}\x1b[0m\n`);
|
|
661
|
+
},
|
|
662
|
+
text: (msg) => {
|
|
663
|
+
if (tuiStore)
|
|
664
|
+
tuiStore.appendText(msg);
|
|
665
|
+
else
|
|
666
|
+
process.stdout.write(`${msg}\n`);
|
|
667
|
+
},
|
|
668
|
+
suppressInput: () => {
|
|
669
|
+
if (tuiStore)
|
|
670
|
+
tuiStore.suppressInput();
|
|
671
|
+
},
|
|
672
|
+
restoreInput: () => {
|
|
673
|
+
if (tuiStore)
|
|
674
|
+
tuiStore.restoreInput();
|
|
675
|
+
},
|
|
676
|
+
};
|
|
677
|
+
const effectiveToolContext = {
|
|
678
|
+
signin: options.toolContext?.signin ?? (async () => undefined),
|
|
679
|
+
...options.toolContext,
|
|
680
|
+
codingFeedback: {
|
|
681
|
+
send: async (message) => {
|
|
682
|
+
const assistantMessage = {
|
|
683
|
+
role: "assistant",
|
|
684
|
+
content: message,
|
|
685
|
+
};
|
|
686
|
+
messages.push(assistantMessage);
|
|
687
|
+
await options.onAsyncAssistantMessage?.(messages, assistantMessage);
|
|
688
|
+
display.text(message);
|
|
689
|
+
await options.toolContext?.codingFeedback?.send(message);
|
|
690
|
+
},
|
|
691
|
+
},
|
|
692
|
+
};
|
|
483
693
|
// exitOnToolCall machinery: wrap execTool to detect target tool
|
|
484
694
|
let exitToolResult;
|
|
485
695
|
let exitToolFired = false;
|
|
@@ -490,8 +700,6 @@ async function runCliSession(options) {
|
|
|
490
700
|
if (name === options.exitOnToolCall) {
|
|
491
701
|
exitToolResult = result;
|
|
492
702
|
exitToolFired = true;
|
|
493
|
-
// Abort immediately so the model doesn't generate more output
|
|
494
|
-
// (e.g. reasoning about calling final_answer after complete_adoption)
|
|
495
703
|
currentAbort?.abort();
|
|
496
704
|
}
|
|
497
705
|
return result;
|
|
@@ -499,28 +707,6 @@ async function runCliSession(options) {
|
|
|
499
707
|
: resolvedExecTool;
|
|
500
708
|
// Resolve toolChoiceRequired: use explicit option if set, else fall back to toggle
|
|
501
709
|
const getEffectiveToolChoiceRequired = () => options.toolChoiceRequired !== undefined ? options.toolChoiceRequired : (0, commands_1.getToolChoiceRequired)();
|
|
502
|
-
// Ctrl-C at the input prompt: clear line or warn/exit
|
|
503
|
-
rl.on("SIGINT", () => {
|
|
504
|
-
const rlInt = rl;
|
|
505
|
-
const currentLine = rlInt.line || "";
|
|
506
|
-
const result = handleSigint(rl, currentLine);
|
|
507
|
-
if (result === "clear") {
|
|
508
|
-
rlInt.line = "";
|
|
509
|
-
rlInt.cursor = 0;
|
|
510
|
-
process.stdout.write("\r\x1b[K\x1b[36m> \x1b[0m");
|
|
511
|
-
}
|
|
512
|
-
else if (result === "warn") {
|
|
513
|
-
rlInt.line = "";
|
|
514
|
-
rlInt.cursor = 0;
|
|
515
|
-
process.stdout.write("\r\x1b[K");
|
|
516
|
-
process.stderr.write("press Ctrl-C again to exit\n");
|
|
517
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
518
|
-
}
|
|
519
|
-
else {
|
|
520
|
-
rl.close();
|
|
521
|
-
}
|
|
522
|
-
});
|
|
523
|
-
const debouncedLines = (source) => createDebouncedLines(source, pasteDebounceMs);
|
|
524
710
|
(0, runtime_1.emitNervesEvent)({
|
|
525
711
|
component: "senses",
|
|
526
712
|
event: "senses.cli_session_start",
|
|
@@ -533,7 +719,7 @@ async function runCliSession(options) {
|
|
|
533
719
|
if (options.autoFirstTurn && messages.length > 0 && messages[messages.length - 1]?.role === "user") {
|
|
534
720
|
currentAbort = new AbortController();
|
|
535
721
|
const traceId = (0, nerves_1.createTraceId)();
|
|
536
|
-
|
|
722
|
+
display.suppressInput();
|
|
537
723
|
let result;
|
|
538
724
|
try {
|
|
539
725
|
result = await (0, core_1.runAgent)(messages, cliCallbacks, options.skipSystemPromptRefresh ? undefined : "cli", currentAbort.signal, {
|
|
@@ -541,124 +727,145 @@ async function runCliSession(options) {
|
|
|
541
727
|
traceId,
|
|
542
728
|
tools: options.tools,
|
|
543
729
|
execTool: wrappedExecTool,
|
|
544
|
-
toolContext:
|
|
730
|
+
toolContext: effectiveToolContext,
|
|
545
731
|
});
|
|
546
732
|
}
|
|
547
733
|
catch (err) {
|
|
548
|
-
// AbortError (Ctrl-C) -- silently continue to prompt
|
|
549
|
-
// All other errors: show the user what happened
|
|
550
734
|
if (!(err instanceof DOMException && err.name === "AbortError")) {
|
|
551
|
-
|
|
735
|
+
display.error(err instanceof Error ? err.message : String(err));
|
|
552
736
|
}
|
|
553
737
|
}
|
|
554
738
|
cliCallbacks.flushMarkdown();
|
|
555
|
-
|
|
739
|
+
display.restoreInput();
|
|
556
740
|
currentAbort = null;
|
|
557
741
|
if (exitToolFired) {
|
|
558
742
|
exitReason = "tool_exit";
|
|
559
|
-
|
|
743
|
+
closed = true;
|
|
560
744
|
}
|
|
561
745
|
else {
|
|
562
746
|
const lastMsg = messages[messages.length - 1];
|
|
563
747
|
if (lastMsg?.role === "assistant" && !(typeof lastMsg.content === "string" ? lastMsg.content : "").trim()) {
|
|
564
|
-
|
|
748
|
+
display.warn("(empty response)");
|
|
565
749
|
}
|
|
566
|
-
process.stdout.write("\n\n");
|
|
567
750
|
if (options.onTurnEnd) {
|
|
568
751
|
await options.onTurnEnd(messages, result ?? { usage: undefined });
|
|
569
752
|
}
|
|
570
753
|
}
|
|
571
754
|
}
|
|
572
|
-
if (!exitToolFired) {
|
|
573
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
574
|
-
}
|
|
575
755
|
try {
|
|
576
|
-
for
|
|
756
|
+
// Input source: TUI queue for Ink, test source for tests, readline fallback
|
|
757
|
+
let inputSource;
|
|
758
|
+
let inputCtrl = null;
|
|
759
|
+
if (tuiStore && inputQueue) {
|
|
760
|
+
// TUI path: input comes from Ink's onSubmit → InputQueue
|
|
761
|
+
inputSource = inputQueue;
|
|
762
|
+
}
|
|
763
|
+
else if (options._testInputSource) {
|
|
764
|
+
inputSource = options._testInputSource;
|
|
765
|
+
}
|
|
766
|
+
else {
|
|
767
|
+
// Imperative fallback: readline
|
|
768
|
+
const isTTY = process.stdin.isTTY === true;
|
|
769
|
+
rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: isTTY });
|
|
770
|
+
inputCtrl = new InputController(rl);
|
|
771
|
+
inputSource = rl;
|
|
772
|
+
process.stdout.write(CLI_PROMPT);
|
|
773
|
+
}
|
|
774
|
+
for await (const input of inputSource) {
|
|
577
775
|
if (closed)
|
|
578
776
|
break;
|
|
579
|
-
if (!input.trim())
|
|
580
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
777
|
+
if (!input.trim())
|
|
581
778
|
continue;
|
|
582
|
-
|
|
779
|
+
// Remove from TUI queue display as the agent picks up this message
|
|
780
|
+
tuiStore?.dequeueInput(input);
|
|
583
781
|
// Optional input gate (e.g. trust gate in main)
|
|
584
782
|
if (options.onInput) {
|
|
585
783
|
const gate = options.onInput(input);
|
|
586
784
|
if (!gate.allowed) {
|
|
587
785
|
if (gate.reply) {
|
|
588
|
-
|
|
786
|
+
display.text(gate.reply);
|
|
589
787
|
}
|
|
590
788
|
if (closed)
|
|
591
789
|
break;
|
|
592
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
593
790
|
continue;
|
|
594
791
|
}
|
|
595
792
|
}
|
|
596
|
-
// Check for slash commands
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
if (dispatchResult.
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
console.log("session cleared");
|
|
610
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
611
|
-
continue;
|
|
612
|
-
}
|
|
613
|
-
else if (dispatchResult.result.action === "response") {
|
|
614
|
-
// eslint-disable-next-line no-console -- terminal UX: command dispatch result
|
|
615
|
-
console.log(dispatchResult.result.message || "");
|
|
616
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
617
|
-
continue;
|
|
793
|
+
// Check for slash commands (legacy path only — pipeline handles commands for runTurn path)
|
|
794
|
+
if (!options.runTurn) {
|
|
795
|
+
const parsed = (0, commands_1.parseSlashCommand)(input);
|
|
796
|
+
if (parsed) {
|
|
797
|
+
const dispatchResult = registry.dispatch(parsed.command, { channel: "cli" });
|
|
798
|
+
if (dispatchResult.handled && dispatchResult.result) {
|
|
799
|
+
if (dispatchResult.result.action === "exit") {
|
|
800
|
+
break;
|
|
801
|
+
}
|
|
802
|
+
else if (dispatchResult.result.action === "response") {
|
|
803
|
+
display.text(dispatchResult.result.message || "");
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
618
806
|
}
|
|
619
807
|
}
|
|
620
808
|
}
|
|
621
|
-
//
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
let echoRows = 0;
|
|
625
|
-
for (const line of inputLines) {
|
|
626
|
-
echoRows += Math.ceil((2 + line.length) / cols);
|
|
627
|
-
}
|
|
628
|
-
process.stdout.write(`\x1b[${echoRows}A\x1b[K` + `\x1b[1m> ${inputLines[0]}${inputLines.length > 1 ? ` (+${inputLines.length - 1} lines)` : ""}\x1b[0m\n\n`);
|
|
629
|
-
addHistory(history, input);
|
|
809
|
+
// Track user message in TUI for display
|
|
810
|
+
if (tuiStore)
|
|
811
|
+
tuiStore.addUserMessage(input);
|
|
630
812
|
currentAbort = new AbortController();
|
|
631
|
-
|
|
813
|
+
if (tuiStore)
|
|
814
|
+
tuiStore.suppressInput();
|
|
815
|
+
inputCtrl?.suppress(() => { currentAbort?.abort(); });
|
|
816
|
+
// Resolve pending image content before the turn executes
|
|
817
|
+
let contentParts = null;
|
|
818
|
+
const currentImages = pendingImages;
|
|
819
|
+
if (currentImages && currentImages.size > 0) {
|
|
820
|
+
contentParts = await (0, image_paste_1.resolveImageContent)(input, currentImages);
|
|
821
|
+
pendingImages = null;
|
|
822
|
+
}
|
|
632
823
|
let result;
|
|
633
824
|
try {
|
|
634
825
|
if (options.runTurn) {
|
|
635
826
|
// Pipeline-based turn: the runTurn callback handles user message assembly,
|
|
636
827
|
// pending drain, trust gate, runAgent, postTurn, and token accumulation.
|
|
637
|
-
result = await options.runTurn(messages, input, cliCallbacks, currentAbort.signal);
|
|
828
|
+
result = await options.runTurn(messages, input, cliCallbacks, currentAbort.signal, effectiveToolContext, contentParts ?? undefined);
|
|
829
|
+
// Handle pipeline-intercepted commands with loop-control side effects
|
|
830
|
+
if (result?.turnOutcome === "command") {
|
|
831
|
+
if (result.commandAction === "exit") {
|
|
832
|
+
break;
|
|
833
|
+
}
|
|
834
|
+
// For "response" commands: the pipeline already emitted the response via onTextChunk
|
|
835
|
+
cliCallbacks.flushMarkdown();
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
638
838
|
}
|
|
639
839
|
else {
|
|
640
|
-
// Legacy path: inline runAgent (used by
|
|
840
|
+
// Legacy path: inline runAgent (used by serpent guide and tests)
|
|
641
841
|
const prefix = options.getContentPrefix?.();
|
|
642
|
-
|
|
842
|
+
const userContent = contentParts
|
|
843
|
+
? contentParts
|
|
844
|
+
: (prefix ? `${prefix}\n\n${input}` : input);
|
|
845
|
+
const userMsg = { role: "user", content: userContent };
|
|
846
|
+
(0, session_events_1.stampIngressTime)(userMsg);
|
|
847
|
+
messages.push(userMsg);
|
|
643
848
|
const traceId = (0, nerves_1.createTraceId)();
|
|
644
849
|
result = await (0, core_1.runAgent)(messages, cliCallbacks, options.skipSystemPromptRefresh ? undefined : "cli", currentAbort.signal, {
|
|
645
850
|
toolChoiceRequired: getEffectiveToolChoiceRequired(),
|
|
646
851
|
traceId,
|
|
647
852
|
tools: options.tools,
|
|
648
853
|
execTool: wrappedExecTool,
|
|
649
|
-
toolContext:
|
|
854
|
+
toolContext: effectiveToolContext,
|
|
650
855
|
});
|
|
651
856
|
}
|
|
652
857
|
}
|
|
653
858
|
catch (err) {
|
|
654
|
-
// AbortError (Ctrl-C) -- silently return to prompt
|
|
655
|
-
// All other errors: show the user what happened
|
|
656
859
|
if (!(err instanceof DOMException && err.name === "AbortError")) {
|
|
657
|
-
|
|
860
|
+
display.error(err instanceof Error ? err.message : String(err));
|
|
658
861
|
}
|
|
659
862
|
}
|
|
660
863
|
cliCallbacks.flushMarkdown();
|
|
661
|
-
|
|
864
|
+
if (!tuiStore)
|
|
865
|
+
process.stdout.write("\n"); // ensure response ends with newline before prompt (imperative only)
|
|
866
|
+
if (tuiStore)
|
|
867
|
+
tuiStore.restoreInput();
|
|
868
|
+
inputCtrl?.restore();
|
|
662
869
|
currentAbort = null;
|
|
663
870
|
// Check if exit tool was fired during this turn
|
|
664
871
|
if (exitToolFired) {
|
|
@@ -668,20 +875,35 @@ async function runCliSession(options) {
|
|
|
668
875
|
// Safety net: never silently swallow an empty response
|
|
669
876
|
const lastMsg = messages[messages.length - 1];
|
|
670
877
|
if (lastMsg?.role === "assistant" && !(typeof lastMsg.content === "string" ? lastMsg.content : "").trim()) {
|
|
671
|
-
|
|
878
|
+
display.warn("(empty response)");
|
|
672
879
|
}
|
|
673
|
-
process.stdout.write("\n\n");
|
|
674
880
|
// Post-turn hook (session persistence, pending drain, prompt refresh, etc.)
|
|
675
881
|
if (options.onTurnEnd) {
|
|
676
882
|
await options.onTurnEnd(messages, result ?? { usage: undefined });
|
|
677
883
|
}
|
|
678
884
|
if (closed)
|
|
679
885
|
break;
|
|
680
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
681
886
|
}
|
|
682
887
|
}
|
|
683
888
|
finally {
|
|
684
|
-
rl
|
|
889
|
+
rl?.close();
|
|
890
|
+
if (inkRef) {
|
|
891
|
+
// Suppress React "state update on unmounted component" warnings during cleanup.
|
|
892
|
+
// Ink's useInput hook fires after unmount — this is harmless but noisy.
|
|
893
|
+
// This also covers the SerpentGuide exitOnToolCall path, which aborts the
|
|
894
|
+
// current request and breaks out of the loop into this finally block.
|
|
895
|
+
// eslint-disable-next-line no-console -- intentional console.warn/error override for cleanup
|
|
896
|
+
const origWarn = console.warn;
|
|
897
|
+
const origError = console.error; // eslint-disable-line no-console
|
|
898
|
+
// eslint-disable-next-line no-console -- suppress React unmount warnings
|
|
899
|
+
console.warn = (...args) => { if (typeof args[0] === "string" && args[0].includes("Can't perform a React state update"))
|
|
900
|
+
return; origWarn.apply(console, args); };
|
|
901
|
+
// eslint-disable-next-line no-console -- suppress React unmount warnings
|
|
902
|
+
console.error = (...args) => { if (typeof args[0] === "string" && args[0].includes("Can't perform a React state update"))
|
|
903
|
+
return; origError.apply(console, args); };
|
|
904
|
+
inkRef.unmount();
|
|
905
|
+
setTimeout(() => { console.warn = origWarn; console.error = origError; }, 100); // eslint-disable-line no-console
|
|
906
|
+
}
|
|
685
907
|
if (options.banner !== false) {
|
|
686
908
|
// eslint-disable-next-line no-console -- terminal UX: goodbye
|
|
687
909
|
console.log("bye");
|
|
@@ -694,17 +916,32 @@ async function main(agentName, options) {
|
|
|
694
916
|
if (agentName)
|
|
695
917
|
(0, identity_1.setAgentName)(agentName);
|
|
696
918
|
const pasteDebounceMs = options?.pasteDebounceMs ?? 50;
|
|
919
|
+
// Safety net: process-level SIGINT handler ensures Ctrl+C always exits,
|
|
920
|
+
// even when Ink's event loop is blocked by expensive renders.
|
|
921
|
+
/* v8 ignore start -- process signal handler @preserve */
|
|
922
|
+
let sigintCount = 0;
|
|
923
|
+
const sigintHandler = () => {
|
|
924
|
+
sigintCount++;
|
|
925
|
+
if (sigintCount >= 2)
|
|
926
|
+
process.exit(1);
|
|
927
|
+
};
|
|
928
|
+
if (!options?._testInputSource) {
|
|
929
|
+
process.on("SIGINT", sigintHandler);
|
|
930
|
+
}
|
|
931
|
+
/* v8 ignore stop */
|
|
932
|
+
// Register spinner hooks so log output clears the spinner before printing
|
|
933
|
+
(0, nerves_1.registerSpinnerHooks)(pauseActiveSpinner, resumeActiveSpinner);
|
|
697
934
|
// Fallback: apply pending updates for daemon-less direct CLI usage
|
|
698
935
|
(0, update_hooks_1.registerUpdateHook)(bundle_meta_1.bundleMetaHook);
|
|
936
|
+
(0, update_hooks_1.registerUpdateHook)(agent_config_v2_1.agentConfigV2Hook);
|
|
699
937
|
await (0, update_hooks_1.applyPendingUpdates)((0, identity_1.getAgentBundlesRoot)(), (0, bundle_manifest_1.getPackageVersion)());
|
|
700
938
|
// Fail fast if provider is misconfigured (triggers human-readable error + exit)
|
|
701
|
-
(0, core_1.getProvider)();
|
|
939
|
+
(0, core_1.getProvider)("human");
|
|
702
940
|
// Resolve context kernel (identity + channel) for CLI
|
|
703
941
|
const friendsPath = path.join((0, identity_1.getAgentRoot)(), "friends");
|
|
704
942
|
const friendStore = new store_file_1.FileFriendStore(friendsPath);
|
|
705
943
|
const username = os.userInfo().username;
|
|
706
|
-
const
|
|
707
|
-
const localExternalId = `${username}@${hostname}`;
|
|
944
|
+
const localExternalId = username;
|
|
708
945
|
const resolver = new resolver_1.FriendResolver(friendStore, {
|
|
709
946
|
provider: "local",
|
|
710
947
|
externalId: localExternalId,
|
|
@@ -735,31 +972,73 @@ async function main(agentName, options) {
|
|
|
735
972
|
// Load existing session or start fresh
|
|
736
973
|
const existing = (0, context_1.loadSession)(sessPath);
|
|
737
974
|
let sessionState = existing?.state;
|
|
975
|
+
let sessionEvents = existing?.events ?? [];
|
|
976
|
+
const mcpManager = await (0, mcp_manager_1.getSharedMcpManager)() ?? undefined;
|
|
738
977
|
const sessionMessages = existing?.messages && existing.messages.length > 0
|
|
739
978
|
? existing.messages
|
|
740
|
-
: [{ role: "system", content: await (0, prompt_1.buildSystem)("cli",
|
|
979
|
+
: [{ role: "system", content: (0, prompt_1.flattenSystemPrompt)(await (0, prompt_1.buildSystem)("cli", {}, resolvedContext)) }];
|
|
980
|
+
// Repair any orphaned tool calls from a crash mid-turn
|
|
981
|
+
(0, core_1.repairOrphanedToolCalls)(sessionMessages);
|
|
741
982
|
// Per-turn pipeline input: CLI capabilities and pending dir
|
|
742
983
|
const cliCapabilities = (0, channel_1.getChannelCapabilities)("cli");
|
|
743
984
|
const currentAgentName = (0, identity_1.getAgentName)();
|
|
744
985
|
const pendingDir = (0, pending_1.getPendingDir)(currentAgentName, friendId, "cli", "session");
|
|
745
|
-
const summarize = (0, core_1.createSummarize)();
|
|
986
|
+
const summarize = (0, core_1.createSummarize)("human");
|
|
987
|
+
const cliFailoverState = { pending: null };
|
|
746
988
|
try {
|
|
747
989
|
await runCliSession({
|
|
748
990
|
agentName: currentAgentName,
|
|
749
991
|
pasteDebounceMs,
|
|
750
992
|
messages: sessionMessages,
|
|
751
|
-
|
|
993
|
+
lastActivityAt: sessionState?.lastFriendActivityAt,
|
|
994
|
+
_testInputSource: options?._testInputSource,
|
|
995
|
+
onAsyncAssistantMessage: async (messages, _assistantMessage) => {
|
|
996
|
+
const prepared = (0, context_1.postTurnTrim)(messages);
|
|
997
|
+
const events = (0, context_1.postTurnPersist)(sessPath, prepared, undefined, sessionState);
|
|
998
|
+
/* v8 ignore next -- defensive: postTurnPersist always returns events in practice @preserve */
|
|
999
|
+
sessionEvents = events.length > 0 ? events : sessionEvents;
|
|
1000
|
+
},
|
|
1001
|
+
runTurn: async (messages, userInput, callbacks, signal, toolContext, userContent) => {
|
|
752
1002
|
// Run the full per-turn pipeline: resolve -> gate -> session -> drain -> runAgent -> postTurn -> tokens
|
|
753
1003
|
// User message passed via input.messages so the pipeline can prepend pending messages to it.
|
|
1004
|
+
const failoverState = cliFailoverState;
|
|
1005
|
+
// Capture terminal errors instead of displaying immediately — the failover
|
|
1006
|
+
// message replaces the raw error if failover triggers successfully.
|
|
1007
|
+
let capturedTerminalError = null;
|
|
1008
|
+
/* v8 ignore start -- failover-aware callback wrapper: tested via pipeline integration @preserve */
|
|
1009
|
+
const failoverAwareCallbacks = {
|
|
1010
|
+
...callbacks,
|
|
1011
|
+
// Save session after each tool result for crash recovery (deferred to avoid blocking)
|
|
1012
|
+
onToolResult: (turnMessages) => {
|
|
1013
|
+
const prepared = (0, context_1.postTurnTrim)(turnMessages);
|
|
1014
|
+
(0, context_1.deferPostTurnPersist)(sessPath, prepared, undefined, sessionState);
|
|
1015
|
+
},
|
|
1016
|
+
onError: (error, severity) => {
|
|
1017
|
+
if (severity === "terminal" && failoverState) {
|
|
1018
|
+
capturedTerminalError = error;
|
|
1019
|
+
callbacks.onError(new Error(""), "transient");
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
callbacks.onError(error, severity);
|
|
1023
|
+
},
|
|
1024
|
+
};
|
|
1025
|
+
/* v8 ignore stop */
|
|
754
1026
|
const result = await (0, pipeline_1.handleInboundTurn)({
|
|
755
1027
|
channel: "cli",
|
|
756
1028
|
sessionKey: "session",
|
|
757
1029
|
capabilities: cliCapabilities,
|
|
758
|
-
messages: [{ role: "user", content: userInput }],
|
|
1030
|
+
messages: [{ role: "user", content: userContent ?? userInput }],
|
|
759
1031
|
continuityIngressTexts: getCliContinuityIngressTexts(userInput),
|
|
760
|
-
callbacks,
|
|
1032
|
+
callbacks: failoverAwareCallbacks,
|
|
761
1033
|
friendResolver: { resolve: () => Promise.resolve(resolvedContext) },
|
|
762
|
-
sessionLoader: {
|
|
1034
|
+
sessionLoader: {
|
|
1035
|
+
loadOrCreate: () => Promise.resolve({
|
|
1036
|
+
messages,
|
|
1037
|
+
sessionPath: sessPath,
|
|
1038
|
+
state: sessionState,
|
|
1039
|
+
events: sessionEvents,
|
|
1040
|
+
}),
|
|
1041
|
+
},
|
|
763
1042
|
pendingDir,
|
|
764
1043
|
friendStore,
|
|
765
1044
|
provider: "local",
|
|
@@ -777,30 +1056,53 @@ async function main(agentName, options) {
|
|
|
777
1056
|
},
|
|
778
1057
|
}),
|
|
779
1058
|
postTurn: (turnMessages, sessionPathArg, usage, hooks, state) => {
|
|
780
|
-
(
|
|
1059
|
+
// Trim synchronously (mutates turnMessages for next turn),
|
|
1060
|
+
// then defer envelope build + disk I/O to avoid blocking the TUI.
|
|
1061
|
+
const prepared = (0, context_1.postTurnTrim)(turnMessages, usage, hooks);
|
|
781
1062
|
sessionState = state;
|
|
1063
|
+
(0, context_1.deferPostTurnPersist)(sessionPathArg, prepared, usage, state).then((events) => {
|
|
1064
|
+
/* v8 ignore next -- defensive: deferPostTurnPersist always resolves events in practice @preserve */
|
|
1065
|
+
sessionEvents = events.length > 0 ? events : sessionEvents;
|
|
1066
|
+
});
|
|
782
1067
|
},
|
|
783
1068
|
accumulateFriendTokens: tokens_1.accumulateFriendTokens,
|
|
784
1069
|
signal,
|
|
785
1070
|
runAgentOptions: {
|
|
786
1071
|
toolChoiceRequired: (0, commands_1.getToolChoiceRequired)(),
|
|
787
1072
|
traceId: (0, nerves_1.createTraceId)(),
|
|
1073
|
+
mcpManager,
|
|
1074
|
+
toolContext,
|
|
788
1075
|
},
|
|
1076
|
+
failoverState,
|
|
789
1077
|
});
|
|
1078
|
+
/* v8 ignore start -- failover display: tested via pipeline integration tests @preserve */
|
|
1079
|
+
if (result.failoverMessage) {
|
|
1080
|
+
// Failover handled it — show the actionable message instead of the raw error
|
|
1081
|
+
process.stdout.write(`\x1b[33m${result.failoverMessage}\x1b[0m\n`);
|
|
1082
|
+
}
|
|
1083
|
+
else if (capturedTerminalError) {
|
|
1084
|
+
// Failover didn't trigger (no failoverState, or sequence failed) — show the raw error
|
|
1085
|
+
process.stderr.write(`\x1b[31m${(0, format_1.formatError)(capturedTerminalError)}\x1b[0m\n`);
|
|
1086
|
+
}
|
|
1087
|
+
/* v8 ignore stop */
|
|
790
1088
|
// Handle gate rejection: display auto-reply if present
|
|
791
1089
|
if (!result.gateResult.allowed) {
|
|
792
1090
|
if ("autoReply" in result.gateResult && result.gateResult.autoReply) {
|
|
793
1091
|
process.stdout.write(`${result.gateResult.autoReply}\n`);
|
|
794
1092
|
}
|
|
795
1093
|
}
|
|
796
|
-
return { usage: result.usage };
|
|
797
|
-
},
|
|
798
|
-
onNewSession: () => {
|
|
799
|
-
(0, context_1.deleteSession)(sessPath);
|
|
1094
|
+
return { usage: result.usage, turnOutcome: result.turnOutcome, commandAction: result.commandAction };
|
|
800
1095
|
},
|
|
801
1096
|
});
|
|
802
1097
|
}
|
|
803
1098
|
finally {
|
|
804
1099
|
sessionLock?.release();
|
|
805
1100
|
}
|
|
1101
|
+
// Force exit: lingering handles (Ink cleanup timers, MCP connections) keep the
|
|
1102
|
+
// event loop alive after the interactive session ends. This is safe because all
|
|
1103
|
+
// session persistence has already completed in the finally block above.
|
|
1104
|
+
/* v8 ignore next -- process.exit not callable in vitest @preserve */
|
|
1105
|
+
if (!options?._testInputSource)
|
|
1106
|
+
process.exit(0);
|
|
806
1107
|
}
|
|
1108
|
+
// CI trigger
|