@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,56 @@
1
+ import { existsSync } from 'node:fs'
2
+ import process from 'node:process'
3
+
4
+ import { buildCommand, getStore, trimRequired } from './runtime-store-shared'
5
+ import type { AppendRuntimeCommandParams } from './runtime-store-shared'
6
+
7
+ export const appendRuntimeCommand = async (params: AppendRuntimeCommandParams) => {
8
+ const sessionId = trimRequired(params.sessionId, 'session')
9
+ const store = await getStore(params.cwd, params.env)
10
+ const session = store.session(sessionId)
11
+ if (!existsSync(session.sessionPath)) throw new Error(`Runtime session "${sessionId}" not found.`)
12
+
13
+ const content = params.type === 'send_message' || params.type === 'resume'
14
+ ? trimRequired(params.message, 'message')
15
+ : params.message?.trim()
16
+ const requestId = params.type === 'submit_input'
17
+ ? trimRequired(params.requestId, 'request')
18
+ : params.requestId?.trim()
19
+ const submitValue = params.value ?? params.data
20
+ if (params.type === 'submit_input' && submitValue == null) {
21
+ throw new Error('value is required.')
22
+ }
23
+ const value = typeof params.value === 'string' ? params.value.trim() : params.value
24
+ const data = params.data
25
+ const command = await session.appendCommand(buildCommand({
26
+ sessionId,
27
+ type: params.type,
28
+ ts: (params.now ?? Date.now)(),
29
+ content,
30
+ commandId: params.commandId,
31
+ memberKey: params.memberKey,
32
+ priority: params.priority,
33
+ requestId,
34
+ roomId: params.roomId,
35
+ runId: params.runId,
36
+ source: params.source,
37
+ value,
38
+ data
39
+ }))
40
+
41
+ return {
42
+ ok: true,
43
+ commandId: command.commandId ?? command.id,
44
+ runtimeCommandId: command.id,
45
+ sessionId,
46
+ storePath: session.sessionPath,
47
+ type: command.type,
48
+ status: 'queued' as const
49
+ }
50
+ }
51
+
52
+ export const readRuntimeCommands = async (
53
+ cwd: string,
54
+ sessionId: string,
55
+ env: NodeJS.ProcessEnv = process.env
56
+ ) => (await getStore(cwd, env)).session(sessionId).readCommands()
@@ -0,0 +1,23 @@
1
+ import { existsSync } from 'node:fs'
2
+ import process from 'node:process'
3
+
4
+ import type { RuntimeEvent, RuntimeEventDraft } from '@oneworks/runtime-store'
5
+
6
+ import { getStore } from './runtime-store-shared'
7
+
8
+ export const readRuntimeEvents = async (
9
+ cwd: string,
10
+ sessionId: string,
11
+ env: NodeJS.ProcessEnv = process.env
12
+ ) => {
13
+ const session = (await getStore(cwd, env)).session(sessionId)
14
+ if (!existsSync(session.sessionPath)) throw new Error(`Runtime session "${sessionId}" not found.`)
15
+ return session.replayEvents()
16
+ }
17
+
18
+ export const appendRuntimeEventForTest = async (
19
+ cwd: string,
20
+ sessionId: string,
21
+ event: RuntimeEvent | RuntimeEventDraft,
22
+ env: NodeJS.ProcessEnv = process.env
23
+ ) => (await getStore(cwd, env)).session(sessionId).appendEvent(event)
@@ -0,0 +1,170 @@
1
+ import { existsSync } from 'node:fs'
2
+ import path from 'node:path'
3
+ import process from 'node:process'
4
+
5
+ import { DEFAULT_SUPPORTED_PROTOCOL_RANGE, getCurrentProtocolVersion } from '@oneworks/runtime-protocol'
6
+ import { getSessionStorePath, resolveRuntimeRoot } from '@oneworks/runtime-store'
7
+ import type { RuntimeHeartbeat, RuntimeMeta, RuntimeState } from '@oneworks/runtime-store'
8
+
9
+ import { buildCommand, createSessionId, getStore, trimRequired } from './runtime-store-shared'
10
+ import type { CreateRuntimeSessionParams } from './runtime-store-shared'
11
+
12
+ export const createRuntimeRoomIdForHostSession = (hostSessionId: string) => `room_${hostSessionId}`
13
+
14
+ export const resolveRuntimeSessionStore = async (
15
+ cwd: string,
16
+ sessionId: string,
17
+ env: NodeJS.ProcessEnv = process.env
18
+ ) => {
19
+ const root = await resolveRuntimeRoot({ cwd, env })
20
+ const storePath = getSessionStorePath(root, trimRequired(sessionId, 'session'))
21
+ return {
22
+ root,
23
+ sessionId,
24
+ storePath,
25
+ metaPath: path.resolve(storePath, 'meta.json'),
26
+ eventsPath: path.resolve(storePath, 'events.jsonl'),
27
+ commandsPath: path.resolve(storePath, 'commands.jsonl'),
28
+ statePath: path.resolve(storePath, 'state.json'),
29
+ heartbeatPath: path.resolve(storePath, 'heartbeat.json'),
30
+ locksPath: path.resolve(storePath, 'locks')
31
+ }
32
+ }
33
+
34
+ export const createRuntimeSession = async (params: CreateRuntimeSessionParams) => {
35
+ const now = params.now ?? Date.now
36
+ const sessionId = params.sessionId ?? createSessionId()
37
+ const entity = trimRequired(params.entity, 'entity')
38
+ const content = trimRequired(params.message, 'message')
39
+ const title = params.title?.trim() || `${entity} session`
40
+ const adapter = params.adapter?.trim() || undefined
41
+ const effort = params.effort
42
+ const model = params.model?.trim() || undefined
43
+ const permissionMode = params.permissionMode
44
+ const parentSessionId = params.parentSessionId?.trim() || undefined
45
+ const hostSessionId = params.hostSessionId?.trim() || parentSessionId
46
+ const roomId = params.roomId?.trim() ||
47
+ (hostSessionId == null ? undefined : createRuntimeRoomIdForHostSession(hostSessionId))
48
+ const roomTitle = params.roomTitle?.trim() || undefined
49
+ const memberKey = params.memberKey?.trim() || entity
50
+ const memberAvatar = params.memberAvatar?.trim() || undefined
51
+ const memberLabel = params.memberLabel?.trim() || entity
52
+ const runId = params.runId?.trim() || sessionId
53
+ const runTitle = params.runTitle?.trim() || title
54
+ const ts = now()
55
+ const store = await getStore(params.cwd, params.env)
56
+ const session = await store.createSession(
57
+ {
58
+ protocolVersion: getCurrentProtocolVersion(),
59
+ supportedProtocolRange: DEFAULT_SUPPORTED_PROTOCOL_RANGE,
60
+ sessionId,
61
+ title,
62
+ entity,
63
+ ...(adapter != null ? { adapter } : {}),
64
+ ...(effort != null ? { effort } : {}),
65
+ ...(model != null ? { model } : {}),
66
+ ...(permissionMode != null ? { permissionMode } : {}),
67
+ cwd: params.cwd,
68
+ ...(parentSessionId != null ? { parentSessionId } : {}),
69
+ ...(roomId != null ? { roomId } : {}),
70
+ ...(roomTitle != null ? { roomTitle } : {}),
71
+ ...(hostSessionId != null ? { hostSessionId } : {}),
72
+ memberKey,
73
+ ...(memberAvatar != null ? { memberAvatar } : {}),
74
+ memberKind: 'entity',
75
+ memberLabel,
76
+ runId,
77
+ runTitle,
78
+ createdAt: ts,
79
+ needsEngineConsumer: true
80
+ } satisfies RuntimeMeta
81
+ )
82
+
83
+ await Promise.all([
84
+ session.writeState(buildInitialState({ sessionId, title, ts })),
85
+ session.writeHeartbeat(buildInitialHeartbeat({ sessionId, ts }))
86
+ ])
87
+ const startCommand = await session.appendCommand(buildCommand({
88
+ sessionId,
89
+ type: 'start',
90
+ ts,
91
+ content,
92
+ commandId: params.commandId,
93
+ entity,
94
+ adapter,
95
+ effort,
96
+ model,
97
+ memberKey,
98
+ permissionMode,
99
+ priority: params.priority,
100
+ ...(roomId != null ? { roomId } : {}),
101
+ runId,
102
+ source: params.source,
103
+ title
104
+ }))
105
+
106
+ return {
107
+ ...(params.commandId != null
108
+ ? {
109
+ commandId: startCommand.commandId,
110
+ runtimeCommandId: startCommand.id
111
+ }
112
+ : {}),
113
+ sessionId,
114
+ storePath: session.sessionPath,
115
+ status: 'starting',
116
+ title,
117
+ ...(hostSessionId != null ? { hostSessionId } : {}),
118
+ ...(roomId != null ? { roomId } : {})
119
+ }
120
+ }
121
+
122
+ const buildInitialState = (params: {
123
+ sessionId: string
124
+ title: string
125
+ ts: number
126
+ }): RuntimeState => ({
127
+ protocolVersion: getCurrentProtocolVersion(),
128
+ supportedProtocolRange: DEFAULT_SUPPORTED_PROTOCOL_RANGE,
129
+ sessionId: params.sessionId,
130
+ status: 'starting',
131
+ title: params.title,
132
+ lastSeq: 0,
133
+ updatedAt: params.ts,
134
+ needsEngineConsumer: true
135
+ })
136
+
137
+ const buildInitialHeartbeat = (params: {
138
+ sessionId: string
139
+ ts: number
140
+ }): RuntimeHeartbeat => ({
141
+ protocolVersion: getCurrentProtocolVersion(),
142
+ supportedProtocolRange: DEFAULT_SUPPORTED_PROTOCOL_RANGE,
143
+ sessionId: params.sessionId,
144
+ runtimeId: 'pending_engine_consumer',
145
+ status: 'starting',
146
+ updatedAt: params.ts
147
+ })
148
+
149
+ export const readRuntimeStatus = async (
150
+ cwd: string,
151
+ sessionId: string,
152
+ env: NodeJS.ProcessEnv = process.env
153
+ ) => {
154
+ const session = (await getStore(cwd, env)).session(sessionId)
155
+ if (!existsSync(session.sessionPath)) throw new Error(`Runtime session "${sessionId}" not found.`)
156
+ const [meta, state, heartbeat] = await Promise.all([
157
+ session.readMeta(),
158
+ session.readState(),
159
+ session.readHeartbeat()
160
+ ])
161
+ return {
162
+ sessionId,
163
+ storePath: session.sessionPath,
164
+ status: state?.status ?? heartbeat?.status ?? 'starting',
165
+ title: String(state?.title ?? meta?.title ?? ''),
166
+ meta,
167
+ state,
168
+ heartbeat
169
+ }
170
+ }
@@ -0,0 +1,139 @@
1
+ import { randomUUID } from 'node:crypto'
2
+ import process from 'node:process'
3
+
4
+ import { DEFAULT_SUPPORTED_PROTOCOL_RANGE, getCurrentProtocolVersion } from '@oneworks/runtime-protocol'
5
+ import { FileRuntimeStore, resolveRuntimeRoot } from '@oneworks/runtime-store'
6
+ import type { RuntimeCommand } from '@oneworks/runtime-store'
7
+
8
+ export type RuntimeCommandType =
9
+ | 'start'
10
+ | 'send_message'
11
+ | 'stop'
12
+ | 'kill'
13
+ | 'submit_input'
14
+ | 'resume'
15
+
16
+ export interface CreateRuntimeSessionParams {
17
+ cwd: string
18
+ entity: string
19
+ title?: string
20
+ message: string
21
+ adapter?: string
22
+ effort?: 'low' | 'medium' | 'high' | 'max'
23
+ model?: string
24
+ commandId?: string
25
+ env?: NodeJS.ProcessEnv
26
+ hostSessionId?: string
27
+ memberAvatar?: string
28
+ memberLabel?: string
29
+ memberKey?: string
30
+ now?: () => number
31
+ parentSessionId?: string
32
+ priority?: number
33
+ roomId?: string
34
+ roomTitle?: string
35
+ runId?: string
36
+ runTitle?: string
37
+ sessionId?: string
38
+ source?: string
39
+ permissionMode?: 'default' | 'acceptEdits' | 'plan' | 'dontAsk' | 'bypassPermissions'
40
+ }
41
+
42
+ export interface AppendRuntimeCommandParams {
43
+ cwd: string
44
+ sessionId: string
45
+ type: Exclude<RuntimeCommandType, 'start'>
46
+ commandId?: string
47
+ message?: string
48
+ memberKey?: string
49
+ priority?: number
50
+ requestId?: string
51
+ roomId?: string
52
+ runId?: string
53
+ source?: string
54
+ value?: unknown
55
+ data?: string | string[]
56
+ env?: NodeJS.ProcessEnv
57
+ now?: () => number
58
+ }
59
+
60
+ export const trimRequired = (value: string | undefined, name: string) => {
61
+ const normalized = value?.trim() ?? ''
62
+ if (normalized === '') throw new Error(`${name} is required.`)
63
+ return normalized
64
+ }
65
+
66
+ export const getStore = async (cwd: string, env: NodeJS.ProcessEnv = process.env) =>
67
+ new FileRuntimeStore(await resolveRuntimeRoot({ cwd, env }))
68
+
69
+ const getCommandPriority = (type: RuntimeCommandType) => {
70
+ switch (type) {
71
+ case 'stop':
72
+ case 'kill':
73
+ return 0
74
+ case 'submit_input':
75
+ return 10
76
+ case 'start':
77
+ case 'send_message':
78
+ case 'resume':
79
+ return 20
80
+ }
81
+ }
82
+
83
+ const commandTypeToMode = (type: RuntimeCommandType) => {
84
+ if (type === 'stop') return 'graceful'
85
+ if (type === 'kill') return 'force'
86
+ return undefined
87
+ }
88
+
89
+ export const createSessionId = () => `sess_${randomUUID()}`
90
+
91
+ export const buildCommand = (params: {
92
+ sessionId: string
93
+ type: RuntimeCommandType
94
+ ts: number
95
+ content?: string
96
+ commandId?: string
97
+ requestId?: string
98
+ value?: unknown
99
+ data?: string | string[]
100
+ entity?: string
101
+ adapter?: string
102
+ effort?: 'low' | 'medium' | 'high' | 'max'
103
+ model?: string
104
+ memberKey?: string
105
+ permissionMode?: 'default' | 'acceptEdits' | 'plan' | 'dontAsk' | 'bypassPermissions'
106
+ roomId?: string
107
+ runId?: string
108
+ priority?: number
109
+ source?: string
110
+ title?: string
111
+ }): RuntimeCommand => {
112
+ const command: RuntimeCommand = {
113
+ protocolVersion: getCurrentProtocolVersion(),
114
+ supportedProtocolRange: DEFAULT_SUPPORTED_PROTOCOL_RANGE,
115
+ id: `cmd_${params.type}_${randomUUID()}`,
116
+ ts: params.ts,
117
+ sessionId: params.sessionId,
118
+ type: params.type,
119
+ priority: params.priority ?? getCommandPriority(params.type),
120
+ source: params.source ?? 'cli'
121
+ }
122
+ if (params.commandId != null) command.commandId = params.commandId
123
+ if (params.content != null) command.content = params.content
124
+ if (params.requestId != null) command.requestId = params.requestId
125
+ if (params.value != null) command.value = params.value
126
+ if (params.data != null) command.data = params.data
127
+ if (params.entity != null) command.entity = params.entity
128
+ if (params.adapter != null) command.adapter = params.adapter
129
+ if (params.effort != null) command.effort = params.effort
130
+ if (params.model != null) command.model = params.model
131
+ if (params.permissionMode != null) command.permissionMode = params.permissionMode
132
+ if (params.memberKey != null) command.memberKey = params.memberKey
133
+ if (params.roomId != null) command.roomId = params.roomId
134
+ if (params.runId != null) command.runId = params.runId
135
+ if (params.title != null) command.title = params.title
136
+ const mode = commandTypeToMode(params.type)
137
+ if (mode != null) command.mode = mode
138
+ return command
139
+ }
@@ -0,0 +1,4 @@
1
+ export * from './runtime-store-commands'
2
+ export * from './runtime-store-events'
3
+ export * from './runtime-store-session'
4
+ export type { RuntimeCommandType } from './runtime-store-shared'
@@ -0,0 +1,81 @@
1
+ import type { Command } from 'commander'
2
+
3
+ import { runAgentAction, runAgentCommand, runAgentEvents, runAgentStart, runAgentStatus } from './agent/actions'
4
+ import type { AgentEventsOptions, AgentSessionOptions, AgentStartOptions } from './agent/actions'
5
+
6
+ export function registerAgentCommand(program: Command) {
7
+ const agentCommand = program
8
+ .command('agent')
9
+ .description('Operate agent runtime sessions through the runtime store protocol')
10
+
11
+ agentCommand
12
+ .command('start')
13
+ .description('Create a runtime session store and queue the initial start command')
14
+ .requiredOption('--entity <entity>', 'Entity key to start, for example dev or qa')
15
+ .requiredOption('--message <message>', 'Initial user message for the runtime session')
16
+ .option('--title <title>', 'Display title for the runtime session')
17
+ .option('--host-session <session>', 'Parent host session id for automatic room binding')
18
+ .option('--room <room>', 'Existing agent room id to attach to')
19
+ .option('--room-title <title>', 'Agent room title hint when a room is created')
20
+ .option('--avatar <avatar>', 'Avatar label shown for this room member')
21
+ .option('--json', 'Print JSON output', false)
22
+ .action((opts: AgentStartOptions) => runAgentAction(() => runAgentStart(opts)))
23
+
24
+ agentCommand
25
+ .command('send')
26
+ .description('Queue a message for an existing runtime session')
27
+ .requiredOption('--session <session>', 'Runtime session id')
28
+ .requiredOption('--message <message>', 'Message to send')
29
+ .option('--json', 'Print JSON output', false)
30
+ .action((opts: AgentSessionOptions & { session: string }) =>
31
+ runAgentAction(() => runAgentCommand(opts, 'send_message'))
32
+ )
33
+
34
+ agentCommand
35
+ .command('stop')
36
+ .description('Queue a graceful stop command for an existing runtime session')
37
+ .requiredOption('--session <session>', 'Runtime session id')
38
+ .option('--json', 'Print JSON output', false)
39
+ .action((opts: AgentSessionOptions & { session: string }) => runAgentAction(() => runAgentCommand(opts, 'stop')))
40
+
41
+ agentCommand
42
+ .command('kill')
43
+ .description('Queue a force-kill command for an existing runtime session')
44
+ .requiredOption('--session <session>', 'Runtime session id')
45
+ .option('--json', 'Print JSON output', false)
46
+ .action((opts: AgentSessionOptions & { session: string }) => runAgentAction(() => runAgentCommand(opts, 'kill')))
47
+
48
+ agentCommand
49
+ .command('submit')
50
+ .description('Queue a response to a pending runtime input request')
51
+ .requiredOption('--session <session>', 'Runtime session id')
52
+ .requiredOption('--request <request>', 'Pending input or approval request id')
53
+ .requiredOption('--value <value>', 'Submitted value')
54
+ .option('--json', 'Print JSON output', false)
55
+ .action((opts: AgentSessionOptions & { session: string }) =>
56
+ runAgentAction(() => runAgentCommand(opts, 'submit_input'))
57
+ )
58
+
59
+ agentCommand
60
+ .command('resume')
61
+ .description('Queue a resume command and optional follow-up message')
62
+ .requiredOption('--session <session>', 'Runtime session id')
63
+ .requiredOption('--message <message>', 'Resume message')
64
+ .option('--json', 'Print JSON output', false)
65
+ .action((opts: AgentSessionOptions & { session: string }) => runAgentAction(() => runAgentCommand(opts, 'resume')))
66
+
67
+ agentCommand
68
+ .command('status')
69
+ .description('Read runtime session status from state, heartbeat, and metadata files')
70
+ .requiredOption('--session <session>', 'Runtime session id')
71
+ .option('--json', 'Print JSON output', false)
72
+ .action((opts: AgentSessionOptions & { session: string }) => runAgentAction(() => runAgentStatus(opts)))
73
+
74
+ agentCommand
75
+ .command('events')
76
+ .description('Read or follow runtime session events')
77
+ .requiredOption('--session <session>', 'Runtime session id')
78
+ .option('--follow', 'Keep following the events.jsonl file', false)
79
+ .option('--jsonl', 'Print JSON Lines output', false)
80
+ .action((opts: AgentEventsOptions & { session: string }) => runAgentAction(() => runAgentEvents(opts)))
81
+ }
@@ -0,0 +1,198 @@
1
+ import process from 'node:process'
2
+
3
+ import {
4
+ getBenchmarkCase,
5
+ listBenchmarkCases,
6
+ listBenchmarkCategories,
7
+ readBenchmarkResult,
8
+ runBenchmarkCase,
9
+ runBenchmarkCategory
10
+ } from '@oneworks/app-runtime'
11
+ import type { BenchmarkCase, BenchmarkCategory, BenchmarkResult } from '@oneworks/types'
12
+ import type { Command } from 'commander'
13
+
14
+ import { createAdapterOption } from './@core/adapter-option'
15
+ import { resolvePermissionModeOption } from './run/options'
16
+
17
+ interface BenchmarkRunOptions {
18
+ category: string
19
+ title?: string
20
+ concurrency?: string
21
+ adapter?: string
22
+ model?: string
23
+ effort?: 'low' | 'medium' | 'high' | 'max'
24
+ systemPrompt?: string
25
+ permissionMode?: 'default' | 'acceptEdits' | 'plan' | 'dontAsk' | 'bypassPermissions'
26
+ yolo?: boolean
27
+ }
28
+
29
+ const printResult = (result: BenchmarkResult) => {
30
+ console.table([{
31
+ Category: result.category,
32
+ Title: result.title,
33
+ Status: result.status,
34
+ Score: result.finalScore,
35
+ Test: result.scores.testScore,
36
+ Goal: result.scores.goalScore,
37
+ Reference: result.scores.referenceScore,
38
+ DurationMs: result.durationMs,
39
+ Timestamp: result.timestamp
40
+ }])
41
+
42
+ if (result.issues.length > 0) {
43
+ console.log('\nIssues:')
44
+ for (const issue of result.issues) {
45
+ console.log(`- ${issue}`)
46
+ }
47
+ }
48
+ }
49
+
50
+ const createEventPrinter = () => {
51
+ return (event: {
52
+ category: string
53
+ title?: string
54
+ phase: string
55
+ message: string
56
+ }) => {
57
+ const target = event.title ? `${event.category}/${event.title}` : event.category
58
+ console.log(`[benchmark:${target}] [${event.phase}] ${event.message}`)
59
+ }
60
+ }
61
+
62
+ export function registerBenchmarkCommand(program: Command) {
63
+ const benchmark = program
64
+ .command('benchmark')
65
+ .description('List and run benchmark cases')
66
+
67
+ benchmark
68
+ .command('list')
69
+ .description('List benchmark categories and cases')
70
+ .option('--category <category>', 'Filter by category')
71
+ .action(async (opts: { category?: string }) => {
72
+ const categories = await listBenchmarkCategories()
73
+ const cases = await listBenchmarkCases({
74
+ category: opts.category
75
+ })
76
+
77
+ if (categories.length === 0) {
78
+ console.log('No benchmark cases found.')
79
+ return
80
+ }
81
+
82
+ console.log('Categories:')
83
+ console.table(categories.map((item: BenchmarkCategory) => ({
84
+ Category: item.category,
85
+ Cases: item.caseCount,
86
+ Pass: item.lastStatuses.pass,
87
+ Partial: item.lastStatuses.partial,
88
+ Fail: item.lastStatuses.fail
89
+ })))
90
+
91
+ if (cases.length === 0) {
92
+ console.log(`No cases found for category ${opts.category}.`)
93
+ return
94
+ }
95
+
96
+ console.log('\nCases:')
97
+ console.table(cases.map((item: BenchmarkCase) => ({
98
+ Category: item.category,
99
+ Title: item.title,
100
+ Summary: item.summary,
101
+ Status: item.latestResult?.status ?? '-',
102
+ Score: item.latestResult?.finalScore ?? '-'
103
+ })))
104
+ })
105
+
106
+ benchmark
107
+ .command('run')
108
+ .description('Run one benchmark case or an entire category')
109
+ .requiredOption('--category <category>', 'Benchmark category')
110
+ .option('--title <title>', 'Benchmark title')
111
+ .option('--concurrency <count>', 'Category concurrency', '2')
112
+ .addOption(createAdapterOption('Adapter to use'))
113
+ .option('--model <model>', 'Model to use')
114
+ .option('--effort <effort>', 'Effort to use (low, medium, high, max)')
115
+ .option('--system-prompt <prompt>', 'Additional system prompt')
116
+ .option('--permission-mode <mode>', 'Permission mode')
117
+ .option('--yolo', 'Shortcut for --permission-mode bypassPermissions', false)
118
+ .action(async (opts: BenchmarkRunOptions) => {
119
+ opts.permissionMode = resolvePermissionModeOption(opts.permissionMode, opts.yolo)
120
+ const onEvent = createEventPrinter()
121
+ if (opts.title != null && opts.title !== '') {
122
+ const output = await runBenchmarkCase({
123
+ category: opts.category,
124
+ title: opts.title,
125
+ adapter: opts.adapter,
126
+ model: opts.model,
127
+ effort: opts.effort,
128
+ systemPrompt: opts.systemPrompt,
129
+ permissionMode: opts.permissionMode,
130
+ runtime: 'cli',
131
+ onEvent
132
+ })
133
+ printResult(output.result)
134
+ process.exit(output.result.status === 'fail' ? 1 : 0)
135
+ }
136
+
137
+ const concurrency = Number.parseInt(opts.concurrency ?? '2', 10)
138
+ const output = await runBenchmarkCategory({
139
+ category: opts.category,
140
+ concurrency: Number.isNaN(concurrency) ? 2 : concurrency,
141
+ adapter: opts.adapter,
142
+ model: opts.model,
143
+ effort: opts.effort,
144
+ systemPrompt: opts.systemPrompt,
145
+ permissionMode: opts.permissionMode,
146
+ runtime: 'cli',
147
+ onEvent
148
+ })
149
+
150
+ console.table(output.results.map((result: BenchmarkResult) => ({
151
+ Title: result.title,
152
+ Status: result.status,
153
+ Score: result.finalScore,
154
+ Test: result.scores.testScore,
155
+ Goal: result.scores.goalScore,
156
+ Reference: result.scores.referenceScore
157
+ })))
158
+
159
+ const hasFailure = output.results.some((result: BenchmarkResult) => result.status === 'fail')
160
+ process.exit(hasFailure ? 1 : 0)
161
+ })
162
+
163
+ benchmark
164
+ .command('show')
165
+ .description('Show benchmark case metadata and latest result')
166
+ .requiredOption('--category <category>', 'Benchmark category')
167
+ .requiredOption('--title <title>', 'Benchmark title')
168
+ .action(async (opts: { category: string; title: string }) => {
169
+ const caseItem = await getBenchmarkCase(opts)
170
+ if (caseItem == null) {
171
+ console.error(`Benchmark case not found: ${opts.category}/${opts.title}`)
172
+ process.exit(1)
173
+ }
174
+
175
+ console.log('Case:')
176
+ console.table([{
177
+ Category: caseItem.category,
178
+ Title: caseItem.title,
179
+ Summary: caseItem.summary,
180
+ BaseCommit: caseItem.frontmatter.baseCommit,
181
+ Setup: caseItem.frontmatter.setupCommand,
182
+ Test: caseItem.frontmatter.testCommand,
183
+ TimeoutSec: caseItem.frontmatter.timeoutSec
184
+ }])
185
+
186
+ console.log('\nRFC:')
187
+ console.log(caseItem.rfcBody)
188
+
189
+ const result = await readBenchmarkResult(process.cwd(), opts.category, opts.title)
190
+ if (result == null) {
191
+ console.log('\nNo result.json found.')
192
+ return
193
+ }
194
+
195
+ console.log('\nLatest Result:')
196
+ printResult(result)
197
+ })
198
+ }