@oh-my-pi/pi-coding-agent 15.10.9 → 15.10.10
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 +26 -0
- package/dist/types/config/model-registry.d.ts +13 -0
- package/dist/types/config/settings-schema.d.ts +0 -9
- package/dist/types/debug/terminal-info.d.ts +0 -1
- package/dist/types/modes/components/transcript-container.d.ts +12 -26
- package/dist/types/task/discovery.d.ts +1 -2
- package/dist/types/tiny/title-client.d.ts +1 -1
- package/dist/types/tools/todo.d.ts +2 -0
- package/package.json +9 -9
- package/src/cli/list-models.ts +5 -11
- package/src/config/model-registry.ts +91 -20
- package/src/config/settings-schema.ts +0 -10
- package/src/debug/terminal-info.ts +0 -3
- package/src/edit/diff.ts +48 -15
- package/src/eval/js/shared/rewrite-imports.ts +9 -1
- package/src/internal-urls/docs-index.generated.ts +4 -4
- package/src/modes/components/model-selector.ts +62 -52
- package/src/modes/components/transcript-container.ts +134 -107
- package/src/modes/controllers/event-controller.ts +0 -45
- package/src/modes/controllers/input-controller.ts +4 -4
- package/src/modes/controllers/selector-controller.ts +0 -4
- package/src/modes/interactive-mode.ts +2 -10
- package/src/prompts/system/system-prompt.md +3 -3
- package/src/prompts/tools/bash.md +3 -3
- package/src/prompts/tools/todo.md +5 -1
- package/src/task/discovery.ts +17 -24
- package/src/tiny/title-client.ts +6 -3
- package/src/tools/todo.ts +16 -7
- package/src/web/search/providers/anthropic.ts +8 -2
|
@@ -266,10 +266,6 @@ export class SelectorController {
|
|
|
266
266
|
this.ctx.updateEditorBorderColor();
|
|
267
267
|
break;
|
|
268
268
|
|
|
269
|
-
case "clearOnShrink":
|
|
270
|
-
this.ctx.ui.setClearOnShrink(value as boolean);
|
|
271
|
-
break;
|
|
272
|
-
|
|
273
269
|
case "autocompleteMaxVisible":
|
|
274
270
|
this.ctx.editor.setAutocompleteMaxVisible(typeof value === "number" ? value : Number(value));
|
|
275
271
|
break;
|
|
@@ -409,7 +409,6 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
409
409
|
}
|
|
410
410
|
|
|
411
411
|
this.ui = new TUI(new ProcessTerminal(), settings.get("showHardwareCursor"));
|
|
412
|
-
this.ui.setClearOnShrink(settings.get("clearOnShrink"));
|
|
413
412
|
this.ui.setMaxInlineImages(settings.get("tui.maxInlineImages"));
|
|
414
413
|
// OSC 66 text-sizing is Kitty-only; resolve the setting against the terminal's
|
|
415
414
|
// capability (`TERMINAL.textSizing` defaults on for Kitty) so it stays off
|
|
@@ -429,7 +428,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
429
428
|
this.ui.requestRender(true);
|
|
430
429
|
};
|
|
431
430
|
this.editor.onAutocompleteUpdate = () => {
|
|
432
|
-
this.ui.requestRender(
|
|
431
|
+
this.ui.requestRender();
|
|
433
432
|
};
|
|
434
433
|
this.#syncEditorMaxHeight();
|
|
435
434
|
this.#resizeHandler = () => {
|
|
@@ -959,13 +958,6 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
959
958
|
}
|
|
960
959
|
this.editor.setText("");
|
|
961
960
|
this.editor.imageLinks = undefined;
|
|
962
|
-
// Reconciliation checkpoint: only retire frozen block snapshots after TUI
|
|
963
|
-
// proves the native viewport is at the tail and replays scrollback safely.
|
|
964
|
-
// Unknown host viewports stay frozen; thawing them would expose live rows
|
|
965
|
-
// over stale native history and can yank or duplicate when ED3 is unsafe.
|
|
966
|
-
if (this.ui.refreshNativeScrollbackIfDirty()) {
|
|
967
|
-
this.chatContainer.thaw();
|
|
968
|
-
}
|
|
969
961
|
this.ensureLoadingAnimation();
|
|
970
962
|
this.ui.requestRender();
|
|
971
963
|
return submission;
|
|
@@ -2587,7 +2579,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2587
2579
|
this.ui.requestRender(true);
|
|
2588
2580
|
};
|
|
2589
2581
|
nextEditor.onAutocompleteUpdate = () => {
|
|
2590
|
-
this.ui.requestRender(
|
|
2582
|
+
this.ui.requestRender();
|
|
2591
2583
|
};
|
|
2592
2584
|
nextEditor.setMaxHeight(this.#computeEditorMaxHeight());
|
|
2593
2585
|
if (this.historyStorage) {
|
|
@@ -60,10 +60,10 @@ You MUST use the specialized tool over its shell equivalent:
|
|
|
60
60
|
{{#has tools "search"}}- regex search → `{{toolRefs.search}}`, not `grep`/`rg`/`awk`{{/has}}
|
|
61
61
|
{{#has tools "find"}}- file globbing → `{{toolRefs.find}}`, not `ls **/*.ext`/`fd`{{/has}}
|
|
62
62
|
{{#has tools "eval"}}- Then, you MAY use `{{toolRefs.eval}}` for quick compute, but you SHOULD go step by step.{{/has}}
|
|
63
|
-
{{#has tools "bash"}}- Finally, you MAY use `{{toolRefs.bash}}` for
|
|
63
|
+
{{#has tools "bash"}}- Finally, you MAY use `{{toolRefs.bash}}` for terminal work — builds, tests, git, package managers — and for pipelines that COMPUTE a new fact: `wc -l`, `sort | uniq -c`, `comm`, `diff a b`, checksums. Commands shadowing the tools above are intercepted and blocked at runtime.
|
|
64
|
+
- Litmus: produces a count, frequency table, set difference, or checksum no tool returns → bash. Merely moves, pages, or trims bytes a tool can fetch → use the tool.
|
|
64
65
|
- You NEVER read line ranges with `sed -n 'A,Bp'`, `awk 'NR≥A && NR≤B'`, or `head | tail` pipelines. Use `{{toolRefs.read}}` with `offset`/`limit`.
|
|
65
|
-
- You NEVER
|
|
66
|
-
- You NEVER suffix commands with `| head -n N` or `| tail -n N` — the harness already streams output and returns a truncated view, with the full result available via `artifact://<id>`.
|
|
66
|
+
- You NEVER trim or silence output: no `| head -n N`, `| tail -n N`, `2>&1`, `2>/dev/null`. stderr is already merged; long output is auto-truncated with the full capture kept at `artifact://<id>`. Trimming destroys data the artifact would have saved.
|
|
67
67
|
- If you catch yourself typing `cat`, `head`, `tail`, `less`, `more`, `ls`, `grep`, `rg`, `find`, `fd`, `sed -i`, `awk -i`, or a heredoc redirect inside a Bash call, stop and switch to the dedicated tool.{{/has}}
|
|
68
68
|
{{#has tools "report_tool_issue"}}
|
|
69
69
|
<critical>
|
|
@@ -13,9 +13,9 @@ Executes bash command in shell session for terminal operations like git, bun, ca
|
|
|
13
13
|
</instruction>
|
|
14
14
|
|
|
15
15
|
<critical>
|
|
16
|
-
- NEVER use
|
|
17
|
-
- NEVER
|
|
18
|
-
-
|
|
16
|
+
- NEVER use shell to fetch, display, list, page, or search content a dedicated tool serves: `cat`/`head`/`tail`/`less`/`more`/`ls` → `read`; `grep`/`rg`/`ag`/`ack` → `search`; `find`/`fd` → `find`; `sed -i`/`perl -i`/`awk -i` → `edit`; `echo >`/heredoc → `write`. The tools keep gitignore semantics, line anchors, and structured output that shell loses.
|
|
17
|
+
- NEVER trim or silence output: no `| head -n N`, `| tail -n N`, `| less`, `2>&1`, `2>/dev/null`. stderr is already merged; long output is auto-truncated with the FULL capture kept at `artifact://<id>`. Defensive trimming is a habit from harnesses without artifact recovery — here it only destroys data the artifact would have saved.
|
|
18
|
+
- Pipelines that COMPUTE a new fact are correct bash: `wc -l`, `sort | uniq -c`, `comm`, `cut`, `diff a b`, `shasum`. Litmus: produces a count, frequency table, set difference, or checksum no tool returns → bash. Merely moves or trims bytes a tool can fetch → use the tool.
|
|
19
19
|
</critical>
|
|
20
20
|
|
|
21
21
|
<output>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Manages a phased task list. Pass `ops`: a flat array of operations.
|
|
4
4
|
The next pending task is auto-promoted to `in_progress` after each completion.
|
|
5
|
-
Allowed `op` values are only `init`, `start`, `done`, `drop`, `rm`, `append`, and `
|
|
5
|
+
Allowed `op` values are only `init`, `start`, `done`, `drop`, `rm`, `append`, `note`, and `view`. `pending` is a task status, not an `op`; leave not-yet-started tasks implicit in `init`/`append` lists.
|
|
6
6
|
|
|
7
7
|
## Operations
|
|
8
8
|
|
|
@@ -15,6 +15,7 @@ Allowed `op` values are only `init`, `start`, `done`, `drop`, `rm`, `append`, an
|
|
|
15
15
|
|`rm`|`task` or `phase`|Remove|
|
|
16
16
|
|`append`|`phase`, `items: string[]`|Append tasks to `phase`; lazily creates phase|
|
|
17
17
|
|`note`|`task`, `text`|Append a note to a task. Reminders for future-you only.|
|
|
18
|
+
|`view`|—|Read-only: echo the current list without modifying it|
|
|
18
19
|
|
|
19
20
|
## Anatomy
|
|
20
21
|
- **Task content**: 5–10 words, what is being done, not how. Used as the task identifier — unique.
|
|
@@ -25,6 +26,7 @@ Allowed `op` values are only `init`, `start`, `done`, `drop`, `rm`, `append`, an
|
|
|
25
26
|
- Complete phases in order.
|
|
26
27
|
- On blockers, `append` a new task to the active phase to unblock yourself, or `drop`.
|
|
27
28
|
- `task` and `phase` fields reference content/name verbatim; keep them stable once introduced.
|
|
29
|
+
- Lost track of exact task text? `view` echoes the full list — NEVER guess content from memory; a mismatched `task` string is an error.
|
|
28
30
|
|
|
29
31
|
## When to create a list
|
|
30
32
|
- Task requires 3+ distinct steps
|
|
@@ -35,6 +37,8 @@ Allowed `op` values are only `init`, `start`, `done`, `drop`, `rm`, `append`, an
|
|
|
35
37
|
<examples>
|
|
36
38
|
# Initial setup (multi-phase)
|
|
37
39
|
`{"ops":[{"op":"init","list":[{"phase":"Foundation","items":["Scaffold crate","Wire workspace"]},{"phase":"Auth","items":["Port credential store","Wire OAuth providers"]},{"phase":"Verification","items":["Run cargo test"]}]}]}`
|
|
40
|
+
# View current state (read-only)
|
|
41
|
+
`{"ops":[{"op":"view"}]}`
|
|
38
42
|
# Initial setup (single phase)
|
|
39
43
|
`{"ops":[{"op":"init","list":[{"phase":"Implementation","items":["Apply fix","Run tests"]}]}]}`
|
|
40
44
|
# Complete one task
|
package/src/task/discovery.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent discovery from filesystem.
|
|
3
3
|
*
|
|
4
|
-
* Discovers agent definitions from:
|
|
5
|
-
* - ~/.omp/agent/agents/*.md (user-level
|
|
6
|
-
* -
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
4
|
+
* Discovers agent definitions from OMP-native task-agent roots:
|
|
5
|
+
* - ~/.omp/agent/agents/*.md (user-level)
|
|
6
|
+
* - .omp/agents/*.md (project-level)
|
|
7
|
+
*
|
|
8
|
+
* Claude Code marketplace plugin agents are discovered separately via the
|
|
9
|
+
* claude-plugins provider. Direct cross-harness roots such as .claude/agents
|
|
10
|
+
* are intentionally skipped because their frontmatter schema is not the OMP
|
|
11
|
+
* task-agent contract.
|
|
11
12
|
*
|
|
12
13
|
* Agent files use markdown with YAML frontmatter.
|
|
13
14
|
*/
|
|
@@ -21,6 +22,8 @@ import { listClaudePluginRoots } from "../discovery/helpers";
|
|
|
21
22
|
import { loadBundledAgents, parseAgent } from "./agents";
|
|
22
23
|
import type { AgentDefinition, AgentSource } from "./types";
|
|
23
24
|
|
|
25
|
+
const TASK_AGENT_CONFIG_SOURCE = ".omp";
|
|
26
|
+
|
|
24
27
|
/** Result of agent discovery */
|
|
25
28
|
export interface DiscoveryResult {
|
|
26
29
|
agents: AgentDefinition[];
|
|
@@ -52,41 +55,31 @@ async function loadAgentsFromDir(dir: string, source: AgentSource): Promise<Agen
|
|
|
52
55
|
/**
|
|
53
56
|
* Discover agents from filesystem and merge with bundled agents.
|
|
54
57
|
*
|
|
55
|
-
* Precedence (highest wins): .omp
|
|
56
|
-
*
|
|
58
|
+
* Precedence (highest wins): project .omp, user .omp, Claude plugin agents, then bundled
|
|
57
59
|
* @param cwd - Current working directory for project agent discovery
|
|
58
60
|
*/
|
|
59
61
|
export async function discoverAgents(cwd: string, home: string = os.homedir()): Promise<DiscoveryResult> {
|
|
60
62
|
const resolvedCwd = path.resolve(cwd);
|
|
61
|
-
const agentSources = Array.from(new Set(getConfigDirs("", { project: false }).map(entry => entry.source)));
|
|
62
63
|
|
|
63
|
-
// Get user directories (priority order: .omp, .pi, .claude, ...)
|
|
64
64
|
const userDirs = getConfigDirs("agents", { project: false })
|
|
65
|
-
.filter(entry =>
|
|
65
|
+
.filter(entry => entry.source === TASK_AGENT_CONFIG_SOURCE)
|
|
66
66
|
.map(entry => ({
|
|
67
67
|
...entry,
|
|
68
68
|
path: path.resolve(entry.path),
|
|
69
69
|
}));
|
|
70
70
|
|
|
71
|
-
// Get project directories by walking up from cwd (priority order)
|
|
72
71
|
const projectDirs = findAllNearestProjectConfigDirs("agents", resolvedCwd)
|
|
73
|
-
.filter(entry =>
|
|
72
|
+
.filter(entry => entry.source === TASK_AGENT_CONFIG_SOURCE)
|
|
74
73
|
.map(entry => ({
|
|
75
74
|
...entry,
|
|
76
75
|
path: path.resolve(entry.path),
|
|
77
76
|
}));
|
|
78
77
|
|
|
79
|
-
const orderedSources = agentSources.filter(
|
|
80
|
-
source => userDirs.some(entry => entry.source === source) || projectDirs.some(entry => entry.source === source),
|
|
81
|
-
);
|
|
82
|
-
|
|
83
78
|
const orderedDirs: Array<{ dir: string; source: AgentSource }> = [];
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (user) orderedDirs.push({ dir: user.path, source: "user" });
|
|
89
|
-
}
|
|
79
|
+
const project = projectDirs[0];
|
|
80
|
+
if (project) orderedDirs.push({ dir: project.path, source: "project" });
|
|
81
|
+
const user = userDirs[0];
|
|
82
|
+
if (user) orderedDirs.push({ dir: user.path, source: "user" });
|
|
90
83
|
|
|
91
84
|
// Load agents from Claude Code marketplace plugins (respects disabledProviders)
|
|
92
85
|
const { roots: pluginRoots } = isProviderEnabled("claude-plugins")
|
package/src/tiny/title-client.ts
CHANGED
|
@@ -122,7 +122,7 @@ function tinyWorkerSpawnCmd(): string[] {
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
interface SpawnedSubprocess {
|
|
125
|
-
proc: Subprocess<"ignore", "
|
|
125
|
+
proc: Subprocess<"ignore", "ignore", "ignore">;
|
|
126
126
|
inbound: Set<(message: TinyTitleWorkerOutbound) => void>;
|
|
127
127
|
errors: Set<(error: Error) => void>;
|
|
128
128
|
/**
|
|
@@ -147,10 +147,13 @@ export function createTinyTitleSubprocess(): SpawnedSubprocess {
|
|
|
147
147
|
cmd: tinyWorkerSpawnCmd(),
|
|
148
148
|
env: tinyWorkerEnv(),
|
|
149
149
|
stdin: "ignore",
|
|
150
|
-
stdout: "
|
|
151
|
-
stderr: "
|
|
150
|
+
stdout: "ignore",
|
|
151
|
+
stderr: "ignore",
|
|
152
152
|
serialization: "advanced",
|
|
153
153
|
windowsHide: true,
|
|
154
|
+
// The worker is an implementation detail of the interactive TUI. Native
|
|
155
|
+
// model runtimes may print progress or decoded text directly; never let
|
|
156
|
+
// those bytes inherit the terminal and corrupt the chat scrollback.
|
|
154
157
|
ipc(message) {
|
|
155
158
|
for (const handler of inbound) handler(message as TinyTitleWorkerOutbound);
|
|
156
159
|
},
|
package/src/tools/todo.ts
CHANGED
|
@@ -51,7 +51,7 @@ export interface TodoToolDetails {
|
|
|
51
51
|
// =============================================================================
|
|
52
52
|
|
|
53
53
|
const TodoOp = z
|
|
54
|
-
.enum(["init", "start", "done", "rm", "drop", "append", "note"] as const)
|
|
54
|
+
.enum(["init", "start", "done", "rm", "drop", "append", "note", "view"] as const)
|
|
55
55
|
.describe("operation to apply");
|
|
56
56
|
|
|
57
57
|
const InitListEntry = z.object({
|
|
@@ -380,6 +380,8 @@ function applyEntry(phases: TodoPhase[], entry: TodoOpEntryValue, errors: string
|
|
|
380
380
|
}
|
|
381
381
|
case "append":
|
|
382
382
|
return appendItems(phases, entry, errors);
|
|
383
|
+
case "view":
|
|
384
|
+
return phases;
|
|
383
385
|
}
|
|
384
386
|
}
|
|
385
387
|
|
|
@@ -523,9 +525,12 @@ export function markdownToPhases(md: string): { phases: TodoPhase[]; errors: str
|
|
|
523
525
|
return { phases, errors };
|
|
524
526
|
}
|
|
525
527
|
|
|
526
|
-
function formatSummary(phases: TodoPhase[], errors: string[]): string {
|
|
528
|
+
function formatSummary(phases: TodoPhase[], errors: string[], readOnly = false): string {
|
|
527
529
|
const tasks = phases.flatMap(phase => phase.tasks);
|
|
528
|
-
if (tasks.length === 0)
|
|
530
|
+
if (tasks.length === 0) {
|
|
531
|
+
if (errors.length > 0) return `Errors: ${errors.join("; ")}`;
|
|
532
|
+
return readOnly ? "Todo list is empty." : "Todo list cleared.";
|
|
533
|
+
}
|
|
529
534
|
|
|
530
535
|
const remainingByPhase = phases
|
|
531
536
|
.map(phase => ({
|
|
@@ -608,15 +613,19 @@ export class TodoTool implements AgentTool<typeof todoSchema, TodoToolDetails> {
|
|
|
608
613
|
_context?: AgentToolContext,
|
|
609
614
|
): Promise<AgentToolResult<TodoToolDetails>> {
|
|
610
615
|
const previousPhases = clonePhases(this.session.getTodoPhases?.() ?? []);
|
|
611
|
-
|
|
612
|
-
const
|
|
613
|
-
|
|
616
|
+
// Pure-view calls are reads: no normalization, no state write.
|
|
617
|
+
const readOnly = params.ops.every(entry => entry.op === "view");
|
|
618
|
+
const { phases: updated, errors } = readOnly
|
|
619
|
+
? { phases: previousPhases, errors: [] as string[] }
|
|
620
|
+
: applyParams(clonePhases(previousPhases), params);
|
|
621
|
+
const completedTasks = readOnly ? [] : getCompletionTransitions(previousPhases, updated);
|
|
622
|
+
if (!readOnly) this.session.setTodoPhases?.(updated);
|
|
614
623
|
const storage = this.session.getSessionFile() ? "session" : "memory";
|
|
615
624
|
const details: TodoToolDetails = { phases: updated, storage };
|
|
616
625
|
if (completedTasks.length > 0) details.completedTasks = completedTasks;
|
|
617
626
|
|
|
618
627
|
return {
|
|
619
|
-
content: [{ type: "text", text: formatSummary(updated, errors) }],
|
|
628
|
+
content: [{ type: "text", text: formatSummary(updated, errors, readOnly) }],
|
|
620
629
|
details,
|
|
621
630
|
isError: errors.length > 0 ? true : undefined,
|
|
622
631
|
};
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
type FetchImpl,
|
|
17
17
|
stripClaudeToolPrefix,
|
|
18
18
|
withAuth,
|
|
19
|
+
wrapFetchForCch,
|
|
19
20
|
} from "@oh-my-pi/pi-ai";
|
|
20
21
|
import { $env } from "@oh-my-pi/pi-utils";
|
|
21
22
|
import type {
|
|
@@ -64,7 +65,9 @@ function buildSystemBlocks(
|
|
|
64
65
|
model: string,
|
|
65
66
|
systemPrompt?: string,
|
|
66
67
|
): AnthropicSystemBlock[] | undefined {
|
|
67
|
-
|
|
68
|
+
// Match the streaming path: the CC billing header + system instruction are
|
|
69
|
+
// an OAuth fingerprint and must not be claimed on API-key requests.
|
|
70
|
+
const includeClaudeCode = auth.isOAuth && !model.startsWith("claude-3-5-haiku");
|
|
68
71
|
const extraInstructions = auth.isOAuth ? ["You are a helpful AI assistant with web search capabilities."] : [];
|
|
69
72
|
|
|
70
73
|
return buildAnthropicSystemBlocks(systemPrompt ? [systemPrompt] : undefined, {
|
|
@@ -118,7 +121,10 @@ async function callSearch(
|
|
|
118
121
|
body.system = systemBlocks;
|
|
119
122
|
}
|
|
120
123
|
|
|
121
|
-
|
|
124
|
+
// OAuth requests inject the CC billing header (buildSystemBlocks); patch its
|
|
125
|
+
// cch attestation like the streaming path instead of shipping `cch=00000`.
|
|
126
|
+
const doFetch = auth.isOAuth ? wrapFetchForCch(fetchImpl) : fetchImpl;
|
|
127
|
+
const response = await doFetch(url, {
|
|
122
128
|
method: "POST",
|
|
123
129
|
headers,
|
|
124
130
|
body: JSON.stringify(body),
|