@rigxyz/tapd 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -224,6 +224,43 @@ var RelayClient = class {
224
224
  );
225
225
  return expectJson(route, res);
226
226
  }
227
+ // ────────── intents (Context Layer) ──────────
228
+ /** Open an intent. Actor (device/user) is taken from the token, not the body. */
229
+ async createIntent(body) {
230
+ const route = "POST /v1/bindings/:id/intents";
231
+ const res = await this.fetch(this.bindingPath("/intents"), {
232
+ method: "POST",
233
+ headers: this.authHeaders({ "content-type": "application/json" }),
234
+ body: JSON.stringify(body)
235
+ });
236
+ return expectJson(route, res);
237
+ }
238
+ /** Update / close an intent. Actor-only — the relay rejects a non-actor. */
239
+ async updateIntent(intentId, body) {
240
+ const route = "PATCH /v1/bindings/:id/intents/:intentId";
241
+ const res = await this.fetch(
242
+ this.bindingPath(`/intents/${encodeURIComponent(intentId)}`),
243
+ {
244
+ method: "PATCH",
245
+ headers: this.authHeaders({ "content-type": "application/json" }),
246
+ body: JSON.stringify(body)
247
+ }
248
+ );
249
+ return expectJson(route, res);
250
+ }
251
+ async listIntents(opts) {
252
+ const route = "GET /v1/bindings/:id/intents";
253
+ const params = new URLSearchParams();
254
+ if (opts?.status) params.set("status", opts.status);
255
+ if (opts?.after) params.set("after", opts.after);
256
+ if (opts?.limit) params.set("limit", String(opts.limit));
257
+ const qs = params.toString();
258
+ const res = await this.fetch(
259
+ this.bindingPath("/intents") + (qs ? `?${qs}` : ""),
260
+ { headers: this.authHeaders() }
261
+ );
262
+ return expectJson(route, res);
263
+ }
227
264
  // ────────── invites ──────────
