@atolis-hq/wake 0.4.48 → 0.4.50
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 +3 -0
- package/dist/src/activities/agent/agent-activity.js +9 -3
- package/dist/src/activities/contracts/activity.d.ts +14 -0
- package/dist/src/artifacts/application/artifact-projection.d.ts +3 -0
- package/dist/src/artifacts/application/artifact-projection.js +15 -0
- package/dist/src/artifacts/application/artifact-repository.d.ts +13 -0
- package/dist/src/artifacts/application/artifact-repository.js +19 -0
- package/dist/src/artifacts/application/artifact-service.d.ts +40 -0
- package/dist/src/artifacts/application/artifact-service.js +128 -0
- package/dist/src/artifacts/contracts/config.d.ts +6 -0
- package/dist/src/artifacts/contracts/config.js +19 -0
- package/dist/src/artifacts/contracts/event-factory.d.ts +6 -0
- package/dist/src/artifacts/contracts/event-factory.js +10 -0
- package/dist/src/artifacts/contracts/events.d.ts +33 -0
- package/dist/src/artifacts/contracts/events.js +59 -0
- package/dist/src/artifacts/contracts/identifiers.d.ts +5 -0
- package/dist/src/artifacts/contracts/identifiers.js +8 -0
- package/dist/src/artifacts/contracts/paths.d.ts +5 -0
- package/dist/src/artifacts/contracts/paths.js +9 -0
- package/dist/src/artifacts/contracts/streams.d.ts +7 -0
- package/dist/src/artifacts/contracts/streams.js +4 -0
- package/dist/src/artifacts/contracts/views.d.ts +20 -0
- package/dist/src/artifacts/contracts/views.js +1 -0
- package/dist/src/artifacts/domain/artifact-work-item.d.ts +4 -0
- package/dist/src/artifacts/domain/artifact-work-item.js +42 -0
- package/dist/src/artifacts/index.d.ts +14 -0
- package/dist/src/artifacts/index.js +14 -0
- package/dist/src/artifacts/infrastructure/file-artifact-store.d.ts +22 -0
- package/dist/src/artifacts/infrastructure/file-artifact-store.js +51 -0
- package/dist/src/artifacts/infrastructure/mcp-server.d.ts +18 -0
- package/dist/src/artifacts/infrastructure/mcp-server.js +135 -0
- package/dist/src/artifacts/infrastructure/mcp-session-store.d.ts +25 -0
- package/dist/src/artifacts/infrastructure/mcp-session-store.js +61 -0
- package/dist/src/bootstrap/artifact-mcp.d.ts +23 -0
- package/dist/src/bootstrap/artifact-mcp.js +119 -0
- package/dist/src/bootstrap/composition-root.d.ts +2 -0
- package/dist/src/bootstrap/composition-root.js +19 -0
- package/dist/src/bootstrap/config/root-schema.d.ts +6 -0
- package/dist/src/bootstrap/config/root-schema.js +5 -0
- package/dist/src/bootstrap/paths.d.ts +1 -0
- package/dist/src/bootstrap/paths.js +1 -0
- package/dist/src/bootstrap/projection-runtime.js +2 -0
- package/dist/src/bootstrap/runner-registry.js +22 -4
- package/dist/src/bootstrap/surface-api-work-applications.js +39 -1
- package/dist/src/bootstrap/surface-cli-applications.js +12 -1
- package/dist/src/bootstrap/version.js +1 -1
- package/dist/src/execution/application/execution-activity.d.ts +11 -0
- package/dist/src/execution/application/execution-activity.js +26 -10
- package/dist/src/execution/application/execution-service.js +1 -1
- package/dist/src/execution/application/execution-validation.js +17 -3
- package/dist/src/execution/contracts/runner.d.ts +10 -0
- package/dist/src/execution/contracts/workspace.d.ts +11 -1
- package/dist/src/execution/infrastructure/runners/claude.d.ts +1 -0
- package/dist/src/execution/infrastructure/runners/claude.js +21 -0
- package/dist/src/execution/infrastructure/runners/codex.js +12 -0
- package/dist/src/execution/infrastructure/runners/fake.d.ts +1 -0
- package/dist/src/execution/infrastructure/runners/fake.js +1 -0
- package/dist/src/execution/infrastructure/runners/registry.d.ts +3 -1
- package/dist/src/execution/infrastructure/runners/registry.js +5 -3
- package/dist/src/execution/infrastructure/workspace/git-workspace.d.ts +10 -5
- package/dist/src/execution/infrastructure/workspace/git-workspace.js +270 -38
- package/dist/src/surfaces/api/contracts/work.d.ts +10 -0
- package/dist/src/surfaces/api/routes/applications.d.ts +5 -0
- package/dist/src/surfaces/api/routes/read.js +34 -3
- package/dist/src/surfaces/api/routes/work.d.ts +1 -1
- package/dist/src/surfaces/api/routes/work.js +1 -0
- package/dist/src/surfaces/cli/main.d.ts +8 -0
- package/dist/src/surfaces/cli/main.js +31 -0
- package/dist/src/surfaces/cli/usage.js +1 -0
- package/dist/src/surfaces/index.d.ts +1 -0
- package/dist/src/surfaces/index.js +1 -0
- package/dist/src/surfaces/mcp/stdio.d.ts +3 -0
- package/dist/src/surfaces/mcp/stdio.js +5 -0
- package/dist/src/surfaces/web-assets/assets/{index-CuAZYm7r.js → index-CuIiUKuh.js} +2 -2
- package/dist/src/surfaces/web-assets/index.html +1 -1
- package/package.json +3 -1
|
@@ -4,7 +4,7 @@ import { conversationIdForWorkItem, ConversationOriginKind, } from '../conversat
|
|
|
4
4
|
import { executionProjection, runsByWorkflowInstanceProjection, } from '../execution/index.js';
|
|
5
5
|
import { ApprovalAuthorityKind, ConversationSurfaceCapability, GroupBudgetExtensionIneligibleError, isGroupBudgetExtensionEligible, OperatorRetryIneligibleError, orchestrationProjection, selectOperatorRetryTarget, workflowsByWorkItemProjection, } from '../orchestration/index.js';
|
|
6
6
|
import { workCorrelationsProjection, } from '../resources/index.js';
|
|
7
|
-
import { ApiCommandStatus, fromWorkItemKey, presentResource, presentRun, presentWorkflowInstance, presentWorkItem, } from '../surfaces/index.js';
|
|
7
|
+
import { ApiCommandStatus, fromWorkItemKey, presentResource, presentRun, presentWorkflowInstance, presentWorkItem, toWorkItemKey, } from '../surfaces/index.js';
|
|
8
8
|
import { workItemId, WorkStatus } from '../work/index.js';
|
|
9
9
|
import { primaryExternalRef } from './external-ref.js';
|
|
10
10
|
import { projectionMeta } from './surface-api-metadata.js';
|
|
@@ -29,6 +29,20 @@ export function createSurfaceWorkApplications(root, now) {
|
|
|
29
29
|
});
|
|
30
30
|
},
|
|
31
31
|
detail: (key) => workDetail(root, key, now),
|
|
32
|
+
async artifact(key, revisionId) {
|
|
33
|
+
const id = decodeWorkItemId(key);
|
|
34
|
+
if (id === undefined)
|
|
35
|
+
return undefined;
|
|
36
|
+
const accepted = new Set((await root.orchestration.listForWorkItem(id)).flatMap((workflow) => workflow.acceptedOutcomes));
|
|
37
|
+
const artifact = await root.artifacts.readAcceptedRevision(id, revisionId, async (activationId) => accepted.has(activationId));
|
|
38
|
+
if (artifact === null)
|
|
39
|
+
return undefined;
|
|
40
|
+
return {
|
|
41
|
+
bytes: artifact.bytes,
|
|
42
|
+
mediaType: safeMediaType(artifact.revision.mediaType),
|
|
43
|
+
filename: artifact.revision.path.split('/').at(-1) ?? 'artifact',
|
|
44
|
+
};
|
|
45
|
+
},
|
|
32
46
|
async transcript(key, groupId) {
|
|
33
47
|
const id = decodeWorkItemId(key);
|
|
34
48
|
if (id === undefined)
|
|
@@ -197,6 +211,7 @@ async function workDetail(root, key, now) {
|
|
|
197
211
|
transcriptGroups: await transcriptGroups(root.transcriptStore, id, runs),
|
|
198
212
|
},
|
|
199
213
|
activities: presentPullRequest(pullRequest?.value),
|
|
214
|
+
artifacts: await presentArtifacts(root, id),
|
|
200
215
|
conversation: presentConversation(conversation, root.config?.surfaces.api.conversationMessages.enabled === true),
|
|
201
216
|
};
|
|
202
217
|
const [projections, correlationFacts] = await Promise.all([
|
|
@@ -208,6 +223,29 @@ async function workDetail(root, key, now) {
|
|
|
208
223
|
meta: await projectionMeta(root.journal, [...projections, ...correlationFacts, ...(pullRequest == null ? [] : [pullRequest])], now()),
|
|
209
224
|
};
|
|
210
225
|
}
|
|
226
|
+
async function presentArtifacts(root, id) {
|
|
227
|
+
const partial = root;
|
|
228
|
+
if (partial.artifacts === undefined || partial.orchestration?.listAll === undefined)
|
|
229
|
+
return [];
|
|
230
|
+
const accepted = new Set((await partial.orchestration.listAll())
|
|
231
|
+
.filter((workflow) => workflow.workItemId === id)
|
|
232
|
+
.flatMap((workflow) => workflow.acceptedOutcomes));
|
|
233
|
+
return (await partial.artifacts.latestPublished(id, async (activationId) => accepted.has(activationId))).map((artifact) => ({
|
|
234
|
+
revisionId: artifact.revisionId,
|
|
235
|
+
producer: artifact.producer,
|
|
236
|
+
path: artifact.path,
|
|
237
|
+
occurredAt: artifact.occurredAt,
|
|
238
|
+
...(artifact.mediaType === undefined ? {} : { mediaType: artifact.mediaType }),
|
|
239
|
+
...(artifact.byteLength === undefined ? {} : { byteLength: artifact.byteLength }),
|
|
240
|
+
...(artifact.digest === undefined ? {} : { digest: artifact.digest }),
|
|
241
|
+
href: `/api/v1/work-items/${encodeURIComponent(toWorkItemKey(id))}/artifacts/${encodeURIComponent(artifact.revisionId)}`,
|
|
242
|
+
}));
|
|
243
|
+
}
|
|
244
|
+
function safeMediaType(value) {
|
|
245
|
+
return value !== undefined && /^[^\r\n]+\/[^\r\n]+$/.test(value)
|
|
246
|
+
? value
|
|
247
|
+
: 'application/octet-stream';
|
|
248
|
+
}
|
|
211
249
|
async function conversationForWorkItem(root, id) {
|
|
212
250
|
// Narrow test/application seams may provide a partial composition root while
|
|
213
251
|
// the production root always composes Conversations.
|
|
@@ -6,11 +6,14 @@ import { join } from 'node:path';
|
|
|
6
6
|
import { createInterface } from 'node:readline/promises';
|
|
7
7
|
import { promisify } from 'node:util';
|
|
8
8
|
import { BuiltInActivityName, agentActivityDefinition } from '../activities/index.js';
|
|
9
|
+
import { FileArtifactMcpSessionStore } from '../artifacts/index.js';
|
|
9
10
|
import { IntakeHost, ResidentHost, TickHost, } from '../control-plane/index.js';
|
|
10
11
|
import { ExecutionFailureCode, RunStatus, isActiveRunStatus, loadPromptTemplate, } from '../execution/index.js';
|
|
12
|
+
import { UlidIdGenerator } from '../kernel/index.js';
|
|
11
13
|
import { ResourceCorrelationRole, resourceId } from '../resources/index.js';
|
|
12
|
-
import { DockerProcessError, createApiDispatcher, createLoggedDockerCli, createPackagedAssetSource, createPairingGrant, createProcessLogSink, createSandboxDockerPort, createSurfaceHttpServer, describeSandboxStartupFailure, drainProcessOutput, loadOrCreateCredentials, promoteSandboxImage, redeemPairingGrant, replaceAccessKey, runDoctor, runSandbox, runSandboxEntrypoint, runSandboxSetup, runSelfUpdateLatestLoop, runTargetSmoke, verifyResidentStart, waitForActiveRuns, waitForever, } from '../surfaces/index.js';
|
|
14
|
+
import { DockerProcessError, createApiDispatcher, createLoggedDockerCli, createPackagedAssetSource, createPairingGrant, createProcessLogSink, createSandboxDockerPort, createSurfaceHttpServer, describeSandboxStartupFailure, drainProcessOutput, loadOrCreateCredentials, promoteSandboxImage, redeemPairingGrant, replaceAccessKey, runDoctor, runSandbox, runSandboxEntrypoint, runSandboxSetup, runSelfUpdateLatestLoop, runTargetSmoke, serveMcpOverStdio, verifyResidentStart, waitForActiveRuns, waitForever, } from '../surfaces/index.js';
|
|
13
15
|
import { WorkStreamKind, workItemId } from '../work/index.js';
|
|
16
|
+
import { createArtifactMcpServerForSession } from './artifact-mcp.js';
|
|
14
17
|
import { loadConfig } from './config/load-config.js';
|
|
15
18
|
import { createNpmUpdatePort } from './npm-update-port.js';
|
|
16
19
|
import { runtimeProjectionDefinitions } from './projection-runtime.js';
|
|
@@ -24,6 +27,8 @@ import { UpdateMaintenancePhase } from './update-maintenance-lease.js';
|
|
|
24
27
|
import { resolveWakeVersion, wakePackageVersion } from './version.js';
|
|
25
28
|
const execFile = promisify(nodeExecFile);
|
|
26
29
|
export function createSurfaceCliApplications(root, api, now) {
|
|
30
|
+
const artifactMcpSessions = new FileArtifactMcpSessionStore(root.paths.artifactsRoot, now);
|
|
31
|
+
const artifactMcpIds = new UlidIdGenerator();
|
|
27
32
|
const runnerTick = new TickHost(createOneShotRunnerAdvance(root));
|
|
28
33
|
const runnerResidentTick = new TickHost(createResidentRunnerAdvance(root));
|
|
29
34
|
const intakeHost = new IntakeHost((signal) => root.intakePipeline.run(signal));
|
|
@@ -84,6 +89,12 @@ export function createSurfaceCliApplications(root, api, now) {
|
|
|
84
89
|
},
|
|
85
90
|
api: { start: (options) => startHttp(options, false) },
|
|
86
91
|
ui: { start: (options) => startHttp(options, true) },
|
|
92
|
+
mcp: {
|
|
93
|
+
async serve(sessionPath, token) {
|
|
94
|
+
const server = await createArtifactMcpServerForSession(root, artifactMcpSessions, sessionPath, token, { now, nextId: () => artifactMcpIds.next('artifact') });
|
|
95
|
+
await serveMcpOverStdio(server);
|
|
96
|
+
},
|
|
97
|
+
},
|
|
87
98
|
auth: {
|
|
88
99
|
async token(accessKey) {
|
|
89
100
|
if (root.config.surfaces.web.auth.disabled)
|
|
@@ -124,6 +124,6 @@ export function resolveWakeVersion(options = {}) {
|
|
|
124
124
|
return `g${headHash.slice(0, 7)}`;
|
|
125
125
|
return '0.1.0-dev';
|
|
126
126
|
}
|
|
127
|
-
export const wakeVersion = "v0.4.
|
|
127
|
+
export const wakeVersion = "v0.4.50";
|
|
128
128
|
/** The registry-installable version used by packaged sandbox images. */
|
|
129
129
|
export const wakePackageVersion = packageVersion(defaultRepoRoot) ?? wakeVersion;
|
|
@@ -4,6 +4,7 @@ import { type Clock, type IdGenerator } from '../../kernel/index.js';
|
|
|
4
4
|
import type { ExecutionActivation, ExecutionAttemptContext } from '../contracts/commands.js';
|
|
5
5
|
import type { ExecutionConfig } from '../contracts/config.js';
|
|
6
6
|
import type { runId } from '../contracts/identifiers.js';
|
|
7
|
+
import { type RunnerMcpServer } from '../contracts/runner.js';
|
|
7
8
|
import type { WorkspaceProvider } from '../contracts/workspace.js';
|
|
8
9
|
import type { RunnerRegistry } from '../infrastructure/runners/registry.js';
|
|
9
10
|
import type { RunRepository } from './run-repository.js';
|
|
@@ -12,6 +13,11 @@ export interface ExecutionDependencies {
|
|
|
12
13
|
readonly ids: IdGenerator;
|
|
13
14
|
readonly workspaces?: WorkspaceProvider;
|
|
14
15
|
readonly runners?: RunnerRegistry;
|
|
16
|
+
readonly mcp?: (input: {
|
|
17
|
+
readonly runId: string;
|
|
18
|
+
readonly activation: ExecutionActivation;
|
|
19
|
+
readonly context: ExecutionAttemptContext;
|
|
20
|
+
}) => Promise<ExecutionMcpConfiguration>;
|
|
15
21
|
readonly transcriptRecorder?: ActivityExecutionContext['transcriptRecorder'];
|
|
16
22
|
readonly logOperationalError?: ActivityExecutionContext['logOperationalError'];
|
|
17
23
|
readonly reportRunnerQuota?: (input: {
|
|
@@ -19,6 +25,11 @@ export interface ExecutionDependencies {
|
|
|
19
25
|
readonly message: string;
|
|
20
26
|
}) => Promise<void>;
|
|
21
27
|
}
|
|
28
|
+
export interface ExecutionMcpConfiguration {
|
|
29
|
+
readonly servers: readonly RunnerMcpServer[];
|
|
30
|
+
readonly prompt?: string;
|
|
31
|
+
release(): Promise<void>;
|
|
32
|
+
}
|
|
22
33
|
export interface ExecutionRuntime {
|
|
23
34
|
readonly activities: ActivityRegistry;
|
|
24
35
|
readonly config: ExecutionConfig;
|
|
@@ -4,10 +4,15 @@ import { ExecutionCancellationReason } from '../contracts/vocabulary.js';
|
|
|
4
4
|
import { parseAgentRunnerResponse } from '../infrastructure/agent-runner-adapter.js';
|
|
5
5
|
import { createRunEvent } from './run-lifecycle.js';
|
|
6
6
|
import { confirmCancellation, requestCancellation } from './run-liveness-service.js';
|
|
7
|
+
// Execution context assembly intentionally keeps every run-bound capability visible at dispatch.
|
|
8
|
+
// eslint-disable-next-line max-lines-per-function, complexity
|
|
7
9
|
export async function executeActivity(runtime, currentRunId, request) {
|
|
8
10
|
const { activation, context, occurredAt, runner } = request;
|
|
9
11
|
const controller = activityController(runtime, currentRunId);
|
|
10
12
|
runtime.active.set(currentRunId, controller);
|
|
13
|
+
const mcp = runner === undefined || runtime.dependencies.mcp === undefined
|
|
14
|
+
? undefined
|
|
15
|
+
: await runtime.dependencies.mcp({ runId: currentRunId, activation, context });
|
|
11
16
|
const executionContext = {
|
|
12
17
|
signal: controller.signal,
|
|
13
18
|
occurredAt,
|
|
@@ -20,6 +25,12 @@ export async function executeActivity(runtime, currentRunId, request) {
|
|
|
20
25
|
: { reportRunnerStarted: request.reportRunnerStarted }),
|
|
21
26
|
...workspaceContext(request.workspace),
|
|
22
27
|
...(runner === undefined ? {} : { runner }),
|
|
28
|
+
...(mcp === undefined
|
|
29
|
+
? {}
|
|
30
|
+
: {
|
|
31
|
+
mcpServers: mcp.servers,
|
|
32
|
+
...(mcp.prompt === undefined ? {} : { mcpPrompt: mcp.prompt }),
|
|
33
|
+
}),
|
|
23
34
|
...(runtime.dependencies.transcriptRecorder === undefined
|
|
24
35
|
? {}
|
|
25
36
|
: { transcriptRecorder: runtime.dependencies.transcriptRecorder }),
|
|
@@ -41,16 +52,21 @@ export async function executeActivity(runtime, currentRunId, request) {
|
|
|
41
52
|
reportRunnerResult: runnerResultReporter(runtime, currentRunId, context, activation, request),
|
|
42
53
|
reportRunnerTimeout: runnerTimeoutReporter(runtime, currentRunId),
|
|
43
54
|
};
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
55
|
+
try {
|
|
56
|
+
return await runtime.activities.execute({
|
|
57
|
+
activationId: activation.activationId,
|
|
58
|
+
activity: activation.activity,
|
|
59
|
+
workItemId: context.workItemId,
|
|
60
|
+
workflowInstanceId: context.workflowInstanceId,
|
|
61
|
+
orchestrationGroupId: context.orchestrationGroupId,
|
|
62
|
+
causationId: activation.activationId,
|
|
63
|
+
input: activation.input,
|
|
64
|
+
resources: context.resources,
|
|
65
|
+
}, executionContext);
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
await mcp?.release();
|
|
69
|
+
}
|
|
54
70
|
}
|
|
55
71
|
function activityController(runtime, currentRunId) {
|
|
56
72
|
return runtime.active.get(currentRunId) ?? new AbortController();
|
|
@@ -563,7 +563,7 @@ function resolveRunner(runtime, executionKind, activation, context) {
|
|
|
563
563
|
throw new Error(`Unknown execution runner pool: ${runnerPool}`);
|
|
564
564
|
if (executionKind !== ActivityExecutionKind.Agent)
|
|
565
565
|
return { runner: undefined };
|
|
566
|
-
const resolved = runtime.dependencies.runners?.resolve(runnerPool, context.ineligibleRunners ?? new Set());
|
|
566
|
+
const resolved = runtime.dependencies.runners?.resolve(runnerPool, context.ineligibleRunners ?? new Set(), { ephemeralMcp: runtime.dependencies.mcp !== undefined });
|
|
567
567
|
return describeResolvedRunner(runtime, runnerPool, resolved);
|
|
568
568
|
}
|
|
569
569
|
function describeResolvedRunner(runtime, pool, resolved) {
|
|
@@ -7,12 +7,26 @@ export async function acquireWorkspace(runId, mode, workItemId, resources, provi
|
|
|
7
7
|
if (provider === undefined)
|
|
8
8
|
throw new Error('Workspace provider is required');
|
|
9
9
|
const repositoryResource = resources.find((resource) => resource.kind === BuiltInResourceKind.Repository) ??
|
|
10
|
-
resources.find((resource) => resource.kind === BuiltInResourceKind.
|
|
11
|
-
|
|
10
|
+
resources.find((resource) => resource.kind === BuiltInResourceKind.PullRequest) ??
|
|
11
|
+
resources.find((resource) => resource.kind === BuiltInResourceKind.Issue);
|
|
12
12
|
if (repositoryResource === undefined)
|
|
13
13
|
throw new Error('Repository Resource is required');
|
|
14
14
|
signal.throwIfAborted();
|
|
15
|
-
|
|
15
|
+
const revision = repositoryResource.revision;
|
|
16
|
+
const checkoutRevision = repositoryResource.kind === BuiltInResourceKind.Repository ||
|
|
17
|
+
(repositoryResource.kind === BuiltInResourceKind.PullRequest &&
|
|
18
|
+
revision !== undefined &&
|
|
19
|
+
/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/i.test(revision))
|
|
20
|
+
? revision
|
|
21
|
+
: undefined;
|
|
22
|
+
return provider.acquire({
|
|
23
|
+
runId,
|
|
24
|
+
signal,
|
|
25
|
+
mode,
|
|
26
|
+
workItemId,
|
|
27
|
+
repositoryResource,
|
|
28
|
+
...(checkoutRevision === undefined ? {} : { checkoutRevision }),
|
|
29
|
+
});
|
|
16
30
|
}
|
|
17
31
|
export function validateResourceRequirements(requirements, resources) {
|
|
18
32
|
for (const requirement of requirements) {
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import type { ExternalExecutionKind } from '../../activities/index.js';
|
|
2
2
|
import type { FinishedRunStatus, WorkspaceMode } from './vocabulary.js';
|
|
3
3
|
export declare const ProviderQuotaExceededFailureKind = "provider-quota-exceeded";
|
|
4
|
+
/** A trusted MCP process configured only for this runner invocation. */
|
|
5
|
+
export interface RunnerMcpServer {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly command: string;
|
|
8
|
+
readonly args?: readonly string[];
|
|
9
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
10
|
+
}
|
|
4
11
|
export interface RunnerRequest {
|
|
5
12
|
readonly runId: string;
|
|
6
13
|
readonly prompt: string;
|
|
@@ -23,6 +30,7 @@ export interface RunnerRequest {
|
|
|
23
30
|
readonly cacheRead?: number;
|
|
24
31
|
readonly cacheWrite?: number;
|
|
25
32
|
};
|
|
33
|
+
readonly mcpServers?: readonly RunnerMcpServer[];
|
|
26
34
|
}
|
|
27
35
|
export type AgentRunOutcome = 'DONE' | 'REJECTED' | 'BLOCKED' | 'FAILED' | 'NEEDS_CLARIFICATION';
|
|
28
36
|
export interface AgentRunResponse {
|
|
@@ -74,5 +82,7 @@ export interface RunnerExecution {
|
|
|
74
82
|
export interface Runner {
|
|
75
83
|
/** Whether this adapter can continue an earlier session with its opaque session ID. */
|
|
76
84
|
readonly supportsSessionResume?: boolean;
|
|
85
|
+
/** Whether this adapter can inject trusted MCP configuration per invocation. */
|
|
86
|
+
readonly supportsEphemeralMcp?: boolean;
|
|
77
87
|
start(request: RunnerRequest, signal: AbortSignal): Promise<RunnerExecution>;
|
|
78
88
|
}
|
|
@@ -9,6 +9,8 @@ export interface WorkspaceRequest {
|
|
|
9
9
|
readonly mode: typeof WorkspaceMode.ReadOnly | typeof WorkspaceMode.Branch;
|
|
10
10
|
readonly workItemId: WorkItemId;
|
|
11
11
|
readonly repositoryResource: ResourceView;
|
|
12
|
+
/** Git checkout target, independent of the resource observation revision. */
|
|
13
|
+
readonly checkoutRevision?: string;
|
|
12
14
|
}
|
|
13
15
|
export interface WorkspaceLease {
|
|
14
16
|
readonly workspaceId: string;
|
|
@@ -16,7 +18,7 @@ export interface WorkspaceLease {
|
|
|
16
18
|
/** Branch actually checked out for this lease, when the provider can attest it. */
|
|
17
19
|
readonly branch?: string | undefined;
|
|
18
20
|
readonly mode: WorkspaceRequest['mode'];
|
|
19
|
-
/** Ends this Run's use of the workspace;
|
|
21
|
+
/** Ends this Run's use of the workspace; workspace disposal is WorkItem-scoped recovery. */
|
|
20
22
|
release(): Promise<void>;
|
|
21
23
|
}
|
|
22
24
|
export interface WorkspaceProvider {
|
|
@@ -46,3 +48,11 @@ export interface WorkspaceRecoveryFailure {
|
|
|
46
48
|
readonly path: string;
|
|
47
49
|
readonly message: string;
|
|
48
50
|
}
|
|
51
|
+
/** Process-owned exclusion shared by acquisition and recovery. */
|
|
52
|
+
export interface WorkspaceLockProvider {
|
|
53
|
+
tryAcquire(path: string): Promise<WorkspaceLock>;
|
|
54
|
+
}
|
|
55
|
+
export interface WorkspaceLock {
|
|
56
|
+
readonly acquired: boolean;
|
|
57
|
+
release(): Promise<void>;
|
|
58
|
+
}
|
|
@@ -14,6 +14,7 @@ export declare function cliRunner(name: string, command: string, args: (request:
|
|
|
14
14
|
readonly runnerTimeouts?: ProcessTimeouts;
|
|
15
15
|
readonly defaultModel?: string;
|
|
16
16
|
readonly supportsSessionResume?: boolean;
|
|
17
|
+
readonly supportsEphemeralMcp?: boolean;
|
|
17
18
|
readonly parseSuccessfulOutput?: (stdout: string, request: RunnerRequest) => Partial<AgentRunnerResult>;
|
|
18
19
|
readonly classifyFailure?: (input: {
|
|
19
20
|
readonly stdout: string;
|
|
@@ -9,6 +9,7 @@ export function createClaudeRunner(options = {}) {
|
|
|
9
9
|
parseSuccessfulOutput: parseClaudeOutput,
|
|
10
10
|
classifyFailure: classifyClaudeFailure,
|
|
11
11
|
supportsSessionResume: true,
|
|
12
|
+
supportsEphemeralMcp: true,
|
|
12
13
|
});
|
|
13
14
|
}
|
|
14
15
|
export function parseClaudeOutput(stdout, _request) {
|
|
@@ -84,15 +85,35 @@ export function claudeCommandArgs(request, passthroughArgs = [], defaults = {})
|
|
|
84
85
|
...(request.allowedTools.length === 0
|
|
85
86
|
? []
|
|
86
87
|
: ['--allowedTools', request.allowedTools.join(' ')]),
|
|
88
|
+
...(request.mcpServers === undefined || request.mcpServers.length === 0
|
|
89
|
+
? []
|
|
90
|
+
: ['--mcp-config', JSON.stringify(claudeMcpConfig(request)), '--strict-mcp-config']),
|
|
87
91
|
...passthroughArgs,
|
|
88
92
|
'--',
|
|
89
93
|
request.prompt,
|
|
90
94
|
];
|
|
91
95
|
}
|
|
96
|
+
function claudeMcpConfig(request) {
|
|
97
|
+
return {
|
|
98
|
+
mcpServers: Object.fromEntries((request.mcpServers ?? []).map((server) => [
|
|
99
|
+
server.name,
|
|
100
|
+
{
|
|
101
|
+
command: server.command,
|
|
102
|
+
...(server.args === undefined ? {} : { args: server.args }),
|
|
103
|
+
...(server.env === undefined ? {} : { env: server.env }),
|
|
104
|
+
},
|
|
105
|
+
])),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
92
108
|
export function cliRunner(name, command, args, options = {}) {
|
|
93
109
|
return {
|
|
94
110
|
supportsSessionResume: options.supportsSessionResume === true,
|
|
111
|
+
supportsEphemeralMcp: options.supportsEphemeralMcp === true,
|
|
95
112
|
async start(request, signal) {
|
|
113
|
+
if (request.mcpServers !== undefined &&
|
|
114
|
+
request.mcpServers.length > 0 &&
|
|
115
|
+
!options.supportsEphemeralMcp)
|
|
116
|
+
throw new Error(`${name} runner does not support ephemeral MCP configuration`);
|
|
96
117
|
const process = runProcess(command, args(request), request.workspacePath, signal, {
|
|
97
118
|
...options.runnerTimeouts,
|
|
98
119
|
...(request.onTimeout === undefined ? {} : { onTimeout: request.onTimeout }),
|
|
@@ -42,6 +42,7 @@ export function createCodexRunner(options = {}) {
|
|
|
42
42
|
parseSuccessfulOutput: parseCodexOutput,
|
|
43
43
|
classifyFailure: classifyCodexFailure,
|
|
44
44
|
supportsSessionResume: true,
|
|
45
|
+
supportsEphemeralMcp: true,
|
|
45
46
|
});
|
|
46
47
|
return runner;
|
|
47
48
|
}
|
|
@@ -59,11 +60,22 @@ export function codexCommandArgs(request, passthroughArgs = [], defaults = {}) {
|
|
|
59
60
|
...((request.effort ?? defaults.effort) === undefined
|
|
60
61
|
? []
|
|
61
62
|
: ['-c', `model_reasoning_effort=${request.effort ?? defaults.effort}`]),
|
|
63
|
+
...codexMcpConfigArgs(request),
|
|
62
64
|
...(request.resumeSessionId === undefined ? [] : ['resume', request.resumeSessionId]),
|
|
63
65
|
...passthroughArgs,
|
|
64
66
|
`${request.prompt}\n\n${waitBackgroundInstruction}`,
|
|
65
67
|
];
|
|
66
68
|
}
|
|
69
|
+
function codexMcpConfigArgs(request) {
|
|
70
|
+
return (request.mcpServers ?? []).flatMap((server) => [
|
|
71
|
+
'-c',
|
|
72
|
+
`mcp_servers.${JSON.stringify(server.name)}={command=${JSON.stringify(server.command)},args=${JSON.stringify(server.args ?? [])}${server.env === undefined
|
|
73
|
+
? ''
|
|
74
|
+
: `,env={${Object.entries(server.env)
|
|
75
|
+
.map(([key, value]) => `${JSON.stringify(key)}=${JSON.stringify(value)}`)
|
|
76
|
+
.join(',')}}`}}`,
|
|
77
|
+
]);
|
|
78
|
+
}
|
|
67
79
|
// Work around Codex's missing background-command lifecycle contract (https://github.com/openai/codex/issues/32505, https://github.com/openai/codex/issues/34866).
|
|
68
80
|
const waitBackgroundInstruction = `When a command you started is still running or lacks a terminal result, do not report a normal Wake outcome. Make your entire final response exactly WAIT_BACKGROUND. Wake will reject that stop attempt and ask you to continue. After that hook prompt, poll the still-running command and report a normal terminal status only after it has settled. Do not use WAIT_BACKGROUND for a human-input or implementation blocker.`;
|
|
69
81
|
function codexSandboxMode(_workspaceMode) {
|
|
@@ -3,6 +3,7 @@ import { type FakeScenarioResolver } from './fake-scenarios.js';
|
|
|
3
3
|
export declare class FakeExecutionRunner implements Runner {
|
|
4
4
|
readonly name: string;
|
|
5
5
|
private readonly scenarios;
|
|
6
|
+
readonly supportsEphemeralMcp = true;
|
|
6
7
|
constructor(name?: string, scenarios?: FakeScenarioResolver);
|
|
7
8
|
start(request: RunnerRequest, signal: AbortSignal): Promise<RunnerExecution>;
|
|
8
9
|
}
|
|
@@ -7,7 +7,9 @@ export declare class RunnerRegistry {
|
|
|
7
7
|
private readonly runnerPools;
|
|
8
8
|
private readonly runners;
|
|
9
9
|
constructor(runnerPools: Readonly<Record<string, readonly string[]>>, runners: Readonly<Record<string, Runner>>);
|
|
10
|
-
resolve(runnerPool: string, ineligible?: ReadonlySet<string
|
|
10
|
+
resolve(runnerPool: string, ineligible?: ReadonlySet<string>, requirements?: {
|
|
11
|
+
readonly ephemeralMcp?: boolean;
|
|
12
|
+
}): {
|
|
11
13
|
readonly name: string;
|
|
12
14
|
readonly runner: Runner;
|
|
13
15
|
};
|
|
@@ -13,21 +13,23 @@ export class RunnerRegistry {
|
|
|
13
13
|
this.runnerPools = runnerPools;
|
|
14
14
|
this.runners = runners;
|
|
15
15
|
}
|
|
16
|
-
resolve(runnerPool, ineligible = new Set()) {
|
|
16
|
+
resolve(runnerPool, ineligible = new Set(), requirements = {}) {
|
|
17
17
|
const candidates = this.runnerPools[runnerPool];
|
|
18
18
|
if (candidates === undefined)
|
|
19
19
|
throw new Error(`Execution runner pool ${runnerPool} has no runner`);
|
|
20
|
-
return selectEligibleCandidate(runnerPool, candidates, this.runners, ineligible);
|
|
20
|
+
return selectEligibleCandidate(runnerPool, candidates, this.runners, ineligible, requirements);
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
// Falls sideways to the next candidate on quota ineligibility; never a different runner pool.
|
|
24
|
-
function selectEligibleCandidate(runnerPool, candidates, runners, ineligible) {
|
|
24
|
+
function selectEligibleCandidate(runnerPool, candidates, runners, ineligible, requirements) {
|
|
25
25
|
for (const name of candidates) {
|
|
26
26
|
if (ineligible.has(name))
|
|
27
27
|
continue;
|
|
28
28
|
const runner = runners[name];
|
|
29
29
|
if (runner === undefined)
|
|
30
30
|
throw new Error(`Runner ${name} is not registered`);
|
|
31
|
+
if (requirements.ephemeralMcp === true && runner.supportsEphemeralMcp !== true)
|
|
32
|
+
continue;
|
|
31
33
|
return { name, runner };
|
|
32
34
|
}
|
|
33
35
|
throw new NoEligibleRunnerError(runnerPool);
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import type { WorkItemId } from '../../../work/index.js';
|
|
2
1
|
import type { RunView } from '../../contracts/views.js';
|
|
3
|
-
import type { WorkspaceProvider, WorkspaceRecovery, WorkspaceRecoveryOptions, WorkspaceRecoveryResult, WorkspaceRequest } from '../../contracts/workspace.js';
|
|
2
|
+
import type { WorkspaceLockProvider, WorkspaceProvider, WorkspaceRecovery, WorkspaceRecoveryOptions, WorkspaceRecoveryResult, WorkspaceRequest } from '../../contracts/workspace.js';
|
|
4
3
|
import { type WorkspacePrepareHook } from './prepare-workspace.js';
|
|
5
4
|
export interface RepositoryCloneResolver {
|
|
6
5
|
cloneLocator(resourceId: string, signal: AbortSignal): Promise<string>;
|
|
7
6
|
}
|
|
8
|
-
export type GitRunner = (arguments_: readonly string[], signal: AbortSignal) => Promise<void>;
|
|
7
|
+
export type GitRunner = (arguments_: readonly string[], signal: AbortSignal) => Promise<string | void>;
|
|
9
8
|
export interface WorkspaceRecoveryFileSystem {
|
|
10
9
|
remove(path: string): Promise<void>;
|
|
11
10
|
canonicalize(path: string): Promise<string>;
|
|
@@ -13,17 +12,23 @@ export interface WorkspaceRecoveryFileSystem {
|
|
|
13
12
|
export declare class GitWorkspaceProvider implements WorkspaceProvider, WorkspaceRecovery {
|
|
14
13
|
private readonly root;
|
|
15
14
|
private readonly resolver;
|
|
15
|
+
private readonly locks;
|
|
16
16
|
private readonly git;
|
|
17
17
|
private readonly prepareHook?;
|
|
18
18
|
private readonly markerRoot;
|
|
19
19
|
private readonly recoveryFileSystem;
|
|
20
|
-
|
|
20
|
+
private readonly pendingReleases;
|
|
21
|
+
constructor(root: string, resolver: RepositoryCloneResolver, locks: WorkspaceLockProvider, git?: GitRunner, recoveryFileSystem?: WorkspaceRecoveryFileSystem, prepareHook?: WorkspacePrepareHook);
|
|
21
22
|
acquire(request: WorkspaceRequest): Promise<{
|
|
22
23
|
release: () => Promise<void>;
|
|
23
|
-
branch?:
|
|
24
|
+
branch?: string;
|
|
24
25
|
workspaceId: string;
|
|
25
26
|
path: string;
|
|
26
27
|
mode: "read-only" | "branch";
|
|
27
28
|
}>;
|
|
29
|
+
private releaseWorkspaceLock;
|
|
30
|
+
private prepareCheckout;
|
|
31
|
+
private reuseCheckout;
|
|
32
|
+
private validateLegacyCheckout;
|
|
28
33
|
recover(runs: readonly RunView[], options?: WorkspaceRecoveryOptions): Promise<WorkspaceRecoveryResult>;
|
|
29
34
|
}
|