pi-sessions 0.8.0 → 0.9.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 (104) hide show
  1. package/README.md +70 -37
  2. package/extensions/pi-sessions.ts +125 -0
  3. package/extensions/session-ask/agent.ts +34 -14
  4. package/extensions/{session-ask.ts → session-ask/install.ts} +73 -90
  5. package/extensions/session-ask/navigate.ts +3 -1
  6. package/extensions/session-ask/presenter.ts +23 -0
  7. package/extensions/session-ask/renderer.ts +63 -0
  8. package/extensions/session-ask/tool-contract.ts +27 -0
  9. package/extensions/session-ask/view-model.ts +18 -0
  10. package/extensions/session-auto-title/generate.ts +18 -18
  11. package/extensions/{session-auto-title.ts → session-auto-title/install.ts} +60 -53
  12. package/extensions/session-auto-title/model.ts +25 -17
  13. package/extensions/session-auto-title/retitle.ts +17 -18
  14. package/extensions/session-auto-title/wizard.ts +12 -11
  15. package/extensions/session-handoff/board-view-model.ts +181 -0
  16. package/extensions/session-handoff/board.ts +663 -0
  17. package/extensions/session-handoff/bootstrap.ts +174 -0
  18. package/extensions/session-handoff/extract.ts +101 -122
  19. package/extensions/session-handoff/install.ts +225 -0
  20. package/extensions/session-handoff/kickoff.ts +92 -0
  21. package/extensions/session-handoff/launch/backend.ts +16 -0
  22. package/extensions/session-handoff/launch/deferred.ts +20 -0
  23. package/extensions/session-handoff/launch/ghostty.ts +99 -0
  24. package/extensions/session-handoff/launch/resolution.ts +57 -0
  25. package/extensions/session-handoff/launch/shell.ts +7 -0
  26. package/extensions/session-handoff/launch/tmux.ts +31 -0
  27. package/extensions/session-handoff/launch-options.ts +61 -0
  28. package/extensions/session-handoff/launch-target.ts +91 -0
  29. package/extensions/session-handoff/metadata.ts +152 -63
  30. package/extensions/session-handoff/model.ts +58 -0
  31. package/extensions/session-handoff/query.ts +0 -1
  32. package/extensions/session-handoff/receipt.ts +96 -0
  33. package/extensions/session-handoff/review.ts +2 -28
  34. package/extensions/session-handoff/spawn.ts +76 -181
  35. package/extensions/session-handoff/strong-modal.ts +13 -0
  36. package/extensions/session-handoff/tool-contract.ts +12 -0
  37. package/extensions/session-handoff/tool-presenter.ts +35 -0
  38. package/extensions/session-handoff/tool-renderer.ts +66 -0
  39. package/extensions/session-handoff/tool-schema.ts +54 -0
  40. package/extensions/session-handoff/tool-view-model.ts +49 -0
  41. package/extensions/session-handoff/tool.ts +206 -0
  42. package/extensions/session-handoff/ui.ts +66 -0
  43. package/extensions/session-hooks/install.ts +75 -0
  44. package/extensions/{session-index.ts → session-index/install.ts} +9 -7
  45. package/extensions/session-messaging/broker/process.ts +21 -28
  46. package/extensions/session-messaging/install.ts +78 -0
  47. package/extensions/session-messaging/pi/cancel-session-presenter.ts +64 -0
  48. package/extensions/session-messaging/pi/incoming-message-presenter.ts +32 -0
  49. package/extensions/session-messaging/pi/incoming-message-view-model.ts +20 -0
  50. package/extensions/session-messaging/pi/incoming-runtime.ts +27 -23
  51. package/extensions/session-messaging/pi/message-contracts.ts +32 -0
  52. package/extensions/session-messaging/pi/renderer.ts +19 -12
  53. package/extensions/session-messaging/pi/send-message-presenter.ts +39 -0
  54. package/extensions/session-messaging/pi/send-message-view-model.ts +54 -0
  55. package/extensions/session-messaging/pi/service.ts +262 -81
  56. package/extensions/session-messaging/pi/tools.ts +184 -40
  57. package/extensions/session-search/extract.ts +60 -14
  58. package/extensions/session-search/hooks.ts +10 -13
  59. package/extensions/session-search/install.ts +373 -0
  60. package/extensions/session-search/renderer.ts +195 -0
  61. package/extensions/session-search/tool-contract.ts +51 -0
  62. package/extensions/shared/composition.ts +20 -0
  63. package/extensions/shared/errors.ts +3 -0
  64. package/extensions/shared/model-resolution.ts +73 -0
  65. package/extensions/shared/model-runtime.ts +33 -0
  66. package/extensions/shared/model.ts +7 -10
  67. package/extensions/shared/rendering/collapsible-text.ts +89 -0
  68. package/extensions/shared/rendering/expandable-content-layout.ts +84 -0
  69. package/extensions/shared/rendering/theme.ts +5 -0
  70. package/extensions/shared/session-broker/client.ts +30 -29
  71. package/extensions/shared/session-broker/protocol.ts +128 -10
  72. package/extensions/shared/session-index/common.ts +5 -5
  73. package/extensions/shared/session-index/lineage.ts +0 -3
  74. package/extensions/shared/session-index/schema.ts +0 -1
  75. package/extensions/shared/session-index/scoring.ts +1 -2
  76. package/extensions/shared/session-index/search.ts +11 -4
  77. package/extensions/shared/session-index/store.ts +4 -9
  78. package/extensions/shared/settings.ts +76 -38
  79. package/extensions/shared/text.ts +5 -0
  80. package/extensions/shared/thinking-levels.ts +22 -0
  81. package/extensions/shared/tmux.ts +205 -0
  82. package/extensions/subagents/cancel.ts +111 -0
  83. package/extensions/subagents/classify.ts +52 -0
  84. package/extensions/subagents/install.ts +344 -0
  85. package/extensions/subagents/launch-target.ts +75 -0
  86. package/extensions/subagents/ledger.ts +217 -0
  87. package/extensions/subagents/reconcile.ts +386 -0
  88. package/extensions/subagents/report-message-presenter.ts +36 -0
  89. package/extensions/subagents/report-message-renderer.ts +21 -0
  90. package/extensions/subagents/report-message-view-model.ts +29 -0
  91. package/extensions/subagents/report.ts +159 -0
  92. package/extensions/subagents/roster.ts +262 -0
  93. package/extensions/subagents/wake.ts +173 -0
  94. package/images/handoff-board-subagents.png +0 -0
  95. package/images/handoff-board-user-sessions.png +0 -0
  96. package/images/handoff-subagent-report.png +0 -0
  97. package/images/session-handoff-tool.png +0 -0
  98. package/package.json +10 -16
  99. package/extensions/session-handoff.ts +0 -741
  100. package/extensions/session-hooks.ts +0 -75
  101. package/extensions/session-messaging/pi/message-view.ts +0 -131
  102. package/extensions/session-messaging.ts +0 -30
  103. package/extensions/session-search.ts +0 -419
  104. package/extensions/shared/session-broker/active.ts +0 -26
