pi-sessions 0.8.0 → 0.10.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 (111) hide show
  1. package/README.md +74 -37
  2. package/dist/session-messaging/broker/process.js +234 -0
  3. package/dist/shared/session-broker/framing.js +58 -0
  4. package/dist/shared/session-broker/protocol.js +163 -0
  5. package/dist/shared/session-broker/socket-path.js +19 -0
  6. package/dist/shared/typebox.js +36 -0
  7. package/extensions/pi-sessions.ts +125 -0
  8. package/extensions/session-ask/agent.ts +34 -14
  9. package/extensions/{session-ask.ts → session-ask/install.ts} +73 -90
  10. package/extensions/session-ask/navigate.ts +3 -1
  11. package/extensions/session-ask/presenter.ts +23 -0
  12. package/extensions/session-ask/renderer.ts +63 -0
  13. package/extensions/session-ask/tool-contract.ts +27 -0
  14. package/extensions/session-ask/view-model.ts +18 -0
  15. package/extensions/session-auto-title/generate.ts +18 -18
  16. package/extensions/{session-auto-title.ts → session-auto-title/install.ts} +60 -53
  17. package/extensions/session-auto-title/model.ts +25 -17
  18. package/extensions/session-auto-title/retitle.ts +17 -18
  19. package/extensions/session-auto-title/wizard.ts +12 -11
  20. package/extensions/session-handoff/board-view-model.ts +179 -0
  21. package/extensions/session-handoff/board.ts +663 -0
  22. package/extensions/session-handoff/bootstrap.ts +172 -0
  23. package/extensions/session-handoff/extract.ts +129 -132
  24. package/extensions/session-handoff/install.ts +225 -0
  25. package/extensions/session-handoff/kickoff.ts +92 -0
  26. package/extensions/session-handoff/launch/backend.ts +16 -0
  27. package/extensions/session-handoff/launch/deferred.ts +20 -0
  28. package/extensions/session-handoff/launch/ghostty.ts +99 -0
  29. package/extensions/session-handoff/launch/resolution.ts +57 -0
  30. package/extensions/session-handoff/launch/shell.ts +7 -0
  31. package/extensions/session-handoff/launch/tmux.ts +31 -0
  32. package/extensions/session-handoff/launch-options.ts +61 -0
  33. package/extensions/session-handoff/launch-target.ts +91 -0
  34. package/extensions/session-handoff/metadata.ts +194 -56
  35. package/extensions/session-handoff/model.ts +58 -0
  36. package/extensions/session-handoff/query.ts +0 -1
  37. package/extensions/session-handoff/receipt.ts +96 -0
  38. package/extensions/session-handoff/review.ts +2 -28
  39. package/extensions/session-handoff/spawn.ts +76 -181
  40. package/extensions/session-handoff/strong-modal.ts +13 -0
  41. package/extensions/session-handoff/tool-contract.ts +12 -0
  42. package/extensions/session-handoff/tool-presenter.ts +35 -0
  43. package/extensions/session-handoff/tool-renderer.ts +66 -0
  44. package/extensions/session-handoff/tool-schema.ts +54 -0
  45. package/extensions/session-handoff/tool-view-model.ts +49 -0
  46. package/extensions/session-handoff/tool.ts +206 -0
  47. package/extensions/session-handoff/ui.ts +66 -0
  48. package/extensions/session-hooks/install.ts +75 -0
  49. package/extensions/{session-index.ts → session-index/install.ts} +9 -7
  50. package/extensions/session-messaging/broker/process.ts +21 -28
  51. package/extensions/session-messaging/broker/spawn.ts +12 -7
  52. package/extensions/session-messaging/install.ts +78 -0
  53. package/extensions/session-messaging/pi/cancel-session-presenter.ts +64 -0
  54. package/extensions/session-messaging/pi/incoming-message-presenter.ts +32 -0
  55. package/extensions/session-messaging/pi/incoming-message-view-model.ts +20 -0
  56. package/extensions/session-messaging/pi/incoming-runtime.ts +27 -23
  57. package/extensions/session-messaging/pi/message-contracts.ts +32 -0
  58. package/extensions/session-messaging/pi/renderer.ts +19 -12
  59. package/extensions/session-messaging/pi/send-message-presenter.ts +39 -0
  60. package/extensions/session-messaging/pi/send-message-view-model.ts +54 -0
  61. package/extensions/session-messaging/pi/service.ts +262 -81
  62. package/extensions/session-messaging/pi/tools.ts +184 -40
  63. package/extensions/session-search/extract.ts +60 -14
  64. package/extensions/session-search/hooks.ts +10 -13
  65. package/extensions/session-search/install.ts +373 -0
  66. package/extensions/session-search/renderer.ts +195 -0
  67. package/extensions/session-search/tool-contract.ts +51 -0
  68. package/extensions/shared/composition.ts +20 -0
  69. package/extensions/shared/errors.ts +3 -0
  70. package/extensions/shared/model-resolution.ts +73 -0
  71. package/extensions/shared/model-runtime.ts +33 -0
  72. package/extensions/shared/model.ts +7 -10
  73. package/extensions/shared/rendering/collapsible-text.ts +89 -0
  74. package/extensions/shared/rendering/expandable-content-layout.ts +84 -0
  75. package/extensions/shared/rendering/theme.ts +5 -0
  76. package/extensions/shared/session-broker/client.ts +30 -29
  77. package/extensions/shared/session-broker/protocol.ts +128 -10
  78. package/extensions/shared/session-index/common.ts +5 -5
  79. package/extensions/shared/session-index/lineage.ts +0 -3
  80. package/extensions/shared/session-index/schema.ts +0 -1
  81. package/extensions/shared/session-index/scoring.ts +1 -2
  82. package/extensions/shared/session-index/search.ts +11 -4
  83. package/extensions/shared/session-index/store.ts +4 -9
  84. package/extensions/shared/settings.ts +83 -40
  85. package/extensions/shared/text.ts +5 -0
  86. package/extensions/shared/thinking-levels.ts +22 -0
  87. package/extensions/shared/tmux.ts +205 -0
  88. package/extensions/subagents/cancel.ts +111 -0
  89. package/extensions/subagents/classify.ts +58 -0
  90. package/extensions/subagents/install.ts +229 -0
  91. package/extensions/subagents/launch-target.ts +75 -0
  92. package/extensions/subagents/ledger.ts +217 -0
  93. package/extensions/subagents/reconcile.ts +386 -0
  94. package/extensions/subagents/report-message-presenter.ts +36 -0
  95. package/extensions/subagents/report-message-renderer.ts +21 -0
  96. package/extensions/subagents/report-message-view-model.ts +29 -0
  97. package/extensions/subagents/report.ts +159 -0
  98. package/extensions/subagents/roster.ts +262 -0
  99. package/extensions/subagents/settle.ts +182 -0
  100. package/extensions/subagents/wake.ts +173 -0
  101. package/images/handoff-board-subagents.png +0 -0
  102. package/images/handoff-board-user-sessions.png +0 -0
  103. package/images/handoff-subagent-report.png +0 -0
  104. package/images/session-handoff-tool.png +0 -0
  105. package/package.json +16 -18
  106. package/extensions/session-handoff.ts +0 -741
  107. package/extensions/session-hooks.ts +0 -75
  108. package/extensions/session-messaging/pi/message-view.ts +0 -131
  109. package/extensions/session-messaging.ts +0 -30
  110. package/extensions/session-search.ts +0 -419
  111. package/extensions/shared/session-broker/active.ts +0 -26
