@oh-my-pi/pi-coding-agent 17.2.9 → 17.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (281) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/dist/{CHANGELOG-8yy8h3bc.md → CHANGELOG-d9xenpn9.md} +83 -0
  3. package/dist/cli.js +10208 -12596
  4. package/dist/types/capability/mcp.d.ts +11 -0
  5. package/dist/types/capability/skill.d.ts +6 -0
  6. package/dist/types/cli/args.d.ts +1 -0
  7. package/dist/types/cli/command-help.d.ts +3 -0
  8. package/dist/types/commands/share.d.ts +25 -0
  9. package/dist/types/commit/agentic/index.d.ts +3 -1
  10. package/dist/types/commit/execute.d.ts +32 -0
  11. package/dist/types/commit/index.d.ts +2 -1
  12. package/dist/types/commit/pipeline.d.ts +7 -1
  13. package/dist/types/config/model-registry.d.ts +4 -0
  14. package/dist/types/config/model-resolver.d.ts +13 -0
  15. package/dist/types/config/settings-schema.d.ts +1 -34
  16. package/dist/types/discovery/agent-plugin-format.d.ts +143 -0
  17. package/dist/types/discovery/agent-plugins.d.ts +1 -0
  18. package/dist/types/discovery/contained-path.d.ts +34 -0
  19. package/dist/types/discovery/index.d.ts +1 -0
  20. package/dist/types/extensibility/custom-commands/types.d.ts +5 -5
  21. package/dist/types/extensibility/custom-tools/types.d.ts +3 -3
  22. package/dist/types/extensibility/extensions/loader.d.ts +9 -2
  23. package/dist/types/extensibility/extensions/types.d.ts +45 -8
  24. package/dist/types/extensibility/hooks/types.d.ts +5 -5
  25. package/dist/types/extensibility/shared-events.d.ts +3 -2
  26. package/dist/types/extensibility/skills.d.ts +5 -0
  27. package/dist/types/index.d.ts +2 -2
  28. package/dist/types/main.d.ts +1 -1
  29. package/dist/types/mcp/transports/header-policy.d.ts +45 -0
  30. package/dist/types/mcp/types.d.ts +15 -0
  31. package/dist/types/modes/acp/acp-agent.d.ts +1 -1
  32. package/dist/types/modes/acp/acp-client-bridge.d.ts +1 -1
  33. package/dist/types/modes/acp/acp-event-mapper.d.ts +1 -1
  34. package/dist/types/modes/acp/acp-mode.d.ts +1 -1
  35. package/dist/types/modes/components/agent-hub-projection.d.ts +38 -0
  36. package/dist/types/modes/components/agent-hub-renderer.d.ts +41 -0
  37. package/dist/types/modes/components/agent-hub.d.ts +14 -5
  38. package/dist/types/modes/components/agent-transcript-viewer.d.ts +2 -0
  39. package/dist/types/modes/components/chat-transcript-builder.d.ts +2 -0
  40. package/dist/types/modes/components/custom-editor.d.ts +1 -2
  41. package/dist/types/modes/components/model-browser.d.ts +9 -1
  42. package/dist/types/modes/components/status-line/types.d.ts +2 -0
  43. package/dist/types/modes/components/tool-execution.d.ts +2 -0
  44. package/dist/types/modes/interactive-mode.d.ts +8 -1
  45. package/dist/types/modes/utils/transcript-render-helpers.d.ts +3 -2
  46. package/dist/types/registry/agent-registry.d.ts +47 -0
  47. package/dist/types/registry/persisted-agents.d.ts +3 -1
  48. package/dist/types/secrets/index.d.ts +23 -1
  49. package/dist/types/session/agent-session-events.d.ts +2 -2
  50. package/dist/types/session/agent-session.d.ts +4 -2
  51. package/dist/types/session/messages.d.ts +1 -0
  52. package/dist/types/session/prewalk.d.ts +3 -1
  53. package/dist/types/session/session-entries.d.ts +10 -0
  54. package/dist/types/session/session-loader.d.ts +11 -1
  55. package/dist/types/session/session-manager.d.ts +9 -1
  56. package/dist/types/session/session-tools.d.ts +18 -1
  57. package/dist/types/session/turn-recovery.d.ts +7 -2
  58. package/dist/types/slash-commands/acp-builtins.d.ts +1 -1
  59. package/dist/types/slash-commands/available-commands.d.ts +1 -1
  60. package/dist/types/task/executor.d.ts +6 -0
  61. package/dist/types/task/index.d.ts +4 -3
  62. package/dist/types/task/read-only-policy.d.ts +3 -0
  63. package/dist/types/task/structured-subagent.d.ts +2 -0
  64. package/dist/types/task/types.d.ts +4 -0
  65. package/dist/types/tools/bash.d.ts +3 -3
  66. package/dist/types/tools/browser/launch.d.ts +2 -1
  67. package/dist/types/tools/browser/tab-worker.d.ts +2 -2
  68. package/dist/types/tools/path-utils.d.ts +1 -0
  69. package/dist/types/tools/read.d.ts +1 -5
  70. package/dist/types/tools/run-scope.d.ts +27 -1
  71. package/dist/types/tools/shell-tokenize.d.ts +16 -0
  72. package/dist/types/tools/terminal-output.d.ts +1 -1
  73. package/dist/types/utils/turndown.d.ts +1 -1
  74. package/dist/types/vibe/runtime.d.ts +2 -3
  75. package/dist/types/web/scrapers/readthedocs.d.ts +0 -3
  76. package/dist/types/web/search/providers/browser-headers.d.ts +2 -3
  77. package/examples/custom-tools/README.md +1 -1
  78. package/examples/extensions/README.md +1 -1
  79. package/examples/extensions/api-demo.ts +5 -6
  80. package/examples/extensions/chalk-logger.ts +1 -1
  81. package/examples/extensions/hello.ts +2 -2
  82. package/examples/extensions/reload-runtime.ts +1 -1
  83. package/examples/extensions/tools.ts +2 -2
  84. package/examples/extensions/with-deps/index.ts +1 -1
  85. package/examples/sdk/06-extensions.ts +1 -1
  86. package/package.json +15 -31
  87. package/scripts/bundle-dist.ts +1 -11
  88. package/scripts/legacy-pi-virtual-module.ts +4 -1
  89. package/src/advisor/runtime.ts +54 -0
  90. package/src/capability/mcp.ts +11 -0
  91. package/src/capability/skill.ts +6 -0
  92. package/src/cli/agents-cli.ts +1 -1
  93. package/src/cli/args.ts +24 -2
  94. package/src/cli/auth-broker-cli.ts +1 -1
  95. package/src/cli/auth-gateway-cli.ts +1 -1
  96. package/src/cli/bench-cli.ts +1 -1
  97. package/src/cli/claude-trace-cli.ts +1 -1
  98. package/src/cli/command-help.ts +4 -0
  99. package/src/cli/config-cli.ts +1 -1
  100. package/src/cli/dry-balance-cli.ts +1 -1
  101. package/src/cli/file-processor.ts +1 -1
  102. package/src/cli/flag-tables.ts +4 -0
  103. package/src/cli/gallery-cli.ts +1 -1
  104. package/src/cli/grep-cli.ts +1 -1
  105. package/src/cli/grievances-cli.ts +1 -1
  106. package/src/cli/help-extra.ts +1 -1
  107. package/src/cli/models-cli.ts +1 -1
  108. package/src/cli/plugin-cli.ts +1 -1
  109. package/src/cli/read-cli.ts +1 -1
  110. package/src/cli/setup-cli.ts +1 -1
  111. package/src/cli/shell-cli.ts +1 -1
  112. package/src/cli/ssh-cli.ts +1 -1
  113. package/src/cli/stats-cli.ts +1 -1
  114. package/src/cli/tiny-models-cli.ts +1 -1
  115. package/src/cli/ttsr-cli.ts +1 -1
  116. package/src/cli/update-cli.ts +1 -1
  117. package/src/cli/usage-cli.ts +1 -1
  118. package/src/cli/web-search-cli.ts +1 -1
  119. package/src/cli/worktree-cli.ts +1 -1
  120. package/src/cli-commands.ts +5 -0
  121. package/src/commands/commit.ts +16 -3
  122. package/src/commands/say.ts +1 -1
  123. package/src/commands/share.ts +71 -0
  124. package/src/commands/token.ts +1 -1
  125. package/src/commit/agentic/agent.ts +1 -1
  126. package/src/commit/agentic/index.ts +39 -21
  127. package/src/commit/cli.ts +1 -1
  128. package/src/commit/execute.ts +56 -0
  129. package/src/commit/index.ts +2 -1
  130. package/src/commit/pipeline.ts +20 -7
  131. package/src/config/model-discovery.ts +1 -1
  132. package/src/config/model-registry.ts +21 -1
  133. package/src/config/model-resolver.ts +54 -7
  134. package/src/config/settings-schema.ts +2 -33
  135. package/src/config/settings.ts +46 -0
  136. package/src/discovery/agent-plugin-format.ts +551 -0
  137. package/src/discovery/agent-plugins.ts +341 -0
  138. package/src/discovery/agents.ts +4 -2
  139. package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +5 -4
  140. package/src/discovery/builtin-rules/ts-no-tiny-functions.md +1 -1
  141. package/src/discovery/claude-plugins.ts +21 -5
  142. package/src/discovery/contained-path.ts +78 -0
  143. package/src/discovery/helpers.ts +23 -9
  144. package/src/discovery/index.ts +1 -0
  145. package/src/discovery/omp-plugins.ts +20 -7
  146. package/src/eval/py/runner.py +38 -1
  147. package/src/exec/non-interactive-env.ts +1 -0
  148. package/src/extensibility/custom-commands/loader.ts +4 -4
  149. package/src/extensibility/custom-commands/types.ts +5 -5
  150. package/src/extensibility/custom-tools/loader.ts +4 -4
  151. package/src/extensibility/custom-tools/types.ts +3 -3
  152. package/src/extensibility/extensions/loader.ts +85 -20
  153. package/src/extensibility/extensions/runner.ts +6 -0
  154. package/src/extensibility/extensions/types.ts +51 -8
  155. package/src/extensibility/hooks/loader.ts +4 -5
  156. package/src/extensibility/hooks/types.ts +5 -5
  157. package/src/extensibility/plugins/legacy-pi-compat.ts +53 -0
  158. package/src/extensibility/plugins/marketplace/manager.ts +2 -1
  159. package/src/extensibility/shared-events.ts +3 -2
  160. package/src/extensibility/skills.ts +9 -0
  161. package/src/index.ts +2 -2
  162. package/src/internal-urls/memory-protocol.ts +5 -1
  163. package/src/internal-urls/skill-protocol.ts +14 -0
  164. package/src/internal-urls/vault-protocol.ts +2 -2
  165. package/src/launch/client.ts +11 -1
  166. package/src/launch/protocol.ts +7 -2
  167. package/src/launch/terminal-output.ts +1 -1
  168. package/src/main.ts +88 -23
  169. package/src/markit/converters/docx.ts +1 -1
  170. package/src/markit/converters/epub.ts +1 -1
  171. package/src/markit/converters/pptx.ts +1 -1
  172. package/src/markit/converters/xlsx.ts +1 -1
  173. package/src/mcp/config.ts +3 -0
  174. package/src/mcp/manager.ts +12 -9
  175. package/src/mcp/transports/header-policy.ts +95 -0
  176. package/src/mcp/transports/http.ts +35 -52
  177. package/src/mcp/transports/sse.ts +20 -27
  178. package/src/mcp/types.ts +15 -0
  179. package/src/modes/acp/acp-agent.ts +20 -9
  180. package/src/modes/acp/acp-client-bridge.ts +1 -1
  181. package/src/modes/acp/acp-event-mapper.ts +1 -1
  182. package/src/modes/acp/acp-mode.ts +1 -1
  183. package/src/modes/components/agent-dashboard.ts +2 -0
  184. package/src/modes/components/agent-hub-projection.ts +248 -0
  185. package/src/modes/components/agent-hub-renderer.ts +194 -0
  186. package/src/modes/components/agent-hub.ts +674 -207
  187. package/src/modes/components/agent-transcript-viewer.ts +3 -0
  188. package/src/modes/components/assistant-message.ts +1 -1
  189. package/src/modes/components/chat-transcript-builder.ts +3 -0
  190. package/src/modes/components/custom-editor.ts +0 -9
  191. package/src/modes/components/extensions/inspector-panel.ts +11 -6
  192. package/src/modes/components/footer.ts +1 -3
  193. package/src/modes/components/model-browser.ts +11 -3
  194. package/src/modes/components/model-hub.ts +4 -1
  195. package/src/modes/components/status-line/component.ts +1 -0
  196. package/src/modes/components/status-line/segments.ts +10 -7
  197. package/src/modes/components/status-line/types.ts +2 -0
  198. package/src/modes/components/tool-execution.ts +13 -16
  199. package/src/modes/components/tree-selector.ts +62 -3
  200. package/src/modes/controllers/command-controller.ts +4 -1
  201. package/src/modes/controllers/event-controller.ts +44 -20
  202. package/src/modes/controllers/extension-ui-controller.ts +2 -2
  203. package/src/modes/controllers/input-controller.ts +20 -2
  204. package/src/modes/controllers/mcp-command-controller.ts +81 -20
  205. package/src/modes/controllers/selector-controller.ts +41 -49
  206. package/src/modes/controllers/streaming-reveal.ts +1 -1
  207. package/src/modes/interactive-mode.ts +13 -2
  208. package/src/modes/rpc/rpc-mode.ts +2 -2
  209. package/src/modes/runtime-init.ts +1 -1
  210. package/src/modes/theme/theme.ts +2 -2
  211. package/src/modes/utils/transcript-render-helpers.ts +4 -2
  212. package/src/modes/utils/ui-helpers.ts +1 -0
  213. package/src/prompts/tools/computer.md +1 -1
  214. package/src/registry/agent-lifecycle.ts +14 -7
  215. package/src/registry/agent-registry.ts +65 -2
  216. package/src/registry/persisted-agents.ts +341 -16
  217. package/src/sdk.ts +6 -49
  218. package/src/secrets/index.ts +52 -1
  219. package/src/session/agent-session-events.ts +2 -2
  220. package/src/session/agent-session.ts +54 -16
  221. package/src/session/messages.ts +1 -0
  222. package/src/session/prewalk.ts +57 -17
  223. package/src/session/session-context.ts +3 -5
  224. package/src/session/session-entries.ts +10 -0
  225. package/src/session/session-handoff.ts +5 -1
  226. package/src/session/session-listing.ts +1 -1
  227. package/src/session/session-loader.ts +74 -8
  228. package/src/session/session-manager.ts +22 -2
  229. package/src/session/session-paths.ts +57 -9
  230. package/src/session/session-tools.ts +65 -4
  231. package/src/session/todo-tracker.ts +11 -1
  232. package/src/session/turn-recovery.ts +96 -74
  233. package/src/slash-commands/acp-builtins.ts +1 -1
  234. package/src/slash-commands/available-commands.ts +1 -1
  235. package/src/slash-commands/builtin-registry.ts +12 -8
  236. package/src/task/executor.ts +70 -20
  237. package/src/task/index.ts +30 -34
  238. package/src/task/isolation-runner.ts +24 -11
  239. package/src/task/persisted-revive.ts +12 -5
  240. package/src/task/read-only-policy.ts +27 -0
  241. package/src/task/structured-subagent.ts +14 -4
  242. package/src/task/types.ts +4 -0
  243. package/src/tools/auto-generated-guard.ts +1 -1
  244. package/src/tools/bash-interactive.ts +4 -4
  245. package/src/tools/bash-skill-urls.ts +15 -0
  246. package/src/tools/bash.ts +16 -17
  247. package/src/tools/browser/cmux/cmux-tab.ts +66 -18
  248. package/src/tools/browser/launch.ts +41 -12
  249. package/src/tools/browser/readable.ts +9 -9
  250. package/src/tools/browser/tab-supervisor.ts +12 -3
  251. package/src/tools/browser/tab-worker-entry.ts +1 -1
  252. package/src/tools/browser/tab-worker.ts +101 -12
  253. package/src/tools/debug.ts +1 -1
  254. package/src/tools/fetch.ts +1 -1
  255. package/src/tools/jtd-to-json-schema.ts +123 -21
  256. package/src/tools/path-utils.ts +9 -2
  257. package/src/tools/read.ts +21 -9
  258. package/src/tools/run-scope.ts +290 -4
  259. package/src/tools/shell-tokenize.ts +98 -0
  260. package/src/tools/terminal-output.ts +1 -1
  261. package/src/tools/todo.ts +1 -1
  262. package/src/utils/clipboard.ts +10 -2
  263. package/src/utils/external-editor.ts +4 -2
  264. package/src/utils/jj.ts +1 -1
  265. package/src/utils/turndown.ts +1 -2
  266. package/src/vibe/runtime.ts +5 -5
  267. package/src/web/scrapers/arxiv.ts +1 -1
  268. package/src/web/scrapers/go-pkg.ts +1 -1
  269. package/src/web/scrapers/iacr.ts +1 -1
  270. package/src/web/scrapers/readthedocs.ts +2 -1
  271. package/src/web/scrapers/twitter.ts +2 -2
  272. package/src/web/scrapers/types.ts +1 -1
  273. package/src/web/scrapers/wikipedia.ts +1 -1
  274. package/src/web/search/providers/browser-headers.ts +12 -39
  275. package/src/web/search/providers/codex.ts +3 -26
  276. package/src/web/search/providers/ecosia.ts +1 -1
  277. package/src/web/search/providers/exa.ts +1 -1
  278. package/src/web/search/providers/google.ts +1 -1
  279. package/src/web/search/providers/mojeek.ts +1 -1
  280. package/src/web/search/providers/startpage.ts +1 -1
  281. package/src/markit/converters/mammoth.d.ts +0 -24
