@actiondock/core 2.10.0 → 2.12.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 (252) hide show
  1. package/README.md +1 -2
  2. package/dist/catalog/action-catalog.js +3 -33
  3. package/dist/catalog/discovery.d.ts +0 -3
  4. package/dist/catalog/discovery.js +4 -4
  5. package/dist/catalog/graph.d.ts +0 -4
  6. package/dist/catalog/graph.js +8 -14
  7. package/dist/{profile/client-actions.d.ts → client/actions.d.ts} +1 -1
  8. package/dist/{profile/client-actions.js → client/actions.js} +2 -8
  9. package/dist/{profile/client-config.d.ts → client/config.d.ts} +1 -1
  10. package/dist/{profile/client-config.js → client/config.js} +1 -1
  11. package/dist/client/dispatcher.d.ts +4 -0
  12. package/dist/client/dispatcher.js +21 -0
  13. package/dist/client/health.d.ts +38 -0
  14. package/dist/{profile/client-health.js → client/health.js} +9 -6
  15. package/dist/client/index.d.ts +16 -0
  16. package/dist/client/index.js +20 -0
  17. package/dist/{profile/client-playbooks.d.ts → client/playbooks.d.ts} +1 -1
  18. package/dist/{profile/client-playbooks.js → client/playbooks.js} +1 -1
  19. package/dist/{profile/client-runs.d.ts → client/runs.d.ts} +4 -2
  20. package/dist/{profile/client-runs.js → client/runs.js} +7 -1
  21. package/dist/{profile/client-state.d.ts → client/state.d.ts} +1 -1
  22. package/dist/{profile/client-state.js → client/state.js} +1 -1
  23. package/dist/{profile/client-transport.d.ts → client/transport.d.ts} +29 -10
  24. package/dist/{profile/client-transport.js → client/transport.js} +125 -35
  25. package/dist/doctor/checks/index.d.ts +1 -1
  26. package/dist/doctor/checks/index.js +1 -1
  27. package/dist/doctor/checks/project.js +20 -88
  28. package/dist/doctor/checks/runtime.d.ts +0 -4
  29. package/dist/doctor/checks/runtime.js +0 -28
  30. package/dist/doctor/doctor.js +1 -2
  31. package/dist/errors.d.ts +53 -3
  32. package/dist/errors.js +53 -3
  33. package/dist/execution/service.d.ts +55 -37
  34. package/dist/execution/service.js +601 -212
  35. package/dist/execution/types.d.ts +24 -5
  36. package/dist/host/host.d.ts +42 -22
  37. package/dist/host/host.js +562 -149
  38. package/dist/host/routing.js +6 -6
  39. package/dist/host/types.d.ts +25 -13
  40. package/dist/index.d.ts +9 -4
  41. package/dist/index.js +5 -2
  42. package/dist/input/advice.d.ts +0 -44
  43. package/dist/input/advice.js +7 -141
  44. package/dist/input/flat-materializer.js +1 -1
  45. package/dist/input/flat-parser.js +1 -1
  46. package/dist/input/index.d.ts +0 -1
  47. package/dist/input/index.js +0 -1
  48. package/dist/input/input-resolver.js +1 -1
  49. package/dist/input/validation-mapper.d.ts +1 -1
  50. package/dist/ipc/diagnostic.d.ts +8 -21
  51. package/dist/ipc/diagnostic.js +24 -57
  52. package/dist/ipc/host.js +21 -7
  53. package/dist/ipc/service.d.ts +14 -0
  54. package/dist/ipc/service.js +132 -26
  55. package/dist/ipc/types.d.ts +0 -4
  56. package/dist/package/index.d.ts +4 -5
  57. package/dist/package/index.js +1 -2
  58. package/dist/package/runtime.d.ts +94 -23
  59. package/dist/package/runtime.js +628 -274
  60. package/dist/package/static-index.d.ts +3 -6
  61. package/dist/package/static-index.js +11 -40
  62. package/dist/package/types.d.ts +19 -4
  63. package/dist/platform/index.d.ts +2 -1
  64. package/dist/platform/index.js +2 -1
  65. package/dist/platform/loader-hook.d.ts +32 -0
  66. package/dist/platform/loader-hook.js +90 -0
  67. package/dist/{node → platform}/module-loader.d.ts +13 -0
  68. package/dist/{node → platform}/module-loader.js +51 -4
  69. package/dist/platform/node.d.ts +19 -8
  70. package/dist/platform/node.js +50 -20
  71. package/dist/platform/types.d.ts +7 -47
  72. package/dist/process/context-process.d.ts +3 -18
  73. package/dist/process/context-process.js +3 -44
  74. package/dist/process/control.d.ts +21 -0
  75. package/dist/process/control.js +34 -0
  76. package/dist/process/converters.d.ts +23 -0
  77. package/dist/process/converters.js +92 -0
  78. package/dist/process/cursor.js +2 -22
  79. package/dist/process/{diagnostics-sink.d.ts → diagnostics.d.ts} +0 -7
  80. package/dist/process/{diagnostics-sink.js → diagnostics.js} +6 -8
  81. package/dist/process/index.d.ts +1 -3
  82. package/dist/process/index.js +1 -3
  83. package/dist/process/input-dispatcher.d.ts +4 -13
  84. package/dist/process/input-dispatcher.js +48 -69
  85. package/dist/process/managed-record.d.ts +3 -31
  86. package/dist/process/metadata-store.d.ts +0 -41
  87. package/dist/process/metadata-store.js +2 -341
  88. package/dist/process/process-driver.js +2 -2
  89. package/dist/process/process-manager.d.ts +8 -50
  90. package/dist/process/process-manager.js +28 -417
  91. package/dist/process/quotas.d.ts +14 -0
  92. package/dist/process/quotas.js +29 -0
  93. package/dist/process/run-executor.js +0 -1
  94. package/dist/process/terminal-output-cache.d.ts +20 -34
  95. package/dist/process/terminal-output-cache.js +43 -64
  96. package/dist/profile/index.d.ts +1 -2
  97. package/dist/profile/index.js +1 -2
  98. package/dist/profile/manager.d.ts +2 -4
  99. package/dist/profile/manager.js +10 -40
  100. package/dist/profile/types.d.ts +1 -17
  101. package/dist/project/digest.d.ts +5 -1
  102. package/dist/project/digest.js +81 -219
  103. package/dist/project/index.d.ts +4 -5
  104. package/dist/project/index.js +4 -5
  105. package/dist/project/init.js +2 -2
  106. package/dist/project/loader.d.ts +3 -3
  107. package/dist/project/loader.js +72 -50
  108. package/dist/project/lockfile.js +6 -5
  109. package/dist/project/manifest.js +5 -4
  110. package/dist/project/transactions.d.ts +4 -42
  111. package/dist/project/transactions.js +83 -138
  112. package/dist/project/types-generator.js +2 -1
  113. package/dist/project/types.d.ts +2 -0
  114. package/dist/registry/index.d.ts +0 -1
  115. package/dist/registry/index.js +0 -1
  116. package/dist/registry/lock.d.ts +2 -11
  117. package/dist/registry/lock.js +14 -183
  118. package/dist/registry/registry.d.ts +1 -6
  119. package/dist/registry/registry.js +5 -17
  120. package/dist/runtime/action-registry.d.ts +1 -5
  121. package/dist/runtime/action-registry.js +0 -12
  122. package/dist/runtime/context.js +2 -2
  123. package/dist/runtime/events.d.ts +11 -50
  124. package/dist/runtime/events.js +88 -368
  125. package/dist/runtime/index.d.ts +0 -4
  126. package/dist/runtime/index.js +0 -4
  127. package/dist/runtime/run-persistence.d.ts +2 -0
  128. package/dist/runtime/run-persistence.js +2 -1
  129. package/dist/runtime/standalone-commands.d.ts +18 -0
  130. package/dist/runtime/standalone-commands.js +466 -0
  131. package/dist/runtime/standalone-help.d.ts +20 -0
  132. package/dist/runtime/standalone-help.js +72 -0
  133. package/dist/runtime/standalone.d.ts +1 -44
  134. package/dist/runtime/standalone.js +32 -573
  135. package/dist/schema/validator.d.ts +2 -2
  136. package/dist/schema/validator.js +2 -9
  137. package/dist/server/index.d.ts +0 -2
  138. package/dist/server/index.js +0 -2
  139. package/dist/server/routes/actions.js +1 -1
  140. package/dist/server/routes/info.js +1 -1
  141. package/dist/server/routes/playbooks.js +1 -1
  142. package/dist/server/routes/runs.js +123 -16
  143. package/dist/server/routes/state.js +43 -18
  144. package/dist/server/security.d.ts +1 -1
  145. package/dist/server/security.js +2 -2
  146. package/dist/server/server.js +3 -7
  147. package/dist/service/factory.js +4 -3
  148. package/dist/service/index.d.ts +1 -1
  149. package/dist/service/index.js +1 -1
  150. package/dist/service/remote-errors.d.ts +1 -1
  151. package/dist/service/remote-errors.js +6 -6
  152. package/dist/service/remote-polling.d.ts +3 -0
  153. package/dist/service/remote-polling.js +10 -5
  154. package/dist/service/remote.d.ts +5 -1
  155. package/dist/service/remote.js +108 -17
  156. package/dist/service/sse-stream.js +2 -2
  157. package/dist/service/types.d.ts +22 -19
  158. package/dist/service/types.js +1 -24
  159. package/dist/storage/clock.d.ts +1 -1
  160. package/dist/storage/clock.js +1 -1
  161. package/dist/storage/data-dir-lock.d.ts +10 -22
  162. package/dist/storage/data-dir-lock.js +28 -56
  163. package/dist/storage/file-lock.d.ts +64 -0
  164. package/dist/storage/file-lock.js +447 -0
  165. package/dist/storage/index.d.ts +2 -2
  166. package/dist/storage/index.js +5 -4
  167. package/dist/storage/run-liveness.d.ts +48 -0
  168. package/dist/storage/run-liveness.js +44 -0
  169. package/dist/storage/sqlite-config.d.ts +16 -0
  170. package/dist/storage/sqlite-config.js +59 -0
  171. package/dist/storage/sqlite-driver.d.ts +3 -1
  172. package/dist/storage/sqlite-driver.js +14 -4
  173. package/dist/storage/sqlite-events.d.ts +13 -0
  174. package/dist/storage/sqlite-events.js +48 -0
  175. package/dist/storage/sqlite-runs.d.ts +107 -0
  176. package/dist/storage/sqlite-runs.js +622 -0
  177. package/dist/storage/sqlite-schema.d.ts +5 -0
  178. package/dist/storage/sqlite-schema.js +143 -0
  179. package/dist/storage/sqlite-state.d.ts +33 -0
  180. package/dist/storage/sqlite-state.js +270 -0
  181. package/dist/storage/sqlite.d.ts +30 -57
  182. package/dist/storage/sqlite.js +84 -741
  183. package/dist/storage/types.d.ts +35 -2
  184. package/dist/utils/index.d.ts +18 -14
  185. package/dist/utils/index.js +37 -29
  186. package/dist/utils/semver.d.ts +18 -0
  187. package/dist/utils/semver.js +19 -0
  188. package/dist/utils/warning.d.ts +1 -0
  189. package/dist/utils/warning.js +52 -0
  190. package/dist/{json/value-validator.d.ts → value-validator.d.ts} +9 -0
  191. package/dist/value-validator.js +330 -0
  192. package/dist/version.d.ts +1 -1
  193. package/dist/version.js +1 -1
  194. package/package.json +10 -12
  195. package/dist/execution/index.d.ts +0 -2
  196. package/dist/execution/index.js +0 -2
  197. package/dist/filter/index.d.ts +0 -1
  198. package/dist/filter/index.js +0 -1
  199. package/dist/graph/index.d.ts +0 -4
  200. package/dist/graph/index.js +0 -4
  201. package/dist/input/metadata.d.ts +0 -99
  202. package/dist/input/metadata.js +0 -99
  203. package/dist/invocation/index.d.ts +0 -2
  204. package/dist/invocation/index.js +0 -2
  205. package/dist/ipc/index.d.ts +0 -4
  206. package/dist/ipc/index.js +0 -4
  207. package/dist/json/index.d.ts +0 -1
  208. package/dist/json/index.js +0 -1
  209. package/dist/json/value-validator.js +0 -330
  210. package/dist/node/index.d.ts +0 -1
  211. package/dist/node/index.js +0 -1
  212. package/dist/platform/node-fs.d.ts +0 -37
  213. package/dist/platform/node-fs.js +0 -81
  214. package/dist/process/control-arbiter.d.ts +0 -108
  215. package/dist/process/control-arbiter.js +0 -250
  216. package/dist/process/process-executor.d.ts +0 -47
  217. package/dist/process/process-executor.js +0 -296
  218. package/dist/profile/client-health.d.ts +0 -14
  219. package/dist/profile/client-query.d.ts +0 -30
  220. package/dist/profile/client-query.js +0 -69
  221. package/dist/profile/client.d.ts +0 -26
  222. package/dist/profile/client.js +0 -31
  223. package/dist/project/closure.d.ts +0 -25
  224. package/dist/project/closure.js +0 -74
  225. package/dist/registry/store.d.ts +0 -42
  226. package/dist/registry/store.js +0 -43
  227. package/dist/runtime/clock.d.ts +0 -7
  228. package/dist/runtime/clock.js +0 -7
  229. package/dist/runtime/module-loader.d.ts +0 -2
  230. package/dist/runtime/module-loader.js +0 -1
  231. package/dist/runtime/process.d.ts +0 -8
  232. package/dist/runtime/process.js +0 -1
  233. package/dist/runtime/runner.d.ts +0 -234
  234. package/dist/runtime/runner.js +0 -609
  235. package/dist/schema/index.d.ts +0 -1
  236. package/dist/schema/index.js +0 -1
  237. package/dist/server/dispatcher.d.ts +0 -25
  238. package/dist/server/dispatcher.js +0 -54
  239. package/dist/service/local.d.ts +0 -27
  240. package/dist/service/local.js +0 -141
  241. package/dist/storage/driver.d.ts +0 -10
  242. package/dist/storage/driver.js +0 -10
  243. package/dist/storage/lazy.d.ts +0 -8
  244. package/dist/storage/lazy.js +0 -45
  245. package/dist/storage/lock-core.d.ts +0 -158
  246. package/dist/storage/lock-core.js +0 -872
  247. package/dist/utils/net.d.ts +0 -10
  248. package/dist/utils/net.js +0 -16
  249. package/dist/utils/process.d.ts +0 -10
  250. package/dist/utils/process.js +0 -21
  251. /package/dist/{filter → utils}/intent.d.ts +0 -0
  252. /package/dist/{filter → utils}/intent.js +0 -0
