@oh-my-pi/pi-coding-agent 14.9.2 → 14.9.5
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 +89 -0
- package/package.json +7 -7
- package/scripts/format-prompts.ts +3 -3
- package/src/async/job-manager.ts +66 -9
- package/src/capability/rule.ts +20 -0
- package/src/config/model-registry.ts +13 -0
- package/src/config/model-resolver.ts +8 -2
- package/src/config/prompt-templates.ts +0 -5
- package/src/config/settings-schema.ts +39 -1
- package/src/edit/index.ts +8 -0
- package/src/edit/renderer.ts +6 -1
- package/src/edit/streaming.ts +53 -2
- package/src/eval/eval.lark +10 -31
- package/src/eval/index.ts +1 -0
- package/src/eval/js/context-manager.ts +1 -38
- package/src/eval/js/prelude.txt +0 -2
- package/src/eval/parse.ts +156 -255
- package/src/eval/py/executor.ts +24 -8
- package/src/eval/py/index.ts +1 -0
- package/src/eval/py/prelude.py +11 -80
- package/src/eval/sniff.ts +28 -0
- package/src/export/html/template.css +50 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +229 -17
- package/src/extensibility/plugins/loader.ts +31 -6
- package/src/extensibility/skills.ts +20 -0
- package/src/hashline/constants.ts +20 -0
- package/src/hashline/grammar.lark +16 -23
- package/src/hashline/hash.ts +4 -34
- package/src/hashline/input.ts +16 -2
- package/src/hashline/parser.ts +12 -1
- package/src/internal-urls/agent-protocol.ts +64 -52
- package/src/internal-urls/artifact-protocol.ts +52 -51
- package/src/internal-urls/docs-index.generated.ts +34 -1
- package/src/internal-urls/index.ts +6 -19
- package/src/internal-urls/local-protocol.ts +50 -7
- package/src/internal-urls/mcp-protocol.ts +3 -8
- package/src/internal-urls/memory-protocol.ts +90 -59
- package/src/internal-urls/pi-protocol.ts +1 -0
- package/src/internal-urls/router.ts +40 -23
- package/src/internal-urls/rule-protocol.ts +3 -20
- package/src/internal-urls/skill-protocol.ts +5 -27
- package/src/internal-urls/types.ts +18 -2
- package/src/main.ts +1 -1
- package/src/mcp/manager.ts +17 -0
- package/src/modes/components/session-observer-overlay.ts +2 -2
- package/src/modes/components/tool-execution.ts +6 -0
- package/src/modes/components/tree-selector.ts +4 -0
- package/src/modes/controllers/event-controller.ts +23 -2
- package/src/modes/controllers/mcp-command-controller.ts +7 -10
- package/src/modes/interactive-mode.ts +2 -2
- package/src/modes/theme/theme.ts +27 -27
- package/src/modes/types.ts +1 -1
- package/src/modes/utils/ui-helpers.ts +14 -9
- package/src/prompts/commands/orchestrate.md +1 -0
- package/src/prompts/system/custom-system-prompt.md +0 -2
- package/src/prompts/system/project-prompt.md +10 -0
- package/src/prompts/system/subagent-system-prompt.md +18 -9
- package/src/prompts/system/subagent-user-prompt.md +1 -10
- package/src/prompts/system/system-prompt.md +159 -232
- package/src/prompts/tools/ask.md +0 -1
- package/src/prompts/tools/bash.md +0 -34
- package/src/prompts/tools/eval.md +27 -16
- package/src/prompts/tools/github.md +6 -5
- package/src/prompts/tools/hashline.md +1 -0
- package/src/prompts/tools/job.md +14 -6
- package/src/prompts/tools/task.md +20 -3
- package/src/registry/agent-registry.ts +2 -1
- package/src/sdk.ts +87 -89
- package/src/session/agent-session.ts +107 -37
- package/src/session/artifacts.ts +7 -4
- package/src/session/session-manager.ts +30 -1
- package/src/ssh/connection-manager.ts +32 -16
- package/src/ssh/sshfs-mount.ts +10 -7
- package/src/system-prompt.ts +3 -9
- package/src/task/executor.ts +23 -7
- package/src/task/index.ts +57 -36
- package/src/tool-discovery/tool-index.ts +21 -8
- package/src/tools/ast-edit.ts +3 -2
- package/src/tools/ast-grep.ts +3 -2
- package/src/tools/bash.ts +30 -50
- package/src/tools/browser/tab-supervisor.ts +12 -2
- package/src/tools/eval.ts +59 -44
- package/src/tools/fetch.ts +1 -1
- package/src/tools/gh.ts +140 -4
- package/src/tools/index.ts +12 -11
- package/src/tools/job.ts +48 -12
- package/src/tools/path-utils.ts +21 -1
- package/src/tools/read.ts +74 -31
- package/src/tools/search.ts +16 -3
- package/src/tools/todo-write.ts +1 -1
- package/src/utils/file-display-mode.ts +11 -5
- package/src/web/scrapers/mastodon.ts +1 -1
- package/src/web/scrapers/repology.ts +7 -7
- package/src/internal-urls/jobs-protocol.ts +0 -119
- package/src/task/template.ts +0 -47
- package/src/tools/bash-normalize.ts +0 -107
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,95 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [14.9.5] - 2026-05-12
|
|
6
|
+
### Breaking Changes
|
|
7
|
+
|
|
8
|
+
- Removed the `jobs://` internal URL protocol; inspect background jobs via the `job` tool's `list: true` operation instead
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Added `since` and `until` date-range filters to `search_issues`, `search_prs`, `search_commits`, and `search_repos`, accepting relative durations (`m`/`h`/`d`/`w`/`mo`/`y`), ISO dates, and ISO datetimes
|
|
13
|
+
- Added `dateField` support for date filtering (`created` or `updated`) so search results can be constrained by creation, update, pushed (for repos), or committer date (for commits)
|
|
14
|
+
- Added owner-based scoping to async job registration and queries so background jobs can be registered with an `ownerId` and filtered per agent in `getRunningJobs`, `getRecentJobs`, `getAllJobs`, and `cancelAll`
|
|
15
|
+
- Added agent ownership metadata to async jobs started by `task` and `bash` tools so their lifecycle and cancellation is attributed to the creating agent
|
|
16
|
+
- Added `list: true` operation to the `job` tool, returning an immediate snapshot of every job spawned by the calling agent without waiting (replaces the deleted `jobs://` URL)
|
|
17
|
+
- Added per-agent visibility scoping to the `job` tool so `list`, `poll`, and `cancel` only see and act on jobs owned by the calling agent; cross-agent operations now return `not_found`
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- Changed `search_issues`, `search_prs`, `search_commits`, and `search_repos` to allow date-only queries where `query` is omitted if `since`/`until` is provided
|
|
22
|
+
- Changed `search_code` to return a validation error when `since`/`until` is supplied because GitHub code search does not support date qualifiers
|
|
23
|
+
- Changed async job manager ownership so subagents inherit the parent session’s global `AsyncJobManager` instead of creating and owning separate instances
|
|
24
|
+
- Changed session lifecycle cleanup so the global async-job manager is disposed only by the owning top-level session
|
|
25
|
+
- Changed subagent session switches and handoff paths to stop global async-job cancellation and cancel only jobs owned by that session
|
|
26
|
+
- Changed `agent://` and `artifact://` URL resolution to search artifact outputs across all active sessions instead of only the current session, allowing parent and subagent sessions to read each other’s generated outputs by ID
|
|
27
|
+
- Changed `memory://` URL resolution to walk all active sessions’ memory roots and return the first matching file, so worktree-based subagents can access their own memory views as well as shared roots
|
|
28
|
+
- Changed internal URL routing to use a shared process-global `InternalUrlRouter` and protocol handlers, so built-in tools resolve `agent://`, `artifact://`, `memory://`, `skill://`, `rule://`, `mcp://`, and `local://` URLs without requiring session-specific router wiring
|
|
29
|
+
- Changed `mcp://` handler to use the globally registered MCP manager so MCP resource links work for agents sharing session context
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- Changed the `ask.timeout` default from `30` (seconds) to `0` (wait indefinitely). Auto-selecting the recommended option after a fixed delay was surprising users mid-deliberation; the timer is now strictly opt-in. The legacy auto-select behavior is preserved when `ask.timeout` is set to a non-zero value, and the `ask` tool's prompt has been updated so the model expects unlimited reply time by default.
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- Added `ModelRegistry.hasConfiguredAuth(model)` to mirror the upstream `@mariozechner/pi-coding-agent` API surface; external plugins and downstream wrappers that pre-flight auth before launching a subagent no longer crash with `this._modelRegistry.hasConfiguredAuth is not a function` on the direct agent-launch path. ([#993](https://github.com/can1357/oh-my-pi/issues/993))
|
|
38
|
+
- Fixed an ESM circular-import TDZ that crashed test suites when modules from the `task/` and `tools/` graphs were evaluated together (e.g. `executor-warnings.test.ts` + `task-simple-mode.test.ts`) by deferring `BUILTIN_TOOLS.task`'s `TaskTool.create` dereference to factory-call time and sourcing `truncateTail` from `session/streaming-output` instead of the `tools/` barrel
|
|
39
|
+
- Treat keyless-by-design providers (llama.cpp, ollama, lm-studio) as authenticated in subagent model resolution; fixes silent fallback to parent remote model when a local model is configured. ([#1008](https://github.com/can1357/oh-my-pi/issues/1008))
|
|
40
|
+
- Fixed subagent disposal and session transitions that previously canceled all running async jobs, preventing inadvertent termination of a parent agent’s background work
|
|
41
|
+
- Fixed multi-entry edits silently rendering a fake success when every entry failed (e.g. all hit the auto-generated guard), by surfacing `isError: true` from the single-path edit orchestrator so the renderer takes the error branch instead of falling through to the streaming-preview fallback that displays the *proposed* diff
|
|
42
|
+
- Fixed the auto-generated streaming guard being gated behind `edit.streamingAbort` (default false), so it now pre-empts streaming edit tool calls targeting auto-generated files regardless of that setting
|
|
43
|
+
- Fixed subagents launched in the same parallel batch not seeing each other in their initial `# IRC Peers` system-prompt block by pre-registering the agent in the global `AgentRegistry` before `rebuildSystemPrompt` runs and attaching the live session afterwards
|
|
44
|
+
- Fixed plugin manifest extensions whose entry points at a directory (e.g. `pi-goal`'s `"pi": { "extensions": [".pi/extensions/pi-goal"] }`) failing to load with `Failed to load extension: Directories cannot be read like files`. The plugin path resolver now resolves directory entries to their `index.{ts,js,mjs,cjs}` file, matching the behavior of native auto-discovery via `resolveExtensionEntries`.
|
|
45
|
+
- Fixed the SSH tool on native Windows by avoiding OpenSSH ControlMaster multiplexing, which Win32-OpenSSH does not support and reports as `getsockname failed` ([#154](https://github.com/can1357/oh-my-pi/issues/154)).
|
|
46
|
+
- Fixed `/export` and `/tree` not showing developer-role messages (including the plan content injected after `/plan` approval) so the HTML export and TUI session tree now render developer messages dimmed with their actual content instead of hiding them entirely ([#753](https://github.com/can1357/oh-my-pi/issues/753))
|
|
47
|
+
- Fixed `Timed out initializing browser tab worker` on prebuilt binaries by rewriting `spawnTabWorker` to import the worker entry with `with { type: "file" }` so Bun's `--compile` bundler statically discovers and embeds `tab-worker-entry.ts` in the single-file binary ([#1011](https://github.com/can1357/oh-my-pi/issues/1011))
|
|
48
|
+
|
|
49
|
+
## [14.9.3] - 2026-05-10
|
|
50
|
+
### Breaking Changes
|
|
51
|
+
|
|
52
|
+
- Changed the `eval` tool input format to canonical `*** Begin <LANG>` ... `*** End <LANG>` cells with `*** Title`, `*** Timeout`, and `*** Reset` directives, so legacy `===== ... =====` eval inputs are no longer accepted for execution
|
|
53
|
+
- Removed the `sectionSeparator` re-export from `config/prompt-templates`, so existing imports from `@oh-my-pi/pi-coding-agent/config/prompt-templates` now need to resolve `sectionSeparator` from its utility package
|
|
54
|
+
|
|
55
|
+
### Added
|
|
56
|
+
|
|
57
|
+
- Added support for the `*** Abort` recovery marker in eval and hashline parsing to terminate processing safely when stream corruption is detected
|
|
58
|
+
- Added support for wrapping hashline edits in `*** Begin Patch` and `*** End Patch` markers so patch input with these envelopes is parsed and applied
|
|
59
|
+
- Added support in the HTML export renderer for the new `*** Begin`/`*** End` eval cell format
|
|
60
|
+
- Added a dedicated `[now]` prompt block to `buildSystemPrompt` output containing current date, current working directory, and required end-of-turn continuation/verification guidance
|
|
61
|
+
- Added a new `[project]` prompt block wrapper around workstation and workspace context and ensured it is emitted as a separate system prompt segment
|
|
62
|
+
- Added dedicated HTML rendering for `eval` tool calls, including cell-by-cell parsing of `===== ... =====` blocks with inferred Python/JS/TypeScript highlighting
|
|
63
|
+
- Added dedicated rendering support for `search`, `recipe`, and `irc` tool calls in transcript exports
|
|
64
|
+
- Added a collapsible `Available Tools` section with a tool count and chip-style compact tool names
|
|
65
|
+
- Added macOS power assertion settings `power.preventIdleSleep`, `power.preventSystemSleep`, `power.declareUserActive`, and `power.preventDisplaySleep` so users can control what types of sleep are blocked during sessions
|
|
66
|
+
|
|
67
|
+
### Changed
|
|
68
|
+
|
|
69
|
+
- Kept legacy `===== ... =====` eval transcripts renderable in HTML while adding parsing for the new `*** Begin` format for newer transcripts
|
|
70
|
+
- Changed the system prompt’s Bash usage guidance to explicitly forbid specific anti-patterns (`sed`/`awk` line-range reads, stderr redirects, and `| head|tail` pagination) and require using dedicated tools for those operations
|
|
71
|
+
- Changed delegated subagent prompts so shared task context is now rendered only in the system-level `[context]` block, while the user-facing task message now contains only the assignment prompt text
|
|
72
|
+
- Changed system prompt rendering to use block markers such as `[env]`, `[contract]`, `[role]`, `[coop]`, and `[closure]` for more explicit structural instructions
|
|
73
|
+
- Changed the working-directory value in rendered prompts to use `shortenPath` before interpolation
|
|
74
|
+
- Updated subagent prompt assembly to compose prompt blocks and place the `[now]` block after the subagent-specific instructions
|
|
75
|
+
- Changed GitHub (`gh`) tool cards to include operation, PR, branch, and truncated query/title/body details
|
|
76
|
+
- Changed tool-call output to display internal `_i` intent separately and hide it from rendered argument JSON
|
|
77
|
+
- Changed `ast_edit` and `find`/`search` rendering to show resolved path values and option flags such as `limit`, `no-hidden`, and `no-reply`
|
|
78
|
+
- Changed power assertion behavior to take effect only while a prompt is in flight, replacing session-level persistent assertions
|
|
79
|
+
|
|
80
|
+
### Removed
|
|
81
|
+
|
|
82
|
+
- Removed the unused `head` and `tail` parameters from the `bash` tool schema, along with the dead `normalizeBashCommand` / `applyHeadTail` post-processing module — output truncation is already handled by the harness's streaming tail buffer and artifact spillover, so the agent should rely on `read` (or the artifact link) instead of inline truncation pipes.
|
|
83
|
+
|
|
84
|
+
### Fixed
|
|
85
|
+
|
|
86
|
+
- Fixed eval tool outputs to append a truncation warning and ask users to re-issue remaining work when parsing is aborted by `*** Abort`
|
|
87
|
+
- Fixed hashline parsing and input splitting to stop at `*** Abort` and ignore trailing edits after the marker
|
|
88
|
+
- Fixed subagent task prompt construction so a trailing `[now]` block in the base prompts is preserved and not swallowed when rendering `subagent-system-prompt`
|
|
89
|
+
- Fixed edit rendering so provided `input` text is shown in the export even without a file path
|
|
90
|
+
- Fixed `args.paths` handling in `ast_edit` and `find` so multiple paths are shown as a comma-separated list
|
|
91
|
+
- Fixed power assertion state handling so subsequent prompts are no longer blocked after an aborted or canceled prompt
|
|
92
|
+
- Fixed IRC background exchange poll loop leaking after session disposal: `#scheduleBackgroundExchangeFlush` now stops immediately when `dispose()` is called, preventing stale `setTimeout` callbacks from firing against a torn-down agent
|
|
93
|
+
|
|
5
94
|
## [14.9.2] - 2026-05-10
|
|
6
95
|
### Added
|
|
7
96
|
|
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": "14.9.
|
|
4
|
+
"version": "14.9.5",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/can1357/oh-my-pi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -47,12 +47,12 @@
|
|
|
47
47
|
"@agentclientprotocol/sdk": "0.21.0",
|
|
48
48
|
"@babel/parser": "^7.29.3",
|
|
49
49
|
"@mozilla/readability": "^0.6.0",
|
|
50
|
-
"@oh-my-pi/omp-stats": "14.9.
|
|
51
|
-
"@oh-my-pi/pi-agent-core": "14.9.
|
|
52
|
-
"@oh-my-pi/pi-ai": "14.9.
|
|
53
|
-
"@oh-my-pi/pi-natives": "14.9.
|
|
54
|
-
"@oh-my-pi/pi-tui": "14.9.
|
|
55
|
-
"@oh-my-pi/pi-utils": "14.9.
|
|
50
|
+
"@oh-my-pi/omp-stats": "14.9.5",
|
|
51
|
+
"@oh-my-pi/pi-agent-core": "14.9.5",
|
|
52
|
+
"@oh-my-pi/pi-ai": "14.9.5",
|
|
53
|
+
"@oh-my-pi/pi-natives": "14.9.5",
|
|
54
|
+
"@oh-my-pi/pi-tui": "14.9.5",
|
|
55
|
+
"@oh-my-pi/pi-utils": "14.9.5",
|
|
56
56
|
"@puppeteer/browsers": "^2.13.0",
|
|
57
57
|
"@sinclair/typebox": "^0.34.49",
|
|
58
58
|
"@types/turndown": "5.0.6",
|
|
@@ -16,9 +16,9 @@ import { prompt } from "@oh-my-pi/pi-utils";
|
|
|
16
16
|
*/
|
|
17
17
|
import { Glob } from "bun";
|
|
18
18
|
|
|
19
|
-
const PROMPTS_DIR =
|
|
20
|
-
const COMMIT_PROMPTS_DIR =
|
|
21
|
-
const AGENTIC_PROMPTS_DIR =
|
|
19
|
+
const PROMPTS_DIR = `${import.meta.dir}/../src/prompts/`;
|
|
20
|
+
const COMMIT_PROMPTS_DIR = `${import.meta.dir}/../src/commit/prompts/`;
|
|
21
|
+
const AGENTIC_PROMPTS_DIR = `${import.meta.dir}/../src/commit/agentic/prompts/`;
|
|
22
22
|
|
|
23
23
|
const PROMPT_DIRS = [PROMPTS_DIR, COMMIT_PROMPTS_DIR, AGENTIC_PROMPTS_DIR];
|
|
24
24
|
|
package/src/async/job-manager.ts
CHANGED
|
@@ -16,6 +16,13 @@ export interface AsyncJob {
|
|
|
16
16
|
promise: Promise<void>;
|
|
17
17
|
resultText?: string;
|
|
18
18
|
errorText?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Registry id of the agent that registered the job (e.g. "0-Main",
|
|
21
|
+
* "3-AuthLoader"). Used by scoped cancel/list APIs so a subagent's teardown
|
|
22
|
+
* does not cancel its parent's jobs. Undefined for callers that don't
|
|
23
|
+
* supply an id (e.g. legacy tests, SDK consumers without an agent context).
|
|
24
|
+
*/
|
|
25
|
+
ownerId?: string;
|
|
19
26
|
}
|
|
20
27
|
|
|
21
28
|
export interface AsyncJobManagerOptions {
|
|
@@ -41,10 +48,38 @@ export interface AsyncJobDeliveryState {
|
|
|
41
48
|
|
|
42
49
|
export interface AsyncJobRegisterOptions {
|
|
43
50
|
id?: string;
|
|
51
|
+
/** Registry id of the agent that owns this job; used to scope cancelAll. */
|
|
52
|
+
ownerId?: string;
|
|
44
53
|
onProgress?: (text: string, details?: Record<string, unknown>) => void | Promise<void>;
|
|
45
54
|
}
|
|
46
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Filter applied to job query/cancel APIs. With `ownerId`, results are
|
|
58
|
+
* restricted to jobs registered by that agent (registry id from
|
|
59
|
+
* `AgentRegistry`, e.g. "0-Main", "3-AuthLoader").
|
|
60
|
+
*/
|
|
61
|
+
export interface AsyncJobFilter {
|
|
62
|
+
ownerId?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
47
65
|
export class AsyncJobManager {
|
|
66
|
+
static #instance: AsyncJobManager | undefined;
|
|
67
|
+
|
|
68
|
+
/** Process-global instance shared by internal URL protocol handlers and tools. */
|
|
69
|
+
static instance(): AsyncJobManager | undefined {
|
|
70
|
+
return AsyncJobManager.#instance;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Install or clear the process-global instance. */
|
|
74
|
+
static setInstance(value: AsyncJobManager | undefined): void {
|
|
75
|
+
AsyncJobManager.#instance = value;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Reset the process-global instance. Test-only. */
|
|
79
|
+
static resetForTests(): void {
|
|
80
|
+
AsyncJobManager.#instance = undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
48
83
|
readonly #jobs = new Map<string, AsyncJob>();
|
|
49
84
|
readonly #deliveries: AsyncJobDelivery[] = [];
|
|
50
85
|
readonly #suppressedDeliveries = new Set<string>();
|
|
@@ -56,6 +91,16 @@ export class AsyncJobManager {
|
|
|
56
91
|
#deliveryLoop: Promise<void> | undefined;
|
|
57
92
|
#disposed = false;
|
|
58
93
|
|
|
94
|
+
#filterJobs(jobs: Iterable<AsyncJob>, filter?: AsyncJobFilter): AsyncJob[] {
|
|
95
|
+
const ownerId = filter?.ownerId;
|
|
96
|
+
if (!ownerId) return Array.from(jobs);
|
|
97
|
+
const out: AsyncJob[] = [];
|
|
98
|
+
for (const job of jobs) {
|
|
99
|
+
if (job.ownerId === ownerId) out.push(job);
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
59
104
|
constructor(options: AsyncJobManagerOptions) {
|
|
60
105
|
this.#onJobComplete = options.onJobComplete;
|
|
61
106
|
this.#maxRunningJobs = Math.max(1, Math.floor(options.maxRunningJobs ?? DEFAULT_MAX_RUNNING_JOBS));
|
|
@@ -95,6 +140,7 @@ export class AsyncJobManager {
|
|
|
95
140
|
label,
|
|
96
141
|
abortController,
|
|
97
142
|
promise: Promise.resolve(),
|
|
143
|
+
ownerId: options?.ownerId,
|
|
98
144
|
};
|
|
99
145
|
|
|
100
146
|
const reportProgress = async (text: string, details?: Record<string, unknown>): Promise<void> => {
|
|
@@ -138,9 +184,15 @@ export class AsyncJobManager {
|
|
|
138
184
|
return id;
|
|
139
185
|
}
|
|
140
186
|
|
|
141
|
-
|
|
187
|
+
/**
|
|
188
|
+
* Cancel a single job by id. When `filter.ownerId` is set and does not
|
|
189
|
+
* match the job's owner, the call is treated as not-found (returns false)
|
|
190
|
+
* so cross-agent cancellation is rejected at the manager level.
|
|
191
|
+
*/
|
|
192
|
+
cancel(id: string, filter?: AsyncJobFilter): boolean {
|
|
142
193
|
const job = this.#jobs.get(id);
|
|
143
194
|
if (!job) return false;
|
|
195
|
+
if (filter?.ownerId && job.ownerId !== filter.ownerId) return false;
|
|
144
196
|
if (job.status !== "running") return false;
|
|
145
197
|
job.status = "cancelled";
|
|
146
198
|
job.abortController.abort();
|
|
@@ -152,19 +204,19 @@ export class AsyncJobManager {
|
|
|
152
204
|
return this.#jobs.get(id);
|
|
153
205
|
}
|
|
154
206
|
|
|
155
|
-
getRunningJobs(): AsyncJob[] {
|
|
156
|
-
return
|
|
207
|
+
getRunningJobs(filter?: AsyncJobFilter): AsyncJob[] {
|
|
208
|
+
return this.#filterJobs(this.#jobs.values(), filter).filter(job => job.status === "running");
|
|
157
209
|
}
|
|
158
210
|
|
|
159
|
-
getRecentJobs(limit = 10): AsyncJob[] {
|
|
160
|
-
return
|
|
211
|
+
getRecentJobs(limit = 10, filter?: AsyncJobFilter): AsyncJob[] {
|
|
212
|
+
return this.#filterJobs(this.#jobs.values(), filter)
|
|
161
213
|
.filter(job => job.status !== "running")
|
|
162
214
|
.sort((a, b) => b.startTime - a.startTime)
|
|
163
215
|
.slice(0, limit);
|
|
164
216
|
}
|
|
165
217
|
|
|
166
|
-
getAllJobs(): AsyncJob[] {
|
|
167
|
-
return
|
|
218
|
+
getAllJobs(filter?: AsyncJobFilter): AsyncJob[] {
|
|
219
|
+
return this.#filterJobs(this.#jobs.values(), filter);
|
|
168
220
|
}
|
|
169
221
|
|
|
170
222
|
getDeliveryState(): AsyncJobDeliveryState {
|
|
@@ -221,8 +273,13 @@ export class AsyncJobManager {
|
|
|
221
273
|
return before - this.#deliveries.length;
|
|
222
274
|
}
|
|
223
275
|
|
|
224
|
-
|
|
225
|
-
|
|
276
|
+
/**
|
|
277
|
+
* Cancel running jobs. With `filter.ownerId` set, cancels only jobs the
|
|
278
|
+
* matching agent registered; with no filter, cancels every running job
|
|
279
|
+
* (used by `dispose()` to nuke the manager's state).
|
|
280
|
+
*/
|
|
281
|
+
cancelAll(filter?: AsyncJobFilter): void {
|
|
282
|
+
for (const job of this.getRunningJobs(filter)) {
|
|
226
283
|
job.status = "cancelled";
|
|
227
284
|
job.abortController.abort();
|
|
228
285
|
this.#scheduleEviction(job.id);
|
package/src/capability/rule.ts
CHANGED
|
@@ -209,6 +209,26 @@ export function parseRuleConditionAndScope(frontmatter: RuleFrontmatter): Pick<R
|
|
|
209
209
|
};
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
let activeRules: readonly Rule[] = [];
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Process-global snapshot of rules the active session loaded.
|
|
216
|
+
* Read by internal URL protocol handlers (rule://).
|
|
217
|
+
*/
|
|
218
|
+
export function getActiveRules(): readonly Rule[] {
|
|
219
|
+
return activeRules;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Replace the active rule snapshot. Called once per top-level session. */
|
|
223
|
+
export function setActiveRules(value: readonly Rule[]): void {
|
|
224
|
+
activeRules = value;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Reset the active rule snapshot. Test-only. */
|
|
228
|
+
export function resetActiveRulesForTests(): void {
|
|
229
|
+
activeRules = [];
|
|
230
|
+
}
|
|
231
|
+
|
|
212
232
|
export const ruleCapability = defineCapability<Rule>({
|
|
213
233
|
id: "rules",
|
|
214
234
|
displayName: "Rules",
|
|
@@ -2072,6 +2072,19 @@ export class ModelRegistry {
|
|
|
2072
2072
|
return this.#models.filter(model => this.#isModelAvailable(model));
|
|
2073
2073
|
}
|
|
2074
2074
|
|
|
2075
|
+
/**
|
|
2076
|
+
* Check whether auth is configured for a model's provider.
|
|
2077
|
+
*
|
|
2078
|
+
* Mirrors the upstream `@mariozechner/pi-coding-agent` API surface so that
|
|
2079
|
+
* external plugins/extensions and downstream wrappers (e.g. subagent launch
|
|
2080
|
+
* paths that pre-flight auth before model resolution) can probe a model
|
|
2081
|
+
* without resolving an API key. Returns true for keyless providers as well
|
|
2082
|
+
* as providers with stored credentials. See issue #993.
|
|
2083
|
+
*/
|
|
2084
|
+
hasConfiguredAuth(model: Model<Api>): boolean {
|
|
2085
|
+
return this.#keylessProviders.has(model.provider) || this.authStorage.hasAuth(model.provider);
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2075
2088
|
getDiscoverableProviders(): string[] {
|
|
2076
2089
|
const disabledProviders = getDisabledProviderIdsFromSettings();
|
|
2077
2090
|
return this.#discoverableProviders
|
|
@@ -16,7 +16,7 @@ import chalk from "chalk";
|
|
|
16
16
|
import MODEL_PRIO from "../priority.json" with { type: "json" };
|
|
17
17
|
import { parseThinkingLevel, resolveThinkingLevelForModel } from "../thinking";
|
|
18
18
|
import { fuzzyMatch } from "../utils/fuzzy";
|
|
19
|
-
import { isAuthenticated, MODEL_ROLE_IDS, type ModelRegistry, type ModelRole } from "./model-registry";
|
|
19
|
+
import { isAuthenticated, kNoAuth, MODEL_ROLE_IDS, type ModelRegistry, type ModelRole } from "./model-registry";
|
|
20
20
|
import type { Settings } from "./settings";
|
|
21
21
|
|
|
22
22
|
/** Default model IDs for each known provider */
|
|
@@ -743,6 +743,12 @@ export function resolveModelOverride(
|
|
|
743
743
|
* `modelRoles.task` pointing at an unqualified id whose only available
|
|
744
744
|
* provider variant has no configured credentials — see #985).
|
|
745
745
|
*
|
|
746
|
+
* Keyless-by-design providers (llama.cpp, ollama, lm-studio) advertise the
|
|
747
|
+
* `kNoAuth` sentinel from `getApiKey` to signal that they do not require
|
|
748
|
+
* credentials. Those are treated as authenticated here so an explicitly
|
|
749
|
+
* configured local model is never silently rerouted to the parent's remote
|
|
750
|
+
* provider (see #1008).
|
|
751
|
+
*
|
|
746
752
|
* If neither the subagent nor the parent has working auth, returns the
|
|
747
753
|
* primary resolution unchanged so the existing error path still surfaces
|
|
748
754
|
* a meaningful failure downstream.
|
|
@@ -764,7 +770,7 @@ export async function resolveModelOverrideWithAuthFallback(
|
|
|
764
770
|
}
|
|
765
771
|
|
|
766
772
|
const primaryKey = await modelRegistry.getApiKey(primary.model);
|
|
767
|
-
if (isAuthenticated(primaryKey)) {
|
|
773
|
+
if (primaryKey === kNoAuth || isAuthenticated(primaryKey)) {
|
|
768
774
|
return { ...primary, authFallbackUsed: false };
|
|
769
775
|
}
|
|
770
776
|
|
|
@@ -30,11 +30,6 @@ prompt.registerHelper("jtdToTypeScript", (schema: unknown): string => {
|
|
|
30
30
|
}
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
-
// `sectionSeparator` + SECTION_SEPARATOR helper live in pi-utils/prompt so every
|
|
34
|
-
// template consumer gets them registered without a coupling back to this module.
|
|
35
|
-
// Re-exported here for call sites that already reference the coding-agent path.
|
|
36
|
-
export { sectionSeparator } from "@oh-my-pi/pi-utils/prompt";
|
|
37
|
-
|
|
38
33
|
function formatHashlineRef(lineNum: unknown, content: unknown): { num: number; text: string; ref: string } {
|
|
39
34
|
const num = typeof lineNum === "number" ? lineNum : Number.parseInt(String(lineNum), 10);
|
|
40
35
|
const raw = typeof content === "string" ? content : String(content ?? "");
|
|
@@ -243,6 +243,44 @@ export const SETTINGS_SCHEMA = {
|
|
|
243
243
|
description: "Automatically resume the most recent session in the current directory",
|
|
244
244
|
},
|
|
245
245
|
},
|
|
246
|
+
|
|
247
|
+
// macOS power assertions (caffeinate flags). No-op on other platforms.
|
|
248
|
+
"power.preventIdleSleep": {
|
|
249
|
+
type: "boolean",
|
|
250
|
+
default: true,
|
|
251
|
+
ui: {
|
|
252
|
+
tab: "interaction",
|
|
253
|
+
label: "Prevent Idle Sleep (macOS)",
|
|
254
|
+
description: "caffeinate -i: keep the system awake while a session is open",
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
"power.preventSystemSleep": {
|
|
258
|
+
type: "boolean",
|
|
259
|
+
default: false,
|
|
260
|
+
ui: {
|
|
261
|
+
tab: "interaction",
|
|
262
|
+
label: "Prevent System Sleep on AC (macOS)",
|
|
263
|
+
description: "caffeinate -s: block all system sleep while on AC power",
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
"power.declareUserActive": {
|
|
267
|
+
type: "boolean",
|
|
268
|
+
default: false,
|
|
269
|
+
ui: {
|
|
270
|
+
tab: "interaction",
|
|
271
|
+
label: "Declare User Active (macOS)",
|
|
272
|
+
description: "caffeinate -u: keep the display lit and treat the user as active",
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
"power.preventDisplaySleep": {
|
|
276
|
+
type: "boolean",
|
|
277
|
+
default: false,
|
|
278
|
+
ui: {
|
|
279
|
+
tab: "interaction",
|
|
280
|
+
label: "Prevent Display Sleep (macOS)",
|
|
281
|
+
description: "caffeinate -d: keep the display from idle-sleeping while a session is open",
|
|
282
|
+
},
|
|
283
|
+
},
|
|
246
284
|
shellPath: { type: "string", default: undefined },
|
|
247
285
|
|
|
248
286
|
extensions: { type: "array", default: EMPTY_STRING_ARRAY },
|
|
@@ -867,7 +905,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
867
905
|
|
|
868
906
|
"ask.timeout": {
|
|
869
907
|
type: "number",
|
|
870
|
-
default:
|
|
908
|
+
default: 0,
|
|
871
909
|
ui: {
|
|
872
910
|
tab: "interaction",
|
|
873
911
|
label: "Ask Timeout",
|
package/src/edit/index.ts
CHANGED
|
@@ -204,6 +204,7 @@ async function executeSinglePathEntries(
|
|
|
204
204
|
const contentTexts: string[] = [];
|
|
205
205
|
const diffTexts: string[] = [];
|
|
206
206
|
let firstChangedLine: number | undefined;
|
|
207
|
+
let errorCount = 0;
|
|
207
208
|
|
|
208
209
|
for (let i = 0; i < runs.length; i++) {
|
|
209
210
|
const isLast = i === runs.length - 1;
|
|
@@ -221,6 +222,7 @@ async function executeSinglePathEntries(
|
|
|
221
222
|
} catch (err) {
|
|
222
223
|
const errorText = err instanceof Error ? err.message : String(err);
|
|
223
224
|
contentTexts.push(`Error editing ${path}: ${errorText}`);
|
|
225
|
+
errorCount++;
|
|
224
226
|
}
|
|
225
227
|
|
|
226
228
|
if (!isLast && onUpdate) {
|
|
@@ -230,6 +232,7 @@ async function executeSinglePathEntries(
|
|
|
230
232
|
diff: diffTexts.join("\n"),
|
|
231
233
|
firstChangedLine,
|
|
232
234
|
},
|
|
235
|
+
...(errorCount > 0 ? { isError: true } : {}),
|
|
233
236
|
});
|
|
234
237
|
}
|
|
235
238
|
}
|
|
@@ -240,6 +243,11 @@ async function executeSinglePathEntries(
|
|
|
240
243
|
diff: diffTexts.join("\n"),
|
|
241
244
|
firstChangedLine,
|
|
242
245
|
},
|
|
246
|
+
// Any per-entry failure marks the aggregate result as an error so the
|
|
247
|
+
// renderer takes the error branch instead of falling through to the
|
|
248
|
+
// streaming-edit preview (which displays the *proposed* diff and looks
|
|
249
|
+
// indistinguishable from success).
|
|
250
|
+
...(errorCount > 0 ? { isError: true } : {}),
|
|
243
251
|
};
|
|
244
252
|
}
|
|
245
253
|
|
package/src/edit/renderer.ts
CHANGED
|
@@ -148,6 +148,8 @@ export interface EditRenderContext {
|
|
|
148
148
|
editDiffPreview?: DiffResult | DiffError;
|
|
149
149
|
/** Multi-file streaming diff preview (edits spanning several files) */
|
|
150
150
|
perFileDiffPreview?: PerFileDiffPreview[];
|
|
151
|
+
/** Raw in-flight edit text shown while a computed diff preview is unavailable */
|
|
152
|
+
editStreamingFallback?: string;
|
|
151
153
|
/** Function to render diff text with syntax highlighting */
|
|
152
154
|
renderDiff?: (diffText: string, options?: { filePath?: string }) => string;
|
|
153
155
|
}
|
|
@@ -272,7 +274,7 @@ function formatMultiFileStreamingDiff(previews: PerFileDiffPreview[], uiTheme: T
|
|
|
272
274
|
const parts: string[] = [];
|
|
273
275
|
for (const preview of previews) {
|
|
274
276
|
if (!preview.diff && !preview.error) continue;
|
|
275
|
-
const header = uiTheme.fg("dim", `\n\n
|
|
277
|
+
const header = uiTheme.fg("dim", `\n\n── ${shortenPath(preview.path)} ──`);
|
|
276
278
|
if (preview.error) {
|
|
277
279
|
parts.push(`${header}\n${uiTheme.fg("error", replaceTabs(preview.error, preview.path))}`);
|
|
278
280
|
continue;
|
|
@@ -306,6 +308,9 @@ function getCallPreview(
|
|
|
306
308
|
if (args.newText || args.patch) {
|
|
307
309
|
return renderPlainTextPreview(args.newText ?? args.patch ?? "", uiTheme, rawPath);
|
|
308
310
|
}
|
|
311
|
+
if (renderContext?.editStreamingFallback) {
|
|
312
|
+
return renderContext.editStreamingFallback;
|
|
313
|
+
}
|
|
309
314
|
return "";
|
|
310
315
|
}
|
|
311
316
|
|
package/src/edit/streaming.ts
CHANGED
|
@@ -13,14 +13,19 @@
|
|
|
13
13
|
* the injected `editMode` rather than probing argument shape.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { sanitizeText } from "@oh-my-pi/pi-natives";
|
|
16
17
|
import {
|
|
18
|
+
ABORT_MARKER,
|
|
19
|
+
BEGIN_PATCH_MARKER,
|
|
17
20
|
computeHashlineDiff,
|
|
18
21
|
computeHashlineSectionDiff,
|
|
19
22
|
containsRecognizableHashlineOperations,
|
|
23
|
+
END_PATCH_MARKER,
|
|
20
24
|
type HashlineInputSection,
|
|
21
25
|
splitHashlineInputs,
|
|
22
26
|
} from "../hashline";
|
|
23
27
|
import type { Theme } from "../modes/theme/theme";
|
|
28
|
+
import { replaceTabs, truncateToWidth } from "../tools/render-utils";
|
|
24
29
|
import { type EditMode, resolveEditMode } from "../utils/edit-mode";
|
|
25
30
|
import { computeEditDiff, type DiffError, type DiffResult } from "./diff";
|
|
26
31
|
import { type ApplyPatchEntry, expandApplyPatchToEntries, expandApplyPatchToPreviewEntries } from "./modes/apply-patch";
|
|
@@ -61,6 +66,52 @@ export interface EditStreamingStrategy<Args = unknown> {
|
|
|
61
66
|
renderStreamingFallback(args: Args, uiTheme: Theme): string;
|
|
62
67
|
}
|
|
63
68
|
|
|
69
|
+
const STREAMING_FALLBACK_LINES = 12;
|
|
70
|
+
const STREAMING_FALLBACK_WIDTH = 80;
|
|
71
|
+
|
|
72
|
+
function isHashlineHeaderLine(line: string): boolean {
|
|
73
|
+
const trimmed = line.trimEnd();
|
|
74
|
+
return trimmed.startsWith("@") && trimmed.length > 1;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isHashlineEnvelopeMarkerLine(line: string): boolean {
|
|
78
|
+
const trimmed = line.trimEnd();
|
|
79
|
+
return trimmed === BEGIN_PATCH_MARKER || trimmed === END_PATCH_MARKER || trimmed === ABORT_MARKER;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function trimHashlineStreamingSyntax(lines: string[]): string[] {
|
|
83
|
+
let index = lines.findIndex(line => line.trim().length > 0);
|
|
84
|
+
if (index === -1) return [];
|
|
85
|
+
|
|
86
|
+
if (lines[index].trimEnd() === BEGIN_PATCH_MARKER) {
|
|
87
|
+
index++;
|
|
88
|
+
while (index < lines.length && lines[index].trim().length === 0) index++;
|
|
89
|
+
}
|
|
90
|
+
if (index < lines.length && isHashlineHeaderLine(lines[index])) {
|
|
91
|
+
index++;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return lines.slice(index).filter(line => !isHashlineEnvelopeMarkerLine(line));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function renderHashlineInputFallback(input: string, uiTheme: Theme): string {
|
|
98
|
+
const lines = trimHashlineStreamingSyntax(sanitizeText(input).split("\n"));
|
|
99
|
+
if (!lines.some(line => line.trim().length > 0)) return "";
|
|
100
|
+
|
|
101
|
+
const displayLines = lines.slice(-STREAMING_FALLBACK_LINES);
|
|
102
|
+
const hidden = lines.length - displayLines.length;
|
|
103
|
+
let text = "\n\n";
|
|
104
|
+
text += displayLines
|
|
105
|
+
.map(line => uiTheme.fg("toolOutput", truncateToWidth(replaceTabs(line), STREAMING_FALLBACK_WIDTH)))
|
|
106
|
+
.join("\n");
|
|
107
|
+
if (hidden > 0) {
|
|
108
|
+
text += uiTheme.fg("dim", `\n… (streaming +${hidden} lines)`);
|
|
109
|
+
} else {
|
|
110
|
+
text += uiTheme.fg("dim", "\n(streaming)");
|
|
111
|
+
}
|
|
112
|
+
return text;
|
|
113
|
+
}
|
|
114
|
+
|
|
64
115
|
// -----------------------------------------------------------------------------
|
|
65
116
|
// Partial-JSON handling
|
|
66
117
|
// -----------------------------------------------------------------------------
|
|
@@ -273,8 +324,8 @@ const hashlineStrategy: EditStreamingStrategy<HashlineArgs> = {
|
|
|
273
324
|
}
|
|
274
325
|
return previews.length > 0 ? previews : null;
|
|
275
326
|
},
|
|
276
|
-
renderStreamingFallback() {
|
|
277
|
-
return "";
|
|
327
|
+
renderStreamingFallback(args, uiTheme) {
|
|
328
|
+
return typeof args.input === "string" ? renderHashlineInputFallback(args.input, uiTheme) : "";
|
|
278
329
|
},
|
|
279
330
|
};
|
|
280
331
|
|
package/src/eval/eval.lark
CHANGED
|
@@ -1,37 +1,16 @@
|
|
|
1
|
-
%import common.LF
|
|
2
|
-
%import common.WS_INLINE
|
|
3
|
-
|
|
4
|
-
// Canonical Eval input. Each cell is introduced by a header line:
|
|
5
|
-
//
|
|
6
|
-
// ===== <info> =====
|
|
7
|
-
//
|
|
8
|
-
// where each side is at least 5 equal signs. The info between the bars is
|
|
9
|
-
// a list of space-separated tokens, all optional, in any order:
|
|
10
|
-
//
|
|
11
|
-
// py | js | ts language for this cell
|
|
12
|
-
// py:"..." | js:"..." | ts:"..." language plus title shorthand
|
|
13
|
-
// id:"..." cell title (when language unchanged)
|
|
14
|
-
// t:<digits>(ms|s|m)? per-cell timeout (default 30s)
|
|
15
|
-
// rst reset this language's kernel before running
|
|
16
|
-
//
|
|
17
|
-
// Everything between one header line and the next (or end of input) is
|
|
18
|
-
// the cell's code, verbatim. The runtime additionally accepts content
|
|
19
|
-
// before the first header as an implicit default-language cell, but that
|
|
20
|
-
// is lenient fallback only and MUST NOT be relied on.
|
|
21
|
-
|
|
22
1
|
start: cell+
|
|
23
|
-
cell: header LF code_line*
|
|
24
2
|
|
|
25
|
-
|
|
26
|
-
|
|
3
|
+
cell: begin_cell attr* code_line* end_cell
|
|
4
|
+
begin_cell: "*** Begin " LANG LF
|
|
5
|
+
end_cell: "*** End Cell" LF?
|
|
27
6
|
|
|
28
|
-
attr:
|
|
7
|
+
attr: title | timeout | reset
|
|
8
|
+
title: "*** Title: " /(.+)/ LF
|
|
9
|
+
timeout: "*** Timeout: " /\d+(ms|s|m)?/ LF
|
|
10
|
+
reset: "*** Reset" LF
|
|
29
11
|
|
|
30
12
|
code_line: /[^\r\n]*/ LF
|
|
31
13
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
ID_ATTR: "id:\"" /[^"\r\n]*/ "\""
|
|
36
|
-
T_ATTR: "t:" /\d+(ms|s|m)?/
|
|
37
|
-
RST_FLAG: "rst"
|
|
14
|
+
LANG: "JS" | "TS" | "PY"
|
|
15
|
+
|
|
16
|
+
%import common.LF
|