@assethub/cli 0.1.36 → 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/save.d.ts +8 -0
- package/dist/hooks/save.d.ts.map +1 -1
- package/dist/hooks/save.js +32 -6
- 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; ' +
|
package/dist/hooks/save.d.ts
CHANGED
|
@@ -56,6 +56,14 @@ export declare const SWEEP_LIMIT = 3;
|
|
|
56
56
|
* only be uploaded once abandoned (ABANDONED_AFTER_MS quiet, then a new session).
|
|
57
57
|
*/
|
|
58
58
|
export declare const WORKING_UPLOAD_INTERVAL_MS: number;
|
|
59
|
+
/** The shortest interval ASSETHUB_SESSION_UPLOAD_INTERVAL_MIN may set. */
|
|
60
|
+
export declare const MIN_WORKING_UPLOAD_INTERVAL_MS: number;
|
|
61
|
+
/**
|
|
62
|
+
* ASSETHUB_SESSION_UPLOAD_INTERVAL_MIN, in minutes, else WORKING_UPLOAD_INTERVAL_MS.
|
|
63
|
+
* Each upload sends a new snapshot of the whole transcript, so it is never
|
|
64
|
+
* shorter than a minute; a value that is not a positive number is ignored.
|
|
65
|
+
*/
|
|
66
|
+
export declare const workingUploadIntervalMs: (env: NodeJS.ProcessEnv) => number;
|
|
59
67
|
/**
|
|
60
68
|
* Copy + redact the transcript Claude Code keeps at `meta.transcriptPath`, and
|
|
61
69
|
* collect its images. Runs once per SessionEnd and before an upload, never per
|
package/dist/hooks/save.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"save.d.ts","sourceRoot":"","sources":["../../src/hooks/save.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAC,UAAU,EAAE,SAAS,EAAgB,WAAW,EAAC,MAAM,YAAY,CAAA;AAChF,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,aAAa,CAAA;AAE9C,eAAO,MAAM,UAAU,KAAK,CAAA;AAC5B,eAAO,MAAM,eAAe,QAAmB,CAAA;AAC/C,eAAO,MAAM,sBAAsB,KAAK,CAAA;AASxC,MAAM,MAAM,kBAAkB,GAAG;IAC/B,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oCAAoC;IACpC,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,IAAI,CAAA;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;IACvB,MAAM,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,CAAA;IACvC;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IAC5D,uEAAuE;IACvE,UAAU,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CACpE,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,SAAS,GAAG,UAAU,CAAA;CACjC,CAAA;AAYD,wFAAwF;AACxF,eAAO,MAAM,mBAAmB,GAAI,YAAY,MAAM,EAAE,UAAU,MAAM,KAAG,IACgC,CAAA;AAE3G,qEAAqE;AACrE,eAAO,MAAM,0BAA0B,GAAI,YAAY,MAAM,EAAE,UAAU,MAAM,KAAG,IACP,CAAA;AAE3E,8DAA8D;AAC9D,eAAO,MAAM,kBAAkB,GAAI,UAAU,MAAM,KAAG,IAC+B,CAAA;AAErF,eAAO,MAAM,aAAa,GAAI,KAAK,MAAM,CAAC,UAAU,KAAG,MAAM,GAAG,IAM/D,CAAA;AAED,4FAA4F;AAC5F,eAAO,MAAM,aAAa,GACxB,MAAM,MAAM,EACZ,KAAK,IAAI,EACT,MAAM,MAAM,EACZ,MAAM,MAAM,KACX,OAAO,CAAC,MAAM,CAwBhB,CAAA;AAgFD,qGAAqG;AACrG,eAAO,MAAM,kBAAkB,QAAqB,CAAA;AACpD,wEAAwE;AACxE,eAAO,MAAM,WAAW,IAAI,CAAA;AAE5B;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,
|
|
1
|
+
{"version":3,"file":"save.d.ts","sourceRoot":"","sources":["../../src/hooks/save.ts"],"names":[],"mappings":"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"}
|
package/dist/hooks/save.js
CHANGED
|
@@ -162,7 +162,24 @@ export const SWEEP_LIMIT = 3;
|
|
|
162
162
|
* stay open for days and rarely reach SessionEnd, so without this they would
|
|
163
163
|
* only be uploaded once abandoned (ABANDONED_AFTER_MS quiet, then a new session).
|
|
164
164
|
*/
|
|
165
|
-
export const WORKING_UPLOAD_INTERVAL_MS =
|
|
165
|
+
export const WORKING_UPLOAD_INTERVAL_MS = 8 * 60 * 1000;
|
|
166
|
+
/** The shortest interval ASSETHUB_SESSION_UPLOAD_INTERVAL_MIN may set. */
|
|
167
|
+
export const MIN_WORKING_UPLOAD_INTERVAL_MS = 60 * 1000;
|
|
168
|
+
/**
|
|
169
|
+
* ASSETHUB_SESSION_UPLOAD_INTERVAL_MIN, in minutes, else WORKING_UPLOAD_INTERVAL_MS.
|
|
170
|
+
* Each upload sends a new snapshot of the whole transcript, so it is never
|
|
171
|
+
* shorter than a minute; a value that is not a positive number is ignored.
|
|
172
|
+
*/
|
|
173
|
+
export const workingUploadIntervalMs = (env) => {
|
|
174
|
+
const raw = env.ASSETHUB_SESSION_UPLOAD_INTERVAL_MIN?.trim();
|
|
175
|
+
if (!raw)
|
|
176
|
+
return WORKING_UPLOAD_INTERVAL_MS;
|
|
177
|
+
const ms = Number(raw) * 60 * 1000;
|
|
178
|
+
// Finite after scaling too: a huge value would otherwise stop uploads for good.
|
|
179
|
+
if (!Number.isFinite(ms) || ms <= 0)
|
|
180
|
+
return WORKING_UPLOAD_INTERVAL_MS;
|
|
181
|
+
return Math.max(ms, MIN_WORKING_UPLOAD_INTERVAL_MS);
|
|
182
|
+
};
|
|
166
183
|
const WORKING_UPLOAD_EVENTS = new Set(['Stop', 'PreCompact']);
|
|
167
184
|
/** Events that copy and redact the transcript now; the rest only mark it changed. */
|
|
168
185
|
const FULL_SAVE_EVENTS = new Set(['SessionEnd', 'Manual']);
|
|
@@ -323,11 +340,20 @@ export const saveSession = async (options = {}) => {
|
|
|
323
340
|
const result = { sessionDir, event };
|
|
324
341
|
if (uploadOn && options.startWorkingUpload && WORKING_UPLOAD_EVENTS.has(event)) {
|
|
325
342
|
const latest = await readMeta(sessionDir);
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
343
|
+
const interval = workingUploadIntervalMs(env);
|
|
344
|
+
let claimed = false;
|
|
345
|
+
// Checked and stamped under the meta lock, before spawning, so neither the
|
|
346
|
+
// next few turns nor a hook running beside this one start another.
|
|
347
|
+
if (latest)
|
|
348
|
+
await updateMeta(sessionDir, latest, current => {
|
|
349
|
+
// Counts uploads any path started (an earlier Stop, SessionEnd, a sweep).
|
|
350
|
+
const lastStart = Math.max(Date.parse(current.lastWorkingUploadAt ?? '') || 0, Date.parse(current.lastUploadAttemptAt ?? '') || 0);
|
|
351
|
+
if (now.getTime() - lastStart < interval)
|
|
352
|
+
return current;
|
|
353
|
+
claimed = true;
|
|
354
|
+
return { ...current, lastWorkingUploadAt: now.toISOString() };
|
|
355
|
+
});
|
|
356
|
+
if (claimed) {
|
|
331
357
|
options.startWorkingUpload(sessionDir, options.upload?.profile);
|
|
332
358
|
result.uploadStarted = true;
|
|
333
359
|
}
|
|
@@ -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,
|