comisai 1.0.19 → 1.0.23

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 (154) hide show
  1. package/dist/cli-entry.js +0 -0
  2. package/node_modules/@comis/agent/dist/context-engine/context-engine.js +43 -2
  3. package/node_modules/@comis/agent/dist/context-engine/signature-replay-scrubber.d.ts +51 -0
  4. package/node_modules/@comis/agent/dist/context-engine/signature-replay-scrubber.js +110 -0
  5. package/node_modules/@comis/agent/dist/context-engine/signature-surrogate-guard.d.ts +54 -0
  6. package/node_modules/@comis/agent/dist/context-engine/signature-surrogate-guard.js +145 -0
  7. package/node_modules/@comis/agent/dist/context-engine/types-core.d.ts +17 -0
  8. package/node_modules/@comis/agent/dist/executor/error-classifier.d.ts +11 -1
  9. package/node_modules/@comis/agent/dist/executor/error-classifier.js +13 -0
  10. package/node_modules/@comis/agent/dist/executor/executor-context-engine-setup.d.ts +1 -0
  11. package/node_modules/@comis/agent/dist/executor/executor-context-engine-setup.js +55 -0
  12. package/node_modules/@comis/agent/dist/executor/executor-prompt-runner.js +106 -5
  13. package/node_modules/@comis/agent/dist/executor/executor-tool-assembly.js +1 -0
  14. package/node_modules/@comis/agent/dist/executor/pi-executor.d.ts +1 -4
  15. package/node_modules/@comis/agent/dist/executor/pi-executor.js +30 -3
  16. package/node_modules/@comis/agent/dist/executor/replay-drift-detector.d.ts +85 -0
  17. package/node_modules/@comis/agent/dist/executor/replay-drift-detector.js +92 -0
  18. package/node_modules/@comis/agent/dist/executor/signature-block-scrubber.d.ts +34 -0
  19. package/node_modules/@comis/agent/dist/executor/signature-block-scrubber.js +69 -0
  20. package/node_modules/@comis/agent/dist/executor/signed-replay-detector.d.ts +39 -0
  21. package/node_modules/@comis/agent/dist/executor/signed-replay-detector.js +72 -0
  22. package/node_modules/@comis/agent/package.json +1 -1
  23. package/node_modules/@comis/channels/package.json +1 -1
  24. package/node_modules/@comis/cli/dist/cli.js +0 -0
  25. package/node_modules/@comis/cli/package.json +1 -1
  26. package/node_modules/@comis/core/dist/config/git-manager.js +10 -4
  27. package/node_modules/@comis/core/dist/config/index.d.ts +1 -0
  28. package/node_modules/@comis/core/dist/config/index.js +2 -0
  29. package/node_modules/@comis/core/dist/config/managed-sections.d.ts +67 -0
  30. package/node_modules/@comis/core/dist/config/managed-sections.js +124 -0
  31. package/node_modules/@comis/core/dist/config/schema-agent.d.ts +28 -10
  32. package/node_modules/@comis/core/dist/config/schema-agent.js +6 -0
  33. package/node_modules/@comis/core/dist/config/schema-gateway.d.ts +2 -2
  34. package/node_modules/@comis/core/dist/config/schema.d.ts +65 -64
  35. package/node_modules/@comis/core/dist/event-bus/events-messaging.d.ts +16 -0
  36. package/node_modules/@comis/core/dist/exports/config.d.ts +1 -1
  37. package/node_modules/@comis/core/dist/exports/config.js +1 -1
  38. package/node_modules/@comis/core/package.json +1 -1
  39. package/node_modules/@comis/daemon/bundled-skills/skill-creator/scripts/init-skill.py +0 -0
  40. package/node_modules/@comis/daemon/bundled-skills/skill-creator/scripts/validate-skill.py +0 -0
  41. package/node_modules/@comis/daemon/dist/daemon.js +11 -4
  42. package/node_modules/@comis/daemon/dist/rpc/config-handlers.js +20 -7
  43. package/node_modules/@comis/daemon/dist/rpc/session-handlers.js +27 -1
  44. package/node_modules/@comis/daemon/dist/wiring/setup-gateway.d.ts +22 -0
  45. package/node_modules/@comis/daemon/dist/wiring/setup-gateway.js +34 -8
  46. package/node_modules/@comis/daemon/dist/wiring/setup-tools.js +14 -1
  47. package/node_modules/@comis/daemon/package.json +1 -1
  48. package/node_modules/@comis/gateway/package.json +1 -1
  49. package/node_modules/@comis/infra/dist/logging/log-fields.d.ts +2 -2
  50. package/node_modules/@comis/infra/package.json +1 -1
  51. package/node_modules/@comis/memory/package.json +1 -1
  52. package/node_modules/@comis/scheduler/package.json +1 -1
  53. package/node_modules/@comis/shared/package.json +1 -1
  54. package/node_modules/@comis/skills/dist/bridge/tool-metadata-registry.js +23 -8
  55. package/node_modules/@comis/skills/dist/builtin/platform/gateway-tool.d.ts +1 -1
  56. package/node_modules/@comis/skills/dist/builtin/platform/gateway-tool.js +18 -14
  57. package/node_modules/@comis/skills/dist/builtin/platform/unified-session-tool.js +1 -1
  58. package/node_modules/@comis/skills/dist/builtin/sandbox/detect-provider.d.ts +1 -0
  59. package/node_modules/@comis/skills/dist/builtin/sandbox/detect-provider.js +78 -5
  60. package/node_modules/@comis/skills/package.json +1 -1
  61. package/node_modules/@comis/web/package.json +1 -1
  62. package/package.json +24 -26
  63. package/node_modules/@comis/agent/dist/provider/response/strip-minimax-xml.d.ts +0 -9
  64. package/node_modules/@comis/agent/dist/provider/response/strip-minimax-xml.js +0 -17
  65. package/node_modules/@comis/agent/dist/provider/response/strip-model-tokens.d.ts +0 -13
  66. package/node_modules/@comis/agent/dist/provider/response/strip-model-tokens.js +0 -19
  67. package/node_modules/@comis/agent/dist/provider/response/strip-tool-text.d.ts +0 -11
  68. package/node_modules/@comis/agent/dist/provider/response/strip-tool-text.js +0 -32
  69. package/node_modules/@comis/agent/dist/safety/follow-through-detector.d.ts +0 -46
  70. package/node_modules/@comis/agent/dist/safety/follow-through-detector.js +0 -76
  71. package/node_modules/@comis/agent/dist/safety/post-compaction-safety.d.ts +0 -30
  72. package/node_modules/@comis/agent/dist/safety/post-compaction-safety.js +0 -51
  73. package/node_modules/@comis/agent/dist/safety/schema-normalizer.d.ts +0 -37
  74. package/node_modules/@comis/agent/dist/safety/schema-normalizer.js +0 -137
  75. package/node_modules/@comis/agent/dist/safety/schema-pruning.d.ts +0 -50
  76. package/node_modules/@comis/agent/dist/safety/schema-pruning.js +0 -112
  77. package/node_modules/@comis/agent/dist/safety/tool-image-sanitizer.d.ts +0 -43
  78. package/node_modules/@comis/agent/dist/safety/tool-image-sanitizer.js +0 -96
  79. package/node_modules/@comis/agent/dist/safety/tool-sanitizer.d.ts +0 -44
  80. package/node_modules/@comis/agent/dist/safety/tool-sanitizer.js +0 -94
  81. package/node_modules/@comis/channels/dist/shared/thinking-tag-filter.d.ts +0 -28
  82. package/node_modules/@comis/channels/dist/shared/thinking-tag-filter.js +0 -206
  83. package/node_modules/@comis/cli/dist/wizard/config-writer.d.ts +0 -25
  84. package/node_modules/@comis/cli/dist/wizard/config-writer.js +0 -144
  85. package/node_modules/@comis/cli/dist/wizard/flow-types.d.ts +0 -48
  86. package/node_modules/@comis/cli/dist/wizard/flow-types.js +0 -70
  87. package/node_modules/@comis/cli/dist/wizard/manual-flow.d.ts +0 -21
  88. package/node_modules/@comis/cli/dist/wizard/manual-flow.js +0 -345
  89. package/node_modules/@comis/cli/dist/wizard/quickstart-flow.d.ts +0 -21
  90. package/node_modules/@comis/cli/dist/wizard/quickstart-flow.js +0 -116
  91. package/node_modules/@comis/core/dist/config/schema-agent-model.d.ts +0 -135
  92. package/node_modules/@comis/core/dist/config/schema-agent-model.js +0 -114
  93. package/node_modules/@comis/core/dist/config/schema-agent-session.d.ts +0 -177
  94. package/node_modules/@comis/core/dist/config/schema-agent-session.js +0 -116
  95. package/node_modules/@comis/core/dist/config/schema-context-engine.d.ts +0 -92
  96. package/node_modules/@comis/core/dist/config/schema-context-engine.js +0 -92
  97. package/node_modules/@comis/core/dist/config/schema-context-guard.d.ts +0 -34
  98. package/node_modules/@comis/core/dist/config/schema-context-guard.js +0 -32
  99. package/node_modules/@comis/core/dist/config/schema-delivery-mirror.d.ts +0 -27
  100. package/node_modules/@comis/core/dist/config/schema-delivery-mirror.js +0 -26
  101. package/node_modules/@comis/core/dist/config/schema-delivery-queue.d.ts +0 -31
  102. package/node_modules/@comis/core/dist/config/schema-delivery-queue.js +0 -30
  103. package/node_modules/@comis/core/dist/config/schema-delivery-timing.d.ts +0 -41
  104. package/node_modules/@comis/core/dist/config/schema-delivery-timing.js +0 -31
  105. package/node_modules/@comis/core/dist/config/schema-monitoring.d.ts +0 -105
  106. package/node_modules/@comis/core/dist/config/schema-monitoring.js +0 -67
  107. package/node_modules/@comis/core/dist/ports/media-ports.d.ts +0 -278
  108. package/node_modules/@comis/core/dist/ports/media-ports.js +0 -1
  109. package/node_modules/@comis/core/dist/security/input-guard.d.ts +0 -46
  110. package/node_modules/@comis/core/dist/security/input-guard.js +0 -166
  111. package/node_modules/@comis/core/dist/security/scoped-secret-manager.d.ts +0 -38
  112. package/node_modules/@comis/core/dist/security/scoped-secret-manager.js +0 -94
  113. package/node_modules/@comis/daemon/dist/observability/delivery-context.d.ts +0 -37
  114. package/node_modules/@comis/daemon/dist/observability/delivery-context.js +0 -1
  115. package/node_modules/@comis/daemon/dist/observability/log-level-manager.d.ts +0 -23
  116. package/node_modules/@comis/daemon/dist/observability/log-level-manager.js +0 -34
  117. package/node_modules/@comis/daemon/dist/observability/log-transport.d.ts +0 -44
  118. package/node_modules/@comis/daemon/dist/observability/log-transport.js +0 -74
  119. package/node_modules/@comis/daemon/dist/observability/obs-write-buffer.d.ts +0 -53
  120. package/node_modules/@comis/daemon/dist/observability/obs-write-buffer.js +0 -68
  121. package/node_modules/@comis/daemon/dist/observability/types.d.ts +0 -6
  122. package/node_modules/@comis/daemon/dist/observability/types.js +0 -1
  123. package/node_modules/@comis/daemon/dist/wiring/seed-bundled-skills.d.ts +0 -41
  124. package/node_modules/@comis/daemon/dist/wiring/seed-bundled-skills.js +0 -84
  125. package/node_modules/@comis/daemon/dist/wiring/setup-delivery-mirror.d.ts +0 -24
  126. package/node_modules/@comis/daemon/dist/wiring/setup-delivery-mirror.js +0 -88
  127. package/node_modules/@comis/daemon/dist/wiring/setup-delivery-queue.d.ts +0 -31
  128. package/node_modules/@comis/daemon/dist/wiring/setup-delivery-queue.js +0 -132
  129. package/node_modules/@comis/daemon/dist/wiring/setup-monitoring.d.ts +0 -38
  130. package/node_modules/@comis/daemon/dist/wiring/setup-monitoring.js +0 -100
  131. package/node_modules/@comis/daemon/dist/wiring/setup-rpc-bridge.d.ts +0 -34
  132. package/node_modules/@comis/daemon/dist/wiring/setup-rpc-bridge.js +0 -52
  133. package/node_modules/@comis/daemon/dist/wiring/setup-task-extraction.d.ts +0 -41
  134. package/node_modules/@comis/daemon/dist/wiring/setup-task-extraction.js +0 -86
  135. package/node_modules/@comis/memory/dist/embedding-cache.d.ts +0 -36
  136. package/node_modules/@comis/memory/dist/embedding-cache.js +0 -94
  137. package/node_modules/@comis/skills/dist/bridge/tool-output-schemas.d.ts +0 -17
  138. package/node_modules/@comis/skills/dist/bridge/tool-output-schemas.js +0 -125
  139. package/node_modules/@comis/skills/dist/bridge/tool-parallelism-metadata.d.ts +0 -14
  140. package/node_modules/@comis/skills/dist/bridge/tool-parallelism-metadata.js +0 -92
  141. package/node_modules/@comis/skills/dist/bridge/tool-result-caps.d.ts +0 -14
  142. package/node_modules/@comis/skills/dist/bridge/tool-result-caps.js +0 -36
  143. package/node_modules/@comis/skills/dist/bridge/tool-search-hints.d.ts +0 -15
  144. package/node_modules/@comis/skills/dist/bridge/tool-search-hints.js +0 -68
  145. package/node_modules/@comis/skills/dist/bridge/tool-validators.d.ts +0 -11
  146. package/node_modules/@comis/skills/dist/bridge/tool-validators.js +0 -105
  147. package/node_modules/@comis/skills/dist/builtin/file/find-sort-wrapper.d.ts +0 -22
  148. package/node_modules/@comis/skills/dist/builtin/file/find-sort-wrapper.js +0 -95
  149. package/node_modules/@comis/skills/dist/builtin/file/grep-output-mode-wrapper.d.ts +0 -24
  150. package/node_modules/@comis/skills/dist/builtin/file/grep-output-mode-wrapper.js +0 -167
  151. package/node_modules/@comis/skills/dist/builtin/task-plan-tool.d.ts +0 -25
  152. package/node_modules/@comis/skills/dist/builtin/task-plan-tool.js +0 -67
  153. package/node_modules/@comis/skills/dist/integrations/mcp-tool-bridge.d.ts +0 -75
  154. package/node_modules/@comis/skills/dist/integrations/mcp-tool-bridge.js +0 -235
