@assethub/cli 0.1.37 → 0.1.38
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/dist/hooks/install.js +2 -2
- package/dist/hooks/upload.d.ts.map +1 -1
- package/dist/hooks/upload.js +39 -13
- package/package.json +1 -1
package/dist/hooks/install.js
CHANGED
|
@@ -42,9 +42,9 @@ export const sessionSaveDisclosure = (scope, client = 'claude') => {
|
|
|
42
42
|
? `\`assethub hooks uninstall --client ${client}\` from that folder`
|
|
43
43
|
: `\`assethub hooks uninstall --client ${client} --global\``;
|
|
44
44
|
return (`Session saving adds SessionStart, Stop, PreCompact and SessionEnd hooks to ${where} ` +
|
|
45
|
-
'is then copied to ~/.assethub/sessions/ when it ends ' +
|
|
45
|
+
'is then copied to ~/.assethub/sessions/ while you work and when it ends ' +
|
|
46
46
|
'(the conversation only: your prompts, the agent\'s replies and tool calls, with recognised secrets masked, readable only by you; copies older than 30 days are deleted), ' +
|
|
47
|
-
'and uploaded to AssetHub as a coding-agent session only you can read (internal accounts only for now): the masked transcript, ' +
|
|
47
|
+
'and uploaded to AssetHub, every 8 minutes while you work (ASSETHUB_SESSION_UPLOAD_INTERVAL_MIN) and when it ends, as a coding-agent session only you can read (internal accounts only for now): the masked transcript, ' +
|
|
48
48
|
'your prompts, the agent\'s replies, its tool calls and images from the project. Uploads that fail are retried in the background ' +
|
|
49
49
|
'when a later session starts or ends. Secret masking is best effort. ' +
|
|
50
50
|
'Opt a project out with .assethub/no-session-save, or turn it off with ASSETHUB_SESSION_SAVE=off / ASSETHUB_SESSION_UPLOAD=off; ' +
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../src/hooks/upload.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAc,aAAa,EAAC,MAAM,YAAY,CAAA;AAG1D,eAAO,MAAM,+BAA+B,kCAAkC,CAAA;
|
|
1
|
+
{"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../src/hooks/upload.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAc,aAAa,EAAC,MAAM,YAAY,CAAA;AAG1D,eAAO,MAAM,+BAA+B,kCAAkC,CAAA;AAc9E,eAAO,MAAM,iBAAiB,QAAS,CAAA;AACvC,2EAA2E;AAC3E,eAAO,MAAM,oBAAoB,QAAc,CAAA;AAC/C,uFAAuF;AACvF,eAAO,MAAM,wBAAwB,QAAS,CAAA;AAE9C,MAAM,MAAM,UAAU,GAAG;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAC,CAAA;AAEhF,MAAM,MAAM,aAAa,GAAG;IAC1B,wEAAwE;IACxE,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;IACvB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;IACxB,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAA;IAChB,wEAAwE;IACxE,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAC5B,SAAS,IAAI,CAAC,aAAa,EAAE,SAAS,GAAG,MAAM,GAAG,KAAK,CAAC,KACvD,OAAO,CAAC,UAAU,GAAG,IAAI,CA2B3B,CAAA;AAED,iFAAiF;AACjF,eAAO,MAAM,YAAY,GAAI,gBAAgB,MAAM,KAAG,MACmB,CAAA;AAmBzE;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAC5B,MAAM,UAAU,EAChB,YAAW,OAAO,KAAyC,KAC1D,OAAO,CAAC,OAAO,CAgBjB,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,aAAa,GACxB,YAAY,MAAM,EAClB,UAAS,aAAkB,KAC1B,OAAO,CAAC,aAAa,CAmBvB,CAAA;AAqID;;;;GAIG;AACH,eAAO,MAAM,aAAa,GACxB,UAAS,aAAa,GAAG;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAM,KAC/D,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,aAAa,CAAA;CAAC,EAAE,CAkBrD,CAAA"}
|
package/dist/hooks/upload.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { readFile, utimes } from 'node:fs/promises';
|
|
7
7
|
import { basename, join } from 'node:path';
|
|
8
8
|
import { buildRunUploadPlan } from '../runsUpload/buildRunUpload.js';
|
|
9
|
-
import { controlRunUploadUrls } from '../runsUpload/controlEndpoints.js';
|
|
9
|
+
import { CONTROL_ERROR_CODES, controlRunUploadUrls } from '../runsUpload/controlEndpoints.js';
|
|
10
10
|
import { readGraphFolder } from '../runsUpload/graphFolder.js';
|
|
11
11
|
import { uploadRun } from '../runsUpload/uploadRun.js';
|
|
12
12
|
import { RunUploadError } from '../runsUpload/runUploadError.js';
|
|
@@ -23,6 +23,12 @@ const canvasIdOf = (value) => {
|
|
|
23
23
|
return Number.isSafeInteger(id) && id > 0 ? id : null;
|
|
24
24
|
};
|
|
25
25
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
26
|
+
/**
|
|
27
|
+
* Tries per upload to get past revisions the server already holds that this
|
|
28
|
+
* machine has no record of (its ~/.assethub was reset, or another copy of the
|
|
29
|
+
* session uploaded). Each try jumps twice as far, so 8 cover 255 revisions.
|
|
30
|
+
*/
|
|
31
|
+
const MAX_REV_RECOVERIES = 8;
|
|
26
32
|
export const UPLOAD_TIMEOUT_MS = 20_000;
|
|
27
33
|
/** A session's upload lock older than this belongs to a crashed upload. */
|
|
28
34
|
export const UPLOAD_LOCK_STALE_MS = 15 * 60_000;
|
|
@@ -171,6 +177,9 @@ const uploadSessionUnlocked = async (sessionDir, options) => {
|
|
|
171
177
|
if (meta.dirty || meta.transcriptFormat !== TRANSCRIPT_FORMAT)
|
|
172
178
|
meta = (await materializeSession(sessionDir, { env: options.env })) ?? meta;
|
|
173
179
|
let level = meta.compactLevel ?? 0;
|
|
180
|
+
// The next rev is above this too: revs the server turned away in this upload.
|
|
181
|
+
let revFloor = -1;
|
|
182
|
+
let recoveries = 0;
|
|
174
183
|
try {
|
|
175
184
|
const baseFetch = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
176
185
|
const timeoutMs = options.timeoutMs ?? UPLOAD_TIMEOUT_MS;
|
|
@@ -182,7 +191,7 @@ const uploadSessionUnlocked = async (sessionDir, options) => {
|
|
|
182
191
|
// been published, and re-sending its rev with new content is a permanent
|
|
183
192
|
// conflict. The server accepts gaps, so skipping an unsent rev is harmless.
|
|
184
193
|
const latest = (await readMeta(sessionDir)) ?? meta;
|
|
185
|
-
const rev = Math.max(latest.uploadedRev ?? -1, latest.attemptedRev ?? -1) + 1;
|
|
194
|
+
const rev = Math.max(latest.uploadedRev ?? -1, latest.attemptedRev ?? -1, revFloor) + 1;
|
|
186
195
|
let plan;
|
|
187
196
|
try {
|
|
188
197
|
const folder = await readGraphFolder(await buildSessionGraphFolder(sessionDir, { compactLevel: level }));
|
|
@@ -204,17 +213,34 @@ const uploadSessionUnlocked = async (sessionDir, options) => {
|
|
|
204
213
|
throw error;
|
|
205
214
|
}
|
|
206
215
|
await record({ attemptedRev: rev, compactLevel: level });
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
216
|
+
let result;
|
|
217
|
+
try {
|
|
218
|
+
result = await uploadRun({
|
|
219
|
+
plan,
|
|
220
|
+
baseUrl: auth.baseUrl,
|
|
221
|
+
apiKey: auth.apiKey,
|
|
222
|
+
workspaceId: auth.workspaceId,
|
|
223
|
+
fetchImpl,
|
|
224
|
+
urls: controlRunUploadUrls(auth.baseUrl, CODING_AGENT_SESSION_API_PREFIX),
|
|
225
|
+
registrationExtra: {
|
|
226
|
+
session: { client: meta.client, sessionId: meta.sessionId, canvasId: canvasIdOf(meta.canvasId) },
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
if (!(error instanceof RunUploadError && error.code === CONTROL_ERROR_CODES.snapshotConflict))
|
|
232
|
+
throw error;
|
|
233
|
+
result = null;
|
|
234
|
+
}
|
|
235
|
+
// The server holds this rev with other content (conflict) or a newer one
|
|
236
|
+
// (stale), so this snapshot was not stored: jump past it and send again.
|
|
237
|
+
if (!result || result.status === 'stale') {
|
|
238
|
+
if (recoveries >= MAX_REV_RECOVERIES)
|
|
239
|
+
throw new Error(`The server already holds revision ${rev} or later of this session.`);
|
|
240
|
+
revFloor = rev + 2 ** recoveries - 1;
|
|
241
|
+
recoveries += 1;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
218
244
|
await record({
|
|
219
245
|
status: 'uploaded',
|
|
220
246
|
graphId: result.graphId,
|