@myagentroam/node 0.9.0 → 0.9.3
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/dist/connector.d.ts +2 -0
- package/dist/connector.js +50 -25
- package/dist/control-outbound-scheduler.d.ts +4 -1
- package/dist/control-outbound-scheduler.js +39 -6
- package/dist/database.d.ts +0 -19
- package/dist/database.js +1 -62
- package/dist/event-buffer.d.ts +2 -5
- package/dist/event-buffer.js +3 -12
- package/dist/native-session-history.js +105 -26
- package/dist/runner/abstract-runner.d.ts +1 -1
- package/dist/runner/abstract-runner.js +2 -2
- package/dist/runner/claude/managed-run-controller.d.ts +0 -1
- package/dist/runner/claude/managed-run-controller.js +32 -17
- package/dist/runner/claude-code-runner.js +3 -4
- package/dist/runner/codex/conversation-parser.d.ts +3 -3
- package/dist/runner/codex/conversation-parser.js +33 -12
- package/dist/runner/codex/managed-run-controller.d.ts +2 -2
- package/dist/runner/codex/managed-run-controller.js +26 -10
- package/dist/runner/codex-runner.d.ts +3 -1
- package/dist/runner/codex-runner.js +40 -10
- package/dist/runner/opencode/conversation-parser.d.ts +11 -7
- package/dist/runner/opencode/conversation-parser.js +16 -16
- package/dist/runner/opencode/managed-run-controller.d.ts +8 -2
- package/dist/runner/opencode/managed-run-controller.js +94 -22
- package/dist/runner/opencode-runner.d.ts +3 -0
- package/dist/runner/opencode-runner.js +25 -0
- package/dist/runner-command-engine.js +1 -1
- package/dist/runner-profiles.js +29 -17
- package/dist/runner-usage.js +5 -5
- package/dist/runtime-state.d.ts +5 -12
- package/dist/runtime-state.js +32 -46
- package/dist/service/attachment-domain-state.d.ts +2 -0
- package/dist/service/attachment-domain-state.js +2 -0
- package/dist/service/conversation-history-service.js +55 -24
- package/dist/service/conversation-segment-service.d.ts +20 -0
- package/dist/service/conversation-segment-service.js +156 -0
- package/dist/service/external-session-resume-service.js +9 -0
- package/dist/service/fake-managed-run-service.d.ts +0 -1
- package/dist/service/fake-managed-run-service.js +0 -2
- package/dist/service/mcp-installation-verifier.js +1 -1
- package/dist/service/native-session-projection-service.js +5 -0
- package/dist/service/native-session-watch-service.d.ts +1 -0
- package/dist/service/native-session-watch-service.js +21 -4
- package/dist/service/node-connection-lifecycle-service.d.ts +0 -2
- package/dist/service/node-connection-lifecycle-service.js +0 -1
- package/dist/service/node-control-channel.js +1 -1
- package/dist/service/node-control-message-service.d.ts +0 -1
- package/dist/service/node-control-message-service.js +9 -6
- package/dist/service/node-request-service.js +2 -1
- package/dist/service/node-workspace-coordinator.d.ts +1 -4
- package/dist/service/node-workspace-coordinator.js +2 -12
- package/dist/service/run-attachment-service.d.ts +3 -2
- package/dist/service/run-attachment-service.js +51 -17
- package/dist/service/run-event-service.d.ts +2 -0
- package/dist/service/run-event-service.js +77 -2
- package/dist/service/run-workbench-service.d.ts +1 -1
- package/dist/service/run-workbench-service.js +1 -5
- package/dist/service/runner-interaction-service.d.ts +1 -2
- package/dist/service/runner-interaction-service.js +1 -4
- package/dist/service/session-catalog-service.js +4 -4
- package/dist/service/session-context-service.d.ts +1 -0
- package/dist/service/session-context-service.js +14 -0
- package/dist/service/session-domain-state.d.ts +1 -0
- package/dist/service/session-domain-state.js +1 -0
- package/dist/service/session-message-service.js +5 -10
- package/dist/service/session-presentation-service.d.ts +1 -0
- package/dist/service/session-presentation-service.js +8 -1
- package/dist/service/session-query-service.d.ts +4 -0
- package/dist/service/session-query-service.js +6 -1
- package/dist/service/skill-directory-service.d.ts +3 -1
- package/dist/service/skill-directory-service.js +31 -30
- package/dist/service/skill-install-service.js +4 -74
- package/dist/service/skill-node-operation-service.d.ts +1 -1
- package/dist/service/skill-node-operation-service.js +6 -3
- package/dist/service/workbench-event-service.d.ts +1 -13
- package/dist/service/workbench-event-service.js +2 -36
- package/dist/service/workbench-manifest-service.d.ts +0 -1
- package/dist/service/workbench-manifest-service.js +0 -1
- package/dist/service/workspace-domain-state.d.ts +1 -0
- package/dist/service/workspace-git-exclude-service.d.ts +4 -0
- package/dist/service/workspace-git-exclude-service.js +119 -0
- package/dist/service/workspace-queue-workbench-service.d.ts +1 -0
- package/dist/service/workspace-queue-workbench-service.js +4 -2
- package/dist/service/workspace-service.js +2 -0
- package/dist/service/workspace-upload-service.d.ts +2 -0
- package/dist/service/workspace-upload-service.js +28 -6
- package/dist/service/workspace-workbench-service.d.ts +0 -3
- package/dist/service/workspace-workbench-service.js +0 -17
- package/dist/supervisor.js +2 -1
- package/dist/terminal.js +1 -1
- package/dist/util/node-operation-parsers.js +2 -2
- package/dist/util/runner-error.d.ts +6 -0
- package/dist/util/runner-error.js +52 -0
- package/dist/util/runner-native-session-parsers.d.ts +2 -3
- package/dist/util/runner-native-session-parsers.js +48 -20
- package/dist/util/safe-error.d.ts +2 -0
- package/dist/util/safe-error.js +5 -0
- package/package.json +2 -2
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { extractId } from '../../util/runner-native-session-parsers.js';
|
|
2
2
|
import { parseSecretEnvironment } from '../../util/secret-environment.js';
|
|
3
|
+
import { safeErrorCode } from '../../util/safe-error.js';
|
|
4
|
+
import { runnerErrorProjection } from '../../util/runner-error.js';
|
|
3
5
|
import { boundedConversationItemId, codexImageAttachments, codexLiveConversationItem, codexUserInputQuestions, compactRunnerText, isPlainRecord } from './conversation-parser.js';
|
|
4
6
|
export class CodexManagedRunController {
|
|
5
7
|
runner;
|
|
@@ -32,10 +34,10 @@ export class CodexManagedRunController {
|
|
|
32
34
|
commandId: 'plan',
|
|
33
35
|
active: true,
|
|
34
36
|
label: 'Plan Mode',
|
|
35
|
-
detail: '
|
|
37
|
+
detail: 'Subsequent messages run in collaborative Plan Mode.',
|
|
36
38
|
closable: true,
|
|
37
39
|
closeInput: '/plan',
|
|
38
|
-
continuation: { label: '
|
|
40
|
+
continuation: { label: 'Implement', prompt: 'Implement the plan above.' }
|
|
39
41
|
});
|
|
40
42
|
return { command: command.id, states: this.host.commandStates(session.id) };
|
|
41
43
|
}
|
|
@@ -83,7 +85,8 @@ export class CodexManagedRunController {
|
|
|
83
85
|
};
|
|
84
86
|
}
|
|
85
87
|
catch (error) {
|
|
86
|
-
this.
|
|
88
|
+
this.emitError(runId, error, 'CODEX_COMPACT_FAILED');
|
|
89
|
+
this.host.emit(runId, 'run.failed', runnerErrorProjection(error, 'CODEX_COMPACT_FAILED'), 'FAILED');
|
|
87
90
|
this.runner.execution.runs.delete(runId);
|
|
88
91
|
this.runner.releaseEnvironment(runId);
|
|
89
92
|
this.host.removeActive(runId);
|
|
@@ -183,16 +186,18 @@ export class CodexManagedRunController {
|
|
|
183
186
|
if (notification.method === 'turn/completed') {
|
|
184
187
|
const status = turn?.status;
|
|
185
188
|
const terminal = status === 'completed' ? 'SUCCEEDED' : status === 'interrupted' ? 'INTERRUPTED' : 'FAILED';
|
|
186
|
-
|
|
189
|
+
if (terminal === 'FAILED')
|
|
190
|
+
this.emitError(runId, turn?.error ?? turn, 'CODEX_RUN_FAILED');
|
|
191
|
+
const failure = terminal === 'FAILED'
|
|
192
|
+
? runnerErrorProjection(turn?.error ?? turn, 'CODEX_RUN_FAILED')
|
|
193
|
+
: { status: status ?? 'unknown' };
|
|
194
|
+
this.host.emit(runId, terminal === 'FAILED' ? 'run.failed' : 'run.completed', failure, terminal);
|
|
187
195
|
const active = this.runner.execution.runs.get(runId);
|
|
188
196
|
const titleRefresh = active === undefined ||
|
|
189
197
|
active.refreshTitle === false ||
|
|
190
198
|
this.host.session(active.sessionId)?.titleSource !== 'AUTO'
|
|
191
199
|
? undefined
|
|
192
200
|
: this.host.syncTitle(active.sessionId, active.threadId, active.cwd);
|
|
193
|
-
if (active !== undefined) {
|
|
194
|
-
void this.host.captureSnapshot(runId, active.cwd);
|
|
195
|
-
}
|
|
196
201
|
this.runner.execution.runs.delete(runId);
|
|
197
202
|
if (titleRefresh === undefined)
|
|
198
203
|
this.runner.releaseEnvironment(runId);
|
|
@@ -283,7 +288,8 @@ export class CodexManagedRunController {
|
|
|
283
288
|
secretEnvironment = parseSecretEnvironment(payload.secretEnvironment);
|
|
284
289
|
}
|
|
285
290
|
catch (error) {
|
|
286
|
-
this.
|
|
291
|
+
this.emitError(runId, error, 'CODEX_RUN_FAILED');
|
|
292
|
+
this.host.emit(runId, 'run.rejected', runnerErrorProjection(error, 'MESSAGE_ATTACHMENTS_INVALID'), 'FAILED');
|
|
287
293
|
return;
|
|
288
294
|
}
|
|
289
295
|
this.host.adopt({
|
|
@@ -311,7 +317,7 @@ export class CodexManagedRunController {
|
|
|
311
317
|
this.runner.prepareEnvironment(secretEnvironment, runId);
|
|
312
318
|
}
|
|
313
319
|
catch (error) {
|
|
314
|
-
this.host.emit(runId, 'run.rejected', { code: error
|
|
320
|
+
this.host.emit(runId, 'run.rejected', { code: safeErrorCode(error, 'SECRET_ENVIRONMENT_INVALID') }, 'FAILED');
|
|
315
321
|
return;
|
|
316
322
|
}
|
|
317
323
|
this.host.addActive(runId);
|
|
@@ -381,10 +387,20 @@ export class CodexManagedRunController {
|
|
|
381
387
|
this.runner.releaseEnvironment(runId);
|
|
382
388
|
return;
|
|
383
389
|
}
|
|
384
|
-
this.
|
|
390
|
+
this.emitError(runId, error, 'CODEX_RUN_FAILED');
|
|
391
|
+
this.host.emit(runId, 'run.failed', runnerErrorProjection(error, 'CODEX_RUN_FAILED'), 'FAILED');
|
|
385
392
|
this.runner.execution.runs.delete(runId);
|
|
386
393
|
this.runner.releaseEnvironment(runId);
|
|
387
394
|
this.host.removeActive(runId);
|
|
388
395
|
}
|
|
389
396
|
}
|
|
397
|
+
emitError(runId, error, fallbackCode) {
|
|
398
|
+
const projected = runnerErrorProjection(error, fallbackCode);
|
|
399
|
+
this.host.emitItem(runId, {
|
|
400
|
+
itemId: boundedConversationItemId('runner-error', runId),
|
|
401
|
+
kind: 'error',
|
|
402
|
+
status: 'FAILED',
|
|
403
|
+
payload: { ...projected, recoverable: true, retryAfterMs: null }
|
|
404
|
+
});
|
|
405
|
+
}
|
|
390
406
|
}
|
|
@@ -13,6 +13,7 @@ export declare class CodexRunner extends AbstractRunner<'codex'> {
|
|
|
13
13
|
private environmentFingerprint;
|
|
14
14
|
private readonly environmentRuns;
|
|
15
15
|
private readonly profileCache;
|
|
16
|
+
private readonly profileCacheTimers;
|
|
16
17
|
private readonly profileRefreshes;
|
|
17
18
|
private readonly profileClients;
|
|
18
19
|
private readonly profileKeyByEnvironment;
|
|
@@ -23,6 +24,7 @@ export declare class CodexRunner extends AbstractRunner<'codex'> {
|
|
|
23
24
|
refreshProfile(capabilities: NodeCapabilities, workspace?: NodeWorkspace, environment?: Readonly<Record<string, string>>): Promise<RunnerProfile>;
|
|
24
25
|
private refreshCodexProfile;
|
|
25
26
|
private selectCachedProfile;
|
|
27
|
+
private cacheProfile;
|
|
26
28
|
private fetchCodexProfile;
|
|
27
29
|
available(capabilities: NodeCapabilities): boolean;
|
|
28
30
|
executeGlobalCommand(commandId: string, input: string): unknown;
|
|
@@ -44,7 +46,7 @@ export declare class CodexRunner extends AbstractRunner<'codex'> {
|
|
|
44
46
|
readExternalActivity(session: NodeAgentSession): Promise<"UNAVAILABLE" | "MANAGED_ACTIVE" | "EXTERNAL_ACTIVE" | "IDLE" | undefined>;
|
|
45
47
|
acceptsTruncatedNativeHistory(): boolean;
|
|
46
48
|
readManagedRunnerTitle(session: NodeAgentSession, nativeSessionId: string): Promise<string | undefined>;
|
|
47
|
-
|
|
49
|
+
nativeImageRoots(session: NodeAgentSession): readonly string[];
|
|
48
50
|
managedRunForNativeTurn(nativeTurnId: string): string | undefined;
|
|
49
51
|
protected profileForValidation(): RunnerProfile;
|
|
50
52
|
defaultConfiguration(_workspace?: NodeWorkspace, environment?: Readonly<Record<string, string>>): RunnerDefaultConfiguration;
|
|
@@ -6,11 +6,12 @@ import { resolve } from 'node:path';
|
|
|
6
6
|
import { createHash } from 'node:crypto';
|
|
7
7
|
import { discoverAllNativeSessions } from '../native-session-history.js';
|
|
8
8
|
import { readCodexNativeContextUsage } from '../native-session-history.js';
|
|
9
|
-
import { codexActiveTurnId, codexOfficialConversationPage, codexThreadCwd, codexThreadActivity, deduplicateDirectSessions,
|
|
9
|
+
import { codexActiveTurnId, codexOfficialConversationPage, codexThreadCwd, codexThreadActivity, deduplicateDirectSessions, extractCodexThreads, isWithinWorkspace, latestNativeActivity } from './codex/conversation-parser.js';
|
|
10
10
|
import { codexSessionControl } from '../codex-app-server.js';
|
|
11
11
|
import { extractId } from '../util/runner-native-session-parsers.js';
|
|
12
12
|
import { nodeLog } from '../operational.js';
|
|
13
13
|
const CODEX_MODEL_CACHE_MS = 10 * 60 * 1_000;
|
|
14
|
+
const CODEX_PROFILE_ENTRY_TTL_MS = 30 * 60 * 1_000;
|
|
14
15
|
const CODEX_MODEL_FAILURE_RETRY_MS = 30 * 1_000;
|
|
15
16
|
const CODEX_MODEL_PAGE_SIZE = 100;
|
|
16
17
|
export class CodexRunner extends AbstractRunner {
|
|
@@ -23,6 +24,7 @@ export class CodexRunner extends AbstractRunner {
|
|
|
23
24
|
environmentFingerprint;
|
|
24
25
|
environmentRuns = new Set();
|
|
25
26
|
profileCache = new Map();
|
|
27
|
+
profileCacheTimers = new Map();
|
|
26
28
|
profileRefreshes = new Map();
|
|
27
29
|
profileClients = new Set();
|
|
28
30
|
profileKeyByEnvironment = new Map();
|
|
@@ -74,7 +76,7 @@ export class CodexRunner extends AbstractRunner {
|
|
|
74
76
|
successful: true,
|
|
75
77
|
retryAt: 0
|
|
76
78
|
};
|
|
77
|
-
this.
|
|
79
|
+
this.cacheProfile(key, cache);
|
|
78
80
|
if (this.selectedProfileKey === key)
|
|
79
81
|
this.latestProfileCache = cache;
|
|
80
82
|
return cache.profile;
|
|
@@ -88,7 +90,7 @@ export class CodexRunner extends AbstractRunner {
|
|
|
88
90
|
cached.retryAt = Date.now() + CODEX_MODEL_FAILURE_RETRY_MS;
|
|
89
91
|
return cached.profile;
|
|
90
92
|
}
|
|
91
|
-
this.
|
|
93
|
+
this.cacheProfile(key, {
|
|
92
94
|
profile: { ...base, models: [] },
|
|
93
95
|
defaultConfiguration: emptyCodexConfiguration(),
|
|
94
96
|
fetchedAt: 0,
|
|
@@ -106,6 +108,25 @@ export class CodexRunner extends AbstractRunner {
|
|
|
106
108
|
this.latestProfileCache = cached;
|
|
107
109
|
return cached.profile;
|
|
108
110
|
}
|
|
111
|
+
cacheProfile(key, cache) {
|
|
112
|
+
const previousTimer = this.profileCacheTimers.get(key);
|
|
113
|
+
if (previousTimer !== undefined)
|
|
114
|
+
clearTimeout(previousTimer);
|
|
115
|
+
this.profileCache.set(key, cache);
|
|
116
|
+
const timer = setTimeout(() => {
|
|
117
|
+
this.profileCacheTimers.delete(key);
|
|
118
|
+
this.profileCache.delete(key);
|
|
119
|
+
for (const [environmentKey, profileKey] of this.profileKeyByEnvironment)
|
|
120
|
+
if (profileKey === key)
|
|
121
|
+
this.profileKeyByEnvironment.delete(environmentKey);
|
|
122
|
+
if (this.selectedProfileKey === key) {
|
|
123
|
+
this.selectedProfileKey = undefined;
|
|
124
|
+
this.latestProfileCache = undefined;
|
|
125
|
+
}
|
|
126
|
+
}, CODEX_PROFILE_ENTRY_TTL_MS);
|
|
127
|
+
timer.unref();
|
|
128
|
+
this.profileCacheTimers.set(key, timer);
|
|
129
|
+
}
|
|
109
130
|
async fetchCodexProfile(base, environment) {
|
|
110
131
|
const client = this.profileClientFactory();
|
|
111
132
|
this.profileClients.add(client);
|
|
@@ -248,10 +269,13 @@ export class CodexRunner extends AbstractRunner {
|
|
|
248
269
|
await this.start();
|
|
249
270
|
return extractCodexThreads(await this.listThreads()).find((thread) => thread.id === nativeSessionId)?.title;
|
|
250
271
|
}
|
|
251
|
-
|
|
252
|
-
return
|
|
253
|
-
|
|
254
|
-
|
|
272
|
+
nativeImageRoots(session) {
|
|
273
|
+
return [
|
|
274
|
+
session.cwd,
|
|
275
|
+
...(session.externalSessionId === null
|
|
276
|
+
? []
|
|
277
|
+
: [resolve(homedir(), '.codex', 'generated_images', session.externalSessionId)])
|
|
278
|
+
];
|
|
255
279
|
}
|
|
256
280
|
managedRunForNativeTurn(nativeTurnId) {
|
|
257
281
|
return this.execution.managedTurns.get(nativeTurnId)?.runId;
|
|
@@ -313,11 +337,10 @@ export class CodexRunner extends AbstractRunner {
|
|
|
313
337
|
}
|
|
314
338
|
return { activity: 'IDLE', session: context.create(external) };
|
|
315
339
|
}
|
|
316
|
-
catch
|
|
340
|
+
catch {
|
|
317
341
|
return {
|
|
318
342
|
activity: 'UNAVAILABLE',
|
|
319
|
-
failureCode: 'RUNNER_RESUME_FAILED'
|
|
320
|
-
failureDetail: externalResumeFailureDetail(error)
|
|
343
|
+
failureCode: 'RUNNER_RESUME_FAILED'
|
|
321
344
|
};
|
|
322
345
|
}
|
|
323
346
|
}
|
|
@@ -464,6 +487,13 @@ export class CodexRunner extends AbstractRunner {
|
|
|
464
487
|
this.client.clearEnvironment();
|
|
465
488
|
this.environmentFingerprint = undefined;
|
|
466
489
|
this.environmentRuns.clear();
|
|
490
|
+
for (const timer of this.profileCacheTimers.values())
|
|
491
|
+
clearTimeout(timer);
|
|
492
|
+
this.profileCacheTimers.clear();
|
|
493
|
+
this.profileCache.clear();
|
|
494
|
+
this.profileKeyByEnvironment.clear();
|
|
495
|
+
this.latestProfileCache = undefined;
|
|
496
|
+
this.selectedProfileKey = undefined;
|
|
467
497
|
}
|
|
468
498
|
request(method, params) {
|
|
469
499
|
return this.client.request(method, params);
|
|
@@ -18,10 +18,14 @@ export declare function openCodeQuestions(value: unknown): {
|
|
|
18
18
|
allowOther: boolean;
|
|
19
19
|
multiSelect: boolean;
|
|
20
20
|
}[];
|
|
21
|
-
export declare function
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
export declare function openCodePatchToolPayload(): {
|
|
22
|
+
namespace: string;
|
|
23
|
+
toolName: string;
|
|
24
|
+
title: string;
|
|
25
|
+
inputSummary: null;
|
|
26
|
+
outputSummary: string;
|
|
27
|
+
outputSummaryCode: string;
|
|
28
|
+
progressLabel: null;
|
|
29
|
+
errorCode: null;
|
|
30
|
+
truncated: boolean;
|
|
31
|
+
};
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { basename } from 'node:path';
|
|
2
1
|
import { boundedConversationItemId, compactRunnerText, compactRunnerValue } from '../codex/conversation-parser.js';
|
|
3
2
|
export function openCodeConversationPage(session, messages, input, managedTurn) {
|
|
4
3
|
const records = messages.flatMap((value) => {
|
|
@@ -114,10 +113,11 @@ function partItem(session, turnId, raw, planMode, startedAt, completedAt, sequen
|
|
|
114
113
|
if (part.type === 'compaction')
|
|
115
114
|
return item(session, turnId, part.id, 'context_compaction', 'COMPLETED', {
|
|
116
115
|
phase: 'COMPLETED',
|
|
117
|
-
summary: '
|
|
116
|
+
summary: 'Context compacted',
|
|
117
|
+
summaryCode: 'CONTEXT_COMPACTED'
|
|
118
118
|
}, startedAt, completedAt, sequence);
|
|
119
119
|
if (part.type === 'patch')
|
|
120
|
-
return item(session, turnId, part.id, '
|
|
120
|
+
return item(session, turnId, part.id, 'tool_call', 'COMPLETED', openCodePatchToolPayload(), startedAt, completedAt, sequence);
|
|
121
121
|
if (part.type === 'step-start' || part.type === 'step-finish')
|
|
122
122
|
return undefined;
|
|
123
123
|
if (part.type === 'text' || part.type === 'reasoning')
|
|
@@ -128,7 +128,7 @@ function partItem(session, turnId, raw, planMode, startedAt, completedAt, sequen
|
|
|
128
128
|
}, startedAt, completedAt, sequence);
|
|
129
129
|
}
|
|
130
130
|
export function openCodeQuestions(value) {
|
|
131
|
-
if (!Array.isArray(value) || value.length === 0
|
|
131
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
132
132
|
return [];
|
|
133
133
|
return value.flatMap((value, index) => {
|
|
134
134
|
const question = record(value);
|
|
@@ -162,18 +162,18 @@ export function openCodeQuestions(value) {
|
|
|
162
162
|
];
|
|
163
163
|
});
|
|
164
164
|
}
|
|
165
|
-
export function
|
|
166
|
-
return
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
165
|
+
export function openCodePatchToolPayload() {
|
|
166
|
+
return {
|
|
167
|
+
namespace: 'opencode',
|
|
168
|
+
toolName: 'patch',
|
|
169
|
+
title: 'patch',
|
|
170
|
+
inputSummary: null,
|
|
171
|
+
outputSummary: 'OpenCode applied the patch',
|
|
172
|
+
outputSummaryCode: 'OPENCODE_PATCH_APPLIED',
|
|
173
|
+
progressLabel: null,
|
|
174
|
+
errorCode: null,
|
|
175
|
+
truncated: false
|
|
176
|
+
};
|
|
177
177
|
}
|
|
178
178
|
function item(session, turnId, nativeId, kind, status, payload, startedAt, completedAt, sequence) {
|
|
179
179
|
return {
|
|
@@ -7,6 +7,7 @@ interface OpenCodeManagedRunHost {
|
|
|
7
7
|
intercepted(): boolean;
|
|
8
8
|
readonly active: Set<string>;
|
|
9
9
|
parseAttachments(value: unknown): readonly RunnerImageAttachment[];
|
|
10
|
+
persistAttachments(cwd: string, runId: string, attachments: readonly RunnerImageAttachment[]): Promise<readonly string[]>;
|
|
10
11
|
emit(runId: string, eventType: string, payload: unknown, status?: FakeRunnerStatus): void;
|
|
11
12
|
emitItem(runId: string, item: OpenCodePublicItem): void;
|
|
12
13
|
adopt(input: {
|
|
@@ -16,10 +17,13 @@ interface OpenCodeManagedRunHost {
|
|
|
16
17
|
cwd: string;
|
|
17
18
|
externalSessionId?: string;
|
|
18
19
|
}): void;
|
|
20
|
+
run(runId: string): {
|
|
21
|
+
readonly id: string;
|
|
22
|
+
};
|
|
23
|
+
hasActiveSessionLease(sessionId: string): boolean;
|
|
19
24
|
promote(sessionId: string, nativeSessionId: string): void;
|
|
20
25
|
rememberNative(nativeSessionId: string, sessionId: string): void;
|
|
21
26
|
send(type: string, payload: unknown): void;
|
|
22
|
-
captureSnapshot(runId: string, cwd: string): Promise<void>;
|
|
23
27
|
createApproval(input: {
|
|
24
28
|
readonly runId: string;
|
|
25
29
|
readonly approvalId: string;
|
|
@@ -40,7 +44,7 @@ interface OpenCodeManagedRunHost {
|
|
|
40
44
|
}
|
|
41
45
|
type OpenCodePublicItem = {
|
|
42
46
|
readonly itemId: string;
|
|
43
|
-
readonly kind: 'assistant_message' | 'plan' | 'reasoning_summary' | 'tool_call' | 'user_input_request' | 'context_compaction' | '
|
|
47
|
+
readonly kind: 'assistant_message' | 'plan' | 'reasoning_summary' | 'tool_call' | 'user_input_request' | 'context_compaction' | 'error' | 'unknown';
|
|
44
48
|
readonly status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'FAILED';
|
|
45
49
|
readonly payload: Record<string, unknown>;
|
|
46
50
|
readonly merge?: boolean;
|
|
@@ -59,6 +63,8 @@ export declare class OpenCodeManagedRunController {
|
|
|
59
63
|
executeCommand(session: NodeAgentSession, command: RunnerCommandDescriptor, context: {
|
|
60
64
|
readonly secretEnvironment: Readonly<Record<string, string>>;
|
|
61
65
|
}): Promise<unknown>;
|
|
66
|
+
private compact;
|
|
67
|
+
private runCompact;
|
|
62
68
|
start(envelope: NodeEnvelope): void;
|
|
63
69
|
cancel(runId: string, intent: 'CANCEL' | 'INTERRUPT'): boolean;
|
|
64
70
|
decideApproval(input: {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parseSecretEnvironment } from '../../util/secret-environment.js';
|
|
2
|
+
import { runnerErrorProjection } from '../../util/runner-error.js';
|
|
2
3
|
import { boundedConversationItemId, compactRunnerText, compactRunnerValue } from '../codex/conversation-parser.js';
|
|
3
|
-
import {
|
|
4
|
+
import { openCodePatchToolPayload, openCodeQuestions } from './conversation-parser.js';
|
|
4
5
|
export class OpenCodeManagedRunController {
|
|
5
6
|
runner;
|
|
6
7
|
host;
|
|
@@ -18,7 +19,7 @@ export class OpenCodeManagedRunController {
|
|
|
18
19
|
}
|
|
19
20
|
async executeCommand(session, command, context) {
|
|
20
21
|
if (command.id === 'compact')
|
|
21
|
-
return this.
|
|
22
|
+
return this.compact(session, context.secretEnvironment);
|
|
22
23
|
if (command.id !== 'plan')
|
|
23
24
|
throw new Error('RUNNER_COMMAND_UNAVAILABLE');
|
|
24
25
|
const current = this.host.commandState(session.id, 'plan');
|
|
@@ -29,13 +30,60 @@ export class OpenCodeManagedRunController {
|
|
|
29
30
|
commandId: 'plan',
|
|
30
31
|
active: true,
|
|
31
32
|
label: 'Plan Mode',
|
|
32
|
-
detail: '
|
|
33
|
+
detail: 'Subsequent messages use the OpenCode Plan Agent for read-only analysis.',
|
|
33
34
|
closable: true,
|
|
34
35
|
closeInput: '/plan',
|
|
35
|
-
continuation: { label: '
|
|
36
|
+
continuation: { label: 'Implement', prompt: 'Implement the plan above.' }
|
|
36
37
|
});
|
|
37
38
|
return { command: command.id, states: this.host.commandStates(session.id) };
|
|
38
39
|
}
|
|
40
|
+
compact(session, environment) {
|
|
41
|
+
if (session.externalSessionId === null ||
|
|
42
|
+
session.model === null ||
|
|
43
|
+
session.nativeControl !== 'MAR_MANAGED' ||
|
|
44
|
+
this.host.hasActiveSessionLease(session.id))
|
|
45
|
+
throw new Error('RUNNER_COMMAND_UNAVAILABLE');
|
|
46
|
+
const runId = crypto.randomUUID();
|
|
47
|
+
const client = this.runner.managedClient(environment);
|
|
48
|
+
this.host.adopt({
|
|
49
|
+
runId,
|
|
50
|
+
sessionId: session.id,
|
|
51
|
+
runner: 'opencode',
|
|
52
|
+
cwd: session.cwd,
|
|
53
|
+
externalSessionId: session.externalSessionId
|
|
54
|
+
});
|
|
55
|
+
this.host.active.add(runId);
|
|
56
|
+
this.runner.execution.set(runId, {
|
|
57
|
+
runId,
|
|
58
|
+
sessionId: session.id,
|
|
59
|
+
nativeSessionId: session.externalSessionId,
|
|
60
|
+
cwd: session.cwd,
|
|
61
|
+
client,
|
|
62
|
+
abort: new AbortController(),
|
|
63
|
+
access: session.access ?? 'default',
|
|
64
|
+
agent: 'build'
|
|
65
|
+
});
|
|
66
|
+
this.host.emit(runId, 'run.started', { command: 'compact' }, 'STARTING');
|
|
67
|
+
void this.runCompact(runId, session.model);
|
|
68
|
+
return {
|
|
69
|
+
command: 'compact',
|
|
70
|
+
run: this.host.run(runId),
|
|
71
|
+
states: this.host.commandStates(session.id)
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
async runCompact(runId, model) {
|
|
75
|
+
const execution = this.runner.execution.get(runId);
|
|
76
|
+
if (execution === undefined)
|
|
77
|
+
return;
|
|
78
|
+
this.host.emit(runId, 'run.running', { command: 'compact' }, 'RUNNING');
|
|
79
|
+
try {
|
|
80
|
+
await execution.client.summarize(execution.cwd, execution.nativeSessionId, model);
|
|
81
|
+
this.finish(runId, 'SUCCEEDED', 'run.completed', { status: 'SUCCEEDED' });
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
this.finish(runId, 'FAILED', 'run.failed', error, 'OPENCODE_COMPACT_FAILED');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
39
87
|
start(envelope) {
|
|
40
88
|
const payload = envelope.payload;
|
|
41
89
|
if (payload.runner !== 'opencode' ||
|
|
@@ -56,7 +104,14 @@ export class OpenCodeManagedRunController {
|
|
|
56
104
|
attachments = this.host.parseAttachments(payload.attachments);
|
|
57
105
|
}
|
|
58
106
|
catch (error) {
|
|
59
|
-
|
|
107
|
+
const projected = runnerErrorProjection(error, 'RUNNER_INPUT_INVALID');
|
|
108
|
+
this.host.emitItem(payload.runId, {
|
|
109
|
+
itemId: boundedConversationItemId('runner-error', payload.runId),
|
|
110
|
+
kind: 'error',
|
|
111
|
+
status: 'FAILED',
|
|
112
|
+
payload: { ...projected, recoverable: true, retryAfterMs: null }
|
|
113
|
+
});
|
|
114
|
+
this.host.emit(payload.runId, 'run.rejected', projected, 'FAILED');
|
|
60
115
|
return;
|
|
61
116
|
}
|
|
62
117
|
void this.run({
|
|
@@ -122,8 +177,8 @@ export class OpenCodeManagedRunController {
|
|
|
122
177
|
return true;
|
|
123
178
|
}
|
|
124
179
|
async run(input) {
|
|
125
|
-
// OpenCode
|
|
126
|
-
//
|
|
180
|
+
// OpenCode dynamic MCP registration belongs to Server process state. Runs that load MCP use an
|
|
181
|
+
// isolated Server so parallel sessions in one workspace do not share MCP processes or context.
|
|
127
182
|
const client = this.runner.managedClient(input.environment, hasMcpConfiguration(input.mcp) ? input.runId : undefined);
|
|
128
183
|
this.startingClients.set(input.runId, client);
|
|
129
184
|
this.startingCwds.set(input.runId, input.cwd);
|
|
@@ -180,18 +235,19 @@ export class OpenCodeManagedRunController {
|
|
|
180
235
|
rejectReady(error);
|
|
181
236
|
return;
|
|
182
237
|
}
|
|
183
|
-
this.finish(input.runId, 'FAILED', 'run.failed',
|
|
184
|
-
code: error instanceof Error ? error.message : 'OPENCODE_EVENT_FAILED'
|
|
185
|
-
});
|
|
238
|
+
this.finish(input.runId, 'FAILED', 'run.failed', error, 'OPENCODE_EVENT_FAILED');
|
|
186
239
|
});
|
|
187
240
|
await subscriptionReady;
|
|
188
241
|
this.host.emit(input.runId, 'run.running', {}, 'RUNNING');
|
|
189
242
|
// The initial SSE snapshot can contain an idle event from before this prompt.
|
|
190
243
|
this.pendingIdle.delete(input.runId);
|
|
244
|
+
const attachmentPaths = input.attachments.length === 0
|
|
245
|
+
? []
|
|
246
|
+
: await this.host.persistAttachments(input.cwd, input.runId, input.attachments);
|
|
191
247
|
await client.prompt({
|
|
192
248
|
directory: input.cwd,
|
|
193
249
|
sessionID: nativeSessionId,
|
|
194
|
-
text: input.input,
|
|
250
|
+
text: openCodeImageInput(input.input, attachmentPaths),
|
|
195
251
|
model: input.model,
|
|
196
252
|
variant: input.effort,
|
|
197
253
|
agent: input.agent,
|
|
@@ -202,9 +258,7 @@ export class OpenCodeManagedRunController {
|
|
|
202
258
|
this.finish(input.runId, 'SUCCEEDED', 'run.completed', { status: 'SUCCEEDED' });
|
|
203
259
|
}
|
|
204
260
|
catch (error) {
|
|
205
|
-
this.finish(input.runId, 'FAILED', 'run.failed',
|
|
206
|
-
code: error instanceof Error ? error.message : 'OPENCODE_RUN_START_FAILED'
|
|
207
|
-
});
|
|
261
|
+
this.finish(input.runId, 'FAILED', 'run.failed', error, 'OPENCODE_RUN_START_FAILED');
|
|
208
262
|
}
|
|
209
263
|
}
|
|
210
264
|
event(runId, nativeSessionId, value) {
|
|
@@ -227,7 +281,7 @@ export class OpenCodeManagedRunController {
|
|
|
227
281
|
return;
|
|
228
282
|
}
|
|
229
283
|
if (event?.type === 'session.error') {
|
|
230
|
-
this.finish(runId, 'FAILED', 'run.failed',
|
|
284
|
+
this.finish(runId, 'FAILED', 'run.failed', properties?.error ?? properties);
|
|
231
285
|
return;
|
|
232
286
|
}
|
|
233
287
|
if (event?.type === 'message.part.updated') {
|
|
@@ -305,7 +359,7 @@ export class OpenCodeManagedRunController {
|
|
|
305
359
|
if (execution === undefined || requestId === undefined)
|
|
306
360
|
return;
|
|
307
361
|
const questions = openCodeQuestions(rawQuestions);
|
|
308
|
-
if (questions.length === 0 ||
|
|
362
|
+
if (questions.length === 0 || this.questions.has(requestId))
|
|
309
363
|
return;
|
|
310
364
|
const timer = setTimeout(() => {
|
|
311
365
|
const pending = this.questions.get(requestId);
|
|
@@ -336,7 +390,7 @@ export class OpenCodeManagedRunController {
|
|
|
336
390
|
payload: { requestId, questions }
|
|
337
391
|
});
|
|
338
392
|
}
|
|
339
|
-
finish(runId, status, eventType, payload) {
|
|
393
|
+
finish(runId, status, eventType, payload, fallbackCode = 'OPENCODE_RUN_FAILED') {
|
|
340
394
|
const execution = this.runner.execution.get(runId);
|
|
341
395
|
const cwd = execution?.cwd ?? this.startingCwds.get(runId);
|
|
342
396
|
const client = execution?.client ?? this.startingClients.get(runId);
|
|
@@ -359,13 +413,27 @@ export class OpenCodeManagedRunController {
|
|
|
359
413
|
clearTimeout(pending.timer);
|
|
360
414
|
this.questions.delete(id);
|
|
361
415
|
}
|
|
416
|
+
if (status === 'FAILED') {
|
|
417
|
+
const projected = runnerErrorProjection(payload, fallbackCode);
|
|
418
|
+
this.host.emitItem(runId, {
|
|
419
|
+
itemId: boundedConversationItemId('runner-error', runId),
|
|
420
|
+
kind: 'error',
|
|
421
|
+
status: 'FAILED',
|
|
422
|
+
payload: { ...projected, recoverable: true, retryAfterMs: null }
|
|
423
|
+
});
|
|
424
|
+
payload = projected;
|
|
425
|
+
}
|
|
362
426
|
this.host.emit(runId, eventType, payload, status);
|
|
363
427
|
if (client !== undefined)
|
|
364
428
|
this.runner.releaseManagedClient(client);
|
|
365
|
-
if (cwd !== undefined)
|
|
366
|
-
void this.host.captureSnapshot(runId, cwd);
|
|
367
429
|
}
|
|
368
430
|
}
|
|
431
|
+
function openCodeImageInput(input, attachmentPaths) {
|
|
432
|
+
if (attachmentPaths.length === 0)
|
|
433
|
+
return input;
|
|
434
|
+
const paths = attachmentPaths.map((path) => `- ${JSON.stringify(path)}`).join('\n');
|
|
435
|
+
return `${input}\n\nIf an inline image in this message is unavailable, use the read tool on the corresponding copy below. Do not read it again when the inline image is already visible:\n${paths}`;
|
|
436
|
+
}
|
|
369
437
|
function hasMcpConfiguration(value) {
|
|
370
438
|
return value !== null && typeof value === 'object' && Object.keys(value).length > 0;
|
|
371
439
|
}
|
|
@@ -425,14 +493,18 @@ function openCodePart(part, planMode = false) {
|
|
|
425
493
|
itemId,
|
|
426
494
|
kind: 'context_compaction',
|
|
427
495
|
status: 'COMPLETED',
|
|
428
|
-
payload: {
|
|
496
|
+
payload: {
|
|
497
|
+
phase: 'COMPLETED',
|
|
498
|
+
summary: 'Context compacted',
|
|
499
|
+
summaryCode: 'CONTEXT_COMPACTED'
|
|
500
|
+
}
|
|
429
501
|
};
|
|
430
502
|
if (part.type === 'patch')
|
|
431
503
|
return {
|
|
432
504
|
itemId,
|
|
433
|
-
kind: '
|
|
505
|
+
kind: 'tool_call',
|
|
434
506
|
status: 'COMPLETED',
|
|
435
|
-
payload:
|
|
507
|
+
payload: openCodePatchToolPayload(),
|
|
436
508
|
merge: true
|
|
437
509
|
};
|
|
438
510
|
if (part.type === 'step-start' || part.type === 'step-finish')
|
|
@@ -19,6 +19,7 @@ export declare class OpenCodeRunner extends AbstractRunner<'opencode'> {
|
|
|
19
19
|
readonly discoveryScope: "WORKSPACE_REQUIRED";
|
|
20
20
|
private readonly modelsByWorkspace;
|
|
21
21
|
private readonly contextLimitsByWorkspace;
|
|
22
|
+
private readonly profileTimersByWorkspace;
|
|
22
23
|
readonly execution: Map<string, OpenCodeManagedExecution>;
|
|
23
24
|
readonly managedTurns: Map<string, string>;
|
|
24
25
|
private readonly environmentClients;
|
|
@@ -56,6 +57,8 @@ export declare class OpenCodeRunner extends AbstractRunner<'opencode'> {
|
|
|
56
57
|
maxTokens: number;
|
|
57
58
|
} | undefined>;
|
|
58
59
|
stop(): Promise<void>;
|
|
60
|
+
private scheduleProfileExpiry;
|
|
61
|
+
private clearProfileTimer;
|
|
59
62
|
managedClient(environment: Readonly<Record<string, string>>, isolationKey?: string): OpenCodeServerClient;
|
|
60
63
|
releaseManagedClient(client: OpenCodeServerClient): void;
|
|
61
64
|
}
|
|
@@ -5,6 +5,7 @@ import { AbstractRunner } from './abstract-runner.js';
|
|
|
5
5
|
import { isWithinWorkspace } from './codex/conversation-parser.js';
|
|
6
6
|
import { createHash } from 'node:crypto';
|
|
7
7
|
import { openCodeConversationPage } from './opencode/conversation-parser.js';
|
|
8
|
+
const PROFILE_ENTRY_TTL_MS = 30 * 60_000;
|
|
8
9
|
export class OpenCodeRunner extends AbstractRunner {
|
|
9
10
|
client;
|
|
10
11
|
clientFactory;
|
|
@@ -12,6 +13,7 @@ export class OpenCodeRunner extends AbstractRunner {
|
|
|
12
13
|
discoveryScope = 'WORKSPACE_REQUIRED';
|
|
13
14
|
modelsByWorkspace = new Map();
|
|
14
15
|
contextLimitsByWorkspace = new Map();
|
|
16
|
+
profileTimersByWorkspace = new Map();
|
|
15
17
|
execution = new Map();
|
|
16
18
|
managedTurns = new Map();
|
|
17
19
|
environmentClients = new Map();
|
|
@@ -39,11 +41,13 @@ export class OpenCodeRunner extends AbstractRunner {
|
|
|
39
41
|
const models = openCodeProfileModels(catalog);
|
|
40
42
|
this.modelsByWorkspace.set(workspace.path, models);
|
|
41
43
|
this.contextLimitsByWorkspace.set(workspace.path, openCodeContextLimits(catalog));
|
|
44
|
+
this.scheduleProfileExpiry(workspace.path);
|
|
42
45
|
return { ...this.profile(capabilities), models };
|
|
43
46
|
}
|
|
44
47
|
catch {
|
|
45
48
|
this.modelsByWorkspace.delete(workspace.path);
|
|
46
49
|
this.contextLimitsByWorkspace.delete(workspace.path);
|
|
50
|
+
this.clearProfileTimer(workspace.path);
|
|
47
51
|
}
|
|
48
52
|
finally {
|
|
49
53
|
this.releaseManagedClient(client);
|
|
@@ -279,6 +283,11 @@ export class OpenCodeRunner extends AbstractRunner {
|
|
|
279
283
|
execution.abort.abort();
|
|
280
284
|
this.execution.clear();
|
|
281
285
|
this.managedTurns.clear();
|
|
286
|
+
for (const timer of this.profileTimersByWorkspace.values())
|
|
287
|
+
clearTimeout(timer);
|
|
288
|
+
this.profileTimersByWorkspace.clear();
|
|
289
|
+
this.modelsByWorkspace.clear();
|
|
290
|
+
this.contextLimitsByWorkspace.clear();
|
|
282
291
|
const stopping = [
|
|
283
292
|
this.client.stop(),
|
|
284
293
|
...[...this.environmentClients.values()].map(({ client }) => client.stop()),
|
|
@@ -288,6 +297,22 @@ export class OpenCodeRunner extends AbstractRunner {
|
|
|
288
297
|
await Promise.allSettled(stopping);
|
|
289
298
|
this.stoppingClients.clear();
|
|
290
299
|
}
|
|
300
|
+
scheduleProfileExpiry(workspacePath) {
|
|
301
|
+
this.clearProfileTimer(workspacePath);
|
|
302
|
+
const timer = setTimeout(() => {
|
|
303
|
+
this.profileTimersByWorkspace.delete(workspacePath);
|
|
304
|
+
this.modelsByWorkspace.delete(workspacePath);
|
|
305
|
+
this.contextLimitsByWorkspace.delete(workspacePath);
|
|
306
|
+
}, PROFILE_ENTRY_TTL_MS);
|
|
307
|
+
timer.unref();
|
|
308
|
+
this.profileTimersByWorkspace.set(workspacePath, timer);
|
|
309
|
+
}
|
|
310
|
+
clearProfileTimer(workspacePath) {
|
|
311
|
+
const timer = this.profileTimersByWorkspace.get(workspacePath);
|
|
312
|
+
if (timer !== undefined)
|
|
313
|
+
clearTimeout(timer);
|
|
314
|
+
this.profileTimersByWorkspace.delete(workspacePath);
|
|
315
|
+
}
|
|
291
316
|
managedClient(environment, isolationKey) {
|
|
292
317
|
const entries = Object.entries(environment).sort(([left], [right]) => left.localeCompare(right));
|
|
293
318
|
if (entries.length === 0 && isolationKey === undefined)
|