@senpi/runtime 3.0.180 → 3.0.181

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 (135) hide show
  1. package/dist/actions/llm-decision/openclaw-llm-decision.d.ts +8 -0
  2. package/dist/actions/llm-decision/openclaw-llm-decision.d.ts.map +1 -1
  3. package/dist/actions/llm-decision/openclaw-llm-decision.js +56 -1
  4. package/dist/actions/llm-decision/openclaw-llm-decision.js.map +1 -1
  5. package/dist/approvals/approval-store.d.ts +37 -7
  6. package/dist/approvals/approval-store.d.ts.map +1 -1
  7. package/dist/approvals/approval-store.js +127 -23
  8. package/dist/approvals/approval-store.js.map +1 -1
  9. package/dist/approvals/telemetry.d.ts +27 -3
  10. package/dist/approvals/telemetry.d.ts.map +1 -1
  11. package/dist/approvals/telemetry.js +8 -4
  12. package/dist/approvals/telemetry.js.map +1 -1
  13. package/dist/approvals/tool-approval-gate.d.ts +79 -0
  14. package/dist/approvals/tool-approval-gate.d.ts.map +1 -1
  15. package/dist/approvals/tool-approval-gate.js +184 -15
  16. package/dist/approvals/tool-approval-gate.js.map +1 -1
  17. package/dist/approvals/turn-context.d.ts +271 -0
  18. package/dist/approvals/turn-context.d.ts.map +1 -0
  19. package/dist/approvals/turn-context.js +551 -0
  20. package/dist/approvals/turn-context.js.map +1 -0
  21. package/dist/cli/guide-commands.d.ts.map +1 -1
  22. package/dist/cli/guide-commands.js +17 -3
  23. package/dist/cli/guide-commands.js.map +1 -1
  24. package/dist/cli/senpi-commands.d.ts.map +1 -1
  25. package/dist/cli/senpi-commands.js +71 -4
  26. package/dist/cli/senpi-commands.js.map +1 -1
  27. package/dist/deploy/job.d.ts +32 -0
  28. package/dist/deploy/job.d.ts.map +1 -1
  29. package/dist/deploy/job.js +72 -7
  30. package/dist/deploy/job.js.map +1 -1
  31. package/dist/deploy/register.d.ts +11 -0
  32. package/dist/deploy/register.d.ts.map +1 -1
  33. package/dist/deploy/register.js +84 -2
  34. package/dist/deploy/register.js.map +1 -1
  35. package/dist/host/openclaw-host.d.ts +138 -0
  36. package/dist/host/openclaw-host.d.ts.map +1 -0
  37. package/dist/host/openclaw-host.js +177 -0
  38. package/dist/host/openclaw-host.js.map +1 -0
  39. package/dist/index.d.ts +46 -2
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +220 -12
  42. package/dist/index.js.map +1 -1
  43. package/dist/runtime/auto-update/coordinator.d.ts +203 -0
  44. package/dist/runtime/auto-update/coordinator.d.ts.map +1 -1
  45. package/dist/runtime/auto-update/coordinator.js +621 -12
  46. package/dist/runtime/auto-update/coordinator.js.map +1 -1
  47. package/dist/runtime/auto-update/gateway-restart.d.ts +60 -1
  48. package/dist/runtime/auto-update/gateway-restart.d.ts.map +1 -1
  49. package/dist/runtime/auto-update/gateway-restart.js +95 -21
  50. package/dist/runtime/auto-update/gateway-restart.js.map +1 -1
  51. package/dist/runtime/auto-update/idle-gate.d.ts +139 -0
  52. package/dist/runtime/auto-update/idle-gate.d.ts.map +1 -0
  53. package/dist/runtime/auto-update/idle-gate.js +217 -0
  54. package/dist/runtime/auto-update/idle-gate.js.map +1 -0
  55. package/dist/runtime/auto-update/install-results.d.ts +130 -0
  56. package/dist/runtime/auto-update/install-results.d.ts.map +1 -0
  57. package/dist/runtime/auto-update/install-results.js +264 -0
  58. package/dist/runtime/auto-update/install-results.js.map +1 -0
  59. package/dist/runtime/auto-update/money-call-tracker.d.ts +36 -0
  60. package/dist/runtime/auto-update/money-call-tracker.d.ts.map +1 -0
  61. package/dist/runtime/auto-update/money-call-tracker.js +129 -0
  62. package/dist/runtime/auto-update/money-call-tracker.js.map +1 -0
  63. package/dist/runtime/auto-update/register-idle-gate.d.ts +70 -0
  64. package/dist/runtime/auto-update/register-idle-gate.d.ts.map +1 -0
  65. package/dist/runtime/auto-update/register-idle-gate.js +123 -0
  66. package/dist/runtime/auto-update/register-idle-gate.js.map +1 -0
  67. package/dist/runtime/auto-update/run-update-command.d.ts +13 -0
  68. package/dist/runtime/auto-update/run-update-command.d.ts.map +1 -1
  69. package/dist/runtime/auto-update/run-update-command.js +18 -0
  70. package/dist/runtime/auto-update/run-update-command.js.map +1 -1
  71. package/dist/runtime/auto-update/state-store.d.ts +14 -1
  72. package/dist/runtime/auto-update/state-store.d.ts.map +1 -1
  73. package/dist/runtime/auto-update/state-store.js +37 -3
  74. package/dist/runtime/auto-update/state-store.js.map +1 -1
  75. package/dist/runtime/auto-update/types.d.ts +51 -0
  76. package/dist/runtime/auto-update/types.d.ts.map +1 -1
  77. package/dist/runtime/auto-update/types.js.map +1 -1
  78. package/dist/runtime/auto-update/update-marker.d.ts +95 -0
  79. package/dist/runtime/auto-update/update-marker.d.ts.map +1 -0
  80. package/dist/runtime/auto-update/update-marker.js +185 -0
  81. package/dist/runtime/auto-update/update-marker.js.map +1 -0
  82. package/dist/runtime/auto-update/update-schedule.d.ts +57 -0
  83. package/dist/runtime/auto-update/update-schedule.d.ts.map +1 -0
  84. package/dist/runtime/auto-update/update-schedule.js +75 -0
  85. package/dist/runtime/auto-update/update-schedule.js.map +1 -0
  86. package/dist/runtime/decision-errors.d.ts +7 -0
  87. package/dist/runtime/decision-errors.d.ts.map +1 -1
  88. package/dist/runtime/decision-errors.js +15 -0
  89. package/dist/runtime/decision-errors.js.map +1 -1
  90. package/dist/senpi/client.d.ts +25 -0
  91. package/dist/senpi/client.d.ts.map +1 -1
  92. package/dist/senpi/client.js +56 -0
  93. package/dist/senpi/client.js.map +1 -1
  94. package/dist/telemetry/metrics.d.ts +14 -0
  95. package/dist/telemetry/metrics.d.ts.map +1 -1
  96. package/dist/telemetry/metrics.js +24 -0
  97. package/dist/telemetry/metrics.js.map +1 -1
  98. package/dist/telemetry/otel-sdk.d.ts +12 -0
  99. package/dist/telemetry/otel-sdk.d.ts.map +1 -1
  100. package/dist/telemetry/otel-sdk.js +47 -1
  101. package/dist/telemetry/otel-sdk.js.map +1 -1
  102. package/dist/telemetry/spans/span-hooks.d.ts +18 -1
  103. package/dist/telemetry/spans/span-hooks.d.ts.map +1 -1
  104. package/dist/telemetry/spans/span-hooks.js +306 -96
  105. package/dist/telemetry/spans/span-hooks.js.map +1 -1
  106. package/dist/telemetry/tool-plane.d.ts +2 -0
  107. package/dist/telemetry/tool-plane.d.ts.map +1 -1
  108. package/dist/telemetry/tool-plane.js +1 -0
  109. package/dist/telemetry/tool-plane.js.map +1 -1
  110. package/dist/tool-surface/crib-cache.d.ts +21 -0
  111. package/dist/tool-surface/crib-cache.d.ts.map +1 -0
  112. package/dist/tool-surface/crib-cache.js +54 -0
  113. package/dist/tool-surface/crib-cache.js.map +1 -0
  114. package/dist/tool-surface/crib-hook.d.ts +78 -0
  115. package/dist/tool-surface/crib-hook.d.ts.map +1 -0
  116. package/dist/tool-surface/crib-hook.js +217 -0
  117. package/dist/tool-surface/crib-hook.js.map +1 -0
  118. package/dist/tool-surface/crib-sheet.d.ts +56 -0
  119. package/dist/tool-surface/crib-sheet.d.ts.map +1 -0
  120. package/dist/tool-surface/crib-sheet.js +129 -0
  121. package/dist/tool-surface/crib-sheet.js.map +1 -0
  122. package/dist/tool-surface/mcp-tool-source.d.ts +64 -0
  123. package/dist/tool-surface/mcp-tool-source.d.ts.map +1 -0
  124. package/dist/tool-surface/mcp-tool-source.js +189 -0
  125. package/dist/tool-surface/mcp-tool-source.js.map +1 -0
  126. package/dist/utils/event-catalog.d.ts +117 -5
  127. package/dist/utils/event-catalog.d.ts.map +1 -1
  128. package/dist/utils/event-catalog.js +19 -0
  129. package/dist/utils/event-catalog.js.map +1 -1
  130. package/dist/widgets/tool.d.ts +9 -0
  131. package/dist/widgets/tool.d.ts.map +1 -1
  132. package/dist/widgets/tool.js +1 -0
  133. package/dist/widgets/tool.js.map +1 -1
  134. package/openclaw.plugin.json +11 -1
  135. package/package.json +5 -2
