pi-squire 0.20.0 → 0.20.2

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.
@@ -4,33 +4,20 @@ import type { TmuxClient } from "./tmux.ts";
4
4
  import { ActivityCollector } from "./widget-activity.ts";
5
5
  import { LIVE_LAUNCH_ENV, LiveActivityPublisher, liveLaunch } from "./widget-live.ts";
6
6
 
7
- function observeLocator(
7
+ async function observeLocator(
8
8
  publisher: LiveActivityPublisher,
9
9
  id: string,
10
10
  token: string,
11
11
  tmux: TmuxClient,
12
12
  ctx: ExtensionContext,
13
- ): ReturnType<typeof setInterval> {
14
- let reading = false;
15
- const refresh = async (): Promise<void> => {
16
- if (reading) return;
17
- reading = true;
18
- try {
19
- const locator = await tmux.inspectPane?.().catch(() => undefined);
20
- if (matchesSessionOwner(id, process.pid, token))
21
- publisher.publishLocator(locator, token, ctx.sessionManager.getSessionFile());
22
- } catch {
23
- /* Optional observation cannot block task processing. */
24
- } finally {
25
- reading = false;
26
- }
27
- };
28
- void refresh();
29
- const timer: ReturnType<typeof setInterval> = setInterval(() => {
30
- void refresh();
31
- }, 1000);
32
- timer.unref();
33
- return timer;
13
+ ): Promise<void> {
14
+ try {
15
+ const locator = await tmux.inspectPane?.().catch(() => undefined);
16
+ if (matchesSessionOwner(id, process.pid, token))
17
+ publisher.publishLocator(locator, token, ctx.sessionManager.getSessionFile());
18
+ } catch {
19
+ /* Optional observation cannot block task processing. */
20
+ }
34
21
  }
35
22
 
36
23
  /** Observe raw lifecycle after a positive startup owner gate; never grants task/action authority. */
@@ -43,12 +30,8 @@ export function registerActivityRuntime(
43
30
  const seed: string | undefined = process.env[LIVE_LAUNCH_ENV];
44
31
  let collector: ActivityCollector | undefined;
45
32
  let publisher: LiveActivityPublisher | undefined;
46
- let timer: ReturnType<typeof setInterval> | undefined;
47
33
 
48
34
  const stop = (): void => {
49
- if (timer) clearInterval(timer);
50
- timer = undefined;
51
-
52
35
  collector?.clear();
53
36
  collector = undefined;
54
37
  publisher?.stop();
@@ -66,7 +49,7 @@ export function registerActivityRuntime(
66
49
  collector = new ActivityCollector();
67
50
  publisher = new LiveActivityPublisher(id, liveLaunch(seed, id));
68
51
  publish();
69
- if (tmux && ownerToken) timer = observeLocator(publisher, id, ownerToken, tmux, ctx);
52
+ if (tmux && ownerToken) void observeLocator(publisher, id, ownerToken, tmux, ctx);
70
53
  });
71
54
  pi.on("tool_execution_start", (event) => {
72
55
  collector?.start(event.toolCallId, event.toolName, event.parentToolCallId);
@@ -4,20 +4,30 @@ import { type Identity, readIdentity } from "./identity.ts";
4
4
  import { locateSession, recordedName } from "./results.ts";
5
5
  import { matchesSessionOwner, sessionIsOwned } from "./session-owner.ts";
6
6
  import type { PaneLocator, TmuxClient } from "./tmux.ts";
7
- import { readTree, type TreeEntry } from "./tree-registry.ts";
7
+ import { readTreeDisplay, type TreeEntry } from "./tree-registry.ts";
8
8
  import { LiveActivityReader, type LiveLocator } from "./widget-live.ts";
9
9
  import { WidgetTitleMirrors } from "./widget-title.ts";
10
10
  import { WidgetTitleFiles } from "./widget-title-file.ts";
11
11
  import { listWindows } from "./windows.ts";
12
12
 
13
- export type AncestorRow = { key: string; sessionId: string; identity: string; parent: boolean; title?: string };
13
+ export type WidgetIdentity = { identity: string; agent?: string };
14
+ export type Breadcrumb = {
15
+ key: string;
16
+ parentSessionId: string;
17
+ crumbs: WidgetIdentity[];
18
+ self: WidgetIdentity;
19
+ parentTitle?: string;
20
+ };
14
21
  type Ancestor = { sessionId: string; identity?: Identity };
15
22
 
16
23
  /** Durable parent ids only. A broken/cyclic chain refuses rather than inventing ancestry. */
17
24
  export function ancestorSessions(ctx: ExtensionContext): Ancestor[] {
18
25
  const session: string = ctx.sessionManager.getSessionId();
19
26
  const own: Identity | undefined = readIdentity(session);
20
- if (!own) return [];
27
+ return own ? ancestorChain(session, own) : [];
28
+ }
29
+
30
+ function ancestorChain(session: string, own: Identity): Ancestor[] {
21
31
  const found: Ancestor[] = [];
22
32
  const seen: Set<string> = new Set([session]);
23
33
  let child: Identity = own;
@@ -44,7 +54,7 @@ export function ancestorSessions(ctx: ExtensionContext): Ancestor[] {
44
54
  throw new Error("ancestor identity chain does not reach root");
45
55
  }
46
56
 
47
- export class AncestorView {
57
+ export class BreadcrumbView {
48
58
  private readonly titles: WidgetTitleFiles = new WidgetTitleFiles();
49
59
  private readonly mirrors: WidgetTitleMirrors = new WidgetTitleMirrors();
50
60
 
@@ -53,33 +63,40 @@ export class AncestorView {
53
63
  this.mirrors.clear();
54
64
  }
55
65
 
56
- async read(ctx: ExtensionContext, reader: LiveActivityReader): Promise<AncestorRow[]> {
57
- const ancestors: Ancestor[] = ancestorSessions(ctx);
66
+ async read(ctx: ExtensionContext, reader: LiveActivityReader): Promise<Breadcrumb | undefined> {
67
+ const session = ctx.sessionManager.getSessionId();
68
+ const own = readIdentity(session);
69
+ if (!own) return;
70
+ const ancestors = ancestorChain(session, own);
58
71
  let entries: TreeEntry[] = [];
59
72
  try {
60
- if (ancestors.length) entries = readTree(ancestors[0].sessionId, 20).entries;
73
+ entries = readTreeDisplay(ancestors[0].sessionId).entries;
61
74
  } catch {
62
75
  /* labels optional */
63
76
  }
64
- return Promise.all(
65
- ancestors.map(async (ancestor: Ancestor, index: number): Promise<AncestorRow> => {
66
- const entry: TreeEntry | undefined = entries.findLast((item) => item.sessionId === ancestor.sessionId);
67
- const live: LiveLocator | undefined = await reader.locator(ancestor.sessionId);
68
- const path: string | undefined =
69
- live?.sessionFile ?? (entry?.launch ? locateSession(entry.launch, undefined) : undefined);
70
- const name: string | undefined =
71
- this.titles.read(path, ancestor.sessionId)?.name ?? this.mirrors.read(ancestor.sessionId);
72
- let label = "root";
73
- if (ancestor.identity) label = entry?.launch ? recordedName(entry.launch) : ancestor.identity.agent;
74
- return {
75
- key: `ancestor:${ancestor.sessionId}`,
76
- sessionId: ancestor.sessionId,
77
- identity: label,
78
- parent: index === ancestors.length - 1,
79
- title: name,
80
- };
81
- }),
82
- );
77
+ const crumbs = ancestors.map((ancestor): WidgetIdentity => {
78
+ const entry = entries.findLast((item) => item.sessionId === ancestor.sessionId);
79
+ const label = entry?.launch ? recordedName(entry.launch) : ancestor.identity?.agent;
80
+ return {
81
+ identity: ancestor.identity ? (label ?? ancestor.identity.agent) : "root",
82
+ agent: ancestor.identity?.agent,
83
+ };
84
+ });
85
+ const parent = ancestors[ancestors.length - 1];
86
+ const entry = entries.findLast((item) => item.sessionId === parent.sessionId);
87
+ const live = await reader.locator(parent.sessionId);
88
+ const path = live?.sessionFile ?? (entry?.launch ? locateSession(entry.launch, undefined) : undefined);
89
+ const self = entries.findLast((item) => item.sessionId === session);
90
+ return {
91
+ key: `ancestor:${parent.sessionId}`,
92
+ parentSessionId: parent.sessionId,
93
+ crumbs,
94
+ self: {
95
+ identity: self?.launch ? recordedName(self.launch) : own.agent,
96
+ agent: own.agent,
97
+ },
98
+ parentTitle: this.titles.read(path, parent.sessionId)?.name ?? this.mirrors.read(parent.sessionId),
99
+ };
83
100
  }
84
101
  }
85
102
 
@@ -95,20 +112,13 @@ function sameTarget(a: LiveLocator, b: LiveLocator): boolean {
95
112
  }
96
113
 
97
114
  /** Positive own-server + exact target-pane + id/name listing, not a launch snapshot. */
98
- export async function verifyLiveWindow(tmux: TmuxClient, live: LiveLocator): Promise<boolean> {
115
+ export async function verifyLiveWindow(tmux: TmuxClient, live: LiveLocator): Promise<PaneLocator | undefined> {
99
116
  const own: PaneLocator | undefined = await tmux.inspectPane?.();
100
- if (!own || !sameServer(own, live.locator)) return false;
117
+ if (!own || !sameServer(own, live.locator)) return undefined;
101
118
  const pane: PaneLocator | undefined = await tmux.inspectPane?.(live.locator.paneId);
102
- if (
103
- !pane ||
104
- pane.exited ||
105
- !sameServer(pane, live.locator) ||
106
- pane.windowId !== live.locator.windowId ||
107
- pane.windowName !== live.locator.windowName
108
- )
109
- return false;
119
+ if (!pane || pane.exited || !sameServer(pane, live.locator)) return undefined;
110
120
  const windows = await listWindows(tmux);
111
- return !!windows?.some((window) => window.id === pane.windowId && window.name === pane.windowName);
121
+ return windows?.some((window) => window.id === pane.windowId && window.name === pane.windowName) ? pane : undefined;
112
122
  }
113
123
 
114
124
  export async function switchAncestor(
@@ -122,21 +132,23 @@ export async function switchAncestor(
122
132
  if (!ancestorSessions(ctx).some((ancestor) => ancestor.sessionId === sessionId))
123
133
  throw new Error("target is not a current ancestor");
124
134
  const live: LiveLocator | undefined = await reader.locator(sessionId);
125
- if (!live || !(await verifyLiveWindow(tmux, live))) throw new Error("no verified live window for this ancestor");
135
+ const pane = live ? await verifyLiveWindow(tmux, live) : undefined;
136
+ if (!live || !pane) throw new Error("no verified live window for this ancestor");
126
137
  assertActive();
127
138
  const own = await tmux.ownSession();
128
139
  const client: string | undefined = own ? await tmux.clientFor(own.name) : undefined;
129
140
  if (!client) throw new Error("no tmux client attached to this session");
130
141
  const current: LiveLocator | undefined = await reader.locator(sessionId);
131
142
  assertActive();
132
- if (!current || !sameTarget(live, current) || !(await verifyLiveWindow(tmux, current)))
143
+ const verified = current ? await verifyLiveWindow(tmux, current) : undefined;
144
+ if (!current || !sameTarget(live, current) || !verified || JSON.stringify(pane) !== JSON.stringify(verified))
133
145
  throw new Error("ancestor window changed or is unavailable");
134
146
  assertActive();
135
147
  if (!ancestorSessions(ctx).some((ancestor) => ancestor.sessionId === sessionId))
136
148
  throw new Error("ancestor identity changed");
137
149
  if (!matchesSessionOwner(sessionId, current.pid, current.ownerToken) || !sessionIsOwned(sessionId))
138
150
  throw new Error("ancestor owner changed or is unavailable");
139
- await tmux.switchTo(client, current.locator.windowId);
151
+ await tmux.switchTo(client, verified.windowId);
140
152
  assertActive();
141
- ctx.ui.notify(`delegate: switched to ancestor ${sessionId} (${current.locator.windowId})`, "info");
153
+ ctx.ui.notify(`delegate: switched to ancestor ${sessionId} (${verified.windowId})`, "info");
142
154
  }
@@ -1,40 +1,41 @@
1
1
  // Optional live-window overrides for known local children, never semantic rewrites.
2
2
  import { isStarted, type Results, type StartedRecorded, sessionIdOf } from "./results.ts";
3
- import type { PaneLocator, TmuxClient, TmuxWindow } from "./tmux.ts";
4
- import type { LiveActivityReader, LiveLocator } from "./widget-live.ts";
3
+ import type { PaneListing } from "./tmux.ts";
4
+ import type { LiveActivityReader } from "./widget-live.ts";
5
5
 
6
6
  export async function currentChildWindows(
7
7
  results: Results,
8
- tmux: TmuxClient,
9
- windows: TmuxWindow[] | undefined,
8
+ listing: PaneListing | undefined,
10
9
  reader: LiveActivityReader,
11
10
  ): Promise<Map<string, { windowId: string; windowName: string }>> {
12
- const found: Map<string, { windowId: string; windowName: string }> = new Map();
13
- if (!windows?.length || !tmux.inspectPane) return found;
14
- const latest: Map<string, StartedRecorded> = new Map(
11
+ const found = new Map<string, { windowId: string; windowName: string }>();
12
+ if (!listing?.server || !listing.windows.length) return found;
13
+ const server = listing.server;
14
+ const latest = new Map<string, StartedRecorded>(
15
15
  results
16
16
  .list()
17
17
  .filter(isStarted)
18
18
  .map((record) => [sessionIdOf(record), record]),
19
19
  );
20
- const candidates: StartedRecorded[] = [...latest.values()].filter((record) => !record.closed);
21
- if (!candidates.length) return found;
22
- const own: PaneLocator | undefined = await tmux.inspectPane().catch(() => undefined);
23
- if (!own) return found;
24
20
  await Promise.all(
25
- candidates.map(async (record): Promise<void> => {
26
- const live: LiveLocator | undefined = await reader.locator(sessionIdOf(record));
21
+ [...latest.values()].map(async (record): Promise<void> => {
22
+ if (
23
+ record.closed ||
24
+ listing.windows.some(
25
+ (window) => window.id === record.windowId && window.name === record.windowName && !window.exited,
26
+ )
27
+ )
28
+ return;
29
+ const live = await reader.locator(sessionIdOf(record));
27
30
  if (
28
31
  !live ||
29
- live.locator.socket !== own.socket ||
30
- live.locator.serverPid !== own.serverPid ||
31
- live.locator.serverStarted !== own.serverStarted
32
+ live.locator.socket !== server.socket ||
33
+ live.locator.serverPid !== server.serverPid ||
34
+ live.locator.serverStarted !== server.serverStarted
32
35
  )
33
36
  return;
34
- const pane: PaneLocator | undefined = await tmux.inspectPane?.(live.locator.paneId).catch(() => undefined);
35
- if (!pane || pane.exited || JSON.stringify(pane) !== JSON.stringify(live.locator)) return;
36
- if (windows.some((window) => window.id === pane.windowId && window.name === pane.windowName))
37
- found.set(record.id, { windowId: pane.windowId, windowName: pane.windowName });
37
+ const pane = listing.panes.get(live.locator.paneId);
38
+ if (pane && !pane.exited) found.set(record.id, { windowId: pane.windowId, windowName: pane.windowName });
38
39
  }),
39
40
  );
40
41
  return found;
@@ -12,7 +12,12 @@ export type DelegateRow = {
12
12
  key: string;
13
13
  sessionId: string;
14
14
  launchId: string;
15
+ parentKey?: string;
16
+ depth?: number;
17
+ descendantCount?: number;
18
+ expanded?: boolean;
15
19
  identity: string;
20
+ agent?: string;
16
21
  title: string | undefined;
17
22
  state: string;
18
23
  running: boolean;
@@ -21,7 +26,6 @@ export type DelegateRow = {
21
26
  /** Transcript mtime is a last-active hint, not proof of tool activity. */
22
27
  lastActive: number | undefined;
23
28
  activity?: Activity;
24
- summary?: string;
25
29
  };
26
30
 
27
31
  /** All eligible rows remain available; only this viewport incurs metadata I/O. */
@@ -82,6 +86,7 @@ export class DirectDelegateView {
82
86
  sessionDir: string | undefined,
83
87
  viewport: MetadataViewport = { offset: 0, limit: 3 },
84
88
  currentWindows: ReadonlyMap<string, { windowId: string; windowName: string }> = new Map(),
89
+ visibleKeys?: ReadonlySet<string>,
85
90
  ): DelegateRow[] {
86
91
  const running: Set<string> = new Set(results.running().map((record) => record.id));
87
92
  const entries: Entry[] = [];
@@ -100,7 +105,8 @@ export class DirectDelegateView {
100
105
  ...entries.filter(({ row }) => !row.running),
101
106
  ];
102
107
  return ordered.map(({ record, row }, index) => {
103
- if (index >= viewport.offset && index < viewport.offset + viewport.limit) this.enrich(record, row, sessionDir);
108
+ if (visibleKeys ? visibleKeys.has(row.key) : index >= viewport.offset && index < viewport.offset + viewport.limit)
109
+ this.enrich(record, row, sessionDir);
104
110
  return row;
105
111
  });
106
112
  }
@@ -111,7 +117,9 @@ export class DirectDelegateView {
111
117
  key: sessionId,
112
118
  sessionId,
113
119
  launchId: record.id,
120
+ parentKey: record.identity?.parent,
114
121
  identity: recordedName(record),
122
+ agent: record.agent,
115
123
  title: undefined,
116
124
  lastActive: undefined,
117
125
  state: running ? "running" : (record.result?.status ?? (record.gone ? "missing" : "unknown")),
@@ -145,3 +153,28 @@ export class DirectDelegateView {
145
153
  row.lastActive = disk?.modified;
146
154
  }
147
155
  }
156
+
157
+ export function delegateTree(rows: DelegateRow[], viewer: string, expanded: ReadonlySet<string>): DelegateRow[] {
158
+ const children = new Map<string, DelegateRow[]>();
159
+ const keys = new Set(rows.map((row) => row.key));
160
+ for (const row of rows) {
161
+ const parent = row.parentKey && keys.has(row.parentKey) ? row.parentKey : viewer;
162
+ const siblings = children.get(parent) ?? [];
163
+ siblings.push(row);
164
+ children.set(parent, siblings);
165
+ }
166
+ const count = (key: string): number =>
167
+ (children.get(key) ?? []).reduce((total, child) => total + 1 + count(child.key), 0);
168
+ const visible: DelegateRow[] = [];
169
+ const walk = (parent: string, depth: number): void => {
170
+ for (const row of children.get(parent) ?? []) {
171
+ row.depth = depth;
172
+ row.descendantCount = count(row.key);
173
+ row.expanded = expanded.has(row.key);
174
+ visible.push(row);
175
+ if (expanded.has(row.key)) walk(row.key, depth + 1);
176
+ }
177
+ };
178
+ walk(viewer, 0);
179
+ return visible;
180
+ }
@@ -29,10 +29,12 @@ type FocusOptions = {
29
29
  selection: WidgetSelection;
30
30
  changed(): void;
31
31
  open(key: string): Promise<void>;
32
+ close(key: string): Promise<void>;
33
+ horizontal(direction: -1 | 1): void;
32
34
  error(error: unknown): void;
33
35
  };
34
36
 
35
- /** Pseudo-focus: real Pi focus stays with the prompt until Pi mounts a dashboard/dialog. */
37
+ /** Pseudo-focus leaves real Pi focus with the prompt or its confirmation dialog. */
36
38
  export class WidgetFocus {
37
39
  private readonly options: FocusOptions;
38
40
  private tui: unknown;
@@ -68,7 +70,7 @@ export class WidgetFocus {
68
70
  suspend(suspended: boolean): void {
69
71
  if (this.stopped) return;
70
72
  this.suspended = suspended;
71
- // Our own dashboard keeps selection; unrelated prompts relinquish pseudo-focus.
73
+ // Our own confirmation keeps selection; unrelated prompts relinquish pseudo-focus.
72
74
  if (suspended && !this.busy) this.options.selection.leave();
73
75
  this.options.changed();
74
76
  }
@@ -88,8 +90,7 @@ export class WidgetFocus {
88
90
 
89
91
  private input(data: string): { consume: true } | undefined {
90
92
  if (this.stopped || isKeyRelease(data)) return;
91
- // Async listing/action loops temporarily return focus to the prompt. Do not
92
- // let dashboard Escape abort a run or action letters leak into that editor.
93
+ // Pending actions consume keys only while the exact empty prompt still owns focus.
93
94
  if (this.busy) return this.pendingInput();
94
95
  if (this.suspended || promptState(this.tui, this.options.ui) !== "empty") {
95
96
  this.leave();
@@ -98,11 +99,7 @@ export class WidgetFocus {
98
99
  const selection: WidgetSelection = this.options.selection;
99
100
  if (!selection.active) {
100
101
  if (!matchesKey(data, "down") || !selection.enter()) return;
101
- } else if (matchesKey(data, "down")) selection.move(1);
102
- else if (matchesKey(data, "up")) selection.move(-1);
103
- else if (matchesKey(data, "escape")) selection.leave();
104
- else if (matchesKey(data, "enter")) this.open();
105
- else {
102
+ } else if (!this.navigate(data)) {
106
103
  this.leave();
107
104
  return;
108
105
  }
@@ -110,17 +107,30 @@ export class WidgetFocus {
110
107
  return { consume: true };
111
108
  }
112
109
 
110
+ private navigate(data: string): boolean {
111
+ const selection = this.options.selection;
112
+ if (matchesKey(data, "down")) selection.move(1);
113
+ else if (matchesKey(data, "up")) selection.move(-1);
114
+ else if (matchesKey(data, "escape")) selection.leave();
115
+ else if (matchesKey(data, "left")) this.options.horizontal(-1);
116
+ else if (matchesKey(data, "right")) this.options.horizontal(1);
117
+ else if (matchesKey(data, "enter")) this.action("open");
118
+ else if (matchesKey(data, "x")) this.action("close");
119
+ else return false;
120
+ return true;
121
+ }
122
+
113
123
  private pendingInput(): { consume: true } | undefined {
114
124
  return promptState(this.tui, this.options.ui) === "empty" ? { consume: true } : undefined;
115
125
  }
116
126
 
117
- private open(): void {
127
+ private action(action: "open" | "close"): void {
118
128
  const key: string | undefined = this.options.selection.selectedKey;
119
129
  if (key === undefined) return;
120
130
  this.busy = true; // Set before invoking async custom UI; repeated Enter cannot reenter.
121
131
  void Promise.resolve()
122
132
  .then(() => {
123
- if (!this.stopped) return this.options.open(key);
133
+ if (!this.stopped) return this.options[action](key);
124
134
  })
125
135
  .catch((error: unknown) => {
126
136
  if (!this.stopped) this.options.error(error);
@@ -6,8 +6,8 @@ import { join } from "node:path";
6
6
  import { getAgentDir } from "@earendil-works/pi-coding-agent";
7
7
  import { matchesSessionOwner } from "./session-owner.ts";
8
8
  import type { PaneLocator } from "./tmux.ts";
9
+ import { processAlive } from "./tree-lock.ts";
9
10
  import { type Activity, activityText } from "./widget-activity.ts";
10
- import { type Liveness, processLiveness, processStart } from "./widget-live-process.ts";
11
11
 
12
12
  export const LIVE_LAUNCH_ENV = "PI_SQUIRE_LIVE_LAUNCH";
13
13
  export const LIVE_MAX_BYTES = 8192;
@@ -29,7 +29,6 @@ type Snapshot = {
29
29
  launchId?: string;
30
30
  generation: string;
31
31
  pid: number;
32
- processStart: string;
33
32
  revision: number;
34
33
  observedAt: number;
35
34
  activity: Activity | null;
@@ -97,7 +96,6 @@ function validSnapshot(value: Partial<Snapshot>, sessionId: string, generation:
97
96
  (value.launchId === undefined || validString(value.launchId)) &&
98
97
  Number.isSafeInteger(value.pid) &&
99
98
  (value.pid ?? 0) > 0 &&
100
- validString(value.processStart) &&
101
99
  Number.isSafeInteger(value.revision) &&
102
100
  (value.revision ?? -1) >= 0 &&
103
101
  typeof value.observedAt === "number" &&
@@ -131,13 +129,17 @@ function snapshotFile(name: string, file: boolean): boolean {
131
129
  return file && /^[a-f0-9-]{36}\.json$/.test(name);
132
130
  }
133
131
 
134
- type Probe = (owner: { pid: number; processStart: string }) => Promise<Liveness>;
132
+ type Liveness = "live" | "dead" | "unknown";
133
+ type Probe = (owner: { pid: number }) => Promise<Liveness>;
135
134
 
136
135
  export class LiveActivityReader {
137
136
  private readonly agentDir: string;
138
137
  private readonly probe: Probe;
139
138
 
140
- constructor(agentDir = getAgentDir(), probe: Probe = processLiveness) {
139
+ constructor(
140
+ agentDir = getAgentDir(),
141
+ probe: Probe = async ({ pid }: { pid: number }): Promise<Liveness> => (processAlive(pid) ? "live" : "dead"),
142
+ ) {
141
143
  this.agentDir = agentDir;
142
144
  this.probe = probe;
143
145
  }
@@ -209,7 +211,6 @@ export class LiveActivityReader {
209
211
  }
210
212
 
211
213
  type PublisherOptions = {
212
- start?: () => Promise<string | undefined>;
213
214
  rename?: typeof rename;
214
215
  };
215
216
 
@@ -221,7 +222,6 @@ export class LiveActivityPublisher {
221
222
  private readonly dir: string;
222
223
  private readonly path: string;
223
224
  private readonly temporary: string;
224
- private readonly start: Promise<string | undefined>;
225
225
  private readonly replace: typeof rename;
226
226
  private activity: Activity | null = null;
227
227
  private locator: PaneLocator | undefined;
@@ -243,9 +243,6 @@ export class LiveActivityPublisher {
243
243
  this.dir = liveSessionDir(sessionId);
244
244
  this.path = join(this.dir, `${this.generation}.json`);
245
245
  this.temporary = `${this.path}.tmp`;
246
- this.start = (options.start ?? ((): Promise<string | undefined> => processStart(process.pid)))().catch(
247
- () => undefined,
248
- );
249
246
  this.replace = options.rename ?? rename;
250
247
  }
251
248
 
@@ -311,11 +308,6 @@ export class LiveActivityPublisher {
311
308
  }
312
309
 
313
310
  private async drain(): Promise<void> {
314
- const start: string | undefined = await this.start;
315
- if (!start || this.stopped) {
316
- this.dirty = false;
317
- return;
318
- }
319
311
  if (this.dirty && !this.stopped) {
320
312
  this.dirty = false;
321
313
  this.urgent = false;
@@ -326,7 +318,6 @@ export class LiveActivityPublisher {
326
318
  launchId: this.launchId,
327
319
  generation: this.generation,
328
320
  pid: process.pid,
329
- processStart: start,
330
321
  revision: this.revision,
331
322
  observedAt: this.observedAt,
332
323
  activity: this.activity,