mcp-codex-worker 0.1.11 → 0.1.13
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/src/execution/base-adapter.d.ts +63 -0
- package/dist/src/execution/base-adapter.js +74 -0
- package/dist/src/execution/base-adapter.js.map +1 -0
- package/dist/src/execution/claude-adapter.d.ts +12 -0
- package/dist/src/execution/claude-adapter.js +23 -0
- package/dist/src/execution/claude-adapter.js.map +1 -0
- package/dist/src/execution/codex-adapter.d.ts +21 -0
- package/dist/src/execution/codex-adapter.js +35 -0
- package/dist/src/execution/codex-adapter.js.map +1 -0
- package/dist/src/execution/codex-pause-flow.d.ts +23 -0
- package/dist/src/execution/codex-pause-flow.js +185 -0
- package/dist/src/execution/codex-pause-flow.js.map +1 -0
- package/dist/src/execution/copilot-adapter.d.ts +12 -0
- package/dist/src/execution/copilot-adapter.js +23 -0
- package/dist/src/execution/copilot-adapter.js.map +1 -0
- package/dist/src/execution/provider-capabilities.d.ts +35 -0
- package/dist/src/execution/provider-capabilities.js +58 -0
- package/dist/src/execution/provider-capabilities.js.map +1 -0
- package/dist/src/execution/provider-registry.d.ts +37 -0
- package/dist/src/execution/provider-registry.js +69 -0
- package/dist/src/execution/provider-registry.js.map +1 -0
- package/dist/src/mcp/resource-renderers.d.ts +25 -0
- package/dist/src/mcp/resource-renderers.js +190 -0
- package/dist/src/mcp/resource-renderers.js.map +1 -0
- package/dist/src/mcp/sep1686-handlers.d.ts +56 -0
- package/dist/src/mcp/sep1686-handlers.js +98 -0
- package/dist/src/mcp/sep1686-handlers.js.map +1 -0
- package/dist/src/mcp/tool-definitions.d.ts +157 -0
- package/dist/src/mcp/tool-definitions.js +242 -0
- package/dist/src/mcp/tool-definitions.js.map +1 -1
- package/dist/src/task/fsm-transitions.d.ts +17 -0
- package/dist/src/task/fsm-transitions.js +66 -0
- package/dist/src/task/fsm-transitions.js.map +1 -0
- package/dist/src/task/task-handle-impl.d.ts +10 -0
- package/dist/src/task/task-handle-impl.js +139 -0
- package/dist/src/task/task-handle-impl.js.map +1 -0
- package/dist/src/task/task-handle.d.ts +88 -0
- package/dist/src/task/task-handle.js +2 -0
- package/dist/src/task/task-handle.js.map +1 -0
- package/dist/src/task/task-manager.d.ts +99 -0
- package/dist/src/task/task-manager.js +246 -0
- package/dist/src/task/task-manager.js.map +1 -0
- package/dist/src/task/task-persistence.d.ts +18 -0
- package/dist/src/task/task-persistence.js +61 -0
- package/dist/src/task/task-persistence.js.map +1 -0
- package/dist/src/task/task-state.d.ts +79 -0
- package/dist/src/task/task-state.js +24 -0
- package/dist/src/task/task-state.js.map +1 -0
- package/dist/src/task/task-store.d.ts +46 -0
- package/dist/src/task/task-store.js +104 -0
- package/dist/src/task/task-store.js.map +1 -0
- package/dist/src/task/wire-state-mapper.d.ts +21 -0
- package/dist/src/task/wire-state-mapper.js +63 -0
- package/dist/src/task/wire-state-mapper.js.map +1 -0
- package/package.json +2 -1
- package/src/execution/base-adapter.ts +133 -0
- package/src/execution/claude-adapter.ts +40 -0
- package/src/execution/codex-adapter.ts +67 -0
- package/src/execution/codex-pause-flow.ts +225 -0
- package/src/execution/copilot-adapter.ts +40 -0
- package/src/execution/provider-capabilities.ts +100 -0
- package/src/execution/provider-registry.ts +81 -0
- package/src/mcp/resource-renderers.ts +224 -0
- package/src/mcp/sep1686-handlers.ts +149 -0
- package/src/mcp/tool-definitions.ts +255 -0
- package/src/task/fsm-transitions.ts +72 -0
- package/src/task/task-handle-impl.ts +170 -0
- package/src/task/task-handle.ts +135 -0
- package/src/task/task-manager.ts +328 -0
- package/src/task/task-persistence.ts +95 -0
- package/src/task/task-state.ts +121 -0
- package/src/task/task-store.ts +121 -0
- package/src/task/wire-state-mapper.ts +77 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { isTerminalStatus } from './task-state.js';
|
|
2
|
+
import { isValidTransition } from './fsm-transitions.js';
|
|
3
|
+
/**
|
|
4
|
+
* In-memory task store with FSM-validated state transitions.
|
|
5
|
+
*
|
|
6
|
+
* This is the single source of truth for task state. It enforces the FSM at
|
|
7
|
+
* the data layer — illegal transitions are rejected before any mutation occurs.
|
|
8
|
+
*
|
|
9
|
+
* Terminal tasks can be evicted by age to prevent unbounded memory growth.
|
|
10
|
+
*/
|
|
11
|
+
export class TaskStore {
|
|
12
|
+
tasks = new Map();
|
|
13
|
+
/** Store a new task. Overwrites if the ID already exists. */
|
|
14
|
+
create(task) {
|
|
15
|
+
this.tasks.set(task.id, task);
|
|
16
|
+
}
|
|
17
|
+
/** Retrieve a task by its unique ID. */
|
|
18
|
+
get(id) {
|
|
19
|
+
return this.tasks.get(id);
|
|
20
|
+
}
|
|
21
|
+
/** Linear scan for a task matching the given session ID. */
|
|
22
|
+
getBySessionId(sessionId) {
|
|
23
|
+
for (const task of this.tasks.values()) {
|
|
24
|
+
if (task.sessionId === sessionId)
|
|
25
|
+
return task;
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
/** Return all tasks as an array. */
|
|
30
|
+
getAll() {
|
|
31
|
+
return [...this.tasks.values()];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Transition a task to a new status.
|
|
35
|
+
*
|
|
36
|
+
* Returns `true` if the transition was valid and applied, `false` if the
|
|
37
|
+
* task was not found or the transition is illegal per the FSM.
|
|
38
|
+
*/
|
|
39
|
+
updateStatus(id, next) {
|
|
40
|
+
const task = this.tasks.get(id);
|
|
41
|
+
if (!task)
|
|
42
|
+
return false;
|
|
43
|
+
if (!isValidTransition(task.status, next))
|
|
44
|
+
return false;
|
|
45
|
+
const now = new Date().toISOString();
|
|
46
|
+
task.status = next;
|
|
47
|
+
task.updatedAt = now;
|
|
48
|
+
if (isTerminalStatus(next)) {
|
|
49
|
+
task.completedAt = now;
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Apply a partial update to a task. If the update includes a `status`
|
|
55
|
+
* change, the FSM is validated first — an illegal transition causes the
|
|
56
|
+
* entire update to be rejected (returns `undefined`).
|
|
57
|
+
*/
|
|
58
|
+
update(id, updates) {
|
|
59
|
+
const task = this.tasks.get(id);
|
|
60
|
+
if (!task)
|
|
61
|
+
return undefined;
|
|
62
|
+
// Validate status transition if status is being changed
|
|
63
|
+
if (updates.status !== undefined && updates.status !== task.status) {
|
|
64
|
+
if (!isValidTransition(task.status, updates.status))
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
const now = new Date().toISOString();
|
|
68
|
+
Object.assign(task, updates, { updatedAt: now });
|
|
69
|
+
if (updates.status !== undefined && isTerminalStatus(updates.status)) {
|
|
70
|
+
task.completedAt = now;
|
|
71
|
+
}
|
|
72
|
+
return task;
|
|
73
|
+
}
|
|
74
|
+
/** Remove a task by ID. Returns `true` if it existed. */
|
|
75
|
+
delete(id) {
|
|
76
|
+
return this.tasks.delete(id);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Remove terminal tasks whose `updatedAt` timestamp is older than
|
|
80
|
+
* `maxAgeMs` milliseconds from now.
|
|
81
|
+
*
|
|
82
|
+
* Returns the number of tasks evicted.
|
|
83
|
+
*/
|
|
84
|
+
evict(maxAgeMs) {
|
|
85
|
+
const cutoff = Date.now() - maxAgeMs;
|
|
86
|
+
let count = 0;
|
|
87
|
+
for (const [id, task] of this.tasks) {
|
|
88
|
+
if (isTerminalStatus(task.status) && new Date(task.updatedAt).getTime() < cutoff) {
|
|
89
|
+
this.tasks.delete(id);
|
|
90
|
+
count++;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return count;
|
|
94
|
+
}
|
|
95
|
+
/** Remove all tasks. */
|
|
96
|
+
clear() {
|
|
97
|
+
this.tasks.clear();
|
|
98
|
+
}
|
|
99
|
+
/** Return the number of stored tasks. */
|
|
100
|
+
size() {
|
|
101
|
+
return this.tasks.size;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=task-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-store.js","sourceRoot":"","sources":["../../../src/task/task-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { TaskStatus } from './task-state.js';
|
|
2
|
+
/**
|
|
3
|
+
* SEP-1686 wire states — the 7 status values visible to MCP clients.
|
|
4
|
+
* Spec reference: §4.2
|
|
5
|
+
*/
|
|
6
|
+
export type WireState = 'submitted' | 'working' | 'input_required' | 'completed' | 'failed' | 'cancelled' | 'unknown';
|
|
7
|
+
/**
|
|
8
|
+
* Maps an internal 10-state TaskStatus to a SEP-1686 wire state.
|
|
9
|
+
*
|
|
10
|
+
* Uses an exhaustive switch (no default) so TypeScript raises a compile error
|
|
11
|
+
* if a new TaskStatus variant is added without updating this mapping.
|
|
12
|
+
*/
|
|
13
|
+
export declare function mapToWireState(status: TaskStatus): WireState;
|
|
14
|
+
/**
|
|
15
|
+
* Maps an internal 10-state TaskStatus to a short bracketed display badge
|
|
16
|
+
* for resource renders and UI.
|
|
17
|
+
*
|
|
18
|
+
* Uses an exhaustive switch (no default) so TypeScript raises a compile error
|
|
19
|
+
* if a new TaskStatus variant is added without updating this mapping.
|
|
20
|
+
*/
|
|
21
|
+
export declare function mapToDisplay(status: TaskStatus): string;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { TaskStatus } from './task-state.js';
|
|
2
|
+
/**
|
|
3
|
+
* Maps an internal 10-state TaskStatus to a SEP-1686 wire state.
|
|
4
|
+
*
|
|
5
|
+
* Uses an exhaustive switch (no default) so TypeScript raises a compile error
|
|
6
|
+
* if a new TaskStatus variant is added without updating this mapping.
|
|
7
|
+
*/
|
|
8
|
+
export function mapToWireState(status) {
|
|
9
|
+
switch (status) {
|
|
10
|
+
case TaskStatus.WAITING:
|
|
11
|
+
return 'submitted';
|
|
12
|
+
case TaskStatus.PENDING:
|
|
13
|
+
return 'submitted';
|
|
14
|
+
case TaskStatus.RUNNING:
|
|
15
|
+
return 'working';
|
|
16
|
+
case TaskStatus.RATE_LIMITED:
|
|
17
|
+
return 'working';
|
|
18
|
+
case TaskStatus.WAITING_ANSWER:
|
|
19
|
+
return 'input_required';
|
|
20
|
+
case TaskStatus.COMPLETED:
|
|
21
|
+
return 'completed';
|
|
22
|
+
case TaskStatus.FAILED:
|
|
23
|
+
return 'failed';
|
|
24
|
+
case TaskStatus.TIMED_OUT:
|
|
25
|
+
return 'failed';
|
|
26
|
+
case TaskStatus.CANCELLED:
|
|
27
|
+
return 'cancelled';
|
|
28
|
+
case TaskStatus.UNKNOWN:
|
|
29
|
+
return 'unknown';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Maps an internal 10-state TaskStatus to a short bracketed display badge
|
|
34
|
+
* for resource renders and UI.
|
|
35
|
+
*
|
|
36
|
+
* Uses an exhaustive switch (no default) so TypeScript raises a compile error
|
|
37
|
+
* if a new TaskStatus variant is added without updating this mapping.
|
|
38
|
+
*/
|
|
39
|
+
export function mapToDisplay(status) {
|
|
40
|
+
switch (status) {
|
|
41
|
+
case TaskStatus.WAITING:
|
|
42
|
+
return '[wait]';
|
|
43
|
+
case TaskStatus.PENDING:
|
|
44
|
+
return '[wait]';
|
|
45
|
+
case TaskStatus.RUNNING:
|
|
46
|
+
return '[busy]';
|
|
47
|
+
case TaskStatus.RATE_LIMITED:
|
|
48
|
+
return '[rate]';
|
|
49
|
+
case TaskStatus.WAITING_ANSWER:
|
|
50
|
+
return '[input]';
|
|
51
|
+
case TaskStatus.COMPLETED:
|
|
52
|
+
return '[done]';
|
|
53
|
+
case TaskStatus.FAILED:
|
|
54
|
+
return '[fail]';
|
|
55
|
+
case TaskStatus.TIMED_OUT:
|
|
56
|
+
return '[time]';
|
|
57
|
+
case TaskStatus.CANCELLED:
|
|
58
|
+
return '[cncl]';
|
|
59
|
+
case TaskStatus.UNKNOWN:
|
|
60
|
+
return '[???]';
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=wire-state-mapper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wire-state-mapper.js","sourceRoot":"","sources":["../../../src/task/wire-state-mapper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAe7C;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,MAAkB;IAC/C,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,UAAU,CAAC,OAAO;YACrB,OAAO,WAAW,CAAC;QACrB,KAAK,UAAU,CAAC,OAAO;YACrB,OAAO,WAAW,CAAC;QACrB,KAAK,UAAU,CAAC,OAAO;YACrB,OAAO,SAAS,CAAC;QACnB,KAAK,UAAU,CAAC,YAAY;YAC1B,OAAO,SAAS,CAAC;QACnB,KAAK,UAAU,CAAC,cAAc;YAC5B,OAAO,gBAAgB,CAAC;QAC1B,KAAK,UAAU,CAAC,SAAS;YACvB,OAAO,WAAW,CAAC;QACrB,KAAK,UAAU,CAAC,MAAM;YACpB,OAAO,QAAQ,CAAC;QAClB,KAAK,UAAU,CAAC,SAAS;YACvB,OAAO,QAAQ,CAAC;QAClB,KAAK,UAAU,CAAC,SAAS;YACvB,OAAO,WAAW,CAAC;QACrB,KAAK,UAAU,CAAC,OAAO;YACrB,OAAO,SAAS,CAAC;IACrB,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,MAAkB;IAC7C,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,UAAU,CAAC,OAAO;YACrB,OAAO,QAAQ,CAAC;QAClB,KAAK,UAAU,CAAC,OAAO;YACrB,OAAO,QAAQ,CAAC;QAClB,KAAK,UAAU,CAAC,OAAO;YACrB,OAAO,QAAQ,CAAC;QAClB,KAAK,UAAU,CAAC,YAAY;YAC1B,OAAO,QAAQ,CAAC;QAClB,KAAK,UAAU,CAAC,cAAc;YAC5B,OAAO,SAAS,CAAC;QACnB,KAAK,UAAU,CAAC,SAAS;YACvB,OAAO,QAAQ,CAAC;QAClB,KAAK,UAAU,CAAC,MAAM;YACpB,OAAO,QAAQ,CAAC;QAClB,KAAK,UAAU,CAAC,SAAS;YACvB,OAAO,QAAQ,CAAC;QAClB,KAAK,UAAU,CAAC,SAAS;YACvB,OAAO,QAAQ,CAAC;QAClB,KAAK,UAAU,CAAC,OAAO;YACrB,OAAO,OAAO,CAAC;IACnB,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-codex-worker",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "MCP server bridge for Codex app-server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
45
45
|
"tsx": "^4.20.6",
|
|
46
|
+
"unique-names-generator": "^4.7.1",
|
|
46
47
|
"zod": "^3.24.4"
|
|
47
48
|
},
|
|
48
49
|
"devDependencies": {
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// BaseProviderAdapter — Template Method pattern for provider lifecycle
|
|
3
|
+
// Spec reference: §3.4
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
|
|
6
|
+
import type { TaskHandle } from '../task/task-handle.js';
|
|
7
|
+
import type { Provider } from '../task/task-state.js';
|
|
8
|
+
import type { ProviderCapabilities } from './provider-capabilities.js';
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Supporting types
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Options passed to `BaseProviderAdapter.spawn()` to start a provider session.
|
|
16
|
+
*/
|
|
17
|
+
export interface ProviderSpawnOptions {
|
|
18
|
+
taskId: string;
|
|
19
|
+
prompt: string;
|
|
20
|
+
cwd: string;
|
|
21
|
+
timeout: number;
|
|
22
|
+
model?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Result of an availability check on a provider adapter.
|
|
27
|
+
*/
|
|
28
|
+
export type AvailabilityResult =
|
|
29
|
+
| { available: true }
|
|
30
|
+
| { available: false; reason: string };
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Abstract class
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Base class for all provider adapters.
|
|
38
|
+
*
|
|
39
|
+
* Implements the Template Method pattern: `spawn()` provides the common
|
|
40
|
+
* lifecycle skeleton (abort controller, timeout, error handling) and
|
|
41
|
+
* delegates the actual provider interaction to `executeSession()`.
|
|
42
|
+
*/
|
|
43
|
+
export abstract class BaseProviderAdapter {
|
|
44
|
+
/** Unique provider identifier. */
|
|
45
|
+
abstract readonly id: Provider;
|
|
46
|
+
|
|
47
|
+
/** Human-readable display name. */
|
|
48
|
+
abstract readonly displayName: string;
|
|
49
|
+
|
|
50
|
+
// -- Abstract methods (subclasses must implement) --------------------------
|
|
51
|
+
|
|
52
|
+
/** Check whether the provider is currently available. */
|
|
53
|
+
abstract checkAvailability(): AvailabilityResult;
|
|
54
|
+
|
|
55
|
+
/** Return the capability matrix for this provider. */
|
|
56
|
+
abstract getCapabilities(): ProviderCapabilities;
|
|
57
|
+
|
|
58
|
+
/** Return runtime statistics for observability. */
|
|
59
|
+
abstract getStats(): Record<string, unknown>;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Template Method hook — the actual provider session logic.
|
|
63
|
+
* Subclasses implement this to drive the provider (spawn process, call API, etc.).
|
|
64
|
+
*/
|
|
65
|
+
protected abstract executeSession(
|
|
66
|
+
handle: TaskHandle,
|
|
67
|
+
prompt: string,
|
|
68
|
+
signal: AbortSignal,
|
|
69
|
+
options: ProviderSpawnOptions,
|
|
70
|
+
): Promise<void>;
|
|
71
|
+
|
|
72
|
+
// -- Public lifecycle methods ---------------------------------------------
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Spawn a provider session for the given task.
|
|
76
|
+
*
|
|
77
|
+
* Template method: checks terminal state, sets up AbortController + timeout,
|
|
78
|
+
* calls `executeSession`, and handles errors.
|
|
79
|
+
*/
|
|
80
|
+
async spawn(options: ProviderSpawnOptions, handle: TaskHandle): Promise<void> {
|
|
81
|
+
// Guard: do nothing if already in a terminal state
|
|
82
|
+
if (handle.isTerminal()) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const controller = new AbortController();
|
|
87
|
+
handle.registerAbort(controller);
|
|
88
|
+
|
|
89
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
90
|
+
|
|
91
|
+
if (options.timeout > 0) {
|
|
92
|
+
timer = setTimeout(() => {
|
|
93
|
+
controller.abort();
|
|
94
|
+
}, options.timeout);
|
|
95
|
+
// Allow the Node.js process to exit even if this timer is pending
|
|
96
|
+
if (timer && typeof timer === 'object' && 'unref' in timer) {
|
|
97
|
+
timer.unref();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
await this.executeSession(handle, options.prompt, controller.signal, options);
|
|
103
|
+
} catch (err: unknown) {
|
|
104
|
+
if (controller.signal.aborted) {
|
|
105
|
+
handle.markCancelled('Session timed out');
|
|
106
|
+
} else {
|
|
107
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
108
|
+
handle.markFailed(message);
|
|
109
|
+
}
|
|
110
|
+
} finally {
|
|
111
|
+
if (timer !== undefined) {
|
|
112
|
+
clearTimeout(timer);
|
|
113
|
+
}
|
|
114
|
+
handle.unregisterAbort();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Attempt to abort a running task by ID.
|
|
120
|
+
* Default implementation returns false (not supported).
|
|
121
|
+
*/
|
|
122
|
+
async abort(_taskId: string): Promise<boolean> {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Graceful shutdown hook. Called when the server is stopping.
|
|
128
|
+
* Default implementation is a no-op.
|
|
129
|
+
*/
|
|
130
|
+
async shutdown(): Promise<void> {
|
|
131
|
+
// no-op by default
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// ClaudeAdapter — Phase 2 stub
|
|
3
|
+
// Spec reference: §3.4
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
|
|
6
|
+
import type { Provider } from '../task/task-state.js';
|
|
7
|
+
import type { TaskHandle } from '../task/task-handle.js';
|
|
8
|
+
import type { ProviderCapabilities } from './provider-capabilities.js';
|
|
9
|
+
import { CLAUDE_CAPABILITIES } from './provider-capabilities.js';
|
|
10
|
+
import {
|
|
11
|
+
BaseProviderAdapter,
|
|
12
|
+
type ProviderSpawnOptions,
|
|
13
|
+
type AvailabilityResult,
|
|
14
|
+
} from './base-adapter.js';
|
|
15
|
+
|
|
16
|
+
export class ClaudeAdapter extends BaseProviderAdapter {
|
|
17
|
+
readonly id: Provider = 'claude-cli';
|
|
18
|
+
readonly displayName = 'Claude';
|
|
19
|
+
|
|
20
|
+
checkAvailability(): AvailabilityResult {
|
|
21
|
+
return { available: false, reason: 'Phase 2 — not yet implemented' };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
getCapabilities(): ProviderCapabilities {
|
|
25
|
+
return CLAUDE_CAPABILITIES;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getStats(): Record<string, unknown> {
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
protected async executeSession(
|
|
33
|
+
_handle: TaskHandle,
|
|
34
|
+
_prompt: string,
|
|
35
|
+
_signal: AbortSignal,
|
|
36
|
+
_options: ProviderSpawnOptions,
|
|
37
|
+
): Promise<void> {
|
|
38
|
+
throw new Error('Not implemented — Phase 2');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// CodexAdapter — wraps CodexRuntime behind the BaseProviderAdapter contract
|
|
3
|
+
// Spec reference: §3.4
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
|
|
6
|
+
import type { Provider } from '../task/task-state.js';
|
|
7
|
+
import type { TaskHandle } from '../task/task-handle.js';
|
|
8
|
+
import type { ProviderCapabilities } from './provider-capabilities.js';
|
|
9
|
+
import { CODEX_CAPABILITIES } from './provider-capabilities.js';
|
|
10
|
+
import {
|
|
11
|
+
BaseProviderAdapter,
|
|
12
|
+
type ProviderSpawnOptions,
|
|
13
|
+
type AvailabilityResult,
|
|
14
|
+
} from './base-adapter.js';
|
|
15
|
+
import type { CodexRuntime } from '../services/codex-runtime.js';
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Options
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
export interface CodexAdapterOptions {
|
|
22
|
+
command?: string;
|
|
23
|
+
args?: string[];
|
|
24
|
+
env?: NodeJS.ProcessEnv;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Adapter
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
export class CodexAdapter extends BaseProviderAdapter {
|
|
32
|
+
readonly id: Provider = 'codex';
|
|
33
|
+
readonly displayName = 'Codex';
|
|
34
|
+
|
|
35
|
+
private readonly options: CodexAdapterOptions;
|
|
36
|
+
private runtime?: CodexRuntime;
|
|
37
|
+
|
|
38
|
+
constructor(options: CodexAdapterOptions) {
|
|
39
|
+
super();
|
|
40
|
+
this.options = options;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
checkAvailability(): AvailabilityResult {
|
|
44
|
+
return { available: true };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
getCapabilities(): ProviderCapabilities {
|
|
48
|
+
return CODEX_CAPABILITIES;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
getStats(): Record<string, unknown> {
|
|
52
|
+
return {};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
protected async executeSession(
|
|
56
|
+
_handle: TaskHandle,
|
|
57
|
+
_prompt: string,
|
|
58
|
+
_signal: AbortSignal,
|
|
59
|
+
_options: ProviderSpawnOptions,
|
|
60
|
+
): Promise<void> {
|
|
61
|
+
throw new Error('CodexAdapter.executeSession is not yet implemented');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async shutdown(): Promise<void> {
|
|
65
|
+
await this.runtime?.shutdown();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import type { AppServerClient } from '../services/app-server-client.js';
|
|
2
|
+
import type { TaskHandle } from '../task/task-handle.js';
|
|
3
|
+
import type { PendingQuestion } from '../task/task-state.js';
|
|
4
|
+
import type { JsonLineRequest, PendingServerRequest } from '../types/codex.js';
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Helpers
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
function asObject(value: unknown): Record<string, unknown> | undefined {
|
|
11
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
return value as Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function asArray(value: unknown): unknown[] | undefined {
|
|
18
|
+
return Array.isArray(value) ? value : undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function asString(value: unknown): string | undefined {
|
|
22
|
+
return typeof value === 'string' ? value : undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Translation: Codex server-request → PendingQuestion
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Translate a raw Codex JSON-RPC server request into a provider-blind
|
|
31
|
+
* PendingQuestion variant.
|
|
32
|
+
*
|
|
33
|
+
* Returns `null` for:
|
|
34
|
+
* - Guardian Subagent auto-approval events (method contains `autoApprovalReview`)
|
|
35
|
+
* - Unrecognised / unmapped methods
|
|
36
|
+
*
|
|
37
|
+
* Spec reference: §6.1, §6.2
|
|
38
|
+
*/
|
|
39
|
+
export function translateCodexRequestToPendingQuestion(
|
|
40
|
+
req: JsonLineRequest,
|
|
41
|
+
): PendingQuestion | null {
|
|
42
|
+
// Guardian Subagent bypass (PR #13860) — never surface as a pause
|
|
43
|
+
if (req.method.includes('autoApprovalReview')) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const params = asObject(req.params);
|
|
48
|
+
const requestId = String(req.id);
|
|
49
|
+
|
|
50
|
+
switch (req.method) {
|
|
51
|
+
// -- user_input --------------------------------------------------------
|
|
52
|
+
case 'tool/requestUserInput':
|
|
53
|
+
case 'item/tool/requestUserInput': {
|
|
54
|
+
const rawQuestions = asArray(params?.questions) ?? [];
|
|
55
|
+
const questions = rawQuestions.map((raw) => {
|
|
56
|
+
const q = asObject(raw) ?? {};
|
|
57
|
+
return {
|
|
58
|
+
id: asString(q.id) ?? '',
|
|
59
|
+
text: asString(q.text) ?? asString(q.question) ?? '',
|
|
60
|
+
...(asArray(q.options)
|
|
61
|
+
? { options: (q.options as unknown[]).map(String) }
|
|
62
|
+
: {}),
|
|
63
|
+
...(q.isOther !== undefined || q.allowEmpty !== undefined
|
|
64
|
+
? { allowFreeform: Boolean(q.isOther ?? q.allowEmpty) }
|
|
65
|
+
: {}),
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
return { type: 'user_input', requestId, questions };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// -- command_approval --------------------------------------------------
|
|
72
|
+
case 'item/commandExecution/requestApproval':
|
|
73
|
+
case 'execCommandApproval': {
|
|
74
|
+
const command = asString(params?.command) ?? '';
|
|
75
|
+
const sandboxPolicy = asString(params?.sandbox_policy) ?? asString(params?.sandboxPolicy);
|
|
76
|
+
return {
|
|
77
|
+
type: 'command_approval',
|
|
78
|
+
requestId,
|
|
79
|
+
command,
|
|
80
|
+
...(sandboxPolicy !== undefined ? { sandboxPolicy } : {}),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// -- file_approval -----------------------------------------------------
|
|
85
|
+
case 'item/fileChange/requestApproval':
|
|
86
|
+
case 'applyPatchApproval': {
|
|
87
|
+
const rawChanges = asArray(params?.file_changes) ?? asArray(params?.fileChanges) ?? [];
|
|
88
|
+
const fileChanges = rawChanges.map((raw) => {
|
|
89
|
+
const fc = asObject(raw) ?? {};
|
|
90
|
+
return {
|
|
91
|
+
path: asString(fc.path) ?? '',
|
|
92
|
+
patch: asString(fc.patch) ?? '',
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
return { type: 'file_approval', requestId, fileChanges };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// -- elicitation -------------------------------------------------------
|
|
99
|
+
case 'mcpServer/elicitation/request': {
|
|
100
|
+
const serverName = asString(params?.server_name) ?? asString(params?.serverName);
|
|
101
|
+
const message = asString(params?.message) ?? '';
|
|
102
|
+
const schema = params?.schema;
|
|
103
|
+
return {
|
|
104
|
+
type: 'elicitation',
|
|
105
|
+
requestId,
|
|
106
|
+
...(serverName !== undefined ? { serverName } : {}),
|
|
107
|
+
message,
|
|
108
|
+
...(schema !== undefined ? { schema } : {}),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// -- dynamic_tool ------------------------------------------------------
|
|
113
|
+
case 'item/tool/call': {
|
|
114
|
+
const toolName = asString(params?.tool_name) ?? asString(params?.toolName) ?? '';
|
|
115
|
+
const rawArgs = params?.arguments;
|
|
116
|
+
const args =
|
|
117
|
+
typeof rawArgs === 'string'
|
|
118
|
+
? rawArgs
|
|
119
|
+
: rawArgs !== undefined
|
|
120
|
+
? JSON.stringify(rawArgs)
|
|
121
|
+
: '{}';
|
|
122
|
+
return { type: 'dynamic_tool', requestId, toolName, arguments: args };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
default:
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
// Elicitation auto-decline (Issue #11816 workaround)
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
const ELICITATION_AUTO_DECLINE_MS = 30_000;
|
|
135
|
+
|
|
136
|
+
function scheduleElicitationAutoDecline(
|
|
137
|
+
client: AppServerClient,
|
|
138
|
+
handle: TaskHandle,
|
|
139
|
+
question: Extract<PendingQuestion, { type: 'elicitation' }>,
|
|
140
|
+
timeoutMs: number = ELICITATION_AUTO_DECLINE_MS,
|
|
141
|
+
): void {
|
|
142
|
+
const timer = setTimeout(() => {
|
|
143
|
+
const still = handle
|
|
144
|
+
.getPendingQuestions()
|
|
145
|
+
.find((q) => q.requestId === question.requestId);
|
|
146
|
+
if (!still) return;
|
|
147
|
+
|
|
148
|
+
client
|
|
149
|
+
.respondToServerRequest(question.requestId, {
|
|
150
|
+
action: 'decline',
|
|
151
|
+
content: {},
|
|
152
|
+
})
|
|
153
|
+
.catch(() => {});
|
|
154
|
+
|
|
155
|
+
const head = handle.getPendingQuestions()[0];
|
|
156
|
+
if (head?.requestId === question.requestId) {
|
|
157
|
+
handle.dequeuePendingQuestion();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
handle.writeOutput(
|
|
161
|
+
`[elicitation] auto-declined after ${timeoutMs}ms (issue #11816 workaround)`,
|
|
162
|
+
);
|
|
163
|
+
}, timeoutMs);
|
|
164
|
+
timer.unref();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
// Attachment: wire pause-flow onto an AppServerClient + TaskHandle
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
|
|
171
|
+
function extractThreadIdFromParams(params: unknown): string | undefined {
|
|
172
|
+
const obj = asObject(params);
|
|
173
|
+
const threadId = obj?.threadId;
|
|
174
|
+
if (typeof threadId === 'string') return threadId;
|
|
175
|
+
const thread = asObject(obj?.thread);
|
|
176
|
+
if (typeof thread?.id === 'string') return thread.id;
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Listen for `server-request` events on the AppServerClient, translate each
|
|
182
|
+
* to a PendingQuestion, queue it on the handle, and transition to
|
|
183
|
+
* WAITING_ANSWER. Returns an unsubscribe function.
|
|
184
|
+
*
|
|
185
|
+
* Spec reference: §6.2, §6.3
|
|
186
|
+
*/
|
|
187
|
+
export function attachPauseFlow(
|
|
188
|
+
client: AppServerClient,
|
|
189
|
+
handle: TaskHandle,
|
|
190
|
+
threadId: string,
|
|
191
|
+
): () => void {
|
|
192
|
+
const onServerRequest = (pending: PendingServerRequest) => {
|
|
193
|
+
const reqThreadId = extractThreadIdFromParams(pending.params);
|
|
194
|
+
if (reqThreadId !== threadId) return;
|
|
195
|
+
|
|
196
|
+
// Guardian Subagent bypass — log but don't surface
|
|
197
|
+
if (pending.method.includes('autoApprovalReview')) {
|
|
198
|
+
handle.writeOutputFileOnly(`[guardian] ${pending.method}`);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const req: JsonLineRequest = {
|
|
203
|
+
method: pending.method,
|
|
204
|
+
id: pending.id,
|
|
205
|
+
params: pending.params,
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const question = translateCodexRequestToPendingQuestion(req);
|
|
209
|
+
if (!question) return;
|
|
210
|
+
|
|
211
|
+
handle.queuePendingQuestion(question);
|
|
212
|
+
if (!handle.isTerminal()) {
|
|
213
|
+
handle.markInputRequired();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (question.type === 'elicitation') {
|
|
217
|
+
scheduleElicitationAutoDecline(client, handle, question);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
client.on('server-request', onServerRequest);
|
|
222
|
+
return () => {
|
|
223
|
+
client.off('server-request', onServerRequest);
|
|
224
|
+
};
|
|
225
|
+
}
|