@xelauvas/xela-cli 0.3.1 → 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.
- package/bin/xela.js +0 -12
- package/local/README.md +112 -0
- package/local/start-qwen.sh +47 -0
- package/local/start-tunnel.sh +36 -0
- package/local/xela-local.sh +139 -0
- package/package.json +9 -5
- package/run.sh +33 -0
- package/src/Tool.ts +1 -1
- package/src/_shims/_generated_stubs/_universal_stub.mjs +168 -0
- package/src/_shims/_generated_stubs/stub_346f96741420f62caece15b196e233c6.mjs +19 -0
- package/src/_shims/_generated_stubs/stub_4b79af2a6ac1af5a53a55fd666b2bbb4.mjs +3 -0
- package/src/_shims/_generated_stubs/stub_94345ddeadacc184ff895597ec5abf71.mjs +6 -0
- package/src/_shims/_generated_stubs/stub_99fa0bc272643c549d8a2ec6d578436f.mjs +3 -0
- package/src/_shims/_generated_stubs/stub_e3451b131fcaf272a2a75270b9a427a3.mjs +9 -0
- package/src/_shims/_generated_stubs/text_stub.mjs +1 -0
- package/src/_shims/bun_bundle.js +79 -4
- package/src/_shims/cjs_stub.cjs +32 -10
- package/src/_shims/loader.js +104 -5
- package/src/_shims/openai_adapter.ts +446 -230
- package/src/_shims/register.js +16 -3
- package/src/assistant/gate.ts +15 -0
- package/src/assistant/index.ts +89 -0
- package/src/bootstrap/state.ts +6 -1
- package/src/bridge/peerSessions.ts +18 -0
- package/src/cli/print.ts +5 -0
- package/src/commands/assistant/assistant.ts +17 -0
- package/src/commands/assistant/index.ts +11 -0
- package/src/commands/brief.ts +3 -1
- package/src/commands/buddy/buddy.ts +88 -0
- package/src/commands/buddy/index.ts +12 -0
- package/src/commands/model/model.tsx +8 -23
- package/src/commands/peers/index.ts +11 -0
- package/src/commands/peers/peers.ts +24 -0
- package/src/commands/proactive/proactive.ts +64 -0
- package/src/commands/proactive.ts +12 -0
- package/src/commands/workflows/index.ts +12 -0
- package/src/commands/workflows/workflows.ts +36 -0
- package/src/commands.ts +4 -8
- package/src/components/Messages.tsx +1 -1
- package/src/components/ModelPicker.tsx +4 -34
- package/src/components/Spinner.tsx +54 -1
- package/src/components/StructuredDiff/colorDiff.ts +3 -1
- package/src/components/messages/SnipBoundaryMessage.tsx +15 -0
- package/src/components/messages/UserCrossSessionMessage.tsx +26 -0
- package/src/components/permissions/PermissionRequest.tsx +4 -2
- package/src/components/permissions/ReviewArtifactPermissionRequest/ReviewArtifactPermissionRequest.tsx +96 -0
- package/src/constants/prompts.ts +26 -0
- package/src/ink/terminal.ts +9 -1
- package/src/jobs/classifier.ts +103 -0
- package/src/memdir/paths.ts +13 -1
- package/src/proactive/index.ts +158 -0
- package/src/proactive/useProactive.ts +78 -0
- package/src/screens/REPL.tsx +28 -8
- package/src/services/api/claude.ts +6 -1
- package/src/services/compact/autoCompact.ts +18 -4
- package/src/services/compact/cachedMCConfig.ts +1 -0
- package/src/services/compact/cachedMicrocompact.ts +113 -0
- package/src/services/compact/compact.ts +22 -2
- package/src/services/compact/reactiveCompact.ts +251 -0
- package/src/services/compact/snipCompact.ts +282 -0
- package/src/services/compact/snipProjection.ts +50 -0
- package/src/services/contextCollapse/index.ts +322 -0
- package/src/services/contextCollapse/operations.ts +187 -0
- package/src/services/contextCollapse/persist.ts +40 -0
- package/src/services/extractMemories/extractMemories.ts +11 -2
- package/src/services/skillSearch/featureCheck.ts +12 -0
- package/src/services/skillSearch/localSearch.ts +98 -0
- package/src/services/skillSearch/prefetch.ts +125 -0
- package/src/services/skillSearch/remoteSkillLoader.ts +9 -0
- package/src/services/skillSearch/remoteSkillState.ts +21 -0
- package/src/services/skillSearch/signals.ts +7 -0
- package/src/services/skillSearch/telemetry.ts +9 -0
- package/src/setup.ts +10 -5
- package/src/skills/bundled/dream.ts +31 -0
- package/src/skills/bundled/hunter.ts +45 -0
- package/src/skills/bundled/index.ts +10 -23
- package/src/skills/bundled/runSkillGenerator.ts +61 -0
- package/src/skills/mcpSkills.ts +101 -0
- package/src/tasks/LocalWorkflowTask/LocalWorkflowTask.ts +301 -0
- package/src/tasks/MonitorMcpTask/MonitorMcpTask.ts +273 -0
- package/src/tasks.ts +22 -8
- package/src/tools/AgentTool/runAgent.ts +11 -0
- package/src/tools/BriefTool/BriefTool.ts +2 -0
- package/src/tools/CtxInspectTool/CtxInspectTool.ts +102 -0
- package/src/tools/DiscoverSkillsTool/DiscoverSkillsTool.ts +78 -0
- package/src/tools/DiscoverSkillsTool/prompt.ts +3 -0
- package/src/tools/ListPeersTool/ListPeersTool.ts +89 -0
- package/src/tools/MCPTool/MCPTool.ts +6 -0
- package/src/tools/MonitorTool/MonitorTool.ts +273 -0
- package/src/tools/MonitorTool/UI.tsx +25 -0
- package/src/tools/MonitorTool/events.ts +84 -0
- package/src/tools/MonitorTool/prompt.ts +45 -0
- package/src/tools/OverflowTestTool/OverflowTestTool.ts +73 -0
- package/src/tools/PushNotificationTool/PushNotificationTool.ts +119 -0
- package/src/tools/ReviewArtifactTool/ReviewArtifactTool.ts +93 -0
- package/src/tools/ReviewArtifactTool/prompt.ts +5 -0
- package/src/tools/SendUserFileTool/SendUserFileTool.ts +87 -0
- package/src/tools/SendUserFileTool/prompt.ts +7 -0
- package/src/tools/SleepTool/SleepTool.ts +126 -0
- package/src/tools/SnipTool/SnipTool.ts +90 -0
- package/src/tools/SnipTool/prompt.ts +5 -0
- package/src/tools/SubscribePRTool/SubscribePRTool.ts +115 -0
- package/src/tools/TerminalCaptureTool/TerminalCaptureTool.ts +129 -0
- package/src/tools/TerminalCaptureTool/prompt.ts +8 -0
- package/src/tools/ToolSearchTool/prompt.ts +1 -1
- package/src/tools/VerifyPlanExecutionTool/VerifyPlanExecutionTool.ts +99 -0
- package/src/tools/WebBrowserTool/WebBrowserPanel.tsx +10 -0
- package/src/tools/WebBrowserTool/WebBrowserTool.ts +190 -0
- package/src/tools/WebBrowserTool/prompt.ts +16 -0
- package/src/tools/WorkflowTool/UI.tsx +27 -0
- package/src/tools/WorkflowTool/WorkflowTool.ts +401 -0
- package/src/tools/WorkflowTool/bundled/index.ts +88 -0
- package/src/tools/WorkflowTool/constants.ts +7 -0
- package/src/tools/WorkflowTool/createWorkflowCommand.ts +84 -0
- package/src/tools/WorkflowTool/prompt.ts +38 -0
- package/src/tools/WorkflowTool/runtime.ts +331 -0
- package/src/tools.ts +36 -31
- package/src/utils/betas.ts +5 -0
- package/src/utils/context.ts +9 -0
- package/src/utils/conversationRecovery.ts +1 -1
- package/src/utils/forkedAgent.ts +4 -0
- package/src/utils/logoV2Utils.ts +7 -3
- package/src/utils/messageQueueManager.ts +23 -9
- package/src/utils/model/model.ts +10 -9
- package/src/utils/model/modelOptions.ts +0 -21
- package/src/utils/model/validateModel.ts +0 -19
- package/src/utils/permissions/permissionSetup.ts +5 -0
- package/src/utils/permissions/permissions.ts +25 -0
- package/src/utils/permissions/yolo-classifier-prompts/auto_mode_system_prompt.txt +38 -0
- package/src/utils/permissions/yolo-classifier-prompts/permissions_external.txt +25 -0
- package/src/utils/permissions/yoloClassifier.ts +28 -6
- package/src/utils/plugins/officialMarketplaceStartupCheck.ts +9 -0
- package/src/utils/systemThemeWatcher.ts +62 -0
- package/src/utils/udsClient.ts +115 -0
- package/src/utils/udsMessaging.ts +157 -0
- package/src/utils/yaml.ts +251 -5
- 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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
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,
|