@superblocksteam/vite-plugin-file-sync 2.0.140 → 2.0.141-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.
Files changed (156) hide show
  1. package/dist/ai-service/agent/prompts/build-base-system-prompt.js +1 -0
  2. package/dist/ai-service/agent/prompts/build-base-system-prompt.js.map +1 -1
  3. package/dist/ai-service/agent/tools/apis/api-testing-state.d.ts +1 -0
  4. package/dist/ai-service/agent/tools/apis/api-testing-state.d.ts.map +1 -1
  5. package/dist/ai-service/agent/tools/apis/api-testing-state.js +16 -0
  6. package/dist/ai-service/agent/tools/apis/api-testing-state.js.map +1 -1
  7. package/dist/ai-service/agent/tools/apis/get-api-docs.d.ts +1 -1
  8. package/dist/ai-service/agent/tools/apis/test-api.d.ts +1 -1
  9. package/dist/ai-service/agent/tools/apis/test-api.d.ts.map +1 -1
  10. package/dist/ai-service/agent/tools/apis/test-api.js +13 -7
  11. package/dist/ai-service/agent/tools/apis/test-api.js.map +1 -1
  12. package/dist/ai-service/agent/tools/build-copy-directory.d.ts +1 -1
  13. package/dist/ai-service/agent/tools/build-copy-file.d.ts +1 -1
  14. package/dist/ai-service/agent/tools/build-copy-utils.d.ts +8 -8
  15. package/dist/ai-service/agent/tools/build-delete-file.d.ts +1 -1
  16. package/dist/ai-service/agent/tools/build-finalize.d.ts.map +1 -1
  17. package/dist/ai-service/agent/tools/build-finalize.js +1 -2
  18. package/dist/ai-service/agent/tools/build-finalize.js.map +1 -1
  19. package/dist/ai-service/agent/tools/build-manage-checklist.d.ts +4 -4
  20. package/dist/ai-service/agent/tools/databases/dev-database-tasks.d.ts +4 -4
  21. package/dist/ai-service/agent/tools/get-logs.d.ts +3 -3
  22. package/dist/ai-service/agent/tools/integrations/execute-request.d.ts +9 -9
  23. package/dist/ai-service/agent/tools2/tools/end-test-run.d.ts +2 -2
  24. package/dist/ai-service/agent/tools2/tools/exit-plan-mode.d.ts.map +1 -1
  25. package/dist/ai-service/agent/tools2/tools/exit-plan-mode.js +93 -6
  26. package/dist/ai-service/agent/tools2/tools/exit-plan-mode.js.map +1 -1
  27. package/dist/ai-service/agent/tools2/tools/git.d.ts +1 -1
  28. package/dist/ai-service/agent/tools2/tools/grep-metadata.d.ts +1 -1
  29. package/dist/ai-service/agent/tools2/tools/grep.d.ts +1 -1
  30. package/dist/ai-service/agent/tools2/tools/list-attachments.d.ts +1 -1
  31. package/dist/ai-service/app-interface/npm-package-lookup.d.ts +3 -2
  32. package/dist/ai-service/app-interface/npm-package-lookup.d.ts.map +1 -1
  33. package/dist/ai-service/app-interface/npm-package-lookup.js +21 -13
  34. package/dist/ai-service/app-interface/npm-package-lookup.js.map +1 -1
  35. package/dist/ai-service/app-interface/shell.d.ts.map +1 -1
  36. package/dist/ai-service/app-interface/shell.js +119 -0
  37. package/dist/ai-service/app-interface/shell.js.map +1 -1
  38. package/dist/ai-service/checklist/api-migration-checklist-gate.js +1 -1
  39. package/dist/ai-service/checklist/api-migration-checklist-gate.js.map +1 -1
  40. package/dist/ai-service/features.d.ts +11 -0
  41. package/dist/ai-service/features.d.ts.map +1 -1
  42. package/dist/ai-service/features.js +24 -0
  43. package/dist/ai-service/features.js.map +1 -1
  44. package/dist/ai-service/index.d.ts +38 -4
  45. package/dist/ai-service/index.d.ts.map +1 -1
  46. package/dist/ai-service/index.js +210 -13
  47. package/dist/ai-service/index.js.map +1 -1
  48. package/dist/ai-service/llm/client.d.ts.map +1 -1
  49. package/dist/ai-service/llm/client.js +23 -0
  50. package/dist/ai-service/llm/client.js.map +1 -1
  51. package/dist/ai-service/llm/context-v2/context.d.ts +31 -2
  52. package/dist/ai-service/llm/context-v2/context.d.ts.map +1 -1
  53. package/dist/ai-service/llm/context-v2/context.js +81 -8
  54. package/dist/ai-service/llm/context-v2/context.js.map +1 -1
  55. package/dist/ai-service/llm/context-v2/turns.d.ts.map +1 -1
  56. package/dist/ai-service/llm/context-v2/turns.js +25 -1
  57. package/dist/ai-service/llm/context-v2/turns.js.map +1 -1
  58. package/dist/ai-service/llm/context-v2/types.d.ts +9 -0
  59. package/dist/ai-service/llm/context-v2/types.d.ts.map +1 -1
  60. package/dist/ai-service/llm/context-v2/types.js.map +1 -1
  61. package/dist/ai-service/llm/stream/config.d.ts +15 -0
  62. package/dist/ai-service/llm/stream/config.d.ts.map +1 -1
  63. package/dist/ai-service/llm/stream/config.js +9 -0
  64. package/dist/ai-service/llm/stream/config.js.map +1 -1
  65. package/dist/ai-service/llm/stream/errors.d.ts +1 -1
  66. package/dist/ai-service/llm/stream/errors.d.ts.map +1 -1
  67. package/dist/ai-service/llm/stream/errors.js.map +1 -1
  68. package/dist/ai-service/llm/stream/idle-monitor.d.ts +8 -0
  69. package/dist/ai-service/llm/stream/idle-monitor.d.ts.map +1 -1
  70. package/dist/ai-service/llm/stream/idle-monitor.js +51 -0
  71. package/dist/ai-service/llm/stream/idle-monitor.js.map +1 -1
  72. package/dist/ai-service/llm/stream/index.d.ts +1 -1
  73. package/dist/ai-service/llm/stream/index.d.ts.map +1 -1
  74. package/dist/ai-service/llm/stream/index.js.map +1 -1
  75. package/dist/ai-service/llm/stream/managed-stream.d.ts.map +1 -1
  76. package/dist/ai-service/llm/stream/managed-stream.js +2 -5
  77. package/dist/ai-service/llm/stream/managed-stream.js.map +1 -1
  78. package/dist/ai-service/llm/stream/orchestrator.d.ts +2 -0
  79. package/dist/ai-service/llm/stream/orchestrator.d.ts.map +1 -1
  80. package/dist/ai-service/llm/stream/orchestrator.js +67 -3
  81. package/dist/ai-service/llm/stream/orchestrator.js.map +1 -1
  82. package/dist/ai-service/llm/stream/structured-output-chunk.d.ts +16 -0
  83. package/dist/ai-service/llm/stream/structured-output-chunk.d.ts.map +1 -0
  84. package/dist/ai-service/llm/stream/structured-output-chunk.js +26 -0
  85. package/dist/ai-service/llm/stream/structured-output-chunk.js.map +1 -0
  86. package/dist/ai-service/migration-auto-dispatch-readiness.d.ts +18 -0
  87. package/dist/ai-service/migration-auto-dispatch-readiness.d.ts.map +1 -0
  88. package/dist/ai-service/migration-auto-dispatch-readiness.js +17 -0
  89. package/dist/ai-service/migration-auto-dispatch-readiness.js.map +1 -0
  90. package/dist/ai-service/migration-autodispatch-metrics.d.ts +3 -0
  91. package/dist/ai-service/migration-autodispatch-metrics.d.ts.map +1 -0
  92. package/dist/ai-service/migration-autodispatch-metrics.js +51 -0
  93. package/dist/ai-service/migration-autodispatch-metrics.js.map +1 -0
  94. package/dist/ai-service/migration-llm-config.d.ts +12 -0
  95. package/dist/ai-service/migration-llm-config.d.ts.map +1 -0
  96. package/dist/ai-service/migration-llm-config.js +130 -0
  97. package/dist/ai-service/migration-llm-config.js.map +1 -0
  98. package/dist/ai-service/migration-session-active.d.ts +14 -0
  99. package/dist/ai-service/migration-session-active.d.ts.map +1 -0
  100. package/dist/ai-service/migration-session-active.js +29 -0
  101. package/dist/ai-service/migration-session-active.js.map +1 -0
  102. package/dist/ai-service/resolve-generation-state-for-ui.d.ts +8 -0
  103. package/dist/ai-service/resolve-generation-state-for-ui.d.ts.map +1 -0
  104. package/dist/ai-service/resolve-generation-state-for-ui.js +25 -0
  105. package/dist/ai-service/resolve-generation-state-for-ui.js.map +1 -0
  106. package/dist/ai-service/skills/system/superblocks-migration/skill.generated.d.ts +1 -1
  107. package/dist/ai-service/skills/system/superblocks-migration/skill.generated.d.ts.map +1 -1
  108. package/dist/ai-service/skills/system/superblocks-migration/skill.generated.js +5 -1
  109. package/dist/ai-service/skills/system/superblocks-migration/skill.generated.js.map +1 -1
  110. package/dist/ai-service/state-machine/helpers/peer.d.ts +19 -1
  111. package/dist/ai-service/state-machine/helpers/peer.d.ts.map +1 -1
  112. package/dist/ai-service/state-machine/helpers/peer.js +81 -7
  113. package/dist/ai-service/state-machine/helpers/peer.js.map +1 -1
  114. package/dist/ai-service/state-machine/mocks.d.ts.map +1 -1
  115. package/dist/ai-service/state-machine/mocks.js +1 -0
  116. package/dist/ai-service/state-machine/mocks.js.map +1 -1
  117. package/dist/ai-service/types.d.ts +6 -0
  118. package/dist/ai-service/types.d.ts.map +1 -1
  119. package/dist/ai-service/types.js.map +1 -1
  120. package/dist/ai-service/util/inference-jwt.d.ts +15 -0
  121. package/dist/ai-service/util/inference-jwt.d.ts.map +1 -0
  122. package/dist/ai-service/util/inference-jwt.js +56 -0
  123. package/dist/ai-service/util/inference-jwt.js.map +1 -0
  124. package/dist/file-sync-vite-plugin.d.ts.map +1 -1
  125. package/dist/file-sync-vite-plugin.js +3 -6
  126. package/dist/file-sync-vite-plugin.js.map +1 -1
  127. package/dist/file-system-helpers.d.ts +14 -0
  128. package/dist/file-system-helpers.d.ts.map +1 -1
  129. package/dist/file-system-helpers.js +30 -0
  130. package/dist/file-system-helpers.js.map +1 -1
  131. package/dist/migration/integration-write-policy.d.ts +12 -0
  132. package/dist/migration/integration-write-policy.d.ts.map +1 -0
  133. package/dist/migration/integration-write-policy.js +31 -0
  134. package/dist/migration/integration-write-policy.js.map +1 -0
  135. package/dist/migration/migration-metrics.d.ts +69 -0
  136. package/dist/migration/migration-metrics.d.ts.map +1 -0
  137. package/dist/migration/migration-metrics.js +98 -0
  138. package/dist/migration/migration-metrics.js.map +1 -0
  139. package/dist/migration/migration-routes.d.ts.map +1 -1
  140. package/dist/migration/migration-routes.js +53 -121
  141. package/dist/migration/migration-routes.js.map +1 -1
  142. package/dist/migration/migration-verification.d.ts +49 -7
  143. package/dist/migration/migration-verification.d.ts.map +1 -1
  144. package/dist/migration/migration-verification.js +92 -28
  145. package/dist/migration/migration-verification.js.map +1 -1
  146. package/dist/migration/restructure.d.ts +10 -4
  147. package/dist/migration/restructure.d.ts.map +1 -1
  148. package/dist/migration/restructure.js +3 -2
  149. package/dist/migration/restructure.js.map +1 -1
  150. package/dist/server-rpc/client.d.ts.map +1 -1
  151. package/dist/server-rpc/client.js +11 -2
  152. package/dist/server-rpc/client.js.map +1 -1
  153. package/dist/socket-manager.d.ts.map +1 -1
  154. package/dist/socket-manager.js +5 -0
  155. package/dist/socket-manager.js.map +1 -1
  156. package/package.json +10 -10
