@senpi/runtime 1.1.0-dev.runtime-phase-2.20260521125138 → 1.1.0-dev.runtime-phase-2.20260522115501

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 (46) hide show
  1. package/dist/actions/close-position/close-position.action.d.ts.map +1 -1
  2. package/dist/actions/close-position/close-position.action.js +4 -1
  3. package/dist/actions/close-position/close-position.action.js.map +1 -1
  4. package/dist/actions/llm-decision/anthropic-llm-decision.d.ts.map +1 -1
  5. package/dist/actions/llm-decision/anthropic-llm-decision.js +41 -25
  6. package/dist/actions/llm-decision/anthropic-llm-decision.js.map +1 -1
  7. package/dist/actions/llm-decision/openclaw-llm-decision.d.ts +30 -0
  8. package/dist/actions/llm-decision/openclaw-llm-decision.d.ts.map +1 -1
  9. package/dist/actions/llm-decision/openclaw-llm-decision.js +87 -16
  10. package/dist/actions/llm-decision/openclaw-llm-decision.js.map +1 -1
  11. package/dist/actions/open-position/open-position.action.d.ts.map +1 -1
  12. package/dist/actions/open-position/open-position.action.js +4 -1
  13. package/dist/actions/open-position/open-position.action.js.map +1 -1
  14. package/dist/runtime/decision-errors.d.ts +166 -0
  15. package/dist/runtime/decision-errors.d.ts.map +1 -0
  16. package/dist/runtime/decision-errors.js +328 -0
  17. package/dist/runtime/decision-errors.js.map +1 -0
  18. package/dist/runtime/notifications.d.ts +15 -1
  19. package/dist/runtime/notifications.d.ts.map +1 -1
  20. package/dist/runtime/notifications.js +16 -57
  21. package/dist/runtime/notifications.js.map +1 -1
  22. package/dist/senpi/client.d.ts.map +1 -1
  23. package/dist/senpi/client.js +30 -17
  24. package/dist/senpi/client.js.map +1 -1
  25. package/dist/tracking/spans/constants.d.ts +0 -1
  26. package/dist/tracking/spans/constants.d.ts.map +1 -1
  27. package/dist/tracking/spans/constants.js +0 -4
  28. package/dist/tracking/spans/constants.js.map +1 -1
  29. package/dist/tracking/spans/span-hooks.d.ts +13 -2
  30. package/dist/tracking/spans/span-hooks.d.ts.map +1 -1
  31. package/dist/tracking/spans/span-hooks.js +12 -63
  32. package/dist/tracking/spans/span-hooks.js.map +1 -1
  33. package/dist/tracking/spans/span-tracker.d.ts +0 -7
  34. package/dist/tracking/spans/span-tracker.d.ts.map +1 -1
  35. package/dist/tracking/spans/span-tracker.js +55 -26
  36. package/dist/tracking/spans/span-tracker.js.map +1 -1
  37. package/dist/tracking/spans/span-transport.d.ts.map +1 -1
  38. package/dist/tracking/spans/span-transport.js +21 -2
  39. package/dist/tracking/spans/span-transport.js.map +1 -1
  40. package/dist/tracking/spans/types.d.ts +6 -9
  41. package/dist/tracking/spans/types.d.ts.map +1 -1
  42. package/dist/utils/retry.d.ts +7 -0
  43. package/dist/utils/retry.d.ts.map +1 -1
  44. package/dist/utils/retry.js +5 -1
  45. package/dist/utils/retry.js.map +1 -1
  46. package/package.json +1 -1
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Classification of LLM-decision failures into user-facing messages + a retry flag.
3
+ *
4
+ * ## Why this module exists
5
+ *
6
+ * When an action runs an LLM decision (`OpenClawLlmDecision.decide`,
7
+ * `src/actions/llm-decision/openclaw-llm-decision.ts`) and the OpenClaw `llm-task`
8
+ * tool fails, the only thing the runtime has is a flat error string. Two consumers
9
+ * need to make decisions from that string:
10
+ *
11
+ * 1. The **notification layer** ({@link formatDecisionErrorNotification} in
12
+ * `notifications.ts`) — what to tell the skill author in Telegram.
13
+ * 2. The **decision client** (`openclaw-llm-decision.ts`) — whether a retry can
14
+ * plausibly succeed.
15
+ *
16
+ * ## Three pieces, separated on purpose
17
+ *
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.
24
+ *
25
+ * To change wording: edit {@link DECISION_ERROR_MESSAGES} only. To change matching
26
+ * or retry behavior: edit {@link DECISION_ERROR_TABLE} only.
27
+ *
28
+ * ## Where the raw strings come from
29
+ *
30
+ * The granular OpenClaw messages (e.g. `"LLM returned empty output"`,
31
+ * `"prompt required"`) are thrown by the **external gateway**, not by this plugin.
32
+ * They reach us **embedded** inside one of the wrappers that
33
+ * `OpenClawLlmDecision.decide` throws:
34
+ *
35
+ * - `OpenClaw gateway error (<status>): <body-text>` (non-2xx HTTP)
36
+ * - `OpenClaw gateway rejected request: <body.error>` (HTTP 200, `body.ok === false`)
37
+ *
38
+ * Matchers therefore look for the granular substring **anywhere** in the string,
39
+ * not anchored. Our own parse failures (`Invalid llm-task response: ...` from
40
+ * `extractDecision`) are classified too.
41
+ *
42
+ * ## v2026.2.22 caveat
43
+ *
44
+ * On OpenClaw v2026.2.22 (current prod) the gateway collapses most `llm-task`
45
+ * failures into a generic `tool_error` whose message is `"tool execution failed"`,
46
+ * so the granular substrings frequently do **not** surface there — only on
47
+ * v2026.5.7+. The table is built in full anyway: on v2026.2.22 the generic
48
+ * `tool_error` matches {@link DecisionErrorCode.ProviderError}; on newer gateways
49
+ * the specific code matches.
50
+ */
51
+ /** Stable identifier for each classified LLM-decision failure mode. */
52
+ export declare enum DecisionErrorCode {
53
+ /** Gateway auth token missing in env/config — thrown before any HTTP call. */
54
+ GatewayTokenMissing = "GATEWAY_TOKEN_MISSING",
55
+ /** `fetch()` rejected before a response (connection refused/reset/DNS). */
56
+ GatewayUnreachable = "GATEWAY_UNREACHABLE",
57
+ /** Gateway returned 404 — plugin not installed at this path / version mismatch. */
58
+ PluginEndpointMissing = "PLUGIN_ENDPOINT_MISSING",
59
+ /** `llm-task` invoked with empty/whitespace prompt — missing `decision_prompt`. */
60
+ PromptRequired = "PROMPT_REQUIRED",
61
+ /** `decision_model` did not resolve to a known provider/model pair. */
62
+ ProviderModelUnresolved = "PROVIDER_MODEL_UNRESOLVED",
63
+ /** Resolved model is not on the gateway's `allowedModels` allowlist. */
64
+ ModelNotAllowed = "MODEL_NOT_ALLOWED",
65
+ /** Context/payload passed to `llm-task` was not JSON-serializable — internal bug. */
66
+ InputNotSerializable = "INPUT_NOT_SERIALIZABLE",
67
+ /** Provider responded but produced no text (safety filter / refusal / empty completion). */
68
+ LlmEmptyOutput = "LLM_EMPTY_OUTPUT",
69
+ /** Model reply could not be parsed as JSON (truncation / prose around JSON). */
70
+ LlmInvalidJson = "LLM_INVALID_JSON",
71
+ /** Model reply was valid JSON but failed schema validation. */
72
+ LlmSchemaMismatch = "LLM_SCHEMA_MISMATCH",
73
+ /** Upstream provider error (rate limit / outage) or generic `tool_error`. */
74
+ ProviderError = "PROVIDER_ERROR",
75
+ /** Gateway/upstream timeout (408/504) or runtime-side abort — we already waited 90s. */
76
+ Timeout = "TIMEOUT",
77
+ /** Embedded LLM helper failed to load on the host — corrupt/partial OpenClaw install. */
78
+ EmbeddedAgentUnavailable = "EMBEDDED_AGENT_UNAVAILABLE",
79
+ /** Thinking level string does not normalize to any known level (v2026.5.7+). */
80
+ InvalidThinkingLevel = "INVALID_THINKING_LEVEL",
81
+ /** Thinking level is known but unsupported by the resolved model (v2026.5.7+). */
82
+ ThinkingLevelUnsupported = "THINKING_LEVEL_UNSUPPORTED",
83
+ /** Gateway returned 200 with a response shape this plugin can't parse — internal bug. */
84
+ PluginResponseShape = "PLUGIN_RESPONSE_SHAPE",
85
+ /** Any other 5xx with no recognizable structured body. */
86
+ OpenClawUnexpected = "OPENCLAW_UNEXPECTED",
87
+ /** No matcher matched — surfaces the raw message so operators can still diagnose. */
88
+ Unknown = "UNKNOWN"
89
+ }
90
+ /**
91
+ * The single catalog of every user-facing message, keyed by {@link DecisionErrorCode}.
92
+ *
93
+ * Templates use `{token}` placeholders, filled by the matching entry's `vars`
94
+ * function in {@link DECISION_ERROR_TABLE} (e.g. `{name}` = action name, `{model}`,
95
+ * `{list}`, `{errors}`, `{level}`, `{hint}`, `{providerModel}`, `{raw}`). A `{token}`
96
+ * with no supplied value is left verbatim so a missing var is visible, not silent.
97
+ *
98
+ * **This is the only place to edit copy.** Nothing else hardcodes a message.
99
+ */
100
+ export declare const DECISION_ERROR_MESSAGES: Record<DecisionErrorCode, string>;
101
+ /**
102
+ * Plugin-side context the gateway error string does not carry but the message
103
+ * needs (e.g. the action's YAML name, the configured `decision_model`).
104
+ */
105
+ export interface DecisionErrorContext {
106
+ /** The failing action's `name` from the runtime YAML (the `{name}` placeholder). */
107
+ actionName?: string;
108
+ /** The action's configured `decision_model` (fallback for the `{model}` placeholder). */
109
+ model?: string;
110
+ }
111
+ /** Result of classifying a raw LLM-decision error string. */
112
+ export interface ClassifiedDecisionError {
113
+ code: DecisionErrorCode;
114
+ /** Whether a retry of the `llm-task` call can plausibly succeed. */
115
+ retryable: boolean;
116
+ /** User-facing message for Telegram. */
117
+ userMessage: string;
118
+ }
119
+ /** One row of {@link DECISION_ERROR_TABLE}. */
120
+ interface DecisionErrorEntry {
121
+ code: DecisionErrorCode;
122
+ /**
123
+ * Tested against the raw error string. Returning a {@link RegExpMatchArray}
124
+ * (truthy) or `true` counts as a match; `null`/`false` means no match. Capture
125
+ * groups are forwarded to {@link DecisionErrorEntry.vars} as `caps`.
126
+ */
127
+ match: (raw: string) => RegExpMatchArray | boolean | null;
128
+ retryable: boolean;
129
+ /**
130
+ * Optional: produces the `{token}` substitutions for this code's catalog entry.
131
+ * Omit for static messages.
132
+ * @param caps - Capture groups from {@link DecisionErrorEntry.match}, or `null`.
133
+ * @param ctx - Plugin-side context (action name, configured model).
134
+ * @param raw - The original raw error string.
135
+ */
136
+ vars?: (caps: RegExpMatchArray | null, ctx: DecisionErrorContext, raw: string) => Record<string, string>;
137
+ }
138
+ /**
139
+ * Ordered classification table — matchers + retry flag + template-variable logic.
140
+ * First match wins, so keep specific matchers above generic ones (e.g.
141
+ * `LlmEmptyOutput` before the catch-all `ProviderError`, since both can appear
142
+ * inside a `"rejected request: ..."` wrapper). Copy lives in
143
+ * {@link DECISION_ERROR_MESSAGES}, not here.
144
+ */
145
+ export declare const DECISION_ERROR_TABLE: readonly DecisionErrorEntry[];
146
+ /**
147
+ * Classify a raw LLM-decision error string into a {@link ClassifiedDecisionError}.
148
+ *
149
+ * Walks {@link DECISION_ERROR_TABLE} in order; the first matching entry wins. If
150
+ * nothing matches, returns {@link DecisionErrorCode.Unknown} (not retryable). The
151
+ * message is always rendered from {@link DECISION_ERROR_MESSAGES}.
152
+ *
153
+ * @param rawMessage - The error string thrown by the decision client (may embed an
154
+ * OpenClaw gateway message). Non-string / empty inputs are coerced safely.
155
+ * @param ctx - Optional plugin-side context for placeholders the gateway can't supply.
156
+ */
157
+ export declare function classifyDecisionError(rawMessage: string, ctx?: DecisionErrorContext): ClassifiedDecisionError;
158
+ /**
159
+ * Convenience predicate for retry wiring: `true` when a retry of the failed
160
+ * `llm-task` call can plausibly succeed.
161
+ *
162
+ * @param error - The thrown error (or any value); only its message is inspected.
163
+ */
164
+ export declare function isRetryableDecisionError(error: unknown): boolean;
165
+ export {};
166
+ //# sourceMappingURL=decision-errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision-errors.d.ts","sourceRoot":"","sources":["../../src/runtime/decision-errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAEH,uEAAuE;AACvE,oBAAY,iBAAiB;IAC3B,8EAA8E;IAC9E,mBAAmB,0BAA0B;IAC7C,2EAA2E;IAC3E,kBAAkB,wBAAwB;IAC1C,mFAAmF;IACnF,qBAAqB,4BAA4B;IACjD,mFAAmF;IACnF,cAAc,oBAAoB;IAClC,uEAAuE;IACvE,uBAAuB,8BAA8B;IACrD,wEAAwE;IACxE,eAAe,sBAAsB;IACrC,qFAAqF;IACrF,oBAAoB,2BAA2B;IAC/C,4FAA4F;IAC5F,cAAc,qBAAqB;IACnC,gFAAgF;IAChF,cAAc,qBAAqB;IACnC,+DAA+D;IAC/D,iBAAiB,wBAAwB;IACzC,6EAA6E;IAC7E,aAAa,mBAAmB;IAChC,wFAAwF;IACxF,OAAO,YAAY;IACnB,yFAAyF;IACzF,wBAAwB,+BAA+B;IACvD,gFAAgF;IAChF,oBAAoB,2BAA2B;IAC/C,kFAAkF;IAClF,wBAAwB,+BAA+B;IACvD,yFAAyF;IACzF,mBAAmB,0BAA0B;IAC7C,0DAA0D;IAC1D,kBAAkB,wBAAwB;IAC1C,qFAAqF;IACrF,OAAO,YAAY;CACpB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,EAAE,MAAM,CAAC,iBAAiB,EAAE,MAAM,CAgFrE,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC,oFAAoF;IACpF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yFAAyF;IACzF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,6DAA6D;AAC7D,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,iBAAiB,CAAC;IACxB,oEAAoE;IACpE,SAAS,EAAE,OAAO,CAAC;IACnB,wCAAwC;IACxC,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,+CAA+C;AAC/C,UAAU,kBAAkB;IAC1B,IAAI,EAAE,iBAAiB,CAAC;IACxB;;;;OAIG;IACH,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,gBAAgB,GAAG,OAAO,GAAG,IAAI,CAAC;IAC1D,SAAS,EAAE,OAAO,CAAC;IACnB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,CACL,IAAI,EAAE,gBAAgB,GAAG,IAAI,EAC7B,GAAG,EAAE,oBAAoB,EACzB,GAAG,EAAE,MAAM,KACR,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7B;AAeD;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,EAAE,SAAS,kBAAkB,EA+HpD,CAAC;AAEX;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,MAAM,EAClB,GAAG,GAAE,oBAAyB,GAC7B,uBAAuB,CAoBzB;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAGhE"}
@@ -0,0 +1,328 @@
1
+ /**
2
+ * Classification of LLM-decision failures into user-facing messages + a retry flag.
3
+ *
4
+ * ## Why this module exists
5
+ *
6
+ * When an action runs an LLM decision (`OpenClawLlmDecision.decide`,
7
+ * `src/actions/llm-decision/openclaw-llm-decision.ts`) and the OpenClaw `llm-task`
8
+ * tool fails, the only thing the runtime has is a flat error string. Two consumers
9
+ * need to make decisions from that string:
10
+ *
11
+ * 1. The **notification layer** ({@link formatDecisionErrorNotification} in
12
+ * `notifications.ts`) — what to tell the skill author in Telegram.
13
+ * 2. The **decision client** (`openclaw-llm-decision.ts`) — whether a retry can
14
+ * plausibly succeed.
15
+ *
16
+ * ## Three pieces, separated on purpose
17
+ *
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.
24
+ *
25
+ * To change wording: edit {@link DECISION_ERROR_MESSAGES} only. To change matching
26
+ * or retry behavior: edit {@link DECISION_ERROR_TABLE} only.
27
+ *
28
+ * ## Where the raw strings come from
29
+ *
30
+ * The granular OpenClaw messages (e.g. `"LLM returned empty output"`,
31
+ * `"prompt required"`) are thrown by the **external gateway**, not by this plugin.
32
+ * They reach us **embedded** inside one of the wrappers that
33
+ * `OpenClawLlmDecision.decide` throws:
34
+ *
35
+ * - `OpenClaw gateway error (<status>): <body-text>` (non-2xx HTTP)
36
+ * - `OpenClaw gateway rejected request: <body.error>` (HTTP 200, `body.ok === false`)
37
+ *
38
+ * Matchers therefore look for the granular substring **anywhere** in the string,
39
+ * not anchored. Our own parse failures (`Invalid llm-task response: ...` from
40
+ * `extractDecision`) are classified too.
41
+ *
42
+ * ## v2026.2.22 caveat
43
+ *
44
+ * On OpenClaw v2026.2.22 (current prod) the gateway collapses most `llm-task`
45
+ * failures into a generic `tool_error` whose message is `"tool execution failed"`,
46
+ * so the granular substrings frequently do **not** surface there — only on
47
+ * v2026.5.7+. The table is built in full anyway: on v2026.2.22 the generic
48
+ * `tool_error` matches {@link DecisionErrorCode.ProviderError}; on newer gateways
49
+ * the specific code matches.
50
+ */
51
+ /** Stable identifier for each classified LLM-decision failure mode. */
52
+ export var DecisionErrorCode;
53
+ (function (DecisionErrorCode) {
54
+ /** Gateway auth token missing in env/config — thrown before any HTTP call. */
55
+ DecisionErrorCode["GatewayTokenMissing"] = "GATEWAY_TOKEN_MISSING";
56
+ /** `fetch()` rejected before a response (connection refused/reset/DNS). */
57
+ DecisionErrorCode["GatewayUnreachable"] = "GATEWAY_UNREACHABLE";
58
+ /** Gateway returned 404 — plugin not installed at this path / version mismatch. */
59
+ DecisionErrorCode["PluginEndpointMissing"] = "PLUGIN_ENDPOINT_MISSING";
60
+ /** `llm-task` invoked with empty/whitespace prompt — missing `decision_prompt`. */
61
+ DecisionErrorCode["PromptRequired"] = "PROMPT_REQUIRED";
62
+ /** `decision_model` did not resolve to a known provider/model pair. */
63
+ DecisionErrorCode["ProviderModelUnresolved"] = "PROVIDER_MODEL_UNRESOLVED";
64
+ /** Resolved model is not on the gateway's `allowedModels` allowlist. */
65
+ DecisionErrorCode["ModelNotAllowed"] = "MODEL_NOT_ALLOWED";
66
+ /** Context/payload passed to `llm-task` was not JSON-serializable — internal bug. */
67
+ DecisionErrorCode["InputNotSerializable"] = "INPUT_NOT_SERIALIZABLE";
68
+ /** Provider responded but produced no text (safety filter / refusal / empty completion). */
69
+ DecisionErrorCode["LlmEmptyOutput"] = "LLM_EMPTY_OUTPUT";
70
+ /** Model reply could not be parsed as JSON (truncation / prose around JSON). */
71
+ DecisionErrorCode["LlmInvalidJson"] = "LLM_INVALID_JSON";
72
+ /** Model reply was valid JSON but failed schema validation. */
73
+ DecisionErrorCode["LlmSchemaMismatch"] = "LLM_SCHEMA_MISMATCH";
74
+ /** Upstream provider error (rate limit / outage) or generic `tool_error`. */
75
+ DecisionErrorCode["ProviderError"] = "PROVIDER_ERROR";
76
+ /** Gateway/upstream timeout (408/504) or runtime-side abort — we already waited 90s. */
77
+ DecisionErrorCode["Timeout"] = "TIMEOUT";
78
+ /** Embedded LLM helper failed to load on the host — corrupt/partial OpenClaw install. */
79
+ DecisionErrorCode["EmbeddedAgentUnavailable"] = "EMBEDDED_AGENT_UNAVAILABLE";
80
+ /** Thinking level string does not normalize to any known level (v2026.5.7+). */
81
+ DecisionErrorCode["InvalidThinkingLevel"] = "INVALID_THINKING_LEVEL";
82
+ /** Thinking level is known but unsupported by the resolved model (v2026.5.7+). */
83
+ DecisionErrorCode["ThinkingLevelUnsupported"] = "THINKING_LEVEL_UNSUPPORTED";
84
+ /** Gateway returned 200 with a response shape this plugin can't parse — internal bug. */
85
+ DecisionErrorCode["PluginResponseShape"] = "PLUGIN_RESPONSE_SHAPE";
86
+ /** Any other 5xx with no recognizable structured body. */
87
+ DecisionErrorCode["OpenClawUnexpected"] = "OPENCLAW_UNEXPECTED";
88
+ /** No matcher matched — surfaces the raw message so operators can still diagnose. */
89
+ DecisionErrorCode["Unknown"] = "UNKNOWN";
90
+ })(DecisionErrorCode || (DecisionErrorCode = {}));
91
+ /**
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.
165
+ */
166
+ export const DECISION_ERROR_TABLE = [
167
+ // ── Pre-HTTP / infra (not retryable) ──────────────────────────────────────
168
+ {
169
+ code: DecisionErrorCode.GatewayTokenMissing,
170
+ match: (s) => /gateway token not configured|OPENCLAW_GATEWAY_TOKEN/.test(s),
171
+ retryable: false,
172
+ },
173
+ {
174
+ code: DecisionErrorCode.GatewayUnreachable,
175
+ match: (s) => /^fetch failed$|fetch failed:|ECONNREFUSED|ECONNRESET|ETIMEDOUT|ENOTFOUND|EAI_AGAIN/.test(s),
176
+ retryable: false,
177
+ },
178
+ {
179
+ code: DecisionErrorCode.PluginEndpointMissing,
180
+ match: (s) => /OpenClaw gateway error \(404\)/.test(s),
181
+ retryable: false,
182
+ },
183
+ // ── Author/config errors surfaced by llm-task (not retryable) ─────────────
184
+ {
185
+ code: DecisionErrorCode.PromptRequired,
186
+ match: (s) => /prompt required/i.test(s),
187
+ retryable: false,
188
+ vars: (_caps, ctx) => ({ name: clean(ctx.actionName, "<name>") }),
189
+ },
190
+ {
191
+ code: DecisionErrorCode.ProviderModelUnresolved,
192
+ match: (s) => s.match(/provider\/model could not be resolved \(provider=(.*?), model=(.*?)\)/),
193
+ retryable: false,
194
+ vars: (caps, ctx) => ({ model: clean(caps?.[2], clean(ctx.model, "your configured model")) }),
195
+ },
196
+ {
197
+ code: DecisionErrorCode.ModelNotAllowed,
198
+ match: (s) => s.match(/Model not allowed by llm-task plugin config:\s*([^.]+)\.\s*Allowed models:\s*([^"}]+)/),
199
+ 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
+ },
205
+ {
206
+ code: DecisionErrorCode.InputNotSerializable,
207
+ match: (s) => /input must be JSON-serializable/i.test(s),
208
+ retryable: false,
209
+ },
210
+ // ── Model output problems ─────────────────────────────────────────────────
211
+ {
212
+ // Retryable: usually a transient safety-filter block or empty completion.
213
+ code: DecisionErrorCode.LlmEmptyOutput,
214
+ match: (s) => /LLM returned empty output/i.test(s),
215
+ retryable: true,
216
+ },
217
+ {
218
+ // Not retryable: a fixed prompt that fails to force JSON will fail identically.
219
+ code: DecisionErrorCode.LlmInvalidJson,
220
+ match: (s) => /LLM returned invalid JSON/i.test(s),
221
+ retryable: false,
222
+ },
223
+ {
224
+ // Not retryable: deterministic given a fixed prompt + schema.
225
+ code: DecisionErrorCode.LlmSchemaMismatch,
226
+ match: (s) => s.match(/LLM JSON did not match schema:\s*([^"}]+)/),
227
+ retryable: false,
228
+ vars: (caps) => ({ errors: clean(caps?.[1], "(schema validation failed)") }),
229
+ },
230
+ // ── Thinking-level misconfig (v2026.5.7+, not retryable) ──────────────────
231
+ {
232
+ code: DecisionErrorCode.InvalidThinkingLevel,
233
+ match: (s) => s.match(/Invalid thinking level "(.*?)"\.\s*Use one of:\s*([^"}]+?)\./),
234
+ retryable: false,
235
+ vars: (caps) => ({
236
+ level: clean(caps?.[1], "that value"),
237
+ hint: clean(caps?.[2], "the supported levels"),
238
+ }),
239
+ },
240
+ {
241
+ code: DecisionErrorCode.ThinkingLevelUnsupported,
242
+ match: (s) => s.match(/Thinking level "(.*?)" is not supported for (.+?)\.\s*Use one of:\s*([^"}]+?)\./),
243
+ 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
+ },
250
+ // ── Host/load failure (not retryable) ─────────────────────────────────────
251
+ {
252
+ code: DecisionErrorCode.EmbeddedAgentUnavailable,
253
+ match: (s) => /runEmbeddedPiAgent not available/.test(s),
254
+ retryable: false,
255
+ },
256
+ {
257
+ code: DecisionErrorCode.PluginResponseShape,
258
+ match: (s) => /Invalid llm-task response/.test(s),
259
+ retryable: false,
260
+ },
261
+ // ── Transient transport (timeout NOT retried; provider errors ARE) ────────
262
+ {
263
+ // Not retryable: we already waited 90s, and signal processing is strictly
264
+ // serial — retrying would stall the whole runtime's queue (see runtime.ts).
265
+ code: DecisionErrorCode.Timeout,
266
+ match: (s) => /OpenClaw gateway error \(408\)|OpenClaw gateway error \(504\)|AbortError|operation was aborted/.test(s),
267
+ retryable: false,
268
+ },
269
+ {
270
+ // Retryable: rate limit / transient outage / generic tool_error (the shape
271
+ // v2026.2.22 collapses empty-output and provider blips into).
272
+ code: DecisionErrorCode.ProviderError,
273
+ match: (s) => (/OpenClaw gateway error \(5\d\d\)/.test(s) && /"type":\s*"tool_error"|tool execution failed/.test(s)) ||
274
+ /OpenClaw gateway error \(429\)/.test(s) ||
275
+ /OpenClaw gateway rejected request/.test(s) ||
276
+ /tool execution failed/.test(s),
277
+ retryable: true,
278
+ },
279
+ {
280
+ // Any other 5xx with no recognizable body. Not retryable under the locked
281
+ // policy (only tool_error-carrying 5xx / 429 retry).
282
+ code: DecisionErrorCode.OpenClawUnexpected,
283
+ match: (s) => /OpenClaw gateway error \(5\d\d\)/.test(s),
284
+ retryable: false,
285
+ },
286
+ ];
287
+ /**
288
+ * Classify a raw LLM-decision error string into a {@link ClassifiedDecisionError}.
289
+ *
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}.
293
+ *
294
+ * @param rawMessage - The error string thrown by the decision client (may embed an
295
+ * 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
+ */
298
+ export function classifyDecisionError(rawMessage, ctx = {}) {
299
+ const raw = typeof rawMessage === "string" ? rawMessage : String(rawMessage ?? "");
300
+ for (const entry of DECISION_ERROR_TABLE) {
301
+ const result = entry.match(raw);
302
+ if (!result)
303
+ continue;
304
+ const caps = Array.isArray(result) ? result : null;
305
+ const vars = entry.vars ? entry.vars(caps, ctx, raw) : {};
306
+ return {
307
+ code: entry.code,
308
+ retryable: entry.retryable,
309
+ userMessage: renderTemplate(DECISION_ERROR_MESSAGES[entry.code], vars),
310
+ };
311
+ }
312
+ return {
313
+ code: DecisionErrorCode.Unknown,
314
+ retryable: false,
315
+ userMessage: renderTemplate(DECISION_ERROR_MESSAGES[DecisionErrorCode.Unknown], { raw }),
316
+ };
317
+ }
318
+ /**
319
+ * Convenience predicate for retry wiring: `true` when a retry of the failed
320
+ * `llm-task` call can plausibly succeed.
321
+ *
322
+ * @param error - The thrown error (or any value); only its message is inspected.
323
+ */
324
+ export function isRetryableDecisionError(error) {
325
+ const message = error instanceof Error ? error.message : String(error);
326
+ return classifyDecisionError(message).retryable;
327
+ }
328
+ //# sourceMappingURL=decision-errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision-errors.js","sourceRoot":"","sources":["../../src/runtime/decision-errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAEH,uEAAuE;AACvE,MAAM,CAAN,IAAY,iBAqCX;AArCD,WAAY,iBAAiB;IAC3B,8EAA8E;IAC9E,kEAA6C,CAAA;IAC7C,2EAA2E;IAC3E,+DAA0C,CAAA;IAC1C,mFAAmF;IACnF,sEAAiD,CAAA;IACjD,mFAAmF;IACnF,uDAAkC,CAAA;IAClC,uEAAuE;IACvE,0EAAqD,CAAA;IACrD,wEAAwE;IACxE,0DAAqC,CAAA;IACrC,qFAAqF;IACrF,oEAA+C,CAAA;IAC/C,4FAA4F;IAC5F,wDAAmC,CAAA;IACnC,gFAAgF;IAChF,wDAAmC,CAAA;IACnC,+DAA+D;IAC/D,8DAAyC,CAAA;IACzC,6EAA6E;IAC7E,qDAAgC,CAAA;IAChC,wFAAwF;IACxF,wCAAmB,CAAA;IACnB,yFAAyF;IACzF,4EAAuD,CAAA;IACvD,gFAAgF;IAChF,oEAA+C,CAAA;IAC/C,kFAAkF;IAClF,4EAAuD,CAAA;IACvD,yFAAyF;IACzF,kEAA6C,CAAA;IAC7C,0DAA0D;IAC1D,+DAA0C,CAAA;IAC1C,qFAAqF;IACrF,wCAAmB,CAAA;AACrB,CAAC,EArCW,iBAAiB,KAAjB,iBAAiB,QAqC5B;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAsC;IACxE,CAAC,iBAAiB,CAAC,mBAAmB,CAAC,EACrC,2EAA2E;IAE7E,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,EAAE,yCAAyC;IAEjF,CAAC,iBAAiB,CAAC,qBAAqB,CAAC,EACvC,8EAA8E;IAEhF,CAAC,iBAAiB,CAAC,cAAc,CAAC,EAChC,8FAA8F;QAC9F,iGAAiG;QACjG,kGAAkG;QAClG,gBAAgB;IAElB,CAAC,iBAAiB,CAAC,uBAAuB,CAAC,EACzC,gGAAgG;QAChG,qGAAqG;QACrG,+DAA+D;IAEjE,CAAC,iBAAiB,CAAC,eAAe,CAAC,EACjC,8FAA8F;QAC9F,8FAA8F;QAC9F,qGAAqG;IAEvG,CAAC,iBAAiB,CAAC,oBAAoB,CAAC,EACtC,kGAAkG;QAClG,+EAA+E;IAEjF,CAAC,iBAAiB,CAAC,cAAc,CAAC,EAChC,kGAAkG;QAClG,qGAAqG;QACrG,mGAAmG;QACnG,YAAY;IAEd,CAAC,iBAAiB,CAAC,cAAc,CAAC,EAChC,sGAAsG;QACtG,uGAAuG;QACvG,sGAAsG;QACtG,wBAAwB;IAE1B,CAAC,iBAAiB,CAAC,iBAAiB,CAAC,EACnC,iGAAiG;QACjG,gGAAgG;QAChG,qGAAqG;QACrG,oEAAoE;IAEtE,CAAC,iBAAiB,CAAC,aAAa,CAAC,EAC/B,uGAAuG;QACvG,+FAA+F;QAC/F,uEAAuE;IAEzE,CAAC,iBAAiB,CAAC,OAAO,CAAC,EACzB,iGAAiG;QACjG,4FAA4F;QAC5F,8EAA8E;IAEhF,CAAC,iBAAiB,CAAC,wBAAwB,CAAC,EAC1C,oGAAoG;QACpG,oGAAoG;QACpG,wGAAwG;QACxG,+DAA+D;IAEjE,CAAC,iBAAiB,CAAC,oBAAoB,CAAC,EACtC,iGAAiG;QACjG,uGAAuG;QACvG,+DAA+D;IAEjE,CAAC,iBAAiB,CAAC,wBAAwB,CAAC,EAC1C,qGAAqG;QACrG,uGAAuG;QACvG,4CAA4C;IAE9C,CAAC,iBAAiB,CAAC,mBAAmB,CAAC,EACrC,sGAAsG;QACtG,kDAAkD;IAEpD,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,EAAE,2DAA2D;IAEnG,CAAC,iBAAiB,CAAC,OAAO,CAAC,EAAE,wCAAwC;CACtE,CAAC;AA8CF,yEAAyE;AACzE,SAAS,KAAK,CAAC,KAAyB,EAAE,QAAgB;IACxD,MAAM,CAAC,GAAG,KAAK,EAAE,IAAI,EAAE,CAAC;IACxB,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC1C,CAAC;AAED,iFAAiF;AACjF,SAAS,cAAc,CAAC,QAAgB,EAAE,IAA4B;IACpE,OAAO,QAAQ,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,KAAK,EAAE,GAAW,EAAE,EAAE,CAC3D,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CACpE,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAkC;IACjE,6EAA6E;IAC7E;QACE,IAAI,EAAE,iBAAiB,CAAC,mBAAmB;QAC3C,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,qDAAqD,CAAC,IAAI,CAAC,CAAC,CAAC;QAC3E,SAAS,EAAE,KAAK;KACjB;IACD;QACE,IAAI,EAAE,iBAAiB,CAAC,kBAAkB;QAC1C,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,oFAAoF,CAAC,IAAI,CAAC,CAAC,CAAC;QAC1G,SAAS,EAAE,KAAK;KACjB;IACD;QACE,IAAI,EAAE,iBAAiB,CAAC,qBAAqB;QAC7C,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,gCAAgC,CAAC,IAAI,CAAC,CAAC,CAAC;QACtD,SAAS,EAAE,KAAK;KACjB;IAED,6EAA6E;IAC7E;QACE,IAAI,EAAE,iBAAiB,CAAC,cAAc;QACtC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;QACxC,SAAS,EAAE,KAAK;QAChB,IAAI,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAC,EAAE,CAAC;KAClE;IACD;QACE,IAAI,EAAE,iBAAiB,CAAC,uBAAuB;QAC/C,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,uEAAuE,CAAC;QAC9F,SAAS,EAAE,KAAK;QAChB,IAAI,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,uBAAuB,CAAC,CAAC,EAAE,CAAC;KAC9F;IACD;QACE,IAAI,EAAE,iBAAiB,CAAC,eAAe;QACvC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,uFAAuF,CAAC;QAC9G,SAAS,EAAE,KAAK;QAChB,IAAI,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;YACpB,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;YACvD,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,wCAAwC,CAAC;SACjE,CAAC;KACH;IACD;QACE,IAAI,EAAE,iBAAiB,CAAC,oBAAoB;QAC5C,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,kCAAkC,CAAC,IAAI,CAAC,CAAC,CAAC;QACxD,SAAS,EAAE,KAAK;KACjB;IAED,6EAA6E;IAC7E;QACE,0EAA0E;QAC1E,IAAI,EAAE,iBAAiB,CAAC,cAAc;QACtC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,4BAA4B,CAAC,IAAI,CAAC,CAAC,CAAC;QAClD,SAAS,EAAE,IAAI;KAChB;IACD;QACE,gFAAgF;QAChF,IAAI,EAAE,iBAAiB,CAAC,cAAc;QACtC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,4BAA4B,CAAC,IAAI,CAAC,CAAC,CAAC;QAClD,SAAS,EAAE,KAAK;KACjB;IACD;QACE,8DAA8D;QAC9D,IAAI,EAAE,iBAAiB,CAAC,iBAAiB;QACzC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,2CAA2C,CAAC;QAClE,SAAS,EAAE,KAAK;QAChB,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,4BAA4B,CAAC,EAAE,CAAC;KAC7E;IAED,6EAA6E;IAC7E;QACE,IAAI,EAAE,iBAAiB,CAAC,oBAAoB;QAC5C,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,8DAA8D,CAAC;QACrF,SAAS,EAAE,KAAK;QAChB,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACf,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,CAAC;YACrC,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,sBAAsB,CAAC;SAC/C,CAAC;KACH;IACD;QACE,IAAI,EAAE,iBAAiB,CAAC,wBAAwB;QAChD,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,iFAAiF,CAAC;QACxG,SAAS,EAAE,KAAK;QAChB,IAAI,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;YACpB,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,CAAC;YACrC,aAAa,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;YAC/D,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,sBAAsB,CAAC;SAC/C,CAAC;KACH;IAED,6EAA6E;IAC7E;QACE,IAAI,EAAE,iBAAiB,CAAC,wBAAwB;QAChD,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,kCAAkC,CAAC,IAAI,CAAC,CAAC,CAAC;QACxD,SAAS,EAAE,KAAK;KACjB;IACD;QACE,IAAI,EAAE,iBAAiB,CAAC,mBAAmB;QAC3C,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,2BAA2B,CAAC,IAAI,CAAC,CAAC,CAAC;QACjD,SAAS,EAAE,KAAK;KACjB;IAED,6EAA6E;IAC7E;QACE,0EAA0E;QAC1E,4EAA4E;QAC5E,IAAI,EAAE,iBAAiB,CAAC,OAAO;QAC/B,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CACX,gGAAgG,CAAC,IAAI,CAAC,CAAC,CAAC;QAC1G,SAAS,EAAE,KAAK;KACjB;IACD;QACE,2EAA2E;QAC3E,8DAA8D;QAC9D,IAAI,EAAE,iBAAiB,CAAC,aAAa;QACrC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CACX,CAAC,kCAAkC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,8CAA8C,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACtG,gCAAgC,CAAC,IAAI,CAAC,CAAC,CAAC;YACxC,mCAAmC,CAAC,IAAI,CAAC,CAAC,CAAC;YAC3C,uBAAuB,CAAC,IAAI,CAAC,CAAC,CAAC;QACjC,SAAS,EAAE,IAAI;KAChB;IACD;QACE,0EAA0E;QAC1E,qDAAqD;QACrD,IAAI,EAAE,iBAAiB,CAAC,kBAAkB;QAC1C,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,kCAAkC,CAAC,IAAI,CAAC,CAAC,CAAC;QACxD,SAAS,EAAE,KAAK;KACjB;CACO,CAAC;AAEX;;;;;;;;;;GAUG;AACH,MAAM,UAAU,qBAAqB,CACnC,UAAkB,EAClB,MAA4B,EAAE;IAE9B,MAAM,GAAG,GAAG,OAAO,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC;IAEnF,KAAK,MAAM,KAAK,IAAI,oBAAoB,EAAE,CAAC;QACzC,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,CAAC,MAAM;YAAE,SAAS;QACtB,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;QACnD,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1D,OAAO;YACL,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,WAAW,EAAE,cAAc,CAAC,uBAAuB,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC;SACvE,CAAC;IACJ,CAAC;IAED,OAAO;QACL,IAAI,EAAE,iBAAiB,CAAC,OAAO;QAC/B,SAAS,EAAE,KAAK;QAChB,WAAW,EAAE,cAAc,CAAC,uBAAuB,CAAC,iBAAiB,CAAC,OAAO,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC;KACzF,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB,CAAC,KAAc;IACrD,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,OAAO,qBAAqB,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC;AAClD,CAAC"}
@@ -1,5 +1,6 @@
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";
3
4
  export interface NotificationServiceOptions {
4
5
  telegram_chat_id?: string;
5
6
  mcpTransport?: McpTransport;
@@ -7,7 +8,20 @@ export interface NotificationServiceOptions {
7
8
  gateway_token?: string;
8
9
  [key: string]: unknown;
9
10
  }
10
- export declare function formatDecisionErrorNotification(rawMessage: string): string;
11
+ /**
12
+ * Map a raw LLM-decision error string to the user-facing Telegram message.
13
+ *
14
+ * Thin wrapper over {@link classifyDecisionError} — all message wording and the
15
+ * failure-mode lookup live in `decision-errors.ts` (the single source of truth
16
+ * shared with the retry logic in `openclaw-llm-decision.ts`). The runtime catches
17
+ * errors from the LLM-decision call and passes `err.message` here.
18
+ *
19
+ * @param rawMessage - Error message thrown by the decision client (may embed an
20
+ * OpenClaw gateway message).
21
+ * @param ctx - Optional plugin-side context (action name, configured model) for
22
+ * placeholders the gateway error string cannot supply.
23
+ */
24
+ export declare function formatDecisionErrorNotification(rawMessage: string, ctx?: DecisionErrorContext): string;
11
25
  /**
12
26
  * IMPORTANT: Do NOT use logger.error(...) anywhere inside this service.
13
27
  * NotificationService is itself the Telegram delivery path for error notifications.
@@ -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;AAKpE,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;AAmBD,wBAAgB,+BAA+B,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAiD1E;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,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,62 +1,21 @@
1
1
  import { createLogger } from "../utils/logger.js";
2
+ import { classifyDecisionError } from "./decision-errors.js";
2
3
  const logger = createLogger("[senpi-core][notifications]");
3
- // Decision-error → user-facing chat message.
4
- //
5
- // The runtime catches errors from the LLM-decision call (openclaw-llm-decision.ts:
6
- // throws on token-missing, on non-2xx HTTP, and on body.ok=false) and passes
7
- // err.message here. Network-level failures (fetch() rejecting) also reach this
8
- // with the underlying error string.
9
- //
10
- // Discriminators: HTTP status prefix + presence of a structured tool_error body
11
- // + node fetch error markers. The mapping below is intentionally specific per
12
- // failure mode so the chat reader can tell apart:
13
- // - infrastructure unreachable vs misconfigured,
14
- // - signal dropped because the AI model timed out vs the AI model errored vs
15
- // OpenClaw itself returned an unexpected error.
16
- // Auth (401/403) is intentionally NOT handled separately — the runtime plugin
17
- // and the gateway it calls share the same process env and the same token, so
18
- // 401/403 is unreachable in practice; if it ever fires, the generic fallback
19
- // at the bottom surfaces the raw message.
20
- export function formatDecisionErrorNotification(rawMessage) {
21
- const s = rawMessage;
22
- // Token missing at config-time — fires before any HTTP call, so check first.
23
- if (/gateway token not configured|OPENCLAW_GATEWAY_TOKEN/.test(s)) {
24
- return "Senpi: OpenClaw gateway token not configured. Set OPENCLAW_GATEWAY_TOKEN.";
25
- }
26
- // (1) OpenClaw Gateway not reachable — fetch() rejected before getting a
27
- // response. Node's undici fetch surfaces these as "fetch failed" with the
28
- // underlying SystemError code in .cause; the action runner stringifies it
29
- // so the code marker usually lands in the message.
30
- if (/^fetch failed$|fetch failed:|ECONNREFUSED|ECONNRESET|ETIMEDOUT|ENOTFOUND|EAI_AGAIN/.test(s)) {
31
- return "Senpi: OpenClaw Gateway is unreachable.";
32
- }
33
- // (2) Endpoint missing — plugin not installed at this path, or version mismatch.
34
- if (/OpenClaw gateway error \(404\)/.test(s)) {
35
- return "Senpi: OpenClaw is missing the plugin endpoint. Please reinstall the plugin.";
36
- }
37
- // (3) AI model timed out — OpenClaw's llm-task tool returns 408 when it hits
38
- // its own per-call cap; the gateway returns 504 when the upstream call exceeded
39
- // the gateway's frame timeout. AbortError covers cancellation paths that may
40
- // be added at the runtime side in the future.
41
- if (/OpenClaw gateway error \(408\)|OpenClaw gateway error \(504\)|AbortError|operation was aborted/.test(s)) {
42
- return "Senpi: Signal Dropped - AI model didn't respond in 90 seconds.";
43
- }
44
- // (4) AI model failed — HTTP 5xx whose body carries a structured tool_error
45
- // (today's case: Gemini overload → llm-task wraps as `{"ok":false,
46
- // "error":{"type":"tool_error","message":"tool execution failed"}}` →
47
- // gateway returns HTTP 500), OR HTTP 200 with body.ok=false from the same
48
- // tool layer ("OpenClaw gateway rejected request: ...").
49
- if ((/OpenClaw gateway error \(5\d\d\)/.test(s) && /"type":\s*"tool_error"/.test(s)) ||
50
- /OpenClaw gateway rejected request/.test(s)) {
51
- return "Senpi: Signal Dropped - AI model failed to respond.";
52
- }
53
- // (5) Any other 5xx — gateway-frame error or unparseable body.
54
- if (/OpenClaw gateway error \(5\d\d\)/.test(s)) {
55
- return "Senpi: Signal Dropped - OpenClaw hit an unexpected error.";
56
- }
57
- // Last-resort generic prefix — surfaces the underlying message so operators
58
- // can still diagnose unknown failure modes from the chat.
59
- return `Senpi: Trading decision failed — ${s}`;
4
+ /**
5
+ * Map a raw LLM-decision error string to the user-facing Telegram message.
6
+ *
7
+ * Thin wrapper over {@link classifyDecisionError} — all message wording and the
8
+ * failure-mode lookup live in `decision-errors.ts` (the single source of truth
9
+ * shared with the retry logic in `openclaw-llm-decision.ts`). The runtime catches
10
+ * errors from the LLM-decision call and passes `err.message` here.
11
+ *
12
+ * @param rawMessage - Error message thrown by the decision client (may embed an
13
+ * OpenClaw gateway message).
14
+ * @param ctx - Optional plugin-side context (action name, configured model) for
15
+ * placeholders the gateway error string cannot supply.
16
+ */
17
+ export function formatDecisionErrorNotification(rawMessage, ctx) {
18
+ return classifyDecisionError(rawMessage, ctx).userMessage;
60
19
  }
61
20
  /**
62
21
  * IMPORTANT: Do NOT use logger.error(...) anywhere inside this service.