@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,108 @@
1
+ import type { Buffer } from 'node:buffer'
2
+ import { createInterface } from 'node:readline'
3
+
4
+ import type { ChatMessageContent } from '@oneworks/core'
5
+
6
+ import { parseCliInputControlEvent } from './input-control'
7
+ import type { CliInputControlEvent, CliInputSession, RunInputFormat } from './types'
8
+
9
+ const dispatchCliInputControlEvent = async (params: {
10
+ session: CliInputSession
11
+ event: CliInputControlEvent
12
+ submitInput?: (event: Extract<CliInputControlEvent, { type: 'submit_input' }>) => void | Promise<void>
13
+ }) => {
14
+ const { session, event, submitInput } = params
15
+ if (event.type === 'interrupt') {
16
+ session.emit({ type: 'interrupt' })
17
+ return
18
+ }
19
+ if (event.type === 'stop') {
20
+ session.emit({ type: 'stop' })
21
+ return
22
+ }
23
+ if (event.type === 'submit_input') {
24
+ if (submitInput == null) {
25
+ throw new TypeError('The current session does not support submit_input events.')
26
+ }
27
+ await submitInput(event)
28
+ return
29
+ }
30
+
31
+ const content = typeof event.content === 'string'
32
+ ? [{ type: 'text', text: event.content } satisfies ChatMessageContent]
33
+ : event.content ?? []
34
+
35
+ session.emit({
36
+ type: 'message',
37
+ content
38
+ })
39
+ }
40
+
41
+ export const attachInputBridge = (params: {
42
+ format: RunInputFormat
43
+ session: CliInputSession
44
+ stdin: NodeJS.ReadStream
45
+ onError: (message: string) => void
46
+ onInputClosed: () => void
47
+ submitInput?: (event: Extract<CliInputControlEvent, { type: 'submit_input' }>) => void | Promise<void>
48
+ }) => {
49
+ const { format, session, stdin, onError, onInputClosed, submitInput } = params
50
+ stdin.setEncoding('utf8')
51
+
52
+ if (format === 'stream-json') {
53
+ const rl = createInterface({ input: stdin, crlfDelay: Infinity })
54
+ const onEnd = () => {
55
+ onInputClosed()
56
+ }
57
+ rl.on('line', (line) => {
58
+ const trimmed = line.trim()
59
+ if (trimmed === '') return
60
+ try {
61
+ void dispatchCliInputControlEvent({
62
+ session,
63
+ event: parseCliInputControlEvent(JSON.parse(trimmed)),
64
+ submitInput
65
+ }).catch((error) => {
66
+ onError(error instanceof Error ? error.message : String(error))
67
+ })
68
+ } catch (error) {
69
+ onError(error instanceof Error ? error.message : String(error))
70
+ }
71
+ })
72
+ stdin.once('end', onEnd)
73
+ return () => {
74
+ stdin.off('end', onEnd)
75
+ rl.close()
76
+ }
77
+ }
78
+
79
+ const chunks: string[] = []
80
+ const onData = (chunk: string | Buffer) => {
81
+ chunks.push(String(chunk))
82
+ }
83
+ const onEnd = () => {
84
+ const raw = chunks.join('')
85
+ if (raw.trim() !== '') {
86
+ try {
87
+ const payload = format === 'json' ? JSON.parse(raw) : raw
88
+ void dispatchCliInputControlEvent({
89
+ session,
90
+ event: parseCliInputControlEvent(payload),
91
+ submitInput
92
+ }).catch((error) => {
93
+ onError(error instanceof Error ? error.message : String(error))
94
+ })
95
+ } catch (error) {
96
+ onError(error instanceof Error ? error.message : String(error))
97
+ }
98
+ }
99
+ onInputClosed()
100
+ }
101
+
102
+ stdin.on('data', onData)
103
+ stdin.once('end', onEnd)
104
+ return () => {
105
+ stdin.off('data', onData)
106
+ stdin.off('end', onEnd)
107
+ }
108
+ }
@@ -0,0 +1,112 @@
1
+ import type { ChatMessageContent } from '@oneworks/core'
2
+
3
+ import type { CliInputControlEvent, RunInputFormat } from './types'
4
+
5
+ export const supportsPrintInteractionResponses = (format: RunInputFormat | undefined) => format === 'stream-json'
6
+
7
+ export const supportsRuntimeInteractionResponses = (
8
+ format: RunInputFormat | undefined,
9
+ isRuntimeProtocolConsumer: boolean
10
+ ) => isRuntimeProtocolConsumer || supportsPrintInteractionResponses(format)
11
+
12
+ const isChatTextContent = (value: unknown): value is Extract<ChatMessageContent, { type: 'text' }> => (
13
+ value != null &&
14
+ typeof value === 'object' &&
15
+ (value as { type?: unknown }).type === 'text' &&
16
+ typeof (value as { text?: unknown }).text === 'string'
17
+ )
18
+
19
+ const isChatImageContent = (value: unknown): value is Extract<ChatMessageContent, { type: 'image' }> => (
20
+ value != null &&
21
+ typeof value === 'object' &&
22
+ (value as { type?: unknown }).type === 'image' &&
23
+ typeof (value as { url?: unknown }).url === 'string'
24
+ )
25
+
26
+ const normalizeChatInputContent = (value: unknown): string | ChatMessageContent[] => {
27
+ if (typeof value === 'string') {
28
+ return value
29
+ }
30
+
31
+ if (Array.isArray(value)) {
32
+ const items = value.filter((item): item is ChatMessageContent =>
33
+ isChatTextContent(item) || isChatImageContent(item)
34
+ )
35
+ if (items.length > 0) {
36
+ return items
37
+ }
38
+ }
39
+
40
+ throw new Error('Unsupported message content. Use a string or an array of text/image content items.')
41
+ }
42
+
43
+ const normalizeSubmitInputData = (value: unknown): string | string[] => {
44
+ if (typeof value === 'string') {
45
+ const trimmed = value.trim()
46
+ if (trimmed !== '') {
47
+ return trimmed
48
+ }
49
+ }
50
+
51
+ if (Array.isArray(value)) {
52
+ const items = value
53
+ .filter((item): item is string => typeof item === 'string' && item.trim() !== '')
54
+ .map(item => item.trim())
55
+ if (items.length > 0) {
56
+ return items
57
+ }
58
+ }
59
+
60
+ throw new Error('Submit input requires a non-empty string or string array in "data" or "response".')
61
+ }
62
+
63
+ export const parseCliInputControlEvent = (value: unknown): CliInputControlEvent => {
64
+ if (typeof value === 'string') {
65
+ return {
66
+ type: 'message',
67
+ content: value
68
+ }
69
+ }
70
+
71
+ if (Array.isArray(value)) {
72
+ return {
73
+ type: 'message',
74
+ content: normalizeChatInputContent(value)
75
+ }
76
+ }
77
+
78
+ if (value == null || typeof value !== 'object') {
79
+ throw new Error('Invalid input payload. Expected a string, array, or object.')
80
+ }
81
+
82
+ const record = value as Record<string, unknown>
83
+ const type = typeof record.type === 'string' ? record.type : 'message'
84
+ if (type === 'interrupt') {
85
+ return { type: 'interrupt' }
86
+ }
87
+ if (type === 'stop') {
88
+ return { type: 'stop' }
89
+ }
90
+ if (type === 'submit_input' || type === 'interaction_response' || type === 'respond_interaction') {
91
+ const data = record.data ?? record.response ?? record.answer ?? record.value
92
+ return {
93
+ type: 'submit_input',
94
+ interactionId: typeof record.interactionId === 'string' && record.interactionId.trim() !== ''
95
+ ? record.interactionId.trim()
96
+ : undefined,
97
+ data: normalizeSubmitInputData(data)
98
+ }
99
+ }
100
+ if (type === 'message' || type === 'user_message') {
101
+ const content = record.content ?? record.text ?? record.message
102
+ if (content == null) {
103
+ throw new Error('Message input requires "content" or "text".')
104
+ }
105
+ return {
106
+ type: 'message',
107
+ content: normalizeChatInputContent(content)
108
+ }
109
+ }
110
+
111
+ throw new Error(`Unsupported input event type: ${type}`)
112
+ }
@@ -0,0 +1,88 @@
1
+ import type { Buffer } from 'node:buffer'
2
+ import { createInterface } from 'node:readline'
3
+
4
+ import { parseCliInputControlEvent } from './input-control'
5
+ import type { RunInputFormat } from './types'
6
+
7
+ type PermissionDecisionInput = NodeJS.ReadableStream & {
8
+ setEncoding(encoding: BufferEncoding): void
9
+ }
10
+
11
+ const resolveDecisionFromPayload = (payload: unknown) => {
12
+ const event = parseCliInputControlEvent(payload)
13
+ if (event.type === 'submit_input') {
14
+ return event.data
15
+ }
16
+ if (event.type === 'message' && typeof event.content === 'string' && event.content.trim() !== '') {
17
+ return event.content.trim()
18
+ }
19
+ throw new TypeError('Permission recovery expects submit_input or a plain text decision like allow_once.')
20
+ }
21
+
22
+ const readFirstLine = async (stdin: PermissionDecisionInput) =>
23
+ await new Promise<string>((resolve, reject) => {
24
+ const rl = createInterface({ input: stdin, crlfDelay: Infinity })
25
+ let settled = false
26
+ const cleanup = () => {
27
+ stdin.off('error', onError)
28
+ rl.close()
29
+ }
30
+ const onError = (error: Error) => {
31
+ if (settled) return
32
+ settled = true
33
+ cleanup()
34
+ reject(error)
35
+ }
36
+ rl.on('line', (line) => {
37
+ const trimmed = line.trim()
38
+ if (trimmed === '') return
39
+ settled = true
40
+ cleanup()
41
+ resolve(trimmed)
42
+ })
43
+ rl.once('close', () => {
44
+ if (settled) return
45
+ settled = true
46
+ reject(new TypeError('Permission recovery input closed before any decision was provided.'))
47
+ })
48
+ stdin.once('error', onError)
49
+ })
50
+
51
+ const readAll = async (stdin: PermissionDecisionInput) =>
52
+ await new Promise<string>((resolve, reject) => {
53
+ const chunks: string[] = []
54
+ const onData = (chunk: string | Buffer) => {
55
+ chunks.push(String(chunk))
56
+ }
57
+ const onEnd = () => {
58
+ cleanup()
59
+ resolve(chunks.join(''))
60
+ }
61
+ const onError = (error: Error) => {
62
+ cleanup()
63
+ reject(error)
64
+ }
65
+ const cleanup = () => {
66
+ stdin.off('data', onData)
67
+ stdin.off('end', onEnd)
68
+ stdin.off('error', onError)
69
+ }
70
+ stdin.on('data', onData)
71
+ stdin.once('end', onEnd)
72
+ stdin.once('error', onError)
73
+ })
74
+
75
+ export const readCliPermissionDecision = async (params: {
76
+ format: RunInputFormat
77
+ stdin: PermissionDecisionInput
78
+ }) => {
79
+ params.stdin.setEncoding('utf8')
80
+ if (params.format === 'stream-json') {
81
+ return resolveDecisionFromPayload(JSON.parse(await readFirstLine(params.stdin)))
82
+ }
83
+ if (params.format === 'json') {
84
+ return resolveDecisionFromPayload(JSON.parse((await readAll(params.stdin)).trim()))
85
+ }
86
+
87
+ return resolveDecisionFromPayload(await readFirstLine(params.stdin))
88
+ }
@@ -0,0 +1,104 @@
1
+ import type { Command, OptionValueSource } from 'commander'
2
+
3
+ import type { CliSessionResumeRecord } from '#~/session-cache.js'
4
+
5
+ import type { RunOptions, RunOutputFormat } from './types'
6
+
7
+ const getRunMode = (print: boolean): 'stream' | 'direct' => print ? 'stream' : 'direct'
8
+
9
+ export const getOutputFormat = (
10
+ value: RunOutputFormat | undefined,
11
+ source: OptionValueSource | undefined,
12
+ fallback: RunOutputFormat
13
+ ) => source === 'default' ? fallback : (value ?? 'text')
14
+
15
+ export const resolveRunMode = (
16
+ print: boolean,
17
+ source: OptionValueSource | undefined,
18
+ fallback: 'stream' | 'direct'
19
+ ) => source === 'default' ? 'direct' : getRunMode(print)
20
+
21
+ export const resolveInjectDefaultSystemPromptOption = (
22
+ value: boolean | undefined,
23
+ source: OptionValueSource | undefined
24
+ ) => source === 'default' ? undefined : value
25
+
26
+ export const resolveDefaultOneworksMcpServerOption = (
27
+ value: boolean | undefined,
28
+ source: OptionValueSource | undefined
29
+ ) => source === 'default' ? undefined : value
30
+
31
+ export const resolvePermissionModeOption = (
32
+ permissionMode: RunOptions['permissionMode'],
33
+ yolo: RunOptions['yolo']
34
+ ): RunOptions['permissionMode'] => {
35
+ if (yolo !== true) return permissionMode
36
+ if (permissionMode != null && permissionMode !== 'bypassPermissions') {
37
+ throw new Error('--yolo cannot be combined with --permission-mode unless the mode is bypassPermissions.')
38
+ }
39
+ return 'bypassPermissions'
40
+ }
41
+
42
+ export const getDisallowedResumeFlags = (
43
+ opts: RunOptions,
44
+ command: Command
45
+ ) => {
46
+ const disallowed: string[] = []
47
+
48
+ if (opts.adapter) disallowed.push('--adapter')
49
+ if (opts.account) disallowed.push('--account')
50
+ if (opts.systemPrompt) disallowed.push('--system-prompt')
51
+ if (opts.sessionId) disallowed.push('--session-id')
52
+ if (opts.spec) disallowed.push('--spec')
53
+ if (opts.entity) disallowed.push('--entity')
54
+ if (opts.workspace) disallowed.push('--workspace')
55
+ if ((opts.includeMcpServer?.length ?? 0) > 0) disallowed.push('--include-mcp-server')
56
+ if ((opts.excludeMcpServer?.length ?? 0) > 0) disallowed.push('--exclude-mcp-server')
57
+ if ((opts.includeSkill?.length ?? 0) > 0) disallowed.push('--include-skill')
58
+ if ((opts.excludeSkill?.length ?? 0) > 0) disallowed.push('--exclude-skill')
59
+ if (command.getOptionValueSource('injectDefaultSystemPrompt') !== 'default') {
60
+ disallowed.push('--no-inject-default-system-prompt')
61
+ }
62
+ if (command.getOptionValueSource('defaultOneworksMcpServer') !== 'default') {
63
+ disallowed.push('--no-default-oneworks-mcp-server')
64
+ }
65
+
66
+ return disallowed
67
+ }
68
+
69
+ export const mergeListConfig = (
70
+ config: { include?: string[]; exclude?: string[] } | undefined,
71
+ includeOpts: string[] | undefined,
72
+ excludeOpts: string[] | undefined
73
+ ) => {
74
+ const include = config?.include || includeOpts
75
+ ? [
76
+ ...(config?.include ?? []),
77
+ ...(includeOpts ?? [])
78
+ ]
79
+ : undefined
80
+
81
+ const exclude = config?.exclude || excludeOpts
82
+ ? [
83
+ ...(config?.exclude ?? []),
84
+ ...(excludeOpts ?? [])
85
+ ]
86
+ : undefined
87
+
88
+ return include || exclude
89
+ ? {
90
+ include,
91
+ exclude
92
+ }
93
+ : undefined
94
+ }
95
+
96
+ export const resolveResumeAdapterOptions = (
97
+ cached: CliSessionResumeRecord['adapterOptions'],
98
+ opts: Pick<RunOptions, 'model' | 'effort' | 'includeTool' | 'excludeTool'>
99
+ ): CliSessionResumeRecord['adapterOptions'] => ({
100
+ ...cached,
101
+ ...(opts.model != null ? { model: opts.model } : {}),
102
+ ...(opts.effort != null ? { effort: opts.effort } : {}),
103
+ tools: mergeListConfig(cached.tools, opts.includeTool, opts.excludeTool)
104
+ })
@@ -0,0 +1,179 @@
1
+ import type { ChatMessage } from '@oneworks/core'
2
+ import type { AdapterErrorData, AdapterOutputEvent, AskUserQuestionParams, TaskDetail } from '@oneworks/types'
3
+ import { extractTextFromMessage } from '@oneworks/utils/chat-message'
4
+
5
+ import type { RunOutputFormat } from './types'
6
+
7
+ export const getPrintableAssistantText = (message: ChatMessage | undefined) => {
8
+ if (message?.role !== 'assistant') return undefined
9
+
10
+ const text = extractTextFromMessage(message).trim()
11
+ return text === '' ? undefined : text
12
+ }
13
+
14
+ export const resolvePrintableStopText = (
15
+ message: ChatMessage | undefined,
16
+ lastAssistantText: string | undefined
17
+ ) => getPrintableAssistantText(message) ?? lastAssistantText
18
+
19
+ export const getAdapterErrorMessage = (data: AdapterErrorData) => {
20
+ if (data.code === 'permission_required') {
21
+ const details = data.details != null && typeof data.details === 'object'
22
+ ? data.details as { permissionDenials?: Array<{ message?: string; deniedTools?: string[] }> }
23
+ : undefined
24
+ const deniedTools = [
25
+ ...new Set(
26
+ (details?.permissionDenials ?? []).flatMap(item => Array.isArray(item.deniedTools) ? item.deniedTools : [])
27
+ )
28
+ ]
29
+ return deniedTools.length > 0
30
+ ? `${data.message}\nDenied tools: ${deniedTools.join(', ')}`
31
+ : data.message
32
+ }
33
+
34
+ const details = data.details == null
35
+ ? undefined
36
+ : typeof data.details === 'string'
37
+ ? data.details
38
+ : JSON.stringify(data.details, null, 2)
39
+
40
+ return details ? `${data.message}\n${details}` : data.message
41
+ }
42
+
43
+ export const getAdapterInteractionMessage = (payload: AskUserQuestionParams) => {
44
+ const header = payload.kind === 'permission' ? 'Permission required' : 'Input required'
45
+ const optionLines = (payload.options ?? [])
46
+ .map((option) => {
47
+ const value = option.value ?? option.label
48
+ return option.description != null && option.description.trim() !== ''
49
+ ? `- ${value}: ${option.description}`
50
+ : `- ${value}`
51
+ })
52
+ return optionLines.length > 0
53
+ ? `${header}\n${payload.question}\nOptions:\n${optionLines.join('\n')}`
54
+ : `${header}\n${payload.question}`
55
+ }
56
+
57
+ export const shouldPrintResumeHint = (input: {
58
+ shouldPrintOutput: boolean
59
+ status: TaskDetail['status']
60
+ }) => !(input.shouldPrintOutput && input.status === 'completed')
61
+
62
+ export const handlePrintEvent = (input: {
63
+ event: AdapterOutputEvent
64
+ outputFormat: RunOutputFormat
65
+ lastAssistantText: string | undefined
66
+ didExitAfterError: boolean
67
+ exitOnInteractionRequest?: boolean
68
+ stopExitsStreamJson?: boolean
69
+ log: (message: string) => void
70
+ errorLog: (message: string) => void
71
+ requestExit: (code: number) => void
72
+ }) => {
73
+ const nextAssistantText = input.event.type === 'message'
74
+ ? (getPrintableAssistantText(input.event.data) ?? input.lastAssistantText)
75
+ : input.lastAssistantText
76
+
77
+ if (input.event.type === 'interaction_request') {
78
+ switch (input.outputFormat) {
79
+ case 'stream-json':
80
+ case 'json':
81
+ input.log(JSON.stringify(input.event, null, 2))
82
+ if (input.exitOnInteractionRequest === true) {
83
+ input.requestExit(1)
84
+ }
85
+ return {
86
+ lastAssistantText: nextAssistantText,
87
+ didExitAfterError: input.didExitAfterError || input.exitOnInteractionRequest === true
88
+ }
89
+ case 'text':
90
+ input.errorLog(getAdapterInteractionMessage(input.event.data.payload))
91
+ if (input.exitOnInteractionRequest === true) {
92
+ input.requestExit(1)
93
+ }
94
+ return {
95
+ lastAssistantText: nextAssistantText,
96
+ didExitAfterError: input.didExitAfterError || input.exitOnInteractionRequest === true
97
+ }
98
+ }
99
+ }
100
+
101
+ if (input.event.type === 'error') {
102
+ const isFatal = input.event.data.fatal !== false
103
+
104
+ switch (input.outputFormat) {
105
+ case 'stream-json':
106
+ input.log(JSON.stringify(input.event, null, 2))
107
+ if (isFatal) {
108
+ input.requestExit(1)
109
+ return {
110
+ lastAssistantText: nextAssistantText,
111
+ didExitAfterError: true
112
+ }
113
+ }
114
+ return {
115
+ lastAssistantText: nextAssistantText,
116
+ didExitAfterError: input.didExitAfterError
117
+ }
118
+ case 'json':
119
+ if (isFatal) {
120
+ input.log(JSON.stringify(input.event, null, 2))
121
+ input.requestExit(1)
122
+ return {
123
+ lastAssistantText: nextAssistantText,
124
+ didExitAfterError: true
125
+ }
126
+ }
127
+ return {
128
+ lastAssistantText: nextAssistantText,
129
+ didExitAfterError: input.didExitAfterError
130
+ }
131
+ case 'text':
132
+ if (isFatal) {
133
+ input.errorLog(getAdapterErrorMessage(input.event.data))
134
+ input.requestExit(1)
135
+ return {
136
+ lastAssistantText: nextAssistantText,
137
+ didExitAfterError: true
138
+ }
139
+ }
140
+ return {
141
+ lastAssistantText: nextAssistantText,
142
+ didExitAfterError: input.didExitAfterError
143
+ }
144
+ }
145
+ }
146
+
147
+ switch (input.outputFormat) {
148
+ case 'stream-json':
149
+ input.log(JSON.stringify(input.event, null, 2))
150
+ if (input.event.type === 'stop' && input.stopExitsStreamJson === true && !input.didExitAfterError) {
151
+ input.requestExit(0)
152
+ }
153
+ return {
154
+ lastAssistantText: nextAssistantText,
155
+ didExitAfterError: input.didExitAfterError
156
+ }
157
+ case 'json':
158
+ if (input.event.type === 'stop' && !input.didExitAfterError) {
159
+ input.log(JSON.stringify(input.event, null, 2))
160
+ input.requestExit(0)
161
+ }
162
+ return {
163
+ lastAssistantText: nextAssistantText,
164
+ didExitAfterError: input.didExitAfterError
165
+ }
166
+ case 'text':
167
+ if (input.event.type === 'stop' && !input.didExitAfterError) {
168
+ const output = resolvePrintableStopText(input.event.data, nextAssistantText)
169
+ if (output != null) {
170
+ input.log(output)
171
+ }
172
+ input.requestExit(0)
173
+ }
174
+ return {
175
+ lastAssistantText: nextAssistantText,
176
+ didExitAfterError: input.didExitAfterError
177
+ }
178
+ }
179
+ }
@@ -0,0 +1,19 @@
1
+ import type { PermissionInteractionDecision } from '@oneworks/types'
2
+
3
+ import { PERMISSION_DECISION_CANCEL } from './permission-recovery'
4
+
5
+ export const shouldApplyPermissionDecision = (
6
+ decision: PermissionInteractionDecision | typeof PERMISSION_DECISION_CANCEL
7
+ ) => decision !== PERMISSION_DECISION_CANCEL && decision !== 'deny_once'
8
+
9
+ export const shouldClearPermissionRecoveryCache = (
10
+ decision: PermissionInteractionDecision | typeof PERMISSION_DECISION_CANCEL
11
+ ) => decision !== PERMISSION_DECISION_CANCEL
12
+
13
+ export const isTerminalPermissionDecision = (
14
+ decision: PermissionInteractionDecision | typeof PERMISSION_DECISION_CANCEL
15
+ ) => (
16
+ decision === 'deny_once' ||
17
+ decision === 'deny_session' ||
18
+ decision === 'deny_project'
19
+ )