@@ -30,7 +30,7 @@ import { AppSkillsManager } from "./app-skills/manager.js";
30
30
  import { isUploadedAttachment, toUploadedAttachmentContentPart, } from "./attachments/uploaded-content-part.js";
31
31
  import { ChatSessionStore } from "./chat/chat-session-store.js";
32
32
  import { normalizeTextAttachmentForModel } from "./chat/utils.js";
33
- import { API_MIGRATION_ORIGINS, readPersistedChecklist, updateChecklistItem, } from "./checklist/persisted-checklist-store.js";
33
+ import { API_MIGRATION_ORIGINS, isApiMigrationOrigin, readPersistedChecklist, updateChecklistItem, } from "./checklist/persisted-checklist-store.js";
34
34
  import { downloadContextFromBucketeer, downloadLatestContextFromBucketeer, restoreContextFromCheckpoint, } from "./context-download.js";
35
35
  import { uploadContextToBucketeer } from "./context-upload.js";
36
36
  import { AppContextStore } from "./context/app-context.js";
@@ -53,12 +53,17 @@ import { traceLLM } from "./llmobs/helpers.js";
53
53
  import { getJwtTraceTags } from "./llmobs/helpers.js";
54
54
  import llmobs from "./llmobs/index.js";
55
55
  import { PlaywrightMcpServerManager } from "./mcp/playwright-server.js";