@@ -0,0 +1,217 @@
1
+ /**
2
+ * When is a box idle enough for a runtime self-update restart?
3
+ *
4
+ * On OpenClaw 10.x the runtime applies an update by asking the gateway to
5
+ * restart (restart-to-apply, R2). The gateway's own deferral already waits for
6
+ * running replies before it exits, so this gate is not what keeps a reply from
7
+ * being cut. Its job is to keep the ~70 s outage away from a user who is in a
8
+ * conversation or money in motion: no user turn running or queued, no trade
9
+ * approval waiting for an answer, no money call in flight, no `senpi deploy`
10
+ * job running, and no user message in the last 15 min.
11
+ *
12
+ * The gate is pure state plus a decision. It does no I/O, registers nothing
13
+ * and reads no clock of its own: callers pass `now` to every method. That keeps
14
+ * it host-neutral, so a later read-only `senpi.idle.status` gateway method can
15
+ * expose {@link UpdateIdleGate.isIdle} on 5.x hosts as well as 10.x.
16
+ */
17
+ import { isChildSession } from "../../approvals/turn-context.js";
18
+ /** Default: drop a run with no end after 2 h. */
19
+ export const IDLE_STALE_RUN_MS = 2 * 60 * 60_000;
20
+ /** Default: a received message counts as queued for 60 s. */
21
+ export const IDLE_QUEUED_TTL_MS = 60_000;
22
+ /** Default: no user message for 15 min. */
23
+ export const IDLE_RECENT_MESSAGE_MS = 15 * 60_000;
24
+ function asString(value) {
25
+ return typeof value === "string" && value.length > 0 ? value : undefined;
26
+ }
27
+ /**
28
+ * Whether a run is one the user is waiting on: the hook context's `trigger` is
29
+ * `"user"`, or its input provenance is `external_user`. Heartbeat, cron,
30
+ * memory and other background runs are not.
31
+ *
32
+ * A spawned child's own run (`subagent:` / `acp:` session) is never a user run
33
+ * by its context: 10.10 hard-codes `trigger: "user"` for every child run
34
+ * (`attempt-execution.ts:349`), a heartbeat's or cron's included. A child the
35
+ * user is waiting on is counted from `subagent_spawned` instead
36
+ * ({@link UpdateIdleGate.onSubagentSpawned}), which checks its requester.
37
+ */
38
+ export function isUserTriggeredRun(info) {
39
+ if (isChildSession(typeof info.sessionKey === "string" ? info.sessionKey : undefined))
40
+ return false;
41
+ return info.trigger === "user" || info.inputProvenanceKind === "external_user";
42
+ }
43
+ /**
44
+ * Tracks user activity for the self-update idle check.
45
+ *
46
+ * Inputs (wired in `src/index.ts`, modern hosts only):
47
+ * - `onRunStart` / `onRunEnd`: `api.runtime.events.onAgentEvent` lifecycle
48
+ * `start` and `end`/`error`, plus the `agent_end` hook;
49
+ * - `onRunClassified`: hook contexts that carry `trigger`/`inputProvenance`
50
+ * (`before_prompt_build`, `agent_end`). Only a run classified as
51
+ * user-triggered counts; an unclassified run does not block;
52
+ * - `onSubagentSpawned` / `onSubagentEnded`: subagents of an active user run;
53
+ * - `onUserMessage`: `message_received`.
54
+ *
55
+ * Every tracked run or subagent is dropped after `staleRunMs` without an end,
56
+ * with one warning per entry, so a lost `end` event cannot pin the box busy.
57
+ */
58
+ export class UpdateIdleGate {
59
+ options;
60
+ runs = new Map();
61
+ subagents = new Map();
62
+ /** Arrival times of user messages whose run has not started yet. */
63
+ queued = [];
64
+ lastUserMessageAt;
65
+ staleRunMs;
66
+ queuedTtlMs;
67
+ recentMessageMs;
68
+ constructor(options = {}) {
69
+ this.options = options;
70
+ this.staleRunMs = options.staleRunMs ?? IDLE_STALE_RUN_MS;
71
+ this.queuedTtlMs = options.queuedTtlMs ?? IDLE_QUEUED_TTL_MS;
72
+ this.recentMessageMs = options.recentMessageMs ?? IDLE_RECENT_MESSAGE_MS;
73
+ }
74
+ /** A run's lifecycle started. It does not block until a hook classifies it as a user run. */
75
+ onRunStart(runId, sessionKey, now) {
76
+ const id = asString(runId);
77
+ if (!id)
78
+ return;
79
+ // Prune here too, so runs whose end was missed do not pile up between
80
+ // releases (isIdle is only read while an update is pending).
81
+ this.dropStale(now);
82
+ const existing = this.runs.get(id);
83
+ if (existing) {
84
+ existing.sessionKey ??= asString(sessionKey);
85
+ return;
86
+ }
87
+ this.runs.set(id, { startedAt: now, user: undefined, sessionKey: asString(sessionKey) });
88
+ }
89
+ /**
90
+ * A hook context described the run's origin. May arrive before the
91
+ * lifecycle start. Classifying a run as a user run consumes one queued
92
+ * message, since that message's run has now started.
93
+ */
94
+ onRunClassified(runId, info, now) {
95
+ const id = asString(runId);
96
+ if (!id)
97
+ return;
98
+ const user = isUserTriggeredRun(info);
99
+ let run = this.runs.get(id);
100
+ if (!run) {
101
+ run = { startedAt: now, user: undefined, sessionKey: asString(info.sessionKey) };
102
+ this.runs.set(id, run);
103
+ }
104
+ run.sessionKey ??= asString(info.sessionKey);
105
+ if (user && run.user !== true) {
106
+ this.dropExpiredQueued(now);
107
+ this.queued.shift();
108
+ }
109
+ // Once a user run, always a user run: a later hook with a thinner context
110
+ // must not downgrade it.
111
+ run.user = run.user === true ? true : user;
112
+ }
113
+ /** A run ended (lifecycle `end`/`error`, or the `agent_end` hook). */
114
+ onRunEnd(runId, _now) {
115
+ const id = asString(runId);
116
+ if (id)
117
+ this.runs.delete(id);
118
+ }
119
+ /**
120
+ * A subagent was spawned. It counts only when its requester session has an
121
+ * active user run, and then until `onSubagentEnded`, even after the parent
122
+ * run ends (the user is still waiting on the subagent's result).
123
+ */
124
+ onSubagentSpawned(event, now) {
125
+ const child = asString(event.childSessionKey);
126
+ const requester = asString(event.requesterSessionKey);
127
+ if (!child || !requester)
128
+ return;
129
+ for (const run of this.runs.values()) {
130
+ if (run.user === true && run.sessionKey === requester) {
131
+ this.subagents.set(child, { startedAt: now });
132
+ return;
133
+ }
134
+ }
135
+ }
136
+ /** A subagent ended (`subagent_ended`, keyed by its child/target session). */
137
+ onSubagentEnded(childSessionKey, _now) {
138
+ const child = asString(childSessionKey);
139
+ if (child)
140
+ this.subagents.delete(child);
141
+ }
142
+ /** A user message arrived (`message_received`). */
143
+ onUserMessage(now) {
144
+ this.lastUserMessageAt = now;
145
+ this.queued.push(now);
146
+ }
147
+ /**
148
+ * The idle decision at `now`. Idle only when no user run (or user subagent)
149
+ * is active, no user message is queued, no approval is pending, no order
150
+ * call is in flight, and the last user message is at least
151
+ * `recentMessageMs` old. A counter that throws counts as its blocker: an
152
+ * unknown state is never idle.
153
+ */
154
+ isIdle(now) {
155
+ this.dropStale(now);
156
+ this.dropExpiredQueued(now);
157
+ const blockers = [];
158
+ const userRun = [...this.runs.values()].some((r) => r.user === true) || this.subagents.size > 0;
159
+ if (userRun)
160
+ blockers.push("user-run");
161
+ if (this.queued.length > 0)
162
+ blockers.push("user-queued");
163
+ if (safeCount(this.options.pendingApprovals) > 0)
164
+ blockers.push("approval");
165
+ if (safeCount(this.options.ordersInFlight) > 0)
166
+ blockers.push("order");
167
+ if (safeFlag(this.options.deploysInFlight))
168
+ blockers.push("deploy");
169
+ if (this.lastUserMessageAt !== undefined && now - this.lastUserMessageAt < this.recentMessageMs) {
170
+ blockers.push("recent-message");
171
+ }
172
+ return { idle: blockers.length === 0, blockers };
173
+ }
174
+ dropExpiredQueued(now) {
175
+ this.queued = this.queued.filter((at) => now - at < this.queuedTtlMs);
176
+ }
177
+ dropStale(now) {
178
+ for (const [id, run] of this.runs) {
179
+ if (now - run.startedAt >= this.staleRunMs) {
180
+ this.runs.delete(id);
181
+ if (run.user === true) {
182
+ this.options.logger?.warn(`[senpi-auto-update] idle gate: dropped user run ${id} with no end after ${Math.round(this.staleRunMs / 60_000)} min`);
183
+ }
184
+ }
185
+ }
186
+ for (const [key, sub] of this.subagents) {
187
+ if (now - sub.startedAt >= this.staleRunMs) {
188
+ this.subagents.delete(key);
189
+ this.options.logger?.warn(`[senpi-auto-update] idle gate: dropped subagent ${key} with no end after ${Math.round(this.staleRunMs / 60_000)} min`);
190
+ }
191
+ }
192
+ }
193
+ }
194
+ /** Reads a counter, treating a throw as "at least one" so the gate fails busy. */
195
+ /** A flag reader that throws reads as set: an unknown state is never idle. */
196
+ function safeFlag(read) {
197
+ if (!read)
198
+ return false;
199
+ try {
200
+ return read() !== false;
201
+ }
202
+ catch {
203
+ return true;
204
+ }
205
+ }
206
+ function safeCount(read) {
207
+ if (!read)
208
+ return 0;
209
+ try {
210
+ const n = read();
211
+ return Number.isFinite(n) ? n : 1;
212
+ }
213
+ catch {
214
+ return 1;
215
+ }
216
+ }
217
+ //# sourceMappingURL=idle-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idle-gate.js","sourceRoot":"","sources":["../../../src/runtime/auto-update/idle-gate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,130 @@
1
+ import type { LogLevel } from "../../utils/logger.js";
2
+ /**
3
+ * The runtime update results file, schema version 1 (written by the senpi-agent wrapper).
4
+ *
5
+ * - Location: `$OPENCLAW_STATE_DIR/senpi-state/runtime-update-results.json`
6
+ * (`/data/.openclaw/senpi-state/runtime-update-results.json` on the senpi-agent image).
7
+ * - Encoding: one UTF-8 JSON object `{ "schemaVersion": 1, "results": [Entry, ...] }`, written
8
+ * atomically (temp file in the same directory, then rename). Oldest entry first, at most
9
+ * `maxResults`.
10
+ * - Entry, every field in `fields` present:
11
+ * - `id`: 16 lowercase hex chars, unique per entry (the de-duplication key).
12
+ * - `at`: ISO 8601 UTC time the step ended.
13
+ * - `source`: one of `sources`. `outcome`: one of `outcomes`.
14
+ * - `spec`, `fromVersion`, `toVersion`, `targetVersion`, `openclawVersion`: string or null.
15
+ * - `durationMs`: the CLI run time in ms, or null when no CLI install ran.
16
+ * - `reason`: first line, at most `maxReasonChars`; null on success.
17
+ * - Readers ignore fields they do not know. An outcome or source this runtime does not know is
18
+ * reported as written.
19
+ */
20
+ export declare const RUNTIME_UPDATE_RESULTS_SCHEMA: {
21
+ readonly schemaVersion: 1;
22
+ readonly path: "senpi-state/runtime-update-results.json";
23
+ readonly maxResults: 20;
24
+ readonly maxReasonChars: 300;
25
+ readonly sources: readonly ["marker", "floor"];
26
+ readonly outcomes: readonly ["updated", "failed", "timeout", "not-newer", "invalid", "lease-skip", "still-below"];
27
+ readonly fields: readonly ["id", "at", "source", "outcome", "spec", "fromVersion", "toVersion", "targetVersion", "durationMs", "reason", "openclawVersion"];
28
+ };
29
+ /** File name under `<OPENCLAW_STATE_DIR>/senpi-state/`, next to the update marker. */
30
+ export declare const INSTALL_RESULTS_FILE = "runtime-update-results.json";
31
+ /** The only results-file schema this runtime reads. */
32
+ export declare const INSTALL_RESULTS_SCHEMA_VERSION: 1;
33
+ /**
34
+ * How long the report waits for the OTel SDK init `register()` started (off the start path, so
35
+ * nothing else waits). Past it the file is kept for the next start.
36
+ */
37
+ export declare const INSTALL_RESULTS_OTEL_READY_MS = 10000;
38
+ /** How long the report waits for the logs flush before persisting the ids and deleting the file. */
39
+ export declare const INSTALL_RESULTS_OTEL_FLUSH_MS = 5000;
40
+ /** How many reported entry ids the auto-update state keeps for de-duplication. */
41
+ export declare const MAX_REPORTED_RESULT_IDS = 50;
42
+ /** The outcomes the wrapper writes. Anything else is reported as written, at `warn`. */
43
+ export declare const INSTALL_RESULT_OUTCOMES: readonly ["updated", "failed", "timeout", "not-newer", "invalid", "lease-skip", "still-below"];
44
+ /** One results-file entry after validation; a field that was missing or of the wrong type is null. */
45
+ export interface InstallResultEntry {
46
+ id: string | null;
47
+ at: string | null;
48
+ source: string | null;
49
+ /** `"unknown"` when the entry had no string outcome. */
50
+ outcome: string;
51
+ spec: string | null;
52
+ fromVersion: string | null;
53
+ toVersion: string | null;
54
+ targetVersion: string | null;
55
+ durationMs: number | null;
56
+ reason: string | null;
57
+ openclawVersion: string | null;
58
+ }
59
+ /** Where the results file lives for a given OpenClaw state dir. */
60
+ export declare function resolveInstallResultsPath(openclawStateDir: string): string;
61
+ /** The event level for an outcome: success info, a refused or skipped step warn, a failed one error. */
62
+ export declare function installResultLevel(outcome: string): LogLevel;
63
+ /**
64
+ * Validate one entry. Returns null only for a value that is not an object; every field of the
65
+ * wrong type is dropped (null), every string is capped.
66
+ */
67
+ export declare function sanitizeInstallResult(value: unknown): InstallResultEntry | null;
68
+ export type ParsedInstallResults = {
69
+ kind: "ok";
70
+ entries: InstallResultEntry[];
71
+ } | {
72
+ kind: "unparsable";
73
+ reason: string;
74
+ } | {
75
+ kind: "unsupported-schema";
76
+ schemaVersion: number;
77
+ };
78
+ /** Parse the file's text. A numeric schemaVersion other than 1 is a newer writer, not garbage. */
79
+ export declare function parseInstallResults(raw: string): ParsedInstallResults;
80
+ /**
81
+ * The event body: one deterministic line, e.g.
82
+ * `Wrapper runtime install (marker): updated @senpi-ai/runtime@3.0.180, 3.0.179 -> 3.0.180, target 3.0.180, 44s`.
83
+ */
84
+ export declare function describeInstallResult(e: InstallResultEntry): string;
85
+ interface WarnLogger {
86
+ warn(...args: unknown[]): void;
87
+ }
88
+ /**
89
+ * The emit path, so an entry is never deleted before its event could leave the process. Each
90
+ * call is bounded by the implementation and never throws.
91
+ */
92
+ export interface InstallResultsTelemetry {
93
+ /**
94
+ * Resolves `true` once events can be exported (the OTel SDK init settled, or no SDK will
95
+ * run), `false` when that did not happen in time: nothing is emitted and the file is kept for
96
+ * the next start.
97
+ */
98
+ ready(): Promise<boolean>;
99
+ /** Force-flush what was emitted. Runs before the ids are persisted and the file deleted. */
100
+ flush(): Promise<void>;
101
+ }
102
+ export interface ConsumeInstallResultsOptions {
103
+ path: string;
104
+ /** Ids reported before, oldest first (from the auto-update state). */
105
+ reportedIds: readonly string[];
106
+ /** Emit one event. The caller guards it; a throw here is caught and the entry counts as reported. */
107
+ emit: (entry: InstallResultEntry) => void;
108
+ /** Persist the new reported ids, before the file is deleted. A throw is one warning; the delete still runs. */
109
+ persistReportedIds: (ids: string[]) => Promise<void>;
110
+ /** Waited for before emitting and flushed after; omitted, events are emitted at once. */
111
+ telemetry?: InstallResultsTelemetry;
112
+ logger: WarnLogger;
113
+ }
114
+ export interface ConsumeInstallResultsResult {
115
+ /** The ids now on record: the previous ones plus those emitted now, the last 50. */
116
+ reportedIds: string[];
117
+ emitted: number;
118
+ skipped: number;
119
+ /** True when the emit path was not ready in time and the file was kept. */
120
+ deferred: boolean;
121
+ }
122
+ /**
123
+ * Read the results file and, for the entries not reported before: wait for the emit path,
124
+ * emit one event each, flush, persist their ids, then delete the file. That order is what keeps
125
+ * an entry from being lost (deleted before its event left) or reported twice (deleted, ids not
126
+ * saved, delete failed next time). Never throws.
127
+ */
128
+ export declare function consumeInstallResults(opts: ConsumeInstallResultsOptions): Promise<ConsumeInstallResultsResult>;
129
+ export {};
130
+ //# sourceMappingURL=install-results.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-results.d.ts","sourceRoot":"","sources":["../../../src/runtime/auto-update/install-results.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,264 @@
1
+ /**
2
+ * The wrapper's runtime install outcomes, reported as `auto_update.install_result` events
3
+ * (R2 Step 10 ↔ senpi-agent A12 W6).
4
+ *
5
+ * On OpenClaw 10.x the senpi-agent wrapper installs the runtime with the gateway down: the
6
+ * marker install before every spawn, and the `SENPI_RUNTIME_MIN_VERSION` floor at boot. The
7
+ * wrapper has no OTel exporter, so it appends each outcome to
8
+ * `$OPENCLAW_STATE_DIR/senpi-state/runtime-update-results.json` (senpi-agent
9
+ * `src/lib/runtimeUpdateResults.js`). The coordinator reads that file once per start, on a
10
+ * modern host and in the gateway process only (plugin services start only in the gateway),
11
+ * whether or not auto-update is enabled (operator markers and the floor install without it),
12
+ * emits one event per entry and deletes the file.
13
+ *
14
+ * The contract is {@link RUNTIME_UPDATE_RESULTS_SCHEMA}. Its twin is the wrapper's
15
+ * `RUNTIME_UPDATE_RESULTS_SCHEMA` in `src/lib/runtimeUpdateResults.js`; both must change
16
+ * together. The golden file `__tests__/fixtures/runtime-update-results.v1.json` is
17
+ * byte-identical to senpi-agent's `src/lib/__tests__/fixtures/runtime-update-results.v1.json`:
18
+ * the wrapper's writer output is compared against it, this parser parses it.
19
+ *
20
+ * The file is written by another program, so every field is checked here: a field of the wrong
21
+ * type is dropped, strings are capped. An unparsable file is deleted with one warning; a file
22
+ * with a numeric schemaVersion this runtime does not know is left for a newer runtime (the
23
+ * wrapper leaves it too). Ids already reported (the last {@link MAX_REPORTED_RESULT_IDS}, kept
24
+ * in the auto-update state) are skipped, so a failed delete never reports an entry twice.
25
+ *
26
+ * Validation is hand-written, not zod: the sibling on-disk files of this directory (the update
27
+ * marker in `update-marker.ts`, the state in `state-store.ts`) are parsed the same way, and this
28
+ * parser keeps every valid field of a partly bad entry instead of refusing the entry.
29
+ */
30
+ import { readFile, rm } from "node:fs/promises";
31
+ import { join } from "node:path";
32
+ /**
33
+ * The runtime update results file, schema version 1 (written by the senpi-agent wrapper).
34
+ *
35
+ * - Location: `$OPENCLAW_STATE_DIR/senpi-state/runtime-update-results.json`
36
+ * (`/data/.openclaw/senpi-state/runtime-update-results.json` on the senpi-agent image).
37
+ * - Encoding: one UTF-8 JSON object `{ "schemaVersion": 1, "results": [Entry, ...] }`, written
38
+ * atomically (temp file in the same directory, then rename). Oldest entry first, at most
39
+ * `maxResults`.
40
+ * - Entry, every field in `fields` present:
41
+ * - `id`: 16 lowercase hex chars, unique per entry (the de-duplication key).
42
+ * - `at`: ISO 8601 UTC time the step ended.
43
+ * - `source`: one of `sources`. `outcome`: one of `outcomes`.
44
+ * - `spec`, `fromVersion`, `toVersion`, `targetVersion`, `openclawVersion`: string or null.
45
+ * - `durationMs`: the CLI run time in ms, or null when no CLI install ran.
46
+ * - `reason`: first line, at most `maxReasonChars`; null on success.
47
+ * - Readers ignore fields they do not know. An outcome or source this runtime does not know is
48
+ * reported as written.
49
+ */
50
+ export const RUNTIME_UPDATE_RESULTS_SCHEMA = {
51
+ schemaVersion: 1,
52
+ path: "senpi-state/runtime-update-results.json",
53
+ maxResults: 20,
54
+ maxReasonChars: 300,
55
+ sources: ["marker", "floor"],
56
+ outcomes: ["updated", "failed", "timeout", "not-newer", "invalid", "lease-skip", "still-below"],
57
+ fields: [
58
+ "id",
59
+ "at",
60
+ "source",
61
+ "outcome",
62
+ "spec",
63
+ "fromVersion",
64
+ "toVersion",
65
+ "targetVersion",
66
+ "durationMs",
67
+ "reason",
68
+ "openclawVersion",
69
+ ],
70
+ };
71
+ /** File name under `<OPENCLAW_STATE_DIR>/senpi-state/`, next to the update marker. */
72
+ export const INSTALL_RESULTS_FILE = "runtime-update-results.json";
73
+ /** The only results-file schema this runtime reads. */
74
+ export const INSTALL_RESULTS_SCHEMA_VERSION = RUNTIME_UPDATE_RESULTS_SCHEMA.schemaVersion;
75
+ /**
76
+ * How long the report waits for the OTel SDK init `register()` started (off the start path, so
77
+ * nothing else waits). Past it the file is kept for the next start.
78
+ */
79
+ export const INSTALL_RESULTS_OTEL_READY_MS = 10_000;
80
+ /** How long the report waits for the logs flush before persisting the ids and deleting the file. */
81
+ export const INSTALL_RESULTS_OTEL_FLUSH_MS = 5_000;
82
+ /** How many reported entry ids the auto-update state keeps for de-duplication. */
83
+ export const MAX_REPORTED_RESULT_IDS = 50;
84
+ /** The outcomes the wrapper writes. Anything else is reported as written, at `warn`. */
85
+ export const INSTALL_RESULT_OUTCOMES = RUNTIME_UPDATE_RESULTS_SCHEMA.outcomes;
86
+ const MAX_REASON_CHARS = RUNTIME_UPDATE_RESULTS_SCHEMA.maxReasonChars;
87
+ const MAX_FIELD_CHARS = 200;
88
+ const MAX_TOKEN_CHARS = 40;
89
+ const ID_RE = /^[0-9A-Za-z_-]{1,64}$/;
90
+ /** Where the results file lives for a given OpenClaw state dir. */
91
+ export function resolveInstallResultsPath(openclawStateDir) {
92
+ return join(openclawStateDir, RUNTIME_UPDATE_RESULTS_SCHEMA.path);
93
+ }
94
+ /** The event level for an outcome: success info, a refused or skipped step warn, a failed one error. */
95
+ export function installResultLevel(outcome) {
96
+ switch (outcome) {
97
+ case "updated":
98
+ case "not-newer":
99
+ return "info";
100
+ case "failed":
101
+ case "timeout":
102
+ case "still-below":
103
+ return "error";
104
+ default:
105
+ // "invalid", "lease-skip", and an outcome a newer wrapper added.
106
+ return "warn";
107
+ }
108
+ }
109
+ /** First non-empty line, whitespace collapsed, capped; null for a non-string or an empty one. */
110
+ function cleanString(value, max) {
111
+ if (typeof value !== "string")
112
+ return null;
113
+ const line = value.split("\n").map((l) => l.replace(/\s+/g, " ").trim()).find((l) => l.length > 0);
114
+ return line ? line.slice(0, max) : null;
115
+ }
116
+ /**
117
+ * Validate one entry. Returns null only for a value that is not an object; every field of the
118
+ * wrong type is dropped (null), every string is capped.
119
+ */
120
+ export function sanitizeInstallResult(value) {
121
+ if (!value || typeof value !== "object" || Array.isArray(value))
122
+ return null;
123
+ const v = value;
124
+ const id = typeof v.id === "string" && ID_RE.test(v.id) ? v.id : null;
125
+ const at = cleanString(v.at, MAX_TOKEN_CHARS);
126
+ const duration = v.durationMs;
127
+ return {
128
+ id,
129
+ at: at && Number.isFinite(Date.parse(at)) ? at : null,
130
+ source: cleanString(v.source, MAX_TOKEN_CHARS),
131
+ outcome: cleanString(v.outcome, MAX_TOKEN_CHARS) ?? "unknown",
132
+ spec: cleanString(v.spec, MAX_FIELD_CHARS),
133
+ fromVersion: cleanString(v.fromVersion, MAX_TOKEN_CHARS),
134
+ toVersion: cleanString(v.toVersion, MAX_TOKEN_CHARS),
135
+ targetVersion: cleanString(v.targetVersion, MAX_TOKEN_CHARS),
136
+ durationMs: typeof duration === "number" && Number.isFinite(duration) && duration >= 0 ? Math.round(duration) : null,
137
+ reason: cleanString(v.reason, MAX_REASON_CHARS),
138
+ openclawVersion: cleanString(v.openclawVersion, MAX_TOKEN_CHARS),
139
+ };
140
+ }
141
+ /** Parse the file's text. A numeric schemaVersion other than 1 is a newer writer, not garbage. */
142
+ export function parseInstallResults(raw) {
143
+ let value;
144
+ try {
145
+ value = JSON.parse(raw);
146
+ }
147
+ catch (error) {
148
+ return { kind: "unparsable", reason: `not JSON (${error instanceof Error ? error.message : String(error)})` };
149
+ }
150
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
151
+ return { kind: "unparsable", reason: "not a JSON object" };
152
+ }
153
+ const { schemaVersion, results } = value;
154
+ if (typeof schemaVersion === "number" && Number.isFinite(schemaVersion) && schemaVersion !== INSTALL_RESULTS_SCHEMA_VERSION) {
155
+ return { kind: "unsupported-schema", schemaVersion };
156
+ }
157
+ if (schemaVersion !== INSTALL_RESULTS_SCHEMA_VERSION) {
158
+ return { kind: "unparsable", reason: `schemaVersion ${JSON.stringify(schemaVersion) ?? "undefined"} is not 1` };
159
+ }
160
+ if (!Array.isArray(results))
161
+ return { kind: "unparsable", reason: "results is not an array" };
162
+ const entries = [];
163
+ for (const r of results) {
164
+ const entry = sanitizeInstallResult(r);
165
+ if (entry)
166
+ entries.push(entry);
167
+ }
168
+ return { kind: "ok", entries };
169
+ }
170
+ /**
171
+ * The event body: one deterministic line, e.g.
172
+ * `Wrapper runtime install (marker): updated @senpi-ai/runtime@3.0.180, 3.0.179 -> 3.0.180, target 3.0.180, 44s`.
173
+ */
174
+ export function describeInstallResult(e) {
175
+ const parts = [`${e.spec ?? "unknown spec"}`, `${e.fromVersion ?? "?"} -> ${e.toVersion ?? "?"}`];
176
+ if (e.targetVersion)
177
+ parts.push(`target ${e.targetVersion}`);
178
+ if (e.durationMs !== null)
179
+ parts.push(`${Math.round(e.durationMs / 1000)}s`);
180
+ const tail = e.reason ? `: ${e.reason}` : "";
181
+ return `Wrapper runtime install (${e.source ?? "unknown"}): ${e.outcome} ${parts.join(", ")}${tail}`;
182
+ }
183
+ /**
184
+ * Read the results file and, for the entries not reported before: wait for the emit path,
185
+ * emit one event each, flush, persist their ids, then delete the file. That order is what keeps
186
+ * an entry from being lost (deleted before its event left) or reported twice (deleted, ids not
187
+ * saved, delete failed next time). Never throws.
188
+ */
189
+ export async function consumeInstallResults(opts) {
190
+ const { path, logger } = opts;
191
+ const kept = opts.reportedIds.filter((id) => typeof id === "string");
192
+ const unchanged = { reportedIds: kept.slice(-MAX_REPORTED_RESULT_IDS), emitted: 0, skipped: 0, deferred: false };
193
+ let raw;
194
+ try {
195
+ raw = await readFile(path, "utf8");
196
+ }
197
+ catch (error) {
198
+ if (error?.code !== "ENOENT") {
199
+ logger.warn(`[senpi-auto-update] could not read the wrapper's install results | path=${path} err=${formatErr(error)}`);
200
+ }
201
+ return unchanged;
202
+ }
203
+ const parsed = parseInstallResults(raw);
204
+ if (parsed.kind === "unsupported-schema") {
205
+ logger.warn(`[senpi-auto-update] wrapper install results use schemaVersion ${parsed.schemaVersion}, which this runtime does not read; leaving the file for a newer runtime | path=${path}`);
206
+ return unchanged;
207
+ }
208
+ if (parsed.kind === "unparsable") {
209
+ logger.warn(`[senpi-auto-update] wrapper install results unparsable (${parsed.reason}); deleting the file | path=${path}`);
210
+ await removeQuietly(path, logger);
211
+ return unchanged;
212
+ }
213
+ const seen = new Set(kept);
214
+ const fresh = [];
215
+ for (const entry of parsed.entries) {
216
+ if (entry.id !== null && seen.has(entry.id))
217
+ continue;
218
+ if (entry.id !== null)
219
+ seen.add(entry.id);
220
+ fresh.push(entry);
221
+ }
222
+ const skipped = parsed.entries.length - fresh.length;
223
+ if (fresh.length === 0) {
224
+ await removeQuietly(path, logger);
225
+ return { ...unchanged, skipped };
226
+ }
227
+ if (opts.telemetry && !(await opts.telemetry.ready())) {
228
+ logger.warn(`[senpi-auto-update] telemetry not ready in time; keeping ${fresh.length} wrapper install result(s) for the next start | path=${path}`);
229
+ return { ...unchanged, skipped, deferred: true };
230
+ }
231
+ const ids = [...kept];
232
+ for (const entry of fresh) {
233
+ try {
234
+ opts.emit(entry);
235
+ }
236
+ catch {
237
+ // Telemetry never breaks the coordinator; the entry still counts as reported.
238
+ }
239
+ if (entry.id !== null)
240
+ ids.push(entry.id);
241
+ }
242
+ await opts.telemetry?.flush();
243
+ const reportedIds = ids.slice(-MAX_REPORTED_RESULT_IDS);
244
+ try {
245
+ await opts.persistReportedIds(reportedIds);
246
+ }
247
+ catch (error) {
248
+ logger.warn(`[senpi-auto-update] could not save the reported install result ids; if the delete below also fails they are reported again next start | err=${formatErr(error)}`);
249
+ }
250
+ await removeQuietly(path, logger);
251
+ return { reportedIds, emitted: fresh.length, skipped, deferred: false };
252
+ }
253
+ async function removeQuietly(path, logger) {
254
+ try {
255
+ await rm(path, { force: true });
256
+ }
257
+ catch (error) {
258
+ logger.warn(`[senpi-auto-update] could not delete the wrapper's install results; reported ids prevent a repeat | path=${path} err=${formatErr(error)}`);
259
+ }
260
+ }
261
+ function formatErr(error) {
262
+ return error instanceof Error ? error.message : String(error);
263
+ }
264
+ //# sourceMappingURL=install-results.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-results.js","sourceRoot":"","sources":["../../../src/runtime/auto-update/install-results.ts"],"names":[],"mappings":"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"}