@paradow/ledge 0.3.3 → 0.5.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,25 +1,27 @@
1
1
  import {
2
2
  QUESTION_TOOLS
3
- } from "./chunk-ZPF5I3LS.js";
3
+ } from "./chunk-SMA4Z3N2.js";
4
4
  import {
5
5
  CODEX_ENV_VAR,
6
- CODEX_HOOK_VAR,
6
+ CODEX_HOOK_VAR
7
+ } from "./chunk-U4A4XKDM.js";
8
+ import {
7
9
  log
8
- } from "./chunk-OUIDBDIH.js";
9
- import "./chunk-6ZTCETRG.js";
10
+ } from "./chunk-P2SHPHAN.js";
10
11
  import {
11
12
  connect
12
- } from "./chunk-W5XAZ5IS.js";
13
- import "./chunk-6GMDFIGB.js";
13
+ } from "./chunk-5HVWJTTJ.js";
14
+ import "./chunk-Y4ODVTTN.js";
14
15
 
15
16
  // src/cli/hook.ts
16
17
  import { readFileSync, openSync, fstatSync, readSync, closeSync } from "fs";
17
18
  import { basename, join } from "path";
18
19
  var str = (v) => typeof v === "string" ? v : void 0;
19
20
  var PROMPT_MAX = 500;
21
+ var handBack = (t) => t.startsWith('<agent-message from="');
20
22
  var promptText = (p) => {
21
23
  const t = str(p.prompt)?.trim();
22
- return t ? { text: t.slice(0, PROMPT_MAX) } : {};
24
+ return t ? { text: t.slice(0, handBack(t) ? INPUT_MAX : PROMPT_MAX) } : {};
23
25
  };
