oh-my-openagent 5.1.3 → 5.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/dist/cli/index.js +599 -191
  2. package/dist/cli-node/index.js +599 -191
  3. package/dist/config/prune-plugin-view.d.ts +18 -0
  4. package/dist/config/validate.d.ts +3 -1
  5. package/dist/index.js +12044 -11657
  6. package/dist/plugin-config/omo-config-chain.d.ts +4 -6
  7. package/dist/skills/browser/runtime/omowright/manifest.json +1 -1
  8. package/dist/tui.js +683 -280
  9. package/package.json +14 -14
  10. package/packages/lsp-core/src/lsp/outside-context-workspace.ts +1 -1
  11. package/packages/lsp-core/src/missing-dependency-result.test.ts +32 -2
  12. package/packages/lsp-core/src/missing-dependency-result.ts +3 -0
  13. package/packages/lsp-core/src/post-edit/file-location.ts +50 -0
  14. package/packages/lsp-core/src/post-edit/index.ts +2 -0
  15. package/packages/lsp-core/src/post-edit/not-installed-guidance.ts +36 -0
  16. package/packages/lsp-core/src/post-edit/not-installed.test.ts +199 -0
  17. package/packages/lsp-core/src/post-edit/orchestration.ts +26 -5
  18. package/packages/lsp-daemon/dist/cli.js +2 -1
  19. package/packages/lsp-daemon/dist/client.js +2 -1
  20. package/packages/lsp-daemon/dist/index.js +2 -1
  21. package/packages/lsp-tools-mcp/dist/cli.js +2 -1
  22. package/packages/lsp-tools-mcp/dist/mcp.js +2 -1
  23. package/packages/lsp-tools-mcp/dist/tools.js +2 -1
  24. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  25. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +309 -133
  26. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  27. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  28. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  29. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  30. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  31. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  32. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  33. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  34. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +4 -4
  35. package/packages/omo-codex/plugin/components/lsp/dist/cli.js +141 -107
  36. package/packages/omo-codex/plugin/components/lsp/dist/lsp-session-state.js +2 -2
  37. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  38. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  39. package/packages/omo-codex/plugin/components/lsp/src/lsp-session-state.ts +2 -2
  40. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  41. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  42. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  43. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  44. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  45. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  46. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  47. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  48. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
  49. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  50. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
  51. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  52. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  53. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  54. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  55. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  56. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  57. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  58. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  59. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
  60. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  61. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  62. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  63. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  64. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  65. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  66. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  67. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  68. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  69. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  70. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  71. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  72. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  73. package/packages/omo-codex/plugin/package-lock.json +12 -12
  74. package/packages/omo-codex/plugin/package.json +1 -1
  75. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
  76. package/packages/omo-codex/scripts/install-dist/install-local.mjs +311 -135
  77. package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