@@ -0,0 +1,95 @@
1
+ /**
2
+ * HTTP header precedence and redirect-origin policy for remote MCP transports.
3
+ *
4
+ * Two invariants, applied at every transport fetch:
5
+ *
6
+ * 1. Client-generated headers (protocol headers like `Content-Type`, `Accept`,
7
+ * `Mcp-Session-Id`, and authorization) take precedence over configured
8
+ * headers with the same case-insensitive name. Configured headers can never
9
+ * corrupt the MCP wire protocol via casing tricks.
10
+ * 2. Origin-locked servers (Agent Plugins §7.2.1) never forward configured
11
+ * headers to a different origin: redirects are followed manually and
12
+ * configured headers are attached only when the hop targets the configured
13
+ * origin. Method-changing redirects of non-GET requests are refused.
14
+ */
15
+
16
+ /** Header buckets for one MCP HTTP request. */
17
+ export interface MCPHeaderSources {
18
+ /** Client-generated HTTP/MCP/authorization headers; win case-insensitively. */
19
+ generated: Record<string, string>;
20
+ /** Configured headers from the server entry (package or user config). */
21
+ configured?: Record<string, string>;
22
+ }
23
+
24
+ /**
25
+ * Merge configured headers under client-generated ones: a configured entry is
26
+ * dropped when a generated header with the same case-insensitive name exists.
27
+ */
28
+ export function mergeMCPHeaders({ generated, configured }: MCPHeaderSources): Record<string, string> {
29
+ if (!configured) return { ...generated };
30
+ const generatedNames = new Set<string>();
31
+ for (const name in generated) generatedNames.add(name.toLowerCase());
32
+ const merged: Record<string, string> = {};
33
+ for (const name in configured) {
34
+ if (!generatedNames.has(name.toLowerCase())) merged[name] = configured[name];
35
+ }
36
+ return { ...merged, ...generated };
37
+ }
38
+
39
+ /**
40
+ * Set a client-generated header, removing any existing entry with the same
41
+ * case-insensitive name so the generated value is the only one sent.
42
+ */
43
+ export function setGeneratedHeader(headers: Record<string, string>, name: string, value: string): void {
44
+ const lower = name.toLowerCase();
45
+ for (const existing in headers) {
46
+ if (existing.toLowerCase() === lower) delete headers[existing];
47
+ }
48
+ headers[name] = value;
49
+ }
50
+
51
+ const REDIRECT_STATUSES: Record<number, true> = { 301: true, 302: true, 303: true, 307: true, 308: true };
52
+ const MAX_REDIRECT_HOPS = 5;
53
+
54
+ export interface MCPFetchInit {
55
+ method: "GET" | "POST" | "DELETE";
56
+ body?: string;
57
+ signal?: AbortSignal;
58
+ }
59
+
60
+ /**
61
+ * Fetch an MCP endpoint with header precedence and, for origin-locked servers,
62
+ * manual redirect handling that strips configured headers on cross-origin hops.
63
+ *
64
+ * Non-locked servers keep the platform default redirect behavior. Locked
65
+ * non-GET requests only follow 307/308 (method-preserving); a 301/302/303
66
+ * redirect of a JSON-RPC POST is a connection error, never a silent GET.
67
+ */
68
+ export async function mcpFetch(
69
+ url: string,
70
+ init: MCPFetchInit,
71
+ sources: MCPHeaderSources,
72
+ originLocked: boolean,
73
+ ): Promise<Response> {
74
+ if (!originLocked) {
75
+ return fetch(url, { ...init, headers: mergeMCPHeaders(sources) });
76
+ }
77
+
78
+ const configuredOrigin = new URL(url).origin;
79
+ let currentUrl = url;
80
+ for (let hop = 0; hop <= MAX_REDIRECT_HOPS; hop++) {
81
+ const attachConfigured = new URL(currentUrl).origin === configuredOrigin;
82
+ const headers = mergeMCPHeaders(attachConfigured ? sources : { generated: sources.generated });
83
+ const response = await fetch(currentUrl, { ...init, headers, redirect: "manual" });
84
+ if (!REDIRECT_STATUSES[response.status]) return response;
85
+
86
+ const location = response.headers.get("Location");
87
+ if (!location) return response;
88
+ await response.body?.cancel();
89
+ if (init.method !== "GET" && response.status !== 307 && response.status !== 308) {
90
+ throw new Error(`HTTP ${response.status}: server redirected a ${init.method} request; refusing to follow`);
91
+ }
92
+ currentUrl = new URL(location, currentUrl).href;
93
+ }
94
+ throw new Error(`Too many redirects (> ${MAX_REDIRECT_HOPS}) fetching ${url}`);
95
+ }
@@ -19,6 +19,7 @@ import type {
19
19
  import { toJsonRpcError } from "../../mcp/types";
20
20
  import { RequestIdAllocator } from "../request-id";
21
21
  import { createMCPTimeout, getNeverAbortSignal, isMCPTimeoutEnabled, resolveMCPTimeoutMs } from "../timeout";
22
+ import { type MCPFetchInit, mcpFetch } from "./header-policy";
22
23
 
23
24
  const HTTP_SSE_CONNECT_TIMEOUT_MS = 1_000;
24
25
  /**
@@ -54,6 +55,16 @@ export class HttpTransport implements MCPTransport {
54
55
 
55
56
  constructor(private config: MCPHttpServerConfig | MCPSseServerConfig) {}
56
57
 
58
+ /** Fetch the configured endpoint with header precedence and origin policy. */
59
+ #fetch(init: MCPFetchInit, generated: Record<string, string>): Promise<Response> {
60
+ return mcpFetch(
61
+ this.config.url,
62
+ init,
63
+ { generated, configured: this.config.headers },
64
+ this.config.headerPolicy === "origin-locked",
65
+ );
66
+ }
67
+
57
68
  get connected(): boolean {
58
69
  return this.#connected;
59
70
  }
