@ckanner/dsh-plugin-flywheel 0.28.0 → 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 +27 -0
- package/README.en.md +111 -7
- package/README.md +32 -6
- package/THIRD_PARTY_NOTICES.md +38 -0
- package/dist/client.js +699 -172
- package/dist/control.js +36 -10
- package/dist/index.js +17 -3
- package/package.json +10 -4
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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ckanner/dsh-plugin-flywheel",
|
|
3
|
-
"version": "0.28.
|
|
3
|
+
"version": "0.28.1",
|
|
4
4
|
"description": "Flywheel engine controls with owned DSH SDK and Codex Pro machine workers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"cordis.patch.yml",
|
|
26
26
|
"README.md",
|
|
27
27
|
"README.en.md",
|
|
28
|
-
"PROTOCOL.md"
|
|
28
|
+
"PROTOCOL.md",
|
|
29
|
+
"THIRD_PARTY_NOTICES.md"
|
|
29
30
|
],
|
|
30
31
|
"dsh": {
|
|
31
32
|
"bundle": {
|
|
@@ -33,9 +34,10 @@
|
|
|
33
34
|
},
|
|
34
35
|
"client": {
|
|
35
36
|
"inject": [
|
|
36
|
-
"@deepseek-ai/dsh-client-ui-
|
|
37
|
+
"@deepseek-ai/dsh-client-ui-conversation",
|
|
37
38
|
"@deepseek-ai/dsh-client-ui-commands",
|
|
38
|
-
"@deepseek-ai/dsh-api-remotes"
|
|
39
|
+
"@deepseek-ai/dsh-api-remotes",
|
|
40
|
+
"@deepseek-ai/dsh-client-locale"
|
|
39
41
|
],
|
|
40
42
|
"platform": "web"
|
|
41
43
|
}
|
|
@@ -66,7 +68,11 @@
|
|
|
66
68
|
"dsh-subagent-codex-pro": "0.8.0"
|
|
67
69
|
},
|
|
68
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",
|
|
69
74
|
"jsdom": "26.1.0",
|
|
75
|
+
"lightningcss": "1.33.0",
|
|
70
76
|
"react": "18.3.1",
|
|
71
77
|
"react-dom": "18.3.1"
|
|
72
78
|
},
|