runpane 2.4.185 → 2.4.186

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.
@@ -0,0 +1,92 @@
1
+ /**
2
+ * An agent's own session (a Claude Code transcript, a Codex rollout, a Cursor chat, an OpenCode
3
+ * session) as files, so `runpane handoff` can carry it to another machine or fork it on this one.
4
+ * The stores are each tool's internal format; this module only finds, copies and forks them, and
5
+ * prefers each tool's own commands to editing files. OpenCode keeps sessions in a database, so its
6
+ * session travels as the file `opencode export` writes and `opencode import` reads.
7
+ */
8
+ export type NativeAgent = 'claude' | 'codex' | 'cursor' | 'opencode';
9
+ interface StoreFile {
10
+ absolute: string;
11
+ /** Path relative to the user's home folder, with forward slashes: where the copy goes on the destination. */
12
+ home: string;
13
+ bytes: number;
14
+ }
15
+ export interface NativeSession {
16
+ agent: NativeAgent;
17
+ id: string;
18
+ /** The store's folder under the home folder: `.claude`, `.codex` or `.cursor`. */
19
+ storeDir: string;
20
+ /** The session's main file, then any files that belong to it (Claude subagent transcripts, a Cursor chat's folder). */
21
+ files: StoreFile[];
22
+ /** A Cursor chat's own folder, which is the whole session. */
23
+ folder?: string;
24
+ /** OpenCode: the one file is an export, which the destination brings in with `opencode import`. */
25
+ exported?: true;
26
+ }
27
+ export declare function isNativeAgent(value: string | undefined): value is NativeAgent;
28
+ /**
29
+ * The files of one session on this machine, or undefined when the store has none. Throws when
30
+ * the id is ambiguous (two copies in one store), which the agent itself cannot resume either.
31
+ * `cwd` is where an OpenCode export runs.
32
+ */
33
+ export declare function locateNativeSession(agent: NativeAgent, id: string, cwd: string, env?: NodeJS.ProcessEnv): Promise<NativeSession | undefined>;
34
+ /**
35
+ * How many messages another copy of an OpenCode session (`opencode export` output) has that this
36
+ * export lacks: more than none means that copy was continued after this one was taken. A copy
37
+ * runpane can't read counts as all of its messages missing, since nothing in it can be matched.
38
+ */
39
+ export declare function openCodeMessagesMissing(exported: string, other: string): number;
40
+ /**
41
+ * A short, distinctive phrase from the last complete user or assistant message in the session,
42
+ * for checking that the resumed agent shows its history. Undefined when the store is not text
43
+ * this module can read (a compressed rollout, a Cursor chat).
44
+ */
45
+ export declare function lastMessageSnippet(session: NativeSession): string | undefined;
46
+ /** Whether a screen shows the snippet, ignoring line wrapping and spacing. */
47
+ export declare function screenShows(screen: string, snippet: string): boolean;
48
+ interface FileStamp {
49
+ absolute: string;
50
+ bytes: number;
51
+ modifiedMs: number;
52
+ /** sha256 of the file's content when it was stamped. */
53
+ sha256: string;
54
+ }
55
+ export declare function sha256(content: Buffer): string;
56
+ /** How the original looked before a fork, to prove the fork left it untouched (continuity check 4). */
57
+ export type OriginalSnapshot = {
58
+ kind: 'files';
59
+ stamps: FileStamp[];
60
+ folder?: string;
61
+ listing?: string[];
62
+ } | {
63
+ kind: 'export';
64
+ id: string;
65
+ cwd: string;
66
+ text: string;
67
+ };
68
+ export declare function snapshotOriginal(session: NativeSession, cwd: string): Promise<OriginalSnapshot>;
69
+ /** How the original compares with its snapshot. */
70
+ export interface OriginalComparison {
71
+ /** Files whose earlier content changed, files new in a Cursor chat folder, or the session's export. */
72
+ changed: string[];
73
+ /** Files that only grew: everything they held at the snapshot is still there, byte for byte. */
74
+ appended: string[];
75
+ }
76
+ /**
77
+ * What happened to the original since the snapshot. Any change to a file fails, unless
78
+ * `appendOnly`: the sender is the running agent, so its own turn (the one running this handoff)
79
+ * keeps appending to its transcript. Then a file may grow, as long as every byte it held at the
80
+ * snapshot is unchanged; it is reported in `appended`, not `changed`.
81
+ */
82
+ export declare function originalChanges(snapshot: OriginalSnapshot, options?: {
83
+ appendOnly?: boolean;
84
+ }): Promise<OriginalComparison>;
85
+ export declare function forkNativeSession(session: NativeSession, cwd: string, env?: NodeJS.ProcessEnv): Promise<string>;
86
+ /**
87
+ * Why a Cursor session can't be continued by a second agent on the same machine: a copy of its
88
+ * chat folder under a new id still writes to the original chat once it is used (QA saw it).
89
+ */
90
+ export declare const CURSOR_NO_FORK = "Cursor has no fork command, and a copied Cursor chat still writes to the original chat once it is used, so a second Cursor agent on this machine can't continue your session without changing it";
91
+ export {};
92
+ //# sourceMappingURL=nativeSession.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nativeSession.d.ts","sourceRoot":"","sources":["../src/nativeSession.ts"],"names":[],"mappings":"AAOA;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,UAAU,CAAC;AAErE,UAAU,SAAS;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,6GAA6G;IAC7G,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,WAAW,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,kFAAkF;IAClF,QAAQ,EAAE,MAAM,CAAC;IACjB,uHAAuH;IACvH,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mGAAmG;IACnG,QAAQ,CAAC,EAAE,IAAI,CAAC;CACjB;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,KAAK,IAAI,WAAW,CAE7E;AAyCD;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAwC/J;AA0CD;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAY/E;AAuED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,GAAG,SAAS,CAiB7E;AAiBD,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAGpE;AAED,UAAU,SAAS;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,uGAAuG;AACvG,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,SAAS,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GAC3E;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9D,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAoBrG;AAED,mDAAmD;AACnD,MAAM,WAAW,kBAAkB;IACjC,uGAAuG;IACvG,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,gGAAgG;IAChG,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,gBAAgB,EAAE,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAwBrI;AA+BD,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CAgClI;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,qMAAsM,CAAC"}
@@ -0,0 +1,410 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CURSOR_NO_FORK = void 0;
7
+ exports.isNativeAgent = isNativeAgent;
8
+ exports.locateNativeSession = locateNativeSession;
9
+ exports.openCodeMessagesMissing = openCodeMessagesMissing;
10
+ exports.lastMessageSnippet = lastMessageSnippet;
11
+ exports.screenShows = screenShows;
12
+ exports.sha256 = sha256;
13
+ exports.snapshotOriginal = snapshotOriginal;
14
+ exports.originalChanges = originalChanges;
15
+ exports.forkNativeSession = forkNativeSession;
16
+ const node_child_process_1 = require("node:child_process");
17
+ const node_crypto_1 = require("node:crypto");
18
+ const node_fs_1 = __importDefault(require("node:fs"));
19
+ const node_os_1 = __importDefault(require("node:os"));
20
+ const node_path_1 = __importDefault(require("node:path"));
21
+ const boundaryDecoder_1 = require("./boundaryDecoder");
22
+ function isNativeAgent(value) {
23
+ return value === 'claude' || value === 'codex' || value === 'cursor' || value === 'opencode';
24
+ }
25
+ function homeDirectory(env) {
26
+ return env.HOME || env.USERPROFILE || node_os_1.default.homedir();
27
+ }
28
+ function storeRoot(agent, env) {
29
+ const home = homeDirectory(env);
30
+ if (agent === 'claude')
31
+ return { root: env.CLAUDE_CONFIG_DIR || node_path_1.default.join(home, '.claude'), storeDir: '.claude' };
32
+ if (agent === 'codex')
33
+ return { root: env.CODEX_HOME || node_path_1.default.join(home, '.codex'), storeDir: '.codex' };
34
+ return { root: node_path_1.default.join(home, '.cursor'), storeDir: '.cursor' };
35
+ }
36
+ function entries(directory) {
37
+ try {
38
+ return node_fs_1.default.readdirSync(directory, { withFileTypes: true });
39
+ }
40
+ catch {
41
+ return [];
42
+ }
43
+ }
44
+ function filesUnder(directory) {
45
+ return entries(directory).flatMap(entry => {
46
+ const full = node_path_1.default.join(directory, entry.name);
47
+ return entry.isDirectory() ? filesUnder(full) : entry.isFile() ? [full] : [];
48
+ });
49
+ }
50
+ /** Where a file sits relative to the store root, re-rooted at the store's default folder under home. */
51
+ function storeFile(absolute, root, storeDir) {
52
+ const relative = node_path_1.default.relative(root, absolute).split(node_path_1.default.sep).join('/');
53
+ return { absolute, home: `${storeDir}/${relative}`, bytes: node_fs_1.default.statSync(absolute).size };
54
+ }
55
+ /**
56
+ * The files of one session on this machine, or undefined when the store has none. Throws when
57
+ * the id is ambiguous (two copies in one store), which the agent itself cannot resume either.
58
+ * `cwd` is where an OpenCode export runs.
59
+ */
60
+ async function locateNativeSession(agent, id, cwd, env = process.env) {
61
+ if (!/^[A-Za-z0-9._-]+$/.test(id))
62
+ return undefined;
63
+ if (agent === 'opencode')
64
+ return exportOpenCodeSession(id, cwd);
65
+ const { root, storeDir } = storeRoot(agent, env);
66
+ const mains = [];
67
+ const extras = [];
68
+ let folder;
69
+ if (agent === 'claude') {
70
+ for (const project of entries(node_path_1.default.join(root, 'projects')).filter(entry => entry.isDirectory())) {
71
+ const projectDir = node_path_1.default.join(root, 'projects', project.name);
72
+ const transcript = node_path_1.default.join(projectDir, `${id}.jsonl`);
73
+ if (node_fs_1.default.existsSync(transcript)) {
74
+ mains.push(transcript);
75
+ // Claude keeps a session's subagent transcripts in a folder named by its id.
76
+ extras.push(...filesUnder(node_path_1.default.join(projectDir, id)));
77
+ }
78
+ }
79
+ }
80
+ else if (agent === 'codex') {
81
+ const pattern = new RegExp(`^rollout-.*${id.replace(/[.]/g, '\\.')}\\.jsonl(?:\\.zst)?$`);
82
+ for (const file of filesUnder(node_path_1.default.join(root, 'sessions'))) {
83
+ if (pattern.test(node_path_1.default.basename(file)))
84
+ mains.push(file);
85
+ }
86
+ }
87
+ else {
88
+ for (const workspace of entries(node_path_1.default.join(root, 'chats')).filter(entry => entry.isDirectory())) {
89
+ const chat = node_path_1.default.join(root, 'chats', workspace.name, id);
90
+ const files = filesUnder(chat);
91
+ if (files.length) {
92
+ folder = chat;
93
+ mains.push(files[0]);
94
+ extras.push(...files.slice(1));
95
+ }
96
+ }
97
+ }
98
+ if (mains.length === 0)
99
+ return undefined;
100
+ if (mains.length > 1) {
101
+ throw new Error(`${agent} has ${mains.length} copies of session ${id} on this machine (${mains.join(', ')}), so it cannot tell which one to resume.`);
102
+ }
103
+ const located = { agent, id, storeDir, files: [mains[0], ...extras].map(file => storeFile(file, root, storeDir)) };
104
+ if (folder)
105
+ located.folder = folder;
106
+ return located;
107
+ }
108
+ /** An OpenCode export: the session and its messages, each with parts of which some carry text. */
109
+ const openCodeExportSchema = boundaryDecoder_1.boundary.object({
110
+ info: boundaryDecoder_1.boundary.object({ id: boundaryDecoder_1.boundary.string }),
111
+ messages: boundaryDecoder_1.boundary.array(boundaryDecoder_1.boundary.object({
112
+ info: boundaryDecoder_1.boundary.object({ id: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.string), role: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.string) }),
113
+ parts: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.array(boundaryDecoder_1.boundary.object({ type: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.string), text: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.string) }))),
114
+ })),
115
+ });
116
+ async function openCodeExport(id, cwd) {
117
+ const result = await run('opencode', ['export', id], cwd, 120_000);
118
+ if (result.code !== 0)
119
+ return undefined;
120
+ // The JSON is on stdout; a progress line goes to stderr.
121
+ return result.stdout.includes('{') ? exportJson(result.stdout) : undefined;
122
+ }
123
+ /** An OpenCode session as the file `opencode export` writes, in a private temporary folder. */
124
+ async function exportOpenCodeSession(id, cwd) {
125
+ const text = await openCodeExport(id, cwd);
126
+ if (text === undefined)
127
+ return undefined;
128
+ try {
129
+ if ((0, boundaryDecoder_1.decodeBoundary)(JSON.parse(text), openCodeExportSchema).info.id !== id)
130
+ return undefined;
131
+ }
132
+ catch {
133
+ return undefined;
134
+ }
135
+ const folder = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'runpane-opencode-'));
136
+ const absolute = node_path_1.default.join(folder, `${id}.json`);
137
+ node_fs_1.default.writeFileSync(absolute, text, { mode: 0o600 });
138
+ return {
139
+ agent: 'opencode', id, storeDir: '.pane', exported: true,
140
+ files: [{ absolute, home: `.pane/handoffs/opencode-${id}.json`, bytes: Buffer.byteLength(text) }],
141
+ };
142
+ }
143
+ /** The JSON in `opencode export`'s output: a progress line can come first. */
144
+ function exportJson(output) {
145
+ const start = output.indexOf('{');
146
+ return start >= 0 ? output.slice(start) : output;
147
+ }
148
+ /**
149
+ * How many messages another copy of an OpenCode session (`opencode export` output) has that this
150
+ * export lacks: more than none means that copy was continued after this one was taken. A copy
151
+ * runpane can't read counts as all of its messages missing, since nothing in it can be matched.
152
+ */
153
+ function openCodeMessagesMissing(exported, other) {
154
+ const ids = (text) => {
155
+ try {
156
+ return (0, boundaryDecoder_1.decodeBoundary)(JSON.parse(exportJson(text)), openCodeExportSchema).messages.map((message, index) => message.info.id ?? `#${index}`);
157
+ }
158
+ catch {
159
+ return undefined;
160
+ }
161
+ };
162
+ const ours = new Set(ids(exported) ?? []);
163
+ const theirs = ids(other);
164
+ if (!theirs)
165
+ return Number.POSITIVE_INFINITY;
166
+ return theirs.filter(id => !ours.has(id)).length;
167
+ }
168
+ function openCodeLastMessage(text) {
169
+ try {
170
+ const { messages } = (0, boundaryDecoder_1.decodeBoundary)(JSON.parse(text), openCodeExportSchema);
171
+ for (const message of [...messages].reverse()) {
172
+ if (message.info.role !== 'user' && message.info.role !== 'assistant')
173
+ continue;
174
+ const snippet = snippetOf((message.parts ?? []).filter(part => part.type === 'text').map(part => part.text ?? '').join('\n'));
175
+ if (snippet)
176
+ return snippet;
177
+ }
178
+ }
179
+ catch {
180
+ return undefined;
181
+ }
182
+ return undefined;
183
+ }
184
+ /** A message's content as Claude and Codex store it: a string, or parts of which some carry text. */
185
+ const contentSchema = boundaryDecoder_1.boundary.union(boundaryDecoder_1.boundary.string, boundaryDecoder_1.boundary.array(boundaryDecoder_1.boundary.object({ type: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.string), text: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.string) })));
186
+ /** A Claude transcript line; other fields are ignored. */
187
+ const claudeLineSchema = boundaryDecoder_1.boundary.object({
188
+ type: boundaryDecoder_1.boundary.string,
189
+ isMeta: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.boolean),
190
+ message: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.object({ content: boundaryDecoder_1.boundary.optional(contentSchema) })),
191
+ });
192
+ /** A Codex rollout line; a message is a `response_item` whose payload has a role. */
193
+ const codexLineSchema = boundaryDecoder_1.boundary.object({
194
+ type: boundaryDecoder_1.boundary.string,
195
+ payload: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.object({
196
+ type: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.string),
197
+ role: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.string),
198
+ content: boundaryDecoder_1.boundary.optional(contentSchema),
199
+ })),
200
+ });
201
+ const TEXT_PARTS = new Set(['text', 'output_text', 'input_text']);
202
+ function messageText(content) {
203
+ if (!Array.isArray(content))
204
+ return content ?? '';
205
+ return content.map(part => (part.type === undefined || TEXT_PARTS.has(part.type) ? part.text ?? '' : '')).join('\n');
206
+ }
207
+ /** Decodes one JSON line with `schema`, or undefined when the line is not that kind of record. */
208
+ function decodeLine(line, schema) {
209
+ try {
210
+ return (0, boundaryDecoder_1.decodeBoundary)(JSON.parse(line), schema);
211
+ }
212
+ catch {
213
+ return undefined;
214
+ }
215
+ }
216
+ /** The text of a complete user or assistant message on one transcript line, if the line is one. */
217
+ function lineMessage(agent, line) {
218
+ if (agent === 'claude') {
219
+ const record = decodeLine(line, claudeLineSchema);
220
+ if (!record || (record.type !== 'user' && record.type !== 'assistant') || record.isMeta === true)
221
+ return '';
222
+ return messageText(record.message?.content);
223
+ }
224
+ const record = decodeLine(line, codexLineSchema);
225
+ const payload = record?.type === 'response_item' ? record.payload : undefined;
226
+ if (payload?.type !== 'message' || (payload.role !== 'user' && payload.role !== 'assistant'))
227
+ return '';
228
+ const text = messageText(payload.content);
229
+ // Codex records its own context as user messages wrapped in tags.
230
+ return /^\s*</.test(text) ? '' : text;
231
+ }
232
+ /**
233
+ * A short, distinctive phrase from the last complete user or assistant message in the session,
234
+ * for checking that the resumed agent shows its history. Undefined when the store is not text
235
+ * this module can read (a compressed rollout, a Cursor chat).
236
+ */
237
+ function lastMessageSnippet(session) {
238
+ const main = session.files[0];
239
+ if (session.agent === 'cursor' || main.absolute.endsWith('.zst'))
240
+ return undefined;
241
+ let text;
242
+ try {
243
+ text = node_fs_1.default.readFileSync(main.absolute, 'utf8');
244
+ }
245
+ catch {
246
+ return undefined;
247
+ }
248
+ if (session.agent === 'opencode')
249
+ return openCodeLastMessage(text);
250
+ const lines = text.split('\n');
251
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
252
+ const message = lineMessage(session.agent, lines[index]);
253
+ const snippet = snippetOf(message);
254
+ if (snippet)
255
+ return snippet;
256
+ }
257
+ return undefined;
258
+ }
259
+ /** The first few words of the message's last line of prose: short enough not to wrap on screen. */
260
+ function snippetOf(message) {
261
+ const lines = message.split('\n')
262
+ .map(line => line.replace(/[`*_#>|[\]]/g, ' ').replace(/\s+/g, ' ').trim())
263
+ .filter(line => line.split(' ').length >= 3);
264
+ const last = lines.at(-1);
265
+ if (!last)
266
+ return undefined;
267
+ const words = [];
268
+ for (const word of last.split(' ')) {
269
+ if ([...words, word].join(' ').length > 40)
270
+ break;
271
+ words.push(word);
272
+ }
273
+ return words.length >= 3 ? words.join(' ') : undefined;
274
+ }
275
+ /** Whether a screen shows the snippet, ignoring line wrapping and spacing. */
276
+ function screenShows(screen, snippet) {
277
+ const normalize = (value) => value.replace(/\s+/g, ' ').toLowerCase();
278
+ return normalize(screen).includes(normalize(snippet));
279
+ }
280
+ function sha256(content) {
281
+ return (0, node_crypto_1.createHash)('sha256').update(content).digest('hex');
282
+ }
283
+ async function snapshotOriginal(session, cwd) {
284
+ if (session.agent === 'opencode') {
285
+ // Without a readable export there is nothing to compare the original with afterwards.
286
+ const text = await openCodeExport(session.id, cwd);
287
+ if (text === undefined)
288
+ throw new Error(`opencode export ${session.id} failed, so the original can't be checked after the fork`);
289
+ return { kind: 'export', id: session.id, cwd, text };
290
+ }
291
+ const snapshot = {
292
+ kind: 'files',
293
+ stamps: session.files.map(file => {
294
+ const stat = node_fs_1.default.statSync(file.absolute);
295
+ return { absolute: file.absolute, bytes: stat.size, modifiedMs: stat.mtimeMs, sha256: sha256(node_fs_1.default.readFileSync(file.absolute)) };
296
+ }),
297
+ };
298
+ // A Cursor chat is a folder; a file that appears in it changes the original too.
299
+ if (session.folder) {
300
+ snapshot.folder = session.folder;
301
+ snapshot.listing = filesUnder(session.folder);
302
+ }
303
+ return snapshot;
304
+ }
305
+ /**
306
+ * What happened to the original since the snapshot. Any change to a file fails, unless
307
+ * `appendOnly`: the sender is the running agent, so its own turn (the one running this handoff)
308
+ * keeps appending to its transcript. Then a file may grow, as long as every byte it held at the
309
+ * snapshot is unchanged; it is reported in `appended`, not `changed`.
310
+ */
311
+ async function originalChanges(snapshot, options = {}) {
312
+ if (snapshot.kind === 'export') {
313
+ const same = (await openCodeExport(snapshot.id, snapshot.cwd)) === snapshot.text;
314
+ return { changed: same ? [] : [`OpenCode session ${snapshot.id}`], appended: [] };
315
+ }
316
+ const changed = [];
317
+ const appended = [];
318
+ for (const stamp of snapshot.stamps) {
319
+ let content;
320
+ try {
321
+ content = node_fs_1.default.readFileSync(stamp.absolute);
322
+ }
323
+ catch {
324
+ changed.push(stamp.absolute);
325
+ continue;
326
+ }
327
+ const kept = content.length >= stamp.bytes && sha256(content.subarray(0, stamp.bytes)) === stamp.sha256;
328
+ if (!kept)
329
+ changed.push(stamp.absolute);
330
+ else if (content.length > stamp.bytes)
331
+ (options.appendOnly ? appended : changed).push(stamp.absolute);
332
+ // Same content but a new modification time: something rewrote or touched it, which check 4 doesn't allow.
333
+ else if (node_fs_1.default.statSync(stamp.absolute).mtimeMs !== stamp.modifiedMs)
334
+ changed.push(stamp.absolute);
335
+ }
336
+ const before = new Set(snapshot.listing ?? []);
337
+ if (snapshot.folder)
338
+ changed.push(...filesUnder(snapshot.folder).filter(file => !before.has(file)));
339
+ return { changed, appended };
340
+ }
341
+ function run(command, args, cwd, timeoutMs) {
342
+ return new Promise(resolve => {
343
+ // Agent CLIs install as .cmd shims on Windows, which only a shell can start. Every argument
344
+ // here is an id, a flag or a fixed sentence, quoted below.
345
+ const windows = process.platform === 'win32';
346
+ const child = (0, node_child_process_1.spawn)(windows ? [command, ...args.map(arg => (/^[\w.:=/-]+$/.test(arg) ? arg : `"${arg}"`))].join(' ') : command, windows ? [] : args, { cwd, shell: windows, timeout: timeoutMs, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
347
+ let stdout = '';
348
+ let stderr = '';
349
+ child.stdout.on('data', (chunk) => { stdout += chunk.toString('utf8'); });
350
+ child.stderr.on('data', (chunk) => { stderr += chunk.toString('utf8'); });
351
+ child.on('error', error => resolve({ code: 1, stdout, stderr: stderr + error.message }));
352
+ child.on('close', code => resolve({ code, stdout, stderr }));
353
+ });
354
+ }
355
+ function failure(what, result) {
356
+ const detail = (result.stderr || result.stdout).trim().split('\n').slice(-3).join(' ');
357
+ return new Error(`${what} failed: ${detail || `exit ${result.code}`}`);
358
+ }
359
+ /**
360
+ * A new session with its own id holding a copy of this one's history, made with the agent's own
361
+ * fork so the original is never written. Each fork runs one short turn of its own (never in the
362
+ * original), and a fork whose command fails is not used. Cursor has no fork command; see
363
+ * CURSOR_NO_FORK.
364
+ */
365
+ const FORK_PROMPT = 'This conversation was forked for a handoff. Reply with one word: forked.';
366
+ async function forkNativeSession(session, cwd, env = process.env) {
367
+ if (session.agent === 'claude') {
368
+ const forkId = (0, node_crypto_1.randomUUID)();
369
+ const result = await run('claude', ['-p', '--resume', session.id, '--fork-session', '--session-id', forkId, FORK_PROMPT], cwd, 300_000);
370
+ if (result.code !== 0)
371
+ throw failure(`claude --resume ${session.id} --fork-session`, result);
372
+ if (!await locateNativeSession('claude', forkId, cwd, env))
373
+ throw new Error(`claude reported a fork, but its transcript ${forkId}.jsonl is not in the store.`);
374
+ return forkId;
375
+ }
376
+ if (session.agent === 'codex') {
377
+ // A fork with no turn of its own can lead Codex back to the original when it is opened, which
378
+ // then writes to the original's file, so the fork carries one short turn.
379
+ const result = await run('codex', ['exec', 'fork', session.id, FORK_PROMPT, '--json'], cwd, 300_000);
380
+ // The id arrives before the fork's turn runs: a failed turn leaves a fork without its own turn.
381
+ if (result.code !== 0)
382
+ throw failure(`codex exec fork ${session.id}`, result);
383
+ // `--json` prints one event per line; the fork's id arrives as `thread.started`'s thread_id.
384
+ const started = result.stdout.split('\n')
385
+ .map(line => decodeLine(line, boundaryDecoder_1.boundary.object({ thread_id: boundaryDecoder_1.boundary.string }))?.thread_id)
386
+ .find(Boolean);
387
+ if (!started)
388
+ throw failure(`codex exec fork ${session.id}`, result);
389
+ return started;
390
+ }
391
+ if (session.agent === 'opencode') {
392
+ // `--format json` prints one event per line, each naming the session it belongs to: the fork.
393
+ const result = await run('opencode', ['run', '--session', session.id, '--fork', '--format', 'json', FORK_PROMPT], cwd, 300_000);
394
+ if (result.code !== 0)
395
+ throw failure(`opencode run --session ${session.id} --fork`, result);
396
+ const forked = result.stdout.split('\n')
397
+ .map(line => decodeLine(line, boundaryDecoder_1.boundary.object({ sessionID: boundaryDecoder_1.boundary.string }))?.sessionID)
398
+ .find(id => id !== undefined && id !== session.id);
399
+ if (!forked)
400
+ throw failure(`opencode run --session ${session.id} --fork`, result);
401
+ return forked;
402
+ }
403
+ throw new Error(exports.CURSOR_NO_FORK);
404
+ }
405
+ /**
406
+ * Why a Cursor session can't be continued by a second agent on the same machine: a copy of its
407
+ * chat folder under a new id still writes to the original chat once it is used (QA saw it).
408
+ */
409
+ exports.CURSOR_NO_FORK = 'Cursor has no fork command, and a copied Cursor chat still writes to the original chat once it is used, so a second Cursor agent on this machine can\'t continue your session without changing it';
410
+ //# sourceMappingURL=nativeSession.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nativeSession.js","sourceRoot":"","sources":["../src/nativeSession.ts"],"names":[],"mappings":";;;;;;AAoCA,sCAEC;AA8CD,kDAwCC;AA+CD,0DAYC;AA4ED,gDAiBC;AAkBD,kCAGC;AAUD,wBAEC;AAOD,4CAoBC;AAgBD,0CAwBC;AA+BD,8CAgCC;AAvbD,2DAA2C;AAC3C,6CAAqD;AACrD,sDAAyB;AACzB,sDAAyB;AACzB,0DAA6B;AAC7B,uDAAkF;AA+BlF,SAAgB,aAAa,CAAC,KAAyB;IACrD,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,UAAU,CAAC;AAC/F,CAAC;AAED,SAAS,aAAa,CAAC,GAAsB;IAC3C,OAAO,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,WAAW,IAAI,iBAAE,CAAC,OAAO,EAAE,CAAC;AACrD,CAAC;AASD,SAAS,SAAS,CAAC,KAAuC,EAAE,GAAsB;IAChF,MAAM,IAAI,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IAChC,IAAI,KAAK,KAAK,QAAQ;QAAE,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,iBAAiB,IAAI,mBAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IAClH,IAAI,KAAK,KAAK,OAAO;QAAE,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,UAAU,IAAI,mBAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACxG,OAAO,EAAE,IAAI,EAAE,mBAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;AACnE,CAAC;AAED,SAAS,OAAO,CAAC,SAAiB;IAChC,IAAI,CAAC;QACH,OAAO,iBAAE,CAAC,WAAW,CAAC,SAAS,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,SAAiB;IACnC,OAAO,OAAO,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;QACxC,MAAM,IAAI,GAAG,mBAAI,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAC9C,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/E,CAAC,CAAC,CAAC;AACL,CAAC;AAED,wGAAwG;AACxG,SAAS,SAAS,CAAC,QAAgB,EAAE,IAAY,EAAE,QAAgB;IACjE,MAAM,QAAQ,GAAG,mBAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,KAAK,CAAC,mBAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACzE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,QAAQ,IAAI,QAAQ,EAAE,EAAE,KAAK,EAAE,iBAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;AAC1F,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,mBAAmB,CAAC,KAAkB,EAAE,EAAU,EAAE,GAAW,EAAE,MAAyB,OAAO,CAAC,GAAG;IACzH,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,EAAE,CAAC;QAAE,OAAO,SAAS,CAAC;IACpD,IAAI,KAAK,KAAK,UAAU;QAAE,OAAO,qBAAqB,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IAChE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,SAAS,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACjD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,MAA0B,CAAC;IAC/B,IAAI,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvB,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,mBAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;YAChG,MAAM,UAAU,GAAG,mBAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;YAC7D,MAAM,UAAU,GAAG,mBAAI,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;YACxD,IAAI,iBAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC9B,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;gBACvB,6EAA6E;gBAC7E,MAAM,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,mBAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;YACxD,CAAC;QACH,CAAC;IACH,CAAC;SAAM,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;QAC7B,MAAM,OAAO,GAAG,IAAI,MAAM,CAAC,cAAc,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,sBAAsB,CAAC,CAAC;QAC1F,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,mBAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC;YAC3D,IAAI,OAAO,CAAC,IAAI,CAAC,mBAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;gBAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;SAAM,CAAC;QACN,KAAK,MAAM,SAAS,IAAI,OAAO,CAAC,mBAAI,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;YAC/F,MAAM,IAAI,GAAG,mBAAI,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAC1D,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;YAC/B,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;gBACjB,MAAM,GAAG,IAAI,CAAC;gBACd,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;gBACrB,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;IACH,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACzC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,QAAQ,KAAK,CAAC,MAAM,sBAAsB,EAAE,qBAAqB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAC;IACxJ,CAAC;IACD,MAAM,OAAO,GAAkB,EAAE,KAAK,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC;IAClI,IAAI,MAAM;QAAE,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC;IACpC,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,kGAAkG;AAClG,MAAM,oBAAoB,GAAG,0BAAQ,CAAC,MAAM,CAAC;IAC3C,IAAI,EAAE,0BAAQ,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,0BAAQ,CAAC,MAAM,EAAE,CAAC;IAC9C,QAAQ,EAAE,0BAAQ,CAAC,KAAK,CAAC,0BAAQ,CAAC,MAAM,CAAC;QACvC,IAAI,EAAE,0BAAQ,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,0BAAQ,CAAC,QAAQ,CAAC,0BAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,0BAAQ,CAAC,QAAQ,CAAC,0BAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3G,KAAK,EAAE,0BAAQ,CAAC,QAAQ,CAAC,0BAAQ,CAAC,KAAK,CAAC,0BAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,0BAAQ,CAAC,QAAQ,CAAC,0BAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,0BAAQ,CAAC,QAAQ,CAAC,0BAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;KAClJ,CAAC,CAAC;CACJ,CAAC,CAAC;AAEH,KAAK,UAAU,cAAc,CAAC,EAAU,EAAE,GAAW;IACnD,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,UAAU,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;IACnE,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACxC,yDAAyD;IACzD,OAAO,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC7E,CAAC;AAED,+FAA+F;AAC/F,KAAK,UAAU,qBAAqB,CAAC,EAAU,EAAE,GAAW;IAC1D,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IAC3C,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACzC,IAAI,CAAC;QACH,IAAI,IAAA,gCAAc,EAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,oBAAoB,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE;YAAE,OAAO,SAAS,CAAC;IAC9F,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,MAAM,GAAG,iBAAE,CAAC,WAAW,CAAC,mBAAI,CAAC,IAAI,CAAC,iBAAE,CAAC,MAAM,EAAE,EAAE,mBAAmB,CAAC,CAAC,CAAC;IAC3E,MAAM,QAAQ,GAAG,mBAAI,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;IACjD,iBAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAClD,OAAO;QACL,KAAK,EAAE,UAAU,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI;QACxD,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,2BAA2B,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;KAClG,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,SAAS,UAAU,CAAC,MAAc;IAChC,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAClC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;AACnD,CAAC;AAED;;;;GAIG;AACH,SAAgB,uBAAuB,CAAC,QAAgB,EAAE,KAAa;IACrE,MAAM,GAAG,GAAG,CAAC,IAAY,EAAwB,EAAE;QACjD,IAAI,CAAC;YACH,OAAO,IAAA,gCAAc,EAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,oBAAoB,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,IAAI,KAAK,EAAE,CAAC,CAAC;QAC7I,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC,CAAC;IACF,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC;IAC1B,IAAI,CAAC,MAAM;QAAE,OAAO,MAAM,CAAC,iBAAiB,CAAC;IAC7C,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;AACnD,CAAC;AAED,SAAS,mBAAmB,CAAC,IAAY;IACvC,IAAI,CAAC;QACH,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAA,gCAAc,EAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,oBAAoB,CAAC,CAAC;QAC5E,KAAK,MAAM,OAAO,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;YAC9C,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,KAAK,WAAW;gBAAE,SAAS;YAChF,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YAC9H,IAAI,OAAO;gBAAE,OAAO,OAAO,CAAC;QAC9B,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,qGAAqG;AACrG,MAAM,aAAa,GAAG,0BAAQ,CAAC,KAAK,CAClC,0BAAQ,CAAC,MAAM,EACf,0BAAQ,CAAC,KAAK,CAAC,0BAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,0BAAQ,CAAC,QAAQ,CAAC,0BAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,0BAAQ,CAAC,QAAQ,CAAC,0BAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CACxH,CAAC;AAGF,0DAA0D;AAC1D,MAAM,gBAAgB,GAAG,0BAAQ,CAAC,MAAM,CAAC;IACvC,IAAI,EAAE,0BAAQ,CAAC,MAAM;IACrB,MAAM,EAAE,0BAAQ,CAAC,QAAQ,CAAC,0BAAQ,CAAC,OAAO,CAAC;IAC3C,OAAO,EAAE,0BAAQ,CAAC,QAAQ,CAAC,0BAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,0BAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;CAC3F,CAAC,CAAC;AAEH,qFAAqF;AACrF,MAAM,eAAe,GAAG,0BAAQ,CAAC,MAAM,CAAC;IACtC,IAAI,EAAE,0BAAQ,CAAC,MAAM;IACrB,OAAO,EAAE,0BAAQ,CAAC,QAAQ,CAAC,0BAAQ,CAAC,MAAM,CAAC;QACzC,IAAI,EAAE,0BAAQ,CAAC,QAAQ,CAAC,0BAAQ,CAAC,MAAM,CAAC;QACxC,IAAI,EAAE,0BAAQ,CAAC,QAAQ,CAAC,0BAAQ,CAAC,MAAM,CAAC;QACxC,OAAO,EAAE,0BAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC;KAC1C,CAAC,CAAC;CACJ,CAAC,CAAC;AAEH,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,aAAa,EAAE,YAAY,CAAC,CAAC,CAAC;AAElE,SAAS,WAAW,CAAC,OAAmC;IACtD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,IAAI,EAAE,CAAC;IAClD,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACvH,CAAC;AAED,kGAAkG;AAClG,SAAS,UAAU,CAAQ,IAAY,EAAE,MAA6B;IACpE,IAAI,CAAC;QACH,OAAO,IAAA,gCAAc,EAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,mGAAmG;AACnG,SAAS,WAAW,CAAC,KAAkB,EAAE,IAAY;IACnD,IAAI,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvB,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;QAClD,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,MAAM,IAAI,MAAM,CAAC,IAAI,KAAK,WAAW,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI;YAAE,OAAO,EAAE,CAAC;QAC5G,OAAO,WAAW,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC9C,CAAC;IACD,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC;IACjD,MAAM,OAAO,GAAG,MAAM,EAAE,IAAI,KAAK,eAAe,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;IAC9E,IAAI,OAAO,EAAE,IAAI,KAAK,SAAS,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW,CAAC;QAAE,OAAO,EAAE,CAAC;IACxG,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C,kEAAkE;IAClE,OAAO,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACxC,CAAC;AAED;;;;GAIG;AACH,SAAgB,kBAAkB,CAAC,OAAsB;IACvD,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC9B,IAAI,OAAO,CAAC,KAAK,KAAK,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,SAAS,CAAC;IACnF,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,iBAAE,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,OAAO,CAAC,KAAK,KAAK,UAAU;QAAE,OAAO,mBAAmB,CAAC,IAAI,CAAC,CAAC;IACnE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC/B,KAAK,IAAI,KAAK,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QAC1D,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QACzD,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;QACnC,IAAI,OAAO;YAAE,OAAO,OAAO,CAAC;IAC9B,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,mGAAmG;AACnG,SAAS,SAAS,CAAC,OAAe;IAChC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC;SAC9B,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;SAC1E,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;IAC/C,MAAM,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1B,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5B,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACnC,IAAI,CAAC,GAAG,KAAK,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,EAAE;YAAE,MAAM;QAClD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnB,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACzD,CAAC;AAED,8EAA8E;AAC9E,SAAgB,WAAW,CAAC,MAAc,EAAE,OAAe;IACzD,MAAM,SAAS,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IACtF,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;AACxD,CAAC;AAUD,SAAgB,MAAM,CAAC,OAAe;IACpC,OAAO,IAAA,wBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC5D,CAAC;AAOM,KAAK,UAAU,gBAAgB,CAAC,OAAsB,EAAE,GAAW;IACxE,IAAI,OAAO,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;QACjC,sFAAsF;QACtF,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;QACnD,IAAI,IAAI,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,OAAO,CAAC,EAAE,0DAA0D,CAAC,CAAC;QACjI,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;IACvD,CAAC;IACD,MAAM,QAAQ,GAAqB;QACjC,IAAI,EAAE,OAAO;QACb,MAAM,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;YAC/B,MAAM,IAAI,GAAG,iBAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACxC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,iBAAE,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;QACjI,CAAC,CAAC;KACH,CAAC;IACF,iFAAiF;IACjF,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,QAAQ,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QACjC,QAAQ,CAAC,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAChD,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAUD;;;;;GAKG;AACI,KAAK,UAAU,eAAe,CAAC,QAA0B,EAAE,UAAoC,EAAE;IACtG,IAAI,QAAQ,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAG,CAAC,MAAM,cAAc,CAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,QAAQ,CAAC,IAAI,CAAC;QACjF,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,oBAAoB,QAAQ,CAAC,EAAE,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IACpF,CAAC;IACD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;QACpC,IAAI,OAAe,CAAC;QACpB,IAAI,CAAC;YACH,OAAO,GAAG,iBAAE,CAAC,YAAY,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC5C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YAC7B,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,IAAI,KAAK,CAAC,KAAK,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,KAAK,CAAC,MAAM,CAAC;QACxG,IAAI,CAAC,IAAI;YAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;aACnC,IAAI,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC,KAAK;YAAE,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACtG,0GAA0G;aACrG,IAAI,iBAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,OAAO,KAAK,KAAK,CAAC,UAAU;YAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAClG,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;IAC/C,IAAI,QAAQ,CAAC,MAAM;QAAE,OAAO,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpG,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;AAC/B,CAAC;AAED,SAAS,GAAG,CAAC,OAAe,EAAE,IAAc,EAAE,GAAW,EAAE,SAAiB;IAC1E,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;QAC3B,4FAA4F;QAC5F,2DAA2D;QAC3D,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC;QAC7C,MAAM,KAAK,GAAG,IAAA,0BAAK,EAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,EAC5H,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1H,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,GAAG,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAClF,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,GAAG,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAClF,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACzF,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,OAAO,CAAC,IAAY,EAAE,MAA+D;IAC5F,MAAM,MAAM,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACvF,OAAO,IAAI,KAAK,CAAC,GAAG,IAAI,YAAY,MAAM,IAAI,QAAQ,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;AACzE,CAAC;AAED;;;;;GAKG;AACH,MAAM,WAAW,GAAG,0EAA0E,CAAC;AAExF,KAAK,UAAU,iBAAiB,CAAC,OAAsB,EAAE,GAAW,EAAE,MAAyB,OAAO,CAAC,GAAG;IAC/G,IAAI,OAAO,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,IAAA,wBAAU,GAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;QACxI,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;YAAE,MAAM,OAAO,CAAC,mBAAmB,OAAO,CAAC,EAAE,iBAAiB,EAAE,MAAM,CAAC,CAAC;QAC7F,IAAI,CAAC,MAAM,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,8CAA8C,MAAM,6BAA6B,CAAC,CAAC;QAC/J,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,IAAI,OAAO,CAAC,KAAK,KAAK,OAAO,EAAE,CAAC;QAC9B,8FAA8F;QAC9F,0EAA0E;QAC1E,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;QACrG,gGAAgG;QAChG,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;YAAE,MAAM,OAAO,CAAC,mBAAmB,OAAO,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC;QAC9E,6FAA6F;QAC7F,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;aACtC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,0BAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,0BAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,SAAS,CAAC;aACzF,IAAI,CAAC,OAAO,CAAC,CAAC;QACjB,IAAI,CAAC,OAAO;YAAE,MAAM,OAAO,CAAC,mBAAmB,OAAO,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC;QACrE,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,IAAI,OAAO,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;QACjC,8FAA8F;QAC9F,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;QAChI,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;YAAE,MAAM,OAAO,CAAC,0BAA0B,OAAO,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;QAC5F,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;aACrC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,0BAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,0BAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,SAAS,CAAC;aACzF,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,SAAS,IAAI,EAAE,KAAK,OAAO,CAAC,EAAE,CAAC,CAAC;QACrD,IAAI,CAAC,MAAM;YAAE,MAAM,OAAO,CAAC,0BAA0B,OAAO,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;QAClF,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,sBAAc,CAAC,CAAC;AAClC,CAAC;AAED;;;GAGG;AACU,QAAA,cAAc,GAAG,mMAAmM,CAAC"}
@@ -13,10 +13,13 @@ export interface TailnetMachine {
13
13
  /** Signed in to this machine's own login. Other people's machines let in only who their visibility allows. */
14
14
  mine: boolean;
15
15
  }
16
- export type Tailnet = {
16
+ export type Tailnet =
17
+ /** `httpsCertificates` is the tailnet-wide setting that workspaces need on every machine. */
18
+ {
17
19
  ok: true;
18
20
  self: TailnetMachine;
19
21
  machines: TailnetMachine[];
22
+ httpsCertificates: boolean;
20
23
  } | {
21
24
  ok: false;
22
25
  reason: string;
@@ -1 +1 @@
1
- {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAA2D,KAAK,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAWlH,KAAK,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC;AAE/C,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,SAAS,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,8CAA8C;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,8GAA8G;IAC9G,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,MAAM,OAAO,GACf;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,cAAc,CAAC;IAAC,QAAQ,EAAE,cAAc,EAAE,CAAA;CAAE,GAC9D;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AA0D/C;;;GAGG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAsCpD;AAyCD,6FAA6F;AAC7F,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,cAAc,EAAE,GAAG,cAAc,CAWjG;AAoCD,wBAAgB,eAAe,CAAC,OAAO,EAAE,cAAc,GAAG,kBAAkB,CAE3E;AAuCD,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA0B1E;AAED,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAKlG;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAU1E;AAED,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAU3E;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA8B1E;AAkCD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,IAAI,GAAG,KAAK,CAAC;IACpB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,SAAS,CAAC;QAAC,MAAM,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACrG,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,6EAA6E;AAC7E,wBAAsB,oBAAoB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAkBtF;AAkBD,6GAA6G;AAC7G,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,CAAC,cAAc,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA0B/G"}
1
+ {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAA2D,KAAK,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAiBlH,KAAK,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC;AAE/C,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,SAAS,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,8CAA8C;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,8GAA8G;IAC9G,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,MAAM,OAAO;AACjB,6FAA6F;AAC3F;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,cAAc,CAAC;IAAC,QAAQ,EAAE,cAAc,EAAE,CAAC;IAAC,iBAAiB,EAAE,OAAO,CAAA;CAAE,GAC1F;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AA2D/C;;;GAGG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAsCpD;AAyCD,6FAA6F;AAC7F,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,cAAc,EAAE,GAAG,cAAc,CAWjG;AAoCD,wBAAgB,eAAe,CAAC,OAAO,EAAE,cAAc,GAAG,kBAAkB,CAE3E;AA2CD,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA0B1E;AAED,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAKlG;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAU1E;AAED,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAU3E;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA8B1E;AAkCD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,IAAI,GAAG,KAAK,CAAC;IACpB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,SAAS,CAAC;QAAC,MAAM,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACrG,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,6EAA6E;AAC7E,wBAAsB,oBAAoB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAkBtF;AAkBD,6GAA6G;AAC7G,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,CAAC,cAAc,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA0B/G"}
package/dist/workspace.js CHANGED
@@ -19,8 +19,14 @@ const node_os_1 = __importDefault(require("node:os"));
19
19
  const node_path_1 = __importDefault(require("node:path"));
20
20
  const boundaryDecoder_1 = require("./boundaryDecoder");
21
21
  const daemonClient_1 = require("./daemonClient");
22
- /** Tailnet port where each joined machine's Pane serves workspaces. */
23
- const WORKSPACE_HTTPS_PORT = 8443;
22
+ /**
23
+ * Tailnet port where each joined machine's Pane serves workspaces. Pane reads PANE_WORKSPACES_PORT
24
+ * to serve somewhere else (a development build beside an installed Pane), and so does runpane.
25
+ */
26
+ function workspaceHttpsPort() {
27
+ const port = Number(process.env.PANE_WORKSPACES_PORT);
28
+ return Number.isInteger(port) && port > 0 && port < 65536 ? port : 8443;
29
+ }
24
30
  const TAILSCALE_TIMEOUT_MS = 3_000;
25
31
  const PROBE_TIMEOUT_MS = 4_000;
26
32
  const LOCAL_STATUS_TIMEOUT_MS = 1_500;
@@ -42,6 +48,7 @@ const statusSchema = boundaryDecoder_1.boundary.object({
42
48
  Self: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.nullable(peerSchema)),
43
49
  Peer: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.nullable(boundaryDecoder_1.boundary.jsonObject)),
44
50
  User: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.nullable(boundaryDecoder_1.boundary.jsonObject)),
51
+ CertDomains: boundaryDecoder_1.boundary.optional(boundaryDecoder_1.boundary.nullable(boundaryDecoder_1.boundary.array(boundaryDecoder_1.boundary.string))),
45
52
  });
46
53
  const userSchema = boundaryDecoder_1.boundary.object({ LoginName: boundaryDecoder_1.boundary.string });
47
54
  const machineInfoSchema = boundaryDecoder_1.boundary.object({
@@ -124,7 +131,7 @@ async function readTailnet() {
124
131
  || left.name.localeCompare(right.name));
125
132
  if (!self)
126
133
  return { ok: false, reason: `Tailscale reports an unsupported OS (${status.Self.OS})`, fix: 'Run Pane on macOS, Windows, or Linux.' };
127
- return { ok: true, self, machines };
134
+ return { ok: true, self, machines, httpsCertificates: Boolean(status.CertDomains?.length) };
128
135
  }
129
136
  const TAILSCALE_OS_NAMES = new Map([['macos', 'macOS'], ['windows', 'Windows'], ['linux', 'Linux']]);
130
137
  function decodePeer(value) {
@@ -208,9 +215,12 @@ function describeMachine(machine) {
208
215
  return `${machine.name} (${machine.os}, ${machine.online ? 'online' : 'offline'}${machine.mine ? '' : `, owner ${machine.owner}`})`;
209
216
  }
210
217
  function workspaceTarget(machine) {
211
- return { machine: machine.name, baseUrl: `https://${machine.dnsName}:${WORKSPACE_HTTPS_PORT}` };
218
+ return { machine: machine.name, baseUrl: `https://${machine.dnsName}:${workspaceHttpsPort()}` };
212
219
  }
213
- async function probeMachine(machine) {
220
+ const NOT_ANSWERING = 'Pane is not answering there; it needs a Pane with workspaces running and signed in to Tailscale';
221
+ /** With the tailnet-wide setting off, no machine can answer, so name the setting instead of the machine. */
222
+ const CERTIFICATES_OFF = 'this tailnet has HTTPS certificates turned off. Fix: Turn on HTTPS Certificates at https://login.tailscale.com/admin/dns.';
223
+ async function probeMachine(machine, notAnswering = NOT_ANSWERING) {
214
224
  if (!machine.online)
215
225
  return { error: 'offline' };
216
226
  try {
@@ -218,7 +228,7 @@ async function probeMachine(machine) {
218
228
  }
219
229
  catch (error) {
220
230
  if (error instanceof daemonClient_1.PaneDaemonClientError && (error.code === 'ERR_WORKSPACE_UNREACHABLE' || error.code === 'ERR_WORKSPACE_TIMEOUT')) {
221
- return { error: 'Pane is not answering there; it needs a Pane with workspaces running and signed in to Tailscale' };
231
+ return { error: notAnswering };
222
232
  }
223
233
  return { error: error instanceof Error ? error.message : String(error) };
224
234
  }
@@ -255,7 +265,7 @@ async function runWorkspaceList(parsed) {
255
265
  const rows = await Promise.all(tailnet.machines.map(async (machine) => ({
256
266
  machine,
257
267
  probe: probed.has(machine) || !machine.online
258
- ? await probeMachine(machine)
268
+ ? await probeMachine(machine, tailnet.httpsCertificates ? NOT_ANSWERING : CERTIFICATES_OFF)
259
269
  : { error: `not checked (Pane checks the first ${MAX_PROBED_MACHINES} online machines)` },
260
270
  })));
261
271
  if (parsed.json) {