@oh-my-pi/pi-coding-agent 16.1.10 → 16.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/dist/cli.js +2811 -2812
- package/dist/types/export/html/index.d.ts +31 -2
- package/dist/types/export/html/web-palette.d.ts +117 -0
- package/dist/types/hindsight/content.d.ts +7 -0
- package/dist/types/hindsight/transcript.d.ts +1 -1
- package/dist/types/modes/components/hook-editor.d.ts +2 -1
- package/dist/types/modes/interactive-mode.d.ts +5 -0
- package/dist/types/modes/types.d.ts +17 -0
- package/dist/types/modes/utils/keybinding-matchers.d.ts +13 -0
- package/dist/types/session/agent-session.d.ts +4 -0
- package/dist/types/session/session-context.d.ts +2 -0
- package/dist/types/session/session-entries.d.ts +6 -0
- package/dist/types/session/session-manager.d.ts +2 -1
- package/dist/types/tools/acp-bridge.d.ts +29 -0
- package/dist/types/tools/browser/cmux/cmux-tab.d.ts +7 -0
- package/dist/types/tools/browser/tab-worker.d.ts +20 -0
- package/dist/types/utils/image-loading.d.ts +4 -3
- package/dist/types/utils/jj.d.ts +25 -0
- package/dist/types/utils/title-generator.d.ts +0 -2
- package/package.json +12 -12
- package/scripts/generate-share-viewer.ts +4 -2
- package/src/autoresearch/git.ts +12 -0
- package/src/discovery/opencode.ts +47 -4
- package/src/edit/hashline/filesystem.ts +8 -0
- package/src/edit/modes/patch.ts +18 -2
- package/src/edit/modes/replace.ts +13 -10
- package/src/export/html/index.ts +50 -8
- package/src/export/html/web-palette.ts +142 -0
- package/src/hindsight/backend.ts +4 -4
- package/src/hindsight/content.ts +17 -1
- package/src/hindsight/transcript.ts +2 -2
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/modes/components/agent-dashboard.ts +8 -8
- package/src/modes/components/hook-editor.ts +13 -10
- package/src/modes/components/session-selector.ts +3 -0
- package/src/modes/controllers/event-controller.ts +9 -5
- package/src/modes/controllers/extension-ui-controller.ts +6 -2
- package/src/modes/interactive-mode.ts +69 -29
- package/src/modes/theme/dark.json +1 -1
- package/src/modes/types.ts +18 -0
- package/src/modes/utils/keybinding-matchers.ts +36 -1
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/prompts/tools/browser.md +3 -2
- package/src/sdk.ts +12 -1
- package/src/session/agent-session.ts +38 -14
- package/src/session/session-context.ts +5 -0
- package/src/session/session-entries.ts +6 -0
- package/src/session/session-manager.ts +3 -1
- package/src/task/worktree.ts +12 -4
- package/src/thinking.ts +1 -1
- package/src/tools/acp-bridge.ts +66 -0
- package/src/tools/browser/cmux/cmux-tab.ts +37 -0
- package/src/tools/browser/tab-worker.ts +160 -37
- package/src/tools/write.ts +2 -25
- package/src/utils/image-loading.ts +6 -3
- package/src/utils/jj.ts +47 -0
- package/src/utils/title-generator.ts +31 -99
|
@@ -5,12 +5,41 @@ import { SessionManager } from "../../session/session-manager";
|
|
|
5
5
|
export declare function getTemplate(): string;
|
|
6
6
|
export interface ExportOptions {
|
|
7
7
|
outputPath?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Which color palette the export ships with.
|
|
10
|
+
* - `"web"` (default) — the omp brand identity (collab-web pink/purple),
|
|
11
|
+
* so public HTML exports and the `/s/<id>` share viewer match the live
|
|
12
|
+
* `my.omp.sh` client. See `web-palette.ts`.
|
|
13
|
+
* - `"theme"` — derive from `themeName` (or the active TUI theme), preserving
|
|
14
|
+
* the pre-15.12 behavior where an export mirrored the user's terminal.
|
|
15
|
+
*/
|
|
16
|
+
palette?: "web" | "theme";
|
|
17
|
+
/**
|
|
18
|
+
* TUI theme to derive colors from when `palette: "theme"`. Ignored for the
|
|
19
|
+
* default `"web"` palette. Resolves to the active TUI theme when omitted.
|
|
20
|
+
*/
|
|
8
21
|
themeName?: string;
|
|
9
22
|
/** Embed subagent session transcripts found next to the session file (default true). */
|
|
10
23
|
includeSubSessions?: boolean;
|
|
11
24
|
}
|
|
12
|
-
/**
|
|
13
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Generate CSS custom properties for the export `:root`.
|
|
27
|
+
*
|
|
28
|
+
* Two call shapes:
|
|
29
|
+
* • `generateThemeVars("web" | "theme", themeName?)` — explicit palette.
|
|
30
|
+
* `"web"` (the default for public artifacts) returns the fixed omp brand
|
|
31
|
+
* palette from `web-palette.ts` — collab-web pink/purple identity, shared
|
|
32
|
+
* with the live `my.omp.sh` client, so exports and the share viewer render
|
|
33
|
+
* identically to it. `"theme"` derives from the TUI theme via
|
|
34
|
+
* `getResolvedThemeColors(themeName)` plus the three
|
|
35
|
+
* `export.{pageBg,cardBg,infoBg}` surface overrides.
|
|
36
|
+
* • `generateThemeVars(themeName)` — legacy single-arg form: derive from the
|
|
37
|
+
* named TUI theme. Kept so existing callers (and the theme-islight test)
|
|
38
|
+
* keep working; equivalent to `generateThemeVars("theme", themeName)`.
|
|
39
|
+
*
|
|
40
|
+
* Exported for the share-viewer build script.
|
|
41
|
+
*/
|
|
42
|
+
export declare function generateThemeVars(palette?: "web" | "theme" | (string & {}), themeName?: string): Promise<string>;
|
|
14
43
|
/** Embedded subagent session transcript, keyed by slash-joined agent path in `SessionData.subSessions`. */
|
|
15
44
|
export interface SubSession {
|
|
16
45
|
/** Bare agent id (session file stem), e.g. "ToolAsk". */
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web/export palette — the omp brand identity shared by the collab-web live
|
|
3
|
+
* client (`my.omp.sh/`) and every public HTML export / share viewer (`/s/<id>`).
|
|
4
|
+
*
|
|
5
|
+
* Why this exists separately from `modes/theme/dark.json`: the `dark` theme is
|
|
6
|
+
* the **default TUI theme** — its amber accent (`#febc38`) drives the terminal
|
|
7
|
+
* status line, syntax highlighting, thinking levels, and bash/python mode
|
|
8
|
+
* colors for every omp user. The public web artifacts want the collab-web
|
|
9
|
+
* pink/purple identity instead, so they pin this palette rather than inheriting
|
|
10
|
+
* the TUI's. Editing `dark.json` to repurpose it for the web would repaint
|
|
11
|
+
* every terminal; this file keeps the two surfaces decoupled.
|
|
12
|
+
*
|
|
13
|
+
* Token layout — emitted as CSS custom properties on `:root`:
|
|
14
|
+
* • Legacy export names consumed by `template.css` / `template.js`
|
|
15
|
+
* (`--text`, `--body-bg`, `--container-bg`, `--info-bg`, `--accent`,
|
|
16
|
+
* `--border`, `--success`, `--error`, `--warning`, `--muted`, `--dim`,
|
|
17
|
+
* `--borderAccent`, `--selectedBg`, `--userMessageBg`, `--customMessageBg`,
|
|
18
|
+
* `--customMessageLabel`, `--mdHeading`, `--mdLink`, `--mdCode`,
|
|
19
|
+
* `--mdListBullet`, `--toolOutput`, `--thinkingText`, syntax*, …).
|
|
20
|
+
* • collab-web-native aliases consumed by the `tv-` tool-render bridge
|
|
21
|
+
* (`tool-render.css`: `var(--bg-inset, …)`, `var(--fg, …)`, …) so embedded
|
|
22
|
+
* tool cards resolve to the *real* collab-web tokens and render
|
|
23
|
+
* pixel-identical to the live client.
|
|
24
|
+
*
|
|
25
|
+
* Alpha-bearing tokens (`--border`, `--ring`, `--accent-muted`, …) keep their
|
|
26
|
+
* `oklch(… / N%)` form — flattening them to opaque hex would produce harsh
|
|
27
|
+
* white borders and non-matching translucent focus rings. Opaque surfaces are
|
|
28
|
+
* sRGB hex (the collab-web `tokens.css` OKLCH dark-theme tokens converted via
|
|
29
|
+
* the standard OKLab→linear-sRGB→gamma path); if the live client palette
|
|
30
|
+
* changes, regenerate those from there.
|
|
31
|
+
*/
|
|
32
|
+
export declare const WEB_EXPORT_PALETTE: {
|
|
33
|
+
readonly "--bg": "#0f0b14";
|
|
34
|
+
readonly "--bg-raised": "#16111c";
|
|
35
|
+
readonly "--bg-inset": "#09060c";
|
|
36
|
+
readonly "--bg-overlay": "#211b28";
|
|
37
|
+
readonly "--fg": "#e6e3ea";
|
|
38
|
+
readonly "--fg-muted": "#a49faa";
|
|
39
|
+
readonly "--fg-faint": "#6e6974";
|
|
40
|
+
readonly "--accent": "#ed4abf";
|
|
41
|
+
readonly "--accent-muted": "oklch(0.674 0.23 341 / 18%)";
|
|
42
|
+
readonly "--ok": "#68ca80";
|
|
43
|
+
readonly "--err": "#f05653";
|
|
44
|
+
readonly "--warn": "#e4b33f";
|
|
45
|
+
readonly "--ring": "oklch(0.817 0.112 205 / 70%)";
|
|
46
|
+
readonly "--font-mono": 'ui-monospace, "SF Mono", "JetBrains Mono", "Cascadia Mono", Menlo, Consolas, monospace';
|
|
47
|
+
readonly "--body-bg": "#0f0b14";
|
|
48
|
+
readonly "--container-bg": "#16111c";
|
|
49
|
+
readonly "--info-bg": "#09060c";
|
|
50
|
+
readonly "--text": "#e6e3ea";
|
|
51
|
+
readonly "--muted": "#a49faa";
|
|
52
|
+
readonly "--dim": "#6e6974";
|
|
53
|
+
readonly "--thinkingText": "#a49faa";
|
|
54
|
+
readonly "--border": "oklch(1 0 0 / 9%)";
|
|
55
|
+
readonly "--borderMuted": "oklch(1 0 0 / 6%)";
|
|
56
|
+
readonly "--borderAccent": "#945ff9";
|
|
57
|
+
readonly "--selectedBg": "#2d2535";
|
|
58
|
+
readonly "--success": "#68ca80";
|
|
59
|
+
readonly "--error": "#f05653";
|
|
60
|
+
readonly "--warning": "#e4b33f";
|
|
61
|
+
readonly "--userMessageBg": "oklch(0.674 0.23 341 / 6%)";
|
|
62
|
+
readonly "--userMessageText": "#e6e3ea";
|
|
63
|
+
readonly "--customMessageBg": "#211b28";
|
|
64
|
+
readonly "--customMessageText": "#a49faa";
|
|
65
|
+
readonly "--customMessageLabel": "#b281d6";
|
|
66
|
+
readonly "--toolPendingBg": "#16111c";
|
|
67
|
+
readonly "--toolSuccessBg": "#09060c";
|
|
68
|
+
readonly "--toolErrorBg": "oklch(0.66 0.19 25 / 14%)";
|
|
69
|
+
readonly "--toolTitle": "#e6e3ea";
|
|
70
|
+
readonly "--toolOutput": "#a49faa";
|
|
71
|
+
readonly "--mdHeading": "#ed4abf";
|
|
72
|
+
readonly "--mdLink": "#5ad8e5";
|
|
73
|
+
readonly "--mdLinkUrl": "#6e6974";
|
|
74
|
+
readonly "--mdCode": "#e6e3ea";
|
|
75
|
+
readonly "--mdCodeBlock": "#e6e3ea";
|
|
76
|
+
readonly "--mdCodeBlockBorder": "oklch(1 0 0 / 9%)";
|
|
77
|
+
readonly "--mdQuote": "#a49faa";
|
|
78
|
+
readonly "--mdQuoteBorder": "oklch(1 0 0 / 13%)";
|
|
79
|
+
readonly "--mdHr": "oklch(1 0 0 / 9%)";
|
|
80
|
+
readonly "--mdListBullet": "#ed4abf";
|
|
81
|
+
readonly "--toolDiffAdded": "#68ca80";
|
|
82
|
+
readonly "--toolDiffRemoved": "#f05653";
|
|
83
|
+
readonly "--toolDiffContext": "#6e6974";
|
|
84
|
+
readonly "--syntaxComment": "#6e6974";
|
|
85
|
+
readonly "--syntaxKeyword": "#945ff9";
|
|
86
|
+
readonly "--syntaxFunction": "#e4b33f";
|
|
87
|
+
readonly "--syntaxVariable": "#5ad8e5";
|
|
88
|
+
readonly "--syntaxString": "#68ca80";
|
|
89
|
+
readonly "--syntaxNumber": "#ed4abf";
|
|
90
|
+
readonly "--syntaxType": "#b281d6";
|
|
91
|
+
readonly "--syntaxOperator": "#e6e3ea";
|
|
92
|
+
readonly "--syntaxPunctuation": "#a49faa";
|
|
93
|
+
readonly "--thinkingOff": "#6e6974";
|
|
94
|
+
readonly "--thinkingMinimal": "#6e6974";
|
|
95
|
+
readonly "--thinkingLow": "#945ff9";
|
|
96
|
+
readonly "--thinkingMedium": "#b281d6";
|
|
97
|
+
readonly "--thinkingHigh": "#ed4abf";
|
|
98
|
+
readonly "--thinkingXhigh": "#e4b33f";
|
|
99
|
+
readonly "--bashMode": "#5ad8e5";
|
|
100
|
+
readonly "--pythonMode": "#e4b33f";
|
|
101
|
+
readonly "--statusLineBg": "#0f0b14";
|
|
102
|
+
readonly "--statusLineSep": "#6e6974";
|
|
103
|
+
readonly "--statusLineModel": "#ed4abf";
|
|
104
|
+
readonly "--statusLinePath": "#5ad8e5";
|
|
105
|
+
readonly "--statusLineGitClean": "#68ca80";
|
|
106
|
+
readonly "--statusLineGitDirty": "#e4b33f";
|
|
107
|
+
readonly "--statusLineContext": "#a49faa";
|
|
108
|
+
readonly "--statusLineSpend": "#5ad8e5";
|
|
109
|
+
readonly "--statusLineStaged": "#68ca80";
|
|
110
|
+
readonly "--statusLineDirty": "#e4b33f";
|
|
111
|
+
readonly "--statusLineUntracked": "#945ff9";
|
|
112
|
+
readonly "--statusLineOutput": "#b281d6";
|
|
113
|
+
readonly "--statusLineCost": "#b281d6";
|
|
114
|
+
readonly "--statusLineSubagents": "#ed4abf";
|
|
115
|
+
};
|
|
116
|
+
/** Serialize the palette as `--key: value;` declarations for `:root { … }`. */
|
|
117
|
+
export declare function webExportThemeVars(): string;
|
|
@@ -28,6 +28,13 @@ export interface RecallResultLike {
|
|
|
28
28
|
* retaining.
|
|
29
29
|
*/
|
|
30
30
|
export declare function stripMemoryTags(content: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* True when `content` carries at least one letter or digit. Used by retain
|
|
33
|
+
* and recall paths to drop placeholder assistant turns ("." / "..." / pure
|
|
34
|
+
* whitespace) that would otherwise pollute the bank and waste tokens on
|
|
35
|
+
* embeddings with no semantic content.
|
|
36
|
+
*/
|
|
37
|
+
export declare function hasSubstantiveContent(content: string): boolean;
|
|
31
38
|
/** Format recall results into a bullet list for context injection. */
|
|
32
39
|
export declare function formatMemories(results: RecallResultLike[]): string;
|
|
33
40
|
/** Format current UTC time for the recall preamble. */
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* surviving message's `TextContent` parts are joined with newlines.
|
|
8
8
|
*/
|
|
9
9
|
import type { SessionEntry } from "../session/session-entries";
|
|
10
|
-
import type
|
|
10
|
+
import { type HindsightMessage } from "./content";
|
|
11
11
|
export interface ReadonlySessionManagerLike {
|
|
12
12
|
getEntries(): SessionEntry[];
|
|
13
13
|
}
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* Supports Ctrl+G for external editor.
|
|
4
4
|
*
|
|
5
5
|
* Two modes:
|
|
6
|
-
* - Default (hook): Enter inserts newline,
|
|
6
|
+
* - Default (hook): Enter inserts newline, the `app.message.followUp` chord
|
|
7
|
+
* (Ctrl+Q / Ctrl+Enter) submits, bordered popup
|
|
7
8
|
* - Prompt-style (ask): Enter submits, Shift+Enter inserts newline, legacy ask chrome
|
|
8
9
|
*/
|
|
9
10
|
import { Container, type TUI } from "@oh-my-pi/pi-tui";
|
|
@@ -95,6 +95,7 @@ export declare class InteractiveMode implements InteractiveModeContext {
|
|
|
95
95
|
hookWidgetContainerBelow: Container;
|
|
96
96
|
statusLine: StatusLineComponent;
|
|
97
97
|
isInitialized: boolean;
|
|
98
|
+
initialChatRendered: boolean;
|
|
98
99
|
isBashMode: boolean;
|
|
99
100
|
toolOutputExpanded: boolean;
|
|
100
101
|
todoExpanded: boolean;
|
|
@@ -183,6 +184,10 @@ export declare class InteractiveMode implements InteractiveModeContext {
|
|
|
183
184
|
withLocalSubmission<T>(text: string, fn: () => Promise<T>, options?: {
|
|
184
185
|
imageCount?: number;
|
|
185
186
|
}): Promise<T>;
|
|
187
|
+
clearOptimisticUserMessage(): void;
|
|
188
|
+
replaceOptimisticUserMessage(message: AgentMessage, options?: {
|
|
189
|
+
imageLinks?: readonly (string | undefined)[];
|
|
190
|
+
}): void;
|
|
186
191
|
startPendingSubmission(input: {
|
|
187
192
|
text: string;
|
|
188
193
|
images?: ImageContent[];
|
|
@@ -118,6 +118,17 @@ export interface InteractiveModeContext {
|
|
|
118
118
|
eventController: EventController;
|
|
119
119
|
eventBus?: EventBus;
|
|
120
120
|
isInitialized: boolean;
|
|
121
|
+
/**
|
|
122
|
+
* `true` once `renderInitialMessages` has rendered the session transcript
|
|
123
|
+
* into `chatContainer` at least once.
|
|
124
|
+
*
|
|
125
|
+
* Extension chat-rebuilds (`ExtensionUiController.#applyCustomMessageDisplay`)
|
|
126
|
+
* are gated on this: rebuilding before the initial render would plant a
|
|
127
|
+
* session-derived component into the chat that `renderInitialMessages` then
|
|
128
|
+
* both re-renders from session entries AND re-appends via
|
|
129
|
+
* `preserveExistingChat`, duplicating the message (issue #1955).
|
|
130
|
+
*/
|
|
131
|
+
initialChatRendered: boolean;
|
|
121
132
|
isBashMode: boolean;
|
|
122
133
|
toolOutputExpanded: boolean;
|
|
123
134
|
todoExpanded: boolean;
|
|
@@ -237,6 +248,12 @@ export interface InteractiveModeContext {
|
|
|
237
248
|
withLocalSubmission<T>(text: string, fn: () => Promise<T>, options?: {
|
|
238
249
|
imageCount?: number;
|
|
239
250
|
}): Promise<T>;
|
|
251
|
+
/** Clears bookkeeping for an optimistic local user message once the matching session event arrives. */
|
|
252
|
+
clearOptimisticUserMessage(): void;
|
|
253
|
+
/** Replaces the raw optimistic user render with the canonical message emitted by the session. */
|
|
254
|
+
replaceOptimisticUserMessage(message: AgentMessage, options?: {
|
|
255
|
+
imageLinks?: readonly (string | undefined)[];
|
|
256
|
+
}): void;
|
|
240
257
|
isKnownSlashCommand(text: string): boolean;
|
|
241
258
|
addMessageToChat(message: AgentMessage, options?: {
|
|
242
259
|
populateHistory?: boolean;
|
|
@@ -17,3 +17,16 @@ export declare function matchesSelectPageUp(data: string): boolean;
|
|
|
17
17
|
/** Match the generic selector page-down keybinding. */
|
|
18
18
|
export declare function matchesSelectPageDown(data: string): boolean;
|
|
19
19
|
export declare function matchesAppExternalEditor(data: string): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Match the "submit multi-line text input" keybinding (`app.message.followUp`).
|
|
22
|
+
*
|
|
23
|
+
* Used by forms where plain Enter inserts a newline and a modified-Enter chord
|
|
24
|
+
* submits — the main editor's follow-up handler, the agent dashboard's new-agent
|
|
25
|
+
* description, and the hook editor's hook-style mode. The keybinding defaults to
|
|
26
|
+
* `["ctrl+q", "ctrl+enter"]` so Windows Terminal (which can't deliver a distinct
|
|
27
|
+
* Ctrl+Enter event; #1903) still has a working chord without user remapping.
|
|
28
|
+
*
|
|
29
|
+
* Also recognizes modifier-tagged LF as Ctrl+Enter only when Ctrl+Enter is an
|
|
30
|
+
* effective follow-up binding.
|
|
31
|
+
*/
|
|
32
|
+
export declare function matchesAppFollowUp(data: string): boolean;
|
|
@@ -156,6 +156,8 @@ export interface AgentSessionConfig {
|
|
|
156
156
|
modelRegistry: ModelRegistry;
|
|
157
157
|
/** Tool registry for LSP and settings */
|
|
158
158
|
toolRegistry?: Map<string, AgentTool>;
|
|
159
|
+
/** Tool names whose current registry entry is still the built-in implementation. */
|
|
160
|
+
builtInToolNames?: Iterable<string>;
|
|
159
161
|
/** Current session pre-LLM message transform pipeline */
|
|
160
162
|
transformContext?: (messages: AgentMessage[], signal?: AbortSignal) => AgentMessage[] | Promise<AgentMessage[]>;
|
|
161
163
|
/** Provider payload hook used by the active session request path */
|
|
@@ -511,6 +513,8 @@ export declare class AgentSession {
|
|
|
511
513
|
* Get a tool by name from the registry.
|
|
512
514
|
*/
|
|
513
515
|
getToolByName(name: string): AgentTool | undefined;
|
|
516
|
+
/** True when the current registry entry for `name` came from a built-in factory. */
|
|
517
|
+
hasBuiltInTool(name: string): boolean;
|
|
514
518
|
/**
|
|
515
519
|
* Get all configured tool names (built-in via --tools or default, plus custom tools).
|
|
516
520
|
*/
|
|
@@ -4,6 +4,8 @@ import { type CompactionEntry, type SessionEntry } from "./session-entries";
|
|
|
4
4
|
export interface SessionContext {
|
|
5
5
|
messages: AgentMessage[];
|
|
6
6
|
thinkingLevel?: string;
|
|
7
|
+
/** Configured thinking selector (`"auto"` or a concrete level) from the latest change. */
|
|
8
|
+
configuredThinkingLevel?: string;
|
|
7
9
|
serviceTier?: ServiceTier;
|
|
8
10
|
/** Model roles: { default: "provider/modelId", small: "provider/modelId", ... } */
|
|
9
11
|
models: Record<string, string>;
|
|
@@ -30,6 +30,12 @@ export interface SessionMessageEntry extends SessionEntryBase {
|
|
|
30
30
|
export interface ThinkingLevelChangeEntry extends SessionEntryBase {
|
|
31
31
|
type: "thinking_level_change";
|
|
32
32
|
thinkingLevel?: string | null;
|
|
33
|
+
/**
|
|
34
|
+
* The user-configured selector at the time of this change: `"auto"` when auto
|
|
35
|
+
* mode was active, otherwise the concrete level. Absent on entries written
|
|
36
|
+
* before auto-mode persistence existed; readers fall back to `thinkingLevel`.
|
|
37
|
+
*/
|
|
38
|
+
configured?: string | null;
|
|
33
39
|
}
|
|
34
40
|
export interface ModelChangeEntry extends SessionEntryBase {
|
|
35
41
|
type: "model_change";
|
|
@@ -138,7 +138,8 @@ export declare class SessionManager {
|
|
|
138
138
|
* top-level entries via appendCompaction()/branchWithSummary().
|
|
139
139
|
*/
|
|
140
140
|
appendMessage(message: Message | CustomMessage | HookMessage | BashExecutionMessage | PythonExecutionMessage | FileMentionMessage): string;
|
|
141
|
-
|
|
141
|
+
/** Append a thinking level change as child of current leaf, then advance leaf. Returns entry id. */
|
|
142
|
+
appendThinkingLevelChange(thinkingLevel?: string, configured?: string): string;
|
|
142
143
|
appendServiceTierChange(serviceTier: ServiceTier | null): string;
|
|
143
144
|
appendModeChange(mode: string, data?: Record<string, unknown>): string;
|
|
144
145
|
/**
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared ACP client bridge routing for file-write sites.
|
|
3
|
+
*
|
|
4
|
+
* When an ACP client (e.g. Zed) advertises the `fs.writeTextFile` capability,
|
|
5
|
+
* all write-mode tools must route through it so the editor's open buffer is
|
|
6
|
+
* updated immediately. Internal artifacts ('/Users/theo/.omp/agent/sessions/-Projects-oh-my-pi/2026-06-10T09-11-41-506Z_019eb0cd-3ec2-7000-92aa-1b82aa4d78f0/local' plan files, other scheme
|
|
7
|
+
* URLs) are always written directly to disk — those are OMP-owned and should
|
|
8
|
+
* never be pushed into the editor.
|
|
9
|
+
*/
|
|
10
|
+
import type { ToolSession } from ".";
|
|
11
|
+
/**
|
|
12
|
+
* Return `true` when an ACP client bridge write is appropriate for this path.
|
|
13
|
+
*
|
|
14
|
+
* Returns `false` for internal-URL paths (e.g. `'/Users/theo/.omp/agent/sessions/-Projects-oh-my-pi/2026-06-10T09-11-41-506Z_019eb0cd-3ec2-7000-92aa-1b82aa4d78f0/local/PLAN.md'`) and for the
|
|
15
|
+
* active plan file while plan mode is enabled — both are OMP-internal artifacts
|
|
16
|
+
* that must stay off the editor's buffer.
|
|
17
|
+
*/
|
|
18
|
+
export declare function shouldRouteWriteThroughBridge(session: ToolSession, requestedPath: string, absolutePath: string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Try to route a file write through the ACP client bridge.
|
|
21
|
+
*
|
|
22
|
+
* Performs the full guard check, bridge call (wrapped in {@link ToolError}),
|
|
23
|
+
* FS-scan cache invalidation, and session mutation-version bump.
|
|
24
|
+
*
|
|
25
|
+
* Returns `true` when the bridge was used and the caller must skip the
|
|
26
|
+
* writethrough path. Returns `false` when the bridge is unavailable or the
|
|
27
|
+
* path should not be routed through it.
|
|
28
|
+
*/
|
|
29
|
+
export declare function routeWriteThroughBridge(session: ToolSession, requestedPath: string, absolutePath: string, content: string): Promise<boolean>;
|
|
@@ -93,6 +93,9 @@ export declare class CmuxTab {
|
|
|
93
93
|
waitFor(selector: string, opts?: {
|
|
94
94
|
timeout?: number;
|
|
95
95
|
}): Promise<CmuxElementHandle>;
|
|
96
|
+
waitForSelector(selector: string, opts?: {
|
|
97
|
+
timeout?: number;
|
|
98
|
+
}): Promise<CmuxElementHandle>;
|
|
96
99
|
evaluate<TResult, TArgs extends unknown[]>(fn: string | ((...args: TArgs) => TResult | Promise<TResult>), ...args: TArgs): Promise<TResult>;
|
|
97
100
|
scrollIntoView(selector: string): Promise<void>;
|
|
98
101
|
select(selector: string, ...values: string[]): Promise<string[]>;
|
|
@@ -101,6 +104,10 @@ export declare class CmuxTab {
|
|
|
101
104
|
waitForUrl(pattern: string | RegExp, opts?: {
|
|
102
105
|
timeout?: number;
|
|
103
106
|
}): Promise<string>;
|
|
107
|
+
waitForNavigation(opts?: {
|
|
108
|
+
waitUntil?: WaitUntil;
|
|
109
|
+
timeout?: number;
|
|
110
|
+
}): Promise<null>;
|
|
104
111
|
drag(from: DragTarget, to: DragTarget): Promise<void>;
|
|
105
112
|
uploadFile(selector: string, ...filePaths: string[]): Promise<void>;
|
|
106
113
|
waitForResponse(pattern: string | RegExp | ((response: CmuxResponse) => boolean | Promise<boolean>), opts?: {
|
|
@@ -11,12 +11,32 @@ declare global {
|
|
|
11
11
|
elementFromPoint(x: number, y: number): Element | null;
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
|
+
export interface OpTimeouts {
|
|
15
|
+
/** Largest per-op deadline allowed — strictly below the cell budget. */
|
|
16
|
+
budgetBound: number;
|
|
17
|
+
/** Ceiling for quick page reads. */
|
|
18
|
+
quickOpMs: number;
|
|
19
|
+
/** Ceiling for interactive actions + default for waits. */
|
|
20
|
+
actionOpMs: number;
|
|
21
|
+
}
|
|
22
|
+
/** Resolve the per-op fail-fast ceilings for a given cell budget. */
|
|
23
|
+
export declare function resolveOpTimeouts(cellTimeoutMs: number): OpTimeouts;
|
|
24
|
+
/**
|
|
25
|
+
* Effective timeout for a wait helper (`waitFor*`). A positive explicit `{ timeout }` is
|
|
26
|
+
* honored but clamped to the cell budget so it still fails fast + named; raising the tool
|
|
27
|
+
* `timeout` raises that cap, so a longer budget stays meaningful. No `{ timeout }` → the
|
|
28
|
+
* action ceiling. Puppeteer's `{ timeout: 0 }` / `Infinity` ("disable") maps to the largest
|
|
29
|
+
* bounded wait (`budgetBound`) — the harness never permits an unbounded wait. Garbage input
|
|
30
|
+
* (negative, `NaN`) falls back to the action ceiling rather than the longest wait.
|
|
31
|
+
*/
|
|
32
|
+
export declare function resolveWaitTimeout(cellTimeoutMs: number, explicit?: number): number;
|
|
14
33
|
interface ScreenshotOptions {
|
|
15
34
|
selector?: string;
|
|
16
35
|
fullPage?: boolean;
|
|
17
36
|
save?: string;
|
|
18
37
|
silent?: boolean;
|
|
19
38
|
}
|
|
39
|
+
export declare function normalizeSelector(selector: string): string;
|
|
20
40
|
export interface InflightOp {
|
|
21
41
|
label: string;
|
|
22
42
|
startedAt: number;
|
|
@@ -4,9 +4,10 @@ export declare const MAX_IMAGE_INPUT_BYTES: number;
|
|
|
4
4
|
export declare const SUPPORTED_INPUT_IMAGE_MIME_TYPES: Set<string>;
|
|
5
5
|
/**
|
|
6
6
|
* Ollama and its local-backend family decode image input through llama.cpp /
|
|
7
|
-
* `stb_image`, which is compiled without WebP support
|
|
8
|
-
*
|
|
9
|
-
* to PNG/JPEG instead — the
|
|
7
|
+
* `stb_image`, which is compiled without WebP support. The first-party Codex
|
|
8
|
+
* Responses backend also rejects WebP `input_image.image_url` data URLs. Detect
|
|
9
|
+
* those models so the resize pipeline encodes to PNG/JPEG instead — the
|
|
10
|
+
* automatic equivalent of `OMP_NO_WEBP=1`.
|
|
10
11
|
*/
|
|
11
12
|
export declare function modelLacksWebpSupport(model: Pick<Model, "provider" | "api" | "imageInputDecoder"> | undefined): boolean;
|
|
12
13
|
/**
|
package/dist/types/utils/jj.d.ts
CHANGED
|
@@ -47,3 +47,28 @@ export declare const repo: {
|
|
|
47
47
|
/** Check whether `cwd` is inside a Jujutsu repository. */
|
|
48
48
|
is(cwd: string): Promise<boolean>;
|
|
49
49
|
};
|
|
50
|
+
/**
|
|
51
|
+
* Detect a "pure" Jujutsu workspace — one where Git-mutating automation has
|
|
52
|
+
* no safe Git target. Invoking `git checkout -b`, `git worktree add`, or
|
|
53
|
+
* `git apply` against a pure jj workspace either fails outright (no `.git/`
|
|
54
|
+
* present) or mutates state that jj itself cannot reconcile.
|
|
55
|
+
*
|
|
56
|
+
* `cwd` is "pure jj" iff its nearest jj workspace ancestor is **closer than**
|
|
57
|
+
* its nearest Git checkout ancestor (or no Git checkout is present at all).
|
|
58
|
+
* Both lookups walk upward from `cwd`, so the deeper ancestor is the one the
|
|
59
|
+
* user is actually working inside.
|
|
60
|
+
*
|
|
61
|
+
* Returns:
|
|
62
|
+
* - `false` for plain Git checkouts (no jj metadata anywhere up the tree).
|
|
63
|
+
* - `false` for colocated jj-git workspaces — `jj git init --colocate` keeps
|
|
64
|
+
* `.jj/` and `.git/` at the same root.
|
|
65
|
+
* - `false` when a nested Git checkout (e.g. a vendored repo or fixture)
|
|
66
|
+
* lives **under** an outer jj workspace; Git automation targets the inner
|
|
67
|
+
* repo and never touches the surrounding jj tree.
|
|
68
|
+
* - `true` when jj is the deeper ancestor — either a standalone pure jj
|
|
69
|
+
* workspace, or a jj workspace nested under an unrelated outer Git
|
|
70
|
+
* checkout, where Git automation against the outer root would silently
|
|
71
|
+
* bypass jj.
|
|
72
|
+
* - `false` for directories backed by neither tool.
|
|
73
|
+
*/
|
|
74
|
+
export declare function isPureJjRepo(cwd: string): Promise<boolean>;
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { type Api, type Model } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import type { ModelRegistry } from "../config/model-registry";
|
|
3
3
|
import type { Settings } from "../config/settings";
|
|
4
|
-
export declare const TITLE_LOCAL_FALLBACK_DELAY_MS = 10000;
|
|
5
|
-
export declare function raceFirstNonNull<T>(primary: Promise<T | null>, startFallback: () => Promise<T | null>, delayMs?: number, onPrimaryWinAfterFallback?: () => void): Promise<T | null>;
|
|
6
4
|
/**
|
|
7
5
|
* Generate a title for a session based on the first user message.
|
|
8
6
|
*
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "16.1.
|
|
4
|
+
"version": "16.1.11",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -48,17 +48,17 @@
|
|
|
48
48
|
"@agentclientprotocol/sdk": "0.25.0",
|
|
49
49
|
"@babel/parser": "^7.29.7",
|
|
50
50
|
"@mozilla/readability": "^0.6.0",
|
|
51
|
-
"@oh-my-pi/hashline": "16.1.
|
|
52
|
-
"@oh-my-pi/omp-stats": "16.1.
|
|
53
|
-
"@oh-my-pi/pi-agent-core": "16.1.
|
|
54
|
-
"@oh-my-pi/pi-ai": "16.1.
|
|
55
|
-
"@oh-my-pi/pi-catalog": "16.1.
|
|
56
|
-
"@oh-my-pi/pi-mnemopi": "16.1.
|
|
57
|
-
"@oh-my-pi/pi-natives": "16.1.
|
|
58
|
-
"@oh-my-pi/pi-tui": "16.1.
|
|
59
|
-
"@oh-my-pi/pi-utils": "16.1.
|
|
60
|
-
"@oh-my-pi/pi-wire": "16.1.
|
|
61
|
-
"@oh-my-pi/snapcompact": "16.1.
|
|
51
|
+
"@oh-my-pi/hashline": "16.1.11",
|
|
52
|
+
"@oh-my-pi/omp-stats": "16.1.11",
|
|
53
|
+
"@oh-my-pi/pi-agent-core": "16.1.11",
|
|
54
|
+
"@oh-my-pi/pi-ai": "16.1.11",
|
|
55
|
+
"@oh-my-pi/pi-catalog": "16.1.11",
|
|
56
|
+
"@oh-my-pi/pi-mnemopi": "16.1.11",
|
|
57
|
+
"@oh-my-pi/pi-natives": "16.1.11",
|
|
58
|
+
"@oh-my-pi/pi-tui": "16.1.11",
|
|
59
|
+
"@oh-my-pi/pi-utils": "16.1.11",
|
|
60
|
+
"@oh-my-pi/pi-wire": "16.1.11",
|
|
61
|
+
"@oh-my-pi/snapcompact": "16.1.11",
|
|
62
62
|
"@opentelemetry/api": "^1.9.1",
|
|
63
63
|
"@opentelemetry/context-async-hooks": "^2.7.1",
|
|
64
64
|
"@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
|
|
@@ -19,8 +19,10 @@ if (!outPath) {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const loaderJs = await Bun.file(new URL("../src/export/html/share-loader.js", import.meta.url).pathname).text();
|
|
22
|
-
// Pin
|
|
23
|
-
|
|
22
|
+
// Pin the omp brand palette (collab-web pink/purple identity) — the viewer is
|
|
23
|
+
// a public artifact matching the live my.omp.sh client, not a per-user export
|
|
24
|
+
// that should mirror the host's terminal theme.
|
|
25
|
+
const themeVars = await generateThemeVars("web");
|
|
24
26
|
|
|
25
27
|
const html = getTemplate()
|
|
26
28
|
.replace("<theme-vars/>", () => `<style>:root { ${themeVars} }</style>`)
|
package/src/autoresearch/git.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "../extensibility/extensions";
|
|
2
2
|
import * as git from "../utils/git";
|
|
3
|
+
import * as jj from "../utils/jj";
|
|
3
4
|
import { normalizePathSpec } from "./helpers";
|
|
4
5
|
|
|
5
6
|
const AUTORESEARCH_BRANCH_PREFIX = "autoresearch/";
|
|
@@ -37,6 +38,17 @@ export async function ensureAutoresearchBranch(
|
|
|
37
38
|
workDir: string,
|
|
38
39
|
goal: string | null,
|
|
39
40
|
): Promise<EnsureAutoresearchBranchResult> {
|
|
41
|
+
// Pure-jj check runs first so a jj workspace nested under an unrelated
|
|
42
|
+
// outer Git checkout is rejected at its own root rather than silently
|
|
43
|
+
// creating `autoresearch/*` branches and commits in the surrounding Git
|
|
44
|
+
// tree behind jj's back.
|
|
45
|
+
if (await jj.isPureJjRepo(workDir)) {
|
|
46
|
+
return {
|
|
47
|
+
ok: false,
|
|
48
|
+
error: "Autoresearch needs a Git checkout for branch isolation and baseline commits, but this workspace is pure Jujutsu (`.jj/` without a colocated `.git/`). Run `jj git init --colocate` to add a Git checkout before starting autoresearch.",
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
40
52
|
const repoRoot = await git.repo.root(workDir);
|
|
41
53
|
if (!repoRoot) {
|
|
42
54
|
return {
|
|
@@ -91,15 +91,55 @@ async function loadContextFiles(ctx: LoadContext): Promise<LoadResult<ContextFil
|
|
|
91
91
|
/** OpenCode MCP server config (from opencode.json "mcp" key) */
|
|
92
92
|
interface OpenCodeMCPConfig {
|
|
93
93
|
type?: "local" | "remote";
|
|
94
|
-
command?: string;
|
|
94
|
+
command?: string | string[];
|
|
95
95
|
args?: string[];
|
|
96
96
|
env?: Record<string, string>;
|
|
97
|
+
environment?: Record<string, string>;
|
|
97
98
|
url?: string;
|
|
98
99
|
headers?: Record<string, string>;
|
|
99
100
|
enabled?: boolean;
|
|
100
101
|
timeout?: number;
|
|
101
102
|
}
|
|
102
103
|
|
|
104
|
+
function stringArray(value: unknown): string[] | undefined {
|
|
105
|
+
if (!Array.isArray(value)) return undefined;
|
|
106
|
+
for (const item of value) {
|
|
107
|
+
if (typeof item !== "string") return undefined;
|
|
108
|
+
}
|
|
109
|
+
return value;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function stringRecord(value: unknown): Record<string, string> | undefined {
|
|
113
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
114
|
+
|
|
115
|
+
const record: Record<string, string> = {};
|
|
116
|
+
for (const [key, item] of Object.entries(value)) {
|
|
117
|
+
if (typeof item !== "string") return undefined;
|
|
118
|
+
record[key] = item;
|
|
119
|
+
}
|
|
120
|
+
return record;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function normalizeCommand(
|
|
124
|
+
commandValue: string | string[] | undefined,
|
|
125
|
+
argsValue: unknown,
|
|
126
|
+
): { command: string | undefined; args: string[] | undefined } {
|
|
127
|
+
const configuredArgs = stringArray(argsValue);
|
|
128
|
+
if (Array.isArray(commandValue)) {
|
|
129
|
+
const [command, ...commandArgs] = commandValue;
|
|
130
|
+
const args = configuredArgs ? [...commandArgs, ...configuredArgs] : commandArgs;
|
|
131
|
+
return {
|
|
132
|
+
command: typeof command === "string" ? command : undefined,
|
|
133
|
+
args: args.length > 0 ? args : undefined,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
command: typeof commandValue === "string" ? commandValue : undefined,
|
|
139
|
+
args: configuredArgs && configuredArgs.length > 0 ? configuredArgs : undefined,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
103
143
|
async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>> {
|
|
104
144
|
const items: MCPServer[] = [];
|
|
105
145
|
const warnings: string[] = [];
|
|
@@ -161,11 +201,14 @@ function extractMCPServers(
|
|
|
161
201
|
transport = "stdio";
|
|
162
202
|
}
|
|
163
203
|
|
|
204
|
+
const command = normalizeCommand(serverConfig.command, serverConfig.args);
|
|
205
|
+
const env = stringRecord(serverConfig.environment) ?? stringRecord(serverConfig.env);
|
|
206
|
+
|
|
164
207
|
items.push({
|
|
165
208
|
name,
|
|
166
|
-
command:
|
|
167
|
-
args:
|
|
168
|
-
env
|
|
209
|
+
command: command.command,
|
|
210
|
+
args: command.args,
|
|
211
|
+
env,
|
|
169
212
|
url: typeof serverConfig.url === "string" ? serverConfig.url : undefined,
|
|
170
213
|
headers: serverConfig.headers && typeof serverConfig.headers === "object" ? serverConfig.headers : undefined,
|
|
171
214
|
enabled: serverConfig.enabled,
|
|
@@ -20,6 +20,7 @@ import { Filesystem, NotFoundError, type WriteResult } from "@oh-my-pi/hashline"
|
|
|
20
20
|
import { isEnoent } from "@oh-my-pi/pi-utils";
|
|
21
21
|
import type { FileDiagnosticsResult, WritethroughCallback, WritethroughDeferredHandle } from "../../lsp";
|
|
22
22
|
import type { ToolSession } from "../../tools";
|
|
23
|
+
import { routeWriteThroughBridge } from "../../tools/acp-bridge";
|
|
23
24
|
import { assertEditableFileContent } from "../../tools/auto-generated-guard";
|
|
24
25
|
import { invalidateFsScanAfterWrite } from "../../tools/fs-cache-invalidation";
|
|
25
26
|
import { enforcePlanModeWrite, resolvePlanPath } from "../../tools/plan-mode-guard";
|
|
@@ -110,6 +111,13 @@ export class HashlineFilesystem extends Filesystem {
|
|
|
110
111
|
await this.preflightWrite(relativePath);
|
|
111
112
|
const absolutePath = this.resolveAbsolute(relativePath);
|
|
112
113
|
const finalContent = await serializeEditFileText(absolutePath, relativePath, content);
|
|
114
|
+
|
|
115
|
+
// Route through ACP bridge when available; skips internal artifacts.
|
|
116
|
+
if (await routeWriteThroughBridge(this.session, relativePath, absolutePath, finalContent)) {
|
|
117
|
+
this.#diagnosticsByPath.set(relativePath, undefined);
|
|
118
|
+
return { text: finalContent };
|
|
119
|
+
}
|
|
120
|
+
|
|
113
121
|
const diagnostics = await this.#writethrough(
|
|
114
122
|
absolutePath,
|
|
115
123
|
finalContent,
|