@ckanner/dsh-plugin-flywheel 0.27.3 → 0.28.1
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/PROTOCOL.md +84 -4
- package/README.en.md +349 -0
- package/README.md +60 -13
- package/THIRD_PARTY_NOTICES.md +38 -0
- package/dist/client.js +699 -172
- package/dist/codex-pro-bridge.js +60 -0
- package/dist/codex-pro-worker.js +117 -0
- package/dist/control.js +36 -10
- package/dist/index.js +17 -3
- package/package.json +19 -7
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Public machine API consumer. Go remains the owner of scheduling and acceptance.
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
|
|
4
|
+
export const ADAPTER = Object.freeze({ package: 'dsh-subagent-codex-pro', version: '0.8.0', protocol: 1 });
|
|
5
|
+
export const LIMITS = Object.freeze({ requestBytes: 1024 * 1024, machineOutputBytes: 1024 * 1024,
|
|
6
|
+
progressBytes: 1024 * 1024, receiptFrameBytes: 8 * 1024 * 1024, admissionMs: 10000 });
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
|
|
9
|
+
export class WorkerError extends Error {
|
|
10
|
+
constructor(code, message) { super(message); this.name = 'WorkerError'; this.code = code; }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** The doctor executes this same import, but never calls runMachine. */
|
|
14
|
+
export async function loadAdapter() {
|
|
15
|
+
const [major, minor] = process.versions.node.split('.').map(Number);
|
|
16
|
+
if (major < 22 || (major === 22 && minor < 18)) throw new WorkerError('UNSUPPORTED_RUNTIME', 'Codex Pro requires Node.js 22.18.0 or newer');
|
|
17
|
+
if (!['darwin', 'linux'].includes(process.platform)) throw new WorkerError('UNSUPPORTED_RUNTIME', 'Codex Pro requires a supported POSIX platform');
|
|
18
|
+
let manifest, api;
|
|
19
|
+
try {
|
|
20
|
+
manifest = require('dsh-subagent-codex-pro/package.json');
|
|
21
|
+
} catch {
|
|
22
|
+
throw new WorkerError('ADAPTER_UNAVAILABLE', 'Install the exact dsh-subagent-codex-pro@0.8.0 dependency with Flywheel');
|
|
23
|
+
}
|
|
24
|
+
if (manifest.name !== ADAPTER.package || manifest.version !== ADAPTER.version) {
|
|
25
|
+
throw new WorkerError('ADAPTER_MISMATCH', 'Codex Pro package identity or version does not match the pinned contract');
|
|
26
|
+
}
|
|
27
|
+
try { api = await import('dsh-subagent-codex-pro/machine'); }
|
|
28
|
+
catch { throw new WorkerError('ADAPTER_UNAVAILABLE', 'The pinned Codex Pro public machine API could not be imported'); }
|
|
29
|
+
if (api.MACHINE_PROTOCOL_VERSION !== ADAPTER.protocol ||
|
|
30
|
+
typeof api.parseMachineRequest !== 'function' || typeof api.runMachine !== 'function' || typeof api.MachineInputError !== 'function') {
|
|
31
|
+
throw new WorkerError('ADAPTER_MISMATCH', 'Codex Pro package identity, version or public machine API does not match the pinned contract');
|
|
32
|
+
}
|
|
33
|
+
return api;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Reject unsupported fields and permissions before any process or prompt starts. */
|
|
37
|
+
export function machineRequest(value, api, env) {
|
|
38
|
+
const keys = new Set(['type', 'protocol', 'requestId', 'cwd', 'model', 'reasoningEffort', 'permissionMode', 'prompt', 'timeoutMs']);
|
|
39
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value) || value.type !== 'start' || value.protocol !== 1 ||
|
|
40
|
+
Object.keys(value).some(key => !keys.has(key)) || typeof value.requestId !== 'string' || value.requestId.length > 256 ||
|
|
41
|
+
!['read-only', 'workspace-write'].includes(value.permissionMode)) {
|
|
42
|
+
throw new WorkerError('INVALID_REQUEST', 'Expected one protocol=1 start request with an explicit supported permission mode');
|
|
43
|
+
}
|
|
44
|
+
if (value.timeoutMs !== undefined && (!Number.isSafeInteger(value.timeoutMs) || value.timeoutMs < 1 || value.timeoutMs > 2147483647)) {
|
|
45
|
+
throw new WorkerError('INVALID_REQUEST', 'timeoutMs must be a positive safe integer no greater than 2147483647');
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
return api.parseMachineRequest({ protocol: 1, requestId: value.requestId, cwd: value.cwd, model: value.model,
|
|
49
|
+
effort: value.reasoningEffort, permissionMode: value.permissionMode, prompt: value.prompt,
|
|
50
|
+
...(value.timeoutMs === undefined ? {} : { timeoutMs: value.timeoutMs }),
|
|
51
|
+
command: env.FLYWHEEL_CODEX_BIN || 'codex', threadMode: 'ephemeral', maxOutputBytes: LIMITS.machineOutputBytes });
|
|
52
|
+
} catch (error) {
|
|
53
|
+
throw new WorkerError('INVALID_REQUEST', error instanceof api.MachineInputError ? error.message : 'Invalid Codex Pro machine request');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function doctorResult() {
|
|
58
|
+
return { protocol: 1, type: 'doctor', package: ADAPTER.package, version: ADAPTER.version,
|
|
59
|
+
machineProtocol: ADAPTER.protocol, available: true, inferenceTested: false };
|
|
60
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// One owned invocation. stdin remains a lifeline to the Go parent after admission.
|
|
3
|
+
import { ADAPTER, LIMITS, WorkerError, loadAdapter, machineRequest, doctorResult } from './codex-pro-bridge.js';
|
|
4
|
+
|
|
5
|
+
const controller = new AbortController();
|
|
6
|
+
let cancelledBy, inputFailure, outputFailure, requestId = 'invalid-request', finished = false;
|
|
7
|
+
const cancel = signal => { cancelledBy ??= signal; controller.abort(); };
|
|
8
|
+
const handlers = new Map(['SIGTERM', 'SIGINT', 'SIGHUP'].map(signal => [signal, () => cancel(signal)]));
|
|
9
|
+
for (const [signal, handler] of handlers) process.on(signal, handler);
|
|
10
|
+
process.stdout.on('error', () => {
|
|
11
|
+
outputFailure ??= new WorkerError('OUTPUT_ERROR', 'The result consumer closed or failed');
|
|
12
|
+
process.exitCode = 1;
|
|
13
|
+
controller.abort();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
function write(frame) {
|
|
17
|
+
if (outputFailure) return;
|
|
18
|
+
const line = JSON.stringify(frame) + '\n';
|
|
19
|
+
if (Buffer.byteLength(line) > LIMITS.receiptFrameBytes) throw new WorkerError('OUTPUT_LIMIT', 'Codex Pro result exceeded the complete receipt frame limit');
|
|
20
|
+
process.stdout.write(line);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Do not close stdin after the first line: parent death must cancel the run. */
|
|
24
|
+
function input() {
|
|
25
|
+
let bytes = 0, buffer = Buffer.alloc(0), admitted = false, resolveInput, rejectInput;
|
|
26
|
+
const promise = new Promise((resolve, reject) => { resolveInput = resolve; rejectInput = reject; });
|
|
27
|
+
const fail = (code, message) => {
|
|
28
|
+
if (finished) return;
|
|
29
|
+
inputFailure ??= new WorkerError(code, message);
|
|
30
|
+
clearTimeout(timer);
|
|
31
|
+
rejectInput(inputFailure);
|
|
32
|
+
controller.abort();
|
|
33
|
+
};
|
|
34
|
+
const timer = setTimeout(() => fail('INPUT_TIMEOUT', 'No complete start request arrived within 10 seconds'), LIMITS.admissionMs);
|
|
35
|
+
const onData = chunk => {
|
|
36
|
+
bytes += chunk.length;
|
|
37
|
+
if (bytes > LIMITS.requestBytes) { fail('INPUT_LIMIT', 'The request exceeded the 1 MiB input limit'); return; }
|
|
38
|
+
if (admitted) {
|
|
39
|
+
if (chunk.toString('utf8').trim() !== '') fail('INVALID_REQUEST', 'Only one start request is allowed');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
43
|
+
const newline = buffer.indexOf(10);
|
|
44
|
+
if (newline < 0) return;
|
|
45
|
+
admitted = true;
|
|
46
|
+
clearTimeout(timer);
|
|
47
|
+
if (buffer.subarray(newline + 1).toString('utf8').trim() !== '') { fail('INVALID_REQUEST', 'Only one start request is allowed'); return; }
|
|
48
|
+
try { resolveInput(JSON.parse(buffer.subarray(0, newline).toString('utf8'))); }
|
|
49
|
+
catch { fail('INVALID_REQUEST', 'The first line must contain one JSON request'); }
|
|
50
|
+
buffer = Buffer.alloc(0);
|
|
51
|
+
};
|
|
52
|
+
const onEnd = () => fail('PARENT_DISCONNECTED', 'The parent stdin lifeline closed before completion');
|
|
53
|
+
const onError = () => fail('INPUT_ERROR', 'The parent stdin lifeline failed');
|
|
54
|
+
const onAbort = () => { clearTimeout(timer); rejectInput(new WorkerError('CANCELLED', 'Codex Pro worker was cancelled before admission')); };
|
|
55
|
+
process.stdin.on('data', onData);
|
|
56
|
+
process.stdin.once('end', onEnd);
|
|
57
|
+
process.stdin.once('error', onError);
|
|
58
|
+
controller.signal.addEventListener('abort', onAbort, { once: true });
|
|
59
|
+
if (controller.signal.aborted) onAbort();
|
|
60
|
+
return { promise, stop() {
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
process.stdin.off('data', onData); process.stdin.off('end', onEnd); process.stdin.off('error', onError);
|
|
63
|
+
controller.signal.removeEventListener('abort', onAbort);
|
|
64
|
+
process.stdin.pause();
|
|
65
|
+
process.stdin.destroy();
|
|
66
|
+
} };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let lifeline;
|
|
70
|
+
try {
|
|
71
|
+
const args = process.argv.slice(2);
|
|
72
|
+
if (args.length > 1 || (args.length === 1 && args[0] !== '--doctor')) throw new WorkerError('INVALID_REQUEST', 'Use flywheel-codex-pro-worker [--doctor]');
|
|
73
|
+
if (args[0] === '--doctor') {
|
|
74
|
+
await loadAdapter();
|
|
75
|
+
write(doctorResult());
|
|
76
|
+
process.exitCode = 0;
|
|
77
|
+
} else {
|
|
78
|
+
lifeline = input();
|
|
79
|
+
const value = await lifeline.promise;
|
|
80
|
+
if (typeof value?.requestId === 'string' && value.requestId.trim() !== '' && value.requestId.length <= 256) requestId = value.requestId;
|
|
81
|
+
const api = await loadAdapter();
|
|
82
|
+
if (controller.signal.aborted) throw inputFailure || new WorkerError('CANCELLED', 'Codex Pro worker was cancelled before the run');
|
|
83
|
+
const request = machineRequest(value, api, process.env);
|
|
84
|
+
requestId = request.requestId;
|
|
85
|
+
let progressBytes = 0;
|
|
86
|
+
const receipt = await api.runMachine(request, { signal: controller.signal, env: process.env, onProgress(text) {
|
|
87
|
+
if (controller.signal.aborted) return;
|
|
88
|
+
const frame = { protocol: 1, type: 'progress', requestId, text };
|
|
89
|
+
progressBytes += Buffer.byteLength(JSON.stringify(frame) + '\n');
|
|
90
|
+
if (progressBytes > LIMITS.progressBytes) {
|
|
91
|
+
outputFailure ??= new WorkerError('OUTPUT_LIMIT', 'Codex Pro progress exceeded the 1 MiB stream limit');
|
|
92
|
+
controller.abort();
|
|
93
|
+
} else write(frame);
|
|
94
|
+
} });
|
|
95
|
+
finished = true;
|
|
96
|
+
lifeline.stop();
|
|
97
|
+
const error = inputFailure || outputFailure;
|
|
98
|
+
// An output-limit error still allows a bounded terminal receipt. A broken
|
|
99
|
+
// stdout cannot, but the run has already settled and cleanup was awaited.
|
|
100
|
+
if (outputFailure?.code === 'OUTPUT_LIMIT') outputFailure = undefined;
|
|
101
|
+
write({ protocol: 1, type: 'result', requestId, adapter: ADAPTER, receipt,
|
|
102
|
+
error: error ? { code: error.code, message: error.message } : null });
|
|
103
|
+
process.exitCode = receipt.ok && !error && !controller.signal.aborted ? 0 : 1;
|
|
104
|
+
}
|
|
105
|
+
} catch (error) {
|
|
106
|
+
finished = true;
|
|
107
|
+
lifeline?.stop();
|
|
108
|
+
const known = error instanceof WorkerError ? error : new WorkerError('WORKER_ERROR', 'Codex Pro worker failed before a complete receipt was available');
|
|
109
|
+
write({ protocol: 1, type: 'result', requestId, adapter: ADAPTER, receipt: null,
|
|
110
|
+
error: { code: known.code, message: known.message } });
|
|
111
|
+
process.exitCode = 1;
|
|
112
|
+
} finally {
|
|
113
|
+
finished = true;
|
|
114
|
+
lifeline?.stop();
|
|
115
|
+
if (cancelledBy) process.exitCode = { SIGINT: 130, SIGTERM: 143, SIGHUP: 129 }[cancelledBy];
|
|
116
|
+
for (const [signal, handler] of handlers) process.off(signal, handler);
|
|
117
|
+
}
|
package/dist/control.js
CHANGED
|
@@ -4,6 +4,15 @@ import { directory, nonempty, record } from './protocol.js';
|
|
|
4
4
|
const actions = ['start', 'status', 'stop', 'config', 'doctor'];
|
|
5
5
|
const checkStatuses = ['pass', 'fail', 'warn', 'unknown', 'skipped'];
|
|
6
6
|
|
|
7
|
+
// This marker is issued only where the host can prove that no control process
|
|
8
|
+
// started. An engine/transport error after spawn must never acquire it.
|
|
9
|
+
export class ControlNotDispatchedError extends Error {
|
|
10
|
+
constructor(cause) {
|
|
11
|
+
super(cause instanceof Error ? cause.message : String(cause), { cause });
|
|
12
|
+
this.name = 'ControlNotDispatchedError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
7
16
|
export async function validateControl(input, defaultCwd) {
|
|
8
17
|
if (!record(input) || !actions.includes(input.action)) {
|
|
9
18
|
throw new Error('action must be start, status, stop, config, or doctor');
|
|
@@ -46,13 +55,18 @@ function validDiagnostic(value, request) {
|
|
|
46
55
|
|
|
47
56
|
/** Only the engine executable can mutate engine state. No shell interpolation. */
|
|
48
57
|
export async function runControl(input, { binary = 'flywheel', defaultCwd, signal, timeoutMs = 60000 } = {}) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
throw new Error('
|
|
58
|
+
let request;
|
|
59
|
+
try {
|
|
60
|
+
nonempty(binary, 'flywheelBinary', 4096);
|
|
61
|
+
if (binary.startsWith('-')) throw new Error('flywheelBinary must name an executable');
|
|
62
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 100 || timeoutMs > 120000) {
|
|
63
|
+
throw new Error('controlTimeoutMs must be between 100 and 120000');
|
|
64
|
+
}
|
|
65
|
+
request = await validateControl(input, defaultCwd);
|
|
66
|
+
if (signal?.aborted) throw new Error('Flywheel control request cancelled');
|
|
67
|
+
} catch (error) {
|
|
68
|
+
throw new ControlNotDispatchedError(error);
|
|
53
69
|
}
|
|
54
|
-
const request = await validateControl(input, defaultCwd);
|
|
55
|
-
if (signal?.aborted) throw new Error('Flywheel control request cancelled');
|
|
56
70
|
// Diagnostic commands own watchdog child groups. Its TERM grace, pipe
|
|
57
71
|
// WaitDelay, and quiescence confirmation may each take 5s; keep the Go owner
|
|
58
72
|
// alive long enough to finish all three instead of orphaning its workers.
|
|
@@ -61,9 +75,17 @@ export async function runControl(input, { binary = 'flywheel', defaultCwd, signa
|
|
|
61
75
|
if (request.taskDir) args.push('--task-dir', request.taskDir);
|
|
62
76
|
if (request.probe) args.push('--probe');
|
|
63
77
|
return new Promise((resolve, reject) => {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
78
|
+
let child;
|
|
79
|
+
try {
|
|
80
|
+
child = spawn(binary, args, {
|
|
81
|
+
cwd: request.cwd, detached: false, stdio: ['ignore', 'pipe', 'pipe'],
|
|
82
|
+
});
|
|
83
|
+
} catch (error) {
|
|
84
|
+
reject(new ControlNotDispatchedError(error));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
let spawned = false;
|
|
88
|
+
child.once('spawn', () => { spawned = true; });
|
|
67
89
|
let stdout = '';
|
|
68
90
|
let stderr = '';
|
|
69
91
|
let failure;
|
|
@@ -88,7 +110,11 @@ export async function runControl(input, { binary = 'flywheel', defaultCwd, signa
|
|
|
88
110
|
if (Buffer.byteLength(stdout) > 1024 * 1024) stop(new Error('Flywheel control response exceeds 1 MiB'));
|
|
89
111
|
});
|
|
90
112
|
child.stderr.on('data', chunk => { stderr = (stderr + chunk).slice(-8192); });
|
|
91
|
-
child.on('error', error => {
|
|
113
|
+
child.on('error', error => {
|
|
114
|
+
// Node emits spawn before other events after successful process creation.
|
|
115
|
+
// Require both the missing spawn event and missing PID for positive proof.
|
|
116
|
+
failure = !spawned && child.pid === undefined ? new ControlNotDispatchedError(error) : error;
|
|
117
|
+
});
|
|
92
118
|
child.on('close', (code, childSignal) => {
|
|
93
119
|
clearTimeout(timer);
|
|
94
120
|
clearTimeout(killTimer);
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { runControl } from './control.js';
|
|
1
|
+
import { ControlNotDispatchedError, runControl } from './control.js';
|
|
2
2
|
import { resolveFlywheelBinary } from './installation.js';
|
|
3
3
|
|
|
4
4
|
export const inject = ['commands', 'tools'];
|
|
@@ -33,11 +33,25 @@ export function apply(ctx, config = {}) {
|
|
|
33
33
|
description: '通过 Go 引擎控制 Flywheel 任务、查看实际模型配置或执行无推理环境检查。',
|
|
34
34
|
input: { hint: '{"action":"doctor"} 或 {"action":"status","taskDir":"/absolute/docs/task"}' },
|
|
35
35
|
async handler({ rawInput, agent, signal }) {
|
|
36
|
+
let input;
|
|
36
37
|
try {
|
|
37
|
-
|
|
38
|
+
try {
|
|
39
|
+
input = JSON.parse(rawInput);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
throw new ControlNotDispatchedError(error);
|
|
42
|
+
}
|
|
43
|
+
const value = await control(input, agent, signal);
|
|
38
44
|
return { kind: 'success', text: JSON.stringify(value, null, 2) };
|
|
39
45
|
} catch (error) {
|
|
40
|
-
|
|
46
|
+
// CommandResult preserves only kind/text. Wrap every failure, including
|
|
47
|
+
// unknown outcomes, so a child error's text cannot impersonate host proof.
|
|
48
|
+
return { kind: 'error', text: JSON.stringify({
|
|
49
|
+
protocol: 1,
|
|
50
|
+
type: 'flywheel-control-error',
|
|
51
|
+
action: typeof input?.action === 'string' ? input.action : null,
|
|
52
|
+
dispatch: error instanceof ControlNotDispatchedError ? 'not-dispatched' : 'unknown',
|
|
53
|
+
message: error instanceof Error ? error.message : String(error),
|
|
54
|
+
}, null, 2) };
|
|
41
55
|
}
|
|
42
56
|
},
|
|
43
57
|
});
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ckanner/dsh-plugin-flywheel",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Flywheel engine controls
|
|
3
|
+
"version": "0.28.1",
|
|
4
|
+
"description": "Flywheel engine controls with owned DSH SDK and Codex Pro machine workers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
7
|
"engines": {
|
|
8
|
-
"node": ">=22.
|
|
8
|
+
"node": ">=22.18.0",
|
|
9
9
|
"dsh": "0.2.1-rc.5"
|
|
10
10
|
},
|
|
11
11
|
"exports": {
|
|
@@ -13,16 +13,20 @@
|
|
|
13
13
|
"./client": "./dist/client.js",
|
|
14
14
|
"./worker-policy": "./dist/policy.js",
|
|
15
15
|
"./worker": "./dist/worker.js",
|
|
16
|
+
"./codex-pro-worker": "./dist/codex-pro-worker.js",
|
|
16
17
|
"./package.json": "./package.json"
|
|
17
18
|
},
|
|
18
19
|
"bin": {
|
|
19
|
-
"flywheel-dsh-worker": "./dist/worker.js"
|
|
20
|
+
"flywheel-dsh-worker": "./dist/worker.js",
|
|
21
|
+
"flywheel-codex-pro-worker": "./dist/codex-pro-worker.js"
|
|
20
22
|
},
|
|
21
23
|
"files": [
|
|
22
24
|
"dist",
|
|
23
25
|
"cordis.patch.yml",
|
|
24
26
|
"README.md",
|
|
25
|
-
"
|
|
27
|
+
"README.en.md",
|
|
28
|
+
"PROTOCOL.md",
|
|
29
|
+
"THIRD_PARTY_NOTICES.md"
|
|
26
30
|
],
|
|
27
31
|
"dsh": {
|
|
28
32
|
"bundle": {
|
|
@@ -30,9 +34,10 @@
|
|
|
30
34
|
},
|
|
31
35
|
"client": {
|
|
32
36
|
"inject": [
|
|
33
|
-
"@deepseek-ai/dsh-client-ui-
|
|
37
|
+
"@deepseek-ai/dsh-client-ui-conversation",
|
|
34
38
|
"@deepseek-ai/dsh-client-ui-commands",
|
|
35
|
-
"@deepseek-ai/dsh-api-remotes"
|
|
39
|
+
"@deepseek-ai/dsh-api-remotes",
|
|
40
|
+
"@deepseek-ai/dsh-client-locale"
|
|
36
41
|
],
|
|
37
42
|
"platform": "web"
|
|
38
43
|
}
|
|
@@ -59,8 +64,15 @@
|
|
|
59
64
|
"check": "npm test",
|
|
60
65
|
"prepack": "npm run build"
|
|
61
66
|
},
|
|
67
|
+
"dependencies": {
|
|
68
|
+
"dsh-subagent-codex-pro": "0.8.0"
|
|
69
|
+
},
|
|
62
70
|
"devDependencies": {
|
|
71
|
+
"@deepseek-ai/cordis": "4.0.5",
|
|
72
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.2.1-rc.5",
|
|
73
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.5",
|
|
63
74
|
"jsdom": "26.1.0",
|
|
75
|
+
"lightningcss": "1.33.0",
|
|
64
76
|
"react": "18.3.1",
|
|
65
77
|
"react-dom": "18.3.1"
|
|
66
78
|
},
|