@kisev/agentomatic 11.0.0-dev.54.g26e86b0b9fcd → 11.0.0-dev.55.g6d90f51aeff6

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,4 +1,3 @@
1
- export declare const RECEIPT_TTL_MS: number;
2
1
  export { LifecycleError, assertSafePath, readRegular, writeAtomic } from "@kisev/safe-fs";
3
2
  export type Scope = "global" | "project";
4
3
  export type FileExpectation = {
@@ -30,12 +29,6 @@ export type FileMutation = {
30
29
  operation: "external-remove";
31
30
  expected: FileExpectation;
32
31
  };
33
- export type SupersededPlan = {
34
- kind: string;
35
- confirmation_digest: string;
36
- created_at: string;
37
- expires_at: string;
38
- };
39
32
  export declare function stable(value: unknown): string;
40
33
  export declare function sha256(value: Buffer | string): string;
41
34
  export declare function digest(value: unknown): string;
@@ -47,20 +40,6 @@ export declare function migrateLegacyDeploymentNamespace(root: string): Promise<
47
40
  export declare function migrateLegacyNamespaces(home?: string): Promise<string[]>;
48
41
  export declare function archiveRoot(scope: Scope, cwd?: string, home?: string): string;
49
42
  export declare function withLifecycleLock<T>(stateRoot: string, callback: () => Promise<T>): Promise<T>;
50
- export declare function saveReceipt(stateRoot: string, kind: string, scope: Scope, root: string, payload: unknown, now?: number, planDigest?: string): Promise<{
51
- digest: string;
52
- plan_digest: string;
53
- expires_at: string;
54
- superseded_plan?: SupersededPlan;
55
- }>;
56
- export declare function consumeReceipt(stateRoot: string, expected: {
57
- digest: string;
58
- kind: string;
59
- scope: Scope;
60
- root: string;
61
- }, now?: number): Promise<unknown>;
62
- /** Invalidate an active receipt when a preview is blocked before a replacement exists. */
63
- export declare function supersedeReceipt(stateRoot: string, now?: number): Promise<void>;
64
43
  export declare function recoverTransaction(root: string, stateRoot: string, allowedRoots?: readonly string[]): Promise<boolean>;
65
44
  export type TransactionOptions = {
66
45
  beforePublish?: (index: number) => void;
package/dist/lifecycle.js CHANGED
@@ -1,9 +1,8 @@
1
- import { createHash, randomBytes } from "node:crypto";
1
+ import { createHash } from "node:crypto";
2
2
  import { constants } from "node:fs";
3
3
  import { lstat, mkdir, open, readFile, readdir, rename, rm, rmdir, stat, unlink, } from "node:fs/promises";
4
4
  import { homedir } from "node:os";
5
5
  import { dirname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
6
- export const RECEIPT_TTL_MS = 10 * 60 * 1000;
7
6
  export { LifecycleError, assertSafePath, readRegular, writeAtomic } from "@kisev/safe-fs";
8
7
  import { LifecycleError, assertSafePath, ensureDirectory, lstatSafe, readRegular, writeAtomic, } from "@kisev/safe-fs";
9
8
  export function stable(value) {
@@ -179,200 +178,6 @@ export async function withLifecycleLock(stateRoot, callback) {
179
178
  }
180
179
  throw new LifecycleError("lifecycle_locked", "Cannot acquire lifecycle lock");
181
180
  }
182
- function receiptPath(stateRoot) {
183
- return join(stateRoot, "receipt.json");
184
- }
185
- export async function saveReceipt(stateRoot, kind, scope, root, payload, now = Date.now(), planDigest = digest(payload)) {
186
- const existingRaw = await readRegular(receiptPath(stateRoot));
187
- let superseded;
188
- if (existingRaw) {
189
- const existing = parseReceipt(existingRaw);
190
- if (!existing.consumed && Date.parse(existing.expires_at) >= now) {
191
- superseded = existing;
192
- }
193
- }
194
- const confirmationDigest = sha256(randomBytes(32));
195
- const receipt = {
196
- schema_version: 1,
197
- digest: confirmationDigest,
198
- plan_digest: planDigest,
199
- nonce: randomBytes(32).toString("base64url"),
200
- created_at: new Date(now).toISOString(),
201
- expires_at: new Date(now + RECEIPT_TTL_MS).toISOString(),
202
- consumed: false,
203
- kind,
204
- scope,
205
- root,
206
- payload,
207
- ...(superseded
208
- ? {
209
- superseded_digests: [superseded.digest, ...(superseded.superseded_digests ?? [])],
210
- }
211
- : {}),
212
- integrity: "",
213
- };
214
- receipt.integrity = receiptIntegrity(receipt);
215
- await writeAtomic(receiptPath(stateRoot), Buffer.from(`${stable(receipt)}\n`), 0o600);
216
- return {
217
- digest: receipt.digest,
218
- plan_digest: receipt.plan_digest,
219
- expires_at: receipt.expires_at,
220
- ...(superseded
221
- ? {
222
- superseded_plan: {
223
- kind: superseded.kind,
224
- confirmation_digest: superseded.digest.slice(0, 12),
225
- created_at: superseded.created_at,
226
- expires_at: superseded.expires_at,
227
- },
228
- }
229
- : {}),
230
- };
231
- }
232
- function parseReceipt(raw) {
233
- let value;
234
- try {
235
- value = JSON.parse(raw.toString("utf8"));
236
- }
237
- catch {
238
- throw new LifecycleError("invalid_receipt", "Receipt is not valid JSON");
239
- }
240
- const receipt = value;
241
- if (receipt &&
242
- receipt.schema_version === 1 &&
243
- typeof receipt.digest === "string" &&
244
- /^[a-f0-9]{64}$/.test(receipt.digest) &&
245
- typeof receipt.nonce === "string" &&
246
- typeof receipt.expires_at === "string" &&
247
- typeof receipt.consumed === "boolean" &&
248
- typeof receipt.kind === "string" &&
249
- (receipt.scope === "global" || receipt.scope === "project") &&
250
- typeof receipt.root === "string" &&
251
- typeof receipt.integrity === "string" &&
252
- receipt.plan_digest === undefined) {
253
- const legacyDocument = {
254
- schema_version: 1,
255
- digest: receipt.digest,
256
- nonce: receipt.nonce,
257
- expires_at: receipt.expires_at,
258
- consumed: receipt.consumed,
259
- kind: receipt.kind,
260
- scope: receipt.scope,
261
- root: receipt.root,
262
- payload: receipt.payload,
263
- integrity: receipt.integrity,
264
- };
265
- if (receipt.digest !==
266
- digest({
267
- schema_version: 1,
268
- kind: receipt.kind,
269
- scope: receipt.scope,
270
- root: receipt.root,
271
- payload: receipt.payload,
272
- }) ||
273
- receipt.integrity !==
274
- digest({
275
- schema_version: 1,
276
- digest: receipt.digest,
277
- nonce: receipt.nonce,
278
- expires_at: receipt.expires_at,
279
- consumed: receipt.consumed,
280
- kind: receipt.kind,
281
- scope: receipt.scope,
282
- root: receipt.root,
283
- payload: receipt.payload,
284
- })) {
285
- throw new LifecycleError("invalid_receipt", "Receipt integrity check failed");
286
- }
287
- const payloadRecord = receipt.payload;
288
- const planDigest = payloadRecord &&
289
- typeof payloadRecord === "object" &&
290
- typeof payloadRecord.digest === "string" &&
291
- /^[a-f0-9]{64}$/.test(payloadRecord.digest)
292
- ? payloadRecord.digest
293
- : digest(receipt.payload);
294
- return {
295
- ...legacyDocument,
296
- plan_digest: planDigest,
297
- created_at: new Date(Date.parse(receipt.expires_at) - RECEIPT_TTL_MS).toISOString(),
298
- };
299
- }
300
- if (!receipt ||
301
- receipt.schema_version !== 1 ||
302
- typeof receipt.digest !== "string" ||
303
- !/^[a-f0-9]{64}$/.test(receipt.digest) ||
304
- typeof receipt.plan_digest !== "string" ||
305
- !/^[a-f0-9]{64}$/.test(receipt.plan_digest) ||
306
- typeof receipt.nonce !== "string" ||
307
- typeof receipt.created_at !== "string" ||
308
- typeof receipt.expires_at !== "string" ||
309
- typeof receipt.consumed !== "boolean" ||
310
- (receipt.superseded !== undefined && typeof receipt.superseded !== "boolean") ||
311
- typeof receipt.kind !== "string" ||
312
- (receipt.scope !== "global" && receipt.scope !== "project") ||
313
- typeof receipt.root !== "string" ||
314
- (receipt.superseded_digests !== undefined &&
315
- (!Array.isArray(receipt.superseded_digests) ||
316
- !receipt.superseded_digests.every((value) => typeof value === "string" && /^[a-f0-9]{64}$/.test(value)))) ||
317
- typeof receipt.integrity !== "string") {
318
- throw new LifecycleError("invalid_receipt", "Receipt has an unsupported schema");
319
- }
320
- const payloadRecord = receipt.payload;
321
- const payloadPlanDigest = payloadRecord &&
322
- typeof payloadRecord === "object" &&
323
- typeof payloadRecord.plan_digest === "string"
324
- ? payloadRecord.plan_digest
325
- : digest(receipt.payload);
326
- if (receipt.plan_digest !== payloadPlanDigest) {
327
- throw new LifecycleError("invalid_receipt", "Receipt digest does not match its payload");
328
- }
329
- if (receipt.integrity !== receiptIntegrity(receipt))
330
- throw new LifecycleError("invalid_receipt", "Receipt integrity check failed");
331
- return receipt;
332
- }
333
- function receiptIntegrity(receipt) {
334
- const { integrity: _integrity, ...document } = receipt;
335
- return digest(document);
336
- }
337
- export async function consumeReceipt(stateRoot, expected, now = Date.now()) {
338
- if (!/^[a-f0-9]{64}$/.test(expected.digest))
339
- throw new LifecycleError("invalid_digest", "Confirmation digest must be a SHA-256 value");
340
- const raw = await readRegular(receiptPath(stateRoot));
341
- if (!raw)
342
- throw new LifecycleError("confirmation_unknown", "Confirmation receipt is missing");
343
- const receipt = parseReceipt(raw);
344
- if (receipt.digest !== expected.digest ||
345
- receipt.kind !== expected.kind ||
346
- receipt.scope !== expected.scope ||
347
- receipt.root !== expected.root) {
348
- if (receipt.superseded_digests?.includes(expected.digest))
349
- throw new LifecycleError("superseded_plan", "Confirmation receipt was superseded by a newer plan");
350
- throw new LifecycleError("confirmation_unknown", "Confirmation does not match the saved plan");
351
- }
352
- if (receipt.superseded)
353
- throw new LifecycleError("superseded_plan", "Confirmation receipt was superseded by a newer plan");
354
- if (receipt.consumed)
355
- throw new LifecycleError("confirmation_consumed", "Confirmation receipt was already consumed");
356
- if (Date.parse(receipt.expires_at) < now)
357
- throw new LifecycleError("confirmation_expired", "Confirmation receipt expired; request a fresh plan");
358
- const consumed = { ...receipt, consumed: true, integrity: "" };
359
- consumed.integrity = receiptIntegrity(consumed);
360
- await writeAtomic(receiptPath(stateRoot), Buffer.from(`${stable(consumed)}\n`), 0o600);
361
- return receipt.payload;
362
- }
363
- /** Invalidate an active receipt when a preview is blocked before a replacement exists. */
364
- export async function supersedeReceipt(stateRoot, now = Date.now()) {
365
- const path = receiptPath(stateRoot);
366
- const raw = await readRegular(path);
367
- if (!raw)
368
- return;
369
- const receipt = parseReceipt(raw);
370
- if (receipt.consumed || receipt.superseded || Date.parse(receipt.expires_at) < now)
371
- return;
372
- const superseded = { ...receipt, consumed: true, superseded: true, integrity: "" };
373
- superseded.integrity = receiptIntegrity(superseded);
374
- await writeAtomic(path, Buffer.from(`${stable(superseded)}\n`), 0o600);
375
- }
376
181
  function journalPath(stateRoot) {
377
182
  return join(stateRoot, "transaction-journal.json");
378
183
  }
@@ -1,4 +1,4 @@
1
- import { LifecycleError, type SupersededPlan, type Scope, type TransactionOptions } from "./lifecycle.js";
1
+ import { LifecycleError, type Scope, type TransactionOptions } from "./lifecycle.js";
2
2
  export type ReconcileStatus = "current" | "retired" | "renamed" | "modified-managed" | "user-owned" | "unknown" | "conflict" | "diagnostic-state-only";
3
3
  export type ReconcileItem = {
4
4
  path: string;
@@ -26,12 +26,7 @@ export type ReconcilePlan = {
26
26
  operation: "archive" | "remove" | "write";
27
27
  sha256?: string;
28
28
  }>;
29
- plan_digest: string;
30
- confirmation_digest?: string;
31
- superseded_plan?: SupersededPlan;
32
29
  confirmable: boolean;
33
- digest?: string;
34
- receipt_expires_at?: string;
35
30
  };
36
31
  export type ReconcileResult = {
37
32
  status: "ok";
@@ -44,4 +39,4 @@ export declare class ReconcileError extends LifecycleError {
44
39
  export declare function previewReconcile(scope: Scope, cwd?: string, home?: string): Promise<ReconcilePlan>;
45
40
  /** Build the ownership classification without receipts, locks, or recovery. */
46
41
  export declare function inspectReconcile(scope: Scope, cwd?: string, home?: string): Promise<ReconcilePlan>;
47
- export declare function applyReconcile(scope: Scope, confirmationDigest: string, cwd?: string, home?: string, options?: ReconcileOptions): Promise<ReconcileResult>;
42
+ export declare function applyReconcile(scope: Scope, cwd?: string, home?: string, options?: ReconcileOptions): Promise<ReconcileResult>;
package/dist/reconcile.js CHANGED
@@ -3,7 +3,7 @@ import { lstat, readdir, readFile } from "node:fs/promises";
3
3
  import { homedir } from "node:os";
4
4
  import { dirname, join, relative, resolve, sep } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
- import { applyTransaction, archiveRoot, assertSafePath, consumeReceipt, destination, digest, LifecycleError, lifecycleRoot, recoverTransaction, saveReceipt, supersedeReceipt, sha256, stable, withLifecycleLock, } from "./lifecycle.js";
6
+ import { applyTransaction, archiveRoot, assertSafePath, destination, LifecycleError, lifecycleRoot, recoverTransaction, sha256, stable, withLifecycleLock, } from "./lifecycle.js";
7
7
  import { archiveMutations } from "./installer.js";
8
8
  const PACKAGE_NAME = "@kisev/agentomatic";
9
9
  const GENERIC_MANIFEST = ".agentomatic-manifest.json";
@@ -394,12 +394,11 @@ async function build(scope, cwd = process.cwd(), home = homedir()) {
394
394
  diagnostic_state_only: await diagnostic(home),
395
395
  operations: operations.sort((left, right) => left.path.localeCompare(right.path)),
396
396
  };
397
- const planDigest = digest(base);
398
397
  const confirmable = operations.length > 0 &&
399
398
  groups["modified-managed"].length === 0 &&
400
399
  groups.conflict.length === 0;
401
400
  return {
402
- plan: { ...base, plan_digest: planDigest, confirmable, digest: planDigest },
401
+ plan: { ...base, confirmable },
403
402
  mutations,
404
403
  };
405
404
  }
@@ -413,23 +412,7 @@ export async function previewReconcile(scope, cwd = process.cwd(), home = homedi
413
412
  const built = await build(scope, cwd, home);
414
413
  const blocked = built.plan.modified_managed.length > 0 || built.plan.conflicts.length > 0;
415
414
  const actionable = built.plan.operations.length > 0;
416
- if (blocked || !actionable) {
417
- await supersedeReceipt(stateRoot);
418
- const { digest: _digest, ...withoutReceipt } = built.plan;
419
- return { ...withoutReceipt, confirmable: false };
420
- }
421
- const receipt = await saveReceipt(stateRoot, "reconcile", scope, root, {
422
- plan_digest: built.plan.plan_digest,
423
- }, Date.now(), built.plan.plan_digest);
424
- return {
425
- ...built.plan,
426
- plan_digest: built.plan.plan_digest,
427
- confirmation_digest: receipt.digest,
428
- digest: receipt.digest,
429
- confirmable: true,
430
- receipt_expires_at: receipt.expires_at,
431
- ...(receipt.superseded_plan ? { superseded_plan: receipt.superseded_plan } : {}),
432
- };
415
+ return { ...built.plan, confirmable: !blocked && actionable };
433
416
  });
434
417
  }
435
418
  catch (error) {
@@ -444,22 +427,14 @@ export async function previewReconcile(scope, cwd = process.cwd(), home = homedi
444
427
  export async function inspectReconcile(scope, cwd = process.cwd(), home = homedir()) {
445
428
  return (await build(scope, cwd, home)).plan;
446
429
  }
447
- export async function applyReconcile(scope, confirmationDigest, cwd = process.cwd(), home = homedir(), options = {}) {
430
+ export async function applyReconcile(scope, cwd = process.cwd(), home = homedir(), options = {}) {
448
431
  const stateRoot = lifecycleRoot(scope, cwd, home);
449
432
  const root = scopeRoot(scope, cwd, home);
450
433
  try {
451
434
  return await withLifecycleLock(stateRoot, async () => {
452
435
  if (await recoverTransaction(root, stateRoot, reconcileAllowedRoots(scope, cwd, home)))
453
436
  throw new ReconcileError("recovered_transaction", "Recovered an interrupted transaction; request a fresh plan");
454
- const payload = (await consumeReceipt(stateRoot, {
455
- digest: confirmationDigest,
456
- kind: "reconcile",
457
- scope,
458
- root,
459
- }));
460
437
  const built = await build(scope, cwd, home);
461
- if (built.plan.plan_digest !== (payload.plan_digest ?? payload.digest))
462
- throw new ReconcileError("stale_plan", "Reconcile inventory changed after preview");
463
438
  if (built.plan.conflicts.length || built.plan.modified_managed.length)
464
439
  throw new ReconcileError("conflict", "Reconcile contains unsafe ownership conflicts");
465
440
  await applyTransaction(root, stateRoot, built.mutations, {
@@ -476,7 +451,7 @@ export async function applyReconcile(scope, confirmationDigest, cwd = process.cw
476
451
  return {
477
452
  status: "ok",
478
453
  applied: true,
479
- plan: { ...built.plan, digest: confirmationDigest },
454
+ plan: built.plan,
480
455
  };
481
456
  });
482
457
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kisev/agentomatic",
3
- "version": "11.0.0-dev.54.g26e86b0b9fcd",
3
+ "version": "11.0.0-dev.55.g6d90f51aeff6",
4
4
  "description": "OpenCode integration and opt-in installer for portable Agent Skills.",
5
5
  "homepage": "https://github.com/kisev/skills#readme",
6
6
  "bugs": {
@@ -49,8 +49,8 @@
49
49
  "pack:check": "node test/pack-allowlist.mjs"
50
50
  },
51
51
  "dependencies": {
52
- "@kisev/memomatic": "1.0.0-dev.54.g26e86b0b9fcd",
53
- "@kisev/safe-fs": "1.0.0-dev.54.g26e86b0b9fcd"
52
+ "@kisev/memomatic": "1.0.0-dev.55.g6d90f51aeff6",
53
+ "@kisev/safe-fs": "1.0.0-dev.55.g6d90f51aeff6"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@opencode-ai/plugin": "1.18.29",