@@ -0,0 +1,64 @@
1
+ import { type Clock } from "./clock.js";
2
+ export declare const DEFAULT_FILE_LOCK_STALE_MS = 10000;
3
+ export declare const DEFAULT_FILE_LOCK_ACQUIRE_TIMEOUT_MS = 15000;
4
+ export declare const DEFAULT_FILE_LOCK_RETRY_DELAY_MS = 25;
5
+ export declare const DEFAULT_FILE_LOCK_HEARTBEAT_MS = 3000;
6
+ export declare const LOCK_METADATA_FILE = "metadata.json";
7
+ export interface FileLockMetadata {
8
+ pid: number;
9
+ token: string;
10
+ createdAt: string;
11
+ sessionToken?: string;
12
+ lockToken?: string;
13
+ [key: string]: unknown;
14
+ }
15
+ export interface FileLockOptions {
16
+ staleMs?: number;
17
+ acquireTimeoutMs?: number;
18
+ retryDelayMs?: number;
19
+ heartbeatMs?: number;
20
+ metadata?: Record<string, unknown>;
21
+ mtimeFirst?: boolean;
22
+ createLockError?: (message: string) => Error;
23
+ assertHolderReclaimable?: (metadata: FileLockMetadata) => void;
24
+ clock?: Clock;
25
+ }
26
+ export interface FileLockHandle {
27
+ lockPath: string;
28
+ metadata: FileLockMetadata;
29
+ release(): void | Promise<void>;
30
+ }
31
+ export interface LockDriver<T> {
32
+ mkdir(path: string, options?: {
33
+ mode?: number;
34
+ recursive?: boolean;
35
+ }): T;
36
+ rmdir(path: string, options?: {
37
+ recursive?: boolean;
38
+ force?: boolean;
39
+ }): T;
40
+ readFile(path: string, encoding: "utf-8"): T;
41
+ writeFile(path: string, data: string, options?: {
42
+ mode?: number;
43
+ }): T;
44
+ rename(oldPath: string, newPath: string): T;
45
+ stat(path: string): T;
46
+ unlink(path: string): T;
47
+ delay(ms: number): T;
48
+ timeExceeded(deadline: number): boolean;
49
+ setInterval?(fn: () => void, ms: number): NodeJS.Timeout;
50
+ }
51
+ export declare const syncLockDriver: LockDriver<any>;
52
+ export declare const asyncLockDriver: LockDriver<Promise<any>>;
53
+ export declare function runSync<T>(driver: LockDriver<any>, machine: Generator<any, T, any>): T;
54
+ export declare function runAsync<T>(driver: LockDriver<Promise<any>>, machine: Generator<any, T, any>): Promise<T>;
55
+ export declare function readLockMetadata(lockPath: string): Promise<FileLockMetadata | undefined>;
56
+ export declare function readLockMetadataSync(lockPath: string): FileLockMetadata | undefined;
57
+ export declare function isStaleLock(lockPath: string, staleMs?: number, options?: FileLockOptions, clock?: Clock): Promise<boolean>;
58
+ export declare function isStaleLockSync(lockPath: string, staleMs?: number, options?: FileLockOptions, clock?: Clock): boolean;
59
+ export declare function isLockHeld(lockPath: string, excludeSelf?: boolean, clock?: Clock): boolean;
60
+ export declare function safeReleaseFileLock(lockPath: string, expectedToken?: string, expectedSessionToken?: string): Promise<void>;
61
+ export declare function safeReleaseFileLockSync(lockPath: string, expectedToken?: string, expectedSessionToken?: string): void;
62
+ export declare function acquireFileLockSync(lockPath: string, options?: FileLockOptions): FileLockHandle;
63
+ export declare function acquireFileLock(lockPath: string, options?: FileLockOptions): Promise<FileLockHandle>;
64
+ export declare function withFileLock<T>(lockPath: string, fn: () => T | Promise<T>, options?: FileLockOptions): Promise<T>;
@@ -0,0 +1,447 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
3
+ import { mkdir, readFile, rename, rm, stat, utimes, writeFile } from "node:fs/promises";
4
+ import { dirname, join, resolve } from "node:path";
5
+ import { isProcessAlive } from "../utils/index.js";
6
+ import { SystemClock } from "./clock.js";
7
+ import { ActionDockError, STORAGE_BUSY, TIMEOUT } from "../errors.js";
8
+ const defaultClock = new SystemClock();
9
+ export const DEFAULT_FILE_LOCK_STALE_MS = 10000;
10
+ export const DEFAULT_FILE_LOCK_ACQUIRE_TIMEOUT_MS = 15000;
11
+ export const DEFAULT_FILE_LOCK_RETRY_DELAY_MS = 25;
12
+ export const DEFAULT_FILE_LOCK_HEARTBEAT_MS = 3000;
13
+ export const LOCK_METADATA_FILE = "metadata.json";
14
+ const activeLockPaths = new Set();
15
+ export const syncLockDriver = {
16
+ mkdir: (path, opts) => mkdirSync(path, opts),
17
+ rmdir: (path, opts) => rmSync(path, opts),
18
+ readFile: (path, enc) => readFileSync(path, enc),
19
+ writeFile: (path, data, opts) => writeFileSync(path, data, opts),
20
+ rename: (oldPath, newPath) => renameSync(oldPath, newPath),
21
+ stat: (path) => statSync(path),
22
+ unlink: (path) => rmSync(path, { force: true }),
23
+ delay: () => { },
24
+ timeExceeded: () => false,
25
+ };
26
+ export const asyncLockDriver = {
27
+ mkdir: (path, opts) => mkdir(path, opts),
28
+ rmdir: (path, opts) => rm(path, opts),
29
+ readFile: (path, enc) => readFile(path, enc),
30
+ writeFile: (path, data, opts) => writeFile(path, data, opts),
31
+ rename: (oldPath, newPath) => rename(oldPath, newPath),
32
+ stat: (path) => stat(path),
33
+ unlink: (path) => rm(path, { force: true }),
34
+ delay: (ms) => new Promise(resolve => setTimeout(resolve, ms)),
35
+ timeExceeded: (deadline) => Date.now() >= deadline,
36
+ setInterval: (fn, ms) => setInterval(fn, ms),
37
+ };
38
+ export function runSync(driver, machine) {
39
+ let result = machine.next();
40
+ while (!result.done) {
41
+ try {
42
+ const value = result.value(driver);
43
+ result = machine.next(value);
44
+ }
45
+ catch (err) {
46
+ result = machine.throw(err);
47
+ }
48
+ }
49
+ return result.value;
50
+ }
51
+ export async function runAsync(driver, machine) {
52
+ let result = machine.next();
53
+ while (!result.done) {
54
+ try {
55
+ const value = await result.value(driver);
56
+ result = machine.next(value);
57
+ }
58
+ catch (err) {
59
+ result = machine.throw(err);
60
+ }
61
+ }
62
+ return result.value;
63
+ }
64
+ function* readLockMetadataMachine(lockPath) {
65
+ let isDir = false;
66
+ try {
67
+ const s = yield (d) => d.stat(lockPath);
68
+ isDir = s.isDirectory();
69
+ }
70
+ catch {
71
+ return undefined;
72
+ }
73
+ const targetFile = isDir ? join(lockPath, LOCK_METADATA_FILE) : lockPath;
74
+ try {
75
+ const raw = yield (d) => d.readFile(targetFile, "utf-8");
76
+ const parsed = JSON.parse(raw);
77
+ if (parsed && typeof parsed === "object") {
78
+ return parsed;
79
+ }
80
+ }
81
+ catch {
82
+ // 忽略异常
83
+ }
84
+ return undefined;
85
+ }
86
+ export async function readLockMetadata(lockPath) {
87
+ return runAsync(asyncLockDriver, readLockMetadataMachine(lockPath));
88
+ }
89
+ export function readLockMetadataSync(lockPath) {
90
+ return runSync(syncLockDriver, readLockMetadataMachine(lockPath));
91
+ }
92
+ function* checkIsStaleLock(lockPath, staleMs, options, clock) {
93
+ const effectiveClock = options?.clock ?? clock;
94
+ let lockAgeMs;
95
+ try {
96
+ const s = yield (d) => d.stat(lockPath);
97
+ lockAgeMs = effectiveClock.now().getTime() - s.mtimeMs;
98
+ }
99
+ catch (err) {
100
+ throw err;
101
+ }
102
+ if (options?.mtimeFirst && lockAgeMs <= staleMs) {
103
+ return false;
104
+ }
105
+ const metadata = yield* readLockMetadataMachine(lockPath);
106
+ if (options?.assertHolderReclaimable && metadata) {
107
+ options.assertHolderReclaimable(metadata);
108
+ }
109
+ if (metadata === undefined) {
110
+ if (lockAgeMs <= staleMs) {
111
+ return false;
112
+ }
113
+ return true;
114
+ }
115
+ if (typeof metadata.pid === "number") {
116
+ if (metadata.pid === process.pid) {
117
+ return !activeLockPaths.has(resolve(lockPath));
118
+ }
119
+ return !isProcessAlive(metadata.pid);
120
+ }
121
+ return lockAgeMs > staleMs;
122
+ }
123
+ export async function isStaleLock(lockPath, staleMs = DEFAULT_FILE_LOCK_STALE_MS, options, clock) {
124
+ return runAsync(asyncLockDriver, checkIsStaleLock(lockPath, staleMs, options, clock ?? defaultClock));
125
+ }
126
+ export function isStaleLockSync(lockPath, staleMs = DEFAULT_FILE_LOCK_STALE_MS, options, clock) {
127
+ return runSync(syncLockDriver, checkIsStaleLock(lockPath, staleMs, options, clock ?? defaultClock));
128
+ }
129
+ export function isLockHeld(lockPath, excludeSelf = true, clock) {
130
+ if (!existsSync(lockPath))
131
+ return false;
132
+ const effectiveClock = clock ?? defaultClock;
133
+ try {
134
+ const meta = readLockMetadataSync(lockPath);
135
+ if (meta && typeof meta.pid === "number") {
136
+ if (excludeSelf && meta.pid === process.pid) {
137
+ return false;
138
+ }
139
+ return isProcessAlive(meta.pid);
140
+ }
141
+ const stat = statSync(lockPath);
142
+ return effectiveClock.now().getTime() - stat.mtimeMs < DEFAULT_FILE_LOCK_STALE_MS;
143
+ }
144
+ catch {
145
+ return false;
146
+ }
147
+ }
148
+ function* safeReleaseMachine(lockPath, expectedToken, expectedSessionToken) {
149
+ try {
150
+ const meta = yield* readLockMetadataMachine(lockPath);
151
+ if (meta) {
152
+ if (expectedSessionToken && meta.sessionToken && meta.sessionToken !== expectedSessionToken)
153
+ return;
154
+ if (expectedToken && meta.token && meta.token !== expectedToken)
155
+ return;
156
+ }
157
+ yield (d) => d.rmdir(lockPath, { recursive: true, force: true });
158
+ }
159
+ catch {
160
+ // 忽略释放异常
161
+ }
162
+ }
163
+ export async function safeReleaseFileLock(lockPath, expectedToken, expectedSessionToken) {
164
+ return runAsync(asyncLockDriver, safeReleaseMachine(lockPath, expectedToken, expectedSessionToken));
165
+ }
166
+ export function safeReleaseFileLockSync(lockPath, expectedToken, expectedSessionToken) {
167
+ return runSync(syncLockDriver, safeReleaseMachine(lockPath, expectedToken, expectedSessionToken));
168
+ }
169
+ function* checkIsReclaimGuardActive(reclaimPath, staleMs, clock) {
170
+ try {
171
+ const raw = yield (d) => d.readFile(join(reclaimPath, "reclaim.json"), "utf-8");
172
+ const meta = JSON.parse(raw);
173
+ if (typeof meta.pid === "number") {
174
+ const age = clock.now().getTime() - (meta.createdAt ?? 0);
175
+ if (age < staleMs && isProcessAlive(meta.pid))
176
+ return true;
177
+ }
178
+ }
179
+ catch { }
180
+ try {
181
+ const s = yield (d) => d.stat(reclaimPath);
182
+ return clock.now().getTime() - s.mtimeMs < staleMs;
183
+ }
184
+ catch {
185
+ return false;
186
+ }
187
+ }
188
+ function* tryAcquireReclaimGuard(reclaimPath, staleMs, clock) {
189
+ try {
190
+ yield (d) => d.mkdir(reclaimPath, { mode: 0o700 });
191
+ yield (d) => d.writeFile(join(reclaimPath, "reclaim.json"), JSON.stringify({ pid: process.pid, createdAt: clock.now().getTime() }), { mode: 0o600 });
192
+ return true;
193
+ }
194
+ catch (err) {
195
+ if (err.code !== "EEXIST")
196
+ throw err;
197
+ }
198
+ const active = yield* checkIsReclaimGuardActive(reclaimPath, staleMs, clock);
199
+ if (!active) {
200
+ try {
201
+ yield (d) => d.rmdir(reclaimPath, { recursive: true, force: true });
202
+ yield (d) => d.mkdir(reclaimPath, { mode: 0o700 });
203
+ yield (d) => d.writeFile(join(reclaimPath, "reclaim.json"), JSON.stringify({ pid: process.pid, createdAt: clock.now().getTime() }), { mode: 0o600 });
204
+ return true;
205
+ }
206
+ catch {
207
+ return false;
208
+ }
209
+ }
210
+ return false;
211
+ }
212
+ function* acquireLockMachine(lockPath, options, isAsync) {
213
+ const clock = options?.clock ?? defaultClock;
214
+ const staleMs = options?.staleMs ?? DEFAULT_FILE_LOCK_STALE_MS;
215
+ const acquireTimeoutMs = options?.acquireTimeoutMs ?? DEFAULT_FILE_LOCK_ACQUIRE_TIMEOUT_MS;
216
+ const retryDelayMs = options?.retryDelayMs ?? DEFAULT_FILE_LOCK_RETRY_DELAY_MS;
217
+ const heartbeatMs = options?.heartbeatMs ?? DEFAULT_FILE_LOCK_HEARTBEAT_MS;
218
+ const deadline = clock.monotonic() + acquireTimeoutMs;
219
+ const resolvedPath = resolve(lockPath);
220
+ const reclaimPath = `${lockPath}.reclaim`;
221
+ const parentDir = dirname(lockPath);
222
+ try {
223
+ yield (d) => d.mkdir(parentDir, { recursive: true });
224
+ }
225
+ catch { }
226
+ let token = "";
227
+ let metadata;
228
+ let reclaimMetadataWritten = false;
229
+ let acquired = false;
230
+ while (!acquired) {
231
+ const guardActive = yield* checkIsReclaimGuardActive(reclaimPath, staleMs, clock);
232
+ if (guardActive) {
233
+ if (!isAsync) {
234
+ if (options?.createLockError) {
235
+ throw options.createLockError(`Lock '${lockPath}' is currently being reclaimed by another active process`);
236
+ }
237
+ throw new ActionDockError(STORAGE_BUSY, `Lock '${lockPath}' is currently being reclaimed by another active process`);
238
+ }
239
+ }
240
+ else {
241
+ try {
242
+ yield (d) => d.mkdir(lockPath, { mode: 0o700 });
243
+ activeLockPaths.add(resolvedPath);
244
+ acquired = true;
245
+ }
246
+ catch (err) {
247
+ if (err.code !== "EEXIST") {
248
+ throw err;
249
+ }
250
+ }
251
+ }
252
+ if (!acquired) {
253
+ let stale = false;
254
+ try {
255
+ stale = yield* checkIsStaleLock(lockPath, staleMs, options, clock);
256
+ }
257
+ catch (err) {
258
+ if (err.code !== "ENOENT")
259
+ throw err;
260
+ }
261
+ if (stale) {
262
+ const guardAcquired = yield* tryAcquireReclaimGuard(reclaimPath, staleMs, clock);
263
+ if (!guardAcquired) {
264
+ if (!isAsync) {
265
+ if (options?.createLockError) {
266
+ throw options.createLockError(`Lock '${lockPath}' is currently held by another active process`);
267
+ }
268
+ throw new ActionDockError(STORAGE_BUSY, `Lock '${lockPath}' is currently held by another active process`);
269
+ }
270
+ }
271
+ else {
272
+ try {
273
+ let stillStale = false;
274
+ try {
275
+ stillStale = yield* checkIsStaleLock(lockPath, staleMs, options, clock);
276
+ }
277
+ catch (err) {
278
+ if (err.code === "ENOENT")
279
+ stillStale = true;
280
+ else
281
+ throw err;
282
+ }
283
+ if (!stillStale) {
284
+ if (options?.createLockError) {
285
+ throw options.createLockError(`Lock '${lockPath}' is currently held by another active process`);
286
+ }
287
+ throw new ActionDockError(STORAGE_BUSY, `Lock '${lockPath}' is currently held by another active process`);
288
+ }
289
+ let isDir = false;
290
+ try {
291
+ const s = yield (d) => d.stat(lockPath);
292
+ isDir = s.isDirectory();
293
+ }
294
+ catch { }
295
+ if (!isDir) {
296
+ try {
297
+ yield (d) => d.unlink(lockPath);
298
+ }
299
+ catch { }
300
+ try {
301
+ yield (d) => d.mkdir(lockPath, { mode: 0o700 });
302
+ }
303
+ catch (err) {
304
+ if (err.code !== "EEXIST")
305
+ throw err;
306
+ }
307
+ }
308
+ // 在接管守卫独占保护下写入并核验元数据,杜绝外部并发读取到残余陈旧元数据
309
+ token = randomUUID();
310
+ metadata = {
311
+ pid: process.pid,
312
+ createdAt: clock.now().toISOString(),
313
+ ...options?.metadata,
314
+ token,
315
+ };
316
+ const metaPath = join(lockPath, LOCK_METADATA_FILE);
317
+ const tmpPath = join(lockPath, `meta.tmp.${process.pid}.${randomUUID().slice(0, 8)}`);
318
+ yield (d) => d.writeFile(tmpPath, JSON.stringify(metadata, null, 2), { mode: 0o600 });
319
+ yield (d) => d.rename(tmpPath, metaPath);
320
+ const raw = yield (d) => d.readFile(metaPath, "utf-8");
321
+ const verify = JSON.parse(raw);
322
+ if (verify?.token !== token) {
323
+ if (options?.createLockError) {
324
+ throw options.createLockError(`Failed to verify acquired lock ownership in '${lockPath}'`);
325
+ }
326
+ throw new ActionDockError(STORAGE_BUSY, `Failed to verify acquired lock ownership in '${lockPath}'`);
327
+ }
328
+ activeLockPaths.add(resolvedPath);
329
+ acquired = true;
330
+ reclaimMetadataWritten = true;
331
+ }
332
+ finally {
333
+ try {
334
+ yield (d) => d.rmdir(reclaimPath, { recursive: true, force: true });
335
+ }
336
+ catch { }
337
+ }
338
+ }
339
+ }
340
+ }
341
+ if (!acquired) {
342
+ if (isAsync) {
343
+ const exceeded = clock.monotonic() >= deadline;
344
+ if (exceeded) {
345
+ if (options?.createLockError) {
346
+ throw options.createLockError(`Failed to acquire lock '${lockPath}' within ${acquireTimeoutMs}ms`);
347
+ }
348
+ throw new ActionDockError(TIMEOUT, `Failed to acquire lock '${lockPath}' within ${acquireTimeoutMs}ms`);
349
+ }
350
+ yield () => clock.sleep(retryDelayMs);
351
+ }
352
+ else {
353
+ if (options?.createLockError) {
354
+ throw options.createLockError(`Lock '${lockPath}' is currently held by another active process`);
355
+ }
356
+ throw new ActionDockError(STORAGE_BUSY, `Lock '${lockPath}' is currently held by another active process`);
357
+ }
358
+ }
359
+ }
360
+ if (!reclaimMetadataWritten) {
361
+ token = randomUUID();
362
+ metadata = {
363
+ pid: process.pid,
364
+ createdAt: clock.now().toISOString(),
365
+ ...options?.metadata,
366
+ token,
367
+ };
368
+ const metaPath = join(lockPath, LOCK_METADATA_FILE);
369
+ const tmpPath = join(lockPath, `meta.tmp.${process.pid}.${randomUUID().slice(0, 8)}`);
370
+ try {
371
+ yield (d) => d.writeFile(tmpPath, JSON.stringify(metadata, null, 2), { mode: 0o600 });
372
+ yield (d) => d.rename(tmpPath, metaPath);
373
+ const raw = yield (d) => d.readFile(metaPath, "utf-8");
374
+ const verify = JSON.parse(raw);
375
+ if (verify?.token !== token) {
376
+ throw new Error("Lock metadata verification failed");
377
+ }
378
+ }
379
+ catch (err) {
380
+ activeLockPaths.delete(resolvedPath);
381
+ try {
382
+ yield (d) => d.rmdir(lockPath, { recursive: true, force: true });
383
+ }
384
+ catch { }
385
+ if (options?.createLockError) {
386
+ throw options.createLockError(`Failed to write or verify lock metadata for '${lockPath}'`);
387
+ }
388
+ throw new ActionDockError(STORAGE_BUSY, `Failed to write or verify lock metadata for '${lockPath}'`);
389
+ }
390
+ }
391
+ let timer;
392
+ if (isAsync && heartbeatMs > 0) {
393
+ timer = yield (d) => {
394
+ if (d.setInterval) {
395
+ return d.setInterval(() => {
396
+ const now = clock.now();
397
+ utimes(lockPath, now, now).catch(() => { });
398
+ }, heartbeatMs);
399
+ }
400
+ };
401
+ if (timer?.unref) {
402
+ timer.unref();
403
+ }
404
+ }
405
+ let released = false;
406
+ const release = () => {
407
+ if (released)
408
+ return;
409
+ released = true;
410
+ if (timer) {
411
+ clearInterval(timer);
412
+ }
413
+ activeLockPaths.delete(resolvedPath);
414
+ };
415
+ return {
416
+ lockPath,
417
+ metadata: metadata,
418
+ release,
419
+ };
420
+ }
421
+ export function acquireFileLockSync(lockPath, options) {
422
+ const handle = runSync(syncLockDriver, acquireLockMachine(lockPath, options, false));
423
+ const origRelease = handle.release;
424
+ handle.release = () => {
425
+ origRelease();
426
+ safeReleaseFileLockSync(lockPath, handle.metadata.token, handle.metadata.sessionToken);
427
+ };
428
+ return handle;
429
+ }
430
+ export async function acquireFileLock(lockPath, options) {
431
+ const handle = await runAsync(asyncLockDriver, acquireLockMachine(lockPath, options, true));
432
+ const origRelease = handle.release;
433
+ handle.release = async () => {
434
+ origRelease();
435
+ await safeReleaseFileLock(lockPath, handle.metadata.token, handle.metadata.sessionToken);
436
+ };
437
+ return handle;
438
+ }
439
+ export async function withFileLock(lockPath, fn, options) {
440
+ const lock = await acquireFileLock(lockPath, options);
441
+ try {
442
+ return await fn();
443
+ }
444
+ finally {
445
+ await lock.release();
446
+ }
447
+ }
@@ -1,9 +1,9 @@
1
1
  import type { RuntimeStorage } from "./types.js";
