@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.
Files changed (48) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +41 -3
  3. package/docs/superpowers/plans/2026-09-03-code-refs-pr-backlink-narrow.md +551 -0
  4. package/docs/superpowers/plans/2026-09-04-evidence-outputpreview.md +209 -0
  5. package/docs/superpowers/plans/2026-09-04-warm-host-reclaim.md +796 -0
  6. package/docs/superpowers/plans/2026-09-05-issue-13-drainnextfollowup-pty-probe.md +114 -0
  7. package/docs/superpowers/plans/2026-09-05-issue-38-windows-wezterm-ime-cursor.md +73 -0
  8. package/docs/superpowers/plans/2026-09-05-issue-39-truncate-codepoint-boundary.md +143 -0
  9. package/docs/superpowers/plans/2026-09-05-issue-61-mention-fallback-guards.md +226 -0
  10. package/docs/superpowers/plans/2026-09-05-issue-63-flaky-manual-completion.md +87 -0
  11. package/docs/superpowers/plans/2026-09-05-issue-64-changelog-release-helper.md +53 -0
  12. package/docs/superpowers/plans/2026-09-05-pty-host-stacking-sock-race.md +731 -0
  13. package/docs/superpowers/specs/2026-08-29-code-refs-badges-design.md +1 -1
  14. package/docs/superpowers/specs/2026-09-03-code-refs-pr-backlink-narrow-design.md +92 -0
  15. package/docs/superpowers/specs/2026-09-04-evidence-outputpreview-design.md +50 -0
  16. package/docs/superpowers/specs/2026-09-04-warm-host-reclaim-design.md +106 -0
  17. package/docs/superpowers/specs/2026-09-05-issue-13-drainnextfollowup-pty-probe-design.md +64 -0
  18. package/docs/superpowers/specs/2026-09-05-issue-38-windows-wezterm-ime-design.md +48 -0
  19. package/docs/superpowers/specs/2026-09-05-issue-39-truncate-codepoint-boundary-design.md +64 -0
  20. package/docs/superpowers/specs/2026-09-05-issue-61-mention-fallback-design.md +71 -0
  21. package/docs/superpowers/specs/2026-09-05-issue-63-flaky-manual-completion-design.md +49 -0
  22. package/docs/superpowers/specs/2026-09-05-issue-64-changelog-helper-design.md +76 -0
  23. package/docs/superpowers/specs/2026-09-05-pty-host-stacking-sock-race-design.md +510 -0
  24. package/package.json +83 -81
  25. package/runner/job-runner.mjs +2 -2
  26. package/runner/pty-runner.mjs +573 -2
  27. package/runner/state-runner.mjs +3 -0
  28. package/runner/title-runner.mjs +1 -1
  29. package/scripts/release_helper.mjs +277 -0
  30. package/src/commands/agent-board.ts +38 -35
  31. package/src/commands/attach-decision.mjs +66 -0
  32. package/src/commands/attach-flow.ts +45 -39
  33. package/src/core/code-refs.mjs +85 -33
  34. package/src/core/evidence.mjs +2 -2
  35. package/src/core/heuristics.mjs +40 -2
  36. package/src/core/host-coordination.mjs +159 -0
  37. package/src/core/host-crash.mjs +43 -3
  38. package/src/core/host-probe.mjs +196 -0
  39. package/src/core/launch.mjs +3 -1
  40. package/src/core/locks.mjs +196 -1
  41. package/src/core/paths.mjs +24 -0
  42. package/src/core/store.mjs +164 -5
  43. package/src/core/types.mjs +17 -1
  44. package/src/core/warm-host-sweeper.mjs +150 -0
  45. package/src/index.ts +29 -1
  46. package/src/runtime/service.mjs +967 -109
  47. package/src/ui/dashboard-decisions.mjs +55 -0
  48. 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
+ }
@@ -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 || row.hostAlive || isAgentBusy(row)) return;
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
- return this.submitReply();
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
- const res = this.deps.service.reply(row.meta.id, text);
878
- if (!res.ok) this.notice(res.error ?? "Reply failed", "error");
879
- else if (res.hostMode === "json-runner") this.notice(`Reply sent with non-live fallback: ${res.fallbackReason ?? "PTY unavailable"}`, "warn");
880
- else this.notice("Reply sent", "info");
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
- if (row.hostAlive) {
1120
- this.done({ action: "attach", viewId: row.meta.id, stopFirst: false });
1121
- } else if (isAgentBusy(row)) {
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
- } else {
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 ----------------------------------------------------------