mixdog 0.9.95 → 0.9.97
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/LICENSES/Apache-2.0.txt +201 -0
- package/LICENSES/MIT.txt +56 -0
- package/LICENSES/codex-NOTICE.txt +6 -0
- package/NOTICE.md +46 -9
- package/README.md +0 -17
- package/package.json +16 -12
- package/scripts/.tmp-coverage-report.mjs +96 -0
- package/scripts/build-tui.mjs +17 -1
- package/scripts/{channel-daemon-stub.mjs → daemon-stub.mjs} +18 -12
- package/scripts/fixtures/patch-replay-corpus.json +98 -0
- package/src/defaults/mixdog-config.template.json +5 -8
- package/src/defaults/skills/setup/SKILL.md +1 -1
- package/src/lib/keychain-cjs.cjs +1 -1
- package/src/lib/mixdog-debug.cjs +2 -3
- package/src/rules/agent/30-explorer.md +7 -4
- package/src/rules/lead/01-general.md +1 -2
- package/src/rules/lead/lead-tool.md +0 -3
- package/src/rules/shared/01-tool.md +21 -29
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +18 -2
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +31 -1
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +6 -17
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +12 -1
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +4 -13
- package/src/runtime/agent/orchestrator/config.mjs +246 -67
- package/src/runtime/agent/orchestrator/mcp/client.mjs +104 -41
- package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +23 -0
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +113 -14
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +5 -1
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +32 -11
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +89 -2
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +12 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +19 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +144 -39
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +22 -8
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +15 -1
- package/src/runtime/agent/orchestrator/providers/openai-transport-policy.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +3 -4
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +3 -6
- package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +67 -7
- package/src/runtime/agent/orchestrator/providers/stream-json-pool.mjs +291 -0
- package/src/runtime/agent/orchestrator/providers/stream-json-worker.mjs +21 -0
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +36 -10
- package/src/runtime/agent/orchestrator/session/compact.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +22 -33
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +0 -24
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +21 -29
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +7 -10
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +45 -25
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +2 -3
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +142 -84
- package/src/runtime/agent/orchestrator/session/manager/prefetch-bridge.mjs +12 -6
- package/src/runtime/agent/orchestrator/session/manager/runtime-loaders.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +31 -1
- package/src/runtime/agent/orchestrator/session/manager/session-lock.mjs +38 -2
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +29 -9
- package/src/runtime/agent/orchestrator/session/manager.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/result-classification.mjs +65 -0
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +86 -8
- package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +10 -3
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +71 -42
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +4 -33
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +4 -5
- package/src/runtime/agent/orchestrator/stall-policy.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +35 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +116 -26
- package/src/runtime/agent/orchestrator/tools/builtin/binary-file.mjs +92 -19
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +8 -9
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/fs-reachability.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-context-expander.mjs +6 -15
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +12 -26
- package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +83 -33
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +42 -45
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +27 -9
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +7 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +17 -39
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +73 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-spawn.mjs +0 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +3 -8
- package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +30 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +142 -26
- package/src/runtime/agent/orchestrator/tools/code-graph/search-references.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +47 -19
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/env-scrub.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/lib/pwsh-standby-pool.mjs +83 -13
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +39 -1
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +94 -20
- package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +16 -0
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +108 -12
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +12 -11
- package/src/runtime/agent/orchestrator/tools/patch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +63 -9
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +2 -2
- package/src/runtime/channels/lib/config.mjs +34 -34
- package/src/runtime/channels/lib/inbound-handler.mjs +8 -8
- package/src/runtime/channels/lib/interaction-handlers.mjs +6 -6
- package/src/runtime/channels/lib/memory-client.mjs +31 -157
- package/src/runtime/channels/lib/output-forwarder.mjs +16 -16
- package/src/runtime/channels/lib/owned-runtime.mjs +60 -60
- package/src/runtime/channels/lib/owner-heartbeat.mjs +1 -1
- package/src/runtime/channels/lib/{backend-dispatch.mjs → provider-dispatch.mjs} +6 -6
- package/src/runtime/channels/lib/runtime-paths.mjs +4 -30
- package/src/runtime/channels/lib/scheduler.mjs +2 -2
- package/src/runtime/channels/lib/status-snapshot.mjs +3 -3
- package/src/runtime/channels/lib/tool-dispatch.mjs +4 -4
- package/src/runtime/channels/lib/worker-bootstrap.mjs +1 -1
- package/src/runtime/channels/lib/worker-ipc.mjs +7 -7
- package/src/runtime/channels/lib/worker-main.mjs +38 -38
- package/src/runtime/channels/{backends → providers}/discord-access.mjs +1 -1
- package/src/runtime/channels/{backends → providers}/discord-gateway.mjs +1 -1
- package/src/runtime/channels/{backends → providers}/discord.mjs +3 -3
- package/src/runtime/channels/{backends → providers}/telegram.mjs +12 -12
- package/src/runtime/channels/tool-defs.mjs +1 -1
- package/src/runtime/memory/index.mjs +76 -248
- package/src/runtime/memory/lib/agent-ipc.mjs +146 -81
- package/src/runtime/memory/lib/compact-vector-cache.mjs +88 -0
- package/src/runtime/memory/lib/core-memory-store.mjs +18 -1
- package/src/runtime/memory/lib/cycle-llm-adapters.mjs +13 -34
- package/src/runtime/memory/lib/embedding-provider.mjs +47 -26
- package/src/runtime/memory/lib/embedding-worker.mjs +30 -34
- package/src/runtime/memory/lib/http-router.mjs +9 -6
- package/src/runtime/memory/lib/ko-morph.mjs +3 -20
- package/src/runtime/memory/lib/memory-action-handlers.mjs +2 -1
- package/src/runtime/memory/lib/memory-config-flags.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle1.mjs +1 -0
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +4 -2
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +1 -0
- package/src/runtime/memory/lib/memory-cycle2-shared.mjs +4 -1
- package/src/runtime/memory/lib/memory-cycle3.mjs +3 -1
- package/src/runtime/memory/lib/memory-daemon-lifecycle.mjs +5 -0
- package/src/runtime/memory/lib/memory-log.mjs +1 -1
- package/src/runtime/memory/lib/memory-process-lock.mjs +5 -60
- package/src/runtime/memory/lib/memory.mjs +1 -1
- package/src/runtime/memory/lib/pg/process.mjs +32 -9
- package/src/runtime/memory/lib/session-ingest.mjs +1 -1
- package/src/runtime/memory/tool-defs.mjs +1 -5
- package/src/runtime/search/index.mjs +2 -2
- package/src/runtime/search/tool-defs.mjs +2 -2
- package/src/runtime/shared/abort-race.mjs +70 -0
- package/src/runtime/shared/agent-route-config.mjs +119 -0
- package/src/runtime/shared/atomic-file.mjs +80 -4
- package/src/runtime/shared/channel-notification-routing.mjs +2 -2
- package/src/runtime/shared/child-guardian.mjs +204 -108
- package/src/runtime/shared/child-spawn-gate.mjs +139 -72
- package/src/runtime/shared/config.mjs +120 -47
- package/src/runtime/shared/llm/index.mjs +1 -1
- package/src/runtime/shared/owner-fair-gate.mjs +134 -0
- package/src/runtime/shared/resource-admission.mjs +150 -100
- package/src/runtime/shared/stream-progress.mjs +6 -0
- package/src/runtime/shared/tool-execution-owner.mjs +12 -0
- package/src/runtime/shared/tool-result-summary.mjs +1 -1
- package/src/runtime/shared/tool-surface.mjs +91 -27
- package/src/runtime/shared/tool-workload-gates.mjs +29 -0
- package/src/runtime/shared/turn-snapshot.mjs +47 -6
- package/src/runtime/shared/turn-worktree-snapshot.mjs +14 -2
- package/src/session-runtime/channel-config-api.mjs +5 -5
- package/src/session-runtime/config-helpers.mjs +39 -37
- package/src/session-runtime/config-lifecycle.mjs +27 -27
- package/src/session-runtime/lifecycle-api.mjs +22 -3
- package/src/session-runtime/memory-runtime-prewarm.mjs +15 -0
- package/src/session-runtime/model-route-api.mjs +7 -5
- package/src/session-runtime/prewarm.mjs +1 -1
- package/src/session-runtime/provider-auth-api.mjs +0 -7
- package/src/session-runtime/provider-models.mjs +1 -1
- package/src/session-runtime/remote-control.mjs +6 -7
- package/src/session-runtime/runtime-core.mjs +63 -39
- package/src/session-runtime/runtime-tunables.mjs +4 -4
- package/src/session-runtime/self-update.mjs +1 -1
- package/src/session-runtime/session-lifecycle.mjs +45 -17
- package/src/session-runtime/session-title.mjs +1 -1
- package/src/session-runtime/session-turn-api.mjs +96 -24
- package/src/session-runtime/settings-api.mjs +12 -24
- package/src/session-runtime/tool-defs.mjs +3 -3
- package/src/session-runtime/workflow-agents-api.mjs +55 -81
- package/src/session-runtime/workflow.mjs +14 -24
- package/src/standalone/agent-dispatch-broker.mjs +198 -0
- package/src/standalone/agent-tool/helpers.mjs +2 -2
- package/src/standalone/agent-tool/spawn-flow.mjs +2 -0
- package/src/standalone/agent-tool/spawn-preset.mjs +11 -21
- package/src/standalone/agent-tool/tool-def.mjs +0 -14
- package/src/standalone/channel-admin.mjs +53 -64
- package/src/standalone/{channel-daemon-client.mjs → channel-client.mjs} +33 -19
- package/src/standalone/{channel-daemon-transport.mjs → channel-transport.mjs} +127 -46
- package/src/standalone/channel-worker.mjs +124 -647
- package/src/standalone/{backend-daemon.mjs → daemon.mjs} +270 -154
- package/src/standalone/explore-tool.mjs +187 -70
- package/src/standalone/fair-call-scheduler.mjs +264 -0
- package/src/standalone/memory-runtime-proxy.mjs +58 -1
- package/src/standalone/session-client.mjs +1134 -0
- package/src/standalone/session-protocol.mjs +192 -0
- package/src/standalone/session-runtime-pool.mjs +393 -0
- package/src/standalone/session-runtime-record.mjs +20 -0
- package/src/standalone/session-runtime-worker.mjs +203 -0
- package/src/standalone/{engine-daemon-service.mjs → session-service.mjs} +481 -224
- package/src/standalone/{engine-daemon-transport.mjs → session-transport.mjs} +390 -121
- package/src/standalone/session-wire.mjs +55 -0
- package/src/tui/App.jsx +34 -21
- package/src/tui/app/app-format.mjs +1 -1
- package/src/tui/app/app-view.jsx +5 -2
- package/src/tui/app/channel-pickers.mjs +41 -41
- package/src/tui/app/core-memory-picker.mjs +19 -19
- package/src/tui/app/doctor.mjs +5 -11
- package/src/tui/app/input-parsers.mjs +2 -2
- package/src/tui/app/message-selector.mjs +103 -0
- package/src/tui/app/onboarding-steps.mjs +3 -4
- package/src/tui/app/project-picker.mjs +80 -56
- package/src/tui/app/prompt-submit.mjs +48 -43
- package/src/tui/app/resume-picker.mjs +2 -2
- package/src/tui/app/route-pickers.mjs +3 -1
- package/src/tui/app/settings-picker.mjs +31 -33
- package/src/tui/app/transcript-window.mjs +38 -0
- package/src/tui/app/use-prompt-draft-flow.mjs +5 -7
- package/src/tui/app/use-prompt-handlers.mjs +86 -21
- package/src/tui/app/use-prompt-hint.mjs +3 -2
- package/src/tui/app/use-prompt-queue-history.mjs +10 -3
- package/src/tui/app/use-transcript-scroll.mjs +10 -3
- package/src/tui/app/use-transcript-window.mjs +50 -17
- package/src/tui/components/PromptInput.jsx +71 -38
- package/src/tui/components/Spinner.jsx +1 -1
- package/src/tui/components/ToolExecution.jsx +1 -1
- package/src/tui/components/prompt-input/edit-helpers.mjs +9 -11
- package/src/tui/components/prompt-input/escape-policy.mjs +42 -0
- package/src/tui/components/prompt-input/immediate-render.mjs +0 -10
- package/src/tui/components/prompt-input/interrupt-policy.mjs +10 -0
- package/src/tui/components/prompt-input/restore-policy.mjs +10 -0
- package/src/tui/components/tool-execution/surface-detail.mjs +1 -1
- package/src/tui/dist/index.mjs +1031 -1676
- package/src/tui/hooks/useSession.mjs +16 -0
- package/src/tui/index.jsx +12 -13
- package/src/tui/{engine → session}/agent-envelope.mjs +3 -3
- package/src/tui/{engine → session}/agent-job-feed.mjs +4 -4
- package/src/tui/{engine → session}/boot-profile.mjs +2 -2
- package/src/tui/{engine → session}/context-state.mjs +3 -3
- package/src/tui/{engine → session}/labels.mjs +2 -2
- package/src/tui/{engine → session}/live-share.mjs +38 -4
- package/src/tui/{engine → session}/notice-text.mjs +2 -2
- package/src/tui/{engine → session}/notification-plan.mjs +3 -3
- package/src/tui/session/oauth-flows.mjs +140 -0
- package/src/tui/{engine → session}/prompt-history.mjs +3 -3
- package/src/tui/{engine → session}/queue-helpers.mjs +2 -2
- package/src/tui/{engine → session}/render-timing.mjs +2 -2
- package/src/tui/{engine → session}/session-api-ext.mjs +30 -20
- package/src/tui/{engine → session}/session-api.mjs +110 -93
- package/src/tui/{engine → session}/session-flow.mjs +8 -2
- package/src/tui/{engine → session}/session-stats.mjs +3 -3
- package/src/tui/{engine → session}/tool-approval.mjs +3 -3
- package/src/tui/{engine → session}/tool-call-fields.mjs +2 -2
- package/src/tui/{engine → session}/tool-card-results.mjs +8 -6
- package/src/tui/{engine → session}/tool-result-status.mjs +5 -5
- package/src/tui/{engine → session}/tool-result-text.mjs +13 -3
- package/src/tui/{engine → session}/transcript-spill.mjs +34 -34
- package/src/tui/{engine → session}/turn.mjs +38 -176
- package/src/tui/{engine-local-session.mjs → session-local.mjs} +103 -103
- package/src/tui/session.mjs +22 -0
- package/src/tui/spinner-meta.mjs +4 -4
- package/src/tui/spinner-verbs.mjs +51 -3
- package/src/ui/statusline-segments.mjs +1 -1
- package/src/ui/statusline.mjs +1 -1
- package/vendor/ink/build/components/App.js +10 -1
- package/src/session-runtime/memory-daemon-probe.mjs +0 -61
- package/src/standalone/engine-daemon-client.mjs +0 -894
- package/src/standalone/engine-daemon-local-bridge.mjs +0 -20
- package/src/standalone/engine-daemon-protocol.mjs +0 -33
- package/src/tui/engine/turn-watchdog.mjs +0 -92
- package/src/tui/engine.mjs +0 -22
- package/src/tui/hooks/useEngine.mjs +0 -16
- /package/src/runtime/agent/orchestrator/session/compact/{engine.mjs → runner.mjs} +0 -0
- /package/src/runtime/channels/{backends → providers}/discord-attachments.mjs +0 -0
- /package/src/tui/{engine → session}/agent-response-tail.mjs +0 -0
- /package/src/tui/{engine → session}/frame-batched-store.mjs +0 -0
- /package/src/tui/{engine → session}/tui-steering-persist.mjs +0 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tui/hooks/useSession.mjs — subscribe React to the session store.
|
|
3
|
+
*
|
|
4
|
+
* The store (session-local.mjs) lives outside React and emits immutable snapshots;
|
|
5
|
+
* useSyncExternalStore re-renders the tree whenever a snapshot changes. This
|
|
6
|
+
* keeps the agentLoop fully decoupled from React's lifecycle.
|
|
7
|
+
*/
|
|
8
|
+
import { useSyncExternalStore } from 'react';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {object} store returned by createSessionRuntime()
|
|
12
|
+
* @returns the current session runtime state snapshot
|
|
13
|
+
*/
|
|
14
|
+
export function useSession(store) {
|
|
15
|
+
return useSyncExternalStore(store.subscribe, store.getState, store.getState);
|
|
16
|
+
}
|
package/src/tui/index.jsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* src/tui/index.jsx — entry that mounts the React/ink TUI.
|
|
3
3
|
*
|
|
4
|
-
* Creates the
|
|
4
|
+
* Creates the session runtime session (runs OUR agentLoop outside React) and ink-renders
|
|
5
5
|
* <App store={...}/>. Resolves when the app exits (/exit or /quit).
|
|
6
6
|
*/
|
|
7
7
|
import React from 'react';
|
|
@@ -13,8 +13,8 @@ import { performance } from 'node:perf_hooks';
|
|
|
13
13
|
import { format } from 'node:util';
|
|
14
14
|
import { App } from './App.jsx';
|
|
15
15
|
import { cancelPendingMouseTrackingRestores } from './app/use-mouse-input.mjs';
|
|
16
|
-
import {
|
|
17
|
-
import { scheduleRenderFrameAck, TUI_RENDER_FPS } from './
|
|
16
|
+
import { createSessionRuntime } from './session.mjs';
|
|
17
|
+
import { scheduleRenderFrameAck, TUI_RENDER_FPS } from './session/render-timing.mjs';
|
|
18
18
|
import { installProcessSignalCleanup } from '../runtime/shared/process-shutdown.mjs';
|
|
19
19
|
import { finishProcessLifecycle } from '../runtime/shared/process-lifecycle.mjs';
|
|
20
20
|
import { rgbSgr } from '../ui/ansi.mjs';
|
|
@@ -30,7 +30,6 @@ import { POP_KITTY, DISABLE_MODIFY_OTHER_KEYS } from './keyboard-protocol.mjs';
|
|
|
30
30
|
import { displayWidth } from './display-width.mjs';
|
|
31
31
|
import { rotateBoundedLog, PLUGIN_LOG_MAX_BYTES, PLUGIN_LOG_KEEP_BYTES } from '../lib/mixdog-debug.cjs';
|
|
32
32
|
import { localPackageVersion } from '../runtime/shared/update-checker.mjs';
|
|
33
|
-
import { touchProjectSelected } from '../standalone/projects.mjs';
|
|
34
33
|
|
|
35
34
|
// Trailing `\x1b[>0s` restores XTSHIFTESCAPE (shift-to-select-extend) to its
|
|
36
35
|
// terminal default; MOUSE_TRACKING_ON opts into `\x1b[>1s`, so every mouse/alt
|
|
@@ -473,7 +472,7 @@ export async function runTui({ provider, model, toolMode, remote, forceOnboardin
|
|
|
473
472
|
|
|
474
473
|
// Enter the alternate screen buffer before session/runtime boot so no stale
|
|
475
474
|
// shell rows are visible while the statusline/TUI data warms up. The first
|
|
476
|
-
// real Ink frame may still arrive after
|
|
475
|
+
// real Ink frame may still arrive after createSessionRuntime(), but the user
|
|
477
476
|
// sees a clean fullscreen surface immediately instead of the previous terminal
|
|
478
477
|
// contents bleeding through the bottom status area.
|
|
479
478
|
process.stdout.write(`${TERMINAL_MODE_RESET_HIDDEN_CURSOR}\x1b[?1049h${TERMINAL_MODE_RESET_HIDDEN_CURSOR}\x1b[2J\x1b[H`);
|
|
@@ -489,10 +488,10 @@ export async function runTui({ provider, model, toolMode, remote, forceOnboardin
|
|
|
489
488
|
|
|
490
489
|
process.on('exit', restoreTerminal);
|
|
491
490
|
|
|
492
|
-
//
|
|
491
|
+
// Session runtime startup is independent from palette selection. Start it now and
|
|
493
492
|
// overlap its runtime import/config work with the theme read, while still
|
|
494
493
|
// awaiting the theme before the first React frame.
|
|
495
|
-
const storeOutcomePromise =
|
|
494
|
+
const storeOutcomePromise = createSessionRuntime({ provider, model, toolMode, remote })
|
|
496
495
|
.then((store) => ({ store, error: null }), (error) => ({ store: null, error }));
|
|
497
496
|
|
|
498
497
|
// Apply the persisted UI theme (ui.theme in mixdog-config.json) before the
|
|
@@ -502,7 +501,7 @@ export async function runTui({ provider, model, toolMode, remote, forceOnboardin
|
|
|
502
501
|
try { await loadThemeSettingFromConfig(); } catch { /* default theme stays */ }
|
|
503
502
|
emitTerminalBackground(theme.background);
|
|
504
503
|
|
|
505
|
-
// Static splash + loading spinner while
|
|
504
|
+
// Static splash + loading spinner while createSessionRuntime() warms up
|
|
506
505
|
// (config, providers, sessions). Without this the user stares at an empty
|
|
507
506
|
// alt screen for the whole boot, then the UI "pops in" — the splash makes
|
|
508
507
|
// the first ink frame an in-place repaint instead.
|
|
@@ -517,15 +516,15 @@ export async function runTui({ provider, model, toolMode, remote, forceOnboardin
|
|
|
517
516
|
// this, a project entered straight from the shell never moved in the
|
|
518
517
|
// picker's most-recent-first order. Unregistered folders are ignored.
|
|
519
518
|
try {
|
|
520
|
-
touchProjectSelected(store.getState?.()?.cwd || process.cwd());
|
|
519
|
+
await store.touchProjectSelected(store.getState?.()?.cwd || process.cwd());
|
|
521
520
|
} catch { /* best-effort recency */ }
|
|
522
|
-
// The palette lives in THIS process: on a daemon-backed store the
|
|
521
|
+
// The palette lives in THIS process: on a daemon-backed store the session runtime's
|
|
523
522
|
// theme methods would run inside the daemon, so the picker saw a promise
|
|
524
523
|
// instead of the list and a switch repainted nothing here. Bind the three
|
|
525
524
|
// theme methods locally (same pattern as the ink selection helpers below)
|
|
526
|
-
// and mirror the switch to the
|
|
525
|
+
// and mirror the switch to the session runtime with persist:false, purely so its
|
|
527
526
|
// themeEpoch bump re-renders this tree.
|
|
528
|
-
if (store.
|
|
527
|
+
if (store.isRemoteSession === true) {
|
|
529
528
|
const remoteSetTheme = store.setTheme;
|
|
530
529
|
store.listThemes = () => listThemes();
|
|
531
530
|
store.getTheme = () => getThemeSetting();
|
|
@@ -548,7 +547,7 @@ export async function runTui({ provider, model, toolMode, remote, forceOnboardin
|
|
|
548
547
|
process.stderr.write(`mixdog: ${error?.message || error}\n`);
|
|
549
548
|
return 1;
|
|
550
549
|
}
|
|
551
|
-
// The
|
|
550
|
+
// The session runtime store is a daemon-backed Proxy: every method it does not
|
|
552
551
|
// implement locally comes back as an ASYNC remote call. A synchronous
|
|
553
552
|
// getOnboardingStatus() probe inside App therefore sees a Promise, never
|
|
554
553
|
// `.completed`, and re-opens the finished wizard on every launch. Resolve
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* src/tui/
|
|
2
|
+
* src/tui/session/agent-envelope.mjs - parse agent/background-task/shell
|
|
3
3
|
* notification envelopes into synthetic tool-item shapes, and derive
|
|
4
|
-
* status/result text. Extracted from
|
|
5
|
-
* remains part of
|
|
4
|
+
* status/result text. Extracted from session-local.mjs; parseBackgroundTaskEnvelope
|
|
5
|
+
* remains part of session-local.mjs's public surface.
|
|
6
6
|
*/
|
|
7
7
|
import { isBackgroundErrorOnlyBody } from '../../runtime/shared/err-text.mjs';
|
|
8
8
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* src/tui/
|
|
3
|
-
* extracted from
|
|
2
|
+
* src/tui/session/agent-job-feed.mjs — agent-job / runtime-notification plumbing
|
|
3
|
+
* extracted from the session runtime as a dependency-injection
|
|
4
4
|
* factory.
|
|
5
5
|
*
|
|
6
6
|
* Owns: the agent-job card result patch (updateAgentJobCard), the
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* These handlers mutate live session state and drive the queue, so
|
|
12
12
|
* state/set/enqueue/drain/pushUserOrSyntheticItem/patchItem/etc are threaded via
|
|
13
13
|
* the factory argument (getters/callbacks) — never stale snapshots. Every body
|
|
14
|
-
* is the original
|
|
14
|
+
* is the original session-local.mjs logic verbatim.
|
|
15
15
|
*/
|
|
16
16
|
import {
|
|
17
17
|
parseAgentJob,
|
|
@@ -314,7 +314,7 @@ export function createAgentJobFeed({
|
|
|
314
314
|
if (firstDelivery && !successfulPreview && !bodyAlreadyDisplayed) {
|
|
315
315
|
if (cardKey) rememberDisplayedExecutionNotificationKey(cardKey, terminal, executionId);
|
|
316
316
|
if (executionId) rememberDisplayedExecutionResponseState(executionId, hasBody ? 'body' : 'preview', terminal);
|
|
317
|
-
// Execution completions are inbound agent responses. The
|
|
317
|
+
// Execution completions are inbound agent responses. The session runtime keeps
|
|
318
318
|
// their aggregation tail-safe (only an immediately-adjacent inbound
|
|
319
319
|
// response of the same preview/body phase can merge); the fallback
|
|
320
320
|
// preserves the standalone path for minimal/test harnesses.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* src/tui/
|
|
2
|
+
* src/tui/session/boot-profile.mjs - opt-in boot timing trace.
|
|
3
3
|
*
|
|
4
|
-
* Extracted from
|
|
4
|
+
* Extracted from session-local.mjs. Emits `[mixdog-boot]` stderr lines when
|
|
5
5
|
* MIXDOG_BOOT_PROFILE is truthy; a no-op otherwise.
|
|
6
6
|
*/
|
|
7
7
|
import { performance } from 'node:perf_hooks';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* src/tui/
|
|
2
|
+
* src/tui/session/context-state.mjs - route/context/agent-status derivations.
|
|
3
3
|
*
|
|
4
|
-
* Extracted from
|
|
4
|
+
* Extracted from session-local.mjs unchanged. These read the live runtime + store
|
|
5
5
|
* snapshot. The two sync helpers stage immutable draft patches through
|
|
6
6
|
* updateState; callers still follow with set(...) to schedule publication.
|
|
7
7
|
*/
|
|
@@ -24,7 +24,7 @@ export function createContextState({ runtime, getState, updateState, getPendingS
|
|
|
24
24
|
};
|
|
25
25
|
const baseRouteState = () => ({
|
|
26
26
|
sessionId: runtime.id,
|
|
27
|
-
// Fork-on-resume marker: when the
|
|
27
|
+
// Fork-on-resume marker: when the session runtime opened a live session as a fork
|
|
28
28
|
// (fresh id, copied transcript), hosts validating "resume returned the
|
|
29
29
|
// requested session" must accept the fork by its origin id.
|
|
30
30
|
sessionForkedFrom: runtime.session?.forkedFrom || null,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* src/tui/
|
|
3
|
-
* event label/detail helpers. Extracted from
|
|
2
|
+
* src/tui/session/labels.mjs - spinner verbs, elapsed formatting, and compact
|
|
3
|
+
* event label/detail helpers. Extracted from session-local.mjs.
|
|
4
4
|
*/
|
|
5
5
|
import { SPINNER_VERBS } from '../spinner-verbs.mjs';
|
|
6
6
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* src/tui/
|
|
2
|
+
* src/tui/session/live-share.mjs — real-time cross-surface session mirroring.
|
|
3
3
|
*
|
|
4
4
|
* OWNER leg: hosts a local named-pipe (win32) / unix-socket server for the
|
|
5
5
|
* session it owns and pushes frame-batched transcript deltas (items /
|
|
6
6
|
* streaming tail / spinner) to attached viewers. Push-only: zero polling,
|
|
7
7
|
* idle cost is one open listener.
|
|
8
8
|
* VIEWER leg: connects to the owner's pipe and mirrors those deltas into the
|
|
9
|
-
* local
|
|
9
|
+
* local session runtime store, so streaming output appears live on every co-open
|
|
10
10
|
* surface (desktop <-> terminal). Viewer submits travel over the same pipe
|
|
11
11
|
* into the owner's normal submit queue (concurrent input from both sides is
|
|
12
12
|
* serialized there); the durable pending spool remains the fallback when the
|
|
@@ -25,7 +25,7 @@ const MAX_BUFFER_BYTES = 8 * 1024 * 1024;
|
|
|
25
25
|
const SYNC_REQUEST_MIN_INTERVAL_MS = 500;
|
|
26
26
|
// Session entry is latency-sensitive: the owner pipe can be a few event-loop
|
|
27
27
|
// turns behind the viewer resume. Retry locally instead of waiting for the
|
|
28
|
-
// coarse 3s
|
|
28
|
+
// coarse 3s session runtime safety tick.
|
|
29
29
|
const LIVE_CONNECT_RETRY_MIN_MS = 10;
|
|
30
30
|
const LIVE_CONNECT_RETRY_MAX_MS = 160;
|
|
31
31
|
|
|
@@ -640,7 +640,7 @@ export function createLiveShare({
|
|
|
640
640
|
};
|
|
641
641
|
|
|
642
642
|
// Reconciles both legs against the current session role. Failed pipe opens
|
|
643
|
-
// continue through the local retry timers; the
|
|
643
|
+
// continue through the local retry timers; the session runtime's 3s call is only a
|
|
644
644
|
// safety net, never the normal connection cadence.
|
|
645
645
|
const ensureShare = () => {
|
|
646
646
|
if (disposed) return;
|
|
@@ -709,3 +709,37 @@ export function createLiveShare({
|
|
|
709
709
|
},
|
|
710
710
|
};
|
|
711
711
|
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* ONE viewer-submit intake for EVERY session runtime entry point. The TUI store calls
|
|
715
|
+
* submit(); the daemon awaits submitAsync() (session-service prefers it).
|
|
716
|
+
* Wrapping only one of them let the other path enqueue the prompt on the VIEWER
|
|
717
|
+
* session runtime, so that surface rendered its own local user row beside the owner's
|
|
718
|
+
* mirrored one — the same message twice on screen
|
|
719
|
+
* (user: 데스크탑에서만 입력이 두 개로 들어간다).
|
|
720
|
+
*
|
|
721
|
+
* `spool(submissionId)` is the durable fallback. The submission id is minted
|
|
722
|
+
* here when the caller has none, so the pipe frame and the spool entry carry
|
|
723
|
+
* the SAME identity and the owner reuses it for the settled user row — which is
|
|
724
|
+
* what releases the submitting surface's optimistic bubble.
|
|
725
|
+
*/
|
|
726
|
+
export function forwardViewerSubmit({ text, options = {}, share, spool, pid = process.pid }) {
|
|
727
|
+
const value = String(text || '').trim();
|
|
728
|
+
if (!value) return false;
|
|
729
|
+
// Reconcile first so a session that became attachable this event-loop turn
|
|
730
|
+
// takes the instant pipe path instead of the durable detour.
|
|
731
|
+
try { share.ensure?.(); } catch { /* durable fallback below */ }
|
|
732
|
+
const submissionId = options.id != null && String(options.id).trim()
|
|
733
|
+
? String(options.id).trim()
|
|
734
|
+
: `view-submit-${pid}-${Date.now()}`;
|
|
735
|
+
const deliverDurably = () => spool?.(submissionId) === true;
|
|
736
|
+
// A pipe WRITE is not a delivery: the owner may refuse the prompt and the
|
|
737
|
+
// socket may die between write and read, so an unacknowledged submit is
|
|
738
|
+
// re-delivered through the spool — late, never lost.
|
|
739
|
+
if (share.sendSubmit(value, {
|
|
740
|
+
id: submissionId,
|
|
741
|
+
submittedAt: options.submittedAt,
|
|
742
|
+
onUndelivered: deliverDurably,
|
|
743
|
+
})) return true;
|
|
744
|
+
return deliverDurably();
|
|
745
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* src/tui/
|
|
3
|
-
* Extracted from
|
|
2
|
+
* src/tui/session/notice-text.mjs - polish user-facing failure/notice text.
|
|
3
|
+
* Extracted from session-local.mjs.
|
|
4
4
|
*/
|
|
5
5
|
const FAILED_NOTICE_ACTIONS = new Map([
|
|
6
6
|
['api key save', 'save API key'],
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* src/tui/
|
|
2
|
+
* src/tui/session/notification-plan.mjs - pure runtime.onNotification delivery plan.
|
|
3
3
|
*
|
|
4
|
-
* Extracted from
|
|
4
|
+
* Extracted from session-local.mjs (no behavior change). These are stateless helpers
|
|
5
5
|
* that decide how a runtime notification envelope should be delivered to the
|
|
6
|
-
* TUI store (dedupe key + display/model routing). No closures over
|
|
6
|
+
* TUI store (dedupe key + display/model routing). No closures over session runtime state.
|
|
7
7
|
*/
|
|
8
8
|
import {
|
|
9
9
|
isStatusOnlyAgentCompletionNotification,
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// Daemon-owned provider OAuth flows. The provider runtime returns live
|
|
2
|
+
// promises/functions, so the session surface must retain them locally and
|
|
3
|
+
// expose only serializable status records to desktop clients.
|
|
4
|
+
const DEFAULT_FLOW_TTL_MS = 10 * 60 * 1_000;
|
|
5
|
+
|
|
6
|
+
export function oauthFlowStatus(flow) {
|
|
7
|
+
return {
|
|
8
|
+
flowId: flow.id,
|
|
9
|
+
provider: flow.provider,
|
|
10
|
+
url: flow.url,
|
|
11
|
+
manualUrl: flow.manualUrl,
|
|
12
|
+
state: flow.state,
|
|
13
|
+
completed: flow.state === 'complete',
|
|
14
|
+
error: flow.error,
|
|
15
|
+
manualCodeSupported: typeof flow.completeCode === 'function',
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function requiredFlowId(value) {
|
|
20
|
+
const id = String(value || '').trim();
|
|
21
|
+
if (!/^oauth_[a-z0-9_]+$/i.test(id)) throw new TypeError('OAuth flow id is invalid.');
|
|
22
|
+
return id;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function cancelFlow(flow, reason) {
|
|
26
|
+
try {
|
|
27
|
+
const result = flow.cancel?.();
|
|
28
|
+
if (result && typeof result.then === 'function') {
|
|
29
|
+
void Promise.resolve(result).catch((error) => {
|
|
30
|
+
console.warn(`OAuth flow could not be ${reason}:`, error);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.warn(`OAuth flow could not be ${reason}:`, error);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function createSessionOAuthFlowRegistry({ ttlMs = DEFAULT_FLOW_TTL_MS } = {}) {
|
|
39
|
+
const flows = new Map();
|
|
40
|
+
let sequence = 0;
|
|
41
|
+
|
|
42
|
+
function requireFlow(id) {
|
|
43
|
+
const key = requiredFlowId(id);
|
|
44
|
+
const flow = flows.get(key);
|
|
45
|
+
if (!flow) throw new Error('OAuth login flow is no longer available.');
|
|
46
|
+
return flow;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function register(value) {
|
|
50
|
+
const started = value && typeof value === 'object' ? value : null;
|
|
51
|
+
if (!started) throw new Error('OAuth provider did not return a login flow.');
|
|
52
|
+
const provider = String(started.provider || '').trim();
|
|
53
|
+
if (!provider) throw new Error('OAuth provider login is missing its provider id.');
|
|
54
|
+
const id = `oauth_${Date.now().toString(36)}_${(++sequence).toString(36)}`;
|
|
55
|
+
const flow = {
|
|
56
|
+
id,
|
|
57
|
+
provider,
|
|
58
|
+
url: typeof started.url === 'string' ? started.url : null,
|
|
59
|
+
manualUrl: typeof started.manualUrl === 'string' ? started.manualUrl : null,
|
|
60
|
+
state: 'pending',
|
|
61
|
+
result: null,
|
|
62
|
+
error: null,
|
|
63
|
+
completeCode: typeof started.completeCode === 'function' ? started.completeCode : undefined,
|
|
64
|
+
cancel: typeof started.cancel === 'function' ? started.cancel : undefined,
|
|
65
|
+
timeout: null,
|
|
66
|
+
};
|
|
67
|
+
flow.timeout = setTimeout(() => {
|
|
68
|
+
const current = flows.get(id);
|
|
69
|
+
if (!current) return;
|
|
70
|
+
cancelFlow(current, 'expired');
|
|
71
|
+
flows.delete(id);
|
|
72
|
+
}, Math.max(1, Number(ttlMs) || DEFAULT_FLOW_TTL_MS));
|
|
73
|
+
flow.timeout.unref?.();
|
|
74
|
+
flows.set(id, flow);
|
|
75
|
+
if (started.waitForCallback && typeof started.waitForCallback.then === 'function') {
|
|
76
|
+
void Promise.resolve(started.waitForCallback).then((result) => {
|
|
77
|
+
const current = flows.get(id);
|
|
78
|
+
if (!current || current.state !== 'pending' || !result) return;
|
|
79
|
+
current.state = 'complete';
|
|
80
|
+
current.result = true;
|
|
81
|
+
}).catch((error) => {
|
|
82
|
+
const current = flows.get(id);
|
|
83
|
+
if (!current || current.state !== 'pending') return;
|
|
84
|
+
current.state = 'failed';
|
|
85
|
+
current.error = error instanceof Error ? error.message : String(error);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return oauthFlowStatus(flow);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function status(id) {
|
|
92
|
+
return oauthFlowStatus(requireFlow(id));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function complete(id, rawCode) {
|
|
96
|
+
const flow = requireFlow(id);
|
|
97
|
+
if (typeof flow.completeCode !== 'function') {
|
|
98
|
+
throw new Error('This OAuth provider does not accept a manual code.');
|
|
99
|
+
}
|
|
100
|
+
const code = String(rawCode || '').trim();
|
|
101
|
+
if (!code || code.length > 16_384) throw new TypeError('OAuth code is invalid.');
|
|
102
|
+
try {
|
|
103
|
+
const completed = await flow.completeCode(code);
|
|
104
|
+
flow.result = Boolean(completed);
|
|
105
|
+
flow.state = completed ? 'complete' : 'failed';
|
|
106
|
+
flow.error = completed ? null : 'OAuth code did not complete the login.';
|
|
107
|
+
} catch (error) {
|
|
108
|
+
flow.state = 'failed';
|
|
109
|
+
flow.error = error instanceof Error ? error.message : String(error);
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
return oauthFlowStatus(flow);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function cancel(id) {
|
|
116
|
+
const flow = requireFlow(id);
|
|
117
|
+
let failure = null;
|
|
118
|
+
try {
|
|
119
|
+
await flow.cancel?.();
|
|
120
|
+
} catch (error) {
|
|
121
|
+
failure = error;
|
|
122
|
+
} finally {
|
|
123
|
+
flow.state = 'cancelled';
|
|
124
|
+
clearTimeout(flow.timeout);
|
|
125
|
+
flows.delete(flow.id);
|
|
126
|
+
}
|
|
127
|
+
if (failure) throw failure;
|
|
128
|
+
return oauthFlowStatus(flow);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function cancelAll() {
|
|
132
|
+
for (const flow of flows.values()) {
|
|
133
|
+
clearTimeout(flow.timeout);
|
|
134
|
+
cancelFlow(flow, 'cancelled');
|
|
135
|
+
}
|
|
136
|
+
flows.clear();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return Object.freeze({ register, status, complete, cancel, cancelAll });
|
|
140
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* src/tui/
|
|
2
|
+
* src/tui/session/prompt-history.mjs - pure prompt-history derivation.
|
|
3
3
|
*
|
|
4
|
-
* Extracted from
|
|
5
|
-
* history the
|
|
4
|
+
* Extracted from session-local.mjs unchanged: the newest-first, deduped user-prompt
|
|
5
|
+
* history the session runtime publishes on state.promptHistoryList. Pure (input items
|
|
6
6
|
* → array); callers decide when/whether to publish so the store's
|
|
7
7
|
* immutable-emit contract is preserved.
|
|
8
8
|
*/
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* src/tui/
|
|
2
|
+
* src/tui/session/queue-helpers.mjs - pure helpers for the prompt/notification
|
|
3
3
|
* queue: priority ordering, entry visibility/editability, prompt content
|
|
4
4
|
* flattening, session activity timestamps, and batch merging. Extracted from
|
|
5
|
-
*
|
|
5
|
+
* session-local.mjs.
|
|
6
6
|
*/
|
|
7
7
|
import {
|
|
8
8
|
agentJobResultText,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* src/tui/
|
|
2
|
+
* src/tui/session/render-timing.mjs - render-throttle timing helper.
|
|
3
3
|
*
|
|
4
|
-
* Extracted from
|
|
4
|
+
* Extracted from session-local.mjs (no behavior change originally).
|
|
5
5
|
*
|
|
6
6
|
* Ink renders through a maxFps throttle (120fps in index.jsx, ≈8.3ms). A plain
|
|
7
7
|
* setImmediate only yields to the event loop; if Ink already painted within the
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* src/tui/
|
|
2
|
+
* src/tui/session/session-api-ext.mjs - part of the public session runtime session object.
|
|
3
3
|
*/
|
|
4
4
|
import { listThemes, getThemeSetting, setThemeSetting } from '../theme.mjs';
|
|
5
5
|
import { resetAllStreamingMarkdownStablePrefixes } from '../markdown/streaming-markdown.mjs';
|
|
@@ -7,7 +7,8 @@ import { toolResultText } from './tool-result-text.mjs';
|
|
|
7
7
|
import { parseSyntheticAgentMessage } from './agent-envelope.mjs';
|
|
8
8
|
import { flushTuiSteeringPersist } from './tui-steering-persist.mjs';
|
|
9
9
|
import { getVoiceStatus, toggleVoice } from '../lib/voice-setup.mjs';
|
|
10
|
-
import {
|
|
10
|
+
import { createSessionOAuthFlowRegistry } from './oauth-flows.mjs';
|
|
11
|
+
import { aggregateToolCategoryEntries, aggregateDoneCategories, classifyToolCategory, formatAggregateDetail, summarizeToolResult } from '../../runtime/shared/tool-surface.mjs';
|
|
11
12
|
import { aggregateBucketForCategory, aggregateRawResult, failureDetailText, shellCommandExitCode } from './tool-result-status.mjs';
|
|
12
13
|
import { isInternalTranscriptDisplayText } from '../../runtime/shared/tool-execution-contract.mjs';
|
|
13
14
|
import { toolResultTerminalStatus } from '../../runtime/shared/tool-status.mjs';
|
|
@@ -121,10 +122,11 @@ function buildRestoredAggregateItem(members) {
|
|
|
121
122
|
const categoryOrder = [];
|
|
122
123
|
const calls = [];
|
|
123
124
|
for (const { item, category } of members) {
|
|
124
|
-
const entry
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
for (const entry of aggregateToolCategoryEntries(item.name, item.args, category)) {
|
|
126
|
+
if (!categories.has(entry.key)) categoryOrder.push(entry.key);
|
|
127
|
+
const prev = categories.get(entry.key);
|
|
128
|
+
categories.set(entry.key, { ...entry, count: Number(prev?.count || 0) + Number(entry.count || 1) });
|
|
129
|
+
}
|
|
128
130
|
const resultText = String(item.result ?? '');
|
|
129
131
|
// Mirror live outcome semantics: a plain non-zero shell exit is the
|
|
130
132
|
// neutral 'Exit N' state, not a red failure; only real call errors count.
|
|
@@ -186,7 +188,7 @@ function buildRestoredAggregateItem(members) {
|
|
|
186
188
|
}
|
|
187
189
|
|
|
188
190
|
// Restored transcripts must mirror the live turn's category merging: the live
|
|
189
|
-
//
|
|
191
|
+
// session runtime (turn.mjs) collapses consecutive same-bucket tool calls into one
|
|
190
192
|
// aggregate card, but resume rebuilt one card per call, so a reopened session
|
|
191
193
|
// un-merged every run (user bug). Walk the restored items and merge adjacent
|
|
192
194
|
// tool cards whose aggregateBucketForCategory matches; any non-tool item
|
|
@@ -381,10 +383,11 @@ export function sessionContextSnapshotProjection(session, contextStatus) {
|
|
|
381
383
|
};
|
|
382
384
|
}
|
|
383
385
|
|
|
384
|
-
export function
|
|
386
|
+
export function createSessionApiB(bag) {
|
|
385
387
|
const {
|
|
386
388
|
runtime, nextId, flags, lifecycle, listeners, getState, set, flushEmitImmediate, disposeEmit, replaceItems, pushNotice, removeNotice, setProgressHint, clearToastTimers, disposeTranscriptSpill, routeState, syncContextStats, finishToolApproval, denyAllToolApprovals, restoreLeadSteeringFromDisk, resetStats, clearUiActivityBeforeContextSync, resetTuiForPendingSessionReset, snapshotTuiBeforeSessionReset, restoreTuiAfterFailedSessionReset, commitTuiSessionReset, resetStatsAndSyncContext,
|
|
387
389
|
} = bag;
|
|
390
|
+
const oauthFlows = createSessionOAuthFlowRegistry();
|
|
388
391
|
return {
|
|
389
392
|
resolveToolApproval: (id, decision = {}) => {
|
|
390
393
|
const approved = decision === true || decision?.approved === true;
|
|
@@ -519,7 +522,7 @@ export function createEngineApiB(bag) {
|
|
|
519
522
|
// `/remote` slash command — repeated /remote just re-claims (idempotent).
|
|
520
523
|
claimRemote: async () => {
|
|
521
524
|
// Desired state paints before daemon cold boot. The final read in
|
|
522
|
-
// `finally` reconciles a skipped/failed claim to the
|
|
525
|
+
// `finally` reconciles a skipped/failed claim to the session truth.
|
|
523
526
|
set({
|
|
524
527
|
remoteEnabled: true,
|
|
525
528
|
remoteSessionId: runtime.getRemoteSessionId?.() || getState().sessionId || null,
|
|
@@ -623,9 +626,6 @@ export function createEngineApiB(bag) {
|
|
|
623
626
|
setAgentRoute: async (agentId, opts) => {
|
|
624
627
|
return await runtime.setAgentRoute?.(agentId, opts);
|
|
625
628
|
},
|
|
626
|
-
setDefaultProvider: async (provider) => {
|
|
627
|
-
return await runtime.setDefaultProvider?.(provider);
|
|
628
|
-
},
|
|
629
629
|
listProviders: () => {
|
|
630
630
|
return runtime.listProviders();
|
|
631
631
|
},
|
|
@@ -636,7 +636,7 @@ export function createEngineApiB(bag) {
|
|
|
636
636
|
return await runtime.getUsageDashboard?.(options);
|
|
637
637
|
},
|
|
638
638
|
consumeCodexRateLimitResetCredit: async (options = {}) => {
|
|
639
|
-
// Desktop capability parity: without this delegation the
|
|
639
|
+
// Desktop capability parity: without this delegation the session runtime surface
|
|
640
640
|
// rejects the sidebar's reset-credit invoke as unsupported even though
|
|
641
641
|
// the session runtime implements it.
|
|
642
642
|
if (typeof runtime.consumeCodexRateLimitResetCredit !== 'function') {
|
|
@@ -644,8 +644,14 @@ export function createEngineApiB(bag) {
|
|
|
644
644
|
}
|
|
645
645
|
return await runtime.consumeCodexRateLimitResetCredit(options);
|
|
646
646
|
},
|
|
647
|
-
getTurnReviewDiff: async () => {
|
|
648
|
-
return (await runtime.getTurnReviewDiff?.()) ?? { supported: false, files: [], patch: '' };
|
|
647
|
+
getTurnReviewDiff: async (options = {}) => {
|
|
648
|
+
return (await runtime.getTurnReviewDiff?.(options)) ?? { supported: false, files: [], patch: '' };
|
|
649
|
+
},
|
|
650
|
+
revertTurnReviewFile: async (file) => {
|
|
651
|
+
if (typeof runtime.revertTurnReviewFile !== 'function') {
|
|
652
|
+
throw new Error('Turn review revert is unavailable');
|
|
653
|
+
}
|
|
654
|
+
return await runtime.revertTurnReviewFile(file);
|
|
649
655
|
},
|
|
650
656
|
getOnboardingStatus: () => {
|
|
651
657
|
return runtime.getOnboardingStatus?.() || { completed: true, workflowRoutes: {} };
|
|
@@ -682,13 +688,16 @@ export function createEngineApiB(bag) {
|
|
|
682
688
|
if (getState().commandBusy) throw new Error('command busy');
|
|
683
689
|
set({ commandBusy: true });
|
|
684
690
|
try {
|
|
685
|
-
const result = await runtime.beginOAuthProviderLogin(provider);
|
|
691
|
+
const result = oauthFlows.register(await runtime.beginOAuthProviderLogin(provider));
|
|
686
692
|
pushNotice(`provider oauth started: ${result.provider}`, 'info');
|
|
687
693
|
return result;
|
|
688
694
|
} finally {
|
|
689
695
|
set({ commandBusy: false });
|
|
690
696
|
}
|
|
691
697
|
},
|
|
698
|
+
getOAuthProviderLoginStatus: (flowId) => oauthFlows.status(flowId),
|
|
699
|
+
completeOAuthProviderLogin: async (flowId, code) => oauthFlows.complete(flowId, code),
|
|
700
|
+
cancelOAuthProviderLogin: async (flowId) => oauthFlows.cancel(flowId),
|
|
692
701
|
saveProviderApiKey: (provider, secret) => {
|
|
693
702
|
const result = runtime.saveProviderApiKey(provider, secret);
|
|
694
703
|
pushNotice(`provider api key saved: ${result.provider}`, 'info');
|
|
@@ -741,7 +750,7 @@ export function createEngineApiB(bag) {
|
|
|
741
750
|
return runtime.getChannelSetup();
|
|
742
751
|
},
|
|
743
752
|
getChannelWorkerStatus: () => runtime.getChannelWorkerStatus?.(),
|
|
744
|
-
|
|
753
|
+
setChannelProvider: (name) => runtime.setChannelProvider?.(name),
|
|
745
754
|
saveDiscordToken: (token) => {
|
|
746
755
|
const result = runtime.saveDiscordToken(token);
|
|
747
756
|
pushNotice('discord token saved', 'info');
|
|
@@ -883,7 +892,7 @@ export function createEngineApiB(bag) {
|
|
|
883
892
|
renameSessionTitle: (id, title) => {
|
|
884
893
|
return runtime.renameSessionTitle?.(id, title) ?? false;
|
|
885
894
|
},
|
|
886
|
-
// Desktop sidebar watcher hook:
|
|
895
|
+
// Desktop sidebar watcher hook: the daemon watches this directory so
|
|
887
896
|
// heartbeat sidecar create/delete pushes instant working/dot updates.
|
|
888
897
|
// Without it the watcher silently no-ops and the sidebar falls back to
|
|
889
898
|
// the 60s safety poll (user: spinner kept spinning after the turn ended).
|
|
@@ -1005,7 +1014,7 @@ export function createEngineApiB(bag) {
|
|
|
1005
1014
|
}
|
|
1006
1015
|
},
|
|
1007
1016
|
// Desktop split panes: read-only transcript + route peek of an idle
|
|
1008
|
-
// session — no resume, no ownership claim, no
|
|
1017
|
+
// session — no resume, no ownership claim, no session runtime state mutation.
|
|
1009
1018
|
// Panes render every visible session as foreground from this frame.
|
|
1010
1019
|
peekSessionTranscript: (id, options = {}) => {
|
|
1011
1020
|
const session = runtime.peekSession?.(id);
|
|
@@ -1031,7 +1040,7 @@ export function createEngineApiB(bag) {
|
|
|
1031
1040
|
},
|
|
1032
1041
|
resume: async (id, options = {}) => {
|
|
1033
1042
|
if (getState().commandBusy) return false;
|
|
1034
|
-
// quiet: viewer-follow refreshes (
|
|
1043
|
+
// quiet: viewer-follow refreshes (session runtime share tick) re-resume on every
|
|
1035
1044
|
// owner turn — they must not flash the "Resuming conversation" status.
|
|
1036
1045
|
set({
|
|
1037
1046
|
commandBusy: true,
|
|
@@ -1098,6 +1107,7 @@ export function createEngineApiB(bag) {
|
|
|
1098
1107
|
try { lifecycle.unsubscribeRemoteState?.(); } catch {}
|
|
1099
1108
|
lifecycle.unsubscribeRemoteState = null;
|
|
1100
1109
|
denyAllToolApprovals('runtime closing');
|
|
1110
|
+
oauthFlows.cancelAll();
|
|
1101
1111
|
await flushTuiSteeringPersist();
|
|
1102
1112
|
await runtime.close(reason, options);
|
|
1103
1113
|
listeners.clear();
|