@oneworks/cli 0.1.0-alpha.0

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.
Files changed (87) hide show
  1. package/LICENSE +21 -0
  2. package/channel.js +7 -0
  3. package/cli.js +5 -0
  4. package/mem.js +7 -0
  5. package/package.json +59 -0
  6. package/postinstall.js +75 -0
  7. package/src/AGENTS.md +169 -0
  8. package/src/channel-cli.ts +19 -0
  9. package/src/cli-argv.ts +27 -0
  10. package/src/cli.ts +63 -0
  11. package/src/commands/@core/adapter-option.ts +85 -0
  12. package/src/commands/@core/extra-options.ts +12 -0
  13. package/src/commands/@core/plugin-install.ts +1 -0
  14. package/src/commands/@core/plugin-source.ts +1 -0
  15. package/src/commands/accounts.ts +204 -0
  16. package/src/commands/adapter/prepare-selection.ts +181 -0
  17. package/src/commands/adapter/prepare.ts +104 -0
  18. package/src/commands/adapter.ts +48 -0
  19. package/src/commands/agent/actions.ts +176 -0
  20. package/src/commands/agent/runtime-store-commands.ts +56 -0
  21. package/src/commands/agent/runtime-store-events.ts +23 -0
  22. package/src/commands/agent/runtime-store-session.ts +170 -0
  23. package/src/commands/agent/runtime-store-shared.ts +139 -0
  24. package/src/commands/agent/runtime-store.ts +4 -0
  25. package/src/commands/agent.ts +81 -0
  26. package/src/commands/benchmark.ts +198 -0
  27. package/src/commands/channel.ts +594 -0
  28. package/src/commands/clear.ts +140 -0
  29. package/src/commands/config/actions.ts +196 -0
  30. package/src/commands/config/display-state.ts +108 -0
  31. package/src/commands/config/index.ts +135 -0
  32. package/src/commands/config/interactive.ts +121 -0
  33. package/src/commands/config/read-state.ts +56 -0
  34. package/src/commands/config/section-state.ts +109 -0
  35. package/src/commands/config/shared.ts +195 -0
  36. package/src/commands/kill.ts +41 -0
  37. package/src/commands/list.ts +224 -0
  38. package/src/commands/memory/context.ts +76 -0
  39. package/src/commands/memory/entries.ts +131 -0
  40. package/src/commands/memory/shared.ts +89 -0
  41. package/src/commands/memory/store.ts +69 -0
  42. package/src/commands/memory/target.ts +54 -0
  43. package/src/commands/memory.ts +97 -0
  44. package/src/commands/plugin.ts +62 -0
  45. package/src/commands/report-targets.ts +149 -0
  46. package/src/commands/report.ts +232 -0
  47. package/src/commands/run/adapter-cli-version.ts +65 -0
  48. package/src/commands/run/command.ts +982 -0
  49. package/src/commands/run/input-bridge.ts +108 -0
  50. package/src/commands/run/input-control.ts +112 -0
  51. package/src/commands/run/input-decision.ts +88 -0
  52. package/src/commands/run/options.ts +104 -0
  53. package/src/commands/run/output.ts +179 -0
  54. package/src/commands/run/permission-decision.ts +19 -0
  55. package/src/commands/run/permission-recovery.ts +194 -0
  56. package/src/commands/run/permission-state.ts +177 -0
  57. package/src/commands/run/print-idle-timeout.ts +47 -0
  58. package/src/commands/run/protocol-envelope.ts +111 -0
  59. package/src/commands/run/protocol-stdio.ts +71 -0
  60. package/src/commands/run/protocol.ts +391 -0
  61. package/src/commands/run/runtime-command-bridge.ts +190 -0
  62. package/src/commands/run/runtime-event-sink.ts +560 -0
  63. package/src/commands/run/session-exit-controller.ts +45 -0
  64. package/src/commands/run/types.ts +65 -0
  65. package/src/commands/run.ts +62 -0
  66. package/src/commands/session-control.ts +133 -0
  67. package/src/commands/skills/add-command.ts +88 -0
  68. package/src/commands/skills/install-command.ts +105 -0
  69. package/src/commands/skills/install.ts +216 -0
  70. package/src/commands/skills/progress.ts +126 -0
  71. package/src/commands/skills/publish-command.ts +85 -0
  72. package/src/commands/skills/register.ts +17 -0
  73. package/src/commands/skills/remove-command.ts +102 -0
  74. package/src/commands/skills/shared.ts +117 -0
  75. package/src/commands/skills/sync.ts +571 -0
  76. package/src/commands/skills/types.ts +33 -0
  77. package/src/commands/skills.ts +1 -0
  78. package/src/commands/stop.ts +41 -0
  79. package/src/config.ts +1 -0
  80. package/src/default-skill-plugin.ts +29 -0
  81. package/src/env.ts +1 -0
  82. package/src/hooks/plugins/index.ts +66 -0
  83. package/src/mem-cli.ts +19 -0
  84. package/src/session-cache.ts +250 -0
  85. package/src/session-permission-cache.ts +40 -0
  86. package/src/utils.ts +25 -0
  87. package/src/workspace.ts +12 -0