@@ -1,9 +1,9 @@
1
1
  import { appendFileSync, mkdirSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
+ import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
4
5
  import type { Api, Model, TextContent, UserMessage } from "@earendil-works/pi-ai";
5
- import { completeSimple } from "@earendil-works/pi-ai/compat";
6
- import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
6
+ import type { ModelRuntime } from "@earendil-works/pi-coding-agent";
7
7
  import type { AutoTitleContext } from "./context.ts";
8
8
  import type { AutoTitleTrigger } from "./state.ts";
9
9
 
@@ -17,6 +17,12 @@ export interface AutoTitleFailure {
17
17
  message: string;
18
18
  }
19
19
 
20
+ export interface AutoTitleGeneration {
21
+ systemPrompt: string;
22
+ timeoutMs: number;
23
+ thinkingLevel: ThinkingLevel | undefined;
24
+ }
25
+
20
26
  export type AutoTitleGenerationResult =
21
27
  | {
22
28
  ok: true;
@@ -28,12 +34,11 @@ export type AutoTitleGenerationResult =
28
34
  };
29
35
 
30
36
  export async function generateAutoTitle(
31
- ctx: ExtensionContext,
37
+ modelRuntime: ModelRuntime,
32
38
  model: Model<Api>,
33
39
  context: AutoTitleContext,
34
40
  trigger: AutoTitleTrigger,
35
- systemPrompt: string,
36
- timeoutMs: number,
41
+ generation: AutoTitleGeneration,
37
42
  ): Promise<AutoTitleGenerationResult> {
38
43
  if (!context.conversationText) {
39
44
  return {
@@ -46,16 +51,11 @@ export async function generateAutoTitle(
46
51
  };
47
52
  }
48
53
 
49
- const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
50
- if (!auth.ok) {
51
- return {
52
- ok: false,
53
- failure: createAutoTitleFailure(trigger, model, "Failed to authenticate auto-title model."),
54
- };
55
- }
56
-
57
54
  const shouldPreserveTitle = trigger === "periodic" && Boolean(context.currentTitle?.trim());
58
- const resolvedSystemPrompt = buildAutoTitleSystemPrompt(systemPrompt, shouldPreserveTitle);
55
+ const resolvedSystemPrompt = buildAutoTitleSystemPrompt(
56
+ generation.systemPrompt,
57
+ shouldPreserveTitle,
58
+ );
59
59
  const userPrompt = buildAutoTitlePrompt(context, resolvedSystemPrompt, shouldPreserveTitle);
60
60
  const message: UserMessage = {
61
61
  role: "user",
@@ -64,7 +64,8 @@ export async function generateAutoTitle(
64
64
  };
65
65
 
66
66
  const abortController = new AbortController();
67
- const timeoutId = setTimeout(() => abortController.abort(), timeoutMs);
67
+ const timeoutId = setTimeout(() => abortController.abort(), generation.timeoutMs);
68
+ const thinkingLevel = generation.thinkingLevel;
68
69
 
69
70
  try {
70
71
  const requestContext = {
@@ -72,10 +73,9 @@ export async function generateAutoTitle(
72
73
  messages: [message],
73
74
  };
74
75
  writeAutoTitleDebugRequest(model, trigger, requestContext);
75
- const response = await completeSimple(model, requestContext, {
76
- ...(auth.apiKey && { apiKey: auth.apiKey }),
77
- ...(auth.headers && { headers: auth.headers }),
76
+ const response = await modelRuntime.completeSimple(model, requestContext, {
78
77
  maxTokens: AUTO_TITLE_MAX_TOKENS,
78
+ ...(thinkingLevel && thinkingLevel !== "off" ? { reasoning: thinkingLevel } : {}),
79
79
  signal: abortController.signal,
80
80
  });
81
81
  writeAutoTitleDebugResponse(model, trigger, response);
@@ -3,37 +3,30 @@ import type {
3
3
  ExtensionAPI,
4
4
  ExtensionCommandContext,
5
5
  ExtensionContext,
6
- SessionStartEvent,
6
+ ModelRuntime,
7
7
  TurnEndEvent,
8
8
  } from "@earendil-works/pi-coding-agent";
9
+ import type { SessionLifecycle } from "../shared/composition.ts";
10
+ import type { ModelRuntimeProvider } from "../shared/model-runtime.ts";
11
+ import { isTuiMode } from "../shared/pi-mode.ts";
12
+ import type { SessionSettings } from "../shared/settings.ts";
9
13
  import {
10
14
  createSessionAutoTitleCommandHandler,
11
15
  getRetitleArgumentCompletions,
12
16
  type RetitleCommandInvocation,
13
17
  type RetitleCommandOutcome,
14
- } from "./session-auto-title/command.ts";
15
- import {
16
- createSessionAutoTitleController,
17
- type SessionAutoTitleController,
18
- } from "./session-auto-title/controller.ts";
19
- import { resolveAutoTitleModel } from "./session-auto-title/model.ts";
18
+ } from "./command.ts";
19
+ import { createSessionAutoTitleController, type SessionAutoTitleController } from "./controller.ts";
20
+ import type { AutoTitleGeneration } from "./generate.ts";
21
+ import { type AutoTitleModelResolution, resolveAutoTitleModel } from "./model.ts";
20
22
  import {
21
23
  buildRetitleScopeScan,
22
24
  notifyBulkRetitleResult,
23
25
  persistAutoTitleState,
24
26
  runBulkRetitle,
25
27
  runRetitlePlan,
26
- } from "./session-auto-title/retitle.ts";
27
- import { showRetitleWizard } from "./session-auto-title/wizard.ts";
28
- import { isTuiMode } from "./shared/pi-mode.ts";
29
- import { loadSettings } from "./shared/settings.ts";
30
-
31
- export {
32
- createSessionAutoTitleCommandHandler,
33
- getRetitleArgumentCompletions,
34
- parseRetitleCommand,
35
- TITLE_USAGE,
36
- } from "./session-auto-title/command.ts";
28
+ } from "./retitle.ts";
29
+ import { showRetitleWizard } from "./wizard.ts";
37
30
 
38
31
  interface TitleRunState {
39
32
  controller: SessionAutoTitleController;
@@ -42,12 +35,26 @@ interface TitleRunState {
42
35
  clearInFlight: () => void;
43
36
  }
44
37
 
45
- export default function sessionAutoTitleExtension(pi: ExtensionAPI): void {
46
- const settings = loadSettings();
38
+ export function installAutoTitle(
39
+ pi: ExtensionAPI,
40
+ deps: {
41
+ settings: SessionSettings;
42
+ getModelRuntime: ModelRuntimeProvider;
43
+ getSessionEpoch: () => number;
44
+ },
45
+ ): SessionLifecycle {
46
+ const { settings, getModelRuntime, getSessionEpoch } = deps;
47
47
  const controller = createSessionAutoTitleController(settings.autoTitle);
48
- let sessionEpoch = 0;
49
48
  let titleWorkInFlight: Promise<void> | undefined;
50
- let resolvedModel: Model<Api> | undefined;
49
+
50
+ // An explicit thinkingLevel setting overrides a thinking suffix on the configured model.
51
+ const buildGeneration = (
52
+ resolution: AutoTitleModelResolution | undefined,
53
+ ): AutoTitleGeneration => ({
54
+ systemPrompt: settings.autoTitle.prompt,
55
+ timeoutMs: settings.autoTitle.timeoutMs,
56
+ thinkingLevel: settings.autoTitle.thinkingLevel ?? resolution?.thinkingLevel,
57
+ });
51
58
 
52
59
  pi.registerCommand("title", {
53
60
  description: "Generate titles for this session, this folder, or all of Pi",
@@ -58,12 +65,13 @@ export default function sessionAutoTitleExtension(pi: ExtensionAPI): void {
58
65
  await titleWorkInFlight;
59
66
  }
60
67
 
61
- const model = resolvedModel ?? resolveAutoTitleModel(ctx, settings.autoTitle.model)?.model;
68
+ const modelRuntime = await getModelRuntime(ctx.modelRegistry);
69
+ const resolution = resolveAutoTitleModel(modelRuntime, ctx.model, settings.autoTitle.model);
62
70
  return handleTitleInvocation(
63
71
  pi,
64
72
  {
65
73
  controller,
66
- getSessionEpoch: () => sessionEpoch,
74
+ getSessionEpoch,
67
75
  setInFlight: (work) => {
68
76
  titleWorkInFlight = work;
69
77
  },
@@ -72,21 +80,15 @@ export default function sessionAutoTitleExtension(pi: ExtensionAPI): void {
72
80
  },
73
81
  },
74
82
  ctx,
75
- model,
83
+ modelRuntime,
84
+ resolution?.model,
76
85
  invocation,
77
- settings.autoTitle.prompt,
78
- settings.autoTitle.timeoutMs,
86
+ buildGeneration(resolution),
79
87
  );
80
88
  },
81
89
  ),
82
90
  });
83
91
 
84
- pi.on("session_start", async (_event: SessionStartEvent, ctx: ExtensionContext) => {
85
- sessionEpoch += 1;
86
- resolvedModel = resolveAutoTitleModel(ctx, settings.autoTitle.model)?.model;
87
- persistAutoTitleState(pi, controller.handleSessionStart(ctx));
88
- });
89
-
90
92
  pi.on("turn_end", async (_event: TurnEndEvent, ctx: ExtensionContext) => {
91
93
  const result = controller.handleTurnEnd(ctx);
92
94
  persistAutoTitleState(pi, result.persistedState);
@@ -95,17 +97,19 @@ export default function sessionAutoTitleExtension(pi: ExtensionAPI): void {
95
97
  return;
96
98
  }
97
99
 
100
+ const modelRuntime = await getModelRuntime(ctx.modelRegistry);
101
+ const resolution = resolveAutoTitleModel(modelRuntime, ctx.model, settings.autoTitle.model);
98
102
  titleWorkInFlight = runRetitlePlan({
99
103
  pi,
100
104
  controller,
101
105
  ctx,
102
- model: resolvedModel,
106
+ modelRuntime,
107
+ model: resolution?.model,
103
108
  isManual: false,
104
109
  existingPlan: result.plan,
105
- getSessionEpoch: () => sessionEpoch,
110
+ getSessionEpoch,
106
111
  notifyOnSuccess: false,
107
- systemPrompt: settings.autoTitle.prompt,
108
- timeoutMs: settings.autoTitle.timeoutMs,
112
+ generation: buildGeneration(resolution),
109
113
  })
110
114
  .then((outcome) => {
111
115
  if (outcome.ok) {
@@ -126,31 +130,34 @@ export default function sessionAutoTitleExtension(pi: ExtensionAPI): void {
126
130
  });
127
131
  });
128
132
 
129
- pi.on("session_shutdown", async () => {
130
- sessionEpoch += 1;
131
- controller.handleSessionShutdown();
132
- titleWorkInFlight = undefined;
133
- resolvedModel = undefined;
134
- });
133
+ return {
134
+ onSessionStart(_event, ctx) {
135
+ persistAutoTitleState(pi, controller.handleSessionStart(ctx));
136
+ },
137
+ onSessionShutdown() {
138
+ controller.handleSessionShutdown();
139
+ titleWorkInFlight = undefined;
140
+ },
141
+ };
135
142
  }
136
143
 
137
144
  async function handleTitleInvocation(
138
145
  pi: ExtensionAPI,
139
146
  state: TitleRunState,
140
147
  ctx: ExtensionCommandContext,
148
+ modelRuntime: ModelRuntime,
141
149
  model: Model<Api> | undefined,
142
150
  invocation: RetitleCommandInvocation,
143
- systemPrompt: string,
144
- timeoutMs: number,
151
+ generation: AutoTitleGeneration,
145
152
  ): Promise<RetitleCommandOutcome> {
146
153
  const retitleOpts = {
147
154
  pi,
148
155
  controller: state.controller,
149
156
  ctx,
157
+ modelRuntime,
150
158
  model,
151
159
  isManual: true,
152
- systemPrompt,
153
- timeoutMs,
160
+ generation,
154
161
  getSessionEpoch: state.getSessionEpoch,
155
162
  };
156
163
 
@@ -173,10 +180,10 @@ async function handleTitleInvocation(
173
180
  pi,
174
181
  state.controller,
175
182
  ctx,
183
+ modelRuntime,
176
184
  model,
177
185
  state.getSessionEpoch,
178
- systemPrompt,
179
- timeoutMs,
186
+ generation,
180
187
  );
181
188
  }
182
189
 
@@ -189,10 +196,10 @@ async function handleTitleInvocation(
189
196
  pi,
190
197
  state.controller,
191
198
  ctx,
199
+ modelRuntime,
192
200
  model,
193
201
  state.getSessionEpoch,
194
- systemPrompt,
195
- timeoutMs,
202
+ generation,
196
203
  {
197
204
  initialInvocation: {
198
205
  scope: invocation.scope,
@@ -209,12 +216,12 @@ async function handleTitleInvocation(
209
216
  pi,
210
217
  state.controller,
211
218
  ctx,
219
+ modelRuntime,
212
220
  model,
213
221
  scan,
214
222
  mode,
215
223
  state.getSessionEpoch,
216
- systemPrompt,
217
- timeoutMs,
224
+ generation,
218
225
  );
219
226
  notifyBulkRetitleResult(ctx, scan, mode, result);
220
227
  return "success";
@@ -1,12 +1,15 @@
1
+ import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
1
2
  import type { Api, Model } from "@earendil-works/pi-ai";
2
- import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
3
- import { findModelByReference } from "../shared/model.ts";
4
- import { ModelReference } from "../shared/settings.ts";
5
-
6
- const DEFAULT_AUTO_TITLE_FALLBACK_MODELS: readonly ModelReference[] = [
7
- new ModelReference("google", "gemini-flash-lite-latest"),
8
- new ModelReference("anthropic", "claude-haiku-4-5"),
9
- new ModelReference("openai", "gpt-5.4-mini"),
3
+ import type { ModelRuntime } from "@earendil-works/pi-coding-agent";
4
+ import { type ExactModelReference, findModelByReference } from "../shared/model.ts";
5
+ import { resolveAuthenticatedModel } from "../shared/model-resolution.ts";
6
+ import { freshenModel } from "../shared/model-runtime.ts";
7
+
8
+ const DEFAULT_AUTO_TITLE_FALLBACK_MODELS: readonly ExactModelReference[] = [
9
+ { provider: "openai-codex", modelId: "gpt-5.6-luna" },
10
+ { provider: "openai", modelId: "gpt-5.6-luna" },
11
+ { provider: "anthropic", modelId: "claude-haiku-4-5" },
12
+ { provider: "google", modelId: "gemini-flash-lite-latest" },
10
13
  ] as const;
11
14
 
12
15
  export type AutoTitleModelSource = "configured" | "fallback" | "current";
@@ -14,24 +17,29 @@ export type AutoTitleModelSource = "configured" | "fallback" | "current";
14
17
  export interface AutoTitleModelResolution {
15
18
  model: Model<Api>;
16
19
  source: AutoTitleModelSource;
20
+ thinkingLevel?: ThinkingLevel | undefined;
17
21
  }
18
22
 
19
23
  export function resolveAutoTitleModel(
20
- ctx: ExtensionContext,
21
- configuredModel: ModelReference | undefined,
24
+ modelRuntime: ModelRuntime,
25
+ currentModel: Model<Api> | undefined,
26
+ configuredModel: string | undefined,
22
27
  ): AutoTitleModelResolution | undefined {
23
- const availableModels = ctx.modelRegistry.getAvailable();
24
-
25
28
  if (configuredModel) {
26
- const configuredMatch = findModelByReference(availableModels, configuredModel);
27
- if (configuredMatch) {
29
+ const configured = resolveAuthenticatedModel({
30
+ modelRuntime,
31
+ modelPattern: configuredModel,
32
+ });
33
+ if (configured.ok) {
28
34
  return {
29
- model: configuredMatch,
35
+ model: configured.model,
30
36
  source: "configured",
37
+ thinkingLevel: configured.thinkingLevel,
31
38
  };
32
39
  }
33
40
  }
34
41
 
42
+ const availableModels = modelRuntime.getAvailableSnapshot();
35
43
  for (const fallbackReference of DEFAULT_AUTO_TITLE_FALLBACK_MODELS) {
36
44
  const fallbackMatch = findModelByReference(availableModels, fallbackReference);
37
45
  if (fallbackMatch) {
@@ -42,12 +50,12 @@ export function resolveAutoTitleModel(
42
50
  }
43
51
  }
44
52
 
45
- if (!ctx.model) {
53
+ if (!currentModel) {
46
54
  return undefined;
47
55
  }
48
56
 
49
57
  return {
50
- model: ctx.model,
58
+ model: freshenModel(modelRuntime, currentModel),
51
59
  source: "current",
52
60
  };
53
61
  }
@@ -3,6 +3,7 @@ import type {
3
3
  ExtensionAPI,
4
4
  ExtensionCommandContext,
5
5
  ExtensionContext,
6
+ ModelRuntime,
6
7
  SessionInfo,
7
8
  } from "@earendil-works/pi-coding-agent";
8
9
  import { SessionManager } from "@earendil-works/pi-coding-agent";
@@ -10,6 +11,7 @@ import type { RetitleMode, RetitleScope } from "./command.ts";
10
11
  import { buildAutoTitleContext } from "./context.ts";
11
12
  import type { AutoTitleTriggerPlan, SessionAutoTitleController } from "./controller.ts";
12
13
  import {
14
+ type AutoTitleGeneration,
13
15
  type AutoTitleGenerationResult,
14
16
  createAutoTitleFailure,
15
17
  generateAutoTitle,
@@ -53,12 +55,12 @@ export async function runBulkRetitle(
53
55
  pi: ExtensionAPI,
54
56
  controller: SessionAutoTitleController,
55
57
  ctx: ExtensionCommandContext,
58
+ modelRuntime: ModelRuntime,
56
59
  model: Model<Api> | undefined,
57
60
  scan: RetitleScopeScan,
58
61
  mode: RetitleMode,
59
62
  getSessionEpoch: () => number,
60
- systemPrompt: string,
61
- timeoutMs: number,
63
+ generation: AutoTitleGeneration,
62
64
  ): Promise<BulkRetitleResult> {
63
65
  const result: BulkRetitleResult = {
64
66
  attempted: 0,
@@ -83,10 +85,10 @@ export async function runBulkRetitle(
83
85
  pi,
84
86
  controller,
85
87
  ctx,
88
+ modelRuntime,
86
89
  model,
87
90
  isManual: true,
88
- systemPrompt,
89
- timeoutMs,
91
+ generation,
90
92
  getSessionEpoch,
91
93
  notifyOnSuccess: false,
92
94
  });
@@ -94,7 +96,7 @@ export async function runBulkRetitle(
94
96
  continue;
95
97
  }
96
98
 
97
- const outcome = await retitleStoredSession(ctx, model, session.path, systemPrompt, timeoutMs);
99
+ const outcome = await retitleStoredSession(modelRuntime, model, session.path, generation);
98
100
  result[outcome] += 1;
99
101
  }
100
102
 
@@ -164,10 +166,10 @@ export interface RetitlePlanOptions {
164
166
  pi: ExtensionAPI;
165
167
  controller: SessionAutoTitleController;
166
168
  ctx: ExtensionContext;
169
+ modelRuntime: ModelRuntime;
167
170
  model: Model<Api> | undefined;
168
171
  isManual: boolean;
169
- systemPrompt: string;
170
- timeoutMs: number;
172
+ generation: AutoTitleGeneration;
171
173
  existingPlan?: AutoTitleTriggerPlan;
172
174
  getSessionEpoch?: () => number;
173
175
  notifyOnSuccess?: boolean;
@@ -180,10 +182,10 @@ export async function runRetitlePlan(
180
182
  pi,
181
183
  controller,
182
184
  ctx,
185
+ modelRuntime,
183
186
  model,
184
187
  isManual,
185
- systemPrompt,
186
- timeoutMs,
188
+ generation,
187
189
  existingPlan,
188
190
  getSessionEpoch,
189
191
  } = options;
@@ -218,12 +220,11 @@ export async function runRetitlePlan(
218
220
  },
219
221
  );
220
222
  const generatedTitle = await generateAutoTitle(
221
- ctx,
223
+ modelRuntime,
222
224
  model,
223
225
  titleContext,
224
226
  plan.reason,
225
- systemPrompt,
226
- timeoutMs,
227
+ generation,
227
228
  );
228
229
  if (!generatedTitle.ok) {
229
230
  return generatedTitle;
@@ -266,11 +267,10 @@ export function persistAutoTitleState(
266
267
  }
267
268
 
268
269
  async function retitleStoredSession(
269
- ctx: ExtensionContext,
270
+ modelRuntime: ModelRuntime,
270
271
  model: Model<Api>,
271
272
  sessionPath: string,
272
- systemPrompt: string,
273
- timeoutMs: number,
273
+ generation: AutoTitleGeneration,
274
274
  ): Promise<"retitled" | "unchanged" | "failed"> {
275
275
  const sessionManager = SessionManager.open(sessionPath);
276
276
  const currentTitle = sessionManager.getSessionName();
@@ -288,12 +288,11 @@ async function retitleStoredSession(
288
288
  currentTitle,
289
289
  };
290
290
  const generatedTitle = await generateAutoTitle(
291
- ctx,
291
+ modelRuntime,
292
292
  model,
293
293
  titleContext,
294
294
  plan.reason,
295
- systemPrompt,
296
- timeoutMs,
295
+ generation,
297
296
  );
298
297
  if (!generatedTitle.ok) {
299
298
  return "failed";
@@ -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(