pi-ui-extend 0.1.13 → 0.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/app/app.d.ts +7 -0
- package/dist/app/app.js +102 -17
- package/dist/app/commands/command-controller.js +2 -0
- package/dist/app/commands/command-host.d.ts +5 -0
- package/dist/app/commands/command-model-actions.d.ts +2 -0
- package/dist/app/commands/command-model-actions.js +40 -4
- package/dist/app/commands/command-navigation-actions.d.ts +9 -0
- package/dist/app/commands/command-navigation-actions.js +62 -0
- package/dist/app/commands/command-registry.d.ts +2 -0
- package/dist/app/commands/command-registry.js +16 -0
- package/dist/app/constants.d.ts +0 -1
- package/dist/app/constants.js +0 -1
- package/dist/app/extensions/extension-ui-controller.d.ts +16 -5
- package/dist/app/extensions/extension-ui-controller.js +99 -61
- package/dist/app/icons.d.ts +1 -0
- package/dist/app/icons.js +2 -0
- package/dist/app/input/input-action-controller.d.ts +2 -0
- package/dist/app/input/input-action-controller.js +8 -1
- package/dist/app/logger.d.ts +25 -0
- package/dist/app/logger.js +90 -0
- package/dist/app/model/model-usage-status.js +30 -15
- package/dist/app/popup/menu-items-controller.d.ts +4 -0
- package/dist/app/popup/menu-items-controller.js +68 -6
- package/dist/app/popup/popup-action-controller.d.ts +2 -1
- package/dist/app/popup/popup-action-controller.js +7 -4
- package/dist/app/popup/popup-menu-controller.d.ts +36 -23
- package/dist/app/popup/popup-menu-controller.js +97 -326
- package/dist/app/rendering/conversation-entry-renderer.js +3 -3
- package/dist/app/rendering/conversation-viewport.d.ts +10 -2
- package/dist/app/rendering/conversation-viewport.js +157 -16
- package/dist/app/rendering/editor-panels.js +22 -9
- package/dist/app/rendering/popup-menu-renderer.d.ts +62 -0
- package/dist/app/rendering/popup-menu-renderer.js +405 -0
- package/dist/app/rendering/render-controller.js +30 -28
- package/dist/app/rendering/render-text.js +5 -2
- package/dist/app/rendering/status-line-renderer.d.ts +8 -1
- package/dist/app/rendering/status-line-renderer.js +217 -117
- package/dist/app/rendering/toast-controller.d.ts +12 -3
- package/dist/app/rendering/toast-controller.js +70 -12
- package/dist/app/runtime.d.ts +2 -1
- package/dist/app/runtime.js +20 -10
- package/dist/app/screen/mouse-controller.d.ts +2 -2
- package/dist/app/screen/mouse-controller.js +27 -48
- package/dist/app/screen/screen-styler.d.ts +1 -1
- package/dist/app/screen/screen-styler.js +9 -7
- package/dist/app/screen/scroll-controller.d.ts +12 -9
- package/dist/app/screen/scroll-controller.js +56 -45
- package/dist/app/screen/status-controller.js +2 -1
- package/dist/app/session/lazy-session-manager.d.ts +11 -0
- package/dist/app/session/lazy-session-manager.js +539 -0
- package/dist/app/session/pix-system-message.d.ts +16 -0
- package/dist/app/session/pix-system-message.js +64 -0
- package/dist/app/session/request-history.d.ts +4 -0
- package/dist/app/session/request-history.js +11 -0
- package/dist/app/session/session-event-controller.d.ts +11 -0
- package/dist/app/session/session-event-controller.js +58 -2
- package/dist/app/session/session-history.d.ts +18 -0
- package/dist/app/session/session-history.js +72 -3
- package/dist/app/session/session-lifecycle-controller.d.ts +6 -2
- package/dist/app/session/session-lifecycle-controller.js +7 -2
- package/dist/app/session/session-search.js +10 -0
- package/dist/app/session/tabs-controller.d.ts +17 -5
- package/dist/app/session/tabs-controller.js +308 -29
- package/dist/app/todo/todo-model.d.ts +4 -2
- package/dist/app/todo/todo-model.js +23 -13
- package/dist/app/types.d.ts +17 -6
- package/dist/app/workspace/workspace-actions-controller.d.ts +2 -0
- package/dist/app/workspace/workspace-actions-controller.js +12 -0
- package/dist/config.d.ts +6 -1
- package/dist/config.js +82 -25
- package/dist/default-pix-config.js +4 -0
- package/dist/fuzzy.d.ts +2 -0
- package/dist/fuzzy.js +27 -7
- package/dist/input-editor.d.ts +9 -0
- package/dist/input-editor.js +52 -0
- package/dist/schemas/pi-tools-suite-schema.d.ts +1 -0
- package/dist/schemas/pi-tools-suite-schema.js +1 -0
- package/dist/schemas/pix-schema.d.ts +3 -1
- package/dist/schemas/pix-schema.js +6 -4
- package/dist/terminal-width.d.ts +2 -0
- package/dist/terminal-width.js +64 -3
- package/dist/theme.js +6 -6
- package/dist/ui.d.ts +8 -0
- package/external/pi-tools-suite/README.md +3 -2
- package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +52 -8
- package/external/pi-tools-suite/src/antigravity-auth/commands.ts +3 -41
- package/external/pi-tools-suite/src/antigravity-auth/constants.ts +0 -2
- package/external/pi-tools-suite/src/antigravity-auth/index.ts +11 -18
- package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +129 -61
- package/external/pi-tools-suite/src/antigravity-auth/status.ts +82 -3
- package/external/pi-tools-suite/src/antigravity-auth/stream.ts +20 -7
- package/external/pi-tools-suite/src/antigravity-auth/types.ts +21 -0
- package/external/pi-tools-suite/src/config.ts +8 -0
- package/external/pi-tools-suite/src/dcp/index.ts +16 -1
- package/external/pi-tools-suite/src/dcp/state.ts +35 -0
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +3 -0
- package/external/pi-tools-suite/src/todo/index.ts +123 -14
- package/external/pi-tools-suite/src/todo/state/persistence.ts +0 -1
- package/external/pi-tools-suite/src/todo/state/state-reducer.ts +26 -43
- package/external/pi-tools-suite/src/todo/todo.ts +12 -23
- package/external/pi-tools-suite/src/todo/tool/response-envelope.ts +34 -16
- package/external/pi-tools-suite/src/todo/tool/types.ts +7 -28
- package/external/pi-tools-suite/src/todo/view/format.ts +2 -3
- package/external/pi-tools-suite/src/tool-descriptions.ts +6 -4
- package/external/pi-tools-suite/src/usage/index.ts +5 -2
- package/external/pi-tools-suite/src/usage/lib/google.ts +53 -40
- package/external/pi-tools-suite/src/usage/lib/types.ts +12 -2
- package/package.json +1 -1
- package/schemas/pi-tools-suite.json +4 -0
- package/schemas/pix.json +11 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* TypeBox JSON Schema definitions for pix.jsonc (~/.config/pi/pix.jsonc).
|
|
2
|
+
* TypeBox JSON Schema definitions for pix.jsonc (~/.config/pi/pix.jsonc or <cwd>/.pi/pix.jsonc).
|
|
3
3
|
*
|
|
4
4
|
* These schemas describe the _user-facing_ config shape — all fields are optional
|
|
5
5
|
* because the runtime applies generous defaults. The generated JSON Schema files
|
|
@@ -9,7 +9,7 @@ import { Type } from "typebox";
|
|
|
9
9
|
// ---------------------------------------------------------------------------
|
|
10
10
|
// Shared primitives
|
|
11
11
|
// ---------------------------------------------------------------------------
|
|
12
|
-
const
|
|
12
|
+
const DefaultThinkingSelection = Type.Union(["off", "minimal", "low", "medium", "high", "xhigh"].map((v) => Type.Literal(v)), { description: "Default model thinking budget level." });
|
|
13
13
|
// ---------------------------------------------------------------------------
|
|
14
14
|
// Tool renderer
|
|
15
15
|
// ---------------------------------------------------------------------------
|
|
@@ -33,7 +33,7 @@ const OutputFiltersConfig = Type.Object({
|
|
|
33
33
|
}, { description: "Output filter patterns." });
|
|
34
34
|
const DefaultModelConfig = Type.Object({
|
|
35
35
|
modelRef: Type.Optional(Type.String({ description: "Provider/model identifier, e.g. 'openai-codex/gpt-5.4'." })),
|
|
36
|
-
thinking: Type.Optional(
|
|
36
|
+
thinking: Type.Optional(DefaultThinkingSelection),
|
|
37
37
|
}, { description: "Default model selection for new sessions." });
|
|
38
38
|
const PromptEnhancerConfig = Type.Object({
|
|
39
39
|
modelRef: Type.Optional(Type.String({ description: "Model used for prompt enhancement." })),
|
|
@@ -73,6 +73,8 @@ const DictationConfig = Type.Object({
|
|
|
73
73
|
// ---------------------------------------------------------------------------
|
|
74
74
|
export const PixConfigSchema = Type.Object({
|
|
75
75
|
$schema: Type.Optional(Type.String({ description: "JSON Schema URL used by editors for validation and autocomplete." })),
|
|
76
|
+
ignoreContextFiles: Type.Optional(Type.Boolean({ description: "Disable AGENTS.md / CLAUDE.md discovery for sessions started in this project, equivalent to pi --no-context-files." })),
|
|
77
|
+
maxProjectSessions: Type.Optional(Type.Number({ description: "Maximum number of pi session JSONL files to retain per project. Set to 0 to disable automatic session deletion.", minimum: 0 })),
|
|
76
78
|
defaultModel: Type.Optional(DefaultModelConfig),
|
|
77
79
|
toolRenderer: Type.Optional(ToolRendererConfig),
|
|
78
80
|
outputFilters: Type.Optional(OutputFiltersConfig),
|
|
@@ -86,6 +88,6 @@ export const PixConfigSchema = Type.Object({
|
|
|
86
88
|
$id: "https://unpkg.com/pi-ui-extend/schemas/pix.json",
|
|
87
89
|
$schema: "https://json-schema.org/draft-07/schema#",
|
|
88
90
|
title: "Pix Configuration",
|
|
89
|
-
description: "Configuration for the pix terminal renderer (~/.config/pi/pix.jsonc).",
|
|
91
|
+
description: "Configuration for the pix terminal renderer (~/.config/pi/pix.jsonc, with project overrides in <cwd>/.pi/pix.jsonc).",
|
|
90
92
|
additionalProperties: true,
|
|
91
93
|
});
|
package/dist/terminal-width.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export declare function expandTabs(text: string, tabWidth?: number): string;
|
|
2
2
|
export declare function stringDisplayWidth(text: string): number;
|
|
3
3
|
export declare function sliceByDisplayWidth(text: string, width: number): string;
|
|
4
|
+
export declare function displayIndexForColumn(text: string, column: number): number;
|
|
5
|
+
export declare function sliceByDisplayColumns(text: string, startColumn: number, endColumn: number): string;
|
|
4
6
|
export declare function padOrTrimDisplay(text: string, width: number): string;
|
|
5
7
|
export declare function wrapDisplayLine(text: string, width: number): string[];
|
|
6
8
|
export declare function wrapDisplayLineByWords(text: string, width: number): string[];
|
package/dist/terminal-width.js
CHANGED
|
@@ -4,6 +4,8 @@ const EMOJI_PRESENTATION_REGEX = /\p{Emoji_Presentation}/u;
|
|
|
4
4
|
const EMOJI_REGEX = /\p{Emoji}/u;
|
|
5
5
|
const GRAPHEME_SEGMENTER = typeof Intl.Segmenter === "function" ? new Intl.Segmenter(undefined, { granularity: "grapheme" }) : undefined;
|
|
6
6
|
export function expandTabs(text, tabWidth = TAB_WIDTH) {
|
|
7
|
+
if (!text.includes("\t"))
|
|
8
|
+
return text;
|
|
7
9
|
let result = "";
|
|
8
10
|
let column = 0;
|
|
9
11
|
for (const cluster of displayClusters(text)) {
|
|
@@ -29,6 +31,8 @@ export function expandTabs(text, tabWidth = TAB_WIDTH) {
|
|
|
29
31
|
return result;
|
|
30
32
|
}
|
|
31
33
|
export function stringDisplayWidth(text) {
|
|
34
|
+
if (isPrintableAscii(text))
|
|
35
|
+
return text.length;
|
|
32
36
|
let width = 0;
|
|
33
37
|
for (const cluster of displayClusters(text)) {
|
|
34
38
|
width += cluster.width;
|
|
@@ -37,6 +41,8 @@ export function stringDisplayWidth(text) {
|
|
|
37
41
|
}
|
|
38
42
|
export function sliceByDisplayWidth(text, width) {
|
|
39
43
|
const safeWidth = Math.max(0, width);
|
|
44
|
+
if (isPrintableAscii(text))
|
|
45
|
+
return text.slice(0, safeWidth);
|
|
40
46
|
let result = "";
|
|
41
47
|
let used = 0;
|
|
42
48
|
let sawAnsi = false;
|
|
@@ -58,13 +64,41 @@ export function sliceByDisplayWidth(text, width) {
|
|
|
58
64
|
return `${result}${ANSI_RESET}`;
|
|
59
65
|
return result;
|
|
60
66
|
}
|
|
67
|
+
export function displayIndexForColumn(text, column) {
|
|
68
|
+
const targetColumn = Math.max(1, column);
|
|
69
|
+
let displayColumn = 1;
|
|
70
|
+
for (const cluster of indexedDisplayClusters(text)) {
|
|
71
|
+
if (targetColumn <= displayColumn)
|
|
72
|
+
return cluster.start;
|
|
73
|
+
if (cluster.ansi || cluster.width <= 0)
|
|
74
|
+
continue;
|
|
75
|
+
const nextColumn = displayColumn + cluster.width;
|
|
76
|
+
if (targetColumn < nextColumn)
|
|
77
|
+
return cluster.start;
|
|
78
|
+
if (targetColumn === nextColumn)
|
|
79
|
+
return cluster.end;
|
|
80
|
+
displayColumn = nextColumn;
|
|
81
|
+
}
|
|
82
|
+
return text.length;
|
|
83
|
+
}
|
|
84
|
+
export function sliceByDisplayColumns(text, startColumn, endColumn) {
|
|
85
|
+
const startIndex = displayIndexForColumn(text, startColumn);
|
|
86
|
+
const endIndex = Math.max(startIndex, displayIndexForColumn(text, endColumn));
|
|
87
|
+
return text.slice(startIndex, endIndex);
|
|
88
|
+
}
|
|
61
89
|
export function padOrTrimDisplay(text, width) {
|
|
62
90
|
const safeWidth = Math.max(0, width);
|
|
91
|
+
if (isPrintableAscii(text)) {
|
|
92
|
+
const trimmed = text.slice(0, safeWidth);
|
|
93
|
+
return `${trimmed}${" ".repeat(Math.max(0, safeWidth - trimmed.length))}`;
|
|
94
|
+
}
|
|
63
95
|
const trimmed = sliceByDisplayWidth(text, safeWidth);
|
|
64
96
|
return `${trimmed}${" ".repeat(Math.max(0, safeWidth - stringDisplayWidth(trimmed)))}`;
|
|
65
97
|
}
|
|
66
98
|
export function wrapDisplayLine(text, width) {
|
|
67
99
|
const safeWidth = Math.max(1, width);
|
|
100
|
+
if (isPrintableAscii(text))
|
|
101
|
+
return wrapPrintableAsciiLine(text, safeWidth);
|
|
68
102
|
const chunks = [];
|
|
69
103
|
let chunk = "";
|
|
70
104
|
let chunkWidth = 0;
|
|
@@ -152,6 +186,23 @@ function appendTokenToEmptyChunk(token, width, chunks) {
|
|
|
152
186
|
function trimTrailingWhitespace(text) {
|
|
153
187
|
return text.replace(/\s+$/u, "");
|
|
154
188
|
}
|
|
189
|
+
function isPrintableAscii(text) {
|
|
190
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
191
|
+
const code = text.charCodeAt(index);
|
|
192
|
+
if (code < 0x20 || code > 0x7e)
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
function wrapPrintableAsciiLine(text, width) {
|
|
198
|
+
if (text.length <= width)
|
|
199
|
+
return [text];
|
|
200
|
+
const chunks = [];
|
|
201
|
+
for (let start = 0; start < text.length; start += width) {
|
|
202
|
+
chunks.push(text.slice(start, start + width));
|
|
203
|
+
}
|
|
204
|
+
return chunks;
|
|
205
|
+
}
|
|
155
206
|
function ansiSequenceLength(text, index) {
|
|
156
207
|
if (text.charCodeAt(index) !== 0x1b)
|
|
157
208
|
return 0;
|
|
@@ -179,29 +230,39 @@ function ansiSequenceLength(text, index) {
|
|
|
179
230
|
return 2;
|
|
180
231
|
}
|
|
181
232
|
function* displayClusters(text) {
|
|
233
|
+
for (const cluster of indexedDisplayClusters(text)) {
|
|
234
|
+
yield { text: cluster.text, width: cluster.width, ansi: cluster.ansi };
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function* indexedDisplayClusters(text) {
|
|
182
238
|
for (let index = 0; index < text.length;) {
|
|
183
239
|
const ansiLength = ansiSequenceLength(text, index);
|
|
184
240
|
if (ansiLength > 0) {
|
|
241
|
+
const start = index;
|
|
185
242
|
const cluster = text.slice(index, index + ansiLength);
|
|
186
|
-
yield { text: cluster, width: 0, ansi: true };
|
|
187
243
|
index += ansiLength;
|
|
244
|
+
yield { text: cluster, width: 0, ansi: true, start, end: index };
|
|
188
245
|
continue;
|
|
189
246
|
}
|
|
190
247
|
const nextAnsiIndex = text.indexOf("\x1b", index + 1);
|
|
191
248
|
const textEnd = nextAnsiIndex === -1 ? text.length : nextAnsiIndex;
|
|
192
249
|
const segment = text.slice(index, textEnd);
|
|
193
250
|
if (GRAPHEME_SEGMENTER) {
|
|
251
|
+
let segmentOffset = index;
|
|
194
252
|
for (const { segment: cluster } of GRAPHEME_SEGMENTER.segment(segment)) {
|
|
195
|
-
|
|
253
|
+
const start = segmentOffset;
|
|
254
|
+
segmentOffset += cluster.length;
|
|
255
|
+
yield { text: cluster, width: graphemeDisplayWidth(cluster), ansi: false, start, end: segmentOffset };
|
|
196
256
|
}
|
|
197
257
|
index = textEnd;
|
|
198
258
|
continue;
|
|
199
259
|
}
|
|
200
260
|
while (index < textEnd) {
|
|
261
|
+
const start = index;
|
|
201
262
|
const codePoint = text.codePointAt(index) ?? 0;
|
|
202
263
|
const cluster = String.fromCodePoint(codePoint);
|
|
203
|
-
yield { text: cluster, width: graphemeDisplayWidth(cluster), ansi: false };
|
|
204
264
|
index += codePointLength(codePoint);
|
|
265
|
+
yield { text: cluster, width: graphemeDisplayWidth(cluster), ansi: false, start, end: index };
|
|
205
266
|
}
|
|
206
267
|
}
|
|
207
268
|
}
|
package/dist/theme.js
CHANGED
|
@@ -18,11 +18,11 @@ export const THEMES = {
|
|
|
18
18
|
inputCursorBackground: "#7fb3c8",
|
|
19
19
|
popupForeground: "#e6edf3",
|
|
20
20
|
popupBackground: "#1e1e1e",
|
|
21
|
-
popupHeaderBackground: "#
|
|
21
|
+
popupHeaderBackground: "#263241",
|
|
22
22
|
popupBorder: "#1e1e1e",
|
|
23
23
|
popupMuted: "#8a8a8a",
|
|
24
|
-
popupSelectedForeground: "#
|
|
25
|
-
popupSelectedBackground: "#
|
|
24
|
+
popupSelectedForeground: "#e6edf3",
|
|
25
|
+
popupSelectedBackground: "#2a2f36",
|
|
26
26
|
selectionForeground: "#ffffff",
|
|
27
27
|
selectionBackground: "#3b82f6",
|
|
28
28
|
toastForeground: "#0d1117",
|
|
@@ -60,11 +60,11 @@ export const THEMES = {
|
|
|
60
60
|
inputCursorBackground: "#0284c7",
|
|
61
61
|
popupForeground: "#0f172a",
|
|
62
62
|
popupBackground: "#ffffff",
|
|
63
|
-
popupHeaderBackground: "#
|
|
63
|
+
popupHeaderBackground: "#dbeafe",
|
|
64
64
|
popupBorder: "#ffffff",
|
|
65
65
|
popupMuted: "#64748b",
|
|
66
|
-
popupSelectedForeground: "#
|
|
67
|
-
popupSelectedBackground: "#
|
|
66
|
+
popupSelectedForeground: "#0f172a",
|
|
67
|
+
popupSelectedBackground: "#f1f5f9",
|
|
68
68
|
selectionForeground: "#ffffff",
|
|
69
69
|
selectionBackground: "#2563eb",
|
|
70
70
|
toastForeground: "#064e3b",
|
package/dist/ui.d.ts
CHANGED
|
@@ -2,6 +2,14 @@ export type PopupMenuItem<T> = {
|
|
|
2
2
|
value: T;
|
|
3
3
|
label: string;
|
|
4
4
|
description?: string;
|
|
5
|
+
labelHighlightRanges?: readonly {
|
|
6
|
+
start: number;
|
|
7
|
+
end: number;
|
|
8
|
+
}[];
|
|
9
|
+
descriptionHighlightRanges?: readonly {
|
|
10
|
+
start: number;
|
|
11
|
+
end: number;
|
|
12
|
+
}[];
|
|
5
13
|
};
|
|
6
14
|
export type VisiblePopupMenuItem<T> = PopupMenuItem<T> & {
|
|
7
15
|
index: number;
|
|
@@ -8,8 +8,8 @@ This package keeps shared Pi tools as ordinary source folders under `src/` and r
|
|
|
8
8
|
- `src/async-subagents` — `subagents` tool and sub-agent slash commands, including oh-my-openagent-style `/ultrawork` (`/ulw`) and `/hyperplan` orchestration prompts, plus config-defined sub-agent model/thinking/args presets selected via `/subagent-preset` from `asyncSubagents` in `~/.config/pi/pi-tools-suite.jsonc`; includes the `frontend` profile for Gemini-friendly UI/UX and visual frontend work plus the `vision` profile for screenshot/image description via `openai-codex/gpt-5.4-mini`; enforces a 30-minute per-agent execution timeout, project-wide `maxConcurrent` queueing, optional retry/backoff, and `result.json` structured metadata/chaining fields next to raw `result.md`; stores project-local run files and a registry under `.pi/subagents/` so result/status collection can recover after compaction or reload while the main session remains alive
|
|
9
9
|
- `src/lsp` — shared LSP diagnostics hook/library that enriches mutating tool results with diagnostics and shuts down language servers on session shutdown
|
|
10
10
|
- `src/repo-discovery` — `/idx-init`, `/idx-update`, and indexed-only `repo_architecture` / `repo_structure` / `repo_ast` / `repo_search` / `repo_explain` / `repo_deps`; tools register only when the launch project has `.indexer-cli`
|
|
11
|
-
- `src/antigravity-auth` — `antigravity` custom provider with Google Antigravity OAuth login, startup account list,
|
|
12
|
-
- `src/todo` — `todo` tool, `/todos`, `/todos-persist`, and `/todos-scope`; supports
|
|
11
|
+
- `src/antigravity-auth` — `antigravity` custom provider with Google Antigravity OAuth login, startup account list, auth.json-only runtime account loading, `/antigravity-add-account` OAuth append into rotation, `/antigravity-account` status display, account rotation/failover, Antigravity plus Gemini CLI model registration, and streaming through the Cloud Code Assist unified gateway
|
|
12
|
+
- `src/todo` — `todo` tool, `/todos`, `/todos-persist`, and `/todos-scope`; supports parent/subtask hierarchy, blockers, ready-task filtering, deferred out-of-scope items, batch operations, JSON/Markdown import/export, automatic clearing when all visible todos are completed, and optional project persistence via `/todos persist on` or `/todos-persist on`; localization/i18n has been removed
|
|
13
13
|
- `src/model-tools` — model-specific tool aliases such as Claude/GLM-style `Read` / `Edit` / `Write` / `Bash` / `Grep` / `Glob` / `LS`, GPT/Codex-style `shell`, and model-gated `apply_patch`
|
|
14
14
|
- `src/usage` — `/usage` command and startup hint for read-only AI quota checks across OpenAI, Zhipu AI, Z.ai, and Google Antigravity, including Antigravity quota by model
|
|
15
15
|
- `src/web-search` — `web_search` and `web_fetch` tools migrated from `@ollama/pi-web-search`; calls the local Ollama experimental web search/fetch APIs, honors `OLLAMA_HOST`, supports request timeouts via `timeout_ms` / `PI_WEB_SEARCH_TIMEOUT_MS`, and reports targeted `ollama signin`, unsupported-endpoint, invalid-response, timeout, DNS, and Ollama-not-running errors
|
|
@@ -62,6 +62,7 @@ DCP settings are stored only under `dcp` in the user shared config file `~/.conf
|
|
|
62
62
|
"enabled": true,
|
|
63
63
|
"compress": {
|
|
64
64
|
"minContextPercent": "25%",
|
|
65
|
+
"maxContextPercent": "65%",
|
|
65
66
|
"maxContextLimit": 160000,
|
|
66
67
|
"nudgeFrequency": 1,
|
|
67
68
|
"iterationNudgeThreshold": 8,
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { promises as fs } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
-
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
5
4
|
import { DEFAULT_PROJECT_ID, PROVIDER_ID } from "./constants";
|
|
6
|
-
import type { OpencodeAntigravityAccount, OpencodeAntigravityImportResult, OpencodeAntigravityStorage, PiAuthCredential, PiAuthData } from "./types";
|
|
5
|
+
import type { GoogleOAuthClientCredentials, OpencodeAntigravityAccount, OpencodeAntigravityImportResult, OpencodeAntigravityStorage, PiAuthCredential, PiAuthData } from "./types";
|
|
6
|
+
|
|
7
|
+
export const PI_AUTH_PATH = join(homedir(), ".pi", "agent", "auth.json");
|
|
8
|
+
|
|
9
|
+
function testPiAuthPath(): string | undefined {
|
|
10
|
+
return process.env.NODE_ENV === "test" ? process.env.PI_TOOLS_SUITE_TEST_AUTH_PATH : undefined;
|
|
11
|
+
}
|
|
7
12
|
|
|
8
13
|
export function splitRefresh(refresh: string): { refreshToken: string; projectId?: string; managedProjectId?: string } {
|
|
9
14
|
const [refreshToken = "", projectId = "", managedProjectId = ""] = refresh.split("|");
|
|
@@ -33,13 +38,17 @@ export function decodeApiKey(apiKey: string): { access: string; projectId?: stri
|
|
|
33
38
|
return { access, projectId: projectId || undefined };
|
|
34
39
|
}
|
|
35
40
|
|
|
36
|
-
function getDefaultOpencodeAccountsPath(): string {
|
|
41
|
+
export function getDefaultOpencodeAccountsPath(): string {
|
|
37
42
|
const configDir = process.env.OPENCODE_CONFIG_DIR ?? join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "opencode");
|
|
38
43
|
return join(configDir, "antigravity-accounts.json");
|
|
39
44
|
}
|
|
40
45
|
|
|
46
|
+
export async function importDefaultOpencodeAntigravityAccount(options: { overwrite?: boolean } = {}): Promise<OpencodeAntigravityImportResult> {
|
|
47
|
+
return importOpencodeAntigravityAccount({ sourcePath: getDefaultOpencodeAccountsPath(), authPath: getPiAuthPath(), overwrite: options.overwrite });
|
|
48
|
+
}
|
|
49
|
+
|
|
41
50
|
export function getPiAuthPath(): string {
|
|
42
|
-
return
|
|
51
|
+
return testPiAuthPath() ?? PI_AUTH_PATH;
|
|
43
52
|
}
|
|
44
53
|
|
|
45
54
|
export async function readJsonFile<T>(path: string, fallback: T): Promise<T> {
|
|
@@ -58,7 +67,36 @@ export async function writeJsonFileSecure(path: string, data: unknown): Promise<
|
|
|
58
67
|
}
|
|
59
68
|
|
|
60
69
|
export function getStoredAccounts(credential?: PiAuthCredential): OpencodeAntigravityAccount[] {
|
|
61
|
-
return Array.isArray(credential?.accounts) ? credential.accounts.filter((account) => account.enabled !== false && account
|
|
70
|
+
return Array.isArray(credential?.accounts) ? credential.accounts.filter((account) => account.enabled !== false && getAccountRefreshToken(account)) : [];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function getAccountRefreshToken(account: OpencodeAntigravityAccount): string | undefined {
|
|
74
|
+
if (account.refreshToken) return account.refreshToken;
|
|
75
|
+
if (!account.refresh) return undefined;
|
|
76
|
+
const refresh = splitRefresh(account.refresh);
|
|
77
|
+
return refresh.refreshToken || undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function stringProperty(source: unknown, keys: string[]): string | undefined {
|
|
81
|
+
if (!source || typeof source !== "object") return undefined;
|
|
82
|
+
const record = source as Record<string, unknown>;
|
|
83
|
+
for (const key of keys) {
|
|
84
|
+
const value = record[key];
|
|
85
|
+
if (typeof value === "string" && value) return value;
|
|
86
|
+
}
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function getGoogleOAuthClientCredentials(...sources: Array<unknown>): GoogleOAuthClientCredentials | undefined {
|
|
91
|
+
for (const source of sources) {
|
|
92
|
+
const nested = source && typeof source === "object" ? (source as Record<string, unknown>).oauthClient : undefined;
|
|
93
|
+
const nestedClientId = stringProperty(nested, ["clientId", "client_id", "id"]);
|
|
94
|
+
const nestedClientSecret = stringProperty(nested, ["clientSecret", "client_secret", "secret"]);
|
|
95
|
+
const clientId = nestedClientId ?? stringProperty(source, ["clientId", "client_id", "googleClientId", "google_client_id", "oauthClientId", "oauth_client_id"]);
|
|
96
|
+
const clientSecret = nestedClientSecret ?? stringProperty(source, ["clientSecret", "client_secret", "googleClientSecret", "google_client_secret", "oauthClientSecret", "oauth_client_secret"]);
|
|
97
|
+
if (clientId) return { clientId, ...(clientSecret ? { clientSecret } : {}) };
|
|
98
|
+
}
|
|
99
|
+
return undefined;
|
|
62
100
|
}
|
|
63
101
|
|
|
64
102
|
export function clampAccountIndex(index: unknown, accountCount: number): number {
|
|
@@ -67,7 +105,12 @@ export function clampAccountIndex(index: unknown, accountCount: number): number
|
|
|
67
105
|
}
|
|
68
106
|
|
|
69
107
|
export function getAccountProjectId(account: OpencodeAntigravityAccount): string {
|
|
70
|
-
return account.projectId || account.managedProjectId || DEFAULT_PROJECT_ID;
|
|
108
|
+
if (account.projectId || account.managedProjectId) return account.projectId || account.managedProjectId || DEFAULT_PROJECT_ID;
|
|
109
|
+
if (account.refresh) {
|
|
110
|
+
const refresh = splitRefresh(account.refresh);
|
|
111
|
+
return refresh.projectId || refresh.managedProjectId || DEFAULT_PROJECT_ID;
|
|
112
|
+
}
|
|
113
|
+
return DEFAULT_PROJECT_ID;
|
|
71
114
|
}
|
|
72
115
|
|
|
73
116
|
export function accountFromCredential(credential?: PiAuthCredential): OpencodeAntigravityAccount | undefined {
|
|
@@ -79,6 +122,7 @@ export function accountFromCredential(credential?: PiAuthCredential): OpencodeAn
|
|
|
79
122
|
refreshToken: refresh.refreshToken,
|
|
80
123
|
projectId: refresh.projectId || refresh.managedProjectId || DEFAULT_PROJECT_ID,
|
|
81
124
|
managedProjectId: refresh.managedProjectId,
|
|
125
|
+
...getGoogleOAuthClientCredentials(credential),
|
|
82
126
|
enabled: true,
|
|
83
127
|
};
|
|
84
128
|
}
|
|
@@ -99,7 +143,7 @@ function selectOpencodeAccount(
|
|
|
99
143
|
storage: OpencodeAntigravityStorage,
|
|
100
144
|
options: { accountIndex?: number; email?: string },
|
|
101
145
|
): { account: OpencodeAntigravityAccount; index: number; count: number } | undefined {
|
|
102
|
-
const accounts = storage.accounts?.filter((account) => account &&
|
|
146
|
+
const accounts = storage.accounts?.filter((account) => account && getAccountRefreshToken(account)) ?? [];
|
|
103
147
|
if (accounts.length === 0) return undefined;
|
|
104
148
|
|
|
105
149
|
if (options.email) {
|
|
@@ -177,7 +221,7 @@ export async function importOpencodeAntigravityAccount(options: {
|
|
|
177
221
|
access: "",
|
|
178
222
|
expires: 0,
|
|
179
223
|
email: selected.account.email,
|
|
180
|
-
accounts: storage.accounts.filter((account) => account.enabled !== false && account
|
|
224
|
+
accounts: storage.accounts.filter((account) => account.enabled !== false && getAccountRefreshToken(account)),
|
|
181
225
|
activeIndex: selected.index,
|
|
182
226
|
};
|
|
183
227
|
await writeJsonFileSecure(authPath, piAuth);
|
|
@@ -1,56 +1,18 @@
|
|
|
1
|
-
import type { AntigravityAddAccountResult, AntigravityStatusDetails
|
|
1
|
+
import type { AntigravityAddAccountResult, AntigravityStatusDetails } from "./types";
|
|
2
2
|
|
|
3
3
|
function tokenizeArgs(args: string): string[] {
|
|
4
4
|
return args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g)?.map((part) => part.replace(/^("|')|("|')$/g, "")) ?? [];
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
export function
|
|
7
|
+
export function parseAddAccountCommandArgs(args: string): { activate?: boolean; email?: string } {
|
|
8
8
|
const tokens = tokenizeArgs(args);
|
|
9
|
-
const parsed: {
|
|
10
|
-
for (let i = 0; i < tokens.length; i += 1) {
|
|
11
|
-
const token = tokens[i];
|
|
12
|
-
if (token === "--force" || token === "-f") {
|
|
13
|
-
parsed.overwrite = true;
|
|
14
|
-
} else if (token === "--path" && tokens[i + 1]) {
|
|
15
|
-
parsed.sourcePath = tokens[++i];
|
|
16
|
-
} else if ((token === "--index" || token === "--account-index") && tokens[i + 1]) {
|
|
17
|
-
const index = Number(tokens[++i]);
|
|
18
|
-
if (Number.isInteger(index)) parsed.accountIndex = index;
|
|
19
|
-
} else if (token === "--email" && tokens[i + 1]) {
|
|
20
|
-
parsed.email = tokens[++i];
|
|
21
|
-
} else if (!token.startsWith("-") && !parsed.sourcePath) {
|
|
22
|
-
parsed.sourcePath = token;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return parsed;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function formatImportResult(result: OpencodeAntigravityImportResult): string {
|
|
29
|
-
const account = result.email ? `${result.email} ` : "";
|
|
30
|
-
const position = typeof result.accountIndex === "number" && result.accountCount ? `(account ${result.accountIndex}/${result.accountCount - 1}) ` : "";
|
|
31
|
-
if (result.imported) {
|
|
32
|
-
return `Imported ${account}${position}from ${result.sourcePath} into ${result.authPath}. Token will refresh on first use.${result.overwroteExisting ? " Existing Antigravity auth was overwritten." : ""}`;
|
|
33
|
-
}
|
|
34
|
-
if (result.reason === "auth-exists-use-force") {
|
|
35
|
-
return `Antigravity auth already exists in ${result.authPath}; run /antigravity-import --force to overwrite it with ${account}${position}from ${result.sourcePath}.`;
|
|
36
|
-
}
|
|
37
|
-
if (result.reason === "already-imported") {
|
|
38
|
-
return `Antigravity auth is already imported from ${result.sourcePath} (${account}${position.trim()}).`;
|
|
39
|
-
}
|
|
40
|
-
return `Could not import Antigravity auth from ${result.sourcePath}: ${result.reason ?? "unknown error"}.`;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function parseAddAccountCommandArgs(args: string): { activate?: boolean; email?: string; authPath?: string } {
|
|
44
|
-
const tokens = tokenizeArgs(args);
|
|
45
|
-
const parsed: { activate?: boolean; email?: string; authPath?: string } = {};
|
|
9
|
+
const parsed: { activate?: boolean; email?: string } = {};
|
|
46
10
|
for (let i = 0; i < tokens.length; i += 1) {
|
|
47
11
|
const token = tokens[i];
|
|
48
12
|
if (token === "--activate" || token === "-a") {
|
|
49
13
|
parsed.activate = true;
|
|
50
14
|
} else if (token === "--email" && tokens[i + 1]) {
|
|
51
15
|
parsed.email = tokens[++i];
|
|
52
|
-
} else if (token === "--auth-path" && tokens[i + 1]) {
|
|
53
|
-
parsed.authPath = tokens[++i];
|
|
54
16
|
}
|
|
55
17
|
}
|
|
56
18
|
return parsed;
|
|
@@ -4,8 +4,6 @@ export const STATUS_KEY = "dcp:antigravity";
|
|
|
4
4
|
export const LEGACY_STATUS_KEY = "antigravity";
|
|
5
5
|
export const ALL_ACCOUNTS_EXHAUSTED_MARKER = "ANTIGRAVITY_ALL_ACCOUNTS_EXHAUSTED";
|
|
6
6
|
|
|
7
|
-
export const CLIENT_ID = process.env.PIX_ANTIGRAVITY_GOOGLE_CLIENT_ID ?? process.env.ANTIGRAVITY_GOOGLE_CLIENT_ID ?? "";
|
|
8
|
-
export const CLIENT_SECRET = process.env.PIX_ANTIGRAVITY_GOOGLE_CLIENT_SECRET ?? process.env.ANTIGRAVITY_GOOGLE_CLIENT_SECRET ?? "";
|
|
9
7
|
export const REDIRECT_URI = "http://localhost:51121/oauth-callback";
|
|
10
8
|
export const SCOPES = [
|
|
11
9
|
"https://www.googleapis.com/auth/cloud-platform",
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { decodeApiKey, getEffectiveProjectId
|
|
3
|
-
import { formatAddAccountResult,
|
|
2
|
+
import { decodeApiKey, getEffectiveProjectId } from "./auth-store";
|
|
3
|
+
import { formatAddAccountResult, parseAddAccountCommandArgs } from "./commands";
|
|
4
4
|
import { API_ID, DEFAULT_PROJECT_ID, ENDPOINT_DAILY, PROVIDER_ID } from "./constants";
|
|
5
5
|
import { modelDefinitions } from "./models";
|
|
6
6
|
import { addAntigravityAccount, loginAntigravity, refreshAntigravityToken } from "./oauth";
|
|
7
|
-
import { emitAntigravityStatus, getCurrentAntigravityStatus, publishAntigravityAuthStartupSection, rememberAntigravityApi, rememberAntigravityUi } from "./status";
|
|
7
|
+
import { emitAntigravityStatus, getCurrentAntigravityStatus, notifyAntigravityLoginFailure, notifyAntigravityProviderFailure, publishAntigravityAuthStartupSection, rememberAntigravityApi, rememberAntigravityUi } from "./status";
|
|
8
8
|
import { streamAntigravity } from "./stream";
|
|
9
9
|
|
|
10
|
-
export { importOpencodeAntigravityAccount } from "./auth-store";
|
|
11
10
|
export { addAntigravityAccount } from "./oauth";
|
|
12
|
-
export type { AntigravityAddAccountResult
|
|
11
|
+
export type { AntigravityAddAccountResult } from "./types";
|
|
13
12
|
|
|
14
13
|
export default async function antigravityAuth(pi: ExtensionAPI): Promise<void> {
|
|
15
14
|
rememberAntigravityApi(pi);
|
|
@@ -22,20 +21,14 @@ export default async function antigravityAuth(pi: ExtensionAPI): Promise<void> {
|
|
|
22
21
|
pi.on("before_provider_request", (_event, ctx) => {
|
|
23
22
|
rememberAntigravityUi(ctx.ui);
|
|
24
23
|
});
|
|
24
|
+
pi.on("message_end", (event, ctx) => {
|
|
25
|
+
rememberAntigravityUi(ctx.ui);
|
|
26
|
+
const message = event.message;
|
|
27
|
+
if (message.role !== "assistant" || message.provider !== PROVIDER_ID || message.stopReason !== "error" || !message.errorMessage) return;
|
|
28
|
+
notifyAntigravityProviderFailure(message.errorMessage, { ui: ctx.ui, model: message.model });
|
|
29
|
+
});
|
|
25
30
|
}
|
|
26
31
|
|
|
27
|
-
pi.registerCommand("antigravity-import", {
|
|
28
|
-
description: "Import Antigravity OAuth from opencode antigravity-accounts.json into Pi auth.json",
|
|
29
|
-
handler: async (args: string, ctx: any) => {
|
|
30
|
-
try {
|
|
31
|
-
const result = await importOpencodeAntigravityAccount(parseImportCommandArgs(args));
|
|
32
|
-
ctx.ui?.notify?.(formatImportResult(result), result.imported ? "info" : result.reason === "auth-exists-use-force" ? "warn" : "error");
|
|
33
|
-
} catch (error) {
|
|
34
|
-
ctx.ui?.notify?.(error instanceof Error ? error.message : String(error), "error");
|
|
35
|
-
}
|
|
36
|
-
},
|
|
37
|
-
});
|
|
38
|
-
|
|
39
32
|
pi.registerCommand("antigravity-add-account", {
|
|
40
33
|
description: "Add a new Google Antigravity OAuth account to the Pi rotation pool",
|
|
41
34
|
handler: async (args: string, ctx: any) => {
|
|
@@ -63,7 +56,7 @@ export default async function antigravityAuth(pi: ExtensionAPI): Promise<void> {
|
|
|
63
56
|
ctx.ui?.notify?.(formatAddAccountResult(result), "info");
|
|
64
57
|
emitAntigravityStatus(await getCurrentAntigravityStatus());
|
|
65
58
|
} catch (error) {
|
|
66
|
-
|
|
59
|
+
notifyAntigravityLoginFailure(error);
|
|
67
60
|
}
|
|
68
61
|
},
|
|
69
62
|
});
|