@cueloop/client 0.1.0-alpha.110 → 0.1.0-alpha.112
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cueloop/client",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.112",
|
|
4
4
|
"description": "The cueloop review UI: projection renderer, keyboard grammar, review rail",
|
|
5
5
|
"homepage": "https://github.com/mmurakaru/cueloop#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
"access": "public"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@cueloop/daemon": "0.1.0-alpha.
|
|
31
|
-
"@cueloop/extension-api": "0.1.0-alpha.
|
|
32
|
-
"@cueloop/integration-obsidian": "0.1.0-alpha.
|
|
33
|
-
"@cueloop/schema": "0.1.0-alpha.
|
|
30
|
+
"@cueloop/daemon": "0.1.0-alpha.112",
|
|
31
|
+
"@cueloop/extension-api": "0.1.0-alpha.112",
|
|
32
|
+
"@cueloop/integration-obsidian": "0.1.0-alpha.112",
|
|
33
|
+
"@cueloop/schema": "0.1.0-alpha.112",
|
|
34
34
|
"@opentui/core": "^0.6.8",
|
|
35
35
|
"@opentui/keymap": "0.6.8",
|
|
36
36
|
"@opentui/react": "^0.6.8",
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
press,
|
|
25
25
|
pressKey,
|
|
26
26
|
typeText,
|
|
27
|
+
captureSettledFrame,
|
|
27
28
|
waitForState,
|
|
28
29
|
waitForText,
|
|
29
30
|
waitForTextGone,
|
|
@@ -212,7 +213,7 @@ describe("the four-pane workbench", () => {
|
|
|
212
213
|
await waitForState(setup, () => diffToggleColumn(setup) === -1);
|
|
213
214
|
await setup.mockMouse.click(railToggleColumn(setup), HEADER_ROW);
|
|
214
215
|
|
|
215
|
-
expect(changedFileVisibleInRightTree(setup
|
|
216
|
+
expect(changedFileVisibleInRightTree(await captureSettledFrame(setup), "store.ts")).toBe(false);
|
|
216
217
|
setup.renderer.destroy();
|
|
217
218
|
});
|
|
218
219
|
|
|
@@ -661,6 +661,23 @@ describe("the /lgtm default quick action", () => {
|
|
|
661
661
|
});
|
|
662
662
|
});
|
|
663
663
|
|
|
664
|
+
test("herdr ACP defaults off and requires a boolean experimental TOML flag", () => {
|
|
665
|
+
const dir = mkdtempSync(join(tmpdir(), "cueloop-herdr-config-"));
|
|
666
|
+
const path = join(dir, "config.toml");
|
|
667
|
+
|
|
668
|
+
try {
|
|
669
|
+
expect(loadConfig({ userConfigPath: path }).experimental.herdrAcp).toBe(false);
|
|
670
|
+
writeFileSync(path, "[experimental]\nherdr_acp = true\n");
|
|
671
|
+
expect(loadConfig({ userConfigPath: path }).experimental.herdrAcp).toBe(true);
|
|
672
|
+
writeFileSync(path, "[experimental]\nherdr_acp = true\nthread_agent = true\n");
|
|
673
|
+
expect(loadConfig({ userConfigPath: path }).experimental.threadAgent).toBe(true);
|
|
674
|
+
writeFileSync(path, '[experimental]\nherdr_acp = "true"\n');
|
|
675
|
+
expect(loadConfig({ userConfigPath: path }).experimental.herdrAcp).toBe(false);
|
|
676
|
+
} finally {
|
|
677
|
+
rmSync(dir, { recursive: true, force: true });
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
|
|
664
681
|
test("Thread agents default off and require a boolean experimental TOML flag", () => {
|
|
665
682
|
const dir = mkdtempSync(join(tmpdir(), "cueloop-agent-config-"));
|
|
666
683
|
const path = join(dir, "config.toml");
|
package/src/settings/config.ts
CHANGED
|
@@ -142,6 +142,7 @@ export interface CueloopConfig {
|
|
|
142
142
|
export interface ExperimentalConfig {
|
|
143
143
|
prototypePixels: boolean;
|
|
144
144
|
threadAgent: boolean;
|
|
145
|
+
herdrAcp: boolean;
|
|
145
146
|
}
|
|
146
147
|
|
|
147
148
|
/** Where a display name came from: typed by the user, or verified from a GitHub login. */
|
|
@@ -264,6 +265,7 @@ const IntegrationsSchema = v.object({ obsidian: v.optional(ObsidianSchema) });
|
|
|
264
265
|
const ExperimentalSchema = v.object({
|
|
265
266
|
prototype_pixels: v.fallback(v.optional(v.boolean()), undefined),
|
|
266
267
|
thread_agent: v.fallback(v.optional(v.boolean()), undefined),
|
|
268
|
+
herdr_acp: v.fallback(v.optional(v.boolean()), undefined),
|
|
267
269
|
});
|
|
268
270
|
const ReviewSchema = v.object({
|
|
269
271
|
skill: v.fallback(v.optional(v.string()), undefined),
|
|
@@ -452,7 +454,7 @@ export function loadConfig(
|
|
|
452
454
|
fx: { ...THREAD_MODEL_DEFAULTS.fx },
|
|
453
455
|
},
|
|
454
456
|
integrations: { obsidian: { ...OBSIDIAN_DEFAULTS } },
|
|
455
|
-
experimental: { prototypePixels: false, threadAgent: false },
|
|
457
|
+
experimental: { prototypePixels: false, threadAgent: false, herdrAcp: false },
|
|
456
458
|
};
|
|
457
459
|
// Theme name and per-token overrides are separate concerns, composed once
|
|
458
460
|
// after all layers: the last file to set [ui] theme wins, and every [theme]
|
|
@@ -700,6 +702,8 @@ function applyExperimental(
|
|
|
700
702
|
if (raw.prototype_pixels !== undefined) config.prototypePixels = raw.prototype_pixels;
|
|
701
703
|
|
|
702
704
|
if (raw.thread_agent !== undefined) config.threadAgent = raw.thread_agent;
|
|
705
|
+
|
|
706
|
+
if (raw.herdr_acp !== undefined) config.herdrAcp = raw.herdr_acp;
|
|
703
707
|
}
|
|
704
708
|
|
|
705
709
|
/** The preferred harness affects new Threads; existing Threads require a handoff. */
|
package/src/terminal/pty.ts
CHANGED
|
@@ -27,6 +27,7 @@ export interface PtyForkOptions {
|
|
|
27
27
|
rows?: number;
|
|
28
28
|
cwd?: string;
|
|
29
29
|
env?: Record<string, string>;
|
|
30
|
+
readPollMaxMs?: number;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
export interface ExitEvent {
|
|
@@ -127,6 +128,7 @@ class Pty implements IPty {
|
|
|
127
128
|
private processId: number;
|
|
128
129
|
private columns: number;
|
|
129
130
|
private rowCount: number;
|
|
131
|
+
private readonly readPollMaxMs: number;
|
|
130
132
|
private closing = false;
|
|
131
133
|
private reading = false;
|
|
132
134
|
private readonly decoder = new TextDecoder("utf-8");
|
|
@@ -139,6 +141,11 @@ class Pty implements IPty {
|
|
|
139
141
|
args: string[],
|
|
140
142
|
options: PtyForkOptions,
|
|
141
143
|
) {
|
|
144
|
+
this.readPollMaxMs = options.readPollMaxMs ?? READ_IDLE_MAX_MS;
|
|
145
|
+
|
|
146
|
+
if (!Number.isFinite(this.readPollMaxMs) || this.readPollMaxMs < 1)
|
|
147
|
+
throw new Error("PTY read poll limit must be at least one millisecond");
|
|
148
|
+
|
|
142
149
|
this.columns = options.cols ?? DEFAULT_COLS;
|
|
143
150
|
this.rowCount = options.rows ?? DEFAULT_ROWS;
|
|
144
151
|
const cwd = options.cwd ?? process.cwd();
|
|
@@ -236,7 +243,7 @@ class Pty implements IPty {
|
|
|
236
243
|
this.closing = true;
|
|
237
244
|
this.exitEvent.fire({ exitCode });
|
|
238
245
|
} else {
|
|
239
|
-
const delay = Math.min(
|
|
246
|
+
const delay = Math.min(this.readPollMaxMs, READ_IDLE_MS * 2 ** Math.min(idlePolls, 5));
|
|
240
247
|
|
|
241
248
|
idlePolls += 1;
|
|
242
249
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
@@ -308,7 +308,9 @@ async function waitForFramePredicate(
|
|
|
308
308
|
);
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
-
|
|
311
|
+
// Retry while a render still holds the buffer lease.
|
|
312
|
+
// eslint-disable-next-line no-await-in-loop
|
|
313
|
+
const frame = await captureSettledFrame(setup);
|
|
312
314
|
|
|
313
315
|
polls++;
|
|
314
316
|
|
|
@@ -340,6 +342,27 @@ async function waitForFramePredicate(
|
|
|
340
342
|
}
|
|
341
343
|
}
|
|
342
344
|
|
|
345
|
+
/**
|
|
346
|
+
* Capture a frame, retrying while OpenTUI still holds the buffer lease.
|
|
347
|
+
* A render in progress answers the capture with OutputBusy.
|
|
348
|
+
*/
|
|
349
|
+
export async function captureSettledFrame(
|
|
350
|
+
setup: { captureCharFrame(): string },
|
|
351
|
+
deadline = Date.now() + 1_000,
|
|
352
|
+
): Promise<string> {
|
|
353
|
+
try {
|
|
354
|
+
return setup.captureCharFrame();
|
|
355
|
+
} catch (error) {
|
|
356
|
+
const busy = error instanceof Error && error.message.includes("OutputBusy");
|
|
357
|
+
|
|
358
|
+
if (!busy || Date.now() >= deadline) throw error;
|
|
359
|
+
|
|
360
|
+
await new Promise((resolve) => setTimeout(resolve, 15));
|
|
361
|
+
|
|
362
|
+
return captureSettledFrame(setup, deadline);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
343
366
|
/** Wait until a state predicate holds (daemon round-trips included). */
|
|
344
367
|
export async function waitForState(
|
|
345
368
|
setup: TestRendererSetup,
|
|
@@ -373,7 +396,9 @@ export async function waitForState(
|
|
|
373
396
|
);
|
|
374
397
|
}
|
|
375
398
|
|
|
376
|
-
|
|
399
|
+
// Retry while a render still holds the buffer lease.
|
|
400
|
+
// eslint-disable-next-line no-await-in-loop
|
|
401
|
+
const frame = await captureSettledFrame(setup);
|
|
377
402
|
|
|
378
403
|
polls++;
|
|
379
404
|
|
|
@@ -479,6 +479,9 @@ class Controller implements ReviewController {
|
|
|
479
479
|
|
|
480
480
|
this.client = client;
|
|
481
481
|
client.onEvent((event) => {
|
|
482
|
+
// Agent transcripts have their own observer; they do not change Thread records or the inbox.
|
|
483
|
+
if (event.event === "agent.updated") return;
|
|
484
|
+
|
|
482
485
|
// another controller/observer changed state: re-fetch the active session's content
|
|
483
486
|
const session = this.snapshot.session;
|
|
484
487
|
|