@autono/pinbox-core 0.1.0 → 0.8.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.
@@ -1,4 +1,4 @@
1
- import { r as ConnectorTransport, t as Connector } from "../types-BLNQb7MH.js";
1
+ import { r as ConnectorTransport, t as Connector } from "../types-Bu7YuW6a.js";
2
2
  //#region src/connectors/github.d.ts
3
3
  declare function createGithubConnector(transport: ConnectorTransport): Connector;
4
4
  //#endregion
@@ -1,6 +1,6 @@
1
- import { m as ThreadMessage } from "../schema-BOTmn5SM.js";
2
- import { a as RemoteStatus, i as RemoteComment, n as ConnectorEvents, r as ConnectorTransport, t as Connector } from "../types-BLNQb7MH.js";
3
- import { c as PinStore } from "../store-DHbwWu93.js";
1
+ import { g as ThreadMessage } from "../schema-HSZFobyd.js";
2
+ import { a as RemoteStatus, i as RemoteComment, n as ConnectorEvents, r as ConnectorTransport, t as Connector } from "../types-Bu7YuW6a.js";
3
+ import { c as PinStore } from "../store-DPp8xMdp.js";
4
4
  //#region src/connectors/mirror.d.ts
5
5
  declare function createConnectorEvents(store: PinStore, pinId: string): ConnectorEvents;
