@actiondock/core 2.5.1 → 2.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/dist/app/app.d.ts +14 -1
- package/dist/app/app.js +88 -222
- package/dist/app/static-index.d.ts +39 -0
- package/dist/app/static-index.js +164 -0
- package/dist/app/types.d.ts +5 -0
- package/dist/catalog/action-index.js +6 -4
- package/dist/catalog/package-catalog.js +2 -1
- package/dist/doctor/checks/index.d.ts +5 -0
- package/dist/doctor/checks/index.js +5 -0
- package/dist/doctor/checks/project-locate.d.ts +6 -0
- package/dist/doctor/checks/project-locate.js +36 -0
- package/dist/doctor/checks/project.d.ts +6 -0
- package/dist/doctor/checks/project.js +383 -0
- package/dist/doctor/checks/registry.d.ts +13 -0
- package/dist/doctor/checks/registry.js +207 -0
- package/dist/doctor/checks/runtime.d.ts +13 -0
- package/dist/doctor/checks/runtime.js +90 -0
- package/dist/doctor/checks/storage.d.ts +5 -0
- package/dist/doctor/checks/storage.js +34 -0
- package/dist/doctor/context.d.ts +35 -0
- package/dist/doctor/context.js +11 -0
- package/dist/doctor/doctor.d.ts +5 -0
- package/dist/doctor/doctor.js +33 -602
- package/dist/errors.d.ts +2 -0
- package/dist/errors.js +2 -0
- package/dist/execution/service.js +14 -9
- package/dist/host/host.d.ts +33 -0
- package/dist/host/host.js +222 -362
- package/dist/host/routing.d.ts +92 -0
- package/dist/host/routing.js +238 -0
- package/dist/host/types.d.ts +8 -0
- package/dist/ipc/host.js +38 -0
- package/dist/platform/types.d.ts +7 -0
- package/dist/process/control-arbiter.d.ts +107 -0
- package/dist/process/control-arbiter.js +251 -0
- package/dist/process/diagnostics-sink.d.ts +26 -0
- package/dist/process/diagnostics-sink.js +35 -0
- package/dist/process/index.d.ts +7 -0
- package/dist/process/index.js +7 -0
- package/dist/process/input-dispatcher.d.ts +88 -0
- package/dist/process/input-dispatcher.js +240 -0
- package/dist/process/managed-record.d.ts +78 -0
- package/dist/process/managed-record.js +1 -0
- package/dist/process/process-manager.d.ts +15 -56
- package/dist/process/process-manager.js +119 -707
- package/dist/process/reservation-table.d.ts +39 -0
- package/dist/process/reservation-table.js +80 -0
- package/dist/process/run-executor.d.ts +16 -0
- package/dist/process/run-executor.js +155 -0
- package/dist/process/terminal-output-cache.d.ts +66 -0
- package/dist/process/terminal-output-cache.js +132 -0
- package/dist/profile/client-actions.d.ts +56 -0
- package/dist/profile/client-actions.js +215 -0
- package/dist/profile/client-config.d.ts +20 -0
- package/dist/profile/client-config.js +51 -0
- package/dist/profile/client-health.d.ts +14 -0
- package/dist/profile/client-health.js +68 -0
- package/dist/profile/client-playbooks.d.ts +19 -0
- package/dist/profile/client-playbooks.js +36 -0
- package/dist/profile/client-query.d.ts +30 -0
- package/dist/profile/client-query.js +70 -0
- package/dist/profile/client-runs.d.ts +42 -0
- package/dist/profile/client-runs.js +58 -0
- package/dist/profile/client-state.d.ts +56 -0
- package/dist/profile/client-state.js +83 -0
- package/dist/profile/client-transport.d.ts +61 -0
- package/dist/profile/client-transport.js +155 -0
- package/dist/profile/client.d.ts +23 -142
- package/dist/profile/client.js +28 -588
- package/dist/profile/manager.js +1 -1
- package/dist/project/closure.d.ts +4 -4
- package/dist/project/closure.js +11 -5
- package/dist/project/init.js +2 -2
- package/dist/project/loader.d.ts +0 -25
- package/dist/project/loader.js +1 -154
- package/dist/project/transactions.d.ts +12 -14
- package/dist/project/transactions.js +60 -836
- package/dist/registry/registry.js +6 -4
- package/dist/registry/resolve.js +1 -1
- package/dist/runtime/action-registry.d.ts +45 -0
- package/dist/runtime/action-registry.js +108 -0
- package/dist/runtime/clock.d.ts +5 -17
- package/dist/runtime/clock.js +5 -15
- package/dist/runtime/context.d.ts +3 -0
- package/dist/runtime/context.js +17 -56
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/index.js +3 -0
- package/dist/runtime/package-runner-factory.d.ts +82 -0
- package/dist/runtime/package-runner-factory.js +186 -0
- package/dist/runtime/run-persistence.d.ts +85 -0
- package/dist/runtime/run-persistence.js +111 -0
- package/dist/runtime/runner.d.ts +23 -33
- package/dist/runtime/runner.js +92 -286
- package/dist/runtime/standalone.d.ts +11 -1
- package/dist/runtime/standalone.js +137 -25
- package/dist/server/dispatcher.d.ts +8 -3
- package/dist/server/dispatcher.js +16 -40
- package/dist/server/routes/runs.js +6 -4
- package/dist/server/security.d.ts +1 -7
- package/dist/server/security.js +2 -13
- package/dist/storage/clock.d.ts +25 -0
- package/dist/storage/clock.js +23 -0
- package/dist/storage/data-dir-lock.d.ts +3 -36
- package/dist/storage/data-dir-lock.js +48 -881
- package/dist/storage/index.d.ts +8 -1
- package/dist/storage/index.js +8 -2
- package/dist/storage/lazy.js +1 -0
- package/dist/storage/lock-core.d.ts +162 -0
- package/dist/storage/lock-core.js +885 -0
- package/dist/storage/sqlite.d.ts +19 -6
- package/dist/storage/sqlite.js +81 -89
- package/dist/storage/types.d.ts +27 -5
- package/dist/storage/types.js +21 -0
- package/dist/storage/utils.d.ts +22 -0
- package/dist/storage/utils.js +34 -0
- package/dist/target/local.d.ts +1 -0
- package/dist/target/local.js +23 -17
- package/dist/target/remote-errors.d.ts +21 -0
- package/dist/target/remote-errors.js +51 -0
- package/dist/target/remote-polling.d.ts +35 -0
- package/dist/target/remote-polling.js +104 -0
- package/dist/target/remote.d.ts +4 -0
- package/dist/target/remote.js +108 -301
- package/dist/target/sse-parser.d.ts +35 -0
- package/dist/target/sse-parser.js +163 -0
- package/dist/target/target.js +2 -0
- package/dist/target/types.d.ts +6 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/net.d.ts +10 -0
- package/dist/utils/net.js +16 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
+
import { decodeStateKey } from "@actiondock/sdk";
|
|
2
3
|
import { filterWithFallbackInfo } from "../filter/index.js";
|
|
3
4
|
import { createActionDockTarget } from "../target/target.js";
|
|
4
5
|
import { STANDALONE_ASYNC_UNSUPPORTED } from "../errors.js";
|
|
@@ -119,7 +120,7 @@ export class StandaloneDispatcher {
|
|
|
119
120
|
const subArgs = filteredArgs.slice(1);
|
|
120
121
|
// 2. 独立入口拒绝异步启动语义
|
|
121
122
|
if (args.includes("--async")) {
|
|
122
|
-
const isJson = args.includes("--json")
|
|
123
|
+
const isJson = args.includes("--json");
|
|
123
124
|
if (isJson) {
|
|
124
125
|
this.writeOut(JSON.stringify({
|
|
125
126
|
ok: false,
|
|
@@ -193,8 +194,7 @@ export class StandaloneDispatcher {
|
|
|
193
194
|
}
|
|
194
195
|
}
|
|
195
196
|
async handleList(target, subArgs) {
|
|
196
|
-
const isJson = subArgs.includes("--json")
|
|
197
|
-
const useEnvelope = subArgs.includes("--envelope");
|
|
197
|
+
const isJson = subArgs.includes("--json");
|
|
198
198
|
const noFallback = subArgs.includes("--no-fallback");
|
|
199
199
|
let intent;
|
|
200
200
|
const positionalPatterns = [];
|
|
@@ -222,10 +222,7 @@ export class StandaloneDispatcher {
|
|
|
222
222
|
}));
|
|
223
223
|
const filterRes = filterWithFallbackInfo(list, effectiveIntent, [(a) => a.id, (a) => a.description], !noFallback);
|
|
224
224
|
if (isJson) {
|
|
225
|
-
|
|
226
|
-
? { ok: true, data: filterRes.items }
|
|
227
|
-
: filterRes.items;
|
|
228
|
-
this.writeOut(JSON.stringify(payload, null, 2));
|
|
225
|
+
this.writeOut(JSON.stringify(filterRes.items, null, 2));
|
|
229
226
|
}
|
|
230
227
|
else {
|
|
231
228
|
let text = `Actions in ${this.options.packageId} (v${this.options.version}):\n\n`;
|
|
@@ -238,8 +235,7 @@ export class StandaloneDispatcher {
|
|
|
238
235
|
}
|
|
239
236
|
async handleDescribe(target, subArgs) {
|
|
240
237
|
const id = subArgs.find((a) => !a.startsWith("-"));
|
|
241
|
-
const isJson = subArgs.includes("--json")
|
|
242
|
-
const useEnvelope = subArgs.includes("--envelope");
|
|
238
|
+
const isJson = subArgs.includes("--json");
|
|
243
239
|
if (!id) {
|
|
244
240
|
this.writeErr("Error: Action ID is required for describe");
|
|
245
241
|
return ExitCode.INVALID_ARGUMENT;
|
|
@@ -260,8 +256,7 @@ export class StandaloneDispatcher {
|
|
|
260
256
|
outputSchema: action.outputSchema,
|
|
261
257
|
};
|
|
262
258
|
if (isJson) {
|
|
263
|
-
|
|
264
|
-
this.writeOut(JSON.stringify(payload, null, 2));
|
|
259
|
+
this.writeOut(JSON.stringify(detail, null, 2));
|
|
265
260
|
}
|
|
266
261
|
else {
|
|
267
262
|
let text = `Action: ${action.id}\n`;
|
|
@@ -285,9 +280,13 @@ export class StandaloneDispatcher {
|
|
|
285
280
|
}
|
|
286
281
|
let input = {};
|
|
287
282
|
let timeoutMs;
|
|
283
|
+
let isJson = false;
|
|
288
284
|
for (let i = 0; i < subArgs.length; i++) {
|
|
289
285
|
const arg = subArgs[i];
|
|
290
|
-
if (arg === "--
|
|
286
|
+
if (arg === "--json") {
|
|
287
|
+
isJson = true;
|
|
288
|
+
}
|
|
289
|
+
else if (arg === "--timeout" && i + 1 < subArgs.length) {
|
|
291
290
|
timeoutMs = parseDuration(subArgs[++i]);
|
|
292
291
|
}
|
|
293
292
|
else if (arg.startsWith("--timeout=")) {
|
|
@@ -325,7 +324,89 @@ export class StandaloneDispatcher {
|
|
|
325
324
|
signal,
|
|
326
325
|
timeoutMs,
|
|
327
326
|
});
|
|
328
|
-
|
|
327
|
+
if (isJson) {
|
|
328
|
+
this.writeOut(JSON.stringify(result, null, 2));
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
if (result.ok) {
|
|
332
|
+
const data = result.data;
|
|
333
|
+
let rawText;
|
|
334
|
+
let metaInfo;
|
|
335
|
+
if (typeof data === "string") {
|
|
336
|
+
rawText = data;
|
|
337
|
+
}
|
|
338
|
+
else if (data !== null && typeof data === "object") {
|
|
339
|
+
if ("content" in data && data.content !== undefined) {
|
|
340
|
+
rawText =
|
|
341
|
+
typeof data.content === "object" && data.content !== null
|
|
342
|
+
? JSON.stringify(data.content, null, 2)
|
|
343
|
+
: String(data.content);
|
|
344
|
+
const { content, ...rest } = data;
|
|
345
|
+
if (Object.keys(rest).length > 0) {
|
|
346
|
+
metaInfo = rest;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
else if ("text" in data && typeof data.text === "string") {
|
|
350
|
+
rawText = data.text;
|
|
351
|
+
const { text, ...rest } = data;
|
|
352
|
+
if (Object.keys(rest).length > 0) {
|
|
353
|
+
metaInfo = rest;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
else if ("message" in data && typeof data.message === "string") {
|
|
357
|
+
rawText = data.message;
|
|
358
|
+
const { message, ...rest } = data;
|
|
359
|
+
if (Object.keys(rest).length > 0) {
|
|
360
|
+
metaInfo = rest;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
rawText = JSON.stringify(data, null, 2);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
else if (data !== undefined) {
|
|
368
|
+
rawText = String(data);
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
rawText = "";
|
|
372
|
+
}
|
|
373
|
+
if (metaInfo) {
|
|
374
|
+
const parts = [];
|
|
375
|
+
const title = metaInfo.path ? String(metaInfo.path) : id;
|
|
376
|
+
parts.push(title);
|
|
377
|
+
if (metaInfo.startLine !== undefined && metaInfo.endLine !== undefined) {
|
|
378
|
+
parts.push(`lines ${metaInfo.startLine}-${metaInfo.endLine}`);
|
|
379
|
+
}
|
|
380
|
+
else if (metaInfo.line !== undefined) {
|
|
381
|
+
parts.push(`line ${metaInfo.line}`);
|
|
382
|
+
}
|
|
383
|
+
if (metaInfo.hasMore !== undefined) {
|
|
384
|
+
parts.push(`hasMore: ${metaInfo.hasMore}`);
|
|
385
|
+
}
|
|
386
|
+
if (metaInfo.truncated) {
|
|
387
|
+
parts.push("truncated: true");
|
|
388
|
+
}
|
|
389
|
+
const handled = new Set(["path", "startLine", "endLine", "line", "hasMore", "truncated"]);
|
|
390
|
+
for (const [k, v] of Object.entries(metaInfo)) {
|
|
391
|
+
if (!handled.has(k) && v !== undefined) {
|
|
392
|
+
parts.push(`${k}: ${typeof v === "object" ? JSON.stringify(v) : v}`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (parts.length > 0) {
|
|
396
|
+
this.writeErr(`[${parts.join(" | ")}]`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
this.writeOut(rawText);
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
this.writeErr(`Error [${result.error.code}]: ${result.error.message}`);
|
|
403
|
+
if (result.error.details) {
|
|
404
|
+
this.writeErr(typeof result.error.details === "string"
|
|
405
|
+
? result.error.details
|
|
406
|
+
: JSON.stringify(result.error.details, null, 2));
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
329
410
|
return result.ok ? ExitCode.SUCCESS : ExitCode.FAILURE;
|
|
330
411
|
}
|
|
331
412
|
async handleConfig(target, subArgs) {
|
|
@@ -352,9 +433,15 @@ export class StandaloneDispatcher {
|
|
|
352
433
|
this.writeOut(item.configured ? JSON.stringify(item.value) : "undefined");
|
|
353
434
|
return ExitCode.SUCCESS;
|
|
354
435
|
}
|
|
355
|
-
catch {
|
|
356
|
-
|
|
357
|
-
|
|
436
|
+
catch (err) {
|
|
437
|
+
// 目标层能力缺失(如远端未开启管理接口)视为键未配置,输出 undefined
|
|
438
|
+
if (this.isTargetCapabilityError(err)) {
|
|
439
|
+
this.writeOut("undefined");
|
|
440
|
+
return ExitCode.SUCCESS;
|
|
441
|
+
}
|
|
442
|
+
// 内部异常严禁吞没:透传 stderr 并以 FAILURE 退出码暴露
|
|
443
|
+
this.writeErr(`Error reading config '${key}': ${err?.message || String(err)}`);
|
|
444
|
+
return ExitCode.FAILURE;
|
|
358
445
|
}
|
|
359
446
|
}
|
|
360
447
|
if (sub === "set") {
|
|
@@ -425,10 +512,10 @@ export class StandaloneDispatcher {
|
|
|
425
512
|
}
|
|
426
513
|
let ns = namespace;
|
|
427
514
|
let actualKey = key;
|
|
428
|
-
if (ns === undefined
|
|
429
|
-
const
|
|
430
|
-
ns =
|
|
431
|
-
actualKey = key
|
|
515
|
+
if (ns === undefined) {
|
|
516
|
+
const decoded = this.decodeStateKeyArg(key);
|
|
517
|
+
ns = decoded.namespace || undefined;
|
|
518
|
+
actualKey = decoded.key;
|
|
432
519
|
}
|
|
433
520
|
const val = await target.getState(this.options.packageId, "", actualKey, { namespace: ns });
|
|
434
521
|
if (isJson) {
|
|
@@ -448,10 +535,10 @@ export class StandaloneDispatcher {
|
|
|
448
535
|
}
|
|
449
536
|
let ns = namespace || "";
|
|
450
537
|
let actualKey = key;
|
|
451
|
-
if (namespace === undefined
|
|
452
|
-
const
|
|
453
|
-
ns =
|
|
454
|
-
actualKey = key
|
|
538
|
+
if (namespace === undefined) {
|
|
539
|
+
const decoded = this.decodeStateKeyArg(key);
|
|
540
|
+
ns = decoded.namespace;
|
|
541
|
+
actualKey = decoded.key;
|
|
455
542
|
}
|
|
456
543
|
let parsed = rawVal;
|
|
457
544
|
try {
|
|
@@ -501,6 +588,30 @@ export class StandaloneDispatcher {
|
|
|
501
588
|
this.writeErr(`Unknown state subcommand: '${sub}'`);
|
|
502
589
|
return ExitCode.INVALID_ARGUMENT;
|
|
503
590
|
}
|
|
591
|
+
/**
|
|
592
|
+
* 判定配置读取异常是否为目标层能力缺失(键未配置语义)而非内部故障。
|
|
593
|
+
*/
|
|
594
|
+
isTargetCapabilityError(err) {
|
|
595
|
+
const code = String(err?.code || "");
|
|
596
|
+
const msg = String(err?.message || "");
|
|
597
|
+
return (code === "TARGET_CAPABILITY_UNAVAILABLE" ||
|
|
598
|
+
code === "CAPABILITY_UNAVAILABLE" ||
|
|
599
|
+
msg.includes("TARGET_CAPABILITY_UNAVAILABLE") ||
|
|
600
|
+
msg.includes("Management APIs are not enabled"));
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* 解析携带命名空间前缀的状态键参数:委托 sdk 单一事实源 decodeStateKey。
|
|
604
|
+
*
|
|
605
|
+
* 解析失败(如存在多个未转义冒号的歧义键)时按纯键处理,交由存储层兜底。
|
|
606
|
+
*/
|
|
607
|
+
decodeStateKeyArg(key) {
|
|
608
|
+
try {
|
|
609
|
+
return decodeStateKey(key);
|
|
610
|
+
}
|
|
611
|
+
catch {
|
|
612
|
+
return { namespace: "", key };
|
|
613
|
+
}
|
|
614
|
+
}
|
|
504
615
|
printHelp() {
|
|
505
616
|
this.writeOut(`${this.options.packageId} (v${this.options.version})`);
|
|
506
617
|
if (this.options.description)
|
|
@@ -508,7 +619,8 @@ export class StandaloneDispatcher {
|
|
|
508
619
|
this.writeOut("Usage:");
|
|
509
620
|
this.writeOut(" <cmd> list [--json] List available actions");
|
|
510
621
|
this.writeOut(" <cmd> describe <id> [--json] Show action details and schemas");
|
|
511
|
-
this.writeOut(" <cmd> run <id> [--input '<json>'] Execute action
|
|
622
|
+
this.writeOut(" <cmd> run <id> [--input '<json>'] Execute action (raw text output by default)");
|
|
623
|
+
this.writeOut(" <cmd> run <id> [--json] Output standard execution result in JSON format");
|
|
512
624
|
this.writeOut(" <cmd> config list/get/set/delete Manage package configuration");
|
|
513
625
|
this.writeOut(" <cmd> state list/get/set/delete Manage shared state store");
|
|
514
626
|
this.writeOut("\nGlobal options:");
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
export type FetchDispatcherProvider = () => unknown;
|
|
5
5
|
/**
|
|
6
6
|
* 注册全局用于忽略服务端证书校验的调度器提供者。
|
|
7
|
+
* 唯一注册通道:平台适配层(如 @actiondock/runtime-node 的
|
|
8
|
+
* installInsecureDispatcher)显式调用完成注册,不再读取全局符号注入。
|
|
7
9
|
*/
|
|
8
10
|
export declare function setInsecureDispatcherProvider(provider?: FetchDispatcherProvider): void;
|
|
9
11
|
/**
|
|
@@ -13,12 +15,15 @@ export declare function getInsecureDispatcherProvider(): FetchDispatcherProvider
|
|
|
13
15
|
/**
|
|
14
16
|
* 获取用于忽略服务端证书校验的调度器实例。
|
|
15
17
|
* 纯粹解耦设计:
|
|
16
|
-
* 1. 优先调用显式注册的提供者(如由 @actiondock/runtime-node
|
|
17
|
-
* 2.
|
|
18
|
-
*
|
|
18
|
+
* 1. 优先调用显式注册的提供者(如由 @actiondock/runtime-node 显式安装);
|
|
19
|
+
* 2. 若在 Node 环境下且无注册提供者,动态加载 @actiondock/runtime-node
|
|
20
|
+
* (其入口聚合点会显式完成安装),绝不让 core 包产生静态硬编译依赖;
|
|
21
|
+
* 3. 仍不可得时返回 undefined,由调用方回退默认 fetch,绝不在 core 内
|
|
22
|
+
* 直接探测加载 undici,避免与平台适配层形成层次倒置与隐藏依赖环。
|
|
19
23
|
*/
|
|
20
24
|
export declare function getInsecureDispatcher(): unknown;
|
|
21
25
|
/**
|
|
22
26
|
* 显式强制重置并销毁调度器连接池(仅用于单元测试重置或进程退出)。
|
|
27
|
+
* core 自身不再持有任何兜底连接池,关闭语义完全委托已注册的平台提供者宿主。
|
|
23
28
|
*/
|
|
24
29
|
export declare function closeInsecureDispatcher(): Promise<void>;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
let insecureDispatcherProvider;
|
|
3
|
-
let fallbackInsecureAgent;
|
|
4
3
|
/**
|
|
5
4
|
* 注册全局用于忽略服务端证书校验的调度器提供者。
|
|
5
|
+
* 唯一注册通道:平台适配层(如 @actiondock/runtime-node 的
|
|
6
|
+
* installInsecureDispatcher)显式调用完成注册,不再读取全局符号注入。
|
|
6
7
|
*/
|
|
7
8
|
export function setInsecureDispatcherProvider(provider) {
|
|
8
9
|
insecureDispatcherProvider = provider;
|
|
@@ -16,18 +17,16 @@ export function getInsecureDispatcherProvider() {
|
|
|
16
17
|
/**
|
|
17
18
|
* 获取用于忽略服务端证书校验的调度器实例。
|
|
18
19
|
* 纯粹解耦设计:
|
|
19
|
-
* 1. 优先调用显式注册的提供者(如由 @actiondock/runtime-node
|
|
20
|
-
* 2.
|
|
21
|
-
*
|
|
20
|
+
* 1. 优先调用显式注册的提供者(如由 @actiondock/runtime-node 显式安装);
|
|
21
|
+
* 2. 若在 Node 环境下且无注册提供者,动态加载 @actiondock/runtime-node
|
|
22
|
+
* (其入口聚合点会显式完成安装),绝不让 core 包产生静态硬编译依赖;
|
|
23
|
+
* 3. 仍不可得时返回 undefined,由调用方回退默认 fetch,绝不在 core 内
|
|
24
|
+
* 直接探测加载 undici,避免与平台适配层形成层次倒置与隐藏依赖环。
|
|
22
25
|
*/
|
|
23
26
|
export function getInsecureDispatcher() {
|
|
24
27
|
if (insecureDispatcherProvider) {
|
|
25
28
|
return insecureDispatcherProvider();
|
|
26
29
|
}
|
|
27
|
-
const globalProvider = globalThis[Symbol.for("actiondock.insecureDispatcherProvider")];
|
|
28
|
-
if (typeof globalProvider === "function") {
|
|
29
|
-
return globalProvider();
|
|
30
|
-
}
|
|
31
30
|
if (typeof process !== "undefined" && process.versions?.node) {
|
|
32
31
|
try {
|
|
33
32
|
const req = createRequire(import.meta.url);
|
|
@@ -39,48 +38,25 @@ export function getInsecureDispatcher() {
|
|
|
39
38
|
catch {
|
|
40
39
|
// 忽略未安装 @actiondock/runtime-node 的情况
|
|
41
40
|
}
|
|
42
|
-
if (!fallbackInsecureAgent || fallbackInsecureAgent.closed || fallbackInsecureAgent.destroyed) {
|
|
43
|
-
try {
|
|
44
|
-
const req = createRequire(import.meta.url);
|
|
45
|
-
const undici = req("undici");
|
|
46
|
-
if (undici?.Agent) {
|
|
47
|
-
fallbackInsecureAgent = new undici.Agent({
|
|
48
|
-
connect: {
|
|
49
|
-
rejectUnauthorized: false,
|
|
50
|
-
},
|
|
51
|
-
bodyTimeout: 0,
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
// 忽略非 Node 环境或未安装 undici 的异常
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return fallbackInsecureAgent;
|
|
60
41
|
}
|
|
61
42
|
return undefined;
|
|
62
43
|
}
|
|
63
44
|
/**
|
|
64
45
|
* 显式强制重置并销毁调度器连接池(仅用于单元测试重置或进程退出)。
|
|
46
|
+
* core 自身不再持有任何兜底连接池,关闭语义完全委托已注册的平台提供者宿主。
|
|
65
47
|
*/
|
|
66
48
|
export async function closeInsecureDispatcher() {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
await globalClose();
|
|
71
|
-
}
|
|
72
|
-
catch {
|
|
73
|
-
// 忽略关闭异常
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
if (fallbackInsecureAgent) {
|
|
77
|
-
const agent = fallbackInsecureAgent;
|
|
78
|
-
fallbackInsecureAgent = undefined;
|
|
49
|
+
if (insecureDispatcherProvider) {
|
|
50
|
+
// 提供者由平台层注册:优先委托提供者宿主的关闭语义(如可用)
|
|
79
51
|
try {
|
|
80
|
-
|
|
52
|
+
const req = createRequire(import.meta.url);
|
|
53
|
+
const runtimeNode = req("@actiondock/runtime-node");
|
|
54
|
+
if (typeof runtimeNode?.closeInsecureDispatcher === "function") {
|
|
55
|
+
await runtimeNode.closeInsecureDispatcher();
|
|
56
|
+
}
|
|
81
57
|
}
|
|
82
58
|
catch {
|
|
83
|
-
//
|
|
59
|
+
// 忽略未安装 @actiondock/runtime-node 的情况
|
|
84
60
|
}
|
|
85
61
|
}
|
|
86
62
|
}
|
|
@@ -49,12 +49,11 @@ export async function handleRunsRoutes(ctx) {
|
|
|
49
49
|
}
|
|
50
50
|
if (app.storage && typeof app.storage.listRuns === "function") {
|
|
51
51
|
try {
|
|
52
|
-
|
|
52
|
+
// 状态过滤下推到存储层 SQL,避免先分页后过滤导致某包前 N 条非目标状态时贡献 0 条
|
|
53
|
+
const records = app.storage.listRuns({ actionId, status, limit });
|
|
53
54
|
for (const r of records) {
|
|
54
55
|
if (!seenRunIds.has(r.id)) {
|
|
55
56
|
seenRunIds.add(r.id);
|
|
56
|
-
if (status && r.status !== status)
|
|
57
|
-
continue;
|
|
58
57
|
if (options.packageAllowlist &&
|
|
59
58
|
options.packageAllowlist.length > 0 &&
|
|
60
59
|
r.packageId &&
|
|
@@ -65,7 +64,10 @@ export async function handleRunsRoutes(ctx) {
|
|
|
65
64
|
}
|
|
66
65
|
}
|
|
67
66
|
}
|
|
68
|
-
catch {
|
|
67
|
+
catch (err) {
|
|
68
|
+
// 单包查询失败不阻断整体列表响应,但必须可观测
|
|
69
|
+
console.warn(`[actiondock] listRuns failed for package '${app.packageId}': ${err instanceof Error ? err.message : String(err)}`);
|
|
70
|
+
}
|
|
69
71
|
}
|
|
70
72
|
}
|
|
71
73
|
allRuns.sort((a, b) => (b.startedAt || "").localeCompare(a.startedAt || ""));
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { isLoopbackHost } from "../utils/net.js";
|
|
1
2
|
/**
|
|
2
3
|
* 恒定时间字符串比较(Constant-time comparison)。
|
|
3
4
|
* 使用底层的 `crypto.timingSafeEqual` 防范时序攻击(Timing Attack)。
|
|
@@ -7,13 +8,6 @@
|
|
|
7
8
|
* @returns 两个字符串内容是否完全一致
|
|
8
9
|
*/
|
|
9
10
|
export declare function safeEqual(a: string, b: string): boolean;
|
|
10
|
-
/**
|
|
11
|
-
* 检查指定的主机地址是否为本地回环接口(Loopback Host)。
|
|
12
|
-
* 支持 127.0.0.1, localhost, ::1 等形式。
|
|
13
|
-
*
|
|
14
|
-
* @param host 主机名或 IP 字符串
|
|
15
|
-
*/
|
|
16
|
-
export declare function isLoopbackHost(host: string): boolean;
|
|
17
11
|
/**
|
|
18
12
|
* 校验 HTTP 请求中的鉴权令牌选项。
|
|
19
13
|
*/
|
package/dist/server/security.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { timingSafeEqual } from "node:crypto";
|
|
2
|
+
// isLoopbackHost 已上移至 utils/net.ts(通用谓词单一事实源),此处仅保留 re-export 兼容旧引用路径
|
|
3
|
+
export { isLoopbackHost } from "../utils/net.js";
|
|
2
4
|
/**
|
|
3
5
|
* 恒定时间字符串比较(Constant-time comparison)。
|
|
4
6
|
* 使用底层的 `crypto.timingSafeEqual` 防范时序攻击(Timing Attack)。
|
|
@@ -15,19 +17,6 @@ export function safeEqual(a, b) {
|
|
|
15
17
|
}
|
|
16
18
|
return timingSafeEqual(aa, bb);
|
|
17
19
|
}
|
|
18
|
-
/**
|
|
19
|
-
* 检查指定的主机地址是否为本地回环接口(Loopback Host)。
|
|
20
|
-
* 支持 127.0.0.1, localhost, ::1 等形式。
|
|
21
|
-
*
|
|
22
|
-
* @param host 主机名或 IP 字符串
|
|
23
|
-
*/
|
|
24
|
-
export function isLoopbackHost(host) {
|
|
25
|
-
const trimmed = host.trim().toLowerCase().replace(/^\[|\]$/g, "");
|
|
26
|
-
return (trimmed === "127.0.0.1" ||
|
|
27
|
-
trimmed === "::1" ||
|
|
28
|
-
trimmed === "localhost" ||
|
|
29
|
-
trimmed === "0:0:0:0:0:0:0:1");
|
|
30
|
-
}
|
|
31
20
|
/**
|
|
32
21
|
* 校验 HTTP 请求中的鉴权令牌是否有效。
|
|
33
22
|
*
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 统一时间与时钟基础契约。
|
|
3
|
+
*
|
|
4
|
+
* 单一事实源:Clock 接口与 SystemClock 实现定义于此,runtime/clock.ts 与
|
|
5
|
+
* storage/types.ts 一律从本文件引用或转发,禁止重复实现。
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* 统一时间与时钟接口。
|
|
9
|
+
*/
|
|
10
|
+
export interface Clock {
|
|
11
|
+
/** 获取当前系统墙上时间 */
|
|
12
|
+
now(): Date;
|
|
13
|
+
/** 获取单调递增时间戳(单位:毫秒) */
|
|
14
|
+
monotonic(): number;
|
|
15
|
+
/** 异步休眠指定毫秒 */
|
|
16
|
+
sleep(ms: number): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 生产环境系统时钟实现。
|
|
20
|
+
*/
|
|
21
|
+
export declare class SystemClock implements Clock {
|
|
22
|
+
now(): Date;
|
|
23
|
+
monotonic(): number;
|
|
24
|
+
sleep(ms: number): Promise<void>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 统一时间与时钟基础契约。
|
|
3
|
+
*
|
|
4
|
+
* 单一事实源:Clock 接口与 SystemClock 实现定义于此,runtime/clock.ts 与
|
|
5
|
+
* storage/types.ts 一律从本文件引用或转发,禁止重复实现。
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* 生产环境系统时钟实现。
|
|
9
|
+
*/
|
|
10
|
+
export class SystemClock {
|
|
11
|
+
now() {
|
|
12
|
+
return new Date();
|
|
13
|
+
}
|
|
14
|
+
monotonic() {
|
|
15
|
+
if (typeof performance !== "undefined" && typeof performance.now === "function") {
|
|
16
|
+
return performance.now();
|
|
17
|
+
}
|
|
18
|
+
return Date.now();
|
|
19
|
+
}
|
|
20
|
+
sleep(ms) {
|
|
21
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { cleanStaleQuarantines, isProcessAlive, parseQuarantineTimestamp, safeRemoveStaleReclaimGuard, safeRollbackLock } from "./lock-core.js";
|
|
1
2
|
/**
|
|
2
3
|
* 数据目录排他锁元数据契约。
|
|
3
4
|
*/
|
|
@@ -17,41 +18,7 @@ export interface DataDirLockInfo {
|
|
|
17
18
|
/** 宿主会话标识 */
|
|
18
19
|
hostSessionId?: string;
|
|
19
20
|
}
|
|
20
|
-
|
|
21
|
-
* 检查目标进程是否处于存活状态。
|
|
22
|
-
*
|
|
23
|
-
* @param pid 待检测的进程标识符
|
|
24
|
-
*/
|
|
25
|
-
export declare function isProcessAlive(pid: number): boolean;
|
|
26
|
-
/**
|
|
27
|
-
* 安全清理陈旧接管守卫(reclaim guard)。
|
|
28
|
-
* 采用原子重命名检疫并严格核对 guardToken 与持有者存活状态,确保仅删除目标陈旧 guard,严禁误删活跃守卫或并发新守卫。
|
|
29
|
-
*
|
|
30
|
-
* @param reclaimPath 目标守卫路径
|
|
31
|
-
* @param expectedGuardToken 预期持有的守卫令牌(必填,拒绝未指定令牌的盲目清理)
|
|
32
|
-
*/
|
|
33
|
-
export declare function safeRemoveStaleReclaimGuard(reclaimPath: string, expectedGuardToken: string): void;
|
|
34
|
-
/**
|
|
35
|
-
* 从隔离或回滚临时目录名称中解析操作者 PID 与隔离生成时间戳。
|
|
36
|
-
* 命名规范:*.(quarantine|rollback|release).<pid>.<timestamp>.<uuid>
|
|
37
|
-
*/
|
|
38
|
-
export declare function parseQuarantineTimestamp(entryName: string): {
|
|
39
|
-
operatorPid?: number;
|
|
40
|
-
timestamp?: number;
|
|
41
|
-
};
|
|
42
|
-
/**
|
|
43
|
-
* 清理过期的隔离目录(GC 回收机制)。
|
|
44
|
-
* 实施分流存活校验,防止误删活跃持锁者与回滚中目录,同时防止孤儿目录无限泄漏:
|
|
45
|
-
* - 共同前置条件:存活年龄必须超过 maxAgeMs(默认 10000ms),未超期前严禁清理(保持宽限期)。
|
|
46
|
-
* - 活跃隔离目录(quarantine / rollback / release):若 operatorPid 存活或内部 metadata.json 记录的 pid 与 childPids 存活,跳过不予清理。
|
|
47
|
-
* - 孤儿脱离目录(orphan):内部旧 metadata.pid 不再作为续命依据;提取其内部 orphanToken,仅当当前规范主路径(主锁目录与 reclaim 目录)依然持有该凭证时保守跳过;规范路径不存在或已变更凭据时直接安全清理。
|
|
48
|
-
*/
|
|
49
|
-
export declare function cleanStaleQuarantines(parentDir: string, basePrefix: string, maxAgeMs?: number, deadline?: number): void;
|
|
50
|
-
/**
|
|
51
|
-
* 安全回滚当前进程创建的主锁。
|
|
52
|
-
* 优先核对 lockToken,回退核对 sessionToken,防止误删接管者或并发新锁。
|
|
53
|
-
*/
|
|
54
|
-
export declare function safeRollbackLock(lockPath: string, expectedSessionToken: string, expectedLockToken?: string): void;
|
|
21
|
+
export { isProcessAlive, parseQuarantineTimestamp, cleanStaleQuarantines, safeRemoveStaleReclaimGuard, safeRollbackLock, };
|
|
55
22
|
/**
|
|
56
23
|
* 安全释放主锁。
|
|
57
24
|
* 仅当锁目录中持有当前 lockToken / sessionToken 时才删除,杜绝删除他人新锁。
|
|
@@ -95,7 +62,7 @@ export declare class DataDirLock {
|
|
|
95
62
|
/**
|
|
96
63
|
* 尝试获取指定数据目录的排他锁。
|
|
97
64
|
*
|
|
98
|
-
*
|
|
65
|
+
* 仲裁规则由目录锁内核(lock-core)统一承载:
|
|
99
66
|
* - 引入所有竞争者均遵守的原子 reclaim guard 机制(lockDirPath.reclaim),每个 guard 具备全局唯一 guardToken。
|
|
100
67
|
* - 基于原子目录创建 mkdirSync(lockDirPath, { mode: 0o700 }) 确立所有权,并在其下存放 metadata.json。
|
|
101
68
|
* - 兼容已有常规文件锁(如老版本或测试 mock 场景)。
|