comisai 1.0.36 → 1.0.37
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/background/auto-background-middleware.js +9 -0
- package/node_modules/@comis/agent/dist/background/background-task-manager.d.ts +22 -2
- package/node_modules/@comis/agent/dist/background/background-task-manager.js +48 -41
- package/node_modules/@comis/agent/dist/background/background-task-persistence.js +28 -5
- package/node_modules/@comis/agent/dist/background/background-task-types.d.ts +49 -0
- package/node_modules/@comis/agent/dist/background/completion-dispatcher.d.ts +130 -0
- package/node_modules/@comis/agent/dist/background/completion-dispatcher.js +215 -0
- package/node_modules/@comis/agent/dist/background/completion-runner.d.ts +10 -1
- package/node_modules/@comis/agent/dist/background/completion-runner.js +98 -15
- package/node_modules/@comis/agent/dist/background/index.d.ts +6 -1
- package/node_modules/@comis/agent/dist/background/index.js +2 -0
- package/node_modules/@comis/agent/dist/background/session-resolver.d.ts +85 -0
- package/node_modules/@comis/agent/dist/background/session-resolver.js +78 -0
- package/node_modules/@comis/agent/dist/bootstrap/sections/messaging-sections.js +1 -0
- package/node_modules/@comis/agent/dist/bootstrap/sections/tool-descriptions.js +3 -3
- package/node_modules/@comis/agent/dist/bootstrap/sections/tooling-sections.d.ts +30 -2
- package/node_modules/@comis/agent/dist/bootstrap/sections/tooling-sections.js +51 -2
- package/node_modules/@comis/agent/dist/bootstrap/system-prompt-assembler.d.ts +22 -0
- package/node_modules/@comis/agent/dist/bootstrap/system-prompt-assembler.js +2 -2
- package/node_modules/@comis/agent/dist/bridge/bridge-event-handlers.d.ts +1 -5
- package/node_modules/@comis/agent/dist/bridge/bridge-event-handlers.js +2 -14
- package/node_modules/@comis/agent/dist/bridge/bridge-metrics.d.ts +26 -0
- package/node_modules/@comis/agent/dist/bridge/bridge-metrics.js +3 -0
- package/node_modules/@comis/agent/dist/bridge/pi-event-bridge.d.ts +9 -0
- package/node_modules/@comis/agent/dist/bridge/pi-event-bridge.js +73 -2
- package/node_modules/@comis/agent/dist/context-engine/signature-surrogate-guard.d.ts +10 -10
- package/node_modules/@comis/agent/dist/context-engine/signature-surrogate-guard.js +14 -14
- package/node_modules/@comis/agent/dist/context-engine/thinking-block-cleaner.d.ts +11 -13
- package/node_modules/@comis/agent/dist/context-engine/thinking-block-cleaner.js +14 -15
- package/node_modules/@comis/agent/dist/executor/capability-index-context.d.ts +72 -0
- package/node_modules/@comis/agent/dist/executor/capability-index-context.js +329 -0
- package/node_modules/@comis/agent/dist/executor/drain-helper.d.ts +122 -0
- package/node_modules/@comis/agent/dist/executor/drain-helper.js +173 -0
- package/node_modules/@comis/agent/dist/executor/error-classifier.js +2 -2
- package/node_modules/@comis/agent/dist/executor/executor-post-execution.d.ts +48 -4
- package/node_modules/@comis/agent/dist/executor/executor-post-execution.js +134 -31
- package/node_modules/@comis/agent/dist/executor/executor-prompt-runner.d.ts +7 -0
- package/node_modules/@comis/agent/dist/executor/executor-prompt-runner.js +25 -4
- package/node_modules/@comis/agent/dist/executor/executor-tool-assembly.d.ts +18 -1
- package/node_modules/@comis/agent/dist/executor/executor-tool-assembly.js +19 -16
- package/node_modules/@comis/agent/dist/executor/jit-guide-injector.d.ts +11 -2
- package/node_modules/@comis/agent/dist/executor/jit-guide-injector.js +16 -2
- package/node_modules/@comis/agent/dist/executor/pi-executor.d.ts +8 -2
- package/node_modules/@comis/agent/dist/executor/pi-executor.js +25 -12
- package/node_modules/@comis/agent/dist/executor/prompt-assembly.d.ts +9 -1
- package/node_modules/@comis/agent/dist/executor/prompt-assembly.js +15 -1
- package/node_modules/@comis/agent/dist/executor/tool-deferral.d.ts +18 -27
- package/node_modules/@comis/agent/dist/executor/tool-deferral.js +29 -38
- package/node_modules/@comis/agent/dist/model/model-registry-adapter.js +1 -1
- package/node_modules/@comis/agent/dist/model/model-scanner.js +1 -1
- package/node_modules/@comis/agent/dist/safety/tool-retry-breaker.d.ts +11 -1
- package/node_modules/@comis/agent/dist/safety/tool-retry-breaker.js +19 -22
- package/node_modules/@comis/agent/dist/session/comis-session-manager.d.ts +16 -2
- package/node_modules/@comis/agent/dist/spawn/pi-mono-adapters.d.ts +1 -1
- package/node_modules/@comis/agent/dist/spawn/pi-mono-adapters.js +5 -5
- package/node_modules/@comis/agent/dist/workspace/data-env.d.ts +38 -0
- package/node_modules/@comis/agent/dist/workspace/data-env.js +56 -0
- package/node_modules/@comis/agent/dist/workspace/index.d.ts +1 -0
- package/node_modules/@comis/agent/dist/workspace/index.js +1 -0
- package/node_modules/@comis/agent/dist/workspace/templates.js +5 -1
- package/node_modules/@comis/agent/package.json +1 -1
- package/node_modules/@comis/channels/dist/index.d.ts +1 -1
- package/node_modules/@comis/channels/dist/index.js +1 -1
- package/node_modules/@comis/channels/dist/shared/channel-manager.d.ts +9 -3
- package/node_modules/@comis/channels/dist/shared/inbound-gate.d.ts +1 -1
- package/node_modules/@comis/channels/dist/shared/inbound-gate.js +22 -7
- package/node_modules/@comis/channels/dist/shared/inbound-pipeline.d.ts +10 -3
- package/node_modules/@comis/channels/dist/shared/inbound-route.d.ts +1 -1
- package/node_modules/@comis/channels/dist/shared/inbound-route.js +13 -2
- package/node_modules/@comis/channels/dist/shared/response-filter.d.ts +11 -24
- package/node_modules/@comis/channels/dist/shared/response-filter.js +25 -53
- package/node_modules/@comis/channels/package.json +1 -1
- package/node_modules/@comis/cli/dist/commands/providers.d.ts +1 -2
- package/node_modules/@comis/cli/dist/commands/providers.js +5 -6
- package/node_modules/@comis/cli/package.json +1 -1
- package/node_modules/@comis/core/dist/config/field-metadata.js +2 -0
- package/node_modules/@comis/core/dist/config/immutable-keys.js +4 -1
- package/node_modules/@comis/core/dist/config/index.d.ts +4 -0
- package/node_modules/@comis/core/dist/config/index.js +2 -0
- package/node_modules/@comis/core/dist/config/schema-agent.d.ts +0 -792
- package/node_modules/@comis/core/dist/config/schema-approvals.d.ts +0 -14
- package/node_modules/@comis/core/dist/config/schema-auto-reply-engine.d.ts +0 -6
- package/node_modules/@comis/core/dist/config/schema-background-tasks.d.ts +0 -12
- package/node_modules/@comis/core/dist/config/schema-browser.d.ts +0 -18
- package/node_modules/@comis/core/dist/config/schema-channel.d.ts +0 -158
- package/node_modules/@comis/core/dist/config/schema-coalescer.d.ts +0 -5
- package/node_modules/@comis/core/dist/config/schema-daemon.d.ts +0 -32
- package/node_modules/@comis/core/dist/config/schema-delivery.d.ts +0 -18
- package/node_modules/@comis/core/dist/config/schema-documentation.d.ts +0 -12
- package/node_modules/@comis/core/dist/config/schema-embedding.d.ts +0 -20
- package/node_modules/@comis/core/dist/config/schema-envelope.d.ts +0 -15
- package/node_modules/@comis/core/dist/config/schema-gateway.d.ts +0 -37
- package/node_modules/@comis/core/dist/config/schema-gemini-cache.d.ts +0 -2
- package/node_modules/@comis/core/dist/config/schema-integrations.d.ts +0 -318
- package/node_modules/@comis/core/dist/config/schema-lifecycle-reactions.d.ts +0 -18
- package/node_modules/@comis/core/dist/config/schema-memory-review.d.ts +0 -7
- package/node_modules/@comis/core/dist/config/schema-memory.d.ts +0 -16
- package/node_modules/@comis/core/dist/config/schema-messages.d.ts +0 -8
- package/node_modules/@comis/core/dist/config/schema-models.d.ts +0 -15
- package/node_modules/@comis/core/dist/config/schema-notification.d.ts +0 -5
- package/node_modules/@comis/core/dist/config/schema-oauth.d.ts +0 -5
- package/node_modules/@comis/core/dist/config/schema-observability.d.ts +0 -38
- package/node_modules/@comis/core/dist/config/schema-output-retention.d.ts +34 -0
- package/node_modules/@comis/core/dist/config/schema-output-retention.js +48 -0
- package/node_modules/@comis/core/dist/config/schema-plugins.d.ts +0 -8
- package/node_modules/@comis/core/dist/config/schema-providers.d.ts +0 -64
- package/node_modules/@comis/core/dist/config/schema-queue.d.ts +0 -58
- package/node_modules/@comis/core/dist/config/schema-response-prefix.d.ts +0 -2
- package/node_modules/@comis/core/dist/config/schema-retry.d.ts +0 -6
- package/node_modules/@comis/core/dist/config/schema-scheduler.d.ts +0 -39
- package/node_modules/@comis/core/dist/config/schema-secrets.d.ts +0 -3
- package/node_modules/@comis/core/dist/config/schema-security.d.ts +0 -18
- package/node_modules/@comis/core/dist/config/schema-send-policy.d.ts +0 -13
- package/node_modules/@comis/core/dist/config/schema-sender-trust-display.d.ts +0 -5
- package/node_modules/@comis/core/dist/config/schema-serializer.js +2 -0
- package/node_modules/@comis/core/dist/config/schema-skills.d.ts +0 -61
- package/node_modules/@comis/core/dist/config/schema-streaming.d.ts +0 -38
- package/node_modules/@comis/core/dist/config/schema-telegram-file-guard.d.ts +0 -3
- package/node_modules/@comis/core/dist/config/schema-tooling.d.ts +87 -0
- package/node_modules/@comis/core/dist/config/schema-tooling.js +152 -0
- package/node_modules/@comis/core/dist/config/schema-verbosity.d.ts +0 -12
- package/node_modules/@comis/core/dist/config/schema-webhooks.d.ts +0 -40
- package/node_modules/@comis/core/dist/config/schema.d.ts +41 -38
- package/node_modules/@comis/core/dist/config/schema.js +6 -0
- package/node_modules/@comis/core/dist/context/context.d.ts +0 -4
- package/node_modules/@comis/core/dist/domain/approval-request.d.ts +0 -17
- package/node_modules/@comis/core/dist/domain/background-task-origin.d.ts +0 -10
- package/node_modules/@comis/core/dist/domain/delivery-origin.d.ts +0 -5
- package/node_modules/@comis/core/dist/domain/execution-graph.d.ts +0 -48
- package/node_modules/@comis/core/dist/domain/memory-entry.d.ts +0 -3
- package/node_modules/@comis/core/dist/domain/model-compat.d.ts +0 -4
- package/node_modules/@comis/core/dist/domain/normalized-message.d.ts +0 -15
- package/node_modules/@comis/core/dist/domain/provider-capabilities.d.ts +0 -6
- package/node_modules/@comis/core/dist/domain/rich-message.d.ts +0 -14
- package/node_modules/@comis/core/dist/domain/subagent-context-config.d.ts +0 -22
- package/node_modules/@comis/core/dist/domain/subagent-context-types.d.ts +0 -8
- package/node_modules/@comis/core/dist/event-bus/events-agent.d.ts +31 -0
- package/node_modules/@comis/core/dist/event-bus/events-infra.d.ts +5 -0
- package/node_modules/@comis/core/dist/exports/config.d.ts +2 -2
- package/node_modules/@comis/core/dist/exports/config.js +3 -1
- package/node_modules/@comis/core/dist/exports/hooks.d.ts +1 -1
- package/node_modules/@comis/core/dist/exports/ports.d.ts +2 -2
- package/node_modules/@comis/core/dist/exports/ports.js +1 -1
- package/node_modules/@comis/core/dist/ports/channel-plugin.d.ts +0 -13
- package/node_modules/@comis/core/dist/ports/index.d.ts +2 -0
- package/node_modules/@comis/core/dist/ports/index.js +4 -0
- package/node_modules/@comis/core/dist/ports/no-op-tool-capability.d.ts +30 -0
- package/node_modules/@comis/core/dist/ports/no-op-tool-capability.js +47 -0
- package/node_modules/@comis/core/dist/ports/tool-capability.d.ts +165 -0
- package/node_modules/@comis/core/dist/ports/tool-capability.js +15 -0
- package/node_modules/@comis/core/dist/security/audit.d.ts +0 -11
- package/node_modules/@comis/core/dist/tool-metadata.d.ts +21 -1
- package/node_modules/@comis/core/dist/tool-metadata.js +1 -1
- package/node_modules/@comis/core/package.json +1 -1
- package/node_modules/@comis/daemon/bundled-skills/skill-creator/scripts/validate-skill.py +1 -1
- package/node_modules/@comis/daemon/dist/daemon.js +89 -14
- package/node_modules/@comis/daemon/dist/rpc/agent-inline-workspace.d.ts +1 -1
- package/node_modules/@comis/daemon/dist/rpc/agent-inline-workspace.js +1 -1
- package/node_modules/@comis/daemon/dist/rpc/builtin-provider-guard.js +2 -2
- package/node_modules/@comis/daemon/dist/rpc/credential-resolver.js +1 -1
- package/node_modules/@comis/daemon/dist/rpc/model-handlers.d.ts +1 -1
- package/node_modules/@comis/daemon/dist/rpc/model-handlers.js +2 -2
- package/node_modules/@comis/daemon/dist/sub-agent-runner.d.ts +18 -0
- package/node_modules/@comis/daemon/dist/sub-agent-runner.js +41 -9
- package/node_modules/@comis/daemon/dist/wiring/index.d.ts +2 -0
- package/node_modules/@comis/daemon/dist/wiring/index.js +1 -0
- package/node_modules/@comis/daemon/dist/wiring/setup-agents.d.ts +36 -2
- package/node_modules/@comis/daemon/dist/wiring/setup-agents.js +45 -8
- package/node_modules/@comis/daemon/dist/wiring/setup-background-completion-runner.d.ts +28 -9
- package/node_modules/@comis/daemon/dist/wiring/setup-background-completion-runner.js +36 -9
- package/node_modules/@comis/daemon/dist/wiring/setup-background-tasks.js +2 -2
- package/node_modules/@comis/daemon/dist/wiring/setup-channels.d.ts +9 -2
- package/node_modules/@comis/daemon/dist/wiring/setup-channels.js +15 -9
- package/node_modules/@comis/daemon/dist/wiring/setup-cross-session.d.ts +20 -5
- package/node_modules/@comis/daemon/dist/wiring/setup-cross-session.js +20 -15
- package/node_modules/@comis/daemon/dist/wiring/setup-delivery.js +14 -2
- package/node_modules/@comis/daemon/dist/wiring/setup-gateway.d.ts +4 -6
- package/node_modules/@comis/daemon/dist/wiring/setup-gateway.js +3 -5
- package/node_modules/@comis/daemon/dist/wiring/setup-heartbeat.d.ts +20 -5
- package/node_modules/@comis/daemon/dist/wiring/setup-heartbeat.js +11 -2
- package/node_modules/@comis/daemon/dist/wiring/setup-output-retention.d.ts +89 -0
- package/node_modules/@comis/daemon/dist/wiring/setup-output-retention.js +212 -0
- package/node_modules/@comis/daemon/dist/wiring/setup-tools.d.ts +18 -4
- package/node_modules/@comis/daemon/dist/wiring/setup-tools.js +29 -10
- package/node_modules/@comis/daemon/dist/wiring/tool-capability-adapter.d.ts +75 -0
- package/node_modules/@comis/daemon/dist/wiring/tool-capability-adapter.js +253 -0
- package/node_modules/@comis/daemon/package.json +1 -1
- package/node_modules/@comis/gateway/dist/webhook/webhook-endpoint.d.ts +0 -4
- package/node_modules/@comis/gateway/package.json +1 -1
- package/node_modules/@comis/infra/package.json +1 -1
- package/node_modules/@comis/memory/package.json +1 -1
- package/node_modules/@comis/scheduler/dist/cron/cron-types.d.ts +0 -42
- package/node_modules/@comis/scheduler/dist/heartbeat/agent-heartbeat-source.d.ts +29 -8
- package/node_modules/@comis/scheduler/dist/heartbeat/agent-heartbeat-source.js +19 -7
- package/node_modules/@comis/scheduler/dist/system-events/system-event-types.d.ts +0 -3
- package/node_modules/@comis/scheduler/dist/tasks/task-types.d.ts +0 -17
- package/node_modules/@comis/scheduler/package.json +1 -1
- package/node_modules/@comis/shared/dist/index.d.ts +3 -0
- package/node_modules/@comis/shared/dist/index.js +4 -0
- package/node_modules/@comis/shared/dist/mcp-tool-name.d.ts +78 -0
- package/node_modules/@comis/shared/dist/mcp-tool-name.js +92 -0
- package/node_modules/@comis/shared/dist/silent-tokens.d.ts +38 -0
- package/node_modules/@comis/shared/dist/silent-tokens.js +51 -0
- package/node_modules/@comis/shared/dist/visible-delivery.d.ts +28 -0
- package/node_modules/@comis/shared/dist/visible-delivery.js +16 -0
- package/node_modules/@comis/shared/package.json +1 -1
- package/node_modules/@comis/skills/dist/bridge/mcp-tool-bridge.d.ts +2 -13
- package/node_modules/@comis/skills/dist/bridge/mcp-tool-bridge.js +3 -21
- package/node_modules/@comis/skills/dist/bridge/tool-metadata-enforcement.js +1 -1
- package/node_modules/@comis/skills/dist/bridge/tool-metadata-registry.js +4 -4
- package/node_modules/@comis/skills/dist/builtin/exec-tool.d.ts +55 -9
- package/node_modules/@comis/skills/dist/builtin/exec-tool.js +383 -19
- package/node_modules/@comis/skills/dist/builtin/install-detour.d.ts +67 -0
- package/node_modules/@comis/skills/dist/builtin/install-detour.js +342 -0
- package/node_modules/@comis/skills/dist/builtin/platform/admin-manage-factory.js +5 -5
- package/node_modules/@comis/skills/dist/builtin/platform/agents-manage-tool.d.ts +2 -2
- package/node_modules/@comis/skills/dist/builtin/platform/agents-manage-tool.js +2 -2
- package/node_modules/@comis/skills/dist/builtin/platform/message-tool.js +18 -0
- package/node_modules/@comis/skills/dist/builtin/platform/messaging-factory.d.ts +18 -1
- package/node_modules/@comis/skills/dist/builtin/platform/messaging-factory.js +18 -2
- package/node_modules/@comis/skills/dist/builtin/platform/models-manage-tool.js +3 -3
- package/node_modules/@comis/skills/dist/builtin/process-registry.d.ts +14 -0
- package/node_modules/@comis/skills/dist/builtin/process-tool.d.ts +24 -4
- package/node_modules/@comis/skills/dist/builtin/process-tool.js +25 -7
- package/node_modules/@comis/skills/dist/builtin/sandbox/bwrap-provider.d.ts +1 -1
- package/node_modules/@comis/skills/dist/builtin/sandbox/bwrap-provider.js +9 -0
- package/node_modules/@comis/skills/dist/index.d.ts +4 -1
- package/node_modules/@comis/skills/dist/index.js +3 -1
- package/node_modules/@comis/skills/dist/manifest/capability-parser.d.ts +44 -0
- package/node_modules/@comis/skills/dist/manifest/capability-parser.js +68 -0
- package/node_modules/@comis/skills/dist/manifest/schema.d.ts +44 -37
- package/node_modules/@comis/skills/dist/manifest/schema.js +35 -0
- package/node_modules/@comis/skills/dist/registry/discovery.d.ts +8 -0
- package/node_modules/@comis/skills/dist/registry/discovery.js +10 -3
- package/node_modules/@comis/skills/dist/registry/skill-registry.d.ts +45 -1
- package/node_modules/@comis/skills/dist/registry/skill-registry.js +70 -7
- package/node_modules/@comis/skills/package.json +1 -1
- package/node_modules/@comis/web/package.json +1 -1
- package/package.json +21 -21
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Per-turn capability-index renderer.
|
|
4
|
+
*
|
|
5
|
+
* Renders the `## Capabilities` block for the dynamic preamble. Lives
|
|
6
|
+
* post-deferral in the executor lifecycle; consumes a `ToolCapabilityPort`
|
|
7
|
+
* for cluster/skill resolution and an `ExcludeDeferralResult` for active
|
|
8
|
+
* vs deferred tool partitioning.
|
|
9
|
+
*
|
|
10
|
+
* Pure-function builder: no logger, no IO, no `Result` envelope, no mutable
|
|
11
|
+
* module state beyond frozen module-level constants. Mirrors the shape of
|
|
12
|
+
* `buildDeferredToolsContext` in `tool-deferral.ts`.
|
|
13
|
+
*
|
|
14
|
+
* IMPORTANT -- cache fence:
|
|
15
|
+
* This module is consumed ONLY by `executor-tool-assembly.ts`. It MUST NOT
|
|
16
|
+
* be imported by `prompt-assembly.ts` -- the static prompt cache prefix MUST
|
|
17
|
+
* stay byte-identical when the skill registry reloads between turns.
|
|
18
|
+
* An architecture-grep enforces this invariant.
|
|
19
|
+
*
|
|
20
|
+
* @module
|
|
21
|
+
*/
|
|
22
|
+
import { extractMcpServerName } from "@comis/shared";
|
|
23
|
+
import { TOOL_ORDER } from "../bootstrap/sections/tool-descriptions.js";
|
|
24
|
+
import { CHARS_PER_TOKEN_RATIO } from "../context-engine/constants.js";
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Module-level frozen sentinel + constants
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
/**
|
|
29
|
+
* Frozen empty result. Returned when the gate is off or when all three
|
|
30
|
+
* surface counts are zero. Identity-stable so callers can do cheap reference
|
|
31
|
+
* equality checks if useful.
|
|
32
|
+
*/
|
|
33
|
+
const EMPTY = Object.freeze({
|
|
34
|
+
text: "",
|
|
35
|
+
capabilityIndexTokens: 0,
|
|
36
|
+
clusterCount: 0,
|
|
37
|
+
activeToolCount: 0,
|
|
38
|
+
deferredToolCount: 0,
|
|
39
|
+
promptSkillCount: 0,
|
|
40
|
+
});
|
|
41
|
+
/**
|
|
42
|
+
* Active-tool count threshold above which all per-cluster name lists are
|
|
43
|
+
* dropped (cluster headers + `(N tools)` counts remain).
|
|
44
|
+
* Constant in v1.1; revisit only if telemetry shows fleets clustering near it.
|
|
45
|
+
*/
|
|
46
|
+
const ELISION_THRESHOLD = 32;
|
|
47
|
+
/**
|
|
48
|
+
* Maximum names rendered per server (active MCP) or per skill cluster
|
|
49
|
+
* before `+N more` truncation.
|
|
50
|
+
*/
|
|
51
|
+
const PER_GROUP_NAME_CAP = 8;
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Reserved cluster IDs
|
|
54
|
+
//
|
|
55
|
+
// Inlined as string literals at the three fallback sites instead of named
|
|
56
|
+
// constants -- the IDs are part of the user-visible config schema in
|
|
57
|
+
// `packages/core/src/config/schema-tooling.ts`. Renaming them is intentionally
|
|
58
|
+
// not supported -- the cluster ID itself is fixed.
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Public renderer
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
/**
|
|
64
|
+
* Build the per-turn capability-index render result.
|
|
65
|
+
*
|
|
66
|
+
* Behavior:
|
|
67
|
+
* - Gate respect: returns {@link EMPTY} when `port.isCapabilityIndexEnabled()` is false.
|
|
68
|
+
* - Empty-input fast path: returns {@link EMPTY} when all three surface counts are zero.
|
|
69
|
+
* - Cluster bucketing: builtins -> `getBuiltinCluster()` (or `"other-tools"`),
|
|
70
|
+
* MCP -> `getMcpServerHint().cluster` (or `"external-integrations"`),
|
|
71
|
+
* skills -> `skill.cluster` (or `"prompt-skills"`).
|
|
72
|
+
* - Orphan-drop: deferred MCP tools whose server is not in the live
|
|
73
|
+
* `getConnectedMcpServers()` snapshot are dropped silently.
|
|
74
|
+
* - Sort: `(priority asc, clusterId asc)` for clusters; `TOOL_ORDER` for
|
|
75
|
+
* builtins (alphabetical fallback for unknowns); alphabetical for MCP
|
|
76
|
+
* servers and skills.
|
|
77
|
+
* - Per-group cap: 8 names + `+N more`.
|
|
78
|
+
* - >32 elision: drop ALL per-cluster name lists; keep headers + counts only.
|
|
79
|
+
* - Forbidden-literal discipline: the rendered text names neither the
|
|
80
|
+
* client-side discovery tool nor the server-side tool search regex tool.
|
|
81
|
+
* The deferred-tools preamble bullet uses the mechanism-neutral
|
|
82
|
+
* `"discovery mechanism available in your active toolspace"` wording.
|
|
83
|
+
* An architecture-grep enforces the file-level invariant.
|
|
84
|
+
*
|
|
85
|
+
* Restart-required note: `tooling.capabilityIndex.enabled` requires a daemon
|
|
86
|
+
* restart to take effect. The renderer respects the port's reported value at
|
|
87
|
+
* render time but does not enforce the restart constraint.
|
|
88
|
+
*
|
|
89
|
+
* @param deferralResult - Output of `applyToolDeferral` (active + deferred tool partition).
|
|
90
|
+
* @param port - The capability port (gate flag, cluster/skill resolution, live runtime view).
|
|
91
|
+
* @returns Frozen {@link CapabilityIndexRenderResult}; identity-stable {@link EMPTY} for the no-op path.
|
|
92
|
+
*/
|
|
93
|
+
export function buildCapabilityIndexContext(deferralResult, port) {
|
|
94
|
+
// Gate (restart-required).
|
|
95
|
+
if (!port.isCapabilityIndexEnabled())
|
|
96
|
+
return EMPTY;
|
|
97
|
+
// Snapshot the live runtime view ONCE per render. Re-querying the port
|
|
98
|
+
// mid-render would risk inconsistent state if a server connect/disconnect
|
|
99
|
+
// happens between two reads.
|
|
100
|
+
const connectedServers = new Set(port.getConnectedMcpServers());
|
|
101
|
+
const visibleSkills = port.getPromptSkillCapabilities();
|
|
102
|
+
// Bucket every input source into a clusterId -> ClusterRender map.
|
|
103
|
+
const clusterMap = new Map();
|
|
104
|
+
// Active builtin / non-MCP tools.
|
|
105
|
+
for (const tool of deferralResult.activeTools) {
|
|
106
|
+
if (extractMcpServerName(tool.name) !== undefined)
|
|
107
|
+
continue;
|
|
108
|
+
const clusterId = port.getBuiltinCluster(tool.name) ?? "other-tools";
|
|
109
|
+
const cluster = ensureCluster(clusterMap, clusterId, port);
|
|
110
|
+
cluster.builtins.push(tool.name);
|
|
111
|
+
}
|
|
112
|
+
// Active MCP tools. Group by server within their cluster.
|
|
113
|
+
for (const tool of deferralResult.activeTools) {
|
|
114
|
+
const server = extractMcpServerName(tool.name);
|
|
115
|
+
if (server === undefined)
|
|
116
|
+
continue;
|
|
117
|
+
const clusterId = port.getMcpServerHint(server)?.cluster ?? "external-integrations";
|
|
118
|
+
const cluster = ensureCluster(clusterMap, clusterId, port);
|
|
119
|
+
const bucket = ensureServerBucket(cluster, server);
|
|
120
|
+
bucket.activeTools.push(tool.name);
|
|
121
|
+
}
|
|
122
|
+
// Deferred MCP tools (with orphan-drop). Non-MCP deferred entries are
|
|
123
|
+
// dropped entirely -- a header-only shell would be misleading because the
|
|
124
|
+
// renderer cannot teach what to do with a non-MCP deferred name.
|
|
125
|
+
let deferredToolCount = 0;
|
|
126
|
+
for (const entry of deferralResult.deferredEntries) {
|
|
127
|
+
const server = extractMcpServerName(entry.name);
|
|
128
|
+
if (server === undefined)
|
|
129
|
+
continue;
|
|
130
|
+
if (!connectedServers.has(server))
|
|
131
|
+
continue; // orphan-drop
|
|
132
|
+
const clusterId = port.getMcpServerHint(server)?.cluster ?? "external-integrations";
|
|
133
|
+
const cluster = ensureCluster(clusterMap, clusterId, port);
|
|
134
|
+
const bucket = ensureServerBucket(cluster, server);
|
|
135
|
+
bucket.deferredCount += 1;
|
|
136
|
+
deferredToolCount += 1;
|
|
137
|
+
}
|
|
138
|
+
// Visible prompt skills. The port has already merged
|
|
139
|
+
// operator > comis.capability > fallback; we only resolve cluster.
|
|
140
|
+
for (const skill of visibleSkills) {
|
|
141
|
+
const clusterId = skill.cluster ?? "prompt-skills";
|
|
142
|
+
const cluster = ensureCluster(clusterMap, clusterId, port);
|
|
143
|
+
cluster.skills.push(skill);
|
|
144
|
+
}
|
|
145
|
+
// Compute totals.
|
|
146
|
+
let activeToolCount = 0;
|
|
147
|
+
for (const cluster of clusterMap.values()) {
|
|
148
|
+
activeToolCount += cluster.builtins.length;
|
|
149
|
+
for (const bucket of cluster.mcpServers.values()) {
|
|
150
|
+
activeToolCount += bucket.activeTools.length;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Empty-input fast path.
|
|
154
|
+
if (activeToolCount + deferredToolCount + visibleSkills.length === 0) {
|
|
155
|
+
return EMPTY;
|
|
156
|
+
}
|
|
157
|
+
// Sort clusters: (priority asc, clusterId asc).
|
|
158
|
+
const orderedClusters = [...clusterMap.values()].sort((a, b) => a.config.priority - b.config.priority || a.id.localeCompare(b.id));
|
|
159
|
+
// Determine elision: when total active exceeds 32, drop all per-cluster
|
|
160
|
+
// name lists (cluster headers + `(N tools)` counts remain).
|
|
161
|
+
const eliminateNameLists = activeToolCount > ELISION_THRESHOLD;
|
|
162
|
+
// Render the text envelope.
|
|
163
|
+
const lines = [];
|
|
164
|
+
lines.push("## Capabilities");
|
|
165
|
+
lines.push("");
|
|
166
|
+
lines.push("Map the task to one of these connected capabilities before using exec to install libraries.");
|
|
167
|
+
lines.push("");
|
|
168
|
+
lines.push("- Active tools: callable now.");
|
|
169
|
+
lines.push("- Deferred tools: connected, but load them through the discovery mechanism available in your active toolspace before invoking them.");
|
|
170
|
+
lines.push("- Prompt skills: available instructions/workflows; use the existing skill-loading mechanism when the task matches.");
|
|
171
|
+
for (const cluster of orderedClusters) {
|
|
172
|
+
lines.push("");
|
|
173
|
+
lines.push(`### ${cluster.config.label}`);
|
|
174
|
+
if (cluster.config.preferOverInstalls) {
|
|
175
|
+
lines.push("Prefer connected tools and available skills over installing equivalent libraries.");
|
|
176
|
+
}
|
|
177
|
+
if (eliminateNameLists) {
|
|
178
|
+
// Headers + count-only.
|
|
179
|
+
const tools = cluster.builtins.length + sumActiveServerTools(cluster);
|
|
180
|
+
lines.push(`(${tools} tools)`);
|
|
181
|
+
const deferredHere = sumDeferredServerTools(cluster);
|
|
182
|
+
if (deferredHere > 0) {
|
|
183
|
+
lines.push(`(${deferredHere} deferred tools)`);
|
|
184
|
+
}
|
|
185
|
+
if (cluster.skills.length > 0) {
|
|
186
|
+
lines.push(`(${cluster.skills.length} skills)`);
|
|
187
|
+
}
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
appendClusterBody(lines, cluster);
|
|
191
|
+
}
|
|
192
|
+
const text = lines.join("\n");
|
|
193
|
+
const clusterCount = orderedClusters.length;
|
|
194
|
+
return Object.freeze({
|
|
195
|
+
text,
|
|
196
|
+
capabilityIndexTokens: Math.ceil(text.length / CHARS_PER_TOKEN_RATIO),
|
|
197
|
+
clusterCount,
|
|
198
|
+
activeToolCount,
|
|
199
|
+
deferredToolCount,
|
|
200
|
+
promptSkillCount: visibleSkills.length,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
// Internal helpers (file-scoped; not exported)
|
|
205
|
+
// ---------------------------------------------------------------------------
|
|
206
|
+
/**
|
|
207
|
+
* Lookup-or-create a {@link ClusterRender} bucket for a cluster ID. The
|
|
208
|
+
* cluster's {@link ClusterConfig} resolves through the port; missing config
|
|
209
|
+
* for a non-reserved ID falls back to a synthesized default labelled by the
|
|
210
|
+
* cluster ID itself. The wiring layer emits a WARN for missing configs; the
|
|
211
|
+
* renderer only renders.
|
|
212
|
+
*/
|
|
213
|
+
function ensureCluster(map, clusterId, port) {
|
|
214
|
+
const existing = map.get(clusterId);
|
|
215
|
+
if (existing)
|
|
216
|
+
return existing;
|
|
217
|
+
const config = port.getClusterConfig(clusterId) ?? synthesizeClusterConfig(clusterId);
|
|
218
|
+
const cluster = {
|
|
219
|
+
id: clusterId,
|
|
220
|
+
config,
|
|
221
|
+
builtins: [],
|
|
222
|
+
mcpServers: new Map(),
|
|
223
|
+
skills: [],
|
|
224
|
+
};
|
|
225
|
+
map.set(clusterId, cluster);
|
|
226
|
+
return cluster;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Synthesize a {@link ClusterConfig} for a cluster ID the port does not
|
|
230
|
+
* recognize. This keeps the renderer total pure -- it never throws on
|
|
231
|
+
* misconfiguration. The wiring layer owns the WARN path; here we render
|
|
232
|
+
* with the cluster ID as both label and a sentinel `9999` priority (sorts
|
|
233
|
+
* last) and `preferOverInstalls: false`.
|
|
234
|
+
*/
|
|
235
|
+
function synthesizeClusterConfig(clusterId) {
|
|
236
|
+
return Object.freeze({
|
|
237
|
+
label: clusterId,
|
|
238
|
+
priority: 9999,
|
|
239
|
+
preferOverInstalls: false,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
function ensureServerBucket(cluster, server) {
|
|
243
|
+
const existing = cluster.mcpServers.get(server);
|
|
244
|
+
if (existing)
|
|
245
|
+
return existing;
|
|
246
|
+
const bucket = { activeTools: [], deferredCount: 0 };
|
|
247
|
+
cluster.mcpServers.set(server, bucket);
|
|
248
|
+
return bucket;
|
|
249
|
+
}
|
|
250
|
+
function sumActiveServerTools(cluster) {
|
|
251
|
+
let total = 0;
|
|
252
|
+
for (const bucket of cluster.mcpServers.values())
|
|
253
|
+
total += bucket.activeTools.length;
|
|
254
|
+
return total;
|
|
255
|
+
}
|
|
256
|
+
function sumDeferredServerTools(cluster) {
|
|
257
|
+
let total = 0;
|
|
258
|
+
for (const bucket of cluster.mcpServers.values())
|
|
259
|
+
total += bucket.deferredCount;
|
|
260
|
+
return total;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Sort builtin/non-MCP tool names within a cluster: known names follow
|
|
264
|
+
* {@link TOOL_ORDER}; unknown names fall through to alphabetical via
|
|
265
|
+
* `localeCompare`.
|
|
266
|
+
*/
|
|
267
|
+
function sortBuiltinsInCluster(builtins) {
|
|
268
|
+
const orderIndex = (name) => {
|
|
269
|
+
const idx = TOOL_ORDER.indexOf(name);
|
|
270
|
+
return idx === -1 ? Number.MAX_SAFE_INTEGER : idx;
|
|
271
|
+
};
|
|
272
|
+
return builtins.slice().sort((a, b) => {
|
|
273
|
+
const diff = orderIndex(a) - orderIndex(b);
|
|
274
|
+
if (diff !== 0)
|
|
275
|
+
return diff;
|
|
276
|
+
return a.localeCompare(b);
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Append the body of a single cluster (after its `### Label` heading and
|
|
281
|
+
* optional callout have already been pushed) to the line buffer. Renders:
|
|
282
|
+
* 1. Builtin/non-MCP tool names (TOOL_ORDER sort, alphabetical fallback).
|
|
283
|
+
* 2. MCP servers alphabetical, each with capped `+N more` tool list and
|
|
284
|
+
* optional `(N deferred)` suffix when deferred entries exist.
|
|
285
|
+
* 3. Prompt skills alphabetical, capped at 8 + `+N more`.
|
|
286
|
+
*
|
|
287
|
+
* Elision is handled by the caller (skip this body, emit count-only lines
|
|
288
|
+
* instead).
|
|
289
|
+
*/
|
|
290
|
+
function appendClusterBody(lines, cluster) {
|
|
291
|
+
if (cluster.builtins.length > 0) {
|
|
292
|
+
const sorted = sortBuiltinsInCluster(cluster.builtins);
|
|
293
|
+
lines.push(`- ${sorted.join(", ")}`);
|
|
294
|
+
}
|
|
295
|
+
const sortedServers = [...cluster.mcpServers.entries()].sort(([a], [b]) => a.localeCompare(b));
|
|
296
|
+
for (const [server, bucket] of sortedServers) {
|
|
297
|
+
const sortedTools = bucket.activeTools.slice().sort((a, b) => a.localeCompare(b));
|
|
298
|
+
const shortNames = sortedTools.map((full) => stripServerPrefix(full, server));
|
|
299
|
+
const head = shortNames.slice(0, PER_GROUP_NAME_CAP);
|
|
300
|
+
const overflow = shortNames.length - head.length;
|
|
301
|
+
const namesPart = head.length === 0
|
|
302
|
+
? ""
|
|
303
|
+
: `: ${head.join(", ")}${overflow > 0 ? `, +${overflow} more` : ""}`;
|
|
304
|
+
const deferredPart = bucket.deferredCount > 0
|
|
305
|
+
? ` (${bucket.deferredCount} deferred)`
|
|
306
|
+
: "";
|
|
307
|
+
lines.push(`- [${server}] (${bucket.activeTools.length} tools${deferredPart})${namesPart}`);
|
|
308
|
+
}
|
|
309
|
+
if (cluster.skills.length > 0) {
|
|
310
|
+
const sortedSkills = cluster.skills
|
|
311
|
+
.slice()
|
|
312
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
313
|
+
const head = sortedSkills.slice(0, PER_GROUP_NAME_CAP);
|
|
314
|
+
const overflow = sortedSkills.length - head.length;
|
|
315
|
+
const names = head.map((s) => s.name).join(", ");
|
|
316
|
+
const overflowText = overflow > 0 ? `, +${overflow} more` : "";
|
|
317
|
+
lines.push(`- skills: ${names}${overflowText}`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Strip the `mcp__<server>--` prefix from a sanitized MCP tool name so the
|
|
322
|
+
* cluster body shows compact short names. Falls back to the full name if
|
|
323
|
+
* the prefix does not match (defensive -- the upstream parser already
|
|
324
|
+
* validated the shape).
|
|
325
|
+
*/
|
|
326
|
+
function stripServerPrefix(toolName, server) {
|
|
327
|
+
const prefix = `mcp__${server}--`;
|
|
328
|
+
return toolName.startsWith(prefix) ? toolName.slice(prefix.length) : toolName;
|
|
329
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inline-consumption drain seams.
|
|
3
|
+
*
|
|
4
|
+
* The drain trigger lives at the BRIDGE call site (`tool_execution_end` for
|
|
5
|
+
* `message` send/reply/attach) -- NOT in pi-executor.ts. The helpers in
|
|
6
|
+
* this module are what the bridge invokes:
|
|
7
|
+
*
|
|
8
|
+
* - markRead(key): mark inbound messages for the composite key as read.
|
|
9
|
+
* Reads tool context via `tryGetContext()` so the
|
|
10
|
+
* function does NOT take a passed-in deps object.
|
|
11
|
+
* No-op outside an AsyncLocalStorage scope.
|
|
12
|
+
*
|
|
13
|
+
* - markConsumed(key): mark inbound messages for the composite key as
|
|
14
|
+
* consumed by the agent's response. Same context
|
|
15
|
+
* contract as markRead.
|
|
16
|
+
*
|
|
17
|
+
* - drainAt(key): orchestrator. Runs markRead + markConsumed under
|
|
18
|
+
* a per-composite-key single-tick inflight gate
|
|
19
|
+
* (`drainInflightByKey: Map<string, Promise<void>>`).
|
|
20
|
+
* Concurrent calls for the same composite key
|
|
21
|
+
* return immediately; concurrent calls for
|
|
22
|
+
* DIFFERENT composite keys (different agentId /
|
|
23
|
+
* channelType / channelId) drain independently.
|
|
24
|
+
* Failures are non-fatal: suppressError +
|
|
25
|
+
* structured WARN log. The drainInflightByKey state
|
|
26
|
+
* is owned by the bridge (BridgeMetricsState) so
|
|
27
|
+
* the bridge threads it into drainAt at each call
|
|
28
|
+
* site.
|
|
29
|
+
*
|
|
30
|
+
* The actual inline-consumption queue does not exist as a concrete data
|
|
31
|
+
* structure today -- this module provides the structural seam. Future
|
|
32
|
+
* work plugs queue/state into `tryGetContext()` so markRead / markConsumed
|
|
33
|
+
* read it without re-threading through every caller. Today the helpers
|
|
34
|
+
* are observability-only stubs that log at DEBUG when context is present
|
|
35
|
+
* and fall through silently when outside any request scope.
|
|
36
|
+
*
|
|
37
|
+
* This module lives in `packages/agent/src/executor/` (not in the bridge)
|
|
38
|
+
* so executor-post-execution.ts can re-export the helpers for source-grep
|
|
39
|
+
* tests, while the bridge imports from here directly. This avoids a
|
|
40
|
+
* circular import (executor-post-execution -> pi-executor -> bridge ->
|
|
41
|
+
* executor-post-execution).
|
|
42
|
+
*
|
|
43
|
+
* @module
|
|
44
|
+
*/
|
|
45
|
+
import type { ComisLogger } from "@comis/infra";
|
|
46
|
+
/**
|
|
47
|
+
* Composite drain key uniquely identifies the inline-consumption queue
|
|
48
|
+
* partition for a single (agent, channel, channel-id) triple.
|
|
49
|
+
*
|
|
50
|
+
* Same shape as `BackgroundSessionResolver.ActiveSessionKey` so a single
|
|
51
|
+
* triple is reusable across the bridge / resolver / drain surface.
|
|
52
|
+
*/
|
|
53
|
+
export interface DrainKey {
|
|
54
|
+
agentId: string;
|
|
55
|
+
channelType: string;
|
|
56
|
+
channelId: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* State container for the per-composite-key drain inflight gate.
|
|
60
|
+
*
|
|
61
|
+
* Owned by the bridge (`BridgeMetricsState.drainInflightByKey`) and
|
|
62
|
+
* threaded into `drainAt` at each call site. A `Map` (rather than a single
|
|
63
|
+
* `drainInflight: Promise`) is required so concurrent drains for DIFFERENT
|
|
64
|
+
* composite keys can run independently (multi-agent isolation).
|
|
65
|
+
*/
|
|
66
|
+
export interface DrainInflightState {
|
|
67
|
+
drainInflightByKey: Map<string, Promise<void>>;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Format a composite drain key into a deterministic string used as the
|
|
71
|
+
* inflight-gate Map key. Mirrors the resolver's composite-key shape so the
|
|
72
|
+
* gate keys are interchangeable with resolver keys (no parallel formatting
|
|
73
|
+
* surfaces to drift).
|
|
74
|
+
*/
|
|
75
|
+
export declare function formatDrainKey(key: DrainKey): string;
|
|
76
|
+
/**
|
|
77
|
+
* Mark inbound messages for the composite drain key as read.
|
|
78
|
+
*
|
|
79
|
+
* Reads tool context via `tryGetContext()` -- when called outside any
|
|
80
|
+
* AsyncLocalStorage scope (test fixture, sub-agent path), this is a silent
|
|
81
|
+
* no-op. Otherwise emits a DEBUG-level observability log so operators can
|
|
82
|
+
* correlate drain activity with ALS context propagation.
|
|
83
|
+
*
|
|
84
|
+
* @param key - Composite drain key (agentId, channelType, channelId).
|
|
85
|
+
* @param logger - Logger for the (rare) DEBUG observability path.
|
|
86
|
+
*/
|
|
87
|
+
export declare function markRead(key: DrainKey, logger: ComisLogger): void;
|
|
88
|
+
/**
|
|
89
|
+
* Mark inbound messages for the composite drain key as consumed.
|
|
90
|
+
*
|
|
91
|
+
* Same context contract as `markRead`. No-op outside AsyncLocalStorage
|
|
92
|
+
* scope.
|
|
93
|
+
*
|
|
94
|
+
* @param key - Composite drain key (agentId, channelType, channelId).
|
|
95
|
+
* @param logger - Logger for the (rare) DEBUG observability path.
|
|
96
|
+
*/
|
|
97
|
+
export declare function markConsumed(key: DrainKey, logger: ComisLogger): void;
|
|
98
|
+
/**
|
|
99
|
+
* drainAt: composite-keyed inline-consumption drain with single-tick gate.
|
|
100
|
+
*
|
|
101
|
+
* Invoked by the bridge on `tool_execution_end` for successful
|
|
102
|
+
* `message(send|reply|attach)` calls. Runs `markRead` + `markConsumed`
|
|
103
|
+
* under a per-composite-key inflight gate so:
|
|
104
|
+
* - Concurrent drains for the SAME composite key return immediately
|
|
105
|
+
* (lock-safe drain).
|
|
106
|
+
* - Concurrent drains for DIFFERENT composite keys (different
|
|
107
|
+
* agentId / channelType / channelId) run independently (multi-agent
|
|
108
|
+
* isolation).
|
|
109
|
+
*
|
|
110
|
+
* Failures are non-fatal: a per-event `.catch(...)` logs WARN with `hint`
|
|
111
|
+
* + `errorKind`, and the outer `suppressError` ensures the bridge's
|
|
112
|
+
* `tool_execution_end` propagation is never aborted by drain misbehavior.
|
|
113
|
+
*
|
|
114
|
+
* Map-entry cleanup (`.delete(formatted)` in `.finally(...)`) is required
|
|
115
|
+
* to prevent unbounded growth across long-running sessions; the entry is
|
|
116
|
+
* removed within one event-loop tick of the drain promise settling.
|
|
117
|
+
*
|
|
118
|
+
* @param key - Composite drain key (agentId, channelType, channelId).
|
|
119
|
+
* @param state - Bridge-owned inflight-gate Map (drainInflightByKey).
|
|
120
|
+
* @param logger - Logger for the WARN failure log + DEBUG observability.
|
|
121
|
+
*/
|
|
122
|
+
export declare function drainAt(key: DrainKey, state: DrainInflightState, logger: ComisLogger): void;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Inline-consumption drain seams.
|
|
4
|
+
*
|
|
5
|
+
* The drain trigger lives at the BRIDGE call site (`tool_execution_end` for
|
|
6
|
+
* `message` send/reply/attach) -- NOT in pi-executor.ts. The helpers in
|
|
7
|
+
* this module are what the bridge invokes:
|
|
8
|
+
*
|
|
9
|
+
* - markRead(key): mark inbound messages for the composite key as read.
|
|
10
|
+
* Reads tool context via `tryGetContext()` so the
|
|
11
|
+
* function does NOT take a passed-in deps object.
|
|
12
|
+
* No-op outside an AsyncLocalStorage scope.
|
|
13
|
+
*
|
|
14
|
+
* - markConsumed(key): mark inbound messages for the composite key as
|
|
15
|
+
* consumed by the agent's response. Same context
|
|
16
|
+
* contract as markRead.
|
|
17
|
+
*
|
|
18
|
+
* - drainAt(key): orchestrator. Runs markRead + markConsumed under
|
|
19
|
+
* a per-composite-key single-tick inflight gate
|
|
20
|
+
* (`drainInflightByKey: Map<string, Promise<void>>`).
|
|
21
|
+
* Concurrent calls for the same composite key
|
|
22
|
+
* return immediately; concurrent calls for
|
|
23
|
+
* DIFFERENT composite keys (different agentId /
|
|
24
|
+
* channelType / channelId) drain independently.
|
|
25
|
+
* Failures are non-fatal: suppressError +
|
|
26
|
+
* structured WARN log. The drainInflightByKey state
|
|
27
|
+
* is owned by the bridge (BridgeMetricsState) so
|
|
28
|
+
* the bridge threads it into drainAt at each call
|
|
29
|
+
* site.
|
|
30
|
+
*
|
|
31
|
+
* The actual inline-consumption queue does not exist as a concrete data
|
|
32
|
+
* structure today -- this module provides the structural seam. Future
|
|
33
|
+
* work plugs queue/state into `tryGetContext()` so markRead / markConsumed
|
|
34
|
+
* read it without re-threading through every caller. Today the helpers
|
|
35
|
+
* are observability-only stubs that log at DEBUG when context is present
|
|
36
|
+
* and fall through silently when outside any request scope.
|
|
37
|
+
*
|
|
38
|
+
* This module lives in `packages/agent/src/executor/` (not in the bridge)
|
|
39
|
+
* so executor-post-execution.ts can re-export the helpers for source-grep
|
|
40
|
+
* tests, while the bridge imports from here directly. This avoids a
|
|
41
|
+
* circular import (executor-post-execution -> pi-executor -> bridge ->
|
|
42
|
+
* executor-post-execution).
|
|
43
|
+
*
|
|
44
|
+
* @module
|
|
45
|
+
*/
|
|
46
|
+
import { tryGetContext } from "@comis/core";
|
|
47
|
+
import { suppressError } from "@comis/shared";
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// Helpers
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
/**
|
|
52
|
+
* Format a composite drain key into a deterministic string used as the
|
|
53
|
+
* inflight-gate Map key. Mirrors the resolver's composite-key shape so the
|
|
54
|
+
* gate keys are interchangeable with resolver keys (no parallel formatting
|
|
55
|
+
* surfaces to drift).
|
|
56
|
+
*/
|
|
57
|
+
export function formatDrainKey(key) {
|
|
58
|
+
return `${key.agentId}:${key.channelType}:${key.channelId}`;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Mark inbound messages for the composite drain key as read.
|
|
62
|
+
*
|
|
63
|
+
* Reads tool context via `tryGetContext()` -- when called outside any
|
|
64
|
+
* AsyncLocalStorage scope (test fixture, sub-agent path), this is a silent
|
|
65
|
+
* no-op. Otherwise emits a DEBUG-level observability log so operators can
|
|
66
|
+
* correlate drain activity with ALS context propagation.
|
|
67
|
+
*
|
|
68
|
+
* @param key - Composite drain key (agentId, channelType, channelId).
|
|
69
|
+
* @param logger - Logger for the (rare) DEBUG observability path.
|
|
70
|
+
*/
|
|
71
|
+
export function markRead(key, logger) {
|
|
72
|
+
const ctx = tryGetContext();
|
|
73
|
+
if (!ctx) {
|
|
74
|
+
// No AsyncLocalStorage scope: markRead is a no-op outside a request-
|
|
75
|
+
// scoped context. The bridge's `drainAt` is invoked from inside the
|
|
76
|
+
// request scope, but tests / sub-agent paths may invoke directly.
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
// Future: read the inline-consumption queue partition for `key` from
|
|
80
|
+
// `ctx` and flip status. Today: structural seam + observability.
|
|
81
|
+
logger.debug({
|
|
82
|
+
submodule: "drain.markRead",
|
|
83
|
+
agentId: key.agentId,
|
|
84
|
+
channelType: key.channelType,
|
|
85
|
+
channelId: key.channelId,
|
|
86
|
+
traceId: ctx.traceId,
|
|
87
|
+
}, "markRead");
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Mark inbound messages for the composite drain key as consumed.
|
|
91
|
+
*
|
|
92
|
+
* Same context contract as `markRead`. No-op outside AsyncLocalStorage
|
|
93
|
+
* scope.
|
|
94
|
+
*
|
|
95
|
+
* @param key - Composite drain key (agentId, channelType, channelId).
|
|
96
|
+
* @param logger - Logger for the (rare) DEBUG observability path.
|
|
97
|
+
*/
|
|
98
|
+
export function markConsumed(key, logger) {
|
|
99
|
+
const ctx = tryGetContext();
|
|
100
|
+
if (!ctx) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
logger.debug({
|
|
104
|
+
submodule: "drain.markConsumed",
|
|
105
|
+
agentId: key.agentId,
|
|
106
|
+
channelType: key.channelType,
|
|
107
|
+
channelId: key.channelId,
|
|
108
|
+
traceId: ctx.traceId,
|
|
109
|
+
}, "markConsumed");
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Run a single drain pass for the composite key.
|
|
113
|
+
*
|
|
114
|
+
* Calls `markRead` + `markConsumed` sequentially. Both helpers no-op
|
|
115
|
+
* outside an AsyncLocalStorage scope, so this function is safe to invoke
|
|
116
|
+
* from the bridge's event handler without wrapping in `runWithContext`.
|
|
117
|
+
*/
|
|
118
|
+
async function runOneDrainPass(key, logger) {
|
|
119
|
+
markRead(key, logger);
|
|
120
|
+
markConsumed(key, logger);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* drainAt: composite-keyed inline-consumption drain with single-tick gate.
|
|
124
|
+
*
|
|
125
|
+
* Invoked by the bridge on `tool_execution_end` for successful
|
|
126
|
+
* `message(send|reply|attach)` calls. Runs `markRead` + `markConsumed`
|
|
127
|
+
* under a per-composite-key inflight gate so:
|
|
128
|
+
* - Concurrent drains for the SAME composite key return immediately
|
|
129
|
+
* (lock-safe drain).
|
|
130
|
+
* - Concurrent drains for DIFFERENT composite keys (different
|
|
131
|
+
* agentId / channelType / channelId) run independently (multi-agent
|
|
132
|
+
* isolation).
|
|
133
|
+
*
|
|
134
|
+
* Failures are non-fatal: a per-event `.catch(...)` logs WARN with `hint`
|
|
135
|
+
* + `errorKind`, and the outer `suppressError` ensures the bridge's
|
|
136
|
+
* `tool_execution_end` propagation is never aborted by drain misbehavior.
|
|
137
|
+
*
|
|
138
|
+
* Map-entry cleanup (`.delete(formatted)` in `.finally(...)`) is required
|
|
139
|
+
* to prevent unbounded growth across long-running sessions; the entry is
|
|
140
|
+
* removed within one event-loop tick of the drain promise settling.
|
|
141
|
+
*
|
|
142
|
+
* @param key - Composite drain key (agentId, channelType, channelId).
|
|
143
|
+
* @param state - Bridge-owned inflight-gate Map (drainInflightByKey).
|
|
144
|
+
* @param logger - Logger for the WARN failure log + DEBUG observability.
|
|
145
|
+
*/
|
|
146
|
+
export function drainAt(key, state, logger) {
|
|
147
|
+
const formatted = formatDrainKey(key);
|
|
148
|
+
if (state.drainInflightByKey.has(formatted)) {
|
|
149
|
+
// Single-tick gate: a drain is already in flight for this composite
|
|
150
|
+
// key; second concurrent call returns immediately.
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const draining = runOneDrainPass(key, logger)
|
|
154
|
+
.catch((err) => {
|
|
155
|
+
logger.warn({
|
|
156
|
+
submodule: "drain.drainAt",
|
|
157
|
+
agentId: key.agentId,
|
|
158
|
+
channelType: key.channelType,
|
|
159
|
+
channelId: key.channelId,
|
|
160
|
+
err,
|
|
161
|
+
hint: "drainAt failed; will retry on next tool_execution_end. Investigate when this fires repeatedly without recovery.",
|
|
162
|
+
errorKind: "internal",
|
|
163
|
+
}, "drainAt failed");
|
|
164
|
+
})
|
|
165
|
+
.finally(() => {
|
|
166
|
+
state.drainInflightByKey.delete(formatted);
|
|
167
|
+
});
|
|
168
|
+
state.drainInflightByKey.set(formatted, draining);
|
|
169
|
+
// Belt-and-braces: outer suppressError ensures the bridge's
|
|
170
|
+
// tool_execution_end propagation is NEVER aborted by drain misbehavior
|
|
171
|
+
// (fire-and-forget contract).
|
|
172
|
+
suppressError(draining, "drain at bridge call site (B15)");
|
|
173
|
+
}
|
|
@@ -13,9 +13,9 @@ import { isSignedReplayError } from "./signed-replay-detector.js";
|
|
|
13
13
|
const ERROR_PATTERNS = [
|
|
14
14
|
// Billing / credits
|
|
15
15
|
{
|
|
16
|
-
test: /credit balance is too low|billing|purchase credits|insufficient.?funds|payment.?required/i,
|
|
16
|
+
test: /credit balance is too low|billing|purchase credits|insufficient.?funds|payment.?required|usage.?limits?|regain.?access|spend.?(cap|limit)/i,
|
|
17
17
|
category: "credit_exhausted",
|
|
18
|
-
userMessage: "The AI service is currently unavailable due to a billing issue. Please notify the system administrator.",
|
|
18
|
+
userMessage: "The AI service is currently unavailable due to a billing or usage-cap issue. Please notify the system administrator.",
|
|
19
19
|
retryable: false,
|
|
20
20
|
},
|
|
21
21
|
// Rate limiting (429)
|