@autono/pinbox-core 0.1.0

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 (48) hide show
  1. package/README.md +17 -0
  2. package/dist/auth/verify.d.ts +15 -0
  3. package/dist/auth/verify.js +2 -0
  4. package/dist/connectors/github.d.ts +5 -0
  5. package/dist/connectors/github.js +49 -0
  6. package/dist/connectors/index.d.ts +37 -0
  7. package/dist/connectors/index.js +103 -0
  8. package/dist/context-BHcEpzVb.js +30 -0
  9. package/dist/delivery/openclaw.d.ts +10 -0
  10. package/dist/delivery/openclaw.js +50 -0
  11. package/dist/delivery/resume.d.ts +23 -0
  12. package/dist/delivery/resume.js +102 -0
  13. package/dist/delivery/router.d.ts +2 -0
  14. package/dist/delivery/router.js +262 -0
  15. package/dist/delivery/webhook.d.ts +9 -0
  16. package/dist/delivery/webhook.js +52 -0
  17. package/dist/do.d.ts +67 -0
  18. package/dist/do.js +713 -0
  19. package/dist/hub-QYz6OqYQ.js +328 -0
  20. package/dist/hub-server.d.ts +44 -0
  21. package/dist/hub-server.js +251 -0
  22. package/dist/hub.d.ts +125 -0
  23. package/dist/hub.js +2 -0
  24. package/dist/markdown.d.ts +14 -0
  25. package/dist/markdown.js +87 -0
  26. package/dist/payload-m1DbRWDD.d.ts +6 -0
  27. package/dist/poll-BrcAuaAz.js +252 -0
  28. package/dist/proc-BMp_pbPS.js +42 -0
  29. package/dist/router-D3dDjIaD.d.ts +62 -0
  30. package/dist/schema-BOTmn5SM.d.ts +297 -0
  31. package/dist/schema.d.ts +2 -0
  32. package/dist/schema.js +121 -0
  33. package/dist/schema.json +403 -0
  34. package/dist/sessions-CJdMBH3C.d.ts +47 -0
  35. package/dist/sessions-DrCVTMfI.js +129 -0
  36. package/dist/sessions.d.ts +2 -0
  37. package/dist/sessions.js +2 -0
  38. package/dist/store-DHbwWu93.d.ts +105 -0
  39. package/dist/store-DM8MjB8M.js +479 -0
  40. package/dist/store.d.ts +2 -0
  41. package/dist/store.js +3 -0
  42. package/dist/types-BLNQb7MH.d.ts +28 -0
  43. package/dist/verify-BDt9d9Np.js +55 -0
  44. package/dist/ws-protocol.d.ts +61 -0
  45. package/dist/ws-protocol.js +51 -0
  46. package/dist/ws.d.ts +7 -0
  47. package/dist/ws.js +1 -0
  48. package/package.json +104 -0
