@xelauvas/xela-cli 0.3.0 → 0.4.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 (136) hide show
  1. package/local/README.md +112 -0
  2. package/local/start-qwen.sh +47 -0
  3. package/local/start-tunnel.sh +36 -0
  4. package/local/xela-local.sh +139 -0
  5. package/package.json +19 -5
  6. package/run.sh +33 -0
  7. package/src/Tool.ts +1 -1
  8. package/src/_shims/_generated_stubs/_universal_stub.mjs +168 -0
  9. package/src/_shims/_generated_stubs/stub_346f96741420f62caece15b196e233c6.mjs +19 -0
  10. package/src/_shims/_generated_stubs/stub_4b79af2a6ac1af5a53a55fd666b2bbb4.mjs +3 -0
  11. package/src/_shims/_generated_stubs/stub_94345ddeadacc184ff895597ec5abf71.mjs +6 -0
  12. package/src/_shims/_generated_stubs/stub_99fa0bc272643c549d8a2ec6d578436f.mjs +3 -0
  13. package/src/_shims/_generated_stubs/stub_e3451b131fcaf272a2a75270b9a427a3.mjs +9 -0
  14. package/src/_shims/_generated_stubs/text_stub.mjs +1 -0
  15. package/src/_shims/bun_bundle.js +79 -4
  16. package/src/_shims/cjs_stub.cjs +32 -10
  17. package/src/_shims/loader.js +104 -5
  18. package/src/_shims/openai_adapter.ts +446 -230
  19. package/src/_shims/register.js +16 -3
  20. package/src/assistant/gate.ts +15 -0
  21. package/src/assistant/index.ts +89 -0
  22. package/src/bootstrap/state.ts +6 -1
  23. package/src/bridge/peerSessions.ts +18 -0
  24. package/src/cli/print.ts +5 -0
  25. package/src/commands/assistant/assistant.ts +17 -0
  26. package/src/commands/assistant/index.ts +11 -0
  27. package/src/commands/brief.ts +3 -1
  28. package/src/commands/buddy/buddy.ts +88 -0
  29. package/src/commands/buddy/index.ts +12 -0
  30. package/src/commands/model/model.tsx +8 -23
  31. package/src/commands/peers/index.ts +11 -0
  32. package/src/commands/peers/peers.ts +24 -0
  33. package/src/commands/proactive/proactive.ts +64 -0
  34. package/src/commands/proactive.ts +12 -0
  35. package/src/commands/workflows/index.ts +12 -0
  36. package/src/commands/workflows/workflows.ts +36 -0
  37. package/src/commands.ts +4 -8
  38. package/src/components/Messages.tsx +1 -1
  39. package/src/components/ModelPicker.tsx +4 -34
  40. package/src/components/Spinner.tsx +54 -1
  41. package/src/components/StructuredDiff/colorDiff.ts +3 -1
  42. package/src/components/messages/SnipBoundaryMessage.tsx +15 -0
  43. package/src/components/messages/UserCrossSessionMessage.tsx +26 -0
  44. package/src/components/permissions/PermissionRequest.tsx +4 -2
  45. package/src/components/permissions/ReviewArtifactPermissionRequest/ReviewArtifactPermissionRequest.tsx +96 -0
  46. package/src/constants/prompts.ts +26 -0
  47. package/src/ink/terminal.ts +9 -1
  48. package/src/jobs/classifier.ts +103 -0
  49. package/src/memdir/paths.ts +13 -1
  50. package/src/proactive/index.ts +158 -0
  51. package/src/proactive/useProactive.ts +78 -0
  52. package/src/screens/REPL.tsx +28 -8
  53. package/src/services/api/claude.ts +6 -1
  54. package/src/services/compact/autoCompact.ts +18 -4
  55. package/src/services/compact/cachedMCConfig.ts +1 -0
  56. package/src/services/compact/cachedMicrocompact.ts +113 -0
  57. package/src/services/compact/compact.ts +22 -2
  58. package/src/services/compact/reactiveCompact.ts +251 -0
  59. package/src/services/compact/snipCompact.ts +282 -0
  60. package/src/services/compact/snipProjection.ts +50 -0
  61. package/src/services/contextCollapse/index.ts +322 -0
  62. package/src/services/contextCollapse/operations.ts +187 -0
  63. package/src/services/contextCollapse/persist.ts +40 -0
  64. package/src/services/extractMemories/extractMemories.ts +11 -2
  65. package/src/services/skillSearch/featureCheck.ts +12 -0
  66. package/src/services/skillSearch/localSearch.ts +98 -0
  67. package/src/services/skillSearch/prefetch.ts +125 -0
  68. package/src/services/skillSearch/remoteSkillLoader.ts +9 -0
  69. package/src/services/skillSearch/remoteSkillState.ts +21 -0
  70. package/src/services/skillSearch/signals.ts +7 -0
  71. package/src/services/skillSearch/telemetry.ts +9 -0
  72. package/src/setup.ts +10 -5
  73. package/src/skills/bundled/dream.ts +31 -0
  74. package/src/skills/bundled/hunter.ts +45 -0
  75. package/src/skills/bundled/index.ts +10 -23
  76. package/src/skills/bundled/runSkillGenerator.ts +61 -0
  77. package/src/skills/mcpSkills.ts +101 -0
  78. package/src/tasks/LocalWorkflowTask/LocalWorkflowTask.ts +301 -0
  79. package/src/tasks/MonitorMcpTask/MonitorMcpTask.ts +273 -0
  80. package/src/tasks.ts +22 -8
  81. package/src/tools/AgentTool/runAgent.ts +11 -0
  82. package/src/tools/BriefTool/BriefTool.ts +2 -0
  83. package/src/tools/CtxInspectTool/CtxInspectTool.ts +102 -0
  84. package/src/tools/DiscoverSkillsTool/DiscoverSkillsTool.ts +78 -0
  85. package/src/tools/DiscoverSkillsTool/prompt.ts +3 -0
  86. package/src/tools/ListPeersTool/ListPeersTool.ts +89 -0
  87. package/src/tools/MCPTool/MCPTool.ts +6 -0
  88. package/src/tools/MonitorTool/MonitorTool.ts +273 -0
  89. package/src/tools/MonitorTool/UI.tsx +25 -0
  90. package/src/tools/MonitorTool/events.ts +84 -0
  91. package/src/tools/MonitorTool/prompt.ts +45 -0
  92. package/src/tools/OverflowTestTool/OverflowTestTool.ts +73 -0
  93. package/src/tools/PushNotificationTool/PushNotificationTool.ts +119 -0
  94. package/src/tools/ReviewArtifactTool/ReviewArtifactTool.ts +93 -0
  95. package/src/tools/ReviewArtifactTool/prompt.ts +5 -0
  96. package/src/tools/SendUserFileTool/SendUserFileTool.ts +87 -0
  97. package/src/tools/SendUserFileTool/prompt.ts +7 -0
  98. package/src/tools/SleepTool/SleepTool.ts +126 -0
  99. package/src/tools/SnipTool/SnipTool.ts +90 -0
  100. package/src/tools/SnipTool/prompt.ts +5 -0
  101. package/src/tools/SubscribePRTool/SubscribePRTool.ts +115 -0
  102. package/src/tools/TerminalCaptureTool/TerminalCaptureTool.ts +129 -0
  103. package/src/tools/TerminalCaptureTool/prompt.ts +8 -0
  104. package/src/tools/ToolSearchTool/prompt.ts +1 -1
  105. package/src/tools/VerifyPlanExecutionTool/VerifyPlanExecutionTool.ts +99 -0
  106. package/src/tools/WebBrowserTool/WebBrowserPanel.tsx +10 -0
  107. package/src/tools/WebBrowserTool/WebBrowserTool.ts +190 -0
  108. package/src/tools/WebBrowserTool/prompt.ts +16 -0
  109. package/src/tools/WorkflowTool/UI.tsx +27 -0
  110. package/src/tools/WorkflowTool/WorkflowTool.ts +401 -0
  111. package/src/tools/WorkflowTool/bundled/index.ts +88 -0
  112. package/src/tools/WorkflowTool/constants.ts +7 -0
  113. package/src/tools/WorkflowTool/createWorkflowCommand.ts +84 -0
  114. package/src/tools/WorkflowTool/prompt.ts +38 -0
  115. package/src/tools/WorkflowTool/runtime.ts +331 -0
  116. package/src/tools.ts +36 -31
  117. package/src/utils/betas.ts +5 -0
  118. package/src/utils/context.ts +9 -0
  119. package/src/utils/conversationRecovery.ts +1 -1
  120. package/src/utils/forkedAgent.ts +4 -0
  121. package/src/utils/logoV2Utils.ts +7 -3
  122. package/src/utils/messageQueueManager.ts +23 -9
  123. package/src/utils/model/model.ts +10 -9
  124. package/src/utils/model/modelOptions.ts +0 -21
  125. package/src/utils/model/validateModel.ts +0 -19
  126. package/src/utils/permissions/permissionSetup.ts +5 -0
  127. package/src/utils/permissions/permissions.ts +25 -0
  128. package/src/utils/permissions/yolo-classifier-prompts/auto_mode_system_prompt.txt +38 -0
  129. package/src/utils/permissions/yolo-classifier-prompts/permissions_external.txt +25 -0
  130. package/src/utils/permissions/yoloClassifier.ts +28 -6
  131. package/src/utils/plugins/officialMarketplaceStartupCheck.ts +9 -0
  132. package/src/utils/systemThemeWatcher.ts +62 -0
  133. package/src/utils/udsClient.ts +115 -0
  134. package/src/utils/udsMessaging.ts +157 -0
  135. package/src/utils/yaml.ts +251 -5
  136. package/src/utils/model/providerModels.ts +0 -168
