oh-my-opencode 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/tui.js CHANGED
@@ -3502,6 +3502,8 @@ var init_category_model_requirements = __esm(() => {
3502
3502
  },
3503
3503
  "deep-low": {
3504
3504
  fallbackChain: [
3505
+ { providers: ["openai", "chatgpt-subscription"], model: "gpt-6.1-sol", variant: "medium" },
3506
+ { providers: ["openai", "chatgpt-subscription"], model: "gpt-6.1-sol-fast", variant: "medium" },
3505
3507
  {
3506
3508
  providers: ["openai", "chatgpt-subscription", "github-copilot", "opencode"],
3507
3509
  model: "gpt-5.6-sol",
@@ -3554,7 +3556,9 @@ var init_category_model_requirements = __esm(() => {
3554
3556
  providers: ["anthropic", "anthropic-api", "github-copilot"],
3555
3557
  model: "claude-haiku-4-5",
3556
3558
  variant: "off"
3557
- }
3559
+ },
3560
+ { providers: ["zai-coding-plan"], model: "glm-5.3-flash", variant: "low" },
3561
+ { providers: ["xiaomi"], model: "mimo-v2.6-flash", variant: "low" }
3558
3562
  ]
3559
3563
  },
3560
3564
  "unspecified-low": {
@@ -3700,7 +3704,7 @@ var init_model_capability_aliases = __esm(() => {
3700
3704
  description: "Normalizes OpenCode's OpenAI GPT fast service-tier IDs to canonical snapshot IDs.",
3701
3705
  providerIDs: ["openai"],
3702
3706
  allowedSubproviderHosts: ["vercel"],
3703
- match: (normalizedModelID) => /^(?:gpt-5\.6-(?:sol|terra|luna)|gpt-6-(?:astra|sol|luna))-fast$/.test(normalizedModelID),
3707
+ match: (normalizedModelID) => /^(?:gpt-5\.6-(?:sol|terra|luna)|gpt-6-(?:astra|sol|luna)|gpt-6\.1-sol)-fast$/.test(normalizedModelID),
3704
3708
  canonicalize: (normalizedModelID) => normalizedModelID.slice(0, -"-fast".length)
3705
3709
  },
3706
3710
  {
@@ -3836,6 +3840,14 @@ var init_model_capability_heuristics = __esm(() => {
3836
3840
  reasoningEffortAliases: { none: "low", minimal: "low" },
3837
3841
  supportsTemperature: false
3838
3842
  },
3843
+ {
3844
+ family: "gpt-6.1-sol",
3845
+ pattern: /gpt-6-1-sol/,
3846
+ variants: ["low", "medium", "high", "xhigh", "max"],
3847
+ reasoningEfforts: ["low", "medium", "high", "xhigh", "max"],
3848
+ reasoningEffortAliases: { none: "low", minimal: "low" },
3849
+ supportsTemperature: false
3850
+ },
3839
3851
  {
3840
3852
  family: "gpt-6",
3841
3853
  includes: ["gpt-6"],
@@ -4076,6 +4088,36 @@ var init_supplemental_entries = __esm(() => {
4076
4088
  output: 128000
4077
4089
  }
4078
4090
  },
4091
+ "gpt-6.1-sol": {
4092
+ id: "gpt-6.1-sol",
4093
+ family: "gpt",
4094
+ reasoning: true,
4095
+ temperature: false,
4096
+ toolCall: true,
4097
+ modalities: {
4098
+ input: ["text", "image"],
4099
+ output: ["text"]
4100
+ },
4101
+ limit: {
4102
+ context: 400000,
4103
+ output: 128000
4104
+ }
4105
+ },
4106
+ "gpt-6.1-sol-fast": {
4107
+ id: "gpt-6.1-sol-fast",
4108
+ family: "gpt",
4109
+ reasoning: true,
4110
+ temperature: false,
4111
+ toolCall: true,
4112
+ modalities: {
4113
+ input: ["text", "image"],
4114
+ output: ["text"]
4115
+ },
4116
+ limit: {
4117
+ context: 400000,
4118
+ output: 128000
4119
+ }
4120
+ },
4079
4121
  "gpt-6-luna": {
4080
4122
  id: "gpt-6-luna",
4081
4123
  family: "gpt-nano",
@@ -5257,7 +5299,7 @@ You are working on tasks that don't fit specific categories but require substant
5257
5299
  </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;
5258
5300
  var init_openai_categories = __esm(() => {
5259
5301
  init_types();
5260
- DEEP_LOW_GATE_MODELS = ["gpt-5.6-sol-fast", "gpt-5.6-sol"];
5302
+ DEEP_LOW_GATE_MODELS = ["gpt-6.1-sol", "gpt-6.1-sol-fast", "gpt-5.6-sol-fast", "gpt-5.6-sol"];
5261
5303
  OPENAI_CATEGORIES = [
5262
5304
  {
5263
5305
  name: "ultrabrain",
@@ -5268,7 +5310,7 @@ var init_openai_categories = __esm(() => {
5268
5310
  },
5269
5311
  {
5270
5312
  name: "deep-low",
5271
- config: { model: "openai/gpt-5.6-sol", variant: "medium" },
5313
+ config: { model: "openai/gpt-6.1-sol", variant: "medium" },
5272
5314
  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.",
5273
5315
  callerGuidance: DEEP_LOW_CATEGORY_CALLER_GUIDANCE,
5274
5316
  promptAppend: DEEP_LOW_CATEGORY_PROMPT_APPEND,
@@ -60328,6 +60370,94 @@ var init_model_capabilities_generated = __esm(() => {
60328
60370
  context: 1e6,
60329
60371
  output: 128000
60330
60372
  }
60373
+ },
60374
+ "glm-5.3-flash": {
60375
+ id: "glm-5.3-flash",
60376
+ family: "glm-flash",
60377
+ reasoning: true,
60378
+ temperature: true,
60379
+ toolCall: true,
60380
+ modalities: {
60381
+ input: [
60382
+ "text",
60383
+ "image",
60384
+ "video"
60385
+ ],
60386
+ output: [
60387
+ "text"
60388
+ ]
60389
+ },
60390
+ limit: {
60391
+ context: 1048576,
60392
+ output: 1048576
60393
+ }
60394
+ },
60395
+ "zai/glm-5.3-flash": {
60396
+ id: "zai/glm-5.3-flash",
60397
+ family: "glm-flash",
60398
+ reasoning: true,
60399
+ temperature: true,
60400
+ toolCall: true,
60401
+ modalities: {
60402
+ input: [
60403
+ "text",
60404
+ "image",
60405
+ "video",
60406
+ "pdf"
60407
+ ],
60408
+ output: [
60409
+ "text"
60410
+ ]
60411
+ },
60412
+ limit: {
60413
+ context: 1e6,
60414
+ output: 131072
60415
+ }
60416
+ },
60417
+ "mimo-v2.6-flash": {
60418
+ id: "mimo-v2.6-flash",
60419
+ family: "mimo",
60420
+ reasoning: true,
60421
+ temperature: true,
60422
+ toolCall: true,
60423
+ modalities: {
60424
+ input: [
60425
+ "text",
60426
+ "image",
60427
+ "audio",
60428
+ "video"
60429
+ ],
60430
+ output: [
60431
+ "text"
60432
+ ]
60433
+ },
60434
+ limit: {
60435
+ context: 1048576,
60436
+ output: 131072
60437
+ }
60438
+ },
60439
+ "xiaomi/mimo-v2.6-flash": {
60440
+ id: "xiaomi/mimo-v2.6-flash",
60441
+ family: "mimo",
60442
+ reasoning: true,
60443
+ temperature: true,
60444
+ toolCall: true,
60445
+ modalities: {
60446
+ input: [
60447
+ "text",
60448
+ "image",
60449
+ "audio",
60450
+ "video"
60451
+ ],
60452
+ output: [
60453
+ "text"
60454
+ ]
60455
+ },
60456
+ limit: {
60457
+ context: 1048576,
60458
+ input: 1048576,
60459
+ output: 131072
60460
+ }
60331
60461
  }
60332
60462
  }
60333
60463
  };
@@ -69481,97 +69611,6 @@ var init_zod = __esm(() => {
69481
69611
  init_external();
69482
69612
  });
69483
69613
 
69484
- // packages/omo-opencode/src/shared/disabled-providers.ts
69485
- function getModelProvider(model) {
69486
- const slash = model.indexOf("/");
69487
- if (slash <= 0)
69488
- return;
69489
- const provider = model.slice(0, slash).trim();
69490
- return provider || undefined;
69491
- }
69492
- function isProviderDisabled(model, disabled) {
69493
- if (!model || disabled.length === 0)
69494
- return false;
69495
- const provider = getModelProvider(model);
69496
- if (provider === undefined)
69497
- return false;
69498
- const providerLower = provider.toLowerCase();
69499
- return disabled.some((entry) => entry.trim().toLowerCase() === providerLower);
69500
- }
69501
- function filterDisabledProviderModels(models, disabled) {
69502
- if (disabled.length === 0)
69503
- return [...models];
69504
- return models.filter((entry) => {
69505
- const model = typeof entry === "string" ? entry : entry.model;
69506
- return !isProviderDisabled(model, disabled);
69507
- });
69508
- }
69509
- function findFirstAllowedReplacement(chain, disabled) {
69510
- if (!chain)
69511
- return;
69512
- for (const entry of chain) {
69513
- const model = typeof entry === "string" ? entry : entry.model;
69514
- if (!isProviderDisabled(model, disabled))
69515
- return model;
69516
- }
69517
- return;
69518
- }
69519
- function applyToHolder(label, holder, disabled) {
69520
- const normalizedChain = normalizeFallbackModels(holder.fallback_models);
69521
- if (normalizedChain) {
69522
- const filteredChain = filterDisabledProviderModels(normalizedChain, disabled);
69523
- if (filteredChain.length !== normalizedChain.length) {
69524
- log2(`[${HOOK_NAME}] Filtered disabled-provider entries from fallback chain`, {
69525
- label,
69526
- removed: normalizedChain.length - filteredChain.length,
69527
- remaining: filteredChain.length
69528
- });
69529
- }
69530
- holder.fallback_models = filteredChain.length === 0 ? undefined : filteredChain;
69531
- }
69532
- if (typeof holder.model === "string" && isProviderDisabled(holder.model, disabled)) {
69533
- const replacement = findFirstAllowedReplacement(normalizeFallbackModels(holder.fallback_models), disabled);
69534
- if (replacement) {
69535
- log2(`[${HOOK_NAME}] Substituted primary model from fallback chain`, {
69536
- label,
69537
- from: holder.model,
69538
- to: replacement
69539
- });
69540
- holder.model = replacement;
69541
- } else {
69542
- const message = `${label} primary model "${holder.model}" uses a disabled provider and no allowed entry is available in fallback_models. ` + `Either remove the provider from disabled_providers or add an allowed entry to fallback_models.`;
69543
- addConfigLoadError({ path: `disabled_providers:${label}`, error: message });
69544
- log2(`[${HOOK_NAME}] ${message}`, { label, primary: holder.model });
69545
- }
69546
- }
69547
- }
69548
- function applyDisabledProviders(config) {
69549
- const disabled = config.disabled_providers ?? [];
69550
- if (disabled.length === 0)
69551
- return config;
69552
- if (config.agents) {
69553
- for (const [name, agentConfig] of Object.entries(config.agents)) {
69554
- if (agentConfig && typeof agentConfig === "object") {
69555
- applyToHolder(`agents.${name}`, agentConfig, disabled);
69556
- }
69557
- }
69558
- }
69559
- if (config.categories) {
69560
- for (const [name, categoryConfig] of Object.entries(config.categories)) {
69561
- if (categoryConfig && typeof categoryConfig === "object") {
69562
- applyToHolder(`categories.${name}`, categoryConfig, disabled);
69563
- }
69564
- }
69565
- }
69566
- return config;
69567
- }
69568
- var HOOK_NAME = "disabled-providers";
69569
- var init_disabled_providers = __esm(() => {
69570
- init_config_errors();
69571
- init_logger2();
69572
- init_model_resolver2();
69573
- });
69574
-
69575
69614
  // packages/omo-config-core/src/schema/reasoning-vocabulary.ts
69576
69615
  function isReasoningLevel(value) {
69577
69616
  return REASONING_LEVEL_SET2.has(value);
@@ -69663,7 +69702,6 @@ function normalizeLegacyModelFields(entry) {
69663
69702
  delete normalized["reasoningEffort"];
69664
69703
  delete normalized["thinking"];
69665
69704
  delete normalized["textVerbosity"];
69666
- delete normalized["maxTokens"];
69667
69705
  delete normalized["providerOptions"];
69668
69706
  if (typeof entry["model"] === "string")
69669
69707
  normalized["model"] = canonicalModelString(entry["model"]);
@@ -69681,10 +69719,15 @@ function normalizeLegacyModelFields(entry) {
69681
69719
  providerOptions["textVerbosity"] = entry["textVerbosity"];
69682
69720
  if (Object.keys(providerOptions).length > 0)
69683
69721
  normalized["provider_options"] = providerOptions;
69684
- if (entry["max_tokens"] !== undefined)
69722
+ if (entry["max_tokens"] !== undefined) {
69685
69723
  normalized["max_tokens"] = entry["max_tokens"];
69686
- else if (entry["maxTokens"] !== undefined)
69724
+ if (entry["maxTokens"] === undefined || typeof entry["maxTokens"] === "number") {
69725
+ delete normalized["maxTokens"];
69726
+ }
69727
+ } else if (typeof entry["maxTokens"] === "number") {
69687
69728
  normalized["max_tokens"] = entry["maxTokens"];
69729
+ delete normalized["maxTokens"];
69730
+ }
69688
69731
  return normalized;
69689
69732
  }
69690
69733
  var OmoThinkingConfigSchema, OmoReasoningEffortSchema, OmoLegacyFallbackModelObjectInputSchema, OmoFallbackModelObjectSchema, OmoFallbackModelsSchema;
@@ -69724,12 +69767,25 @@ var init_fallback_models = __esm(() => {
69724
69767
  function isRecord5(value) {
69725
69768
  return typeof value === "object" && value !== null && !Array.isArray(value);
69726
69769
  }
69727
- var OmoAgentModelEntrySchema, OmoAgentDefInputSchema, OmoAgentDefSchema, OmoAgentsConfigSchema;
69770
+ var OmoAgentModelEntrySchema, PermissionValueSchema, BashPermissionSchema, OmoAgentPermissionSchema, OmoAgentDefInputSchema, OmoAgentDefSchema, OmoAgentsConfigSchema;
69728
69771
  var init_agent = __esm(() => {
69729
69772
  init_zod();
69730
69773
  init_fallback_models();
69731
69774
  init_model_ref();
69732
69775
  OmoAgentModelEntrySchema = union([string2(), OmoFallbackModelObjectSchema]);
69776
+ PermissionValueSchema = _enum(["ask", "allow", "deny"]);
69777
+ BashPermissionSchema = union([
69778
+ PermissionValueSchema,
69779
+ record(string2(), PermissionValueSchema)
69780
+ ]);
69781
+ OmoAgentPermissionSchema = object({
69782
+ edit: PermissionValueSchema.optional(),
69783
+ bash: BashPermissionSchema.optional(),
69784
+ webfetch: PermissionValueSchema.optional(),
69785
+ task: PermissionValueSchema.optional(),
69786
+ doom_loop: PermissionValueSchema.optional(),
69787
+ external_directory: PermissionValueSchema.optional()
69788
+ }).catchall(PermissionValueSchema.optional());
69733
69789
  OmoAgentDefInputSchema = object({
69734
69790
  description: string2().optional(),
69735
69791
  prompt: string2().optional(),
@@ -69746,7 +69802,9 @@ var init_agent = __esm(() => {
69746
69802
  disallowed_tools: array(string2()).optional(),
69747
69803
  max_turns: number2().int().nonnegative().optional(),
69748
69804
  temperature: number2().min(0).max(2).optional(),
69749
- disable: boolean2().optional()
69805
+ disable: boolean2().optional(),
69806
+ permission: OmoAgentPermissionSchema.optional(),
69807
+ prompt_append: string2().optional()
69750
69808
  }).strict();
69751
69809
  OmoAgentDefSchema = preprocess((value) => isRecord5(value) ? normalizeLegacyModelFields(value) : value, OmoAgentDefInputSchema);
69752
69810
  OmoAgentsConfigSchema = record(string2(), OmoAgentDefSchema);
@@ -70526,6 +70584,64 @@ var init_schema = __esm(() => {
70526
70584
  init_computer();
70527
70585
  });
70528
70586
 
70587
+ // packages/omo-config-core/src/loader/types.ts
70588
+ import { existsSync as existsSync6, lstatSync, readFileSync as readFileSync6, realpathSync as realpathSync4 } from "fs";
70589
+ var MERGED_OMO_CONFIG_PATH = "(merged omo config)", DEFAULT_READ_FILE_SYSTEM;
70590
+ var init_types3 = __esm(() => {
70591
+ DEFAULT_READ_FILE_SYSTEM = {
70592
+ existsSync: existsSync6,
70593
+ lstatSync,
70594
+ readFileSync: readFileSync6,
70595
+ realpathSync: realpathSync4
70596
+ };
70597
+ });
70598
+
70599
+ // packages/omo-config-core/src/loader/diagnostic-lines.ts
70600
+ function displayOmoConfigPath(path, homeDir) {
70601
+ if (path === MERGED_OMO_CONFIG_PATH)
70602
+ return "merged config";
70603
+ if (homeDir === undefined || homeDir.length === 0)
70604
+ return path;
70605
+ const slashed = (value) => value.replaceAll("\\", "/");
70606
+ const home = slashed(homeDir).replace(/\/+$/, "");
70607
+ const file = slashed(path);
70608
+ const fold = (value) => /^[A-Za-z]:\//.test(home) ? value.toLowerCase() : value;
70609
+ if (fold(file) === fold(home))
70610
+ return "~";
70611
+ return fold(file).startsWith(`${fold(home)}/`) ? `~${file.slice(home.length)}` : path;
70612
+ }
70613
+ function notLoadedReason(diagnostic) {
70614
+ switch (diagnostic.kind) {
70615
+ case "parse":
70616
+ return "JSONC parse error";
70617
+ case "read":
70618
+ return "unreadable";
70619
+ default:
70620
+ return diagnostic.issuePaths === undefined || diagnostic.issuePaths.length === 0 ? "invalid config" : `invalid: ${diagnostic.issuePaths.join(", ")}`;
70621
+ }
70622
+ }
70623
+ function omoConfigDiagnosticLines(diagnostics, options = {}) {
70624
+ return diagnostics.flatMap((diagnostic) => {
70625
+ const file = displayOmoConfigPath(diagnostic.path, options.homeDir);
70626
+ switch (diagnostic.kind) {
70627
+ case "invalid-value":
70628
+ return (diagnostic.issuePaths ?? []).map((key) => `config: ${file}: ${key} ignored (invalid value)`);
70629
+ case "unknown-keys":
70630
+ return (diagnostic.issuePaths ?? []).map((key) => `config: ${file}: ${key} ignored (unknown key)`);
70631
+ case "parse":
70632
+ case "read":
70633
+ case "validation":
70634
+ return diagnostic.path === MERGED_OMO_CONFIG_PATH ? [`config: ${file}: reset to defaults (${notLoadedReason(diagnostic)})`] : [`config: ${file}: not loaded (${notLoadedReason(diagnostic)})`];
70635
+ case "deprecated-keys":
70636
+ case "profile":
70637
+ return [];
70638
+ }
70639
+ });
70640
+ }
70641
+ var init_diagnostic_lines = __esm(() => {
70642
+ init_types3();
70643
+ });
70644
+
70529
70645
  // packages/omo-config-core/src/loader/disabled-skills.ts
70530
70646
  var init_disabled_skills = () => {};
70531
70647
 
@@ -70565,24 +70681,265 @@ var init_merge = __esm(() => {
70565
70681
  DANGEROUS_KEYS2 = new Set(["__proto__", "constructor", "prototype"]);
70566
70682
  });
70567
70683
 
70684
+ // packages/omo-config-core/src/loader/prune-invalid-leaves.ts
70685
+ function isContainer(value) {
70686
+ return typeof value === "object" && value !== null;
70687
+ }
70688
+ function childOf(container, segment) {
70689
+ return Array.isArray(container) ? container[Number(segment)] : container[String(segment)];
70690
+ }
70691
+ function hasChild(container, segment) {
70692
+ if (Array.isArray(container)) {
70693
+ const index = Number(segment);
70694
+ return Number.isInteger(index) && index >= 0 && index < container.length;
70695
+ }
70696
+ return Object.hasOwn(container, String(segment));
70697
+ }
70698
+ function segmentsOf(path) {
70699
+ return path.map((segment) => typeof segment === "number" ? segment : String(segment));
70700
+ }
70701
+ function targetPaths(root, issue) {
70702
+ const base = segmentsOf(issue.path);
70703
+ if (issue.code === "unrecognized_keys")
70704
+ return issue.keys.map((key) => [...base, key]);
70705
+ let node = root;
70706
+ let depth = 0;
70707
+ for (const segment of base) {
70708
+ if (!isContainer(node) || !hasChild(node, segment))
70709
+ break;
70710
+ node = childOf(node, segment);
70711
+ depth += 1;
70712
+ }
70713
+ return [base.slice(0, depth)];
70714
+ }
70715
+ function isAncestor(ancestor, path) {
70716
+ return ancestor.length < path.length && ancestor.every((segment, index) => segment === path[index]);
70717
+ }
70718
+ function compareDescending(left, right) {
70719
+ const length = Math.min(left.length, right.length);
70720
+ for (let index = 0;index < length; index += 1) {
70721
+ const a = left[index];
70722
+ const b = right[index];
70723
+ if (a === b)
70724
+ continue;
70725
+ if (typeof a === "number" && typeof b === "number")
70726
+ return b - a;
70727
+ return String(b).localeCompare(String(a));
70728
+ }
70729
+ return right.length - left.length;
70730
+ }
70731
+ function removeChild(container, segment) {
70732
+ if (Array.isArray(container))
70733
+ container.splice(Number(segment), 1);
70734
+ else
70735
+ delete container[String(segment)];
70736
+ }
70737
+ function isEmpty(container) {
70738
+ return Array.isArray(container) ? container.length === 0 : Object.keys(container).length === 0;
70739
+ }
70740
+ function removePathAndEmptiedAncestors(root, path) {
70741
+ const chain = [root];
70742
+ for (const segment of path.slice(0, -1)) {
70743
+ const current = chain[chain.length - 1];
70744
+ if (current === undefined)
70745
+ return;
70746
+ const next = childOf(current, segment);
70747
+ if (!isContainer(next))
70748
+ return;
70749
+ chain.push(next);
70750
+ }
70751
+ for (let depth = path.length - 1;depth >= 0; depth -= 1) {
70752
+ const container = chain[depth];
70753
+ const segment = path[depth];
70754
+ if (container === undefined || segment === undefined)
70755
+ return;
70756
+ removeChild(container, segment);
70757
+ if (depth === 0 || !isEmpty(container))
70758
+ return;
70759
+ }
70760
+ }
70761
+ function prunePass(root, issues) {
70762
+ const byKey = new Map;
70763
+ for (const issue of issues) {
70764
+ for (const path of targetPaths(root, issue)) {
70765
+ if (path.length === 0)
70766
+ return null;
70767
+ const key = path.map((segment) => String(segment)).join(".");
70768
+ if (!byKey.has(key))
70769
+ byKey.set(key, { key, message: issue.message, path });
70770
+ }
70771
+ }
70772
+ const targets = [...byKey.values()];
70773
+ const outermost = targets.filter((target) => !targets.some((other) => isAncestor(other.path, target.path))).sort((left, right) => compareDescending(left.path, right.path));
70774
+ const next = structuredClone(root);
70775
+ for (const target of outermost)
70776
+ removePathAndEmptiedAncestors(next, target.path);
70777
+ return { dropped: outermost, next };
70778
+ }
70779
+ function pruneInvalidConfigPaths(config, issues, validate, maxPasses = MAX_PRUNE_PASSES) {
70780
+ const dropped = [];
70781
+ let current = config;
70782
+ let pending = issues;
70783
+ for (let pass = 0;pass < maxPasses; pass += 1) {
70784
+ const step = prunePass(current, pending);
70785
+ if (step === null || step.dropped.length === 0)
70786
+ return { ok: false, dropped };
70787
+ dropped.push(...step.dropped);
70788
+ current = step.next;
70789
+ if (Object.keys(current).length === 0)
70790
+ return { ok: false, dropped };
70791
+ const validation = validate(current);
70792
+ if (validation.success)
70793
+ return { ok: true, config: current, dropped };
70794
+ pending = validation.issues;
70795
+ }
70796
+ return { ok: false, dropped };
70797
+ }
70798
+ var MAX_PRUNE_PASSES = 32;
70799
+
70800
+ // packages/omo-config-core/src/loader/layer-validation.ts
70801
+ function validationDiagnostic(path, issues) {
70802
+ const issuePaths = issues.map((issue) => issue.path.map((segment) => String(segment)).join("."));
70803
+ return {
70804
+ kind: "validation",
70805
+ message: `Invalid omo config at ${path}: ${issuePaths.join(", ")}`,
70806
+ path,
70807
+ issuePaths
70808
+ };
70809
+ }
70810
+ function invalidValueDiagnostics(path, dropped) {
70811
+ return dropped.map((entry) => ({
70812
+ kind: "invalid-value",
70813
+ message: `Ignored invalid value in ${path}: ${entry.key}: ${entry.message}`,
70814
+ path,
70815
+ issuePaths: [entry.key]
70816
+ }));
70817
+ }
70818
+ function unrecognizedKeyIssues(issues) {
70819
+ return issues.flatMap((issue) => issue.code === "unrecognized_keys" ? [{ keys: issue.keys, path: issue.path.map((segment) => String(segment)) }] : []);
70820
+ }
70821
+ function isRecord11(value) {
70822
+ return typeof value === "object" && value !== null && !Array.isArray(value);
70823
+ }
70824
+ function sanitizeUnsafeKeys(value, path = []) {
70825
+ if (Array.isArray(value)) {
70826
+ const issues = [];
70827
+ const sanitized = value.map((entry, index) => {
70828
+ const nested = sanitizeUnsafeKeys(entry, [...path, String(index)]);
70829
+ issues.push(...nested.issues);
70830
+ return nested.value;
70831
+ });
70832
+ return { issues, value: sanitized };
70833
+ }
70834
+ if (!isRecord11(value))
70835
+ return { issues: [], value };
70836
+ const issues = [];
70837
+ if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
70838
+ issues.push({ keys: ["__proto__"], path });
70839
+ }
70840
+ const sanitized = {};
70841
+ for (const [key, entry] of Object.entries(value)) {
70842
+ if (isUnsafeObjectKey2(key)) {
70843
+ issues.push({ keys: [key], path });
70844
+ continue;
70845
+ }
70846
+ const nested = sanitizeUnsafeKeys(entry, [...path, key]);
70847
+ issues.push(...nested.issues);
70848
+ sanitized[key] = nested.value;
70849
+ }
70850
+ return { issues, value: sanitized };
70851
+ }
70852
+ function containerAt(record, path) {
70853
+ let node = record;
70854
+ for (const segment of path) {
70855
+ if (Array.isArray(node)) {
70856
+ const index = Number(segment);
70857
+ if (!Number.isInteger(index) || index < 0 || index >= node.length)
70858
+ return null;
70859
+ node = node[index];
70860
+ } else if (isRecord11(node) && Object.hasOwn(node, segment)) {
70861
+ node = node[segment];
70862
+ } else {
70863
+ return null;
70864
+ }
70865
+ }
70866
+ return isRecord11(node) ? node : null;
70867
+ }
70868
+ function stripUnrecognizedKeys(record, issues) {
70869
+ const stripped = structuredClone(record);
70870
+ const issuePaths = [];
70871
+ for (const issue of issues) {
70872
+ const container = containerAt(stripped, issue.path);
70873
+ if (container === null)
70874
+ continue;
70875
+ for (const key of issue.keys) {
70876
+ delete container[key];
70877
+ issuePaths.push([...issue.path, key].join("."));
70878
+ }
70879
+ }
70880
+ return { issuePaths, stripped };
70881
+ }
70882
+ function toRecord(value) {
70883
+ if (value === null || typeof value !== "object" || Array.isArray(value))
70884
+ return null;
70885
+ const record = {};
70886
+ for (const [key, entry] of Object.entries(value)) {
70887
+ record[key] = entry;
70888
+ }
70889
+ return record;
70890
+ }
70891
+ function validateConfigLayer(path, data) {
70892
+ const sanitized = sanitizeUnsafeKeys(data);
70893
+ const record = toRecord(sanitized.value);
70894
+ const unsafeIssuePaths = sanitized.issues.flatMap((issue) => issue.keys.map((key) => [...issue.path, key].join(".")));
70895
+ const unsafeDiagnostics = unsafeIssuePaths.length === 0 ? [] : [{ kind: "unknown-keys", message: `Ignored unknown keys in ${path}: ${unsafeIssuePaths.join(", ")}`, path, issuePaths: unsafeIssuePaths }];
70896
+ const validation = OmoConfigLayerSchema.safeParse(sanitized.value);
70897
+ if (validation.success) {
70898
+ if (record !== null)
70899
+ return { loaded: true, diagnostics: unsafeDiagnostics, value: record };
70900
+ return {
70901
+ loaded: false,
70902
+ diagnostics: [{ kind: "validation", message: `Invalid omo config at ${path}: root must be an object`, path }]
70903
+ };
70904
+ }
70905
+ const rejected = { loaded: false, diagnostics: [validationDiagnostic(path, validation.error.issues)] };
70906
+ const unknownIssues = unrecognizedKeyIssues(validation.error.issues);
70907
+ if (record === null)
70908
+ return rejected;
70909
+ let candidate = record;
70910
+ let issues = validation.error.issues;
70911
+ const diagnostics = [...unsafeDiagnostics];
70912
+ if (unknownIssues.length > 0) {
70913
+ const { issuePaths, stripped } = stripUnrecognizedKeys(record, unknownIssues);
70914
+ if (issuePaths.length > 0) {
70915
+ diagnostics.push({ kind: "unknown-keys", message: `Ignored unknown keys in ${path}: ${issuePaths.join(", ")}`, path, issuePaths });
70916
+ }
70917
+ const strippedValidation = validateLayerRecord(stripped);
70918
+ if (strippedValidation.success)
70919
+ return { loaded: true, diagnostics, value: stripped };
70920
+ candidate = stripped;
70921
+ issues = strippedValidation.issues;
70922
+ }
70923
+ const pruned = pruneInvalidConfigPaths(candidate, issues, validateLayerRecord);
70924
+ if (!pruned.ok)
70925
+ return rejected;
70926
+ return { loaded: true, diagnostics: [...diagnostics, ...invalidValueDiagnostics(path, pruned.dropped)], value: pruned.config };
70927
+ }
70928
+ var validateLayerRecord = (record) => {
70929
+ const parsed = OmoConfigLayerSchema.safeParse(record);
70930
+ return parsed.success ? { success: true } : { success: false, issues: parsed.error.issues };
70931
+ };
70932
+ var init_layer_validation = __esm(() => {
70933
+ init_schema();
70934
+ init_merge();
70935
+ });
70936
+
70568
70937
  // packages/omo-config-core/src/internal/posix-path.ts
70569
70938
  function toPosixPath(path) {
70570
70939
  return path.split("\\").join("/");
70571
70940
  }
70572
70941
  var init_posix_path = () => {};
70573
70942
 
70574
- // packages/omo-config-core/src/loader/types.ts
70575
- import { existsSync as existsSync6, lstatSync, readFileSync as readFileSync6, realpathSync as realpathSync4 } from "fs";
70576
- var DEFAULT_READ_FILE_SYSTEM;
70577
- var init_types3 = __esm(() => {
70578
- DEFAULT_READ_FILE_SYSTEM = {
70579
- existsSync: existsSync6,
70580
- lstatSync,
70581
- readFileSync: readFileSync6,
70582
- realpathSync: realpathSync4
70583
- };
70584
- });
70585
-
70586
70943
  // packages/omo-config-core/src/loader/paths.ts
70587
70944
  import { userInfo } from "os";
70588
70945
  import { dirname as dirname4, join as join12, posix as posix2, resolve as resolve4 } from "path";
@@ -70683,7 +71040,7 @@ function resolveOmoProfileName(options = {}) {
70683
71040
  const env = options.env ?? process.env;
70684
71041
  return profileName(options.profile) ?? profileName(env["OMO_PROFILE"]) ?? profileName(env["OCX_PROFILE"]) ?? profileNameFromOpenCodeConfigDir(env["OPENCODE_CONFIG_DIR"]);
70685
71042
  }
70686
- function toRecord(value) {
71043
+ function toRecord2(value) {
70687
71044
  if (value === null || typeof value !== "object" || Array.isArray(value))
70688
71045
  return;
70689
71046
  return Object.fromEntries(Object.entries(value));
@@ -70704,13 +71061,13 @@ function harnessLayer(config, harness) {
70704
71061
  const legacyKeys = Object.entries(OMO_CONFIG_LEGACY_HARNESS_ALIASES).filter(([, target]) => target === canonical).map(([legacy]) => harnessBlockKey(legacy));
70705
71062
  let layer = {};
70706
71063
  for (const key of [...legacyKeys, harnessBlockKey(canonical)]) {
70707
- layer = mergeOmoConfigRecords(layer, toRecord(config[key]) ?? {});
71064
+ layer = mergeOmoConfigRecords(layer, toRecord2(config[key]) ?? {});
70708
71065
  }
70709
71066
  return layer;
70710
71067
  }
70711
71068
  function resolveOmoConfigView(options) {
70712
- const profiles = toRecord(options.config["profiles"]);
70713
- const profile = options.profile === undefined ? undefined : toRecord(profiles?.[options.profile]);
71069
+ const profiles = toRecord2(options.config["profiles"]);
71070
+ const profile = options.profile === undefined ? undefined : toRecord2(profiles?.[options.profile]);
70714
71071
  const diagnostics = profile === undefined && options.profile !== undefined ? [{
70715
71072
  kind: "profile",
70716
71073
  message: `Activated omo profile "${options.profile}" does not exist; using the base configuration`,
@@ -70765,70 +71122,9 @@ function stripResolutionControlKeys(config) {
70765
71122
  } = config;
70766
71123
  return resolved;
70767
71124
  }
70768
- function validationDiagnostic(path, issues) {
70769
- const issuePaths = issues.map((issue) => issue.path.map((segment) => String(segment)).join("."));
70770
- return {
70771
- kind: "validation",
70772
- message: `Invalid omo config at ${path}: ${issuePaths.join(", ")}`,
70773
- path,
70774
- issuePaths
70775
- };
70776
- }
70777
- function unrecognizedKeyIssues(issues) {
70778
- return issues.flatMap((issue) => issue.code === "unrecognized_keys" ? [{ keys: issue.keys, path: issue.path.map((segment) => String(segment)) }] : []);
70779
- }
70780
- function hasUnsafeUnrecognizedKey(issues) {
70781
- return issues.some((issue) => issue.keys.some((key) => isUnsafeObjectKey2(key)));
70782
- }
70783
- function hasTamperedPrototype(value) {
70784
- if (Array.isArray(value))
70785
- return value.some((entry) => hasTamperedPrototype(entry));
70786
- if (!isRecord11(value))
70787
- return false;
70788
- const prototype = Object.getPrototypeOf(value);
70789
- if (prototype !== Object.prototype && prototype !== null)
70790
- return true;
70791
- return Object.values(value).some((entry) => hasTamperedPrototype(entry));
70792
- }
70793
- function isRecord11(value) {
70794
- return typeof value === "object" && value !== null && !Array.isArray(value);
70795
- }
70796
- function containerAt(record, path) {
70797
- let container = record;
70798
- for (const segment of path) {
70799
- const next = container[segment];
70800
- if (!isRecord11(next))
70801
- return null;
70802
- container = next;
70803
- }
70804
- return container;
70805
- }
70806
- function stripUnrecognizedKeys(record, issues) {
70807
- const stripped = structuredClone(record);
70808
- const issuePaths = [];
70809
- for (const issue of issues) {
70810
- const container = containerAt(stripped, issue.path);
70811
- if (container === null)
70812
- continue;
70813
- for (const key of issue.keys) {
70814
- delete container[key];
70815
- issuePaths.push([...issue.path, key].join("."));
70816
- }
70817
- }
70818
- return { issuePaths, stripped };
70819
- }
70820
- function toRecord2(value) {
70821
- if (value === null || typeof value !== "object" || Array.isArray(value))
70822
- return null;
70823
- const record = {};
70824
- for (const [key, entry] of Object.entries(value)) {
70825
- record[key] = entry;
70826
- }
70827
- return record;
70828
- }
70829
71125
  function readConfigSource(path, scope, fileSystem) {
70830
71126
  if (!fileSystem.existsSync(path)) {
70831
- return { source: { exists: false, loaded: false, path, scope } };
71127
+ return { diagnostics: [], source: { exists: false, loaded: false, path, scope } };
70832
71128
  }
70833
71129
  let content;
70834
71130
  try {
@@ -70836,68 +71132,23 @@ function readConfigSource(path, scope, fileSystem) {
70836
71132
  } catch (error) {
70837
71133
  const message = error instanceof Error ? error.message : String(error);
70838
71134
  return {
70839
- diagnostic: { kind: "read", message: `Failed to read ${path}: ${message}`, path },
71135
+ diagnostics: [{ kind: "read", message: `Failed to read ${path}: ${message}`, path }],
70840
71136
  source: { exists: true, loaded: false, path, scope }
70841
71137
  };
70842
71138
  }
70843
71139
  const parsed = parseJsoncSafe2(content);
70844
71140
  if (parsed.errors.length > 0) {
70845
71141
  return {
70846
- diagnostic: {
71142
+ diagnostics: [{
70847
71143
  kind: "parse",
70848
71144
  message: `JSONC parse error in ${path}: ${parsed.errors.map((error) => error.message).join(", ")}`,
70849
71145
  path
70850
- },
70851
- source: { exists: true, loaded: false, path, scope }
70852
- };
70853
- }
70854
- if (hasTamperedPrototype(parsed.data)) {
70855
- return {
70856
- diagnostic: { kind: "validation", message: `Invalid omo config at ${path}: "__proto__" member is not allowed`, path },
70857
- source: { exists: true, loaded: false, path, scope }
70858
- };
70859
- }
70860
- const parsedRecord = toRecord2(parsed.data);
70861
- const validation = OmoConfigLayerSchema.safeParse(parsed.data);
70862
- if (!validation.success) {
70863
- const unrecognized = unrecognizedKeyIssues(validation.error.issues);
70864
- if (hasUnsafeUnrecognizedKey(unrecognized)) {
70865
- return {
70866
- diagnostic: validationDiagnostic(path, validation.error.issues),
70867
- source: { exists: true, loaded: false, path, scope }
70868
- };
70869
- }
70870
- const rejected = {
70871
- diagnostic: validationDiagnostic(path, validation.error.issues),
71146
+ }],
70872
71147
  source: { exists: true, loaded: false, path, scope }
70873
71148
  };
70874
- const unknownIssues = unrecognizedKeyIssues(validation.error.issues);
70875
- if (parsedRecord === null || unknownIssues.length === 0)
70876
- return rejected;
70877
- const { issuePaths, stripped } = stripUnrecognizedKeys(parsedRecord, unknownIssues);
70878
- if (!OmoConfigLayerSchema.safeParse(stripped).success)
70879
- return rejected;
70880
- return {
70881
- diagnostic: {
70882
- kind: "unknown-keys",
70883
- message: `Ignored unknown keys in ${path}: ${issuePaths.join(", ")}`,
70884
- path,
70885
- issuePaths
70886
- },
70887
- source: { exists: true, loaded: true, path, scope },
70888
- value: stripped
70889
- };
70890
71149
  }
70891
- if (parsedRecord === null) {
70892
- return {
70893
- diagnostic: { kind: "validation", message: `Invalid omo config at ${path}: root must be an object`, path },
70894
- source: { exists: true, loaded: false, path, scope }
70895
- };
70896
- }
70897
- return {
70898
- source: { exists: true, loaded: true, path, scope },
70899
- value: parsedRecord
70900
- };
71150
+ const layer = validateConfigLayer(path, parsed.data);
71151
+ 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 } };
70901
71152
  }
70902
71153
  function legacyCategoryDiagnostic(path, renames) {
70903
71154
  const detail = renames.map((rename) => rename.dropped ? `${rename.path} ignored because ${rename.canonical} is also configured` : `${rename.path} renamed to ${rename.canonical}`).join(", ");
@@ -70932,8 +71183,7 @@ function loadOmoConfig(options = {}) {
70932
71183
  })) {
70933
71184
  const loaded = readConfigSource(candidate.path, candidate.scope, fileSystem);
70934
71185
  sources.push(loaded.source);
70935
- if (loaded.diagnostic !== undefined)
70936
- diagnostics.push(loaded.diagnostic);
71186
+ diagnostics.push(...loaded.diagnostics);
70937
71187
  if (loaded.value !== undefined) {
70938
71188
  const canonicalized = canonicalizeLegacyCategoryNames(loaded.value);
70939
71189
  if (canonicalized.renames.length > 0) {
@@ -70956,7 +71206,8 @@ function loadOmoConfig(options = {}) {
70956
71206
  ...options.harness === undefined ? {} : { harness: options.harness },
70957
71207
  ...requestedProfile === undefined ? {} : { profile: requestedProfile }
70958
71208
  });
70959
- const finalConfig = OmoConfigSchema.safeParse(mergeOmoConfigRecords(DEFAULT_RAW_CONFIG, resolved.config));
71209
+ const finalInput = mergeOmoConfigRecords(DEFAULT_RAW_CONFIG, resolved.config);
71210
+ const finalConfig = OmoConfigSchema.safeParse(finalInput);
70960
71211
  if (finalConfig.success) {
70961
71212
  return {
70962
71213
  config: stripResolutionControlKeys(finalConfig.data),
@@ -70966,9 +71217,22 @@ function loadOmoConfig(options = {}) {
70966
71217
  sources
70967
71218
  };
70968
71219
  }
71220
+ const pruned = pruneInvalidConfigPaths(finalInput, finalConfig.error.issues, (record) => {
71221
+ const parsed = OmoConfigSchema.safeParse(record);
71222
+ return parsed.success ? { success: true } : { success: false, issues: parsed.error.issues };
71223
+ });
71224
+ if (pruned.ok) {
71225
+ return {
71226
+ config: stripResolutionControlKeys(OmoConfigSchema.parse(pruned.config)),
71227
+ diagnostics: [...diagnostics, ...resolved.diagnostics, ...invalidValueDiagnostics(MERGED_OMO_CONFIG_PATH, pruned.dropped)],
71228
+ layers,
71229
+ ...resolved.profile === undefined ? {} : { profile: resolved.profile },
71230
+ sources
71231
+ };
71232
+ }
70969
71233
  return {
70970
71234
  config: stripResolutionControlKeys(OmoConfigSchema.parse(DEFAULT_RAW_CONFIG)),
70971
- diagnostics: [...diagnostics, ...resolved.diagnostics, validationDiagnostic("(merged omo config)", finalConfig.error.issues)],
71235
+ diagnostics: [...diagnostics, ...resolved.diagnostics, validationDiagnostic(MERGED_OMO_CONFIG_PATH, finalConfig.error.issues)],
70972
71236
  layers,
70973
71237
  ...resolved.profile === undefined ? {} : { profile: resolved.profile },
70974
71238
  sources
@@ -70978,6 +71242,7 @@ var DEFAULT_RAW_CONFIG;
70978
71242
  var init_loader2 = __esm(() => {
70979
71243
  init_main();
70980
71244
  init_schema();
71245
+ init_layer_validation();
70981
71246
  init_merge();
70982
71247
  init_paths();
70983
71248
  init_resolution();
@@ -70992,6 +71257,7 @@ var init_loader2 = __esm(() => {
70992
71257
 
70993
71258
  // packages/omo-config-core/src/loader/index.ts
70994
71259
  var init_loader3 = __esm(() => {
71260
+ init_diagnostic_lines();
70995
71261
  init_disabled_skills();
70996
71262
  init_loader2();
70997
71263
  init_merge();
@@ -71285,6 +71551,97 @@ var init_src4 = __esm(() => {
71285
71551
  init_migration3();
71286
71552
  });
71287
71553
 
71554
+ // packages/omo-opencode/src/shared/disabled-providers.ts
71555
+ function getModelProvider(model) {
71556
+ const slash = model.indexOf("/");
71557
+ if (slash <= 0)
71558
+ return;
71559
+ const provider = model.slice(0, slash).trim();
71560
+ return provider || undefined;
71561
+ }
71562
+ function isProviderDisabled(model, disabled) {
71563
+ if (!model || disabled.length === 0)
71564
+ return false;
71565
+ const provider = getModelProvider(model);
71566
+ if (provider === undefined)
71567
+ return false;
71568
+ const providerLower = provider.toLowerCase();
71569
+ return disabled.some((entry) => entry.trim().toLowerCase() === providerLower);
71570
+ }
71571
+ function filterDisabledProviderModels(models, disabled) {
71572
+ if (disabled.length === 0)
71573
+ return [...models];
71574
+ return models.filter((entry) => {
71575
+ const model = typeof entry === "string" ? entry : entry.model;
71576
+ return !isProviderDisabled(model, disabled);
71577
+ });
71578
+ }
71579
+ function findFirstAllowedReplacement(chain, disabled) {
71580
+ if (!chain)
71581
+ return;
71582
+ for (const entry of chain) {
71583
+ const model = typeof entry === "string" ? entry : entry.model;
71584
+ if (!isProviderDisabled(model, disabled))
71585
+ return model;
71586
+ }
71587
+ return;
71588
+ }
71589
+ function applyToHolder(label, holder, disabled) {
71590
+ const normalizedChain = normalizeFallbackModels(holder.fallback_models);
71591
+ if (normalizedChain) {
71592
+ const filteredChain = filterDisabledProviderModels(normalizedChain, disabled);
71593
+ if (filteredChain.length !== normalizedChain.length) {
71594
+ log2(`[${HOOK_NAME}] Filtered disabled-provider entries from fallback chain`, {
71595
+ label,
71596
+ removed: normalizedChain.length - filteredChain.length,
71597
+ remaining: filteredChain.length
71598
+ });
71599
+ }
71600
+ holder.fallback_models = filteredChain.length === 0 ? undefined : filteredChain;
71601
+ }
71602
+ if (typeof holder.model === "string" && isProviderDisabled(holder.model, disabled)) {
71603
+ const replacement = findFirstAllowedReplacement(normalizeFallbackModels(holder.fallback_models), disabled);
71604
+ if (replacement) {
71605
+ log2(`[${HOOK_NAME}] Substituted primary model from fallback chain`, {
71606
+ label,
71607
+ from: holder.model,
71608
+ to: replacement
71609
+ });
71610
+ holder.model = replacement;
71611
+ } else {
71612
+ const message = `${label} primary model "${holder.model}" uses a disabled provider and no allowed entry is available in fallback_models. ` + `Either remove the provider from disabled_providers or add an allowed entry to fallback_models.`;
71613
+ addConfigLoadError({ path: `disabled_providers:${label}`, error: message });
71614
+ log2(`[${HOOK_NAME}] ${message}`, { label, primary: holder.model });
71615
+ }
71616
+ }
71617
+ }
71618
+ function applyDisabledProviders(config) {
71619
+ const disabled = config.disabled_providers ?? [];
71620
+ if (disabled.length === 0)
71621
+ return config;
71622
+ if (config.agents) {
71623
+ for (const [name, agentConfig] of Object.entries(config.agents)) {
71624
+ if (agentConfig && typeof agentConfig === "object") {
71625
+ applyToHolder(`agents.${name}`, agentConfig, disabled);
71626
+ }
71627
+ }
71628
+ }
71629
+ if (config.categories) {
71630
+ for (const [name, categoryConfig] of Object.entries(config.categories)) {
71631
+ if (categoryConfig && typeof categoryConfig === "object") {
71632
+ applyToHolder(`categories.${name}`, categoryConfig, disabled);
71633
+ }
71634
+ }
71635
+ }
71636
+ return config;
71637
+ }
71638
+ var HOOK_NAME = "disabled-providers";
71639
+ var init_disabled_providers = __esm(() => {
71640
+ init_config_errors();
71641
+ init_logger2();
71642
+ init_model_resolver2();
71643
+ });
71644
+
71288
71645
  // packages/omo-opencode/src/plugin-config/omo-config-chain.ts
71289
71646
  function block(config) {
71290
71647
  const value = config["[opencode]"];
@@ -71308,11 +71665,11 @@ function baseView(config) {
71308
71665
  }
71309
71666
  return result;
71310
71667
  }
71311
- function appendViews(views, layers, select) {
71668
+ function appendViews(views, layers, select, keyPrefix) {
71312
71669
  for (const layer of layers) {
71313
71670
  const config = select(layer.config);
71314
71671
  if (Object.keys(config).length > 0)
71315
- views.push({ config, path: layer.source.path });
71672
+ views.push({ config, keyPrefix, path: layer.source.path });
71316
71673
  }
71317
71674
  }
71318
71675
  function recordFields(value, fields) {
@@ -71341,10 +71698,21 @@ function modelInput(view) {
71341
71698
  };
71342
71699
  }
71343
71700
  function modelView(view) {
71344
- const parsed = OmoConfigSchema.safeParse(modelInput(view));
71345
- if (!parsed.success)
71701
+ const input = modelInput(view);
71702
+ const parsed = OmoConfigSchema.safeParse(input);
71703
+ const modelConfig = parsed.success ? parsed.data : (() => {
71704
+ const pruned = pruneInvalidConfigPaths(input, parsed.error.issues, (record) => {
71705
+ const validation = OmoConfigSchema.safeParse(record);
71706
+ return validation.success ? { success: true } : { success: false, issues: validation.error.issues };
71707
+ });
71708
+ if (!pruned.ok)
71709
+ return;
71710
+ const validation = OmoConfigSchema.safeParse(pruned.config);
71711
+ return validation.success ? validation.data : undefined;
71712
+ })();
71713
+ if (modelConfig === undefined)
71346
71714
  return { config: {}, diagnostics: [] };
71347
- const resolved = resolveModelReferences(parsed.data);
71715
+ const resolved = resolveModelReferences(modelConfig);
71348
71716
  return {
71349
71717
  config: {
71350
71718
  ...resolved.view.agents === undefined ? {} : { agents: resolved.view.agents },
@@ -71363,14 +71731,15 @@ function loadOmoOpenCodeConfigChain(directory, environment = process.env) {
71363
71731
  });
71364
71732
  const views = [];
71365
71733
  const selectedProfile = loaded.profile;
71366
- appendViews(views, loaded.layers, baseView);
71367
- appendViews(views, loaded.layers, block);
71368
- appendViews(views, loaded.layers, (config) => baseView(profile(config, selectedProfile)));
71369
- appendViews(views, loaded.layers, (config) => block(profile(config, selectedProfile)));
71734
+ const profilePrefix = selectedProfile === undefined ? "" : `profiles.${selectedProfile}.`;
71735
+ appendViews(views, loaded.layers, baseView, "");
71736
+ appendViews(views, loaded.layers, block, "[opencode].");
71737
+ appendViews(views, loaded.layers, (config) => baseView(profile(config, selectedProfile)), profilePrefix);
71738
+ appendViews(views, loaded.layers, (config) => block(profile(config, selectedProfile)), `${profilePrefix}[opencode].`);
71370
71739
  const resolvedModels = modelView(resolved.config);
71371
71740
  if (Object.keys(resolvedModels.config).length > 0) {
71372
71741
  const source = loaded.layers[0]?.source.path ?? directory;
71373
- views.push({ config: resolvedModels.config, path: source });
71742
+ views.push({ config: resolvedModels.config, keyPrefix: "", path: source });
71374
71743
  }
71375
71744
  let protectedUserView = {};
71376
71745
  for (const layer of loaded.layers) {
@@ -71465,7 +71834,7 @@ function unknownPaths(schema, value, path) {
71465
71834
  if (def.type === "object" && isPlainRecord2(value) && def.shape !== undefined) {
71466
71835
  const catchall = def.catchall !== undefined && definition(def.catchall).type !== "never" ? def.catchall : undefined;
71467
71836
  return Object.entries(value).flatMap(([key, entry]) => {
71468
- const propertySchema = def.shape?.[key];
71837
+ const propertySchema = def.shape !== undefined && Object.hasOwn(def.shape, key) ? def.shape[key] : undefined;
71469
71838
  if (propertySchema !== undefined)
71470
71839
  return unknownPaths(propertySchema, entry, [...path, key]);
71471
71840
  if (catchall !== undefined)
@@ -71582,22 +71951,22 @@ var init_fallback_models2 = __esm(() => {
71582
71951
  });
71583
71952
 
71584
71953
  // packages/omo-opencode/src/config/schema/internal/permission.ts
71585
- var PermissionValueSchema, BashPermissionSchema, AgentPermissionSchema;
71954
+ var PermissionValueSchema2, BashPermissionSchema2, AgentPermissionSchema;
71586
71955
  var init_permission = __esm(() => {
71587
71956
  init_zod();
71588
- PermissionValueSchema = _enum(["ask", "allow", "deny"]);
71589
- BashPermissionSchema = union([
71590
- PermissionValueSchema,
71591
- record(string2(), PermissionValueSchema)
71957
+ PermissionValueSchema2 = _enum(["ask", "allow", "deny"]);
71958
+ BashPermissionSchema2 = union([
71959
+ PermissionValueSchema2,
71960
+ record(string2(), PermissionValueSchema2)
71592
71961
  ]);
71593
71962
  AgentPermissionSchema = object({
71594
- edit: PermissionValueSchema.optional(),
71595
- bash: BashPermissionSchema.optional(),
71596
- webfetch: PermissionValueSchema.optional(),
71597
- task: PermissionValueSchema.optional(),
71598
- doom_loop: PermissionValueSchema.optional(),
71599
- external_directory: PermissionValueSchema.optional()
71600
- }).catchall(PermissionValueSchema.optional());
71963
+ edit: PermissionValueSchema2.optional(),
71964
+ bash: BashPermissionSchema2.optional(),
71965
+ webfetch: PermissionValueSchema2.optional(),
71966
+ task: PermissionValueSchema2.optional(),
71967
+ doom_loop: PermissionValueSchema2.optional(),
71968
+ external_directory: PermissionValueSchema2.optional()
71969
+ }).catchall(PermissionValueSchema2.optional());
71601
71970
  });
71602
71971
 
71603
71972
  // packages/omo-opencode/src/config/schema/agent-overrides.ts
@@ -72361,6 +72730,30 @@ var init_schema2 = __esm(() => {
72361
72730
  init_websearch();
72362
72731
  });
72363
72732
 
72733
+ // packages/omo-opencode/src/config/prune-plugin-view.ts
72734
+ function prunePluginView(input) {
72735
+ const validation = OhMyOpenCodeConfigSchema.safeParse(input.config);
72736
+ if (validation.success)
72737
+ return { config: input.config, issues: [], warnings: [] };
72738
+ const pruned = pruneInvalidConfigPaths(input.config, validation.error.issues, validatePluginRecord);
72739
+ if (!pruned.ok)
72740
+ return { config: input.config, issues: validation.error.issues, warnings: [] };
72741
+ const file = displayOmoConfigPath(input.path, input.homeDir);
72742
+ return {
72743
+ config: pruned.config,
72744
+ issues: [],
72745
+ warnings: pruned.dropped.map((entry) => `config: ${file}: ${input.keyPrefix}${entry.key} ignored (invalid value)`)
72746
+ };
72747
+ }
72748
+ var validatePluginRecord = (record) => {
72749
+ const parsed = OhMyOpenCodeConfigSchema.safeParse(record);
72750
+ return parsed.success ? { success: true } : { success: false, issues: parsed.error.issues };
72751
+ };
72752
+ var init_prune_plugin_view = __esm(() => {
72753
+ init_src4();
72754
+ init_schema2();
72755
+ });
72756
+
72364
72757
  // packages/omo-opencode/src/config/validate.ts
72365
72758
  import { relative } from "path";
72366
72759
  function shortPath(configPath) {
@@ -72371,9 +72764,8 @@ function formatIssuePath(path) {
72371
72764
  const formatted = path.map((segment) => String(segment)).join(".");
72372
72765
  return formatted.length > 0 ? formatted : "<root>";
72373
72766
  }
72374
- function schemaMessages(configPath, rawConfig) {
72375
- const result = OhMyOpenCodeConfigSchema.safeParse(rawConfig);
72376
- const validationMessages = result.success ? [] : result.error.issues.map((issue) => `${shortPath(configPath)}: ${formatIssuePath(issue.path)}: ${issue.message}`);
72767
+ function schemaMessages(configPath, rawConfig, issues) {
72768
+ const validationMessages = issues.map((issue) => `${shortPath(configPath)}: ${formatIssuePath(issue.path)}: ${issue.message}`);
72377
72769
  const unknownKeyMessages = findUnknownKeyPaths(OhMyOpenCodeConfigSchema, rawConfig).map((path) => `${shortPath(configPath)}: Unknown config key: ${formatIssuePath(path)}`);
72378
72770
  return [...validationMessages, ...unknownKeyMessages];
72379
72771
  }
@@ -72389,11 +72781,13 @@ function parseConfig(rawConfig) {
72389
72781
  }
72390
72782
  return config;
72391
72783
  }
72392
- function parseConfigView(path, rawConfig) {
72784
+ function parseConfigView(view, homeDir) {
72785
+ const pruned = prunePluginView({ config: view.config, homeDir, keyPrefix: view.keyPrefix, path: view.path });
72393
72786
  return {
72394
- config: parseConfig(rawConfig),
72395
- messages: schemaMessages(path, rawConfig),
72396
- path
72787
+ config: parseConfig(pruned.config),
72788
+ messages: schemaMessages(view.path, view.config, pruned.issues),
72789
+ path: view.path,
72790
+ warnings: pruned.warnings
72397
72791
  };
72398
72792
  }
72399
72793
  function mergeViews(views) {
@@ -72483,27 +72877,36 @@ function migrateRalphLoopConfig(config) {
72483
72877
  }
72484
72878
  function validatePluginConfig(directory, environment = process.env) {
72485
72879
  const chain = loadOmoOpenCodeConfigChain(directory, environment);
72486
- const views = chain.views.map((view) => parseConfigView(view.path, view.config));
72487
- const chainMessages = chain.diagnostics.filter((diagnostic) => diagnostic.kind !== "deprecated-keys").map((diagnostic) => `${shortPath(diagnostic.path)}: ${diagnostic.message}`);
72880
+ const homeDir = environment.HOME ?? environment.USERPROFILE;
72881
+ const views = chain.views.map((view) => parseConfigView(view, homeDir));
72882
+ const failing = chain.diagnostics.filter((diagnostic) => diagnostic.kind !== "deprecated-keys" && diagnostic.kind !== "invalid-value");
72883
+ const chainMessages = failing.map((diagnostic) => `${shortPath(diagnostic.path)}: ${diagnostic.message}`);
72488
72884
  const messages = [...chainMessages, ...views.flatMap((view) => view.messages)];
72885
+ const warnings = [
72886
+ ...omoConfigDiagnosticLines(chain.diagnostics.flatMap((diagnostic) => diagnostic.kind === "invalid-value" ? [diagnostic] : []), { homeDir }),
72887
+ ...views.flatMap((view) => view.warnings)
72888
+ ];
72489
72889
  const firstFailingView = views.find((view) => view.messages.length > 0);
72490
72890
  const firstView = views[0];
72491
- const userConfig = parseConfig(chain.protectedUserView);
72891
+ const userConfig = parseConfig(prunePluginView({ config: chain.protectedUserView, homeDir, keyPrefix: "", path: "" }).config);
72492
72892
  const config = applyDisabledProviders(materializeAgentModelChains(protectUserFields(mergeViews(views), userConfig)));
72493
72893
  warnLegacyUlwExecuteKey(chain.views);
72494
72894
  return {
72495
72895
  valid: messages.length === 0,
72496
72896
  messages,
72497
- path: chainMessages.length > 0 ? chain.diagnostics[0]?.path ?? null : firstFailingView?.path ?? firstView?.path ?? null,
72897
+ warnings,
72898
+ path: failing[0]?.path ?? firstFailingView?.path ?? firstView?.path ?? null,
72498
72899
  config: migrateRalphLoopConfig(migrateLegacyUlwExecuteKey(config))
72499
72900
  };
72500
72901
  }
72501
72902
  var START_WORK_DEPRECATION_MESSAGE = 'config key "start_work" is deprecated, rename to "ulw_execute" - will be removed next release', deprecationWarningSink;
72502
72903
  var init_validate = __esm(() => {
72904
+ init_src4();
72503
72905
  init_disabled_providers();
72504
72906
  init_logger2();
72505
72907
  init_omo_config_chain();
72506
72908
  init_config_merger();
72909
+ init_prune_plugin_view();
72507
72910
  init_schema2();
72508
72911
  deprecationWarningSink = log2;
72509
72912
  });
@@ -74214,7 +74617,7 @@ init_shared();
74214
74617
  // package.json
74215
74618
  var package_default = {
74216
74619
  name: "oh-my-opencode",
74217
- version: "5.1.3",
74620
+ version: "5.1.5",
74218
74621
  description: "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
74219
74622
  main: "./dist/index.js",
74220
74623
  types: "dist/index.d.ts",
@@ -74391,7 +74794,7 @@ var package_default = {
74391
74794
  },
74392
74795
  devDependencies: {
74393
74796
  "@clack/prompts": "^1.8.1",
74394
- "@code-yeongyu/senpi": "2026.9.29-4",
74797
+ "@code-yeongyu/senpi": "2026.9.29-5",
74395
74798
  "@modelcontextprotocol/sdk": "^1.30.0",
74396
74799
  "@oh-my-opencode/agents-md-core": "workspace:*",
74397
74800
  "@oh-my-opencode/ast-grep-mcp": "workspace:*",
@@ -74449,18 +74852,18 @@ var package_default = {
74449
74852
  typescript: "^7.0.2"
74450
74853
  },
74451
74854
  optionalDependencies: {
74452
- "oh-my-opencode-darwin-arm64": "5.1.3",
74453
- "oh-my-opencode-darwin-x64": "5.1.3",
74454
- "oh-my-opencode-darwin-x64-baseline": "5.1.3",
74455
- "oh-my-opencode-linux-arm64": "5.1.3",
74456
- "oh-my-opencode-linux-arm64-musl": "5.1.3",
74457
- "oh-my-opencode-linux-x64": "5.1.3",
74458
- "oh-my-opencode-linux-x64-baseline": "5.1.3",
74459
- "oh-my-opencode-linux-x64-musl": "5.1.3",
74460
- "oh-my-opencode-linux-x64-musl-baseline": "5.1.3",
74461
- "oh-my-opencode-windows-arm64": "5.1.3",
74462
- "oh-my-opencode-windows-x64": "5.1.3",
74463
- "oh-my-opencode-windows-x64-baseline": "5.1.3"
74855
+ "oh-my-opencode-darwin-arm64": "5.1.5",
74856
+ "oh-my-opencode-darwin-x64": "5.1.5",
74857
+ "oh-my-opencode-darwin-x64-baseline": "5.1.5",
74858
+ "oh-my-opencode-linux-arm64": "5.1.5",
74859
+ "oh-my-opencode-linux-arm64-musl": "5.1.5",
74860
+ "oh-my-opencode-linux-x64": "5.1.5",
74861
+ "oh-my-opencode-linux-x64-baseline": "5.1.5",
74862
+ "oh-my-opencode-linux-x64-musl": "5.1.5",
74863
+ "oh-my-opencode-linux-x64-musl-baseline": "5.1.5",
74864
+ "oh-my-opencode-windows-arm64": "5.1.5",
74865
+ "oh-my-opencode-windows-x64": "5.1.5",
74866
+ "oh-my-opencode-windows-x64-baseline": "5.1.5"
74464
74867
  },
74465
74868
  overrides: {
74466
74869
  hono: "^4.13.8",