56
+ import { evaluateMigrationAutoDispatchReadiness } from "./migration-auto-dispatch-readiness.js";
57
+ import { recordMigrationAutoDispatchOutcome } from "./migration-autodispatch-metrics.js";
58
+ import { buildServerMigrationLlmConfig, isMigrationAutoDispatchEnabled, } from "./migration-llm-config.js";
59
+ import { isMigrationSessionActive } from "./migration-session-active.js";
56
60
  import { policyGateStreamPartToAgentStep } from "./policy-gate-step.js";
57
61
  import { ClarkProfiler } from "./profiler/clark-profiler.js";
58
62
  import { buildSummarizeApiUsagePrompt } from "./prompts/summarize-api-usage.js";
59
63
  import { checkAiQuota } from "./quota-client.js";
60
64
  import { RecordingManager, createRecordingFetch } from "./recording/index.js";
61
65
  import { RequestDeduplicator } from "./request-deduplicator.js";
66
+ import { resolveGenerationStateForUi } from "./resolve-generation-state-for-ui.js";
62
67
  import { scanContentForSecrets } from "./security/secret-scanner-service.js";
63
68
  import { SecretRedactor } from "./security/secret-scanner.js";
64
69
  import { ClarkStateNames, Clark, SERVICE_STARTED_WITH_DRAFT, USER_ACCEPTED_DRAFT, USER_REJECTED_DRAFT, USER_SENT_PROMPT, USER_CANCELED, AGENT_CANCELED, } from "./state-machine/clark-fsm.js";
