@oh-my-pi/pi-coding-agent 17.2.0 → 17.2.1

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 (87) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/{CHANGELOG-be1f2t8h.md → CHANGELOG-dj46zzrm.md} +18 -0
  3. package/dist/cli.js +3340 -3195
  4. package/dist/types/config/model-discovery.d.ts +12 -0
  5. package/dist/types/config/settings-schema.d.ts +10 -0
  6. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +23 -0
  7. package/dist/types/internal-urls/index.d.ts +1 -0
  8. package/dist/types/internal-urls/security-protocol.d.ts +15 -0
  9. package/dist/types/internal-urls/types.d.ts +1 -1
  10. package/dist/types/sdk.d.ts +17 -0
  11. package/dist/types/security/auth.d.ts +30 -0
  12. package/dist/types/security/cloud.d.ts +79 -0
  13. package/dist/types/security/comparison.d.ts +49 -0
  14. package/dist/types/security/contracts/ids.d.ts +15 -0
  15. package/dist/types/security/contracts/index.d.ts +4 -0
  16. package/dist/types/security/contracts/schemas.d.ts +660 -0
  17. package/dist/types/security/contracts/types.d.ts +234 -0
  18. package/dist/types/security/contracts/validation.d.ts +5 -0
  19. package/dist/types/security/coordinator.d.ts +100 -0
  20. package/dist/types/security/importers/codex-security.d.ts +7 -0
  21. package/dist/types/security/importers/index.d.ts +2 -0
  22. package/dist/types/security/importers/sarif.d.ts +9 -0
  23. package/dist/types/security/index.d.ts +13 -0
  24. package/dist/types/security/preflight.d.ts +61 -0
  25. package/dist/types/security/provenance.d.ts +24 -0
  26. package/dist/types/security/publication.d.ts +78 -0
  27. package/dist/types/security/remediation.d.ts +27 -0
  28. package/dist/types/security/resource-output.d.ts +8 -0
  29. package/dist/types/security/sarif.d.ts +2 -0
  30. package/dist/types/security/store.d.ts +40 -0
  31. package/dist/types/slash-commands/helpers/security.d.ts +2 -0
  32. package/dist/types/system-prompt.d.ts +2 -0
  33. package/dist/types/task/executor.d.ts +3 -0
  34. package/dist/types/tools/builtin-names.d.ts +1 -1
  35. package/dist/types/tools/index.d.ts +6 -1
  36. package/dist/types/tools/security-scan.d.ts +96 -0
  37. package/package.json +12 -12
  38. package/scripts/security-compare.ts +40 -0
  39. package/src/config/model-discovery.ts +32 -5
  40. package/src/config/model-registry.ts +4 -0
  41. package/src/config/settings-schema.ts +12 -0
  42. package/src/eval/py/prelude.py +7 -3
  43. package/src/extensibility/legacy-pi-coding-agent-shim.ts +53 -0
  44. package/src/internal-urls/index.ts +1 -0
  45. package/src/internal-urls/router.ts +4 -1
  46. package/src/internal-urls/security-protocol.ts +261 -0
  47. package/src/internal-urls/types.ts +1 -1
  48. package/src/lsp/index.ts +3 -0
  49. package/src/modes/rpc/host-uris.ts +6 -0
  50. package/src/prompts/agents/security-reviewer.md +75 -0
  51. package/src/prompts/security/scan-coordinator.md +7 -0
  52. package/src/prompts/security/scan-request.md +21 -0
  53. package/src/prompts/security/validate-request.md +8 -0
  54. package/src/prompts/system/system-prompt.md +3 -0
  55. package/src/prompts/tools/security-publish.md +1 -0
  56. package/src/prompts/tools/security-scan.md +1 -0
  57. package/src/sdk.ts +34 -6
  58. package/src/security/auth.ts +98 -0
  59. package/src/security/cloud.ts +686 -0
  60. package/src/security/comparison.ts +255 -0
  61. package/src/security/contracts/ids.ts +111 -0
  62. package/src/security/contracts/index.ts +4 -0
  63. package/src/security/contracts/schemas.ts +201 -0
  64. package/src/security/contracts/types.ts +254 -0
  65. package/src/security/contracts/validation.ts +65 -0
  66. package/src/security/coordinator.ts +708 -0
  67. package/src/security/importers/codex-security.ts +387 -0
  68. package/src/security/importers/index.ts +2 -0
  69. package/src/security/importers/sarif.ts +357 -0
  70. package/src/security/index.ts +13 -0
  71. package/src/security/preflight.ts +405 -0
  72. package/src/security/provenance.ts +106 -0
  73. package/src/security/publication.ts +326 -0
  74. package/src/security/remediation.ts +93 -0
  75. package/src/security/resource-output.ts +50 -0
  76. package/src/security/sarif.ts +78 -0
  77. package/src/security/store.ts +430 -0
  78. package/src/slash-commands/builtin-registry.ts +21 -0
  79. package/src/slash-commands/helpers/security.ts +451 -0
  80. package/src/system-prompt.ts +4 -0
  81. package/src/task/agents.ts +2 -0
  82. package/src/task/executor.ts +3 -0
  83. package/src/task/structured-subagent.ts +6 -4
  84. package/src/tools/builtin-names.ts +1 -0
  85. package/src/tools/index.ts +9 -1
  86. package/src/tools/path-utils.ts +3 -0
  87. package/src/tools/security-scan.ts +287 -0
