@vellumai/assistant 0.5.9 → 0.5.10

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.
@@ -1,150 +1,12 @@
1
- import { getLogger } from "../../util/logger.js";
2
1
  import type { WorkspaceMigration } from "./types.js";
3
2
 
4
- const log = getLogger("workspace-migrations");
5
-
6
- const BROKER_WAIT_INTERVAL_MS = 500;
7
- const BROKER_WAIT_MAX_ATTEMPTS = 10; // 5 seconds total
8
-
3
+ /**
4
+ * Originally migrated credentials from macOS Keychain back to encrypted store.
5
+ * No-op'd for the same reasons as migration 015 — see that file for details.
6
+ */
9
7
  export const migrateCredentialsFromKeychainMigration: WorkspaceMigration = {
10
8
  id: "016-migrate-credentials-from-keychain",
11
- description:
12
- "Copy keychain credentials back to encrypted store for CES unification",
13
-
14
- async down(_workspaceDir: string): Promise<void> {
15
- // Reverse: copy credentials from encrypted store back to keychain.
16
- // Mirrors the forward logic of 015-migrate-credentials-to-keychain.
17
- if (
18
- process.env.VELLUM_DESKTOP_APP !== "1" ||
19
- process.env.VELLUM_DEV === "1"
20
- ) {
21
- return;
22
- }
23
-
24
- const { createBrokerClient } =
25
- await import("../../security/keychain-broker-client.js");
26
- const client = createBrokerClient();
27
-
28
- let brokerAvailable = false;
29
- for (let i = 0; i < BROKER_WAIT_MAX_ATTEMPTS; i++) {
30
- if (client.isAvailable()) {
31
- brokerAvailable = true;
32
- break;
33
- }
34
- await new Promise((r) => setTimeout(r, BROKER_WAIT_INTERVAL_MS));
35
- }
36
-
37
- if (!brokerAvailable) {
38
- throw new Error(
39
- "Keychain broker not available after waiting — credential rollback " +
40
- "will be retried on next startup",
41
- );
42
- }
43
-
44
- const { listKeys, getKey, deleteKey } =
45
- await import("../../security/encrypted-store.js");
46
-
47
- const accounts = listKeys();
48
- if (accounts.length === 0) return;
49
-
50
- let rolledBackCount = 0;
51
- let failedCount = 0;
52
-
53
- for (const account of accounts) {
54
- const value = getKey(account);
55
- if (value === undefined) {
56
- log.warn(
57
- { account },
58
- "Failed to read key from encrypted store during rollback — skipping",
59
- );
60
- failedCount++;
61
- continue;
62
- }
63
-
64
- const result = await client.set(account, value);
65
- if (result.status === "ok") {
66
- deleteKey(account);
67
- rolledBackCount++;
68
- } else {
69
- log.warn(
70
- { account, status: result.status },
71
- "Failed to write key to keychain during rollback — skipping",
72
- );
73
- failedCount++;
74
- }
75
- }
76
-
77
- log.info(
78
- { rolledBackCount, failedCount },
79
- "Credential rollback from encrypted store to keychain complete",
80
- );
81
- },
82
-
83
- async run(_workspaceDir: string): Promise<void> {
84
- // Only run on mac production builds (desktop app, non-dev).
85
- if (
86
- process.env.VELLUM_DESKTOP_APP !== "1" ||
87
- process.env.VELLUM_DEV === "1"
88
- ) {
89
- return;
90
- }
91
-
92
- const { createBrokerClient } =
93
- await import("../../security/keychain-broker-client.js");
94
- const client = createBrokerClient();
95
-
96
- // Wait for the broker to become available (up to 5 seconds), matching
97
- // the retry strategy in secure-keys.ts waitForBrokerAvailability().
98
- let brokerAvailable = false;
99
- for (let i = 0; i < BROKER_WAIT_MAX_ATTEMPTS; i++) {
100
- if (client.isAvailable()) {
101
- brokerAvailable = true;
102
- break;
103
- }
104
- await new Promise((r) => setTimeout(r, BROKER_WAIT_INTERVAL_MS));
105
- }
106
-
107
- if (!brokerAvailable) {
108
- throw new Error(
109
- "Keychain broker not available after waiting — credential migration " +
110
- "will be retried on next startup",
111
- );
112
- }
113
-
114
- const { setKey } = await import("../../security/encrypted-store.js");
115
-
116
- const accounts = await client.list();
117
- if (accounts.length === 0) {
118
- return;
119
- }
120
-
121
- let migratedCount = 0;
122
- let failedCount = 0;
123
-
124
- for (const account of accounts) {
125
- const result = await client.get(account);
126
- if (!result || !result.found || result.value === undefined) {
127
- log.warn({ account }, "Failed to read key from keychain — skipping");
128
- failedCount++;
129
- continue;
130
- }
131
-
132
- const written = setKey(account, result.value);
133
- if (written) {
134
- await client.del(account);
135
- migratedCount++;
136
- } else {
137
- log.warn(
138
- { account },
139
- "Failed to write key to encrypted store — skipping",
140
- );
141
- failedCount++;
142
- }
143
- }
144
-
145
- log.info(
146
- { migratedCount, failedCount },
147
- "Credential migration from keychain complete",
148
- );
149
- },
9
+ description: "No-op (keychain migration removed)",
10
+ async run(): Promise<void> {},
11
+ async down(): Promise<void> {},
150
12
  };
