comisai 1.0.14 → 1.0.16

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 (79) hide show
  1. package/node_modules/@comis/agent/dist/bootstrap/index.d.ts +1 -1
  2. package/node_modules/@comis/agent/dist/bootstrap/index.js +1 -1
  3. package/node_modules/@comis/agent/dist/bootstrap/system-prompt-assembler.d.ts +26 -0
  4. package/node_modules/@comis/agent/dist/bootstrap/system-prompt-assembler.js +86 -54
  5. package/node_modules/@comis/agent/dist/bootstrap/types.d.ts +7 -1
  6. package/node_modules/@comis/agent/dist/bootstrap/workspace-loader.d.ts +12 -0
  7. package/node_modules/@comis/agent/dist/bootstrap/workspace-loader.js +21 -0
  8. package/node_modules/@comis/agent/dist/bridge/pi-event-bridge.js +12 -11
  9. package/node_modules/@comis/agent/dist/context-engine/constants.d.ts +11 -0
  10. package/node_modules/@comis/agent/dist/context-engine/constants.js +11 -0
  11. package/node_modules/@comis/agent/dist/context-engine/index.d.ts +1 -1
  12. package/node_modules/@comis/agent/dist/context-engine/index.js +1 -1
  13. package/node_modules/@comis/agent/dist/context-engine/llm-compaction.js +32 -18
  14. package/node_modules/@comis/agent/dist/executor/cache-break-diff-writer.d.ts +2 -0
  15. package/node_modules/@comis/agent/dist/executor/cache-break-diff-writer.js +36 -0
  16. package/node_modules/@comis/agent/dist/executor/executor-post-execution.d.ts +12 -0
  17. package/node_modules/@comis/agent/dist/executor/executor-post-execution.js +117 -27
  18. package/node_modules/@comis/agent/dist/executor/executor-prompt-runner.d.ts +1 -0
  19. package/node_modules/@comis/agent/dist/executor/executor-prompt-runner.js +16 -4
  20. package/node_modules/@comis/agent/dist/executor/executor-stream-setup.js +17 -1
  21. package/node_modules/@comis/agent/dist/executor/executor-tool-assembly.js +29 -1
  22. package/node_modules/@comis/agent/dist/executor/prompt-assembly.d.ts +8 -1
  23. package/node_modules/@comis/agent/dist/executor/prompt-assembly.js +22 -5
  24. package/node_modules/@comis/agent/dist/executor/session-snapshot-cleanup.js +3 -1
  25. package/node_modules/@comis/agent/dist/executor/stream-wrappers/index.d.ts +2 -0
  26. package/node_modules/@comis/agent/dist/executor/stream-wrappers/index.js +2 -0
  27. package/node_modules/@comis/agent/dist/executor/stream-wrappers/request-body-injector.d.ts +9 -0
  28. package/node_modules/@comis/agent/dist/executor/stream-wrappers/request-body-injector.js +65 -3
  29. package/node_modules/@comis/agent/dist/executor/stream-wrappers/stub-filter-injector.d.ts +28 -0
  30. package/node_modules/@comis/agent/dist/executor/stream-wrappers/stub-filter-injector.js +63 -0
  31. package/node_modules/@comis/agent/dist/executor/tool-deferral.d.ts +18 -1
  32. package/node_modules/@comis/agent/dist/executor/tool-deferral.js +275 -133
  33. package/node_modules/@comis/agent/dist/executor/ttl-guard.d.ts +6 -0
  34. package/node_modules/@comis/agent/dist/executor/ttl-guard.js +8 -0
  35. package/node_modules/@comis/agent/dist/session/comis-session-manager.js +7 -2
  36. package/node_modules/@comis/agent/package.json +1 -1
  37. package/node_modules/@comis/channels/dist/shared/typing-controller.d.ts +7 -0
  38. package/node_modules/@comis/channels/dist/shared/typing-controller.js +33 -0
  39. package/node_modules/@comis/channels/package.json +1 -1
  40. package/node_modules/@comis/cli/dist/commands/daemon.js +116 -28
  41. package/node_modules/@comis/cli/dist/doctor/checks/channel-health.js +2 -2
  42. package/node_modules/@comis/cli/dist/wizard/index.d.ts +2 -3
  43. package/node_modules/@comis/cli/dist/wizard/index.js +2 -3
  44. package/node_modules/@comis/cli/dist/wizard/steps/10-write-config.js +1 -1
  45. package/node_modules/@comis/cli/dist/wizard/types.d.ts +1 -2
  46. package/node_modules/@comis/cli/dist/wizard/types.js +1 -2
  47. package/node_modules/@comis/cli/package.json +1 -1
  48. package/node_modules/@comis/core/dist/config/schema-agent.d.ts +64 -10
  49. package/node_modules/@comis/core/dist/config/schema-agent.js +19 -0
  50. package/node_modules/@comis/core/dist/config/schema-skills.d.ts +16 -2
  51. package/node_modules/@comis/core/dist/config/schema-skills.js +9 -2
  52. package/node_modules/@comis/core/dist/config/schema.d.ts +19 -11
  53. package/node_modules/@comis/core/dist/event-bus/events-infra.d.ts +8 -0
  54. package/node_modules/@comis/core/package.json +1 -1
  55. package/node_modules/@comis/daemon/dist/daemon.js +2 -1
  56. package/node_modules/@comis/daemon/dist/wiring/setup-channels.js +19 -2
  57. package/node_modules/@comis/daemon/dist/wiring/setup-heartbeat.js +10 -0
  58. package/node_modules/@comis/daemon/dist/wiring/setup-memory.d.ts +2 -0
  59. package/node_modules/@comis/daemon/dist/wiring/setup-memory.js +15 -0
  60. package/node_modules/@comis/daemon/dist/wiring/setup-schedulers.js +1 -0
  61. package/node_modules/@comis/daemon/dist/wiring/setup-shutdown.d.ts +1 -0
  62. package/node_modules/@comis/daemon/dist/wiring/setup-shutdown.js +4 -0
  63. package/node_modules/@comis/daemon/package.json +1 -1
  64. package/node_modules/@comis/gateway/package.json +1 -1
  65. package/node_modules/@comis/infra/package.json +1 -1
  66. package/node_modules/@comis/memory/package.json +1 -1
  67. package/node_modules/@comis/scheduler/dist/cron/cron-types.d.ts +11 -0
  68. package/node_modules/@comis/scheduler/dist/cron/cron-types.js +11 -0
  69. package/node_modules/@comis/scheduler/dist/heartbeat/agent-heartbeat-source.d.ts +26 -1
  70. package/node_modules/@comis/scheduler/dist/heartbeat/agent-heartbeat-source.js +23 -2
  71. package/node_modules/@comis/scheduler/dist/heartbeat/heartbeat-config.d.ts +7 -0
  72. package/node_modules/@comis/scheduler/dist/heartbeat/heartbeat-config.js +2 -0
  73. package/node_modules/@comis/scheduler/package.json +1 -1
  74. package/node_modules/@comis/shared/package.json +1 -1
  75. package/node_modules/@comis/skills/dist/index.d.ts +1 -1
  76. package/node_modules/@comis/skills/dist/index.js +1 -1
  77. package/node_modules/@comis/skills/dist/policy/tool-policy.js +25 -0
  78. package/node_modules/@comis/skills/package.json +1 -1
  79. package/package.json +12 -12
