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.
Files changed (239) hide show
  1. package/node_modules/@comis/agent/dist/background/auto-background-middleware.js +9 -0
  2. package/node_modules/@comis/agent/dist/background/background-task-manager.d.ts +22 -2
  3. package/node_modules/@comis/agent/dist/background/background-task-manager.js +48 -41
  4. package/node_modules/@comis/agent/dist/background/background-task-persistence.js +28 -5
  5. package/node_modules/@comis/agent/dist/background/background-task-types.d.ts +49 -0
  6. package/node_modules/@comis/agent/dist/background/completion-dispatcher.d.ts +130 -0
  7. package/node_modules/@comis/agent/dist/background/completion-dispatcher.js +215 -0
  8. package/node_modules/@comis/agent/dist/background/completion-runner.d.ts +10 -1
  9. package/node_modules/@comis/agent/dist/background/completion-runner.js +98 -15
  10. package/node_modules/@comis/agent/dist/background/index.d.ts +6 -1
  11. package/node_modules/@comis/agent/dist/background/index.js +2 -0
  12. package/node_modules/@comis/agent/dist/background/session-resolver.d.ts +85 -0
  13. package/node_modules/@comis/agent/dist/background/session-resolver.js +78 -0
  14. package/node_modules/@comis/agent/dist/bootstrap/sections/messaging-sections.js +1 -0
  15. package/node_modules/@comis/agent/dist/bootstrap/sections/tool-descriptions.js +3 -3
  16. package/node_modules/@comis/agent/dist/bootstrap/sections/tooling-sections.d.ts +30 -2
  17. package/node_modules/@comis/agent/dist/bootstrap/sections/tooling-sections.js +51 -2
  18. package/node_modules/@comis/agent/dist/bootstrap/system-prompt-assembler.d.ts +22 -0
  19. package/node_modules/@comis/agent/dist/bootstrap/system-prompt-assembler.js +2 -2
  20. package/node_modules/@comis/agent/dist/bridge/bridge-event-handlers.d.ts +1 -5
  21. package/node_modules/@comis/agent/dist/bridge/bridge-event-handlers.js +2 -14
  22. package/node_modules/@comis/agent/dist/bridge/bridge-metrics.d.ts +26 -0
  23. package/node_modules/@comis/agent/dist/bridge/bridge-metrics.js +3 -0
  24. package/node_modules/@comis/agent/dist/bridge/pi-event-bridge.d.ts +9 -0
  25. package/node_modules/@comis/agent/dist/bridge/pi-event-bridge.js +73 -2
  26. package/node_modules/@comis/agent/dist/context-engine/signature-surrogate-guard.d.ts +10 -10
  27. package/node_modules/@comis/agent/dist/context-engine/signature-surrogate-guard.js +14 -14
  28. package/node_modules/@comis/agent/dist/context-engine/thinking-block-cleaner.d.ts +11 -13
  29. package/node_modules/@comis/agent/dist/context-engine/thinking-block-cleaner.js +14 -15
  30. package/node_modules/@comis/agent/dist/executor/capability-index-context.d.ts +72 -0
  31. package/node_modules/@comis/agent/dist/executor/capability-index-context.js +329 -0
  32. package/node_modules/@comis/agent/dist/executor/drain-helper.d.ts +122 -0
  33. package/node_modules/@comis/agent/dist/executor/drain-helper.js +173 -0
  34. package/node_modules/@comis/agent/dist/executor/error-classifier.js +2 -2
  35. package/node_modules/@comis/agent/dist/executor/executor-post-execution.d.ts +48 -4
  36. package/node_modules/@comis/agent/dist/executor/executor-post-execution.js +134 -31
  37. package/node_modules/@comis/agent/dist/executor/executor-prompt-runner.d.ts +7 -0
  38. package/node_modules/@comis/agent/dist/executor/executor-prompt-runner.js +25 -4
  39. package/node_modules/@comis/agent/dist/executor/executor-tool-assembly.d.ts +18 -1
  40. package/node_modules/@comis/agent/dist/executor/executor-tool-assembly.js +19 -16
  41. package/node_modules/@comis/agent/dist/executor/jit-guide-injector.d.ts +11 -2
  42. package/node_modules/@comis/agent/dist/executor/jit-guide-injector.js +16 -2
  43. package/node_modules/@comis/agent/dist/executor/pi-executor.d.ts +8 -2
  44. package/node_modules/@comis/agent/dist/executor/pi-executor.js +25 -12
  45. package/node_modules/@comis/agent/dist/executor/prompt-assembly.d.ts +9 -1
  46. package/node_modules/@comis/agent/dist/executor/prompt-assembly.js +15 -1
  47. package/node_modules/@comis/agent/dist/executor/tool-deferral.d.ts +18 -27
  48. package/node_modules/@comis/agent/dist/executor/tool-deferral.js +29 -38
  49. package/node_modules/@comis/agent/dist/model/model-registry-adapter.js +1 -1
  50. package/node_modules/@comis/agent/dist/model/model-scanner.js +1 -1
  51. package/node_modules/@comis/agent/dist/safety/tool-retry-breaker.d.ts +11 -1
  52. package/node_modules/@comis/agent/dist/safety/tool-retry-breaker.js +19 -22
  53. package/node_modules/@comis/agent/dist/session/comis-session-manager.d.ts +16 -2
  54. package/node_modules/@comis/agent/dist/spawn/pi-mono-adapters.d.ts +1 -1
  55. package/node_modules/@comis/agent/dist/spawn/pi-mono-adapters.js +5 -5
  56. package/node_modules/@comis/agent/dist/workspace/data-env.d.ts +38 -0
  57. package/node_modules/@comis/agent/dist/workspace/data-env.js +56 -0
  58. package/node_modules/@comis/agent/dist/workspace/index.d.ts +1 -0
  59. package/node_modules/@comis/agent/dist/workspace/index.js +1 -0
  60. package/node_modules/@comis/agent/dist/workspace/templates.js +5 -1
  61. package/node_modules/@comis/agent/package.json +1 -1
  62. package/node_modules/@comis/channels/dist/index.d.ts +1 -1
  63. package/node_modules/@comis/channels/dist/index.js +1 -1
  64. package/node_modules/@comis/channels/dist/shared/channel-manager.d.ts +9 -3
  65. package/node_modules/@comis/channels/dist/shared/inbound-gate.d.ts +1 -1
  66. package/node_modules/@comis/channels/dist/shared/inbound-gate.js +22 -7
  67. package/node_modules/@comis/channels/dist/shared/inbound-pipeline.d.ts +10 -3
  68. package/node_modules/@comis/channels/dist/shared/inbound-route.d.ts +1 -1
  69. package/node_modules/@comis/channels/dist/shared/inbound-route.js +13 -2
  70. package/node_modules/@comis/channels/dist/shared/response-filter.d.ts +11 -24
  71. package/node_modules/@comis/channels/dist/shared/response-filter.js +25 -53
  72. package/node_modules/@comis/channels/package.json +1 -1
  73. package/node_modules/@comis/cli/dist/commands/providers.d.ts +1 -2
  74. package/node_modules/@comis/cli/dist/commands/providers.js +5 -6
  75. package/node_modules/@comis/cli/package.json +1 -1
  76. package/node_modules/@comis/core/dist/config/field-metadata.js +2 -0
  77. package/node_modules/@comis/core/dist/config/immutable-keys.js +4 -1
  78. package/node_modules/@comis/core/dist/config/index.d.ts +4 -0
  79. package/node_modules/@comis/core/dist/config/index.js +2 -0
  80. package/node_modules/@comis/core/dist/config/schema-agent.d.ts +0 -792
  81. package/node_modules/@comis/core/dist/config/schema-approvals.d.ts +0 -14
  82. package/node_modules/@comis/core/dist/config/schema-auto-reply-engine.d.ts +0 -6
  83. package/node_modules/@comis/core/dist/config/schema-background-tasks.d.ts +0 -12
  84. package/node_modules/@comis/core/dist/config/schema-browser.d.ts +0 -18
  85. package/node_modules/@comis/core/dist/config/schema-channel.d.ts +0 -158
  86. package/node_modules/@comis/core/dist/config/schema-coalescer.d.ts +0 -5
  87. package/node_modules/@comis/core/dist/config/schema-daemon.d.ts +0 -32
  88. package/node_modules/@comis/core/dist/config/schema-delivery.d.ts +0 -18
  89. package/node_modules/@comis/core/dist/config/schema-documentation.d.ts +0 -12
  90. package/node_modules/@comis/core/dist/config/schema-embedding.d.ts +0 -20
  91. package/node_modules/@comis/core/dist/config/schema-envelope.d.ts +0 -15
  92. package/node_modules/@comis/core/dist/config/schema-gateway.d.ts +0 -37
  93. package/node_modules/@comis/core/dist/config/schema-gemini-cache.d.ts +0 -2
  94. package/node_modules/@comis/core/dist/config/schema-integrations.d.ts +0 -318
  95. package/node_modules/@comis/core/dist/config/schema-lifecycle-reactions.d.ts +0 -18
  96. package/node_modules/@comis/core/dist/config/schema-memory-review.d.ts +0 -7
  97. package/node_modules/@comis/core/dist/config/schema-memory.d.ts +0 -16
  98. package/node_modules/@comis/core/dist/config/schema-messages.d.ts +0 -8
  99. package/node_modules/@comis/core/dist/config/schema-models.d.ts +0 -15
  100. package/node_modules/@comis/core/dist/config/schema-notification.d.ts +0 -5
  101. package/node_modules/@comis/core/dist/config/schema-oauth.d.ts +0 -5
  102. package/node_modules/@comis/core/dist/config/schema-observability.d.ts +0 -38
  103. package/node_modules/@comis/core/dist/config/schema-output-retention.d.ts +34 -0
  104. package/node_modules/@comis/core/dist/config/schema-output-retention.js +48 -0
  105. package/node_modules/@comis/core/dist/config/schema-plugins.d.ts +0 -8
  106. package/node_modules/@comis/core/dist/config/schema-providers.d.ts +0 -64
  107. package/node_modules/@comis/core/dist/config/schema-queue.d.ts +0 -58
  108. package/node_modules/@comis/core/dist/config/schema-response-prefix.d.ts +0 -2
  109. package/node_modules/@comis/core/dist/config/schema-retry.d.ts +0 -6
  110. package/node_modules/@comis/core/dist/config/schema-scheduler.d.ts +0 -39
  111. package/node_modules/@comis/core/dist/config/schema-secrets.d.ts +0 -3
  112. package/node_modules/@comis/core/dist/config/schema-security.d.ts +0 -18
  113. package/node_modules/@comis/core/dist/config/schema-send-policy.d.ts +0 -13
  114. package/node_modules/@comis/core/dist/config/schema-sender-trust-display.d.ts +0 -5
  115. package/node_modules/@comis/core/dist/config/schema-serializer.js +2 -0
  116. package/node_modules/@comis/core/dist/config/schema-skills.d.ts +0 -61
  117. package/node_modules/@comis/core/dist/config/schema-streaming.d.ts +0 -38
  118. package/node_modules/@comis/core/dist/config/schema-telegram-file-guard.d.ts +0 -3
  119. package/node_modules/@comis/core/dist/config/schema-tooling.d.ts +87 -0
  120. package/node_modules/@comis/core/dist/config/schema-tooling.js +152 -0
  121. package/node_modules/@comis/core/dist/config/schema-verbosity.d.ts +0 -12
  122. package/node_modules/@comis/core/dist/config/schema-webhooks.d.ts +0 -40
  123. package/node_modules/@comis/core/dist/config/schema.d.ts +41 -38
  124. package/node_modules/@comis/core/dist/config/schema.js +6 -0
  125. package/node_modules/@comis/core/dist/context/context.d.ts +0 -4
  126. package/node_modules/@comis/core/dist/domain/approval-request.d.ts +0 -17
  127. package/node_modules/@comis/core/dist/domain/background-task-origin.d.ts +0 -10
  128. package/node_modules/@comis/core/dist/domain/delivery-origin.d.ts +0 -5
  129. package/node_modules/@comis/core/dist/domain/execution-graph.d.ts +0 -48
  130. package/node_modules/@comis/core/dist/domain/memory-entry.d.ts +0 -3
  131. package/node_modules/@comis/core/dist/domain/model-compat.d.ts +0 -4
  132. package/node_modules/@comis/core/dist/domain/normalized-message.d.ts +0 -15
  133. package/node_modules/@comis/core/dist/domain/provider-capabilities.d.ts +0 -6
  134. package/node_modules/@comis/core/dist/domain/rich-message.d.ts +0 -14
  135. package/node_modules/@comis/core/dist/domain/subagent-context-config.d.ts +0 -22
  136. package/node_modules/@comis/core/dist/domain/subagent-context-types.d.ts +0 -8
  137. package/node_modules/@comis/core/dist/event-bus/events-agent.d.ts +31 -0
  138. package/node_modules/@comis/core/dist/event-bus/events-infra.d.ts +5 -0
  139. package/node_modules/@comis/core/dist/exports/config.d.ts +2 -2
  140. package/node_modules/@comis/core/dist/exports/config.js +3 -1
  141. package/node_modules/@comis/core/dist/exports/hooks.d.ts +1 -1
  142. package/node_modules/@comis/core/dist/exports/ports.d.ts +2 -2
  143. package/node_modules/@comis/core/dist/exports/ports.js +1 -1
  144. package/node_modules/@comis/core/dist/ports/channel-plugin.d.ts +0 -13
  145. package/node_modules/@comis/core/dist/ports/index.d.ts +2 -0
  146. package/node_modules/@comis/core/dist/ports/index.js +4 -0
  147. package/node_modules/@comis/core/dist/ports/no-op-tool-capability.d.ts +30 -0
  148. package/node_modules/@comis/core/dist/ports/no-op-tool-capability.js +47 -0
  149. package/node_modules/@comis/core/dist/ports/tool-capability.d.ts +165 -0
  150. package/node_modules/@comis/core/dist/ports/tool-capability.js +15 -0
  151. package/node_modules/@comis/core/dist/security/audit.d.ts +0 -11
  152. package/node_modules/@comis/core/dist/tool-metadata.d.ts +21 -1
  153. package/node_modules/@comis/core/dist/tool-metadata.js +1 -1
  154. package/node_modules/@comis/core/package.json +1 -1
  155. package/node_modules/@comis/daemon/bundled-skills/skill-creator/scripts/validate-skill.py +1 -1
  156. package/node_modules/@comis/daemon/dist/daemon.js +89 -14
  157. package/node_modules/@comis/daemon/dist/rpc/agent-inline-workspace.d.ts +1 -1
  158. package/node_modules/@comis/daemon/dist/rpc/agent-inline-workspace.js +1 -1
  159. package/node_modules/@comis/daemon/dist/rpc/builtin-provider-guard.js +2 -2
  160. package/node_modules/@comis/daemon/dist/rpc/credential-resolver.js +1 -1
  161. package/node_modules/@comis/daemon/dist/rpc/model-handlers.d.ts +1 -1
  162. package/node_modules/@comis/daemon/dist/rpc/model-handlers.js +2 -2
  163. package/node_modules/@comis/daemon/dist/sub-agent-runner.d.ts +18 -0
  164. package/node_modules/@comis/daemon/dist/sub-agent-runner.js +41 -9
  165. package/node_modules/@comis/daemon/dist/wiring/index.d.ts +2 -0
  166. package/node_modules/@comis/daemon/dist/wiring/index.js +1 -0
  167. package/node_modules/@comis/daemon/dist/wiring/setup-agents.d.ts +36 -2
  168. package/node_modules/@comis/daemon/dist/wiring/setup-agents.js +45 -8
  169. package/node_modules/@comis/daemon/dist/wiring/setup-background-completion-runner.d.ts +28 -9
  170. package/node_modules/@comis/daemon/dist/wiring/setup-background-completion-runner.js +36 -9
  171. package/node_modules/@comis/daemon/dist/wiring/setup-background-tasks.js +2 -2
  172. package/node_modules/@comis/daemon/dist/wiring/setup-channels.d.ts +9 -2
  173. package/node_modules/@comis/daemon/dist/wiring/setup-channels.js +15 -9
  174. package/node_modules/@comis/daemon/dist/wiring/setup-cross-session.d.ts +20 -5
  175. package/node_modules/@comis/daemon/dist/wiring/setup-cross-session.js +20 -15
  176. package/node_modules/@comis/daemon/dist/wiring/setup-delivery.js +14 -2
  177. package/node_modules/@comis/daemon/dist/wiring/setup-gateway.d.ts +4 -6
  178. package/node_modules/@comis/daemon/dist/wiring/setup-gateway.js +3 -5
  179. package/node_modules/@comis/daemon/dist/wiring/setup-heartbeat.d.ts +20 -5
  180. package/node_modules/@comis/daemon/dist/wiring/setup-heartbeat.js +11 -2
  181. package/node_modules/@comis/daemon/dist/wiring/setup-output-retention.d.ts +89 -0
  182. package/node_modules/@comis/daemon/dist/wiring/setup-output-retention.js +212 -0
  183. package/node_modules/@comis/daemon/dist/wiring/setup-tools.d.ts +18 -4
  184. package/node_modules/@comis/daemon/dist/wiring/setup-tools.js +29 -10
  185. package/node_modules/@comis/daemon/dist/wiring/tool-capability-adapter.d.ts +75 -0
  186. package/node_modules/@comis/daemon/dist/wiring/tool-capability-adapter.js +253 -0
  187. package/node_modules/@comis/daemon/package.json +1 -1
  188. package/node_modules/@comis/gateway/dist/webhook/webhook-endpoint.d.ts +0 -4
  189. package/node_modules/@comis/gateway/package.json +1 -1
  190. package/node_modules/@comis/infra/package.json +1 -1
  191. package/node_modules/@comis/memory/package.json +1 -1
  192. package/node_modules/@comis/scheduler/dist/cron/cron-types.d.ts +0 -42
  193. package/node_modules/@comis/scheduler/dist/heartbeat/agent-heartbeat-source.d.ts +29 -8
  194. package/node_modules/@comis/scheduler/dist/heartbeat/agent-heartbeat-source.js +19 -7
  195. package/node_modules/@comis/scheduler/dist/system-events/system-event-types.d.ts +0 -3
  196. package/node_modules/@comis/scheduler/dist/tasks/task-types.d.ts +0 -17
  197. package/node_modules/@comis/scheduler/package.json +1 -1
  198. package/node_modules/@comis/shared/dist/index.d.ts +3 -0
  199. package/node_modules/@comis/shared/dist/index.js +4 -0
  200. package/node_modules/@comis/shared/dist/mcp-tool-name.d.ts +78 -0
  201. package/node_modules/@comis/shared/dist/mcp-tool-name.js +92 -0
  202. package/node_modules/@comis/shared/dist/silent-tokens.d.ts +38 -0
  203. package/node_modules/@comis/shared/dist/silent-tokens.js +51 -0
  204. package/node_modules/@comis/shared/dist/visible-delivery.d.ts +28 -0
  205. package/node_modules/@comis/shared/dist/visible-delivery.js +16 -0
  206. package/node_modules/@comis/shared/package.json +1 -1
  207. package/node_modules/@comis/skills/dist/bridge/mcp-tool-bridge.d.ts +2 -13
  208. package/node_modules/@comis/skills/dist/bridge/mcp-tool-bridge.js +3 -21
  209. package/node_modules/@comis/skills/dist/bridge/tool-metadata-enforcement.js +1 -1
  210. package/node_modules/@comis/skills/dist/bridge/tool-metadata-registry.js +4 -4
  211. package/node_modules/@comis/skills/dist/builtin/exec-tool.d.ts +55 -9
  212. package/node_modules/@comis/skills/dist/builtin/exec-tool.js +383 -19
  213. package/node_modules/@comis/skills/dist/builtin/install-detour.d.ts +67 -0
  214. package/node_modules/@comis/skills/dist/builtin/install-detour.js +342 -0
  215. package/node_modules/@comis/skills/dist/builtin/platform/admin-manage-factory.js +5 -5
  216. package/node_modules/@comis/skills/dist/builtin/platform/agents-manage-tool.d.ts +2 -2
  217. package/node_modules/@comis/skills/dist/builtin/platform/agents-manage-tool.js +2 -2
  218. package/node_modules/@comis/skills/dist/builtin/platform/message-tool.js +18 -0
  219. package/node_modules/@comis/skills/dist/builtin/platform/messaging-factory.d.ts +18 -1
  220. package/node_modules/@comis/skills/dist/builtin/platform/messaging-factory.js +18 -2
  221. package/node_modules/@comis/skills/dist/builtin/platform/models-manage-tool.js +3 -3
  222. package/node_modules/@comis/skills/dist/builtin/process-registry.d.ts +14 -0
  223. package/node_modules/@comis/skills/dist/builtin/process-tool.d.ts +24 -4
  224. package/node_modules/@comis/skills/dist/builtin/process-tool.js +25 -7
  225. package/node_modules/@comis/skills/dist/builtin/sandbox/bwrap-provider.d.ts +1 -1
  226. package/node_modules/@comis/skills/dist/builtin/sandbox/bwrap-provider.js +9 -0
  227. package/node_modules/@comis/skills/dist/index.d.ts +4 -1
  228. package/node_modules/@comis/skills/dist/index.js +3 -1
  229. package/node_modules/@comis/skills/dist/manifest/capability-parser.d.ts +44 -0
  230. package/node_modules/@comis/skills/dist/manifest/capability-parser.js +68 -0
  231. package/node_modules/@comis/skills/dist/manifest/schema.d.ts +44 -37
  232. package/node_modules/@comis/skills/dist/manifest/schema.js +35 -0
  233. package/node_modules/@comis/skills/dist/registry/discovery.d.ts +8 -0
  234. package/node_modules/@comis/skills/dist/registry/discovery.js +10 -3
  235. package/node_modules/@comis/skills/dist/registry/skill-registry.d.ts +45 -1
  236. package/node_modules/@comis/skills/dist/registry/skill-registry.js +70 -7
  237. package/node_modules/@comis/skills/package.json +1 -1
  238. package/node_modules/@comis/web/package.json +1 -1
  239. package/package.json +21 -21
