pi-ui-extend 0.1.50 → 0.1.53
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/app/icons.d.ts +1 -0
- package/dist/app/icons.js +2 -0
- package/dist/app/input/autocomplete-controller.js +1 -1
- package/dist/app/input/input-controller.d.ts +1 -0
- package/dist/app/input/input-controller.js +7 -24
- package/dist/app/input/input-paste-handler.d.ts +1 -0
- package/dist/app/input/input-paste-handler.js +13 -16
- package/dist/app/rendering/conversation-tool-renderer.js +1 -0
- package/dist/app/rendering/popup-menu-renderer.js +2 -2
- package/dist/app/rendering/render-controller.js +10 -16
- package/dist/app/rendering/status-line-renderer.d.ts +0 -7
- package/dist/app/rendering/status-line-renderer.js +7 -79
- package/dist/app/rendering/tab-line-renderer.js +1 -1
- package/dist/app/rendering/toast-renderer.js +40 -15
- package/dist/app/rendering/tool-block-renderer.d.ts +2 -1
- package/dist/app/rendering/tool-block-renderer.js +43 -3
- package/dist/app/screen/mouse-controller.d.ts +1 -0
- package/dist/app/screen/mouse-controller.js +30 -76
- package/dist/app/session/lazy-session-manager.js +0 -7
- package/dist/app/session/queued-message-controller.d.ts +0 -1
- package/dist/app/session/queued-message-controller.js +5 -13
- package/dist/app/session/queued-message-entries.d.ts +1 -0
- package/dist/app/session/queued-message-entries.js +8 -0
- package/dist/app/session/tabs-controller.d.ts +2 -1
- package/dist/app/session/tabs-controller.js +67 -24
- package/dist/app/types.d.ts +1 -0
- package/dist/bundled-extensions/question/tool-description.js +2 -3
- package/dist/bundled-extensions/session-title/title-generation.js +1 -1
- package/dist/tool-renderers/compress.js +28 -7
- package/dist/tool-renderers/patch-normalize.js +4 -4
- package/dist/tool-renderers/read.js +1 -1
- package/dist/tool-renderers/types.d.ts +10 -0
- package/external/pi-tools-suite/package.json +3 -3
- package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +6 -10
- package/external/pi-tools-suite/src/async-subagents/core/cleanup.ts +2 -2
- package/external/pi-tools-suite/src/async-subagents/core/paths.ts +12 -0
- package/external/pi-tools-suite/src/async-subagents/core/registry.ts +3 -4
- package/external/pi-tools-suite/src/async-subagents/core/routing.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/core/sessions.ts +2 -2
- package/external/pi-tools-suite/src/async-subagents/core/state.ts +3 -3
- package/external/pi-tools-suite/src/async-subagents/core/ultrawork-auto.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/lib.ts +1 -1
- package/external/pi-tools-suite/src/coding-discipline/index.ts +13 -23
- package/external/pi-tools-suite/src/dcp/auto-compress.ts +1 -1
- package/external/pi-tools-suite/src/dcp/prompts.ts +9 -49
- package/external/pi-tools-suite/src/tool-descriptions.ts +34 -61
- package/package.json +4 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { formatCompactProgressBar } from "../context-progress-bar.js";
|
|
1
|
+
import { compactProgressBarSegments, formatCompactProgressBar } from "../context-progress-bar.js";
|
|
2
2
|
import { parseArgsText, stringArg } from "./utils.js";
|
|
3
3
|
export const renderCompressTool = (input) => {
|
|
4
4
|
const topic = stringArg(input, ["topic"]);
|
|
@@ -6,6 +6,7 @@ export const renderCompressTool = (input) => {
|
|
|
6
6
|
const header = joinHeaderParts(topic ? { text: topic } : undefined, summary) ?? { text: "" };
|
|
7
7
|
return {
|
|
8
8
|
...(header.text ? { headerArgs: header.text } : {}),
|
|
9
|
+
...(header.segments && header.segments.length > 0 ? { headerArgSegments: header.segments } : {}),
|
|
9
10
|
collapsedBody: "",
|
|
10
11
|
expandedText: fullCompressResponse(input.output, input.status),
|
|
11
12
|
};
|
|
@@ -19,14 +20,14 @@ function formatCompressSummary(input) {
|
|
|
19
20
|
const parsed = parseArgsText(output);
|
|
20
21
|
if (!isRecord(parsed))
|
|
21
22
|
return { text: oneLine(output) };
|
|
22
|
-
return formatCompressSuccess(parsed);
|
|
23
|
+
return formatCompressSuccess(parsed, input.colors);
|
|
23
24
|
}
|
|
24
25
|
function fullCompressResponse(output, status) {
|
|
25
26
|
if (output.trim())
|
|
26
27
|
return output.trimEnd();
|
|
27
28
|
return status === "running" ? "running…" : "(empty)";
|
|
28
29
|
}
|
|
29
|
-
function formatCompressSuccess(result) {
|
|
30
|
+
function formatCompressSuccess(result, colors) {
|
|
30
31
|
const tokensSaved = numberValue(result.tokensSaved);
|
|
31
32
|
const contextPercent = numberValue(result.contextPercent);
|
|
32
33
|
const contextTokens = numberValue(result.contextTokens);
|
|
@@ -42,7 +43,7 @@ function formatCompressSuccess(result) {
|
|
|
42
43
|
const barPercent = compressedContextPercent ?? contextPercent;
|
|
43
44
|
const parts = [
|
|
44
45
|
{ text: tokensSaved != null ? `saved ${formatCompactNumber(tokensSaved)}` : "compressed" },
|
|
45
|
-
barPercent != null ? progressPart(barPercent, originalContextTokens) : undefined,
|
|
46
|
+
barPercent != null ? progressPart(barPercent, originalContextTokens, colors) : undefined,
|
|
46
47
|
compressedContextPercent != null && contextPercent != null ? { text: `context ${formatPercent(contextPercent)}` } : undefined,
|
|
47
48
|
itemCount != null ? { text: `${itemCount} ${plural(itemCount, "item")}` } : undefined,
|
|
48
49
|
summaryTokens != null ? { text: `${formatCompactNumber(summaryTokens)} summary tokens` } : undefined,
|
|
@@ -52,9 +53,17 @@ function formatCompressSuccess(result) {
|
|
|
52
53
|
].filter((part) => Boolean(part));
|
|
53
54
|
return joinHeaderParts(...parts) ?? { text: "compressed" };
|
|
54
55
|
}
|
|
55
|
-
function progressPart(percent, previousContextTokens) {
|
|
56
|
+
function progressPart(percent, previousContextTokens, colors) {
|
|
56
57
|
const text = `${formatCompactProgressBar(percent)} ${formatPercent(percent)}${previousContextTokens != null ? ` of ${formatCompactNumber(previousContextTokens)}` : ""}`;
|
|
57
|
-
return {
|
|
58
|
+
return {
|
|
59
|
+
text,
|
|
60
|
+
...(colors ? {
|
|
61
|
+
segments: compactProgressBarSegments(0, percent, {
|
|
62
|
+
fill: colors.statusForeground,
|
|
63
|
+
track: colors.statusDotBase,
|
|
64
|
+
}),
|
|
65
|
+
} : {}),
|
|
66
|
+
};
|
|
58
67
|
}
|
|
59
68
|
function inferContextTokens(contextPercent, contextWindow) {
|
|
60
69
|
if (contextPercent == null || contextWindow == null || contextWindow <= 0)
|
|
@@ -71,7 +80,19 @@ function joinHeaderParts(...parts) {
|
|
|
71
80
|
if (defined.length === 0)
|
|
72
81
|
return undefined;
|
|
73
82
|
const separator = " · ";
|
|
74
|
-
|
|
83
|
+
let offset = 0;
|
|
84
|
+
const segments = [];
|
|
85
|
+
for (const [index, part] of defined.entries()) {
|
|
86
|
+
if (index > 0)
|
|
87
|
+
offset += separator.length;
|
|
88
|
+
if (part.segments)
|
|
89
|
+
segments.push(...part.segments.map((segment) => ({ ...segment, start: segment.start + offset, end: segment.end + offset })));
|
|
90
|
+
offset += part.text.length;
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
text: defined.map((part) => part.text).join(separator),
|
|
94
|
+
...(segments.length > 0 ? { segments } : {}),
|
|
95
|
+
};
|
|
75
96
|
}
|
|
76
97
|
function numberValue(value) {
|
|
77
98
|
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
@@ -110,10 +110,10 @@ function renderDiffOp(op) {
|
|
|
110
110
|
return `-${op.text}`;
|
|
111
111
|
if (op.type === "insert")
|
|
112
112
|
return `+${op.text}`;
|
|
113
|
-
// Context marker is a leading space.
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
return
|
|
113
|
+
// Context marker is a leading space. Always emit it even when the content is
|
|
114
|
+
// already indented, so unchanged lines reserve the same marker column as `+`
|
|
115
|
+
// and `-` lines in the terminal diff view.
|
|
116
|
+
return ` ${op.text}`;
|
|
117
117
|
}
|
|
118
118
|
/** Minimal LCS-based line diff. */
|
|
119
119
|
function diffLines(oldLines, newLines) {
|
|
@@ -2,7 +2,7 @@ import { isAbsolute, relative, sep } from "node:path";
|
|
|
2
2
|
import { syntaxHighlightLanguageForPath } from "../syntax-highlight.js";
|
|
3
3
|
import { numberArg, stringArg } from "./utils.js";
|
|
4
4
|
export const renderReadTool = (input) => {
|
|
5
|
-
const filePath = stringArg(input, ["path", "file", "target"]);
|
|
5
|
+
const filePath = stringArg(input, ["path", "file_path", "filePath", "file", "target"]);
|
|
6
6
|
if (!filePath)
|
|
7
7
|
return undefined;
|
|
8
8
|
const displayPath = pathForDisplay(filePath, input.cwd);
|
|
@@ -23,6 +23,7 @@ export type ToolRenderInput = {
|
|
|
23
23
|
export type ToolRenderResult = {
|
|
24
24
|
toolName?: string;
|
|
25
25
|
headerArgs?: string;
|
|
26
|
+
headerArgSegments?: readonly ToolHeaderSegment[];
|
|
26
27
|
bodyLineStyles?: readonly ToolBodyLineStyle[];
|
|
27
28
|
bodyStyle?: "diff";
|
|
28
29
|
preserveAnsi?: boolean;
|
|
@@ -30,4 +31,13 @@ export type ToolRenderResult = {
|
|
|
30
31
|
collapsedBody: string;
|
|
31
32
|
expandedText: string;
|
|
32
33
|
};
|
|
34
|
+
export type ToolHeaderSegment = {
|
|
35
|
+
start: number;
|
|
36
|
+
end: number;
|
|
37
|
+
foreground?: string;
|
|
38
|
+
background?: string;
|
|
39
|
+
bold?: boolean;
|
|
40
|
+
underline?: boolean;
|
|
41
|
+
strikethrough?: boolean;
|
|
42
|
+
};
|
|
33
43
|
export type ToolRendererMiddleware = (input: ToolRenderInput) => ToolRenderResult | undefined;
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
"vscode-languageserver-protocol": "^3.17.5"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@earendil-works/pi-ai": "0.
|
|
42
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
43
|
-
"@earendil-works/pi-tui": "0.
|
|
41
|
+
"@earendil-works/pi-ai": "0.80.2",
|
|
42
|
+
"@earendil-works/pi-coding-agent": "0.80.2",
|
|
43
|
+
"@earendil-works/pi-tui": "0.80.2",
|
|
44
44
|
"typebox": "*"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
@@ -12,23 +12,19 @@ const FALSE_ENV_PATTERN = /^(0|false|off|no|disabled|none)$/i;
|
|
|
12
12
|
const TRUE_ENV_PATTERN = /^(1|true|on|yes|auto)$/i;
|
|
13
13
|
|
|
14
14
|
const PARALLEL_FIRST_STRATEGY_PROMPT = `<agent_strategy name="parallel-first">
|
|
15
|
-
|
|
15
|
+
Execution hint for Pi, not a replacement for system/developer/user instructions.
|
|
16
16
|
|
|
17
|
-
Default
|
|
17
|
+
Default for non-GPT models: orchestration-first. For broad, multi-file, ambiguous, review/audit, frontend, test-strategy, architecture, or root-cause work, prefer ultrawork mode: split independent tracks into focused async subagents, adding one oracle only for high-stakes uncertainty. Keep parent context lean, read compact results when needed, synthesize, then verify.
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
Do not over-delegate trivial work. For a simple question, one known file, exact lookup, typo, or narrow edit, solve directly with the cheapest suitable tool.
|
|
19
|
+
Do not over-delegate simple questions, known-file work, exact lookups, typos, or narrow edits; solve them directly with the cheapest suitable tool. If compressing unfinished work, preserve active objective + next step via todo/DCP rules.
|
|
22
20
|
</agent_strategy>`;
|
|
23
21
|
|
|
24
22
|
const DEEP_WORK_STRATEGY_PROMPT = `<agent_strategy name="deep-work">
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
Default posture: autonomous deep worker. Build context directly, make concrete progress, edit and verify end-to-end. Use async subagents and ultrawork mode when the user asks for parallel/delegated work or when independent tracks will clearly reduce risk, but do not force orchestration onto narrow tasks.
|
|
23
|
+
Execution hint for Pi, not a replacement for system/developer/user instructions.
|
|
28
24
|
|
|
29
|
-
|
|
25
|
+
Default: autonomous deep worker. Build context directly, make progress, edit, and verify end-to-end. Use async subagents/ultrawork only when the user asks for delegation or independent tracks clearly reduce risk; do not force orchestration onto narrow tasks.
|
|
30
26
|
|
|
31
|
-
For broad work, keep delegation explicit and bounded:
|
|
27
|
+
For broad work, keep delegation explicit and bounded: focused review/research/tests/frontend/deep tracks, plus one oracle only for high-stakes uncertainty or final plan checks. Read compact results, decide in the parent session, and report only what matters. If compressing unfinished work, preserve active objective + next step via todo/DCP rules.
|
|
32
28
|
</agent_strategy>`;
|
|
33
29
|
|
|
34
30
|
export function agentStrategyPrompt(options: AgentStrategyOptions = {}): string | undefined {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { isDir } from "./paths.js";
|
|
3
|
+
import { hasLaunchedAgentPrompt, isDir } from "./paths.js";
|
|
4
4
|
|
|
5
5
|
export function findCleanupCandidates(
|
|
6
6
|
runRoot: string,
|
|
@@ -57,8 +57,8 @@ function isCompletedRun(runDir: string): boolean {
|
|
|
57
57
|
let foundAgent = false;
|
|
58
58
|
for (const entry of fs.readdirSync(runDir, { withFileTypes: true })) {
|
|
59
59
|
if (!entry.isDirectory()) continue;
|
|
60
|
+
if (!hasLaunchedAgentPrompt(runDir, entry.name)) continue;
|
|
60
61
|
const agentDir = path.join(runDir, entry.name);
|
|
61
|
-
if (!fs.existsSync(path.join(agentDir, "prompt.md"))) continue;
|
|
62
62
|
foundAgent = true;
|
|
63
63
|
if (!fs.existsSync(path.join(agentDir, "exit_code"))) return false;
|
|
64
64
|
}
|
|
@@ -33,6 +33,18 @@ export function isDir(p: string): boolean {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
export function hasLaunchedAgentPrompt(runDir: string, agentId: string): boolean {
|
|
37
|
+
return fs.existsSync(path.join(runDir, agentId, "prompt.md"));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function hasQueuedAgentPrompt(runDir: string, agentId: string): boolean {
|
|
41
|
+
return fs.existsSync(path.join(runDir, "prompts", `${agentId}.md`));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function hasAgentPrompt(runDir: string, agentId: string): boolean {
|
|
45
|
+
return hasLaunchedAgentPrompt(runDir, agentId) || hasQueuedAgentPrompt(runDir, agentId);
|
|
46
|
+
}
|
|
47
|
+
|
|
36
48
|
const SAFE_BASENAME = /^[A-Za-z0-9._-]+$/;
|
|
37
49
|
|
|
38
50
|
export function validateBasename(value: string, label: string): void {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { getRunRoot, isDir, resolveRunDir } from "./paths.js";
|
|
3
|
+
import { getRunRoot, hasAgentPrompt, hasLaunchedAgentPrompt, isDir, resolveRunDir } from "./paths.js";
|
|
4
4
|
|
|
5
5
|
export const SUBAGENT_REGISTRY_FILE = "registry.json";
|
|
6
6
|
|
|
@@ -161,15 +161,14 @@ function looksLikeRunDir(runDir: string): boolean {
|
|
|
161
161
|
if (isDir(path.join(runDir, "prompts"))) return true;
|
|
162
162
|
try {
|
|
163
163
|
return fs.readdirSync(runDir, { withFileTypes: true })
|
|
164
|
-
.some((entry) => entry.isDirectory() &&
|
|
164
|
+
.some((entry) => entry.isDirectory() && hasLaunchedAgentPrompt(runDir, entry.name));
|
|
165
165
|
} catch {
|
|
166
166
|
return false;
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
function hasAgentRecord(runDir: string, agentId: string): boolean {
|
|
171
|
-
return
|
|
172
|
-
|| fs.existsSync(path.join(runDir, "prompts", `${agentId}.md`));
|
|
171
|
+
return hasAgentPrompt(runDir, agentId);
|
|
173
172
|
}
|
|
174
173
|
|
|
175
174
|
function normalizeRegistryRun(runId: string, value: unknown): SubagentRegistryRun | undefined {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { isDir } from "./paths.js";
|
|
3
|
+
import { hasLaunchedAgentPrompt, isDir } from "./paths.js";
|
|
4
4
|
import { listSubagentRunDirs } from "./registry.js";
|
|
5
5
|
import { getAgentState } from "./state.js";
|
|
6
6
|
import type { AgentState } from "./types.js";
|
|
@@ -105,7 +105,7 @@ export function listSubagentSessionRecords(cwd: string): SubagentSessionRecord[]
|
|
|
105
105
|
for (const entry of fs.readdirSync(runDir, { withFileTypes: true })) {
|
|
106
106
|
if (!entry.isDirectory()) continue;
|
|
107
107
|
const agentDir = path.join(runDir, entry.name);
|
|
108
|
-
if (!
|
|
108
|
+
if (!hasLaunchedAgentPrompt(runDir, entry.name)) continue;
|
|
109
109
|
records.push({
|
|
110
110
|
runDir,
|
|
111
111
|
runName: path.basename(runDir),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { isDir } from "./paths.js";
|
|
3
|
+
import { hasLaunchedAgentPrompt, isDir } from "./paths.js";
|
|
4
4
|
import { readStructuredResult } from "./structured-result.js";
|
|
5
5
|
import type { AgentResult, AgentState, RpcEventRecord, RunState } from "./types.js";
|
|
6
6
|
|
|
@@ -23,7 +23,7 @@ export function getAgentState(
|
|
|
23
23
|
): AgentState | null {
|
|
24
24
|
const agentDir = path.join(runDir, agentId);
|
|
25
25
|
if (!isDir(agentDir)) return null;
|
|
26
|
-
if (!
|
|
26
|
+
if (!hasLaunchedAgentPrompt(runDir, agentId)) return null;
|
|
27
27
|
const includeLineCounts = options.includeLineCounts ?? true;
|
|
28
28
|
const checkRpcPromptFailure = options.checkRpcPromptFailure ?? true;
|
|
29
29
|
|
|
@@ -188,7 +188,7 @@ export function getRunState(
|
|
|
188
188
|
// Read launched agent dirs
|
|
189
189
|
for (const entry of fs.readdirSync(runDir, { withFileTypes: true })) {
|
|
190
190
|
if (!entry.isDirectory()) continue;
|
|
191
|
-
if (!
|
|
191
|
+
if (!hasLaunchedAgentPrompt(runDir, entry.name)) continue;
|
|
192
192
|
if (filterIds && !filterIds.includes(entry.name)) continue;
|
|
193
193
|
const state = getAgentState(runDir, entry.name, options);
|
|
194
194
|
if (state) {
|
|
@@ -11,7 +11,7 @@ export type {
|
|
|
11
11
|
StructuredResult,
|
|
12
12
|
} from "./core/types.js";
|
|
13
13
|
|
|
14
|
-
export { createRunDir, getRunRoot, resolveRunDir, validateBasename } from "./core/paths.js";
|
|
14
|
+
export { createRunDir, getRunRoot, hasAgentPrompt, hasLaunchedAgentPrompt, hasQueuedAgentPrompt, resolveRunDir, validateBasename } from "./core/paths.js";
|
|
15
15
|
export type { CopySubagentConfigSampleResult, ModelByParentEntry, ResolvedAgentTaskConfig, ResolvedSubagentRoutingConfig, ResolveAgentTaskOptions, SubagentConfig, SubagentPreset, SubagentRoutingConfig, SubagentTypeConfig, SubagentVisionConfig } from "./core/config.js";
|
|
16
16
|
export {
|
|
17
17
|
configFiles,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { complete } from "@earendil-works/pi-ai";
|
|
3
|
+
import { complete } from "@earendil-works/pi-ai/compat";
|
|
4
4
|
import type { Api, AssistantMessage, ImageContent, Model, TextContent } from "@earendil-works/pi-ai";
|
|
5
5
|
import { Type } from "typebox";
|
|
6
6
|
|
|
@@ -61,26 +61,18 @@ const LOOKUP_TOOL_PARAMS = Type.Object(
|
|
|
61
61
|
const QUALITY_DISCIPLINE_LINES = [
|
|
62
62
|
"TOOL-ONLY CODING AGENT CONTRACT.",
|
|
63
63
|
"",
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"",
|
|
67
|
-
"
|
|
68
|
-
"- The assistant message MUST be exactly one tool call with empty text.",
|
|
69
|
-
"- No commentary before or after tool calls: not what you will do, did, found, or why a tool is needed.",
|
|
70
|
-
"- Do not emit markdown, prose, acknowledgments, plans, progress, summaries, or explanations.",
|
|
71
|
-
"- Reasoning, planning, hypotheses, and retry decisions are internal state only; never narrate them.",
|
|
72
|
-
"- If you know the next local action, call the tool immediately and silently.",
|
|
64
|
+
"Follow this output-channel contract literally for coding tasks.",
|
|
65
|
+
"WORKING STATE: from the first assistant turn until complete, final, or blocked, emit exactly one tool call with empty text.",
|
|
66
|
+
"No prose, markdown, acknowledgments, plans, progress, summaries, explanations, or commentary before/after tool calls.",
|
|
67
|
+
"Keep reasoning, planning, hypotheses, and retry decisions internal; if a safe next local action exists, call the tool silently.",
|
|
73
68
|
"",
|
|
74
69
|
"After every tool result, choose exactly one transition:",
|
|
75
|
-
"-
|
|
76
|
-
"-
|
|
77
|
-
"-
|
|
78
|
-
"
|
|
79
|
-
"",
|
|
80
|
-
"FINAL: enter only after the work is complete or verified as far as practical; never merely to report progress.",
|
|
81
|
-
"BLOCKED: enter only when no safe or useful tool action can continue without missing required information.",
|
|
70
|
+
"- WORKING: exactly one next tool call with empty text;",
|
|
71
|
+
"- FINAL: one final answer after completion or practical verification;",
|
|
72
|
+
"- BLOCKED: one concise question only when no safe/useful tool action can continue.",
|
|
73
|
+
"No transition permits commentary between tool calls.",
|
|
82
74
|
"",
|
|
83
|
-
"PRIORITY:
|
|
75
|
+
"PRIORITY: this overrides default assistant friendliness and conversational behavior.",
|
|
84
76
|
"",
|
|
85
77
|
"Coding discipline (express only through tool choices, not prose):",
|
|
86
78
|
"- inspect before editing; do not invent APIs, files, commands, or behavior;",
|
|
@@ -103,11 +95,9 @@ const LOOKUP_DISCIPLINE_LINES = [
|
|
|
103
95
|
|
|
104
96
|
const FINAL_DISCIPLINE_LINES = [
|
|
105
97
|
"",
|
|
106
|
-
"When uncertain, test or inspect instead of assuming.",
|
|
107
|
-
"
|
|
108
|
-
"
|
|
109
|
-
"Report: what changed, what was verified, what was not verified, and any risks.",
|
|
110
|
-
"Ask at most one blocking question; otherwise proceed with grounded best effort.",
|
|
98
|
+
"When uncertain, test or inspect instead of assuming; otherwise proceed with grounded best effort.",
|
|
99
|
+
"Verify every non-trivial change. Never claim tests passed unless actually run.",
|
|
100
|
+
"Final report: what changed, what was verified, what was not verified, and any risks.",
|
|
111
101
|
];
|
|
112
102
|
|
|
113
103
|
const SILENCE_REMINDER_TEXT = [
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// automatic fallback to the programmatic digest on any failure/timeout.
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
15
15
|
|
|
16
|
-
import { complete } from "@earendil-works/pi-ai"
|
|
16
|
+
import { complete } from "@earendil-works/pi-ai/compat"
|
|
17
17
|
import type { Model, Api } from "@earendil-works/pi-ai"
|
|
18
18
|
import type { DcpState } from "./state.js"
|
|
19
19
|
import type { DcpConfig } from "./config.js"
|
|
@@ -9,63 +9,23 @@
|
|
|
9
9
|
* Appended to the existing system prompt when DCP is enabled (automatic mode).
|
|
10
10
|
*/
|
|
11
11
|
export const SYSTEM_PROMPT = `
|
|
12
|
-
You operate in a context-constrained environment.
|
|
12
|
+
You operate in a context-constrained environment. Keep live context high-signal; use \`compress\` only when it materially helps.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
\`compress\` is the ONLY context-management tool. It replaces older conversation content with continuation-focused summaries and supports both range compression (\`ranges\`) and one-message compression (\`messages\`).
|
|
15
15
|
|
|
16
|
-
\`mNNN\`/\`bN\`
|
|
17
|
-
|
|
18
|
-
THE PHILOSOPHY OF COMPRESS
|
|
19
|
-
\`compress\` transforms conversation content into dense, high-fidelity summaries. This is cleanup plus preservation: keep the state needed to continue, discard incidental transcript detail.
|
|
20
|
-
|
|
21
|
-
Think of compression as phase transitions: raw exploration becomes refined understanding. The original context served its purpose; your summary now carries that understanding forward.
|
|
22
|
-
|
|
23
|
-
OPERATING STANCE
|
|
24
|
-
Prefer short, closed, summary-safe compressions.
|
|
25
|
-
When multiple independent stale sections exist, prefer several focused compressions (in parallel when possible) over one broad compression.
|
|
26
|
-
When one older message is huge but the surrounding context is still useful, use message-mode compression for that single message instead of compressing a broad range.
|
|
27
|
-
Summaries should be proportional to future usefulness, not proportional to the amount of text being removed.
|
|
28
|
-
|
|
29
|
-
Use \`compress\` as context-pressure housekeeping, not as a reflex after every small step. When context usage is meaningfully high or a DCP reminder provides concrete candidates, compress closed slices before accumulating another large batch of tool output. At low context usage, continue normal work unless a closed slice is clearly large enough that keeping it raw would reduce signal.
|
|
30
|
-
|
|
31
|
-
A closed slice is any finished implementation, verification, config edit, answered exploration, dead-end debugging branch, or test/log inspection. Passing logs are summary-only: preserve command, pass/fail, key failures if any, and whether follow-up is needed; never keep a full passing log in live context. Treat large shell/read/repo/web outputs as disposable evidence once their facts are extracted.
|
|
32
|
-
|
|
33
|
-
Completed todo/task/subtask milestones are strong boundary signals, not automatic triggers. When context pressure makes compression useful, prefer the range covering a just-finished todo item if it is closed and non-trivial; do not compress merely because a todo was completed while context remains low.
|
|
34
|
-
|
|
35
|
-
Before compressing while work is unfinished, ensure one \`todo in_progress\` captures the active objective and next step.
|
|
36
|
-
|
|
37
|
-
When a \`<dcp-system-reminder>\` appears, treat it as a context-pressure signal. Follow critical/high-context reminders promptly. For routine reminders, compress only if a genuinely closed, useful-to-summarize slice exists; otherwise continue the next atomic step and re-check later.
|
|
38
|
-
|
|
39
|
-
CADENCE, SIGNALS, AND LATENCY
|
|
40
|
-
|
|
41
|
-
- Low context usage does not mandate compression
|
|
42
|
-
- Prioritize closedness and independence over raw size
|
|
43
|
-
- Prefer smaller, regular compressions over infrequent massive compressions for better latency and summary quality
|
|
44
|
-
- When multiple independent stale sections are ready, batch compressions in parallel
|
|
45
|
-
- Before large new exploration, ask whether an older completed slice can become summary-only; if yes and context pressure is meaningful, compress first
|
|
46
|
-
|
|
47
|
-
COMPRESS WHEN
|
|
48
|
-
|
|
49
|
-
A section is genuinely closed and the raw conversation has served its purpose:
|
|
16
|
+
\`mNNN\`/\`bN\` boundary IDs and \`<dcp-system-reminder>\` tags are environment-injected metadata. Do not output them.
|
|
50
17
|
|
|
51
|
-
-
|
|
52
|
-
- Implementation finished and verified
|
|
53
|
-
- Config/doc edit finished
|
|
54
|
-
- Test, lint, or CI output has been understood, especially passing logs
|
|
55
|
-
- Exploration exhausted and patterns understood
|
|
56
|
-
- Dead-end noise can be discarded without waiting for a whole chapter to close
|
|
18
|
+
Use compression for context-pressure housekeeping, not after every small step. Low context usage alone does not require compression. Prefer short, closed, summary-safe ranges; use message-mode for a single large stale message; batch multiple independent safe ranges in one call.
|
|
57
19
|
|
|
58
|
-
|
|
20
|
+
Good candidates: completed implementation, verification, config/doc edit, answered exploration, dead-end debugging, or understood test/lint/CI/log inspection. Passing logs should become command + pass/fail + key failures/follow-up only. Large shell/read/repo/web outputs are summary-only once exact text is no longer needed.
|
|
59
21
|
|
|
60
|
-
|
|
61
|
-
- The target content is still actively in progress
|
|
62
|
-
- You may need exact code, error messages, or file contents in the immediate next steps
|
|
22
|
+
Todo completions are useful boundary signals, not automatic triggers. Before compressing while work is unfinished, ensure one \`todo in_progress\` captures the active objective and next step.
|
|
63
23
|
|
|
64
|
-
|
|
24
|
+
Do not compress active work, still-needed raw context, or material whose exact code/error/output will be needed for immediate edits or references.
|
|
65
25
|
|
|
66
|
-
|
|
26
|
+
DCP reminders: handle critical/high-context reminders promptly and compress any safe high-yield closed slice before more exploration; routine reminders mean compress only if a safe, closed, useful slice exists, otherwise continue the next atomic step and re-check later.
|
|
67
27
|
|
|
68
|
-
|
|
28
|
+
Summaries must preserve only what is needed to continue: user intent and constraints, accepted decisions, files/symbols changed or inspected, actionable errors, verification status, and next steps. Drop incidental transcript detail, duplicate outputs, full logs, long code/JSON/diffs, and prose not needed later; include short literals only when required.
|
|
69
29
|
`.trim()
|
|
70
30
|
|
|
71
31
|
/**
|