228
265
  /**
229
266
  * Mint a new invite (owner-only). Returns the secret + a ready-to-share
@@ -435,6 +472,9 @@ var BUILTIN_IGNORE_LINES = [
435
472
  "# Dependencies and build output",
436
473
  "node_modules/",
437
474
  "dist/",
475
+ // rig's default folder-registry target (`rig publish --registry`, default
476
+ // ./local-registry) — publish artifacts, not workspace content to sync.
477
+ "local-registry/",
438
478
  ".venv/",
439
479
  "__pycache__/",
440
480
  "*.pyc",
@@ -583,10 +623,10 @@ import { createHash } from "crypto";
583
623
  import { createReadStream } from "fs";
584
624
  async function hashFile(absPath) {
585
625
  const hash = createHash("sha256");
586
- await new Promise((resolve, reject) => {
626
+ await new Promise((resolve2, reject) => {
587
627
  const stream = createReadStream(absPath);
588
628
  stream.on("data", (chunk) => hash.update(chunk));
589
- stream.on("end", () => resolve());
629
+ stream.on("end", () => resolve2());
590
630
  stream.on("error", (err) => reject(err));
591
631
  });
592
632
  return `sha256:${hash.digest("hex")}`;
@@ -622,6 +662,9 @@ async function mapPool(items, limit, fn) {
622
662
  function sha256OfBuffer(bytes) {
623
663
  return `sha256:${createHash2("sha256").update(bytes).digest("hex")}`;
624
664
  }
665
+ function isRecord(v) {
666
+ return typeof v === "object" && v !== null;
667
+ }
625
668
  var UPLOAD_CONCURRENCY = 8;
626
669
  var AlreadyInitializedError = class extends Error {
627
670
  constructor(path) {
@@ -631,6 +674,16 @@ var AlreadyInitializedError = class extends Error {
631
674
  }
632
675
  path;
633
676
  };
677
+ var OrgForbiddenError = class extends Error {
678
+ constructor(org) {
679
+ super(
680
+ `not a member of org "${org}" \u2014 ask an org admin for an invite, or check the slug`
681
+ );
682
+ this.org = org;
683
+ this.name = "OrgForbiddenError";
684
+ }
685
+ org;
686
+ };
634
687
  async function init(opts) {
635
688
  if (readBindingConfig(opts.rootDir)) {
636
689
  throw new AlreadyInitializedError(join4(opts.rootDir, BINDING_CONFIG_PATH));
@@ -642,12 +695,20 @@ async function init(opts) {
642
695
  const h = await hashFile(join4(opts.rootDir, f.path));
643
696
  hashed.push({ path: f.path, size: f.size, hash: h });
644
697
  }
645
- const created = await createBinding({
646
- baseUrl: opts.relayUrl,
647
- request: { name: opts.bindingName, deviceLabel: opts.deviceLabel },
648
- accessToken: opts.accessToken,
649
- fetch: opts.fetch
650
- });
698
+ let created;
699
+ try {
700
+ created = await createBinding({
701
+ baseUrl: opts.relayUrl,
702
+ request: { name: opts.bindingName, deviceLabel: opts.deviceLabel, org: opts.org },
703
+ accessToken: opts.accessToken,
704
+ fetch: opts.fetch
705
+ });
706
+ } catch (err) {
707
+ if (err instanceof RelayError && isRecord(err.body) && err.body.error === "org_forbidden" && opts.org) {
708
+ throw new OrgForbiddenError(opts.org);
709
+ }
710
+ throw err;
711
+ }
651
712
  writeBindingConfig(opts.rootDir, {
652
713
  bindingId: created.binding.id,
653
714
  relayUrl: opts.relayUrl,
@@ -1044,7 +1105,7 @@ var ImportTokenScopeError = class extends Error {
1044
1105
  }
1045
1106
  ops;
1046
1107
  };
1047
- function isRecord(v) {
1108
+ function isRecord2(v) {
1048
1109
  return typeof v === "object" && v !== null;
1049
1110
  }
1050
1111
  function deriveName(canonicalPath) {
@@ -1090,7 +1151,7 @@ async function addImport(opts) {
1090
1151
  fetch: opts.fetch
1091
1152
  });
1092
1153
  } catch (err) {
1093
- if (err instanceof RelayError && isRecord(err.body) && err.body.reason === "import_requires_read_subscribe") {
1154
+ if (err instanceof RelayError && isRecord2(err.body) && err.body.reason === "import_requires_read_subscribe") {
1094
1155
  throw new ImportTokenScopeError();
1095
1156
  }
1096
1157
  throw err;
@@ -1180,6 +1241,32 @@ import {
1180
1241
  writeFileSync as writeFileSync4
1181
1242
  } from "fs";
1182
1243
  import { basename, dirname as dirname5, extname, join as join7, sep as sep2 } from "path";
1244
+
1245
+ // src/safe-path.ts
1246
+ import { isAbsolute as isAbsolute2, relative as relative2, resolve } from "path";
1247
+ var UnsafeRigPathError = class extends Error {
1248
+ constructor(path, reason) {
1249
+ super(`Unsafe rig path "${path}": ${reason}`);
1250
+ this.path = path;
1251
+ this.reason = reason;
1252
+ this.name = "UnsafeRigPathError";
1253
+ }
1254
+ path;
1255
+ reason;
1256
+ };
1257
+ function safeResolveRigPath(rootDir, path) {
1258
+ const valid = validateRigPath(path);
1259
+ if (!valid.ok) throw new UnsafeRigPathError(path, valid.reason);
1260
+ const rootAbs = resolve(rootDir);
1261
+ const targetAbs = resolve(rootAbs, path);
1262
+ const rel = relative2(rootAbs, targetAbs);
1263
+ if (rel === "" || rel.startsWith("..") || isAbsolute2(rel)) {
1264
+ throw new UnsafeRigPathError(path, "outside_root");
1265
+ }
1266
+ return targetAbs;
1267
+ }
1268
+
1269
+ // src/applier.ts
1183
1270
  var STATE_KEY_CONFLICT_COUNT = "conflict_count";
1184
1271
  var HashMismatchError = class extends Error {
1185
1272
  constructor(path, expectedHash, actualHash) {
@@ -1297,7 +1384,7 @@ async function applyOnce(opts) {
1297
1384
  async function applyEvent(opts) {
1298
1385
  const { rootDir, client, stateDb, event: ev, mode } = opts;
1299
1386
  const mirror = mode === "mirror";
1300
- const abs = join7(rootDir, ev.path);
1387
+ const abs = safeResolveRigPath(rootDir, ev.path);
1301
1388
  switch (ev.op) {
1302
1389
  case "write": {
1303
1390
  if (!ev.hash) throw new Error(`write event ${ev.id} missing hash`);
@@ -1329,7 +1416,7 @@ async function applyEvent(opts) {
1329
1416
  }
1330
1417
  if (localDirty) {
1331
1418
  const sidecarPath = conflictSidecarPath(ev.path, ev);
1332
- const sidecarAbs = join7(rootDir, sidecarPath);
1419
+ const sidecarAbs = safeResolveRigPath(rootDir, sidecarPath);
1333
1420
  mkdirSync4(dirname5(sidecarAbs), { recursive: true });
1334
1421
  const tmp2 = `${sidecarAbs}.tap-tmp.${randomBytes(6).toString("hex")}`;
1335
1422
  writeFileSync4(tmp2, bytes, { mode: ev.executable ? 493 : 420 });
@@ -1661,6 +1748,7 @@ async function startSubscription(opts) {
1661
1748
  import { createHash as createHash4 } from "crypto";
1662
1749
  import { readFileSync as readFileSync8 } from "fs";
1663
1750
  import { join as join8 } from "path";
1751
+ import { basename as basename2 } from "path";
1664
1752
  var UPLOAD_CONCURRENCY2 = 8;
1665
1753
  function sha256OfBuffer2(bytes) {
1666
1754
  return `sha256:${createHash4("sha256").update(bytes).digest("hex")}`;
@@ -1725,8 +1813,16 @@ async function computeLocalDiff(opts) {
1725
1813
  };
1726
1814
  }
1727
1815
  async function scanAndPush(opts) {
1728
- const { rootDir, client, stateDb } = opts;
1816
+ const { rootDir, client, stateDb, resolveIntents } = opts;
1729
1817
  const diff = await computeLocalDiff(opts);
1818
+ const divergedByPath = /* @__PURE__ */ new Map();
1819
+ for (const { path, sidecarPath } of findConflictSidecars(rootDir)) {
1820
+ const parsed = parseConflictSidecar(basename2(sidecarPath));
1821
+ if (!parsed) continue;
1822
+ const list2 = divergedByPath.get(path) ?? [];
1823
+ if (!list2.includes(parsed.chgId)) list2.push(parsed.chgId);
1824
+ divergedByPath.set(path, list2);
1825
+ }
1730
1826
  const uniqueHashes = /* @__PURE__ */ new Map();