2
2
  export { DataDirLock, type DataDirLockInfo, isProcessAlive, } from "./data-dir-lock.js";
3
3
  export * from "./clock.js";
4
- export * from "./driver.js";
4
+ export { NodeSqliteDriver } from "./sqlite-driver.js";
5
+ export { normalizeSqliteParams } from "./params.js";
5
6
  export * from "./sqlite-driver.js";
6
- export * from "./lazy.js";
7
7
  export * from "./mask.js";
8
8
  export * from "./params.js";
9
9
  export * from "./sqlite.js";
@@ -1,11 +1,12 @@
1
1
  import { join, relative, resolve } from "node:path";
2
2
  import { assertValidPackageId, getActionDockHome, isPathOutsideBoundary } from "../utils/index.js";
3
+ import { ActionDockError, PATH_TRAVERSAL } from "../errors.js";
3
4
  import { SqliteRuntimeStorage } from "./sqlite.js";
4
5
  export { DataDirLock, isProcessAlive, } from "./data-dir-lock.js";
5
6
  export * from "./clock.js";
6
- export * from "./driver.js";
7
+ export { NodeSqliteDriver } from "./sqlite-driver.js";
8
+ export { normalizeSqliteParams } from "./params.js";
7
9
  export * from "./sqlite-driver.js";
