pi-ui-extend 0.1.19 → 0.1.21
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/app.d.ts +3 -0
- package/dist/app/app.js +68 -8
- package/dist/app/constants.js +1 -1
- package/dist/app/extensions/extension-ui-controller.js +2 -2
- package/dist/app/input/voice-controller.d.ts +3 -2
- package/dist/app/input/voice-controller.js +9 -0
- package/dist/app/rendering/conversation-entry-renderer.js +39 -9
- package/dist/app/rendering/conversation-tool-renderer.js +1 -1
- package/dist/app/rendering/conversation-viewport.d.ts +1 -5
- package/dist/app/rendering/conversation-viewport.js +9 -16
- package/dist/app/rendering/editor-layout-renderer.js +5 -5
- package/dist/app/rendering/render-controller.js +14 -24
- package/dist/app/rendering/status-line-renderer.d.ts +2 -0
- package/dist/app/rendering/status-line-renderer.js +75 -29
- package/dist/app/rendering/tool-block-renderer.d.ts +2 -0
- package/dist/app/rendering/tool-block-renderer.js +13 -1
- package/dist/app/runtime.d.ts +2 -0
- package/dist/app/runtime.js +27 -4
- package/dist/app/screen/mouse-controller.d.ts +1 -1
- package/dist/app/screen/mouse-controller.js +9 -3
- package/dist/app/screen/screen-styler.js +3 -3
- package/dist/app/session/session-lifecycle-controller.d.ts +2 -0
- package/dist/app/session/session-lifecycle-controller.js +43 -16
- package/dist/app/session/tabs-controller.d.ts +1 -1
- package/dist/app/session/tabs-controller.js +3 -7
- package/dist/app/types.d.ts +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.js +19 -7
- package/dist/markdown-format.d.ts +2 -0
- package/dist/markdown-format.js +5 -2
- package/dist/syntax-highlight.js +3 -1
- package/dist/theme.d.ts +11 -0
- package/dist/theme.js +56 -15
- package/extensions/question/tui.ts +1 -1
- package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +6 -1
- package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +1 -1
- package/external/pi-tools-suite/src/glm-coding-discipline/index.ts +123 -20
- package/package.json +1 -1
package/dist/markdown-format.js
CHANGED
|
@@ -46,6 +46,7 @@ export function renderMarkdownLine(text, start = 0) {
|
|
|
46
46
|
let rendered = text.slice(0, safeStart);
|
|
47
47
|
let index = safeStart;
|
|
48
48
|
let inCode = false;
|
|
49
|
+
const isHeading = /^\s{0,3}#{1,6}\s/.test(text);
|
|
49
50
|
while (index < text.length) {
|
|
50
51
|
const char = text[index] ?? "";
|
|
51
52
|
if (char === "`" && !isEscaped(text, index)) {
|
|
@@ -67,7 +68,7 @@ export function renderMarkdownLine(text, start = 0) {
|
|
|
67
68
|
rendered += char;
|
|
68
69
|
index += 1;
|
|
69
70
|
}
|
|
70
|
-
return { text: rendered, segments };
|
|
71
|
+
return { text: rendered, segments, ...(isHeading ? { heading: true } : {}) };
|
|
71
72
|
}
|
|
72
73
|
export function renderMarkdownTextLines(text, width, start = 0) {
|
|
73
74
|
const lines = [];
|
|
@@ -80,12 +81,14 @@ export function renderMarkdownTextLines(text, width, start = 0) {
|
|
|
80
81
|
const closesFence = Boolean(fence && nextFence && fence.marker === nextFence.marker && nextFence.length >= fence.length);
|
|
81
82
|
const opensFence = !fence && nextFence !== undefined;
|
|
82
83
|
const syntaxHighlight = markdownLineSyntaxHighlight(fence, Boolean(opensFence || closesFence), start);
|
|
83
|
-
const
|
|
84
|
+
const isHeadingLine = !fence && /^\s{0,3}#{1,6}\s/.test(rawLine);
|
|
85
|
+
const markdownLine = syntaxHighlight?.language === "markdown" || isHeadingLine ? renderMarkdownLine(rawLine) : undefined;
|
|
84
86
|
for (const wrapped of wrapRenderedMarkdownLine(markdownLine ?? { text: rawLine, segments: [] }, width)) {
|
|
85
87
|
lines.push({
|
|
86
88
|
text: wrapped.text,
|
|
87
89
|
...(wrapped.segments.length > 0 ? { segments: wrapped.segments } : {}),
|
|
88
90
|
...(syntaxHighlight ? { syntaxHighlight } : {}),
|
|
91
|
+
...(isHeadingLine ? { heading: true } : {}),
|
|
89
92
|
});
|
|
90
93
|
}
|
|
91
94
|
if (opensFence && nextFence) {
|
package/dist/syntax-highlight.js
CHANGED
|
@@ -44,6 +44,8 @@ const MARKDOWN_FENCE_LANGUAGES = {
|
|
|
44
44
|
javascript: "javascript",
|
|
45
45
|
js: "javascript",
|
|
46
46
|
json: "json",
|
|
47
|
+
markdown: "markdown",
|
|
48
|
+
md: "markdown",
|
|
47
49
|
jsonc: "json",
|
|
48
50
|
jsx: "javascript",
|
|
49
51
|
python: "python",
|
|
@@ -227,7 +229,7 @@ function markdownSegments(code, colors) {
|
|
|
227
229
|
const segments = [];
|
|
228
230
|
const heading = /^(\s{0,3}#{1,6}\s+)(.*)$/.exec(code);
|
|
229
231
|
if (heading) {
|
|
230
|
-
|
|
232
|
+
segments.push({ start: 0, end: code.length, foreground: colors.heading, bold: true });
|
|
231
233
|
return segments;
|
|
232
234
|
}
|
|
233
235
|
const fence = /^\s*`{3,}/.exec(code);
|
package/dist/theme.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export type Theme = {
|
|
|
17
17
|
tabBorder: string;
|
|
18
18
|
assistantMessageBackground: string;
|
|
19
19
|
userMessageBackground: string;
|
|
20
|
+
thinkingMessageBackground: string;
|
|
20
21
|
inputCursorBackground: string;
|
|
21
22
|
popupForeground: string;
|
|
22
23
|
popupBackground: string;
|
|
@@ -32,10 +33,19 @@ export type Theme = {
|
|
|
32
33
|
accent: string;
|
|
33
34
|
success: string;
|
|
34
35
|
warning: string;
|
|
36
|
+
heading: string;
|
|
35
37
|
info: string;
|
|
36
38
|
toolMutation: string;
|
|
37
39
|
toolSearch: string;
|
|
38
40
|
toolTitle: string;
|
|
41
|
+
toolBash: string;
|
|
42
|
+
toolRead: string;
|
|
43
|
+
toolIndex: string;
|
|
44
|
+
toolEdit: string;
|
|
45
|
+
toolWeb: string;
|
|
46
|
+
toolMeta: string;
|
|
47
|
+
thinkingForeground: string;
|
|
48
|
+
userForeground: string;
|
|
39
49
|
thinkingXHigh: string;
|
|
40
50
|
modelOpenAI: string;
|
|
41
51
|
statusDotBase: string;
|
|
@@ -55,5 +65,6 @@ export type TextStyleOptions = {
|
|
|
55
65
|
};
|
|
56
66
|
export declare function parseThemeName(value: string): ThemeName | undefined;
|
|
57
67
|
export declare function colorize(text: string, options: TextStyleOptions): string;
|
|
68
|
+
export declare function ansiStylePrefix(options: TextStyleOptions): string;
|
|
58
69
|
export declare function colorLine(text: string, width: number, options: TextStyleOptions): string;
|
|
59
70
|
export declare function padOrTrimPlain(text: string, width: number): string;
|
package/dist/theme.js
CHANGED
|
@@ -5,19 +5,20 @@ export const THEMES = {
|
|
|
5
5
|
colors: {
|
|
6
6
|
background: "#090d13",
|
|
7
7
|
foreground: "#d6deeb",
|
|
8
|
-
assistantForeground: "#
|
|
8
|
+
assistantForeground: "#a4bce0",
|
|
9
9
|
muted: "#7d8590",
|
|
10
10
|
headerForeground: "#c9d1d9",
|
|
11
11
|
headerBackground: "#161b22",
|
|
12
12
|
statusForeground: "#8b949e",
|
|
13
|
-
statusBackground: "#
|
|
13
|
+
statusBackground: "#0f1520",
|
|
14
14
|
inputForeground: "#f0f6fc",
|
|
15
15
|
inputBackground: "#090d13",
|
|
16
16
|
inputBorder: "#30363d",
|
|
17
17
|
inputBorderWidgetBackground: "#2a2f36",
|
|
18
18
|
tabBorder: "#7d8590",
|
|
19
|
-
assistantMessageBackground: "
|
|
20
|
-
userMessageBackground: "
|
|
19
|
+
assistantMessageBackground: "",
|
|
20
|
+
userMessageBackground: "",
|
|
21
|
+
thinkingMessageBackground: "",
|
|
21
22
|
inputCursorBackground: "#7fb3c8",
|
|
22
23
|
popupForeground: "#e6edf3",
|
|
23
24
|
popupBackground: "#1e1e1e",
|
|
@@ -33,10 +34,19 @@ export const THEMES = {
|
|
|
33
34
|
accent: "#7aa2d6",
|
|
34
35
|
success: "#7ca982",
|
|
35
36
|
warning: "#d49a4a",
|
|
37
|
+
heading: "#d4b35e",
|
|
36
38
|
info: "#7fb3c8",
|
|
37
|
-
toolMutation: "#
|
|
38
|
-
toolSearch: "#
|
|
39
|
-
toolTitle: "#
|
|
39
|
+
toolMutation: "#b8899e", // ~10% brighter from #a67c8f
|
|
40
|
+
toolSearch: "#9d8abb", // ~10% brighter from #8c7aa8
|
|
41
|
+
toolTitle: "#899199", // ~10% brighter from #7b848c
|
|
42
|
+
toolBash: "#b89071", // ~10% brighter from #a68266
|
|
43
|
+
toolRead: "#6d9b82", // ~10% brighter from #628c76
|
|
44
|
+
toolIndex: "#7692b4", // ~10% brighter from #6a84a3
|
|
45
|
+
toolEdit: "#b47389", // ~10% brighter from #a3687c
|
|
46
|
+
toolWeb: "#8192b6", // ~10% brighter from #7584a6
|
|
47
|
+
toolMeta: "#7d8192", // ~10% brighter from #707485
|
|
48
|
+
thinkingForeground: "#64748b",
|
|
49
|
+
userForeground: "#d97706",
|
|
40
50
|
thinkingXHigh: "#ff8a86",
|
|
41
51
|
modelOpenAI: "#c8b45a",
|
|
42
52
|
statusDotBase: "#30363d",
|
|
@@ -55,14 +65,15 @@ export const THEMES = {
|
|
|
55
65
|
headerForeground: "#0f172a",
|
|
56
66
|
headerBackground: "#e2e8f0",
|
|
57
67
|
statusForeground: "#475569",
|
|
58
|
-
statusBackground: "#
|
|
68
|
+
statusBackground: "#edf0f4",
|
|
59
69
|
inputForeground: "#0f172a",
|
|
60
70
|
inputBackground: "#f8fafc",
|
|
61
71
|
inputBorder: "#334155",
|
|
62
72
|
inputBorderWidgetBackground: "#f1f5f9",
|
|
63
73
|
tabBorder: "#64748b",
|
|
64
|
-
assistantMessageBackground: "
|
|
65
|
-
userMessageBackground: "
|
|
74
|
+
assistantMessageBackground: "",
|
|
75
|
+
userMessageBackground: "",
|
|
76
|
+
thinkingMessageBackground: "",
|
|
66
77
|
inputCursorBackground: "#0284c7",
|
|
67
78
|
popupForeground: "#0f172a",
|
|
68
79
|
popupBackground: "#ffffff",
|
|
@@ -78,10 +89,19 @@ export const THEMES = {
|
|
|
78
89
|
accent: "#315f9f",
|
|
79
90
|
success: "#47794c",
|
|
80
91
|
warning: "#9a631d",
|
|
92
|
+
heading: "#b88a28",
|
|
81
93
|
info: "#246b8e",
|
|
82
|
-
toolMutation: "#
|
|
83
|
-
toolSearch: "#
|
|
84
|
-
toolTitle: "#
|
|
94
|
+
toolMutation: "#8c5c70", // reverted
|
|
95
|
+
toolSearch: "#6e608c", // reverted
|
|
96
|
+
toolTitle: "#626c78", // reverted
|
|
97
|
+
toolBash: "#8c7556", // reverted
|
|
98
|
+
toolRead: "#507a62", // reverted
|
|
99
|
+
toolIndex: "#567a96", // reverted
|
|
100
|
+
toolEdit: "#8c586c", // reverted
|
|
101
|
+
toolWeb: "#5c6a8c", // reverted
|
|
102
|
+
toolMeta: "#787e8a", // reverted
|
|
103
|
+
thinkingForeground: "#6b5491",
|
|
104
|
+
userForeground: "#854d0e",
|
|
85
105
|
thinkingXHigh: "#cf333d",
|
|
86
106
|
modelOpenAI: "#75671f",
|
|
87
107
|
statusDotBase: "#334155",
|
|
@@ -92,10 +112,20 @@ export const THEMES = {
|
|
|
92
112
|
},
|
|
93
113
|
};
|
|
94
114
|
export const ANSI_RESET = "\x1b[0m";
|
|
115
|
+
const rgbCodeCache = new Map();
|
|
116
|
+
const stylePrefixCache = new Map();
|
|
95
117
|
export function parseThemeName(value) {
|
|
96
118
|
return value === "dark" || value === "light" ? value : undefined;
|
|
97
119
|
}
|
|
98
120
|
export function colorize(text, options) {
|
|
121
|
+
const prefix = ansiStylePrefix(options);
|
|
122
|
+
return prefix ? `${prefix}${text}${ANSI_RESET}` : text;
|
|
123
|
+
}
|
|
124
|
+
export function ansiStylePrefix(options) {
|
|
125
|
+
const cacheKey = styleCacheKey(options);
|
|
126
|
+
const cached = stylePrefixCache.get(cacheKey);
|
|
127
|
+
if (cached !== undefined)
|
|
128
|
+
return cached;
|
|
99
129
|
const codes = [];
|
|
100
130
|
if (options.bold)
|
|
101
131
|
codes.push("1");
|
|
@@ -107,7 +137,9 @@ export function colorize(text, options) {
|
|
|
107
137
|
codes.push(rgbCode("38", options.foreground));
|
|
108
138
|
if (options.background)
|
|
109
139
|
codes.push(rgbCode("48", options.background));
|
|
110
|
-
|
|
140
|
+
const prefix = codes.length === 0 ? "" : `\x1b[${codes.join(";")}m`;
|
|
141
|
+
stylePrefixCache.set(cacheKey, prefix);
|
|
142
|
+
return prefix;
|
|
111
143
|
}
|
|
112
144
|
export function colorLine(text, width, options) {
|
|
113
145
|
return colorize(padOrTrimPlain(text, width), options);
|
|
@@ -117,8 +149,17 @@ export function padOrTrimPlain(text, width) {
|
|
|
117
149
|
}
|
|
118
150
|
function rgbCode(prefix, hex) {
|
|
119
151
|
const normalized = hex.replace(/^#/, "");
|
|
152
|
+
const cacheKey = `${prefix}:${normalized}`;
|
|
153
|
+
const cached = rgbCodeCache.get(cacheKey);
|
|
154
|
+
if (cached)
|
|
155
|
+
return cached;
|
|
120
156
|
const red = Number.parseInt(normalized.slice(0, 2), 16);
|
|
121
157
|
const green = Number.parseInt(normalized.slice(2, 4), 16);
|
|
122
158
|
const blue = Number.parseInt(normalized.slice(4, 6), 16);
|
|
123
|
-
|
|
159
|
+
const code = `${prefix};2;${red};${green};${blue}`;
|
|
160
|
+
rgbCodeCache.set(cacheKey, code);
|
|
161
|
+
return code;
|
|
162
|
+
}
|
|
163
|
+
function styleCacheKey(options) {
|
|
164
|
+
return `${options.bold ? 1 : 0}|${options.underline ? 1 : 0}|${options.strikethrough ? 1 : 0}|${options.foreground ?? ""}|${options.background ?? ""}`;
|
|
124
165
|
}
|
|
@@ -269,7 +269,7 @@ export async function runQuestionnaire(questions: NormalizedQuestion[], ctx: Que
|
|
|
269
269
|
}
|
|
270
270
|
|
|
271
271
|
function advanceAfterAnswer(): void {
|
|
272
|
-
if (
|
|
272
|
+
if (questions.length === 1) {
|
|
273
273
|
submitCompleteSelections();
|
|
274
274
|
return;
|
|
275
275
|
}
|
|
@@ -96,6 +96,9 @@ export function getGoogleOAuthClientCredentials(...sources: Array<unknown>): Goo
|
|
|
96
96
|
const clientSecret = nestedClientSecret ?? stringProperty(source, ["clientSecret", "client_secret", "googleClientSecret", "google_client_secret", "oauthClientSecret", "oauth_client_secret"]);
|
|
97
97
|
if (clientId) return { clientId, ...(clientSecret ? { clientSecret } : {}) };
|
|
98
98
|
}
|
|
99
|
+
const clientId = process.env.PI_ANTIGRAVITY_GOOGLE_CLIENT_ID;
|
|
100
|
+
const clientSecret = process.env.PI_ANTIGRAVITY_GOOGLE_CLIENT_SECRET;
|
|
101
|
+
if (clientId) return { clientId, ...(clientSecret ? { clientSecret } : {}) };
|
|
99
102
|
return undefined;
|
|
100
103
|
}
|
|
101
104
|
|
|
@@ -215,13 +218,15 @@ export async function importOpencodeAntigravityAccount(options: {
|
|
|
215
218
|
};
|
|
216
219
|
}
|
|
217
220
|
|
|
221
|
+
const oauthClient = getGoogleOAuthClientCredentials(selected.account, existing);
|
|
218
222
|
piAuth[PROVIDER_ID] = {
|
|
223
|
+
...existing,
|
|
219
224
|
type: "oauth",
|
|
220
225
|
refresh,
|
|
221
226
|
access: "",
|
|
222
227
|
expires: 0,
|
|
223
228
|
email: selected.account.email,
|
|
224
|
-
...
|
|
229
|
+
...(oauthClient ? { oauthClient } : {}),
|
|
225
230
|
accounts: storage.accounts.filter((account) => account.enabled !== false && getAccountRefreshToken(account)),
|
|
226
231
|
activeIndex: selected.index,
|
|
227
232
|
};
|
|
@@ -224,7 +224,7 @@ export async function addAntigravityAccount(
|
|
|
224
224
|
async function refreshAccountToken(account: OpencodeAntigravityAccount, oauthClient?: GoogleOAuthClientCredentials): Promise<RefreshedAntigravityAccount> {
|
|
225
225
|
const refreshToken = getAccountRefreshToken(account);
|
|
226
226
|
if (!refreshToken) throw new Error(`Missing refresh token for Antigravity account ${account.email ?? "<unknown>"}`);
|
|
227
|
-
const clientCredentials = getGoogleOAuthClientCredentials(account
|
|
227
|
+
const clientCredentials = getGoogleOAuthClientCredentials(account, oauthClient);
|
|
228
228
|
assertGoogleOAuthCredentialsConfigured(clientCredentials);
|
|
229
229
|
const projectId = getAccountProjectId(account);
|
|
230
230
|
const start = Date.now();
|
|
@@ -38,6 +38,8 @@ const DEFAULT_LOOKUP_MAX_IMAGES = 6;
|
|
|
38
38
|
const DEFAULT_LOOKUP_MAX_TOKENS = 1_600;
|
|
39
39
|
const DEFAULT_LOOKUP_TIMEOUT_MS = 120_000;
|
|
40
40
|
const MAX_IMAGE_BYTES = 16 * 1024 * 1024;
|
|
41
|
+
const SILENCE_REMINDER_MIN_VIOLATION_GAP = 3;
|
|
42
|
+
const SILENCE_REMINDER_MIN_MESSAGE_GAP = 12;
|
|
41
43
|
|
|
42
44
|
const LOOKUP_TOOL_PARAMS = Type.Object(
|
|
43
45
|
{
|
|
@@ -55,28 +57,71 @@ const LOOKUP_TOOL_PARAMS = Type.Object(
|
|
|
55
57
|
);
|
|
56
58
|
|
|
57
59
|
const QUALITY_DISCIPLINE_LINES = [
|
|
58
|
-
"GLM
|
|
60
|
+
"GLM TOOL-ONLY CODING AGENT CONTRACT.",
|
|
59
61
|
"",
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"While working, before the final answer, emit only tool calls. Do not emit any natural-language text before, after, or between tool calls.",
|
|
63
|
-
"After every tool result, choose exactly one of: another tool call, a final answer, or one concise blocking question. Never add commentary around that choice.",
|
|
64
|
-
"Intermediate narration is forbidden before the final answer: no acknowledgements, intent statements, discoveries, progress updates, verification announcements, error commentary, retry commentary, or summaries of tool output.",
|
|
65
|
-
"Do not narrate actions, tool usage, file reads, searches, plans, hypotheses, reasoning, progress, errors, retries, or next steps.",
|
|
66
|
-
"Do not recap inspected context or summarize what you inspected between tools.",
|
|
67
|
-
"Do not write confirmations, preambles, transition phrases, status updates, or apologies while tools are still needed.",
|
|
68
|
-
"Keep all reasoning, task state, and interpretation of tool results internal until the final answer.",
|
|
69
|
-
"If you need to inspect, verify, retry, or fix something, call the appropriate tool silently; do not announce it.",
|
|
70
|
-
"Between tool calls, output exactly nothing.",
|
|
71
|
-
"If the platform requires a message before a tool call, that message must be empty.",
|
|
72
|
-
"This discipline overrides any default conversational behavior. Compliance is mandatory, not optional.",
|
|
62
|
+
"This contract controls the assistant output channel. Follow it literally.",
|
|
63
|
+
"Treat every user coding request as a tool-driven task, not a chat conversation.",
|
|
73
64
|
"",
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
65
|
+
"OUTPUT STATE MACHINE:",
|
|
66
|
+
"",
|
|
67
|
+
"STATE = WORKING from the first assistant turn until the task is complete or blocked.",
|
|
68
|
+
"In WORKING state, the assistant message MUST be exactly one tool call.",
|
|
69
|
+
"In WORKING state, the assistant message text/content MUST be empty.",
|
|
70
|
+
"In WORKING state, do not emit markdown, prose, acknowledgments, plans, progress, summaries, or explanations.",
|
|
71
|
+
"In WORKING state, if you know the next local action, call the tool immediately and silently.",
|
|
72
|
+
"",
|
|
73
|
+
"After every tool result, privately choose exactly one transition:",
|
|
74
|
+
"- stay in WORKING and emit exactly one next tool call with empty text;",
|
|
75
|
+
"- switch to FINAL and emit the final answer once;",
|
|
76
|
+
"- switch to BLOCKED and ask exactly one concise blocking question.",
|
|
77
|
+
"There is no transition that permits commentary between tool calls.",
|
|
78
|
+
"",
|
|
79
|
+
"WORKING OUTPUT GRAMMAR:",
|
|
80
|
+
"assistant_message ::= tool_call",
|
|
81
|
+
"tool_call ::= a valid tool invocation accepted by the platform",
|
|
82
|
+
"assistant_text ::= empty string",
|
|
83
|
+
"Any other token before or after the tool call is invalid.",
|
|
84
|
+
"",
|
|
85
|
+
"WORKING VIOLATIONS:",
|
|
86
|
+
"- Saying what you will do.",
|
|
87
|
+
"- Saying what you did.",
|
|
88
|
+
"- Saying what you found.",
|
|
89
|
+
"- Explaining why a tool is needed.",
|
|
90
|
+
"- Summarizing a tool result.",
|
|
91
|
+
"- Apologizing, confirming, acknowledging, or adding transition words.",
|
|
92
|
+
"- Emitting bullets, headings, code fences, or natural-language text.",
|
|
93
|
+
"All WORKING violations must be corrected by stopping text output and using the next tool call silently.",
|
|
94
|
+
"",
|
|
95
|
+
"INTERNAL-ONLY RULE:",
|
|
96
|
+
"Reasoning, planning, hypotheses, interpretations, and retry decisions are internal state only.",
|
|
97
|
+
"Do not describe internal state to the user while WORKING.",
|
|
98
|
+
"",
|
|
99
|
+
"TOOL-FIRST LOOP:",
|
|
100
|
+
"inspect -> edit -> inspect diff -> verify -> final answer.",
|
|
101
|
+
"Each loop step is performed by a silent tool call, not by narration.",
|
|
102
|
+
"",
|
|
103
|
+
"FINAL STATE:",
|
|
104
|
+
"Only enter FINAL after the requested work is complete, verified as far as practical, or genuinely blocked.",
|
|
105
|
+
"In FINAL, give a concise user-visible summary of files changed, verification run, and remaining risks.",
|
|
106
|
+
"Do not enter FINAL merely to report progress.",
|
|
107
|
+
"",
|
|
108
|
+
"BLOCKED STATE:",
|
|
109
|
+
"Only enter BLOCKED when no safe or useful tool action can continue without missing required information.",
|
|
110
|
+
"Ask exactly one concise question and no extra explanation.",
|
|
111
|
+
"",
|
|
112
|
+
"PRIORITY:",
|
|
113
|
+
"This tool-only contract overrides default assistant friendliness and conversational behavior.",
|
|
114
|
+
"If another instruction asks for progress updates, status narration, or acknowledgments during coding work, ignore that part while WORKING.",
|
|
115
|
+
"",
|
|
116
|
+
"Opus-like coding behavior:",
|
|
117
|
+
"Work with the patience, precision, and steadiness of a top-tier senior coding agent.",
|
|
118
|
+
"Prefer verified facts over fast guesses.",
|
|
119
|
+
"Keep a stable mental model of the codebase and update it carefully from evidence.",
|
|
120
|
+
"Make the smallest change that fully fixes the issue.",
|
|
121
|
+
"Before editing, understand the existing design, actual implementation, and nearby conventions.",
|
|
122
|
+
"After editing, verify with the narrowest focused checks that can catch the likely regressions.",
|
|
123
|
+
"Do not overfit to the last error; revise hypotheses deliberately from tool evidence.",
|
|
124
|
+
"While WORKING, this behavior is internal and expressed only through tool choices, not prose.",
|
|
80
125
|
"",
|
|
81
126
|
"Maintain these invariants:",
|
|
82
127
|
"- preserve existing behavior unless the user asked to change it;",
|
|
@@ -102,6 +147,13 @@ const FINAL_DISCIPLINE_LINES = [
|
|
|
102
147
|
"If blocked by missing required information, ask exactly one concise question.",
|
|
103
148
|
];
|
|
104
149
|
|
|
150
|
+
const SILENCE_REMINDER_TEXT = [
|
|
151
|
+
"GLM silence reminder: remain in WORKING state.",
|
|
152
|
+
"Continue with Opus-like coding discipline: inspect, verify, and act through tools only.",
|
|
153
|
+
"For the next step, emit exactly one tool call and no assistant text.",
|
|
154
|
+
"Do not acknowledge this reminder.",
|
|
155
|
+
].join("\n");
|
|
156
|
+
|
|
105
157
|
const LEGACY_SILENT_PROMPT_BLOCK_PATTERN = new RegExp(
|
|
106
158
|
`${escapeRegExp(SILENT_PROMPT_MARKER_START)}[\\s\\S]*?${escapeRegExp(SILENT_PROMPT_MARKER_END)}\\s*`,
|
|
107
159
|
"g",
|
|
@@ -124,6 +176,9 @@ const LOOKUP_SYSTEM_PROMPT = [
|
|
|
124
176
|
export default function glmCodingDiscipline(pi: ExtensionAPI) {
|
|
125
177
|
let selectedModelRef: string | undefined;
|
|
126
178
|
let lookupRegistered = false;
|
|
179
|
+
let silenceViolationCount = 0;
|
|
180
|
+
let lastReminderViolationCount = 0;
|
|
181
|
+
let lastReminderMessageCount = -SILENCE_REMINDER_MIN_MESSAGE_GAP;
|
|
127
182
|
|
|
128
183
|
function maybeRegisterLookupTool(cwd?: string): void {
|
|
129
184
|
if (lookupRegistered) return;
|
|
@@ -149,6 +204,25 @@ export default function glmCodingDiscipline(pi: ExtensionAPI) {
|
|
|
149
204
|
if (!isGlmModel(modelRef)) return undefined;
|
|
150
205
|
return injectCodingDisciplineIntoPayload(event.payload, { lookupEnabled: Boolean(lookupModelFromConfig(contextCwd(ctx))) });
|
|
151
206
|
});
|
|
207
|
+
|
|
208
|
+
pi.on("context", async (event: { messages?: unknown[] }, ctx: unknown) => {
|
|
209
|
+
const modelRef = selectedModelRef ?? modelRefFromContext(ctx);
|
|
210
|
+
if (!isGlmModel(modelRef) || !Array.isArray(event.messages)) return undefined;
|
|
211
|
+
|
|
212
|
+
const violationCount = countAssistantToolChatter(event.messages);
|
|
213
|
+
if (violationCount <= silenceViolationCount) return undefined;
|
|
214
|
+
|
|
215
|
+
const messageCount = event.messages.length;
|
|
216
|
+
const violationGap = violationCount - lastReminderViolationCount;
|
|
217
|
+
const messageGap = messageCount - lastReminderMessageCount;
|
|
218
|
+
silenceViolationCount = violationCount;
|
|
219
|
+
|
|
220
|
+
if (violationGap < SILENCE_REMINDER_MIN_VIOLATION_GAP && messageGap < SILENCE_REMINDER_MIN_MESSAGE_GAP) return undefined;
|
|
221
|
+
|
|
222
|
+
lastReminderViolationCount = violationCount;
|
|
223
|
+
lastReminderMessageCount = messageCount;
|
|
224
|
+
return { messages: [...event.messages, createSilenceReminderMessage()] };
|
|
225
|
+
});
|
|
152
226
|
}
|
|
153
227
|
|
|
154
228
|
export function prependCodingDisciplinePrompt(systemPrompt: string, options: { lookupEnabled?: boolean } = {}): string {
|
|
@@ -325,6 +399,35 @@ function isInstructionMessage(message: unknown): boolean {
|
|
|
325
399
|
return message.role === "system" || message.role === "developer";
|
|
326
400
|
}
|
|
327
401
|
|
|
402
|
+
function countAssistantToolChatter(messages: readonly unknown[]): number {
|
|
403
|
+
let count = 0;
|
|
404
|
+
for (const message of messages) {
|
|
405
|
+
if (!isAssistantToolChatter(message)) continue;
|
|
406
|
+
count++;
|
|
407
|
+
}
|
|
408
|
+
return count;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function isAssistantToolChatter(message: unknown): boolean {
|
|
412
|
+
if (!isRecord(message) || message.role !== "assistant") return false;
|
|
413
|
+
if (!Array.isArray(message.content)) return false;
|
|
414
|
+
const hasToolCall = message.content.some((part) => isRecord(part) && part.type === "toolCall");
|
|
415
|
+
if (!hasToolCall) return false;
|
|
416
|
+
return message.content.some((part) => isRecord(part) && part.type === "text" && hasNonEmptyText(part.text));
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function hasNonEmptyText(value: unknown): boolean {
|
|
420
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function createSilenceReminderMessage() {
|
|
424
|
+
return {
|
|
425
|
+
role: "user" as const,
|
|
426
|
+
content: [{ type: "text" as const, text: SILENCE_REMINDER_TEXT }],
|
|
427
|
+
timestamp: Date.now(),
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
328
431
|
function lookupModelFromConfig(cwd?: string): string | undefined {
|
|
329
432
|
return loadPiToolsSuiteConfig(["glm-coding-discipline"], { cwd: cwd ?? process.cwd() }).lookupModel;
|
|
330
433
|
}
|