@oh-my-pi/pi-coding-agent 16.1.10 → 16.1.11
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/CHANGELOG.md +31 -0
- package/dist/cli.js +2811 -2812
- package/dist/types/export/html/index.d.ts +31 -2
- package/dist/types/export/html/web-palette.d.ts +117 -0
- package/dist/types/hindsight/content.d.ts +7 -0
- package/dist/types/hindsight/transcript.d.ts +1 -1
- package/dist/types/modes/components/hook-editor.d.ts +2 -1
- package/dist/types/modes/interactive-mode.d.ts +5 -0
- package/dist/types/modes/types.d.ts +17 -0
- package/dist/types/modes/utils/keybinding-matchers.d.ts +13 -0
- package/dist/types/session/agent-session.d.ts +4 -0
- package/dist/types/session/session-context.d.ts +2 -0
- package/dist/types/session/session-entries.d.ts +6 -0
- package/dist/types/session/session-manager.d.ts +2 -1
- package/dist/types/tools/acp-bridge.d.ts +29 -0
- package/dist/types/tools/browser/cmux/cmux-tab.d.ts +7 -0
- package/dist/types/tools/browser/tab-worker.d.ts +20 -0
- package/dist/types/utils/image-loading.d.ts +4 -3
- package/dist/types/utils/jj.d.ts +25 -0
- package/dist/types/utils/title-generator.d.ts +0 -2
- package/package.json +12 -12
- package/scripts/generate-share-viewer.ts +4 -2
- package/src/autoresearch/git.ts +12 -0
- package/src/discovery/opencode.ts +47 -4
- package/src/edit/hashline/filesystem.ts +8 -0
- package/src/edit/modes/patch.ts +18 -2
- package/src/edit/modes/replace.ts +13 -10
- package/src/export/html/index.ts +50 -8
- package/src/export/html/web-palette.ts +142 -0
- package/src/hindsight/backend.ts +4 -4
- package/src/hindsight/content.ts +17 -1
- package/src/hindsight/transcript.ts +2 -2
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/modes/components/agent-dashboard.ts +8 -8
- package/src/modes/components/hook-editor.ts +13 -10
- package/src/modes/components/session-selector.ts +3 -0
- package/src/modes/controllers/event-controller.ts +9 -5
- package/src/modes/controllers/extension-ui-controller.ts +6 -2
- package/src/modes/interactive-mode.ts +69 -29
- package/src/modes/theme/dark.json +1 -1
- package/src/modes/types.ts +18 -0
- package/src/modes/utils/keybinding-matchers.ts +36 -1
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/prompts/tools/browser.md +3 -2
- package/src/sdk.ts +12 -1
- package/src/session/agent-session.ts +38 -14
- package/src/session/session-context.ts +5 -0
- package/src/session/session-entries.ts +6 -0
- package/src/session/session-manager.ts +3 -1
- package/src/task/worktree.ts +12 -4
- package/src/thinking.ts +1 -1
- package/src/tools/acp-bridge.ts +66 -0
- package/src/tools/browser/cmux/cmux-tab.ts +37 -0
- package/src/tools/browser/tab-worker.ts +160 -37
- package/src/tools/write.ts +2 -25
- package/src/utils/image-loading.ts +6 -3
- package/src/utils/jj.ts +47 -0
- package/src/utils/title-generator.ts +31 -99
package/src/edit/modes/patch.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
type WritethroughDeferredHandle,
|
|
18
18
|
} from "../../lsp";
|
|
19
19
|
import type { ToolSession } from "../../tools";
|
|
20
|
+
import { routeWriteThroughBridge } from "../../tools/acp-bridge";
|
|
20
21
|
import { assertEditableFile } from "../../tools/auto-generated-guard";
|
|
21
22
|
import {
|
|
22
23
|
invalidateFsScanAfterDelete,
|
|
@@ -1663,6 +1664,8 @@ class LspFileSystem implements FileSystem {
|
|
|
1663
1664
|
#fileCache: Record<string, Bun.BunFile> = {};
|
|
1664
1665
|
|
|
1665
1666
|
constructor(
|
|
1667
|
+
private readonly session: ToolSession,
|
|
1668
|
+
private readonly requestedPath: string,
|
|
1666
1669
|
private readonly writethrough: WritethroughCallback,
|
|
1667
1670
|
private readonly signal?: AbortSignal,
|
|
1668
1671
|
private readonly batchRequest?: LspBatchRequest,
|
|
@@ -1692,8 +1695,14 @@ class LspFileSystem implements FileSystem {
|
|
|
1692
1695
|
}
|
|
1693
1696
|
|
|
1694
1697
|
async write(path: string, content: string): Promise<void> {
|
|
1695
|
-
const file = this.#getFile(path);
|
|
1696
1698
|
const finalContent = await serializeEditFileText(path, path, content);
|
|
1699
|
+
|
|
1700
|
+
// Route through ACP bridge when available; skips internal artifacts and local:// paths.
|
|
1701
|
+
if (await routeWriteThroughBridge(this.session, this.requestedPath, path, finalContent)) {
|
|
1702
|
+
return;
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
const file = this.#getFile(path);
|
|
1697
1706
|
const deferredForPath = this.deferredForPath;
|
|
1698
1707
|
const result = await this.writethrough(
|
|
1699
1708
|
path,
|
|
@@ -1785,7 +1794,14 @@ export async function executePatchSingle(
|
|
|
1785
1794
|
}
|
|
1786
1795
|
|
|
1787
1796
|
const input: PatchInput = { path: resolvedPath, op, rename: resolvedRename, diff };
|
|
1788
|
-
const patchFileSystem = new LspFileSystem(
|
|
1797
|
+
const patchFileSystem = new LspFileSystem(
|
|
1798
|
+
session,
|
|
1799
|
+
path, // original user-provided path for bridge guard (may be local://, vault://, etc.)
|
|
1800
|
+
writethrough,
|
|
1801
|
+
signal,
|
|
1802
|
+
batchRequest,
|
|
1803
|
+
beginDeferredDiagnosticsForPath,
|
|
1804
|
+
);
|
|
1789
1805
|
const result = await applyPatch(input, {
|
|
1790
1806
|
cwd: session.cwd,
|
|
1791
1807
|
fs: patchFileSystem,
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
|
8
8
|
import { type } from "arktype";
|
|
9
|
-
import type { WritethroughCallback, WritethroughDeferredHandle } from "../../lsp";
|
|
9
|
+
import type { FileDiagnosticsResult, WritethroughCallback, WritethroughDeferredHandle } from "../../lsp";
|
|
10
10
|
import type { ToolSession } from "../../tools";
|
|
11
|
+
import { routeWriteThroughBridge } from "../../tools/acp-bridge";
|
|
11
12
|
import { invalidateFsScanAfterWrite } from "../../tools/fs-cache-invalidation";
|
|
12
13
|
import { outputMeta } from "../../tools/output-meta";
|
|
13
14
|
import { enforcePlanModeWrite, resolvePlanPath } from "../../tools/plan-mode-guard";
|
|
@@ -1098,15 +1099,17 @@ export async function executeReplaceSingle(
|
|
|
1098
1099
|
path,
|
|
1099
1100
|
bom + restoreLineEndings(result.content, originalEnding),
|
|
1100
1101
|
);
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1102
|
+
|
|
1103
|
+
// Route through ACP bridge when available; skips internal artifacts.
|
|
1104
|
+
let diagnostics: FileDiagnosticsResult | undefined;
|
|
1105
|
+
if (await routeWriteThroughBridge(session, path, absolutePath, finalContent)) {
|
|
1106
|
+
// bridge handled the write; diagnostics not available via writethrough
|
|
1107
|
+
} else {
|
|
1108
|
+
diagnostics = await writethrough(absolutePath, finalContent, signal, Bun.file(absolutePath), batchRequest, dst =>
|
|
1109
|
+
dst === absolutePath ? beginDeferredDiagnosticsForPath(absolutePath) : undefined,
|
|
1110
|
+
);
|
|
1111
|
+
invalidateFsScanAfterWrite(absolutePath);
|
|
1112
|
+
}
|
|
1110
1113
|
|
|
1111
1114
|
const diffResult = generateDiffString(normalizedContent, result.content, undefined, { path });
|
|
1112
1115
|
const resultText =
|
package/src/export/html/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ import templateJs from "./template.js" with { type: "text" };
|
|
|
12
12
|
// Pre-built React tool renderers: built by `bun --cwd=packages/collab-web run build:tool-views`,
|
|
13
13
|
// run automatically by root `prepare` on install and by `prepack` at publish.
|
|
14
14
|
import toolViewsJs from "./tool-views.generated.js" with { type: "text" };
|
|
15
|
+
import { webExportThemeVars } from "./web-palette";
|
|
15
16
|
|
|
16
17
|
let cachedTemplate: string | undefined;
|
|
17
18
|
|
|
@@ -36,6 +37,19 @@ export function getTemplate(): string {
|
|
|
36
37
|
|
|
37
38
|
export interface ExportOptions {
|
|
38
39
|
outputPath?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Which color palette the export ships with.
|
|
42
|
+
* - `"web"` (default) — the omp brand identity (collab-web pink/purple),
|
|
43
|
+
* so public HTML exports and the `/s/<id>` share viewer match the live
|
|
44
|
+
* `my.omp.sh` client. See `web-palette.ts`.
|
|
45
|
+
* - `"theme"` — derive from `themeName` (or the active TUI theme), preserving
|
|
46
|
+
* the pre-15.12 behavior where an export mirrored the user's terminal.
|
|
47
|
+
*/
|
|
48
|
+
palette?: "web" | "theme";
|
|
49
|
+
/**
|
|
50
|
+
* TUI theme to derive colors from when `palette: "theme"`. Ignored for the
|
|
51
|
+
* default `"web"` palette. Resolves to the active TUI theme when omitted.
|
|
52
|
+
*/
|
|
39
53
|
themeName?: string;
|
|
40
54
|
/** Embed subagent session transcripts found next to the session file (default true). */
|
|
41
55
|
includeSubSessions?: boolean;
|
|
@@ -101,12 +115,38 @@ function deriveExportColors(baseColor: string): { pageBg: string; cardBg: string
|
|
|
101
115
|
};
|
|
102
116
|
}
|
|
103
117
|
|
|
104
|
-
/**
|
|
105
|
-
|
|
118
|
+
/**
|
|
119
|
+
* Generate CSS custom properties for the export `:root`.
|
|
120
|
+
*
|
|
121
|
+
* Two call shapes:
|
|
122
|
+
* • `generateThemeVars("web" | "theme", themeName?)` — explicit palette.
|
|
123
|
+
* `"web"` (the default for public artifacts) returns the fixed omp brand
|
|
124
|
+
* palette from `web-palette.ts` — collab-web pink/purple identity, shared
|
|
125
|
+
* with the live `my.omp.sh` client, so exports and the share viewer render
|
|
126
|
+
* identically to it. `"theme"` derives from the TUI theme via
|
|
127
|
+
* `getResolvedThemeColors(themeName)` plus the three
|
|
128
|
+
* `export.{pageBg,cardBg,infoBg}` surface overrides.
|
|
129
|
+
* • `generateThemeVars(themeName)` — legacy single-arg form: derive from the
|
|
130
|
+
* named TUI theme. Kept so existing callers (and the theme-islight test)
|
|
131
|
+
* keep working; equivalent to `generateThemeVars("theme", themeName)`.
|
|
132
|
+
*
|
|
133
|
+
* Exported for the share-viewer build script.
|
|
134
|
+
*/
|
|
135
|
+
export async function generateThemeVars(
|
|
136
|
+
palette: "web" | "theme" | (string & {}) = "web",
|
|
137
|
+
themeName?: string,
|
|
138
|
+
): Promise<string> {
|
|
139
|
+
// Legacy single-arg form: `generateThemeVars("my-theme")` — the first arg
|
|
140
|
+
// is a theme name, not a palette. Route it to the themed path.
|
|
141
|
+
if (palette !== "web" && palette !== "theme") {
|
|
142
|
+
return generateThemeVars("theme", palette);
|
|
143
|
+
}
|
|
144
|
+
if (palette === "web") return webExportThemeVars();
|
|
145
|
+
|
|
106
146
|
const colors = await getResolvedThemeColors(themeName);
|
|
107
147
|
const lines: string[] = [];
|
|
108
|
-
for (const
|
|
109
|
-
lines.push(`--${key}: ${
|
|
148
|
+
for (const key in colors) {
|
|
149
|
+
lines.push(`--${key}: ${colors[key]};`);
|
|
110
150
|
}
|
|
111
151
|
|
|
112
152
|
const themeExport = await getThemeExportColors(themeName);
|
|
@@ -200,8 +240,8 @@ async function collectSubSessionsFromDir(
|
|
|
200
240
|
}
|
|
201
241
|
|
|
202
242
|
/** Generate HTML from bundled template with runtime substitutions. */
|
|
203
|
-
async function generateHtml(sessionData: SessionData, themeName?: string): Promise<string> {
|
|
204
|
-
const themeVars = await generateThemeVars(themeName);
|
|
243
|
+
async function generateHtml(sessionData: SessionData, palette: "web" | "theme", themeName?: string): Promise<string> {
|
|
244
|
+
const themeVars = await generateThemeVars(palette, themeName);
|
|
205
245
|
const sessionDataBase64 = Buffer.from(JSON.stringify(sessionData)).toBase64();
|
|
206
246
|
|
|
207
247
|
// Use function replacements so `$'`, `$&`, `$$`, `$n`, etc. in the
|
|
@@ -229,7 +269,8 @@ export async function exportSessionToHtml(
|
|
|
229
269
|
if (Object.keys(subSessions).length > 0) sessionData.subSessions = subSessions;
|
|
230
270
|
}
|
|
231
271
|
|
|
232
|
-
const
|
|
272
|
+
const palette = opts.palette ?? (opts.themeName ? "theme" : "web");
|
|
273
|
+
const html = await generateHtml(sessionData, palette, opts.themeName);
|
|
233
274
|
const outputPath = opts.outputPath || `${APP_NAME}-session-${path.basename(sessionFile, ".jsonl")}.html`;
|
|
234
275
|
|
|
235
276
|
await Bun.write(outputPath, html);
|
|
@@ -258,7 +299,8 @@ export async function exportFromFile(inputPath: string, options?: ExportOptions
|
|
|
258
299
|
if (Object.keys(subSessions).length > 0) sessionData.subSessions = subSessions;
|
|
259
300
|
}
|
|
260
301
|
|
|
261
|
-
const
|
|
302
|
+
const palette = opts.palette ?? (opts.themeName ? "theme" : "web");
|
|
303
|
+
const html = await generateHtml(sessionData, palette, opts.themeName);
|
|
262
304
|
const outputPath = opts.outputPath || `${APP_NAME}-session-${path.basename(inputPath, ".jsonl")}.html`;
|
|
263
305
|
|
|
264
306
|
await Bun.write(outputPath, html);
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web/export palette — the omp brand identity shared by the collab-web live
|
|
3
|
+
* client (`my.omp.sh/`) and every public HTML export / share viewer (`/s/<id>`).
|
|
4
|
+
*
|
|
5
|
+
* Why this exists separately from `modes/theme/dark.json`: the `dark` theme is
|
|
6
|
+
* the **default TUI theme** — its amber accent (`#febc38`) drives the terminal
|
|
7
|
+
* status line, syntax highlighting, thinking levels, and bash/python mode
|
|
8
|
+
* colors for every omp user. The public web artifacts want the collab-web
|
|
9
|
+
* pink/purple identity instead, so they pin this palette rather than inheriting
|
|
10
|
+
* the TUI's. Editing `dark.json` to repurpose it for the web would repaint
|
|
11
|
+
* every terminal; this file keeps the two surfaces decoupled.
|
|
12
|
+
*
|
|
13
|
+
* Token layout — emitted as CSS custom properties on `:root`:
|
|
14
|
+
* • Legacy export names consumed by `template.css` / `template.js`
|
|
15
|
+
* (`--text`, `--body-bg`, `--container-bg`, `--info-bg`, `--accent`,
|
|
16
|
+
* `--border`, `--success`, `--error`, `--warning`, `--muted`, `--dim`,
|
|
17
|
+
* `--borderAccent`, `--selectedBg`, `--userMessageBg`, `--customMessageBg`,
|
|
18
|
+
* `--customMessageLabel`, `--mdHeading`, `--mdLink`, `--mdCode`,
|
|
19
|
+
* `--mdListBullet`, `--toolOutput`, `--thinkingText`, syntax*, …).
|
|
20
|
+
* • collab-web-native aliases consumed by the `tv-` tool-render bridge
|
|
21
|
+
* (`tool-render.css`: `var(--bg-inset, …)`, `var(--fg, …)`, …) so embedded
|
|
22
|
+
* tool cards resolve to the *real* collab-web tokens and render
|
|
23
|
+
* pixel-identical to the live client.
|
|
24
|
+
*
|
|
25
|
+
* Alpha-bearing tokens (`--border`, `--ring`, `--accent-muted`, …) keep their
|
|
26
|
+
* `oklch(… / N%)` form — flattening them to opaque hex would produce harsh
|
|
27
|
+
* white borders and non-matching translucent focus rings. Opaque surfaces are
|
|
28
|
+
* sRGB hex (the collab-web `tokens.css` OKLCH dark-theme tokens converted via
|
|
29
|
+
* the standard OKLab→linear-sRGB→gamma path); if the live client palette
|
|
30
|
+
* changes, regenerate those from there.
|
|
31
|
+
*/
|
|
32
|
+
export const WEB_EXPORT_PALETTE = {
|
|
33
|
+
// --- collab-web-native aliases (tv- bridge) ---
|
|
34
|
+
"--bg": "#0f0b14",
|
|
35
|
+
"--bg-raised": "#16111c",
|
|
36
|
+
"--bg-inset": "#09060c",
|
|
37
|
+
"--bg-overlay": "#211b28",
|
|
38
|
+
"--fg": "#e6e3ea",
|
|
39
|
+
"--fg-muted": "#a49faa",
|
|
40
|
+
"--fg-faint": "#6e6974",
|
|
41
|
+
"--accent": "#ed4abf",
|
|
42
|
+
"--accent-muted": "oklch(0.674 0.23 341 / 18%)",
|
|
43
|
+
"--ok": "#68ca80",
|
|
44
|
+
"--err": "#f05653",
|
|
45
|
+
"--warn": "#e4b33f",
|
|
46
|
+
"--ring": "oklch(0.817 0.112 205 / 70%)",
|
|
47
|
+
"--font-mono": 'ui-monospace, "SF Mono", "JetBrains Mono", "Cascadia Mono", Menlo, Consolas, monospace',
|
|
48
|
+
|
|
49
|
+
// --- legacy export names (template.css / template.js) ---
|
|
50
|
+
// surfaces — map onto the collab-web purple ramp
|
|
51
|
+
"--body-bg": "#0f0b14", // = --bg
|
|
52
|
+
"--container-bg": "#16111c", // = --bg-raised
|
|
53
|
+
"--info-bg": "#09060c", // = --bg-inset (recessed wells: code blocks, tool output)
|
|
54
|
+
// text
|
|
55
|
+
"--text": "#e6e3ea", // = --fg
|
|
56
|
+
"--muted": "#a49faa", // = --fg-muted
|
|
57
|
+
"--dim": "#6e6974", // = --fg-faint
|
|
58
|
+
"--thinkingText": "#a49faa",
|
|
59
|
+
// hairlines — white-alpha, matching collab-web's --border/--border-strong
|
|
60
|
+
"--border": "oklch(1 0 0 / 9%)",
|
|
61
|
+
"--borderMuted": "oklch(1 0 0 / 6%)",
|
|
62
|
+
// accent + brand purple (the gradient's mid stop) for secondary highlights
|
|
63
|
+
"--borderAccent": "#945ff9",
|
|
64
|
+
"--selectedBg": "#2d2535",
|
|
65
|
+
// status — semantic, used sparingly (cancelled / exit-code / success dots)
|
|
66
|
+
"--success": "#68ca80", // = --ok
|
|
67
|
+
"--error": "#f05653", // = --err
|
|
68
|
+
"--warning": "#e4b33f", // = --warn
|
|
69
|
+
// message bubbles
|
|
70
|
+
"--userMessageBg": "oklch(0.674 0.23 341 / 6%)", // accent-tinted, like template.css user-message
|
|
71
|
+
"--userMessageText": "#e6e3ea",
|
|
72
|
+
"--customMessageBg": "#211b28", // = --bg-overlay
|
|
73
|
+
"--customMessageText": "#a49faa", // = --fg-muted
|
|
74
|
+
"--customMessageLabel": "#b281d6", // lilac, matching dark.json's label hue
|
|
75
|
+
// tool surfaces
|
|
76
|
+
"--toolPendingBg": "#16111c",
|
|
77
|
+
"--toolSuccessBg": "#09060c",
|
|
78
|
+
"--toolErrorBg": "oklch(0.66 0.19 25 / 14%)",
|
|
79
|
+
"--toolTitle": "#e6e3ea",
|
|
80
|
+
"--toolOutput": "#a49faa", // = --fg-muted
|
|
81
|
+
// markdown
|
|
82
|
+
"--mdHeading": "#ed4abf", // accent — headings carry the brand
|
|
83
|
+
"--mdLink": "#5ad8e5", // ring cyan — links distinct from the pink accent
|
|
84
|
+
"--mdLinkUrl": "#6e6974", // = --fg-faint
|
|
85
|
+
"--mdCode": "#e6e3ea",
|
|
86
|
+
"--mdCodeBlock": "#e6e3ea",
|
|
87
|
+
"--mdCodeBlockBorder": "oklch(1 0 0 / 9%)",
|
|
88
|
+
"--mdQuote": "#a49faa",
|
|
89
|
+
"--mdQuoteBorder": "oklch(1 0 0 / 13%)",
|
|
90
|
+
"--mdHr": "oklch(1 0 0 / 9%)",
|
|
91
|
+
"--mdListBullet": "#ed4abf", // accent — bullets carry the brand
|
|
92
|
+
// diff
|
|
93
|
+
"--toolDiffAdded": "#68ca80",
|
|
94
|
+
"--toolDiffRemoved": "#f05653",
|
|
95
|
+
"--toolDiffContext": "#6e6974",
|
|
96
|
+
// syntax — cool-neutral with pink/purple accents for keywords/types
|
|
97
|
+
"--syntaxComment": "#6e6974", // = --fg-faint
|
|
98
|
+
"--syntaxKeyword": "#945ff9", // brand purple
|
|
99
|
+
"--syntaxFunction": "#e4b33f", // warn amber (analog of dark.json's DCDCAA)
|
|
100
|
+
"--syntaxVariable": "#5ad8e5", // ring cyan
|
|
101
|
+
"--syntaxString": "#68ca80", // ok green
|
|
102
|
+
"--syntaxNumber": "#ed4abf", // accent
|
|
103
|
+
"--syntaxType": "#b281d6", // lilac
|
|
104
|
+
"--syntaxOperator": "#e6e3ea",
|
|
105
|
+
"--syntaxPunctuation": "#a49faa",
|
|
106
|
+
// thinking-level ramp — purple → lilac, matching the brand gradient
|
|
107
|
+
"--thinkingOff": "#6e6974",
|
|
108
|
+
"--thinkingMinimal": "#6e6974",
|
|
109
|
+
"--thinkingLow": "#945ff9",
|
|
110
|
+
"--thinkingMedium": "#b281d6",
|
|
111
|
+
"--thinkingHigh": "#ed4abf",
|
|
112
|
+
"--thinkingXhigh": "#e4b33f",
|
|
113
|
+
// mode tints (sidebar/role tags) — not surfaced in the export tree but
|
|
114
|
+
// emitted for completeness so template.js role classes resolve cleanly
|
|
115
|
+
"--bashMode": "#5ad8e5",
|
|
116
|
+
"--pythonMode": "#e4b33f",
|
|
117
|
+
// status-line tokens are TUI-only; not consumed by the export template, but
|
|
118
|
+
// emitted so any future surface that reads them inherits the brand.
|
|
119
|
+
"--statusLineBg": "#0f0b14",
|
|
120
|
+
"--statusLineSep": "#6e6974",
|
|
121
|
+
"--statusLineModel": "#ed4abf",
|
|
122
|
+
"--statusLinePath": "#5ad8e5",
|
|
123
|
+
"--statusLineGitClean": "#68ca80",
|
|
124
|
+
"--statusLineGitDirty": "#e4b33f",
|
|
125
|
+
"--statusLineContext": "#a49faa",
|
|
126
|
+
"--statusLineSpend": "#5ad8e5",
|
|
127
|
+
"--statusLineStaged": "#68ca80",
|
|
128
|
+
"--statusLineDirty": "#e4b33f",
|
|
129
|
+
"--statusLineUntracked": "#945ff9",
|
|
130
|
+
"--statusLineOutput": "#b281d6",
|
|
131
|
+
"--statusLineCost": "#b281d6",
|
|
132
|
+
"--statusLineSubagents": "#ed4abf",
|
|
133
|
+
} as const satisfies Record<string, string>;
|
|
134
|
+
|
|
135
|
+
/** Serialize the palette as `--key: value;` declarations for `:root { … }`. */
|
|
136
|
+
export function webExportThemeVars(): string {
|
|
137
|
+
let out = "";
|
|
138
|
+
for (const k in WEB_EXPORT_PALETTE) {
|
|
139
|
+
out += `${k}: ${WEB_EXPORT_PALETTE[k as keyof typeof WEB_EXPORT_PALETTE]}; `;
|
|
140
|
+
}
|
|
141
|
+
return out.trimEnd();
|
|
142
|
+
}
|
package/src/hindsight/backend.ts
CHANGED
|
@@ -15,7 +15,7 @@ import type { AgentSession } from "../session/agent-session";
|
|
|
15
15
|
import { type BankScope, computeBankScope } from "./bank";
|
|
16
16
|
import { createHindsightClient } from "./client";
|
|
17
17
|
import { isHindsightConfigured, loadHindsightConfig } from "./config";
|
|
18
|
-
import type
|
|
18
|
+
import { type HindsightMessage, hasSubstantiveContent } from "./content";
|
|
19
19
|
import { HindsightSessionState } from "./state";
|
|
20
20
|
|
|
21
21
|
const STATIC_INSTRUCTIONS = [
|
|
@@ -330,7 +330,7 @@ function flattenMessagesForRecall(messages: AgentMessage[]): HindsightMessage[]
|
|
|
330
330
|
if (msg.role === "user") {
|
|
331
331
|
const content = msg.content;
|
|
332
332
|
if (typeof content === "string") {
|
|
333
|
-
if (content
|
|
333
|
+
if (hasSubstantiveContent(content)) out.push({ role: "user", content });
|
|
334
334
|
continue;
|
|
335
335
|
}
|
|
336
336
|
if (Array.isArray(content)) {
|
|
@@ -338,7 +338,7 @@ function flattenMessagesForRecall(messages: AgentMessage[]): HindsightMessage[]
|
|
|
338
338
|
.filter((b): b is { type: "text"; text: string } => !!b && (b as { type?: unknown }).type === "text")
|
|
339
339
|
.map(b => b.text)
|
|
340
340
|
.join("\n");
|
|
341
|
-
if (text
|
|
341
|
+
if (hasSubstantiveContent(text)) out.push({ role: "user", content: text });
|
|
342
342
|
}
|
|
343
343
|
continue;
|
|
344
344
|
}
|
|
@@ -347,7 +347,7 @@ function flattenMessagesForRecall(messages: AgentMessage[]): HindsightMessage[]
|
|
|
347
347
|
.filter((b): b is { type: "text"; text: string } => b.type === "text")
|
|
348
348
|
.map(b => b.text)
|
|
349
349
|
.join("\n");
|
|
350
|
-
if (text
|
|
350
|
+
if (hasSubstantiveContent(text)) out.push({ role: "assistant", content: text });
|
|
351
351
|
}
|
|
352
352
|
}
|
|
353
353
|
return out;
|
package/src/hindsight/content.ts
CHANGED
|
@@ -43,6 +43,22 @@ export function stripMemoryTags(content: string): string {
|
|
|
43
43
|
.replace(LEGACY_RELEVANT_MEMORIES_REGEX, "");
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
// At least one letter or digit means the message carries a token a retriever
|
|
47
|
+
// can actually match on. Punctuation/whitespace-only strings (e.g. the lone
|
|
48
|
+
// `.` some providers emit for tool-call-only or thinking-only assistant turns)
|
|
49
|
+
// are dropped before retain/recall touches them — see issue #1806.
|
|
50
|
+
const SUBSTANTIVE_CHAR_RE = /[\p{L}\p{N}]/u;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* True when `content` carries at least one letter or digit. Used by retain
|
|
54
|
+
* and recall paths to drop placeholder assistant turns ("." / "..." / pure
|
|
55
|
+
* whitespace) that would otherwise pollute the bank and waste tokens on
|
|
56
|
+
* embeddings with no semantic content.
|
|
57
|
+
*/
|
|
58
|
+
export function hasSubstantiveContent(content: string): boolean {
|
|
59
|
+
return SUBSTANTIVE_CHAR_RE.test(content);
|
|
60
|
+
}
|
|
61
|
+
|
|
46
62
|
/** Format recall results into a bullet list for context injection. */
|
|
47
63
|
export function formatMemories(results: RecallResultLike[]): string {
|
|
48
64
|
if (results.length === 0) return "";
|
|
@@ -197,7 +213,7 @@ export function prepareRetentionTranscript(
|
|
|
197
213
|
const parts: string[] = [];
|
|
198
214
|
for (const msg of targetMessages) {
|
|
199
215
|
const content = stripMemoryTags(msg.content).trim();
|
|
200
|
-
if (!content) continue;
|
|
216
|
+
if (!hasSubstantiveContent(content)) continue;
|
|
201
217
|
parts.push(`[role: ${msg.role}]\n${content}\n[${msg.role}:end]`);
|
|
202
218
|
}
|
|
203
219
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import type { AssistantMessage } from "@oh-my-pi/pi-ai";
|
|
11
11
|
import type { SessionEntry } from "../session/session-entries";
|
|
12
|
-
import type
|
|
12
|
+
import { type HindsightMessage, hasSubstantiveContent } from "./content";
|
|
13
13
|
|
|
14
14
|
export interface ReadonlySessionManagerLike {
|
|
15
15
|
getEntries(): SessionEntry[];
|
|
@@ -39,7 +39,7 @@ export function extractMessages(sessionManager: ReadonlySessionManagerLike): Hin
|
|
|
39
39
|
if (role !== "user" && role !== "assistant") continue;
|
|
40
40
|
|
|
41
41
|
const text = role === "user" ? extractUserText(msg) : extractAssistantText(msg as AssistantMessage);
|
|
42
|
-
if (text
|
|
42
|
+
if (!hasSubstantiveContent(text)) continue;
|
|
43
43
|
messages.push({ role, content: text });
|
|
44
44
|
}
|
|
45
45
|
|