@senpi/runtime 3.0.59 → 3.0.63

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 (151) hide show
  1. package/README.md +73 -2
  2. package/dist/actions/decision-engine.d.ts +0 -2
  3. package/dist/actions/decision-engine.d.ts.map +1 -1
  4. package/dist/actions/decision-engine.js +1 -79
  5. package/dist/actions/decision-engine.js.map +1 -1
  6. package/dist/cli/deploy-render.d.ts +91 -0
  7. package/dist/cli/deploy-render.d.ts.map +1 -0
  8. package/dist/cli/deploy-render.js +216 -0
  9. package/dist/cli/deploy-render.js.map +1 -0
  10. package/dist/cli/guide-commands.d.ts.map +1 -1
  11. package/dist/cli/guide-commands.js +78 -6
  12. package/dist/cli/guide-commands.js.map +1 -1
  13. package/dist/cli/senpi-commands.d.ts +6 -1
  14. package/dist/cli/senpi-commands.d.ts.map +1 -1
  15. package/dist/cli/senpi-commands.js +244 -8
  16. package/dist/cli/senpi-commands.js.map +1 -1
  17. package/dist/cli/validate-command.d.ts.map +1 -1
  18. package/dist/cli/validate-command.js +32 -1
  19. package/dist/cli/validate-command.js.map +1 -1
  20. package/dist/deploy/deadlines.d.ts +47 -0
  21. package/dist/deploy/deadlines.d.ts.map +1 -0
  22. package/dist/deploy/deadlines.js +56 -0
  23. package/dist/deploy/deadlines.js.map +1 -0
  24. package/dist/deploy/error-text.d.ts +25 -0
  25. package/dist/deploy/error-text.d.ts.map +1 -0
  26. package/dist/deploy/error-text.js +42 -0
  27. package/dist/deploy/error-text.js.map +1 -0
  28. package/dist/deploy/funding.d.ts +87 -0
  29. package/dist/deploy/funding.d.ts.map +1 -0
  30. package/dist/deploy/funding.js +229 -0
  31. package/dist/deploy/funding.js.map +1 -0
  32. package/dist/deploy/install-gate-runner.d.ts +12 -0
  33. package/dist/deploy/install-gate-runner.d.ts.map +1 -0
  34. package/dist/deploy/install-gate-runner.js +17 -0
  35. package/dist/deploy/install-gate-runner.js.map +1 -0
  36. package/dist/deploy/job.d.ts +199 -0
  37. package/dist/deploy/job.d.ts.map +1 -0
  38. package/dist/deploy/job.js +463 -0
  39. package/dist/deploy/job.js.map +1 -0
  40. package/dist/deploy/min-budget.d.ts +68 -0
  41. package/dist/deploy/min-budget.d.ts.map +1 -0
  42. package/dist/deploy/min-budget.js +297 -0
  43. package/dist/deploy/min-budget.js.map +1 -0
  44. package/dist/deploy/orchestrator.d.ts +305 -0
  45. package/dist/deploy/orchestrator.d.ts.map +1 -0
  46. package/dist/deploy/orchestrator.js +2910 -0
  47. package/dist/deploy/orchestrator.js.map +1 -0
  48. package/dist/deploy/package.d.ts +130 -0
  49. package/dist/deploy/package.d.ts.map +1 -0
  50. package/dist/deploy/package.js +351 -0
  51. package/dist/deploy/package.js.map +1 -0
  52. package/dist/deploy/proof-gate.d.ts +82 -0
  53. package/dist/deploy/proof-gate.d.ts.map +1 -0
  54. package/dist/deploy/proof-gate.js +269 -0
  55. package/dist/deploy/proof-gate.js.map +1 -0
  56. package/dist/deploy/python-float.d.ts +34 -0
  57. package/dist/deploy/python-float.d.ts.map +1 -0
  58. package/dist/deploy/python-float.js +46 -0
  59. package/dist/deploy/python-float.js.map +1 -0
  60. package/dist/deploy/recheck-commands.d.ts +50 -0
  61. package/dist/deploy/recheck-commands.d.ts.map +1 -0
  62. package/dist/deploy/recheck-commands.js +59 -0
  63. package/dist/deploy/recheck-commands.js.map +1 -0
  64. package/dist/deploy/register.d.ts +99 -0
  65. package/dist/deploy/register.d.ts.map +1 -0
  66. package/dist/deploy/register.js +689 -0
  67. package/dist/deploy/register.js.map +1 -0
  68. package/dist/deploy/taxonomy.d.ts +24 -0
  69. package/dist/deploy/taxonomy.d.ts.map +1 -0
  70. package/dist/deploy/taxonomy.js +31 -0
  71. package/dist/deploy/taxonomy.js.map +1 -0
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js +244 -132
  74. package/dist/index.js.map +1 -1
  75. package/dist/runtime/strategy-registry.d.ts +8 -0
  76. package/dist/runtime/strategy-registry.d.ts.map +1 -1
  77. package/dist/runtime/strategy-registry.js +14 -1
  78. package/dist/runtime/strategy-registry.js.map +1 -1
  79. package/dist/runtime-api/intake/intake-mount.d.ts.map +1 -1
  80. package/dist/runtime-api/intake/intake-mount.js +7 -4
  81. package/dist/runtime-api/intake/intake-mount.js.map +1 -1
  82. package/dist/scanners/implementations/market-regime.d.ts +1 -1
  83. package/dist/senpi/client.d.ts +58 -4
  84. package/dist/senpi/client.d.ts.map +1 -1
  85. package/dist/senpi/client.js +192 -15
  86. package/dist/senpi/client.js.map +1 -1
  87. package/dist/senpi/strategy-status.d.ts +32 -0
  88. package/dist/senpi/strategy-status.d.ts.map +1 -0
  89. package/dist/senpi/strategy-status.js +70 -0
  90. package/dist/senpi/strategy-status.js.map +1 -0
  91. package/dist/senpi/types.d.ts +47 -3
  92. package/dist/senpi/types.d.ts.map +1 -1
  93. package/dist/skills-manager/manifest.js +1 -1
  94. package/dist/skills-manager/skills-manifest.json +1 -1
  95. package/dist/telemetry/metrics.d.ts +23 -0
  96. package/dist/telemetry/metrics.d.ts.map +1 -1
  97. package/dist/telemetry/metrics.js +65 -0
  98. package/dist/telemetry/metrics.js.map +1 -1
  99. package/dist/types/strategy.d.ts +9 -1
  100. package/dist/types/strategy.d.ts.map +1 -1
  101. package/dist/universe/package-universe.d.ts +97 -0
  102. package/dist/universe/package-universe.d.ts.map +1 -0
  103. package/dist/universe/package-universe.js +275 -0
  104. package/dist/universe/package-universe.js.map +1 -0
  105. package/dist/validate/codes.d.ts +20 -0
  106. package/dist/validate/codes.d.ts.map +1 -1
  107. package/dist/validate/codes.js +36 -0
  108. package/dist/validate/codes.js.map +1 -1
  109. package/dist/validate/import-stage.js +5 -5
  110. package/dist/validate/import-stage.js.map +1 -1
  111. package/dist/validate/index.d.ts +1 -1
  112. package/dist/validate/index.d.ts.map +1 -1
  113. package/dist/validate/index.js.map +1 -1
  114. package/dist/validate/install-gate.d.ts.map +1 -1
  115. package/dist/validate/install-gate.js +10 -2
  116. package/dist/validate/install-gate.js.map +1 -1
  117. package/dist/validate/live-instruments.d.ts +44 -0
  118. package/dist/validate/live-instruments.d.ts.map +1 -0
  119. package/dist/validate/live-instruments.js +71 -0
  120. package/dist/validate/live-instruments.js.map +1 -0
  121. package/dist/validate/live-stage.js +5 -5
  122. package/dist/validate/live-stage.js.map +1 -1
  123. package/dist/validate/proof.d.ts +11 -1
  124. package/dist/validate/proof.d.ts.map +1 -1
  125. package/dist/validate/proof.js +7 -6
  126. package/dist/validate/proof.js.map +1 -1
  127. package/dist/validate/recipe-checks.d.ts.map +1 -1
  128. package/dist/validate/recipe-checks.js +6 -4
  129. package/dist/validate/recipe-checks.js.map +1 -1
  130. package/dist/validate/run.d.ts +5 -0
  131. package/dist/validate/run.d.ts.map +1 -1
  132. package/dist/validate/run.js +10 -1
  133. package/dist/validate/run.js.map +1 -1
  134. package/dist/validate/target.d.ts +41 -0
  135. package/dist/validate/target.d.ts.map +1 -1
  136. package/dist/validate/target.js +192 -16
  137. package/dist/validate/target.js.map +1 -1
  138. package/dist/validate/universe-stage.d.ts +71 -0
  139. package/dist/validate/universe-stage.d.ts.map +1 -0
  140. package/dist/validate/universe-stage.js +183 -0
  141. package/dist/validate/universe-stage.js.map +1 -0
  142. package/openclaw.plugin.json +1 -1
  143. package/package.json +1 -1
  144. package/dist/runtime/managed-wallet-guard.d.ts +0 -47
  145. package/dist/runtime/managed-wallet-guard.d.ts.map +0 -1
  146. package/dist/runtime/managed-wallet-guard.js +0 -106
  147. package/dist/runtime/managed-wallet-guard.js.map +0 -1
  148. package/dist/runtime/protection-nudge.d.ts +0 -67
  149. package/dist/runtime/protection-nudge.d.ts.map +0 -1
  150. package/dist/runtime/protection-nudge.js +0 -244
  151. package/dist/runtime/protection-nudge.js.map +0 -1