1731
1827
  for (const w of diff.writes) if (!uniqueHashes.has(w.hash)) uniqueHashes.set(w.hash, w);
1732
1828
  const mutatedPaths = /* @__PURE__ */ new Set();
@@ -1755,12 +1851,14 @@ async function scanAndPush(opts) {
1755
1851
  if (mutatedPaths.has(w.path)) continue;
1756
1852
  const repPath = (uniqueHashes.get(w.hash) ?? w).path;
1757
1853
  if (mutatedPaths.has(repPath)) continue;
1854
+ const diverged = divergedByPath.get(w.path);
1758
1855
  events.push({
1759
1856
  op: "write",
1760
1857
  path: w.path,
1761
1858
  hash: w.hash,
1762
1859
  size: w.size,
1763
- ...w.baseChangeId ? { baseChangeId: w.baseChangeId } : {}
1860
+ ...w.baseChangeId ? { baseChangeId: w.baseChangeId } : {},
1861
+ ...diverged && diverged.length > 0 ? { meta: { divergedFrom: diverged } } : {}
1764
1862
  });
1765
1863
  }
1766
1864
  for (const d of diff.deletes) {
@@ -1776,6 +1874,15 @@ async function scanAndPush(opts) {
1776
1874
  for (const path of diff.mkdirs) {
1777
1875
  events.push({ op: "mkdir", path });
1778
1876
  }
1877
+ if (resolveIntents && events.length > 0) {
1878
+ const assignments = await resolveIntents(events.map((e) => e.path));
1879
+ for (const ev of events) {
1880
+ const a = assignments.get(ev.path);
1881
+ if (!a) continue;
1882
+ ev.intentId = a.intentId;
1883
+ ev.meta = { ...ev.meta, attribution: a.attribution };
1884
+ }
1885
+ }
1779
1886
  let submitted = [];
1780
1887
  if (events.length > 0) {
1781
1888
  const result = await client.submitChanges(events);
@@ -1819,8 +1926,288 @@ function cursorLessThan(a, b) {
1819
1926
  }
1820
1927
 
1821
1928
  // src/start.ts
1822
- import { join as join9 } from "path";
1929
+ import { join as join10 } from "path";
1823
1930
  import chokidar from "chokidar";
1931
+
1932
+ // src/claude-code.ts
1933
+ import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync9, statSync as statSync3 } from "fs";
1934
+ import { homedir } from "os";
1935
+ import { join as join9, relative as relative3, sep as sep3 } from "path";
1936
+ function claudeProjectDirName(rootDir) {
1937
+ return rootDir.replace(/[/.]/g, "-");
1938
+ }
1939
+ function defaultClaudeProjectsRoot() {
1940
+ return join9(homedir(), ".claude", "projects");
1941
+ }
1942
+ var TITLE_MAX = 120;
1943
+ var BOUT_GAP_MS = 30 * 60 * 1e3;
1944
+ var READ_TOOLS = /* @__PURE__ */ new Set(["Read", "Grep", "Glob"]);
1945
+ var WRITE_TOOLS = /* @__PURE__ */ new Set([
1946
+ "Write",
1947
+ "Edit",
1948
+ "MultiEdit",
1949
+ "NotebookEdit",
1950
+ "mcp__acp__Write",
1951
+ "mcp__acp__Edit"
1952
+ ]);
1953
+ function asObj(v) {
1954
+ return typeof v === "object" && v !== null ? v : null;
1955
+ }
1956
+ function userPromptText(line) {
1957
+ const msg = asObj(line.message);
1958
+ if (!msg) return null;
1959
+ const content = msg.content;
1960
+ if (typeof content === "string") return content.trim() || null;
1961
+ if (Array.isArray(content)) {
1962
+ for (const block of content) {
1963
+ const b = asObj(block);
1964
+ if (b && b.type === "text" && typeof b.text === "string" && b.text.trim()) {
1965
+ return b.text.trim();
1966
+ }
1967
+ }
1968
+ }
1969
+ return null;
1970
+ }
1971
+ function toolPathsFromAssistant(line, tools) {
1972
+ const msg = asObj(line.message);
1973
+ if (!msg || !Array.isArray(msg.content)) return [];
1974
+ const out = [];
1975
+ for (const block of msg.content) {
1976
+ const b = asObj(block);
1977
+ if (!b || b.type !== "tool_use" || typeof b.name !== "string") continue;
1978
+ if (!tools.has(b.name)) continue;
1979
+ const input = asObj(b.input);
1980
+ if (!input) continue;
1981
+ const p = input.file_path ?? input.notebook_path ?? input.path;
1982
+ if (typeof p === "string" && p) out.push(p);
1983
+ }
1984
+ return out;
1985
+ }
1986
+ function toRigRelative(absPath, rootDir) {
1987
+ const rel = relative3(rootDir, absPath);
1988
+ if (rel === "" || rel.startsWith("..") || sep3 !== "/" && rel.includes(".." + sep3)) {
1989
+ return null;
1990
+ }
1991
+ return sep3 === "/" ? rel : rel.replaceAll(sep3, "/");
1992
+ }
1993
+ var PROMPT_SNIPPET_MAX = 2e3;
1994
+ function num(v) {
1995
+ return typeof v === "number" && Number.isFinite(v) ? v : 0;
1996
+ }
1997
+ function toolNamesFromAssistant(line) {
1998
+ const msg = asObj(line.message);
1999
+ if (!msg || !Array.isArray(msg.content)) return [];
2000
+ const out = [];
2001
+ for (const block of msg.content) {
2002
+ const b = asObj(block);
2003
+ if (b && b.type === "tool_use" && typeof b.name === "string") out.push(b.name);
2004
+ }
2005
+ return out;
2006
+ }
2007
+ function parseSessions(transcriptPath, rootDir, opts = {}) {
2008
+ let raw;
2009
+ try {
2010
+ raw = readFileSync9(transcriptPath, "utf8");
2011
+ } catch {
2012
+ return [];
2013
+ }
2014
+ const fileId = basenameNoExt(transcriptPath);
2015
+ const gap = opts.boutGapMs ?? BOUT_GAP_MS;
2016
+ let aiTitle = null;
2017
+ const bouts = [];
2018
+ let cur = null;
2019
+ let prevMs = null;
2020
+ for (const rawLine of raw.split("\n")) {
2021
+ if (!rawLine) continue;
2022
+ let line;
2023
+ try {
2024
+ line = JSON.parse(rawLine);
2025
+ } catch {
2026
+ continue;
2027
+ }
2028
+ const type = line.type;
2029
+ if (type === "ai-title" && typeof line.aiTitle === "string") {
2030
+ aiTitle = line.aiTitle.trim() || aiTitle;
2031
+ continue;
2032
+ }
2033
+ const ts = typeof line.timestamp === "string" ? line.timestamp : null;
2034
+ const tsMs = ts ? Date.parse(ts) : null;
2035
+ const timed = tsMs !== null && Number.isFinite(tsMs);
2036
+ const contentLine = type === "user" || type === "assistant";
2037
+ const gapBreak = timed && cur !== null && prevMs !== null && tsMs - prevMs > gap;
2038
+ if (cur === null ? timed || contentLine : gapBreak) {
2039
+ cur = { index: bouts.length, firstTs: null, lastTs: null, model: null, firstPrompt: null, reads: /* @__PURE__ */ new Set(), writes: /* @__PURE__ */ new Set(), tokens: 0, tools: /* @__PURE__ */ new Map() };
2040
+ bouts.push(cur);
2041
+ }
2042
+ if (timed) prevMs = tsMs;
2043
+ if (cur === null) continue;
2044
+ if (type === "user") {
2045
+ if (!cur.firstPrompt) cur.firstPrompt = userPromptText(line);
2046
+ } else if (type === "assistant") {
2047
+ const msg = asObj(line.message);
2048
+ if (msg && typeof msg.model === "string") cur.model = msg.model;
2049
+ const usage = msg ? asObj(msg.usage) : null;
2050
+ if (usage) {
2051
+ cur.tokens += num(usage.output_tokens);
2052
+ }
2053
+ for (const name of toolNamesFromAssistant(line)) {
2054
+ cur.tools.set(name, (cur.tools.get(name) ?? 0) + 1);
2055
+ }
2056
+ for (const abs of toolPathsFromAssistant(line, READ_TOOLS)) {
2057
+ const rel = toRigRelative(abs, rootDir);
2058
+ if (rel) cur.reads.add(rel);
2059
+ }
2060
+ for (const abs of toolPathsFromAssistant(line, WRITE_TOOLS)) {
2061
+ const rel = toRigRelative(abs, rootDir);
2062
+ if (rel) cur.writes.add(rel);
2063
+ }
2064
+ }
2065
+ if (ts) {
2066
+ if (cur.firstTs === null || ts < cur.firstTs) cur.firstTs = ts;
2067
+ if (cur.lastTs === null || ts > cur.lastTs) cur.lastTs = ts;
2068
+ }
2069
+ }
2070
+ const single = bouts.length <= 1;
2071
+ return bouts.map((b) => ({
2072
+ sessionId: single ? fileId : `${fileId}#${b.index}`,
2073
+ // aiTitle is the best concise label; bouts are disambiguated by their window.
2074
+ title: aiTitle ?? (b.firstPrompt ? truncate(b.firstPrompt, TITLE_MAX) : fileId),
2075
+ agent: "claude-code",
2076
+ model: b.model,
2077
+ firstTs: b.firstTs,
2078
+ lastTs: b.lastTs,
2079
+ readPaths: [...b.reads],
2080
+ writePaths: [...b.writes],
2081
+ detail: {
2082
+ model: b.model,
2083
+ promptSnippet: b.firstPrompt ? truncate(cleanPrompt(b.firstPrompt), PROMPT_SNIPPET_MAX) : null,
2084
+ tokens: b.tokens > 0 ? b.tokens : null,
2085
+ tools: [...b.tools.entries()].map(([name, count]) => ({ name, count })).sort((x, y) => y.count - x.count)
2086
+ },
2087
+ transcriptPath
2088
+ }));
2089
+ }
2090
+ var parseCache = /* @__PURE__ */ new Map();
2091
+ function listSessions(rootDir, opts = {}) {
2092
+ const dir = join9(opts.projectsRoot ?? defaultClaudeProjectsRoot(), claudeProjectDirName(rootDir));
2093
+ if (!existsSync6(dir)) return [];
2094
+ let files;
2095
+ try {
2096
+ files = readdirSync4(dir).filter((f) => f.endsWith(".jsonl"));
2097
+ } catch {
2098
+ return [];
2099
+ }
2100
+ const out = [];
2101
+ for (const f of files) {
2102
+ const abs = join9(dir, f);
2103
+ let st;
2104
+ try {
2105
+ st = statSync3(abs);
2106
+ } catch {
2107
+ continue;
2108
+ }
2109
+ const cached = parseCache.get(abs);
2110
+ if (cached && cached.mtimeMs === st.mtimeMs && cached.size === st.size) {
2111
+ out.push(...cached.bouts);
2112
+ continue;
2113
+ }
2114
+ const bouts = parseSessions(abs, rootDir);
2115
+ parseCache.set(abs, { mtimeMs: st.mtimeMs, size: st.size, bouts });
2116
+ out.push(...bouts);
2117
+ }
2118
+ out.sort((a, b) => (a.lastTs ?? "").localeCompare(b.lastTs ?? ""));
2119
+ return out;
2120
+ }
2121
+ var DEFAULT_IDLE_MS = 30 * 60 * 1e3;
2122
+ function recentSessions(rootDir, opts = {}) {
2123
+ const now = opts.now ?? Date.now();
2124
+ const idleMs = opts.idleMs ?? DEFAULT_IDLE_MS;
2125
+ return listSessions(rootDir, opts).filter((s) => {
2126
+ if (!s.lastTs) return false;
2127
+ const ms = Date.parse(s.lastTs);
2128
+ return Number.isFinite(ms) && now - ms <= idleMs;
2129
+ }).sort((a, b) => Date.parse(a.lastTs) - Date.parse(b.lastTs));
2130
+ }
2131
+ function basenameNoExt(p) {
2132
+ const base = p.slice(p.lastIndexOf(sep3 === "/" ? "/" : sep3) + 1);
2133
+ const dot = base.lastIndexOf(".");
2134
+ return dot > 0 ? base.slice(0, dot) : base;
2135
+ }
2136
+ function truncate(s, max) {
2137
+ return s.length <= max ? s : s.slice(0, max - 1) + "\u2026";
2138
+ }
2139
+ function cleanPrompt(s) {
2140
+ return s.split("\n").map((l) => l.replace(/^\s*>+\s?/, "")).join(" ").replace(/\s+/g, " ").trim();
2141
+ }
2142
+
2143
+ // src/intent-association.ts
2144
+ var META_INTENT_PREFIX = "intent:";
2145
+ var META_FP_PREFIX = "intentfp:";
2146
+ async function resolveIntentAssignments(deps, changedPaths) {
2147
+ const out = /* @__PURE__ */ new Map();
2148
+ if (changedPaths.length === 0) return out;
2149
+ const sessions = recentSessions(deps.rootDir, deps.sessionOpts);
2150
+ if (sessions.length === 0) return out;
2151
+ const claim = /* @__PURE__ */ new Map();
2152
+ for (const s of sessions) {
2153
+ for (const p of s.writePaths) claim.set(p, s);
2154
+ }
2155
+ const mostRecent = sessions[sessions.length - 1];
2156
+ const decided = /* @__PURE__ */ new Map();
2157
+ const used = /* @__PURE__ */ new Set();
2158
+ for (const path of changedPaths) {
2159
+ const claimed = claim.get(path);
2160
+ const session = claimed ?? mostRecent;
2161
+ decided.set(path, { session, attribution: claimed ? "confirmed" : "window" });
2162
+ used.add(session);
2163
+ }
2164
+ const intentBySession = /* @__PURE__ */ new Map();
2165
+ for (const s of used) {
2166
+ intentBySession.set(s, await ensureIntentForSession(deps, s));
2167
+ }
2168
+ for (const [path, d] of decided) {
2169
+ out.set(path, { intentId: intentBySession.get(d.session), attribution: d.attribution });
2170
+ }
2171
+ return out;
2172
+ }
2173
+ async function ensureIntentForSession(deps, session) {
2174
+ const { client, stateDb } = deps;
2175
+ const readPaths = mapReadPaths(stateDb, session.readPaths);
2176
+ const fp = fingerprint(session.title, readPaths);
2177
+ const cachedId = stateDb.getMeta(META_INTENT_PREFIX + session.sessionId);
2178
+ if (cachedId) {
2179
+ if (stateDb.getMeta(META_FP_PREFIX + session.sessionId) !== fp) {
2180
+ await client.updateIntent(cachedId, { title: session.title, readPaths });
2181
+ stateDb.setMeta(META_FP_PREFIX + session.sessionId, fp);
2182
+ }
2183
+ return cachedId;
2184
+ }
2185
+ const intent = await client.createIntent({
2186
+ agent: session.agent,
2187
+ title: session.title,
2188
+ readPaths,
2189
+ detail: session.detail,
2190
+ contextCursor: stateDb.getCursor(),
2191
+ payloadRef: `local:${session.transcriptPath}`
2192
+ });
2193
+ stateDb.setMeta(META_INTENT_PREFIX + session.sessionId, intent.id);
2194
+ stateDb.setMeta(META_FP_PREFIX + session.sessionId, fp);
2195
+ return intent.id;
2196
+ }
2197
+ function mapReadPaths(stateDb, paths) {
2198
+ const out = [];
2199
+ for (const path of paths) {
2200
+ const changeId = stateDb.getPath(path)?.lastAppliedChangeId;
2201
+ if (changeId) out.push({ path, changeId });
2202
+ }
2203
+ return out;
2204
+ }
2205
+ function fingerprint(title, readPaths) {
2206
+ const reads = readPaths.map((r) => `${r.path}@${r.changeId}`).sort().join(",");
2207
+ return `${title}\0${reads}`;
2208
+ }
2209
+
2210
+ // src/start.ts
1824
2211
  var DEFAULT_POLL_SECONDS2 = 3;
1825
2212
  var DEFAULT_DEBOUNCE_MS = 300;
1826
2213
  var NOOP_APPLY = {
@@ -1864,6 +2251,20 @@ async function start(opts) {
1864
2251
  let scanInFlight = Promise.resolve();
1865
2252
  let writerClient;
1866
2253
  const ignore2 = loadIgnore(opts.rootDir);
2254
+ const intentResolverFor = (client, stateDb2) => {
2255
+ if (opts.disableIntentCapture) return void 0;
2256
+ return async (changedPaths) => {
2257
+ try {
2258
+ return await resolveIntentAssignments(
2259
+ { client, stateDb: stateDb2, rootDir: opts.rootDir, sessionOpts: opts.intentSessionOpts },
2260
+ changedPaths
2261
+ );
2262
+ } catch (err) {
2263
+ console.error("tapd intent capture error:", err);
2264
+ return /* @__PURE__ */ new Map();
2265
+ }
2266
+ };
2267
+ };
1867
2268
  let importPaths = [];
1868
2269
  if (config) {
1869
2270
  primary = await startSubscription({
@@ -1904,7 +2305,8 @@ async function start(opts) {
1904
2305
  client: writerClient,
1905
2306
  stateDb: primaryStateDb,
1906
2307
  ignore: ignore2,
1907
- excludePaths: importPaths
2308
+ excludePaths: importPaths,
2309
+ resolveIntents: intentResolverFor(writerClient, primaryStateDb)
1908
2310
  }).catch((err) => {
1909
2311
  console.error("tapd scan error:", err);
1910
2312
  });
@@ -1934,7 +2336,8 @@ async function start(opts) {
1934
2336
  client: writerClient,
1935
2337
  stateDb: primaryStateDb,
1936
2338
  ignore: ignore2,
1937
- excludePaths: importPaths
2339
+ excludePaths: importPaths,
2340
+ resolveIntents: intentResolverFor(writerClient, primaryStateDb)
1938
2341
  }).catch((err) => {
1939
2342
  console.error("tapd initial scan error:", err);
1940
2343
  });
@@ -1945,7 +2348,7 @@ async function start(opts) {
1945
2348
  for (const imp of validImports) {
1946
2349
  try {
1947
2350
  const sub = await startSubscription({
1948
- rootDir: join9(opts.rootDir, imp.path),
2351
+ rootDir: join10(opts.rootDir, imp.path),
1949
2352
  config: { bindingId: imp.bindingId, relayUrl: imp.relayUrl, token: imp.token },
1950
2353
  stateDbPath: importStateDbPath(opts.rootDir, imp.name),
1951
2354
  mode: "mirror",
@@ -1991,7 +2394,8 @@ async function start(opts) {
1991
2394
  client: writerClient,
1992
2395
  stateDb: primary.stateDb,
1993
2396
  ignore: ignore2,
1994
- excludePaths: importPaths
2397
+ excludePaths: importPaths,
2398
+ resolveIntents: intentResolverFor(writerClient, primary.stateDb)
1995
2399
  });
1996
2400
  },
1997
2401
  stateDb,
@@ -2015,6 +2419,7 @@ export {
2015
2419
  walk,
2016
2420
  hashFile,
2017
2421
  AlreadyInitializedError,
2422
+ OrgForbiddenError,
2018
2423
  init,
2019
2424
  mint,
2020
2425
  list,
@@ -2038,6 +2443,7 @@ export {
2038
2443
  ImportTokenScopeError,
2039
2444
  addImport,
2040
2445
  removeImport,
2446
+ safeResolveRigPath,
2041
2447
  STATE_KEY_CONFLICT_COUNT,
2042
2448
  conflictSidecarPath,
2043
2449
  findConflictSidecars,
@@ -2048,4 +2454,4 @@ export {
2048
2454
  scanAndPush,
2049
2455
  start
2050
2456
  };
2051
- //# sourceMappingURL=chunk-LQWX63LP.js.map
2457
+ //# sourceMappingURL=chunk-GIL4KPR7.js.map