@rigxyz/tapd 0.6.4 → 0.6.7
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/bin.js +56 -5
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-F3HYZR2Y.js → chunk-32K6ANXS.js} +0 -0
- package/dist/chunk-32K6ANXS.js.map +1 -0
- package/dist/index.d.ts +53 -7
- package/dist/index.js +3 -1
- package/package.json +1 -1
- package/dist/chunk-F3HYZR2Y.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../core/src/sync/capability.ts","../../core/src/sync/object.ts","../../core/src/sync/path.ts","../../core/src/sync/protocol.ts","../../core/src/ids.ts","../src/binding-config.ts","../src/relay-client.ts","../src/state-db.ts","../src/tapignore.ts","../src/walker.ts","../src/hash.ts","../src/init.ts","../src/pool.ts","../src/invite.ts","../src/join.ts","../src/imports.ts","../src/import-invite.ts","../src/applier.ts","../src/safe-path.ts","../src/sse.ts","../src/subscription.ts","../src/uploader.ts","../src/start.ts","../src/claude-code.ts","../src/intent-association.ts"],"sourcesContent":["/**\n * Capability tokens — the daemon's access primitive. Every sync API\n * request resolves to an `AuthContext`. Roles (owner/editor/viewer) only\n * gate token *minting*; per-request authorization reads the token's\n * scope, not the underlying role.\n */\n\nexport type CapabilityOp = \"read\" | \"write\" | \"subscribe\";\n\nexport type CapabilityToken = {\n tokenHash: string; // sha256 of the secret; secret never stored\n bindingId: string;\n userId: string | null;\n deviceId: string;\n ops: ReadonlyArray<CapabilityOp>;\n pathGlobs: ReadonlyArray<string>;\n expiresAt: string | null;\n revokedAt: string | null;\n label: string | null;\n createdAt: string;\n};\n\n/**\n * What auth middleware resolves a bearer token into. Carried in\n * request context throughout the relay; never serialized to the wire.\n */\nexport type AuthContext = {\n tokenHash: string;\n bindingId: string;\n ops: ReadonlyArray<CapabilityOp>;\n pathGlobs: ReadonlyArray<string>;\n userId: string | null;\n deviceId: string;\n};\n\nexport function hasOp(auth: AuthContext, op: CapabilityOp): boolean {\n return auth.ops.includes(op);\n}\n","/**\n * Object record — metadata for one file blob in object storage.\n *\n * Objects are content-addressed by full-file sha256 (no chunking in v1).\n * Composite key is (bindingId, hash). v1 deliberately avoids cross-tenant\n * dedup; each binding has its own object pool, scoping IAM and lifecycle\n * boundaries cleanly.\n *\n * Storage key shape:\n * s3://<bucket>/bindings/<bindingId>/sha256/<first2>/<hash>\n */\n\n/** v1 hard ceiling on a single file's bytes. Daemon refuses oversize files. */\nexport const MAX_FILE_BYTES = 100 * 1024 * 1024;\n\nexport type ObjectRecord = {\n bindingId: string;\n hash: string; // \"sha256:…\"\n size: number;\n storageKey: string; // \"s3://bucket/path…\"\n createdAt: string;\n};\n\n/**\n * Build the canonical storage key for a content-addressed object.\n * Same layout in every environment; only the bucket differs.\n */\nexport function objectStorageKey(bindingId: string, hash: string): string {\n const bare = hash.startsWith(\"sha256:\") ? hash.slice(\"sha256:\".length) : hash;\n const fanout = bare.slice(0, 2);\n return `bindings/${bindingId}/sha256/${fanout}/${bare}`;\n}\n","/**\n * Path safety + case-fold for cross-platform filesystem rules.\n *\n * Rules enforced by `validateRigPath`:\n * - reject absolute paths (leading \"/\")\n * - reject \"..\" anywhere\n * - reject backslash separators (windows-style)\n * - reject embedded NUL or other control characters\n * - reject empty segments and leading/trailing slashes\n * - reject the two paths Tap reserves for local metadata (case-insensitively:\n * on APFS/NTFS `.RIG/tap-binding.local.json` IS the binding file)\n *\n * Symlink rejection happens at the daemon (it requires an `lstat`).\n *\n * Also here, so the relay and the daemon share one list: the paths that\n * never sync between members (`isNeverSyncPath`) and the agent-instruction\n * files only a space's owner may change (`isOwnerOnlyPath`).\n */\n\nconst RESERVED_PATHS = [\n \".rig/tap/\",\n \".rig/tap-binding.local.json\",\n];\n\n/**\n * Lower-case + NFC-normalize. Matches what macOS APFS / Windows NTFS\n * treat as case-equivalent in the common case. Pure ASCII is unchanged.\n * Non-ASCII case-folding edge cases (Turkish dotted I, German ß, etc.)\n * are out of scope; we prefer false-positive collisions over false-negative\n * ones since file *sync* should be stricter than the OS.\n */\nexport function foldPath(path: string): string {\n return path.normalize(\"NFC\").toLowerCase();\n}\n\nexport type PathValidationError =\n | \"empty\"\n | \"absolute\"\n | \"parent_segment\"\n | \"backslash\"\n | \"control_char\"\n | \"empty_segment\"\n | \"trailing_slash\"\n | \"reserved\";\n\nexport function validateRigPath(\n path: string,\n): { ok: true } | { ok: false; reason: PathValidationError } {\n if (path.length === 0) return { ok: false, reason: \"empty\" };\n if (path.startsWith(\"/\")) return { ok: false, reason: \"absolute\" };\n if (path.endsWith(\"/\")) return { ok: false, reason: \"trailing_slash\" };\n if (path.includes(\"\\\\\")) return { ok: false, reason: \"backslash\" };\n // Control chars 0x00–0x1F and 0x7F. Any of these in a filename is\n // a sign of bytes-treated-as-text confusion at minimum.\n // eslint-disable-next-line no-control-regex\n if (/[\u0000-\u001f]/.test(path)) return { ok: false, reason: \"control_char\" };\n const segments = path.split(\"/\");\n for (const seg of segments) {\n if (seg.length === 0) return { ok: false, reason: \"empty_segment\" };\n if (seg === \"..\") return { ok: false, reason: \"parent_segment\" };\n }\n const folded = foldPath(path);\n for (const reserved of RESERVED_PATHS) {\n if (folded === reserved.replace(/\\/$/, \"\") || folded.startsWith(reserved)) {\n return { ok: false, reason: \"reserved\" };\n }\n }\n return { ok: true };\n}\n\n// ────────── protected paths ──────────\n//\n// A space is a folder on every member's disk, so a file one member writes\n// lands on everyone's machine. Some files there are not content but\n// configuration that runs code or steers an agent — those are split in two:\n//\n// never-sync Git internals (hooks run on the next commit), Claude Code\n// settings (hooks, permissions), MCP server config, Codex\n// project config. No member's write reaches anyone else: the\n// daemon doesn't upload them, the relay refuses them, and the\n// daemon doesn't apply them if one is in the log anyway.\n// owner-only Agent instructions (CLAUDE.md, AGENTS.md, skills, slash\n// commands, subagents). They are the point of sharing a space,\n// so they sync — but only the space's owner can change them.\n//\n// Matched case-insensitively (APFS/NTFS resolve `.GIT/hooks` to `.git/hooks`)\n// and at any depth: an agent started in a subfolder reads that folder's\n// config too.\n\n/** Directory names that never sync, wherever they appear. */\nconst NEVER_SYNC_DIRS = new Set([\".git\", \".codex\"]);\n/** Trailing segments that never sync, wherever they appear. */\nconst NEVER_SYNC_TAILS: ReadonlyArray<ReadonlyArray<string>> = [\n [\".claude\", \"settings.json\"],\n [\".claude\", \"settings.local.json\"],\n [\".mcp.json\"],\n];\n/** File names only the owner may change, wherever they appear. */\nconst OWNER_ONLY_FILES = new Set([\"claude.md\", \"agents.md\"]);\n/** Directories only the owner may change (and everything below them). */\nconst OWNER_ONLY_DIRS: ReadonlyArray<readonly [string, string]> = [\n [\".claude\", \"skills\"],\n [\".claude\", \"commands\"],\n [\".claude\", \"agents\"],\n // Codex/Amp and other AGENTS.md harnesses load skills from here.\n [\".agents\", \"skills\"],\n];\n\nfunction foldedSegments(path: string): string[] {\n return foldPath(path).split(\"/\");\n}\n\n/** True if `path` is (or is inside) a path that never syncs between members. */\nexport function isNeverSyncPath(path: string): boolean {\n const segs = foldedSegments(path);\n if (segs.some((s) => NEVER_SYNC_DIRS.has(s))) return true;\n return NEVER_SYNC_TAILS.some(\n (tail) =>\n segs.length >= tail.length &&\n tail.every((t, i) => segs[segs.length - tail.length + i] === t),\n );\n}\n\n/** True if only the space's owner may write, delete, or create `path`. */\nexport function isOwnerOnlyPath(path: string): boolean {\n const segs = foldedSegments(path);\n if (OWNER_ONLY_FILES.has(segs[segs.length - 1]!)) return true;\n for (let i = 0; i + 1 < segs.length; i++) {\n if (OWNER_ONLY_DIRS.some(([a, b]) => segs[i] === a && segs[i + 1] === b)) return true;\n }\n return false;\n}\n","/**\n * Sync API path constants and version header.\n *\n * The relay advertises the API version it speaks in every response via\n * `Tap-Sync-Version`. Daemons compare and refuse to talk to incompatible\n * versions.\n */\n\nexport const TAP_SYNC_VERSION = \"1\";\nexport const TAP_SYNC_VERSION_HEADER = \"tap-sync-version\";\n\n// Binding lifecycle\nexport const RIG_BINDINGS_PATH = \"/v1/bindings\";\nexport const RIG_BINDING_PATH = \"/v1/bindings/:bindingId\";\n\n// Sync\nexport const RIG_MANIFEST_PATH = \"/v1/bindings/:bindingId/manifest\";\nexport const RIG_CHANGES_PATH = \"/v1/bindings/:bindingId/changes\";\nexport const RIG_STREAM_PATH = \"/v1/bindings/:bindingId/stream\";\n\n// Objects\nexport const RIG_OBJECTS_PREPARE_UPLOAD_PATH =\n \"/v1/bindings/:bindingId/objects/prepare-upload\";\nexport const RIG_OBJECTS_COMPLETE_UPLOAD_PATH =\n \"/v1/bindings/:bindingId/objects/complete-upload\";\nexport const RIG_OBJECT_DOWNLOAD_URL_PATH =\n \"/v1/bindings/:bindingId/objects/:hash/download-url\";\n\n// Membership (Phase 3)\nexport const RIG_INVITES_PATH = \"/v1/bindings/:bindingId/invites\";\nexport const RIG_INVITE_ACCEPT_PATH = \"/v1/invites/:secret/accept\";\nexport const RIG_MEMBERS_PATH = \"/v1/bindings/:bindingId/members\";\nexport const RIG_DEVICES_PATH = \"/v1/bindings/:bindingId/devices\";\n","import { randomBytes } from \"node:crypto\";\n\nconst ALPHABET = \"0123456789abcdefghjkmnpqrstvwxyz\";\n\nexport function shortId(prefix: string, bytes = 6): string {\n const buf = randomBytes(bytes);\n let out = \"\";\n for (let i = 0; i < buf.length; i++) {\n out += ALPHABET[buf[i]! % ALPHABET.length];\n }\n return `${prefix}_${out}`;\n}\n\n// Hosted rig sync. Change-event ids come from Postgres (a bigint\n// sequence, formatted as `chg_<n>` at the wire boundary), so there\n// is no client-side minter for them.\nexport const newBindingId = () => shortId(\"bnd\");\nexport const newDeviceId = () => shortId(\"dev\");\nexport const newInviteId = () => shortId(\"inv\");\nexport const newImportEdgeId = () => shortId(\"ime\");\nexport const newIntentId = () => shortId(\"int\");\nexport const newUserId = () => shortId(\"usr\");\nexport const newUserSessionId = () => shortId(\"usess\");\nexport const newOrgId = () => shortId(\"org\");\nexport const newOrgInviteId = () => shortId(\"oinv\");\nexport const newShareLinkId = () => shortId(\"shl\");\n\n/**\n * Capability-token secret. ~192 bits, base64url-encoded. The relay\n * only stores `sha256(secret)`; the secret is returned exactly once,\n * at mint time, and never re-derivable from the DB.\n */\nexport function newCapabilitySecret(): string {\n return `tap_cap_${randomBytes(24).toString(\"base64url\")}`;\n}\n\n/**\n * Invite secret. Same shape and storage discipline as capability\n * secrets — relay stores sha256(secret), plaintext returned once at\n * mint time. URL form: `<baseUrl>/v1/invites/<secret>/accept`.\n */\nexport function newInviteSecret(): string {\n return `tap_inv_${randomBytes(24).toString(\"base64url\")}`;\n}\n\n/**\n * Personal Access Token. Long-lived, full-account, no refresh. Today\n * this is the only kind of user session the relay mints — `tapd login`\n * picks one up from `POST /v1/me/tokens` after rig's web `/auth/device`\n * page hands it the user's Clerk JWT. The distinct prefix makes\n * accidental commits to public repos easier to scan for.\n */\nexport function newPatSecret(): string {\n return `rpat_${randomBytes(24).toString(\"base64url\")}`;\n}\n\n/**\n * Share-link secret. Same shape and storage discipline as capability/invite\n * secrets — relay stores sha256(secret), plaintext returned once at mint\n * time. URL form: `<publicUrl>/a/<secret>`.\n */\nexport function newShareLinkSecret(): string {\n return `tap_shr_${randomBytes(24).toString(\"base64url\")}`;\n}\n","/**\n * .rig/tap-binding.local.json — machine-local per-binding pointer.\n *\n * Holds the bindingId, the device's capability token, and where to find\n * the relay. NEVER synced (validated by isAlwaysExcluded in tapignore.ts;\n * the relay also rejects the path via validateRigPath).\n *\n * Tokens live here in plaintext for now. A future revision can move\n * them to the OS keychain; the file structure is designed to make that\n * swap localized.\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\nexport const BINDING_CONFIG_PATH = \".rig/tap-binding.local.json\";\n\nexport type BindingConfig = {\n bindingId: string;\n relayUrl: string;\n deviceId: string;\n /** Capability-token secret. Returned once by the relay, never re-derivable. */\n token: string;\n};\n\nexport function bindingConfigFile(rootDir: string): string {\n return join(rootDir, BINDING_CONFIG_PATH);\n}\n\nexport function readBindingConfig(rootDir: string): BindingConfig | null {\n const path = bindingConfigFile(rootDir);\n if (!existsSync(path)) return null;\n const raw = readFileSync(path, \"utf8\");\n const parsed = JSON.parse(raw) as Partial<BindingConfig>;\n if (\n typeof parsed.bindingId !== \"string\" ||\n typeof parsed.relayUrl !== \"string\" ||\n typeof parsed.deviceId !== \"string\" ||\n typeof parsed.token !== \"string\"\n ) {\n throw new Error(`malformed binding config at ${path}`);\n }\n return {\n bindingId: parsed.bindingId,\n relayUrl: parsed.relayUrl,\n deviceId: parsed.deviceId,\n token: parsed.token,\n };\n}\n\nexport function writeBindingConfig(rootDir: string, config: BindingConfig): void {\n const path = bindingConfigFile(rootDir);\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, JSON.stringify(config, null, 2) + \"\\n\", { mode: 0o600 });\n}\n\n/**\n * `.rig/sync-paused.json` — the per-rig pause flag written by `rig pause` /\n * cleared by `rig resume` (rig's collab.mjs, not this package). Sibling to\n * tap-binding.local.json so it travels with a `rig move`. `tapd start`\n * checks this before doing anything else and refuses/exits-clean while it's\n * set, so a stray restart (cron, launchd, a supervisor) can't silently\n * resume a workspace the user paused.\n */\nexport const SYNC_PAUSED_PATH = \".rig/sync-paused.json\";\n\nexport function syncPausedFile(rootDir: string): string {\n return join(rootDir, SYNC_PAUSED_PATH);\n}\n\n/** Never throws — a missing or malformed file just reads as \"not paused\". */\nexport function isSyncPaused(rootDir: string): boolean {\n const path = syncPausedFile(rootDir);\n if (!existsSync(path)) return false;\n try {\n const parsed = JSON.parse(readFileSync(path, \"utf8\")) as { paused?: boolean };\n return parsed.paused === true;\n } catch {\n return false;\n }\n}\n\n/**\n * Write the pause flag ourselves — normally `rig pause` writes this, but\n * the daemon writes it too when the relay says the binding is gone (410\n * `binding_deleted` on the sync loop — see subscription.ts): a stray\n * restart must see \"paused\", not silently start retrying against a rig\n * that no longer exists. `reason` is informational only — `isSyncPaused`\n * reads just `paused`.\n */\nexport function writeSyncPaused(rootDir: string, reason: string): void {\n const path = syncPausedFile(rootDir);\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, JSON.stringify({ paused: true, reason }, null, 2) + \"\\n\");\n}\n\n/**\n * Thrown when an operation requires a bound workspace but\n * `.rig/tap-binding.local.json` is missing. All three callers (`invite`,\n * `status`, `uninit`) raise the same class so bin.ts maps it to a\n * single `not_initialized` JSON error code for rig.\n */\nexport class NotInitializedError extends Error {\n constructor(public readonly rootDir: string) {\n super(`not initialized — no binding config at ${rootDir}/${BINDING_CONFIG_PATH}`);\n this.name = \"NotInitializedError\";\n }\n}\n","/**\n * Thin fetch wrapper around the relay routes.\n *\n * Module-level `createBinding` and `acceptInvite` are user-authenticated\n * via a relay-issued session bearer (Phase 3.5 replaced the\n * X-Tap-User-Id placeholder). Binding-scoped routes use the\n * `RelayClient` class which carries a (baseUrl, bindingId, capability\n * token) triple.\n *\n * `RelayError` carries the route + status + parsed body so caller logic\n * can map specific 400s (\"object_not_uploaded\", \"case_collision_with\",\n * etc.) to user-facing warnings instead of \"something went wrong\".\n */\n\nimport type {\n AcceptInviteRequest,\n AcceptInviteResponse,\n BindingInviteWithAcceptances,\n ChangesSinceResponse,\n CompleteUploadRequest,\n CompleteUploadResponse,\n CreateBindingRequest,\n CreateBindingResponse,\n CreateInviteRequest,\n CreateInviteResponse,\n DownloadUrlResponse,\n GetBindingResponse,\n Intent,\n IntentUpdate,\n ListIntentsResponse,\n ManifestSnapshotResponse,\n PrepareUploadRequest,\n PrepareUploadResponse,\n SubmitChangeEvent,\n SubmitChangesRequest,\n SubmitChangesResponse,\n SubmitIntent,\n} from \"@tap/core\";\n\ntype FetchLike = typeof fetch;\n\nfunction summarizeRelayError(body: unknown, status: number): string {\n if (typeof body === \"object\" && body !== null && \"error\" in body) {\n const code = String((body as { error: unknown }).error);\n if (code === \"quota_exceeded\") {\n const limit = (body as { limitBytes?: unknown }).limitBytes;\n const mb =\n typeof limit === \"number\" ? Math.round(limit / (1024 * 1024)) : null;\n return mb\n ? `workspace storage limit reached (${mb} MB) — remove files and retry`\n : \"workspace storage limit reached — remove files and retry\";\n }\n return code;\n }\n return `status ${status}`;\n}\n\nexport class RelayError extends Error {\n constructor(\n public readonly route: string,\n public readonly status: number,\n public readonly body: unknown,\n ) {\n super(`relay ${route}: ${summarizeRelayError(body, status)}`);\n this.name = \"RelayError\";\n }\n}\n\n/** The 410 body every binding-scoped route returns once its rig is deleted. */\nexport type BindingDeletedBody = {\n error: \"binding_deleted\";\n deletedAt: string;\n deletedBy: { name: string | null; email: string | null };\n};\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null;\n}\n\n/**\n * True for the relay's 410 `binding_deleted` response — the daemon's sync\n * loop (subscription.ts) uses this to stop retrying a deleted rig instead\n * of spinning on it forever.\n */\nexport function isBindingDeletedError(\n err: unknown,\n): err is RelayError & { body: BindingDeletedBody } {\n return (\n err instanceof RelayError &&\n err.status === 410 &&\n isRecord(err.body) &&\n err.body.error === \"binding_deleted\"\n );\n}\n\nasync function readJson(res: Response): Promise<unknown> {\n const ct = res.headers.get(\"content-type\") ?? \"\";\n if (!ct.includes(\"application/json\")) return undefined;\n try {\n return await res.json();\n } catch {\n return undefined;\n }\n}\n\nasync function expectJson<T>(route: string, res: Response): Promise<T> {\n const body = await readJson(res);\n if (!res.ok) {\n throw new RelayError(route, res.status, body);\n }\n return body as T;\n}\n\n/**\n * Accept an invite. User-authenticated via a relay-issued session\n * bearer (optional for pure-capability invites; required for member\n * invites — the route enforces).\n *\n * Returns the device's per-machine capability token exactly once.\n */\nexport async function acceptInvite(opts: {\n baseUrl: string;\n secret: string;\n /** Bearer access token. Omit for pure-capability accepts. */\n accessToken?: string | null;\n request?: AcceptInviteRequest;\n fetch?: FetchLike;\n}): Promise<AcceptInviteResponse> {\n const fn = opts.fetch ?? fetch;\n const route = \"POST /v1/invites/:secret/accept\";\n const headers: Record<string, string> = { \"content-type\": \"application/json\" };\n if (opts.accessToken) headers.authorization = `Bearer ${opts.accessToken}`;\n const res = await fn(\n joinUrl(opts.baseUrl, `/v1/invites/${encodeURIComponent(opts.secret)}/accept`),\n {\n method: \"POST\",\n headers,\n body: JSON.stringify(opts.request ?? {}),\n },\n );\n return expectJson<AcceptInviteResponse>(route, res);\n}\n\n/**\n * Create a new binding. User-authenticated via a relay-issued session\n * bearer. The response carries the owner's first capability token,\n * returned exactly once.\n */\nexport async function createBinding(opts: {\n baseUrl: string;\n request: CreateBindingRequest;\n accessToken: string;\n fetch?: FetchLike;\n}): Promise<CreateBindingResponse> {\n const fn = opts.fetch ?? fetch;\n const route = \"POST /v1/bindings\";\n const res = await fn(joinUrl(opts.baseUrl, \"/v1/bindings\"), {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n authorization: `Bearer ${opts.accessToken}`,\n },\n body: JSON.stringify(opts.request),\n });\n return expectJson<CreateBindingResponse>(route, res);\n}\n\n/**\n * Self-revoke an import edge (user-authed via the hub token). Tombstones the\n * edge + revokes its capability token. Returns the outcome rather than\n * throwing, so `rig import remove` stays best-effort (a local remove must\n * succeed even when the relay is unreachable or the grant isn't ours).\n */\nexport async function revokeImportEdge(opts: {\n baseUrl: string;\n edgeId: string;\n accessToken: string;\n fetch?: FetchLike;\n}): Promise<{ revoked: boolean; status: number; reason?: string }> {\n const fn = opts.fetch ?? fetch;\n const res = await fn(\n joinUrl(opts.baseUrl, `/v1/me/imports/${encodeURIComponent(opts.edgeId)}`),\n { method: \"DELETE\", headers: { authorization: `Bearer ${opts.accessToken}` } },\n );\n if (res.ok) return { revoked: true, status: res.status };\n const body = await readJson(res);\n const reason =\n typeof body === \"object\" && body !== null && \"reason\" in body\n ? String((body as { reason: unknown }).reason)\n : undefined;\n return { revoked: false, status: res.status, ...(reason ? { reason } : {}) };\n}\n\nexport type RelayClientOptions = {\n baseUrl: string;\n bindingId: string;\n token: string;\n fetch?: FetchLike;\n};\n\n/**\n * Binding-scoped relay client. One instance per active binding; cheap to\n * construct, holds no resources beyond the closed-over fetch impl.\n */\nexport class RelayClient {\n private readonly baseUrl: string;\n private readonly bindingId: string;\n private readonly token: string;\n private readonly fetch: FetchLike;\n\n constructor(opts: RelayClientOptions) {\n this.baseUrl = opts.baseUrl;\n this.bindingId = opts.bindingId;\n this.token = opts.token;\n this.fetch = opts.fetch ?? fetch;\n }\n\n private authHeaders(extra?: Record<string, string>): Record<string, string> {\n return { authorization: `Bearer ${this.token}`, ...extra };\n }\n\n private bindingPath(suffix: string): string {\n return joinUrl(\n this.baseUrl,\n `/v1/bindings/${encodeURIComponent(this.bindingId)}${suffix}`,\n );\n }\n\n async getBinding(): Promise<GetBindingResponse> {\n const route = \"GET /v1/bindings/:id\";\n const res = await this.fetch(this.bindingPath(\"\"), {\n headers: this.authHeaders(),\n });\n return expectJson<GetBindingResponse>(route, res);\n }\n\n // ────────── objects ──────────\n\n async prepareUpload(body: PrepareUploadRequest): Promise<PrepareUploadResponse> {\n const route = \"POST /v1/bindings/:id/objects/prepare-upload\";\n const res = await this.fetch(this.bindingPath(\"/objects/prepare-upload\"), {\n method: \"POST\",\n headers: this.authHeaders({ \"content-type\": \"application/json\" }),\n body: JSON.stringify(body),\n });\n return expectJson<PrepareUploadResponse>(route, res);\n }\n\n async completeUpload(body: CompleteUploadRequest): Promise<CompleteUploadResponse> {\n const route = \"POST /v1/bindings/:id/objects/complete-upload\";\n const res = await this.fetch(this.bindingPath(\"/objects/complete-upload\"), {\n method: \"POST\",\n headers: this.authHeaders({ \"content-type\": \"application/json\" }),\n body: JSON.stringify(body),\n });\n return expectJson<CompleteUploadResponse>(route, res);\n }\n\n async downloadUrl(hash: string): Promise<DownloadUrlResponse> {\n const route = \"GET /v1/bindings/:id/objects/:hash/download-url\";\n const res = await this.fetch(\n this.bindingPath(`/objects/${encodeURIComponent(hash)}/download-url`),\n { headers: this.authHeaders() },\n );\n return expectJson<DownloadUrlResponse>(route, res);\n }\n\n /**\n * PUT bytes to a presigned URL. Throws on non-2xx — except 409, which\n * means the content-addressed object is already stored (same hash = same\n * bytes). That's idempotent success, not a conflict: two files with\n * identical content (e.g. several empty files), or an initial upload that\n * overlaps the daemon's first scan, race to PUT the same key. Treating\n * 409 as fatal aborts the whole push and wedges sync at \"N pending\".\n */\n async putObjectBytes(uploadUrl: string, bytes: Uint8Array | Buffer): Promise<void> {\n const route = \"PUT <uploadUrl>\";\n const res = await this.fetch(uploadUrl, {\n method: \"PUT\",\n body: new Uint8Array(bytes),\n headers: { \"content-length\": String(bytes.byteLength) },\n });\n if (res.status === 409) return;\n if (!res.ok) {\n throw new RelayError(route, res.status, await readJson(res));\n }\n }\n\n /** GET bytes from a presigned URL. Throws on non-2xx. */\n async getObjectBytes(downloadUrl: string): Promise<Buffer> {\n const route = \"GET <downloadUrl>\";\n const res = await this.fetch(downloadUrl);\n if (!res.ok) {\n throw new RelayError(route, res.status, await readJson(res));\n }\n return Buffer.from(await res.arrayBuffer());\n }\n\n // ────────── changes ──────────\n\n async submitChanges(events: ReadonlyArray<SubmitChangeEvent>): Promise<SubmitChangesResponse> {\n const route = \"POST /v1/bindings/:id/changes\";\n const body: SubmitChangesRequest = { events };\n const res = await this.fetch(this.bindingPath(\"/changes\"), {\n method: \"POST\",\n headers: this.authHeaders({ \"content-type\": \"application/json\" }),\n body: JSON.stringify(body),\n });\n return expectJson<SubmitChangesResponse>(route, res);\n }\n\n async listChanges(opts?: {\n after?: string;\n limit?: number;\n path?: string;\n untilInclusive?: string;\n }): Promise<ChangesSinceResponse> {\n const route = \"GET /v1/bindings/:id/changes\";\n const params = new URLSearchParams();\n if (opts?.after) params.set(\"after\", opts.after);\n if (opts?.limit) params.set(\"limit\", String(opts.limit));\n if (opts?.path) params.set(\"path\", opts.path);\n if (opts?.untilInclusive) params.set(\"untilInclusive\", opts.untilInclusive);\n const qs = params.toString();\n const res = await this.fetch(\n this.bindingPath(\"/changes\") + (qs ? `?${qs}` : \"\"),\n { headers: this.authHeaders() },\n );\n return expectJson<ChangesSinceResponse>(route, res);\n }\n\n // ────────── intents (Context Layer) ──────────\n\n /** Open an intent. Actor (device/user) is taken from the token, not the body. */\n async createIntent(body: SubmitIntent): Promise<Intent> {\n const route = \"POST /v1/bindings/:id/intents\";\n const res = await this.fetch(this.bindingPath(\"/intents\"), {\n method: \"POST\",\n headers: this.authHeaders({ \"content-type\": \"application/json\" }),\n body: JSON.stringify(body),\n });\n return expectJson<Intent>(route, res);\n }\n\n /** Update / close an intent. Actor-only — the relay rejects a non-actor. */\n async updateIntent(intentId: string, body: IntentUpdate): Promise<Intent> {\n const route = \"PATCH /v1/bindings/:id/intents/:intentId\";\n const res = await this.fetch(\n this.bindingPath(`/intents/${encodeURIComponent(intentId)}`),\n {\n method: \"PATCH\",\n headers: this.authHeaders({ \"content-type\": \"application/json\" }),\n body: JSON.stringify(body),\n },\n );\n return expectJson<Intent>(route, res);\n }\n\n async listIntents(opts?: {\n status?: string;\n after?: string;\n limit?: number;\n }): Promise<ListIntentsResponse> {\n const route = \"GET /v1/bindings/:id/intents\";\n const params = new URLSearchParams();\n if (opts?.status) params.set(\"status\", opts.status);\n if (opts?.after) params.set(\"after\", opts.after);\n if (opts?.limit) params.set(\"limit\", String(opts.limit));\n const qs = params.toString();\n const res = await this.fetch(\n this.bindingPath(\"/intents\") + (qs ? `?${qs}` : \"\"),\n { headers: this.authHeaders() },\n );\n return expectJson<ListIntentsResponse>(route, res);\n }\n\n // ────────── invites ──────────\n\n /**\n * Mint a new invite (owner-only). Returns the secret + a ready-to-share\n * accept URL, exactly once. Caller must surface to the user immediately\n * and rely on `listInvites()` later if they want to refer back without\n * the secret.\n */\n async mintInvite(body: CreateInviteRequest): Promise<CreateInviteResponse> {\n const route = \"POST /v1/bindings/:id/invites\";\n const res = await this.fetch(this.bindingPath(\"/invites\"), {\n method: \"POST\",\n headers: this.authHeaders({ \"content-type\": \"application/json\" }),\n body: JSON.stringify(body),\n });\n return expectJson<CreateInviteResponse>(route, res);\n }\n\n async listInvites(): Promise<{ invites: BindingInviteWithAcceptances[] }> {\n const route = \"GET /v1/bindings/:id/invites\";\n const res = await this.fetch(this.bindingPath(\"/invites\"), {\n headers: this.authHeaders(),\n });\n return expectJson<{ invites: BindingInviteWithAcceptances[] }>(route, res);\n }\n\n async revokeInvite(\n inviteId: string,\n ): Promise<{ revoked: boolean; alreadyRevoked: boolean }> {\n const route = \"DELETE /v1/bindings/:id/invites/:inviteId\";\n const res = await this.fetch(\n this.bindingPath(`/invites/${encodeURIComponent(inviteId)}`),\n { method: \"DELETE\", headers: this.authHeaders() },\n );\n return expectJson<{ revoked: boolean; alreadyRevoked: boolean }>(route, res);\n }\n\n // ────────── members ──────────\n\n async listMembers(): Promise<{\n members: Array<{ userId: string; role: string; email: string | null; joinedAt: string }>;\n }> {\n const route = \"GET /v1/bindings/:id/members\";\n const res = await this.fetch(this.bindingPath(\"/members\"), {\n headers: this.authHeaders(),\n });\n return expectJson(route, res);\n }\n\n async removeMember(\n userId: string,\n ): Promise<{ removed: boolean; tokensRevoked: number }> {\n const route = \"DELETE /v1/bindings/:id/members/:userId\";\n const res = await this.fetch(\n this.bindingPath(`/members/${encodeURIComponent(userId)}`),\n { method: \"DELETE\", headers: this.authHeaders() },\n );\n return expectJson(route, res);\n }\n\n async setMemberRole(\n userId: string,\n role: \"owner\" | \"editor\" | \"viewer\",\n ): Promise<{ changed: boolean; role: string }> {\n const route = \"PATCH /v1/bindings/:id/members/:userId\";\n const res = await this.fetch(\n this.bindingPath(`/members/${encodeURIComponent(userId)}`),\n {\n method: \"PATCH\",\n headers: this.authHeaders({ \"content-type\": \"application/json\" }),\n body: JSON.stringify({ role }),\n },\n );\n return expectJson(route, res);\n }\n\n // ────────── manifest ──────────\n\n async getManifest(opts?: { after?: string; limit?: number }): Promise<ManifestSnapshotResponse> {\n const route = \"GET /v1/bindings/:id/manifest\";\n const params = new URLSearchParams();\n if (opts?.after) params.set(\"after\", opts.after);\n if (opts?.limit) params.set(\"limit\", String(opts.limit));\n const qs = params.toString();\n const res = await this.fetch(\n this.bindingPath(\"/manifest\") + (qs ? `?${qs}` : \"\"),\n { headers: this.authHeaders() },\n );\n return expectJson<ManifestSnapshotResponse>(route, res);\n }\n}\n\nfunction joinUrl(base: string, suffix: string): string {\n const b = base.replace(/\\/+$/, \"\");\n const s = suffix.startsWith(\"/\") ? suffix : `/${suffix}`;\n return b + s;\n}\n","/**\n * Local sync metadata — one file per binding, at\n * `<rig>/.rig/tap/state.local.db`. Machine-local; never synced.\n *\n * Tracks:\n * - per-path: last_applied_change_id, last_seen_hash, local_dirty,\n * last_scan_at\n * - cursor: the change_event id we've consumed up to (so a daemon\n * restart resumes from the right place)\n *\n * Storage is a plain JSON document, written atomically (tmp + rename) with\n * a debounced flush. No native module — earlier versions used SQLite\n * (better-sqlite3), whose native build broke installs across Node versions\n * (no prebuilt for newer Node → `npm i -g @rigxyz/tapd` failed). The state\n * is two small maps; SQL bought us nothing. An existing SQLite `.db` from an\n * older tapd fails to parse and is treated as empty — the daemon re-derives\n * it on the next scan (idempotent: objects dedupe, applies are hash-checked).\n */\n\nimport { mkdirSync, readFileSync, writeFileSync, renameSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\n\nconst CURSOR_PREFIX = \"chg_\";\n\n/** Compare two `chg_<n>` cursors numerically. Non-prefixed values are treated as -∞. */\nfunction cursorGreaterThan(a: string, b: string): boolean {\n const an = a.startsWith(CURSOR_PREFIX) ? safeBigInt(a.slice(CURSOR_PREFIX.length)) : null;\n const bn = b.startsWith(CURSOR_PREFIX) ? safeBigInt(b.slice(CURSOR_PREFIX.length)) : null;\n if (an === null) return false;\n if (bn === null) return true;\n return an > bn;\n}\n\nfunction safeBigInt(s: string): bigint | null {\n try { return BigInt(s); } catch { return null; }\n}\n\nexport type LocalPathState = {\n path: string;\n lastAppliedChangeId: string | null;\n lastSeenHash: string | null;\n localDirty: boolean;\n lastScanAt: string | null;\n};\n\ntype PathRecord = {\n lastAppliedChangeId: string | null;\n lastSeenHash: string | null;\n localDirty: boolean;\n lastScanAt: string | null;\n};\n\ntype StoreShape = {\n version: 1;\n meta: Record<string, string>;\n paths: Record<string, PathRecord>;\n};\n\nexport type StateDb = {\n /** Cursor we've applied up to, or \"chg_0\" if nothing yet. */\n getCursor(): string;\n setCursor(cursor: string): void;\n\n upsertPath(state: LocalPathState): void;\n getPath(path: string): LocalPathState | null;\n deletePath(path: string): void;\n /** Enumerate every tracked path. Cheap — state DBs are small. */\n listPaths(): string[];\n\n setMeta(key: string, value: string): void;\n getMeta(key: string): string | null;\n\n close(): void;\n};\n\n// One process-level exit hook flushes every open store, so one-shot commands\n// (init/status) that don't explicitly close() still persist. Using a single\n// shared hook (not one listener per instance) avoids MaxListeners warnings.\nconst pendingFlushers = new Set<() => void>();\nlet exitHookInstalled = false;\nfunction installExitHook() {\n if (exitHookInstalled) return;\n exitHookInstalled = true;\n process.once(\"exit\", () => {\n for (const flush of pendingFlushers) {\n try { flush(); } catch { /* best-effort on exit */ }\n }\n });\n}\n\nfunction loadStore(dbPath: string): StoreShape {\n try {\n const parsed = JSON.parse(readFileSync(dbPath, \"utf8\")) as Partial<StoreShape>;\n if (parsed && parsed.version === 1 && parsed.meta && parsed.paths) {\n return { version: 1, meta: parsed.meta, paths: parsed.paths };\n }\n } catch {\n // Missing file or a legacy SQLite binary — start fresh.\n }\n return { version: 1, meta: {}, paths: {} };\n}\n\nexport function openStateDb(dbPath: string): StateDb {\n mkdirSync(dirname(dbPath), { recursive: true });\n const store = loadStore(dbPath);\n\n let dirty = false;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const flush = () => {\n if (timer) { clearTimeout(timer); timer = null; }\n if (!dirty) return;\n const tmp = `${dbPath}.tmp`;\n writeFileSync(tmp, JSON.stringify(store), \"utf8\");\n renameSync(tmp, dbPath);\n dirty = false;\n };\n const schedule = () => {\n dirty = true;\n if (timer) return;\n timer = setTimeout(flush, 100);\n timer.unref?.();\n };\n\n pendingFlushers.add(flush);\n installExitHook();\n\n return {\n getCursor() {\n return store.meta.cursor ?? \"chg_0\";\n },\n setCursor(cursor: string) {\n // Monotonic write: refuse to move the cursor backwards. Belt and\n // suspenders for the SSE/poll race — even if two applies overlap and\n // try to commit different cursors, we never regress.\n const current = store.meta.cursor;\n if (current && !cursorGreaterThan(cursor, current)) return;\n store.meta.cursor = cursor;\n schedule();\n },\n upsertPath(state: LocalPathState) {\n store.paths[state.path] = {\n lastAppliedChangeId: state.lastAppliedChangeId,\n lastSeenHash: state.lastSeenHash,\n localDirty: state.localDirty,\n lastScanAt: state.lastScanAt,\n };\n schedule();\n },\n getPath(path: string): LocalPathState | null {\n const r = store.paths[path];\n if (!r) return null;\n return {\n path,\n lastAppliedChangeId: r.lastAppliedChangeId,\n lastSeenHash: r.lastSeenHash,\n localDirty: r.localDirty,\n lastScanAt: r.lastScanAt,\n };\n },\n deletePath(path: string) {\n if (path in store.paths) {\n delete store.paths[path];\n schedule();\n }\n },\n listPaths(): string[] {\n return Object.keys(store.paths);\n },\n setMeta(key: string, value: string) {\n store.meta[key] = value;\n schedule();\n },\n getMeta(key: string): string | null {\n return store.meta[key] ?? null;\n },\n close() {\n flush();\n pendingFlushers.delete(flush);\n },\n };\n}\n","/**\n * .tapignore loader.\n *\n * Layers:\n * 1. Built-in defaults — common secrets, dependencies, OS noise, and\n * bulky generated artifacts (per design doc § \"Large File Policy\").\n * Bulky media/archives are re-included under the top-level\n * `attachments/` folder (chat attachments must sync).\n * A user CAN override the non-secret ones (dependencies, media, …)\n * via `!pattern` in their .tapignore.\n * 2. Project .tapignore.\n * 3. Secrets + conflict sidecars (NON_OVERRIDABLE_IGNORE_LINES), added\n * again after the project file. The .tapignore itself syncs from\n * other members, so a `!.env` there would otherwise make every\n * member's daemon upload their own .env.\n * 4. Always-excluded paths — Tap's own local metadata, plus the paths\n * that never sync between members (core isNeverSyncPath: .git,\n * agent/MCP config). Matched case-insensitively, never re-enabled by\n * `!pattern`; the relay refuses them anyway (validateRigPath rejects\n * reserved paths, the change route refuses never-sync ones).\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport ignore, { type Ignore } from \"ignore\";\nimport { foldPath, isNeverSyncPath } from \"@tap/core\";\n\n// ORDER MATTERS: `ignore` is last-match-wins, so the `!attachments/**`\n// exception must follow the bulky-artifact lines it re-includes, and the\n// secrets + sidecar lines must follow the exception so it can never\n// re-include them (e.g. `attachments/clip.local.mp4`,\n// `attachments/clip.conflict-from.dev_x.chg_1.mp4`).\nexport const BUILTIN_IGNORE_LINES: ReadonlyArray<string> = [\n \"# Dependencies and build output\",\n \"node_modules/\",\n \"dist/\",\n // rig's default folder-registry target (`rig publish --registry`, default\n // ./local-registry) — publish artifacts, not workspace content to sync.\n \"local-registry/\",\n \".venv/\",\n \"__pycache__/\",\n \"*.pyc\",\n \"\",\n \"# Git and OS noise\",\n \".git/\",\n \".DS_Store\",\n \"\",\n \"# Bulky generated artifacts\",\n \"*.mov\",\n \"*.mp4\",\n \"*.zip\",\n \"*.tar.gz\",\n \"*.sqlite\",\n \"\",\n \"# ...except chat attachments: media/archives in the top-level attachments/\",\n \"# folder DO sync (anchored: a nested sub/attachments/ stays excluded)\",\n \"!attachments/**/*.mov\",\n \"!attachments/**/*.mp4\",\n \"!attachments/**/*.zip\",\n \"!attachments/**/*.tar.gz\",\n \"\",\n \"# Secrets and machine-local\",\n \".env\",\n \".env.*\",\n \"*.local.*\",\n \"\",\n \"# Tap conflict sidecars — local-only by default\",\n \"*.conflict-from.*\",\n];\n\n// Re-applied after the project .tapignore so its `!pattern`s can't\n// re-include them. Secrets for the obvious reason; conflict sidecars\n// because the daemon treats them as local state (scanAndPush reads them\n// for divergedFrom) — an uploaded one would land as a stray copy on\n// every peer. Must stay a subset of the builtin lines above.\nexport const NON_OVERRIDABLE_IGNORE_LINES: ReadonlyArray<string> = [\n \".env\",\n \".env.*\",\n \"*.local.*\",\n \"*.conflict-from.*\",\n];\n\n// `.rig/` as a whole is local-metadata namespace: tap state DB +\n// binding config (this repo) plus rig's per-machine provenance\n// (`.rig/instance.toml`, `.rig/setup.json` — see ~/Code/rig). Nothing\n// in here is portable across machines, so the walker never sees it.\n// Tightening from the original `.rig/tap/`-only exclusion also fixes\n// a latent bug: the walker would push `.rig` to emptyDirs (its only\n// children being always-excluded) and scanAndPush would submit a\n// phantom `mkdir .rig` on every fresh daemon's first scan.\nconst ALWAYS_EXCLUDED_PREFIXES: ReadonlyArray<string> = [\".rig/\"];\n\nexport function loadIgnore(rootDir: string): Ignore {\n const ig = ignore();\n ig.add(BUILTIN_IGNORE_LINES.join(\"\\n\"));\n const projectIgnore = join(rootDir, \".tapignore\");\n if (existsSync(projectIgnore)) {\n ig.add(readFileSync(projectIgnore, \"utf8\"));\n }\n // Last-match-wins: these come after the project file, so they win.\n ig.add(NON_OVERRIDABLE_IGNORE_LINES.join(\"\\n\"));\n return ig;\n}\n\n/**\n * Hard-exclude check that runs in addition to .tapignore. Tap's own\n * local metadata and the never-sync paths (.git, agent/MCP config) are\n * invisible to sync no matter what the user puts in their .tapignore —\n * including `!pattern` overrides. Case-insensitive: on APFS `.RIG/` is\n * `.rig/`.\n */\nexport function isAlwaysExcluded(posixPath: string): boolean {\n const folded = foldPath(posixPath);\n for (const prefix of ALWAYS_EXCLUDED_PREFIXES) {\n const exact = prefix.endsWith(\"/\") ? prefix.slice(0, -1) : prefix;\n if (folded === exact) return true;\n if (folded.startsWith(prefix)) return true;\n }\n return isNeverSyncPath(posixPath);\n}\n\n/**\n * True if `posixPath` is at or under any of `prefixes`. Used to\n * hard-exclude import mount points from the primary binding's walker\n * and watcher: a mirrored subtree must NEVER be re-scanned and uploaded\n * into the primary's own binding (the \"accidental re-share\").\n *\n * Deliberately a non-overridable predicate alongside `isAlwaysExcluded`,\n * NOT a `.tapignore` pattern — built-in ignore lines can be re-armed\n * with `!pattern`, which would defeat the guard.\n */\nexport function isUnderAny(\n posixPath: string,\n prefixes: ReadonlyArray<string>,\n): boolean {\n for (const raw of prefixes) {\n const prefix = raw.replace(/\\/+$/, \"\");\n if (prefix === \"\") continue;\n if (posixPath === prefix) return true;\n if (posixPath.startsWith(prefix + \"/\")) return true;\n }\n return false;\n}\n","/**\n * Recursive directory walker for a rig. Yields:\n * - `files`: candidates for upload (size + posix path relative to root)\n * - `emptyDirs`: paths to mkdir markers (dirs with no tracked children)\n * - `warnings`: paths that won't sync (oversized, symlink, invalid path)\n *\n * Rules enforced:\n * - `.tapignore` (loadIgnore)\n * - Tap's own reserved metadata (isAlwaysExcluded)\n * - MAX_FILE_BYTES (100 MB) — daemon refuses upload above this\n * - Symlinks (lstat-detected) — rejected; warning emitted\n * - validateRigPath — defends against control chars / weird segments\n * before the relay sees them\n *\n * Path strings are POSIX (forward slashes) even on Windows so they\n * match what the relay stores and what the manifest returns.\n */\n\nimport { readdirSync, statSync, type Dirent } from \"node:fs\";\nimport { join, sep } from \"node:path\";\nimport type { Ignore } from \"ignore\";\nimport { MAX_FILE_BYTES, validateRigPath } from \"@tap/core\";\nimport { isAlwaysExcluded, isUnderAny } from \"./tapignore.js\";\n\nexport type WalkFile = { path: string; size: number };\nexport type WalkWarning = {\n path: string;\n reason: \"too_large\" | \"symlink\" | \"invalid_path\" | \"unreadable\";\n};\n\nexport type WalkResult = {\n files: WalkFile[];\n emptyDirs: string[];\n /**\n * Every directory visited during the walk, including non-empty ones.\n * Used by `scanAndPush` to detect \"previously-tracked dir no longer\n * on disk\" cases that emptyDirs alone wouldn't catch — e.g. a\n * directory that was empty-and-tracked, then gained files (now\n * non-empty and on disk: should NOT be deleted) vs one that was\n * `rm -rf`d (gone from disk: should be rmdir'd).\n */\n directoriesScanned: string[];\n warnings: WalkWarning[];\n};\n\nexport function walk(\n rootDir: string,\n opts: {\n ignore: Ignore;\n /**\n * Mount points (posix, relative to rootDir) to hard-exclude — import\n * subdirectories materialized by mirror subscriptions. Never scanned,\n * never uploaded into this binding. Non-overridable by .tapignore.\n */\n excludePaths?: ReadonlyArray<string>;\n },\n): WalkResult {\n const files: WalkFile[] = [];\n const emptyDirs: string[] = [];\n const directoriesScanned: string[] = [];\n const warnings: WalkWarning[] = [];\n const excludePaths = opts.excludePaths ?? [];\n\n walkDir(\".\");\n\n return { files, emptyDirs, directoriesScanned, warnings };\n\n /** Returns true if this directory contained any tracked child (file or dir). */\n function walkDir(rel: string): boolean {\n const abs = join(rootDir, rel);\n let entries: Dirent[];\n try {\n entries = readdirSync(abs, { withFileTypes: true });\n } catch {\n warnings.push({ path: posixOf(rel), reason: \"unreadable\" });\n return false;\n }\n let hasVisible = false;\n for (const ent of entries) {\n const childRel = rel === \".\" ? ent.name : join(rel, ent.name);\n const posix = posixOf(childRel);\n const isDir = ent.isDirectory();\n // ignore.ignores() expects a trailing slash for directories so\n // patterns like `node_modules/` match.\n if (isAlwaysExcluded(posix)) continue;\n if (excludePaths.length > 0 && isUnderAny(posix, excludePaths)) {\n // This child is an import mount (or under one). It's never\n // tracked/uploaded — but its presence means the parent dir is NOT\n // empty, so mark visible to avoid emitting a phantom `mkdir` for\n // a directory that exists only to hold the mount.\n hasVisible = true;\n continue;\n }\n if (opts.ignore.ignores(posix + (isDir ? \"/\" : \"\"))) continue;\n\n if (ent.isSymbolicLink()) {\n warnings.push({ path: posix, reason: \"symlink\" });\n continue;\n }\n const pathOk = validateRigPath(posix);\n if (!pathOk.ok) {\n warnings.push({ path: posix, reason: \"invalid_path\" });\n continue;\n }\n if (isDir) {\n directoriesScanned.push(posix);\n const subHas = walkDir(childRel);\n if (!subHas) emptyDirs.push(posix);\n hasVisible = true;\n continue;\n }\n if (ent.isFile()) {\n let size: number;\n try {\n size = statSync(join(abs, ent.name)).size;\n } catch {\n warnings.push({ path: posix, reason: \"unreadable\" });\n continue;\n }\n if (size > MAX_FILE_BYTES) {\n warnings.push({ path: posix, reason: \"too_large\" });\n continue;\n }\n files.push({ path: posix, size });\n hasVisible = true;\n }\n }\n return hasVisible;\n }\n}\n\nfunction posixOf(s: string): string {\n return sep === \"/\" ? s : s.replaceAll(sep, \"/\");\n}\n","/**\n * Streaming sha256 of a file path, returned in the relay's wire format\n * (\"sha256:\" + 64 hex). Used by walker → uploader and by watcher → uploader.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { createReadStream } from \"node:fs\";\n\nexport async function hashFile(absPath: string): Promise<string> {\n const hash = createHash(\"sha256\");\n await new Promise<void>((resolve, reject) => {\n const stream = createReadStream(absPath);\n stream.on(\"data\", (chunk) => hash.update(chunk));\n stream.on(\"end\", () => resolve());\n stream.on(\"error\", (err) => reject(err));\n });\n return `sha256:${hash.digest(\"hex\")}`;\n}\n","/**\n * `tapd init` — bootstrap a fresh binding from the current working\n * directory.\n *\n * Steps (mapped to design doc § \"Init Flow\"):\n * 1. Refuse if .rig/tap-binding.local.json already exists.\n * 2. Walk the rig, applying .tapignore + reserved-path rules.\n * 3. Hash every tracked file (de-duplicated by hash so identical\n * content uploads once).\n * 4. For each unique hash: prepare-upload → PUT bytes → complete-upload.\n * 5. Submit one batched POST /changes with one event per file +\n * one mkdir per empty-dir marker.\n * 6. Write .rig/tap-binding.local.json (mode 0600).\n * 7. Populate .rig/tap/state.local.db with last_applied_change_id\n * per path, and the resulting cursor.\n *\n * `deferUpload` stops after step 3a (plus an empty state DB): the binding\n * exists and the config is on disk, and the daemon's first scan uploads\n * everything (`tapd start` pushes whatever the relay doesn't know yet).\n *\n * Returns a summary with the binding, the owner token (so the caller\n * CLI can print it once), upload + change counts, and any walker\n * warnings (oversized files / symlinks / invalid paths). Caller is\n * responsible for surfacing the warnings to the user.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { CreateBindingResponse, SubmitChangeEvent } from \"@tap/core\";\n\nfunction sha256OfBuffer(bytes: Buffer | Uint8Array): string {\n return `sha256:${createHash(\"sha256\").update(bytes).digest(\"hex\")}`;\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null;\n}\nimport { createBinding, RelayClient, RelayError } from \"./relay-client.js\";\nimport { openStateDb, type LocalPathState } from \"./state-db.js\";\nimport { loadIgnore } from \"./tapignore.js\";\nimport { walk, type WalkResult } from \"./walker.js\";\nimport { hashFile } from \"./hash.js\";\nimport {\n BINDING_CONFIG_PATH,\n readBindingConfig,\n writeBindingConfig,\n} from \"./binding-config.js\";\nimport { mapPool } from \"./pool.js\";\n\n// Max object uploads in flight during the initial scan (see uploader.ts).\nconst UPLOAD_CONCURRENCY = 8;\n\nexport type InitOptions = {\n rootDir: string;\n relayUrl: string;\n /** User access token (from credentials). */\n accessToken: string;\n bindingName: string;\n deviceLabel?: string;\n /** Org slug to scope the new binding to. Caller must already be a member. */\n org?: string;\n /** Create the binding as this kind (the relay defaults to `'rig'`). */\n kind?: \"rig\" | \"space\";\n /**\n * Create the binding and write the local config, but leave the initial\n * upload to the daemon's first scan (`tapd start` pushes every file the\n * relay doesn't know yet). For a brand-new folder that should be usable\n * the moment its binding exists — a new space in the desktop app.\n */\n deferUpload?: boolean;\n /** Test seam: override fetch (used to drive the in-process relay). */\n fetch?: typeof fetch;\n};\n\nexport type InitResult = {\n binding: CreateBindingResponse[\"binding\"];\n device: CreateBindingResponse[\"device\"];\n /** Owner secret. Returned exactly once. The caller should display it. */\n ownerSecret: string;\n uploadedHashes: number;\n reusedHashes: number;\n submittedEvents: number;\n warnings: WalkResult[\"warnings\"];\n /** Cursor written to state.local.db after the initial submit. */\n cursor: string;\n /** True when the upload was left to the daemon's first scan (`deferUpload`). */\n deferredUpload: boolean;\n};\n\nexport class AlreadyInitializedError extends Error {\n constructor(public readonly path: string) {\n super(`already initialized — ${path} exists`);\n this.name = \"AlreadyInitializedError\";\n }\n}\n\nexport class OrgForbiddenError extends Error {\n constructor(public readonly org: string) {\n super(\n `not a member of org \"${org}\" — ask an org admin for an invite, or check the slug`,\n );\n this.name = \"OrgForbiddenError\";\n }\n}\n\nexport async function init(opts: InitOptions): Promise<InitResult> {\n if (readBindingConfig(opts.rootDir)) {\n throw new AlreadyInitializedError(join(opts.rootDir, BINDING_CONFIG_PATH));\n }\n\n // 1. Walk the rig before touching the relay, so we fail fast on a\n // structurally invalid project (e.g. wholly unreadable root).\n const ig = loadIgnore(opts.rootDir);\n const scan = walk(opts.rootDir, { ignore: ig });\n\n // 2. Hash everything. Stream sha256 so a multi-GB rig stays bounded.\n // Deferred: nothing is uploaded here, so nothing needs hashing yet.\n type HashedFile = { path: string; size: number; hash: string };\n const hashed: HashedFile[] = [];\n for (const f of opts.deferUpload ? [] : scan.files) {\n const h = await hashFile(join(opts.rootDir, f.path));\n hashed.push({ path: f.path, size: f.size, hash: h });\n }\n\n // 3. Create the binding (the response carries the owner token once).\n // User identity comes from the Authorization: Bearer header\n // (relay-client adds it from `accessToken`).\n let created: CreateBindingResponse;\n try {\n created = await createBinding({\n baseUrl: opts.relayUrl,\n request: {\n name: opts.bindingName,\n deviceLabel: opts.deviceLabel,\n org: opts.org,\n ...(opts.kind ? { kind: opts.kind } : {}),\n },\n accessToken: opts.accessToken,\n fetch: opts.fetch,\n });\n } catch (err) {\n if (err instanceof RelayError && isRecord(err.body) && err.body.error === \"org_forbidden\" && opts.org) {\n throw new OrgForbiddenError(opts.org);\n }\n throw err;\n }\n\n // 3a. Persist the binding config IMMEDIATELY. The owner secret is\n // returned exactly once by the relay; if upload or submit fails\n // downstream, the user can resume via `tapd start` (which scans\n // and pushes anything not yet known to the relay). Without this\n // write, a mid-init failure orphans the binding — token unknown,\n // no way to access from this machine ever again.\n writeBindingConfig(opts.rootDir, {\n bindingId: created.binding.id,\n relayUrl: opts.relayUrl,\n deviceId: created.device.id,\n token: created.token.secret,\n });\n\n const client = new RelayClient({\n baseUrl: opts.relayUrl,\n bindingId: created.binding.id,\n token: created.token.secret,\n fetch: opts.fetch,\n });\n\n // 4. Upload each unique hash exactly once. Identical content across\n // paths (e.g. duplicated assets) reuses the same object.\n const uniqueHashes = new Map<string, HashedFile>();\n for (const f of hashed) {\n if (!uniqueHashes.has(f.hash)) uniqueHashes.set(f.hash, f);\n }\n const mutatedDuringScan = new Set<string>();\n let uploaded = 0;\n let reused = 0;\n // Bounded-concurrency upload — serial round-trips made a many-file initial\n // scan take minutes against a remote relay.\n await mapPool([...uniqueHashes.values()], UPLOAD_CONCURRENCY, async (f) => {\n const prep = await client.prepareUpload({ hash: f.hash, size: f.size });\n if (prep.exists) {\n reused += 1;\n return;\n }\n const bytes = readFileSync(join(opts.rootDir, f.path));\n // Re-verify that the buffer we're PUTting matches the hash we\n // promised the relay. If the file was modified between the\n // streaming hash above and this read, skip the upload and warn —\n // the user can re-run `tapd start` to pick it up.\n const actual = sha256OfBuffer(bytes);\n if (actual !== f.hash) {\n mutatedDuringScan.add(f.path);\n scan.warnings.push({\n path: f.path,\n reason: \"invalid_path\", // closest existing warning kind; semantics are file-mutated-mid-scan\n });\n return;\n }\n await client.putObjectBytes(prep.uploadUrl, bytes);\n await client.completeUpload({ hash: f.hash, size: f.size });\n uploaded += 1;\n });\n\n // 5. Build one batch: writes first, then mkdir markers. Skip writes\n // for any file whose hash failed to upload (mutated mid-scan).\n const events: SubmitChangeEvent[] = [\n ...hashed\n .filter((f) => !mutatedDuringScan.has(f.path))\n .map((f) => ({\n op: \"write\" as const,\n path: f.path,\n hash: f.hash,\n size: f.size,\n })),\n ...(opts.deferUpload ? [] : scan.emptyDirs.map((path) => ({ op: \"mkdir\" as const, path }))),\n ];\n\n let cursor = \"chg_0\";\n let submittedEvents: ReadonlyArray<import(\"@tap/core\").ChangeEvent> = [];\n if (events.length > 0) {\n const submit = await client.submitChanges(events);\n submittedEvents = submit.events;\n cursor = submit.events[submit.events.length - 1]!.id;\n }\n\n // 6. Populate state.local.db. The binding config was written above\n // (step 3a) so the token isn't lost on a mid-init failure.\n const stateDb = openStateDb(\n join(opts.rootDir, \".rig\", \"tap\", \"state.local.db\"),\n );\n try {\n const now = new Date().toISOString();\n for (const ev of submittedEvents) {\n const state: LocalPathState = {\n path: ev.path,\n lastAppliedChangeId: ev.id,\n lastSeenHash: ev.hash, // null for mkdirs\n localDirty: false,\n lastScanAt: now,\n };\n stateDb.upsertPath(state);\n }\n stateDb.setCursor(cursor);\n stateDb.setMeta(\"device_id\", created.device.id);\n stateDb.setMeta(\"binding_id\", created.binding.id);\n } finally {\n stateDb.close();\n }\n\n return {\n binding: created.binding,\n device: created.device,\n ownerSecret: created.token.secret,\n uploadedHashes: uploaded,\n reusedHashes: reused,\n submittedEvents: submittedEvents.length,\n warnings: scan.warnings,\n cursor,\n deferredUpload: opts.deferUpload === true,\n };\n}\n","/**\n * Bounded-concurrency map. Runs `fn` over `items` with at most `limit`\n * promises in flight. Used to parallelize object uploads: a many-file\n * initial sync was O(files) serial round-trips (prepareUpload → PUT →\n * completeUpload, one at a time), which is minutes for a few hundred files\n * against a remote relay. Workers pull the next index until the list drains.\n *\n * First rejection propagates (in-flight tasks settle but no new ones start).\n * No ordering guarantees.\n */\nexport async function mapPool<T>(\n items: readonly T[],\n limit: number,\n fn: (item: T) => Promise<void>,\n): Promise<void> {\n let next = 0;\n let failed = false;\n const worker = async () => {\n while (!failed) {\n const idx = next++;\n if (idx >= items.length) break;\n const item = items[idx]!;\n try {\n await fn(item);\n } catch (err) {\n failed = true;\n throw err;\n }\n }\n };\n const n = Math.max(1, Math.min(limit, items.length));\n await Promise.all(Array.from({ length: n }, worker));\n}\n","/**\n * `tapd invite` — owner-side helpers for the invite lifecycle.\n *\n * Reads .rig/tap-binding.local.json for the relay URL + token, builds\n * a RelayClient, and calls the matching endpoint. Designed to be a\n * thin wrapper that the CLI dispatcher (bin.ts) can call without\n * thinking about plumbing.\n */\n\nimport type {\n BindingInviteWithAcceptances,\n CapabilityOp,\n CreateInviteRequest,\n CreateInviteResponse,\n Role,\n} from \"@tap/core\";\nimport { NotInitializedError, readBindingConfig } from \"./binding-config.js\";\nimport { RelayClient } from \"./relay-client.js\";\n\n// Re-exported so existing `import { NotInitializedError } from \"./invite.js\"`\n// imports + tests don't need to change. Class lives in binding-config.\nexport { NotInitializedError };\n\nfunction clientFor(rootDir: string, fetchImpl?: typeof fetch): RelayClient {\n const cfg = readBindingConfig(rootDir);\n if (!cfg) throw new NotInitializedError(rootDir);\n return new RelayClient({\n baseUrl: cfg.relayUrl,\n bindingId: cfg.bindingId,\n token: cfg.token,\n fetch: fetchImpl,\n });\n}\n\nexport type MintInviteOptions = {\n rootDir: string;\n ops: ReadonlyArray<CapabilityOp>;\n role?: Role | null;\n pathGlobs?: ReadonlyArray<string>;\n ttlSeconds?: number;\n maxUses?: number;\n emailConstraint?: string;\n label?: string;\n fetch?: typeof fetch;\n};\n\nexport async function mint(opts: MintInviteOptions): Promise<CreateInviteResponse> {\n const client = clientFor(opts.rootDir, opts.fetch);\n const body: CreateInviteRequest = {\n ops: opts.ops,\n ...(opts.role !== undefined ? { role: opts.role } : {}),\n ...(opts.pathGlobs ? { pathGlobs: opts.pathGlobs } : {}),\n ...(opts.ttlSeconds !== undefined ? { ttlSeconds: opts.ttlSeconds } : {}),\n ...(opts.maxUses !== undefined ? { maxUses: opts.maxUses } : {}),\n ...(opts.emailConstraint ? { emailConstraint: opts.emailConstraint } : {}),\n ...(opts.label ? { label: opts.label } : {}),\n };\n return client.mintInvite(body);\n}\n\nexport async function list(\n opts: { rootDir: string; fetch?: typeof fetch },\n): Promise<BindingInviteWithAcceptances[]> {\n const client = clientFor(opts.rootDir, opts.fetch);\n const res = await client.listInvites();\n return res.invites;\n}\n\nexport async function revoke(opts: {\n rootDir: string;\n inviteId: string;\n fetch?: typeof fetch;\n}): Promise<{ revoked: boolean; alreadyRevoked: boolean }> {\n const client = clientFor(opts.rootDir, opts.fetch);\n return client.revokeInvite(opts.inviteId);\n}\n","/**\n * `tapd join` — accept an invite URL and set up a fresh local binding.\n *\n * Unlike `tapd init`, join does NOT touch the rig contents:\n * 1. Refuse if .rig/tap-binding.local.json already exists.\n * 2. Parse the invite URL → (baseUrl, secret).\n * 3. POST /v1/invites/:secret/accept with the user's identity header,\n * receive {bindingId, device, token}.\n * 4. Write .rig/tap-binding.local.json (mode 0600) IMMEDIATELY — same\n * \"secret-returned-once survives downstream failure\" discipline as\n * `tapd init`.\n * 5. Open .rig/tap/state.local.db with cursor `chg_0`.\n *\n * After join, `tapd start` will:\n * - applyOnce: pull the remote manifest + change log\n * - scanAndPush: push any pre-existing local divergence\n *\n * That covers both the bootstrap-from-empty-dir case and the\n * \"user already has some files locally\" merge case.\n */\n\nimport { join as joinPath } from \"node:path\";\nimport type { AcceptInviteResponse, BindingDevice } from \"@tap/core\";\nimport { acceptInvite } from \"./relay-client.js\";\nimport {\n BINDING_CONFIG_PATH,\n readBindingConfig,\n writeBindingConfig,\n} from \"./binding-config.js\";\nimport { openStateDb } from \"./state-db.js\";\n\nexport type JoinOptions = {\n rootDir: string;\n inviteUrl: string;\n /**\n * User access token from credentials. Required for member invites\n * (role !== null), optional for pure-capability invites.\n */\n accessToken?: string | null;\n deviceLabel?: string;\n /** Test seam: override fetch. */\n fetch?: typeof fetch;\n};\n\nexport type JoinResult = {\n bindingId: string;\n device: BindingDevice;\n /** Capability secret. Returned exactly once. */\n tokenSecret: string;\n /** Whether the invite granted a member role (vs pure-capability). */\n becameMember: boolean;\n};\n\nexport class AlreadyJoinedError extends Error {\n constructor(public readonly path: string) {\n super(`already initialized — ${path} exists`);\n this.name = \"AlreadyJoinedError\";\n }\n}\n\nexport class InvalidInviteUrlError extends Error {\n constructor(public readonly url: string) {\n super(`invalid invite URL: ${url} (expected <baseUrl>/v1/invites/<secret>/accept)`);\n this.name = \"InvalidInviteUrlError\";\n }\n}\n\n/**\n * Parse `https://relay.example/v1/invites/<secret>/accept` into\n * (baseUrl, secret). Trailing slash + missing /accept both accepted.\n */\nexport function parseInviteUrl(url: string): { baseUrl: string; secret: string } {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new InvalidInviteUrlError(url);\n }\n const match = /^\\/v1\\/invites\\/([^/]+)(?:\\/accept)?\\/?$/.exec(parsed.pathname);\n if (!match) throw new InvalidInviteUrlError(url);\n return {\n baseUrl: `${parsed.protocol}//${parsed.host}`,\n secret: decodeURIComponent(match[1]!),\n };\n}\n\nexport async function join(opts: JoinOptions): Promise<JoinResult> {\n if (readBindingConfig(opts.rootDir)) {\n throw new AlreadyJoinedError(joinPath(opts.rootDir, BINDING_CONFIG_PATH));\n }\n const { baseUrl, secret } = parseInviteUrl(opts.inviteUrl);\n\n const accepted: AcceptInviteResponse = await acceptInvite({\n baseUrl,\n secret,\n accessToken: opts.accessToken ?? null,\n request: opts.deviceLabel ? { deviceLabel: opts.deviceLabel } : undefined,\n fetch: opts.fetch,\n });\n\n // Persist config FIRST. The token is returned exactly once; if the\n // state-DB open ever fails (unlikely but possible — disk full,\n // permissions), the user can still re-attempt via `tapd start`.\n writeBindingConfig(opts.rootDir, {\n bindingId: accepted.bindingId,\n relayUrl: baseUrl,\n deviceId: accepted.device.id,\n token: accepted.token.secret,\n });\n\n const stateDb = openStateDb(\n joinPath(opts.rootDir, \".rig\", \"tap\", \"state.local.db\"),\n );\n try {\n stateDb.setMeta(\"binding_id\", accepted.bindingId);\n stateDb.setMeta(\"device_id\", accepted.device.id);\n // cursor stays at default \"chg_0\" — start's initial applyOnce\n // pulls everything from the manifest.\n } finally {\n stateDb.close();\n }\n\n return {\n bindingId: accepted.bindingId,\n device: accepted.device,\n tokenSecret: accepted.token.secret,\n becameMember: accepted.member !== null,\n };\n}\n","/**\n * Rig imports — read-only live mirrors of another binding into a\n * subdirectory (see ~/Code/rig/docs/rig-imports.md).\n *\n * This module owns the *declaration* side: the ImportSpec shape, on-disk\n * path helpers, and the security-critical validator. The daemon wiring\n * (mirror subscriptions, orchestration) lives in start.ts; the\n * apply-time behavior (remote-wins) lives in applier.ts (mode: \"mirror\").\n *\n * The validator is the single highest-risk part of the feature: a bad\n * mount point is how an import could silently re-share — or, because\n * mirror mode is remote-authoritative, destroy — primary content. Treat\n * every rule here as security code, not input hygiene.\n */\n\nimport {\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n realpathSync,\n statSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, isAbsolute, join, relative } from \"node:path\";\n\nexport type ImportSpec = {\n /** Boring slug — becomes the token filename + state-db dir name. */\n name: string;\n bindingId: string;\n relayUrl: string;\n /** Per-import capability token (read + subscribe). Machine-local. */\n token: string;\n /** Mount point: subdirectory relative to the rig root (posix). */\n path: string;\n};\n\n/**\n * On-disk shape of a per-import mount file\n * (`.rig/tap/imports/<name>.local.json`). Machine-local, gitignored,\n * never synced — mirrors `tap-binding.local.json`. Carries the capability\n * secret plus enough to start a mirror subscription without parsing\n * `rig.toml` (tapd has no TOML parser; the daemon reads JSON like it does\n * for the primary binding). `ops` is persisted from the accept-invite\n * response so the daemon can refuse a token that lacks read+subscribe.\n */\nexport type ImportMountFile = {\n bindingId: string;\n relayUrl: string;\n token: string;\n ops: ReadonlyArray<\"read\" | \"write\" | \"subscribe\">;\n /** Mount point: subdirectory relative to the rig root (posix). */\n path: string;\n /** Import-edge id from the relay (inter-rig graph). Lets `rig import remove`\n * tombstone the exact edge. Absent for anonymous imports / pre-edge mounts. */\n edgeId?: string;\n};\n\nexport type ImportValidationError = { name: string; path: string; reason: string };\n\n/**\n * Slug rule for `import.name`. Dots are banned outright, which by\n * construction rules out every \"confusing suffix\" game (a name can never\n * end `.local.json` or collide with the `<name>.local.json` token file /\n * `<name>/state.local.db` dir). No separators, no `..`, no leading dot.\n */\nconst NAME_RE = /^[a-z0-9][a-z0-9_-]*$/;\n\nexport function validateImportName(name: string): string | null {\n if (!name) return \"empty_name\";\n if (!NAME_RE.test(name)) {\n return \"invalid_name (must match ^[a-z0-9][a-z0-9_-]*$ — lowercase slug, no dots/slashes/..)\";\n }\n return null;\n}\n\n/**\n * Canonicalize a declared import path to a posix form with `.` and empty\n * segments collapsed (`./sources/alex` and `sources/./alex` → `sources/alex`).\n * `..` is deliberately preserved so validateImportPath can reject it. This\n * canonical form is what the daemon uses for BOTH the mirror root and the\n * primary writer's exclude set — they must be byte-identical or a mirror\n * could materialize at `sources/alex` while the writer excludes\n * `./sources/alex` (which never matches walker paths) and re-uploads it.\n */\nexport function normalizeImportPath(p: string): string {\n return p\n .replace(/\\\\/g, \"/\")\n .split(\"/\")\n .filter((s) => s !== \"\" && s !== \".\")\n .join(\"/\");\n}\n\n/**\n * Static + symlink validation of a single mount point. Returns a reason\n * string on rejection, or null if the path is a legal mount.\n *\n * The mount may not exist on disk yet (first materialization hasn't\n * run), so we can't realpath the full path. Instead we realpath the\n * nearest existing ancestor and re-attach the not-yet-created remainder,\n * then assert the result stays inside the (realpathed) rig root. This\n * catches a symlinked ancestor that escapes the root even before the\n * mount dir exists.\n */\nexport function validateImportPath(rootDir: string, importPath: string): string | null {\n if (!importPath) return \"empty_path\";\n if (isAbsolute(importPath)) return \"absolute_path\";\n const norm = normalizeImportPath(importPath);\n if (norm === \"\" || norm === \".\") return \"root_path (cannot mount over the rig root)\";\n\n const segs = norm.split(\"/\");\n if (segs.some((s) => s === \"..\")) return \"dotdot_in_path\";\n if (segs.some((s) => s === \"\")) return \"empty_segment\";\n if (segs[0] === \".rig\") return \"reserved_rig_path\";\n\n // Realpath the root for an apples-to-apples containment check — on\n // macOS tmpdir() itself is a symlink (/var → /private/var).\n const realRoot = realpathSync(rootDir);\n const abs = join(realRoot, norm);\n\n // Lexical escape (belt to the symlink suspenders below).\n if (!isWithin(realRoot, abs)) return \"escapes_root\";\n\n // Symlink escape: resolve the nearest existing ancestor.\n const realMount = realpathNearestExisting(abs);\n if (!isWithin(realRoot, realMount)) return \"symlink_escape\";\n\n // Ancestor-is-file: a mirror must be able to mkdir the mount path. If\n // any existing ancestor (or the mount itself) is a FILE, materialization\n // would hit ENOTDIR; applyOnce then advances past the failed event and\n // silently drops source content. Catches \"primary tracks `sources` as a\n // file, import mounts at `sources/alex`\" (a parent collision that the\n // tracked-path string check in validateImports can't see). The primary's\n // content is materialized before imports validate, so a file ancestor is\n // on disk by the time we check.\n const fileAncestor = firstExistingFileOnPath(abs);\n if (fileAncestor) {\n return fileAncestor === abs ? \"mount_path_is_file\" : \"ancestor_is_file\";\n }\n\n return null;\n}\n\n/**\n * Validate a batch of imports against each other and against the\n * primary's already-tracked content. Never throws — returns the imports\n * that passed plus a structured error per rejection, so the orchestrator\n * can degrade a bad mount without taking down the daemon.\n */\nexport function validateImports(opts: {\n rootDir: string;\n imports: ReadonlyArray<ImportSpec>;\n /** Paths the primary binding already tracks (state-db listPaths). */\n primaryTrackedPaths: ReadonlyArray<string>;\n}): { valid: ImportSpec[]; errors: ImportValidationError[] } {\n const valid: ImportSpec[] = [];\n const errors: ImportValidationError[] = [];\n const seenNames = new Set<string>();\n const acceptedPaths: string[] = [];\n\n for (const imp of opts.imports) {\n const reject = (reason: string) =>\n errors.push({ name: imp.name, path: imp.path, reason });\n\n const nameErr = validateImportName(imp.name);\n if (nameErr) {\n reject(nameErr);\n continue;\n }\n if (seenNames.has(imp.name)) {\n reject(\"duplicate_name\");\n continue;\n }\n const pathErr = validateImportPath(opts.rootDir, imp.path);\n if (pathErr) {\n reject(pathErr);\n continue;\n }\n const norm = normalizeImportPath(imp.path);\n\n // No two imports may overlap (one a prefix of the other).\n if (acceptedPaths.some((p) => pathsOverlap(p, norm))) {\n reject(\"overlaps_other_import\");\n continue;\n }\n\n // Must not mount over content the primary already tracks — mirror\n // mode would delete/overwrite the user's real work. A deliberate\n // migration flow could opt in later; default is refuse.\n if (opts.primaryTrackedPaths.some((tp) => tp === norm || tp.startsWith(norm + \"/\"))) {\n reject(\"over_primary_content\");\n continue;\n }\n\n seenNames.add(imp.name);\n acceptedPaths.push(norm);\n // Return the CANONICAL path, not imp.path — start() uses this for both\n // the mirror root and the writer's exclude set, which must match.\n valid.push({ ...imp, path: norm });\n }\n\n return { valid, errors };\n}\n\n// ────────── on-disk path helpers ──────────\n\n/** `.rig/tap/state.local.db` — the primary binding's cursor/state. */\nexport function primaryStateDbPath(rootDir: string): string {\n return join(rootDir, \".rig\", \"tap\", \"state.local.db\");\n}\n\n/** `.rig/tap/imports/<name>/state.local.db` — per-import cursor/state. */\nexport function importStateDbPath(rootDir: string, name: string): string {\n return join(rootDir, \".rig\", \"tap\", \"imports\", name, \"state.local.db\");\n}\n\n/** `.rig/tap/imports` — holds `<name>.local.json` token files + `<name>/` state dirs. */\nexport function importsDir(rootDir: string): string {\n return join(rootDir, \".rig\", \"tap\", \"imports\");\n}\n\n/** `.rig/tap/imports/<name>.local.json` — per-import token file. */\nexport function importMountFile(rootDir: string, name: string): string {\n return join(importsDir(rootDir), `${name}.local.json`);\n}\n\nconst MOUNT_FILE_SUFFIX = \".local.json\";\n\nexport function writeImportMount(\n rootDir: string,\n name: string,\n mount: ImportMountFile,\n): void {\n const path = importMountFile(rootDir, name);\n mkdirSync(dirname(path), { recursive: true });\n // 0o600 — carries a capability secret, same as tap-binding.local.json.\n writeFileSync(path, JSON.stringify(mount, null, 2) + \"\\n\", { mode: 0o600 });\n}\n\n/**\n * Read + validate a single mount file. Returns the spec on success or a\n * structured reason on failure — never throws, so one broken mount can be\n * degraded without taking down the daemon (gotcha #7).\n */\nexport function readImportMount(\n rootDir: string,\n name: string,\n): { ok: true; spec: ImportSpec } | { ok: false; reason: string } {\n const file = importMountFile(rootDir, name);\n if (!existsSync(file)) return { ok: false, reason: \"missing_token_file\" };\n let parsed: Partial<ImportMountFile>;\n try {\n parsed = JSON.parse(readFileSync(file, \"utf8\")) as Partial<ImportMountFile>;\n } catch {\n return { ok: false, reason: \"malformed_json\" };\n }\n if (typeof parsed.bindingId !== \"string\" || parsed.bindingId === \"\") {\n return { ok: false, reason: \"missing_bindingId\" };\n }\n if (typeof parsed.relayUrl !== \"string\" || parsed.relayUrl === \"\") {\n return { ok: false, reason: \"missing_relayUrl\" };\n }\n if (typeof parsed.token !== \"string\" || parsed.token === \"\") {\n return { ok: false, reason: \"missing_token\" };\n }\n if (typeof parsed.path !== \"string\" || parsed.path === \"\") {\n return { ok: false, reason: \"missing_path\" };\n }\n // Token-vs-capability check: a mirror needs read AND subscribe. Refuse\n // a write-only/owner token pasted in, or a too-narrow scope.\n const ops = Array.isArray(parsed.ops) ? parsed.ops : [];\n if (!ops.includes(\"read\") || !ops.includes(\"subscribe\")) {\n return { ok: false, reason: \"insufficient_ops (need read+subscribe)\" };\n }\n return {\n ok: true,\n spec: {\n name,\n bindingId: parsed.bindingId,\n relayUrl: parsed.relayUrl,\n token: parsed.token,\n path: parsed.path,\n },\n };\n}\n\n/**\n * Enumerate `.rig/tap/imports/*.local.json` and resolve each to an\n * ImportSpec. Broken mount files are reported, not thrown. This is the\n * daemon's source of truth for which mirrors to run — read fresh at every\n * `tapd start` (v1 \"reload\" = restart).\n */\nexport function loadImportMounts(rootDir: string): {\n ready: ImportSpec[];\n broken: ImportValidationError[];\n} {\n const dir = importsDir(rootDir);\n const ready: ImportSpec[] = [];\n const broken: ImportValidationError[] = [];\n if (!existsSync(dir)) return { ready, broken };\n\n for (const ent of readdirSync(dir, { withFileTypes: true })) {\n if (!ent.isFile() || !ent.name.endsWith(MOUNT_FILE_SUFFIX)) continue;\n const name = ent.name.slice(0, -MOUNT_FILE_SUFFIX.length);\n const nameErr = validateImportName(name);\n if (nameErr) {\n broken.push({ name, path: \"\", reason: nameErr });\n continue;\n }\n const res = readImportMount(rootDir, name);\n if (!res.ok) {\n broken.push({ name, path: \"\", reason: res.reason });\n continue;\n }\n ready.push(res.spec);\n }\n return { ready, broken };\n}\n\n// ────────── internals ──────────\n\nfunction isWithin(parent: string, child: string): boolean {\n const rel = relative(parent, child);\n return rel === \"\" || (!rel.startsWith(\"..\") && !isAbsolute(rel));\n}\n\nfunction realpathNearestExisting(abs: string): string {\n let cur = abs;\n while (!existsSync(cur)) {\n const parent = dirname(cur);\n if (parent === cur) break;\n cur = parent;\n }\n const realExisting = realpathSync(cur);\n const remainder = relative(cur, abs);\n return remainder ? join(realExisting, remainder) : realExisting;\n}\n\n/**\n * Walk up from `abs` to the first path that exists on disk. Returns it if\n * it's a file (a collision — nothing can be created beneath a file), else\n * null. Once an existing *directory* is found we stop: on a real\n * filesystem its ancestors are all directories too.\n */\nfunction firstExistingFileOnPath(abs: string): string | null {\n let cur = abs;\n for (;;) {\n if (existsSync(cur)) {\n return statSync(cur).isFile() ? cur : null;\n }\n const parent = dirname(cur);\n if (parent === cur) return null;\n cur = parent;\n }\n}\n\nfunction pathsOverlap(a: string, b: string): boolean {\n return a === b || a.startsWith(b + \"/\") || b.startsWith(a + \"/\");\n}\n","/**\n * `tapd import` — accept an invite to another binding and mount it\n * read-only into a subdirectory of this rig (see rig-imports.md).\n *\n * Mirror of `join.ts`, but instead of making this folder a binding root\n * it writes a per-import mount file (`.rig/tap/imports/<name>.local.json`)\n * that the daemon picks up as a mirror subscription. Nothing about the\n * primary binding (if any) changes.\n *\n * 1. Canonicalize + validate the mount (name slug, path, symlink/parent\n * collisions, overlap with other mounts + primary content) BEFORE any\n * network call — reusing validateImports so the rules can't drift.\n * 2. Parse the invite URL → (baseUrl, secret).\n * 3. acceptInvite → { bindingId, token: { secret, ops } }.\n * 4. Refuse a token without read+subscribe (a mirror can't run on it).\n * 5. Write the mount file (mode 0600). The daemon must be (re)started to\n * pick it up — v1 \"reload\" = restart.\n */\n\nimport { existsSync, readFileSync, rmSync } from \"node:fs\";\nimport { dirname, join as joinPath } from \"node:path\";\nimport type { AcceptInviteResponse } from \"@tap/core\";\nimport { readBindingConfig } from \"./binding-config.js\";\nimport {\n importMountFile,\n importStateDbPath,\n loadImportMounts,\n normalizeImportPath,\n primaryStateDbPath,\n readImportMount,\n validateImports,\n writeImportMount,\n type ImportSpec,\n} from \"./imports.js\";\nimport { parseInviteUrl } from \"./join.js\";\nimport { acceptInvite, revokeImportEdge, RelayError } from \"./relay-client.js\";\nimport { openStateDb } from \"./state-db.js\";\n\nexport class ImportValidationFailed extends Error {\n constructor(public readonly reason: string) {\n super(`import rejected: ${reason}`);\n this.name = \"ImportValidationFailed\";\n }\n}\n\nexport class ImportTokenScopeError extends Error {\n constructor(public readonly ops?: ReadonlyArray<string>) {\n super(\n ops\n ? `invite token has ops [${ops.join(\", \")}] — a mirror needs read+subscribe`\n : `invite can't drive a mirror — it needs read+subscribe`,\n );\n this.name = \"ImportTokenScopeError\";\n }\n}\n\nexport type AddImportOptions = {\n rootDir: string;\n inviteUrl: string;\n /** Mount point (subdir, relative to the rig root). */\n path: string;\n /** Slug; defaults to the last path segment. */\n name?: string;\n accessToken?: string | null;\n deviceLabel?: string;\n fetch?: typeof fetch;\n};\n\nexport type AddImportResult = {\n name: string;\n path: string;\n bindingId: string;\n ops: ReadonlyArray<\"read\" | \"write\" | \"subscribe\">;\n /** Import-edge id recorded by the relay (absent for anonymous imports). */\n edgeId?: string;\n /** False when we sent our binding as the importer but the relay couldn't\n * verify membership — the edge was recorded as a personal import (you → S),\n * not rig → S. The CLI surfaces this as a warning. Undefined when we sent no\n * binding (import-only) or no edge was recorded. */\n importerBindingVerified?: boolean;\n};\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null;\n}\n\n/** Last path segment, used as the default mount name. */\nfunction deriveName(canonicalPath: string): string {\n const segs = canonicalPath.split(\"/\").filter(Boolean);\n return segs[segs.length - 1] ?? \"\";\n}\n\nfunction readPrimaryTrackedPaths(rootDir: string): string[] {\n const dbPath = primaryStateDbPath(rootDir);\n if (!existsSync(dbPath)) return [];\n const db = openStateDb(dbPath);\n try {\n return db.listPaths();\n } finally {\n db.close();\n }\n}\n\nexport async function addImport(opts: AddImportOptions): Promise<AddImportResult> {\n const path = normalizeImportPath(opts.path);\n const name = opts.name && opts.name.length > 0 ? opts.name : deriveName(path);\n\n // Validate BEFORE accepting — reuse the daemon's validator so a path\n // that the daemon would reject is never persisted. We fold the new mount\n // into the existing set so overlap/duplicate checks see everything; the\n // candidate carries placeholder connection fields (validateImports only\n // inspects name + path).\n const existing = loadImportMounts(opts.rootDir).ready;\n const candidate: ImportSpec = { name, bindingId: \"\", relayUrl: \"\", token: \"\", path };\n const { valid, errors } = validateImports({\n rootDir: opts.rootDir,\n imports: [...existing, candidate],\n primaryTrackedPaths: readPrimaryTrackedPaths(opts.rootDir),\n });\n if (!valid.some((v) => v.name === name && v.path === path)) {\n const err = errors.find((e) => e.name === name);\n throw new ImportValidationFailed(err?.reason ?? \"invalid_import\");\n }\n\n // Accept the invite (throws InvalidInviteUrlError on a bad URL). Send our\n // own binding (if bound) as the importer so the relay can record a rig → S\n // edge — it verifies our membership before trusting the claim. Import-only\n // meta-rigs omit it and get a user → S edge.\n const primaryBindingId = readBindingConfig(opts.rootDir)?.bindingId;\n const { baseUrl, secret } = parseInviteUrl(opts.inviteUrl);\n let accepted: AcceptInviteResponse;\n try {\n accepted = await acceptInvite({\n baseUrl,\n secret,\n accessToken: opts.accessToken ?? null,\n request: {\n ...(opts.deviceLabel ? { deviceLabel: opts.deviceLabel } : {}),\n importer: { ...(primaryBindingId ? { bindingId: primaryBindingId } : {}), path },\n },\n fetch: opts.fetch,\n });\n } catch (err) {\n // The relay rejects an import accept whose invite lacks read+subscribe\n // BEFORE creating any device/token/edge. Map that to the same error the\n // local post-accept check would raise.\n if (\n err instanceof RelayError &&\n isRecord(err.body) &&\n err.body.reason === \"import_requires_read_subscribe\"\n ) {\n throw new ImportTokenScopeError();\n }\n throw err;\n }\n\n // Fallback for older relays that don't reject server-side (and defence in\n // depth): refuse to write a mount the daemon couldn't run.\n const ops = accepted.token.ops;\n if (!ops.includes(\"read\") || !ops.includes(\"subscribe\")) {\n throw new ImportTokenScopeError(ops);\n }\n\n writeImportMount(opts.rootDir, name, {\n bindingId: accepted.bindingId,\n relayUrl: baseUrl,\n token: accepted.token.secret,\n ops,\n path,\n ...(accepted.importEdgeId ? { edgeId: accepted.importEdgeId } : {}),\n });\n\n return {\n name,\n path,\n bindingId: accepted.bindingId,\n ops,\n ...(accepted.importEdgeId ? { edgeId: accepted.importEdgeId } : {}),\n // Only meaningful when we actually sent a binding to verify.\n ...(primaryBindingId !== undefined\n ? { importerBindingVerified: accepted.importerBindingVerified ?? false }\n : {}),\n };\n}\n\n/**\n * Outcome of the best-effort server-side revoke half of a remove:\n * - revoked — edge tombstoned + token revoked on the relay\n * - not-creator — 403: the grant belongs to another user; left active\n * - gone — 404: already revoked/removed server-side\n * - no-edge — anonymous / pre-edge mount; nothing to revoke\n * - no-token — not logged in; can't reach the user plane (local-only)\n * - offline — relay unreachable / unexpected error\n */\nexport type RemoveImportRevoke =\n | \"revoked\"\n | \"not-creator\"\n | \"gone\"\n | \"no-edge\"\n | \"no-token\"\n | \"offline\";\n\nexport type RemoveImportResult = {\n /** False if there was no mount file by that name. */\n removed: boolean;\n /** The mount point that was removed (for an optional purge), if known. */\n path: string | null;\n /** Whether the materialized subdir was deleted (--purge). */\n purged: boolean;\n /** What happened to the server-side grant. */\n revoke: RemoveImportRevoke;\n};\n\nfunction readRawMount(\n rootDir: string,\n name: string,\n): { edgeId?: string; relayUrl?: string; path?: string } | null {\n const file = importMountFile(rootDir, name);\n if (!existsSync(file)) return null;\n try {\n return JSON.parse(readFileSync(file, \"utf8\"));\n } catch {\n return null;\n }\n}\n\n/**\n * Remove a mount: relinquish the grant on the relay (best-effort), then delete\n * the local token file + per-import state dir. With `purge`, also delete the\n * materialized subdir. The local removal ALWAYS happens — even offline, even\n * when the grant isn't ours — so the daemon stops mirroring locally. The\n * caller restarts the daemon. See docs/inter-rig-graph.md §7.\n */\nexport async function removeImport(opts: {\n rootDir: string;\n name: string;\n purge?: boolean;\n /** Hub token for the self-revoke. Omit → local-only (\"no-token\"). */\n accessToken?: string | null;\n fetch?: typeof fetch;\n}): Promise<RemoveImportResult> {\n const mountPath = importMountFile(opts.rootDir, opts.name);\n const res = readImportMount(opts.rootDir, opts.name);\n const raw = readRawMount(opts.rootDir, opts.name);\n const path = res.ok ? res.spec.path : raw?.path ?? null;\n\n // Best-effort server-side revoke (Layer 2), BEFORE deleting the file.\n let revoke: RemoveImportRevoke;\n if (!raw?.edgeId || !raw.relayUrl) {\n revoke = \"no-edge\";\n } else if (!opts.accessToken) {\n revoke = \"no-token\";\n } else {\n try {\n const r = await revokeImportEdge({\n baseUrl: raw.relayUrl,\n edgeId: raw.edgeId,\n accessToken: opts.accessToken,\n fetch: opts.fetch,\n });\n revoke = r.revoked\n ? \"revoked\"\n : r.status === 403\n ? \"not-creator\"\n : r.status === 404\n ? \"gone\"\n : \"offline\";\n } catch {\n revoke = \"offline\";\n }\n }\n\n let removed = false;\n if (existsSync(mountPath)) {\n rmSync(mountPath, { force: true });\n removed = true;\n }\n // Per-import state dir: .rig/tap/imports/<name>/\n rmSync(dirname(importStateDbPath(opts.rootDir, opts.name)), {\n recursive: true,\n force: true,\n });\n\n let purged = false;\n if (opts.purge && path) {\n rmSync(joinPath(opts.rootDir, path), { recursive: true, force: true });\n purged = true;\n }\n\n return { removed, path, purged, revoke };\n}\n","/**\n * Apply remote change events to the local filesystem.\n *\n * Phase 2 keeps this simple: last-write-wins, no conflict materialization\n * (Phase 4 adds it). Echo suppression is handled via the state DB:\n * if the remote event's hash matches our last_seen_hash for the path,\n * we know it's our own write coming back through the poll loop and skip\n * the download.\n *\n * Materialization is atomic: download to `<file>.tap-tmp.<rand>` then\n * `rename` into place. Atomic on POSIX; on Windows, `fs.rename` falls\n * back to MoveFileEx-style replace.\n *\n * Files from other members are treated like downloads: never executable\n * (the event's `executable` bit is ignored), quarantined on macOS so\n * Gatekeeper checks them before they run, and never-sync paths (.git,\n * agent/MCP config — core isNeverSyncPath) are not applied at all.\n */\n\nimport { execFile } from \"node:child_process\";\nimport { createHash, randomBytes } from \"node:crypto\";\nimport {\n existsSync,\n lstatSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n renameSync,\n rmdirSync,\n rmSync,\n writeFileSync,\n type Dirent,\n} from \"node:fs\";\nimport { basename, dirname, extname, join, sep } from \"node:path\";\nimport { isNeverSyncPath, type ChangeEvent } from \"@tap/core\";\nimport type { RelayClient } from \"./relay-client.js\";\nimport type { StateDb } from \"./state-db.js\";\nimport { safeResolveRigPath } from \"./safe-path.js\";\n\n/** State-DB meta key — running count of conflicts surfaced by `tapd status`. */\nexport const STATE_KEY_CONFLICT_COUNT = \"conflict_count\";\n\n/**\n * Thrown when downloaded bytes don't hash to the value we trusted via\n * the change event. Caught by applyOnce so the cursor still advances\n * (the daemon would otherwise spin forever).\n */\nexport class HashMismatchError extends Error {\n constructor(\n public readonly path: string,\n public readonly expectedHash: string,\n public readonly actualHash: string,\n ) {\n super(\n `hash mismatch at ${path}: expected ${expectedHash}, got ${actualHash}`,\n );\n this.name = \"HashMismatchError\";\n }\n}\n\nfunction sha256Hex(bytes: Buffer | Uint8Array): string {\n return createHash(\"sha256\").update(bytes).digest(\"hex\");\n}\n\nfunction sha256OfFile(absPath: string): string {\n return `sha256:${sha256Hex(readFileSync(absPath))}`;\n}\n\n/**\n * Sidecar path for a conflict materialization. Original extension is\n * preserved at the end so editor highlighting still works.\n *\n * decisions/pricing.md → decisions/pricing.conflict-from.dev_xyz.chg_456.md\n *\n * Device label resolution lands when GET /v1/bindings/:id/devices does\n * (Phase 5); until then we use the raw deviceId, which is at least an\n * audit-stable reference.\n */\nexport function conflictSidecarPath(\n path: string,\n ev: { id: string; actorDeviceId: string | null },\n): string {\n const dir = dirname(path);\n const ext = extname(path);\n const stem = basename(path, ext);\n const actor = ev.actorDeviceId ?? \"unknown\";\n const sidecar = `${stem}.conflict-from.${actor}.${ev.id}${ext}`;\n return dir === \".\" ? sidecar : `${dir}/${sidecar}`;\n}\n\n/**\n * Inverse of conflictSidecarPath — given a sidecar filename, recover the\n * original path it was forked from. Returns null for any filename that\n * doesn't match the sidecar format. Used by `tapd status` to surface\n * conflicts to rig.\n */\nconst SIDECAR_RE = /^(.*)\\.conflict-from\\.[^.]+\\.(chg_\\d+)(\\.[^.]+)?$/;\nexport function parseConflictSidecar(\n filename: string,\n): { stem: string; ext: string; chgId: string } | null {\n const m = SIDECAR_RE.exec(filename);\n if (!m) return null;\n return { stem: m[1]!, chgId: m[2]!, ext: m[3] ?? \"\" };\n}\n\n/**\n * Recursively walk `rootDir` for `*.conflict-from.*` sidecars. Bypasses\n * .tapignore (sidecars are in the default ignore list — that's why\n * scanAndPush doesn't push them — but we still want `tapd status` to\n * surface them). Skips `.rig/`, `.git/`, `node_modules/` for speed.\n */\nconst SCAN_SKIP_DIRS = new Set([\".git\", \"node_modules\", \".rig\"]);\nexport function findConflictSidecars(\n rootDir: string,\n): Array<{ path: string; sidecarPath: string }> {\n const out: Array<{ path: string; sidecarPath: string }> = [];\n walk(\".\");\n return out;\n\n function walk(rel: string): void {\n const abs = rel === \".\" ? rootDir : join(rootDir, rel);\n let entries: Dirent[];\n try {\n entries = readdirSync(abs, { withFileTypes: true });\n } catch {\n return;\n }\n for (const ent of entries) {\n if (ent.isSymbolicLink()) continue;\n const childRel = rel === \".\" ? ent.name : join(rel, ent.name);\n if (ent.isDirectory()) {\n if (SCAN_SKIP_DIRS.has(ent.name)) continue;\n walk(childRel);\n continue;\n }\n if (!ent.isFile()) continue;\n const parsed = parseConflictSidecar(ent.name);\n if (!parsed) continue;\n const sidecarPosix = sep === \"/\" ? childRel : childRel.replaceAll(sep, \"/\");\n const dir = dirname(sidecarPosix);\n const original = dir === \".\" ? `${parsed.stem}${parsed.ext}` : `${dir}/${parsed.stem}${parsed.ext}`;\n out.push({ path: original, sidecarPath: sidecarPosix });\n }\n }\n}\n\n/**\n * Mode for every file the applier writes. The protocol still carries\n * `executable`, but a file another member wrote must not become runnable\n * on this machine just because they said so.\n */\nconst APPLIED_FILE_MODE = 0o644;\n\n// Keep each xattr spawn's argv far below macOS's ARG_MAX (1 MiB, shared\n// with the environment).\nconst QUARANTINE_ARGV_BYTES = 128 * 1024;\n\n/**\n * Mark files as downloaded (`com.apple.quarantine`) so Gatekeeper checks\n * them before they run, like anything else fetched from the internet.\n * macOS only; one `/usr/bin/xattr` spawn per chunk of paths. Best-effort:\n * a failure is logged and never fails the sync.\n */\nexport async function quarantineFiles(absPaths: ReadonlyArray<string>): Promise<void> {\n if (process.platform !== \"darwin\") return;\n const paths = [...new Set(absPaths)].filter((p) => existsSync(p));\n if (paths.length === 0) return;\n const value = `0081;${Math.floor(Date.now() / 1000).toString(16)};Rig;`;\n const chunks: string[][] = [];\n let chunk: string[] = [];\n let bytes = 0;\n for (const p of paths) {\n const len = Buffer.byteLength(p) + 1;\n if (chunk.length > 0 && bytes + len > QUARANTINE_ARGV_BYTES) {\n chunks.push(chunk);\n chunk = [];\n bytes = 0;\n }\n chunk.push(p);\n bytes += len;\n }\n chunks.push(chunk);\n for (const files of chunks) {\n await new Promise<void>((resolve) => {\n execFile(\"/usr/bin/xattr\", [\"-w\", \"com.apple.quarantine\", value, ...files], (err) => {\n if (err) console.error(`tapd apply: could not quarantine ${files.length} file(s): ${err.message}`);\n resolve();\n });\n });\n }\n}\n\n/** Never-sync paths already reported, so the log says so once per path. */\nconst loggedNeverSync = new Set<string>();\n\nfunction bumpConflictCount(stateDb: StateDb): void {\n const current = Number.parseInt(\n stateDb.getMeta(STATE_KEY_CONFLICT_COUNT) ?? \"0\",\n 10,\n );\n stateDb.setMeta(STATE_KEY_CONFLICT_COUNT, String(current + 1));\n}\n\n/**\n * Apply mode.\n *\n * - `primary`: the local copy has write authority. Local-divergence\n * guards protect unpushed edits (sidecars, keep-on-delete, refuse to\n * clobber a dir) on the assumption scanAndPush carries them upstream.\n * - `mirror`: a read-only import of another binding. The mount has no\n * local authority, so **remote always wins** across every event type —\n * every divergence guard flips to remote-authoritative. There is no\n * scanAndPush to drain sidecars or re-submit kept files, so the primary\n * guards would instead let edited imported content linger forever.\n */\nexport type ApplyMode = \"primary\" | \"mirror\";\n\nexport type ApplyEventError = {\n eventId: string;\n path: string;\n op: string;\n reason: string;\n};\n\nexport type ApplyResult = {\n applied: number;\n echoSkipped: number;\n errored: number;\n events: ReadonlyArray<ChangeEvent>;\n errors: ReadonlyArray<ApplyEventError>;\n};\n\nconst STATE_KEY_LAST_APPLY_ERROR = \"last_apply_error\";\nconst STATE_KEY_LAST_APPLY_ERROR_AT = \"last_apply_error_at\";\n\n/**\n * One iteration of the apply loop: poll `GET /changes?after=<cursor>`,\n * materialize each event, update the cursor.\n *\n * Per-event errors (hash mismatch, file/directory collision, disk full,\n * etc.) are caught, logged, and recorded to the state DB — the cursor\n * still advances past the failed event so the daemon doesn't loop\n * forever on the same broken row. Phase 4 conflict materialization\n * will replace the \"skip + log\" behavior for the file/dir collision\n * case; for now it's the safe default.\n */\nexport async function applyOnce(opts: {\n rootDir: string;\n client: RelayClient;\n stateDb: StateDb;\n limit?: number;\n /** Defaults to \"primary\". See ApplyMode. */\n mode?: ApplyMode;\n}): Promise<ApplyResult> {\n const mode = opts.mode ?? \"primary\";\n const cursor = opts.stateDb.getCursor();\n const since = await opts.client.listChanges({ after: cursor, limit: opts.limit });\n let applied = 0;\n let echoSkipped = 0;\n let errored = 0;\n const errors: ApplyEventError[] = [];\n const written: string[] = [];\n\n for (const ev of since.events) {\n try {\n const wasEcho = await applyEvent({\n rootDir: opts.rootDir,\n client: opts.client,\n stateDb: opts.stateDb,\n event: ev,\n mode,\n written,\n });\n if (wasEcho) echoSkipped += 1;\n else applied += 1;\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n const recorded: ApplyEventError = {\n eventId: ev.id,\n path: ev.path,\n op: ev.op,\n reason,\n };\n errors.push(recorded);\n errored += 1;\n opts.stateDb.setMeta(STATE_KEY_LAST_APPLY_ERROR, JSON.stringify(recorded));\n opts.stateDb.setMeta(STATE_KEY_LAST_APPLY_ERROR_AT, new Date().toISOString());\n // Surface to the operator. We keep going so a single broken event\n // doesn't wedge the rest of the queue.\n console.error(\n `tapd apply: skipping ${ev.id} (${ev.op} ${ev.path}): ${reason}`,\n );\n }\n // Always advance the cursor, regardless of success/skip/error.\n // Crash-mid-batch resumes from the last-touched event, not the start.\n opts.stateDb.setCursor(ev.id);\n }\n\n await quarantineFiles(written);\n\n return { applied, echoSkipped, errored, events: since.events, errors };\n}\n\nasync function applyEvent(opts: {\n rootDir: string;\n client: RelayClient;\n stateDb: StateDb;\n event: ChangeEvent;\n mode: ApplyMode;\n /** Absolute paths of files written, for quarantineFiles. */\n written: string[];\n}): Promise<boolean> {\n const { rootDir, client, stateDb, event: ev, mode, written } = opts;\n const mirror = mode === \"mirror\";\n const abs = safeResolveRigPath(rootDir, ev.path);\n\n // Never-sync paths (.git, agent/MCP config) from anyone else are never\n // written or deleted here — the relay refuses new ones, this covers\n // legacy entries and a relay that didn't. Nothing touched: count it as\n // skipped, like an echo.\n if (isNeverSyncPath(ev.path)) {\n if (!loggedNeverSync.has(ev.path)) {\n loggedNeverSync.add(ev.path);\n console.warn(`tapd apply: not applying ${ev.op} ${ev.path} from another member: protected path`);\n }\n return true;\n }\n\n switch (ev.op) {\n case \"write\": {\n if (!ev.hash) throw new Error(`write event ${ev.id} missing hash`);\n const local = stateDb.getPath(ev.path);\n if (local?.lastSeenHash === ev.hash && existsSync(abs)) {\n // Echo: we already have these bytes (likely our own write\n // coming back through the change stream).\n stateDb.upsertPath({\n path: ev.path,\n lastAppliedChangeId: ev.id,\n lastSeenHash: ev.hash,\n localDirty: false,\n lastScanAt: new Date().toISOString(),\n });\n return true;\n }\n\n // File-vs-directory collision: a file write lands where a local\n // directory sits.\n // primary → refuse to clobber (caught by applyOnce, recorded,\n // cursor still advances).\n // mirror → remote wins: replace the directory with the file.\n if (existsSync(abs) && lstatSync(abs).isDirectory()) {\n if (!mirror) {\n throw new Error(\n `file_vs_dir_collision: ${ev.path} is a local directory; refusing to overwrite with a file`,\n );\n }\n rmSync(abs, { recursive: true, force: true });\n }\n\n // Local-divergence detection. If the local file's bytes differ\n // from our recorded last_seen_hash AND we have a recorded\n // baseline (i.e., we've seen this path before), the user edited\n // locally without pushing yet. Applying the remote would\n // overwrite those edits — instead, materialize the remote as a\n // conflict sidecar and leave local alone. The next scanAndPush\n // pushes the local edit upstream as a normal change event.\n //\n // mirror mode has no local authority and no scanAndPush, so we\n // never take the sidecar branch — remote always wins (plain\n // overwrite below).\n const localDirty =\n !mirror &&\n existsSync(abs) &&\n local?.lastSeenHash != null &&\n sha256OfFile(abs) !== local.lastSeenHash;\n\n const dl = await client.downloadUrl(ev.hash);\n const bytes = await client.getObjectBytes(dl.downloadUrl);\n // Hash-verify the downloaded bytes BEFORE any disk touch.\n const actualHash = `sha256:${sha256Hex(bytes)}`;\n if (actualHash !== ev.hash) {\n throw new HashMismatchError(ev.path, ev.hash, actualHash);\n }\n\n if (localDirty) {\n const sidecarPath = conflictSidecarPath(ev.path, ev);\n const sidecarAbs = safeResolveRigPath(rootDir, sidecarPath);\n mkdirSync(dirname(sidecarAbs), { recursive: true });\n const tmp = `${sidecarAbs}.tap-tmp.${randomBytes(6).toString(\"hex\")}`;\n writeFileSync(tmp, bytes, { mode: APPLIED_FILE_MODE });\n renameSync(tmp, sidecarAbs);\n written.push(sidecarAbs);\n // Cursor advances + lastAppliedChangeId updates, but\n // lastSeenHash is NOT updated — local file still differs from\n // baseline, so the next scanAndPush sees the unpushed local\n // edit and submits it (with baseChangeId = ev.id, so the relay\n // can record the causality).\n stateDb.upsertPath({\n path: ev.path,\n lastAppliedChangeId: ev.id,\n lastSeenHash: local!.lastSeenHash,\n localDirty: true,\n lastScanAt: new Date().toISOString(),\n });\n bumpConflictCount(stateDb);\n return false;\n }\n\n mkdirSync(dirname(abs), { recursive: true });\n // Atomic rename — tmp file in the same dir so it's the same fs/volume.\n const tmp = `${abs}.tap-tmp.${randomBytes(6).toString(\"hex\")}`;\n writeFileSync(tmp, bytes, { mode: APPLIED_FILE_MODE });\n renameSync(tmp, abs);\n written.push(abs);\n stateDb.upsertPath({\n path: ev.path,\n lastAppliedChangeId: ev.id,\n lastSeenHash: ev.hash,\n localDirty: false,\n lastScanAt: new Date().toISOString(),\n });\n return false;\n }\n case \"delete\": {\n if (existsSync(abs)) {\n if (lstatSync(abs).isDirectory()) {\n // primary → refuse recursive removal of a local directory.\n // mirror → remote wins: remove the directory recursively.\n if (!mirror) {\n throw new Error(\n `delete_vs_dir: ${ev.path} is a local directory; refusing recursive removal`,\n );\n }\n rmSync(abs, { recursive: true, force: true });\n stateDb.deletePath(ev.path);\n return false;\n }\n // Symmetric to write: if the user edited the file locally\n // since we last synced (or if there's no baseline at all but\n // the file is on disk), don't destroy their unpushed work.\n // Keep the file. Record state with lastSeenHash=null and\n // lastAppliedChangeId = ev.id so the next scanAndPush detects\n // the file as \"needs push\" (hash !== null), submits a write\n // event with baseChangeId = ev.id (the delete), and the relay\n // resurrects the file on peers with proper causality.\n //\n // mirror mode skips this guard entirely (no local authority,\n // no scanAndPush) — remote always wins, the file is removed.\n if (!mirror) {\n const localStateRow = stateDb.getPath(ev.path);\n const localBytesHash = sha256OfFile(abs);\n const localDirty =\n localStateRow?.lastSeenHash != null &&\n localBytesHash !== localStateRow.lastSeenHash;\n const localUntracked = localStateRow?.lastSeenHash == null;\n if (localDirty || localUntracked) {\n stateDb.upsertPath({\n path: ev.path,\n lastAppliedChangeId: ev.id,\n // null so scanAndPush's `prev?.lastSeenHash === hash` check\n // falls through and the file gets re-submitted as a write.\n lastSeenHash: null,\n localDirty: true,\n lastScanAt: new Date().toISOString(),\n });\n bumpConflictCount(stateDb);\n // Cursor advance happens in applyOnce regardless of return value.\n return false;\n }\n }\n rmSync(abs, { force: true });\n }\n stateDb.deletePath(ev.path);\n return false;\n }\n case \"mkdir\": {\n mkdirSync(abs, { recursive: true });\n stateDb.upsertPath({\n path: ev.path,\n lastAppliedChangeId: ev.id,\n lastSeenHash: null,\n localDirty: false,\n lastScanAt: new Date().toISOString(),\n });\n return false;\n }\n case \"rmdir\": {\n if (mirror) {\n // Remote wins: remove the directory even if the mount has\n // accumulated stray content. A read-only import must converge\n // to the source — a lingering non-empty dir would never drain.\n rmSync(abs, { recursive: true, force: true });\n } else {\n // Best-effort, non-recursive: if the user has put new content\n // inside since the rmdir was issued, leave it alone. rmdirSync is\n // the right primitive — fs.rmSync requires recursive:true for any\n // directory, even an empty one.\n try {\n rmdirSync(abs);\n } catch {\n // ignore — directory was non-empty or already gone\n }\n }\n stateDb.deletePath(ev.path);\n return false;\n }\n }\n}\n","import { isAbsolute, relative, resolve } from \"node:path\";\nimport { validateRigPath, type PathValidationError } from \"@tap/core\";\n\nexport class UnsafeRigPathError extends Error {\n constructor(\n public path: string,\n public reason: PathValidationError | \"outside_root\",\n ) {\n super(`Unsafe rig path \"${path}\": ${reason}`);\n this.name = \"UnsafeRigPathError\";\n }\n}\n\nexport function safeResolveRigPath(rootDir: string, path: string): string {\n const valid = validateRigPath(path);\n if (!valid.ok) throw new UnsafeRigPathError(path, valid.reason);\n\n const rootAbs = resolve(rootDir);\n const targetAbs = resolve(rootAbs, path);\n const rel = relative(rootAbs, targetAbs);\n if (rel === \"\" || rel.startsWith(\"..\") || isAbsolute(rel)) {\n throw new UnsafeRigPathError(path, \"outside_root\");\n }\n return targetAbs;\n}\n","/**\n * SSE consumer for the relay's /stream endpoint.\n *\n * Phase 4c — push delivery so applyOnce fires within milliseconds of a\n * remote change instead of waiting on the poll interval. The poll loop\n * remains the source of truth and keeps running underneath as a\n * fallback (slower cadence is fine when SSE is healthy).\n *\n * Reconnect semantics:\n * - Server-side disconnect (stream end) or client-side fetch error\n * → backoff 1s, 2s, 4s, 8s, 16s, capped at 30s; reset on success.\n * - Heartbeat watchdog: if no event/heartbeat lands within\n * `heartbeatTimeoutMs` (default 45s), abort + reconnect.\n * - `stop()` aborts the in-flight fetch + cancels pending reconnects.\n *\n * The subscriber NEVER advances the cursor — it just nudges applyOnce.\n * Cursor management stays in the apply loop, which reads /changes as\n * the durable source of truth.\n */\n\nimport type { ChangeEvent } from \"@tap/core\";\n\ntype FetchLike = typeof fetch;\n\nexport type SseSubscriberOptions = {\n baseUrl: string;\n bindingId: string;\n token: string;\n onChange: (change: ChangeEvent) => void | Promise<void>;\n onSubscribed?: (info: { bindingId: string; pathGlobs: string[] }) => void;\n onError?: (err: Error) => void;\n fetch?: FetchLike;\n /** Watchdog window. Default 45s — matches a server heartbeat of 15s × 3. */\n heartbeatTimeoutMs?: number;\n /** Backoff for the Nth reconnect attempt (0-indexed). */\n reconnectDelayMs?: (attempt: number) => number;\n};\n\nexport type SseSubscriber = {\n stop(): Promise<void>;\n};\n\nconst DEFAULT_HEARTBEAT_TIMEOUT_MS = 45_000;\n\nfunction defaultBackoff(attempt: number): number {\n return Math.min(30_000, 1_000 * 2 ** attempt);\n}\n\nexport function subscribe(opts: SseSubscriberOptions): SseSubscriber {\n const fetchImpl = opts.fetch ?? fetch;\n const heartbeatTimeoutMs = opts.heartbeatTimeoutMs ?? DEFAULT_HEARTBEAT_TIMEOUT_MS;\n const backoff = opts.reconnectDelayMs ?? defaultBackoff;\n\n let stopped = false;\n let attempt = 0;\n let activeAbort: AbortController | null = null;\n let reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n let loop: Promise<void>;\n\n const surface = (err: Error) => {\n if (opts.onError) opts.onError(err);\n else console.error(\"tapd sse:\", err.message);\n };\n\n const connect = async (): Promise<void> => {\n if (stopped) return;\n const ac = new AbortController();\n activeAbort = ac;\n const url = `${opts.baseUrl.replace(/\\/+$/, \"\")}/v1/bindings/${encodeURIComponent(opts.bindingId)}/stream`;\n\n let res: Response;\n try {\n res = await fetchImpl(url, {\n method: \"GET\",\n headers: {\n accept: \"text/event-stream\",\n authorization: `Bearer ${opts.token}`,\n },\n signal: ac.signal,\n });\n } catch (err) {\n if (stopped) return;\n surface(asError(err, \"sse_connect_failed\"));\n scheduleReconnect();\n return;\n }\n\n if (!res.ok || !res.body) {\n surface(new Error(`sse_bad_status:${res.status}`));\n scheduleReconnect();\n return;\n }\n\n // Success — reset backoff so a subsequent disconnect retries promptly.\n attempt = 0;\n await pumpStream(res.body, ac);\n if (stopped) return;\n // Stream ended without error → reconnect.\n scheduleReconnect();\n };\n\n const pumpStream = async (\n body: ReadableStream<Uint8Array>,\n ac: AbortController,\n ): Promise<void> => {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n let lastTick = Date.now();\n\n // Watchdog: bail out if no chunk arrives within heartbeatTimeoutMs.\n const watchdog = setInterval(() => {\n if (Date.now() - lastTick > heartbeatTimeoutMs) {\n surface(new Error(\"sse_heartbeat_timeout\"));\n ac.abort();\n }\n }, Math.max(1_000, heartbeatTimeoutMs / 5));\n\n try {\n while (!stopped) {\n let result: Awaited<ReturnType<typeof reader.read>>;\n try {\n result = await reader.read();\n } catch (err) {\n if (stopped) break;\n surface(asError(err, \"sse_read_failed\"));\n break;\n }\n if (result.done) break;\n lastTick = Date.now();\n buffer += decoder.decode(result.value, { stream: true });\n let idx: number;\n while ((idx = buffer.indexOf(\"\\n\\n\")) >= 0) {\n const frame = buffer.slice(0, idx);\n buffer = buffer.slice(idx + 2);\n await dispatchFrame(frame);\n }\n }\n } finally {\n clearInterval(watchdog);\n try {\n await reader.cancel();\n } catch {\n /* already cancelled */\n }\n }\n };\n\n const dispatchFrame = async (frame: string): Promise<void> => {\n if (frame.trim().length === 0) return;\n let event: string | undefined;\n let data: string | undefined;\n for (const line of frame.split(\"\\n\")) {\n if (line.startsWith(\":\")) continue;\n if (line.startsWith(\"event: \")) event = line.slice(7);\n else if (line.startsWith(\"data: \")) data = line.slice(6);\n }\n if (!event || !data) return;\n try {\n if (event === \"subscribed\" && opts.onSubscribed) {\n opts.onSubscribed(JSON.parse(data) as { bindingId: string; pathGlobs: string[] });\n } else if (event === \"change_created\") {\n await opts.onChange(JSON.parse(data) as ChangeEvent);\n } else if (event === \"heartbeat\") {\n // Already accounted for via watchdog tick reset.\n }\n } catch (err) {\n surface(asError(err, `sse_handler_failed:${event}`));\n }\n };\n\n const scheduleReconnect = () => {\n if (stopped) return;\n const delay = backoff(attempt++);\n reconnectTimer = setTimeout(() => {\n reconnectTimer = null;\n void connect();\n }, delay);\n };\n\n loop = connect();\n\n return {\n async stop() {\n stopped = true;\n if (reconnectTimer) {\n clearTimeout(reconnectTimer);\n reconnectTimer = null;\n }\n if (activeAbort) activeAbort.abort();\n await loop.catch(() => {});\n },\n };\n}\n\nfunction asError(err: unknown, fallbackName: string): Error {\n if (err instanceof Error) return err;\n const e = new Error(String(err));\n e.name = fallbackName;\n return e;\n}\n","/**\n * A single binding's apply loop: SSE subscriber + poll fallback +\n * apply-mutex → applyOnce. This is the \"download direction\" of the\n * daemon, extracted from start.ts so it can run N times — once for the\n * primary binding (mode \"primary\") and once per import (mode \"mirror\").\n *\n * It does NOT own the watcher or scanAndPush — that \"upload direction\"\n * stays attached only to the primary binding, in start.ts. A mirror is a\n * degenerate daemon: apply-only, no uploader.\n *\n * Each subscription owns its own state DB (separate cursor per binding)\n * and its own RelayClient. stop() closes the state DB, so the caller\n * must stop any writer that shares it FIRST.\n */\n\nimport { applyOnce, type ApplyMode, type ApplyResult } from \"./applier.js\";\nimport { writeSyncPaused } from \"./binding-config.js\";\nimport { isBindingDeletedError, RelayClient } from \"./relay-client.js\";\nimport { subscribe, type SseSubscriber } from \"./sse.js\";\nimport { openStateDb, type StateDb } from \"./state-db.js\";\n\n/** Minimal connection config — the apply side never needs deviceId. */\nexport type SubscriptionConfig = {\n bindingId: string;\n relayUrl: string;\n token: string;\n};\n\nexport type SubscriptionHandle = {\n /** Stop SSE + poll, drain the in-flight apply, then close the state DB. */\n stop(): Promise<void>;\n /** Force one apply cycle now (through the mutex). */\n applyNow(): Promise<ApplyResult>;\n /** Underlying state DB. Caller MUST NOT close it — stop() does. */\n stateDb: StateDb;\n};\n\nexport type StartSubscriptionOptions = {\n /** Directory this binding materializes into (primary root, or a mount subdir). */\n rootDir: string;\n config: SubscriptionConfig;\n /** Separate db per binding → separate cursor. */\n stateDbPath: string;\n mode: ApplyMode;\n fetch?: typeof fetch;\n pollSeconds?: number;\n /** Test seam — disable SSE (poll-only). */\n noSse?: boolean;\n sseHeartbeatTimeoutMs?: number;\n /**\n * Called once, after this subscription has already stopped itself (poll\n * timer cleared, SSE stopped, `.rig/sync-paused.json` written), when the\n * relay reports the binding was deleted (410 `binding_deleted`). The\n * caller (start.ts for the primary binding) can use this to also stop\n * the writer — the subscription itself only owns the download side.\n */\n onBindingDeleted?: (info: { deletedAt: string }) => void;\n};\n\nconst DEFAULT_POLL_SECONDS = 3;\nconst SSE_APPLY_DEBOUNCE_MS = 50;\n\nexport async function startSubscription(\n opts: StartSubscriptionOptions,\n): Promise<SubscriptionHandle> {\n const { mode } = opts;\n const stateDb = openStateDb(opts.stateDbPath);\n const client = new RelayClient({\n baseUrl: opts.config.relayUrl,\n bindingId: opts.config.bindingId,\n token: opts.config.token,\n fetch: opts.fetch,\n });\n const pollSeconds = opts.pollSeconds ?? DEFAULT_POLL_SECONDS;\n\n let stopped = false;\n let pollTimer: ReturnType<typeof setTimeout> | undefined;\n let sseApplyTimer: ReturnType<typeof setTimeout> | undefined;\n let sseSubscriber: SseSubscriber | undefined;\n\n // ────────── apply mutex ──────────\n // SSE and poll both want to trigger applyOnce. Serialize: at most one\n // applyOnce in flight; a trigger arriving mid-flight sets \"rerun-after\"\n // so the next cycle picks up whatever landed since.\n let applyInFlight: Promise<ApplyResult | undefined> | null = null;\n let rerunRequested = false;\n let lastApplyResult: ApplyResult | undefined;\n const triggerApply = (): Promise<ApplyResult | undefined> => {\n if (applyInFlight) {\n rerunRequested = true;\n return applyInFlight;\n }\n const loop = (async () => {\n try {\n do {\n rerunRequested = false;\n try {\n lastApplyResult = await applyOnce({\n rootDir: opts.rootDir,\n client,\n stateDb,\n mode,\n });\n } catch (err) {\n if (isBindingDeletedError(err)) {\n const { deletedAt, deletedBy } = err.body;\n const by = deletedBy?.name ?? deletedBy?.email ?? \"someone\";\n console.error(\n `tapd: binding ${opts.config.bindingId} was deleted by ${by} at ${deletedAt} — pausing sync and stopping.`,\n );\n writeSyncPaused(opts.rootDir, \"deleted\");\n // Stop retrying: no more polls, no more SSE reconnects. The\n // `!stopped` guard in the `while` below (and in\n // `schedulePoll`) does the rest — this just needs to be set\n // before either of those next checks.\n stopped = true;\n if (pollTimer) clearTimeout(pollTimer);\n if (sseApplyTimer) clearTimeout(sseApplyTimer);\n if (sseSubscriber) await sseSubscriber.stop();\n opts.onBindingDeleted?.({ deletedAt });\n } else {\n console.error(\"tapd apply error:\", err);\n }\n }\n } while (rerunRequested && !stopped);\n return lastApplyResult;\n } finally {\n applyInFlight = null;\n }\n })();\n applyInFlight = loop;\n return loop;\n };\n\n const schedulePoll = () => {\n if (stopped) return;\n pollTimer = setTimeout(() => {\n triggerApply().finally(() => schedulePoll());\n }, pollSeconds * 1000);\n };\n\n // SSE-triggered apply is debounced — bursts coalesce into one apply.\n const scheduleSseApply = () => {\n if (stopped) return;\n if (sseApplyTimer) clearTimeout(sseApplyTimer);\n sseApplyTimer = setTimeout(() => {\n sseApplyTimer = undefined;\n void triggerApply();\n }, SSE_APPLY_DEBOUNCE_MS);\n };\n\n if (!opts.noSse) {\n sseSubscriber = subscribe({\n baseUrl: opts.config.relayUrl,\n bindingId: opts.config.bindingId,\n token: opts.config.token,\n fetch: opts.fetch,\n heartbeatTimeoutMs: opts.sseHeartbeatTimeoutMs,\n onChange: () => scheduleSseApply(),\n onError: (err) => {\n console.error(\"tapd sse:\", err.message);\n },\n });\n }\n\n // Apply remote changes before returning so callers (and the primary's\n // initial scan) observe a reconciled tree.\n await triggerApply();\n schedulePoll();\n\n return {\n async stop() {\n stopped = true;\n if (pollTimer) clearTimeout(pollTimer);\n if (sseApplyTimer) clearTimeout(sseApplyTimer);\n if (sseSubscriber) await sseSubscriber.stop();\n await Promise.allSettled([applyInFlight ?? Promise.resolve()]);\n stateDb.close();\n },\n async applyNow() {\n const result = await triggerApply();\n return (\n result ?? {\n applied: 0,\n echoSkipped: 0,\n errored: 0,\n events: [],\n errors: [],\n }\n );\n },\n stateDb,\n };\n}\n","/**\n * Local → remote sync direction: compute the diff between the file\n * system and the state DB, upload any new bytes, submit one batched\n * `POST /changes`.\n *\n * Phase 2 calls this on startup (initial reconcile) and on each\n * watcher event (debounced).\n */\n\nimport { createHash } from \"node:crypto\";\nimport { readdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { Ignore } from \"ignore\";\nimport type { ChangeEvent, SubmitChangeEvent } from \"@tap/core\";\nimport { hashFile } from \"./hash.js\";\nimport { findConflictSidecars, parseConflictSidecar } from \"./applier.js\";\nimport { basename } from \"node:path\";\nimport type { IntentAssignment } from \"./intent-association.js\";\nimport { RelayError, type RelayClient } from \"./relay-client.js\";\nimport type { StateDb } from \"./state-db.js\";\nimport { walk } from \"./walker.js\";\nimport { mapPool } from \"./pool.js\";\n\n// Max object uploads in flight. Keeps a many-file scan fast without\n// hammering the relay (a small shared VM) with an unbounded burst.\nconst UPLOAD_CONCURRENCY = 8;\n\n/**\n * Files the relay refused (or the walker never offered) because of size,\n * or changes the relay refused because only the space's owner may make\n * them (agent instructions — core isOwnerOnlyPath): persisted in the state\n * DB meta so `tapd status` can say \"N files not synced\", and so an\n * unchanged refused change isn't retried on every tick. Recomputed each\n * scan — an entry disappears once its file syncs, changes away from the\n * refused bytes and uploads, or is deleted.\n */\nexport const STATE_KEY_NOT_SYNCED = \"not_synced\";\n/** An unchanged refused file is retried after this long (a slow backoff). */\nexport const NOT_SYNCED_RETRY_MS = 15 * 60 * 1000;\n\nexport type NotSyncedFile = {\n path: string;\n reason: \"quota_exceeded\" | \"file_too_large\" | \"owner_only\" | \"protected\";\n /**\n * Hash of the refused bytes; absent for walker-level oversize skips and\n * for refused deletes/mkdirs/rmdirs.\n */\n hash?: string;\n size?: number;\n /** The space's byte limit, for quota_exceeded. */\n limitBytes?: number;\n /** When these bytes were (last) refused. */\n at: string;\n};\n\nexport function readNotSynced(stateDb: StateDb): NotSyncedFile[] {\n const raw = stateDb.getMeta(STATE_KEY_NOT_SYNCED);\n if (!raw) return [];\n try {\n const parsed = JSON.parse(raw) as unknown;\n return Array.isArray(parsed) ? (parsed as NotSyncedFile[]) : [];\n } catch {\n return [];\n }\n}\n\n/** The relay's per-file refusal codes; anything else still fails the scan. */\nfunction refusalOf(err: unknown): { reason: NotSyncedFile[\"reason\"]; limitBytes?: number } | null {\n if (!(err instanceof RelayError)) return null;\n const body = err.body as { error?: unknown; limitBytes?: unknown } | null;\n if (body?.error === \"quota_exceeded\") {\n return {\n reason: \"quota_exceeded\",\n ...(typeof body.limitBytes === \"number\" ? { limitBytes: body.limitBytes } : {}),\n };\n }\n if (body?.error === \"file_too_large\") return { reason: \"file_too_large\" };\n return null;\n}\n\nfunction sha256OfBuffer(bytes: Buffer | Uint8Array): string {\n return `sha256:${createHash(\"sha256\").update(bytes).digest(\"hex\")}`;\n}\n\nexport type ScanAndPushResult = {\n /** Bytes uploaded this cycle. */\n uploaded: number;\n /** Hashes that were already on the relay (no PUT needed). */\n reused: number;\n /** Change events submitted (writes + deletes + mkdir + rmdir). */\n submitted: ReadonlyArray<ChangeEvent>;\n /** Warnings from the walker (symlinks / oversize / invalid) +\n * mid-scan mutations detected before upload. */\n warnings: ReadonlyArray<{ path: string; reason: string }>;\n};\n\n/**\n * Pure-read diff: what would scanAndPush submit right now, if called?\n * Hashes every visible file (cost: one read pass), compares against\n * state DB. No relay calls, no writes. Both scanAndPush and `tapd\n * status` use this — keeps the \"is sync clean?\" gate honest because\n * status sees exactly what the next push will see.\n */\nexport type LocalDiff = {\n writes: ReadonlyArray<{\n path: string;\n size: number;\n hash: string;\n baseChangeId?: string;\n }>;\n deletes: ReadonlyArray<{ path: string; baseChangeId?: string }>;\n mkdirs: ReadonlyArray<string>;\n /** Explicit rmdirs carry baseChangeId; synthesized ones don't. */\n rmdirs: ReadonlyArray<{ path: string; baseChangeId?: string }>;\n /**\n * Tracked paths the walker no longer sees but that still exist on disk\n * (newly ignored, oversize, symlinked, under an import mount, …). They\n * stop being tracked locally; they are NEVER turned into remote deletes.\n */\n untracked: ReadonlyArray<string>;\n warnings: ReadonlyArray<{ path: string; reason: string }>;\n};\n\n/**\n * \"Is this exact path still on disk?\" — by directory listing, not lstat,\n * so a case-only rename on a case-insensitive volume (Notes.md → notes.md)\n * still reads as the old name being gone. Listings are cached per call.\n */\nfunction onDiskChecker(rootDir: string): (posixPath: string) => boolean {\n const listings = new Map<string, Set<string> | null>();\n return (posixPath) => {\n const i = posixPath.lastIndexOf(\"/\");\n const dir = i < 0 ? \"\" : posixPath.slice(0, i);\n let names = listings.get(dir);\n if (names === undefined) {\n try {\n names = new Set(readdirSync(join(rootDir, dir)));\n } catch {\n names = null;\n }\n listings.set(dir, names);\n }\n return names !== null && names.has(posixPath.slice(i + 1));\n };\n}\n\nexport async function computeLocalDiff(opts: {\n rootDir: string;\n stateDb: StateDb;\n ignore: Ignore;\n /** Import mount points to hard-exclude from the scan. See walk(). */\n excludePaths?: ReadonlyArray<string>;\n}): Promise<LocalDiff> {\n const { rootDir, stateDb, ignore, excludePaths } = opts;\n const scan = walk(rootDir, { ignore, excludePaths });\n\n // Writes — every file whose hash differs from the state DB's last-seen.\n const writes: Array<{ path: string; size: number; hash: string; baseChangeId?: string }> = [];\n for (const f of scan.files) {\n const hash = await hashFile(join(rootDir, f.path));\n const prev = stateDb.getPath(f.path);\n if (prev?.lastSeenHash === hash) continue;\n const baseChangeId = prev?.lastAppliedChangeId ?? undefined;\n writes.push({ path: f.path, size: f.size, hash, ...(baseChangeId ? { baseChangeId } : {}) });\n }\n\n // Deletes + rmdirs. A tracked path is \"still on disk\" iff it appears\n // as a file in this scan OR appears as ANY directory in this scan\n // (including non-empty ones — `directoriesScanned` covers both).\n // Using `emptyDirs` alone would mis-classify a previously-empty\n // tracked dir that now contains files as \"deleted\".\n const filesNow = new Set(scan.files.map((f) => f.path));\n const dirsNow = new Set(scan.directoriesScanned);\n const deletes: Array<{ path: string; baseChangeId?: string }> = [];\n const explicitRmdirs: Array<{ path: string; baseChangeId?: string }> = [];\n const removedPaths = new Set<string>();\n const untracked: string[] = [];\n const existsOnDisk = onDiskChecker(rootDir);\n for (const path of stateDb.listPaths()) {\n const prev = stateDb.getPath(path);\n if (!prev) continue;\n const stillOnDisk = filesNow.has(path) || dirsNow.has(path);\n if (stillOnDisk) continue;\n // Invisible to the walker but still on disk: a rule changed (e.g. a\n // new .tapignore line, or an older daemon's built-ins ignoring a file\n // a newer peer synced). Only a path that is really gone is a delete —\n // otherwise one peer's ignore rules would delete the file for everyone.\n if (existsOnDisk(path)) {\n untracked.push(path);\n continue;\n }\n const baseChangeId = prev.lastAppliedChangeId ?? undefined;\n if (prev.lastSeenHash === null) {\n explicitRmdirs.push({ path, ...(baseChangeId ? { baseChangeId } : {}) });\n } else {\n deletes.push({ path, ...(baseChangeId ? { baseChangeId } : {}) });\n }\n removedPaths.add(path);\n }\n\n // Synthesize rmdir for ancestor dirs of removed paths that are no\n // longer on disk. The walker only emits `emptyDirs` for currently-\n // visible dirs, so a `rm -rf docs/` that wiped a previously-untracked\n // (because non-empty) directory tree wouldn't produce an rmdir for\n // `docs/` without this step.\n const synthesizedRmdirs = new Set<string>();\n for (const path of removedPaths) {\n let parent = parentDir(path);\n while (parent !== null) {\n if (filesNow.has(parent) || dirsNow.has(parent)) break;\n if (synthesizedRmdirs.has(parent)) break;\n // An ignored parent dir that still exists is not removed either.\n if (existsOnDisk(parent)) break;\n if (!removedPaths.has(parent)) synthesizedRmdirs.add(parent);\n parent = parentDir(parent);\n }\n }\n // Deepest-first so peer-side best-effort rmSync succeeds.\n const synthDeepest = [...synthesizedRmdirs].sort(\n (a, b) => b.split(\"/\").length - a.split(\"/\").length,\n );\n\n // Empty-dir markers: anything in scan.emptyDirs that doesn't already\n // have a state entry.\n const mkdirs: string[] = [];\n for (const dir of scan.emptyDirs) {\n if (stateDb.getPath(dir)) continue;\n mkdirs.push(dir);\n }\n\n // Combine explicit + synthesized rmdirs into one stream; explicit\n // first so they retain their baseChangeId at submit time, then\n // synthesized deepest-first so peer rmSync(recursive:false) succeeds.\n const rmdirs = [\n ...explicitRmdirs,\n ...synthDeepest.map((path) => ({ path })),\n ];\n\n return {\n writes,\n deletes,\n mkdirs,\n rmdirs,\n untracked,\n warnings: scan.warnings.map((w) => ({ path: w.path, reason: w.reason })),\n };\n}\n\nexport async function scanAndPush(opts: {\n rootDir: string;\n client: RelayClient;\n stateDb: StateDb;\n ignore: Ignore;\n /** Import mount points to hard-exclude from the scan. See walk(). */\n excludePaths?: ReadonlyArray<string>;\n /**\n * Per-path intent resolver (Context Layer § 4.2). Given the batch's changed\n * paths, returns each path's intent + attribution (`confirmed`/`window`).\n * Best-effort: the daemon wraps it so a parse/relay hiccup yields an empty\n * map and the batch simply carries no intent.\n */\n resolveIntents?: (\n changedPaths: ReadonlyArray<string>,\n ) => Promise<Map<string, IntentAssignment>>;\n}): Promise<ScanAndPushResult> {\n const { rootDir, client, stateDb, resolveIntents } = opts;\n const diff = await computeLocalDiff(opts);\n\n // Local-divergence signal (Context Layer § 3.2): a `.conflict-from.*` sidecar\n // on disk means a remote write displaced an unpushed local edit. The sidecars\n // persist until resolved, so reading them here captures every displaced change\n // id for a path — \"the signal is never lost\" without extra daemon state.\n const divergedByPath = new Map<string, string[]>();\n for (const { path, sidecarPath } of findConflictSidecars(rootDir)) {\n const parsed = parseConflictSidecar(basename(sidecarPath));\n if (!parsed) continue;\n const list = divergedByPath.get(path) ?? [];\n if (!list.includes(parsed.chgId)) list.push(parsed.chgId);\n divergedByPath.set(path, list);\n }\n\n // Newly ignored (or otherwise invisible) paths still on disk: stop\n // tracking them locally. No remote delete — see computeLocalDiff.\n for (const path of diff.untracked) stateDb.deletePath(path);\n\n // Size refusals. Walker-level oversize files never reach the relay; a\n // file the relay already refused is held (not re-uploaded) while its\n // bytes are unchanged and the backoff hasn't elapsed.\n const nowMs = Date.now();\n const nowIso = new Date(nowMs).toISOString();\n const prevNotSynced = new Map(readNotSynced(stateDb).map((s) => [s.path, s]));\n const notSynced: NotSyncedFile[] = [];\n for (const w of diff.warnings) {\n if (w.reason !== \"too_large\") continue;\n notSynced.push({ path: w.path, reason: \"file_too_large\", at: prevNotSynced.get(w.path)?.at ?? nowIso });\n }\n // A refused write is held while its bytes are unchanged; a refused\n // delete/mkdir/rmdir (no hash) while the path is still being removed or\n // created — both until the backoff elapses.\n const isHeld = (path: string, hash: string | undefined): boolean => {\n const prev = prevNotSynced.get(path);\n const held =\n prev !== undefined &&\n prev.hash === hash &&\n nowMs - Date.parse(prev.at) < NOT_SYNCED_RETRY_MS;\n if (held) notSynced.push(prev);\n return held;\n };\n const writes = diff.writes.filter((w) => !isHeld(w.path, w.hash));\n\n // Dedupe writes by hash so identical content uploads exactly once.\n const uniqueHashes = new Map<string, { path: string; size: number; hash: string }>();\n for (const w of writes) if (!uniqueHashes.has(w.hash)) uniqueHashes.set(w.hash, w);\n // Hashes the relay refused this scan (quota / size) — skipped, not fatal.\n const refusedHashes = new Map<string, { reason: NotSyncedFile[\"reason\"]; limitBytes?: number }>();\n\n // Files whose content changed between the walker's streaming hash and\n // our `readFileSync` for PUT. We skip those — the next watcher tick\n // re-hashes with the current bytes.\n const mutatedPaths = new Set<string>();\n const skipWarnings: Array<{ path: string; reason: string }> = [];\n\n let uploaded = 0;\n let reused = 0;\n // Upload unique objects with bounded concurrency — serial round-trips made\n // a many-file scan take minutes against a remote relay.\n await mapPool([...uniqueHashes.values()], UPLOAD_CONCURRENCY, async (f) => {\n let prep;\n try {\n prep = await client.prepareUpload({ hash: f.hash, size: f.size });\n } catch (err) {\n // One over-quota / too-large file must not sink the whole batch.\n const refusal = refusalOf(err);\n if (!refusal) throw err;\n refusedHashes.set(f.hash, refusal);\n return;\n }\n if (prep.exists) {\n reused += 1;\n return;\n }\n const bytes = readFileSync(join(rootDir, f.path));\n const actual = sha256OfBuffer(bytes);\n if (actual !== f.hash) {\n mutatedPaths.add(f.path);\n skipWarnings.push({ path: f.path, reason: `mutated_during_scan:${actual}` });\n return;\n }\n await client.putObjectBytes(prep.uploadUrl, bytes);\n await client.completeUpload({ hash: f.hash, size: f.size });\n uploaded += 1;\n });\n\n // Build the submit batch. Skip writes whose dedup-representative hash\n // didn't actually upload (the file mutated mid-scan).\n const events: SubmitChangeEvent[] = [];\n for (const w of writes) {\n if (mutatedPaths.has(w.path)) continue;\n const repPath = (uniqueHashes.get(w.hash) ?? w).path;\n if (mutatedPaths.has(repPath)) continue;\n const refusal = refusedHashes.get(w.hash);\n if (refusal) {\n // Not submitted, so its state stays un-synced; recorded for status.\n notSynced.push({ path: w.path, hash: w.hash, size: w.size, at: nowIso, ...refusal });\n skipWarnings.push({ path: w.path, reason: refusal.reason });\n console.warn(`tapd: not syncing ${w.path} (${w.size} bytes): ${refusal.reason}`);\n continue;\n }\n const diverged = divergedByPath.get(w.path);\n events.push({\n op: \"write\",\n path: w.path,\n hash: w.hash,\n size: w.size,\n ...(w.baseChangeId ? { baseChangeId: w.baseChangeId } : {}),\n ...(diverged && diverged.length > 0 ? { meta: { divergedFrom: diverged } } : {}),\n });\n }\n for (const d of diff.deletes) {\n if (isHeld(d.path, undefined)) continue;\n events.push({\n op: \"delete\",\n path: d.path,\n ...(d.baseChangeId ? { baseChangeId: d.baseChangeId } : {}),\n });\n }\n // Explicit rmdirs (with baseChangeId) come first then synth-deepest-\n // first, which is what we want on the wire too — emit in that order.\n for (const r of diff.rmdirs) {\n if (isHeld(r.path, undefined)) continue;\n events.push({ op: \"rmdir\", path: r.path, ...(r.baseChangeId ? { baseChangeId: r.baseChangeId } : {}) });\n }\n for (const path of diff.mkdirs) {\n if (isHeld(path, undefined)) continue;\n events.push({ op: \"mkdir\", path });\n }\n\n // Stamp each event with its resolved intent + attribution (the \"why\").\n if (resolveIntents && events.length > 0) {\n const assignments = await resolveIntents(events.map((e) => e.path));\n for (const ev of events) {\n const a = assignments.get(ev.path);\n if (!a) continue;\n ev.intentId = a.intentId;\n ev.meta = { ...ev.meta, attribution: a.attribution };\n }\n }\n\n // Persist before submitting: status must see refusals even if submit fails.\n if (notSynced.length > 0 || prevNotSynced.size > 0) {\n stateDb.setMeta(STATE_KEY_NOT_SYNCED, JSON.stringify(notSynced));\n }\n\n let submitted: ReadonlyArray<ChangeEvent> = [];\n if (events.length > 0) {\n const result = await client.submitChanges(events);\n submitted = result.events;\n // Per-event refusals (a protected path; the rest of the batch landed).\n // Not applied, so the path's state stays un-synced; held like a size\n // refusal so it isn't resubmitted every tick.\n for (const r of result.refused ?? []) {\n const ev = events[r.index];\n const reason = r.reason === \"owner_only\" ? \"owner_only\" : \"protected\";\n notSynced.push({\n path: r.path,\n reason,\n ...(ev?.op === \"write\" && ev.hash ? { hash: ev.hash, size: ev.size } : {}),\n at: nowIso,\n });\n skipWarnings.push({ path: r.path, reason });\n console.warn(`tapd: not syncing ${r.op} ${r.path}: ${reason === \"owner_only\" ? \"only the space's owner can change it\" : \"protected path\"}`);\n }\n if (result.refused?.length) stateDb.setMeta(STATE_KEY_NOT_SYNCED, JSON.stringify(notSynced));\n const now = new Date().toISOString();\n for (const ev of submitted) {\n if (ev.op === \"delete\" || ev.op === \"rmdir\") {\n stateDb.deletePath(ev.path);\n continue;\n }\n stateDb.upsertPath({\n path: ev.path,\n lastAppliedChangeId: ev.id,\n lastSeenHash: ev.op === \"write\" ? ev.hash : null,\n localDirty: false,\n lastScanAt: now,\n });\n }\n // Advance cursor past our own writes so the apply loop doesn't\n // re-fetch them as \"remote\" events. (Empty when every event was refused.)\n const lastId = submitted[submitted.length - 1]?.id;\n if (lastId && cursorLessThan(stateDb.getCursor(), lastId)) {\n stateDb.setCursor(lastId);\n }\n }\n\n return {\n uploaded,\n reused,\n submitted,\n warnings: [...diff.warnings, ...skipWarnings],\n };\n}\n\n/** Returns the immediate parent of a POSIX path, or null at the root. */\nfunction parentDir(posix: string): string | null {\n const i = posix.lastIndexOf(\"/\");\n if (i < 0) return null;\n return posix.slice(0, i);\n}\n\nfunction cursorLessThan(a: string, b: string): boolean {\n const prefix = \"chg_\";\n const an = a.startsWith(prefix) ? BigInt(a.slice(prefix.length)) : 0n;\n const bn = b.startsWith(prefix) ? BigInt(b.slice(prefix.length)) : 0n;\n return an < bn;\n}\n","/**\n * `tapd start` — the daemon orchestrator.\n *\n * A daemon = N subscriptions (apply loops) + 1 writer (watcher + scan).\n * - The **primary** binding gets a subscription (mode \"primary\") AND\n * the writer: chokidar → debounced `scanAndPush` pushes local diffs\n * upstream.\n * - Each **import** gets an apply-only subscription (mode \"mirror\")\n * materializing another binding into a subdirectory, read-only. No\n * writer — a mirror never pushes.\n *\n * The subscription machinery (SSE + poll + apply-mutex) lives in\n * subscription.ts; this file wires the pieces together and owns the\n * primary writer.\n *\n * Critical invariant: import mount points are HARD-excluded from the\n * primary writer (watcher `ignored` + walker `excludePaths`), so a\n * mirrored subtree is never re-uploaded into the primary's own binding\n * — the \"accidental re-share\".\n */\n\nimport { join } from \"node:path\";\nimport chokidar, { type FSWatcher } from \"chokidar\";\nimport { type ApplyResult } from \"./applier.js\";\nimport { readBindingConfig } from \"./binding-config.js\";\nimport {\n importStateDbPath,\n loadImportMounts,\n primaryStateDbPath,\n validateImports,\n type ImportSpec,\n type ImportValidationError,\n} from \"./imports.js\";\nimport { RelayClient } from \"./relay-client.js\";\nimport type { ActiveSessionOptions } from \"./claude-code.js\";\nimport { resolveIntentAssignments, type IntentAssignment } from \"./intent-association.js\";\nimport { openStateDb, type StateDb } from \"./state-db.js\";\nimport {\n startSubscription,\n type SubscriptionHandle,\n} from \"./subscription.js\";\nimport { loadIgnore, isAlwaysExcluded, isUnderAny } from \"./tapignore.js\";\nimport { scanAndPush, type ScanAndPushResult } from \"./uploader.js\";\n\nexport type StartOptions = {\n rootDir: string;\n pollSeconds?: number;\n /** Quiescence window before the watcher triggers a rescan. */\n debounceMs?: number;\n /**\n * Read-only imports to mount into subdirectories. When omitted, the\n * daemon loads them from `.rig/tap/imports/*.local.json` (the normal\n * path). Pass explicitly to bypass disk loading (tests / programmatic).\n * Each carries its own binding + capability token.\n */\n imports?: ReadonlyArray<ImportSpec>;\n /** Test seam — override fetch (passed to RelayClient + SSE subscriber). */\n fetch?: typeof fetch;\n /** Test seam — disable the chokidar watcher (poll loop only). */\n noWatch?: boolean;\n /** Test seam — disable SSE (poll-only mode). */\n noSse?: boolean;\n /** SSE watchdog window in ms. Default 45s. */\n sseHeartbeatTimeoutMs?: number;\n /**\n * Turn off Claude Code intent capture (Context Layer). When off, pushed\n * changes carry no `intentId`. Default on; capture is best-effort and never\n * blocks sync.\n */\n disableIntentCapture?: boolean;\n /** Test seam — override active-session resolution (projectsRoot, now, idleMs). */\n intentSessionOpts?: ActiveSessionOptions;\n /**\n * Called when the PRIMARY binding is reported deleted by the relay (410\n * `binding_deleted`). By the time this fires, the primary subscription\n * has already stopped itself (poll + SSE) and written\n * `.rig/sync-paused.json`; the writer (watcher + scheduled scans) is\n * ALSO already stopped before this callback runs. `bin.ts`'s CLI\n * entrypoint uses this to exit the process cleanly.\n */\n onBindingDeleted?: (info: { bindingId: string; deletedAt: string }) => void;\n};\n\n/** A live import mount — exposed for status + tests. */\nexport type ImportHandle = {\n name: string;\n path: string;\n applyNow(): Promise<ApplyResult>;\n stateDb: StateDb;\n};\n\nexport type DaemonHandle = {\n /** Cleanly shut down the writer + every subscription + close state DBs. */\n stop(): Promise<void>;\n /** Force one primary apply cycle now. No-op for an import-only daemon. */\n applyNow(): Promise<ApplyResult>;\n /** Force one upstream scan+push now. No-op for an import-only daemon. */\n scanNow(): Promise<ScanAndPushResult>;\n /** Primary state DB (or first mount's, if import-only). Do NOT close it. */\n stateDb: StateDb;\n /** Live import mounts, in declaration order (valid ones only). */\n imports: ImportHandle[];\n};\n\nconst DEFAULT_POLL_SECONDS = 3;\nconst DEFAULT_DEBOUNCE_MS = 300;\n\nconst NOOP_APPLY: ApplyResult = {\n applied: 0,\n echoSkipped: 0,\n errored: 0,\n events: [],\n errors: [],\n};\nconst NOOP_SCAN: ScanAndPushResult = {\n uploaded: 0,\n reused: 0,\n submitted: [],\n warnings: [],\n};\n\nexport async function start(opts: StartOptions): Promise<DaemonHandle> {\n const config = readBindingConfig(opts.rootDir);\n\n // Imports come from the caller (tests/programmatic) or, normally, from\n // the mount files on disk. Broken mount files degrade — they're logged\n // and skipped, never fatal.\n let importSpecs: ReadonlyArray<ImportSpec>;\n let loaderBroken: ImportValidationError[] = [];\n if (opts.imports) {\n importSpecs = opts.imports;\n } else {\n const loaded = loadImportMounts(opts.rootDir);\n importSpecs = loaded.ready;\n loaderBroken = loaded.broken;\n }\n for (const e of loaderBroken) {\n console.error(`tapd import \"${e.name}\": skipped — ${e.reason}`);\n }\n\n if (!config && importSpecs.length === 0) {\n throw new Error(\n `no binding config at ${opts.rootDir}/.rig/tap-binding.local.json and no imports — run \\`tapd init\\` first`,\n );\n }\n\n const pollSeconds = opts.pollSeconds ?? DEFAULT_POLL_SECONDS;\n const debounceMs = opts.debounceMs ?? DEFAULT_DEBOUNCE_MS;\n\n let stopped = false;\n\n // ────────── primary subscription + writer ──────────\n let primary: SubscriptionHandle | undefined;\n let watcher: FSWatcher | undefined;\n let scanTimer: ReturnType<typeof setTimeout> | undefined;\n let scanInFlight: Promise<unknown> = Promise.resolve();\n let writerClient: RelayClient | undefined;\n const ignore = loadIgnore(opts.rootDir);\n\n // Build a per-path intent resolver for a push, bound to this client+stateDb.\n // Best-effort and observe-first (Context Layer § 2): any parse/relay error →\n // empty map, sync proceeds untouched.\n const intentResolverFor = (client: RelayClient, stateDb: StateDb) => {\n if (opts.disableIntentCapture) return undefined;\n return async (changedPaths: ReadonlyArray<string>): Promise<Map<string, IntentAssignment>> => {\n try {\n return await resolveIntentAssignments(\n { client, stateDb, rootDir: opts.rootDir, sessionOpts: opts.intentSessionOpts },\n changedPaths,\n );\n } catch (err) {\n console.error(\"tapd intent capture error:\", err);\n return new Map();\n }\n };\n };\n\n // Resolved at primary-startup; the writer reads it so the exclusion set\n // reflects exactly the imports that passed validation.\n let importPaths: string[] = [];\n\n if (config) {\n primary = await startSubscription({\n rootDir: opts.rootDir,\n config,\n stateDbPath: primaryStateDbPath(opts.rootDir),\n mode: \"primary\",\n fetch: opts.fetch,\n pollSeconds,\n noSse: opts.noSse,\n sseHeartbeatTimeoutMs: opts.sseHeartbeatTimeoutMs,\n onBindingDeleted: (info) => {\n // The subscription (download side) already stopped itself and\n // wrote the pause flag. Stop the writer (upload side) too — no\n // scheduled scan should fire against a rig that no longer exists.\n stopped = true;\n if (scanTimer) clearTimeout(scanTimer);\n if (watcher) void watcher.close();\n opts.onBindingDeleted?.({ bindingId: config.bindingId, deletedAt: info.deletedAt });\n },\n });\n }\n\n // ────────── validate imports ──────────\n // Reject illegal mounts (bad name/path, symlink escape, overlap) and\n // mounts over content the primary already tracks. Rejections are\n // logged and skipped — they never take down the daemon (gotcha #7).\n const primaryTrackedPaths = primary ? primary.stateDb.listPaths() : [];\n const { valid: validImports, errors: importErrors } = validateImports({\n rootDir: opts.rootDir,\n imports: importSpecs,\n primaryTrackedPaths,\n });\n for (const e of importErrors) {\n console.error(`tapd import \"${e.name}\" (${e.path}): skipped — ${e.reason}`);\n }\n importPaths = validImports.map((i) => i.path);\n\n // ────────── primary writer (bound only) ──────────\n if (config && primary) {\n const primaryStateDb = primary.stateDb;\n writerClient = new RelayClient({\n baseUrl: config.relayUrl,\n bindingId: config.bindingId,\n token: config.token,\n fetch: opts.fetch,\n });\n\n const scheduleScan = () => {\n if (stopped) return;\n if (scanTimer) clearTimeout(scanTimer);\n scanTimer = setTimeout(() => {\n scanInFlight = scanAndPush({\n rootDir: opts.rootDir,\n client: writerClient!,\n stateDb: primaryStateDb,\n ignore,\n excludePaths: importPaths,\n resolveIntents: intentResolverFor(writerClient!, primaryStateDb),\n }).catch((err) => {\n console.error(\"tapd scan error:\", err);\n });\n }, debounceMs);\n };\n\n if (!opts.noWatch) {\n watcher = chokidar.watch(opts.rootDir, {\n ignoreInitial: true,\n // Hard-exclude tap metadata AND every import mount point — the\n // latter so a mirrored subtree never triggers a re-upload into\n // the primary binding.\n ignored: (path: string) => {\n const rel = path.startsWith(opts.rootDir)\n ? path.slice(opts.rootDir.length).replace(/^[/\\\\]/, \"\")\n : path;\n if (isAlwaysExcluded(rel)) return true;\n if (importPaths.length > 0 && isUnderAny(rel, importPaths)) return true;\n return false;\n },\n awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 },\n atomic: true,\n });\n for (const ev of [\"add\", \"change\", \"unlink\", \"addDir\", \"unlinkDir\"] as const) {\n watcher.on(ev, () => scheduleScan());\n }\n }\n\n // Initial scan: the primary subscription already applied remote\n // changes before returning, so now push local/offline edits. Import\n // paths are excluded so we never upload another binding's content.\n scanInFlight = scanAndPush({\n rootDir: opts.rootDir,\n client: writerClient,\n stateDb: primaryStateDb,\n ignore,\n excludePaths: importPaths,\n resolveIntents: intentResolverFor(writerClient, primaryStateDb),\n }).catch((err) => {\n console.error(\"tapd initial scan error:\", err);\n });\n await scanInFlight;\n }\n\n // ────────── import (mirror) subscriptions ──────────\n const imports: ImportHandle[] = [];\n const mirrorSubs: SubscriptionHandle[] = [];\n for (const imp of validImports) {\n try {\n const sub = await startSubscription({\n rootDir: join(opts.rootDir, imp.path),\n config: { bindingId: imp.bindingId, relayUrl: imp.relayUrl, token: imp.token },\n stateDbPath: importStateDbPath(opts.rootDir, imp.name),\n mode: \"mirror\",\n fetch: opts.fetch,\n pollSeconds,\n noSse: opts.noSse,\n sseHeartbeatTimeoutMs: opts.sseHeartbeatTimeoutMs,\n });\n mirrorSubs.push(sub);\n imports.push({\n name: imp.name,\n path: imp.path,\n applyNow: () => sub.applyNow(),\n stateDb: sub.stateDb,\n });\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n console.error(`tapd import \"${imp.name}\" (${imp.path}): failed to start — ${reason}`);\n }\n }\n\n const stateDb = primary?.stateDb ?? imports[0]?.stateDb;\n if (!stateDb) {\n // Every import failed to start and there's no primary — nothing to do.\n throw new Error(\"tapd start: no primary binding and no import mounted successfully\");\n }\n\n return {\n async stop() {\n stopped = true;\n // Writer first — it shares the primary's state DB, which the\n // primary subscription's stop() will close.\n if (scanTimer) clearTimeout(scanTimer);\n await Promise.allSettled([scanInFlight]);\n if (watcher) await watcher.close();\n // Subscriptions (each closes its own state DB).\n const subs: Array<Promise<void>> = [];\n if (primary) subs.push(primary.stop());\n for (const sub of mirrorSubs) subs.push(sub.stop());\n await Promise.allSettled(subs);\n },\n async applyNow() {\n return primary ? primary.applyNow() : NOOP_APPLY;\n },\n async scanNow() {\n if (!config || !primary || !writerClient) return NOOP_SCAN;\n return scanAndPush({\n rootDir: opts.rootDir,\n client: writerClient,\n stateDb: primary.stateDb,\n ignore,\n excludePaths: importPaths,\n resolveIntents: intentResolverFor(writerClient, primary.stateDb),\n });\n },\n stateDb,\n imports,\n };\n}\n","/**\n * Claude Code context provider (Context Layer § 4, docs/context-layer-design-doc.md).\n *\n * The capture doctrine: the fs watcher is the write ground-truth; the transcript\n * is the authoritative payload we read to derive the \"why\". This module reads\n * Claude Code's own session JSONL — no hooks required for a first cut — and\n * splits each transcript into **work-bouts** (one DerivedSession per bout):\n * a Claude Code session file is appended to across resumes, so one file spans\n * days. We cut a new bout when the gap between consecutive timestamps exceeds\n * BOUT_GAP_MS — so each intent has a real, minutes-long window and old bouts\n * correctly go idle, instead of one ever-growing \"session\".\n *\n * Grounded in real logs (CC 2.1.165):\n * - one file per session at ~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl\n * - `{type:\"ai-title\", aiTitle}` → a model-generated session title (free!)\n * - `{type:\"user\", message.content}` → user prompts (title fallback)\n * - `{type:\"assistant\", message:{model, content:[{type:\"tool_use\", name, input}]}}`\n * → model + Read/Grep/Glob file reads\n * - per-line `timestamp`/`cwd` → session window + sanity check\n *\n * Reads are filtered to in-rig paths and relativized — staleness only makes\n * sense for files this binding actually tracks.\n */\n\nimport { spawnSync } from \"node:child_process\";\nimport { existsSync, readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join, relative, sep } from \"node:path\";\nimport type { IntentDetail } from \"@tap/core\";\n\nexport type DerivedSession = {\n sessionId: string;\n title: string;\n agent: \"claude-code\";\n model: string | null;\n firstTs: string | null;\n lastTs: string | null;\n /** rig-relative POSIX paths the session read (in-rig only, deduped). */\n readPaths: string[];\n /**\n * rig-relative POSIX paths the session WROTE per its transcript write tools\n * (in-rig, deduped). Powers `confirmed` attribution: a change to one of these\n * is provably this session's. Bash-mediated writes never appear here.\n */\n writePaths: string[];\n /** Display receipts: model, prompt snippet, token cost, tool-use counts. */\n detail: IntentDetail;\n /** Absolute path to the transcript — used for Intent.payloadRef. */\n transcriptPath: string;\n};\n\n/** Encode a cwd the way Claude Code names its project dir: '/' and '.' → '-'. */\nexport function claudeProjectDirName(rootDir: string): string {\n return rootDir.replace(/[/.]/g, \"-\");\n}\n\nexport function defaultClaudeProjectsRoot(): string {\n return join(homedir(), \".claude\", \"projects\");\n}\n\nconst TITLE_MAX = 120;\n// A gap longer than this between consecutive timestamps starts a new work-bout.\nconst BOUT_GAP_MS = 30 * 60 * 1000;\nconst READ_TOOLS = new Set([\"Read\", \"Grep\", \"Glob\"]);\n// File-write tool names in a Claude Code transcript. Matches Entire's set\n// (Write/Edit/NotebookEdit + the Zed ACP bridge's mcp__acp__*), plus MultiEdit\n// (top-level `file_path`), which Entire omits. Bash writes never appear here —\n// they fall to `window` attribution downstream.\nconst WRITE_TOOLS = new Set([\n \"Write\",\n \"Edit\",\n \"MultiEdit\",\n \"NotebookEdit\",\n \"mcp__acp__Write\",\n \"mcp__acp__Edit\",\n]);\n\ntype Line = Record<string, unknown>;\n\nfunction asObj(v: unknown): Record<string, unknown> | null {\n return typeof v === \"object\" && v !== null ? (v as Record<string, unknown>) : null;\n}\n\n/** Pull plain prompt text out of a user line's message.content (string or blocks). */\nfunction userPromptText(line: Line): string | null {\n const msg = asObj(line.message);\n if (!msg) return null;\n const content = msg.content;\n if (typeof content === \"string\") return content.trim() || null;\n if (Array.isArray(content)) {\n for (const block of content) {\n const b = asObj(block);\n if (b && b.type === \"text\" && typeof b.text === \"string\" && b.text.trim()) {\n return b.text.trim();\n }\n }\n }\n return null;\n}\n\n/**\n * Absolute paths a tool_use in `tools` touched. Reads use file_path/path;\n * writes use file_path/notebook_path (matching Claude Code's input shapes).\n */\nfunction toolPathsFromAssistant(line: Line, tools: Set<string>): string[] {\n const msg = asObj(line.message);\n if (!msg || !Array.isArray(msg.content)) return [];\n const out: string[] = [];\n for (const block of msg.content) {\n const b = asObj(block);\n if (!b || b.type !== \"tool_use\" || typeof b.name !== \"string\") continue;\n if (!tools.has(b.name)) continue;\n const input = asObj(b.input);\n if (!input) continue;\n const p = input.file_path ?? input.notebook_path ?? input.path;\n if (typeof p === \"string\" && p) out.push(p);\n }\n return out;\n}\n\n/** Relativize an absolute path to rootDir as POSIX, or null if not under it. */\nfunction toRigRelative(absPath: string, rootDir: string): string | null {\n const rel = relative(rootDir, absPath);\n if (rel === \"\" || rel.startsWith(\"..\") || (sep !== \"/\" && rel.includes(\"..\" + sep))) {\n return null;\n }\n return sep === \"/\" ? rel : rel.replaceAll(sep, \"/\");\n}\n\n/** True if absPath is `base` itself or lives underneath it. */\nfunction isWithin(absPath: string, base: string): boolean {\n const rel = relative(base, absPath);\n return rel === \"\" || !(rel.startsWith(\"..\") || (sep !== \"/\" && rel.includes(\"..\" + sep)));\n}\n\ntype BoutAcc = {\n index: number;\n firstTs: string | null;\n lastTs: string | null;\n model: string | null;\n firstPrompt: string | null;\n reads: Set<string>;\n writes: Set<string>;\n tokens: number;\n tools: Map<string, number>;\n};\n\n// Store a fuller opening prompt so the UI can offer \"show full prompt\" — the\n// collapsed view still clamps to a few lines. (The truly full prompt + the\n// per-edit instruction live in the transcript; that's rung-3 association.)\nconst PROMPT_SNIPPET_MAX = 2000;\nfunction num(v: unknown): number {\n return typeof v === \"number\" && Number.isFinite(v) ? v : 0;\n}\n/** All tool_use names in an assistant line (for usage counts). */\nfunction toolNamesFromAssistant(line: Line): string[] {\n const msg = asObj(line.message);\n if (!msg || !Array.isArray(msg.content)) return [];\n const out: string[] = [];\n for (const block of msg.content) {\n const b = asObj(block);\n if (b && b.type === \"tool_use\" && typeof b.name === \"string\") out.push(b.name);\n }\n return out;\n}\n\nexport type ParseOptions = {\n boutGapMs?: number;\n /**\n * The cwd this transcript is expected to have been recorded in. Project-dir\n * names are a lossy encoding ('/' and '.' both → '-'), so two different cwds\n * can share one dir; when a line states its own `cwd`, we trust it over the\n * dir name and drop the file if it isn't this base (or inside it).\n */\n expectedCwd?: string;\n};\n\n/**\n * Parse one transcript file into one DerivedSession **per work-bout**. A bout\n * boundary is a timestamp gap longer than BOUT_GAP_MS. Tolerant: malformed\n * lines are skipped. Returns [] if the file can't be read. A single-bout file\n * keeps the plain session id; split files get `#<n>` suffixes.\n *\n * `rootDir` is the base paths are relativized against — the rig root for the\n * rig's own transcripts, the worktree root for a worktree's (see listSessions).\n */\nexport function parseSessions(\n transcriptPath: string,\n rootDir: string,\n opts: ParseOptions = {},\n): DerivedSession[] {\n let raw: string;\n try {\n raw = readFileSync(transcriptPath, \"utf8\");\n } catch {\n return [];\n }\n const fileId = basenameNoExt(transcriptPath);\n const gap = opts.boutGapMs ?? BOUT_GAP_MS;\n\n let aiTitle: string | null = null; // file-global; the best human label\n const bouts: BoutAcc[] = [];\n let cur: BoutAcc | null = null;\n let prevMs: number | null = null;\n\n for (const rawLine of raw.split(\"\\n\")) {\n if (!rawLine) continue;\n let line: Line;\n try {\n line = JSON.parse(rawLine) as Line;\n } catch {\n continue;\n }\n\n // Collision guard: the transcript's own cwd outranks the dir name it lives\n // under. One mismatching line means this file isn't ours — drop it whole.\n if (opts.expectedCwd && typeof line.cwd === \"string\" && line.cwd) {\n if (!isWithin(line.cwd, opts.expectedCwd)) return [];\n }\n\n const type = line.type;\n if (type === \"ai-title\" && typeof line.aiTitle === \"string\") {\n aiTitle = line.aiTitle.trim() || aiTitle;\n continue;\n }\n\n const ts = typeof line.timestamp === \"string\" ? line.timestamp : null;\n const tsMs = ts ? Date.parse(ts) : null;\n const timed = tsMs !== null && Number.isFinite(tsMs);\n const contentLine = type === \"user\" || type === \"assistant\";\n // Start a new bout when: no bout yet and this line carries content/time, OR\n // an idle gap broke the current bout. (Inline, not via a closure, so cur\n // narrows cleanly below.)\n const gapBreak = timed && cur !== null && prevMs !== null && (tsMs as number) - prevMs > gap;\n if (cur === null ? timed || contentLine : gapBreak) {\n cur = { index: bouts.length, firstTs: null, lastTs: null, model: null, firstPrompt: null, reads: new Set(), writes: new Set(), tokens: 0, tools: new Map() };\n bouts.push(cur);\n }\n if (timed) prevMs = tsMs as number;\n if (cur === null) continue;\n\n if (type === \"user\") {\n if (!cur.firstPrompt) cur.firstPrompt = userPromptText(line);\n } else if (type === \"assistant\") {\n const msg = asObj(line.message);\n if (msg && typeof msg.model === \"string\") cur.model = msg.model;\n const usage = msg ? asObj(msg.usage) : null;\n if (usage) {\n // What the agent generated — output tokens only. (Input/cache_read are\n // the context it re-reads each turn, not work it produced.)\n cur.tokens += num(usage.output_tokens);\n }\n for (const name of toolNamesFromAssistant(line)) {\n cur.tools.set(name, (cur.tools.get(name) ?? 0) + 1);\n }\n for (const abs of toolPathsFromAssistant(line, READ_TOOLS)) {\n const rel = toRigRelative(abs, rootDir);\n if (rel) cur.reads.add(rel);\n }\n for (const abs of toolPathsFromAssistant(line, WRITE_TOOLS)) {\n const rel = toRigRelative(abs, rootDir);\n if (rel) cur.writes.add(rel);\n }\n }\n if (ts) {\n if (cur.firstTs === null || ts < cur.firstTs) cur.firstTs = ts;\n if (cur.lastTs === null || ts > cur.lastTs) cur.lastTs = ts;\n }\n }\n\n const single = bouts.length <= 1;\n return bouts.map((b) => ({\n sessionId: single ? fileId : `${fileId}#${b.index}`,\n // aiTitle is the best concise label; bouts are disambiguated by their window.\n title: aiTitle ?? (b.firstPrompt ? truncate(b.firstPrompt, TITLE_MAX) : fileId),\n agent: \"claude-code\" as const,\n model: b.model,\n firstTs: b.firstTs,\n lastTs: b.lastTs,\n readPaths: [...b.reads],\n writePaths: [...b.writes],\n detail: {\n model: b.model,\n promptSnippet: b.firstPrompt ? truncate(cleanPrompt(b.firstPrompt), PROMPT_SNIPPET_MAX) : null,\n tokens: b.tokens > 0 ? b.tokens : null,\n tools: [...b.tools.entries()]\n .map(([name, count]) => ({ name, count }))\n .sort((x, y) => y.count - x.count),\n },\n transcriptPath,\n }));\n}\n\n// ────────── session listing (mtime-cached) ──────────\n\ntype CacheEntry = { mtimeMs: number; size: number; bouts: DerivedSession[] };\nconst parseCache = new Map<string, CacheEntry>();\n\nexport type ListSessionsOptions = {\n /** Override the projects root (default ~/.claude/projects). For tests. */\n projectsRoot?: string;\n};\n\n/**\n * Linked git worktrees of `rootDir` (the rig's own path excluded). Agent\n * runners increasingly work in a worktree rather than the rig folder itself\n * (Emdash cuts one per task), and Claude Code names its project dir after the\n * *worktree* cwd — so the rig's own project dir sits empty while the real\n * transcripts live next door under a differently-encoded name.\n *\n * We enumerate forward only (ask git for paths, then encode them), because the\n * dir-name encoding is lossy and can't be reversed. Entirely best-effort: no\n * git binary, not a repo, non-zero exit or timeout → [], i.e. exactly the\n * single-dir behaviour we had before.\n */\nconst WORKTREE_TTL_MS = 30_000;\ntype WorktreeCacheEntry = { at: number; paths: string[] };\nconst worktreeCache = new Map<string, WorktreeCacheEntry>();\n\nfunction gitWorktrees(rootDir: string): string[] {\n const now = Date.now();\n const cached = worktreeCache.get(rootDir);\n // Cached with a short TTL so a hot scanAndPush loop doesn't spawn git per call.\n if (cached && now - cached.at < WORKTREE_TTL_MS) return cached.paths;\n\n let paths: string[] = [];\n try {\n const res = spawnSync(\"git\", [\"worktree\", \"list\", \"--porcelain\"], {\n cwd: rootDir,\n encoding: \"utf8\",\n timeout: 2000,\n windowsHide: true,\n });\n if (res.status === 0 && typeof res.stdout === \"string\") {\n paths = res.stdout\n .split(\"\\n\")\n .filter((l) => l.startsWith(\"worktree \"))\n .map((l) => l.slice(\"worktree \".length).trim())\n .filter((p) => p !== \"\" && p !== rootDir);\n }\n } catch {\n // no git, cwd gone, spawn refused → single-dir behaviour\n }\n worktreeCache.set(rootDir, { at: now, paths });\n return paths;\n}\n\n/** Work-bouts from one project dir, relativized against `base`. */\nfunction sessionsInDir(dir: string, base: string): DerivedSession[] {\n if (!existsSync(dir)) return [];\n let files: string[];\n try {\n files = readdirSync(dir).filter((f) => f.endsWith(\".jsonl\"));\n } catch {\n return [];\n }\n const out: DerivedSession[] = [];\n for (const f of files) {\n const abs = join(dir, f);\n let st: ReturnType<typeof statSync>;\n try {\n st = statSync(abs);\n } catch {\n continue;\n }\n const cached = parseCache.get(abs);\n if (cached && cached.mtimeMs === st.mtimeMs && cached.size === st.size) {\n out.push(...cached.bouts);\n continue;\n }\n const bouts = parseSessions(abs, base, { expectedCwd: base });\n parseCache.set(abs, { mtimeMs: st.mtimeMs, size: st.size, bouts });\n out.push(...bouts);\n }\n return out;\n}\n\n/**\n * All Claude Code work-bouts for `rootDir` **and each of its linked git\n * worktrees**, freshest last (one DerivedSession per bout, across all\n * transcript files). Parses are cached by (mtime, size) so unchanged\n * transcripts cost a stat, not a read.\n *\n * Every candidate dir carries its own base to relativize against — the rig root\n * for the rig's own dir, the worktree path for a worktree's. A worktree session\n * writes absolute paths *inside the worktree*, so relativizing those against the\n * rig root would drop all of them; against the worktree root they yield exactly\n * the same rig-relative paths (the layouts mirror), which is what lets the\n * write-set claim map match changes the watcher sees in the rig.\n */\nexport function listSessions(rootDir: string, opts: ListSessionsOptions = {}): DerivedSession[] {\n const projectsRoot = opts.projectsRoot ?? defaultClaudeProjectsRoot();\n const out: DerivedSession[] = [];\n const seen = new Set<string>();\n for (const base of [rootDir, ...gitWorktrees(rootDir)]) {\n const dir = join(projectsRoot, claudeProjectDirName(base));\n if (seen.has(dir)) continue; // two bases can encode to one dir\n seen.add(dir);\n out.push(...sessionsInDir(dir, base));\n }\n out.sort((a, b) => (a.lastTs ?? \"\").localeCompare(b.lastTs ?? \"\"));\n return out;\n}\n\nexport type ActiveSessionOptions = ListSessionsOptions & {\n /** Now, ms since epoch. Defaults to Date.now(). */\n now?: number;\n /** A session counts as active if its last activity is within this window. */\n idleMs?: number;\n};\n\nconst DEFAULT_IDLE_MS = 30 * 60 * 1000;\n\n/**\n * Sessions active within the idle window, sorted oldest→newest by last activity.\n * Used to build the write-set claim map (newest wins on overlap).\n */\nexport function recentSessions(rootDir: string, opts: ActiveSessionOptions = {}): DerivedSession[] {\n const now = opts.now ?? Date.now();\n const idleMs = opts.idleMs ?? DEFAULT_IDLE_MS;\n return listSessions(rootDir, opts)\n .filter((s) => {\n if (!s.lastTs) return false;\n const ms = Date.parse(s.lastTs);\n return Number.isFinite(ms) && now - ms <= idleMs;\n })\n .sort((a, b) => Date.parse(a.lastTs!) - Date.parse(b.lastTs!));\n}\n\n/**\n * The session a change right now most plausibly belongs to: the most-recently\n * active one whose last activity is within the idle window. null if none.\n */\nexport function activeSession(rootDir: string, opts: ActiveSessionOptions = {}): DerivedSession | null {\n const recent = recentSessions(rootDir, opts);\n return recent.length > 0 ? recent[recent.length - 1]! : null;\n}\n\nfunction basenameNoExt(p: string): string {\n const base = p.slice(p.lastIndexOf(sep === \"/\" ? \"/\" : sep) + 1);\n const dot = base.lastIndexOf(\".\");\n return dot > 0 ? base.slice(0, dot) : base;\n}\n\nfunction truncate(s: string, max: number): string {\n return s.length <= max ? s : s.slice(0, max - 1) + \"…\";\n}\n\n/**\n * Tidy a raw user prompt for display: drop markdown quote markers (the user\n * quoting a doc leaks a literal `> ` into the snippet) and collapse whitespace\n * so a multi-line prompt reads as one clean line.\n */\nfunction cleanPrompt(s: string): string {\n return s\n .split(\"\\n\")\n .map((l) => l.replace(/^\\s*>+\\s?/, \"\"))\n .join(\" \")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\n/** Test seam: drop the parse cache. */\nexport function _clearParseCache(): void {\n parseCache.clear();\n}\n\n/** Test seam: drop the worktree-list cache (normally TTL'd at 30s). */\nexport function _clearWorktreeCache(): void {\n worktreeCache.clear();\n}\n","/**\n * Intent association (Context Layer § 4 \"Association\").\n *\n * Joins the active Claude Code session to an intent on the relay and returns\n * its id, so scanAndPush can stamp it onto the batch. The mapping\n * sessionId → intentId is cached in the state DB's meta so a session creates\n * exactly one intent; the title/read-set are refreshed when they change.\n *\n * All of this is best-effort and observe-first (§ 2): callers wrap it so a\n * parse/relay hiccup never blocks sync — it just means a batch carries no\n * intent.\n */\n\nimport type { IntentReadPath } from \"@tap/core\";\nimport { recentSessions, type ActiveSessionOptions, type DerivedSession } from \"./claude-code.js\";\nimport type { RelayClient } from \"./relay-client.js\";\nimport type { StateDb } from \"./state-db.js\";\n\n/** How a change's intent link was decided (Context Layer § 4.2). */\nexport type IntentAssignment = { intentId: string; attribution: \"confirmed\" | \"window\" };\n\nconst META_INTENT_PREFIX = \"intent:\"; // intent:<sessionId> = intentId\nconst META_FP_PREFIX = \"intentfp:\"; // intentfp:<sessionId> = last-pushed fingerprint\n\nexport type AssociateDeps = {\n client: RelayClient;\n stateDb: StateDb;\n rootDir: string;\n /** Forwarded to activeSession() — projectsRoot override, now, idleMs. */\n sessionOpts?: ActiveSessionOptions;\n};\n\n/**\n * Decide, per changed path, which session's intent it belongs to and how\n * confidently (Context Layer § 4.2 \"the watcher grounds, the transcript\n * attributes\"):\n * - `confirmed` — the path is in some recent session's transcript write-set\n * (newest such session wins on overlap).\n * - `window` — no session claims it (Bash-mediated write, human edit) →\n * attributed to the most-recently-active session by recency alone.\n * Intents are created once per session actually used and cached. Returns an\n * empty map when no session is active (→ the batch carries no intent).\n */\nexport async function resolveIntentAssignments(\n deps: AssociateDeps,\n changedPaths: ReadonlyArray<string>,\n): Promise<Map<string, IntentAssignment>> {\n const out = new Map<string, IntentAssignment>();\n if (changedPaths.length === 0) return out;\n const sessions = recentSessions(deps.rootDir, deps.sessionOpts);\n if (sessions.length === 0) return out;\n\n // Claim map: write-path → session. Sessions are oldest→newest, so the last\n // writer of a path wins.\n const claim = new Map<string, DerivedSession>();\n for (const s of sessions) {\n for (const p of s.writePaths) claim.set(p, s);\n }\n const mostRecent = sessions[sessions.length - 1]!;\n\n const decided = new Map<string, { session: DerivedSession; attribution: \"confirmed\" | \"window\" }>();\n const used = new Set<DerivedSession>();\n for (const path of changedPaths) {\n const claimed = claim.get(path);\n const session = claimed ?? mostRecent;\n decided.set(path, { session, attribution: claimed ? \"confirmed\" : \"window\" });\n used.add(session);\n }\n\n // Create/refresh an intent for each session we actually used, then map paths.\n const intentBySession = new Map<DerivedSession, string>();\n for (const s of used) {\n intentBySession.set(s, await ensureIntentForSession(deps, s));\n }\n for (const [path, d] of decided) {\n out.set(path, { intentId: intentBySession.get(d.session)!, attribution: d.attribution });\n }\n return out;\n}\n\n/**\n * Ensure an intent exists for `session` and return its id. Creates once (caching\n * the id in state meta), then PATCHes title/read-set when the fingerprint moves.\n */\nexport async function ensureIntentForSession(\n deps: AssociateDeps,\n session: DerivedSession,\n): Promise<string> {\n const { client, stateDb } = deps;\n const readPaths = mapReadPaths(stateDb, session.readPaths);\n const fp = fingerprint(session.title, readPaths);\n\n const cachedId = stateDb.getMeta(META_INTENT_PREFIX + session.sessionId);\n if (cachedId) {\n if (stateDb.getMeta(META_FP_PREFIX + session.sessionId) !== fp) {\n await client.updateIntent(cachedId, { title: session.title, readPaths });\n stateDb.setMeta(META_FP_PREFIX + session.sessionId, fp);\n }\n return cachedId;\n }\n\n const intent = await client.createIntent({\n agent: session.agent,\n title: session.title,\n readPaths,\n detail: session.detail,\n contextCursor: stateDb.getCursor(),\n payloadRef: `local:${session.transcriptPath}`,\n });\n stateDb.setMeta(META_INTENT_PREFIX + session.sessionId, intent.id);\n stateDb.setMeta(META_FP_PREFIX + session.sessionId, fp);\n return intent.id;\n}\n\n/**\n * Map the session's in-rig read paths to {path, changeId} using the version the\n * daemon has applied for each. Reads of untracked/new files (no baseline) are\n * dropped — you can't be stale on a file the binding hasn't synced.\n */\nfunction mapReadPaths(stateDb: StateDb, paths: ReadonlyArray<string>): IntentReadPath[] {\n const out: IntentReadPath[] = [];\n for (const path of paths) {\n const changeId = stateDb.getPath(path)?.lastAppliedChangeId;\n if (changeId) out.push({ path, changeId });\n }\n return out;\n}\n\nfunction fingerprint(title: string, readPaths: ReadonlyArray<IntentReadPath>): string {\n const reads = readPaths.map((r) => `${r.path}@${r.changeId}`).sort().join(\",\");\n return `${title}\u0000${reads}`;\n}\n"],"mappings":"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","names":["mkdirSync","readFileSync","writeFileSync","dirname","existsSync","readFileSync","join","join","existsSync","readFileSync","join","join","resolve","createHash","readFileSync","join","createHash","isRecord","join","readFileSync","join","existsSync","mkdirSync","readdirSync","readFileSync","statSync","writeFileSync","dirname","join","existsSync","readFileSync","dirname","joinPath","isRecord","existsSync","readFileSync","revoke","dirname","joinPath","createHash","existsSync","mkdirSync","readdirSync","readFileSync","renameSync","rmSync","writeFileSync","dirname","join","sep","isAbsolute","relative","relative","isAbsolute","createHash","readFileSync","dirname","walk","join","readdirSync","sep","existsSync","resolve","rmSync","mkdirSync","tmp","writeFileSync","renameSync","createHash","readdirSync","readFileSync","join","basename","UPLOAD_CONCURRENCY","sha256OfBuffer","createHash","readdirSync","join","ignore","basename","list","readFileSync","join","existsSync","readdirSync","readFileSync","statSync","join","relative","sep","isWithin","sep","DEFAULT_POLL_SECONDS","ignore","stateDb","join"]}
|