@@ -3,13 +3,14 @@ import {
3
3
  copyToClipboard,
4
4
  type ExtensionAPI,
5
5
  type ExtensionCommandContext,
6
+ type ModelRuntime,
6
7
  type Theme,
7
8
  } from "@earendil-works/pi-coding-agent";
8
9
  import type { Focusable, TUI } from "@earendil-works/pi-tui";
9
10
  import { matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
10
11
  import type { RetitleCommandOutcome, RetitleMode, RetitleScope } from "./command.ts";
11
12
  import type { AutoRetitleStatus, SessionAutoTitleController } from "./controller.ts";
12
- import type { AutoTitleFailure } from "./generate.ts";
13
+ import type { AutoTitleFailure, AutoTitleGeneration } from "./generate.ts";
13
14
  import {
14
15
  buildBulkRetitleMessage,
15
16
  buildRetitleScopeScan,
@@ -54,10 +55,10 @@ export async function showRetitleWizard(
54
55
  pi: ExtensionAPI,
55
56
  controller: SessionAutoTitleController,
56
57
  ctx: ExtensionCommandContext,
58
+ modelRuntime: ModelRuntime,
57
59
  model: Model<Api> | undefined,
58
60
  getSessionEpoch: () => number,
59
- systemPrompt: string,
60
- timeoutMs: number,
61
+ generation: AutoTitleGeneration,
61
62
  options?: RetitleWizardOptions,
62
63
  ): Promise<RetitleCommandOutcome> {
63
64
  return ctx.ui.custom<RetitleCommandOutcome>(
@@ -68,10 +69,10 @@ export async function showRetitleWizard(
68
69
  pi,
69
70
  controller,
70
71
  ctx,
72
+ modelRuntime,
71
73
  model,
72
74
  getSessionEpoch,
73
- systemPrompt,
74
- timeoutMs,
75
+ generation,
75
76
  done,
76
77
  options,
77
78
  ),
@@ -98,10 +99,10 @@ class RetitleWizardPanel implements Focusable {
98
99
  private readonly pi: ExtensionAPI,
99
100
  private readonly controller: SessionAutoTitleController,
100
101
  private readonly ctx: ExtensionCommandContext,
102
+ private readonly modelRuntime: ModelRuntime,
101
103
  private readonly model: Model<Api> | undefined,
102
104
  private readonly getSessionEpoch: () => number,
103
- private readonly systemPrompt: string,
104
- private readonly timeoutMs: number,
105
+ private readonly generation: AutoTitleGeneration,
105
106
  private readonly done: (result: RetitleCommandOutcome) => void,
106
107
  options?: RetitleWizardOptions,
107
108
  ) {
@@ -250,10 +251,10 @@ class RetitleWizardPanel implements Focusable {
250
251
  pi: this.pi,
251
252
  controller: this.controller,
252
253
  ctx: this.ctx,
254
+ modelRuntime: this.modelRuntime,
253
255
  model: this.model,
254
256
  isManual: true,
255
- systemPrompt: this.systemPrompt,
256
- timeoutMs: this.timeoutMs,
257
+ generation: this.generation,
257
258
  getSessionEpoch: this.getSessionEpoch,
258
259
  });
259
260
  if (result.ok) {
@@ -309,12 +310,12 @@ class RetitleWizardPanel implements Focusable {
309
310
  this.pi,
310
311
  this.controller,
311
312
  this.ctx,
313
+ this.modelRuntime,
312
314
  this.model,
313
315
  scan,
314
316
  mode,
315
317
  this.getSessionEpoch,
316
- this.systemPrompt,
317
- this.timeoutMs,
318
+ this.generation,
318
319
  );
319
320
  notifyBulkRetitleResult(this.ctx, scan, mode, result);
320
321
  this.done(
@@ -0,0 +1,181 @@
1
+ import { tmuxSessionName } from "../shared/tmux.ts";
2
+ import type { SubagentState } from "../subagents/classify.ts";
3
+ import type { SubagentRosterEntry } from "../subagents/roster.ts";
4
+ import { shellQuote } from "./launch/shell.ts";
5
+ import type { HandoffLaunchReceipt } from "./receipt.ts";
6
+
7
+ const RUNNING_SUBAGENT_STATES: ReadonlySet<SubagentState> = new Set(["starting", "busy", "active"]);
8
+
9
+ export type HandoffBoardTab = "subagents" | "user-sessions";
10
+ export type UserSessionStatus = "live" | "ready" | "starting" | "closed" | "unknown";
11
+
12
+ export interface UserSessionRunEvidence {
13
+ transcriptAvailable: boolean;
14
+ hasStarted: boolean;
15
+ }
16
+
17
+ export interface UserSessionEntry {
18
+ sessionId: string;
19
+ timestamp: string;
20
+ receipt: HandoffLaunchReceipt;
21
+ runEvidence?: UserSessionRunEvidence | undefined;
22
+ }
23
+
24
+ export interface HandoffBoardSnapshot {
25
+ subagents: readonly SubagentRosterEntry[];
26
+ userSessions: readonly UserSessionEntry[];
27
+ liveSessionIds: ReadonlySet<string>;
28
+ hasLiveSessionEvidence: boolean;
29
+ }
30
+
31
+ export interface HandoffBoardDetail {
32
+ label: string;
33
+ value: string;
34
+ }
35
+
36
+ export interface HandoffBoardAction {
37
+ subagent?: SubagentRosterEntry | undefined;
38
+ canStop: boolean;
39
+ observeCommand?: string | undefined;
40
+ resumeCommand?: string | undefined;
41
+ }
42
+
43
+ export interface HandoffSubagentRow {
44
+ kind: "subagent";
45
+ title: string;
46
+ depth: number;
47
+ status: SubagentState;
48
+ timestamp: string;
49
+ }
50
+
51
+ export interface UserSessionRow {
52
+ kind: "user-session";
53
+ title: string;
54
+ status: UserSessionStatus;
55
+ timestamp: string;
56
+ }
57
+
58
+ export interface HandoffSubagentsView {
59
+ tab: "subagents";
60
+ rows: readonly HandoffSubagentRow[];
61
+ details: readonly HandoffBoardDetail[];
62
+ action?: HandoffBoardAction | undefined;
63
+ }
64
+
65
+ export interface UserSessionsView {
66
+ tab: "user-sessions";
67
+ rows: readonly UserSessionRow[];
68
+ details: readonly HandoffBoardDetail[];
69
+ action?: HandoffBoardAction | undefined;
70
+ }
71
+
72
+ export type HandoffBoardView = HandoffSubagentsView | UserSessionsView;
73
+
74
+ export function buildHandoffBoardView(
75
+ snapshot: HandoffBoardSnapshot,
76
+ tab: HandoffBoardTab,
77
+ selectedIndex: number,
78
+ options: { insideTmux: boolean },
79
+ ): HandoffBoardView {
80
+ if (tab === "subagents") {
81
+ const selected = snapshot.subagents[selectedIndex];
82
+ return {
83
+ tab,
84
+ rows: snapshot.subagents.map((subagent) => ({
85
+ kind: "subagent",
86
+ title: subagent.title,
87
+ depth: subagent.depth,
88
+ status: subagent.state,
89
+ timestamp: subagent.launchedAt,
90
+ })),
91
+ details: selected
92
+ ? [
93
+ { label: "Session", value: selected.sessionId },
94
+ { label: "Model", value: selected.model ?? "Default" },
95
+ {
96
+ label: "Owner",
97
+ value: selected.ownerIsCurrentSession ? "this session" : selected.ownerTitle,
98
+ },
99
+ { label: "Goal", value: selected.goal },
100
+ ]
101
+ : [],
102
+ ...(selected ? { action: buildSubagentAction(selected, options.insideTmux) } : {}),
103
+ };
104
+ }
105
+
106
+ const rows = snapshot.userSessions.map(
107
+ (entry): UserSessionRow => ({
108
+ kind: "user-session",
109
+ title: entry.receipt.title,
110
+ status: getUserSessionStatus(snapshot, entry),
111
+ timestamp: entry.timestamp,
112
+ }),
113
+ );
114
+ const selected = snapshot.userSessions[selectedIndex];
115
+ if (!selected) {
116
+ return { tab, rows, details: [] };
117
+ }
118
+
119
+ const details: HandoffBoardDetail[] = [
120
+ { label: "Session", value: selected.sessionId },
121
+ { label: "Model", value: selected.receipt.model },
122
+ { label: "Launch", value: selected.receipt.launch },
123
+ ];
124
+ if (selected.receipt.backend) {
125
+ details.push({ label: "Backend", value: selected.receipt.backend });
126
+ }
127
+ if (selected.receipt.cwd) {
128
+ details.push({ label: "Directory", value: selected.receipt.cwd });
129
+ }
130
+ const action = buildUserSessionAction(snapshot, selected);
131
+ return {
132
+ tab,
133
+ rows,
134
+ details,
135
+ ...(action ? { action } : {}),
136
+ };
137
+ }
138
+
139
+ function getUserSessionStatus(
140
+ snapshot: HandoffBoardSnapshot,
141
+ entry: UserSessionEntry,
142
+ ): UserSessionStatus {
143
+ if (snapshot.liveSessionIds.has(entry.sessionId)) {
144
+ return "live";
145
+ }
146
+ const evidence = entry.runEvidence;
147
+ if (!evidence?.transcriptAvailable) {
148
+ return "unknown";
149
+ }
150
+ if (evidence.hasStarted) {
151
+ return snapshot.hasLiveSessionEvidence ? "closed" : "unknown";
152
+ }
153
+ return entry.receipt.launch === "deferred" ? "ready" : "starting";
154
+ }
155
+
156
+ function buildUserSessionAction(
157
+ snapshot: HandoffBoardSnapshot,
158
+ entry: UserSessionEntry,
159
+ ): HandoffBoardAction | undefined {
160
+ if (!snapshot.hasLiveSessionEvidence || snapshot.liveSessionIds.has(entry.sessionId)) {
161
+ return undefined;
162
+ }
163
+ return {
164
+ canStop: false,
165
+ resumeCommand: entry.receipt.resumeCommand,
166
+ };
167
+ }
168
+
169
+ function buildSubagentAction(entry: SubagentRosterEntry, insideTmux: boolean): HandoffBoardAction {
170
+ return {
171
+ subagent: entry,
172
+ canStop: RUNNING_SUBAGENT_STATES.has(entry.state),
173
+ ...(entry.tmuxWindowId ? { observeCommand: buildObserveCommand(entry, insideTmux) } : {}),
174
+ ...(!entry.managedLive ? { resumeCommand: entry.resumeCommand } : {}),
175
+ };
176
+ }
177
+
178
+ function buildObserveCommand(entry: SubagentRosterEntry, insideTmux: boolean): string {
179
+ const target = shellQuote(`${tmuxSessionName(entry.ownerSessionId)}:${entry.tmuxWindowId}`);
180
+ return insideTmux ? `tmux switch-client -t ${target}` : `tmux attach-session -t ${target}`;
181
+ }