@tt-a1i/openpi 0.1.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/README.md +643 -0
- package/SETUP.md +74 -0
- package/THIRD_PARTY_NOTICES.md +16 -0
- package/assets/openpi-package.png +0 -0
- package/assets/readme-hero-mobile.svg +72 -0
- package/assets/readme-hero.svg +118 -0
- package/assets/readme-runtime-mobile.svg +91 -0
- package/assets/readme-runtime.svg +111 -0
- package/extensions/ask-user/handoff.ts +205 -0
- package/extensions/ask-user/index.ts +1110 -0
- package/extensions/ask-user/limits.ts +89 -0
- package/extensions/ask-user/prompt.ts +76 -0
- package/extensions/background-terminals/index.ts +653 -0
- package/extensions/background-terminals/src/domain.ts +99 -0
- package/extensions/background-terminals/src/manager.ts +989 -0
- package/extensions/background-terminals/src/output.ts +84 -0
- package/extensions/background-terminals/src/prompt.ts +195 -0
- package/extensions/background-terminals/src/result-delivery.ts +43 -0
- package/extensions/background-terminals/src/runtime.ts +36 -0
- package/extensions/background-terminals/src/ui/output-view.ts +55 -0
- package/extensions/background-terminals/src/ui/ps.ts +642 -0
- package/extensions/background-terminals/src/ui/tool-result.ts +146 -0
- package/extensions/background-terminals/src/watch.ts +192 -0
- package/extensions/context-pivot/index.ts +222 -0
- package/extensions/copy-all/index.ts +65 -0
- package/extensions/cron/index.ts +173 -0
- package/extensions/cron/schedule.ts +127 -0
- package/extensions/file-mutation-display/index.ts +105 -0
- package/extensions/file-mutation-display/render.ts +107 -0
- package/extensions/file-search/index.ts +515 -0
- package/extensions/file-search/src/args.ts +129 -0
- package/extensions/file-search/src/binaries.ts +419 -0
- package/extensions/file-search/src/output.ts +142 -0
- package/extensions/file-search/src/process.ts +309 -0
- package/extensions/file-search/src/prompt.ts +53 -0
- package/extensions/git-info/index.ts +272 -0
- package/extensions/git-info/src/changed-files-view.ts +414 -0
- package/extensions/git-info/src/process.ts +107 -0
- package/extensions/git-info/src/refresh-coordinator.ts +13 -0
- package/extensions/git-info/src/runtime.ts +28 -0
- package/extensions/goal/controller.ts +794 -0
- package/extensions/goal/index.ts +521 -0
- package/extensions/goal/prompts.ts +122 -0
- package/extensions/goal/state.ts +763 -0
- package/extensions/goal/ui.ts +158 -0
- package/extensions/model-info/index.ts +234 -0
- package/extensions/plan-mode/bash-policy.ts +313 -0
- package/extensions/plan-mode/index.ts +539 -0
- package/extensions/post-edit/index.ts +129 -0
- package/extensions/sessions/LICENSE.upstream +21 -0
- package/extensions/sessions/git-stats.ts +226 -0
- package/extensions/sessions/index.ts +1092 -0
- package/extensions/sessions/sessions.ts +385 -0
- package/extensions/setup/index.ts +408 -0
- package/extensions/shared/activity-status.ts +65 -0
- package/extensions/shared/below-editor-navigation.ts +343 -0
- package/extensions/shared/child-session.ts +352 -0
- package/extensions/shared/context-utilization.ts +47 -0
- package/extensions/shared/dashboard-state.ts +102 -0
- package/extensions/shared/plan-mode-state.ts +65 -0
- package/extensions/shared/setup-config.ts +971 -0
- package/extensions/shared/subagent-roles.ts +22 -0
- package/extensions/shared/terminal-text.ts +38 -0
- package/extensions/shared/tool-call-timeout.ts +104 -0
- package/extensions/shared/worktree.ts +526 -0
- package/extensions/subagents/index.ts +1225 -0
- package/extensions/subagents/navigation.ts +121 -0
- package/extensions/subagents/src/agent-types.ts +543 -0
- package/extensions/subagents/src/backend.ts +63 -0
- package/extensions/subagents/src/backends/pi.ts +493 -0
- package/extensions/subagents/src/backends/stub.ts +296 -0
- package/extensions/subagents/src/by-the-way.ts +21 -0
- package/extensions/subagents/src/domain.ts +271 -0
- package/extensions/subagents/src/format.ts +48 -0
- package/extensions/subagents/src/manager.ts +769 -0
- package/extensions/subagents/src/prompt.ts +190 -0
- package/extensions/subagents/src/result-delivery.ts +20 -0
- package/extensions/subagents/src/runtime.ts +51 -0
- package/extensions/subagents/src/ui/takeover.ts +615 -0
- package/extensions/subagents/src/ui/transcript.ts +293 -0
- package/extensions/subagents/src/ui/wait-result.ts +89 -0
- package/extensions/suggestions/index.ts +172 -0
- package/extensions/suggestions/src/config.ts +12 -0
- package/extensions/suggestions/src/predictor.ts +147 -0
- package/extensions/suggestions/src/prompt.ts +20 -0
- package/extensions/suggestions/src/transcript.ts +233 -0
- package/extensions/suggestions/src/ui.ts +224 -0
- package/extensions/tasks/index.ts +512 -0
- package/extensions/tasks/tasks.ts +649 -0
- package/extensions/tasks/ui.ts +421 -0
- package/extensions/turn-time/index.ts +61 -0
- package/extensions/ui-customization/footer.ts +512 -0
- package/extensions/ui-customization/index.ts +217 -0
- package/extensions/workflows/acceptance.ts +298 -0
- package/extensions/workflows/artifacts.ts +225 -0
- package/extensions/workflows/controller.ts +210 -0
- package/extensions/workflows/dashboard.ts +1226 -0
- package/extensions/workflows/index.ts +1884 -0
- package/extensions/workflows/journal.ts +188 -0
- package/extensions/workflows/meta.ts +250 -0
- package/extensions/workflows/model.ts +423 -0
- package/extensions/workflows/navigation.ts +93 -0
- package/extensions/workflows/prompt.ts +212 -0
- package/extensions/workflows/replay-safety.ts +577 -0
- package/extensions/workflows/runner.ts +786 -0
- package/extensions/workflows/sandbox-child.cjs +402 -0
- package/extensions/workflows/sandbox.ts +397 -0
- package/extensions/workflows/serialization.ts +162 -0
- package/extensions/workflows/worktree-handoff.ts +216 -0
- package/package.json +87 -0
- package/scripts/prepare-effect-tsgo.mjs +16 -0
- package/skills/background-terminals/SKILL.md +30 -0
- package/skills/subagents/SKILL.md +15 -0
- package/themes/github-dark-default.json +89 -0
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import { sanitizeTerminalText } from "../shared/terminal-text.ts";
|
|
2
|
+
|
|
3
|
+
export interface SessionInfoLike {
|
|
4
|
+
id: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
cwd: string;
|
|
7
|
+
modified: Date;
|
|
8
|
+
created?: Date;
|
|
9
|
+
firstMessage: string;
|
|
10
|
+
path: string;
|
|
11
|
+
messageCount?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type PreviewContent =
|
|
15
|
+
| string
|
|
16
|
+
| Array<
|
|
17
|
+
| { type: "text"; text: string }
|
|
18
|
+
| { type: "image"; mimeType?: string }
|
|
19
|
+
| { type: "toolCall"; name: string; arguments?: Record<string, unknown> }
|
|
20
|
+
| { type: "thinking"; thinking?: string; redacted?: boolean }
|
|
21
|
+
>;
|
|
22
|
+
|
|
23
|
+
export interface PreviewMessageLike {
|
|
24
|
+
role: string;
|
|
25
|
+
content?: PreviewContent;
|
|
26
|
+
toolName?: string;
|
|
27
|
+
isError?: boolean;
|
|
28
|
+
command?: string;
|
|
29
|
+
output?: string;
|
|
30
|
+
summary?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type PreviewBlock =
|
|
34
|
+
| { kind: "notice"; text: string }
|
|
35
|
+
| { kind: "user"; text: string }
|
|
36
|
+
| { kind: "assistant"; text: string }
|
|
37
|
+
| { kind: "thinking"; text: string; redacted?: boolean }
|
|
38
|
+
| { kind: "toolCall"; name: string; args?: string }
|
|
39
|
+
| { kind: "toolResult"; name?: string; text: string; isError?: boolean }
|
|
40
|
+
| { kind: "bash"; command: string; output?: string; isError?: boolean }
|
|
41
|
+
| { kind: "summary"; label: string; text: string }
|
|
42
|
+
| { kind: "custom"; label: string; text: string };
|
|
43
|
+
|
|
44
|
+
export interface SessionPreview {
|
|
45
|
+
title: string;
|
|
46
|
+
subtitle: string;
|
|
47
|
+
blocks: PreviewBlock[];
|
|
48
|
+
error?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface SessionPaneLayout {
|
|
52
|
+
mode: "single" | "split";
|
|
53
|
+
listWidth: number;
|
|
54
|
+
previewWidth: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function parseLimit(args: string | undefined, defaultLimit = 5): number {
|
|
58
|
+
if (!args) return defaultLimit;
|
|
59
|
+
const parsed = Number.parseInt(args.trim(), 10);
|
|
60
|
+
if (!Number.isFinite(parsed) || parsed < 1) return defaultLimit;
|
|
61
|
+
return parsed;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const pad = (value: number): string => value.toString().padStart(2, "0");
|
|
65
|
+
|
|
66
|
+
export function formatTimestamp(date: Date): string {
|
|
67
|
+
const year = date.getFullYear();
|
|
68
|
+
const month = pad(date.getMonth() + 1);
|
|
69
|
+
const day = pad(date.getDate());
|
|
70
|
+
const hours = pad(date.getHours());
|
|
71
|
+
const minutes = pad(date.getMinutes());
|
|
72
|
+
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function formatRelativeTime(date: Date): string {
|
|
76
|
+
const now = new Date();
|
|
77
|
+
const diffMs = now.getTime() - date.getTime();
|
|
78
|
+
const diffSec = Math.floor(diffMs / 1000);
|
|
79
|
+
const diffMin = Math.floor(diffSec / 60);
|
|
80
|
+
const diffHour = Math.floor(diffMin / 60);
|
|
81
|
+
const diffDay = Math.floor(diffHour / 24);
|
|
82
|
+
|
|
83
|
+
if (diffSec < 60) return "Just now";
|
|
84
|
+
if (diffMin < 60) return `${diffMin}m ago`;
|
|
85
|
+
if (diffHour < 24) return `${diffHour}h ago`;
|
|
86
|
+
if (diffDay === 1) return "Yesterday";
|
|
87
|
+
return `${diffDay}d ago`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const cleanDisplayLine = (text: string) =>
|
|
91
|
+
sanitizeTerminalText(text).replace(/\s+/gu, " ").trim();
|
|
92
|
+
|
|
93
|
+
export function buildSessionLabel(session: SessionInfoLike): string {
|
|
94
|
+
const trimmedName = cleanDisplayLine(session.name ?? "");
|
|
95
|
+
if (trimmedName) return trimmedName;
|
|
96
|
+
const id = cleanDisplayLine(session.id);
|
|
97
|
+
return id.length > 8 ? id.slice(0, 8) : id;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const normalizeSnippet = (text: string, maxLength: number): string => {
|
|
101
|
+
const cleaned = cleanDisplayLine(text);
|
|
102
|
+
const fallback = cleaned.length > 0 ? cleaned : "No messages";
|
|
103
|
+
if (maxLength < 1) return "";
|
|
104
|
+
if (fallback.length <= maxLength) return fallback;
|
|
105
|
+
if (maxLength === 1) return "…";
|
|
106
|
+
return `${fallback.slice(0, maxLength - 1)}…`;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export function buildSessionDescription(
|
|
110
|
+
session: SessionInfoLike,
|
|
111
|
+
snippetMax = 60,
|
|
112
|
+
): string {
|
|
113
|
+
const snippet = normalizeSnippet(session.firstMessage ?? "", snippetMax);
|
|
114
|
+
return `${formatTimestamp(session.modified)} • ${snippet} — ${cleanDisplayLine(session.cwd)}`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface SessionSearchEntry {
|
|
118
|
+
session: SessionInfoLike;
|
|
119
|
+
searchText: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export const buildSearchText = (session: SessionInfoLike): string =>
|
|
123
|
+
[
|
|
124
|
+
session.name?.trim() ?? "",
|
|
125
|
+
session.id,
|
|
126
|
+
session.cwd,
|
|
127
|
+
session.firstMessage ?? "",
|
|
128
|
+
]
|
|
129
|
+
.join(" ")
|
|
130
|
+
.toLowerCase();
|
|
131
|
+
|
|
132
|
+
export function buildSessionSearchEntries(
|
|
133
|
+
sessions: SessionInfoLike[],
|
|
134
|
+
): SessionSearchEntry[] {
|
|
135
|
+
return sessions.map((session) => ({
|
|
136
|
+
session,
|
|
137
|
+
searchText: buildSearchText(session),
|
|
138
|
+
}));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function filterSessionEntries(
|
|
142
|
+
entries: SessionSearchEntry[],
|
|
143
|
+
filter: string,
|
|
144
|
+
): SessionSearchEntry[] {
|
|
145
|
+
const normalized = filter.trim().toLowerCase();
|
|
146
|
+
if (!normalized) return entries;
|
|
147
|
+
|
|
148
|
+
const tokens = normalized.split(/\s+/).filter(Boolean);
|
|
149
|
+
if (tokens.length === 0) return entries;
|
|
150
|
+
|
|
151
|
+
return entries.filter((entry) =>
|
|
152
|
+
tokens.every((token) => entry.searchText.includes(token)),
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function filterSessionInfos(
|
|
157
|
+
sessions: SessionInfoLike[],
|
|
158
|
+
filter: string,
|
|
159
|
+
): SessionInfoLike[] {
|
|
160
|
+
return filterSessionEntries(buildSessionSearchEntries(sessions), filter).map(
|
|
161
|
+
(entry) => entry.session,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function getSessionPaneLayout(width: number): SessionPaneLayout {
|
|
166
|
+
if (width < 80) {
|
|
167
|
+
return { mode: "single", listWidth: width, previewWidth: 0 };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const dividerWidth = 3;
|
|
171
|
+
const available = Math.max(0, width - dividerWidth);
|
|
172
|
+
let listRatio = 0.36;
|
|
173
|
+
if (width < 110) {
|
|
174
|
+
listRatio = 0.42;
|
|
175
|
+
} else if (width >= 180) {
|
|
176
|
+
listRatio = 0.32;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const listWidth = Math.max(
|
|
180
|
+
34,
|
|
181
|
+
Math.min(58, Math.floor(available * listRatio)),
|
|
182
|
+
);
|
|
183
|
+
return {
|
|
184
|
+
mode: "split",
|
|
185
|
+
listWidth,
|
|
186
|
+
previewWidth: Math.max(0, available - listWidth),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const cleanPreviewText = (text: string) =>
|
|
191
|
+
sanitizeTerminalText(text).replace(/\s+$/g, "");
|
|
192
|
+
|
|
193
|
+
function sanitizePreviewValue(value: unknown): unknown {
|
|
194
|
+
if (typeof value === "string") return sanitizeTerminalText(value);
|
|
195
|
+
if (Array.isArray(value)) return value.map(sanitizePreviewValue);
|
|
196
|
+
if (typeof value !== "object" || value === null) return value;
|
|
197
|
+
return Object.fromEntries(
|
|
198
|
+
Object.entries(value).map(([key, nested]) => [
|
|
199
|
+
sanitizeTerminalText(key),
|
|
200
|
+
sanitizePreviewValue(nested),
|
|
201
|
+
]),
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function textBlocksFromContent(
|
|
206
|
+
content: PreviewContent | undefined,
|
|
207
|
+
): PreviewBlock[] {
|
|
208
|
+
if (!content) return [];
|
|
209
|
+
if (typeof content === "string") {
|
|
210
|
+
const text = cleanPreviewText(content);
|
|
211
|
+
return text.trim() ? [{ kind: "assistant", text }] : [];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const blocks: PreviewBlock[] = [];
|
|
215
|
+
for (const part of content) {
|
|
216
|
+
if (part.type === "text") {
|
|
217
|
+
const text = cleanPreviewText(part.text);
|
|
218
|
+
if (text.trim()) blocks.push({ kind: "assistant", text });
|
|
219
|
+
} else if (part.type === "image") {
|
|
220
|
+
const mimeType = cleanDisplayLine(part.mimeType ?? "");
|
|
221
|
+
blocks.push({
|
|
222
|
+
kind: "notice",
|
|
223
|
+
text: `[image${mimeType ? `: ${mimeType}` : ""}]`,
|
|
224
|
+
});
|
|
225
|
+
} else if (part.type === "toolCall") {
|
|
226
|
+
const args =
|
|
227
|
+
part.arguments && Object.keys(part.arguments).length > 0
|
|
228
|
+
? JSON.stringify(sanitizePreviewValue(part.arguments))
|
|
229
|
+
: undefined;
|
|
230
|
+
blocks.push({
|
|
231
|
+
kind: "toolCall",
|
|
232
|
+
name: cleanDisplayLine(part.name),
|
|
233
|
+
args,
|
|
234
|
+
});
|
|
235
|
+
} else if (part.type === "thinking") {
|
|
236
|
+
blocks.push({
|
|
237
|
+
kind: "thinking",
|
|
238
|
+
text: part.redacted
|
|
239
|
+
? "[thinking redacted]"
|
|
240
|
+
: cleanPreviewText(part.thinking ?? "[thinking]"),
|
|
241
|
+
redacted: part.redacted,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return blocks;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function contentToPlainText(content: PreviewContent | undefined): string {
|
|
249
|
+
if (!content) return "";
|
|
250
|
+
if (typeof content === "string") return content;
|
|
251
|
+
return content
|
|
252
|
+
.map((part) => {
|
|
253
|
+
if (part.type === "text") return part.text;
|
|
254
|
+
if (part.type === "image")
|
|
255
|
+
return `[image${part.mimeType ? `: ${part.mimeType}` : ""}]`;
|
|
256
|
+
if (part.type === "toolCall") return `[tool call: ${part.name}]`;
|
|
257
|
+
if (part.type === "thinking")
|
|
258
|
+
return part.redacted
|
|
259
|
+
? "[thinking redacted]"
|
|
260
|
+
: (part.thinking ?? "[thinking]");
|
|
261
|
+
return "";
|
|
262
|
+
})
|
|
263
|
+
.filter(Boolean)
|
|
264
|
+
.join("\n");
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function messageToBlocks(message: PreviewMessageLike): PreviewBlock[] {
|
|
268
|
+
if (message.role === "user") {
|
|
269
|
+
const text = cleanPreviewText(contentToPlainText(message.content));
|
|
270
|
+
return text ? [{ kind: "user", text }] : [];
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (message.role === "assistant") {
|
|
274
|
+
return textBlocksFromContent(message.content);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (message.role === "toolResult" || message.role === "tool") {
|
|
278
|
+
const text = cleanPreviewText(contentToPlainText(message.content));
|
|
279
|
+
return text
|
|
280
|
+
? [
|
|
281
|
+
{
|
|
282
|
+
kind: "toolResult",
|
|
283
|
+
name: message.toolName
|
|
284
|
+
? cleanDisplayLine(message.toolName)
|
|
285
|
+
: undefined,
|
|
286
|
+
text,
|
|
287
|
+
isError: message.isError,
|
|
288
|
+
},
|
|
289
|
+
]
|
|
290
|
+
: [];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (message.role === "bashExecution") {
|
|
294
|
+
const command = message.command ?? "";
|
|
295
|
+
return command || message.output
|
|
296
|
+
? [
|
|
297
|
+
{
|
|
298
|
+
kind: "bash",
|
|
299
|
+
command: cleanPreviewText(command),
|
|
300
|
+
output: cleanPreviewText(message.output ?? ""),
|
|
301
|
+
isError: message.isError,
|
|
302
|
+
},
|
|
303
|
+
]
|
|
304
|
+
: [];
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (
|
|
308
|
+
message.role === "compactionSummary" ||
|
|
309
|
+
message.role === "branchSummary"
|
|
310
|
+
) {
|
|
311
|
+
const text = cleanPreviewText(
|
|
312
|
+
message.summary ?? contentToPlainText(message.content),
|
|
313
|
+
);
|
|
314
|
+
return text
|
|
315
|
+
? [
|
|
316
|
+
{
|
|
317
|
+
kind: "summary",
|
|
318
|
+
label:
|
|
319
|
+
message.role === "branchSummary" ? "Branch summary" : "Summary",
|
|
320
|
+
text,
|
|
321
|
+
},
|
|
322
|
+
]
|
|
323
|
+
: [];
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const text = cleanPreviewText(
|
|
327
|
+
message.summary ?? contentToPlainText(message.content),
|
|
328
|
+
);
|
|
329
|
+
return text
|
|
330
|
+
? [
|
|
331
|
+
{
|
|
332
|
+
kind: "custom",
|
|
333
|
+
label: cleanDisplayLine(message.role) || "Message",
|
|
334
|
+
text,
|
|
335
|
+
},
|
|
336
|
+
]
|
|
337
|
+
: [];
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export function buildSessionPreview(
|
|
341
|
+
session: SessionInfoLike,
|
|
342
|
+
messages: PreviewMessageLike[],
|
|
343
|
+
options: { maxMessages?: number } = {},
|
|
344
|
+
): SessionPreview {
|
|
345
|
+
const maxMessages = options.maxMessages ?? 80;
|
|
346
|
+
const blocks: PreviewBlock[] = [];
|
|
347
|
+
const omitted = Math.max(0, messages.length - maxMessages);
|
|
348
|
+
const visibleMessages = omitted > 0 ? messages.slice(-maxMessages) : messages;
|
|
349
|
+
|
|
350
|
+
if (omitted > 0) {
|
|
351
|
+
blocks.push({
|
|
352
|
+
kind: "notice",
|
|
353
|
+
text: `… ${omitted} earlier messages omitted`,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
for (const message of visibleMessages) {
|
|
358
|
+
blocks.push(...messageToBlocks(message));
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const messageCount = session.messageCount ?? messages.length;
|
|
362
|
+
return {
|
|
363
|
+
title: buildSessionLabel(session),
|
|
364
|
+
subtitle: `${formatTimestamp(session.modified)} · ${messageCount} messages · ${cleanDisplayLine(session.cwd)}`,
|
|
365
|
+
blocks:
|
|
366
|
+
blocks.length > 0
|
|
367
|
+
? blocks
|
|
368
|
+
: [{ kind: "notice", text: "No previewable messages." }],
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export function buildPreviewError(
|
|
373
|
+
session: SessionInfoLike,
|
|
374
|
+
error: unknown,
|
|
375
|
+
): SessionPreview {
|
|
376
|
+
const message = cleanPreviewText(
|
|
377
|
+
error instanceof Error ? error.message : String(error),
|
|
378
|
+
);
|
|
379
|
+
return {
|
|
380
|
+
title: buildSessionLabel(session),
|
|
381
|
+
subtitle: `${formatTimestamp(session.modified)} · preview unavailable`,
|
|
382
|
+
blocks: [{ kind: "notice", text: `Failed to load preview: ${message}` }],
|
|
383
|
+
error: message,
|
|
384
|
+
};
|
|
385
|
+
}
|