@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.
- package/LICENSE +21 -0
- package/channel.js +7 -0
- package/cli.js +5 -0
- package/mem.js +7 -0
- package/package.json +59 -0
- package/postinstall.js +75 -0
- package/src/AGENTS.md +169 -0
- package/src/channel-cli.ts +19 -0
- package/src/cli-argv.ts +27 -0
- package/src/cli.ts +63 -0
- package/src/commands/@core/adapter-option.ts +85 -0
- package/src/commands/@core/extra-options.ts +12 -0
- package/src/commands/@core/plugin-install.ts +1 -0
- package/src/commands/@core/plugin-source.ts +1 -0
- package/src/commands/accounts.ts +204 -0
- package/src/commands/adapter/prepare-selection.ts +181 -0
- package/src/commands/adapter/prepare.ts +104 -0
- package/src/commands/adapter.ts +48 -0
- package/src/commands/agent/actions.ts +176 -0
- package/src/commands/agent/runtime-store-commands.ts +56 -0
- package/src/commands/agent/runtime-store-events.ts +23 -0
- package/src/commands/agent/runtime-store-session.ts +170 -0
- package/src/commands/agent/runtime-store-shared.ts +139 -0
- package/src/commands/agent/runtime-store.ts +4 -0
- package/src/commands/agent.ts +81 -0
- package/src/commands/benchmark.ts +198 -0
- package/src/commands/channel.ts +594 -0
- package/src/commands/clear.ts +140 -0
- package/src/commands/config/actions.ts +196 -0
- package/src/commands/config/display-state.ts +108 -0
- package/src/commands/config/index.ts +135 -0
- package/src/commands/config/interactive.ts +121 -0
- package/src/commands/config/read-state.ts +56 -0
- package/src/commands/config/section-state.ts +109 -0
- package/src/commands/config/shared.ts +195 -0
- package/src/commands/kill.ts +41 -0
- package/src/commands/list.ts +224 -0
- package/src/commands/memory/context.ts +76 -0
- package/src/commands/memory/entries.ts +131 -0
- package/src/commands/memory/shared.ts +89 -0
- package/src/commands/memory/store.ts +69 -0
- package/src/commands/memory/target.ts +54 -0
- package/src/commands/memory.ts +97 -0
- package/src/commands/plugin.ts +62 -0
- package/src/commands/report-targets.ts +149 -0
- package/src/commands/report.ts +232 -0
- package/src/commands/run/adapter-cli-version.ts +65 -0
- package/src/commands/run/command.ts +982 -0
- package/src/commands/run/input-bridge.ts +108 -0
- package/src/commands/run/input-control.ts +112 -0
- package/src/commands/run/input-decision.ts +88 -0
- package/src/commands/run/options.ts +104 -0
- package/src/commands/run/output.ts +179 -0
- package/src/commands/run/permission-decision.ts +19 -0
- package/src/commands/run/permission-recovery.ts +194 -0
- package/src/commands/run/permission-state.ts +177 -0
- package/src/commands/run/print-idle-timeout.ts +47 -0
- package/src/commands/run/protocol-envelope.ts +111 -0
- package/src/commands/run/protocol-stdio.ts +71 -0
- package/src/commands/run/protocol.ts +391 -0
- package/src/commands/run/runtime-command-bridge.ts +190 -0
- package/src/commands/run/runtime-event-sink.ts +560 -0
- package/src/commands/run/session-exit-controller.ts +45 -0
- package/src/commands/run/types.ts +65 -0
- package/src/commands/run.ts +62 -0
- package/src/commands/session-control.ts +133 -0
- package/src/commands/skills/add-command.ts +88 -0
- package/src/commands/skills/install-command.ts +105 -0
- package/src/commands/skills/install.ts +216 -0
- package/src/commands/skills/progress.ts +126 -0
- package/src/commands/skills/publish-command.ts +85 -0
- package/src/commands/skills/register.ts +17 -0
- package/src/commands/skills/remove-command.ts +102 -0
- package/src/commands/skills/shared.ts +117 -0
- package/src/commands/skills/sync.ts +571 -0
- package/src/commands/skills/types.ts +33 -0
- package/src/commands/skills.ts +1 -0
- package/src/commands/stop.ts +41 -0
- package/src/config.ts +1 -0
- package/src/default-skill-plugin.ts +29 -0
- package/src/env.ts +1 -0
- package/src/hooks/plugins/index.ts +66 -0
- package/src/mem-cli.ts +19 -0
- package/src/session-cache.ts +250 -0
- package/src/session-permission-cache.ts +40 -0
- package/src/utils.ts +25 -0
- 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
|
+
}
|