@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,331 @@
|
|
|
1
|
+
import vm from 'node:vm'
|
|
2
|
+
import { DEFAULT_CONCURRENCY } from './constants.js'
|
|
3
|
+
|
|
4
|
+
export type WorkflowMeta = {
|
|
5
|
+
name: string
|
|
6
|
+
description: string
|
|
7
|
+
phases?: Array<{ title: string; detail?: string }>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type WorkflowAgentOptions = {
|
|
11
|
+
label?: string
|
|
12
|
+
phase?: string
|
|
13
|
+
schema?: unknown
|
|
14
|
+
model?: string
|
|
15
|
+
subagent_type?: string
|
|
16
|
+
allowedTools?: string[]
|
|
17
|
+
maxTurns?: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type WorkflowHost = {
|
|
21
|
+
/** Run one subagent to completion and return its final text. */
|
|
22
|
+
runAgent: (
|
|
23
|
+
prompt: string,
|
|
24
|
+
options: WorkflowAgentOptions,
|
|
25
|
+
agentIndex: number,
|
|
26
|
+
) => Promise<string>
|
|
27
|
+
onPhase?: (title: string) => void
|
|
28
|
+
onLog?: (line: string) => void
|
|
29
|
+
signal: AbortSignal
|
|
30
|
+
maxAgents: number
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class WorkflowScriptError extends Error {}
|
|
34
|
+
|
|
35
|
+
const FORBIDDEN = [
|
|
36
|
+
{ re: /^\s*import\s/m, what: 'import statements' },
|
|
37
|
+
{ re: /\brequire\s*\(/, what: 'require()' },
|
|
38
|
+
{ re: /\bprocess\s*\./, what: 'process' },
|
|
39
|
+
{ re: /\beval\s*\(/, what: 'eval()' },
|
|
40
|
+
{ re: /\bFunction\s*\(/, what: 'Function()' },
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Splits the script into its `export const meta = {...}` literal and the body.
|
|
45
|
+
* The literal is evaluated in an empty context; anything that is not a plain
|
|
46
|
+
* object literal fails there, which is the "pure literal" rule.
|
|
47
|
+
*/
|
|
48
|
+
export function parseWorkflowScript(script: string): {
|
|
49
|
+
meta: WorkflowMeta
|
|
50
|
+
body: string
|
|
51
|
+
} {
|
|
52
|
+
for (const { re, what } of FORBIDDEN) {
|
|
53
|
+
if (re.test(script)) {
|
|
54
|
+
throw new WorkflowScriptError(`Workflow scripts cannot use ${what}`)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const match = /export\s+const\s+meta\s*=\s*/.exec(script)
|
|
58
|
+
if (!match) {
|
|
59
|
+
throw new WorkflowScriptError(
|
|
60
|
+
'Workflow script must begin with `export const meta = {...}`',
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
const start = match.index + match[0].length
|
|
64
|
+
const end = findBalancedEnd(script, start)
|
|
65
|
+
if (end < 0) {
|
|
66
|
+
throw new WorkflowScriptError('Unterminated meta object literal')
|
|
67
|
+
}
|
|
68
|
+
const literal = script.slice(start, end + 1)
|
|
69
|
+
let meta: unknown
|
|
70
|
+
try {
|
|
71
|
+
meta = vm.runInNewContext(`(${literal})`, Object.create(null), {
|
|
72
|
+
timeout: 1000,
|
|
73
|
+
})
|
|
74
|
+
} catch (e) {
|
|
75
|
+
throw new WorkflowScriptError(
|
|
76
|
+
`meta must be a pure object literal: ${(e as Error).message}`,
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
if (
|
|
80
|
+
!meta ||
|
|
81
|
+
typeof meta !== 'object' ||
|
|
82
|
+
typeof (meta as WorkflowMeta).name !== 'string' ||
|
|
83
|
+
typeof (meta as WorkflowMeta).description !== 'string'
|
|
84
|
+
) {
|
|
85
|
+
throw new WorkflowScriptError(
|
|
86
|
+
'meta needs string `name` and `description` fields',
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
const body = script.slice(0, match.index) + script.slice(end + 1)
|
|
90
|
+
return {
|
|
91
|
+
meta: JSON.parse(JSON.stringify(meta)) as WorkflowMeta,
|
|
92
|
+
body: body.replace(/^\s*;/, ''),
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function findBalancedEnd(text: string, openIndex: number): number {
|
|
97
|
+
if (text[openIndex] !== '{') return -1
|
|
98
|
+
let depth = 0
|
|
99
|
+
let quote: string | null = null
|
|
100
|
+
for (let i = openIndex; i < text.length; i++) {
|
|
101
|
+
const ch = text[i]!
|
|
102
|
+
if (quote) {
|
|
103
|
+
if (ch === '\\') i++
|
|
104
|
+
else if (ch === quote) quote = null
|
|
105
|
+
continue
|
|
106
|
+
}
|
|
107
|
+
if (ch === '"' || ch === "'" || ch === '`') quote = ch
|
|
108
|
+
else if (ch === '{') depth++
|
|
109
|
+
else if (ch === '}') {
|
|
110
|
+
depth--
|
|
111
|
+
if (depth === 0) return i
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return -1
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Pulls the first JSON object/array out of a model answer (tolerates prose and code fences). */
|
|
118
|
+
export function extractJson(text: string): unknown {
|
|
119
|
+
const fenced = /```(?:json)?\s*([\s\S]*?)```/i.exec(text)
|
|
120
|
+
const candidates = [fenced?.[1], text].filter(
|
|
121
|
+
(s): s is string => typeof s === 'string',
|
|
122
|
+
)
|
|
123
|
+
for (const candidate of candidates) {
|
|
124
|
+
const trimmed = candidate.trim()
|
|
125
|
+
try {
|
|
126
|
+
return JSON.parse(trimmed)
|
|
127
|
+
} catch {
|
|
128
|
+
// fall through to a bracket scan
|
|
129
|
+
}
|
|
130
|
+
for (const open of ['{', '[']) {
|
|
131
|
+
const close = open === '{' ? '}' : ']'
|
|
132
|
+
const first = trimmed.indexOf(open)
|
|
133
|
+
const last = trimmed.lastIndexOf(close)
|
|
134
|
+
if (first >= 0 && last > first) {
|
|
135
|
+
try {
|
|
136
|
+
return JSON.parse(trimmed.slice(first, last + 1))
|
|
137
|
+
} catch {
|
|
138
|
+
// try the other bracket
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
throw new WorkflowScriptError(
|
|
144
|
+
`Agent did not return JSON: ${text.slice(0, 200)}`,
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function runWithConcurrency<T>(
|
|
149
|
+
thunks: Array<() => Promise<T>>,
|
|
150
|
+
concurrency: number,
|
|
151
|
+
signal: AbortSignal,
|
|
152
|
+
onError: (index: number, err: unknown) => void,
|
|
153
|
+
): Promise<Array<T | null>> {
|
|
154
|
+
const results: Array<T | null> = new Array(thunks.length).fill(null)
|
|
155
|
+
let next = 0
|
|
156
|
+
const worker = async (): Promise<void> => {
|
|
157
|
+
while (next < thunks.length) {
|
|
158
|
+
if (signal.aborted) return
|
|
159
|
+
const index = next++
|
|
160
|
+
try {
|
|
161
|
+
results[index] = await thunks[index]!()
|
|
162
|
+
} catch (err) {
|
|
163
|
+
onError(index, err)
|
|
164
|
+
results[index] = null
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const workers = Array.from(
|
|
169
|
+
{ length: Math.max(1, Math.min(concurrency, thunks.length)) },
|
|
170
|
+
() => worker(),
|
|
171
|
+
)
|
|
172
|
+
await Promise.all(workers)
|
|
173
|
+
return results
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Runs the workflow body inside a vm context with the script API as globals.
|
|
178
|
+
* Resolves to whatever the script returns.
|
|
179
|
+
*/
|
|
180
|
+
export async function runWorkflowScript(
|
|
181
|
+
body: string,
|
|
182
|
+
args: unknown,
|
|
183
|
+
host: WorkflowHost,
|
|
184
|
+
): Promise<unknown> {
|
|
185
|
+
let agentCount = 0
|
|
186
|
+
const log = (...parts: unknown[]): void => {
|
|
187
|
+
host.onLog?.(
|
|
188
|
+
parts
|
|
189
|
+
.map(p => (typeof p === 'string' ? p : safeStringify(p)))
|
|
190
|
+
.join(' '),
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const agent = async (
|
|
195
|
+
prompt: unknown,
|
|
196
|
+
options: WorkflowAgentOptions = {},
|
|
197
|
+
): Promise<unknown> => {
|
|
198
|
+
if (typeof prompt !== 'string' || prompt.trim() === '') {
|
|
199
|
+
throw new WorkflowScriptError('agent(prompt) needs a non-empty string')
|
|
200
|
+
}
|
|
201
|
+
if (host.signal.aborted) throw new WorkflowScriptError('Workflow aborted')
|
|
202
|
+
if (agentCount >= host.maxAgents) {
|
|
203
|
+
throw new WorkflowScriptError(
|
|
204
|
+
`Workflow exceeded max_agents (${host.maxAgents}); raise max_agents or launch fewer agents`,
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
const index = agentCount++
|
|
208
|
+
if (options.phase) host.onPhase?.(options.phase)
|
|
209
|
+
let fullPrompt = prompt
|
|
210
|
+
if (options.schema) {
|
|
211
|
+
fullPrompt +=
|
|
212
|
+
`\n\nRespond with ONLY a JSON value matching this JSON Schema, no prose, no markdown fences:\n` +
|
|
213
|
+
safeStringify(options.schema)
|
|
214
|
+
}
|
|
215
|
+
const text = await host.runAgent(fullPrompt, options, index)
|
|
216
|
+
return options.schema ? extractJson(text) : text
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const parallel = async (
|
|
220
|
+
items: unknown,
|
|
221
|
+
options: { concurrency?: number } = {},
|
|
222
|
+
): Promise<unknown[]> => {
|
|
223
|
+
if (!Array.isArray(items)) {
|
|
224
|
+
throw new WorkflowScriptError('parallel() expects an array of () => Promise')
|
|
225
|
+
}
|
|
226
|
+
const thunks = items.map((item, i) => {
|
|
227
|
+
if (typeof item === 'function') return item as () => Promise<unknown>
|
|
228
|
+
if (item && typeof (item as Promise<unknown>).then === 'function') {
|
|
229
|
+
return () => item as Promise<unknown>
|
|
230
|
+
}
|
|
231
|
+
throw new WorkflowScriptError(
|
|
232
|
+
`parallel(): item ${i} is neither a function nor a promise`,
|
|
233
|
+
)
|
|
234
|
+
})
|
|
235
|
+
return runWithConcurrency(
|
|
236
|
+
thunks,
|
|
237
|
+
options.concurrency ?? DEFAULT_CONCURRENCY,
|
|
238
|
+
host.signal,
|
|
239
|
+
(i, err) => log(`parallel[${i}] failed: ${errorText(err)}`),
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const pipeline = async (
|
|
244
|
+
items: unknown,
|
|
245
|
+
...stages: unknown[]
|
|
246
|
+
): Promise<unknown[]> => {
|
|
247
|
+
if (!Array.isArray(items)) {
|
|
248
|
+
throw new WorkflowScriptError('pipeline() expects an array as first argument')
|
|
249
|
+
}
|
|
250
|
+
if (stages.length === 0 || stages.some(s => typeof s !== 'function')) {
|
|
251
|
+
throw new WorkflowScriptError('pipeline() needs one or more stage functions')
|
|
252
|
+
}
|
|
253
|
+
const fns = stages as Array<(input: unknown) => Promise<unknown>>
|
|
254
|
+
const thunks = items.map(item => async () => {
|
|
255
|
+
let value: unknown = item
|
|
256
|
+
for (const stage of fns) {
|
|
257
|
+
value = await stage(value)
|
|
258
|
+
}
|
|
259
|
+
return value
|
|
260
|
+
})
|
|
261
|
+
return runWithConcurrency(thunks, DEFAULT_CONCURRENCY, host.signal, (i, err) =>
|
|
262
|
+
log(`pipeline[${i}] failed: ${errorText(err)}`),
|
|
263
|
+
)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const phase = async (title: unknown, fn?: unknown): Promise<unknown> => {
|
|
267
|
+
if (typeof title === 'string') host.onPhase?.(title)
|
|
268
|
+
if (typeof fn === 'function') return (fn as () => Promise<unknown>)()
|
|
269
|
+
return undefined
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const sleep = (ms: unknown): Promise<void> =>
|
|
273
|
+
new Promise(resolve => {
|
|
274
|
+
const t = setTimeout(resolve, Math.max(0, Number(ms) || 0))
|
|
275
|
+
t.unref()
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
const sandbox: Record<string, unknown> = {
|
|
279
|
+
agent,
|
|
280
|
+
parallel,
|
|
281
|
+
pipeline,
|
|
282
|
+
phase,
|
|
283
|
+
sleep,
|
|
284
|
+
log,
|
|
285
|
+
console: { log, info: log, warn: log, error: log },
|
|
286
|
+
args: args === undefined ? undefined : JSON.parse(JSON.stringify(args)),
|
|
287
|
+
JSON,
|
|
288
|
+
Math,
|
|
289
|
+
Date,
|
|
290
|
+
Promise,
|
|
291
|
+
Array,
|
|
292
|
+
Object,
|
|
293
|
+
String,
|
|
294
|
+
Number,
|
|
295
|
+
Boolean,
|
|
296
|
+
RegExp,
|
|
297
|
+
Error,
|
|
298
|
+
Set,
|
|
299
|
+
Map,
|
|
300
|
+
setTimeout,
|
|
301
|
+
clearTimeout,
|
|
302
|
+
structuredClone,
|
|
303
|
+
}
|
|
304
|
+
const context = vm.createContext(sandbox, { name: 'workflow' })
|
|
305
|
+
const wrapped = `(async () => {\n${body}\n})()`
|
|
306
|
+
const script = new vm.Script(wrapped, { filename: 'workflow.js' })
|
|
307
|
+
const resultPromise = script.runInContext(context) as Promise<unknown>
|
|
308
|
+
return await Promise.race([
|
|
309
|
+
resultPromise,
|
|
310
|
+
new Promise<never>((_, reject) => {
|
|
311
|
+
if (host.signal.aborted) reject(new WorkflowScriptError('Workflow aborted'))
|
|
312
|
+
host.signal.addEventListener(
|
|
313
|
+
'abort',
|
|
314
|
+
() => reject(new WorkflowScriptError('Workflow aborted')),
|
|
315
|
+
{ once: true },
|
|
316
|
+
)
|
|
317
|
+
}),
|
|
318
|
+
])
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export function errorText(err: unknown): string {
|
|
322
|
+
return err instanceof Error ? err.message : String(err)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function safeStringify(value: unknown): string {
|
|
326
|
+
try {
|
|
327
|
+
return JSON.stringify(value, null, 2) ?? String(value)
|
|
328
|
+
} catch {
|
|
329
|
+
return String(value)
|
|
330
|
+
}
|
|
331
|
+
}
|
package/src/tools.ts
CHANGED
|
@@ -23,9 +23,7 @@ const SuggestBackgroundPRTool =
|
|
|
23
23
|
.SuggestBackgroundPRTool
|
|
24
24
|
: null
|
|
25
25
|
const SleepTool =
|
|
26
|
-
feature('PROACTIVE') || feature('KAIROS')
|
|
27
|
-
? require('./tools/SleepTool/SleepTool.js').SleepTool
|
|
28
|
-
: null
|
|
26
|
+
feature('PROACTIVE') || feature('KAIROS') ? SleepToolImpl : null
|
|
29
27
|
const cronTools = feature('AGENT_TRIGGERS')
|
|
30
28
|
? [
|
|
31
29
|
require('./tools/ScheduleCronTool/CronCreateTool.js').CronCreateTool,
|
|
@@ -36,20 +34,15 @@ const cronTools = feature('AGENT_TRIGGERS')
|
|
|
36
34
|
const RemoteTriggerTool = feature('AGENT_TRIGGERS_REMOTE')
|
|
37
35
|
? require('./tools/RemoteTriggerTool/RemoteTriggerTool.js').RemoteTriggerTool
|
|
38
36
|
: null
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
: null
|
|
37
|
+
// Static import (not require): the Monitor shares the notification queue and
|
|
38
|
+
// task registry with the main loop, and the CJS require shim would hand it
|
|
39
|
+
// private copies of those modules.
|
|
40
|
+
const MonitorTool = feature('MONITOR_TOOL') ? MonitorToolImpl : null
|
|
41
|
+
const SendUserFileTool =
|
|
42
|
+
feature('KAIROS') || feature('KAIROS_PUSH_NOTIFICATION') ? SendUserFileToolImpl : null
|
|
45
43
|
const PushNotificationTool =
|
|
46
|
-
feature('KAIROS') || feature('KAIROS_PUSH_NOTIFICATION')
|
|
47
|
-
|
|
48
|
-
.PushNotificationTool
|
|
49
|
-
: null
|
|
50
|
-
const SubscribePRTool = feature('KAIROS_GITHUB_WEBHOOKS')
|
|
51
|
-
? require('./tools/SubscribePRTool/SubscribePRTool.js').SubscribePRTool
|
|
52
|
-
: null
|
|
44
|
+
feature('KAIROS') || feature('KAIROS_PUSH_NOTIFICATION') ? PushNotificationToolImpl : null
|
|
45
|
+
const SubscribePRTool = feature('KAIROS_GITHUB_WEBHOOKS') ? SubscribePRToolImpl : null
|
|
53
46
|
/* eslint-enable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */
|
|
54
47
|
import { TaskOutputTool } from './tools/TaskOutputTool/TaskOutputTool.js'
|
|
55
48
|
import { WebSearchTool } from './tools/WebSearchTool/WebSearchTool.js'
|
|
@@ -90,8 +83,7 @@ import { isTodoV2Enabled } from './utils/tasks.js'
|
|
|
90
83
|
/* eslint-disable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */
|
|
91
84
|
const VerifyPlanExecutionTool =
|
|
92
85
|
process.env.CLAUDE_CODE_VERIFY_PLAN === 'true'
|
|
93
|
-
?
|
|
94
|
-
.VerifyPlanExecutionTool
|
|
86
|
+
? VerifyPlanExecutionToolImpl
|
|
95
87
|
: null
|
|
96
88
|
/* eslint-enable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */
|
|
97
89
|
import { SYNTHETIC_OUTPUT_TOOL_NAME } from './tools/SyntheticOutputTool/SyntheticOutputTool.js'
|
|
@@ -104,32 +96,29 @@ export {
|
|
|
104
96
|
import { feature } from 'bun:bundle'
|
|
105
97
|
// Dead code elimination: conditional import for OVERFLOW_TEST_TOOL
|
|
106
98
|
/* eslint-disable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */
|
|
107
|
-
const OverflowTestTool = feature('OVERFLOW_TEST_TOOL')
|
|
108
|
-
|
|
109
|
-
: null
|
|
110
|
-
const CtxInspectTool = feature('CONTEXT_COLLAPSE')
|
|
111
|
-
? require('./tools/CtxInspectTool/CtxInspectTool.js').CtxInspectTool
|
|
112
|
-
: null
|
|
99
|
+
const OverflowTestTool = feature('OVERFLOW_TEST_TOOL') ? OverflowTestToolImpl : null
|
|
100
|
+
const CtxInspectTool = feature('CONTEXT_COLLAPSE') ? CtxInspectToolImpl : null
|
|
113
101
|
const TerminalCaptureTool = feature('TERMINAL_PANEL')
|
|
114
|
-
?
|
|
115
|
-
.TerminalCaptureTool
|
|
102
|
+
? TerminalCaptureToolImpl
|
|
116
103
|
: null
|
|
117
104
|
const WebBrowserTool = feature('WEB_BROWSER_TOOL')
|
|
118
|
-
?
|
|
105
|
+
? WebBrowserToolImpl
|
|
119
106
|
: null
|
|
120
107
|
const coordinatorModeModule = feature('COORDINATOR_MODE')
|
|
121
108
|
? (require('./coordinator/coordinatorMode.js') as typeof import('./coordinator/coordinatorMode.js'))
|
|
122
109
|
: null
|
|
123
110
|
const SnipTool = feature('HISTORY_SNIP')
|
|
124
|
-
?
|
|
111
|
+
? SnipToolImpl
|
|
125
112
|
: null
|
|
126
113
|
const ListPeersTool = feature('UDS_INBOX')
|
|
127
|
-
?
|
|
114
|
+
? ListPeersToolImpl
|
|
128
115
|
: null
|
|
116
|
+
// Static import (see MonitorTool): the workflow shares the task registry and
|
|
117
|
+
// notification queue with the app
|
|
129
118
|
const WorkflowTool = feature('WORKFLOW_SCRIPTS')
|
|
130
119
|
? (() => {
|
|
131
|
-
|
|
132
|
-
return
|
|
120
|
+
initBundledWorkflows()
|
|
121
|
+
return WorkflowToolImpl
|
|
133
122
|
})()
|
|
134
123
|
: null
|
|
135
124
|
/* eslint-enable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */
|
|
@@ -140,6 +129,21 @@ import { isEnvTruthy } from './utils/envUtils.js'
|
|
|
140
129
|
import { isPowerShellToolEnabled } from './utils/shell/shellToolUtils.js'
|
|
141
130
|
import { isAgentSwarmsEnabled } from './utils/agentSwarmsEnabled.js'
|
|
142
131
|
import { isWorktreeModeEnabled } from './utils/worktreeModeEnabled.js'
|
|
132
|
+
import { MonitorTool as MonitorToolImpl } from './tools/MonitorTool/MonitorTool.js'
|
|
133
|
+
import { SendUserFileTool as SendUserFileToolImpl } from './tools/SendUserFileTool/SendUserFileTool.js'
|
|
134
|
+
import { PushNotificationTool as PushNotificationToolImpl } from './tools/PushNotificationTool/PushNotificationTool.js'
|
|
135
|
+
import { SubscribePRTool as SubscribePRToolImpl } from './tools/SubscribePRTool/SubscribePRTool.js'
|
|
136
|
+
import { OverflowTestTool as OverflowTestToolImpl } from './tools/OverflowTestTool/OverflowTestTool.js'
|
|
137
|
+
import { SleepTool as SleepToolImpl } from './tools/SleepTool/SleepTool.js'
|
|
138
|
+
import { TerminalCaptureTool as TerminalCaptureToolImpl } from './tools/TerminalCaptureTool/TerminalCaptureTool.js'
|
|
139
|
+
import { WebBrowserTool as WebBrowserToolImpl } from './tools/WebBrowserTool/WebBrowserTool.js'
|
|
140
|
+
import { SnipTool as SnipToolImpl } from './tools/SnipTool/SnipTool.js'
|
|
141
|
+
import { ListPeersTool as ListPeersToolImpl } from './tools/ListPeersTool/ListPeersTool.js'
|
|
142
|
+
import { VerifyPlanExecutionTool as VerifyPlanExecutionToolImpl } from './tools/VerifyPlanExecutionTool/VerifyPlanExecutionTool.js'
|
|
143
|
+
import { CtxInspectTool as CtxInspectToolImpl } from './tools/CtxInspectTool/CtxInspectTool.js'
|
|
144
|
+
import { WorkflowTool as WorkflowToolImpl } from './tools/WorkflowTool/WorkflowTool.js'
|
|
145
|
+
import { initBundledWorkflows } from './tools/WorkflowTool/bundled/index.js'
|
|
146
|
+
import { DiscoverSkillsTool as DiscoverSkillsToolImpl } from './tools/DiscoverSkillsTool/DiscoverSkillsTool.js'
|
|
143
147
|
import {
|
|
144
148
|
REPL_TOOL_NAME,
|
|
145
149
|
REPL_ONLY_TOOLS,
|
|
@@ -231,6 +235,7 @@ export function getAllBaseTools(): Tools {
|
|
|
231
235
|
...(VerifyPlanExecutionTool ? [VerifyPlanExecutionTool] : []),
|
|
232
236
|
...(process.env.USER_TYPE === 'ant' && REPLTool ? [REPLTool] : []),
|
|
233
237
|
...(WorkflowTool ? [WorkflowTool] : []),
|
|
238
|
+
...(feature('EXPERIMENTAL_SKILL_SEARCH') ? [DiscoverSkillsToolImpl] : []),
|
|
234
239
|
...(SleepTool ? [SleepTool] : []),
|
|
235
240
|
...cronTools,
|
|
236
241
|
...(RemoteTriggerTool ? [RemoteTriggerTool] : []),
|
package/src/utils/betas.ts
CHANGED
|
@@ -163,6 +163,11 @@ export function modelSupportsAutoMode(model: string): boolean {
|
|
|
163
163
|
// External: firstParty-only at launch (PI probes not wired for
|
|
164
164
|
// Bedrock/Vertex/Foundry yet). Checked before allowModels so the GB
|
|
165
165
|
// override can't enable auto mode on unsupported providers.
|
|
166
|
+
// OpenAI-compatible backend: the classifier runs on the local model itself
|
|
167
|
+
// (getClassifierModel falls back to the main loop model), so no provider gate
|
|
168
|
+
if (process.env.OPENAI_API_KEY) {
|
|
169
|
+
return true
|
|
170
|
+
}
|
|
166
171
|
if (process.env.USER_TYPE !== 'ant' && getAPIProvider() !== 'firstParty') {
|
|
167
172
|
return false
|
|
168
173
|
}
|
package/src/utils/context.ts
CHANGED
|
@@ -52,6 +52,15 @@ export function getContextWindowForModel(
|
|
|
52
52
|
model: string,
|
|
53
53
|
betas?: string[],
|
|
54
54
|
): number {
|
|
55
|
+
// OpenAI-compatible backends: the real window comes from the server (a local
|
|
56
|
+
// llama.cpp may run 32k-128k), so auto-compact and the context meter follow it
|
|
57
|
+
// instead of the Claude default
|
|
58
|
+
if (process.env.OPENAI_API_KEY && process.env.OPENAI_CONTEXT_WINDOW) {
|
|
59
|
+
const override = parseInt(process.env.OPENAI_CONTEXT_WINDOW, 10)
|
|
60
|
+
if (!isNaN(override) && override > 0) {
|
|
61
|
+
return override
|
|
62
|
+
}
|
|
63
|
+
}
|
|
55
64
|
// Allow override via environment variable (ant-only)
|
|
56
65
|
// This takes precedence over all other context window resolution, including 1M detection,
|
|
57
66
|
// so users can cap the effective context window for local decisions (auto-compact, etc.)
|
|
@@ -64,7 +64,7 @@ const LEGACY_BRIEF_TOOL_NAME: string | null =
|
|
|
64
64
|
require('../tools/BriefTool/prompt.js') as typeof import('../tools/BriefTool/prompt.js')
|
|
65
65
|
).LEGACY_BRIEF_TOOL_NAME
|
|
66
66
|
: null
|
|
67
|
-
const SEND_USER_FILE_TOOL_NAME: string | null = feature('KAIROS')
|
|
67
|
+
const SEND_USER_FILE_TOOL_NAME: string | null = feature('KAIROS') || feature('KAIROS_PUSH_NOTIFICATION')
|
|
68
68
|
? (
|
|
69
69
|
require('../tools/SendUserFileTool/prompt.js') as typeof import('../tools/SendUserFileTool/prompt.js')
|
|
70
70
|
).SEND_USER_FILE_TOOL_NAME
|
package/src/utils/forkedAgent.ts
CHANGED
|
@@ -105,6 +105,8 @@ export type ForkedAgentParams = {
|
|
|
105
105
|
maxTurns?: number
|
|
106
106
|
/** Optional callback invoked for each message as it arrives (for streaming UI) */
|
|
107
107
|
onMessage?: (message: Message) => void
|
|
108
|
+
/** Called for every raw stream event (text deltas etc.) before they are dropped. */
|
|
109
|
+
onStreamEvent?: (event: unknown) => void
|
|
108
110
|
/** Skip sidechain transcript recording (e.g., for ephemeral work like speculation) */
|
|
109
111
|
skipTranscript?: boolean
|
|
110
112
|
/** Skip writing new prompt cache entries on the last message. For
|
|
@@ -496,6 +498,7 @@ export async function runForkedAgent({
|
|
|
496
498
|
maxOutputTokens,
|
|
497
499
|
maxTurns,
|
|
498
500
|
onMessage,
|
|
501
|
+
onStreamEvent,
|
|
499
502
|
skipTranscript,
|
|
500
503
|
skipCacheWrite,
|
|
501
504
|
}: ForkedAgentParams): Promise<ForkedAgentResult> {
|
|
@@ -556,6 +559,7 @@ export async function runForkedAgent({
|
|
|
556
559
|
})) {
|
|
557
560
|
// Extract real usage from message_delta stream events (final usage per API call)
|
|
558
561
|
if (message.type === 'stream_event') {
|
|
562
|
+
onStreamEvent?.(message)
|
|
559
563
|
if (
|
|
560
564
|
'event' in message &&
|
|
561
565
|
message.event?.type === 'message_delta' &&
|
package/src/utils/logoV2Utils.ts
CHANGED
|
@@ -253,9 +253,13 @@ export function getLogoDisplayData(): {
|
|
|
253
253
|
const cwd = serverUrl
|
|
254
254
|
? `${displayPath} in ${serverUrl.replace(/^https?:\/\//, '')}`
|
|
255
255
|
: displayPath
|
|
256
|
-
const billingType =
|
|
257
|
-
?
|
|
258
|
-
|
|
256
|
+
const billingType = process.env.OPENAI_API_KEY
|
|
257
|
+
? process.env.OPENAI_BASE_URL && /localhost|127\.0\.0\.1/.test(process.env.OPENAI_BASE_URL)
|
|
258
|
+
? 'Local model'
|
|
259
|
+
: 'OpenAI-compatible API'
|
|
260
|
+
: isClaudeAISubscriber()
|
|
261
|
+
? getSubscriptionName()
|
|
262
|
+
: 'API Usage Billing'
|
|
259
263
|
const agentName = getInitialSettings().agent
|
|
260
264
|
|
|
261
265
|
return {
|
|
@@ -50,13 +50,27 @@ function logOperation(operation: QueueOperation, content?: string): void {
|
|
|
50
50
|
// Within the same priority, commands are processed FIFO.
|
|
51
51
|
// ============================================================================
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
// Process-wide state. Feature-gated modules are loaded through the CJS
|
|
54
|
+
// `require` shim and get their own copy of this module; without a shared
|
|
55
|
+
// store they would enqueue into a queue nobody drains.
|
|
56
|
+
type QueueGlobals = {
|
|
57
|
+
commandQueue: QueuedCommand[]
|
|
58
|
+
/** Frozen snapshot — recreated on every mutation for useSyncExternalStore. */
|
|
59
|
+
snapshot: readonly QueuedCommand[]
|
|
60
|
+
queueChanged: ReturnType<typeof createSignal>
|
|
61
|
+
}
|
|
62
|
+
const queueGlobals: QueueGlobals = ((
|
|
63
|
+
globalThis as unknown as { __xelaMessageQueue?: QueueGlobals }
|
|
64
|
+
).__xelaMessageQueue ??= {
|
|
65
|
+
commandQueue: [],
|
|
66
|
+
snapshot: Object.freeze([]),
|
|
67
|
+
queueChanged: createSignal(),
|
|
68
|
+
})
|
|
69
|
+
const commandQueue: QueuedCommand[] = queueGlobals.commandQueue
|
|
70
|
+
const queueChanged = queueGlobals.queueChanged
|
|
57
71
|
|
|
58
72
|
function notifySubscribers(): void {
|
|
59
|
-
snapshot = Object.freeze([...commandQueue])
|
|
73
|
+
queueGlobals.snapshot = Object.freeze([...commandQueue])
|
|
60
74
|
queueChanged.emit()
|
|
61
75
|
}
|
|
62
76
|
|
|
@@ -76,7 +90,7 @@ export const subscribeToCommandQueue = queueChanged.subscribe
|
|
|
76
90
|
* Returns a frozen array that only changes reference on mutation.
|
|
77
91
|
*/
|
|
78
92
|
export function getCommandQueueSnapshot(): readonly QueuedCommand[] {
|
|
79
|
-
return snapshot
|
|
93
|
+
return queueGlobals.snapshot
|
|
80
94
|
}
|
|
81
95
|
|
|
82
96
|
// ============================================================================
|
|
@@ -328,12 +342,12 @@ export function clearCommandQueue(): void {
|
|
|
328
342
|
}
|
|
329
343
|
|
|
330
344
|
/**
|
|
331
|
-
* Clear all commands and reset snapshot.
|
|
345
|
+
* Clear all commands and reset queueGlobals.snapshot.
|
|
332
346
|
* Used for test cleanup.
|
|
333
347
|
*/
|
|
334
348
|
export function resetCommandQueue(): void {
|
|
335
349
|
commandQueue.length = 0
|
|
336
|
-
snapshot = Object.freeze([])
|
|
350
|
+
queueGlobals.snapshot = Object.freeze([])
|
|
337
351
|
}
|
|
338
352
|
|
|
339
353
|
// ============================================================================
|
|
@@ -492,7 +506,7 @@ export const subscribeToPendingNotifications = subscribeToCommandQueue
|
|
|
492
506
|
|
|
493
507
|
/** @deprecated Use getCommandQueueSnapshot */
|
|
494
508
|
export function getPendingNotificationsSnapshot(): readonly QueuedCommand[] {
|
|
495
|
-
return snapshot
|
|
509
|
+
return queueGlobals.snapshot
|
|
496
510
|
}
|
|
497
511
|
|
|
498
512
|
/** @deprecated Use hasCommandsInQueue */
|
package/src/utils/model/model.ts
CHANGED
|
@@ -63,6 +63,12 @@ export function isNonCustomOpusModel(model: ModelName): boolean {
|
|
|
63
63
|
* 4. Settings (from user's saved settings)
|
|
64
64
|
*/
|
|
65
65
|
export function getUserSpecifiedModelSetting(): ModelSetting | undefined {
|
|
66
|
+
// OpenAI-compatible backend: the configured model wins over any Claude model
|
|
67
|
+
// left in settings, so the UI shows what actually answers
|
|
68
|
+
if (process.env.OPENAI_API_KEY) {
|
|
69
|
+
return process.env.OPENAI_MODEL || 'gpt-4o'
|
|
70
|
+
}
|
|
71
|
+
|
|
66
72
|
let specifiedModel: ModelSetting | undefined
|
|
67
73
|
|
|
68
74
|
const modelOverride = getMainLoopModelOverride()
|
|
@@ -74,8 +80,7 @@ export function getUserSpecifiedModelSetting(): ModelSetting | undefined {
|
|
|
74
80
|
}
|
|
75
81
|
|
|
76
82
|
// Ignore the user-specified model if it's not in the availableModels allowlist.
|
|
77
|
-
|
|
78
|
-
if (specifiedModel && !process.env.OPENAI_API_KEY && !isModelAllowed(specifiedModel)) {
|
|
83
|
+
if (specifiedModel && !isModelAllowed(specifiedModel)) {
|
|
79
84
|
return undefined
|
|
80
85
|
}
|
|
81
86
|
|
|
@@ -95,12 +100,8 @@ export function getUserSpecifiedModelSetting(): ModelSetting | undefined {
|
|
|
95
100
|
* @returns The resolved model name to use
|
|
96
101
|
*/
|
|
97
102
|
export function getMainLoopModel(): ModelName {
|
|
98
|
-
// When using OpenAI backend,
|
|
103
|
+
// When using OpenAI backend, return the OpenAI model name directly
|
|
99
104
|
if (process.env.OPENAI_API_KEY) {
|
|
100
|
-
const model = getUserSpecifiedModelSetting()
|
|
101
|
-
if (model !== undefined && model !== null) {
|
|
102
|
-
return model
|
|
103
|
-
}
|
|
104
105
|
return process.env.OPENAI_MODEL || 'gpt-4o'
|
|
105
106
|
}
|
|
106
107
|
const model = getUserSpecifiedModelSetting()
|
|
@@ -165,9 +166,9 @@ export function getRuntimeMainLoopModel(params: {
|
|
|
165
166
|
}): ModelName {
|
|
166
167
|
const { permissionMode, mainLoopModel, exceeds200kTokens = false } = params
|
|
167
168
|
|
|
168
|
-
// When using OpenAI backend, use the
|
|
169
|
+
// When using OpenAI backend, always use the configured model
|
|
169
170
|
if (process.env.OPENAI_API_KEY) {
|
|
170
|
-
return
|
|
171
|
+
return process.env.OPENAI_MODEL || 'gpt-4o'
|
|
171
172
|
}
|
|
172
173
|
|
|
173
174
|
// opusplan uses Opus in plan mode without [1m] suffix.
|