@@ -0,0 +1,463 @@
1
+ /**
2
+ * Everything async about a deploy: the single-flight job slot, the append-only
3
+ * JSONL journal, the wall-clock deadline watchdog, the boot-time interrupted
4
+ * scan, and status snapshots. It runs whatever `run` closure the gateway hands
5
+ * it and knows nothing about MCP or orchestration.
6
+ *
7
+ * **There is no cancel** (D-4): undeploying a strategy is closing it, not
8
+ * stopping the job. The only thing that stops a running deploy early is the
9
+ * deadline watchdog armed in {@link DeployJobController.start} — a soft stop the
10
+ * orchestrator honours at its next step boundary, followed by a force-terminate
11
+ * that frees the slot even when the run is wedged inside an await.
12
+ *
13
+ * **The journal is narration-only.** No code path may read `deploys/*.jsonl` to
14
+ * decide an action. Journals are read for exactly three purposes: rendering
15
+ * `status` history, the boot scan that stamps unterminated journals
16
+ * `interrupted`, and the one sanctioned carve-out — {@link
17
+ * DeployJobController.priorCreatedStrategyIds}, which yields a LOOKUP KEY (a
18
+ * strategyId this box journaled at create time) that reconcile must verify
19
+ * against the live backend before acting on it. Every orchestrator decision
20
+ * still reads the live surfaces (backend strategies + runtime registry) — that
21
+ * is what keeps the E_STATE drift class dead.
22
+ *
23
+ * **No auto-resume.** `scanOnBoot` is a janitor: it stamps and prunes, and never
24
+ * re-enters a state machine. Only a fresh agent-initiated `senpi deploy`
25
+ * resumes, via reconciliation.
26
+ */
27
+ import { randomBytes } from "node:crypto";
28
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync } from "node:fs";
29
+ import { join } from "node:path";
30
+ import { recordDeploy } from "../telemetry/metrics.js";
31
+ import { extractTaxonomyCode } from "./taxonomy.js";
32
+ /**
33
+ * The taxonomy code of the FIRST refused step in the report, for the deploy metric's dimension.
34
+ *
35
+ * Reads the shared extractor, not a private pattern: this was the third reader, and its narrower
36
+ * one silently dropped any code carrying a digit — so `[E_MIN_10]` would exit 2 at the CLI and
37
+ * carry a `senpi.deploy.refusal.code` span attribute while producing no metric dimension at all.
38
+ */
39
+ function firstRefusalCode(report) {
40
+ for (const instance of report.instances) {
41
+ for (const outcome of Object.values(instance.steps)) {
42
+ if (outcome.status !== "refused")
43
+ continue;
44
+ const code = extractTaxonomyCode(outcome.detail);
45
+ if (code)
46
+ return code;
47
+ }
48
+ }
49
+ return undefined;
50
+ }
51
+ const EVENT_TAIL_MAX = 50;
52
+ const DEFAULT_KEEP = 20;
53
+ const DEPLOY_ID_RE = /^dpl-[0-9a-f]{8}$/;
54
+ function errorText(err) {
55
+ return err instanceof Error ? err.message : String(err);
56
+ }
57
+ /**
58
+ * H6: every MCP call a deploy makes is separately deadline-bounded. Owned here, beside the grace it
59
+ * determines, so the two can never be hand-synced apart; `register.ts` imports it for its own
60
+ * per-call signals.
61
+ */
62
+ export const MCP_CALL_TIMEOUT_MS = 90_000;
63
+ /**
64
+ * How long the soft stop gets to land at a step boundary before the slot is taken back by force.
65
+ *
66
+ * DERIVED, never a bare number: force-terminate releases the single-flight slot while the abandoned
67
+ * run may still be waiting on a call. Sized past the per-call deadline so that by the time the slot
68
+ * frees, every call the abandoned run could still be awaiting has already been abandoned by its own
69
+ * deadline — a NEW deploy never starts its preflight/create while the old job is still parked on a
70
+ * money-moving call against the same funding waterfall. The headroom covers the abort landing and
71
+ * the step-boundary check that follows it.
72
+ *
73
+ * What this does NOT buy: a deadline bounds the WAIT, not the backend's work (`boundAwait` in
74
+ * `senpi/client.ts` — the gateway-injected MCP transport accepts no abort option). A create or fund
75
+ * this job stopped waiting on may still complete server-side after the slot has been handed on.
76
+ * What the grace guarantees is that the job is no longer *waiting* on a money-moving call when the
77
+ * slot comes back; the residue is reconciled by the next deploy, not prevented here.
78
+ *
79
+ * (This supersedes the plan's locked 60s: 60 < the locked 90s call timeout, so the two locked
80
+ * defaults were mutually inconsistent.)
81
+ */
82
+ export const GRACE_MS = MCP_CALL_TIMEOUT_MS + 30_000;
83
+ /**
84
+ * The single-flight slot is IN-MEMORY ONLY (`this.live`). It must never be persisted: an
85
+ * interrupted job belongs to a dead process and leaves the slot free by construction — persisting
86
+ * it would wedge every deploy on the box after one crash.
87
+ */
88
+ export class DeployJobController {
89
+ deploysDir;
90
+ now;
91
+ log;
92
+ keep;
93
+ live = null;
94
+ constructor(opts) {
95
+ this.deploysDir = opts.deploysDir;
96
+ this.now = opts.now;
97
+ this.log = opts.log;
98
+ this.keep = opts.keep ?? DEFAULT_KEEP;
99
+ }
100
+ /** The running job's identity and progress, or null. The single-flight gate reads this. */
101
+ running() {
102
+ if (!this.live)
103
+ return null;
104
+ return { ...this.live.meta, phase: this.live.phase, stopRequested: this.live.stopRequested };
105
+ }
106
+ /**
107
+ * Claim the single job slot and fire `run` un-awaited.
108
+ *
109
+ * Single-flight is a money invariant, not a convenience: concurrent deploys
110
+ * share one funding waterfall, so two preflights can both pass while jointly
111
+ * overdrawing. Throws when a job is already running. The ordinary
112
+ * `E_DEPLOY_IN_PROGRESS` refusal comes from the gateway's own `running()`
113
+ * check BEFORE it gets here; this throw is the lost race between that check
114
+ * and this call, and the gateway catches it and renders the same refusal.
115
+ *
116
+ * `capMs` is the job's wall-clock cap (H6). Every MCP call the run makes is separately
117
+ * timeout-bounded, but a cap is what guarantees the slot comes back: one hung await used to hold
118
+ * it until the gateway restarted, and a boundary-honored stop flag never protected that case.
119
+ */
120
+ start(meta, capMs, run) {
121
+ if (this.live) {
122
+ throw new Error(`deploy ${this.live.meta.deployId} is already running (${this.live.phase})`);
123
+ }
124
+ const full = {
125
+ ...meta,
126
+ deployId: `dpl-${randomBytes(4).toString("hex")}`,
127
+ startedAt: new Date(this.now()).toISOString(),
128
+ };
129
+ const job = { meta: full, phase: "reconcile", stopRequested: false, terminated: false, events: [], seq: 0 };
130
+ // Header first, slot second: the journal is narration, so a write failure must not be able to
131
+ // claim (and then never release) the single-flight slot. `append` never throws — see its JSDoc.
132
+ try {
133
+ mkdirSync(this.deploysDir, { recursive: true });
134
+ }
135
+ catch (err) {
136
+ this.log(`deploy ${full.deployId}: could not create ${this.deploysDir} — journaling disabled (${errorText(err)})`);
137
+ }
138
+ this.append(full.deployId, { kind: "header", ...full });
139
+ this.live = job;
140
+ const handle = {
141
+ deployId: full.deployId,
142
+ emit: (event) => this.recordEvent(job, event),
143
+ shouldAbandon: () => job.stopRequested,
144
+ };
145
+ // Soft stop at the cap, hard stop `GRACE_MS` later. Both timers are unref'd so a deploy never
146
+ // keeps the gateway process alive, and both are cleared in `terminate`.
147
+ job.watchdog = setTimeout(() => {
148
+ job.stopRequested = true;
149
+ this.recordEvent(job, {
150
+ type: "job",
151
+ detail: `deploy deadline (${Math.round(capMs / 1000)}s) exceeded — abandoning at the next step boundary`,
152
+ });
153
+ job.grace = setTimeout(() => {
154
+ if (this.live === job) {
155
+ this.terminate(job, { status: "done", overall: "failed" }, {
156
+ packageId: full.packageId,
157
+ version: full.version,
158
+ overall: "failed",
159
+ instances: [],
160
+ next: `The deploy exceeded its deadline and was abandoned; a step may still have been in ` +
161
+ `flight. Nothing was rolled back. Re-run \`openclaw senpi deploy -p '${full.packageDir}'\` — ` +
162
+ `reconciliation adopts what exists.`,
163
+ });
164
+ }
165
+ }, GRACE_MS);
166
+ job.grace.unref?.();
167
+ }, capMs);
168
+ job.watchdog.unref?.();
169
+ // Deferred a microtask so `start` stays pure bookkeeping: a synchronously throwing `run`
170
+ // becomes a journaled terminal state like any other failure, never an exception at the caller.
171
+ void Promise.resolve()
172
+ .then(() => run(handle))
173
+ .then((report) => this.terminate(job, { status: "done", overall: report.overall }, report), (err) => {
174
+ if (job.terminated)
175
+ return; // already force-terminated; do not narrate past the terminal line
176
+ const message = err instanceof Error ? err.message : String(err);
177
+ this.recordEvent(job, { type: "job", detail: `deploy failed: ${message}` });
178
+ this.terminate(job, { status: "done", overall: "failed" }, {
179
+ packageId: full.packageId,
180
+ version: full.version,
181
+ overall: "failed",
182
+ instances: [],
183
+ next: `The deploy died mid-flight: ${message}. Nothing was rolled back. ` +
184
+ `Re-run \`openclaw senpi deploy -p '${full.packageDir}'\` — reconciliation adopts what exists.`,
185
+ });
186
+ });
187
+ return full;
188
+ }
189
+ /**
190
+ * The running job (from memory) when it is the one being asked about — by id or by omission —
191
+ * else the newest journal, or the named one. Unknown id → null.
192
+ *
193
+ * The live check MUST come first even for an explicit id: a
194
+ * running job's journal has no terminal line yet, so falling through to {@link readJournal}
195
+ * would render it `interrupted` — a state only the boot scan may ever assign, and a flat lie
196
+ * about a job that is actively moving money.
197
+ */
198
+ async status(deployId) {
199
+ // A caller's id is joined into a path by `journalPath`, so it is shape-checked against the same
200
+ // pattern `journalIds` applies to what it reads back. An unknown id already answers "no such
201
+ // deploy"; this is what keeps `../…` from being READ before answering it.
202
+ if (deployId !== undefined && !DEPLOY_ID_RE.test(deployId))
203
+ return null;
204
+ if (this.live && (!deployId || deployId === this.live.meta.deployId)) {
205
+ return {
206
+ meta: this.live.meta,
207
+ state: { status: "running", phase: this.live.phase, stopRequested: this.live.stopRequested },
208
+ events: [...this.live.events],
209
+ };
210
+ }
211
+ const id = deployId ?? this.newestJournalId();
212
+ if (!id)
213
+ return null;
214
+ return this.readJournal(id);
215
+ }
216
+ /**
217
+ * C2 carve-out: the ONE sanctioned journal read that feeds the orchestrator — and only as a
218
+ * LOOKUP KEY. Every id returned here is verified against the live backend (strategy_list
219
+ * strategyIds:[…]) before reconcile acts on it; the journal never decides anything.
220
+ *
221
+ * It exists because a create whose custom name the backend rejected lands under a
222
+ * backend-assigned name, which the reconcile name match can never find again: without the key,
223
+ * a re-run creates and funds a second wallet beside the first.
224
+ *
225
+ * Returns instance name → journaled created-strategy ids, newest journals first.
226
+ *
227
+ * `deployId` scopes the answer to ONE journal. Reconcile deliberately passes nothing: every
228
+ * journal of the package is a legitimate key source there, because any of them may hold the id
229
+ * of the wallet a re-run must adopt instead of funding a second one (D-9). The interrupted RENDER
230
+ * passes its own id, because it answers a narrower question — what did THIS job leave behind —
231
+ * and a still-live wallet from an earlier completed deploy is not that. Rendering it there would
232
+ * offer to close a strategy that is running exactly as intended.
233
+ */
234
+ priorCreatedStrategyIds(packageId, deployId) {
235
+ const byInstance = new Map();
236
+ for (const id of this.journalIdsNewestFirst()) {
237
+ if (deployId !== undefined && id !== deployId)
238
+ continue;
239
+ const lines = this.readLines(id);
240
+ const header = lines.find((line) => line.kind === "header");
241
+ if (!header || header.kind !== "header" || header.packageId !== packageId)
242
+ continue;
243
+ for (const line of lines) {
244
+ // ONLY the create-key event. Narrowing the shape keeps the carve-out explicit: any future
245
+ // job event that happens to carry an instance + strategyId must not silently become a
246
+ // reconcile input.
247
+ if (line.kind !== "event" || line.type !== "job" || line.phase !== "create")
248
+ continue;
249
+ const instance = line.evidence?.instance;
250
+ const strategyId = line.evidence?.strategyId;
251
+ if (typeof instance !== "string" || typeof strategyId !== "string")
252
+ continue;
253
+ const list = byInstance.get(instance) ?? [];
254
+ if (!list.includes(strategyId))
255
+ list.push(strategyId);
256
+ byInstance.set(instance, list);
257
+ }
258
+ }
259
+ return byInstance;
260
+ }
261
+ /**
262
+ * The second D-9 lookup input off the SAME create-key event: strategyId → the amount that create
263
+ * call asked the backend to fund.
264
+ *
265
+ * Narrower than {@link priorCreatedStrategyIds} in what it may do, and deliberately so. It feeds
266
+ * exactly one thing — the report's funded-vs-requested comparison — and only the REQUESTED half
267
+ * of it; the funded half is always re-read live from `strategy_list` on the current run. No
268
+ * orchestration branch reads it, so a stale, missing or wrong entry costs a warn, never an action.
269
+ *
270
+ * It exists because the ask lives only in the memory of the run that funded. A deploy that funds
271
+ * $60 of a $500 ask and dies before assembling its report resumes as an adoption, and without
272
+ * this the shortfall is simply never mentioned again.
273
+ *
274
+ * Newest journal wins on a repeated strategyId — the ask that most recently produced that wallet.
275
+ */
276
+ priorRequestedFunding(packageId) {
277
+ const byStrategy = new Map();
278
+ for (const id of this.journalIdsNewestFirst()) {
279
+ const lines = this.readLines(id);
280
+ const header = lines.find((line) => line.kind === "header");
281
+ if (!header || header.kind !== "header" || header.packageId !== packageId)
282
+ continue;
283
+ for (const line of lines) {
284
+ if (line.kind !== "event" || line.type !== "job" || line.phase !== "create")
285
+ continue;
286
+ const strategyId = line.evidence?.strategyId;
287
+ const requested = line.evidence?.requested;
288
+ if (typeof strategyId !== "string" || typeof requested !== "number")
289
+ continue;
290
+ if (!(requested > 0) || byStrategy.has(strategyId))
291
+ continue;
292
+ byStrategy.set(strategyId, requested);
293
+ }
294
+ }
295
+ return byStrategy;
296
+ }
297
+ /**
298
+ * Boot janitor: stamp every unterminated journal `interrupted`, then prune to
299
+ * the newest `keep`. Discovery is pull-based — the next `openclaw senpi deploy status`
300
+ * renders the interruption; nothing is resumed or notified here.
301
+ */
302
+ async scanOnBoot() {
303
+ if (!existsSync(this.deploysDir))
304
+ return;
305
+ for (const id of this.journalIds()) {
306
+ const lines = this.readLines(id);
307
+ if (lines.length === 0 || lines.some((line) => line.kind === "terminal"))
308
+ continue;
309
+ this.append(id, {
310
+ kind: "terminal",
311
+ state: { status: "interrupted" },
312
+ endedAt: new Date(this.now()).toISOString(),
313
+ });
314
+ this.log(`deploy ${id} interrupted by restart — surfaced on next openclaw senpi deploy status`);
315
+ }
316
+ this.prune();
317
+ }
318
+ // ── internals ───────────────────────────────────────────────────────────────
319
+ recordEvent(job, body) {
320
+ job.seq += 1;
321
+ const event = { ...body, seq: job.seq, ts: new Date(this.now()).toISOString() };
322
+ if (body.type === "job" && body.phase)
323
+ job.phase = body.phase;
324
+ job.events.push(event);
325
+ if (job.events.length > EVENT_TAIL_MAX)
326
+ job.events.splice(0, job.events.length - EVENT_TAIL_MAX);
327
+ this.append(job.meta.deployId, { kind: "event", ...event });
328
+ }
329
+ /**
330
+ * Close a job out. The slot is released in `finally`: a journal-write failure is narration lost,
331
+ * never a wedged single-flight slot that would refuse every future deploy with a stale
332
+ * `E_DEPLOY_IN_PROGRESS`.
333
+ */
334
+ terminate(job, state, report) {
335
+ // Idempotent: after a force-terminate the abandoned `run` promise settles later and calls in
336
+ // again. `readJournal` already tolerates the second terminal line (first wins), but the deploy
337
+ // METRIC does not — without this guard one wedged deploy counts twice, under two different
338
+ // outcomes, on precisely the path where the count matters most.
339
+ if (job.terminated)
340
+ return;
341
+ job.terminated = true;
342
+ try {
343
+ if (job.watchdog)
344
+ clearTimeout(job.watchdog);
345
+ if (job.grace)
346
+ clearTimeout(job.grace);
347
+ // M6: `refused` is recorded as an outcome like any other, with its taxonomy code — a refusal
348
+ // is a correct answer, never an error.
349
+ const refusalCode = firstRefusalCode(report);
350
+ recordDeploy({
351
+ outcome: state.status === "done" ? state.overall : state.status,
352
+ ...(refusalCode ? { refusalCode } : {}),
353
+ durationSec: Math.max(0, (this.now() - Date.parse(job.meta.startedAt)) / 1000),
354
+ });
355
+ this.append(job.meta.deployId, {
356
+ kind: "terminal",
357
+ state,
358
+ endedAt: new Date(this.now()).toISOString(),
359
+ report,
360
+ });
361
+ this.prune();
362
+ }
363
+ finally {
364
+ if (this.live === job)
365
+ this.live = null;
366
+ }
367
+ }
368
+ journalPath(deployId) {
369
+ return join(this.deploysDir, `${deployId}.jsonl`);
370
+ }
371
+ /**
372
+ * Append one journal line. **Never throws**: the journal is narration, and the narration-only
373
+ * rule cuts both ways — a full or read-only state volume must not be able to change what a
374
+ * deploy does (or wedge its slot). A failed write is logged and the line is lost.
375
+ */
376
+ append(deployId, line) {
377
+ try {
378
+ appendFileSync(this.journalPath(deployId), `${JSON.stringify(line)}\n`);
379
+ }
380
+ catch (err) {
381
+ this.log(`deploy ${deployId}: journal write failed (${errorText(err)}) — narration lost, deploy unaffected`);
382
+ }
383
+ }
384
+ journalIds() {
385
+ if (!existsSync(this.deploysDir))
386
+ return [];
387
+ return readdirSync(this.deploysDir)
388
+ .filter((name) => name.endsWith(".jsonl"))
389
+ .map((name) => name.slice(0, -".jsonl".length))
390
+ .filter((id) => DEPLOY_ID_RE.test(id));
391
+ }
392
+ readLines(deployId) {
393
+ const path = this.journalPath(deployId);
394
+ if (!existsSync(path))
395
+ return [];
396
+ const lines = [];
397
+ for (const raw of readFileSync(path, "utf8").split("\n")) {
398
+ if (raw.trim() === "")
399
+ continue;
400
+ try {
401
+ lines.push(JSON.parse(raw));
402
+ }
403
+ catch {
404
+ // A torn last line (killed mid-append) is narration lost, not state lost.
405
+ }
406
+ }
407
+ return lines;
408
+ }
409
+ /** Journals newest-first by header `startedAt` — the only ordering the controller trusts. */
410
+ journalIdsNewestFirst() {
411
+ return this.journalIds()
412
+ .map((id) => {
413
+ const header = this.readLines(id).find((line) => line.kind === "header");
414
+ return { id, startedAt: header?.startedAt ?? "" };
415
+ })
416
+ .sort((a, b) => (a.startedAt < b.startedAt ? 1 : a.startedAt > b.startedAt ? -1 : 0))
417
+ .map((entry) => entry.id);
418
+ }
419
+ newestJournalId() {
420
+ return this.journalIdsNewestFirst()[0] ?? null;
421
+ }
422
+ /**
423
+ * Journal → snapshot. A journal with no terminal line IS an interrupted job.
424
+ *
425
+ * The FIRST terminal line wins. That ordering is load-bearing: when the deadline watchdog
426
+ * force-terminates a wedged job, the zombie `run` promise may settle later and append a second
427
+ * terminal line — the watchdog's verdict is the true one, and reading the first keeps it.
428
+ */
429
+ readJournal(deployId) {
430
+ const lines = this.readLines(deployId);
431
+ const header = lines.find((line) => line.kind === "header");
432
+ if (!header)
433
+ return null;
434
+ const { kind: _kind, ...meta } = header;
435
+ const events = lines
436
+ .filter((line) => line.kind === "event")
437
+ .map(({ kind: _eventKind, ...event }) => event);
438
+ const terminal = lines.find((line) => line.kind === "terminal");
439
+ return {
440
+ meta,
441
+ state: terminal?.state ?? { status: "interrupted" },
442
+ events: events.slice(-EVENT_TAIL_MAX),
443
+ ...(terminal?.report ? { report: terminal.report } : {}),
444
+ };
445
+ }
446
+ /** Retain the newest `keep` journals. Runs on every terminate and on boot, so a long-lived
447
+ * gateway does not grow `deploys/` without bound between restarts. Never throws.
448
+ *
449
+ * Unlocked by design: prune and every journal read are fully synchronous on one thread, so they
450
+ * cannot interleave in-process; a torn last line is already tolerated by `readLines`. Do not add
451
+ * locking without a cross-process caller. */
452
+ prune() {
453
+ try {
454
+ const stale = this.journalIdsNewestFirst().slice(this.keep);
455
+ for (const id of stale)
456
+ rmSync(this.journalPath(id), { force: true });
457
+ }
458
+ catch (err) {
459
+ this.log(`deploy journal prune failed (${errorText(err)}) — harmless, retried on the next terminate`);
460
+ }
461
+ }
462
+ }
463
+ //# sourceMappingURL=job.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"job.js","sourceRoot":"","sources":["../../src/deploy/job.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ import { type DeployPackage } from "./package.js";
2
+ /**
3
+ * The platform per-wallet floor — mirrors the backend's `MINIMUM_STRATEGY_BUDGET` default
4
+ * (`getMinimumStrategyBudget` in senpi-hyperliquid-mcp). `funding.ts` re-exports it as `MIN_WALLET`
5
+ * so the runtime, like the skills side, has ONE copy of the physics.
6
+ */
7
+ export declare const WALLET_FLOOR = 10;
8
+ /** The notional the engine bumps a small order up to. */
9
+ export declare const BUMPED_NOTIONAL = 12;
10
+ /** USDC reserved per wallet for the creation fee (observed ~$1). */
11
+ export declare const FEE_BUFFER = 1.5;
12
+ /** One instance's contribution, in the Python's `breakdown` shape (camelCased). */
13
+ export interface MinBudgetRow {
14
+ wallet: string;
15
+ /** null = nothing in this sleeve resolved to a slot size. */
16
+ marginPct: number | null;
17
+ minLeverage: number;
18
+ fundingShare: number;
19
+ perWalletMin: number;
20
+ }
21
+ export interface MinBudgetResult {
22
+ /** The number to warn against. Never rounded down; a `catalog.min_budget_floor` may raise it. */
23
+ minBudget: number;
24
+ walletCount: number;
25
+ /** The sleeve whose per-wallet minimum over its share drives the total. null for an empty package. */
26
+ bindingWallet: string | null;
27
+ /**
28
+ * Sleeves whose marginPct could NOT be resolved — their per-wallet minimum fell back to the bare
29
+ * floor, so `minBudget` may be UNDERSTATED. Never silently swallowed: deploy warns rather than
30
+ * assert a number it never computed.
31
+ */
32
+ unresolvedWallets: string[];
33
+ /** The pre-rounding total, for the report's arithmetic to be checkable. */
34
+ raw: number;
35
+ breakdown: MinBudgetRow[];
36
+ }
37
+ /** The `strategy.yaml` subset this reads: instances with their shares, plus the catalog block. */
38
+ export interface MinBudgetManifest {
39
+ instances?: Array<{
40
+ name?: unknown;
41
+ funding_share?: unknown;
42
+ }> | null;
43
+ catalog?: Record<string, unknown> | null;
44
+ }
45
+ /** instance name → its parsed `runtime.yaml` document. */
46
+ export type MinBudgetRuntimes = Record<string, Record<string, unknown> | null | undefined>;
47
+ /**
48
+ * PURE. `manifest` is the parsed `strategy.yaml`; `runtimes` maps instance name → parsed
49
+ * `runtime.yaml`. Mirrors the Python's full return shape (camelCased).
50
+ */
51
+ export declare function strategyMinBudget(manifest: MinBudgetManifest, runtimes: MinBudgetRuntimes): MinBudgetResult;
52
+ /**
53
+ * The same calculation over a loaded package — the port of `deploy.py`'s `strategy_min(pkg)`. The
54
+ * package's own YAML is the only input, because a custom-authored package never passes through
55
+ * catalog generation: "what the card promised" and "what deploy warns about" are one function.
56
+ *
57
+ * A runtime.yaml that cannot be parsed contributes an EMPTY document rather than throwing: this is
58
+ * an advisory warning computed after the money gates have already passed, and it must never be the
59
+ * thing that fails a deploy. Such a sleeve lands in `unresolvedWallets`, which is exactly the
60
+ * "may be understated" warning.
61
+ *
62
+ * **An absent `funding_share` resolves to `1/n`, not to the Python's `1.0`** — see the header's
63
+ * documented-deviation list. The adapter is the right place for it: `strategyMinBudget` keeps
64
+ * byte-parity with the reference, and this layer is where the manifest meets the verb that will
65
+ * actually spend the money.
66
+ */
67
+ export declare function packageMinBudget(pkg: DeployPackage): MinBudgetResult;
68
+ //# sourceMappingURL=min-budget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"min-budget.d.ts","sourceRoot":"","sources":["../../src/deploy/min-budget.ts"],"names":[],"mappings":"AA6DA,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAGnE;;;;GAIG;AACH,eAAO,MAAM,YAAY,KAAO,CAAC;AAEjC,yDAAyD;AACzD,eAAO,MAAM,eAAe,KAAO,CAAC;AAEpC,oEAAoE;AACpE,eAAO,MAAM,UAAU,MAAM,CAAC;AAe9B,mFAAmF;AACnF,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,iGAAiG;IACjG,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,sGAAsG;IACtG,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B;;;;OAIG;IACH,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,2EAA2E;IAC3E,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,YAAY,EAAE,CAAC;CAC3B;AAED,kGAAkG;AAClG,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,GAAG,IAAI,CAAC;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC1C;AAED,0DAA0D;AAC1D,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;AAqH3F;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,iBAAiB,EAC3B,QAAQ,EAAE,iBAAiB,GAC1B,eAAe,CA4DjB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,eAAe,CA2BpE"}