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
@@ -16,6 +16,8 @@
16
16
  import { PerAgentConfigSchema } from "@comis/core";
17
17
  import { resolveWorkspaceDir, resolveOperationModel, resolveProviderFamily, OPERATION_TIER_MAP, DEFAULT_PROVIDER_KEYS, } from "@comis/agent";
18
18
  import { persistToConfig } from "./persist-to-config.js";
19
+ import { writeInlineWorkspaceFiles, } from "./agent-inline-workspace.js";
20
+ import { probeProviderAuth } from "./probe-provider-auth.js";
19
21
  // ---------------------------------------------------------------------------
20
22
  // Factory
21
23
  // ---------------------------------------------------------------------------
@@ -36,6 +38,16 @@ export function createAgentHandlers(deps) {
36
38
  if (deps.agents[agentId] !== undefined) {
37
39
  throw new Error(`Agent already exists: ${agentId}`);
38
40
  }
41
+ // 260428-vyf L2: extract inlineContent BEFORE config processing.
42
+ // role/identity are write-once side-effects (ROLE.md / IDENTITY.md
43
+ // file writes), NOT durable state — they NEVER enter the persisted
44
+ // config patch. The L1 tool boundary is responsible for stripping
45
+ // them from `config.workspace` before this RPC is called; this
46
+ // handler only consumes the dedicated top-level `inlineContent`
47
+ // field. If a (mis)caller leaves them inside config.workspace, the
48
+ // downstream Zod strict-object will reject them — that's an
49
+ // explicit failure mode, not a silent drop.
50
+ const inlineContent = params.inlineContent ?? undefined;
39
51
  const config = params.config ?? {};
40
52
  // Strip workspacePath so new agents always get the auto-computed
41
53
  // isolated workspace (~/.comis/workspace-{agentId}) instead of
@@ -83,7 +95,46 @@ export function createAgentHandlers(deps) {
83
95
  errorKind: "internal" }, "Agent hot-add failed");
84
96
  }
85
97
  }
86
- return { agentId, config: parsedConfig, created: true, workspaceDir: resolveWorkspaceDir(parsedConfig, agentId) };
98
+ const workspaceDir = resolveWorkspaceDir(parsedConfig, agentId);
99
+ // 260428-vyf L2: best-effort inline ROLE.md / IDENTITY.md write.
100
+ // Only invoke when inlineContent has at least one populated field
101
+ // AND the persistDeps logger is available (the helper requires a
102
+ // structured logger; the in-memory-only test path skips it).
103
+ let inlineWritesResult;
104
+ if (deps.persistDeps?.logger
105
+ && inlineContent
106
+ && (inlineContent.role !== undefined || inlineContent.identity !== undefined)) {
107
+ const writeResult = await writeInlineWorkspaceFiles({ logger: deps.persistDeps.logger }, { workspaceDir, agentId, role: inlineContent.role, identity: inlineContent.identity });
108
+ if (writeResult.ok) {
109
+ inlineWritesResult = writeResult.value;
110
+ }
111
+ else {
112
+ // Best-effort: don't fail the create. The helper has already
113
+ // emitted a structured WARN for io / path_traversal. For the
114
+ // oversize branch the helper does NOT log (the schema layer
115
+ // is the canonical gate) — emit a defensive WARN here so the
116
+ // daemon-side surface is not silent.
117
+ if (writeResult.error.kind === "oversize") {
118
+ deps.persistDeps.logger.warn({
119
+ method: "agents.create",
120
+ agentId,
121
+ file: writeResult.error.file,
122
+ limit: writeResult.error.limit,
123
+ actual: writeResult.error.actual,
124
+ hint: "Inline content exceeded size limit at helper layer (schema should have caught this); agent exists with template files.",
125
+ errorKind: "validation",
126
+ }, "Inline workspace content oversize");
127
+ }
128
+ inlineWritesResult = { ok: false, error: writeResult.error };
129
+ }
130
+ }
131
+ return {
132
+ agentId,
133
+ config: parsedConfig,
134
+ created: true,
135
+ workspaceDir,
136
+ ...(inlineWritesResult !== undefined ? { inlineWritesResult } : {}),
137
+ };
87
138
  },
88
139
  "agents.get": async (params) => {
89
140
  const agentId = params.agentId;
@@ -142,8 +193,36 @@ export function createAgentHandlers(deps) {
142
193
  : existing.scheduler.heartbeat,
143
194
  };
144
195
  }