@@ -78,10 +83,22 @@ import { catchBackgroundPeerDisconnect, StablePeer, } from "./state-machine/help
78
83
  import { transitionFrom } from "./state-machine/helpers/transition.js";
79
84
  import { isTaskAccessibleTo, registerBackgroundTaskTypes, resyncActiveTaskStatusesToPeer, TaskStore, wireTaskStatusPushToPeer, } from "./tasks/index.js";
80
85
  import { TemplateRenderer } from "./template-renderer.js";
86
+ import { describeMigrationInferenceJwt, isUsableMigrationInferenceJwt, } from "./util/inference-jwt.js";
81
87
  import { processLLMConfig } from "./util/llm-config-utils.js";
82
88
  import { parseJwt } from "./util/parse-jwt.js";
83
89
  import { getToolCallSignature } from "./util/tool-signature.js";
84
90
  export { AiServiceFeatureFlags } from "./features.js";
91
+ function streamRetryOptionsFromConfig(retryOptions, streamTimeoutBudgets) {
92
+ if (streamTimeoutBudgets === undefined ||
93
+ Object.keys(streamTimeoutBudgets).length === 0) {
94
+ return retryOptions;
95
+ }
96
+ return {
97
+ ...retryOptions,
98
+ streamTimeoutBudgets,
99
+ timeoutSource: "feature-flag",
100
+ };
101
+ }
85
102
  export { stripResolvedFromLockfile } from "./app-interface/npm-registry.js";
86
103
  export { SnapshotManager } from "./recording/index.js";
87
104
  export { isSdkApiTemplate } from "./sdk-api-templates.js";
