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
@@ -27,6 +27,15 @@ import { suppressError } from "@comis/shared";
27
27
  * If promotion fails (concurrency limit), awaits the tool normally (foreground fallback).
28
28
  */
29
29
  export function wrapToolForAutoBackground(tool, manager, config, notifyFn, originResolver) {
30
+ // `exec` opts out of the generic auto-background wrapper to enforce
31
+ // single-owner backgrounding. The exec-tool's own internal escalation path
32
+ // (packages/skills/src/builtin/exec-tool.ts:613-668) is the SOLE
33
+ // backgrounding owner for `exec`; the generic timeout-based wrapper would
34
+ // double-promote. Hardcoded literal — does NOT modify config.excludeTools
35
+ // so operator-set exclusions remain unchanged.
36
+ if (tool.name === "exec") {
37
+ return tool;
38
+ }
30
39
  if (config.excludeTools.includes(tool.name)) {
31
40
  return tool;
32
41
  }
@@ -1,6 +1,6 @@
1
1
  import { type Result } from "@comis/shared";
2
2
  import type { TypedEventBus } from "@comis/core";
3
- import type { BackgroundTask, BackgroundTaskOrigin } from "./background-task-types.js";
3
+ import type { BackgroundTask, BackgroundTaskOrigin, BackgroundSessionState, BackgroundTaskNotificationPolicy } from "./background-task-types.js";
4
4
  /** Notification callback fired when background task completes or fails. */
5
5
  export type NotifyFn = (opts: {
6
6
  agentId: string;
@@ -21,8 +21,21 @@ export interface BackgroundTaskManagerOpts {
21
21
  maxBackgroundDurationMs?: number;
22
22
  }
23
23
  export interface BackgroundTaskManager {
24
- promote(toolName: string, promise: Promise<unknown>, ac: AbortController, origin: BackgroundTaskOrigin): Result<string, Error>;
24
+ promote(toolName: string, promise: Promise<unknown>, ac: AbortController, origin: BackgroundTaskOrigin, notificationPolicy?: BackgroundTaskNotificationPolicy): Result<string, Error>;
25
+ /**
26
+ * Mark a task as completed.
27
+ *
28
+ * The legacy `notifyFn` argument is preserved for backward compatibility but
29
+ * unused — the completion-dispatcher subscribes to the
30
+ * `background_task:completed` event emitted here and decides whether to fire
31
+ * the user-visible fallback notification. Single-owner contract eliminates
32
+ * double-notify.
33
+ */
25
34
  complete(taskId: string, result: unknown, notifyFn?: NotifyFn): void;
35
+ /**
36
+ * Mark a task as failed. See `complete` for the single-owner note — the
37
+ * `notifyFn` argument is unused; the dispatcher routes notification.
38
+ */
26
39
  fail(taskId: string, error: unknown, notifyFn?: NotifyFn): void;
27
40
  cancel(taskId: string): Result<void, Error>;
28
41
  getTask(taskId: string): BackgroundTask | undefined;
@@ -30,5 +43,12 @@ export interface BackgroundTaskManager {
30
43
  getAllTasks(): BackgroundTask[];
31
44
  recoverOnStartup(): void;
32
45
  cleanup(maxAgeMs?: number): void;
46
+ /**
47
+ * Atomically transition the in-memory task's dispatchState AND persist.
48
+ * Returns true on success; false if task does not exist. The dispatcher
49
+ * calls this from its handler so SIGKILL-recovery preserves the recovered
50
+ * state across daemon restart.
51
+ */
52
+ transitionDispatchState(taskId: string, next: BackgroundSessionState): boolean;
33
53
  }
34
54
  export declare function createBackgroundTaskManager(opts: BackgroundTaskManagerOpts): BackgroundTaskManager;
@@ -8,7 +8,7 @@
8
8
  * @module
9
9
  */
10
10
  import { randomUUID } from "node:crypto";
11
- import { ok, err, suppressError } from "@comis/shared";
11
+ import { ok, err } from "@comis/shared";
12
12
  import { persistTaskSync, recoverTasks, removeTaskFile } from "./background-task-persistence.js";
13
13
  const MAX_RESULT_CHARS = 102_400; // 100KB
14
14
  export function createBackgroundTaskManager(opts) {
@@ -35,7 +35,7 @@ export function createBackgroundTaskManager(opts) {
35
35
  }
36
36
  }
37
37
  const manager = {
38
- promote(toolName, promise, ac, origin) {
38
+ promote(toolName, promise, ac, origin, notificationPolicy) {
39
39
  // Reject calls with missing/invalid origin (no silent fallback).
40
40
  if (!origin || typeof origin !== "object") {
41
41
  return err(new Error("BackgroundTaskOrigin is required (received undefined or non-object)"));
@@ -69,6 +69,11 @@ export function createBackgroundTaskManager(opts) {
69
69
  status: "running",
70
70
  startedAt: Date.now(),
71
71
  origin,
72
+ // Seed the dispatch state machine. Default policy is "deferred" —
73
+ // the dispatcher inspects dispatchState before firing fallback notify
74
+ // (at-most-once).
75
+ notificationPolicy: notificationPolicy ?? "deferred",
76
+ dispatchState: "pending",
72
77
  _promise: promise,
73
78
  _abortController: ac,
74
79
  };
@@ -92,7 +97,7 @@ export function createBackgroundTaskManager(opts) {
92
97
  });
93
98
  return ok(taskId);
94
99
  },
95
- complete(taskId, result, notifyFn) {
100
+ complete(taskId, result, _notifyFn) {
96
101
  const task = tasks.get(taskId);
97
102
  if (!task || task.status !== "running")
98
103
  return;
@@ -112,16 +117,14 @@ export function createBackgroundTaskManager(opts) {
112
117
  origin: task.origin,
113
118
  timestamp: Date.now(),
114
119
  });
115
- if (notifyFn) {
116
- suppressError(notifyFn({
117
- agentId: task.origin.agentId,
118
- message: `Background task "${task.toolName}" completed (${Math.round(durationMs / 1000)}s). Task ID: ${taskId}`,
119
- priority: "normal",
120
- origin: "background_task",
121
- }), "background task completion notification");
122
- }
120
+ // Notification routing lives in the completion-dispatcher (subscribed
121
+ // to background_task:completed above). The legacy `notifyFn` argument
122
+ // is kept for backward compatibility but unused here — the dispatcher
123
+ // inspects task.dispatchState before firing the user-visible fallback,
124
+ // and the runner skips when state is "notified" (single-owner contract,
125
+ // zero spurious outbound).
123
126
  },
124
- fail(taskId, error, notifyFn) {
127
+ fail(taskId, error, _notifyFn) {
125
128
  const task = tasks.get(taskId);
126
129
  if (!task || task.status !== "running")
127
130
  return;
@@ -142,14 +145,7 @@ export function createBackgroundTaskManager(opts) {
142
145
  origin: task.origin,
143
146
  timestamp: Date.now(),
144
147
  });
145
- if (notifyFn) {
146
- suppressError(notifyFn({
147
- agentId: task.origin.agentId,
148
- message: `Background task "${task.toolName}" failed: ${task.error}. Task ID: ${taskId}`,
149
- priority: "normal",
150
- origin: "background_task",
151
- }), "background task failure notification");
152
- }
148
+ // See complete() above — notifyFn arg is unused; dispatcher owns routing.
153
149
  },
154
150
  cancel(taskId) {
155
151
  const task = tasks.get(taskId);
@@ -185,24 +181,29 @@ export function createBackgroundTaskManager(opts) {
185
181
  recoverOnStartup() {
186
182
  const recovered = recoverTasks(dataDir);
187
183
  let count = 0;
188
- let skipped = 0;
184
+ let dispatchPreserved = 0;
189
185
  for (const persisted of recovered) {
190
- if (!persisted.origin || typeof persisted.origin !== "object" || !persisted.origin.agentId || !persisted.origin.sessionKey) {
191
- // Legacy file without origin. Skip with a warning -- the file
192
- // remains on disk for audit, but the manager doesn't import it.
193
- skipped++;
194
- logger.warn({
195
- taskId: persisted.id,
196
- hint: "Legacy task file lacks origin; skipping recovery -- delete the file or wait for cleanup",
197
- errorKind: "internal",
198
- }, "Skipping recovered task without origin");
199
- continue;
200
- }
201
- const task = {
202
- ...persisted,
203
- };
186
+ // The persistence-write contract guarantees populated origin /
187
+ // notificationPolicy / dispatchState on every task file.
188
+ // background-task-persistence.ts rejects shape-malformed files
189
+ // (missing id / toolName) before they reach here; we propagate the
190
+ // persisted record as-is.
191
+ const task = persisted;
204
192
  tasks.set(task.id, task);
205
193
  if (persisted.status === "failed" && persisted.error === "Daemon restarted while task was running") {
194
+ // Recovery-without-events: if dispatchState is already "notified" or
195
+ // "dispatched", the dispatcher already routed pre-restart; do NOT
196
+ // re-emit the background_task:failed event (which would re-trigger
197
+ // fallback).
198
+ if (task.dispatchState === "notified" || task.dispatchState === "dispatched") {
199
+ dispatchPreserved++;
200
+ logger.debug({
201
+ taskId: task.id,
202
+ dispatchState: task.dispatchState,
203
+ hint: "Pre-restart dispatch state preserved; skipping re-emit (D-S2 recovery-without-events)",
204
+ }, "Recovery: skipped re-emit");
205
+ continue;
206
+ }
206
207
  count++;
207
208
  eventBus.emit("background_task:failed", {
208
209
  agentId: task.origin.agentId,
@@ -218,14 +219,20 @@ export function createBackgroundTaskManager(opts) {
218
219
  if (count > 0) {
219
220
  logger.info({ count }, "Recovered background tasks marked as failed");
220
221
  }
221
- if (skipped > 0) {
222
- logger.warn({
223
- skipped,
224
- hint: "Legacy task files cannot be recovered without origin -- they remain on disk for audit",
225
- errorKind: "internal",
226
- }, "Skipped legacy task files during recovery");
222
+ if (dispatchPreserved > 0) {
223
+ logger.info({ count: dispatchPreserved }, "Recovered tasks with preserved dispatch state (no re-emit)");
227
224
  }
228
225
  },
226
+ transitionDispatchState(taskId, next) {
227
+ const task = tasks.get(taskId);
228
+ if (!task)
229
+ return false;
230
+ // Idempotent — same-state transitions are allowed (no-op write).
231
+ task.dispatchState = next;
232
+ // Persist atomically so recovery-after-SIGKILL sees the transition.
233
+ persistTaskSync(dataDir, task);
234
+ return true;
235
+ },
229
236
  cleanup(maxAgeMs = 86_400_000) {
230
237
  const cutoff = Date.now() - maxAgeMs;
231
238
  for (const [taskId, task] of tasks) {
@@ -7,12 +7,17 @@
7
7
  *
8
8
  * @module
9
9
  */
10
- import { mkdirSync, writeFileSync, readFileSync, readdirSync, unlinkSync, existsSync } from "node:fs";
10
+ import { mkdirSync, writeFileSync, readFileSync, readdirSync, statSync, unlinkSync, existsSync } from "node:fs";
11
11
  import { safePath } from "@comis/core";
12
12
  /** Directory name under data dir for background task state files. */
13
13
  export const TASK_DIR_NAME = "background-tasks";
14
14
  /**
15
15
  * Extract the serializable subset from a BackgroundTask.
16
+ *
17
+ * notificationPolicy + dispatchState are copied across when present so the
18
+ * state machine survives daemon restart-recovery. Both fields are optional in
19
+ * PersistedTaskState; we use spread-when-defined to avoid emitting
20
+ * `"notificationPolicy": undefined` to disk for callers that do not set them.
16
21
  */
17
22
  function toPersistedState(task) {
18
23
  return {
@@ -24,6 +29,8 @@ function toPersistedState(task) {
24
29
  result: task.result,
25
30
  error: task.error,
26
31
  origin: task.origin,
32
+ ...(task.notificationPolicy !== undefined && { notificationPolicy: task.notificationPolicy }),
33
+ ...(task.dispatchState !== undefined && { dispatchState: task.dispatchState }),
27
34
  };
28
35
  }
29
36
  /**
@@ -72,6 +79,20 @@ export function recoverTasks(dataDir) {
72
79
  }
73
80
  for (const agentId of agentDirs) {
74
81
  const agentDir = safePath(dataDir, agentId);
82
+ // Guard against non-directory entries in dataDir. statSync may throw if
83
+ // the entry vanished between readdirSync and here; skip gracefully.
84
+ // Non-directory entries (lock files, READMEs, accidental
85
+ // file-with-agentId-name) MUST be skipped explicitly so they don't
86
+ // shadow legitimate agent recovery silently.
87
+ let dirStat;
88
+ try {
89
+ dirStat = statSync(agentDir);
90
+ }
91
+ catch {
92
+ continue;
93
+ }
94
+ if (!dirStat.isDirectory())
95
+ continue;
75
96
  let files;
76
97
  try {
77
98
  files = readdirSync(agentDir);
@@ -86,10 +107,12 @@ export function recoverTasks(dataDir) {
86
107
  try {
87
108
  const raw = readFileSync(filePath, "utf-8");
88
109
  const parsed = JSON.parse(raw);
89
- // Sanity guard: skip completely malformed files (no id or toolName).
90
- // Files missing origin (pre-Phase-14) are passed through to the manager
91
- // so recoverOnStartup can warn about them and track the skip count.
92
- if (!parsed.id || !parsed.toolName) {
110
+ // Shape guard skip completely malformed files. Tasks always carry
111
+ // id + toolName + origin; the producer-side persistTaskSync writes
112
+ // all three unconditionally. A file failing this guard is either
113
+ // truncated mid-write or a legacy artifact operators should clean
114
+ // up manually.
115
+ if (!parsed.id || !parsed.toolName || !parsed.origin) {
93
116
  continue;
94
117
  }
95
118
  const task = parsed;
@@ -6,6 +6,38 @@
6
6
  import type { BackgroundTaskOrigin } from "@comis/core";
7
7
  export type { BackgroundTaskOrigin };
8
8
  export type BackgroundTaskStatus = "running" | "completed" | "failed" | "cancelled";
9
+ /**
10
+ * Notification policy for a background task. Typed enum (NOT a boolean):
11
+ * preserves intent across restart-recovery so the recovered task's dispatch
12
+ * path matches its original promote-time intent. A boolean collapses to
13
+ * true/false on rehydrate and loses the distinction between "the operator
14
+ * wanted deferred routing" and "the operator wanted immediate notification".
15
+ *
16
+ * - "deferred" — Default. Wait for the dispatcher to attempt session
17
+ * re-entry; only fall back to user-visible notification
18
+ * when re-entry fails (session expired, hop cap hit).
19
+ * - "immediate" — Skip the dispatcher; fire user-visible notification
20
+ * immediately. Reserved for tasks that explicitly want
21
+ * the legacy literal-text notification.
22
+ * - "silent" — Skip both dispatcher and user-visible notification.
23
+ * Reserved for fully-internal tasks.
24
+ *
25
+ * Default for new promote() calls: "deferred".
26
+ */
27
+ export type BackgroundTaskNotificationPolicy = "deferred" | "immediate" | "silent";
28
+ /**
29
+ * Three-state session lifecycle for a background task's notification routing.
30
+ * State-machine transitions are the single source of truth for at-most-once
31
+ * fallback.
32
+ *
33
+ * - "pending" — Promotion happened; no completion event yet (or completion
34
+ * event arrived but dispatcher has not classified it).
35
+ * - "notified" — Fallback notification fired (user-visible literal text);
36
+ * recovery-after-restart MUST NOT re-emit.
37
+ * - "dispatched" — Re-entry triggered against the originating session;
38
+ * no fallback notification needed.
39
+ */
40
+ export type BackgroundSessionState = "pending" | "notified" | "dispatched";
9
41
  export interface BackgroundTask {
10
42
  id: string;
11
43
  toolName: string;
@@ -18,6 +50,13 @@ export interface BackgroundTask {
18
50
  * promote-time, persisted on disk, preserved across recoverOnStartup.
19
51
  * Required (no silent fallback). */
20
52
  origin: BackgroundTaskOrigin;
53
+ /** Live notification policy. Optional; recovery defaults to "deferred" when
54
+ * absent. */
55
+ notificationPolicy?: BackgroundTaskNotificationPolicy;
56
+ /** Live three-state session lifecycle. Optional; recovery defaults to
57
+ * "pending" when absent. The dispatcher inspects this before firing
58
+ * notifyFn. */
59
+ dispatchState?: BackgroundSessionState;
21
60
  _promise?: Promise<unknown>;
22
61
  _abortController?: AbortController;
23
62
  _hardTimeoutTimer?: ReturnType<typeof setTimeout>;
@@ -34,4 +73,14 @@ export interface PersistedTaskState {
34
73
  /** Persisted origin -- read back by recoverOnStartup so completion routing
35
74
  * survives daemon restarts. */
36
75
  origin: BackgroundTaskOrigin;
76
+ /**
77
+ * Notification policy chosen at promote time. Optional; recovery defaults
78
+ * to "deferred" when absent.
79
+ */
80
+ notificationPolicy?: BackgroundTaskNotificationPolicy;
81
+ /**
82
+ * Three-state session lifecycle. Optional; recovery defaults to "pending"
83
+ * when absent. The dispatcher inspects this before firing notifyFn.
84
+ */
85
+ dispatchState?: BackgroundSessionState;
37
86
  }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Completion dispatcher: routes background_task:completed/failed events
3
+ * through the BackgroundSessionState machine.
4
+ *
5
+ * Subscribes to background_task:completed and background_task:failed BEFORE
6
+ * the existing BackgroundCompletionRunner. On each event:
7
+ * 1. Reads `task.dispatchState`.
8
+ * 2. If "pending": transitions to "notified" only when the runner cannot
9
+ * re-enter the originating session (no active session for the formatted
10
+ * key, or recursion limit reached). Otherwise transitions to "dispatched"
11
+ * and lets the completion-runner perform re-entry.
12
+ * 3. If already "notified" or "dispatched": no-op (at-most-once).
13
+ *
14
+ * The runner is wired AFTER the dispatcher in setup-background-completion-
15
+ * runner.ts so its handler reads the updated `task.dispatchState` and skips
16
+ * its own work when state is "notified" (the dispatcher already fired
17
+ * fallback). This single-owner contract ensures the completion runner does
18
+ * not double-fire user-visible notifications: the dispatcher routes via
19
+ * persistent state instead of an in-memory event handler, and gates on
20
+ * state instead of unconditionally firing.
21
+ *
22
+ * **State persistence:** every transition calls `manager.transitionDispatch
23
+ * State(taskId, next)` (when the manager exposes it) which mutates the
24
+ * in-memory task AND calls persistTaskSync. Recovery-after-SIGKILL reads
25
+ * the persisted state and the manager skips re-emitting completion events
26
+ * for already-dispatched / already-notified tasks.
27
+ *
28
+ * **Failure isolation:** each handler is wrapped in suppressError so a
29
+ * single dispatch's failure does not tear down the subscription
30
+ * (AGENTS §2.1).
31
+ *
32
+ * @module
33
+ */
34
+ import type { TypedEventBus } from "@comis/core";
35
+ import type { ComisLogger } from "@comis/infra";
36
+ import type { BackgroundTask, BackgroundSessionState, BackgroundTaskNotificationPolicy as NotificationPolicyType } from "./background-task-types.js";
37
+ import type { NotifyFn } from "./background-task-manager.js";
38
+ /**
39
+ * The 3-state typed enum as a runtime array. Order matches transition order:
40
+ * pending → (notified | dispatched).
41
+ *
42
+ * Exported as a `readonly string[]` so tests can assert
43
+ * `STATES === ["pending", "notified", "dispatched"]`.
44
+ */
45
+ export declare const STATES: readonly BackgroundSessionState[];
46
+ /**
47
+ * Notification policy as a runtime object so it round-trips through
48
+ * JSON.parse(JSON.stringify(...)) preserving identity. A boolean would
49
+ * collapse to true/false on rehydrate and lose the distinction between
50
+ * "deferred" / "immediate" / "silent".
51
+ *
52
+ * The typed enum is the single source of truth. This runtime object is a
53
+ * discoverability surface (tests, debugging, logs); production code uses
54
+ * the type-only `BackgroundTaskNotificationPolicy` from
55
+ * `background-task-types.ts`.
56
+ */
57
+ export declare const BackgroundTaskNotificationPolicy: Record<string, NotificationPolicyType>;
58
+ /** Public-facing handle on the dispatcher. */
59
+ export interface CompletionDispatcher {
60
+ /** Unsubscribe from the event bus. Idempotent. Awaitable so callers can
61
+ * ensure no in-flight handler outlives daemon shutdown. */
62
+ shutdown(): Promise<void>;
63
+ }
64
+ /** Minimal session-store contract the dispatcher needs (active-session check). */
65
+ export interface DispatcherSessionStore {
66
+ loadByFormattedKey(sessionKey: string): unknown | undefined;
67
+ }
68
+ /**
69
+ * Minimal taskManager contract: read + (optionally) persist transitions.
70
+ *
71
+ * `transitionDispatchState` is optional so the dispatcher composes cleanly
72
+ * with the test fixture in completion-dispatcher.test.ts (which constructs
73
+ * `taskManager: { getTask: vi.fn() }` and asserts the at-most-once gate
74
+ * without exercising state persistence). Production wiring adds
75
+ * `transitionDispatchState` on the real BackgroundTaskManager so the
76
+ * recovery-after-SIGKILL contract is binding.
77
+ */
78
+ export interface DispatcherTaskManager {
79
+ getTask(taskId: string): BackgroundTask | undefined;
80
+ /**
81
+ * Atomically transition the in-memory task's dispatchState AND persist.
82
+ * Returns true on success; false if task does not exist. Optional —
83
+ * when absent, the dispatcher routes purely via in-memory state.
84
+ */
85
+ transitionDispatchState?(taskId: string, next: BackgroundSessionState): boolean;
86
+ }
87
+ /**
88
+ * Dispatcher dependencies.
89
+ *
90
+ * Public minimum: `eventBus`, `taskManager`, `logger`. Tests use
91
+ * `notifyFn`; production wires both `notifyFn` and `fallbackNotifyFn`
92
+ * (they are aliases — the dispatcher prefers `fallbackNotifyFn` when
93
+ * both are provided so production callers reading the daemon wiring
94
+ * see the canonical name).
95
+ *
96
+ * `sessionStore` + `maxBackgroundHops` are optional. When absent, the
97
+ * dispatcher falls back to the safe behavior: pending → dispatched
98
+ * (let the runner attempt re-entry), no fallback notification fired
99
+ * from the dispatcher itself.
100
+ */
101
+ export interface CompletionDispatcherDeps {
102
+ eventBus: TypedEventBus;
103
+ taskManager: DispatcherTaskManager;
104
+ /**
105
+ * User-visible notification fired when the dispatcher cannot route to
106
+ * the originating session. Either name accepted; `fallbackNotifyFn`
107
+ * preferred when both are provided.
108
+ */
109
+ fallbackNotifyFn?: NotifyFn;
110
+ /** Alias for `fallbackNotifyFn` (test fixture compatibility). */
111
+ notifyFn?: NotifyFn;
112
+ /** Active-session check (production wiring). When absent, the dispatcher
113
+ * defers to the runner without firing fallback. */
114
+ sessionStore?: DispatcherSessionStore;
115
+ /** Recursion limit for background-task hop counting. When absent, the
116
+ * dispatcher does not enforce the cap (defers to the runner). */
117
+ maxBackgroundHops?: number;
118
+ logger: ComisLogger;
119
+ }
120
+ /**
121
+ * Wire the completion dispatcher against an event bus + task manager.
122
+ * Subscriptions are installed synchronously; call shutdown() to remove them.
123
+ *
124
+ * At-most-once fallback: the state-machine transitions on
125
+ * `task.dispatchState` are the single source of truth. The dispatcher's
126
+ * synchronous transitionDispatchState runs BEFORE the completion-runner's
127
+ * handler reads the updated state, by virtue of the event-bus subscribing
128
+ * the dispatcher first (see setup-background-completion-runner.ts).
129
+ */
130
+ export declare function createCompletionDispatcher(deps: CompletionDispatcherDeps): CompletionDispatcher;