@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
@@ -423,6 +423,25 @@ function isGlobalRequireCall(node: StructuralAstNode | null, scope: BindingScope
423
423
  );
424
424
  }
425
425
 
426
+ /**
427
+ * Whether `node` is a `createRequire(...)` factory call imported from
428
+ * `node:module` (or its `module` alias).
429
+ */
430
+ function isCreateRequireInvocation(
431
+ node: StructuralAstNode | null,
432
+ createRequireBindings: ReadonlySet<string>,
433
+ moduleNamespaceBindings: ReadonlySet<string>,
434
+ ): boolean {
435
+ if (node?.type !== "CallExpression") return false;
436
+ const callee = asAstNode(node.callee);
437
+ if (callee?.type === "Identifier" && typeof callee.name === "string") {
438
+ return createRequireBindings.has(callee.name);
439
+ }
440
+ if (callee?.type !== "MemberExpression" || staticMemberPropertyName(callee) !== "createRequire") return false;
441
+ const object = asAstNode(callee.object);
442
+ return object?.type === "Identifier" && typeof object.name === "string" && moduleNamespaceBindings.has(object.name);
443
+ }
444
+
426
445
  function staticMemberPropertyName(node: StructuralAstNode): string | null {
427
446
  const property = asAstNode(node.property);
428
447
  if (node.computed !== true && property?.type === "Identifier" && typeof property.name === "string") {
@@ -458,6 +477,28 @@ function collectExtensionSpecifierReferences(
458
477
  references.push({ kind, specifier: node.value, start: node.start, end: node.end });
459
478
  }
460
479
  };
480
+ const createRequireBindings = new Set<string>();
481
+ const moduleNamespaceBindings = new Set<string>();
482
+ for (const { node } of collectScopedAstNodes(ast, candidate => candidate.type === "ImportDeclaration")) {
483
+ const source = asAstNode(node.source);
484
+ if (source?.type !== "StringLiteral" || (source.value !== "node:module" && source.value !== "module")) continue;
485
+ for (const value of Array.isArray(node.specifiers) ? node.specifiers : []) {
486
+ const specifier = asAstNode(value);
487
+ const local = asAstNode(specifier?.local);
488
+ if (local?.type !== "Identifier" || typeof local.name !== "string") continue;
489
+ if (specifier?.type === "ImportNamespaceSpecifier") {
490
+ moduleNamespaceBindings.add(local.name);
491
+ } else if (specifier?.type === "ImportSpecifier") {
492
+ const imported = asAstNode(specifier.imported);
493
+ if (
494
+ (imported?.type === "Identifier" && imported.name === "createRequire") ||
495
+ (imported?.type === "StringLiteral" && imported.value === "createRequire")
496
+ ) {
497
+ createRequireBindings.add(local.name);
498
+ }
499
+ }
500
+ }
501
+ }
461
502
  for (const { node, scope } of collectScopedAstNodes(ast, isSpecifierReferenceNode)) {
462
503
  if (
463
504
  node.type === "ImportDeclaration" ||
@@ -478,6 +519,18 @@ function collectExtensionSpecifierReferences(
478
519
  record("import", nodeArgument(node, 0));
479
520
  } else if (isIdentifier(callee, "require") && !scopeHasBinding(scope, REQUIRE_BINDING)) {
480
521
  record("require", nodeArgument(node, 0));
522
+ } else if (isCreateRequireInvocation(callee, createRequireBindings, moduleNamespaceBindings)) {
523
+ // `createRequire(base)(spec)` — pin the invoked bare dependency so it
524
+ // loads without a runtime `node_modules` lookup. Relative specifiers
525
+ // resolve against `base`, which is not rewritten, so restrict to bare.
526
+ const argument = nodeArgument(node, 0);
527
+ if (
528
+ argument?.type === "StringLiteral" &&
529
+ typeof argument.value === "string" &&
530
+ isBareExtensionDependencySpecifier(argument.value)
531
+ ) {
532
+ record("require", argument);
533
+ }
481
534
  }
482
535
  }
483
536
  }
@@ -408,7 +408,7 @@ export class MarketplaceManager {
408
408
  /**
409
409
  * Resolve plugin version from multiple sources:
410
410
  * 1. Catalog entry version (if set)
411
- * 2. Plugin manifest (.claude-plugin/plugin.json or package.json)
411
+ * 2. Plugin manifest (.claude-plugin/plugin.json, Agent Plugins root plugin.json, or package.json)
412
412
  * 3. Git SHA from source (truncated to 7 chars)
413
413
  * 4. Fallback "0.0.0"
414
414
  */
@@ -419,6 +419,7 @@ export class MarketplaceManager {
419
419
  // 2. Plugin manifest
420
420
  for (const manifestPath of [
421
421
  path.join(sourcePath, ".claude-plugin", "plugin.json"),
422
+ path.join(sourcePath, "plugin.json"),
422
423
  path.join(sourcePath, "package.json"),
423
424
  ]) {
424
425
  try {
@@ -249,7 +249,8 @@ export interface AutoRetryStartEvent {
249
249
  errorId?: number;
250
250
  }
251
251
 
252
- export interface RecoveredRetryError {
252
+ /** Persisted retry error whose transcript presentation changed when the retry saga settled. */
253
+ export interface RetryErrorUpdate {
253
254
  entryId: string;
254
255
  persistenceKey?: string;
255
256
  note: string;
@@ -262,7 +263,7 @@ export interface AutoRetryEndEvent {
262
263
  success: boolean;
263
264
  attempt: number;
264
265
  finalError?: string;
265
- recoveredErrors?: RecoveredRetryError[];
266
+ retryErrors?: RetryErrorUpdate[];
266
267
  }
267
268
 
268
269
  // ============================================================================
@@ -27,6 +27,11 @@ export interface Skill {
27
27
  * prompt's `<skills>` listing.
28
28
  */
29
29
  hide?: boolean;
30
+ /**
31
+ * Filesystem-resolved plugin root for Agent Plugin skills (spec §4.1):
32
+ * every `skill://` resource access must realpath-resolve within it.
33
+ */
34
+ containRoot?: string;
30
35
  /** Source metadata for display */
31
36
  _source?: SourceMeta;
32
37
  }
@@ -104,6 +109,7 @@ export async function loadSkillsFromDir(options: LoadSkillsFromDirOptions): Prom
104
109
  filePath: capSkill.path,
105
110
  baseDir: capSkill.path.replace(/[\\/]SKILL\.md$/, ""),
106
111
  source: options.source,
112
+ ...(capSkill.containRoot !== undefined && { containRoot: capSkill.containRoot }),
107
113
  hide: capSkill.frontmatter?.hide === true || capSkill.frontmatter?.disableModelInvocation === true,
108
114
  _source: capSkill._source,
109
115
  })),
@@ -239,6 +245,7 @@ export async function loadSkills(options: LoadSkillsOptions = {}): Promise<LoadS
239
245
  filePath: capSkill.path,
240
246
  baseDir: capSkill.path.replace(/[\\/]SKILL\.md$/, ""),
241
247
  source: `${capSkill._source.provider}:${capSkill.level}`,
248
+ ...(capSkill.containRoot !== undefined && { containRoot: capSkill.containRoot }),
242
249
  hide: capSkill.frontmatter?.hide === true || capSkill.frontmatter?.disableModelInvocation === true,
243
250
  _source: capSkill._source,
244
251
  });
@@ -276,6 +283,7 @@ export async function loadSkills(options: LoadSkillsOptions = {}): Promise<LoadS
276
283
  filePath: capSkill.path,
277
284
  baseDir: capSkill.path.replace(/[\\/]SKILL\.md$/, ""),
278
285
  source: "custom:user",
286
+ ...(capSkill.containRoot !== undefined && { containRoot: capSkill.containRoot }),
279
287
  hide: capSkill.frontmatter?.hide === true || capSkill.frontmatter?.disableModelInvocation === true,
280
288
  _source: { ...capSkill._source, providerName: "Custom" },
281
289
  },
@@ -376,6 +384,7 @@ export async function loadSkills(options: LoadSkillsOptions = {}): Promise<LoadS
376
384
  filePath: capSkill.path,
377
385
  baseDir: capSkill.path.replace(/[\\/]SKILL\.md$/, ""),
378
386
  source: `${capSkill._source.provider}:${capSkill.level}`,
387
+ ...(capSkill.containRoot !== undefined && { containRoot: capSkill.containRoot }),
379
388
  hide: capSkill.frontmatter?.hide === true || capSkill.frontmatter?.disableModelInvocation === true,
380
389
  _source: capSkill._source,
381
390
  });
