@ouro.bot/cli 0.1.0-alpha.38 → 0.1.0-alpha.381
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 +109 -14
- 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 +2303 -2
- 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 +832 -0
- 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 +378 -0
- package/dist/heart/bridges/manager.js +358 -0
- package/dist/heart/bridges/state-machine.js +135 -0
- package/dist/heart/bridges/store.js +123 -0
- package/dist/heart/bundle-state.js +168 -0
- package/dist/heart/commitments.js +111 -0
- package/dist/heart/config-registry.js +304 -0
- package/dist/heart/config.js +111 -128
- package/dist/heart/core.js +803 -259
- package/dist/heart/cross-chat-delivery.js +131 -0
- package/dist/heart/daemon/agent-config-check.js +376 -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 +205 -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 +560 -0
- package/dist/heart/daemon/cli-exec.js +3220 -0
- package/dist/heart/daemon/cli-help.js +319 -0
- package/dist/heart/daemon/cli-parse.js +1060 -0
- package/dist/heart/daemon/cli-render-doctor.js +57 -0
- package/dist/heart/daemon/cli-render.js +560 -0
- package/dist/heart/daemon/cli-types.js +8 -0
- package/dist/heart/daemon/daemon-cli.js +29 -1498
- package/dist/heart/daemon/daemon-entry.js +356 -3
- package/dist/heart/daemon/daemon-health.js +141 -0
- package/dist/heart/daemon/daemon-runtime-sync.js +157 -12
- package/dist/heart/daemon/daemon-tombstone.js +236 -0
- package/dist/heart/daemon/daemon.js +774 -58
- package/dist/heart/daemon/doctor-types.js +8 -0
- package/dist/heart/daemon/doctor.js +419 -0
- package/dist/heart/daemon/health-monitor.js +79 -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/inner-status.js +89 -0
- package/dist/heart/daemon/interactive-repair.js +182 -0
- package/dist/heart/daemon/launchd.js +46 -9
- package/dist/heart/daemon/log-tailer.js +82 -12
- package/dist/heart/daemon/logs-prune.js +105 -0
- package/dist/heart/daemon/message-router.js +17 -8
- package/dist/heart/daemon/os-cron-deps.js +134 -0
- package/dist/heart/daemon/ouro-bot-entry.js +1 -1
- package/dist/heart/daemon/process-manager.js +201 -0
- package/dist/heart/daemon/provider-discovery.js +137 -0
- package/dist/heart/daemon/pulse.js +475 -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 +119 -30
- 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 +307 -0
- package/dist/heart/daemon/stale-bundle-prune.js +96 -0
- package/dist/heart/daemon/startup-tui.js +237 -0
- package/dist/heart/daemon/task-scheduler.js +3 -25
- package/dist/heart/daemon/thoughts.js +510 -0
- package/dist/heart/daemon/up-progress.js +135 -0
- package/dist/heart/delegation.js +62 -0
- package/dist/heart/habits/habit-migration.js +181 -0
- package/dist/heart/habits/habit-parser.js +140 -0
- package/dist/heart/habits/habit-scheduler.js +371 -0
- package/dist/heart/{daemon → hatch}/hatch-flow.js +55 -126
- package/dist/heart/{daemon → hatch}/hatch-specialist.js +3 -3
- package/dist/heart/{daemon → hatch}/specialist-prompt.js +10 -8
- package/dist/heart/{daemon → hatch}/specialist-tools.js +30 -10
- package/dist/heart/identity.js +153 -65
- package/dist/heart/kept-notes.js +357 -0
- package/dist/heart/kicks.js +2 -20
- package/dist/heart/machine-identity.js +161 -0
- package/dist/heart/mcp/mcp-server.js +653 -0
- package/dist/heart/migrate-config.js +100 -0
- package/dist/heart/model-capabilities.js +59 -0
- package/dist/heart/outlook/outlook-http-hooks.js +64 -0
- package/dist/heart/outlook/outlook-http-response.js +7 -0
- package/dist/heart/outlook/outlook-http-routes.js +232 -0
- package/dist/heart/outlook/outlook-http-static.js +99 -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 +28 -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 +359 -0
- package/dist/heart/outlook/readers/continuity-readers.js +332 -0
- package/dist/heart/outlook/readers/runtime-readers.js +660 -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/progress-story.js +42 -0
- package/dist/heart/provider-attempt.js +133 -0
- package/dist/heart/provider-binding-resolver.js +239 -0
- package/dist/heart/provider-credentials.js +379 -0
- package/dist/heart/provider-failover.js +266 -0
- package/dist/heart/provider-models.js +81 -0
- package/dist/heart/provider-ping.js +237 -0
- package/dist/heart/provider-state.js +216 -0
- package/dist/heart/provider-visibility.js +186 -0
- package/dist/heart/providers/anthropic-token.js +131 -0
- package/dist/heart/providers/anthropic.js +193 -55
- package/dist/heart/providers/azure.js +103 -12
- package/dist/heart/providers/error-classification.js +63 -0
- package/dist/heart/providers/github-copilot.js +145 -0
- package/dist/heart/providers/minimax-vlm.js +189 -0
- package/dist/heart/providers/minimax.js +29 -7
- package/dist/heart/providers/openai-codex.js +39 -29
- package/dist/heart/runtime-credentials.js +181 -0
- package/dist/heart/session-activity.js +190 -0
- package/dist/heart/session-events.js +855 -0
- package/dist/heart/session-transcript.js +167 -0
- package/dist/heart/start-of-turn-packet.js +345 -0
- package/dist/heart/streaming.js +36 -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 +351 -0
- package/dist/heart/turn-coordinator.js +28 -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 +301 -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 +12 -2
- package/dist/heart/{daemon → versioning}/update-hooks.js +63 -59
- package/dist/mind/bundle-manifest.js +7 -1
- package/dist/mind/context.js +141 -94
- package/dist/mind/diary-integrity.js +60 -0
- package/dist/mind/{memory.js → diary.js} +84 -96
- package/dist/mind/embedding-provider.js +60 -0
- package/dist/mind/file-state.js +179 -0
- package/dist/mind/first-impressions.js +14 -1
- package/dist/mind/friends/channel.js +48 -0
- package/dist/mind/friends/group-context.js +144 -0
- package/dist/mind/friends/resolver.js +38 -1
- package/dist/mind/friends/store-file.js +39 -3
- package/dist/mind/friends/trust-explanation.js +74 -0
- package/dist/mind/friends/types.js +9 -1
- 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 +74 -7
- package/dist/mind/prompt-refresh.js +3 -2
- package/dist/mind/prompt.js +966 -183
- package/dist/mind/provenance-trust.js +26 -0
- package/dist/mind/scrutiny.js +173 -0
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/cli-logging.js +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/file-completeness.js +83 -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/runtime.js +5 -1
- package/dist/outlook-ui/assets/index-BAcU08c-.css +1 -0
- package/dist/outlook-ui/assets/index-D7l3l4vY.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 +365 -0
- package/dist/repertoire/bundle-templates.js +72 -0
- package/dist/repertoire/bw-installer.js +79 -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 +107 -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 +371 -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 +43 -5
- package/dist/repertoire/tasks/fix.js +182 -0
- package/dist/repertoire/tasks/index.js +28 -10
- package/dist/repertoire/tasks/lifecycle.js +2 -2
- package/dist/repertoire/tasks/parser.js +3 -2
- package/dist/repertoire/tasks/scanner.js +194 -37
- package/dist/repertoire/tasks/transitions.js +16 -79
- package/dist/repertoire/tool-results.js +29 -0
- package/dist/repertoire/tools-attachments.js +317 -0
- package/dist/repertoire/tools-base.js +42 -690
- 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 +182 -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-notes.js +376 -0
- package/dist/repertoire/tools-session.js +739 -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 +12 -62
- package/dist/repertoire/tools-travel.js +125 -0
- package/dist/repertoire/tools-user-profile.js +144 -0
- package/dist/repertoire/tools-vault.js +40 -0
- package/dist/repertoire/tools.js +144 -120
- package/dist/repertoire/travel-api-client.js +360 -0
- package/dist/repertoire/user-profile.js +118 -0
- package/dist/repertoire/vault-setup.js +241 -0
- package/dist/repertoire/vault-unlock.js +364 -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} +260 -9
- package/dist/senses/bluebubbles/entry.js +70 -0
- package/dist/senses/bluebubbles/inbound-log.js +113 -0
- package/dist/senses/bluebubbles/index.js +1620 -0
- package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -70
- package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +43 -12
- package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +46 -6
- package/dist/senses/bluebubbles/replay.js +129 -0
- package/dist/senses/bluebubbles/runtime-state.js +109 -0
- 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 +574 -254
- package/dist/senses/commands.js +66 -3
- package/dist/senses/continuity.js +94 -0
- package/dist/senses/habit-turn-message.js +108 -0
- package/dist/senses/inner-dialog-worker.js +112 -19
- package/dist/senses/inner-dialog.js +630 -82
- package/dist/senses/pipeline.js +602 -0
- package/dist/senses/proactive-content-guard.js +51 -0
- package/dist/senses/shared-turn.js +205 -0
- package/dist/senses/surface-tool.js +68 -0
- package/dist/senses/teams-entry.js +60 -8
- package/dist/senses/teams.js +585 -231
- package/dist/senses/trust-gate.js +112 -2
- package/package.json +28 -7
- package/skills/agent-commerce.md +106 -0
- package/skills/browser-navigation.md +110 -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 +134 -0
- package/dist/heart/daemon/ouro-path-installer.js +0 -178
- package/dist/heart/daemon/subagent-installer.js +0 -134
- package/dist/mind/associative-recall.js +0 -197
- package/dist/senses/bluebubbles-entry.js +0 -11
- package/dist/senses/bluebubbles.js +0 -736
- package/dist/senses/debug-activity.js +0 -127
- package/subagents/README.md +0 -60
- package/subagents/work-doer.md +0 -235
- package/subagents/work-merger.md +0 -618
- package/subagents/work-planner.md +0 -382
- /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,8 +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
|
+
exports.getCliContinuityIngressTexts = getCliContinuityIngressTexts;
|
|
39
|
+
exports.formatTimeAgo = formatTimeAgo;
|
|
40
|
+
exports.writeCliAsyncAssistantMessage = writeCliAsyncAssistantMessage;
|
|
41
|
+
exports.pauseActiveSpinner = pauseActiveSpinner;
|
|
42
|
+
exports.resumeActiveSpinner = resumeActiveSpinner;
|
|
43
|
+
exports.setActiveSpinner = setActiveSpinner;
|
|
38
44
|
exports.handleSigint = handleSigint;
|
|
39
45
|
exports.addHistory = addHistory;
|
|
40
46
|
exports.renderMarkdown = renderMarkdown;
|
|
@@ -50,11 +56,12 @@ const prompt_1 = require("../mind/prompt");
|
|
|
50
56
|
const phrases_1 = require("../mind/phrases");
|
|
51
57
|
const format_1 = require("../mind/format");
|
|
52
58
|
const config_1 = require("../heart/config");
|
|
59
|
+
const session_events_1 = require("../heart/session-events");
|
|
53
60
|
const context_1 = require("../mind/context");
|
|
54
61
|
const pending_1 = require("../mind/pending");
|
|
55
|
-
const prompt_refresh_1 = require("../mind/prompt-refresh");
|
|
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");
|
|
@@ -62,10 +69,21 @@ const tokens_1 = require("../mind/friends/tokens");
|
|
|
62
69
|
const cli_logging_1 = require("../nerves/cli-logging");
|
|
63
70
|
const runtime_1 = require("../nerves/runtime");
|
|
64
71
|
const trust_gate_1 = require("./trust-gate");
|
|
72
|
+
const pipeline_1 = require("./pipeline");
|
|
73
|
+
const channel_1 = require("../mind/friends/channel");
|
|
65
74
|
const session_lock_1 = require("./session-lock");
|
|
66
|
-
const update_hooks_1 = require("../heart/
|
|
75
|
+
const update_hooks_1 = require("../heart/versioning/update-hooks");
|
|
67
76
|
const bundle_meta_1 = require("../heart/daemon/hooks/bundle-meta");
|
|
77
|
+
const agent_config_v2_1 = require("../heart/daemon/hooks/agent-config-v2");
|
|
68
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; } });
|
|
69
87
|
/**
|
|
70
88
|
* Format pending messages as content-prefix strings for injection into
|
|
71
89
|
* the next user message. Self-messages (from === agentName) become
|
|
@@ -79,68 +97,50 @@ function formatPendingPrefix(messages, agentName) {
|
|
|
79
97
|
: `[message from ${msg.from}: ${msg.content}]`)
|
|
80
98
|
.join("\n");
|
|
81
99
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
this.i = (this.i + 1) % this.frames.length;
|
|
114
|
-
}
|
|
115
|
-
rotatePhrase() {
|
|
116
|
-
/* v8 ignore next -- race guard: timer callback fires after stop() @preserve */
|
|
117
|
-
if (this.stopped)
|
|
118
|
-
return;
|
|
119
|
-
const next = (0, phrases_1.pickPhrase)(this.phrases, this.lastPhrase);
|
|
120
|
-
this.lastPhrase = next;
|
|
121
|
-
this.msg = next;
|
|
122
|
-
}
|
|
123
|
-
stop(ok) {
|
|
124
|
-
this.stopped = true;
|
|
125
|
-
if (this.iv) {
|
|
126
|
-
clearInterval(this.iv);
|
|
127
|
-
this.iv = null;
|
|
128
|
-
}
|
|
129
|
-
if (this.piv) {
|
|
130
|
-
clearInterval(this.piv);
|
|
131
|
-
this.piv = null;
|
|
132
|
-
}
|
|
133
|
-
process.stderr.write("\r\x1b[K");
|
|
134
|
-
/* v8 ignore next -- ok parameter currently unused by callers @preserve */
|
|
135
|
-
if (ok)
|
|
136
|
-
process.stderr.write(`\x1b[32m\u2713\x1b[0m ${ok}\n`);
|
|
137
|
-
}
|
|
138
|
-
fail(msg) {
|
|
139
|
-
this.stop();
|
|
140
|
-
process.stderr.write(`\x1b[31m\u2717\x1b[0m ${msg}\n`);
|
|
100
|
+
function getCliContinuityIngressTexts(input) {
|
|
101
|
+
const trimmed = input.trim();
|
|
102
|
+
return trimmed ? [trimmed] : [];
|
|
103
|
+
}
|
|
104
|
+
/* v8 ignore start -- cosmetic time formatting @preserve */
|
|
105
|
+
function formatTimeAgo(date) {
|
|
106
|
+
const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
|
|
107
|
+
if (seconds < 60)
|
|
108
|
+
return "just now";
|
|
109
|
+
const minutes = Math.floor(seconds / 60);
|
|
110
|
+
if (minutes < 60)
|
|
111
|
+
return `${minutes}m ago`;
|
|
112
|
+
const hours = Math.floor(minutes / 60);
|
|
113
|
+
if (hours < 24)
|
|
114
|
+
return `${hours}h ago`;
|
|
115
|
+
const days = Math.floor(hours / 24);
|
|
116
|
+
return `${days}d ago`;
|
|
117
|
+
}
|
|
118
|
+
const CLI_PROMPT = "\x1b[36m) \x1b[0m";
|
|
119
|
+
function writeCliAsyncAssistantMessage(rl, message, stdout = process.stdout) {
|
|
120
|
+
const rlInt = rl;
|
|
121
|
+
const currentLine = rlInt.line ?? "";
|
|
122
|
+
const currentCursor = rlInt.cursor ?? currentLine.length;
|
|
123
|
+
stdout.write("\r\x1b[K");
|
|
124
|
+
stdout.write(`${renderMarkdown(message)}\n`);
|
|
125
|
+
stdout.write(CLI_PROMPT);
|
|
126
|
+
if (!currentLine)
|
|
127
|
+
return;
|
|
128
|
+
stdout.write(currentLine);
|
|
129
|
+
if (currentCursor < currentLine.length) {
|
|
130
|
+
readline.cursorTo(process.stdout, 2 + currentCursor);
|
|
141
131
|
}
|
|
142
132
|
}
|
|
143
|
-
|
|
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; } });
|
|
144
144
|
// Input controller: pauses readline during model/tool execution.
|
|
145
145
|
// Does NOT touch raw mode — readline with terminal:true manages raw mode
|
|
146
146
|
// internally. Touching it causes ^C to be echoed by the terminal driver.
|
|
@@ -308,59 +308,55 @@ function createCliCallbacks() {
|
|
|
308
308
|
meta: {},
|
|
309
309
|
});
|
|
310
310
|
let currentSpinner = null;
|
|
311
|
-
|
|
311
|
+
function setSpinner(s) { currentSpinner = s; setActiveSpinner(s); }
|
|
312
312
|
let hadToolRun = false;
|
|
313
313
|
let textDirty = false; // true when text/reasoning was written without a trailing newline
|
|
314
314
|
const streamer = new MarkdownStreamer();
|
|
315
|
+
const wrapper = new cli_layout_1.StreamingWordWrapper();
|
|
315
316
|
return {
|
|
316
317
|
onModelStart: () => {
|
|
317
318
|
currentSpinner?.stop();
|
|
318
|
-
|
|
319
|
-
hadReasoning = false;
|
|
319
|
+
setSpinner(null);
|
|
320
320
|
textDirty = false;
|
|
321
321
|
streamer.reset();
|
|
322
|
+
wrapper.reset();
|
|
322
323
|
const phrases = (0, phrases_1.getPhrases)();
|
|
323
324
|
const pool = hadToolRun ? phrases.followup : phrases.thinking;
|
|
324
325
|
const first = (0, phrases_1.pickPhrase)(pool);
|
|
325
|
-
|
|
326
|
+
setSpinner(new spinner_imperative_1.ImperativeSpinner(first, pool));
|
|
326
327
|
currentSpinner.start();
|
|
327
328
|
},
|
|
328
329
|
onModelStreamStart: () => {
|
|
329
330
|
// No-op: content callbacks (onTextChunk, onReasoningChunk) handle
|
|
330
331
|
// stopping the spinner. onModelStreamStart fires too early and
|
|
331
|
-
// doesn't fire at all for
|
|
332
|
+
// doesn't fire at all for settle tool streaming.
|
|
332
333
|
},
|
|
333
334
|
onClearText: () => {
|
|
334
335
|
streamer.reset();
|
|
336
|
+
wrapper.reset();
|
|
335
337
|
},
|
|
336
338
|
onTextChunk: (text) => {
|
|
337
|
-
// Stop spinner if still running —
|
|
339
|
+
// Stop spinner if still running — settle streaming and Anthropic
|
|
338
340
|
// tool-only responses bypass onModelStreamStart, so the spinner would
|
|
339
341
|
// otherwise keep running (and its \r writes overwrite response text).
|
|
340
342
|
if (currentSpinner) {
|
|
341
343
|
currentSpinner.stop();
|
|
342
|
-
|
|
343
|
-
}
|
|
344
|
-
if (hadReasoning) {
|
|
345
|
-
// Single newline to separate reasoning from reply — reasoning
|
|
346
|
-
// output often ends with its own trailing newline(s)
|
|
347
|
-
process.stdout.write("\n");
|
|
348
|
-
hadReasoning = false;
|
|
344
|
+
setSpinner(null);
|
|
349
345
|
}
|
|
350
346
|
const rendered = streamer.push(text);
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
if (currentSpinner) {
|
|
357
|
-
currentSpinner.stop();
|
|
358
|
-
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);
|
|
359
352
|
}
|
|
360
|
-
|
|
361
|
-
process.stdout.write(`\x1b[2m${text}\x1b[0m`);
|
|
353
|
+
/* v8 ignore stop */
|
|
362
354
|
textDirty = text.length > 0 && !text.endsWith("\n");
|
|
363
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
|
+
},
|
|
364
360
|
onToolStart: (_name, _args) => {
|
|
365
361
|
// Stop the model-start spinner: when the model returns only tool calls
|
|
366
362
|
// (no content/reasoning), onModelStreamStart never fires, so the old
|
|
@@ -374,31 +370,29 @@ function createCliCallbacks() {
|
|
|
374
370
|
}
|
|
375
371
|
const toolPhrases = (0, phrases_1.getPhrases)().tool;
|
|
376
372
|
const first = (0, phrases_1.pickPhrase)(toolPhrases);
|
|
377
|
-
|
|
373
|
+
setSpinner(new spinner_imperative_1.ImperativeSpinner(first, toolPhrases));
|
|
378
374
|
currentSpinner.start();
|
|
379
375
|
hadToolRun = true;
|
|
380
376
|
},
|
|
381
377
|
onToolEnd: (name, argSummary, success) => {
|
|
382
378
|
currentSpinner?.stop();
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
const color = success ? "\x1b[32m" : "\x1b[31m";
|
|
386
|
-
process.stderr.write(`${color}${msg}\x1b[0m\n`);
|
|
379
|
+
setSpinner(null);
|
|
380
|
+
(0, tool_display_1.writeToolEnd)(name, argSummary, success);
|
|
387
381
|
},
|
|
388
382
|
onError: (error, severity) => {
|
|
389
383
|
if (severity === "transient") {
|
|
390
384
|
currentSpinner?.fail(error.message);
|
|
391
|
-
|
|
385
|
+
setSpinner(null);
|
|
392
386
|
}
|
|
393
387
|
else {
|
|
394
388
|
currentSpinner?.stop();
|
|
395
|
-
|
|
389
|
+
setSpinner(null);
|
|
396
390
|
process.stderr.write(`\x1b[31m${(0, format_1.formatError)(error)}\x1b[0m\n`);
|
|
397
391
|
}
|
|
398
392
|
},
|
|
399
393
|
onKick: () => {
|
|
400
394
|
currentSpinner?.stop();
|
|
401
|
-
|
|
395
|
+
setSpinner(null);
|
|
402
396
|
if (textDirty) {
|
|
403
397
|
process.stdout.write("\n");
|
|
404
398
|
textDirty = false;
|
|
@@ -407,10 +401,18 @@ function createCliCallbacks() {
|
|
|
407
401
|
},
|
|
408
402
|
flushMarkdown: () => {
|
|
409
403
|
currentSpinner?.stop();
|
|
410
|
-
|
|
404
|
+
setSpinner(null);
|
|
405
|
+
/* v8 ignore start -- wrapper flush: tested via cli.test.ts flushMarkdown tests @preserve */
|
|
411
406
|
const remaining = streamer.flush();
|
|
412
|
-
if (remaining)
|
|
413
|
-
|
|
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 */
|
|
414
416
|
},
|
|
415
417
|
};
|
|
416
418
|
}
|
|
@@ -451,29 +453,243 @@ async function* createDebouncedLines(source, debounceMs) {
|
|
|
451
453
|
yield lines.join("\n");
|
|
452
454
|
}
|
|
453
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;
|
|
454
507
|
async function runCliSession(options) {
|
|
455
508
|
/* v8 ignore start -- integration: runCliSession is interactive, tested via E2E @preserve */
|
|
456
|
-
const pasteDebounceMs = options.pasteDebounceMs ?? 50;
|
|
457
509
|
const registry = (0, commands_1.createCommandRegistry)();
|
|
458
510
|
if (!options.disableCommands) {
|
|
459
511
|
(0, commands_1.registerDefaultCommands)(registry);
|
|
460
512
|
}
|
|
461
513
|
const messages = options.messages
|
|
462
|
-
?? [{ role: "system", content: await (0, prompt_1.buildSystem)("cli") }];
|
|
463
|
-
|
|
464
|
-
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;
|
|
465
517
|
let currentAbort = null;
|
|
466
|
-
const history = [];
|
|
467
518
|
let closed = false;
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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
|
+
}
|
|
475
647
|
}
|
|
476
|
-
|
|
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
|
+
};
|
|
477
693
|
// exitOnToolCall machinery: wrap execTool to detect target tool
|
|
478
694
|
let exitToolResult;
|
|
479
695
|
let exitToolFired = false;
|
|
@@ -484,8 +700,6 @@ async function runCliSession(options) {
|
|
|
484
700
|
if (name === options.exitOnToolCall) {
|
|
485
701
|
exitToolResult = result;
|
|
486
702
|
exitToolFired = true;
|
|
487
|
-
// Abort immediately so the model doesn't generate more output
|
|
488
|
-
// (e.g. reasoning about calling final_answer after complete_adoption)
|
|
489
703
|
currentAbort?.abort();
|
|
490
704
|
}
|
|
491
705
|
return result;
|
|
@@ -493,28 +707,6 @@ async function runCliSession(options) {
|
|
|
493
707
|
: resolvedExecTool;
|
|
494
708
|
// Resolve toolChoiceRequired: use explicit option if set, else fall back to toggle
|
|
495
709
|
const getEffectiveToolChoiceRequired = () => options.toolChoiceRequired !== undefined ? options.toolChoiceRequired : (0, commands_1.getToolChoiceRequired)();
|
|
496
|
-
// Ctrl-C at the input prompt: clear line or warn/exit
|
|
497
|
-
rl.on("SIGINT", () => {
|
|
498
|
-
const rlInt = rl;
|
|
499
|
-
const currentLine = rlInt.line || "";
|
|
500
|
-
const result = handleSigint(rl, currentLine);
|
|
501
|
-
if (result === "clear") {
|
|
502
|
-
rlInt.line = "";
|
|
503
|
-
rlInt.cursor = 0;
|
|
504
|
-
process.stdout.write("\r\x1b[K\x1b[36m> \x1b[0m");
|
|
505
|
-
}
|
|
506
|
-
else if (result === "warn") {
|
|
507
|
-
rlInt.line = "";
|
|
508
|
-
rlInt.cursor = 0;
|
|
509
|
-
process.stdout.write("\r\x1b[K");
|
|
510
|
-
process.stderr.write("press Ctrl-C again to exit\n");
|
|
511
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
512
|
-
}
|
|
513
|
-
else {
|
|
514
|
-
rl.close();
|
|
515
|
-
}
|
|
516
|
-
});
|
|
517
|
-
const debouncedLines = (source) => createDebouncedLines(source, pasteDebounceMs);
|
|
518
710
|
(0, runtime_1.emitNervesEvent)({
|
|
519
711
|
component: "senses",
|
|
520
712
|
event: "senses.cli_session_start",
|
|
@@ -527,7 +719,7 @@ async function runCliSession(options) {
|
|
|
527
719
|
if (options.autoFirstTurn && messages.length > 0 && messages[messages.length - 1]?.role === "user") {
|
|
528
720
|
currentAbort = new AbortController();
|
|
529
721
|
const traceId = (0, nerves_1.createTraceId)();
|
|
530
|
-
|
|
722
|
+
display.suppressInput();
|
|
531
723
|
let result;
|
|
532
724
|
try {
|
|
533
725
|
result = await (0, core_1.runAgent)(messages, cliCallbacks, options.skipSystemPromptRefresh ? undefined : "cli", currentAbort.signal, {
|
|
@@ -535,116 +727,145 @@ async function runCliSession(options) {
|
|
|
535
727
|
traceId,
|
|
536
728
|
tools: options.tools,
|
|
537
729
|
execTool: wrappedExecTool,
|
|
538
|
-
toolContext:
|
|
730
|
+
toolContext: effectiveToolContext,
|
|
539
731
|
});
|
|
540
732
|
}
|
|
541
733
|
catch (err) {
|
|
542
|
-
// AbortError (Ctrl-C) -- silently continue to prompt
|
|
543
|
-
// All other errors: show the user what happened
|
|
544
734
|
if (!(err instanceof DOMException && err.name === "AbortError")) {
|
|
545
|
-
|
|
735
|
+
display.error(err instanceof Error ? err.message : String(err));
|
|
546
736
|
}
|
|
547
737
|
}
|
|
548
738
|
cliCallbacks.flushMarkdown();
|
|
549
|
-
|
|
739
|
+
display.restoreInput();
|
|
550
740
|
currentAbort = null;
|
|
551
741
|
if (exitToolFired) {
|
|
552
742
|
exitReason = "tool_exit";
|
|
553
|
-
|
|
743
|
+
closed = true;
|
|
554
744
|
}
|
|
555
745
|
else {
|
|
556
746
|
const lastMsg = messages[messages.length - 1];
|
|
557
747
|
if (lastMsg?.role === "assistant" && !(typeof lastMsg.content === "string" ? lastMsg.content : "").trim()) {
|
|
558
|
-
|
|
748
|
+
display.warn("(empty response)");
|
|
559
749
|
}
|
|
560
|
-
process.stdout.write("\n\n");
|
|
561
750
|
if (options.onTurnEnd) {
|
|
562
751
|
await options.onTurnEnd(messages, result ?? { usage: undefined });
|
|
563
752
|
}
|
|
564
753
|
}
|
|
565
754
|
}
|
|
566
|
-
if (!exitToolFired) {
|
|
567
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
568
|
-
}
|
|
569
755
|
try {
|
|
570
|
-
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) {
|
|
571
775
|
if (closed)
|
|
572
776
|
break;
|
|
573
|
-
if (!input.trim())
|
|
574
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
777
|
+
if (!input.trim())
|
|
575
778
|
continue;
|
|
576
|
-
|
|
779
|
+
// Remove from TUI queue display as the agent picks up this message
|
|
780
|
+
tuiStore?.dequeueInput(input);
|
|
577
781
|
// Optional input gate (e.g. trust gate in main)
|
|
578
782
|
if (options.onInput) {
|
|
579
783
|
const gate = options.onInput(input);
|
|
580
784
|
if (!gate.allowed) {
|
|
581
785
|
if (gate.reply) {
|
|
582
|
-
|
|
786
|
+
display.text(gate.reply);
|
|
583
787
|
}
|
|
584
788
|
if (closed)
|
|
585
789
|
break;
|
|
586
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
587
790
|
continue;
|
|
588
791
|
}
|
|
589
792
|
}
|
|
590
|
-
// Check for slash commands
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
if (dispatchResult.
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
console.log("session cleared");
|
|
604
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
605
|
-
continue;
|
|
606
|
-
}
|
|
607
|
-
else if (dispatchResult.result.action === "response") {
|
|
608
|
-
// eslint-disable-next-line no-console -- terminal UX: command dispatch result
|
|
609
|
-
console.log(dispatchResult.result.message || "");
|
|
610
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
611
|
-
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
|
+
}
|
|
612
806
|
}
|
|
613
807
|
}
|
|
614
808
|
}
|
|
615
|
-
//
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
let echoRows = 0;
|
|
619
|
-
for (const line of inputLines) {
|
|
620
|
-
echoRows += Math.ceil((2 + line.length) / cols);
|
|
621
|
-
}
|
|
622
|
-
process.stdout.write(`\x1b[${echoRows}A\x1b[K` + `\x1b[1m> ${inputLines[0]}${inputLines.length > 1 ? ` (+${inputLines.length - 1} lines)` : ""}\x1b[0m\n\n`);
|
|
623
|
-
const prefix = options.getContentPrefix?.();
|
|
624
|
-
messages.push({ role: "user", content: prefix ? `${prefix}\n\n${input}` : input });
|
|
625
|
-
addHistory(history, input);
|
|
809
|
+
// Track user message in TUI for display
|
|
810
|
+
if (tuiStore)
|
|
811
|
+
tuiStore.addUserMessage(input);
|
|
626
812
|
currentAbort = new AbortController();
|
|
627
|
-
|
|
628
|
-
|
|
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
|
+
}
|
|
629
823
|
let result;
|
|
630
824
|
try {
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
825
|
+
if (options.runTurn) {
|
|
826
|
+
// Pipeline-based turn: the runTurn callback handles user message assembly,
|
|
827
|
+
// pending drain, trust gate, runAgent, postTurn, and token accumulation.
|
|
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
|
+
}
|
|
838
|
+
}
|
|
839
|
+
else {
|
|
840
|
+
// Legacy path: inline runAgent (used by serpent guide and tests)
|
|
841
|
+
const prefix = options.getContentPrefix?.();
|
|
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);
|
|
848
|
+
const traceId = (0, nerves_1.createTraceId)();
|
|
849
|
+
result = await (0, core_1.runAgent)(messages, cliCallbacks, options.skipSystemPromptRefresh ? undefined : "cli", currentAbort.signal, {
|
|
850
|
+
toolChoiceRequired: getEffectiveToolChoiceRequired(),
|
|
851
|
+
traceId,
|
|
852
|
+
tools: options.tools,
|
|
853
|
+
execTool: wrappedExecTool,
|
|
854
|
+
toolContext: effectiveToolContext,
|
|
855
|
+
});
|
|
856
|
+
}
|
|
638
857
|
}
|
|
639
858
|
catch (err) {
|
|
640
|
-
// AbortError (Ctrl-C) -- silently return to prompt
|
|
641
|
-
// All other errors: show the user what happened
|
|
642
859
|
if (!(err instanceof DOMException && err.name === "AbortError")) {
|
|
643
|
-
|
|
860
|
+
display.error(err instanceof Error ? err.message : String(err));
|
|
644
861
|
}
|
|
645
862
|
}
|
|
646
863
|
cliCallbacks.flushMarkdown();
|
|
647
|
-
|
|
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();
|
|
648
869
|
currentAbort = null;
|
|
649
870
|
// Check if exit tool was fired during this turn
|
|
650
871
|
if (exitToolFired) {
|
|
@@ -654,20 +875,35 @@ async function runCliSession(options) {
|
|
|
654
875
|
// Safety net: never silently swallow an empty response
|
|
655
876
|
const lastMsg = messages[messages.length - 1];
|
|
656
877
|
if (lastMsg?.role === "assistant" && !(typeof lastMsg.content === "string" ? lastMsg.content : "").trim()) {
|
|
657
|
-
|
|
878
|
+
display.warn("(empty response)");
|
|
658
879
|
}
|
|
659
|
-
process.stdout.write("\n\n");
|
|
660
880
|
// Post-turn hook (session persistence, pending drain, prompt refresh, etc.)
|
|
661
881
|
if (options.onTurnEnd) {
|
|
662
882
|
await options.onTurnEnd(messages, result ?? { usage: undefined });
|
|
663
883
|
}
|
|
664
884
|
if (closed)
|
|
665
885
|
break;
|
|
666
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
667
886
|
}
|
|
668
887
|
}
|
|
669
888
|
finally {
|
|
670
|
-
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
|
+
}
|
|
671
907
|
if (options.banner !== false) {
|
|
672
908
|
// eslint-disable-next-line no-console -- terminal UX: goodbye
|
|
673
909
|
console.log("bye");
|
|
@@ -680,17 +916,32 @@ async function main(agentName, options) {
|
|
|
680
916
|
if (agentName)
|
|
681
917
|
(0, identity_1.setAgentName)(agentName);
|
|
682
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);
|
|
683
934
|
// Fallback: apply pending updates for daemon-less direct CLI usage
|
|
684
935
|
(0, update_hooks_1.registerUpdateHook)(bundle_meta_1.bundleMetaHook);
|
|
936
|
+
(0, update_hooks_1.registerUpdateHook)(agent_config_v2_1.agentConfigV2Hook);
|
|
685
937
|
await (0, update_hooks_1.applyPendingUpdates)((0, identity_1.getAgentBundlesRoot)(), (0, bundle_manifest_1.getPackageVersion)());
|
|
686
938
|
// Fail fast if provider is misconfigured (triggers human-readable error + exit)
|
|
687
|
-
(0, core_1.getProvider)();
|
|
939
|
+
(0, core_1.getProvider)("human");
|
|
688
940
|
// Resolve context kernel (identity + channel) for CLI
|
|
689
941
|
const friendsPath = path.join((0, identity_1.getAgentRoot)(), "friends");
|
|
690
942
|
const friendStore = new store_file_1.FileFriendStore(friendsPath);
|
|
691
943
|
const username = os.userInfo().username;
|
|
692
|
-
const
|
|
693
|
-
const localExternalId = `${username}@${hostname}`;
|
|
944
|
+
const localExternalId = username;
|
|
694
945
|
const resolver = new resolver_1.FriendResolver(friendStore, {
|
|
695
946
|
provider: "local",
|
|
696
947
|
externalId: localExternalId,
|
|
@@ -698,13 +949,6 @@ async function main(agentName, options) {
|
|
|
698
949
|
channel: "cli",
|
|
699
950
|
});
|
|
700
951
|
const resolvedContext = await resolver.resolve();
|
|
701
|
-
const cliToolContext = {
|
|
702
|
-
/* v8 ignore next -- CLI has no OAuth sign-in; this no-op satisfies the interface @preserve */
|
|
703
|
-
signin: async () => undefined,
|
|
704
|
-
context: resolvedContext,
|
|
705
|
-
friendStore,
|
|
706
|
-
summarize: (0, core_1.createSummarize)(),
|
|
707
|
-
};
|
|
708
952
|
const friendId = resolvedContext.friend.id;
|
|
709
953
|
const agentConfig = (0, identity_1.loadAgentConfig)();
|
|
710
954
|
(0, cli_logging_1.configureCliRuntimeLogger)(friendId, {
|
|
@@ -727,62 +971,138 @@ async function main(agentName, options) {
|
|
|
727
971
|
}
|
|
728
972
|
// Load existing session or start fresh
|
|
729
973
|
const existing = (0, context_1.loadSession)(sessPath);
|
|
974
|
+
let sessionState = existing?.state;
|
|
975
|
+
let sessionEvents = existing?.events ?? [];
|
|
976
|
+
const mcpManager = await (0, mcp_manager_1.getSharedMcpManager)() ?? undefined;
|
|
730
977
|
const sessionMessages = existing?.messages && existing.messages.length > 0
|
|
731
978
|
? existing.messages
|
|
732
|
-
: [{ role: "system", content: await (0, prompt_1.buildSystem)("cli",
|
|
733
|
-
//
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
const
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
return pending.length;
|
|
742
|
-
};
|
|
743
|
-
// Startup drain: collect offline messages as prefix for next user message
|
|
744
|
-
const startupCount = drainToPrefix();
|
|
745
|
-
if (startupCount > 0) {
|
|
746
|
-
(0, context_1.saveSession)(sessPath, sessionMessages);
|
|
747
|
-
}
|
|
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);
|
|
982
|
+
// Per-turn pipeline input: CLI capabilities and pending dir
|
|
983
|
+
const cliCapabilities = (0, channel_1.getChannelCapabilities)("cli");
|
|
984
|
+
const currentAgentName = (0, identity_1.getAgentName)();
|
|
985
|
+
const pendingDir = (0, pending_1.getPendingDir)(currentAgentName, friendId, "cli", "session");
|
|
986
|
+
const summarize = (0, core_1.createSummarize)("human");
|
|
987
|
+
const cliFailoverState = { pending: null };
|
|
748
988
|
try {
|
|
749
989
|
await runCliSession({
|
|
750
|
-
agentName:
|
|
990
|
+
agentName: currentAgentName,
|
|
751
991
|
pasteDebounceMs,
|
|
752
992
|
messages: sessionMessages,
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
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) => {
|
|
1002
|
+
// Run the full per-turn pipeline: resolve -> gate -> session -> drain -> runAgent -> postTurn -> tokens
|
|
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 */
|
|
1026
|
+
const result = await (0, pipeline_1.handleInboundTurn)({
|
|
1027
|
+
channel: "cli",
|
|
1028
|
+
sessionKey: "session",
|
|
1029
|
+
capabilities: cliCapabilities,
|
|
1030
|
+
messages: [{ role: "user", content: userContent ?? userInput }],
|
|
1031
|
+
continuityIngressTexts: getCliContinuityIngressTexts(userInput),
|
|
1032
|
+
callbacks: failoverAwareCallbacks,
|
|
1033
|
+
friendResolver: { resolve: () => Promise.resolve(resolvedContext) },
|
|
1034
|
+
sessionLoader: {
|
|
1035
|
+
loadOrCreate: () => Promise.resolve({
|
|
1036
|
+
messages,
|
|
1037
|
+
sessionPath: sessPath,
|
|
1038
|
+
state: sessionState,
|
|
1039
|
+
events: sessionEvents,
|
|
1040
|
+
}),
|
|
1041
|
+
},
|
|
1042
|
+
pendingDir,
|
|
1043
|
+
friendStore,
|
|
757
1044
|
provider: "local",
|
|
758
1045
|
externalId: localExternalId,
|
|
759
|
-
|
|
1046
|
+
enforceTrustGate: trust_gate_1.enforceTrustGate,
|
|
1047
|
+
drainPending: pending_1.drainPending,
|
|
1048
|
+
drainDeferredReturns: (deferredFriendId) => (0, pending_1.drainDeferredReturns)(currentAgentName, deferredFriendId),
|
|
1049
|
+
runAgent: (msgs, cb, channel, sig, opts) => (0, core_1.runAgent)(msgs, cb, channel, sig, {
|
|
1050
|
+
...opts,
|
|
1051
|
+
toolContext: {
|
|
1052
|
+
/* v8 ignore next -- default no-op signin; pipeline provides the real one @preserve */
|
|
1053
|
+
signin: async () => undefined,
|
|
1054
|
+
...opts?.toolContext,
|
|
1055
|
+
summarize,
|
|
1056
|
+
},
|
|
1057
|
+
}),
|
|
1058
|
+
postTurn: (turnMessages, sessionPathArg, usage, hooks, state) => {
|
|
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);
|
|
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
|
+
});
|
|
1067
|
+
},
|
|
1068
|
+
accumulateFriendTokens: tokens_1.accumulateFriendTokens,
|
|
1069
|
+
signal,
|
|
1070
|
+
runAgentOptions: {
|
|
1071
|
+
toolChoiceRequired: (0, commands_1.getToolChoiceRequired)(),
|
|
1072
|
+
traceId: (0, nerves_1.createTraceId)(),
|
|
1073
|
+
mcpManager,
|
|
1074
|
+
toolContext,
|
|
1075
|
+
},
|
|
1076
|
+
failoverState,
|
|
760
1077
|
});
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
};
|
|
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`);
|
|
766
1082
|
}
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
},
|
|
780
|
-
onNewSession: () => {
|
|
781
|
-
(0, context_1.deleteSession)(sessPath);
|
|
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 */
|
|
1088
|
+
// Handle gate rejection: display auto-reply if present
|
|
1089
|
+
if (!result.gateResult.allowed) {
|
|
1090
|
+
if ("autoReply" in result.gateResult && result.gateResult.autoReply) {
|
|
1091
|
+
process.stdout.write(`${result.gateResult.autoReply}\n`);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
return { usage: result.usage, turnOutcome: result.turnOutcome, commandAction: result.commandAction };
|
|
782
1095
|
},
|
|
783
1096
|
});
|
|
784
1097
|
}
|
|
785
1098
|
finally {
|
|
786
1099
|
sessionLock?.release();
|
|
787
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);
|
|
788
1107
|
}
|
|
1108
|
+
// CI trigger
|