mixdog 0.9.96 → 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/README.md +0 -17
- package/package.json +8 -7
- package/scripts/build-tui.mjs +6 -6
- package/scripts/{channel-daemon-stub.mjs → daemon-stub.mjs} +18 -12
- 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/lead/01-general.md +1 -2
- package/src/rules/lead/lead-tool.md +0 -3
- package/src/rules/shared/01-tool.md +11 -10
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +4 -13
- package/src/runtime/agent/orchestrator/mcp/client.mjs +81 -33
- 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/session/agent-loop.mjs +8 -6
- package/src/runtime/agent/orchestrator/session/compact.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +22 -4
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +20 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +4 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +40 -25
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +2 -3
- package/src/runtime/agent/orchestrator/session/manager/prefetch-bridge.mjs +12 -6
- package/src/runtime/agent/orchestrator/session/manager/session-lock.mjs +38 -2
- 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 +59 -42
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +12 -22
- package/src/runtime/agent/orchestrator/tools/builtin/binary-file.mjs +92 -19
- package/src/runtime/agent/orchestrator/tools/builtin/fs-reachability.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +80 -30
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +41 -22
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +55 -11
- package/src/runtime/agent/orchestrator/tools/code-graph/search-references.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +55 -18
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -0
- package/src/runtime/agent/orchestrator/tools/env-scrub.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/lib/pwsh-standby-pool.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +67 -21
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/patch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -1
- package/src/runtime/channels/lib/config.mjs +33 -33
- package/src/runtime/channels/lib/inbound-handler.mjs +7 -7
- 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/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 +72 -211
- package/src/runtime/memory/lib/agent-ipc.mjs +9 -8
- package/src/runtime/memory/lib/cycle-llm-adapters.mjs +2 -2
- package/src/runtime/memory/lib/http-router.mjs +1 -6
- package/src/runtime/memory/lib/memory-config-flags.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2-shared.mjs +4 -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/session-ingest.mjs +1 -1
- package/src/runtime/search/index.mjs +2 -2
- package/src/runtime/shared/abort-race.mjs +70 -0
- package/src/runtime/shared/child-spawn-gate.mjs +101 -63
- package/src/runtime/shared/config.mjs +11 -24
- 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 +6 -5
- 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 -22
- package/src/runtime/shared/tool-workload-gates.mjs +29 -0
- package/src/runtime/shared/turn-snapshot.mjs +17 -2
- package/src/session-runtime/channel-config-api.mjs +5 -5
- 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/prewarm.mjs +1 -1
- 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 +52 -28
- package/src/session-runtime/self-update.mjs +1 -1
- package/src/session-runtime/session-lifecycle.mjs +36 -15
- package/src/session-runtime/session-title.mjs +1 -1
- package/src/session-runtime/session-turn-api.mjs +55 -15
- package/src/session-runtime/settings-api.mjs +7 -7
- package/src/standalone/agent-dispatch-broker.mjs +1 -1
- package/src/standalone/channel-admin.mjs +42 -53
- package/src/standalone/{channel-daemon-client.mjs → channel-client.mjs} +16 -18
- package/src/standalone/{channel-daemon-transport.mjs → channel-transport.mjs} +45 -46
- package/src/standalone/channel-worker.mjs +124 -647
- package/src/standalone/{backend-daemon.mjs → daemon.mjs} +187 -132
- package/src/standalone/memory-runtime-proxy.mjs +58 -1
- package/src/standalone/{engine-daemon-client.mjs → session-client.mjs} +151 -86
- package/src/standalone/session-protocol.mjs +8 -4
- 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} +170 -157
- package/src/standalone/{engine-daemon-transport.mjs → session-transport.mjs} +139 -61
- package/src/standalone/session-wire.mjs +55 -0
- package/src/tui/App.jsx +4 -4
- package/src/tui/app/app-format.mjs +1 -1
- package/src/tui/app/channel-pickers.mjs +41 -41
- package/src/tui/app/input-parsers.mjs +2 -2
- package/src/tui/app/onboarding-steps.mjs +2 -2
- package/src/tui/app/project-picker.mjs +8 -8
- package/src/tui/app/prompt-submit.mjs +8 -8
- package/src/tui/app/settings-picker.mjs +25 -25
- package/src/tui/app/use-prompt-queue-history.mjs +1 -1
- package/src/tui/app/use-transcript-scroll.mjs +1 -1
- package/src/tui/components/ToolExecution.jsx +1 -1
- package/src/tui/components/prompt-input/restore-policy.mjs +1 -1
- package/src/tui/components/tool-execution/surface-detail.mjs +1 -1
- package/src/tui/dist/index.mjs +154 -127
- package/src/tui/hooks/useSession.mjs +16 -0
- package/src/tui/index.jsx +11 -11
- 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 +7 -7
- 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 -17
- package/src/tui/{engine → session}/session-api.mjs +7 -60
- package/src/tui/{engine → session}/session-flow.mjs +1 -1
- 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 +4 -4
- package/src/tui/{engine → session}/tool-result-status.mjs +5 -5
- package/src/tui/{engine → session}/tool-result-text.mjs +3 -3
- package/src/tui/{engine → session}/transcript-spill.mjs +34 -34
- package/src/tui/{engine → session}/turn.mjs +29 -107
- package/src/tui/{engine-local-session.mjs → session-local.mjs} +59 -75
- package/src/tui/session.mjs +22 -0
- 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-protocol.mjs +0 -32
- 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';
|
|
@@ -472,7 +472,7 @@ export async function runTui({ provider, model, toolMode, remote, forceOnboardin
|
|
|
472
472
|
|
|
473
473
|
// Enter the alternate screen buffer before session/runtime boot so no stale
|
|
474
474
|
// shell rows are visible while the statusline/TUI data warms up. The first
|
|
475
|
-
// real Ink frame may still arrive after
|
|
475
|
+
// real Ink frame may still arrive after createSessionRuntime(), but the user
|
|
476
476
|
// sees a clean fullscreen surface immediately instead of the previous terminal
|
|
477
477
|
// contents bleeding through the bottom status area.
|
|
478
478
|
process.stdout.write(`${TERMINAL_MODE_RESET_HIDDEN_CURSOR}\x1b[?1049h${TERMINAL_MODE_RESET_HIDDEN_CURSOR}\x1b[2J\x1b[H`);
|
|
@@ -488,10 +488,10 @@ export async function runTui({ provider, model, toolMode, remote, forceOnboardin
|
|
|
488
488
|
|
|
489
489
|
process.on('exit', restoreTerminal);
|
|
490
490
|
|
|
491
|
-
//
|
|
491
|
+
// Session runtime startup is independent from palette selection. Start it now and
|
|
492
492
|
// overlap its runtime import/config work with the theme read, while still
|
|
493
493
|
// awaiting the theme before the first React frame.
|
|
494
|
-
const storeOutcomePromise =
|
|
494
|
+
const storeOutcomePromise = createSessionRuntime({ provider, model, toolMode, remote })
|
|
495
495
|
.then((store) => ({ store, error: null }), (error) => ({ store: null, error }));
|
|
496
496
|
|
|
497
497
|
// Apply the persisted UI theme (ui.theme in mixdog-config.json) before the
|
|
@@ -501,7 +501,7 @@ export async function runTui({ provider, model, toolMode, remote, forceOnboardin
|
|
|
501
501
|
try { await loadThemeSettingFromConfig(); } catch { /* default theme stays */ }
|
|
502
502
|
emitTerminalBackground(theme.background);
|
|
503
503
|
|
|
504
|
-
// Static splash + loading spinner while
|
|
504
|
+
// Static splash + loading spinner while createSessionRuntime() warms up
|
|
505
505
|
// (config, providers, sessions). Without this the user stares at an empty
|
|
506
506
|
// alt screen for the whole boot, then the UI "pops in" — the splash makes
|
|
507
507
|
// the first ink frame an in-place repaint instead.
|
|
@@ -518,13 +518,13 @@ export async function runTui({ provider, model, toolMode, remote, forceOnboardin
|
|
|
518
518
|
try {
|
|
519
519
|
await store.touchProjectSelected(store.getState?.()?.cwd || process.cwd());
|
|
520
520
|
} catch { /* best-effort recency */ }
|
|
521
|
-
// 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
|
|
522
522
|
// theme methods would run inside the daemon, so the picker saw a promise
|
|
523
523
|
// instead of the list and a switch repainted nothing here. Bind the three
|
|
524
524
|
// theme methods locally (same pattern as the ink selection helpers below)
|
|
525
|
-
// and mirror the switch to the
|
|
525
|
+
// and mirror the switch to the session runtime with persist:false, purely so its
|
|
526
526
|
// themeEpoch bump re-renders this tree.
|
|
527
|
-
if (store.
|
|
527
|
+
if (store.isRemoteSession === true) {
|
|
528
528
|
const remoteSetTheme = store.setTheme;
|
|
529
529
|
store.listThemes = () => listThemes();
|
|
530
530
|
store.getTheme = () => getThemeSetting();
|
|
@@ -547,7 +547,7 @@ export async function runTui({ provider, model, toolMode, remote, forceOnboardin
|
|
|
547
547
|
process.stderr.write(`mixdog: ${error?.message || error}\n`);
|
|
548
548
|
return 1;
|
|
549
549
|
}
|
|
550
|
-
// The
|
|
550
|
+
// The session runtime store is a daemon-backed Proxy: every method it does not
|
|
551
551
|
// implement locally comes back as an ASYNC remote call. A synchronous
|
|
552
552
|
// getOnboardingStatus() probe inside App therefore sees a Promise, never
|
|
553
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;
|
|
@@ -711,10 +711,10 @@ export function createLiveShare({
|
|
|
711
711
|
}
|
|
712
712
|
|
|
713
713
|
/**
|
|
714
|
-
* ONE viewer-submit intake for EVERY
|
|
715
|
-
* submit(); the daemon awaits submitAsync() (
|
|
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
716
|
* Wrapping only one of them let the other path enqueue the prompt on the VIEWER
|
|
717
|
-
*
|
|
717
|
+
* session runtime, so that surface rendered its own local user row beside the owner's
|
|
718
718
|
* mirrored one — the same message twice on screen
|
|
719
719
|
* (user: 데스크탑에서만 입력이 두 개로 들어간다).
|
|
720
720
|
*
|
|
@@ -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,
|
|
@@ -633,7 +636,7 @@ export function createEngineApiB(bag) {
|
|
|
633
636
|
return await runtime.getUsageDashboard?.(options);
|
|
634
637
|
},
|
|
635
638
|
consumeCodexRateLimitResetCredit: async (options = {}) => {
|
|
636
|
-
// Desktop capability parity: without this delegation the
|
|
639
|
+
// Desktop capability parity: without this delegation the session runtime surface
|
|
637
640
|
// rejects the sidebar's reset-credit invoke as unsupported even though
|
|
638
641
|
// the session runtime implements it.
|
|
639
642
|
if (typeof runtime.consumeCodexRateLimitResetCredit !== 'function') {
|
|
@@ -641,8 +644,14 @@ export function createEngineApiB(bag) {
|
|
|
641
644
|
}
|
|
642
645
|
return await runtime.consumeCodexRateLimitResetCredit(options);
|
|
643
646
|
},
|
|
644
|
-
getTurnReviewDiff: async () => {
|
|
645
|
-
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);
|
|
646
655
|
},
|
|
647
656
|
getOnboardingStatus: () => {
|
|
648
657
|
return runtime.getOnboardingStatus?.() || { completed: true, workflowRoutes: {} };
|
|
@@ -679,13 +688,16 @@ export function createEngineApiB(bag) {
|
|
|
679
688
|
if (getState().commandBusy) throw new Error('command busy');
|
|
680
689
|
set({ commandBusy: true });
|
|
681
690
|
try {
|
|
682
|
-
const result = await runtime.beginOAuthProviderLogin(provider);
|
|
691
|
+
const result = oauthFlows.register(await runtime.beginOAuthProviderLogin(provider));
|
|
683
692
|
pushNotice(`provider oauth started: ${result.provider}`, 'info');
|
|
684
693
|
return result;
|
|
685
694
|
} finally {
|
|
686
695
|
set({ commandBusy: false });
|
|
687
696
|
}
|
|
688
697
|
},
|
|
698
|
+
getOAuthProviderLoginStatus: (flowId) => oauthFlows.status(flowId),
|
|
699
|
+
completeOAuthProviderLogin: async (flowId, code) => oauthFlows.complete(flowId, code),
|
|
700
|
+
cancelOAuthProviderLogin: async (flowId) => oauthFlows.cancel(flowId),
|
|
689
701
|
saveProviderApiKey: (provider, secret) => {
|
|
690
702
|
const result = runtime.saveProviderApiKey(provider, secret);
|
|
691
703
|
pushNotice(`provider api key saved: ${result.provider}`, 'info');
|
|
@@ -738,7 +750,7 @@ export function createEngineApiB(bag) {
|
|
|
738
750
|
return runtime.getChannelSetup();
|
|
739
751
|
},
|
|
740
752
|
getChannelWorkerStatus: () => runtime.getChannelWorkerStatus?.(),
|
|
741
|
-
|
|
753
|
+
setChannelProvider: (name) => runtime.setChannelProvider?.(name),
|
|
742
754
|
saveDiscordToken: (token) => {
|
|
743
755
|
const result = runtime.saveDiscordToken(token);
|
|
744
756
|
pushNotice('discord token saved', 'info');
|
|
@@ -880,7 +892,7 @@ export function createEngineApiB(bag) {
|
|
|
880
892
|
renameSessionTitle: (id, title) => {
|
|
881
893
|
return runtime.renameSessionTitle?.(id, title) ?? false;
|
|
882
894
|
},
|
|
883
|
-
// Desktop sidebar watcher hook: the
|
|
895
|
+
// Desktop sidebar watcher hook: the daemon watches this directory so
|
|
884
896
|
// heartbeat sidecar create/delete pushes instant working/dot updates.
|
|
885
897
|
// Without it the watcher silently no-ops and the sidebar falls back to
|
|
886
898
|
// the 60s safety poll (user: spinner kept spinning after the turn ended).
|
|
@@ -1002,7 +1014,7 @@ export function createEngineApiB(bag) {
|
|
|
1002
1014
|
}
|
|
1003
1015
|
},
|
|
1004
1016
|
// Desktop split panes: read-only transcript + route peek of an idle
|
|
1005
|
-
// session — no resume, no ownership claim, no
|
|
1017
|
+
// session — no resume, no ownership claim, no session runtime state mutation.
|
|
1006
1018
|
// Panes render every visible session as foreground from this frame.
|
|
1007
1019
|
peekSessionTranscript: (id, options = {}) => {
|
|
1008
1020
|
const session = runtime.peekSession?.(id);
|
|
@@ -1028,7 +1040,7 @@ export function createEngineApiB(bag) {
|
|
|
1028
1040
|
},
|
|
1029
1041
|
resume: async (id, options = {}) => {
|
|
1030
1042
|
if (getState().commandBusy) return false;
|
|
1031
|
-
// quiet: viewer-follow refreshes (
|
|
1043
|
+
// quiet: viewer-follow refreshes (session runtime share tick) re-resume on every
|
|
1032
1044
|
// owner turn — they must not flash the "Resuming conversation" status.
|
|
1033
1045
|
set({
|
|
1034
1046
|
commandBusy: true,
|
|
@@ -1095,6 +1107,7 @@ export function createEngineApiB(bag) {
|
|
|
1095
1107
|
try { lifecycle.unsubscribeRemoteState?.(); } catch {}
|
|
1096
1108
|
lifecycle.unsubscribeRemoteState = null;
|
|
1097
1109
|
denyAllToolApprovals('runtime closing');
|
|
1110
|
+
oauthFlows.cancelAll();
|
|
1098
1111
|
await flushTuiSteeringPersist();
|
|
1099
1112
|
await runtime.close(reason, options);
|
|
1100
1113
|
listeners.clear();
|