@zhuxixi/pi-agent-board 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -1
- package/docs/superpowers/plans/2026-08-27-locks-acquirelock-spin.md +614 -0
- package/docs/superpowers/plans/2026-08-27-pty-runner-test-flaky.md +32 -0
- package/docs/superpowers/plans/2026-08-29-code-refs-badges.md +223 -0
- package/docs/superpowers/plans/2026-08-30-post-exit-timing-fix.md +37 -0
- package/docs/superpowers/specs/2026-08-27-locks-acquirelock-spin-design.md +97 -0
- package/docs/superpowers/specs/2026-08-27-pty-runner-test-flaky-design.md +35 -0
- package/docs/superpowers/specs/2026-08-29-code-refs-badges-design.md +128 -0
- package/docs/superpowers/specs/2026-08-30-eprm-atomicwrite-race-design.md +77 -0
- package/docs/superpowers/specs/2026-08-30-post-exit-timing-fix-design.md +80 -0
- package/package.json +4 -2
- package/runner/job-runner.mjs +57 -5
- package/runner/pty-runner.mjs +89 -12
- package/runner/state-runner.mjs +7 -2
- package/runner/title-runner.mjs +1 -1
- package/src/core/atomic.mjs +40 -1
- package/src/core/code-refs-store.mjs +312 -0
- package/src/core/code-refs.mjs +861 -0
- package/src/core/follow-up-queue.mjs +23 -5
- package/src/core/host-crash.mjs +39 -0
- package/src/core/locks.mjs +79 -23
- package/src/core/paths.mjs +24 -1
- package/src/core/pty-attach-reconnect.mjs +43 -0
- package/src/core/repo.mjs +53 -0
- package/src/core/rows.mjs +50 -0
- package/src/core/store.mjs +4 -1
- package/src/core/types.mjs +12 -0
- package/src/runtime/service.mjs +7 -1
- package/src/ui/dashboard.ts +14 -2
- package/src/ui/pty-attach.ts +32 -3
package/src/ui/pty-attach.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { CURSOR_MARKER, Key, matchesKey, truncateToWidth, visibleWidth } from "@
|
|
|
8
8
|
import { isProbablyEmptyPiInputLine } from "../core/pty-input.mjs";
|
|
9
9
|
import { findHttpUrlAtCells, findWordRangeAtCells } from "../core/pty-links.mjs";
|
|
10
10
|
import { createAttachOutputRenderScheduler, nextAttachRender, projectPtyCursor, shouldScheduleAttachRenderForMessage } from "../core/pty-attach-render.mjs";
|
|
11
|
+
import { evaluateAttachReconnect, shouldEscapeAttach } from "../core/pty-attach-reconnect.mjs";
|
|
11
12
|
import { installImeCursorCoalesce } from "../core/ime-cursor-coalesce.mjs";
|
|
12
13
|
import { createJiggleRetryController } from "../core/pty-attach-jiggle-controller.mjs";
|
|
13
14
|
import { clampInt, parseMouseInputChunk, resolveWheelLines, scrollViewportTop, selectionDragScrollLines } from "../core/pty-scroll.mjs";
|
|
@@ -133,6 +134,13 @@ export class PtyAttachComponent implements Component {
|
|
|
133
134
|
private osc52Carry = "";
|
|
134
135
|
private passthroughCarry = "";
|
|
135
136
|
private readonly connectStartedAt = Date.now();
|
|
137
|
+
// Set once the first socket connect succeeds; drives the reconnect give-up
|
|
138
|
+
// policy (short window after a live session dies, long window during cold
|
|
139
|
+
// host start — see pty-attach-reconnect.mjs).
|
|
140
|
+
private everConnected = false;
|
|
141
|
+
// Timestamp of the first disconnect that started the current reconnect
|
|
142
|
+
// cycle; reset on every successful (re)connect.
|
|
143
|
+
private disconnectedAt: number | null = null;
|
|
136
144
|
// Lines scrolled per mouse-wheel event; configurable via $AGENT_BOARD_WHEEL_LINES.
|
|
137
145
|
private readonly mouseWheelLines = resolveWheelLines();
|
|
138
146
|
private readonly term: XtermLike;
|
|
@@ -235,7 +243,10 @@ export class PtyAttachComponent implements Component {
|
|
|
235
243
|
matchesKey(data, Key.left) ||
|
|
236
244
|
matchesKey(data, Key.ctrl("]"))
|
|
237
245
|
) {
|
|
238
|
-
|
|
246
|
+
// While the socket is down the key can never reach the child, so
|
|
247
|
+
// escape unconditionally — the view must always be exitable, even
|
|
248
|
+
// after the host crashes mid-output (issue #48).
|
|
249
|
+
if (shouldEscapeAttach(this.connected, this.childInputLooksEmpty())) {
|
|
239
250
|
this.detach();
|
|
240
251
|
return;
|
|
241
252
|
}
|
|
@@ -288,7 +299,7 @@ export class PtyAttachComponent implements Component {
|
|
|
288
299
|
|
|
289
300
|
private loadingDetail(elapsedSeconds: number): string {
|
|
290
301
|
if (this.status === "attached") return "Attached · waiting for the session to render…";
|
|
291
|
-
if (this.status.startsWith("error") || this.status === "host exited") return this.status;
|
|
302
|
+
if (this.status.startsWith("error") || this.status === "host exited" || this.status === "host not reachable") return this.status;
|
|
292
303
|
if (this.status === "disconnected") return `Reconnecting to the session host… ${elapsedSeconds}s`;
|
|
293
304
|
return `Starting the session host… ${elapsedSeconds}s`;
|
|
294
305
|
}
|
|
@@ -315,7 +326,10 @@ export class PtyAttachComponent implements Component {
|
|
|
315
326
|
|
|
316
327
|
private connect(): void {
|
|
317
328
|
if (this.closed || this.connected || this.socket) return;
|
|
318
|
-
|
|
329
|
+
// On Windows the control socket is a named pipe — it never exists as a
|
|
330
|
+
// filesystem entry, so existsSync can't gate the connect; a missing pipe
|
|
331
|
+
// surfaces as an error event on the socket and retries via scheduleReconnect.
|
|
332
|
+
if (process.platform !== "win32" && !existsSync(this.opts.socketPath)) {
|
|
319
333
|
this.status = `starting host… ${Math.ceil((Date.now() - this.connectStartedAt) / 1000)}s`;
|
|
320
334
|
this.scheduleReconnect();
|
|
321
335
|
return;
|
|
@@ -325,6 +339,8 @@ export class PtyAttachComponent implements Component {
|
|
|
325
339
|
socket.on("connect", () => {
|
|
326
340
|
this.clearRetry();
|
|
327
341
|
this.connected = true;
|
|
342
|
+
this.everConnected = true;
|
|
343
|
+
this.disconnectedAt = null;
|
|
328
344
|
this.status = "attached";
|
|
329
345
|
this.send({ type: "hello", clientId: `ui-${Date.now()}`, wantOutput: true });
|
|
330
346
|
this.jiggleRetry.start(this.cols, this.rows);
|
|
@@ -336,6 +352,7 @@ export class PtyAttachComponent implements Component {
|
|
|
336
352
|
socket.on("close", () => {
|
|
337
353
|
this.socket = null;
|
|
338
354
|
this.connected = false;
|
|
355
|
+
this.disconnectedAt ??= Date.now();
|
|
339
356
|
if (!this.closed && this.status !== "host exited") {
|
|
340
357
|
this.status = "disconnected";
|
|
341
358
|
this.scheduleReconnect();
|
|
@@ -345,6 +362,7 @@ export class PtyAttachComponent implements Component {
|
|
|
345
362
|
socket.on("error", (err) => {
|
|
346
363
|
this.socket = null;
|
|
347
364
|
this.connected = false;
|
|
365
|
+
this.disconnectedAt ??= Date.now();
|
|
348
366
|
if (this.closed) return;
|
|
349
367
|
this.status = `waiting for host… ${err.message}`;
|
|
350
368
|
this.scheduleReconnect();
|
|
@@ -365,6 +383,17 @@ export class PtyAttachComponent implements Component {
|
|
|
365
383
|
|
|
366
384
|
private scheduleReconnect(): void {
|
|
367
385
|
if (this.closed || this.retryTimer) return;
|
|
386
|
+
const verdict = evaluateAttachReconnect({
|
|
387
|
+
everConnected: this.everConnected,
|
|
388
|
+
disconnectedAt: this.disconnectedAt,
|
|
389
|
+
connectStartedAt: this.connectStartedAt,
|
|
390
|
+
now: Date.now(),
|
|
391
|
+
});
|
|
392
|
+
if (verdict.giveUp) {
|
|
393
|
+
this.status = verdict.status ?? "host exited";
|
|
394
|
+
this.scheduleRender();
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
368
397
|
this.retryTimer = setTimeout(() => {
|
|
369
398
|
this.retryTimer = null;
|
|
370
399
|
this.connect();
|