@yemi33/minions 0.1.2382 → 0.1.2384
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/minions.js +1 -0
- package/dashboard/js/refresh.js +2 -1
- package/dashboard/js/settings.js +1 -1
- package/dashboard.js +37 -19
- package/docs/completion-reports.md +20 -1
- package/docs/keep-processes.md +7 -2
- package/docs/live-checkout-mode.md +7 -7
- package/docs/managed-spawn.md +14 -1
- package/docs/runtime-adapters.md +61 -26
- package/docs/skills.md +2 -0
- package/docs/workspace-manifests.md +1 -1
- package/docs/worktree-lifecycle.md +36 -0
- package/engine/acp-transport.js +273 -58
- package/engine/ado-comment.js +3 -1
- package/engine/agent-worker-pool.js +278 -54
- package/engine/cc-worker-pool.js +12 -3
- package/engine/cleanup.js +15 -11
- package/engine/cli.js +56 -28
- package/engine/comment-format.js +51 -14
- package/engine/create-pr-worktree.js +57 -79
- package/engine/dispatch.js +7 -2
- package/engine/execution-model.js +68 -0
- package/engine/gh-comment.js +6 -3
- package/engine/github.js +6 -7
- package/engine/keep-process-sweep.js +131 -9
- package/engine/lifecycle.js +126 -45
- package/engine/live-checkout.js +4 -2
- package/engine/llm.js +59 -83
- package/engine/managed-spawn.js +112 -5
- package/engine/memory-store.js +3 -1
- package/engine/playbook.js +4 -6
- package/engine/pooled-agent-process.js +512 -45
- package/engine/pr-action.js +6 -8
- package/engine/process-utils.js +154 -18
- package/engine/runtimes/claude.js +94 -25
- package/engine/runtimes/codex.js +1 -0
- package/engine/runtimes/contract.js +239 -0
- package/engine/runtimes/copilot.js +97 -9
- package/engine/runtimes/index.js +37 -9
- package/engine/shared.js +349 -82
- package/engine/spawn-agent.js +85 -116
- package/engine/spawn-phase-watchdog.js +8 -37
- package/engine/timeout.js +14 -10
- package/engine/worktree-gc.js +55 -46
- package/engine.js +418 -241
- package/package.json +1 -1
- package/playbooks/fix.md +20 -0
- package/playbooks/review.md +2 -0
- package/playbooks/shared-rules.md +2 -2
- package/prompts/cc-system.md +11 -11
- package/skills/check-self-authored-review-comment/SKILL.md +34 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const ADAPTER_API_VERSION = 1;
|
|
4
|
+
const RUNTIME_OPTIONS_FLAG = '--runtime-options';
|
|
5
|
+
const RUNTIME_IMAGES_FILE_OPTION = '_minionsImagesFile';
|
|
6
|
+
const legacyAdapters = new WeakSet();
|
|
7
|
+
|
|
8
|
+
const CORE_METHODS = Object.freeze([
|
|
9
|
+
'resolveBinary',
|
|
10
|
+
'listModels',
|
|
11
|
+
'buildArgs',
|
|
12
|
+
'buildPrompt',
|
|
13
|
+
'resolveModel',
|
|
14
|
+
'parseOutput',
|
|
15
|
+
'parseStreamChunk',
|
|
16
|
+
'parseError',
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
function normalizeRuntimeAdapter(name, adapter, { strict = false } = {}) {
|
|
20
|
+
if (!name || typeof name !== 'string') {
|
|
21
|
+
throw new Error('registerRuntime: name must be a non-empty string');
|
|
22
|
+
}
|
|
23
|
+
if (!adapter || typeof adapter !== 'object' || Array.isArray(adapter)) {
|
|
24
|
+
throw new Error('registerRuntime: adapter must be an object');
|
|
25
|
+
}
|
|
26
|
+
if (adapter.name != null && adapter.name !== name) {
|
|
27
|
+
throw new Error(`registerRuntime: adapter name "${adapter.name}" does not match registry key "${name}"`);
|
|
28
|
+
}
|
|
29
|
+
const hasExplicitApiVersion = adapter.apiVersion != null;
|
|
30
|
+
if (strict && !hasExplicitApiVersion) {
|
|
31
|
+
throw new Error(`Runtime "${name}" adapter must declare apiVersion ${ADAPTER_API_VERSION}`);
|
|
32
|
+
}
|
|
33
|
+
if (hasExplicitApiVersion && adapter.apiVersion !== ADAPTER_API_VERSION) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`Runtime "${name}" adapter API version ${adapter.apiVersion} is unsupported; expected ${ADAPTER_API_VERSION}`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
adapter.name = name;
|
|
40
|
+
adapter.apiVersion = ADAPTER_API_VERSION;
|
|
41
|
+
if (!hasExplicitApiVersion) legacyAdapters.add(adapter);
|
|
42
|
+
if (adapter.capabilities == null) {
|
|
43
|
+
adapter.capabilities = {};
|
|
44
|
+
} else if (typeof adapter.capabilities !== 'object' || Array.isArray(adapter.capabilities)) {
|
|
45
|
+
throw new Error(`Runtime "${name}" adapter member capabilities must be an object`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const method of CORE_METHODS) {
|
|
49
|
+
if (adapter[method] != null && typeof adapter[method] !== 'function') {
|
|
50
|
+
throw new Error(`Runtime "${name}" adapter member ${method} must be a function`);
|
|
51
|
+
}
|
|
52
|
+
if (strict && typeof adapter[method] !== 'function') {
|
|
53
|
+
throw new Error(`Runtime "${name}" adapter is missing required method ${method}()`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (adapter.capabilities.streamConsumer === true && typeof adapter.createStreamConsumer !== 'function') {
|
|
58
|
+
throw new Error(`Runtime "${name}" declares streamConsumer but does not implement createStreamConsumer()`);
|
|
59
|
+
}
|
|
60
|
+
if (adapter.capabilities.acpWorkerPool === true) {
|
|
61
|
+
for (const method of ['buildWorkerArgs', 'encodePooledOutput']) {
|
|
62
|
+
if (typeof adapter[method] !== 'function') {
|
|
63
|
+
throw new Error(`Runtime "${name}" declares acpWorkerPool but does not implement ${method}()`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return adapter;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function sanitizeInvocationOptions(runtime, options = {}) {
|
|
71
|
+
const caps = runtime?.capabilities || {};
|
|
72
|
+
const sanitized = { ...(options || {}) };
|
|
73
|
+
if (!caps.effortLevels) delete sanitized.effort;
|
|
74
|
+
if (!caps.sessionResume
|
|
75
|
+
|| typeof sanitized.sessionId !== 'string'
|
|
76
|
+
|| !sanitized.sessionId.trim()) {
|
|
77
|
+
delete sanitized.sessionId;
|
|
78
|
+
} else {
|
|
79
|
+
sanitized.sessionId = sanitized.sessionId.trim();
|
|
80
|
+
}
|
|
81
|
+
if (!caps.budgetCap) delete sanitized.maxBudget;
|
|
82
|
+
if (!caps.bareMode) delete sanitized.bare;
|
|
83
|
+
if (!caps.fallbackModel) delete sanitized.fallbackModel;
|
|
84
|
+
if (!caps.imageInput) {
|
|
85
|
+
delete sanitized.images;
|
|
86
|
+
delete sanitized[RUNTIME_IMAGES_FILE_OPTION];
|
|
87
|
+
}
|
|
88
|
+
return sanitized;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function resolveInvocationOptions(runtime, options = {}, context = {}) {
|
|
92
|
+
if (!runtime || typeof runtime !== 'object') throw new Error('resolveInvocationOptions: runtime adapter is required');
|
|
93
|
+
let resolved = { ...(options || {}) };
|
|
94
|
+
if (typeof runtime.resolveInvocationOptions === 'function') {
|
|
95
|
+
resolved = runtime.resolveInvocationOptions({
|
|
96
|
+
...context,
|
|
97
|
+
options: resolved,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (!resolved || typeof resolved !== 'object' || Array.isArray(resolved)) {
|
|
101
|
+
throw new Error(`Runtime "${runtime.name}" resolveInvocationOptions() must return an object`);
|
|
102
|
+
}
|
|
103
|
+
return sanitizeInvocationOptions(runtime, resolved);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function encodeRuntimeOptions(options = {}) {
|
|
107
|
+
return Buffer.from(JSON.stringify(options || {}), 'utf8').toString('base64url');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function decodeRuntimeOptions(encoded) {
|
|
111
|
+
if (typeof encoded !== 'string' || !encoded) {
|
|
112
|
+
throw new Error(`${RUNTIME_OPTIONS_FLAG} requires a non-empty base64url value`);
|
|
113
|
+
}
|
|
114
|
+
let decoded;
|
|
115
|
+
try {
|
|
116
|
+
decoded = JSON.parse(Buffer.from(encoded, 'base64url').toString('utf8'));
|
|
117
|
+
} catch (err) {
|
|
118
|
+
throw new Error(`Invalid ${RUNTIME_OPTIONS_FLAG} payload: ${err.message}`);
|
|
119
|
+
}
|
|
120
|
+
if (!decoded || typeof decoded !== 'object' || Array.isArray(decoded)) {
|
|
121
|
+
throw new Error(`Invalid ${RUNTIME_OPTIONS_FLAG} payload: expected a JSON object`);
|
|
122
|
+
}
|
|
123
|
+
for (const key of ['__proto__', 'prototype', 'constructor']) {
|
|
124
|
+
if (Object.prototype.hasOwnProperty.call(decoded, key)) {
|
|
125
|
+
throw new Error(`Invalid ${RUNTIME_OPTIONS_FLAG} payload: forbidden key "${key}"`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return decoded;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function buildSpawnFlags(runtime, options = {}) {
|
|
132
|
+
if (!runtime || typeof runtime !== 'object') throw new Error('buildSpawnFlags: runtime adapter is required');
|
|
133
|
+
const isLegacy = runtime.apiVersion !== ADAPTER_API_VERSION || legacyAdapters.has(runtime);
|
|
134
|
+
if (isLegacy && typeof runtime.buildSpawnFlags === 'function') {
|
|
135
|
+
const legacyFlags = runtime.buildSpawnFlags(options || {});
|
|
136
|
+
if (!Array.isArray(legacyFlags) || legacyFlags.some(flag => typeof flag !== 'string')) {
|
|
137
|
+
throw new Error(`Runtime "${runtime.name}" buildSpawnFlags() must return string[]`);
|
|
138
|
+
}
|
|
139
|
+
return legacyFlags;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const sanitized = sanitizeInvocationOptions(runtime, options);
|
|
143
|
+
let flags;
|
|
144
|
+
if (typeof runtime.buildSpawnFlags === 'function') {
|
|
145
|
+
flags = runtime.buildSpawnFlags(sanitized);
|
|
146
|
+
} else {
|
|
147
|
+
const caps = runtime.capabilities || {};
|
|
148
|
+
flags = ['--runtime', String(runtime.name)];
|
|
149
|
+
if (sanitized.maxTurns != null) flags.push('--max-turns', String(sanitized.maxTurns));
|
|
150
|
+
if (sanitized.model) flags.push('--model', String(sanitized.model));
|
|
151
|
+
if (sanitized.allowedTools) flags.push('--allowedTools', String(sanitized.allowedTools));
|
|
152
|
+
if (caps.effortLevels && sanitized.effort) flags.push('--effort', String(sanitized.effort));
|
|
153
|
+
if (caps.sessionResume && sanitized.sessionId) flags.push('--resume', String(sanitized.sessionId));
|
|
154
|
+
if (caps.budgetCap && sanitized.maxBudget != null) flags.push('--max-budget-usd', String(sanitized.maxBudget));
|
|
155
|
+
if (caps.bareMode && sanitized.bare === true) flags.push('--bare');
|
|
156
|
+
if (caps.fallbackModel && sanitized.fallbackModel) {
|
|
157
|
+
flags.push('--fallback-model', String(sanitized.fallbackModel));
|
|
158
|
+
}
|
|
159
|
+
if (sanitized.stream === 'on' || sanitized.stream === 'off') {
|
|
160
|
+
flags.push('--stream', sanitized.stream);
|
|
161
|
+
}
|
|
162
|
+
if (sanitized.disableBuiltinMcps === true) flags.push('--disable-builtin-mcps');
|
|
163
|
+
if (sanitized.reasoningSummaries === true) flags.push('--enable-reasoning-summaries');
|
|
164
|
+
}
|
|
165
|
+
if (!Array.isArray(flags) || flags.some(flag => typeof flag !== 'string')) {
|
|
166
|
+
throw new Error(`Runtime "${runtime.name}" buildSpawnFlags() must return string[]`);
|
|
167
|
+
}
|
|
168
|
+
if (flags[0] !== '--runtime') flags = ['--runtime', String(runtime.name), ...flags];
|
|
169
|
+
if (flags[1] !== runtime.name) {
|
|
170
|
+
throw new Error(`Runtime "${runtime.name}" buildSpawnFlags() emitted mismatched runtime "${flags[1]}"`);
|
|
171
|
+
}
|
|
172
|
+
if (isLegacy) return flags;
|
|
173
|
+
return [...flags, RUNTIME_OPTIONS_FLAG, encodeRuntimeOptions(sanitized)];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function prepareWorkspace(runtime, context = {}) {
|
|
177
|
+
if (!runtime || typeof runtime.prepareWorkspace !== 'function') {
|
|
178
|
+
return { wrote: false, reason: 'unsupported-runtime', servers: [] };
|
|
179
|
+
}
|
|
180
|
+
return runtime.prepareWorkspace(context);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function isSpawnStartupEvent(runtime, event) {
|
|
184
|
+
if (!runtime || typeof runtime.isSpawnStartupEvent !== 'function') return false;
|
|
185
|
+
try {
|
|
186
|
+
return runtime.isSpawnStartupEvent(event) === true;
|
|
187
|
+
} catch {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function shouldSuppressPostMutationError(runtime, context = {}) {
|
|
193
|
+
if (!runtime || typeof runtime.shouldSuppressPostMutationError !== 'function') return false;
|
|
194
|
+
try {
|
|
195
|
+
return runtime.shouldSuppressPostMutationError(context) === true;
|
|
196
|
+
} catch {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function buildWorkerArgs(runtime, options = {}) {
|
|
202
|
+
if (runtime?.capabilities?.acpWorkerPool !== true) {
|
|
203
|
+
throw new Error(`Runtime "${runtime?.name || '<unknown>'}" does not support persistent ACP workers`);
|
|
204
|
+
}
|
|
205
|
+
const args = runtime.buildWorkerArgs(options);
|
|
206
|
+
if (!Array.isArray(args) || args.some(arg => typeof arg !== 'string')) {
|
|
207
|
+
throw new Error(`Runtime "${runtime.name}" buildWorkerArgs() must return string[]`);
|
|
208
|
+
}
|
|
209
|
+
return args;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function encodePooledOutput(runtime, event) {
|
|
213
|
+
if (runtime?.capabilities?.acpWorkerPool !== true) {
|
|
214
|
+
throw new Error(`Runtime "${runtime?.name || '<unknown>'}" does not support pooled output`);
|
|
215
|
+
}
|
|
216
|
+
const encoded = runtime.encodePooledOutput(event);
|
|
217
|
+
if (typeof encoded !== 'string') {
|
|
218
|
+
throw new Error(`Runtime "${runtime.name}" encodePooledOutput() must return a string`);
|
|
219
|
+
}
|
|
220
|
+
return encoded;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
module.exports = {
|
|
224
|
+
ADAPTER_API_VERSION,
|
|
225
|
+
RUNTIME_OPTIONS_FLAG,
|
|
226
|
+
RUNTIME_IMAGES_FILE_OPTION,
|
|
227
|
+
CORE_METHODS,
|
|
228
|
+
normalizeRuntimeAdapter,
|
|
229
|
+
sanitizeInvocationOptions,
|
|
230
|
+
resolveInvocationOptions,
|
|
231
|
+
encodeRuntimeOptions,
|
|
232
|
+
decodeRuntimeOptions,
|
|
233
|
+
buildSpawnFlags,
|
|
234
|
+
prepareWorkspace,
|
|
235
|
+
isSpawnStartupEvent,
|
|
236
|
+
shouldSuppressPostMutationError,
|
|
237
|
+
buildWorkerArgs,
|
|
238
|
+
encodePooledOutput,
|
|
239
|
+
};
|
|
@@ -32,11 +32,19 @@ const https = require('https');
|
|
|
32
32
|
const os = require('os');
|
|
33
33
|
const path = require('path');
|
|
34
34
|
const { execSync } = require('child_process');
|
|
35
|
-
const { FAILURE_CLASS, safeWrite, ts, resolveEngineCacheDir } = require('../shared');
|
|
35
|
+
const { ENGINE_DEFAULTS, FAILURE_CLASS, safeWrite, ts, resolveEngineCacheDir } = require('../shared');
|
|
36
36
|
|
|
37
37
|
const ENGINE_DIR = __dirname.replace(/[\\/]runtimes$/, '');
|
|
38
38
|
const _CACHE_DIR = resolveEngineCacheDir(ENGINE_DIR);
|
|
39
39
|
const isWin = process.platform === 'win32';
|
|
40
|
+
const RUNTIME_NAME = 'copilot';
|
|
41
|
+
const SPAWN_STARTUP_TYPES = new Set([
|
|
42
|
+
'session.mcp_server_status_changed',
|
|
43
|
+
'session.mcp_servers_loaded',
|
|
44
|
+
'session.skills_loaded',
|
|
45
|
+
'session.tools_updated',
|
|
46
|
+
'session.info',
|
|
47
|
+
]);
|
|
40
48
|
|
|
41
49
|
// ── Binary Resolution ───────────────────────────────────────────────────────
|
|
42
50
|
//
|
|
@@ -529,7 +537,7 @@ function buildArgs(opts = {}) {
|
|
|
529
537
|
}
|
|
530
538
|
|
|
531
539
|
function buildSpawnFlags(opts = {}) {
|
|
532
|
-
const flags = ['--runtime',
|
|
540
|
+
const flags = ['--runtime', RUNTIME_NAME];
|
|
533
541
|
if (opts.maxTurns != null) flags.push('--max-turns', String(opts.maxTurns));
|
|
534
542
|
if (opts.model) flags.push('--model', String(opts.model));
|
|
535
543
|
if (opts.allowedTools) flags.push('--allowedTools', String(opts.allowedTools));
|
|
@@ -552,12 +560,84 @@ function buildSpawnFlags(opts = {}) {
|
|
|
552
560
|
return flags;
|
|
553
561
|
}
|
|
554
562
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
const
|
|
563
|
+
function resolveInvocationOptions({
|
|
564
|
+
options = {},
|
|
565
|
+
engineConfig = {},
|
|
566
|
+
failureClass = null,
|
|
567
|
+
} = {}) {
|
|
568
|
+
const resolved = { ...options };
|
|
569
|
+
if (resolved.stream == null) resolved.stream = engineConfig.copilotStreamMode;
|
|
570
|
+
if (resolved.disableBuiltinMcps == null) {
|
|
571
|
+
resolved.disableBuiltinMcps = engineConfig.copilotDisableBuiltinMcps
|
|
572
|
+
?? ENGINE_DEFAULTS.copilotDisableBuiltinMcps;
|
|
573
|
+
}
|
|
574
|
+
if (resolved.reasoningSummaries == null) {
|
|
575
|
+
resolved.reasoningSummaries = engineConfig.copilotReasoningSummaries;
|
|
576
|
+
}
|
|
577
|
+
if (failureClass === FAILURE_CLASS.MODEL_UNAVAILABLE && engineConfig.copilotFallbackModel) {
|
|
578
|
+
resolved.model = engineConfig.copilotFallbackModel;
|
|
579
|
+
}
|
|
580
|
+
if (Array.isArray(resolved.disabledMcpServers)) {
|
|
581
|
+
resolved.disabledMcpServers = [...new Set(
|
|
582
|
+
resolved.disabledMcpServers.filter(Boolean).map(String)
|
|
583
|
+
)].sort();
|
|
584
|
+
}
|
|
585
|
+
return resolved;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function isSpawnStartupEvent(event) {
|
|
589
|
+
return !!event && SPAWN_STARTUP_TYPES.has(event.type);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function shouldSuppressPostMutationError({ mutationApplied, error } = {}) {
|
|
593
|
+
if (mutationApplied !== true || !error) return false;
|
|
594
|
+
return error.errorClass === 'unknown-model' || error.errorClass === 'model-unavailable';
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function buildWorkerArgs(options = {}) {
|
|
598
|
+
const maxTurns = Number(options.maxTurns);
|
|
599
|
+
const args = [
|
|
600
|
+
'--acp',
|
|
601
|
+
'--allow-all',
|
|
602
|
+
'--max-autopilot-continues',
|
|
603
|
+
String(Number.isFinite(maxTurns) && maxTurns > 0 ? Math.floor(maxTurns) : 1),
|
|
604
|
+
];
|
|
605
|
+
if (options.disableBuiltinMcps === true) args.push('--disable-builtin-mcps');
|
|
606
|
+
if (options.reasoningSummaries === true) args.push('--enable-reasoning-summaries');
|
|
607
|
+
for (const name of options.disabledMcpServers || []) {
|
|
608
|
+
if (name) args.push('--disable-mcp-server', String(name));
|
|
609
|
+
}
|
|
610
|
+
return args;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function encodePooledOutput(event = {}) {
|
|
614
|
+
if (event.kind === 'model') {
|
|
615
|
+
return JSON.stringify({
|
|
616
|
+
type: 'session.tools_updated',
|
|
617
|
+
data: { model: event.model || null },
|
|
618
|
+
}) + '\n';
|
|
619
|
+
}
|
|
620
|
+
if (event.kind === 'chunk') {
|
|
621
|
+
return JSON.stringify({
|
|
622
|
+
type: 'assistant.message_delta',
|
|
623
|
+
data: { deltaContent: event.text || '' },
|
|
624
|
+
}) + '\n';
|
|
625
|
+
}
|
|
626
|
+
if (event.kind === 'result') {
|
|
627
|
+
return JSON.stringify({
|
|
628
|
+
type: 'result',
|
|
629
|
+
sessionId: event.sessionId || null,
|
|
630
|
+
usage: {
|
|
631
|
+
totalApiDurationMs: event.durationMs,
|
|
632
|
+
stopReason: event.stopReason || null,
|
|
633
|
+
},
|
|
634
|
+
}) + '\n';
|
|
635
|
+
}
|
|
636
|
+
throw new Error(`Unsupported pooled output event kind: ${event.kind || '<missing>'}`);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// RUNTIME_NAME is stamped into every session.json so the pre-spawn resume path
|
|
640
|
+
// can reject session IDs produced by another adapter before invoking the CLI.
|
|
561
641
|
|
|
562
642
|
function getResumeSessionId({ agentId, branchName, agentsDir, maxAgeMs = 2 * 60 * 60 * 1000, logger = console } = {}) {
|
|
563
643
|
if (!agentId || agentId.startsWith('temp-') || !agentsDir) return null;
|
|
@@ -1370,7 +1450,8 @@ function getMcpConfigPaths({ homeDir = os.homedir(), project = null } = {}) {
|
|
|
1370
1450
|
}
|
|
1371
1451
|
|
|
1372
1452
|
module.exports = {
|
|
1373
|
-
|
|
1453
|
+
apiVersion: 1,
|
|
1454
|
+
name: RUNTIME_NAME,
|
|
1374
1455
|
capabilities,
|
|
1375
1456
|
resolveBinary,
|
|
1376
1457
|
capsFile: CAPS_FILE,
|
|
@@ -1379,7 +1460,11 @@ module.exports = {
|
|
|
1379
1460
|
// Use the same wrapper as Claude — spawn-agent.js is runtime-agnostic per P-9c4f2d6a
|
|
1380
1461
|
spawnScript: path.join(ENGINE_DIR, 'spawn-agent.js'),
|
|
1381
1462
|
installHint: INSTALL_HINT,
|
|
1463
|
+
workerErrorHint: 'copilot --acp failed -- ensure copilot CLI >=1.0.46 and copilot login is complete',
|
|
1464
|
+
resolveInvocationOptions,
|
|
1382
1465
|
buildSpawnFlags,
|
|
1466
|
+
buildWorkerArgs,
|
|
1467
|
+
encodePooledOutput,
|
|
1383
1468
|
buildArgs,
|
|
1384
1469
|
buildPrompt,
|
|
1385
1470
|
getUserAssetDirs,
|
|
@@ -1399,6 +1484,8 @@ module.exports = {
|
|
|
1399
1484
|
parseStreamChunk,
|
|
1400
1485
|
parseError,
|
|
1401
1486
|
createStreamConsumer,
|
|
1487
|
+
isSpawnStartupEvent,
|
|
1488
|
+
shouldSuppressPostMutationError,
|
|
1402
1489
|
permissionBypassFlags: PERMISSION_BYPASS_FLAGS,
|
|
1403
1490
|
// Exposed for unit tests — engine code MUST go through resolveRuntime + the
|
|
1404
1491
|
// adapter contract; never reach into these helpers directly.
|
|
@@ -1419,4 +1506,5 @@ module.exports = {
|
|
|
1419
1506
|
_extractInvalidModelName,
|
|
1420
1507
|
CAPS_SCHEMA_VERSION,
|
|
1421
1508
|
KNOWN_EVENT_TYPES,
|
|
1509
|
+
SPAWN_STARTUP_TYPES,
|
|
1422
1510
|
};
|
package/engine/runtimes/index.js
CHANGED
|
@@ -7,18 +7,16 @@
|
|
|
7
7
|
* runtime name fails loudly with a clear list of registered options.
|
|
8
8
|
*
|
|
9
9
|
* Adding a new runtime:
|
|
10
|
-
* 1. Implement
|
|
11
|
-
* 2. `
|
|
10
|
+
* 1. Implement API version 1 from contract.js.
|
|
11
|
+
* 2. `registerRuntime('<name>', require('./<name>'), { strict: true });`.
|
|
12
12
|
* 3. Expose its capabilities via the `/api/runtimes` endpoint (free).
|
|
13
13
|
*
|
|
14
14
|
* Engine code MUST gate behavior on `runtime.capabilities.*` flags, not on
|
|
15
15
|
* `runtime.name === 'claude'` comparisons. The whole point of this layer.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
const contract = require('./contract');
|
|
18
19
|
const registry = new Map();
|
|
19
|
-
registry.set('claude', require('./claude'));
|
|
20
|
-
registry.set('codex', require('./codex'));
|
|
21
|
-
registry.set('copilot', require('./copilot'));
|
|
22
20
|
|
|
23
21
|
/**
|
|
24
22
|
* Look up a runtime adapter by name. Throws when the name is unknown so
|
|
@@ -47,16 +45,46 @@ function listRuntimes() {
|
|
|
47
45
|
* Register a runtime adapter. Exposed for tests and for downstream tooling
|
|
48
46
|
* that wants to register a custom runtime without editing this file.
|
|
49
47
|
*/
|
|
50
|
-
function registerRuntime(name, adapter) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
function registerRuntime(name, adapter, options) {
|
|
49
|
+
const normalized = contract.normalizeRuntimeAdapter(name, adapter, options);
|
|
50
|
+
registry.set(name, normalized);
|
|
51
|
+
return normalized;
|
|
54
52
|
}
|
|
55
53
|
|
|
54
|
+
function resolveRuntimeByCapability(capability) {
|
|
55
|
+
const matches = listRuntimes()
|
|
56
|
+
.map(name => registry.get(name))
|
|
57
|
+
.filter(adapter => adapter?.capabilities?.[capability] === true);
|
|
58
|
+
if (matches.length === 0) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`No registered runtime declares capability "${capability}"`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
return matches[0];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
registerRuntime('claude', require('./claude'), { strict: true });
|
|
67
|
+
registerRuntime('codex', require('./codex'), { strict: true });
|
|
68
|
+
registerRuntime('copilot', require('./copilot'), { strict: true });
|
|
69
|
+
|
|
56
70
|
module.exports = {
|
|
71
|
+
ADAPTER_API_VERSION: contract.ADAPTER_API_VERSION,
|
|
72
|
+
RUNTIME_OPTIONS_FLAG: contract.RUNTIME_OPTIONS_FLAG,
|
|
73
|
+
RUNTIME_IMAGES_FILE_OPTION: contract.RUNTIME_IMAGES_FILE_OPTION,
|
|
57
74
|
resolveRuntime,
|
|
75
|
+
resolveRuntimeByCapability,
|
|
58
76
|
listRuntimes,
|
|
59
77
|
registerRuntime,
|
|
78
|
+
sanitizeInvocationOptions: contract.sanitizeInvocationOptions,
|
|
79
|
+
resolveInvocationOptions: contract.resolveInvocationOptions,
|
|
80
|
+
decodeRuntimeOptions: contract.decodeRuntimeOptions,
|
|
81
|
+
buildSpawnFlags: contract.buildSpawnFlags,
|
|
82
|
+
prepareWorkspace: contract.prepareWorkspace,
|
|
83
|
+
isSpawnStartupEvent: contract.isSpawnStartupEvent,
|
|
84
|
+
shouldSuppressPostMutationError: contract.shouldSuppressPostMutationError,
|
|
85
|
+
buildWorkerArgs: contract.buildWorkerArgs,
|
|
86
|
+
encodePooledOutput: contract.encodePooledOutput,
|
|
87
|
+
validateRuntimeAdapter: contract.normalizeRuntimeAdapter,
|
|
60
88
|
// Exposed for tests — engine code MUST go through resolveRuntime/listRuntimes
|
|
61
89
|
_registry: registry,
|
|
62
90
|
};
|