@paradow/ledge 0.13.0 → 0.15.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 (28) hide show
  1. package/dist/cli/{attach-PJHKOS4N.js → attach-Y4OMZXAH.js} +7 -7
  2. package/dist/cli/{bridge-VQZCDSQE.js → bridge-6K523KUX.js} +2 -2
  3. package/dist/cli/{chunk-ZEHTNFPV.js → chunk-2H5FJH4Q.js} +2 -2
  4. package/dist/cli/{chunk-PPRB4XIX.js → chunk-2LQITRTR.js} +531 -141
  5. package/dist/cli/{chunk-ZSKCV73V.js → chunk-4IBQ4PG6.js} +6 -6
  6. package/dist/cli/{chunk-YUSY3E47.js → chunk-BNKPXSUY.js} +1 -1
  7. package/dist/cli/{chunk-IOASI5FF.js → chunk-CU2E3W4L.js} +79 -4
  8. package/dist/cli/{chunk-XIO4OIHR.js → chunk-DXVHFVJI.js} +2 -2
  9. package/dist/cli/chunk-EHHYRQWY.js +6 -0
  10. package/dist/cli/{chunk-FXI2EE52.js → chunk-IMSPQUFC.js} +2 -2
  11. package/dist/cli/{chunk-5XFBRTKO.js → chunk-KGEF6OIU.js} +2 -2
  12. package/dist/cli/{chunk-7XZNRFWJ.js → chunk-NKYJF3VT.js} +2 -2
  13. package/dist/cli/{chunk-SVVQ5KPJ.js → chunk-PXO4FFJR.js} +1 -1
  14. package/dist/cli/{chunk-FGOFRWIB.js → chunk-R5462SQM.js} +1 -1
  15. package/dist/cli/{daemon-ZUDRXUCP.js → daemon-NIEJZDEC.js} +3947 -957
  16. package/dist/cli/{hook-KKJLIOOM.js → hook-BGIM76EW.js} +8 -8
  17. package/dist/cli/index.js +11 -11
  18. package/dist/cli/{launch-4CBL5ZRR.js → launch-JS74JGW6.js} +7 -7
  19. package/dist/cli/send-JX6O7AQR.js +14 -0
  20. package/dist/cli/{setup-TULNBDFB.js → setup-2GROTY6B.js} +4 -4
  21. package/dist/cli/{status-R7IOK5KL.js → status-Q6TQXVLF.js} +3 -3
  22. package/dist/cli/{uninstall-OFSLCP4T.js → uninstall-JCX7QXU2.js} +4 -4
  23. package/dist/cli/{version-JOFQUAE4.js → version-X45HSUBB.js} +1 -1
  24. package/dist/cli/who-KAAEASAA.js +11 -0
  25. package/package.json +2 -1
  26. package/dist/cli/chunk-LMQADKT7.js +0 -6
  27. package/dist/cli/send-6EGRHCRO.js +0 -14
  28. package/dist/cli/who-NVF6ZMSP.js +0 -11
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  paths
3
- } from "./chunk-FGOFRWIB.js";
3
+ } from "./chunk-R5462SQM.js";
4
4
 
5
5
  // ../protocol/src/session.ts
6
6
  import { z } from "zod";
7
7
  var ToolSchema = z.enum(["claude", "codex", "cursor", "omp"]);
8
+ var OmpApprovalSchema = z.enum(["always-ask", "write", "yolo"]);
8
9
  var BackendSchema = z.enum(["tmux", "omp-rpc"]);
9
10
  var SessionStateSchema = z.enum(["starting", "working", "waiting_input", "waiting_permission", "idle", "exited", "error"]);
