@paradow/ledge 0.34.0 → 0.36.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.
- package/dist/cli/index.js +40782 -39
- package/dist/cli/reduceWorker.js +2216 -0
- package/package.json +4 -1
- package/dist/cli/action-K42H5BDA.js +0 -27
- package/dist/cli/attach-KEE4A5YP.js +0 -87
- package/dist/cli/bridge-X327AZY4.js +0 -70
- package/dist/cli/chunk-25RVT5MR.js +0 -623
- package/dist/cli/chunk-3OJCWJAA.js +0 -144
- package/dist/cli/chunk-4OYVM4MT.js +0 -49
- package/dist/cli/chunk-4TCSADFR.js +0 -252
- package/dist/cli/chunk-53ISXX2R.js +0 -39
- package/dist/cli/chunk-54KF5RBY.js +0 -17
- package/dist/cli/chunk-7NHQGP6J.js +0 -95
- package/dist/cli/chunk-G7XPEVCG.js +0 -118
- package/dist/cli/chunk-IWY7DSSO.js +0 -2476
- package/dist/cli/chunk-KPDTYX7F.js +0 -60
- package/dist/cli/chunk-OLLQBK6N.js +0 -41
- package/dist/cli/chunk-RPH773TY.js +0 -6
- package/dist/cli/chunk-RX5CJ7YH.js +0 -43
- package/dist/cli/chunk-T547FLQN.js +0 -128
- package/dist/cli/chunk-U67V476Y.js +0 -35
- package/dist/cli/chunk-UJ2WBGLW.js +0 -305
- package/dist/cli/chunk-ULOMKEB6.js +0 -85
- package/dist/cli/chunk-V4XKKL6K.js +0 -205
- package/dist/cli/daemon-RO43S3S5.js +0 -11449
- package/dist/cli/decide-KZFYL3HY.js +0 -59
- package/dist/cli/handover-BWNMI2RA.js +0 -17
- package/dist/cli/hook-ZLWXI5EQ.js +0 -473
- package/dist/cli/launch-GCX7TXOH.js +0 -28
- package/dist/cli/send-SXZAPVRH.js +0 -19
- package/dist/cli/setup-TJ32FMVJ.js +0 -38
- package/dist/cli/status-YDVRES4E.js +0 -28
- package/dist/cli/task-TUSOVJK7.js +0 -19
- package/dist/cli/tui-TCVIRZCQ.js +0 -19348
- package/dist/cli/uninstall-QO7YMSLA.js +0 -162
- package/dist/cli/version-BAYSYWFP.js +0 -7
- package/dist/cli/who-6AZYSP7T.js +0 -12
|
@@ -1,623 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ledgeTitleRest
|
|
3
|
-
} from "./chunk-4TCSADFR.js";
|
|
4
|
-
import {
|
|
5
|
-
cleanTopic,
|
|
6
|
-
sessionTitle
|
|
7
|
-
} from "./chunk-IWY7DSSO.js";
|
|
8
|
-
|
|
9
|
-
// src/daemon/crossSession.ts
|
|
10
|
-
import { createHash } from "crypto";
|
|
11
|
-
var FIRST_LINE = /^Another Claude session sent a message:\s*\n/;
|
|
12
|
-
var BLOCK = /<cross-session-message\b([^>]*)>([\s\S]*?)(?:<\/cross-session-message>|(?=\n[ \t]*<cross-session-message\b)|$)/g;
|
|
13
|
-
var ATTR = /([\w-]+)="([^"]*)"/g;
|
|
14
|
-
function crossSession(text) {
|
|
15
|
-
const body = text.trimStart().replace(FIRST_LINE, "");
|
|
16
|
-
if (!body.startsWith("<cross-session-message")) return void 0;
|
|
17
|
-
const out = [];
|
|
18
|
-
for (const m of body.matchAll(BLOCK)) {
|
|
19
|
-
const attrs = new Map([...m[1].matchAll(ATTR)].map((a) => [a[1], a[2]]));
|
|
20
|
-
const from = attrs.get("from");
|
|
21
|
-
const name = attrs.get("from-name");
|
|
22
|
-
if (from) out.push({ from, ...name ? { name } : {}, text: m[2].trim() });
|
|
23
|
-
}
|
|
24
|
-
return out.length ? out : void 0;
|
|
25
|
-
}
|
|
26
|
-
var LEDGE_CLOSE = "</ledge-message>";
|
|
27
|
-
var LEDGE_BLOCK = /^<ledge-message\b([^>]*)>([\s\S]*?)(?:<\/ledge-message>|$)/;
|
|
28
|
-
var messageNote = (from, to) => `(message de la session ${from}, pas de l'utilisateur ; pour r\xE9pondre : ${to === "claude" ? `SendMessage vers ${from}` : `ledge send ${from} "\u2026"`})`;
|
|
29
|
-
var NOTE_SENDER = /^\(message de la session (.+?), pas de l'utilisateur ; /;
|
|
30
|
-
function stripNote(text) {
|
|
31
|
-
const body = text.trimStart();
|
|
32
|
-
const sender = NOTE_SENDER.exec(body)?.[1];
|
|
33
|
-
const lead = sender === void 0 ? void 0 : [messageNote(sender, "codex"), messageNote(sender, "claude")].find((n) => body.startsWith(n));
|
|
34
|
-
return lead ? { sender, text: body.slice(lead.length).trim() } : { text };
|
|
35
|
-
}
|
|
36
|
-
var ESCAPES = /\x1b(?:\[[0-?]*[ -/]*[@-~]|[\]PX^_][^\x07\x1b]*(?:\x07|\x1b\\)|[ -/]*[0-~])/g;
|
|
37
|
-
var CONTROLS = /[\x00-\x1f\x7f-\x9f]/g;
|
|
38
|
-
var typable = (text, newline) => text.replace(ESCAPES, "").replace(/\r\n|\r|\n/g, newline).replace(CONTROLS, " ");
|
|
39
|
-
var ledgeBody = (text) => typable(text.replaceAll("<", "\u2039"), " \u23CE ");
|
|
40
|
-
var INLINE_MAX = 800;
|
|
41
|
-
var INLINE_LINES = 8;
|
|
42
|
-
var POINTER_HEAD = 200;
|
|
43
|
-
var lineCount = (text) => text.split(/\r\n|\r|\n/).length;
|
|
44
|
-
var mailId = (rid, length = 6) => `m${createHash("sha1").update(rid).digest("hex").slice(0, length)}`;
|
|
45
|
-
var inline = (text) => text.length <= INLINE_MAX && lineCount(text) <= INLINE_LINES;
|
|
46
|
-
var attr = (s) => typable(s, " ").replace(/["<>]/g, "").trim();
|
|
47
|
-
function formatLedgeMessage(m) {
|
|
48
|
-
const from = attr(m.from);
|
|
49
|
-
const open = `<ledge-message from="${from}" tool="${m.tool}"${m.id ? ` id="${attr(m.id)}"` : ""}>${messageNote(from, m.to)} `;
|
|
50
|
-
if (!m.id || inline(m.text)) return `${open}${ledgeBody(m.text)}${LEDGE_CLOSE}`;
|
|
51
|
-
const first = m.summary?.trim() || m.text.trim().split(/\r\n|\r|\n/)[0].trim();
|
|
52
|
-
const head = ledgeBody(first.length > POINTER_HEAD ? `${first.slice(0, POINTER_HEAD)}\u2026` : first);
|
|
53
|
-
return `${open}${head} \u2026 [message de ${m.text.length} caract\xE8res, ${lineCount(m.text)} lignes : lis-le en entier avec ledge read ${attr(m.id)}]${LEDGE_CLOSE}`;
|
|
54
|
-
}
|
|
55
|
-
function ledgeMessages(text) {
|
|
56
|
-
const out = [];
|
|
57
|
-
let rest = text.trimStart();
|
|
58
|
-
let m;
|
|
59
|
-
while (rest && (m = LEDGE_BLOCK.exec(rest))) {
|
|
60
|
-
const attrs = new Map([...m[1].matchAll(ATTR)].map((a) => [a[1], a[2]]));
|
|
61
|
-
const from = attrs.get("from");
|
|
62
|
-
const tool = attrs.get("tool");
|
|
63
|
-
const id = attrs.get("id");
|
|
64
|
-
if (!from) break;
|
|
65
|
-
out.push({ from, ...tool ? { tool } : {}, ...id ? { id } : {}, text: stripNote(m[2]).text.trim() });
|
|
66
|
-
rest = rest.slice(m[0].length).trimStart();
|
|
67
|
-
}
|
|
68
|
-
return out.length ? out : void 0;
|
|
69
|
-
}
|
|
70
|
-
var NOTICE_BLOCK = /^<ledge-notice\b([^>]*)>([\s\S]*?)(?:<\/ledge-notice>|$)/;
|
|
71
|
-
var NOTICE_QUOTE = 80;
|
|
72
|
-
var NOTICE_REASON = " \xBB) : ";
|
|
73
|
-
function formatLedgeNotice(n) {
|
|
74
|
-
const to = attr(n.to);
|
|
75
|
-
const flat = typable(n.text, " ").trim();
|
|
76
|
-
const quote = ledgeBody(flat.length > NOTICE_QUOTE ? `${flat.slice(0, NOTICE_QUOTE)}\u2026` : flat);
|
|
77
|
-
return `<ledge-notice to="${to}">Ledge n'a pas pu remettre ton message \xE0 ${to} (\xAB ${quote}${NOTICE_REASON}${ledgeBody(n.reason)}</ledge-notice>`;
|
|
78
|
-
}
|
|
79
|
-
var TASK_BLOCK = /^<ledge-task\b([^>]*)>([\s\S]*?)(?:<\/ledge-task>|$)/;
|
|
80
|
-
function formatLedgeTask(t) {
|
|
81
|
-
return `<ledge-task id="${attr(t.id)}" from="${attr(t.from)}">${ledgeBody(t.text)}</ledge-task>`;
|
|
82
|
-
}
|
|
83
|
-
function ledgeTask(text) {
|
|
84
|
-
const m = TASK_BLOCK.exec(text.trimStart());
|
|
85
|
-
if (!m) return void 0;
|
|
86
|
-
const attrs = new Map([...m[1].matchAll(ATTR)].map((a) => [a[1], a[2]]));
|
|
87
|
-
const id = attrs.get("id");
|
|
88
|
-
const from = attrs.get("from");
|
|
89
|
-
return id ? { id, ...from ? { from } : {}, text: m[2].trim() } : void 0;
|
|
90
|
-
}
|
|
91
|
-
function ledgeNotice(text) {
|
|
92
|
-
const m = NOTICE_BLOCK.exec(text.trimStart());
|
|
93
|
-
if (!m) return void 0;
|
|
94
|
-
const to = new Map([...m[1].matchAll(ATTR)].map((a) => [a[1], a[2]])).get("to");
|
|
95
|
-
if (!to) return void 0;
|
|
96
|
-
const body = m[2].trim();
|
|
97
|
-
const at = body.lastIndexOf(NOTICE_REASON);
|
|
98
|
-
return { to, ...at >= 0 ? { reason: body.slice(at + NOTICE_REASON.length) } : {}, text: body };
|
|
99
|
-
}
|
|
100
|
-
var NOTICE_HEAD = /^\[Cross-session ([\w -]+?) notice\]/;
|
|
101
|
-
var NOTICE_SPLIT = /\n\s*(?=\[Cross-session [\w -]+? notice\])/;
|
|
102
|
-
var DELIVERY = [
|
|
103
|
-
[/\bheld by that session\b/, "held"],
|
|
104
|
-
[/\bdenied by the recipient\b/, "denied"],
|
|
105
|
-
[/\bnot approved before expiry\b/, "expired"],
|
|
106
|
-
[/\bapproved and released\b/, "delivered"],
|
|
107
|
-
[/\bdropped at that session's inbox\b/, "dropped"],
|
|
108
|
-
[/\b(?:was|were) refused\b/, "refused"]
|
|
109
|
-
];
|
|
110
|
-
function crossSessionNotices(text) {
|
|
111
|
-
const body = text.trim();
|
|
112
|
-
if (!NOTICE_HEAD.test(body)) return void 0;
|
|
113
|
-
return body.split(NOTICE_SPLIT).map((part) => notice(part.trim()));
|
|
114
|
-
}
|
|
115
|
-
function notice(text) {
|
|
116
|
-
const kind = NOTICE_HEAD.exec(text)[1];
|
|
117
|
-
if (kind === "idle") {
|
|
118
|
-
const peer = /"([^"]*)"/.exec(text)?.[1] ?? "";
|
|
119
|
-
if (/^\[[^\]]*\]\s*No idle signal arrived from "/.test(text)) return { kind: "expired", peer, text };
|
|
120
|
-
const exited = /\bhas exited(?: \(at ([^)]+)\))?/.exec(text);
|
|
121
|
-
if (exited) return { kind: "exited", peer, ...exited[1] ? { time: exited[1] } : {}, text };
|
|
122
|
-
const unavailable = /is not holding your idle subscription(?: \(([^)]*)\))?/.exec(text);
|
|
123
|
-
if (unavailable) return { kind: "unavailable", peer, ...unavailable[1] ? { reason: unavailable[1] } : {}, text };
|
|
124
|
-
const time = /\bfinished a turn at (.+?)\.(?=\s|$)/.exec(text)?.[1];
|
|
125
|
-
const summary = /Its harness reports:\s*«([\s\S]*?)»/.exec(text)?.[1]?.trim();
|
|
126
|
-
return { kind: "idle", peer, ...time ? { time } : {}, ...summary ? { summary } : {}, text };
|
|
127
|
-
}
|
|
128
|
-
if (kind === "delivery") {
|
|
129
|
-
const status = DELIVERY.find(([re]) => re.test(text))?.[1] ?? "unknown";
|
|
130
|
-
const many = /\b(\d+) of your messages\b/.exec(text);
|
|
131
|
-
const to = /\brecipients?:\s*([^;)]*)/.exec(text)?.[1];
|
|
132
|
-
const recipients = to ? to.split(",").map((r) => r.trim()).filter((r) => r && r !== "\u2026") : [];
|
|
133
|
-
const reason = status === "dropped" ? /NOT delivered \(([^)]*)\)/.exec(text)?.[1] : void 0;
|
|
134
|
-
return { kind: "delivery", status, count: many ? Number(many[1]) : 1, recipients, ...reason ? { reason } : {}, text };
|
|
135
|
-
}
|
|
136
|
-
return { kind: "other", text };
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// src/adapters/types.ts
|
|
140
|
-
var Refusal = class extends Error {
|
|
141
|
-
constructor(message, code) {
|
|
142
|
-
super(message);
|
|
143
|
-
this.code = code;
|
|
144
|
-
this.name = "Refusal";
|
|
145
|
-
}
|
|
146
|
-
code;
|
|
147
|
-
};
|
|
148
|
-
var NO_ALWAYS = 'no "always" option in this dialog, answer in the terminal';
|
|
149
|
-
var NO_NOTE = "no place for a note in this dialog, answer in the terminal";
|
|
150
|
-
var STALE = "the request changed, look again";
|
|
151
|
-
function assertDialogIs(pending, shown) {
|
|
152
|
-
if (shown === null) return;
|
|
153
|
-
const want = pending.kind === "diff" ? "permission" : pending.kind;
|
|
154
|
-
if (shown !== want) throw new Refusal(STALE, "stale");
|
|
155
|
-
}
|
|
156
|
-
var UNCHECKED = "the request on screen cannot be checked, answer in the terminal";
|
|
157
|
-
var COMMAND_TOOLS = /* @__PURE__ */ new Set(["Bash", "shell", "Shell"]);
|
|
158
|
-
var compact = (t) => t.replace(/\s+/g, "");
|
|
159
|
-
function sameQuestion(shown, asked) {
|
|
160
|
-
const screen = compact(shown), want = compact(asked);
|
|
161
|
-
return !want || screen === want || !!screen && (want.startsWith(screen) || screen.startsWith(want));
|
|
162
|
-
}
|
|
163
|
-
var EDGE = 4;
|
|
164
|
-
var WIDE = /[\u1100-\u115f\u2e80-\ua4cf\uac00-\ud7a3\uf900-\ufaff\ufe30-\ufe4f\uff00-\uff60\uffe0-\uffe6\u{1f300}-\u{1faff}\u{20000}-\u{3fffd}]/u;
|
|
165
|
-
var columns = (text) => [...text].reduce((n, c) => n + (WIDE.test(c) ? 2 : 1), 0);
|
|
166
|
-
function shownCommand(raw) {
|
|
167
|
-
const lines = raw.map((l) => l.trimEnd());
|
|
168
|
-
while (lines.length && !lines.at(-1).trim()) lines.pop();
|
|
169
|
-
while (lines.length && !lines[0].trim()) lines.shift();
|
|
170
|
-
return { lines: lines.map((l) => l.trim()), ends: lines.map(columns) };
|
|
171
|
-
}
|
|
172
|
-
function commandMismatch({ lines, ends }, asked, width) {
|
|
173
|
-
if (!lines.length || lines.at(-1).endsWith("\u2026") || lines.length > 1 && !width) return UNCHECKED;
|
|
174
|
-
const edge = (width ?? 0) - EDGE;
|
|
175
|
-
const a = asked.trim();
|
|
176
|
-
let at = 0;
|
|
177
|
-
for (let i = 0; i < lines.length; i++) {
|
|
178
|
-
if (!a.startsWith(lines[i], at)) return STALE;
|
|
179
|
-
at += lines[i].length;
|
|
180
|
-
if (i === lines.length - 1) break;
|
|
181
|
-
if (ends[i] + 1 + columns(lines[i + 1].split(/\s/)[0]) <= edge) {
|
|
182
|
-
const nl = /^[ \t]*\n[ \t]*/.exec(a.slice(at));
|
|
183
|
-
if (!nl) return STALE;
|
|
184
|
-
at += nl[0].length;
|
|
185
|
-
} else if (a[at] === " ") at++;
|
|
186
|
-
else if (ends[i] < edge) return STALE;
|
|
187
|
-
}
|
|
188
|
-
return at === a.length ? null : STALE;
|
|
189
|
-
}
|
|
190
|
-
function dialogMismatch(shown, pending, width) {
|
|
191
|
-
if (!shown) return UNCHECKED;
|
|
192
|
-
if (shown.kind !== (pending.kind === "diff" ? "permission" : pending.kind)) return STALE;
|
|
193
|
-
if (pending.kind === "question") return sameQuestion(shown.text, pending.text) ? null : STALE;
|
|
194
|
-
if (pending.source === "pane") return shown.text === pending.text ? null : STALE;
|
|
195
|
-
if (pending.tool !== void 0 && COMMAND_TOOLS.has(pending.tool)) return shown.command ? commandMismatch(shown.command, pending.text, width) : UNCHECKED;
|
|
196
|
-
return null;
|
|
197
|
-
}
|
|
198
|
-
function bottomLines(pane, n = 5) {
|
|
199
|
-
return pane.split("\n").filter((l) => l.trim() !== "").slice(-n).join("\n");
|
|
200
|
-
}
|
|
201
|
-
var flatten = (text) => text.replace(/\s*[\r\n]+\s*/g, " ").trim();
|
|
202
|
-
|
|
203
|
-
// src/daemon/state.ts
|
|
204
|
-
var HOOK_PRIORITY_MS = 2e3;
|
|
205
|
-
var hooksReportPrompts = (s) => s.tool === "claude";
|
|
206
|
-
var QUESTION_TOOLS = /* @__PURE__ */ new Set(["AskUserQuestion", "request_user_input"]);
|
|
207
|
-
var DIFF_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "apply_patch"]);
|
|
208
|
-
var details = (ev) => ev.optionDetails?.some(Boolean) ? { optionDetails: ev.optionDetails } : {};
|
|
209
|
-
function pendingFor(ev) {
|
|
210
|
-
if (ev.name === "PermissionRequest") {
|
|
211
|
-
const diff = !!ev.tool && DIFF_TOOLS.has(ev.tool);
|
|
212
|
-
return { kind: diff ? "diff" : "permission", text: ev.text ?? "", tool: ev.tool, source: "hook" };
|
|
213
|
-
}
|
|
214
|
-
if (ev.name === "PreToolUse" && ev.tool && QUESTION_TOOLS.has(ev.tool)) {
|
|
215
|
-
return { kind: "question", text: ev.text ?? "", options: ev.options, ...details(ev), source: "hook", ...ev.tool !== "AskUserQuestion" ? { tool: ev.tool } : {} };
|
|
216
|
-
}
|
|
217
|
-
if (ev.name === "PreToolUse" && ev.tool === "ExitPlanMode") {
|
|
218
|
-
const plan = ev.toolInput?.plan;
|
|
219
|
-
return { kind: "plan", text: ev.text ?? "", ...typeof plan === "string" && plan.trim() ? { body: plan } : {}, source: "hook" };
|
|
220
|
-
}
|
|
221
|
-
if (ev.name === "Notification" && ev.kind === "permission") {
|
|
222
|
-
return { kind: "permission", text: ev.text ?? "", source: "hook" };
|
|
223
|
-
}
|
|
224
|
-
if (ev.name === "NativeRequest") {
|
|
225
|
-
return { kind: "question", text: ev.text ?? "", ...ev.options?.length ? { options: ev.options } : {}, ...ev.questions ? { questions: ev.questions } : {}, ...ev.requestId ? { requestId: ev.requestId } : {}, source: "rpc", tool: "omp" };
|
|
226
|
-
}
|
|
227
|
-
if (ev.name === "PanePrompt") {
|
|
228
|
-
return { kind: ev.kind ?? "permission", text: ev.text ?? "", ...ev.body ? { body: ev.body } : {}, options: ev.options, ...details(ev), source: "pane" };
|
|
229
|
-
}
|
|
230
|
-
return void 0;
|
|
231
|
-
}
|
|
232
|
-
function mergePending(current, incoming) {
|
|
233
|
-
if (!current) return incoming;
|
|
234
|
-
if (current.source === "hook" && incoming.source === "pane") return current;
|
|
235
|
-
if (incoming.source === "hook" && current.source === "pane") return incoming;
|
|
236
|
-
if (current.helperId !== incoming.helperId) return current;
|
|
237
|
-
if (current.source === "hook" && (current.kind === "plan" || current.kind === "question") && incoming.source === "hook") return current;
|
|
238
|
-
const kind = incoming.kind;
|
|
239
|
-
const options = incoming.options ?? current.options;
|
|
240
|
-
const optionDetails = incoming.options ? incoming.optionDetails : current.optionDetails;
|
|
241
|
-
const { optionDetails: _old, ...rest } = { ...current, ...incoming };
|
|
242
|
-
return { ...rest, kind, text: incoming.text || current.text, tool: incoming.tool ?? current.tool, options, ...optionDetails ? { optionDetails } : {} };
|
|
243
|
-
}
|
|
244
|
-
function pendingTool(p) {
|
|
245
|
-
if (p.kind === "question") return p.tool ?? "AskUserQuestion";
|
|
246
|
-
if (p.kind === "plan") return "ExitPlanMode";
|
|
247
|
-
return p.tool;
|
|
248
|
-
}
|
|
249
|
-
function answeredBy(p, ev) {
|
|
250
|
-
if (p.source !== "hook" || ev.name !== "PostToolUse" && ev.name !== "PostToolUseFailure") return false;
|
|
251
|
-
const tool = pendingTool(p);
|
|
252
|
-
return tool !== void 0 && ev.tool === tool && ev.helperId === p.helperId;
|
|
253
|
-
}
|
|
254
|
-
function clearedByToolRun(p, tool) {
|
|
255
|
-
const t = pendingTool(p);
|
|
256
|
-
return p.source === "pane" || t === void 0 || t === tool;
|
|
257
|
-
}
|
|
258
|
-
function setHelper(s, id, patch, kind = "subagent") {
|
|
259
|
-
const i = s.helpers.findIndex((h) => h.id === id);
|
|
260
|
-
if (i < 0) return [...s.helpers, { id, kind, task: "", state: "working", ...patch }];
|
|
261
|
-
const helpers = [...s.helpers];
|
|
262
|
-
helpers[i] = { ...helpers[i], ...patch };
|
|
263
|
-
return helpers;
|
|
264
|
-
}
|
|
265
|
-
function settled(state) {
|
|
266
|
-
return state === "waiting_permission" || state === "waiting_input" ? "working" : state;
|
|
267
|
-
}
|
|
268
|
-
function restored(s) {
|
|
269
|
-
return s.pending?.parentState ?? settled(s.state);
|
|
270
|
-
}
|
|
271
|
-
function underHelper(s, parentState, at, extra = {}) {
|
|
272
|
-
return { ...s, ...extra, pending: { ...s.pending, parentState }, lastHookAt: at, lastActivityAt: at };
|
|
273
|
-
}
|
|
274
|
-
function adoptCursorTask(s, id) {
|
|
275
|
-
if (s.tool !== "cursor" || id.startsWith("task:") || s.helpers.some((h) => h.id === id)) return s;
|
|
276
|
-
const i = s.helpers.findIndex((h) => h.id.startsWith("task:") && h.state === "working");
|
|
277
|
-
if (i < 0) return s;
|
|
278
|
-
const helpers = [...s.helpers];
|
|
279
|
-
helpers[i] = { ...helpers[i], id };
|
|
280
|
-
return { ...s, helpers };
|
|
281
|
-
}
|
|
282
|
-
var untracked = (s, id) => s.tool === "claude" && !s.helpers.some((h) => h.id === id);
|
|
283
|
-
function reduceHelper(s, ev) {
|
|
284
|
-
s = adoptCursorTask(s, ev.helperId);
|
|
285
|
-
const at = ev.at;
|
|
286
|
-
const id = ev.helperId;
|
|
287
|
-
const own = s.pending?.helperId === id;
|
|
288
|
-
const named = ev.task && !s.helpers.find((h) => h.id === id)?.task ? { task: ev.task } : {};
|
|
289
|
-
const track = (patch, kind) => untracked(s, id) ? s.helpers : setHelper(s, id, { ...patch, ...named }, kind);
|
|
290
|
-
switch (ev.name) {
|
|
291
|
-
case "SubagentStart": {
|
|
292
|
-
const known = s.helpers.find((h) => h.id === id);
|
|
293
|
-
const patch = { state: "working", kind: ev.tool ?? known?.kind ?? "subagent", task: ev.text || known?.task || ev.task || "" };
|
|
294
|
-
return { ...s, helpers: setHelper(s, id, patch), lastHookAt: at, lastActivityAt: at };
|
|
295
|
-
}
|
|
296
|
-
case "SubagentStop": {
|
|
297
|
-
const helpers = track({ state: "exited" }, ev.tool ?? "subagent");
|
|
298
|
-
if (own) return { ...s, helpers, pending: void 0, state: restored(s), lastHookAt: at, lastActivityAt: at };
|
|
299
|
-
return { ...s, helpers, lastHookAt: at, lastActivityAt: at };
|
|
300
|
-
}
|
|
301
|
-
case "PermissionRequest": {
|
|
302
|
-
const helpers = track({ state: "waiting_permission" });
|
|
303
|
-
if (s.pending && s.pending.source === "hook") return { ...s, helpers, lastHookAt: at, lastActivityAt: at };
|
|
304
|
-
const pending = { ...pendingFor(ev), helperId: id, parentState: settled(s.state) };
|
|
305
|
-
return { ...s, helpers, state: "waiting_permission", pending, lastHookAt: at, lastActivityAt: at };
|
|
306
|
-
}
|
|
307
|
-
case "PreToolUse":
|
|
308
|
-
case "PostToolUse":
|
|
309
|
-
case "PostToolUseFailure": {
|
|
310
|
-
const helper = s.helpers.find((h) => h.id === id);
|
|
311
|
-
const busy = ev.name === "PreToolUse" && helper?.state === "waiting_permission";
|
|
312
|
-
const helpers = busy ? s.helpers : track({ state: ev.interrupt ? "idle" : "working" });
|
|
313
|
-
if (ev.name !== "PreToolUse" && own && (ev.interrupt || clearedByToolRun(s.pending, ev.tool))) {
|
|
314
|
-
return { ...s, helpers, pending: void 0, state: restored(s), lastHookAt: at, lastActivityAt: at };
|
|
315
|
-
}
|
|
316
|
-
return { ...s, helpers, lastHookAt: at, lastActivityAt: at };
|
|
317
|
-
}
|
|
318
|
-
default:
|
|
319
|
-
return { ...s, lastHookAt: at, lastActivityAt: at };
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
function withoutReply(s) {
|
|
323
|
-
const { lastReply: _gone, ...rest } = s;
|
|
324
|
-
return rest;
|
|
325
|
-
}
|
|
326
|
-
function samePending(a, b) {
|
|
327
|
-
return a.kind === b.kind && a.text === b.text && a.body === b.body && a.tool === b.tool && a.source === b.source && JSON.stringify(a.options) === JSON.stringify(b.options) && JSON.stringify(a.optionDetails) === JSON.stringify(b.optionDetails);
|
|
328
|
-
}
|
|
329
|
-
function endCursorHelpers(s, ev) {
|
|
330
|
-
if (s.tool !== "cursor" || ev.source !== "hook" || ev.helperId || !s.helpers.some((h) => h.state === "working")) return s;
|
|
331
|
-
const moves = ev.name === "PostToolUse" || ev.name === "PostToolUseFailure" || ev.name === "Stop" || ev.name === "StopFailure" || ev.name === "UserPromptSubmit" || ev.name === "PreToolUse";
|
|
332
|
-
return moves ? { ...s, helpers: s.helpers.map((h) => h.state === "working" ? { ...h, state: "exited" } : h) } : s;
|
|
333
|
-
}
|
|
334
|
-
function withTopic(s, text, source) {
|
|
335
|
-
const topic = cleanTopic(text) || void 0;
|
|
336
|
-
if (topic === s.topic && (topic ? source : void 0) === s.topicSource) return s;
|
|
337
|
-
const { topic: _t, topicSource: _ts, ...rest } = s;
|
|
338
|
-
const next = topic ? { ...rest, topic, topicSource: source } : rest;
|
|
339
|
-
return { ...next, title: sessionTitle(next) };
|
|
340
|
-
}
|
|
341
|
-
function reduce(s, ev) {
|
|
342
|
-
const next = reduceEvent(s, ev);
|
|
343
|
-
if (next === s) return s;
|
|
344
|
-
let changed = false;
|
|
345
|
-
const helpers = next.helpers.map((h) => {
|
|
346
|
-
if (h.state === "exited" || next.state === "exited") {
|
|
347
|
-
if (h.state === "exited" && h.finishedAt) return h;
|
|
348
|
-
changed = true;
|
|
349
|
-
return { ...h, state: "exited", finishedAt: h.finishedAt ?? next.exitedAt ?? ev.at };
|
|
350
|
-
}
|
|
351
|
-
if (!h.finishedAt) return h;
|
|
352
|
-
changed = true;
|
|
353
|
-
const { finishedAt: _gone, ...active } = h;
|
|
354
|
-
return active;
|
|
355
|
-
});
|
|
356
|
-
return changed ? { ...next, helpers } : next;
|
|
357
|
-
}
|
|
358
|
-
function reduceEvent(s, ev) {
|
|
359
|
-
if (ev.agentSessionId && !ev.helperId && ev.agentSessionId !== s.agentSessionId) s = { ...s, agentSessionId: ev.agentSessionId };
|
|
360
|
-
if (s.state === "exited") return s;
|
|
361
|
-
s = endCursorHelpers(s, ev);
|
|
362
|
-
const at = ev.at;
|
|
363
|
-
const lastHookAt = s.lastHookAt;
|
|
364
|
-
if (ev.name === "SubagentStart" || ev.name === "SubagentStop") return ev.helperId ? reduceHelper(s, { ...ev, helperId: ev.helperId }) : s;
|
|
365
|
-
if (ev.helperId && ev.source === "hook" && ev.name !== "SessionEnd") return reduceHelper(s, { ...ev, helperId: ev.helperId });
|
|
366
|
-
if (ev.source === "hook" && ev.transcriptPath && ev.transcriptPath !== s.transcriptPath) s = { ...s, transcriptPath: ev.transcriptPath };
|
|
367
|
-
switch (ev.name) {
|
|
368
|
-
case "SessionEnd":
|
|
369
|
-
return { ...s, state: "exited", pending: void 0, exitCode: ev.exitCode ?? null, exitedAt: s.exitedAt ?? at, lastActivityAt: at };
|
|
370
|
-
case "PaneGone":
|
|
371
|
-
return { ...s, state: "exited", pending: void 0, exitCode: null, exitedAt: s.exitedAt ?? at, lastActivityAt: at };
|
|
372
|
-
case "PaneChanged":
|
|
373
|
-
return s.lastActivityAt === at ? s : { ...s, lastActivityAt: at };
|
|
374
|
-
case "PaneTitle": {
|
|
375
|
-
if (s.topicSource === "user") return s;
|
|
376
|
-
const t = ev.text ?? "";
|
|
377
|
-
const rest = ledgeTitleRest(t, s.name);
|
|
378
|
-
if (rest === "") return s.topicSource === "pane" && s.topic !== void 0 && ledgeTitleRest(s.topic, s.name) === "" ? withTopic(s, "", void 0) : s;
|
|
379
|
-
return withTopic(s, rest ?? t, "pane");
|
|
380
|
-
}
|
|
381
|
-
case "Renamed":
|
|
382
|
-
return withTopic(s, ev.text ?? "", ev.text?.trim() ? "user" : void 0);
|
|
383
|
-
case "PaneIdle": {
|
|
384
|
-
if (s.state === "idle" || s.state === "error" || s.pending?.helperId) return s;
|
|
385
|
-
if (lastHookAt && Date.parse(at) - Date.parse(lastHookAt) < HOOK_PRIORITY_MS) return s;
|
|
386
|
-
return { ...s, state: "idle", pending: void 0, lastActivityAt: at };
|
|
387
|
-
}
|
|
388
|
-
case "InjectFailed":
|
|
389
|
-
return { ...s, state: "error", lastError: ev.text ?? "injection not confirmed", lastActivityAt: at };
|
|
390
|
-
case "Decided": {
|
|
391
|
-
const helperId = s.pending?.helperId;
|
|
392
|
-
if (helperId) {
|
|
393
|
-
const state = restored(s);
|
|
394
|
-
const helpers = s.helpers.some((h) => h.id === helperId) ? setHelper(s, helperId, { state: "working" }) : s.helpers;
|
|
395
|
-
return { ...s, state, helpers, pending: void 0, lastError: state === "error" ? s.lastError : void 0, lastActivityAt: at };
|
|
396
|
-
}
|
|
397
|
-
return { ...s, state: "working", pending: void 0, lastError: void 0, lastHookAt: at, lastActivityAt: at };
|
|
398
|
-
}
|
|
399
|
-
case "Stop": {
|
|
400
|
-
const lastReply = ev.text ? { text: ev.text, at, ...ev.kind === "plan" ? { plan: true } : {} } : s.lastReply;
|
|
401
|
-
if (s.pending?.helperId) return underHelper(s, "idle", at, { lastReply });
|
|
402
|
-
return { ...s, state: "idle", pending: void 0, lastReply, lastHookAt: at, lastActivityAt: at };
|
|
403
|
-
}
|
|
404
|
-
case "StopFailure": {
|
|
405
|
-
if (s.state === "idle") return s;
|
|
406
|
-
const lastError = ev.text || "the turn stopped on an error";
|
|
407
|
-
if (s.pending?.helperId) return underHelper(s, "error", at, { lastError });
|
|
408
|
-
return { ...s, state: "error", pending: void 0, lastError, lastHookAt: at, lastActivityAt: at };
|
|
409
|
-
}
|
|
410
|
-
case "PostToolUse":
|
|
411
|
-
case "PostToolUseFailure": {
|
|
412
|
-
const rest = ev.interrupt ? "idle" : "working";
|
|
413
|
-
if (s.pending?.helperId) return underHelper(s, rest, at);
|
|
414
|
-
if (s.pending && !ev.interrupt && !clearedByToolRun(s.pending, ev.tool)) return { ...s, lastHookAt: at, lastActivityAt: at };
|
|
415
|
-
return { ...s, state: rest, pending: void 0, lastHookAt: at, lastActivityAt: at };
|
|
416
|
-
}
|
|
417
|
-
case "PanePrompt": {
|
|
418
|
-
const incoming = pendingFor(ev);
|
|
419
|
-
const moved = s.pending?.source === "hook" && s.pending.kind === "question" && incoming.kind === "question" && !sameQuestion(incoming.text, s.pending.text);
|
|
420
|
-
const bare = s.pending?.source === "hook" && s.pending.kind === "permission" && !s.pending.tool;
|
|
421
|
-
if (s.pending?.source === "hook" && !moved && !bare) return s;
|
|
422
|
-
if (!moved && hooksReportPrompts(s) && lastHookAt && Date.parse(at) - Date.parse(lastHookAt) < HOOK_PRIORITY_MS) return s;
|
|
423
|
-
let pending = moved ? { ...incoming, ...s.pending.helperId ? { helperId: s.pending.helperId } : {} } : bare ? incoming : mergePending(s.pending, incoming);
|
|
424
|
-
if (pending.kind === "plan" && !pending.body && s.tool === "codex" && s.lastReply) pending = { ...pending, body: s.lastReply.text };
|
|
425
|
-
if (s.pending && samePending(s.pending, pending)) return s;
|
|
426
|
-
const state = pending.kind === "question" ? "waiting_input" : "waiting_permission";
|
|
427
|
-
return { ...s, state, pending, lastActivityAt: at };
|
|
428
|
-
}
|
|
429
|
-
case "QueueUpdate": {
|
|
430
|
-
const { queue: _q, ...rest } = s;
|
|
431
|
-
return ev.queue?.length ? { ...rest, queue: ev.queue } : s.queue ? rest : s;
|
|
432
|
-
}
|
|
433
|
-
case "AgentMessage":
|
|
434
|
-
return s.lastActivityAt === at ? s : { ...s, lastActivityAt: at };
|
|
435
|
-
case "NativeRequest": {
|
|
436
|
-
if (s.pending) return s;
|
|
437
|
-
return { ...s, state: "waiting_input", pending: pendingFor(ev), lastActivityAt: at };
|
|
438
|
-
}
|
|
439
|
-
case "SessionStart":
|
|
440
|
-
case "UserPromptSubmit":
|
|
441
|
-
case "PreToolUse":
|
|
442
|
-
case "PermissionRequest":
|
|
443
|
-
case "Notification": {
|
|
444
|
-
const notice2 = ev.name === "UserPromptSubmit" && !!ev.text && (!!crossSessionNotices(ev.text) || !!ledgeNotice(ev.text) || !!ledgeTask(ev.text));
|
|
445
|
-
if (ev.name === "UserPromptSubmit" && ev.text && !s.topic && !notice2 && !crossSession(ev.text)) s = withTopic(s, ev.text, "prompt");
|
|
446
|
-
if (s.pending?.source === "rpc" && ev.name === "PreToolUse") return { ...s, lastHookAt: at, lastActivityAt: at };
|
|
447
|
-
const incoming = pendingFor(ev);
|
|
448
|
-
if (incoming) {
|
|
449
|
-
if (ev.name === "Notification" && s.pending) return { ...s, lastHookAt: at, lastActivityAt: at };
|
|
450
|
-
const pending = mergePending(s.pending, incoming);
|
|
451
|
-
const state = pending.kind === "question" ? "waiting_input" : "waiting_permission";
|
|
452
|
-
return { ...s, state, pending, lastHookAt: at, lastActivityAt: at };
|
|
453
|
-
}
|
|
454
|
-
if (ev.name === "Notification" || ev.name === "SessionStart") return { ...s, lastHookAt: at, lastActivityAt: at };
|
|
455
|
-
const prompted = ev.name === "UserPromptSubmit" && s.lastReply && !notice2 ? withoutReply(s) : s;
|
|
456
|
-
if (prompted.pending?.helperId) return underHelper(prompted, "working", at);
|
|
457
|
-
if (prompted === s && s.state === "working" && s.lastActivityAt === at && !s.pending) return s;
|
|
458
|
-
return { ...prompted, state: "working", pending: void 0, lastHookAt: at, lastActivityAt: at };
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// src/daemon/git.ts
|
|
464
|
-
import { execFile } from "child_process";
|
|
465
|
-
import { basename, dirname, resolve } from "path";
|
|
466
|
-
function repositoryId(remote) {
|
|
467
|
-
const value = remote.trim();
|
|
468
|
-
try {
|
|
469
|
-
const url = new URL(value);
|
|
470
|
-
if (!["https:", "http:", "ssh:", "git:"].includes(url.protocol)) return void 0;
|
|
471
|
-
return `${url.hostname.toLowerCase()}${url.pathname.replace(/\.git\/?$/, "").replace(/\/$/, "")}`;
|
|
472
|
-
} catch {
|
|
473
|
-
const scp = value.match(/^(?:[^@\s]+@)?([a-zA-Z0-9.-]+):([^\s]+)$/);
|
|
474
|
-
return scp ? `${scp[1].toLowerCase()}/${scp[2].replace(/\.git$/, "").replace(/\/$/, "")}` : void 0;
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
var execGit = (file, args, cwd) => new Promise((res) => {
|
|
478
|
-
execFile(file, args, { cwd, timeout: 3e3, maxBuffer: 1024 * 1024 }, (err, stdout) => res({ code: err ? 1 : 0, stdout: String(stdout) }));
|
|
479
|
-
});
|
|
480
|
-
async function gitInfo(cwd, exec = execGit) {
|
|
481
|
-
const r = await exec("git", ["rev-parse", "--path-format=absolute", "--show-toplevel", "--git-common-dir", "--abbrev-ref", "HEAD"], cwd);
|
|
482
|
-
if (r.code !== 0) return null;
|
|
483
|
-
const [toplevel = "", common = "", abbrev = ""] = r.stdout.split("\n").map((l) => l.trim());
|
|
484
|
-
if (!toplevel || !common || !abbrev) return null;
|
|
485
|
-
const worktree = resolve(common) !== resolve(toplevel, ".git");
|
|
486
|
-
const repoPath = worktree ? dirname(resolve(common)) : resolve(toplevel);
|
|
487
|
-
let branch = abbrev;
|
|
488
|
-
if (abbrev === "HEAD") {
|
|
489
|
-
const sha = await exec("git", ["rev-parse", "--short", "HEAD"], cwd);
|
|
490
|
-
if (sha.code !== 0 || !sha.stdout.trim()) return null;
|
|
491
|
-
branch = sha.stdout.trim();
|
|
492
|
-
}
|
|
493
|
-
const remote = await exec("git", ["config", "--get", "remote.origin.url"], cwd);
|
|
494
|
-
const repoId = remote.code === 0 ? repositoryId(remote.stdout) : void 0;
|
|
495
|
-
return { repo: basename(repoPath), repoPath, branch, worktree, ...repoId ? { repoId } : {} };
|
|
496
|
-
}
|
|
497
|
-
async function gitSummary(cwd, exec = execGit) {
|
|
498
|
-
const status = await exec("git", ["status", "--short", "--branch"], cwd);
|
|
499
|
-
if (status.code !== 0) return null;
|
|
500
|
-
const log = await exec("git", ["log", "--oneline", "-10"], cwd);
|
|
501
|
-
const diff = await exec("git", ["diff", "--stat"], cwd);
|
|
502
|
-
return [`# git status --short --branch`, status.stdout.trimEnd(), "", "# git log --oneline -10", log.stdout.trimEnd(), "", "# git diff --stat", diff.stdout.trimEnd() || "(clean)"].join("\n");
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
// src/daemon/peers.ts
|
|
506
|
-
import { readdirSync, readFileSync } from "fs";
|
|
507
|
-
import { homedir } from "os";
|
|
508
|
-
import { basename as basename2, join } from "path";
|
|
509
|
-
var defaultDir = () => join(process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), ".claude"), "sessions");
|
|
510
|
-
function parsePeer(path) {
|
|
511
|
-
try {
|
|
512
|
-
const j = JSON.parse(readFileSync(path, "utf8"));
|
|
513
|
-
if (typeof j.pid !== "number" || typeof j.sessionId !== "string" || typeof j.name !== "string" || typeof j.messagingSocketPath !== "string") return void 0;
|
|
514
|
-
return { pid: j.pid, sessionId: j.sessionId, name: j.name, address: `uds:${j.messagingSocketPath}` };
|
|
515
|
-
} catch {
|
|
516
|
-
return void 0;
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
function pidAlive(pid) {
|
|
520
|
-
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
521
|
-
try {
|
|
522
|
-
process.kill(pid, 0);
|
|
523
|
-
return true;
|
|
524
|
-
} catch (e) {
|
|
525
|
-
return e.code === "EPERM";
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
function readPeers(dir = defaultDir(), alive = pidAlive) {
|
|
529
|
-
let names;
|
|
530
|
-
try {
|
|
531
|
-
names = readdirSync(dir);
|
|
532
|
-
} catch {
|
|
533
|
-
return [];
|
|
534
|
-
}
|
|
535
|
-
return names.filter((n) => n.endsWith(".json")).flatMap((n) => parsePeer(join(dir, n)) ?? []).filter((p) => alive(p.pid));
|
|
536
|
-
}
|
|
537
|
-
function addressOf(s, peers) {
|
|
538
|
-
const t = s.transcriptPath && basename2(s.transcriptPath, ".jsonl");
|
|
539
|
-
const byTranscript = t ? peers.filter((p) => p.sessionId === t) : [];
|
|
540
|
-
const found = byTranscript.length ? byTranscript : peers.filter((p) => s.agentSessionId && p.sessionId === s.agentSessionId);
|
|
541
|
-
return found.length === 1 ? found[0].address : void 0;
|
|
542
|
-
}
|
|
543
|
-
var bareName = (name) => name.replace(/ \[[^\]]*\]$/, "");
|
|
544
|
-
var sessionOf = (id, sessions) => sessions.find((s) => s.agentSessionId === id || s.transcriptPath?.endsWith(`/${id}.jsonl`));
|
|
545
|
-
function lookup(to, sessions, peers) {
|
|
546
|
-
const name = bareName(to);
|
|
547
|
-
if (name.startsWith("uds:")) {
|
|
548
|
-
const via = peers.find((p) => p.address === name);
|
|
549
|
-
return { target: via && sessionOf(via.sessionId, sessions), via };
|
|
550
|
-
}
|
|
551
|
-
const t = sessions.find((s) => s.name === name && s.state !== "exited" && !s.archivedAt);
|
|
552
|
-
if (t) return { target: t };
|
|
553
|
-
const named = peers.filter((p) => p.name === name);
|
|
554
|
-
if (named.length > 1) return { reason: `plusieurs sessions s'appellent ${name}` };
|
|
555
|
-
return { target: named[0] && sessionOf(named[0].sessionId, sessions) };
|
|
556
|
-
}
|
|
557
|
-
function ledgePeer(target, peers, sessions) {
|
|
558
|
-
return lookup(target, sessions, peers).target;
|
|
559
|
-
}
|
|
560
|
-
function resolveTarget(to, sessions, peers) {
|
|
561
|
-
const { target: t, via, reason } = lookup(to, sessions, peers);
|
|
562
|
-
if (reason) return { outcome: "refused", reason };
|
|
563
|
-
if (!t) return { outcome: "unknown" };
|
|
564
|
-
if (t.source === "hook_only") return { outcome: "refused", reason: `${t.name} n'a pas \xE9t\xE9 lanc\xE9e par Ledge : impossible de lui \xE9crire` };
|
|
565
|
-
if (t.tool !== "claude") return { outcome: "ledge", target: t };
|
|
566
|
-
const address = via?.address ?? addressOf(t, peers);
|
|
567
|
-
return address ? { outcome: "native", target: t, address } : { outcome: "refused", reason: `${t.name} n'est pas joignable` };
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
// src/attachmentText.ts
|
|
571
|
-
var imageBracket = (path) => `[Image attached: ${path}]`;
|
|
572
|
-
var withImages = (text, paths) => [text, ...paths.map(imageBracket)].join(" ").trim();
|
|
573
|
-
var BRACKET = /[ \t]*\[Image attached: ([^\]\n]*)\]/g;
|
|
574
|
-
function takeImages(text, dir) {
|
|
575
|
-
const prefix = dir.replace(/\/+$/, "") + "/";
|
|
576
|
-
const ids = [];
|
|
577
|
-
const rest = text.replace(BRACKET, (whole, path) => {
|
|
578
|
-
if (!path.startsWith(prefix)) return whole;
|
|
579
|
-
const m = /^([0-9a-f]{64})\.(?:webp|jpg|png|gif)$/.exec(path.slice(prefix.length));
|
|
580
|
-
if (!m) return whole;
|
|
581
|
-
ids.push(m[1]);
|
|
582
|
-
return "";
|
|
583
|
-
});
|
|
584
|
-
return { text: rest.trim(), ids };
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
export {
|
|
588
|
-
crossSession,
|
|
589
|
-
messageNote,
|
|
590
|
-
stripNote,
|
|
591
|
-
ledgeBody,
|
|
592
|
-
mailId,
|
|
593
|
-
inline,
|
|
594
|
-
formatLedgeMessage,
|
|
595
|
-
ledgeMessages,
|
|
596
|
-
formatLedgeNotice,
|
|
597
|
-
formatLedgeTask,
|
|
598
|
-
ledgeTask,
|
|
599
|
-
ledgeNotice,
|
|
600
|
-
crossSessionNotices,
|
|
601
|
-
Refusal,
|
|
602
|
-
NO_ALWAYS,
|
|
603
|
-
NO_NOTE,
|
|
604
|
-
STALE,
|
|
605
|
-
assertDialogIs,
|
|
606
|
-
shownCommand,
|
|
607
|
-
dialogMismatch,
|
|
608
|
-
bottomLines,
|
|
609
|
-
flatten,
|
|
610
|
-
QUESTION_TOOLS,
|
|
611
|
-
pendingTool,
|
|
612
|
-
answeredBy,
|
|
613
|
-
reduce,
|
|
614
|
-
repositoryId,
|
|
615
|
-
gitInfo,
|
|
616
|
-
gitSummary,
|
|
617
|
-
readPeers,
|
|
618
|
-
bareName,
|
|
619
|
-
ledgePeer,
|
|
620
|
-
resolveTarget,
|
|
621
|
-
withImages,
|
|
622
|
-
takeImages
|
|
623
|
-
};
|