@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,66 @@
1
+ import type { HookContext, HookInputs, HookOutputs, Plugin } from '@oneworks/hooks'
2
+
3
+ export const callPluginHook = async <K extends keyof HookInputs>(
4
+ eventName: K,
5
+ context: HookContext,
6
+ input: HookInputs[K],
7
+ plugins: Partial<Plugin>[] = []
8
+ ): Promise<HookOutputs[K]> => {
9
+ const { logger } = context
10
+ const filterPlugins = plugins.filter(
11
+ (
12
+ item
13
+ ): item is
14
+ & {
15
+ name?: string
16
+ }
17
+ & {
18
+ [k in K]: NonNullable<Plugin[k]>
19
+ } => !!item && !!item[eventName]
20
+ )
21
+
22
+ // 创建中间件链
23
+ let index = 0
24
+
25
+ const next = async (): Promise<HookOutputs[K]> => {
26
+ if (index >= filterPlugins.length) {
27
+ // 如果没有更多插件,返回默认结果
28
+ return { continue: true }
29
+ }
30
+
31
+ const currentPlugin = filterPlugins[index]
32
+ const { name = '<anonymous>', [eventName]: hook } = currentPlugin
33
+
34
+ // 增加索引,防止重复调用导致死循环或错误逻辑
35
+ index++
36
+
37
+ const withNameLogger = {
38
+ ...logger,
39
+ info: logger.info.bind(logger, `[plugin.${name}]`),
40
+ warn: logger.warn.bind(logger, `[plugin.${name}]`),
41
+ debug: logger.debug.bind(logger, `[plugin.${name}]`),
42
+ error: logger.error.bind(logger, `[plugin.${name}]`)
43
+ }
44
+ try {
45
+ return await hook(
46
+ {
47
+ ...context,
48
+ logger: withNameLogger
49
+ },
50
+ // @ts-ignore
51
+ input,
52
+ next
53
+ )
54
+ } catch (e) {
55
+ if (e instanceof Error) {
56
+ // 只有第一个插件抛出的错误才需要添加对应的插件名
57
+ if (!e.name.includes('[plugin.')) {
58
+ e.name = `${e.name}[plugin.${name}]`
59
+ }
60
+ }
61
+ throw e
62
+ }
63
+ }
64
+
65
+ return next()
66
+ }
package/src/mem-cli.ts ADDED
@@ -0,0 +1,19 @@
1
+ import process from 'node:process'
2
+
3
+ import { Command } from 'commander'
4
+
5
+ import { getCliVersion } from '#~/utils.js'
6
+
7
+ import { registerMemorySubcommands } from './commands/memory'
8
+
9
+ const program = new Command()
10
+
11
+ program
12
+ .name('oneworks mem')
13
+ .description('Read and write OneWorks channel memory from agent sessions')
14
+ .version(getCliVersion())
15
+ .showHelpAfterError()
16
+
17
+ registerMemorySubcommands(program)
18
+
19
+ program.parse(process.argv)
@@ -0,0 +1,250 @@
1
+ import fs from 'node:fs/promises'
2
+ import path from 'node:path'
3
+ import process from 'node:process'
4
+
5
+ import type { RunTaskOptions } from '@oneworks/app-runtime'
6
+ import type { AdapterQueryOptions, Cache, TaskDetail } from '@oneworks/types'
7
+ import { migrateProjectHomeSegment, resolveProjectOoPath } from '@oneworks/utils'
8
+ import { getCache, getCachePath, setCache } from '@oneworks/utils/cache'
9
+
10
+ export type CliOutputFormat = 'text' | 'json' | 'stream-json'
11
+
12
+ export interface CliSessionResumeRecord {
13
+ version: 1
14
+ ctxId: string
15
+ sessionId: string
16
+ cwd: string
17
+ description?: string
18
+ createdAt: number
19
+ updatedAt: number
20
+ resolvedAdapter?: string
21
+ taskOptions: RunTaskOptions
22
+ adapterOptions: Omit<AdapterQueryOptions, 'description' | 'onEvent' | 'type'>
23
+ outputFormat: CliOutputFormat
24
+ }
25
+
26
+ export interface CliSessionRecord {
27
+ resume?: CliSessionResumeRecord
28
+ detail?: TaskDetail
29
+ }
30
+
31
+ export interface CliSessionControlRecord {
32
+ signal: 'SIGTERM' | 'SIGKILL'
33
+ requestedAt: number
34
+ expiresAt: number
35
+ }
36
+
37
+ declare module '@oneworks/types' {
38
+ interface Cache {
39
+ 'cli-session': CliSessionResumeRecord
40
+ 'cli-session-control': CliSessionControlRecord
41
+ detail: TaskDetail
42
+ }
43
+ }
44
+
45
+ const readDirSafe = async (target: string) => {
46
+ try {
47
+ return await fs.readdir(target, { withFileTypes: true })
48
+ } catch (error) {
49
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') return []
50
+ throw error
51
+ }
52
+ }
53
+
54
+ const readCacheFileAtPath = async <K extends keyof Cache>(target: string): Promise<Cache[K] | undefined> => {
55
+ try {
56
+ const content = await fs.readFile(target, 'utf-8')
57
+ if (content.trim() === '') return undefined
58
+ return JSON.parse(content) as Cache[K]
59
+ } catch (error) {
60
+ if (
61
+ (error as NodeJS.ErrnoException).code === 'ENOENT' ||
62
+ error instanceof SyntaxError
63
+ ) {
64
+ return undefined
65
+ }
66
+ throw error
67
+ }
68
+ }
69
+
70
+ const getCacheFromRoot = async <K extends keyof Cache>(
71
+ cacheRoot: string,
72
+ ctxId: string,
73
+ sessionId: string,
74
+ key: K
75
+ ) => readCacheFileAtPath<K>(path.resolve(cacheRoot, ctxId, sessionId, `${String(key)}.json`))
76
+
77
+ const resolveCliSessionCacheRoot = (cwd: string) => resolveProjectOoPath(cwd, process.env, 'caches')
78
+
79
+ const getRecordUpdatedAt = (record: CliSessionRecord) =>
80
+ record.resume?.updatedAt ??
81
+ record.detail?.endTime ??
82
+ record.detail?.startTime ??
83
+ 0
84
+
85
+ const getRecordCreatedAt = (record: CliSessionRecord) =>
86
+ record.resume?.createdAt ??
87
+ record.detail?.startTime ??
88
+ 0
89
+
90
+ const isSessionDirNameMatch = (value: string, target: string) => value === target || value.startsWith(target)
91
+
92
+ const normalizeResumeCommandPrefix = (prefix: string | undefined) => {
93
+ const normalizedPrefix = prefix?.trim()
94
+ return normalizedPrefix == null || normalizedPrefix === '' ? 'oneworks' : normalizedPrefix
95
+ }
96
+
97
+ export const formatResumeCommand = (sessionId: string, prefix = process.env.__ONEWORKS_CLI_RESUME_COMMAND_PREFIX__) =>
98
+ `${normalizeResumeCommandPrefix(prefix)} --resume ${sessionId}`
99
+ export const formatStopCommand = (sessionId: string) => `oneworks stop ${sessionId}`
100
+ export const formatKillCommand = (sessionId: string) => `oneworks kill ${sessionId}`
101
+ export const formatListCommand = (params?: {
102
+ running?: boolean
103
+ view?: string
104
+ }) => {
105
+ const args = ['oneworks', 'list']
106
+ if (params?.running) args.push('--running')
107
+ if (params?.view != null && params.view !== '') args.push('--view', params.view)
108
+ return args.join(' ')
109
+ }
110
+
111
+ export const resolveCliSessionId = (record: CliSessionRecord) =>
112
+ record.resume?.sessionId ?? record.detail?.sessionId ?? ''
113
+
114
+ export const resolveCliSessionCtxId = (record: CliSessionRecord) => record.resume?.ctxId ?? record.detail?.ctxId ?? ''
115
+
116
+ export const resolveCliSessionAdapter = (record: CliSessionRecord) =>
117
+ record.resume?.resolvedAdapter ??
118
+ record.detail?.adapter ??
119
+ record.resume?.taskOptions.adapter ??
120
+ ''
121
+
122
+ export const resolveCliSessionModel = (record: CliSessionRecord) =>
123
+ record.detail?.model ?? record.resume?.adapterOptions.model ?? ''
124
+
125
+ export const resolveCliSessionDescription = (record: CliSessionRecord) =>
126
+ record.detail?.description ?? record.resume?.description ?? ''
127
+
128
+ export const resolveCliSessionUpdatedAt = (record: CliSessionRecord) => getRecordUpdatedAt(record)
129
+
130
+ export const listCliSessions = async (cwd: string): Promise<CliSessionRecord[]> => {
131
+ const sessions: CliSessionRecord[] = []
132
+ const seen = new Set<string>()
133
+ await migrateProjectHomeSegment(cwd, process.env, 'caches')
134
+
135
+ const cacheRoot = resolveCliSessionCacheRoot(cwd)
136
+ const ctxEntries = await readDirSafe(cacheRoot)
137
+
138
+ for (const ctxEntry of ctxEntries) {
139
+ if (!ctxEntry.isDirectory()) continue
140
+
141
+ const ctxId = ctxEntry.name
142
+ const sessionEntries = await readDirSafe(path.resolve(cacheRoot, ctxId))
143
+
144
+ for (const sessionEntry of sessionEntries) {
145
+ if (!sessionEntry.isDirectory()) continue
146
+
147
+ const sessionId = sessionEntry.name
148
+ const sessionKey = `${ctxId}\0${sessionId}`
149
+ if (seen.has(sessionKey)) continue
150
+
151
+ const [resume, detail] = await Promise.all([
152
+ getCacheFromRoot(cacheRoot, ctxId, sessionId, 'cli-session'),
153
+ getCacheFromRoot(cacheRoot, ctxId, sessionId, 'detail')
154
+ ])
155
+
156
+ if (resume == null && detail == null) continue
157
+ seen.add(sessionKey)
158
+ sessions.push({ resume, detail })
159
+ }
160
+ }
161
+
162
+ return sessions.sort((left, right) => getRecordUpdatedAt(right) - getRecordUpdatedAt(left))
163
+ }
164
+
165
+ export const resolveCliSession = async (cwd: string, id?: string): Promise<CliSessionRecord> => {
166
+ const sessions = await listCliSessions(cwd)
167
+ if (id == null || id.trim() === '') {
168
+ const latest = [...sessions].sort((left, right) => getRecordCreatedAt(right) - getRecordCreatedAt(left))[0]
169
+ if (latest != null) return latest
170
+ throw new Error(`No sessions found. Start a session first or use "${formatListCommand({ view: 'full' })}".`)
171
+ }
172
+
173
+ const normalizedId = id.trim()
174
+
175
+ const exactSessionMatch = sessions.find((record) =>
176
+ (record.resume?.sessionId ?? record.detail?.sessionId) === normalizedId
177
+ )
178
+ if (exactSessionMatch != null) return exactSessionMatch
179
+
180
+ const exactCtxMatches = sessions.filter((record) => (record.resume?.ctxId ?? record.detail?.ctxId) === normalizedId)
181
+ if (exactCtxMatches.length === 1) return exactCtxMatches[0]!
182
+ if (exactCtxMatches.length > 1) {
183
+ throw new Error(`Session id "${id}" matches multiple task contexts. Use a session id instead.`)
184
+ }
185
+
186
+ const prefixMatches = sessions.filter((record) => {
187
+ const sessionId = record.resume?.sessionId ?? record.detail?.sessionId
188
+ const ctxId = record.resume?.ctxId ?? record.detail?.ctxId
189
+ return (
190
+ (sessionId != null && isSessionDirNameMatch(sessionId, normalizedId)) ||
191
+ (ctxId != null && isSessionDirNameMatch(ctxId, normalizedId))
192
+ )
193
+ })
194
+
195
+ if (prefixMatches.length === 1) return prefixMatches[0]!
196
+ if (prefixMatches.length > 1) {
197
+ const candidates = prefixMatches
198
+ .map(resolveCliSessionId)
199
+ .filter((value): value is string => value != null)
200
+ .slice(0, 5)
201
+ .join(', ')
202
+ throw new Error(`Session id "${id}" is ambiguous: ${candidates}`)
203
+ }
204
+
205
+ throw new Error(
206
+ `Session "${id}" not found. Use "${formatListCommand({ view: 'full' })}" to inspect available sessions.`
207
+ )
208
+ }
209
+
210
+ export const writeCliSessionRecord = async (
211
+ cwd: string,
212
+ ctxId: string,
213
+ sessionId: string,
214
+ record: CliSessionRecord
215
+ ) => {
216
+ await Promise.all([
217
+ record.resume == null
218
+ ? Promise.resolve()
219
+ : setCache(cwd, ctxId, sessionId, 'cli-session', record.resume),
220
+ record.detail == null
221
+ ? Promise.resolve()
222
+ : setCache(cwd, ctxId, sessionId, 'detail', record.detail)
223
+ ])
224
+ }
225
+
226
+ export const readCliSessionControl = (
227
+ cwd: string,
228
+ ctxId: string,
229
+ sessionId: string
230
+ ) => getCache(cwd, ctxId, sessionId, 'cli-session-control')
231
+
232
+ export const writeCliSessionControl = (
233
+ cwd: string,
234
+ ctxId: string,
235
+ sessionId: string,
236
+ control: CliSessionControlRecord
237
+ ) => setCache(cwd, ctxId, sessionId, 'cli-session-control', control)
238
+
239
+ export const clearCliSessionControl = async (
240
+ cwd: string,
241
+ ctxId: string,
242
+ sessionId: string
243
+ ) => {
244
+ await fs.rm(getCachePath(cwd, ctxId, sessionId, 'cli-session-control'), { force: true })
245
+ }
246
+
247
+ export const isCliSessionStopActive = (
248
+ control: CliSessionControlRecord | undefined,
249
+ endedAt: number
250
+ ) => control != null && endedAt <= control.expiresAt
@@ -0,0 +1,40 @@
1
+ import fs from 'node:fs/promises'
2
+
3
+ import type { AskUserQuestionParams, SessionPermissionMode } from '@oneworks/types'
4
+ import { getCache, getCachePath, setCache } from '@oneworks/utils/cache'
5
+
6
+ export interface CliSessionPermissionRecoveryRecord {
7
+ version: 1
8
+ sessionId: string
9
+ adapter?: string
10
+ permissionMode?: SessionPermissionMode
11
+ subjectKeys: string[]
12
+ payload: AskUserQuestionParams
13
+ }
14
+
15
+ declare module '@oneworks/types' {
16
+ interface Cache {
17
+ 'cli-session-permission-recovery': CliSessionPermissionRecoveryRecord
18
+ }
19
+ }
20
+
21
+ export const readCliSessionPermissionRecovery = (
22
+ cwd: string,
23
+ ctxId: string,
24
+ sessionId: string
25
+ ) => getCache(cwd, ctxId, sessionId, 'cli-session-permission-recovery')
26
+
27
+ export const writeCliSessionPermissionRecovery = (
28
+ cwd: string,
29
+ ctxId: string,
30
+ sessionId: string,
31
+ record: CliSessionPermissionRecoveryRecord
32
+ ) => setCache(cwd, ctxId, sessionId, 'cli-session-permission-recovery', record)
33
+
34
+ export const clearCliSessionPermissionRecovery = async (
35
+ cwd: string,
36
+ ctxId: string,
37
+ sessionId: string
38
+ ) => {
39
+ await fs.rm(getCachePath(cwd, ctxId, sessionId, 'cli-session-permission-recovery'), { force: true })
40
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,25 @@
1
+ let packageJson: Record<string, any> | undefined
2
+
3
+ function getPackageJson() {
4
+ if (!packageJson) {
5
+ try {
6
+ // eslint-disable-next-line ts/no-require-imports
7
+ packageJson = require('@oneworks/cli/package.json')
8
+ } catch {
9
+ packageJson = {}
10
+ }
11
+ }
12
+ return packageJson!
13
+ }
14
+
15
+ export function getPackageConfig(key: string, defaultValue = ''): string {
16
+ return getPackageJson()[key] ?? defaultValue
17
+ }
18
+
19
+ export function getCliVersion() {
20
+ return getPackageConfig('version', '0.0.0')
21
+ }
22
+
23
+ export function getCliDescription() {
24
+ return getPackageConfig('description', 'One Works CLI')
25
+ }
@@ -0,0 +1,12 @@
1
+ import process from 'node:process'
2
+
3
+ import { resolveProjectWorkspaceFolder } from '@oneworks/utils'
4
+
5
+ export const resolveCliWorkspaceCwd = (
6
+ cwd: string = process.cwd(),
7
+ env: NodeJS.ProcessEnv = process.env
8
+ ) => {
9
+ const workspaceCwd = resolveProjectWorkspaceFolder(cwd, env)
10
+ env.__ONEWORKS_PROJECT_WORKSPACE_FOLDER__ = workspaceCwd
11
+ return workspaceCwd
12
+ }