package/dist/cli/index.js CHANGED
@@ -64,7 +64,7 @@ var package_default;
64
64
  var init_package = __esm(() => {
65
65
  package_default = {
66
66
  name: "oh-my-opencode",
67
- version: "5.1.3",
67
+ version: "5.1.5",
68
68
  description: "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
69
69
  main: "./dist/index.js",
70
70
  types: "dist/index.d.ts",
@@ -241,7 +241,7 @@ var init_package = __esm(() => {
241
241
  },
242
242
  devDependencies: {
243
243
  "@clack/prompts": "^1.8.1",
244
- "@code-yeongyu/senpi": "2026.9.29-4",
244
+ "@code-yeongyu/senpi": "2026.9.29-5",
245
245
  "@modelcontextprotocol/sdk": "^1.30.0",
246
246
  "@oh-my-opencode/agents-md-core": "workspace:*",
247
247
  "@oh-my-opencode/ast-grep-mcp": "workspace:*",
@@ -299,18 +299,18 @@ var init_package = __esm(() => {
299
299
  typescript: "^7.0.2"
300
300
  },
301
301
  optionalDependencies: {
302
- "oh-my-opencode-darwin-arm64": "5.1.3",
303
- "oh-my-opencode-darwin-x64": "5.1.3",
304
- "oh-my-opencode-darwin-x64-baseline": "5.1.3",
305
- "oh-my-opencode-linux-arm64": "5.1.3",
306
- "oh-my-opencode-linux-arm64-musl": "5.1.3",
307
- "oh-my-opencode-linux-x64": "5.1.3",
308
- "oh-my-opencode-linux-x64-baseline": "5.1.3",
309
- "oh-my-opencode-linux-x64-musl": "5.1.3",
310
- "oh-my-opencode-linux-x64-musl-baseline": "5.1.3",
311
- "oh-my-opencode-windows-arm64": "5.1.3",
312
- "oh-my-opencode-windows-x64": "5.1.3",
313
- "oh-my-opencode-windows-x64-baseline": "5.1.3"
302
+ "oh-my-opencode-darwin-arm64": "5.1.5",
303
+ "oh-my-opencode-darwin-x64": "5.1.5",
304
+ "oh-my-opencode-darwin-x64-baseline": "5.1.5",
305
+ "oh-my-opencode-linux-arm64": "5.1.5",
306
+ "oh-my-opencode-linux-arm64-musl": "5.1.5",
307
+ "oh-my-opencode-linux-x64": "5.1.5",
308
+ "oh-my-opencode-linux-x64-baseline": "5.1.5",
309
+ "oh-my-opencode-linux-x64-musl": "5.1.5",
310
+ "oh-my-opencode-linux-x64-musl-baseline": "5.1.5",
311
+ "oh-my-opencode-windows-arm64": "5.1.5",
312
+ "oh-my-opencode-windows-x64": "5.1.5",
313
+ "oh-my-opencode-windows-x64-baseline": "5.1.5"
314
314
  },
315
315
  overrides: {
316
316
  hono: "^4.13.8",
@@ -7392,6 +7392,8 @@ var init_category_model_requirements = __esm(() => {
7392
7392
  },
7393
7393
  "deep-low": {
7394
7394
  fallbackChain: [
7395
+ { providers: ["openai", "chatgpt-subscription"], model: "gpt-6.1-sol", variant: "medium" },
7396
+ { providers: ["openai", "chatgpt-subscription"], model: "gpt-6.1-sol-fast", variant: "medium" },
7395
7397
  {
7396
7398
  providers: ["openai", "chatgpt-subscription", "github-copilot", "opencode"],
7397
7399
  model: "gpt-5.6-sol",
@@ -7444,7 +7446,9 @@ var init_category_model_requirements = __esm(() => {
7444
7446
  providers: ["anthropic", "anthropic-api", "github-copilot"],
7445
7447
  model: "claude-haiku-4-5",
7446
7448
  variant: "off"
7447
- }
7449
+ },
7450
+ { providers: ["zai-coding-plan"], model: "glm-5.3-flash", variant: "low" },
7451
+ { providers: ["xiaomi"], model: "mimo-v2.6-flash", variant: "low" }
7448
7452
  ]
7449
7453
  },
7450
7454
  "unspecified-low": {
@@ -7609,7 +7613,7 @@ var init_model_capability_aliases = __esm(() => {
7609
7613
  description: "Normalizes OpenCode's OpenAI GPT fast service-tier IDs to canonical snapshot IDs.",
7610
7614
  providerIDs: ["openai"],
7611
7615
  allowedSubproviderHosts: ["vercel"],
7612
- match: (normalizedModelID) => /^(?:gpt-5\.6-(?:sol|terra|luna)|gpt-6-(?:astra|sol|luna))-fast$/.test(normalizedModelID),
7616
+ match: (normalizedModelID) => /^(?:gpt-5\.6-(?:sol|terra|luna)|gpt-6-(?:astra|sol|luna)|gpt-6\.1-sol)-fast$/.test(normalizedModelID),
7613
7617
  canonicalize: (normalizedModelID) => normalizedModelID.slice(0, -"-fast".length)
7614
7618
  },
7615
7619
  {
@@ -7757,6 +7761,14 @@ var init_model_capability_heuristics = __esm(() => {
7757
7761
  reasoningEffortAliases: { none: "low", minimal: "low" },
7758
7762
  supportsTemperature: false
7759
7763
  },
7764
+ {
7765
+ family: "gpt-6.1-sol",
7766
+ pattern: /gpt-6-1-sol/,
7767
+ variants: ["low", "medium", "high", "xhigh", "max"],
7768
+ reasoningEfforts: ["low", "medium", "high", "xhigh", "max"],
7769
+ reasoningEffortAliases: { none: "low", minimal: "low" },
7770
+ supportsTemperature: false
7771
+ },
7760
7772
  {
7761
7773
  family: "gpt-6",
7762
7774
  includes: ["gpt-6"],
@@ -8188,6 +8200,36 @@ var init_supplemental_entries = __esm(() => {
8188
8200
  output: 128000
8189
8201
  }
8190
8202
  },
8203
+ "gpt-6.1-sol": {
8204
+ id: "gpt-6.1-sol",
8205
+ family: "gpt",
8206
+ reasoning: true,
8207
+ temperature: false,
8208
+ toolCall: true,
8209
+ modalities: {
8210
+ input: ["text", "image"],
8211
+ output: ["text"]
8212
+ },
8213
+ limit: {
8214
+ context: 400000,
8215
+ output: 128000
8216
+ }
8217
+ },
8218
+ "gpt-6.1-sol-fast": {
8219
+ id: "gpt-6.1-sol-fast",
8220
+ family: "gpt",
8221
+ reasoning: true,
8222
+ temperature: false,
8223
+ toolCall: true,
8224
+ modalities: {
8225
+ input: ["text", "image"],
8226
+ output: ["text"]
8227
+ },
8228
+ limit: {
8229
+ context: 400000,
8230
+ output: 128000
8231
+ }
8232
+ },
8191
8233
  "gpt-6-luna": {
8192
8234
  id: "gpt-6-luna",
8193
8235
  family: "gpt-nano",
@@ -9454,7 +9496,7 @@ You are working on tasks that don't fit specific categories but require substant
9454
9496
  </Category_Context>`, UNSPECIFIED_HIGH_CATEGORY_CALLER_GUIDANCE = `<Selection_Gate>Use only when no specialist category fits and substantial effort spans systems/modules with broad impact. Use unspecified-low for contained moderate work.</Selection_Gate>`, DEEP_LOW_CATEGORY_CALLER_GUIDANCE = `<Selection_Gate>Route here when one subsystem plus its callers holds the mechanism and the evidence, once read, leaves one right answer. Wide but mechanical work belongs here or in a quick batch. When unsure, choose deep-low: a misrouted child returns \`ESCALATE: deep-high\` after one cheap attempt; re-spawn the same brief as deep-high with its findings.</Selection_Gate>`, DEEP_HIGH_CATEGORY_CALLER_GUIDANCE = `<Selection_Gate>Route here only when you can name the decision evidence cannot settle: a trade-off with no single right answer, a contract change crossing a package or process boundary, a mechanism with no in-repo pattern to copy, or correctness argued from invariants rather than observed in a test. Wide scope with easy decisions is deep-low or unspecified-high; reasoning as the deliverable is ultrabrain.</Selection_Gate>`, DEEP_LOW_GATE_MODELS, DEEP_HIGH_GATE_MODEL = "gpt-6-astra", OPENAI_CATEGORIES;
9455
9497
  var init_openai_categories = __esm(() => {
9456
9498
  init_types();
9457
- DEEP_LOW_GATE_MODELS = ["gpt-5.6-sol-fast", "gpt-5.6-sol"];
9499
+ DEEP_LOW_GATE_MODELS = ["gpt-6.1-sol", "gpt-6.1-sol-fast", "gpt-5.6-sol-fast", "gpt-5.6-sol"];
9458
9500
  OPENAI_CATEGORIES = [
9459
9501
  {
9460
9502
  name: "ultrabrain",
@@ -9465,7 +9507,7 @@ var init_openai_categories = __esm(() => {
9465
9507
  },
9466
9508
  {
9467
9509
  name: "deep-low",
9468
- config: { model: "openai/gpt-5.6-sol", variant: "medium" },
9510
+ config: { model: "openai/gpt-6.1-sol", variant: "medium" },
9469
9511
  description: "Default deep lane: one goal, one deliverable, decisions the child can settle from what it reads. **3D graphics, computer/browser use, CAPTCHA, multimodal, backend, logic, and algorithm work is routed here.** Multiple goals fan out as parallel calls.",
9470
9512
  callerGuidance: DEEP_LOW_CATEGORY_CALLER_GUIDANCE,
9471
9513
  promptAppend: DEEP_LOW_CATEGORY_PROMPT_APPEND,
@@ -64779,6 +64821,94 @@ var init_model_capabilities_generated = __esm(() => {
64779
64821
  context: 1e6,
64780
64822
  output: 128000
64781
64823
  }
64824
+ },
64825
+ "glm-5.3-flash": {
64826
+ id: "glm-5.3-flash",
64827
+ family: "glm-flash",
64828
+ reasoning: true,
64829
+ temperature: true,
64830
+ toolCall: true,
64831
+ modalities: {
64832
+ input: [
64833
+ "text",
64834
+ "image",
64835
+ "video"
64836
+ ],
64837
+ output: [
64838
+ "text"
64839
+ ]
64840
+ },
64841
+ limit: {
64842
+ context: 1048576,
64843
+ output: 1048576
64844
+ }
64845
+ },
64846
+ "zai/glm-5.3-flash": {
64847
+ id: "zai/glm-5.3-flash",
64848
+ family: "glm-flash",
64849
+ reasoning: true,
64850
+ temperature: true,
64851
+ toolCall: true,
64852
+ modalities: {
64853
+ input: [
64854
+ "text",
64855
+ "image",
64856
+ "video",
64857
+ "pdf"
64858
+ ],
64859
+ output: [
64860
+ "text"
64861
+ ]
64862
+ },
64863
+ limit: {
64864
+ context: 1e6,
64865
+ output: 131072
64866
+ }
64867
+ },
64868
+ "mimo-v2.6-flash": {
64869
+ id: "mimo-v2.6-flash",
64870
+ family: "mimo",
64871
+ reasoning: true,
64872
+ temperature: true,
64873
+ toolCall: true,
64874
+ modalities: {
64875
+ input: [
64876
+ "text",
64877
+ "image",
64878
+ "audio",
64879
+ "video"
64880
+ ],
64881
+ output: [
64882
+ "text"
64883
+ ]
64884
+ },
64885
+ limit: {
64886
+ context: 1048576,
64887
+ output: 131072
64888
+ }
64889
+ },
64890
+ "xiaomi/mimo-v2.6-flash": {
64891
+ id: "xiaomi/mimo-v2.6-flash",
64892
+ family: "mimo",
64893
+ reasoning: true,
64894
+ temperature: true,
64895
+ toolCall: true,
64896
+ modalities: {
64897
+ input: [
64898
+ "text",
64899
+ "image",
64900
+ "audio",
64901
+ "video"
64902
+ ],
64903
+ output: [
64904
+ "text"
64905
+ ]
64906
+ },
64907
+ limit: {
64908
+ context: 1048576,
64909
+ input: 1048576,
64910
+ output: 131072
64911
+ }
64782
64912
  }
64783
64913
  }
64784
64914
  };
@@ -74249,7 +74379,6 @@ function normalizeLegacyModelFields(entry) {
74249
74379
  delete normalized["reasoningEffort"];
74250
74380
  delete normalized["thinking"];
74251
74381
  delete normalized["textVerbosity"];
74252
- delete normalized["maxTokens"];
74253
74382
  delete normalized["providerOptions"];
74254
74383
  if (typeof entry["model"] === "string")
74255
74384
  normalized["model"] = canonicalModelString(entry["model"]);
@@ -74267,10 +74396,15 @@ function normalizeLegacyModelFields(entry) {
74267
74396
  providerOptions["textVerbosity"] = entry["textVerbosity"];
74268
74397
  if (Object.keys(providerOptions).length > 0)
74269
74398
  normalized["provider_options"] = providerOptions;
74270
- if (entry["max_tokens"] !== undefined)
74399
+ if (entry["max_tokens"] !== undefined) {
74271
74400
  normalized["max_tokens"] = entry["max_tokens"];
74272
- else if (entry["maxTokens"] !== undefined)
74401
+ if (entry["maxTokens"] === undefined || typeof entry["maxTokens"] === "number") {
74402
+ delete normalized["maxTokens"];
74403
+ }
74404
+ } else if (typeof entry["maxTokens"] === "number") {
74273
74405
  normalized["max_tokens"] = entry["maxTokens"];
74406
+ delete normalized["maxTokens"];
74407
+ }
74274
74408
  return normalized;
74275
74409
  }
74276
74410
  function normalizeLegacyModelEntry(entry) {
@@ -74313,12 +74447,25 @@ var init_fallback_models = __esm(() => {
74313
74447
  function isRecord5(value) {
74314
74448
  return typeof value === "object" && value !== null && !Array.isArray(value);
74315
74449
  }
74316
- var OmoAgentModelEntrySchema, OmoAgentDefInputSchema, OmoAgentDefSchema, OmoAgentsConfigSchema;
74450
+ var OmoAgentModelEntrySchema, PermissionValueSchema, BashPermissionSchema, OmoAgentPermissionSchema, OmoAgentDefInputSchema, OmoAgentDefSchema, OmoAgentsConfigSchema;
74317
74451
  var init_agent = __esm(() => {
74318
74452
  init_zod();
74319
74453
  init_fallback_models();
74320
74454
  init_model_ref();
74321
74455
  OmoAgentModelEntrySchema = union([string2(), OmoFallbackModelObjectSchema]);
74456
+ PermissionValueSchema = _enum(["ask", "allow", "deny"]);
74457
+ BashPermissionSchema = union([
74458
+ PermissionValueSchema,
74459
+ record(string2(), PermissionValueSchema)
74460
+ ]);
74461
+ OmoAgentPermissionSchema = object({
74462
+ edit: PermissionValueSchema.optional(),
74463
+ bash: BashPermissionSchema.optional(),
74464
+ webfetch: PermissionValueSchema.optional(),
74465
+ task: PermissionValueSchema.optional(),
74466
+ doom_loop: PermissionValueSchema.optional(),
74467
+ external_directory: PermissionValueSchema.optional()
74468
+ }).catchall(PermissionValueSchema.optional());
74322
74469
  OmoAgentDefInputSchema = object({
74323
74470
  description: string2().optional(),
74324
74471
  prompt: string2().optional(),
@@ -74335,7 +74482,9 @@ var init_agent = __esm(() => {
74335
74482
  disallowed_tools: array(string2()).optional(),
74336
74483
  max_turns: number2().int().nonnegative().optional(),
74337
74484
  temperature: number2().min(0).max(2).optional(),
74338
- disable: boolean2().optional()
74485
+ disable: boolean2().optional(),
74486
+ permission: OmoAgentPermissionSchema.optional(),
74487
+ prompt_append: string2().optional()
74339
74488
  }).strict();
74340
74489
  OmoAgentDefSchema = preprocess((value) => isRecord5(value) ? normalizeLegacyModelFields(value) : value, OmoAgentDefInputSchema);
74341
74490
  OmoAgentsConfigSchema = record(string2(), OmoAgentDefSchema);
@@ -75121,6 +75270,64 @@ var init_schema = __esm(() => {
75121
75270
  init_computer();
75122
75271
  });
75123
75272
 
75273
+ // packages/omo-config-core/src/loader/types.ts
75274
+ import { existsSync as existsSync13, lstatSync as lstatSync2, readFileSync as readFileSync5, realpathSync as realpathSync5 } from "fs";
75275
+ var MERGED_OMO_CONFIG_PATH = "(merged omo config)", DEFAULT_READ_FILE_SYSTEM;
75276
+ var init_types3 = __esm(() => {
75277
+ DEFAULT_READ_FILE_SYSTEM = {
75278
+ existsSync: existsSync13,
75279
+ lstatSync: lstatSync2,
75280
+ readFileSync: readFileSync5,
75281
+ realpathSync: realpathSync5
75282
+ };
75283
+ });
75284
+
75285
+ // packages/omo-config-core/src/loader/diagnostic-lines.ts
75286
+ function displayOmoConfigPath(path, homeDir) {
75287
+ if (path === MERGED_OMO_CONFIG_PATH)
75288
+ return "merged config";
75289
+ if (homeDir === undefined || homeDir.length === 0)
75290
+ return path;
75291
+ const slashed = (value) => value.replaceAll("\\", "/");
75292
+ const home = slashed(homeDir).replace(/\/+$/, "");
75293
+ const file = slashed(path);
75294
+ const fold = (value) => /^[A-Za-z]:\//.test(home) ? value.toLowerCase() : value;
75295
+ if (fold(file) === fold(home))
75296
+ return "~";
75297
+ return fold(file).startsWith(`${fold(home)}/`) ? `~${file.slice(home.length)}` : path;
75298
+ }
75299
+ function notLoadedReason(diagnostic) {
75300
+ switch (diagnostic.kind) {
75301
+ case "parse":
75302
+ return "JSONC parse error";
75303
+ case "read":
75304
+ return "unreadable";
75305
+ default:
75306
+ return diagnostic.issuePaths === undefined || diagnostic.issuePaths.length === 0 ? "invalid config" : `invalid: ${diagnostic.issuePaths.join(", ")}`;
75307
+ }
75308
+ }
75309
+ function omoConfigDiagnosticLines(diagnostics, options = {}) {
75310
+ return diagnostics.flatMap((diagnostic) => {
75311
+ const file = displayOmoConfigPath(diagnostic.path, options.homeDir);
75312
+ switch (diagnostic.kind) {
75313
+ case "invalid-value":
75314
+ return (diagnostic.issuePaths ?? []).map((key) => `config: ${file}: ${key} ignored (invalid value)`);
75315
+ case "unknown-keys":
75316
+ return (diagnostic.issuePaths ?? []).map((key) => `config: ${file}: ${key} ignored (unknown key)`);
75317
+ case "parse":
75318
+ case "read":
75319
+ case "validation":
75320
+ return diagnostic.path === MERGED_OMO_CONFIG_PATH ? [`config: ${file}: reset to defaults (${notLoadedReason(diagnostic)})`] : [`config: ${file}: not loaded (${notLoadedReason(diagnostic)})`];
75321
+ case "deprecated-keys":
75322
+ case "profile":
75323
+ return [];
75324
+ }
75325
+ });
75326
+ }
75327
+ var init_diagnostic_lines = __esm(() => {
75328
+ init_types3();
75329
+ });
75330
+
75124
75331
  // packages/omo-config-core/src/loader/disabled-skills.ts
75125
75332
  var init_disabled_skills = () => {};
75126
75333
 
@@ -75160,24 +75367,265 @@ var init_merge = __esm(() => {
75160
75367
  DANGEROUS_KEYS2 = new Set(["__proto__", "constructor", "prototype"]);
75161
75368
  });
75162
75369
 
75370
+ // packages/omo-config-core/src/loader/prune-invalid-leaves.ts
75371
+ function isContainer(value) {
75372
+ return typeof value === "object" && value !== null;
75373
+ }
75374
+ function childOf(container, segment) {
75375
+ return Array.isArray(container) ? container[Number(segment)] : container[String(segment)];
75376
+ }
75377
+ function hasChild(container, segment) {
75378
+ if (Array.isArray(container)) {
75379
+ const index = Number(segment);
75380
+ return Number.isInteger(index) && index >= 0 && index < container.length;
75381
+ }
75382
+ return Object.hasOwn(container, String(segment));
75383
+ }
75384
+ function segmentsOf(path) {
75385
+ return path.map((segment) => typeof segment === "number" ? segment : String(segment));
75386
+ }
75387
+ function targetPaths(root, issue) {
75388
+ const base = segmentsOf(issue.path);
75389
+ if (issue.code === "unrecognized_keys")
75390
+ return issue.keys.map((key) => [...base, key]);
75391
+ let node = root;
75392
+ let depth = 0;
75393
+ for (const segment of base) {
75394
+ if (!isContainer(node) || !hasChild(node, segment))
75395
+ break;
75396
+ node = childOf(node, segment);
75397
+ depth += 1;
75398
+ }
75399
+ return [base.slice(0, depth)];
75400
+ }
75401
+ function isAncestor(ancestor, path) {
75402
+ return ancestor.length < path.length && ancestor.every((segment, index) => segment === path[index]);
75403
+ }
75404
+ function compareDescending(left, right) {
75405
+ const length = Math.min(left.length, right.length);
75406
+ for (let index = 0;index < length; index += 1) {
75407
+ const a = left[index];
75408
+ const b = right[index];
75409
+ if (a === b)
75410
+ continue;
75411
+ if (typeof a === "number" && typeof b === "number")
75412
+ return b - a;
75413
+ return String(b).localeCompare(String(a));
75414
+ }
75415
+ return right.length - left.length;
75416
+ }
75417
+ function removeChild(container, segment) {
75418
+ if (Array.isArray(container))
75419
+ container.splice(Number(segment), 1);
75420
+ else
75421
+ delete container[String(segment)];
75422
+ }
75423
+ function isEmpty(container) {
75424
+ return Array.isArray(container) ? container.length === 0 : Object.keys(container).length === 0;
75425
+ }
75426
+ function removePathAndEmptiedAncestors(root, path) {
75427
+ const chain = [root];
75428
+ for (const segment of path.slice(0, -1)) {
75429
+ const current = chain[chain.length - 1];
75430
+ if (current === undefined)
75431
+ return;
75432
+ const next = childOf(current, segment);
75433
+ if (!isContainer(next))
75434
+ return;
75435
+ chain.push(next);
75436
+ }
75437
+ for (let depth = path.length - 1;depth >= 0; depth -= 1) {
75438
+ const container = chain[depth];
75439
+ const segment = path[depth];
75440
+ if (container === undefined || segment === undefined)
75441
+ return;
75442
+ removeChild(container, segment);
75443
+ if (depth === 0 || !isEmpty(container))
75444
+ return;
75445
+ }
75446
+ }
75447
+ function prunePass(root, issues) {
75448
+ const byKey = new Map;
75449
+ for (const issue of issues) {
75450
+ for (const path of targetPaths(root, issue)) {
75451
+ if (path.length === 0)
75452
+ return null;
75453
+ const key = path.map((segment) => String(segment)).join(".");
75454
+ if (!byKey.has(key))
75455
+ byKey.set(key, { key, message: issue.message, path });
75456
+ }
75457
+ }
75458
+ const targets = [...byKey.values()];
75459
+ const outermost = targets.filter((target) => !targets.some((other) => isAncestor(other.path, target.path))).sort((left, right) => compareDescending(left.path, right.path));
75460
+ const next = structuredClone(root);
75461
+ for (const target of outermost)
75462
+ removePathAndEmptiedAncestors(next, target.path);
75463
+ return { dropped: outermost, next };
75464
+ }
75465
+ function pruneInvalidConfigPaths(config, issues, validate, maxPasses = MAX_PRUNE_PASSES) {
75466
+ const dropped = [];
75467
+ let current = config;
75468
+ let pending = issues;
75469
+ for (let pass = 0;pass < maxPasses; pass += 1) {
75470
+ const step = prunePass(current, pending);
75471
+ if (step === null || step.dropped.length === 0)
75472
+ return { ok: false, dropped };
75473
+ dropped.push(...step.dropped);
75474
+ current = step.next;
75475
+ if (Object.keys(current).length === 0)
75476
+ return { ok: false, dropped };
75477
+ const validation = validate(current);
75478
+ if (validation.success)
75479
+ return { ok: true, config: current, dropped };
75480
+ pending = validation.issues;
75481
+ }
75482
+ return { ok: false, dropped };
75483
+ }
75484
+ var MAX_PRUNE_PASSES = 32;
75485
+
75486
+ // packages/omo-config-core/src/loader/layer-validation.ts
75487
+ function validationDiagnostic(path, issues) {
75488
+ const issuePaths = issues.map((issue) => issue.path.map((segment) => String(segment)).join("."));
75489
+ return {
75490
+ kind: "validation",
75491
+ message: `Invalid omo config at ${path}: ${issuePaths.join(", ")}`,
75492
+ path,
75493
+ issuePaths
75494
+ };
75495
+ }
75496
+ function invalidValueDiagnostics(path, dropped) {
75497
+ return dropped.map((entry) => ({
75498
+ kind: "invalid-value",
75499
+ message: `Ignored invalid value in ${path}: ${entry.key}: ${entry.message}`,
75500
+ path,
75501
+ issuePaths: [entry.key]
75502
+ }));
75503
+ }
75504
+ function unrecognizedKeyIssues(issues) {
75505
+ return issues.flatMap((issue) => issue.code === "unrecognized_keys" ? [{ keys: issue.keys, path: issue.path.map((segment) => String(segment)) }] : []);
75506
+ }
75507
+ function isRecord11(value) {
75508
+ return typeof value === "object" && value !== null && !Array.isArray(value);
75509
+ }
75510
+ function sanitizeUnsafeKeys(value, path = []) {
75511
+ if (Array.isArray(value)) {
75512
+ const issues = [];
75513
+ const sanitized = value.map((entry, index) => {
75514
+ const nested = sanitizeUnsafeKeys(entry, [...path, String(index)]);
75515
+ issues.push(...nested.issues);
75516
+ return nested.value;
75517
+ });
75518
+ return { issues, value: sanitized };
75519
+ }
75520
+ if (!isRecord11(value))
75521
+ return { issues: [], value };
75522
+ const issues = [];
75523
+ if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
75524
+ issues.push({ keys: ["__proto__"], path });
75525
+ }
75526
+ const sanitized = {};
75527
+ for (const [key, entry] of Object.entries(value)) {
75528
+ if (isUnsafeObjectKey2(key)) {
75529
+ issues.push({ keys: [key], path });
75530
+ continue;
75531
+ }
75532
+ const nested = sanitizeUnsafeKeys(entry, [...path, key]);
75533
+ issues.push(...nested.issues);
75534
+ sanitized[key] = nested.value;
75535
+ }
75536
+ return { issues, value: sanitized };
75537
+ }
75538
+ function containerAt(record, path) {
75539
+ let node = record;
75540
+ for (const segment of path) {
75541
+ if (Array.isArray(node)) {
75542
+ const index = Number(segment);
75543
+ if (!Number.isInteger(index) || index < 0 || index >= node.length)
75544
+ return null;
75545
+ node = node[index];
75546
+ } else if (isRecord11(node) && Object.hasOwn(node, segment)) {
75547
+ node = node[segment];
75548
+ } else {
75549
+ return null;
75550
+ }
75551
+ }
75552
+ return isRecord11(node) ? node : null;
75553
+ }
75554
+ function stripUnrecognizedKeys(record, issues) {
75555
+ const stripped = structuredClone(record);
75556
+ const issuePaths = [];
75557
+ for (const issue of issues) {
75558
+ const container = containerAt(stripped, issue.path);
75559
+ if (container === null)
75560
+ continue;
75561
+ for (const key of issue.keys) {
75562
+ delete container[key];
75563
+ issuePaths.push([...issue.path, key].join("."));
75564
+ }
75565
+ }
75566
+ return { issuePaths, stripped };
75567
+ }
75568
+ function toRecord(value) {
75569
+ if (value === null || typeof value !== "object" || Array.isArray(value))
75570
+ return null;
75571
+ const record = {};
75572
+ for (const [key, entry] of Object.entries(value)) {
75573
+ record[key] = entry;
75574
+ }
75575
+ return record;
75576
+ }
75577
+ function validateConfigLayer(path, data) {
75578
+ const sanitized = sanitizeUnsafeKeys(data);
75579
+ const record = toRecord(sanitized.value);
75580
+ const unsafeIssuePaths = sanitized.issues.flatMap((issue) => issue.keys.map((key) => [...issue.path, key].join(".")));
75581
+ const unsafeDiagnostics = unsafeIssuePaths.length === 0 ? [] : [{ kind: "unknown-keys", message: `Ignored unknown keys in ${path}: ${unsafeIssuePaths.join(", ")}`, path, issuePaths: unsafeIssuePaths }];
75582
+ const validation = OmoConfigLayerSchema.safeParse(sanitized.value);
75583
+ if (validation.success) {
75584
+ if (record !== null)
75585
+ return { loaded: true, diagnostics: unsafeDiagnostics, value: record };
75586
+ return {
75587
+ loaded: false,
75588
+ diagnostics: [{ kind: "validation", message: `Invalid omo config at ${path}: root must be an object`, path }]
75589
+ };
75590
+ }
75591
+ const rejected = { loaded: false, diagnostics: [validationDiagnostic(path, validation.error.issues)] };
75592
+ const unknownIssues = unrecognizedKeyIssues(validation.error.issues);
75593
+ if (record === null)
75594
+ return rejected;
75595
+ let candidate = record;
75596
+ let issues = validation.error.issues;
75597
+ const diagnostics = [...unsafeDiagnostics];
75598
+ if (unknownIssues.length > 0) {
75599
+ const { issuePaths, stripped } = stripUnrecognizedKeys(record, unknownIssues);
75600
+ if (issuePaths.length > 0) {
75601
+ diagnostics.push({ kind: "unknown-keys", message: `Ignored unknown keys in ${path}: ${issuePaths.join(", ")}`, path, issuePaths });
75602
+ }
75603
+ const strippedValidation = validateLayerRecord(stripped);
75604
+ if (strippedValidation.success)
75605
+ return { loaded: true, diagnostics, value: stripped };
75606
+ candidate = stripped;
75607
+ issues = strippedValidation.issues;
75608
+ }
75609
+ const pruned = pruneInvalidConfigPaths(candidate, issues, validateLayerRecord);
75610
+ if (!pruned.ok)
75611
+ return rejected;
75612
+ return { loaded: true, diagnostics: [...diagnostics, ...invalidValueDiagnostics(path, pruned.dropped)], value: pruned.config };
75613
+ }
75614
+ var validateLayerRecord = (record) => {
75615
+ const parsed = OmoConfigLayerSchema.safeParse(record);
75616
+ return parsed.success ? { success: true } : { success: false, issues: parsed.error.issues };
75617
+ };
75618
+ var init_layer_validation = __esm(() => {
75619
+ init_schema();
75620
+ init_merge();
75621
+ });
75622
+
75163
75623
  // packages/omo-config-core/src/internal/posix-path.ts
75164
75624
  function toPosixPath(path) {
75165
75625
  return path.split("\\").join("/");
75166
75626
  }
75167
75627
  var init_posix_path = () => {};
75168
75628
 
75169
- // packages/omo-config-core/src/loader/types.ts
75170
- import { existsSync as existsSync13, lstatSync as lstatSync2, readFileSync as readFileSync5, realpathSync as realpathSync5 } from "fs";
75171
- var DEFAULT_READ_FILE_SYSTEM;
75172
- var init_types3 = __esm(() => {
75173
- DEFAULT_READ_FILE_SYSTEM = {
75174
- existsSync: existsSync13,
75175
- lstatSync: lstatSync2,
75176
- readFileSync: readFileSync5,
75177
- realpathSync: realpathSync5
75178
- };
75179
- });
75180
-
75181
75629
  // packages/omo-config-core/src/loader/paths.ts
75182
75630
  import { userInfo } from "os";
75183
75631
  import { dirname as dirname4, join as join15, posix as posix2, resolve as resolve3 } from "path";
@@ -75281,7 +75729,7 @@ function resolveOmoProfileName(options = {}) {
75281
75729
  const env = options.env ?? process.env;
75282
75730
  return profileName(options.profile) ?? profileName(env["OMO_PROFILE"]) ?? profileName(env["OCX_PROFILE"]) ?? profileNameFromOpenCodeConfigDir(env["OPENCODE_CONFIG_DIR"]);
75283
75731
  }
75284
- function toRecord(value) {
75732
+ function toRecord2(value) {
75285
75733
  if (value === null || typeof value !== "object" || Array.isArray(value))
75286
75734
  return;
75287
75735
  return Object.fromEntries(Object.entries(value));
@@ -75302,13 +75750,13 @@ function harnessLayer(config, harness) {
75302
75750
  const legacyKeys = Object.entries(OMO_CONFIG_LEGACY_HARNESS_ALIASES).filter(([, target]) => target === canonical).map(([legacy]) => harnessBlockKey(legacy));
75303
75751
  let layer = {};
75304
75752
  for (const key of [...legacyKeys, harnessBlockKey(canonical)]) {
75305
- layer = mergeOmoConfigRecords(layer, toRecord(config[key]) ?? {});
75753
+ layer = mergeOmoConfigRecords(layer, toRecord2(config[key]) ?? {});
75306
75754
  }
75307
75755
  return layer;
75308
75756
  }
75309
75757
  function resolveOmoConfigView(options) {
75310
- const profiles = toRecord(options.config["profiles"]);
75311
- const profile = options.profile === undefined ? undefined : toRecord(profiles?.[options.profile]);
75758
+ const profiles = toRecord2(options.config["profiles"]);
75759
+ const profile = options.profile === undefined ? undefined : toRecord2(profiles?.[options.profile]);
75312
75760
  const diagnostics = profile === undefined && options.profile !== undefined ? [{
75313
75761
  kind: "profile",
75314
75762
  message: `Activated omo profile "${options.profile}" does not exist; using the base configuration`,
@@ -75363,70 +75811,9 @@ function stripResolutionControlKeys(config) {
75363
75811
  } = config;
75364
75812
  return resolved;
75365
75813
  }
75366
- function validationDiagnostic(path, issues) {
75367
- const issuePaths = issues.map((issue) => issue.path.map((segment) => String(segment)).join("."));
75368
- return {
75369
- kind: "validation",
75370
- message: `Invalid omo config at ${path}: ${issuePaths.join(", ")}`,
75371
- path,
75372
- issuePaths
75373
- };
75374
- }
75375
- function unrecognizedKeyIssues(issues) {
75376
- return issues.flatMap((issue) => issue.code === "unrecognized_keys" ? [{ keys: issue.keys, path: issue.path.map((segment) => String(segment)) }] : []);
75377
- }
75378
- function hasUnsafeUnrecognizedKey(issues) {
75379
- return issues.some((issue) => issue.keys.some((key) => isUnsafeObjectKey2(key)));
75380
- }
75381
- function hasTamperedPrototype(value) {
75382
- if (Array.isArray(value))
75383
- return value.some((entry) => hasTamperedPrototype(entry));
75384
- if (!isRecord11(value))
75385
- return false;
75386
- const prototype = Object.getPrototypeOf(value);
75387
- if (prototype !== Object.prototype && prototype !== null)
75388
- return true;
75389
- return Object.values(value).some((entry) => hasTamperedPrototype(entry));
75390
- }
75391
- function isRecord11(value) {
75392
- return typeof value === "object" && value !== null && !Array.isArray(value);
75393
- }
75394
- function containerAt(record, path) {
75395
- let container = record;
75396
- for (const segment of path) {
75397
- const next = container[segment];
75398
- if (!isRecord11(next))
75399
- return null;
75400
- container = next;
75401
- }
75402
- return container;
75403
- }
75404
- function stripUnrecognizedKeys(record, issues) {
75405
- const stripped = structuredClone(record);
75406
- const issuePaths = [];
75407
- for (const issue of issues) {
75408
- const container = containerAt(stripped, issue.path);
75409
- if (container === null)
75410
- continue;
75411
- for (const key of issue.keys) {
75412
- delete container[key];
75413
- issuePaths.push([...issue.path, key].join("."));
75414
- }
75415
- }
75416
- return { issuePaths, stripped };
75417
- }
75418
- function toRecord2(value) {
75419
- if (value === null || typeof value !== "object" || Array.isArray(value))
75420
- return null;
75421
- const record = {};
75422
- for (const [key, entry] of Object.entries(value)) {
75423
- record[key] = entry;
75424
- }
75425
- return record;
75426
- }
75427
75814
  function readConfigSource(path, scope, fileSystem) {
75428
75815
  if (!fileSystem.existsSync(path)) {
75429
- return { source: { exists: false, loaded: false, path, scope } };
75816
+ return { diagnostics: [], source: { exists: false, loaded: false, path, scope } };
75430
75817
  }
75431
75818
  let content;
75432
75819
  try {
@@ -75434,68 +75821,23 @@ function readConfigSource(path, scope, fileSystem) {
75434
75821
  } catch (error) {
75435
75822
  const message = error instanceof Error ? error.message : String(error);
75436
75823
  return {
75437
- diagnostic: { kind: "read", message: `Failed to read ${path}: ${message}`, path },
75824
+ diagnostics: [{ kind: "read", message: `Failed to read ${path}: ${message}`, path }],
75438
75825
  source: { exists: true, loaded: false, path, scope }
75439
75826
  };
75440
75827
  }
75441
75828
  const parsed = parseJsoncSafe2(content);
75442
75829
  if (parsed.errors.length > 0) {
75443
75830
  return {
75444
- diagnostic: {
75831
+ diagnostics: [{
75445
75832
  kind: "parse",
75446
75833
  message: `JSONC parse error in ${path}: ${parsed.errors.map((error) => error.message).join(", ")}`,
75447
75834
  path
75448
- },
75449
- source: { exists: true, loaded: false, path, scope }
75450
- };
75451
- }
75452
- if (hasTamperedPrototype(parsed.data)) {
75453
- return {
75454
- diagnostic: { kind: "validation", message: `Invalid omo config at ${path}: "__proto__" member is not allowed`, path },
75835
+ }],
75455
75836
  source: { exists: true, loaded: false, path, scope }
75456
75837
  };
75457
75838
  }
75458
- const parsedRecord = toRecord2(parsed.data);
75459
- const validation = OmoConfigLayerSchema.safeParse(parsed.data);
75460
- if (!validation.success) {
75461
- const unrecognized = unrecognizedKeyIssues(validation.error.issues);
75462
- if (hasUnsafeUnrecognizedKey(unrecognized)) {
75463
- return {
75464
- diagnostic: validationDiagnostic(path, validation.error.issues),
75465
- source: { exists: true, loaded: false, path, scope }
75466
- };
75467
- }
75468
- const rejected = {
75469
- diagnostic: validationDiagnostic(path, validation.error.issues),
75470
- source: { exists: true, loaded: false, path, scope }
75471
- };
75472
- const unknownIssues = unrecognizedKeyIssues(validation.error.issues);
75473
- if (parsedRecord === null || unknownIssues.length === 0)
75474
- return rejected;
75475
- const { issuePaths, stripped } = stripUnrecognizedKeys(parsedRecord, unknownIssues);
75476
- if (!OmoConfigLayerSchema.safeParse(stripped).success)
75477
- return rejected;
75478
- return {
75479
- diagnostic: {
75480
- kind: "unknown-keys",
75481
- message: `Ignored unknown keys in ${path}: ${issuePaths.join(", ")}`,
75482
- path,
75483
- issuePaths
75484
- },
75485
- source: { exists: true, loaded: true, path, scope },
75486
- value: stripped
75487
- };
75488
- }
75489
- if (parsedRecord === null) {
75490
- return {
75491
- diagnostic: { kind: "validation", message: `Invalid omo config at ${path}: root must be an object`, path },
75492
- source: { exists: true, loaded: false, path, scope }
75493
- };
75494
- }
75495
- return {
75496
- source: { exists: true, loaded: true, path, scope },
75497
- value: parsedRecord
75498
- };
75839
+ const layer = validateConfigLayer(path, parsed.data);
75840
+ return layer.loaded ? { diagnostics: layer.diagnostics, source: { exists: true, loaded: true, path, scope }, value: layer.value } : { diagnostics: layer.diagnostics, source: { exists: true, loaded: false, path, scope } };
75499
75841
  }
75500
75842
  function legacyCategoryDiagnostic(path, renames) {
75501
75843
  const detail = renames.map((rename) => rename.dropped ? `${rename.path} ignored because ${rename.canonical} is also configured` : `${rename.path} renamed to ${rename.canonical}`).join(", ");
@@ -75530,8 +75872,7 @@ function loadOmoConfig(options = {}) {
75530
75872
  })) {
75531
75873
  const loaded = readConfigSource(candidate.path, candidate.scope, fileSystem);
75532
75874
  sources.push(loaded.source);
75533
- if (loaded.diagnostic !== undefined)
75534
- diagnostics.push(loaded.diagnostic);
75875
+ diagnostics.push(...loaded.diagnostics);
75535
75876
  if (loaded.value !== undefined) {
75536
75877
  const canonicalized = canonicalizeLegacyCategoryNames(loaded.value);
75537
75878
  if (canonicalized.renames.length > 0) {
@@ -75554,7 +75895,8 @@ function loadOmoConfig(options = {}) {
75554
75895
  ...options.harness === undefined ? {} : { harness: options.harness },
75555
75896
  ...requestedProfile === undefined ? {} : { profile: requestedProfile }
75556
75897
  });
75557
- const finalConfig = OmoConfigSchema.safeParse(mergeOmoConfigRecords(DEFAULT_RAW_CONFIG, resolved.config));
75898
+ const finalInput = mergeOmoConfigRecords(DEFAULT_RAW_CONFIG, resolved.config);
75899
+ const finalConfig = OmoConfigSchema.safeParse(finalInput);
75558
75900
  if (finalConfig.success) {
75559
75901
  return {
75560
75902
  config: stripResolutionControlKeys(finalConfig.data),
@@ -75564,9 +75906,22 @@ function loadOmoConfig(options = {}) {
75564
75906
  sources
75565
75907
  };
75566
75908
  }
75909
+ const pruned = pruneInvalidConfigPaths(finalInput, finalConfig.error.issues, (record) => {
75910
+ const parsed = OmoConfigSchema.safeParse(record);
75911
+ return parsed.success ? { success: true } : { success: false, issues: parsed.error.issues };
75912
+ });
75913
+ if (pruned.ok) {
75914
+ return {
75915
+ config: stripResolutionControlKeys(OmoConfigSchema.parse(pruned.config)),
75916
+ diagnostics: [...diagnostics, ...resolved.diagnostics, ...invalidValueDiagnostics(MERGED_OMO_CONFIG_PATH, pruned.dropped)],
75917
+ layers,
75918
+ ...resolved.profile === undefined ? {} : { profile: resolved.profile },
75919
+ sources
75920
+ };
75921
+ }
75567
75922
  return {
75568
75923
  config: stripResolutionControlKeys(OmoConfigSchema.parse(DEFAULT_RAW_CONFIG)),
75569
- diagnostics: [...diagnostics, ...resolved.diagnostics, validationDiagnostic("(merged omo config)", finalConfig.error.issues)],
75924
+ diagnostics: [...diagnostics, ...resolved.diagnostics, validationDiagnostic(MERGED_OMO_CONFIG_PATH, finalConfig.error.issues)],
75570
75925
  layers,
75571
75926
  ...resolved.profile === undefined ? {} : { profile: resolved.profile },
75572
75927
  sources
@@ -75576,6 +75931,7 @@ var DEFAULT_RAW_CONFIG;
75576
75931
  var init_loader2 = __esm(() => {
75577
75932
  init_main();
75578
75933
  init_schema();
75934
+ init_layer_validation();
75579
75935
  init_merge();
75580
75936
  init_paths();
75581
75937
  init_resolution();
@@ -75590,6 +75946,7 @@ var init_loader2 = __esm(() => {
75590
75946
 
75591
75947
  // packages/omo-config-core/src/loader/index.ts
75592
75948
  var init_loader3 = __esm(() => {
75949
+ init_diagnostic_lines();
75593
75950
  init_disabled_skills();
75594
75951
  init_loader2();
75595
75952
  init_merge();
@@ -89020,7 +89377,7 @@ var package_default2;
89020
89377
  var init_package2 = __esm(() => {
89021
89378
  package_default2 = {
89022
89379
  name: "@oh-my-opencode/omo-codex",
89023
- version: "5.1.3",
89380
+ version: "5.1.5",
89024
89381
  type: "module",
89025
89382
  private: true,
89026
89383
  description: "Codex harness adapter for oh-my-openagent. Vendored Codex plugin namespace (omo) + TypeScript installer + telemetry.",
@@ -102021,6 +102378,7 @@ async function processEvents(ctx, stream, state) {
102021
102378
  }
102022
102379
  }
102023
102380
  // packages/omo-opencode/src/config/validate.ts
102381
+ init_src4();
102024
102382
  import { relative as relative7 } from "path";
102025
102383
 
102026
102384
  // packages/omo-opencode/src/shared/disabled-providers.ts
@@ -102141,11 +102499,11 @@ function baseView(config) {
102141
102499
  }
102142
102500
  return result;
102143
102501
  }
102144
- function appendViews(views, layers, select) {
102502
+ function appendViews(views, layers, select, keyPrefix) {
102145
102503
  for (const layer of layers) {
102146
102504
  const config = select(layer.config);
102147
102505
  if (Object.keys(config).length > 0)
102148
- views.push({ config, path: layer.source.path });
102506
+ views.push({ config, keyPrefix, path: layer.source.path });
102149
102507
  }
102150
102508
  }
102151
102509
  function recordFields(value, fields) {
@@ -102174,10 +102532,21 @@ function modelInput(view) {
102174
102532
  };
102175
102533
  }
102176
102534
  function modelView(view) {
102177
- const parsed = OmoConfigSchema.safeParse(modelInput(view));
102178
- if (!parsed.success)
102535
+ const input = modelInput(view);
102536
+ const parsed = OmoConfigSchema.safeParse(input);
102537
+ const modelConfig = parsed.success ? parsed.data : (() => {
102538
+ const pruned = pruneInvalidConfigPaths(input, parsed.error.issues, (record) => {
102539
+ const validation = OmoConfigSchema.safeParse(record);
102540
+ return validation.success ? { success: true } : { success: false, issues: validation.error.issues };
102541
+ });
102542
+ if (!pruned.ok)
102543
+ return;
102544
+ const validation = OmoConfigSchema.safeParse(pruned.config);
102545
+ return validation.success ? validation.data : undefined;
102546
+ })();
102547
+ if (modelConfig === undefined)
102179
102548
  return { config: {}, diagnostics: [] };
102180
- const resolved = resolveModelReferences(parsed.data);
102549
+ const resolved = resolveModelReferences(modelConfig);
102181
102550
  return {
102182
102551
  config: {
102183
102552
  ...resolved.view.agents === undefined ? {} : { agents: resolved.view.agents },
@@ -102196,14 +102565,15 @@ function loadOmoOpenCodeConfigChain(directory, environment = process.env) {
102196
102565
  });
102197
102566
  const views = [];
102198
102567
  const selectedProfile = loaded.profile;
102199
- appendViews(views, loaded.layers, baseView);
102200
- appendViews(views, loaded.layers, block2);
102201
- appendViews(views, loaded.layers, (config) => baseView(profile(config, selectedProfile)));
102202
- appendViews(views, loaded.layers, (config) => block2(profile(config, selectedProfile)));
102568
+ const profilePrefix = selectedProfile === undefined ? "" : `profiles.${selectedProfile}.`;
102569
+ appendViews(views, loaded.layers, baseView, "");
102570
+ appendViews(views, loaded.layers, block2, "[opencode].");
102571
+ appendViews(views, loaded.layers, (config) => baseView(profile(config, selectedProfile)), profilePrefix);
102572
+ appendViews(views, loaded.layers, (config) => block2(profile(config, selectedProfile)), `${profilePrefix}[opencode].`);
102203
102573
  const resolvedModels = modelView(resolved.config);
102204
102574
  if (Object.keys(resolvedModels.config).length > 0) {
102205
102575
  const source = loaded.layers[0]?.source.path ?? directory;
102206
- views.push({ config: resolvedModels.config, path: source });
102576
+ views.push({ config: resolvedModels.config, keyPrefix: "", path: source });
102207
102577
  }
102208
102578
  let protectedUserView = {};
102209
102579
  for (const layer of loaded.layers) {
@@ -102290,7 +102660,7 @@ function unknownPaths(schema, value, path) {
102290
102660
  if (def.type === "object" && isPlainRecord5(value) && def.shape !== undefined) {
102291
102661
  const catchall = def.catchall !== undefined && definition(def.catchall).type !== "never" ? def.catchall : undefined;
102292
102662
  return Object.entries(value).flatMap(([key, entry]) => {
102293
- const propertySchema = def.shape?.[key];
102663
+ const propertySchema = def.shape !== undefined && Object.hasOwn(def.shape, key) ? def.shape[key] : undefined;
102294
102664
  if (propertySchema !== undefined)
102295
102665
  return unknownPaths(propertySchema, entry, [...path, key]);
102296
102666
  if (catchall !== undefined)
@@ -102328,6 +102698,9 @@ function findUnknownKeyPaths(schema, value) {
102328
102698
  return unknownPaths(schema, value, []);
102329
102699
  }
102330
102700
 
102701
+ // packages/omo-opencode/src/config/prune-plugin-view.ts
102702
+ init_src4();
102703
+
102331
102704
  // packages/omo-opencode/src/config/schema/agent-names.ts
102332
102705
  init_zod();
102333
102706
  var BuiltinAgentNameSchema = _enum([
@@ -102405,19 +102778,19 @@ var FallbackModelsSchema = union([
102405
102778
 
102406
102779
  // packages/omo-opencode/src/config/schema/internal/permission.ts
102407
102780
  init_zod();
102408
- var PermissionValueSchema = _enum(["ask", "allow", "deny"]);
102409
- var BashPermissionSchema = union([
102410
- PermissionValueSchema,
102411
- record(string2(), PermissionValueSchema)
102781
+ var PermissionValueSchema2 = _enum(["ask", "allow", "deny"]);
102782
+ var BashPermissionSchema2 = union([
102783
+ PermissionValueSchema2,
102784
+ record(string2(), PermissionValueSchema2)
102412
102785
  ]);
102413
102786
  var AgentPermissionSchema = object({
102414
- edit: PermissionValueSchema.optional(),
102415
- bash: BashPermissionSchema.optional(),
102416
- webfetch: PermissionValueSchema.optional(),
102417
- task: PermissionValueSchema.optional(),
102418
- doom_loop: PermissionValueSchema.optional(),
102419
- external_directory: PermissionValueSchema.optional()
102420
- }).catchall(PermissionValueSchema.optional());
102787
+ edit: PermissionValueSchema2.optional(),
102788
+ bash: BashPermissionSchema2.optional(),
102789
+ webfetch: PermissionValueSchema2.optional(),
102790
+ task: PermissionValueSchema2.optional(),
102791
+ doom_loop: PermissionValueSchema2.optional(),
102792
+ external_directory: PermissionValueSchema2.optional()
102793
+ }).catchall(PermissionValueSchema2.optional());
102421
102794
 
102422
102795
  // packages/omo-opencode/src/config/schema/agent-overrides.ts
102423
102796
  var AgentOverrideConfigSchema = object({
@@ -102982,6 +103355,26 @@ var OhMyOpenCodeConfigSchema = object({
102982
103355
  default_mode: DefaultModeConfigSchema.optional(),
102983
103356
  _migrations: array(string2()).optional()
102984
103357
  });
103358
+ // packages/omo-opencode/src/config/prune-plugin-view.ts
103359
+ var validatePluginRecord = (record) => {
103360
+ const parsed = OhMyOpenCodeConfigSchema.safeParse(record);
103361
+ return parsed.success ? { success: true } : { success: false, issues: parsed.error.issues };
103362
+ };
103363
+ function prunePluginView(input) {
103364
+ const validation = OhMyOpenCodeConfigSchema.safeParse(input.config);
103365
+ if (validation.success)
103366
+ return { config: input.config, issues: [], warnings: [] };
103367
+ const pruned = pruneInvalidConfigPaths(input.config, validation.error.issues, validatePluginRecord);
103368
+ if (!pruned.ok)
103369
+ return { config: input.config, issues: validation.error.issues, warnings: [] };
103370
+ const file = displayOmoConfigPath(input.path, input.homeDir);
103371
+ return {
103372
+ config: pruned.config,
103373
+ issues: [],
103374
+ warnings: pruned.dropped.map((entry) => `config: ${file}: ${input.keyPrefix}${entry.key} ignored (invalid value)`)
103375
+ };
103376
+ }
103377
+
102985
103378
  // packages/omo-opencode/src/config/validate.ts
102986
103379
  function shortPath(configPath) {
102987
103380
  const candidate = relative7(process.cwd(), configPath);
@@ -102991,9 +103384,8 @@ function formatIssuePath(path) {
102991
103384
  const formatted = path.map((segment) => String(segment)).join(".");
102992
103385
  return formatted.length > 0 ? formatted : "<root>";
102993
103386
  }
102994
- function schemaMessages(configPath, rawConfig) {
102995
- const result = OhMyOpenCodeConfigSchema.safeParse(rawConfig);
102996
- const validationMessages = result.success ? [] : result.error.issues.map((issue) => `${shortPath(configPath)}: ${formatIssuePath(issue.path)}: ${issue.message}`);
103387
+ function schemaMessages(configPath, rawConfig, issues) {
103388
+ const validationMessages = issues.map((issue) => `${shortPath(configPath)}: ${formatIssuePath(issue.path)}: ${issue.message}`);
102997
103389
  const unknownKeyMessages = findUnknownKeyPaths(OhMyOpenCodeConfigSchema, rawConfig).map((path) => `${shortPath(configPath)}: Unknown config key: ${formatIssuePath(path)}`);
102998
103390
  return [...validationMessages, ...unknownKeyMessages];
102999
103391
  }
@@ -103009,11 +103401,13 @@ function parseConfig(rawConfig) {
103009
103401
  }
103010
103402
  return config;
103011
103403
  }
103012
- function parseConfigView(path, rawConfig) {
103404
+ function parseConfigView(view, homeDir) {
103405
+ const pruned = prunePluginView({ config: view.config, homeDir, keyPrefix: view.keyPrefix, path: view.path });
103013
103406
  return {
103014
- config: parseConfig(rawConfig),
103015
- messages: schemaMessages(path, rawConfig),
103016
- path
103407
+ config: parseConfig(pruned.config),
103408
+ messages: schemaMessages(view.path, view.config, pruned.issues),
103409
+ path: view.path,
103410
+ warnings: pruned.warnings
103017
103411
  };
103018
103412
  }
103019
103413
  function mergeViews(views) {
@@ -103105,18 +103499,25 @@ function migrateRalphLoopConfig(config) {
103105
103499
  }
103106
103500
  function validatePluginConfig(directory, environment = process.env) {
103107
103501
  const chain = loadOmoOpenCodeConfigChain(directory, environment);
103108
- const views = chain.views.map((view) => parseConfigView(view.path, view.config));
103109
- const chainMessages = chain.diagnostics.filter((diagnostic) => diagnostic.kind !== "deprecated-keys").map((diagnostic) => `${shortPath(diagnostic.path)}: ${diagnostic.message}`);
103502
+ const homeDir = environment.HOME ?? environment.USERPROFILE;
103503
+ const views = chain.views.map((view) => parseConfigView(view, homeDir));
103504
+ const failing = chain.diagnostics.filter((diagnostic) => diagnostic.kind !== "deprecated-keys" && diagnostic.kind !== "invalid-value");
103505
+ const chainMessages = failing.map((diagnostic) => `${shortPath(diagnostic.path)}: ${diagnostic.message}`);
103110
103506
  const messages = [...chainMessages, ...views.flatMap((view) => view.messages)];
103507
+ const warnings = [
103508
+ ...omoConfigDiagnosticLines(chain.diagnostics.flatMap((diagnostic) => diagnostic.kind === "invalid-value" ? [diagnostic] : []), { homeDir }),
103509
+ ...views.flatMap((view) => view.warnings)
103510
+ ];
103111
103511
  const firstFailingView = views.find((view) => view.messages.length > 0);
103112
103512
  const firstView = views[0];
103113
- const userConfig = parseConfig(chain.protectedUserView);
103513
+ const userConfig = parseConfig(prunePluginView({ config: chain.protectedUserView, homeDir, keyPrefix: "", path: "" }).config);
103114
103514
  const config = applyDisabledProviders(materializeAgentModelChains(protectUserFields(mergeViews(views), userConfig)));
103115
103515
  warnLegacyUlwExecuteKey(chain.views);
103116
103516
  return {
103117
103517
  valid: messages.length === 0,
103118
103518
  messages,
103119
- path: chainMessages.length > 0 ? chain.diagnostics[0]?.path ?? null : firstFailingView?.path ?? firstView?.path ?? null,
103519
+ warnings,
103520
+ path: failing[0]?.path ?? firstFailingView?.path ?? firstView?.path ?? null,
103120
103521
  config: migrateRalphLoopConfig(migrateLegacyUlwExecuteKey(config))
103121
103522
  };
103122
103523
  }
@@ -106240,14 +106641,15 @@ function toOmoConfig(config) {
106240
106641
  function validateConfig() {
106241
106642
  const validation = validatePluginConfig(process.cwd());
106242
106643
  if (!validation.path) {
106243
- return { exists: false, path: null, valid: true, config: null, errors: [] };
106644
+ return { exists: false, path: null, valid: true, config: null, errors: [], warnings: validation.warnings };
106244
106645
  }
106245
106646
  return {
106246
106647
  exists: true,
106247
106648
  path: validation.path,
106248
106649
  valid: validation.valid,
106249
106650
  config: toOmoConfig(validation.config),
106250
- errors: [...validation.messages]
106651
+ errors: [...validation.messages],
106652
+ warnings: validation.warnings
106251
106653
  };
106252
106654
  }
106253
106655
  function collectModelResolutionIssues(config) {
@@ -106300,6 +106702,12 @@ async function checkConfig() {
106300
106702
  }));
106301
106703
  if (legacyWarning !== undefined)
106302
106704
  issues.push(legacyWarning);
106705
+ issues.push(...validation.warnings.map((warning) => ({
106706
+ title: warning,
106707
+ description: "The rest of that file still applies; fix or remove this value to use it.",
106708
+ severity: "warning",
106709
+ affects: ["configuration"]
106710
+ })));
106303
106711
  if (!validation.exists) {
106304
106712
  return {
106305
106713
  name: CHECK_NAMES[CHECK_IDS.CONFIG],