@senpi/runtime 3.0.21 → 3.0.23

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 (136) hide show
  1. package/README.md +7 -4
  2. package/dist/actions/close-position/close-position.action.d.ts.map +1 -1
  3. package/dist/actions/close-position/close-position.action.js +8 -6
  4. package/dist/actions/close-position/close-position.action.js.map +1 -1
  5. package/dist/actions/decision-error-notification.d.ts +14 -0
  6. package/dist/actions/decision-error-notification.d.ts.map +1 -0
  7. package/dist/actions/decision-error-notification.js +36 -0
  8. package/dist/actions/decision-error-notification.js.map +1 -0
  9. package/dist/actions/open-position/open-position.action.d.ts.map +1 -1
  10. package/dist/actions/open-position/open-position.action.js +8 -6
  11. package/dist/actions/open-position/open-position.action.js.map +1 -1
  12. package/dist/actions/types.d.ts +2 -0
  13. package/dist/actions/types.d.ts.map +1 -1
  14. package/dist/cli/guide-commands.d.ts.map +1 -1
  15. package/dist/cli/guide-commands.js +5 -7
  16. package/dist/cli/guide-commands.js.map +1 -1
  17. package/dist/cli/senpi-cli-config.d.ts +0 -17
  18. package/dist/cli/senpi-cli-config.d.ts.map +1 -1
  19. package/dist/cli/senpi-cli-config.js +0 -30
  20. package/dist/cli/senpi-cli-config.js.map +1 -1
  21. package/dist/cli/senpi-config-commands.d.ts +1 -1
  22. package/dist/cli/senpi-config-commands.d.ts.map +1 -1
  23. package/dist/cli/senpi-config-commands.js +1 -16
  24. package/dist/cli/senpi-config-commands.js.map +1 -1
  25. package/dist/constants/dsl/index.d.ts +1 -5
  26. package/dist/constants/dsl/index.d.ts.map +1 -1
  27. package/dist/constants/dsl/index.js +1 -5
  28. package/dist/constants/dsl/index.js.map +1 -1
  29. package/dist/dsl/engine-client/event-mapping.d.ts +1 -1
  30. package/dist/dsl/engine-client/event-mapping.d.ts.map +1 -1
  31. package/dist/dsl/events/build-closed-event.d.ts +1 -1
  32. package/dist/dsl/events/build-closed-event.js +1 -1
  33. package/dist/dsl/events/handlers.js +1 -1
  34. package/dist/dsl/monitor/process-one-position.js +3 -3
  35. package/dist/dsl/monitor/process-one-position.js.map +1 -1
  36. package/dist/dsl/notifications/format-utils.d.ts +1 -11
  37. package/dist/dsl/notifications/format-utils.d.ts.map +1 -1
  38. package/dist/dsl/notifications/format-utils.js +1 -48
  39. package/dist/dsl/notifications/format-utils.js.map +1 -1
  40. package/dist/dsl/notifications/formatter.d.ts +9 -25
  41. package/dist/dsl/notifications/formatter.d.ts.map +1 -1
  42. package/dist/dsl/notifications/formatter.js +20 -105
  43. package/dist/dsl/notifications/formatter.js.map +1 -1
  44. package/dist/dsl/notifications/index.d.ts +2 -8
  45. package/dist/dsl/notifications/index.d.ts.map +1 -1
  46. package/dist/dsl/notifications/index.js +60 -57
  47. package/dist/dsl/notifications/index.js.map +1 -1
  48. package/dist/dsl/plugin/index.d.ts.map +1 -1
  49. package/dist/dsl/plugin/index.js +1 -27
  50. package/dist/dsl/plugin/index.js.map +1 -1
  51. package/dist/dsl/plugin/options.d.ts +3 -2
  52. package/dist/dsl/plugin/options.d.ts.map +1 -1
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +2 -122
  55. package/dist/index.js.map +1 -1
  56. package/dist/prompt/session-context-seed-hook.d.ts +11 -0
  57. package/dist/prompt/session-context-seed-hook.d.ts.map +1 -0
  58. package/dist/prompt/session-context-seed-hook.js +99 -0
  59. package/dist/prompt/session-context-seed-hook.js.map +1 -0
  60. package/dist/runtime/auto-update/coordinator.d.ts +5 -6
  61. package/dist/runtime/auto-update/coordinator.d.ts.map +1 -1
  62. package/dist/runtime/auto-update/coordinator.js +19 -20
  63. package/dist/runtime/auto-update/coordinator.js.map +1 -1
  64. package/dist/runtime/auto-update/types.d.ts.map +1 -1
  65. package/dist/runtime/auto-update/types.js +1 -2
  66. package/dist/runtime/auto-update/types.js.map +1 -1
  67. package/dist/runtime/create-action-context.d.ts.map +1 -1
  68. package/dist/runtime/create-action-context.js +1 -0
  69. package/dist/runtime/create-action-context.js.map +1 -1
  70. package/dist/runtime/create-runtime-context.d.ts +1 -0
  71. package/dist/runtime/create-runtime-context.d.ts.map +1 -1
  72. package/dist/runtime/create-runtime-context.js +1 -0
  73. package/dist/runtime/create-runtime-context.js.map +1 -1
  74. package/dist/runtime/decision-errors.d.ts +41 -53
  75. package/dist/runtime/decision-errors.d.ts.map +1 -1
  76. package/dist/runtime/decision-errors.js +48 -111
  77. package/dist/runtime/decision-errors.js.map +1 -1
  78. package/dist/runtime/notifications.d.ts +21 -14
  79. package/dist/runtime/notifications.d.ts.map +1 -1
  80. package/dist/runtime/notifications.js +192 -59
  81. package/dist/runtime/notifications.js.map +1 -1
  82. package/dist/runtime/runtime.d.ts +16 -2
  83. package/dist/runtime/runtime.d.ts.map +1 -1
  84. package/dist/runtime/runtime.js +106 -26
  85. package/dist/runtime/runtime.js.map +1 -1
  86. package/dist/runtime/strategy-lifecycle.d.ts +5 -5
  87. package/dist/runtime/strategy-lifecycle.d.ts.map +1 -1
  88. package/dist/runtime/strategy-lifecycle.js +96 -28
  89. package/dist/runtime/strategy-lifecycle.js.map +1 -1
  90. package/dist/runtime/supervisor/long-running-supervisor.js +3 -3
  91. package/dist/runtime/supervisor/long-running-supervisor.js.map +1 -1
  92. package/dist/runtime-api/lifecycle.d.ts +1 -1
  93. package/dist/runtime-api/lifecycle.js +1 -1
  94. package/dist/scripts/run-dsl-standalone-live.d.ts +7 -6
  95. package/dist/scripts/run-dsl-standalone-live.d.ts.map +1 -1
  96. package/dist/scripts/run-dsl-standalone-live.js +25 -21
  97. package/dist/scripts/run-dsl-standalone-live.js.map +1 -1
  98. package/dist/skills-manager/coordinator.d.ts +2 -2
  99. package/dist/skills-manager/coordinator.js +2 -2
  100. package/dist/test-setup.d.ts +2 -0
  101. package/dist/test-setup.d.ts.map +1 -0
  102. package/dist/test-setup.js +23 -0
  103. package/dist/test-setup.js.map +1 -0
  104. package/dist/types/dsl/index.d.ts +1 -1
  105. package/dist/types/notification.d.ts +95 -1
  106. package/dist/types/notification.d.ts.map +1 -1
  107. package/dist/types/notification.js +76 -1
  108. package/dist/types/notification.js.map +1 -1
  109. package/dist/types/runtime.d.ts +6 -0
  110. package/dist/types/runtime.d.ts.map +1 -1
  111. package/dist/types/session-context.d.ts +19 -0
  112. package/dist/types/session-context.d.ts.map +1 -0
  113. package/dist/types/session-context.js +20 -0
  114. package/dist/types/session-context.js.map +1 -0
  115. package/dist/utils/logger.d.ts +2 -18
  116. package/dist/utils/logger.d.ts.map +1 -1
  117. package/dist/utils/logger.js +2 -40
  118. package/dist/utils/logger.js.map +1 -1
  119. package/openclaw.plugin.json +2 -2
  120. package/package.json +1 -1
  121. package/dist/actions/notifications/formatter.d.ts +0 -39
  122. package/dist/actions/notifications/formatter.d.ts.map +0 -1
  123. package/dist/actions/notifications/formatter.js +0 -68
  124. package/dist/actions/notifications/formatter.js.map +0 -1
  125. package/dist/actions/notifications/index.d.ts +0 -37
  126. package/dist/actions/notifications/index.d.ts.map +0 -1
  127. package/dist/actions/notifications/index.js +0 -89
  128. package/dist/actions/notifications/index.js.map +0 -1
  129. package/dist/dsl/notifications/close-resolver.d.ts +0 -92
  130. package/dist/dsl/notifications/close-resolver.d.ts.map +0 -1
  131. package/dist/dsl/notifications/close-resolver.js +0 -309
  132. package/dist/dsl/notifications/close-resolver.js.map +0 -1
  133. package/dist/dsl/notifications/types.d.ts +0 -27
  134. package/dist/dsl/notifications/types.d.ts.map +0 -1
  135. package/dist/dsl/notifications/types.js +0 -5
  136. package/dist/dsl/notifications/types.js.map +0 -1
