|
|
@@ -32,7 +32,6 @@ import type {} from '@deepseek-ai/dsh-token-meter'
|
|
|
import type { CommandResult } from '@deepseek-ai/dsh-commands'
|
|
|
import { createUserMessage, errorChain } from '@deepseek-ai/dsh-llm'
|
|
|
import type { ContentBlock, MessageId } from '@deepseek-ai/dsh-llm'
|
|
|
-import { renderUnknownXml } from './components/xml-tool-output.ts'
|
|
|
import type {} from '@deepseek-ai/dsh-llm-retry'
|
|
|
import { renderPrompt } from '@deepseek-ai/dsh-system-prompt'
|
|
|
import {
|
|
|
@@ -67,7 +66,7 @@ import type {
|
|
|
TuiTheme,
|
|
|
} from './extension/types.ts'
|
|
|
import { displayInlineText, displayText } from './components/text.ts'
|
|
|
-import { createPalette, markdownTheme, selectTheme } from './components/theme.ts'
|
|
|
+import { createPalette, markdownTheme, renderPalette, selectTheme } from './components/theme.ts'
|
|
|
import { contentText, parseArguments } from './components/content.ts'
|
|
|
import {
|
|
|
cacheHitRate,
|
|
|
@@ -92,6 +91,8 @@ import {
|
|
|
type Config,
|
|
|
} from './config.ts'
|
|
|
import {
|
|
|
+ ContextCardComponent,
|
|
|
+ type ToolCardVisibility,
|
|
|
HeaderComponent,
|
|
|
StreamingAssistantComponent,
|
|
|
ToolCardComponent,
|
|
|
@@ -175,9 +176,68 @@ declare module 'cordis' {
|
|
|
tui: TuiExtensionService
|
|
|
/** Optional process host that can replace this TUI with a resumed session. */
|
|
|
tuiResumeHost: TuiResumeHost
|
|
|
+ /** Launcher-owned `main` session identity; absent lets the app mint one. */
|
|
|
+ mainSessionId: MainSessionIdentity | undefined
|
|
|
+ /** Line the launcher wants printed on exit; absent prints nothing. */
|
|
|
+ tuiGoodbyeMessage: string | undefined
|
|
|
+ /** Skill the launcher wants auto-invoked as the fresh session's first turn; absent leaves it to the user. */
|
|
|
+ tuiInitialSkill: string | undefined
|
|
|
+ /** Launcher-owned session-store root the app bundle defaults to; absent keeps the bundle's project-local default. */
|
|
|
+ launcherSessionsRoot: string | undefined
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/** Launcher-chosen identity for the app's `main` session. */
|
|
|
+export interface MainSessionIdentity {
|
|
|
+ /** Exact session id `main` binds to. */
|
|
|
+ readonly id: SessionId
|
|
|
+ /**
|
|
|
+ * Whether that session already has persisted history to load. `true` requires
|
|
|
+ * an existing log and fails loud when absent; `false` creates it fresh.
|
|
|
+ */
|
|
|
+ readonly resume: boolean
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Context key a launcher sets before any Loader entry mounts
|
|
|
+ * (`ctx.provide(MAIN_SESSION_ID_KEY, identity)`) to fix the `main` agent's
|
|
|
+ * session identity, so an app bundle mounted from a `cordis.yml` binds a
|
|
|
+ * launcher-selected session without a config key. `ctx.provide` is the only
|
|
|
+ * channel from launcher argv into a Loader-mounted plugin, because config
|
|
|
+ * `!!js` expressions evaluate against the entry's context. Absent leaves the
|
|
|
+ * choice to the app.
|
|
|
+ */
|
|
|
+export const MAIN_SESSION_ID_KEY = 'mainSessionId'
|
|
|
+
|
|
|
+/**
|
|
|
+ * Context key a launcher sets before any Loader entry mounts
|
|
|
+ * (`ctx.provide(TUI_GOODBYE_MESSAGE_KEY, line)`) to supply the line the TUI
|
|
|
+ * prints once the terminal is released on exit — for the shipped CLI, the
|
|
|
+ * command that resumes this session. The launcher owns the wording because only
|
|
|
+ * it knows how it was invoked; the TUI escapes terminal controls before
|
|
|
+ * rendering. Absent prints nothing.
|
|
|
+ */
|
|
|
+export const TUI_GOODBYE_MESSAGE_KEY = 'tuiGoodbyeMessage'
|
|
|
+
|
|
|
+/**
|
|
|
+ * Context key a launcher sets before any Loader entry mounts
|
|
|
+ * (`ctx.provide(INITIAL_SKILL_KEY, name)`) to seed a fresh session's first user
|
|
|
+ * turn with `/skill:<name>` — the `dsh migrate`/`dsh upgrade` guided-session
|
|
|
+ * entry. The launcher sets it only when minting a fresh session, so it never
|
|
|
+ * re-fires on a resumed one. Absent leaves the first turn to the user.
|
|
|
+ */
|
|
|
+export const INITIAL_SKILL_KEY = 'tuiInitialSkill'
|
|
|
+
|
|
|
+/**
|
|
|
+ * Context key a launcher sets before any Loader entry mounts
|
|
|
+ * (`ctx.provide(SESSIONS_ROOT_KEY, root)`) to supply its session-store root as
|
|
|
+ * the app bundle's default persistence root. Shared-store policy (one store
|
|
|
+ * across every cwd) belongs to the launcher — the dsh CLI resolves it under the
|
|
|
+ * Harness home — never to a plugin; a bundle without this slot keeps its own
|
|
|
+ * project-local default, and an explicit `persistenceRoot` config still wins.
|
|
|
+ */
|
|
|
+export const SESSIONS_ROOT_KEY = 'launcherSessionsRoot'
|
|
|
+
|
|
|
/**
|
|
|
* Optional terminal-local interaction service provided by one mounted TUI.
|
|
|
*
|
|
|
@@ -246,7 +306,6 @@ export function createTuiChat(
|
|
|
const sessionId = SessionId(config.sessionId ?? 'main')
|
|
|
const agent = ctx.agents.get(sessionId)
|
|
|
if (agent === undefined) throw new Error(`ui-tui: session "${sessionId}" is not running`)
|
|
|
- const persistence = ctx.get('sessionPersistence')
|
|
|
const sessionQuery = ctx.get('sessionQuery')
|
|
|
const resolved = resolveTuiConfig(config)
|
|
|
const palette = createPalette(resolved.theme.color)
|
|
|
@@ -271,7 +330,9 @@ export function createTuiChat(
|
|
|
editor.hintPrefix = initialInputPrompt
|
|
|
const todo = new TodoComponent(palette)
|
|
|
let showReasoning = resolved.showReasoning
|
|
|
- let toolsExpanded = false
|
|
|
+ // Ctrl+O cycles collapsed -> expanded -> hidden. Codex-style: hidden drops
|
|
|
+ // tool cards entirely, collapsed previews, expanded shows full bodies.
|
|
|
+ let toolsVisibility: ToolCardVisibility = 'collapsed'
|
|
|
let streaming: StreamingAssistantComponent | undefined
|
|
|
let completedStreaming: StreamingAssistantComponent | undefined
|
|
|
let runningStatus: RunningStatus | undefined
|
|
|
@@ -295,6 +356,7 @@ export function createTuiChat(
|
|
|
const tokens = sessionTokens(agent.session)
|
|
|
const toolCards = new Map<string, ToolCardComponent>()
|
|
|
const allToolCards = new Set<ToolCardComponent>()
|
|
|
+ const contextCards = new Set<ContextCardComponent>()
|
|
|
const liveErrors = new Set<string>()
|
|
|
const commandControllers = new Set<AbortController>()
|
|
|
const referenceControllers = new Set<AbortController>()
|
|
|
@@ -323,33 +385,33 @@ export function createTuiChat(
|
|
|
const branch = runtime.gitBranch?.(cwd) ?? gitBranch(cwd)
|
|
|
const promptValues: TuiPromptValueHandle[] = [
|
|
|
ctx.tuiPrompt.register('cwd', palette.bold(palette.accent(formattedCwd))),
|
|
|
- ctx.tuiPrompt.register('git/worktree', branch === undefined ? undefined : palette.muted(` (${displayText(branch)})`)),
|
|
|
+ ctx.tuiPrompt.register('git/worktree', branch === undefined ? undefined : palette.dim(` (${displayText(branch)})`)),
|
|
|
ctx.tuiPrompt.register('token_meter/cache_hit_rate'),
|
|
|
ctx.tuiPrompt.register('model'),
|
|
|
ctx.tuiPrompt.register('context'),
|
|
|
- ctx.tuiPrompt.register('timing'),
|
|
|
+ ctx.tuiPrompt.register('queued'),
|
|
|
ctx.tuiPrompt.register('symbol', palette.bold(palette.accent('dsh'))),
|
|
|
- ctx.tuiPrompt.register('indicator', palette.muted('> ')),
|
|
|
+ ctx.tuiPrompt.register('indicator', palette.dim('> ')),
|
|
|
]
|
|
|
- const [cwdValue, gitValue, tokenValue, modelValue, contextValue, timingValue, symbolValue, indicatorValue] = promptValues
|
|
|
+ const [cwdValue, gitValue, tokenValue, modelValue, contextValue, queuedValue, symbolValue, indicatorValue] = promptValues
|
|
|
/* v8 ignore next -- the fixed built-in registration list always supplies each handle. */
|
|
|
if (cwdValue === undefined || gitValue === undefined || tokenValue === undefined || modelValue === undefined
|
|
|
- || contextValue === undefined || timingValue === undefined || symbolValue === undefined || indicatorValue === undefined) {
|
|
|
+ || contextValue === undefined || queuedValue === undefined || symbolValue === undefined || indicatorValue === undefined) {
|
|
|
throw new Error('TUI prompt built-ins failed to initialize')
|
|
|
}
|
|
|
const updatePromptValues = (): void => {
|
|
|
cwdValue.set(palette.bold(palette.accent(formattedCwd)))
|
|
|
- gitValue.set(branch === undefined ? undefined : palette.muted(` (${displayText(branch)})`))
|
|
|
+ gitValue.set(branch === undefined ? undefined : palette.dim(` (${displayText(branch)})`))
|
|
|
const rate = cacheHitRate(tokens)
|
|
|
const usage = `↑${formatTokens(tokens.input)} ↓${formatTokens(tokens.output)}`
|
|
|
- modelValue.set(` ${palette.muted(displayText(target.current === undefined ? 'model unset' : compactTargetLabel(target.current)))}`)
|
|
|
- tokenValue.set(` ${palette.muted(rate === undefined ? usage : `${usage} cache ${rate}%`)}`)
|
|
|
+ modelValue.set(` ${palette.dim(displayText(target.current === undefined ? 'model unset' : compactTargetLabel(target.current)))}`)
|
|
|
+ tokenValue.set(` ${palette.dim(rate === undefined ? usage : `${usage} cache ${rate}%`)}`)
|
|
|
const contextWindow = modelController.contextWindow()
|
|
|
- contextValue.set(contextWindow === undefined ? undefined : ` ${palette.muted(
|
|
|
+ contextValue.set(contextWindow === undefined ? undefined : ` ${palette.dim(
|
|
|
`${Math.min(100, Math.round(ctx.tokenMeter.measure(agent.session).totalTokens / contextWindow * 100))}% context`,
|
|
|
)}`)
|
|
|
const queued = runningStatus === undefined ? undefined : formatQueuedStatus(pendingSteering.size)
|
|
|
- timingValue.set(queued === undefined ? undefined : palette.dim(queued))
|
|
|
+ queuedValue.set(queued === undefined ? undefined : palette.dim(queued))
|
|
|
symbolValue.set(palette.bold(palette.accent('dsh')))
|
|
|
// `${indicator}` owns the caret column and its trailing gap before the
|
|
|
// cursor. The phase glyph replaces the `>` caret in place — same width
|
|
|
@@ -370,7 +432,7 @@ export function createTuiChat(
|
|
|
? { glyph: fadingStatus.glyph, level: Math.max(0, 1 - (now() - fadingStatus.endedAt) / STATUS_FADE_MS) }
|
|
|
: undefined
|
|
|
const caret = envelope === undefined
|
|
|
- ? palette.muted('>')
|
|
|
+ ? palette.dim('>')
|
|
|
: fadeGlyph(
|
|
|
envelope.glyph,
|
|
|
palette,
|
|
|
@@ -379,7 +441,7 @@ export function createTuiChat(
|
|
|
envelope.level * pulseLevel(now()),
|
|
|
envelope.level >= 0.5,
|
|
|
)
|
|
|
- indicatorValue.set(`${caret}${palette.muted(' ')}`)
|
|
|
+ indicatorValue.set(`${caret}${palette.dim(' ')}`)
|
|
|
}
|
|
|
const promptContext = new PromptContextComponent(
|
|
|
parseTuiPromptTemplate(displayInlineText(resolved.theme.leftPrompt)),
|
|
|
@@ -416,7 +478,7 @@ export function createTuiChat(
|
|
|
const disposePromptChanges = ctx.tuiPrompt.subscribe(requestRender)
|
|
|
|
|
|
const appendNotice = (message: string, kind: 'info' | 'warning' | 'error' = 'info'): void => {
|
|
|
- const color = kind === 'error' ? palette.error : kind === 'warning' ? palette.warning : palette.muted
|
|
|
+ const color = kind === 'error' ? palette.error : kind === 'warning' ? palette.warning : palette.dim
|
|
|
chat.addChild(new Spacer(1))
|
|
|
chat.addChild(new Text(color(displayText(message)), 0, 0))
|
|
|
requestRender()
|
|
|
@@ -424,7 +486,6 @@ export function createTuiChat(
|
|
|
|
|
|
const extensionTheme: TuiTheme = Object.freeze({
|
|
|
text: (value: string) => palette.text(value),
|
|
|
- muted: (value: string) => palette.muted(value),
|
|
|
dim: (value: string) => palette.dim(value),
|
|
|
accent: (value: string) => palette.accent(value),
|
|
|
success: (value: string) => palette.success(value),
|
|
|
@@ -547,7 +608,7 @@ export function createTuiChat(
|
|
|
palette,
|
|
|
mdTheme,
|
|
|
)
|
|
|
- card.setExpanded(toolsExpanded)
|
|
|
+ card.setVisibility(toolsVisibility)
|
|
|
toolCards.set(event.data.callId, card)
|
|
|
allToolCards.add(card)
|
|
|
return card
|
|
|
@@ -626,22 +687,18 @@ export function createTuiChat(
|
|
|
/* v8 ignore next -- context events with empty content are rejected by their owning producers. */
|
|
|
if (text) {
|
|
|
// The tui type view lacks plugin-augmented source kinds (e.g. goal),
|
|
|
- // so read the display label without narrowing on `kind`.
|
|
|
- const labelled = source as { kind: string; plugin?: string }
|
|
|
- /* v8 ignore next -- current plugin-augmented context sources always carry their display label. */
|
|
|
- const label = labelled.plugin ?? labelled.kind
|
|
|
- const xml = renderUnknownXml(
|
|
|
- text,
|
|
|
- resolved.maxToolOutputLines,
|
|
|
- true,
|
|
|
- displayText,
|
|
|
- value => palette.muted(value),
|
|
|
- /* v8 ignore next -- expanded context XML never asks renderUnknownXml for a collapsed summary. */
|
|
|
- () => '',
|
|
|
- )
|
|
|
+ // so read the display label without narrowing on `kind`. The session
|
|
|
+ // log is a durable/replay boundary: a corrupt or foreign injected
|
|
|
+ // source may not match the typed shape, so fall back to `context`.
|
|
|
+ const labelled = source as { kind?: unknown; plugin?: unknown }
|
|
|
+ const label = typeof labelled.plugin === 'string' ? labelled.plugin
|
|
|
+ : typeof labelled.kind === 'string' ? labelled.kind
|
|
|
+ : 'context'
|
|
|
+ const card = new ContextCardComponent(label, text, resolved.maxToolOutputLines, palette)
|
|
|
+ card.setExpanded(toolsVisibility === 'expanded')
|
|
|
+ contextCards.add(card)
|
|
|
chat.addChild(new Spacer(1))
|
|
|
- chat.addChild(new Text(palette.dim(`Context · ${displayText(label)}`), 0, 0))
|
|
|
- chat.addChild(new Text(xml?.join('\n') ?? palette.muted(displayText(text)), 0, 0))
|
|
|
+ chat.addChild(card)
|
|
|
}
|
|
|
break
|
|
|
}
|
|
|
@@ -681,8 +738,9 @@ export function createTuiChat(
|
|
|
)
|
|
|
break
|
|
|
}
|
|
|
+ // No external Spacer for tool cards: the card renders its own leading
|
|
|
+ // gap, so the hidden state removes the row and the gap together.
|
|
|
case 'tool/call':
|
|
|
- chat.addChild(new Spacer(1))
|
|
|
chat.addChild(parsedTool(event))
|
|
|
trailStreamingTiming()
|
|
|
break
|
|
|
@@ -691,7 +749,7 @@ export function createTuiChat(
|
|
|
let card = toolCards.get(callId)
|
|
|
if (card === undefined) {
|
|
|
card = new ToolCardComponent('tool', { value: {}, valid: true }, undefined, resolved.maxToolOutputLines, palette, mdTheme)
|
|
|
- chat.addChild(new Spacer(1))
|
|
|
+ card.setVisibility(toolsVisibility)
|
|
|
chat.addChild(card)
|
|
|
allToolCards.add(card)
|
|
|
}
|
|
|
@@ -762,6 +820,7 @@ export function createTuiChat(
|
|
|
chat.clear()
|
|
|
toolCards.clear()
|
|
|
allToolCards.clear()
|
|
|
+ contextCards.clear()
|
|
|
streaming = undefined
|
|
|
todo.update([])
|
|
|
const active = activeSurfaceSeqs(agent.session)
|
|
|
@@ -790,12 +849,10 @@ export function createTuiChat(
|
|
|
const resume = createResumeController({
|
|
|
ctx,
|
|
|
agent,
|
|
|
- config,
|
|
|
runtime,
|
|
|
resolved,
|
|
|
palette,
|
|
|
overlayManager,
|
|
|
- persistence,
|
|
|
sessionQuery,
|
|
|
ui,
|
|
|
editor,
|
|
|
@@ -824,9 +881,8 @@ export function createTuiChat(
|
|
|
await runtime.terminal.drainInput(100, 20)
|
|
|
ui.stop()
|
|
|
if (exitProcess) {
|
|
|
- const command = await resume.currentResumeCommand()
|
|
|
- if (command !== undefined) {
|
|
|
- runtime.terminal.write(`${palette.muted('To resume this session:')} ${displayText(command)}\n`)
|
|
|
+ if (runtime.goodbyeMessage !== undefined) {
|
|
|
+ runtime.terminal.write(`${palette.dim(displayText(runtime.goodbyeMessage))}\n`)
|
|
|
}
|
|
|
runtime.exit(0)
|
|
|
}
|
|
|
@@ -870,9 +926,15 @@ export function createTuiChat(
|
|
|
ui.queryTerminalColorScheme({ timeoutMs: 2000 }).catch(() => {})
|
|
|
|
|
|
const toggleTools = (): void => {
|
|
|
- toolsExpanded = !toolsExpanded
|
|
|
- for (const card of allToolCards) card.setExpanded(toolsExpanded)
|
|
|
- appendNotice(`Tool cards ${toolsExpanded ? 'expanded' : 'collapsed'}.`)
|
|
|
+ // The cycle order puts the two common reading modes adjacent: preview ->
|
|
|
+ // full detail -> conversation-only, then back to the preview default.
|
|
|
+ toolsVisibility = toolsVisibility === 'collapsed' ? 'expanded'
|
|
|
+ : toolsVisibility === 'expanded' ? 'hidden' : 'collapsed'
|
|
|
+ for (const card of allToolCards) card.setVisibility(toolsVisibility)
|
|
|
+ // Context cards carry injected instructions rather than tool traffic, so
|
|
|
+ // they never hide: the hidden phase reads as their collapsed preview.
|
|
|
+ for (const card of contextCards) card.setExpanded(toolsVisibility === 'expanded')
|
|
|
+ appendNotice(toolsVisibility === 'hidden' ? 'Tool cards hidden.' : `Tool and context cards ${toolsVisibility}.`)
|
|
|
}
|
|
|
|
|
|
const toggleReasoning = (): void => {
|
|
|
@@ -898,12 +960,20 @@ export function createTuiChat(
|
|
|
chat.addChild(new Text(palette.bold(palette.accent('Keyboard shortcuts')), 0, 0))
|
|
|
chat.addChild(new Text([
|
|
|
'Enter send • Shift/Alt+Enter newline • Up/Down prompt history',
|
|
|
- 'Esc cancel active turn • Ctrl+O toggle tool cards • Ctrl+R toggle reasoning',
|
|
|
+ 'Esc cancel turn • Ctrl+O cycle cards (collapse/expand/hide) • Ctrl+R toggle reasoning • Ctrl+L redraw',
|
|
|
'Ctrl+C cancel while running; clear input or exit while idle • Ctrl+D exit',
|
|
|
'',
|
|
|
...commandLines,
|
|
|
'/skill:<name> [instructions] — load a skill into the conversation',
|
|
|
- ].map(line => palette.muted(line)).join('\n'), 0, 0))
|
|
|
+ ].map(line => palette.dim(line)).join('\n'), 0, 0))
|
|
|
+ requestRender()
|
|
|
+ }
|
|
|
+
|
|
|
+ const showPalette = (): void => {
|
|
|
+ chat.addChild(new Spacer(1))
|
|
|
+ chat.addChild(new Text(
|
|
|
+ renderPalette(palette, currentScheme, resolved.theme.color).join('\n'), 0, 0,
|
|
|
+ ))
|
|
|
requestRender()
|
|
|
}
|
|
|
|
|
|
@@ -1050,19 +1120,9 @@ export function createTuiChat(
|
|
|
handler: () => { chat.clear(); requestRender(); return { kind: 'success' } },
|
|
|
})
|
|
|
commandCtx.commands.register({
|
|
|
- name: 'reasoning',
|
|
|
- description: 'Toggle reasoning blocks',
|
|
|
- handler: () => { toggleReasoning(); return { kind: 'success' } },
|
|
|
- })
|
|
|
- commandCtx.commands.register({
|
|
|
- name: 'tools',
|
|
|
- description: 'Expand or collapse all tool cards',
|
|
|
- handler: () => { toggleTools(); return { kind: 'success' } },
|
|
|
- })
|
|
|
- commandCtx.commands.register({
|
|
|
- name: 'redraw',
|
|
|
- description: 'Invalidate components and redraw the terminal',
|
|
|
- handler: () => { ui.invalidate(); ui.requestRender(true); return { kind: 'success' } },
|
|
|
+ name: 'palette',
|
|
|
+ description: 'Show every color and attribute role this terminal renders',
|
|
|
+ handler: () => { showPalette(); return { kind: 'success' } },
|
|
|
})
|
|
|
commandCtx.commands.register({
|
|
|
name: 'reload',
|
|
|
@@ -1504,6 +1564,13 @@ export function createTuiChat(
|
|
|
})
|
|
|
startBannerReveal()
|
|
|
|
|
|
+ // A launcher-seeded first turn (`dsh migrate`/`dsh upgrade`): invoke the
|
|
|
+ // named skill exactly as a typed `/skill:<name>` would, once the chat is live
|
|
|
+ // and the agent is idle. The launcher sets this only for a fresh session, so
|
|
|
+ // there is no prior turn to collide with; invokeSkill reports an unknown skill
|
|
|
+ // as a notice.
|
|
|
+ if (config.initialSkill !== undefined) invokeSkill(config.initialSkill, '')
|
|
|
+
|
|
|
return {
|
|
|
async dispose(): Promise<void> {
|
|
|
detachListeners()
|
|
|
@@ -1567,10 +1634,20 @@ export function apply(ctx: Context, config: Config): void {
|
|
|
// boundary from COLORTERM; an explicit theme value still wins.
|
|
|
const truecolor = config.theme?.truecolor ?? ['truecolor', '24bit'].includes(process.env.COLORTERM ?? '')
|
|
|
const resumeHost = ctx.get('tuiResumeHost')
|
|
|
- mountTui(ctx, Object.assign({}, config, { theme: Object.assign({}, config.theme, { truecolor }) }), {
|
|
|
+ const goodbyeMessage = ctx.get('tuiGoodbyeMessage')
|
|
|
+ // The launcher seeds a guided fresh session's first turn through this key; a
|
|
|
+ // config value still wins. Consumed in createTuiChat via config.initialSkill.
|
|
|
+ const initialSkill = config.initialSkill ?? ctx.get('tuiInitialSkill')
|
|
|
+ mountTui(ctx, Object.assign(
|
|
|
+ {},
|
|
|
+ config,
|
|
|
+ { theme: Object.assign({}, config.theme, { truecolor }) },
|
|
|
+ initialSkill === undefined ? {} : { initialSkill },
|
|
|
+ ), {
|
|
|
terminal: new ProcessTerminal(),
|
|
|
exit: code => process.exit(code),
|
|
|
- ...resumeHost === undefined ? {} : { handoffResume: sessionId => resumeHost.handoff(sessionId) },
|
|
|
+ ...resumeHost === undefined ? {} : { handoffResume: (sessionId, cwd) => resumeHost.handoff(sessionId, cwd) },
|
|
|
+ ...goodbyeMessage === undefined ? {} : { goodbyeMessage },
|
|
|
})
|
|
|
}
|
|
|
/* v8 ignore stop */
|