mainflow 0.33.0 → 0.34.0-alpha.1
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/cli.js +3 -3
- package/dist/facts.d.ts +1 -1
- package/dist/facts.js +1 -1
- package/dist/hooks.js +2 -2
- package/dist/{host-CObkJQmr.js → host-79T-_NMu.js} +291 -15
- package/dist/host.js +1 -1
- package/dist/{launch-BemxyV9P.js → launch-DQVyIbpf.js} +2 -2
- package/dist/launch.js +1 -1
- package/dist/native/mainflow-swap-darwin-arm64 +0 -0
- package/dist/native/mainflow-swap-darwin-x64 +0 -0
- package/dist/{operation-B2pq2Q9z.js → operation--fJnUqvA.js} +1 -1
- package/dist/{ui-CFLjhYJ9.js → ui-BcXPk-0P.js} +3 -3
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { B as parseRef, H as refExample, V as parseUntil, Y as stampMs, k as UNTIL_FORMS } from "./dist-IdbnCDqM.js";
|
|
3
|
-
import { $ as MAX_FILE_BYTES, B as checkHubName, H as loadHub, S as readSession, V as HubError, Y as nodeStorage, _ as untilState, ct as TASK_FILE, d as retroReportFromReceiver, dt as findHubRoot, f as receiverFromEnv, g as initHub, ht as WriteError, l as migrateHub, lt as requireSlug, mt as DATA_DIR, p as RETRY_RULES, q as nowMinute, st as MAINLINE_FILE, u as retroReport, v as duplicateClaimLines, x as loadSessions, y as sessionBriefing, z as applyOperation } from "./host-
|
|
3
|
+
import { $ as MAX_FILE_BYTES, B as checkHubName, H as loadHub, S as readSession, V as HubError, Y as nodeStorage, _ as untilState, ct as TASK_FILE, d as retroReportFromReceiver, dt as findHubRoot, f as receiverFromEnv, g as initHub, ht as WriteError, l as migrateHub, lt as requireSlug, mt as DATA_DIR, p as RETRY_RULES, q as nowMinute, st as MAINLINE_FILE, u as retroReport, v as duplicateClaimLines, x as loadSessions, y as sessionBriefing, z as applyOperation } from "./host-79T-_NMu.js";
|
|
4
4
|
import { t as listProgress } from "./progress-1BfPg2zN.js";
|
|
5
|
-
import { A as syncPaths, C as ENV, D as SETTINGS_FILE, E as syncSettings, S as ServerError, T as hubName, _ as readDaemon, a as remoteMode, b as oversizedFiles, c as planAnnounce, d as toolCallOf, f as startDaemon, g as probeDaemon, h as callDaemon, i as queuedCount, l as planFetch, m as DaemonUnavailable, n as hubWriter, o as fetchDenial, p as DaemonRunning, r as writtenLines, s as hookAnswer, t as CONFIRM_MS, u as planFlush, v as silentHint, w as earlierToken, x as ServerApi, y as readRemote } from "./operation
|
|
5
|
+
import { A as syncPaths, C as ENV, D as SETTINGS_FILE, E as syncSettings, S as ServerError, T as hubName, _ as readDaemon, a as remoteMode, b as oversizedFiles, c as planAnnounce, d as toolCallOf, f as startDaemon, g as probeDaemon, h as callDaemon, i as queuedCount, l as planFetch, m as DaemonUnavailable, n as hubWriter, o as fetchDenial, p as DaemonRunning, r as writtenLines, s as hookAnswer, t as CONFIRM_MS, u as planFlush, v as silentHint, w as earlierToken, x as ServerApi, y as readRemote } from "./operation--fJnUqvA.js";
|
|
6
6
|
import { COMMAND_SPECS, OPTIONS } from "./facts.js";
|
|
7
7
|
import { hookFallback, parsePayload, runHook } from "./hooks.js";
|
|
8
|
-
import { c as watchHubGone, l as CLI_VERSION, r as launchUi, s as startUiInBackground } from "./launch-
|
|
8
|
+
import { c as watchHubGone, l as CLI_VERSION, r as launchUi, s as startUiInBackground } from "./launch-DQVyIbpf.js";
|
|
9
9
|
import { randomBytes } from "node:crypto";
|
|
10
10
|
import { existsSync, readFileSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
|
|
11
11
|
import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
|
package/dist/facts.d.ts
CHANGED
|
@@ -336,7 +336,7 @@ export declare const COMMAND_SPECS: {
|
|
|
336
336
|
};
|
|
337
337
|
readonly retro: {
|
|
338
338
|
readonly usage: 'retro <m> [--json]';
|
|
339
|
-
readonly summary: 'Read the Claude Code transcripts of the sessions that served a mainline, and their subagents\', into estimated list-price cost (per model, opening context, cache hit rate, hook injections) and candidates for where work went wrong (tool errors, rerun Bash commands, hook injections followed by no tool call, blocks, pauses over 5 minutes, failed completion checks); read-only. With MAINFLOW_TELEMETRY_URL and
|
|
339
|
+
readonly summary: 'Read the Claude Code transcripts of the sessions that served a mainline, and their subagents\', into estimated list-price cost (per model, opening context, cache hit rate, hook injections) and candidates for where work went wrong (tool errors, rerun Bash commands, hook injections followed by no tool call, blocks, pauses over 5 minutes, failed completion checks); read-only. With MAINFLOW_TELEMETRY_URL, MAINFLOW_TELEMETRY_ACCESS_CLIENT_ID and MAINFLOW_TELEMETRY_ACCESS_CLIENT_SECRET (a Cloudflare Access service token) set it also reads the measurement receiver\'s export: billed cost per request, and the run time of every hook, the silent ones too; sessions whose transcript is not on this machine are downloaded from the receiver into .mainflow/local/transcripts/ first';
|
|
340
340
|
readonly args: 1;
|
|
341
341
|
readonly needsHub: true;
|
|
342
342
|
};
|
package/dist/facts.js
CHANGED
|
@@ -326,7 +326,7 @@ const COMMAND_SPECS = {
|
|
|
326
326
|
},
|
|
327
327
|
retro: {
|
|
328
328
|
usage: "retro <m> [--json]",
|
|
329
|
-
summary: "Read the Claude Code transcripts of the sessions that served a mainline, and their subagents', into estimated list-price cost (per model, opening context, cache hit rate, hook injections) and candidates for where work went wrong (tool errors, rerun Bash commands, hook injections followed by no tool call, blocks, pauses over 5 minutes, failed completion checks); read-only. With MAINFLOW_TELEMETRY_URL and
|
|
329
|
+
summary: "Read the Claude Code transcripts of the sessions that served a mainline, and their subagents', into estimated list-price cost (per model, opening context, cache hit rate, hook injections) and candidates for where work went wrong (tool errors, rerun Bash commands, hook injections followed by no tool call, blocks, pauses over 5 minutes, failed completion checks); read-only. With MAINFLOW_TELEMETRY_URL, MAINFLOW_TELEMETRY_ACCESS_CLIENT_ID and MAINFLOW_TELEMETRY_ACCESS_CLIENT_SECRET (a Cloudflare Access service token) set it also reads the measurement receiver's export: billed cost per request, and the run time of every hook, the silent ones too; sessions whose transcript is not on this machine are downloaded from the receiver into .mainflow/local/transcripts/ first",
|
|
330
330
|
args: 1,
|
|
331
331
|
needsHub: true
|
|
332
332
|
},
|
package/dist/hooks.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { $ as describeRequest, j as configSchema } from "./dist-IdbnCDqM.js";
|
|
2
|
-
import { D as endsWithMarker, E as decorateReply, H as loadHub, J as readYaml, O as headerLine, S as readSession, T as matchGrants, V as HubError, b as turnReminder, dt as findHubRoot, ft as worktreesOf, i as hostName, k as mayDecorate, mt as DATA_DIR, ut as currentBranch, w as grantScope, x as loadSessions, y as sessionBriefing } from "./host-
|
|
3
|
-
import { n as hubWriter, r as writtenLines } from "./operation
|
|
2
|
+
import { D as endsWithMarker, E as decorateReply, H as loadHub, J as readYaml, O as headerLine, S as readSession, T as matchGrants, V as HubError, b as turnReminder, dt as findHubRoot, ft as worktreesOf, i as hostName, k as mayDecorate, mt as DATA_DIR, ut as currentBranch, w as grantScope, x as loadSessions, y as sessionBriefing } from "./host-79T-_NMu.js";
|
|
3
|
+
import { n as hubWriter, r as writtenLines } from "./operation--fJnUqvA.js";
|
|
4
4
|
import { resolveFixed, scanCommand, scopedCwd } from "./grant-commands.js";
|
|
5
5
|
import { appendFileSync, existsSync, readFileSync } from "node:fs";
|
|
6
6
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
@@ -7,7 +7,7 @@ import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
|
7
7
|
import { parse as parse$1, stringify } from "yaml";
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
9
|
import { performance } from "node:perf_hooks";
|
|
10
|
-
import { gunzipSync } from "node:zlib";
|
|
10
|
+
import { gunzipSync, inflateRawSync } from "node:zlib";
|
|
11
11
|
//#region ../core/dist/ops/document.js
|
|
12
12
|
const DATA_DIR = ".mainflow";
|
|
13
13
|
const ENTRY_DIR = /^([mtdif]\d{3})-(.+)$/;
|
|
@@ -3774,6 +3774,8 @@ async function setArchivedMany(kind, ids, archived, write) {
|
|
|
3774
3774
|
//#endregion
|
|
3775
3775
|
//#region ../core/dist/retro/util.js
|
|
3776
3776
|
const isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3777
|
+
/** A 2xx response. Requests use redirect 'manual', so a 3xx (Access sending the caller to its login page) is a failure. */
|
|
3778
|
+
const isOk = (response) => response.status >= 200 && response.status < 300;
|
|
3777
3779
|
const str = (v) => typeof v === "string" && v ? v : void 0;
|
|
3778
3780
|
const finite = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
3779
3781
|
/** Appends `items` to `into` one by one. */
|
|
@@ -6334,19 +6336,246 @@ function pairInjection(injection, own, toolUseId, successes, stopSummaries) {
|
|
|
6334
6336
|
if (tag?.reason) return { reason: tag.reason };
|
|
6335
6337
|
}
|
|
6336
6338
|
//#endregion
|
|
6339
|
+
//#region ../core/dist/retro/transcripts.js
|
|
6340
|
+
const PAGE_LIMIT = 1e3;
|
|
6341
|
+
const MAX_BUNDLE_KEYS$1 = 40;
|
|
6342
|
+
/** Rounds of re-asking for the keys a bundle deferred, so a receiver that never delivers cannot loop forever. */
|
|
6343
|
+
const MAX_DEFERRED_ROUNDS = 20;
|
|
6344
|
+
const TEXT_SHOWN$1 = 200;
|
|
6345
|
+
const SAFE_ID = /^[\w.-]+$/;
|
|
6346
|
+
const SAFE_PATH = /^(?:[\w.-]+\.jsonl|subagents\/[\w.-]+\.(?:jsonl|meta\.json))$/;
|
|
6347
|
+
/** The file path under a session and the offset a stored key holds: transcripts/{sid}/{path}/{offset}.gz */
|
|
6348
|
+
function parseTranscriptKey(sid, key) {
|
|
6349
|
+
const m = new RegExp(`^transcripts/${sid.replace(/[.]/g, "\\.")}/(.+)/(\\d{1,15})\\.gz$`).exec(key);
|
|
6350
|
+
return m && SAFE_PATH.test(m[1]) ? {
|
|
6351
|
+
path: m[1],
|
|
6352
|
+
offset: Number(m[2])
|
|
6353
|
+
} : void 0;
|
|
6354
|
+
}
|
|
6355
|
+
/** Where a stored path lands: the main transcript beside the directory, the rest under <dir>/{sid}/. */
|
|
6356
|
+
function transcriptFile(want, path) {
|
|
6357
|
+
return path.includes("/") ? join(want.dir, want.sid, path) : join(want.dir, path);
|
|
6358
|
+
}
|
|
6359
|
+
/**
|
|
6360
|
+
* The segments of one file in use, from offset 0 on and as far as they run without a gap; a segment overlapping the
|
|
6361
|
+
* ones before it (a resend) only adds what lies past them. `length` of a segment is unknown (undefined) before it
|
|
6362
|
+
* is downloaded unless the listing gave rawLength, in which case `end` is undefined.
|
|
6363
|
+
*/
|
|
6364
|
+
function joined(segments, lengthOf) {
|
|
6365
|
+
const used = [];
|
|
6366
|
+
let end = 0;
|
|
6367
|
+
for (const s of [...segments].sort((a, b) => a.offset - b.offset)) {
|
|
6368
|
+
if (end === void 0) {
|
|
6369
|
+
used.push(s);
|
|
6370
|
+
continue;
|
|
6371
|
+
}
|
|
6372
|
+
if (s.offset > end) return {
|
|
6373
|
+
used,
|
|
6374
|
+
end,
|
|
6375
|
+
gapAt: s.offset
|
|
6376
|
+
};
|
|
6377
|
+
used.push(s);
|
|
6378
|
+
const length = lengthOf(s);
|
|
6379
|
+
end = length === void 0 ? void 0 : Math.max(end, s.offset + length);
|
|
6380
|
+
}
|
|
6381
|
+
return {
|
|
6382
|
+
used,
|
|
6383
|
+
end
|
|
6384
|
+
};
|
|
6385
|
+
}
|
|
6386
|
+
/** The size the receiver's segments add up to for each file, or undefined when a segment of it has no rawLength. */
|
|
6387
|
+
function expectedSizes(sid, listed) {
|
|
6388
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
6389
|
+
for (const o of listed) {
|
|
6390
|
+
const parsed = parseTranscriptKey(sid, o.key);
|
|
6391
|
+
if (parsed) byPath.set(parsed.path, [...byPath.get(parsed.path) ?? [], {
|
|
6392
|
+
...o,
|
|
6393
|
+
offset: parsed.offset
|
|
6394
|
+
}]);
|
|
6395
|
+
}
|
|
6396
|
+
return new Map([...byPath].map(([path, segments]) => [path, joined(segments, (s) => s.rawLength).end]));
|
|
6397
|
+
}
|
|
6398
|
+
/** Cuts the gzip members out of a bundle, one per key: a member is a header, a deflate stream and an 8-byte trailer. */
|
|
6399
|
+
function members(bytes, count) {
|
|
6400
|
+
const out = [];
|
|
6401
|
+
let at = 0;
|
|
6402
|
+
while (out.length < count && at < bytes.length) {
|
|
6403
|
+
if (bytes[at] !== 31 || bytes[at + 1] !== 139) throw new Error("bundle is not gzip members");
|
|
6404
|
+
const flags = bytes[at + 3];
|
|
6405
|
+
let p = at + 10;
|
|
6406
|
+
if (flags & 4) p += 2 + bytes.readUInt16LE(p);
|
|
6407
|
+
if (flags & 8) p = bytes.indexOf(0, p) + 1;
|
|
6408
|
+
if (flags & 16) p = bytes.indexOf(0, p) + 1;
|
|
6409
|
+
if (flags & 2) p += 2;
|
|
6410
|
+
const { buffer, engine } = inflateRawSync(bytes.subarray(p), { info: true });
|
|
6411
|
+
out.push(buffer);
|
|
6412
|
+
at = p + engine.bytesWritten + 8;
|
|
6413
|
+
}
|
|
6414
|
+
if (out.length !== count) throw new Error(`bundle holds ${out.length} objects, expected ${count}`);
|
|
6415
|
+
return out;
|
|
6416
|
+
}
|
|
6417
|
+
/**
|
|
6418
|
+
* Brings the transcripts of the wanted sessions into their directories. A session whose files are all there and as
|
|
6419
|
+
* long as the receiver's segments add up to (by rawLength) is not downloaded again; with no rawLength it always is.
|
|
6420
|
+
*/
|
|
6421
|
+
async function fetchTranscripts(source, wanted) {
|
|
6422
|
+
const doFetch = source.fetch ?? globalThis.fetch;
|
|
6423
|
+
const result = {
|
|
6424
|
+
fetched: [],
|
|
6425
|
+
warnings: [],
|
|
6426
|
+
requests: 0
|
|
6427
|
+
};
|
|
6428
|
+
const scrub = (text) => source.accessClientSecret ? text.split(source.accessClientSecret).join("***") : text;
|
|
6429
|
+
const warn = (text) => result.warnings.push(scrub(text));
|
|
6430
|
+
let origin;
|
|
6431
|
+
try {
|
|
6432
|
+
const base = new URL(source.url);
|
|
6433
|
+
if (base.protocol !== "https:" && base.protocol !== "http:") throw new Error("not http");
|
|
6434
|
+
origin = base.origin;
|
|
6435
|
+
} catch {
|
|
6436
|
+
warn("接收端網址不是 http 或 https 網址,沒有下載對話紀錄");
|
|
6437
|
+
return result;
|
|
6438
|
+
}
|
|
6439
|
+
const headers = {
|
|
6440
|
+
"cf-access-client-id": source.accessClientId,
|
|
6441
|
+
"cf-access-client-secret": source.accessClientSecret
|
|
6442
|
+
};
|
|
6443
|
+
const failure = async (response) => {
|
|
6444
|
+
let text = "";
|
|
6445
|
+
try {
|
|
6446
|
+
text = (await response.text()).replace(/\s+/g, " ").trim().slice(0, TEXT_SHOWN$1);
|
|
6447
|
+
} catch {}
|
|
6448
|
+
return `HTTP ${response.status}${text ? ` ${text}` : ""}`;
|
|
6449
|
+
};
|
|
6450
|
+
const thrown = (error) => {
|
|
6451
|
+
const e = error;
|
|
6452
|
+
const cause = e.cause?.code ?? e.cause?.message;
|
|
6453
|
+
return `${e.message ?? String(error)}${cause ? `(${cause})` : ""}`;
|
|
6454
|
+
};
|
|
6455
|
+
const list = async (sid) => {
|
|
6456
|
+
const objects = [];
|
|
6457
|
+
let cursor;
|
|
6458
|
+
for (let page = 0; page < PAGE_LIMIT; page++) {
|
|
6459
|
+
const u = new URL(`/v1/transcripts/${encodeURIComponent(sid)}`, origin);
|
|
6460
|
+
if (cursor) u.searchParams.set("cursor", cursor);
|
|
6461
|
+
result.requests += 1;
|
|
6462
|
+
const response = await doFetch(u, {
|
|
6463
|
+
headers,
|
|
6464
|
+
redirect: "manual"
|
|
6465
|
+
});
|
|
6466
|
+
if (!isOk(response)) throw new Error(`物件清單讀取失敗:${await failure(response)}`);
|
|
6467
|
+
const body = await response.json();
|
|
6468
|
+
if (!isObject(body) || !Array.isArray(body.objects)) throw new Error("物件清單格式不對");
|
|
6469
|
+
for (const o of body.objects) {
|
|
6470
|
+
if (!isObject(o) || typeof o.key !== "string") continue;
|
|
6471
|
+
const parsed = parseTranscriptKey(sid, o.key);
|
|
6472
|
+
if (parsed) objects.push({
|
|
6473
|
+
key: o.key,
|
|
6474
|
+
offset: parsed.offset,
|
|
6475
|
+
...typeof o.rawLength === "number" ? { rawLength: o.rawLength } : {}
|
|
6476
|
+
});
|
|
6477
|
+
}
|
|
6478
|
+
cursor = typeof body.next === "string" && body.next ? body.next : void 0;
|
|
6479
|
+
if (!cursor) return objects;
|
|
6480
|
+
}
|
|
6481
|
+
throw new Error(`物件清單超過 ${PAGE_LIMIT} 頁`);
|
|
6482
|
+
};
|
|
6483
|
+
/** The bytes of each key, asking again for the keys a bundle deferred. */
|
|
6484
|
+
const download = async (sid, keys) => {
|
|
6485
|
+
const got = /* @__PURE__ */ new Map();
|
|
6486
|
+
let pending = [...keys];
|
|
6487
|
+
for (let round = 0; pending.length && round < MAX_DEFERRED_ROUNDS; round++) {
|
|
6488
|
+
const deferred = [];
|
|
6489
|
+
for (let i = 0; i < pending.length; i += MAX_BUNDLE_KEYS$1) {
|
|
6490
|
+
const batch = pending.slice(i, i + MAX_BUNDLE_KEYS$1);
|
|
6491
|
+
result.requests += 1;
|
|
6492
|
+
const response = await doFetch(new URL(`/v1/transcripts/${encodeURIComponent(sid)}/bundle`, origin), {
|
|
6493
|
+
method: "POST",
|
|
6494
|
+
headers: {
|
|
6495
|
+
...headers,
|
|
6496
|
+
"content-type": "application/json"
|
|
6497
|
+
},
|
|
6498
|
+
redirect: "manual",
|
|
6499
|
+
body: JSON.stringify({ keys: batch })
|
|
6500
|
+
});
|
|
6501
|
+
if (!isOk(response)) throw new Error(`物件下載失敗:${await failure(response)}`);
|
|
6502
|
+
const split = (name) => (response.headers.get(name) ?? "").split(",").map((k) => k.trim()).filter(Boolean);
|
|
6503
|
+
const absent = new Set(split("x-objects-missing"));
|
|
6504
|
+
const later = new Set(split("x-objects-deferred"));
|
|
6505
|
+
if (absent.size) warn(`會話 ${sid} 的 ${absent.size} 個物件接收端找不到,略過`);
|
|
6506
|
+
const present = batch.filter((k) => !absent.has(k) && !later.has(k));
|
|
6507
|
+
const parts = members(Buffer.from(await response.arrayBuffer()), present.length);
|
|
6508
|
+
present.forEach((k, j) => got.set(k, parts[j]));
|
|
6509
|
+
deferred.push(...batch.filter((k) => later.has(k)));
|
|
6510
|
+
}
|
|
6511
|
+
pending = deferred;
|
|
6512
|
+
}
|
|
6513
|
+
if (pending.length) throw new Error(`${pending.length} 個物件一直沒有取回`);
|
|
6514
|
+
return got;
|
|
6515
|
+
};
|
|
6516
|
+
for (const want of wanted) {
|
|
6517
|
+
const { sid } = want;
|
|
6518
|
+
if (!SAFE_ID.test(sid)) continue;
|
|
6519
|
+
try {
|
|
6520
|
+
const listed = await list(sid);
|
|
6521
|
+
if (listed.length === 0) continue;
|
|
6522
|
+
if ([...expectedSizes(sid, listed)].every(([path, size]) => {
|
|
6523
|
+
const file = transcriptFile(want, path);
|
|
6524
|
+
return size !== void 0 && existsSync(file) && statSync(file).size === size;
|
|
6525
|
+
})) continue;
|
|
6526
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
6527
|
+
for (const o of listed) {
|
|
6528
|
+
const { path } = parseTranscriptKey(sid, o.key);
|
|
6529
|
+
byPath.set(path, [...byPath.get(path) ?? [], {
|
|
6530
|
+
key: o.key,
|
|
6531
|
+
offset: o.offset
|
|
6532
|
+
}]);
|
|
6533
|
+
}
|
|
6534
|
+
const bytes = await download(sid, listed.map((o) => o.key));
|
|
6535
|
+
const files = /* @__PURE__ */ new Map();
|
|
6536
|
+
for (const [path, segments] of byPath) {
|
|
6537
|
+
const { used, gapAt } = joined(segments.filter((s) => bytes.has(s.key)).map((s) => ({
|
|
6538
|
+
...s,
|
|
6539
|
+
data: bytes.get(s.key)
|
|
6540
|
+
})), (s) => s.data.length);
|
|
6541
|
+
if (gapAt !== void 0) warn(`會話 ${sid} 的 ${path} 在位置 ${gapAt} 之前的分段不連續,只用前面已有的部分`);
|
|
6542
|
+
const parts = [];
|
|
6543
|
+
let end = 0;
|
|
6544
|
+
for (const s of used) {
|
|
6545
|
+
if (s.offset + s.data.length > end) parts.push(s.data.subarray(end - s.offset));
|
|
6546
|
+
end = Math.max(end, s.offset + s.data.length);
|
|
6547
|
+
}
|
|
6548
|
+
if (parts.length) files.set(path, Buffer.concat(parts));
|
|
6549
|
+
}
|
|
6550
|
+
for (const [path, data] of files) {
|
|
6551
|
+
const file = transcriptFile(want, path);
|
|
6552
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
6553
|
+
writeFileSync(file, data);
|
|
6554
|
+
}
|
|
6555
|
+
if (files.size) result.fetched.push(sid);
|
|
6556
|
+
} catch (error) {
|
|
6557
|
+
warn(`會話 ${sid} 的對話紀錄下載失敗:${thrown(error)}`);
|
|
6558
|
+
}
|
|
6559
|
+
}
|
|
6560
|
+
return result;
|
|
6561
|
+
}
|
|
6562
|
+
//#endregion
|
|
6337
6563
|
//#region ../core/dist/retro/receiver.js
|
|
6338
|
-
/** The environment variables a command reads the receiver from.
|
|
6564
|
+
/** The environment variables a command reads the receiver from. None set: the retro does not read the receiver. */
|
|
6339
6565
|
const RECEIVER_ENV = {
|
|
6340
6566
|
url: "MAINFLOW_TELEMETRY_URL",
|
|
6341
|
-
|
|
6567
|
+
accessClientId: "MAINFLOW_TELEMETRY_ACCESS_CLIENT_ID",
|
|
6568
|
+
accessClientSecret: "MAINFLOW_TELEMETRY_ACCESS_CLIENT_SECRET"
|
|
6342
6569
|
};
|
|
6343
|
-
/** The receiver named by the environment, or null when
|
|
6570
|
+
/** The receiver named by the environment, or null when none of the three variables is set. A partial set is passed on, so the report says which is missing. */
|
|
6344
6571
|
function receiverFromEnv(env) {
|
|
6345
6572
|
const url = env[RECEIVER_ENV.url]?.trim() ?? "";
|
|
6346
|
-
const
|
|
6347
|
-
|
|
6573
|
+
const accessClientId = env[RECEIVER_ENV.accessClientId]?.trim() ?? "";
|
|
6574
|
+
const accessClientSecret = env[RECEIVER_ENV.accessClientSecret]?.trim() ?? "";
|
|
6575
|
+
return url || accessClientId || accessClientSecret ? {
|
|
6348
6576
|
url,
|
|
6349
|
-
|
|
6577
|
+
accessClientId,
|
|
6578
|
+
accessClientSecret
|
|
6350
6579
|
} : null;
|
|
6351
6580
|
}
|
|
6352
6581
|
/** Longest window one export covers. */
|
|
@@ -6393,7 +6622,7 @@ function windows(scope) {
|
|
|
6393
6622
|
}
|
|
6394
6623
|
async function readReceiver(config, scopes) {
|
|
6395
6624
|
const doFetch = config.fetch ?? globalThis.fetch;
|
|
6396
|
-
const scrub = (text) => config.
|
|
6625
|
+
const scrub = (text) => config.accessClientSecret ? text.split(config.accessClientSecret).join("***") : text;
|
|
6397
6626
|
const problems = [];
|
|
6398
6627
|
const problem = (text) => problems.push(scrub(text));
|
|
6399
6628
|
const unread = (origin) => ({
|
|
@@ -6406,8 +6635,13 @@ async function readReceiver(config, scopes) {
|
|
|
6406
6635
|
requests: 0,
|
|
6407
6636
|
problems
|
|
6408
6637
|
});
|
|
6409
|
-
|
|
6410
|
-
|
|
6638
|
+
const absent = [
|
|
6639
|
+
...config.url ? [] : [RECEIVER_ENV.url],
|
|
6640
|
+
...config.accessClientId ? [] : [RECEIVER_ENV.accessClientId],
|
|
6641
|
+
...config.accessClientSecret ? [] : [RECEIVER_ENV.accessClientSecret]
|
|
6642
|
+
];
|
|
6643
|
+
if (absent.length) {
|
|
6644
|
+
problems.push(`${absent.join("、")} 沒有設定,沒有讀取接收端`);
|
|
6411
6645
|
return unread("");
|
|
6412
6646
|
}
|
|
6413
6647
|
let base;
|
|
@@ -6419,7 +6653,10 @@ async function readReceiver(config, scopes) {
|
|
|
6419
6653
|
return unread("");
|
|
6420
6654
|
}
|
|
6421
6655
|
const origin = base.origin;
|
|
6422
|
-
const auth = {
|
|
6656
|
+
const auth = {
|
|
6657
|
+
"cf-access-client-id": config.accessClientId,
|
|
6658
|
+
"cf-access-client-secret": config.accessClientSecret
|
|
6659
|
+
};
|
|
6423
6660
|
let requests = 0;
|
|
6424
6661
|
/** The reason a request failed: its status and the start of its text, or the network error. */
|
|
6425
6662
|
const failure = async (response) => {
|
|
@@ -6447,8 +6684,11 @@ async function readReceiver(config, scopes) {
|
|
|
6447
6684
|
u.searchParams.set("sid", scope.sessionId);
|
|
6448
6685
|
if (cursor) u.searchParams.set("cursor", cursor);
|
|
6449
6686
|
requests += 1;
|
|
6450
|
-
const response = await doFetch(u, {
|
|
6451
|
-
|
|
6687
|
+
const response = await doFetch(u, {
|
|
6688
|
+
headers: auth,
|
|
6689
|
+
redirect: "manual"
|
|
6690
|
+
});
|
|
6691
|
+
if (!isOk(response)) {
|
|
6452
6692
|
problem(`${where}的物件清單讀取失敗:${await failure(response)},這段的資料缺`);
|
|
6453
6693
|
break;
|
|
6454
6694
|
}
|
|
@@ -6484,9 +6724,10 @@ async function readReceiver(config, scopes) {
|
|
|
6484
6724
|
...auth,
|
|
6485
6725
|
"content-type": "application/json"
|
|
6486
6726
|
},
|
|
6727
|
+
redirect: "manual",
|
|
6487
6728
|
body: JSON.stringify({ keys: batch })
|
|
6488
6729
|
});
|
|
6489
|
-
if (!response
|
|
6730
|
+
if (!isOk(response)) {
|
|
6490
6731
|
problem(`${batch.length} 個物件下載失敗(${keysText(batch)}):${await failure(response)},這些列缺`);
|
|
6491
6732
|
append(missing, batch);
|
|
6492
6733
|
return;
|
|
@@ -6756,6 +6997,29 @@ const secondText = (ms) => {
|
|
|
6756
6997
|
const d = new Date(ms);
|
|
6757
6998
|
return `${d.toLocaleDateString("sv-SE")}T${d.toTimeString().slice(0, 8)}`;
|
|
6758
6999
|
};
|
|
7000
|
+
/** Where transcripts fetched from the receiver are kept: in the hub's local directory, which the hub ignores. */
|
|
7001
|
+
const transcriptCache = (root) => join(root, ".mainflow/local/transcripts");
|
|
7002
|
+
/** The transcript to read: the recorded path when the file is there, else the copy fetched from the receiver, else the recorded path. */
|
|
7003
|
+
function transcriptOf(root, transcript) {
|
|
7004
|
+
const recorded = transcriptPath(root, transcript);
|
|
7005
|
+
if (existsSync(recorded)) return recorded;
|
|
7006
|
+
const cached = join(transcriptCache(root), basename(recorded));
|
|
7007
|
+
return existsSync(cached) ? cached : recorded;
|
|
7008
|
+
}
|
|
7009
|
+
/** Sessions serving the mainline whose recorded transcript is not on this machine, as the receiver knows them. */
|
|
7010
|
+
function missingTranscripts(root, mainlineId) {
|
|
7011
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
7012
|
+
for (const s of loadSessions(root).sessions) {
|
|
7013
|
+
if (!s.transcript || !s.actions.some((a) => servesMainline(a, mainlineId))) continue;
|
|
7014
|
+
if (existsSync(transcriptPath(root, s.transcript))) continue;
|
|
7015
|
+
const sid = basename(s.transcript, ".jsonl");
|
|
7016
|
+
wanted.set(sid, {
|
|
7017
|
+
sid,
|
|
7018
|
+
dir: transcriptCache(root)
|
|
7019
|
+
});
|
|
7020
|
+
}
|
|
7021
|
+
return [...wanted.values()];
|
|
7022
|
+
}
|
|
6759
7023
|
/** Subagent transcripts Claude Code keeps beside a session's: <dir>/<session>/subagents/*.jsonl. */
|
|
6760
7024
|
function subagentFiles(transcript) {
|
|
6761
7025
|
const dir = join(transcript.replace(/\.jsonl$/, ""), "subagents");
|
|
@@ -6888,7 +7152,7 @@ function readGroups(root, mainlineId, windowOf) {
|
|
|
6888
7152
|
};
|
|
6889
7153
|
const ordered = [...serving].sort((a, b) => Number(Boolean(a.delegatedFrom)) - Number(Boolean(b.delegatedFrom)));
|
|
6890
7154
|
for (const s of ordered) {
|
|
6891
|
-
const file = s.transcript ?
|
|
7155
|
+
const file = s.transcript ? transcriptOf(root, s.transcript) : void 0;
|
|
6892
7156
|
if (s.delegatedFrom) {
|
|
6893
7157
|
if (!file || seen.has(file)) continue;
|
|
6894
7158
|
const parent = groups.find((g) => g.session === s.delegatedFrom);
|
|
@@ -6949,6 +7213,18 @@ function retroReport(root, mainlineId, options = {}) {
|
|
|
6949
7213
|
* `measurements.problems`.
|
|
6950
7214
|
*/
|
|
6951
7215
|
async function retroReportFromReceiver(root, mainlineId, receiver, options = {}) {
|
|
7216
|
+
if (receiver.url && receiver.accessClientId && receiver.accessClientSecret) {
|
|
7217
|
+
const wanted = missingTranscripts(root, mainlineId);
|
|
7218
|
+
if (wanted.length) {
|
|
7219
|
+
const { warnings } = await fetchTranscripts({
|
|
7220
|
+
url: receiver.url,
|
|
7221
|
+
accessClientId: receiver.accessClientId,
|
|
7222
|
+
accessClientSecret: receiver.accessClientSecret,
|
|
7223
|
+
...receiver.fetch ? { fetch: receiver.fetch } : {}
|
|
7224
|
+
}, wanted);
|
|
7225
|
+
for (const line of warnings) (receiver.warn ?? ((l) => process.stderr.write(`${l}\n`)))(line);
|
|
7226
|
+
}
|
|
7227
|
+
}
|
|
6952
7228
|
const read = readRetro(root, mainlineId, options);
|
|
6953
7229
|
const t = performance.now();
|
|
6954
7230
|
const exported = await readReceiver(receiver, receiverScopes(read.groups));
|
package/dist/host.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as openInApp, c as syncHost, i as hostName, n as DEFAULT_UI_PORT, o as removeHostLink, r as caddySites, s as siteBlock, t as APP_BUNDLE_ID } from "./host-
|
|
1
|
+
import { a as openInApp, c as syncHost, i as hostName, n as DEFAULT_UI_PORT, o as removeHostLink, r as caddySites, s as siteBlock, t as APP_BUNDLE_ID } from "./host-79T-_NMu.js";
|
|
2
2
|
export { APP_BUNDLE_ID, DEFAULT_UI_PORT, caddySites, hostName, openInApp, removeHostLink, siteBlock, syncHost };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as syncHost, ht as WriteError, i as hostName, mt as DATA_DIR, n as DEFAULT_UI_PORT, o as removeHostLink } from "./host-
|
|
1
|
+
import { c as syncHost, ht as WriteError, i as hostName, mt as DATA_DIR, n as DEFAULT_UI_PORT, o as removeHostLink } from "./host-79T-_NMu.js";
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { spawn, spawnSync } from "node:child_process";
|
|
@@ -176,7 +176,7 @@ async function launchUi(root, hub, configured, options) {
|
|
|
176
176
|
}
|
|
177
177
|
let server;
|
|
178
178
|
if (!reuse) {
|
|
179
|
-
const { serveUi } = await import("./ui-
|
|
179
|
+
const { serveUi } = await import("./ui-BcXPk-0P.js");
|
|
180
180
|
server = await serveUi(root, choice.port);
|
|
181
181
|
writeUiRecord(root, {
|
|
182
182
|
port: choice.port,
|
package/dist/launch.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as portCandidates, c as watchHubGone, i as networkProbe, n as choosePort, o as readUiRecord, r as launchUi, s as startUiInBackground, t as PORT_SPAN } from "./launch-
|
|
1
|
+
import { a as portCandidates, c as watchHubGone, i as networkProbe, n as choosePort, o as readUiRecord, r as launchUi, s as startUiInBackground, t as PORT_SPAN } from "./launch-DQVyIbpf.js";
|
|
2
2
|
export { PORT_SPAN, choosePort, launchUi, networkProbe, portCandidates, readUiRecord, startUiInBackground, watchHubGone };
|
|
Binary file
|
|
Binary file
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as MAX_FILE_BYTES, F as renumberFiles, I as renumberPath, L as renumberText, N as applyOffline, P as renumberRequest, Q as MAX_BLOB_BYTES, R as scopeOfPath, X as ACCESS_HEADERS, Y as nodeStorage, Z as BLOB_HASH, _t as splitFrontMatter, at as requestTime, et as ROUTES, gt as contentHash, ht as WriteError, it as readsAsText, mt as DATA_DIR, nt as comparePaths, ot as textBytes, pt as ConflictError, q as nowMinute, rt as exceedsFileLimit, tt as SOCKET_MAX_AGE_CLOSE, z as applyOperation } from "./host-
|
|
1
|
+
import { $ as MAX_FILE_BYTES, F as renumberFiles, I as renumberPath, L as renumberText, N as applyOffline, P as renumberRequest, Q as MAX_BLOB_BYTES, R as scopeOfPath, X as ACCESS_HEADERS, Y as nodeStorage, Z as BLOB_HASH, _t as splitFrontMatter, at as requestTime, et as ROUTES, gt as contentHash, ht as WriteError, it as readsAsText, mt as DATA_DIR, nt as comparePaths, ot as textBytes, pt as ConflictError, q as nowMinute, rt as exceedsFileLimit, tt as SOCKET_MAX_AGE_CLOSE, z as applyOperation } from "./host-79T-_NMu.js";
|
|
2
2
|
import { createHash, randomBytes } from "node:crypto";
|
|
3
3
|
import { accessSync, appendFileSync, chmodSync, closeSync, constants, copyFileSync, createReadStream, createWriteStream, existsSync, linkSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, renameSync, rmSync, statSync, watch, writeFileSync } from "node:fs";
|
|
4
4
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { j as configSchema } from "./dist-IdbnCDqM.js";
|
|
2
|
-
import { A as attributeTrace, C as transcriptPath, G as linksInto, H as loadHub, J as readYaml, K as refIndex, M as transcriptTitle, S as readSession, U as dependencyGraph, W as mainlineEdges, a as openInApp, h as setArchivedMany, ht as WriteError, j as traceTranscript, m as ARCHIVE_KINDS, mt as DATA_DIR, pt as ConflictError, x as loadSessions } from "./host-
|
|
2
|
+
import { A as attributeTrace, C as transcriptPath, G as linksInto, H as loadHub, J as readYaml, K as refIndex, M as transcriptTitle, S as readSession, U as dependencyGraph, W as mainlineEdges, a as openInApp, h as setArchivedMany, ht as WriteError, j as traceTranscript, m as ARCHIVE_KINDS, mt as DATA_DIR, pt as ConflictError, x as loadSessions } from "./host-79T-_NMu.js";
|
|
3
3
|
import { t as listProgress } from "./progress-1BfPg2zN.js";
|
|
4
|
-
import { A as syncPaths, O as SYNC_DIR, _ as readDaemon, a as remoteMode, h as callDaemon, k as isSynced, m as DaemonUnavailable, n as hubWriter, r as writtenLines, y as readRemote } from "./operation
|
|
5
|
-
import { l as CLI_VERSION } from "./launch-
|
|
4
|
+
import { A as syncPaths, O as SYNC_DIR, _ as readDaemon, a as remoteMode, h as callDaemon, k as isSynced, m as DaemonUnavailable, n as hubWriter, r as writtenLines, y as readRemote } from "./operation--fJnUqvA.js";
|
|
5
|
+
import { l as CLI_VERSION } from "./launch-DQVyIbpf.js";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
7
|
import { closeSync, existsSync, lstatSync, openSync, readFileSync, readSync, realpathSync, statSync, watch } from "node:fs";
|
|
8
8
|
import { basename, dirname, extname, join, normalize, relative, resolve, sep } from "node:path";
|