@letta-ai/dreams 0.0.1

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.
@@ -0,0 +1,223 @@
1
+ "use strict";
2
+ /**
3
+ * Minimal typed client for the Dreams Cloud API ([02] contract).
4
+ *
5
+ * - Bearer credentials come from the shared Dreams auth subsystem.
6
+ * - Redirects are refused while carrying Authorization (a redirect on an API
7
+ * POST is misconfiguration or an attempt to exfiltrate the token).
8
+ * - Outcomes are classified from structured server error codes where present,
9
+ * falling back to HTTP status, so callers branch on intent rather than status.
10
+ */
11
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ var desc = Object.getOwnPropertyDescriptor(m, k);
14
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
15
+ desc = { enumerable: true, get: function() { return m[k]; } };
16
+ }
17
+ Object.defineProperty(o, k2, desc);
18
+ }) : (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ o[k2] = m[k];
21
+ }));
22
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
23
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
24
+ }) : function(o, v) {
25
+ o["default"] = v;
26
+ });
27
+ var __importStar = (this && this.__importStar) || (function () {
28
+ var ownKeys = function(o) {
29
+ ownKeys = Object.getOwnPropertyNames || function (o) {
30
+ var ar = [];
31
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
32
+ return ar;
33
+ };
34
+ return ownKeys(o);
35
+ };
36
+ return function (mod) {
37
+ if (mod && mod.__esModule) return mod;
38
+ var result = {};
39
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
40
+ __setModuleDefault(result, mod);
41
+ return result;
42
+ };
43
+ })();
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.CloudError = exports.CloudAuthError = void 0;
46
+ exports.registerSource = registerSource;
47
+ exports.uploadObject = uploadObject;
48
+ const zlib = __importStar(require("node:zlib"));
49
+ const index_1 = require("../auth/index");
50
+ const oauth_1 = require("../auth/oauth");
51
+ class CloudAuthError extends Error {
52
+ code;
53
+ constructor(message, code = null) {
54
+ super(message);
55
+ this.code = code;
56
+ this.name = "CloudAuthError";
57
+ }
58
+ }
59
+ exports.CloudAuthError = CloudAuthError;
60
+ class CloudError extends Error {
61
+ retryable;
62
+ status;
63
+ constructor(message, options = {}) {
64
+ super(message);
65
+ this.name = "CloudError";
66
+ this.retryable = options.retryable ?? false;
67
+ this.status = options.status ?? null;
68
+ }
69
+ }
70
+ exports.CloudError = CloudError;
71
+ // Comfortably below the source lease TTL (30s) so a stalled request cannot
72
+ // outlive the lease and let another process reclaim it mid-flight.
73
+ const REQUEST_TIMEOUT_MS = 20_000;
74
+ function errorDetail(error) {
75
+ const cause = error.cause;
76
+ return cause?.code || cause?.message || (error instanceof Error ? error.message : String(error));
77
+ }
78
+ function parseRetryAfter(value) {
79
+ if (!value)
80
+ return null;
81
+ const seconds = Number(value);
82
+ if (Number.isFinite(seconds))
83
+ return Math.max(0, seconds * 1000);
84
+ const date = Date.parse(value);
85
+ return Number.isFinite(date) ? Math.max(0, date - Date.now()) : null;
86
+ }
87
+ async function request(method, path, body) {
88
+ const auth = await (0, index_1.getAuthHeader)(); // throws NotAuthenticatedError when no credential exists
89
+ const url = `${(0, oauth_1.apiBaseUrl)()}${path}`;
90
+ let response;
91
+ try {
92
+ response = await fetch(url, {
93
+ method,
94
+ headers: { ...auth, "content-type": "application/json", accept: "application/json" },
95
+ body: body === undefined ? undefined : JSON.stringify(body),
96
+ redirect: "manual",
97
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
98
+ });
99
+ }
100
+ catch (error) {
101
+ // Timeouts and connection failures are retryable (the row stays pending).
102
+ throw new CloudError(`network error contacting ${url} (${errorDetail(error)})`, { retryable: true });
103
+ }
104
+ if (response.type === "opaqueredirect" || response.status === 0 || (response.status >= 300 && response.status < 400)) {
105
+ throw new CloudError(`refusing to follow a redirect from ${url} while authenticated`, { status: response.status });
106
+ }
107
+ let json = null;
108
+ const text = await response.text();
109
+ if (text) {
110
+ try {
111
+ json = JSON.parse(text);
112
+ }
113
+ catch {
114
+ json = null;
115
+ }
116
+ }
117
+ return { status: response.status, json, retryAfterMs: parseRetryAfter(response.headers.get("retry-after")) };
118
+ }
119
+ function serverCode(json) {
120
+ if (!json)
121
+ return null;
122
+ const code = json.code ?? json.error;
123
+ return typeof code === "string" ? code : null;
124
+ }
125
+ function serverMessage(json, fallback) {
126
+ if (json) {
127
+ const message = json.message ?? json.error_description ?? json.detail;
128
+ if (typeof message === "string")
129
+ return message;
130
+ }
131
+ return fallback;
132
+ }
133
+ function classifyAuthOrRetry(status, json, path) {
134
+ const code = serverCode(json);
135
+ if (status === 401 || status === 403)
136
+ throw new CloudAuthError(serverMessage(json, `unauthorized (${status})`), code);
137
+ if (status === 429 || status >= 500)
138
+ throw new CloudError(serverMessage(json, `server error ${status} from ${path}`), { retryable: true, status });
139
+ throw new CloudError(serverMessage(json, `unexpected ${status} from ${path}`), { status });
140
+ }
141
+ /**
142
+ * Register (and, on the server, provision the workspace for) a source. The [02]
143
+ * contract has no separate workspace endpoint: this call returns the workspace
144
+ * id, so it is also how the CLI resolves the workspace.
145
+ */
146
+ async function registerSource(input) {
147
+ const r = await request("POST", "/v1/dreams/sources", {
148
+ source_type: input.sourceType,
149
+ source_key: input.sourceKey,
150
+ display_name: input.displayName,
151
+ installation_id: input.installationId,
152
+ adapter_version: input.adapterVersion,
153
+ privacy_config_hash: input.privacyConfigHash ?? null,
154
+ backfill_policy: input.backfillPolicy ?? null,
155
+ });
156
+ if (r.status !== 200 && r.status !== 201)
157
+ classifyAuthOrRetry(r.status, r.json, "sources");
158
+ const id = r.json?.id;
159
+ if (typeof id !== "string")
160
+ throw new CloudError("source response missing id", { retryable: true });
161
+ return {
162
+ serverSourceId: id,
163
+ status: typeof r.json?.status === "string" ? r.json.status : "active",
164
+ workspaceId: typeof r.json?.workspace_id === "string" ? r.json.workspace_id : null,
165
+ };
166
+ }
167
+ async function uploadObject(input) {
168
+ const content = zlib.gzipSync(input.bytes).toString("base64");
169
+ const r = await request("POST", "/v1/dreams/uploads", {
170
+ source_id: input.sourceId,
171
+ client_upload_id: input.clientUploadId,
172
+ content_sha256: input.contentSha256,
173
+ content_encoding: "gzip+base64",
174
+ content,
175
+ source_schema: input.sourceSchema,
176
+ source_metadata: input.sourceMetadata,
177
+ priority: input.priority,
178
+ cli_version: input.cliVersion,
179
+ adapter_version: input.adapterVersion,
180
+ });
181
+ const code = serverCode(r.json);
182
+ const message = serverMessage(r.json, `upload responded ${r.status}`);
183
+ // Strictly validate 2xx bodies before treating anything as accepted. The [02]
184
+ // ack must carry upload_id, accepted_at, and a content_sha256 that echoes the
185
+ // exact row hash; anything else (including a mismatched hash) is retryable, so
186
+ // the row stays pending and the immutable blob is retained.
187
+ if (r.status === 201 || r.status === 200) {
188
+ const status = typeof r.json?.status === "string" ? r.json.status : null;
189
+ const uploadId = typeof r.json?.upload_id === "string" ? r.json.upload_id : null;
190
+ const echoedSha = typeof r.json?.content_sha256 === "string" ? r.json.content_sha256 : null;
191
+ const acceptedAt = typeof r.json?.accepted_at === "string" ? r.json.accepted_at : null;
192
+ const valid = uploadId !== null && acceptedAt !== null && echoedSha === input.contentSha256;
193
+ if (valid && status === "accepted")
194
+ return { kind: "accepted", uploadId: uploadId, contentSha256: echoedSha, acceptedAt };
195
+ if (valid && status === "duplicate")
196
+ return { kind: "duplicate", uploadId, contentSha256: echoedSha, acceptedAt };
197
+ return { kind: "retryable", code: "malformed_success_body", message: `malformed or hash-mismatched ${r.status} upload ack`, retryAfterMs: null };
198
+ }
199
+ // Prefer structured codes over bare status.
200
+ if (code) {
201
+ if (/auth|credential|unauthorized|forbidden/i.test(code))
202
+ return { kind: "unauthenticated", code, message };
203
+ if (/source.*(paus|revok|not_upload|unusable|disabled)/i.test(code))
204
+ return { kind: "source_unusable", code, message };
205
+ if (/conflict|mismatch|invalid|malformed|too_large|payload/i.test(code))
206
+ return { kind: "quarantine", code, message };
207
+ if (/rate|capacity|unavailable|timeout|throttle|retry/i.test(code))
208
+ return { kind: "retryable", code, message, retryAfterMs: r.retryAfterMs };
209
+ }
210
+ if (r.status === 401 || r.status === 403)
211
+ return { kind: "unauthenticated", code, message };
212
+ if (r.status === 404)
213
+ return { kind: "not_found", code, message };
214
+ // [02] returns message-only 409s for BOTH client_upload_id/content conflicts and
215
+ // a source becoming non-upload-capable. Without a discriminating code we cannot
216
+ // safely make the row permanent (quarantine), so we conservatively stop the
217
+ // source instead of silently dropping data.
218
+ if (r.status === 409)
219
+ return { kind: "source_unusable", code: code ?? "conflict", message };
220
+ if (r.status === 400 || r.status === 413 || (r.status >= 400 && r.status < 500 && r.status !== 429))
221
+ return { kind: "quarantine", code: code ?? `http_${r.status}`, message };
222
+ return { kind: "retryable", code, message, retryAfterMs: r.retryAfterMs };
223
+ }
@@ -0,0 +1,384 @@
1
+ "use strict";
2
+ /**
3
+ * Upload orchestration ([07]): drain the local pending queue to the Dreams Cloud
4
+ * under the shared source-mutation lease.
5
+ *
6
+ * Rules (Amelia sign-off):
7
+ * - No eligible due rows => no auth and no network.
8
+ * - Register workspace/source fresh per invocation (server-idempotent), so a
9
+ * changed LETTA_BASE_URL never reuses another environment's IDs.
10
+ * - Policy is re-evaluated immediately before each send (the linearization
11
+ * point); a row disallowed by tightened policy is canceled, not uploaded.
12
+ * - Mark-accepted and acknowledged-cursor collapse happen in one owner-fenced
13
+ * transaction. The cursor advances through accepted+excluded coverage only,
14
+ * stalling at the first pending/quarantined gap, never past queued_through.
15
+ * - Never sleep holding the lease: retryable rows get next_attempt_at and the
16
+ * run continues with other due rows, then exits.
17
+ * - Blob lifecycle: retain for pending/retryable/quarantined; delete after the
18
+ * accepted (or canceled) transaction commits.
19
+ */
20
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ var desc = Object.getOwnPropertyDescriptor(m, k);
23
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
24
+ desc = { enumerable: true, get: function() { return m[k]; } };
25
+ }
26
+ Object.defineProperty(o, k2, desc);
27
+ }) : (function(o, m, k, k2) {
28
+ if (k2 === undefined) k2 = k;
29
+ o[k2] = m[k];
30
+ }));
31
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
32
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
33
+ }) : function(o, v) {
34
+ o["default"] = v;
35
+ });
36
+ var __importStar = (this && this.__importStar) || (function () {
37
+ var ownKeys = function(o) {
38
+ ownKeys = Object.getOwnPropertyNames || function (o) {
39
+ var ar = [];
40
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
41
+ return ar;
42
+ };
43
+ return ownKeys(o);
44
+ };
45
+ return function (mod) {
46
+ if (mod && mod.__esModule) return mod;
47
+ var result = {};
48
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
49
+ __setModuleDefault(result, mod);
50
+ return result;
51
+ };
52
+ })();
53
+ Object.defineProperty(exports, "__esModule", { value: true });
54
+ exports.runUpload = runUpload;
55
+ const path = __importStar(require("node:path"));
56
+ const index_1 = require("./../auth/index");
57
+ const claude_1 = require("./../local/claude");
58
+ const config_1 = require("./../local/config");
59
+ const db_1 = require("./../local/db");
60
+ const dreamignore_1 = require("./../local/dreamignore");
61
+ const leases_1 = require("./../local/leases");
62
+ const scan_1 = require("./../local/scan");
63
+ const blobs_1 = require("./../local/blobs");
64
+ const version_1 = require("./../version");
65
+ const client_1 = require("./client");
66
+ /**
67
+ * Final local policy gate: ONE fresh roots/.dreamignore snapshot drives both the
68
+ * eligibility decision and the fingerprint, so a tightening cannot be recorded
69
+ * without also being evaluated (P1). Called immediately before the send.
70
+ */
71
+ function evaluatePolicy(db, cwd) {
72
+ const roots = (0, config_1.getApprovedRoots)(db);
73
+ const approvedRoot = cwd ? (0, config_1.isUnderApprovedRoot)(roots, cwd) : null;
74
+ if (!approvedRoot || !cwd)
75
+ return { eligible: false, approvedRoot: null, fingerprint: null };
76
+ const ignoreText = (0, dreamignore_1.loadDreamignoreText)(approvedRoot);
77
+ const eligible = !(0, dreamignore_1.isIgnored)((0, dreamignore_1.parseDreamignore)(ignoreText), path.relative(approvedRoot, cwd), true);
78
+ return { eligible, approvedRoot, fingerprint: (0, scan_1.policyFingerprint)(roots, approvedRoot, ignoreText) };
79
+ }
80
+ const SOURCE_SCHEMA = "claude-code-jsonl-v1";
81
+ const UPLOAD_CAPABLE = new Set(["active", "backfilling", "discovered", "awaiting_approval"]);
82
+ const MAX_BACKOFF_MS = 5 * 60_000;
83
+ function localSourceId() {
84
+ return `src_${claude_1.CLAUDE_SOURCE_TYPE}`;
85
+ }
86
+ function dueRows(db, localSource) {
87
+ return db
88
+ .prepare(`SELECT q.id, q.upload_id, q.source_file_id, q.session_id, q.generation, q.start_offset, q.end_offset,
89
+ q.content_sha256, q.priority, q.policy_fingerprint, sf.cwd
90
+ FROM upload_queue q JOIN source_files sf ON sf.id = q.source_file_id
91
+ WHERE q.source_id = ? AND q.state = 'pending'
92
+ AND (q.next_attempt_at IS NULL OR q.next_attempt_at <= ?)
93
+ ORDER BY CASE q.priority WHEN 'live' THEN 0 WHEN 'backfill' THEN 1 ELSE 2 END,
94
+ q.session_id, q.generation, q.start_offset`)
95
+ .all(localSource, (0, db_1.nowIso)());
96
+ }
97
+ function backoffMs(attempts, retryAfterMs) {
98
+ const exponential = Math.min(MAX_BACKOFF_MS, 1000 * 2 ** Math.min(Math.max(attempts, 1), 8));
99
+ return Math.max(exponential, retryAfterMs ?? 0);
100
+ }
101
+ /**
102
+ * Delete a blob only when no pending/quarantined row still needs it (accepted
103
+ * rows do not). Fenced + write-lock-held across the check and unlink so a stale
104
+ * process cannot delete a blob the current lease holder just re-referenced (P2).
105
+ */
106
+ function gcBlob(db, contentSha, owner) {
107
+ (0, db_1.tx)(db, () => {
108
+ (0, leases_1.assertLeaseHeld)(db, leases_1.SOURCE_LEASE, owner);
109
+ const row = db
110
+ .prepare("SELECT count(*) c FROM upload_queue WHERE content_sha256 = ? AND state IN ('pending','quarantined')")
111
+ .get(contentSha);
112
+ if (row.c === 0)
113
+ (0, blobs_1.deleteBlob)(contentSha);
114
+ });
115
+ }
116
+ /**
117
+ * Advance the acknowledged cursor for one source_file through the contiguous
118
+ * union of accepted queue intervals and excluded ranges, stopping at the first
119
+ * pending/quarantined gap and never exceeding queued_through_offset.
120
+ */
121
+ function collapseAckCursor(db, sourceFileId) {
122
+ const cursor = db
123
+ .prepare("SELECT queued_through_offset, acked_offset FROM source_cursors WHERE source_file_id = ?")
124
+ .get(sourceFileId);
125
+ if (!cursor)
126
+ return;
127
+ const intervals = db
128
+ .prepare(`SELECT start_offset, end_offset FROM upload_queue WHERE source_file_id = ? AND state = 'accepted'
129
+ UNION ALL
130
+ SELECT start_offset, end_offset FROM excluded_ranges WHERE source_file_id = ?
131
+ ORDER BY start_offset, end_offset`)
132
+ .all(sourceFileId, sourceFileId);
133
+ let acked = cursor.acked_offset;
134
+ for (const interval of intervals) {
135
+ if (interval.start_offset > acked)
136
+ break; // gap (pending/quarantined/uncovered)
137
+ if (interval.end_offset > acked)
138
+ acked = interval.end_offset;
139
+ }
140
+ acked = Math.min(acked, cursor.queued_through_offset);
141
+ if (acked !== cursor.acked_offset) {
142
+ db.prepare("UPDATE source_cursors SET acked_offset = ?, updated_at = ? WHERE source_file_id = ?").run(acked, (0, db_1.nowIso)(), sourceFileId);
143
+ }
144
+ }
145
+ function cancelRowForPolicy(db, row, owner) {
146
+ (0, db_1.tx)(db, () => {
147
+ (0, leases_1.assertLeaseHeld)(db, leases_1.SOURCE_LEASE, owner);
148
+ const info = db.prepare("UPDATE upload_queue SET state = 'canceled', updated_at = ? WHERE id = ? AND state = 'pending'").run((0, db_1.nowIso)(), row.id);
149
+ if (info.changes === 0)
150
+ return;
151
+ (0, scan_1.addExcludedRange)(db, row.source_file_id, row.start_offset, row.end_offset, "privacy");
152
+ collapseAckCursor(db, row.source_file_id);
153
+ });
154
+ gcBlob(db, row.content_sha256, owner);
155
+ }
156
+ function markAccepted(db, row, serverUploadId, acceptedAt, owner) {
157
+ (0, db_1.tx)(db, () => {
158
+ (0, leases_1.assertLeaseHeld)(db, leases_1.SOURCE_LEASE, owner);
159
+ db.prepare("UPDATE upload_queue SET state = 'accepted', server_upload_id = ?, accepted_at = ?, last_error_code = NULL, last_error_message = NULL, updated_at = ? WHERE id = ?").run(serverUploadId, acceptedAt ?? (0, db_1.nowIso)(), (0, db_1.nowIso)(), row.id);
160
+ collapseAckCursor(db, row.source_file_id);
161
+ });
162
+ gcBlob(db, row.content_sha256, owner); // safe: the server has durably accepted it
163
+ }
164
+ function quarantineRow(db, row, code, message, owner) {
165
+ (0, db_1.tx)(db, () => {
166
+ (0, leases_1.assertLeaseHeld)(db, leases_1.SOURCE_LEASE, owner);
167
+ db.prepare("UPDATE upload_queue SET state = 'quarantined', last_error_code = ?, last_error_message = ?, updated_at = ? WHERE id = ?").run(code, message.slice(0, 500), (0, db_1.nowIso)(), row.id);
168
+ // The gap deliberately stalls this generation's acknowledged cursor.
169
+ });
170
+ }
171
+ function scheduleRetry(db, row, code, message, retryAfterMs, owner) {
172
+ (0, db_1.tx)(db, () => {
173
+ (0, leases_1.assertLeaseHeld)(db, leases_1.SOURCE_LEASE, owner);
174
+ const attempts = db.prepare("SELECT attempts FROM upload_queue WHERE id = ?").get(row.id).attempts + 1;
175
+ const next = new Date(Date.now() + backoffMs(attempts, retryAfterMs)).toISOString();
176
+ db.prepare("UPDATE upload_queue SET attempts = ?, next_attempt_at = ?, last_error_code = ?, last_error_message = ?, updated_at = ? WHERE id = ?").run(attempts, next, code, message.slice(0, 500), (0, db_1.nowIso)(), row.id);
177
+ });
178
+ }
179
+ function refreshPolicyFingerprint(db, row, fingerprint, owner) {
180
+ if (fingerprint === row.policy_fingerprint)
181
+ return;
182
+ (0, db_1.tx)(db, () => {
183
+ (0, leases_1.assertLeaseHeld)(db, leases_1.SOURCE_LEASE, owner);
184
+ db.prepare("UPDATE upload_queue SET policy_fingerprint = ?, updated_at = ? WHERE id = ?").run(fingerprint, (0, db_1.nowIso)(), row.id);
185
+ });
186
+ }
187
+ async function runUpload(db, options) {
188
+ const owner = options.leaseOwner;
189
+ const local = localSourceId();
190
+ const summary = {
191
+ local_source_id: null,
192
+ server_source_id: null,
193
+ workspace_id: null,
194
+ source_status: null,
195
+ due: 0,
196
+ accepted: 0,
197
+ duplicate: 0,
198
+ quarantined: 0,
199
+ retry_scheduled: 0,
200
+ canceled_by_policy: 0,
201
+ bytes_uploaded: 0,
202
+ stopped_reason: null,
203
+ partial: false,
204
+ };
205
+ const sourceExists = db.prepare("SELECT id FROM sources WHERE id = ?").get(local);
206
+ if (!sourceExists)
207
+ return summary; // nothing scanned yet
208
+ summary.local_source_id = local;
209
+ let due = dueRows(db, local);
210
+ summary.due = due.length;
211
+ if (due.length === 0)
212
+ return summary; // no eligible pending rows => no auth, no network
213
+ // Register the source fresh (server-idempotent). Per [02] this also provisions
214
+ // and returns the workspace, so there is no separate workspace call. Registering
215
+ // per invocation, bound to the current base URL, avoids reusing a prior env's IDs.
216
+ let registration;
217
+ try {
218
+ if (!(0, leases_1.renewLease)(db, leases_1.SOURCE_LEASE, owner))
219
+ throw new leases_1.LeaseLostError(leases_1.SOURCE_LEASE);
220
+ registration = await (0, client_1.registerSource)({
221
+ sourceType: claude_1.CLAUDE_SOURCE_TYPE,
222
+ sourceKey: claude_1.CLAUDE_SOURCE_TYPE,
223
+ displayName: "Claude Code",
224
+ installationId: options.installationId,
225
+ adapterVersion: claude_1.CLAUDE_ADAPTER_VERSION,
226
+ });
227
+ }
228
+ catch (error) {
229
+ if (error instanceof index_1.NotAuthenticatedError) {
230
+ summary.stopped_reason = "not authenticated: run `dreams auth login`";
231
+ summary.partial = true;
232
+ return summary;
233
+ }
234
+ if (error instanceof client_1.CloudAuthError) {
235
+ summary.stopped_reason = `authentication failed: ${error.message}`;
236
+ summary.partial = true;
237
+ return summary;
238
+ }
239
+ if (error instanceof client_1.CloudError) {
240
+ summary.stopped_reason = `could not resolve source: ${error.message}`;
241
+ summary.partial = true;
242
+ return summary;
243
+ }
244
+ throw error;
245
+ }
246
+ summary.server_source_id = registration.serverSourceId;
247
+ summary.source_status = registration.status;
248
+ summary.workspace_id = registration.workspaceId;
249
+ persistBindings(db, registration, owner);
250
+ if (!UPLOAD_CAPABLE.has(registration.status)) {
251
+ summary.stopped_reason = `source is ${registration.status} (not upload-capable)`;
252
+ summary.partial = true;
253
+ return summary;
254
+ }
255
+ let reRegistered = false;
256
+ for (let index = 0; index < due.length; index++) {
257
+ const row = due[index];
258
+ if (!(0, leases_1.renewLease)(db, leases_1.SOURCE_LEASE, owner))
259
+ throw new leases_1.LeaseLostError(leases_1.SOURCE_LEASE);
260
+ // Prepare the payload first.
261
+ let bytes;
262
+ try {
263
+ bytes = (0, blobs_1.readBlob)(row.content_sha256); // verifies the hash
264
+ }
265
+ catch (error) {
266
+ const code = error.code === "ENOENT" ? "blob_missing" : "blob_corrupt";
267
+ quarantineRow(db, row, code, error instanceof Error ? error.message : String(error), owner);
268
+ summary.quarantined++;
269
+ summary.partial = true;
270
+ continue;
271
+ }
272
+ // Final local policy gate (one snapshot for eligibility AND fingerprint),
273
+ // immediately before the send — the policy linearization point.
274
+ const policy = evaluatePolicy(db, row.cwd);
275
+ if (!policy.eligible) {
276
+ cancelRowForPolicy(db, row, owner);
277
+ summary.canceled_by_policy++;
278
+ continue;
279
+ }
280
+ if (policy.fingerprint)
281
+ refreshPolicyFingerprint(db, row, policy.fingerprint, owner);
282
+ // Renew immediately before the network attempt so the request cannot outlive the lease.
283
+ if (!(0, leases_1.renewLease)(db, leases_1.SOURCE_LEASE, owner))
284
+ throw new leases_1.LeaseLostError(leases_1.SOURCE_LEASE);
285
+ let outcome;
286
+ try {
287
+ outcome = await (0, client_1.uploadObject)({
288
+ sourceId: registration.serverSourceId,
289
+ clientUploadId: row.upload_id,
290
+ contentSha256: row.content_sha256,
291
+ bytes,
292
+ sourceSchema: SOURCE_SCHEMA,
293
+ sourceMetadata: { session_id: row.session_id, generation: row.generation, start_offset: row.start_offset, end_offset: row.end_offset },
294
+ priority: row.priority,
295
+ cliVersion: (0, version_1.packageVersion)(),
296
+ adapterVersion: claude_1.CLAUDE_ADAPTER_VERSION,
297
+ });
298
+ }
299
+ catch (error) {
300
+ if (error instanceof index_1.NotAuthenticatedError) {
301
+ summary.stopped_reason = "not authenticated: run `dreams auth login`";
302
+ summary.partial = true;
303
+ return summary;
304
+ }
305
+ if (error instanceof client_1.CloudAuthError) {
306
+ summary.stopped_reason = `authentication failed: ${error.message}`;
307
+ summary.partial = true;
308
+ return summary;
309
+ }
310
+ if (error instanceof client_1.CloudError && error.retryable) {
311
+ scheduleRetry(db, row, "network", error.message, null, owner);
312
+ summary.retry_scheduled++;
313
+ continue;
314
+ }
315
+ throw error; // programming/unexpected error
316
+ }
317
+ switch (outcome.kind) {
318
+ case "accepted":
319
+ markAccepted(db, row, outcome.uploadId, outcome.acceptedAt, owner);
320
+ summary.accepted++;
321
+ summary.bytes_uploaded += bytes.length;
322
+ break;
323
+ case "duplicate":
324
+ markAccepted(db, row, outcome.uploadId, outcome.acceptedAt, owner);
325
+ summary.duplicate++;
326
+ break;
327
+ case "unauthenticated":
328
+ summary.stopped_reason = `authentication failed: ${outcome.message}`;
329
+ summary.partial = true;
330
+ return summary;
331
+ case "source_unusable":
332
+ summary.stopped_reason = `source not upload-capable: ${outcome.message}`;
333
+ summary.partial = true;
334
+ return summary;
335
+ case "not_found":
336
+ if (reRegistered) {
337
+ summary.stopped_reason = `source not found after re-registration: ${outcome.message}`;
338
+ summary.partial = true;
339
+ return summary;
340
+ }
341
+ reRegistered = true;
342
+ {
343
+ // Renew before this second network attempt so the two bounded requests
344
+ // cannot exceed the lease TTL before persistBindings commits (P2).
345
+ if (!(0, leases_1.renewLease)(db, leases_1.SOURCE_LEASE, owner))
346
+ throw new leases_1.LeaseLostError(leases_1.SOURCE_LEASE);
347
+ const again = await (0, client_1.registerSource)({
348
+ sourceType: claude_1.CLAUDE_SOURCE_TYPE,
349
+ sourceKey: claude_1.CLAUDE_SOURCE_TYPE,
350
+ displayName: "Claude Code",
351
+ installationId: options.installationId,
352
+ adapterVersion: claude_1.CLAUDE_ADAPTER_VERSION,
353
+ });
354
+ summary.server_source_id = again.serverSourceId;
355
+ summary.source_status = again.status;
356
+ summary.workspace_id = again.workspaceId;
357
+ persistBindings(db, again, owner);
358
+ registration.serverSourceId = again.serverSourceId;
359
+ }
360
+ index--; // retry the same row once with the refreshed source id
361
+ break;
362
+ case "quarantine":
363
+ quarantineRow(db, row, outcome.code, outcome.message, owner);
364
+ summary.quarantined++;
365
+ summary.partial = true;
366
+ break;
367
+ case "retryable":
368
+ scheduleRetry(db, row, outcome.code, outcome.message, outcome.retryAfterMs, owner);
369
+ summary.retry_scheduled++;
370
+ break;
371
+ }
372
+ }
373
+ return summary;
374
+ }
375
+ function persistBindings(db, registration, owner) {
376
+ const base = process.env.LETTA_BASE_URL || "https://api.letta.com";
377
+ (0, db_1.tx)(db, () => {
378
+ (0, leases_1.assertLeaseHeld)(db, leases_1.SOURCE_LEASE, owner);
379
+ db.prepare(`INSERT INTO workspace_bindings (id, api_base_url, workspace_id, organization_id, user_id, created_at, updated_at)
380
+ VALUES (1, ?, ?, NULL, NULL, ?, ?)
381
+ ON CONFLICT(id) DO UPDATE SET api_base_url = excluded.api_base_url, workspace_id = excluded.workspace_id, updated_at = excluded.updated_at`).run(base, registration.workspaceId, (0, db_1.nowIso)(), (0, db_1.nowIso)());
382
+ db.prepare("UPDATE sources SET server_source_id = ?, status = ?, updated_at = ? WHERE id = ?").run(registration.serverSourceId, registration.status, (0, db_1.nowIso)(), localSourceId());
383
+ });
384
+ }