@superblocksteam/vite-plugin-file-sync 2.0.151 → 2.0.152-next.0
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/ai-service/agent/middleware.d.ts.map +1 -1
- package/dist/ai-service/agent/middleware.js +10 -1
- package/dist/ai-service/agent/middleware.js.map +1 -1
- package/dist/ai-service/agent/prompts/build-base-system-prompt.d.ts.map +1 -1
- package/dist/ai-service/agent/prompts/build-base-system-prompt.js +25 -6
- package/dist/ai-service/agent/prompts/build-base-system-prompt.js.map +1 -1
- package/dist/ai-service/agent/prompts/build-security-scan-prompt.d.ts.map +1 -1
- package/dist/ai-service/agent/prompts/build-security-scan-prompt.js +7 -2
- package/dist/ai-service/agent/prompts/build-security-scan-prompt.js.map +1 -1
- package/dist/ai-service/agent/tools/apis/get-sdk-api-docs.d.ts +47 -1
- package/dist/ai-service/agent/tools/apis/get-sdk-api-docs.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/apis/get-sdk-api-docs.js +129 -15
- package/dist/ai-service/agent/tools/apis/get-sdk-api-docs.js.map +1 -1
- package/dist/ai-service/agent/tools/apis/test-api.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/apis/test-api.js +1 -24
- package/dist/ai-service/agent/tools/apis/test-api.js.map +1 -1
- package/dist/ai-service/agent/tools/databases/dev-database-tasks.d.ts +53 -9
- package/dist/ai-service/agent/tools/databases/dev-database-tasks.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/databases/dev-database-tasks.js +304 -67
- package/dist/ai-service/agent/tools/databases/dev-database-tasks.js.map +1 -1
- package/dist/ai-service/agent/tools/databases/dev-database-tools.d.ts +47 -1
- package/dist/ai-service/agent/tools/databases/dev-database-tools.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/databases/dev-database-tools.js +38 -1
- package/dist/ai-service/agent/tools/databases/dev-database-tools.js.map +1 -1
- package/dist/ai-service/agent/tools/integrations/execute-request.d.ts +391 -0
- package/dist/ai-service/agent/tools/integrations/execute-request.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/integrations/execute-request.js +257 -44
- package/dist/ai-service/agent/tools/integrations/execute-request.js.map +1 -1
- package/dist/ai-service/agent/tools/integrations/index.d.ts +1 -1
- package/dist/ai-service/agent/tools/integrations/index.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/integrations/index.js +1 -1
- package/dist/ai-service/agent/tools/integrations/index.js.map +1 -1
- package/dist/ai-service/agent/tools/integrations/managed-app-database.d.ts +16 -0
- package/dist/ai-service/agent/tools/integrations/managed-app-database.d.ts.map +1 -0
- package/dist/ai-service/agent/tools/integrations/managed-app-database.js +31 -0
- package/dist/ai-service/agent/tools/integrations/managed-app-database.js.map +1 -0
- package/dist/ai-service/agent/tools/integrations/sql-classification.d.ts +44 -0
- package/dist/ai-service/agent/tools/integrations/sql-classification.d.ts.map +1 -0
- package/dist/ai-service/agent/tools/integrations/sql-classification.js +352 -0
- package/dist/ai-service/agent/tools/integrations/sql-classification.js.map +1 -0
- package/dist/ai-service/agent/tools/report-security-findings.d.ts +5 -5
- package/dist/ai-service/agent/tools.d.ts.map +1 -1
- package/dist/ai-service/agent/tools.js +5 -1
- package/dist/ai-service/agent/tools.js.map +1 -1
- package/dist/ai-service/agent/tools2/registry.d.ts.map +1 -1
- package/dist/ai-service/agent/tools2/registry.js +2 -0
- package/dist/ai-service/agent/tools2/registry.js.map +1 -1
- package/dist/ai-service/agent/tools2/tools/check-task.d.ts.map +1 -1
- package/dist/ai-service/agent/tools2/tools/check-task.js +4 -1
- package/dist/ai-service/agent/tools2/tools/check-task.js.map +1 -1
- package/dist/ai-service/agent/tools2/tools/exit-plan-mode.d.ts.map +1 -1
- package/dist/ai-service/agent/tools2/tools/exit-plan-mode.js +12 -1
- package/dist/ai-service/agent/tools2/tools/exit-plan-mode.js.map +1 -1
- package/dist/ai-service/agent/tools2/tools/wait-for-task.d.ts.map +1 -1
- package/dist/ai-service/agent/tools2/tools/wait-for-task.js +5 -1
- package/dist/ai-service/agent/tools2/tools/wait-for-task.js.map +1 -1
- package/dist/ai-service/chat/chat-session-store.d.ts +10 -1
- package/dist/ai-service/chat/chat-session-store.d.ts.map +1 -1
- package/dist/ai-service/chat/chat-session-store.js +65 -15
- package/dist/ai-service/chat/chat-session-store.js.map +1 -1
- package/dist/ai-service/context-archive-paths.d.ts +7 -0
- package/dist/ai-service/context-archive-paths.d.ts.map +1 -1
- package/dist/ai-service/context-archive-paths.js +7 -0
- package/dist/ai-service/context-archive-paths.js.map +1 -1
- package/dist/ai-service/context-download.d.ts.map +1 -1
- package/dist/ai-service/context-download.js +8 -10
- package/dist/ai-service/context-download.js.map +1 -1
- package/dist/ai-service/context-flush.d.ts +36 -0
- package/dist/ai-service/context-flush.d.ts.map +1 -0
- package/dist/ai-service/context-flush.js +90 -0
- package/dist/ai-service/context-flush.js.map +1 -0
- package/dist/ai-service/context-restore.d.ts +64 -0
- package/dist/ai-service/context-restore.d.ts.map +1 -0
- package/dist/ai-service/context-restore.js +136 -0
- package/dist/ai-service/context-restore.js.map +1 -0
- package/dist/ai-service/context-upload.d.ts +13 -4
- package/dist/ai-service/context-upload.d.ts.map +1 -1
- package/dist/ai-service/context-upload.js +28 -18
- package/dist/ai-service/context-upload.js.map +1 -1
- package/dist/ai-service/dev-database-client.d.ts +51 -0
- package/dist/ai-service/dev-database-client.d.ts.map +1 -1
- package/dist/ai-service/dev-database-client.js +3 -0
- package/dist/ai-service/dev-database-client.js.map +1 -1
- package/dist/ai-service/index.d.ts +19 -0
- package/dist/ai-service/index.d.ts.map +1 -1
- package/dist/ai-service/index.js +449 -182
- package/dist/ai-service/index.js.map +1 -1
- package/dist/ai-service/llm/context-v2/adapter.d.ts.map +1 -1
- package/dist/ai-service/llm/context-v2/adapter.js +1 -0
- package/dist/ai-service/llm/context-v2/adapter.js.map +1 -1
- package/dist/ai-service/llm/context-v2/compaction/client-side.d.ts +30 -0
- package/dist/ai-service/llm/context-v2/compaction/client-side.d.ts.map +1 -1
- package/dist/ai-service/llm/context-v2/compaction/client-side.js +77 -4
- package/dist/ai-service/llm/context-v2/compaction/client-side.js.map +1 -1
- package/dist/ai-service/llm/context-v2/compaction/compaction-strategy.d.ts +5 -0
- package/dist/ai-service/llm/context-v2/compaction/compaction-strategy.d.ts.map +1 -1
- package/dist/ai-service/llm/context-v2/compaction/server-side.d.ts +1 -0
- package/dist/ai-service/llm/context-v2/compaction/server-side.d.ts.map +1 -1
- package/dist/ai-service/llm/context-v2/compaction/server-side.js +3 -0
- package/dist/ai-service/llm/context-v2/compaction/server-side.js.map +1 -1
- package/dist/ai-service/llm/context-v2/compactor.d.ts.map +1 -1
- package/dist/ai-service/llm/context-v2/compactor.js +57 -6
- package/dist/ai-service/llm/context-v2/compactor.js.map +1 -1
- package/dist/ai-service/llm/context-v2/config.d.ts +13 -1
- package/dist/ai-service/llm/context-v2/config.d.ts.map +1 -1
- package/dist/ai-service/llm/context-v2/config.js +39 -2
- package/dist/ai-service/llm/context-v2/config.js.map +1 -1
- package/dist/ai-service/llm/context-v2/context-metrics.d.ts +23 -0
- package/dist/ai-service/llm/context-v2/context-metrics.d.ts.map +1 -1
- package/dist/ai-service/llm/context-v2/context-metrics.js +52 -1
- package/dist/ai-service/llm/context-v2/context-metrics.js.map +1 -1
- package/dist/ai-service/llm/context-v2/context.d.ts +27 -3
- package/dist/ai-service/llm/context-v2/context.d.ts.map +1 -1
- package/dist/ai-service/llm/context-v2/context.js +107 -10
- package/dist/ai-service/llm/context-v2/context.js.map +1 -1
- package/dist/ai-service/llm/context-v2/manager.d.ts +66 -0
- package/dist/ai-service/llm/context-v2/manager.d.ts.map +1 -1
- package/dist/ai-service/llm/context-v2/manager.js +194 -2
- package/dist/ai-service/llm/context-v2/manager.js.map +1 -1
- package/dist/ai-service/llm/context-v2/storage/event-store.d.ts +31 -5
- package/dist/ai-service/llm/context-v2/storage/event-store.d.ts.map +1 -1
- package/dist/ai-service/llm/context-v2/storage/jsonl-event-store.d.ts +30 -9
- package/dist/ai-service/llm/context-v2/storage/jsonl-event-store.d.ts.map +1 -1
- package/dist/ai-service/llm/context-v2/storage/jsonl-event-store.js +96 -13
- package/dist/ai-service/llm/context-v2/storage/jsonl-event-store.js.map +1 -1
- package/dist/ai-service/llm/context-v2/storage/legacy-context-adoption.d.ts +46 -0
- package/dist/ai-service/llm/context-v2/storage/legacy-context-adoption.d.ts.map +1 -0
- package/dist/ai-service/llm/context-v2/storage/legacy-context-adoption.js +240 -0
- package/dist/ai-service/llm/context-v2/storage/legacy-context-adoption.js.map +1 -0
- package/dist/ai-service/llm/context-v2/storage/local.d.ts +1 -1
- package/dist/ai-service/llm/context-v2/storage/local.d.ts.map +1 -1
- package/dist/ai-service/llm/context-v2/storage/local.js +3 -2
- package/dist/ai-service/llm/context-v2/storage/local.js.map +1 -1
- package/dist/ai-service/llm/context-v2/types.d.ts +5 -1
- package/dist/ai-service/llm/context-v2/types.d.ts.map +1 -1
- package/dist/ai-service/llm/context-v2/types.js.map +1 -1
- package/dist/ai-service/llm/stream/config.d.ts +4 -3
- package/dist/ai-service/llm/stream/config.d.ts.map +1 -1
- package/dist/ai-service/llm/stream/config.js.map +1 -1
- package/dist/ai-service/llm/stream/idle-monitor.d.ts +37 -4
- package/dist/ai-service/llm/stream/idle-monitor.d.ts.map +1 -1
- package/dist/ai-service/llm/stream/idle-monitor.js +38 -3
- package/dist/ai-service/llm/stream/idle-monitor.js.map +1 -1
- package/dist/ai-service/policy-agent-metrics.d.ts +18 -0
- package/dist/ai-service/policy-agent-metrics.d.ts.map +1 -0
- package/dist/ai-service/policy-agent-metrics.js +99 -0
- package/dist/ai-service/policy-agent-metrics.js.map +1 -0
- package/dist/ai-service/state-machine/clark-fsm.d.ts +9 -0
- package/dist/ai-service/state-machine/clark-fsm.d.ts.map +1 -1
- package/dist/ai-service/state-machine/helpers/prompt-request-metrics.d.ts +9 -0
- package/dist/ai-service/state-machine/helpers/prompt-request-metrics.d.ts.map +1 -1
- package/dist/ai-service/state-machine/helpers/prompt-request-metrics.js +27 -0
- package/dist/ai-service/state-machine/helpers/prompt-request-metrics.js.map +1 -1
- package/dist/ai-service/tasks/factory.d.ts +7 -0
- package/dist/ai-service/tasks/factory.d.ts.map +1 -1
- package/dist/ai-service/tasks/factory.js +5 -0
- package/dist/ai-service/tasks/factory.js.map +1 -1
- package/dist/ai-service/tasks/task-store.d.ts +11 -1
- package/dist/ai-service/tasks/task-store.d.ts.map +1 -1
- package/dist/ai-service/tasks/task-store.js +104 -29
- package/dist/ai-service/tasks/task-store.js.map +1 -1
- package/dist/ai-service/tasks/types.d.ts +9 -1
- package/dist/ai-service/tasks/types.d.ts.map +1 -1
- package/dist/extract-api-integrations.d.ts +76 -0
- package/dist/extract-api-integrations.d.ts.map +1 -1
- package/dist/extract-api-integrations.js +428 -109
- package/dist/extract-api-integrations.js.map +1 -1
- package/dist/file-sync-vite-plugin.d.ts +1 -1
- package/dist/file-sync-vite-plugin.d.ts.map +1 -1
- package/dist/file-sync-vite-plugin.js +42 -79
- package/dist/file-sync-vite-plugin.js.map +1 -1
- package/dist/file-system-helpers.d.ts +1 -1
- package/dist/file-system-helpers.d.ts.map +1 -1
- package/dist/file-system-helpers.js +1 -0
- package/dist/file-system-helpers.js.map +1 -1
- package/dist/git-auth-state.d.ts +4 -0
- package/dist/git-auth-state.d.ts.map +1 -0
- package/dist/git-auth-state.js +25 -0
- package/dist/git-auth-state.js.map +1 -0
- package/dist/git-service/checkpoint-commit-metrics.d.ts +2 -0
- package/dist/git-service/checkpoint-commit-metrics.d.ts.map +1 -0
- package/dist/git-service/checkpoint-commit-metrics.js +15 -0
- package/dist/git-service/checkpoint-commit-metrics.js.map +1 -0
- package/dist/git-service/checkpoint-committer.d.ts +22 -0
- package/dist/git-service/checkpoint-committer.d.ts.map +1 -0
- package/dist/git-service/checkpoint-committer.js +91 -0
- package/dist/git-service/checkpoint-committer.js.map +1 -0
- package/dist/git-service/commit-live-branch.d.ts +17 -0
- package/dist/git-service/commit-live-branch.d.ts.map +1 -0
- package/dist/git-service/commit-live-branch.js +28 -0
- package/dist/git-service/commit-live-branch.js.map +1 -0
- package/dist/git-service/provider-workflow-files.d.ts +12 -0
- package/dist/git-service/provider-workflow-files.d.ts.map +1 -0
- package/dist/git-service/provider-workflow-files.js +36 -0
- package/dist/git-service/provider-workflow-files.js.map +1 -0
- package/dist/plugin-options.d.ts +8 -0
- package/dist/plugin-options.d.ts.map +1 -1
- package/dist/plugin-options.js.map +1 -1
- package/dist/policy-gate-runner.d.ts.map +1 -1
- package/dist/policy-gate-runner.js +7 -9
- package/dist/policy-gate-runner.js.map +1 -1
- package/dist/server-rpc/client.d.ts.map +1 -1
- package/dist/server-rpc/client.js +2 -0
- package/dist/server-rpc/client.js.map +1 -1
- package/dist/server-rpc/index.d.ts +43 -1
- package/dist/server-rpc/index.d.ts.map +1 -1
- package/dist/server-rpc/index.js +143 -5
- package/dist/server-rpc/index.js.map +1 -1
- package/dist/server-rpc/request-abort-context.d.ts +3 -0
- package/dist/server-rpc/request-abort-context.d.ts.map +1 -0
- package/dist/server-rpc/request-abort-context.js +23 -0
- package/dist/server-rpc/request-abort-context.js.map +1 -0
- package/dist/server-rpc/rpc-invalidate-metrics.d.ts +20 -0
- package/dist/server-rpc/rpc-invalidate-metrics.d.ts.map +1 -0
- package/dist/server-rpc/rpc-invalidate-metrics.js +86 -0
- package/dist/server-rpc/rpc-invalidate-metrics.js.map +1 -0
- package/dist/socket-manager.d.ts.map +1 -1
- package/dist/socket-manager.js +21 -60
- package/dist/socket-manager.js.map +1 -1
- package/dist/sync-service/index.d.ts +26 -2
- package/dist/sync-service/index.d.ts.map +1 -1
- package/dist/sync-service/index.js +74 -37
- package/dist/sync-service/index.js.map +1 -1
- package/package.json +9 -8
package/dist/ai-service/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { context as otelContext, propagation, SpanKind, SpanStatusCode, trace, } from "@opentelemetry/api";
|
|
5
|
-
import {
|
|
5
|
+
import { hasToolCall, streamText as aiStreamText, } from "ai";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { AiGenerationState, AiMode, resolveAttachmentFileName, } from "@superblocksteam/library-shared/types";
|
|
8
8
|
import { isToolPermissionClarification } from "@superblocksteam/library-shared/types/tool-permission-response";
|
|
@@ -34,6 +34,7 @@ import { ChatSessionStore } from "./chat/chat-session-store.js";
|
|
|
34
34
|
import { normalizeTextAttachmentForModel } from "./chat/utils.js";
|
|
35
35
|
import { API_MIGRATION_ORIGINS, isApiMigrationOrigin, readPersistedChecklist, updateChecklistItem, } from "./checklist/persisted-checklist-store.js";
|
|
36
36
|
import { downloadContextFromBucketeer, downloadLatestContextFromBucketeer, restoreContextFromCheckpoint, } from "./context-download.js";
|
|
37
|
+
import { restoreConversationToCheckpoint, } from "./context-restore.js";
|
|
37
38
|
import { uploadContextToBucketeer } from "./context-upload.js";
|
|
38
39
|
import { AppContextStore } from "./context/app-context.js";
|
|
39
40
|
import { createDevDatabaseHttpTransport, DevDatabaseClient, } from "./dev-database-client.js";
|
|
@@ -47,8 +48,10 @@ import { createChaosFetch } from "./llm/chaos-fetch.js";
|
|
|
47
48
|
import { createClarkStreamTelemetrySink } from "./llm/clark-stream-telemetry-sink.js";
|
|
48
49
|
import { LLMClient } from "./llm/client.js";
|
|
49
50
|
import { selectContextManagementStrategy, } from "./llm/context-v2/context-management.js";
|
|
51
|
+
import { recordCheckpointRestore } from "./llm/context-v2/context-metrics.js";
|
|
50
52
|
import { ContextManagerV2 } from "./llm/context-v2/manager.js";
|
|
51
53
|
import { JsonlEventStore } from "./llm/context-v2/storage/jsonl-event-store.js";
|
|
54
|
+
import { adoptLegacyPerUserContextDirs } from "./llm/context-v2/storage/legacy-context-adoption.js";
|
|
52
55
|
import { LocalContextStorageV2 } from "./llm/context-v2/storage/local.js";
|
|
53
56
|
import { createLLMProvider, setSpanError } from "./llm/provider.js";
|
|
54
57
|
import { traceLLM } from "./llmobs/helpers.js";
|
|
@@ -60,6 +63,7 @@ import { recordMigrationAutoDispatchOutcome } from "./migration-autodispatch-met
|
|
|
60
63
|
import { buildServerMigrationLlmConfig, isMigrationAutoDispatchEnabled, } from "./migration-llm-config.js";
|
|
61
64
|
import { isMigrationSessionActive } from "./migration-session-active.js";
|
|
62
65
|
import { hasSettledNormalMigrationTurn, recordMigrationTurnDispatched, recordMigrationTurnSettled, } from "./migration-turns-log.js";
|
|
66
|
+
import { recordPolicyAgentExecution, } from "./policy-agent-metrics.js";
|
|
63
67
|
import { policyGateStreamPartToAgentStep } from "./policy-gate-step.js";
|
|
64
68
|
import { ClarkProfiler } from "./profiler/clark-profiler.js";
|
|
65
69
|
import { buildSummarizeApiUsagePrompt } from "./prompts/summarize-api-usage.js";
|
|
@@ -83,7 +87,7 @@ import { sendUserGenerationStateChannel } from "./state-machine/helpers/peer.js"
|
|
|
83
87
|
import { sendUserMessageChannel, clearPendingToolPermissionRequest, } from "./state-machine/helpers/peer.js";
|
|
84
88
|
import { finalizePromptBillingMetrics } from "./state-machine/helpers/prompt-billing-metrics.js";
|
|
85
89
|
import { paywallStopAdvice } from "./state-machine/helpers/prompt-failure.js";
|
|
86
|
-
import { promptFailureFromStreamAbort, promptFailurePaywall, promptFailurePlatform, promptFailureUserCanceled, recordPromptAttempt, recordPromptEntryRejection, recordPromptOutcome, recordAwaitingUserHandoffDismissed, resolvePromptMetricLabels, resolvePromptOutcomeDedupeKey, resolvePromptRequestDedupeKey, setPromptRequestMetricIdentity, } from "./state-machine/helpers/prompt-request-metrics.js";
|
|
90
|
+
import { promptFailureFromStreamAbort, promptFailurePaywall, promptFailurePlatform, promptFailureUserCanceled, recordBaseCommitResolutionFailed, recordPromptAttempt, recordPromptEntryRejection, recordPromptOutcome, recordAwaitingUserHandoffDismissed, resolvePromptMetricLabels, resolvePromptOutcomeDedupeKey, resolvePromptRequestDedupeKey, setPromptRequestMetricIdentity, } from "./state-machine/helpers/prompt-request-metrics.js";
|
|
87
91
|
import { catchBackgroundPeerDisconnect, StablePeer, } from "./state-machine/helpers/stable-peer.js";
|
|
88
92
|
import { transitionFrom } from "./state-machine/helpers/transition.js";
|
|
89
93
|
import { isTaskAccessibleTo, registerBackgroundTaskTypes, resyncActiveTaskStatusesToPeer, TaskStore, wireTaskStatusPushToPeer, } from "./tasks/index.js";
|
|
@@ -132,6 +136,9 @@ const MAX_POLICY_GATE_STREAM_ERROR_ARRAY_LENGTH = 10;
|
|
|
132
136
|
const MAX_POLICY_GATE_STREAM_ERROR_FIELD_LENGTH = 500;
|
|
133
137
|
const MAX_POLICY_GATE_STREAM_ERROR_OBJECT_DEPTH = 3;
|
|
134
138
|
const MAX_POLICY_GATE_STREAM_ERROR_OBJECT_KEYS = 20;
|
|
139
|
+
const POLICY_GATE_MAX_TOTAL_TIME_MS = 15 * 60 * 1000;
|
|
140
|
+
// AbortError near the budget is treated as timeout (SDK may abort slightly early).
|
|
141
|
+
const POLICY_GATE_TIMEOUT_CLASSIFICATION_SLACK_MS = 5_000;
|
|
135
142
|
const POLICY_GATE_STREAM_ERROR_REDACTED_VALUE = "[redacted]";
|
|
136
143
|
const POLICY_GATE_STREAM_ERROR_SECRET_KEY_PATTERN = /(access[-_]?key|api[-_]?key|authorization|connection[-_]?string|credential|dsn|password|private[-_]?key|secret|token)/i;
|
|
137
144
|
const POLICY_GATE_STREAM_ERROR_SECRET_VALUE_PATTERN_SOURCES = [
|
|
@@ -284,6 +291,17 @@ const NPM_REGISTRY_JWT_WAIT_MS = 5_000;
|
|
|
284
291
|
* via the second-401 branch in `NpmRegistryClient.fetchWithRefresh`.
|
|
285
292
|
*/
|
|
286
293
|
const NPM_REGISTRY_REFRESH_WAIT_MS = 5_000;
|
|
294
|
+
/**
|
|
295
|
+
* Deadline for the chat-store fallback in `resolveBaseCommitId` (2 seconds).
|
|
296
|
+
*
|
|
297
|
+
* baseCommitId is a billing annotation, not something the prompt needs to
|
|
298
|
+
* start -- a slow or hung `chatSessionStore.getLatestCommitId()` call must
|
|
299
|
+
* not delay the turn. A store that never resolves within this window is
|
|
300
|
+
* treated the same as a failure: the counter records
|
|
301
|
+
* `source: "chat_store_timeout"`, a correlated warn is logged, and the
|
|
302
|
+
* prompt proceeds with no baseCommitId rather than waiting indefinitely.
|
|
303
|
+
*/
|
|
304
|
+
const RESOLVE_BASE_COMMIT_ID_CHAT_STORE_TIMEOUT_MS = 2_000;
|
|
287
305
|
export function truncateString(str, maxBytes) {
|
|
288
306
|
// Rough approximation: assume 2 bytes per character
|
|
289
307
|
const maxChars = Math.floor(maxBytes / 2);
|
|
@@ -800,6 +818,9 @@ export class AiService extends TracedEventEmitter {
|
|
|
800
818
|
rpcClient: config.rpcClient,
|
|
801
819
|
logger: config.logger,
|
|
802
820
|
});
|
|
821
|
+
// Lazy closure: this.clark is assigned later in the constructor, so the
|
|
822
|
+
// provider reads the active turn's promptId at append time, not now.
|
|
823
|
+
this.chatSessionStore.setPromptIdProvider(() => this.clark?.context?.promptId);
|
|
803
824
|
if (this.config.tokenManager) {
|
|
804
825
|
this.config.tokenManager.on("tokenUpdated", this.handleTokenUpdated.bind(this));
|
|
805
826
|
}
|
|
@@ -809,6 +830,7 @@ export class AiService extends TracedEventEmitter {
|
|
|
809
830
|
storage: new LocalContextStorageV2(config.appRootDirPath),
|
|
810
831
|
eventStore: new JsonlEventStore(config.appRootDirPath),
|
|
811
832
|
rootDir: config.appRootDirPath,
|
|
833
|
+
adoptLegacyContexts: () => adoptLegacyPerUserContextDirs(config.appRootDirPath),
|
|
812
834
|
getModeState: () => {
|
|
813
835
|
const mode = this.clark?.context?.currentMode;
|
|
814
836
|
if (mode === undefined)
|
|
@@ -1267,10 +1289,11 @@ export class AiService extends TracedEventEmitter {
|
|
|
1267
1289
|
to === ClarkStateNames.AwaitingUser) {
|
|
1268
1290
|
// The turn is over: drop per-turn billing attribution so stray
|
|
1269
1291
|
// LLM calls before the next prompt bill as default generation.
|
|
1270
|
-
if (clark.context.usageMetadata) {
|
|
1292
|
+
if (clark.context.usageMetadata || clark.context.baseCommitId) {
|
|
1271
1293
|
clark.updateContext((context) => ({
|
|
1272
1294
|
...context,
|
|
1273
1295
|
usageMetadata: undefined,
|
|
1296
|
+
baseCommitId: undefined,
|
|
1274
1297
|
}));
|
|
1275
1298
|
}
|
|
1276
1299
|
this.triggerGenerationComplete();
|
|
@@ -1412,8 +1435,80 @@ export class AiService extends TracedEventEmitter {
|
|
|
1412
1435
|
summarizeModel: () => createLLMProvider(this.getLLMProviderSettings(llmConfig), () => this.getProviderInferenceAuthToken(), llmConfig, () => this.getLlmAiAttributes(this.clark), this.clark.context.traceHeaders).modelForTask("summarizeMessages"),
|
|
1413
1436
|
});
|
|
1414
1437
|
}
|
|
1438
|
+
/**
|
|
1439
|
+
* The checkpoint the app is currently based on: the last non-draft commit
|
|
1440
|
+
* seeded by sync (preferred — same key the post-generation upload uses) or
|
|
1441
|
+
* the most recent chat-history commit. Captured once at prompt start
|
|
1442
|
+
* because the checkpoint an eventual accept will create does not exist yet
|
|
1443
|
+
* while the LLM calls run. Never throws: a billing annotation must not be
|
|
1444
|
+
* able to break generation.
|
|
1445
|
+
*/
|
|
1446
|
+
async resolveBaseCommitId(promptId, chatStoreTimeoutMs = RESOLVE_BASE_COMMIT_ID_CHAT_STORE_TIMEOUT_MS) {
|
|
1447
|
+
let preferredSourceFailed = false;
|
|
1448
|
+
try {
|
|
1449
|
+
// Normalize falsy (e.g. "") to undefined so an empty string can't
|
|
1450
|
+
// short-circuit the `??` fallback below and then be silently dropped
|
|
1451
|
+
// by the truthy merge guard downstream in getLlmAiAttributes.
|
|
1452
|
+
const syncCommitId = this._syncContextProvider?.getLastCommitId() || undefined;
|
|
1453
|
+
if (syncCommitId) {
|
|
1454
|
+
return syncCommitId;
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
catch (error) {
|
|
1458
|
+
preferredSourceFailed = true;
|
|
1459
|
+
this.getLogger().warn("[ai-service] Failed to resolve sync base commit id; falling back to chat history", {
|
|
1460
|
+
applicationId: this.config.applicationId,
|
|
1461
|
+
sessionId: this.sessionId,
|
|
1462
|
+
promptId,
|
|
1463
|
+
...getErrorMeta(error),
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
let source = "chat_store";
|
|
1467
|
+
try {
|
|
1468
|
+
const chatStoreCommitId = await this.raceChatStoreLatestCommitId(chatStoreTimeoutMs, () => {
|
|
1469
|
+
source = "chat_store_timeout";
|
|
1470
|
+
});
|
|
1471
|
+
return chatStoreCommitId || undefined;
|
|
1472
|
+
}
|
|
1473
|
+
catch (error) {
|
|
1474
|
+
this.recordAndWarnBaseCommitResolutionFailure(source, preferredSourceFailed, promptId, error);
|
|
1475
|
+
return undefined;
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
/**
|
|
1479
|
+
* Races the chat-store fallback against `timeoutMs` so a hung store
|
|
1480
|
+
* degrades exactly like a failure instead of delaying prompt start. The
|
|
1481
|
+
* timeout rejects (never resolves) so the caller's catch block handles
|
|
1482
|
+
* both outcomes uniformly; `onTimeout` lets the caller distinguish which
|
|
1483
|
+
* branch fired for metrics/logging without a second code path.
|
|
1484
|
+
* `.unref()` keeps the fallback timer from holding the process open.
|
|
1485
|
+
*/
|
|
1486
|
+
raceChatStoreLatestCommitId(timeoutMs, onTimeout) {
|
|
1487
|
+
let timeoutHandle;
|
|
1488
|
+
const timeout = new Promise((_, reject) => {
|
|
1489
|
+
timeoutHandle = setTimeout(() => {
|
|
1490
|
+
onTimeout();
|
|
1491
|
+
reject(new Error(`chatSessionStore.getLatestCommitId timed out after ${timeoutMs}ms`));
|
|
1492
|
+
}, timeoutMs);
|
|
1493
|
+
timeoutHandle.unref?.();
|
|
1494
|
+
});
|
|
1495
|
+
return Promise.race([
|
|
1496
|
+
this.chatSessionStore.getLatestCommitId(),
|
|
1497
|
+
timeout,
|
|
1498
|
+
]).finally(() => clearTimeout(timeoutHandle));
|
|
1499
|
+
}
|
|
1500
|
+
recordAndWarnBaseCommitResolutionFailure(source, preferredSourceFailed, promptId, error) {
|
|
1501
|
+
recordBaseCommitResolutionFailed({ source, preferredSourceFailed });
|
|
1502
|
+
this.getLogger().warn("[ai-service] Failed to resolve base commit id for usage metadata", {
|
|
1503
|
+
applicationId: this.config.applicationId,
|
|
1504
|
+
sessionId: this.sessionId,
|
|
1505
|
+
promptId,
|
|
1506
|
+
...getErrorMeta(error),
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1415
1509
|
getLlmAiAttributes(clark) {
|
|
1416
1510
|
const usageMetadata = clark.context.usageMetadata ?? APP_CLARK_GENERATION_METADATA;
|
|
1511
|
+
const baseCommitId = clark.context.baseCommitId;
|
|
1417
1512
|
return {
|
|
1418
1513
|
prompt_id: clark.context.promptId,
|
|
1419
1514
|
chatmessage_id: clark.context.chatmessageId,
|
|
@@ -1422,7 +1517,9 @@ export class AiService extends TracedEventEmitter {
|
|
|
1422
1517
|
// Note: retry_count for LLM retries is added by orchestrator via providerOptions
|
|
1423
1518
|
mode: clark.context.currentMode === AiMode.PLAN ? "plan" : "build",
|
|
1424
1519
|
plan_size: clark.context.planSize,
|
|
1425
|
-
metadata:
|
|
1520
|
+
metadata: baseCommitId
|
|
1521
|
+
? { ...usageMetadata, baseCommitId }
|
|
1522
|
+
: usageMetadata,
|
|
1426
1523
|
};
|
|
1427
1524
|
}
|
|
1428
1525
|
/**
|
|
@@ -1626,9 +1723,11 @@ export class AiService extends TracedEventEmitter {
|
|
|
1626
1723
|
retryMode,
|
|
1627
1724
|
triggeredBy,
|
|
1628
1725
|
};
|
|
1726
|
+
const baseCommitId = await this.resolveBaseCommitId(promptId);
|
|
1629
1727
|
this.clark.updateContext((context) => ({
|
|
1630
1728
|
...context,
|
|
1631
1729
|
promptId,
|
|
1730
|
+
baseCommitId,
|
|
1632
1731
|
usageMetadata: migrationUsageMetadata,
|
|
1633
1732
|
llmConfig: migrationLlmConfig,
|
|
1634
1733
|
}));
|
|
@@ -2072,11 +2171,13 @@ export class AiService extends TracedEventEmitter {
|
|
|
2072
2171
|
dedupeKeySource: request.requestId ? "requestId" : "ephemeral",
|
|
2073
2172
|
applicationId: this.config.applicationId,
|
|
2074
2173
|
});
|
|
2174
|
+
const baseCommitId = await this.resolveBaseCommitId(promptId);
|
|
2075
2175
|
this.clark.updateContext((context) => {
|
|
2076
2176
|
return {
|
|
2077
2177
|
...context,
|
|
2078
2178
|
promptId,
|
|
2079
2179
|
promptOutcomeDedupeKey,
|
|
2180
|
+
baseCommitId,
|
|
2080
2181
|
isInterruptAbort: undefined,
|
|
2081
2182
|
userInitiatedPromptAbort: undefined,
|
|
2082
2183
|
// Third-party import turns are billed as app_migration; everything
|
|
@@ -2646,6 +2747,7 @@ export class AiService extends TracedEventEmitter {
|
|
|
2646
2747
|
promptId: undefined,
|
|
2647
2748
|
promptOutcomeDedupeKey: undefined,
|
|
2648
2749
|
usageMetadata: undefined,
|
|
2750
|
+
baseCommitId: undefined,
|
|
2649
2751
|
awaitingUserHandoffPromptId: undefined,
|
|
2650
2752
|
awaitingUserHandoffRecorded: undefined,
|
|
2651
2753
|
}));
|
|
@@ -3645,25 +3747,11 @@ export class AiService extends TracedEventEmitter {
|
|
|
3645
3747
|
async handlePolicyGateExecution(request) {
|
|
3646
3748
|
const logger = this.getLogger();
|
|
3647
3749
|
logger.info(`[ai-service] Policy gate execution requested for app=${request.applicationId}`);
|
|
3648
|
-
const requestLlmConfig = request.llmConfig;
|
|
3649
|
-
const contextLlmConfig = this.clark.context.llmConfig;
|
|
3650
|
-
const llmConfig = requestLlmConfig ?? contextLlmConfig;
|
|
3651
|
-
const llmProvider = createLLMProvider(this.getLLMProviderSettings(llmConfig), () => this.requireProvidedInferenceJwt(request.jwt, "policy gate"), llmConfig, () => ({
|
|
3652
|
-
ai_agent_run_id: request.aiAgentRunId,
|
|
3653
|
-
application_id: request.applicationId,
|
|
3654
|
-
metadata: POLICY_GATE_USAGE_METADATA,
|
|
3655
|
-
session_id: this.sessionId,
|
|
3656
|
-
trigger_type: "policy_gate",
|
|
3657
|
-
}));
|
|
3658
|
-
const { model } = llmProvider.modelForTask("generateBroadEdit");
|
|
3659
|
-
const availableIntegrations = await this.integrationStore.availableIntegrations();
|
|
3660
|
-
const integrations = availableIntegrations.map((i) => ({
|
|
3661
|
-
name: i.name,
|
|
3662
|
-
pluginType: i.plugin?.id ?? "unknown",
|
|
3663
|
-
}));
|
|
3664
|
-
const appContext = { integrations };
|
|
3665
|
-
const systemPrompt = composeSecurityAgentPrompt(appContext, request.adminPrompt);
|
|
3666
3750
|
const startedAt = Date.now();
|
|
3751
|
+
const policyRunLogContext = {
|
|
3752
|
+
applicationId: request.applicationId,
|
|
3753
|
+
reviewPolicyId: request.reviewPolicyId,
|
|
3754
|
+
};
|
|
3667
3755
|
const toolCallCounts = {};
|
|
3668
3756
|
const filePathsRead = new Set();
|
|
3669
3757
|
const filePathsListed = new Set();
|
|
@@ -3671,15 +3759,31 @@ export class AiService extends TracedEventEmitter {
|
|
|
3671
3759
|
const grepPatterns = [];
|
|
3672
3760
|
const grepMatchCounts = [];
|
|
3673
3761
|
const grepPatternByToolCallId = new Map();
|
|
3762
|
+
const lsPathByToolCallId = new Map();
|
|
3674
3763
|
const streamErrors = [];
|
|
3675
3764
|
const streamPartTypeCounts = {};
|
|
3765
|
+
let stepCount = 0;
|
|
3676
3766
|
let textDeltaChars = 0;
|
|
3677
|
-
|
|
3678
|
-
|
|
3767
|
+
let modelName;
|
|
3768
|
+
let promptHash;
|
|
3679
3769
|
const scanRoot = typeof this.appShell.appRootDirPath === "string"
|
|
3680
3770
|
? this.appShell.appRootDirPath
|
|
3681
3771
|
: undefined;
|
|
3682
3772
|
let scanResult;
|
|
3773
|
+
let metricsRecorded = false;
|
|
3774
|
+
const totalToolCalls = () => Object.values(toolCallCounts).reduce((sum, count) => sum + count, 0);
|
|
3775
|
+
const recordExecutionMetrics = (outcome) => {
|
|
3776
|
+
if (metricsRecorded) {
|
|
3777
|
+
return;
|
|
3778
|
+
}
|
|
3779
|
+
metricsRecorded = true;
|
|
3780
|
+
recordPolicyAgentExecution({
|
|
3781
|
+
durationMs: Date.now() - startedAt,
|
|
3782
|
+
outcome,
|
|
3783
|
+
stepCount,
|
|
3784
|
+
toolCallCount: totalToolCalls(),
|
|
3785
|
+
});
|
|
3786
|
+
};
|
|
3683
3787
|
const buildRuntimeMetadata = (finalFailureReason) => ({
|
|
3684
3788
|
diagnosticsVersion: 2,
|
|
3685
3789
|
durationMs: Date.now() - startedAt,
|
|
@@ -3694,156 +3798,260 @@ export class AiService extends TracedEventEmitter {
|
|
|
3694
3798
|
reportToolCalled: Boolean(scanResult),
|
|
3695
3799
|
scanComplete: scanResult?.scanComplete === true,
|
|
3696
3800
|
scanRoot,
|
|
3801
|
+
stepCount,
|
|
3697
3802
|
streamErrors,
|
|
3698
3803
|
streamPartTypeCounts,
|
|
3699
3804
|
textDeltaChars,
|
|
3700
3805
|
toolCallCounts,
|
|
3701
3806
|
});
|
|
3702
|
-
const failPolicyGateExecution = (message) => {
|
|
3807
|
+
const failPolicyGateExecution = (message, outcome) => {
|
|
3808
|
+
recordExecutionMetrics(outcome);
|
|
3703
3809
|
const error = new Error(message);
|
|
3704
3810
|
error.runtimeMetadataJson = buildRuntimeMetadata(message);
|
|
3705
3811
|
throw error;
|
|
3706
3812
|
};
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
}
|
|
3735
|
-
const
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3813
|
+
try {
|
|
3814
|
+
const requestLlmConfig = request.llmConfig;
|
|
3815
|
+
const contextLlmConfig = this.clark.context.llmConfig;
|
|
3816
|
+
const llmConfig = requestLlmConfig ?? contextLlmConfig;
|
|
3817
|
+
const llmProvider = createLLMProvider(this.getLLMProviderSettings(llmConfig), () => this.requireProvidedInferenceJwt(request.jwt, "policy gate"), llmConfig, () => ({
|
|
3818
|
+
ai_agent_run_id: request.aiAgentRunId,
|
|
3819
|
+
application_id: request.applicationId,
|
|
3820
|
+
metadata: POLICY_GATE_USAGE_METADATA,
|
|
3821
|
+
session_id: this.sessionId,
|
|
3822
|
+
trigger_type: "policy_gate",
|
|
3823
|
+
}));
|
|
3824
|
+
const { model } = llmProvider.modelForTask("generateBroadEdit");
|
|
3825
|
+
modelName = typeof model === "string" ? model : model.modelId;
|
|
3826
|
+
const availableIntegrations = await this.integrationStore.availableIntegrations();
|
|
3827
|
+
const integrations = availableIntegrations.map((i) => ({
|
|
3828
|
+
name: i.name,
|
|
3829
|
+
pluginType: i.plugin?.id ?? "unknown",
|
|
3830
|
+
}));
|
|
3831
|
+
const appContext = { integrations };
|
|
3832
|
+
const systemPrompt = composeSecurityAgentPrompt(appContext, request.adminPrompt);
|
|
3833
|
+
promptHash = createHash("sha256").update(systemPrompt).digest("hex");
|
|
3834
|
+
const scanToolsDeps = {
|
|
3835
|
+
appShell: this.appShell,
|
|
3836
|
+
features: this.config.features ?? {},
|
|
3837
|
+
integrationStore: this.integrationStore,
|
|
3838
|
+
llmClient: this.llmClient,
|
|
3839
|
+
llmProvider: llmProvider,
|
|
3840
|
+
};
|
|
3841
|
+
const tools = await buildSecurityScanTools(this.clark, scanToolsDeps);
|
|
3842
|
+
const result = aiStreamText({
|
|
3843
|
+
messages: [
|
|
3844
|
+
{ content: systemPrompt, role: "system" },
|
|
3845
|
+
{
|
|
3846
|
+
content: "Begin your security review of this application now.",
|
|
3847
|
+
role: "user",
|
|
3848
|
+
},
|
|
3849
|
+
],
|
|
3850
|
+
model,
|
|
3851
|
+
tools,
|
|
3852
|
+
stopWhen: hasToolCall("reportReviewRun"),
|
|
3853
|
+
timeout: { totalMs: POLICY_GATE_MAX_TOTAL_TIME_MS },
|
|
3747
3854
|
});
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
const
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
(streamPartTypeCounts[partType] ?? 0) + 1;
|
|
3756
|
-
if (partType === "error") {
|
|
3757
|
-
const streamError = serializePolicyGateStreamError(part.error, part);
|
|
3758
|
-
if (streamErrors.length < MAX_POLICY_GATE_STREAM_ERRORS) {
|
|
3759
|
-
streamErrors.push(streamError);
|
|
3855
|
+
// Best-effort: surface the agent's latest tool/reasoning step in the editor
|
|
3856
|
+
// so a running policy row shows live activity instead of an opaque spinner.
|
|
3857
|
+
// Only meaningful when an editor session is connected; never blocks the run.
|
|
3858
|
+
const peer = this.clark.context.peer;
|
|
3859
|
+
const emitAgentStep = (part) => {
|
|
3860
|
+
if (!this.hasConnectedPeer() || !peer) {
|
|
3861
|
+
return;
|
|
3760
3862
|
}
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
const
|
|
3777
|
-
part
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3863
|
+
const step = policyGateStreamPartToAgentStep(part);
|
|
3864
|
+
if (!step) {
|
|
3865
|
+
return;
|
|
3866
|
+
}
|
|
3867
|
+
void peer.call
|
|
3868
|
+
.aiPushPolicyAgentStep({
|
|
3869
|
+
applicationId: request.applicationId,
|
|
3870
|
+
policyId: request.reviewPolicyId,
|
|
3871
|
+
step,
|
|
3872
|
+
})
|
|
3873
|
+
.catch((error) => {
|
|
3874
|
+
logger.warn("[ai-service] Failed to push policy agent step", getErrorMeta(error));
|
|
3875
|
+
});
|
|
3876
|
+
};
|
|
3877
|
+
try {
|
|
3878
|
+
for await (const part of result.fullStream) {
|
|
3879
|
+
emitAgentStep(part);
|
|
3880
|
+
const partType = typeof part.type === "string"
|
|
3881
|
+
? part.type
|
|
3882
|
+
: "unknown";
|
|
3883
|
+
streamPartTypeCounts[partType] =
|
|
3884
|
+
(streamPartTypeCounts[partType] ?? 0) + 1;
|
|
3885
|
+
if (partType === "error") {
|
|
3886
|
+
const streamError = serializePolicyGateStreamError(part.error, part);
|
|
3887
|
+
if (streamErrors.length < MAX_POLICY_GATE_STREAM_ERRORS) {
|
|
3888
|
+
streamErrors.push(streamError);
|
|
3889
|
+
}
|
|
3890
|
+
logger.warn("[ai-service] Policy gate stream error", {
|
|
3891
|
+
...policyRunLogContext,
|
|
3892
|
+
streamError,
|
|
3893
|
+
});
|
|
3783
3894
|
}
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
grepPatternByToolCallId.set(toolCallId, record.pattern);
|
|
3895
|
+
const textDelta = part.textDelta ??
|
|
3896
|
+
part.text ??
|
|
3897
|
+
part.delta;
|
|
3898
|
+
if (typeof textDelta === "string") {
|
|
3899
|
+
textDeltaChars += textDelta.length;
|
|
3790
3900
|
}
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3901
|
+
if (partType === "finish-step") {
|
|
3902
|
+
stepCount += 1;
|
|
3903
|
+
}
|
|
3904
|
+
const finishReason = part
|
|
3905
|
+
.finishReason;
|
|
3906
|
+
if (typeof finishReason === "string") {
|
|
3907
|
+
finishReasons.push(finishReason);
|
|
3908
|
+
}
|
|
3909
|
+
if (part.type === "tool-call") {
|
|
3910
|
+
toolCallCounts[part.toolName] =
|
|
3911
|
+
(toolCallCounts[part.toolName] ?? 0) + 1;
|
|
3912
|
+
const input = part.input ??
|
|
3913
|
+
part.args;
|
|
3914
|
+
if (input && typeof input === "object") {
|
|
3915
|
+
const record = input;
|
|
3916
|
+
if (part.toolName === "build_readFile" &&
|
|
3917
|
+
typeof record.filePath === "string") {
|
|
3918
|
+
filePathsRead.add(record.filePath);
|
|
3919
|
+
}
|
|
3920
|
+
if (part.toolName === "grep" &&
|
|
3921
|
+
typeof record.pattern === "string") {
|
|
3922
|
+
grepPatterns.push(record.pattern);
|
|
3923
|
+
const toolCallId = typeof part.toolCallId ===
|
|
3924
|
+
"string"
|
|
3925
|
+
? part.toolCallId
|
|
3926
|
+
: `grep-${grepPatterns.length}`;
|
|
3927
|
+
grepPatternByToolCallId.set(toolCallId, record.pattern);
|
|
3928
|
+
}
|
|
3929
|
+
if (part.toolName === "ls") {
|
|
3930
|
+
const toolCallId = typeof part.toolCallId ===
|
|
3931
|
+
"string"
|
|
3932
|
+
? part.toolCallId
|
|
3933
|
+
: `ls-${lsPathByToolCallId.size + 1}`;
|
|
3934
|
+
lsPathByToolCallId.set(toolCallId, typeof record.path === "string" ? record.path : ".");
|
|
3803
3935
|
}
|
|
3804
3936
|
}
|
|
3805
3937
|
}
|
|
3806
|
-
if (part.
|
|
3807
|
-
const
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3938
|
+
if (part.type === "tool-result") {
|
|
3939
|
+
const output = part.output ??
|
|
3940
|
+
part.result;
|
|
3941
|
+
if (output && typeof output === "object") {
|
|
3942
|
+
const record = output;
|
|
3943
|
+
if ((part.toolName === "build_listFiles" ||
|
|
3944
|
+
part.toolName === "glob") &&
|
|
3945
|
+
Array.isArray(record.files)) {
|
|
3946
|
+
for (const file of record.files) {
|
|
3947
|
+
if (typeof file === "string") {
|
|
3948
|
+
filePathsListed.add(file);
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3951
|
+
}
|
|
3952
|
+
if (part.toolName === "ls" && typeof record.output === "string") {
|
|
3953
|
+
const toolCallId = typeof part.toolCallId ===
|
|
3954
|
+
"string"
|
|
3955
|
+
? part.toolCallId
|
|
3956
|
+
: undefined;
|
|
3957
|
+
const dirPath = (toolCallId
|
|
3958
|
+
? lsPathByToolCallId.get(toolCallId)
|
|
3959
|
+
: undefined) ?? ".";
|
|
3960
|
+
for (const line of record.output.split("\n")) {
|
|
3961
|
+
const trimmed = line.trim();
|
|
3962
|
+
if (!trimmed || trimmed.startsWith("total")) {
|
|
3963
|
+
continue;
|
|
3964
|
+
}
|
|
3965
|
+
const name = trimmed.split(/\s+/).at(-1);
|
|
3966
|
+
if (!name) {
|
|
3967
|
+
continue;
|
|
3968
|
+
}
|
|
3969
|
+
filePathsListed.add(dirPath === "."
|
|
3970
|
+
? name
|
|
3971
|
+
: `${dirPath.replace(/\/$/, "")}/${name}`);
|
|
3972
|
+
}
|
|
3973
|
+
}
|
|
3974
|
+
if (part.toolName === "grep" &&
|
|
3975
|
+
typeof record.matches === "number") {
|
|
3976
|
+
const toolCallId = typeof part.toolCallId ===
|
|
3977
|
+
"string"
|
|
3978
|
+
? part.toolCallId
|
|
3979
|
+
: `grep-${grepMatchCounts.length + 1}`;
|
|
3980
|
+
grepMatchCounts.push({
|
|
3981
|
+
matches: record.matches,
|
|
3982
|
+
pattern: grepPatternByToolCallId.get(toolCallId) ??
|
|
3983
|
+
grepPatterns[grepPatterns.length - 1] ??
|
|
3984
|
+
"",
|
|
3985
|
+
});
|
|
3986
|
+
}
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3989
|
+
if (part.type === "tool-call" &&
|
|
3990
|
+
part.toolName === "reportReviewRun") {
|
|
3991
|
+
const input = part.input ??
|
|
3992
|
+
part.args;
|
|
3993
|
+
const parsedInput = securityScanResultSchema.safeParse(input);
|
|
3994
|
+
if (!parsedInput.success) {
|
|
3995
|
+
logger.error("[ai-service] Invalid policy gate reportReviewRun input", {
|
|
3996
|
+
...policyRunLogContext,
|
|
3997
|
+
...getErrorMeta(parsedInput.error),
|
|
3998
|
+
});
|
|
3999
|
+
failPolicyGateExecution("Invalid policy gate reportReviewRun input", "invalid_report");
|
|
4000
|
+
}
|
|
4001
|
+
scanResult = parsedInput.data;
|
|
3816
4002
|
}
|
|
3817
4003
|
}
|
|
3818
4004
|
}
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
4005
|
+
catch (error) {
|
|
4006
|
+
if (error instanceof Error &&
|
|
4007
|
+
(error.name === "AbortError" || error.name === "TimeoutError")) {
|
|
4008
|
+
const elapsedMs = Date.now() - startedAt;
|
|
4009
|
+
const isBudgetTimeout = error.name === "TimeoutError" ||
|
|
4010
|
+
elapsedMs + POLICY_GATE_TIMEOUT_CLASSIFICATION_SLACK_MS >=
|
|
4011
|
+
POLICY_GATE_MAX_TOTAL_TIME_MS;
|
|
4012
|
+
if (isBudgetTimeout) {
|
|
4013
|
+
logger.warn("[ai-service] Policy gate agent timed out", {
|
|
4014
|
+
...policyRunLogContext,
|
|
4015
|
+
...getErrorMeta(error),
|
|
4016
|
+
elapsedMs,
|
|
4017
|
+
});
|
|
4018
|
+
failPolicyGateExecution("Policy gate agent timed out", "timeout");
|
|
4019
|
+
}
|
|
4020
|
+
logger.warn("[ai-service] Policy gate agent aborted", {
|
|
4021
|
+
...policyRunLogContext,
|
|
4022
|
+
...getErrorMeta(error),
|
|
4023
|
+
elapsedMs,
|
|
4024
|
+
});
|
|
4025
|
+
failPolicyGateExecution("Policy gate agent aborted", "aborted");
|
|
3826
4026
|
}
|
|
3827
|
-
|
|
4027
|
+
throw error;
|
|
3828
4028
|
}
|
|
4029
|
+
if (!scanResult) {
|
|
4030
|
+
logger.warn("[ai-service] Policy gate agent did not call reportReviewRun", policyRunLogContext);
|
|
4031
|
+
failPolicyGateExecution("Policy gate agent did not complete a report", "missing_report");
|
|
4032
|
+
}
|
|
4033
|
+
const completedScanResult = scanResult;
|
|
4034
|
+
if (!completedScanResult.scanComplete) {
|
|
4035
|
+
logger.warn("[ai-service] Policy gate agent reported an incomplete scan", {
|
|
4036
|
+
...policyRunLogContext,
|
|
4037
|
+
findingCount: completedScanResult.findings.length,
|
|
4038
|
+
scannedFiles: completedScanResult.scannedFiles,
|
|
4039
|
+
});
|
|
4040
|
+
failPolicyGateExecution("Policy gate agent did not complete a report", "incomplete_report");
|
|
4041
|
+
}
|
|
4042
|
+
logger.info(`[ai-service] Policy gate execution complete: ${completedScanResult.findings.length} findings`);
|
|
4043
|
+
recordExecutionMetrics("success");
|
|
4044
|
+
return {
|
|
4045
|
+
...completedScanResult,
|
|
4046
|
+
runtimeMetadataJson: buildRuntimeMetadata(),
|
|
4047
|
+
};
|
|
3829
4048
|
}
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
if (!completedScanResult.scanComplete) {
|
|
3836
|
-
logger.warn("[ai-service] Policy gate agent reported an incomplete scan", {
|
|
3837
|
-
findingCount: completedScanResult.findings.length,
|
|
3838
|
-
scannedFiles: completedScanResult.scannedFiles,
|
|
3839
|
-
});
|
|
3840
|
-
failPolicyGateExecution("Policy gate agent did not complete a report");
|
|
4049
|
+
catch (error) {
|
|
4050
|
+
if (!metricsRecorded) {
|
|
4051
|
+
recordExecutionMetrics("error");
|
|
4052
|
+
}
|
|
4053
|
+
throw error;
|
|
3841
4054
|
}
|
|
3842
|
-
logger.info(`[ai-service] Policy gate execution complete: ${completedScanResult.findings.length} findings`);
|
|
3843
|
-
return {
|
|
3844
|
-
...completedScanResult,
|
|
3845
|
-
runtimeMetadataJson: buildRuntimeMetadata(),
|
|
3846
|
-
};
|
|
3847
4055
|
}
|
|
3848
4056
|
handleUserChange(change) {
|
|
3849
4057
|
this.getLogger().info(`[ai-service] User changed: ${change.type}`, change);
|
|
@@ -3867,7 +4075,7 @@ export class AiService extends TracedEventEmitter {
|
|
|
3867
4075
|
// instead of the one the user just cleared (APPS-4407).
|
|
3868
4076
|
if (contextId) {
|
|
3869
4077
|
await this.contextManagerV2.persistEmptyContext(contextId);
|
|
3870
|
-
await this.uploadEmptyContextToBucketeer(
|
|
4078
|
+
await this.uploadEmptyContextToBucketeer(commitIdForUpload);
|
|
3871
4079
|
}
|
|
3872
4080
|
}
|
|
3873
4081
|
catch (error) {
|
|
@@ -3898,7 +4106,7 @@ export class AiService extends TracedEventEmitter {
|
|
|
3898
4106
|
* commitId is captured before newChat() clears the session.
|
|
3899
4107
|
* Failures are logged but do not fail clear chat.
|
|
3900
4108
|
*/
|
|
3901
|
-
async uploadEmptyContextToBucketeer(
|
|
4109
|
+
async uploadEmptyContextToBucketeer(commitId) {
|
|
3902
4110
|
const jwt = this.getInferenceJwt();
|
|
3903
4111
|
const branchName = this._syncContextProvider?.getBranchName();
|
|
3904
4112
|
if (!jwt || !commitId || !branchName) {
|
|
@@ -3910,7 +4118,6 @@ export class AiService extends TracedEventEmitter {
|
|
|
3910
4118
|
commitId,
|
|
3911
4119
|
branchName,
|
|
3912
4120
|
appRootDirPath: this.config.appRootDirPath,
|
|
3913
|
-
userId,
|
|
3914
4121
|
useServerUpload: this.isAiFileUploadEnabled,
|
|
3915
4122
|
superblocksBaseUrl: this.config.superblocksBaseUrl,
|
|
3916
4123
|
logger: this.getLogger(),
|
|
@@ -4018,44 +4225,104 @@ export class AiService extends TracedEventEmitter {
|
|
|
4018
4225
|
this.getLogger().warn(`[handleRestoreToCommit] No JWT available, cannot restore or truncate context`);
|
|
4019
4226
|
return;
|
|
4020
4227
|
}
|
|
4021
|
-
// 1.
|
|
4022
|
-
|
|
4228
|
+
// 1. Roll the conversation back to the checkpoint. The local event log is
|
|
4229
|
+
// the source of truth (see restoreConversationToCheckpoint); the remote
|
|
4230
|
+
// checkpoint archive is only a fallback for a recycled pod, because a
|
|
4231
|
+
// post-checkpoint flush may have overwritten it. The orchestrator returns
|
|
4232
|
+
// an outcome and does NOT touch the metric — this method is the single
|
|
4233
|
+
// place restore outcomes are recorded, so every exit is counted exactly
|
|
4234
|
+
// once.
|
|
4235
|
+
let outcome;
|
|
4023
4236
|
try {
|
|
4024
4237
|
const contextId = getContextId(this.clark, this.config);
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
commitId,
|
|
4028
|
-
branchName,
|
|
4238
|
+
outcome = await restoreConversationToCheckpoint({
|
|
4239
|
+
contextManagerV2: this.contextManagerV2,
|
|
4029
4240
|
contextId,
|
|
4030
|
-
|
|
4031
|
-
superblocksBaseUrl: this.config.superblocksBaseUrl,
|
|
4241
|
+
sinceTimestamp,
|
|
4032
4242
|
logger: this.getLogger(),
|
|
4033
|
-
|
|
4034
|
-
|
|
4243
|
+
coldRestore: async () => {
|
|
4244
|
+
const result = await restoreContextFromCheckpoint({
|
|
4245
|
+
applicationId,
|
|
4246
|
+
commitId,
|
|
4247
|
+
branchName,
|
|
4248
|
+
contextId,
|
|
4249
|
+
appRootDirPath: this.config.appRootDirPath,
|
|
4250
|
+
superblocksBaseUrl: this.config.superblocksBaseUrl,
|
|
4251
|
+
logger: this.getLogger(),
|
|
4252
|
+
jwt,
|
|
4253
|
+
contextManagerV2: this.contextManagerV2,
|
|
4254
|
+
});
|
|
4255
|
+
if (!result.success) {
|
|
4256
|
+
const reason = result.savedFiles.length > 0
|
|
4257
|
+
? "main.json not found in checkpoint"
|
|
4258
|
+
: "no checkpoint found";
|
|
4259
|
+
this.getLogger().info(`[handleRestoreToCommit] Context restore failed: ${reason} for commitId=${commitId}, preserving chat messages`);
|
|
4260
|
+
}
|
|
4261
|
+
return result.success;
|
|
4262
|
+
},
|
|
4263
|
+
republish: async () => {
|
|
4264
|
+
await uploadContextToBucketeer({
|
|
4265
|
+
applicationId,
|
|
4266
|
+
commitId,
|
|
4267
|
+
branchName,
|
|
4268
|
+
appRootDirPath: this.config.appRootDirPath,
|
|
4269
|
+
useServerUpload: this.isAiFileUploadEnabled,
|
|
4270
|
+
superblocksBaseUrl: this.config.superblocksBaseUrl,
|
|
4271
|
+
logger: this.getLogger(),
|
|
4272
|
+
jwt,
|
|
4273
|
+
// The heal may have deleted the legacy snapshot, leaving an
|
|
4274
|
+
// events-only archive that IS the rolled-back state; upload it so the
|
|
4275
|
+
// remote /context/latest is healed (the download rebuilds the record
|
|
4276
|
+
// from the truncated event log) instead of staying poisoned.
|
|
4277
|
+
allowEventsOnlyArchive: true,
|
|
4278
|
+
});
|
|
4279
|
+
},
|
|
4035
4280
|
});
|
|
4036
|
-
if (result.success) {
|
|
4037
|
-
restored = true;
|
|
4038
|
-
}
|
|
4039
|
-
else {
|
|
4040
|
-
const reason = result.savedFiles.length > 0
|
|
4041
|
-
? "main.json not found in checkpoint"
|
|
4042
|
-
: "no checkpoint found";
|
|
4043
|
-
this.getLogger().info(`[handleRestoreToCommit] Context restore failed: ${reason} for commitId=${commitId}, preserving chat messages`);
|
|
4044
|
-
}
|
|
4045
4281
|
}
|
|
4046
4282
|
catch (error) {
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4283
|
+
// The orchestrator itself threw (e.g. a cold restore's scoped-token or
|
|
4284
|
+
// network error propagating out of coldRestore). Count it so the restore
|
|
4285
|
+
// metric reflects every attempt, not just the ones that return normally.
|
|
4286
|
+
this.getLogger().error(`[handleRestoreToCommit] Error restoring context from checkpoint, preserving chat messages`, getErrorMeta(error));
|
|
4287
|
+
recordCheckpointRestore("aborted");
|
|
4050
4288
|
return;
|
|
4051
4289
|
}
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4290
|
+
if (outcome !== "rolled_back") {
|
|
4291
|
+
// Context was NOT rolled back (aborted / unhealed / no_checkpoint). Record
|
|
4292
|
+
// the outcome and leave the chat intact so it keeps matching the context.
|
|
4293
|
+
recordCheckpointRestore(outcome);
|
|
4294
|
+
this.getLogger().info(`[handleRestoreToCommit] No context restored (${outcome}) for commitId=${commitId}; leaving chat intact`, { applicationId, branchName });
|
|
4295
|
+
return;
|
|
4056
4296
|
}
|
|
4057
|
-
|
|
4058
|
-
|
|
4297
|
+
// 2. Context is rolled back — truncate the server chat to match. This is a
|
|
4298
|
+
// cross-service RPC and is NOT transactional with the context rollback
|
|
4299
|
+
// above, so a failure here leaves the context rolled back while the chat
|
|
4300
|
+
// still shows the reverted turns (a chat-ahead-of-context divergence).
|
|
4301
|
+
// Retry a few times to ride out a transient blip; if it still fails,
|
|
4302
|
+
// surface it at error level with a metric so the divergence is alertable,
|
|
4303
|
+
// not buried.
|
|
4304
|
+
// truncateAfterTimestamp is idempotent (same cutoff → same result), so the
|
|
4305
|
+
// retries are safe.
|
|
4306
|
+
const CHAT_TRUNCATE_ATTEMPTS = 3;
|
|
4307
|
+
for (let attempt = 1; attempt <= CHAT_TRUNCATE_ATTEMPTS; attempt++) {
|
|
4308
|
+
try {
|
|
4309
|
+
await this.chatSessionStore.truncateAfterTimestamp(sinceTimestamp);
|
|
4310
|
+
this.getLogger().info(`[handleRestoreToCommit] Successfully restored context and truncated chat for commitId=${commitId}`);
|
|
4311
|
+
recordCheckpointRestore("restored");
|
|
4312
|
+
return;
|
|
4313
|
+
}
|
|
4314
|
+
catch (error) {
|
|
4315
|
+
if (attempt < CHAT_TRUNCATE_ATTEMPTS) {
|
|
4316
|
+
this.getLogger().warn(`[handleRestoreToCommit] Chat truncation attempt ${attempt}/${CHAT_TRUNCATE_ATTEMPTS} failed for commitId=${commitId}, retrying`, getErrorMeta(error));
|
|
4317
|
+
await new Promise((resolve) => setTimeout(resolve, 200 * attempt));
|
|
4318
|
+
continue;
|
|
4319
|
+
}
|
|
4320
|
+
// Exhausted retries: context is rolled back but the chat is not. Log at
|
|
4321
|
+
// error (a real, if less-harmful, chat/context divergence) and count it.
|
|
4322
|
+
this.getLogger().error(`[handleRestoreToCommit] Context rolled back but chat truncation failed after ${CHAT_TRUNCATE_ATTEMPTS} attempts for commitId=${commitId} ` +
|
|
4323
|
+
`(applicationId=${applicationId}, branchName=${branchName}); chat may show reverted turns until the next turn resyncs it`, getErrorMeta(error));
|
|
4324
|
+
recordCheckpointRestore("chat_truncate_failed");
|
|
4325
|
+
}
|
|
4059
4326
|
}
|
|
4060
4327
|
}
|
|
4061
4328
|
async handleApiUpdate(update) {
|