@@ -1,5 +1,5 @@
1
1
  import type { Clipboard, InMemorySnapshotStore } from "@oh-my-pi/hashline";
2
- import type { AgentTelemetryConfig, AgentTool } from "@oh-my-pi/pi-agent-core";
2
+ import type { AgentOptions, AgentTelemetryConfig, AgentTool } from "@oh-my-pi/pi-agent-core";
3
3
  import type { FetchImpl, ImageContent, Model, ServiceTierByFamily, ToolChoice } from "@oh-my-pi/pi-ai";
4
4
  import type { AsyncJobManager } from "../async/job-manager.js";
5
5
  import type { Rule } from "../capability/rule.js";
@@ -65,6 +65,7 @@ export * from "./read.js";
65
65
  export * from "./report-tool-issue.js";
66
66
  export * from "./resolve.js";
67
67
  export * from "./review.js";
68
+ export * from "./security-scan.js";
68
69
  export * from "./todo.js";
69
70
  export * from "./tts.js";
70
71
  export * from "./vibe.js";
@@ -123,6 +124,8 @@ export interface ToolSession {
123
124
  suppressSpawnAdvisory?: boolean;
124
125
  /** Optional fetch implementation injected into the URL read pipeline (tests, proxies). Defaults to global fetch. */
125
126
  fetch?: FetchImpl;
127
+ /** Provider credential resolver forwarded unchanged to restricted child sessions. */
128
+ getApiKey?: AgentOptions["getApiKey"];
126
129
  /** Skip subprocess-kernel availability checks and warmup */
127
130
  skipPythonPreflight?: boolean;
128
131
  /** Pre-loaded context files (AGENTS.md, etc) */
@@ -152,6 +155,8 @@ export interface ToolSession {
152
155
  customToolPaths?: ToolPathWithSource[];
153
156
  /** Whether LSP integrations are enabled */
154
157
  enableLsp?: boolean;
158
+ /** Whether LSP is limited to navigation and diagnostics. */
159
+ lspReadOnly?: boolean;
155
160
  /** Whether this invocation may expose IRC. `false` removes it even for subagents. */
156
161
  enableIrc?: boolean;
157
162
  /**
@@ -0,0 +1,96 @@
1
+ import type { AgentTool, AgentToolResult, ToolTier } from "@oh-my-pi/pi-agent-core";
2
+ import { type CodexSecurityCloudConfiguration, type CodexSecurityCloudStats } from "../security/cloud.js";
3
+ import { type SecurityValidationStatus } from "../security/contracts/index.js";
4
+ import type { SecurityOperationSnapshot } from "../security/coordinator.js";
5
+ import type { ToolSession } from "./index.js";
6
+ declare const securityScanSchema: import("arktype/internal/variants/object.ts").ObjectType<{
7
+ action: "cancel" | "cloud_pull" | "cloud_scans" | "cloud_start" | "cloud_status" | "preflight" | "start" | "status" | "validate";
8
+ plan_id?: string | undefined;
9
+ operation_id?: string | undefined;
10
+ target_kind?: "ref_diff" | "repository" | "scoped_path" | "working_tree" | undefined;
11
+ include_paths?: string[] | undefined;
12
+ exclude_paths?: string[] | undefined;
13
+ base_revision?: string | undefined;
14
+ head_revision?: string | undefined;
15
+ knowledge_base_paths?: string[] | undefined;
16
+ output_root?: string | undefined;
17
+ archive_existing?: boolean | undefined;
18
+ credential_id?: number | undefined;
19
+ scan_id?: string | undefined;
20
+ finding_id?: string | undefined;
21
+ validation_status?: "error" | "partial" | "rejected" | "unvalidated" | "validated" | undefined;
22
+ validation_summary?: string | undefined;
23
+ validation_evidence?: {
24
+ label: string;
25
+ explanation: string;
26
+ }[] | undefined;
27
+ cloud_configuration_id?: string | undefined;
28
+ repository_id?: string | undefined;
29
+ repository_url?: string | undefined;
30
+ environment_id?: string | undefined;
31
+ lookback_days?: number | "all" | undefined;
32
+ }, {}>;
33
+ type SecurityScanParams = typeof securityScanSchema.infer;
34
+ export interface SecurityScanToolDetails {
35
+ action: SecurityScanParams["action"];
36
+ plan?: {
37
+ id: string;
38
+ fingerprint: string;
39
+ };
40
+ operation?: SecurityOperationSnapshot;
41
+ cancelled?: boolean;
42
+ finding?: {
43
+ id: string;
44
+ validationStatus: SecurityValidationStatus;
45
+ };
46
+ cloudConfigurations?: CodexSecurityCloudConfiguration[];
47
+ cloudStats?: CodexSecurityCloudStats;
48
+ cloudScan?: {
49
+ id: string;
50
+ repositoryUrl: string;
51
+ };
52
+ importedScan?: {
53
+ id: string;
54
+ findingCount: number;
55
+ };
56
+ }
57
+ export declare class SecurityScanTool implements AgentTool<typeof securityScanSchema, SecurityScanToolDetails> {
58
+ readonly session: ToolSession;
59
+ readonly name = "security_scan";
60
+ readonly approval: ToolTier;
61
+ readonly label = "Security Scan";
62
+ readonly loadMode = "discoverable";
63
+ readonly summary = "Run OMP-native scans and explicit Codex Security cloud operations";
64
+ readonly description: string;
65
+ readonly parameters: import("arktype/internal/variants/object.ts").ObjectType<{
66
+ action: "cancel" | "cloud_pull" | "cloud_scans" | "cloud_start" | "cloud_status" | "preflight" | "start" | "status" | "validate";
67
+ plan_id?: string | undefined;
68
+ operation_id?: string | undefined;
69
+ target_kind?: "ref_diff" | "repository" | "scoped_path" | "working_tree" | undefined;
70
+ include_paths?: string[] | undefined;
71
+ exclude_paths?: string[] | undefined;
72
+ base_revision?: string | undefined;
73
+ head_revision?: string | undefined;
74
+ knowledge_base_paths?: string[] | undefined;
75
+ output_root?: string | undefined;
76
+ archive_existing?: boolean | undefined;
77
+ credential_id?: number | undefined;
78
+ scan_id?: string | undefined;
79
+ finding_id?: string | undefined;
80
+ validation_status?: "error" | "partial" | "rejected" | "unvalidated" | "validated" | undefined;
81
+ validation_summary?: string | undefined;
82
+ validation_evidence?: {
83
+ label: string;
84
+ explanation: string;
85
+ }[] | undefined;
86
+ cloud_configuration_id?: string | undefined;
87
+ repository_id?: string | undefined;
88
+ repository_url?: string | undefined;
89
+ environment_id?: string | undefined;
90
+ lookback_days?: number | "all" | undefined;
91
+ }, {}>;
92
+ readonly strict = true;
93
+ constructor(session: ToolSession);
94
+ execute(_toolCallId: string, params: SecurityScanParams, signal?: AbortSignal): Promise<AgentToolResult<SecurityScanToolDetails>>;
95
+ }
96
+ export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-coding-agent",
4
- "version": "17.2.0",
4
+ "version": "17.2.1",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -52,17 +52,17 @@
52
52
  "@agentclientprotocol/sdk": "1.2.1",
53
53
  "@babel/parser": "^7.29.7",
54
54
  "@mozilla/readability": "^0.6.0",
55
- "@oh-my-pi/hashline": "17.2.0",
56
- "@oh-my-pi/omp-stats": "17.2.0",
57
- "@oh-my-pi/pi-agent-core": "17.2.0",
58
- "@oh-my-pi/pi-ai": "17.2.0",
59
- "@oh-my-pi/pi-catalog": "17.2.0",
60
- "@oh-my-pi/pi-mnemopi": "17.2.0",
61
- "@oh-my-pi/pi-natives": "17.2.0",
62
- "@oh-my-pi/pi-tui": "17.2.0",
63
- "@oh-my-pi/pi-utils": "17.2.0",
64
- "@oh-my-pi/pi-wire": "17.2.0",
65
- "@oh-my-pi/snapcompact": "17.2.0",
55
+ "@oh-my-pi/hashline": "17.2.1",
56
+ "@oh-my-pi/omp-stats": "17.2.1",
57
+ "@oh-my-pi/pi-agent-core": "17.2.1",
58
+ "@oh-my-pi/pi-ai": "17.2.1",
59
+ "@oh-my-pi/pi-catalog": "17.2.1",
60
+ "@oh-my-pi/pi-mnemopi": "17.2.1",
61
+ "@oh-my-pi/pi-natives": "17.2.1",
62
+ "@oh-my-pi/pi-tui": "17.2.1",
63
+ "@oh-my-pi/pi-utils": "17.2.1",
64
+ "@oh-my-pi/pi-wire": "17.2.1",
65
+ "@oh-my-pi/snapcompact": "17.2.1",
66
66
  "@opentelemetry/api": "^1.9.1",
67
67
  "@opentelemetry/api-logs": "^0.220.0",
68
68
  "@opentelemetry/context-async-hooks": "^2.9.0",
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env bun
2
+ import * as path from "node:path";
3
+ import { isEnoent } from "@oh-my-pi/pi-utils";
4
+ import { compareSecurityProducers, importCodexSecurityBundle, parseSecurityScanBundle } from "../src/security";
5
+
6
+ async function readBundle(directory: string) {
7
+ const root = path.resolve(directory);
8
+ let scan: unknown;
9
+ try {
10
+ scan = JSON.parse(await Bun.file(path.join(root, "scan.json")).text()) as unknown;
11
+ } catch (error) {
12
+ if (!isEnoent(error)) throw error;
13
+ return importCodexSecurityBundle(root, { repositoryRoot: root });
14
+ }
15
+ const findings = JSON.parse(await Bun.file(path.join(root, "findings.json")).text()) as unknown;
16
+ const report = await Bun.file(path.join(root, "report.md"))
17
+ .text()
18
+ .catch(() => undefined);
19
+ const sarifText = await Bun.file(path.join(root, "results.sarif"))
20
+ .text()
21
+ .catch(() => undefined);
22
+ return parseSecurityScanBundle({
23
+ scan,
24
+ findings,
25
+ report,
26
+ sarif: sarifText ? (JSON.parse(sarifText) as Record<string, unknown>) : undefined,
27
+ });
28
+ }
29
+
30
+ const [referenceDirectory, candidateDirectory, outputPath] = process.argv.slice(2);
31
+ if (!referenceDirectory || !candidateDirectory) {
32
+ process.stderr.write(
33
+ "Usage: bun scripts/security-compare.ts <reference-scan-dir> <candidate-scan-dir> [output.json]\n",
34
+ );
35
+ process.exit(2);
36
+ }
37
+ const report = compareSecurityProducers(await readBundle(referenceDirectory), await readBundle(candidateDirectory));
38
+ const serialized = `${JSON.stringify(report, null, 2)}\n`;
39
+ if (outputPath) await Bun.write(path.resolve(outputPath), serialized);
40
+ else process.stdout.write(serialized);
@@ -62,6 +62,33 @@ async function withTimeoutSignal<T>(timeoutMs: number, fn: (signal: AbortSignal)
62
62
  }
63
63
  }
64
64
 
65
+ /** Generous discovery budget for a non-loopback (remote / LAN) inference host. */
66
+ const REMOTE_DISCOVERY_TIMEOUT_MS = 10_000;
67
+
68
+ /**
69
+ * Pick a discovery-probe timeout for a local-engine base URL.
70
+ *
71
+ * The implicit `127.0.0.1` default probe keeps a tight `loopbackMs` cap so a
72
+ * busy or foreign service on the default port never stalls startup. But that
73
+ * cap is far too short for a host reached over the network: a user who points
74
+ * `LLAMA_CPP_BASE_URL` / `OLLAMA_BASE_URL` / `OLLAMA_HOST` at a remote or LAN
75
+ * machine has real round-trip latency, and a 250ms cap made that server look
76
+ * empty (issue #7087). Anything that is not strictly loopback therefore gets
77
+ * {@link REMOTE_DISCOVERY_TIMEOUT_MS}.
78
+ */
79
+ export function discoveryProbeTimeoutMs(baseUrl: string, loopbackMs: number): number {
80
+ let hostname: string;
81
+ try {
82
+ hostname = new URL(baseUrl).hostname;
83
+ } catch {
84
+ return loopbackMs;
85
+ }
86
+ hostname = hostname.replace(/^\[/, "").replace(/\]$/, "");
87
+ const isLoopback =
88
+ hostname === "localhost" || hostname === "0.0.0.0" || hostname === "::1" || /^127\./.test(hostname);
89
+ return isLoopback ? loopbackMs : REMOTE_DISCOVERY_TIMEOUT_MS;
90
+ }
91
+
65
92
  const DEFAULT_OLLAMA_BASE_URL = "http://127.0.0.1:11434";
66
93
  const OLLAMA_HOST_DEFAULT_PORT = "11434";
67
94
 
@@ -391,7 +418,7 @@ async function discoverOllamaModelMetadata(
391
418
  ): Promise<OllamaDiscoveredModelMetadata | null> {
392
419
  const showUrl = `${endpoint}/api/show`;
393
420
  try {
394
- const payload = await withTimeoutSignal(150, async signal => {
421
+ const payload = await withTimeoutSignal(discoveryProbeTimeoutMs(endpoint, 150), async signal => {
395
422
  const response = await ctx.fetch(showUrl, {
396
423
  method: "POST",
397
424
  headers: { ...(headers ?? {}), "Content-Type": "application/json" },
@@ -444,7 +471,7 @@ export async function discoverOllamaModels(
444
471
  const endpoint = normalizeOllamaBaseUrl(providerConfig.baseUrl);
445
472
  const tagsUrl = `${endpoint}/api/tags`;
446
473
  const headers = { ...(providerConfig.headers ?? {}) };
447
- const payload = await withTimeoutSignal(250, async signal => {
474
+ const payload = await withTimeoutSignal(discoveryProbeTimeoutMs(endpoint, 250), async signal => {
448
475
  const response = await ctx.fetch(tagsUrl, {
449
476
  headers,
450
477
  signal,
@@ -491,7 +518,7 @@ async function discoverLlamaCppServerMetadata(
491
518
  ): Promise<LlamaCppDiscoveredServerMetadata | null> {
492
519
  const propsUrl = `${toLlamaCppNativeBaseUrl(baseUrl)}/props`;
493
520
  try {
494
- const payload = await withTimeoutSignal(150, async signal => {
521
+ const payload = await withTimeoutSignal(discoveryProbeTimeoutMs(baseUrl, 150), async signal => {
495
522
  const response = await ctx.fetch(propsUrl, {
496
523
  headers,
497
524
  signal,
@@ -567,7 +594,7 @@ export async function discoverLlamaCppModels(
567
594
  let headers = baseHeaders;
568
595
  const attempt = async (h: Record<string, string>) => {
569
596
  const [payload, metadata] = await Promise.all([
570
- withTimeoutSignal(250, async signal => {
597
+ withTimeoutSignal(discoveryProbeTimeoutMs(baseUrl, 250), async signal => {
571
598
  const response = await ctx.fetch(modelsUrl, {
572
599
  headers: h,
573
600
  signal,
@@ -641,7 +668,7 @@ export async function discoverLlamaCppModelRuntimeMetadata(
641
668
  const baseHeaders: Record<string, string> = { ...(model.headers ?? {}) };
642
669
  const attempt = async (headers: Record<string, string>) => {
643
670
  const [entries, serverMetadata] = await Promise.all([
644
- withTimeoutSignal(250, async signal => {
671
+ withTimeoutSignal(discoveryProbeTimeoutMs(nativeBaseUrl, 250), async signal => {
645
672
  const response = await ctx.fetch(modelsUrl, {
646
673
  headers,
647
674
  signal,
@@ -27,6 +27,7 @@ import {
27
27
  openaiCodexModelManagerOptions,
28
28
  PROVIDER_DESCRIPTORS,
29
29
  resolveModelCacheProviderId,
30
+ resolveOllamaModelCacheProviderId,
30
31
  } from "@oh-my-pi/pi-catalog/provider-models";
31
32
  import {
32
33
  collapseBuiltModelVariants,
@@ -1699,6 +1700,9 @@ export class ModelRegistry {
1699
1700
  }
1700
1701
 
1701
1702
  #configuredDiscoveryCacheProviderId(providerConfig: DiscoveryProviderConfig): string {
1703
+ if (providerConfig.discovery.type === "ollama") {
1704
+ return resolveOllamaModelCacheProviderId(providerConfig.provider, providerConfig.baseUrl);
1705
+ }
1702
1706
  if (providerConfig.discovery.type === "openai-models-list") {
1703
1707
  return `${providerConfig.provider}:openai-models-list-context-v2`;
1704
1708
  }
@@ -4062,6 +4062,18 @@ export const SETTINGS_SCHEMA = {
4062
4062
  },
4063
4063
  },
4064
4064
 
4065
+ "security.enabled": {
4066
+ type: "boolean",
4067
+ default: false,
4068
+ ui: {
4069
+ tab: "tools",
4070
+ group: "Available Tools",
4071
+ label: "Security",
4072
+ description:
4073
+ "Enable OMP-native security scan planning, execution, and the read-only security:// resource namespace",
4074
+ },
4075
+ },
4076
+
4065
4077
  "ask.enabled": {
4066
4078
  type: "boolean",
4067
4079
  default: true,
@@ -376,10 +376,14 @@ if "__omp_prelude_loaded__" not in globals():
376
376
  raise RuntimeError("tool bridge is unavailable in this kernel")
377
377
  return (base.rstrip("/"), token, session)
378
378
 
379
+ import urllib.error, urllib.request
380
+
381
+ # urllib discovers environment and macOS SystemConfiguration proxies. This
382
+ # host-owned loopback endpoint must always connect directly.
383
+ _BRIDGE_OPENER = urllib.request.build_opener(urllib.request.ProxyHandler({}))
384
+
379
385
  def _bridge_call(name: str, args: dict):
380
386
  """POST one request to the host tool bridge and return its `value`."""
381
- import urllib.request, urllib.error
382
-
383
387
  base, token, session = _tool_proxy_from_env()
384
388
  _run_id_getter = globals().get("__omp_current_run_id__")
385
389
  _run_id = (
@@ -400,7 +404,7 @@ if "__omp_prelude_loaded__" not in globals():
400
404
  },
401
405
  )
402
406
  try:
403
- with urllib.request.urlopen(req) as resp:
407
+ with _BRIDGE_OPENER.open(req) as resp:
404
408
  body = resp.read()
405
409
  except urllib.error.HTTPError as exc:
406
410
  body = exc.read()
@@ -140,6 +140,25 @@ export interface LsToolOptions {
140
140
  operations?: LsOperations;
141
141
  }
142
142
 
143
+ export interface EditOperations {
144
+ readFile: (absolutePath: string) => Promise<Buffer>;
145
+ writeFile: (absolutePath: string, content: string) => Promise<void>;
146
+ access: (absolutePath: string) => Promise<void>;
147
+ }
148
+
149
+ export interface EditToolOptions {
150
+ operations?: EditOperations;
151
+ }
152
+
153
+ export interface WriteOperations {
154
+ writeFile: (absolutePath: string, content: string) => Promise<void>;
155
+ mkdir: (dir: string) => Promise<void>;
156
+ }
157
+
158
+ export interface WriteToolOptions {
159
+ operations?: WriteOperations;
160
+ }
161
+
143
162
  const legacyBashSchema = Type.Object({
144
163
  command: Type.String({ description: "Bash command to execute" }),
145
164
  timeout: Type.Optional(Type.Number({ description: "Timeout in seconds" })),
@@ -636,6 +655,40 @@ export function createLsTool(cwd: string, options?: LsToolOptions): ToolDefiniti
636
655
  return createLsToolDefinition(cwd, options);
637
656
  }
638
657
 
658
+ /** Create the legacy edit tool definition. */
659
+ export function createEditToolDefinition(cwd: string, options?: EditToolOptions): ToolDefinition {
660
+ if (options?.operations) {
661
+ throw new Error(
662
+ "Legacy EditToolOptions.operations is not supported: OMP's built-in edit tool writes the local " +
663
+ "filesystem natively and exposes no pluggable operations seam. Register a custom edit tool via " +
664
+ "defineTool() instead of passing operations to createEditTool()/createEditToolDefinition().",
665
+ );
666
+ }
667
+ return legacyBuiltinTool(cwd, "edit");
668
+ }
669
+
670
+ /** Create the legacy edit tool. */
671
+ export function createEditTool(cwd: string, options?: EditToolOptions): ToolDefinition {
672
+ return createEditToolDefinition(cwd, options);
673
+ }
674
+
675
+ /** Create the legacy write tool definition. */
676
+ export function createWriteToolDefinition(cwd: string, options?: WriteToolOptions): ToolDefinition {
677
+ if (options?.operations) {
678
+ throw new Error(
679
+ "Legacy WriteToolOptions.operations is not supported: OMP's built-in write tool writes the local " +
680
+ "filesystem natively and exposes no pluggable operations seam. Register a custom write tool via " +
681
+ "defineTool() instead of passing operations to createWriteTool()/createWriteToolDefinition().",
682
+ );
683
+ }
684
+ return legacyBuiltinTool(cwd, "write");
685
+ }
686
+
687
+ /** Create the legacy write tool. */
688
+ export function createWriteTool(cwd: string, options?: WriteToolOptions): ToolDefinition {
689
+ return createWriteToolDefinition(cwd, options);
690
+ }
691
+
639
692
  /** Create legacy read, bash, edit, and write tools. */
640
693
  export function createCodingTools(cwd: string): ToolDefinition[] {
641
694
  return LEGACY_CODING_TOOL_NAMES.map(name => legacyBuiltinTool(cwd, name));
@@ -20,6 +20,7 @@ export * from "./omp-protocol";
20
20
  export * from "./parse";
21
21
  export * from "./router";
22
22
  export * from "./rule-protocol";
23
+ export * from "./security-protocol";
23
24
  export * from "./skill-protocol";
24
25
  export * from "./ssh-protocol";
25
26
  export type * from "./types";
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Internal URL router for internal protocols (`agent://`, `artifact://`, `history://`, `issue://`, `local://`, `mcp://`, `memory://`, `omp://`, `pr://`, `rule://`, `skill://`, `ssh://`, `vault://`, and `xd://`).
2
+ * Internal URL router for internal protocols (`agent://`, `artifact://`, `history://`, `issue://`, `local://`, `mcp://`, `memory://`, `omp://`, `pr://`, `rule://`, `security://`, `skill://`, `ssh://`, `vault://`, and `xd://`).
3
3
  *
4
4
  * One process-global router with one handler per scheme. Access via
5
5
  * `InternalUrlRouter.instance()`. Handlers are stateless; per-session and
@@ -15,6 +15,7 @@ import { MemoryProtocolHandler } from "./memory-protocol";
15
15
  import { OmpProtocolHandler } from "./omp-protocol";
16
16
  import { extractUriScheme, parseInternalUrl } from "./parse";
17
17
  import { RuleProtocolHandler } from "./rule-protocol";
18
+ import { SecurityProtocolHandler } from "./security-protocol";
18
19
  import { SkillProtocolHandler } from "./skill-protocol";
19
20
  import { SshProtocolHandler } from "./ssh-protocol";
20
21
  import type {
@@ -42,6 +43,8 @@ export class InternalUrlRouter {
42
43
  this.register(new VaultProtocolHandler());
43
44
  this.register(new SkillProtocolHandler());
44
45
  this.register(new RuleProtocolHandler());
46
+ // Reserved OMP-owned security-analysis namespace; vendor adapters normalize into its store.
47
+ this.register(new SecurityProtocolHandler());
45
48
  this.register(new McpProtocolHandler());
46
49
  this.register(new IssueProtocolHandler());
47
50
  this.register(new PrProtocolHandler());