comisai 1.0.24 → 1.0.26

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 (192) hide show
  1. package/node_modules/@comis/agent/dist/bootstrap/sections/tool-descriptions.js +130 -10
  2. package/node_modules/@comis/agent/dist/bootstrap/sections/tooling-sections.d.ts +1 -1
  3. package/node_modules/@comis/agent/dist/bootstrap/sections/tooling-sections.js +9 -2
  4. package/node_modules/@comis/agent/dist/bridge/bridge-metrics.d.ts +8 -0
  5. package/node_modules/@comis/agent/dist/bridge/bridge-metrics.js +2 -0
  6. package/node_modules/@comis/agent/dist/bridge/pi-event-bridge.d.ts +29 -0
  7. package/node_modules/@comis/agent/dist/bridge/pi-event-bridge.js +242 -2
  8. package/node_modules/@comis/agent/dist/bridge/thinking-block-hash-invariant.d.ts +210 -0
  9. package/node_modules/@comis/agent/dist/bridge/thinking-block-hash-invariant.js +566 -0
  10. package/node_modules/@comis/agent/dist/context-engine/context-engine.js +8 -6
  11. package/node_modules/@comis/agent/dist/context-engine/signature-replay-scrubber.d.ts +51 -30
  12. package/node_modules/@comis/agent/dist/context-engine/signature-replay-scrubber.js +109 -36
  13. package/node_modules/@comis/agent/dist/executor/executor-context-engine-setup.js +5 -1
  14. package/node_modules/@comis/agent/dist/executor/executor-post-execution.js +22 -20
  15. package/node_modules/@comis/agent/dist/executor/executor-prompt-runner.d.ts +2 -0
  16. package/node_modules/@comis/agent/dist/executor/executor-prompt-runner.js +111 -15
  17. package/node_modules/@comis/agent/dist/executor/executor-response-filter.d.ts +20 -17
  18. package/node_modules/@comis/agent/dist/executor/executor-response-filter.js +132 -52
  19. package/node_modules/@comis/agent/dist/executor/executor-tool-assembly.js +16 -3
  20. package/node_modules/@comis/agent/dist/executor/model-retry.d.ts +14 -0
  21. package/node_modules/@comis/agent/dist/executor/model-retry.js +72 -1
  22. package/node_modules/@comis/agent/dist/executor/pi-executor.d.ts +3 -0
  23. package/node_modules/@comis/agent/dist/executor/pi-executor.js +68 -9
  24. package/node_modules/@comis/agent/dist/executor/post-batch-continuation.d.ts +82 -0
  25. package/node_modules/@comis/agent/dist/executor/post-batch-continuation.js +200 -0
  26. package/node_modules/@comis/agent/dist/executor/stream-wrappers/request-body-injector.js +1 -9
  27. package/node_modules/@comis/agent/dist/executor/tool-deferral.d.ts +37 -2
  28. package/node_modules/@comis/agent/dist/executor/tool-deferral.js +45 -3
  29. package/node_modules/@comis/agent/dist/executor/tool-parallelism.js +0 -1
  30. package/node_modules/@comis/agent/dist/executor/types.d.ts +11 -2
  31. package/node_modules/@comis/agent/dist/index.d.ts +3 -1
  32. package/node_modules/@comis/agent/dist/index.js +2 -0
  33. package/node_modules/@comis/agent/dist/model/last-known-model.d.ts +36 -0
  34. package/node_modules/@comis/agent/dist/model/last-known-model.js +49 -0
  35. package/node_modules/@comis/agent/dist/model/model-registry-adapter.d.ts +16 -4
  36. package/node_modules/@comis/agent/dist/model/model-registry-adapter.js +65 -21
  37. package/node_modules/@comis/agent/dist/planner/types.d.ts +0 -2
  38. package/node_modules/@comis/agent/dist/session/comis-session-manager.d.ts +10 -0
  39. package/node_modules/@comis/agent/dist/session/comis-session-manager.js +5 -0
  40. package/node_modules/@comis/agent/dist/spawn/pi-mono-adapters.js +7 -0
  41. package/node_modules/@comis/agent/package.json +1 -1
  42. package/node_modules/@comis/channels/package.json +1 -1
  43. package/node_modules/@comis/cli/dist/client/rpc-client.js +6 -1
  44. package/node_modules/@comis/cli/dist/commands/doctor.js +5 -3
  45. package/node_modules/@comis/cli/dist/commands/health.js +5 -2
  46. package/node_modules/@comis/cli/dist/wizard/json-output.js +7 -3
  47. package/node_modules/@comis/cli/dist/wizard/steps/11-daemon-start.js +130 -0
  48. package/node_modules/@comis/cli/package.json +1 -1
  49. package/node_modules/@comis/core/dist/bootstrap.js +5 -0
  50. package/node_modules/@comis/core/dist/config/env-layer.d.ts +31 -0
  51. package/node_modules/@comis/core/dist/config/env-layer.js +41 -0
  52. package/node_modules/@comis/core/dist/config/immutable-keys.d.ts +2 -2
  53. package/node_modules/@comis/core/dist/config/immutable-keys.js +8 -3
  54. package/node_modules/@comis/core/dist/config/layered.d.ts +9 -0
  55. package/node_modules/@comis/core/dist/config/layered.js +11 -0
  56. package/node_modules/@comis/core/dist/config/managed-sections.d.ts +43 -4
  57. package/node_modules/@comis/core/dist/config/managed-sections.js +100 -6
  58. package/node_modules/@comis/core/dist/config/schema-agent.d.ts +39 -0
  59. package/node_modules/@comis/core/dist/config/schema-agent.js +14 -0
  60. package/node_modules/@comis/core/dist/config/schema.d.ts +4 -0
  61. package/node_modules/@comis/core/dist/config/schema.js +14 -0
  62. package/node_modules/@comis/core/dist/domain/execution-graph.d.ts +1 -1
  63. package/node_modules/@comis/core/dist/event-bus/events-agent.d.ts +17 -2
  64. package/node_modules/@comis/core/dist/exports/config.d.ts +2 -2
  65. package/node_modules/@comis/core/dist/exports/config.js +1 -1
  66. package/node_modules/@comis/core/package.json +1 -1
  67. package/node_modules/@comis/daemon/dist/daemon.d.ts +22 -0
  68. package/node_modules/@comis/daemon/dist/daemon.js +45 -0
  69. package/node_modules/@comis/daemon/dist/rpc/agent-handlers.d.ts +5 -2
  70. package/node_modules/@comis/daemon/dist/rpc/agent-handlers.js +80 -1
  71. package/node_modules/@comis/daemon/dist/rpc/agent-inline-workspace.d.ts +67 -0
  72. package/node_modules/@comis/daemon/dist/rpc/agent-inline-workspace.js +139 -0
  73. package/node_modules/@comis/daemon/dist/rpc/model-handlers.d.ts +3 -0
  74. package/node_modules/@comis/daemon/dist/rpc/model-handlers.js +29 -5
  75. package/node_modules/@comis/daemon/dist/rpc/probe-provider-auth.d.ts +30 -0
  76. package/node_modules/@comis/daemon/dist/rpc/probe-provider-auth.js +59 -0
  77. package/node_modules/@comis/daemon/dist/rpc/provider-handlers.d.ts +37 -0
  78. package/node_modules/@comis/daemon/dist/rpc/provider-handlers.js +330 -0
  79. package/node_modules/@comis/daemon/dist/rpc/rpc-dispatch.js +18 -1
  80. package/node_modules/@comis/daemon/dist/setup-docker-restart-warn.d.ts +4 -0
  81. package/node_modules/@comis/daemon/dist/setup-docker-restart-warn.js +30 -0
  82. package/node_modules/@comis/daemon/dist/wiring/setup-agents.d.ts +3 -1
  83. package/node_modules/@comis/daemon/dist/wiring/setup-agents.js +28 -2
  84. package/node_modules/@comis/daemon/dist/wiring/setup-cross-session.js +1 -0
  85. package/node_modules/@comis/daemon/dist/wiring/setup-tools.js +7 -4
  86. package/node_modules/@comis/daemon/package.json +1 -1
  87. package/node_modules/@comis/gateway/package.json +1 -1
  88. package/node_modules/@comis/infra/dist/index.d.ts +1 -0
  89. package/node_modules/@comis/infra/dist/index.js +2 -0
  90. package/node_modules/@comis/infra/dist/runtime/is-docker.d.ts +1 -0
  91. package/node_modules/@comis/infra/dist/runtime/is-docker.js +25 -0
  92. package/node_modules/@comis/infra/package.json +1 -1
  93. package/node_modules/@comis/memory/package.json +1 -1
  94. package/node_modules/@comis/scheduler/package.json +1 -1
  95. package/node_modules/@comis/shared/package.json +1 -1
  96. package/node_modules/@comis/skills/dist/bridge/tool-metadata-registry.js +1 -3
  97. package/node_modules/@comis/skills/dist/builtin/platform/admin-manage-factory.js +24 -1
  98. package/node_modules/@comis/skills/dist/builtin/platform/agents-manage-tool.d.ts +53 -7
  99. package/node_modules/@comis/skills/dist/builtin/platform/agents-manage-tool.js +218 -24
  100. package/node_modules/@comis/skills/dist/builtin/platform/gateway-tool.d.ts +4 -1
  101. package/node_modules/@comis/skills/dist/builtin/platform/gateway-tool.js +16 -1
  102. package/node_modules/@comis/skills/dist/builtin/platform/index.d.ts +1 -1
  103. package/node_modules/@comis/skills/dist/builtin/platform/index.js +1 -1
  104. package/node_modules/@comis/skills/dist/builtin/platform/providers-manage-tool.d.ts +56 -0
  105. package/node_modules/@comis/skills/dist/builtin/platform/providers-manage-tool.js +203 -0
  106. package/node_modules/@comis/skills/dist/index.d.ts +1 -1
  107. package/node_modules/@comis/skills/dist/index.js +2 -2
  108. package/node_modules/@comis/skills/dist/policy/tool-policy.js +0 -1
  109. package/node_modules/@comis/skills/package.json +1 -1
  110. package/node_modules/@comis/web/dist/assets/{agent-detail-BG9MGWWj.js → agent-detail-DqL6Artv.js} +270 -270
  111. package/node_modules/@comis/web/dist/assets/agent-editor-CNM_h94Y.js +2173 -0
  112. package/node_modules/@comis/web/dist/assets/{agent-list-LHCJ4rw2.js → agent-list-Dbh-xD_F.js} +170 -170
  113. package/node_modules/@comis/web/dist/assets/{approvals-q9VH_IKr.js → approvals-C-K6hN2U.js} +13 -13
  114. package/node_modules/@comis/web/dist/assets/billing-view-C1DmtyzK.js +375 -0
  115. package/node_modules/@comis/web/dist/assets/{channel-detail-CaInesJM.js → channel-detail-CtCH22N1.js} +265 -265
  116. package/node_modules/@comis/web/dist/assets/channel-list-C7xXn-60.js +323 -0
  117. package/node_modules/@comis/web/dist/assets/{chat-console-CNmzl0JW.js → chat-console-C51pjFwk.js} +243 -246
  118. package/node_modules/@comis/web/dist/assets/{config-editor-DX4ITw6y.js → config-editor-BLArYRB7.js} +477 -477
  119. package/node_modules/@comis/web/dist/assets/{context-dag-browser-BwiaF5tf.js → context-dag-browser-fuyMinNI.js} +105 -105
  120. package/node_modules/@comis/web/dist/assets/{context-engine-BZ5Am6hA.js → context-engine-Bngf2bH0.js} +136 -136
  121. package/node_modules/@comis/web/dist/assets/decorate-BvWYovGE.js +38 -0
  122. package/node_modules/@comis/web/dist/assets/{delivery-view-OfBZof-m.js → delivery-view-C80hucxX.js} +134 -134
  123. package/node_modules/@comis/web/dist/assets/{diagnostics-view-YFwCxgr2.js → diagnostics-view-Cl4VbHZ6.js} +82 -82
  124. package/node_modules/@comis/web/dist/assets/directive-BOYXJ-K-.js +1 -0
  125. package/node_modules/@comis/web/dist/assets/{extract-variables-BM5qyK-s.js → extract-variables-B7-Doo7l.js} +39 -39
  126. package/node_modules/@comis/web/dist/assets/{ic-array-editor-B7m6x7-S.js → ic-array-editor-BLoEyeLS.js} +29 -29
  127. package/node_modules/@comis/web/dist/assets/{ic-breadcrumb-CUMpp3BL.js → ic-breadcrumb-DqN6G3gc.js} +16 -16
  128. package/node_modules/@comis/web/dist/assets/{ic-budget-segment-bar-BtJ6x5mN.js → ic-budget-segment-bar-zLsMzjDO.js} +20 -20
  129. package/node_modules/@comis/web/dist/assets/ic-chat-message-ByFUoMm6.js +352 -0
  130. package/node_modules/@comis/web/dist/assets/{ic-confirm-dialog-CCDbB04e.js → ic-confirm-dialog-DGlPbV1T.js} +26 -26
  131. package/node_modules/@comis/web/dist/assets/{ic-connection-dot-CnT1b8xr.js → ic-connection-dot-C4nDHgY2.js} +13 -13
  132. package/node_modules/@comis/web/dist/assets/ic-data-table-CKIvr-ag.js +277 -0
  133. package/node_modules/@comis/web/dist/assets/ic-delivery-row-B3YwjjuM.js +67 -0
  134. package/node_modules/@comis/web/dist/assets/{ic-detail-panel-BF83r-if.js → ic-detail-panel-DiCe4hLr.js} +27 -27
  135. package/node_modules/@comis/web/dist/assets/{ic-empty-state-60l2ePhd.js → ic-empty-state-CM3Wbj2f.js} +19 -19
  136. package/node_modules/@comis/web/dist/assets/ic-graph-canvas-ByRjij68.js +359 -0
  137. package/node_modules/@comis/web/dist/assets/ic-icon-BGNCCPpZ.js +33 -0
  138. package/node_modules/@comis/web/dist/assets/{ic-layer-waterfall-COvEYMg5.js → ic-layer-waterfall-WkaFyu-l.js} +18 -18
  139. package/node_modules/@comis/web/dist/assets/ic-relative-time-B3UAnTqg.js +12 -0
  140. package/node_modules/@comis/web/dist/assets/{ic-search-input-CSOxY9g7.js → ic-search-input-B02AGw1i.js} +22 -22
  141. package/node_modules/@comis/web/dist/assets/{ic-select-Ce-Raudx.js → ic-select-BqfZISjw.js} +29 -29
  142. package/node_modules/@comis/web/dist/assets/ic-tabs-yBjkWKJH.js +95 -0
  143. package/node_modules/@comis/web/dist/assets/ic-tag-CvMVQFRR.js +33 -0
  144. package/node_modules/@comis/web/dist/assets/{ic-time-range-picker-CypCT68y.js → ic-time-range-picker-DXbYeBmY.js} +31 -31
  145. package/node_modules/@comis/web/dist/assets/{ic-tool-call-7MaXSsCW.js → ic-tool-call-Bh5kq-yY.js} +51 -51
  146. package/node_modules/@comis/web/dist/assets/index-BBkuC-EU.js +2792 -0
  147. package/node_modules/@comis/web/dist/assets/index-CVEaS9aY.css +2 -0
  148. package/node_modules/@comis/web/dist/assets/{mcp-management-BNZPnpDn.js → mcp-management-DB-phOo7.js} +209 -209
  149. package/node_modules/@comis/web/dist/assets/{media-config-BBvTYxOX.js → media-config-CRqZ1ZUH.js} +154 -154
  150. package/node_modules/@comis/web/dist/assets/{media-test-BkK3RCRK.js → media-test-C9vE20Oy.js} +259 -259
  151. package/node_modules/@comis/web/dist/assets/{memory-inspector-1hDGCGat.js → memory-inspector-CeqfnxMZ.js} +450 -450
  152. package/node_modules/@comis/web/dist/assets/{message-center-CXefwsUu.js → message-center-Daup7Mof.js} +290 -290
  153. package/node_modules/@comis/web/dist/assets/{models-C1qcU_j3.js → models-DLYnEU8E.js} +371 -371
  154. package/node_modules/@comis/web/dist/assets/observability-types-D0tkwElU.js +1 -0
  155. package/node_modules/@comis/web/dist/assets/{observe-view-C0VBhX4C.js → observe-view-BTSt_PO5.js} +399 -399
  156. package/node_modules/@comis/web/dist/assets/pipeline-builder-DknfzyLt.js +1495 -0
  157. package/node_modules/@comis/web/dist/assets/{pipeline-history-DkfOQ6SW.js → pipeline-history-JnHZdeU_.js} +124 -124
  158. package/node_modules/@comis/web/dist/assets/{pipeline-history-detail-hyHgD0ai.js → pipeline-history-detail-Dg4knsEb.js} +65 -65
  159. package/node_modules/@comis/web/dist/assets/{pipeline-list-BPW8hV-q.js → pipeline-list-AEnibjsp.js} +227 -227
  160. package/node_modules/@comis/web/dist/assets/{pipeline-monitor-Bip16T7e.js → pipeline-monitor-DG7RbIOO.js} +298 -298
  161. package/node_modules/@comis/web/dist/assets/{scheduler-BGgwKd06.js → scheduler-uL1fYKAT.js} +486 -486
  162. package/node_modules/@comis/web/dist/assets/{security-D15st4xx.js → security-C3DywRLH.js} +389 -389
  163. package/node_modules/@comis/web/dist/assets/{session-detail-SGEYNJ0M.js → session-detail-BtqCNWXV.js} +294 -294
  164. package/node_modules/@comis/web/dist/assets/session-key-parser-Dkqcj2Ss.js +1 -0
  165. package/node_modules/@comis/web/dist/assets/session-list-CJXWa2XT.js +231 -0
  166. package/node_modules/@comis/web/dist/assets/{setup-wizard-nT0tz9QP.js → setup-wizard-ywn7oJvu.js} +486 -494
  167. package/node_modules/@comis/web/dist/assets/{skills-D8yVfSUy.js → skills-DX0KYnWD.js} +329 -329
  168. package/node_modules/@comis/web/dist/assets/{subagents-HHXMeHYo.js → subagents-B8p5YJEB.js} +74 -74
  169. package/node_modules/@comis/web/dist/assets/{workspace-manager-BQlr10iH.js → workspace-manager-CgzNIrw1.js} +236 -236
  170. package/node_modules/@comis/web/dist/index.html +3 -2
  171. package/node_modules/@comis/web/package.json +1 -1
  172. package/package.json +15 -15
  173. package/node_modules/@comis/skills/dist/builtin/platform/agents-list-tool.d.ts +0 -19
  174. package/node_modules/@comis/skills/dist/builtin/platform/agents-list-tool.js +0 -39
  175. package/node_modules/@comis/web/dist/assets/agent-editor-C26Q_xCs.js +0 -2173
  176. package/node_modules/@comis/web/dist/assets/billing-view-CtYvBqTE.js +0 -375
  177. package/node_modules/@comis/web/dist/assets/channel-list-B8dj3O9a.js +0 -323
  178. package/node_modules/@comis/web/dist/assets/directive-DoeGSK_T.js +0 -1
  179. package/node_modules/@comis/web/dist/assets/ic-chat-message-CFyDJd0z.js +0 -352
  180. package/node_modules/@comis/web/dist/assets/ic-data-table-CKUNTxHw.js +0 -277
  181. package/node_modules/@comis/web/dist/assets/ic-delivery-row-GP5Fkygs.js +0 -67
  182. package/node_modules/@comis/web/dist/assets/ic-graph-canvas-C8FuSMe1.js +0 -359
  183. package/node_modules/@comis/web/dist/assets/ic-icon-xeGTVhVG.js +0 -33
  184. package/node_modules/@comis/web/dist/assets/ic-relative-time-3FqpjeAI.js +0 -12
  185. package/node_modules/@comis/web/dist/assets/ic-tabs-B7QtM_v8.js +0 -95
  186. package/node_modules/@comis/web/dist/assets/ic-tag-CPPUnDLF.js +0 -33
  187. package/node_modules/@comis/web/dist/assets/index-CEcM1R_C.js +0 -2830
  188. package/node_modules/@comis/web/dist/assets/index-CIJFuItj.css +0 -1
  189. package/node_modules/@comis/web/dist/assets/observability-types-D7jUtSde.js +0 -1
  190. package/node_modules/@comis/web/dist/assets/pipeline-builder-DcUUIrm0.js +0 -1496
  191. package/node_modules/@comis/web/dist/assets/session-key-parser-DPORMVyU.js +0 -1
  192. package/node_modules/@comis/web/dist/assets/session-list-6ybUTxbY.js +0 -231