@@ -0,0 +1,301 @@
1
+ import {
2
+ OUTPUT_FILE_TAG,
3
+ STATUS_TAG,
4
+ SUMMARY_TAG,
5
+ TASK_ID_TAG,
6
+ TASK_NOTIFICATION_TAG,
7
+ TOOL_USE_ID_TAG,
8
+ } from '../../constants/xml.js'
9
+ import type { AppState } from '../../state/AppState.js'
10
+ import {
11
+ createTaskStateBase,
12
+ generateTaskId,
13
+ type SetAppState,
14
+ type Task,
15
+ type TaskStateBase,
16
+ } from '../../Task.js'
17
+ import type { AgentId } from '../../types/ids.js'
18
+ import { registerCleanup } from '../../utils/cleanupRegistry.js'
19
+ import { enqueuePendingNotification } from '../../utils/messageQueueManager.js'
20
+ import {
21
+ appendTaskOutput,
22
+ getTaskOutputPath,
23
+ initTaskOutput,
24
+ } from '../../utils/task/diskOutput.js'
25
+ import { registerTask, updateTaskState } from '../../utils/task/framework.js'
26
+ import { escapeXml } from '../../utils/xml.js'
27
+
28
+ export type WorkflowAgentStatus =
29
+ | 'pending'
30
+ | 'running'
31
+ | 'completed'
32
+ | 'failed'
33
+ | 'skipped'
34
+
35
+ export type WorkflowAgentRecord = {
36
+ id: string
37
+ index: number
38
+ label: string
39
+ phase?: string
40
+ status: WorkflowAgentStatus
41
+ startTime: number
42
+ endTime?: number
43
+ error?: string
44
+ resultPreview?: string
45
+ /** Set when the user asked for a retry from the task panel. */
46
+ retryRequested?: boolean
47
+ }
48
+
49
+ export type WorkflowPhaseRecord = {
50
+ title: string
51
+ detail?: string
52
+ status: 'pending' | 'running' | 'completed'
53
+ }
54
+
55
+ /**
56
+ * Per-agent controller, kept outside the (immutable) task state so skip/retry
57
+ * from the UI can reach the running agent.
58
+ */
59
+ export type WorkflowAgentController = {
60
+ abortController: AbortController
61
+ retry: boolean
62
+ }
63
+
64
+ export type LocalWorkflowTaskState = TaskStateBase & {
65
+ type: 'local_workflow'
66
+ runId: string
67
+ name: string
68
+ workflowDescription: string
69
+ args?: unknown
70
+ phases: WorkflowPhaseRecord[]
71
+ currentPhase?: string
72
+ agents: WorkflowAgentRecord[]
73
+ result?: unknown
74
+ error?: string
75
+ abortController: AbortController
76
+ agentControllers: Map<string, WorkflowAgentController>
77
+ isBackgrounded: boolean
78
+ agentId?: AgentId
79
+ unregisterCleanup?: () => void
80
+ }
81
+
82
+ export function isLocalWorkflowTask(
83
+ task: unknown,
84
+ ): task is LocalWorkflowTaskState {
85
+ return (
86
+ typeof task === 'object' &&
87
+ task !== null &&
88
+ 'type' in task &&
89
+ (task as { type: unknown }).type === 'local_workflow'
90
+ )
91
+ }
92
+
93
+ export const LocalWorkflowTask: Task = {
94
+ name: 'LocalWorkflowTask',
95
+ type: 'local_workflow',
96
+ async kill(taskId, setAppState) {
97
+ killWorkflowTask(taskId, setAppState)
98
+ },
99
+ }
100
+
101
+ export function killWorkflowTask(
102
+ taskId: string,
103
+ setAppState: SetAppState,
104
+ ): void {
105
+ let state: LocalWorkflowTaskState | undefined
106
+ setAppState(prev => {
107
+ const t = prev.tasks?.[taskId]
108
+ if (isLocalWorkflowTask(t)) state = t
109
+ return prev
110
+ })
111
+ if (!state) return
112
+ for (const ctrl of state.agentControllers.values()) ctrl.abortController.abort()
113
+ state.abortController.abort()
114
+ finishWorkflow(taskId, 'killed', undefined, 'Stopped by user', setAppState)
115
+ }
116
+
117
+ export function skipWorkflowAgent(
118
+ taskId: string,
119
+ agentId: string,
120
+ setAppState: SetAppState,
121
+ ): void {
122
+ let ctrl: WorkflowAgentController | undefined
123
+ setAppState(prev => {
124
+ const t = prev.tasks?.[taskId]
125
+ if (isLocalWorkflowTask(t)) ctrl = t.agentControllers.get(agentId)
126
+ return prev
127
+ })
128
+ if (!ctrl) return
129
+ ctrl.retry = false
130
+ ctrl.abortController.abort()
131
+ }
132
+
133
+ export function retryWorkflowAgent(
134
+ taskId: string,
135
+ agentId: string,
136
+ setAppState: SetAppState,
137
+ ): void {
138
+ let ctrl: WorkflowAgentController | undefined
139
+ setAppState(prev => {
140
+ const t = prev.tasks?.[taskId]
141
+ if (isLocalWorkflowTask(t)) ctrl = t.agentControllers.get(agentId)
142
+ return prev
143
+ })
144
+ if (!ctrl) return
145
+ ctrl.retry = true
146
+ ctrl.abortController.abort()
147
+ }
148
+
149
+ export function updateWorkflowAgent(
150
+ taskId: string,
151
+ agentId: string,
152
+ patch: Partial<WorkflowAgentRecord>,
153
+ setAppState: SetAppState,
154
+ ): void {
155
+ updateTaskState<LocalWorkflowTaskState>(taskId, setAppState, t => ({
156
+ ...t,
157
+ agents: t.agents.map(a => (a.id === agentId ? { ...a, ...patch } : a)),
158
+ }))
159
+ }
160
+
161
+ export function addWorkflowAgent(
162
+ taskId: string,
163
+ record: WorkflowAgentRecord,
164
+ controller: WorkflowAgentController,
165
+ setAppState: SetAppState,
166
+ ): void {
167
+ updateTaskState<LocalWorkflowTaskState>(taskId, setAppState, t => {
168
+ t.agentControllers.set(record.id, controller)
169
+ return { ...t, agents: [...t.agents, record] }
170
+ })
171
+ }
172
+
173
+ export function setWorkflowPhase(
174
+ taskId: string,
175
+ title: string,
176
+ setAppState: SetAppState,
177
+ ): void {
178
+ updateTaskState<LocalWorkflowTaskState>(taskId, setAppState, t => {
179
+ const known = t.phases.some(p => p.title === title)
180
+ const phases = (known ? t.phases : [...t.phases, { title, status: 'pending' as const }]).map(
181
+ p => {
182
+ if (p.title === title) return { ...p, status: 'running' as const }
183
+ if (p.status === 'running') return { ...p, status: 'completed' as const }
184
+ return p
185
+ },
186
+ )
187
+ return { ...t, phases, currentPhase: title }
188
+ })
189
+ }
190
+
191
+ export function finishWorkflow(
192
+ taskId: string,
193
+ status: 'completed' | 'failed' | 'killed',
194
+ result: unknown,
195
+ error: string | undefined,
196
+ setAppState: SetAppState,
197
+ ): void {
198
+ let snapshot: LocalWorkflowTaskState | null = null
199
+ updateTaskState<LocalWorkflowTaskState>(taskId, setAppState, t => {
200
+ if (t.status !== 'running' && t.status !== 'pending') return t
201
+ t.unregisterCleanup?.()
202
+ const next: LocalWorkflowTaskState = {
203
+ ...t,
204
+ status,
205
+ result,
206
+ error,
207
+ endTime: Date.now(),
208
+ currentPhase: undefined,
209
+ phases: t.phases.map(p =>
210
+ p.status === 'running' ? { ...p, status: 'completed' as const } : p,
211
+ ),
212
+ unregisterCleanup: undefined,
213
+ }
214
+ snapshot = next
215
+ return next
216
+ })
217
+ if (!snapshot) return
218
+ const s = snapshot as LocalWorkflowTaskState
219
+ let shouldEnqueue = false
220
+ updateTaskState<LocalWorkflowTaskState>(taskId, setAppState, t => {
221
+ if (t.notified) return t
222
+ shouldEnqueue = true
223
+ return { ...t, notified: true }
224
+ })
225
+ if (!shouldEnqueue) return
226
+ const done = s.agents.filter(a => a.status === 'completed').length
227
+ const summary =
228
+ status === 'completed'
229
+ ? `Workflow "${s.name}" completed (${done}/${s.agents.length} agents succeeded)`
230
+ : status === 'failed'
231
+ ? `Workflow "${s.name}" failed: ${error ?? 'unknown error'}`
232
+ : `Workflow "${s.name}" stopped`
233
+ const toolUseIdLine = s.toolUseId
234
+ ? `\n<${TOOL_USE_ID_TAG}>${s.toolUseId}</${TOOL_USE_ID_TAG}>`
235
+ : ''
236
+ let resultText = ''
237
+ if (status === 'completed') {
238
+ try {
239
+ resultText = JSON.stringify(result, null, 2) ?? ''
240
+ } catch {
241
+ resultText = String(result)
242
+ }
243
+ if (resultText.length > 20_000) {
244
+ resultText = resultText.slice(0, 20_000) + `\n… (truncated; full result in ${getTaskOutputPath(taskId)})`
245
+ }
246
+ }
247
+ enqueuePendingNotification({
248
+ value: `<${TASK_NOTIFICATION_TAG}>
249
+ <${TASK_ID_TAG}>${taskId}</${TASK_ID_TAG}>${toolUseIdLine}
250
+ <${OUTPUT_FILE_TAG}>${getTaskOutputPath(taskId)}</${OUTPUT_FILE_TAG}>
251
+ <${STATUS_TAG}>${status}</${STATUS_TAG}>
252
+ <${SUMMARY_TAG}>${escapeXml(summary)}</${SUMMARY_TAG}>
253
+ </${TASK_NOTIFICATION_TAG}>${resultText ? `\nWorkflow result:\n${resultText}` : ''}`,
254
+ mode: 'task-notification',
255
+ priority: 'next',
256
+ agentId: s.agentId,
257
+ })
258
+ }
259
+
260
+ export async function registerWorkflowTask(
261
+ input: {
262
+ name: string
263
+ description: string
264
+ args?: unknown
265
+ phases: Array<{ title: string; detail?: string }>
266
+ toolUseId?: string
267
+ agentId?: AgentId
268
+ isBackgrounded: boolean
269
+ },
270
+ setAppState: SetAppState,
271
+ ): Promise<LocalWorkflowTaskState> {
272
+ const taskId = generateTaskId('local_workflow')
273
+ await initTaskOutput(taskId)
274
+ const abortController = new AbortController()
275
+ const unregisterCleanup = registerCleanup(async () => {
276
+ abortController.abort()
277
+ })
278
+ const state: LocalWorkflowTaskState = {
279
+ ...createTaskStateBase(taskId, 'local_workflow', input.description, input.toolUseId),
280
+ type: 'local_workflow',
281
+ status: 'running',
282
+ runId: `wf_${taskId}`,
283
+ name: input.name,
284
+ workflowDescription: input.description,
285
+ args: input.args,
286
+ phases: input.phases.map(p => ({ ...p, status: 'pending' as const })),
287
+ agents: [],
288
+ abortController,
289
+ agentControllers: new Map(),
290
+ isBackgrounded: input.isBackgrounded,
291
+ agentId: input.agentId,
292
+ unregisterCleanup,
293
+ }
294
+ registerTask(state, setAppState)
295
+ appendTaskOutput(taskId, `[workflow] ${input.name}: ${input.description}\n`)
296
+ return state
297
+ }
298
+
299
+ export function getWorkflowTasks(state: AppState): LocalWorkflowTaskState[] {
300
+ return Object.values(state.tasks ?? {}).filter(isLocalWorkflowTask)
301
+ }
@@ -0,0 +1,273 @@
1
+ import WebSocket from 'ws'
2
+ import {
3
+ OUTPUT_FILE_TAG,
4
+ STATUS_TAG,
5
+ SUMMARY_TAG,
6
+ TASK_ID_TAG,
7
+ TASK_NOTIFICATION_TAG,
8
+ TOOL_USE_ID_TAG,
9
+ } from '../../constants/xml.js'
10
+ import type { AppState } from '../../state/AppState.js'
11
+ import {
12
+ createTaskStateBase,
13
+ generateTaskId,
14
+ type SetAppState,
15
+ type Task,
16
+ type TaskContext,
17
+ type TaskHandle,
18
+ type TaskStateBase,
19
+ } from '../../Task.js'
20
+ import { createEventEmitter } from '../../tools/MonitorTool/events.js'
21
+ import type { AgentId } from '../../types/ids.js'
22
+ import { registerCleanup } from '../../utils/cleanupRegistry.js'
23
+ import { enqueuePendingNotification } from '../../utils/messageQueueManager.js'
24
+ import {
25
+ appendTaskOutput,
26
+ getTaskOutputPath,
27
+ initTaskOutput,
28
+ } from '../../utils/task/diskOutput.js'
29
+ import { registerTask, updateTaskState } from '../../utils/task/framework.js'
30
+ import { escapeXml } from '../../utils/xml.js'
31
+
32
+ /**
33
+ * WebSocket monitor: each text frame is an event, socket close ends the
34
+ * watch. Called "monitor_mcp" for compatibility with the task type the rest
35
+ * of the app knows about (dialogs, pills, kill paths).
36
+ */
37
+ export type MonitorMcpTaskState = TaskStateBase & {
38
+ type: 'monitor_mcp'
39
+ url: string
40
+ eventCount: number
41
+ closeCode?: number
42
+ error?: string
43
+ agentId?: AgentId
44
+ socket?: WebSocket | null
45
+ timeoutId?: NodeJS.Timeout
46
+ unregisterCleanup?: () => void
47
+ }
48
+
49
+ export function isMonitorMcpTask(task: unknown): task is MonitorMcpTaskState {
50
+ return (
51
+ typeof task === 'object' &&
52
+ task !== null &&
53
+ 'type' in task &&
54
+ (task as { type: unknown }).type === 'monitor_mcp'
55
+ )
56
+ }
57
+
58
+ export const MonitorMcpTask: Task = {
59
+ name: 'MonitorMcpTask',
60
+ type: 'monitor_mcp',
61
+ async kill(taskId, setAppState) {
62
+ killMonitorMcp(taskId, setAppState)
63
+ },
64
+ }
65
+
66
+ function notifyTerminal(
67
+ task: MonitorMcpTaskState,
68
+ status: 'completed' | 'failed' | 'killed',
69
+ summary: string,
70
+ setAppState: SetAppState,
71
+ ): void {
72
+ let shouldEnqueue = false
73
+ updateTaskState<MonitorMcpTaskState>(task.id, setAppState, t => {
74
+ if (t.notified) return t
75
+ shouldEnqueue = true
76
+ return { ...t, notified: true }
77
+ })
78
+ if (!shouldEnqueue) return
79
+ const toolUseIdLine = task.toolUseId
80
+ ? `\n<${TOOL_USE_ID_TAG}>${task.toolUseId}</${TOOL_USE_ID_TAG}>`
81
+ : ''
82
+ enqueuePendingNotification({
83
+ value: `<${TASK_NOTIFICATION_TAG}>
84
+ <${TASK_ID_TAG}>${task.id}</${TASK_ID_TAG}>${toolUseIdLine}
85
+ <${OUTPUT_FILE_TAG}>${getTaskOutputPath(task.id)}</${OUTPUT_FILE_TAG}>
86
+ <${STATUS_TAG}>${status}</${STATUS_TAG}>
87
+ <${SUMMARY_TAG}>${escapeXml(summary)}</${SUMMARY_TAG}>
88
+ </${TASK_NOTIFICATION_TAG}>`,
89
+ mode: 'task-notification',
90
+ priority: 'next',
91
+ agentId: task.agentId,
92
+ })
93
+ }
94
+
95
+ function finish(
96
+ taskId: string,
97
+ status: 'completed' | 'failed' | 'killed',
98
+ summaryFor: (t: MonitorMcpTaskState) => string,
99
+ patch: Partial<MonitorMcpTaskState>,
100
+ setAppState: SetAppState,
101
+ ): void {
102
+ let snapshot: MonitorMcpTaskState | null = null
103
+ updateTaskState<MonitorMcpTaskState>(taskId, setAppState, t => {
104
+ if (t.status !== 'running' && t.status !== 'pending') return t
105
+ if (t.timeoutId) clearTimeout(t.timeoutId)
106
+ t.unregisterCleanup?.()
107
+ const next: MonitorMcpTaskState = {
108
+ ...t,
109
+ ...patch,
110
+ status,
111
+ endTime: Date.now(),
112
+ socket: null,
113
+ timeoutId: undefined,
114
+ unregisterCleanup: undefined,
115
+ }
116
+ snapshot = next
117
+ return next
118
+ })
119
+ if (snapshot) {
120
+ const s = snapshot as MonitorMcpTaskState
121
+ notifyTerminal(s, status, summaryFor(s), setAppState)
122
+ }
123
+ }
124
+
125
+ export function killMonitorMcp(taskId: string, setAppState: SetAppState): void {
126
+ let socket: WebSocket | null | undefined
127
+ setAppState(prev => {
128
+ const t = prev.tasks?.[taskId]
129
+ if (isMonitorMcpTask(t)) socket = t.socket
130
+ return prev
131
+ })
132
+ try {
133
+ socket?.close(1000, 'stopped')
134
+ } catch {
135
+ // already closed
136
+ }
137
+ finish(
138
+ taskId,
139
+ 'killed',
140
+ t => `Monitor "${t.description}" stopped`,
141
+ {},
142
+ setAppState,
143
+ )
144
+ }
145
+
146
+ export function killMonitorMcpTasksForAgent(
147
+ agentId: AgentId,
148
+ getAppState: () => AppState,
149
+ setAppState: SetAppState,
150
+ ): void {
151
+ const tasks = getAppState().tasks ?? {}
152
+ for (const task of Object.values(tasks)) {
153
+ if (
154
+ isMonitorMcpTask(task) &&
155
+ task.agentId === agentId &&
156
+ (task.status === 'running' || task.status === 'pending')
157
+ ) {
158
+ killMonitorMcp(task.id, setAppState)
159
+ }
160
+ }
161
+ }
162
+
163
+ export async function spawnMonitorMcpTask(
164
+ input: {
165
+ url: string
166
+ protocols?: string[]
167
+ description: string
168
+ timeoutMs: number
169
+ toolUseId?: string
170
+ agentId?: AgentId
171
+ },
172
+ context: TaskContext,
173
+ ): Promise<TaskHandle> {
174
+ const { url, protocols, description, timeoutMs, toolUseId, agentId } = input
175
+ const { setAppState } = context
176
+ const taskId = generateTaskId('monitor_mcp')
177
+ await initTaskOutput(taskId)
178
+
179
+ const unregisterCleanup = registerCleanup(async () => {
180
+ killMonitorMcp(taskId, setAppState)
181
+ })
182
+
183
+ const socket = new WebSocket(url, protocols)
184
+ const timeoutId = setTimeout(() => {
185
+ try {
186
+ socket.close(1000, 'timeout')
187
+ } catch {
188
+ // ignore
189
+ }
190
+ finish(
191
+ taskId,
192
+ 'completed',
193
+ t =>
194
+ `Monitor "${t.description}" expired after ${Math.round(timeoutMs / 1000)}s (${t.eventCount} events)`,
195
+ {},
196
+ setAppState,
197
+ )
198
+ }, timeoutMs)
199
+ timeoutId.unref()
200
+
201
+ const taskState: MonitorMcpTaskState = {
202
+ ...createTaskStateBase(taskId, 'monitor_mcp', description, toolUseId),
203
+ type: 'monitor_mcp',
204
+ status: 'running',
205
+ url,
206
+ eventCount: 0,
207
+ agentId,
208
+ socket,
209
+ timeoutId,
210
+ unregisterCleanup,
211
+ }
212
+ registerTask(taskState, setAppState)
213
+
214
+ const emitter = createEventEmitter({
215
+ taskId,
216
+ description,
217
+ toolUseId,
218
+ agentId,
219
+ onFlood: () => {
220
+ enqueuePendingNotification({
221
+ value: `Monitor "${description}" (task ${taskId}) produced too many events per minute and was stopped. Subscribe to a filtered feed where one exists.`,
222
+ mode: 'task-notification',
223
+ priority: 'next',
224
+ agentId,
225
+ })
226
+ killMonitorMcp(taskId, setAppState)
227
+ },
228
+ })
229
+
230
+ socket.on('message', (data, isBinary) => {
231
+ const line = isBinary
232
+ ? `[binary frame, ${Buffer.byteLength(data as Buffer)} bytes]`
233
+ : data.toString()
234
+ appendTaskOutput(taskId, line + '\n')
235
+ updateTaskState<MonitorMcpTaskState>(taskId, setAppState, t => ({
236
+ ...t,
237
+ eventCount: t.eventCount + 1,
238
+ }))
239
+ emitter.push(line)
240
+ })
241
+ socket.on('error', err => {
242
+ appendTaskOutput(taskId, `[error] ${err.message}\n`)
243
+ finish(
244
+ taskId,
245
+ 'failed',
246
+ t => `Monitor "${t.description}" socket error: ${err.message}`,
247
+ { error: err.message },
248
+ setAppState,
249
+ )
250
+ })
251
+ socket.on('close', (code, reason) => {
252
+ emitter.flush()
253
+ appendTaskOutput(
254
+ taskId,
255
+ `[closed] code ${code}${reason?.length ? ` ${reason.toString()}` : ''}\n`,
256
+ )
257
+ finish(
258
+ taskId,
259
+ 'completed',
260
+ t =>
261
+ `Monitor "${t.description}" socket closed (code ${code}, ${t.eventCount} events)`,
262
+ { closeCode: code },
263
+ setAppState,
264
+ )
265
+ })
266
+
267
+ return {
268
+ taskId,
269
+ cleanup: () => {
270
+ unregisterCleanup()
271
+ },
272
+ }
273
+ }
package/src/tasks.ts CHANGED
@@ -3,15 +3,30 @@ import type { Task, TaskType } from './Task.js'
3
3
  import { DreamTask } from './tasks/DreamTask/DreamTask.js'