@@ -1,5 +1,6 @@
1
1
  /**
2
- * Classification of LLM-decision failures into user-facing messages + a retry flag.
2
+ * Classification of LLM-decision failures into a stable code, a retry flag, and
3
+ * a two-bucket user-facing message.
3
4
  *
4
5
  * ## Why this module exists
5
6
  *
@@ -9,21 +10,25 @@
9
10
  * need to make decisions from that string:
10
11
  *
11
12
  * 1. The **notification layer** ({@link formatDecisionErrorNotification} in
12
- * `notifications.ts`) — what to tell the skill author in Telegram.
13
+ * `notifications.ts`) — what to tell the skill author.
13
14
  * 2. The **decision client** (`openclaw-llm-decision.ts`) — whether a retry can
14
15
  * plausibly succeed.
15
16
  *
16
- * ## Three pieces, separated on purpose
17
+ * ## Two pieces
17
18
  *
18
- * 1. {@link DECISION_ERROR_MESSAGES} — a **flat catalog** of every user-facing
19
- * string, keyed by {@link DecisionErrorCode}. This is the single place to read,
20
- * review, or edit copy. Templates use `{placeholder}` tokens.
21
- * 2. {@link DECISION_ERROR_TABLE} — the ordered matchers (raw string → code) and
22
- * the `retryable` flag, plus the per-code logic that produces template values.
23
- * 3. {@link classifyDecisionError} — walks the table, renders the catalog entry.
19
+ * 1. {@link DECISION_ERROR_TABLE} — the ordered matchers (raw string → code)
20
+ * and the `retryable` flag. Per-code detail is preserved here for logs and
21
+ * telemetry, even though the two consumers above only see the collapsed
22
+ * bucket.
23
+ * 2. {@link classifyDecisionError} — walks the table, then renders the
24
+ * user-facing message from {@link DECISION_ERROR_BUCKET_COPY}, keyed only
25
+ * by `retryable` (see {@link decisionErrorBucket}). Author-facing copy
26
+ * does not vary by code — dozens of failure-mode-specific messages proved
27
+ * to be more than users act on; the two buckets are "this will likely
28
+ * sort itself out" vs. "this needs you to look at it."
24
29
  *
25
- * To change wording: edit {@link DECISION_ERROR_MESSAGES} only. To change matching
26
- * or retry behavior: edit {@link DECISION_ERROR_TABLE} only.
30
+ * To change matching or retry behavior: edit {@link DECISION_ERROR_TABLE}.
31
+ * To change user-facing wording: edit {@link DECISION_ERROR_BUCKET_COPY}.
27
32
  *
28
33
  * ## Where the raw strings come from
29
34
  *
@@ -89,79 +94,11 @@ export var DecisionErrorCode;
89
94
  DecisionErrorCode["Unknown"] = "UNKNOWN";
90
95
  })(DecisionErrorCode || (DecisionErrorCode = {}));
91
96
  /**
92
- * The single catalog of every user-facing message, keyed by {@link DecisionErrorCode}.
93
- *
94
- * Templates use `{token}` placeholders, filled by the matching entry's `vars`
95
- * function in {@link DECISION_ERROR_TABLE} (e.g. `{name}` = action name, `{model}`,
96
- * `{list}`, `{errors}`, `{level}`, `{hint}`, `{providerModel}`, `{raw}`). A `{token}`
97
- * with no supplied value is left verbatim so a missing var is visible, not silent.
98
- *
99
- * **This is the only place to edit copy.** Nothing else hardcodes a message.
100
- */
101
- export const DECISION_ERROR_MESSAGES = {
102
- [DecisionErrorCode.GatewayTokenMissing]: "Senpi: OpenClaw gateway token not configured. Set OPENCLAW_GATEWAY_TOKEN.",
103
- [DecisionErrorCode.GatewayUnreachable]: "Senpi: OpenClaw Gateway is unreachable.",
104
- [DecisionErrorCode.PluginEndpointMissing]: "Senpi: OpenClaw is missing the plugin endpoint. Please reinstall the plugin.",
105
- [DecisionErrorCode.PromptRequired]: "Your agent can't trade until its template is fixed. Action `{name}` in your runtime YAML is " +
106
- "missing a `decision_prompt`. To recover: delete this runtime, ask your OpenClaw agent to add a " +
107
- "`decision_prompt` to that action, and install it again. If you didn't write this skill, contact " +
108
- "Senpi support.",
109
- [DecisionErrorCode.ProviderModelUnresolved]: "Your agent can't trade because the AI model `{model}` isn't recognized by your OpenClaw host. " +
110
- "To recover: delete this runtime, set a valid `decision_model` in your runtime YAML, and install it " +
111
- "again. If you didn't write this skill, contact Senpi support.",
112
- [DecisionErrorCode.ModelNotAllowed]: "Your agent can't trade because `{model}` isn't on your OpenClaw host's allowed-models list. " +
113
- "Allowed: `{list}`. To recover: pick a model from the allowed list (delete the runtime, edit " +
114
- "`decision_model` in the runtime YAML, install again) or add it to your host's allowedModels config.",
115
- [DecisionErrorCode.InputNotSerializable]: "Your agent hit an internal bug while building the AI request (data wasn't serializable). Please " +
116
- "report this to Senpi support — it isn't something you can fix from your side.",
117
- [DecisionErrorCode.LlmEmptyOutput]: "Your agent dropped one signal because the AI model returned no answer. This is usually a safety " +
118
- "filter or a transient model quirk. If it keeps happening, try a different `decision_model` in your " +
119
- "runtime YAML (delete the runtime, edit, reinstall) or rephrase your `decision_prompt` to be less " +
120
- "ambiguous.",
121
- [DecisionErrorCode.LlmInvalidJson]: "Your agent dropped one signal because the AI model's reply wasn't valid JSON — usually a truncation " +
122
- "or a prompt that didn't force JSON output. To recover: tighten your `decision_prompt` in the runtime " +
123
- "YAML to require strict JSON, then delete and reinstall the runtime. If you didn't write this skill, " +
124
- "contact Senpi support.",
125
- [DecisionErrorCode.LlmSchemaMismatch]: "Your agent dropped one signal because the AI model's reply was valid JSON but didn't match the " +
126
- "expected shape: `{errors}`. To recover: tweak your `decision_prompt` so the model returns the " +
127
- "required fields, or have your OpenClaw agent loosen the schema (delete the runtime, edit the YAML, " +
128
- "reinstall). If you didn't write this skill, contact Senpi support.",
129
- [DecisionErrorCode.ProviderError]: "Your agent dropped one signal because the AI provider rejected the call (e.g. rate limit or outage). " +
130
- "Usually transient — the next signal often goes through. If it keeps happening, try switching " +
131
- "providers or using a different `decision_model` in your runtime YAML.",
132
- [DecisionErrorCode.Timeout]: "Your agent dropped one signal because the AI model didn't reply in 90 seconds. This is usually " +
133
- "transient — the next signal often goes through. If it keeps happening, switch to a faster " +
134
- "`decision_model` in your runtime YAML (delete the runtime, edit, reinstall).",
135
- [DecisionErrorCode.EmbeddedAgentUnavailable]: "Your agent dropped one signal because OpenClaw's internal LLM helper failed to load on your host. " +
136
- "This usually means the OpenClaw install is corrupted or partially upgraded. To recover: reinstall " +
137
- "OpenClaw on your host (or upgrade to the latest version — this error no longer exists in v2026.5.7+). " +
138
- "If it keeps happening after reinstall, contact Senpi support.",
139
- [DecisionErrorCode.InvalidThinkingLevel]: "Your agent's runtime YAML uses an unknown thinking level (`{level}`). Valid options: `{hint}`. " +
140
- "To recover: delete this runtime, set `thinking` to a valid value in the runtime YAML, and install it " +
141
- "again. If you didn't write this skill, contact Senpi support.",
142
- [DecisionErrorCode.ThinkingLevelUnsupported]: "Your agent's model (`{providerModel}`) doesn't support the thinking level `{level}`. Supported for " +
143
- "this model: `{hint}`. To recover: delete this runtime, change either the model or the thinking level " +
144
- "in the runtime YAML, and install it again.",
145
- [DecisionErrorCode.PluginResponseShape]: "Your agent hit an internal bug parsing the AI model's response. Please report this to Senpi support " +
146
- "— it isn't something you can fix from your side.",
147
- [DecisionErrorCode.OpenClawUnexpected]: "Senpi: Signal Dropped - OpenClaw hit an unexpected error.",
148
- [DecisionErrorCode.Unknown]: "Senpi: Trading decision failed — {raw}",
149
- };
150
- /** Trim a captured token; falls back to `fallback` when empty/absent. */
151
- function clean(value, fallback) {
152
- const t = value?.trim();
153
- return t && t.length > 0 ? t : fallback;
154
- }
155
- /** Fill `{token}` placeholders from `vars`; unknown tokens are left verbatim. */
156
- function renderTemplate(template, vars) {
157
- return template.replace(/\{(\w+)\}/g, (whole, key) => Object.prototype.hasOwnProperty.call(vars, key) ? vars[key] : whole);
158
- }
159
- /**
160
- * Ordered classification table — matchers + retry flag + template-variable logic.
161
- * First match wins, so keep specific matchers above generic ones (e.g.
162
- * `LlmEmptyOutput` before the catch-all `ProviderError`, since both can appear
163
- * inside a `"rejected request: ..."` wrapper). Copy lives in
164
- * {@link DECISION_ERROR_MESSAGES}, not here.
97
+ * Ordered classification table — matchers + retry flag. First match wins, so
98
+ * keep specific matchers above generic ones (e.g. `LlmEmptyOutput` before the
99
+ * catch-all `ProviderError`, since both can appear inside a `"rejected
100
+ * request: ..."` wrapper). `code` and `retryable` are the source of truth for
101
+ * logs/telemetry; user-facing copy lives in {@link DECISION_ERROR_BUCKET_COPY}.
165
102
  */