package/dist/hub.d.ts ADDED
@@ -0,0 +1,125 @@
1
+ import { t as Connector } from "./types-BLNQb7MH.js";
2
+ import { c as PinStore } from "./store-DHbwWu93.js";
3
+ //#region src/hub.d.ts
4
+ type Identity = {
5
+ userId: string;
6
+ tenantId?: string;
7
+ name?: string;
8
+ email?: string;
9
+ };
10
+ type VerifyFn = (req: Request) => Promise<Identity | null>;
11
+ type HubOptions = {
12
+ store: PinStore;
13
+ token: string;
14
+ enrichEnv?: () => {
15
+ branch?: string;
16
+ commit?: string;
17
+ };
18
+ verify?: VerifyFn;
19
+ connectors?: Connector[];
20
+ };
21
+ type RouteModule = (req: Request, url: URL, opts: HubOptions) => Promise<Response | null>;
22
+ type HubErrorCode = "E_INTERNAL" | "E_INVALID_INPUT" | "E_NOT_FOUND" | "E_CONFLICT" | "E_SESSION_GONE" | "E_DELIVERY" | "E_WS_PROTOCOL" | "E_ATTACHMENT" | "E_CONNECTOR" | "E_AUTH";
23
+ declare function createHubHandler(opts: HubOptions): (req: Request) => Promise<Response>;
24
+ declare function match(path: string, pattern: RegExp): string | null;
25
+ declare function mustGetPin(store: PinStore, id: string): {
26
+ text: string;
27
+ kind: "move" | "note";
28
+ target?: {
29
+ url?: string | undefined;
30
+ selector?: string | undefined;
31
+ tag?: string | undefined;
32
+ rect?: {
33
+ x: number;
34
+ y: number;
35
+ width: number;
36
+ height: number;
37
+ } | undefined;
38
+ fixed?: boolean | undefined;
39
+ anchor?: string | undefined;
40
+ source?: {
41
+ file: string;
42
+ line?: number | undefined;
43
+ via: "framework" | "none" | "plugin";
44
+ } | undefined;
45
+ context?: {
46
+ classes?: string[] | undefined;
47
+ styles?: Record<string, string> | undefined;
48
+ aria?: Record<string, string> | undefined;
49
+ nearbyText?: string | undefined;
50
+ selectedText?: string | undefined;
51
+ } | undefined;
52
+ } | undefined;
53
+ move?: {
54
+ from: {
55
+ x: number;
56
+ y: number;
57
+ width: number;
58
+ height: number;
59
+ };
60
+ to: {
61
+ x: number;
62
+ y: number;
63
+ width: number;
64
+ height: number;
65
+ };
66
+ } | undefined;
67
+ env?: {
68
+ viewport?: {
69
+ w: number;
70
+ h: number;
71
+ dpr: number;
72
+ } | undefined;
73
+ browser?: string | undefined;
74
+ os?: string | undefined;
75
+ colorScheme?: "dark" | "light" | undefined;
76
+ branch?: string | undefined;
77
+ commit?: string | undefined;
78
+ } | undefined;
79
+ author: {
80
+ userId: string;
81
+ name?: string | undefined;
82
+ email?: string | undefined;
83
+ };
84
+ agentSession?: {
85
+ agent: string;
86
+ key: string;
87
+ cwd?: string | undefined;
88
+ } | undefined;
89
+ attachments?: {
90
+ id: string;
91
+ kind: "file" | "screenshot";
92
+ path?: string | undefined;
93
+ url?: string | undefined;
94
+ contentType?: string | undefined;
95
+ width?: number | undefined;
96
+ height?: number | undefined;
97
+ }[] | undefined;
98
+ id: string;
99
+ schemaVersion: 1;
100
+ status: "open" | "resolved";
101
+ createdAt: string;
102
+ resolution?: {
103
+ by: "agent" | "human";
104
+ note?: string | undefined;
105
+ commit?: string | undefined;
106
+ at: string;
107
+ } | undefined;
108
+ verification?: {
109
+ outcome: "accepted" | "reopened";
110
+ at: string;
111
+ } | undefined;
112
+ links?: {
113
+ connector: string;
114
+ ref: string;
115
+ url: string;
116
+ }[] | undefined;
117
+ };
118
+ declare function readJson(req: Request): Promise<unknown>;
119
+ declare class BodyNotJsonError extends Error {}
120
+ declare function ok(status: number, data: unknown): Response;
121
+ declare function err(status: number, code: HubErrorCode, message: string, extra?: {
122
+ hint?: string;
123
+ }): Response;
124
+ //#endregion
125
+ export { BodyNotJsonError, type HubErrorCode, HubOptions, Identity, RouteModule, VerifyFn, createHubHandler, err, match, mustGetPin, ok, readJson };
package/dist/hub.js ADDED
@@ -0,0 +1,2 @@
1
+ import { a as mustGetPin, i as match, n as createHubHandler, o as ok, r as err, s as readJson, t as BodyNotJsonError } from "./hub-QYz6OqYQ.js";
2
+ export { BodyNotJsonError, createHubHandler, err, match, mustGetPin, ok, readJson };
@@ -0,0 +1,14 @@
1
+ import { a as Pin } from "./schema-BOTmn5SM.js";
2
+ //#region src/markdown.d.ts
3
+ type DetailLevel = "compact" | "standard" | "forensic";
4
+ declare function pinsToMarkdown(pins: Pin[], level: DetailLevel): string;
5
+ /**
6
+ * The "where" in the headline, most specific first: a browser pin has a selector, a
7
+ * terminal `--file` pin has a source anchor, a `--url` pin has a URL. A pin with none
8
+ * of them is still a valid pin — the headline just drops the locus and its separator.
9
+ * Exported so every one-line pin summary (delivery/context.ts, the CLI's list) names
10
+ * a pin the same way.
11
+ */
12
+ declare function pinLocus(pin: Pin): string | undefined;
13
+ //#endregion
14
+ export { DetailLevel, pinLocus, pinsToMarkdown };
@@ -0,0 +1,87 @@
1
+ //#region src/markdown.ts
2
+ function pinsToMarkdown(pins, level) {
3
+ return pins.map((pin) => pinToMarkdown(pin, level).join("\n")).join("\n");
4
+ }
5
+ function pinToMarkdown(pin, level) {
6
+ const lines = [headline(pin)];
7
+ if (level === "compact") return lines;
8
+ lines.push(...standardFacts(pin));
9
+ if (level === "standard") return lines;
10
+ lines.push(...forensicBlock(pin));
11
+ return lines;
12
+ }
13
+ /** `- [open] <where> — <text> (<id>)`, minus the locus when the pin names no place. */
14
+ function headline(pin) {
15
+ const where = pinLocus(pin);
16
+ const head = where === void 0 ? "" : `${where} — `;
17
+ return `- [${pin.status}] ${head}${pin.text} (${pin.id})`;
18
+ }
19
+ /** The indented context lines, each present only when the fact behind it exists. */
20
+ function standardFacts(pin) {
21
+ const target = pin.target;
22
+ const lines = [];
23
+ if (target?.url !== void 0) lines.push(` - url: ${target.url}`);
24
+ const source = sourceRef(pin);
25
+ if (source !== void 0) lines.push(` - source: ${source}`);
26
+ const rect = target?.rect;
27
+ if (rect) lines.push(` - rect: ${rect.x},${rect.y} ${rect.width}x${rect.height}`);
28
+ const nearby = target?.context?.nearbyText;
29
+ if (nearby !== void 0) lines.push(` - nearby: "${nearby}"`);
30
+ return lines;
31
+ }
32
+ /**
33
+ * The fenced JSON record: target.context + env. A terminal pin with no git stamp and
34
+ * no context has nothing to record — it gets no fence, because an empty one is noise
35
+ * the agent still pays tokens for.
36
+ */
37
+ function forensicBlock(pin) {
38
+ const forensic = {};
39
+ if (pin.target?.context) forensic["context"] = pin.target.context;
40
+ if (pin.env && Object.keys(pin.env).length > 0) forensic["env"] = pin.env;
41
+ if (Object.keys(forensic).length === 0) return [];
42
+ return [
43
+ "",
44
+ " ```json",
45
+ ...renderJson(forensic, 0).split("\n").map((line) => ` ${line}`),
46
+ " ```"
47
+ ];
48
+ }
49
+ /**
50
+ * The "where" in the headline, most specific first: a browser pin has a selector, a
51
+ * terminal `--file` pin has a source anchor, a `--url` pin has a URL. A pin with none
52
+ * of them is still a valid pin — the headline just drops the locus and its separator.
53
+ * Exported so every one-line pin summary (delivery/context.ts, the CLI's list) names
54
+ * a pin the same way.
55
+ */
56
+ function pinLocus(pin) {
57
+ return pin.target?.selector ?? sourceRef(pin) ?? pin.target?.url;
58
+ }
59
+ function sourceRef(pin) {
60
+ const source = pin.target?.source;
61
+ if (source === void 0) return void 0;
62
+ return source.line === void 0 ? source.file : `${source.file}:${source.line}`;
63
+ }
64
+ const INLINE_WIDTH = 80;
65
+ function renderJson(value, indent, cursor = indent) {
66
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
67
+ const inline = inlineJson(value);
68
+ if (cursor + inline.length <= INLINE_WIDTH) return inline;
69
+ const pad = " ".repeat(indent + 2);
70
+ if (Array.isArray(value)) return `[\n${value.map((item) => `${pad}${renderJson(item, indent + 2)}`).join(",\n")}\n${" ".repeat(indent)}]`;
71
+ return `{\n${jsonEntries(value).map(([key, item]) => {
72
+ const name = JSON.stringify(key);
73
+ return `${pad}${name}: ${renderJson(item, indent + 2, indent + 2 + name.length + 2)}`;
74
+ }).join(",\n")}\n${" ".repeat(indent)}}`;
75
+ }
76
+ function inlineJson(value) {
77
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
78
+ if (Array.isArray(value)) return `[${value.map(inlineJson).join(", ")}]`;
79
+ const entries = jsonEntries(value);
80
+ if (entries.length === 0) return "{}";
81
+ return `{ ${entries.map(([key, item]) => `${JSON.stringify(key)}: ${inlineJson(item)}`).join(", ")} }`;
82
+ }
83
+ function jsonEntries(value) {
84
+ return Object.entries(value).filter(([, item]) => item !== void 0);
85
+ }
86
+ //#endregion
87
+ export { pinLocus, pinsToMarkdown };
@@ -0,0 +1,6 @@
1
+ import { a as Pin } from "./schema-BOTmn5SM.js";
2
+ import "./store-DHbwWu93.js";
3
+ //#region src/delivery/payload.d.ts
4
+ type GetPin = (id: string) => Pin | null;
5
+ //#endregion
6
+ export { GetPin as t };
@@ -0,0 +1,252 @@
1
+ //#region src/connectors/mirror.ts
2
+ function createConnectorEvents(store, pinId) {
3
+ return {
4
+ async onRemoteComment(_link, comment) {
5
+ store.addThreadMessage(pinId, "mirror", comment.text, { origin: comment.origin });
6
+ },
7
+ async onRemoteStatus(_link, status) {
8
+ const pin = store.getPin(pinId);
9
+ if (pin === null) return;
10
+ if (status === "closed") {
11
+ if (pin.status !== "resolved") store.resolvePin(pinId, "agent");
12
+ return;
13
+ }
14
+ if (pin.status !== "resolved") return;
15
+ store.verifyPin(pinId, "reopened");
16
+ }
17
+ };
18
+ }
19
+ /**
20
+ * The §7 skip rule, tested exhaustively: messages newer than the cursor that did NOT come
21
+ * from this connector. Human/agent messages flow out; mirrors flow out only cross-connector
22
+ * (a slack-origin mirror is outbound for github); origin-less mirrors (local notices) and
23
+ * anything tagged `<connector>:` never echo back.
24
+ */
25
+ function outboundCandidates(thread, connector, since) {
26
+ const prefix = `${connector}:`;
27
+ return thread.filter((message) => {
28
+ if (since !== null && message.at <= since) return false;
29
+ if ((message.origin ?? "").startsWith(prefix)) return false;
30
+ if (message.role === "mirror") return message.origin !== void 0;
31
+ return true;
32
+ });
33
+ }
34
+ //#endregion
35
+ //#region src/connectors/inbound.ts
36
+ /** The §7 sinks plus the replay filter and the transition probe the cursor needs. */
37
+ function inboundEvents(store, pinId, connector, statusPushed) {
38
+ const sinks = createConnectorEvents(store, pinId);
39
+ const unmatched = mirroredCounts(store.getThread(pinId), connector);
40
+ let transitionedAt = null;
41
+ return {
42
+ events: {
43
+ async onRemoteComment(link, comment) {
44
+ const key = mirrorKey(comment.origin, comment.text);
45
+ const already = unmatched.get(key) ?? 0;
46
+ if (already > 0) {
47
+ unmatched.set(key, already - 1);
48
+ return;
49
+ }
50
+ await sinks.onRemoteComment(link, comment);
51
+ },
52
+ async onRemoteStatus(link, status) {
53
+ if (statusPushed) return;
54
+ const before = store.getPin(pinId)?.status;
55
+ await sinks.onRemoteStatus(link, status);
56
+ transitionedAt = transitionStamp(before, store.getPin(pinId)) ?? transitionedAt;
57
+ }
58
+ },
59
+ transitionedAt: () => transitionedAt
60
+ };
61
+ }
62
+ /** How many times each (origin, text) already sits in the thread as this connector's mirror. */
63
+ function mirroredCounts(thread, connector) {
64
+ const prefix = `${connector}:`;
65
+ const counts = /* @__PURE__ */ new Map();
66
+ for (const message of thread) {
67
+ const origin = message.origin;
68
+ if (message.role !== "mirror" || origin === void 0 || !origin.startsWith(prefix)) continue;
69
+ const key = mirrorKey(origin, message.text);
70
+ counts.set(key, (counts.get(key) ?? 0) + 1);
71
+ }
72
+ return counts;
73
+ }
74
+ /** NUL separator: it cannot occur in an origin tag, so the two fields never blur. */
75
+ function mirrorKey(origin, text) {
76
+ return `${origin}\u0000${text}`;
77
+ }
78
+ /** The timestamp the sinks stamped a status transition with, or null if none happened. */
79
+ function transitionStamp(before, after) {
80
+ if (before === void 0 || after === null || after.status === before) return null;
81
+ return (after.status === "resolved" ? after.resolution?.at : after.verification?.at) ?? (/* @__PURE__ */ new Date()).toISOString();
82
+ }
83
+ //#endregion
84
+ //#region src/connectors/outbound.ts
85
+ /**
86
+ * Everything owed to the remote for this link, oldest first. The status transition is an
87
+ * op like any other precisely so the watermark stays a single ordered cursor: if it were
88
+ * flushed out of band, a watermark past its timestamp would drop it on the next drain.
89
+ * Sort is stable, so a transition stamped in the same millisecond as a comment still goes
90
+ * out after it (comments, then the close).
91
+ */
92
+ function outboundPlan(thread, connector, pin, since) {
93
+ const ops = outboundCandidates(thread, connector, since).map((message) => ({
94
+ at: message.at,
95
+ kind: "comment",
96
+ message
97
+ }));
98
+ const status = pendingStatus(pin, since);
99
+ if (status !== null) ops.push({
100
+ at: status.at,
101
+ kind: "status",
102
+ status: status.status
103
+ });
104
+ return ops.sort((a, b) => a.at < b.at ? -1 : a.at > b.at ? 1 : 0);
105
+ }
106
+ /** The local status transition newer than the cursor, or null — §7's outbound half (A2). */
107
+ function pendingStatus(pin, since) {
108
+ const newerThanCursor = (at) => since === null || at > since;
109
+ const resolution = pin.resolution;
110
+ if (pin.status === "resolved" && resolution !== void 0 && newerThanCursor(resolution.at)) return {
111
+ status: "closed",
112
+ at: resolution.at
113
+ };
114
+ const verification = pin.verification;
115
+ if (pin.status === "open" && verification?.outcome === "reopened" && newerThanCursor(verification.at)) return {
116
+ status: "open",
117
+ at: verification.at
118
+ };
119
+ return null;
120
+ }
121
+ /** Execute the plan in order, stopping at the first throw. Never rejects — the caller
122
+ * banks the watermark before it re-raises, so progress is never lost to an exception. */
123
+ async function flushOutbound(connector, link, ops) {
124
+ const posted = /* @__PURE__ */ new Set();
125
+ let statusPushed = false;
126
+ let done = 0;
127
+ for (const op of ops) {
128
+ try {
129
+ if (op.kind === "comment") {
130
+ await connector.postComment(link, op.message);
131
+ posted.add(op.message.id);
132
+ } else {
133
+ await connector.setRemoteStatus(link, op.status);
134
+ statusPushed = true;
135
+ }
136
+ } catch (error) {
137
+ return {
138
+ posted,
139
+ statusPushed,
140
+ watermark: resumeWatermark(ops, done),
141
+ error
142
+ };
143
+ }
144
+ done += 1;
145
+ }
146
+ return {
147
+ posted,
148
+ statusPushed,
149
+ watermark: ops.at(-1)?.at ?? null,
150
+ error: null
151
+ };
152
+ }
153
+ /**
154
+ * How far a partial flush may move the cursor: strictly below the first unfinished op's
155
+ * stamp. Ops sharing a millisecond cannot be split by a timestamp cursor, so a tie group
156
+ * containing unfinished work holds the cursor below the whole group — the landed members
157
+ * are re-sent on the next drain (one duplicate) rather than the unfinished ones being
158
+ * silently dropped. Distinct stamps, the ordinary case, resume exactly.
159
+ */
160
+ function resumeWatermark(ops, done) {
161
+ const pending = ops[done]?.at;
162
+ if (pending === void 0) return ops.at(-1)?.at ?? null;
163
+ for (let i = done - 1; i >= 0; i -= 1) {
164
+ const at = ops[i]?.at;
165
+ if (at !== void 0 && at < pending) return at;
166
+ }
167
+ return null;
168
+ }
169
+ //#endregion
170
+ //#region src/connectors/poll.ts
171
+ const POLL_OPEN_MS = 6e4;
172
+ const POLL_RESOLVED_MS = 6e5;
173
+ /** Per-pin failures are caught + logged to stderr; the drain never throws (§6 dispatch discipline). */
174
+ async function drainConnectorPolls(store, connectors, now) {
175
+ const at = now ?? (/* @__PURE__ */ new Date()).toISOString();
176
+ for (const pin of store.pinsDueBefore(at)) try {
177
+ await drainPin(store, connectors, pin, at);
178
+ } catch (cause) {
179
+ console.error(`pinbox: connector poll failed for ${pin.id}:`, cause);
180
+ }
181
+ }
182
+ async function drainPin(store, connectors, pin, at) {
183
+ let drained = 0;
184
+ for (const row of store.links.all().filter((r) => r.pinId === pin.id)) {
185
+ const connector = connectors.find((c) => c.name === row.link.connector);
186
+ if (connector === void 0) continue;
187
+ await syncLink(store, connector, pin, row, at);
188
+ drained += 1;
189
+ }
190
+ if (drained === 0) return;
191
+ rearm(store, pin, at);
192
+ }
193
+ /** One link: drain it, bank however far it got, then re-raise any failure. Banking BEFORE
194
+ * re-raising is the whole point — a partial flush that already reached the remote must
195
+ * never be replayed, or the user sees duplicate comments on their issue. */
196
+ async function syncLink(store, connector, pin, row, at) {
197
+ const result = await drainLink(store, connector, pin, row.link, row.lastSyncedAt, at);
198
+ if (advances(result.syncedAt, row.lastSyncedAt)) store.links.markSynced(pin.id, row.link, result.syncedAt);
199
+ if (result.error !== null) throw result.error;
200
+ }
201
+ /** Re-arm off the post-sync status (an inbound close moves the pin to the slow cadence). */
202
+ function rearm(store, pin, at) {
203
+ const cadence = (store.getPin(pin.id)?.status ?? pin.status) === "open" ? POLL_OPEN_MS : POLL_RESOLVED_MS;
204
+ store.setDueAt(pin.id, new Date(Date.parse(at) + cadence).toISOString());
205
+ }
206
+ async function drainLink(store, connector, pin, link, lastSyncedAt, at) {
207
+ const flush = await flushOutbound(connector, link, outboundPlan(store.getThread(pin.id), connector.name, pin, lastSyncedAt));
208
+ if (flush.error !== null) return {
209
+ syncedAt: flush.watermark,
210
+ error: flush.error
211
+ };
212
+ const inbound = inboundEvents(store, pin.id, connector.name, flush.statusPushed);
213
+ try {
214
+ await connector.sync(link, inbound.events);
215
+ } catch (error) {
216
+ return {
217
+ syncedAt: flush.watermark,
218
+ error
219
+ };
220
+ }
221
+ const syncedAt = latest(at, flush.watermark, inbound.transitionedAt()) ?? at;
222
+ const second = await flushOutbound(connector, link, lateComments(store, connector, pin, lastSyncedAt, syncedAt, flush.posted));
223
+ if (second.error !== null) return {
224
+ syncedAt: latest(flush.watermark, second.watermark),
225
+ error: second.error
226
+ };
227
+ return {
228
+ syncedAt,
229
+ error: null
230
+ };
231
+ }
232
+ /** Messages the cursor is about to pass that this drain has not posted yet. */
233
+ function lateComments(store, connector, pin, lastSyncedAt, syncedAt, posted) {
234
+ return outboundCandidates(store.getThread(pin.id), connector.name, lastSyncedAt).filter((message) => message.at <= syncedAt && !posted.has(message.id)).map((message) => ({
235
+ at: message.at,
236
+ kind: "comment",
237
+ message
238
+ }));
239
+ }
240
+ /** Newest of the given stamps, ignoring nulls. */
241
+ function latest(...stamps) {
242
+ let newest = null;
243
+ for (const stamp of stamps) if (stamp !== null && (newest === null || stamp > newest)) newest = stamp;
244
+ return newest;
245
+ }
246
+ /** A cursor only ever moves forward — a banked partial watermark must not regress it. */
247
+ function advances(next, current) {
248
+ if (next === null) return false;
249
+ return current === null || next > current;
250
+ }
251
+ //#endregion
252
+ export { outboundCandidates as a, createConnectorEvents as i, POLL_RESOLVED_MS as n, drainConnectorPolls as r, POLL_OPEN_MS as t };
@@ -0,0 +1,42 @@
1
+ import { PinSchema, ThreadMessageSchema } from "./schema.js";
2
+ import { n as buildReplyPrompt, t as buildInjectionContext } from "./context-BHcEpzVb.js";
3
+ //#region src/delivery/payload.ts
4
+ /**
5
+ * Injection context for pin.created; the fenced reply prompt for thread.message.
6
+ * A thread.message payload carries only pinId, but buildReplyPrompt needs the Pin —
7
+ * the serve boot passes (id) => store.getPin(id) as getPin; without it, reply
8
+ * deliveries fail (retryable) rather than ship a pinless payload.
9
+ */
10
+ function payloadForEvent(event, adapter, getPin) {
11
+ if (event.type === "pin.created") return buildInjectionContext([PinSchema.parse(event.payload)]);
12
+ if (event.type === "thread.message") {
13
+ const message = ThreadMessageSchema.parse(event.payload);
14
+ const pin = getPin?.(message.pinId) ?? null;
15
+ if (pin === null) throw new Error(`pin ${message.pinId} not found for ${adapter} payload (adapter needs opts.getPin)`);
16
+ return buildReplyPrompt(pin, message);
17
+ }
18
+ throw new Error(`${adapter} adapter cannot deliver ${event.type}`);
19
+ }
20
+ //#endregion
21
+ //#region src/delivery/proc.ts
22
+ /**
23
+ * Bun.which against the CURRENT process.env.PATH — Bun caches the startup environ for
24
+ * bare-name spawn resolution and default child env, so both are passed explicitly
25
+ * (measured: a runtime PATH prepend was ignored by Bun.which and Bun.spawn alike).
26
+ */
27
+ function resolveBinary(binary) {
28
+ return Bun.which(binary, { PATH: process.env["PATH"] ?? "" });
29
+ }
30
+ /**
31
+ * Await close, never exit (deep-dive §1.8b): drain both pipes to EOF, THEN await the
32
+ * exit code — a probe lost 65,538 of 200,000 bytes awaiting exit alone.
33
+ */
34
+ async function drainToExit(proc) {
35
+ const [, stderrText] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text()]);
36
+ return {
37
+ code: await proc.exited,
38
+ stderr: stderrText.trim()
39
+ };
40
+ }
41
+ //#endregion
42
+ export { resolveBinary as n, payloadForEvent as r, drainToExit as t };
@@ -0,0 +1,62 @@
1
+ import { a as Pin, m as ThreadMessage } from "./schema-BOTmn5SM.js";
2
+ import { t as Session } from "./sessions-CJdMBH3C.js";
3
+ import { c as PinStore, l as StoredEvent } from "./store-DHbwWu93.js";
4
+ //#region src/delivery/context.d.ts
5
+ /**
6
+ * The per-turn injection context: every open pin at the `compact` dial,
7
+ * between a data-quoting header and a one-line skill pointer.
8
+ */
9
+ declare function buildInjectionContext(pins: Pin[]): string;
10
+ /** The resume/openclaw payload for one thread reply: the message text fenced as data. */
11
+ declare function buildReplyPrompt(pin: Pin, message: ThreadMessage): string;
12
+ //#endregion
13
+ //#region src/delivery/hooks.d.ts
14
+ /** Agents whose hook systems can register + pull (research §1: shared hooks schema). */
15
+ declare const HOOK_CAPABLE_AGENTS: ReadonlySet<string>;
16
+ declare function createHooksAdapter(): DeliveryAdapter;
17
+ //#endregion
18
+ //#region src/delivery/router.d.ts
19
+ interface DeliveryAdapter {
20
+ readonly name: string;
21
+ matches(session: Session): boolean | Promise<boolean>;
22
+ deliver(event: StoredEvent, session: Session): Promise<void>;
23
+ }
24
+ declare const DEFAULT_HOOKS_ESCALATE_MS = 600000;
25
+ /** Escalation window for pull rows: PINBOX_HOOKS_ESCALATE_MS, default 10 min. */
26
+ declare function hooksEscalateMs(): number;
27
+ declare class DeliveryRouter {
28
+ private readonly store;
29
+ private readonly adapters;
30
+ private chain;
31
+ constructor(opts: {
32
+ store: PinStore;
33
+ adapters: DeliveryAdapter[];
34
+ });
35
+ /** THE single entry point; never throws — failures land in the queue. */
36
+ dispatch(event: StoredEvent): Promise<void>;
37
+ /**
38
+ * Runs on hub wake + a coarse unref'd interval: (a) boot/cursor reconciliation,
39
+ * (b) retry/escalate due pending rows, (c) assign unassigned rows to the active
40
+ * session. Never rejects.
41
+ */
42
+ drainDue(now?: string): Promise<void>;
43
+ private enqueueWork;
44
+ private dispatchOne;
45
+ /**
46
+ * Deliverable events are pin.created and thread.message with role human|mirror
47
+ * (rule 3: agent-authored events are never delivered back). Everything else —
48
+ * pin.resolved, and later event types — is skipped-by-design.
49
+ */
50
+ private route;
51
+ private bindTarget;
52
+ private attemptNew;
53
+ private drainNow;
54
+ private retryRow;
55
+ private claimRow;
56
+ private selectAdapter;
57
+ private rotatedAfter;
58
+ private recordFailure;
59
+ private eventOf;
60
+ }
61
+ //#endregion
62
+ export { HOOK_CAPABLE_AGENTS as a, buildReplyPrompt as c, hooksEscalateMs as i, DeliveryAdapter as n, createHooksAdapter as o, DeliveryRouter as r, buildInjectionContext as s, DEFAULT_HOOKS_ESCALATE_MS as t };