@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,391 @@
|
|
|
1
|
+
/* eslint-disable max-lines -- protocol command dispatch handles every runtime envelope type in one place */
|
|
2
|
+
import { spawn } from 'node:child_process'
|
|
3
|
+
import process from 'node:process'
|
|
4
|
+
|
|
5
|
+
import type { RuntimeSessionCommandEnvelope, RuntimeSessionResultEnvelope } from '@oneworks/runtime-protocol'
|
|
6
|
+
import { mergeProcessEnvWithProjectEnv } from '@oneworks/utils'
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
appendRuntimeCommand,
|
|
10
|
+
createRuntimeSession,
|
|
11
|
+
readRuntimeEvents,
|
|
12
|
+
readRuntimeStatus
|
|
13
|
+
} from '../agent/runtime-store'
|
|
14
|
+
import {
|
|
15
|
+
fallbackProtocolCommand,
|
|
16
|
+
parseRuntimeProtocolCommand,
|
|
17
|
+
requiredString,
|
|
18
|
+
toErrorResult,
|
|
19
|
+
toSuccessResult
|
|
20
|
+
} from './protocol-envelope'
|
|
21
|
+
|
|
22
|
+
export interface ExecuteRuntimeProtocolCommandOptions {
|
|
23
|
+
cwd: string
|
|
24
|
+
env?: NodeJS.ProcessEnv
|
|
25
|
+
now?: () => number
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const trimOptional = (value: string | undefined) => {
|
|
29
|
+
const normalized = value?.trim()
|
|
30
|
+
return normalized == null || normalized === '' ? undefined : normalized
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const optionalString = (value: unknown) =>
|
|
34
|
+
typeof value === 'string' && value.trim() !== ''
|
|
35
|
+
? value.trim()
|
|
36
|
+
: undefined
|
|
37
|
+
|
|
38
|
+
const RUNTIME_EFFORTS = new Set(['low', 'medium', 'high', 'max'])
|
|
39
|
+
const RUNTIME_PERMISSION_MODES = new Set(['default', 'acceptEdits', 'plan', 'dontAsk', 'bypassPermissions'])
|
|
40
|
+
const RUNTIME_RESUME_CONSUMER_STATUSES = new Set([
|
|
41
|
+
'completed',
|
|
42
|
+
'failed',
|
|
43
|
+
'crashed',
|
|
44
|
+
'stopped',
|
|
45
|
+
'cancelled',
|
|
46
|
+
'killed'
|
|
47
|
+
])
|
|
48
|
+
|
|
49
|
+
const optionalEffort = (value: unknown): RuntimeSessionCommandEnvelope['effort'] => {
|
|
50
|
+
const normalized = optionalString(value)
|
|
51
|
+
return normalized != null && RUNTIME_EFFORTS.has(normalized)
|
|
52
|
+
? normalized as RuntimeSessionCommandEnvelope['effort']
|
|
53
|
+
: undefined
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const optionalPermissionMode = (value: unknown): RuntimeSessionCommandEnvelope['permissionMode'] => {
|
|
57
|
+
const normalized = optionalString(value)
|
|
58
|
+
return normalized != null && RUNTIME_PERMISSION_MODES.has(normalized)
|
|
59
|
+
? normalized as RuntimeSessionCommandEnvelope['permissionMode']
|
|
60
|
+
: undefined
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const envDefault = (env: NodeJS.ProcessEnv | undefined, key: string) => trimOptional(env?.[key])
|
|
64
|
+
|
|
65
|
+
const pushOption = (args: string[], flag: string, value: string | undefined) => {
|
|
66
|
+
if (value != null) {
|
|
67
|
+
args.push(flag, value)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const CODEX_PARENT_RUNTIME_ENV_KEYS = [
|
|
72
|
+
'CODEX_SANDBOX',
|
|
73
|
+
'CODEX_SANDBOX_NETWORK_DISABLED',
|
|
74
|
+
'CODEX_THREAD_ID',
|
|
75
|
+
'CODEX_SHELL',
|
|
76
|
+
'__ONEWORKS_CODEX_HOOK_RUNTIME__',
|
|
77
|
+
'__ONEWORKS_CODEX_TASK_SESSION_ID__',
|
|
78
|
+
'__ONEWORKS_CODEX_HOOKS_ACTIVE__',
|
|
79
|
+
'__ONEWORKS_HOOK_BRIDGE_ADAPTER__'
|
|
80
|
+
] as const
|
|
81
|
+
|
|
82
|
+
const shouldDeferRuntimeConsumerToServer = (
|
|
83
|
+
command: RuntimeSessionCommandEnvelope,
|
|
84
|
+
env: NodeJS.ProcessEnv | undefined
|
|
85
|
+
) =>
|
|
86
|
+
(
|
|
87
|
+
trimOptional(command.hostSessionId) != null ||
|
|
88
|
+
trimOptional(command.roomId) != null ||
|
|
89
|
+
trimOptional(env?.__ONEWORKS_AGENT_ROOM_HOST_SESSION_ID__) != null
|
|
90
|
+
) &&
|
|
91
|
+
(
|
|
92
|
+
trimOptional(env?.__ONEWORKS_PROJECT_BASE_DIR__) != null ||
|
|
93
|
+
trimOptional(env?.__ONEWORKS_RUNTIME_PROTOCOL_CONSUMER__) != null
|
|
94
|
+
) &&
|
|
95
|
+
env?.ONEWORKS_RUNTIME_PROTOCOL_FORCE_LOCAL_CONSUMER !== '1' &&
|
|
96
|
+
env?.ONEWORKS_RUNTIME_PROTOCOL_FORCE_LOCAL_CONSUMER !== 'true'
|
|
97
|
+
|
|
98
|
+
const sanitizeRuntimeConsumerEnv = (env: NodeJS.ProcessEnv) => {
|
|
99
|
+
const nextEnv = { ...env }
|
|
100
|
+
for (const key of CODEX_PARENT_RUNTIME_ENV_KEYS) {
|
|
101
|
+
delete nextEnv[key]
|
|
102
|
+
}
|
|
103
|
+
return nextEnv
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const shouldStartRuntimeConsumer = (command: RuntimeSessionCommandEnvelope, env?: NodeJS.ProcessEnv) =>
|
|
107
|
+
env != null &&
|
|
108
|
+
command.background !== false &&
|
|
109
|
+
env?.ONEWORKS_RUNTIME_PROTOCOL_DISABLE_CONSUMER !== '1' &&
|
|
110
|
+
env?.ONEWORKS_RUNTIME_PROTOCOL_DISABLE_CONSUMER !== 'true' &&
|
|
111
|
+
!shouldDeferRuntimeConsumerToServer(command, env)
|
|
112
|
+
|
|
113
|
+
export const shouldStartRuntimeResumeConsumer = (params: {
|
|
114
|
+
command: RuntimeSessionCommandEnvelope
|
|
115
|
+
env?: NodeJS.ProcessEnv
|
|
116
|
+
status?: string
|
|
117
|
+
}) =>
|
|
118
|
+
params.env != null &&
|
|
119
|
+
params.command.background !== false &&
|
|
120
|
+
params.env?.ONEWORKS_RUNTIME_PROTOCOL_DISABLE_CONSUMER !== '1' &&
|
|
121
|
+
params.env?.ONEWORKS_RUNTIME_PROTOCOL_DISABLE_CONSUMER !== 'true' &&
|
|
122
|
+
params.status != null &&
|
|
123
|
+
RUNTIME_RESUME_CONSUMER_STATUSES.has(params.status)
|
|
124
|
+
|
|
125
|
+
const startRuntimeConsumer = (params: {
|
|
126
|
+
adapter?: string
|
|
127
|
+
command: RuntimeSessionCommandEnvelope
|
|
128
|
+
cwd: string
|
|
129
|
+
effort?: RuntimeSessionCommandEnvelope['effort']
|
|
130
|
+
entity: string
|
|
131
|
+
env?: NodeJS.ProcessEnv
|
|
132
|
+
message: string
|
|
133
|
+
model?: string
|
|
134
|
+
permissionMode?: RuntimeSessionCommandEnvelope['permissionMode']
|
|
135
|
+
sessionId: string
|
|
136
|
+
}) => {
|
|
137
|
+
const cliEntrypoint = process.argv[1]
|
|
138
|
+
if (cliEntrypoint == null || cliEntrypoint.trim() === '') {
|
|
139
|
+
throw new Error('Unable to resolve One Works CLI entrypoint for runtime consumer.')
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const args = [
|
|
143
|
+
cliEntrypoint,
|
|
144
|
+
'__run',
|
|
145
|
+
'--print',
|
|
146
|
+
'--output-format',
|
|
147
|
+
'stream-json',
|
|
148
|
+
'--session-id',
|
|
149
|
+
params.sessionId,
|
|
150
|
+
'--entity',
|
|
151
|
+
params.entity
|
|
152
|
+
]
|
|
153
|
+
pushOption(args, '--adapter', params.adapter)
|
|
154
|
+
pushOption(args, '--model', params.model)
|
|
155
|
+
pushOption(args, '--effort', params.effort)
|
|
156
|
+
pushOption(args, '--permission-mode', params.permissionMode)
|
|
157
|
+
args.push(params.message)
|
|
158
|
+
|
|
159
|
+
const child = spawn(process.execPath, args, {
|
|
160
|
+
cwd: params.cwd,
|
|
161
|
+
detached: true,
|
|
162
|
+
env: sanitizeRuntimeConsumerEnv(mergeProcessEnvWithProjectEnv({
|
|
163
|
+
...(params.env ?? {}),
|
|
164
|
+
__ONEWORKS_RUNTIME_PROTOCOL_CONSUMER__: '1',
|
|
165
|
+
__ONEWORKS_RUNTIME_PROTOCOL_SESSION_ID__: params.sessionId
|
|
166
|
+
}, { workspaceFolder: params.cwd })),
|
|
167
|
+
stdio: 'ignore'
|
|
168
|
+
})
|
|
169
|
+
child.unref()
|
|
170
|
+
return child.pid
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const startRuntimeResumeConsumer = (params: {
|
|
174
|
+
cwd: string
|
|
175
|
+
effort?: RuntimeSessionCommandEnvelope['effort']
|
|
176
|
+
env?: NodeJS.ProcessEnv
|
|
177
|
+
model?: string
|
|
178
|
+
permissionMode?: RuntimeSessionCommandEnvelope['permissionMode']
|
|
179
|
+
sessionId: string
|
|
180
|
+
}) => {
|
|
181
|
+
const cliEntrypoint = process.argv[1]
|
|
182
|
+
if (cliEntrypoint == null || cliEntrypoint.trim() === '') {
|
|
183
|
+
throw new Error('Unable to resolve One Works CLI entrypoint for runtime resume consumer.')
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const args = [
|
|
187
|
+
cliEntrypoint,
|
|
188
|
+
'__run',
|
|
189
|
+
'--print',
|
|
190
|
+
'--output-format',
|
|
191
|
+
'stream-json',
|
|
192
|
+
'--resume',
|
|
193
|
+
params.sessionId
|
|
194
|
+
]
|
|
195
|
+
pushOption(args, '--model', params.model)
|
|
196
|
+
pushOption(args, '--effort', params.effort)
|
|
197
|
+
pushOption(args, '--permission-mode', params.permissionMode)
|
|
198
|
+
|
|
199
|
+
const child = spawn(process.execPath, args, {
|
|
200
|
+
cwd: params.cwd,
|
|
201
|
+
detached: true,
|
|
202
|
+
env: sanitizeRuntimeConsumerEnv(mergeProcessEnvWithProjectEnv({
|
|
203
|
+
...(params.env ?? {}),
|
|
204
|
+
__ONEWORKS_RUNTIME_PROTOCOL_CONSUMER__: '1',
|
|
205
|
+
__ONEWORKS_RUNTIME_PROTOCOL_SESSION_ID__: params.sessionId
|
|
206
|
+
}, { workspaceFolder: params.cwd })),
|
|
207
|
+
stdio: 'ignore'
|
|
208
|
+
})
|
|
209
|
+
child.unref()
|
|
210
|
+
return child.pid
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const appendFollowUpRuntimeCommand = async (
|
|
214
|
+
command: RuntimeSessionCommandEnvelope,
|
|
215
|
+
options: ExecuteRuntimeProtocolCommandOptions,
|
|
216
|
+
type: 'resume' | 'send_message'
|
|
217
|
+
) => {
|
|
218
|
+
const sessionId = requiredString(command, 'sessionId')
|
|
219
|
+
const status = await readRuntimeStatus(options.cwd, sessionId, options.env)
|
|
220
|
+
const result = await appendRuntimeCommand({
|
|
221
|
+
cwd: options.cwd,
|
|
222
|
+
commandId: command.commandId,
|
|
223
|
+
env: options.env,
|
|
224
|
+
memberKey: command.memberKey,
|
|
225
|
+
message: command.message ?? command.content,
|
|
226
|
+
now: options.now,
|
|
227
|
+
priority: command.priority,
|
|
228
|
+
roomId: command.roomId,
|
|
229
|
+
runId: command.runId,
|
|
230
|
+
sessionId,
|
|
231
|
+
source: command.source,
|
|
232
|
+
type
|
|
233
|
+
})
|
|
234
|
+
const consumerPid = shouldStartRuntimeResumeConsumer({
|
|
235
|
+
command,
|
|
236
|
+
env: options.env,
|
|
237
|
+
status: status.status
|
|
238
|
+
})
|
|
239
|
+
? startRuntimeResumeConsumer({
|
|
240
|
+
cwd: options.cwd,
|
|
241
|
+
effort: optionalEffort(command.effort),
|
|
242
|
+
env: options.env,
|
|
243
|
+
model: optionalString(command.model),
|
|
244
|
+
permissionMode: optionalPermissionMode(command.permissionMode),
|
|
245
|
+
sessionId
|
|
246
|
+
})
|
|
247
|
+
: undefined
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
...result,
|
|
251
|
+
...(consumerPid != null ? { consumerPid } : {})
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export const executeRuntimeProtocolCommand = async (
|
|
256
|
+
input: RuntimeSessionCommandEnvelope | unknown,
|
|
257
|
+
options: ExecuteRuntimeProtocolCommandOptions
|
|
258
|
+
): Promise<RuntimeSessionResultEnvelope> => {
|
|
259
|
+
let command: RuntimeSessionCommandEnvelope
|
|
260
|
+
try {
|
|
261
|
+
command = parseRuntimeProtocolCommand(input)
|
|
262
|
+
} catch (error) {
|
|
263
|
+
return toErrorResult(fallbackProtocolCommand(input), error)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
switch (command.type) {
|
|
268
|
+
case 'session.start': {
|
|
269
|
+
const hostSessionId = trimOptional(command.hostSessionId) ??
|
|
270
|
+
trimOptional(options.env?.__ONEWORKS_AGENT_ROOM_HOST_SESSION_ID__)
|
|
271
|
+
const roomId = trimOptional(command.roomId) ?? trimOptional(options.env?.__ONEWORKS_AGENT_ROOM_ID__)
|
|
272
|
+
const roomTitle = trimOptional(command.roomTitle) ?? trimOptional(options.env?.__ONEWORKS_AGENT_ROOM_TITLE__)
|
|
273
|
+
const adapter = optionalString(command.adapter) ??
|
|
274
|
+
envDefault(options.env, '__ONEWORKS_RUNTIME_PROTOCOL_DEFAULT_ADAPTER__')
|
|
275
|
+
const model = optionalString(command.model) ??
|
|
276
|
+
envDefault(options.env, '__ONEWORKS_RUNTIME_PROTOCOL_DEFAULT_MODEL__')
|
|
277
|
+
const effort = optionalEffort(command.effort) ??
|
|
278
|
+
optionalEffort(envDefault(options.env, '__ONEWORKS_RUNTIME_PROTOCOL_DEFAULT_EFFORT__'))
|
|
279
|
+
const permissionMode = optionalPermissionMode(command.permissionMode) ??
|
|
280
|
+
optionalPermissionMode(envDefault(options.env, '__ONEWORKS_RUNTIME_PROTOCOL_DEFAULT_PERMISSION_MODE__'))
|
|
281
|
+
const entity = requiredString(command, 'entity')
|
|
282
|
+
const message = command.message ?? command.content ?? ''
|
|
283
|
+
const result = await createRuntimeSession({
|
|
284
|
+
cwd: options.cwd,
|
|
285
|
+
commandId: command.commandId,
|
|
286
|
+
entity,
|
|
287
|
+
adapter,
|
|
288
|
+
effort,
|
|
289
|
+
model,
|
|
290
|
+
env: options.env,
|
|
291
|
+
hostSessionId,
|
|
292
|
+
memberAvatar: command.memberAvatar,
|
|
293
|
+
memberKey: command.memberKey,
|
|
294
|
+
memberLabel: command.memberLabel,
|
|
295
|
+
message,
|
|
296
|
+
now: options.now,
|
|
297
|
+
parentSessionId: command.parentSessionId ?? hostSessionId,
|
|
298
|
+
priority: command.priority,
|
|
299
|
+
roomId,
|
|
300
|
+
roomTitle,
|
|
301
|
+
runId: command.runId,
|
|
302
|
+
runTitle: command.runTitle,
|
|
303
|
+
sessionId: command.sessionId,
|
|
304
|
+
source: command.source,
|
|
305
|
+
permissionMode,
|
|
306
|
+
title: command.title
|
|
307
|
+
})
|
|
308
|
+
const consumerPid = shouldStartRuntimeConsumer(command, options.env)
|
|
309
|
+
? startRuntimeConsumer({
|
|
310
|
+
adapter,
|
|
311
|
+
command,
|
|
312
|
+
cwd: options.cwd,
|
|
313
|
+
effort,
|
|
314
|
+
entity,
|
|
315
|
+
env: options.env,
|
|
316
|
+
message,
|
|
317
|
+
model,
|
|
318
|
+
permissionMode,
|
|
319
|
+
sessionId: result.sessionId
|
|
320
|
+
})
|
|
321
|
+
: undefined
|
|
322
|
+
return toSuccessResult(
|
|
323
|
+
command,
|
|
324
|
+
{
|
|
325
|
+
...result,
|
|
326
|
+
...(consumerPid != null ? { consumerPid } : {})
|
|
327
|
+
}
|
|
328
|
+
)
|
|
329
|
+
}
|
|
330
|
+
case 'session.message':
|
|
331
|
+
return toSuccessResult(
|
|
332
|
+
command,
|
|
333
|
+
await appendFollowUpRuntimeCommand(command, options, 'send_message')
|
|
334
|
+
)
|
|
335
|
+
case 'session.resume':
|
|
336
|
+
return toSuccessResult(
|
|
337
|
+
command,
|
|
338
|
+
await appendFollowUpRuntimeCommand(command, options, 'resume')
|
|
339
|
+
)
|
|
340
|
+
case 'session.stop':
|
|
341
|
+
return toSuccessResult(
|
|
342
|
+
command,
|
|
343
|
+
await appendRuntimeCommand({
|
|
344
|
+
cwd: options.cwd,
|
|
345
|
+
commandId: command.commandId,
|
|
346
|
+
env: options.env,
|
|
347
|
+
memberKey: command.memberKey,
|
|
348
|
+
now: options.now,
|
|
349
|
+
priority: command.priority,
|
|
350
|
+
roomId: command.roomId,
|
|
351
|
+
runId: command.runId,
|
|
352
|
+
sessionId: requiredString(command, 'sessionId'),
|
|
353
|
+
source: command.source,
|
|
354
|
+
type: command.mode === 'force' || command.mode === 'kill' ? 'kill' : 'stop'
|
|
355
|
+
})
|
|
356
|
+
)
|
|
357
|
+
case 'session.submit':
|
|
358
|
+
return toSuccessResult(
|
|
359
|
+
command,
|
|
360
|
+
await appendRuntimeCommand({
|
|
361
|
+
cwd: options.cwd,
|
|
362
|
+
commandId: command.commandId,
|
|
363
|
+
data: command.data,
|
|
364
|
+
env: options.env,
|
|
365
|
+
memberKey: command.memberKey,
|
|
366
|
+
now: options.now,
|
|
367
|
+
priority: command.priority,
|
|
368
|
+
requestId: command.requestId ?? command.interactionId,
|
|
369
|
+
roomId: command.roomId,
|
|
370
|
+
runId: command.runId,
|
|
371
|
+
sessionId: requiredString(command, 'sessionId'),
|
|
372
|
+
source: command.source,
|
|
373
|
+
type: 'submit_input',
|
|
374
|
+
value: command.value
|
|
375
|
+
})
|
|
376
|
+
)
|
|
377
|
+
case 'session.status':
|
|
378
|
+
return toSuccessResult(
|
|
379
|
+
command,
|
|
380
|
+
await readRuntimeStatus(options.cwd, requiredString(command, 'sessionId'), options.env)
|
|
381
|
+
)
|
|
382
|
+
case 'session.events':
|
|
383
|
+
return toSuccessResult(command, {
|
|
384
|
+
sessionId: requiredString(command, 'sessionId'),
|
|
385
|
+
events: await readRuntimeEvents(options.cwd, requiredString(command, 'sessionId'), options.env)
|
|
386
|
+
})
|
|
387
|
+
}
|
|
388
|
+
} catch (error) {
|
|
389
|
+
return toErrorResult(command, error)
|
|
390
|
+
}
|
|
391
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import type { ChatMessageContent } from '@oneworks/core'
|
|
2
|
+
import { orderRuntimeCommands } from '@oneworks/runtime-store'
|
|
3
|
+
import type { RuntimeCommand } from '@oneworks/runtime-store'
|
|
4
|
+
|
|
5
|
+
import { readRuntimeCommands, readRuntimeEvents } from '../agent/runtime-store'
|
|
6
|
+
import type { RuntimeEventSink } from './runtime-event-sink'
|
|
7
|
+
import type { CliInputSession } from './types'
|
|
8
|
+
|
|
9
|
+
export interface RuntimeCommandBridgeSession extends CliInputSession {
|
|
10
|
+
kill?: () => void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface RuntimeCommandBridgeOptions {
|
|
14
|
+
cwd: string
|
|
15
|
+
env?: NodeJS.ProcessEnv
|
|
16
|
+
intervalMs?: number
|
|
17
|
+
session: RuntimeCommandBridgeSession
|
|
18
|
+
sessionId: string
|
|
19
|
+
sink: RuntimeEventSink
|
|
20
|
+
submitInput?: (params: { interactionId?: string; data: string | string[] }) => Promise<void>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const toMessageContent = (command: RuntimeCommand): ChatMessageContent[] => {
|
|
24
|
+
const runtimeContentItems = normalizeCommandContentItems(command.runtimeContentItems)
|
|
25
|
+
if (runtimeContentItems != null) {
|
|
26
|
+
return runtimeContentItems
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const contentItems = normalizeCommandContentItems(command.contentItems)
|
|
30
|
+
if (contentItems != null) {
|
|
31
|
+
return contentItems
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const content = typeof command.runtimeMessage === 'string'
|
|
35
|
+
? command.runtimeMessage
|
|
36
|
+
: command.content ?? command.message ?? ''
|
|
37
|
+
return [{ type: 'text', text: content }]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const isChatMessageContent = (item: unknown): item is ChatMessageContent => {
|
|
41
|
+
if (item == null || typeof item !== 'object') {
|
|
42
|
+
return false
|
|
43
|
+
}
|
|
44
|
+
const record = item as Record<string, unknown>
|
|
45
|
+
switch (record.type) {
|
|
46
|
+
case 'text':
|
|
47
|
+
return typeof record.text === 'string'
|
|
48
|
+
case 'image':
|
|
49
|
+
return typeof record.url === 'string'
|
|
50
|
+
case 'file':
|
|
51
|
+
return typeof record.path === 'string'
|
|
52
|
+
case 'tool_use':
|
|
53
|
+
return typeof record.id === 'string' && typeof record.name === 'string'
|
|
54
|
+
case 'tool_result':
|
|
55
|
+
return typeof record.tool_use_id === 'string'
|
|
56
|
+
default:
|
|
57
|
+
return false
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const normalizeCommandContentItems = (value: unknown): ChatMessageContent[] | undefined => {
|
|
62
|
+
if (!Array.isArray(value) || !value.every(isChatMessageContent)) {
|
|
63
|
+
return undefined
|
|
64
|
+
}
|
|
65
|
+
return structuredClone(value)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const toSubmitData = (command: RuntimeCommand): string | string[] => {
|
|
69
|
+
if (typeof command.data === 'string' || Array.isArray(command.data)) {
|
|
70
|
+
return command.data
|
|
71
|
+
}
|
|
72
|
+
if (typeof command.value === 'string') {
|
|
73
|
+
return command.value
|
|
74
|
+
}
|
|
75
|
+
if (Array.isArray(command.value) && command.value.every(item => typeof item === 'string')) {
|
|
76
|
+
return command.value
|
|
77
|
+
}
|
|
78
|
+
if (command.value == null) {
|
|
79
|
+
throw new Error('submit_input command requires value or data.')
|
|
80
|
+
}
|
|
81
|
+
return JSON.stringify(command.value)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const dispatchCommand = async (
|
|
85
|
+
command: RuntimeCommand,
|
|
86
|
+
options: RuntimeCommandBridgeOptions
|
|
87
|
+
) => {
|
|
88
|
+
switch (command.type) {
|
|
89
|
+
case 'send_message':
|
|
90
|
+
case 'resume':
|
|
91
|
+
await options.sink.ackCommand(command)
|
|
92
|
+
await options.sink.recordMessageCommand(command)
|
|
93
|
+
options.session.emit({ type: 'message', content: toMessageContent(command) })
|
|
94
|
+
return
|
|
95
|
+
case 'start':
|
|
96
|
+
if (command.messageDelivery === 'bridge') {
|
|
97
|
+
await options.sink.ackCommand(command)
|
|
98
|
+
await options.sink.recordMessageCommand(command)
|
|
99
|
+
options.session.emit({ type: 'message', content: toMessageContent(command) })
|
|
100
|
+
}
|
|
101
|
+
return
|
|
102
|
+
case 'submit_input':
|
|
103
|
+
await options.sink.ackCommand(command)
|
|
104
|
+
await options.submitInput?.({
|
|
105
|
+
interactionId: command.requestId ?? command.interactionId,
|
|
106
|
+
data: toSubmitData(command)
|
|
107
|
+
})
|
|
108
|
+
await options.sink.recordInputSubmitted(command)
|
|
109
|
+
return
|
|
110
|
+
case 'stop':
|
|
111
|
+
await options.sink.ackCommand(command)
|
|
112
|
+
options.session.emit({ type: 'stop' })
|
|
113
|
+
return
|
|
114
|
+
case 'kill':
|
|
115
|
+
await options.sink.ackCommand(command)
|
|
116
|
+
if (options.session.kill != null) {
|
|
117
|
+
options.session.kill()
|
|
118
|
+
} else {
|
|
119
|
+
options.session.emit({ type: 'stop' })
|
|
120
|
+
}
|
|
121
|
+
break
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const PROCESSED_COMMAND_EVENT_TYPES = new Set([
|
|
126
|
+
'command_ack',
|
|
127
|
+
'command_failed',
|
|
128
|
+
'command_cancelled',
|
|
129
|
+
'input_submitted'
|
|
130
|
+
])
|
|
131
|
+
|
|
132
|
+
const readProcessedCommandIds = async (options: RuntimeCommandBridgeOptions) => {
|
|
133
|
+
const processed = new Set<string>()
|
|
134
|
+
const events = await readRuntimeEvents(options.cwd, options.sessionId, options.env)
|
|
135
|
+
for (const event of events) {
|
|
136
|
+
if (
|
|
137
|
+
typeof event.commandId === 'string' &&
|
|
138
|
+
event.commandId.trim() !== '' &&
|
|
139
|
+
PROCESSED_COMMAND_EVENT_TYPES.has(event.type)
|
|
140
|
+
) {
|
|
141
|
+
processed.add(event.commandId)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return processed
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export const attachRuntimeCommandBridge = async (options: RuntimeCommandBridgeOptions) => {
|
|
148
|
+
const processed = await readProcessedCommandIds(options)
|
|
149
|
+
let stopped = false
|
|
150
|
+
let tickQueue = Promise.resolve()
|
|
151
|
+
|
|
152
|
+
const tick = async () => {
|
|
153
|
+
const commands = await readRuntimeCommands(options.cwd, options.sessionId, options.env)
|
|
154
|
+
const pending = orderRuntimeCommands(commands.filter(command => !processed.has(command.id)))
|
|
155
|
+
for (const command of pending) {
|
|
156
|
+
processed.add(command.id)
|
|
157
|
+
try {
|
|
158
|
+
await dispatchCommand(command, options)
|
|
159
|
+
} catch (error) {
|
|
160
|
+
await options.sink.failCommand(command, error)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const runTick = () => {
|
|
166
|
+
tickQueue = tickQueue
|
|
167
|
+
.catch(() => {})
|
|
168
|
+
.then(() => tick())
|
|
169
|
+
.catch((error) => {
|
|
170
|
+
console.error(
|
|
171
|
+
`[runtime-protocol] Failed to process runtime command: ${
|
|
172
|
+
error instanceof Error ? error.message : String(error)
|
|
173
|
+
}`
|
|
174
|
+
)
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
runTick()
|
|
179
|
+
const timer = setInterval(() => {
|
|
180
|
+
if (!stopped) {
|
|
181
|
+
runTick()
|
|
182
|
+
}
|
|
183
|
+
}, options.intervalMs ?? 1000)
|
|
184
|
+
|
|
185
|
+
return async () => {
|
|
186
|
+
stopped = true
|
|
187
|
+
clearInterval(timer)
|
|
188
|
+
await tickQueue
|
|
189
|
+
}
|
|
190
|
+
}
|