@menghuan1918/dsh-apollo 0.4.1 → 0.5.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/README.md +135 -131
- package/cordis.patch.yml +75 -42
- package/docs/DEVELOPMENT.md +280 -468
- package/package.json +7 -4
- package/presets/eng/plugins/exit-guard/index.js +39 -51
- package/presets/eng/plugins/mode-instructions/index.js +23 -59
- package/presets/eng-ptc.patch.yml +5 -9
- package/presets/eng.patch.yml +29 -36
- package/src/client.js +63 -912
- package/src/config.js +31 -10
- package/src/index.js +13 -7
- package/src/migrate.js +602 -0
- package/src/owns-host.js +66 -0
- package/src/settings.js +39 -35
- package/presets/eng/plugins/wait-subagent/index.js +0 -233
- package/presets/eng/plugins/wait-subagent/package.json +0 -7
- package/src/cli/claude.js +0 -166
- package/src/cli/codex.js +0 -164
- package/src/cli/jsonl.js +0 -216
- package/src/cli-adapter.js +0 -198
- package/src/cli-sessions.js +0 -69
- package/src/panel.js +0 -844
- package/src/subagents.js +0 -198
package/src/subagents.js
DELETED
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto'
|
|
2
|
-
import { homedir } from 'node:os'
|
|
3
|
-
import { join, isAbsolute, resolve } from 'node:path'
|
|
4
|
-
import { normalizeConfig } from './config.js'
|
|
5
|
-
import { CLI_ROUTES, createCliAdapter, enabledIn, selectCli } from './cli-adapter.js'
|
|
6
|
-
import { openCliSessions } from './cli-sessions.js'
|
|
7
|
-
|
|
8
|
-
const CLI_NAMES = Object.keys(CLI_ROUTES)
|
|
9
|
-
const cliName = (provider) => CLI_NAMES.includes(provider) ? provider : undefined
|
|
10
|
-
const internalRoute = (provider) => Object.values(CLI_ROUTES).includes(provider)
|
|
11
|
-
const success = (value) => ({ isError: false, value, content: [] })
|
|
12
|
-
|
|
13
|
-
export function cliCatalog(config, request = {}) {
|
|
14
|
-
const providers = CLI_NAMES.filter((cli) => normalizeConfig(config).subagents[cli].enabled)
|
|
15
|
-
if (request.provider === undefined) return providers.map((cli) => `${cli} — ${cli === 'codex' ? 'Codex' : 'Claude'} CLI (background continuable subagents)`).join('\n')
|
|
16
|
-
const cli = cliName(request.provider)
|
|
17
|
-
if (!cli) return ''
|
|
18
|
-
const backend = normalizeConfig(config).subagents[cli]
|
|
19
|
-
if (!backend.enabled) throw new Error(`Apollo ${cli} backend is disabled`)
|
|
20
|
-
if (request.model === undefined) return backend.models.map((model) => `${cli}/${model.id}${model.id === backend.defaultModel ? ' (default)' : ''}`).join('\n') || '(no allowed models)'
|
|
21
|
-
const model = backend.models.find((item) => item.id === request.model)
|
|
22
|
-
if (!model) throw new Error(`Apollo ${cli} model "${request.model}" is not allowed`)
|
|
23
|
-
return `${cli}/${model.id}\nReasoning efforts:\n${model.efforts.map((effort) => `${effort}${effort === model.defaultEffort ? ' (default)' : ''}`).join('\n')}`
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Resolve the child's initial absolute directory from the parent's effective one.
|
|
28
|
-
*
|
|
29
|
-
* The Session header keeps the original project directory, so it can be stale
|
|
30
|
-
* after a working-directory change; the working-directory service is the only
|
|
31
|
-
* authority for the parent's current directory. An absolute explicit `cwd`
|
|
32
|
-
* needs no such lookup, a relative one resolves against that current directory,
|
|
33
|
-
* and omission inherits it. Resolving here keeps the KV record and the
|
|
34
|
-
* manager's pinned initial directory identical.
|
|
35
|
-
* @param {object} ctx - runtime owning the working-directory service.
|
|
36
|
-
* @param {object} parent - calling Agent whose directory is inherited.
|
|
37
|
-
* @param {unknown} requested - explicit tool `cwd`, already schema-validated upstream.
|
|
38
|
-
* @param {AbortSignal} signal - cancellation for the directory inspection.
|
|
39
|
-
* @returns {Promise<string>} absolute child directory.
|
|
40
|
-
*/
|
|
41
|
-
async function childCwd(ctx, parent, requested, signal) {
|
|
42
|
-
if (requested !== undefined && typeof requested !== 'string') throw new Error('cwd must be a string')
|
|
43
|
-
signal.throwIfAborted()
|
|
44
|
-
if (requested !== undefined && isAbsolute(requested)) return requested
|
|
45
|
-
const current = await ctx.workingDirectory.ensure(parent, signal)
|
|
46
|
-
signal.throwIfAborted()
|
|
47
|
-
if (!current || !isAbsolute(current)) throw new Error('Apollo CLI subagent requires an absolute session working directory')
|
|
48
|
-
return requested === undefined ? current : resolve(current, requested)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export async function startCliSubagent(ctx, config, sessions, args, exec) {
|
|
52
|
-
const cli = cliName(args.provider)
|
|
53
|
-
if (!cli) throw new Error('Unknown Apollo CLI provider')
|
|
54
|
-
const parent = exec.agent
|
|
55
|
-
if (!parent) throw new Error('Apollo subagent requires a calling Agent')
|
|
56
|
-
if (!enabledIn(config, parent)) throw new Error('Apollo CLI subagents are disabled for this preset')
|
|
57
|
-
if (args.run_in_background !== undefined) throw new Error('run_in_background was removed for CLI subagents; they always run in the background and are continued with send_message')
|
|
58
|
-
if (args.agent_preset !== undefined) throw new Error('agent_preset is not supported for CLI subagents yet')
|
|
59
|
-
if (typeof args.prompt !== 'string' || typeof args.description !== 'string') throw new Error('subagent requires a prompt and description')
|
|
60
|
-
const selected = selectCli(config, cli, args.model, args.reasoning_effort)
|
|
61
|
-
const cwd = await childCwd(ctx, parent, args.cwd, exec.signal)
|
|
62
|
-
let executable
|
|
63
|
-
try {
|
|
64
|
-
const command = selected.executable.startsWith('~/') ? join(homedir(), selected.executable.slice(2)) : selected.executable
|
|
65
|
-
executable = await ctx.subprocess.resolveExecutable(command, undefined, exec.signal)
|
|
66
|
-
} catch (error) {
|
|
67
|
-
exec.signal.throwIfAborted()
|
|
68
|
-
throw new Error(`Apollo ${cli} executable is unavailable; set its absolute path in Plugins → Apollo`, { cause: error })
|
|
69
|
-
}
|
|
70
|
-
const id = randomUUID()
|
|
71
|
-
const record = {
|
|
72
|
-
version: 1, id, parentId: parent.id, cli, cwd,
|
|
73
|
-
executable, profile: cli === 'codex' ? selected.profile : '',
|
|
74
|
-
cliHome: cli === 'codex' ? process.env.CODEX_HOME ?? join(homedir(), '.codex')
|
|
75
|
-
: process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), '.claude'),
|
|
76
|
-
// Explicit CLAUDE_CONFIG_DIR changes config and keychain lookup, even for ~/.claude.
|
|
77
|
-
...(cli === 'claude' && process.env.CLAUDE_CONFIG_DIR === undefined ? { claudeDefaultHome: true } : {}),
|
|
78
|
-
model: selected.model, effort: selected.effort, delivered: [], started: false,
|
|
79
|
-
}
|
|
80
|
-
await sessions.put(record)
|
|
81
|
-
try {
|
|
82
|
-
exec.signal.throwIfAborted()
|
|
83
|
-
const maxDepth = ctx.subagents.resolveMaxDepth()
|
|
84
|
-
const created = await ctx.subagents.startActivation({
|
|
85
|
-
provider: 'spawn', label: args.description, childId: id, signal: exec.signal,
|
|
86
|
-
delivery: 'parent',
|
|
87
|
-
request: {
|
|
88
|
-
parent, prompt: [{ type: 'text', text: args.prompt }], cwd,
|
|
89
|
-
agentOptions: { provider: CLI_ROUTES[cli], model: selected.model, reasoningEffort: selected.effort },
|
|
90
|
-
...(maxDepth === undefined ? {} : { maxDepth }),
|
|
91
|
-
},
|
|
92
|
-
})
|
|
93
|
-
exec.signal.throwIfAborted()
|
|
94
|
-
return { kind: 'activation', subagentId: created.childId }
|
|
95
|
-
} catch (error) {
|
|
96
|
-
// The manager rolls back pre-admission failure; drain also covers the final
|
|
97
|
-
// acceptance/cancellation race before this tool could hand the id back.
|
|
98
|
-
await ctx.subagents.drainChildren(parent, [id])
|
|
99
|
-
await sessions.delete(id)
|
|
100
|
-
throw error
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/** Host-level interception extends existing tools without replacing registrations. */
|
|
105
|
-
export function installCliRouting(ctx, config, sessions) {
|
|
106
|
-
const unpublished = new Map()
|
|
107
|
-
const cleanups = new Set()
|
|
108
|
-
const creating = new Set()
|
|
109
|
-
const lifetime = new AbortController()
|
|
110
|
-
const reclaim = (entry) => {
|
|
111
|
-
if (entry.cleanup) return entry.cleanup
|
|
112
|
-
const task = (async () => {
|
|
113
|
-
await ctx.subagents.drainChildren(entry.parent, [entry.id])
|
|
114
|
-
await sessions.delete(entry.id)
|
|
115
|
-
})()
|
|
116
|
-
entry.cleanup = task
|
|
117
|
-
cleanups.add(task)
|
|
118
|
-
task.catch((error) => ctx.logger?.warn?.(`Apollo could not reclaim unreported CLI child ${entry.id}: ${error.message}`))
|
|
119
|
-
.finally(() => cleanups.delete(task))
|
|
120
|
-
return task
|
|
121
|
-
}
|
|
122
|
-
ctx.on('tools/result', (exec, result) => {
|
|
123
|
-
const key = exec.token ?? exec
|
|
124
|
-
const entry = unpublished.get(key)
|
|
125
|
-
if (!entry) return
|
|
126
|
-
unpublished.delete(key)
|
|
127
|
-
if (!result.isError && result.value?.kind === 'activation' && result.value.subagentId === entry.id) return
|
|
128
|
-
void reclaim(entry)
|
|
129
|
-
})
|
|
130
|
-
ctx.tools.guard((exec) => {
|
|
131
|
-
const args = exec.arguments ?? {}
|
|
132
|
-
if (!['subagent', 'subagent_fork'].includes(exec.name)) return
|
|
133
|
-
if (internalRoute(args.provider)) return 'Use provider="codex" or provider="claude" with subagent; internal CLI routes are reserved'
|
|
134
|
-
const selected = cliName(args.provider)
|
|
135
|
-
if (selected && !enabledIn(config, exec.agent)) return 'Apollo CLI subagents are disabled for this preset'
|
|
136
|
-
if (exec.name === 'subagent_fork' && (selected || (args.provider === undefined && internalRoute(exec.agent?.options.provider)))) {
|
|
137
|
-
return 'Codex/Claude support background subagent creation only; subagent_fork cannot create a CLI child'
|
|
138
|
-
}
|
|
139
|
-
})
|
|
140
|
-
ctx.on('tools/execute', async (exec, next) => {
|
|
141
|
-
const args = exec.arguments ?? {}
|
|
142
|
-
if (!enabledIn(config, exec.agent)) return next()
|
|
143
|
-
if (exec.name === 'subagent' && cliName(args.provider)) {
|
|
144
|
-
const signal = AbortSignal.any([exec.signal, lifetime.signal])
|
|
145
|
-
const creation = (async () => {
|
|
146
|
-
const value = await startCliSubagent(ctx, config, sessions, args, { ...exec, signal })
|
|
147
|
-
unpublished.set(exec.token ?? exec, { parent: exec.agent, id: value.subagentId })
|
|
148
|
-
return success(value)
|
|
149
|
-
})()
|
|
150
|
-
creating.add(creation)
|
|
151
|
-
try { return await creation } finally { creating.delete(creation) }
|
|
152
|
-
}
|
|
153
|
-
if (exec.name !== 'list_subagent_models') return next()
|
|
154
|
-
if (cliName(args.provider)) return success(cliCatalog(config, args))
|
|
155
|
-
if (args.provider !== undefined || args.model !== undefined) return next()
|
|
156
|
-
const base = await next()
|
|
157
|
-
const extra = cliCatalog(config)
|
|
158
|
-
if (base.isError || !extra) return base
|
|
159
|
-
return success([base.value, extra].filter(Boolean).join('\n'))
|
|
160
|
-
})
|
|
161
|
-
ctx.systemPrompt.section({
|
|
162
|
-
name: 'apollo:cli-subagents', order: 80,
|
|
163
|
-
text: (context) => {
|
|
164
|
-
const agent = context.agent ?? ctx.agents.get(context.session?.id ?? context.scope?.id)
|
|
165
|
-
if (!enabledIn(config, agent)) return ''
|
|
166
|
-
const catalog = cliCatalog(config)
|
|
167
|
-
if (!catalog) return ''
|
|
168
|
-
return [
|
|
169
|
-
'Apollo CLI subagents: subagent also accepts provider="codex" or provider="claude"; model and reasoning_effort default to Apollo settings (list_subagent_models with that provider shows allowed values).',
|
|
170
|
-
'They always run in the background, start in cwd (default: current directory) with permissions from Apollo settings; subagent_fork and agent_preset are unsupported.',
|
|
171
|
-
'Control them by child id with send_message, wait_subagent, list_agents and interrupt_agent.',
|
|
172
|
-
catalog,
|
|
173
|
-
].join('\n')
|
|
174
|
-
},
|
|
175
|
-
})
|
|
176
|
-
return async () => {
|
|
177
|
-
lifetime.abort(new Error('Apollo CLI routing unloaded'))
|
|
178
|
-
await Promise.allSettled([...creating])
|
|
179
|
-
await Promise.allSettled([...unpublished.values()].map(reclaim))
|
|
180
|
-
unpublished.clear()
|
|
181
|
-
await Promise.allSettled([...cleanups])
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
export function mountSubagents(ctx, config) {
|
|
186
|
-
ctx.inject(['tools', 'subagents', 'agents', 'llm', 'storage', 'subprocess', 'systemPrompt', 'workingDirectory'], async (runtime) => {
|
|
187
|
-
const sessions = await openCliSessions(runtime)
|
|
188
|
-
const adapters = CLI_NAMES.map((cli) => createCliAdapter(runtime, config, sessions, cli))
|
|
189
|
-
let stopRouting = async () => {}
|
|
190
|
-
runtime.effect(() => async () => {
|
|
191
|
-
await stopRouting()
|
|
192
|
-
await Promise.all(adapters.map((adapter) => adapter.dispose()))
|
|
193
|
-
await sessions.close()
|
|
194
|
-
})
|
|
195
|
-
adapters.forEach((adapter, index) => runtime.llm.registerAdapter([CLI_ROUTES[CLI_NAMES[index]]], adapter))
|
|
196
|
-
stopRouting = installCliRouting(runtime, config, sessions)
|
|
197
|
-
})
|
|
198
|
-
}
|