4
4
  import { LocalAgentTask } from './tasks/LocalAgentTask/LocalAgentTask.js'
5
5
  import { LocalShellTask } from './tasks/LocalShellTask/LocalShellTask.js'
6
+ import { MonitorMcpTask as MonitorMcpTaskImpl } from './tasks/MonitorMcpTask/MonitorMcpTask.js'
7
+ import { LocalWorkflowTask as LocalWorkflowTaskImpl } from './tasks/LocalWorkflowTask/LocalWorkflowTask.js'
6
8
  import { RemoteAgentTask } from './tasks/RemoteAgentTask/RemoteAgentTask.js'
7
9
 
8
10
  /* eslint-disable @typescript-eslint/no-require-imports */
9
- const LocalWorkflowTask: Task | null = feature('WORKFLOW_SCRIPTS')
10
- ? require('./tasks/LocalWorkflowTask/LocalWorkflowTask.js').LocalWorkflowTask
11
- : null
12
- const MonitorMcpTask: Task | null = feature('MONITOR_TOOL')
13
- ? require('./tasks/MonitorMcpTask/MonitorMcpTask.js').MonitorMcpTask
14
- : null
11
+ // Resolved lazily: these task modules import the task framework, which
12
+ // imports this file. Reading the export at top level during a CJS require
13
+ // hits the temporal dead zone ("Cannot access before initialization").
14
+ let LocalWorkflowTask: Task | null | undefined
15
+ let MonitorMcpTask: Task | null | undefined
16
+ function getOptionalTasks(): Task[] {
17
+ if (LocalWorkflowTask === undefined) {
18
+ LocalWorkflowTask = feature('WORKFLOW_SCRIPTS') ? LocalWorkflowTaskImpl : null
19
+ }
20
+ if (MonitorMcpTask === undefined) {
21
+ // Static import: the WebSocket monitor shares the task registry and
22
+ // notification queue with the app, so it must not be a CJS copy
23
+ MonitorMcpTask = feature('MONITOR_TOOL') ? MonitorMcpTaskImpl : null
24
+ }
25
+ const tasks: Task[] = []
26
+ if (LocalWorkflowTask) tasks.push(LocalWorkflowTask)
27
+ if (MonitorMcpTask) tasks.push(MonitorMcpTask)
28
+ return tasks
29
+ }
15
30
  /* eslint-enable @typescript-eslint/no-require-imports */