10
11
  var AskQuestionSchema = z.object({
@@ -88,6 +89,8 @@ var SessionSchema = z.object({
88
89
  /** The model and effort Ledge launched the agent with (#58); left out, the agent's own default. */
89
90
  model: z.string().optional(),
90
91
  effort: z.string().optional(),
92
+ /** OMP: the approval mode it was launched with, kept by a resume; left out, `always-ask`. */
93
+ approval: OmpApprovalSchema.optional(),
91
94
  /** A model change asked while the agent worked (`session.model`), applied at its next free moment; a field left out is the agent's default. */
92
95
  modelNext: z.object({ model: z.string().optional(), effort: z.string().optional() }).optional(),
93
96
  /** The agent's own session id when Ledge chose it at launch (Claude Code `--session-id`). */
@@ -113,7 +116,7 @@ function isArchived(s) {
113
116
  }
114
117
 
115
118
  // ../protocol/src/messages.ts
116
- import { z as z14 } from "zod";
119
+ import { z as z15 } from "zod";
117
120
 
118
121
  // ../protocol/src/chat.ts
119
122
  import { z as z2 } from "zod";
@@ -453,73 +456,125 @@ function pendingKey(p) {
453
456
  return cyrb53(s, 0) + cyrb53(s, 1);
454
457
  }
455
458
 
459
+ // ../protocol/src/mobile.ts
460
+ import { z as z14 } from "zod";
461
+ var DEFAULT_RELAY_URL = "https://app.kl-ledge.dev";
462
+ var RelayUrlSchema = z14.string().transform((v, ctx) => {
463
+ const bad = () => {
464
+ ctx.addIssue({ code: z14.ZodIssueCode.custom, message: "invalid relay URL" });
465
+ return z14.NEVER;
466
+ };
467
+ let u;
468
+ try {
469
+ u = new URL(v.replace(/\/$/, ""));
470
+ } catch {
471
+ return bad();
472
+ }
473
+ const local = u.hostname === "localhost" || u.hostname === "127.0.0.1";
474
+ if (u.protocol !== "https:" && !(u.protocol === "http:" && local)) return bad();
475
+ if (u.username || u.password || u.pathname !== "/" || u.search || u.hash) return bad();
476
+ return u.origin;
477
+ });
478
+ var MobileConfigSchema = z14.object({ enabled: z14.boolean(), relayUrl: RelayUrlSchema });
479
+ var MobileLinkStateSchema = z14.discriminatedUnion("state", [
480
+ z14.object({ state: z14.literal("off") }),
481
+ z14.object({ state: z14.literal("connecting") }),
482
+ z14.object({ state: z14.literal("online") }),
483
+ z14.object({ state: z14.literal("offline"), retryAt: z14.string().datetime(), error: z14.string() }),
484
+ z14.object({ state: z14.literal("signed_out"), reason: z14.enum(["other_pc", "session_invalid"]) }),
485
+ /** The mobile service did not start (a broken mobile.json, a missing key store): nothing is linked until the daemon restarts. */
486
+ z14.object({ state: z14.literal("unavailable"), error: z14.string().max(200) })
487
+ ]);
488
+ var MobilePairingSchema = z14.object({
489
+ id: z14.string(),
490
+ label: z14.string(),
491
+ code: z14.string().nullable(),
492
+ expiresAt: z14.string().datetime(),
493
+ state: z14.enum(["waiting", "confirm", "approved", "linked", "expired", "refused"])
494
+ });
495
+ var MobileStateSchema = z14.object({
496
+ enabled: z14.boolean(),
497
+ relayUrl: z14.string(),
498
+ account: z14.object({ login: z14.string(), accountId: z14.string() }).nullable(),
499
+ signin: z14.object({ userCode: z14.string(), verificationUri: z14.string(), expiresAt: z14.string().datetime() }).nullable(),
500
+ /** The last sign-in that did not end with a session; cleared by the next one. */
501
+ signinError: z14.enum(["expired", "denied", "not_allowed", "failed"]).nullable(),
502
+ link: MobileLinkStateSchema,
503
+ devices: z14.array(z14.object({ id: z14.string(), label: z14.string(), approvedAt: z14.string(), online: z14.boolean(), lastSeenAt: z14.string().optional() })),
504
+ pairings: z14.array(MobilePairingSchema)
505
+ });
506
+
456
507
  // ../protocol/src/messages.ts
457
508
  var PROTOCOL_PORT = 47831;
458
- var SessionEventSchema = z14.object({
459
- name: z14.enum(["SessionStart", "UserPromptSubmit", "PreToolUse", "PermissionRequest", "Notification", "Stop", "SessionEnd", "PaneChanged", "PaneGone", "PanePrompt", "PaneIdle", "InjectFailed", "Decided", "PostToolUse", "PostToolUseFailure", "StopFailure", "SubagentStart", "SubagentStop", "PaneTitle", "Renamed", "NativeRequest", "AgentMessage", "QueueUpdate"]),
460
- at: z14.string().datetime(),
461
- source: z14.enum(["hook", "pane", "daemon"]),
462
- tool: z14.string().optional(),
463
- text: z14.string().optional(),
464
- options: z14.array(z14.string()).optional(),
509
+ var RefusalCodeSchema = z15.enum(["stale", "read_only", "no_prompt", "not_taken", "busy", "unknown_session", "nothing_to_decide", "not_offered", "free_text", "note_not_typed", "unavailable", "invalid"]);
510
+ var SessionEventSchema = z15.object({
511
+ name: z15.enum(["SessionStart", "UserPromptSubmit", "PreToolUse", "PermissionRequest", "Notification", "Stop", "SessionEnd", "PaneChanged", "PaneGone", "PanePrompt", "PaneIdle", "InjectFailed", "Decided", "PostToolUse", "PostToolUseFailure", "StopFailure", "SubagentStart", "SubagentStop", "PaneTitle", "Renamed", "NativeRequest", "AgentMessage", "QueueUpdate"]),
512
+ at: z15.string().datetime(),
513
+ source: z15.enum(["hook", "pane", "daemon"]),
514
+ tool: z15.string().optional(),
515
+ text: z15.string().optional(),
516
+ options: z15.array(z15.string()).optional(),
465
517
  /** Each option's description, by index ('' when it has none); left out when no option has one. */
466
- optionDetails: z14.array(z14.string()).optional(),
467
- kind: z14.enum(["question", "permission", "plan", "diff"]).optional(),
518
+ optionDetails: z15.array(z15.string()).optional(),
519
+ kind: z15.enum(["question", "permission", "plan", "diff"]).optional(),
468
520
  /** PanePrompt: what to review in full before deciding (a plan read in the terminal), when `text` is its summary. */
469
- body: z14.string().optional(),
470
- exitCode: z14.number().int().nullable().optional(),
471
- helperId: z14.string().optional(),
521
+ body: z15.string().optional(),
522
+ exitCode: z15.number().int().nullable().optional(),
523
+ helperId: z15.string().optional(),
472
524
  /** The helper's description, once known (Claude Code: its `.meta.json`, written after SubagentStart). */
473
- task: z14.string().optional(),
525
+ task: z15.string().optional(),
474
526
  /** PostToolUseFailure: the user interrupted the turn (Esc); Claude then waits for input with no Stop. */
475
- interrupt: z14.boolean().optional(),
527
+ interrupt: z15.boolean().optional(),
476
528
  /** QueueUpdate: OMP's queue as it stands. */
477
- queue: z14.array(z14.object({ text: z14.string(), mode: z14.enum(["steer", "follow_up"]) })).optional(),
529
+ queue: z15.array(z15.object({ text: z15.string(), mode: z15.enum(["steer", "follow_up"]) })).optional(),
478
530
  /** AgentMessage: the harness's own message, its text so far (`streaming`) or whole. `messageId` is unique for the session's life. */
479
- messageId: z14.string().optional(),
480
- streaming: z14.boolean().optional(),
531
+ messageId: z15.string().optional(),
532
+ streaming: z15.boolean().optional(),
481
533
  /** NativeRequest: the dialog's id, and OMP's `ask` questions when it is one. */
482
- requestId: z14.string().optional(),
483
- questions: z14.array(AskQuestionSchema).optional(),
534
+ requestId: z15.string().optional(),
535
+ questions: z15.array(AskQuestionSchema).optional(),
484
536
  /** Stop: its text is already in the thread as AgentMessage cards, which the reply card must not repeat. */
485
- streamed: z14.boolean().optional(),
537
+ streamed: z15.boolean().optional(),
486
538
  /** Native tracking (spec 2026-10-13 §2): the harness's id of the tool call, so its result lands on the same row. */
487
- toolUseId: z14.string().optional(),
539
+ toolUseId: z15.string().optional(),
488
540
  /** The tool's input as the harness gave it, every string capped by the hook. */
489
- toolInput: z14.record(z14.string(), z14.unknown()).optional(),
541
+ toolInput: z15.record(z15.string(), z15.unknown()).optional(),
490
542
  /** PostToolUse: the result as text; SubagentStop: nothing (the report travels in `text`). */
491
- output: z14.string().optional(),
492
- durationMs: z14.number().nonnegative().optional(),
543
+ output: z15.string().optional(),
544
+ durationMs: z15.number().nonnegative().optional(),
493
545
  /** PostToolUseFailure: the harness's error message. */
494
- error: z14.string().optional(),
546
+ error: z15.string().optional(),
495
547
  /** The session's transcript, or the helper's on SubagentStop; the daemon reads its tail for the agent's messages. */
496
- transcriptPath: z14.string().optional(),
548
+ transcriptPath: z15.string().optional(),
497
549
  /** Decided: what was chosen when the daemon knows it. */
498
550
  decision: DecisionSchema.optional(),
499
551
  /** The agent's own session id when the hook names it, so the daemon can resume that conversation. */
500
- agentSessionId: z14.string().optional()
552
+ agentSessionId: z15.string().optional()
501
553
  });
502
- var RecentSchema = z14.object({ cwd: z14.string().min(1), project: z14.string().min(1), lastAt: z14.string().datetime() });
554
+ var RecentSchema = z15.object({ cwd: z15.string().min(1), project: z15.string().min(1), lastAt: z15.string().datetime() });
503
555
  var SEMVER = /^\d+\.\d+\.\d+$/;
504
- var DaemonInfoSchema = z14.object({
505
- version: z14.string(),
506
- install: z14.enum(["npm", "git", "unknown"]),
507
- systemd: z14.boolean(),
508
- manual: z14.string().optional(),
509
- routing: z14.boolean().optional(),
510
- archive: z14.boolean().optional(),
556
+ var DaemonInfoSchema = z15.object({
557
+ version: z15.string(),
558
+ install: z15.enum(["npm", "git", "unknown"]),
559
+ systemd: z15.boolean(),
560
+ manual: z15.string().optional(),
561
+ routing: z15.boolean().optional(),
562
+ archive: z15.boolean().optional(),
511
563
  /** Takes `model` and `effort` on `session.launch` and answers `models.list` (#58). */
512
- models: z14.boolean().optional(),
564
+ models: z15.boolean().optional(),
513
565
  /** Drives OMP over its RPC: launching `omp` sessions is possible when the binary is there (`models.list` says so). */
514
- omp: z14.boolean().optional(),
566
+ omp: z15.boolean().optional(),
515
567
  /** Takes `session.model`: a live session's model and effort changed (#58, lot 2). */
516
- modelSwitch: z14.boolean().optional(),
568
+ modelSwitch: z15.boolean().optional(),
569
+ /** Takes `approval` on an OMP `session.launch` (auto mode). */
570
+ ompApproval: z15.boolean().optional(),
517
571
  /** Its home folder, so a client shows the folders under it as `~` (absent from older daemons). */
518
- home: z14.string().optional()
572
+ home: z15.string().optional(),
573
+ mobile: z15.boolean().optional()
519
574
  });
520
- var UpdateFailureSchema = z14.enum(["git-install", "no-systemd", "sessions-at-risk", "npm-failed", "busy"]);
521
- var ServerMessageSchema = z14.discriminatedUnion("type", [
522
- z14.object({ type: z14.literal("chat.routed"), qid: z14.string(), result: RouteResultSchema }),
575
+ var UpdateFailureSchema = z15.enum(["git-install", "no-systemd", "sessions-at-risk", "npm-failed", "busy"]);
576
+ var ServerMessageSchema = z15.discriminatedUnion("type", [
577
+ z15.object({ type: z15.literal("chat.routed"), qid: z15.string(), result: RouteResultSchema }),
523
578
  DocumentContentSchema,
524
579
  DocumentErrorSchema,
525
580
  ReposInfoSchema,
@@ -528,64 +583,66 @@ var ServerMessageSchema = z14.discriminatedUnion("type", [
528
583
  /** `recents`: the folders of past sessions, most recent first. `launcher`: the command that runs `ledge` inside WSL
529
584
  * (`[node, cli]`), so the overlay can attach a terminal to a session with `<launcher> attach <id>`. `daemon`: absent
530
585
  * from daemons older than the auto-update. */
531
- z14.object({ type: z14.literal("snapshot"), sessions: z14.array(SessionSchema), recents: z14.array(RecentSchema).optional(), launcher: z14.array(z14.string()).optional(), daemon: DaemonInfoSchema.optional(), sshAlias: SshAliasSchema.optional() }),
532
- z14.object({ type: z14.literal("session.updated"), session: SessionSchema }),
586
+ z15.object({ type: z15.literal("snapshot"), sessions: z15.array(SessionSchema), recents: z15.array(RecentSchema).optional(), launcher: z15.array(z15.string()).optional(), daemon: DaemonInfoSchema.optional(), sshAlias: SshAliasSchema.optional() }),
587
+ z15.object({ type: z15.literal("session.updated"), session: SessionSchema }),
533
588
  /** OMP: the instructions taken back from its queue by an interrupt that restores it; the asker puts them back in its draft. */
534
- z14.object({ type: z14.literal("session.queue.restored"), id: z14.string(), texts: z14.array(z14.string()) }),
535
- z14.object({ type: z14.literal("session.removed"), id: z14.string() }),
536
- z14.object({ type: z14.literal("session.preview"), id: z14.string(), lines: z14.array(z14.string()) }),
589
+ z15.object({ type: z15.literal("session.queue.restored"), id: z15.string(), texts: z15.array(z15.string()) }),
590
+ z15.object({ type: z15.literal("session.removed"), id: z15.string() }),
591
+ z15.object({ type: z15.literal("session.preview"), id: z15.string(), lines: z15.array(z15.string()) }),
537
592
  /** A session's thread, on request (spec 2026-10-13 §5); then each created or updated item, broadcast. */
538
- z14.object({ type: z14.literal("session.thread"), id: z14.string(), items: z14.array(ThreadItemSchema) }),
539
- z14.object({ type: z14.literal("session.thread.item"), id: z14.string(), item: ThreadItemSchema }),
593
+ z15.object({ type: z15.literal("session.thread"), id: z15.string(), items: z15.array(ThreadItemSchema) }),
594
+ z15.object({ type: z15.literal("session.thread.item"), id: z15.string(), item: ThreadItemSchema }),
540
595
  /** Answer to the requester of a `session.launch`: the session exists (its `session.updated` is broadcast as usual). */
541
- z14.object({ type: z14.literal("session.launched"), id: z14.string() }),
542
- z14.object({ type: z14.literal("recents"), recents: z14.array(RecentSchema) }),
596
+ z15.object({ type: z15.literal("session.launched"), id: z15.string() }),
597
+ z15.object({ type: z15.literal("recents"), recents: z15.array(RecentSchema) }),
543
598
  /** Answer to the requester of a `folder.list` or `folder.create`: `path` resolved (`~` expanded), `parent` null at the
544
599
  * root, `entries` its subfolders by name (hidden ones left out). A failure keeps the asked path and says why in `error`,
545
600
  * never in an `error` message, which the launch form reads as a failed launch. */
546
- z14.object({
547
- type: z14.literal("folder.listing"),
548
- path: z14.string(),
549
- parent: z14.string().nullable(),
550
- entries: z14.array(z14.string()),
551
- error: z14.string().optional(),
601
+ z15.object({
602
+ type: z15.literal("folder.listing"),
603
+ path: z15.string(),
604
+ parent: z15.string().nullable(),
605
+ entries: z15.array(z15.string()),
606
+ error: z15.string().optional(),
552
607
  /** The request this answers, when it carried one. */
553
- requestId: z14.string().optional(),
608
+ requestId: z15.string().optional(),
554
609
  /** Those `entries` that are Git repositories, with their branch (the short commit when HEAD is detached). */
555
- repos: z14.array(z14.object({ name: z14.string(), branch: z14.string() })).optional()
610
+ repos: z15.array(z15.object({ name: z15.string(), branch: z15.string() })).optional()
556
611
  }),
557
612
  /** The chat (spec 2026-10-11 §5): what Ledge reads, its words as they come, the final text, or why it stopped; the day's turns after each snapshot. */
558
- z14.object({ type: z14.literal("chat.step"), qid: z14.string(), step: ChatStepSchema }),
559
- z14.object({ type: z14.literal("chat.delta"), qid: z14.string(), text: z14.string() }),
560
- z14.object({ type: z14.literal("chat.done"), qid: z14.string(), text: z14.string(), at: z14.string().datetime(), proposal: ChatProposalSchema.optional(), proposals: ChatProposalsSchema.optional(), relay: ChatRelaySchema.optional(), projectProposal: ChatProjectProposalSchema.optional() }),
561
- z14.object({ type: z14.literal("chat.error"), qid: z14.string(), message: z14.string() }),
562
- z14.object({ type: z14.literal("chat.history"), turns: z14.array(ChatTurnSchema) }),
613
+ z15.object({ type: z15.literal("chat.step"), qid: z15.string(), step: ChatStepSchema }),
614
+ z15.object({ type: z15.literal("chat.delta"), qid: z15.string(), text: z15.string() }),
615
+ z15.object({ type: z15.literal("chat.done"), qid: z15.string(), text: z15.string(), at: z15.string().datetime(), proposal: ChatProposalSchema.optional(), proposals: ChatProposalsSchema.optional(), relay: ChatRelaySchema.optional(), projectProposal: ChatProjectProposalSchema.optional() }),
616
+ z15.object({ type: z15.literal("chat.error"), qid: z15.string(), message: z15.string() }),
617
+ z15.object({ type: z15.literal("chat.history"), turns: z15.array(ChatTurnSchema) }),
563
618
  /** The daemon updating itself: npm is running, then the service restarts (the socket closes right after). */
564
- z14.object({ type: z14.literal("update.progress"), version: z14.string(), step: z14.enum(["installing", "restarting"]) }),
619
+ z15.object({ type: z15.literal("update.progress"), version: z15.string(), step: z15.enum(["installing", "restarting"]) }),
565
620
  /** Nothing was changed. `detail`: the useful end of npm's output; `command`: what the person can run instead. */
566
- z14.object({ type: z14.literal("update.failed"), version: z14.string(), reason: UpdateFailureSchema, detail: z14.string().optional(), command: z14.string().optional() }),
621
+ z15.object({ type: z15.literal("update.failed"), version: z15.string(), reason: UpdateFailureSchema, detail: z15.string().optional(), command: z15.string().optional() }),
567
622
  /** The shared machines list (spec 2026-10-14 §5), sent to local clients only. */
568
- z14.object({ type: z14.literal("machines"), machines: MachinesSchema }),
623
+ z15.object({ type: z15.literal("machines"), machines: MachinesSchema }),
569
624
  /** The local daemon's projects and folder rules (spec 2026-10-16 build §5), sent to local clients only. */
570
- z14.object({ type: z14.literal("projects"), projects: ProjectsSchema }),
625
+ z15.object({ type: z15.literal("projects"), projects: ProjectsSchema }),
571
626
  /** The island's settings shared by the local fronts; null until a front sets them (the island then sends its own). Local clients only. */
572
- z14.object({ type: z14.literal("island"), prefs: IslandPrefsSchema.nullable() }),
627
+ z15.object({ type: z15.literal("island"), prefs: IslandPrefsSchema.nullable() }),
628
+ /** Mobile access (lot 3), after the island in each snapshot and on every change. Local clients only. */
629
+ z15.object({ type: z15.literal("mobile"), state: MobileStateSchema }),
573
630
  /** The daemon's immediate answer to a `session.message`: delivered (`accepted`), held for later (`queued`), left to Claude's own SendMessage (`native`), or refused with a `reason`. */
574
- z14.object({ type: z14.literal("message.ack"), rid: z14.string(), outcome: z14.enum(["accepted", "queued", "native", "refused"]), reason: z14.string().optional() }),
575
- z14.object({
576
- type: z14.literal("error"),
577
- message: z14.string(),
578
- id: z14.string().optional(),
631
+ z15.object({ type: z15.literal("message.ack"), rid: z15.string(), outcome: z15.enum(["accepted", "queued", "native", "refused"]), reason: z15.string().optional() }),
632
+ z15.object({
633
+ type: z15.literal("error"),
634
+ message: z15.string(),
635
+ id: z15.string().optional(),
579
636
  /** Le type du message client refusé. */
580
- request: z14.string().optional(),
637
+ request: z15.string().optional(),
581
638
  /** Ce que le front doit reconnaître au-delà du texte (NOTE_NOT_TYPED) ; une chaîne libre, qu'un front plus ancien ignore. */
582
- code: z14.string().optional()
639
+ code: z15.string().optional()
583
640
  })
584
641
  ]);
585
642
  var MESSAGE_MAX = 2e4;
586
643
  var SUMMARY_MAX = 500;
587
- var ClientMessageSchema = z14.discriminatedUnion("type", [
588
- z14.object({ type: z14.literal("chat.route"), qid: z14.string().min(1).max(100), text: z14.string().min(1).max(2e4), tool: ToolSchema.optional(), context: RoutingContextSchema }),
644
+ var ClientMessageSchema = z15.discriminatedUnion("type", [
645
+ z15.object({ type: z15.literal("chat.route"), qid: z15.string().min(1).max(100), text: z15.string().min(1).max(2e4), tool: ToolSchema.optional(), context: RoutingContextSchema }),
589
646
  DocumentReadSchema,
590
647
  ReposInspectSchema,
591
648
  ReposScanSchema,
@@ -594,99 +651,112 @@ var ClientMessageSchema = z14.discriminatedUnion("type", [
594
651
  * free one otherwise), `prompt` the agent's first instruction, `continue` resumes the folder's last conversation.
595
652
  * With `qid` it is the chat card of that turn: the daemon launches the turn's proposal and ignores the other fields
596
653
  * but `rows`, `model` and `effort`. */
597
- z14.object({
598
- type: z14.literal("session.launch"),
654
+ z15.object({
655
+ type: z15.literal("session.launch"),
599
656
  tool: ToolSchema,
600
- cwd: z14.string().min(1),
601
- name: z14.string().optional(),
602
- prompt: z14.string().optional(),
603
- continue: z14.boolean().optional(),
604
- qid: z14.string().min(1).optional(),
657
+ cwd: z15.string().min(1),
658
+ name: z15.string().optional(),
659
+ prompt: z15.string().optional(),
660
+ continue: z15.boolean().optional(),
661
+ qid: z15.string().min(1).optional(),
605
662
  /** With `qid`: the card's rows to launch (#24), indexes into the turn's `proposals`, each once; the first row without it. */
606
- rows: z14.array(z14.number().int().min(0).max(MAX_PROPOSALS - 1)).min(1).max(MAX_PROPOSALS).refine((r) => new Set(r).size === r.length, "a row twice").optional(),
663
+ rows: z15.array(z15.number().int().min(0).max(MAX_PROPOSALS - 1)).min(1).max(MAX_PROPOSALS).refine((r) => new Set(r).size === r.length, "a row twice").optional(),
607
664
  /** Folders in addition to `cwd` (Claude: `--add-dir`); resolved like `cwd`. */
608
- dirs: z14.array(z14.string().min(1)).max(20).optional(),
665
+ dirs: z15.array(z15.string().min(1)).max(20).optional(),
609
666
  /** The agent's model and effort (#58), the agent's own default when left out; with `qid`, for every row launched. */
610
667
  model: ModelIdSchema.optional(),
611
668
  effort: EffortSchema.optional(),
669
+ /** OMP only: its approval mode, `always-ask` when left out. */
670
+ approval: OmpApprovalSchema.optional(),
612
671
  /** Routing by the fleet, removed before the message reaches the daemon. */
613
- machineId: z14.string().optional()
672
+ machineId: z15.string().optional()
614
673
  }),
615
674
  /** The launch form's folder browser: list a folder's subfolders (home when no `path`), or make `name` inside `parent`
616
675
  * and list it. Both are answered with a `folder.listing`. */
617
- z14.object({
618
- type: z14.literal("folder.list"),
619
- path: z14.string().optional(),
676
+ z15.object({
677
+ type: z15.literal("folder.list"),
678
+ path: z15.string().optional(),
620
679
  /** Routing by the fleet, removed before the message reaches the daemon. */
621
- machineId: z14.string().optional(),
680
+ machineId: z15.string().optional(),
622
681
  /** Copied into the `folder.listing` that answers; the fleet relays only that listing, from the machine asked. */
623
- requestId: z14.string().min(1).max(100).optional()
682
+ requestId: z15.string().min(1).max(100).optional()
624
683
  }),
625
- z14.object({
626
- type: z14.literal("folder.create"),
627
- parent: z14.string().min(1),
628
- name: z14.string().min(1),
684
+ z15.object({
685
+ type: z15.literal("folder.create"),
686
+ parent: z15.string().min(1),
687
+ name: z15.string().min(1),
629
688
  /** As for `folder.list`: routing by the fleet, and the request its `folder.listing` answers. */
630
- machineId: z14.string().optional(),
631
- requestId: z14.string().min(1).max(100).optional()
689
+ machineId: z15.string().optional(),
690
+ requestId: z15.string().min(1).max(100).optional()
632
691
  }),
633
692
  /** `steer`: OMP only, redirect the turn running now instead of queueing the text after it. */
634
693
  /** OMP's `ask`: the answers to every question of the dialog `requestId`, in order (`selected` labels, and/or free `text`); `cancel` declines it. */
635
- z14.object({ type: z14.literal("session.answer"), id: z14.string(), requestId: z14.string().min(1).max(200), answers: z14.array(z14.object({ id: z14.string(), selected: z14.array(z14.string().max(2e3)).max(100), text: z14.string().max(2e4).optional() })).max(50), cancel: z14.boolean().optional() }),
636
- z14.object({ type: z14.literal("session.reply"), id: z14.string(), text: z14.string().min(1), steer: z14.boolean().optional() }),
694
+ z15.object({ type: z15.literal("session.answer"), id: z15.string(), requestId: z15.string().min(1).max(200), answers: z15.array(z15.object({ id: z15.string(), selected: z15.array(z15.string().max(2e3)).max(100), text: z15.string().max(2e4).optional() })).max(50), cancel: z15.boolean().optional() }),
695
+ z15.object({ type: z15.literal("session.reply"), id: z15.string(), text: z15.string().min(1), steer: z15.boolean().optional() }),
637
696
  /** The chat card's instruction (spec 2026-10-12 relay §4): sent now if the session is free, else queued until it is. */
638
- z14.object({ type: z14.literal("session.relay"), qid: z14.string().min(1) }),
697
+ z15.object({ type: z15.literal("session.relay"), qid: z15.string().min(1) }),
639
698
  /** Takes the card's queued instruction back out of the queue. */
640
- z14.object({ type: z14.literal("session.relay.cancel"), qid: z14.string().min(1) }),
699
+ z15.object({ type: z15.literal("session.relay.cancel"), qid: z15.string().min(1) }),
641
700
  /** `always` and `note` only where the harness offers them (`offers`); a note goes with `deny`, for the agent to read. */
642
- z14.object({
643
- type: z14.literal("session.decide"),
644
- id: z14.string(),
701
+ z15.object({
702
+ type: z15.literal("session.decide"),
703
+ id: z15.string(),
645
704
  decision: DecisionSchema,
646
- note: z14.string().trim().min(1).max(NOTE_MAX).optional(),
705
+ note: z15.string().trim().min(1).max(NOTE_MAX).optional(),
647
706
  /** The request the user saw when deciding: refused (the request changed) unless it is still the pending one. Left out by older fronts and the island.
648
707
  * `key` (pendingKey) covers its whole content; older fronts send kind and text only. */
649
- expect: PendingSchema.pick({ kind: true, text: true }).extend({ key: z14.string().max(64).optional() }).optional()
708
+ expect: PendingSchema.pick({ kind: true, text: true }).extend({ key: z15.string().max(64).optional() }).optional()
650
709
  }),
651
- z14.object({ type: z14.literal("session.focus"), id: z14.string() }),
652
- z14.object({ type: z14.literal("session.kill"), id: z14.string() }),
710
+ z15.object({ type: z15.literal("session.focus"), id: z15.string() }),
711
+ z15.object({ type: z15.literal("session.kill"), id: z15.string() }),
653
712
  /** `restoreQueue` (OMP): also take back what waits in its queue, returned as `session.queue.restored` to put in the draft. */
654
- z14.object({ type: z14.literal("session.interrupt"), id: z14.string(), restoreQueue: z14.boolean().optional() }),
713
+ z15.object({ type: z15.literal("session.interrupt"), id: z15.string(), restoreQueue: z15.boolean().optional() }),
655
714
  /** Removes a finished (exited) or read-only (hook_only) session from the list; refused for a live launcher session. */
656
- z14.object({ type: z14.literal("session.dismiss"), id: z14.string() }),
715
+ z15.object({ type: z15.literal("session.dismiss"), id: z15.string() }),
657
716
  /** Puts a session in the archive: it leaves the live list but is kept. */
658
- z14.object({ type: z14.literal("session.archive"), id: z14.string() }),
717
+ z15.object({ type: z15.literal("session.archive"), id: z15.string() }),
659
718
  /** Takes an archived session back out of the archive, as it was. */
660
- z14.object({ type: z14.literal("session.restore"), id: z14.string() }),
719
+ z15.object({ type: z15.literal("session.restore"), id: z15.string() }),
661
720
  /** Starts the agent again on an archived session's conversation. */
662
- z14.object({ type: z14.literal("session.resume"), id: z14.string() }),
721
+ z15.object({ type: z15.literal("session.resume"), id: z15.string() }),
663
722
  /** Changes a live session's model and effort (#58, lot 2), `null` for the agent's own default: the daemon restarts the
664
723
  * agent on the same conversation with them, at once when the session is free, else at its next free moment. */
665
- z14.object({ type: z14.literal("session.model"), id: z14.string(), model: ModelIdSchema.nullable(), effort: EffortSchema.nullable() }),
724
+ z15.object({ type: z15.literal("session.model"), id: z15.string(), model: ModelIdSchema.nullable(), effort: EffortSchema.nullable() }),
666
725
  /** Deletes a session for good, archived or not. */
667
- z14.object({ type: z14.literal("session.delete"), id: z14.string() }),
726
+ z15.object({ type: z15.literal("session.delete"), id: z15.string() }),
668
727
  /** The user names the session's topic from the overlay (500 characters at most, the hook's prompt cap); an empty topic hands back to the automatic sources. */
669
- z14.object({ type: z14.literal("session.rename"), id: z14.string(), topic: z14.string().max(500) }),
670
- z14.object({ type: z14.literal("session.thread"), id: z14.string() }),
671
- z14.object({ type: z14.literal("session.register"), session: SessionSchema }),
728
+ z15.object({ type: z15.literal("session.rename"), id: z15.string(), topic: z15.string().max(500) }),
729
+ z15.object({ type: z15.literal("session.thread"), id: z15.string() }),
730
+ z15.object({ type: z15.literal("session.register"), session: SessionSchema }),
672
731
  /** `agent` and `cwd` describe the session when the daemon does not know it yet (a hook from an agent started without ledge). */
673
- z14.object({ type: z14.literal("session.event"), id: z14.string(), event: SessionEventSchema, agent: ToolSchema.optional(), cwd: z14.string().optional() }),
674
- z14.object({ type: z14.literal("session.unregister"), id: z14.string(), exitCode: z14.number().int().nullable() }),
675
- z14.object({ type: z14.literal("snapshot.request") }),
676
- z14.object({ type: z14.literal("chat.ask"), qid: z14.string().min(1), text: z14.string().min(1), tool: ToolSchema.optional() }),
677
- z14.object({ type: z14.literal("chat.cancel"), qid: z14.string().min(1) }),
732
+ z15.object({ type: z15.literal("session.event"), id: z15.string(), event: SessionEventSchema, agent: ToolSchema.optional(), cwd: z15.string().optional() }),
733
+ z15.object({ type: z15.literal("session.unregister"), id: z15.string(), exitCode: z15.number().int().nullable() }),
734
+ z15.object({ type: z15.literal("snapshot.request") }),
735
+ z15.object({ type: z15.literal("chat.ask"), qid: z15.string().min(1), text: z15.string().min(1), tool: ToolSchema.optional() }),
736
+ z15.object({ type: z15.literal("chat.cancel"), qid: z15.string().min(1) }),
678
737
  /** Starts a new conversation with Ledge: the day's turns are cleared and the next question opens a new harness session. */
679
- z14.object({ type: z14.literal("chat.reset") }),
738
+ z15.object({ type: z15.literal("chat.reset") }),
680
739
  /** Installs `@paradow/ledge@version` and restarts the service (spec 2026-10-12 auto-update §6). */
681
- z14.object({ type: z14.literal("update.daemon"), version: z14.string().regex(SEMVER) }),
740
+ z15.object({ type: z15.literal("update.daemon"), version: z15.string().regex(SEMVER) }),
682
741
  /** Replaces the daemon's machines list (first connected front wins); refused unless valid. */
683
- z14.object({ type: z14.literal("machines.set"), machines: MachinesSchema }),
742
+ z15.object({ type: z15.literal("machines.set"), machines: MachinesSchema }),
684
743
  /** Replaces the local daemon's projects and rules as a whole; refused unless valid. */
685
- z14.object({ type: z14.literal("projects.set"), projects: ProjectsSchema }),
744
+ z15.object({ type: z15.literal("projects.set"), projects: ProjectsSchema }),
686
745
  /** Replaces the island's settings as a whole; refused unless valid. */
687
- z14.object({ type: z14.literal("island.set"), prefs: IslandPrefsSchema }),
746
+ z15.object({ type: z15.literal("island.set"), prefs: IslandPrefsSchema }),
747
+ /** Mobile access (lot 3), local fronts only. A changed `relayUrl` drops the relay session. */
748
+ z15.object({ type: z15.literal("mobile.set"), enabled: z15.boolean(), relayUrl: RelayUrlSchema }),
749
+ z15.object({ type: z15.literal("mobile.signin") }),
750
+ z15.object({ type: z15.literal("mobile.signin.cancel") }),
751
+ z15.object({ type: z15.literal("mobile.signout") }),
752
+ /** While the link is offline: try again now. */
753
+ z15.object({ type: z15.literal("mobile.retry") }),
754
+ z15.object({ type: z15.literal("mobile.pairing.approve"), id: z15.string().min(1).max(100) }),
755
+ z15.object({ type: z15.literal("mobile.pairing.deny"), id: z15.string().min(1).max(100) }),
756
+ /** `id`: the device's fingerprint. */
757
+ z15.object({ type: z15.literal("mobile.device.revoke"), id: z15.string().min(1).max(100) }),
688
758
  /** One session writing to another by Ledge name (`to` may end in ` [ref]`); `intercept` (the Claude hook): answer `native` unless `to` is a live non-Claude session. */
689
- z14.object({ type: z14.literal("session.message"), rid: z14.string().min(1), from: z14.string().min(1), to: z14.string().min(1), text: z14.string().min(1).max(MESSAGE_MAX), summary: z14.string().max(SUMMARY_MAX).optional(), intercept: z14.boolean().optional() })
759
+ z15.object({ type: z15.literal("session.message"), rid: z15.string().min(1), from: z15.string().min(1), to: z15.string().min(1), text: z15.string().min(1).max(MESSAGE_MAX), summary: z15.string().max(SUMMARY_MAX).optional(), intercept: z15.boolean().optional() })
690
760
  ]);
691
761
  function parseClientMessage(raw) {
692
762
  return ClientMessageSchema.parse(raw);
@@ -778,14 +848,14 @@ function pickName(taken, preferred) {
778
848
  }
779
849
 
780
850
  // ../protocol/src/proposal.ts
781
- import { z as z15 } from "zod";
782
- var ProposalBlockSchema = z15.object({ cwd: z15.string().trim().min(1), prompt: z15.string().optional() });
783
- var RelayBlockSchema = z15.object({ session: z15.string().trim().min(1), text: z15.string().trim().min(1) });
784
- var ProjectBlockSchema = z15.object({
785
- name: z15.string().trim().min(1).max(80),
786
- description: z15.string().max(500).optional(),
787
- repos: z15.array(z15.object({ path: z15.string().min(1).max(4096), why: z15.string().trim().min(1).max(200) })).min(1).max(50),
788
- addTo: z15.string().trim().max(80).optional()
851
+ import { z as z16 } from "zod";
852
+ var ProposalBlockSchema = z16.object({ cwd: z16.string().trim().min(1), prompt: z16.string().optional() });
853
+ var RelayBlockSchema = z16.object({ session: z16.string().trim().min(1), text: z16.string().trim().min(1) });
854
+ var ProjectBlockSchema = z16.object({
855
+ name: z16.string().trim().min(1).max(80),
856
+ description: z16.string().max(500).optional(),
857
+ repos: z16.array(z16.object({ path: z16.string().min(1).max(4096), why: z16.string().trim().min(1).max(200) })).min(1).max(50),
858
+ addTo: z16.string().trim().max(80).optional()
789
859
  });
790
860
  var BLOCK = /```ledge-(session|relay|project)[ \t]*\n?([\s\S]*?)(?:```|$)/g;
791
861
  function extractProposal(answer) {
@@ -833,6 +903,300 @@ function sessionTitle(s) {
833
903
  return `${s.name} \xB7 ${s.topic || s.project}`;
834
904
  }
835
905
 
906
+ // ../protocol/src/relay.ts
907
+ import { z as z17 } from "zod";
908
+ var SEALED_MAX = 1048576;
909
+ var RelayPeerSchema = z17.union([z17.literal("pc"), z17.string().regex(/^d_[A-Za-z0-9_-]{16,40}$/)]);
910
+ var RelayClientMessageSchema = z17.object({
911
+ type: z17.literal("send"),
912
+ to: RelayPeerSchema,
913
+ body: z17.string().max(SEALED_MAX)
914
+ });
915
+ var RelayServerMessageSchema = z17.discriminatedUnion("type", [
916
+ z17.object({ type: z17.literal("welcome"), accountId: z17.string(), self: RelayPeerSchema, online: z17.array(RelayPeerSchema) }),
917
+ z17.object({ type: z17.literal("deliver"), from: RelayPeerSchema, body: z17.string() }),
918
+ z17.object({ type: z17.literal("presence"), peer: RelayPeerSchema, online: z17.boolean() }),
919
+ /**
920
+ * A frame was refused; `to` names the target when it is known.
921
+ * Contract: an error concerning a sealed body (`offline`, `too_large`, or a socket closed with 4008
922
+ * `rate_limited`) means that body was not delivered, so the sequence on that channel is lost: the
923
+ * sender must re-handshake with that peer before sending to it again.
924
+ */
925
+ z17.object({
926
+ type: z17.literal("error"),
927
+ code: z17.enum(["bad_frame", "too_large", "bad_target", "offline", "rate_limited"]),
928
+ to: RelayPeerSchema.optional()
929
+ })
930
+ ]);
931
+ var PAIR_LABEL_MAX = 40;
932
+ var KeyB64 = z17.string().regex(/^[A-Za-z0-9_-]{87}$/);
933
+ var Nonce = z17.string().regex(/^[A-Za-z0-9_-]{43}$/);
934
+ var Fingerprint = z17.string().regex(/^[A-Za-z0-9_-]{22}$/);
935
+ var SealedFrameSchema = z17.discriminatedUnion("k", [
936
+ /** `e` is the ephemeral key, `f` the fingerprint of the sender's static key. */
937
+ z17.object({ k: z17.literal("hello"), e: KeyB64, f: Fingerprint }),
938
+ z17.object({ k: z17.literal("msg"), s: z17.string() }),
939
+ z17.object({ k: z17.literal("pair_commit"), c: Nonce, label: z17.string().trim().min(1).max(PAIR_LABEL_MAX) }),
940
+ z17.object({ k: z17.literal("pair_offer"), pc: KeyB64, n: Nonce }),
941
+ z17.object({ k: z17.literal("pair_reveal"), d: KeyB64, n: Nonce }),
942
+ z17.object({ k: z17.literal("pair_ok") }),
943
+ z17.object({ k: z17.literal("pair_no") }),
944
+ z17.object({ k: z17.literal("denied") }),
945
+ z17.object({ k: z17.literal("rekey") })
946
+ ]);
947
+
948
+ // ../protocol/src/relayAuth.ts
949
+ import { z as z18 } from "zod";
950
+ var RELAY_CLOSE_SIGNED_OUT = 4001;
951
+ var DeviceStartResponseSchema = z18.object({
952
+ deviceCode: z18.string().min(1),
953
+ userCode: z18.string().min(1),
954
+ verificationUri: z18.string().min(1),
955
+ expiresIn: z18.number().int().positive(),
956
+ interval: z18.number().int().positive()
957
+ });
958
+ var DevicePollResponseSchema = z18.discriminatedUnion("status", [
959
+ z18.object({ status: z18.literal("pending") }),
960
+ z18.object({ status: z18.literal("slow_down"), interval: z18.number().int().positive() }),
961
+ /** `token` is the relay session token, shown this once only. */
962
+ z18.object({ status: z18.literal("ok"), token: z18.string().min(1), accountId: z18.string().min(1), login: z18.string().min(1) }),
963
+ z18.object({ status: z18.literal("expired") }),
964
+ z18.object({ status: z18.literal("denied") }),
965
+ z18.object({ status: z18.literal("not_allowed") })
966
+ ]);
967
+ var DeviceRevokeResponseSchema = z18.object({ revoked: z18.number().int().min(0) });
968
+ var MeResponseSchema = z18.object({
969
+ accountId: z18.string().min(1),
970
+ login: z18.string().min(1),
971
+ peer: RelayPeerSchema,
972
+ kind: z18.enum(["pc", "device"])
973
+ });
974
+
975
+ // ../protocol/src/phone.ts
976
+ import { z as z20 } from "zod";
977
+
978
+ // ../protocol/src/push.ts
979
+ import { z as z19 } from "zod";
980
+ var B64U = z19.string().regex(/^[A-Za-z0-9_-]*$/);
981
+ var PUSH_HOSTS = ["*.push.apple.com", "fcm.googleapis.com", "updates.push.services.mozilla.com", "*.notify.windows.com"];
982
+ function pushEndpointAllowed(url) {
983
+ let u;
984
+ try {
985
+ u = new URL(url);
986
+ } catch {
987
+ return false;
988
+ }
989
+ if (u.protocol !== "https:" || u.port !== "" || u.username !== "" || u.password !== "") return false;
990
+ const host = u.hostname.toLowerCase();
991
+ return PUSH_HOSTS.some((h) => {
992
+ if (!h.startsWith("*.")) return host === h;
993
+ const suffix = h.slice(1);
994
+ return host.length > suffix.length && host.endsWith(suffix);
995
+ });
996
+ }
997
+ var PushSendSchema = z19.object({
998
+ endpoint: z19.string().url().max(1024),
999
+ body: B64U.max(5462),
1000
+ ttl: z19.number().int().min(0).max(86400),
1001
+ urgency: z19.enum(["normal", "high"]),
1002
+ topic: z19.string().regex(/^[A-Za-z0-9_-]{1,32}$/)
1003
+ });
1004
+ var PushSendResultSchema = z19.object({
1005
+ result: z19.enum(["sent", "gone", "failed"]),
1006
+ status: z19.number().int().optional()
1007
+ });
1008
+ var PushSubscriptionSchema = z19.object({
1009
+ endpoint: z19.string().url().max(1024).refine(pushEndpointAllowed),
1010
+ keys: z19.object({ p256dh: B64U.length(87), auth: B64U.length(22) })
1011
+ });
1012
+ var PushPayloadSchema = z19.object({
1013
+ v: z19.literal(1),
1014
+ kind: z19.enum(["request", "question", "finished", "chat"]),
1015
+ title: z19.string().max(80),
1016
+ body: z19.string().max(120),
1017
+ url: z19.string().regex(/^\/#\/[A-Za-z0-9/%._~-]*$/).max(300),
1018
+ badge: z19.number().int().min(0).max(999),
1019
+ tag: z19.string().regex(/^[A-Za-z0-9_-]{1,32}$/)
1020
+ });
1021
+
1022
+ // ../protocol/src/phone.ts
1023
+ var PHONE_PROTOCOL = 1;
1024
+ var PAD_TIERS = [1024, 4096, 16384, 65536, 262144, 655360];
1025
+ var PLAIN_MAX = 655360;
1026
+ var utf8 = new TextEncoder();
1027
+ function padPlain(json) {
1028
+ const bytes = utf8.encode(json).length;
1029
+ if (bytes > PLAIN_MAX) return null;
1030
+ const tier = PAD_TIERS.find((t) => t >= bytes);
1031
+ if (tier === void 0) return null;
1032
+ return json + " ".repeat(tier - bytes);
1033
+ }
1034
+ var THREAD_ITEMS_MAX = 200;
1035
+ var THREAD_BUDGET = 524288;
1036
+ var THREAD_TEXT_MAX = 8192;
1037
+ var TOOL_TEXT_MAX = 2048;
1038
+ var PENDING_BODY_MAX = 65536;
1039
+ var SNAPSHOT_BODY_MAX = 2048;
1040
+ var RiskSchema = z20.enum(["low", "high"]);
1041
+ var RiskReasonSchema = z20.enum(["question", "read_only_command", "edit_inside_folder", "pushes", "destroys", "deploys", "publishes", "pipes_to_shell", "network", "writes_outside_folder", "sensitive_path", "unsafe_flag", "shell_syntax", "script", "plan", "unknown"]);
1042
+ var OperationIdSchema = z20.string().uuid();
1043
+ var OperationStatusSchema = z20.enum(["received", "applied", "rejected", "failed"]);
1044
+ var OperationReasonSchema = z20.enum([...RefusalCodeSchema.options, "untypable", "confirm_required", "conflict", "assertion_invalid", "assertion_expired"]);
1045
+ var CapabilitiesSchema = z20.object({
1046
+ /** A question can be answered in free text. */
1047
+ freeTextAnswer: z20.boolean(),
1048
+ /** The session's accept/deny can be typed; absent = true. */
1049
+ decide: z20.boolean().optional(),
1050
+ /** OMP's dialog questions can be answered from the phone; absent = false. */
1051
+ questions: z20.boolean().optional()
1052
+ });
1053
+ var UNTYPABLE = /[\u0000-\u001f\u007f-\u009f\u200b\u200e\u200f\u2028-\u202e\u2066-\u2069\ufeff]/;
1054
+ var CapabilitiesByToolSchema = z20.object({ claude: CapabilitiesSchema, codex: CapabilitiesSchema, cursor: CapabilitiesSchema, omp: CapabilitiesSchema });
1055
+ var PhonePendingSchema = PendingSchema.extend({ key: z20.string().max(64), risk: RiskSchema, riskReason: RiskReasonSchema, cut: z20.literal(true).optional() });
1056
+ var PhoneSessionSchema = SessionSchema.pick({
1057
+ id: true,
1058
+ name: true,
1059
+ tool: true,
1060
+ project: true,
1061
+ title: true,
1062
+ topic: true,
1063
+ repo: true,
1064
+ branch: true,
1065
+ worktree: true,
1066
+ state: true,
1067
+ helpers: true,
1068
+ lastReply: true,
1069
+ startedAt: true,
1070
+ lastActivityAt: true
1071
+ }).extend({ pending: PhonePendingSchema.optional(), unread: z20.boolean() });
1072
+ var B64U2 = z20.string().regex(/^[A-Za-z0-9_-]*$/);
1073
+ var CredentialSchema = z20.object({
1074
+ id: B64U2.max(1366),
1075
+ publicKey: B64U2.max(2048),
1076
+ alg: z20.union([z20.literal(-7), z20.literal(-257)]),
1077
+ clientDataJSON: B64U2.max(4096),
1078
+ authenticatorData: B64U2.max(4096),
1079
+ transports: z20.array(z20.string().max(20)).max(8).optional()
1080
+ });
1081
+ var AssertionSchema = z20.object({
1082
+ id: B64U2.max(1366),
1083
+ clientDataJSON: B64U2.max(4096),
1084
+ authenticatorData: B64U2.max(4096),
1085
+ signature: B64U2.max(1024)
1086
+ });
1087
+ var ConfirmPurposeSchema = z20.enum(["register", "decide", "launch"]);
1088
+ var PhoneChatStepSchema = ChatStepSchema.pick({ kind: true, id: true, name: true }).strict();
1089
+ var PhoneChatProposalSchema = z20.object({
1090
+ project: z20.string().max(80),
1091
+ tool: ToolSchema,
1092
+ prompt: z20.string().max(TOOL_TEXT_MAX).optional(),
1093
+ launchedId: z20.string().optional(),
1094
+ failed: z20.literal(true).optional()
1095
+ }).strict();
1096
+ var PhoneChatRelaySchema = z20.object({
1097
+ id: z20.string().min(1),
1098
+ name: z20.string(),
1099
+ tool: ToolSchema,
1100
+ text: z20.string().max(TOOL_TEXT_MAX),
1101
+ status: z20.enum(["queued", "sent"]).optional(),
1102
+ sentAt: z20.string().datetime().optional(),
1103
+ failed: z20.literal(true).optional()
1104
+ }).strict();
1105
+ var ChatErrorCodeSchema = z20.enum(["busy", "timeout", "cancelled", "failed"]);
1106
+ var PhoneChatTurnSchema = z20.object({
1107
+ qid: OperationIdSchema,
1108
+ question: z20.string().max(MESSAGE_MAX),
1109
+ askedAt: z20.string().datetime(),
1110
+ steps: z20.array(PhoneChatStepSchema).max(50),
1111
+ answer: z20.string().max(THREAD_TEXT_MAX).optional(),
1112
+ answeredAt: z20.string().datetime().optional(),
1113
+ error: ChatErrorCodeSchema.optional(),
1114
+ proposals: z20.array(PhoneChatProposalSchema).max(MAX_PROPOSALS).optional(),
1115
+ relay: PhoneChatRelaySchema.optional(),
1116
+ cut: z20.literal(true).optional()
1117
+ }).strict();
1118
+ var CHAT_HISTORY_BUDGET = 393216;
1119
+ var sessionId = z20.string().min(1).max(100);
1120
+ var PhoneExpectSchema = PendingSchema.pick({ kind: true, text: true }).extend({ key: z20.string().min(1).max(64) });
1121
+ var PhoneClientMessageSchema = z20.discriminatedUnion("type", [
1122
+ z20.object({ type: z20.literal("snapshot.request") }),
1123
+ z20.object({ type: z20.literal("session.thread"), id: sessionId }),
1124
+ /** The session's request with its whole body (up to `PENDING_BODY_MAX`). */
1125
+ z20.object({ type: z20.literal("session.pending"), id: sessionId }),
1126
+ /** The session whose terminal the phone shows; null when it shows none. */
1127
+ z20.object({ type: z20.literal("session.watch"), id: sessionId.nullable() }),
1128
+ z20.object({ type: z20.literal("session.read"), id: sessionId, at: z20.string().datetime() }),
1129
+ /** Asks for a challenge. One object for both purposes: the gateway refuses a `decide` without `id` and `expect`. */
1130
+ z20.object({ type: z20.literal("confirm.prepare"), purpose: ConfirmPurposeSchema, id: sessionId.optional(), expect: PhoneExpectSchema.optional(), qid: OperationIdSchema.optional() }),
1131
+ z20.object({ type: z20.literal("credential.register"), op: OperationIdSchema, credential: CredentialSchema }),
1132
+ z20.object({
1133
+ type: z20.literal("session.decide"),
1134
+ op: OperationIdSchema,
1135
+ id: sessionId,
1136
+ decision: DecisionSchema,
1137
+ note: z20.string().trim().min(1).max(NOTE_MAX).optional(),
1138
+ /** The request the phone showed: refused unless it is still the pending one. */
1139
+ expect: PhoneExpectSchema,
1140
+ /** The device check over the challenge of `confirm.prepare`; required by the daemon for a high-risk accept. */
1141
+ assertion: AssertionSchema.optional()
1142
+ }),
1143
+ z20.object({
1144
+ type: z20.literal("session.reply"),
1145
+ op: OperationIdSchema,
1146
+ id: sessionId,
1147
+ text: z20.string().min(1).max(MESSAGE_MAX),
1148
+ /** The request the reply answers, or null when the phone showed none: refused if the session's request is no longer that one. */
1149
+ expect: PhoneExpectSchema.nullable(),
1150
+ assertion: AssertionSchema.optional()
1151
+ }),
1152
+ /** OMP's `ask`: the answers to every question of the dialog `requestId`, in order; `cancel` declines it. Bounds as the local `session.answer`. */
1153
+ z20.object({
1154
+ type: z20.literal("session.answer"),
1155
+ op: OperationIdSchema,
1156
+ id: sessionId,
1157
+ requestId: z20.string().min(1).max(200),
1158
+ answers: z20.array(z20.object({ id: z20.string().max(200), selected: z20.array(z20.string().max(2e3)).max(100), text: z20.string().max(2e4).optional() })).max(50),
1159
+ cancel: z20.literal(true).optional(),
1160
+ expect: PhoneExpectSchema
1161
+ }),
1162
+ z20.object({ type: z20.literal("session.interrupt"), op: OperationIdSchema, id: sessionId }),
1163
+ /** The phone drops its passkey from the daemon. */
1164
+ z20.object({ type: z20.literal("device.forget"), op: OperationIdSchema }),
1165
+ /** The phone's push subscription, with the VAPID key it was made with (so a key change is seen). */
1166
+ z20.object({ type: z20.literal("push.subscribe"), op: OperationIdSchema, subscription: PushSubscriptionSchema, key: B64U2.length(87) }),
1167
+ z20.object({ type: z20.literal("push.unsubscribe"), op: OperationIdSchema }),
1168
+ /** The phone's own chat with Ledge. */
1169
+ z20.object({ type: z20.literal("chat.ask"), qid: OperationIdSchema, text: z20.string().trim().min(1).max(MESSAGE_MAX) }),
1170
+ z20.object({ type: z20.literal("chat.cancel"), qid: OperationIdSchema }),
1171
+ z20.object({ type: z20.literal("chat.reset") }),
1172
+ z20.object({ type: z20.literal("chat.history") }),
1173
+ z20.object({ type: z20.literal("chat.relay"), op: OperationIdSchema, qid: OperationIdSchema }),
1174
+ z20.object({ type: z20.literal("chat.launch"), op: OperationIdSchema, qid: OperationIdSchema, rows: z20.array(z20.number().int().min(0).max(MAX_PROPOSALS - 1)).min(1).max(MAX_PROPOSALS).refine((r) => new Set(r).size === r.length, "a row twice").optional(), assertion: AssertionSchema.optional() }),
1175
+ /** Whether the phone's app is in front: a device that is visible gets no push. */
1176
+ z20.object({ type: z20.literal("presence"), visible: z20.boolean() })
1177
+ ]);
1178
+ var PhoneServerMessageSchema = z20.discriminatedUnion("type", [
1179
+ z20.object({ type: z20.literal("snapshot"), protocol: z20.literal(PHONE_PROTOCOL), sessions: z20.array(PhoneSessionSchema), capabilities: CapabilitiesByToolSchema, daemon: z20.object({ version: z20.string(), name: z20.string().max(40), boot: z20.string().uuid().optional() }), device: z20.object({ confirm: z20.boolean(), push: z20.enum(["on", "off", "stale", "unavailable"]).optional(), chat: z20.literal(true).optional() }).optional() }),
1180
+ z20.object({ type: z20.literal("confirm.challenge"), purpose: ConfirmPurposeSchema, challenge: B64U2.max(64), rpId: z20.string().max(253), credentialId: B64U2.max(1366).optional(), expiresInMs: z20.number().int().positive() }),
1181
+ z20.object({ type: z20.literal("session.updated"), session: PhoneSessionSchema }),
1182
+ z20.object({ type: z20.literal("session.removed"), id: z20.string() }),
1183
+ /** `pending.cut` is set when even this body was shortened. */
1184
+ z20.object({ type: z20.literal("session.pending"), id: z20.string(), pending: PhonePendingSchema }),
1185
+ z20.object({ type: z20.literal("session.preview"), id: z20.string(), lines: z20.array(z20.string()) }),
1186
+ /** `cut`: the ids of the items shortened; `more`: older items were left out. */
1187
+ z20.object({ type: z20.literal("session.thread"), id: z20.string(), items: z20.array(ThreadItemSchema), cut: z20.array(z20.string()), more: z20.boolean() }),
1188
+ z20.object({ type: z20.literal("session.thread.item"), id: z20.string(), item: ThreadItemSchema, cut: z20.literal(true).optional() }),
1189
+ z20.object({ type: z20.literal("operation.result"), op: OperationIdSchema, status: OperationStatusSchema, reason: OperationReasonSchema.optional() }),
1190
+ z20.object({ type: z20.literal("chat.history"), turns: z20.array(PhoneChatTurnSchema).max(50) }),
1191
+ z20.object({ type: z20.literal("chat.step"), qid: OperationIdSchema, step: PhoneChatStepSchema }),
1192
+ /** The whole text so far, not an increment. */
1193
+ z20.object({ type: z20.literal("chat.delta"), qid: OperationIdSchema, text: z20.string().max(THREAD_TEXT_MAX) }),
1194
+ z20.object({ type: z20.literal("chat.done"), qid: OperationIdSchema, turn: PhoneChatTurnSchema }),
1195
+ z20.object({ type: z20.literal("chat.error"), qid: OperationIdSchema, code: ChatErrorCodeSchema }),
1196
+ /** Never carries free text. */
1197
+ z20.object({ type: z20.literal("error"), code: z20.enum(["invalid", "unknown_session", "too_large"]), request: z20.string().max(40).optional() })
1198
+ ]);
1199
+
836
1200
  // src/auth.ts
837
1201
  import { createHash, randomBytes, timingSafeEqual } from "crypto";
838
1202
  import { chmod, link, mkdir, readFile, unlink, writeFile } from "fs/promises";
@@ -953,6 +1317,7 @@ function readDaemonToken() {
953
1317
  }
954
1318
 
955
1319
  export {
1320
+ ToolSchema,
956
1321
  SessionSchema,
957
1322
  backendOf,
958
1323
  isArchived,
@@ -971,7 +1336,10 @@ export {
971
1336
  ModelIdSchema,
972
1337
  EffortSchema,
973
1338
  RouteResultSchema,
1339
+ DEFAULT_RELAY_URL,
1340
+ MobileConfigSchema,
974
1341
  PROTOCOL_PORT,
1342
+ RefusalCodeSchema,
975
1343
  RecentSchema,
976
1344
  MESSAGE_MAX,
977
1345
  SUMMARY_MAX,
@@ -982,8 +1350,30 @@ export {
982
1350
  parseProposalBlock,
983
1351
  parseRelayBlock,
984
1352
  parseProjectBlock,
1353
+ TOPIC_MAX,
985
1354
  cleanTopic,
986
1355
  sessionTitle,
1356
+ RelayServerMessageSchema,
1357
+ PAIR_LABEL_MAX,
1358
+ SealedFrameSchema,
1359
+ RELAY_CLOSE_SIGNED_OUT,
1360
+ DeviceStartResponseSchema,
1361
+ DevicePollResponseSchema,
1362
+ pushEndpointAllowed,
1363
+ PushSendResultSchema,
1364
+ PushPayloadSchema,
1365
+ PHONE_PROTOCOL,
1366
+ padPlain,
1367
+ THREAD_ITEMS_MAX,
1368
+ THREAD_BUDGET,
1369
+ THREAD_TEXT_MAX,
1370
+ TOOL_TEXT_MAX,
1371
+ PENDING_BODY_MAX,
1372
+ SNAPSHOT_BODY_MAX,
1373
+ OperationIdSchema,
1374
+ UNTYPABLE,
1375
+ CHAT_HISTORY_BUDGET,
1376
+ PhoneClientMessageSchema,
987
1377
  hostAllowed,
988
1378
  requestAuthorized,
989
1379
  selectDaemonProtocol,