8
- export * from "./lazy.js";
9
10
  export * from "./mask.js";
10
11
  export * from "./params.js";
11
12
  export * from "./sqlite.js";
@@ -34,7 +35,7 @@ export function resolveDatabasePath(packageId, options = {}) {
34
35
  const dbPath = resolve(rootDir, safePkgPath, "runtime.db");
35
36
  const rel = relative(rootDir, dbPath);
36
37
  if (isPathOutsideBoundary(rel)) {
37
- throw new Error(`'database dataDir path' escapes boundary '${rootDir}': ${dbPath}`);
38
+ throw new ActionDockError(PATH_TRAVERSAL, `'database dataDir path' escapes boundary '${rootDir}': ${dbPath}`);
38
39
  }
39
40
  return dbPath;
40
41
  }
@@ -43,7 +44,7 @@ export function resolveDatabasePath(packageId, options = {}) {
43
44
  const dbPath = resolve(rootDir, safePkgPath, "runtime.db");
44
45
  const rel = relative(rootDir, dbPath);
45
46
  if (isPathOutsideBoundary(rel)) {
46
- throw new Error(`'database storage path' escapes boundary '${rootDir}': ${dbPath}`);
47
+ throw new ActionDockError(PATH_TRAVERSAL, `'database storage path' escapes boundary '${rootDir}': ${dbPath}`);
47
48
  }
48
49
  return dbPath;
49
50
  }
