@feng0531/dsh-execution-bus 0.12.0 → 1.0.0
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 +47 -264
- package/cordis.patch.yml +1 -1
- package/dist/error-codes.d.ts +14 -0
- package/dist/error-codes.js +15 -0
- package/dist/mode.d.ts +2 -2
- package/dist/mode.js +7 -7
- package/dist/plugin.d.ts +2 -6
- package/dist/plugin.js +28 -232
- package/dist/preset.d.ts +1 -1
- package/dist/preset.js +4 -6
- package/dist/product.d.ts +91 -195
- package/dist/product.js +66 -146
- package/dist/runtime/executor.d.ts +18 -0
- package/dist/runtime/executor.js +239 -0
- package/dist/runtime/plan.d.ts +30 -0
- package/dist/runtime/plan.js +185 -0
- package/dist/runtime/service.d.ts +66 -0
- package/dist/runtime/service.js +185 -0
- package/dist/runtime/store.d.ts +45 -0
- package/dist/runtime/store.js +102 -0
- package/docs/DECLARATIVE-RUNTIME.md +44 -0
- package/docs/FLOW-TEST.md +7 -54
- package/docs/INTEGRATION.md +4 -2
- package/docs/OPTIMIZATION.md +2 -0
- package/docs/PRODUCT.md +15 -40
- package/docs/RETROSPECTIVE.md +51 -0
- package/docs/host-compatibility.md +11 -74
- package/docs/protocol.md +27 -120
- package/docs/releases/0.11.1.md +2 -0
- package/docs/releases/0.12.1.md +30 -0
- package/docs/releases/1.0.0.md +33 -0
- package/docs/validation.md +23 -61
- package/package.json +4 -6
- package/dist/bus.d.ts +0 -148
- package/dist/bus.js +0 -773
- package/dist/diagnostics.d.ts +0 -41
- package/dist/diagnostics.js +0 -32
- package/dist/execution.d.ts +0 -70
- package/dist/execution.js +0 -193
- package/dist/handlers.d.ts +0 -25
- package/dist/handlers.js +0 -194
- package/dist/json.d.ts +0 -4
- package/dist/json.js +0 -46
- package/dist/limits.d.ts +0 -20
- package/dist/limits.js +0 -23
- package/dist/protocol.d.ts +0 -62
- package/dist/protocol.js +0 -54
package/dist/diagnostics.d.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
export type TraceStage = "accepted" | "started" | "cancel_requested" | "terminal" | "returned" | "job_created" | "job_released" | "job_error" | "reused" | "unique_reused";
|
|
2
|
-
export interface Trace {
|
|
3
|
-
operation: number;
|
|
4
|
-
stage: TraceStage;
|
|
5
|
-
elapsedMs: number;
|
|
6
|
-
}
|
|
7
|
-
/** In-memory numeric metadata only: never accepts request/session strings, input, output or error text. */
|
|
8
|
-
export declare class Diagnostics {
|
|
9
|
-
private next;
|
|
10
|
-
private events;
|
|
11
|
-
private cursor;
|
|
12
|
-
private size;
|
|
13
|
-
private counts;
|
|
14
|
-
begin(): {
|
|
15
|
-
operation: number;
|
|
16
|
-
at: number;
|
|
17
|
-
};
|
|
18
|
-
record(context: {
|
|
19
|
-
operation: number;
|
|
20
|
-
at: number;
|
|
21
|
-
}, stage: TraceStage): void;
|
|
22
|
-
snapshot(): {
|
|
23
|
-
counts: {
|
|
24
|
-
accepted?: number | undefined;
|
|
25
|
-
started?: number | undefined;
|
|
26
|
-
cancel_requested?: number | undefined;
|
|
27
|
-
terminal?: number | undefined;
|
|
28
|
-
returned?: number | undefined;
|
|
29
|
-
job_created?: number | undefined;
|
|
30
|
-
job_released?: number | undefined;
|
|
31
|
-
job_error?: number | undefined;
|
|
32
|
-
reused?: number | undefined;
|
|
33
|
-
unique_reused?: number | undefined;
|
|
34
|
-
};
|
|
35
|
-
traces: {
|
|
36
|
-
operation: number;
|
|
37
|
-
stage: TraceStage;
|
|
38
|
-
elapsedMs: number;
|
|
39
|
-
}[];
|
|
40
|
-
};
|
|
41
|
-
}
|
package/dist/diagnostics.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { performance } from "node:perf_hooks";
|
|
2
|
-
import { LIMITS } from "./limits.js";
|
|
3
|
-
/** In-memory numeric metadata only: never accepts request/session strings, input, output or error text. */
|
|
4
|
-
export class Diagnostics {
|
|
5
|
-
next = 0;
|
|
6
|
-
events = new Array(LIMITS.traceRingSize);
|
|
7
|
-
cursor = 0;
|
|
8
|
-
size = 0;
|
|
9
|
-
counts = {};
|
|
10
|
-
begin() {
|
|
11
|
-
return { operation: ++this.next, at: performance.now() };
|
|
12
|
-
}
|
|
13
|
-
record(context, stage) {
|
|
14
|
-
this.counts[stage] = (this.counts[stage] ?? 0) + 1;
|
|
15
|
-
this.events[this.cursor] = {
|
|
16
|
-
operation: context.operation,
|
|
17
|
-
stage,
|
|
18
|
-
elapsedMs: Math.max(0, performance.now() - context.at),
|
|
19
|
-
};
|
|
20
|
-
this.cursor = (this.cursor + 1) % LIMITS.traceRingSize;
|
|
21
|
-
this.size = Math.min(this.size + 1, LIMITS.traceRingSize);
|
|
22
|
-
}
|
|
23
|
-
snapshot() {
|
|
24
|
-
return {
|
|
25
|
-
counts: { ...this.counts },
|
|
26
|
-
traces: Array.from({ length: this.size }, (_, index) => ({
|
|
27
|
-
...this.events[(this.cursor - this.size + LIMITS.traceRingSize + index) %
|
|
28
|
-
LIMITS.traceRingSize],
|
|
29
|
-
})),
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
}
|
package/dist/execution.d.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import type { JobRegistry, JobId } from "@deepseek-ai/dsh-jobs";
|
|
2
|
-
import type { SessionId } from "@deepseek-ai/dsh-session";
|
|
3
|
-
import type { RecordView, Event } from "./bus.js";
|
|
4
|
-
import { Diagnostics } from "./diagnostics.js";
|
|
5
|
-
export interface ExecutionAttachment {
|
|
6
|
-
done: Promise<RecordView>;
|
|
7
|
-
cancel(): void;
|
|
8
|
-
}
|
|
9
|
-
/** One host projection of a logical execution. The core completion remains authoritative. */
|
|
10
|
-
export declare class ExecutionHandle {
|
|
11
|
-
private jobs;
|
|
12
|
-
private owner;
|
|
13
|
-
private execution;
|
|
14
|
-
private telemetry;
|
|
15
|
-
readonly completion: Promise<RecordView>;
|
|
16
|
-
readonly settled: Promise<void>;
|
|
17
|
-
readonly job: JobId;
|
|
18
|
-
private progressHandle?;
|
|
19
|
-
private trace;
|
|
20
|
-
constructor(jobs: JobRegistry, owner: SessionId, execution: ExecutionAttachment, telemetry?: Diagnostics);
|
|
21
|
-
requestCancel(): void;
|
|
22
|
-
progress(event: Event): void;
|
|
23
|
-
private absent;
|
|
24
|
-
private waitAndClean;
|
|
25
|
-
/** 等待宿主 Job 到达终态(离开 running/stopping)。 */
|
|
26
|
-
private awaitTerminal;
|
|
27
|
-
/** wait 失败后的有界恢复:清理已终态的 Job,但仍向上保留原始错误可见性。 */
|
|
28
|
-
private recoverOnWaitError;
|
|
29
|
-
}
|
|
30
|
-
export type ShutdownPhase = "open" | "stopping" | "draining" | "releasing" | "closed";
|
|
31
|
-
/** Owns host projections and active deliveries, not a second execution state machine. */
|
|
32
|
-
export declare class ExecutionOwner {
|
|
33
|
-
private handles;
|
|
34
|
-
private deliveries;
|
|
35
|
-
private closing?;
|
|
36
|
-
private telemetry;
|
|
37
|
-
create(key: string, jobs: JobRegistry, owner: SessionId, execution: ExecutionAttachment): ExecutionHandle;
|
|
38
|
-
diagnostics(): {
|
|
39
|
-
phase: ShutdownPhase;
|
|
40
|
-
activeProjections: number;
|
|
41
|
-
deliveries: number;
|
|
42
|
-
counts: {
|
|
43
|
-
accepted?: number | undefined;
|
|
44
|
-
started?: number | undefined;
|
|
45
|
-
cancel_requested?: number | undefined;
|
|
46
|
-
terminal?: number | undefined;
|
|
47
|
-
returned?: number | undefined;
|
|
48
|
-
job_created?: number | undefined;
|
|
49
|
-
job_released?: number | undefined;
|
|
50
|
-
job_error?: number | undefined;
|
|
51
|
-
reused?: number | undefined;
|
|
52
|
-
unique_reused?: number | undefined;
|
|
53
|
-
};
|
|
54
|
-
traces: {
|
|
55
|
-
operation: number;
|
|
56
|
-
stage: import("./diagnostics.js").TraceStage;
|
|
57
|
-
elapsedMs: number;
|
|
58
|
-
}[];
|
|
59
|
-
};
|
|
60
|
-
private currentPhase;
|
|
61
|
-
get phase(): ShutdownPhase;
|
|
62
|
-
get(key: string): ExecutionHandle | undefined;
|
|
63
|
-
add(key: string, handle: ExecutionHandle): void;
|
|
64
|
-
/** 拒绝关闭后或重复 key 的准入;create/add 共用这一道守卫。 */
|
|
65
|
-
private assertAdmittable;
|
|
66
|
-
/** 登记 handle,并在其结算后从表中移除该登记。 */
|
|
67
|
-
private attach;
|
|
68
|
-
track<T>(delivery: Promise<T>): Promise<T>;
|
|
69
|
-
close(drainCore: () => Promise<void>): Promise<void>;
|
|
70
|
-
}
|
package/dist/execution.js
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import { setTimeout as pause } from "node:timers/promises";
|
|
2
|
-
import { Diagnostics } from "./diagnostics.js";
|
|
3
|
-
import { ERRORS } from "./error-codes.js";
|
|
4
|
-
/** 把执行终态映射为宿主 Job 的结果状态。 */
|
|
5
|
-
function jobOutcome(state) {
|
|
6
|
-
return {
|
|
7
|
-
status: state === "succeeded"
|
|
8
|
-
? "completed"
|
|
9
|
-
: state === "cancelled"
|
|
10
|
-
? "killed"
|
|
11
|
-
: "failed",
|
|
12
|
-
detail: state,
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
/** One host projection of a logical execution. The core completion remains authoritative. */
|
|
16
|
-
export class ExecutionHandle {
|
|
17
|
-
jobs;
|
|
18
|
-
owner;
|
|
19
|
-
execution;
|
|
20
|
-
telemetry;
|
|
21
|
-
completion;
|
|
22
|
-
settled;
|
|
23
|
-
job;
|
|
24
|
-
progressHandle;
|
|
25
|
-
trace;
|
|
26
|
-
constructor(jobs, owner, execution, telemetry = new Diagnostics()) {
|
|
27
|
-
this.jobs = jobs;
|
|
28
|
-
this.owner = owner;
|
|
29
|
-
this.execution = execution;
|
|
30
|
-
this.telemetry = telemetry;
|
|
31
|
-
this.trace = telemetry.begin();
|
|
32
|
-
this.completion = execution.done;
|
|
33
|
-
const outcome = this.completion.then((v) => jobOutcome(v.state));
|
|
34
|
-
this.job = jobs.start({
|
|
35
|
-
kind: "execution-bus",
|
|
36
|
-
label: "萧何 / Xiao He",
|
|
37
|
-
owner,
|
|
38
|
-
run: (handle) => {
|
|
39
|
-
this.progressHandle = handle;
|
|
40
|
-
return { done: outcome, cancel: () => this.requestCancel() };
|
|
41
|
-
},
|
|
42
|
-
});
|
|
43
|
-
telemetry.record(this.trace, "job_created");
|
|
44
|
-
this.settled = this.waitAndClean();
|
|
45
|
-
}
|
|
46
|
-
requestCancel() {
|
|
47
|
-
this.execution.cancel();
|
|
48
|
-
}
|
|
49
|
-
progress(event) {
|
|
50
|
-
this.progressHandle?.updateProgress(event.type === "progress"
|
|
51
|
-
? String(event.payload.percent) + "%"
|
|
52
|
-
: event.type);
|
|
53
|
-
}
|
|
54
|
-
absent(error) {
|
|
55
|
-
try {
|
|
56
|
-
return (error instanceof Error &&
|
|
57
|
-
error.message === "unknown job " + this.job &&
|
|
58
|
-
!this.jobs.list(this.owner).some((v) => v.id === this.job));
|
|
59
|
-
}
|
|
60
|
-
catch {
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
async waitAndClean() {
|
|
65
|
-
try {
|
|
66
|
-
await this.awaitTerminal();
|
|
67
|
-
this.jobs.remove(this.job, this.owner);
|
|
68
|
-
this.telemetry.record(this.trace, "job_released");
|
|
69
|
-
}
|
|
70
|
-
catch (error) {
|
|
71
|
-
this.telemetry.record(this.trace, "job_error");
|
|
72
|
-
await this.recoverOnWaitError(error);
|
|
73
|
-
}
|
|
74
|
-
finally {
|
|
75
|
-
this.progressHandle = undefined;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
/** 等待宿主 Job 到达终态(离开 running/stopping)。 */
|
|
79
|
-
async awaitTerminal() {
|
|
80
|
-
for (;;) {
|
|
81
|
-
const view = await this.jobs.wait(this.job, 60000, this.owner);
|
|
82
|
-
if (view.status !== "running" && view.status !== "stopping")
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
/** wait 失败后的有界恢复:清理已终态的 Job,但仍向上保留原始错误可见性。 */
|
|
87
|
-
async recoverOnWaitError(error) {
|
|
88
|
-
const alreadyRemoved = this.absent(error);
|
|
89
|
-
// 保留原执行结果,即便其调用者/owner 正在被释放。
|
|
90
|
-
await this.completion;
|
|
91
|
-
// Job 已不在列表时仅记录释放,不把 wait 错误视为未处理的清理失败。
|
|
92
|
-
if (alreadyRemoved) {
|
|
93
|
-
this.telemetry.record(this.trace, "job_released");
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
for (let attempt = 0; attempt < 20; attempt++) {
|
|
97
|
-
try {
|
|
98
|
-
const view = this.jobs
|
|
99
|
-
.list(this.owner)
|
|
100
|
-
.find((v) => v.id === this.job && v.owner === this.owner);
|
|
101
|
-
if (!view) {
|
|
102
|
-
this.telemetry.record(this.trace, "job_released");
|
|
103
|
-
break;
|
|
104
|
-
}
|
|
105
|
-
if (view.status !== "running" && view.status !== "stopping") {
|
|
106
|
-
this.jobs.remove(this.job, this.owner);
|
|
107
|
-
this.telemetry.record(this.trace, "job_released");
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
catch {
|
|
112
|
-
break;
|
|
113
|
-
}
|
|
114
|
-
await pause(10);
|
|
115
|
-
}
|
|
116
|
-
throw error;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
/** Owns host projections and active deliveries, not a second execution state machine. */
|
|
120
|
-
export class ExecutionOwner {
|
|
121
|
-
handles = new Map();
|
|
122
|
-
deliveries = new Set();
|
|
123
|
-
closing;
|
|
124
|
-
telemetry = new Diagnostics();
|
|
125
|
-
create(key, jobs, owner, execution) {
|
|
126
|
-
this.assertAdmittable(key);
|
|
127
|
-
const handle = new ExecutionHandle(jobs, owner, execution, this.telemetry);
|
|
128
|
-
this.attach(key, handle);
|
|
129
|
-
return handle;
|
|
130
|
-
}
|
|
131
|
-
diagnostics() {
|
|
132
|
-
return {
|
|
133
|
-
...this.telemetry.snapshot(),
|
|
134
|
-
phase: this.phase,
|
|
135
|
-
activeProjections: this.handles.size,
|
|
136
|
-
deliveries: this.deliveries.size,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
currentPhase = "open";
|
|
140
|
-
get phase() {
|
|
141
|
-
return this.currentPhase;
|
|
142
|
-
}
|
|
143
|
-
get(key) {
|
|
144
|
-
return this.handles.get(key);
|
|
145
|
-
}
|
|
146
|
-
add(key, handle) {
|
|
147
|
-
this.assertAdmittable(key);
|
|
148
|
-
this.attach(key, handle);
|
|
149
|
-
}
|
|
150
|
-
/** 拒绝关闭后或重复 key 的准入;create/add 共用这一道守卫。 */
|
|
151
|
-
assertAdmittable(key) {
|
|
152
|
-
if (this.phase !== "open")
|
|
153
|
-
throw Error(ERRORS.CLOSED);
|
|
154
|
-
if (this.handles.has(key))
|
|
155
|
-
throw Error(ERRORS.DUPLICATE_EXECUTION_HANDLE);
|
|
156
|
-
}
|
|
157
|
-
/** 登记 handle,并在其结算后从表中移除该登记。 */
|
|
158
|
-
attach(key, handle) {
|
|
159
|
-
this.handles.set(key, handle);
|
|
160
|
-
const release = () => {
|
|
161
|
-
if (this.handles.get(key) === handle)
|
|
162
|
-
this.handles.delete(key);
|
|
163
|
-
};
|
|
164
|
-
void handle.settled.then(release, release);
|
|
165
|
-
}
|
|
166
|
-
track(delivery) {
|
|
167
|
-
this.deliveries.add(delivery);
|
|
168
|
-
const release = () => this.deliveries.delete(delivery);
|
|
169
|
-
void delivery.then(release, release);
|
|
170
|
-
return delivery;
|
|
171
|
-
}
|
|
172
|
-
close(drainCore) {
|
|
173
|
-
if (this.closing)
|
|
174
|
-
return this.closing;
|
|
175
|
-
this.currentPhase = "stopping";
|
|
176
|
-
let resolve, reject;
|
|
177
|
-
this.closing = new Promise((r, j) => {
|
|
178
|
-
resolve = r;
|
|
179
|
-
reject = j;
|
|
180
|
-
});
|
|
181
|
-
void (async () => {
|
|
182
|
-
const core = drainCore();
|
|
183
|
-
this.currentPhase = "draining";
|
|
184
|
-
await core;
|
|
185
|
-
await Promise.allSettled([...this.deliveries]);
|
|
186
|
-
this.currentPhase = "releasing";
|
|
187
|
-
await Promise.allSettled([...this.handles.values()].map((h) => h.settled));
|
|
188
|
-
this.handles.clear();
|
|
189
|
-
this.currentPhase = "closed";
|
|
190
|
-
})().then(resolve, reject);
|
|
191
|
-
return this.closing;
|
|
192
|
-
}
|
|
193
|
-
}
|
package/dist/handlers.d.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { Handler } from "./bus.js";
|
|
2
|
-
/** 宿主工具转发 Handler 工厂:校验后把调用交回宿主工具管线执行。
|
|
3
|
-
* idempotent 由调用方按工具副作用显式声明;prepare 可补默认参数。 */
|
|
4
|
-
export declare function hostToolHandler(toolName: string, opts: {
|
|
5
|
-
idempotent: boolean;
|
|
6
|
-
validate(input: Record<string, unknown>): void;
|
|
7
|
-
prepare?(input: Record<string, unknown>): Record<string, unknown>;
|
|
8
|
-
queue?: string;
|
|
9
|
-
maxConcurrency?: number;
|
|
10
|
-
resources?(input: Record<string, unknown>): readonly string[];
|
|
11
|
-
dedupe?: "payload";
|
|
12
|
-
}): Handler;
|
|
13
|
-
export declare const testHandler: Handler;
|
|
14
|
-
/** 读取:无副作用,幂等,终态结果可复用;可按 payload 指纹做唯一任务。 */
|
|
15
|
-
export declare const readHandler: Handler;
|
|
16
|
-
/** 文件名搜索:只读,幂等,终态结果可复用;搜索基数由宿主工具限制。 */
|
|
17
|
-
export declare const globHandler: Handler;
|
|
18
|
-
/** 内容搜索:只读,幂等,终态结果可复用;匹配上限由宿主工具限制。 */
|
|
19
|
-
export declare const grepHandler: Handler;
|
|
20
|
-
/** 写入:有副作用,非幂等,终态结果不可复用。 */
|
|
21
|
-
export declare const writeHandler: Handler;
|
|
22
|
-
/** 编辑:有副作用,非幂等,终态结果不可复用。 */
|
|
23
|
-
export declare const editHandler: Handler;
|
|
24
|
-
/** 命令执行:有副作用,非幂等,终态结果不可复用;后台模式返回 Job ID 不等于完成。 */
|
|
25
|
-
export declare const pwshHandler: Handler;
|
package/dist/handlers.js
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
import { setTimeout } from "node:timers/promises";
|
|
2
|
-
import { ERRORS } from "./error-codes.js";
|
|
3
|
-
import { HANDLER_LIMITS } from "./product.js";
|
|
4
|
-
/** 宿主工具转发 Handler 工厂:校验后把调用交回宿主工具管线执行。
|
|
5
|
-
* idempotent 由调用方按工具副作用显式声明;prepare 可补默认参数。 */
|
|
6
|
-
export function hostToolHandler(toolName, opts) {
|
|
7
|
-
// toolName 只表达意图;实际宿主工具已由宿主层按 handler 名绑定到 capability。
|
|
8
|
-
void toolName;
|
|
9
|
-
return {
|
|
10
|
-
idempotent: opts.idempotent,
|
|
11
|
-
queue: opts.queue,
|
|
12
|
-
maxConcurrency: opts.maxConcurrency,
|
|
13
|
-
resources: opts.resources,
|
|
14
|
-
dedupe: opts.dedupe,
|
|
15
|
-
validate: opts.validate,
|
|
16
|
-
async run(input, ctx) {
|
|
17
|
-
if (!ctx.invoke)
|
|
18
|
-
throw Error(ERRORS.HOST_CAPABILITY_REQUIRED);
|
|
19
|
-
return ctx.invoke(opts.prepare ? opts.prepare(input) : input, ctx.signal);
|
|
20
|
-
},
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
export const testHandler = {
|
|
24
|
-
idempotent: true,
|
|
25
|
-
queue: "diagnostic",
|
|
26
|
-
validate(input) {
|
|
27
|
-
if (Object.keys(input).some((k) => !["delayMs", "fail"].includes(k)) ||
|
|
28
|
-
!Number.isSafeInteger(input.delayMs) ||
|
|
29
|
-
Number(input.delayMs) < 0 ||
|
|
30
|
-
Number(input.delayMs) > HANDLER_LIMITS.diagnosticDelayMs ||
|
|
31
|
-
typeof input.fail !== "boolean")
|
|
32
|
-
throw Error(ERRORS.INVALID_TEST_INPUT);
|
|
33
|
-
},
|
|
34
|
-
async run(input, ctx) {
|
|
35
|
-
for (let i = 1; i <= 5; i++) {
|
|
36
|
-
await setTimeout(Number(input.delayMs) / 5, undefined, {
|
|
37
|
-
signal: ctx.signal,
|
|
38
|
-
});
|
|
39
|
-
ctx.progress(i, i * 20);
|
|
40
|
-
}
|
|
41
|
-
if (input.fail)
|
|
42
|
-
throw Error(ERRORS.CONTROLLED_FAILURE);
|
|
43
|
-
return { ok: true };
|
|
44
|
-
},
|
|
45
|
-
};
|
|
46
|
-
function validateRead(input) {
|
|
47
|
-
if (Object.keys(input).some((k) => !["file_path", "offset", "limit"].includes(k)) ||
|
|
48
|
-
typeof input.file_path !== "string" ||
|
|
49
|
-
!input.file_path ||
|
|
50
|
-
input.file_path.length > HANDLER_LIMITS.readPathChars)
|
|
51
|
-
throw Error(ERRORS.INVALID_READ_INPUT);
|
|
52
|
-
for (const k of ["offset", "limit"])
|
|
53
|
-
if (input[k] !== undefined &&
|
|
54
|
-
(!Number.isSafeInteger(input[k]) || Number(input[k]) < 1))
|
|
55
|
-
throw Error(ERRORS.INVALID_READ_INPUT);
|
|
56
|
-
if (Number(input.limit ?? HANDLER_LIMITS.readLines) > HANDLER_LIMITS.readLines)
|
|
57
|
-
throw Error(ERRORS.READ_LIMIT);
|
|
58
|
-
}
|
|
59
|
-
function validateEdit(input) {
|
|
60
|
-
if (Object.keys(input).some((k) => !["file_path", "old_string", "new_string", "replace_all"].includes(k)) ||
|
|
61
|
-
typeof input.file_path !== "string" ||
|
|
62
|
-
!input.file_path ||
|
|
63
|
-
input.file_path.length > HANDLER_LIMITS.readPathChars ||
|
|
64
|
-
typeof input.old_string !== "string" ||
|
|
65
|
-
typeof input.new_string !== "string" ||
|
|
66
|
-
(input.replace_all !== undefined && typeof input.replace_all !== "boolean"))
|
|
67
|
-
throw Error(ERRORS.INVALID_EDIT_INPUT);
|
|
68
|
-
}
|
|
69
|
-
/** 同一路径的文件写入/编辑互斥;路径按调用原文作为资源键(不做跨平台归一化)。 */
|
|
70
|
-
function fileResource(input) {
|
|
71
|
-
return ["file:" + String(input.file_path)];
|
|
72
|
-
}
|
|
73
|
-
/** 读取:无副作用,幂等,终态结果可复用;可按 payload 指纹做唯一任务。 */
|
|
74
|
-
export const readHandler = hostToolHandler("read", {
|
|
75
|
-
idempotent: true,
|
|
76
|
-
queue: "fs",
|
|
77
|
-
dedupe: "payload",
|
|
78
|
-
validate: validateRead,
|
|
79
|
-
prepare: (input) => ({
|
|
80
|
-
...input,
|
|
81
|
-
limit: input.limit ?? HANDLER_LIMITS.readLines,
|
|
82
|
-
}),
|
|
83
|
-
});
|
|
84
|
-
function validateGlob(input) {
|
|
85
|
-
if (Object.keys(input).some((k) => !["pattern", "path"].includes(k)) ||
|
|
86
|
-
typeof input.pattern !== "string" ||
|
|
87
|
-
input.pattern.trim().length === 0 ||
|
|
88
|
-
(input.path !== undefined &&
|
|
89
|
-
(typeof input.path !== "string" || input.path.trim().length === 0)))
|
|
90
|
-
throw Error(ERRORS.INVALID_GLOB_INPUT);
|
|
91
|
-
}
|
|
92
|
-
/** 文件名搜索:只读,幂等,终态结果可复用;搜索基数由宿主工具限制。 */
|
|
93
|
-
export const globHandler = hostToolHandler("glob", {
|
|
94
|
-
idempotent: true,
|
|
95
|
-
queue: "fs",
|
|
96
|
-
dedupe: "payload",
|
|
97
|
-
validate: validateGlob,
|
|
98
|
-
});
|
|
99
|
-
function validateGrep(input) {
|
|
100
|
-
if (Object.keys(input).some((k) => !["pattern", "path", "include"].includes(k)) ||
|
|
101
|
-
typeof input.pattern !== "string" ||
|
|
102
|
-
input.pattern.length === 0 ||
|
|
103
|
-
(input.path !== undefined &&
|
|
104
|
-
(typeof input.path !== "string" || input.path.trim().length === 0)))
|
|
105
|
-
throw Error(ERRORS.INVALID_GREP_INPUT);
|
|
106
|
-
if (input.include === undefined)
|
|
107
|
-
return;
|
|
108
|
-
const include = input.include;
|
|
109
|
-
if (typeof include !== "string" || include.trim().length === 0)
|
|
110
|
-
throw Error(ERRORS.INVALID_GREP_INPUT);
|
|
111
|
-
// 与宿主 grep 契约一致:只接受单个正向 glob,不支持取反或顶层逗号列表。
|
|
112
|
-
if (include.startsWith("!"))
|
|
113
|
-
throw Error(ERRORS.INVALID_GREP_INPUT);
|
|
114
|
-
let braceDepth = 0;
|
|
115
|
-
for (const char of include) {
|
|
116
|
-
if (char === "{")
|
|
117
|
-
braceDepth++;
|
|
118
|
-
else if (char === "}")
|
|
119
|
-
braceDepth = Math.max(0, braceDepth - 1);
|
|
120
|
-
else if (char === "," && braceDepth === 0)
|
|
121
|
-
throw Error(ERRORS.INVALID_GREP_INPUT);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
/** 内容搜索:只读,幂等,终态结果可复用;匹配上限由宿主工具限制。 */
|
|
125
|
-
export const grepHandler = hostToolHandler("grep", {
|
|
126
|
-
idempotent: true,
|
|
127
|
-
queue: "fs",
|
|
128
|
-
dedupe: "payload",
|
|
129
|
-
validate: validateGrep,
|
|
130
|
-
});
|
|
131
|
-
function validateWrite(input) {
|
|
132
|
-
if (Object.keys(input).some((k) => !["file_path", "content"].includes(k)) ||
|
|
133
|
-
typeof input.file_path !== "string" ||
|
|
134
|
-
!input.file_path ||
|
|
135
|
-
input.file_path.length > HANDLER_LIMITS.readPathChars ||
|
|
136
|
-
typeof input.content !== "string")
|
|
137
|
-
throw Error(ERRORS.INVALID_WRITE_INPUT);
|
|
138
|
-
}
|
|
139
|
-
/** 写入:有副作用,非幂等,终态结果不可复用。 */
|
|
140
|
-
export const writeHandler = hostToolHandler("write", {
|
|
141
|
-
idempotent: false,
|
|
142
|
-
queue: "fs",
|
|
143
|
-
resources: fileResource,
|
|
144
|
-
validate: validateWrite,
|
|
145
|
-
});
|
|
146
|
-
/** 编辑:有副作用,非幂等,终态结果不可复用。 */
|
|
147
|
-
export const editHandler = hostToolHandler("edit", {
|
|
148
|
-
idempotent: false,
|
|
149
|
-
queue: "fs",
|
|
150
|
-
resources: fileResource,
|
|
151
|
-
validate: validateEdit,
|
|
152
|
-
});
|
|
153
|
-
/** 与宿主 pwsh 工具一致的提权目标;沙箱组合未提供提权面时由宿主拒绝。 */
|
|
154
|
-
const PWSH_ESCALATION_TARGETS = Object.freeze([
|
|
155
|
-
"workspace-write",
|
|
156
|
-
"danger-full-access",
|
|
157
|
-
]);
|
|
158
|
-
function validatePwsh(input) {
|
|
159
|
-
if (Object.keys(input).some((k) => ![
|
|
160
|
-
"description",
|
|
161
|
-
"command",
|
|
162
|
-
"timeoutMs",
|
|
163
|
-
"workdir",
|
|
164
|
-
"run_in_background",
|
|
165
|
-
"sandbox_permissions",
|
|
166
|
-
"justification",
|
|
167
|
-
].includes(k)) ||
|
|
168
|
-
typeof input.command !== "string" ||
|
|
169
|
-
input.command.trim().length === 0 ||
|
|
170
|
-
typeof input.description !== "string" ||
|
|
171
|
-
input.description.trim().length === 0 ||
|
|
172
|
-
(input.timeoutMs !== undefined &&
|
|
173
|
-
(!Number.isFinite(input.timeoutMs) || Number(input.timeoutMs) <= 0)) ||
|
|
174
|
-
(input.workdir !== undefined && typeof input.workdir !== "string") ||
|
|
175
|
-
(input.run_in_background !== undefined &&
|
|
176
|
-
typeof input.run_in_background !== "boolean"))
|
|
177
|
-
throw Error(ERRORS.INVALID_PWSH_INPUT);
|
|
178
|
-
const permissions = input.sandbox_permissions, justification = input.justification;
|
|
179
|
-
if ((permissions !== undefined && justification === undefined) ||
|
|
180
|
-
(justification !== undefined && permissions === undefined) ||
|
|
181
|
-
(permissions !== undefined &&
|
|
182
|
-
(typeof permissions !== "string" ||
|
|
183
|
-
!PWSH_ESCALATION_TARGETS.includes(permissions))) ||
|
|
184
|
-
(justification !== undefined &&
|
|
185
|
-
(typeof justification !== "string" ||
|
|
186
|
-
justification.trim().length === 0)))
|
|
187
|
-
throw Error(ERRORS.INVALID_PWSH_INPUT);
|
|
188
|
-
}
|
|
189
|
-
/** 命令执行:有副作用,非幂等,终态结果不可复用;后台模式返回 Job ID 不等于完成。 */
|
|
190
|
-
export const pwshHandler = hostToolHandler("pwsh", {
|
|
191
|
-
idempotent: false,
|
|
192
|
-
queue: "command",
|
|
193
|
-
validate: validatePwsh,
|
|
194
|
-
});
|
package/dist/json.d.ts
DELETED
package/dist/json.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { ERRORS } from "./error-codes.js";
|
|
2
|
-
import { LIMITS } from "./limits.js";
|
|
3
|
-
/** Strict JSON tree validation before serialization, cloning or recursive freezing. */
|
|
4
|
-
export function assertJson(value, depth = 0, active = new Set(), budget = { left: LIMITS.jsonNodeBudget }) {
|
|
5
|
-
if (--budget.left < 0 || depth > LIMITS.jsonMaxDepth)
|
|
6
|
-
throw Error(ERRORS.JSON_LIMIT);
|
|
7
|
-
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
8
|
-
return;
|
|
9
|
-
if (typeof value === "number" &&
|
|
10
|
-
Number.isFinite(value) &&
|
|
11
|
-
!Object.is(value, -0))
|
|
12
|
-
return;
|
|
13
|
-
if (typeof value !== "object" || value === null)
|
|
14
|
-
throw Error(ERRORS.INVALID_JSON);
|
|
15
|
-
if (active.has(value))
|
|
16
|
-
throw Error(ERRORS.CYCLIC_JSON);
|
|
17
|
-
const array = Array.isArray(value);
|
|
18
|
-
if (array && Object.getPrototypeOf(value) !== Array.prototype)
|
|
19
|
-
throw Error(ERRORS.INVALID_JSON_ARRAY);
|
|
20
|
-
if (!array &&
|
|
21
|
-
Object.getPrototypeOf(value) !== Object.prototype &&
|
|
22
|
-
Object.getPrototypeOf(value) !== null)
|
|
23
|
-
throw Error(ERRORS.INVALID_JSON_OBJECT);
|
|
24
|
-
active.add(value);
|
|
25
|
-
try {
|
|
26
|
-
const keys = Reflect.ownKeys(value);
|
|
27
|
-
if (array && keys.length !== value.length + 1)
|
|
28
|
-
throw Error(ERRORS.INVALID_JSON_ARRAY);
|
|
29
|
-
for (const key of keys) {
|
|
30
|
-
if (array && key === "length")
|
|
31
|
-
continue;
|
|
32
|
-
if (typeof key !== "string")
|
|
33
|
-
throw Error(ERRORS.INVALID_JSON_KEY);
|
|
34
|
-
if (array &&
|
|
35
|
-
(!/^(0|[1-9][0-9]*)$/.test(key) || Number(key) >= value.length))
|
|
36
|
-
throw Error(ERRORS.INVALID_JSON_ARRAY);
|
|
37
|
-
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
38
|
-
if (!descriptor.enumerable || !("value" in descriptor))
|
|
39
|
-
throw Error(ERRORS.INVALID_JSON_PROPERTY);
|
|
40
|
-
assertJson(descriptor.value, depth + 1, active, budget);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
finally {
|
|
44
|
-
active.delete(value);
|
|
45
|
-
}
|
|
46
|
-
}
|
package/dist/limits.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export declare const LIMITS: Readonly<{
|
|
2
|
-
/** 单条协议消息 JSON 的最大字节数(parse 拒绝更大消息)。 */
|
|
3
|
-
messageMaxBytes: 16384;
|
|
4
|
-
/** messageId / requestId 的最大字符数。 */
|
|
5
|
-
idMaxLength: 128;
|
|
6
|
-
/** messageId / requestId 的合法字符集。 */
|
|
7
|
-
idPattern: RegExp;
|
|
8
|
-
/** 单个逻辑执行所记账的消息条数上限(含首次、重复请求与查询)。 */
|
|
9
|
-
messageAccountLimit: 128;
|
|
10
|
-
/** 单个执行可同时附着的观察者上限。 */
|
|
11
|
-
waiterLimit: 32;
|
|
12
|
-
/** inspect() 单次返回的任务元数据条数上限。 */
|
|
13
|
-
inspectTaskLimit: 50;
|
|
14
|
-
/** 严格 JSON 树的最大嵌套深度。 */
|
|
15
|
-
jsonMaxDepth: 64;
|
|
16
|
-
/** 严格 JSON 树可访问的节点总数预算。 */
|
|
17
|
-
jsonNodeBudget: 65536;
|
|
18
|
-
/** 诊断环形轨迹的槽位数量。 */
|
|
19
|
-
traceRingSize: 128;
|
|
20
|
-
}>;
|
package/dist/limits.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/** 集中定义跨模块共享的固定数值上限。内部实现约束,非用户可配置项。
|
|
2
|
-
* 数值相同时语义依然独立,勿为了“省一个常量”而合并。 */
|
|
3
|
-
const ID_MAX_LENGTH = 128;
|
|
4
|
-
export const LIMITS = Object.freeze({
|
|
5
|
-
/** 单条协议消息 JSON 的最大字节数(parse 拒绝更大消息)。 */
|
|
6
|
-
messageMaxBytes: 16384,
|
|
7
|
-
/** messageId / requestId 的最大字符数。 */
|
|
8
|
-
idMaxLength: ID_MAX_LENGTH,
|
|
9
|
-
/** messageId / requestId 的合法字符集。 */
|
|
10
|
-
idPattern: new RegExp(`^[a-zA-Z0-9_.:-]{1,${ID_MAX_LENGTH}}$`),
|
|
11
|
-
/** 单个逻辑执行所记账的消息条数上限(含首次、重复请求与查询)。 */
|
|
12
|
-
messageAccountLimit: 128,
|
|
13
|
-
/** 单个执行可同时附着的观察者上限。 */
|
|
14
|
-
waiterLimit: 32,
|
|
15
|
-
/** inspect() 单次返回的任务元数据条数上限。 */
|
|
16
|
-
inspectTaskLimit: 50,
|
|
17
|
-
/** 严格 JSON 树的最大嵌套深度。 */
|
|
18
|
-
jsonMaxDepth: 64,
|
|
19
|
-
/** 严格 JSON 树可访问的节点总数预算。 */
|
|
20
|
-
jsonNodeBudget: 65536,
|
|
21
|
-
/** 诊断环形轨迹的槽位数量。 */
|
|
22
|
-
traceRingSize: 128,
|
|
23
|
-
});
|