@aexol/spectral 0.7.6 → 0.7.8
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/dist/agent/index.js +16 -140
- package/dist/cli.js +25 -220
- package/dist/extensions/spectral-vision-fallback.js +188 -0
- package/dist/memory/commands/status.js +5 -5
- package/dist/memory/commands/view.js +16 -14
- package/dist/memory/compaction.js +31 -3
- package/dist/memory/prompts.js +5 -5
- package/dist/memory/tools/recall-observation.js +2 -2
- package/dist/pi/coding-agent/config.js +0 -11
- package/dist/pi/coding-agent/core/agent-session.js +3 -17
- package/dist/pi/coding-agent/core/extensions/loader.js +0 -6
- package/dist/pi/coding-agent/core/extensions/runner.js +7 -1
- package/dist/pi/coding-agent/core/keybindings.js +129 -2
- package/dist/pi/coding-agent/core/settings-manager.js +20 -0
- package/dist/pi/coding-agent/core/tools/bash.js +17 -63
- package/dist/pi/coding-agent/core/tools/edit.js +4 -141
- package/dist/pi/coding-agent/core/tools/find.js +0 -11
- package/dist/pi/coding-agent/core/tools/grep.js +0 -11
- package/dist/pi/coding-agent/core/tools/ls.js +0 -11
- package/dist/pi/coding-agent/core/tools/read.js +0 -12
- package/dist/pi/coding-agent/core/tools/render-utils.js +1 -14
- package/dist/pi/coding-agent/core/tools/write.js +2 -97
- package/dist/pi/coding-agent/modes/interactive/components/keybinding-hints.js +1 -1
- package/dist/pi/coding-agent/modes/interactive/components/visual-truncate.js +6 -12
- package/dist/pi/coding-agent/modes/interactive/theme/theme.js +1 -2
- package/dist/relay/models-fetch.js +13 -1
- package/dist/server/pi-bridge.js +57 -4
- package/dist/server/session-stream.js +7 -1
- package/package.json +1 -1
- package/dist/pi/coding-agent/core/export-html/ansi-to-html.js +0 -248
- package/dist/pi/coding-agent/core/export-html/index.js +0 -225
- package/dist/pi/coding-agent/core/export-html/tool-renderer.js +0 -107
- package/dist/pi/tui/autocomplete.js +0 -631
- package/dist/pi/tui/components/box.js +0 -103
- package/dist/pi/tui/components/cancellable-loader.js +0 -34
- package/dist/pi/tui/components/editor.js +0 -1915
- package/dist/pi/tui/components/image.js +0 -88
- package/dist/pi/tui/components/input.js +0 -425
- package/dist/pi/tui/components/loader.js +0 -68
- package/dist/pi/tui/components/markdown.js +0 -633
- package/dist/pi/tui/components/select-list.js +0 -158
- package/dist/pi/tui/components/settings-list.js +0 -184
- package/dist/pi/tui/components/spacer.js +0 -22
- package/dist/pi/tui/components/text.js +0 -88
- package/dist/pi/tui/components/truncated-text.js +0 -50
- package/dist/pi/tui/editor-component.js +0 -1
- package/dist/pi/tui/fuzzy.js +0 -109
- package/dist/pi/tui/index.js +0 -31
- package/dist/pi/tui/keybindings.js +0 -173
- package/dist/pi/tui/keys.js +0 -1172
- package/dist/pi/tui/kill-ring.js +0 -43
- package/dist/pi/tui/stdin-buffer.js +0 -360
- package/dist/pi/tui/terminal-image.js +0 -335
- package/dist/pi/tui/terminal.js +0 -324
- package/dist/pi/tui/tui.js +0 -1076
- package/dist/pi/tui/undo-stack.js +0 -24
- package/dist/pi/tui/utils.js +0 -1016
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spectral Vision Extension
|
|
3
|
+
*
|
|
4
|
+
* Automatically describes images using a vision-capable model and replaces
|
|
5
|
+
* raw image data with text descriptions. This saves context for the main agent
|
|
6
|
+
* and allows non-vision models to "see" images.
|
|
7
|
+
*
|
|
8
|
+
* Logic:
|
|
9
|
+
* - ALWAYS intercepts images (even when main model supports vision),
|
|
10
|
+
* replacing them with text descriptions to save main agent context.
|
|
11
|
+
* - Uses the main model for vision descriptions if it supports images.
|
|
12
|
+
* - Falls back to the admin-configured default vision model (isVisionDefault
|
|
13
|
+
* flag from backend via SettingsManager).
|
|
14
|
+
* - If no admin default is configured, falls back to the first available
|
|
15
|
+
* vision-capable model by provider priority.
|
|
16
|
+
*
|
|
17
|
+
* Hooks into the `context` event to intercept ALL images before they reach
|
|
18
|
+
* the LLM (covers user-attached images and tool-result images alike).
|
|
19
|
+
*/
|
|
20
|
+
import { streamSimple } from "../pi/ai/index.js";
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Helpers
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
/** Find the best available vision-capable model from the registry */
|
|
25
|
+
function findVisionModel(ctx) {
|
|
26
|
+
const allModels = ctx.modelRegistry.getAll();
|
|
27
|
+
const availableModels = allModels.filter((m) => ctx.modelRegistry.hasConfiguredAuth(m));
|
|
28
|
+
// Filter to models that support images
|
|
29
|
+
const visionModels = availableModels.filter((m) => m.input.includes("image"));
|
|
30
|
+
if (visionModels.length === 0)
|
|
31
|
+
return undefined;
|
|
32
|
+
// 1. Try admin-configured default vision model from settings
|
|
33
|
+
const settings = ctx.settingsManager;
|
|
34
|
+
if (settings) {
|
|
35
|
+
const defaultVisionProvider = settings.getDefaultVisionProvider();
|
|
36
|
+
const defaultVisionModel = settings.getDefaultVisionModel();
|
|
37
|
+
if (defaultVisionProvider && defaultVisionModel) {
|
|
38
|
+
const match = visionModels.find((m) => m.provider === defaultVisionProvider && m.id === defaultVisionModel);
|
|
39
|
+
if (match) {
|
|
40
|
+
process.stderr.write(`[spectral-vision] Using admin-configured default: ${match.provider}/${match.id}\n`);
|
|
41
|
+
return match;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// 2. Fall back to provider priority
|
|
46
|
+
const providerPriority = ["anthropic", "openai", "google", "openrouter"];
|
|
47
|
+
for (const provider of providerPriority) {
|
|
48
|
+
const match = visionModels.find((m) => m.provider === provider);
|
|
49
|
+
if (match)
|
|
50
|
+
return match;
|
|
51
|
+
}
|
|
52
|
+
return visionModels[0];
|
|
53
|
+
}
|
|
54
|
+
/** Check if any content block in an array is an image */
|
|
55
|
+
function hasImageContent(content) {
|
|
56
|
+
return Array.isArray(content) && content.some((c) => c?.type === "image");
|
|
57
|
+
}
|
|
58
|
+
/** Count images in a message */
|
|
59
|
+
function countImages(msg) {
|
|
60
|
+
if (!Array.isArray(msg.content))
|
|
61
|
+
return 0;
|
|
62
|
+
return msg.content.filter((c) => c.type === "image").length;
|
|
63
|
+
}
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// Core: call vision model to describe images
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
async function describeImages(visionModel, content, contextText, ctx) {
|
|
68
|
+
const userContent = [
|
|
69
|
+
{
|
|
70
|
+
type: "text",
|
|
71
|
+
text: `Please describe the following image(s). ` +
|
|
72
|
+
(contextText ? `Context: ${contextText}. ` : "") +
|
|
73
|
+
"Focus on what is visually visible — text content, UI elements, diagrams, " +
|
|
74
|
+
"code structure, layout, colors, etc. Be concise but thorough. " +
|
|
75
|
+
"If multiple images are provided, describe each one separately with a heading.",
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
const textPrefixBlocks = [];
|
|
79
|
+
let imageCount = 0;
|
|
80
|
+
for (const block of content) {
|
|
81
|
+
if (block.type === "image") {
|
|
82
|
+
userContent.push(block);
|
|
83
|
+
imageCount++;
|
|
84
|
+
}
|
|
85
|
+
else if (block.type === "text") {
|
|
86
|
+
textPrefixBlocks.push(block);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (imageCount === 0)
|
|
90
|
+
return content;
|
|
91
|
+
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(visionModel);
|
|
92
|
+
if (!auth.ok) {
|
|
93
|
+
process.stderr.write(`[spectral-vision] No API key for vision model ${visionModel.provider}/${visionModel.id}\n`);
|
|
94
|
+
return content;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
const result = await streamSimple(visionModel, {
|
|
98
|
+
systemPrompt: "You are an image description assistant. Describe images accurately and concisely.",
|
|
99
|
+
messages: [{ role: "user", content: userContent, timestamp: Date.now() }],
|
|
100
|
+
tools: [],
|
|
101
|
+
}, {
|
|
102
|
+
apiKey: auth.apiKey,
|
|
103
|
+
headers: auth.headers,
|
|
104
|
+
});
|
|
105
|
+
let description = "";
|
|
106
|
+
for await (const event of result) {
|
|
107
|
+
if (event.type === "text_delta") {
|
|
108
|
+
description += event.delta;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const textNote = description.trim()
|
|
112
|
+
? `[Image description from ${visionModel.provider}/${visionModel.id} (${imageCount} image(s)):\n${description}\n]`
|
|
113
|
+
: `[${imageCount} image(s) — vision model returned empty description]`;
|
|
114
|
+
return [
|
|
115
|
+
...textPrefixBlocks,
|
|
116
|
+
{ type: "text", text: textNote },
|
|
117
|
+
];
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
121
|
+
process.stderr.write(`[spectral-vision] Vision call failed: ${msg}\n`);
|
|
122
|
+
return [
|
|
123
|
+
...textPrefixBlocks,
|
|
124
|
+
{
|
|
125
|
+
type: "text",
|
|
126
|
+
text: `[${imageCount} image(s) omitted — vision fallback failed: ${msg}]`,
|
|
127
|
+
},
|
|
128
|
+
];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// Extension entry point
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
export default function spectralVisionExtension(pi) {
|
|
135
|
+
let visionModel;
|
|
136
|
+
pi.on("session_start", (_event, ctx) => {
|
|
137
|
+
visionModel = findVisionModel(ctx);
|
|
138
|
+
if (visionModel) {
|
|
139
|
+
process.stderr.write(`[spectral-vision] Ready — using ${visionModel.provider}/${visionModel.id} for image descriptions.\n`);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
process.stderr.write("[spectral-vision] No vision model with auth configured. Image description disabled.\n");
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
pi.on("context", async (event, ctx) => {
|
|
146
|
+
const messages = event.messages;
|
|
147
|
+
// Check if any message contains images
|
|
148
|
+
let totalImages = 0;
|
|
149
|
+
for (const msg of messages) {
|
|
150
|
+
totalImages += countImages(msg);
|
|
151
|
+
}
|
|
152
|
+
if (totalImages === 0)
|
|
153
|
+
return;
|
|
154
|
+
// Resolve vision model
|
|
155
|
+
const currentModel = ctx.model;
|
|
156
|
+
// If main model supports images, use it for vision (saves auth setup)
|
|
157
|
+
if (currentModel?.input.includes("image")) {
|
|
158
|
+
visionModel = currentModel;
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
// Refresh vision model
|
|
162
|
+
if (!visionModel ||
|
|
163
|
+
!ctx.modelRegistry.hasConfiguredAuth(visionModel)) {
|
|
164
|
+
visionModel = findVisionModel(ctx);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (!visionModel) {
|
|
168
|
+
process.stderr.write("[spectral-vision] No vision model available, images will be omitted\n");
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
process.stderr.write(`[spectral-vision] Describing ${totalImages} image(s) with ${visionModel.provider}/${visionModel.id}...\n`);
|
|
172
|
+
// Process each message
|
|
173
|
+
const processed = await Promise.all(messages.map(async (msg) => {
|
|
174
|
+
if (msg.role !== "user" || !Array.isArray(msg.content) || !hasImageContent(msg.content)) {
|
|
175
|
+
return msg;
|
|
176
|
+
}
|
|
177
|
+
// Extract context from preceding text blocks
|
|
178
|
+
const textBlocks = msg.content
|
|
179
|
+
.filter((c) => c.type === "text")
|
|
180
|
+
.map((c) => c.text)
|
|
181
|
+
.join(" ");
|
|
182
|
+
const contextText = textBlocks.substring(0, 500); // limit context
|
|
183
|
+
const described = await describeImages(visionModel, msg.content, contextText, ctx);
|
|
184
|
+
return { ...msg, content: described };
|
|
185
|
+
}));
|
|
186
|
+
return { messages: processed };
|
|
187
|
+
});
|
|
188
|
+
}
|
|
@@ -39,14 +39,14 @@ export function registerStatusCommand(pi, runtime) {
|
|
|
39
39
|
const pObsLabel = pendingObsCount === 1 ? "observation" : "observations";
|
|
40
40
|
const passiveLines = runtime.config.passive === true
|
|
41
41
|
? [
|
|
42
|
-
"
|
|
42
|
+
"### Mode",
|
|
43
43
|
"Passive: proactive observation and compaction triggers disabled; compaction hook remains active",
|
|
44
44
|
"",
|
|
45
45
|
]
|
|
46
46
|
: [];
|
|
47
47
|
const activityLines = runtime.config.passive === true
|
|
48
48
|
? [
|
|
49
|
-
"
|
|
49
|
+
"### Activity",
|
|
50
50
|
`Observation trigger: passive (~${sinceBound.toLocaleString()} / ${obsThreshold.toLocaleString()} tokens, ${obsPct}%)`,
|
|
51
51
|
" → proactive observation is disabled; manual/Pi compaction can still run sync catch-up observation",
|
|
52
52
|
`Compaction trigger: passive (~${sinceCompaction.toLocaleString()} / ${compThreshold.toLocaleString()} tokens, ${compPct}%)`,
|
|
@@ -56,7 +56,7 @@ export function registerStatusCommand(pi, runtime) {
|
|
|
56
56
|
` distilled from them and redundant observations are pruned away`,
|
|
57
57
|
]
|
|
58
58
|
: [
|
|
59
|
-
"
|
|
59
|
+
"### Activity",
|
|
60
60
|
`Next observation: ~${sinceBound.toLocaleString()} / ${obsThreshold.toLocaleString()} tokens (${obsPct}%)`,
|
|
61
61
|
` → at ${obsThreshold.toLocaleString()} tokens, recent conversation is compressed into new observations`,
|
|
62
62
|
`Next compaction: ~${sinceCompaction.toLocaleString()} / ${compThreshold.toLocaleString()} tokens (${compPct}%)`,
|
|
@@ -68,7 +68,7 @@ export function registerStatusCommand(pi, runtime) {
|
|
|
68
68
|
];
|
|
69
69
|
const lines = [
|
|
70
70
|
...passiveLines,
|
|
71
|
-
"
|
|
71
|
+
"### Memory",
|
|
72
72
|
`Reflections: ~${committedRefsTokens.toLocaleString()} tokens (${committedRefsCount} ${refLabel}) — durable insights`,
|
|
73
73
|
`Observations:`,
|
|
74
74
|
` committed ~${committedObsTokens.toLocaleString()} tokens (${committedObsCount} ${cObsLabel}) — folded into last compaction`,
|
|
@@ -79,7 +79,7 @@ export function registerStatusCommand(pi, runtime) {
|
|
|
79
79
|
];
|
|
80
80
|
if (runtime.observerInFlight || runtime.compactInFlight) {
|
|
81
81
|
lines.push("");
|
|
82
|
-
lines.push("
|
|
82
|
+
lines.push("### In Flight");
|
|
83
83
|
if (runtime.observerInFlight)
|
|
84
84
|
lines.push("Observer: running");
|
|
85
85
|
if (runtime.compactInFlight)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { getMemoryState } from "../branch.js";
|
|
2
|
-
import { observationPoolTokens as estimateObservationPoolTokens } from "../compaction.js";
|
|
2
|
+
import { observationPoolTokens as estimateObservationPoolTokens, observationsToMarkdownList, reflectionsToMarkdownList, } from "../compaction.js";
|
|
3
3
|
import { countByRelevance, formatRelevanceHistogram } from "../relevance.js";
|
|
4
4
|
import { estimateStringTokens } from "../tokens.js";
|
|
5
|
-
import { reflectionContent
|
|
5
|
+
import { reflectionContent } from "../types.js";
|
|
6
6
|
export function registerViewCommand(pi, runtime) {
|
|
7
7
|
pi.registerCommand("om-view", {
|
|
8
8
|
description: "Print observational memory details (reflections + observations)",
|
|
@@ -22,39 +22,41 @@ export function registerViewCommand(pi, runtime) {
|
|
|
22
22
|
const totalTokens = committedRefTokens + totalObsTokens;
|
|
23
23
|
const relevanceHistogram = countByRelevance([...committedObs, ...pendingObs]);
|
|
24
24
|
const plural = (n, singular, plural) => (n === 1 ? singular : plural);
|
|
25
|
-
const renderObs = (r) => `[${r.id}] ${r.timestamp} [${r.relevance}] ${r.content}`;
|
|
26
25
|
const sections = [];
|
|
27
|
-
sections.push(
|
|
26
|
+
sections.push(`## Memory Overview\n\n` +
|
|
27
|
+
`${committedRefCount} ${plural(committedRefCount, "reflection", "reflections")} · ` +
|
|
28
28
|
`${totalObsCount} ${plural(totalObsCount, "observation", "observations")} ` +
|
|
29
29
|
`(${committedObsCount} committed, ${pendingObsCount} pending) · ` +
|
|
30
30
|
`~${totalTokens.toLocaleString()} tokens · ` +
|
|
31
31
|
`relevance ${formatRelevanceHistogram(relevanceHistogram)}`);
|
|
32
|
+
sections.push(`## Reflections (${committedRefCount} ${plural(committedRefCount, "entry", "entries")}, ~${committedRefTokens.toLocaleString()} tokens)`);
|
|
32
33
|
sections.push("");
|
|
33
|
-
sections.push(`── Reflections (${committedRefCount} ${plural(committedRefCount, "entry", "entries")}, ~${committedRefTokens.toLocaleString()} tokens) ──`);
|
|
34
34
|
if (committedRefItems.length > 0) {
|
|
35
|
-
sections.push(committedRefItems
|
|
35
|
+
sections.push(reflectionsToMarkdownList(committedRefItems));
|
|
36
36
|
}
|
|
37
37
|
else {
|
|
38
|
-
sections.push("(none)");
|
|
38
|
+
sections.push("*(none)*");
|
|
39
39
|
}
|
|
40
40
|
sections.push("");
|
|
41
|
-
sections.push(
|
|
41
|
+
sections.push(`## Observations (committed: ${committedObsCount} ${plural(committedObsCount, "observation", "observations")}, ~${committedObsTokens.toLocaleString()} tokens)`);
|
|
42
|
+
sections.push("");
|
|
42
43
|
if (committedObs.length > 0) {
|
|
43
|
-
sections.push(committedObs
|
|
44
|
+
sections.push(observationsToMarkdownList(committedObs));
|
|
44
45
|
}
|
|
45
46
|
else {
|
|
46
|
-
sections.push("(none)");
|
|
47
|
+
sections.push("*(none)*");
|
|
47
48
|
}
|
|
48
49
|
sections.push("");
|
|
49
|
-
sections.push(
|
|
50
|
+
sections.push(`## Observations (pending: ${pendingObsCount} ${plural(pendingObsCount, "observation", "observations")}, ~${pendingObsTokens.toLocaleString()} tokens)`);
|
|
51
|
+
sections.push("");
|
|
50
52
|
if (pendingObs.length > 0) {
|
|
51
|
-
sections.push(pendingObs
|
|
53
|
+
sections.push(observationsToMarkdownList(pendingObs));
|
|
52
54
|
}
|
|
53
55
|
else {
|
|
54
|
-
sections.push("(none)");
|
|
56
|
+
sections.push("*(none)*");
|
|
55
57
|
}
|
|
56
58
|
sections.push("");
|
|
57
|
-
sections.push("Tip: use /tree to browse the raw messages still live in the session
|
|
59
|
+
sections.push("*Tip: use /tree to browse the raw messages still live in the session.*");
|
|
58
60
|
ctx.ui.notify(sections.join("\n"), "info");
|
|
59
61
|
},
|
|
60
62
|
});
|
|
@@ -798,16 +798,44 @@ export async function runPruner(args, reflections, observations, budgetTokens, o
|
|
|
798
798
|
});
|
|
799
799
|
return result;
|
|
800
800
|
}
|
|
801
|
+
/**
|
|
802
|
+
* Format an observation for Markdown-safe display.
|
|
803
|
+
* Wraps the ID in backticks so it renders as inline code in Markdown
|
|
804
|
+
* rather than being misinterpreted as a link reference.
|
|
805
|
+
*/
|
|
806
|
+
export function observationToMarkdown(record) {
|
|
807
|
+
return `[\`${record.id}\`] ${record.timestamp} [${record.relevance}] ${record.content}`;
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Format a reflection for Markdown-safe display.
|
|
811
|
+
* Wraps the ID in backticks so it renders as inline code in Markdown.
|
|
812
|
+
*/
|
|
813
|
+
export function reflectionToMarkdown(reflection) {
|
|
814
|
+
if (typeof reflection === "string")
|
|
815
|
+
return reflection;
|
|
816
|
+
return `[\`${reflection.id}\`] ${reflection.content}`;
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Format observations as a Markdown bullet list for display.
|
|
820
|
+
*/
|
|
821
|
+
export function observationsToMarkdownList(observations) {
|
|
822
|
+
return observations.map((o) => `- ${observationToMarkdown(o)}`).join("\n");
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Format reflections as a Markdown bullet list for display.
|
|
826
|
+
*/
|
|
827
|
+
export function reflectionsToMarkdownList(reflections) {
|
|
828
|
+
return reflections.map((r) => `- ${reflectionToMarkdown(r)}`).join("\n");
|
|
829
|
+
}
|
|
801
830
|
export function renderSummary(reflections, observations) {
|
|
802
831
|
if (reflections.length === 0 && observations.length === 0)
|
|
803
832
|
return "";
|
|
804
833
|
const parts = [CONTEXT_USAGE_INSTRUCTIONS];
|
|
805
834
|
if (reflections.length > 0) {
|
|
806
|
-
parts.push(`## Reflections\n${reflections
|
|
835
|
+
parts.push(`## Reflections\n${reflectionsToMarkdownList(reflections)}`);
|
|
807
836
|
}
|
|
808
837
|
if (observations.length > 0) {
|
|
809
|
-
|
|
810
|
-
parts.push(`## Observations\n${body}`);
|
|
838
|
+
parts.push(`## Observations\n${observationsToMarkdownList(observations)}`);
|
|
811
839
|
}
|
|
812
840
|
return parts.join("\n\n");
|
|
813
841
|
}
|
package/dist/memory/prompts.js
CHANGED
|
@@ -92,7 +92,7 @@ Your job is to compress a chunk of recent conversation into timestamped, rated o
|
|
|
92
92
|
|
|
93
93
|
You receive:
|
|
94
94
|
- Current reflections (long-lived facts already crystallized).
|
|
95
|
-
- Current observations (already-recorded observations, each shown as "[id] YYYY-MM-DD HH:MM [relevance] content").
|
|
95
|
+
- Current observations (already-recorded observations, each shown as "[\`id\`] YYYY-MM-DD HH:MM [relevance] content").
|
|
96
96
|
- A new chunk of conversation with source entry labels and inline message timestamps. Each source block starts with "[Source entry id: <id>]" followed by content formatted as "[User @ YYYY-MM-DD HH:MM]:", "[Assistant @ ...]:", "[Tool result for <name> @ ...]:", custom messages, or branch summaries.
|
|
97
97
|
- A current local time fallback for observations that have no obvious message timestamp.
|
|
98
98
|
|
|
@@ -141,7 +141,7 @@ Your task is different from the observer's: you are not recording events, you ar
|
|
|
141
141
|
|
|
142
142
|
You receive:
|
|
143
143
|
- Current reflections (already-crystallized long-lived facts, one per line). Newer reflections may begin with a bracketed id handle; treat that id as recall metadata, not as part of the reflection prose.
|
|
144
|
-
- Current observations (timestamped, relevance-tagged events accumulated over many turns). Each is shown as "[id] YYYY-MM-DD HH:MM [relevance] content".
|
|
144
|
+
- Current observations (timestamped, relevance-tagged events accumulated over many turns). Each is shown as "[\`id\`] YYYY-MM-DD HH:MM [relevance] content".
|
|
145
145
|
|
|
146
146
|
How you work:
|
|
147
147
|
1. Read current reflections and observations to understand what is already crystallized and what new signal exists in the pool.
|
|
@@ -198,7 +198,7 @@ ${RELEVANCE_RUBRIC}
|
|
|
198
198
|
|
|
199
199
|
You receive:
|
|
200
200
|
- Current reflections (long-lived facts; they survive regardless — treat them as already captured). Newer reflections may begin with a bracketed id handle; treat that id as recall metadata, not as part of the reflection prose.
|
|
201
|
-
- Current observations (timestamped, relevance-tagged events to prune). Each is shown as "[id] YYYY-MM-DD HH:MM [relevance] [coverage: tag] content", where id is the 12-character hex handle you reference when dropping.
|
|
201
|
+
- Current observations (timestamped, relevance-tagged events to prune). Each is shown as "[\`id\`] YYYY-MM-DD HH:MM [relevance] [coverage: tag] content", where id is the 12-character hex handle you reference when dropping.
|
|
202
202
|
- A pressure line stating pool size, target, tokens still to cut, and the current pass strategy.
|
|
203
203
|
|
|
204
204
|
Coverage tags are pruning signals derived from current provenance-backed reflection support ids. They are strong evidence, not blind commands:
|
|
@@ -279,8 +279,8 @@ export function buildPrunerPassGuidance(pass, maxPasses) {
|
|
|
279
279
|
}
|
|
280
280
|
export const CONTEXT_USAGE_INSTRUCTIONS = `These are condensed memories from earlier in this session.
|
|
281
281
|
|
|
282
|
-
- Reflections: stable, long-lived facts about the user, project, decisions, and constraints. New reflection lines may include ids in brackets.
|
|
283
|
-
- Observations: timestamped events from the conversation history, in chronological order. Observation lines include ids in brackets.
|
|
282
|
+
- Reflections: stable, long-lived facts about the user, project, decisions, and constraints. New reflection lines may include ids in brackets wrapped in backticks.
|
|
283
|
+
- Observations: timestamped events from the conversation history, in chronological order. Observation lines include ids in brackets wrapped in backticks.
|
|
284
284
|
|
|
285
285
|
Treat these as past records. When entries conflict, the most recent observation reflects the latest known state. Work that prior observations describe as completed should not be redone unless the user explicitly asks to revisit it.
|
|
286
286
|
|
|
@@ -181,10 +181,10 @@ function friendlySourceUnavailableMessage(match) {
|
|
|
181
181
|
return `Observation ${match.observation.id} has source entries associated, but some are unavailable on the current branch or are not source-renderable.${missing}${nonSource}`;
|
|
182
182
|
}
|
|
183
183
|
function reflectionLineText(reflection) {
|
|
184
|
-
return `[
|
|
184
|
+
return `[\`${reflection.id}\`] ${reflection.content}`;
|
|
185
185
|
}
|
|
186
186
|
function observationLineText(observation) {
|
|
187
|
-
return `[
|
|
187
|
+
return `[\`${observation.id}\`] ${observation.timestamp} [${observation.relevance}] ${observation.content}`;
|
|
188
188
|
}
|
|
189
189
|
function renderObservationOnlyTextFromResult(result) {
|
|
190
190
|
const sections = [];
|
|
@@ -297,17 +297,6 @@ export function getThemesDir() {
|
|
|
297
297
|
/**
|
|
298
298
|
* Get path to HTML export template directory (shipped with package)
|
|
299
299
|
* - For Bun binary: export-html/ next to executable
|
|
300
|
-
* - For Node.js (dist/): dist/core/export-html/
|
|
301
|
-
* - For tsx (src/): src/core/export-html/
|
|
302
|
-
*/
|
|
303
|
-
export function getExportTemplateDir() {
|
|
304
|
-
if (isBunBinary) {
|
|
305
|
-
return join(getPackageDir(), "export-html");
|
|
306
|
-
}
|
|
307
|
-
const packageDir = getPackageDir();
|
|
308
|
-
const srcOrDist = existsSync(join(packageDir, "src")) ? "src" : "dist";
|
|
309
|
-
return join(packageDir, srcOrDist, "core", "export-html");
|
|
310
|
-
}
|
|
311
300
|
/** Get path to package.json */
|
|
312
301
|
export function getPackageJsonPath() {
|
|
313
302
|
return join(getPackageDir(), "package.json");
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
16
16
|
import { basename, dirname } from "node:path";
|
|
17
17
|
import { clampThinkingLevel, cleanupSessionResources, getSupportedThinkingLevels, isContextOverflow, modelsAreEqual, resetApiProviders, streamSimple, } from "../../ai/index.js";
|
|
18
|
-
import { theme } from "../modes/interactive/theme/theme.js";
|
|
19
18
|
import { stripFrontmatter } from "../utils/frontmatter.js";
|
|
20
19
|
import { resolvePath } from "../utils/paths.js";
|
|
21
20
|
import { sleep } from "../utils/sleep.js";
|
|
@@ -23,8 +22,6 @@ import { formatNoApiKeyFoundMessage, formatNoModelSelectedMessage } from "./auth
|
|
|
23
22
|
import { executeBashWithOperations } from "./bash-executor.js";
|
|
24
23
|
import { calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, generateBranchSummary, prepareCompaction, shouldCompact, } from "./compaction/index.js";
|
|
25
24
|
import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
|
|
26
|
-
import { exportSessionToHtml } from "./export-html/index.js";
|
|
27
|
-
import { createToolHtmlRenderer } from "./export-html/tool-renderer.js";
|
|
28
25
|
import { ExtensionRunner, wrapRegisteredTools, } from "./extensions/index.js";
|
|
29
26
|
import { emitSessionShutdownEvent } from "./extensions/runner.js";
|
|
30
27
|
import { expandPromptTemplate } from "./prompt-templates.js";
|
|
@@ -1889,7 +1886,7 @@ export class AgentSession {
|
|
|
1889
1886
|
extensionsResult.runtime.flagValues.set(name, value);
|
|
1890
1887
|
}
|
|
1891
1888
|
}
|
|
1892
|
-
this._extensionRunner = new ExtensionRunner(extensionsResult.extensions, extensionsResult.runtime, this._cwd, this.sessionManager, this._modelRegistry);
|
|
1889
|
+
this._extensionRunner = new ExtensionRunner(extensionsResult.extensions, extensionsResult.runtime, this._cwd, this.sessionManager, this._modelRegistry, this.settingsManager);
|
|
1893
1890
|
if (this._extensionRunnerRef) {
|
|
1894
1891
|
this._extensionRunnerRef.current = this._extensionRunner;
|
|
1895
1892
|
}
|
|
@@ -2398,19 +2395,8 @@ export class AgentSession {
|
|
|
2398
2395
|
* @param outputPath Optional output path (defaults to session directory)
|
|
2399
2396
|
* @returns Path to exported file
|
|
2400
2397
|
*/
|
|
2401
|
-
async exportToHtml(
|
|
2402
|
-
|
|
2403
|
-
// Create tool renderer if we have an extension runner (for custom tool HTML rendering)
|
|
2404
|
-
const toolRenderer = createToolHtmlRenderer({
|
|
2405
|
-
getToolDefinition: (name) => this.getToolDefinition(name),
|
|
2406
|
-
theme,
|
|
2407
|
-
cwd: this.sessionManager.getCwd(),
|
|
2408
|
-
});
|
|
2409
|
-
return await exportSessionToHtml(this.sessionManager, this.state, {
|
|
2410
|
-
outputPath,
|
|
2411
|
-
themeName,
|
|
2412
|
-
toolRenderer,
|
|
2413
|
-
});
|
|
2398
|
+
async exportToHtml(_outputPath) {
|
|
2399
|
+
throw new Error("HTML export has been removed. Use spectral serve instead.");
|
|
2414
2400
|
}
|
|
2415
2401
|
/**
|
|
2416
2402
|
* Export the current session branch to a JSONL file.
|
|
@@ -9,7 +9,6 @@ import { fileURLToPath } from "node:url";
|
|
|
9
9
|
import * as _bundledPiAgentCore from "../../../agent-core/index.js";
|
|
10
10
|
import * as _bundledPiAi from "../../../ai/index.js";
|
|
11
11
|
import * as _bundledPiAiOauth from "../../../ai/oauth.js";
|
|
12
|
-
import * as _bundledPiTui from "../../../tui/index.js";
|
|
13
12
|
import { createJiti } from "@mariozechner/jiti";
|
|
14
13
|
// Static imports of packages that extensions may use.
|
|
15
14
|
// These MUST be static so Bun bundles them into the compiled binary.
|
|
@@ -34,12 +33,10 @@ const VIRTUAL_MODULES = {
|
|
|
34
33
|
"@sinclair/typebox/compile": _bundledTypeboxCompile,
|
|
35
34
|
"@sinclair/typebox/value": _bundledTypeboxValue,
|
|
36
35
|
"../../../agent-core/index.ts": _bundledPiAgentCore,
|
|
37
|
-
"../../../tui/index.ts": _bundledPiTui,
|
|
38
36
|
"../../../ai/index.ts": _bundledPiAi,
|
|
39
37
|
"../../../ai/oauth.ts": _bundledPiAiOauth,
|
|
40
38
|
"../../index.ts": _bundledPiCodingAgent,
|
|
41
39
|
"@mariozechner/pi-agent": _bundledPiAgentCore,
|
|
42
|
-
"@mariozechner/pi-tui": _bundledPiTui,
|
|
43
40
|
"@mariozechner/pi-ai": _bundledPiAi,
|
|
44
41
|
"@mariozechner/pi-ai/oauth": _bundledPiAiOauth,
|
|
45
42
|
"@mariozechner/pi-coding-agent": _bundledPiCodingAgent,
|
|
@@ -68,18 +65,15 @@ function getAliases() {
|
|
|
68
65
|
};
|
|
69
66
|
const piCodingAgentEntry = packageIndex;
|
|
70
67
|
const piAgentCoreEntry = resolveWorkspaceOrImport("agent/dist/index.js", "../../../agent-core/index.ts");
|
|
71
|
-
const piTuiEntry = resolveWorkspaceOrImport("tui/dist/index.js", "../../../tui/index.ts");
|
|
72
68
|
const piAiEntry = resolveWorkspaceOrImport("ai/dist/index.js", "../../../ai/index.ts");
|
|
73
69
|
const piAiOauthEntry = resolveWorkspaceOrImport("ai/dist/oauth.js", "../../../ai/oauth.ts");
|
|
74
70
|
_aliases = {
|
|
75
71
|
"../../index.ts": piCodingAgentEntry,
|
|
76
72
|
"../../../agent-core/index.ts": piAgentCoreEntry,
|
|
77
|
-
"../../../tui/index.ts": piTuiEntry,
|
|
78
73
|
"../../../ai/index.ts": piAiEntry,
|
|
79
74
|
"../../../ai/oauth.ts": piAiOauthEntry,
|
|
80
75
|
"@mariozechner/pi-coding-agent": piCodingAgentEntry,
|
|
81
76
|
"@mariozechner/pi-agent": piAgentCoreEntry,
|
|
82
|
-
"@mariozechner/pi-tui": piTuiEntry,
|
|
83
77
|
"@mariozechner/pi-ai": piAiEntry,
|
|
84
78
|
"@mariozechner/pi-ai/oauth": piAiOauthEntry,
|
|
85
79
|
typebox: typeboxEntry,
|
|
@@ -113,14 +113,16 @@ export class ExtensionRunner {
|
|
|
113
113
|
shutdownHandler = () => { };
|
|
114
114
|
shortcutDiagnostics = [];
|
|
115
115
|
commandDiagnostics = [];
|
|
116
|
+
settingsManager;
|
|
116
117
|
staleMessage;
|
|
117
|
-
constructor(extensions, runtime, cwd, sessionManager, modelRegistry) {
|
|
118
|
+
constructor(extensions, runtime, cwd, sessionManager, modelRegistry, settingsManager) {
|
|
118
119
|
this.extensions = extensions;
|
|
119
120
|
this.runtime = runtime;
|
|
120
121
|
this.uiContext = noOpUIContext;
|
|
121
122
|
this.cwd = cwd;
|
|
122
123
|
this.sessionManager = sessionManager;
|
|
123
124
|
this.modelRegistry = modelRegistry;
|
|
125
|
+
this.settingsManager = settingsManager;
|
|
124
126
|
}
|
|
125
127
|
bindCore(actions, contextActions, providerActions) {
|
|
126
128
|
// Copy actions into the shared runtime (all extension APIs reference this)
|
|
@@ -399,6 +401,10 @@ export class ExtensionRunner {
|
|
|
399
401
|
runner.assertActive();
|
|
400
402
|
return runner.modelRegistry;
|
|
401
403
|
},
|
|
404
|
+
get settingsManager() {
|
|
405
|
+
runner.assertActive();
|
|
406
|
+
return runner.settingsManager;
|
|
407
|
+
},
|
|
402
408
|
get model() {
|
|
403
409
|
runner.assertActive();
|
|
404
410
|
return getModel();
|