@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
@@ -1,4 +1,22 @@
1
- import { CONTROL_REVOKED, INPUT_CLOSED, INPUT_OUTCOME_UNKNOWN, PROCESS_QUARANTINED, QUEUE_FULL, ProcessError, } from "../errors.js";
1
+ import { ActionDockError, CONTROL_REVOKED, INPUT_CLOSED, INPUT_OUTCOME_UNKNOWN, INVALID_STATE, PROCESS_QUARANTINED, ProcessError, } from "../errors.js";
2
+ /**
3
+ * 断言进程仍处于可交互(非隔离、非终态)状态。
4
+ */
5
+ function assertProcessOperable(proc) {
6
+ if (proc.info.control === "quarantined") {
7
+ throw new ProcessError(PROCESS_QUARANTINED, "Process is in quarantined state");
8
+ }
9
+ const state = proc.info.state;
10
+ if (state === "exited" || state === "failed" || state === "lost") {
11
+ throw new ProcessError(INPUT_CLOSED, `Process is no longer operable (state: ${state})`);
12
+ }
13
+ }
14
+ /**
15
+ * 进程是否已进入不可交互的终态。
16
+ */
17
+ function isProcessTerminal(proc) {
18
+ return proc.info.control === "quarantined" || proc.info.control === "closed";
19
+ }
2
20
  /**
3
21
  * 输入调度器。
4
22
  *
@@ -11,74 +29,47 @@ import { CONTROL_REVOKED, INPUT_CLOSED, INPUT_OUTCOME_UNKNOWN, PROCESS_QUARANTIN
11
29
  */
