@cueloop/daemon 0.1.0-alpha.90 → 0.1.0-alpha.92
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/package.json +3 -3
- package/src/client.ts +1 -0
- package/src/herdr-thread-surface-store.ts +2 -0
- package/src/herdr-thread-surface.test.ts +54 -0
- package/src/herdr-thread-surface.ts +35 -12
- package/src/server.ts +1 -0
- package/src/validate.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cueloop/daemon",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.92",
|
|
4
4
|
"description": "The cueloop session daemon and its client: socket protocol, atomic store, resumable waits",
|
|
5
5
|
"homepage": "https://github.com/mmurakaru/cueloop#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"access": "public"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@cueloop/extension-api": "0.1.0-alpha.
|
|
43
|
-
"@cueloop/schema": "0.1.0-alpha.
|
|
42
|
+
"@cueloop/extension-api": "0.1.0-alpha.92",
|
|
43
|
+
"@cueloop/schema": "0.1.0-alpha.92",
|
|
44
44
|
"@pierre/diffs": "^1.4.1",
|
|
45
45
|
"effect": "^4.0.0-rc.116",
|
|
46
46
|
"smol-toml": "^1.8.0",
|
package/src/client.ts
CHANGED
|
@@ -86,6 +86,7 @@ const HerdrThreadSurfaceResultSchema = v.nullable(
|
|
|
86
86
|
tabId: v.string(),
|
|
87
87
|
paneId: v.string(),
|
|
88
88
|
mode: v.optional(v.picklist(["tab", "pane"])),
|
|
89
|
+
layout: v.optional(v.literal("pair")),
|
|
89
90
|
}),
|
|
90
91
|
);
|
|
91
92
|
const GhosttyThreadSurfaceResultSchema = v.nullable(v.object({ terminalId: v.string() }));
|
|
@@ -5,6 +5,7 @@ import { herdrThreadSurfacesPath } from "./paths";
|
|
|
5
5
|
/** Herdr-native handle, stored outside the canonical Thread. */
|
|
6
6
|
export interface HerdrThreadSurfaceHandle {
|
|
7
7
|
mode?: "tab" | "pane";
|
|
8
|
+
layout?: "pair";
|
|
8
9
|
tabId: string;
|
|
9
10
|
paneId: string;
|
|
10
11
|
}
|
|
@@ -13,6 +14,7 @@ const HerdrThreadSurfaceHandleSchema = v.object({
|
|
|
13
14
|
tabId: v.string(),
|
|
14
15
|
paneId: v.string(),
|
|
15
16
|
mode: v.optional(v.picklist(["tab", "pane"])),
|
|
17
|
+
layout: v.optional(v.literal("pair")),
|
|
16
18
|
});
|
|
17
19
|
const HerdrThreadSurfaceMapSchema = v.record(v.string(), v.unknown());
|
|
18
20
|
|
|
@@ -358,6 +358,60 @@ describe("openHerdrThreadSurface", () => {
|
|
|
358
358
|
expect(store.saved()).toEqual({ tabId: "w9:t9", paneId: "w9:p9" });
|
|
359
359
|
});
|
|
360
360
|
|
|
361
|
+
test("opens a tab when the recorded live surface is a pane", async () => {
|
|
362
|
+
const stub = makeStub("pane-to-tab", true);
|
|
363
|
+
const herdrEnvironment = {
|
|
364
|
+
HERDR_ENV: "1",
|
|
365
|
+
HERDR_PANE_ID: "w1:p1",
|
|
366
|
+
HERDR_BIN_PATH: stub.binPath,
|
|
367
|
+
};
|
|
368
|
+
const store = fakePersistence({ mode: "pane", tabId: "w1:t1", paneId: "w1:p3" });
|
|
369
|
+
|
|
370
|
+
expect(
|
|
371
|
+
await openHerdrThreadSurface(
|
|
372
|
+
newSession(),
|
|
373
|
+
store.persistence,
|
|
374
|
+
herdrEnvironment,
|
|
375
|
+
"tab",
|
|
376
|
+
"pair",
|
|
377
|
+
),
|
|
378
|
+
).toBe("opened");
|
|
379
|
+
expect(readLines(stub.logPath)).toEqual([
|
|
380
|
+
"tab create --cwd /repo/work --label Rollout Plan --focus",
|
|
381
|
+
"pane send-text w1:p2 cueloop pair ses_new1",
|
|
382
|
+
"pane send-keys w1:p2 enter",
|
|
383
|
+
"pane close w1:p3",
|
|
384
|
+
]);
|
|
385
|
+
expect(store.saved()).toEqual({ tabId: "w1:t2", paneId: "w1:p2", layout: "pair" });
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
test("replaces a generic review tab with the pairing layout", async () => {
|
|
389
|
+
const stub = makeStub("review-to-pair", true);
|
|
390
|
+
const herdrEnvironment = {
|
|
391
|
+
HERDR_ENV: "1",
|
|
392
|
+
HERDR_PANE_ID: "w1:p1",
|
|
393
|
+
HERDR_BIN_PATH: stub.binPath,
|
|
394
|
+
};
|
|
395
|
+
const store = fakePersistence({ tabId: "w9:t9", paneId: "w9:p9" });
|
|
396
|
+
|
|
397
|
+
expect(
|
|
398
|
+
await openHerdrThreadSurface(
|
|
399
|
+
newSession(),
|
|
400
|
+
store.persistence,
|
|
401
|
+
herdrEnvironment,
|
|
402
|
+
"tab",
|
|
403
|
+
"pair",
|
|
404
|
+
),
|
|
405
|
+
).toBe("opened");
|
|
406
|
+
expect(readLines(stub.logPath)).toEqual([
|
|
407
|
+
"tab create --cwd /repo/work --label Rollout Plan --focus",
|
|
408
|
+
"pane send-text w1:p2 cueloop pair ses_new1",
|
|
409
|
+
"pane send-keys w1:p2 enter",
|
|
410
|
+
"tab close w9:t9",
|
|
411
|
+
]);
|
|
412
|
+
expect(store.saved()).toEqual({ tabId: "w1:t2", paneId: "w1:p2", layout: "pair" });
|
|
413
|
+
});
|
|
414
|
+
|
|
361
415
|
test("a persistence failure never escapes - review creation stays best-effort", async () => {
|
|
362
416
|
const stub = makeStub("gated-persist-fail");
|
|
363
417
|
const env = { HERDR_ENV: "1", HERDR_PANE_ID: "w1:p1", HERDR_BIN_PATH: stub.binPath };
|
|
@@ -60,6 +60,7 @@ export interface OpenHerdrThreadTabOptions {
|
|
|
60
60
|
binPath: string;
|
|
61
61
|
label: string;
|
|
62
62
|
workspaceId?: string;
|
|
63
|
+
layout?: "pair";
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
/** Daemon-owned native handles, separate from canonical Thread records. */
|
|
@@ -72,7 +73,7 @@ export interface HerdrThreadSurfacePersistence {
|
|
|
72
73
|
export function openHerdrThreadTab(
|
|
73
74
|
options: OpenHerdrThreadTabOptions,
|
|
74
75
|
): HerdrThreadSurfaceHandle | null {
|
|
75
|
-
const { sessionId, cwd, binPath, label, workspaceId } = options;
|
|
76
|
+
const { sessionId, cwd, binPath, label, workspaceId, layout } = options;
|
|
76
77
|
|
|
77
78
|
try {
|
|
78
79
|
const created = runHerdrCommand(
|
|
@@ -99,13 +100,13 @@ export function openHerdrThreadTab(
|
|
|
99
100
|
|
|
100
101
|
if (!paneId || !tabId) return null;
|
|
101
102
|
|
|
102
|
-
if (!sendCueloopThreadCommand(binPath, paneId, sessionId)) {
|
|
103
|
-
|
|
103
|
+
if (!sendCueloopThreadCommand(binPath, paneId, sessionId, layout)) {
|
|
104
|
+
closeHerdrThreadSurface(binPath, "tab", tabId);
|
|
104
105
|
|
|
105
106
|
return null;
|
|
106
107
|
}
|
|
107
108
|
|
|
108
|
-
return { tabId, paneId };
|
|
109
|
+
return { tabId, paneId, ...(layout && { layout }) };
|
|
109
110
|
} catch {
|
|
110
111
|
return null;
|
|
111
112
|
}
|
|
@@ -118,13 +119,14 @@ export interface OpenHerdrThreadPaneOptions {
|
|
|
118
119
|
binPath: string;
|
|
119
120
|
sourcePaneId: string;
|
|
120
121
|
tabId: string;
|
|
122
|
+
layout?: "pair";
|
|
121
123
|
}
|
|
122
124
|
|
|
123
125
|
/** Split the calling Herdr pane right at 50 percent and launch cueloop in it. */
|
|
124
126
|
export function openHerdrThreadPane(
|
|
125
127
|
options: OpenHerdrThreadPaneOptions,
|
|
126
128
|
): HerdrThreadSurfaceHandle | null {
|
|
127
|
-
const { sessionId, cwd, binPath, sourcePaneId, tabId } = options;
|
|
129
|
+
const { sessionId, cwd, binPath, sourcePaneId, tabId, layout } = options;
|
|
128
130
|
|
|
129
131
|
try {
|
|
130
132
|
const created = runHerdrCommand(
|
|
@@ -149,19 +151,19 @@ export function openHerdrThreadPane(
|
|
|
149
151
|
const paneId = parsed.success ? parsed.output.result?.pane?.pane_id : undefined;
|
|
150
152
|
|
|
151
153
|
if (!paneId) return null;
|
|
152
|
-
if (!sendCueloopThreadCommand(binPath, paneId, sessionId)) {
|
|
153
|
-
|
|
154
|
+
if (!sendCueloopThreadCommand(binPath, paneId, sessionId, layout)) {
|
|
155
|
+
closeHerdrThreadSurface(binPath, "pane", paneId);
|
|
154
156
|
|
|
155
157
|
return null;
|
|
156
158
|
}
|
|
157
159
|
|
|
158
|
-
return { mode: "pane", tabId, paneId };
|
|
160
|
+
return { mode: "pane", tabId, paneId, ...(layout && { layout }) };
|
|
159
161
|
} catch {
|
|
160
162
|
return null;
|
|
161
163
|
}
|
|
162
164
|
}
|
|
163
165
|
|
|
164
|
-
function
|
|
166
|
+
function closeHerdrThreadSurface(binPath: string, kind: "tab" | "pane", id: string): void {
|
|
165
167
|
try {
|
|
166
168
|
runHerdrCommand([binPath, kind, "close", id], {
|
|
167
169
|
stdout: "ignore",
|
|
@@ -173,8 +175,14 @@ function closeUnlaunchedHerdrSurface(binPath: string, kind: "tab" | "pane", id:
|
|
|
173
175
|
}
|
|
174
176
|
}
|
|
175
177
|
|
|
176
|
-
function sendCueloopThreadCommand(
|
|
177
|
-
|
|
178
|
+
function sendCueloopThreadCommand(
|
|
179
|
+
binPath: string,
|
|
180
|
+
paneId: string,
|
|
181
|
+
sessionId: string,
|
|
182
|
+
layout?: "pair",
|
|
183
|
+
): boolean {
|
|
184
|
+
const command = layout === "pair" ? `cueloop pair ${sessionId}` : `cueloop ${sessionId}`;
|
|
185
|
+
const typed = runHerdrCommand([binPath, "pane", "send-text", paneId, command], {
|
|
178
186
|
stdout: "ignore",
|
|
179
187
|
stderr: "ignore",
|
|
180
188
|
timeout: HERDR_SPAWN_TIMEOUT_MS,
|
|
@@ -292,6 +300,7 @@ export async function openHerdrThreadSurface(
|
|
|
292
300
|
persistence: HerdrThreadSurfacePersistence,
|
|
293
301
|
env: HerdrEnv = process.env,
|
|
294
302
|
mode: HerdrThreadSurface = loadHerdrThreadSurface(),
|
|
303
|
+
layout?: "pair",
|
|
295
304
|
): Promise<ThreadSurfaceOpenStatus> {
|
|
296
305
|
const herdr = detectHerdr(env);
|
|
297
306
|
|
|
@@ -300,7 +309,12 @@ export async function openHerdrThreadSurface(
|
|
|
300
309
|
if (mode === "none") return "disabled";
|
|
301
310
|
const recorded = await recallHerdrThreadSurface(persistence, session.id);
|
|
302
311
|
|
|
303
|
-
if (
|
|
312
|
+
if (
|
|
313
|
+
recorded &&
|
|
314
|
+
(recorded.mode ?? "tab") === mode &&
|
|
315
|
+
recorded.layout === layout &&
|
|
316
|
+
herdrPaneAlive(herdr.binPath, recorded.paneId)
|
|
317
|
+
) {
|
|
304
318
|
return focusHerdrThreadSurface(herdr.binPath, recorded) ? "focused" : "failed";
|
|
305
319
|
}
|
|
306
320
|
const cwd = session.artifact.meta.cwd ?? session.workspace.repoRoot;
|
|
@@ -313,6 +327,7 @@ export async function openHerdrThreadSurface(
|
|
|
313
327
|
binPath: herdr.binPath,
|
|
314
328
|
sourcePaneId: herdr.paneId,
|
|
315
329
|
tabId: env.HERDR_TAB_ID,
|
|
330
|
+
layout,
|
|
316
331
|
})
|
|
317
332
|
: null
|
|
318
333
|
: openHerdrThreadTab({
|
|
@@ -321,9 +336,17 @@ export async function openHerdrThreadSurface(
|
|
|
321
336
|
binPath: herdr.binPath,
|
|
322
337
|
label: session.artifact.meta.title ?? session.id,
|
|
323
338
|
workspaceId: env.HERDR_WORKSPACE_ID,
|
|
339
|
+
layout,
|
|
324
340
|
});
|
|
325
341
|
|
|
326
342
|
if (!opened) return "failed";
|
|
343
|
+
if (recorded && recorded.paneId !== opened.paneId) {
|
|
344
|
+
closeHerdrThreadSurface(
|
|
345
|
+
herdr.binPath,
|
|
346
|
+
recorded.mode === "pane" ? "pane" : "tab",
|
|
347
|
+
recorded.mode === "pane" ? recorded.paneId : recorded.tabId,
|
|
348
|
+
);
|
|
349
|
+
}
|
|
327
350
|
await rememberHerdrThreadSurface(persistence, session.id, opened);
|
|
328
351
|
|
|
329
352
|
return "opened";
|
package/src/server.ts
CHANGED
package/src/validate.ts
CHANGED
|
@@ -353,6 +353,7 @@ export const Params = {
|
|
|
353
353
|
tabId: NonEmpty,
|
|
354
354
|
paneId: NonEmpty,
|
|
355
355
|
mode: v.optional(v.picklist(["tab", "pane"])),
|
|
356
|
+
layout: v.optional(v.literal("pair")),
|
|
356
357
|
}),
|
|
357
358
|
"ghostty.getThreadSurface": v.object({ id: SessionId }),
|
|
358
359
|
"ghostty.setThreadSurface": v.object({ id: SessionId, terminalId: NonEmpty }),
|