@actiondock/core 2.5.1 → 2.7.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.
Files changed (175) hide show
  1. package/dist/app/app.d.ts +14 -1
  2. package/dist/app/app.js +88 -222
  3. package/dist/app/static-index.d.ts +39 -0
  4. package/dist/app/static-index.js +164 -0
  5. package/dist/app/types.d.ts +5 -0
  6. package/dist/catalog/action-index.js +6 -4
  7. package/dist/catalog/package-catalog.js +2 -1
  8. package/dist/doctor/checks/index.d.ts +5 -0
  9. package/dist/doctor/checks/index.js +5 -0
  10. package/dist/doctor/checks/project-locate.d.ts +6 -0
  11. package/dist/doctor/checks/project-locate.js +36 -0
  12. package/dist/doctor/checks/project.d.ts +6 -0
  13. package/dist/doctor/checks/project.js +383 -0
  14. package/dist/doctor/checks/registry.d.ts +13 -0
  15. package/dist/doctor/checks/registry.js +207 -0
  16. package/dist/doctor/checks/runtime.d.ts +13 -0
  17. package/dist/doctor/checks/runtime.js +90 -0
  18. package/dist/doctor/checks/storage.d.ts +5 -0
  19. package/dist/doctor/checks/storage.js +34 -0
  20. package/dist/doctor/context.d.ts +35 -0
  21. package/dist/doctor/context.js +11 -0
  22. package/dist/doctor/doctor.d.ts +5 -0
  23. package/dist/doctor/doctor.js +33 -602
  24. package/dist/errors.d.ts +22 -0
  25. package/dist/errors.js +22 -0
  26. package/dist/execution/service.js +14 -9
  27. package/dist/export/templates.js +42 -30
  28. package/dist/host/host.d.ts +33 -0
  29. package/dist/host/host.js +222 -362
  30. package/dist/host/routing.d.ts +92 -0
  31. package/dist/host/routing.js +238 -0
  32. package/dist/host/types.d.ts +8 -0
  33. package/dist/index.d.ts +3 -1
  34. package/dist/index.js +3 -1
  35. package/dist/input/advice.d.ts +100 -0
  36. package/dist/input/advice.js +767 -0
  37. package/dist/input/describe.d.ts +106 -0
  38. package/dist/input/describe.js +176 -0
  39. package/dist/input/file-input.d.ts +54 -0
  40. package/dist/input/file-input.js +124 -0
  41. package/dist/input/flat-decode.d.ts +11 -0
  42. package/dist/input/flat-decode.js +13 -0
  43. package/dist/input/flat-errors.d.ts +72 -0
  44. package/dist/input/flat-errors.js +87 -0
  45. package/dist/input/flat-materializer.d.ts +29 -0
  46. package/dist/input/flat-materializer.js +179 -0
  47. package/dist/input/flat-parser.d.ts +50 -0
  48. package/dist/input/flat-parser.js +169 -0
  49. package/dist/input/flat-predicates.d.ts +34 -0
  50. package/dist/input/flat-predicates.js +50 -0
  51. package/dist/input/index.d.ts +14 -0
  52. package/dist/input/index.js +14 -0
  53. package/dist/input/input-resolver.d.ts +63 -0
  54. package/dist/input/input-resolver.js +254 -0
  55. package/dist/input/json-depth-scanner.d.ts +17 -0
  56. package/dist/input/json-depth-scanner.js +49 -0
  57. package/dist/input/metadata.d.ts +99 -0
  58. package/dist/input/metadata.js +99 -0
  59. package/dist/input/stdin-input.d.ts +26 -0
  60. package/dist/input/stdin-input.js +125 -0
  61. package/dist/input/utf8.d.ts +19 -0
  62. package/dist/input/utf8.js +36 -0
  63. package/dist/input/validation-mapper.d.ts +27 -0
  64. package/dist/input/validation-mapper.js +144 -0
  65. package/dist/ipc/host.js +38 -0
  66. package/dist/json/index.d.ts +1 -0
  67. package/dist/json/index.js +1 -0
  68. package/dist/json/value-validator.d.ts +88 -0
  69. package/dist/json/value-validator.js +330 -0
  70. package/dist/platform/types.d.ts +7 -0
  71. package/dist/process/control-arbiter.d.ts +107 -0
  72. package/dist/process/control-arbiter.js +251 -0
  73. package/dist/process/diagnostics-sink.d.ts +26 -0
  74. package/dist/process/diagnostics-sink.js +35 -0
  75. package/dist/process/index.d.ts +7 -0
  76. package/dist/process/index.js +7 -0
  77. package/dist/process/input-dispatcher.d.ts +88 -0
  78. package/dist/process/input-dispatcher.js +240 -0
  79. package/dist/process/managed-record.d.ts +78 -0
  80. package/dist/process/managed-record.js +1 -0
  81. package/dist/process/process-manager.d.ts +15 -56
  82. package/dist/process/process-manager.js +119 -707
  83. package/dist/process/reservation-table.d.ts +39 -0
  84. package/dist/process/reservation-table.js +80 -0
  85. package/dist/process/run-executor.d.ts +16 -0
  86. package/dist/process/run-executor.js +155 -0
  87. package/dist/process/terminal-output-cache.d.ts +66 -0
  88. package/dist/process/terminal-output-cache.js +132 -0
  89. package/dist/profile/client-actions.d.ts +56 -0
  90. package/dist/profile/client-actions.js +215 -0
  91. package/dist/profile/client-config.d.ts +20 -0
  92. package/dist/profile/client-config.js +51 -0
  93. package/dist/profile/client-health.d.ts +14 -0
  94. package/dist/profile/client-health.js +69 -0
  95. package/dist/profile/client-playbooks.d.ts +19 -0
  96. package/dist/profile/client-playbooks.js +36 -0
  97. package/dist/profile/client-query.d.ts +30 -0
  98. package/dist/profile/client-query.js +70 -0
  99. package/dist/profile/client-runs.d.ts +42 -0
  100. package/dist/profile/client-runs.js +58 -0
  101. package/dist/profile/client-state.d.ts +56 -0
  102. package/dist/profile/client-state.js +83 -0
  103. package/dist/profile/client-transport.d.ts +61 -0
  104. package/dist/profile/client-transport.js +156 -0
  105. package/dist/profile/client.d.ts +23 -142
  106. package/dist/profile/client.js +28 -588
  107. package/dist/profile/manager.js +1 -1
  108. package/dist/project/closure.d.ts +4 -4
  109. package/dist/project/closure.js +11 -5
  110. package/dist/project/init.js +2 -2
  111. package/dist/project/loader.d.ts +0 -25
  112. package/dist/project/loader.js +1 -154
  113. package/dist/project/transactions.d.ts +12 -14
  114. package/dist/project/transactions.js +60 -836
  115. package/dist/registry/registry.js +6 -4
  116. package/dist/registry/resolve.js +1 -1
  117. package/dist/runtime/action-registry.d.ts +45 -0
  118. package/dist/runtime/action-registry.js +108 -0
  119. package/dist/runtime/clock.d.ts +5 -17
  120. package/dist/runtime/clock.js +5 -15
  121. package/dist/runtime/context.d.ts +3 -0
  122. package/dist/runtime/context.js +17 -56
  123. package/dist/runtime/index.d.ts +3 -0
  124. package/dist/runtime/index.js +3 -0
  125. package/dist/runtime/package-runner-factory.d.ts +82 -0
  126. package/dist/runtime/package-runner-factory.js +186 -0
  127. package/dist/runtime/run-persistence.d.ts +85 -0
  128. package/dist/runtime/run-persistence.js +111 -0
  129. package/dist/runtime/runner.d.ts +25 -45
  130. package/dist/runtime/runner.js +130 -354
  131. package/dist/runtime/standalone.d.ts +25 -2
  132. package/dist/runtime/standalone.js +305 -87
  133. package/dist/schema/index.d.ts +1 -0
  134. package/dist/schema/index.js +1 -0
  135. package/dist/schema/validator.d.ts +10 -0
  136. package/dist/schema/validator.js +48 -54
  137. package/dist/server/dispatcher.d.ts +8 -3
  138. package/dist/server/dispatcher.js +16 -40
  139. package/dist/server/routes/runs.js +6 -4
  140. package/dist/server/security.d.ts +1 -7
  141. package/dist/server/security.js +2 -13
  142. package/dist/storage/clock.d.ts +25 -0
  143. package/dist/storage/clock.js +23 -0
  144. package/dist/storage/data-dir-lock.d.ts +3 -36
  145. package/dist/storage/data-dir-lock.js +48 -881
  146. package/dist/storage/index.d.ts +8 -1
  147. package/dist/storage/index.js +8 -2
  148. package/dist/storage/lazy.js +1 -0
  149. package/dist/storage/lock-core.d.ts +162 -0
  150. package/dist/storage/lock-core.js +885 -0
  151. package/dist/storage/sqlite.d.ts +19 -6
  152. package/dist/storage/sqlite.js +81 -89
  153. package/dist/storage/types.d.ts +27 -5
  154. package/dist/storage/types.js +21 -0
  155. package/dist/storage/utils.d.ts +22 -0
  156. package/dist/storage/utils.js +34 -0
  157. package/dist/target/local.d.ts +1 -0
  158. package/dist/target/local.js +23 -17
  159. package/dist/target/remote-errors.d.ts +21 -0
  160. package/dist/target/remote-errors.js +51 -0
  161. package/dist/target/remote-polling.d.ts +35 -0
  162. package/dist/target/remote-polling.js +104 -0
  163. package/dist/target/remote.d.ts +4 -0
  164. package/dist/target/remote.js +108 -301
  165. package/dist/target/sse-parser.d.ts +35 -0
  166. package/dist/target/sse-parser.js +163 -0
  167. package/dist/target/target.js +2 -0
  168. package/dist/target/types.d.ts +6 -0
  169. package/dist/utils/index.d.ts +1 -0
  170. package/dist/utils/index.js +1 -0
  171. package/dist/utils/net.d.ts +10 -0
  172. package/dist/utils/net.js +16 -0
  173. package/dist/version.d.ts +1 -1
  174. package/dist/version.js +1 -1
  175. package/package.json +2 -2
