From d0bf9091f1d31732f2e5e4127e08bb1f04b909b1 Mon Sep 17 00:00:00 2001 From: Turtle Date: Thu, 30 Jul 2026 15:28:58 +0800 Subject: [PATCH] fix(cli): preserve active query access --- .../tests/fixtures/telemetry-otel.cordis.yml | 3 +++ packages/ui/tui/src/index.ts | 18 +++++++++++++----- packages/ui/tui/tests/tui.spec.ts | 9 ++++++--- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/examples/headless-agent/tests/fixtures/telemetry-otel.cordis.yml b/examples/headless-agent/tests/fixtures/telemetry-otel.cordis.yml index 34e23b828e..d9cc454bfb 100644 --- a/examples/headless-agent/tests/fixtures/telemetry-otel.cordis.yml +++ b/examples/headless-agent/tests/fixtures/telemetry-otel.cordis.yml @@ -8,6 +8,9 @@ - id: telemetry-redact-rule name: './telemetry-redact-rule.ts' +- id: subprocess + name: '@deepseek-ai/dsh-subprocess-local' + - id: bash name: '@deepseek-ai/dsh-bash-local' diff --git a/packages/ui/tui/src/index.ts b/packages/ui/tui/src/index.ts index bca49abae6..7e7e7dec13 100644 --- a/packages/ui/tui/src/index.ts +++ b/packages/ui/tui/src/index.ts @@ -19,7 +19,7 @@ import { type SlashCommand, type TerminalColorScheme, } from '@earendil-works/pi-tui' -import { Service, type Context, type Fiber } from 'cordis' +import { Service, type Context, type Fiber, type FiberState } from 'cordis' import { assembleContextFor, installAgentLlmTarget, @@ -55,6 +55,7 @@ import { TuiExtensionServiceImpl, TuiOverlayManager, } from './extension/overlay-manager.ts' + import { parseTuiPromptTemplate, renderTuiPromptTemplate, @@ -170,6 +171,9 @@ export type { TuiViewport, } from './extension/types.ts' +/** First terminal Cordis state: FAILED, DISPOSED, and UNLOADING are unusable. */ +const FIBER_FAILED = 3 as FiberState.FAILED + declare module 'cordis' { interface Context { /** Terminal-only interaction service, available only while a TUI is mounted. */ @@ -840,10 +844,14 @@ export function createTuiChat( resolved, palette, overlayManager, - // Optional and independently mounted: read at each use so config row order - // cannot decide whether /resume works. Strict lookup excludes a closing or - // closed provider rather than dispatching into a stale SQLite handle. - sessionQuery: () => ctx.get('sessionQuery'), + // Optional and independently mounted. Cordis transiently leaves this sibling + // non-ACTIVE during command callbacks, so the non-strict read is intentional; + // terminal fiber states still exclude failed, closing, and closed providers. + sessionQuery: () => { + const implementation = ctx.reflect._getImpl('sessionQuery', false) + if (implementation === undefined || implementation.fiber.state >= FIBER_FAILED) return undefined + return ctx.get('sessionQuery', false) + }, ui, editor, appendNotice, diff --git a/packages/ui/tui/tests/tui.spec.ts b/packages/ui/tui/tests/tui.spec.ts index 3b2050f92e..942f059162 100644 --- a/packages/ui/tui/tests/tui.spec.ts +++ b/packages/ui/tui/tests/tui.spec.ts @@ -2,7 +2,7 @@ import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises' import { homedir, tmpdir } from 'node:os' import { join, resolve } from 'node:path' import { describe, expect, it, vi } from 'vitest' -import { Context } from 'cordis' +import { Context, type FiberState } from 'cordis' import { CombinedAutocompleteProvider, visibleWidth, type Terminal } from '@earendil-works/pi-tui' import AgentRegistry, { agentEvents, assembleContextFor, InboxItemId, type Agent, type InboxItem, @@ -503,7 +503,7 @@ describe('goodbye message and /resume', () => { await dispose(result) }) - it('treats a closed session-query provider as unavailable', async () => { + it('treats a terminal-state session-query provider as unavailable', async () => { let queryCtx: Context | undefined const result = await setup({ cwd: '/workspace', @@ -518,12 +518,15 @@ describe('goodbye message and /resume', () => { }) }, }) - await queryCtx!.fiber.dispose() + if (queryCtx === undefined) throw new Error('query provider did not mount') + const activeState = queryCtx.fiber.state + queryCtx.fiber.state = 5 result.terminal.send('/resume') result.terminal.send('\r') await tick() expect(result.terminal.output).toContain('session query is not mounted') expect(result.terminal.output).not.toContain('closed database') + queryCtx.fiber.state = activeState await dispose(result) })