16
31
 
17
32
  /**
@@ -26,8 +41,7 @@ export function getAllTasks(): Task[] {
26
41
  RemoteAgentTask,
27
42
  DreamTask,
28
43
  ]
29
- if (LocalWorkflowTask) tasks.push(LocalWorkflowTask)
30
- if (MonitorMcpTask) tasks.push(MonitorMcpTask)
44
+ tasks.push(...getOptionalTasks())
31
45
  return tasks
32
46
  }
33
47
 
@@ -756,6 +756,17 @@ export async function* runAgent({
756
756
  maxTurns: maxTurns ?? agentDefinition.maxTurns,
757
757
  })) {
758
758
  onQueryProgress?.()
759
+ if (process.env.XELA_LOADER_DEBUG) {
760
+ const kinds =
761
+ message.type === 'assistant' || message.type === 'user'
762
+ ? (Array.isArray(message.message.content)
763
+ ? message.message.content.map(b => b.type).join(',')
764
+ : typeof message.message.content)
765
+ : message.type === 'stream_event'
766
+ ? message.event.type
767
+ : ''
768
+ process.stderr.write(`[runAgent ${agentDefinition.agentType}] ${message.type} ${kinds}\n`)
769
+ }
759
770
  // Forward subagent API request starts to parent's metrics display
760
771
  // so TTFT/OTPS update during subagent execution.
761
772
  if (
@@ -91,6 +91,8 @@ export function isBriefEntitled(): boolean {
91
91
  return feature('KAIROS') || feature('KAIROS_BRIEF')
92
92
  ? getKairosActive() ||
93
93
  isEnvTruthy(process.env.CLAUDE_CODE_BRIEF) ||
94
+ // Local/OpenAI-compatible backends: nobody to ask for entitlement
95
+ !!process.env.OPENAI_API_KEY ||
94
96
  getFeatureValue_CACHED_WITH_REFRESH(
95
97
  'tengu_kairos_brief',
96
98
  false,