196
+ // Preserve scalar fields on partial modelFailover updates. fallbackModels,
197
+ // authProfiles, and allowedModels are arrays -- they are replaced wholesale
198
+ // by the spread (no element-wise merge), which matches the documented
199
+ // "user provides the complete desired list" semantic. Scalar fields
200
+ // (cooldownInitialMs, cooldownMultiplier, cooldownCapMs, maxAttempts) are
201
+ // preserved when omitted from the patch.
202
+ if (config.modelFailover && existing.modelFailover) {
203
+ config.modelFailover = {
204
+ ...existing.modelFailover,
205
+ ...config.modelFailover,
206
+ };
207
+ }
145
208
  const merged = { ...existing, ...config };
146
209
  const parsedConfig = PerAgentConfigSchema.parse(merged);
210
+ // Probe provider API key when provider or model changes
211
+ const providerChanging = config.provider !== undefined && config.provider !== existing.provider;
212
+ const modelChanging = config.model !== undefined && config.model !== existing.model;
213
+ if ((providerChanging || modelChanging) && deps.providerEntries) {
214
+ const targetProvider = parsedConfig.provider;
215
+ const providerEntry = deps.providerEntries[targetProvider];
216
+ if (providerEntry?.apiKeyName && deps.secretManager) {
217
+ const apiKey = deps.secretManager.get(providerEntry.apiKeyName);
218
+ if (apiKey) {
219
+ const probeResult = await probeProviderAuth(providerEntry.baseUrl, apiKey, parsedConfig.model);
220
+ if (!probeResult.ok) {
221
+ throw new Error(`Cannot switch agent "${agentId}" to provider "${targetProvider}": ${probeResult.error}`);
222
+ }
223
+ }
224
+ }
225
+ }
147
226
  deps.agents[agentId] = parsedConfig;
148
227
  // Best-effort persistence to config.yaml
