comisai 1.0.25 → 1.0.27
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/sections/tool-descriptions.js +130 -10
- package/node_modules/@comis/agent/dist/bootstrap/sections/tooling-sections.d.ts +1 -1
- package/node_modules/@comis/agent/dist/bootstrap/sections/tooling-sections.js +9 -2
- package/node_modules/@comis/agent/dist/bridge/bridge-metrics.d.ts +8 -0
- package/node_modules/@comis/agent/dist/bridge/bridge-metrics.js +2 -0
- package/node_modules/@comis/agent/dist/bridge/pi-event-bridge.d.ts +29 -0
- package/node_modules/@comis/agent/dist/bridge/pi-event-bridge.js +242 -2
- package/node_modules/@comis/agent/dist/bridge/thinking-block-hash-invariant.d.ts +210 -0
- package/node_modules/@comis/agent/dist/bridge/thinking-block-hash-invariant.js +566 -0
- package/node_modules/@comis/agent/dist/context-engine/context-engine.js +8 -6
- package/node_modules/@comis/agent/dist/context-engine/signature-replay-scrubber.d.ts +51 -30
- package/node_modules/@comis/agent/dist/context-engine/signature-replay-scrubber.js +109 -36
- package/node_modules/@comis/agent/dist/executor/executor-context-engine-setup.js +5 -1
- package/node_modules/@comis/agent/dist/executor/executor-post-execution.js +22 -20
- package/node_modules/@comis/agent/dist/executor/executor-prompt-runner.d.ts +2 -0
- package/node_modules/@comis/agent/dist/executor/executor-prompt-runner.js +111 -15
- package/node_modules/@comis/agent/dist/executor/executor-response-filter.d.ts +20 -17
- package/node_modules/@comis/agent/dist/executor/executor-response-filter.js +132 -52
- package/node_modules/@comis/agent/dist/executor/executor-tool-assembly.js +16 -3
- package/node_modules/@comis/agent/dist/executor/model-retry.d.ts +14 -0
- package/node_modules/@comis/agent/dist/executor/model-retry.js +72 -1
- package/node_modules/@comis/agent/dist/executor/pi-executor.d.ts +3 -0
- package/node_modules/@comis/agent/dist/executor/pi-executor.js +68 -9
- package/node_modules/@comis/agent/dist/executor/post-batch-continuation.d.ts +82 -0
- package/node_modules/@comis/agent/dist/executor/post-batch-continuation.js +200 -0
- package/node_modules/@comis/agent/dist/executor/stream-wrappers/request-body-injector.js +1 -9
- package/node_modules/@comis/agent/dist/executor/tool-deferral.d.ts +37 -2
- package/node_modules/@comis/agent/dist/executor/tool-deferral.js +45 -3
- package/node_modules/@comis/agent/dist/executor/tool-parallelism.js +0 -1
- package/node_modules/@comis/agent/dist/executor/types.d.ts +11 -2
- package/node_modules/@comis/agent/dist/index.d.ts +3 -1
- package/node_modules/@comis/agent/dist/index.js +2 -0
- package/node_modules/@comis/agent/dist/model/last-known-model.d.ts +36 -0
- package/node_modules/@comis/agent/dist/model/last-known-model.js +49 -0
- package/node_modules/@comis/agent/dist/model/model-registry-adapter.d.ts +16 -4
- package/node_modules/@comis/agent/dist/model/model-registry-adapter.js +65 -21
- package/node_modules/@comis/agent/dist/planner/types.d.ts +0 -2
- package/node_modules/@comis/agent/dist/session/comis-session-manager.d.ts +10 -0
- package/node_modules/@comis/agent/dist/session/comis-session-manager.js +5 -0
- package/node_modules/@comis/agent/dist/spawn/pi-mono-adapters.js +7 -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/client/rpc-client.js +6 -1
- package/node_modules/@comis/cli/dist/commands/doctor.js +5 -3
- package/node_modules/@comis/cli/dist/commands/health.js +5 -2
- package/node_modules/@comis/cli/dist/wizard/json-output.js +7 -3
- package/node_modules/@comis/cli/dist/wizard/steps/11-daemon-start.js +130 -0
- package/node_modules/@comis/cli/package.json +1 -1
- package/node_modules/@comis/core/dist/config/immutable-keys.d.ts +2 -2
- package/node_modules/@comis/core/dist/config/immutable-keys.js +8 -3
- package/node_modules/@comis/core/dist/config/managed-sections.d.ts +43 -4
- package/node_modules/@comis/core/dist/config/managed-sections.js +100 -6
- package/node_modules/@comis/core/dist/config/schema-agent.d.ts +39 -0
- package/node_modules/@comis/core/dist/config/schema-agent.js +14 -0
- package/node_modules/@comis/core/dist/config/schema.d.ts +4 -0
- package/node_modules/@comis/core/dist/config/schema.js +14 -0
- package/node_modules/@comis/core/dist/domain/execution-graph.d.ts +1 -1
- package/node_modules/@comis/core/dist/event-bus/events-agent.d.ts +17 -2
- package/node_modules/@comis/core/dist/exports/config.d.ts +2 -2
- 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/dist/daemon.d.ts +22 -0
- package/node_modules/@comis/daemon/dist/daemon.js +42 -0
- package/node_modules/@comis/daemon/dist/rpc/agent-handlers.d.ts +5 -2
- package/node_modules/@comis/daemon/dist/rpc/agent-handlers.js +80 -1
- package/node_modules/@comis/daemon/dist/rpc/agent-inline-workspace.d.ts +67 -0
- package/node_modules/@comis/daemon/dist/rpc/agent-inline-workspace.js +139 -0
- package/node_modules/@comis/daemon/dist/rpc/model-handlers.d.ts +3 -0
- package/node_modules/@comis/daemon/dist/rpc/model-handlers.js +29 -5
- package/node_modules/@comis/daemon/dist/rpc/probe-provider-auth.d.ts +30 -0
- package/node_modules/@comis/daemon/dist/rpc/probe-provider-auth.js +59 -0
- package/node_modules/@comis/daemon/dist/rpc/provider-handlers.d.ts +37 -0
- package/node_modules/@comis/daemon/dist/rpc/provider-handlers.js +330 -0
- package/node_modules/@comis/daemon/dist/rpc/rpc-dispatch.js +18 -1
- package/node_modules/@comis/daemon/dist/setup-docker-restart-warn.d.ts +4 -0
- package/node_modules/@comis/daemon/dist/setup-docker-restart-warn.js +30 -0
- package/node_modules/@comis/daemon/dist/wiring/setup-agents.d.ts +3 -1
- package/node_modules/@comis/daemon/dist/wiring/setup-agents.js +28 -2
- package/node_modules/@comis/daemon/dist/wiring/setup-cross-session.js +1 -0
- package/node_modules/@comis/daemon/dist/wiring/setup-tools.js +7 -4
- package/node_modules/@comis/daemon/package.json +1 -1
- package/node_modules/@comis/gateway/package.json +1 -1
- package/node_modules/@comis/infra/dist/index.d.ts +1 -0
- package/node_modules/@comis/infra/dist/index.js +2 -0
- package/node_modules/@comis/infra/dist/runtime/is-docker.d.ts +1 -0
- package/node_modules/@comis/infra/dist/runtime/is-docker.js +25 -0
- 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 +1 -3
- package/node_modules/@comis/skills/dist/builtin/platform/admin-manage-factory.js +24 -1
- package/node_modules/@comis/skills/dist/builtin/platform/agents-manage-tool.d.ts +53 -7
- package/node_modules/@comis/skills/dist/builtin/platform/agents-manage-tool.js +218 -24
- package/node_modules/@comis/skills/dist/builtin/platform/gateway-tool.d.ts +4 -1
- package/node_modules/@comis/skills/dist/builtin/platform/gateway-tool.js +16 -1
- package/node_modules/@comis/skills/dist/builtin/platform/index.d.ts +1 -1
- package/node_modules/@comis/skills/dist/builtin/platform/index.js +1 -1
- package/node_modules/@comis/skills/dist/builtin/platform/providers-manage-tool.d.ts +56 -0
- package/node_modules/@comis/skills/dist/builtin/platform/providers-manage-tool.js +203 -0
- package/node_modules/@comis/skills/dist/index.d.ts +1 -1
- package/node_modules/@comis/skills/dist/index.js +2 -2
- package/node_modules/@comis/skills/dist/policy/tool-policy.js +0 -1
- package/node_modules/@comis/skills/package.json +1 -1
- package/node_modules/@comis/web/dist/assets/{agent-detail-ru-AhppM.js → agent-detail-DqL6Artv.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{agent-editor-hjwRuFVp.js → agent-editor-CNM_h94Y.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{agent-list-6Uotjatr.js → agent-list-Dbh-xD_F.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{billing-view-CxysXH0p.js → billing-view-C1DmtyzK.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{channel-detail-BBCKtmne.js → channel-detail-CtCH22N1.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{channel-list-FkfeOLBQ.js → channel-list-C7xXn-60.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{chat-console-BumBaIgO.js → chat-console-C51pjFwk.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{config-editor-C9BSwHGy.js → config-editor-BLArYRB7.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{context-dag-browser-BHm00mJD.js → context-dag-browser-fuyMinNI.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{context-engine-BENY3pWE.js → context-engine-Bngf2bH0.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{delivery-view-BCnkPsAp.js → delivery-view-C80hucxX.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{diagnostics-view-C_jQFG2H.js → diagnostics-view-Cl4VbHZ6.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{ic-chat-message-FdQcZsSQ.js → ic-chat-message-ByFUoMm6.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{ic-connection-dot-BgYiK2N4.js → ic-connection-dot-C4nDHgY2.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{ic-tool-call-DMPHsLyx.js → ic-tool-call-Bh5kq-yY.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{index-FLPhHz8p.js → index-BBkuC-EU.js} +2 -2
- package/node_modules/@comis/web/dist/assets/{mcp-management-5jyScQis.js → mcp-management-DB-phOo7.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{media-config-J9oT9PPs.js → media-config-CRqZ1ZUH.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{media-test-DGTCtM8-.js → media-test-C9vE20Oy.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{memory-inspector-D5Re9ptG.js → memory-inspector-CeqfnxMZ.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{message-center-cRLK6ZmG.js → message-center-Daup7Mof.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{models-D5vu07MR.js → models-DLYnEU8E.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{observe-view-CalNNEmd.js → observe-view-BTSt_PO5.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{pipeline-builder-DUYDGwZf.js → pipeline-builder-DknfzyLt.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{pipeline-history-BAO8brOe.js → pipeline-history-JnHZdeU_.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{pipeline-history-detail-DectIoQt.js → pipeline-history-detail-Dg4knsEb.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{pipeline-list-BHlaBKww.js → pipeline-list-AEnibjsp.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{pipeline-monitor-BhtpNEHf.js → pipeline-monitor-DG7RbIOO.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{scheduler-VafN_8xi.js → scheduler-uL1fYKAT.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{security-QQXMRTlo.js → security-C3DywRLH.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{session-detail-BpZ_8Yih.js → session-detail-BtqCNWXV.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{session-list-DfCm8Cec.js → session-list-CJXWa2XT.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{setup-wizard-C-z477CG.js → setup-wizard-ywn7oJvu.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{skills-BCOGPf6s.js → skills-DX0KYnWD.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{subagents-l-auUraL.js → subagents-B8p5YJEB.js} +1 -1
- package/node_modules/@comis/web/dist/assets/{workspace-manager-DlvBixiq.js → workspace-manager-CgzNIrw1.js} +1 -1
- package/node_modules/@comis/web/dist/index.html +1 -1
- package/node_modules/@comis/web/package.json +1 -1
- package/package.json +14 -14
- package/node_modules/@comis/skills/dist/builtin/platform/agents-list-tool.d.ts +0 -19
- package/node_modules/@comis/skills/dist/builtin/platform/agents-list-tool.js +0 -39
|
@@ -1,45 +1,67 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Signature replay scrubber context engine layer.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* `budget.cacheFenceIndex` exactly like `thinking-block-cleaner`: messages
|
|
9
|
-
* at or below the fence are passed through unchanged.
|
|
4
|
+
* Always-on policy: strips signed `thinking` blocks entirely from EVERY
|
|
5
|
+
* assistant message (latest included) and strips `thoughtSignature` from
|
|
6
|
+
* `toolCall` / `tool_call` blocks in the same messages. `redacted_thinking`
|
|
7
|
+
* blocks are never modified, anywhere.
|
|
10
8
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
9
|
+
* Rationale: Anthropic's signed-thinking validation operates on the full
|
|
10
|
+
* (system + tools + history) prefix. After 8 quick tasks of progressively
|
|
11
|
+
* narrower drift detection (gj6 → kvl) we proved targeted detection is
|
|
12
|
+
* intractable; trace 679c8927 had stable tools (49138 bytes across 4 turns)
|
|
13
|
+
* but the system prompt grew +1824 bytes and the 400 fired anyway.
|
|
14
|
+
*
|
|
15
|
+
* 260428-lm6 introduced an unconditional drop that preserved the LATEST
|
|
16
|
+
* assistant message's signatures, on the theory that the immediate-next
|
|
17
|
+
* continuation could still validate them. 260428-nzp's repro proved that
|
|
18
|
+
* carve-out doesn't work: cross-turn signature validation covers the whole
|
|
19
|
+
* request body (system + tools + history) and comis's dynamic context
|
|
20
|
+
* guarantees the surrounding context changes turn-to-turn. So the latest's
|
|
21
|
+
* signatures get invalidated too. Drop them all.
|
|
22
|
+
*
|
|
23
|
+
* Provider coverage: NOT gated on `model.reasoning` because Gemini's
|
|
24
|
+
* `thoughtSignature` lives on toolCall blocks even when the model itself
|
|
25
|
+
* is not flagged as reasoning. Cost is one walk over assistant messages,
|
|
26
|
+
* no I/O.
|
|
15
27
|
*
|
|
16
28
|
* Immutability: never mutates input messages or arrays. Returns new arrays
|
|
17
|
-
* and shallow-copied messages only when changes are needed. When
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
29
|
+
* and shallow-copied messages only when changes are needed. When the
|
|
30
|
+
* history contains zero touchable signed state (e.g., fresh session, or
|
|
31
|
+
* single-assistant turn) returns the original `messages` reference (zero
|
|
32
|
+
* allocation).
|
|
21
33
|
*
|
|
22
34
|
* @module
|
|
23
35
|
*/
|
|
36
|
+
import type { ComisLogger } from "@comis/infra";
|
|
24
37
|
import type { ContextLayer } from "./types.js";
|
|
25
38
|
import type { DriftCheck } from "../executor/replay-drift-detector.js";
|
|
26
|
-
/** Stats reported via the `onScrubbed` callback. */
|
|
27
|
-
interface ScrubbedStats {
|
|
28
|
-
/** Number of thinking blocks dropped across the whole history. */
|
|
29
|
-
dropped: number;
|
|
30
|
-
/** Number of thoughtSignatures stripped from toolCall blocks. */
|
|
31
|
-
signaturesStripped: number;
|
|
32
|
-
/** Drift reason that triggered the scrub (forwarded for observability). */
|
|
33
|
-
reason?: string;
|
|
34
|
-
}
|
|
35
39
|
/** Dependencies for `createSignatureReplayScrubber`. */
|
|
36
40
|
export interface SignatureReplayScrubberDeps {
|
|
37
|
-
/**
|
|
38
|
-
*
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
*
|
|
42
|
-
|
|
41
|
+
/** Kept on the deps shape for back-compat with existing wiring; unused
|
|
42
|
+
* by this layer. The thinking cleaner's keepTurns override in
|
|
43
|
+
* executor-context-engine-setup.ts still consults the same closure for
|
|
44
|
+
* unrelated reasons, so leaving the field plumbed avoids a chain of
|
|
45
|
+
* unrelated edits in callers. */
|
|
46
|
+
getReplayDriftMode?: () => DriftCheck | undefined;
|
|
47
|
+
/** Optional callback invoked at the end of `apply()` with the scrub
|
|
48
|
+
* counts. Fields preserve the legacy `dropped` / `signaturesStripped`
|
|
49
|
+
* names so the context-engine snapshot consumer keeps working without
|
|
50
|
+
* churn; the new explicit counter names are also included. */
|
|
51
|
+
onScrubbed?: (stats: {
|
|
52
|
+
scrubbedAssistantMessages: number;
|
|
53
|
+
blocksAffected: number;
|
|
54
|
+
toolCallsAffected: number;
|
|
55
|
+
latestAssistantIdx: number;
|
|
56
|
+
/** Alias of blocksAffected (legacy field name preserved). */
|
|
57
|
+
dropped: number;
|
|
58
|
+
/** Alias of toolCallsAffected (legacy field name preserved). */
|
|
59
|
+
signaturesStripped: number;
|
|
60
|
+
/** Legacy; always undefined now (no drift reason in the always-on path). */
|
|
61
|
+
reason?: string;
|
|
62
|
+
}) => void;
|
|
63
|
+
/** Required: per-execute INFO log emission. */
|
|
64
|
+
logger: ComisLogger;
|
|
43
65
|
}
|
|
44
66
|
/**
|
|
45
67
|
* Create the signature-replay-scrubber pipeline layer.
|
|
@@ -48,4 +70,3 @@ export interface SignatureReplayScrubberDeps {
|
|
|
48
70
|
* `signature-surrogate-guard` (and well before `reasoning-tag-stripper`).
|
|
49
71
|
*/
|
|
50
72
|
export declare function createSignatureReplayScrubber(deps: SignatureReplayScrubberDeps): ContextLayer;
|
|
51
|
-
export {};
|
|
@@ -2,23 +2,35 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Signature replay scrubber context engine layer.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* `budget.cacheFenceIndex` exactly like `thinking-block-cleaner`: messages
|
|
10
|
-
* at or below the fence are passed through unchanged.
|
|
5
|
+
* Always-on policy: strips signed `thinking` blocks entirely from EVERY
|
|
6
|
+
* assistant message (latest included) and strips `thoughtSignature` from
|
|
7
|
+
* `toolCall` / `tool_call` blocks in the same messages. `redacted_thinking`
|
|
8
|
+
* blocks are never modified, anywhere.
|
|
11
9
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
10
|
+
* Rationale: Anthropic's signed-thinking validation operates on the full
|
|
11
|
+
* (system + tools + history) prefix. After 8 quick tasks of progressively
|
|
12
|
+
* narrower drift detection (gj6 → kvl) we proved targeted detection is
|
|
13
|
+
* intractable; trace 679c8927 had stable tools (49138 bytes across 4 turns)
|
|
14
|
+
* but the system prompt grew +1824 bytes and the 400 fired anyway.
|
|
15
|
+
*
|
|
16
|
+
* 260428-lm6 introduced an unconditional drop that preserved the LATEST
|
|
17
|
+
* assistant message's signatures, on the theory that the immediate-next
|
|
18
|
+
* continuation could still validate them. 260428-nzp's repro proved that
|
|
19
|
+
* carve-out doesn't work: cross-turn signature validation covers the whole
|
|
20
|
+
* request body (system + tools + history) and comis's dynamic context
|
|
21
|
+
* guarantees the surrounding context changes turn-to-turn. So the latest's
|
|
22
|
+
* signatures get invalidated too. Drop them all.
|
|
23
|
+
*
|
|
24
|
+
* Provider coverage: NOT gated on `model.reasoning` because Gemini's
|
|
25
|
+
* `thoughtSignature` lives on toolCall blocks even when the model itself
|
|
26
|
+
* is not flagged as reasoning. Cost is one walk over assistant messages,
|
|
27
|
+
* no I/O.
|
|
16
28
|
*
|
|
17
29
|
* Immutability: never mutates input messages or arrays. Returns new arrays
|
|
18
|
-
* and shallow-copied messages only when changes are needed. When
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
30
|
+
* and shallow-copied messages only when changes are needed. When the
|
|
31
|
+
* history contains zero touchable signed state (e.g., fresh session, or
|
|
32
|
+
* single-assistant turn) returns the original `messages` reference (zero
|
|
33
|
+
* allocation).
|
|
22
34
|
*
|
|
23
35
|
* @module
|
|
24
36
|
*/
|
|
@@ -32,16 +44,22 @@ export function createSignatureReplayScrubber(deps) {
|
|
|
32
44
|
return {
|
|
33
45
|
name: "signature-replay-scrubber",
|
|
34
46
|
async apply(messages, budget) {
|
|
35
|
-
|
|
36
|
-
if (!drift || !drift.drop) {
|
|
37
|
-
// Gate closed → no-op, return same reference (zero allocation).
|
|
47
|
+
if (messages.length === 0)
|
|
38
48
|
return messages;
|
|
49
|
+
// Find the latest assistant message index. If none, no scrub.
|
|
50
|
+
let latestIdx = -1;
|
|
51
|
+
for (let i = 0; i < messages.length; i++) {
|
|
52
|
+
// eslint-disable-next-line security/detect-object-injection -- numeric index
|
|
53
|
+
const m = messages[i];
|
|
54
|
+
if (m && m.role === "assistant")
|
|
55
|
+
latestIdx = i;
|
|
39
56
|
}
|
|
40
|
-
if (
|
|
57
|
+
if (latestIdx < 0)
|
|
41
58
|
return messages;
|
|
59
|
+
let scrubbedAssistantMessages = 0;
|
|
60
|
+
let blocksAffected = 0;
|
|
61
|
+
let toolCallsAffected = 0;
|
|
42
62
|
let anyChanged = false;
|
|
43
|
-
let dropped = 0;
|
|
44
|
-
let signaturesStripped = 0;
|
|
45
63
|
const result = new Array(messages.length);
|
|
46
64
|
for (let i = 0; i < messages.length; i++) {
|
|
47
65
|
// eslint-disable-next-line security/detect-object-injection -- numeric index
|
|
@@ -58,39 +76,73 @@ export function createSignatureReplayScrubber(deps) {
|
|
|
58
76
|
result[i] = original;
|
|
59
77
|
continue;
|
|
60
78
|
}
|
|
79
|
+
// Assistant message past the fence — walk content blocks. Latest
|
|
80
|
+
// included: cross-turn signature validation invalidates it too.
|
|
61
81
|
const content = msg.content;
|
|
62
82
|
let messageChanged = false;
|
|
63
|
-
const newContent =
|
|
83
|
+
const newContent = new Array(content.length);
|
|
64
84
|
for (let j = 0; j < content.length; j++) {
|
|
65
85
|
// eslint-disable-next-line security/detect-object-injection -- numeric index
|
|
66
86
|
const block = content[j];
|
|
67
87
|
if (!block || typeof block !== "object") {
|
|
68
|
-
|
|
88
|
+
// eslint-disable-next-line security/detect-object-injection -- numeric index
|
|
89
|
+
newContent[j] = block;
|
|
69
90
|
continue;
|
|
70
91
|
}
|
|
71
92
|
const b = block;
|
|
72
93
|
if (b.type === "thinking") {
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
94
|
+
// redacted_thinking: never modified.
|
|
95
|
+
if (b.redacted === true) {
|
|
96
|
+
// eslint-disable-next-line security/detect-object-injection -- numeric index
|
|
97
|
+
newContent[j] = block;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
// Signed thinking: strip the block entirely. Clearing the
|
|
101
|
+
// signature to "" was previously attempted but Anthropic only
|
|
102
|
+
// tolerates it while the prompt cache covers the prefix. On
|
|
103
|
+
// cache eviction the full request is re-validated and a modified
|
|
104
|
+
// thinkingSignature triggers a 400 ("thinking blocks cannot be
|
|
105
|
+
// modified"). Stripping the block avoids this: Anthropic accepts
|
|
106
|
+
// conversations where thinking blocks are absent from historical
|
|
107
|
+
// turns. Reasoning-token continuity is lost, but that is
|
|
108
|
+
// strictly better than a hard 400 that kills the session.
|
|
109
|
+
if (typeof b.thinkingSignature === "string" && b.thinkingSignature.length > 0) {
|
|
110
|
+
// Mark as null — filtered out below.
|
|
111
|
+
// eslint-disable-next-line security/detect-object-injection -- numeric index
|
|
112
|
+
newContent[j] = null;
|
|
113
|
+
blocksAffected++;
|
|
114
|
+
messageChanged = true;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
// eslint-disable-next-line security/detect-object-injection -- numeric index
|
|
118
|
+
newContent[j] = block;
|
|
78
119
|
continue;
|
|
79
120
|
}
|
|
80
|
-
if ((b.type === "toolCall" || b.type === "tool_call") &&
|
|
81
|
-
|
|
121
|
+
if ((b.type === "toolCall" || b.type === "tool_call") &&
|
|
122
|
+
b.thoughtSignature !== undefined &&
|
|
123
|
+
b.thoughtSignature !== null &&
|
|
124
|
+
!(typeof b.thoughtSignature === "string" && b.thoughtSignature.length === 0)) {
|
|
82
125
|
const copy = { ...b };
|
|
83
126
|
delete copy.thoughtSignature;
|
|
84
|
-
|
|
85
|
-
|
|
127
|
+
// eslint-disable-next-line security/detect-object-injection -- numeric index
|
|
128
|
+
newContent[j] = copy;
|
|
129
|
+
toolCallsAffected++;
|
|
86
130
|
messageChanged = true;
|
|
87
131
|
continue;
|
|
88
132
|
}
|
|
89
|
-
|
|
133
|
+
// eslint-disable-next-line security/detect-object-injection -- numeric index
|
|
134
|
+
newContent[j] = block;
|
|
90
135
|
}
|
|
91
136
|
if (messageChanged) {
|
|
137
|
+
const filtered = newContent.filter((b) => b !== null);
|
|
138
|
+
// Safety: if stripping thinking blocks emptied the content, keep
|
|
139
|
+
// a minimal text block so the message structure stays valid.
|
|
140
|
+
const safeContent = filtered.length > 0
|
|
141
|
+
? filtered
|
|
142
|
+
: [{ type: "text", text: "" }];
|
|
92
143
|
// eslint-disable-next-line security/detect-object-injection -- numeric index
|
|
93
|
-
result[i] = { ...msg, content:
|
|
144
|
+
result[i] = { ...msg, content: safeContent };
|
|
145
|
+
scrubbedAssistantMessages++;
|
|
94
146
|
anyChanged = true;
|
|
95
147
|
}
|
|
96
148
|
else {
|
|
@@ -98,9 +150,30 @@ export function createSignatureReplayScrubber(deps) {
|
|
|
98
150
|
result[i] = original;
|
|
99
151
|
}
|
|
100
152
|
}
|
|
101
|
-
// Always
|
|
102
|
-
//
|
|
103
|
-
|
|
153
|
+
// Always invoke onScrubbed so the context-engine snapshot stays
|
|
154
|
+
// consistent on zero-touch turns (e.g., a single assistant message
|
|
155
|
+
// history). Legacy aliases preserved for the existing snapshot
|
|
156
|
+
// consumer at context-engine.ts ~lines 718–725.
|
|
157
|
+
deps.onScrubbed?.({
|
|
158
|
+
scrubbedAssistantMessages,
|
|
159
|
+
blocksAffected,
|
|
160
|
+
toolCallsAffected,
|
|
161
|
+
latestAssistantIdx: latestIdx,
|
|
162
|
+
dropped: blocksAffected,
|
|
163
|
+
signaturesStripped: toolCallsAffected,
|
|
164
|
+
reason: undefined,
|
|
165
|
+
});
|
|
166
|
+
// Emit INFO once per execute() when at least one assistant message
|
|
167
|
+
// was actually scrubbed. Pino object-first; no string interp.
|
|
168
|
+
if (scrubbedAssistantMessages > 0) {
|
|
169
|
+
deps.logger.info({
|
|
170
|
+
module: "agent.context-engine.signature-replay-scrub",
|
|
171
|
+
scrubbedAssistantMessages,
|
|
172
|
+
blocksAffected,
|
|
173
|
+
toolCallsAffected,
|
|
174
|
+
latestAssistantIdx: latestIdx,
|
|
175
|
+
}, "Dropped thinking signatures from all assistant messages (cross-turn replay)");
|
|
176
|
+
}
|
|
104
177
|
// Zero-allocation early return when nothing was actually changed.
|
|
105
178
|
if (!anyChanged)
|
|
106
179
|
return messages;
|
|
@@ -46,6 +46,9 @@ export function setupContextEngine(params) {
|
|
|
46
46
|
// Memoized per-execute() so all pipeline runs in a single execute() see a
|
|
47
47
|
// consistent decision (cleaner + scrubber must agree). The closure reads
|
|
48
48
|
// the latest model identity each time (handles cycleModel mid-execute).
|
|
49
|
+
// Returns the identity/idle drift only — the kvl tool-defs dimension was
|
|
50
|
+
// removed in 260428-lm6 in favor of the unconditional latest-message
|
|
51
|
+
// preserving scrub in signature-replay-scrubber.
|
|
49
52
|
let memoizedDrift;
|
|
50
53
|
const computeDriftIfNeeded = () => {
|
|
51
54
|
if (memoizedDrift !== undefined)
|
|
@@ -58,7 +61,7 @@ export function setupContextEngine(params) {
|
|
|
58
61
|
// Derive currentApi from model.api when present; otherwise fall back to
|
|
59
62
|
// the provider family (resolveProviderFamily strips -bedrock / -vertex).
|
|
60
63
|
const currentApi = model?.api ?? resolveProviderFamily(config.provider);
|
|
61
|
-
|
|
64
|
+
const existingDrift = shouldDropSignedFields({
|
|
62
65
|
// Cast: shouldDropSignedFields tolerates malformed entries internally.
|
|
63
66
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
64
67
|
fileEntries: fileEntries,
|
|
@@ -69,6 +72,7 @@ export function setupContextEngine(params) {
|
|
|
69
72
|
},
|
|
70
73
|
idleMs,
|
|
71
74
|
});
|
|
75
|
+
memoizedDrift = existingDrift;
|
|
72
76
|
return memoizedDrift;
|
|
73
77
|
}
|
|
74
78
|
catch (err) {
|
|
@@ -193,30 +193,28 @@ export async function postExecution(params) {
|
|
|
193
193
|
});
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
|
-
// SEP: Attach planner metrics to result
|
|
196
|
+
// SEP: Attach planner metrics to result (observability-only post-L4).
|
|
197
|
+
// Uses actual tool-call count instead of prose-extracted step count to
|
|
198
|
+
// avoid over-counting (the LLM's numbered plan often has 2-3× more
|
|
199
|
+
// items than logical steps — e.g., "11 steps" for a 4-tool task).
|
|
197
200
|
if (executionPlanRef.current?.active) {
|
|
198
201
|
const plan = executionPlanRef.current;
|
|
202
|
+
const toolCalls = result.stepsExecuted ?? 0;
|
|
199
203
|
result.plannerMetrics = {
|
|
200
|
-
stepsPlanned:
|
|
201
|
-
stepsCompleted:
|
|
202
|
-
stepsSkipped:
|
|
203
|
-
nudgeTriggered: plan.nudged,
|
|
204
|
+
stepsPlanned: toolCalls,
|
|
205
|
+
stepsCompleted: toolCalls,
|
|
206
|
+
stepsSkipped: 0,
|
|
204
207
|
planExtractionTurn: 1,
|
|
205
208
|
};
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
nudgeTriggered: plan.nudged,
|
|
216
|
-
durationMs: Date.now() - plan.createdAtMs,
|
|
217
|
-
timestamp: Date.now(),
|
|
218
|
-
});
|
|
219
|
-
}
|
|
209
|
+
deps.eventBus.emit("sep:plan_completed", {
|
|
210
|
+
agentId: agentId ?? "default",
|
|
211
|
+
sessionKey: formattedKey,
|
|
212
|
+
stepsPlanned: toolCalls,
|
|
213
|
+
stepsCompleted: toolCalls,
|
|
214
|
+
stepsSkipped: 0,
|
|
215
|
+
durationMs: Date.now() - plan.createdAtMs,
|
|
216
|
+
timestamp: Date.now(),
|
|
217
|
+
});
|
|
220
218
|
}
|
|
221
219
|
// Record timestamp after successful execution for TTL guard.
|
|
222
220
|
// Uses the stream-setup captured retention (same ref the wrapper chain captured)
|
|
@@ -292,7 +290,11 @@ export async function postExecution(params) {
|
|
|
292
290
|
...(result.plannerMetrics && {
|
|
293
291
|
sepStepsPlanned: result.plannerMetrics.stepsPlanned,
|
|
294
292
|
sepStepsCompleted: result.plannerMetrics.stepsCompleted,
|
|
295
|
-
|
|
293
|
+
}),
|
|
294
|
+
...(result.continuationMetrics && {
|
|
295
|
+
postBatchContinuationFired: result.continuationMetrics.fired,
|
|
296
|
+
postBatchContinuationAttempts: result.continuationMetrics.attempts,
|
|
297
|
+
postBatchContinuationOutcome: result.continuationMetrics.outcome,
|
|
296
298
|
}),
|
|
297
299
|
// 1.5 + 3.2: Thinking token tracking (conditional -- only when thinking tokens detected)
|
|
298
300
|
...(bridgeResult.thinkingTokens != null && bridgeResult.thinkingTokens > 0 && {
|
|
@@ -24,6 +24,7 @@ import type { ExecutionResult, ExecutionOverrides } from "./types.js";
|
|
|
24
24
|
import type { ExecutionPlan } from "../planner/types.js";
|
|
25
25
|
import type { AuthRotationAdapter } from "../model/auth-rotation-adapter.js";
|
|
26
26
|
import type { ProviderHealthMonitor } from "../safety/provider-health-monitor.js";
|
|
27
|
+
import type { LastKnownModelTracker } from "../model/last-known-model.js";
|
|
27
28
|
import type { EnvelopeConfig } from "@comis/core";
|
|
28
29
|
/** Bridge interface used by the prompt runner (minimal getResult). */
|
|
29
30
|
export interface PromptRunnerBridge {
|
|
@@ -89,6 +90,7 @@ export interface RunPromptParams {
|
|
|
89
90
|
fallbackModels?: string[];
|
|
90
91
|
modelRegistry: ModelRegistry;
|
|
91
92
|
providerHealth?: ProviderHealthMonitor;
|
|
93
|
+
lastKnownModel?: LastKnownModelTracker;
|
|
92
94
|
envelopeConfig?: EnvelopeConfig;
|
|
93
95
|
outputGuard?: OutputGuardPort;
|
|
94
96
|
canaryToken?: string;
|
|
@@ -19,13 +19,15 @@ import { fromPromise } from "@comis/shared";
|
|
|
19
19
|
import { parseUserTokenBudget } from "../commands/budget-command.js";
|
|
20
20
|
import { createTurnBudgetTracker } from "../budget/turn-budget-tracker.js";
|
|
21
21
|
import { wrapInEnvelope } from "../envelope/message-envelope.js";
|
|
22
|
-
import { runWithModelRetry } from "./model-retry.js";
|
|
22
|
+
import { runWithModelRetry, isAuthError } from "./model-retry.js";
|
|
23
|
+
import { normalizeModelId } from "../provider/model-id-normalize.js";
|
|
23
24
|
import { withPromptTimeout, PromptTimeoutError } from "./prompt-timeout.js";
|
|
24
25
|
import { classifyError, classifyPromptTimeout } from "./error-classifier.js";
|
|
25
26
|
import { scrubSignedReplayStateInPlace } from "./signature-block-scrubber.js";
|
|
26
27
|
import { createOverflowRecoveryWrapper } from "./overflow-recovery.js";
|
|
27
28
|
import { isContextOverflowError } from "../safety/context-truncation-recovery.js";
|
|
28
|
-
import { scanWithOutputGuard, recoverEmptyFinalResponse, extractExecutionPlan,
|
|
29
|
+
import { scanWithOutputGuard, recoverEmptyFinalResponse, extractExecutionPlan, } from "./executor-response-filter.js";
|
|
30
|
+
import { runPostBatchContinuation } from "./post-batch-continuation.js";
|
|
29
31
|
import { getVisibleAssistantText } from "./phase-filter.js";
|
|
30
32
|
import { CHARS_PER_TOKEN_RATIO } from "../context-engine/constants.js";
|
|
31
33
|
import { resolveModelPricing } from "../model/model-catalog.js";
|
|
@@ -179,11 +181,16 @@ export async function runPrompt(params) {
|
|
|
179
181
|
agentId,
|
|
180
182
|
sessionKey: formatSessionKey(sessionKey),
|
|
181
183
|
providerHealth: deps.providerHealth,
|
|
184
|
+
lastKnownModel: deps.lastKnownModel,
|
|
182
185
|
onResetTimer: (fn) => { onResetTimer(fn); },
|
|
183
186
|
},
|
|
184
187
|
});
|
|
185
188
|
promptSucceeded = retryResult.succeeded;
|
|
186
189
|
promptError = retryResult.error;
|
|
190
|
+
// Record successful model for last-known-working tracker
|
|
191
|
+
if (retryResult.succeeded && retryResult.effectiveModel) {
|
|
192
|
+
deps.lastKnownModel?.recordSuccess(agentId ?? "default", retryResult.effectiveModel.provider, retryResult.effectiveModel.model);
|
|
193
|
+
}
|
|
187
194
|
}
|
|
188
195
|
// Detect zero-LLM-call stuck session.
|
|
189
196
|
// When session.prompt() succeeds but the agent loop made zero LLM calls
|
|
@@ -304,6 +311,7 @@ export async function runPrompt(params) {
|
|
|
304
311
|
agentId,
|
|
305
312
|
sessionKey: formatSessionKey(sessionKey),
|
|
306
313
|
providerHealth: deps.providerHealth,
|
|
314
|
+
lastKnownModel: deps.lastKnownModel,
|
|
307
315
|
onResetTimer: (fn) => { onResetTimer(fn); },
|
|
308
316
|
},
|
|
309
317
|
});
|
|
@@ -429,6 +437,7 @@ export async function runPrompt(params) {
|
|
|
429
437
|
agentId,
|
|
430
438
|
sessionKey: formatSessionKey(sessionKey),
|
|
431
439
|
providerHealth: deps.providerHealth,
|
|
440
|
+
lastKnownModel: deps.lastKnownModel,
|
|
432
441
|
onResetTimer: (fn) => { onResetTimer(fn); },
|
|
433
442
|
},
|
|
434
443
|
});
|
|
@@ -451,6 +460,62 @@ export async function runPrompt(params) {
|
|
|
451
460
|
? ` — ${retryBridgeResult.lastLlmErrorMessage}`
|
|
452
461
|
: "";
|
|
453
462
|
promptError = new Error(`Silent LLM failure: ${retryBridgeResult.llmCalls} LLM call(s) produced empty response after retry (finishReason: ${retryBridgeResult.finishReason ?? "unknown"})${llmDetail}`);
|
|
463
|
+
// LKW silent-failure fallback: some providers return 403 as
|
|
464
|
+
// an empty response (SDK doesn't throw), so model-retry's LKW
|
|
465
|
+
// block never fires. Detect auth errors here and try the LKW
|
|
466
|
+
// model as a final attempt before giving up.
|
|
467
|
+
const silentAuthErr = retryBridgeResult.lastLlmErrorMessage ?? "";
|
|
468
|
+
if (isAuthError(new Error(silentAuthErr)) && deps.lastKnownModel) {
|
|
469
|
+
const lkw = deps.lastKnownModel.getLastKnown(agentId ?? "") ??
|
|
470
|
+
deps.lastKnownModel.getAnyKnown(config.provider);
|
|
471
|
+
if (lkw && (lkw.provider !== config.provider || lkw.model !== config.model)) {
|
|
472
|
+
deps.logger.info({ lkwProvider: lkw.provider, lkwModel: lkw.model, silentAuthErr }, "Silent auth failure — attempting last-known-working model");
|
|
473
|
+
try {
|
|
474
|
+
const normalizedLkw = normalizeModelId(lkw.provider, lkw.model);
|
|
475
|
+
const lkwModelObj = deps.modelRegistry.find(lkw.provider, normalizedLkw.modelId);
|
|
476
|
+
if (lkwModelObj) {
|
|
477
|
+
await session.setModel(lkwModelObj);
|
|
478
|
+
}
|
|
479
|
+
// Strip trailing empty assistant turns before the LKW attempt
|
|
480
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
481
|
+
const lkwMsgs = session.messages ?? [];
|
|
482
|
+
for (let li = lkwMsgs.length - 1; li >= 0; li--) {
|
|
483
|
+
const lm = lkwMsgs[li]; // eslint-disable-line security/detect-object-injection
|
|
484
|
+
if (lm?.role !== "assistant")
|
|
485
|
+
break;
|
|
486
|
+
const lBlocks = Array.isArray(lm.content) ? lm.content : [];
|
|
487
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK interop boundary
|
|
488
|
+
const lHasText = lBlocks.some((b) => b.type === "text" && typeof b.text === "string" && b.text.trim() !== "");
|
|
489
|
+
if (!lHasText)
|
|
490
|
+
lkwMsgs.splice(li, 1);
|
|
491
|
+
else
|
|
492
|
+
break;
|
|
493
|
+
}
|
|
494
|
+
await withPromptTimeout(session.prompt(messageText, { expandPromptTemplates: false, images: promptImages }), effectiveTimeout.retryPromptTimeoutMs, () => session.abort());
|
|
495
|
+
const lkwText = getVisibleAssistantText(session);
|
|
496
|
+
if (lkwText !== "") {
|
|
497
|
+
promptSucceeded = true;
|
|
498
|
+
promptError = undefined;
|
|
499
|
+
deps.lastKnownModel.recordSuccess(agentId ?? "default", lkw.provider, lkw.model);
|
|
500
|
+
deps.logger.info({ lkwProvider: lkw.provider, lkwModel: lkw.model }, "LKW silent-failure fallback succeeded");
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
deps.logger.warn({
|
|
504
|
+
lkwProvider: lkw.provider, lkwModel: lkw.model,
|
|
505
|
+
hint: "LKW model also produced empty response",
|
|
506
|
+
errorKind: "dependency",
|
|
507
|
+
}, "LKW silent-failure fallback produced empty response");
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
catch (lkwErr) {
|
|
511
|
+
deps.logger.warn({
|
|
512
|
+
err: lkwErr, lkwProvider: lkw.provider, lkwModel: lkw.model,
|
|
513
|
+
hint: "LKW model threw during silent-failure fallback",
|
|
514
|
+
errorKind: "dependency",
|
|
515
|
+
}, "LKW silent-failure fallback failed");
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
454
519
|
}
|
|
455
520
|
}
|
|
456
521
|
}
|
|
@@ -632,20 +697,45 @@ export async function runPrompt(params) {
|
|
|
632
697
|
if (sepEnabled && !executionPlanRef.current && extractedResponse && toolCallCount === 0) {
|
|
633
698
|
deps.logger.debug({ agentId }, "SEP extraction skipped: no tool calls in execution (likely conversational response)");
|
|
634
699
|
}
|
|
635
|
-
//
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
700
|
+
// L4: Post-batch continuation (replaces the deleted SEP one-shot nudge).
|
|
701
|
+
// Detects empty final assistant turn after a successful tool batch within
|
|
702
|
+
// the current execution window and fires a directive followUp with multi-
|
|
703
|
+
// shot retry. Falls through to L3 synthesis (recoverEmptyFinalResponse) on
|
|
704
|
+
// exhaustion. SEP plan extraction + step counting remain intact for
|
|
705
|
+
// observability — see pi-event-bridge.ts:949-1024.
|
|
706
|
+
{
|
|
707
|
+
const continuationConfig = config.contextEngine?.postBatchContinuation
|
|
708
|
+
?? { enabled: true, maxRetries: 2 };
|
|
709
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
710
|
+
const sessionMessages = session.messages ?? [];
|
|
711
|
+
const continuationResult = await runPostBatchContinuation({
|
|
712
|
+
session,
|
|
713
|
+
messages: sessionMessages,
|
|
714
|
+
config: continuationConfig,
|
|
715
|
+
logger: deps.logger,
|
|
716
|
+
agentId,
|
|
717
|
+
getVisibleAssistantText,
|
|
640
718
|
});
|
|
641
|
-
if (
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
const nudgeResponse = getVisibleAssistantText(session);
|
|
646
|
-
if (nudgeResponse) {
|
|
647
|
-
result.response = nudgeResponse;
|
|
719
|
+
if (continuationResult.ok) {
|
|
720
|
+
const v = continuationResult.value;
|
|
721
|
+
if (v.recovered && v.response) {
|
|
722
|
+
result.response = v.response;
|
|
648
723
|
}
|
|
724
|
+
// Stash outcome metrics for executor-post-execution.ts to emit in the
|
|
725
|
+
// Execution complete log.
|
|
726
|
+
result.continuationMetrics = {
|
|
727
|
+
fired: v.outcome !== "no_match" && v.outcome !== "disabled",
|
|
728
|
+
attempts: v.attempts,
|
|
729
|
+
outcome: v.outcome,
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
else {
|
|
733
|
+
deps.logger.warn({
|
|
734
|
+
err: continuationResult.error.cause,
|
|
735
|
+
hint: "Post-batch continuation followUp failed; preserving response collected so far",
|
|
736
|
+
errorKind: "internal",
|
|
737
|
+
}, "Post-batch continuation error");
|
|
738
|
+
result.continuationMetrics = { fired: false, attempts: 0, outcome: "still_empty" };
|
|
649
739
|
}
|
|
650
740
|
}
|
|
651
741
|
// Budget-driven continuation loop
|
|
@@ -764,7 +854,13 @@ export async function runPrompt(params) {
|
|
|
764
854
|
const classified = promptError instanceof PromptTimeoutError
|
|
765
855
|
? classifyPromptTimeout(promptError.timeoutMs)
|
|
766
856
|
: classifyError(promptError);
|
|
767
|
-
|
|
857
|
+
// Enrich auth_invalid messages with the failing provider name
|
|
858
|
+
if (classified.category === "auth_invalid") {
|
|
859
|
+
result.response = `The AI service could not authenticate with the "${config.provider}" provider. Please check the API key or notify the system administrator.`;
|
|
860
|
+
}
|
|
861
|
+
else {
|
|
862
|
+
result.response = classified.userMessage;
|
|
863
|
+
}
|
|
768
864
|
result.errorContext = {
|
|
769
865
|
errorType: promptError instanceof PromptTimeoutError ? "PromptTimeout" : "PromptFailure",
|
|
770
866
|
retryable: classified.retryable,
|