@oh-my-pi/pi-coding-agent 17.1.8 → 17.2.0
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 +92 -0
- package/dist/{CHANGELOG-vt8ene9g.md → CHANGELOG-be1f2t8h.md} +92 -0
- package/dist/cli.js +9986 -9776
- package/dist/{template-dys3vk5b.js → template-8vdv6xb4.js} +1 -1
- package/dist/types/advisor/runtime.d.ts +8 -2
- package/dist/types/advisor/transcript-recorder.d.ts +15 -0
- package/dist/types/auto-thinking/classifier.d.ts +9 -2
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/session-picker.d.ts +14 -7
- package/dist/types/collab/guest.d.ts +21 -2
- package/dist/types/commands/launch.d.ts +6 -0
- package/dist/types/config/settings-schema.d.ts +63 -8
- package/dist/types/cursor-bridge-tools.d.ts +54 -0
- package/dist/types/cursor.d.ts +193 -8
- package/dist/types/edit/edit-clipboard.d.ts +19 -0
- package/dist/types/edit/hashline/diff.d.ts +9 -1
- package/dist/types/edit/index.d.ts +8 -1
- package/dist/types/edit/streaming.d.ts +8 -1
- package/dist/types/extensibility/extensions/runner.d.ts +19 -2
- package/dist/types/extensibility/extensions/types.d.ts +25 -1
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +2 -0
- package/dist/types/internal-urls/local-protocol.d.ts +3 -2
- package/dist/types/launch/terminal-output-worker-client.d.ts +5 -0
- package/dist/types/launch/terminal-output-worker-protocol.d.ts +14 -0
- package/dist/types/launch/terminal-output-worker.d.ts +1 -0
- package/dist/types/lsp/client.d.ts +2 -0
- package/dist/types/main.d.ts +2 -0
- package/dist/types/mcp/manager.d.ts +33 -4
- package/dist/types/modes/components/codex-reset-fireworks.d.ts +51 -0
- package/dist/types/modes/components/read-tool-group.d.ts +12 -0
- package/dist/types/modes/components/session-selector.d.ts +6 -0
- package/dist/types/modes/components/status-line/component.d.ts +23 -0
- package/dist/types/modes/components/tool-execution.d.ts +16 -7
- package/dist/types/modes/components/usage-row.d.ts +2 -0
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +26 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
- package/dist/types/modes/interactive-mode.d.ts +4 -2
- package/dist/types/modes/rpc/rpc-client.d.ts +7 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +16 -0
- package/dist/types/modes/theme/theme.d.ts +12 -1
- package/dist/types/modes/types.d.ts +2 -1
- package/dist/types/sdk.d.ts +1 -0
- package/dist/types/session/agent-session-events.d.ts +2 -0
- package/dist/types/session/agent-session-types.d.ts +36 -1
- package/dist/types/session/agent-session.d.ts +4 -2
- package/dist/types/session/claude-session-store.d.ts +13 -0
- package/dist/types/session/codex-session-store.d.ts +14 -0
- package/dist/types/session/credential-pin.d.ts +58 -0
- package/dist/types/session/foreign-session-import.d.ts +15 -0
- package/dist/types/session/foreign-session-jsonl.d.ts +9 -0
- package/dist/types/session/foreign-session-store.d.ts +23 -0
- package/dist/types/session/session-advisors.d.ts +47 -2
- package/dist/types/session/session-entries.d.ts +19 -1
- package/dist/types/session/session-handoff.d.ts +4 -1
- package/dist/types/session/session-loader.d.ts +2 -0
- package/dist/types/session/session-maintenance.d.ts +1 -1
- package/dist/types/session/session-manager.d.ts +21 -0
- package/dist/types/session/session-tools.d.ts +25 -1
- package/dist/types/session/turn-recovery.d.ts +10 -4
- package/dist/types/system-prompt.d.ts +2 -1
- package/dist/types/task/executor.d.ts +11 -2
- package/dist/types/task/render.d.ts +7 -0
- package/dist/types/thinking.d.ts +18 -6
- package/dist/types/tools/acp-bridge.d.ts +28 -5
- package/dist/types/tools/grep.d.ts +17 -1
- package/dist/types/tools/hub/launch.d.ts +5 -1
- package/dist/types/tools/index.d.ts +4 -1
- package/dist/types/tools/path-utils.d.ts +19 -0
- package/dist/types/tools/read.d.ts +2 -0
- package/dist/types/tools/xdev.d.ts +2 -1
- package/dist/types/utils/changelog.d.ts +19 -0
- package/dist/types/utils/git.d.ts +10 -0
- package/dist/types/utils/jj.d.ts +11 -4
- package/dist/types/web/search/types.d.ts +2 -2
- package/package.json +12 -12
- package/src/advisor/runtime.ts +138 -12
- package/src/advisor/transcript-recorder.ts +56 -0
- package/src/auto-thinking/classifier.ts +48 -10
- package/src/cli/args.ts +6 -0
- package/src/cli/flag-tables.ts +2 -0
- package/src/cli/gallery-fixtures/fs.ts +25 -0
- package/src/cli/session-picker.ts +34 -17
- package/src/cli/ttsr-cli.ts +19 -1
- package/src/cli.ts +9 -0
- package/src/collab/guest.ts +37 -4
- package/src/collab/host.ts +1 -0
- package/src/commands/launch.ts +6 -0
- package/src/config/model-registry.ts +103 -25
- package/src/config/settings-schema.ts +65 -6
- package/src/config/settings.ts +25 -0
- package/src/cursor-bridge-tools.ts +81 -0
- package/src/cursor.ts +463 -12
- package/src/edit/edit-clipboard.ts +23 -0
- package/src/edit/hashline/diff.ts +49 -10
- package/src/edit/hashline/execute.ts +38 -6
- package/src/edit/hashline/filesystem.ts +27 -3
- package/src/edit/index.ts +12 -2
- package/src/edit/renderer.ts +4 -4
- package/src/edit/streaming.ts +17 -2
- package/src/export/html/template.js +1 -1
- package/src/extensibility/extensions/runner.ts +70 -0
- package/src/extensibility/extensions/types.ts +30 -0
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +12 -13
- package/src/internal-urls/local-protocol.ts +3 -2
- package/src/internal-urls/mcp-protocol.ts +4 -1
- package/src/launch/terminal-output-worker-client.ts +53 -0
- package/src/launch/terminal-output-worker-protocol.ts +11 -0
- package/src/launch/terminal-output-worker.ts +23 -0
- package/src/lsp/client.ts +18 -3
- package/src/lsp/index.ts +4 -0
- package/src/lsp/render.ts +1 -1
- package/src/main.ts +127 -56
- package/src/mcp/manager.ts +120 -18
- package/src/modes/acp/acp-agent.ts +15 -9
- package/src/modes/components/chat-transcript-builder.ts +30 -11
- package/src/modes/components/codex-reset-fireworks.ts +369 -0
- package/src/modes/components/read-tool-group.ts +159 -17
- package/src/modes/components/session-selector.ts +19 -3
- package/src/modes/components/status-line/component.ts +381 -54
- package/src/modes/components/tool-execution.ts +78 -31
- package/src/modes/components/usage-row.ts +10 -5
- package/src/modes/controllers/event-controller.ts +26 -4
- package/src/modes/controllers/mcp-command-controller.ts +68 -3
- package/src/modes/controllers/selector-controller.ts +94 -36
- package/src/modes/controllers/tan-command-controller.ts +14 -0
- package/src/modes/interactive-mode.ts +31 -17
- package/src/modes/rpc/rpc-client.ts +18 -0
- package/src/modes/rpc/rpc-mode.ts +16 -1
- package/src/modes/rpc/rpc-types.ts +11 -0
- package/src/modes/theme/theme.ts +151 -6
- package/src/modes/types.ts +2 -1
- package/src/modes/utils/ui-helpers.ts +38 -21
- package/src/prompts/system/auto-thinking-difficulty.md +4 -2
- package/src/prompts/system/plan-mode-active.md +2 -2
- package/src/prompts/system/system-prompt.md +3 -0
- package/src/prompts/system/xdev-mount-notice.md +1 -1
- package/src/prompts/tools/bash.md +15 -17
- package/src/prompts/tools/checkpoint.md +1 -1
- package/src/prompts/tools/glob.md +7 -6
- package/src/prompts/tools/grep.md +7 -6
- package/src/sdk.ts +178 -15
- package/src/session/agent-session-events.ts +1 -0
- package/src/session/agent-session-types.ts +40 -1
- package/src/session/agent-session.ts +302 -175
- package/src/session/claude-session-store.ts +426 -0
- package/src/session/codex-session-store.ts +673 -0
- package/src/session/credential-pin.ts +93 -0
- package/src/session/foreign-session-import.ts +52 -0
- package/src/session/foreign-session-jsonl.ts +29 -0
- package/src/session/foreign-session-store.ts +26 -0
- package/src/session/model-controls.ts +11 -3
- package/src/session/session-advisors.ts +140 -16
- package/src/session/session-entries.ts +21 -1
- package/src/session/session-handoff.ts +20 -3
- package/src/session/session-loader.ts +25 -9
- package/src/session/session-maintenance.ts +59 -11
- package/src/session/session-manager.ts +61 -0
- package/src/session/session-tools.ts +107 -5
- package/src/session/settings-stream-fn.ts +1 -0
- package/src/session/turn-recovery.ts +97 -51
- package/src/slash-commands/builtin-registry.ts +131 -3
- package/src/system-prompt.ts +3 -2
- package/src/task/executor.ts +17 -4
- package/src/task/render.ts +20 -4
- package/src/thinking.ts +29 -10
- package/src/tools/acp-bridge.ts +52 -8
- package/src/tools/browser.ts +5 -0
- package/src/tools/checkpoint.ts +0 -13
- package/src/tools/grep.ts +64 -8
- package/src/tools/hub/launch.ts +10 -15
- package/src/tools/index.ts +28 -4
- package/src/tools/path-utils.ts +88 -0
- package/src/tools/read.ts +3 -0
- package/src/tools/write.ts +9 -3
- package/src/tools/xdev.ts +39 -14
- package/src/utils/changelog.ts +144 -4
- package/src/utils/git.ts +61 -14
- package/src/utils/jj.ts +19 -13
- package/src/web/search/providers/codex.ts +34 -0
- package/src/web/search/types.ts +1 -1
|
@@ -1,25 +1,23 @@
|
|
|
1
|
-
Runs commands in a persistent shell
|
|
1
|
+
Runs commands in a persistent shell.
|
|
2
2
|
|
|
3
|
-
Use ONLY for
|
|
4
|
-
{{#if
|
|
5
|
-
{{#if hasEval}}Inline scripts, heredocs, shell control flow, `$(…)`, multi-stage pipelines, `&&`-chains, quote/JSON escaping → `eval` cells.{{else}}Inline scripts, heredocs, shell control flow, `$(…)`, multi-stage pipelines, `&&`-chains → purpose-built tool or checked-in script.{{/if}}
|
|
3
|
+
Use ONLY for one binary or a short pipeline that computes a fact (`wc -l`, `sort | uniq -c`, `diff`).
|
|
4
|
+
{{#if hasEval}}Inline scripts, heredocs, `$(…)`, complex control flow/quoting, and non-trivial pipelines → `eval`.{{else}}Inline scripts, heredocs, `$(…)`, and complex control flow → a purpose-built tool or checked-in script.{{/if}}
|
|
6
5
|
|
|
7
6
|
<instruction>
|
|
8
|
-
- `cwd`
|
|
9
|
-
- `pty: true` only for
|
|
10
|
-
-
|
|
11
|
-
- Internal URIs (`skill://`, `agent://`, …) auto-resolve to
|
|
12
|
-
{{#if hasShellBuiltins}}- aux utils available: mkdir,
|
|
13
|
-
{{#if asyncEnabled}}- `async: true` defers
|
|
7
|
+
- Set `cwd` instead of `cd`; use `env: { NAME: "…" }` for multiline/quote-heavy values.
|
|
8
|
+
- `pty: true` only for terminal interaction (`sudo`, `ssh`).
|
|
9
|
+
- Order-dependent commands use `&&` in one call; independent calls may run concurrently.
|
|
10
|
+
- Internal URIs (`skill://`, `agent://`, …) auto-resolve to paths.
|
|
11
|
+
{{#if hasShellBuiltins}}- aux utils available: mkdir, wc, sort, comm, diff, uniq, base64, cmp, md5sum, sha{1,224,256,384,512}sum, b2sum, basename, dirname, readlink, realpath, touch, stat, date, mktemp, seq, yes, printenv, truncate, tac, nproc, uname, whoami, hostname, which, cut, tee, tr, paste, sed, xargs, jq, rm, mv, ln, ts, sponge, ifne, isutf8, combine{{#unless isWindows}}, errno{{/unless}}{{/if}}
|
|
12
|
+
{{#if asyncEnabled}}- `async: true` defers a finite command's result; it does not extend `timeout`.{{/if}}
|
|
14
13
|
</instruction>
|
|
15
14
|
|
|
16
15
|
<critical>
|
|
17
|
-
{{#if hasGrep}}- NEVER shell
|
|
18
|
-
{{#if hasRead}}{{#if hasGlob}}-
|
|
19
|
-
- Avoid head
|
|
20
|
-
{{#if hasLaunch}}-
|
|
16
|
+
{{#if hasGrep}}- NEVER use shell `grep`/`rg`; use built-in `grep`.{{/if}}
|
|
17
|
+
{{#if hasRead}}{{#if hasGlob}}- List directories with `read` and find paths with `glob`; NEVER use `ls`/`find`.{{/if}}{{/if}}
|
|
18
|
+
- Avoid `head`, `tail`, and redirection: output is captured, truncated, and linked as `artifact://<id>`.
|
|
19
|
+
{{#if hasLaunch}}- Services, watchers, debuggers, and REPLs MUST use `hub` (`op:"start"`).{{/if}}
|
|
21
20
|
</critical>
|
|
22
21
|
|
|
23
|
-
{{#if
|
|
24
|
-
|
|
25
|
-
- Long output truncated, test/lint filtered to failures. Footer links full capture. No footer = what you see is exact output.
|
|
22
|
+
{{#if autoBackgroundEnabled}}Long foreground calls may auto-background and deliver later. Need inline? Raise `timeout`.{{/if}}
|
|
23
|
+
No truncation footer means the displayed output is complete.
|
|
@@ -5,7 +5,7 @@ Use this when you need to investigate with many intermediate tool calls (read/gr
|
|
|
5
5
|
Rules:
|
|
6
6
|
- You MUST call `rewind` before yielding after starting a checkpoint.
|
|
7
7
|
- You NEVER call `checkpoint` while another checkpoint is active.
|
|
8
|
-
-
|
|
8
|
+
- Disabled by default in subagents. To enable, list `checkpoint` or `rewind` in the agent definition's `tools:` frontmatter (the sister tool is auto-included; requires `checkpoint.enabled` setting).
|
|
9
9
|
|
|
10
10
|
Typical flow:
|
|
11
11
|
1. `checkpoint(goal: …)`
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
Globs files and
|
|
1
|
+
Globs files, directories, and path-backed internal URLs with fast pattern matching.
|
|
2
2
|
|
|
3
3
|
<instruction>
|
|
4
|
-
- `path`:
|
|
5
|
-
- `
|
|
6
|
-
- `
|
|
4
|
+
- `path`: glob, file, directory, or path-backed internal URL; separate targets with `;` (`src/**/*.ts; test/**/*.ts`).
|
|
5
|
+
- `memory://` glob patterns are supported. `ssh://` has no local path; use `read`. Other internal URLs accept exact paths only.
|
|
6
|
+
- `gitignore` defaults `true`. Set `false` for ignored files such as `.env*`, logs, or build output.
|
|
7
|
+
- `hidden` defaults `true`; pair it with `gitignore: false` for ignored dotfiles.
|
|
7
8
|
</instruction>
|
|
8
9
|
|
|
9
10
|
<output>
|
|
10
|
-
|
|
11
|
+
Matches are newest-first and grouped by directory; directories end in `/`.
|
|
11
12
|
</output>
|
|
12
13
|
|
|
13
14
|
<avoid>
|
|
14
|
-
Open-ended
|
|
15
|
+
Open-ended multi-round discovery → Task + scout.
|
|
15
16
|
</avoid>
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
Searches files and internal URLs with Rust regex plus PCRE2 fallback.
|
|
2
2
|
|
|
3
3
|
<instruction>
|
|
4
|
-
- `path
|
|
5
|
-
|
|
6
|
-
-
|
|
4
|
+
- Scope `path` to known files, directories, globs, or internal URLs; separate roots with `;`.
|
|
5
|
+
- Broad searches can time out; scope them narrowly or use `glob` first.
|
|
6
|
+
- One-file line selector: `src/foo.ts:50-100` (selectors never choose the search root).
|
|
7
|
+
- Literal `\n` or `\\n` enables cross-line patterns.
|
|
7
8
|
</instruction>
|
|
8
9
|
|
|
9
10
|
<critical>
|
|
10
|
-
- MUST use this
|
|
11
|
-
- Open-ended multi-round search
|
|
11
|
+
- MUST use this instead of shell `grep`/`rg`.
|
|
12
|
+
- Open-ended multi-round search MUST use Task + scout, not chained calls.
|
|
12
13
|
</critical>
|
package/src/sdk.ts
CHANGED
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
discoverWatchdogFiles,
|
|
34
34
|
formatActiveRepoWatchdogPrompt,
|
|
35
35
|
formatAdvisorContextPrompt,
|
|
36
|
+
loadAdvisorTranscriptCosts,
|
|
36
37
|
} from "./advisor";
|
|
37
38
|
import { AsyncJobManager } from "./async";
|
|
38
39
|
import { AutoLearnController, buildAutoLearnInstructions } from "./autolearn/controller";
|
|
@@ -60,7 +61,8 @@ import { loadPromptTemplates as loadPromptTemplatesInternal, type PromptTemplate
|
|
|
60
61
|
import { applyProviderGlobalsFromSettings } from "./config/provider-globals";
|
|
61
62
|
import { buildServiceTierByFamily } from "./config/service-tier";
|
|
62
63
|
import { Settings, type SkillsSettings } from "./config/settings";
|
|
63
|
-
import { CursorExecHandlers } from "./cursor";
|
|
64
|
+
import { CursorExecHandlers, type CursorMcpResourceAdapter } from "./cursor";
|
|
65
|
+
import { createBridgeEditTool, createBridgeGrepFactory } from "./cursor-bridge-tools";
|
|
64
66
|
import "./discovery";
|
|
65
67
|
import { initializeWithSettings } from "./discovery";
|
|
66
68
|
import { disposeAllJuliaKernelSessions, disposeJuliaKernelSessionsByOwner } from "./eval/jl/executor";
|
|
@@ -607,6 +609,10 @@ export type * from "./extensibility/extensions";
|
|
|
607
609
|
export type { Skill } from "./extensibility/skills";
|
|
608
610
|
export type { FileSlashCommand } from "./extensibility/slash-commands";
|
|
609
611
|
export type { MCPManager, MCPServerConfig, MCPServerConnection, MCPToolsLoadResult } from "./mcp";
|
|
612
|
+
// Agent registry: pass a private instance per `createAgentSession` when
|
|
613
|
+
// embedding several concurrent top-level sessions in one process (the default
|
|
614
|
+
// global registry admits only one "Main" per process generation).
|
|
615
|
+
export { type AgentRef, AgentRegistry, MAIN_AGENT_ID } from "./registry/agent-registry";
|
|
610
616
|
export type { Tool } from "./tools";
|
|
611
617
|
export { buildDirectoryTree, buildWorkspaceTree, type DirectoryTree, type WorkspaceTree } from "./workspace-tree";
|
|
612
618
|
|
|
@@ -1772,7 +1778,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1772
1778
|
getArtifactsDir,
|
|
1773
1779
|
getSessionId: () => sessionManager.getSessionId?.() ?? null,
|
|
1774
1780
|
};
|
|
1775
|
-
if (options.localProtocolOptions) {
|
|
1781
|
+
if (options.localProtocolOptions && !options.parentTaskPrefix) {
|
|
1776
1782
|
LocalProtocolHandler.setOverride(options.localProtocolOptions);
|
|
1777
1783
|
}
|
|
1778
1784
|
toolSession.getArtifactsDir = getArtifactsDir;
|
|
@@ -2513,6 +2519,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2513
2519
|
() => (hasSession ? createSessionMemoryRuntimeContext(session, agentDir, cwd) : undefined),
|
|
2514
2520
|
settings,
|
|
2515
2521
|
localProtocolOptions,
|
|
2522
|
+
() => (hasSession ? session.getAsyncJobSnapshot() : null),
|
|
2516
2523
|
);
|
|
2517
2524
|
|
|
2518
2525
|
credentialDisabledTarget = extensionRunner;
|
|
@@ -2582,6 +2589,43 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2582
2589
|
for (const tool of toolRegistry.values()) {
|
|
2583
2590
|
toolRegistry.set(tool.name, new ExtensionToolWrapper(tool, extensionRunner));
|
|
2584
2591
|
}
|
|
2592
|
+
// Cursor's own client owns file edits, so `edit` is not advertised to the
|
|
2593
|
+
// model (commit 8ba0498eb: full-file `write` is used instead). The exec
|
|
2594
|
+
// bridge is a different consumer: the server sends native `pi_edit`
|
|
2595
|
+
// frames regardless of the advertised catalog, and answering them needs
|
|
2596
|
+
// a real tool.
|
|
2597
|
+
//
|
|
2598
|
+
// It must be a `replace`-mode instance. `PiEditExecArgs` carries
|
|
2599
|
+
// `old_text`/`new_text` pairs, which is exactly `replace`'s schema and
|
|
2600
|
+
// nothing else's — under the default `hashline` mode the frame's args do
|
|
2601
|
+
// not match the tool's parameters at all. The registry instance follows
|
|
2602
|
+
// the session's configured mode, so the bridge builds its own.
|
|
2603
|
+
//
|
|
2604
|
+
// The grant is captured HERE, before the Cursor branch below deletes
|
|
2605
|
+
// `edit` from the registry, and independently of the session's provider:
|
|
2606
|
+
// a session that starts on another provider can switch to Cursor later,
|
|
2607
|
+
// and the roster is built once, at session creation. Reading the registry
|
|
2608
|
+
// at frame time would see the switched-to state, not the grant.
|
|
2609
|
+
const editWasGranted = toolRegistry.has("edit");
|
|
2610
|
+
// Built on first use rather than eagerly: a session that never reaches
|
|
2611
|
+
// Cursor never constructs it.
|
|
2612
|
+
let cursorBridgeEditTool: AgentTool | undefined;
|
|
2613
|
+
const getCursorBridgeEditTool = (): AgentTool | undefined => {
|
|
2614
|
+
// Only when the session actually granted `edit`. `createTools` omits
|
|
2615
|
+
// it entirely for a restricted tool set, and the bridge answers native
|
|
2616
|
+
// frames that arrive regardless of the advertised catalog — so
|
|
2617
|
+
// building one unconditionally would hand a read-only agent a
|
|
2618
|
+
// mutating tool it was denied (the issue #5680 escalation).
|
|
2619
|
+
if (!editWasGranted) return undefined;
|
|
2620
|
+
cursorBridgeEditTool ??= createBridgeEditTool(toolSession, extensionRunner);
|
|
2621
|
+
return cursorBridgeEditTool;
|
|
2622
|
+
};
|
|
2623
|
+
// Whether this session granted a file-writing tool. Same capture-early
|
|
2624
|
+
// reasoning, plus `write` may be auto-registered further down as an xdev
|
|
2625
|
+
// transport. The exec bridge answers native `delete` and
|
|
2626
|
+
// resource-download frames that mutate files without running a registry
|
|
2627
|
+
// tool, so it needs the grant as the session actually made it.
|
|
2628
|
+
const cursorCanMutateFiles = editWasGranted || toolRegistry.has("write");
|
|
2585
2629
|
if (model?.provider === "cursor") {
|
|
2586
2630
|
toolRegistry.delete("edit");
|
|
2587
2631
|
builtInRegistryToolNames.delete("edit");
|
|
@@ -2624,15 +2668,52 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2624
2668
|
if (!state) return undefined;
|
|
2625
2669
|
return resolveMountedXdevExecutable(state, name);
|
|
2626
2670
|
};
|
|
2671
|
+
// Cursor's resource frames ask what THIS client's servers advertise; only
|
|
2672
|
+
// live connections have any. Built once: the advisor bridges answer from
|
|
2673
|
+
// the same connections the primary does.
|
|
2674
|
+
const cursorMcpResources: CursorMcpResourceAdapter | undefined = mcpManager && {
|
|
2675
|
+
serverNames: () => mcpManager.getConnectedServers(),
|
|
2676
|
+
getServerResources: async name => {
|
|
2677
|
+
// The manager registers a server's tools before its background
|
|
2678
|
+
// resource load finishes, so a frame arriving in that window
|
|
2679
|
+
// would read an empty cache and report "advertises nothing".
|
|
2680
|
+
await mcpManager.ensureServerResources(name);
|
|
2681
|
+
return mcpManager.getServerResources(name);
|
|
2682
|
+
},
|
|
2683
|
+
readServerResource: (name, uri) => mcpManager.readServerResource(name, uri),
|
|
2684
|
+
};
|
|
2627
2685
|
const cursorExecHandlers = new CursorExecHandlers({
|
|
2628
2686
|
cwd,
|
|
2687
|
+
// The session's cwd moves (`/cd`, resume, branch restore) while this
|
|
2688
|
+
// bridge is built once at startup. Path-confining frames — the native
|
|
2689
|
+
// `delete` and a `download_path` resource read — resolve against
|
|
2690
|
+
// whichever of the two they are given, so without the live resolver the
|
|
2691
|
+
// primary would write into the workspace the session has left while
|
|
2692
|
+
// reporting success for the path the server asked about. The advisor
|
|
2693
|
+
// bridge already passes one.
|
|
2694
|
+
getCwd: () => sessionManager.getCwd(),
|
|
2629
2695
|
tools: toolRegistry,
|
|
2630
2696
|
getExecutableTool: resolveDeviceTool,
|
|
2697
|
+
// `pi_edit` needs the `replace`-mode instance specifically, and the
|
|
2698
|
+
// registry may still hold the session's own `edit` (any mode) when
|
|
2699
|
+
// this session did not start on Cursor.
|
|
2700
|
+
getEditReplaceTool: getCursorBridgeEditTool,
|
|
2631
2701
|
getToolContext: () => toolContextStore.getContext(),
|
|
2702
|
+
mcpResources: cursorMcpResources,
|
|
2632
2703
|
emitEvent: event => cursorEventEmitter?.(event),
|
|
2633
2704
|
getTodoPhases: () => session.getTodoPhases(),
|
|
2634
2705
|
setTodoPhases: phases => session.setTodoPhases(phases),
|
|
2635
2706
|
persistTodoPhases: phases => sessionManager.appendCustomEntry(USER_TODO_EDIT_CUSTOM_TYPE, { phases }),
|
|
2707
|
+
// `pi_grep` carries its own context width and match cap, which the
|
|
2708
|
+
// shared grep instance fixed at construction cannot express. Gated on
|
|
2709
|
+
// the grant: the factory builds a fresh tool and `executeTool` prefers
|
|
2710
|
+
// it over the registry, so installing it unconditionally would let a
|
|
2711
|
+
// session without `grep` search anyway.
|
|
2712
|
+
createGrepTool: toolRegistry.has("grep") ? createBridgeGrepFactory(toolSession, extensionRunner) : undefined,
|
|
2713
|
+
// The native `delete` and resource-download frames mutate files
|
|
2714
|
+
// without running a registry tool, so this grant is the only thing
|
|
2715
|
+
// standing between a restricted session and a workspace write.
|
|
2716
|
+
allowDirectFileMutation: cursorCanMutateFiles,
|
|
2636
2717
|
});
|
|
2637
2718
|
|
|
2638
2719
|
// Resolve the inline-descriptors setting against the session-start model.
|
|
@@ -2793,6 +2874,19 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2793
2874
|
}
|
|
2794
2875
|
}
|
|
2795
2876
|
}
|
|
2877
|
+
// Checkpoint and rewind are a pair: `createTools` auto-includes the sister
|
|
2878
|
+
// tool in the registry, but an explicit `toolNames` list would otherwise
|
|
2879
|
+
// drop it from the ACTIVE set — leaving the agent able to checkpoint but
|
|
2880
|
+
// unable to rewind (or vice versa). Mirror the pairing here. Unlike the
|
|
2881
|
+
// manage_skill/learn mirror above, this is a safety pairing — it applies
|
|
2882
|
+
// to restricted sessions too.
|
|
2883
|
+
if (explicitlyRequestedToolNames) {
|
|
2884
|
+
if (builtInToolNames.includes("checkpoint") && !explicitlyRequestedToolNames.includes("rewind")) {
|
|
2885
|
+
explicitlyRequestedToolNames.push("rewind");
|
|
2886
|
+
} else if (builtInToolNames.includes("rewind") && !explicitlyRequestedToolNames.includes("checkpoint")) {
|
|
2887
|
+
explicitlyRequestedToolNames.push("checkpoint");
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2796
2890
|
const requestedToolNames = explicitlyRequestedToolNames ?? toolNamesFromRegistry;
|
|
2797
2891
|
const normalizedRequested = requestedToolNames.filter(name => toolRegistry.has(name));
|
|
2798
2892
|
const defaultInactiveToolNames = new Set(
|
|
@@ -3112,7 +3206,15 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3112
3206
|
advisorToolBuilds.push(BUILTIN_TOOLS[name as keyof typeof BUILTIN_TOOLS](advisorToolSession));
|
|
3113
3207
|
}
|
|
3114
3208
|
const built = await Promise.all(advisorToolBuilds);
|
|
3115
|
-
|
|
3209
|
+
// Wrapped like every registry tool: `ExtensionToolWrapper` is where the
|
|
3210
|
+
// approval mode, per-tool `tools.approval.<tool>` policies and
|
|
3211
|
+
// `autoApprove` are enforced. The advisor's loop and its Cursor exec
|
|
3212
|
+
// bridge both run these instances directly, so a raw one would execute a
|
|
3213
|
+
// `bash`/`write` the user configured as `ask` or `deny`. Meta-notice
|
|
3214
|
+
// first, matching the registry's wrap order.
|
|
3215
|
+
const advisorTools: Tool[] = built
|
|
3216
|
+
.filter((tool): tool is Tool => tool != null)
|
|
3217
|
+
.map(tool => new ExtensionToolWrapper(wrapToolWithMetaNotice(tool), extensionRunner) as Tool);
|
|
3116
3218
|
|
|
3117
3219
|
const advisorWatchdogPrompts = [...watchdogFiles];
|
|
3118
3220
|
if (activeRepoContext) {
|
|
@@ -3126,6 +3228,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3126
3228
|
// Owned only when this session created the manager; subagents receive a
|
|
3127
3229
|
// parent's manager via `options.mcpManager` and MUST NOT disconnect it.
|
|
3128
3230
|
const ownedMcpManager = options.mcpManager ? undefined : mcpManager;
|
|
3231
|
+
// A resumed session already has advisor turns on disk; without this the status
|
|
3232
|
+
// line would restart its `(adv)` total at zero for the rest of the session.
|
|
3233
|
+
const initialAdvisorCosts = await loadAdvisorTranscriptCosts(sessionManager.getSessionFile());
|
|
3129
3234
|
session = new AgentSession({
|
|
3130
3235
|
advisorWatchdogPrompt,
|
|
3131
3236
|
advisorContextPrompt,
|
|
@@ -3140,6 +3245,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3140
3245
|
planYolo: options.planYolo,
|
|
3141
3246
|
serviceTierByFamily: initialServiceTierByFamily,
|
|
3142
3247
|
sessionManager,
|
|
3248
|
+
initialAdvisorCosts,
|
|
3143
3249
|
settings,
|
|
3144
3250
|
autoApprove: options.autoApprove,
|
|
3145
3251
|
evalKernelOwnerId,
|
|
@@ -3218,6 +3324,20 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3218
3324
|
providerPromptCacheKeySource,
|
|
3219
3325
|
parentEvalSessionId: options.parentEvalSessionId,
|
|
3220
3326
|
advisorTools,
|
|
3327
|
+
// Same per-call `grep` seam the primary bridge gets, built against the
|
|
3328
|
+
// advisor's own tool session so a `pi_grep` frame's context width and
|
|
3329
|
+
// match cap are honored there too.
|
|
3330
|
+
advisorCreateGrepTool: createBridgeGrepFactory(advisorToolSession, extensionRunner),
|
|
3331
|
+
// Same `replace`-mode requirement as the primary bridge; the advisor
|
|
3332
|
+
// path gates it on the advisor's own `edit` grant.
|
|
3333
|
+
advisorCreateEditTool: () => createBridgeEditTool(advisorToolSession, extensionRunner),
|
|
3334
|
+
// The advisor's bridge tools are wrapped for approval, but the wrapper
|
|
3335
|
+
// reads the mode and per-tool policies only from the execute-time
|
|
3336
|
+
// context — the primary bridge passes the same store.
|
|
3337
|
+
advisorGetToolContext: () => toolContextStore.getContext(),
|
|
3338
|
+
// Same live connections the primary bridge reads; an advisor's
|
|
3339
|
+
// resource frame would otherwise report every server as empty.
|
|
3340
|
+
advisorMcpResources: cursorMcpResources,
|
|
3221
3341
|
titleSystemPrompt: options.titleSystemPrompt,
|
|
3222
3342
|
});
|
|
3223
3343
|
hasSession = true;
|
|
@@ -3245,6 +3365,15 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3245
3365
|
throw new Error(`Agent "${resolvedAgentId}" was replaced during session initialization.`);
|
|
3246
3366
|
}
|
|
3247
3367
|
hasRegistered = true;
|
|
3368
|
+
// MCP notification bridge cleanup — assigned when the bridge is wired below,
|
|
3369
|
+
// invoked from the dispose wrapper AND registered as a postmortem so both
|
|
3370
|
+
// explicit-dispose (SDK embedders that reuse the process across sessions) and
|
|
3371
|
+
// process-exit paths tear the listener down. Nulled after use so the closure
|
|
3372
|
+
// graph (`extensionRunner`, `session`) can be GC'd instead of retained by the
|
|
3373
|
+
// process-global postmortem list.
|
|
3374
|
+
let unsubscribeMcpNotifications: (() => void) | undefined;
|
|
3375
|
+
let unregisterMcpPostmortem: (() => void) | undefined;
|
|
3376
|
+
|
|
3248
3377
|
{
|
|
3249
3378
|
const originalDispose = session.dispose.bind(session);
|
|
3250
3379
|
session.dispose = async () => {
|
|
@@ -3275,6 +3404,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3275
3404
|
} finally {
|
|
3276
3405
|
unregisterUnlessParked();
|
|
3277
3406
|
unsubscribeCredentialDisabled?.();
|
|
3407
|
+
unsubscribeMcpNotifications?.();
|
|
3408
|
+
unregisterMcpPostmortem?.();
|
|
3409
|
+
// Drop refs so the process-global postmortem list doesn't retain
|
|
3410
|
+
// the bridge closure past explicit dispose.
|
|
3411
|
+
unsubscribeMcpNotifications = undefined;
|
|
3412
|
+
unregisterMcpPostmortem = undefined;
|
|
3278
3413
|
}
|
|
3279
3414
|
};
|
|
3280
3415
|
}
|
|
@@ -3447,19 +3582,24 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3447
3582
|
}
|
|
3448
3583
|
}
|
|
3449
3584
|
|
|
3450
|
-
//
|
|
3451
|
-
//
|
|
3585
|
+
// MCP manager wiring has two ownership models:
|
|
3586
|
+
// * Single-slot callbacks (tools/prompts/resources changed) — exactly one
|
|
3587
|
+
// owner per manager. When reusing a parent's manager (subagent path,
|
|
3588
|
+
// see task/executor.ts), the parent already owns these slots so we
|
|
3589
|
+
// MUST NOT overwrite them. Guarded by `!options.mcpManager`.
|
|
3590
|
+
// * Notification listener — multi-listener by design. Every session with
|
|
3591
|
+
// an MCP manager (fresh OR reused) needs its own bridge to its own
|
|
3592
|
+
// `extensionRunner` so extensions loaded in that session receive frames.
|
|
3593
|
+
// Guarded only by `mcpManager` (see the second `if` below).
|
|
3452
3594
|
if (mcpManager && !options.mcpManager) {
|
|
3453
|
-
mcpManager.setOnToolsChanged(tools => {
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
}
|
|
3462
|
-
})();
|
|
3595
|
+
mcpManager.setOnToolsChanged(async tools => {
|
|
3596
|
+
try {
|
|
3597
|
+
await session.refreshMCPTools(tools);
|
|
3598
|
+
} catch (error) {
|
|
3599
|
+
logger.warn("MCP tool refresh failed", {
|
|
3600
|
+
error: error instanceof Error ? error.message : String(error),
|
|
3601
|
+
});
|
|
3602
|
+
}
|
|
3463
3603
|
});
|
|
3464
3604
|
// Wire prompt refresh → rebuild MCP prompt slash commands
|
|
3465
3605
|
mcpManager.setOnPromptsChanged(serverName => {
|
|
@@ -3492,6 +3632,29 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
3492
3632
|
});
|
|
3493
3633
|
}
|
|
3494
3634
|
|
|
3635
|
+
if (mcpManager) {
|
|
3636
|
+
// Bridge server-initiated notifications to this session's extension
|
|
3637
|
+
// handlers. Multi-listener registration: fresh-manager and reused-manager
|
|
3638
|
+
// sessions both install their own listener here, so a subagent's
|
|
3639
|
+
// extensions get frames even though the parent owns the single-slot
|
|
3640
|
+
// tool/prompt/resource callbacks above. MCPManager fires known
|
|
3641
|
+
// list/update refreshes internally, then invokes all registered
|
|
3642
|
+
// listeners with (server, method, params) for every frame (including
|
|
3643
|
+
// server-custom methods). Two-layer buffering protects the startup
|
|
3644
|
+
// race: MCPManager buffers frames received before the first
|
|
3645
|
+
// `addNotificationListener` subscriber (drains here); ExtensionRunner
|
|
3646
|
+
// buffers frames received before `initialize()` and drains them on
|
|
3647
|
+
// init. Both drop-oldest under pressure at cap 100.
|
|
3648
|
+
unsubscribeMcpNotifications = mcpManager.addNotificationListener((server, method, params) => {
|
|
3649
|
+
void extensionRunner.emitMcpNotification({ server, method, params });
|
|
3650
|
+
});
|
|
3651
|
+
// postmortem.register returns a cancel function; capture it so explicit
|
|
3652
|
+
// session.dispose can remove this from the global list (see finally above).
|
|
3653
|
+
unregisterMcpPostmortem = postmortem.register("mcp-notification-listener-cleanup", () =>
|
|
3654
|
+
unsubscribeMcpNotifications?.(),
|
|
3655
|
+
);
|
|
3656
|
+
}
|
|
3657
|
+
|
|
3495
3658
|
startDeferredMCPDiscovery?.(session);
|
|
3496
3659
|
|
|
3497
3660
|
return {
|
|
@@ -47,6 +47,7 @@ export type AgentSessionEvent =
|
|
|
47
47
|
}
|
|
48
48
|
| { type: "retry_fallback_applied"; from: string; to: string; role: string }
|
|
49
49
|
| { type: "retry_fallback_succeeded"; model: string; role: string }
|
|
50
|
+
| { type: "model_changed" }
|
|
50
51
|
| { type: "ttsr_triggered"; rules: Rule[] }
|
|
51
52
|
| { type: "todo_reminder"; todos: TodoItem[]; attempt: number; maxAttempts: number }
|
|
52
53
|
| { type: "todo_auto_clear" }
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
Agent,
|
|
3
|
+
AgentMessage,
|
|
4
|
+
AgentTool,
|
|
5
|
+
AgentToolContext,
|
|
6
|
+
StreamFn,
|
|
7
|
+
ThinkingLevel,
|
|
8
|
+
} from "@oh-my-pi/pi-agent-core";
|
|
2
9
|
import type {
|
|
3
10
|
Context,
|
|
4
11
|
Effort,
|
|
@@ -17,6 +24,7 @@ import type { AsyncJob, AsyncJobDeliveryState, AsyncJobManager } from "../async"
|
|
|
17
24
|
import type { ModelRegistry } from "../config/model-registry";
|
|
18
25
|
import type { PromptTemplate } from "../config/prompt-templates";
|
|
19
26
|
import type { Settings, SkillsSettings } from "../config/settings";
|
|
27
|
+
import type { CursorMcpResourceAdapter } from "../cursor";
|
|
20
28
|
import type { RawSseDebugBuffer } from "../debug/raw-sse-buffer";
|
|
21
29
|
import type { TtsrManager } from "../export/ttsr";
|
|
22
30
|
import type { LoadedCustomCommand } from "../extensibility/custom-commands";
|
|
@@ -161,6 +169,8 @@ export interface AgentSessionConfig {
|
|
|
161
169
|
sideStreamFn?: StreamFn;
|
|
162
170
|
/** Stream wrapper for advisor requests. */
|
|
163
171
|
advisorStreamFn?: StreamFn;
|
|
172
|
+
/** Advisor spend already recorded for the session being opened, restored on resume. */
|
|
173
|
+
initialAdvisorCosts?: ReadonlyMap<string, number>;
|
|
164
174
|
/** Prefer websocket transport for OpenAI Codex requests when supported. */
|
|
165
175
|
preferWebsockets?: boolean;
|
|
166
176
|
/** Provider payload hook used by the active session request path. */
|
|
@@ -207,6 +217,35 @@ export interface AgentSessionConfig {
|
|
|
207
217
|
providerPromptCacheKeySource?: "explicit" | "fork";
|
|
208
218
|
/** Full advisor toolset built against an advisor-scoped tool session. */
|
|
209
219
|
advisorTools?: AgentTool[];
|
|
220
|
+
/**
|
|
221
|
+
* Build a `grep` honoring a Cursor `pi_grep` frame's own context width and
|
|
222
|
+
* match cap, against the advisor-scoped tool session. Without it an advisor
|
|
223
|
+
* running on Cursor silently drops both fields.
|
|
224
|
+
*/
|
|
225
|
+
advisorCreateGrepTool?(options: { context?: number; totalMatchLimit?: number }): AgentTool | undefined;
|
|
226
|
+
/**
|
|
227
|
+
* Build the `replace`-mode `edit` a Cursor `pi_edit` frame needs, against the
|
|
228
|
+
* advisor-scoped tool session. The advisor's ordinary instance follows the
|
|
229
|
+
* configured `edit.mode` and rejects the frame's `old_text`/`new_text` pairs.
|
|
230
|
+
*/
|
|
231
|
+
advisorCreateEditTool?(): AgentTool | undefined;
|
|
232
|
+
/**
|
|
233
|
+
* The execute-time context the advisor's bridge tools resolve approval from.
|
|
234
|
+
*
|
|
235
|
+
* `ExtensionToolWrapper` reads `tools.approvalMode`, per-tool
|
|
236
|
+
* `tools.approval.<tool>` policies and `autoApprove` only from this context;
|
|
237
|
+
* with none it defaults to `yolo` with empty policies, so a bridge tool would
|
|
238
|
+
* run a native frame the user configured `ask` or `deny` for.
|
|
239
|
+
*/
|
|
240
|
+
advisorGetToolContext?: () => AgentToolContext | undefined;
|
|
241
|
+
/**
|
|
242
|
+
* The live MCP connections the advisor's Cursor resource frames answer from.
|
|
243
|
+
*
|
|
244
|
+
* Advisors share the session's connections and may be granted tools from
|
|
245
|
+
* those same servers; without this their `list_mcp_resources` reports an
|
|
246
|
+
* empty catalog and every `read_mcp_resource` a `not_found`.
|
|
247
|
+
*/
|
|
248
|
+
advisorMcpResources?: CursorMcpResourceAdapter;
|
|
210
249
|
/** Preloaded watchdog prompt content for the advisor. */
|
|
211
250
|
advisorWatchdogPrompt?: string;
|
|
212
251
|
/** Shared advisor instructions loaded from WATCHDOG.yml. */
|