tau-coding-agent 0.1.5 → 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 -73
- package/extensions/branch-term/README.md +7 -0
- package/extensions/{branch-term.ts → branch-term/index.ts} +113 -104
- package/extensions/btw.ts +90 -4
- 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 +138 -183
- package/extensions/loop.ts +150 -135
- package/extensions/memory.ts +172 -97
- 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 +43 -40
- package/extensions/review/review.ts +161 -166
- package/extensions/review/runner.ts +139 -147
- package/extensions/review/runtime.ts +173 -120
- package/extensions/review/submit-review-tool.ts +1 -5
- package/extensions/review/submit-triage-tool.ts +1 -0
- 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 +138 -2850
- package/extensions/sandbox/macos-sandbox-shell.mjs +202 -0
- 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/anthropic.ts +5 -6
- package/extensions/usage/github-copilot.ts +30 -9
- package/extensions/usage/index.ts +250 -357
- package/extensions/usage/openai-codex.ts +2 -2
- 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 +44 -32
- 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 +4 -4
- package/skills/oracle/scripts/oracle +64 -51
- 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 {
|
|
@@ -27,14 +24,15 @@ import {
|
|
|
27
24
|
} from "@earendil-works/pi-tui";
|
|
28
25
|
import { createHash, randomBytes } from "node:crypto";
|
|
29
26
|
import fs from "node:fs/promises";
|
|
27
|
+
import os from "node:os";
|
|
30
28
|
import path from "node:path";
|
|
31
29
|
|
|
32
30
|
// --- Constants ---
|
|
33
31
|
|
|
34
|
-
const INSIGHTS_META_SCHEMA_VERSION =
|
|
35
|
-
const SESSION_FINGERPRINT_VERSION =
|
|
32
|
+
const INSIGHTS_META_SCHEMA_VERSION = 2;
|
|
33
|
+
const SESSION_FINGERPRINT_VERSION = 2;
|
|
36
34
|
const INSIGHTS_FACET_SCHEMA_VERSION = 1;
|
|
37
|
-
const FACET_PROMPT_VERSION =
|
|
35
|
+
const FACET_PROMPT_VERSION = 2;
|
|
38
36
|
const SYNTHESIS_PROMPT_VERSION = 1;
|
|
39
37
|
|
|
40
38
|
const MIN_MEANINGFUL_USER_MESSAGES = 2;
|
|
@@ -45,6 +43,8 @@ const REDUCED_TRANSCRIPT_HEAD_CHARS = 12_000;
|
|
|
45
43
|
const REDUCED_TRANSCRIPT_TAIL_CHARS = 12_000;
|
|
46
44
|
const REDUCED_TRANSCRIPT_MAX_CHARS = FULL_TRANSCRIPT_MAX_CHARS;
|
|
47
45
|
|
|
46
|
+
const MIN_REPORT_WIDTH = 42; // Existing 40-column box plus its outer margin.
|
|
47
|
+
|
|
48
48
|
const AGENT_ROOT = getAgentDir();
|
|
49
49
|
const CACHE_ROOT = path.join(AGENT_ROOT, "insights");
|
|
50
50
|
const META_CACHE_DIR = path.join(CACHE_ROOT, "session-meta");
|
|
@@ -139,8 +139,7 @@ type InsightScope = "current" | "project" | "all";
|
|
|
139
139
|
|
|
140
140
|
type ConfiguredModelSelection = {
|
|
141
141
|
model: Model<Api>;
|
|
142
|
-
|
|
143
|
-
headers?: Record<string, string>;
|
|
142
|
+
modelRegistry: ExtensionCommandContext["modelRegistry"];
|
|
144
143
|
};
|
|
145
144
|
|
|
146
145
|
type ReadonlySessionManager = Pick<
|
|
@@ -309,6 +308,7 @@ class AbortError extends Error {
|
|
|
309
308
|
class InsightsReportComponent implements Component {
|
|
310
309
|
private readonly markdown: Markdown;
|
|
311
310
|
private scrollOffset = 0;
|
|
311
|
+
private renderWidth?: number;
|
|
312
312
|
private cachedWidth?: number;
|
|
313
313
|
private cachedLines?: string[];
|
|
314
314
|
private cachedBodyWidth?: number;
|
|
@@ -342,8 +342,11 @@ class InsightsReportComponent implements Component {
|
|
|
342
342
|
return;
|
|
343
343
|
}
|
|
344
344
|
|
|
345
|
+
const width = this.renderWidth ?? this.tui.terminal.columns;
|
|
346
|
+
if (width < MIN_REPORT_WIDTH) return;
|
|
347
|
+
|
|
345
348
|
const bodyHeight = this.getBodyHeight();
|
|
346
|
-
const boxWidth = this.getBoxWidth(
|
|
349
|
+
const boxWidth = this.getBoxWidth(width);
|
|
347
350
|
const bodyLines = this.getBodyLines(this.getContentWidth(boxWidth));
|
|
348
351
|
const maxScroll = Math.max(0, bodyLines.length - bodyHeight);
|
|
349
352
|
|
|
@@ -368,6 +371,19 @@ class InsightsReportComponent implements Component {
|
|
|
368
371
|
}
|
|
369
372
|
|
|
370
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
|
+
|
|
371
387
|
if (this.cachedWidth === width && this.cachedLines) {
|
|
372
388
|
return this.cachedLines;
|
|
373
389
|
}
|
|
@@ -435,11 +451,11 @@ class InsightsReportComponent implements Component {
|
|
|
435
451
|
}
|
|
436
452
|
|
|
437
453
|
private getBoxWidth(width: number): number {
|
|
438
|
-
return Math.
|
|
454
|
+
return Math.min(width - 2, 140);
|
|
439
455
|
}
|
|
440
456
|
|
|
441
457
|
private getContentWidth(boxWidth: number): number {
|
|
442
|
-
return
|
|
458
|
+
return boxWidth - 4;
|
|
443
459
|
}
|
|
444
460
|
|
|
445
461
|
private borderLine(left: string, right: string, width: number): string {
|
|
@@ -451,7 +467,7 @@ class InsightsReportComponent implements Component {
|
|
|
451
467
|
}
|
|
452
468
|
|
|
453
469
|
private boxLine(content: string, width: number): string {
|
|
454
|
-
const padded = ` ${content}`;
|
|
470
|
+
const padded = ` ${truncateToWidth(content, this.getContentWidth(width))}`;
|
|
455
471
|
const visible = visibleWidth(padded);
|
|
456
472
|
const rightPad = Math.max(0, width - 2 - visible);
|
|
457
473
|
return `${this.theme.fg("borderMuted", "│")}${padded}${" ".repeat(rightPad)}${this.theme.fg("borderMuted", "│")}`;
|
|
@@ -562,9 +578,37 @@ async function runInsightsCommand(
|
|
|
562
578
|
return;
|
|
563
579
|
}
|
|
564
580
|
|
|
581
|
+
let reportPath: string | undefined;
|
|
582
|
+
try {
|
|
583
|
+
reportPath = await saveInsightsReport(result.reportMarkdown, result.generatedAt);
|
|
584
|
+
} catch (error) {
|
|
585
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
586
|
+
ctx.ui.notify(`Failed to save insights report: ${message}`, "warning");
|
|
587
|
+
}
|
|
588
|
+
|
|
565
589
|
await ctx.ui.custom<void>((tui, theme, _kb, done) => {
|
|
566
590
|
return new InsightsReportComponent(result, tui, theme, done);
|
|
567
591
|
});
|
|
592
|
+
|
|
593
|
+
if (reportPath) {
|
|
594
|
+
ctx.ui.notify(`Insights report saved to ${reportPath}`, "info");
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
async function saveInsightsReport(reportMarkdown: string, generatedAt: string): Promise<string> {
|
|
599
|
+
const timestamp = generatedAt.replace(/[:.]/g, "-");
|
|
600
|
+
const reportPath = path.join(os.tmpdir(), `tau-insights-${timestamp}.md`);
|
|
601
|
+
const reportFile = await fs.open(reportPath, "wx", 0o600);
|
|
602
|
+
|
|
603
|
+
try {
|
|
604
|
+
await reportFile.writeFile(`${reportMarkdown.trimEnd()}\n`, "utf8");
|
|
605
|
+
await reportFile.close();
|
|
606
|
+
return reportPath;
|
|
607
|
+
} catch (error) {
|
|
608
|
+
await reportFile.close().catch(() => undefined);
|
|
609
|
+
await fs.rm(reportPath, { force: true }).catch(() => undefined);
|
|
610
|
+
throw error;
|
|
611
|
+
}
|
|
568
612
|
}
|
|
569
613
|
|
|
570
614
|
async function runInsightsPipeline(
|
|
@@ -749,7 +793,7 @@ async function getConfiguredModelSelection(
|
|
|
749
793
|
|
|
750
794
|
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(ctx.model);
|
|
751
795
|
if (!auth.ok) return null;
|
|
752
|
-
return { model: ctx.model,
|
|
796
|
+
return { model: ctx.model, modelRegistry: ctx.modelRegistry };
|
|
753
797
|
}
|
|
754
798
|
|
|
755
799
|
async function listTargets(
|
|
@@ -769,32 +813,16 @@ async function listTargets(
|
|
|
769
813
|
onProgress({ stage: "resolve", current, total });
|
|
770
814
|
};
|
|
771
815
|
|
|
772
|
-
const
|
|
816
|
+
const cwd = path.resolve(ctx.cwd);
|
|
817
|
+
const sessions =
|
|
773
818
|
scope === "project"
|
|
774
|
-
? await
|
|
775
|
-
: await
|
|
819
|
+
? await SessionManager.list(cwd, ctx.sessionManager.getSessionDir(), progress, signal)
|
|
820
|
+
: await SessionManager.listAll(progress, signal);
|
|
776
821
|
|
|
777
822
|
throwIfAborted(signal);
|
|
778
|
-
return sessionFiles.map((sessionFile) => ({ path: sessionFile }));
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
async function listProjectSessionFiles(
|
|
782
|
-
cwd: string,
|
|
783
|
-
sessionDir: string,
|
|
784
|
-
onProgress: (current: number, total: number) => void,
|
|
785
|
-
): Promise<string[]> {
|
|
786
|
-
const currentCwd = path.resolve(cwd);
|
|
787
|
-
const sessions = await SessionManager.list(currentCwd, sessionDir, onProgress);
|
|
788
823
|
return sessions
|
|
789
|
-
.filter((session) => path.resolve(session.cwd ||
|
|
790
|
-
.map((session) => session.path);
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
async function listAllSessionFiles(
|
|
794
|
-
onProgress: (current: number, total: number) => void,
|
|
795
|
-
): Promise<string[]> {
|
|
796
|
-
const sessions = await SessionManager.listAll(onProgress);
|
|
797
|
-
return sessions.map((session) => session.path);
|
|
824
|
+
.filter((session) => scope === "all" || path.resolve(session.cwd || cwd) === cwd)
|
|
825
|
+
.map((session) => ({ path: session.path }));
|
|
798
826
|
}
|
|
799
827
|
|
|
800
828
|
async function loadOrExtractMeta(
|
|
@@ -806,7 +834,7 @@ async function loadOrExtractMeta(
|
|
|
806
834
|
cacheHit: boolean;
|
|
807
835
|
runtime?: SessionRuntime;
|
|
808
836
|
}> {
|
|
809
|
-
const cacheInfo = await getCacheInfo(target
|
|
837
|
+
const cacheInfo = await getCacheInfo(target);
|
|
810
838
|
|
|
811
839
|
if (cacheInfo.cacheKey && cacheInfo.fingerprint) {
|
|
812
840
|
const cached = await readMetaCache(cacheInfo.cacheKey, cacheInfo.fingerprint);
|
|
@@ -884,48 +912,12 @@ async function readOnlyOpenSession(sessionFile: string): Promise<ReadonlySession
|
|
|
884
912
|
throw new Error(`Session file is empty or invalid: ${sessionFile}`);
|
|
885
913
|
}
|
|
886
914
|
|
|
887
|
-
|
|
888
|
-
const header = fileEntries.find((entry): entry is SessionHeader => entry.type === "session");
|
|
915
|
+
const header = fileEntries.find((entry) => entry.type === "session");
|
|
889
916
|
if (!header) {
|
|
890
917
|
throw new Error(`Session file has no header: ${sessionFile}`);
|
|
891
918
|
}
|
|
892
919
|
|
|
893
|
-
|
|
894
|
-
const byId = new Map(entries.map((entry) => [entry.id, entry]));
|
|
895
|
-
const leafId = entries.at(-1)?.id ?? null;
|
|
896
|
-
const sessionDir = path.dirname(sessionFile);
|
|
897
|
-
|
|
898
|
-
return {
|
|
899
|
-
getEntries() {
|
|
900
|
-
return entries.slice();
|
|
901
|
-
},
|
|
902
|
-
getBranch(fromId?: string) {
|
|
903
|
-
const startId = fromId ?? leafId;
|
|
904
|
-
if (!startId) return [];
|
|
905
|
-
const pathEntries: SessionEntry[] = [];
|
|
906
|
-
let current = byId.get(startId);
|
|
907
|
-
while (current) {
|
|
908
|
-
pathEntries.unshift(current);
|
|
909
|
-
current = current.parentId ? byId.get(current.parentId) : undefined;
|
|
910
|
-
}
|
|
911
|
-
return pathEntries;
|
|
912
|
-
},
|
|
913
|
-
getLeafId() {
|
|
914
|
-
return leafId;
|
|
915
|
-
},
|
|
916
|
-
getHeader() {
|
|
917
|
-
return header;
|
|
918
|
-
},
|
|
919
|
-
getSessionDir() {
|
|
920
|
-
return sessionDir;
|
|
921
|
-
},
|
|
922
|
-
getSessionId() {
|
|
923
|
-
return header.id;
|
|
924
|
-
},
|
|
925
|
-
getSessionFile() {
|
|
926
|
-
return sessionFile;
|
|
927
|
-
},
|
|
928
|
-
} satisfies ReadonlySessionManager;
|
|
920
|
+
return SessionManager.inMemory(header.cwd, undefined, fileEntries);
|
|
929
921
|
}
|
|
930
922
|
|
|
931
923
|
function extractSessionArtifacts(
|
|
@@ -958,11 +950,23 @@ function extractSessionArtifacts(
|
|
|
958
950
|
for (const entry of branchEntries) {
|
|
959
951
|
collectModifiedFilesFromEntry(entry, modifiedFiles);
|
|
960
952
|
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
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;
|
|
964
968
|
|
|
965
|
-
if (role === "user") {
|
|
969
|
+
if (message.role === "user") {
|
|
966
970
|
if (isMeaningfulUserMessage(message)) {
|
|
967
971
|
meaningfulUserMessages++;
|
|
968
972
|
if (!firstUserPrompt) {
|
|
@@ -972,14 +976,8 @@ function extractSessionArtifacts(
|
|
|
972
976
|
continue;
|
|
973
977
|
}
|
|
974
978
|
|
|
975
|
-
if (role === "assistant") {
|
|
979
|
+
if (message.role === "assistant") {
|
|
976
980
|
assistantMessages++;
|
|
977
|
-
const usage = isRecord(message.usage) ? message.usage : undefined;
|
|
978
|
-
totalInputTokens +=
|
|
979
|
-
numberValue(usage?.input) + numberValue(usage?.cacheRead) + numberValue(usage?.cacheWrite);
|
|
980
|
-
totalOutputTokens += numberValue(usage?.output);
|
|
981
|
-
totalCost += numberValue(isRecord(usage?.cost) ? usage?.cost.total : undefined);
|
|
982
|
-
|
|
983
981
|
const toolCalls = extractToolCalls(message.content);
|
|
984
982
|
for (const toolCall of toolCalls) {
|
|
985
983
|
incrementRecord(toolCounts, toolCall.name);
|
|
@@ -988,7 +986,7 @@ function extractSessionArtifacts(
|
|
|
988
986
|
continue;
|
|
989
987
|
}
|
|
990
988
|
|
|
991
|
-
if (role === "toolResult") {
|
|
989
|
+
if (message.role === "toolResult") {
|
|
992
990
|
toolResultMessages++;
|
|
993
991
|
if (message.isError === true && typeof message.toolName === "string") {
|
|
994
992
|
incrementRecord(toolsWithErrors, message.toolName);
|
|
@@ -1200,76 +1198,51 @@ function prepareTranscriptForFacet(transcript: NormalizedTranscript): PreparedTr
|
|
|
1200
1198
|
};
|
|
1201
1199
|
}
|
|
1202
1200
|
|
|
1203
|
-
const
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
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
|
+
};
|
|
1210
1218
|
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
selected.add(index);
|
|
1216
|
-
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);
|
|
1217
1223
|
}
|
|
1218
|
-
|
|
1219
|
-
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++) {
|
|
1220
1230
|
if (transcript.blocks[index]!.isSummary) {
|
|
1221
|
-
|
|
1231
|
+
summaryBudget = selectBlock(index, summaryBudget);
|
|
1222
1232
|
}
|
|
1223
1233
|
}
|
|
1224
1234
|
|
|
1225
|
-
const ordered = [...selected].sort((a, b) => a - b);
|
|
1226
|
-
const prefix = [
|
|
1227
|
-
transcript.headerText,
|
|
1228
|
-
`[Transcript reduced due to length. Full size: ${formatCount(transcript.charLength)} chars.]`,
|
|
1229
|
-
]
|
|
1230
|
-
.filter(Boolean)
|
|
1231
|
-
.join("\n\n")
|
|
1232
|
-
.trim();
|
|
1233
|
-
|
|
1234
1235
|
const reducedBlocks: string[] = [];
|
|
1235
1236
|
let previous = -1;
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
for (const index of ordered) {
|
|
1240
|
-
if (previous >= 0 && index > previous + 1) {
|
|
1241
|
-
const gapText = `[... ${index - previous - 1} blocks omitted ...]`;
|
|
1242
|
-
const appendedGap = appendReducedBlock(reducedBlocks, gapText, maxBodyLength, bodyLength);
|
|
1243
|
-
bodyLength = appendedGap.bodyLength;
|
|
1244
|
-
if (!appendedGap.didAppend) break;
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
const appendedBlock = appendReducedBlock(
|
|
1248
|
-
reducedBlocks,
|
|
1249
|
-
transcript.blocks[index]!.text,
|
|
1250
|
-
maxBodyLength,
|
|
1251
|
-
bodyLength,
|
|
1252
|
-
);
|
|
1253
|
-
bodyLength = appendedBlock.bodyLength;
|
|
1254
|
-
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);
|
|
1255
1240
|
previous = index;
|
|
1256
1241
|
}
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
const appendedTailGap = appendReducedBlock(
|
|
1260
|
-
reducedBlocks,
|
|
1261
|
-
`[... ${transcript.blocks.length - previous - 1} blocks omitted ...]`,
|
|
1262
|
-
maxBodyLength,
|
|
1263
|
-
bodyLength,
|
|
1264
|
-
);
|
|
1265
|
-
bodyLength = appendedTailGap.bodyLength;
|
|
1242
|
+
if (previous < transcript.blocks.length - 1) {
|
|
1243
|
+
reducedBlocks.push(gap(transcript.blocks.length - previous - 1));
|
|
1266
1244
|
}
|
|
1267
|
-
|
|
1268
|
-
const reducedText = [prefix, reducedBlocks.join("\n\n")]
|
|
1269
|
-
.filter(Boolean)
|
|
1270
|
-
.join("\n\n")
|
|
1271
|
-
.slice(0, REDUCED_TRANSCRIPT_MAX_CHARS)
|
|
1272
|
-
.trim();
|
|
1245
|
+
const reducedText = [prefix, ...reducedBlocks].join("\n\n");
|
|
1273
1246
|
|
|
1274
1247
|
return {
|
|
1275
1248
|
text: reducedText,
|
|
@@ -1278,32 +1251,17 @@ function prepareTranscriptForFacet(transcript: NormalizedTranscript): PreparedTr
|
|
|
1278
1251
|
};
|
|
1279
1252
|
}
|
|
1280
1253
|
|
|
1281
|
-
function
|
|
1282
|
-
|
|
1283
|
-
block: string,
|
|
1284
|
-
maxBodyLength: number,
|
|
1285
|
-
currentBodyLength: number,
|
|
1286
|
-
): { bodyLength: number; didAppend: boolean } {
|
|
1287
|
-
if (maxBodyLength <= currentBodyLength) {
|
|
1288
|
-
return { bodyLength: currentBodyLength, didAppend: false };
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
|
-
const separatorLength = blocks.length > 0 ? 2 : 0;
|
|
1292
|
-
const remaining = maxBodyLength - currentBodyLength - separatorLength;
|
|
1293
|
-
if (remaining <= 0) {
|
|
1294
|
-
return { bodyLength: currentBodyLength, didAppend: false };
|
|
1295
|
-
}
|
|
1254
|
+
function reduceTranscriptBlock(text: string, maxChars: number): string | undefined {
|
|
1255
|
+
if (text.length <= maxChars) return text;
|
|
1296
1256
|
|
|
1297
|
-
const
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
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;
|
|
1301
1261
|
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
didAppend: true,
|
|
1306
|
-
};
|
|
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)}`;
|
|
1307
1265
|
}
|
|
1308
1266
|
|
|
1309
1267
|
async function extractFacet(
|
|
@@ -1333,17 +1291,13 @@ async function extractFacet(
|
|
|
1333
1291
|
timestamp: Date.now(),
|
|
1334
1292
|
};
|
|
1335
1293
|
|
|
1336
|
-
const response = await complete(
|
|
1294
|
+
const response = await selection.modelRegistry.complete(
|
|
1337
1295
|
selection.model,
|
|
1338
1296
|
{
|
|
1339
1297
|
systemPrompt: FACET_SYSTEM_PROMPT,
|
|
1340
1298
|
messages: [userMessage],
|
|
1341
1299
|
},
|
|
1342
|
-
{
|
|
1343
|
-
apiKey: selection.apiKey,
|
|
1344
|
-
headers: selection.headers,
|
|
1345
|
-
signal,
|
|
1346
|
-
},
|
|
1300
|
+
{ signal },
|
|
1347
1301
|
);
|
|
1348
1302
|
|
|
1349
1303
|
if (response.stopReason === "aborted") {
|
|
@@ -1505,17 +1459,13 @@ async function synthesizeReport(
|
|
|
1505
1459
|
timestamp: Date.now(),
|
|
1506
1460
|
};
|
|
1507
1461
|
|
|
1508
|
-
const response = await complete(
|
|
1462
|
+
const response = await selection.modelRegistry.complete(
|
|
1509
1463
|
selection.model,
|
|
1510
1464
|
{
|
|
1511
1465
|
systemPrompt: SYNTHESIS_SYSTEM_PROMPT,
|
|
1512
1466
|
messages: [userMessage],
|
|
1513
1467
|
},
|
|
1514
|
-
{
|
|
1515
|
-
apiKey: selection.apiKey,
|
|
1516
|
-
headers: selection.headers,
|
|
1517
|
-
signal,
|
|
1518
|
-
},
|
|
1468
|
+
{ signal },
|
|
1519
1469
|
);
|
|
1520
1470
|
|
|
1521
1471
|
if (response.stopReason === "aborted") {
|
|
@@ -1691,15 +1641,20 @@ function setBorderedLoaderMessage(loader: BorderedLoader, message: string): void
|
|
|
1691
1641
|
}
|
|
1692
1642
|
}
|
|
1693
1643
|
|
|
1694
|
-
async function getCacheInfo(
|
|
1644
|
+
async function getCacheInfo(target: SessionTarget): Promise<SessionCacheInfo> {
|
|
1645
|
+
const sessionFile = target.path;
|
|
1695
1646
|
if (!sessionFile) return {};
|
|
1696
1647
|
|
|
1697
|
-
|
|
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 }));
|
|
1698
1652
|
try {
|
|
1699
1653
|
const stats = await fs.stat(sessionFile);
|
|
1700
1654
|
const fingerprint = hashText(
|
|
1701
1655
|
JSON.stringify({
|
|
1702
1656
|
sessionFile,
|
|
1657
|
+
view,
|
|
1703
1658
|
mtimeMs: stats.mtimeMs,
|
|
1704
1659
|
size: stats.size,
|
|
1705
1660
|
schemaVersion: SESSION_FINGERPRINT_VERSION,
|