6
6
  /**
@@ -1,5 +1,5 @@
1
- import { t as GetPin } from "../payload-m1DbRWDD.js";
2
- import { n as DeliveryAdapter } from "../router-D3dDjIaD.js";
1
+ import { t as GetPin } from "../payload-BLDLdUIh.js";
2
+ import { n as DeliveryAdapter } from "../router-DBAEQNfs.js";
3
3
  //#region src/delivery/openclaw.d.ts
4
4
  declare function createOpenclawAdapter(opts?: {
5
5
  command?: string[];
@@ -1,5 +1,5 @@
1
- import { t as GetPin } from "../payload-m1DbRWDD.js";
2
- import { n as DeliveryAdapter } from "../router-D3dDjIaD.js";
1
+ import { t as GetPin } from "../payload-BLDLdUIh.js";
2
+ import { n as DeliveryAdapter } from "../router-DBAEQNfs.js";
3
3
  //#region src/delivery/resume.d.ts
4
4
  type ResumeCommand = (key: string, prompt: string) => string[];
5
5
  declare const RESUME_COMMANDS: Record<string, ResumeCommand>;
@@ -1,2 +1,2 @@
1
- import { a as HOOK_CAPABLE_AGENTS, c as buildReplyPrompt, i as hooksEscalateMs, n as DeliveryAdapter, o as createHooksAdapter, r as DeliveryRouter, s as buildInjectionContext, t as DEFAULT_HOOKS_ESCALATE_MS } from "../router-D3dDjIaD.js";
1
+ import { a as HOOK_CAPABLE_AGENTS, c as buildReplyPrompt, i as hooksEscalateMs, n as DeliveryAdapter, o as createHooksAdapter, r as DeliveryRouter, s as buildInjectionContext, t as DEFAULT_HOOKS_ESCALATE_MS } from "../router-DBAEQNfs.js";
2
2
  export { DEFAULT_HOOKS_ESCALATE_MS, DeliveryAdapter, DeliveryRouter, HOOK_CAPABLE_AGENTS, buildInjectionContext, buildReplyPrompt, createHooksAdapter, hooksEscalateMs };
@@ -1,262 +1,3 @@
1
- import { PinSchema, ThreadMessageSchema } from "../schema.js";
2
1
  import { n as buildReplyPrompt, t as buildInjectionContext } from "../context-BHcEpzVb.js";
3
- //#region src/delivery/hooks.ts
4
- /** Agents whose hook systems can register + pull (research §1: shared hooks schema). */
5
- const HOOK_CAPABLE_AGENTS = /* @__PURE__ */ new Set([
6
- "claude",
7
- "codex",
8
- "hermes"
9
- ]);
10
- function createHooksAdapter() {
11
- return {
12
- name: "hooks",
13
- matches(session) {
14
- return session.endedAt === void 0 && HOOK_CAPABLE_AGENTS.has(session.agent);
15
- },
16
- async deliver() {}
17
- };
18
- }
19
- //#endregion
20
- //#region src/delivery/router.ts
21
- const PULL_ADAPTERS = /* @__PURE__ */ new Set(["hooks"]);
22
- const NO_ADAPTER = "none";
23
- const DEFAULT_HOOKS_ESCALATE_MS = 6e5;
24
- /** Escalation window for pull rows: PINBOX_HOOKS_ESCALATE_MS, default 10 min. */
25
- function hooksEscalateMs() {
26
- const raw = typeof process === "undefined" ? void 0 : process.env["PINBOX_HOOKS_ESCALATE_MS"];
27
- if (raw === void 0) return DEFAULT_HOOKS_ESCALATE_MS;
28
- const parsed = Number.parseInt(raw, 10);
29
- return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_HOOKS_ESCALATE_MS;
30
- }
31
- const BACKOFF_BASE_MS = 3e4;
32
- const BACKOFF_FACTOR = 4;
33
- const BACKOFF_CAP_MS = 9e5;
34
- var DeliveryRouter = class {
35
- store;
36
- adapters;
37
- chain = Promise.resolve();
38
- constructor(opts) {
39
- this.store = opts.store;
40
- this.adapters = [...opts.adapters];
41
- }
42
- /** THE single entry point; never throws — failures land in the queue. */
43
- dispatch(event) {
44
- return this.enqueueWork(() => this.dispatchOne(event));
45
- }
46
- /**
47
- * Runs on hub wake + a coarse unref'd interval: (a) boot/cursor reconciliation,
48
- * (b) retry/escalate due pending rows, (c) assign unassigned rows to the active
49
- * session. Never rejects.
50
- */
51
- drainDue(now) {
52
- return this.enqueueWork(() => this.drainNow(now ?? (/* @__PURE__ */ new Date()).toISOString()));
53
- }
54
- enqueueWork(work) {
55
- const run = this.chain.then(work).catch(() => {});
56
- this.chain = run;
57
- return run;
58
- }
59
- async dispatchOne(event) {
60
- if (event.seq <= this.store.deliveries.lastEventSeq()) return;
61
- let target;
62
- try {
63
- target = this.route(event);
64
- } catch {
65
- target = { kind: "skip" };
66
- }
67
- if (target.kind === "skip") {
68
- this.store.deliveries.enqueue({
69
- eventSeq: event.seq,
70
- sessionId: null,
71
- adapter: NO_ADAPTER,
72
- dueAt: null,
73
- status: "skipped"
74
- });
75
- return;
76
- }
77
- if (target.kind === "unassigned") {
78
- this.store.deliveries.enqueue({
79
- eventSeq: event.seq,
80
- sessionId: null,
81
- adapter: NO_ADAPTER,
82
- dueAt: null
83
- });
84
- return;
85
- }
86
- await this.attemptNew(event, target.session, /* @__PURE__ */ new Date());
87
- }
88
- /**
89
- * Deliverable events are pin.created and thread.message with role human|mirror
90
- * (rule 3: agent-authored events are never delivered back). Everything else —
91
- * pin.resolved, and later event types — is skipped-by-design.
92
- */
93
- route(event) {
94
- if (event.type === "pin.created") {
95
- const pin = PinSchema.parse(event.payload);
96
- return this.bindTarget(pin);
97
- }
98
- if (event.type === "thread.message") {
99
- const message = ThreadMessageSchema.parse(event.payload);
100
- if (message.role === "agent") return { kind: "skip" };
101
- const pin = this.store.getPin(message.pinId);
102
- if (pin === null) return { kind: "skip" };
103
- return this.bindTarget(pin);
104
- }
105
- return { kind: "skip" };
106
- }
107
- bindTarget(pin) {
108
- const { sessions } = this.store;
109
- if (pin.agentSession !== void 0) return {
110
- kind: "session",
111
- session: sessions.findByRef(pin.agentSession) ?? sessions.register(pin.agentSession)
112
- };
113
- const active = sessions.active();
114
- if (active === null) return { kind: "unassigned" };
115
- this.store.bindSession(pin.id, sessionRefOf(active));
116
- return {
117
- kind: "session",
118
- session: active
119
- };
120
- }
121
- async attemptNew(event, session, now) {
122
- const { deliveries } = this.store;
123
- const adapter = await this.selectAdapter(this.adapters, session);
124
- if (adapter === null) {
125
- deliveries.enqueue({
126
- eventSeq: event.seq,
127
- sessionId: session.id,
128
- adapter: NO_ADAPTER,
129
- dueAt: null
130
- });
131
- return;
132
- }
133
- if (PULL_ADAPTERS.has(adapter.name)) {
134
- const dueAt = new Date(now.getTime() + hooksEscalateMs()).toISOString();
135
- const row = deliveries.enqueue({
136
- eventSeq: event.seq,
137
- sessionId: session.id,
138
- adapter: adapter.name,
139
- dueAt
140
- });
141
- try {
142
- await adapter.deliver(event, session);
143
- } catch (cause) {
144
- this.recordFailure(row, cause, now);
145
- }
146
- return;
147
- }
148
- const row = deliveries.enqueue({
149
- eventSeq: event.seq,
150
- sessionId: session.id,
151
- adapter: adapter.name,
152
- dueAt: null
153
- });
154
- try {
155
- await adapter.deliver(event, session);
156
- deliveries.markDelivered(row.id);
157
- } catch (cause) {
158
- this.recordFailure(row, cause, now);
159
- }
160
- }
161
- async drainNow(now) {
162
- const { store } = this;
163
- for (const event of store.eventsAfter(store.deliveries.lastEventSeq())) await this.dispatchOne(event);
164
- for (const row of store.deliveries.due(now)) {
165
- if (row.sessionId === null) continue;
166
- try {
167
- await this.retryRow(row, now);
168
- } catch {}
169
- }
170
- const active = store.sessions.active();
171
- if (active !== null) for (const row of store.deliveries.unassigned()) try {
172
- await this.claimRow(row, active, now);
173
- } catch {}
174
- }
175
- async retryRow(row, now) {
176
- const { deliveries, sessions } = this.store;
177
- const event = this.eventOf(row);
178
- if (event === null) {
179
- deliveries.markFailed(row.id, `E_DELIVERY: event ${row.eventSeq} missing from log`, null);
180
- return;
181
- }
182
- const session = row.sessionId === null ? null : sessions.get(row.sessionId);
183
- if (session === null) {
184
- deliveries.markFailed(row.id, `E_SESSION_GONE: session ${row.sessionId} unknown`, null);
185
- return;
186
- }
187
- const adapter = await this.selectAdapter(this.rotatedAfter(row.adapter), session);
188
- if (adapter === null) return;
189
- if (PULL_ADAPTERS.has(adapter.name)) return;
190
- try {
191
- await adapter.deliver(event, session);
192
- deliveries.markDelivered(row.id);
193
- } catch (cause) {
194
- this.recordFailure(row, cause, new Date(Date.parse(now)));
195
- }
196
- }
197
- async claimRow(row, active, now) {
198
- const event = this.eventOf(row);
199
- if (event === null) {
200
- this.store.deliveries.markFailed(row.id, `E_DELIVERY: event ${row.eventSeq} missing from log`, null);
201
- return;
202
- }
203
- this.store.deliveries.assign(row.id, active.id);
204
- const pinId = pinIdOfEvent(event);
205
- if (pinId !== null) try {
206
- this.store.bindSession(pinId, sessionRefOf(active));
207
- } catch {}
208
- await this.retryRow({
209
- ...row,
210
- sessionId: active.id
211
- }, now);
212
- }
213
- async selectAdapter(candidates, session) {
214
- for (const adapter of candidates) try {
215
- if (await adapter.matches(session)) return adapter;
216
- } catch {}
217
- return null;
218
- }
219
- rotatedAfter(name) {
220
- const index = this.adapters.findIndex((adapter) => adapter.name === name);
221
- if (index < 0) return [...this.adapters];
222
- return [...this.adapters.slice(index + 1), ...this.adapters.slice(0, index + 1)];
223
- }
224
- recordFailure(row, cause, now) {
225
- const message = cause instanceof Error ? cause.message : String(cause);
226
- const sessionGone = message.startsWith("E_SESSION_GONE");
227
- const attempts = row.attempts;
228
- if (sessionGone || attempts >= 4) {
229
- const lastError = sessionGone ? message : `E_DELIVERY: ${message}`;
230
- this.store.deliveries.markFailed(row.id, lastError, null);
231
- return;
232
- }
233
- const backoffMs = Math.min(BACKOFF_BASE_MS * BACKOFF_FACTOR ** attempts, BACKOFF_CAP_MS);
234
- const retryAt = new Date(now.getTime() + backoffMs).toISOString();
235
- this.store.deliveries.markFailed(row.id, message, retryAt);
236
- }
237
- eventOf(row) {
238
- const event = this.store.eventsAfter(row.eventSeq - 1).at(0);
239
- return event !== void 0 && event.seq === row.eventSeq ? event : null;
240
- }
241
- };
242
- function sessionRefOf(session) {
243
- return {
244
- agent: session.agent,
245
- key: session.key,
246
- ...session.cwd !== void 0 ? { cwd: session.cwd } : {}
247
- };
248
- }
249
- /** pin.created payloads are the Pin (`id`); thread.message payloads carry `pinId`. */
250
- function pinIdOfEvent(event) {
251
- if (event.type === "pin.created") {
252
- const parsed = PinSchema.safeParse(event.payload);
253
- return parsed.success ? parsed.data.id : null;
254
- }
255
- if (event.type === "thread.message") {
256
- const parsed = ThreadMessageSchema.safeParse(event.payload);
257
- return parsed.success ? parsed.data.pinId : null;
258
- }
259
- return null;
260
- }
261
- //#endregion
2
+ import { a as createHooksAdapter, i as HOOK_CAPABLE_AGENTS, n as DeliveryRouter, r as hooksEscalateMs, t as DEFAULT_HOOKS_ESCALATE_MS } from "../router-B8EOto30.js";
262
3
  export { DEFAULT_HOOKS_ESCALATE_MS, DeliveryRouter, HOOK_CAPABLE_AGENTS, buildInjectionContext, buildReplyPrompt, createHooksAdapter, hooksEscalateMs };