@@ -0,0 +1,39 @@
1
+ import type { ProcessRequestKey } from "./metadata-store.js";
2
+ /**
3
+ * 同步幂等预占条目:在异步落盘窗口内锁定同作用域、进程、操作类型与请求标识的并发请求。
4
+ */
5
+ export interface RequestReservation {
6
+ payloadHash: string;
7
+ operation: string;
8
+ promise: Promise<unknown>;
9
+ resolve: (value: unknown) => void;
10
+ reject: (err: unknown) => void;
11
+ }
12
+ /**
13
+ * 同步幂等预占表。
14
+ *
15
+ * 以复合键在异步落盘窗口内锁定并发重复请求,独立持有全部预占条目:
16
+ * commit 与 reject 均为「查表、删除、resolve/reject」同步原子序列。
17
+ */
18
+ export declare class ReservationTable {
19
+ private readonly reservations;
20
+ /**
21
+ * 同步预占幂等请求:跨 await 的检查与落盘窗口内锁定同复合键并发调用。
22
+ * 返回 undefined 表示预占成功,调用方继续异步路径并在完成时调用 commit/reject。
23
+ * 返回 RequestReservation 表示已有同请求进行中,调用方可等待其结果。
24
+ * 若已有请求负载或操作类型不同,立即抛出 REQUEST_CONFLICT 杜绝混用结果。
25
+ */
26
+ reserve(key: ProcessRequestKey, payloadHash: string, operation: string): RequestReservation | undefined;
27
+ /**
28
+ * 结算预占:同步完成查表、删除与 resolve 的原子序列。
29
+ */
30
+ commit(key: ProcessRequestKey, operation: string, value: unknown): void;
31
+ /**
32
+ * 拒绝预占:同步完成查表、删除与 reject 的原子序列。
33
+ */
34
+ reject(key: ProcessRequestKey, operation: string, err: unknown): void;
35
+ /**
36
+ * 逐条拒绝并删除全部残余预占,供宿主关闭时唤醒全部等待方。
37
+ */
38
+ rejectAll(err: unknown): void;
39
+ }
@@ -0,0 +1,80 @@
1
+ import { REQUEST_CONFLICT, ProcessError, } from "../errors.js";
2
+ /**
3
+ * 格式化同步幂等预占键。
4
+ */
5
+ function formatReservationKey(key, operation) {
6
+ return `${key.hostEpoch}:${key.scope}:${key.processId ?? ""}:${operation}:${key.requestId}`;
7
+ }
8
+ /**
9
+ * 同步幂等预占表。
10
+ *
11
+ * 以复合键在异步落盘窗口内锁定并发重复请求,独立持有全部预占条目:
12
+ * commit 与 reject 均为「查表、删除、resolve/reject」同步原子序列。
13
+ */
14
+ export class ReservationTable {
15
+ reservations = new Map();
16
+ /**
17
+ * 同步预占幂等请求:跨 await 的检查与落盘窗口内锁定同复合键并发调用。
18
+ * 返回 undefined 表示预占成功,调用方继续异步路径并在完成时调用 commit/reject。
19
+ * 返回 RequestReservation 表示已有同请求进行中,调用方可等待其结果。
20
+ * 若已有请求负载或操作类型不同,立即抛出 REQUEST_CONFLICT 杜绝混用结果。
21
+ */
22
+ reserve(key, payloadHash, operation) {
23
+ const reservationKey = formatReservationKey(key, operation);
24
+ const existing = this.reservations.get(reservationKey);
25
+ if (existing) {
26
+ if (existing.payloadHash !== payloadHash || existing.operation !== operation) {
27
+ throw new ProcessError(REQUEST_CONFLICT, "Request conflict: identical requestId with different payload", { requestId: key.requestId });
28
+ }
29
+ return existing;
30
+ }
31
+ let resolveFn = () => { };
32
+ let rejectFn = () => { };
33
+ const promise = new Promise((res, rej) => {
34
+ resolveFn = res;
35
+ rejectFn = rej;
36
+ });
37
+ // 预占 promise 可能永远无人等待:预先挂接空捕获,避免拒绝时触发 unhandledRejection
38
+ promise.catch(() => { });
39
+ const reservation = {
40
+ payloadHash,
41
+ operation,
42
+ promise,
43
+ resolve: resolveFn,
44
+ reject: rejectFn,
45
+ };
46
+ this.reservations.set(reservationKey, reservation);
47
+ return undefined;
48
+ }
49
+ /**
50
+ * 结算预占:同步完成查表、删除与 resolve 的原子序列。
51
+ */
52
+ commit(key, operation, value) {
53
+ const reservationKey = formatReservationKey(key, operation);
54
+ const reservation = this.reservations.get(reservationKey);
55
+ if (reservation) {
56
+ this.reservations.delete(reservationKey);
57
+ reservation.resolve(value);
58
+ }
59
+ }
60
+ /**
61
+ * 拒绝预占:同步完成查表、删除与 reject 的原子序列。
62
+ */
63
+ reject(key, operation, err) {
64
+ const reservationKey = formatReservationKey(key, operation);
65
+ const reservation = this.reservations.get(reservationKey);
66
+ if (reservation) {
67
+ this.reservations.delete(reservationKey);
68
+ reservation.reject(err);
69
+ }
70
+ }
71
+ /**
72
+ * 逐条拒绝并删除全部残余预占,供宿主关闭时唤醒全部等待方。
73
+ */
74
+ rejectAll(err) {
75
+ for (const [reservationKey, res] of Array.from(this.reservations.entries())) {
76
+ this.reservations.delete(reservationKey);
77
+ res.reject(err);
78
+ }
79
+ }
80
+ }
@@ -0,0 +1,16 @@
1
+ import { type CallOptions, type ProcessRunInput, type ProcessRunResult } from "@actiondock/sdk";
2
+ import type { ProcessDriver } from "./driver.js";
3
+ /**
4
+ * 一次性 run 执行器。
5
+ *
6
+ * 仅依赖底层驱动、错误映射与输入校验,不触碰进程注册表:
7
+ * 派生独立 run 进程、收集有限输出并支持协作式取消与超时回收。
8
+ */
9
+ export declare class RunExecutor {
10
+ private readonly driver;
11
+ constructor(driver: ProcessDriver);
12
+ /**
13
+ * 执行一次性外部命令运行。
14
+ */
15
+ execute(input: ProcessRunInput, call?: CallOptions): Promise<ProcessRunResult>;
16
+ }
@@ -0,0 +1,155 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { encodeBytes, } from "@actiondock/sdk";
3
+ import { PROCESS_CANCELLED, PROCESS_TIMEOUT, UNSUPPORTED_CAPABILITY, ProcessError, } from "../errors.js";
4
+ /**
5
+ * 一次性 run 执行器。
6
+ *
7
+ * 仅依赖底层驱动、错误映射与输入校验,不触碰进程注册表:
8
+ * 派生独立 run 进程、收集有限输出并支持协作式取消与超时回收。
9
+ */
10
+ export class RunExecutor {
11
+ driver;
12
+ constructor(driver) {
13
+ this.driver = driver;
14
+ }
15
+ /**
16
+ * 执行一次性外部命令运行。
17
+ */
18
+ async execute(input, call) {
19
+ const ioMode = input.spec.io?.mode ?? "pipe";
20
+ if (ioMode !== "pipe") {
21
+ throw new ProcessError(UNSUPPORTED_CAPABILITY, "Run requires IO mode to be 'pipe'");
22
+ }
23
+ const effectiveSpec = {
24
+ ...input.spec,
25
+ io: {
26
+ ...input.spec.io,
27
+ mode: "pipe",
28
+ },
29
+ };
30
+ const runProcessId = `run-${randomUUID()}`;
31
+ const chunks = [];
32
+ let totalBytes = 0;
33
+ let truncated = false;
34
+ let timedOut = false;
35
+ let cancelled = false;
36
+ let resolveExit;
37
+ let rejectError;
38
+ const exitPromise = new Promise((resolve, reject) => {
39
+ resolveExit = resolve;
40
+ rejectError = reject;
41
+ });
42
+ let driverHandle;
43
+ const callbacks = {
44
+ onOutput: (stream, data) => {
45
+ if (truncated)
46
+ return;
47
+ if (stream !== "stdout" && stream !== "stderr")
48
+ return;
49
+ const remaining = input.maxOutputBytes - totalBytes;
50
+ if (remaining <= 0) {
51
+ truncated = true;
52
+ if (driverHandle) {
53
+ void driverHandle.terminate(1000);
54
+ }
55
+ return;
56
+ }
57
+ if (data.byteLength > remaining) {
58
+ const slice = data.subarray(0, remaining);
59
+ chunks.push({
60
+ stream,
61
+ data: encodeBytes(slice),
62
+ });
63
+ totalBytes += slice.byteLength;
64
+ truncated = true;
65
+ if (driverHandle) {
66
+ void driverHandle.terminate(1000);
67
+ }
68
+ }
69
+ else {
70
+ chunks.push({
71
+ stream,
72
+ data: encodeBytes(data),
73
+ });
74
+ totalBytes += data.byteLength;
75
+ }
76
+ },
77
+ onExit: (exit) => {
78
+ resolveExit(exit);
79
+ },
80
+ onError: (err) => {
81
+ rejectError(err);
82
+ },
83
+ };
84
+ // 对齐 start 防御顺序:派生前检查取消信号,已中止直接抛出取消错误不再派生进程
85
+ if (call?.signal?.aborted) {
86
+ throw call.signal.reason instanceof ProcessError
87
+ ? call.signal.reason
88
+ : new ProcessError(PROCESS_CANCELLED, "Process run was cancelled");
89
+ }
90
+ driverHandle = await this.driver.spawn(runProcessId, effectiveSpec, callbacks);
91
+ let timeoutTimer;
92
+ if (input.timeoutMs > 0) {
93
+ timeoutTimer = setTimeout(() => {
94
+ timedOut = true;
95
+ if (driverHandle) {
96
+ void driverHandle.terminate(1000);
97
+ }
98
+ }, input.timeoutMs);
99
+ if (typeof timeoutTimer?.unref === "function") {
100
+ timeoutTimer.unref();
101
+ }
102
+ }
103
+ let onAbort;
104
+ if (call?.signal) {
105
+ if (call.signal.aborted) {
106
+ if (timeoutTimer)
107
+ clearTimeout(timeoutTimer);
108
+ if (driverHandle)
109
+ void driverHandle.terminate(1000);
110
+ throw call.signal.reason ?? new ProcessError(PROCESS_CANCELLED, "Process run was cancelled");
111
+ }
112
+ onAbort = () => {
113
+ cancelled = true;
114
+ if (timeoutTimer)
115
+ clearTimeout(timeoutTimer);
116
+ if (driverHandle)
117
+ void driverHandle.terminate(1000);
118
+ };
119
+ call.signal.addEventListener("abort", onAbort, { once: true });
120
+ }
121
+ try {
122
+ const exit = await exitPromise;
123
+ if (timeoutTimer)
124
+ clearTimeout(timeoutTimer);
125
+ if (call?.signal && onAbort) {
126
+ call.signal.removeEventListener("abort", onAbort);
127
+ }
128
+ if (timedOut) {
129
+ throw new ProcessError(PROCESS_TIMEOUT, `Process exceeded timeout of ${input.timeoutMs}ms`);
130
+ }
131
+ if (cancelled || call?.signal?.aborted) {
132
+ throw call?.signal?.reason ?? new ProcessError(PROCESS_CANCELLED, "Process run was cancelled");
133
+ }
134
+ return {
135
+ exit,
136
+ chunks,
137
+ truncated,
138
+ };
139
+ }
140
+ catch (err) {
141
+ if (timeoutTimer)
142
+ clearTimeout(timeoutTimer);
143
+ if (call?.signal && onAbort) {
144
+ call.signal.removeEventListener("abort", onAbort);
145
+ }
146
+ if (timedOut) {
147
+ throw new ProcessError(PROCESS_TIMEOUT, `Process exceeded timeout of ${input.timeoutMs}ms`);
148
+ }
149
+ if (cancelled || call?.signal?.aborted) {
150
+ throw call?.signal?.reason ?? new ProcessError(PROCESS_CANCELLED, "Process run was cancelled");
151
+ }
152
+ throw err;
153
+ }
154
+ }
155
+ }
@@ -0,0 +1,66 @@
1
+ import type { ProcessOutputLog } from "./output-log.js";
2
+ /**
3
+ * 已淘汰输出日志的墓碑信息。
4
+ */
5
+ export interface EvictedOutputTombstone {
6
+ tailCursor: string;
7
+ earliestCursor: string;
8
+ evictedAt: number;
9
+ }
10
+ /**
11
+ * 终态输出日志保留缓存。
12
+ *
13
+ * 独立持有已驱逐进程的输出日志缓存与墓碑登记:内存输出无法从持久层恢复,
14
+ * 保留缓存支撑后续游标读取;TTL 过期转墓碑与 LRU 淘汰均保持同步原子。
15
+ */
16
+ export declare class TerminalOutputCache {
17
+ /** 保留日志最大条目数上限(默认) */
18
+ static readonly DEFAULT_MAX_ENTRIES = 512;
19
+ /** 墓碑最大登记条数上限(默认) */
20
+ static readonly DEFAULT_MAX_TOMBSTONES = 2048;
21
+ private readonly entries;
22
+ private readonly tombstones;
23
+ private readonly retentionMs;
24
+ private readonly maxHostBufferBytes;
25
+ private readonly maxEntries;
26
+ private readonly maxTombstones;
27
+ constructor(options: {
28
+ /** 保留时长(毫秒) */
29
+ retentionMs: number;
30
+ /** 宿主输出缓冲区字节总配额 */
31
+ maxHostBufferBytes: number;
32
+ /** 保留日志最大条目数,默认 512 */
33
+ maxEntries?: number;
34
+ /** 墓碑最大登记条数,默认 2048 */
35
+ maxTombstones?: number;
36
+ });
37
+ /**
38
+ * 记录已淘汰输出日志的墓碑信息(超出容量时淘汰最旧登记,防止内存无限积压)。
39
+ */
40
+ private recordTombstone;
41
+ /**
42
+ * 清理已过期的终态输出日志条目并转为墓碑。
43
+ */
44
+ private cleanExpired;
45
+ /**
46
+ * 获取指定进程的墓碑登记信息。
47
+ */
48
+ getTombstone(processId: string): EvictedOutputTombstone | undefined;
49
+ /**
50
+ * 统计终态保留日志当前在内存中实际占用的输出缓冲字节总数(附带过期清理)。
51
+ */
52
+ retainedBytes(): number;
53
+ /**
54
+ * 将终态输出日志存入保留缓存,并根据宿主配额执行 LRU 与 TTL 淘汰。
55
+ */
56
+ retain(processId: string, log: ProcessOutputLog): void;
57
+ /**
58
+ * 获取终态保留日志(附带过期清理与 LRU 触达更新)。
59
+ */
60
+ get(processId: string): ProcessOutputLog | undefined;
61
+ /**
62
+ * 按 LRU 顺序逐条淘汰最旧保留日志并转为墓碑,直至剩余保留字节数不超过给定的可容纳上限。
63
+ * 返回被淘汰条目释放的字节总数,供宿主输出缓冲预算扣减复用。
64
+ */
65
+ evictLRUUntil(fits: number): number;
66
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * 终态输出日志保留缓存。
3
+ *
4
+ * 独立持有已驱逐进程的输出日志缓存与墓碑登记:内存输出无法从持久层恢复,
5
+ * 保留缓存支撑后续游标读取;TTL 过期转墓碑与 LRU 淘汰均保持同步原子。
6
+ */
7
+ export class TerminalOutputCache {
8
+ /** 保留日志最大条目数上限(默认) */
9
+ static DEFAULT_MAX_ENTRIES = 512;
10
+ /** 墓碑最大登记条数上限(默认) */
11
+ static DEFAULT_MAX_TOMBSTONES = 2048;
12
+ entries = new Map();
13
+ tombstones = new Map();
14
+ retentionMs;
15
+ maxHostBufferBytes;
16
+ maxEntries;
17
+ maxTombstones;
18
+ constructor(options) {
19
+ this.retentionMs = options.retentionMs;
20
+ this.maxHostBufferBytes = options.maxHostBufferBytes;
21
+ this.maxEntries = options.maxEntries ?? TerminalOutputCache.DEFAULT_MAX_ENTRIES;
22
+ this.maxTombstones = options.maxTombstones ?? TerminalOutputCache.DEFAULT_MAX_TOMBSTONES;
23
+ }
24
+ /**
25
+ * 记录已淘汰输出日志的墓碑信息(超出容量时淘汰最旧登记,防止内存无限积压)。
26
+ */
27
+ recordTombstone(processId, tombstone) {
28
+ if (this.tombstones.size >= this.maxTombstones) {
29
+ const oldestKey = this.tombstones.keys().next().value;
30
+ if (oldestKey) {
31
+ this.tombstones.delete(oldestKey);
32
+ }
33
+ }
34
+ this.tombstones.set(processId, tombstone);
35
+ }
36
+ /**
37
+ * 清理已过期的终态输出日志条目并转为墓碑。
38
+ */
39
+ cleanExpired() {
40
+ const now = Date.now();
41
+ for (const [id, entry] of this.entries.entries()) {
42
+ if (now - entry.evictedAt > this.retentionMs) {
43
+ this.recordTombstone(id, {
44
+ tailCursor: entry.log.tailCursor,
45
+ earliestCursor: entry.log.earliestCursor,
46
+ evictedAt: now,
47
+ });
48
+ this.entries.delete(id);
49
+ }
50
+ }
51
+ }
52
+ /**
53
+ * 获取指定进程的墓碑登记信息。
54
+ */
55
+ getTombstone(processId) {
56
+ return this.tombstones.get(processId);
57
+ }
58
+ /**
59
+ * 统计终态保留日志当前在内存中实际占用的输出缓冲字节总数(附带过期清理)。
60
+ */
61
+ retainedBytes() {
62
+ this.cleanExpired();
63
+ let bytes = 0;
64
+ for (const entry of this.entries.values()) {
65
+ bytes += entry.log.currentBytes;
66
+ }
67
+ return bytes;
68
+ }
69
+ /**
70
+ * 将终态输出日志存入保留缓存,并根据宿主配额执行 LRU 与 TTL 淘汰。
71
+ */
72
+ retain(processId, log) {
73
+ this.cleanExpired();
74
+ // 若新加入条目会导致总输出配额超限或数量超限,按 LRU 顺序淘汰最旧条目
75
+ while ((this.retainedBytes() + log.currentBytes > this.maxHostBufferBytes ||
76
+ this.entries.size >= this.maxEntries) &&
77
+ this.entries.size > 0) {
78
+ const oldestKey = this.entries.keys().next().value;
79
+ if (!oldestKey)
80
+ break;
81
+ const oldestEntry = this.entries.get(oldestKey);
82
+ if (oldestEntry) {
83
+ this.recordTombstone(oldestKey, {
84
+ tailCursor: oldestEntry.log.tailCursor,
85
+ earliestCursor: oldestEntry.log.earliestCursor,
86
+ evictedAt: Date.now(),
87
+ });
88
+ }
89
+ this.entries.delete(oldestKey);
90
+ }
91
+ this.entries.set(processId, {
92
+ log,
93
+ evictedAt: Date.now(),
94
+ });
95
+ }
96
+ /**
97
+ * 获取终态保留日志(附带过期清理与 LRU 触达更新)。
98
+ */
99
+ get(processId) {
100
+ this.cleanExpired();
101
+ const entry = this.entries.get(processId);
102
+ if (!entry)
103
+ return undefined;
104
+ // 触达刷新 LRU 顺序
105
+ this.entries.delete(processId);
106
+ this.entries.set(processId, entry);
107
+ return entry.log;
108
+ }
109
+ /**
110
+ * 按 LRU 顺序逐条淘汰最旧保留日志并转为墓碑,直至剩余保留字节数不超过给定的可容纳上限。
111
+ * 返回被淘汰条目释放的字节总数,供宿主输出缓冲预算扣减复用。
112
+ */
113
+ evictLRUUntil(fits) {
114
+ let evictedBytes = 0;
115
+ while (this.entries.size > 0 && this.retainedBytes() > fits) {
116
+ const oldestKey = this.entries.keys().next().value;
117
+ if (!oldestKey)
118
+ break;
119
+ const oldest = this.entries.get(oldestKey);
120
+ if (oldest) {
121
+ this.recordTombstone(oldestKey, {
122
+ tailCursor: oldest.log.tailCursor,
123
+ earliestCursor: oldest.log.earliestCursor,
124
+ evictedAt: Date.now(),
125
+ });
126
+ evictedBytes += oldest.log.currentBytes;
127
+ }
128
+ this.entries.delete(oldestKey);
129
+ }
130
+ return evictedBytes;
131
+ }
132
+ }
@@ -0,0 +1,56 @@
1
+ import type { ExecutionResult } from "@actiondock/sdk";
2
+ import { type RemoteClientRequestOptions } from "./client-transport.js";
3
+ /**
4
+ * 远端 Action 执行与查询域端点。
5
+ */
6
+ /**
7
+ * 调用远端 ActionDock 服务端执行 Action 时的选项参数。
8
+ */
9
+ export interface RemoteExecuteOptions extends RemoteClientRequestOptions {
10
+ /** 动态配置覆盖 */
11
+ configOverrides?: Record<string, unknown>;
12
+ /** 鉴权 Bearer Token */
13
+ token?: string;
14
+ /** 超时毫秒数 */
15
+ timeoutMs?: number;
16
+ /** 中断信号 */
17
+ signal?: AbortSignal;
18
+ /** 幂等请求去重标识 */
19
+ requestId?: string;
20
+ /** 是否异步触发(202 Accepted 立即返回 runId) */
21
+ async?: boolean;
22
+ }
23
+ /**
24
+ * 远端 Action 执行结果信封对象。
25
+ */
26
+ export type RemoteExecutionResult<T = unknown> = ExecutionResult<T> & {
27
+ status?: string;
28
+ };
29
+ /**
30
+ * 执行远端 Action:支持幂等标识、异步触发、外部取消信号与本地超时守卫组合。
31
+ */
32
+ export declare function executeRemoteAction<T = unknown>(serverUrl: string, actionId: string, input?: unknown, configOverridesOrOptions?: Record<string, unknown> | RemoteExecuteOptions, tokenArg?: string): Promise<RemoteExecutionResult<T>>;
33
+ /**
34
+ * 拉取远端 Action 列表(可按意图过滤)。
35
+ */
36
+ export declare function fetchRemoteActions(serverUrl: string, token?: string, intent?: string, options?: RemoteClientRequestOptions): Promise<Array<{
37
+ id: string;
38
+ description: string;
39
+ packageId?: string;
40
+ }>>;
41
+ /**
42
+ * 拉取单个远端 Action 的详细契约描述。
43
+ */
44
+ export declare function fetchRemoteActionShow(serverUrl: string, actionId: string, token?: string, options?: RemoteClientRequestOptions): Promise<any>;
45
+ /**
46
+ * 拉取远端服务信息总览(可按意图、包名过滤并附目录树)。
47
+ */
48
+ export declare function fetchRemoteInfo(serverUrl: string, token?: string, options?: {
49
+ intent?: string;
50
+ package?: string;
51
+ tree?: boolean;
52
+ } & RemoteClientRequestOptions): Promise<any>;
53
+ /**
54
+ * 拉取远端环境体检报告(可定向指定包)。
55
+ */
56
+ export declare function fetchRemoteDoctor(serverUrl: string, token?: string, targetPackage?: string, options?: RemoteClientRequestOptions): Promise<any>;