sealkeep 0.9.0 → 0.11.0
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/ARCHITECTURE.md +163 -14
- package/CHANGELOG.md +223 -1
- package/CONTROL_PLANE.md +2 -2
- package/LICENSE +1 -1
- package/README.md +108 -22
- package/THIRD_PARTY.md +2 -2
- package/THREAT_MODEL.md +23 -4
- package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/chunk-access.d.ts +26 -4
- package/dist/packages/sealkeep-crypto/src/chunk-access.js +219 -0
- package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/envelope.d.ts +11 -1
- package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/envelope.js +58 -21
- package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/format.d.ts +2 -2
- package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/index.d.ts +1 -0
- package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/index.js +1 -0
- package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/recipients.js +4 -4
- package/dist/packages/sealkeep-crypto/src/squeeze.d.ts +6 -0
- package/dist/packages/sealkeep-crypto/src/squeeze.js +39 -0
- package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/stream.d.ts +36 -3
- package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/stream.js +196 -37
- package/dist/site/index.html +1808 -1904
- package/dist/site/llms.txt +67 -0
- package/dist/site/trust/architecture-data-flow.html +53 -0
- package/dist/site/trust/audit-roadmap.html +37 -0
- package/dist/site/trust/deployment-responsibility.html +11 -0
- package/dist/site/trust/dpa-sample.html +30 -0
- package/dist/site/trust/release-provenance.html +21 -0
- package/dist/site/trust/subprocessors.html +15 -0
- package/dist/site/trust/threat-model.html +34 -0
- package/dist/site/trust/toms.html +41 -0
- package/dist/site/trust-document.css +32 -0
- package/dist/site/trust.html +73 -0
- package/dist/site/visual/assets/index-8Nxnkc7o.js +207 -0
- package/dist/site/visual/assets/index-oxLy1bpy.css +1 -0
- package/dist/site/visual/index.html +18 -0
- package/dist/site.zip +0 -0
- package/dist/src/activity.d.ts +9 -0
- package/dist/src/activity.js +90 -1
- package/dist/src/adapters.d.ts +175 -5
- package/dist/src/adapters.js +961 -73
- package/dist/src/agent-context.d.ts +135 -0
- package/dist/src/agent-context.js +1059 -0
- package/dist/src/archive-copies.d.ts +47 -0
- package/dist/src/archive-copies.js +179 -0
- package/dist/src/audit.d.ts +1 -1
- package/dist/src/audit.js +29 -4
- package/dist/src/autopilot.d.ts +52 -7
- package/dist/src/autopilot.js +143 -25
- package/dist/src/background-bandwidth.d.ts +46 -0
- package/dist/src/background-bandwidth.js +301 -0
- package/dist/src/background-cpu.d.ts +82 -0
- package/dist/src/background-cpu.js +212 -0
- package/dist/src/background-worker-error.d.ts +12 -0
- package/dist/src/background-worker-error.js +18 -0
- package/dist/src/branding.d.ts +24 -1
- package/dist/src/branding.js +31 -1
- package/dist/src/bridge.d.ts +233 -0
- package/dist/src/bridge.js +604 -0
- package/dist/src/byte-stream.d.ts +91 -0
- package/dist/src/byte-stream.js +385 -0
- package/dist/src/chunk-store.d.ts +41 -8
- package/dist/src/chunk-store.js +161 -65
- package/dist/src/cli.js +1698 -163
- package/dist/src/cloud.d.ts +841 -31
- package/dist/src/cloud.js +3196 -277
- package/dist/src/context-background.d.ts +37 -0
- package/dist/src/context-background.js +309 -0
- package/dist/src/context-drain-child.d.ts +1 -0
- package/dist/src/context-drain-child.js +98 -0
- package/dist/src/context-reader.d.ts +118 -0
- package/dist/src/context-reader.js +447 -0
- package/dist/src/control-plane/auth.d.ts +32 -4
- package/dist/src/control-plane/auth.js +85 -24
- package/dist/src/control-plane/server.js +19 -6
- package/dist/src/control-plane.d.ts +17 -1
- package/dist/src/control-plane.js +32 -6
- package/dist/src/crypto.d.ts +1 -1
- package/dist/src/crypto.js +5 -5
- package/dist/src/daemon-lease.d.ts +70 -0
- package/dist/src/daemon-lease.js +420 -0
- package/dist/src/daemon.d.ts +94 -1
- package/dist/src/daemon.js +1082 -105
- package/dist/src/darwin-service-policy.d.ts +41 -0
- package/dist/src/darwin-service-policy.js +60 -0
- package/dist/src/dashboard-cli.js +15 -15
- package/dist/src/device-authorization.d.ts +37 -0
- package/dist/src/device-authorization.js +199 -0
- package/dist/src/device-enrollment.d.ts +91 -0
- package/dist/src/device-enrollment.js +349 -0
- package/dist/src/disk.d.ts +17 -12
- package/dist/src/disk.js +43 -17
- package/dist/src/doctor.d.ts +35 -1
- package/dist/src/doctor.js +316 -41
- package/dist/src/durable-ticket-lock.d.ts +24 -0
- package/dist/src/durable-ticket-lock.js +232 -0
- package/dist/src/enroll.d.ts +1 -1
- package/dist/src/enroll.js +13 -7
- package/dist/src/env.d.ts +10 -1
- package/dist/src/env.js +11 -3
- package/dist/src/errors.d.ts +8 -8
- package/dist/src/errors.js +6 -6
- package/dist/src/flush.d.ts +12 -0
- package/dist/src/flush.js +37 -0
- package/dist/src/heartbeat.d.ts +86 -12
- package/dist/src/heartbeat.js +415 -29
- package/dist/src/index-background-watchdog.d.ts +1 -0
- package/dist/src/index-background-watchdog.js +94 -0
- package/dist/src/index-background-work.d.ts +21 -0
- package/dist/src/index-background-work.js +25 -0
- package/dist/src/index-background.d.ts +64 -0
- package/dist/src/index-background.js +394 -0
- package/dist/src/index-build-child.d.ts +1 -0
- package/dist/src/index-build-child.js +109 -0
- package/dist/src/index-manifest.d.ts +52 -0
- package/dist/src/index-manifest.js +444 -0
- package/dist/src/index-publication-proof.d.ts +84 -0
- package/dist/src/index-publication-proof.js +380 -0
- package/dist/src/index-publication-state.d.ts +149 -0
- package/dist/src/index-publication-state.js +696 -0
- package/dist/src/index-publication-verifier.d.ts +89 -0
- package/dist/src/index-publication-verifier.js +341 -0
- package/dist/src/index-publish.d.ts +62 -0
- package/dist/src/index-publish.js +540 -0
- package/dist/src/index-scratch-cleanup.d.ts +19 -0
- package/dist/src/index-scratch-cleanup.js +166 -0
- package/dist/src/index-segment-types.d.ts +132 -0
- package/dist/src/index-segment-types.js +21 -0
- package/dist/src/index-segments.d.ts +9 -0
- package/dist/src/index-segments.js +516 -0
- package/dist/src/index-store.d.ts +123 -0
- package/dist/src/index-store.js +495 -0
- package/dist/src/index-sync.d.ts +91 -15
- package/dist/src/index-sync.js +286 -53
- package/dist/src/index-upgrade-publication.d.ts +30 -0
- package/dist/src/index-upgrade-publication.js +179 -0
- package/dist/src/integration-manager.d.ts +32 -0
- package/dist/src/integration-manager.js +394 -0
- package/dist/src/leakscan.js +1 -1
- package/dist/src/local-api.d.ts +56 -15
- package/dist/src/local-api.js +4979 -541
- package/dist/src/machine-settings.d.ts +51 -0
- package/dist/src/machine-settings.js +166 -0
- package/dist/src/managed-chunks.d.ts +5 -2
- package/dist/src/managed-chunks.js +14 -14
- package/dist/src/mcp-install.d.ts +11 -9
- package/dist/src/mcp-install.js +73 -28
- package/dist/src/mcp-workspace.d.ts +18 -0
- package/dist/src/mcp-workspace.js +50 -0
- package/dist/src/mcp.js +294 -25
- package/dist/src/migrate.js +27 -21
- package/dist/src/notify.d.ts +1 -1
- package/dist/src/notify.js +5 -5
- package/dist/src/offload.d.ts +201 -14
- package/dist/src/offload.js +1848 -140
- package/dist/src/onboarding.d.ts +8 -1
- package/dist/src/onboarding.js +4 -4
- package/dist/src/packages.d.ts +2 -2
- package/dist/src/packages.js +10 -2
- package/dist/src/passkey.d.ts +0 -1
- package/dist/src/passkey.js +2 -7
- package/dist/src/password-lock.d.ts +2 -2
- package/dist/src/password-lock.js +6 -6
- package/dist/src/paths.d.ts +2 -0
- package/dist/src/paths.js +2 -0
- package/dist/src/presence.d.ts +86 -0
- package/dist/src/presence.js +240 -0
- package/dist/src/progress-deadline.d.ts +21 -0
- package/dist/src/progress-deadline.js +91 -0
- package/dist/src/project-repair.d.ts +55 -0
- package/dist/src/project-repair.js +131 -0
- package/dist/src/providers/gcs.d.ts +28 -7
- package/dist/src/providers/gcs.js +35 -24
- package/dist/src/providers/gdrive.d.ts +71 -8
- package/dist/src/providers/gdrive.js +223 -50
- package/dist/src/providers/index.d.ts +11 -3
- package/dist/src/providers/index.js +30 -10
- package/dist/src/providers/s3.d.ts +30 -8
- package/dist/src/providers/s3.js +41 -30
- package/dist/src/providers/safe-storage-fetch.d.ts +12 -0
- package/dist/src/providers/safe-storage-fetch.js +72 -0
- package/dist/src/queue.d.ts +136 -19
- package/dist/src/queue.js +862 -96
- package/dist/src/reclaim-transaction.d.ts +156 -0
- package/dist/src/reclaim-transaction.js +1027 -0
- package/dist/src/recovery-codes.d.ts +32 -0
- package/dist/src/recovery-codes.js +338 -0
- package/dist/src/recovery.js +12 -9
- package/dist/src/rehydrate.d.ts +25 -22
- package/dist/src/rehydrate.js +319 -23
- package/dist/src/restore.d.ts +57 -4
- package/dist/src/restore.js +272 -36
- package/dist/src/resume-stub.d.ts +92 -0
- package/dist/src/resume-stub.js +417 -0
- package/dist/src/retention.d.ts +98 -7
- package/dist/src/retention.js +1066 -62
- package/dist/src/rotate.js +3 -3
- package/dist/src/search.d.ts +566 -8
- package/dist/src/search.js +5897 -290
- package/dist/src/secrets.d.ts +51 -7
- package/dist/src/secrets.js +316 -24
- package/dist/src/service.d.ts +49 -11
- package/dist/src/service.js +776 -35
- package/dist/src/share.js +3 -3
- package/dist/src/shared-spaces.d.ts +98 -0
- package/dist/src/shared-spaces.js +214 -0
- package/dist/src/source-reader.d.ts +73 -0
- package/dist/src/source-reader.js +715 -0
- package/dist/src/spool.d.ts +1 -1
- package/dist/src/spool.js +1 -1
- package/dist/src/start-tui.js +2 -1
- package/dist/src/start.js +2 -2
- package/dist/src/storage-endpoint.d.ts +21 -0
- package/dist/src/storage-endpoint.js +122 -0
- package/dist/src/storage-setup.js +12 -12
- package/dist/src/storage-targets.d.ts +109 -6
- package/dist/src/storage-targets.js +975 -67
- package/dist/src/stream-to-cloud.d.ts +5 -1
- package/dist/src/stream-to-cloud.js +34 -14
- package/dist/src/sync-rules.d.ts +31 -6
- package/dist/src/sync-rules.js +153 -14
- package/dist/src/team-backfill-scheduling.d.ts +8 -0
- package/dist/src/team-backfill-scheduling.js +33 -0
- package/dist/src/team-backfill.d.ts +116 -0
- package/dist/src/team-backfill.js +1429 -0
- package/dist/src/team-index-cache.d.ts +16 -0
- package/dist/src/team-index-cache.js +152 -0
- package/dist/src/team-offboarding.d.ts +38 -0
- package/dist/src/team-offboarding.js +1043 -0
- package/dist/src/team-presence.d.ts +127 -0
- package/dist/src/team-presence.js +904 -0
- package/dist/src/team-publication-policy.d.ts +20 -0
- package/dist/src/team-publication-policy.js +140 -0
- package/dist/src/team-realtime.d.ts +68 -0
- package/dist/src/team-realtime.js +816 -0
- package/dist/src/team-source-facts-cache.d.ts +23 -0
- package/dist/src/team-source-facts-cache.js +255 -0
- package/dist/src/trash.d.ts +1 -1
- package/dist/src/trash.js +2 -2
- package/dist/src/tui.js +11 -12
- package/dist/src/types.d.ts +173 -7
- package/dist/src/types.js +20 -0
- package/dist/src/ui-server.d.ts +163 -35
- package/dist/src/ui-server.js +712 -72
- package/dist/src/ui.d.ts +1 -2
- package/dist/src/ui.js +1 -2
- package/dist/src/upload.d.ts +27 -0
- package/dist/src/upload.js +383 -43
- package/dist/src/vault.d.ts +226 -30
- package/dist/src/vault.js +1776 -192
- package/dist/src/watcher.d.ts +7 -1
- package/dist/src/watcher.js +198 -55
- package/dist/src/worker.d.ts +27 -3
- package/dist/src/worker.js +274 -55
- package/package.json +33 -12
- package/scripts/native-reboot-rehearsal.mjs +90 -0
- package/web/app.js +6032 -343
- package/web/bootstrap.js +17 -0
- package/web/index.html +255 -57
- package/web/rail.js +317 -40
- package/web/retention.html +2 -2
- package/web/rules-view.js +188 -16
- package/web/sessions-view.js +485 -62
- package/web/sessions.html +2 -2
- package/web/setup-api.js +152 -29
- package/web/setup-logic.js +68 -9
- package/web/setup.html +113 -44
- package/web/setup.js +604 -71
- package/web/style.css +513 -98
- package/dist/packages/vaultline-crypto/src/chunk-access.js +0 -93
- /package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/aead.d.ts +0 -0
- /package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/aead.js +0 -0
- /package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/format.js +0 -0
- /package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/recipients.d.ts +0 -0
- /package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/sha256-stream.d.ts +0 -0
- /package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/sha256-stream.js +0 -0
package/dist/src/share.js
CHANGED
|
@@ -5,7 +5,7 @@ import { canonicalPhrase } from "./mnemonic.js";
|
|
|
5
5
|
import { isV2 } from "./types.js";
|
|
6
6
|
import { listArchives, readConfig } from "./vault.js";
|
|
7
7
|
import { frameObject, unframeObject } from "./cloud.js";
|
|
8
|
-
import { openArchiveToFile, rewrapArchive } from "../packages/
|
|
8
|
+
import { openArchiveToFile, rewrapArchive } from "../packages/sealkeep-crypto/src/index.js";
|
|
9
9
|
import { materialiseArchive } from "./offload.js";
|
|
10
10
|
import { randomInt } from "node:crypto";
|
|
11
11
|
import { WORDS } from "./mnemonic.js";
|
|
@@ -14,7 +14,7 @@ export function generateSharePasscode() {
|
|
|
14
14
|
return Array.from({ length: PASSCODE_WORDS }, () => WORDS[randomInt(WORDS.length)]).join(" ");
|
|
15
15
|
}
|
|
16
16
|
export async function createShareBundle(dataDir, archiveId, bundlePath, options = { vaultPhrase: "" }) {
|
|
17
|
-
const record = (await listArchives(dataDir)).find((item) => item.id === archiveId);
|
|
17
|
+
const record = (await listArchives(dataDir)).find((item) => item.id === archiveId || (archiveId.length >= 4 && /^[0-9a-f]+$/i.test(archiveId) && item.id.toLowerCase().startsWith(archiveId.toLowerCase())));
|
|
18
18
|
if (!record)
|
|
19
19
|
fail("archive_not_found", `Archive not found: ${archiveId}`, { archiveId });
|
|
20
20
|
if (!isV2(record))
|
|
@@ -55,7 +55,7 @@ export async function openShareBundle(bundlePath, destination, passcode) {
|
|
|
55
55
|
const { mkdtemp, rm, writeFile: write } = await import("node:fs/promises");
|
|
56
56
|
const { tmpdir } = await import("node:os");
|
|
57
57
|
const { join } = await import("node:path");
|
|
58
|
-
const scratch = await mkdtemp(join(tmpdir(), "
|
|
58
|
+
const scratch = await mkdtemp(join(tmpdir(), "sealkeep-share-"));
|
|
59
59
|
const payload = join(scratch, "payload");
|
|
60
60
|
try {
|
|
61
61
|
await write(payload, ciphertext, { mode: 0o600 });
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared spaces — the experience layer over the sharing primitives.
|
|
3
|
+
*
|
|
4
|
+
* The goal (set 2026-08-26): working together on one project with one shared
|
|
5
|
+
* memory should feel like a place, not a pile of flags. Coming to "Shared"
|
|
6
|
+
* shows what you share and what is shared to you; one copyable command starts
|
|
7
|
+
* an agent WITH that memory reachable; a joining agent reads a compact
|
|
8
|
+
* context pack instead of pulling the whole history; members are managed like
|
|
9
|
+
* a team around a table.
|
|
10
|
+
*
|
|
11
|
+
* Everything here composes machinery that already exists and is already
|
|
12
|
+
* tested: project-scoped recipients (`vault.ts`), the sealed synced content
|
|
13
|
+
* index (`index-sync.ts`, acceptance 4.3–4.8), bridge mirrors (`bridge.ts`),
|
|
14
|
+
* and the MCP tools (`mcp.ts`). This module adds no cryptography and no
|
|
15
|
+
* network calls — it reads local state and writes one markdown file, so the
|
|
16
|
+
* launcher works offline and never blocks an agent start on a fetch.
|
|
17
|
+
*/
|
|
18
|
+
export type SpaceMember = {
|
|
19
|
+
id: string;
|
|
20
|
+
label: string;
|
|
21
|
+
publicKey: string;
|
|
22
|
+
addedAt: string;
|
|
23
|
+
group?: string;
|
|
24
|
+
project?: string;
|
|
25
|
+
projectKey?: string;
|
|
26
|
+
};
|
|
27
|
+
export type SharedProject = {
|
|
28
|
+
name: string;
|
|
29
|
+
/** Collision-safe local identity; null only for pre-stable-key records and recipients. */
|
|
30
|
+
projectKey: string | null;
|
|
31
|
+
/** Unambiguous argument accepted by buildContextPack and the launcher. */
|
|
32
|
+
ref: string;
|
|
33
|
+
/** Members scoped to exactly this project (vault-wide members are listed separately — they open everything). */
|
|
34
|
+
members: SpaceMember[];
|
|
35
|
+
archives: number;
|
|
36
|
+
bytes: number;
|
|
37
|
+
lastSealedAt: string | null;
|
|
38
|
+
};
|
|
39
|
+
export type SharedChannel = {
|
|
40
|
+
channel: string;
|
|
41
|
+
mirrorPath: string;
|
|
42
|
+
bytes: number;
|
|
43
|
+
updatedAt: string;
|
|
44
|
+
};
|
|
45
|
+
export type SharedSpaces = {
|
|
46
|
+
/** Projects that have at least one scoped member, or that were asked for by name. */
|
|
47
|
+
projects: SharedProject[];
|
|
48
|
+
/** Members with no project scope: they can open everything this vault seals. */
|
|
49
|
+
vaultWide: SpaceMember[];
|
|
50
|
+
/** Live-bridge mirrors present on this machine: sessions someone is sharing to us right now (or recently). */
|
|
51
|
+
channels: SharedChannel[];
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* What this machine shares and follows, from local state alone.
|
|
55
|
+
*
|
|
56
|
+
* "Shared by me" is the recipient list grouped by project — a member on
|
|
57
|
+
* `checkout-rewrite` is a grant you made. "Shared to me" appears here as the
|
|
58
|
+
* bridge mirrors under the vault's own bridge dir (a channel someone gave us)
|
|
59
|
+
* — cross-account grants land as storage targets + a key, and read the same
|
|
60
|
+
* way once mounted. No network: the plane is not consulted, so `shared` works
|
|
61
|
+
* on a plane outage and in tests.
|
|
62
|
+
*/
|
|
63
|
+
export declare function listSharedSpaces(dataDir: string): Promise<SharedSpaces>;
|
|
64
|
+
/** A ref names either a project or a bridge channel; the pack says which it resolved to. */
|
|
65
|
+
export type ContextPack = {
|
|
66
|
+
ref: string;
|
|
67
|
+
kind: "project" | "channel";
|
|
68
|
+
projectKey: string | null;
|
|
69
|
+
path: string;
|
|
70
|
+
markdown: string;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* The compact context pack: what a joining agent reads FIRST, instead of the
|
|
74
|
+
* history. It is deliberately small — member list, the dozen most recent
|
|
75
|
+
* sealed sessions, the live mirror if one exists, and instructions on how to
|
|
76
|
+
* reach the rest (search first, recover dry-run). It contains labels, ids,
|
|
77
|
+
* dates and paths, never a phrase, a key, or transcript content — so the file
|
|
78
|
+
* itself is safe to sit in a prompt.
|
|
79
|
+
*/
|
|
80
|
+
export declare function buildContextPack(dataDir: string, ref: string): Promise<ContextPack>;
|
|
81
|
+
export type LaunchPlan = {
|
|
82
|
+
agent: "claude" | "codex";
|
|
83
|
+
argv: string[];
|
|
84
|
+
env: Record<string, string>;
|
|
85
|
+
pack: ContextPack;
|
|
86
|
+
instruction: string;
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* How to start an agent with the shared memory attached. Pure planning — the
|
|
90
|
+
* CLI decides whether to exec it or print it, and tests assert on the plan.
|
|
91
|
+
*
|
|
92
|
+
* The instruction is injected the way each agent accepts context: Claude Code
|
|
93
|
+
* takes `--append-system-prompt`; Codex takes an initial prompt argument. In
|
|
94
|
+
* both cases the instruction is one sentence pointing at the pack — the pack
|
|
95
|
+
* carries the detail, so the injected text stays small and the agent's own
|
|
96
|
+
* context window is spent on work, not on our preamble.
|
|
97
|
+
*/
|
|
98
|
+
export declare function buildLaunchPlan(dataDir: string, agent: "claude" | "codex", ref: string, passthrough?: string[]): Promise<LaunchPlan>;
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared spaces — the experience layer over the sharing primitives.
|
|
3
|
+
*
|
|
4
|
+
* The goal (set 2026-08-26): working together on one project with one shared
|
|
5
|
+
* memory should feel like a place, not a pile of flags. Coming to "Shared"
|
|
6
|
+
* shows what you share and what is shared to you; one copyable command starts
|
|
7
|
+
* an agent WITH that memory reachable; a joining agent reads a compact
|
|
8
|
+
* context pack instead of pulling the whole history; members are managed like
|
|
9
|
+
* a team around a table.
|
|
10
|
+
*
|
|
11
|
+
* Everything here composes machinery that already exists and is already
|
|
12
|
+
* tested: project-scoped recipients (`vault.ts`), the sealed synced content
|
|
13
|
+
* index (`index-sync.ts`, acceptance 4.3–4.8), bridge mirrors (`bridge.ts`),
|
|
14
|
+
* and the MCP tools (`mcp.ts`). This module adds no cryptography and no
|
|
15
|
+
* network calls — it reads local state and writes one markdown file, so the
|
|
16
|
+
* launcher works offline and never blocks an agent start on a fetch.
|
|
17
|
+
*/
|
|
18
|
+
import { readdir, stat, mkdir, writeFile } from "node:fs/promises";
|
|
19
|
+
import { hostname } from "node:os";
|
|
20
|
+
import { createHash } from "node:crypto";
|
|
21
|
+
import { join, basename } from "node:path";
|
|
22
|
+
import { listArchives, readConfig } from "./vault.js";
|
|
23
|
+
import { fail } from "./errors.js";
|
|
24
|
+
import { PRODUCT_SLUG } from "./branding.js";
|
|
25
|
+
/**
|
|
26
|
+
* What this machine shares and follows, from local state alone.
|
|
27
|
+
*
|
|
28
|
+
* "Shared by me" is the recipient list grouped by project — a member on
|
|
29
|
+
* `checkout-rewrite` is a grant you made. "Shared to me" appears here as the
|
|
30
|
+
* bridge mirrors under the vault's own bridge dir (a channel someone gave us)
|
|
31
|
+
* — cross-account grants land as storage targets + a key, and read the same
|
|
32
|
+
* way once mounted. No network: the plane is not consulted, so `shared` works
|
|
33
|
+
* on a plane outage and in tests.
|
|
34
|
+
*/
|
|
35
|
+
export async function listSharedSpaces(dataDir) {
|
|
36
|
+
const config = await readConfig(dataDir);
|
|
37
|
+
const recipients = (config.recipients ?? []);
|
|
38
|
+
const vaultWide = recipients.filter((member) => !member.project && !member.projectKey);
|
|
39
|
+
const byProject = new Map();
|
|
40
|
+
const scopeOf = (project, projectKey) => projectKey ?? `legacy:${project}`;
|
|
41
|
+
const summaryFor = (project, projectKey) => {
|
|
42
|
+
const scope = scopeOf(project, projectKey);
|
|
43
|
+
const held = byProject.get(scope);
|
|
44
|
+
if (held)
|
|
45
|
+
return held;
|
|
46
|
+
const created = {
|
|
47
|
+
name: project,
|
|
48
|
+
projectKey: projectKey ?? null,
|
|
49
|
+
members: [],
|
|
50
|
+
archives: 0,
|
|
51
|
+
bytes: 0,
|
|
52
|
+
last: null,
|
|
53
|
+
};
|
|
54
|
+
byProject.set(scope, created);
|
|
55
|
+
return created;
|
|
56
|
+
};
|
|
57
|
+
for (const member of recipients) {
|
|
58
|
+
if (!member.project && !member.projectKey)
|
|
59
|
+
continue;
|
|
60
|
+
summaryFor(member.project ?? member.projectKey, member.projectKey).members.push(member);
|
|
61
|
+
}
|
|
62
|
+
const records = await listArchives(dataDir);
|
|
63
|
+
for (const record of records) {
|
|
64
|
+
const project = record.version === 2 ? record.source.project : undefined;
|
|
65
|
+
if (!project)
|
|
66
|
+
continue;
|
|
67
|
+
const projectKey = record.version === 2 ? record.source.projectKey : undefined;
|
|
68
|
+
const entry = summaryFor(project, projectKey);
|
|
69
|
+
entry.archives += 1;
|
|
70
|
+
entry.bytes += record.source.bytes;
|
|
71
|
+
if (!entry.last || record.createdAt > entry.last)
|
|
72
|
+
entry.last = record.createdAt;
|
|
73
|
+
}
|
|
74
|
+
// A project is "shared" when someone was let in; a project with archives but
|
|
75
|
+
// no members still lists (with zero members) so the add-member UI has
|
|
76
|
+
// something to attach to.
|
|
77
|
+
const projects = [...byProject.values()]
|
|
78
|
+
.sort((left, right) => left.name.localeCompare(right.name)
|
|
79
|
+
|| (left.projectKey ?? "").localeCompare(right.projectKey ?? ""))
|
|
80
|
+
.map((project) => ({
|
|
81
|
+
name: project.name,
|
|
82
|
+
projectKey: project.projectKey,
|
|
83
|
+
ref: project.projectKey ?? project.name,
|
|
84
|
+
members: project.members,
|
|
85
|
+
archives: project.archives,
|
|
86
|
+
bytes: project.bytes,
|
|
87
|
+
lastSealedAt: project.last,
|
|
88
|
+
}));
|
|
89
|
+
const channels = [];
|
|
90
|
+
const bridgeDir = join(dataDir, "bridge");
|
|
91
|
+
const entries = await readdir(bridgeDir).catch(() => []);
|
|
92
|
+
for (const entry of entries) {
|
|
93
|
+
if (!entry.endsWith(".mirror.jsonl"))
|
|
94
|
+
continue;
|
|
95
|
+
const mirrorPath = join(bridgeDir, entry);
|
|
96
|
+
const info = await stat(mirrorPath).catch(() => null);
|
|
97
|
+
if (!info)
|
|
98
|
+
continue;
|
|
99
|
+
channels.push({ channel: entry.replace(/\.mirror\.jsonl$/, ""), mirrorPath, bytes: info.size, updatedAt: info.mtime.toISOString() });
|
|
100
|
+
}
|
|
101
|
+
channels.sort((a, b) => (a.updatedAt < b.updatedAt ? 1 : -1));
|
|
102
|
+
return { projects, vaultWide, channels };
|
|
103
|
+
}
|
|
104
|
+
const slug = (ref) => ref.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64) || "shared";
|
|
105
|
+
/**
|
|
106
|
+
* The compact context pack: what a joining agent reads FIRST, instead of the
|
|
107
|
+
* history. It is deliberately small — member list, the dozen most recent
|
|
108
|
+
* sealed sessions, the live mirror if one exists, and instructions on how to
|
|
109
|
+
* reach the rest (search first, recover dry-run). It contains labels, ids,
|
|
110
|
+
* dates and paths, never a phrase, a key, or transcript content — so the file
|
|
111
|
+
* itself is safe to sit in a prompt.
|
|
112
|
+
*/
|
|
113
|
+
export async function buildContextPack(dataDir, ref) {
|
|
114
|
+
if (!ref.trim())
|
|
115
|
+
fail("invalid_argument", "A project name or bridge channel is required");
|
|
116
|
+
const spaces = await listSharedSpaces(dataDir);
|
|
117
|
+
const channel = spaces.channels.find((c) => c.channel === ref);
|
|
118
|
+
const exactProject = spaces.projects.find((project) => project.projectKey === ref);
|
|
119
|
+
const namedProjects = spaces.projects.filter((project) => project.name === ref);
|
|
120
|
+
if (!exactProject && namedProjects.length > 1) {
|
|
121
|
+
fail("invalid_argument", `More than one stable project is named ${ref}; use one exact project ref so memories from different folders are not combined: ${namedProjects.map((project) => project.ref).join(", ")}`);
|
|
122
|
+
}
|
|
123
|
+
const project = exactProject ?? namedProjects[0];
|
|
124
|
+
const kind = channel && !project ? "channel" : "project";
|
|
125
|
+
const displayRef = project?.name ?? ref;
|
|
126
|
+
// A channel pack deliberately lists NO sessions: a channel is not a project,
|
|
127
|
+
// and enumerating the vault's newest archives into a pack that rides shared
|
|
128
|
+
// prompts would leak every project's existence to whoever is on the channel.
|
|
129
|
+
const records = kind === "channel" || !project ? [] : (await listArchives(dataDir))
|
|
130
|
+
.filter((record) => record.version === 2 && (project.projectKey
|
|
131
|
+
? record.source.projectKey === project.projectKey
|
|
132
|
+
: !record.source.projectKey && record.source.project === project.name))
|
|
133
|
+
.sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1))
|
|
134
|
+
.slice(0, 12);
|
|
135
|
+
const members = [
|
|
136
|
+
...(project?.members ?? []).map((m) => `- ${m.label} — member of “${displayRef}”`),
|
|
137
|
+
...spaces.vaultWide.map((m) => `- ${m.label} — member of everything in this vault`),
|
|
138
|
+
];
|
|
139
|
+
const lines = [];
|
|
140
|
+
lines.push(`# Sealkeep shared memory — ${displayRef}`);
|
|
141
|
+
lines.push("");
|
|
142
|
+
lines.push(`Generated ${new Date().toISOString()} on ${hostname()}. This file is for the agent starting a session here.`);
|
|
143
|
+
lines.push("");
|
|
144
|
+
lines.push(`This ${kind === "channel" ? "session is following a live shared channel" : "project has a sealed, shared history"} in Sealkeep. You (the agent) can reach it through the ${PRODUCT_SLUG}_* MCP tools.`);
|
|
145
|
+
lines.push("");
|
|
146
|
+
lines.push("## What to do");
|
|
147
|
+
lines.push(kind === "project"
|
|
148
|
+
? `1. Search the shared history BEFORE you read the code or make any change — treat this as a required first step, not an option: call \`${PRODUCT_SLUG}_search\` with your question (project: “${displayRef}”${project?.projectKey ? `, projectKey: “${project.projectKey}”` : ""}). The working files show WHAT exists, not the decisions, constraints, and gotchas the team already settled — those live only in the sealed sessions, and skipping this repeats solved work or breaks an invariant you cannot see.`
|
|
149
|
+
: `1. The live mirror below is your primary context. Search the sealed history only for the project the driver names — ask over the channel rather than searching vault-wide.`);
|
|
150
|
+
lines.push(`2. Restore only what you need: \`${PRODUCT_SLUG}_recover\` is dry-run by default; name the session id the search gave you.`);
|
|
151
|
+
if (kind === "project")
|
|
152
|
+
lines.push(`3. Before taking new work, call \`${PRODUCT_SLUG}_team\` — if a teammate already STARTED the same thing, coordinate instead of doubling it.`);
|
|
153
|
+
if (channel) {
|
|
154
|
+
lines.push(`4. A live mirror of the shared session is at \`${channel.mirrorPath}\` — read it for current state. It grows every few seconds while the driver works; re-read it rather than caching.`);
|
|
155
|
+
lines.push(`5. To speak to the driver and the other followers, call \`${PRODUCT_SLUG}_bridge_push\` with this channel ("${ref}") — your words reach the driver's agent on its next turn. Check \`${PRODUCT_SLUG}_bridge_inbox\` between tasks for what teammates said to you.`);
|
|
156
|
+
}
|
|
157
|
+
lines.push("");
|
|
158
|
+
if (members.length) {
|
|
159
|
+
lines.push("## Who is in this space");
|
|
160
|
+
lines.push(...members);
|
|
161
|
+
lines.push("");
|
|
162
|
+
}
|
|
163
|
+
if (records.length) {
|
|
164
|
+
lines.push("## Recent sealed sessions");
|
|
165
|
+
for (const record of records) {
|
|
166
|
+
lines.push(`- ${record.id.slice(0, 12)} · ${record.source.agent} · ${basename(record.source.path)} · ${record.createdAt.slice(0, 10)}`);
|
|
167
|
+
}
|
|
168
|
+
lines.push("");
|
|
169
|
+
}
|
|
170
|
+
lines.push("## Ground rules");
|
|
171
|
+
lines.push("- Cite the session id when you reuse a decision from the shared history, so provenance survives.");
|
|
172
|
+
lines.push("- Never paste secrets into a session that seals into a shared project — members can read what the team sealed.");
|
|
173
|
+
lines.push(`- If the ${PRODUCT_SLUG}_* tools are missing from your tool list, tell the user to run \`${PRODUCT_SLUG} mcp install\`.`);
|
|
174
|
+
lines.push("");
|
|
175
|
+
const markdown = lines.join("\n");
|
|
176
|
+
const dir = join(dataDir, "context");
|
|
177
|
+
await mkdir(dir, { recursive: true });
|
|
178
|
+
// Injective filename: the slug reads well, the hash keeps “web.app”,
|
|
179
|
+
// “web app” and a channel named like a project from silently sharing a file.
|
|
180
|
+
const canonicalRef = project?.projectKey ?? ref;
|
|
181
|
+
const distinct = createHash("sha256").update(`${kind}:${canonicalRef}`).digest("hex").slice(0, 8);
|
|
182
|
+
const path = join(dir, `${slug(displayRef)}-${distinct}.md`);
|
|
183
|
+
await writeFile(path, markdown, { mode: 0o600 });
|
|
184
|
+
return { ref, kind, projectKey: project?.projectKey ?? null, path, markdown };
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* How to start an agent with the shared memory attached. Pure planning — the
|
|
188
|
+
* CLI decides whether to exec it or print it, and tests assert on the plan.
|
|
189
|
+
*
|
|
190
|
+
* The instruction is injected the way each agent accepts context: Claude Code
|
|
191
|
+
* takes `--append-system-prompt`; Codex takes an initial prompt argument. In
|
|
192
|
+
* both cases the instruction is one sentence pointing at the pack — the pack
|
|
193
|
+
* carries the detail, so the injected text stays small and the agent's own
|
|
194
|
+
* context window is spent on work, not on our preamble.
|
|
195
|
+
*/
|
|
196
|
+
export async function buildLaunchPlan(dataDir, agent, ref, passthrough = []) {
|
|
197
|
+
const pack = await buildContextPack(dataDir, ref);
|
|
198
|
+
const instruction = `Sealkeep: shared team memory for “${ref}” is attached to this session. ` +
|
|
199
|
+
`Before you read the code or make any change, read ${pack.path} and search the shared history with the ${PRODUCT_SLUG}_* MCP tools — the working files do not show the team's prior decisions, constraints, or gotchas, so searching first is a required step, not an option. Recover dry-run.`;
|
|
200
|
+
const argv = agent === "claude"
|
|
201
|
+
? ["claude", "--append-system-prompt", instruction, ...passthrough]
|
|
202
|
+
: ["codex", ...passthrough, instruction];
|
|
203
|
+
return {
|
|
204
|
+
agent,
|
|
205
|
+
argv,
|
|
206
|
+
env: {
|
|
207
|
+
SEALKEEP_CONTEXT: pack.path,
|
|
208
|
+
SEALKEEP_SHARED_REF: pack.projectKey ?? ref,
|
|
209
|
+
...(pack.projectKey ? { SEALKEEP_PROJECT_KEY: pack.projectKey } : {}),
|
|
210
|
+
},
|
|
211
|
+
pack,
|
|
212
|
+
instruction,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type Envelope } from "../packages/sealkeep-crypto/src/index.js";
|
|
2
|
+
import { type ArchiveRecord, type ArchiveRecordV2 } from "./types.js";
|
|
3
|
+
import type { ArchiveCatalogFacts, ContentIndex } from "./search.js";
|
|
4
|
+
/** An immutable citation. Storage aliases may change; these source facts may not. */
|
|
5
|
+
export type SourceReference = {
|
|
6
|
+
version: 1;
|
|
7
|
+
archiveId: string;
|
|
8
|
+
envelopeArchiveId: string;
|
|
9
|
+
sourceSha256: string;
|
|
10
|
+
sourceBytes: number;
|
|
11
|
+
ciphertextSha256: string;
|
|
12
|
+
project?: string;
|
|
13
|
+
projectKey?: string;
|
|
14
|
+
spaceKey?: string;
|
|
15
|
+
};
|
|
16
|
+
export type TeamSourceAccess = {
|
|
17
|
+
spaceKey: string;
|
|
18
|
+
membershipVersion: number;
|
|
19
|
+
index: ContentIndex;
|
|
20
|
+
readGuard?: import("./cloud.js").TeamReadGuard;
|
|
21
|
+
};
|
|
22
|
+
export type SourceReadLimits = {
|
|
23
|
+
maxWireBytes?: number;
|
|
24
|
+
maxDecodedBytes?: number;
|
|
25
|
+
signal?: AbortSignal;
|
|
26
|
+
};
|
|
27
|
+
export type SourceRange = {
|
|
28
|
+
start: number;
|
|
29
|
+
end: number;
|
|
30
|
+
};
|
|
31
|
+
export type SourceRead = {
|
|
32
|
+
source: SourceReference;
|
|
33
|
+
record: ArchiveRecord;
|
|
34
|
+
bytes: Buffer;
|
|
35
|
+
byteStart: number;
|
|
36
|
+
byteEnd: number;
|
|
37
|
+
wireBytes: number;
|
|
38
|
+
decodedBytes: number;
|
|
39
|
+
assertCurrent: () => Promise<void>;
|
|
40
|
+
};
|
|
41
|
+
export declare const envelopeDigest: (envelope: Envelope) => string;
|
|
42
|
+
export declare function sourceReference(id: string, facts: ArchiveCatalogFacts, scope?: {
|
|
43
|
+
project?: string;
|
|
44
|
+
projectKey?: string;
|
|
45
|
+
spaceKey?: string;
|
|
46
|
+
}): SourceReference;
|
|
47
|
+
export declare function resolveSourceReference(dataDir: string, phrase: string, archiveId: string, project: string, projectKey?: string): Promise<SourceReference>;
|
|
48
|
+
declare function catalogEntry(index: ContentIndex, id: string, spaceKey?: string): {
|
|
49
|
+
line: string[];
|
|
50
|
+
facts: ArchiveCatalogFacts;
|
|
51
|
+
plaintextBytes: number;
|
|
52
|
+
};
|
|
53
|
+
export declare function teamCatalogEntry(access: TeamSourceAccess, id: string): ReturnType<typeof catalogEntry>;
|
|
54
|
+
/** Shared by search snippets, context reads and explicit Team restoration. */
|
|
55
|
+
export declare function resolveTeamArchive(dataDir: string, id: string, access: TeamSourceAccess, limits?: SourceReadLimits): Promise<{
|
|
56
|
+
record: ArchiveRecordV2;
|
|
57
|
+
teamRead: {
|
|
58
|
+
framed: Buffer<ArrayBufferLike>;
|
|
59
|
+
grants: Set<string>;
|
|
60
|
+
assertCurrent: (refs?: Iterable<string>) => Promise<void>;
|
|
61
|
+
spaceKey: string;
|
|
62
|
+
membershipVersion: number;
|
|
63
|
+
index: ContentIndex;
|
|
64
|
+
readGuard?: import("./cloud.js").TeamReadGuard;
|
|
65
|
+
};
|
|
66
|
+
guard: import("./cloud.js").TeamReadGuard;
|
|
67
|
+
}>;
|
|
68
|
+
/** Resolves the exact immutable source again for every read; citations are never permissions. */
|
|
69
|
+
export declare function readSourceBytes(dataDir: string, phrase: string, source: SourceReference, options?: SourceReadLimits & {
|
|
70
|
+
range?: SourceRange;
|
|
71
|
+
chunkIndex?: number;
|
|
72
|
+
}): Promise<SourceRead>;
|
|
73
|
+
export {};
|