@zhuxixi/pi-agent-board 0.5.2 → 0.6.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/CHANGELOG.md +32 -0
- package/README.md +41 -3
- package/docs/superpowers/plans/2026-09-03-code-refs-pr-backlink-narrow.md +551 -0
- package/docs/superpowers/plans/2026-09-04-evidence-outputpreview.md +209 -0
- package/docs/superpowers/plans/2026-09-04-warm-host-reclaim.md +796 -0
- package/docs/superpowers/plans/2026-09-05-issue-13-drainnextfollowup-pty-probe.md +114 -0
- package/docs/superpowers/plans/2026-09-05-issue-38-windows-wezterm-ime-cursor.md +73 -0
- package/docs/superpowers/plans/2026-09-05-issue-39-truncate-codepoint-boundary.md +143 -0
- package/docs/superpowers/plans/2026-09-05-issue-61-mention-fallback-guards.md +226 -0
- package/docs/superpowers/plans/2026-09-05-issue-63-flaky-manual-completion.md +87 -0
- package/docs/superpowers/plans/2026-09-05-issue-64-changelog-release-helper.md +53 -0
- package/docs/superpowers/plans/2026-09-05-pty-host-stacking-sock-race.md +731 -0
- package/docs/superpowers/specs/2026-08-29-code-refs-badges-design.md +1 -1
- package/docs/superpowers/specs/2026-09-03-code-refs-pr-backlink-narrow-design.md +92 -0
- package/docs/superpowers/specs/2026-09-04-evidence-outputpreview-design.md +50 -0
- package/docs/superpowers/specs/2026-09-04-warm-host-reclaim-design.md +106 -0
- package/docs/superpowers/specs/2026-09-05-issue-13-drainnextfollowup-pty-probe-design.md +64 -0
- package/docs/superpowers/specs/2026-09-05-issue-38-windows-wezterm-ime-design.md +48 -0
- package/docs/superpowers/specs/2026-09-05-issue-39-truncate-codepoint-boundary-design.md +64 -0
- package/docs/superpowers/specs/2026-09-05-issue-61-mention-fallback-design.md +71 -0
- package/docs/superpowers/specs/2026-09-05-issue-63-flaky-manual-completion-design.md +49 -0
- package/docs/superpowers/specs/2026-09-05-issue-64-changelog-helper-design.md +76 -0
- package/docs/superpowers/specs/2026-09-05-pty-host-stacking-sock-race-design.md +510 -0
- package/package.json +83 -81
- package/runner/job-runner.mjs +2 -2
- package/runner/pty-runner.mjs +573 -2
- package/runner/state-runner.mjs +3 -0
- package/runner/title-runner.mjs +1 -1
- package/scripts/release_helper.mjs +277 -0
- package/src/commands/agent-board.ts +38 -35
- package/src/commands/attach-decision.mjs +66 -0
- package/src/commands/attach-flow.ts +45 -39
- package/src/core/code-refs.mjs +85 -33
- package/src/core/evidence.mjs +2 -2
- package/src/core/heuristics.mjs +40 -2
- package/src/core/host-coordination.mjs +159 -0
- package/src/core/host-crash.mjs +43 -3
- package/src/core/host-probe.mjs +196 -0
- package/src/core/launch.mjs +3 -1
- package/src/core/locks.mjs +196 -1
- package/src/core/paths.mjs +24 -0
- package/src/core/store.mjs +164 -5
- package/src/core/types.mjs +17 -1
- package/src/core/warm-host-sweeper.mjs +150 -0
- package/src/index.ts +29 -1
- package/src/runtime/service.mjs +967 -109
- package/src/ui/dashboard-decisions.mjs +55 -0
- package/src/ui/dashboard.ts +26 -12
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard decision helpers for host prewarm and attach prelude (issue #70 A14).
|
|
3
|
+
*
|
|
4
|
+
* Pure functions extracted from src/ui/dashboard.ts so the branching contract is
|
|
5
|
+
* node-testable without pi's TS loader (same pattern as commands/attach-decision.mjs).
|
|
6
|
+
* `hostActive` covers starting/alive/stopping claims from store.loadRow — pending
|
|
7
|
+
* claims count as an already-registered launch, so prewarm never re-spawns them.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Prewarm decision for the selected row.
|
|
12
|
+
* - "mark-only": the row has an active host claim — mark it prewarmed WITHOUT
|
|
13
|
+
* calling prewarmHost (a pending/starting claim is already a launch; the old
|
|
14
|
+
* hostAlive guard re-checked it on every debounce tick).
|
|
15
|
+
* - "skip": busy row without a host claim — no prewarm, no mark.
|
|
16
|
+
* - "prewarm": no claim — call service.prewarmHost once; the caller marks only on ok.
|
|
17
|
+
* @param {{ hostActive?: boolean, agentBusy?: boolean }} row
|
|
18
|
+
* @returns {{ action: "mark-only" | "skip" | "prewarm" }}
|
|
19
|
+
*/
|
|
20
|
+
export function planPrewarm({ hostActive, agentBusy }) {
|
|
21
|
+
if (hostActive) return { action: "mark-only" };
|
|
22
|
+
if (agentBusy) return { action: "skip" };
|
|
23
|
+
return { action: "prewarm" };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Attach prelude for a dashboard row. A hostActive row attaches directly — the
|
|
28
|
+
* async resolver owns starting/stopping wait, so no interrupt confirmation is
|
|
29
|
+
* offered even when the row is busy. A busy row WITHOUT a host claim keeps the
|
|
30
|
+
* existing stopFirst confirm; everything else attaches directly.
|
|
31
|
+
* @param {{ hostActive?: boolean, agentBusy?: boolean }} row
|
|
32
|
+
* @returns {{ plan: "attach" | "confirm-stop-first" }}
|
|
33
|
+
*/
|
|
34
|
+
export function planDashboardAttach({ hostActive, agentBusy }) {
|
|
35
|
+
if (!hostActive && agentBusy) return { plan: "confirm-stop-first" };
|
|
36
|
+
return { plan: "attach" };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Map a service reply() result to the dashboard notice (issue #70: queued and
|
|
41
|
+
* sent are different outcomes — a queued reply is durable, not yet delivered).
|
|
42
|
+
* Result shapes from service.reply(): {ok:false,error} | {ok:true,queued:true}
|
|
43
|
+
* | {ok:true,sent:true} | {ok:true,hostMode,fallbackReason?}.
|
|
44
|
+
* @param {{ ok?: boolean, error?: string, queued?: boolean, sent?: boolean, hostMode?: string|null, fallbackReason?: string }} res
|
|
45
|
+
* @returns {{ level: "info" | "warn" | "error", text: string }}
|
|
46
|
+
*/
|
|
47
|
+
export function replyNotice(res) {
|
|
48
|
+
if (!res?.ok) return { level: "error", text: res?.error ?? "Reply failed" };
|
|
49
|
+
if (res.queued) return { level: "info", text: "Reply queued — will deliver when the host is ready" };
|
|
50
|
+
if (res.sent) return { level: "info", text: "Reply sent" };
|
|
51
|
+
if (res.hostMode === "json-runner") {
|
|
52
|
+
return { level: "warn", text: `Reply sent with non-live fallback: ${res.fallbackReason ?? "PTY unavailable"}` };
|
|
53
|
+
}
|
|
54
|
+
return { level: "info", text: "Reply sent" };
|
|
55
|
+
}
|
package/src/ui/dashboard.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
supportedThinkingLevels,
|
|
25
25
|
} from "../core/launch-options.mjs";
|
|
26
26
|
import { createPrewarmScheduler } from "../core/prewarm-schedule.mjs";
|
|
27
|
+
import { planDashboardAttach, planPrewarm, replyNotice } from "./dashboard-decisions.mjs";
|
|
27
28
|
import { ensureCwdStatsSeeded, rankedCwdCandidates, recordCwdLaunch } from "../core/cwd-stats.mjs";
|
|
28
29
|
import { filterRows, groupRowsByFolder, rowState, stateGlyph } from "../core/rows.mjs";
|
|
29
30
|
import { loadSessionView } from "../core/session-view.mjs";
|
|
@@ -266,7 +267,16 @@ export class DashboardComponent implements Component {
|
|
|
266
267
|
const id = this.selectedId;
|
|
267
268
|
if (!id || id === this.prewarmedId) return;
|
|
268
269
|
const row = this.selectedRow();
|
|
269
|
-
if (!row
|
|
270
|
+
if (!row) return;
|
|
271
|
+
// hostActive claims (starting/alive/stopping) already have a launch — mark
|
|
272
|
+
// them prewarmed without another service call; a pending claim is not a
|
|
273
|
+
// failure to retry on every debounce tick (issue #70 A14).
|
|
274
|
+
const decision = planPrewarm({ hostActive: row.hostActive, agentBusy: isAgentBusy(row) });
|
|
275
|
+
if (decision.action === "skip") return;
|
|
276
|
+
if (decision.action === "mark-only") {
|
|
277
|
+
this.prewarmedId = id;
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
270
280
|
const res = this.deps.service.prewarmHost?.(id);
|
|
271
281
|
if (res?.ok) this.prewarmedId = id;
|
|
272
282
|
}
|
|
@@ -792,7 +802,8 @@ export class DashboardComponent implements Component {
|
|
|
792
802
|
case "rename":
|
|
793
803
|
return this.submitRename();
|
|
794
804
|
case "reply":
|
|
795
|
-
|
|
805
|
+
void this.submitReply();
|
|
806
|
+
return;
|
|
796
807
|
case "list":
|
|
797
808
|
case "dispatch":
|
|
798
809
|
return this.input.trim() ? this.openLaunchDialog() : this.attachSelected();
|
|
@@ -866,7 +877,7 @@ export class DashboardComponent implements Component {
|
|
|
866
877
|
this.refresh();
|
|
867
878
|
}
|
|
868
879
|
|
|
869
|
-
private submitReply(): void {
|
|
880
|
+
private async submitReply(): Promise<void> {
|
|
870
881
|
const text = this.input.trim();
|
|
871
882
|
const row = this.selectedRow();
|
|
872
883
|
if (!text || !row) {
|
|
@@ -874,10 +885,11 @@ export class DashboardComponent implements Component {
|
|
|
874
885
|
this.setInput("");
|
|
875
886
|
return;
|
|
876
887
|
}
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
888
|
+
// reply() is async since issue #70 A13 (ack-gated host delivery); the
|
|
889
|
+
// result (sent vs queued) only shapes the notice.
|
|
890
|
+
const res = await this.deps.service.reply(row.meta.id, text);
|
|
891
|
+
const notice = replyNotice(res);
|
|
892
|
+
this.notice(notice.text, notice.level);
|
|
881
893
|
this.setInput("");
|
|
882
894
|
this.mode = "peek";
|
|
883
895
|
this.inputNotice = null;
|
|
@@ -1116,17 +1128,19 @@ export class DashboardComponent implements Component {
|
|
|
1116
1128
|
}
|
|
1117
1129
|
|
|
1118
1130
|
private requestAttach(row: Row): void {
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1131
|
+
// hostActive rows (starting/alive/stopping) attach directly — the async
|
|
1132
|
+
// resolver owns the starting/stopping wait, so no interrupt confirm even
|
|
1133
|
+
// when the row is busy (issue #70 A14).
|
|
1134
|
+
const plan = planDashboardAttach({ hostActive: row.hostActive, agentBusy: isAgentBusy(row) });
|
|
1135
|
+
if (plan.plan === "confirm-stop-first") {
|
|
1122
1136
|
this.pending = {
|
|
1123
1137
|
prompt: `"${row.meta.name}" is running. Interrupt and attach? (y/N)`,
|
|
1124
1138
|
onYes: () => this.done({ action: "attach", viewId: row.meta.id, stopFirst: true }),
|
|
1125
1139
|
};
|
|
1126
1140
|
this.mode = "confirm";
|
|
1127
|
-
|
|
1128
|
-
this.done({ action: "attach", viewId: row.meta.id, stopFirst: false });
|
|
1141
|
+
return;
|
|
1129
1142
|
}
|
|
1143
|
+
this.done({ action: "attach", viewId: row.meta.id, stopFirst: false });
|
|
1130
1144
|
}
|
|
1131
1145
|
|
|
1132
1146
|
// ---- rendering ----------------------------------------------------------
|