@@ -0,0 +1,982 @@
1
+ import { readFile } from 'node:fs/promises'
2
+ import process from 'node:process'
3
+
4
+ import { Option } from 'commander'
5
+ import type { Command } from 'commander'
6
+
7
+ import { generateAdapterQueryOptions, run } from '@oneworks/app-runtime'
8
+ import { loadInjectDefaultSystemPromptValue, mergeSystemPrompts } from '@oneworks/config'
9
+ import { callHook, prewarmPersistentHookWorker } from '@oneworks/hooks'
10
+ import type { RuntimeSessionCommandEnvelope } from '@oneworks/runtime-protocol'
11
+ import type { AdapterInteractionRequest, AdapterOutputEvent, SessionInitInfo } from '@oneworks/types'
12
+ import { createStartupProfiler, mergeProcessEnvWithProjectEnv, nowStartupMs } from '@oneworks/utils'
13
+ import { getCache } from '@oneworks/utils/cache'
14
+ import { resolveProjectPrimaryWorkspaceFolder } from '@oneworks/utils/project-cache-path'
15
+ import { uuid } from '@oneworks/utils/uuid'
16
+
17
+ import { getCliDefaultSkillNames, getCliDefaultSkillPluginConfig } from '#~/default-skill-plugin.js'
18
+ import {
19
+ clearCliSessionControl,
20
+ formatResumeCommand,
21
+ isCliSessionStopActive,
22
+ readCliSessionControl,
23
+ resolveCliSession,
24
+ resolveCliSessionAdapter,
25
+ writeCliSessionRecord
26
+ } from '#~/session-cache.js'
27
+ import {
28
+ clearCliSessionPermissionRecovery,
29
+ readCliSessionPermissionRecovery,
30
+ writeCliSessionPermissionRecovery
31
+ } from '#~/session-permission-cache.js'
32
+ import { resolveCliWorkspaceCwd } from '#~/workspace.js'
33
+ import { createAdapterOption, parseCliAdapterOptionValue } from '../@core/adapter-option'
34
+ import { extraOptions } from '../@core/extra-options'
35
+ import { readRuntimeCommands } from '../agent/runtime-store'
36
+ import { applyAdapterCliVersionEnv, persistAdapterCliVersionSelection } from './adapter-cli-version'
37
+ import { attachInputBridge } from './input-bridge'
38
+ import { supportsRuntimeInteractionResponses } from './input-control'
39
+ import { readCliPermissionDecision } from './input-decision'
40
+ import {
41
+ getDisallowedResumeFlags,
42
+ getOutputFormat,
43
+ mergeListConfig,
44
+ resolveDefaultOneworksMcpServerOption,
45
+ resolveInjectDefaultSystemPromptOption,
46
+ resolvePermissionModeOption,
47
+ resolveResumeAdapterOptions,
48
+ resolveRunMode
49
+ } from './options'
50
+ import { getAdapterInteractionMessage, handlePrintEvent, shouldPrintResumeHint } from './output'
51
+ import {
52
+ isTerminalPermissionDecision,
53
+ shouldApplyPermissionDecision,
54
+ shouldClearPermissionRecoveryCache
55
+ } from './permission-decision'
56
+ import {
57
+ PERMISSION_DECISION_CANCEL,
58
+ PERMISSION_RECOVERY_CONTINUE_PROMPT,
59
+ buildPermissionRecoveryRecord,
60
+ extractPermissionErrorContext,
61
+ rememberPermissionToolUses,
62
+ resolvePermissionInteractionDecision
63
+ } from './permission-recovery'
64
+ import { applyCliPermissionDecision } from './permission-state'
65
+ import { createPrintIdleTimeoutController, parsePrintIdleTimeoutSeconds } from './print-idle-timeout'
66
+ import { executeRuntimeProtocolCommand } from './protocol'
67
+ import { runRuntimeProtocolStdio } from './protocol-stdio'
68
+ import { attachRuntimeCommandBridge } from './runtime-command-bridge'
69
+ import { createCliRuntimeEventSink, createRuntimeEventSink } from './runtime-event-sink'
70
+ import { createSessionExitController } from './session-exit-controller'
71
+ import type { ActiveCliSessionRecord, ExitControllableSession, RunOptions } from './types'
72
+ import { RUN_INPUT_FORMATS, RUN_OUTPUT_FORMATS } from './types'
73
+
74
+ type PrintInputCapableSession = ExitControllableSession & {
75
+ pid?: number
76
+ respondInteraction?: (id: string, data: string | string[]) => void | Promise<void>
77
+ }
78
+
79
+ const resolveRunPrimaryWorkspaceFolder = (
80
+ workspaceFolder: string,
81
+ fallbackWorkspaceFolder: string
82
+ ) =>
83
+ resolveProjectPrimaryWorkspaceFolder(
84
+ workspaceFolder,
85
+ mergeProcessEnvWithProjectEnv(undefined, { workspaceFolder })
86
+ ) ?? fallbackWorkspaceFolder
87
+
88
+ const writeRuntimeResumeFallbackResult = (params: {
89
+ result: Awaited<ReturnType<typeof executeRuntimeProtocolCommand>>
90
+ outputFormat: RunOptions['outputFormat']
91
+ }) => {
92
+ if (params.outputFormat === 'json') {
93
+ console.log(JSON.stringify(params.result, null, 2))
94
+ return
95
+ }
96
+ if (params.outputFormat === 'stream-json') {
97
+ console.log(JSON.stringify(params.result))
98
+ return
99
+ }
100
+
101
+ if (params.result.ok) {
102
+ console.log(`Queued runtime resume for session ${params.result.sessionId ?? ''}.`)
103
+ return
104
+ }
105
+
106
+ throw new Error(params.result.error ?? 'Failed to queue runtime resume.')
107
+ }
108
+
109
+ const RUNTIME_RESUME_EFFORTS = new Set(['low', 'medium', 'high', 'max'])
110
+ const RUNTIME_RESUME_PERMISSION_MODES = new Set(['default', 'acceptEdits', 'plan', 'dontAsk', 'bypassPermissions'])
111
+
112
+ const toRuntimeResumeEffort = (value: string | undefined): RuntimeSessionCommandEnvelope['effort'] =>
113
+ value != null && RUNTIME_RESUME_EFFORTS.has(value)
114
+ ? value as RuntimeSessionCommandEnvelope['effort']
115
+ : undefined
116
+
117
+ const toRuntimeResumePermissionMode = (
118
+ value: string | undefined
119
+ ): RuntimeSessionCommandEnvelope['permissionMode'] =>
120
+ value != null && RUNTIME_RESUME_PERMISSION_MODES.has(value)
121
+ ? value as RuntimeSessionCommandEnvelope['permissionMode']
122
+ : undefined
123
+
124
+ const readRuntimeSystemPromptFromEnv = async () => {
125
+ const systemPromptFile = process.env.__ONEWORKS_RUNTIME_PROTOCOL_SYSTEM_PROMPT_FILE__?.trim()
126
+ if (systemPromptFile == null || systemPromptFile === '') {
127
+ return undefined
128
+ }
129
+ const content = await readFile(systemPromptFile, 'utf8')
130
+ const trimmed = content.trim()
131
+ return trimmed === '' ? undefined : content
132
+ }
133
+
134
+ const configureRunCommand = (command: Command) => {
135
+ command
136
+ .argument('[description...]')
137
+ .option('--print', 'Print assistant output to stdout', false)
138
+ .addOption(
139
+ new Option('--print-idle-timeout <seconds>', 'Exit print mode if no adapter events are received for N seconds')
140
+ .argParser(parsePrintIdleTimeoutSeconds)
141
+ )
142
+ .option('--model <model>', 'Model to use')
143
+ .option('--effort <effort>', 'Effort to use (low, medium, high, max)')
144
+ .addOption(createAdapterOption('Adapter to use', { allowCliVersion: true }))
145
+ .option('--account <account>', 'Adapter account to use')
146
+ .option('--system-prompt <prompt>', 'System prompt')
147
+ .option(
148
+ '--no-inject-default-system-prompt',
149
+ 'Do not inject the default system prompt generated from rules/skills/entities/specs'
150
+ )
151
+ .option('--permission-mode <mode>', 'Permission mode (default, acceptEdits, plan, dontAsk, bypassPermissions)')
152
+ .option('--yolo', 'Shortcut for --permission-mode bypassPermissions', false)
153
+ .option('--session-id <id>', 'Session ID')
154
+ .option('--resume [id]', 'Resume an existing session by session id, or the latest created session when omitted')
155
+ .option('--fork [id]', 'Fork an existing session by session id, or the latest created session when omitted')
156
+ .addOption(
157
+ new Option('--output-format <format>', 'Output format')
158
+ .choices([...RUN_OUTPUT_FORMATS])
159
+ .default('text')
160
+ )
161
+ .addOption(
162
+ new Option('--input-format <format>', 'Input format for print mode stdin control')
163
+ .choices([...RUN_INPUT_FORMATS])
164
+ )
165
+ .option('--spec <spec>', 'Load spec definition')
166
+ .option('--entity <entity>', 'Load entity definition')
167
+ .option('--workspace <workspace>', 'Run in a configured workspace')
168
+ .option('--include-mcp-server <server...>', 'Include MCP server')
169
+ .option('--exclude-mcp-server <server...>', 'Exclude MCP server')
170
+ .option('--no-default-oneworks-mcp-server', 'Do not enable the built-in OneWorks MCP server')
171
+ .option('--include-tool <tool...>', 'Include tool')
172
+ .option('--exclude-tool <tool...>', 'Exclude tool')
173
+ .option('--include-skill <skill...>', 'Include skill')
174
+ .option('--exclude-skill <skill...>', 'Exclude skill')
175
+ .option(
176
+ '--update-skills',
177
+ 'Deprecated: runtime skill updates are disabled; use `oneworks skills update` first',
178
+ false
179
+ )
180
+ .addHelpText(
181
+ 'after',
182
+ `
183
+ Examples:
184
+ oneworks 实现一个新的 list 筛选
185
+ oneworks -A codex --print 读取 README 并总结
186
+ oneworks -A codex@0.130.0 使用指定 Codex CLI 版本,并保存为项目默认
187
+ oneworks -A claude 读取 README 并总结
188
+ oneworks --workspace billing 修复订单状态回滚问题
189
+ oneworks --include-skill oneworks-cli-quickstart 介绍一下 One Works CLI 怎么恢复会话
190
+ oneworks 帮我创建一个前端评审实体
191
+ oneworks 给 frontend-reviewer 加上移动端布局记忆
192
+ oneworks list --view default
193
+ oneworks --resume [sessionId]
194
+ oneworks --fork [sessionId]
195
+
196
+ Notes:
197
+ --adapter also supports -A, simplified ids like claude / adapter-codex, and <adapter>@<version> for managed native CLI versions.
198
+ --yolo is a shortcut for --permission-mode bypassPermissions.
199
+ When using --resume without a session id, the latest created cached session is resumed.
200
+ When using --fork without a session id, the latest created cached session is used as the fork source.
201
+ When using --resume, startup-only flags like --adapter, --system-prompt, --spec and --workspace are loaded from cache and cannot be set again.
202
+ --permission-mode is the exception: it overrides the cached permission mode for the resumed run and is saved for later resumes.
203
+ Resume still allows overriding --model, --effort, --include-tool and --exclude-tool for the next turn.
204
+ The resolved adapter is pinned in cache, so later default adapter changes do not affect resume.
205
+ Default CLI skills shipped via @oneworks/plugin-cli-skills: ${getCliDefaultSkillNames().join(', ')}.
206
+ In print mode, live permission/input replies require --input-format stream-json, then send {"type":"submit_input","data":"allow_once"}.
207
+ `
208
+ )
209
+ .action(async (descriptionArgs: string[], opts: RunOptions, currentCommand: Command) => {
210
+ let runtimeConsumerContext:
211
+ | {
212
+ cwd: string
213
+ sessionId: string
214
+ sink?: Awaited<ReturnType<typeof createRuntimeEventSink>>
215
+ shouldRunInitialPrompt?: boolean
216
+ }
217
+ | undefined
218
+ let activeRuntimeEventSink: Awaited<ReturnType<typeof createRuntimeEventSink>> | undefined
219
+ try {
220
+ const description = descriptionArgs.join(' ')
221
+ opts.permissionMode = resolvePermissionModeOption(opts.permissionMode, opts.yolo)
222
+ let lastAssistantText: string | undefined
223
+ let didExitAfterError = false
224
+ let inputClosed = false
225
+ let pendingInteraction: AdapterInteractionRequest | undefined
226
+ const exitController = createSessionExitController()
227
+ const cwd = resolveCliWorkspaceCwd()
228
+ const runtimeSystemPrompt = await readRuntimeSystemPromptFromEnv()
229
+ const adapterSelection = opts.adapter == null
230
+ ? undefined
231
+ : parseCliAdapterOptionValue(opts.adapter)
232
+ if (adapterSelection?.cliVersion != null) {
233
+ applyAdapterCliVersionEnv(process.env, adapterSelection.adapter, adapterSelection.cliVersion)
234
+ }
235
+ const generatedSessionId = opts.sessionId ?? uuid()
236
+ const outputFormatSource = currentCommand.getOptionValueSource('outputFormat')
237
+ const isRuntimeProtocolMode = opts.inputFormat === 'json' && !opts.print ||
238
+ opts.inputFormat === 'stream-json' && !opts.print
239
+ if (isRuntimeProtocolMode) {
240
+ const protocolOutputFormat = outputFormatSource === 'default'
241
+ ? (opts.inputFormat === 'stream-json' ? 'stream-json' : 'json')
242
+ : opts.outputFormat
243
+ if (protocolOutputFormat !== 'json' && protocolOutputFormat !== 'stream-json') {
244
+ throw new Error('Runtime protocol mode requires --output-format json or stream-json.')
245
+ }
246
+ await runRuntimeProtocolStdio({
247
+ cwd,
248
+ env: process.env,
249
+ inputFormat: opts.inputFormat!,
250
+ outputFormat: protocolOutputFormat,
251
+ stdin: process.stdin,
252
+ stdout: process.stdout
253
+ })
254
+ return
255
+ }
256
+
257
+ const selectedTargetFlags = [opts.spec, opts.entity, opts.workspace].filter(
258
+ (value): value is string => value != null && value.trim() !== ''
259
+ )
260
+ if (selectedTargetFlags.length > 1) {
261
+ throw new Error('--spec, --entity and --workspace are mutually exclusive.')
262
+ }
263
+ if (opts.inputFormat != null && !opts.print) {
264
+ throw new Error('--input-format is only supported together with --print or runtime protocol mode.')
265
+ }
266
+
267
+ const resumeId = opts.resume === true
268
+ ? undefined
269
+ : typeof opts.resume === 'string'
270
+ ? opts.resume
271
+ : undefined
272
+ const forkId = opts.fork === true
273
+ ? undefined
274
+ : typeof opts.fork === 'string'
275
+ ? opts.fork
276
+ : undefined
277
+ const isResume = opts.resume === true || typeof opts.resume === 'string'
278
+ const isFork = opts.fork === true || typeof opts.fork === 'string'
279
+ if (isResume && isFork) {
280
+ throw new Error('--resume and --fork are mutually exclusive.')
281
+ }
282
+ const printSource = currentCommand.getOptionValueSource('print')
283
+ const skills = opts.includeSkill || opts.excludeSkill
284
+ ? {
285
+ include: opts.includeSkill,
286
+ exclude: opts.excludeSkill
287
+ }
288
+ : undefined
289
+
290
+ const createCtxId = process.env.__ONEWORKS_PROJECT_CTX_ID__ ?? generatedSessionId
291
+
292
+ let runtimeResumeFallbackResult: Awaited<ReturnType<typeof executeRuntimeProtocolCommand>> | undefined
293
+ const initialCachedRecord = isResume || isFork
294
+ ? (() => {
295
+ const disallowedFlags = getDisallowedResumeFlags(opts, currentCommand)
296
+ if (disallowedFlags.length > 0) {
297
+ throw new Error(`${isFork ? 'Fork' : 'Resume'} mode does not accept ${disallowedFlags.join(', ')}.`)
298
+ }
299
+ return resolveCliSession(cwd, isFork ? forkId : resumeId).catch(async (error: unknown) => {
300
+ if (!isResume || resumeId == null) {
301
+ throw error
302
+ }
303
+ const result = await executeRuntimeProtocolCommand({
304
+ commandId: `cli-resume-${uuid()}`,
305
+ type: 'session.resume',
306
+ sessionId: resumeId,
307
+ source: 'cli',
308
+ message: description.trim() === '' ? '继续' : description,
309
+ model: opts.model,
310
+ effort: toRuntimeResumeEffort(opts.effort),
311
+ permissionMode: toRuntimeResumePermissionMode(opts.permissionMode)
312
+ }, {
313
+ cwd,
314
+ env: mergeProcessEnvWithProjectEnv({
315
+ ONEWORKS_RUNTIME_PROTOCOL_DISABLE_CONSUMER: '1'
316
+ }, { workspaceFolder: cwd })
317
+ })
318
+ if (!result.ok) {
319
+ throw error
320
+ }
321
+ runtimeResumeFallbackResult = result
322
+ return undefined
323
+ })
324
+ })()
325
+ : undefined
326
+
327
+ const cachedSession = await initialCachedRecord
328
+ if (isResume && cachedSession == null && runtimeResumeFallbackResult != null) {
329
+ writeRuntimeResumeFallbackResult({
330
+ result: runtimeResumeFallbackResult,
331
+ outputFormat: opts.outputFormat
332
+ })
333
+ return
334
+ }
335
+ if (!isResume && !isFork && adapterSelection?.cliVersion != null) {
336
+ await persistAdapterCliVersionSelection({
337
+ adapter: adapterSelection.adapter,
338
+ cwd,
339
+ version: adapterSelection.cliVersion
340
+ })
341
+ }
342
+ let resolvedTaskCwd = cachedSession?.resume?.taskOptions.cwd ?? cwd
343
+ const cachedAdapter = cachedSession == null
344
+ ? undefined
345
+ : (resolveCliSessionAdapter(cachedSession) || undefined)
346
+ const sessionId = isResume ? (cachedSession?.resume?.sessionId ?? generatedSessionId) : generatedSessionId
347
+ const cachedCtxId = isResume ? cachedSession?.resume?.ctxId : undefined
348
+ const startupProfiler = createStartupProfiler({
349
+ cwd,
350
+ ctxId: cachedCtxId ?? createCtxId ?? sessionId,
351
+ env: process.env,
352
+ sessionId
353
+ })
354
+ const runtimeProtocolConsumerSessionId = process.env.__ONEWORKS_RUNTIME_PROTOCOL_SESSION_ID__?.trim()
355
+ const isRuntimeProtocolConsumer = process.env.__ONEWORKS_RUNTIME_PROTOCOL_CONSUMER__ === '1' &&
356
+ runtimeProtocolConsumerSessionId === sessionId
357
+ if (isRuntimeProtocolConsumer) {
358
+ runtimeConsumerContext = { cwd, sessionId }
359
+ runtimeConsumerContext.sink = await createRuntimeEventSink({ cwd, env: process.env, sessionId })
360
+ const startupRecord = await runtimeConsumerContext.sink.recordStartup(
361
+ await readRuntimeCommands(cwd, sessionId, process.env)
362
+ )
363
+ runtimeConsumerContext.shouldRunInitialPrompt = startupRecord.shouldRunInitialPrompt
364
+ }
365
+ const adapterDescription = runtimeConsumerContext?.shouldRunInitialPrompt === false ? '' : description
366
+ const ctxId = cachedCtxId ?? createCtxId ?? sessionId
367
+ const createHookEnv = (workspaceFolder: string): Record<string, string> => {
368
+ const env = mergeProcessEnvWithProjectEnv({
369
+ ONEWORKS_HOOK_PERSISTENT_WORKER: process.env.ONEWORKS_HOOK_PERSISTENT_WORKER ?? '1',
370
+ __ONEWORKS_PROJECT_CTX_ID__: ctxId,
371
+ __ONEWORKS_PROJECT_LAUNCH_CWD__: workspaceFolder,
372
+ __ONEWORKS_PROJECT_WORKSPACE_FOLDER__: workspaceFolder,
373
+ __ONEWORKS_PROJECT_WORKSPACE_FOLDER_RESOLVE_CWD__: workspaceFolder,
374
+ __ONEWORKS_PROJECT_PRIMARY_WORKSPACE_FOLDER__: resolveRunPrimaryWorkspaceFolder(workspaceFolder, cwd)
375
+ }, { workspaceFolder })
376
+ return Object.fromEntries(
377
+ Object.entries(env).filter((entry): entry is [string, string] => typeof entry[1] === 'string')
378
+ )
379
+ }
380
+ const initialHookWorkerPrewarmStartedAt = startupProfiler.now()
381
+ const initialHookWorkerPrewarm = prewarmPersistentHookWorker(
382
+ createHookEnv(resolvedTaskCwd),
383
+ resolvedTaskCwd,
384
+ { light: true, warmup: true }
385
+ )
386
+ if (initialHookWorkerPrewarm != null) {
387
+ startupProfiler.mark(
388
+ 'cli.hookWorker.prewarm.initial',
389
+ initialHookWorkerPrewarmStartedAt,
390
+ { ...initialHookWorkerPrewarm }
391
+ )
392
+ }
393
+ const outputFormat = getOutputFormat(
394
+ opts.outputFormat,
395
+ outputFormatSource,
396
+ cachedSession?.resume?.outputFormat ?? 'text'
397
+ )
398
+ const resumeMode = resolveRunMode(
399
+ opts.print,
400
+ printSource,
401
+ cachedSession?.resume?.adapterOptions.mode ?? 'direct'
402
+ )
403
+ const shouldPrintOutput = resumeMode === 'stream'
404
+ if (opts.printIdleTimeout != null && !shouldPrintOutput) {
405
+ throw new Error('--print-idle-timeout is only supported in print mode.')
406
+ }
407
+ const supportsPrintInteractionInput = supportsRuntimeInteractionResponses(
408
+ opts.inputFormat,
409
+ isRuntimeProtocolConsumer
410
+ )
411
+ const pendingPermissionRecovery = await readCliSessionPermissionRecovery(cwd, ctxId, sessionId)
412
+ const cachedResumePermissionMode = cachedSession?.resume?.adapterOptions.permissionMode
413
+ const resolvedCachedPermissionMode = isResume || isFork
414
+ ? (opts.permissionMode ?? cachedResumePermissionMode)
415
+ : opts.permissionMode
416
+ const permissionRecoveryMode = pendingPermissionRecovery?.permissionMode ?? cachedResumePermissionMode
417
+ const resumePermissionModeChanged = isResume &&
418
+ opts.permissionMode != null &&
419
+ opts.permissionMode !== permissionRecoveryMode
420
+ const activePermissionRecovery = resumePermissionModeChanged ? undefined : pendingPermissionRecovery
421
+
422
+ if (resumePermissionModeChanged && pendingPermissionRecovery != null) {
423
+ await clearCliSessionPermissionRecovery(cwd, ctxId, sessionId)
424
+ } else if (isResume && activePermissionRecovery != null) {
425
+ if (shouldPrintOutput) {
426
+ handlePrintEvent({
427
+ event: {
428
+ type: 'interaction_request',
429
+ data: {
430
+ id: `cli-recovery:${sessionId}`,
431
+ payload: activePermissionRecovery.payload
432
+ }
433
+ },
434
+ outputFormat,
435
+ lastAssistantText,
436
+ didExitAfterError,
437
+ exitOnInteractionRequest: false,
438
+ log: (message) => console.log(message),
439
+ errorLog: (message) => console.error(message),
440
+ requestExit: () => {}
441
+ })
442
+ } else {
443
+ console.error(getAdapterInteractionMessage(activePermissionRecovery.payload))
444
+ }
445
+
446
+ if (opts.inputFormat == null) {
447
+ console.error(
448
+ `Resume with --print --input-format to answer this permission request for session ${sessionId}.`
449
+ )
450
+ process.exit(1)
451
+ }
452
+
453
+ const answer = await readCliPermissionDecision({
454
+ format: opts.inputFormat,
455
+ stdin: process.stdin
456
+ })
457
+ const decision = resolvePermissionInteractionDecision(answer)
458
+ if (decision == null) {
459
+ throw new TypeError('Permission recovery requires a decision like allow_once or deny_project.')
460
+ }
461
+
462
+ if (decision === PERMISSION_DECISION_CANCEL) {
463
+ console.error('Permission recovery cancelled. Session was not resumed.')
464
+ process.exit(1)
465
+ }
466
+ if (shouldApplyPermissionDecision(decision)) {
467
+ await applyCliPermissionDecision({
468
+ cwd: resolvedTaskCwd,
469
+ sessionId,
470
+ adapter: activePermissionRecovery.adapter,
471
+ subjectKeys: activePermissionRecovery.subjectKeys,
472
+ action: decision
473
+ })
474
+ }
475
+ if (shouldClearPermissionRecoveryCache(decision)) {
476
+ await clearCliSessionPermissionRecovery(cwd, ctxId, sessionId)
477
+ }
478
+ if (isTerminalPermissionDecision(decision)) {
479
+ console.error(`Permission decision applied: ${decision}. Session was not resumed.`)
480
+ process.exit(1)
481
+ }
482
+ }
483
+
484
+ const adapterOptions = cachedSession?.resume != null
485
+ ? {
486
+ ...resolveResumeAdapterOptions(cachedSession.resume.adapterOptions, opts),
487
+ type: isResume ? 'resume' as const : 'create' as const,
488
+ description: activePermissionRecovery == null
489
+ ? adapterDescription
490
+ : (adapterDescription.trim() === ''
491
+ ? PERMISSION_RECOVERY_CONTINUE_PROMPT
492
+ : `${PERMISSION_RECOVERY_CONTINUE_PROMPT}\n\n${adapterDescription}`),
493
+ permissionMode: resolvedCachedPermissionMode,
494
+ mode: resumeMode,
495
+ sessionId,
496
+ extraOptions
497
+ }
498
+ : await (async () => {
499
+ const promptType = opts.workspace
500
+ ? 'workspace'
501
+ : (opts.spec ? 'spec' : (opts.entity ? 'entity' : undefined))
502
+ const promptName = opts.workspace || opts.spec || opts.entity
503
+ const adapterQueryOptionsStartedAt = nowStartupMs()
504
+ const [data, resolvedConfig] = await generateAdapterQueryOptions(
505
+ promptType,
506
+ promptName,
507
+ cwd,
508
+ {
509
+ skills,
510
+ adapter: cachedAdapter ?? adapterSelection?.adapter,
511
+ model: opts.model,
512
+ plugins: getCliDefaultSkillPluginConfig(),
513
+ updateConfiguredSkills: opts.updateSkills === true
514
+ }
515
+ )
516
+ startupProfiler.mark('cli.generateAdapterQueryOptions', adapterQueryOptionsStartedAt)
517
+ resolvedTaskCwd = resolvedConfig.workspace?.cwd ?? cwd
518
+ const env = createHookEnv(resolvedTaskCwd)
519
+ const resolvedHookWorkerPrewarmStartedAt = startupProfiler.now()
520
+ const resolvedHookWorkerPrewarm = prewarmPersistentHookWorker(env, resolvedTaskCwd, {
521
+ pluginConfig: resolvedConfig.assetBundle?.pluginConfigs,
522
+ pluginInstances: resolvedConfig.assetBundle?.pluginInstances
523
+ })
524
+ if (resolvedHookWorkerPrewarm != null) {
525
+ startupProfiler.mark(
526
+ 'cli.hookWorker.prewarm.resolved',
527
+ resolvedHookWorkerPrewarmStartedAt,
528
+ { ...resolvedHookWorkerPrewarm }
529
+ )
530
+ }
531
+ const generateSystemPromptHookStartedAt = startupProfiler.now()
532
+ await callHook('GenerateSystemPrompt', {
533
+ cwd: resolvedTaskCwd,
534
+ sessionId,
535
+ type: promptType,
536
+ name: promptName,
537
+ data
538
+ }, env)
539
+ startupProfiler.mark('cli.hook.GenerateSystemPrompt', generateSystemPromptHookStartedAt)
540
+
541
+ const injectSystemPromptStartedAt = startupProfiler.now()
542
+ const injectDefaultSystemPrompt = await loadInjectDefaultSystemPromptValue(
543
+ resolvedTaskCwd,
544
+ resolveInjectDefaultSystemPromptOption(
545
+ opts.injectDefaultSystemPrompt,
546
+ currentCommand.getOptionValueSource('injectDefaultSystemPrompt')
547
+ ),
548
+ env
549
+ )
550
+ startupProfiler.mark('cli.loadInjectDefaultSystemPrompt', injectSystemPromptStartedAt)
551
+
552
+ return {
553
+ type: 'create' as const,
554
+ description: adapterDescription,
555
+ runtime: 'cli' as const,
556
+ sessionId,
557
+ model: opts.model,
558
+ account: opts.account,
559
+ effort: opts.effort,
560
+ systemPrompt: mergeSystemPrompts({
561
+ generatedSystemPrompt: resolvedConfig.systemPrompt,
562
+ userSystemPrompt: opts.systemPrompt ?? runtimeSystemPrompt,
563
+ injectDefaultSystemPrompt
564
+ }),
565
+ permissionMode: opts.permissionMode,
566
+ mode: resolveRunMode(
567
+ opts.print,
568
+ printSource,
569
+ 'direct'
570
+ ),
571
+ tools: mergeListConfig(
572
+ resolvedConfig.tools,
573
+ opts.includeTool,
574
+ opts.excludeTool
575
+ ),
576
+ mcpServers: mergeListConfig(
577
+ resolvedConfig.mcpServers,
578
+ opts.includeMcpServer,
579
+ opts.excludeMcpServer
580
+ ),
581
+ useDefaultOneworksMcpServer: resolveDefaultOneworksMcpServerOption(
582
+ opts.defaultOneworksMcpServer,
583
+ currentCommand.getOptionValueSource('defaultOneworksMcpServer')
584
+ ),
585
+ promptAssetIds: resolvedConfig.promptAssetIds,
586
+ skills,
587
+ extraOptions,
588
+ assetBundle: resolvedConfig.assetBundle
589
+ }
590
+ })()
591
+ const {
592
+ type: _adapterType,
593
+ description: _adapterDescription,
594
+ ...cachedAdapterOptions
595
+ } = adapterOptions
596
+ const runTaskOptions = isResume || isFork
597
+ ? undefined
598
+ : {
599
+ adapter: adapterSelection?.adapter,
600
+ cwd: resolvedTaskCwd,
601
+ ctxId
602
+ }
603
+
604
+ const record: ActiveCliSessionRecord = {
605
+ resume: {
606
+ version: 1 as const,
607
+ ctxId,
608
+ sessionId,
609
+ cwd: cachedSession?.resume?.cwd ?? resolvedTaskCwd,
610
+ description: description || cachedSession?.resume?.description,
611
+ createdAt: isResume ? (cachedSession?.resume?.createdAt ?? Date.now()) : Date.now(),
612
+ updatedAt: Date.now(),
613
+ resolvedAdapter: cachedSession?.resume?.resolvedAdapter ?? cachedAdapter,
614
+ taskOptions: {
615
+ ...(cachedSession?.resume?.taskOptions ?? {
616
+ cwd: resolvedTaskCwd,
617
+ ctxId
618
+ }),
619
+ ctxId,
620
+ adapter: cachedAdapter ?? runTaskOptions?.adapter
621
+ },
622
+ adapterOptions: cachedAdapterOptions,
623
+ outputFormat
624
+ },
625
+ detail: {
626
+ ctxId,
627
+ sessionId,
628
+ status: 'pending',
629
+ startTime: isResume ? (cachedSession?.detail?.startTime ?? Date.now()) : Date.now(),
630
+ description: description || cachedSession?.detail?.description || cachedSession?.resume?.description,
631
+ adapter: cachedSession?.detail?.adapter ?? cachedAdapter,
632
+ model: adapterOptions.model ?? cachedSession?.detail?.model ?? cachedSession?.resume?.adapterOptions.model
633
+ }
634
+ }
635
+
636
+ let persistQueue = Promise.resolve()
637
+ const persistRecord = () => {
638
+ persistQueue = persistQueue
639
+ .catch(() => {})
640
+ .then(() => writeCliSessionRecord(cwd, ctxId, sessionId, record))
641
+ .catch((error) => {
642
+ const message = error instanceof Error ? error.message : String(error)
643
+ console.error(`[oneworks] Failed to update session cache: ${message}`)
644
+ })
645
+ return persistQueue
646
+ }
647
+ const updateInitRecord = (info: SessionInitInfo, pid: number | undefined) => {
648
+ const resolvedAdapter = info.adapter ?? record.resume.resolvedAdapter ?? record.resume.taskOptions.adapter
649
+ record.resume = {
650
+ ...record.resume,
651
+ updatedAt: Date.now(),
652
+ resolvedAdapter,
653
+ taskOptions: {
654
+ ...record.resume.taskOptions,
655
+ adapter: resolvedAdapter
656
+ },
657
+ adapterOptions: {
658
+ ...record.resume.adapterOptions,
659
+ model: info.model,
660
+ effort: info.effort ?? record.resume.adapterOptions.effort
661
+ }
662
+ }
663
+ record.detail = {
664
+ ...record.detail,
665
+ status: 'running',
666
+ pid: pid ?? record.detail.pid,
667
+ adapter: resolvedAdapter ?? record.detail.adapter,
668
+ model: info.model ?? record.detail.model
669
+ }
670
+ void persistRecord()
671
+ }
672
+
673
+ await persistRecord()
674
+
675
+ let runtimeEventSink = runtimeConsumerContext?.sink
676
+ if (runtimeEventSink == null && !isResume) {
677
+ runtimeEventSink = await createCliRuntimeEventSink({
678
+ adapter: record.resume.resolvedAdapter ?? record.resume.taskOptions.adapter,
679
+ cwd: record.resume.taskOptions.cwd ?? record.resume.cwd,
680
+ effort: toRuntimeResumeEffort(record.resume.adapterOptions.effort),
681
+ env: process.env,
682
+ message: record.resume.description,
683
+ model: record.resume.adapterOptions.model,
684
+ permissionMode: toRuntimeResumePermissionMode(record.resume.adapterOptions.permissionMode),
685
+ sessionId,
686
+ title: record.detail.description ?? record.resume.description ?? sessionId
687
+ })
688
+ }
689
+ activeRuntimeEventSink = runtimeEventSink
690
+
691
+ let boundSession: PrintInputCapableSession | undefined
692
+ let stopInputBridge: (() => void) | undefined
693
+ let stopRuntimeCommandBridge: (() => Promise<void>) | undefined
694
+ const permissionToolUseCache = new Map<string, string>()
695
+ let permissionRecoveryQueue: Promise<void> = Promise.resolve()
696
+ let didHandleExit = false
697
+ let printIdleTimeoutController: ReturnType<typeof createPrintIdleTimeoutController> | undefined
698
+ let unhandledRejectionHandler: ((reason: unknown) => void) | undefined
699
+ let uncaughtExceptionHandler: ((error: Error) => void) | undefined
700
+ const cleanupRuntimeConsumerFailureHandlers = () => {
701
+ if (unhandledRejectionHandler != null) {
702
+ process.off('unhandledRejection', unhandledRejectionHandler)
703
+ }
704
+ if (uncaughtExceptionHandler != null) {
705
+ process.off('uncaughtException', uncaughtExceptionHandler)
706
+ }
707
+ }
708
+ const submitPrintInput = async (params: { interactionId?: string; data: string | string[] }) => {
709
+ const interactionId = params.interactionId ?? pendingInteraction?.id
710
+ if (interactionId == null || interactionId.trim() === '') {
711
+ throw new TypeError('No pending interaction is available. Wait for an interaction_request event first.')
712
+ }
713
+ const respondInteraction = boundSession?.respondInteraction
714
+ if (typeof respondInteraction !== 'function') {
715
+ throw new TypeError('The current session does not support submit_input events.')
716
+ }
717
+
718
+ await respondInteraction(interactionId, params.data)
719
+
720
+ if (pendingInteraction?.id === interactionId) {
721
+ pendingInteraction = undefined
722
+ }
723
+ }
724
+ const handleExit = (exitCode: number) => {
725
+ if (didHandleExit) {
726
+ return
727
+ }
728
+ didHandleExit = true
729
+ printIdleTimeoutController?.stop()
730
+ void (async () => {
731
+ const endedAt = Date.now()
732
+ const [persistedDetail, control] = await Promise.all([
733
+ getCache(record.resume.taskOptions.cwd ?? record.resume.cwd, ctxId, sessionId, 'detail'),
734
+ readCliSessionControl(cwd, ctxId, sessionId)
735
+ ])
736
+ record.resume = {
737
+ ...record.resume,
738
+ updatedAt: endedAt
739
+ }
740
+ const status = persistedDetail?.status === 'stopped' || isCliSessionStopActive(control, endedAt)
741
+ ? 'stopped'
742
+ : exitCode === 0
743
+ ? 'completed'
744
+ : 'failed'
745
+ record.detail = {
746
+ ...record.detail,
747
+ endTime: endedAt,
748
+ exitCode,
749
+ status
750
+ }
751
+ await persistRecord()
752
+ await persistQueue
753
+ await permissionRecoveryQueue
754
+ await stopRuntimeCommandBridge?.()
755
+ await runtimeEventSink?.flush()
756
+ cleanupRuntimeConsumerFailureHandlers()
757
+ await clearCliSessionControl(cwd, ctxId, sessionId)
758
+ stopInputBridge?.()
759
+ await boundSession?.flushHooks?.()
760
+ if (shouldPrintResumeHint({ shouldPrintOutput, status })) {
761
+ console.error(formatResumeCommand(sessionId))
762
+ }
763
+ exitController.handleSessionExit(exitCode)
764
+ })()
765
+ }
766
+ const runtimeConsumerFailureHandler = runtimeEventSink == null
767
+ ? undefined
768
+ : (error: unknown) => {
769
+ const message = error instanceof Error ? error.message : String(error)
770
+ console.error(message)
771
+ void runtimeEventSink.recordFailure(error)
772
+ .catch(() => {})
773
+ .then(() => handleExit(1))
774
+ }
775
+ unhandledRejectionHandler = runtimeConsumerFailureHandler == null
776
+ ? undefined
777
+ : (reason: unknown) => runtimeConsumerFailureHandler(reason)
778
+ uncaughtExceptionHandler = runtimeConsumerFailureHandler == null
779
+ ? undefined
780
+ : (error: Error) => runtimeConsumerFailureHandler(error)
781
+ if (unhandledRejectionHandler != null) {
782
+ process.once('unhandledRejection', unhandledRejectionHandler)
783
+ }
784
+ if (uncaughtExceptionHandler != null) {
785
+ process.once('uncaughtException', uncaughtExceptionHandler)
786
+ }
787
+ if (shouldPrintOutput && opts.printIdleTimeout != null) {
788
+ printIdleTimeoutController = createPrintIdleTimeoutController({
789
+ timeoutSeconds: opts.printIdleTimeout,
790
+ onTimeout: () => {
791
+ const exitCode = exitController.getPendingExitCode() ?? 1
792
+ console.error(
793
+ `[oneworks] Print mode idle timeout: no adapter events received for ${opts.printIdleTimeout} seconds.`
794
+ )
795
+ exitController.requestExit(exitCode)
796
+ handleExit(exitCode)
797
+ }
798
+ })
799
+ printIdleTimeoutController.start()
800
+ }
801
+
802
+ const runStartedAt = startupProfiler.now()
803
+ const { session, resolvedAdapter } = await run({
804
+ adapter: record.resume.resolvedAdapter ?? record.resume.taskOptions.adapter,
805
+ cwd: record.resume.taskOptions.cwd ?? record.resume.cwd,
806
+ ctxId,
807
+ updateConfiguredSkills: opts.updateSkills === true,
808
+ env: createHookEnv(record.resume.taskOptions.cwd ?? record.resume.cwd),
809
+ plugins: getCliDefaultSkillPluginConfig()
810
+ }, {
811
+ ...adapterOptions,
812
+ onEvent: (event: AdapterOutputEvent) => {
813
+ printIdleTimeoutController?.recordEvent()
814
+ void runtimeEventSink?.handleAdapterEvent(event)
815
+ if (event.type === 'init') {
816
+ updateInitRecord(event.data, boundSession?.pid)
817
+ }
818
+ if (event.type === 'message') {
819
+ rememberPermissionToolUses(permissionToolUseCache, event.data)
820
+ }
821
+ if (event.type === 'error' && event.data.code === 'permission_required') {
822
+ const permissionRecovery = buildPermissionRecoveryRecord({
823
+ sessionId,
824
+ adapter: resolvedAdapter ?? record.resume.resolvedAdapter ?? record.resume.taskOptions.adapter,
825
+ currentMode: record.resume.adapterOptions.permissionMode,
826
+ context: extractPermissionErrorContext(event.data, {
827
+ toolUseSubjects: permissionToolUseCache
828
+ })
829
+ })
830
+ if (permissionRecovery != null) {
831
+ permissionRecoveryQueue = permissionRecoveryQueue
832
+ .catch(() => {})
833
+ .then(async () => {
834
+ await writeCliSessionPermissionRecovery(cwd, ctxId, sessionId, permissionRecovery)
835
+ })
836
+ if (shouldPrintOutput) {
837
+ const nextState = handlePrintEvent({
838
+ event: {
839
+ type: 'interaction_request',
840
+ data: {
841
+ id: `cli-recovery:${sessionId}`,
842
+ payload: permissionRecovery.payload
843
+ }
844
+ },
845
+ outputFormat,
846
+ lastAssistantText,
847
+ didExitAfterError,
848
+ exitOnInteractionRequest: true,
849
+ log: (message) => console.log(message),
850
+ errorLog: (message) => console.error(message),
851
+ requestExit: (code) => exitController.requestExit(code)
852
+ })
853
+ lastAssistantText = nextState.lastAssistantText
854
+ didExitAfterError = nextState.didExitAfterError
855
+ }
856
+ return
857
+ }
858
+ }
859
+ if (event.type === 'interaction_request') {
860
+ pendingInteraction = event.data
861
+ if (shouldPrintOutput && opts.inputFormat != null && !supportsPrintInteractionInput) {
862
+ console.error(
863
+ 'Print-mode interaction responses require --input-format stream-json. Exiting after printing the request.'
864
+ )
865
+ }
866
+ }
867
+ if (
868
+ event.type === 'stop' ||
869
+ event.type === 'exit' ||
870
+ (event.type === 'error' && event.data.fatal !== false)
871
+ ) {
872
+ pendingInteraction = undefined
873
+ }
874
+ if (shouldPrintOutput) {
875
+ const nextState = handlePrintEvent({
876
+ event,
877
+ outputFormat,
878
+ lastAssistantText,
879
+ didExitAfterError,
880
+ exitOnInteractionRequest: event.type === 'interaction_request' &&
881
+ !isRuntimeProtocolConsumer &&
882
+ (!supportsPrintInteractionInput || inputClosed),
883
+ stopExitsStreamJson: outputFormat === 'stream-json' &&
884
+ !isRuntimeProtocolConsumer &&
885
+ (opts.inputFormat == null || inputClosed),
886
+ log: (message) => console.log(message),
887
+ errorLog: (message) => console.error(message),
888
+ requestExit: (code) => exitController.requestExit(code)
889
+ })
890
+ lastAssistantText = nextState.lastAssistantText
891
+ didExitAfterError = nextState.didExitAfterError
892
+ }
893
+ if (event.type === 'exit') {
894
+ handleExit(exitController.getPendingExitCode() ?? event.data.exitCode ?? 0)
895
+ }
896
+ if (isRuntimeProtocolConsumer && event.type === 'stop') {
897
+ handleExit(exitController.getPendingExitCode() ?? 0)
898
+ }
899
+ if (isRuntimeProtocolConsumer && event.type === 'error' && event.data.fatal !== false) {
900
+ handleExit(1)
901
+ }
902
+ }
903
+ })
904
+ startupProfiler.mark('cli.task.run', runStartedAt)
905
+ boundSession = session
906
+ record.resume = {
907
+ ...record.resume,
908
+ resolvedAdapter: resolvedAdapter ?? record.resume.resolvedAdapter,
909
+ taskOptions: {
910
+ ...record.resume.taskOptions,
911
+ adapter: resolvedAdapter ?? record.resume.taskOptions.adapter
912
+ }
913
+ }
914
+ record.detail = {
915
+ ...record.detail,
916
+ pid: session.pid ?? record.detail.pid,
917
+ status: record.detail.status === 'pending' ? 'running' : record.detail.status,
918
+ adapter: resolvedAdapter ?? record.detail.adapter
919
+ }
920
+ void persistRecord()
921
+ exitController.bindSession(session)
922
+ if (runtimeEventSink != null) {
923
+ stopRuntimeCommandBridge = await attachRuntimeCommandBridge({
924
+ cwd,
925
+ env: process.env,
926
+ session,
927
+ sessionId,
928
+ sink: runtimeEventSink,
929
+ submitInput: submitPrintInput
930
+ })
931
+ }
932
+ if (shouldPrintOutput && opts.inputFormat != null) {
933
+ stopInputBridge = attachInputBridge({
934
+ format: opts.inputFormat,
935
+ session,
936
+ stdin: process.stdin,
937
+ onError: (message) => {
938
+ console.error(message)
939
+ exitController.requestExit(1)
940
+ },
941
+ onInputClosed: () => {
942
+ inputClosed = true
943
+ if (pendingInteraction != null) {
944
+ exitController.requestExit(1)
945
+ }
946
+ },
947
+ submitInput: submitPrintInput
948
+ })
949
+ }
950
+ } catch (error) {
951
+ const failureSink = activeRuntimeEventSink ??
952
+ (runtimeConsumerContext == null
953
+ ? undefined
954
+ : runtimeConsumerContext.sink ??
955
+ await createRuntimeEventSink({
956
+ cwd: runtimeConsumerContext.cwd,
957
+ env: process.env,
958
+ sessionId: runtimeConsumerContext.sessionId
959
+ }))
960
+ if (failureSink != null) {
961
+ try {
962
+ await failureSink.recordFailure(error)
963
+ await failureSink.flush()
964
+ } catch (sinkError) {
965
+ const message = sinkError instanceof Error ? sinkError.message : String(sinkError)
966
+ console.error(`[runtime-protocol] Failed to record runtime failure: ${message}`)
967
+ }
968
+ }
969
+ const message = error instanceof Error ? error.message : String(error)
970
+ console.error(message)
971
+ process.exit(1)
972
+ }
973
+ })
974
+ }
975
+
976
+ export function registerRunCommand(program: Command) {
977
+ configureRunCommand(
978
+ program
979
+ .command('__run', { hidden: true })
980
+ .description('Run or resume a session')
981
+ )
982
+ }