mixdog 0.9.24 → 0.9.26
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/package.json +2 -1
- package/scripts/build-runtime-windows.ps1 +242 -242
- package/scripts/explore-bench-tmp.mjs +1 -1
- package/scripts/recall-usecase-cases.json +1 -1
- package/scripts/smoke-runtime-negative.ps1 +106 -106
- package/scripts/steering-fold-provenance-test.mjs +71 -0
- package/scripts/tool-efficiency-diag.mjs +1 -1
- package/scripts/webhook-smoke.mjs +46 -53
- package/src/defaults/cycle3-review-prompt.md +11 -4
- package/src/defaults/memory-promote-prompt.md +9 -0
- package/src/defaults/skills/setup/SKILL.md +6 -1
- package/src/rules/lead/02-channels.md +3 -3
- package/src/rules/shared/01-tool.md +8 -4
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +17 -0
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +2 -2
- package/src/runtime/agent/orchestrator/mcp/client.mjs +28 -10
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +8 -1
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +51 -15
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +164 -9
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +45 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/shell-state.mjs +7 -3
- package/src/runtime/channels/backends/discord.mjs +10 -3
- package/src/runtime/channels/lib/config.mjs +13 -11
- package/src/runtime/channels/lib/inbound-handler.mjs +45 -1
- package/src/runtime/channels/lib/memory-client.mjs +22 -2
- package/src/runtime/channels/lib/owned-runtime.mjs +1 -1
- package/src/runtime/channels/lib/scheduler.mjs +226 -208
- package/src/runtime/channels/lib/status-snapshot.mjs +16 -0
- package/src/runtime/channels/lib/webhook/deliveries.mjs +7 -318
- package/src/runtime/channels/lib/webhook.mjs +98 -150
- package/src/runtime/channels/lib/worker-main.mjs +1 -1
- package/src/runtime/memory/index.mjs +50 -25
- package/src/runtime/memory/lib/cycle-scheduler.mjs +20 -2
- package/src/runtime/memory/lib/memory-config-flags.mjs +13 -1
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +59 -0
- package/src/runtime/memory/lib/memory-cycle2.mjs +10 -3
- package/src/runtime/memory/lib/memory-cycle3.mjs +79 -9
- package/src/runtime/memory/lib/pg/adapter.mjs +6 -1
- package/src/runtime/memory/lib/pg/process.mjs +19 -1
- package/src/runtime/memory/lib/pg/supervisor.mjs +125 -14
- package/src/runtime/memory/lib/query-handlers.mjs +102 -0
- package/src/runtime/memory/lib/recall-format.mjs +60 -22
- package/src/runtime/memory/lib/session-ingest-runtime.mjs +20 -6
- package/src/runtime/memory/tool-defs.mjs +1 -1
- package/src/runtime/shared/child-guardian.mjs +2 -2
- package/src/runtime/shared/open-url.mjs +2 -1
- package/src/runtime/shared/schedules-db.mjs +350 -0
- package/src/runtime/shared/service-discovery.mjs +169 -0
- package/src/runtime/shared/spawn-flags.mjs +27 -0
- package/src/runtime/shared/tool-primitives.mjs +3 -1
- package/src/runtime/shared/tool-surface.mjs +19 -3
- package/src/runtime/shared/update-checker.mjs +54 -10
- package/src/runtime/shared/webhooks-db.mjs +405 -0
- package/src/session-runtime/channel-config-api.mjs +13 -13
- package/src/session-runtime/lifecycle-api.mjs +14 -0
- package/src/session-runtime/runtime-core.mjs +39 -20
- package/src/standalone/agent-tool.mjs +42 -11
- package/src/standalone/channel-admin.mjs +173 -121
- package/src/standalone/channel-worker.mjs +2 -2
- package/src/standalone/memory-runtime-proxy.mjs +10 -5
- package/src/tui/App.jsx +32 -10
- package/src/tui/app/channel-pickers.mjs +9 -9
- package/src/tui/app/clipboard.mjs +14 -0
- package/src/tui/app/doctor.mjs +1 -1
- package/src/tui/app/maintenance-pickers.mjs +17 -7
- package/src/tui/app/settings-picker.mjs +2 -2
- package/src/tui/app/transcript-window.mjs +37 -1
- package/src/tui/app/use-mouse-input.mjs +19 -6
- package/src/tui/components/ToolExecution.jsx +12 -4
- package/src/tui/display-width.mjs +10 -4
- package/src/tui/dist/index.mjs +166 -58
- package/src/tui/engine/agent-job-feed.mjs +42 -3
- package/src/tui/engine/session-api-ext.mjs +12 -12
- package/src/tui/index.jsx +12 -2
- package/src/ui/statusline-segments.mjs +12 -1
- package/src/workflows/default/WORKFLOW.md +29 -38
- package/src/workflows/solo/WORKFLOW.md +15 -20
- package/vendor/ink/build/display-width.js +5 -4
- package/src/runtime/shared/schedules-store.mjs +0 -82
|
@@ -1,318 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import { logWebhook } from "./log.mjs";
|
|
1
|
+
// Header helpers retained after the webhook store moved to PG
|
|
2
|
+
// (src/runtime/shared/webhooks-db.mjs). The file-based endpoint config,
|
|
3
|
+
// secret side-file, deliveries.jsonl log and the fs.watch cache are retired
|
|
4
|
+
// from the serving path; only these two pure functions are still consumed by
|
|
5
|
+
// webhook.mjs (delivery-id extraction + the diagnostic headers summary).
|
|
7
6
|
|
|
8
|
-
const WEBHOOKS_DIR = join(DATA_DIR, "webhooks");
|
|
9
|
-
|
|
10
|
-
// ── Endpoint config loader ─────────────────────────────────────────────
|
|
11
|
-
// Reads DATA_DIR/webhooks/<name>/WEBHOOK.md (written by setup-server.mjs
|
|
12
|
-
// via POST /webhooks). The frontmatter IS the config object; the markdown
|
|
13
|
-
// body is the instructions/prompt. Cached in-memory, invalidated by
|
|
14
|
-
// fs.watch on the webhooks directory. Returns the frontmatter object
|
|
15
|
-
// { secret, parser, channel, model, role, enabled } where routing is by
|
|
16
|
-
// `channel` presence and `role` names a user-workflow entry when set.
|
|
17
|
-
const _endpointCache = new Map();
|
|
18
|
-
let _endpointWatcher = null;
|
|
19
|
-
function _endpointConfigPath(name) {
|
|
20
|
-
return join(WEBHOOKS_DIR, name, "WEBHOOK.md");
|
|
21
|
-
}
|
|
22
|
-
// Per-endpoint HMAC secret is stored in a side file (WEBHOOKS_DIR/<name>/secret),
|
|
23
|
-
// not in WEBHOOK.md frontmatter — frontmatter is a lossy `key: value` format
|
|
24
|
-
// (unquote strips surrounding quotes) and would corrupt user secrets on the
|
|
25
|
-
// save->rewrite round-trip. Read fresh on each verify (no cache): the signing
|
|
26
|
-
// path is not hot and a stale secret would silently reject valid deliveries.
|
|
27
|
-
function _readEndpointSecret(name) {
|
|
28
|
-
try {
|
|
29
|
-
const s = readFileSync(join(WEBHOOKS_DIR, name, "secret"), "utf8").trim();
|
|
30
|
-
return s || null;
|
|
31
|
-
} catch {
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
function _ensureEndpointWatcher() {
|
|
36
|
-
if (_endpointWatcher) return;
|
|
37
|
-
try {
|
|
38
|
-
if (!existsSync(WEBHOOKS_DIR)) return;
|
|
39
|
-
_endpointWatcher = fsWatch(WEBHOOKS_DIR, { recursive: true }, (_event, filename) => {
|
|
40
|
-
if (!filename) { _endpointCache.clear(); return; }
|
|
41
|
-
// filename is like "<endpoint>/WEBHOOK.md" or "<endpoint>"
|
|
42
|
-
const parts = String(filename).split(/[\\/]/);
|
|
43
|
-
const endpointName = parts[0];
|
|
44
|
-
if (endpointName) _endpointCache.delete(endpointName);
|
|
45
|
-
else _endpointCache.clear();
|
|
46
|
-
});
|
|
47
|
-
_endpointWatcher.on("error", () => { _endpointWatcher = null; _endpointCache.clear(); });
|
|
48
|
-
// Don't let the watcher keep the event loop alive: embedders/tests that
|
|
49
|
-
// merely load an endpoint config should still be able to exit cleanly.
|
|
50
|
-
_endpointWatcher.unref?.();
|
|
51
|
-
} catch {
|
|
52
|
-
// Watch failures are non-fatal; cache simply stays until process restart.
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
function _closeEndpointWatcher() {
|
|
56
|
-
if (!_endpointWatcher) return;
|
|
57
|
-
try { _endpointWatcher.close(); } catch {}
|
|
58
|
-
_endpointWatcher = null;
|
|
59
|
-
_endpointCache.clear();
|
|
60
|
-
}
|
|
61
|
-
function loadEndpointConfig(name) {
|
|
62
|
-
if (!name) return null;
|
|
63
|
-
// A cached entry is only authoritative while the fs.watch handle is
|
|
64
|
-
// armed — otherwise a later mkdir+write of WEBHOOKS_DIR/<name>/
|
|
65
|
-
// WEBHOOK.md has no way to invalidate the cache and a stale `null`
|
|
66
|
-
// (e.g. captured before WEBHOOKS_DIR existed) would pin forever.
|
|
67
|
-
if (_endpointCache.has(name) && _endpointWatcher) return _endpointCache.get(name);
|
|
68
|
-
_ensureEndpointWatcher();
|
|
69
|
-
const p = _endpointConfigPath(name);
|
|
70
|
-
if (!existsSync(p)) {
|
|
71
|
-
// Only cache the missing-config state when the watcher is live, so
|
|
72
|
-
// a later create can invalidate it. Otherwise leave the slot empty
|
|
73
|
-
// and re-read on the next call.
|
|
74
|
-
if (_endpointWatcher) _endpointCache.set(name, null);
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
try {
|
|
78
|
-
// Frontmatter is the config object; `enabled` arrives as a string and
|
|
79
|
-
// is cast so `endpoint?.enabled === false` gates match a written
|
|
80
|
-
// `enabled: false`.
|
|
81
|
-
const { frontmatter } = readMarkdownDocument(readFileSync(p, "utf8"));
|
|
82
|
-
const cfg = { ...frontmatter };
|
|
83
|
-
if (Object.prototype.hasOwnProperty.call(cfg, "enabled")) {
|
|
84
|
-
cfg.enabled = cfg.enabled !== "false" && cfg.enabled !== false;
|
|
85
|
-
}
|
|
86
|
-
_endpointCache.set(name, cfg);
|
|
87
|
-
return cfg;
|
|
88
|
-
} catch {
|
|
89
|
-
if (_endpointWatcher) _endpointCache.set(name, null);
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// ── Delivery tracking ─────────────────────────────────────────────────
|
|
95
|
-
// Per-endpoint append-only log at WEBHOOKS_DIR/<name>/deliveries.jsonl.
|
|
96
|
-
// Each POST writes at least two lines: {status:"pending"|"processing"}
|
|
97
|
-
// then {status:"done"|"failed"|"dedup"}. Earlier fields (payloadPreview,
|
|
98
|
-
// headersSummary) are kept on the first line only; later status updates
|
|
99
|
-
// reference the same `id` and are merged latest-wins at read time.
|
|
100
|
-
const DELIVERY_INDEX_MAX_IDS = 2000;
|
|
101
|
-
const DELIVERY_LOG_MAX_LINES = 10_000;
|
|
102
|
-
// A "pending" claim normally resolves to a terminal status (done/failed) or
|
|
103
|
-
// "processing" within seconds. If a row is stuck at "pending" past this TTL
|
|
104
|
-
// (crash between the pending write and the terminal write, with no restart
|
|
105
|
-
// warm to clear it), treat it as abandoned rather than always-kept —
|
|
106
|
-
// otherwise an unbounded number of stuck-pending ids could accumulate and
|
|
107
|
-
// defeat DELIVERY_INDEX_MAX_IDS entirely.
|
|
108
|
-
const DELIVERY_PENDING_TTL_MS = 10 * 60 * 1000;
|
|
109
|
-
function _isFreshPending(entry) {
|
|
110
|
-
const ts = Date.parse(entry?.ts ?? "");
|
|
111
|
-
if (Number.isNaN(ts)) return true; // no timestamp to judge staleness — keep (fail safe toward dedup).
|
|
112
|
-
return Date.now() - ts < DELIVERY_PENDING_TTL_MS;
|
|
113
|
-
}
|
|
114
|
-
/** @type {Map<string, Map<string, object>>} */
|
|
115
|
-
const _deliveryIndexByEndpoint = new Map();
|
|
116
|
-
/** @type {Set<string>} */
|
|
117
|
-
const _deliveryIndexWarmed = new Set();
|
|
118
|
-
/** @type {Map<string, number>} */
|
|
119
|
-
const _deliveryLogLineCountByEndpoint = new Map();
|
|
120
|
-
/** @type {Map<string, number>} distinct-id count at last warm/compaction; drives the redundancy-based compaction trigger. */
|
|
121
|
-
const _deliveryKeptCountByEndpoint = new Map();
|
|
122
|
-
function _deliveriesPath(name) {
|
|
123
|
-
return join(WEBHOOKS_DIR, name, "deliveries.jsonl");
|
|
124
|
-
}
|
|
125
|
-
function _mergeDeliveryRows(prior, entry) {
|
|
126
|
-
return prior ? { ...prior, ...entry } : entry;
|
|
127
|
-
}
|
|
128
|
-
function _isBlockingDeliveryStatus(status) {
|
|
129
|
-
// "pending" is a non-terminal claim too: between the pending row write and
|
|
130
|
-
// the terminal done/failed row (eventPipeline enqueue, delegate dispatch) a
|
|
131
|
-
// concurrent retry of the same delivery id must dedup, not double-dispatch.
|
|
132
|
-
return status === "received" || status === "pending" || status === "processing" || status === "done";
|
|
133
|
-
}
|
|
134
|
-
function _deliveryIndexFor(name) {
|
|
135
|
-
let map = _deliveryIndexByEndpoint.get(name);
|
|
136
|
-
if (!map) {
|
|
137
|
-
map = new Map();
|
|
138
|
-
_deliveryIndexByEndpoint.set(name, map);
|
|
139
|
-
}
|
|
140
|
-
return map;
|
|
141
|
-
}
|
|
142
|
-
// Bound retained ids so successful ("done") deliveries cannot accumulate
|
|
143
|
-
// forever in RAM or on disk. In-flight claims (received/processing) are
|
|
144
|
-
// ALWAYS kept — dropping one would let a duplicate dispatch through. The
|
|
145
|
-
// remaining DELIVERY_INDEX_MAX_IDS budget goes to the newest "done" rows
|
|
146
|
-
// (dedup of recent retries) first, then newest terminal rows for history.
|
|
147
|
-
// Older "done" rows age out; a sender re-delivering an id that stale is
|
|
148
|
-
// treated as new — acceptable beyond any realistic retry window.
|
|
149
|
-
function _retainedDeliveryIds(entries) {
|
|
150
|
-
const inflight = [];
|
|
151
|
-
const done = [];
|
|
152
|
-
const other = [];
|
|
153
|
-
for (const e of entries) {
|
|
154
|
-
// "pending" is a non-terminal claim too (see _isBlockingDeliveryStatus)
|
|
155
|
-
// — it must stay in the always-kept inflight set during pruning, or a
|
|
156
|
-
// concurrent retry of the same id can bypass dedup while the original
|
|
157
|
-
// delivery is still pending. Bounded by DELIVERY_PENDING_TTL_MS so a
|
|
158
|
-
// stuck/abandoned pending row (crash, never resolved) doesn't pin
|
|
159
|
-
// itself in the always-kept set forever and grow the index unbounded;
|
|
160
|
-
// once stale it falls through to the capped `other` pool below.
|
|
161
|
-
if (e.status === "received" || e.status === "processing" || (e.status === "pending" && _isFreshPending(e))) {
|
|
162
|
-
inflight.push(e);
|
|
163
|
-
}
|
|
164
|
-
else if (e.status === "done") done.push(e);
|
|
165
|
-
else other.push(e);
|
|
166
|
-
}
|
|
167
|
-
const keep = new Set(inflight.map((e) => e.id));
|
|
168
|
-
const byTsDesc = (a, b) => String(b.ts || "").localeCompare(String(a.ts || ""));
|
|
169
|
-
done.sort(byTsDesc);
|
|
170
|
-
other.sort(byTsDesc);
|
|
171
|
-
for (const e of [...done, ...other]) {
|
|
172
|
-
if (keep.size >= DELIVERY_INDEX_MAX_IDS) break;
|
|
173
|
-
keep.add(e.id);
|
|
174
|
-
}
|
|
175
|
-
return keep;
|
|
176
|
-
}
|
|
177
|
-
function _pruneDeliveryIndexMap(byId) {
|
|
178
|
-
if (byId.size <= DELIVERY_INDEX_MAX_IDS) return;
|
|
179
|
-
const keep = _retainedDeliveryIds([...byId.values()]);
|
|
180
|
-
for (const id of byId.keys()) {
|
|
181
|
-
if (!keep.has(id)) byId.delete(id);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
function _deliveryLogLineCount(name) {
|
|
185
|
-
return _deliveryLogLineCountByEndpoint.get(name) ?? 0;
|
|
186
|
-
}
|
|
187
|
-
function _setDeliveryLogLineCount(name, n) {
|
|
188
|
-
_deliveryLogLineCountByEndpoint.set(name, Math.max(0, n));
|
|
189
|
-
}
|
|
190
|
-
function _bumpDeliveryLogLineCount(name, delta = 1) {
|
|
191
|
-
_setDeliveryLogLineCount(name, _deliveryLogLineCount(name) + delta);
|
|
192
|
-
}
|
|
193
|
-
function _readDeliveriesFileMerged(name) {
|
|
194
|
-
const p = _deliveriesPath(name);
|
|
195
|
-
const byId = new Map();
|
|
196
|
-
let lineCount = 0;
|
|
197
|
-
if (!existsSync(p)) return { byId, lineCount };
|
|
198
|
-
try {
|
|
199
|
-
const raw = readFileSync(p, "utf8");
|
|
200
|
-
for (const line of raw.split("\n")) {
|
|
201
|
-
if (!line) continue;
|
|
202
|
-
lineCount++;
|
|
203
|
-
try {
|
|
204
|
-
const entry = JSON.parse(line);
|
|
205
|
-
if (!entry?.id) continue;
|
|
206
|
-
byId.set(entry.id, _mergeDeliveryRows(byId.get(entry.id), entry));
|
|
207
|
-
} catch {}
|
|
208
|
-
}
|
|
209
|
-
} catch {}
|
|
210
|
-
return { byId, lineCount };
|
|
211
|
-
}
|
|
212
|
-
function _ingestDeliveriesFileIntoIndex(name) {
|
|
213
|
-
const { byId: merged, lineCount } = _readDeliveriesFileMerged(name);
|
|
214
|
-
const byId = _deliveryIndexFor(name);
|
|
215
|
-
byId.clear();
|
|
216
|
-
for (const [id, row] of merged) byId.set(id, row);
|
|
217
|
-
_pruneDeliveryIndexMap(byId);
|
|
218
|
-
_setDeliveryLogLineCount(name, lineCount);
|
|
219
|
-
// Track the RETAINED (post-prune) distinct count, not the raw file count:
|
|
220
|
-
// a pre-existing oversized log then trips the compaction trigger promptly
|
|
221
|
-
// instead of inflating the threshold until it grows even larger.
|
|
222
|
-
_deliveryKeptCountByEndpoint.set(name, byId.size);
|
|
223
|
-
}
|
|
224
|
-
function _ensureDeliveryIndex(name) {
|
|
225
|
-
if (_deliveryIndexWarmed.has(name)) return;
|
|
226
|
-
_deliveryIndexWarmed.add(name);
|
|
227
|
-
_ingestDeliveriesFileIntoIndex(name);
|
|
228
|
-
}
|
|
229
|
-
function _applyDeliveryEntryToIndex(name, entry) {
|
|
230
|
-
if (!entry?.id) return;
|
|
231
|
-
const byId = _deliveryIndexFor(name);
|
|
232
|
-
byId.set(entry.id, _mergeDeliveryRows(byId.get(entry.id), entry));
|
|
233
|
-
_pruneDeliveryIndexMap(byId);
|
|
234
|
-
}
|
|
235
|
-
function _compactDeliveriesLogIfNeeded(name) {
|
|
236
|
-
// Redundancy-based trigger: compact only when the log holds meaningfully more
|
|
237
|
-
// lines than distinct ids (i.e. there are status-update rows to collapse).
|
|
238
|
-
// The threshold scales with the distinct-id count so an endpoint with many
|
|
239
|
-
// legitimate blocking ids does NOT re-compact on every append (which would
|
|
240
|
-
// re-read the whole log permanently once distinct > DELIVERY_LOG_MAX_LINES).
|
|
241
|
-
const kept = _deliveryKeptCountByEndpoint.get(name) ?? _deliveryIndexFor(name).size;
|
|
242
|
-
const threshold = Math.max(DELIVERY_LOG_MAX_LINES, kept * 2);
|
|
243
|
-
if (_deliveryLogLineCount(name) <= threshold) return;
|
|
244
|
-
const { byId: merged } = _readDeliveriesFileMerged(name);
|
|
245
|
-
const rows = [...merged.values()];
|
|
246
|
-
const keepIds = _retainedDeliveryIds(rows);
|
|
247
|
-
const keep = new Map();
|
|
248
|
-
for (const e of rows) {
|
|
249
|
-
if (keepIds.has(e.id)) keep.set(e.id, e);
|
|
250
|
-
}
|
|
251
|
-
const lines = [...keep.values()]
|
|
252
|
-
.sort((a, b) => String(a.ts || "").localeCompare(String(b.ts || "")))
|
|
253
|
-
.map((e) => JSON.stringify(e) + "\n")
|
|
254
|
-
.join("");
|
|
255
|
-
const p = _deliveriesPath(name);
|
|
256
|
-
const tmp = `${p}.compact-${process.pid}-${Date.now()}.tmp`;
|
|
257
|
-
try {
|
|
258
|
-
writeFileSync(tmp, lines);
|
|
259
|
-
renameSync(tmp, p);
|
|
260
|
-
// Refresh index + counters by RE-READING the post-rename file (not the
|
|
261
|
-
// pre-rename `keep` snapshot). The webhook daemon is the single writer and
|
|
262
|
-
// append+compact run synchronously in one process, so no append can
|
|
263
|
-
// interleave between the fresh read and the rename; re-reading keeps the
|
|
264
|
-
// warmed state exactly matching on-disk content.
|
|
265
|
-
_ingestDeliveriesFileIntoIndex(name);
|
|
266
|
-
} catch (err) {
|
|
267
|
-
logWebhook(`${name}: deliveries compact failed: ${err?.message ?? err}`);
|
|
268
|
-
try {
|
|
269
|
-
if (existsSync(tmp)) unlinkSync(tmp);
|
|
270
|
-
} catch {}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
function appendDelivery(name, entry) {
|
|
274
|
-
try {
|
|
275
|
-
const dir = join(WEBHOOKS_DIR, name);
|
|
276
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
277
|
-
const full = { ts: new Date().toISOString(), ...entry };
|
|
278
|
-
const line = JSON.stringify(full) + "\n";
|
|
279
|
-
void appendFile(_deliveriesPath(name), line).catch((err) => {
|
|
280
|
-
logWebhook(`${name}: deliveries append failed: ${err?.message ?? err}`);
|
|
281
|
-
});
|
|
282
|
-
const wasWarmed = _deliveryIndexWarmed.has(name);
|
|
283
|
-
_ensureDeliveryIndex(name);
|
|
284
|
-
if (wasWarmed) _bumpDeliveryLogLineCount(name, 1);
|
|
285
|
-
_applyDeliveryEntryToIndex(name, full);
|
|
286
|
-
_compactDeliveriesLogIfNeeded(name);
|
|
287
|
-
return true;
|
|
288
|
-
} catch (err) {
|
|
289
|
-
logWebhook(`${name}: deliveries append failed: ${err?.message ?? err}`);
|
|
290
|
-
return false;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
// Dedup gate against a still-active claim or a successful prior delivery.
|
|
294
|
-
// Only rows with status "received" (non-terminal claim) or "done"
|
|
295
|
-
// (successful delivery) block a retry; terminal "failed" rows (and any
|
|
296
|
-
// other unknown/legacy terminal status) are NOT considered duplicates so
|
|
297
|
-
// a sender can legitimately
|
|
298
|
-
// redeliver the same id after a recoverable failure. Without this
|
|
299
|
-
// scoping, every prior row would permanently dedup the id and stop
|
|
300
|
-
// legit redelivery.
|
|
301
|
-
function deliveryExists(name, id) {
|
|
302
|
-
// "processing" must also dedup: a delegate dispatch in flight (up to
|
|
303
|
-
// DISPATCH_TIMEOUT_MS = 10 min) would otherwise be duplicated by a
|
|
304
|
-
// retried delivery of the same id while the first handler is still
|
|
305
|
-
// running. Block on any non-terminal status.
|
|
306
|
-
_ensureDeliveryIndex(name);
|
|
307
|
-
const entry = _deliveryIndexFor(name).get(id);
|
|
308
|
-
return Boolean(entry && _isBlockingDeliveryStatus(entry.status));
|
|
309
|
-
}
|
|
310
7
|
function extractDeliveryId(headers) {
|
|
311
8
|
return headers["x-github-delivery"]
|
|
312
9
|
|| headers["x-delivery-id"]
|
|
313
10
|
|| headers["x-request-id"]
|
|
314
11
|
|| null;
|
|
315
12
|
}
|
|
13
|
+
|
|
316
14
|
function buildHeadersSummary(headers) {
|
|
317
15
|
const summary = {};
|
|
318
16
|
if (headers["x-github-event"]) summary.event_type = headers["x-github-event"];
|
|
@@ -325,13 +23,4 @@ function buildHeadersSummary(headers) {
|
|
|
325
23
|
return summary;
|
|
326
24
|
}
|
|
327
25
|
|
|
328
|
-
export {
|
|
329
|
-
WEBHOOKS_DIR,
|
|
330
|
-
_readEndpointSecret,
|
|
331
|
-
_closeEndpointWatcher,
|
|
332
|
-
loadEndpointConfig,
|
|
333
|
-
appendDelivery,
|
|
334
|
-
deliveryExists,
|
|
335
|
-
extractDeliveryId,
|
|
336
|
-
buildHeadersSummary,
|
|
337
|
-
};
|
|
26
|
+
export { extractDeliveryId, buildHeadersSummary };
|