@mycadet/assistant 0.12.10 → 0.13.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/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
- package/node_modules/@mycadet/ces-client/src/http-credentials.ts +18 -1
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
- package/node_modules/@mycadet/gateway-client/src/gateway-ipc-contracts.ts +0 -20
- package/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
- package/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
- package/openapi.yaml +2 -708
- package/package.json +2 -3
- package/scripts/sync-web-search-catalog.ts +1 -2
- package/src/__tests__/cloud-profile-guard.test.ts +41 -5
- package/src/__tests__/config-schema.test.ts +6 -38
- package/src/__tests__/conversation-queue.test.ts +45 -6
- package/src/__tests__/db-readiness-http-gate.test.ts +0 -6
- package/src/__tests__/persist-onboarding-artifacts.test.ts +0 -1
- package/src/__tests__/persona-resolver.test.ts +0 -51
- package/src/__tests__/provider-cloud-proxy-integration.test.ts +2 -110
- package/src/__tests__/run-due-schedules.test.ts +0 -4
- package/src/__tests__/schedule-store.test.ts +0 -12
- package/src/__tests__/stt-stream-session.test.ts +4 -4
- package/src/__tests__/telephony-tts-guard.test.ts +2 -50
- package/src/__tests__/voice-config-update.test.ts +12 -167
- package/src/__tests__/web-search-catalog-parity.test.ts +0 -2
- package/src/__tests__/workspace-migration-130-speech-mode-to-provider.test.ts +9 -7
- package/src/__tests__/workspace-migration-132-web-search-mode-to-provider.test.ts +6 -6
- package/src/calls/__tests__/voice-session-bridge.test.ts +1 -4
- package/src/calls/resolve-call-tts-provider.ts +6 -25
- package/src/calls/telephony-credential-preflight.ts +0 -10
- package/src/calls/telephony-tts-capability.ts +1 -17
- package/src/cli/commands/tts.ts +1 -2
- package/src/cli/index.help.ts +0 -2
- package/src/cli/program.ts +0 -2
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +4 -42
- package/src/config/schemas/__tests__/stt.test.ts +10 -14
- package/src/config/schemas/llm.ts +3 -5
- package/src/config/schemas/services.ts +5 -6
- package/src/config/schemas/stt.ts +1 -3
- package/src/config/schemas/tts.ts +1 -30
- package/src/daemon/daemon-readiness.ts +4 -29
- package/src/daemon/lifecycle.ts +0 -31
- package/src/daemon/providers-setup.ts +0 -8
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
- package/src/heartbeat/heartbeat-service.ts +1 -66
- package/src/ipc/__tests__/db-readiness-ipc-gate.test.ts +1 -9
- package/src/live-voice/__tests__/live-voice-credential-preflight.test.ts +0 -44
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +4 -6
- package/src/live-voice/__tests__/live-voice-stt.test.ts +1 -1
- package/src/live-voice/live-voice-credential-preflight.ts +9 -33
- package/src/live-voice/live-voice-session.ts +10 -6
- package/src/live-voice/live-voice-tts.ts +1 -8
- package/src/plugins/defaults/memory/worker.ts +3 -3
- package/src/prompts/persona-resolver.ts +0 -35
- package/src/providers/__tests__/search-provider-catalog.test.ts +5 -8
- package/src/providers/__tests__/voice-error-copy.test.ts +9 -10
- package/src/providers/search-provider-catalog.ts +7 -39
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +3 -47
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +6 -372
- package/src/providers/speech-to-text/provider-catalog.ts +2 -73
- package/src/providers/speech-to-text/resolve.ts +3 -120
- package/src/runtime/http-router-types.ts +1 -3
- package/src/runtime/http-server.ts +1 -1
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +13 -11
- package/src/runtime/routes/__tests__/tts-routes.test.ts +2 -6
- package/src/runtime/routes/conversation-query-routes.ts +0 -96
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +7 -7
- package/src/runtime/routes/index.ts +0 -4
- package/src/runtime/routes/live-voice-routes.test.ts +4 -11
- package/src/runtime/routes/live-voice-routes.ts +8 -11
- package/src/runtime/routes/secret-routes.ts +0 -6
- package/src/runtime/routes/tts-routes.ts +0 -38
- package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +1 -7
- package/src/schedule/__tests__/worker-mcp-readiness.test.ts +0 -4
- package/src/schedule/schedule-store.ts +0 -7
- package/src/schedule/scheduler.ts +0 -5
- package/src/schedule/worker.ts +1 -1
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +2 -10
- package/src/stt/daemon-batch-transcriber.ts +4 -38
- package/src/stt/types.ts +1 -9
- package/src/tools/network/__tests__/web-search.test.ts +3 -250
- package/src/tools/network/web-search.ts +4 -165
- package/src/tts/__tests__/provider-resolution.test.ts +0 -5
- package/src/tts/provider-catalog.ts +0 -4
- package/src/tts/types.ts +0 -1
- package/src/__tests__/cloud-speech-defaults.test.ts +0 -330
- package/src/__tests__/migration-cross-version-compatibility.test.ts +0 -1708
- package/src/__tests__/migration-export-http.test.ts +0 -557
- package/src/__tests__/migration-export-streaming.test.ts +0 -350
- package/src/__tests__/migration-export-to-gcs.test.ts +0 -706
- package/src/__tests__/migration-import-commit-http.test.ts +0 -1176
- package/src/__tests__/migration-import-from-gcs.test.ts +0 -534
- package/src/__tests__/migration-import-from-path.test.ts +0 -324
- package/src/__tests__/migration-import-from-url.test.ts +0 -588
- package/src/__tests__/migration-import-preflight-http.test.ts +0 -739
- package/src/__tests__/migration-jobs-status.test.ts +0 -139
- package/src/__tests__/migration-parity-persistence.test.ts +0 -1891
- package/src/__tests__/migration-transport.test.ts +0 -1089
- package/src/__tests__/migration-validate-http.test.ts +0 -634
- package/src/__tests__/migration-wizard.test.ts +0 -1395
- package/src/__tests__/rebind-secrets-screen.test.ts +0 -876
- package/src/__tests__/sanitize-config-for-transfer.test.ts +0 -259
- package/src/__tests__/transfer-progress-screen.test.ts +0 -1217
- package/src/__tests__/validation-results-screen.test.ts +0 -1130
- package/src/__tests__/vbundle-pax-and-symlink.test.ts +0 -147
- package/src/background-wake/background-wake-routes.test.ts +0 -906
- package/src/background-wake/cloud-client.test.ts +0 -328
- package/src/background-wake/cloud-client.ts +0 -196
- package/src/background-wake/next-wake.test.ts +0 -335
- package/src/background-wake/next-wake.ts +0 -188
- package/src/background-wake/publisher.ts +0 -93
- package/src/background-wake/wake-intent-hooks.test.ts +0 -234
- package/src/cli/commands/__tests__/backup.test.ts +0 -363
- package/src/cli/commands/backup.help.ts +0 -180
- package/src/cli/commands/backup.ts +0 -398
- package/src/cloud/cloud-speech.test.ts +0 -335
- package/src/cloud/cloud-speech.ts +0 -300
- package/src/config/cloud-speech-defaults.ts +0 -300
- package/src/config/sanitize-for-transfer.ts +0 -111
- package/src/live-voice/__tests__/live-voice-cloud-speech-fallback.test.ts +0 -250
- package/src/providers/speech-to-text/__tests__/cadet-cloud-realtime.test.ts +0 -457
- package/src/providers/speech-to-text/__tests__/cadet-cloud.test.ts +0 -271
- package/src/providers/speech-to-text/__tests__/cadet-speech-relay-connection.test.ts +0 -126
- package/src/providers/speech-to-text/cadet-cloud-flux-realtime.ts +0 -309
- package/src/providers/speech-to-text/cadet-cloud-realtime.ts +0 -315
- package/src/providers/speech-to-text/cadet-cloud.ts +0 -108
- package/src/providers/speech-to-text/cadet-speech-relay-connection.ts +0 -176
- package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +0 -254
- package/src/runtime/migrations/__tests__/job-registry.test.ts +0 -346
- package/src/runtime/migrations/__tests__/origin-mode.test.ts +0 -62
- package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +0 -184
- package/src/runtime/migrations/__tests__/staged-import-path.test.ts +0 -110
- package/src/runtime/migrations/__tests__/v1-test-helpers.ts +0 -112
- package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +0 -285
- package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +0 -126
- package/src/runtime/migrations/__tests__/vbundle-builder-v1-shape.test.ts +0 -253
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +0 -208
- package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +0 -485
- package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +0 -349
- package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +0 -196
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +0 -551
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +0 -428
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +0 -220
- package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +0 -1823
- package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +0 -598
- package/src/runtime/migrations/__tests__/vbundle-symlink-importer.test.ts +0 -463
- package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -538
- package/src/runtime/migrations/__tests__/vbundle-symlink-streaming.test.ts +0 -515
- package/src/runtime/migrations/__tests__/vbundle-symlink-tar.test.ts +0 -453
- package/src/runtime/migrations/__tests__/vbundle-symlink-walker.test.ts +0 -319
- package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +0 -240
- package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +0 -473
- package/src/runtime/migrations/gcs-signed-url.ts +0 -190
- package/src/runtime/migrations/job-registry.ts +0 -297
- package/src/runtime/migrations/migration-transport.ts +0 -612
- package/src/runtime/migrations/migration-wizard.ts +0 -747
- package/src/runtime/migrations/origin-mode.ts +0 -42
- package/src/runtime/migrations/rebind-secrets-screen.ts +0 -412
- package/src/runtime/migrations/staged-import-path.ts +0 -116
- package/src/runtime/migrations/transfer-progress-screen.ts +0 -323
- package/src/runtime/migrations/validation-results-screen.ts +0 -469
- package/src/runtime/migrations/vbundle-builder.ts +0 -1382
- package/src/runtime/migrations/vbundle-import-analyzer.ts +0 -405
- package/src/runtime/migrations/vbundle-import-policy.ts +0 -241
- package/src/runtime/migrations/vbundle-importer.ts +0 -826
- package/src/runtime/migrations/vbundle-metadata-merge.ts +0 -113
- package/src/runtime/migrations/vbundle-streaming-importer.ts +0 -2844
- package/src/runtime/migrations/vbundle-streaming-validator.ts +0 -426
- package/src/runtime/migrations/vbundle-tar-stream.ts +0 -244
- package/src/runtime/migrations/vbundle-validator.ts +0 -724
- package/src/runtime/routes/__tests__/migration-cadet-metadata-reconcile.test.ts +0 -227
- package/src/runtime/routes/__tests__/migration-export-secrets-redacted.test.ts +0 -54
- package/src/runtime/routes/__tests__/migration-import-allowed-hosts-env.test.ts +0 -70
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +0 -257
- package/src/runtime/routes/background-wake-routes.ts +0 -387
- package/src/runtime/routes/migration-routes.ts +0 -2584
- package/src/tools/network/__tests__/cloud-search-proxy.test.ts +0 -282
- package/src/tools/network/cloud-search-proxy.ts +0 -211
- package/src/tts/__tests__/cadet-provider.test.ts +0 -253
- package/src/tts/providers/cadet-provider.ts +0 -244
- package/src/types/tar-stream.d.ts +0 -66
|
@@ -1,747 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Migration wizard state machine — shared module for guided migration UX.
|
|
3
|
-
*
|
|
4
|
-
* Drives the multi-step wizard for managed <-> self-hosted assistant
|
|
5
|
-
* migrations. Usable from both macOS and iOS web views via WKWebView.
|
|
6
|
-
*
|
|
7
|
-
* Steps:
|
|
8
|
-
* select-direction → upload-bundle → validate → preflight-review →
|
|
9
|
-
* transfer → rebind-secrets → complete
|
|
10
|
-
*
|
|
11
|
-
* Each step has a status (idle / loading / success / error) with error
|
|
12
|
-
* details and retry capability. The entire wizard state is serializable
|
|
13
|
-
* for persistence/resume across app restarts.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { z } from "zod";
|
|
17
|
-
|
|
18
|
-
import type {
|
|
19
|
-
ExportManagedResult,
|
|
20
|
-
ImportCommitResponse,
|
|
21
|
-
ImportPreflightResponse,
|
|
22
|
-
TransportConfig,
|
|
23
|
-
ValidateResponse,
|
|
24
|
-
} from "./migration-transport.js";
|
|
25
|
-
import {
|
|
26
|
-
importCommit,
|
|
27
|
-
importPreflight,
|
|
28
|
-
MigrationTransportError,
|
|
29
|
-
validateBundle,
|
|
30
|
-
} from "./migration-transport.js";
|
|
31
|
-
|
|
32
|
-
// ---------------------------------------------------------------------------
|
|
33
|
-
// Types
|
|
34
|
-
// ---------------------------------------------------------------------------
|
|
35
|
-
|
|
36
|
-
export type MigrationDirection =
|
|
37
|
-
| "managed-to-self-hosted"
|
|
38
|
-
| "self-hosted-to-managed";
|
|
39
|
-
|
|
40
|
-
export const WizardStepSchema = z.enum([
|
|
41
|
-
"select-direction",
|
|
42
|
-
"upload-bundle",
|
|
43
|
-
"validate",
|
|
44
|
-
"preflight-review",
|
|
45
|
-
"transfer",
|
|
46
|
-
"rebind-secrets",
|
|
47
|
-
"complete",
|
|
48
|
-
]);
|
|
49
|
-
export type WizardStep = z.infer<typeof WizardStepSchema>;
|
|
50
|
-
|
|
51
|
-
const StepStatusSchema = z.enum(["idle", "loading", "success", "error"]);
|
|
52
|
-
type StepStatus = z.infer<typeof StepStatusSchema>;
|
|
53
|
-
|
|
54
|
-
const StepErrorSchema = z.object({
|
|
55
|
-
message: z.string(),
|
|
56
|
-
code: z.string().optional(),
|
|
57
|
-
retryable: z.boolean(),
|
|
58
|
-
});
|
|
59
|
-
export type StepError = z.infer<typeof StepErrorSchema>;
|
|
60
|
-
|
|
61
|
-
const StepStateSchema = z.object({
|
|
62
|
-
status: StepStatusSchema,
|
|
63
|
-
error: StepErrorSchema.optional(),
|
|
64
|
-
});
|
|
65
|
-
export type StepState = z.infer<typeof StepStateSchema>;
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Full wizard state — serializable to JSON for persistence.
|
|
69
|
-
*
|
|
70
|
-
* All fields are plain data (no functions, no class instances).
|
|
71
|
-
* Transport results that contain ArrayBuffer (non-serializable) are
|
|
72
|
-
* intentionally excluded; the wizard tracks only the structured
|
|
73
|
-
* metadata from each step.
|
|
74
|
-
*/
|
|
75
|
-
export interface MigrationWizardState {
|
|
76
|
-
currentStep: WizardStep;
|
|
77
|
-
direction?: MigrationDirection;
|
|
78
|
-
steps: Record<WizardStep, StepState>;
|
|
79
|
-
|
|
80
|
-
/** Bundle file data is not persisted — must be re-provided on resume. */
|
|
81
|
-
hasBundleData: boolean;
|
|
82
|
-
|
|
83
|
-
/** Validation result from the validate step. */
|
|
84
|
-
validateResult?: ValidateResponse;
|
|
85
|
-
|
|
86
|
-
/** Preflight result from the preflight-review step. */
|
|
87
|
-
preflightResult?: ImportPreflightResponse;
|
|
88
|
-
|
|
89
|
-
/** Export result metadata (no binary data). */
|
|
90
|
-
exportResult?:
|
|
91
|
-
| ExportManagedResult
|
|
92
|
-
| {
|
|
93
|
-
ok: true;
|
|
94
|
-
filename: string;
|
|
95
|
-
schemaVersion: number;
|
|
96
|
-
checksum: string;
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
/** Import commit result. */
|
|
100
|
-
importResult?: ImportCommitResponse;
|
|
101
|
-
|
|
102
|
-
/** Credential import results from the transfer step (if credentials were in the bundle). */
|
|
103
|
-
credentialsImported?: {
|
|
104
|
-
total: number;
|
|
105
|
-
succeeded: number;
|
|
106
|
-
failed: number;
|
|
107
|
-
failedAccounts: string[];
|
|
108
|
-
skippedPlatform?: number;
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
/** Timestamp of last state change (ISO 8601). */
|
|
112
|
-
updatedAt: string;
|
|
113
|
-
|
|
114
|
-
/** Timestamp of wizard creation (ISO 8601). */
|
|
115
|
-
createdAt: string;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Envelope for the persisted wizard state. The step payloads
|
|
120
|
-
* (validateResult, preflightResult, …) are opaque here — they pass through
|
|
121
|
-
* untouched and are the transport module's contract, validated at its own
|
|
122
|
-
* boundary.
|
|
123
|
-
*/
|
|
124
|
-
const WizardStateSchema = z.looseObject({
|
|
125
|
-
currentStep: WizardStepSchema,
|
|
126
|
-
direction: z
|
|
127
|
-
.enum(["managed-to-self-hosted", "self-hosted-to-managed"])
|
|
128
|
-
.optional(),
|
|
129
|
-
steps: z.record(z.string(), StepStateSchema),
|
|
130
|
-
hasBundleData: z.boolean(),
|
|
131
|
-
updatedAt: z.string(),
|
|
132
|
-
createdAt: z.string(),
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
// ---------------------------------------------------------------------------
|
|
136
|
-
// Step ordering and transitions
|
|
137
|
-
// ---------------------------------------------------------------------------
|
|
138
|
-
|
|
139
|
-
const STEP_ORDER: readonly WizardStep[] = WizardStepSchema.options;
|
|
140
|
-
|
|
141
|
-
const STEP_INDEX = new Map<WizardStep, number>(
|
|
142
|
-
STEP_ORDER.map((step, i) => [step, i]),
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Steps that are available for a given migration direction.
|
|
147
|
-
* Both directions use the same steps, but the semantics differ:
|
|
148
|
-
*
|
|
149
|
-
* - managed-to-self-hosted: export from managed, import to local runtime
|
|
150
|
-
* - self-hosted-to-managed: export from local runtime, import to managed
|
|
151
|
-
*/
|
|
152
|
-
function stepsForDirection(
|
|
153
|
-
_direction: MigrationDirection,
|
|
154
|
-
): readonly WizardStep[] {
|
|
155
|
-
return STEP_ORDER;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// ---------------------------------------------------------------------------
|
|
159
|
-
// Transition validation
|
|
160
|
-
// ---------------------------------------------------------------------------
|
|
161
|
-
|
|
162
|
-
export interface TransitionResult {
|
|
163
|
-
valid: boolean;
|
|
164
|
-
reason?: string;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Check whether advancing from `current` to `next` is a valid transition.
|
|
169
|
-
*
|
|
170
|
-
* Rules:
|
|
171
|
-
* - Can advance to the immediately next step if the current step is in 'success' status.
|
|
172
|
-
* - Can go back to any earlier step (allows re-doing steps).
|
|
173
|
-
* - Cannot skip steps forward.
|
|
174
|
-
* - 'complete' is a terminal step — no further transitions.
|
|
175
|
-
*/
|
|
176
|
-
export function validateWizardTransition(
|
|
177
|
-
state: MigrationWizardState,
|
|
178
|
-
next: WizardStep,
|
|
179
|
-
): TransitionResult {
|
|
180
|
-
const current = state.currentStep;
|
|
181
|
-
if (current === next) {
|
|
182
|
-
return { valid: true };
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (current === "complete") {
|
|
186
|
-
return {
|
|
187
|
-
valid: false,
|
|
188
|
-
reason: 'Cannot transition from terminal step "complete"',
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const currentIdx = STEP_INDEX.get(current)!;
|
|
193
|
-
const nextIdx = STEP_INDEX.get(next)!;
|
|
194
|
-
|
|
195
|
-
// Going backward is always allowed
|
|
196
|
-
if (nextIdx < currentIdx) {
|
|
197
|
-
return { valid: true };
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Going forward: can only advance one step at a time, and current must be 'success'
|
|
201
|
-
if (nextIdx > currentIdx + 1) {
|
|
202
|
-
return {
|
|
203
|
-
valid: false,
|
|
204
|
-
reason: `Cannot skip from "${current}" to "${next}"`,
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// nextIdx === currentIdx + 1
|
|
209
|
-
if (state.steps[current].status !== "success") {
|
|
210
|
-
return {
|
|
211
|
-
valid: false,
|
|
212
|
-
reason: `Cannot advance from "${current}" — step status is "${state.steps[current].status}", expected "success"`,
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return { valid: true };
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// ---------------------------------------------------------------------------
|
|
220
|
-
// State factory
|
|
221
|
-
// ---------------------------------------------------------------------------
|
|
222
|
-
|
|
223
|
-
function makeStepStates(): Record<WizardStep, StepState> {
|
|
224
|
-
const steps = {} as Record<WizardStep, StepState>;
|
|
225
|
-
for (const step of STEP_ORDER) {
|
|
226
|
-
steps[step] = { status: "idle" };
|
|
227
|
-
}
|
|
228
|
-
return steps;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Create a fresh wizard state.
|
|
233
|
-
*/
|
|
234
|
-
export function createWizardState(): MigrationWizardState {
|
|
235
|
-
const now = new Date().toISOString();
|
|
236
|
-
return {
|
|
237
|
-
currentStep: "select-direction",
|
|
238
|
-
steps: makeStepStates(),
|
|
239
|
-
hasBundleData: false,
|
|
240
|
-
updatedAt: now,
|
|
241
|
-
createdAt: now,
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// ---------------------------------------------------------------------------
|
|
246
|
-
// State transitions — pure functions that return a new state
|
|
247
|
-
// ---------------------------------------------------------------------------
|
|
248
|
-
|
|
249
|
-
function touch(state: MigrationWizardState): MigrationWizardState {
|
|
250
|
-
return { ...state, updatedAt: new Date().toISOString() };
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
function setStepStatus(
|
|
254
|
-
state: MigrationWizardState,
|
|
255
|
-
step: WizardStep,
|
|
256
|
-
status: StepStatus,
|
|
257
|
-
error?: StepError,
|
|
258
|
-
): MigrationWizardState {
|
|
259
|
-
return touch({
|
|
260
|
-
...state,
|
|
261
|
-
steps: {
|
|
262
|
-
...state.steps,
|
|
263
|
-
[step]: { status, ...(error ? { error } : {}) },
|
|
264
|
-
},
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Set the migration direction and advance to the next step.
|
|
270
|
-
*/
|
|
271
|
-
export function selectDirection(
|
|
272
|
-
state: MigrationWizardState,
|
|
273
|
-
direction: MigrationDirection,
|
|
274
|
-
): MigrationWizardState {
|
|
275
|
-
if (state.currentStep !== "select-direction") {
|
|
276
|
-
throw new Error(
|
|
277
|
-
`selectDirection called in wrong step: "${state.currentStep}"`,
|
|
278
|
-
);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
const updated = setStepStatus(state, "select-direction", "success");
|
|
282
|
-
return touch({
|
|
283
|
-
...updated,
|
|
284
|
-
direction,
|
|
285
|
-
currentStep: "upload-bundle",
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Record that bundle data has been provided and advance to validate.
|
|
291
|
-
*/
|
|
292
|
-
export function setBundleUploaded(
|
|
293
|
-
state: MigrationWizardState,
|
|
294
|
-
): MigrationWizardState {
|
|
295
|
-
if (state.currentStep !== "upload-bundle") {
|
|
296
|
-
throw new Error(
|
|
297
|
-
`setBundleUploaded called in wrong step: "${state.currentStep}"`,
|
|
298
|
-
);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const updated = setStepStatus(state, "upload-bundle", "success");
|
|
302
|
-
return touch({
|
|
303
|
-
...updated,
|
|
304
|
-
hasBundleData: true,
|
|
305
|
-
currentStep: "validate",
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Go back to a previous step. Resets all steps after the target step
|
|
311
|
-
* to 'idle' so they can be re-executed.
|
|
312
|
-
*/
|
|
313
|
-
export function goBackTo(
|
|
314
|
-
state: MigrationWizardState,
|
|
315
|
-
targetStep: WizardStep,
|
|
316
|
-
): MigrationWizardState {
|
|
317
|
-
const result = validateWizardTransition(state, targetStep);
|
|
318
|
-
if (!result.valid) {
|
|
319
|
-
throw new Error(`Invalid back-navigation: ${result.reason}`);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const targetIdx = STEP_INDEX.get(targetStep)!;
|
|
323
|
-
const newSteps = { ...state.steps };
|
|
324
|
-
|
|
325
|
-
// Reset the target step and all steps after it to idle
|
|
326
|
-
for (const step of STEP_ORDER) {
|
|
327
|
-
const idx = STEP_INDEX.get(step)!;
|
|
328
|
-
if (idx >= targetIdx) {
|
|
329
|
-
newSteps[step] = { status: "idle" };
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
return touch({
|
|
334
|
-
...state,
|
|
335
|
-
currentStep: targetStep,
|
|
336
|
-
steps: newSteps,
|
|
337
|
-
// Clear results for steps that were reset
|
|
338
|
-
...(targetIdx <= STEP_INDEX.get("validate")!
|
|
339
|
-
? { validateResult: undefined }
|
|
340
|
-
: {}),
|
|
341
|
-
...(targetIdx <= STEP_INDEX.get("preflight-review")!
|
|
342
|
-
? { preflightResult: undefined }
|
|
343
|
-
: {}),
|
|
344
|
-
...(targetIdx <= STEP_INDEX.get("transfer")!
|
|
345
|
-
? {
|
|
346
|
-
exportResult: undefined,
|
|
347
|
-
importResult: undefined,
|
|
348
|
-
credentialsImported: undefined,
|
|
349
|
-
}
|
|
350
|
-
: {}),
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// ---------------------------------------------------------------------------
|
|
355
|
-
// Async step executors — coordinate transport calls with state updates
|
|
356
|
-
// ---------------------------------------------------------------------------
|
|
357
|
-
|
|
358
|
-
/**
|
|
359
|
-
* Options for executing a wizard step, including transport config
|
|
360
|
-
* and an optional state update callback for UI reactivity.
|
|
361
|
-
*/
|
|
362
|
-
export interface StepExecutorOptions {
|
|
363
|
-
sourceConfig: TransportConfig;
|
|
364
|
-
destConfig: TransportConfig;
|
|
365
|
-
bundleData: ArrayBuffer;
|
|
366
|
-
onStateChange?: (state: MigrationWizardState) => void;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
* Build a StepError from a caught exception.
|
|
371
|
-
*/
|
|
372
|
-
function toStepError(err: unknown): StepError {
|
|
373
|
-
if (err instanceof MigrationTransportError) {
|
|
374
|
-
const retryable =
|
|
375
|
-
err.statusCode === 429 ||
|
|
376
|
-
(err.statusCode >= 500 && err.statusCode <= 504);
|
|
377
|
-
return {
|
|
378
|
-
message: err.message,
|
|
379
|
-
code: `HTTP_${err.statusCode}`,
|
|
380
|
-
retryable,
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
if (err instanceof Error) {
|
|
384
|
-
return { message: err.message, retryable: false };
|
|
385
|
-
}
|
|
386
|
-
return { message: String(err), retryable: false };
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
/**
|
|
390
|
-
* Execute the validate step: call validateBundle and update state.
|
|
391
|
-
*/
|
|
392
|
-
export async function executeValidateStep(
|
|
393
|
-
state: MigrationWizardState,
|
|
394
|
-
options: StepExecutorOptions,
|
|
395
|
-
): Promise<MigrationWizardState> {
|
|
396
|
-
if (state.currentStep !== "validate") {
|
|
397
|
-
throw new Error(
|
|
398
|
-
`executeValidateStep called in wrong step: "${state.currentStep}"`,
|
|
399
|
-
);
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
let current = setStepStatus(state, "validate", "loading");
|
|
403
|
-
options.onStateChange?.(current);
|
|
404
|
-
|
|
405
|
-
try {
|
|
406
|
-
const result = await validateBundle(options.destConfig, options.bundleData);
|
|
407
|
-
if (result.is_valid) {
|
|
408
|
-
current = setStepStatus(current, "validate", "success");
|
|
409
|
-
current = {
|
|
410
|
-
...current,
|
|
411
|
-
validateResult: result,
|
|
412
|
-
currentStep: "preflight-review",
|
|
413
|
-
};
|
|
414
|
-
} else {
|
|
415
|
-
current = setStepStatus(current, "validate", "error", {
|
|
416
|
-
message:
|
|
417
|
-
result.errors.map((e) => e.message).join("; ") ||
|
|
418
|
-
"Bundle validation failed",
|
|
419
|
-
code: result.errors[0]?.code,
|
|
420
|
-
retryable: true,
|
|
421
|
-
});
|
|
422
|
-
current = { ...current, validateResult: result };
|
|
423
|
-
}
|
|
424
|
-
} catch (err) {
|
|
425
|
-
current = setStepStatus(current, "validate", "error", toStepError(err));
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
current = touch(current);
|
|
429
|
-
options.onStateChange?.(current);
|
|
430
|
-
return current;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
/**
|
|
434
|
-
* Execute the preflight-review step: call importPreflight and update state.
|
|
435
|
-
*/
|
|
436
|
-
export async function executePreflightStep(
|
|
437
|
-
state: MigrationWizardState,
|
|
438
|
-
options: StepExecutorOptions,
|
|
439
|
-
): Promise<MigrationWizardState> {
|
|
440
|
-
if (state.currentStep !== "preflight-review") {
|
|
441
|
-
throw new Error(
|
|
442
|
-
`executePreflightStep called in wrong step: "${state.currentStep}"`,
|
|
443
|
-
);
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
let current = setStepStatus(state, "preflight-review", "loading");
|
|
447
|
-
options.onStateChange?.(current);
|
|
448
|
-
|
|
449
|
-
try {
|
|
450
|
-
const result = await importPreflight(
|
|
451
|
-
options.destConfig,
|
|
452
|
-
options.bundleData,
|
|
453
|
-
);
|
|
454
|
-
if (result.can_import) {
|
|
455
|
-
current = setStepStatus(current, "preflight-review", "success");
|
|
456
|
-
current = {
|
|
457
|
-
...current,
|
|
458
|
-
preflightResult: result,
|
|
459
|
-
currentStep: "transfer",
|
|
460
|
-
};
|
|
461
|
-
} else if ("validation" in result) {
|
|
462
|
-
current = setStepStatus(current, "preflight-review", "error", {
|
|
463
|
-
message:
|
|
464
|
-
result.validation.errors.map((e) => e.message).join("; ") ||
|
|
465
|
-
"Import preflight validation failed",
|
|
466
|
-
code: result.validation.errors[0]?.code,
|
|
467
|
-
retryable: true,
|
|
468
|
-
});
|
|
469
|
-
current = { ...current, preflightResult: result };
|
|
470
|
-
} else {
|
|
471
|
-
current = setStepStatus(current, "preflight-review", "error", {
|
|
472
|
-
message:
|
|
473
|
-
result.conflicts.map((c) => c.message).join("; ") ||
|
|
474
|
-
"Import blocked by conflicts",
|
|
475
|
-
code: result.conflicts[0]?.code,
|
|
476
|
-
retryable: true,
|
|
477
|
-
});
|
|
478
|
-
current = { ...current, preflightResult: result };
|
|
479
|
-
}
|
|
480
|
-
} catch (err) {
|
|
481
|
-
current = setStepStatus(
|
|
482
|
-
current,
|
|
483
|
-
"preflight-review",
|
|
484
|
-
"error",
|
|
485
|
-
toStepError(err),
|
|
486
|
-
);
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
current = touch(current);
|
|
490
|
-
options.onStateChange?.(current);
|
|
491
|
-
return current;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
/**
|
|
495
|
-
* Execute the transfer step: import the validated bundle to the destination.
|
|
496
|
-
*/
|
|
497
|
-
export async function executeTransferStep(
|
|
498
|
-
state: MigrationWizardState,
|
|
499
|
-
options: StepExecutorOptions,
|
|
500
|
-
): Promise<MigrationWizardState> {
|
|
501
|
-
if (state.currentStep !== "transfer") {
|
|
502
|
-
throw new Error(
|
|
503
|
-
`executeTransferStep called in wrong step: "${state.currentStep}"`,
|
|
504
|
-
);
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
let current = setStepStatus(state, "transfer", "loading");
|
|
508
|
-
options.onStateChange?.(current);
|
|
509
|
-
|
|
510
|
-
try {
|
|
511
|
-
// Import the same bundle that was validated and preflighted
|
|
512
|
-
const importResult = await importCommit(
|
|
513
|
-
options.destConfig,
|
|
514
|
-
options.bundleData,
|
|
515
|
-
);
|
|
516
|
-
current = { ...current, importResult };
|
|
517
|
-
|
|
518
|
-
if (importResult.success) {
|
|
519
|
-
current = setStepStatus(current, "transfer", "success");
|
|
520
|
-
// Extract credential import results from the response (if present)
|
|
521
|
-
current = {
|
|
522
|
-
...current,
|
|
523
|
-
currentStep: "rebind-secrets",
|
|
524
|
-
...(importResult.credentialsImported
|
|
525
|
-
? { credentialsImported: importResult.credentialsImported }
|
|
526
|
-
: {}),
|
|
527
|
-
};
|
|
528
|
-
} else {
|
|
529
|
-
current = setStepStatus(current, "transfer", "error", {
|
|
530
|
-
message:
|
|
531
|
-
importResult.message || `Import failed: ${importResult.reason}`,
|
|
532
|
-
code: importResult.reason,
|
|
533
|
-
retryable: true,
|
|
534
|
-
});
|
|
535
|
-
}
|
|
536
|
-
} catch (err) {
|
|
537
|
-
current = setStepStatus(current, "transfer", "error", toStepError(err));
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
current = touch(current);
|
|
541
|
-
options.onStateChange?.(current);
|
|
542
|
-
return current;
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
/**
|
|
546
|
-
* Mark the rebind-secrets step as complete.
|
|
547
|
-
*
|
|
548
|
-
* This step is UI-driven (the user confirms they've rebound secrets
|
|
549
|
-
* and channels). The wizard just tracks its completion.
|
|
550
|
-
*/
|
|
551
|
-
export function completeRebindSecrets(
|
|
552
|
-
state: MigrationWizardState,
|
|
553
|
-
): MigrationWizardState {
|
|
554
|
-
if (state.currentStep !== "rebind-secrets") {
|
|
555
|
-
throw new Error(
|
|
556
|
-
`completeRebindSecrets called in wrong step: "${state.currentStep}"`,
|
|
557
|
-
);
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
let current = setStepStatus(state, "rebind-secrets", "success");
|
|
561
|
-
current = { ...current, currentStep: "complete" };
|
|
562
|
-
current = setStepStatus(current, "complete", "success");
|
|
563
|
-
return touch(current);
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
// ---------------------------------------------------------------------------
|
|
567
|
-
// Persistence helpers
|
|
568
|
-
// ---------------------------------------------------------------------------
|
|
569
|
-
|
|
570
|
-
/**
|
|
571
|
-
* Serialize wizard state to a JSON string for persistence.
|
|
572
|
-
*
|
|
573
|
-
* The state is already plain data — this is a convenience wrapper
|
|
574
|
-
* that validates the shape and strips any undefined values.
|
|
575
|
-
*/
|
|
576
|
-
export function serializeWizardState(state: MigrationWizardState): string {
|
|
577
|
-
return JSON.stringify(state);
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
/**
|
|
581
|
-
* Deserialize a persisted wizard state from JSON.
|
|
582
|
-
*
|
|
583
|
-
* Returns undefined if the input is invalid or corrupted.
|
|
584
|
-
* On successful deserialization, `hasBundleData` is set to false
|
|
585
|
-
* because binary ArrayBuffer data cannot be persisted — the user
|
|
586
|
-
* must re-provide the bundle file after resuming.
|
|
587
|
-
*/
|
|
588
|
-
export function deserializeWizardState(
|
|
589
|
-
json: string,
|
|
590
|
-
): MigrationWizardState | undefined {
|
|
591
|
-
try {
|
|
592
|
-
const parsed = WizardStateSchema.safeParse(JSON.parse(json));
|
|
593
|
-
if (!parsed.success) {
|
|
594
|
-
return undefined;
|
|
595
|
-
}
|
|
596
|
-
const state = parsed.data;
|
|
597
|
-
|
|
598
|
-
// Every step must be present (the persisted record is exhaustive).
|
|
599
|
-
for (const step of STEP_ORDER) {
|
|
600
|
-
if (!state.steps[step]) {
|
|
601
|
-
return undefined;
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
// Bundle data is never persisted — must be re-provided
|
|
606
|
-
return { ...state, hasBundleData: false };
|
|
607
|
-
} catch {
|
|
608
|
-
return undefined;
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
/**
|
|
613
|
-
* Check if a persisted wizard state can be meaningfully resumed.
|
|
614
|
-
*
|
|
615
|
-
* A wizard is resumable if:
|
|
616
|
-
* - It has a direction selected
|
|
617
|
-
* - It is not already complete
|
|
618
|
-
* - It is not on the first step (nothing to resume)
|
|
619
|
-
*/
|
|
620
|
-
export function isResumable(state: MigrationWizardState): boolean {
|
|
621
|
-
if (!state.direction) {
|
|
622
|
-
return false;
|
|
623
|
-
}
|
|
624
|
-
if (state.currentStep === "complete") {
|
|
625
|
-
return false;
|
|
626
|
-
}
|
|
627
|
-
if (state.currentStep === "select-direction") {
|
|
628
|
-
return false;
|
|
629
|
-
}
|
|
630
|
-
return true;
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
/**
|
|
634
|
-
* Prepare a deserialized state for resumption.
|
|
635
|
-
*
|
|
636
|
-
* If the current step was in 'loading' status when the app closed,
|
|
637
|
-
* resets it to 'idle' so the user can retry. Steps that require
|
|
638
|
-
* bundle data (validate, preflight-review, transfer) will need the
|
|
639
|
-
* bundle re-uploaded if it was not persisted.
|
|
640
|
-
*/
|
|
641
|
-
export function prepareForResume(
|
|
642
|
-
state: MigrationWizardState,
|
|
643
|
-
): MigrationWizardState {
|
|
644
|
-
let current = { ...state };
|
|
645
|
-
|
|
646
|
-
// Reset loading steps to idle (they were interrupted)
|
|
647
|
-
const step = current.currentStep;
|
|
648
|
-
if (current.steps[step].status === "loading") {
|
|
649
|
-
current = setStepStatus(current, step, "idle");
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
// If we need bundle data but don't have it, rewind to upload-bundle
|
|
653
|
-
// and reset all downstream steps + stale results (mirrors goBackTo logic)
|
|
654
|
-
const needsBundleData =
|
|
655
|
-
step === "validate" || step === "preflight-review" || step === "transfer";
|
|
656
|
-
if (needsBundleData && !current.hasBundleData) {
|
|
657
|
-
const uploadBundleIdx = STEP_INDEX.get("upload-bundle")!;
|
|
658
|
-
const newSteps = { ...current.steps };
|
|
659
|
-
for (const s of STEP_ORDER) {
|
|
660
|
-
const idx = STEP_INDEX.get(s)!;
|
|
661
|
-
if (idx >= uploadBundleIdx) {
|
|
662
|
-
newSteps[s] = { status: "idle" };
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
current = {
|
|
666
|
-
...current,
|
|
667
|
-
currentStep: "upload-bundle",
|
|
668
|
-
steps: newSteps,
|
|
669
|
-
validateResult: undefined,
|
|
670
|
-
preflightResult: undefined,
|
|
671
|
-
exportResult: undefined,
|
|
672
|
-
importResult: undefined,
|
|
673
|
-
credentialsImported: undefined,
|
|
674
|
-
};
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
return touch(current);
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
// ---------------------------------------------------------------------------
|
|
681
|
-
// Query helpers
|
|
682
|
-
// ---------------------------------------------------------------------------
|
|
683
|
-
|
|
684
|
-
/**
|
|
685
|
-
* Get the ordered list of steps for the current wizard configuration.
|
|
686
|
-
*/
|
|
687
|
-
export function getStepOrder(
|
|
688
|
-
state: MigrationWizardState,
|
|
689
|
-
): readonly WizardStep[] {
|
|
690
|
-
return state.direction ? stepsForDirection(state.direction) : STEP_ORDER;
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
/**
|
|
694
|
-
* Get the index of the current step (0-based).
|
|
695
|
-
*/
|
|
696
|
-
export function getCurrentStepIndex(state: MigrationWizardState): number {
|
|
697
|
-
return STEP_INDEX.get(state.currentStep)!;
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
/**
|
|
701
|
-
* Get the total number of steps.
|
|
702
|
-
*/
|
|
703
|
-
export function getTotalSteps(): number {
|
|
704
|
-
return STEP_ORDER.length;
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
/**
|
|
708
|
-
* Check if a step is accessible (can be navigated to) from the current state.
|
|
709
|
-
*/
|
|
710
|
-
export function isStepAccessible(
|
|
711
|
-
state: MigrationWizardState,
|
|
712
|
-
step: WizardStep,
|
|
713
|
-
): boolean {
|
|
714
|
-
return validateWizardTransition(state, step).valid;
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
/**
|
|
718
|
-
* Check if the wizard is in a terminal (completed) state.
|
|
719
|
-
*/
|
|
720
|
-
export function isWizardComplete(state: MigrationWizardState): boolean {
|
|
721
|
-
return (
|
|
722
|
-
state.currentStep === "complete" &&
|
|
723
|
-
state.steps["complete"].status === "success"
|
|
724
|
-
);
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
/**
|
|
728
|
-
* Check if the current step has an error that can be retried.
|
|
729
|
-
*/
|
|
730
|
-
export function canRetryCurrentStep(state: MigrationWizardState): boolean {
|
|
731
|
-
const stepState = state.steps[state.currentStep];
|
|
732
|
-
return stepState.status === "error" && (stepState.error?.retryable ?? false);
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
/**
|
|
736
|
-
* Reset the current step's error state to idle for retry.
|
|
737
|
-
*/
|
|
738
|
-
export function resetStepForRetry(
|
|
739
|
-
state: MigrationWizardState,
|
|
740
|
-
): MigrationWizardState {
|
|
741
|
-
if (!canRetryCurrentStep(state)) {
|
|
742
|
-
throw new Error(
|
|
743
|
-
`Cannot retry step "${state.currentStep}" — not in a retryable error state`,
|
|
744
|
-
);
|
|
745
|
-
}
|
|
746
|
-
return setStepStatus(state, state.currentStep, "idle");
|
|
747
|
-
}
|