166
103
  export const DECISION_ERROR_TABLE = [
167
104
  // ── Pre-HTTP / infra (not retryable) ──────────────────────────────────────
@@ -185,22 +122,16 @@ export const DECISION_ERROR_TABLE = [
185
122
  code: DecisionErrorCode.PromptRequired,
186
123
  match: (s) => /prompt required/i.test(s),
187
124
  retryable: false,
188
- vars: (_caps, ctx) => ({ name: clean(ctx.actionName, "<name>") }),
189
125
  },
190
126
  {
191
127
  code: DecisionErrorCode.ProviderModelUnresolved,
192
128
  match: (s) => s.match(/provider\/model could not be resolved \(provider=(.*?), model=(.*?)\)/),
193
129
  retryable: false,
194
- vars: (caps, ctx) => ({ model: clean(caps?.[2], clean(ctx.model, "your configured model")) }),
195
130
  },
196
131
  {
197
132
  code: DecisionErrorCode.ModelNotAllowed,
198
133
  match: (s) => s.match(/Model not allowed by llm-task plugin config:\s*([^.]+)\.\s*Allowed models:\s*([^"}]+)/),
199
134
  retryable: false,
200
- vars: (caps, ctx) => ({
201
- model: clean(caps?.[1], clean(ctx.model, "that model")),
202
- list: clean(caps?.[2], "(see your host's allowedModels config)"),
203
- }),
204
135
  },
205
136
  {
206
137
  code: DecisionErrorCode.InputNotSerializable,
@@ -225,27 +156,17 @@ export const DECISION_ERROR_TABLE = [
225
156
  code: DecisionErrorCode.LlmSchemaMismatch,
226
157
  match: (s) => s.match(/LLM JSON did not match schema:\s*([^"}]+)/),
227
158
  retryable: false,
228
- vars: (caps) => ({ errors: clean(caps?.[1], "(schema validation failed)") }),
229
159
  },
230
160
  // ── Thinking-level misconfig (v2026.5.7+, not retryable) ──────────────────
231
161
  {
232
162
  code: DecisionErrorCode.InvalidThinkingLevel,
233
163
  match: (s) => s.match(/Invalid thinking level "(.*?)"\.\s*Use one of:\s*([^"}]+?)\./),
234
164
  retryable: false,
235
- vars: (caps) => ({
236
- level: clean(caps?.[1], "that value"),
237
- hint: clean(caps?.[2], "the supported levels"),
238
- }),
239
165
  },
240
166
  {
241
167
  code: DecisionErrorCode.ThinkingLevelUnsupported,
242
168
  match: (s) => s.match(/Thinking level "(.*?)" is not supported for (.+?)\.\s*Use one of:\s*([^"}]+?)\./),
243
169
  retryable: false,
244
- vars: (caps, ctx) => ({
245
- level: clean(caps?.[1], "that level"),
246
- providerModel: clean(caps?.[2], clean(ctx.model, "this model")),
247
- hint: clean(caps?.[3], "the supported levels"),
248
- }),
249
170
  },
250
171
  // ── Host/load failure (not retryable) ─────────────────────────────────────
251
172
  {
@@ -284,35 +205,51 @@ export const DECISION_ERROR_TABLE = [
284
205
  retryable: false,
285
206
  },
286
207
  ];
208
+ /** Map a classified error's retry flag to its user-facing bucket. */
209
+ export function decisionErrorBucket(retryable) {
210
+ return retryable ? "transient" : "attention";
211
+ }
212
+ /** Label-free copy for each bucket — the emit site applies the `[strategy]` prefix. */
213
+ export const DECISION_ERROR_BUCKET_COPY = {
214
+ transient: {
215
+ pushTitle: "⚠️ Signal skipped",
216
+ pushBody: "Skipped a signal (transient) — still running, next one will go",
217
+ inApp: "⚠️ Skipped one trade signal — a brief issue deciding the trade. Still running; it'll take the next one. " +
218
+ "We've logged it — contact support and we'll help fix it.",
219
+ },
220
+ attention: {
221
+ pushTitle: "⚠️ Needs attention",
222
+ pushBody: "Your strategy needs attention before it can trade",
223
+ inApp: "⚠️ Your strategy needs attention before it can trade. We've logged it — contact support and we'll help fix it.",
224
+ },
225
+ };
287
226
  /**
288
227
  * Classify a raw LLM-decision error string into a {@link ClassifiedDecisionError}.
289
228
  *
290
- * Walks {@link DECISION_ERROR_TABLE} in order; the first matching entry wins. If
291
- * nothing matches, returns {@link DecisionErrorCode.Unknown} (not retryable). The
292
- * message is always rendered from {@link DECISION_ERROR_MESSAGES}.
229
+ * Walks {@link DECISION_ERROR_TABLE} in order; the first matching entry wins,
230
+ * giving `code` + `retryable` (preserved for logs/telemetry). If nothing
231
+ * matches, returns {@link DecisionErrorCode.Unknown} (not retryable). The
232
+ * user-facing `userMessage` is always the collapsed bucket's in-app copy from
233
+ * {@link DECISION_ERROR_BUCKET_COPY} — it does not vary by code.
293
234
  *
294
235
  * @param rawMessage - The error string thrown by the decision client (may embed an
295
236
  * OpenClaw gateway message). Non-string / empty inputs are coerced safely.
296
- * @param ctx - Optional plugin-side context for placeholders the gateway can't supply.
297
237
  */
298
- export function classifyDecisionError(rawMessage, ctx = {}) {
238
+ export function classifyDecisionError(rawMessage) {
299
239
  const raw = typeof rawMessage === "string" ? rawMessage : String(rawMessage ?? "");
300
240
  for (const entry of DECISION_ERROR_TABLE) {
301
- const result = entry.match(raw);
302
- if (!result)
241
+ if (!entry.match(raw))
303
242
  continue;
304
- const caps = Array.isArray(result) ? result : null;
305
- const vars = entry.vars ? entry.vars(caps, ctx, raw) : {};
306
243
  return {
307
244
  code: entry.code,
308
245
  retryable: entry.retryable,
309
- userMessage: renderTemplate(DECISION_ERROR_MESSAGES[entry.code], vars),
246
+ userMessage: DECISION_ERROR_BUCKET_COPY[decisionErrorBucket(entry.retryable)].inApp,
310
247
  };
311
248
  }
312
249
  return {
313
250
  code: DecisionErrorCode.Unknown,
314
251
  retryable: false,
315
- userMessage: renderTemplate(DECISION_ERROR_MESSAGES[DecisionErrorCode.Unknown], { raw }),
252
+ userMessage: DECISION_ERROR_BUCKET_COPY.attention.inApp,
316
253
  };
317
254
  }
318
255
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"decision-errors.js","sourceRoot":"","sources":["../../src/runtime/decision-errors.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"decision-errors.js","sourceRoot":"","sources":["../../src/runtime/decision-errors.ts"],"names":[],"mappings":"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"}
@@ -1,33 +1,40 @@
1
1
  import type { McpTransport } from "../senpi/types.js";
2
2
  import type { NotificationService } from "../types/notification.js";
3
- import { type DecisionErrorContext } from "./decision-errors.js";
4
3
  export interface NotificationServiceOptions {
5
- telegram_chat_id?: string;
6
4
  mcpTransport?: McpTransport;
7
- gateway_url?: string;
8
- gateway_token?: string;
5
+ /** Override for the `NOTIFICATIONS_INGEST_URL` env (tests / non-standard hosting). */
6
+ ingest_url?: string;
7
+ /** The runtime's own Senpi auth token (MCP bearer). Doubles as the ingest-endpoint
8
+ * bearer credential and, via its `senpi_user_id` claim, the recipient every
9
+ * outgoing notification is stamped with. */
10
+ senpi_auth_token?: string;
11
+ /** Override the per-request timeout in ms (tests). */
12
+ ingest_timeout_ms?: number;
9
13
  [key: string]: unknown;
10
14
  }
11
15
  /**
12
- * Map a raw LLM-decision error string to the user-facing Telegram message.
16
+ * Map a raw LLM-decision error string to a user-facing notification message.
13
17
  *
14
- * Thin wrapper over {@link classifyDecisionError} — all message wording and the
18
+ * Thin wrapper over {@link classifyDecisionError} — the two-bucket copy and the
15
19
  * failure-mode lookup live in `decision-errors.ts` (the single source of truth
16
20
  * shared with the retry logic in `openclaw-llm-decision.ts`). The runtime catches
17
21
  * errors from the LLM-decision call and passes `err.message` here.
18
22
  *
19
23
  * @param rawMessage - Error message thrown by the decision client (may embed an
20
24
  * OpenClaw gateway message).
21
- * @param ctx - Optional plugin-side context (action name, configured model) for
22
- * placeholders the gateway error string cannot supply.
23
25
  */
24
- export declare function formatDecisionErrorNotification(rawMessage: string, ctx?: DecisionErrorContext): string;
26
+ export declare function formatDecisionErrorNotification(rawMessage: string): string;
25
27
  /**
26
- * IMPORTANT: Do NOT use logger.error(...) anywhere inside this service.
27
- * NotificationService is itself the Telegram delivery path for error notifications.
28
- * Using logger.error here would trigger the error notifier hook recursively,
29
- * which is blocked by the reentrancy guard (causing the notification to be silently dropped)
30
- * or could cause unexpected feedback loops in future. Use logger.warn for all failure paths.
28
+ * Create the runtime's notification service. Every notification is delivered by
29
+ * POSTing the full structured envelope to the authenticated notification-ingest
30
+ * endpoint (`NOTIFICATIONS_INGEST_URL`) — there is no other delivery path. Missing
31
+ * configuration is a startup error (thrown here, not discovered later as silent
32
+ * notification loss), so callers must construct this before a runtime starts
33
+ * trading, not lazily on first notify.
34
+ *
35
+ * Failure paths inside this service log at warn, not error: a delivery failure is a
36
+ * handled, degraded condition — the caller's notify() contract is fire-and-forget and
37
+ * the next notification retries the transport fresh.
31
38
  */
32
39
  export declare function createNotificationService(options?: NotificationServiceOptions): NotificationService;
33
40
  //# sourceMappingURL=notifications.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../../src/runtime/notifications.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAEpE,OAAO,EAAyB,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAIxF,MAAM,WAAW,0BAA0B;IACzC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,+BAA+B,CAC7C,UAAU,EAAE,MAAM,EAClB,GAAG,CAAC,EAAE,oBAAoB,GACzB,MAAM,CAER;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,CAAC,EAAE,0BAA0B,GAAG,mBAAmB,CAyEnG"}
1
+ {"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../../src/runtime/notifications.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAyC,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AA+B3G,MAAM,WAAW,0BAA0B;IACzC,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,sFAAsF;IACtF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;gDAE4C;IAC5C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sDAAsD;IACtD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,+BAA+B,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE1E;AA8FD;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,CAAC,EAAE,0BAA0B,GAAG,mBAAmB,CAsHnG"}
@@ -1,92 +1,225 @@
1
+ import { toPayload, truncateForPush, PUSH_TITLE_MAX, PUSH_BODY_MAX } from "../types/notification.js";
2
+ import { decodeSenpiUserId } from "../telemetry/otel-sdk.js";
1
3
  import { createLogger } from "../utils/logger.js";
2
4
  import { classifyDecisionError } from "./decision-errors.js";
5
+ import { randomUUID } from "node:crypto";
3
6
  const logger = createLogger("[senpi-core][notifications]");
4
7
  /**
5
- * Map a raw LLM-decision error string to the user-facing Telegram message.
8
+ * Every notification the runtime emits is a strategy/trading alert, so the ingest
9
+ * envelope's top-level category is fixed. Per-alert differentiation (limit trip,
10
+ * decision error, scanner alert, ...) travels in `actionMetadata.notificationSubtype`.
11
+ */
12
+ const NOTIFICATION_CATEGORY = "PERPS";
13
+ const NOTIFICATION_SOURCE = "senpi-trading-runtime";
14
+ /**
15
+ * Bounded per-request timeout for the ingest POST. The service is called both
16
+ * fire-and-forget and awaited inline by producers on the serial signal path, so
17
+ * this must never hang indefinitely: one retry on a retryable failure, no more.
18
+ * Worst-case latency is therefore ~2x this value, never unbounded.
19
+ */
20
+ const DEFAULT_TIMEOUT_MS = 5000;
21
+ /** Only one retry attempt on a retryable failure — see {@link classifyOutcome}. */
22
+ const MAX_ATTEMPTS = 2;
23
+ /** Suppress repeat WARN logs for the same failure reason within this window. */
24
+ const WARN_REPEAT_WINDOW_MS = 30_000;
25
+ /**
26
+ * Map a raw LLM-decision error string to a user-facing notification message.
6
27
  *
7
- * Thin wrapper over {@link classifyDecisionError} — all message wording and the
28
+ * Thin wrapper over {@link classifyDecisionError} — the two-bucket copy and the
8
29
  * failure-mode lookup live in `decision-errors.ts` (the single source of truth
9
30
  * shared with the retry logic in `openclaw-llm-decision.ts`). The runtime catches
10
31
  * errors from the LLM-decision call and passes `err.message` here.
11
32
  *
12
33
  * @param rawMessage - Error message thrown by the decision client (may embed an
13
34
  * OpenClaw gateway message).
14
- * @param ctx - Optional plugin-side context (action name, configured model) for
15
- * placeholders the gateway error string cannot supply.
16
35
  */
17
- export function formatDecisionErrorNotification(rawMessage, ctx) {
18
- return classifyDecisionError(rawMessage, ctx).userMessage;
36
+ export function formatDecisionErrorNotification(rawMessage) {
37
+ return classifyDecisionError(rawMessage).userMessage;
38
+ }
39
+ /** Strip anything shaped like a bearer credential before it can reach a log line. */
40
+ function redactSecrets(text) {
41
+ return text.replace(/Bearer\s+\S+/gi, "Bearer [REDACTED]");
42
+ }
43
+ function describeError(err) {
44
+ const raw = err instanceof Error ? err.message : String(err);
45
+ return redactSecrets(raw);
46
+ }
47
+ /** Classify an HTTP response status into a delivery outcome. 503 and any 5xx are
48
+ * transient (server-side produce failure) and get one retry; every other non-2xx
49
+ * (400 malformed envelope, 401 bad/expired token, 403 recipient mismatch, or any
50
+ * other 4xx) is a caller-side condition a retry cannot fix, so it drops immediately. */
51
+ function classifyStatus(status) {
52
+ if (status >= 200 && status < 300)
53
+ return "ok";
54
+ if (status === 503 || status >= 500)
55
+ return "retry";
56
+ return "drop";
57
+ }
58
+ const lastWarnedAt = new Map();
59
+ /** Log a WARN, but suppress repeats of the same reason within {@link WARN_REPEAT_WINDOW_MS}
60
+ * so a hot failure path (e.g. a sustained outage) doesn't flood the log. */
61
+ function warnRateLimited(key, message, attributes) {
62
+ const now = Date.now();
63
+ const last = lastWarnedAt.get(key);
64
+ if (last !== undefined && now - last < WARN_REPEAT_WINDOW_MS)
65
+ return;
66
+ lastWarnedAt.set(key, now);
67
+ logger.warn(message, undefined, attributes);
68
+ }
69
+ async function postOnce(ingestUrl, authToken, envelope, timeoutMs) {
70
+ const controller = new AbortController();
71
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
72
+ try {
73
+ const res = await fetch(ingestUrl, {
74
+ method: "POST",
75
+ headers: {
76
+ "Content-Type": "application/json",
77
+ Authorization: `Bearer ${authToken}`,
78
+ },
79
+ body: JSON.stringify(envelope),
80
+ signal: controller.signal,
81
+ });
82
+ if (res.ok)
83
+ return { kind: "ok" };
84
+ const bodyText = await res.text().catch(() => "");
85
+ const kind = classifyStatus(res.status);
86
+ return { kind, status: res.status, reason: redactSecrets(bodyText).slice(0, 300) };
87
+ }
88
+ catch (err) {
89
+ // Network failure, DNS failure, connection refused, or our own timeout abort.
90
+ return { kind: "retry", reason: describeError(err) };
91
+ }
92
+ finally {
93
+ clearTimeout(timer);
94
+ }
95
+ }
96
+ /** POST the envelope with a single bounded retry on a transient failure (5xx / network / timeout).
97
+ * Never throws — every outcome resolves, so a delivery failure can never surface as a rejected
98
+ * promise into the caller's (often serial, trading-critical) code path. */
99
+ async function deliver(ingestUrl, authToken, envelope, timeoutMs) {
100
+ let outcome = await postOnce(ingestUrl, authToken, envelope, timeoutMs);
101
+ for (let attempt = 2; outcome.kind === "retry" && attempt <= MAX_ATTEMPTS; attempt++) {
102
+ outcome = await postOnce(ingestUrl, authToken, envelope, timeoutMs);
103
+ }
104
+ if (outcome.kind === "ok")
105
+ return;
106
+ const notificationId = envelope.notificationId;
107
+ const subtype = envelope.actionMetadata?.notificationSubtype;
108
+ const rateLimitKey = `${outcome.status ?? "network"}:${typeof subtype === "string" ? subtype : "unknown"}`;
109
+ warnRateLimited(rateLimitKey, `Notification delivery dropped notificationId=${String(notificationId)} status=${outcome.status ?? "n/a"}: ${outcome.reason}`, { notificationId, status: outcome.status });
19
110
  }
20
111
  /**
21
- * IMPORTANT: Do NOT use logger.error(...) anywhere inside this service.
22
- * NotificationService is itself the Telegram delivery path for error notifications.
23
- * Using logger.error here would trigger the error notifier hook recursively,
24
- * which is blocked by the reentrancy guard (causing the notification to be silently dropped)
25
- * or could cause unexpected feedback loops in future. Use logger.warn for all failure paths.
112
+ * Create the runtime's notification service. Every notification is delivered by
113
+ * POSTing the full structured envelope to the authenticated notification-ingest
114
+ * endpoint (`NOTIFICATIONS_INGEST_URL`) — there is no other delivery path. Missing
115
+ * configuration is a startup error (thrown here, not discovered later as silent
116
+ * notification loss), so callers must construct this before a runtime starts
117
+ * trading, not lazily on first notify.
118
+ *
119
+ * Failure paths inside this service log at warn, not error: a delivery failure is a
120
+ * handled, degraded condition — the caller's notify() contract is fire-and-forget and
121
+ * the next notification retries the transport fresh.
26
122
  */
27
123
  export function createNotificationService(options) {
28
- const chatId = typeof options?.telegram_chat_id === "string" ? options.telegram_chat_id.trim() : undefined;
29
124
  const mcpTransport = options?.mcpTransport;
30
- const gatewayUrl = options?.gateway_url || process.env.OPENCLAW_GATEWAY_URL || "http://127.0.0.1:18789";
31
- const gatewayToken = options?.gateway_token || process.env.OPENCLAW_GATEWAY_TOKEN;
32
- if (chatId) {
33
- logger.info(`Notifications enabled for ${chatId}; routing via OpenClaw /tools/invoke`);
125
+ const ingestUrl = (options?.ingest_url || process.env.NOTIFICATIONS_INGEST_URL)?.trim();
126
+ if (!ingestUrl) {
127
+ throw new Error("NotificationService misconfigured: NOTIFICATIONS_INGEST_URL is not set. " +
128
+ "The runtime has no notification transport — set NOTIFICATIONS_INGEST_URL " +
129
+ "(or pass options.ingest_url) before starting.");
130
+ }
131
+ const authToken = (options?.senpi_auth_token ||
132
+ process.env.SENPI_AUTH_TOKEN ||
133
+ process.env.SENPI_API_KEY)?.trim();
134
+ if (!authToken) {
135
+ throw new Error("NotificationService misconfigured: no Senpi auth token available. " +
136
+ "Set options.senpi_auth_token, or SENPI_AUTH_TOKEN / SENPI_API_KEY in the environment — " +
137
+ "the runtime authenticates to the notification-ingest endpoint with its own Senpi token.");
34
138
  }
139
+ const timeoutMs = options?.ingest_timeout_ms ?? DEFAULT_TIMEOUT_MS;
140
+ // Telegram delivery is gone; a recipe still carrying telegram_chat_id from before
141
+ // the cutover is a silent misconfiguration footgun otherwise — the operator set it
142
+ // expecting delivery and gets nothing. Ignore it, but say so loudly once.
143
+ if (typeof options?.telegram_chat_id === "string" && options.telegram_chat_id.trim().length > 0) {
144
+ logger.warn("telegram_chat_id is set but ignored: Telegram delivery has been removed.");
145
+ }
146
+ // Resolve the recipient once from THIS service's own token — never a process
147
+ // global — so sibling services with different tokens stamp different users.
148
+ const recipientUserId = decodeSenpiUserId(authToken);
149
+ if (recipientUserId === undefined) {
150
+ // A live runtime always holds a valid token, so an undecodable one is an
151
+ // anomaly. WARN only (never logger.error — see the reentrancy note above);
152
+ // delivery still proceeds unstamped.
153
+ logger.warn("Could not resolve recipient senpi_user_id from auth token; delivering notification unstamped.");
154
+ }
155
+ logger.info(`Notifications enabled; routing via POST ${ingestUrl}`);
35
156
  return {
36
- async notify(message) {
157
+ async notify(input) {
158
+ const p = toPayload(input);
159
+ const message = p.message;
37
160
  if (typeof message !== "string" || message.length === 0)
38
161
  return;
162
+ // Precedence: the service's own resolved recipient wins, but an
163
+ // unresolved token must never clobber a value already on the payload.
164
+ const recipient = recipientUserId ?? p.recipientUserId;
39
165
  const preview = message.length > 120 ? message.slice(0, 120) + "…" : message;
40
166
  logger.info("Notification service invoked", { messagePreview: preview });
41
- if (!chatId) {
42
- logger.warn("Notification not sent: no telegram_chat_id in runtime config.");
43
- return;
44
- }
45
- // 1. Prefer MCP transport if host injects it in the future
167
+ // The openability fields (canOpenAgentChat + context) forward verbatim — never inferred,
168
+ // never repaired. notificationId/subtype fall back to the payload's own
169
+ // context when present (the producer already minted one), else this is the
170
+ // first and only place an id is minted for a bare-string/non-openable call.
171
+ const notificationId = p.context?.notificationId ?? randomUUID();
172
+ const subtype = p.subtype ?? p.context?.subtype;
173
+ // notifications-service renders user-visible text ONLY from these three
174
+ // actionMetadata fields (each `|| ""`) — it never displays the top-level
175
+ // `message`. A bare-string / copy-less notify() would otherwise ship a
176
+ // blank notification, so a copy-less payload always gets a synthesized
177
+ // fallback derived from `message` — every family ships non-empty copy.
178
+ const copy = p.copy ?? {
179
+ pushTitle: truncateForPush(message.split("\n")[0], PUSH_TITLE_MAX),
180
+ pushBody: truncateForPush(message.replace(/\n/g, " · "), PUSH_BODY_MAX),
181
+ inApp: message,
182
+ };
183
+ const envelope = {
184
+ notificationId,
185
+ message,
186
+ category: NOTIFICATION_CATEGORY,
187
+ source: NOTIFICATION_SOURCE,
188
+ timestamp: p.context?.createdAt ?? new Date().toISOString(),
189
+ canOpenAgentChat: p.canOpenAgentChat === true,
190
+ actionMetadata: {
191
+ uniqueKeys: [notificationId],
192
+ ...(subtype !== undefined ? { notificationSubtype: subtype } : {}),
193
+ id: notificationId,
194
+ pushNotificationTitle: copy.pushTitle,
195
+ pushNotificationMessage: copy.pushBody,
196
+ inAppNotificationMessage: copy.inApp,
197
+ senpiFormattedMessage: copy.inApp,
198
+ ctas: [],
199
+ category: NOTIFICATION_CATEGORY,
200
+ },
201
+ // Same id drives the ingest endpoint's idempotency precedence, so the two
202
+ // attempts of one delivery (see `deliver`'s bounded retry) collapse to a
203
+ // single ledger row even if the first attempt's response was lost.
204
+ idempotencyKey: notificationId,
205
+ };
206
+ if (p.context !== undefined)
207
+ envelope.context = p.context;
208
+ if (recipient !== undefined)
209
+ envelope.recipientUserId = recipient;
210
+ // Test-only seam: the deployed OpenClaw gateway never injects mcpTransport
211
+ // for notifications, so this branch only ever runs under test. When present
212
+ // it short-circuits before any HTTP call.
46
213
  if (mcpTransport) {
47
214
  try {
48
- await mcpTransport("send_telegram_notification", { target: `telegram:${chatId}`, message });
49
- return;
50
- }
51
- catch (err) {
52
- logger.warn("MCP transport failed; falling back to Gateway HTTP", err);
53
- }
54
- }
55
- // 2. HTTP Gateway Tool Invocation (Matches your LLM Engine)
56
- if (gatewayToken) {
57
- try {
58
- const baseUrl = gatewayUrl.endsWith("/") ? gatewayUrl.slice(0, -1) : gatewayUrl;
59
- const res = await fetch(`${baseUrl}/tools/invoke`, {
60
- method: "POST",
61
- headers: {
62
- "Content-Type": "application/json",
63
- "Authorization": `Bearer ${gatewayToken}`
64
- },
65
- body: JSON.stringify({
66
- tool: "message",
67
- args: {
68
- action: "send",
69
- target: `telegram:${chatId}`,
70
- message: message,
71
- },
72
- })
73
- });
74
- if (!res.ok) {
75
- const errText = await res.text().catch(() => "No response body");
76
- throw new Error(`HTTP ${res.status}: ${errText}`);
77
- }
78
- const body = await res.json();
79
- if (!body.ok) {
80
- throw new Error(`Gateway rejected message tool: ${body.error ?? JSON.stringify(body)}`);
81
- }
82
- return;
215
+ await mcpTransport("notify", envelope);
83
216
  }
84
217
  catch (err) {
85
- logger.warn("Gateway tool invocation failed", err);
218
+ logger.warn("mcpTransport notify failed", undefined, { err: describeError(err) });
86
219
  }
87
220
  return;
88
221
  }
89
- logger.warn("Notification dropped: Missing OPENCLAW_GATEWAY_TOKEN in environment.");
222
+ await deliver(ingestUrl, authToken, envelope, timeoutMs);
90
223
  },
91
224
  };
92
225
  }