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.
- package/node_modules/@comis/agent/dist/bootstrap/index.d.ts +1 -1
- package/node_modules/@comis/agent/dist/bootstrap/index.js +1 -1
- package/node_modules/@comis/agent/dist/bootstrap/system-prompt-assembler.d.ts +26 -0
- package/node_modules/@comis/agent/dist/bootstrap/system-prompt-assembler.js +86 -54
- package/node_modules/@comis/agent/dist/bootstrap/types.d.ts +7 -1
- package/node_modules/@comis/agent/dist/bootstrap/workspace-loader.d.ts +12 -0
- package/node_modules/@comis/agent/dist/bootstrap/workspace-loader.js +21 -0
- package/node_modules/@comis/agent/dist/bridge/pi-event-bridge.js +12 -11
- package/node_modules/@comis/agent/dist/context-engine/constants.d.ts +11 -0
- package/node_modules/@comis/agent/dist/context-engine/constants.js +11 -0
- package/node_modules/@comis/agent/dist/context-engine/index.d.ts +1 -1
- package/node_modules/@comis/agent/dist/context-engine/index.js +1 -1
- package/node_modules/@comis/agent/dist/context-engine/llm-compaction.js +32 -18
- package/node_modules/@comis/agent/dist/executor/cache-break-diff-writer.d.ts +2 -0
- package/node_modules/@comis/agent/dist/executor/cache-break-diff-writer.js +36 -0
- package/node_modules/@comis/agent/dist/executor/executor-post-execution.d.ts +12 -0
- package/node_modules/@comis/agent/dist/executor/executor-post-execution.js +117 -27
- package/node_modules/@comis/agent/dist/executor/executor-prompt-runner.d.ts +1 -0
- package/node_modules/@comis/agent/dist/executor/executor-prompt-runner.js +16 -4
- package/node_modules/@comis/agent/dist/executor/executor-stream-setup.js +17 -1
- package/node_modules/@comis/agent/dist/executor/executor-tool-assembly.js +29 -1
- package/node_modules/@comis/agent/dist/executor/prompt-assembly.d.ts +8 -1
- package/node_modules/@comis/agent/dist/executor/prompt-assembly.js +22 -5
- package/node_modules/@comis/agent/dist/executor/session-snapshot-cleanup.js +3 -1
- package/node_modules/@comis/agent/dist/executor/stream-wrappers/index.d.ts +2 -0
- package/node_modules/@comis/agent/dist/executor/stream-wrappers/index.js +2 -0
- package/node_modules/@comis/agent/dist/executor/stream-wrappers/request-body-injector.d.ts +9 -0
- package/node_modules/@comis/agent/dist/executor/stream-wrappers/request-body-injector.js +65 -3
- package/node_modules/@comis/agent/dist/executor/stream-wrappers/stub-filter-injector.d.ts +28 -0
- package/node_modules/@comis/agent/dist/executor/stream-wrappers/stub-filter-injector.js +63 -0
- package/node_modules/@comis/agent/dist/executor/tool-deferral.d.ts +18 -1
- package/node_modules/@comis/agent/dist/executor/tool-deferral.js +275 -133
- package/node_modules/@comis/agent/dist/executor/ttl-guard.d.ts +6 -0
- package/node_modules/@comis/agent/dist/executor/ttl-guard.js +8 -0
- package/node_modules/@comis/agent/dist/session/comis-session-manager.js +7 -2
- package/node_modules/@comis/agent/package.json +1 -1
- package/node_modules/@comis/channels/dist/shared/typing-controller.d.ts +7 -0
- package/node_modules/@comis/channels/dist/shared/typing-controller.js +33 -0
- package/node_modules/@comis/channels/package.json +1 -1
- package/node_modules/@comis/cli/dist/commands/daemon.js +116 -28
- package/node_modules/@comis/cli/dist/doctor/checks/channel-health.js +2 -2
- package/node_modules/@comis/cli/dist/wizard/index.d.ts +2 -3
- package/node_modules/@comis/cli/dist/wizard/index.js +2 -3
- package/node_modules/@comis/cli/dist/wizard/steps/10-write-config.js +1 -1
- package/node_modules/@comis/cli/dist/wizard/types.d.ts +1 -2
- package/node_modules/@comis/cli/dist/wizard/types.js +1 -2
- package/node_modules/@comis/cli/package.json +1 -1
- package/node_modules/@comis/core/dist/config/schema-agent.d.ts +64 -10
- package/node_modules/@comis/core/dist/config/schema-agent.js +19 -0
- package/node_modules/@comis/core/dist/config/schema-skills.d.ts +16 -2
- package/node_modules/@comis/core/dist/config/schema-skills.js +9 -2
- package/node_modules/@comis/core/dist/config/schema.d.ts +19 -11
- package/node_modules/@comis/core/dist/event-bus/events-infra.d.ts +8 -0
- package/node_modules/@comis/core/package.json +1 -1
- package/node_modules/@comis/daemon/dist/daemon.js +2 -1
- package/node_modules/@comis/daemon/dist/wiring/setup-channels.js +19 -2
- package/node_modules/@comis/daemon/dist/wiring/setup-heartbeat.js +10 -0
- package/node_modules/@comis/daemon/dist/wiring/setup-memory.d.ts +2 -0
- package/node_modules/@comis/daemon/dist/wiring/setup-memory.js +15 -0
- package/node_modules/@comis/daemon/dist/wiring/setup-schedulers.js +1 -0
- package/node_modules/@comis/daemon/dist/wiring/setup-shutdown.d.ts +1 -0
- package/node_modules/@comis/daemon/dist/wiring/setup-shutdown.js +4 -0
- package/node_modules/@comis/daemon/package.json +1 -1
- package/node_modules/@comis/gateway/package.json +1 -1
- package/node_modules/@comis/infra/package.json +1 -1
- package/node_modules/@comis/memory/package.json +1 -1
- package/node_modules/@comis/scheduler/dist/cron/cron-types.d.ts +11 -0
- package/node_modules/@comis/scheduler/dist/cron/cron-types.js +11 -0
- package/node_modules/@comis/scheduler/dist/heartbeat/agent-heartbeat-source.d.ts +26 -1
- package/node_modules/@comis/scheduler/dist/heartbeat/agent-heartbeat-source.js +23 -2
- package/node_modules/@comis/scheduler/dist/heartbeat/heartbeat-config.d.ts +7 -0
- package/node_modules/@comis/scheduler/dist/heartbeat/heartbeat-config.js +2 -0
- package/node_modules/@comis/scheduler/package.json +1 -1
- package/node_modules/@comis/shared/package.json +1 -1
- package/node_modules/@comis/skills/dist/index.d.ts +1 -1
- package/node_modules/@comis/skills/dist/index.js +1 -1
- package/node_modules/@comis/skills/dist/policy/tool-policy.js +25 -0
- package/node_modules/@comis/skills/package.json +1 -1
- 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: {
|
|
@@ -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
|
|
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
|
}
|
|
@@ -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
|
-
|
|
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
|
}
|
|
@@ -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
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "comisai",
|
|
3
|
-
"version": "1.0.
|
|
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.
|
|
119
|
-
"@comis/core": "1.0.
|
|
120
|
-
"@comis/infra": "1.0.
|
|
121
|
-
"@comis/memory": "1.0.
|
|
122
|
-
"@comis/gateway": "1.0.
|
|
123
|
-
"@comis/skills": "1.0.
|
|
124
|
-
"@comis/scheduler": "1.0.
|
|
125
|
-
"@comis/agent": "1.0.
|
|
126
|
-
"@comis/channels": "1.0.
|
|
127
|
-
"@comis/cli": "1.0.
|
|
128
|
-
"@comis/daemon": "1.0.
|
|
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",
|