@@ -16,8 +16,9 @@ import type { AgentSession } from "@mariozechner/pi-coding-agent";
16
16
  import type { CacheRetention } from "@mariozechner/pi-ai";
17
17
  import { type SessionKey, type NormalizedMessage, type PerAgentConfig, type TypedEventBus, type MemoryPort } from "@comis/core";
18
18
  import type { ComisLogger } from "@comis/infra";
19
+ import { drainAt, markRead, markConsumed, formatDrainKey, type DrainKey, type DrainInflightState } from "./drain-helper.js";
19
20
  import type { ActiveRunRegistry } from "./active-run-registry.js";
20
- import type { ComisSessionManager } from "../session/comis-session-manager.js";
21
+ import type { ComisSessionManager, SessionMetadata } from "../session/comis-session-manager.js";
21
22
  import type { ExecutionResult, ExecutionOverrides } from "./types.js";
22
23
  import type { ExecutionPlan } from "../planner/types.js";
23
24
  import type { ContextEngine } from "../context-engine/index.js";
@@ -53,11 +54,11 @@ export interface PostExecutionBridgeResult {
53
54
  cacheWrite5mTokens?: number;
54
55
  /** Estimated 1h TTL cache write tokens from TTL split data. */
55
56
  cacheWrite1hTokens?: number;
56
- /** 1.3: Session-cumulative total cost across all turns (USD). */
57
+ /** Session-cumulative total cost across all turns (USD). */
57
58
  sessionCostUsd?: number;
58
- /** 1.3: Session-cumulative cache savings across all turns (USD). */
59
+ /** Session-cumulative cache savings across all turns (USD). */
59
60
  sessionCacheSavedUsd?: number;
60
- /** 1.5: Thinking tokens from SDK reasoningTokens field. */
61
+ /** Thinking tokens from SDK reasoningTokens field. */
61
62
  thinkingTokens?: number;
62
63
  /** Number of pre-LLM-call hash-assertion walks performed (one per turn_start). */
63
64
  hashAssertionsRan?: number;
@@ -74,6 +75,19 @@ export interface PostExecutionBridgeResult {
74
75
  /** Bridge interface used by post-execution. */
75
76
  export interface PostExecutionBridge {
76
77
  getResult(): PostExecutionBridgeResult;
78
+ /**
79
+ * Expose the bridge-owned drain inflight gate so postExecution can fire
80
+ * an end-of-turn backstop `drainAt(...)`. The bridge already drains on
81
+ * `tool_execution_end` for `message` actions (inline-consumption); the
82
+ * end-of-turn backstop closes the residual race for turns that never
83
+ * invoked the `message` tool but still need the inline-consumption
84
+ * queue flipped (NO_REPLY-only turns, sentinel passes, etc.).
85
+ *
86
+ * Both call sites share the SAME composite key gate map so concurrent
87
+ * drains for the same `(agentId, channelType, channelId)` triple
88
+ * collapse to a single in-flight Promise.
89
+ */
90
+ getDrainState(): DrainInflightState;
77
91
  }
78
92
  /** Parameters for postExecution(). */
79
93
  export interface PostExecutionParams {
@@ -86,6 +100,9 @@ export interface PostExecutionParams {
86
100
  msg: NormalizedMessage;
87
101
  sessionKey: SessionKey;
88
102
  formattedKey: string;
103
+ /** Resolver-aligned key for activeRunRegistry.deregister. Must match the
104
+ * formula used at the corresponding register call site. */
105
+ resolverRegisterKey: string;
89
106
  agentId: string | undefined;
90
107
  executionStartMs: number;
91
108
  executionId: string;
@@ -182,6 +199,33 @@ export declare function shouldStorePairedMemory(userText: string, agentResponse:
182
199
  export declare function isDuplicatePairedMemory(content: string, agentId: string): boolean;
183
200
  /** Reset the paired-memory dedup cache. Exported for unit tests. */
184
201
  export declare function resetPairedMemoryDedupForTests(): void;
202
+ /**
203
+ * Build the SessionMetadata payload written to `_session-metadata.json` at the
204
+ * end of an execution.
205
+ *
206
+ * `traceId` and `runId` are deliberately distinct:
207
+ * - `traceId` is the request-scope AsyncLocalStorage value set by
208
+ * `runWithContext` at the channel boundary (execution-execute.ts) and injected
209
+ * into every daemon log line by the Pino tracing mixin. Operators grep
210
+ * daemon.log for this exact value. Pass `tryGetContext()?.traceId` here.
211
+ * - `runId` is the executor-scope UUID minted in pi-executor.ts per
212
+ * `executor.execute()` call. It keys cost-tracker / token_usage rows.
213
+ *
214
+ * They happen to be 1:1 in the steady-state interactive path (one inbound
215
+ * message → one execution), but the schema treats them as distinct because
216
+ * heartbeat / sub-agent paths can fan out one trace into multiple executions.
217
+ *
218
+ * Pure: no I/O, no side effects. The fire-and-forget try/catch around
219
+ * `writeSessionMetadata` lives at the call site.
220
+ */
221
+ export declare function buildSessionEndMetadata(args: {
222
+ finishReason: string;
223
+ durationMs: number;
224
+ totalTokens: number;
225
+ executionId: string;
226
+ traceId: string | undefined;
227
+ }): SessionMetadata;
228
+ export { drainAt, markRead, markConsumed, formatDrainKey, type DrainKey, type DrainInflightState, };
185
229
  /**
186
230
  * Run post-execution cleanup for a PiExecutor turn.
187
231
  *
@@ -13,7 +13,9 @@
13
13
  *
14
14
  * @module
15
15
  */
16
- import { suppressError } from "@comis/shared";
16
+ import { tryGetContext, } from "@comis/core";
17
+ import { suppressError, isSilentResponse } from "@comis/shared";
18
+ import { drainAt, markRead, markConsumed, formatDrainKey, } from "./drain-helper.js";
17
19
  import { setBreakpointIndex, deleteBreakpointIndex, getBreakpointIndexMapSize, } from "./executor-session-state.js";
18
20
  import { mergeSessionStats } from "./pi-executor.js";
19
21
  import { recordLastResponseTs } from "./ttl-guard.js";
@@ -124,6 +126,63 @@ export function isDuplicatePairedMemory(content, agentId) {
124
126
  export function resetPairedMemoryDedupForTests() {
125
127
  pairedMemoryDedup.clear();
126
128
  }
129
+ /**
130
+ * Map an SDK finishReason to the SessionMetadata.sessionEnd.endReason enum.
131
+ * Unknown reasons fall through to "error" — that's a defensive bucket for
132
+ * provider strings we haven't classified yet (rather than dropping the
133
+ * session_end entry entirely). Module-level so the post-execution path
134
+ * doesn't reallocate it on every turn.
135
+ */
136
+ const END_REASON_MAP = {
137
+ stop: "success", end_turn: "success", error: "error",
138
+ budget_exceeded: "budget_exceeded", budget_exhausted: "budget_exhausted",
139
+ circuit_open: "circuit_open",
140
+ provider_degraded: "provider_degraded", max_steps: "error",
141
+ context_loop: "error", context_exhausted: "error",
142
+ };
143
+ /**
144
+ * Build the SessionMetadata payload written to `_session-metadata.json` at the
145
+ * end of an execution.
146
+ *
147
+ * `traceId` and `runId` are deliberately distinct:
148
+ * - `traceId` is the request-scope AsyncLocalStorage value set by
149
+ * `runWithContext` at the channel boundary (execution-execute.ts) and injected
150
+ * into every daemon log line by the Pino tracing mixin. Operators grep
151
+ * daemon.log for this exact value. Pass `tryGetContext()?.traceId` here.
152
+ * - `runId` is the executor-scope UUID minted in pi-executor.ts per
153
+ * `executor.execute()` call. It keys cost-tracker / token_usage rows.
154
+ *
155
+ * They happen to be 1:1 in the steady-state interactive path (one inbound
156
+ * message → one execution), but the schema treats them as distinct because
157
+ * heartbeat / sub-agent paths can fan out one trace into multiple executions.
158
+ *
159
+ * Pure: no I/O, no side effects. The fire-and-forget try/catch around
160
+ * `writeSessionMetadata` lives at the call site.
161
+ */
162
+ export function buildSessionEndMetadata(args) {
163
+ return {
164
+ ...(args.traceId && { traceId: args.traceId }),
165
+ runId: args.executionId,
166
+ sessionEnd: {
167
+ type: "session_end",
168
+ timestamp: new Date().toISOString(),
169
+ endReason: END_REASON_MAP[args.finishReason] ?? "error",
170
+ durationMs: args.durationMs,
171
+ totalTokens: args.totalTokens,
172
+ },
173
+ };
174
+ }
175
+ // ---------------------------------------------------------------------------
176
+ // Drain-seam re-exports.
177
+ //
178
+ // Canonical implementations live in ./drain-helper.ts so the bridge
179
+ // (packages/agent/src/bridge/pi-event-bridge.ts) can import them without
180
+ // creating a cycle through pi-executor.js. The bridge owns the
181
+ // `drainInflightByKey: Map<string, Promise<void>>` gate state in
182
+ // `BridgeMetricsState` and threads it into drainAt at the
183
+ // `tool_execution_end` call site (inline-consumption + composite drain).
184
+ // ---------------------------------------------------------------------------
185
+ export { drainAt, markRead, markConsumed, formatDrainKey, };
127
186
  // ---------------------------------------------------------------------------
128
187
  // Implementation
129
188
  // ---------------------------------------------------------------------------
@@ -139,7 +198,12 @@ export function resetPairedMemoryDedupForTests() {
139
198
  * @param params - All inputs needed for post-execution cleanup
140
199
  */
141
200
  export async function postExecution(params) {
142
- const { result, session, sm, config, msg, sessionKey, formattedKey, agentId, executionStartMs, executionId, bridge, unsubscribe, contextEngineRef, ceSetup, streamSetup, getTruncationSummary, getTurnBudgetSummary, executionPlanRef, isOnboarding, geminiCacheHit, geminiCachedTokens, modelTier, provider, providerFamily, deferralResult, mergedCustomTools, deliveredGuides, deps, sessionAdapter, executionCacheRetentionClear, adaptiveRetentionClear, } = params;
201
+ const { result, session, sm, config, msg, sessionKey, formattedKey, resolverRegisterKey, agentId, executionStartMs, executionId, bridge, unsubscribe, contextEngineRef, ceSetup, streamSetup, getTruncationSummary, getTurnBudgetSummary, executionPlanRef, isOnboarding, geminiCacheHit, geminiCachedTokens, modelTier, provider, providerFamily, deferralResult, mergedCustomTools, deliveredGuides, deps, sessionAdapter, executionCacheRetentionClear, adaptiveRetentionClear, } = params;
202
+ // Hoist effectiveAgentId normalization to the TOP of the function so all
203
+ // downstream branches (silent-sentinel gate, memory-store path, drainAt
204
+ // call site, skip-log debug branches) share the same normalized value.
205
+ // Multi-agent isolation requires uniformity across all paths.
206
+ const effectiveAgentId = agentId ?? "default";
143
207
  unsubscribe();
144
208
  // Clear per-execution cache retention to prevent state leakage
145
209
  executionCacheRetentionClear();
@@ -168,7 +232,7 @@ export async function postExecution(params) {
168
232
  if (result.errorContext && bridgeResult.lastActiveToolName) {
169
233
  result.errorContext.failingTool = bridgeResult.lastActiveToolName;
170
234
  }
171
- // R-13: Delegate token totals to SDK session stats (single source of truth).
235
+ // Delegate token totals to SDK session stats (single source of truth).
172
236
  // Cost stays from bridge for consistency with per-turn observability events.
173
237
  // Per-turn event emission in bridge remains manual (SDK stats are cumulative only).
174
238
  mergeSessionStats(result, () => session.getSessionStats());
@@ -184,7 +248,7 @@ export async function postExecution(params) {
184
248
  // The context:pipeline event fires pre-LLM with zeros. This event patches actual data.
185
249
  if (deps.eventBus) {
186
250
  deps.eventBus.emit("context:pipeline:cache", {
187
- agentId: agentId ?? "unknown",
251
+ agentId: effectiveAgentId,
188
252
  sessionKey: formattedKey,
189
253
  cacheHitTokens: cacheReadTokens,
190
254
  cacheWriteTokens,
@@ -207,7 +271,7 @@ export async function postExecution(params) {
207
271
  planExtractionTurn: 1,
208
272
  };
209
273
  deps.eventBus.emit("sep:plan_completed", {
210
- agentId: agentId ?? "default",
274
+ agentId: effectiveAgentId,
211
275
  sessionKey: formattedKey,
212
276
  stepsPlanned: toolCalls,
213
277
  stepsCompleted: toolCalls,
@@ -264,7 +328,7 @@ export async function postExecution(params) {
264
328
  comisEstimatedTtlSplit: (bridgeResult.cacheWrite5mTokens ?? 0) > 0 || (bridgeResult.cacheWrite1hTokens ?? 0) > 0,
265
329
  costUsd: result.cost.total,
266
330
  cacheSavedUsd: result.cost.cacheSaved ?? 0,
267
- // 1.3: Session-cumulative cost fields (alongside per-turn costUsd/cacheSavedUsd)
331
+ // Session-cumulative cost fields (alongside per-turn costUsd/cacheSavedUsd)
268
332
  sessionCostUsd: bridgeResult.sessionCostUsd ?? 0,
269
333
  sessionCacheSavedUsd: bridgeResult.sessionCacheSavedUsd ?? 0,
270
334
  // Session cache savings rate
@@ -312,7 +376,7 @@ export async function postExecution(params) {
312
376
  postBatchContinuationAttempts: result.continuationMetrics.attempts,
313
377
  postBatchContinuationOutcome: result.continuationMetrics.outcome,
314
378
  }),
315
- // 1.5 + 3.2: Thinking token tracking (conditional -- only when thinking tokens detected)
379
+ // Thinking token tracking (conditional -- only when thinking tokens detected)
316
380
  ...(bridgeResult.thinkingTokens != null && bridgeResult.thinkingTokens > 0 && {
317
381
  thinkingTokens: bridgeResult.thinkingTokens,
318
382
  totalOutputTokens: result.tokensUsed.output ?? 0,
@@ -339,27 +403,19 @@ export async function postExecution(params) {
339
403
  errorKind: "resource",
340
404
  }, "Turn budget truncation summary");
341
405
  }
342
- // Write session metadata companion file with trace correlation
343
- // Fire-and-forget: metadata write failure must not affect execution
344
- const endReasonMap = {
345
- stop: "success", end_turn: "success", error: "error",
346
- budget_exceeded: "budget_exceeded", budget_exhausted: "budget_exhausted",
347
- circuit_open: "circuit_open",
348
- provider_degraded: "provider_degraded", max_steps: "error",
349
- context_loop: "error", context_exhausted: "error",
350
- };
406
+ // Write session metadata companion file with trace correlation.
407
+ // traceId comes from the AsyncLocalStorage request scope so `_session-metadata.json`
408
+ // can be cross-correlated against daemon.log via grep; runId stays as the
409
+ // executor-scope UUID. See buildSessionEndMetadata for the contract.
410
+ // Fire-and-forget: metadata write failure must not affect execution.
351
411
  try {
352
- sessionAdapter.writeSessionMetadata(sessionKey, {
353
- traceId: executionId,
354
- runId: executionId,
355
- sessionEnd: {
356
- type: "session_end",
357
- timestamp: new Date().toISOString(),
358
- endReason: endReasonMap[result.finishReason] ?? "error",
359
- durationMs,
360
- totalTokens: result.tokensUsed.total,
361
- },
362
- });
412
+ sessionAdapter.writeSessionMetadata(sessionKey, buildSessionEndMetadata({
413
+ finishReason: result.finishReason,
414
+ durationMs,
415
+ totalTokens: result.tokensUsed.total,
416
+ executionId,
417
+ traceId: tryGetContext()?.traceId,
418
+ }));
363
419
  }
364
420
  catch { /* fire-and-forget */ }
365
421
  // Check onboarding completion after execution
@@ -383,14 +439,23 @@ export async function postExecution(params) {
383
439
  // Non-blocking, non-fatal -- execution never fails due to memory store errors.
384
440
  const operationType = params.executionOverrides?.operationType;
385
441
  const skipMemoryForOperation = operationType != null && MEMORY_SKIP_OPERATIONS.has(operationType);
386
- if (deps.memoryPort &&
442
+ // Layer 0: silent sentinels never enter memory. Idempotent under
443
+ // stripReplyTags + trim per @comis/shared silent-tokens.ts JSDoc
444
+ // contract. The check happens BEFORE the operationType + content-hash
445
+ // dedup gates so that even when the response would otherwise pass those
446
+ // gates, a `NO_REPLY` / `HEARTBEAT_OK` / `[SILENT]` sentinel is rejected
447
+ // from memory persistence.
448
+ const isSilent = !!(deps.memoryPort && result.response && msg.text && isSilentResponse(result.response));
449
+ if (isSilent) {
450
+ deps.logger.debug({ agentId: effectiveAgentId, sessionKey: formattedKey, hint: "Silent-sentinel response (NO_REPLY / HEARTBEAT_OK / [SILENT]) skipped from paired memory" }, "Paired memory skipped: silent-sentinel response");
451
+ }
452
+ else if (deps.memoryPort &&
387
453
  result.response &&
388
454
  msg.text &&
389
455
  !skipMemoryForOperation &&
390
456
  shouldStorePairedMemory(msg.text, result.response)) {
391
457
  const now = Date.now();
392
458
  const pairedContent = buildPairedMemoryContent(msg.text, result.response);
393
- const effectiveAgentId = agentId ?? "default";
394
459
  if (isDuplicatePairedMemory(pairedContent, effectiveAgentId)) {
395
460
  deps.logger.debug({ agentId: effectiveAgentId, sessionKey: formattedKey }, "Paired memory skipped: duplicate content within dedup window");
396
461
  }
@@ -433,9 +498,47 @@ export async function postExecution(params) {
433
498
  deps.logger.debug({ userLen: msg.text.trim().length, minUserChars: PAIRED_MIN_USER_CHARS, minCombinedChars: PAIRED_MIN_COMBINED_CHARS }, "Paired memory skipped: content below quality threshold");
434
499
  }
435
500
  }
436
- // Deregister active run before dispose
501
+ // End-of-turn backstop drain.
502
+ //
503
+ // The bridge fires `drainAt(...)` on `tool_execution_end` for successful
504
+ // `message(send|reply|attach)` calls (the primary inline-consumption
505
+ // call site). The end-of-turn call site below is the BACKSTOP for turns
506
+ // that produced a response WITHOUT invoking the message tool (NO_REPLY-
507
+ // only turns, sentinel-passthrough turns, error paths). Both call sites
508
+ // share the SAME composite-key inflight gate (`drainInflightByKey`) so a
509
+ // bridge-fired drain in flight collapses any backstop drain for the same
510
+ // composite key (single-tick gate).
511
+ //
512
+ // The drain key is composed from `effectiveAgentId` (multi-agent
513
+ // isolation) + `msg.channelType` + `msg.channelId`. markRead and
514
+ // markConsumed inside drainAt read tool context via tryGetContext();
515
+ // when the executor runs outside an AsyncLocalStorage scope (e.g.,
516
+ // tests with no runWithContext wrapper) the helpers fall through silently.
517
+ const drainKey = {
518
+ agentId: effectiveAgentId,
519
+ channelType: msg.channelType,
520
+ channelId: msg.channelId,
521
+ };
522
+ // tryGetContext() reads the AsyncLocalStorage scope; markRead/markConsumed
523
+ // do the same internally, but reading once here lets us correlate the
524
+ // backstop-drain log line with the request's traceId without re-deriving
525
+ // it inside the helper. Returns undefined outside any request scope --
526
+ // markRead/markConsumed handle that path silently.
527
+ const drainCtx = tryGetContext();
528
+ if (drainCtx) {
529
+ deps.logger.debug({
530
+ submodule: "drain.endOfTurn",
531
+ agentId: effectiveAgentId,
532
+ channelType: msg.channelType,
533
+ channelId: msg.channelId,
534
+ traceId: drainCtx.traceId,
535
+ }, "End-of-turn drain backstop firing");
536
+ }
537
+ drainAt({ agentId: effectiveAgentId, channelType: drainKey.channelType, channelId: drainKey.channelId }, bridge.getDrainState(), deps.logger);
538
+ // Deregister active run before dispose. Must use the same resolver-aligned
539
+ // key formula as the corresponding register call site.
437
540
  if (deps.activeRunRegistry) {
438
- deps.activeRunRegistry.deregister(formattedKey);
541
+ deps.activeRunRegistry.deregister(resolverRegisterKey);
439
542
  }
440
543
  // Strip verbose <functions> blocks from discover_tools results
441
544
  // in session history. Runs post-execution so the current turn's model
@@ -26,6 +26,7 @@ import type { AuthRotationAdapter } from "../model/auth-rotation-adapter.js";
26
26
  import type { ProviderHealthMonitor } from "../safety/provider-health-monitor.js";
27
27
  import type { LastKnownModelTracker } from "../model/last-known-model.js";
28
28
  import type { EnvelopeConfig } from "@comis/core";
29
+ import type { CapabilityIndexRenderResult } from "./capability-index-context.js";
29
30
  /** Bridge interface used by the prompt runner (minimal getResult). */
30
31
  export interface PromptRunnerBridge {
31
32
  getResult(): {
@@ -60,6 +61,12 @@ export interface RunPromptParams {
60
61
  bridge: PromptRunnerBridge;
61
62
  dynamicPreamble: string | undefined;
62
63
  deferredContext: string | undefined;
64
+ /**
65
+ * Per-turn capability-index render result. The .text field is concatenated
66
+ * into the dynamic preamble via `[...].filter(Boolean).join("\n\n")`; the
67
+ * count fields feed the Pino debug log below.
68
+ */
69
+ capabilityIndexResult: CapabilityIndexRenderResult;
63
70
  inlineMemory: string | undefined;
64
71
  systemPrompt: string | undefined;
65
72
  mergedCustomTools: Array<{
@@ -47,7 +47,7 @@ import { getCacheProviderInfo } from "../executor/cache-usage-helpers.js";
47
47
  * @returns Prompt execution outcome (success, error, escalation state)
48
48
  */
49
49
  export async function runPrompt(params) {
50
- const { msg, session, config, sessionKey, formattedKey, agentId, result, executionStartMs, effectiveTimeout, executionId, bridge, dynamicPreamble, deferredContext, inlineMemory, systemPrompt, mergedCustomTools, cmdResult, sepEnabled, executionPlanRef, _directives, _prevTimestamp, resolvedModel, deps, onResetTimer, getLastCacheWriteTokens, budgetWarningRef, } = params;
50
+ const { msg, session, config, sessionKey, formattedKey, agentId, result, executionStartMs, effectiveTimeout, executionId, bridge, dynamicPreamble, deferredContext, capabilityIndexResult, inlineMemory, systemPrompt, mergedCustomTools, cmdResult, sepEnabled, executionPlanRef, _directives, _prevTimestamp, resolvedModel, deps, onResetTimer, getLastCacheWriteTokens, budgetWarningRef, } = params;
51
51
  // Wrap message text with envelope
52
52
  let messageText = deps.envelopeConfig
53
53
  ? wrapInEnvelope(msg, deps.envelopeConfig, _prevTimestamp)
@@ -55,12 +55,33 @@ export async function runPrompt(params) {
55
55
  // Prepend dynamic preamble (date/time, inbound metadata)
56
56
  // relocated from system prompt for cache stability.
57
57
  // Also includes <deferred-tools> context block when deferred tools exist.
58
- const fullDynamicPreamble = deferredContext
59
- ? (dynamicPreamble ? dynamicPreamble + "\n\n" + deferredContext : deferredContext)
60
- : dynamicPreamble;
58
+ //
59
+ // Array-concat shape. Each element either contributes a non-empty string
60
+ // or filters out cleanly. The renderer's EMPTY sentinel (gate-disabled OR
61
+ // all-zero counts) yields text === "" which .filter(Boolean) drops
62
+ // automatically.
63
+ const capabilityIndexContext = capabilityIndexResult.text;
64
+ const fullDynamicPreamble = [dynamicPreamble, capabilityIndexContext, deferredContext]
65
+ .filter(Boolean)
66
+ .join("\n\n");
61
67
  if (fullDynamicPreamble) {
62
68
  messageText = `[System context]\n${fullDynamicPreamble}\n[End system context]\n\n${messageText}`;
63
69
  }
70
+ // Pino debug log. Submodule binding per AGENTS.md §2.7:
71
+ // deps.logger.child({ submodule }) attaches the label only at this call
72
+ // site, not module-scope.
73
+ const submoduleLogger = deps.logger.child({ submodule: "executor.capability-index" });
74
+ const fullPreambleTokens = Math.ceil((fullDynamicPreamble ?? "").length / CHARS_PER_TOKEN_RATIO);
75
+ const deferredContextTokens = Math.ceil((deferredContext ?? "").length / CHARS_PER_TOKEN_RATIO);
76
+ submoduleLogger.debug({
77
+ capabilityIndexTokens: capabilityIndexResult.capabilityIndexTokens,
78
+ deferredContextTokens,
79
+ fullPreambleTokens,
80
+ clusterCount: capabilityIndexResult.clusterCount,
81
+ activeToolCount: capabilityIndexResult.activeToolCount,
82
+ deferredToolCount: capabilityIndexResult.deferredToolCount,
83
+ promptSkillCount: capabilityIndexResult.promptSkillCount,
84
+ }, "Dynamic preamble assembled");
64
85
  // Task 229: Inject top-1 RAG memory inline, adjacent to user message
65
86
  // for maximum LLM attention. Placed AFTER [End system context] and
66
87
  // BEFORE the user's actual question text.
@@ -15,9 +15,10 @@
15
15
  import { SettingsManager, DefaultResourceLoader } from "@mariozechner/pi-coding-agent";
16
16
  import type { ToolDefinition } from "@mariozechner/pi-coding-agent";
17
17
  import type { AgentTool } from "@mariozechner/pi-agent-core";
18
- import { type SessionKey, type NormalizedMessage, type PerAgentConfig, type TypedEventBus, type MemoryPort, type HookRunner, type SecretManager, type EnvelopeConfig, type SenderTrustDisplayConfig } from "@comis/core";
18
+ import { type SessionKey, type NormalizedMessage, type PerAgentConfig, type TypedEventBus, type MemoryPort, type HookRunner, type SecretManager, type EnvelopeConfig, type SenderTrustDisplayConfig, type ToolCapabilityPort } from "@comis/core";
19
19
  import type { ComisLogger } from "@comis/infra";
20
20
  import type { ExcludeDeferralResult } from "./tool-deferral.js";
21
+ import type { CapabilityIndexRenderResult } from "./capability-index-context.js";
21
22
  import type { DiscoveryTracker } from "./discovery-tracker.js";
22
23
  import type { ExecutionPromptResult } from "./prompt-assembly.js";
23
24
  import type { ExecutionOverrides } from "./types.js";
@@ -48,6 +49,13 @@ export interface ToolAssemblyDeps {
48
49
  maxCharsPerInjection: number;
49
50
  };
50
51
  embeddingPort?: EmbeddingPort;
52
+ /**
53
+ * Tool-capability port for the per-turn capability-index renderer.
54
+ * Daemon wiring injects createNoOpCapabilityPort() from @comis/core; the
55
+ * live adapter is swapped in elsewhere. The no-op is a real production
56
+ * code path — NOT a transitional shim.
57
+ */
58
+ toolCapabilityPort: ToolCapabilityPort;
51
59
  skillRegistry?: {
52
60
  getEligibleSkillNames(): Set<string>;
53
61
  initFromSdkSkills(sdkSkills: Array<{
@@ -70,6 +78,15 @@ export interface ToolAssemblyResult {
70
78
  deferralResult: ExcludeDeferralResult;
71
79
  /** Formatted deferred tools context for dynamic preamble injection. */
72
80
  deferredContext: string;
81
+ /**
82
+ * Per-turn capability-index render result.
83
+ * `text` is concatenated into the dynamic preamble; the count fields feed
84
+ * the Pino debug log emitted in `executor-prompt-runner.ts`.
85
+ * When the port returns gate-disabled or all counts are zero, the renderer
86
+ * returns the EMPTY sentinel and `text === ""` filters out via
87
+ * `[...].filter(Boolean)` in the runner.
88
+ */
89
+ capabilityIndexResult: CapabilityIndexRenderResult;
73
90
  /** Session-scoped guide delivery tracking set. */
74
91
  deliveredGuides: Set<string>;
75
92
  /** Model tier derived from context window: "small" | "medium" | "large". */
@@ -15,7 +15,8 @@
15
15
  */
16
16
  import { SettingsManager, } from "@mariozechner/pi-coding-agent";
17
17
  import { formatSessionKey, } from "@comis/core";
18
- import { applyToolDeferral, buildDeferredToolsContext, createDiscoverTool, createAutoDiscoveryStubs, extractRecentlyUsedToolNames, resolveModelTier, supportsToolSearch, CORE_TOOLS } from "./tool-deferral.js";
18
+ import { applyToolDeferral, buildDeferredToolsContext, createDiscoverTool, createAutoDiscoveryStubs, extractRecentlyUsedToolNames, resolveModelTier, CORE_TOOLS } from "./tool-deferral.js";
19
+ import { buildCapabilityIndexContext } from "./capability-index-context.js";
19
20
  import { getOrCreateDiscoveryTracker } from "./discovery-tracker.js";
20
21
  import { getOrCreateTracker, DEFAULT_LIFECYCLE_CONFIG } from "./tool-lifecycle.js";
21
22
  import { isAnthropicFamily, isGoogleFamily } from "../provider/capabilities.js";
@@ -159,6 +160,9 @@ export async function assembleTools(params) {
159
160
  deliveryMirror: deps.deliveryMirror,
160
161
  deliveryMirrorConfig: deps.deliveryMirrorConfig,
161
162
  channelMaxChars: deps.getChannelMaxChars?.(msg.channelType),
163
+ // Forward the tool-capability port so prompt-assembly.ts can read
164
+ // `port.isCapabilityIndexEnabled()` for the static-prompt swap gate.
165
+ toolCapabilityPort: deps.toolCapabilityPort,
162
166
  },
163
167
  msg,
164
168
  sessionKey,
@@ -321,24 +325,22 @@ export async function assembleTools(params) {
321
325
  const stubs = createAutoDiscoveryStubs(deferralResult.deferredEntries, discoveryTracker, deps.logger);
322
326
  mergedCustomTools.push(...stubs);
323
327
  }
324
- // Build deferred context for dynamic preamble injection.
325
- //
326
- // Under Anthropic Sonnet/Opus 4.x, request-body-injector.ts
327
- // strips client-side `discover_tools` from the API payload and replaces it
328
- // with the server-side `tool_search_tool_regex` + per-tool `defer_loading`
329
- // flag. Pass `useToolSearch=true` so the preamble teaches the model that
330
- // deferred tools auto-load on first direct invocation, rather than telling
331
- // it to call a tool the model can no longer see.
332
- //
333
- // resolvedModel is in scope here (param of assembleToolsForAgent, see
334
- // function signature ~line 143). When undefined (test paths / fallback),
335
- // useToolSearch defaults to false, preserving backward-compatible
336
- // discover_tools wording.
328
+ // Build deferred-tools context for dynamic preamble injection (mechanism-neutral).
329
+ // Provider-specific payload reshaping (stripping the client-side discovery
330
+ // tool, appending the server-side tool-search regex tool for Anthropic
331
+ // Sonnet/Opus 4.x) lives entirely in `request-body-injector.ts` and is
332
+ // gated there by a model-id capability check. See tool-deferral.ts JSDoc
333
+ // on `buildDeferredToolsContext` for the rationale.
337
334
  let deferredContext = "";
338
335
  if (deferralResult.deferredEntries.length > 0) {
339
- const useToolSearch = supportsToolSearch(resolvedModel?.id ?? "");
340
- deferredContext = buildDeferredToolsContext(deferralResult.deferredEntries, { useToolSearch });
336
+ deferredContext = buildDeferredToolsContext(deferralResult.deferredEntries);
341
337
  }
338
+ // Per-turn capability index.
339
+ // Lives AFTER applyToolDeferral so the renderer sees the post-partition
340
+ // state (active vs deferred entries). When the port is the no-op, the
341
+ // renderer's gate check still respects port.isCapabilityIndexEnabled();
342
+ // if false, returns EMPTY which the runner filters via .filter(Boolean).
343
+ const capabilityIndexResult = buildCapabilityIndexContext(deferralResult, deps.toolCapabilityPort);
342
344
  // -------------------------------------------------------------------
343
345
  // 8. JIT guide wrapping, schema pruning, snapshot, normalization, serializer
344
346
  // -------------------------------------------------------------------
@@ -368,6 +370,7 @@ export async function assembleTools(params) {
368
370
  mergedCustomTools,
369
371
  deferralResult,
370
372
  deferredContext,
373
+ capabilityIndexResult,
371
374
  deliveredGuides,
372
375
  modelTier,
373
376
  discoveryTracker,
@@ -35,10 +35,19 @@ import type { AgentToolResult } from "@mariozechner/pi-agent-core";
35
35
  *
36
36
  * - If no guide exists for the tool, returns result unchanged.
37
37
  * - If guide was already delivered in this session, returns result unchanged.
38
- * - Always marks the tool as delivered (even on error).
39
- * - Skips injection when result has isError: true (but still marks delivered).
38
+ * - Skips injection when result has isError: true and does NOT mark delivered
39
+ * (so a retry can fire see regression coverage in `jit-guide-injector.test.ts`).
40
40
  * - Logs at INFO level when guide is injected.
41
41
  *
42
+ * **Re-injection de-dup contract.** The `deliveredGuides` Set is the single
43
+ * source of truth for which guide ids have been injected within the current
44
+ * scope (per-session by default; cleared on session reset by pi-executor).
45
+ * Within a single turn — even when the same tool fires multiple times via
46
+ * mid-turn discovery / retries — the same guide id is not re-injected
47
+ * because the early-return on `deliveredGuides.has(...)` fires on the
48
+ * second-and-later check. The existing Set IS the per-turn (and per-session)
49
+ * tracker; no second data structure is needed.
50
+ *
42
51
  * Note: AgentToolResult does not formally include isError (it is set by
43
52
  * the agent-loop on the message level), but some tools (MCP, discovery)
44
53
  * include it at runtime. We check for it defensively.
@@ -42,10 +42,19 @@ const PRIVILEGED_SECTION_KEY = "section:privileged";
42
42
  *
43
43
  * - If no guide exists for the tool, returns result unchanged.
44
44
  * - If guide was already delivered in this session, returns result unchanged.
45
- * - Always marks the tool as delivered (even on error).
46
- * - Skips injection when result has isError: true (but still marks delivered).
45
+ * - Skips injection when result has isError: true and does NOT mark delivered
46
+ * (so a retry can fire see regression coverage in `jit-guide-injector.test.ts`).
47
47
  * - Logs at INFO level when guide is injected.
48
48
  *
49
+ * **Re-injection de-dup contract.** The `deliveredGuides` Set is the single
50
+ * source of truth for which guide ids have been injected within the current
51
+ * scope (per-session by default; cleared on session reset by pi-executor).
52
+ * Within a single turn — even when the same tool fires multiple times via
53
+ * mid-turn discovery / retries — the same guide id is not re-injected
54
+ * because the early-return on `deliveredGuides.has(...)` fires on the
55
+ * second-and-later check. The existing Set IS the per-turn (and per-session)
56
+ * tracker; no second data structure is needed.
57
+ *
49
58
  * Note: AgentToolResult does not formally include isError (it is set by
50
59
  * the agent-loop on the message level), but some tools (MCP, discovery)
51
60
  * include it at runtime. We check for it defensively.
@@ -67,6 +76,11 @@ export function wrapToolResultWithGuide(toolName, result, deliveredGuides, logge
67
76
  const wantsPrivileged = PRIVILEGED_TOOL_SET.has(toolName) &&
68
77
  !deliveredGuides.has(PRIVILEGED_SECTION_KEY) &&
69
78
  !!SYSTEM_PROMPT_GUIDES["__privileged_tools__"];
79
+ // Re-injection de-dup: when every candidate guide id is already in
80
+ // `deliveredGuides`, none of `wantsTool`/`wantsSection`/`wantsPrivileged`
81
+ // is true and we early-return without appending. This single check is the
82
+ // per-turn (and per-session) de-dup gate — the same `deliveredGuides` Set
83
+ // passed across calls within a turn IS the injectedGuideIds tracker.
70
84
  if (!wantsTool && !wantsSection && !wantsPrivileged)
71
85
  return result;
72
86
  // Skip on error — but DO NOT consume delivery slots so a retry can fire.
@@ -21,7 +21,7 @@
21
21
  */
22
22
  import type { ToolDefinition } from "@mariozechner/pi-coding-agent";
23
23
  import type { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
24
- import { type PerAgentConfig, type TypedEventBus, type MemoryPort, type HookRunner, type SecretManager, type EnvelopeConfig, type OutputGuardPort, type InputValidationResult, type InputSecurityGuard, type InjectionRateLimiter, type SenderTrustDisplayConfig } from "@comis/core";
24
+ import { type PerAgentConfig, type TypedEventBus, type MemoryPort, type HookRunner, type SecretManager, type EnvelopeConfig, type OutputGuardPort, type InputValidationResult, type InputSecurityGuard, type InjectionRateLimiter, type SenderTrustDisplayConfig, type ToolCapabilityPort } from "@comis/core";
25
25
  import type { ComisLogger } from "@comis/infra";
26
26
  import type { AgentTool } from "@mariozechner/pi-agent-core";
27
27
  import type { BudgetGuard } from "../budget/budget-guard.js";
@@ -53,7 +53,7 @@ import type { AgentExecutor } from "./types.js";
53
53
  */
54
54
  export declare function createBeforeToolCallGuard(stepCounter: StepCounter, budgetGuard: BudgetGuard, circuitBreaker: CircuitBreaker, toolRetryBreaker?: ToolRetryBreaker, messageSendLimiter?: MessageSendLimiter): (context: unknown, _signal?: AbortSignal) => Promise<import("../safety/message-send-limiter.js").MessageSendVerdict | undefined>;
55
55
  /**
56
- * Merge SDK session stats into execution result for token totals (R-13).
56
+ * Merge SDK session stats into execution result for token totals.
57
57
  *
58
58
  * Token counts (input, output, cacheRead, cacheWrite, total) are sourced
59
59
  * from the SDK's cumulative session stats -- single source of truth.
@@ -169,6 +169,12 @@ export interface PiExecutorDeps {
169
169
  embeddingEnqueue?: (entryId: string, content: string) => void;
170
170
  /** Optional embedding port for semantic search in discover_tools. */
171
171
  embeddingPort?: import("@comis/core").EmbeddingPort;
172
+ /**
173
+ * Tool-capability port for the per-turn capability-index renderer.
174
+ * Daemon wiring injects createNoOpCapabilityPort() from @comis/core
175
+ * until the live adapter ships.
176
+ */
177
+ toolCapabilityPort: ToolCapabilityPort;
172
178
  /** Sender trust display config from AppConfig. */
173
179
  senderTrustDisplayConfig?: SenderTrustDisplayConfig;
174
180
  /** Documentation config from AppConfig. */