@@ -54,6 +54,9 @@ export declare const AppConfigSchema: z.ZodObject<{
54
54
  "multi-zone": "multi-zone";
55
55
  single: "single";
56
56
  }>>;
57
+ advancedCacheOptimization: z.ZodDefault<z.ZodObject<{
58
+ enableRecentZonePromotion: z.ZodDefault<z.ZodBoolean>;
59
+ }, z.core.$strip>>;
57
60
  geminiCache: z.ZodDefault<z.ZodObject<{
58
61
  enabled: z.ZodDefault<z.ZodBoolean>;
59
62
  maxActiveCaches: z.ZodDefault<z.ZodNumber>;
@@ -105,9 +108,9 @@ export declare const AppConfigSchema: z.ZodObject<{
105
108
  bootstrap: z.ZodDefault<z.ZodObject<{
106
109
  maxChars: z.ZodDefault<z.ZodNumber>;
107
110
  promptMode: z.ZodDefault<z.ZodEnum<{
108
- minimal: "minimal";
109
- full: "full";
110
111
  none: "none";
112
+ full: "full";
113
+ minimal: "minimal";
111
114
  }>>;
112
115
  groupChatFiltering: z.ZodDefault<z.ZodBoolean>;
113
116
  }, z.core.$strict>>;
@@ -184,11 +187,11 @@ export declare const AppConfigSchema: z.ZodObject<{
184
187
  }, z.core.$strict>>;
185
188
  toolPolicy: z.ZodDefault<z.ZodObject<{
186
189
  profile: z.ZodDefault<z.ZodEnum<{
190
+ full: "full";
187
191
  minimal: "minimal";
188
192
  coding: "coding";
189
193
  messaging: "messaging";
190
194
  supervisor: "supervisor";
191
- full: "full";
192
195
  }>>;
193
196
  allow: z.ZodDefault<z.ZodArray<z.ZodString>>;
194
197
  deny: z.ZodDefault<z.ZodArray<z.ZodString>>;
@@ -250,6 +253,11 @@ export declare const AppConfigSchema: z.ZodObject<{
250
253
  alertThreshold: z.ZodOptional<z.ZodNumber>;
251
254
  alertCooldownMs: z.ZodOptional<z.ZodNumber>;
252
255
  staleMs: z.ZodOptional<z.ZodNumber>;
256
+ toolPolicy: z.ZodOptional<z.ZodObject<{
257
+ profile: z.ZodDefault<z.ZodString>;
258
+ allow: z.ZodDefault<z.ZodArray<z.ZodString>>;
259
+ deny: z.ZodDefault<z.ZodArray<z.ZodString>>;
260
+ }, z.core.$strip>>;
253
261
  }, z.core.$strict>>;
254
262
  }, z.core.$strict>>;
255
263
  session: z.ZodOptional<z.ZodObject<{
@@ -1105,11 +1113,11 @@ export declare const AppConfigSchema: z.ZodObject<{
1105
1113
  waitTimeoutMs: z.ZodDefault<z.ZodNumber>;
1106
1114
  subAgentMaxSteps: z.ZodDefault<z.ZodNumber>;
1107
1115
  subAgentToolGroups: z.ZodDefault<z.ZodArray<z.ZodEnum<{
1116
+ full: "full";
1108
1117
  minimal: "minimal";
1109
1118
  coding: "coding";
1110
1119
  messaging: "messaging";
1111
1120
  supervisor: "supervisor";
1112
- full: "full";
1113
1121
  }>>>;
1114
1122
  subAgentMcpTools: z.ZodDefault<z.ZodEnum<{
1115
1123
  none: "none";
@@ -1572,10 +1580,10 @@ export declare const AppConfigSchema: z.ZodObject<{
1572
1580
  streaming: z.ZodDefault<z.ZodObject<{
1573
1581
  enabled: z.ZodDefault<z.ZodBoolean>;
1574
1582
  defaultChunkMode: z.ZodDefault<z.ZodEnum<{
1575
- length: "length";
1576
1583
  paragraph: "paragraph";
1577
1584
  newline: "newline";
1578
1585
  sentence: "sentence";
1586
+ length: "length";
1579
1587
  }>>;
1580
1588
  defaultDeliveryTiming: z.ZodDefault<z.ZodObject<{
1581
1589
  mode: z.ZodDefault<z.ZodEnum<{
@@ -1620,10 +1628,10 @@ export declare const AppConfigSchema: z.ZodObject<{
1620
1628
  perChannel: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
1621
1629
  enabled: z.ZodDefault<z.ZodBoolean>;
1622
1630
  chunkMode: z.ZodDefault<z.ZodEnum<{
1623
- length: "length";
1624
1631
  paragraph: "paragraph";
1625
1632
  newline: "newline";
1626
1633
  sentence: "sentence";
1634
+ length: "length";
1627
1635
  }>>;
1628
1636
  chunkMaxChars: z.ZodOptional<z.ZodNumber>;
1629
1637
  chunkMinChars: z.ZodDefault<z.ZodNumber>;
@@ -1670,10 +1678,10 @@ export declare const AppConfigSchema: z.ZodObject<{
1670
1678
  all: "all";
1671
1679
  }>>;
1672
1680
  replyModeByChatType: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
1673
- channel: "channel";
1674
1681
  dm: "dm";
1675
1682
  group: "group";
1676
1683
  thread: "thread";
1684
+ channel: "channel";
1677
1685
  forum: "forum";
1678
1686
  }>, z.ZodEnum<{
1679
1687
  off: "off";
@@ -1718,8 +1726,8 @@ export declare const AppConfigSchema: z.ZodObject<{
1718
1726
  enabled: z.ZodDefault<z.ZodBoolean>;
1719
1727
  provider: z.ZodDefault<z.ZodEnum<{
1720
1728
  auto: "auto";
1721
- local: "local";
1722
1729
  openai: "openai";
1730
+ local: "local";
1723
1731
  }>>;
1724
1732
  local: z.ZodDefault<z.ZodObject<{
1725
1733
  modelUri: z.ZodDefault<z.ZodString>;
@@ -1867,15 +1875,15 @@ export declare const AppConfigSchema: z.ZodObject<{
1867
1875
  approvals: z.ZodDefault<z.ZodObject<{
1868
1876
  enabled: z.ZodDefault<z.ZodBoolean>;
1869
1877
  defaultMode: z.ZodDefault<z.ZodEnum<{
1870
- deny: "deny";
1871
1878
  auto: "auto";
1879
+ deny: "deny";
1872
1880
  require: "require";
1873
1881
  }>>;
1874
1882
  rules: z.ZodDefault<z.ZodArray<z.ZodObject<{
1875
1883
  actionPattern: z.ZodString;
1876
1884
  mode: z.ZodDefault<z.ZodEnum<{
1877
- deny: "deny";
1878
1885
  auto: "auto";
1886
+ deny: "deny";
1879
1887
  require: "require";
1880
1888
  }>>;
1881
1889
  timeoutMs: z.ZodDefault<z.ZodNumber>;
@@ -2009,8 +2017,8 @@ export declare const AppConfigSchema: z.ZodObject<{
2009
2017
  senderTrustDisplay: z.ZodDefault<z.ZodObject<{
2010
2018
  enabled: z.ZodDefault<z.ZodBoolean>;
2011
2019
  displayMode: z.ZodDefault<z.ZodEnum<{
2012
- raw: "raw";
2013
2020
  alias: "alias";
2021
+ raw: "raw";
2014
2022
  hash: "hash";
2015
2023
  }>>;
2016
2024
  hashPrefix: z.ZodDefault<z.ZodNumber>;
@@ -189,6 +189,14 @@ export interface InfraEvents {
189
189
  cronJobModel?: string;
190
190
  /** Per-cron-job cache retention override from CronJob config. */
191
191
  cacheRetention?: "none" | "short" | "long";
192
+ /** Per-cron-job tool policy override (opt-in). Resolution in the handler:
193
+ * job.toolPolicy > agentConfig.toolPolicy > passthrough `{ profile: "full" }`.
194
+ * Opt-in by design; omitting preserves pre-existing tool set. */
195
+ toolPolicy?: {
196
+ profile: string;
197
+ allow: string[];
198
+ deny: string[];
199
+ };
192
200
  /** Callback for agent_turn jobs to report execution result back to the scheduler.
193
201
  * Called by the event handler after agent execution completes. */
194
202
  onComplete?: (result: {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@comis/core",
3
3
  "private": true,
4
- "version": "1.0.14",
4
+ "version": "1.0.16",
5
5
  "author": "Moshe Anconina",
6
6
  "license": "Apache-2.0",
7
7
  "description": "Core domain types, ports, event bus, security, and config for Comis",
@@ -295,7 +295,7 @@ export async function main(overrides = {}) {
295
295
  container, logger, daemonLogger, _createProcessMonitor, _startWatchdog,
296
296
  });
297
297
  // 6.5. Memory + embedding
298
- const { disposeEmbedding, cachedPort, memoryAdapter, db, sessionStore, memoryApi, embeddingQueue, backgroundIndexingPromise, embeddingCacheStats, embeddingCircuitBreakerState, } = await setupMemory({ container, memoryLogger });
298
+ const { disposeEmbedding, cachedPort, memoryAdapter, db, sessionStore, memoryApi, embeddingQueue, backgroundIndexingPromise, embeddingCacheStats, embeddingCircuitBreakerState, maintenanceTick, } = await setupMemory({ container, memoryLogger });
299
299
  // Observability persistence (dual-write to SQLite)
300
300
  const obsConfig = container.config.observability;
301
301
  let obsStore;
@@ -1179,6 +1179,7 @@ export async function main(overrides = {}) {
1179
1179
  }
1180
1180
  }
1181
1181
  catch { /* stat failure must not crash health check */ }
1182
+ maintenanceTick();
1182
1183
  // Compute sub-agent health metrics (threshold-aware split)
1183
1184
  const stuckKillThresholdMs = container.config.security.agentToAgent.subagentContext?.stuckKillThresholdMs ?? 180_000;
1184
1185
  const graphStuckKillThresholdMs = container.config.security.agentToAgent.subagentContext?.graphStuckKillThresholdMs ?? 600_000;
@@ -16,7 +16,7 @@ import { formatSessionKey, runWithContext, createDeliveryOrigin, safePath } from
16
16
  import { createMessageRouter, createCommandQueue, createCommandHandler, parseSlashCommand, sanitizeAssistantResponse, resolveOperationModel, resolveProviderFamily, runMemoryReview } from "@comis/agent";
17
17
  import { createChannelManager, createRetryEngine, createApprovalNotifier, deliverToChannel, filterResponse, } from "@comis/channels";
18
18
  import { RetryConfigSchema } from "@comis/core";
19
- import { shouldAutoTts, resolveOutputFormat, parseOutboundMedia, } from "@comis/skills";
19
+ import { shouldAutoTts, resolveOutputFormat, parseOutboundMedia, applyToolPolicy, } from "@comis/skills";
20
20
  import { bootstrapAdapters } from "./setup-channels-adapters.js";
21
21
  import { buildMediaPipeline } from "./setup-channels-media.js";
22
22
  import { createLifecycleReactor, reactWithFallback, initTelegramFileGuardConfig } from "@comis/channels";
@@ -193,9 +193,26 @@ export async function setupChannels(deps) {
193
193
  };
194
194
  const execStartTs = Date.now();
195
195
  try {
196
- const tools = deps.assembleToolsForAgent
196
+ const allTools = deps.assembleToolsForAgent
197
197
  ? await deps.assembleToolsForAgent(payload.agentId)
198
198
  : [];
199
+ // Resolve effective tool policy: job > agent > passthrough `{ profile: "full" }`.
200
+ // Opt-in per job — omitting payload.toolPolicy preserves the agent's interactive
201
+ // tool set. The explicit "full" fallback makes the no-silent-default contract
202
+ // readable in the call site (see design-doc §"no silent default").
203
+ const effectivePolicy = payload.toolPolicy ??
204
+ agentConfig?.skills?.toolPolicy ??
205
+ { profile: "full", allow: [], deny: [] };
206
+ const { tools, filtered: policyFiltered } = applyToolPolicy(allTools, effectivePolicy);
207
+ if (policyFiltered.length > 0) {
208
+ logger.debug({
209
+ jobName,
210
+ agentId: payload.agentId,
211
+ profile: effectivePolicy.profile,
212
+ filteredCount: policyFiltered.length,
213
+ filtered: policyFiltered.map((f) => ({ tool: f.toolName, reason: f.reason.kind })),
214
+ }, "Cron tool policy applied");
215
+ }
199
216
  logger.info({ jobName, agentId: payload.agentId, channelType: deliveryTarget.channelType, toolCount: Array.isArray(tools) ? tools.length : 0 }, "Executing cron agentTurn");
200
217
  const execResult = await runWithContext({
201
218
  traceId: randomUUID(),
@@ -11,6 +11,7 @@ import { readFile } from "node:fs/promises";
11
11
  import { safePath } from "@comis/core";
12
12
  import { isHeartbeatContentEffectivelyEmpty } from "@comis/agent";
13
13
  import { resolveEffectiveHeartbeatConfig, createAgentHeartbeatSource, createPerAgentHeartbeatRunner, createDuplicateDetector, } from "@comis/scheduler";
14
+ import { applyToolPolicy } from "@comis/skills";
14
15
  // ---------------------------------------------------------------------------
15
16
  // Setup function
16
17
  // ---------------------------------------------------------------------------
@@ -85,6 +86,7 @@ export function setupHeartbeat(deps) {
85
86
  getAgentConfig: (agentId) => ({
86
87
  model: agents[agentId].model,
87
88
  tenantId: container.config.tenantId,
89
+ toolPolicy: agents[agentId].skills?.toolPolicy,
88
90
  }),
89
91
  checkFileGate,
90
92
  systemEventQueue,
@@ -96,6 +98,14 @@ export function setupHeartbeat(deps) {
96
98
  },
97
99
  activeRunRegistry,
98
100
  getMemoryStats,
101
+ // Inject applyToolPolicy so scheduler can filter without taking a direct
102
+ // dependency on @comis/skills. Type-erased `unknown[]` at the boundary is
103
+ // safe: the filter reads only the `.name` field from each tool, and
104
+ // scheduler never inspects tool contents beyond passing them through.
105
+ applyToolPolicyFilter: (allTools, policy) => {
106
+ const { tools, filtered } = applyToolPolicy(allTools, policy);
107
+ return { tools, filtered };
108
+ },
99
109
  logger: schedulerLogger,
100
110
  });
101
111
  // 5. Create and start the per-agent runner
@@ -32,6 +32,8 @@ export interface MemoryResult {
32
32
  embeddingCacheStats?: () => import("@comis/memory").EmbeddingCacheStats;
33
33
  /** Embedding circuit breaker state accessor for memory persistence operations. */
34
34
  embeddingCircuitBreakerState?: () => import("@comis/agent").CircuitState;
35
+ /** Throttled WAL checkpoint — call every health tick, runs checkpoint every 10th call. */
36
+ maintenanceTick: () => void;
35
37
  }
36
38
  /**
37
39
  * Wraps an EmbeddingPort with a circuit breaker that blocks calls when the
@@ -199,6 +199,20 @@ export async function setupMemory(deps) {
199
199
  const disposeEmbedding = cachedPort?.dispose
200
200
  ? async () => { await cachedPort.dispose(); }
201
201
  : undefined;
202
+ // Throttled WAL checkpoint: runs PASSIVE checkpoint every 10th call (~5 min at 30s health interval)
203
+ let maintenanceTickCount = 0;
204
+ const maintenanceTick = () => {
205
+ maintenanceTickCount++;
206
+ if (maintenanceTickCount % 10 !== 0)
207
+ return;
208
+ try {
209
+ const pages = memoryAdapter.checkpoint();
210
+ if (pages > 0) {
211
+ memoryLogger.debug({ pages }, "WAL checkpoint moved pages");
212
+ }
213
+ }
214
+ catch { /* checkpoint failure must not crash health tick */ }
215
+ };
202
216
  return {
203
217
  disposeEmbedding,
204
218
  cachedPort,
@@ -210,5 +224,6 @@ export async function setupMemory(deps) {
210
224
  backgroundIndexingPromise,
211
225
  embeddingCacheStats,
212
226
  embeddingCircuitBreakerState: embeddingCbRef ? () => embeddingCbRef.getState() : undefined,
227
+ maintenanceTick,
213
228
  };
214
229
  }
@@ -110,6 +110,7 @@ export async function setupSchedulers(deps) {
110
110
  maxHistoryTurns: job.maxHistoryTurns,
111
111
  cronJobModel: job.payload.kind === "agent_turn" ? job.payload.model : undefined,
112
112
  cacheRetention: job.cacheRetention,
113
+ toolPolicy: job.toolPolicy,
113
114
  onComplete: deferredResolve,
114
115
  });
115
116
  // Forward isolated results to main session if requested
@@ -79,6 +79,7 @@ export interface ShutdownDeps {
79
79
  /** Raw database handle for close. */
80
80
  db: {
81
81
  close: () => void;
82
+ pragma: (source: string) => unknown;
82
83
  };
83
84
  /** Coordinated embedding dispose callback: L1 -> L2 flush -> provider dispose */
84
85
  disposeEmbedding?: () => Promise<void>;
@@ -284,6 +284,10 @@ export function setupShutdown(deps) {
284
284
  // DB close is ALWAYS last -- no withStepTimeout (must complete or the outer 30s hard timeout handles it)
285
285
  {
286
286
  const stopMs = Date.now();
287
+ try {
288
+ db.pragma("wal_checkpoint(TRUNCATE)");
289
+ }
290
+ catch { /* best-effort flush before close */ }
287
291
  db.close();
288
292
  daemonLogger.info({ component: "memory-database", durationMs: Date.now() - stopMs, shutdownOrder: shutdownOrder + 1 }, "Component stopped");
289
293
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@comis/daemon",
3
3
  "private": true,
4
- "version": "1.0.14",
4
+ "version": "1.0.16",
5
5
  "author": "Moshe Anconina",
6
6
  "license": "Apache-2.0",
7
7
  "description": "Background daemon and orchestrator for the Comis platform",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@comis/gateway",
3
3
  "private": true,
4
- "version": "1.0.14",
4
+ "version": "1.0.16",
5
5
  "author": "Moshe Anconina",
6
6
  "license": "Apache-2.0",
7
7
  "description": "HTTP, JSON-RPC, and WebSocket gateway for Comis",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@comis/infra",
3
3
  "private": true,
4
- "version": "1.0.14",
4
+ "version": "1.0.16",
5
5
  "author": "Moshe Anconina",
6
6
  "license": "Apache-2.0",
7
7
  "description": "Structured logging infrastructure for Comis",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@comis/memory",
3
3
  "private": true,
4
- "version": "1.0.14",
4
+ "version": "1.0.16",
5
5
  "author": "Moshe Anconina",
6
6
  "license": "Apache-2.0",
7
7
  "description": "SQLite memory, embeddings, and RAG storage for Comis agents",
@@ -144,6 +144,17 @@ export declare const CronJobSchema: z.ZodObject<{
144
144
  short: "short";
145
145
  long: "long";
146
146
  }>>;
147
+ /** Per-job tool policy override -- matches AgentConfig.toolPolicy shape.
148
+ *
149
+ * Resolution order: job.toolPolicy > agentConfig.toolPolicy > passthrough
150
+ * (no filtering). Opt-in: omitting this field preserves existing tool set
151
+ * for the job. No silent defaults -- operators explicitly request
152
+ * conservative presets like `{ profile: "cron-minimal" }`. */
153
+ toolPolicy: z.ZodOptional<z.ZodObject<{
154
+ profile: z.ZodDefault<z.ZodString>;
155
+ allow: z.ZodDefault<z.ZodArray<z.ZodString>>;
156
+ deny: z.ZodDefault<z.ZodArray<z.ZodString>>;
157
+ }, z.core.$strip>>;
147
158
  /** Delivery target for routing results to originating channel */
148
159
  deliveryTarget: z.ZodOptional<z.ZodObject<{
149
160
  channelId: z.ZodString;
@@ -98,6 +98,17 @@ export const CronJobSchema = z.strictObject({
98
98
  maxHistoryTurns: z.number().int().positive().default(3).optional(),
99
99
  /** Per-job cache retention override. Default inherits OPERATION_CACHE_DEFAULTS["cron"] = "short". */
100
100
  cacheRetention: z.enum(["none", "short", "long"]).optional(),
101
+ /** Per-job tool policy override -- matches AgentConfig.toolPolicy shape.
102
+ *
103
+ * Resolution order: job.toolPolicy > agentConfig.toolPolicy > passthrough
104
+ * (no filtering). Opt-in: omitting this field preserves existing tool set
105
+ * for the job. No silent defaults -- operators explicitly request
106
+ * conservative presets like `{ profile: "cron-minimal" }`. */
107
+ toolPolicy: z.object({
108
+ profile: z.string().default("full"),
109
+ allow: z.array(z.string()).default([]),
110
+ deny: z.array(z.string()).default([]),
111
+ }).optional(),
101
112
  /** Delivery target for routing results to originating channel */
102
113
  deliveryTarget: CronDeliveryTargetSchema.optional(),
103
114
  /** Whether this job is currently enabled */
@@ -41,6 +41,23 @@ interface HeartbeatExecutor {
41
41
  interface HeartbeatActiveRunRegistry {
42
42
  has(sessionKey: string): boolean;
43
43
  }
44
+ /** Tool policy filter function signature -- matches applyToolPolicy from @comis/skills.
45
+ * Injected as a dep so scheduler doesn't take a hard dependency on skills. */
46
+ export interface HeartbeatToolPolicyFilter {
47
+ (tools: unknown[], policy: {
48
+ profile: string;
49
+ allow: string[];
50
+ deny: string[];
51
+ }): {
52
+ tools: unknown[];
53
+ filtered: Array<{
54
+ toolName: string;
55
+ reason: {
56
+ kind: string;
57
+ };
58
+ }>;
59
+ };
60
+ }
44
61
  /** Injected session side-effects for heartbeat response processing. */
45
62
  export interface HeartbeatSessionOps {
46
63
  /** Remove the last user+assistant turn from session transcript. */
@@ -63,10 +80,15 @@ export interface AgentHeartbeatSourceDeps {
63
80
  assembleToolsForAgent: (agentId: string) => Promise<unknown[]>;
64
81
  /** Get the resolved heartbeat config for an agent. */
65
82
  getEffectiveConfig: (agentId: string) => EffectiveHeartbeatConfig;
66
- /** Get agent-level config (model, tenantId). */
83
+ /** Get agent-level config (model, tenantId, optional toolPolicy). */
67
84
  getAgentConfig: (agentId: string) => {
68
85
  model: string;
69
86
  tenantId: string;
87
+ toolPolicy?: {
88
+ profile: string;
89
+ allow: string[];
90
+ deny: string[];
91
+ };
70
92
  };
71
93
  /** Returns true if HEARTBEAT.md is effectively empty (should skip LLM). */
72
94
  checkFileGate: (agentId: string) => Promise<boolean>;
@@ -80,6 +102,9 @@ export interface AgentHeartbeatSourceDeps {
80
102
  sessionOps?: HeartbeatSessionOps;
81
103
  /** Optional: fetch memory stats for an agent (for heartbeat prompt injection). */
82
104
  getMemoryStats?: (agentId: string, tenantId: string) => HeartbeatMemoryStats | undefined;
105
+ /** Optional: apply a tool policy filter (wired to `applyToolPolicy` from @comis/skills).
106
+ * When absent, no policy is applied and tools pass through unchanged. */
107
+ applyToolPolicyFilter?: HeartbeatToolPolicyFilter;
83
108
  /** Logger instance. */
84
109
  logger: SchedulerLogger;
85
110
  }
@@ -104,8 +104,29 @@ export function createAgentHeartbeatSource(deps) {
104
104
  logger.debug({ agentId, formattedKey }, "Heartbeat skipped: session queue busy");
105
105
  return;
106
106
  }
107
- // 6. Assemble tools
108
- const tools = await deps.assembleToolsForAgent(agentId);
107
+ // 6. Assemble tools, then apply opt-in tool policy.
108
+ // Resolution order: config.toolPolicy (heartbeat) > agentConfig.toolPolicy > passthrough.
109
+ // Policy is opt-in per agent -- a missing toolPolicy preserves the agent's
110
+ // interactive tool set for heartbeat ticks. No silent defaults: the explicit
111
+ // "full" fallback documents the contract at the call site.
112
+ const allTools = await deps.assembleToolsForAgent(agentId);
113
+ const effectivePolicy = config.toolPolicy ??
114
+ agentConfig.toolPolicy ??
115
+ { profile: "full", allow: [], deny: [] };
116
+ let tools = allTools;
117
+ if (deps.applyToolPolicyFilter) {
118
+ const { tools: filteredTools, filtered } = deps.applyToolPolicyFilter(allTools, effectivePolicy);
119
+ tools = filteredTools;
120
+ if (filtered.length > 0) {
121
+ logger.debug({
122
+ agentId,
123
+ trigger,
124
+ profile: effectivePolicy.profile,
125
+ filteredCount: filtered.length,
126
+ filtered: filtered.map((f) => ({ tool: f.toolName, reason: f.reason.kind })),
127
+ }, "Heartbeat tool policy applied");
128
+ }
129
+ }
109
130
  // 6.5 Fetch memory stats for conditional prompt injection
110
131
  let memoryStats;
111
132
  if (deps.getMemoryStats) {
@@ -23,5 +23,12 @@ export interface EffectiveHeartbeatConfig {
23
23
  alertCooldownMs?: number;
24
24
  /** Max ms a heartbeat tick can run before stuck detection */
25
25
  staleMs?: number;
26
+ /** Per-agent heartbeat tool policy override. Opt-in.
27
+ * Resolution order: config.toolPolicy > agentConfig.toolPolicy > passthrough. */
28
+ toolPolicy?: {
29
+ profile: string;
30
+ allow: string[];
31
+ deny: string[];
32
+ };
26
33
  }
27
34
  export declare function resolveEffectiveHeartbeatConfig(global: HeartbeatConfig, perAgent?: PerAgentHeartbeatConfig): EffectiveHeartbeatConfig;
@@ -15,5 +15,7 @@ export function resolveEffectiveHeartbeatConfig(global, perAgent) {
15
15
  alertThreshold: perAgent?.alertThreshold ?? global.alertThreshold,
16
16
  alertCooldownMs: perAgent?.alertCooldownMs ?? global.alertCooldownMs,
17
17
  staleMs: perAgent?.staleMs ?? global.staleMs,
18
+ // Heartbeat tool policy is per-agent only (no global counterpart). Opt-in.
19
+ toolPolicy: perAgent?.toolPolicy,
18
20
  };
19
21
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@comis/scheduler",
3
3
  "private": true,
4
- "version": "1.0.14",
4
+ "version": "1.0.16",
5
5
  "author": "Moshe Anconina",
6
6
  "license": "Apache-2.0",
7
7
  "description": "Task scheduling and cron management for Comis",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@comis/shared",
3
3
  "private": true,
4
- "version": "1.0.14",
4
+ "version": "1.0.16",
5
5
  "author": "Moshe Anconina",
6
6
  "license": "Apache-2.0",
7
7
  "description": "Shared types and utilities for the Comis platform",
@@ -58,5 +58,5 @@ export type { PdfPageRenderer } from "./integrations/document/pdf-page-renderer.
58
58
  export { createCompositeFileExtractor } from "./integrations/document/composite-extractor.js";
59
59
  export { createImageGenProvider, createImageGenRateLimiter } from "./integrations/image-gen/index.js";
60
60
  export type { ImageGenRateLimiter } from "./integrations/image-gen/index.js";
61
- export { TOOL_PROFILES, TOOL_GROUPS } from "./policy/index.js";
61
+ export { applyToolPolicy, expandGroups, TOOL_PROFILES, TOOL_GROUPS } from "./policy/index.js";
62
62
  export type { ToolFilterReason, ToolPolicyResult } from "./policy/index.js";
@@ -110,4 +110,4 @@ export { createCompositeFileExtractor } from "./integrations/document/composite-
110
110
  // Image generation (provider adapters, factory, rate limiter)
111
111
  export { createImageGenProvider, createImageGenRateLimiter } from "./integrations/image-gen/index.js";
112
112
  // Tool policy (profiles and groups for tool filtering)
113
- export { TOOL_PROFILES, TOOL_GROUPS } from "./policy/index.js";
113
+ export { applyToolPolicy, expandGroups, TOOL_PROFILES, TOOL_GROUPS } from "./policy/index.js";
@@ -40,6 +40,31 @@ export const TOOL_PROFILES = {
40
40
  "mcp_manage",
41
41
  "heartbeat_manage",
42
42
  ],
43
+ /**
44
+ * Conservative presets for non-interactive operations.
45
+ *
46
+ * Opt-in via `toolPolicy: { profile: "cron-minimal" }` on a `CronJob` or
47
+ * `toolPolicy: { profile: "heartbeat-minimal" }` on heartbeat config. The
48
+ * `*-minimal` suffix signals "opinionated narrow default, expect to `allow`
49
+ * extras per job" -- never applied as a silent default at the call site.
50
+ */
51
+ "cron-minimal": [
52
+ "web_search",
53
+ "message",
54
+ "read_file",
55
+ "write_file",
56
+ "list_dir",
57
+ "memory_store",
58
+ "memory_search",
59
+ "cron",
60
+ "discover",
61
+ ],
62
+ "heartbeat-minimal": [
63
+ "message",
64
+ "memory_store",
65
+ "memory_search",
66
+ "discover",
67
+ ],
43
68
  full: [], // empty = all tools allowed
44
69
  };
45
70
  /**
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@comis/skills",
3
3
  "private": true,
4
- "version": "1.0.14",
4
+ "version": "1.0.16",
5
5
  "author": "Moshe Anconina",
6
6
  "license": "Apache-2.0",
7
7
  "description": "Skill system, MCP integration, and tool sandbox for Comis agents",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "comisai",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "author": "Moshe Anconina",
5
5
  "license": "Apache-2.0",
6
6
  "description": "Security-first AI agent platform — connects AI agents to Discord, Telegram, Slack, WhatsApp, and more",
@@ -115,17 +115,17 @@
115
115
  "@comis/daemon"
116
116
  ],
117
117
  "dependencies": {
118
- "@comis/shared": "1.0.14",
119
- "@comis/core": "1.0.14",
120
- "@comis/infra": "1.0.14",
121
- "@comis/memory": "1.0.14",
122
- "@comis/gateway": "1.0.14",
123
- "@comis/skills": "1.0.14",
124
- "@comis/scheduler": "1.0.14",
125
- "@comis/agent": "1.0.14",
126
- "@comis/channels": "1.0.14",
127
- "@comis/cli": "1.0.14",
128
- "@comis/daemon": "1.0.14",
118
+ "@comis/shared": "1.0.16",
119
+ "@comis/core": "1.0.16",
120
+ "@comis/infra": "1.0.16",
121
+ "@comis/memory": "1.0.16",
122
+ "@comis/gateway": "1.0.16",
123
+ "@comis/skills": "1.0.16",
124
+ "@comis/scheduler": "1.0.16",
125
+ "@comis/agent": "1.0.16",
126
+ "@comis/channels": "1.0.16",
127
+ "@comis/cli": "1.0.16",
128
+ "@comis/daemon": "1.0.16",
129
129
  "@agentclientprotocol/sdk": "^0.15.0",
130
130
  "@clack/core": "^1.1.0",
131
131
  "@clack/prompts": "^1.1.0",