@holon-run/agentinbox 1.3.0 → 1.3.1
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/dist/src/service.js +59 -2
- package/package.json +1 -1
- package/skills/agentinbox/SKILL.md +37 -0
package/dist/src/service.js
CHANGED
|
@@ -17,6 +17,8 @@ const DEFAULT_ACTIVATION_MAX_ITEMS = 20;
|
|
|
17
17
|
const DEFAULT_NOTIFY_LEASE_MS = 10 * 60 * 1000;
|
|
18
18
|
const DEFAULT_NOTIFY_RETRY_MS = 5_000;
|
|
19
19
|
const MAX_TERMINAL_DEFER_RETRY_MS = 5 * 60 * 1000;
|
|
20
|
+
const MAX_WEBHOOK_RETRY_MS = 5 * 60 * 1000;
|
|
21
|
+
const MAX_WEBHOOK_CONSECUTIVE_FAILURES = 12;
|
|
20
22
|
const DEFAULT_ACKED_RETENTION_MS = 24 * 60 * 60 * 1000;
|
|
21
23
|
const DEFAULT_OFFLINE_AGENT_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
22
24
|
const DEFAULT_GC_INTERVAL_MS = 60 * 1000;
|
|
@@ -2097,6 +2099,10 @@ class AgentInboxService {
|
|
|
2097
2099
|
: uniqueSorted(entries.map((entry) => entry.sourceId)),
|
|
2098
2100
|
entries: unackedItems,
|
|
2099
2101
|
});
|
|
2102
|
+
// Permanent errors and max-retries-exceeded signal "offline":
|
|
2103
|
+
if (dispatched === "offline") {
|
|
2104
|
+
this.store.deleteActivationDispatchState(buffer.agentId, buffer.targetId);
|
|
2105
|
+
}
|
|
2100
2106
|
if (dispatched === "retryable_failure") {
|
|
2101
2107
|
if (state && state.status === "dirty" && state.leaseExpiresAt == null) {
|
|
2102
2108
|
this.store.upsertActivationDispatchState({
|
|
@@ -2276,10 +2282,17 @@ class AgentInboxService {
|
|
|
2276
2282
|
return;
|
|
2277
2283
|
}
|
|
2278
2284
|
if (dispatched === "retryable_failure") {
|
|
2285
|
+
let backoffMs = DEFAULT_NOTIFY_RETRY_MS;
|
|
2286
|
+
if (target.kind === "webhook") {
|
|
2287
|
+
const updatedTarget = this.getActivationTargetIfExists(agentId, targetId);
|
|
2288
|
+
if (updatedTarget) {
|
|
2289
|
+
backoffMs = webhookDeferRetryMs(updatedTarget.consecutiveFailures);
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2279
2292
|
this.store.upsertActivationDispatchState({
|
|
2280
2293
|
...state,
|
|
2281
2294
|
status: "dirty",
|
|
2282
|
-
leaseExpiresAt: new Date(Date.now() +
|
|
2295
|
+
leaseExpiresAt: new Date(Date.now() + backoffMs).toISOString(),
|
|
2283
2296
|
deferReason: null,
|
|
2284
2297
|
deferAttempts: 0,
|
|
2285
2298
|
firstDeferredAt: null,
|
|
@@ -2548,6 +2561,35 @@ class AgentInboxService {
|
|
|
2548
2561
|
return "offline";
|
|
2549
2562
|
}
|
|
2550
2563
|
}
|
|
2564
|
+
if (target.kind === "webhook") {
|
|
2565
|
+
const statusCode = extractHttpStatus(error);
|
|
2566
|
+
// 404 / 410: endpoint gone
|
|
2567
|
+
// 409 / 422: expired or invalid webhook config
|
|
2568
|
+
if (statusCode === 404 || statusCode === 410 || statusCode === 409 || statusCode === 422) {
|
|
2569
|
+
this.logger.info("activation.webhook_permanent_error", {
|
|
2570
|
+
agentId: input.agentId,
|
|
2571
|
+
targetId: target.targetId,
|
|
2572
|
+
statusCode,
|
|
2573
|
+
message,
|
|
2574
|
+
});
|
|
2575
|
+
this.markActivationTargetOffline(target.targetId, message);
|
|
2576
|
+
this.reconcileAgentStatus(target.agentId);
|
|
2577
|
+
return "offline";
|
|
2578
|
+
}
|
|
2579
|
+
// Max consecutive transient failures exceeded
|
|
2580
|
+
if (target.consecutiveFailures + 1 >= MAX_WEBHOOK_CONSECUTIVE_FAILURES) {
|
|
2581
|
+
this.logger.info("activation.webhook_max_retries_exceeded", {
|
|
2582
|
+
agentId: input.agentId,
|
|
2583
|
+
targetId: target.targetId,
|
|
2584
|
+
consecutiveFailures: target.consecutiveFailures + 1,
|
|
2585
|
+
maxRetries: MAX_WEBHOOK_CONSECUTIVE_FAILURES,
|
|
2586
|
+
});
|
|
2587
|
+
this.markActivationTargetOffline(target.targetId, message);
|
|
2588
|
+
this.reconcileAgentStatus(target.agentId);
|
|
2589
|
+
return "offline";
|
|
2590
|
+
}
|
|
2591
|
+
// Transient error: fall through to markActivationTargetDispatchFailure
|
|
2592
|
+
}
|
|
2551
2593
|
this.markActivationTargetDispatchFailure(target.targetId, message);
|
|
2552
2594
|
return "retryable_failure";
|
|
2553
2595
|
}
|
|
@@ -3374,7 +3416,9 @@ class ActivationDispatcher {
|
|
|
3374
3416
|
body: JSON.stringify(activation),
|
|
3375
3417
|
});
|
|
3376
3418
|
if (!response.ok) {
|
|
3377
|
-
|
|
3419
|
+
const error = new Error(`activation dispatch failed for ${targetUrl}: ${response.status}`);
|
|
3420
|
+
error.statusCode = response.status;
|
|
3421
|
+
throw error;
|
|
3378
3422
|
}
|
|
3379
3423
|
}
|
|
3380
3424
|
catch (error) {
|
|
@@ -3593,6 +3637,19 @@ function terminalDeferRetryMs(attempts) {
|
|
|
3593
3637
|
const multiplier = 2 ** (normalizedAttempts - 1);
|
|
3594
3638
|
return Math.min(DEFAULT_NOTIFY_RETRY_MS * multiplier, MAX_TERMINAL_DEFER_RETRY_MS);
|
|
3595
3639
|
}
|
|
3640
|
+
function webhookDeferRetryMs(consecutiveFailures) {
|
|
3641
|
+
const normalized = Math.max(1, consecutiveFailures);
|
|
3642
|
+
const multiplier = 2 ** (normalized - 1);
|
|
3643
|
+
return Math.min(DEFAULT_NOTIFY_RETRY_MS * multiplier, MAX_WEBHOOK_RETRY_MS);
|
|
3644
|
+
}
|
|
3645
|
+
function extractHttpStatus(error) {
|
|
3646
|
+
if (error instanceof Error) {
|
|
3647
|
+
const code = error.statusCode;
|
|
3648
|
+
if (typeof code === "number")
|
|
3649
|
+
return code;
|
|
3650
|
+
}
|
|
3651
|
+
return null;
|
|
3652
|
+
}
|
|
3596
3653
|
function summarizeSourceEvent(sourceType, sourceKey, eventVariant) {
|
|
3597
3654
|
if (sourceType === "github_repo_ci") {
|
|
3598
3655
|
const parts = eventVariant.split(".");
|
package/package.json
CHANGED
|
@@ -193,6 +193,43 @@ agentinbox source invoke <sourceId> --operation get_message_context --input-json
|
|
|
193
193
|
agentinbox deliver invoke --handle-json '<deliveryHandle-json>' --operation send_text --input-json '{"text":"Acknowledged","uxcAuth":"feishu-default"}'
|
|
194
194
|
```
|
|
195
195
|
|
|
196
|
+
Feishu messages may carry files, images, or cloud document links. List the
|
|
197
|
+
attachments first and then save them locally:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
# List attachments for a message
|
|
201
|
+
agentinbox source invoke <sourceId> \
|
|
202
|
+
--operation list_message_attachments \
|
|
203
|
+
--input-json '{"messageId":"<messageId>"}'
|
|
204
|
+
|
|
205
|
+
# Save one attachment by messageId + attachmentId
|
|
206
|
+
agentinbox source invoke <sourceId> \
|
|
207
|
+
--operation save_attachment \
|
|
208
|
+
--input-json '{"messageId":"<messageId>","attachmentId":"<attachmentId>","outputDir":"/tmp/agentinbox-feishu"}'
|
|
209
|
+
|
|
210
|
+
# Save with explicit output path and identity
|
|
211
|
+
agentinbox source invoke <sourceId> \
|
|
212
|
+
--operation save_attachment \
|
|
213
|
+
--input-json '{"messageId":"<messageId>","attachmentId":"<attachmentId>","outputPath":"/tmp/report.pdf","identity":"bot"}'
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Output formats by attachment kind:
|
|
217
|
+
|
|
218
|
+
| Kind | Default | `format` options |
|
|
219
|
+
|---|---|---|
|
|
220
|
+
| `image` / `file` | original binary | `"original"` only |
|
|
221
|
+
| `doc` / `docx` / `wiki` | `.md` (Markdown) | N/A |
|
|
222
|
+
| `sheet` | `.xlsx` | `"csv"` (requires `subId`) |
|
|
223
|
+
| `bitable` / `base` | `.base` | `"csv"` (requires `subId`) |
|
|
224
|
+
| `drive_file` | original file | `"original"` only |
|
|
225
|
+
|
|
226
|
+
Optional `save_attachment` parameters: `outputPath`, `outputDir`, `fileName`,
|
|
227
|
+
`format`, `subId`, `identity` (`"user"` or `"bot"`), `overwrite`.
|
|
228
|
+
|
|
229
|
+
Rich text and file replies are available through `send_post`, `send_file`,
|
|
230
|
+
and `send_file_from_path` delivery operations. Use `deliver actions` to
|
|
231
|
+
inspect available schemas.
|
|
232
|
+
|
|
196
233
|
Advanced source/subscription commands:
|
|
197
234
|
|
|
198
235
|
```bash
|