@wix/pathgrade 1.0.24 → 1.0.26
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 +42 -0
- package/dist/agents/claude/sdk-options.d.ts +1 -2
- package/dist/agents/claude.js +2 -1
- package/dist/agents/codex-app-server/agent.js +83 -46
- package/dist/agents/codex-app-server/scenario-mount.d.ts +40 -0
- package/dist/agents/codex-app-server/scenario-mount.js +238 -0
- package/dist/agents/cursor-scenario.d.ts +20 -0
- package/dist/agents/cursor-scenario.js +96 -0
- package/dist/agents/cursor.js +20 -0
- package/dist/agents/opencode/scenario.d.ts +25 -0
- package/dist/agents/opencode/scenario.js +195 -0
- package/dist/agents/opencode.js +50 -0
- package/dist/internal/direct-mcp-v2/acp-author-projector.d.ts +20 -0
- package/dist/internal/direct-mcp-v2/acp-author-projector.js +147 -0
- package/dist/internal/direct-mcp-v2/acp-author-session.d.ts +39 -0
- package/dist/internal/direct-mcp-v2/acp-author-session.js +201 -0
- package/dist/internal/direct-mcp-v2/acp-client.d.ts +70 -0
- package/dist/internal/direct-mcp-v2/acp-client.js +310 -0
- package/dist/internal/direct-mcp-v2/acp-direct-mcp.d.ts +38 -0
- package/dist/internal/direct-mcp-v2/acp-direct-mcp.js +326 -0
- package/dist/internal/direct-mcp-v2/claude-direct-mcp.d.ts +68 -0
- package/dist/internal/direct-mcp-v2/claude-direct-mcp.js +456 -0
- package/dist/internal/direct-mcp-v2/claude-profile.d.ts +9 -0
- package/dist/internal/direct-mcp-v2/claude-profile.js +97 -0
- package/dist/internal/direct-mcp-v2/cleanup.d.ts +62 -0
- package/dist/internal/direct-mcp-v2/cleanup.js +182 -0
- package/dist/internal/direct-mcp-v2/codex-direct-mcp.d.ts +41 -0
- package/dist/internal/direct-mcp-v2/codex-direct-mcp.js +436 -0
- package/dist/internal/direct-mcp-v2/codex-profile.d.ts +12 -0
- package/dist/internal/direct-mcp-v2/codex-profile.js +81 -0
- package/dist/internal/direct-mcp-v2/compiler.d.ts +12 -0
- package/dist/internal/direct-mcp-v2/compiler.js +449 -0
- package/dist/internal/direct-mcp-v2/cursor-direct-mcp.d.ts +14 -0
- package/dist/internal/direct-mcp-v2/cursor-direct-mcp.js +60 -0
- package/dist/internal/direct-mcp-v2/cursor-profile.d.ts +38 -0
- package/dist/internal/direct-mcp-v2/cursor-profile.js +79 -0
- package/dist/internal/direct-mcp-v2/evidence.d.ts +50 -0
- package/dist/internal/direct-mcp-v2/evidence.js +147 -0
- package/dist/internal/direct-mcp-v2/host-reducer.d.ts +61 -0
- package/dist/internal/direct-mcp-v2/host-reducer.js +214 -0
- package/dist/internal/direct-mcp-v2/json.d.ts +9 -0
- package/dist/internal/direct-mcp-v2/json.js +220 -0
- package/dist/internal/direct-mcp-v2/materialize.d.ts +39 -0
- package/dist/internal/direct-mcp-v2/materialize.js +22 -0
- package/dist/internal/direct-mcp-v2/opencode-direct-mcp.d.ts +14 -0
- package/dist/internal/direct-mcp-v2/opencode-direct-mcp.js +71 -0
- package/dist/internal/direct-mcp-v2/opencode-profile.d.ts +8 -0
- package/dist/internal/direct-mcp-v2/opencode-profile.js +52 -0
- package/dist/internal/direct-mcp-v2/projection.d.ts +40 -0
- package/dist/internal/direct-mcp-v2/projection.js +46 -0
- package/dist/internal/direct-mcp-v2/public-scenario-runtime.d.ts +51 -0
- package/dist/internal/direct-mcp-v2/public-scenario-runtime.js +353 -0
- package/dist/internal/direct-mcp-v2/runtime-lock.d.ts +51 -0
- package/dist/internal/direct-mcp-v2/runtime-lock.js +203 -0
- package/dist/internal/direct-mcp-v2/runtime-profile.d.ts +28 -0
- package/dist/internal/direct-mcp-v2/runtime-profile.js +163 -0
- package/dist/internal/direct-mcp-v2/scenario-http-host.d.ts +24 -0
- package/dist/internal/direct-mcp-v2/scenario-http-host.js +254 -0
- package/dist/internal/direct-mcp-v2/types.d.ts +233 -0
- package/dist/internal/direct-mcp-v2/types.js +1 -0
- package/dist/sdk/agent.js +57 -2
- package/dist/sdk/evaluate.js +6 -3
- package/dist/sdk/index.d.ts +4 -0
- package/dist/sdk/index.js +2 -0
- package/dist/sdk/managed-session.d.ts +6 -0
- package/dist/sdk/managed-session.js +157 -50
- package/dist/sdk/scenario-evidence.d.ts +27 -0
- package/dist/sdk/scenario-evidence.js +94 -0
- package/dist/sdk/scenario-machine-v2.d.ts +9 -0
- package/dist/sdk/scenario-machine-v2.js +37 -0
- package/dist/sdk/snapshots.d.ts +3 -0
- package/dist/sdk/snapshots.js +2 -0
- package/dist/sdk/types.d.ts +9 -1
- package/dist/types.d.ts +6 -0
- package/package.json +2 -2
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { attachAcpAuthorSession, } from '../internal/direct-mcp-v2/acp-author-session.js';
|
|
2
|
+
import { startScenarioCoordinator, } from '../internal/direct-mcp-v2/public-scenario-runtime.js';
|
|
3
|
+
import { CURSOR_ACP_AUTHOR_DARWIN_ARM64_LOCK, resolveCursorExecutable, } from '../internal/direct-mcp-v2/cursor-profile.js';
|
|
4
|
+
import { mapCursorProviderEnvironment } from '../internal/direct-mcp-v2/cursor-direct-mcp.js';
|
|
5
|
+
const scenarioSessions = new WeakMap();
|
|
6
|
+
export function setCursorScenarioSession(options, session) {
|
|
7
|
+
if (scenarioSessions.has(options))
|
|
8
|
+
throw new Error('Cursor scenario session is already configured');
|
|
9
|
+
scenarioSessions.set(options, session);
|
|
10
|
+
}
|
|
11
|
+
export function getCursorScenarioSession(options) {
|
|
12
|
+
return options ? scenarioSessions.get(options) : undefined;
|
|
13
|
+
}
|
|
14
|
+
/** Cursor's admitted ACP build exposes MCP calls only as the stable title `MCP: tool`. */
|
|
15
|
+
export const normalizeCursorAcpToolName = (title) => title === 'MCP: tool'
|
|
16
|
+
? { action: 'mcp_tool_call', providerToolName: 'cursor_mcp_tool' }
|
|
17
|
+
: { action: 'unknown', providerToolName: title };
|
|
18
|
+
const defaultDependencies = {
|
|
19
|
+
attachAcpAuthorSession,
|
|
20
|
+
resolveCursorExecutable,
|
|
21
|
+
startScenarioCoordinator,
|
|
22
|
+
};
|
|
23
|
+
export function createPublicCursorScenarioRuntimeFactory(factoryOptions = {}, dependencyOverrides = {}) {
|
|
24
|
+
if (factoryOptions.requestedModel !== undefined) {
|
|
25
|
+
throw new Error('Cursor Scenario V2 does not support model overrides until the ACP model field is admitted');
|
|
26
|
+
}
|
|
27
|
+
const deps = { ...defaultDependencies, ...dependencyOverrides };
|
|
28
|
+
return async function startPublicCursorScenarioRuntime(options) {
|
|
29
|
+
const environment = cursorScenarioEnvironment(options.environment);
|
|
30
|
+
const resolvedExecutable = await deps.resolveCursorExecutable(environment);
|
|
31
|
+
const coordinator = await deps.startScenarioCoordinator({
|
|
32
|
+
...options,
|
|
33
|
+
provider: 'cursor',
|
|
34
|
+
attach: async (context) => deps.attachAcpAuthorSession({
|
|
35
|
+
provider: 'cursor',
|
|
36
|
+
context,
|
|
37
|
+
runtimeLock: CURSOR_ACP_AUTHOR_DARWIN_ARM64_LOCK,
|
|
38
|
+
resolvedExecutable,
|
|
39
|
+
cwd: options.workspacePath,
|
|
40
|
+
environment,
|
|
41
|
+
scanRoots: cursorScanRoots(options.workspacePath, environment),
|
|
42
|
+
normalizeToolName: normalizeCursorAcpToolName,
|
|
43
|
+
}),
|
|
44
|
+
});
|
|
45
|
+
let configuredOptions;
|
|
46
|
+
return {
|
|
47
|
+
configureSession(sessionOptions) {
|
|
48
|
+
if (sessionOptions.model) {
|
|
49
|
+
throw new Error('Cursor Scenario V2 does not support model overrides until the ACP model field is admitted');
|
|
50
|
+
}
|
|
51
|
+
if (configuredOptions && configuredOptions !== sessionOptions) {
|
|
52
|
+
throw new Error('Cursor Scenario V2 runtime is already configured for another session');
|
|
53
|
+
}
|
|
54
|
+
configuredOptions = sessionOptions;
|
|
55
|
+
setCursorScenarioSession(sessionOptions, coordinator.attachment);
|
|
56
|
+
},
|
|
57
|
+
beginTurn: coordinator.beginTurn,
|
|
58
|
+
settleTurn: coordinator.settleTurn,
|
|
59
|
+
failTurn: coordinator.failTurn,
|
|
60
|
+
evidence: coordinator.evidence,
|
|
61
|
+
async dispose() {
|
|
62
|
+
try {
|
|
63
|
+
await coordinator.dispose();
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
if (configuredOptions)
|
|
67
|
+
scenarioSessions.delete(configuredOptions);
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export const startPublicCursorScenarioRuntime = createPublicCursorScenarioRuntimeFactory();
|
|
74
|
+
function cursorScenarioEnvironment(source) {
|
|
75
|
+
const environment = mapCursorProviderEnvironment(source);
|
|
76
|
+
for (const name of [
|
|
77
|
+
'HOME', 'TMPDIR', 'TMP', 'TEMP',
|
|
78
|
+
'XDG_CONFIG_HOME', 'XDG_DATA_HOME', 'XDG_CACHE_HOME',
|
|
79
|
+
]) {
|
|
80
|
+
if (source[name])
|
|
81
|
+
environment[name] = source[name];
|
|
82
|
+
}
|
|
83
|
+
return environment;
|
|
84
|
+
}
|
|
85
|
+
function cursorScanRoots(workspacePath, environment) {
|
|
86
|
+
return [...new Set([
|
|
87
|
+
workspacePath,
|
|
88
|
+
environment.HOME,
|
|
89
|
+
environment.TMPDIR,
|
|
90
|
+
environment.TMP,
|
|
91
|
+
environment.TEMP,
|
|
92
|
+
environment.XDG_CONFIG_HOME,
|
|
93
|
+
environment.XDG_DATA_HOME,
|
|
94
|
+
environment.XDG_CACHE_HOME,
|
|
95
|
+
].filter((path) => typeof path === 'string' && path.length > 0))];
|
|
96
|
+
}
|
package/dist/agents/cursor.js
CHANGED
|
@@ -2,6 +2,7 @@ import { BaseAgent, getWorkspacePath } from '../types.js';
|
|
|
2
2
|
import { TOOL_NAME_MAP, buildSummary, enrichSkillEvents } from '../tool-events.js';
|
|
3
3
|
import { getVisibleAssistantMessage } from '../sdk/visible-turn.js';
|
|
4
4
|
import { prependRuntimePolicies } from '../sdk/runtime-policy.js';
|
|
5
|
+
import { getCursorScenarioSession } from './cursor-scenario.js';
|
|
5
6
|
import { assertUnsupportedLiveMcpSafetyRuntime, mountMcpForCursor, } from '../providers/mcp-runtime-mounting.js';
|
|
6
7
|
/**
|
|
7
8
|
* Cursor tool_call discriminants observed in the discovery spike. Probed in
|
|
@@ -150,6 +151,25 @@ export function parseCursorStreamJson(stdout) {
|
|
|
150
151
|
const CURSOR_EXECUTABLE = 'cursor-agent';
|
|
151
152
|
export class CursorAgent extends BaseAgent {
|
|
152
153
|
async createSession(runtime, runCommand, options) {
|
|
154
|
+
const scenarioSession = getCursorScenarioSession(options);
|
|
155
|
+
if (scenarioSession) {
|
|
156
|
+
if (options?.model) {
|
|
157
|
+
throw new Error('Cursor Scenario V2 does not support model overrides until the ACP model field is admitted');
|
|
158
|
+
}
|
|
159
|
+
let turnNumber = 0;
|
|
160
|
+
const runScenarioTurn = async (message) => {
|
|
161
|
+
const appliedRuntimePolicies = turnNumber === 0 ? [...(options?.runtimePolicies ?? [])] : [];
|
|
162
|
+
const effectiveMessage = appliedRuntimePolicies.length > 0
|
|
163
|
+
? prependRuntimePolicies(message, appliedRuntimePolicies, { agent: 'cursor' })
|
|
164
|
+
: message;
|
|
165
|
+
const result = await scenarioSession.runTurn(effectiveMessage, ++turnNumber);
|
|
166
|
+
return { ...result, runtimePoliciesApplied: appliedRuntimePolicies };
|
|
167
|
+
};
|
|
168
|
+
return {
|
|
169
|
+
start: async ({ message }) => runScenarioTurn(message),
|
|
170
|
+
reply: async ({ message }) => runScenarioTurn(message),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
153
173
|
const workspacePath = getWorkspacePath(runtime);
|
|
154
174
|
let sessionId;
|
|
155
175
|
return {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { AgentSessionOptions, AgentTurnResult } from '../../types.js';
|
|
2
|
+
import { attachAcpAuthorSession, type AttachAcpAuthorSessionOptions } from '../../internal/direct-mcp-v2/acp-author-session.js';
|
|
3
|
+
import { type PublicScenarioRuntimeFactory } from '../../internal/direct-mcp-v2/public-scenario-runtime.js';
|
|
4
|
+
export type OpenCodeScenarioRuntimeFactoryOptions = {
|
|
5
|
+
opencodeExecutable: string;
|
|
6
|
+
requestedModel?: string;
|
|
7
|
+
};
|
|
8
|
+
type OpenCodeScenarioRuntimePolicy = {
|
|
9
|
+
readonly model: string;
|
|
10
|
+
readonly oauth: boolean;
|
|
11
|
+
environment(): Record<string, string>;
|
|
12
|
+
beforeTurn(remainingMs: number): Promise<void>;
|
|
13
|
+
afterTurn(): Promise<void>;
|
|
14
|
+
};
|
|
15
|
+
export type OpenCodeScenarioRuntimeDependencies = {
|
|
16
|
+
attachAuthorSession(options: AttachAcpAuthorSessionOptions): ReturnType<typeof attachAcpAuthorSession>;
|
|
17
|
+
createRuntimePolicy(environment: Record<string, string>, requestedModel?: string): Promise<OpenCodeScenarioRuntimePolicy>;
|
|
18
|
+
assertCleanManagedHost(): Promise<void>;
|
|
19
|
+
};
|
|
20
|
+
export type OpenCodeScenarioSession = {
|
|
21
|
+
runTurn(message: string): Promise<AgentTurnResult>;
|
|
22
|
+
};
|
|
23
|
+
export declare function getOpenCodeScenarioSession(options: AgentSessionOptions): OpenCodeScenarioSession | undefined;
|
|
24
|
+
export declare function createPublicOpenCodeScenarioRuntimeFactory(factoryOptions: OpenCodeScenarioRuntimeFactoryOptions, dependencyOverrides?: Partial<OpenCodeScenarioRuntimeDependencies>): PublicScenarioRuntimeFactory;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import { removeSandboxRoot } from '../../providers/sandbox-lifecycle.js';
|
|
4
|
+
import { attachAcpAuthorSession, } from '../../internal/direct-mcp-v2/acp-author-session.js';
|
|
5
|
+
import { startScenarioCoordinator, } from '../../internal/direct-mcp-v2/public-scenario-runtime.js';
|
|
6
|
+
import { OPENCODE_ACP_DARWIN_ARM64_RUNTIME_LOCK } from '../../internal/direct-mcp-v2/opencode-profile.js';
|
|
7
|
+
import { OPENCODE_PERMISSION, OpenCodeRuntimePolicy } from './runtime-policy.js';
|
|
8
|
+
import { assertCleanManagedOpenCodeHost } from './host-safety.js';
|
|
9
|
+
import { DEFAULT_OPENCODE_MODEL, sanitizeOpenCodeToolName } from './contract.js';
|
|
10
|
+
const defaultDependencies = {
|
|
11
|
+
attachAuthorSession: attachAcpAuthorSession,
|
|
12
|
+
createRuntimePolicy: OpenCodeRuntimePolicy.create,
|
|
13
|
+
assertCleanManagedHost: assertCleanManagedOpenCodeHost,
|
|
14
|
+
};
|
|
15
|
+
const configuredSessions = new WeakMap();
|
|
16
|
+
export function getOpenCodeScenarioSession(options) {
|
|
17
|
+
return configuredSessions.get(options);
|
|
18
|
+
}
|
|
19
|
+
export function createPublicOpenCodeScenarioRuntimeFactory(factoryOptions, dependencyOverrides = {}) {
|
|
20
|
+
if (factoryOptions.requestedModel !== undefined) {
|
|
21
|
+
throw new Error(`OpenCode Scenario V2 does not support model overrides; use the default ${DEFAULT_OPENCODE_MODEL} model.`);
|
|
22
|
+
}
|
|
23
|
+
const dependencies = { ...defaultDependencies, ...dependencyOverrides };
|
|
24
|
+
return async (options) => {
|
|
25
|
+
const environment = definedEnvironment(options.environment);
|
|
26
|
+
if (!environment.ANTHROPIC_API_KEY?.trim()) {
|
|
27
|
+
throw new Error(`OpenCode Scenario V2 currently requires API-key authentication using the default model ${DEFAULT_OPENCODE_MODEL}.`);
|
|
28
|
+
}
|
|
29
|
+
await dependencies.assertCleanManagedHost();
|
|
30
|
+
await assertNoProjectConfig(options.workspacePath);
|
|
31
|
+
const xdgDirectories = isolatedOpenCodeDirectories(environment);
|
|
32
|
+
await Promise.all(xdgDirectories.map((directory) => fs.ensureDir(directory)));
|
|
33
|
+
let coordinator;
|
|
34
|
+
try {
|
|
35
|
+
const runtimePolicy = await dependencies.createRuntimePolicy(environment);
|
|
36
|
+
if (runtimePolicy.oauth || runtimePolicy.model !== DEFAULT_OPENCODE_MODEL) {
|
|
37
|
+
throw new Error(`OpenCode Scenario V2 currently requires API-key authentication using the default model ${DEFAULT_OPENCODE_MODEL}.`);
|
|
38
|
+
}
|
|
39
|
+
const providerEnvironment = buildScenarioEnvironment(environment, runtimePolicy, xdgDirectories);
|
|
40
|
+
const mcpToolNames = scenarioToolNames(options.artifact);
|
|
41
|
+
coordinator = await startScenarioCoordinator({
|
|
42
|
+
artifact: options.artifact,
|
|
43
|
+
workspacePath: options.workspacePath,
|
|
44
|
+
environment: {
|
|
45
|
+
HOME: environment.HOME,
|
|
46
|
+
TMPDIR: path.dirname(options.workspacePath),
|
|
47
|
+
},
|
|
48
|
+
onEvidence: options.onEvidence,
|
|
49
|
+
provider: 'opencode',
|
|
50
|
+
attach: (context) => dependencies.attachAuthorSession({
|
|
51
|
+
provider: 'opencode',
|
|
52
|
+
context,
|
|
53
|
+
runtimeLock: OPENCODE_ACP_DARWIN_ARM64_RUNTIME_LOCK,
|
|
54
|
+
resolvedExecutable: factoryOptions.opencodeExecutable,
|
|
55
|
+
cwd: options.workspacePath,
|
|
56
|
+
environment: providerEnvironment,
|
|
57
|
+
scanRoots: scenarioScanRoots(options.workspacePath, providerEnvironment, xdgDirectories),
|
|
58
|
+
normalizeToolName: (title) => normalizeOpenCodeToolName(title, mcpToolNames),
|
|
59
|
+
}),
|
|
60
|
+
});
|
|
61
|
+
let configuredOptions;
|
|
62
|
+
let disposed = false;
|
|
63
|
+
let nextTurnNumber = 0;
|
|
64
|
+
const scenarioSession = {
|
|
65
|
+
async runTurn(message) {
|
|
66
|
+
if (disposed)
|
|
67
|
+
throw new Error('OpenCode Scenario V2 session is disposed');
|
|
68
|
+
const sessionOptions = configuredOptions;
|
|
69
|
+
sessionOptions?.getAbortSignal?.()?.throwIfAborted();
|
|
70
|
+
await runtimePolicy.beforeTurn(sessionOptions?.getRemainingMs?.() ?? 0);
|
|
71
|
+
try {
|
|
72
|
+
sessionOptions?.getAbortSignal?.()?.throwIfAborted();
|
|
73
|
+
return await coordinator.attachment.runTurn(message, ++nextTurnNumber);
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
await runtimePolicy.afterTurn();
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
const attachedCoordinator = coordinator;
|
|
81
|
+
return {
|
|
82
|
+
configureSession(sessionOptions) {
|
|
83
|
+
if (disposed)
|
|
84
|
+
throw new Error('OpenCode Scenario V2 runtime is disposed');
|
|
85
|
+
if (configuredOptions && configuredOptions !== sessionOptions) {
|
|
86
|
+
throw new Error('OpenCode Scenario V2 runtime is already configured for another session');
|
|
87
|
+
}
|
|
88
|
+
configuredOptions = sessionOptions;
|
|
89
|
+
configuredSessions.set(sessionOptions, scenarioSession);
|
|
90
|
+
},
|
|
91
|
+
beginTurn: attachedCoordinator.beginTurn,
|
|
92
|
+
settleTurn: attachedCoordinator.settleTurn,
|
|
93
|
+
failTurn: attachedCoordinator.failTurn,
|
|
94
|
+
evidence: attachedCoordinator.evidence,
|
|
95
|
+
async dispose() {
|
|
96
|
+
if (disposed)
|
|
97
|
+
return;
|
|
98
|
+
disposed = true;
|
|
99
|
+
const failures = [];
|
|
100
|
+
try {
|
|
101
|
+
await attachedCoordinator.dispose();
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
failures.push(error);
|
|
105
|
+
}
|
|
106
|
+
if (configuredOptions)
|
|
107
|
+
configuredSessions.delete(configuredOptions);
|
|
108
|
+
try {
|
|
109
|
+
await Promise.all(xdgDirectories.map((directory) => removeSandboxRoot(directory)));
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
failures.push(error);
|
|
113
|
+
}
|
|
114
|
+
if (failures.length > 0)
|
|
115
|
+
throw new AggregateError(failures, 'OpenCode Scenario V2 cleanup failed');
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
const cleanupErrors = [];
|
|
121
|
+
try {
|
|
122
|
+
await coordinator?.dispose();
|
|
123
|
+
}
|
|
124
|
+
catch (cleanupError) {
|
|
125
|
+
cleanupErrors.push(cleanupError);
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
await Promise.all(xdgDirectories.map((directory) => removeSandboxRoot(directory)));
|
|
129
|
+
}
|
|
130
|
+
catch (cleanupError) {
|
|
131
|
+
cleanupErrors.push(cleanupError);
|
|
132
|
+
}
|
|
133
|
+
if (cleanupErrors.length > 0) {
|
|
134
|
+
throw new AggregateError([error, ...cleanupErrors], 'OpenCode Scenario V2 setup and cleanup failed');
|
|
135
|
+
}
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function definedEnvironment(environment) {
|
|
141
|
+
return Object.fromEntries(Object.entries(environment).filter((entry) => entry[1] !== undefined));
|
|
142
|
+
}
|
|
143
|
+
function isolatedOpenCodeDirectories(environment) {
|
|
144
|
+
if (!environment.HOME)
|
|
145
|
+
throw new Error('OpenCode Scenario V2 requires a managed HOME');
|
|
146
|
+
return [
|
|
147
|
+
path.join(environment.HOME, '.local', 'share'),
|
|
148
|
+
path.join(environment.HOME, '.config'),
|
|
149
|
+
path.join(environment.HOME, '.local', 'state'),
|
|
150
|
+
path.join(environment.HOME, '.cache'),
|
|
151
|
+
];
|
|
152
|
+
}
|
|
153
|
+
function buildScenarioEnvironment(environment, runtimePolicy, directories) {
|
|
154
|
+
const [data, config, state, cache] = directories;
|
|
155
|
+
const result = {
|
|
156
|
+
...environment,
|
|
157
|
+
XDG_DATA_HOME: data,
|
|
158
|
+
XDG_CONFIG_HOME: config,
|
|
159
|
+
XDG_STATE_HOME: state,
|
|
160
|
+
XDG_CACHE_HOME: cache,
|
|
161
|
+
...runtimePolicy.environment(),
|
|
162
|
+
OPENCODE_PERMISSION,
|
|
163
|
+
};
|
|
164
|
+
delete result.OPENCODE_CONFIG_CONTENT;
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
async function assertNoProjectConfig(workspacePath) {
|
|
168
|
+
for (const name of ['opencode.json', 'opencode.jsonc', '.opencode']) {
|
|
169
|
+
if (await fs.pathExists(path.join(workspacePath, name))) {
|
|
170
|
+
throw new Error(`OpenCode workspace configuration is not allowed: ${name}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function scenarioToolNames(artifact) {
|
|
175
|
+
return new Set(Object.entries(artifact.canonicalIr.servers).flatMap(([serverId, server]) => Object.keys(server.tools).map((toolId) => `${sanitizeOpenCodeToolName(serverId)}_${sanitizeOpenCodeToolName(toolId)}`)));
|
|
176
|
+
}
|
|
177
|
+
function normalizeOpenCodeToolName(title, mcpToolNames) {
|
|
178
|
+
if (mcpToolNames.has(title))
|
|
179
|
+
return { action: 'mcp_tool_call', providerToolName: title };
|
|
180
|
+
const nativeActions = {
|
|
181
|
+
bash: 'run_shell', read: 'read_file', write: 'write_file', edit: 'edit_file',
|
|
182
|
+
grep: 'search_code', glob: 'list_files', list: 'list_files', skill: 'use_skill',
|
|
183
|
+
todowrite: 'update_todos', lsp: 'search_code',
|
|
184
|
+
};
|
|
185
|
+
const action = nativeActions[title.toLowerCase()];
|
|
186
|
+
return { action: action ?? 'unknown', providerToolName: title };
|
|
187
|
+
}
|
|
188
|
+
function scenarioScanRoots(workspacePath, environment, xdgDirectories) {
|
|
189
|
+
return [...new Set([
|
|
190
|
+
path.dirname(workspacePath),
|
|
191
|
+
workspacePath,
|
|
192
|
+
environment.HOME,
|
|
193
|
+
...xdgDirectories,
|
|
194
|
+
].filter((entry) => typeof entry === 'string' && entry.length > 0))];
|
|
195
|
+
}
|
package/dist/agents/opencode.js
CHANGED
|
@@ -13,6 +13,7 @@ import { currentOpenCodePlatformKey, OPENCODE_RUNTIME_LOCK, } from './opencode/c
|
|
|
13
13
|
import { OpenCodeRuntimePolicy, OPENCODE_PERMISSION } from './opencode/runtime-policy.js';
|
|
14
14
|
import { killOpenCodeProcessGroup, registerOpenCodeProcessGroup, unregisterOpenCodeProcessGroup, } from './opencode/process-groups.js';
|
|
15
15
|
import { assertCleanManagedOpenCodeHost, sha256File } from './opencode/host-safety.js';
|
|
16
|
+
import { getOpenCodeScenarioSession } from './opencode/scenario.js';
|
|
16
17
|
export { assertCleanManagedOpenCodeHost, managedOpenCodeConfigPaths } from './opencode/host-safety.js';
|
|
17
18
|
const OUTPUT_CAP_BYTES = 16 * 1024 * 1024;
|
|
18
19
|
const NATIVE_TOOL_ACTIONS = {
|
|
@@ -512,11 +513,60 @@ class OpenCodeSession {
|
|
|
512
513
|
await this.cleanupState();
|
|
513
514
|
}
|
|
514
515
|
}
|
|
516
|
+
class OpenCodeScenarioAgentSession {
|
|
517
|
+
scenario;
|
|
518
|
+
started = false;
|
|
519
|
+
running = false;
|
|
520
|
+
failed = false;
|
|
521
|
+
disposed = false;
|
|
522
|
+
constructor(scenario) {
|
|
523
|
+
this.scenario = scenario;
|
|
524
|
+
}
|
|
525
|
+
start({ message }) {
|
|
526
|
+
if (this.started)
|
|
527
|
+
return Promise.reject(new Error('OpenCode session has already started'));
|
|
528
|
+
this.started = true;
|
|
529
|
+
return this.execute(message);
|
|
530
|
+
}
|
|
531
|
+
reply({ message }) {
|
|
532
|
+
if (!this.started)
|
|
533
|
+
return Promise.reject(new Error('OpenCode session has not started'));
|
|
534
|
+
return this.execute(message);
|
|
535
|
+
}
|
|
536
|
+
async execute(message) {
|
|
537
|
+
if (this.disposed)
|
|
538
|
+
throw new Error('OpenCode session is disposed');
|
|
539
|
+
if (this.failed)
|
|
540
|
+
throw new Error('OpenCode session is unusable after a failed turn');
|
|
541
|
+
if (this.running)
|
|
542
|
+
throw new Error('OpenCode does not allow concurrent turns');
|
|
543
|
+
this.running = true;
|
|
544
|
+
try {
|
|
545
|
+
const result = await this.scenario.runTurn(message);
|
|
546
|
+
if (result.exitCode !== 0 && !/"stopReason"\s*:\s*"cancelled"/.test(result.rawOutput))
|
|
547
|
+
this.failed = true;
|
|
548
|
+
return result;
|
|
549
|
+
}
|
|
550
|
+
catch (error) {
|
|
551
|
+
this.failed = true;
|
|
552
|
+
throw error;
|
|
553
|
+
}
|
|
554
|
+
finally {
|
|
555
|
+
this.running = false;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
async dispose() {
|
|
559
|
+
this.disposed = true;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
515
562
|
export class OpenCodeAgent extends BaseAgent {
|
|
516
563
|
async createSession(runtime, _runCommand, options = {}) {
|
|
517
564
|
if (!options.opencodeExecutable) {
|
|
518
565
|
throw new Error('OpenCode requires opencodeExecutable');
|
|
519
566
|
}
|
|
567
|
+
const scenario = getOpenCodeScenarioSession(options);
|
|
568
|
+
if (scenario)
|
|
569
|
+
return new OpenCodeScenarioAgentSession(scenario);
|
|
520
570
|
return new OpenCodeSession(runtime, options);
|
|
521
571
|
}
|
|
522
572
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AgentTurnResult } from '../../types.js';
|
|
2
|
+
import { type ToolAction } from '../../tool-events.js';
|
|
3
|
+
import type { ProviderId } from './types.js';
|
|
4
|
+
import type { AcpPromptResult, JsonRpcMessage } from './acp-client.js';
|
|
5
|
+
export declare const ACP_AUTHOR_TRACE_MAX_CHARS: number;
|
|
6
|
+
export type AcpToolName = {
|
|
7
|
+
action: ToolAction;
|
|
8
|
+
providerToolName: string;
|
|
9
|
+
};
|
|
10
|
+
export type AcpToolNameNormalizer = (title: string) => AcpToolName;
|
|
11
|
+
export type AcpAuthorProjectionInput = {
|
|
12
|
+
provider: Extract<ProviderId, 'cursor' | 'opencode'>;
|
|
13
|
+
terminal: AcpPromptResult;
|
|
14
|
+
notifications: readonly JsonRpcMessage[];
|
|
15
|
+
normalizeToolName(title: string): AcpToolName;
|
|
16
|
+
protectedValues?: readonly string[];
|
|
17
|
+
sensitiveValues?: readonly string[];
|
|
18
|
+
turnNumber?: number;
|
|
19
|
+
};
|
|
20
|
+
export declare function projectAcpAuthorTurn(input: AcpAuthorProjectionInput): AgentTurnResult;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { buildSummary } from '../../tool-events.js';
|
|
2
|
+
import { sanitizePersistenceValue, sanitizeToolEventResult } from '../../tool-event-results.js';
|
|
3
|
+
export const ACP_AUTHOR_TRACE_MAX_CHARS = 64 * 1024;
|
|
4
|
+
function object(value) {
|
|
5
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
6
|
+
}
|
|
7
|
+
function updateFromNotification(message) {
|
|
8
|
+
if (message.method !== 'session/update')
|
|
9
|
+
throw new Error('ACP author projector received a non-update notification');
|
|
10
|
+
const params = object(message.params);
|
|
11
|
+
const update = object(params?.update);
|
|
12
|
+
if (!update || typeof update.sessionUpdate !== 'string')
|
|
13
|
+
throw new Error('ACP session/update contained no typed update');
|
|
14
|
+
return update;
|
|
15
|
+
}
|
|
16
|
+
function toolUpdate(update, priorTitle) {
|
|
17
|
+
if (update.sessionUpdate !== 'tool_call' && update.sessionUpdate !== 'tool_call_update')
|
|
18
|
+
throw new Error('ACP update was not a tool update');
|
|
19
|
+
const title = typeof update.title === 'string' && update.title.length > 0 ? update.title : priorTitle;
|
|
20
|
+
if (!title)
|
|
21
|
+
throw new Error('ACP tool update contained no title');
|
|
22
|
+
const rawInput = update.rawInput === undefined ? undefined : object(update.rawInput);
|
|
23
|
+
if (update.rawInput !== undefined && !rawInput)
|
|
24
|
+
throw new Error('ACP tool update rawInput was not an object');
|
|
25
|
+
const rawOutput = update.rawOutput === undefined ? undefined : object(update.rawOutput);
|
|
26
|
+
if (update.rawOutput !== undefined && !rawOutput)
|
|
27
|
+
throw new Error('ACP tool update rawOutput was not an object');
|
|
28
|
+
const output = update.output ?? rawOutput?.output;
|
|
29
|
+
const error = update.error ?? rawOutput?.error;
|
|
30
|
+
return {
|
|
31
|
+
sessionUpdate: update.sessionUpdate,
|
|
32
|
+
title,
|
|
33
|
+
...(typeof update.toolCallId === 'string' ? { toolCallId: update.toolCallId } : {}),
|
|
34
|
+
...(typeof update.status === 'string' ? { status: update.status } : {}),
|
|
35
|
+
...(rawInput ? { rawInput } : {}),
|
|
36
|
+
...(output !== undefined ? { output } : {}),
|
|
37
|
+
...(error !== undefined ? { error } : {}),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function textValue(value) {
|
|
41
|
+
if (value === undefined)
|
|
42
|
+
return undefined;
|
|
43
|
+
return typeof value === 'string' ? value : JSON.stringify(value);
|
|
44
|
+
}
|
|
45
|
+
export function projectAcpAuthorTurn(input) {
|
|
46
|
+
const protectedValues = input.protectedValues ?? [];
|
|
47
|
+
const redactionValues = [...new Set([...protectedValues, ...(input.sensitiveValues ?? [])])];
|
|
48
|
+
const serialized = JSON.stringify({ terminal: input.terminal, notifications: input.notifications });
|
|
49
|
+
if (protectedValues.some((value) => value.length > 0 && serialized.includes(value))) {
|
|
50
|
+
throw new Error('ACP provider reflected a protected control-plane value');
|
|
51
|
+
}
|
|
52
|
+
const assistantChunks = [];
|
|
53
|
+
const toolEvents = [];
|
|
54
|
+
const trace = [];
|
|
55
|
+
const toolCalls = new Map();
|
|
56
|
+
let providerError = false;
|
|
57
|
+
for (const notification of input.notifications) {
|
|
58
|
+
const update = updateFromNotification(notification);
|
|
59
|
+
if (update.sessionUpdate === 'agent_message_chunk') {
|
|
60
|
+
const content = object(update.content);
|
|
61
|
+
const text = typeof update.text === 'string'
|
|
62
|
+
? update.text
|
|
63
|
+
: typeof content?.text === 'string' ? content.text : undefined;
|
|
64
|
+
if (text === undefined)
|
|
65
|
+
throw new Error('ACP agent message chunk contained no text');
|
|
66
|
+
assistantChunks.push(text);
|
|
67
|
+
trace.push({ sessionUpdate: 'agent_message_chunk', text });
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (update.sessionUpdate === 'error') {
|
|
71
|
+
if (typeof update.message !== 'string' || update.message.length === 0)
|
|
72
|
+
throw new Error('ACP error update contained no message');
|
|
73
|
+
providerError = true;
|
|
74
|
+
trace.push({ sessionUpdate: 'error', message: update.message });
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (update.sessionUpdate !== 'tool_call' && update.sessionUpdate !== 'tool_call_update') {
|
|
78
|
+
trace.push({ sessionUpdate: update.sessionUpdate });
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const toolCallId = typeof update.toolCallId === 'string' ? update.toolCallId : undefined;
|
|
82
|
+
const prior = toolCallId ? toolCalls.get(toolCallId) : undefined;
|
|
83
|
+
const current = toolUpdate(update, prior?.title);
|
|
84
|
+
if (prior && prior.title !== current.title)
|
|
85
|
+
throw new Error('ACP tool update changed title for one call');
|
|
86
|
+
const correlated = {
|
|
87
|
+
...prior,
|
|
88
|
+
...current,
|
|
89
|
+
...(current.rawInput ?? prior?.rawInput ? { rawInput: current.rawInput ?? prior?.rawInput } : {}),
|
|
90
|
+
};
|
|
91
|
+
trace.push({
|
|
92
|
+
sessionUpdate: current.sessionUpdate,
|
|
93
|
+
title: current.title,
|
|
94
|
+
...(current.status ? { status: current.status } : {}),
|
|
95
|
+
...(current.rawInput ? { rawInput: current.rawInput } : {}),
|
|
96
|
+
});
|
|
97
|
+
if (current.status !== 'completed' && current.status !== 'failed') {
|
|
98
|
+
if (current.toolCallId)
|
|
99
|
+
toolCalls.set(current.toolCallId, correlated);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const terminalFingerprint = JSON.stringify({
|
|
103
|
+
status: correlated.status,
|
|
104
|
+
rawInput: correlated.rawInput,
|
|
105
|
+
output: correlated.output,
|
|
106
|
+
error: correlated.error,
|
|
107
|
+
});
|
|
108
|
+
if (prior?.terminalFingerprint !== undefined) {
|
|
109
|
+
if (prior.terminalFingerprint !== terminalFingerprint)
|
|
110
|
+
throw new Error('ACP tool call emitted conflicting terminal updates');
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (current.toolCallId)
|
|
114
|
+
toolCalls.set(current.toolCallId, { ...correlated, terminalFingerprint });
|
|
115
|
+
const normalized = input.normalizeToolName(correlated.title);
|
|
116
|
+
const resultText = textValue(correlated.status === 'completed' ? correlated.output : correlated.error);
|
|
117
|
+
toolEvents.push({
|
|
118
|
+
action: normalized.action,
|
|
119
|
+
provider: input.provider,
|
|
120
|
+
providerToolName: normalized.providerToolName,
|
|
121
|
+
...(input.turnNumber !== undefined ? { turnNumber: input.turnNumber } : {}),
|
|
122
|
+
...(correlated.rawInput ? { arguments: correlated.rawInput } : {}),
|
|
123
|
+
status: correlated.status === 'completed' ? 'completed' : 'error',
|
|
124
|
+
...(resultText !== undefined ? { result: sanitizeToolEventResult({ content: resultText }, redactionValues) } : {}),
|
|
125
|
+
summary: buildSummary(normalized.action, normalized.providerToolName, correlated.rawInput),
|
|
126
|
+
confidence: normalized.action === 'unknown' ? 'low' : 'high',
|
|
127
|
+
rawSnippet: JSON.stringify(sanitizePersistenceValue({ title: normalized.providerToolName, status: correlated.status, rawInput: correlated.rawInput }, redactionValues)).slice(0, 2_000),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
const assistantMessage = sanitizePersistenceValue(assistantChunks.join(''), redactionValues);
|
|
131
|
+
const rawTrace = JSON.stringify(sanitizePersistenceValue({
|
|
132
|
+
terminal: { stopReason: input.terminal.stopReason },
|
|
133
|
+
updates: trace,
|
|
134
|
+
}, redactionValues));
|
|
135
|
+
const traceOutput = rawTrace.length > ACP_AUTHOR_TRACE_MAX_CHARS
|
|
136
|
+
? `${rawTrace.slice(0, ACP_AUTHOR_TRACE_MAX_CHARS - 1)}…`
|
|
137
|
+
: rawTrace;
|
|
138
|
+
return sanitizePersistenceValue({
|
|
139
|
+
rawOutput: traceOutput,
|
|
140
|
+
traceOutput,
|
|
141
|
+
assistantMessage,
|
|
142
|
+
visibleAssistantMessage: assistantMessage,
|
|
143
|
+
visibleAssistantMessageSource: 'assistant_message',
|
|
144
|
+
exitCode: input.terminal.stopReason === 'end_turn' && !providerError ? 0 : 1,
|
|
145
|
+
toolEvents,
|
|
146
|
+
}, redactionValues);
|
|
147
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { AgentTurnResult } from '../../types.js';
|
|
2
|
+
import { type RuntimeLock } from './runtime-lock.js';
|
|
3
|
+
import { type AcpClient, type SpawnAcpClientOptions } from './acp-client.js';
|
|
4
|
+
import { type AcpToolNameNormalizer } from './acp-author-projector.js';
|
|
5
|
+
import type { JsonObject, ProviderId, ScenarioProviderAttachContext, ScenarioProviderAttachment } from './types.js';
|
|
6
|
+
export type AcpAuthorSession = {
|
|
7
|
+
runTurn(message: string, turnNumber?: number): Promise<AgentTurnResult>;
|
|
8
|
+
interrupt(reason?: string): Promise<void>;
|
|
9
|
+
waitForTerminal(): Promise<void>;
|
|
10
|
+
close(): Promise<void>;
|
|
11
|
+
};
|
|
12
|
+
export type CreateAcpAuthorSessionOptions = {
|
|
13
|
+
provider: Extract<ProviderId, 'cursor' | 'opencode'>;
|
|
14
|
+
client: AcpClient;
|
|
15
|
+
mcpServers: readonly JsonObject[];
|
|
16
|
+
normalizeToolName: AcpToolNameNormalizer;
|
|
17
|
+
protectedValues?: readonly string[];
|
|
18
|
+
sensitiveValues?: readonly string[];
|
|
19
|
+
assertProtectedSurfacesClean?(): Promise<void>;
|
|
20
|
+
closeJoinTimeoutMs?: number;
|
|
21
|
+
};
|
|
22
|
+
export type AttachAcpAuthorSessionOptions = {
|
|
23
|
+
provider: Extract<ProviderId, 'cursor' | 'opencode'>;
|
|
24
|
+
context: ScenarioProviderAttachContext;
|
|
25
|
+
runtimeLock: RuntimeLock;
|
|
26
|
+
resolvedExecutable: string;
|
|
27
|
+
cwd: string;
|
|
28
|
+
environment: NodeJS.ProcessEnv;
|
|
29
|
+
scanRoots: readonly string[];
|
|
30
|
+
normalizeToolName: AcpToolNameNormalizer;
|
|
31
|
+
createClient?: (options: SpawnAcpClientOptions) => AcpClient;
|
|
32
|
+
};
|
|
33
|
+
export declare function createAcpAuthorSession(options: CreateAcpAuthorSessionOptions): Promise<AcpAuthorSession>;
|
|
34
|
+
export declare function createAcpScenarioAttachment(session: AcpAuthorSession): ScenarioProviderAttachment<AcpAuthorSession>;
|
|
35
|
+
/**
|
|
36
|
+
* P4/P5 provider seam: attach one exact, relocatable ACP runtime to the
|
|
37
|
+
* coordinator and return the reusable author session as its opaque value.
|
|
38
|
+
*/
|
|
39
|
+
export declare function attachAcpAuthorSession(options: AttachAcpAuthorSessionOptions): Promise<ScenarioProviderAttachment<AcpAuthorSession>>;
|