@@ -0,0 +1,11 @@
1
+ # Workspace Migrations — Agent Instructions
2
+
3
+ ## Self-Containment
4
+
5
+ Each migration file must be **fully self-contained**. All helper functions, constants, and utilities that a migration needs must be defined inline within the migration file itself — not imported from shared modules outside of `./types.js` and the logger.
6
+
7
+ - **No external exports.** Migration files must not export anything other than the single `WorkspaceMigration` object. Other code must never import from a migration file.
8
+ - **Duplicate rather than share.** If two migrations need the same helper, duplicate it in both files. Migrations are write-once code — they run once per assistant and are never modified after release. Duplication is preferable to coupling.
9
+ - **Allowed imports:** `./types.js` (for the `WorkspaceMigration` interface) and `../../util/logger.js` (for structured logging). All other dependencies should be inlined.
10
+ - **Graceful on all platforms.** Migrations run on macOS, Linux, and in Docker. Platform-specific operations must no-op gracefully on unsupported platforms — never throw.
11
+ - **Idempotent.** Migrations must be safe to re-run if interrupted. The runner checkpoints state as `"started"` before execution and `"completed"` after, so a crash mid-migration triggers a re-run on next startup.
@@ -3,7 +3,7 @@ import { dirname, join } from "node:path";
3
3
 
4
4
  import { ensureDir, readTextFileSync } from "../../util/fs.js";
5
5
  import { getLogger } from "../../util/logger.js";
6
- import type { WorkspaceMigration } from "./types.js";
6
+ import type { WorkspaceMigration, WorkspaceMigrationStatus } from "./types.js";
7
7
 
8
8
  const log = getLogger("workspace-migrations");
9
9
 
@@ -16,7 +16,7 @@ export function getLastWorkspaceMigrationId(
16
16
  export type CheckpointFile = {
17
17
  applied: Record<
18
18
  string,
19
- { appliedAt: string; status?: "started" | "completed" | "rolling_back" }
19
+ { appliedAt: string; status?: WorkspaceMigrationStatus }
20
20
  >;
21
21
  };
22
22
 
@@ -108,9 +108,14 @@ export async function runWorkspaceMigrations(
108
108
  } catch (error) {
109
109
  log.error(
110
110
  { migrationId: migration.id, error },
111
- `Workspace migration failed: ${migration.id}`,
111
+ `Workspace migration failed: ${migration.id} — marking as failed and continuing`,
112
112
  );
113
- throw error;
113
+ checkpoints.applied[migration.id] = {
114
+ appliedAt: new Date().toISOString(),
115
+ status: "failed",
116
+ };
117
+ saveCheckpoints(workspaceDir, checkpoints);
118
+ continue;
114
119
  }
115
120
 
116
121
  // Mark as completed
@@ -207,9 +212,14 @@ export async function rollbackWorkspaceMigrations(
207
212
  } catch (error) {
208
213
  log.error(
209
214
  { migrationId: migration.id, error },
210
- `Workspace migration rollback failed: ${migration.id}`,
215
+ `Workspace migration rollback failed: ${migration.id} — marking as failed and continuing`,
211
216
  );
212
- throw error;
217
+ checkpoints.applied[migration.id] = {
218
+ appliedAt: checkpoints.applied[migration.id]!.appliedAt,
219
+ status: "failed",
220
+ };
221
+ saveCheckpoints(workspaceDir, checkpoints);
222
+ continue;
213
223
  }
214
224
 
215
225
  // Remove the migration entry from checkpoints
@@ -13,3 +13,10 @@ export interface WorkspaceMigration {
13
13
  * Both synchronous and asynchronous rollbacks are supported. */
14
14
  down(workspaceDir: string): void | Promise<void>;
15
15
  }
16
+
17
+ /** Checkpoint status values for workspace migration tracking. */
18
+ export type WorkspaceMigrationStatus =
19
+ | "started"
20
+ | "completed"
21
+ | "rolling_back"
22
+ | "failed";