@@ -0,0 +1,48 @@
1
+ import type { Clock } from "./clock.js";
2
+ /**
3
+ * 无进程标识的遗留记录心跳过期阈值(毫秒)。
4
+ *
5
+ * 遗留记录(未落库宿主进程标识的旧版本写入)无法做进程存活探测,只能依赖
6
+ * 心跳时间戳兜底判定。阈值必须显著大于执行服务的心跳刷新间隔
7
+ * (RUN_HEARTBEAT_INTERVAL_MS),保证心跳正常的宿主绝不被误判过期;
8
+ * 同时保持足够小,使真正崩溃后的遗留记录能在下次打开时被及时收敛。
9
+ */
10
+ export declare const RUN_LIVENESS_GRACE_MS = 90000;
11
+ /**
12
+ * 候选收割记录的最小判定输入。
13
+ *
14
+ * 只取判定所需字段,避免收割判定逻辑耦合完整运行记录投影。
15
+ */
16
+ export interface LivenessCandidate {
17
+ /** 运行标识 */
18
+ id: string;
19
+ /** 运行生命周期状态(可选,终态记录不属于宿主死亡异常) */
20
+ status?: string;
21
+ /** 写入方宿主进程标识(遗留记录可能缺失) */
22
+ hostPid?: number | null;
23
+ /** 最后一次心跳刷新时间(ISO 8601) */
24
+ heartbeatAt?: string | null;
25
+ /** 运行开始时间(ISO 8601) */
26
+ startedAt?: string | null;
27
+ }
28
+ /**
29
+ * 进程存活探测函数契约(便于测试注入确定性替身)。
30
+ */
31
+ export type ProcessLivenessProbe = (pid: number) => boolean;
32
+ /**
33
+ * 判定单条运行记录的执行宿主是否确认死亡。
34
+ *
35
+ * 存活判断核心准则:
36
+ * - 终态记录不属于「宿主死亡」的在途异常,返回 false;
37
+ * - 携带合法 hostPid 时以进程探测(process.kill(pid, 0))为准,进程明确不存在(ESRCH)才判死;
38
+ * - 无 hostPid 时仅在存在显式 heartbeatAt 且超时超过宽限期才判死;
39
+ * - 缺少可靠存活依据时,严禁凭「开始时间超过九十秒」认定死亡,保守返回 false。
40
+ *
41
+ * @param candidate 候选记录
42
+ * @param options 可选注入:进程探测替身、时钟替身与心跳宽限期
43
+ */
44
+ export declare function isRunHostDead(candidate: LivenessCandidate, options?: {
45
+ probe?: ProcessLivenessProbe;
46
+ clock?: Clock;
47
+ graceMs?: number;
48
+ }): boolean;
@@ -0,0 +1,44 @@
1
+ import { isProcessAlive } from "../utils/index.js";
2
+ import { isTerminalRunStatus } from "./types.js";
3
+ /**
4
+ * 无进程标识的遗留记录心跳过期阈值(毫秒)。
5
+ *
6
+ * 遗留记录(未落库宿主进程标识的旧版本写入)无法做进程存活探测,只能依赖
7
+ * 心跳时间戳兜底判定。阈值必须显著大于执行服务的心跳刷新间隔
8
+ * (RUN_HEARTBEAT_INTERVAL_MS),保证心跳正常的宿主绝不被误判过期;
9
+ * 同时保持足够小,使真正崩溃后的遗留记录能在下次打开时被及时收敛。
10
+ */
11
+ export const RUN_LIVENESS_GRACE_MS = 90_000;
12
+ /**
13
+ * 判定单条运行记录的执行宿主是否确认死亡。
14
+ *
15
+ * 存活判断核心准则:
16
+ * - 终态记录不属于「宿主死亡」的在途异常,返回 false;
17
+ * - 携带合法 hostPid 时以进程探测(process.kill(pid, 0))为准,进程明确不存在(ESRCH)才判死;
18
+ * - 无 hostPid 时仅在存在显式 heartbeatAt 且超时超过宽限期才判死;
19
+ * - 缺少可靠存活依据时,严禁凭「开始时间超过九十秒」认定死亡,保守返回 false。
20
+ *
21
+ * @param candidate 候选记录
22
+ * @param options 可选注入:进程探测替身、时钟替身与心跳宽限期
23
+ */
24
+ export function isRunHostDead(candidate, options) {
25
+ if (candidate.status && isTerminalRunStatus(candidate.status)) {
26
+ return false;
27
+ }
28
+ const pid = candidate.hostPid;
29
+ if (typeof pid === "number" && Number.isInteger(pid) && pid > 0) {
30
+ const probe = options?.probe ?? isProcessAlive;
31
+ // 存活(含权限受限的 EPERM 保守存活)一律保留;仅明确的进程不存在才判死
32
+ return !probe(pid);
33
+ }
34
+ if (candidate.heartbeatAt) {
35
+ const heartbeatMs = Date.parse(candidate.heartbeatAt);
36
+ if (!Number.isNaN(heartbeatMs)) {
37
+ const graceMs = options?.graceMs ?? RUN_LIVENESS_GRACE_MS;
38
+ const nowMs = options?.clock ? options.clock.now().getTime() : Date.now();
39
+ return nowMs - heartbeatMs > graceMs;
40
+ }
41
+ }
42
+ // 缺少可靠依据(既无有效 hostPid 也无有效 heartbeatAt):绝不凭 startedAt 认定死亡
43
+ return false;
44
+ }
@@ -0,0 +1,16 @@
1
+ import type { SqliteDriver } from "./types.js";
2
+ import type { Clock } from "./clock.js";
3
+ export declare class SqliteConfigStore {
4
+ private statementCache;
5
+ private driver;
6
+ private packageId;
7
+ private dbPath;
8
+ private clock;
9
+ constructor(driver: SqliteDriver, packageId: string, dbPath: string, clock: Clock);
10
+ private getStatement;
11
+ getConfig<T = unknown>(key: string): T | undefined;
12
+ listConfig(): Record<string, unknown>;
13
+ setConfig(key: string, value: unknown): void;
14
+ deleteConfig(key: string): boolean;
15
+ close(): void;
16
+ }