24
26
  function describeTool(tool, input) {
25
27
  const i = input && typeof input === "object" ? input : {};
@@ -40,6 +42,29 @@ function lastReply(p) {
40
42
 
41
43
  [\u2026]` : text;
42
44
  }
45
+ var INPUT_MAX = 8e3;
46
+ function trimInput(input) {
47
+ if (!input || typeof input !== "object" || Array.isArray(input)) return void 0;
48
+ const trim = (v) => typeof v === "string" ? v.slice(0, INPUT_MAX) : Array.isArray(v) ? v.map(trim) : v && typeof v === "object" ? Object.fromEntries(Object.entries(v).map(([k, x]) => [k, trim(x)])) : v;
49
+ return trim(input);
50
+ }
51
+ function toolOutput(response) {
52
+ if (response === null || response === void 0) return void 0;
53
+ let text;
54
+ if (typeof response === "string") text = response;
55
+ else if (Array.isArray(response)) text = response.map((b) => str(b?.text) ?? "").filter(Boolean).join("\n");
56
+ else if (typeof response === "object" && ("stdout" in response || "stderr" in response)) {
57
+ const r = response;
58
+ text = [str(r.stdout), str(r.stderr)].filter(Boolean).join("\n");
59
+ } else {
60
+ try {
61
+ text = JSON.stringify(response);
62
+ } catch {
63
+ text = String(response);
64
+ }
65
+ }
66
+ return text.slice(0, REPLY_MAX);
67
+ }
43
68
  var NON_TERMINAL_END_REASONS = /* @__PURE__ */ new Set(["clear", "resume"]);
44
69
  function mapClaudeHook(eventName, payload, at) {
45
70
  return mapHook("claude", eventName, payload, at);
@@ -89,18 +114,29 @@ function mapEvent(eventName, p, at, launched) {
89
114
  const tool = str(p.tool_name);
90
115
  const agentId = str(p.agent_id) || void 0;
91
116
  const base = { at, source: "hook", ...agentId ? { helperId: agentId } : {} };
117
+ const toolUseId = str(p.tool_use_id) || void 0;
118
+ const toolInput = trimInput(p.tool_input);
119
+ const call = { ...toolUseId ? { toolUseId } : {}, ...toolInput ? { toolInput } : {} };
120
+ const transcript = str(p.transcript_path) ? { transcriptPath: str(p.transcript_path) } : {};
121
+ const duration = typeof p.duration_ms === "number" && p.duration_ms >= 0 ? { durationMs: p.duration_ms } : {};
92
122
  switch (eventName) {
93
123
  case "SubagentStart":
94
124
  case "SubagentStop": {
95
125
  if (!agentId) return null;
96
126
  const type = str(p.agent_type) || void 0;
97
127
  const task = eventName === "SubagentStart" ? (str(p.description) || str(p.prompt))?.split("\n")[0] : void 0;
98
- return { name: eventName, ...base, ...type ? { tool: type } : {}, ...task ? { text: task } : {} };
128
+ const report = eventName === "SubagentStop" ? lastReply(p) : void 0;
129
+ const agentTranscript = eventName === "SubagentStop" && str(p.agent_transcript_path) ? { transcriptPath: str(p.agent_transcript_path) } : {};
130
+ return { name: eventName, ...base, ...type ? { tool: type } : {}, ...task ? { text: task } : {}, ...report ? { text: report } : {}, ...agentTranscript };
131
+ }
132
+ case "PostToolUse": {
133
+ const output = toolOutput(p.tool_response);
134
+ return { name: "PostToolUse", ...base, tool, ...call, ...output !== void 0 ? { output } : {}, ...duration };
135
+ }
136
+ case "PostToolUseFailure": {
137
+ const error = str(p.error) || void 0;
138
+ return { name: "PostToolUseFailure", ...base, tool, ...call, ...error ? { error } : {}, ...duration, ...p.is_interrupt === true ? { interrupt: true } : {} };
99
139
  }
100
- case "PostToolUse":
101
- return { name: "PostToolUse", ...base, tool };
102
- case "PostToolUseFailure":
103
- return { name: "PostToolUseFailure", ...base, tool, ...p.is_interrupt === true ? { interrupt: true } : {} };
104
140
  case "StopFailure": {
105
141
  const error = str(p.error);
106
142
  const details = str(p.error_details);
@@ -113,25 +149,27 @@ function mapEvent(eventName, p, at, launched) {
113
149
  case "SessionStart":
114
150
  return { name: "SessionStart", ...base };
115
151
  case "UserPromptSubmit":
116
- return { name: "UserPromptSubmit", ...base, ...promptText(p) };
152
+ return { name: "UserPromptSubmit", ...base, ...promptText(p), ...transcript };
117
153
  case "Stop": {
118
154
  const text = lastReply(p);
119
- return { name: "Stop", ...base, ...text ? { text } : {} };
155
+ return { name: "Stop", ...base, ...text ? { text } : {}, ...transcript };
120
156
  }
121
157
  case "SessionEnd":
122
158
  if (launched) return null;
123
159
  if (NON_TERMINAL_END_REASONS.has(str(p.reason) ?? "")) return null;
124
160
  return { name: "SessionEnd", ...base, exitCode: null };
125
161
  case "PermissionRequest":
126
- return { name: "PermissionRequest", ...base, tool, text: describeTool(tool, p.tool_input) };
162
+ return { name: "PermissionRequest", ...base, tool, text: describeTool(tool, p.tool_input), ...call, ...transcript };
127
163
  case "PreToolUse": {
128
164
  if (tool && QUESTION_TOOLS.has(tool)) {
129
165
  const qs = p.tool_input?.questions ?? [];
130
166
  const q = qs[0];
131
- const options = (q?.options ?? []).map((o) => str(o.label) ?? "").filter(Boolean);
132
- return { name: "PreToolUse", ...base, tool, text: str(q?.question) ?? "", options };
167
+ const opts = (q?.options ?? []).filter((o) => !!str(o.label));
168
+ const options = opts.map((o) => str(o.label));
169
+ const optionDetails = opts.map((o) => str(o.description)?.trim() ?? "");
170
+ return { name: "PreToolUse", ...base, tool, text: str(q?.question) ?? "", options, ...optionDetails.some(Boolean) ? { optionDetails } : {}, ...call, ...transcript };
133
171
  }
134
- return { name: "PreToolUse", ...base, tool, text: describeTool(tool, p.tool_input) };
172
+ return { name: "PreToolUse", ...base, tool, text: describeTool(tool, p.tool_input), ...call, ...transcript };
135
173
  }
136
174
  case "Notification": {
137
175
  const type = str(p.notification_type) ?? "";
@@ -219,6 +257,7 @@ function mapCursorHelperEvent(eventName, p, at, helperId) {
219
257
  }
220
258
  function mapCursorEvent(eventName, p, at, launched, transcript) {
221
259
  const { name: tool, input } = unwrapTool(str(p.tool_name), p.tool_input);
260
+ const toolInput = p.tool_input ? trimInput(input) : void 0;
222
261
  const base = { at, source: "hook" };
223
262
  switch (eventName) {
224
263
  case "sessionStart":
@@ -234,9 +273,9 @@ function mapCursorEvent(eventName, p, at, launched, transcript) {
234
273
  return { name: "PreToolUse", ...base, tool, text: describeCursorTool(input) };
235
274
  }
236
275
  case "postToolUse":
237
- return { name: "PostToolUse", ...base, tool };
276
+ return { name: "PostToolUse", ...base, tool, ...toolInput ? { toolInput } : {} };
238
277
  case "postToolUseFailure":
239
- return { name: "PostToolUseFailure", ...base, tool, ...p.is_interrupt === true ? { interrupt: true } : {} };
278
+ return { name: "PostToolUseFailure", ...base, tool, ...toolInput ? { toolInput } : {}, ...p.is_interrupt === true ? { interrupt: true } : {} };
240
279
  case "stop":
241
280
  if (str(p.status) === "error") return { name: "StopFailure", ...base };
242
281
  return { name: "Stop", ...base };
@@ -276,6 +315,7 @@ async function runHook(eventName, stdin, opts = {}) {
276
315
  }
277
316
  }
278
317
  export {
318
+ INPUT_MAX,
279
319
  REPLY_MAX,
280
320
  codexLedgeId,
281
321
  cursorReply,
@@ -284,5 +324,7 @@ export {
284
324
  mapHook,
285
325
  readTranscript,
286
326
  runHook,
327
+ toolOutput,
328
+ trimInput,
287
329
  underCodexDaemon
288
330
  };
package/dist/cli/index.js CHANGED
@@ -8,6 +8,7 @@ ledge hook [codex|cursor] <Event> receive a hook (rea
8
8
  ledge setup install hooks and ~/.ledge
9
9
  ledge uninstall [--purge] remove hooks, service and link (--purge: ~/.ledge too)
10
10
  ledge status list sessions
11
+ ledge bridge exchange Ledge messages over SSH stdio
11
12
  ledge --version print the installed version`;
12
13
  async function hookMain(args) {
13
14
  const tool = args[0] === "codex" || args[0] === "cursor" ? args[0] : void 0;
@@ -15,7 +16,7 @@ async function hookMain(args) {
15
16
  try {
16
17
  const chunks = [];
17
18
  for await (const c of process.stdin) chunks.push(c);
18
- const { runHook } = await import("./hook-HP23ZISG.js");
19
+ const { runHook } = await import("./hook-KFSK75QH.js");
19
20
  await runHook(event, Buffer.concat(chunks).toString("utf8"), { tool });
20
21
  } catch {
21
22
  }
@@ -25,7 +26,7 @@ async function main(argv) {
25
26
  const [cmd, ...rest] = argv;
26
27
  switch (cmd) {
27
28
  case "daemon": {
28
- const { startDaemon } = await import("./daemon-D5YDWUHB.js");
29
+ const { startDaemon } = await import("./daemon-6UFCIFQF.js");
29
30
  await startDaemon();
30
31
  await new Promise(() => {
31
32
  });
@@ -33,34 +34,39 @@ async function main(argv) {
33
34
  }
34
35
  case "--version":
35
36
  case "version": {
36
- const { LEDGE_VERSION } = await import("./version-3DLAAFBS.js");
37
+ const { LEDGE_VERSION } = await import("./version-IWGIZJJV.js");
37
38
  console.log(LEDGE_VERSION);
38
39
  return 0;
39
40
  }
40
41
  case "setup": {
41
- const { runSetup } = await import("./setup-KUWSQLVG.js");
42
+ const { runSetup } = await import("./setup-EXA6OG5W.js");
42
43
  for (const l of await runSetup()) console.log(l);
43
44
  return 0;
44
45
  }
45
46
  case "uninstall": {
46
- const { runUninstall } = await import("./uninstall-5GHUYN7P.js");
47
+ const { runUninstall } = await import("./uninstall-34TVL5BP.js");
47
48
  const r = await runUninstall({ purge: rest.includes("--purge") });
48
49
  for (const l of r.lines) console.log(l);
49
50
  return r.ok ? 0 : 1;
50
51
  }
51
52
  case "status": {
52
- const { printStatus } = await import("./status-IW2FESH4.js");
53
+ const { printStatus } = await import("./status-I7FHW6ZM.js");
53
54
  console.log(await printStatus());
54
55
  return 0;
55
56
  }
57
+ case "bridge": {
58
+ const { bridge } = await import("./bridge-R6DKTSYF.js");
59
+ await bridge();
60
+ return 0;
61
+ }
56
62
  case "claude":
57
63
  case "codex":
58
64
  case "cursor": {
59
- const { launch } = await import("./launch-DFB54ANN.js");
65
+ const { launch } = await import("./launch-YKYPT6BU.js");
60
66
  return launch(cmd, rest);
61
67
  }
62
68
  case "attach": {
63
- const { attach } = await import("./attach-YBGTDBQI.js");
69
+ const { attach } = await import("./attach-CUKFTN63.js");
64
70
  return attach(rest[0] ?? "");
65
71
  }
66
72
  default:
@@ -5,15 +5,15 @@ import {
5
5
  ensureDaemon,
6
6
  launch,
7
7
  parseLaunchArgs
8
- } from "./chunk-Y44XZY6I.js";
9
- import "./chunk-7ICDC2LK.js";
8
+ } from "./chunk-QHDPDZOJ.js";
10
9
  import {
11
10
  agentEnv,
12
11
  exitFile
13
- } from "./chunk-OUIDBDIH.js";
14
- import "./chunk-6ZTCETRG.js";
15
- import "./chunk-W5XAZ5IS.js";
16
- import "./chunk-6GMDFIGB.js";
12
+ } from "./chunk-U4A4XKDM.js";
13
+ import "./chunk-XZMLFV6H.js";
14
+ import "./chunk-P2SHPHAN.js";
15
+ import "./chunk-5HVWJTTJ.js";
16
+ import "./chunk-Y4ODVTTN.js";
17
17
  export {
18
18
  DaemonError,
19
19
  agentEnv,
@@ -13,8 +13,9 @@ import {
13
13
  runSetup,
14
14
  stopDetachedDaemon,
15
15
  systemdUnit
16
- } from "./chunk-XDRYMOVF.js";
17
- import "./chunk-6ZTCETRG.js";
16
+ } from "./chunk-HGNMPXXR.js";
17
+ import "./chunk-XZMLFV6H.js";
18
+ import "./chunk-P2SHPHAN.js";
18
19
  export {
19
20
  CLAUDE_EVENTS,
20
21
  CODEX_EVENTS,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  connect
3
- } from "./chunk-W5XAZ5IS.js";
4
- import "./chunk-6GMDFIGB.js";
3
+ } from "./chunk-5HVWJTTJ.js";
4
+ import "./chunk-Y4ODVTTN.js";
5
5
 
6
6
  // src/cli/status.ts
7
7
  async function printStatus(opts = {}) {
@@ -2,10 +2,11 @@ import {
2
2
  SERVICE,
3
3
  execRunner,
4
4
  stopDetachedDaemon
5
- } from "./chunk-XDRYMOVF.js";
5
+ } from "./chunk-HGNMPXXR.js";
6
+ import "./chunk-XZMLFV6H.js";
6
7
  import {
7
8
  paths
8
- } from "./chunk-6ZTCETRG.js";
9
+ } from "./chunk-P2SHPHAN.js";
9
10
 
10
11
  // src/cli/uninstall.ts
11
12
  import { readFile, writeFile, rename, rm, lstat, realpath } from "fs/promises";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  LEDGE_VERSION
3
- } from "./chunk-HDZVQH6J.js";
3
+ } from "./chunk-IUBEGKBP.js";
4
4
  export {
5
5
  LEDGE_VERSION
6
6
  };
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@paradow/ledge",
3
- "version": "0.3.3",
3
+ "version": "0.5.0",
4
4
  "description": "Follow and answer your WSL agent sessions (Claude Code, Codex, Cursor Agent) from the Ledge island on Windows.",
5
5
  "type": "module",
6
6
  "bin": { "ledge": "dist/cli/index.js" },
7
7
  "files": ["dist"],
8
8
  "engines": { "node": ">=22" },
9
- "repository": { "type": "git", "url": "git+https://github.com/Paradow-dev/ledge.git", "directory": "packages/ledge" },
10
- "homepage": "https://github.com/Paradow-dev/ledge#readme",
9
+ "repository": { "type": "git", "url": "git+https://github.com/Lyssaen/ledge.git", "directory": "packages/ledge" },
10
+ "homepage": "https://github.com/Lyssaen/ledge#readme",
11
11
  "license": "UNLICENSED",
12
12
  "publishConfig": { "access": "public", "provenance": true },
13
13
  "scripts": {
@@ -1,306 +0,0 @@
1
- // ../protocol/src/messages.ts
2
- import { z as z4 } from "zod";
3
-
4
- // ../protocol/src/session.ts
5
- import { z } from "zod";
6
- var ToolSchema = z.enum(["claude", "codex", "cursor"]);
7
- var SessionStateSchema = z.enum(["starting", "working", "waiting_input", "waiting_permission", "idle", "exited", "error"]);
8
- var PendingSchema = z.object({
9
- kind: z.enum(["question", "permission", "plan", "diff"]),
10
- text: z.string(),
11
- tool: z.string().optional(),
12
- options: z.array(z.string()).optional(),
13
- source: z.enum(["hook", "pane"]),
14
- /** Set when the request comes from a helper (subagent): it is routed through its parent. */
15
- helperId: z.string().optional(),
16
- /** For a helper-routed request: the state the parent returns to once it is answered (main thread may stop meanwhile). */
17
- parentState: SessionStateSchema.optional()
18
- });
19
- var ReplySchema = z.object({
20
- text: z.string(),
21
- at: z.string().datetime()
22
- });
23
- var HelperSchema = z.object({
24
- id: z.string(),
25
- kind: z.string(),
26
- task: z.string(),
27
- state: SessionStateSchema
28
- });
29
- var TopicSourceSchema = z.enum(["user", "pane", "prompt"]);
30
- var SessionSchema = z.object({
31
- id: z.string().min(1),
32
- name: z.string().min(1),
33
- tool: ToolSchema,
34
- cwd: z.string(),
35
- project: z.string(),
36
- title: z.string(),
37
- /** One line saying what the session is about (spec 2026-10-11 §3). */
38
- topic: z.string().optional(),
39
- topicSource: TopicSourceSchema.optional(),
40
- /** The repository (its main worktree's folder name and absolute path), the checked-out branch, and whether `cwd` is a linked worktree (§4). */
41
- repo: z.string().optional(),
42
- repoPath: z.string().optional(),
43
- branch: z.string().optional(),
44
- worktree: z.boolean().optional(),
45
- tmuxSession: z.string().nullable(),
46
- state: SessionStateSchema,
47
- pending: PendingSchema.optional(),
48
- helpers: z.array(HelperSchema).default([]),
49
- lastReply: ReplySchema.optional(),
50
- startedAt: z.string().datetime(),
51
- lastActivityAt: z.string().datetime(),
52
- lastHookAt: z.string().datetime().optional(),
53
- exitCode: z.number().int().nullable().optional(),
54
- exitedAt: z.string().datetime().optional(),
55
- lastError: z.string().optional(),
56
- source: z.enum(["launcher", "hook_only"])
57
- });
58
-
59
- // ../protocol/src/chat.ts
60
- import { z as z2 } from "zod";
61
- var ChatStepSchema = z2.object({
62
- kind: z2.enum(["sessions", "pane", "reply", "git", "repo"]),
63
- id: z2.string().optional(),
64
- name: z2.string().optional(),
65
- path: z2.string().optional()
66
- });
67
- var ChatTurnSchema = z2.object({
68
- qid: z2.string().min(1),
69
- question: z2.string(),
70
- askedAt: z2.string().datetime(),
71
- steps: z2.array(ChatStepSchema),
72
- answer: z2.string().optional(),
73
- answeredAt: z2.string().datetime().optional(),
74
- error: z2.string().optional()
75
- });
76
-
77
- // ../protocol/src/documents.ts
78
- import { z as z3 } from "zod";
79
- var DOCUMENT_MAX_BYTES = 1048576;
80
- var path = z3.string().min(1).max(4096);
81
- var requestId = z3.string().min(1);
82
- var DocumentReadSchema = z3.object({
83
- type: z3.literal("document.read"),
84
- requestId,
85
- path,
86
- sessionId: z3.string().min(1).optional(),
87
- basePath: path.optional()
88
- });
89
- var DocumentContentSchema = z3.object({
90
- type: z3.literal("document.content"),
91
- requestId,
92
- path,
93
- text: z3.string(),
94
- bytes: z3.number().int().nonnegative(),
95
- modifiedAt: z3.string().datetime()
96
- });
97
- var DocumentErrorCodeSchema = z3.enum([
98
- "not_found",
99
- "not_file",
100
- "unsupported_path",
101
- "unsupported_type",
102
- "permission_denied",
103
- "too_large",
104
- "invalid_utf8",
105
- "ambiguous_path",
106
- "session_missing",
107
- "read_failed"
108
- ]);
109
- var DocumentErrorSchema = z3.object({
110
- type: z3.literal("document.error"),
111
- requestId,
112
- path,
113
- code: DocumentErrorCodeSchema
114
- });
115
-
116
- // ../protocol/src/messages.ts
117
- var PROTOCOL_PORT = 47831;
118
- var SessionEventSchema = z4.object({
119
- name: z4.enum(["SessionStart", "UserPromptSubmit", "PreToolUse", "PermissionRequest", "Notification", "Stop", "SessionEnd", "PaneChanged", "PaneGone", "PanePrompt", "PaneIdle", "InjectFailed", "Decided", "PostToolUse", "PostToolUseFailure", "StopFailure", "SubagentStart", "SubagentStop", "PaneTitle", "Renamed"]),
120
- at: z4.string().datetime(),
121
- source: z4.enum(["hook", "pane", "daemon"]),
122
- tool: z4.string().optional(),
123
- text: z4.string().optional(),
124
- options: z4.array(z4.string()).optional(),
125
- kind: z4.enum(["question", "permission", "plan", "diff"]).optional(),
126
- exitCode: z4.number().int().nullable().optional(),
127
- helperId: z4.string().optional(),
128
- /** The helper's description, once known (Claude Code: its `.meta.json`, written after SubagentStart). */
129
- task: z4.string().optional(),
130
- /** PostToolUseFailure: the user interrupted the turn (Esc); Claude then waits for input with no Stop. */
131
- interrupt: z4.boolean().optional()
132
- });
133
- var RecentSchema = z4.object({ cwd: z4.string().min(1), project: z4.string().min(1), lastAt: z4.string().datetime() });
134
- var SEMVER = /^\d+\.\d+\.\d+$/;
135
- var DaemonInfoSchema = z4.object({ version: z4.string(), install: z4.enum(["npm", "git", "unknown"]), systemd: z4.boolean(), manual: z4.string().optional() });
136
- var UpdateFailureSchema = z4.enum(["git-install", "no-systemd", "sessions-at-risk", "npm-failed", "busy"]);
137
- var ServerMessageSchema = z4.discriminatedUnion("type", [
138
- DocumentContentSchema,
139
- DocumentErrorSchema,
140
- /** `recents`: the folders of past sessions, most recent first. `launcher`: the command that runs `ledge` inside WSL
141
- * (`[node, cli]`), so the overlay can attach a terminal to a session with `<launcher> attach <id>`. `daemon`: absent
142
- * from daemons older than the auto-update. */
143
- z4.object({ type: z4.literal("snapshot"), sessions: z4.array(SessionSchema), recents: z4.array(RecentSchema).optional(), launcher: z4.array(z4.string()).optional(), daemon: DaemonInfoSchema.optional() }),
144
- z4.object({ type: z4.literal("session.updated"), session: SessionSchema }),
145
- z4.object({ type: z4.literal("session.removed"), id: z4.string() }),
146
- z4.object({ type: z4.literal("session.preview"), id: z4.string(), lines: z4.array(z4.string()) }),
147
- /** Answer to the requester of a `session.launch`: the session exists (its `session.updated` is broadcast as usual). */
148
- z4.object({ type: z4.literal("session.launched"), id: z4.string() }),
149
- z4.object({ type: z4.literal("recents"), recents: z4.array(RecentSchema) }),
150
- /** Answer to the requester of a `folder.list` or `folder.create`: `path` resolved (`~` expanded), `parent` null at the
151
- * root, `entries` its subfolders by name (hidden ones left out). A failure keeps the asked path and says why in `error`,
152
- * never in an `error` message, which the launch form reads as a failed launch. */
153
- z4.object({ type: z4.literal("folder.listing"), path: z4.string(), parent: z4.string().nullable(), entries: z4.array(z4.string()), error: z4.string().optional() }),
154
- /** 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. */
155
- z4.object({ type: z4.literal("chat.step"), qid: z4.string(), step: ChatStepSchema }),
156
- z4.object({ type: z4.literal("chat.delta"), qid: z4.string(), text: z4.string() }),
157
- z4.object({ type: z4.literal("chat.done"), qid: z4.string(), text: z4.string(), at: z4.string().datetime() }),
158
- z4.object({ type: z4.literal("chat.error"), qid: z4.string(), message: z4.string() }),
159
- z4.object({ type: z4.literal("chat.history"), turns: z4.array(ChatTurnSchema) }),
160
- /** The daemon updating itself: npm is running, then the service restarts (the socket closes right after). */
161
- z4.object({ type: z4.literal("update.progress"), version: z4.string(), step: z4.enum(["installing", "restarting"]) }),
162
- /** Nothing was changed. `detail`: the useful end of npm's output; `command`: what the person can run instead. */
163
- z4.object({ type: z4.literal("update.failed"), version: z4.string(), reason: UpdateFailureSchema, detail: z4.string().optional(), command: z4.string().optional() }),
164
- z4.object({ type: z4.literal("error"), message: z4.string(), id: z4.string().optional() })
165
- ]);
166
- var ClientMessageSchema = z4.discriminatedUnion("type", [
167
- DocumentReadSchema,
168
- /** The overlay starts a session: `cwd` is a WSL path (`~` allowed), `name` a preferred bot name (the daemon picks a
169
- * free one otherwise), `prompt` the agent's first instruction, `continue` resumes the folder's last conversation. */
170
- z4.object({ type: z4.literal("session.launch"), tool: ToolSchema, cwd: z4.string().min(1), name: z4.string().optional(), prompt: z4.string().optional(), continue: z4.boolean().optional() }),
171
- /** The launch form's folder browser: list a folder's subfolders (home when no `path`), or make `name` inside `parent`
172
- * and list it. Both are answered with a `folder.listing`. */
173
- z4.object({ type: z4.literal("folder.list"), path: z4.string().optional() }),
174
- z4.object({ type: z4.literal("folder.create"), parent: z4.string().min(1), name: z4.string().min(1) }),
175
- z4.object({ type: z4.literal("session.reply"), id: z4.string(), text: z4.string().min(1) }),
176
- z4.object({ type: z4.literal("session.decide"), id: z4.string(), decision: z4.enum(["accept", "deny"]) }),
177
- z4.object({ type: z4.literal("session.focus"), id: z4.string() }),
178
- z4.object({ type: z4.literal("session.kill"), id: z4.string() }),
179
- z4.object({ type: z4.literal("session.interrupt"), id: z4.string() }),
180
- /** Removes a finished (exited) or read-only (hook_only) session from the list; refused for a live launcher session. */
181
- z4.object({ type: z4.literal("session.dismiss"), id: z4.string() }),
182
- /** 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. */
183
- z4.object({ type: z4.literal("session.rename"), id: z4.string(), topic: z4.string().max(500) }),
184
- z4.object({ type: z4.literal("session.register"), session: SessionSchema }),
185
- /** `agent` and `cwd` describe the session when the daemon does not know it yet (a hook from an agent started without ledge). */
186
- z4.object({ type: z4.literal("session.event"), id: z4.string(), event: SessionEventSchema, agent: ToolSchema.optional(), cwd: z4.string().optional() }),
187
- z4.object({ type: z4.literal("session.unregister"), id: z4.string(), exitCode: z4.number().int().nullable() }),
188
- z4.object({ type: z4.literal("snapshot.request") }),
189
- z4.object({ type: z4.literal("chat.ask"), qid: z4.string().min(1), text: z4.string().min(1), tool: ToolSchema.optional() }),
190
- z4.object({ type: z4.literal("chat.cancel"), qid: z4.string().min(1) }),
191
- /** Starts a new conversation with Ledge: the day's turns are cleared and the next question opens a new harness session. */
192
- z4.object({ type: z4.literal("chat.reset") }),
193
- /** Installs `@paradow/ledge@version` and restarts the service (spec 2026-10-12 auto-update §6). */
194
- z4.object({ type: z4.literal("update.daemon"), version: z4.string().regex(SEMVER) })
195
- ]);
196
- function parseClientMessage(raw) {
197
- return ClientMessageSchema.parse(raw);
198
- }
199
- function parseServerMessage(raw) {
200
- return ServerMessageSchema.parse(raw);
201
- }
202
-
203
- // ../protocol/src/names.ts
204
- var NAMES = [
205
- "Otto",
206
- "Pim",
207
- "Nova",
208
- "Bix",
209
- "Lu",
210
- "Max",
211
- "Ida",
212
- "Rex",
213
- "Zoe",
214
- "Kit",
215
- "Ari",
216
- "Bo",
217
- "Cal",
218
- "Dee",
219
- "Eli",
220
- "Fay",
221
- "Gus",
222
- "Hal",
223
- "Ivy",
224
- "Jax",
225
- "Kai",
226
- "Lio",
227
- "Mo",
228
- "Nia",
229
- "Oz",
230
- "Pax",
231
- "Quin",
232
- "Rae",
233
- "Sol",
234
- "Tad",
235
- "Uma",
236
- "Vic",
237
- "Wyn",
238
- "Xen",
239
- "Yan",
240
- "Zia",
241
- "Abe",
242
- "Bea",
243
- "Cy",
244
- "Dot",
245
- "Eva",
246
- "Fin",
247
- "Gil",
248
- "Hen",
249
- "Iro",
250
- "Jo",
251
- "Kip",
252
- "Lex",
253
- "Mia",
254
- "Ned",
255
- "Ola",
256
- "Pat",
257
- "Rio",
258
- "Sam",
259
- "Tia",
260
- "Ulf",
261
- "Val",
262
- "Wes",
263
- "Yui",
264
- "Zed",
265
- "Ash",
266
- "Bry",
267
- "Cleo",
268
- "Dax"
269
- ];
270
- var cap = (s) => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
271
- function pickName(taken, preferred) {
272
- const used = new Set(taken);
273
- const free = (n) => !used.has(n);
274
- const wanted = preferred?.trim();
275
- if (wanted) {
276
- const base = cap(wanted);
277
- if (free(base)) return base;
278
- for (let i = 2; ; i++) if (free(`${base}-${i}`)) return `${base}-${i}`;
279
- }
280
- const first = NAMES.find(free);
281
- if (first) return first;
282
- for (let i = 2; ; i++) if (free(`${NAMES[0]}-${i}`)) return `${NAMES[0]}-${i}`;
283
- }
284
-
285
- // ../protocol/src/topic.ts
286
- var TOPIC_MAX = 80;
287
- function cleanTopic(text) {
288
- const line = text.split(/\r?\n/).map((l) => l.trim()).find((l) => l.length > 0) ?? "";
289
- const flat = line.replace(/\s+/g, " ");
290
- return flat.length > TOPIC_MAX ? `${flat.slice(0, TOPIC_MAX - 1).trimEnd()}\u2026` : flat;
291
- }
292
- function sessionTitle(s) {
293
- return `${s.name} \xB7 ${s.topic || s.project}`;
294
- }
295
-
296
- export {
297
- DOCUMENT_MAX_BYTES,
298
- SessionSchema,
299
- PROTOCOL_PORT,
300
- RecentSchema,
301
- parseClientMessage,
302
- parseServerMessage,
303
- pickName,
304
- cleanTopic,
305
- sessionTitle
306
- };
@@ -1,6 +0,0 @@
1
- // src/version.ts
2
- var LEDGE_VERSION = true ? "0.3.3" : "0.0.0-dev";
3
-
4
- export {
5
- LEDGE_VERSION
6
- };