@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,405 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Analyzes a validated .vbundle archive to produce a dry-run import report.
|
|
3
|
-
*
|
|
4
|
-
* Given a valid .vbundle archive (already validated), this module inspects
|
|
5
|
-
* its manifest and contents to determine what would happen if the bundle
|
|
6
|
-
* were imported. It compares the bundle's files against the current
|
|
7
|
-
* assistant state on disk and reports:
|
|
8
|
-
* - Which files would be written or overwritten
|
|
9
|
-
* - Size changes for each file
|
|
10
|
-
* - Whether existing data would be replaced
|
|
11
|
-
* - Any potential conflicts
|
|
12
|
-
*
|
|
13
|
-
* This is a read-only analysis — no files are written or modified.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { createHash } from "node:crypto";
|
|
17
|
-
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
18
|
-
import { join, resolve } from "node:path";
|
|
19
|
-
|
|
20
|
-
import { resolveGuardianPersonaPath } from "../../prompts/persona-resolver.js";
|
|
21
|
-
import { getLogger } from "../../util/logger.js";
|
|
22
|
-
import {
|
|
23
|
-
isGatewayArchivePath,
|
|
24
|
-
isRetiredArchivePath,
|
|
25
|
-
} from "./vbundle-import-policy.js";
|
|
26
|
-
import type { ManifestType } from "./vbundle-validator.js";
|
|
27
|
-
|
|
28
|
-
const log = getLogger("vbundle-import-analyzer");
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Only these prompt filenames are accepted during import.
|
|
32
|
-
*
|
|
33
|
-
* `USER.md` is retained for backward compatibility with legacy bundles —
|
|
34
|
-
* on import, its content is translated to `users/<slug>.md` at the
|
|
35
|
-
* current guardian's location (see `DefaultPathResolver.resolve`).
|
|
36
|
-
*/
|
|
37
|
-
const ALLOWED_PROMPT_FILENAMES = new Set(["IDENTITY.md", "SOUL.md", "USER.md"]);
|
|
38
|
-
|
|
39
|
-
/** Archive path for the legacy guardian user persona file. */
|
|
40
|
-
const LEGACY_USER_MD_ARCHIVE_PATH = "prompts/USER.md";
|
|
41
|
-
|
|
42
|
-
// ---------------------------------------------------------------------------
|
|
43
|
-
// Public types
|
|
44
|
-
// ---------------------------------------------------------------------------
|
|
45
|
-
|
|
46
|
-
type ImportAction = "create" | "overwrite" | "unchanged" | "skip";
|
|
47
|
-
|
|
48
|
-
interface ImportFileReport {
|
|
49
|
-
/** Archive path (e.g. "data/db/assistant.db") */
|
|
50
|
-
path: string;
|
|
51
|
-
/** What would happen to this file on import */
|
|
52
|
-
action: ImportAction;
|
|
53
|
-
/** Size of the file in the bundle (bytes) */
|
|
54
|
-
bundle_size: number;
|
|
55
|
-
/** Size of the existing file on disk, or null if it does not exist */
|
|
56
|
-
current_size: number | null;
|
|
57
|
-
/** SHA-256 of the file in the bundle */
|
|
58
|
-
bundle_sha256: string;
|
|
59
|
-
/** SHA-256 of the existing file on disk, or null if it does not exist */
|
|
60
|
-
current_sha256: string | null;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
interface ImportConflict {
|
|
64
|
-
code: string;
|
|
65
|
-
message: string;
|
|
66
|
-
path?: string;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export interface ImportDryRunReport {
|
|
70
|
-
/** Whether the import can proceed (bundle is valid and no blocking conflicts) */
|
|
71
|
-
can_import: boolean;
|
|
72
|
-
/** Summary of what would happen */
|
|
73
|
-
summary: {
|
|
74
|
-
total_files: number;
|
|
75
|
-
files_to_create: number;
|
|
76
|
-
files_to_overwrite: number;
|
|
77
|
-
files_unchanged: number;
|
|
78
|
-
files_to_skip: number;
|
|
79
|
-
};
|
|
80
|
-
/** Per-file analysis of what would change */
|
|
81
|
-
files: ImportFileReport[];
|
|
82
|
-
/** Any conflicts or warnings that might block or complicate import */
|
|
83
|
-
conflicts: ImportConflict[];
|
|
84
|
-
/** The manifest from the bundle */
|
|
85
|
-
manifest: ManifestType;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// ---------------------------------------------------------------------------
|
|
89
|
-
// Path mapping
|
|
90
|
-
// ---------------------------------------------------------------------------
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Maps archive paths to their corresponding locations on disk.
|
|
94
|
-
* This is the canonical mapping used during actual import (PR-5) —
|
|
95
|
-
* dry-run uses the same mapping for consistency.
|
|
96
|
-
*/
|
|
97
|
-
export interface PathResolver {
|
|
98
|
-
resolve(archivePath: string): string | null;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export class DefaultPathResolver implements PathResolver {
|
|
102
|
-
/**
|
|
103
|
-
* @param workspaceDir absolute path to the workspace directory.
|
|
104
|
-
* @param hooksDir absolute path to the hooks directory.
|
|
105
|
-
* @param guardianPersonaPathResolver function that returns the
|
|
106
|
-
* current guardian's persona path (`users/<slug>.md`) or `null`
|
|
107
|
-
* when no guardian exists. Defaults to the production
|
|
108
|
-
* `resolveGuardianPersonaPath` helper; tests inject a stub so they
|
|
109
|
-
* don't need to mock the contact store.
|
|
110
|
-
*/
|
|
111
|
-
constructor(
|
|
112
|
-
private workspaceDir?: string,
|
|
113
|
-
private hooksDir?: string,
|
|
114
|
-
private guardianPersonaPathResolver: () =>
|
|
115
|
-
| string
|
|
116
|
-
| null = resolveGuardianPersonaPath,
|
|
117
|
-
) {}
|
|
118
|
-
|
|
119
|
-
resolve(archivePath: string): string | null {
|
|
120
|
-
// Skip credential entries — handled separately by the credential import step
|
|
121
|
-
if (archivePath.startsWith("credentials/")) {
|
|
122
|
-
return null;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Retired-feature files (both legacy and workspace-format spellings)
|
|
126
|
-
// must never resolve to a disk target — resolving would write them
|
|
127
|
-
// back into the workspace on import. The analyzer and both importers
|
|
128
|
-
// turn the resulting null into a silent non-blocking skip.
|
|
129
|
-
if (
|
|
130
|
-
isRetiredArchivePath(archivePath) ||
|
|
131
|
-
isGatewayArchivePath(archivePath)
|
|
132
|
-
) {
|
|
133
|
-
return null;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// New format: workspace/ prefix — maps directly into the workspace dir
|
|
137
|
-
if (archivePath.startsWith("workspace/") && this.workspaceDir) {
|
|
138
|
-
const relPath = archivePath.slice("workspace/".length);
|
|
139
|
-
if (!relPath) {
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
const resolved = resolve(this.workspaceDir, relPath);
|
|
143
|
-
const wsRoot = resolve(this.workspaceDir);
|
|
144
|
-
// Path traversal containment
|
|
145
|
-
if (resolved !== wsRoot && !resolved.startsWith(wsRoot + "/")) {
|
|
146
|
-
return null;
|
|
147
|
-
}
|
|
148
|
-
return resolved;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Backward compat: old bundle formats with specific archive paths
|
|
152
|
-
if (archivePath === "data/db/assistant.db" && this.workspaceDir) {
|
|
153
|
-
return join(this.workspaceDir, "data", "db", "assistant.db");
|
|
154
|
-
}
|
|
155
|
-
if (archivePath === "config/settings.json" && this.workspaceDir) {
|
|
156
|
-
return join(this.workspaceDir, "config.json");
|
|
157
|
-
}
|
|
158
|
-
if (archivePath.startsWith("skills/") && this.workspaceDir) {
|
|
159
|
-
const resolved = resolve(
|
|
160
|
-
this.workspaceDir,
|
|
161
|
-
"skills",
|
|
162
|
-
archivePath.slice("skills/".length),
|
|
163
|
-
);
|
|
164
|
-
const skillsRoot = resolve(this.workspaceDir, "skills");
|
|
165
|
-
if (resolved !== skillsRoot && !resolved.startsWith(skillsRoot + "/")) {
|
|
166
|
-
return null;
|
|
167
|
-
}
|
|
168
|
-
return resolved;
|
|
169
|
-
}
|
|
170
|
-
if (archivePath.startsWith("prompts/") && this.workspaceDir) {
|
|
171
|
-
// Old bundles stored prompts as prompts/IDENTITY.md etc — these map
|
|
172
|
-
// to the workspace root (e.g. workspace/IDENTITY.md).
|
|
173
|
-
// Only accepted prompt filenames resolve — unknown entries are
|
|
174
|
-
// skipped so they cannot trigger workspace clearing.
|
|
175
|
-
const filename = archivePath.slice("prompts/".length);
|
|
176
|
-
if (!ALLOWED_PROMPT_FILENAMES.has(filename)) {
|
|
177
|
-
return null;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Legacy USER.md translation: rewrite the destination to the
|
|
181
|
-
// current guardian's per-user persona file `users/<slug>.md`.
|
|
182
|
-
// Guardian-less workspaces return null → importer skips with a
|
|
183
|
-
// warning (see vbundle-importer.ts). This lookup runs against
|
|
184
|
-
// whatever DB state is live at the time of resolve — typically
|
|
185
|
-
// the pre-import workspace, which is the common upgrade case.
|
|
186
|
-
if (archivePath === LEGACY_USER_MD_ARCHIVE_PATH) {
|
|
187
|
-
const guardianPath = this.guardianPersonaPathResolver();
|
|
188
|
-
if (!guardianPath) {
|
|
189
|
-
log.warn(
|
|
190
|
-
{ path: archivePath },
|
|
191
|
-
"Legacy prompts/USER.md has no guardian target — will be skipped on import",
|
|
192
|
-
);
|
|
193
|
-
return null;
|
|
194
|
-
}
|
|
195
|
-
// Containment check: guardian path must live under the workspace.
|
|
196
|
-
const wsRoot = resolve(this.workspaceDir);
|
|
197
|
-
const guardianResolved = resolve(guardianPath);
|
|
198
|
-
if (
|
|
199
|
-
guardianResolved !== wsRoot &&
|
|
200
|
-
!guardianResolved.startsWith(wsRoot + "/")
|
|
201
|
-
) {
|
|
202
|
-
log.warn(
|
|
203
|
-
{ path: archivePath, guardianPath },
|
|
204
|
-
"Guardian persona path falls outside workspace — refusing to write",
|
|
205
|
-
);
|
|
206
|
-
return null;
|
|
207
|
-
}
|
|
208
|
-
return guardianResolved;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const resolved = resolve(this.workspaceDir, filename);
|
|
212
|
-
const wsRoot = resolve(this.workspaceDir);
|
|
213
|
-
if (resolved !== wsRoot && !resolved.startsWith(wsRoot + "/")) {
|
|
214
|
-
return null;
|
|
215
|
-
}
|
|
216
|
-
return resolved;
|
|
217
|
-
}
|
|
218
|
-
if (archivePath.startsWith("hooks/") && this.hooksDir) {
|
|
219
|
-
const resolved = resolve(
|
|
220
|
-
this.hooksDir,
|
|
221
|
-
archivePath.slice("hooks/".length),
|
|
222
|
-
);
|
|
223
|
-
const hooksRoot = resolve(this.hooksDir);
|
|
224
|
-
if (resolved !== hooksRoot && !resolved.startsWith(hooksRoot + "/")) {
|
|
225
|
-
return null;
|
|
226
|
-
}
|
|
227
|
-
return resolved;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
return null;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// ---------------------------------------------------------------------------
|
|
235
|
-
// Hash helper
|
|
236
|
-
// ---------------------------------------------------------------------------
|
|
237
|
-
|
|
238
|
-
function sha256Hex(data: Uint8Array): string {
|
|
239
|
-
return createHash("sha256").update(data).digest("hex");
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// ---------------------------------------------------------------------------
|
|
243
|
-
// Core analyzer
|
|
244
|
-
// ---------------------------------------------------------------------------
|
|
245
|
-
|
|
246
|
-
interface AnalyzeImportOptions {
|
|
247
|
-
/** The parsed and validated manifest from the bundle */
|
|
248
|
-
manifest: ManifestType;
|
|
249
|
-
/** Resolves archive paths to disk paths for comparison */
|
|
250
|
-
pathResolver: PathResolver;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Analyze what importing a .vbundle archive would do without modifying
|
|
255
|
-
* any state. Compares bundle contents against current files on disk.
|
|
256
|
-
*/
|
|
257
|
-
export function analyzeImport(
|
|
258
|
-
options: AnalyzeImportOptions,
|
|
259
|
-
): ImportDryRunReport {
|
|
260
|
-
const { manifest, pathResolver } = options;
|
|
261
|
-
const files: ImportFileReport[] = [];
|
|
262
|
-
const conflicts: ImportConflict[] = [];
|
|
263
|
-
|
|
264
|
-
for (const fileEntry of manifest.contents) {
|
|
265
|
-
const diskPath = pathResolver.resolve(fileEntry.path);
|
|
266
|
-
|
|
267
|
-
// Credential entries are handled separately by the credential import
|
|
268
|
-
// step — skip them without flagging as unknown/conflict.
|
|
269
|
-
if (fileEntry.path.startsWith("credentials/")) {
|
|
270
|
-
files.push({
|
|
271
|
-
path: fileEntry.path,
|
|
272
|
-
action: "skip",
|
|
273
|
-
bundle_size: fileEntry.size_bytes,
|
|
274
|
-
bundle_sha256: fileEntry.sha256,
|
|
275
|
-
current_size: null,
|
|
276
|
-
current_sha256: null,
|
|
277
|
-
});
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (!diskPath) {
|
|
282
|
-
// Legacy `prompts/USER.md` in a guardian-less workspace has no
|
|
283
|
-
// destination to translate to. The commit-time path skips this
|
|
284
|
-
// entry with a warning rather than failing, so preflight mirrors
|
|
285
|
-
// that: emit a non-blocking skip so `can_import` stays true and
|
|
286
|
-
// upgrade paths proceed. No conflict is registered.
|
|
287
|
-
if (fileEntry.path === LEGACY_USER_MD_ARCHIVE_PATH) {
|
|
288
|
-
log.warn(
|
|
289
|
-
{ path: fileEntry.path },
|
|
290
|
-
"Legacy prompts/USER.md has no guardian target — will be skipped on import",
|
|
291
|
-
);
|
|
292
|
-
files.push({
|
|
293
|
-
path: fileEntry.path,
|
|
294
|
-
action: "skip",
|
|
295
|
-
bundle_size: fileEntry.size_bytes,
|
|
296
|
-
bundle_sha256: fileEntry.sha256,
|
|
297
|
-
current_size: null,
|
|
298
|
-
current_sha256: null,
|
|
299
|
-
});
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// Retired-feature file from a legacy bundle: nothing consumes it
|
|
304
|
-
// anymore, so drop it silently instead of blocking the restore with
|
|
305
|
-
// an UNKNOWN_ARCHIVE_PATH conflict.
|
|
306
|
-
if (
|
|
307
|
-
isRetiredArchivePath(fileEntry.path) ||
|
|
308
|
-
isGatewayArchivePath(fileEntry.path)
|
|
309
|
-
) {
|
|
310
|
-
log.info(
|
|
311
|
-
{ path: fileEntry.path },
|
|
312
|
-
"Archive path is not workspace data — will be skipped on import",
|
|
313
|
-
);
|
|
314
|
-
files.push({
|
|
315
|
-
path: fileEntry.path,
|
|
316
|
-
action: "skip",
|
|
317
|
-
bundle_size: fileEntry.size_bytes,
|
|
318
|
-
bundle_sha256: fileEntry.sha256,
|
|
319
|
-
current_size: null,
|
|
320
|
-
current_sha256: null,
|
|
321
|
-
});
|
|
322
|
-
continue;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// Unknown archive path — would have nowhere to write
|
|
326
|
-
conflicts.push({
|
|
327
|
-
code: "UNKNOWN_ARCHIVE_PATH",
|
|
328
|
-
message: `Archive path "${fileEntry.path}" has no known disk target — it would be skipped during import`,
|
|
329
|
-
path: fileEntry.path,
|
|
330
|
-
});
|
|
331
|
-
files.push({
|
|
332
|
-
path: fileEntry.path,
|
|
333
|
-
action: "skip",
|
|
334
|
-
bundle_size: fileEntry.size_bytes,
|
|
335
|
-
bundle_sha256: fileEntry.sha256,
|
|
336
|
-
current_size: null,
|
|
337
|
-
current_sha256: null,
|
|
338
|
-
});
|
|
339
|
-
continue;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
let currentSize: number | null = null;
|
|
343
|
-
let currentSha256: string | null = null;
|
|
344
|
-
let action: ImportAction;
|
|
345
|
-
|
|
346
|
-
if (existsSync(diskPath)) {
|
|
347
|
-
try {
|
|
348
|
-
const stat = statSync(diskPath);
|
|
349
|
-
currentSize = stat.size;
|
|
350
|
-
const diskData = new Uint8Array(readFileSync(diskPath));
|
|
351
|
-
currentSha256 = sha256Hex(diskData);
|
|
352
|
-
} catch {
|
|
353
|
-
// If we cannot read the file, treat it as a conflict
|
|
354
|
-
conflicts.push({
|
|
355
|
-
code: "UNREADABLE_EXISTING_FILE",
|
|
356
|
-
message: `Cannot read existing file at disk path for "${fileEntry.path}" — import would overwrite it`,
|
|
357
|
-
path: fileEntry.path,
|
|
358
|
-
});
|
|
359
|
-
action = "overwrite";
|
|
360
|
-
files.push({
|
|
361
|
-
path: fileEntry.path,
|
|
362
|
-
action,
|
|
363
|
-
bundle_size: fileEntry.size_bytes,
|
|
364
|
-
bundle_sha256: fileEntry.sha256,
|
|
365
|
-
current_size: currentSize,
|
|
366
|
-
current_sha256: currentSha256,
|
|
367
|
-
});
|
|
368
|
-
continue;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
if (currentSha256 === fileEntry.sha256) {
|
|
372
|
-
action = "unchanged";
|
|
373
|
-
} else {
|
|
374
|
-
action = "overwrite";
|
|
375
|
-
}
|
|
376
|
-
} else {
|
|
377
|
-
action = "create";
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
files.push({
|
|
381
|
-
path: fileEntry.path,
|
|
382
|
-
action,
|
|
383
|
-
bundle_size: fileEntry.size_bytes,
|
|
384
|
-
bundle_sha256: fileEntry.sha256,
|
|
385
|
-
current_size: currentSize,
|
|
386
|
-
current_sha256: currentSha256,
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
const summary = {
|
|
391
|
-
total_files: files.length,
|
|
392
|
-
files_to_create: files.filter((f) => f.action === "create").length,
|
|
393
|
-
files_to_overwrite: files.filter((f) => f.action === "overwrite").length,
|
|
394
|
-
files_unchanged: files.filter((f) => f.action === "unchanged").length,
|
|
395
|
-
files_to_skip: files.filter((f) => f.action === "skip").length,
|
|
396
|
-
};
|
|
397
|
-
|
|
398
|
-
return {
|
|
399
|
-
can_import: conflicts.length === 0,
|
|
400
|
-
summary,
|
|
401
|
-
files,
|
|
402
|
-
conflicts,
|
|
403
|
-
manifest,
|
|
404
|
-
};
|
|
405
|
-
}
|
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared invariants and predicate functions consumed by both the buffer-
|
|
3
|
-
* based `commitImport` and the streaming `streamCommitImport`. These
|
|
4
|
-
* decisions must stay in lockstep across both importers — moving them
|
|
5
|
-
* here removes the parallel-implementation skew risk that would otherwise
|
|
6
|
-
* grow as either importer evolves.
|
|
7
|
-
*
|
|
8
|
-
* Pure: no `node:fs`, no I/O, no async. Functions over strings + manifest
|
|
9
|
-
* data shapes only.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
export const LEGACY_USER_MD_ARCHIVE_PATH = "prompts/USER.md";
|
|
13
|
-
|
|
14
|
-
export const CONFIG_ARCHIVE_PATHS: ReadonlySet<string> = new Set([
|
|
15
|
-
"workspace/config.json",
|
|
16
|
-
"config/settings.json",
|
|
17
|
-
]);
|
|
18
|
-
|
|
19
|
-
export const CREDENTIAL_METADATA_ARCHIVE_PATH =
|
|
20
|
-
"workspace/data/credentials/metadata.json";
|
|
21
|
-
|
|
22
|
-
export const WORKSPACE_PRESERVE_PATHS: readonly string[] = [
|
|
23
|
-
"embedding-models",
|
|
24
|
-
"deprecated",
|
|
25
|
-
"data/db",
|
|
26
|
-
"data/qdrant",
|
|
27
|
-
];
|
|
28
|
-
|
|
29
|
-
export function isWorkspaceNamespacedArchivePath(archivePath: string): boolean {
|
|
30
|
-
return archivePath.startsWith("workspace/");
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function isLegacyPersonaArchivePath(archivePath: string): boolean {
|
|
34
|
-
return archivePath === LEGACY_USER_MD_ARCHIVE_PATH;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Archive paths for retired features. Bundles exported while those features
|
|
39
|
-
* existed still contain these entries; preflight and both importers must
|
|
40
|
-
* treat them as silent non-blocking skips (no conflict, no warning) so old
|
|
41
|
-
* backups stay restorable without noise, and the files are never written
|
|
42
|
-
* back to the workspace.
|
|
43
|
-
*
|
|
44
|
-
* `UPDATES.md` carried the retired update-bulletin release notes. Legacy
|
|
45
|
-
* bundles archived it as `prompts/UPDATES.md`; current-format bundles walk
|
|
46
|
-
* the workspace dir, so it appears as `workspace/UPDATES.md`. Both spellings
|
|
47
|
-
* must be retired — `DefaultPathResolver.resolve()` short-circuits them to
|
|
48
|
-
* `null` before any prefix handling so the workspace-format entry cannot
|
|
49
|
-
* resolve normally and recreate the file on import.
|
|
50
|
-
*/
|
|
51
|
-
export const RETIRED_ARCHIVE_PATHS: ReadonlySet<string> = new Set([
|
|
52
|
-
"prompts/UPDATES.md",
|
|
53
|
-
"workspace/UPDATES.md",
|
|
54
|
-
]);
|
|
55
|
-
|
|
56
|
-
export function isRetiredArchivePath(archivePath: string): boolean {
|
|
57
|
-
return RETIRED_ARCHIVE_PATHS.has(archivePath);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* `gateway/` holds the gateway's own database and logs, added by the debug
|
|
62
|
-
* export profile for Cadet staff to open on a debug clone. It is never
|
|
63
|
-
* part of a workspace, so preflight and both importers skip it silently,
|
|
64
|
-
* the same way they skip retired paths.
|
|
65
|
-
*/
|
|
66
|
-
export function isGatewayArchivePath(archivePath: string): boolean {
|
|
67
|
-
return archivePath.startsWith("gateway/");
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function isConfigArchivePath(archivePath: string): boolean {
|
|
71
|
-
return CONFIG_ARCHIVE_PATHS.has(archivePath);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export function isCredentialMetadataArchivePath(archivePath: string): boolean {
|
|
75
|
-
return archivePath === CREDENTIAL_METADATA_ARCHIVE_PATH;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Partition `WORKSPACE_PRESERVE_PATHS` into the two skip sets the buffer
|
|
80
|
-
* importer's selective-clear loop consumes:
|
|
81
|
-
*
|
|
82
|
-
* - `topLevelSkipDirs`: single-segment preserve-paths (e.g. "embedding-models").
|
|
83
|
-
* - `dataSubdirSkipDirs`: second segment of `data/<x>` preserve-paths
|
|
84
|
-
* (e.g. "db" for "data/db").
|
|
85
|
-
*
|
|
86
|
-
* Stays in sync with WORKSPACE_PRESERVE_PATHS automatically — adding a
|
|
87
|
-
* new entry of either shape doesn't require touching the buffer importer.
|
|
88
|
-
* Multi-segment paths outside the `data/` subtree are intentionally
|
|
89
|
-
* unsupported here; the buffer importer's walk doesn't recurse into
|
|
90
|
-
* arbitrary subdirs. If a future preserve-path needs deeper coverage,
|
|
91
|
-
* widen this helper and the buffer importer's walk together.
|
|
92
|
-
*/
|
|
93
|
-
export function partitionWorkspacePreserveSkipDirs(): {
|
|
94
|
-
topLevelSkipDirs: ReadonlySet<string>;
|
|
95
|
-
dataSubdirSkipDirs: ReadonlySet<string>;
|
|
96
|
-
} {
|
|
97
|
-
const topLevelSkipDirs = new Set<string>();
|
|
98
|
-
const dataSubdirSkipDirs = new Set<string>();
|
|
99
|
-
for (const rel of WORKSPACE_PRESERVE_PATHS) {
|
|
100
|
-
const parts = rel.split("/");
|
|
101
|
-
if (parts.length === 1) {
|
|
102
|
-
topLevelSkipDirs.add(parts[0]!);
|
|
103
|
-
} else if (parts.length === 2 && parts[0] === "data") {
|
|
104
|
-
dataSubdirSkipDirs.add(parts[1]!);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
return { topLevelSkipDirs, dataSubdirSkipDirs };
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export const LEGACY_RUNTIME_VERSION_SENTINEL = "0.0.0-legacy";
|
|
111
|
-
|
|
112
|
-
type SemverTriple = readonly [number, number, number];
|
|
113
|
-
|
|
114
|
-
function parseSemverTriple(version: string): SemverTriple | null {
|
|
115
|
-
// Strip optional prerelease/build suffix ("-foo", "+sha"). We treat
|
|
116
|
-
// "0.7.1-staging.1" as equal-to-release "0.7.1" for gating purposes.
|
|
117
|
-
// The cloud-side check uses packaging.version.Version, which sorts
|
|
118
|
-
// prereleases BEFORE the corresponding release; matching that exactly
|
|
119
|
-
// would require a fuller parser — for runtime-side defense-in-depth
|
|
120
|
-
// this conservative read is sufficient (matches release of the same
|
|
121
|
-
// base triple).
|
|
122
|
-
const base = version.split(/[-+]/)[0] ?? version;
|
|
123
|
-
const parts = base.split(".");
|
|
124
|
-
if (parts.length !== 3) {
|
|
125
|
-
return null;
|
|
126
|
-
}
|
|
127
|
-
// Reject components with anything other than ASCII digits to avoid
|
|
128
|
-
// Number.parseInt's lenient prefix-parse — e.g. "0.8.0foo" would
|
|
129
|
-
// otherwise coerce to [0, 8, 0] and silently pass the gate, defeating
|
|
130
|
-
// the parse-failure-fail-open contract in evaluateRuntimeCompatibility.
|
|
131
|
-
// Leading zeros (e.g. "01.02.03") are intentionally accepted since
|
|
132
|
-
// they round-trip to the same numeric triple as the un-padded form.
|
|
133
|
-
if (!parts.every((p) => /^\d+$/.test(p))) {
|
|
134
|
-
return null;
|
|
135
|
-
}
|
|
136
|
-
const [maj, min, pat] = parts.map((p) => Number(p));
|
|
137
|
-
if (![maj, min, pat].every((n) => Number.isFinite(n) && n >= 0)) {
|
|
138
|
-
return null;
|
|
139
|
-
}
|
|
140
|
-
return [maj!, min!, pat!] as const;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// -1 if a < b, 0 if equal, +1 if a > b. Returns null on parse failure.
|
|
144
|
-
export function compareSemver(a: string, b: string): -1 | 0 | 1 | null {
|
|
145
|
-
const ta = parseSemverTriple(a);
|
|
146
|
-
const tb = parseSemverTriple(b);
|
|
147
|
-
if (!ta || !tb) {
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
for (let i = 0; i < 3; i++) {
|
|
151
|
-
if (ta[i]! < tb[i]!) {
|
|
152
|
-
return -1;
|
|
153
|
-
}
|
|
154
|
-
if (ta[i]! > tb[i]!) {
|
|
155
|
-
return +1;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return 0;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export interface RuntimeCompatibility {
|
|
162
|
-
min_runtime_version: string;
|
|
163
|
-
max_runtime_version: string | null;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export type RuntimeCompatibilityResult =
|
|
167
|
-
| { ok: true }
|
|
168
|
-
| {
|
|
169
|
-
ok: false;
|
|
170
|
-
reason: "version_incompatible";
|
|
171
|
-
bundle_compat: RuntimeCompatibility;
|
|
172
|
-
runtime_version: string;
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
export function formatRuntimeCompatibilityMessage(
|
|
176
|
-
compat: RuntimeCompatibility,
|
|
177
|
-
runtimeVersion: string,
|
|
178
|
-
): string {
|
|
179
|
-
const range = compat.max_runtime_version
|
|
180
|
-
? `${compat.min_runtime_version}–${compat.max_runtime_version}`
|
|
181
|
-
: `${compat.min_runtime_version}+`;
|
|
182
|
-
return `Cannot import: bundle requires runtime ${range}, but this runtime is ${runtimeVersion}. Update your runtime before importing.`;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Whether `version` names a runtime built from a source checkout: the
|
|
187
|
-
* `<pkg.version>-local.<timestamp>.<sha>` `APP_VERSION` that the cloud
|
|
188
|
-
* repo's `vel up` stamps on the assistant images it builds for its local
|
|
189
|
-
* minikube cluster. The triple is the checkout's package.json version,
|
|
190
|
-
* which only moves on release cuts, so it says nothing about whether the
|
|
191
|
-
* checkout is newer or older than a release. A CLI hatch from a checkout
|
|
192
|
-
* without an explicit `APP_VERSION` runs as the plain package version and
|
|
193
|
-
* is not covered.
|
|
194
|
-
*/
|
|
195
|
-
export function isLocalDevRuntimeVersion(version: string): boolean {
|
|
196
|
-
return /^\d+\.\d+\.\d+-local\./.test(version);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
export function evaluateRuntimeCompatibility(
|
|
200
|
-
compat: RuntimeCompatibility,
|
|
201
|
-
runtimeVersion: string,
|
|
202
|
-
): RuntimeCompatibilityResult {
|
|
203
|
-
if (compat.min_runtime_version === LEGACY_RUNTIME_VERSION_SENTINEL) {
|
|
204
|
-
return { ok: true };
|
|
205
|
-
}
|
|
206
|
-
// A local dev build skips the minimum bound, like an unparsable version
|
|
207
|
-
// does below: its triple is not comparable to release versions (see
|
|
208
|
-
// isLocalDevRuntimeVersion), so "runtime older than the bundle" cannot be
|
|
209
|
-
// decided from it. The maximum bound still applies: it is an explicit
|
|
210
|
-
// ceiling the bundle's producer recorded, and a local triple above it is
|
|
211
|
-
// as good a signal as any release's.
|
|
212
|
-
if (!isLocalDevRuntimeVersion(runtimeVersion)) {
|
|
213
|
-
const minCmp = compareSemver(runtimeVersion, compat.min_runtime_version);
|
|
214
|
-
if (minCmp === null) {
|
|
215
|
-
return { ok: true };
|
|
216
|
-
}
|
|
217
|
-
if (minCmp < 0) {
|
|
218
|
-
return {
|
|
219
|
-
ok: false,
|
|
220
|
-
reason: "version_incompatible",
|
|
221
|
-
bundle_compat: compat,
|
|
222
|
-
runtime_version: runtimeVersion,
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
if (compat.max_runtime_version !== null) {
|
|
227
|
-
const maxCmp = compareSemver(runtimeVersion, compat.max_runtime_version);
|
|
228
|
-
if (maxCmp === null) {
|
|
229
|
-
return { ok: true };
|
|
230
|
-
}
|
|
231
|
-
if (maxCmp > 0) {
|
|
232
|
-
return {
|
|
233
|
-
ok: false,
|
|
234
|
-
reason: "version_incompatible",
|
|
235
|
-
bundle_compat: compat,
|
|
236
|
-
runtime_version: runtimeVersion,
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
return { ok: true };
|
|
241
|
-
}
|