@@ -22,6 +22,7 @@ import { wrapInEnvelope } from "../envelope/message-envelope.js";
22
22
  import { runWithModelRetry } from "./model-retry.js";
23
23
  import { withPromptTimeout, PromptTimeoutError } from "./prompt-timeout.js";
24
24
  import { classifyError, classifyPromptTimeout } from "./error-classifier.js";
25
+ import { scrubSignedReplayStateInPlace } from "./signature-block-scrubber.js";
25
26
  import { createOverflowRecoveryWrapper } from "./overflow-recovery.js";
26
27
  import { isContextOverflowError } from "../safety/context-truncation-recovery.js";
27
28
  import { scanWithOutputGuard, recoverEmptyFinalResponse, extractExecutionPlan, generateCompletenessNudge, } from "./executor-response-filter.js";
@@ -252,13 +253,113 @@ export async function runPrompt(params) {
252
253
  }
253
254
  }
254
255
  if (!silent02Recovered && !silentRetryAttempted) {
255
- // Client-side validation errors (e.g. Anthropic 400 invalid_request_error
256
- // on thinking-block immutability) are deterministic retrying with the
257
- // same message body will reproduce the same failure. Short-circuit before
258
- // the strip+retry block to avoid wasting tokens on a repeat request.
256
+ // Classify the bridge's recorded LLM error to pick the correct path:
257
+ // - "client_request_signed_replay": scrub signed thinking state and
258
+ // re-enter runWithModelRetry once (provider-agnostic self-heal,
259
+ // covers Anthropic, Bedrock-Claude, Gemini, OpenAI Responses,
260
+ // OpenAI Completions reasoning, Mistral).
261
+ // - "client_request": deterministic; declare terminal failure
262
+ // verbatim with the original error wording.
263
+ // - default: fall through to the existing strip-empty-turn +
264
+ // re-enter retry path below.
259
265
  const llmErrSource = earlyBridgeResult.lastLlmErrorMessage ?? "";
260
266
  const earlyClassification = classifyError(new Error(llmErrSource));
261
- if (earlyClassification.category === "client_request") {
267
+ if (earlyClassification.category === "client_request_signed_replay") {
268
+ // Provider-agnostic signed-replay self-heal. Scrub stored signed
269
+ // thinking / reasoning state in place, then re-enter the full
270
+ // model retry pipeline once. Mirrors the silent-retry shape but
271
+ // with a 1s settle (vs 3s for transient overload) since the
272
+ // failure cause is deterministic state on disk, not a transient
273
+ // provider condition.
274
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
275
+ const msgs = session.messages ?? [];
276
+ const { blocksRemoved, thoughtSignaturesStripped } = scrubSignedReplayStateInPlace(msgs);
277
+ deps.logger.info({
278
+ blocksRemoved,
279
+ thoughtSignaturesStripped,
280
+ providerError: llmErrSource,
281
+ hint: "Signed-replay rejection detected; scrubbing thinking state and retrying once",
282
+ errorKind: "transient",
283
+ }, "Signed-replay self-heal: scrubbing and retrying");
284
+ // Brief settle before retry. Distinct from the 3s silent-retry
285
+ // delay because signed-replay is a deterministic state error,
286
+ // not a transient provider condition.
287
+ await new Promise(r => setTimeout(r, 1_000));
288
+ const retryResult = await runWithModelRetry({
289
+ session,
290
+ messageText,
291
+ promptImages,
292
+ config: { provider: config.provider, model: config.model },
293
+ resolvedModel: resolvedModel ? `${resolvedModel.provider}:${resolvedModel.id}` : undefined,
294
+ timeoutConfig: {
295
+ promptTimeoutMs: effectiveTimeout.promptTimeoutMs,
296
+ retryPromptTimeoutMs: effectiveTimeout.retryPromptTimeoutMs,
297
+ },
298
+ deps: {
299
+ eventBus: deps.eventBus,
300
+ logger: deps.logger,
301
+ authRotation: deps.authRotation,
302
+ fallbackModels: deps.fallbackModels,
303
+ modelRegistry: deps.modelRegistry,
304
+ agentId,
305
+ sessionKey: formatSessionKey(sessionKey),
306
+ providerHealth: deps.providerHealth,
307
+ onResetTimer: (fn) => { onResetTimer(fn); },
308
+ },
309
+ });
310
+ promptSucceeded = retryResult.succeeded;
311
+ promptError = retryResult.error;
312
+ // Re-check for empty response after retry; mirror the
313
+ // silent-retry post-check semantics so the recovery event
314
+ // reports a faithful succeeded flag.
315
+ let recovered = promptSucceeded;
316
+ if (promptSucceeded) {
317
+ const retryText = session.getLastAssistantText?.() ?? "";
318
+ if (retryText === "") {
319
+ const retryBridgeResult = bridge.getResult();
320
+ if ((retryBridgeResult.llmCalls ?? 0) > 0 && !retryBridgeResult.textEmitted) {
321
+ recovered = false;
322
+ promptSucceeded = false;
323
+ const llmDetail = retryBridgeResult.lastLlmErrorMessage
324
+ ? ` — ${retryBridgeResult.lastLlmErrorMessage}`
325
+ : "";
326
+ promptError = new Error(`Signed-replay self-heal failed: ${retryBridgeResult.llmCalls} LLM call(s) produced empty response after retry (finishReason: ${retryBridgeResult.finishReason ?? "unknown"})${llmDetail}`);
327
+ }
328
+ }
329
+ }
330
+ deps.eventBus.emit("execution:signed_replay_recovered", {
331
+ agentId: agentId ?? "default",
332
+ sessionKey: formatSessionKey(sessionKey),
333
+ blocksRemoved,
334
+ thoughtSignaturesStripped,
335
+ succeeded: recovered,
336
+ timestamp: Date.now(),
337
+ });
338
+ if (recovered) {
339
+ deps.logger.info({
340
+ blocksRemoved,
341
+ thoughtSignaturesStripped,
342
+ recovered: true,
343
+ }, "Signed-replay self-heal succeeded");
344
+ }
345
+ else {
346
+ deps.logger.warn({
347
+ blocksRemoved,
348
+ thoughtSignaturesStripped,
349
+ hint: "Signed-replay self-heal retry also failed; declaring terminal failure",
350
+ errorKind: "dependency",
351
+ }, "Signed-replay self-heal retry failed");
352
+ }
353
+ // Close the gate so this branch cannot be re-entered within the
354
+ // same runPrompt invocation.
355
+ // eslint-disable-next-line no-useless-assignment
356
+ silentRetryAttempted = true;
357
+ }
358
+ else if (earlyClassification.category === "client_request") {
359
+ // Plain client_request: deterministic failure (e.g. unprocessable_entity,
360
+ // bare "cannot be modified" without signature noun). Retrying would
361
+ // reproduce the same failure. Short-circuit before the strip+retry
362
+ // block to avoid wasting tokens.
262
363
  deps.logger.warn({
263
364
  llmCalls: earlyBridgeResult.llmCalls,
264
365
  finishReason: earlyBridgeResult.finishReason,
@@ -186,6 +186,7 @@ export async function assembleTools(params) {
186
186
  // -------------------------------------------------------------------
187
187
  const resourceLoaderOptions = {
188
188
  cwd: deps.workspaceDir,
189
+ agentDir: deps.agentDir,
189
190
  settingsManager,
190
191
  noExtensions: true,
191
192
  additionalSkillPaths: config.skills?.discoveryPaths ?? [],
@@ -50,10 +50,7 @@ import type { AgentExecutor } from "./types.js";
50
50
  *
51
51
  * Extracted as a named function for independent unit testing.
52
52
  */
53
- export declare function createBeforeToolCallGuard(stepCounter: StepCounter, budgetGuard: BudgetGuard, circuitBreaker: CircuitBreaker, toolRetryBreaker?: ToolRetryBreaker, messageSendLimiter?: MessageSendLimiter): (context: unknown, _signal?: AbortSignal) => Promise<{
54
- block: boolean;
55
- reason: string;
56
- } | undefined>;
53
+ export declare function createBeforeToolCallGuard(stepCounter: StepCounter, budgetGuard: BudgetGuard, circuitBreaker: CircuitBreaker, toolRetryBreaker?: ToolRetryBreaker, messageSendLimiter?: MessageSendLimiter): (context: unknown, _signal?: AbortSignal) => Promise<import("../safety/message-send-limiter.js").MessageSendVerdict | undefined>;
57
54
  /**
58
55
  * Merge SDK session stats into execution result for token totals (R-13).
59
56
  *
@@ -449,6 +449,24 @@ export function createPiExecutor(config, deps) {
449
449
  }
450
450
  const resourceLoader = new DefaultResourceLoader(resourceLoaderOptions);
451
451
  await resourceLoader.reload();
452
+ // The SDK's `tools` is an allowlist of tool *names* (not definitions).
453
+ // An empty array is treated as a non-empty allowlist that allows zero
454
+ // tools, including all customTools — which is why the agent ran
455
+ // tool-less from every entry point (chat API, SSE, Telegram, etc.):
456
+ // every Comis tool was filtered out of the SDK's tool registry, the
457
+ // Anthropic API request went out with `tools: []`, and the model
458
+ // emitted `<tool_call>...</tool_call>` markup as plaintext that
459
+ // Comis's loop never parsed back.
460
+ //
461
+ // Pass our customTool names as the explicit allowlist so:
462
+ // 1. All customTools land in the SDK's tool registry (their names
463
+ // pass `isAllowedTool`).
464
+ // 2. SDK built-ins like `bash` that conflict with Comis's policy
465
+ // controls are filtered out (Comis uses `exec` instead, with
466
+ // its own sandbox/audit hooks).
467
+ // 3. Where names overlap (read/edit/write), Comis's customTools
468
+ // override the SDK built-ins via Map.set() in the registry
469
+ // build (`agent-session.js:1810-1813` in pi-coding-agent@0.68.0).
452
470
  const sessionOptions = {
453
471
  cwd: deps.workspaceDir,
454
472
  authStorage: deps.authStorage,
@@ -457,7 +475,7 @@ export function createPiExecutor(config, deps) {
457
475
  sessionManager: sm,
458
476
  settingsManager,
459
477
  resourceLoader,
460
- tools: [],
478
+ tools: mergedCustomTools.map((t) => t.name),
461
479
  customTools: mergedCustomTools,
462
480
  };
463
481
  const { session, modelFallbackMessage } = await createAgentSession(sessionOptions);
@@ -654,11 +672,20 @@ export function createPiExecutor(config, deps) {
654
672
  const postActiveNames = session.getActiveToolNames?.() ?? [];
655
673
  if (postActiveNames.length < mergedToolNames.length) {
656
674
  const rejected = mergedToolNames.filter(n => !postActiveNames.includes(n));
675
+ const allRejected = postActiveNames.length === 0 && rejected.length === mergedToolNames.length;
657
676
  deps.logger.warn({
658
677
  rejected,
659
- hint: "SDK filtered some tools that Comis registered; check tool name collisions with SDK built-ins",
678
+ rejectedCount: rejected.length,
679
+ registeredCount: mergedToolNames.length,
680
+ postActiveCount: postActiveNames.length,
681
+ allRejected,
682
+ hint: allRejected
683
+ ? "SDK has 0 active tools after setActiveToolsByName -- not a name collision (empty active list, every Comis tool dropped). Indicates the SDK ResourceLoader / agent.tools handoff is broken; the LLM will receive no structured tool definitions and may emit `<tool_call>` markup as plaintext instead of using tool_use content blocks."
684
+ : "SDK filtered some Comis tools; likely name collisions with SDK built-ins (e.g. SDK reserves `bash`, `read_file`, etc.). Rename or omit the listed tools to avoid the conflict.",
660
685
  errorKind: "validation",
661
- }, "SDK rejected some tool registrations");
686
+ }, allRejected
687
+ ? "SDK rejected ALL tool registrations -- agent will run with no tools"
688
+ : "SDK rejected some tool registrations");
662
689
  }
663
690
  }
664
691
  catch (toolMgmtError) {
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Provider-agnostic replay-drift detector.
3
+ *
4
+ * Reads session file entries (jsonl-backed; survives daemon restarts) and
5
+ * decides whether the current pipeline run should scrub stored signed
6
+ * thinking / reasoning state pre-send to avoid provider replay-rejection.
7
+ *
8
+ * Drift conditions (first-match-wins ordering):
9
+ * 1. idle gap > threshold
10
+ * 2. model id change vs the last assistant turn
11
+ * 3. provider change
12
+ * 4. api change (e.g. anthropic.messages -> google.generative_ai.responses)
13
+ *
14
+ * Pure function — no async, no I/O. Defensive: tolerates malformed entries,
15
+ * missing timestamps, and the empty-history case.
16
+ *
17
+ * @module
18
+ */
19
+ /** Subset of a session file entry the detector reads. */
20
+ interface DriftFileEntry {
21
+ /** Unix-ms timestamp of the entry. */
22
+ timestamp?: number;
23
+ /** Entry kind; only `"message"` carries a role/content we care about. */
24
+ type?: string;
25
+ /** Message body for message-type entries. */
26
+ message?: {
27
+ role?: string;
28
+ /** Optional metadata persisted on assistant turns; pi-ai records the
29
+ * stream metadata here including model identity. */
30
+ metadata?: {
31
+ model?: {
32
+ id?: string;
33
+ provider?: string;
34
+ api?: string;
35
+ };
36
+ provider?: string;
37
+ api?: string;
38
+ };
39
+ content?: unknown;
40
+ };
41
+ }
42
+ /** Input to `shouldDropSignedFields`. */
43
+ export interface DriftCheckInput {
44
+ /** Session manager file entries, jsonl-backed. */
45
+ fileEntries: ReadonlyArray<DriftFileEntry>;
46
+ /** Current model identity from `session.agent.state.model` plus
47
+ * `config.provider`. All fields optional; missing fields disable the
48
+ * corresponding drift check rather than tripping it. */
49
+ currentModel: {
50
+ id?: string;
51
+ provider?: string;
52
+ api?: string;
53
+ };
54
+ /** Idle threshold from config (already resolved with default applied). */
55
+ idleMs: number;
56
+ /** Now() injection for testability. Default: `Date.now()`. */
57
+ now?: number;
58
+ }
59
+ /** Result of `shouldDropSignedFields`. */
60
+ export interface DriftCheck {
61
+ /** True when the caller should scrub signed thinking state pre-send. */
62
+ drop: boolean;
63
+ /** Human-readable reason; populated only when drop===true. */
64
+ reason?: "idle" | "model_change" | "provider_change" | "api_change";
65
+ /** Diagnostic detail for logger / event payload. */
66
+ detail?: {
67
+ idleGapMs?: number;
68
+ previousModel?: string;
69
+ previousProvider?: string;
70
+ previousApi?: string;
71
+ };
72
+ }
73
+ /**
74
+ * Decide whether signed thinking state should be scrubbed pre-send.
75
+ *
76
+ * Walks `fileEntries` from the END to find the most recent assistant message
77
+ * with content. Compares its timestamp + recorded model identity against the
78
+ * current model identity and the configured idle threshold.
79
+ *
80
+ * Ordering: idle takes precedence over model_change, which takes precedence
81
+ * over provider_change, which takes precedence over api_change. The order is
82
+ * tested explicitly so future refactors cannot silently change it.
83
+ */
84
+ export declare function shouldDropSignedFields(input: DriftCheckInput): DriftCheck;
85
+ export {};
@@ -0,0 +1,92 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * Provider-agnostic replay-drift detector.
4
+ *
5
+ * Reads session file entries (jsonl-backed; survives daemon restarts) and
6
+ * decides whether the current pipeline run should scrub stored signed
7
+ * thinking / reasoning state pre-send to avoid provider replay-rejection.
8
+ *
9
+ * Drift conditions (first-match-wins ordering):
10
+ * 1. idle gap > threshold
11
+ * 2. model id change vs the last assistant turn
12
+ * 3. provider change
13
+ * 4. api change (e.g. anthropic.messages -> google.generative_ai.responses)
14
+ *
15
+ * Pure function — no async, no I/O. Defensive: tolerates malformed entries,
16
+ * missing timestamps, and the empty-history case.
17
+ *
18
+ * @module
19
+ */
20
+ // ---------------------------------------------------------------------------
21
+ // Implementation
22
+ // ---------------------------------------------------------------------------
23
+ /**
24
+ * Decide whether signed thinking state should be scrubbed pre-send.
25
+ *
26
+ * Walks `fileEntries` from the END to find the most recent assistant message
27
+ * with content. Compares its timestamp + recorded model identity against the
28
+ * current model identity and the configured idle threshold.
29
+ *
30
+ * Ordering: idle takes precedence over model_change, which takes precedence
31
+ * over provider_change, which takes precedence over api_change. The order is
32
+ * tested explicitly so future refactors cannot silently change it.
33
+ */
34
+ export function shouldDropSignedFields(input) {
35
+ const { fileEntries, currentModel, idleMs } = input;
36
+ const now = input.now ?? Date.now();
37
+ if (!Array.isArray(fileEntries) || fileEntries.length === 0) {
38
+ return { drop: false };
39
+ }
40
+ // Walk from the end to find the most recent assistant message.
41
+ let lastAssistant = null;
42
+ for (let i = fileEntries.length - 1; i >= 0; i--) {
43
+ // eslint-disable-next-line security/detect-object-injection -- numeric index, caller-supplied array
44
+ const entry = fileEntries[i];
45
+ if (!entry || typeof entry !== "object")
46
+ continue;
47
+ if (entry.type !== "message")
48
+ continue;
49
+ if (entry.message?.role !== "assistant")
50
+ continue;
51
+ lastAssistant = entry;
52
+ break;
53
+ }
54
+ if (lastAssistant === null) {
55
+ // No prior assistant entry → nothing to drift from.
56
+ return { drop: false };
57
+ }
58
+ // Idle check: highest precedence.
59
+ const lastTs = typeof lastAssistant.timestamp === "number" ? lastAssistant.timestamp : undefined;
60
+ if (lastTs !== undefined) {
61
+ const idleGapMs = now - lastTs;
62
+ if (idleGapMs > idleMs) {
63
+ return { drop: true, reason: "idle", detail: { idleGapMs } };
64
+ }
65
+ }
66
+ // Identity comparisons. pi-ai persists stream metadata on assistant turns
67
+ // under message.metadata.model; some legacy paths put it directly on
68
+ // message.metadata.{provider,api}. Probe both shapes.
69
+ const meta = lastAssistant.message?.metadata;
70
+ const previousModel = meta?.model?.id;
71
+ const previousProvider = meta?.model?.provider ?? meta?.provider;
72
+ const previousApi = meta?.model?.api ?? meta?.api;
73
+ // Model id change.
74
+ if (previousModel !== undefined &&
75
+ currentModel.id !== undefined &&
76
+ previousModel !== currentModel.id) {
77
+ return { drop: true, reason: "model_change", detail: { previousModel } };
78
+ }
79
+ // Provider change.
80
+ if (previousProvider !== undefined &&
81
+ currentModel.provider !== undefined &&
82
+ previousProvider !== currentModel.provider) {
83
+ return { drop: true, reason: "provider_change", detail: { previousProvider } };
84
+ }
85
+ // API change.
86
+ if (previousApi !== undefined &&
87
+ currentModel.api !== undefined &&
88
+ previousApi !== currentModel.api) {
89
+ return { drop: true, reason: "api_change", detail: { previousApi } };
90
+ }
91
+ return { drop: false };
92
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Signed-replay state scrubber for the reject-and-retry self-heal path.
3
+ *
4
+ * Mutates the in-memory session message array in place so the next API call
5
+ * does not replay the rejected signed thinking / reasoning state. Distinct
6
+ * from the steady-state thinking-block-cleaner pipeline layer:
7
+ * - This scrubber is more aggressive: drops every `type:"thinking"` block
8
+ * regardless of `redacted` flag, and strips `thoughtSignature` from every
9
+ * `type:"toolCall"` block.
10
+ * - It runs only on the rejection path in `executor-prompt-runner.ts`, after
11
+ * `isSignedReplayError` has classified the error.
12
+ *
13
+ * Defensive: tolerates undefined / non-array `messages`, individual messages
14
+ * lacking `content`, non-array content, blocks lacking `type`. Returns the
15
+ * counts so the caller can emit the recovery event with accurate numbers.
16
+ *
17
+ * @module
18
+ */
19
+ /**
20
+ * Walk every assistant message in `messages` and:
21
+ * - Filter out every block where `block.type === "thinking"` (signed and
22
+ * redacted alike).
23
+ * - For every remaining block where `block.type === "toolCall"` or
24
+ * `block.type === "tool_call"` (pi-ai normalizes inconsistently across
25
+ * providers), delete `thoughtSignature` if present.
26
+ *
27
+ * Mutates the input array in place. Returns the number of blocks removed and
28
+ * the number of thoughtSignatures stripped so the caller can include them
29
+ * in the recovery event payload.
30
+ */
31
+ export declare function scrubSignedReplayStateInPlace(messages: unknown[]): {
32
+ blocksRemoved: number;
33
+ thoughtSignaturesStripped: number;
34
+ };
@@ -0,0 +1,69 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * Signed-replay state scrubber for the reject-and-retry self-heal path.
4
+ *
5
+ * Mutates the in-memory session message array in place so the next API call
6
+ * does not replay the rejected signed thinking / reasoning state. Distinct
7
+ * from the steady-state thinking-block-cleaner pipeline layer:
8
+ * - This scrubber is more aggressive: drops every `type:"thinking"` block
9
+ * regardless of `redacted` flag, and strips `thoughtSignature` from every
10
+ * `type:"toolCall"` block.
11
+ * - It runs only on the rejection path in `executor-prompt-runner.ts`, after
12
+ * `isSignedReplayError` has classified the error.
13
+ *
14
+ * Defensive: tolerates undefined / non-array `messages`, individual messages
15
+ * lacking `content`, non-array content, blocks lacking `type`. Returns the
16
+ * counts so the caller can emit the recovery event with accurate numbers.
17
+ *
18
+ * @module
19
+ */
20
+ // ---------------------------------------------------------------------------
21
+ // Public API
22
+ // ---------------------------------------------------------------------------
23
+ /**
24
+ * Walk every assistant message in `messages` and:
25
+ * - Filter out every block where `block.type === "thinking"` (signed and
26
+ * redacted alike).
27
+ * - For every remaining block where `block.type === "toolCall"` or
28
+ * `block.type === "tool_call"` (pi-ai normalizes inconsistently across
29
+ * providers), delete `thoughtSignature` if present.
30
+ *
31
+ * Mutates the input array in place. Returns the number of blocks removed and
32
+ * the number of thoughtSignatures stripped so the caller can include them
33
+ * in the recovery event payload.
34
+ */
35
+ export function scrubSignedReplayStateInPlace(messages) {
36
+ let blocksRemoved = 0;
37
+ let thoughtSignaturesStripped = 0;
38
+ if (!Array.isArray(messages)) {
39
+ return { blocksRemoved, thoughtSignaturesStripped };
40
+ }
41
+ for (const msg of messages) {
42
+ if (!msg || typeof msg !== "object")
43
+ continue;
44
+ const m = msg;
45
+ if (m.role !== "assistant")
46
+ continue;
47
+ if (!Array.isArray(m.content))
48
+ continue;
49
+ const content = m.content;
50
+ // Walk in reverse so splicing does not shift indices we still need to visit.
51
+ for (let i = content.length - 1; i >= 0; i--) {
52
+ // eslint-disable-next-line security/detect-object-injection -- numeric index over caller's array
53
+ const block = content[i];
54
+ if (!block || typeof block !== "object")
55
+ continue;
56
+ const b = block;
57
+ if (b.type === "thinking") {
58
+ content.splice(i, 1);
59
+ blocksRemoved++;
60
+ continue;
61
+ }
62
+ if ((b.type === "toolCall" || b.type === "tool_call") && b.thoughtSignature !== undefined) {
63
+ delete b.thoughtSignature;
64
+ thoughtSignaturesStripped++;
65
+ }
66
+ }
67
+ }
68
+ return { blocksRemoved, thoughtSignaturesStripped };
69
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Provider-agnostic signed-replay error detector.
3
+ *
4
+ * Detects provider rejections of stored signed thinking / reasoning state on
5
+ * the latest assistant message during replay. Triggered by a real production
6
+ * incident on `srv1593437` 2026-04-24T20:07:34Z (trace
7
+ * `93ba66cf-4283-4ed4-92bd-73d00b4eeb76`, request_id
8
+ * `req_011CaPCYYKfJRpuG3w2y5s52`) where Anthropic returned
9
+ * `400 invalid_request_error: messages.5.content.17: 'thinking' or
10
+ * 'redacted_thinking' blocks in the latest assistant message cannot be
11
+ * modified` after a 74-min idle gap with multiple daemon restarts.
12
+ *
13
+ * Provider coverage:
14
+ * - Anthropic: `messages.N.content.M ... thinking|redacted_thinking ... cannot be modified`
15
+ * (also via the JSON-path fast-path).
16
+ * - Bedrock-Claude: same wire shape as Anthropic over Bedrock.
17
+ * - Google Gemini / Vertex / Gemini-CLI: `thought_signature` mismatch /
18
+ * verification failed / not found.
19
+ * - OpenAI Responses (o-series): `reasoning_item` not found / invalid /
20
+ * expired / mismatch.
21
+ * - OpenAI Completions reasoning: `reasoning_id` not found / expired.
22
+ * - Mistral: `encrypted_content` mismatch / verification failed / tampered.
23
+ *
24
+ * Pure function — no I/O, no logger. The classifier in `error-classifier.ts`
25
+ * uses this as a `RegExp | { test(s: string): boolean }`-shaped pattern. The
26
+ * runner in `executor-prompt-runner.ts` uses the resulting category to drive
27
+ * the scrub-and-retry self-heal path.
28
+ *
29
+ * @module
30
+ */
31
+ /**
32
+ * Returns `true` when the given error message indicates a provider has
33
+ * rejected stored signed thinking / reasoning state on the latest assistant
34
+ * turn during replay, across the seven targeted providers.
35
+ *
36
+ * Match logic: either (signature noun + rejection verb both fire) OR the
37
+ * Anthropic JSON-path fast-path fires.
38
+ */
39
+ export declare function isSignedReplayError(message: string): boolean;
@@ -0,0 +1,72 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * Provider-agnostic signed-replay error detector.
4
+ *
5
+ * Detects provider rejections of stored signed thinking / reasoning state on
6
+ * the latest assistant message during replay. Triggered by a real production
7
+ * incident on `srv1593437` 2026-04-24T20:07:34Z (trace
8
+ * `93ba66cf-4283-4ed4-92bd-73d00b4eeb76`, request_id
9
+ * `req_011CaPCYYKfJRpuG3w2y5s52`) where Anthropic returned
10
+ * `400 invalid_request_error: messages.5.content.17: 'thinking' or
11
+ * 'redacted_thinking' blocks in the latest assistant message cannot be
12
+ * modified` after a 74-min idle gap with multiple daemon restarts.
13
+ *
14
+ * Provider coverage:
15
+ * - Anthropic: `messages.N.content.M ... thinking|redacted_thinking ... cannot be modified`
16
+ * (also via the JSON-path fast-path).
17
+ * - Bedrock-Claude: same wire shape as Anthropic over Bedrock.
18
+ * - Google Gemini / Vertex / Gemini-CLI: `thought_signature` mismatch /
19
+ * verification failed / not found.
20
+ * - OpenAI Responses (o-series): `reasoning_item` not found / invalid /
21
+ * expired / mismatch.
22
+ * - OpenAI Completions reasoning: `reasoning_id` not found / expired.
23
+ * - Mistral: `encrypted_content` mismatch / verification failed / tampered.
24
+ *
25
+ * Pure function — no I/O, no logger. The classifier in `error-classifier.ts`
26
+ * uses this as a `RegExp | { test(s: string): boolean }`-shaped pattern. The
27
+ * runner in `executor-prompt-runner.ts` uses the resulting category to drive
28
+ * the scrub-and-retry self-heal path.
29
+ *
30
+ * @module
31
+ */
32
+ // ---------------------------------------------------------------------------
33
+ // Patterns (case-insensitive)
34
+ // ---------------------------------------------------------------------------
35
+ /**
36
+ * Signature-noun regex: matches the provider-specific name for "the signed
37
+ * piece of state attached to the assistant turn". Covers all seven targeted
38
+ * providers without leaking unrelated false positives.
39
+ */
40
+ const SIGNATURE_NOUN = /thinking|redacted_thinking|reasoning_item|encrypted_content|thought_signature|reasoning_id/i;
41
+ /**
42
+ * Rejection-verb regex: matches any of the verbs providers use to reject
43
+ * tampered / stale / mismatched signed state. Pairing this with a signature
44
+ * noun avoids matching unrelated `invalid` / `not found` errors (e.g. model
45
+ * not found).
46
+ */
47
+ const REJECTION_VERB = /cannot be modified|not found|invalid|mismatch|verification failed|expired|tampered|stale/i;
48
+ /**
49
+ * Anthropic JSON-path fast-path: matches the canonical Anthropic 400 error
50
+ * shape `messages.N.content.M: ...thinking|redacted_thinking...`. This shape
51
+ * always indicates signed-replay rejection regardless of which verb appears
52
+ * in the surrounding text.
53
+ */
54
+ const ANTHROPIC_JSON_PATH = /messages\.\d+\.content\.\d+:.*(?:thinking|redacted_thinking)/i;
55
+ // ---------------------------------------------------------------------------
56
+ // Public API
57
+ // ---------------------------------------------------------------------------
58
+ /**
59
+ * Returns `true` when the given error message indicates a provider has
60
+ * rejected stored signed thinking / reasoning state on the latest assistant
61
+ * turn during replay, across the seven targeted providers.
62
+ *
63
+ * Match logic: either (signature noun + rejection verb both fire) OR the
64
+ * Anthropic JSON-path fast-path fires.
65
+ */
66
+ export function isSignedReplayError(message) {
67
+ if (!message)
68
+ return false;
69
+ if (ANTHROPIC_JSON_PATH.test(message))
70
+ return true;
71
+ return SIGNATURE_NOUN.test(message) && REJECTION_VERB.test(message);
72
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@comis/agent",
3
3
  "private": true,
4
- "version": "1.0.19",
4
+ "version": "1.0.23",
5
5
  "author": "Moshe Anconina",
6
6
  "license": "Apache-2.0",
7
7
  "description": "AI agent executor, budget control, and session management for Comis",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@comis/channels",
3
3
  "private": true,
4
- "version": "1.0.19",
4
+ "version": "1.0.23",
5
5
  "author": "Moshe Anconina",
6
6
  "license": "Apache-2.0",
7
7
  "description": "Chat platform adapters — Discord, Telegram, Slack, WhatsApp, Signal, iMessage, IRC, LINE",
File without changes
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@comis/cli",
3
3
  "private": true,
4
- "version": "1.0.19",
4
+ "version": "1.0.23",
5
5
  "author": "Moshe Anconina",
6
6
  "license": "Apache-2.0",
7
7
  "description": "Command-line interface for the Comis AI agent platform",