@mininglamp-oss/octo-runtime 1.1.0 → 1.1.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/README.md +4 -2
- package/apps/cli/README.md +3 -1
- package/apps/cli/dist/build-info.json +2 -2
- package/apps/cli/dist/extras.d.ts +4 -2
- package/apps/cli/dist/extras.js +20 -21
- package/apps/cli/dist/help.js +3 -0
- package/apps/cli/dist/logs.d.ts +10 -0
- package/apps/cli/dist/logs.js +186 -0
- package/apps/cli/dist/main.d.ts +1 -0
- package/apps/cli/dist/main.js +94 -19
- package/apps/cli/dist/{default_adapters.json → manifest.json} +5 -0
- package/apps/cli/dist/module-manifest.d.ts +3 -0
- package/apps/cli/dist/module-manifest.js +11 -0
- package/apps/cli/dist/openclaw.js +30 -6
- package/apps/cli/dist/output.js +3 -0
- package/apps/cli/dist/playboard.js +10 -8
- package/apps/cli/dist/status-output.d.ts +10 -0
- package/apps/cli/dist/status-output.js +65 -0
- package/apps/cli/dist/worker.d.ts +1 -0
- package/apps/cli/dist/worker.js +18 -0
- package/dist/public/host.d.ts +1 -0
- package/node_modules/@mininglamp-oss/octo-runtime-acp-client/package.json +3 -3
- package/node_modules/@mininglamp-oss/octo-runtime-adapter-acp/package.json +4 -4
- package/node_modules/@mininglamp-oss/octo-runtime-adapter-antigravity/package.json +3 -3
- package/node_modules/@mininglamp-oss/octo-runtime-adapter-claude-code/package.json +3 -3
- package/node_modules/@mininglamp-oss/octo-runtime-adapter-codebuddy/package.json +4 -4
- package/node_modules/@mininglamp-oss/octo-runtime-adapter-codex/package.json +3 -3
- package/node_modules/@mininglamp-oss/octo-runtime-adapter-hermes/package.json +2 -2
- package/node_modules/@mininglamp-oss/octo-runtime-adapter-kimi-code/package.json +2 -2
- package/node_modules/@mininglamp-oss/octo-runtime-adapter-mcode/package.json +2 -2
- package/node_modules/@mininglamp-oss/octo-runtime-adapter-openclaw/package.json +3 -3
- package/node_modules/@mininglamp-oss/octo-runtime-adapter-opencode/package.json +4 -4
- package/node_modules/@mininglamp-oss/octo-runtime-adapter-qodercli/package.json +2 -2
- package/node_modules/@mininglamp-oss/octo-runtime-adapter-qoderclicn/package.json +2 -2
- package/node_modules/@mininglamp-oss/octo-runtime-adapter-sdk/dist/index.d.ts +1 -0
- package/node_modules/@mininglamp-oss/octo-runtime-adapter-sdk/dist/index.js +1 -0
- package/node_modules/@mininglamp-oss/octo-runtime-adapter-sdk/dist/module-manifest.d.ts +33 -0
- package/node_modules/@mininglamp-oss/octo-runtime-adapter-sdk/dist/module-manifest.js +118 -0
- package/node_modules/@mininglamp-oss/octo-runtime-adapter-sdk/dist/runtime-log-worker.js +7 -5
- package/node_modules/@mininglamp-oss/octo-runtime-adapter-sdk/dist/runtime-log.d.ts +3 -0
- package/node_modules/@mininglamp-oss/octo-runtime-adapter-sdk/dist/runtime-log.js +45 -10
- package/node_modules/@mininglamp-oss/octo-runtime-adapter-sdk/dist/service-status.d.ts +11 -0
- package/node_modules/@mininglamp-oss/octo-runtime-adapter-sdk/dist/service-status.js +70 -0
- package/node_modules/@mininglamp-oss/octo-runtime-adapter-sdk/package.json +5 -3
- package/node_modules/@mininglamp-oss/octo-runtime-adapter-zcode-gui/package.json +3 -3
- package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/build-info.json +2 -2
- package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/extras.d.ts +4 -2
- package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/extras.js +20 -21
- package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/help.js +3 -0
- package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/logs.d.ts +10 -0
- package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/logs.js +186 -0
- package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/main.d.ts +1 -0
- package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/main.js +94 -19
- package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/{default_adapters.json → manifest.json} +5 -0
- package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/module-manifest.d.ts +3 -0
- package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/module-manifest.js +11 -0
- package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/openclaw.js +30 -6
- package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/output.js +3 -0
- package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/playboard.js +10 -8
- package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/status-output.d.ts +10 -0
- package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/status-output.js +65 -0
- package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/worker.d.ts +1 -0
- package/node_modules/@mininglamp-oss/octo-runtime-cli/dist/worker.js +18 -0
- package/node_modules/@mininglamp-oss/octo-runtime-cli/package.json +22 -9
- package/node_modules/@mininglamp-oss/octo-runtime-cross-service/package.json +2 -2
- package/node_modules/@mininglamp-oss/octo-runtime-executor-core/dist/model-input.d.ts +1 -1
- package/node_modules/@mininglamp-oss/octo-runtime-executor-core/dist/model-input.js +5 -5
- package/node_modules/@mininglamp-oss/octo-runtime-executor-core/package.json +3 -3
- package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/heartbeat.d.ts +16 -0
- package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/heartbeat.js +59 -0
- package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/invocation-manifest.d.ts +1 -0
- package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/invocation-manifest.js +4 -1
- package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/manifest-adapters.d.ts +6 -0
- package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/manifest-adapters.js +88 -0
- package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/module-specifier.d.ts +1 -2
- package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/module-specifier.js +2 -9
- package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/process.d.ts +7 -2
- package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/process.js +138 -9
- package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/runner.js +3 -92
- package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/serve.js +36 -12
- package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/server.d.ts +4 -1
- package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/server.js +14 -6
- package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/service.js +2 -1
- package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/supervisor.d.ts +2 -2
- package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/dist/supervisor.js +57 -34
- package/node_modules/@mininglamp-oss/octo-runtime-executor-kernel/package.json +8 -16
- package/node_modules/@mininglamp-oss/octo-runtime-extra-channel-gateway/config-schema.json +2 -2
- package/node_modules/@mininglamp-oss/octo-runtime-extra-channel-gateway/dist/application/im-channel.js +16 -17
- package/node_modules/@mininglamp-oss/octo-runtime-extra-channel-gateway/package.json +6 -6
- package/node_modules/@mininglamp-oss/octo-runtime-extra-channel-wecom/config-schema.json +2 -2
- package/node_modules/@mininglamp-oss/octo-runtime-extra-channel-wecom/package.json +5 -5
- package/node_modules/@mininglamp-oss/octo-runtime-extra-webui/config-schema.json +2 -2
- package/node_modules/@mininglamp-oss/octo-runtime-extra-webui/dist/index.js +1 -1
- package/node_modules/@mininglamp-oss/octo-runtime-extra-webui/dist/store.d.ts +4 -1
- package/node_modules/@mininglamp-oss/octo-runtime-extra-webui/dist/store.js +54 -4
- package/node_modules/@mininglamp-oss/octo-runtime-extra-webui/package.json +2 -2
- package/node_modules/@mininglamp-oss/octo-runtime-host-sdk/dist/extra-config.d.ts +8 -1
- package/node_modules/@mininglamp-oss/octo-runtime-host-sdk/dist/extra-config.js +27 -12
- package/node_modules/@mininglamp-oss/octo-runtime-host-sdk/dist/extra-service.js +39 -2
- package/node_modules/@mininglamp-oss/octo-runtime-host-sdk/dist/index.d.ts +1 -0
- package/node_modules/@mininglamp-oss/octo-runtime-host-sdk/package.json +4 -4
- package/node_modules/@mininglamp-oss/octo-runtime-protocol/package.json +1 -1
- package/node_modules/import-meta-resolve/index.d.ts +19 -0
- package/node_modules/import-meta-resolve/index.d.ts.map +1 -0
- package/node_modules/import-meta-resolve/index.js +47 -0
- package/node_modules/import-meta-resolve/lib/errors.d.ts +24 -0
- package/node_modules/import-meta-resolve/lib/errors.d.ts.map +1 -0
- package/node_modules/import-meta-resolve/lib/errors.js +504 -0
- package/node_modules/import-meta-resolve/lib/get-format.d.ts +13 -0
- package/node_modules/import-meta-resolve/lib/get-format.d.ts.map +1 -0
- package/node_modules/import-meta-resolve/lib/get-format.js +159 -0
- package/node_modules/import-meta-resolve/lib/package-json-reader.d.ts +31 -0
- package/node_modules/import-meta-resolve/lib/package-json-reader.d.ts.map +1 -0
- package/node_modules/import-meta-resolve/lib/package-json-reader.js +177 -0
- package/node_modules/import-meta-resolve/lib/resolve.d.ts +29 -0
- package/node_modules/import-meta-resolve/lib/resolve.d.ts.map +1 -0
- package/node_modules/import-meta-resolve/lib/resolve.js +1301 -0
- package/node_modules/import-meta-resolve/lib/utils.d.ts +6 -0
- package/node_modules/import-meta-resolve/lib/utils.d.ts.map +1 -0
- package/node_modules/import-meta-resolve/lib/utils.js +47 -0
- package/node_modules/import-meta-resolve/license +74 -0
- package/node_modules/import-meta-resolve/package.json +78 -0
- package/node_modules/import-meta-resolve/readme.md +252 -0
- package/package.json +28 -26
- package/packages/octo-runtime-extra-channel-gateway/config-schema.json +2 -2
- package/packages/octo-runtime-extra-channel-wecom/config-schema.json +2 -2
- package/packages/octo-runtime-extra-webui/config-schema.json +2 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { spawnWorker } from "./process.js";
|
|
1
|
+
import { closeWorker, spawnWorker } from "./process.js";
|
|
2
2
|
import { KernelError } from "./types.js";
|
|
3
|
-
|
|
3
|
+
import { WorkerHeartbeat } from "./heartbeat.js";
|
|
4
|
+
/** Kernel exclusively owns worker replacement; incomplete cleanup prevents a second owner. */
|
|
4
5
|
export class WorkerSupervisor {
|
|
5
6
|
options;
|
|
6
7
|
recordWorker;
|
|
@@ -11,12 +12,13 @@ export class WorkerSupervisor {
|
|
|
11
12
|
#closing = false;
|
|
12
13
|
#recovery;
|
|
13
14
|
#timer;
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#stableSince = 0;
|
|
15
|
+
#heartbeat;
|
|
16
|
+
#activeTime = 0;
|
|
17
17
|
#attempts = [];
|
|
18
|
+
#slowRecovery = false;
|
|
18
19
|
#abort = new AbortController();
|
|
19
20
|
#cleanupFailure;
|
|
21
|
+
#closeUntil;
|
|
20
22
|
constructor(options, recordWorker) {
|
|
21
23
|
this.options = options;
|
|
22
24
|
this.recordWorker = recordWorker;
|
|
@@ -63,35 +65,44 @@ export class WorkerSupervisor {
|
|
|
63
65
|
throw error;
|
|
64
66
|
}
|
|
65
67
|
if (this.#closing) {
|
|
66
|
-
|
|
68
|
+
try {
|
|
69
|
+
await closeWorker(worker, this.#closeUntil ?? Date.now() + 5000, true);
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
// A late startup still owns resources; failed rollback must block every future replacement.
|
|
73
|
+
throw (this.#cleanupFailure = new AggregateError([error], "WORKER_CLEANUP_INCOMPLETE"));
|
|
74
|
+
}
|
|
67
75
|
return;
|
|
68
76
|
}
|
|
69
77
|
this.#worker = worker;
|
|
70
78
|
this.#generation++;
|
|
71
79
|
this.#state = "ready";
|
|
72
|
-
|
|
73
|
-
this.#
|
|
74
|
-
|
|
80
|
+
const heartbeat = new WorkerHeartbeat();
|
|
81
|
+
this.#heartbeat = heartbeat;
|
|
82
|
+
let elapsed = 0;
|
|
75
83
|
worker.child.on("message", (message) => {
|
|
76
84
|
if (this.#worker === worker &&
|
|
77
85
|
message &&
|
|
78
86
|
typeof message === "object" &&
|
|
79
87
|
"type" in message &&
|
|
80
88
|
message.type === "worker.pong") {
|
|
81
|
-
|
|
82
|
-
this.#pendingPing = false;
|
|
89
|
+
heartbeat.pong("probe" in message ? message.probe : undefined);
|
|
83
90
|
}
|
|
84
91
|
});
|
|
85
92
|
worker.child.once("exit", () => this.#failed(worker));
|
|
86
93
|
worker.child.once("error", () => this.#failed(worker));
|
|
87
94
|
this.#timer = setInterval(() => {
|
|
88
|
-
if (
|
|
95
|
+
if (this.#closing || this.#worker !== worker || this.#state !== "ready")
|
|
96
|
+
return;
|
|
97
|
+
const probe = heartbeat.tick();
|
|
98
|
+
this.#activeTime += heartbeat.activeMs - elapsed;
|
|
99
|
+
elapsed = heartbeat.activeMs;
|
|
100
|
+
if (probe.type === "expired") {
|
|
89
101
|
this.#failed(worker);
|
|
90
102
|
return;
|
|
91
103
|
}
|
|
92
|
-
if (
|
|
93
|
-
|
|
94
|
-
worker.child.send({ type: "kernel.ping" }, (error) => {
|
|
104
|
+
if (probe.type === "ping" && worker.child.connected) {
|
|
105
|
+
worker.child.send({ type: "kernel.ping", probe: probe.id }, (error) => {
|
|
95
106
|
if (error)
|
|
96
107
|
this.#failed(worker);
|
|
97
108
|
});
|
|
@@ -106,31 +117,39 @@ export class WorkerSupervisor {
|
|
|
106
117
|
return;
|
|
107
118
|
clearInterval(this.#timer);
|
|
108
119
|
this.#state = "recovering";
|
|
109
|
-
//
|
|
120
|
+
// Reject the old RPC first so its source emits one lost terminal. Recovery admits new work only.
|
|
110
121
|
worker.client.close(new KernelError("WORKER_LOST"));
|
|
111
|
-
if (
|
|
122
|
+
if ((this.#heartbeat?.healthyMs ?? 0) >= 60_000) {
|
|
112
123
|
this.#attempts = [];
|
|
113
124
|
this.#attempt = 0;
|
|
125
|
+
this.#slowRecovery = false;
|
|
114
126
|
}
|
|
115
|
-
this.#recovery = this.#recover(worker).catch(() => {
|
|
116
|
-
|
|
127
|
+
this.#recovery = this.#recover(worker).catch((error) => {
|
|
128
|
+
// Only unconfirmed cleanup or ownership ends recovery; ordinary startup failures stay retryable.
|
|
129
|
+
this.#cleanupFailure ??= new AggregateError([error], "WORKER_RECOVERY_BLOCKED");
|
|
130
|
+
if (!this.#closing)
|
|
131
|
+
this.#state = "exhausted";
|
|
117
132
|
});
|
|
118
133
|
}
|
|
119
134
|
async #recover(worker) {
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
|
|
135
|
+
// Reap the old worker before replacement to prevent concurrent Provider owners.
|
|
136
|
+
// Restore service availability only; #failed already marked old runs lost and never replays them.
|
|
137
|
+
// Preserve baseline recovery for leaders that exited before close began. On Windows this
|
|
138
|
+
// path cannot verify detached descendants; EOF-close checks require a live leader at entry.
|
|
139
|
+
if (worker.child.exitCode !== null || worker.child.signalCode !== null)
|
|
140
|
+
await worker.close();
|
|
141
|
+
else
|
|
142
|
+
await closeWorker(worker, Date.now() + 4800, true);
|
|
123
143
|
await this.recordWorker?.(undefined);
|
|
124
144
|
this.#worker = undefined;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
this.#attempts = this.#attempts.filter((at) => Date.now() - at < 60_000);
|
|
145
|
+
while (!this.#closing) {
|
|
146
|
+
// Suspend time and clock changes must not replenish a recently exhausted restart budget.
|
|
147
|
+
this.#attempts = this.#attempts.filter((at) => this.#activeTime - at < 60_000);
|
|
129
148
|
if (this.#attempts.length >= 3)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
this.#
|
|
149
|
+
this.#slowRecovery = true;
|
|
150
|
+
// Exhausting the fast budget opens a bounded-rate recovery loop, not a permanent outage.
|
|
151
|
+
// Keep slow recovery across short-lived workers until 60 seconds of health is observed.
|
|
152
|
+
const delay = this.#slowRecovery ? 15_000 : [250, 1000, 4000][this.#attempts.length];
|
|
134
153
|
await new Promise((resolve) => {
|
|
135
154
|
const finish = () => {
|
|
136
155
|
clearTimeout(timer);
|
|
@@ -142,6 +161,9 @@ export class WorkerSupervisor {
|
|
|
142
161
|
});
|
|
143
162
|
if (this.#closing)
|
|
144
163
|
return;
|
|
164
|
+
if (!this.#slowRecovery)
|
|
165
|
+
this.#attempts.push(this.#activeTime);
|
|
166
|
+
this.#attempt = Math.min(Number.MAX_SAFE_INTEGER, this.#attempt + 1);
|
|
145
167
|
try {
|
|
146
168
|
await this.#spawn();
|
|
147
169
|
return;
|
|
@@ -149,17 +171,18 @@ export class WorkerSupervisor {
|
|
|
149
171
|
catch (error) {
|
|
150
172
|
if (error instanceof AggregateError)
|
|
151
173
|
throw error;
|
|
152
|
-
/*
|
|
174
|
+
/* Ordinary startup failures are reaped; aggregate cleanup failures prevent replacement. */
|
|
153
175
|
}
|
|
154
176
|
}
|
|
155
|
-
this.#state = "exhausted";
|
|
156
177
|
}
|
|
157
|
-
async close() {
|
|
178
|
+
async close(until = Date.now() + 5000) {
|
|
158
179
|
this.#closing = true;
|
|
180
|
+
this.#closeUntil ??= until;
|
|
159
181
|
this.#abort.abort();
|
|
160
182
|
clearInterval(this.#timer);
|
|
161
183
|
await this.#recovery;
|
|
162
|
-
|
|
184
|
+
if (this.#worker)
|
|
185
|
+
await closeWorker(this.#worker, this.#closeUntil);
|
|
163
186
|
if (this.#cleanupFailure)
|
|
164
187
|
throw this.#cleanupFailure;
|
|
165
188
|
await this.recordWorker?.(undefined);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mininglamp-oss/octo-runtime-executor-kernel",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -8,27 +8,19 @@
|
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"import": "./dist/index.js"
|
|
10
10
|
},
|
|
11
|
+
"./manifest-adapters": {
|
|
12
|
+
"types": "./dist/manifest-adapters.d.ts",
|
|
13
|
+
"import": "./dist/manifest-adapters.js"
|
|
14
|
+
},
|
|
11
15
|
"./runner": {
|
|
12
16
|
"types": "./dist/runner.d.ts",
|
|
13
17
|
"import": "./dist/runner.js"
|
|
14
18
|
}
|
|
15
19
|
},
|
|
16
20
|
"dependencies": {
|
|
17
|
-
"@mininglamp-oss/octo-runtime-executor-core": "1.1.
|
|
18
|
-
"@mininglamp-oss/octo-runtime-
|
|
19
|
-
"@mininglamp-oss/octo-runtime-adapter-
|
|
20
|
-
"@mininglamp-oss/octo-runtime-adapter-codebuddy": "1.1.0",
|
|
21
|
-
"@mininglamp-oss/octo-runtime-adapter-codex": "1.1.0",
|
|
22
|
-
"@mininglamp-oss/octo-runtime-adapter-hermes": "1.1.0",
|
|
23
|
-
"@mininglamp-oss/octo-runtime-adapter-kimi-code": "1.1.0",
|
|
24
|
-
"@mininglamp-oss/octo-runtime-adapter-mcode": "1.1.0",
|
|
25
|
-
"@mininglamp-oss/octo-runtime-adapter-openclaw": "1.1.0",
|
|
26
|
-
"@mininglamp-oss/octo-runtime-adapter-opencode": "1.1.0",
|
|
27
|
-
"@mininglamp-oss/octo-runtime-adapter-qodercli": "1.1.0",
|
|
28
|
-
"@mininglamp-oss/octo-runtime-adapter-qoderclicn": "1.1.0",
|
|
29
|
-
"@mininglamp-oss/octo-runtime-adapter-zcode-gui": "1.1.0",
|
|
30
|
-
"@mininglamp-oss/octo-runtime-protocol": "1.1.0",
|
|
31
|
-
"@mininglamp-oss/octo-runtime-adapter-sdk": "1.1.0"
|
|
21
|
+
"@mininglamp-oss/octo-runtime-executor-core": "1.1.1",
|
|
22
|
+
"@mininglamp-oss/octo-runtime-protocol": "1.1.1",
|
|
23
|
+
"@mininglamp-oss/octo-runtime-adapter-sdk": "1.1.1"
|
|
32
24
|
},
|
|
33
25
|
"types": "./dist/index.d.ts",
|
|
34
26
|
"files": [
|
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"$comment": "声明文件最多 1 MiB,POSIX 权限须为 0600;单 Bot JSON 最多 256 KiB。Bot 按规范化 api_url 与 bot_id 组合判重。路径可访问性、凭据、身份和 Space 授权、重试起点不大于上限等关系仍需运行时检查。Context 内部对象遵循 octo.context-envelope/v3,具体协议约束由现有运行时校验,本文件不重复展开。",
|
|
7
7
|
"type": "object",
|
|
8
8
|
"additionalProperties": false,
|
|
9
|
-
"required": ["
|
|
9
|
+
"required": ["config"],
|
|
10
10
|
"properties": {
|
|
11
11
|
"module": {
|
|
12
12
|
"type": "string",
|
|
13
13
|
"const": "@mininglamp-oss/octo-runtime-extra-channel-gateway",
|
|
14
|
-
"description": "
|
|
14
|
+
"description": "可选旧声明。CLI 从 manifest.json 选择模块;省略时使用有效清单,保留时须与清单文字一致。SDK 原生 import 不需要此字段。此 schema 约束固定包名,宿主使用其他公开路径时可自行调整。"
|
|
15
15
|
},
|
|
16
16
|
"enabled": {
|
|
17
17
|
"type": "boolean",
|
|
@@ -49,7 +49,6 @@ export class ImRuntime {
|
|
|
49
49
|
#busyNotifications = new Map();
|
|
50
50
|
#busyNotifiedAt = new Map();
|
|
51
51
|
#retry = 0;
|
|
52
|
-
#everOnline = false;
|
|
53
52
|
#disconnecting;
|
|
54
53
|
constructor(config, dependencies) {
|
|
55
54
|
this.dependencies = dependencies;
|
|
@@ -110,7 +109,6 @@ export class ImRuntime {
|
|
|
110
109
|
this.dependencies.signal?.addEventListener("abort", this.#ownerStopped, { once: true });
|
|
111
110
|
this.#delivery = new ImDeliveryDispatcher(this.#signal);
|
|
112
111
|
}
|
|
113
|
-
this.#everOnline = false;
|
|
114
112
|
this.#retry = 0;
|
|
115
113
|
this.#error = undefined;
|
|
116
114
|
this.#state = "connecting";
|
|
@@ -125,6 +123,9 @@ export class ImRuntime {
|
|
|
125
123
|
async #connect(recovering) {
|
|
126
124
|
if (this.#closed || this.#draining || this.#signal.aborted)
|
|
127
125
|
return;
|
|
126
|
+
this.#log(recovering ? "channel.retry_started" : "channel.connecting", {
|
|
127
|
+
attempt: this.#retry
|
|
128
|
+
});
|
|
128
129
|
this.#connectionAbort?.abort();
|
|
129
130
|
try {
|
|
130
131
|
await this.#disconnecting;
|
|
@@ -134,7 +135,7 @@ export class ImRuntime {
|
|
|
134
135
|
this.#state = this.#closed ? "stopped" : "failed";
|
|
135
136
|
if (!this.#closed)
|
|
136
137
|
this.#error = "IM_DISPOSE_FAILED";
|
|
137
|
-
this.#
|
|
138
|
+
this.#diagnostic("IM_DISPOSE_FAILED");
|
|
138
139
|
return;
|
|
139
140
|
}
|
|
140
141
|
if (this.#closed || this.#draining || this.#signal.aborted)
|
|
@@ -183,7 +184,6 @@ export class ImRuntime {
|
|
|
183
184
|
if (disconnected)
|
|
184
185
|
throw disconnected;
|
|
185
186
|
starting = false;
|
|
186
|
-
this.#everOnline = true;
|
|
187
187
|
this.#state = "online";
|
|
188
188
|
this.#log("channel.connected", { state: "online" });
|
|
189
189
|
this.#error = undefined;
|
|
@@ -196,21 +196,19 @@ export class ImRuntime {
|
|
|
196
196
|
const failure = connectionFailure(error);
|
|
197
197
|
this.#connectionAbort.abort();
|
|
198
198
|
this.#error = failure.message;
|
|
199
|
-
|
|
199
|
+
// Offline startup and later disconnects share recovery; only error semantics decide eligibility.
|
|
200
|
+
const retry = failure.retryable;
|
|
200
201
|
this.#state = retry ? "retrying" : "failed";
|
|
201
202
|
this.#log("channel.connection_failed", { state: this.#state, code: failure.message });
|
|
202
203
|
try {
|
|
203
204
|
await connector.close();
|
|
204
205
|
}
|
|
205
206
|
catch {
|
|
206
|
-
this.#everOnline = false;
|
|
207
207
|
if (!this.#closed)
|
|
208
208
|
this.#state = "failed";
|
|
209
209
|
this.#diagnostic("IM_DISPOSE_FAILED");
|
|
210
210
|
return;
|
|
211
211
|
}
|
|
212
|
-
if (!retry)
|
|
213
|
-
this.#everOnline = false;
|
|
214
212
|
if (retry)
|
|
215
213
|
this.#scheduleReconnect(failure.retryAfterMs);
|
|
216
214
|
}
|
|
@@ -218,12 +216,10 @@ export class ImRuntime {
|
|
|
218
216
|
#lostConnection(connector, failure) {
|
|
219
217
|
this.#log("channel.disconnected", { code: failure.message });
|
|
220
218
|
this.#error = failure.message;
|
|
221
|
-
const retry =
|
|
219
|
+
const retry = failure.retryable;
|
|
222
220
|
this.#state = retry ? "retrying" : "failed";
|
|
223
|
-
if (!retry)
|
|
224
|
-
this.#everOnline = false;
|
|
225
221
|
this.#connectionAbort?.abort();
|
|
226
|
-
//
|
|
222
|
+
// Reap the old connection before backoff; shutdown and stale callbacks cannot create a second owner.
|
|
227
223
|
this.#disconnecting = connector.close();
|
|
228
224
|
void this.#disconnecting.then(() => {
|
|
229
225
|
if (retry)
|
|
@@ -231,7 +227,6 @@ export class ImRuntime {
|
|
|
231
227
|
}, () => {
|
|
232
228
|
if (!this.#closed)
|
|
233
229
|
this.#state = "failed";
|
|
234
|
-
this.#everOnline = false;
|
|
235
230
|
this.#diagnostic("IM_DISPOSE_FAILED");
|
|
236
231
|
});
|
|
237
232
|
}
|
|
@@ -242,9 +237,14 @@ export class ImRuntime {
|
|
|
242
237
|
this.#state !== "retrying" ||
|
|
243
238
|
this.#reconnect)
|
|
244
239
|
return;
|
|
245
|
-
const delayMs = Math.max(Math.min(
|
|
246
|
-
this.#retry = Math.min(
|
|
247
|
-
|
|
240
|
+
const delayMs = Math.max([5_000, 10_000, 15_000][Math.min(2, this.#retry)], retryAfterMs);
|
|
241
|
+
this.#retry = Math.min(Number.MAX_SAFE_INTEGER, this.#retry + 1);
|
|
242
|
+
this.#log("channel.retry_scheduled", {
|
|
243
|
+
state: "retrying",
|
|
244
|
+
delay_ms: delayMs,
|
|
245
|
+
attempt: this.#retry
|
|
246
|
+
});
|
|
247
|
+
// Node clamps oversized timers to 1ms. Partition long Retry-After waits without bypassing throttling.
|
|
248
248
|
const deadline = performance.now() + delayMs;
|
|
249
249
|
const wake = () => {
|
|
250
250
|
if (this.#closed || this.#draining || this.#signal.aborted || this.#state !== "retrying")
|
|
@@ -1069,7 +1069,6 @@ export class ImRuntime {
|
|
|
1069
1069
|
this.#state = "stopped";
|
|
1070
1070
|
this.#log("channel.stopped", { state: "stopped" });
|
|
1071
1071
|
this.#error = undefined;
|
|
1072
|
-
this.#everOnline = false;
|
|
1073
1072
|
this.#retry = 0;
|
|
1074
1073
|
for (const preparation of this.#preparing.values())
|
|
1075
1074
|
preparation.abort();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mininglamp-oss/octo-runtime-extra-channel-gateway",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
"docs"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@mininglamp-oss/octo-runtime-executor-kernel": "1.1.
|
|
22
|
-
"@mininglamp-oss/octo-runtime-adapter-sdk": "1.1.
|
|
23
|
-
"@mininglamp-oss/octo-runtime-executor-core": "1.1.
|
|
24
|
-
"@mininglamp-oss/octo-runtime-host-sdk": "1.1.
|
|
25
|
-
"@mininglamp-oss/octo-runtime-protocol": "1.1.
|
|
21
|
+
"@mininglamp-oss/octo-runtime-executor-kernel": "1.1.1",
|
|
22
|
+
"@mininglamp-oss/octo-runtime-adapter-sdk": "1.1.1",
|
|
23
|
+
"@mininglamp-oss/octo-runtime-executor-core": "1.1.1",
|
|
24
|
+
"@mininglamp-oss/octo-runtime-host-sdk": "1.1.1",
|
|
25
|
+
"@mininglamp-oss/octo-runtime-protocol": "1.1.1",
|
|
26
26
|
"mdast-util-from-markdown": "2.1.0",
|
|
27
27
|
"ws": "8.22.0"
|
|
28
28
|
}
|
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"$comment": "声明文件最多 1 MiB,POSIX 权限须为 0600。Bot 按 ws_url 与 bot_id 的组合判重;凭据文件权限、目标 Agent 是否存在、Provider 是否可用等仍需运行时检查。Context 内部对象遵循 octo.context-envelope/v3,具体协议约束由现有运行时校验,本文件不重复展开。",
|
|
7
7
|
"type": "object",
|
|
8
8
|
"additionalProperties": false,
|
|
9
|
-
"required": ["
|
|
9
|
+
"required": ["config"],
|
|
10
10
|
"properties": {
|
|
11
11
|
"module": {
|
|
12
12
|
"type": "string",
|
|
13
13
|
"const": "@mininglamp-oss/octo-runtime-extra-channel-wecom",
|
|
14
|
-
"description": "
|
|
14
|
+
"description": "可选旧声明。CLI 从 manifest.json 选择模块;省略时使用有效清单,保留时须与清单文字一致。SDK 原生 import 不需要此字段。此 schema 约束固定包名,宿主使用其他公开路径时可自行调整。"
|
|
15
15
|
},
|
|
16
16
|
"enabled": {
|
|
17
17
|
"type": "boolean",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mininglamp-oss/octo-runtime-extra-channel-wecom",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
"docs"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@mininglamp-oss/octo-runtime-host-sdk": "1.1.
|
|
22
|
-
"@mininglamp-oss/octo-runtime-executor-core": "1.1.
|
|
23
|
-
"@mininglamp-oss/octo-runtime-executor-kernel": "1.1.
|
|
21
|
+
"@mininglamp-oss/octo-runtime-host-sdk": "1.1.1",
|
|
22
|
+
"@mininglamp-oss/octo-runtime-executor-core": "1.1.1",
|
|
23
|
+
"@mininglamp-oss/octo-runtime-executor-kernel": "1.1.1",
|
|
24
24
|
"ws": "8.22.0",
|
|
25
|
-
"@mininglamp-oss/octo-runtime-protocol": "1.1.
|
|
25
|
+
"@mininglamp-oss/octo-runtime-protocol": "1.1.1"
|
|
26
26
|
}
|
|
27
27
|
}
|
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"$comment": "声明文件最多 1 MiB,POSIX 权限须为 0600。端口占用、路径可访问性、会话文件内容及文件权限仍需运行时检查。加载生命周期 loaded 不等于监听成功,宿主须检查插件状态中的 listening 与 errors。",
|
|
7
7
|
"type": "object",
|
|
8
8
|
"additionalProperties": false,
|
|
9
|
-
"required": ["
|
|
9
|
+
"required": ["config"],
|
|
10
10
|
"properties": {
|
|
11
11
|
"module": {
|
|
12
12
|
"type": "string",
|
|
13
13
|
"const": "@mininglamp-oss/octo-runtime-extra-webui",
|
|
14
|
-
"description": "
|
|
14
|
+
"description": "可选旧声明。CLI 从 manifest.json 选择模块;省略时使用有效清单,保留时须与清单文字一致。SDK 原生 import 不需要此字段。此 schema 约束固定包名,宿主使用其他公开路径时可自行调整。"
|
|
15
15
|
},
|
|
16
16
|
"enabled": {
|
|
17
17
|
"type": "boolean",
|
|
@@ -73,7 +73,7 @@ async function initialize(ctx, config) {
|
|
|
73
73
|
errors.push("WEBUI_CONTEXT_INVALID");
|
|
74
74
|
return;
|
|
75
75
|
}
|
|
76
|
-
const store = new SessionStore(config.sessionStorePath);
|
|
76
|
+
const store = new SessionStore(config.sessionStorePath, ctx.log);
|
|
77
77
|
try {
|
|
78
78
|
await store.open();
|
|
79
79
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { RuntimeLog } from "@mininglamp-oss/octo-runtime-host-sdk";
|
|
1
2
|
export interface Session {
|
|
2
3
|
id: string;
|
|
3
4
|
title: string;
|
|
@@ -14,9 +15,11 @@ export declare function validMessages(value: unknown): value is unknown[];
|
|
|
14
15
|
export declare function validBinding(value: unknown): value is Record<string, unknown>;
|
|
15
16
|
export declare class SessionStore {
|
|
16
17
|
readonly path: string;
|
|
18
|
+
private readonly log?;
|
|
17
19
|
records: Session[];
|
|
18
20
|
private queue;
|
|
19
|
-
constructor(path: string);
|
|
21
|
+
constructor(path: string, log?: RuntimeLog | undefined);
|
|
22
|
+
private cleanupError;
|
|
20
23
|
open(): Promise<void>;
|
|
21
24
|
change<T>(operation: (records: Session[]) => T): Promise<T>;
|
|
22
25
|
}
|
|
@@ -1,6 +1,26 @@
|
|
|
1
|
-
import { mkdir, readFile, rename,
|
|
1
|
+
import { mkdir, open, readFile, rename, unlink } from "node:fs/promises";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
5
|
+
// 本机 Windows 的瞬态 EPERM 在约 1.6 秒后恢复;只重试替换,不重放业务变更。
|
|
6
|
+
// 最多 8 次替换、3170 ms 退避;文件系统调用和调度本身的耗时不计入退避预算。
|
|
7
|
+
const replacementDelays = [20, 50, 100, 200, 400, 800, 1600];
|
|
8
|
+
async function replace(temporary, destination) {
|
|
9
|
+
for (let attempt = 0;; attempt++) {
|
|
10
|
+
try {
|
|
11
|
+
await rename(temporary, destination);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
// 返回结束重试的实际错误及其原生字段,不能让最初的 EPERM 掩盖后续错误。
|
|
16
|
+
if (process.platform !== "win32" ||
|
|
17
|
+
error.code !== "EPERM" ||
|
|
18
|
+
attempt === replacementDelays.length)
|
|
19
|
+
throw error;
|
|
20
|
+
await delay(replacementDelays[attempt]);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
4
24
|
export function validMessages(value) {
|
|
5
25
|
return (Array.isArray(value) &&
|
|
6
26
|
value.every((message) => {
|
|
@@ -24,10 +44,21 @@ export function validBinding(value) {
|
|
|
24
44
|
}
|
|
25
45
|
export class SessionStore {
|
|
26
46
|
path;
|
|
47
|
+
log;
|
|
27
48
|
records = [];
|
|
28
49
|
queue = Promise.resolve();
|
|
29
|
-
constructor(path) {
|
|
50
|
+
constructor(path, log) {
|
|
30
51
|
this.path = path;
|
|
52
|
+
this.log = log;
|
|
53
|
+
}
|
|
54
|
+
cleanupError(event, error) {
|
|
55
|
+
// 清理可能因未释放句柄等原因失败;仅报告错误码,诊断失败不能覆盖保存错误。
|
|
56
|
+
try {
|
|
57
|
+
this.log?.("warn", event, { code: error?.code });
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// 日志回调不能改变存储操作的结果。
|
|
61
|
+
}
|
|
31
62
|
}
|
|
32
63
|
async open() {
|
|
33
64
|
try {
|
|
@@ -69,8 +100,27 @@ export class SessionStore {
|
|
|
69
100
|
throw new Error("WEBUI_STORE_TOO_LARGE");
|
|
70
101
|
await mkdir(dirname(this.path), { recursive: true, mode: 0o700 });
|
|
71
102
|
const temporary = `${this.path}.${randomUUID()}.tmp`;
|
|
72
|
-
|
|
73
|
-
await
|
|
103
|
+
// 独占创建成功才取得所有权;open 失败不能删除可能属于其他写入者的文件。
|
|
104
|
+
const handle = await open(temporary, "wx", 0o600);
|
|
105
|
+
try {
|
|
106
|
+
try {
|
|
107
|
+
await handle.writeFile(text);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
await handle
|
|
111
|
+
.close()
|
|
112
|
+
.catch((closeError) => this.cleanupError("webui.store.close_failed", closeError));
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
// 关闭失败同样是保存失败,不能尝试替换或提交内存。
|
|
116
|
+
await handle.close();
|
|
117
|
+
await replace(temporary, this.path);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
await unlink(temporary).catch((cleanupError) => this.cleanupError("webui.store.temporary_cleanup_failed", cleanupError));
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
// rename 成功就是提交点;此后没有可能将成功翻转成失败的日志或清理动作。
|
|
74
124
|
this.records = next;
|
|
75
125
|
return result;
|
|
76
126
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mininglamp-oss/octo-runtime-extra-webui",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Local Runtime workbench with canonical SSE presentation",
|
|
@@ -19,6 +19,6 @@
|
|
|
19
19
|
"porting-map.md"
|
|
20
20
|
],
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@mininglamp-oss/octo-runtime-protocol": "1.1.
|
|
22
|
+
"@mininglamp-oss/octo-runtime-protocol": "1.1.1"
|
|
23
23
|
}
|
|
24
24
|
}
|
|
@@ -6,8 +6,15 @@ export interface ExtraDescriptor {
|
|
|
6
6
|
enabled?: boolean;
|
|
7
7
|
config: JsonObject;
|
|
8
8
|
}
|
|
9
|
-
|
|
9
|
+
/** CLI 配置可由 manifest 提供模块;原生代码声明 ExtraDescriptor 仍显式指定模块。 */
|
|
10
|
+
export interface StoredExtra {
|
|
11
|
+
module?: string;
|
|
12
|
+
enabled?: boolean;
|
|
13
|
+
config: JsonObject;
|
|
14
|
+
}
|
|
10
15
|
export declare function assertStoredExtra(value: unknown): asserts value is StoredExtra;
|
|
11
16
|
export declare function extraRegistration(descriptor: ExtraDescriptor): ExtraRegistration;
|
|
12
17
|
/** Called only by a host that explicitly selects this directory as its source. */
|
|
13
18
|
export declare function readExtraDirectory(directory: string): Promise<ExtraRegistration[]>;
|
|
19
|
+
/** 宿主显式选取的配置文件;保持凭据权限校验,不解析或导入模块。 */
|
|
20
|
+
export declare function readStoredExtra(directory: string, name: string): Promise<StoredExtra>;
|
|
@@ -8,7 +8,7 @@ export function assertStoredExtra(value) {
|
|
|
8
8
|
const item = value;
|
|
9
9
|
if (Object.keys(item).some((key) => !["module", "enabled", "config"].includes(key)))
|
|
10
10
|
throw new Error("EXTRA_CONFIG_FIELD_INVALID");
|
|
11
|
-
if (typeof item.module !== "string" || !validModule(item.module))
|
|
11
|
+
if (item.module !== undefined && (typeof item.module !== "string" || !validModule(item.module)))
|
|
12
12
|
throw new Error("EXTRA_MODULE_INVALID");
|
|
13
13
|
if (item.enabled !== undefined && typeof item.enabled !== "boolean")
|
|
14
14
|
throw new Error("EXTRA_ENABLED_INVALID");
|
|
@@ -35,12 +35,15 @@ export function extraRegistration(descriptor) {
|
|
|
35
35
|
assertExtraName(descriptor.name);
|
|
36
36
|
const { name, ...stored } = descriptor;
|
|
37
37
|
assertStoredExtra(stored);
|
|
38
|
+
const module = stored.module;
|
|
39
|
+
if (!module)
|
|
40
|
+
throw new Error("EXTRA_MODULE_REQUIRED");
|
|
38
41
|
return {
|
|
39
42
|
name,
|
|
40
|
-
module
|
|
43
|
+
module,
|
|
41
44
|
config: stored.config,
|
|
42
45
|
enabled: stored.enabled,
|
|
43
|
-
load: () => import(isAbsolute(
|
|
46
|
+
load: () => import(isAbsolute(module) ? pathToFileURL(module).href : module)
|
|
44
47
|
};
|
|
45
48
|
}
|
|
46
49
|
/** Called only by a host that explicitly selects this directory as its source. */
|
|
@@ -56,15 +59,14 @@ export async function readExtraDirectory(directory) {
|
|
|
56
59
|
continue;
|
|
57
60
|
assertExtraName(entry.name);
|
|
58
61
|
try {
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
registrations.push(extraRegistration({ name: entry.name, ...stored }));
|
|
62
|
+
const stored = await readStoredExtra(directory, entry.name);
|
|
63
|
+
registrations.push(stored.module
|
|
64
|
+
? extraRegistration({ ...stored, name: entry.name, module: stored.module })
|
|
65
|
+
: {
|
|
66
|
+
...stored,
|
|
67
|
+
name: entry.name,
|
|
68
|
+
load: () => Promise.reject(new Error("EXTRA_MODULE_REQUIRED"))
|
|
69
|
+
});
|
|
68
70
|
}
|
|
69
71
|
catch (error) {
|
|
70
72
|
const code = error instanceof Error && /^EXTRA_[A-Z_]+$/.test(error.message)
|
|
@@ -79,6 +81,19 @@ export async function readExtraDirectory(directory) {
|
|
|
79
81
|
}
|
|
80
82
|
return registrations;
|
|
81
83
|
}
|
|
84
|
+
/** 宿主显式选取的配置文件;保持凭据权限校验,不解析或导入模块。 */
|
|
85
|
+
export async function readStoredExtra(directory, name) {
|
|
86
|
+
assertExtraName(name);
|
|
87
|
+
const file = join(directory, name, "config.json");
|
|
88
|
+
const info = await lstat(file);
|
|
89
|
+
if (!info.isFile() || (process.platform !== "win32" && info.mode & 0o077))
|
|
90
|
+
throw new Error("EXTRA_CONFIG_PERMISSIONS_INVALID");
|
|
91
|
+
if (info.size > 1024 * 1024)
|
|
92
|
+
throw new Error("EXTRA_CONFIG_TOO_LARGE");
|
|
93
|
+
const stored = JSON.parse(await readFile(file, "utf8"));
|
|
94
|
+
assertStoredExtra(stored);
|
|
95
|
+
return stored;
|
|
96
|
+
}
|
|
82
97
|
function validModule(module) {
|
|
83
98
|
if (isAbsolute(module))
|
|
84
99
|
return true;
|