@ouro.bot/cli 0.1.0-alpha.42 → 0.1.0-alpha.420
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 +118 -15
- 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 +2627 -9
- 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 +424 -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 +110 -128
- package/dist/heart/core.js +801 -217
- package/dist/heart/cross-chat-delivery.js +131 -0
- package/dist/heart/daemon/agent-config-check.js +419 -0
- package/dist/heart/daemon/agent-discovery.js +79 -3
- package/dist/heart/daemon/agent-service.js +360 -0
- package/dist/heart/daemon/agentic-repair.js +214 -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 +605 -0
- package/dist/heart/daemon/cli-exec.js +4140 -0
- package/dist/heart/daemon/cli-help.js +413 -0
- package/dist/heart/daemon/cli-parse.js +1151 -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/daemon-cli.js +28 -1582
- 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 +171 -12
- package/dist/heart/daemon/daemon-tombstone.js +236 -0
- package/dist/heart/daemon/daemon.js +684 -58
- package/dist/heart/daemon/doctor-types.js +8 -0
- package/dist/heart/daemon/doctor.js +427 -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 +307 -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 +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/pulse.js +475 -0
- package/dist/heart/daemon/readiness-repair.js +250 -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 +145 -32
- 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 +259 -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 +218 -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 +53 -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 +161 -65
- 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/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 +389 -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 +62 -38
- package/dist/heart/runtime-credentials.js +260 -0
- package/dist/heart/sense-truth.js +3 -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 +3 -1
- package/dist/heart/{daemon → versioning}/update-hooks.js +63 -59
- package/dist/mind/bundle-manifest.js +7 -1
- package/dist/mind/context.js +134 -87
- 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/first-impressions.js +14 -1
- package/dist/mind/friends/channel.js +21 -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 +1 -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 +66 -7
- package/dist/mind/prompt-refresh.js +3 -2
- package/dist/mind/prompt.js +948 -168
- 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 +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 +702 -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 +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 +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 +26 -1
- 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 +42 -687
- 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 +361 -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 +9 -39
- 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 -113
- 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 +421 -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 +73 -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} +33 -12
- package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +45 -3
- 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 +526 -211
- 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 +596 -94
- package/dist/senses/pipeline.js +539 -61
- 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 +569 -237
- package/dist/senses/trust-gate.js +5 -5
- package/package.json +29 -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 -134
- package/dist/mind/associative-recall.js +0 -209
- package/dist/senses/bluebubbles-entry.js +0 -11
- package/dist/senses/bluebubbles.js +0 -832
- 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,10 +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
62
|
const commands_1 = require("./commands");
|
|
56
63
|
const identity_1 = require("../heart/identity");
|
|
64
|
+
const mcp_manager_1 = require("../repertoire/mcp-manager");
|
|
57
65
|
const nerves_1 = require("../nerves");
|
|
58
66
|
const store_file_1 = require("../mind/friends/store-file");
|
|
59
67
|
const resolver_1 = require("../mind/friends/resolver");
|
|
@@ -64,9 +72,18 @@ const trust_gate_1 = require("./trust-gate");
|
|
|
64
72
|
const pipeline_1 = require("./pipeline");
|
|
65
73
|
const channel_1 = require("../mind/friends/channel");
|
|
66
74
|
const session_lock_1 = require("./session-lock");
|
|
67
|
-
const update_hooks_1 = require("../heart/
|
|
75
|
+
const update_hooks_1 = require("../heart/versioning/update-hooks");
|
|
68
76
|
const bundle_meta_1 = require("../heart/daemon/hooks/bundle-meta");
|
|
77
|
+
const agent_config_v2_1 = require("../heart/daemon/hooks/agent-config-v2");
|
|
69
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; } });
|
|
70
87
|
/**
|
|
71
88
|
* Format pending messages as content-prefix strings for injection into
|
|
72
89
|
* the next user message. Self-messages (from === agentName) become
|
|
@@ -80,68 +97,50 @@ function formatPendingPrefix(messages, agentName) {
|
|
|
80
97
|
: `[message from ${msg.from}: ${msg.content}]`)
|
|
81
98
|
.join("\n");
|
|
82
99
|
}
|
|
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
|
-
|
|
114
|
-
this.i = (this.i + 1) % this.frames.length;
|
|
115
|
-
}
|
|
116
|
-
rotatePhrase() {
|
|
117
|
-
/* v8 ignore next -- race guard: timer callback fires after stop() @preserve */
|
|
118
|
-
if (this.stopped)
|
|
119
|
-
return;
|
|
120
|
-
const next = (0, phrases_1.pickPhrase)(this.phrases, this.lastPhrase);
|
|
121
|
-
this.lastPhrase = next;
|
|
122
|
-
this.msg = next;
|
|
123
|
-
}
|
|
124
|
-
stop(ok) {
|
|
125
|
-
this.stopped = true;
|
|
126
|
-
if (this.iv) {
|
|
127
|
-
clearInterval(this.iv);
|
|
128
|
-
this.iv = null;
|
|
129
|
-
}
|
|
130
|
-
if (this.piv) {
|
|
131
|
-
clearInterval(this.piv);
|
|
132
|
-
this.piv = null;
|
|
133
|
-
}
|
|
134
|
-
process.stderr.write("\r\x1b[K");
|
|
135
|
-
/* v8 ignore next -- ok parameter currently unused by callers @preserve */
|
|
136
|
-
if (ok)
|
|
137
|
-
process.stderr.write(`\x1b[32m\u2713\x1b[0m ${ok}\n`);
|
|
138
|
-
}
|
|
139
|
-
fail(msg) {
|
|
140
|
-
this.stop();
|
|
141
|
-
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);
|
|
142
131
|
}
|
|
143
132
|
}
|
|
144
|
-
|
|
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; } });
|
|
145
144
|
// Input controller: pauses readline during model/tool execution.
|
|
146
145
|
// Does NOT touch raw mode — readline with terminal:true manages raw mode
|
|
147
146
|
// internally. Touching it causes ^C to be echoed by the terminal driver.
|
|
@@ -309,59 +308,55 @@ function createCliCallbacks() {
|
|
|
309
308
|
meta: {},
|
|
310
309
|
});
|
|
311
310
|
let currentSpinner = null;
|
|
312
|
-
|
|
311
|
+
function setSpinner(s) { currentSpinner = s; setActiveSpinner(s); }
|
|
313
312
|
let hadToolRun = false;
|
|
314
313
|
let textDirty = false; // true when text/reasoning was written without a trailing newline
|
|
315
314
|
const streamer = new MarkdownStreamer();
|
|
315
|
+
const wrapper = new cli_layout_1.StreamingWordWrapper();
|
|
316
316
|
return {
|
|
317
317
|
onModelStart: () => {
|
|
318
318
|
currentSpinner?.stop();
|
|
319
|
-
|
|
320
|
-
hadReasoning = false;
|
|
319
|
+
setSpinner(null);
|
|
321
320
|
textDirty = false;
|
|
322
321
|
streamer.reset();
|
|
322
|
+
wrapper.reset();
|
|
323
323
|
const phrases = (0, phrases_1.getPhrases)();
|
|
324
324
|
const pool = hadToolRun ? phrases.followup : phrases.thinking;
|
|
325
325
|
const first = (0, phrases_1.pickPhrase)(pool);
|
|
326
|
-
|
|
326
|
+
setSpinner(new spinner_imperative_1.ImperativeSpinner(first, pool));
|
|
327
327
|
currentSpinner.start();
|
|
328
328
|
},
|
|
329
329
|
onModelStreamStart: () => {
|
|
330
330
|
// No-op: content callbacks (onTextChunk, onReasoningChunk) handle
|
|
331
331
|
// stopping the spinner. onModelStreamStart fires too early and
|
|
332
|
-
// doesn't fire at all for
|
|
332
|
+
// doesn't fire at all for settle tool streaming.
|
|
333
333
|
},
|
|
334
334
|
onClearText: () => {
|
|
335
335
|
streamer.reset();
|
|
336
|
+
wrapper.reset();
|
|
336
337
|
},
|
|
337
338
|
onTextChunk: (text) => {
|
|
338
|
-
// Stop spinner if still running —
|
|
339
|
+
// Stop spinner if still running — settle streaming and Anthropic
|
|
339
340
|
// tool-only responses bypass onModelStreamStart, so the spinner would
|
|
340
341
|
// otherwise keep running (and its \r writes overwrite response text).
|
|
341
342
|
if (currentSpinner) {
|
|
342
343
|
currentSpinner.stop();
|
|
343
|
-
|
|
344
|
-
}
|
|
345
|
-
if (hadReasoning) {
|
|
346
|
-
// Single newline to separate reasoning from reply — reasoning
|
|
347
|
-
// output often ends with its own trailing newline(s)
|
|
348
|
-
process.stdout.write("\n");
|
|
349
|
-
hadReasoning = false;
|
|
344
|
+
setSpinner(null);
|
|
350
345
|
}
|
|
351
346
|
const rendered = streamer.push(text);
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
if (currentSpinner) {
|
|
358
|
-
currentSpinner.stop();
|
|
359
|
-
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);
|
|
360
352
|
}
|
|
361
|
-
|
|
362
|
-
process.stdout.write(`\x1b[2m${text}\x1b[0m`);
|
|
353
|
+
/* v8 ignore stop */
|
|
363
354
|
textDirty = text.length > 0 && !text.endsWith("\n");
|
|
364
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
|
+
},
|
|
365
360
|
onToolStart: (_name, _args) => {
|
|
366
361
|
// Stop the model-start spinner: when the model returns only tool calls
|
|
367
362
|
// (no content/reasoning), onModelStreamStart never fires, so the old
|
|
@@ -375,31 +370,29 @@ function createCliCallbacks() {
|
|
|
375
370
|
}
|
|
376
371
|
const toolPhrases = (0, phrases_1.getPhrases)().tool;
|
|
377
372
|
const first = (0, phrases_1.pickPhrase)(toolPhrases);
|
|
378
|
-
|
|
373
|
+
setSpinner(new spinner_imperative_1.ImperativeSpinner(first, toolPhrases));
|
|
379
374
|
currentSpinner.start();
|
|
380
375
|
hadToolRun = true;
|
|
381
376
|
},
|
|
382
377
|
onToolEnd: (name, argSummary, success) => {
|
|
383
378
|
currentSpinner?.stop();
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
const color = success ? "\x1b[32m" : "\x1b[31m";
|
|
387
|
-
process.stderr.write(`${color}${msg}\x1b[0m\n`);
|
|
379
|
+
setSpinner(null);
|
|
380
|
+
(0, tool_display_1.writeToolEnd)(name, argSummary, success);
|
|
388
381
|
},
|
|
389
382
|
onError: (error, severity) => {
|
|
390
383
|
if (severity === "transient") {
|
|
391
384
|
currentSpinner?.fail(error.message);
|
|
392
|
-
|
|
385
|
+
setSpinner(null);
|
|
393
386
|
}
|
|
394
387
|
else {
|
|
395
388
|
currentSpinner?.stop();
|
|
396
|
-
|
|
389
|
+
setSpinner(null);
|
|
397
390
|
process.stderr.write(`\x1b[31m${(0, format_1.formatError)(error)}\x1b[0m\n`);
|
|
398
391
|
}
|
|
399
392
|
},
|
|
400
393
|
onKick: () => {
|
|
401
394
|
currentSpinner?.stop();
|
|
402
|
-
|
|
395
|
+
setSpinner(null);
|
|
403
396
|
if (textDirty) {
|
|
404
397
|
process.stdout.write("\n");
|
|
405
398
|
textDirty = false;
|
|
@@ -408,10 +401,18 @@ function createCliCallbacks() {
|
|
|
408
401
|
},
|
|
409
402
|
flushMarkdown: () => {
|
|
410
403
|
currentSpinner?.stop();
|
|
411
|
-
|
|
404
|
+
setSpinner(null);
|
|
405
|
+
/* v8 ignore start -- wrapper flush: tested via cli.test.ts flushMarkdown tests @preserve */
|
|
412
406
|
const remaining = streamer.flush();
|
|
413
|
-
if (remaining)
|
|
414
|
-
|
|
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 */
|
|
415
416
|
},
|
|
416
417
|
};
|
|
417
418
|
}
|
|
@@ -452,29 +453,243 @@ async function* createDebouncedLines(source, debounceMs) {
|
|
|
452
453
|
yield lines.join("\n");
|
|
453
454
|
}
|
|
454
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;
|
|
455
507
|
async function runCliSession(options) {
|
|
456
508
|
/* v8 ignore start -- integration: runCliSession is interactive, tested via E2E @preserve */
|
|
457
|
-
const pasteDebounceMs = options.pasteDebounceMs ?? 50;
|
|
458
509
|
const registry = (0, commands_1.createCommandRegistry)();
|
|
459
510
|
if (!options.disableCommands) {
|
|
460
511
|
(0, commands_1.registerDefaultCommands)(registry);
|
|
461
512
|
}
|
|
462
513
|
const messages = options.messages
|
|
463
|
-
?? [{ role: "system", content: await (0, prompt_1.buildSystem)("cli") }];
|
|
464
|
-
|
|
465
|
-
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;
|
|
466
517
|
let currentAbort = null;
|
|
467
|
-
const history = [];
|
|
468
518
|
let closed = false;
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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
|
+
}
|
|
476
647
|
}
|
|
477
|
-
|
|
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
|
+
};
|
|
478
693
|
// exitOnToolCall machinery: wrap execTool to detect target tool
|
|
479
694
|
let exitToolResult;
|
|
480
695
|
let exitToolFired = false;
|
|
@@ -485,8 +700,6 @@ async function runCliSession(options) {
|
|
|
485
700
|
if (name === options.exitOnToolCall) {
|
|
486
701
|
exitToolResult = result;
|
|
487
702
|
exitToolFired = true;
|
|
488
|
-
// Abort immediately so the model doesn't generate more output
|
|
489
|
-
// (e.g. reasoning about calling final_answer after complete_adoption)
|
|
490
703
|
currentAbort?.abort();
|
|
491
704
|
}
|
|
492
705
|
return result;
|
|
@@ -494,28 +707,6 @@ async function runCliSession(options) {
|
|
|
494
707
|
: resolvedExecTool;
|
|
495
708
|
// Resolve toolChoiceRequired: use explicit option if set, else fall back to toggle
|
|
496
709
|
const getEffectiveToolChoiceRequired = () => options.toolChoiceRequired !== undefined ? options.toolChoiceRequired : (0, commands_1.getToolChoiceRequired)();
|
|
497
|
-
// Ctrl-C at the input prompt: clear line or warn/exit
|
|
498
|
-
rl.on("SIGINT", () => {
|
|
499
|
-
const rlInt = rl;
|
|
500
|
-
const currentLine = rlInt.line || "";
|
|
501
|
-
const result = handleSigint(rl, currentLine);
|
|
502
|
-
if (result === "clear") {
|
|
503
|
-
rlInt.line = "";
|
|
504
|
-
rlInt.cursor = 0;
|
|
505
|
-
process.stdout.write("\r\x1b[K\x1b[36m> \x1b[0m");
|
|
506
|
-
}
|
|
507
|
-
else if (result === "warn") {
|
|
508
|
-
rlInt.line = "";
|
|
509
|
-
rlInt.cursor = 0;
|
|
510
|
-
process.stdout.write("\r\x1b[K");
|
|
511
|
-
process.stderr.write("press Ctrl-C again to exit\n");
|
|
512
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
513
|
-
}
|
|
514
|
-
else {
|
|
515
|
-
rl.close();
|
|
516
|
-
}
|
|
517
|
-
});
|
|
518
|
-
const debouncedLines = (source) => createDebouncedLines(source, pasteDebounceMs);
|
|
519
710
|
(0, runtime_1.emitNervesEvent)({
|
|
520
711
|
component: "senses",
|
|
521
712
|
event: "senses.cli_session_start",
|
|
@@ -528,7 +719,7 @@ async function runCliSession(options) {
|
|
|
528
719
|
if (options.autoFirstTurn && messages.length > 0 && messages[messages.length - 1]?.role === "user") {
|
|
529
720
|
currentAbort = new AbortController();
|
|
530
721
|
const traceId = (0, nerves_1.createTraceId)();
|
|
531
|
-
|
|
722
|
+
display.suppressInput();
|
|
532
723
|
let result;
|
|
533
724
|
try {
|
|
534
725
|
result = await (0, core_1.runAgent)(messages, cliCallbacks, options.skipSystemPromptRefresh ? undefined : "cli", currentAbort.signal, {
|
|
@@ -536,124 +727,145 @@ async function runCliSession(options) {
|
|
|
536
727
|
traceId,
|
|
537
728
|
tools: options.tools,
|
|
538
729
|
execTool: wrappedExecTool,
|
|
539
|
-
toolContext:
|
|
730
|
+
toolContext: effectiveToolContext,
|
|
540
731
|
});
|
|
541
732
|
}
|
|
542
733
|
catch (err) {
|
|
543
|
-
// AbortError (Ctrl-C) -- silently continue to prompt
|
|
544
|
-
// All other errors: show the user what happened
|
|
545
734
|
if (!(err instanceof DOMException && err.name === "AbortError")) {
|
|
546
|
-
|
|
735
|
+
display.error(err instanceof Error ? err.message : String(err));
|
|
547
736
|
}
|
|
548
737
|
}
|
|
549
738
|
cliCallbacks.flushMarkdown();
|
|
550
|
-
|
|
739
|
+
display.restoreInput();
|
|
551
740
|
currentAbort = null;
|
|
552
741
|
if (exitToolFired) {
|
|
553
742
|
exitReason = "tool_exit";
|
|
554
|
-
|
|
743
|
+
closed = true;
|
|
555
744
|
}
|
|
556
745
|
else {
|
|
557
746
|
const lastMsg = messages[messages.length - 1];
|
|
558
747
|
if (lastMsg?.role === "assistant" && !(typeof lastMsg.content === "string" ? lastMsg.content : "").trim()) {
|
|
559
|
-
|
|
748
|
+
display.warn("(empty response)");
|
|
560
749
|
}
|
|
561
|
-
process.stdout.write("\n\n");
|
|
562
750
|
if (options.onTurnEnd) {
|
|
563
751
|
await options.onTurnEnd(messages, result ?? { usage: undefined });
|
|
564
752
|
}
|
|
565
753
|
}
|
|
566
754
|
}
|
|
567
|
-
if (!exitToolFired) {
|
|
568
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
569
|
-
}
|
|
570
755
|
try {
|
|
571
|
-
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) {
|
|
572
775
|
if (closed)
|
|
573
776
|
break;
|
|
574
|
-
if (!input.trim())
|
|
575
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
777
|
+
if (!input.trim())
|
|
576
778
|
continue;
|
|
577
|
-
|
|
779
|
+
// Remove from TUI queue display as the agent picks up this message
|
|
780
|
+
tuiStore?.dequeueInput(input);
|
|
578
781
|
// Optional input gate (e.g. trust gate in main)
|
|
579
782
|
if (options.onInput) {
|
|
580
783
|
const gate = options.onInput(input);
|
|
581
784
|
if (!gate.allowed) {
|
|
582
785
|
if (gate.reply) {
|
|
583
|
-
|
|
786
|
+
display.text(gate.reply);
|
|
584
787
|
}
|
|
585
788
|
if (closed)
|
|
586
789
|
break;
|
|
587
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
588
790
|
continue;
|
|
589
791
|
}
|
|
590
792
|
}
|
|
591
|
-
// Check for slash commands
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
if (dispatchResult.
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
console.log("session cleared");
|
|
605
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
606
|
-
continue;
|
|
607
|
-
}
|
|
608
|
-
else if (dispatchResult.result.action === "response") {
|
|
609
|
-
// eslint-disable-next-line no-console -- terminal UX: command dispatch result
|
|
610
|
-
console.log(dispatchResult.result.message || "");
|
|
611
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
612
|
-
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
|
+
}
|
|
613
806
|
}
|
|
614
807
|
}
|
|
615
808
|
}
|
|
616
|
-
//
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
let echoRows = 0;
|
|
620
|
-
for (const line of inputLines) {
|
|
621
|
-
echoRows += Math.ceil((2 + line.length) / cols);
|
|
622
|
-
}
|
|
623
|
-
process.stdout.write(`\x1b[${echoRows}A\x1b[K` + `\x1b[1m> ${inputLines[0]}${inputLines.length > 1 ? ` (+${inputLines.length - 1} lines)` : ""}\x1b[0m\n\n`);
|
|
624
|
-
addHistory(history, input);
|
|
809
|
+
// Track user message in TUI for display
|
|
810
|
+
if (tuiStore)
|
|
811
|
+
tuiStore.addUserMessage(input);
|
|
625
812
|
currentAbort = new AbortController();
|
|
626
|
-
|
|
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
|
+
}
|
|
627
823
|
let result;
|
|
628
824
|
try {
|
|
629
825
|
if (options.runTurn) {
|
|
630
826
|
// Pipeline-based turn: the runTurn callback handles user message assembly,
|
|
631
827
|
// pending drain, trust gate, runAgent, postTurn, and token accumulation.
|
|
632
|
-
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
|
+
}
|
|
633
838
|
}
|
|
634
839
|
else {
|
|
635
|
-
// Legacy path: inline runAgent (used by
|
|
840
|
+
// Legacy path: inline runAgent (used by serpent guide and tests)
|
|
636
841
|
const prefix = options.getContentPrefix?.();
|
|
637
|
-
|
|
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);
|
|
638
848
|
const traceId = (0, nerves_1.createTraceId)();
|
|
639
849
|
result = await (0, core_1.runAgent)(messages, cliCallbacks, options.skipSystemPromptRefresh ? undefined : "cli", currentAbort.signal, {
|
|
640
850
|
toolChoiceRequired: getEffectiveToolChoiceRequired(),
|
|
641
851
|
traceId,
|
|
642
852
|
tools: options.tools,
|
|
643
853
|
execTool: wrappedExecTool,
|
|
644
|
-
toolContext:
|
|
854
|
+
toolContext: effectiveToolContext,
|
|
645
855
|
});
|
|
646
856
|
}
|
|
647
857
|
}
|
|
648
858
|
catch (err) {
|
|
649
|
-
// AbortError (Ctrl-C) -- silently return to prompt
|
|
650
|
-
// All other errors: show the user what happened
|
|
651
859
|
if (!(err instanceof DOMException && err.name === "AbortError")) {
|
|
652
|
-
|
|
860
|
+
display.error(err instanceof Error ? err.message : String(err));
|
|
653
861
|
}
|
|
654
862
|
}
|
|
655
863
|
cliCallbacks.flushMarkdown();
|
|
656
|
-
|
|
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();
|
|
657
869
|
currentAbort = null;
|
|
658
870
|
// Check if exit tool was fired during this turn
|
|
659
871
|
if (exitToolFired) {
|
|
@@ -663,20 +875,35 @@ async function runCliSession(options) {
|
|
|
663
875
|
// Safety net: never silently swallow an empty response
|
|
664
876
|
const lastMsg = messages[messages.length - 1];
|
|
665
877
|
if (lastMsg?.role === "assistant" && !(typeof lastMsg.content === "string" ? lastMsg.content : "").trim()) {
|
|
666
|
-
|
|
878
|
+
display.warn("(empty response)");
|
|
667
879
|
}
|
|
668
|
-
process.stdout.write("\n\n");
|
|
669
880
|
// Post-turn hook (session persistence, pending drain, prompt refresh, etc.)
|
|
670
881
|
if (options.onTurnEnd) {
|
|
671
882
|
await options.onTurnEnd(messages, result ?? { usage: undefined });
|
|
672
883
|
}
|
|
673
884
|
if (closed)
|
|
674
885
|
break;
|
|
675
|
-
process.stdout.write("\x1b[36m> \x1b[0m");
|
|
676
886
|
}
|
|
677
887
|
}
|
|
678
888
|
finally {
|
|
679
|
-
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
|
+
}
|
|
680
907
|
if (options.banner !== false) {
|
|
681
908
|
// eslint-disable-next-line no-console -- terminal UX: goodbye
|
|
682
909
|
console.log("bye");
|
|
@@ -689,17 +916,32 @@ async function main(agentName, options) {
|
|
|
689
916
|
if (agentName)
|
|
690
917
|
(0, identity_1.setAgentName)(agentName);
|
|
691
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);
|
|
692
934
|
// Fallback: apply pending updates for daemon-less direct CLI usage
|
|
693
935
|
(0, update_hooks_1.registerUpdateHook)(bundle_meta_1.bundleMetaHook);
|
|
936
|
+
(0, update_hooks_1.registerUpdateHook)(agent_config_v2_1.agentConfigV2Hook);
|
|
694
937
|
await (0, update_hooks_1.applyPendingUpdates)((0, identity_1.getAgentBundlesRoot)(), (0, bundle_manifest_1.getPackageVersion)());
|
|
695
938
|
// Fail fast if provider is misconfigured (triggers human-readable error + exit)
|
|
696
|
-
(0, core_1.getProvider)();
|
|
939
|
+
(0, core_1.getProvider)("human");
|
|
697
940
|
// Resolve context kernel (identity + channel) for CLI
|
|
698
941
|
const friendsPath = path.join((0, identity_1.getAgentRoot)(), "friends");
|
|
699
942
|
const friendStore = new store_file_1.FileFriendStore(friendsPath);
|
|
700
943
|
const username = os.userInfo().username;
|
|
701
|
-
const
|
|
702
|
-
const localExternalId = `${username}@${hostname}`;
|
|
944
|
+
const localExternalId = username;
|
|
703
945
|
const resolver = new resolver_1.FriendResolver(friendStore, {
|
|
704
946
|
provider: "local",
|
|
705
947
|
externalId: localExternalId,
|
|
@@ -729,34 +971,81 @@ async function main(agentName, options) {
|
|
|
729
971
|
}
|
|
730
972
|
// Load existing session or start fresh
|
|
731
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;
|
|
732
977
|
const sessionMessages = existing?.messages && existing.messages.length > 0
|
|
733
978
|
? existing.messages
|
|
734
|
-
: [{ 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);
|
|
735
982
|
// Per-turn pipeline input: CLI capabilities and pending dir
|
|
736
983
|
const cliCapabilities = (0, channel_1.getChannelCapabilities)("cli");
|
|
737
|
-
const
|
|
738
|
-
const
|
|
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 };
|
|
739
988
|
try {
|
|
740
989
|
await runCliSession({
|
|
741
|
-
agentName:
|
|
990
|
+
agentName: currentAgentName,
|
|
742
991
|
pasteDebounceMs,
|
|
743
992
|
messages: sessionMessages,
|
|
744
|
-
|
|
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) => {
|
|
745
1002
|
// Run the full per-turn pipeline: resolve -> gate -> session -> drain -> runAgent -> postTurn -> tokens
|
|
746
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 */
|
|
747
1026
|
const result = await (0, pipeline_1.handleInboundTurn)({
|
|
748
1027
|
channel: "cli",
|
|
1028
|
+
sessionKey: "session",
|
|
749
1029
|
capabilities: cliCapabilities,
|
|
750
|
-
messages: [{ role: "user", content: userInput }],
|
|
751
|
-
|
|
1030
|
+
messages: [{ role: "user", content: userContent ?? userInput }],
|
|
1031
|
+
continuityIngressTexts: getCliContinuityIngressTexts(userInput),
|
|
1032
|
+
callbacks: failoverAwareCallbacks,
|
|
752
1033
|
friendResolver: { resolve: () => Promise.resolve(resolvedContext) },
|
|
753
|
-
sessionLoader: {
|
|
1034
|
+
sessionLoader: {
|
|
1035
|
+
loadOrCreate: () => Promise.resolve({
|
|
1036
|
+
messages,
|
|
1037
|
+
sessionPath: sessPath,
|
|
1038
|
+
state: sessionState,
|
|
1039
|
+
events: sessionEvents,
|
|
1040
|
+
}),
|
|
1041
|
+
},
|
|
754
1042
|
pendingDir,
|
|
755
1043
|
friendStore,
|
|
756
1044
|
provider: "local",
|
|
757
1045
|
externalId: localExternalId,
|
|
758
1046
|
enforceTrustGate: trust_gate_1.enforceTrustGate,
|
|
759
1047
|
drainPending: pending_1.drainPending,
|
|
1048
|
+
drainDeferredReturns: (deferredFriendId) => (0, pending_1.drainDeferredReturns)(currentAgentName, deferredFriendId),
|
|
760
1049
|
runAgent: (msgs, cb, channel, sig, opts) => (0, core_1.runAgent)(msgs, cb, channel, sig, {
|
|
761
1050
|
...opts,
|
|
762
1051
|
toolContext: {
|
|
@@ -766,28 +1055,54 @@ async function main(agentName, options) {
|
|
|
766
1055
|
summarize,
|
|
767
1056
|
},
|
|
768
1057
|
}),
|
|
769
|
-
postTurn:
|
|
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
|
+
},
|
|
770
1068
|
accumulateFriendTokens: tokens_1.accumulateFriendTokens,
|
|
771
1069
|
signal,
|
|
772
1070
|
runAgentOptions: {
|
|
773
1071
|
toolChoiceRequired: (0, commands_1.getToolChoiceRequired)(),
|
|
774
1072
|
traceId: (0, nerves_1.createTraceId)(),
|
|
1073
|
+
mcpManager,
|
|
1074
|
+
toolContext,
|
|
775
1075
|
},
|
|
1076
|
+
failoverState,
|
|
776
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 */
|
|
777
1088
|
// Handle gate rejection: display auto-reply if present
|
|
778
1089
|
if (!result.gateResult.allowed) {
|
|
779
1090
|
if ("autoReply" in result.gateResult && result.gateResult.autoReply) {
|
|
780
1091
|
process.stdout.write(`${result.gateResult.autoReply}\n`);
|
|
781
1092
|
}
|
|
782
1093
|
}
|
|
783
|
-
return { usage: result.usage };
|
|
784
|
-
},
|
|
785
|
-
onNewSession: () => {
|
|
786
|
-
(0, context_1.deleteSession)(sessPath);
|
|
1094
|
+
return { usage: result.usage, turnOutcome: result.turnOutcome, commandAction: result.commandAction };
|
|
787
1095
|
},
|
|
788
1096
|
});
|
|
789
1097
|
}
|
|
790
1098
|
finally {
|
|
791
1099
|
sessionLock?.release();
|
|
792
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);
|
|
793
1107
|
}
|
|
1108
|
+
// CI trigger
|