@@ -81,13 +92,12 @@ export class HttpTransport implements MCPTransport {
81
92
  if (this.#sseConnection) return;
82
93
 
83
94
  this.#sseConnection = new AbortController();
84
- const headers: Record<string, string> = {
95
+ const generated: Record<string, string> = {
85
96
  Accept: "text/event-stream",
86
- ...this.config.headers,
87
97
  };
88
98
 
89
99
  if (this.#sessionId) {
90
- headers["Mcp-Session-Id"] = this.#sessionId;
100
+ generated["Mcp-Session-Id"] = this.#sessionId;
91
101
  }
92
102
 
93
103
  let response: Response | null;
@@ -95,11 +105,7 @@ export class HttpTransport implements MCPTransport {
95
105
  let startupFinished = false;
96
106
  const connection = this.#sseConnection;
97
107
  const startupTimeoutMs = resolveSSEConnectTimeoutMs(this.config.timeout);
98
- const fetchPromise = fetch(this.config.url, {
99
- method: "GET",
100
- headers,
101
- signal: connection.signal,
102
- });
108
+ const fetchPromise = this.#fetch({ method: "GET", signal: connection.signal }, generated);
103
109
  const timeoutPromise =
104
110
  startupTimeoutMs > 0
105
111
  ? new Promise<null>(resolve => {
@@ -219,26 +225,23 @@ export class HttpTransport implements MCPTransport {
219
225
  params: params ?? {},
220
226
  };
221
227
 
222
- const headers: Record<string, string> = {
228
+ const generated: Record<string, string> = {
223
229
  "Content-Type": "application/json",
224
230
  Accept: "application/json, text/event-stream",
225
- ...this.config.headers,
226
231
  };
227
232
 
228
233
  if (this.#sessionId) {
229
- headers["Mcp-Session-Id"] = this.#sessionId;
234
+ generated["Mcp-Session-Id"] = this.#sessionId;
230
235
  }
231
236
 
232
237
  const timeout = resolveMCPTimeoutMs(this.config.timeout);
233
238
  const operation = createMCPTimeout(timeout, options?.signal);
234
239
 
235
240
  try {
236
- const response = await fetch(this.config.url, {
237
- method: "POST",
238
- headers,
239
- body: JSON.stringify(body),
240
- signal: operation.signal,
241
- });
241
+ const response = await this.#fetch(
242
+ { method: "POST", body: JSON.stringify(body), signal: operation.signal },
243
+ generated,
244
+ );
242
245
 
243
246
  // Check for session ID in response
244
247
  const newSessionId = response.headers.get("Mcp-Session-Id");
@@ -365,23 +368,18 @@ export class HttpTransport implements MCPTransport {
365
368
  const body = error
366
369
  ? { jsonrpc: "2.0" as const, id, error }
367
370
  : { jsonrpc: "2.0" as const, id, result: result ?? {} };
368
- const headers: Record<string, string> = {
371
+ const generated: Record<string, string> = {
369
372
  "Content-Type": "application/json",
370
373
  Accept: "application/json, text/event-stream",
371
- ...this.config.headers,
372
374
  };
373
375
  if (this.#sessionId) {
374
- headers["Mcp-Session-Id"] = this.#sessionId;
376
+ generated["Mcp-Session-Id"] = this.#sessionId;
375
377
  }
378
+ const payload = JSON.stringify(body);
376
379
  const timeout = resolveMCPTimeoutMs(this.config.timeout);
377
380
  const operation = createMCPTimeout(timeout);
378
381
  try {
379
- const resp = await fetch(this.config.url, {
380
- method: "POST",
381
- headers,
382
- body: JSON.stringify(body),
383
- signal: operation.signal,
384
- });
382
+ const resp = await this.#fetch({ method: "POST", body: payload, signal: operation.signal }, generated);
385
383
  // Retry once on auth failure if onAuthError is wired
386
384
  if (this.onAuthError && (resp.status === 401 || resp.status === 403)) {
387
385
  await resp.body?.cancel();
@@ -389,16 +387,13 @@ export class HttpTransport implements MCPTransport {
389
387
  if (newHeaders) {
390
388
  this.config.headers ??= {};
391
389
  Object.assign(this.config.headers, newHeaders);
392
- Object.assign(headers, newHeaders);
393
390
  operation.clear();
394
391
  const retryOperation = createMCPTimeout(timeout);
395
392
  try {
396
- const retry = await fetch(this.config.url, {
397
- method: "POST",
398
- headers,
399
- body: JSON.stringify(body),
400
- signal: retryOperation.signal,
401
- });
393
+ const retry = await this.#fetch(
394
+ { method: "POST", body: payload, signal: retryOperation.signal },
395
+ generated,
396
+ );
402
397
  await retry.body?.cancel();
403
398
  } finally {
404
399
  retryOperation.clear();
@@ -425,26 +420,23 @@ export class HttpTransport implements MCPTransport {
425
420
  params: params ?? {},
426
421
  };
427
422
 
428
- const headers: Record<string, string> = {
423
+ const generated: Record<string, string> = {
429
424
  "Content-Type": "application/json",
430
425
  Accept: "application/json, text/event-stream",
431
- ...this.config.headers,
432
426
  };
433
427
 
434
428
  if (this.#sessionId) {
435
- headers["Mcp-Session-Id"] = this.#sessionId;
429
+ generated["Mcp-Session-Id"] = this.#sessionId;
436
430
  }
437
431
 
438
432
  const timeout = resolveMCPTimeoutMs(this.config.timeout);
439
433
  const operation = createMCPTimeout(timeout);
440
434
 
441
435
  try {
442
- const response = await fetch(this.config.url, {
443
- method: "POST",
444
- headers,
445
- body: JSON.stringify(body),
446
- signal: operation.signal,
447
- });
436
+ const response = await this.#fetch(
437
+ { method: "POST", body: JSON.stringify(body), signal: operation.signal },
438
+ generated,
439
+ );
448
440
 
449
441
  // 202 Accepted is success for notifications
450
442
  if (!response.ok && response.status !== 202) {
@@ -492,16 +484,7 @@ export class HttpTransport implements MCPTransport {
492
484
  const timeout = resolveMCPTimeoutMs(this.config.timeout);
493
485
  const operation = createMCPTimeout(timeout);
494
486
  try {
495
- const headers: Record<string, string> = {
496
- ...this.config.headers,
497
- "Mcp-Session-Id": this.#sessionId,
498
- };
499
-
500
- await fetch(this.config.url, {
501
- method: "DELETE",
502
- headers,
503
- signal: operation.signal,
504
- });
487
+ await this.#fetch({ method: "DELETE", signal: operation.signal }, { "Mcp-Session-Id": this.#sessionId });
505
488
  operation.clear();
506
489
  } catch {
507
490
  operation.clear();
@@ -12,6 +12,7 @@ import type {
12
12
  import { toJsonRpcError } from "../../mcp/types";
13
13
  import { RequestIdAllocator } from "../request-id";
14
14
  import { createMCPTimeout, getNeverAbortSignal, resolveMCPTimeoutMs } from "../timeout";
15
+ import { type MCPFetchInit, mcpFetch } from "./header-policy";
15
16
 
16
17
  interface MCPTimeoutOperation {
17
18
  signal?: AbortSignal;
@@ -46,6 +47,16 @@ export class LegacySseTransport implements MCPTransport {
46
47
  this.#config = config;
47
48
  }
48
49
 
50
+ /** Fetch an endpoint with header precedence and origin policy. */
51
+ #fetch(url: string, init: MCPFetchInit, generated: Record<string, string>): Promise<Response> {
52
+ return mcpFetch(
53
+ url,
54
+ init,
55
+ { generated, configured: this.#config.headers },
56
+ this.#config.headerPolicy === "origin-locked",
57
+ );
58
+ }
59
+
49
60
  get connected(): boolean {
50
61
  return this.#connected;
51
62
  }
@@ -65,14 +76,11 @@ export class LegacySseTransport implements MCPTransport {
65
76
  this.#sseConnection = connection;
66
77
 
67
78
  try {
68
- const response = await fetch(this.#config.url, {
69
- method: "GET",
70
- headers: {
71
- Accept: "text/event-stream",
72
- ...this.#config.headers,
73
- },
74
- signal: operation.signal,
75
- });
79
+ const response = await this.#fetch(
80
+ this.#config.url,
81
+ { method: "GET", signal: operation.signal },
82
+ { Accept: "text/event-stream" },
83
+ );
76
84
 
77
85
  if (!response.ok) {
78
86
  const text = await response.text();
@@ -286,17 +294,12 @@ export class LegacySseTransport implements MCPTransport {
286
294
  ): Promise<Response> {
287
295
  const endpointUrl = this.#endpointUrl;
288
296
  if (!endpointUrl) throw new Error("Transport not connected");
289
- let headers: Record<string, string> = {
297
+ const generated: Record<string, string> = {
290
298
  "Content-Type": "application/json",
291
299
  Accept: "application/json, text/event-stream",
292
- ...this.#config.headers,
293
300
  };
294
- let response = await fetch(endpointUrl, {
295
- method: "POST",
296
- headers,
297
- body: JSON.stringify(body),
298
- signal,
299
- });
301
+ const payload = JSON.stringify(body);
302
+ let response = await this.#fetch(endpointUrl, { method: "POST", body: payload, signal }, generated);
300
303
  const status = AIError.status(response);
301
304
  if (!this.onAuthError || (status !== 401 && status !== 403)) return response;
302
305
 
@@ -304,17 +307,7 @@ export class LegacySseTransport implements MCPTransport {
304
307
  if (!refreshedHeaders) return response;
305
308
  await response.body?.cancel();
306
309
  this.#config.headers = refreshedHeaders;
307
- headers = {
308
- "Content-Type": "application/json",
309
- Accept: "application/json, text/event-stream",
310
- ...this.#config.headers,
311
- };
312
- response = await fetch(endpointUrl, {
313
- method: "POST",
314
- headers,
315
- body: JSON.stringify(body),
316
- signal,
317
- });
310
+ response = await this.#fetch(endpointUrl, { method: "POST", body: payload, signal }, generated);
318
311
  return response;
319
312
  }
320
313
 
package/src/mcp/types.ts CHANGED
@@ -100,6 +100,13 @@ export interface MCPStdioServerConfig extends MCPServerConfigBase {
100
100
  command: string;
101
101
  args?: string[];
102
102
  env?: Record<string, string>;
103
+ /**
104
+ * `literal`: env values are opaque plugin package data (Agent Plugins
105
+ * §§4.1/9.2) — no env-name lookup, no `!command` execution, no dropping of
106
+ * empty values. The provider already applied the only permitted expansion
107
+ * (`${PLUGIN_ROOT}`/`${PLUGIN_DATA}`).
108
+ */
109
+ envPolicy?: "literal";
103
110
  cwd?: string;
104
111
  }
105
112
 
@@ -108,6 +115,12 @@ export interface MCPHttpServerConfig extends MCPServerConfigBase {
108
115
  type: "http";
109
116
  url: string;
110
117
  headers?: Record<string, string>;
118
+ /**
119
+ * `origin-locked`: configured headers are literal package data pinned to the
120
+ * configured URL's origin (Agent Plugins §7.2.1) — never expanded, never
121
+ * forwarded cross-origin, and client-generated headers win case-insensitively.
122
+ */
123
+ headerPolicy?: "origin-locked";
111
124
  }
112
125
 
113
126
  /** SSE server configuration (deprecated, use HTTP) */
@@ -115,6 +128,8 @@ export interface MCPSseServerConfig extends MCPServerConfigBase {
115
128
  type: "sse";
116
129
  url: string;
117
130
  headers?: Record<string, string>;
131
+ /** See {@link MCPHttpServerConfig.headerPolicy}. */
132
+ headerPolicy?: "origin-locked";
118
133
  }
119
134
 
120
135
  export type MCPServerConfig = MCPStdioServerConfig | MCPHttpServerConfig | MCPSseServerConfig;
@@ -1,5 +1,8 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import * as path from "node:path";
3
+ import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
4
+ import type { AssistantMessage, Model } from "@oh-my-pi/pi-ai";
5
+ import { getBlobsDir, isEnoent, logger, type postmortem, VERSION } from "@oh-my-pi/pi-utils";
3
6
  import {
4
7
  type Agent,
5
8
  type AgentSideConnection,
@@ -39,10 +42,7 @@ import {
39
42
  type SetSessionModeRequest,
40
43
  type SetSessionModeResponse,
41
44
  type Usage,
42
- } from "@agentclientprotocol/sdk";
43
- import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
44
- import type { AssistantMessage, Model } from "@oh-my-pi/pi-ai";
45
- import { getBlobsDir, isEnoent, logger, type postmortem, VERSION } from "@oh-my-pi/pi-utils";
45
+ } from "@oh-my-pi/pi-utils/acp";
46
46
  import { disableProvider, enableProvider, reset as resetCapabilities } from "../../capability";
47
47
  import { Settings } from "../../config/settings";
48
48
  import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../../discovery/helpers";
@@ -70,6 +70,7 @@ import { SessionManager } from "../../session/session-manager";
70
70
  import { executeAcpBuiltinSlashCommand } from "../../slash-commands/acp-builtins";
71
71
  import { buildAvailableSlashCommands, toAcpAvailableCommands } from "../../slash-commands/available-commands";
72
72
  import { DEFAULT_STT_MODEL_KEY, STT_MODEL_OPTIONS } from "../../stt/models";
73
+ import { refreshAgentDiscovery } from "../../task";
73
74
  import { AUTO_THINKING, parseConfiguredThinkingLevel } from "../../thinking";
74
75
  import { normalizeLocalScheme } from "../../tools/path-utils";
75
76
  import { ToolError } from "../../tools/tool-errors";
@@ -1930,14 +1931,15 @@ export class AcpAgent implements Agent {
1930
1931
  /**
1931
1932
  * Reload plugin/registry state for an ACP session. Mirrors the interactive
1932
1933
  * `/reload-plugins` and `/move` flows: invalidates the plugin-roots cache,
1933
- * resets the capability cache, refreshes the session's slash-command state,
1934
- * then re-advertises commands so the client sees newly installed/disabled
1935
- * plugins.
1934
+ * refreshes task agents, resets the capability cache, refreshes the
1935
+ * session's slash-command state, then re-advertises commands so the client
1936
+ * sees newly installed/disabled plugins.
1936
1937
  */
1937
1938
  async #reloadPluginState(record: ManagedSessionRecord): Promise<void> {
1938
1939
  const cwd = record.session.sessionManager.getCwd();
1939
1940
  const projectPath = await resolveActiveProjectRegistryPath(cwd);
1940
1941
  clearPluginRootsAndCaches(projectPath ? [projectPath] : undefined);
1942
+ await refreshAgentDiscovery(cwd);
1941
1943
  resetCapabilities();
1942
1944
  await record.session.refreshSkills();
1943
1945
  const fileCommands = await loadSlashCommands({ cwd });
@@ -2019,7 +2021,16 @@ export class AcpAgent implements Agent {
2019
2021
 
2020
2022
  async #findStoredSession(sessionId: string, cwd: string): Promise<StoredSessionInfo | undefined> {
2021
2023
  const sessions = await this.#listStoredSessions(cwd);
2022
- return sessions.find(session => session.id === sessionId);
2024
+ const scoped = sessions.find(session => session.id === sessionId);
2025
+ if (scoped) {
2026
+ return scoped;
2027
+ }
2028
+ // The cwd-derived directory only covers sessions stored under the current
2029
+ // naming scheme. Sessions written under a legacy/hashed project directory
2030
+ // (the 17.2.5+ scheme reverted in #7656) live elsewhere, so fall back to a
2031
+ // global by-id scan: the session id is globally unique, and
2032
+ // #openStoredSession reopens the file with the request cwd. See #7779.
2033
+ return this.#findStoredSessionById(sessionId);
2023
2034
  }
2024
2035
 
2025
2036
  async #findStoredSessionById(sessionId: string): Promise<StoredSessionInfo | undefined> {
@@ -2348,7 +2359,7 @@ export class AcpAgent implements Agent {
2348
2359
  record.session.sessionManager.appendLabelChange(targetId, label);
2349
2360
  },
2350
2361
  getActiveTools: () => record.session.getEnabledToolNames(),
2351
- getAllTools: () => record.session.getAllToolNames(),
2362
+ getAllTools: () => record.session.getAllToolInfos(),
2352
2363
  setActiveTools: toolNames => record.session.setActiveToolsByName(toolNames),
2353
2364
  getCommands: () => getSessionSlashCommands(record.session),
2354
2365
  setModel: async model => {
@@ -11,7 +11,7 @@ import type {
11
11
  ClientCapabilities,
12
12
  RequestPermissionRequest,
13
13
  ToolCallUpdate,
14
- } from "@agentclientprotocol/sdk";
14
+ } from "@oh-my-pi/pi-utils/acp";
15
15
  import type {
16
16
  ClientBridge,
17
17
  ClientBridgeCapabilities,
@@ -5,7 +5,7 @@ import type {
5
5
  ToolCallContent,
6
6
  ToolCallLocation,
7
7
  ToolKind,
8
- } from "@agentclientprotocol/sdk";
8
+ } from "@oh-my-pi/pi-utils/acp";
9
9
  import { parseXdUrl } from "../../internal-urls/xd-protocol";
10
10
  import type { AgentSessionEvent } from "../../session/agent-session";
11
11
  import { resolveToCwd } from "../../tools/path-utils";
@@ -1,6 +1,6 @@
1
1
  import * as stream from "node:stream";
2
- import { AgentSideConnection, ndJsonStream, type Stream } from "@agentclientprotocol/sdk";
3
2
  import { postmortem } from "@oh-my-pi/pi-utils";
3
+ import { AgentSideConnection, ndJsonStream, type Stream } from "@oh-my-pi/pi-utils/acp";
4
4
  import type { AgentSession } from "../../session/agent-session";
5
5
  import { AcpAgent } from "./acp-agent";
6
6
 
@@ -48,6 +48,7 @@ import { Settings } from "../../config/settings";
48
48
  import agentCreationArchitectPrompt from "../../prompts/system/agent-creation-architect.md" with { type: "text" };
49
49
  import agentCreationUserPrompt from "../../prompts/system/agent-creation-user.md" with { type: "text" };
50
50
  import { createAgentSession } from "../../sdk";
51
+ import { refreshAgentDiscovery } from "../../task";
51
52
  import { discoverAgents } from "../../task/discovery";
52
53
  import { resolveAgentPrewalkDefault } from "../../task/prewalk";
53
54
  import type { AgentDefinition, AgentSource } from "../../task/types";
@@ -824,6 +825,7 @@ export class AgentDashboard extends Container {
824
825
  ).trimEnd();
825
826
  const content = `---\n${frontmatter}\n---\n\n${spec.systemPrompt.trim()}\n`;
826
827
  await Bun.write(filePath, content);
828
+ await refreshAgentDiscovery(this.cwd);
827
829
  await this.#reloadData();
828
830
  this.#clearCreateFlow();
829
831
  this.#notice = `Created agent ${spec.identifier} at ${shortenPath(filePath)}`;