12
30
  export class InputDispatcher {
13
31
  host;
14
- arbiter;
15
- constructor(host, arbiter) {
32
+ constructor(host) {
16
33
  this.host = host;
17
- this.arbiter = arbiter;
18
34
  }
19
35
  /**
20
- * 入队写入操作:同步阶段完成授权校验、输入通道校验、队列容量检查与字节预扣;
36
+ * 入队写入操作:同步阶段完成授权校验与输入通道校验;
21
37
  * 异步阶段落盘排队收据并推送队列,随后触发串行调度。
22
38
  *
23
39
  * 返回的 Promise 在收据成功入队后 resolve(state 为 queued);
24
- * 落盘或推送失败时回滚字节预扣并原样抛出。
40
+ * 落盘或推送失败时原样抛出。
25
41
  */
26
42
  async enqueueWrite(proc, input) {
27
- // 入队前校验授权与控制状态
28
- this.arbiter.validateOperation(proc, input.token);
43
+ // 入队前校验进程仍处于可交互状态
44
+ assertProcessOperable(proc);
29
45
  if (proc.inputClosed) {
30
46
  throw new ProcessError(INPUT_CLOSED, "Input stream is closed");
31
47
  }
32
48
  const rawBytes = input.data;
33
- const dataSize = rawBytes.byteLength;
34
- // 待写入队列容量检查:在任何 await 前同步原子校验并预占配额
35
- if (proc.pendingInputBytes + dataSize > this.host.maxPendingQueueBytesPerProcess) {
36
- throw new ProcessError(QUEUE_FULL, "Process pending input queue limit exceeded", {
37
- limit: this.host.maxPendingQueueBytesPerProcess,
38
- });
39
- }
40
- const totalHostPending = this.host.countHostPendingInputBytes();
41
- if (totalHostPending + dataSize > this.host.maxPendingQueueBytesPerHost) {
42
- throw new ProcessError(QUEUE_FULL, "Host pending input queue limit exceeded", {
43
- limit: this.host.maxPendingQueueBytesPerHost,
44
- });
45
- }
46
- // 同步占位预扣队列配额,杜绝并发 await 窗口穿透
47
- proc.pendingInputBytes += dataSize;
48
- let pendingBytesCommitted = false;
49
- try {
50
- const receipt = {
51
- requestId: input.requestId,
52
- state: "queued",
53
- };
54
- await this.host.persistReceipt(input.key, receipt, input.payloadHash);
55
- proc.inputQueue.push({
56
- type: "write",
57
- requestId: input.requestId,
58
- token: input.token,
59
- bytes: rawBytes,
60
- receipt,
61
- key: input.key,
62
- payloadHash: input.payloadHash,
63
- cancelEpoch: proc.cancelEpoch,
64
- });
65
- pendingBytesCommitted = true;
66
- // 异步调度推进
67
- queueMicrotask(() => void this.dispatchNext(proc));
68
- return { ...receipt };
69
- }
70
- finally {
71
- if (!pendingBytesCommitted) {
72
- proc.pendingInputBytes = Math.max(0, proc.pendingInputBytes - dataSize);
73
- }
74
- }
49
+ const receipt = {
50
+ requestId: input.requestId,
51
+ state: "queued",
52
+ };
53
+ await this.host.persistReceipt(input.key, receipt, input.payloadHash);
54
+ proc.inputQueue.push({
55
+ type: "write",
56
+ requestId: input.requestId,
57
+ bytes: rawBytes,
58
+ receipt,
59
+ key: input.key,
60
+ payloadHash: input.payloadHash,
61
+ cancelEpoch: proc.cancelEpoch,
62
+ });
63
+ // 异步调度推进
64
+ queueMicrotask(() => void this.dispatchNext(proc));
65
+ return { ...receipt };
75
66
  }
76
67
  /**
77
68
  * 入队控制指令:异步阶段落盘排队收据并推送队列,随后触发串行调度。
78
69
  */
79
70
  async enqueueControl(proc, input) {
80
- // 入队前校验授权与控制状态
81
- this.arbiter.validateOperation(proc, input.token);
71
+ // 入队前校验进程仍处于可交互状态
72
+ assertProcessOperable(proc);
82
73
  const receipt = {
83
74
  requestId: input.requestId,
84
75
  state: "queued",
@@ -87,7 +78,6 @@ export class InputDispatcher {
87
78
  proc.inputQueue.push({
88
79
  type: "control",
89
80
  requestId: input.requestId,
90
- token: input.token,
91
81
  action: input.action,
92
82
  receipt,
93
83
  key: input.key,
@@ -114,7 +104,6 @@ export class InputDispatcher {
114
104
  void this.host.persistReceipt(op.key, op.receipt, op.payloadHash);
115
105
  }
116
106
  proc.inputQueue = [];
117
- proc.pendingInputBytes = 0;
118
107
  }
119
108
  /**
120
109
  * 串行调度执行输入队列操作。
@@ -130,11 +119,8 @@ export class InputDispatcher {
130
119
  try {
131
120
  while (proc.inputQueue.length > 0) {
132
121
  const op = proc.inputQueue[0];
133
- // dispatch 前校验 token、epoch 与授权(经控制权仲裁器委托复核)
134
- if (proc.info.control !== "held" ||
135
- !proc.currentGrant ||
136
- proc.currentGrant.token !== op.token ||
137
- new Date(proc.currentGrant.expiresAt).getTime() <= Date.now() ||
122
+ // dispatch 前校验进程状态与取消纪元
123
+ if (isProcessTerminal(proc) ||
138
124
  op.cancelEpoch !== proc.cancelEpoch) {
139
125
  if (op.cancelEpoch === proc.cancelEpoch) {
140
126
  op.receipt.state = "failed";
@@ -143,9 +129,6 @@ export class InputDispatcher {
143
129
  await this.host.persistReceipt(op.key, op.receipt, op.payloadHash);
144
130
  }
145
131
  proc.inputQueue.shift();
146
- if (op.bytes && op.cancelEpoch === proc.cancelEpoch) {
147
- proc.pendingInputBytes -= op.bytes.byteLength;
148
- }
149
132
  continue;
150
133
  }
151
134
  op.receipt.state = "dispatching";
@@ -153,7 +136,7 @@ export class InputDispatcher {
153
136
  if (op.type === "write" && op.bytes) {
154
137
  try {
155
138
  if (!proc.handle) {
156
- throw new Error("Missing driver handle");
139
+ throw new ActionDockError(INVALID_STATE, "Missing driver handle");
157
140
  }
158
141
  await proc.handle.write(op.bytes);
159
142
  // 写入返回后校验取消纪元与队首位置:已被 stop 或 quarantine 接管则放弃过期结算
@@ -174,15 +157,14 @@ export class InputDispatcher {
174
157
  op.receipt.errorCode = INPUT_OUTCOME_UNKNOWN;
175
158
  await this.host.persistReceipt(op.key, op.receipt, op.payloadHash);
176
159
  proc.inputQueue.shift();
177
- proc.pendingInputBytes -= op.bytes.byteLength;
178
- await this.host.quarantineProcess(proc.owner, proc.info.id, op.token, "Write failed with uncertain outcome");
160
+ await this.host.quarantineProcess(proc.owner, proc.info.id, "Write failed with uncertain outcome");
179
161
  break;
180
162
  }
181
163
  }
182
164
  else if (op.type === "control" && op.action) {
183
165
  try {
184
166
  if (!proc.handle) {
185
- throw new Error("Missing driver handle");
167
+ throw new ActionDockError(INVALID_STATE, "Missing driver handle");
186
168
  }
187
169
  if (op.action.type === "input-eof") {
188
170
  if (proc.handle.sendInputEOF) {
@@ -228,9 +210,6 @@ export class InputDispatcher {
228
210
  }
229
211
  await this.host.persistReceipt(op.key, op.receipt, op.payloadHash);
230
212
  proc.inputQueue.shift();
231
- if (op.bytes) {
232
- proc.pendingInputBytes -= op.bytes.byteLength;
233
- }
234
213
  }
235
214
  }
236
215
  finally {
@@ -1,8 +1,7 @@
1
- import type { ControlGrant, Limits, OperationReceipt, ProcessControlAction, ProcessInfo } from "@actiondock/sdk";
1
+ import type { Limits, OperationReceipt, ProcessControlAction, ProcessInfo } from "@actiondock/sdk";
2
2
  import type { ProcessDriverHandle, ProcessHandle } from "./driver.js";
3
3
  import type { ProcessRequestKey } from "./metadata-store.js";
4
4
  import type { ProcessOutputLog } from "./output-log.js";
5
- import type { EvictedOutputTombstone } from "./terminal-output-cache.js";
6
5
  /**
7
6
  * 受管进程归属所有者身份。
8
7
  */
@@ -18,7 +17,6 @@ export interface ProcessOwner {
18
17
  export interface QueuedOperation {
19
18
  type: "write" | "control";
20
19
  requestId: string;
21
- token: string;
22
20
  bytes?: Uint8Array;
23
21
  action?: ProcessControlAction;
24
22
  /** 入队时进程的取消纪元,用于 stop 与 dispatch 竞态判定 */
@@ -27,24 +25,11 @@ export interface QueuedOperation {
27
25
  key: ProcessRequestKey;
28
26
  payloadHash: string;
29
27
  }
30
- /**
31
- * 排队等待获取控制权的调用者条目。
32
- */
33
- export interface AcquireWaiter {
34
- requestId: string;
35
- waitMs: number;
36
- ttlMs: number;
37
- runId?: string;
38
- timer?: ReturnType<typeof setTimeout>;
39
- onAbort?: () => void;
40
- resolve: (grant: ControlGrant) => void;
41
- reject: (err: unknown) => void;
42
- }
43
28
  /**
44
29
  * 进程管理器内部维护的活跃受管进程记录。
45
30
  *
46
- * 该结构为进程管理器、控制权仲裁器与输入调度器的共享事实源:
47
- * 控制权与输入队列相关字段经仲裁器与调度器协作推进,禁止绕过二者直接改写。
31
+ * 该结构为进程管理器与输入调度器的共享事实源:
32
+ * 输入队列相关字段经调度器协作推进,禁止绕过调度器直接改写。
48
33
  */
49
34
  export interface ManagedProcessRecord {
50
35
  info: ProcessInfo;
@@ -53,25 +38,12 @@ export interface ManagedProcessRecord {
53
38
  handle?: ProcessHandle & ProcessDriverHandle;
54
39
  outputLog: ProcessOutputLog;
55
40
  outputUnavailable?: boolean;
56
- outputTombstone?: EvictedOutputTombstone;
57
- controlEpoch: number;
58
41
  /** 取消纪元:stop 接管输入队列时递增,用于让挂起中的 dispatch 放弃过期结算 */
59
42
  cancelEpoch: number;
60
- currentGrant?: {
61
- token: string;
62
- epoch: number;
63
- ttlMs: number;
64
- expiresAt: string;
65
- runId?: string;
66
- requestId: string;
67
- };
68
- ttlTimer?: ReturnType<typeof setTimeout>;
69
43
  idleTimer?: ReturnType<typeof setTimeout>;
70
44
  lifetimeTimer?: ReturnType<typeof setTimeout>;
71
45
  drainTimer?: ReturnType<typeof setTimeout>;
72
46
  inputQueue: QueuedOperation[];
73
- pendingInputBytes: number;
74
- acquireWaiters: AcquireWaiter[];
75
47
  inputClosed: boolean;
76
48
  isDispatching: boolean;
77
49
  effectiveLimits: Required<Limits>;
@@ -1,4 +1,3 @@
1
- import type { SqliteDriver } from "../storage/types.js";
2
1
  /**
3
2
  * 受管进程运行状态。
4
3
  */
@@ -163,43 +162,3 @@ export declare class MemoryProcessMetadataStore implements ProcessMetadataStore
163
162
  initializeHost(hostEpoch: string): Promise<number>;
164
163
  clear(): void;
165
164
  }
166
- /**
167
- * SQLite 存储选项。
168
- */
169
- export interface SqliteProcessMetadataStoreOptions {
170
- dbPath?: string;
171
- driver?: SqliteDriver;
172
- }
173
- /**
174
- * 基于 SQLite 的受管进程元数据持久化存储实现。
175
- */
176
- export declare class SqliteProcessMetadataStore implements ProcessMetadataStore {
177
- private driver;
178
- private statementCache;
179
- private isClosed;
180
- constructor(options?: SqliteProcessMetadataStoreOptions);
181
- private initTables;
182
- /**
183
- * 旧库结构迁移:逐列探测并补充新增字段,重复执行安全(幂等)。
184
- */
185
- private migrateSchema;
186
- /**
187
- * 枚举指定表的全部列定义。
188
- */
189
- private listTableColumns;
190
- private getStatement;
191
- saveProcess(process: StoredProcessRecord): Promise<void>;
192
- getProcess(processId: string): Promise<StoredProcessRecord | undefined>;
193
- listProcesses(owner: ProcessOwnerFilter, pageToken?: string, limit?: number): Promise<{
194
- processes: ProcessInfo[];
195
- nextPageToken?: string;
196
- }>;
197
- updateProcessState(processId: string, patch: Partial<ProcessInfo>): Promise<void>;
198
- recordRequest(key: ProcessRequestKey, receipt: OperationReceipt, payloadHash?: string): Promise<void>;
199
- getRequest(key: ProcessRequestKey): Promise<{
200
- receipt: OperationReceipt;
201
- payloadHash?: string;
202
- } | undefined>;
203
- initializeHost(hostEpoch: string): Promise<number>;
204
- close(): void;
205
- }
@@ -1,6 +1,4 @@
1
- import { chmodSync, existsSync, mkdirSync } from "node:fs";
2
- import { dirname } from "node:path";
3
- import { createDefaultSqliteDriver } from "../storage/driver.js";
1
+ import { ActionDockError, PROCESS_NOT_FOUND } from "../errors.js";
4
2
  /**
5
3
  * 解析分页游标偏移量。
6
4
  */
@@ -69,7 +67,7 @@ export class MemoryProcessMetadataStore {
69
67
  async updateProcessState(processId, patch) {
70
68
  const target = this.processes.get(processId);
71
69
  if (!target) {
72
- throw new Error(`Process '${processId}' not found`);
70
+ throw new ActionDockError(PROCESS_NOT_FOUND, `Process '${processId}' not found`);
73
71
  }
74
72
  const ctrl = patch.controlState !== undefined ? patch.controlState : patch.control;
75
73
  const updated = {
@@ -123,340 +121,3 @@ export class MemoryProcessMetadataStore {
123
121
  this.requests.clear();
124
122
  }
125
123
  }
126
- /**
127
- * 将数据库行反序列化为进程模型。
128
- */
129
- function mapRowToStoredProcess(row) {
130
- return {
131
- processId: row.process_id,
132
- tenantId: row.tenant_id,
133
- principalId: row.principal_id,
134
- packageInstanceId: row.package_instance_id,
135
- generationId: row.generation_id,
136
- hostEpoch: row.host_epoch,
137
- state: row.state,
138
- controlState: row.control_state ?? undefined,
139
- control: row.control_state ?? undefined,
140
- ioConfig: row.io_config_json ? JSON.parse(row.io_config_json) : undefined,
141
- capabilities: row.capabilities_json ? JSON.parse(row.capabilities_json) : undefined,
142
- createdAt: row.created_at,
143
- exitCode: row.exit_code !== null && row.exit_code !== undefined ? Number(row.exit_code) : undefined,
144
- exitSignal: row.exit_signal ?? undefined,
145
- endReason: row.end_reason ?? undefined,
146
- outputClosed: Boolean(row.output_closed),
147
- outputEndReason: row.output_end_reason ?? undefined,
148
- inputClosed: Boolean(row.input_closed),
149
- effectiveLimits: row.effective_limits_json ? JSON.parse(row.effective_limits_json) : undefined,
150
- startRequestId: row.start_request_id ?? undefined,
151
- };
152
- }
153
- /**
154
- * 基于 SQLite 的受管进程元数据持久化存储实现。
155
- */
156
- export class SqliteProcessMetadataStore {
157
- driver;
158
- statementCache = new Map();
159
- isClosed = false;
160
- constructor(options = {}) {
161
- const dbPath = options.dbPath || ":memory:";
162
- if (dbPath !== ":memory:") {
163
- const dir = dirname(dbPath);
164
- if (!existsSync(dir)) {
165
- try {
166
- mkdirSync(dir, { recursive: true, mode: 0o700 });
167
- chmodSync(dir, 0o700);
168
- }
169
- catch {
170
- // 忽略系统权限配置失败
171
- }
172
- }
173
- }
174
- this.driver = options.driver ?? createDefaultSqliteDriver(dbPath);
175
- if (dbPath !== ":memory:" && existsSync(dbPath)) {
176
- try {
177
- chmodSync(dbPath, 0o600);
178
- }
179
- catch {
180
- // 忽略文件权限配置异常
181
- }
182
- }
183
- this.initTables();
184
- }
185
- initTables() {
186
- this.driver.exec("PRAGMA journal_mode = WAL;");
187
- this.driver.exec("PRAGMA synchronous = NORMAL;");
188
- this.driver.exec(`
189
- CREATE TABLE IF NOT EXISTS managed_processes (
190
- process_id TEXT PRIMARY KEY,
191
- tenant_id TEXT NOT NULL,
192
- principal_id TEXT NOT NULL,
193
- package_instance_id TEXT NOT NULL,
194
- generation_id TEXT NOT NULL,
195
- host_epoch TEXT NOT NULL,
196
- state TEXT NOT NULL,
197
- control_state TEXT,
198
- io_config_json TEXT,
199
- capabilities_json TEXT,
200
- created_at TEXT NOT NULL,
201
- exit_code INTEGER,
202
- exit_signal TEXT,
203
- end_reason TEXT,
204
- output_closed INTEGER NOT NULL DEFAULT 0,
205
- output_end_reason TEXT,
206
- input_closed INTEGER NOT NULL DEFAULT 0,
207
- effective_limits_json TEXT,
208
- start_request_id TEXT
209
- );
210
-
211
- CREATE INDEX IF NOT EXISTS idx_processes_owner_created
212
- ON managed_processes(tenant_id, principal_id, package_instance_id, generation_id, created_at DESC, process_id ASC);
213
-
214
- CREATE INDEX IF NOT EXISTS idx_processes_host_state
215
- ON managed_processes(host_epoch, state);
216
-
217
- CREATE INDEX IF NOT EXISTS idx_processes_start_req
218
- ON managed_processes(start_request_id);
219
-
220
- CREATE TABLE IF NOT EXISTS process_requests (
221
- host_epoch TEXT NOT NULL,
222
- scope TEXT NOT NULL,
223
- process_id TEXT NOT NULL DEFAULT '',
224
- request_id TEXT NOT NULL,
225
- receipt_json TEXT NOT NULL,
226
- payload_hash TEXT,
227
- created_at TEXT NOT NULL,
228
- PRIMARY KEY (host_epoch, scope, process_id, request_id)
229
- );
230
- `);
231
- this.migrateSchema();
232
- }
233
- /**
234
- * 旧库结构迁移:逐列探测并补充新增字段,重复执行安全(幂等)。
235
- */
236
- migrateSchema() {
237
- const columns = new Set();
238
- for (const row of this.listTableColumns("managed_processes")) {
239
- columns.add(String(row.name));
240
- }
241
- if (!columns.has("input_closed")) {
242
- this.driver.exec("ALTER TABLE managed_processes ADD COLUMN input_closed INTEGER NOT NULL DEFAULT 0;");
243
- }
244
- }
245
- /**
246
- * 枚举指定表的全部列定义。
247
- */
248
- listTableColumns(tableName) {
249
- try {
250
- const stmt = this.driver.prepare(`PRAGMA table_info(${tableName})`);
251
- return stmt.all() ?? [];
252
- }
253
- catch {
254
- return [];
255
- }
256
- }
257
- getStatement(sql) {
258
- let stmt = this.statementCache.get(sql);
259
- if (!stmt) {
260
- stmt = this.driver.prepare(sql);
261
- this.statementCache.set(sql, stmt);
262
- }
263
- return stmt;
264
- }
265
- async saveProcess(process) {
266
- if (this.isClosed) {
267
- throw new Error("SqliteProcessMetadataStore is closed");
268
- }
269
- const stmt = this.getStatement(`
270
- INSERT INTO managed_processes (
271
- process_id, tenant_id, principal_id, package_instance_id, generation_id,
272
- host_epoch, state, control_state, io_config_json, capabilities_json,
273
- created_at, exit_code, exit_signal, end_reason, output_closed,
274
- output_end_reason, input_closed, effective_limits_json, start_request_id
275
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
276
- ON CONFLICT(process_id) DO UPDATE SET
277
- tenant_id = excluded.tenant_id,
278
- principal_id = excluded.principal_id,
279
- package_instance_id = excluded.package_instance_id,
280
- generation_id = excluded.generation_id,
281
- host_epoch = excluded.host_epoch,
282
- state = excluded.state,
283
- control_state = excluded.control_state,
284
- io_config_json = excluded.io_config_json,
285
- capabilities_json = excluded.capabilities_json,
286
- created_at = excluded.created_at,
287
- exit_code = excluded.exit_code,
288
- exit_signal = excluded.exit_signal,
289
- end_reason = excluded.end_reason,
290
- output_closed = excluded.output_closed,
291
- output_end_reason = excluded.output_end_reason,
292
- input_closed = excluded.input_closed,
293
- effective_limits_json = excluded.effective_limits_json,
294
- start_request_id = excluded.start_request_id
295
- `);
296
- const createdAt = process.createdAt ?? new Date().toISOString();
297
- const ctrlState = process.controlState ?? process.control ?? null;
298
- const ioConfigJson = process.ioConfig !== undefined ? JSON.stringify(process.ioConfig) : null;
299
- const capabilitiesJson = process.capabilities !== undefined ? JSON.stringify(process.capabilities) : null;
300
- const effectiveLimitsJson = process.effectiveLimits !== undefined ? JSON.stringify(process.effectiveLimits) : null;
301
- const outputClosed = process.outputClosed ? 1 : 0;
302
- const inputClosed = process.inputClosed ? 1 : 0;
303
- stmt.run(process.processId, process.tenantId, process.principalId, process.packageInstanceId, process.generationId, process.hostEpoch, process.state, ctrlState, ioConfigJson, capabilitiesJson, createdAt, process.exitCode !== undefined && process.exitCode !== null ? process.exitCode : null, process.exitSignal !== undefined ? process.exitSignal : null, process.endReason !== undefined ? process.endReason : null, outputClosed, process.outputEndReason !== undefined ? process.outputEndReason : null, inputClosed, effectiveLimitsJson, process.startRequestId !== undefined ? process.startRequestId : null);
304
- }
305
- async getProcess(processId) {
306
- if (this.isClosed) {
307
- throw new Error("SqliteProcessMetadataStore is closed");
308
- }
309
- const stmt = this.getStatement("SELECT * FROM managed_processes WHERE process_id = ?");
310
- const row = stmt.get(processId);
311
- if (!row) {
312
- return undefined;
313
- }
314
- return mapRowToStoredProcess(row);
315
- }
316
- async listProcesses(owner, pageToken, limit) {
317
- if (this.isClosed) {
318
- throw new Error("SqliteProcessMetadataStore is closed");
319
- }
320
- const actualLimit = typeof limit === "number" && limit > 0 ? limit : 50;
321
- const offset = parsePageToken(pageToken);
322
- const stmt = this.getStatement(`
323
- SELECT * FROM managed_processes
324
- WHERE tenant_id = ? AND principal_id = ? AND package_instance_id = ? AND generation_id = ?
325
- ORDER BY created_at DESC, process_id ASC
326
- LIMIT ? OFFSET ?
327
- `);
328
- const rows = stmt.all(owner.tenantId, owner.principalId, owner.packageInstanceId, owner.generationId, actualLimit + 1, offset);
329
- const hasMore = rows.length > actualLimit;
330
- const resultRows = hasMore ? rows.slice(0, actualLimit) : rows;
331
- const processes = resultRows.map(mapRowToStoredProcess);
332
- const nextPageToken = hasMore ? String(offset + actualLimit) : undefined;
333
- return { processes, nextPageToken };
334
- }
335
- async updateProcessState(processId, patch) {
336
- if (this.isClosed) {
337
- throw new Error("SqliteProcessMetadataStore is closed");
338
- }
339
- const sets = [];
340
- const params = [];
341
- if (patch.state !== undefined) {
342
- sets.push("state = ?");
343
- params.push(patch.state);
344
- }
345
- const ctrl = patch.controlState !== undefined ? patch.controlState : patch.control;
346
- if (ctrl !== undefined) {
347
- sets.push("control_state = ?");
348
- params.push(ctrl);
349
- }
350
- if (patch.exitCode !== undefined) {
351
- sets.push("exit_code = ?");
352
- params.push(patch.exitCode !== null ? patch.exitCode : null);
353
- }
354
- if (patch.exitSignal !== undefined) {
355
- sets.push("exit_signal = ?");
356
- params.push(patch.exitSignal);
357
- }
358
- if (patch.endReason !== undefined) {
359
- sets.push("end_reason = ?");
360
- params.push(patch.endReason);
361
- }
362
- if (patch.outputClosed !== undefined) {
363
- sets.push("output_closed = ?");
364
- params.push(patch.outputClosed ? 1 : 0);
365
- }
366
- if (patch.outputEndReason !== undefined) {
367
- sets.push("output_end_reason = ?");
368
- params.push(patch.outputEndReason);
369
- }
370
- if (patch.inputClosed !== undefined) {
371
- sets.push("input_closed = ?");
372
- params.push(patch.inputClosed ? 1 : 0);
373
- }
374
- if (patch.effectiveLimits !== undefined) {
375
- sets.push("effective_limits_json = ?");
376
- params.push(patch.effectiveLimits !== null ? JSON.stringify(patch.effectiveLimits) : null);
377
- }
378
- if (patch.ioConfig !== undefined) {
379
- sets.push("io_config_json = ?");
380
- params.push(patch.ioConfig !== null ? JSON.stringify(patch.ioConfig) : null);
381
- }
382
- if (patch.capabilities !== undefined) {
383
- sets.push("capabilities_json = ?");
384
- params.push(patch.capabilities !== null ? JSON.stringify(patch.capabilities) : null);
385
- }
386
- if (patch.hostEpoch !== undefined) {
387
- sets.push("host_epoch = ?");
388
- params.push(patch.hostEpoch);
389
- }
390
- if (sets.length === 0) {
391
- return;
392
- }
393
- params.push(processId);
394
- const sql = `UPDATE managed_processes SET ${sets.join(", ")} WHERE process_id = ?`;
395
- const stmt = this.getStatement(sql);
396
- const res = stmt.run(...params);
397
- if (res.changes === 0) {
398
- throw new Error(`Process '${processId}' not found`);
399
- }
400
- }
401
- async recordRequest(key, receipt, payloadHash) {
402
- if (this.isClosed) {
403
- throw new Error("SqliteProcessMetadataStore is closed");
404
- }
405
- const stmt = this.getStatement(`
406
- INSERT INTO process_requests (
407
- host_epoch, scope, process_id, request_id, receipt_json, payload_hash, created_at
408
- ) VALUES (?, ?, ?, ?, ?, ?, ?)
409
- ON CONFLICT(host_epoch, scope, process_id, request_id) DO UPDATE SET
410
- receipt_json = excluded.receipt_json,
411
- payload_hash = excluded.payload_hash,
412
- created_at = excluded.created_at
413
- `);
414
- const processId = key.processId ?? "";
415
- const receiptJson = JSON.stringify(receipt);
416
- const createdAt = new Date().toISOString();
417
- stmt.run(key.hostEpoch, key.scope, processId, key.requestId, receiptJson, payloadHash ?? null, createdAt);
418
- }
419
- async getRequest(key) {
420
- if (this.isClosed) {
421
- throw new Error("SqliteProcessMetadataStore is closed");
422
- }
423
- const stmt = this.getStatement(`
424
- SELECT receipt_json, payload_hash FROM process_requests
425
- WHERE host_epoch = ? AND scope = ? AND process_id = ? AND request_id = ?
426
- `);
427
- const processId = key.processId ?? "";
428
- const row = stmt.get(key.hostEpoch, key.scope, processId, key.requestId);
429
- if (!row) {
430
- return undefined;
431
- }
432
- return {
433
- receipt: JSON.parse(row.receipt_json),
434
- payloadHash: row.payload_hash ?? undefined,
435
- };
436
- }
437
- async initializeHost(hostEpoch) {
438
- if (this.isClosed) {
439
- return 0;
440
- }
441
- const stmt = this.getStatement(`
442
- UPDATE managed_processes
443
- SET state = 'lost',
444
- control_state = 'closed',
445
- end_reason = 'host-lost',
446
- output_closed = 1,
447
- output_end_reason = 'host-lost'
448
- WHERE host_epoch != ?
449
- AND state IN ('starting', 'running', 'stopping')
450
- `);
451
- const res = stmt.run(hostEpoch);
452
- return res.changes;
453
- }
454
- close() {
455
- if (this.isClosed) {
456
- return;
457
- }
458
- this.isClosed = true;
459
- this.statementCache.clear();
460
- this.driver.close();
461
- }
462
- }