@@ -1,4 +1,4 @@
1
- import { n as DeliveryAdapter } from "../router-D3dDjIaD.js";
1
+ import { n as DeliveryAdapter } from "../router-DBAEQNfs.js";
2
2
  //#region src/delivery/webhook.d.ts
3
3
  type WebhookConfig = {
4
4
  url: string;
package/dist/do.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { a as Pin, f as SessionRef, m as ThreadMessage, r as Link, t as Attachment } from "./schema-BOTmn5SM.js";
2
- import { r as SessionStore } from "./sessions-CJdMBH3C.js";
3
- import { a as LinkStore, c as PinStore, i as DeliveryStore, l as StoredEvent, t as CursorStore } from "./store-DHbwWu93.js";
1
+ import { a as Link, g as ThreadMessage, m as SessionRef, r as Attachment, s as Pin, t as AppliedEdit } from "./schema-HSZFobyd.js";
2
+ import { r as SessionStore } from "./sessions-CHtzoBF6.js";
3
+ import { a as LinkStore, c as PinStore, i as DeliveryStore, l as StoredEvent, t as CursorStore } from "./store-DPp8xMdp.js";
4
4
  import { Broadcaster } from "./ws.js";
5
5
  import { DurableObjectState, R2Bucket, WebSocket } from "@cloudflare/workers-types";
6
6
  //#region src/do-store.d.ts
@@ -8,6 +8,7 @@ interface DoPinStore extends PinStore {
8
8
  addThreadMessage(pinId: string, role: "human" | "agent" | "mirror", text: string, opts?: {
9
9
  origin?: string;
10
10
  attachments?: Attachment[];
11
+ edit?: AppliedEdit;
11
12
  }): ThreadMessage;
12
13
  /** @throws NotFoundError @throws ConflictError — trailing commit param is additive */
13
14
  resolvePin(id: string, by: "human" | "agent", note?: string, commit?: string): Pin;
@@ -28,6 +29,8 @@ type PinboxDoEnv = {
28
29
  PINBOX_TOKEN?: string;
29
30
  AUTH_STRATEGY?: "none" | "token" | "jwt";
30
31
  ALLOW_UNAUTHENTICATED?: string;
32
+ WEBHOOK_URL?: string;
33
+ WEBHOOK_SECRET?: string;
31
34
  JWT_ISSUER?: string;
32
35
  JWT_JWKS_URL?: string;
33
36
  JWT_AUDIENCE?: string;
@@ -51,12 +54,21 @@ declare class PinboxHubDO {
51
54
  private readonly env;
52
55
  private readonly strategy;
53
56
  private readonly handler;
57
+ /** Undefined when no adapter is configured — the hub then stores pins and delivers nothing. */
58
+ private readonly router;
54
59
  constructor(ctx: DurableObjectState, env: PinboxDoEnv);
55
60
  fetch(req: Request): Promise<Response>;
56
61
  private intercept;
57
62
  webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): void;
58
63
  webSocketClose(_ws: WebSocket, _code: number, _reason: string, _wasClean: boolean): void;
64
+ /**
65
+ * Retry/escalation for the delivery queue. DO alarms, never cron (deep-dive §1.14): the alarm
66
+ * survives eviction, so a webhook receiver that is down when a pin lands is retried after this
67
+ * DO has been unloaded and woken again.
68
+ */
59
69
  alarm(): Promise<void>;
70
+ /** Re-arm while work remains. Setting an alarm that already exists is a no-op. */
71
+ private scheduleDrain;
60
72
  private upgrade;
61
73
  private protocolError;
62
74
  private unauthorized;
package/dist/do.js CHANGED
@@ -1,8 +1,10 @@
1
1
  import { AttachmentSchema, PinInputSchema, PinSchema, SessionRefSchema, ThreadMessageSchema } from "./schema.js";
2
2
  import { a as NotFoundError, i as ConflictError, o as newId, t as SessionSchema } from "./sessions-DrCVTMfI.js";
3
- import { t as MIGRATIONS } from "./store-DM8MjB8M.js";
4
- import { n as createHubHandler, o as ok, r as err } from "./hub-QYz6OqYQ.js";
3
+ import { t as MIGRATIONS } from "./store-qtk4G3q4.js";
4
+ import { n as createHubHandler, o as ok, r as err } from "./hub-I67BuX9S.js";
5
5
  import { ClientHelloSchema, WS_CLOSE_PROTOCOL, WS_CLOSE_UNAUTHORIZED, WS_TOKEN_SUBPROTOCOL_PREFIX, encodeWsEvent } from "./ws-protocol.js";
6
+ import { n as DeliveryRouter } from "./router-B8EOto30.js";
7
+ import { createWebhookAdapter } from "./delivery/webhook.js";
6
8
  import { i as verifyJwt, n as verifyNone, r as verifyToken } from "./verify-BDt9d9Np.js";
7
9
  //#region src/do-store-registries.ts
8
10
  function parseSession(json) {
@@ -250,6 +252,7 @@ var DoSqlitePinStore = class {
250
252
  text,
251
253
  ...opts?.origin !== void 0 ? { origin: opts.origin } : {},
252
254
  ...opts?.attachments !== void 0 ? { attachments: opts.attachments } : {},
255
+ ...opts?.edit !== void 0 ? { edit: opts.edit } : {},
253
256
  at: (/* @__PURE__ */ new Date()).toISOString()
254
257
  });
255
258
  return this.mutate(() => {
@@ -556,6 +559,28 @@ var DoBroadcaster = class {
556
559
  return this.state.getWebSockets(topic).length;
557
560
  }
558
561
  };
562
+ /** Retry cadence for the delivery queue. Coarse: the receiver being down is not urgent. */
563
+ const DRAIN_INTERVAL_MS = 3e4;
564
+ /** Later than any due_at this hub can write, so `due()` answers "anything pending?". */
565
+ const FAR_FUTURE = "9999-12-31T23:59:59.999Z";
566
+ /**
567
+ * The cloud adapter set: `[webhook]` when configured, nothing otherwise.
568
+ *
569
+ * Returning undefined rather than an empty router is deliberate — a router with no adapters
570
+ * still writes pending rows, and nothing would ever drain them.
571
+ */
572
+ function buildRouter(store, env) {
573
+ const url = env.WEBHOOK_URL;
574
+ const secret = env.WEBHOOK_SECRET;
575
+ if (url === void 0 || url === "" || secret === void 0 || secret === "") return void 0;
576
+ return new DeliveryRouter({
577
+ store,
578
+ adapters: [createWebhookAdapter({
579
+ url,
580
+ secret
581
+ })]
582
+ });
583
+ }
559
584
  var PinboxHubDO = class {
560
585
  store;
561
586
  broadcaster;
@@ -564,6 +589,8 @@ var PinboxHubDO = class {
564
589
  env;
565
590
  strategy;
566
591
  handler;
592
+ /** Undefined when no adapter is configured — the hub then stores pins and delivers nothing. */
593
+ router;
567
594
  constructor(ctx, env) {
568
595
  this.ctx = ctx;
569
596
  this.env = env;
@@ -577,6 +604,14 @@ var PinboxHubDO = class {
577
604
  ..."verify" in this.strategy ? { verify: this.strategy.verify } : {}
578
605
  });
579
606
  this.store.subscribe((event) => this.broadcaster.publish(this.topic, encodeWsEvent(event)));
607
+ this.router = buildRouter(this.store, env);
608
+ if (this.router !== void 0) {
609
+ const router = this.router;
610
+ this.store.subscribe((event) => {
611
+ router.dispatch(event).then(() => this.scheduleDrain());
612
+ });
613
+ this.scheduleDrain();
614
+ }
580
615
  ctx.setWebSocketAutoResponse(new WebSocketRequestResponsePair("ping", "pong"));
581
616
  }
582
617
  async fetch(req) {
@@ -634,7 +669,23 @@ var PinboxHubDO = class {
634
669
  }));
635
670
  }
636
671
  webSocketClose(_ws, _code, _reason, _wasClean) {}
637
- async alarm() {}
672
+ /**
673
+ * Retry/escalation for the delivery queue. DO alarms, never cron (deep-dive §1.14): the alarm
674
+ * survives eviction, so a webhook receiver that is down when a pin lands is retried after this
675
+ * DO has been unloaded and woken again.
676
+ */
677
+ async alarm() {
678
+ if (this.router === void 0) return;
679
+ await this.router.drainDue();
680
+ await this.scheduleDrain();
681
+ }
682
+ /** Re-arm while work remains. Setting an alarm that already exists is a no-op. */
683
+ async scheduleDrain() {
684
+ if (this.router === void 0) return;
685
+ if (this.store.deliveries.due(FAR_FUTURE).length === 0) return;
686
+ if (await this.ctx.storage.getAlarm() !== null) return;
687
+ await this.ctx.storage.setAlarm(Date.now() + DRAIN_INTERVAL_MS);
688
+ }
638
689
  async upgrade(req, url, verify) {
639
690
  let token = url.searchParams.get("token");
640
691
  let acceptedProtocol;
@@ -1,4 +1,4 @@
1
- import { AttachmentSchema, PinInputSchema, SessionRefSchema } from "./schema.js";
1
+ import { AppliedEditSchema, AttachmentSchema, PinInputSchema, SessionRefSchema } from "./schema.js";
2
2
  import { a as NotFoundError, i as ConflictError, o as newId } from "./sessions-DrCVTMfI.js";
3
3
  import { t as POLL_OPEN_MS } from "./poll-BrcAuaAz.js";
4
4
  import { t as buildInjectionContext } from "./context-BHcEpzVb.js";
@@ -178,7 +178,8 @@ const ThreadPostSchema = z.object({
178
178
  ]),
179
179
  text: z.string().min(1),
180
180
  attachments: z.array(AttachmentSchema).optional(),
181
- origin: z.string().optional()
181
+ origin: z.string().optional(),
182
+ edit: AppliedEditSchema.optional()
182
183
  });
183
184
  const ResolvePostSchema = z.object({
184
185
  by: z.enum(["human", "agent"]),
@@ -260,7 +261,8 @@ async function routePinItem(req, url, opts) {
260
261
  const body = ThreadPostSchema.parse(await readJson(req));
261
262
  const messageOpts = {
262
263
  ...body.attachments === void 0 ? {} : { attachments: body.attachments },
263
- ...body.origin === void 0 ? {} : { origin: body.origin }
264
+ ...body.origin === void 0 ? {} : { origin: body.origin },
265
+ ...body.edit === void 0 ? {} : { edit: body.edit }
264
266
  };
265
267
  const hasOpts = Object.keys(messageOpts).length > 0;
266
268
  return ok(201, store.addThreadMessage(threadPinId, body.role, body.text, hasOpts ? messageOpts : void 0));
@@ -1,5 +1,5 @@
1
- import { t as Attachment } from "./schema-BOTmn5SM.js";
2
- import { c as PinStore } from "./store-DHbwWu93.js";
1
+ import { r as Attachment } from "./schema-HSZFobyd.js";
2
+ import { c as PinStore } from "./store-DPp8xMdp.js";
3
3
  import { HubOptions } from "./hub.js";
4
4
  //#region src/attachments.d.ts
5
5
  interface AttachmentSink {
@@ -1,4 +1,4 @@
1
- import { c as registerAttachmentSink, n as createHubHandler } from "./hub-QYz6OqYQ.js";
1
+ import { c as registerAttachmentSink, n as createHubHandler } from "./hub-I67BuX9S.js";
2
2
  import { ClientHelloSchema, WS_CLOSE_PROTOCOL, WS_CLOSE_UNAUTHORIZED, WS_TOKEN_SUBPROTOCOL_PREFIX, encodeWsEvent } from "./ws-protocol.js";
3
3
  //#region src/attachments-local.ts
4
4
  const EXTENSIONS = {
package/dist/hub.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { t as Connector } from "./types-BLNQb7MH.js";
2
- import { c as PinStore } from "./store-DHbwWu93.js";
1
+ import { t as Connector } from "./types-Bu7YuW6a.js";
2
+ import { c as PinStore } from "./store-DPp8xMdp.js";
3
3
  //#region src/hub.d.ts
4
4
  type Identity = {
5
5
  userId: string;
@@ -35,6 +35,10 @@ declare function mustGetPin(store: PinStore, id: string): {
35
35
  width: number;
36
36
  height: number;
37
37
  } | undefined;
38
+ spot?: {
39
+ x: number;
40
+ y: number;
41
+ } | undefined;
38
42
  fixed?: boolean | undefined;
39
43
  anchor?: string | undefined;
40
44
  source?: {
@@ -48,6 +52,7 @@ declare function mustGetPin(store: PinStore, id: string): {
48
52
  aria?: Record<string, string> | undefined;
49
53
  nearbyText?: string | undefined;
50
54
  selectedText?: string | undefined;
55
+ textRuns?: string[] | undefined;
51
56
  } | undefined;
52
57
  } | undefined;
53
58
  move?: {
package/dist/hub.js CHANGED
@@ -1,2 +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";
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-I67BuX9S.js";
2
2
  export { BodyNotJsonError, createHubHandler, err, match, mustGetPin, ok, readJson };
@@ -1,4 +1,4 @@
1
- import { a as Pin } from "./schema-BOTmn5SM.js";
1
+ import { s as Pin } from "./schema-HSZFobyd.js";
2
2
  //#region src/markdown.d.ts
3
3
  type DetailLevel = "compact" | "standard" | "forensic";
4
4
  declare function pinsToMarkdown(pins: Pin[], level: DetailLevel): string;
@@ -0,0 +1,6 @@
1
+ import { s as Pin } from "./schema-HSZFobyd.js";
2
+ import "./store-DPp8xMdp.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,261 @@
1
+ import { PinSchema, ThreadMessageSchema } from "./schema.js";
2
+ //#region src/delivery/hooks.ts
3
+ /** Agents whose hook systems can register + pull (research §1: shared hooks schema). */
4
+ const HOOK_CAPABLE_AGENTS = /* @__PURE__ */ new Set([
5
+ "claude",
6
+ "codex",
7
+ "hermes"
8
+ ]);
9
+ function createHooksAdapter() {
10
+ return {
11
+ name: "hooks",
12
+ matches(session) {
13
+ return session.endedAt === void 0 && HOOK_CAPABLE_AGENTS.has(session.agent);
14
+ },
15
+ async deliver() {}
16
+ };
17
+ }
18
+ //#endregion
19
+ //#region src/delivery/router.ts
20
+ const PULL_ADAPTERS = /* @__PURE__ */ new Set(["hooks"]);
21
+ const NO_ADAPTER = "none";
22
+ const DEFAULT_HOOKS_ESCALATE_MS = 6e5;
23
+ /** Escalation window for pull rows: PINBOX_HOOKS_ESCALATE_MS, default 10 min. */
24
+ function hooksEscalateMs() {
25
+ const raw = typeof process === "undefined" ? void 0 : process.env["PINBOX_HOOKS_ESCALATE_MS"];
26
+ if (raw === void 0) return DEFAULT_HOOKS_ESCALATE_MS;
27
+ const parsed = Number.parseInt(raw, 10);
28
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_HOOKS_ESCALATE_MS;
29
+ }
30
+ const BACKOFF_BASE_MS = 3e4;
31
+ const BACKOFF_FACTOR = 4;
32
+ const BACKOFF_CAP_MS = 9e5;
33
+ var DeliveryRouter = class {
34
+ store;
35
+ adapters;
36
+ chain = Promise.resolve();
37
+ constructor(opts) {
38
+ this.store = opts.store;
39
+ this.adapters = [...opts.adapters];
40
+ }
41
+ /** THE single entry point; never throws — failures land in the queue. */
42
+ dispatch(event) {
43
+ return this.enqueueWork(() => this.dispatchOne(event));
44
+ }
45
+ /**
46
+ * Runs on hub wake + a coarse unref'd interval: (a) boot/cursor reconciliation,
47
+ * (b) retry/escalate due pending rows, (c) assign unassigned rows to the active
48
+ * session. Never rejects.
49
+ */
50
+ drainDue(now) {
51
+ return this.enqueueWork(() => this.drainNow(now ?? (/* @__PURE__ */ new Date()).toISOString()));
52
+ }
53
+ enqueueWork(work) {
54
+ const run = this.chain.then(work).catch(() => {});
55
+ this.chain = run;
56
+ return run;
57
+ }
58
+ async dispatchOne(event) {
59
+ if (event.seq <= this.store.deliveries.lastEventSeq()) return;
60
+ let target;
61
+ try {
62
+ target = this.route(event);
63
+ } catch {
64
+ target = { kind: "skip" };
65
+ }
66
+ if (target.kind === "skip") {
67
+ this.store.deliveries.enqueue({
68
+ eventSeq: event.seq,
69
+ sessionId: null,
70
+ adapter: NO_ADAPTER,
71
+ dueAt: null,
72
+ status: "skipped"
73
+ });
74
+ return;
75
+ }
76
+ if (target.kind === "unassigned") {
77
+ this.store.deliveries.enqueue({
78
+ eventSeq: event.seq,
79
+ sessionId: null,
80
+ adapter: NO_ADAPTER,
81
+ dueAt: null
82
+ });
83
+ return;
84
+ }
85
+ await this.attemptNew(event, target.session, /* @__PURE__ */ new Date());
86
+ }
87
+ /**
88
+ * Deliverable events are pin.created and thread.message with role human|mirror
89
+ * (rule 3: agent-authored events are never delivered back). Everything else —
90
+ * pin.resolved, and later event types — is skipped-by-design.
91
+ */
92
+ route(event) {
93
+ if (event.type === "pin.created") {
94
+ const pin = PinSchema.parse(event.payload);
95
+ return this.bindTarget(pin);
96
+ }
97
+ if (event.type === "thread.message") {
98
+ const message = ThreadMessageSchema.parse(event.payload);
99
+ if (message.role === "agent") return { kind: "skip" };
100
+ const pin = this.store.getPin(message.pinId);
101
+ if (pin === null) return { kind: "skip" };
102
+ return this.bindTarget(pin);
103
+ }
104
+ return { kind: "skip" };
105
+ }
106
+ bindTarget(pin) {
107
+ const { sessions } = this.store;
108
+ if (pin.agentSession !== void 0) return {
109
+ kind: "session",
110
+ session: sessions.findByRef(pin.agentSession) ?? sessions.register(pin.agentSession)
111
+ };
112
+ const active = sessions.active();
113
+ if (active === null) return { kind: "unassigned" };
114
+ this.store.bindSession(pin.id, sessionRefOf(active));
115
+ return {
116
+ kind: "session",
117
+ session: active
118
+ };
119
+ }
120
+ async attemptNew(event, session, now) {
121
+ const { deliveries } = this.store;
122
+ const adapter = await this.selectAdapter(this.adapters, session);
123
+ if (adapter === null) {
124
+ deliveries.enqueue({
125
+ eventSeq: event.seq,
126
+ sessionId: session.id,
127
+ adapter: NO_ADAPTER,
128
+ dueAt: null
129
+ });
130
+ return;
131
+ }
132
+ if (PULL_ADAPTERS.has(adapter.name)) {
133
+ const dueAt = new Date(now.getTime() + hooksEscalateMs()).toISOString();
134
+ const row = deliveries.enqueue({
135
+ eventSeq: event.seq,
136
+ sessionId: session.id,
137
+ adapter: adapter.name,
138
+ dueAt
139
+ });
140
+ try {
141
+ await adapter.deliver(event, session);
142
+ } catch (cause) {
143
+ this.recordFailure(row, cause, now);
144
+ }
145
+ return;
146
+ }
147
+ const row = deliveries.enqueue({
148
+ eventSeq: event.seq,
149
+ sessionId: session.id,
150
+ adapter: adapter.name,
151
+ dueAt: null
152
+ });
153
+ try {
154
+ await adapter.deliver(event, session);
155
+ deliveries.markDelivered(row.id);
156
+ } catch (cause) {
157
+ this.recordFailure(row, cause, now);
158
+ }
159
+ }
160
+ async drainNow(now) {
161
+ const { store } = this;
162
+ for (const event of store.eventsAfter(store.deliveries.lastEventSeq())) await this.dispatchOne(event);
163
+ for (const row of store.deliveries.due(now)) {
164
+ if (row.sessionId === null) continue;
165
+ try {
166
+ await this.retryRow(row, now);
167
+ } catch {}
168
+ }
169
+ const active = store.sessions.active();
170
+ if (active !== null) for (const row of store.deliveries.unassigned()) try {
171
+ await this.claimRow(row, active, now);
172
+ } catch {}
173
+ }
174
+ async retryRow(row, now) {
175
+ const { deliveries, sessions } = this.store;
176
+ const event = this.eventOf(row);
177
+ if (event === null) {
178
+ deliveries.markFailed(row.id, `E_DELIVERY: event ${row.eventSeq} missing from log`, null);
179
+ return;
180
+ }
181
+ const session = row.sessionId === null ? null : sessions.get(row.sessionId);
182
+ if (session === null) {
183
+ deliveries.markFailed(row.id, `E_SESSION_GONE: session ${row.sessionId} unknown`, null);
184
+ return;
185
+ }
186
+ const adapter = await this.selectAdapter(this.rotatedAfter(row.adapter), session);
187
+ if (adapter === null) return;
188
+ if (PULL_ADAPTERS.has(adapter.name)) return;
189
+ try {
190
+ await adapter.deliver(event, session);
191
+ deliveries.markDelivered(row.id);
192
+ } catch (cause) {
193
+ this.recordFailure(row, cause, new Date(Date.parse(now)));
194
+ }
195
+ }
196
+ async claimRow(row, active, now) {
197
+ const event = this.eventOf(row);
198
+ if (event === null) {
199
+ this.store.deliveries.markFailed(row.id, `E_DELIVERY: event ${row.eventSeq} missing from log`, null);
200
+ return;
201
+ }
202
+ this.store.deliveries.assign(row.id, active.id);
203
+ const pinId = pinIdOfEvent(event);
204
+ if (pinId !== null) try {
205
+ this.store.bindSession(pinId, sessionRefOf(active));
206
+ } catch {}
207
+ await this.retryRow({
208
+ ...row,
209
+ sessionId: active.id
210
+ }, now);
211
+ }
212
+ async selectAdapter(candidates, session) {
213
+ for (const adapter of candidates) try {
214
+ if (await adapter.matches(session)) return adapter;
215
+ } catch {}
216
+ return null;
217
+ }
218
+ rotatedAfter(name) {
219
+ const index = this.adapters.findIndex((adapter) => adapter.name === name);
220
+ if (index < 0) return [...this.adapters];
221
+ return [...this.adapters.slice(index + 1), ...this.adapters.slice(0, index + 1)];
222
+ }
223
+ recordFailure(row, cause, now) {
224
+ const message = cause instanceof Error ? cause.message : String(cause);
225
+ const sessionGone = message.startsWith("E_SESSION_GONE");
226
+ const attempts = row.attempts;
227
+ if (sessionGone || attempts >= 4) {
228
+ const lastError = sessionGone ? message : `E_DELIVERY: ${message}`;
229
+ this.store.deliveries.markFailed(row.id, lastError, null);
230
+ return;
231
+ }
232
+ const backoffMs = Math.min(BACKOFF_BASE_MS * BACKOFF_FACTOR ** attempts, BACKOFF_CAP_MS);
233
+ const retryAt = new Date(now.getTime() + backoffMs).toISOString();
234
+ this.store.deliveries.markFailed(row.id, message, retryAt);
235
+ }
236
+ eventOf(row) {
237
+ const event = this.store.eventsAfter(row.eventSeq - 1).at(0);
238
+ return event !== void 0 && event.seq === row.eventSeq ? event : null;
239
+ }
240
+ };
241
+ function sessionRefOf(session) {
242
+ return {
243
+ agent: session.agent,
244
+ key: session.key,
245
+ ...session.cwd !== void 0 ? { cwd: session.cwd } : {}
246
+ };
247
+ }
248
+ /** pin.created payloads are the Pin (`id`); thread.message payloads carry `pinId`. */
249
+ function pinIdOfEvent(event) {
250
+ if (event.type === "pin.created") {
251
+ const parsed = PinSchema.safeParse(event.payload);
252
+ return parsed.success ? parsed.data.id : null;
253
+ }
254
+ if (event.type === "thread.message") {
255
+ const parsed = ThreadMessageSchema.safeParse(event.payload);
256
+ return parsed.success ? parsed.data.pinId : null;
257
+ }
258
+ return null;
259
+ }
260
+ //#endregion
261
+ export { createHooksAdapter as a, HOOK_CAPABLE_AGENTS as i, DeliveryRouter as n, hooksEscalateMs as r, DEFAULT_HOOKS_ESCALATE_MS as t };
@@ -1,6 +1,6 @@
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";
1
+ import { g as ThreadMessage, s as Pin } from "./schema-HSZFobyd.js";
2
+ import { t as Session } from "./sessions-CHtzoBF6.js";
3
+ import { c as PinStore, l as StoredEvent } from "./store-DPp8xMdp.js";
4
4
  //#region src/delivery/context.d.ts
5
5
  /**
6
6
  * The per-turn injection context: every open pin at the `compact` dial,
@@ -24,6 +24,21 @@ declare const AttachmentSchema: z.ZodObject<{
24
24
  width: z.ZodOptional<z.ZodNumber>;
25
25
  height: z.ZodOptional<z.ZodNumber>;
26
26
  }, z.core.$strip>;
27
+ /**
28
+ * A change an agent made directly to the live page, rather than to a file.
29
+ *
30
+ * On a project with a repo the agent edits source and the dev server reloads, so the change needs
31
+ * no wire representation. A hosted page has no repo to edit, so the only way an agent can actually
32
+ * change what you are looking at is to say which element and what it should now read.
33
+ *
34
+ * `selector` is the pin's own captured selector, never one the agent chose. `texts` lines up with
35
+ * that element's captured `textRuns`, so pinning a nav bar and asking for all four labels to
36
+ * change is a single edit. Text only, never markup, so this can carry no script.
37
+ */
38
+ declare const AppliedEditSchema: z.ZodObject<{
39
+ selector: z.ZodString;
40
+ texts: z.ZodArray<z.ZodString>;
41
+ }, z.core.$strip>;
27
42
  declare const ThreadMessageSchema: z.ZodObject<{
28
43
  id: z.ZodString;
29
44
  pinId: z.ZodString;
@@ -34,6 +49,10 @@ declare const ThreadMessageSchema: z.ZodObject<{
34
49
  }>;
35
50
  origin: z.ZodOptional<z.ZodString>;
36
51
  text: z.ZodString;
52
+ edit: z.ZodOptional<z.ZodObject<{
53
+ selector: z.ZodString;
54
+ texts: z.ZodArray<z.ZodString>;
55
+ }, z.core.$strip>>;
37
56
  attachments: z.ZodOptional<z.ZodArray<z.ZodObject<{
38
57
  id: z.ZodString;
39
58
  kind: z.ZodEnum<{
@@ -74,6 +93,10 @@ declare const PinInputSchema: z.ZodObject<{
74
93
  width: number;
75
94
  height: number;
76
95
  }, unknown>>>;
96
+ spot: z.ZodOptional<z.ZodObject<{
97
+ x: z.ZodNumber;
98
+ y: z.ZodNumber;
99
+ }, z.core.$strip>>;
77
100
  fixed: z.ZodOptional<z.ZodBoolean>;
78
101
  anchor: z.ZodOptional<z.ZodString>;
79
102
  source: z.ZodOptional<z.ZodObject<{
@@ -91,6 +114,7 @@ declare const PinInputSchema: z.ZodObject<{
91
114
  aria: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
92
115
  nearbyText: z.ZodOptional<z.ZodString>;
93
116
  selectedText: z.ZodOptional<z.ZodString>;
117
+ textRuns: z.ZodOptional<z.ZodArray<z.ZodString>>;
94
118
  }, z.core.$strip>>;
95
119
  }, z.core.$strip>>;
96
120
  move: z.ZodOptional<z.ZodObject<{
@@ -176,6 +200,10 @@ declare const PinSchema: z.ZodObject<{
176
200
  width: number;
177
201
  height: number;
178
202
  }, unknown>>>;
203
+ spot: z.ZodOptional<z.ZodObject<{
204
+ x: z.ZodNumber;
205
+ y: z.ZodNumber;
206
+ }, z.core.$strip>>;
179
207
  fixed: z.ZodOptional<z.ZodBoolean>;
180
208
  anchor: z.ZodOptional<z.ZodString>;
181
209
  source: z.ZodOptional<z.ZodObject<{
@@ -193,6 +221,7 @@ declare const PinSchema: z.ZodObject<{
193
221
  aria: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
194
222
  nearbyText: z.ZodOptional<z.ZodString>;
195
223
  selectedText: z.ZodOptional<z.ZodString>;
224
+ textRuns: z.ZodOptional<z.ZodArray<z.ZodString>>;
196
225
  }, z.core.$strip>>;
197
226
  }, z.core.$strip>>;
198
227
  move: z.ZodOptional<z.ZodObject<{
@@ -289,9 +318,10 @@ type Rect = z.infer<typeof RectSchema>;
289
318
  type Pin = z.infer<typeof PinSchema>;
290
319
  type PinInput = z.infer<typeof PinInputSchema>;
291
320
  type ThreadMessage = z.infer<typeof ThreadMessageSchema>;
321
+ type AppliedEdit = z.infer<typeof AppliedEditSchema>;
292
322
  type SessionRef = z.infer<typeof SessionRefSchema>;
293
323
  type Attachment = z.infer<typeof AttachmentSchema>;
294
324
  type Link = z.infer<typeof LinkSchema>;
295
325
  declare function pinJsonSchema(): Record<string, unknown>;
296
326
  //#endregion
297
- export { Pin as a, PinSchema as c, SCHEMA_VERSION as d, SessionRef as f, pinJsonSchema as g, ThreadMessageSchema as h, LinkSchema as i, Rect as l, ThreadMessage as m, AttachmentSchema as n, PinInput as o, SessionRefSchema as p, Link as r, PinInputSchema as s, Attachment as t, RectSchema as u };
327
+ export { ThreadMessageSchema as _, Link as a, PinInput as c, Rect as d, RectSchema as f, ThreadMessage as g, SessionRefSchema as h, AttachmentSchema as i, PinInputSchema as l, SessionRef as m, AppliedEditSchema as n, LinkSchema as o, SCHEMA_VERSION as p, Attachment as r, Pin as s, AppliedEdit as t, PinSchema as u, pinJsonSchema as v };
package/dist/schema.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as Pin, c as PinSchema, d as SCHEMA_VERSION, f as SessionRef, g as pinJsonSchema, h as ThreadMessageSchema, i as LinkSchema, l as Rect, m as ThreadMessage, n as AttachmentSchema, o as PinInput, p as SessionRefSchema, r as Link, s as PinInputSchema, t as Attachment, u as RectSchema } from "./schema-BOTmn5SM.js";
2
- export { Attachment, AttachmentSchema, Link, LinkSchema, Pin, PinInput, PinInputSchema, PinSchema, Rect, RectSchema, SCHEMA_VERSION, SessionRef, SessionRefSchema, ThreadMessage, ThreadMessageSchema, pinJsonSchema };
1
+ import { _ as ThreadMessageSchema, a as Link, c as PinInput, d as Rect, f as RectSchema, g as ThreadMessage, h as SessionRefSchema, i as AttachmentSchema, l as PinInputSchema, m as SessionRef, n as AppliedEditSchema, o as LinkSchema, p as SCHEMA_VERSION, r as Attachment, s as Pin, t as AppliedEdit, u as PinSchema, v as pinJsonSchema } from "./schema-HSZFobyd.js";
2
+ export { AppliedEdit, AppliedEditSchema, Attachment, AttachmentSchema, Link, LinkSchema, Pin, PinInput, PinInputSchema, PinSchema, Rect, RectSchema, SCHEMA_VERSION, SessionRef, SessionRefSchema, ThreadMessage, ThreadMessageSchema, pinJsonSchema };
package/dist/schema.js CHANGED
@@ -21,6 +21,22 @@ const AttachmentSchema = z.object({
21
21
  width: z.number().int().optional(),
22
22
  height: z.number().int().optional()
23
23
  });
24
+ /**
25
+ * A change an agent made directly to the live page, rather than to a file.
26
+ *
27
+ * On a project with a repo the agent edits source and the dev server reloads, so the change needs
28
+ * no wire representation. A hosted page has no repo to edit, so the only way an agent can actually
29
+ * change what you are looking at is to say which element and what it should now read.
30
+ *
31
+ * `selector` is the pin's own captured selector, never one the agent chose. `texts` lines up with
32
+ * that element's captured `textRuns`, so pinning a nav bar and asking for all four labels to
33
+ * change is a single edit. Text only, never markup, so this can carry no script.
34
+ */
35
+ const AppliedEditSchema = z.object({
36
+ selector: z.string(),
37
+ /** New text for each of the element's text runs, in order. Length must match, or nothing is applied. */
38
+ texts: z.array(z.string())
39
+ });
24
40
  const ThreadMessageSchema = z.object({
25
41
  id: z.string(),
26
42
  pinId: z.string(),
@@ -31,6 +47,7 @@ const ThreadMessageSchema = z.object({
31
47
  ]),
32
48
  origin: z.string().optional(),
33
49
  text: z.string(),
50
+ edit: AppliedEditSchema.optional(),
34
51
  attachments: z.array(AttachmentSchema).optional(),
35
52
  at: z.string()
36
53
  });
@@ -48,13 +65,27 @@ const ContextSchema = z.object({
48
65
  styles: z.record(z.string(), z.string()).optional(),
49
66
  aria: z.record(z.string(), z.string()).optional(),
50
67
  nearbyText: z.string().optional(),
51
- selectedText: z.string().optional()
68
+ selectedText: z.string().optional(),
69
+ /**
70
+ * The pinned element's text, split the way the markup splits it — one entry per element that
71
+ * actually holds words. A heading is one entry; a nav bar is one per link.
72
+ *
73
+ * `nearbyText` runs them together, which is fine to read and useless to edit: it cannot tell an
74
+ * agent that "work approach people contact" is four separate elements. This can, and it is what
75
+ * lets feedback on a group ("capitalise these") reach every item in it.
76
+ */
77
+ textRuns: z.array(z.string()).optional()
52
78
  });
53
79
  const TargetSchema = z.object({
54
80
  url: z.string().optional(),
55
81
  selector: z.string().optional(),
56
82
  tag: z.string().optional(),
57
83
  rect: RectSchema.optional(),
84
+ /** Where inside `rect` the click landed, 0–1 on each axis. Absent ⇒ anchor at the centre. */
85
+ spot: z.object({
86
+ x: z.number(),
87
+ y: z.number()
88
+ }).optional(),
58
89
  fixed: z.boolean().optional(),
59
90
  anchor: z.string().optional(),
60
91
  source: SourceSchema.optional(),
@@ -118,4 +149,4 @@ function pinJsonSchema() {
118
149
  return z.toJSONSchema(PinSchema);
119
150
  }
120
151
  //#endregion
121
- export { AttachmentSchema, LinkSchema, PinInputSchema, PinSchema, RectSchema, SCHEMA_VERSION, SessionRefSchema, ThreadMessageSchema, pinJsonSchema };
152
+ export { AppliedEditSchema, AttachmentSchema, LinkSchema, PinInputSchema, PinSchema, RectSchema, SCHEMA_VERSION, SessionRefSchema, ThreadMessageSchema, pinJsonSchema };
package/dist/schema.json CHANGED
@@ -50,6 +50,22 @@
50
50
  ],
51
51
  "additionalProperties": false
52
52
  },
53
+ "spot": {
54
+ "type": "object",
55
+ "properties": {
56
+ "x": {
57
+ "type": "number"
58
+ },
59
+ "y": {
60
+ "type": "number"
61
+ }
62
+ },
63
+ "required": [
64
+ "x",
65
+ "y"
66
+ ],
67
+ "additionalProperties": false
68
+ },
53
69
  "fixed": {
54
70
  "type": "boolean"
55
71
  },
@@ -112,6 +128,12 @@
112
128
  },
113
129
  "selectedText": {
114
130
  "type": "string"
131
+ },
132
+ "textRuns": {
133
+ "type": "array",
134
+ "items": {
135
+ "type": "string"
136
+ }
115
137
  }
116
138
  },
117
139
  "additionalProperties": false
@@ -1,4 +1,4 @@
1
- import { f as SessionRef } from "./schema-BOTmn5SM.js";
1
+ import { m as SessionRef } from "./schema-HSZFobyd.js";
2
2
  import { z } from "zod";
3
3
  import { Database } from "bun:sqlite";
4
4
  //#region src/trailer.d.ts
@@ -1,2 +1,2 @@
1
- import { a as parseTrailers, i as SqliteSessionStore, n as SessionSchema, r as SessionStore, t as Session } from "./sessions-CJdMBH3C.js";
1
+ import { a as parseTrailers, i as SqliteSessionStore, n as SessionSchema, r as SessionStore, t as Session } from "./sessions-CHtzoBF6.js";
2
2
  export { Session, SessionSchema, SessionStore, SqliteSessionStore, parseTrailers };
@@ -1,5 +1,5 @@
1
- import { a as Pin, f as SessionRef, m as ThreadMessage, o as PinInput, r as Link, t as Attachment } from "./schema-BOTmn5SM.js";
2
- import { r as SessionStore } from "./sessions-CJdMBH3C.js";
1
+ import { a as Link, c as PinInput, g as ThreadMessage, m as SessionRef, r as Attachment, s as Pin, t as AppliedEdit } from "./schema-HSZFobyd.js";
2
+ import { r as SessionStore } from "./sessions-CHtzoBF6.js";
3
3
  //#region src/store-errors.d.ts
4
4
  /** The addressed row does not exist. Mapped to 404 / `E_NOT_FOUND` by the hub. */
5
5
  declare class NotFoundError extends Error {}
@@ -28,6 +28,7 @@ interface PinStore {
28
28
  addThreadMessage(pinId: string, role: "human" | "agent" | "mirror", text: string, opts?: {
29
29
  origin?: string;
30
30
  attachments?: Attachment[];
31
+ edit?: AppliedEdit;
31
32
  }): ThreadMessage;
32
33
  getThread(pinId: string): ThreadMessage[];
33
34
  /** @throws NotFoundError @throws ConflictError — the trailing commit param is additive */
@@ -327,6 +327,7 @@ var SqlitePinStore = class {
327
327
  ...opts?.origin !== void 0 ? { origin: opts.origin } : {},
328
328
  text,
329
329
  ...opts?.attachments !== void 0 ? { attachments: opts.attachments } : {},
330
+ ...opts?.edit !== void 0 ? { edit: opts.edit } : {},
330
331
  at: (/* @__PURE__ */ new Date()).toISOString()
331
332
  });
332
333
  this.mutate(() => {
package/dist/store.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as LinkStore, c as PinStore, d as ConflictError, f as NotFoundError, i as DeliveryStore, l as StoredEvent, n as DeliveryRow, o as MIGRATIONS, r as DeliveryStatus, s as Migration, t as CursorStore, u as openStore } from "./store-DHbwWu93.js";
1
+ import { a as LinkStore, c as PinStore, d as ConflictError, f as NotFoundError, i as DeliveryStore, l as StoredEvent, n as DeliveryRow, o as MIGRATIONS, r as DeliveryStatus, s as Migration, t as CursorStore, u as openStore } from "./store-DPp8xMdp.js";
2
2
  export { ConflictError, CursorStore, DeliveryRow, DeliveryStatus, DeliveryStore, LinkStore, MIGRATIONS, Migration, NotFoundError, PinStore, StoredEvent, openStore };
package/dist/store.js CHANGED
@@ -1,3 +1,3 @@
1
1
  import { a as NotFoundError, i as ConflictError } from "./sessions-DrCVTMfI.js";
2
- import { n as openStore, t as MIGRATIONS } from "./store-DM8MjB8M.js";
2
+ import { n as openStore, t as MIGRATIONS } from "./store-qtk4G3q4.js";
3
3
  export { ConflictError, MIGRATIONS, NotFoundError, openStore };
@@ -1,4 +1,4 @@
1
- import { a as Pin, m as ThreadMessage, r as Link } from "./schema-BOTmn5SM.js";
1
+ import { a as Link, g as ThreadMessage, s as Pin } from "./schema-HSZFobyd.js";
2
2
  //#region src/connectors/types.d.ts
3
3
  /** Host-injected transport: local = `gh` CLI shell-out (impl in packages/cli, Bun.$); Worker = App token fetch. */
4
4
  interface ConnectorTransport {
@@ -1,4 +1,4 @@
1
- import { l as StoredEvent } from "./store-DHbwWu93.js";
1
+ import { l as StoredEvent } from "./store-DPp8xMdp.js";
2
2
  import { z } from "zod";
3
3
  //#region src/ws-protocol.d.ts
4
4
  declare const WS_PATH = "/ws";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autono/pinbox-core",
3
- "version": "0.1.0",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,6 +0,0 @@
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 };