package/src/index.ts CHANGED
@@ -2,12 +2,12 @@ import { HookEditorComponent, HookInputComponent, HookSelectorComponent } from "
2
2
 
3
3
  // Core session management
4
4
 
5
+ export * as zod from "@oh-my-pi/omptype/zod";
6
+ export { z } from "@oh-my-pi/omptype/zod";
5
7
  // Re-export TUI components for custom tool rendering
6
8
  export { Container, Markdown, Spacer, Text } from "@oh-my-pi/pi-tui";
7
9
  // Logging
8
10
  export { getAgentDir, logger, VERSION } from "@oh-my-pi/pi-utils";
9
- export * as zod from "zod/v4";
10
- export { z } from "zod/v4";
11
11
  export * from "./config/keybindings";
12
12
  export * from "./config/model-registry";
13
13
  // Prompt templates
@@ -290,6 +290,10 @@ export class MemoryProtocolHandler implements ProtocolHandler {
290
290
  readonly immutable = true;
291
291
 
292
292
  async resolve(url: InternalUrl, context?: ResolveContext): Promise<InternalResource> {
293
+ const backend = memoryBackendFromContext(context);
294
+ if (backend === "off") {
295
+ throw new Error("Unknown protocol: memory://");
296
+ }
293
297
  const namespace = url.rawHost || url.hostname;
294
298
  if (!namespace) {
295
299
  throw new Error("memory:// URL requires a namespace: memory://root or memory://<memory-id>");
@@ -303,7 +307,7 @@ export class MemoryProtocolHandler implements ProtocolHandler {
303
307
  if (namespace !== MEMORY_NAMESPACE) {
304
308
  const mnemopiStates = mnemopiSessionStatesFromRegistry();
305
309
  const hindsightActive =
306
- memoryBackendFromContext(context) === "hindsight" ||
310
+ backend === "hindsight" ||
307
311
  (mnemopiStates.length === 0 &&
308
312
  AgentRegistry.global()
309
313
  .list()
@@ -11,6 +11,7 @@ import type * as fsTypes from "node:fs";
11
11
  import * as fs from "node:fs/promises";
12
12
  import * as path from "node:path";
13
13
  import { isEnoent } from "@oh-my-pi/pi-utils";
14
+ import { resolveContainedPath } from "../discovery/contained-path";
14
15
  import { getActiveSkills } from "../extensibility/skills";
15
16
  import { isMarkdownPath } from "../utils/lang-from-path";
16
17
  import { buildDirectoryResource } from "./filesystem-resource";
@@ -76,6 +77,19 @@ export class SkillProtocolHandler implements ProtocolHandler {
76
77
  if (!resolvedPath.startsWith(resolvedBaseDir + path.sep) && resolvedPath !== resolvedBaseDir) {
77
78
  throw new Error("Path traversal is not allowed");
78
79
  }
80
+ // Agent Plugin skills (§4.1): the resource must canonically resolve
81
+ // within the plugin root; a dangling or unresolvable path fails closed.
82
+ // Symlinks may target other files inside the same package.
83
+ if (skill.containRoot) {
84
+ const contained = await resolveContainedPath(skill.containRoot, resolvedPath);
85
+ if (contained.status === "outside") {
86
+ throw new Error(`skill:// path resolves outside the plugin root: ${url.href}`);
87
+ }
88
+ if (contained.status === "missing") {
89
+ throw new Error(`File not found: ${resolvedPath}`);
90
+ }
91
+ targetPath = contained.realPath;
92
+ }
79
93
  } else {
80
94
  targetPath = context?.pathOnly === true ? skill.baseDir : skill.filePath;
81
95
  }
@@ -787,7 +787,7 @@ export class VaultProtocolHandler implements ProtocolHandler {
787
787
  const cacheKey = parsed.ref.active ? "_" : (parsed.ref.vault ?? "_");
788
788
  let cliInfo = cachedVaultInfo.get(cacheKey);
789
789
  if (cliInfo === undefined) {
790
- const result = await this.#spawn(["vault", "info", ...this.#vaultCliArg(parsed.ref)], context);
790
+ const result = await this.#spawn([...this.#vaultCliArg(parsed.ref), "vault", "info"], context);
791
791
  assertCliSuccess("vault info", result);
792
792
  cliInfo = result.stdout.trim();
793
793
  cachedVaultInfo.set(cacheKey, cliInfo);
@@ -926,7 +926,7 @@ export class VaultProtocolHandler implements ProtocolHandler {
926
926
  context?: ResolveContext,
927
927
  ): Promise<InternalResource> {
928
928
  const invocation = buildObsidianCliInvocation(parsed);
929
- const args = [...invocation.args, ...this.#vaultCliArg(parsed.ref)];
929
+ const args = [...this.#vaultCliArg(parsed.ref), ...invocation.args];
930
930
  const result = await this.#spawn(args, context);
931
931
  assertCliSuccess(invocation.opLabel, result);
932
932
  return {
@@ -367,7 +367,17 @@ class SocketDaemonClient implements DaemonBrokerClient {
367
367
  try {
368
368
  message = parseDaemonWireMessage(decoded);
369
369
  } catch (error) {
370
- this.#rejectPending(error instanceof Error ? error : new Error(String(error)));
370
+ const parseError = error instanceof Error ? error : new Error(String(error));
371
+ if (
372
+ typeof decoded === "object" &&
373
+ decoded !== null &&
374
+ "event" in decoded &&
375
+ decoded.event === "daemon-completed"
376
+ ) {
377
+ logger.warn("Ignoring malformed daemon completion", { error: parseError.message });
378
+ continue;
379
+ }
380
+ this.#rejectPending(parseError);
371
381
  continue;
372
382
  }
373
383
  if ("event" in message) {
@@ -168,6 +168,11 @@ function optionalString(value: unknown, label: string): string | undefined {
168
168
  return stringValue(value, label);
169
169
  }
170
170
 
171
+ function optionalRawString(value: unknown, label: string): string | undefined {
172
+ if (value === undefined) return undefined;
173
+ return rawString(value, label);
174
+ }
175
+
171
176
  function booleanValue(value: unknown, label: string): boolean {
172
177
  if (typeof value !== "boolean") throw new Error(`${label} must be a boolean`);
173
178
  return value;
@@ -272,7 +277,7 @@ export function parseDaemonSnapshot(value: unknown): DaemonSnapshot {
272
277
  restartCount: numberValue(source.restartCount, "daemon.restartCount"),
273
278
  outputBytes: numberValue(source.outputBytes, "daemon.outputBytes"),
274
279
  owner: optionalString(source.owner, "daemon.owner"),
275
- readyMatch: optionalString(source.readyMatch, "daemon.readyMatch"),
280
+ readyMatch: optionalRawString(source.readyMatch, "daemon.readyMatch"),
276
281
  readyPending: source.readyPending === undefined ? undefined : readyPendingList(source.readyPending),
277
282
  persist: booleanValue(source.persist, "daemon.persist"),
278
283
  detached: source.detached === undefined ? false : booleanValue(source.detached, "daemon.detached"),
@@ -427,7 +432,7 @@ export function parseDaemonRpcResult(operation: DaemonOperation, value: unknown)
427
432
  return {
428
433
  op: "wait",
429
434
  daemon: parseDaemonSnapshot(source.daemon),
430
- matched: optionalString(source.matched, "result.matched"),
435
+ matched: optionalRawString(source.matched, "result.matched"),
431
436
  timedOut: booleanValue(source.timedOut, "result.timedOut"),
432
437
  };
433
438
  case "send":
@@ -1,5 +1,5 @@
1
1
  import { logger } from "@oh-my-pi/pi-utils";
2
- import xterm, { type Terminal as XtermTerminal } from "@xterm/headless";
2
+ import xterm, { type Terminal as XtermTerminal } from "@oh-my-pi/pi-utils/vterm";
3
3
  import { readTerminalRows } from "../tools/terminal-output";
4
4
  import { DAEMON_PTY_COLUMNS, DAEMON_PTY_ROWS } from "./protocol";
5
5
 
package/src/main.ts CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  setProjectDir,
22
22
  VERSION,
23
23
  } from "@oh-my-pi/pi-utils";
24
- import chalk from "chalk";
24
+ import chalk from "@oh-my-pi/pi-utils/chalk";
25
25
  import { reset as resetCapabilities } from "./capability";
26
26
  import { type Args, reportUnrecognizedFlags } from "./cli/args";
27
27
  import { applyExtensionFlags, type ExtensionFlagSink } from "./cli/extension-flags";
@@ -52,6 +52,7 @@ import {
52
52
  } from "./discovery/helpers";
53
53
  import { injectOmpExtensionCliRoots } from "./discovery/omp-extension-roots";
54
54
  import { formatExtensionLoadNotifications } from "./extensibility/extensions/load-errors";
55
+ import { loadExtensions } from "./extensibility/extensions/loader";
55
56
  import { ExtensionRunner } from "./extensibility/extensions/runner";
56
57
  import type { ExtensionUIContext } from "./extensibility/extensions/types";
57
58
  import { scheduleMarketplaceAutoUpdate } from "./extensibility/plugins/marketplace-auto-update";
@@ -358,11 +359,31 @@ export interface AcpSessionFactoryOptions {
358
359
  sessionDir?: string;
359
360
  authStorage: AuthStorage;
360
361
  modelRegistry: ModelRegistry;
361
- parsedArgs: Pick<Args, "apiKey">;
362
+ parsedArgs: Pick<Args, "apiKey" | "trustedExtensions">;
362
363
  rawArgs: string[];
363
364
  createSession: (options: CreateAgentSessionOptions) => Promise<CreateAgentSessionResult>;
364
365
  }
365
366
 
367
+ async function loadTrustedSessionExtensions(
368
+ options: Pick<CreateAgentSessionOptions, "additionalExtensionPaths">,
369
+ cwd: string,
370
+ eventBus: EventBus,
371
+ ) {
372
+ const paths = options.additionalExtensionPaths ?? [];
373
+ for (const trustedPath of paths) {
374
+ let stat: fsSync.Stats;
375
+ try {
376
+ stat = fsSync.statSync(trustedPath);
377
+ } catch {
378
+ throw new Error(`Trusted extension must be an existing module file: ${trustedPath}`);
379
+ }
380
+ if (!stat.isFile()) {
381
+ throw new Error(`Trusted extension must be a module file, not a directory: ${trustedPath}`);
382
+ }
383
+ }
384
+ return loadExtensions(paths, cwd, eventBus);
385
+ }
386
+
366
387
  /**
367
388
  * Build the per-`session/new` factory used by ACP mode.
368
389
  *
@@ -385,6 +406,16 @@ export function createAcpSessionFactory(args: AcpSessionFactoryOptions): AcpSess
385
406
  // policy (PR #3736 follow-up).
386
407
  const titleSystemPromptSource = discoverTitleSystemPromptFile(cwd);
387
408
  const titleSystemPrompt = await resolvePromptInput(titleSystemPromptSource, "title system prompt");
409
+ const eventBus = new EventBus();
410
+ const trustedExtensions =
411
+ args.parsedArgs.trustedExtensions && args.parsedArgs.trustedExtensions.length > 0
412
+ ? await loadTrustedSessionExtensions(args.baseOptions, cwd, eventBus)
413
+ : undefined;
414
+ if (trustedExtensions && trustedExtensions.errors.length > 0) {
415
+ throw new Error(
416
+ `Trusted extension failed to load: ${trustedExtensions.errors.map(item => item.error).join("; ")}`,
417
+ );
418
+ }
388
419
  const { session: nextSession } = await args.createSession({
389
420
  ...args.baseOptions,
390
421
  cwd,
@@ -398,6 +429,8 @@ export function createAcpSessionFactory(args: AcpSessionFactoryOptions): AcpSess
398
429
  deferUsageReserveConfirmation: true,
399
430
  enableMCP: false,
400
431
  titleSystemPrompt,
432
+ eventBus,
433
+ preloadedExtensions: trustedExtensions,
401
434
  });
402
435
  if (args.parsedArgs.apiKey && !args.baseOptions.model && nextSession.model) {
403
436
  args.authStorage.setRuntimeApiKey(nextSession.model.provider, args.parsedArgs.apiKey);
@@ -1000,11 +1033,12 @@ export async function buildSessionOptions(
1000
1033
  if (parsed.noPrewalk && (parsed.prewalk || parsed.prewalkInto !== undefined)) {
1001
1034
  throw new Error("--no-prewalk cannot be combined with --prewalk or --prewalk-into");
1002
1035
  }
1036
+ const explicitPrewalk = parsed.prewalk === true || parsed.prewalkInto !== undefined;
1003
1037
  const prewalkEnabled = parsed.noPrewalk
1004
1038
  ? false
1005
- : parsed.prewalk === true || parsed.prewalkInto !== undefined
1039
+ : explicitPrewalk
1006
1040
  ? true
1007
- : activeSettings.get("prewalk.enabled");
1041
+ : !restoringSession && activeSettings.get("prewalk.enabled");
1008
1042
  if (prewalkEnabled) {
1009
1043
  const rolePattern = expandRoleAlias(parsed.prewalkInto ?? DEFAULT_PREWALK_TARGET, activeSettings);
1010
1044
  const resolved = resolveCliModel({ cliModel: rolePattern, modelRegistry, preferences: modelMatchPreferences });
@@ -1114,14 +1148,34 @@ export async function buildSessionOptions(
1114
1148
  options.rules = [];
1115
1149
  }
1116
1150
 
1117
- // Additional extension paths from CLI
1118
- const cliExtensionPaths = [...(parsed.extensions ?? []), ...(parsed.hooks ?? [])];
1119
- if (cliExtensionPaths.length > 0) {
1120
- options.additionalExtensionPaths = cliExtensionPaths;
1121
- }
1122
-
1123
- if (parsed.noExtensions) {
1151
+ // Trusted extension paths are an exact allowlist for extension modules.
1152
+ if (parsed.trustedExtensions && parsed.trustedExtensions.length > 0) {
1153
+ const trustedPaths = parsed.trustedExtensions.map(trustedPath => {
1154
+ let resolvedPath: string;
1155
+ let stat: fsSync.Stats;
1156
+ try {
1157
+ resolvedPath = fsSync.realpathSync.native(trustedPath);
1158
+ stat = fsSync.statSync(resolvedPath);
1159
+ } catch {
1160
+ throw new Error(`Trusted extension must be an existing module file: ${trustedPath}`);
1161
+ }
1162
+ if (!stat.isFile()) {
1163
+ throw new Error(`Trusted extension must be a module file, not a directory: ${trustedPath}`);
1164
+ }
1165
+ return resolvedPath;
1166
+ });
1124
1167
  options.disableExtensionDiscovery = true;
1168
+ options.additionalExtensionPaths = trustedPaths;
1169
+ } else {
1170
+ // Additional extension paths from CLI
1171
+ const cliExtensionPaths = [...(parsed.extensions ?? []), ...(parsed.hooks ?? [])];
1172
+ if (cliExtensionPaths.length > 0) {
1173
+ options.additionalExtensionPaths = cliExtensionPaths;
1174
+ }
1175
+
1176
+ if (parsed.noExtensions) {
1177
+ options.disableExtensionDiscovery = true;
1178
+ }
1125
1179
  }
1126
1180
 
1127
1181
  return options;
@@ -1194,16 +1248,20 @@ export async function runRootCommand(
1194
1248
  // warning before we reach the await site below.
1195
1249
  pluginPreloadPromise.catch(() => {});
1196
1250
 
1197
- // Register CLI-provided extension package paths (`--extension`, `--hook`) so
1198
- // the `omp-plugins` discovery provider can surface their `skills/`, `hooks/`,
1199
- // `tools/`, `commands/`, `rules/`, `prompts/`, and `.mcp.json` sub-trees.
1200
- // Explicit roots remain authorized under `--no-extensions`; only ambient
1201
- // extension discovery is disabled.
1202
- const cliExtensions = [...(parsedArgs.extensions ?? []), ...(parsedArgs.hooks ?? [])];
1203
- injectOmpExtensionCliRoots(cliExtensions, home, getProjectDir(), {
1204
- mode: parsedArgs.noExtensions ? "explicit-only" : "merge",
1205
- replace: true,
1206
- });
1251
+ // Trusted files load as exact module paths, never as package roots whose
1252
+ // sibling hooks/tools/commands/MCP content could be discovered implicitly.
1253
+ if (!parsedArgs.trustedExtensions?.length) {
1254
+ // Register CLI-provided extension package paths (`--extension`, `--hook`) so
1255
+ // the `omp-plugins` discovery provider can surface their `skills/`, `hooks/`,
1256
+ // `tools/`, `commands/`, `rules/`, `prompts/`, and `.mcp.json` sub-trees.
1257
+ // Explicit roots remain authorized under `--no-extensions`; only ambient
1258
+ // extension discovery is disabled.
1259
+ const cliExtensions = [...(parsedArgs.extensions ?? []), ...(parsedArgs.hooks ?? [])];
1260
+ injectOmpExtensionCliRoots(cliExtensions, home, getProjectDir(), {
1261
+ mode: parsedArgs.noExtensions ? "explicit-only" : "merge",
1262
+ replace: true,
1263
+ });
1264
+ }
1207
1265
 
1208
1266
  let cwd = getProjectDir();
1209
1267
  // Classify the host before opening auth or settings storage so every
@@ -1542,12 +1600,14 @@ export async function runRootCommand(
1542
1600
  // string-flag value such as `--target @notes.md` is the flag's value, not a
1543
1601
  // file — and the same result is handed to createAgentSession via
1544
1602
  // `preloadedExtensions` so the discovery work is not repeated.
1545
- if (isInteractive) {
1603
+ if (isInteractive && !parsedArgs.trustedExtensions?.length) {
1546
1604
  sessionOptions.extensions = [...(sessionOptions.extensions ?? []), createWarpEventBridgeExtension()];
1547
1605
  }
1548
1606
 
1549
1607
  const eventBus = new EventBus();
1550
- const extensionsResult = await loadSessionExtensions(sessionOptions, cwd, settingsInstance, eventBus);
1608
+ const extensionsResult = parsedArgs.trustedExtensions?.length
1609
+ ? await loadTrustedSessionExtensions(sessionOptions, cwd, eventBus)
1610
+ : await loadSessionExtensions(sessionOptions, cwd, settingsInstance, eventBus);
1551
1611
  const extensionFlagSink: ExtensionFlagSink = {
1552
1612
  getFlags: () => ExtensionRunner.aggregateFlags(extensionsResult.extensions),
1553
1613
  setFlagValue: (name, value) => {
@@ -1556,6 +1616,11 @@ export async function runRootCommand(
1556
1616
  };
1557
1617
  const initialArgs = applyExtensionFlags(extensionFlagSink, rawArgs) ?? parsedArgs;
1558
1618
  normalizeContinueSessionArgs(initialArgs, rawArgs);
1619
+ if ((parsedArgs.trustedExtensions?.length ?? 0) > 0 && extensionsResult.errors.length > 0) {
1620
+ throw new Error(
1621
+ `Trusted extension failed to load: ${extensionsResult.errors.map(item => item.error).join("; ")}`,
1622
+ );
1623
+ }
1559
1624
  for (const message of formatExtensionLoadNotifications(extensionsResult.errors)) {
1560
1625
  if (isInteractive) {
1561
1626
  notifs.push({ kind: "warn", message });
@@ -1,6 +1,6 @@
1
1
  // Adapted from markit-ai (MIT). See ../NOTICE.
2
2
  import * as path from "node:path";
3
- import mammoth from "mammoth";
3
+ import mammoth from "@oh-my-pi/pi-utils/docx";
4
4
  import { createTurndown, normalizeTablesHtml } from "../../utils/turndown";
5
5
  import type { ConversionResult, Converter, StreamInfo } from "../types";
6
6
 
@@ -1,5 +1,5 @@
1
1
  // Adapted from markit-ai (MIT). See ../NOTICE.
2
- import { XMLParser } from "fast-xml-parser";
2
+ import { XMLParser } from "@oh-my-pi/pi-utils/xml";
3
3
  import { createTurndown, normalizeTablesHtml } from "../../utils/turndown";
4
4
  import { unzip, unzipText } from "../../utils/zip";
5
5
  import type { ConversionResult, Converter, StreamInfo } from "../types";
@@ -1,6 +1,6 @@
1
1
  // Adapted from markit-ai (MIT). See ../NOTICE.
2
2
  import * as path from "node:path";
3
- import { XMLParser } from "fast-xml-parser";
3
+ import { XMLParser } from "@oh-my-pi/pi-utils/xml";
4
4
  import { unzip, unzipText } from "../../utils/zip";
5
5
  import type { ConversionResult, Converter, StreamInfo } from "../types";
6
6
 
@@ -1,5 +1,5 @@
1
1
  // Adapted from markit-ai (MIT). See ../NOTICE.
2
- import { XMLParser } from "fast-xml-parser";
2
+ import { XMLParser } from "@oh-my-pi/pi-utils/xml";
3
3
  import { unzip, unzipText } from "../../utils/zip";
4
4
  import type { ConversionResult, Converter, StreamInfo } from "../types";
5
5
 
package/src/mcp/config.ts CHANGED
@@ -54,6 +54,7 @@ function convertToLegacyConfig(server: MCPServer): MCPServerConfig {
54
54
  };
55
55
  if (server.args) config.args = server.args;
56
56
  if (server.env) config.env = server.env;
57
+ if (server.envPolicy) config.envPolicy = server.envPolicy;
57
58
  if (server.cwd) config.cwd = server.cwd;
58
59
  return config;
59
60
  }
@@ -65,6 +66,7 @@ function convertToLegacyConfig(server: MCPServer): MCPServerConfig {
65
66
  url: server.url ?? "",
66
67
  };
67
68
  if (server.headers) config.headers = server.headers;
69
+ if (server.headerPolicy) config.headerPolicy = server.headerPolicy;
68
70
  return config;
69
71
  }
70
72
 
@@ -75,6 +77,7 @@ function convertToLegacyConfig(server: MCPServer): MCPServerConfig {
75
77
  url: server.url ?? "",
76
78
  };
77
79
  if (server.headers) config.headers = server.headers;
80
+ if (server.headerPolicy) config.headerPolicy = server.headerPolicy;
78
81
  return config;
79
82
  }
80
83
 
@@ -37,6 +37,7 @@ import type { McpConnectionStatusEvent } from "./startup-events";
37
37
  import type { MCPToolDetails } from "./tool-bridge";
38
38
  import { DeferredMCPTool, MCPTool } from "./tool-bridge";
39
39
  import type { MCPToolCache } from "./tool-cache";
40
+ import { setGeneratedHeader } from "./transports/header-policy";
40
41
  import type {
41
42
  MCPAuthChallenge,
42
43
  MCPGetPromptResult,
@@ -1458,13 +1459,11 @@ export class MCPManager {
1458
1459
 
1459
1460
  if (credential) {
1460
1461
  if (resolved.type === "http" || resolved.type === "sse") {
1461
- resolved = {
1462
- ...resolved,
1463
- headers: {
1464
- ...resolved.headers,
1465
- Authorization: `Bearer ${credential.access}`,
1466
- },
1467
- };
1462
+ // Client-generated authorization wins over any configured header
1463
+ // with the same case-insensitive name (Agent Plugins §7.2.1).
1464
+ const headers = { ...resolved.headers };
1465
+ setGeneratedHeader(headers, "Authorization", `Bearer ${credential.access}`);
1466
+ resolved = { ...resolved, headers };
1468
1467
  } else {
1469
1468
  resolved = {
1470
1469
  ...resolved,
@@ -1481,7 +1480,9 @@ export class MCPManager {
1481
1480
  }
1482
1481
 
1483
1482
  if (resolved.type !== "http" && resolved.type !== "sse") {
1484
- if (resolved.env) {
1483
+ // Literal env values (Agent Plugins §§4.1/9.2) are opaque package data:
1484
+ // no env-name lookup, no `!command` execution, no dropping empty values.
1485
+ if (resolved.env && resolved.envPolicy !== "literal") {
1485
1486
  const nextEnv: Record<string, string> = {};
1486
1487
  for (const [key, value] of Object.entries(resolved.env)) {
1487
1488
  const resolvedValue = await resolveConfigValue(value);
@@ -1490,7 +1491,9 @@ export class MCPManager {
1490
1491
  resolved = { ...resolved, env: nextEnv };
1491
1492
  }
1492
1493
  } else {
1493
- if (resolved.headers) {
1494
+ // Origin-locked servers (Agent Plugins §9.2) carry literal header
1495
+ // values: no placeholder or environment-variable expansion.
1496
+ if (resolved.headers && resolved.headerPolicy !== "origin-locked") {
1494
1497
  const nextHeaders: Record<string, string> = {};
1495
1498
  for (const [key, value] of Object.entries(resolved.headers)) {
1496
1499
  const resolvedValue = await resolveConfigValue(value);