tau-coding-agent 0.1.6 → 0.2.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 +15 -12
- package/extensions/answer.ts +129 -79
- package/extensions/branch-term/README.md +7 -0
- package/extensions/{branch-term.ts → branch-term/index.ts} +113 -104
- package/extensions/btw.ts +17 -2
- package/extensions/caffeinate/README.md +5 -0
- package/extensions/caffeinate/index.ts +144 -0
- package/extensions/fast.ts +292 -0
- package/extensions/ghostty.ts +214 -211
- package/extensions/git-diff-stats.ts +124 -84
- package/extensions/git-pr-status.ts +274 -208
- package/extensions/insights.ts +109 -191
- package/extensions/loop.ts +149 -145
- package/extensions/memory.ts +138 -86
- package/extensions/notify.ts +14 -26
- package/extensions/openai-verbosity.ts +108 -42
- package/extensions/review/fix.ts +15 -6
- package/extensions/review/git.ts +93 -103
- package/extensions/review/index.ts +60 -62
- package/extensions/review/interrupt.ts +117 -0
- package/extensions/review/message-queue.ts +45 -12
- package/extensions/review/models.ts +79 -3
- package/extensions/review/prompts.ts +41 -38
- package/extensions/review/review.ts +161 -166
- package/extensions/review/runner.ts +139 -147
- package/extensions/review/runtime.ts +173 -120
- package/extensions/review/triage.ts +41 -44
- package/extensions/sandbox/bash.ts +775 -0
- package/extensions/sandbox/command.ts +605 -0
- package/extensions/sandbox/config.ts +764 -0
- package/extensions/sandbox/index.ts +132 -3338
- package/extensions/sandbox/permissions/dialog.ts +118 -0
- package/extensions/sandbox/permissions/filesystem.ts +559 -0
- package/extensions/sandbox/permissions/mach-lookup.ts +186 -0
- package/extensions/sandbox/permissions/network.ts +164 -0
- package/extensions/sandbox/permissions/unsandboxed.ts +270 -0
- package/extensions/sandbox/runtime.ts +616 -0
- package/extensions/stash.ts +28 -15
- package/extensions/subagent/README.md +73 -0
- package/extensions/subagent/index.ts +822 -0
- package/extensions/subagent/interrupt.ts +117 -0
- package/extensions/subagent/permissions.ts +101 -0
- package/extensions/subagent/rpc.ts +177 -0
- package/extensions/tool-display-mode.ts +267 -64
- package/extensions/usage/index.ts +249 -356
- package/extensions/usage/openrouter.ts +10 -2
- package/extensions/websearch/README.md +12 -47
- package/extensions/websearch/config.ts +5 -2
- package/extensions/websearch/index.ts +94 -109
- package/extensions/websearch/output.ts +51 -0
- package/extensions/websearch/providers/anthropic.pi.ts +27 -44
- package/extensions/websearch/providers/gemini.browser.ts +68 -77
- package/extensions/websearch/providers/gemini.pi.ts +17 -17
- package/extensions/websearch/providers/openai-codex.pi.ts +145 -7
- package/extensions/websearch/providers/pi-model.shared.ts +33 -39
- package/extensions/websearch/providers/shared.ts +87 -2
- package/extensions/websearch/types.ts +0 -3
- package/extensions/worktree.ts +132 -172
- package/package.json +10 -5
- package/skills/browser-tools/SKILL.md +29 -234
- package/skills/browser-tools/references/cookies.md +36 -0
- package/skills/browser-tools/references/interaction.md +90 -0
- package/skills/browser-tools/references/logging.md +34 -0
- package/skills/git-clean-history/SKILL.md +6 -6
- package/skills/git-commit/SKILL.md +5 -3
- package/skills/github-pull-request/SKILL.md +60 -0
- package/skills/github-pull-request/references/create.md +40 -0
- package/skills/github-pull-request/references/stewardship.md +60 -0
- package/skills/oracle/SKILL.md +3 -3
- package/skills/oracle/scripts/oracle +10 -1
- package/skills/sentry/SKILL.md +15 -185
- package/skills/sentry/references/events.md +79 -0
- package/skills/sentry/references/issues.md +62 -0
- package/skills/sentry/references/logs.md +46 -0
- package/skills/update-changelog/SKILL.md +27 -121
- package/skills/web-design/SKILL.md +16 -105
- package/themes/tau-dark.json +4 -0
- package/extensions/openai-fast.ts +0 -229
- package/extensions/websearch/providers/openai-codex.browser.ts +0 -77
- package/extensions/websearch/providers/openai-codex.shared.ts +0 -123
package/extensions/insights.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { complete } from "@earendil-works/pi-ai/compat";
|
|
2
1
|
import type { Api, Model, UserMessage } from "@earendil-works/pi-ai";
|
|
3
2
|
import {
|
|
4
3
|
BorderedLoader,
|
|
@@ -7,12 +6,10 @@ import {
|
|
|
7
6
|
getAgentDir,
|
|
8
7
|
getLanguageFromPath,
|
|
9
8
|
getMarkdownTheme,
|
|
10
|
-
migrateSessionEntries,
|
|
11
9
|
parseSessionEntries,
|
|
12
10
|
type ExtensionAPI,
|
|
13
11
|
type ExtensionCommandContext,
|
|
14
12
|
type SessionEntry,
|
|
15
|
-
type SessionHeader,
|
|
16
13
|
type Theme,
|
|
17
14
|
} from "@earendil-works/pi-coding-agent";
|
|
18
15
|
import {
|
|
@@ -32,10 +29,10 @@ import path from "node:path";
|
|
|
32
29
|
|
|
33
30
|
// --- Constants ---
|
|
34
31
|
|
|
35
|
-
const INSIGHTS_META_SCHEMA_VERSION =
|
|
36
|
-
const SESSION_FINGERPRINT_VERSION =
|
|
32
|
+
const INSIGHTS_META_SCHEMA_VERSION = 2;
|
|
33
|
+
const SESSION_FINGERPRINT_VERSION = 2;
|
|
37
34
|
const INSIGHTS_FACET_SCHEMA_VERSION = 1;
|
|
38
|
-
const FACET_PROMPT_VERSION =
|
|
35
|
+
const FACET_PROMPT_VERSION = 2;
|
|
39
36
|
const SYNTHESIS_PROMPT_VERSION = 1;
|
|
40
37
|
|
|
41
38
|
const MIN_MEANINGFUL_USER_MESSAGES = 2;
|
|
@@ -46,6 +43,8 @@ const REDUCED_TRANSCRIPT_HEAD_CHARS = 12_000;
|
|
|
46
43
|
const REDUCED_TRANSCRIPT_TAIL_CHARS = 12_000;
|
|
47
44
|
const REDUCED_TRANSCRIPT_MAX_CHARS = FULL_TRANSCRIPT_MAX_CHARS;
|
|
48
45
|
|
|
46
|
+
const MIN_REPORT_WIDTH = 42; // Existing 40-column box plus its outer margin.
|
|
47
|
+
|
|
49
48
|
const AGENT_ROOT = getAgentDir();
|
|
50
49
|
const CACHE_ROOT = path.join(AGENT_ROOT, "insights");
|
|
51
50
|
const META_CACHE_DIR = path.join(CACHE_ROOT, "session-meta");
|
|
@@ -140,9 +139,7 @@ type InsightScope = "current" | "project" | "all";
|
|
|
140
139
|
|
|
141
140
|
type ConfiguredModelSelection = {
|
|
142
141
|
model: Model<Api>;
|
|
143
|
-
|
|
144
|
-
headers?: Record<string, string>;
|
|
145
|
-
env?: Record<string, string>;
|
|
142
|
+
modelRegistry: ExtensionCommandContext["modelRegistry"];
|
|
146
143
|
};
|
|
147
144
|
|
|
148
145
|
type ReadonlySessionManager = Pick<
|
|
@@ -311,6 +308,7 @@ class AbortError extends Error {
|
|
|
311
308
|
class InsightsReportComponent implements Component {
|
|
312
309
|
private readonly markdown: Markdown;
|
|
313
310
|
private scrollOffset = 0;
|
|
311
|
+
private renderWidth?: number;
|
|
314
312
|
private cachedWidth?: number;
|
|
315
313
|
private cachedLines?: string[];
|
|
316
314
|
private cachedBodyWidth?: number;
|
|
@@ -344,8 +342,11 @@ class InsightsReportComponent implements Component {
|
|
|
344
342
|
return;
|
|
345
343
|
}
|
|
346
344
|
|
|
345
|
+
const width = this.renderWidth ?? this.tui.terminal.columns;
|
|
346
|
+
if (width < MIN_REPORT_WIDTH) return;
|
|
347
|
+
|
|
347
348
|
const bodyHeight = this.getBodyHeight();
|
|
348
|
-
const boxWidth = this.getBoxWidth(
|
|
349
|
+
const boxWidth = this.getBoxWidth(width);
|
|
349
350
|
const bodyLines = this.getBodyLines(this.getContentWidth(boxWidth));
|
|
350
351
|
const maxScroll = Math.max(0, bodyLines.length - bodyHeight);
|
|
351
352
|
|
|
@@ -370,6 +371,19 @@ class InsightsReportComponent implements Component {
|
|
|
370
371
|
}
|
|
371
372
|
|
|
372
373
|
render(width: number): string[] {
|
|
374
|
+
this.renderWidth = width;
|
|
375
|
+
if (width < MIN_REPORT_WIDTH) {
|
|
376
|
+
if (width < 1) return [];
|
|
377
|
+
return [
|
|
378
|
+
truncateToWidth(
|
|
379
|
+
this.theme.fg("muted", `Resize to ${MIN_REPORT_WIDTH}+ columns to view insights.`),
|
|
380
|
+
width,
|
|
381
|
+
"",
|
|
382
|
+
),
|
|
383
|
+
truncateToWidth(this.theme.fg("dim", "Enter/Esc close"), width, ""),
|
|
384
|
+
];
|
|
385
|
+
}
|
|
386
|
+
|
|
373
387
|
if (this.cachedWidth === width && this.cachedLines) {
|
|
374
388
|
return this.cachedLines;
|
|
375
389
|
}
|
|
@@ -437,11 +451,11 @@ class InsightsReportComponent implements Component {
|
|
|
437
451
|
}
|
|
438
452
|
|
|
439
453
|
private getBoxWidth(width: number): number {
|
|
440
|
-
return Math.
|
|
454
|
+
return Math.min(width - 2, 140);
|
|
441
455
|
}
|
|
442
456
|
|
|
443
457
|
private getContentWidth(boxWidth: number): number {
|
|
444
|
-
return
|
|
458
|
+
return boxWidth - 4;
|
|
445
459
|
}
|
|
446
460
|
|
|
447
461
|
private borderLine(left: string, right: string, width: number): string {
|
|
@@ -453,7 +467,7 @@ class InsightsReportComponent implements Component {
|
|
|
453
467
|
}
|
|
454
468
|
|
|
455
469
|
private boxLine(content: string, width: number): string {
|
|
456
|
-
const padded = ` ${content}`;
|
|
470
|
+
const padded = ` ${truncateToWidth(content, this.getContentWidth(width))}`;
|
|
457
471
|
const visible = visibleWidth(padded);
|
|
458
472
|
const rightPad = Math.max(0, width - 2 - visible);
|
|
459
473
|
return `${this.theme.fg("borderMuted", "│")}${padded}${" ".repeat(rightPad)}${this.theme.fg("borderMuted", "│")}`;
|
|
@@ -779,12 +793,7 @@ async function getConfiguredModelSelection(
|
|
|
779
793
|
|
|
780
794
|
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(ctx.model);
|
|
781
795
|
if (!auth.ok) return null;
|
|
782
|
-
return {
|
|
783
|
-
model: ctx.model,
|
|
784
|
-
apiKey: auth.apiKey,
|
|
785
|
-
headers: auth.headers,
|
|
786
|
-
env: auth.env,
|
|
787
|
-
};
|
|
796
|
+
return { model: ctx.model, modelRegistry: ctx.modelRegistry };
|
|
788
797
|
}
|
|
789
798
|
|
|
790
799
|
async function listTargets(
|
|
@@ -804,32 +813,16 @@ async function listTargets(
|
|
|
804
813
|
onProgress({ stage: "resolve", current, total });
|
|
805
814
|
};
|
|
806
815
|
|
|
807
|
-
const
|
|
816
|
+
const cwd = path.resolve(ctx.cwd);
|
|
817
|
+
const sessions =
|
|
808
818
|
scope === "project"
|
|
809
|
-
? await
|
|
810
|
-
: await
|
|
819
|
+
? await SessionManager.list(cwd, ctx.sessionManager.getSessionDir(), progress, signal)
|
|
820
|
+
: await SessionManager.listAll(progress, signal);
|
|
811
821
|
|
|
812
822
|
throwIfAborted(signal);
|
|
813
|
-
return sessionFiles.map((sessionFile) => ({ path: sessionFile }));
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
async function listProjectSessionFiles(
|
|
817
|
-
cwd: string,
|
|
818
|
-
sessionDir: string,
|
|
819
|
-
onProgress: (current: number, total: number) => void,
|
|
820
|
-
): Promise<string[]> {
|
|
821
|
-
const currentCwd = path.resolve(cwd);
|
|
822
|
-
const sessions = await SessionManager.list(currentCwd, sessionDir, onProgress);
|
|
823
823
|
return sessions
|
|
824
|
-
.filter((session) => path.resolve(session.cwd ||
|
|
825
|
-
.map((session) => session.path);
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
async function listAllSessionFiles(
|
|
829
|
-
onProgress: (current: number, total: number) => void,
|
|
830
|
-
): Promise<string[]> {
|
|
831
|
-
const sessions = await SessionManager.listAll(onProgress);
|
|
832
|
-
return sessions.map((session) => session.path);
|
|
824
|
+
.filter((session) => scope === "all" || path.resolve(session.cwd || cwd) === cwd)
|
|
825
|
+
.map((session) => ({ path: session.path }));
|
|
833
826
|
}
|
|
834
827
|
|
|
835
828
|
async function loadOrExtractMeta(
|
|
@@ -841,7 +834,7 @@ async function loadOrExtractMeta(
|
|
|
841
834
|
cacheHit: boolean;
|
|
842
835
|
runtime?: SessionRuntime;
|
|
843
836
|
}> {
|
|
844
|
-
const cacheInfo = await getCacheInfo(target
|
|
837
|
+
const cacheInfo = await getCacheInfo(target);
|
|
845
838
|
|
|
846
839
|
if (cacheInfo.cacheKey && cacheInfo.fingerprint) {
|
|
847
840
|
const cached = await readMetaCache(cacheInfo.cacheKey, cacheInfo.fingerprint);
|
|
@@ -919,48 +912,12 @@ async function readOnlyOpenSession(sessionFile: string): Promise<ReadonlySession
|
|
|
919
912
|
throw new Error(`Session file is empty or invalid: ${sessionFile}`);
|
|
920
913
|
}
|
|
921
914
|
|
|
922
|
-
|
|
923
|
-
const header = fileEntries.find((entry): entry is SessionHeader => entry.type === "session");
|
|
915
|
+
const header = fileEntries.find((entry) => entry.type === "session");
|
|
924
916
|
if (!header) {
|
|
925
917
|
throw new Error(`Session file has no header: ${sessionFile}`);
|
|
926
918
|
}
|
|
927
919
|
|
|
928
|
-
|
|
929
|
-
const byId = new Map(entries.map((entry) => [entry.id, entry]));
|
|
930
|
-
const leafId = entries.at(-1)?.id ?? null;
|
|
931
|
-
const sessionDir = path.dirname(sessionFile);
|
|
932
|
-
|
|
933
|
-
return {
|
|
934
|
-
getEntries() {
|
|
935
|
-
return entries.slice();
|
|
936
|
-
},
|
|
937
|
-
getBranch(fromId?: string) {
|
|
938
|
-
const startId = fromId ?? leafId;
|
|
939
|
-
if (!startId) return [];
|
|
940
|
-
const pathEntries: SessionEntry[] = [];
|
|
941
|
-
let current = byId.get(startId);
|
|
942
|
-
while (current) {
|
|
943
|
-
pathEntries.unshift(current);
|
|
944
|
-
current = current.parentId ? byId.get(current.parentId) : undefined;
|
|
945
|
-
}
|
|
946
|
-
return pathEntries;
|
|
947
|
-
},
|
|
948
|
-
getLeafId() {
|
|
949
|
-
return leafId;
|
|
950
|
-
},
|
|
951
|
-
getHeader() {
|
|
952
|
-
return header;
|
|
953
|
-
},
|
|
954
|
-
getSessionDir() {
|
|
955
|
-
return sessionDir;
|
|
956
|
-
},
|
|
957
|
-
getSessionId() {
|
|
958
|
-
return header.id;
|
|
959
|
-
},
|
|
960
|
-
getSessionFile() {
|
|
961
|
-
return sessionFile;
|
|
962
|
-
},
|
|
963
|
-
} satisfies ReadonlySessionManager;
|
|
920
|
+
return SessionManager.inMemory(header.cwd, undefined, fileEntries);
|
|
964
921
|
}
|
|
965
922
|
|
|
966
923
|
function extractSessionArtifacts(
|
|
@@ -993,11 +950,23 @@ function extractSessionArtifacts(
|
|
|
993
950
|
for (const entry of branchEntries) {
|
|
994
951
|
collectModifiedFilesFromEntry(entry, modifiedFiles);
|
|
995
952
|
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
const
|
|
953
|
+
const message =
|
|
954
|
+
entry.type === "message" ? (entry.message as unknown as Record<string, unknown>) : undefined;
|
|
955
|
+
const usage =
|
|
956
|
+
entry.type === "usage"
|
|
957
|
+
? entry.usage
|
|
958
|
+
: message?.role === "assistant"
|
|
959
|
+
? message.usage
|
|
960
|
+
: undefined;
|
|
961
|
+
if (isRecord(usage)) {
|
|
962
|
+
totalInputTokens +=
|
|
963
|
+
numberValue(usage.input) + numberValue(usage.cacheRead) + numberValue(usage.cacheWrite);
|
|
964
|
+
totalOutputTokens += numberValue(usage.output);
|
|
965
|
+
totalCost += numberValue(isRecord(usage.cost) ? usage.cost.total : undefined);
|
|
966
|
+
}
|
|
967
|
+
if (!message) continue;
|
|
999
968
|
|
|
1000
|
-
if (role === "user") {
|
|
969
|
+
if (message.role === "user") {
|
|
1001
970
|
if (isMeaningfulUserMessage(message)) {
|
|
1002
971
|
meaningfulUserMessages++;
|
|
1003
972
|
if (!firstUserPrompt) {
|
|
@@ -1007,14 +976,8 @@ function extractSessionArtifacts(
|
|
|
1007
976
|
continue;
|
|
1008
977
|
}
|
|
1009
978
|
|
|
1010
|
-
if (role === "assistant") {
|
|
979
|
+
if (message.role === "assistant") {
|
|
1011
980
|
assistantMessages++;
|
|
1012
|
-
const usage = isRecord(message.usage) ? message.usage : undefined;
|
|
1013
|
-
totalInputTokens +=
|
|
1014
|
-
numberValue(usage?.input) + numberValue(usage?.cacheRead) + numberValue(usage?.cacheWrite);
|
|
1015
|
-
totalOutputTokens += numberValue(usage?.output);
|
|
1016
|
-
totalCost += numberValue(isRecord(usage?.cost) ? usage?.cost.total : undefined);
|
|
1017
|
-
|
|
1018
981
|
const toolCalls = extractToolCalls(message.content);
|
|
1019
982
|
for (const toolCall of toolCalls) {
|
|
1020
983
|
incrementRecord(toolCounts, toolCall.name);
|
|
@@ -1023,7 +986,7 @@ function extractSessionArtifacts(
|
|
|
1023
986
|
continue;
|
|
1024
987
|
}
|
|
1025
988
|
|
|
1026
|
-
if (role === "toolResult") {
|
|
989
|
+
if (message.role === "toolResult") {
|
|
1027
990
|
toolResultMessages++;
|
|
1028
991
|
if (message.isError === true && typeof message.toolName === "string") {
|
|
1029
992
|
incrementRecord(toolsWithErrors, message.toolName);
|
|
@@ -1235,76 +1198,51 @@ function prepareTranscriptForFacet(transcript: NormalizedTranscript): PreparedTr
|
|
|
1235
1198
|
};
|
|
1236
1199
|
}
|
|
1237
1200
|
|
|
1238
|
-
const
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1201
|
+
const prefix = [
|
|
1202
|
+
truncateChars(transcript.headerText, 1_000),
|
|
1203
|
+
`[Transcript reduced due to length. Full size: ${formatCount(transcript.charLength)} chars.]`,
|
|
1204
|
+
].join("\n\n");
|
|
1205
|
+
const gap = (count: number): string => `[... ${count} blocks omitted ...]`;
|
|
1206
|
+
// Charge each selected block for a possible preceding gap and both separators.
|
|
1207
|
+
// Reserve one more gap for the end, so assembly never needs a head-only final cut.
|
|
1208
|
+
const gapAllowance = gap(transcript.blocks.length).length + 4;
|
|
1209
|
+
const maxBodyLength = REDUCED_TRANSCRIPT_MAX_CHARS - prefix.length - 2 - gapAllowance;
|
|
1210
|
+
const selected = new Map<number, string>();
|
|
1211
|
+
const selectBlock = (index: number, budget: number): number => {
|
|
1212
|
+
if (selected.has(index)) return budget;
|
|
1213
|
+
const text = reduceTranscriptBlock(transcript.blocks[index]!.text, budget - gapAllowance);
|
|
1214
|
+
if (!text) return 0;
|
|
1215
|
+
selected.set(index, text);
|
|
1216
|
+
return budget - text.length - gapAllowance;
|
|
1217
|
+
};
|
|
1245
1218
|
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
selected.add(index);
|
|
1251
|
-
tailChars += transcript.blocks[index]!.text.length + 2;
|
|
1219
|
+
// Independent allocations ensure an oversized opening cannot consume the ending.
|
|
1220
|
+
let headBudget = REDUCED_TRANSCRIPT_HEAD_CHARS;
|
|
1221
|
+
for (let index = 0; index < transcript.blocks.length && headBudget > 0; index++) {
|
|
1222
|
+
headBudget = selectBlock(index, headBudget);
|
|
1252
1223
|
}
|
|
1253
|
-
|
|
1254
|
-
for (let index =
|
|
1224
|
+
let tailBudget = REDUCED_TRANSCRIPT_TAIL_CHARS;
|
|
1225
|
+
for (let index = transcript.blocks.length - 1; index >= 0 && tailBudget > 0; index--) {
|
|
1226
|
+
tailBudget = selectBlock(index, tailBudget);
|
|
1227
|
+
}
|
|
1228
|
+
let summaryBudget = maxBodyLength - REDUCED_TRANSCRIPT_HEAD_CHARS - REDUCED_TRANSCRIPT_TAIL_CHARS;
|
|
1229
|
+
for (let index = 0; index < transcript.blocks.length && summaryBudget > 0; index++) {
|
|
1255
1230
|
if (transcript.blocks[index]!.isSummary) {
|
|
1256
|
-
|
|
1231
|
+
summaryBudget = selectBlock(index, summaryBudget);
|
|
1257
1232
|
}
|
|
1258
1233
|
}
|
|
1259
1234
|
|
|
1260
|
-
const ordered = [...selected].sort((a, b) => a - b);
|
|
1261
|
-
const prefix = [
|
|
1262
|
-
transcript.headerText,
|
|
1263
|
-
`[Transcript reduced due to length. Full size: ${formatCount(transcript.charLength)} chars.]`,
|
|
1264
|
-
]
|
|
1265
|
-
.filter(Boolean)
|
|
1266
|
-
.join("\n\n")
|
|
1267
|
-
.trim();
|
|
1268
|
-
|
|
1269
1235
|
const reducedBlocks: string[] = [];
|
|
1270
1236
|
let previous = -1;
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
for (const index of ordered) {
|
|
1275
|
-
if (previous >= 0 && index > previous + 1) {
|
|
1276
|
-
const gapText = `[... ${index - previous - 1} blocks omitted ...]`;
|
|
1277
|
-
const appendedGap = appendReducedBlock(reducedBlocks, gapText, maxBodyLength, bodyLength);
|
|
1278
|
-
bodyLength = appendedGap.bodyLength;
|
|
1279
|
-
if (!appendedGap.didAppend) break;
|
|
1280
|
-
}
|
|
1281
|
-
|
|
1282
|
-
const appendedBlock = appendReducedBlock(
|
|
1283
|
-
reducedBlocks,
|
|
1284
|
-
transcript.blocks[index]!.text,
|
|
1285
|
-
maxBodyLength,
|
|
1286
|
-
bodyLength,
|
|
1287
|
-
);
|
|
1288
|
-
bodyLength = appendedBlock.bodyLength;
|
|
1289
|
-
if (!appendedBlock.didAppend) break;
|
|
1237
|
+
for (const [index, text] of [...selected].sort(([a], [b]) => a - b)) {
|
|
1238
|
+
if (index > previous + 1) reducedBlocks.push(gap(index - previous - 1));
|
|
1239
|
+
reducedBlocks.push(text);
|
|
1290
1240
|
previous = index;
|
|
1291
1241
|
}
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
const appendedTailGap = appendReducedBlock(
|
|
1295
|
-
reducedBlocks,
|
|
1296
|
-
`[... ${transcript.blocks.length - previous - 1} blocks omitted ...]`,
|
|
1297
|
-
maxBodyLength,
|
|
1298
|
-
bodyLength,
|
|
1299
|
-
);
|
|
1300
|
-
bodyLength = appendedTailGap.bodyLength;
|
|
1242
|
+
if (previous < transcript.blocks.length - 1) {
|
|
1243
|
+
reducedBlocks.push(gap(transcript.blocks.length - previous - 1));
|
|
1301
1244
|
}
|
|
1302
|
-
|
|
1303
|
-
const reducedText = [prefix, reducedBlocks.join("\n\n")]
|
|
1304
|
-
.filter(Boolean)
|
|
1305
|
-
.join("\n\n")
|
|
1306
|
-
.slice(0, REDUCED_TRANSCRIPT_MAX_CHARS)
|
|
1307
|
-
.trim();
|
|
1245
|
+
const reducedText = [prefix, ...reducedBlocks].join("\n\n");
|
|
1308
1246
|
|
|
1309
1247
|
return {
|
|
1310
1248
|
text: reducedText,
|
|
@@ -1313,32 +1251,17 @@ function prepareTranscriptForFacet(transcript: NormalizedTranscript): PreparedTr
|
|
|
1313
1251
|
};
|
|
1314
1252
|
}
|
|
1315
1253
|
|
|
1316
|
-
function
|
|
1317
|
-
|
|
1318
|
-
block: string,
|
|
1319
|
-
maxBodyLength: number,
|
|
1320
|
-
currentBodyLength: number,
|
|
1321
|
-
): { bodyLength: number; didAppend: boolean } {
|
|
1322
|
-
if (maxBodyLength <= currentBodyLength) {
|
|
1323
|
-
return { bodyLength: currentBodyLength, didAppend: false };
|
|
1324
|
-
}
|
|
1254
|
+
function reduceTranscriptBlock(text: string, maxChars: number): string | undefined {
|
|
1255
|
+
if (text.length <= maxChars) return text;
|
|
1325
1256
|
|
|
1326
|
-
const
|
|
1327
|
-
const
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
}
|
|
1257
|
+
const label = text.slice(0, text.indexOf("\n") + 1);
|
|
1258
|
+
const omission = "\n[... message text omitted ...]\n";
|
|
1259
|
+
const remaining = maxChars - label.length - omission.length;
|
|
1260
|
+
if (remaining < 2) return undefined;
|
|
1331
1261
|
|
|
1332
|
-
const
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
blocks.push(nextBlock);
|
|
1338
|
-
return {
|
|
1339
|
-
bodyLength: currentBodyLength + separatorLength + nextBlock.length,
|
|
1340
|
-
didAppend: true,
|
|
1341
|
-
};
|
|
1262
|
+
const head = Math.ceil(remaining / 2);
|
|
1263
|
+
const tail = Math.floor(remaining / 2);
|
|
1264
|
+
return `${label}${text.slice(label.length, label.length + head)}${omission}${text.slice(-tail)}`;
|
|
1342
1265
|
}
|
|
1343
1266
|
|
|
1344
1267
|
async function extractFacet(
|
|
@@ -1368,18 +1291,13 @@ async function extractFacet(
|
|
|
1368
1291
|
timestamp: Date.now(),
|
|
1369
1292
|
};
|
|
1370
1293
|
|
|
1371
|
-
const response = await complete(
|
|
1294
|
+
const response = await selection.modelRegistry.complete(
|
|
1372
1295
|
selection.model,
|
|
1373
1296
|
{
|
|
1374
1297
|
systemPrompt: FACET_SYSTEM_PROMPT,
|
|
1375
1298
|
messages: [userMessage],
|
|
1376
1299
|
},
|
|
1377
|
-
{
|
|
1378
|
-
apiKey: selection.apiKey,
|
|
1379
|
-
headers: selection.headers,
|
|
1380
|
-
env: selection.env,
|
|
1381
|
-
signal,
|
|
1382
|
-
},
|
|
1300
|
+
{ signal },
|
|
1383
1301
|
);
|
|
1384
1302
|
|
|
1385
1303
|
if (response.stopReason === "aborted") {
|
|
@@ -1541,18 +1459,13 @@ async function synthesizeReport(
|
|
|
1541
1459
|
timestamp: Date.now(),
|
|
1542
1460
|
};
|
|
1543
1461
|
|
|
1544
|
-
const response = await complete(
|
|
1462
|
+
const response = await selection.modelRegistry.complete(
|
|
1545
1463
|
selection.model,
|
|
1546
1464
|
{
|
|
1547
1465
|
systemPrompt: SYNTHESIS_SYSTEM_PROMPT,
|
|
1548
1466
|
messages: [userMessage],
|
|
1549
1467
|
},
|
|
1550
|
-
{
|
|
1551
|
-
apiKey: selection.apiKey,
|
|
1552
|
-
headers: selection.headers,
|
|
1553
|
-
env: selection.env,
|
|
1554
|
-
signal,
|
|
1555
|
-
},
|
|
1468
|
+
{ signal },
|
|
1556
1469
|
);
|
|
1557
1470
|
|
|
1558
1471
|
if (response.stopReason === "aborted") {
|
|
@@ -1728,15 +1641,20 @@ function setBorderedLoaderMessage(loader: BorderedLoader, message: string): void
|
|
|
1728
1641
|
}
|
|
1729
1642
|
}
|
|
1730
1643
|
|
|
1731
|
-
async function getCacheInfo(
|
|
1644
|
+
async function getCacheInfo(target: SessionTarget): Promise<SessionCacheInfo> {
|
|
1645
|
+
const sessionFile = target.path;
|
|
1732
1646
|
if (!sessionFile) return {};
|
|
1733
1647
|
|
|
1734
|
-
|
|
1648
|
+
// A live selection can differ from the last entry restored from the same file.
|
|
1649
|
+
// Keep each selected view separate, including the empty (null-leaf) branch.
|
|
1650
|
+
const view = target.manager ? { leafId: target.manager.getLeafId() } : "saved";
|
|
1651
|
+
const cacheKey = hashText(JSON.stringify({ sessionFile, view }));
|
|
1735
1652
|
try {
|
|
1736
1653
|
const stats = await fs.stat(sessionFile);
|
|
1737
1654
|
const fingerprint = hashText(
|
|
1738
1655
|
JSON.stringify({
|
|
1739
1656
|
sessionFile,
|
|
1657
|
+
view,
|
|
1740
1658
|
mtimeMs: stats.mtimeMs,
|
|
1741
1659
|
size: stats.size,
|
|
1742
1660
|
schemaVersion: SESSION_FINGERPRINT_VERSION,
|