149
228
  if (deps.persistDeps) {
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Best-effort writer for inline ROLE.md / IDENTITY.md content supplied via
3
+ * the L2 single-call agents.create path (260428-vyf).
4
+ *
5
+ * Collapses the previous 3-call agent-creation workflow
6
+ * (`agents_manage.create` -> `write(ROLE.md)` -> `write(IDENTITY.md)`)
7
+ * into a single RPC by writing both files atomically as a side-effect of
8
+ * the `agents.create` RPC. role/identity are write-once side-effects, NOT
9
+ * durable state — they never enter the persisted config.yaml.
10
+ *
11
+ * Defense-in-depth posture:
12
+ * - Size limits enforced both at the TypeBox tool-param boundary
13
+ * (maxLength 16384/4096) AND here, so a caller bypassing the tool layer
14
+ * cannot push oversize content past the daemon.
15
+ * - safePath(workspaceDir, "ROLE.md"|"IDENTITY.md") guards against any
16
+ * workspaceDir whose computation could be poisoned upstream. The
17
+ * filename is HARDCODED, not derived from input — defense in depth.
18
+ * - Returns Result<T,E> on every code path (no thrown exceptions).
19
+ *
20
+ * @module
21
+ */
22
+ import { type Result } from "@comis/shared";
23
+ import type { ComisLogger } from "@comis/infra";
24
+ export interface AgentInlineWorkspaceParams {
25
+ /** Already-resolved workspace directory (via resolveWorkspaceDir). */
26
+ workspaceDir: string;
27
+ /** Agent identifier — used for structured logging. */
28
+ agentId: string;
29
+ /** Optional inline ROLE.md content. */
30
+ role?: string;
31
+ /** Optional inline IDENTITY.md content. */
32
+ identity?: string;
33
+ }
34
+ export interface AgentInlineWorkspaceResult {
35
+ roleWritten: boolean;
36
+ identityWritten: boolean;
37
+ bytesWritten: number;
38
+ }
39
+ export type AgentInlineWorkspaceError = {
40
+ kind: "oversize";
41
+ file: "ROLE.md" | "IDENTITY.md";
42
+ limit: number;
43
+ actual: number;
44
+ } | {
45
+ kind: "path_traversal";
46
+ file: "ROLE.md" | "IDENTITY.md";
47
+ message: string;
48
+ } | {
49
+ kind: "io";
50
+ file: "ROLE.md" | "IDENTITY.md";
51
+ message: string;
52
+ };
53
+ export interface AgentInlineWorkspaceDeps {
54
+ logger: ComisLogger;
55
+ }
56
+ /**
57
+ * Write inline ROLE.md / IDENTITY.md content to the agent's workspace
58
+ * directory. Best-effort — the caller (`agents.create`) will surface a
59
+ * Result-shaped outcome on the RPC return so the L1 tool can build the
60
+ * appropriate next-step contract for the LLM.
61
+ *
62
+ * @param deps - Required structured logger.
63
+ * @param params - workspaceDir + agentId + optional role/identity content.
64
+ * @returns Result with per-file written flags + total byte count, or one
65
+ * of three structured error kinds (oversize | path_traversal | io).
66
+ */
67
+ export declare function writeInlineWorkspaceFiles(deps: AgentInlineWorkspaceDeps, params: AgentInlineWorkspaceParams): Promise<Result<AgentInlineWorkspaceResult, AgentInlineWorkspaceError>>;
@@ -0,0 +1,139 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * Best-effort writer for inline ROLE.md / IDENTITY.md content supplied via
4
+ * the L2 single-call agents.create path (260428-vyf).
5
+ *
6
+ * Collapses the previous 3-call agent-creation workflow
7
+ * (`agents_manage.create` -> `write(ROLE.md)` -> `write(IDENTITY.md)`)
8
+ * into a single RPC by writing both files atomically as a side-effect of
9
+ * the `agents.create` RPC. role/identity are write-once side-effects, NOT
10
+ * durable state — they never enter the persisted config.yaml.
11
+ *
12
+ * Defense-in-depth posture:
13
+ * - Size limits enforced both at the TypeBox tool-param boundary
14
+ * (maxLength 16384/4096) AND here, so a caller bypassing the tool layer
15
+ * cannot push oversize content past the daemon.
16
+ * - safePath(workspaceDir, "ROLE.md"|"IDENTITY.md") guards against any
17
+ * workspaceDir whose computation could be poisoned upstream. The
18
+ * filename is HARDCODED, not derived from input — defense in depth.
19
+ * - Returns Result<T,E> on every code path (no thrown exceptions).
20
+ *
21
+ * @module
22
+ */
23
+ import { writeFile } from "node:fs/promises";
24
+ import { safePath, PathTraversalError } from "@comis/core";
25
+ import { ok, err } from "@comis/shared";
26
+ // ---------------------------------------------------------------------------
27
+ // Constants
28
+ // ---------------------------------------------------------------------------
29
+ /** Max ROLE.md inline content size (chars). Mirrored at the TypeBox layer. */
30
+ const ROLE_MAX = 16384;
31
+ /** Max IDENTITY.md inline content size (chars). Mirrored at the TypeBox layer. */
32
+ const IDENTITY_MAX = 4096;
33
+ // ---------------------------------------------------------------------------
34
+ // Helpers
35
+ // ---------------------------------------------------------------------------
36
+ /**
37
+ * Resolve target path via safePath, catching PathTraversalError.
38
+ * Returns Result so the caller can branch without try/catch on its own.
39
+ */
40
+ function resolveTarget(workspaceDir, filename) {
41
+ try {
42
+ return ok(safePath(workspaceDir, filename));
43
+ }
44
+ catch (e) {
45
+ if (e instanceof PathTraversalError) {
46
+ return err({ kind: "path_traversal", file: filename, message: e.message });
47
+ }
48
+ // Unexpected non-traversal error from safePath — treat as IO surface;
49
+ // the caller will WARN-log and the create RPC will still succeed.
50
+ const message = e instanceof Error ? e.message : String(e);
51
+ return err({ kind: "io", file: filename, message });
52
+ }
53
+ }
54
+ /**
55
+ * Attempt a single file write. fs.writeFile rejection -> structured WARN +
56
+ * `{kind:"io"}` Result. Never throws.
57
+ */
58
+ async function attemptWrite(deps, agentId, filename, targetPath, content) {
59
+ try {
60
+ await writeFile(targetPath, content, { encoding: "utf8" });
61
+ return ok(undefined);
62
+ }
63
+ catch (e) {
64
+ const message = e instanceof Error ? e.message : String(e);
65
+ deps.logger.warn({
66
+ module: "daemon.rpc.agent-handlers",
67
+ agentId,
68
+ file: filename,
69
+ err: e,
70
+ hint: "Inline ROLE.md/IDENTITY.md write failed; agent exists with template files. User can call write() to customize.",
71
+ errorKind: "resource",
72
+ }, "Inline workspace file write failed");
73
+ return err({ kind: "io", file: filename, message });
74
+ }
75
+ }
76
+ // ---------------------------------------------------------------------------
77
+ // Public API
78
+ // ---------------------------------------------------------------------------
79
+ /**
80
+ * Write inline ROLE.md / IDENTITY.md content to the agent's workspace
81
+ * directory. Best-effort — the caller (`agents.create`) will surface a
82
+ * Result-shaped outcome on the RPC return so the L1 tool can build the
83
+ * appropriate next-step contract for the LLM.
84
+ *
85
+ * @param deps - Required structured logger.
86
+ * @param params - workspaceDir + agentId + optional role/identity content.
87
+ * @returns Result with per-file written flags + total byte count, or one
88
+ * of three structured error kinds (oversize | path_traversal | io).
89
+ */
90
+ export async function writeInlineWorkspaceFiles(deps, params) {
91
+ // Belt-and-braces size limits. Schema layer already enforces these, but
92
+ // a caller that bypasses the tool (e.g. direct RPC) must still be gated.
93
+ if (params.role !== undefined && params.role.length > ROLE_MAX) {
94
+ return err({ kind: "oversize", file: "ROLE.md", limit: ROLE_MAX, actual: params.role.length });
95
+ }
96
+ if (params.identity !== undefined && params.identity.length > IDENTITY_MAX) {
97
+ return err({
98
+ kind: "oversize",
99
+ file: "IDENTITY.md",
100
+ limit: IDENTITY_MAX,
101
+ actual: params.identity.length,
102
+ });
103
+ }
104
+ let roleWritten = false;
105
+ let identityWritten = false;
106
+ let bytesWritten = 0;
107
+ if (params.role !== undefined) {
108
+ const targetResult = resolveTarget(params.workspaceDir, "ROLE.md");
109
+ if (!targetResult.ok)
110
+ return targetResult;
111
+ const writeResult = await attemptWrite(deps, params.agentId, "ROLE.md", targetResult.value, params.role);
112
+ if (!writeResult.ok)
113
+ return writeResult;
114
+ roleWritten = true;
115
+ bytesWritten += params.role.length;
116
+ }
117
+ if (params.identity !== undefined) {
118
+ const targetResult = resolveTarget(params.workspaceDir, "IDENTITY.md");
119
+ if (!targetResult.ok)
120
+ return targetResult;
121
+ const writeResult = await attemptWrite(deps, params.agentId, "IDENTITY.md", targetResult.value, params.identity);
122
+ if (!writeResult.ok)
123
+ return writeResult;
124
+ identityWritten = true;
125
+ bytesWritten += params.identity.length;
126
+ }
127
+ // Emit canonical INFO log only when at least one file was written.
128
+ // Pure no-op invocations (both fields absent) stay silent.
129
+ if (roleWritten || identityWritten) {
130
+ deps.logger.info({
131
+ module: "daemon.rpc.agent-handlers",
132
+ agentId: params.agentId,
133
+ roleBytes: roleWritten ? params.role.length : 0,
134
+ identityBytes: identityWritten ? params.identity.length : 0,
135
+ hint: "customized inline workspace ROLE.md+IDENTITY.md skips the post-create write() roundtrip",
136
+ }, "Wrote inline workspace files (ROLE.md + IDENTITY.md) on agents.create");
137
+ }
138
+ return ok({ roleWritten, identityWritten, bytesWritten });
139
+ }
@@ -7,6 +7,7 @@
7
7
  * @module
8
8
  */
9
9
  import type { ModelCatalog } from "@comis/agent";
10
+ import type { ProviderEntry } from "@comis/core";
10
11
  import type { RpcHandler } from "./types.js";
11
12
  /** Dependencies required by model management RPC handlers. */
12
13
  export interface ModelHandlerDeps {
@@ -17,6 +18,8 @@ export interface ModelHandlerDeps {
17
18
  provider: string;
18
19
  model: string;
19
20
  }>;
21
+ /** Custom provider entries from config.yaml for fallback when catalog is empty. */
22
+ providerEntries?: Record<string, ProviderEntry>;
20
23
  }
21
24
  /**
22
25
  * Create a record of model management RPC handlers bound to the given deps.
@@ -72,14 +72,15 @@ export function createModelHandlers(deps) {
72
72
  // Find all agents that use this provider
73
73
  const matchingAgents = Object.entries(deps.agents).filter(([, a]) => a.provider === provider);
74
74
  if (matchingAgents.length === 0) {
75
- // Include catalog info so the LLM knows models exist even when
76
- // no agent is wired to this provider yet.
77
75
  const modelsInCatalog = deps.modelCatalog.getByProvider(provider);
76
+ const providerEntry = deps.providerEntries?.[provider];
77
+ const customModelCount = providerEntry?.enabled ? providerEntry.models.length : 0;
78
78
  return {
79
79
  provider,
80
80
  status: "not_configured",
81
81
  message: "No agents use this provider",
82
82
  modelsInCatalog: modelsInCatalog.length,
83
+ ...(customModelCount > 0 ? { customModels: customModelCount } : {}),
83
84
  hint: "To switch to this provider, use agents_manage with action 'update' " +
84
85
  "to set the agent's provider and model. " +
85
86
  "Example: agents_manage({ action: 'update', agent_id: '<id>', " +
@@ -88,11 +89,34 @@ export function createModelHandlers(deps) {
88
89
  }
89
90
  // Check catalog availability for this provider
90
91
  const modelsInCatalog = deps.modelCatalog.getByProvider(provider);
92
+ if (modelsInCatalog.length > 0) {
93
+ return {
94
+ provider,
95
+ status: "available",
96
+ modelsAvailable: modelsInCatalog.length,
97
+ validatedModels: modelsInCatalog.filter((m) => m.validated).length,
98
+ agentsUsing: matchingAgents.map(([id, a]) => ({ agentId: id, model: a.model })),
99
+ };
100
+ }
101
+ // Catalog empty — check custom provider entries (custom providers
102
+ // register with the pi ModelRegistry for routing but are not in the
103
+ // static ModelCatalog used by this management tool).
104
+ const providerEntry = deps.providerEntries?.[provider];
105
+ if (providerEntry && providerEntry.enabled && providerEntry.models.length > 0) {
106
+ return {
107
+ provider,
108
+ status: "available",
109
+ source: "custom_provider",
110
+ modelsAvailable: providerEntry.models.length,
111
+ models: providerEntry.models.map((m) => m.id),
112
+ agentsUsing: matchingAgents.map(([id, a]) => ({ agentId: id, model: a.model })),
113
+ };
114
+ }
91
115
  return {
92
116
  provider,
93
- status: modelsInCatalog.length > 0 ? "available" : "no_models",
94
- modelsAvailable: modelsInCatalog.length,
95
- validatedModels: modelsInCatalog.filter((m) => m.validated).length,
117
+ status: "no_models",
118
+ modelsAvailable: 0,
119
+ validatedModels: 0,
96
120
  agentsUsing: matchingAgents.map(([id, a]) => ({ agentId: id, model: a.model })),
97
121
  };
98
122
  },
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Lightweight HTTP probe to validate a provider API key before committing
3
+ * the provider config to config.yaml.
4
+ *
5
+ * Uses a minimal chat/completions request (max_tokens: 1) rather than
6
+ * GET /models because some providers (nvidia NIM) return 200 on /models
7
+ * but 403 on the completions endpoint — the only reliable auth check is
8
+ * hitting the endpoint that will actually be called.
9
+ *
10
+ * Design:
11
+ * - Only 401/403 are treated as definitive auth failures (block config commit).
12
+ * - 5xx, 4xx (other than auth), network errors, and timeouts return ok()
13
+ * so they do NOT block config changes.
14
+ * - Empty baseUrl or apiKey short-circuits to ok() (nothing to probe).
15
+ * - Uses AbortSignal.timeout to cap probe duration (default 5 s).
16
+ *
17
+ * @module
18
+ */
19
+ import { type Result } from "@comis/shared";
20
+ /**
21
+ * Probe a provider endpoint to verify the API key is accepted.
22
+ *
23
+ * @param baseUrl - Provider API base URL (e.g. "https://api.openai.com/v1")
24
+ * @param apiKey - The secret API key value
25
+ * @param model - Model ID to use in the probe request (e.g. "gpt-4o")
26
+ * @param timeoutMs - Maximum probe duration in milliseconds (default 5000)
27
+ * @returns ok(undefined) if the key appears valid or the check is inconclusive;
28
+ * err(string) if the provider explicitly rejected the key (401/403).
29
+ */
30
+ export declare function probeProviderAuth(baseUrl: string, apiKey: string, model?: string, timeoutMs?: number): Promise<Result<void, string>>;
@@ -0,0 +1,59 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * Lightweight HTTP probe to validate a provider API key before committing
4
+ * the provider config to config.yaml.
5
+ *
6
+ * Uses a minimal chat/completions request (max_tokens: 1) rather than
7
+ * GET /models because some providers (nvidia NIM) return 200 on /models
8
+ * but 403 on the completions endpoint — the only reliable auth check is
9
+ * hitting the endpoint that will actually be called.
10
+ *
11
+ * Design:
12
+ * - Only 401/403 are treated as definitive auth failures (block config commit).
13
+ * - 5xx, 4xx (other than auth), network errors, and timeouts return ok()
14
+ * so they do NOT block config changes.
15
+ * - Empty baseUrl or apiKey short-circuits to ok() (nothing to probe).
16
+ * - Uses AbortSignal.timeout to cap probe duration (default 5 s).
17
+ *
18
+ * @module
19
+ */
20
+ import { ok, err } from "@comis/shared";
21
+ const DEFAULT_TIMEOUT_MS = 5_000;
22
+ /**
23
+ * Probe a provider endpoint to verify the API key is accepted.
24
+ *
25
+ * @param baseUrl - Provider API base URL (e.g. "https://api.openai.com/v1")
26
+ * @param apiKey - The secret API key value
27
+ * @param model - Model ID to use in the probe request (e.g. "gpt-4o")
28
+ * @param timeoutMs - Maximum probe duration in milliseconds (default 5000)
29
+ * @returns ok(undefined) if the key appears valid or the check is inconclusive;
30
+ * err(string) if the provider explicitly rejected the key (401/403).
31
+ */
32
+ export async function probeProviderAuth(baseUrl, apiKey, model, timeoutMs = DEFAULT_TIMEOUT_MS) {
33
+ if (!baseUrl || !apiKey)
34
+ return ok(undefined);
35
+ let response;
36
+ try {
37
+ response = await fetch(`${baseUrl}/chat/completions`, {
38
+ method: "POST",
39
+ headers: {
40
+ "Authorization": `Bearer ${apiKey}`,
41
+ "Content-Type": "application/json",
42
+ },
43
+ body: JSON.stringify({
44
+ model: model ?? "test",
45
+ messages: [{ role: "user", content: "hi" }],
46
+ max_tokens: 1,
47
+ }),
48
+ signal: AbortSignal.timeout(timeoutMs),
49
+ });
50
+ }
51
+ catch {
52
+ return ok(undefined);
53
+ }
54
+ if (response.status === 401 || response.status === 403) {
55
+ return err(`API key rejected by provider (HTTP ${response.status}). ` +
56
+ `Verify the key is correct and has not expired.`);
57
+ }
58
+ return ok(undefined);
59
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Provider management RPC handler module.
3
+ * Provides 7 handlers for runtime LLM provider management:
4
+ * providers.list - List all providers with summary and apiKeyConfigured state
5
+ * providers.get - Retrieve full provider config plus agentsUsing list
6
+ * providers.create - Register a new provider entry with validation
7
+ * providers.update - Patch an existing provider config with merge semantics
8
+ * providers.delete - Remove a provider (blocked if agents reference it)
9
+ * providers.enable - Set enabled:true on a disabled provider
10
+ * providers.disable - Set enabled:false (warns but does not block on references)
11
+ *
12
+ * Follows the same factory pattern as agent-handlers.ts. Each handler validates
13
+ * input, operates on the runtime providerEntries map, and returns structured results.
14
+ * API key values are NEVER exposed -- only apiKeyName references and apiKeyConfigured state.
15
+ * @module
16
+ */
17
+ import type { ProviderEntry, PerAgentConfig } from "@comis/core";
18
+ import { type PersistToConfigDeps } from "./persist-to-config.js";
19
+ import type { RpcHandler } from "./types.js";
20
+ /** Dependencies required by provider management RPC handlers. */
21
+ export interface ProviderHandlerDeps {
22
+ /** Live provider entries map (NOT a spread copy -- mutations are same-object visible). */
23
+ providerEntries: Record<string, ProviderEntry>;
24
+ /** Runtime agents map for reference checks. */
25
+ agents: Record<string, PerAgentConfig>;
26
+ /** Optional persistence deps for writing changes to config.yaml. */
27
+ persistDeps?: PersistToConfigDeps;
28
+ /** SecretManager for apiKeyConfigured three-state and probe key retrieval. */
29
+ secretManager?: {
30
+ has(key: string): boolean;
31
+ get(key: string): string | undefined;
32
+ };
33
+ }
34
+ /**
35
+ * Create a record of provider management RPC handlers bound to the given deps.
36
+ */
37
+ export declare function createProviderHandlers(deps: ProviderHandlerDeps): Record<string, RpcHandler>;