@@ -357,6 +374,9 @@ export class AiService extends TracedEventEmitter {
357
374
  try {
358
375
  this.tokenManagerJwt = event.token;
359
376
  this.getLogger().info(`[ai-service] token updated via tokenManager`);
377
+ if (event.token && isUsableMigrationInferenceJwt(event.token)) {
378
+ this.updateClarkJwt(event.token);
379
+ }
360
380
  }
361
381
  catch (err) {
362
382
  console.error("[ai-service] tokenUpdated listener threw", err);
@@ -456,9 +476,16 @@ export class AiService extends TracedEventEmitter {
456
476
  * Updates the JWT in Clark context
457
477
  */
458
478
  updateClarkJwt(jwt) {
459
- if (jwt) {
460
- this.clark.updateContext({ jwt });
479
+ const jwtDetails = describeMigrationInferenceJwt(jwt);
480
+ if (!jwt || !jwtDetails.usable) {
481
+ return;
461
482
  }
483
+ this.clark.updateContext({ jwt });
484
+ const llmConfig = this.lastMigrationTurnCredentials?.llmConfig ??
485
+ this.clark.context.llmConfig ??
486
+ buildServerMigrationLlmConfig(this.config.flagBootstrap);
487
+ this.storeMigrationTurnCredentials(jwt, llmConfig);
488
+ this.tryAutoDispatchMigrationTurnIfReady();
462
489
  }
463
490
  // ===== Recording API =====
464
491
  /**
@@ -771,7 +798,7 @@ export class AiService extends TracedEventEmitter {
771
798
  this.llmClient = new LLMClient({
772
799
  contextManagerV2: this.contextManagerV2,
773
800
  appShell: this.appShell,
774
- retryOptions: config.retryOptions,
801
+ retryOptions: streamRetryOptionsFromConfig(config.retryOptions, config.features.streamTimeoutBudgets),
775
802
  unifiedProviderEnabled: config.features.unifiedProviderEnabled ?? false,
776
803
  organizationId: config.organizationId,
777
804
  applicationId: config.applicationId,
@@ -916,6 +943,142 @@ export class AiService extends TracedEventEmitter {
916
943
  },
917
944
  });
918
945
  }
946
+ this.tryAutoDispatchMigrationTurnIfReady();
947
+ }
948
+ migrationAppArtifactsReady = false;
949
+ migrationAutoDispatchPending = false;
950
+ migrationAutoDispatchInFlight = false;
951
+ lastMigrationTurnCredentials;
952
+ hasInitialPendingMigrationWork(items) {
953
+ const pendingItems = items.filter((item) => isApiMigrationOrigin(item.origin) &&
954
+ (item.status === "pending" || item.status === "in_progress"));
955
+ if (pendingItems.length === 0) {
956
+ return false;
957
+ }
958
+ return !pendingItems.some((item) => item.lastFailureReason);
959
+ }
960
+ resolveMigrationTurnCredentials() {
961
+ const cachedJwt = this.lastMigrationTurnCredentials?.jwt;
962
+ const inferenceJwt = this.getInferenceJwt();
963
+ const jwt = (cachedJwt && isUsableMigrationInferenceJwt(cachedJwt)
964
+ ? cachedJwt
965
+ : undefined) ??
966
+ (inferenceJwt && isUsableMigrationInferenceJwt(inferenceJwt)
967
+ ? inferenceJwt
968
+ : undefined);
969
+ const llmConfig = this.lastMigrationTurnCredentials?.llmConfig ??
970
+ this.clark.context.llmConfig ??
971
+ buildServerMigrationLlmConfig(this.config.flagBootstrap);
972
+ return { jwt, llmConfig };
973
+ }
974
+ storeMigrationTurnCredentials(jwt, llmConfig) {
975
+ const nextJwt = jwt && jwt.length > 0 ? jwt : this.lastMigrationTurnCredentials?.jwt;
976
+ this.lastMigrationTurnCredentials = {
977
+ ...(nextJwt ? { jwt: nextJwt } : {}),
978
+ llmConfig,
979
+ };
980
+ if (jwt && jwt.length > 0 && !this.getInferenceJwt()) {
981
+ this.clark.updateContext({ jwt });
982
+ }
983
+ }
984
+ isRetriableMigrationAutoDispatchFailure(reason) {
985
+ if (!reason) {
986
+ return false;
987
+ }
988
+ return (reason.startsWith("busy:") ||
989
+ reason === "pending-tool-permission" ||
990
+ reason === "awaiting-interactive-response");
991
+ }
992
+ /**
993
+ * Called by dev startup once DBFS download (or upload-first local tree)
994
+ * has finished so migration scratch/checklist files are on disk.
995
+ */
996
+ notifyMigrationAppArtifactsReady() {
997
+ if (this.migrationAppArtifactsReady) {
998
+ return;
999
+ }
1000
+ this.migrationAppArtifactsReady = true;
1001
+ this.tryAutoDispatchMigrationTurnIfReady();
1002
+ }
1003
+ tryAutoDispatchMigrationTurnIfReady() {
1004
+ if (this.migrationAutoDispatchInFlight) {
1005
+ return;
1006
+ }
1007
+ if (!isMigrationAutoDispatchEnabled(this.config.flagBootstrap)) {
1008
+ return;
1009
+ }
1010
+ const { jwt } = this.resolveMigrationTurnCredentials();
1011
+ const readiness = evaluateMigrationAutoDispatchReadiness({
1012
+ appArtifactsReady: this.migrationAppArtifactsReady,
1013
+ hasConnectedPeer: this.hasConnectedPeer(),
1014
+ hasUsableJwt: Boolean(jwt),
1015
+ });
1016
+ if (!readiness.ready) {
1017
+ if (readiness.reason === "jwt-unavailable") {
1018
+ this.migrationAutoDispatchPending = true;
1019
+ }
1020
+ return;
1021
+ }
1022
+ void this.maybeAutoDispatchMigrationTurn().catch((error) => {
1023
+ this.getLogger().error("[ai-service] Migration auto-dispatch failed", getErrorMeta(error));
1024
+ });
1025
+ }
1026
+ /**
1027
+ * Attempts to start the initial post-flip migration turn without UI
1028
+ * involvement. Skips user-initiated focused-debug retries (items with
1029
+ * `lastFailureReason`) and defers when JWT is unavailable.
1030
+ */
1031
+ async maybeAutoDispatchMigrationTurn() {
1032
+ if (this.migrationAutoDispatchInFlight) {
1033
+ return;
1034
+ }
1035
+ this.migrationAutoDispatchInFlight = true;
1036
+ try {
1037
+ // Positive migration-session gate. Only auto-dispatch while a migration
1038
+ // session is genuinely in flight on disk (scratch/migration-state.json).
1039
+ // Completion and abort both remove it, so a stale checklist on a
1040
+ // never-migrated or already-finished app can never arm auto-dispatch
1041
+ // during normal usage — bounding the blast radius to migrations.
1042
+ if (!(await isMigrationSessionActive(this.appRootDirPath))) {
1043
+ this.migrationAutoDispatchPending = false;
1044
+ return;
1045
+ }
1046
+ const checklist = await readPersistedChecklist(this.appRootDirPath);
1047
+ if (!checklist) {
1048
+ this.migrationAutoDispatchPending = false;
1049
+ return;
1050
+ }
1051
+ if (!this.hasInitialPendingMigrationWork(checklist.items)) {
1052
+ this.migrationAutoDispatchPending = false;
1053
+ return;
1054
+ }
1055
+ const { jwt, llmConfig } = this.resolveMigrationTurnCredentials();
1056
+ if (!jwt) {
1057
+ this.migrationAutoDispatchPending = true;
1058
+ return;
1059
+ }
1060
+ const result = await this.handleAiTriggerMigrationTurn({
1061
+ jwt,
1062
+ llmConfig,
1063
+ triggeredBy: "auto-dispatch",
1064
+ });
1065
+ if (result.dispatched) {
1066
+ this.migrationAutoDispatchPending = false;
1067
+ this.getLogger().info("[ai-service] Auto-dispatched post-flip migration turn");
1068
+ }
1069
+ else if (result.reason === "already-triggered") {
1070
+ this.migrationAutoDispatchPending = false;
1071
+ }
1072
+ else if (this.isRetriableMigrationAutoDispatchFailure(result.reason)) {
1073
+ this.migrationAutoDispatchPending = true;
1074
+ }
1075
+ else {
1076
+ this.migrationAutoDispatchPending = false;
1077
+ }
1078
+ }
1079
+ finally {
1080
+ this.migrationAutoDispatchInFlight = false;
1081
+ }
919
1082
  }
920
1083
  /**
921
1084
  * Remove the integration metadata cache for this session.
@@ -1317,12 +1480,22 @@ export class AiService extends TracedEventEmitter {
1317
1480
  * per ai-service lifetime. The disk checklist is the recovery source of
1318
1481
  * truth across process restarts.
1319
1482
  *
1320
- * Expected caller: the UI issues a dedicated RPC (see
1321
- * v1.ai.triggerMigrationTurn) once it observes the post-flip state via
1322
- * /sb-migration-status. jwt + llmConfig come from the UI's normal auth
1323
- * flow.
1483
+ * Primary auto-trigger: readiness gate (`notifyMigrationAppArtifactsReady`,
1484
+ * `handleUserConnected`, `updateClarkJwt`)
1485
+ * `tryAutoDispatchMigrationTurnIfReady` `maybeAutoDispatchMigrationTurn`.
1486
+ * Backup caller: the UI RPC (see v1.ai.triggerMigrationTurn) once it
1487
+ * observes the post-flip state via /sb-migration-status.
1488
+ * User retry after unresolved failures: UI RPC with
1489
+ * `retryMode: "focused-debug"` after /sb-migration-retry-unresolved.
1324
1490
  */
1325
1491
  async handleAiTriggerMigrationTurn(request) {
1492
+ this.storeMigrationTurnCredentials(request.jwt, request.llmConfig);
1493
+ const isAutoDispatch = request.triggeredBy === "auto-dispatch";
1494
+ const recordAutoDispatchOutcome = (reason) => {
1495
+ if (isAutoDispatch) {
1496
+ recordMigrationAutoDispatchOutcome(reason);
1497
+ }
1498
+ };
1326
1499
  // User-initiated "Fix with Clark" retries bypass the one-shot
1327
1500
  // guard. The HTTP route that flips the items has already landed,
1328
1501
  // so if we refused here the user would be stuck.
@@ -1330,33 +1503,44 @@ export class AiService extends TracedEventEmitter {
1330
1503
  this.migrationTurnTriggered = false;
1331
1504
  }
1332
1505
  if (this.migrationTurnTriggered) {
1506
+ recordAutoDispatchOutcome("already-triggered");
1333
1507
  return { dispatched: false, reason: "already-triggered" };
1334
1508
  }
1335
1509
  if (this.clark.state === ClarkStateNames.Dead) {
1510
+ recordAutoDispatchOutcome("dead");
1336
1511
  return { dispatched: false, reason: "dead" };
1337
1512
  }
1338
1513
  if (this.clark.state !== ClarkStateNames.Idle &&
1339
1514
  this.clark.state !== ClarkStateNames.AwaitingUser) {
1340
- return { dispatched: false, reason: `busy:${this.clark.state}` };
1515
+ const reason = `busy:${this.clark.state}`;
1516
+ recordAutoDispatchOutcome(reason);
1517
+ return { dispatched: false, reason };
1341
1518
  }
1342
1519
  if (this.clark.context.pendingToolPermissionRequest) {
1520
+ recordAutoDispatchOutcome("pending-tool-permission");
1343
1521
  return { dispatched: false, reason: "pending-tool-permission" };
1344
1522
  }
1345
1523
  if (this.clark.state === ClarkStateNames.AwaitingUser) {
1346
1524
  if (this.clark.context.activeInteractiveMessage ||
1347
1525
  this.clark.context.deferredInteractiveMessage) {
1526
+ recordAutoDispatchOutcome("awaiting-interactive-response");
1348
1527
  return { dispatched: false, reason: "awaiting-interactive-response" };
1349
1528
  }
1350
1529
  }
1530
+ this.migrationTurnTriggered = true;
1351
1531
  const checklist = await readPersistedChecklist(this.appRootDirPath);
1352
1532
  if (!checklist) {
1533
+ this.migrationTurnTriggered = false;
1353
1534
  this.getLogger().warn("[ai-service] Migration checklist not found or unreadable; skipping translation turn");
1535
+ recordAutoDispatchOutcome("checklist-unavailable");
1354
1536
  return { dispatched: false, reason: "checklist-unavailable" };
1355
1537
  }
1356
1538
  const pendingSeedApiItems = checklist.items.filter((item) => API_MIGRATION_ORIGINS.has(item.origin) &&
1357
1539
  (item.status === "pending" || item.status === "in_progress"));
1358
1540
  const pendingSeedApiCount = pendingSeedApiItems.length;
1359
1541
  if (pendingSeedApiCount === 0) {
1542
+ this.migrationTurnTriggered = false;
1543
+ recordAutoDispatchOutcome("no-pending-items");
1360
1544
  return { dispatched: false, reason: "no-pending-items" };
1361
1545
  }
1362
1546
  // Pre-flight quota gate. The migration turn drives LLM inference (which
@@ -1368,10 +1552,11 @@ export class AiService extends TracedEventEmitter {
1368
1552
  // same pre-flight check handleAiGenerate already runs.
1369
1553
  const quota = await this.checkAiQuota(request.jwt);
1370
1554
  if (!quota.allowed) {
1555
+ this.migrationTurnTriggered = false;
1371
1556
  this.getLogger().warn("[ai-service] Skipping v2→v3 migration turn — AI quota exceeded", { reason: quota.reason });
1557
+ recordAutoDispatchOutcome("quota-exceeded");
1372
1558
  return { dispatched: false, reason: "quota-exceeded" };
1373
1559
  }
1374
- this.migrationTurnTriggered = true;
1375
1560
  this.appShell.setApiHandlerEnabled(false);
1376
1561
  this.getLogger().info(`[ai-service] Dispatching internal v2→v3 migration turn for ${pendingSeedApiCount} API(s) (API VFS projection disabled, retryMode=${request.retryMode ?? "none"})`);
1377
1562
  const prompt = buildTranslationPrompt({
@@ -1393,6 +1578,8 @@ export class AiService extends TracedEventEmitter {
1393
1578
  origin: "app_migration",
1394
1579
  pendingSeedApiCount,
1395
1580
  retryMode: request.retryMode ?? "none",
1581
+ triggeredBy: request.triggeredBy ??
1582
+ (request.retryMode === "focused-debug" ? "focused-debug" : "ui-rpc"),
1396
1583
  };
1397
1584
  this.clark.updateContext((context) => ({
1398
1585
  ...context,
@@ -1419,6 +1606,7 @@ export class AiService extends TracedEventEmitter {
1419
1606
  this.migrationTurnTriggered = false;
1420
1607
  this.appShell.setApiHandlerEnabled(true);
1421
1608
  });
1609
+ recordAutoDispatchOutcome("dispatched");
1422
1610
  return { dispatched: true };
1423
1611
  }
1424
1612
  getMigrationSafeLlmConfig(llmConfig) {
@@ -1737,6 +1925,7 @@ export class AiService extends TracedEventEmitter {
1737
1925
  }
1738
1926
  async handleAiGenerate(request) {
1739
1927
  this.getLogger().info(`[ai-service] handleAiGenerate: peerId=${this.stablePeer.peerId ?? "unknown"}`);
1928
+ this.storeMigrationTurnCredentials(request.jwt, request.llmConfig);
1740
1929
  if (this.clark.state === ClarkStateNames.Dead) {
1741
1930
  recordPromptEntryRejection({
1742
1931
  dedupeKey: resolvePromptRequestDedupeKey(request),
@@ -2895,6 +3084,12 @@ export class AiService extends TracedEventEmitter {
2895
3084
  }
2896
3085
  return `Continue with the task using alternative approaches. The tool ${toolName} was denied permission.`;
2897
3086
  }
3087
+ resolveGenerationStateForUi() {
3088
+ return resolveGenerationStateForUi({
3089
+ clarkState: this.clark.state,
3090
+ lastGenerationState: this.clark.context.lastGenerationState,
3091
+ });
3092
+ }
2898
3093
  handleUserConnected(peer, peerId) {
2899
3094
  const existingPeerId = this.stablePeer.peerId;
2900
3095
  this.getLogger().info(`[ai-service] handleUserConnected: peerId=${peerId}`);
@@ -2926,9 +3121,10 @@ export class AiService extends TracedEventEmitter {
2926
3121
  this.authenticatedPeerUserId = undefined;
2927
3122
  }
2928
3123
  // 1. Sync generation state if generation is in progress
2929
- if (this.clark.context.lastGenerationState) {
2930
- this.getLogger().info(`[ai-service] Syncing generation state on reconnect: ${this.clark.context.lastGenerationState.state}`);
2931
- catchBackgroundPeerDisconnect(peer.call.aiSetGenerationState(this.clark.context.lastGenerationState), "generation state reconnect push");
3124
+ const generationState = this.resolveGenerationStateForUi();
3125
+ if (generationState) {
3126
+ this.getLogger().info(`[ai-service] Syncing generation state on reconnect: ${generationState.state}`);
3127
+ catchBackgroundPeerDisconnect(peer.call.aiSetGenerationState(generationState), "generation state reconnect push");
2932
3128
  }
2933
3129
  // 2. Sync tool permission state with the UI
2934
3130
  if (this.clark.context.pendingToolPermissionRequest) {
@@ -2979,6 +3175,7 @@ export class AiService extends TracedEventEmitter {
2979
3175
  if (this.taskStore) {
2980
3176
  this.pendingTaskResyncPeerIds.add(peerId);
2981
3177
  }
3178
+ this.tryAutoDispatchMigrationTurnIfReady();
2982
3179
  }
2983
3180
  /**
2984
3181
  * Per-session set of peer IDs that connected since their last task