@feng0531/dsh-execution-bus 0.5.0 → 0.6.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 +15 -4
- package/dist/bus.d.ts +14 -43
- package/dist/bus.js +137 -155
- package/dist/diagnostics.js +6 -4
- package/dist/error-codes.d.ts +54 -0
- package/dist/error-codes.js +60 -0
- package/dist/execution.d.ts +8 -0
- package/dist/execution.js +67 -48
- package/dist/handlers.js +14 -10
- package/dist/json.d.ts +1 -1
- package/dist/json.js +13 -11
- package/dist/limits.d.ts +20 -0
- package/dist/limits.js +23 -0
- package/dist/mode.d.ts +5 -0
- package/dist/mode.js +16 -0
- package/dist/plugin.js +65 -63
- package/dist/preset.d.ts +1 -1
- package/dist/preset.js +23 -20
- package/dist/product.d.ts +56 -0
- package/dist/product.js +48 -0
- package/dist/protocol.d.ts +49 -0
- package/dist/protocol.js +54 -0
- package/dist/test-mode.d.ts +2 -5
- package/dist/test-mode.js +2 -15
- package/docs/PRODUCT.md +46 -0
- package/docs/releases/0.5.1.md +18 -0
- package/docs/releases/0.6.0.md +20 -0
- package/package.json +20 -19
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** 集中全部固定错误码。值即协议 v1 的稳定错误词汇,不得重命名或改动。
|
|
2
|
+
* 新增错误码前先更新 docs/protocol.md 的错误词汇表。 */
|
|
3
|
+
export declare const ERRORS: Readonly<{
|
|
4
|
+
INVALID_OBJECT: "INVALID_OBJECT";
|
|
5
|
+
UNKNOWN_FIELD: "UNKNOWN_FIELD";
|
|
6
|
+
UNSUPPORTED_VERSION: "UNSUPPORTED_VERSION";
|
|
7
|
+
INVALID_ID: "INVALID_ID";
|
|
8
|
+
UNSUPPORTED_TYPE: "UNSUPPORTED_TYPE";
|
|
9
|
+
MESSAGE_TOO_LARGE: "MESSAGE_TOO_LARGE";
|
|
10
|
+
JSON_LIMIT: "JSON_LIMIT";
|
|
11
|
+
INVALID_JSON: "INVALID_JSON";
|
|
12
|
+
CYCLIC_JSON: "CYCLIC_JSON";
|
|
13
|
+
INVALID_JSON_ARRAY: "INVALID_JSON_ARRAY";
|
|
14
|
+
INVALID_JSON_OBJECT: "INVALID_JSON_OBJECT";
|
|
15
|
+
INVALID_JSON_KEY: "INVALID_JSON_KEY";
|
|
16
|
+
INVALID_JSON_PROPERTY: "INVALID_JSON_PROPERTY";
|
|
17
|
+
CLOSED: "CLOSED";
|
|
18
|
+
EXPECTED_REQUEST: "EXPECTED_REQUEST";
|
|
19
|
+
INVALID_REQUEST: "INVALID_REQUEST";
|
|
20
|
+
REQUEST_CONFLICT: "REQUEST_CONFLICT";
|
|
21
|
+
MESSAGE_CONFLICT: "MESSAGE_CONFLICT";
|
|
22
|
+
MESSAGE_LIMIT: "MESSAGE_LIMIT";
|
|
23
|
+
UNKNOWN_HANDLER: "UNKNOWN_HANDLER";
|
|
24
|
+
BACKPRESSURE: "BACKPRESSURE";
|
|
25
|
+
SESSION_BACKPRESSURE: "SESSION_BACKPRESSURE";
|
|
26
|
+
WAITER_LIMIT: "WAITER_LIMIT";
|
|
27
|
+
EXPECTED_CONTROL: "EXPECTED_CONTROL";
|
|
28
|
+
INVALID_CONTROL: "INVALID_CONTROL";
|
|
29
|
+
NOT_FOUND: "NOT_FOUND";
|
|
30
|
+
UNTRUSTED_CONTEXT: "UNTRUSTED_CONTEXT";
|
|
31
|
+
OWNING_SESSION_REQUIRED: "OWNING_SESSION_REQUIRED";
|
|
32
|
+
QUEUE_WAIT_TIMEOUT: "QUEUE_WAIT_TIMEOUT";
|
|
33
|
+
RESULT_NOT_RETAINED: "RESULT_NOT_RETAINED";
|
|
34
|
+
CANCELLED: "CANCELLED";
|
|
35
|
+
CANCELLATION_UNCONFIRMED: "CANCELLATION_UNCONFIRMED";
|
|
36
|
+
HANDLER_FAILED: "HANDLER_FAILED";
|
|
37
|
+
INVALID_ADMISSION_POLICY: "INVALID_ADMISSION_POLICY";
|
|
38
|
+
INVALID_OPTIONS: "INVALID_OPTIONS";
|
|
39
|
+
DUPLICATE_HANDLER: "DUPLICATE_HANDLER";
|
|
40
|
+
MUTATING_REUSE_DENIED: "MUTATING_REUSE_DENIED";
|
|
41
|
+
DUPLICATE_EXECUTION_HANDLE: "DUPLICATE_EXECUTION_HANDLE";
|
|
42
|
+
INVALID_STATE_QUERY: "INVALID_STATE_QUERY";
|
|
43
|
+
HOST_READ_FAILED: "HOST_READ_FAILED";
|
|
44
|
+
HOST_JOB_UNAVAILABLE: "HOST_JOB_UNAVAILABLE";
|
|
45
|
+
PTC_PRESET_TOO_LARGE: "PTC_PRESET_TOO_LARGE";
|
|
46
|
+
PTC_PRESET_ALREADY_CONTAINS_BUS: "PTC_PRESET_ALREADY_CONTAINS_BUS";
|
|
47
|
+
PTC_PRESET_RELATIVE_BASE_UNSUPPORTED: "PTC_PRESET_RELATIVE_BASE_UNSUPPORTED";
|
|
48
|
+
PTC_PRESET_UNAVAILABLE: "PTC_PRESET_UNAVAILABLE";
|
|
49
|
+
INVALID_TEST_INPUT: "INVALID_TEST_INPUT";
|
|
50
|
+
INVALID_READ_INPUT: "INVALID_READ_INPUT";
|
|
51
|
+
READ_LIMIT: "READ_LIMIT";
|
|
52
|
+
CONTROLLED_FAILURE: "CONTROLLED_FAILURE";
|
|
53
|
+
HOST_CAPABILITY_REQUIRED: "HOST_CAPABILITY_REQUIRED";
|
|
54
|
+
}>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** 集中全部固定错误码。值即协议 v1 的稳定错误词汇,不得重命名或改动。
|
|
2
|
+
* 新增错误码前先更新 docs/protocol.md 的错误词汇表。 */
|
|
3
|
+
export const ERRORS = Object.freeze({
|
|
4
|
+
// —— 协议解析(protocol) ——
|
|
5
|
+
INVALID_OBJECT: "INVALID_OBJECT",
|
|
6
|
+
UNKNOWN_FIELD: "UNKNOWN_FIELD",
|
|
7
|
+
UNSUPPORTED_VERSION: "UNSUPPORTED_VERSION",
|
|
8
|
+
INVALID_ID: "INVALID_ID",
|
|
9
|
+
UNSUPPORTED_TYPE: "UNSUPPORTED_TYPE",
|
|
10
|
+
MESSAGE_TOO_LARGE: "MESSAGE_TOO_LARGE",
|
|
11
|
+
// —— 严格 JSON(json) ——
|
|
12
|
+
JSON_LIMIT: "JSON_LIMIT",
|
|
13
|
+
INVALID_JSON: "INVALID_JSON",
|
|
14
|
+
CYCLIC_JSON: "CYCLIC_JSON",
|
|
15
|
+
INVALID_JSON_ARRAY: "INVALID_JSON_ARRAY",
|
|
16
|
+
INVALID_JSON_OBJECT: "INVALID_JSON_OBJECT",
|
|
17
|
+
INVALID_JSON_KEY: "INVALID_JSON_KEY",
|
|
18
|
+
INVALID_JSON_PROPERTY: "INVALID_JSON_PROPERTY",
|
|
19
|
+
// —— 总线调度与准入(bus) ——
|
|
20
|
+
CLOSED: "CLOSED",
|
|
21
|
+
EXPECTED_REQUEST: "EXPECTED_REQUEST",
|
|
22
|
+
INVALID_REQUEST: "INVALID_REQUEST",
|
|
23
|
+
REQUEST_CONFLICT: "REQUEST_CONFLICT",
|
|
24
|
+
MESSAGE_CONFLICT: "MESSAGE_CONFLICT",
|
|
25
|
+
MESSAGE_LIMIT: "MESSAGE_LIMIT",
|
|
26
|
+
UNKNOWN_HANDLER: "UNKNOWN_HANDLER",
|
|
27
|
+
BACKPRESSURE: "BACKPRESSURE",
|
|
28
|
+
SESSION_BACKPRESSURE: "SESSION_BACKPRESSURE",
|
|
29
|
+
WAITER_LIMIT: "WAITER_LIMIT",
|
|
30
|
+
EXPECTED_CONTROL: "EXPECTED_CONTROL",
|
|
31
|
+
INVALID_CONTROL: "INVALID_CONTROL",
|
|
32
|
+
NOT_FOUND: "NOT_FOUND",
|
|
33
|
+
UNTRUSTED_CONTEXT: "UNTRUSTED_CONTEXT",
|
|
34
|
+
OWNING_SESSION_REQUIRED: "OWNING_SESSION_REQUIRED",
|
|
35
|
+
QUEUE_WAIT_TIMEOUT: "QUEUE_WAIT_TIMEOUT",
|
|
36
|
+
RESULT_NOT_RETAINED: "RESULT_NOT_RETAINED",
|
|
37
|
+
CANCELLED: "CANCELLED",
|
|
38
|
+
CANCELLATION_UNCONFIRMED: "CANCELLATION_UNCONFIRMED",
|
|
39
|
+
HANDLER_FAILED: "HANDLER_FAILED",
|
|
40
|
+
INVALID_ADMISSION_POLICY: "INVALID_ADMISSION_POLICY",
|
|
41
|
+
INVALID_OPTIONS: "INVALID_OPTIONS",
|
|
42
|
+
DUPLICATE_HANDLER: "DUPLICATE_HANDLER",
|
|
43
|
+
MUTATING_REUSE_DENIED: "MUTATING_REUSE_DENIED",
|
|
44
|
+
// —— 宿主投影(execution / plugin) ——
|
|
45
|
+
DUPLICATE_EXECUTION_HANDLE: "DUPLICATE_EXECUTION_HANDLE",
|
|
46
|
+
INVALID_STATE_QUERY: "INVALID_STATE_QUERY",
|
|
47
|
+
HOST_READ_FAILED: "HOST_READ_FAILED",
|
|
48
|
+
HOST_JOB_UNAVAILABLE: "HOST_JOB_UNAVAILABLE",
|
|
49
|
+
// —— 预设派生(preset) ——
|
|
50
|
+
PTC_PRESET_TOO_LARGE: "PTC_PRESET_TOO_LARGE",
|
|
51
|
+
PTC_PRESET_ALREADY_CONTAINS_BUS: "PTC_PRESET_ALREADY_CONTAINS_BUS",
|
|
52
|
+
PTC_PRESET_RELATIVE_BASE_UNSUPPORTED: "PTC_PRESET_RELATIVE_BASE_UNSUPPORTED",
|
|
53
|
+
PTC_PRESET_UNAVAILABLE: "PTC_PRESET_UNAVAILABLE",
|
|
54
|
+
// —— 内置 Handler(handlers) ——
|
|
55
|
+
INVALID_TEST_INPUT: "INVALID_TEST_INPUT",
|
|
56
|
+
INVALID_READ_INPUT: "INVALID_READ_INPUT",
|
|
57
|
+
READ_LIMIT: "READ_LIMIT",
|
|
58
|
+
CONTROLLED_FAILURE: "CONTROLLED_FAILURE",
|
|
59
|
+
HOST_CAPABILITY_REQUIRED: "HOST_CAPABILITY_REQUIRED",
|
|
60
|
+
});
|
package/dist/execution.d.ts
CHANGED
|
@@ -22,6 +22,10 @@ export declare class ExecutionHandle {
|
|
|
22
22
|
progress(event: Event): void;
|
|
23
23
|
private absent;
|
|
24
24
|
private waitAndClean;
|
|
25
|
+
/** 等待宿主 Job 到达终态(离开 running/stopping)。 */
|
|
26
|
+
private awaitTerminal;
|
|
27
|
+
/** wait 失败后的有界恢复:清理已终态的 Job,但仍向上保留原始错误可见性。 */
|
|
28
|
+
private recoverOnWaitError;
|
|
25
29
|
}
|
|
26
30
|
export type ShutdownPhase = "open" | "stopping" | "draining" | "releasing" | "closed";
|
|
27
31
|
/** Owns host projections and active deliveries, not a second execution state machine. */
|
|
@@ -56,6 +60,10 @@ export declare class ExecutionOwner {
|
|
|
56
60
|
get phase(): ShutdownPhase;
|
|
57
61
|
get(key: string): ExecutionHandle | undefined;
|
|
58
62
|
add(key: string, handle: ExecutionHandle): void;
|
|
63
|
+
/** 拒绝关闭后或重复 key 的准入;create/add 共用这一道守卫。 */
|
|
64
|
+
private assertAdmittable;
|
|
65
|
+
/** 登记 handle,并在其结算后从表中移除该登记。 */
|
|
66
|
+
private attach;
|
|
59
67
|
track<T>(delivery: Promise<T>): Promise<T>;
|
|
60
68
|
close(drainCore: () => Promise<void>): Promise<void>;
|
|
61
69
|
}
|
package/dist/execution.js
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import { setTimeout as pause } from "node:timers/promises";
|
|
2
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
|
+
}
|
|
3
15
|
/** One host projection of a logical execution. The core completion remains authoritative. */
|
|
4
16
|
export class ExecutionHandle {
|
|
5
17
|
jobs;
|
|
@@ -18,14 +30,7 @@ export class ExecutionHandle {
|
|
|
18
30
|
this.telemetry = telemetry;
|
|
19
31
|
this.trace = telemetry.begin();
|
|
20
32
|
this.completion = execution.done;
|
|
21
|
-
const outcome = this.completion.then((v) => (
|
|
22
|
-
status: v.state === "succeeded"
|
|
23
|
-
? "completed"
|
|
24
|
-
: v.state === "cancelled"
|
|
25
|
-
? "killed"
|
|
26
|
-
: "failed",
|
|
27
|
-
detail: v.state,
|
|
28
|
-
}));
|
|
33
|
+
const outcome = this.completion.then((v) => jobOutcome(v.state));
|
|
29
34
|
this.job = jobs.start({
|
|
30
35
|
kind: "execution-bus",
|
|
31
36
|
label: "萧何 / Xiao He",
|
|
@@ -58,49 +63,58 @@ export class ExecutionHandle {
|
|
|
58
63
|
}
|
|
59
64
|
async waitAndClean() {
|
|
60
65
|
try {
|
|
61
|
-
|
|
62
|
-
const view = await this.jobs.wait(this.job, 60000, this.owner);
|
|
63
|
-
if (view.status !== "running" && view.status !== "stopping")
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
66
|
+
await this.awaitTerminal();
|
|
66
67
|
this.jobs.remove(this.job, this.owner);
|
|
67
68
|
this.telemetry.record(this.trace, "job_released");
|
|
68
69
|
}
|
|
69
70
|
catch (error) {
|
|
70
71
|
this.telemetry.record(this.trace, "job_error");
|
|
71
|
-
|
|
72
|
-
// Retain the original execution, including while its caller/owner is being disposed.
|
|
73
|
-
await this.completion;
|
|
74
|
-
if (removed)
|
|
75
|
-
this.telemetry.record(this.trace, "job_released");
|
|
76
|
-
if (!removed) {
|
|
77
|
-
for (let attempt = 0; attempt < 20; attempt++) {
|
|
78
|
-
try {
|
|
79
|
-
const view = this.jobs
|
|
80
|
-
.list(this.owner)
|
|
81
|
-
.find((v) => v.id === this.job && v.owner === this.owner);
|
|
82
|
-
if (!view) {
|
|
83
|
-
this.telemetry.record(this.trace, "job_released");
|
|
84
|
-
break;
|
|
85
|
-
}
|
|
86
|
-
if (view.status !== "running" && view.status !== "stopping") {
|
|
87
|
-
this.jobs.remove(this.job, this.owner);
|
|
88
|
-
this.telemetry.record(this.trace, "job_released");
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
catch {
|
|
93
|
-
break;
|
|
94
|
-
}
|
|
95
|
-
await pause(10);
|
|
96
|
-
}
|
|
97
|
-
throw error;
|
|
98
|
-
}
|
|
72
|
+
await this.recoverOnWaitError(error);
|
|
99
73
|
}
|
|
100
74
|
finally {
|
|
101
75
|
this.progressHandle = undefined;
|
|
102
76
|
}
|
|
103
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
|
+
}
|
|
104
118
|
}
|
|
105
119
|
/** Owns host projections and active deliveries, not a second execution state machine. */
|
|
106
120
|
export class ExecutionOwner {
|
|
@@ -109,12 +123,9 @@ export class ExecutionOwner {
|
|
|
109
123
|
closing;
|
|
110
124
|
telemetry = new Diagnostics();
|
|
111
125
|
create(key, jobs, owner, execution) {
|
|
112
|
-
|
|
113
|
-
throw Error("CLOSED");
|
|
114
|
-
if (this.handles.has(key))
|
|
115
|
-
throw Error("DUPLICATE_EXECUTION_HANDLE");
|
|
126
|
+
this.assertAdmittable(key);
|
|
116
127
|
const handle = new ExecutionHandle(jobs, owner, execution, this.telemetry);
|
|
117
|
-
this.
|
|
128
|
+
this.attach(key, handle);
|
|
118
129
|
return handle;
|
|
119
130
|
}
|
|
120
131
|
diagnostics() {
|
|
@@ -133,10 +144,18 @@ export class ExecutionOwner {
|
|
|
133
144
|
return this.handles.get(key);
|
|
134
145
|
}
|
|
135
146
|
add(key, handle) {
|
|
147
|
+
this.assertAdmittable(key);
|
|
148
|
+
this.attach(key, handle);
|
|
149
|
+
}
|
|
150
|
+
/** 拒绝关闭后或重复 key 的准入;create/add 共用这一道守卫。 */
|
|
151
|
+
assertAdmittable(key) {
|
|
136
152
|
if (this.phase !== "open")
|
|
137
|
-
throw Error(
|
|
153
|
+
throw Error(ERRORS.CLOSED);
|
|
138
154
|
if (this.handles.has(key))
|
|
139
|
-
throw Error(
|
|
155
|
+
throw Error(ERRORS.DUPLICATE_EXECUTION_HANDLE);
|
|
156
|
+
}
|
|
157
|
+
/** 登记 handle,并在其结算后从表中移除该登记。 */
|
|
158
|
+
attach(key, handle) {
|
|
140
159
|
this.handles.set(key, handle);
|
|
141
160
|
const release = () => {
|
|
142
161
|
if (this.handles.get(key) === handle)
|
package/dist/handlers.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { setTimeout } from "node:timers/promises";
|
|
2
|
+
import { ERRORS } from "./error-codes.js";
|
|
3
|
+
import { HANDLER_LIMITS } from "./product.js";
|
|
2
4
|
export const testHandler = {
|
|
5
|
+
idempotent: true,
|
|
3
6
|
validate(input) {
|
|
4
7
|
if (Object.keys(input).some((k) => !["delayMs", "fail"].includes(k)) ||
|
|
5
8
|
!Number.isSafeInteger(input.delayMs) ||
|
|
6
9
|
Number(input.delayMs) < 0 ||
|
|
7
|
-
Number(input.delayMs) >
|
|
10
|
+
Number(input.delayMs) > HANDLER_LIMITS.diagnosticDelayMs ||
|
|
8
11
|
typeof input.fail !== "boolean")
|
|
9
|
-
throw Error(
|
|
12
|
+
throw Error(ERRORS.INVALID_TEST_INPUT);
|
|
10
13
|
},
|
|
11
14
|
async run(input, ctx) {
|
|
12
15
|
for (let i = 1; i <= 5; i++) {
|
|
@@ -16,27 +19,28 @@ export const testHandler = {
|
|
|
16
19
|
ctx.progress(i, i * 20);
|
|
17
20
|
}
|
|
18
21
|
if (input.fail)
|
|
19
|
-
throw Error(
|
|
22
|
+
throw Error(ERRORS.CONTROLLED_FAILURE);
|
|
20
23
|
return { ok: true };
|
|
21
24
|
},
|
|
22
25
|
};
|
|
23
26
|
export const readHandler = {
|
|
27
|
+
idempotent: true,
|
|
24
28
|
validate(input) {
|
|
25
29
|
if (Object.keys(input).some((k) => !["file_path", "offset", "limit"].includes(k)) ||
|
|
26
30
|
typeof input.file_path !== "string" ||
|
|
27
31
|
!input.file_path ||
|
|
28
|
-
input.file_path.length >
|
|
29
|
-
throw Error(
|
|
32
|
+
input.file_path.length > HANDLER_LIMITS.readPathChars)
|
|
33
|
+
throw Error(ERRORS.INVALID_READ_INPUT);
|
|
30
34
|
for (const k of ["offset", "limit"])
|
|
31
35
|
if (input[k] !== undefined &&
|
|
32
36
|
(!Number.isSafeInteger(input[k]) || Number(input[k]) < 1))
|
|
33
|
-
throw Error(
|
|
34
|
-
if (Number(input.limit ??
|
|
35
|
-
throw Error(
|
|
37
|
+
throw Error(ERRORS.INVALID_READ_INPUT);
|
|
38
|
+
if (Number(input.limit ?? HANDLER_LIMITS.readLines) > HANDLER_LIMITS.readLines)
|
|
39
|
+
throw Error(ERRORS.READ_LIMIT);
|
|
36
40
|
},
|
|
37
41
|
async run(input, ctx) {
|
|
38
42
|
if (!ctx.invoke)
|
|
39
|
-
throw Error(
|
|
40
|
-
return ctx.invoke({ ...input, limit: input.limit ??
|
|
43
|
+
throw Error(ERRORS.HOST_CAPABILITY_REQUIRED);
|
|
44
|
+
return ctx.invoke({ ...input, limit: input.limit ?? HANDLER_LIMITS.readLines }, ctx.signal);
|
|
41
45
|
},
|
|
42
46
|
};
|
package/dist/json.d.ts
CHANGED
package/dist/json.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { ERRORS } from "./error-codes.js";
|
|
2
|
+
import { LIMITS } from "./limits.js";
|
|
1
3
|
/** Strict JSON tree validation before serialization, cloning or recursive freezing. */
|
|
2
|
-
export function assertJson(value, depth = 0, active = new Set(), budget = { left:
|
|
3
|
-
if (--budget.left < 0 || depth >
|
|
4
|
-
throw Error(
|
|
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);
|
|
5
7
|
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
6
8
|
return;
|
|
7
9
|
if (typeof value === "number" &&
|
|
@@ -9,32 +11,32 @@ export function assertJson(value, depth = 0, active = new Set(), budget = { left
|
|
|
9
11
|
!Object.is(value, -0))
|
|
10
12
|
return;
|
|
11
13
|
if (typeof value !== "object" || value === null)
|
|
12
|
-
throw Error(
|
|
14
|
+
throw Error(ERRORS.INVALID_JSON);
|
|
13
15
|
if (active.has(value))
|
|
14
|
-
throw Error(
|
|
16
|
+
throw Error(ERRORS.CYCLIC_JSON);
|
|
15
17
|
const array = Array.isArray(value);
|
|
16
18
|
if (array && Object.getPrototypeOf(value) !== Array.prototype)
|
|
17
|
-
throw Error(
|
|
19
|
+
throw Error(ERRORS.INVALID_JSON_ARRAY);
|
|
18
20
|
if (!array &&
|
|
19
21
|
Object.getPrototypeOf(value) !== Object.prototype &&
|
|
20
22
|
Object.getPrototypeOf(value) !== null)
|
|
21
|
-
throw Error(
|
|
23
|
+
throw Error(ERRORS.INVALID_JSON_OBJECT);
|
|
22
24
|
active.add(value);
|
|
23
25
|
try {
|
|
24
26
|
const keys = Reflect.ownKeys(value);
|
|
25
27
|
if (array && keys.length !== value.length + 1)
|
|
26
|
-
throw Error(
|
|
28
|
+
throw Error(ERRORS.INVALID_JSON_ARRAY);
|
|
27
29
|
for (const key of keys) {
|
|
28
30
|
if (array && key === "length")
|
|
29
31
|
continue;
|
|
30
32
|
if (typeof key !== "string")
|
|
31
|
-
throw Error(
|
|
33
|
+
throw Error(ERRORS.INVALID_JSON_KEY);
|
|
32
34
|
if (array &&
|
|
33
35
|
(!/^(0|[1-9][0-9]*)$/.test(key) || Number(key) >= value.length))
|
|
34
|
-
throw Error(
|
|
36
|
+
throw Error(ERRORS.INVALID_JSON_ARRAY);
|
|
35
37
|
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
36
38
|
if (!descriptor.enumerable || !("value" in descriptor))
|
|
37
|
-
throw Error(
|
|
39
|
+
throw Error(ERRORS.INVALID_JSON_PROPERTY);
|
|
38
40
|
assertJson(descriptor.value, depth + 1, active, budget);
|
|
39
41
|
}
|
|
40
42
|
}
|
package/dist/limits.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
});
|
package/dist/mode.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Context } from "@deepseek-ai/cordis";
|
|
2
|
+
export declare const name: "execution-bus-mode";
|
|
3
|
+
export declare const inject: string[];
|
|
4
|
+
/** An additive preset row: presentation, tool inventory and authority belong to the host PTC composition. */
|
|
5
|
+
export declare function apply(ctx: Context): void;
|
package/dist/mode.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { scopeOf } from "@deepseek-ai/dsh-scope";
|
|
2
|
+
import { apply as applyBus } from "./plugin.js";
|
|
3
|
+
import { PRODUCT, MODE_INSTRUCTIONS } from "./product.js";
|
|
4
|
+
export const name = PRODUCT.modePluginName;
|
|
5
|
+
export const inject = ["tools", "jobs", "systemPrompt"];
|
|
6
|
+
/** An additive preset row: presentation, tool inventory and authority belong to the host PTC composition. */
|
|
7
|
+
export function apply(ctx) {
|
|
8
|
+
if (scopeOf(ctx) === undefined)
|
|
9
|
+
throw Error("Xiao He mode requires an Agent preset scope");
|
|
10
|
+
ctx.effect(() => ctx.systemPrompt.section({
|
|
11
|
+
name: "XIAO_HE_EXECUTION_BUS",
|
|
12
|
+
order: 1700,
|
|
13
|
+
text: MODE_INSTRUCTIONS,
|
|
14
|
+
}));
|
|
15
|
+
applyBus(ctx);
|
|
16
|
+
}
|