@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.
- package/dist/bin.js +32 -16
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-LQWX63LP.js → chunk-GIL4KPR7.js} +426 -20
- package/dist/chunk-GIL4KPR7.js.map +1 -0
- package/dist/index.d.ts +81 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-LQWX63LP.js.map +0 -1
|
@@ -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((
|
|
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", () =>
|
|
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
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
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
|
|
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 &&
|
|
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 =
|
|
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 =
|
|
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
|
|
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:
|
|
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-
|
|
2457
|
+
//# sourceMappingURL=chunk-GIL4KPR7.js.map
|