@@ -10,6 +10,7 @@
10
10
  * @module
11
11
  */
12
12
  import { ModelRegistry } from "@mariozechner/pi-coding-agent";
13
+ import { getModels, getProviders } from "@mariozechner/pi-ai";
13
14
  /**
14
15
  * Create a ModelRegistry from an AuthStorage instance.
15
16
  *
@@ -27,6 +28,18 @@ export function createModelRegistryAdapter(authStorage) {
27
28
  * proxies (NVIDIA NIM, Together, ollama, lm-studio, etc.) work without
28
29
  * code changes.
29
30
  */
31
+ /**
32
+ * Provider types that can register without an API key.
33
+ *
34
+ * Ollama (and similar local inference servers) do not require authentication
35
+ * by default. When a provider entry has a type in this set and no apiKeyName
36
+ * is configured (or the named secret is missing), registration proceeds with
37
+ * the "ollama-no-auth" sentinel instead of being skipped.
38
+ *
39
+ * The sentinel reaches the wire as `Authorization: Bearer ollama-no-auth`.
40
+ * Ollama ignores Authorization unless `OLLAMA_API_KEY` is set server-side.
41
+ */
42
+ const KEYLESS_PROVIDER_TYPES = new Set(["ollama"]);
30
43
  const PROVIDER_TYPE_TO_API = {
31
44
  openai: "openai-completions",
32
45
  groq: "openai-completions",
@@ -39,6 +52,18 @@ const PROVIDER_TYPE_TO_API = {
39
52
  anthropic: "anthropic-messages",
40
53
  google: "google-generative-ai",
41
54
  };
55
+ const _builtInProviders = new Set(getProviders());
56
+ function getBuiltInBaseUrl(type) {
57
+ if (!_builtInProviders.has(type))
58
+ return undefined;
59
+ const models = getModels(type);
60
+ return models[0]?.baseUrl;
61
+ }
62
+ function getBuiltInModelIds(type) {
63
+ if (!_builtInProviders.has(type))
64
+ return new Set();
65
+ return new Set(getModels(type).map((m) => m.id));
66
+ }
42
67
  /**
43
68
  * Register YAML `providers.entries.*` with pi-coding-agent's ModelRegistry.
44
69
  *
@@ -50,50 +75,66 @@ const PROVIDER_TYPE_TO_API = {
50
75
  * Per-entry behavior:
51
76
  * - Skipped if `enabled === false`.
52
77
  * - Skipped if no models declared and no `baseUrl` override.
78
+ * - Models that already exist in the built-in pi SDK catalog for the
79
+ * entry's `type` are filtered out (no redundant registration).
53
80
  * - On `registerProvider` error (missing baseUrl, missing apiKey, etc.),
54
81
  * a WARN is logged and the loop continues -- one bad entry must not
55
82
  * prevent the daemon from starting.
56
- *
57
- * @returns Number of entries successfully registered.
58
83
  */
59
84
  export function registerCustomProviders(registry, entries, secretManager, logger) {
60
85
  let registered = 0;
86
+ const providerAliases = new Map();
61
87
  for (const [providerName, entry] of Object.entries(entries)) {
62
88
  if (!entry.enabled) {
63
89
  logger.debug({ providerName }, "Custom provider skipped (disabled)");
64
90
  continue;
65
91
  }
66
- const hasModels = entry.models.length > 0;
92
+ const builtInIds = getBuiltInModelIds(entry.type);
93
+ const isBuiltInType = builtInIds.size > 0;
94
+ if (isBuiltInType && providerName !== entry.type) {
95
+ providerAliases.set(providerName, entry.type);
96
+ }
97
+ const customModels = isBuiltInType
98
+ ? entry.models.filter((m) => !builtInIds.has(m.id))
99
+ : [...entry.models];
100
+ if (isBuiltInType && customModels.length < entry.models.length) {
101
+ const skipped = entry.models.length - customModels.length;
102
+ logger.debug({ providerName, type: entry.type, skipped, remaining: customModels.length }, "Skipped built-in models already in pi SDK catalog");
103
+ }
104
+ const hasModels = customModels.length > 0;
67
105
  const hasBaseUrlOverride = !!entry.baseUrl;
68
106
  if (!hasModels && !hasBaseUrlOverride) {
69
- logger.debug({ providerName }, "Custom provider skipped (no models and no baseUrl override)");
107
+ logger.debug({ providerName }, "Custom provider skipped (no custom models and no baseUrl override)");
70
108
  continue;
71
109
  }
72
110
  const apiKey = entry.apiKeyName ? secretManager.get(entry.apiKeyName) : undefined;
73
- if (hasModels && !apiKey) {
111
+ const isKeylessType = KEYLESS_PROVIDER_TYPES.has(entry.type);
112
+ if (hasModels && !apiKey && !isKeylessType) {
74
113
  logger.warn({
75
114
  providerName,
76
115
  apiKeyName: entry.apiKeyName,
77
- hint: "Set the named secret in ~/.comis/.env or remove the provider entry from config.yaml",
116
+ hint: "Set the named secret in ~/.comis/.env, omit apiKeyName for type='ollama', or remove the provider entry from config.yaml",
78
117
  errorKind: "config",
79
118
  }, "Custom provider has models but no API key -- skipping registration");
80
119
  continue;
81
120
  }
82
121
  const api = PROVIDER_TYPE_TO_API[entry.type] ?? "openai-completions";
83
122
  const headersResolved = Object.keys(entry.headers).length > 0 ? entry.headers : undefined;
123
+ const resolvedApiKey = apiKey ?? (isKeylessType ? "ollama-no-auth" : undefined);
124
+ if (resolvedApiKey === "ollama-no-auth") {
125
+ logger.debug({
126
+ providerName,
127
+ hint: "Using keyless sentinel for type='ollama'. If your Ollama server requires an OLLAMA_API_KEY, set the provider's apiKeyName explicitly via providers_manage update.",
128
+ }, "Custom provider registered with keyless sentinel");
129
+ }
84
130
  try {
85
131
  registry.registerProvider(providerName, {
86
132
  api,
87
- baseUrl: entry.baseUrl || undefined,
88
- apiKey,
133
+ baseUrl: entry.baseUrl || getBuiltInBaseUrl(entry.type),
134
+ apiKey: resolvedApiKey,
89
135
  headers: headersResolved,
90
- // pi's ProviderModelConfig requires concrete values for name/cost/
91
- // contextWindow/maxTokens. Comis's UserModelSchema lets users omit
92
- // these (defaults to optional/undefined), so we fill in zeros and
93
- // a generous default context window. Cost is informational only
94
- // and our CostTracker uses pi-ai's own catalog where it can.
95
136
  models: hasModels
96
- ? entry.models.map((m) => ({
137
+ ? customModels.map((m) => ({
97
138
  id: m.id,
98
139
  name: m.name ?? m.id,
99
140
  contextWindow: m.contextWindow ?? 128_000,
@@ -114,7 +155,7 @@ export function registerCustomProviders(registry, entries, secretManager, logger
114
155
  providerName,
115
156
  api,
116
157
  baseUrl: entry.baseUrl,
117
- modelCount: entry.models.length,
158
+ modelCount: customModels.length,
118
159
  }, "Custom provider registered with pi ModelRegistry");
119
160
  }
120
161
  catch (error) {
@@ -126,7 +167,7 @@ export function registerCustomProviders(registry, entries, secretManager, logger
126
167
  }, "Custom provider registration failed");
127
168
  }
128
169
  }
129
- return registered;
170
+ return { registered, providerAliases };
130
171
  }
131
172
  /**
132
173
  * Resolve the initial model for an agent session.
@@ -139,11 +180,15 @@ export function registerCustomProviders(registry, entries, secretManager, logger
139
180
  * @param config - Agent model configuration (provider + model ID)
140
181
  * @param allowlist - Optional model allowlist for enforcement
141
182
  */
142
- export async function resolveInitialModel(registry, config, allowlist) {
143
- // Try to find the exact model in the registry
144
- const model = registry.find(config.provider, config.model);
183
+ export async function resolveInitialModel(registry, config, allowlist, providerAliases) {
184
+ let model = registry.find(config.provider, config.model);
185
+ if (!model && providerAliases) {
186
+ const builtInName = providerAliases.get(config.provider);
187
+ if (builtInName) {
188
+ model = registry.find(builtInName, config.model);
189
+ }
190
+ }
145
191
  if (!model) {
146
- // Try to find any available model from the requested provider
147
192
  const available = registry.getAvailable();
148
193
  const providerModel = available.find((m) => m.provider === config.provider);
149
194
  if (!providerModel) {
@@ -154,7 +199,6 @@ export async function resolveInitialModel(registry, config, allowlist) {
154
199
  `Available models: ${available.map((m) => `${m.provider}/${m.id}`).join(", ") || "none"}`,
155
200
  };
156
201
  }
157
- // Found a provider model but not the exact ID -- return it with a note
158
202
  return {
159
203
  model: undefined,
160
204
  thinkingLevel: "off",
@@ -31,8 +31,6 @@ export interface ExecutionPlan {
31
31
  steps: PlanStep[];
32
32
  /** Number of steps marked "done". */
33
33
  completedCount: number;
34
- /** Whether the completeness nudge has been injected. */
35
- nudged: boolean;
36
34
  /** Timestamp of plan creation. */
37
35
  createdAtMs: number;
38
36
  }
@@ -104,6 +104,16 @@ export interface ComisSessionManager {
104
104
  * Fire-and-forget -- metadata write failure must not affect execution.
105
105
  */
106
106
  writeSessionMetadata(sessionKey: SessionKey, metadata: SessionMetadata): void;
107
+ /**
108
+ * 260428-iag: Resolve the absolute JSONL session file path for a session key.
109
+ *
110
+ * Thin synchronous wrapper around `sessionKeyToPath(sessionKey, deps.sessionBaseDir)`
111
+ * that exposes the path resolver to the wire-edge diagnostic in pi-event-bridge.
112
+ * Pure delegation -- no I/O, no logging, no side effects. Path composition is
113
+ * delegated to `sessionKeyToPath`, which uses `safePath` for traversal-safe
114
+ * resolution.
115
+ */
116
+ getSessionPath(sessionKey: SessionKey): string;
107
117
  }
108
118
  /**
109
119
  * Create a ComisSessionManager that manages session lifecycle with write locks.
@@ -70,6 +70,11 @@ export function createComisSessionManager(deps) {
70
70
  catch { /* non-empty or already gone */ }
71
71
  }, { retries: 10, retryMinTimeout: 500 });
72
72
  },
73
+ getSessionPath(sessionKey) {
74
+ // 260428-iag wire-edge diagnostic: pure delegation to sessionKeyToPath
75
+ // (which uses safePath internally). No I/O, no logging.
76
+ return sessionKeyToPath(sessionKey, deps.sessionBaseDir);
77
+ },
73
78
  writeSessionMetadata(sessionKey, metadata) {
74
79
  const sessionPath = sessionKeyToPath(sessionKey, deps.sessionBaseDir);
75
80
  const metadataPath = sessionPath.replace(/\.jsonl$/, "_session-metadata.json");
@@ -50,5 +50,12 @@ export function createEphemeralComisSessionManager(cwd) {
50
50
  writeSessionMetadata() {
51
51
  // No-op: no companion file for in-memory sessions
52
52
  },
53
+ getSessionPath() {
54
+ // 260428-iag wire-edge diagnostic: ephemeral sub-agent sessions never
55
+ // persist a JSONL file, so there is no path to return. Empty string
56
+ // signals "no persisted file"; the bridge's wire-diff hook short-circuits
57
+ // when jsonlPath.length === 0.
58
+ return "";
59
+ },
53
60
  };
54
61
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@comis/agent",
3
3
  "private": true,
4
- "version": "1.0.24",
4
+ "version": "1.0.26",
5
5
  "author": "Moshe Anconina",
6
6
  "license": "Apache-2.0",
7
7
  "description": "AI agent executor, budget control, and session management for Comis",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@comis/channels",
3
3
  "private": true,
4
- "version": "1.0.24",
4
+ "version": "1.0.26",
5
5
  "author": "Moshe Anconina",
6
6
  "license": "Apache-2.0",
7
7
  "description": "Chat platform adapters — Discord, Telegram, Slack, WhatsApp, Signal, iMessage, IRC, LINE",
@@ -127,8 +127,13 @@ function resolveFromConfig() {
127
127
  token = undefined;
128
128
  }
129
129
  }
130
+ // gateway.host is a *bind* address. As a *connect* host, the wildcard
131
+ // values (0.0.0.0 / ::) aren't valid — remap to loopback so the CLI
132
+ // can reach a daemon that's binding all interfaces (the default for
133
+ // LAN / Docker deployments).
134
+ const connectHost = host === "0.0.0.0" ? "127.0.0.1" : host === "::" ? "::1" : host;
130
135
  const protocol = tls ? "wss" : "ws";
131
- return { url: `${protocol}://${host}:${port}/ws`, token, tls };
136
+ return { url: `${protocol}://${connectHost}:${port}/ws`, token, tls };
132
137
  }
133
138
  catch {
134
139
  return { url: FALLBACK_GATEWAY_URL, token: undefined, tls: false };
@@ -76,12 +76,14 @@ function buildDoctorContext(configPaths) {
76
76
  }
77
77
  const dataDir = config?.dataDir || os.homedir() + "/.comis";
78
78
  const daemonPidFile = dataDir + "/daemon.pid";
79
- // Resolve gateway URL from config
79
+ // Resolve gateway URL from config. gw.host is a *bind* address; remap
80
+ // wildcards to loopback so the connectivity probe targets a real address.
80
81
  let gatewayUrl;
81
82
  if (config?.gateway) {
82
83
  const gw = config.gateway;
83
- const host = gw.host || "127.0.0.1";
84
- const port = gw.port || 3000;
84
+ const bindHost = gw.host || "127.0.0.1";
85
+ const host = bindHost === "0.0.0.0" ? "127.0.0.1" : bindHost === "::" ? "::1" : bindHost;
86
+ const port = gw.port || 4766;
85
87
  const protocol = gw.tls ? "https" : "http";
86
88
  gatewayUrl = `${protocol}://${host}:${port}`;
87
89
  }
@@ -75,11 +75,14 @@ function buildHealthContext(configPaths) {
75
75
  }
76
76
  const dataDir = config?.dataDir || os.homedir() + "/.comis";
77
77
  const daemonPidFile = dataDir + "/daemon.pid";
78
+ // gw.host is a *bind* address; remap wildcards to loopback so the
79
+ // connectivity probe targets a real address.
78
80
  let gatewayUrl;
79
81
  if (config?.gateway) {
80
82
  const gw = config.gateway;
81
- const host = gw.host || "127.0.0.1";
82
- const port = gw.port || 3000;
83
+ const bindHost = gw.host || "127.0.0.1";
84
+ const host = bindHost === "0.0.0.0" ? "127.0.0.1" : bindHost === "::" ? "::1" : bindHost;
85
+ const port = gw.port || 4766;
83
86
  const protocol = gw.tls ? "https" : "http";
84
87
  gatewayUrl = `${protocol}://${host}:${port}`;
85
88
  }
@@ -34,13 +34,17 @@ export function buildJsonOutput(state, opts) {
34
34
  let gatewayUrl;
35
35
  let gatewayToken;
36
36
  if (state.gateway) {
37
- const bindIp = state.gateway.bindMode === "loopback"
37
+ // For LAN mode the daemon binds 0.0.0.0, but the printed URL is
38
+ // a *connect* hint — surface 127.0.0.1 (the only address every
39
+ // local caller can reach). External clients use the host's public
40
+ // IP/hostname; surfacing 0.0.0.0 here would just be misleading.
41
+ const connectIp = state.gateway.bindMode === "loopback"
38
42
  ? "127.0.0.1"
39
43
  : state.gateway.bindMode === "lan"
40
- ? "0.0.0.0"
44
+ ? "127.0.0.1"
41
45
  : state.gateway.customIp ?? "127.0.0.1";
42
46
  const port = state.gateway.port ?? 4766;
43
- gatewayUrl = `ws://${bindIp}:${port}`;
47
+ gatewayUrl = `ws://${connectIp}:${port}`;
44
48
  // Only include token when auth method is token
45
49
  if (state.gateway.authMethod === "token" && state.gateway.token) {
46
50
  gatewayToken = state.gateway.token;
@@ -18,6 +18,7 @@ import { existsSync, mkdirSync, writeFileSync, openSync, closeSync, accessSync,
18
18
  import * as os from "node:os";
19
19
  import { promisify } from "node:util";
20
20
  import { safePath } from "@comis/core";
21
+ import { isDocker } from "@comis/infra";
21
22
  const exec = promisify(execFile);
22
23
  import { updateState, sectionSeparator, success as themeSuccess, error as themeError, } from "../index.js";
23
24
  // ---------- Constants ----------
@@ -76,6 +77,45 @@ async function waitForReady(host, port) {
76
77
  }
77
78
  return false;
78
79
  }
80
+ /**
81
+ * Poll the gateway through a stop/start cycle.
82
+ *
83
+ * Used after signalling the in-container daemon for a Docker-managed
84
+ * restart: first waits for the gateway to go down (proving the signal
85
+ * landed and the container is exiting), then for a fresh gateway to
86
+ * come back (proving Docker's restart policy respawned the container).
87
+ * Returns false if either phase times out.
88
+ */
89
+ async function waitForRestart(host, port) {
90
+ const url = `http://${host}:${port}/health`;
91
+ const probe = async () => {
92
+ try {
93
+ const controller = new AbortController();
94
+ const timer = setTimeout(() => controller.abort(), 1500);
95
+ const res = await fetch(url, { signal: controller.signal });
96
+ clearTimeout(timer);
97
+ return res.ok;
98
+ }
99
+ catch {
100
+ return false;
101
+ }
102
+ };
103
+ // Phase 1: wait for the gateway to disappear (signal landed).
104
+ const downDeadline = Date.now() + 5_000;
105
+ while (Date.now() < downDeadline) {
106
+ if (!(await probe()))
107
+ break;
108
+ await new Promise((r) => setTimeout(r, READY_POLL_MS));
109
+ }
110
+ // Phase 2: wait for the gateway to come back (container restarted).
111
+ const upDeadline = Date.now() + READY_TIMEOUT_MS;
112
+ while (Date.now() < upDeadline) {
113
+ if (await probe())
114
+ return true;
115
+ await new Promise((r) => setTimeout(r, READY_POLL_MS));
116
+ }
117
+ return false;
118
+ }
79
119
  /**
80
120
  * Run subsystem health checks and display results.
81
121
  *
@@ -196,6 +236,42 @@ async function runHealthCheck(state, prompter, gatewayHost, gatewayPort) {
196
236
  }
197
237
  }
198
238
  }
239
+ /**
240
+ * Find the comis daemon PID inside the container.
241
+ *
242
+ * Scans /proc for processes whose parent is PID 1 and whose cmdline
243
+ * contains "daemon.js". Used to signal the container's daemon for a
244
+ * Docker-native restart instead of spawning a sibling.
245
+ */
246
+ async function findContainerDaemonPid() {
247
+ try {
248
+ const { readdirSync, readFileSync } = await import("node:fs");
249
+ for (const entry of readdirSync("/proc")) {
250
+ if (!/^\d+$/.test(entry))
251
+ continue;
252
+ const pid = Number(entry);
253
+ if (pid === 1 || pid === process.pid)
254
+ continue;
255
+ let cmdline;
256
+ let ppid;
257
+ try {
258
+ cmdline = readFileSync(`/proc/${entry}/cmdline`, "utf-8");
259
+ const status = readFileSync(`/proc/${entry}/status`, "utf-8");
260
+ ppid = (/^PPid:\s*(\d+)/m.exec(status)?.[1]) ?? "";
261
+ }
262
+ catch {
263
+ continue;
264
+ }
265
+ if (ppid !== "1")
266
+ continue;
267
+ if (!cmdline.includes("daemon.js"))
268
+ continue;
269
+ return pid;
270
+ }
271
+ }
272
+ catch { /* /proc not accessible */ }
273
+ return undefined;
274
+ }
199
275
  async function detectServiceManager() {
200
276
  if (!existsSync("/run/systemd/system"))
201
277
  return "direct";
@@ -351,6 +427,60 @@ export const daemonStartStep = {
351
427
  return updateState(state, {});
352
428
  }
353
429
  // 4. Direct-spawn fallback (no systemd)
430
+ // Docker branch: the daemon is the container's PID 1 process tree.
431
+ // Spawning a sibling produces EADDRINUSE; killing PID 1 directly via
432
+ // pid-file (which the container daemon doesn't write) is a no-op.
433
+ // Signal the actual daemon process so dumb-init exits and Docker's
434
+ // restart policy brings the container back with the new config.
435
+ if (isDocker()) {
436
+ const dockerSpinner = prompter.spinner();
437
+ if (choice === "restart" || daemonRunning) {
438
+ dockerSpinner.start("Signalling container daemon to restart...");
439
+ const targetPid = await findContainerDaemonPid();
440
+ if (targetPid) {
441
+ // 260428-qrn: Prime the user BEFORE the SIGTERM so they have a
442
+ // breadcrumb pointing at the missing `--restart unless-stopped`
443
+ // flag. This is a heads-up, not an abort -- the existing
444
+ // waitForRestart detector still handles the failure path below.
445
+ prompter.log.warn("Restarting daemon via SIGTERM. The container must have been started with `--restart unless-stopped` (or equivalent compose `restart: unless-stopped`). If the container exits and stays exited, run `docker restart <container-name>` from your host.");
446
+ try {
447
+ process.kill(targetPid, "SIGTERM");
448
+ }
449
+ catch { /* already gone */ }
450
+ // Wait for the gateway to disappear, then for it to come back
451
+ // (Docker restart policy respawns the container).
452
+ const restarted = await waitForRestart(host, port);
453
+ if (restarted) {
454
+ dockerSpinner.stop("Daemon restarted via container restart policy");
455
+ }
456
+ else {
457
+ dockerSpinner.stop("Daemon stopped, but the container did not auto-restart");
458
+ prompter.log.warn("Run `docker restart <container>` (or `docker start <container>` if it exited) to apply the new config.");
459
+ return updateState(state, {});
460
+ }
461
+ }
462
+ else {
463
+ dockerSpinner.stop("Could not find the container daemon process");
464
+ prompter.log.warn("Run `docker restart <container>` to apply the new configuration.");
465
+ return updateState(state, {});
466
+ }
467
+ }
468
+ else {
469
+ // Daemon wasn't running and we're inside a container — Docker
470
+ // launches the daemon itself; nothing for the wizard to do here.
471
+ dockerSpinner.start("Waiting for container daemon...");
472
+ const ready = await waitForReady(host, port);
473
+ dockerSpinner.stop(ready ? "Daemon ready" : "Daemon not yet responding");
474
+ if (!ready) {
475
+ prompter.log.warn("Start the container with `docker start <container>` if it isn't already running.");
476
+ return updateState(state, {});
477
+ }
478
+ }
479
+ if (!state.skipHealth) {
480
+ await runHealthCheck(state, prompter, host, port);
481
+ }
482
+ return updateState(state, {});
483
+ }
354
484
  // Stop existing daemon before restart
355
485
  if (choice === "restart") {
356
486
  const stopSpinner = prompter.spinner();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@comis/cli",
3
3
  "private": true,
4
- "version": "1.0.24",
4
+ "version": "1.0.26",
5
5
  "author": "Moshe Anconina",
6
6
  "license": "Apache-2.0",
7
7
  "description": "Command-line interface for the Comis AI agent platform",
@@ -2,6 +2,7 @@ import { ok, err } from "@comis/shared";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { loadLayered } from "./config/layered.js";
5
+ import { buildGatewayEnvLayer } from "./config/env-layer.js";
5
6
  import { TypedEventBus } from "./event-bus/index.js";
6
7
  import { createSecretManager, safePath } from "./security/index.js";
7
8
  import { createPluginRegistry } from "./hooks/plugin-registry.js";
@@ -44,12 +45,16 @@ export function bootstrap(options) {
44
45
  // Wrap getSecret to record every name referenced by the config — the set
45
46
  // becomes container.platformSecretNames and is used by the exec tool to
46
47
  // refuse secretRefs access to platform-managed credentials.
48
+ // envLayer projects operational env vars (COMIS_GATEWAY_HOST/PORT) into
49
+ // the config layer stack at lower priority than YAML files, so explicit
50
+ // user config wins over env — see config/env-layer.ts.
47
51
  const referencedNames = new Set();
48
52
  const configResult = loadLayered(options.configPaths, {
49
53
  getSecret: (key) => {
50
54
  referencedNames.add(key);
51
55
  return secretManager.get(key);
52
56
  },
57
+ envLayer: buildGatewayEnvLayer(env),
53
58
  });
54
59
  if (!configResult.ok) {
55
60
  return err(configResult.error);
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Operational env-var → config-layer projection.
3
+ *
4
+ * Bridges container/systemd/pm2 deployments to layered config without a
5
+ * config.yaml. Currently supported:
6
+ *
7
+ * COMIS_GATEWAY_HOST → gateway.host (e.g. "0.0.0.0" inside the Docker image)
8
+ * COMIS_GATEWAY_PORT → gateway.port
9
+ *
10
+ * The returned object is a partial config layer fed to mergeLayered() at
11
+ * lower priority than YAML files: schema defaults < env layer < config.yaml.
12
+ * This keeps explicit user config authoritative — a `gateway.host: 127.0.0.1`
13
+ * in config.yaml is never silently broadened to 0.0.0.0 by an inherited env
14
+ * var, preserving the secure-by-default contract on `gateway.host`.
15
+ *
16
+ * Empty-string host and non-numeric / out-of-range ports are dropped so a
17
+ * typo never silently relocates the daemon.
18
+ *
19
+ * @module
20
+ */
21
+ /** Subset of env vars consumed by this projection. */
22
+ export interface GatewayEnvSource {
23
+ COMIS_GATEWAY_HOST?: string | undefined;
24
+ COMIS_GATEWAY_PORT?: string | undefined;
25
+ }
26
+ /**
27
+ * Build a partial config layer from env vars. Returns an empty object when
28
+ * no relevant env vars are set (callers can pass through to mergeLayered
29
+ * unconditionally without affecting precedence).
30
+ */
31
+ export declare function buildGatewayEnvLayer(env: GatewayEnvSource): Record<string, unknown>;
@@ -0,0 +1,41 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * Operational env-var → config-layer projection.
4
+ *
5
+ * Bridges container/systemd/pm2 deployments to layered config without a
6
+ * config.yaml. Currently supported:
7
+ *
8
+ * COMIS_GATEWAY_HOST → gateway.host (e.g. "0.0.0.0" inside the Docker image)
9
+ * COMIS_GATEWAY_PORT → gateway.port
10
+ *
11
+ * The returned object is a partial config layer fed to mergeLayered() at
12
+ * lower priority than YAML files: schema defaults < env layer < config.yaml.
13
+ * This keeps explicit user config authoritative — a `gateway.host: 127.0.0.1`
14
+ * in config.yaml is never silently broadened to 0.0.0.0 by an inherited env
15
+ * var, preserving the secure-by-default contract on `gateway.host`.
16
+ *
17
+ * Empty-string host and non-numeric / out-of-range ports are dropped so a
18
+ * typo never silently relocates the daemon.
19
+ *
20
+ * @module
21
+ */
22
+ /**
23
+ * Build a partial config layer from env vars. Returns an empty object when
24
+ * no relevant env vars are set (callers can pass through to mergeLayered
25
+ * unconditionally without affecting precedence).
26
+ */
27
+ export function buildGatewayEnvLayer(env) {
28
+ const gateway = {};
29
+ const rawHost = env.COMIS_GATEWAY_HOST;
30
+ if (typeof rawHost === "string" && rawHost.length > 0) {
31
+ gateway["host"] = rawHost;
32
+ }
33
+ const rawPort = env.COMIS_GATEWAY_PORT;
34
+ if (typeof rawPort === "string" && rawPort.length > 0) {
35
+ const parsed = Number.parseInt(rawPort, 10);
36
+ if (Number.isFinite(parsed) && parsed >= 1 && parsed <= 65_535) {
37
+ gateway["port"] = parsed;
38
+ }
39
+ }
40
+ return Object.keys(gateway).length > 0 ? { gateway } : {};
41
+ }
@@ -25,8 +25,8 @@ export declare const MUTABLE_CONFIG_OVERRIDES: readonly string[];
25
25
  * path segments, the path is considered a match (equal to or a child of the
26
26
  * pattern).
27
27
  *
28
- * @param fullPath - Full dot-notation config path (e.g., "agents.default.persona.name")
29
- * @param pattern - Override pattern with `*` wildcards (e.g., "agents.*.persona")
28
+ * @param fullPath - Full dot-notation config path (e.g., "agents.default.maxSteps")
29
+ * @param pattern - Override pattern with `*` wildcards (e.g., "agents.*.maxSteps")
30
30
  * @returns true if the path matches or is a child of the pattern
31
31
  */
32
32
  export declare function matchesOverridePattern(fullPath: string, pattern: string): boolean;
@@ -22,7 +22,12 @@ export const MUTABLE_CONFIG_OVERRIDES = [
22
22
  "agents.*.skills.watchDebounceMs",
23
23
  "agents.*.skills.discoveryPaths",
24
24
  "agents.*.maxSteps",
25
- "agents.*.persona",
25
+ // 260428-rrr Bug A: removed dead "agents.*.persona" entry. PerAgentConfigSchema
26
+ // is z.strictObject and has no `persona` field, so the override could never
27
+ // produce a successful patch -- it only leaked a misleading capability hint
28
+ // to LLMs (formatRedirectHint emitted "you can also patch agents.<id>.persona")
29
+ // which the LLM echoed back as `persona:` in agents_manage.create config,
30
+ // triggering Zod unrecognized_keys rejection.
26
31
  "agents.*.promptTimeout.promptTimeoutMs", // Allow runtime tuning
27
32
  "agents.*.promptTimeout.retryPromptTimeoutMs", // Allow runtime tuning
28
33
  "agents.*.operationModels", // Allow runtime model tiering tuning
@@ -39,8 +44,8 @@ export const MUTABLE_CONFIG_OVERRIDES = [
39
44
  * path segments, the path is considered a match (equal to or a child of the
40
45
  * pattern).
41
46
  *
42
- * @param fullPath - Full dot-notation config path (e.g., "agents.default.persona.name")
43
- * @param pattern - Override pattern with `*` wildcards (e.g., "agents.*.persona")
47
+ * @param fullPath - Full dot-notation config path (e.g., "agents.default.maxSteps")
48
+ * @param pattern - Override pattern with `*` wildcards (e.g., "agents.*.maxSteps")
44
49
  * @returns true if the path matches or is a child of the pattern
45
50
  */
46
51
  export function matchesOverridePattern(fullPath, pattern) {
@@ -24,7 +24,16 @@ export declare function mergeLayered(layers: Record<string, unknown>[]): Result<
24
24
  * This supports the common pattern: defaults.yaml < config.yaml < config.local.yaml
25
25
  *
26
26
  * If any file fails to load, returns the error immediately.
27
+ *
28
+ * `envLayer` (when provided) is prepended to the layer sequence so it sits
29
+ * between Zod schema defaults and YAML files in precedence order:
30
+ * defaults < envLayer < config files
31
+ * Explicit user config in YAML always wins over env-derived values, which
32
+ * preserves secure-by-default semantics for security-sensitive fields like
33
+ * `gateway.host` — an inherited env var can never silently broaden a bind
34
+ * the operator pinned in config.yaml.
27
35
  */
28
36
  export declare function loadLayered(configPaths: string[], options?: {
29
37
  getSecret?: (key: string) => string | undefined;
38
+ envLayer?: Record<string, unknown>;
30
39
  }): Result<AppConfig, ConfigError>;