comisai 1.0.18 → 1.0.22
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/dist/cli-entry.js +0 -0
- package/node_modules/@comis/agent/dist/context-engine/context-engine.js +43 -2
- package/node_modules/@comis/agent/dist/context-engine/signature-replay-scrubber.d.ts +51 -0
- package/node_modules/@comis/agent/dist/context-engine/signature-replay-scrubber.js +110 -0
- package/node_modules/@comis/agent/dist/context-engine/signature-surrogate-guard.d.ts +54 -0
- package/node_modules/@comis/agent/dist/context-engine/signature-surrogate-guard.js +145 -0
- package/node_modules/@comis/agent/dist/context-engine/types-core.d.ts +17 -0
- package/node_modules/@comis/agent/dist/executor/error-classifier.d.ts +11 -1
- package/node_modules/@comis/agent/dist/executor/error-classifier.js +13 -0
- package/node_modules/@comis/agent/dist/executor/executor-context-engine-setup.d.ts +1 -0
- package/node_modules/@comis/agent/dist/executor/executor-context-engine-setup.js +55 -0
- package/node_modules/@comis/agent/dist/executor/executor-prompt-runner.js +106 -5
- package/node_modules/@comis/agent/dist/executor/executor-tool-assembly.js +1 -0
- package/node_modules/@comis/agent/dist/executor/pi-executor.d.ts +1 -4
- package/node_modules/@comis/agent/dist/executor/replay-drift-detector.d.ts +85 -0
- package/node_modules/@comis/agent/dist/executor/replay-drift-detector.js +92 -0
- package/node_modules/@comis/agent/dist/executor/signature-block-scrubber.d.ts +34 -0
- package/node_modules/@comis/agent/dist/executor/signature-block-scrubber.js +69 -0
- package/node_modules/@comis/agent/dist/executor/signed-replay-detector.d.ts +39 -0
- package/node_modules/@comis/agent/dist/executor/signed-replay-detector.js +72 -0
- package/node_modules/@comis/agent/package.json +1 -1
- package/node_modules/@comis/channels/package.json +1 -1
- package/node_modules/@comis/cli/dist/cli.js +0 -0
- package/node_modules/@comis/cli/dist/wizard/steps/12-finish.d.ts +4 -5
- package/node_modules/@comis/cli/dist/wizard/steps/12-finish.js +99 -40
- package/node_modules/@comis/cli/package.json +1 -1
- package/node_modules/@comis/core/dist/config/git-manager.js +10 -4
- package/node_modules/@comis/core/dist/config/index.d.ts +1 -0
- package/node_modules/@comis/core/dist/config/index.js +2 -0
- package/node_modules/@comis/core/dist/config/managed-sections.d.ts +67 -0
- package/node_modules/@comis/core/dist/config/managed-sections.js +124 -0
- package/node_modules/@comis/core/dist/config/schema-agent.d.ts +28 -10
- package/node_modules/@comis/core/dist/config/schema-agent.js +6 -0
- package/node_modules/@comis/core/dist/config/schema-gateway.d.ts +2 -2
- package/node_modules/@comis/core/dist/config/schema.d.ts +65 -64
- package/node_modules/@comis/core/dist/event-bus/events-messaging.d.ts +16 -0
- package/node_modules/@comis/core/dist/exports/config.d.ts +1 -1
- package/node_modules/@comis/core/dist/exports/config.js +1 -1
- package/node_modules/@comis/core/package.json +1 -1
- package/node_modules/@comis/daemon/bundled-skills/skill-creator/scripts/init-skill.py +0 -0
- package/node_modules/@comis/daemon/bundled-skills/skill-creator/scripts/validate-skill.py +0 -0
- package/node_modules/@comis/daemon/dist/daemon.js +0 -0
- package/node_modules/@comis/daemon/dist/rpc/config-handlers.js +20 -7
- package/node_modules/@comis/daemon/dist/rpc/session-handlers.js +27 -1
- 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/package.json +1 -1
- package/node_modules/@comis/shared/package.json +1 -1
- package/node_modules/@comis/skills/dist/bridge/tool-metadata-registry.js +23 -8
- package/node_modules/@comis/skills/dist/builtin/platform/gateway-tool.d.ts +1 -1
- package/node_modules/@comis/skills/dist/builtin/platform/gateway-tool.js +18 -14
- package/node_modules/@comis/skills/dist/builtin/platform/unified-session-tool.js +1 -1
- package/node_modules/@comis/skills/package.json +1 -1
- package/node_modules/@comis/web/package.json +1 -1
- package/package.json +24 -26
- package/node_modules/@comis/agent/dist/provider/response/strip-minimax-xml.d.ts +0 -9
- package/node_modules/@comis/agent/dist/provider/response/strip-minimax-xml.js +0 -17
- package/node_modules/@comis/agent/dist/provider/response/strip-model-tokens.d.ts +0 -13
- package/node_modules/@comis/agent/dist/provider/response/strip-model-tokens.js +0 -19
- package/node_modules/@comis/agent/dist/provider/response/strip-tool-text.d.ts +0 -11
- package/node_modules/@comis/agent/dist/provider/response/strip-tool-text.js +0 -32
- package/node_modules/@comis/agent/dist/safety/follow-through-detector.d.ts +0 -46
- package/node_modules/@comis/agent/dist/safety/follow-through-detector.js +0 -76
- package/node_modules/@comis/agent/dist/safety/post-compaction-safety.d.ts +0 -30
- package/node_modules/@comis/agent/dist/safety/post-compaction-safety.js +0 -51
- package/node_modules/@comis/agent/dist/safety/schema-normalizer.d.ts +0 -37
- package/node_modules/@comis/agent/dist/safety/schema-normalizer.js +0 -137
- package/node_modules/@comis/agent/dist/safety/schema-pruning.d.ts +0 -50
- package/node_modules/@comis/agent/dist/safety/schema-pruning.js +0 -112
- package/node_modules/@comis/agent/dist/safety/tool-image-sanitizer.d.ts +0 -43
- package/node_modules/@comis/agent/dist/safety/tool-image-sanitizer.js +0 -96
- package/node_modules/@comis/agent/dist/safety/tool-sanitizer.d.ts +0 -44
- package/node_modules/@comis/agent/dist/safety/tool-sanitizer.js +0 -94
- package/node_modules/@comis/channels/dist/shared/thinking-tag-filter.d.ts +0 -28
- package/node_modules/@comis/channels/dist/shared/thinking-tag-filter.js +0 -206
- package/node_modules/@comis/cli/dist/wizard/config-writer.d.ts +0 -25
- package/node_modules/@comis/cli/dist/wizard/config-writer.js +0 -144
- package/node_modules/@comis/cli/dist/wizard/flow-types.d.ts +0 -48
- package/node_modules/@comis/cli/dist/wizard/flow-types.js +0 -70
- package/node_modules/@comis/cli/dist/wizard/manual-flow.d.ts +0 -21
- package/node_modules/@comis/cli/dist/wizard/manual-flow.js +0 -345
- package/node_modules/@comis/cli/dist/wizard/quickstart-flow.d.ts +0 -21
- package/node_modules/@comis/cli/dist/wizard/quickstart-flow.js +0 -116
- package/node_modules/@comis/core/dist/config/schema-agent-model.d.ts +0 -135
- package/node_modules/@comis/core/dist/config/schema-agent-model.js +0 -114
- package/node_modules/@comis/core/dist/config/schema-agent-session.d.ts +0 -177
- package/node_modules/@comis/core/dist/config/schema-agent-session.js +0 -116
- package/node_modules/@comis/core/dist/config/schema-context-engine.d.ts +0 -92
- package/node_modules/@comis/core/dist/config/schema-context-engine.js +0 -92
- package/node_modules/@comis/core/dist/config/schema-context-guard.d.ts +0 -34
- package/node_modules/@comis/core/dist/config/schema-context-guard.js +0 -32
- package/node_modules/@comis/core/dist/config/schema-delivery-mirror.d.ts +0 -27
- package/node_modules/@comis/core/dist/config/schema-delivery-mirror.js +0 -26
- package/node_modules/@comis/core/dist/config/schema-delivery-queue.d.ts +0 -31
- package/node_modules/@comis/core/dist/config/schema-delivery-queue.js +0 -30
- package/node_modules/@comis/core/dist/config/schema-delivery-timing.d.ts +0 -41
- package/node_modules/@comis/core/dist/config/schema-delivery-timing.js +0 -31
- package/node_modules/@comis/core/dist/config/schema-monitoring.d.ts +0 -105
- package/node_modules/@comis/core/dist/config/schema-monitoring.js +0 -67
- package/node_modules/@comis/core/dist/ports/media-ports.d.ts +0 -278
- package/node_modules/@comis/core/dist/ports/media-ports.js +0 -1
- package/node_modules/@comis/core/dist/security/input-guard.d.ts +0 -46
- package/node_modules/@comis/core/dist/security/input-guard.js +0 -166
- package/node_modules/@comis/core/dist/security/scoped-secret-manager.d.ts +0 -38
- package/node_modules/@comis/core/dist/security/scoped-secret-manager.js +0 -94
- package/node_modules/@comis/daemon/dist/observability/delivery-context.d.ts +0 -37
- package/node_modules/@comis/daemon/dist/observability/delivery-context.js +0 -1
- package/node_modules/@comis/daemon/dist/observability/log-level-manager.d.ts +0 -23
- package/node_modules/@comis/daemon/dist/observability/log-level-manager.js +0 -34
- package/node_modules/@comis/daemon/dist/observability/log-transport.d.ts +0 -44
- package/node_modules/@comis/daemon/dist/observability/log-transport.js +0 -74
- package/node_modules/@comis/daemon/dist/observability/obs-write-buffer.d.ts +0 -53
- package/node_modules/@comis/daemon/dist/observability/obs-write-buffer.js +0 -68
- package/node_modules/@comis/daemon/dist/observability/types.d.ts +0 -6
- package/node_modules/@comis/daemon/dist/observability/types.js +0 -1
- package/node_modules/@comis/daemon/dist/wiring/seed-bundled-skills.d.ts +0 -41
- package/node_modules/@comis/daemon/dist/wiring/seed-bundled-skills.js +0 -84
- package/node_modules/@comis/daemon/dist/wiring/setup-delivery-mirror.d.ts +0 -24
- package/node_modules/@comis/daemon/dist/wiring/setup-delivery-mirror.js +0 -88
- package/node_modules/@comis/daemon/dist/wiring/setup-delivery-queue.d.ts +0 -31
- package/node_modules/@comis/daemon/dist/wiring/setup-delivery-queue.js +0 -132
- package/node_modules/@comis/daemon/dist/wiring/setup-monitoring.d.ts +0 -38
- package/node_modules/@comis/daemon/dist/wiring/setup-monitoring.js +0 -100
- package/node_modules/@comis/daemon/dist/wiring/setup-rpc-bridge.d.ts +0 -34
- package/node_modules/@comis/daemon/dist/wiring/setup-rpc-bridge.js +0 -52
- package/node_modules/@comis/daemon/dist/wiring/setup-task-extraction.d.ts +0 -41
- package/node_modules/@comis/daemon/dist/wiring/setup-task-extraction.js +0 -86
- package/node_modules/@comis/memory/dist/embedding-cache.d.ts +0 -36
- package/node_modules/@comis/memory/dist/embedding-cache.js +0 -94
- package/node_modules/@comis/skills/dist/bridge/tool-output-schemas.d.ts +0 -17
- package/node_modules/@comis/skills/dist/bridge/tool-output-schemas.js +0 -125
- package/node_modules/@comis/skills/dist/bridge/tool-parallelism-metadata.d.ts +0 -14
- package/node_modules/@comis/skills/dist/bridge/tool-parallelism-metadata.js +0 -92
- package/node_modules/@comis/skills/dist/bridge/tool-result-caps.d.ts +0 -14
- package/node_modules/@comis/skills/dist/bridge/tool-result-caps.js +0 -36
- package/node_modules/@comis/skills/dist/bridge/tool-search-hints.d.ts +0 -15
- package/node_modules/@comis/skills/dist/bridge/tool-search-hints.js +0 -68
- package/node_modules/@comis/skills/dist/bridge/tool-validators.d.ts +0 -11
- package/node_modules/@comis/skills/dist/bridge/tool-validators.js +0 -105
- package/node_modules/@comis/skills/dist/builtin/file/find-sort-wrapper.d.ts +0 -22
- package/node_modules/@comis/skills/dist/builtin/file/find-sort-wrapper.js +0 -95
- package/node_modules/@comis/skills/dist/builtin/file/grep-output-mode-wrapper.d.ts +0 -24
- package/node_modules/@comis/skills/dist/builtin/file/grep-output-mode-wrapper.js +0 -167
- package/node_modules/@comis/skills/dist/builtin/task-plan-tool.d.ts +0 -25
- package/node_modules/@comis/skills/dist/builtin/task-plan-tool.js +0 -67
- package/node_modules/@comis/skills/dist/integrations/mcp-tool-bridge.d.ts +0 -75
- 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
|
-
//
|
|
256
|
-
//
|
|
257
|
-
//
|
|
258
|
-
//
|
|
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 === "
|
|
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
|
*
|
|
@@ -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
|
+
}
|
|
File without changes
|
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
* Finish step -- step 12 of the init wizard.
|
|
3
3
|
*
|
|
4
4
|
* Displays a quick-reference card with essential CLI commands,
|
|
5
|
-
* gateway access info
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* and immediately actionable.
|
|
5
|
+
* gateway access info (URLs), the full access token with a secure-storage
|
|
6
|
+
* warning, and -- when the gateway binds loopback-only -- a copy-paste SSH
|
|
7
|
+
* tunnel recipe so non-technical users can open the dashboard from another
|
|
8
|
+
* computer. Ends with a shell-completion offer and a branded outro.
|
|
10
9
|
*
|
|
11
10
|
* @module
|
|
12
11
|
*/
|