@tbrandenburg/node-red-agents 0.3.5 → 0.3.7
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.
- package/nodes/agent/agent.js +8 -0
- package/nodes/agent/lib/agents/opencode.js +18 -4
- package/nodes/agent/lib/agents/pi.js +13 -2
- package/nodes/gh/README.md +17 -1
- package/nodes/gh/gh.js +29 -3
- package/package.json +1 -1
package/nodes/agent/agent.js
CHANGED
|
@@ -250,6 +250,14 @@ module.exports = function (RED) {
|
|
|
250
250
|
timedOut: result.timedOut,
|
|
251
251
|
durationMs: result.durationMs,
|
|
252
252
|
sessionID: result.sessionID,
|
|
253
|
+
// Raw error object from the adapter (e.g. opencode's full
|
|
254
|
+
// {"type":"error"} payload, or pi's failing assistant
|
|
255
|
+
// message) when the run failed -- the `done(err)` string
|
|
256
|
+
// below only carries a single summarized message/name, so
|
|
257
|
+
// anything needing the fuller detail (extra fields the
|
|
258
|
+
// adapter didn't fold into errorMessage) should wire a
|
|
259
|
+
// Debug node to output 1 and inspect this field.
|
|
260
|
+
errorDetail: result.errorDetail,
|
|
253
261
|
},
|
|
254
262
|
});
|
|
255
263
|
send([resultMsg, null]);
|
|
@@ -119,11 +119,25 @@ class OpenCodeAdapter extends AgentAdapter {
|
|
|
119
119
|
.trim();
|
|
120
120
|
|
|
121
121
|
if (errorEvent) {
|
|
122
|
+
const errDetail = errorEvent.error || {};
|
|
122
123
|
const message =
|
|
123
|
-
(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
(errDetail.data && errDetail.data.message) || errDetail.name || "opencode reported an error";
|
|
125
|
+
// The JSON error event only carries opencode's own top-level
|
|
126
|
+
// message/name -- append name (if distinct) and any stderr output
|
|
127
|
+
// opencode wrote alongside it, since both would otherwise be
|
|
128
|
+
// silently dropped here (unlike the exitCode!==0 branch below,
|
|
129
|
+
// which already surfaces stderr).
|
|
130
|
+
const extras = [];
|
|
131
|
+
if (errDetail.name && errDetail.name !== message) extras.push(errDetail.name);
|
|
132
|
+
if (stderr && String(stderr).trim()) extras.push(String(stderr).trim());
|
|
133
|
+
const errorMessage = extras.length ? `${message} (${extras.join("; ")})` : message;
|
|
134
|
+
return {
|
|
135
|
+
payload,
|
|
136
|
+
sessionID,
|
|
137
|
+
status: "failed",
|
|
138
|
+
errorMessage,
|
|
139
|
+
errorDetail: errDetail,
|
|
140
|
+
};
|
|
127
141
|
}
|
|
128
142
|
if (signal) {
|
|
129
143
|
return {
|
|
@@ -186,9 +186,10 @@ class PiAdapter extends AgentAdapter {
|
|
|
186
186
|
|
|
187
187
|
let payload = "";
|
|
188
188
|
let errorMessage;
|
|
189
|
+
let lastAssistant;
|
|
189
190
|
|
|
190
191
|
if (agentEnd && Array.isArray(agentEnd.messages)) {
|
|
191
|
-
|
|
192
|
+
lastAssistant = [...agentEnd.messages].reverse().find((m) => m.role === "assistant");
|
|
192
193
|
if (lastAssistant) {
|
|
193
194
|
if (lastAssistant.stopReason === "error") {
|
|
194
195
|
// pi does NOT set a non-zero exit code for a model/API
|
|
@@ -207,7 +208,17 @@ class PiAdapter extends AgentAdapter {
|
|
|
207
208
|
}
|
|
208
209
|
|
|
209
210
|
if (errorMessage) {
|
|
210
|
-
|
|
211
|
+
// Same rationale as the opencode adapter: stderr is otherwise
|
|
212
|
+
// silently dropped for this branch (it's only appended in the
|
|
213
|
+
// exitCode!==0 branch below).
|
|
214
|
+
const extra = stderr && String(stderr).trim() ? ` (${String(stderr).trim()})` : "";
|
|
215
|
+
return {
|
|
216
|
+
payload,
|
|
217
|
+
sessionID,
|
|
218
|
+
status: "failed",
|
|
219
|
+
errorMessage: `${errorMessage}${extra}`,
|
|
220
|
+
errorDetail: lastAssistant,
|
|
221
|
+
};
|
|
211
222
|
}
|
|
212
223
|
if (signal) {
|
|
213
224
|
return {
|
package/nodes/gh/README.md
CHANGED
|
@@ -49,7 +49,23 @@ configuration for that message.
|
|
|
49
49
|
|
|
50
50
|
A non-zero exit code, a timeout, or a missing `gh` executable all go through
|
|
51
51
|
`done(error)` (catchable with a Catch node) instead of producing an output
|
|
52
|
-
message.
|
|
52
|
+
message. Every such error carries an `err.errorType` string so flows can
|
|
53
|
+
branch on it without regex-matching gh's (locale/version dependent) human
|
|
54
|
+
-readable stderr text themselves:
|
|
55
|
+
|
|
56
|
+
| `errorType` | When |
|
|
57
|
+
| ------------------- | ------------------------------------------------------------------------------ |
|
|
58
|
+
| `auth` | Not logged in / bad credentials (stderr mentions `gh auth login`, etc.) |
|
|
59
|
+
| `rate-limit` | GitHub API (primary or secondary) rate limit exceeded |
|
|
60
|
+
| `feature-disabled` | Repo has issues/PRs/projects disabled (e.g. this ticket's original report) |
|
|
61
|
+
| `not-found` | Repository/resource doesn't exist or can't be resolved |
|
|
62
|
+
| `permission` | 403 / insufficient permissions |
|
|
63
|
+
| `network` | Network failure or timeout talking to GitHub |
|
|
64
|
+
| `not-installed` | `gh` executable not found on `PATH` (spawn `ENOENT`) |
|
|
65
|
+
| `spawn-failed` | The child process could not be started for another reason |
|
|
66
|
+
| `timeout` | The command exceeded the configured Timeout and was killed |
|
|
67
|
+
| `killed` | The command was killed by a signal other than the node's own timeout |
|
|
68
|
+
| `unknown` | Non-zero exit whose stderr didn't match any known pattern |
|
|
53
69
|
|
|
54
70
|
## Example
|
|
55
71
|
|
package/nodes/gh/gh.js
CHANGED
|
@@ -38,6 +38,27 @@ function validateCommand(command) {
|
|
|
38
38
|
return null;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
// Classifies a gh stderr message into a stable, machine-checkable category so
|
|
42
|
+
// downstream flows can branch on `err.errorType` instead of regex-matching
|
|
43
|
+
// the (locale/version dependent) human-readable message themselves. Order
|
|
44
|
+
// matters: more specific patterns are checked before generic ones.
|
|
45
|
+
const ERROR_TYPE_PATTERNS = [
|
|
46
|
+
[/not logged into|to authenticate|gh auth login|authentication required|bad credentials/i, "auth"],
|
|
47
|
+
[/api rate limit exceeded|secondary rate limit/i, "rate-limit"],
|
|
48
|
+
[/has disabled issues|has disabled pull requests|has disabled projects/i, "feature-disabled"],
|
|
49
|
+
[/could not resolve to a repository|repository not found|404/i, "not-found"],
|
|
50
|
+
[/403|resource not accessible|must have admin rights|permission/i, "permission"],
|
|
51
|
+
[/network|timed out|econnreset|enotfound/i, "network"],
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
function classifyGhError(stderr) {
|
|
55
|
+
if (!stderr) return "unknown";
|
|
56
|
+
for (const [pattern, type] of ERROR_TYPE_PATTERNS) {
|
|
57
|
+
if (pattern.test(stderr)) return type;
|
|
58
|
+
}
|
|
59
|
+
return "unknown";
|
|
60
|
+
}
|
|
61
|
+
|
|
41
62
|
// Resolves an "args" value (from config or msg.gh.args) into a string[].
|
|
42
63
|
// Strings are tokenized (quote-aware, no shell evaluation); arrays are used
|
|
43
64
|
// as-is (each element coerced to a string); anything else is rejected.
|
|
@@ -172,6 +193,7 @@ module.exports = function (RED) {
|
|
|
172
193
|
const wrapped = new Error("gh: " + message);
|
|
173
194
|
wrapped.command = command;
|
|
174
195
|
wrapped.args = args;
|
|
196
|
+
wrapped.errorType = err && err.code === "ENOENT" ? "not-installed" : "spawn-failed";
|
|
175
197
|
done(wrapped);
|
|
176
198
|
});
|
|
177
199
|
|
|
@@ -188,19 +210,23 @@ module.exports = function (RED) {
|
|
|
188
210
|
err.args = args;
|
|
189
211
|
err.signal = signal;
|
|
190
212
|
err.timedOut = timedOut;
|
|
213
|
+
err.errorType = timedOut ? "timeout" : "killed";
|
|
191
214
|
done(err);
|
|
192
215
|
return;
|
|
193
216
|
}
|
|
194
217
|
|
|
195
218
|
if (code !== 0) {
|
|
196
|
-
|
|
219
|
+
const trimmedStderr = stderr.trim();
|
|
220
|
+
const errorType = classifyGhError(trimmedStderr);
|
|
221
|
+
node.status({ fill: "red", shape: "dot", text: errorType + " (exit " + code + ")" });
|
|
197
222
|
const err = new Error(
|
|
198
|
-
"gh: command failed (exit " + code + ")" + (
|
|
223
|
+
"gh: command failed (exit " + code + ")" + (trimmedStderr ? ": " + trimmedStderr : ""),
|
|
199
224
|
);
|
|
200
225
|
err.command = command;
|
|
201
226
|
err.args = args;
|
|
202
227
|
err.exitCode = code;
|
|
203
|
-
err.stderr =
|
|
228
|
+
err.stderr = trimmedStderr;
|
|
229
|
+
err.errorType = errorType;
|
|
204
230
|
done(err);
|
|
205
231
|
return;
|
|
206
232
|
}
|