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.
- package/node_modules/@comis/agent/dist/bootstrap/sections/tool-descriptions.js +130 -10
- package/node_modules/@comis/agent/dist/bootstrap/sections/tooling-sections.d.ts +1 -1
- package/node_modules/@comis/agent/dist/bootstrap/sections/tooling-sections.js +9 -2
- package/node_modules/@comis/agent/dist/bridge/bridge-metrics.d.ts +8 -0
- package/node_modules/@comis/agent/dist/bridge/bridge-metrics.js +2 -0
- package/node_modules/@comis/agent/dist/bridge/pi-event-bridge.d.ts +29 -0
- package/node_modules/@comis/agent/dist/bridge/pi-event-bridge.js +242 -2
- package/node_modules/@comis/agent/dist/bridge/thinking-block-hash-invariant.d.ts +210 -0
- package/node_modules/@comis/agent/dist/bridge/thinking-block-hash-invariant.js +566 -0
- package/node_modules/@comis/agent/dist/context-engine/context-engine.js +8 -6
- package/node_modules/@comis/agent/dist/context-engine/signature-replay-scrubber.d.ts +51 -30
- package/node_modules/@comis/agent/dist/context-engine/signature-replay-scrubber.js +109 -36
- package/node_modules/@comis/agent/dist/executor/executor-context-engine-setup.js +5 -1
- package/node_modules/@comis/agent/dist/executor/executor-post-execution.js +22 -20
- package/node_modules/@comis/agent/dist/executor/executor-prompt-runner.d.ts +2 -0
- package/node_modules/@comis/agent/dist/executor/executor-prompt-runner.js +111 -15
- package/node_modules/@comis/agent/dist/executor/executor-response-filter.d.ts +20 -17
- package/node_modules/@comis/agent/dist/executor/executor-response-filter.js +132 -52
- package/node_modules/@comis/agent/dist/executor/executor-tool-assembly.js +16 -3
- package/node_modules/@comis/agent/dist/executor/model-retry.d.ts +14 -0
- package/node_modules/@comis/agent/dist/executor/model-retry.js +72 -1
- package/node_modules/@comis/agent/dist/executor/pi-executor.d.ts +3 -0
- package/node_modules/@comis/agent/dist/executor/pi-executor.js +68 -9
- package/node_modules/@comis/agent/dist/executor/post-batch-continuation.d.ts +82 -0
- package/node_modules/@comis/agent/dist/executor/post-batch-continuation.js +200 -0
- package/node_modules/@comis/agent/dist/executor/stream-wrappers/request-body-injector.js +1 -9
- package/node_modules/@comis/agent/dist/executor/tool-deferral.d.ts +37 -2
- package/node_modules/@comis/agent/dist/executor/tool-deferral.js +45 -3
- package/node_modules/@comis/agent/dist/executor/tool-parallelism.js +0 -1
- package/node_modules/@comis/agent/dist/executor/types.d.ts +11 -2
- package/node_modules/@comis/agent/dist/index.d.ts +3 -1
- package/node_modules/@comis/agent/dist/index.js +2 -0
- package/node_modules/@comis/agent/dist/model/last-known-model.d.ts +36 -0
- package/node_modules/@comis/agent/dist/model/last-known-model.js +49 -0
- package/node_modules/@comis/agent/dist/model/model-registry-adapter.d.ts +16 -4
- package/node_modules/@comis/agent/dist/model/model-registry-adapter.js +65 -21
- package/node_modules/@comis/agent/dist/planner/types.d.ts +0 -2
- package/node_modules/@comis/agent/dist/session/comis-session-manager.d.ts +10 -0
- package/node_modules/@comis/agent/dist/session/comis-session-manager.js +5 -0
- package/node_modules/@comis/agent/dist/spawn/pi-mono-adapters.js +7 -0
- package/node_modules/@comis/agent/package.json +1 -1
- package/node_modules/@comis/channels/package.json +1 -1
- package/node_modules/@comis/cli/dist/client/rpc-client.js +6 -1
- package/node_modules/@comis/cli/dist/commands/doctor.js +5 -3
- package/node_modules/@comis/cli/dist/commands/health.js +5 -2
- package/node_modules/@comis/cli/dist/wizard/json-output.js +7 -3
- package/node_modules/@comis/cli/dist/wizard/steps/11-daemon-start.js +130 -0
- package/node_modules/@comis/cli/package.json +1 -1
- package/node_modules/@comis/core/dist/bootstrap.js +5 -0
- package/node_modules/@comis/core/dist/config/env-layer.d.ts +31 -0
- package/node_modules/@comis/core/dist/config/env-layer.js +41 -0
- package/node_modules/@comis/core/dist/config/immutable-keys.d.ts +2 -2
- package/node_modules/@comis/core/dist/config/immutable-keys.js +8 -3
- package/node_modules/@comis/core/dist/config/layered.d.ts +9 -0
- package/node_modules/@comis/core/dist/config/layered.js +11 -0
- package/node_modules/@comis/core/dist/config/managed-sections.d.ts +43 -4
- package/node_modules/@comis/core/dist/config/managed-sections.js +100 -6
- package/node_modules/@comis/core/dist/config/schema-agent.d.ts +39 -0
- package/node_modules/@comis/core/dist/config/schema-agent.js +14 -0
- package/node_modules/@comis/core/dist/config/schema.d.ts +4 -0
- package/node_modules/@comis/core/dist/config/schema.js +14 -0
- package/node_modules/@comis/core/dist/domain/execution-graph.d.ts +1 -1
- package/node_modules/@comis/core/dist/event-bus/events-agent.d.ts +17 -2
- package/node_modules/@comis/core/dist/exports/config.d.ts +2 -2
- package/node_modules/@comis/core/dist/exports/config.js +1 -1
- package/node_modules/@comis/core/package.json +1 -1
- package/node_modules/@comis/daemon/dist/daemon.d.ts +22 -0
- package/node_modules/@comis/daemon/dist/daemon.js +45 -0
- package/node_modules/@comis/daemon/dist/rpc/agent-handlers.d.ts +5 -2
- package/node_modules/@comis/daemon/dist/rpc/agent-handlers.js +80 -1
- package/node_modules/@comis/daemon/dist/rpc/agent-inline-workspace.d.ts +67 -0
- package/node_modules/@comis/daemon/dist/rpc/agent-inline-workspace.js +139 -0
- package/node_modules/@comis/daemon/dist/rpc/model-handlers.d.ts +3 -0
- package/node_modules/@comis/daemon/dist/rpc/model-handlers.js +29 -5
- package/node_modules/@comis/daemon/dist/rpc/probe-provider-auth.d.ts +30 -0
- package/node_modules/@comis/daemon/dist/rpc/probe-provider-auth.js +59 -0
- package/node_modules/@comis/daemon/dist/rpc/provider-handlers.d.ts +37 -0
- package/node_modules/@comis/daemon/dist/rpc/provider-handlers.js +330 -0
- package/node_modules/@comis/daemon/dist/rpc/rpc-dispatch.js +18 -1
- package/node_modules/@comis/daemon/dist/setup-docker-restart-warn.d.ts +4 -0
- package/node_modules/@comis/daemon/dist/setup-docker-restart-warn.js +30 -0
- package/node_modules/@comis/daemon/dist/wiring/setup-agents.d.ts +3 -1
- package/node_modules/@comis/daemon/dist/wiring/setup-agents.js +28 -2
- package/node_modules/@comis/daemon/dist/wiring/setup-cross-session.js +1 -0
- package/node_modules/@comis/daemon/dist/wiring/setup-tools.js +7 -4
- package/node_modules/@comis/daemon/package.json +1 -1
- package/node_modules/@comis/gateway/package.json +1 -1
- package/node_modules/@comis/infra/dist/index.d.ts +1 -0
- package/node_modules/@comis/infra/dist/index.js +2 -0
- package/node_modules/@comis/infra/dist/runtime/is-docker.d.ts +1 -0
- package/node_modules/@comis/infra/dist/runtime/is-docker.js +25 -0
- package/node_modules/@comis/infra/package.json +1 -1
- package/node_modules/@comis/memory/package.json +1 -1
- package/node_modules/@comis/scheduler/package.json +1 -1
- package/node_modules/@comis/shared/package.json +1 -1
- package/node_modules/@comis/skills/dist/bridge/tool-metadata-registry.js +1 -3
- package/node_modules/@comis/skills/dist/builtin/platform/admin-manage-factory.js +24 -1
- package/node_modules/@comis/skills/dist/builtin/platform/agents-manage-tool.d.ts +53 -7
- package/node_modules/@comis/skills/dist/builtin/platform/agents-manage-tool.js +218 -24
- package/node_modules/@comis/skills/dist/builtin/platform/gateway-tool.d.ts +4 -1
- package/node_modules/@comis/skills/dist/builtin/platform/gateway-tool.js +16 -1
- package/node_modules/@comis/skills/dist/builtin/platform/index.d.ts +1 -1
- package/node_modules/@comis/skills/dist/builtin/platform/index.js +1 -1
- package/node_modules/@comis/skills/dist/builtin/platform/providers-manage-tool.d.ts +56 -0
- package/node_modules/@comis/skills/dist/builtin/platform/providers-manage-tool.js +203 -0
- package/node_modules/@comis/skills/dist/index.d.ts +1 -1
- package/node_modules/@comis/skills/dist/index.js +2 -2
- package/node_modules/@comis/skills/dist/policy/tool-policy.js +0 -1
- package/node_modules/@comis/skills/package.json +1 -1
- package/node_modules/@comis/web/dist/assets/{agent-detail-BG9MGWWj.js → agent-detail-DqL6Artv.js} +270 -270
- package/node_modules/@comis/web/dist/assets/agent-editor-CNM_h94Y.js +2173 -0
- package/node_modules/@comis/web/dist/assets/{agent-list-LHCJ4rw2.js → agent-list-Dbh-xD_F.js} +170 -170
- package/node_modules/@comis/web/dist/assets/{approvals-q9VH_IKr.js → approvals-C-K6hN2U.js} +13 -13
- package/node_modules/@comis/web/dist/assets/billing-view-C1DmtyzK.js +375 -0
- package/node_modules/@comis/web/dist/assets/{channel-detail-CaInesJM.js → channel-detail-CtCH22N1.js} +265 -265
- package/node_modules/@comis/web/dist/assets/channel-list-C7xXn-60.js +323 -0
- package/node_modules/@comis/web/dist/assets/{chat-console-CNmzl0JW.js → chat-console-C51pjFwk.js} +243 -246
- package/node_modules/@comis/web/dist/assets/{config-editor-DX4ITw6y.js → config-editor-BLArYRB7.js} +477 -477
- package/node_modules/@comis/web/dist/assets/{context-dag-browser-BwiaF5tf.js → context-dag-browser-fuyMinNI.js} +105 -105
- package/node_modules/@comis/web/dist/assets/{context-engine-BZ5Am6hA.js → context-engine-Bngf2bH0.js} +136 -136
- package/node_modules/@comis/web/dist/assets/decorate-BvWYovGE.js +38 -0
- package/node_modules/@comis/web/dist/assets/{delivery-view-OfBZof-m.js → delivery-view-C80hucxX.js} +134 -134
- package/node_modules/@comis/web/dist/assets/{diagnostics-view-YFwCxgr2.js → diagnostics-view-Cl4VbHZ6.js} +82 -82
- package/node_modules/@comis/web/dist/assets/directive-BOYXJ-K-.js +1 -0
- package/node_modules/@comis/web/dist/assets/{extract-variables-BM5qyK-s.js → extract-variables-B7-Doo7l.js} +39 -39
- package/node_modules/@comis/web/dist/assets/{ic-array-editor-B7m6x7-S.js → ic-array-editor-BLoEyeLS.js} +29 -29
- package/node_modules/@comis/web/dist/assets/{ic-breadcrumb-CUMpp3BL.js → ic-breadcrumb-DqN6G3gc.js} +16 -16
- package/node_modules/@comis/web/dist/assets/{ic-budget-segment-bar-BtJ6x5mN.js → ic-budget-segment-bar-zLsMzjDO.js} +20 -20
- package/node_modules/@comis/web/dist/assets/ic-chat-message-ByFUoMm6.js +352 -0
- package/node_modules/@comis/web/dist/assets/{ic-confirm-dialog-CCDbB04e.js → ic-confirm-dialog-DGlPbV1T.js} +26 -26
- package/node_modules/@comis/web/dist/assets/{ic-connection-dot-CnT1b8xr.js → ic-connection-dot-C4nDHgY2.js} +13 -13
- package/node_modules/@comis/web/dist/assets/ic-data-table-CKIvr-ag.js +277 -0
- package/node_modules/@comis/web/dist/assets/ic-delivery-row-B3YwjjuM.js +67 -0
- package/node_modules/@comis/web/dist/assets/{ic-detail-panel-BF83r-if.js → ic-detail-panel-DiCe4hLr.js} +27 -27
- package/node_modules/@comis/web/dist/assets/{ic-empty-state-60l2ePhd.js → ic-empty-state-CM3Wbj2f.js} +19 -19
- package/node_modules/@comis/web/dist/assets/ic-graph-canvas-ByRjij68.js +359 -0
- package/node_modules/@comis/web/dist/assets/ic-icon-BGNCCPpZ.js +33 -0
- package/node_modules/@comis/web/dist/assets/{ic-layer-waterfall-COvEYMg5.js → ic-layer-waterfall-WkaFyu-l.js} +18 -18
- package/node_modules/@comis/web/dist/assets/ic-relative-time-B3UAnTqg.js +12 -0
- package/node_modules/@comis/web/dist/assets/{ic-search-input-CSOxY9g7.js → ic-search-input-B02AGw1i.js} +22 -22
- package/node_modules/@comis/web/dist/assets/{ic-select-Ce-Raudx.js → ic-select-BqfZISjw.js} +29 -29
- package/node_modules/@comis/web/dist/assets/ic-tabs-yBjkWKJH.js +95 -0
- package/node_modules/@comis/web/dist/assets/ic-tag-CvMVQFRR.js +33 -0
- package/node_modules/@comis/web/dist/assets/{ic-time-range-picker-CypCT68y.js → ic-time-range-picker-DXbYeBmY.js} +31 -31
- package/node_modules/@comis/web/dist/assets/{ic-tool-call-7MaXSsCW.js → ic-tool-call-Bh5kq-yY.js} +51 -51
- package/node_modules/@comis/web/dist/assets/index-BBkuC-EU.js +2792 -0
- package/node_modules/@comis/web/dist/assets/index-CVEaS9aY.css +2 -0
- package/node_modules/@comis/web/dist/assets/{mcp-management-BNZPnpDn.js → mcp-management-DB-phOo7.js} +209 -209
- package/node_modules/@comis/web/dist/assets/{media-config-BBvTYxOX.js → media-config-CRqZ1ZUH.js} +154 -154
- package/node_modules/@comis/web/dist/assets/{media-test-BkK3RCRK.js → media-test-C9vE20Oy.js} +259 -259
- package/node_modules/@comis/web/dist/assets/{memory-inspector-1hDGCGat.js → memory-inspector-CeqfnxMZ.js} +450 -450
- package/node_modules/@comis/web/dist/assets/{message-center-CXefwsUu.js → message-center-Daup7Mof.js} +290 -290
- package/node_modules/@comis/web/dist/assets/{models-C1qcU_j3.js → models-DLYnEU8E.js} +371 -371
- package/node_modules/@comis/web/dist/assets/observability-types-D0tkwElU.js +1 -0
- package/node_modules/@comis/web/dist/assets/{observe-view-C0VBhX4C.js → observe-view-BTSt_PO5.js} +399 -399
- package/node_modules/@comis/web/dist/assets/pipeline-builder-DknfzyLt.js +1495 -0
- package/node_modules/@comis/web/dist/assets/{pipeline-history-DkfOQ6SW.js → pipeline-history-JnHZdeU_.js} +124 -124
- package/node_modules/@comis/web/dist/assets/{pipeline-history-detail-hyHgD0ai.js → pipeline-history-detail-Dg4knsEb.js} +65 -65
- package/node_modules/@comis/web/dist/assets/{pipeline-list-BPW8hV-q.js → pipeline-list-AEnibjsp.js} +227 -227
- package/node_modules/@comis/web/dist/assets/{pipeline-monitor-Bip16T7e.js → pipeline-monitor-DG7RbIOO.js} +298 -298
- package/node_modules/@comis/web/dist/assets/{scheduler-BGgwKd06.js → scheduler-uL1fYKAT.js} +486 -486
- package/node_modules/@comis/web/dist/assets/{security-D15st4xx.js → security-C3DywRLH.js} +389 -389
- package/node_modules/@comis/web/dist/assets/{session-detail-SGEYNJ0M.js → session-detail-BtqCNWXV.js} +294 -294
- package/node_modules/@comis/web/dist/assets/session-key-parser-Dkqcj2Ss.js +1 -0
- package/node_modules/@comis/web/dist/assets/session-list-CJXWa2XT.js +231 -0
- package/node_modules/@comis/web/dist/assets/{setup-wizard-nT0tz9QP.js → setup-wizard-ywn7oJvu.js} +486 -494
- package/node_modules/@comis/web/dist/assets/{skills-D8yVfSUy.js → skills-DX0KYnWD.js} +329 -329
- package/node_modules/@comis/web/dist/assets/{subagents-HHXMeHYo.js → subagents-B8p5YJEB.js} +74 -74
- package/node_modules/@comis/web/dist/assets/{workspace-manager-BQlr10iH.js → workspace-manager-CgzNIrw1.js} +236 -236
- package/node_modules/@comis/web/dist/index.html +3 -2
- package/node_modules/@comis/web/package.json +1 -1
- package/package.json +15 -15
- package/node_modules/@comis/skills/dist/builtin/platform/agents-list-tool.d.ts +0 -19
- package/node_modules/@comis/skills/dist/builtin/platform/agents-list-tool.js +0 -39
- package/node_modules/@comis/web/dist/assets/agent-editor-C26Q_xCs.js +0 -2173
- package/node_modules/@comis/web/dist/assets/billing-view-CtYvBqTE.js +0 -375
- package/node_modules/@comis/web/dist/assets/channel-list-B8dj3O9a.js +0 -323
- package/node_modules/@comis/web/dist/assets/directive-DoeGSK_T.js +0 -1
- package/node_modules/@comis/web/dist/assets/ic-chat-message-CFyDJd0z.js +0 -352
- package/node_modules/@comis/web/dist/assets/ic-data-table-CKUNTxHw.js +0 -277
- package/node_modules/@comis/web/dist/assets/ic-delivery-row-GP5Fkygs.js +0 -67
- package/node_modules/@comis/web/dist/assets/ic-graph-canvas-C8FuSMe1.js +0 -359
- package/node_modules/@comis/web/dist/assets/ic-icon-xeGTVhVG.js +0 -33
- package/node_modules/@comis/web/dist/assets/ic-relative-time-3FqpjeAI.js +0 -12
- package/node_modules/@comis/web/dist/assets/ic-tabs-B7QtM_v8.js +0 -95
- package/node_modules/@comis/web/dist/assets/ic-tag-CPPUnDLF.js +0 -33
- package/node_modules/@comis/web/dist/assets/index-CEcM1R_C.js +0 -2830
- package/node_modules/@comis/web/dist/assets/index-CIJFuItj.css +0 -1
- package/node_modules/@comis/web/dist/assets/observability-types-D7jUtSde.js +0 -1
- package/node_modules/@comis/web/dist/assets/pipeline-builder-DcUUIrm0.js +0 -1496
- package/node_modules/@comis/web/dist/assets/session-key-parser-DPORMVyU.js +0 -1
- 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
|
|
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
|
-
|
|
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 ||
|
|
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
|
-
?
|
|
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:
|
|
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
|
-
|
|
144
|
-
|
|
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
|
}
|
|
@@ -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}://${
|
|
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
|
|
84
|
-
const
|
|
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
|
|
82
|
-
const
|
|
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
|
-
|
|
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
|
-
? "
|
|
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://${
|
|
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();
|
|
@@ -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.
|
|
29
|
-
* @param pattern - Override pattern with `*` wildcards (e.g., "agents.*.
|
|
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.
|
|
43
|
-
* @param pattern - Override pattern with `*` wildcards (e.g., "agents.*.
|
|
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>;
|