@oh-my-pi/pi-coding-agent 16.1.9 → 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 +46 -0
- package/dist/cli.js +3455 -3430
- package/dist/types/config/settings-schema.d.ts +10 -0
- package/dist/types/edit/streaming.d.ts +5 -5
- 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/controllers/selector-controller.d.ts +0 -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/sdk.d.ts +1 -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/system-prompt.d.ts +2 -0
- package/dist/types/tools/acp-bridge.d.ts +29 -0
- package/dist/types/tools/browser/aria/aria-snapshot.d.ts +39 -0
- package/dist/types/tools/browser/cmux/cmux-tab.d.ts +10 -0
- package/dist/types/tools/browser/tab-worker.d.ts +20 -0
- package/dist/types/tools/browser.d.ts +1 -0
- package/dist/types/tools/find.d.ts +1 -2
- package/dist/types/tools/write.d.ts +1 -2
- 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-aria-snapshot.ts +134 -0
- package/scripts/generate-share-viewer.ts +4 -2
- package/src/autoresearch/git.ts +12 -0
- package/src/config/settings-schema.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/edit/streaming.ts +5 -5
- 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 +29 -5
- package/src/modes/controllers/extension-ui-controller.ts +6 -2
- package/src/modes/controllers/selector-controller.ts +3 -5
- 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/system/project-prompt.md +2 -0
- package/src/prompts/system/system-prompt.md +0 -1
- package/src/prompts/tools/browser.md +5 -2
- package/src/sdk.ts +20 -2
- 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/system-prompt.ts +15 -3
- package/src/task/worktree.ts +12 -4
- package/src/thinking.ts +1 -1
- package/src/tools/acp-bridge.ts +66 -0
- package/src/tools/bash.ts +3 -3
- package/src/tools/browser/aria/aria-snapshot.bundle.txt +7 -0
- package/src/tools/browser/aria/aria-snapshot.ts +103 -0
- package/src/tools/browser/cmux/cmux-tab.ts +73 -1
- package/src/tools/browser/tab-worker.ts +242 -47
- package/src/tools/browser.ts +5 -0
- package/src/tools/find.ts +1 -2
- package/src/tools/index.ts +1 -1
- package/src/tools/write.ts +3 -27
- package/src/utils/git.ts +3 -2
- package/src/utils/image-loading.ts +6 -3
- package/src/utils/jj.ts +47 -0
- package/src/utils/title-generator.ts +31 -99
|
@@ -9,9 +9,10 @@ export const SUPPORTED_INPUT_IMAGE_MIME_TYPES = SUPPORTED_IMAGE_MIME_TYPES;
|
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Ollama and its local-backend family decode image input through llama.cpp /
|
|
12
|
-
* `stb_image`, which is compiled without WebP support
|
|
13
|
-
*
|
|
14
|
-
* to PNG/JPEG instead — the
|
|
12
|
+
* `stb_image`, which is compiled without WebP support. The first-party Codex
|
|
13
|
+
* Responses backend also rejects WebP `input_image.image_url` data URLs. Detect
|
|
14
|
+
* those models so the resize pipeline encodes to PNG/JPEG instead — the
|
|
15
|
+
* automatic equivalent of `OMP_NO_WEBP=1`.
|
|
15
16
|
*/
|
|
16
17
|
export function modelLacksWebpSupport(
|
|
17
18
|
model: Pick<Model, "provider" | "api" | "imageInputDecoder"> | undefined,
|
|
@@ -19,6 +20,8 @@ export function modelLacksWebpSupport(
|
|
|
19
20
|
if (!model) return false;
|
|
20
21
|
return (
|
|
21
22
|
model.imageInputDecoder === "stb" ||
|
|
23
|
+
model.api === "openai-codex-responses" ||
|
|
24
|
+
model.provider === "openai-codex" ||
|
|
22
25
|
model.provider === "ollama" ||
|
|
23
26
|
model.provider === "ollama-cloud" ||
|
|
24
27
|
model.provider === "llama.cpp" ||
|
package/src/utils/jj.ts
CHANGED
|
@@ -2,6 +2,7 @@ import * as fs from "node:fs/promises";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { $which } from "@oh-my-pi/pi-utils";
|
|
4
4
|
import { LRUCache } from "lru-cache/raw";
|
|
5
|
+
import * as git from "./git";
|
|
5
6
|
|
|
6
7
|
// ════════════════════════════════════════════════════════════════════════════
|
|
7
8
|
// Types
|
|
@@ -246,3 +247,49 @@ export const repo = {
|
|
|
246
247
|
return (await repo.root(cwd)) !== null;
|
|
247
248
|
},
|
|
248
249
|
};
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Detect a "pure" Jujutsu workspace — one where Git-mutating automation has
|
|
253
|
+
* no safe Git target. Invoking `git checkout -b`, `git worktree add`, or
|
|
254
|
+
* `git apply` against a pure jj workspace either fails outright (no `.git/`
|
|
255
|
+
* present) or mutates state that jj itself cannot reconcile.
|
|
256
|
+
*
|
|
257
|
+
* `cwd` is "pure jj" iff its nearest jj workspace ancestor is **closer than**
|
|
258
|
+
* its nearest Git checkout ancestor (or no Git checkout is present at all).
|
|
259
|
+
* Both lookups walk upward from `cwd`, so the deeper ancestor is the one the
|
|
260
|
+
* user is actually working inside.
|
|
261
|
+
*
|
|
262
|
+
* Returns:
|
|
263
|
+
* - `false` for plain Git checkouts (no jj metadata anywhere up the tree).
|
|
264
|
+
* - `false` for colocated jj-git workspaces — `jj git init --colocate` keeps
|
|
265
|
+
* `.jj/` and `.git/` at the same root.
|
|
266
|
+
* - `false` when a nested Git checkout (e.g. a vendored repo or fixture)
|
|
267
|
+
* lives **under** an outer jj workspace; Git automation targets the inner
|
|
268
|
+
* repo and never touches the surrounding jj tree.
|
|
269
|
+
* - `true` when jj is the deeper ancestor — either a standalone pure jj
|
|
270
|
+
* workspace, or a jj workspace nested under an unrelated outer Git
|
|
271
|
+
* checkout, where Git automation against the outer root would silently
|
|
272
|
+
* bypass jj.
|
|
273
|
+
* - `false` for directories backed by neither tool.
|
|
274
|
+
*/
|
|
275
|
+
export async function isPureJjRepo(cwd: string): Promise<boolean> {
|
|
276
|
+
const jjRoot = await repo.root(cwd);
|
|
277
|
+
if (jjRoot === null) return false;
|
|
278
|
+
const gitRoot = await git.repo.root(cwd);
|
|
279
|
+
if (gitRoot === null) return true;
|
|
280
|
+
return isStrictDescendant(path.resolve(jjRoot), path.resolve(gitRoot));
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Return `true` when `child` is a strict descendant of `ancestor` (same path
|
|
285
|
+
* counts as `false`). Both arguments must already be resolved absolute paths.
|
|
286
|
+
*/
|
|
287
|
+
function isStrictDescendant(child: string, ancestor: string): boolean {
|
|
288
|
+
const rel = path.relative(ancestor, child);
|
|
289
|
+
if (rel === "" || rel === ".") return false;
|
|
290
|
+
if (rel.startsWith("..")) return false;
|
|
291
|
+
// `path.relative` returns an absolute path only when the two arguments
|
|
292
|
+
// live on different filesystem roots (Windows drives, UNC shares); not a
|
|
293
|
+
// real ancestor relationship.
|
|
294
|
+
return !path.isAbsolute(rel);
|
|
295
|
+
}
|
|
@@ -12,7 +12,7 @@ import type { Settings } from "../config/settings";
|
|
|
12
12
|
import titleMarkerInstruction from "../prompts/system/title-marker-instruction.md" with { type: "text" };
|
|
13
13
|
import titleSystemPrompt from "../prompts/system/title-system.md" with { type: "text" };
|
|
14
14
|
import titleMarkerSystemPrompt from "../prompts/system/title-system-marker.md" with { type: "text" };
|
|
15
|
-
import { ONLINE_TINY_TITLE_MODEL_KEY } from "../tiny/models";
|
|
15
|
+
import { isTinyTitleLocalModelKey, ONLINE_TINY_TITLE_MODEL_KEY } from "../tiny/models";
|
|
16
16
|
import { formatTitleUserMessage, isLowSignalTitleInput, normalizeGeneratedTitle } from "../tiny/text";
|
|
17
17
|
import { tinyTitleClient } from "../tiny/title-client";
|
|
18
18
|
|
|
@@ -23,7 +23,6 @@ const TITLE_MARKER_INSTRUCTION = prompt.render(titleMarkerInstruction);
|
|
|
23
23
|
const DEFAULT_TERMINAL_TITLE = "π";
|
|
24
24
|
const TERMINAL_TITLE_CONTROL_CHARS = /[\u0000-\u001f\u007f-\u009f]/g;
|
|
25
25
|
|
|
26
|
-
export const TITLE_LOCAL_FALLBACK_DELAY_MS = 10_000;
|
|
27
26
|
const TITLE_MAX_TOKENS = 30;
|
|
28
27
|
const REASONING_SAFE_MAX_TOKENS = 1024;
|
|
29
28
|
const SET_TITLE_TOOL_NAME = "set_title";
|
|
@@ -81,78 +80,6 @@ function getTitleModel(registry: ModelRegistry, settings: Settings, currentModel
|
|
|
81
80
|
return undefined;
|
|
82
81
|
}
|
|
83
82
|
|
|
84
|
-
export async function raceFirstNonNull<T>(
|
|
85
|
-
primary: Promise<T | null>,
|
|
86
|
-
startFallback: () => Promise<T | null>,
|
|
87
|
-
delayMs: number = TITLE_LOCAL_FALLBACK_DELAY_MS,
|
|
88
|
-
onPrimaryWinAfterFallback?: () => void,
|
|
89
|
-
): Promise<T | null> {
|
|
90
|
-
const { promise, resolve } = Promise.withResolvers<T | null>();
|
|
91
|
-
let resolved = false;
|
|
92
|
-
let primarySettled = false;
|
|
93
|
-
let fallbackStarted = false;
|
|
94
|
-
let fallbackSettled = false;
|
|
95
|
-
|
|
96
|
-
const resolveOnce = (value: T | null): void => {
|
|
97
|
-
if (resolved) return;
|
|
98
|
-
resolved = true;
|
|
99
|
-
resolve(value);
|
|
100
|
-
};
|
|
101
|
-
const maybeResolveNull = (): void => {
|
|
102
|
-
if (primarySettled && fallbackStarted && fallbackSettled) resolveOnce(null);
|
|
103
|
-
};
|
|
104
|
-
const startFallbackOnce = (): void => {
|
|
105
|
-
if (fallbackStarted || resolved) return;
|
|
106
|
-
fallbackStarted = true;
|
|
107
|
-
let fallback: Promise<T | null>;
|
|
108
|
-
try {
|
|
109
|
-
fallback = startFallback();
|
|
110
|
-
} catch {
|
|
111
|
-
fallbackSettled = true;
|
|
112
|
-
maybeResolveNull();
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
void fallback.then(
|
|
116
|
-
value => {
|
|
117
|
-
fallbackSettled = true;
|
|
118
|
-
if (value !== null) resolveOnce(value);
|
|
119
|
-
else maybeResolveNull();
|
|
120
|
-
},
|
|
121
|
-
() => {
|
|
122
|
-
fallbackSettled = true;
|
|
123
|
-
maybeResolveNull();
|
|
124
|
-
},
|
|
125
|
-
);
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
const timer = setTimeout(startFallbackOnce, delayMs);
|
|
129
|
-
void primary.then(
|
|
130
|
-
value => {
|
|
131
|
-
primarySettled = true;
|
|
132
|
-
clearTimeout(timer);
|
|
133
|
-
if (value !== null) {
|
|
134
|
-
if (fallbackStarted) onPrimaryWinAfterFallback?.();
|
|
135
|
-
resolveOnce(value);
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
startFallbackOnce();
|
|
139
|
-
maybeResolveNull();
|
|
140
|
-
},
|
|
141
|
-
() => {
|
|
142
|
-
primarySettled = true;
|
|
143
|
-
clearTimeout(timer);
|
|
144
|
-
startFallbackOnce();
|
|
145
|
-
maybeResolveNull();
|
|
146
|
-
},
|
|
147
|
-
);
|
|
148
|
-
|
|
149
|
-
try {
|
|
150
|
-
return await promise;
|
|
151
|
-
} finally {
|
|
152
|
-
clearTimeout(timer);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
83
|
/**
|
|
157
84
|
* Generate a title for a session based on the first user message.
|
|
158
85
|
*
|
|
@@ -200,36 +127,41 @@ export async function generateSessionTitle(
|
|
|
200
127
|
);
|
|
201
128
|
}
|
|
202
129
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
130
|
+
// User explicitly picked a local tiny model. NEVER fall back to the online
|
|
131
|
+
// smol path (issue #3187): the smol role resolves through priority.json and
|
|
132
|
+
// silently bills whatever provider holds the resolved API key — OpenRouter
|
|
133
|
+
// in the reporter's case, leaking real credits without consent. If the
|
|
134
|
+
// local worker fails (unknown key, download missing, transformers.js
|
|
135
|
+
// crash, abort), leave the session untitled; the next user turn retries.
|
|
136
|
+
if (!isTinyTitleLocalModelKey(tinyModel)) {
|
|
137
|
+
logger.warn("title-generator: unknown local tiny model; skipping title (will not fall back to online)", {
|
|
138
|
+
sessionId,
|
|
139
|
+
model: tinyModel,
|
|
140
|
+
reason: "unknown-local-model",
|
|
141
|
+
});
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
const localTitle = titleSystemPrompt
|
|
146
|
+
? await tinyTitleClient.generate(tinyModel, firstMessage, { systemPrompt: titleSystemPrompt })
|
|
147
|
+
: await tinyTitleClient.generate(tinyModel, firstMessage);
|
|
148
|
+
if (!localTitle) {
|
|
149
|
+
logger.warn("title-generator: local tiny model produced no title; skipping (no online fallback)", {
|
|
211
150
|
sessionId,
|
|
212
151
|
model: tinyModel,
|
|
213
|
-
|
|
152
|
+
reason: "local-no-output",
|
|
214
153
|
});
|
|
215
154
|
return null;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
firstMessage,
|
|
221
|
-
registry,
|
|
222
|
-
settings,
|
|
155
|
+
}
|
|
156
|
+
return localTitle;
|
|
157
|
+
} catch (err) {
|
|
158
|
+
logger.warn("title-generator: local tiny model errored; skipping (no online fallback)", {
|
|
223
159
|
sessionId,
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
return raceFirstNonNull(localTitle, startOnline, TITLE_LOCAL_FALLBACK_DELAY_MS, () => {
|
|
231
|
-
onlineAbortController.abort();
|
|
232
|
-
});
|
|
160
|
+
model: tinyModel,
|
|
161
|
+
error: err instanceof Error ? err.message : String(err),
|
|
162
|
+
});
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
233
165
|
}
|
|
234
166
|
|
|
235
167
|
export async function generateTitleOnline(
|