@usepowerplant/cli 0.4.6 → 0.4.8
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/README.md +10 -5
- package/dist/cli.js +865 -282
- package/dist/menubar/Powerplant.app.zip +0 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -55485,20 +55485,210 @@ function workspaceClaimsAnyRepo(policy) {
|
|
|
55485
55485
|
return policy.repos !== null ? policy.repos.length > 0 : policy.owners.length > 0;
|
|
55486
55486
|
}
|
|
55487
55487
|
|
|
55488
|
+
// ../core/src/encodedImages.ts
|
|
55489
|
+
var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
55490
|
+
function isEncodedImage(data, mediaType) {
|
|
55491
|
+
if (data.length < 16 || data.length % 4 !== 0) return false;
|
|
55492
|
+
const padding = data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0;
|
|
55493
|
+
const body = data.slice(0, data.length - padding);
|
|
55494
|
+
if (/[^A-Za-z0-9+/]/.test(body)) return false;
|
|
55495
|
+
const last = BASE64_ALPHABET.indexOf(body.at(-1));
|
|
55496
|
+
if (padding === 2 && (last & 15) !== 0 || padding === 1 && (last & 3) !== 0) return false;
|
|
55497
|
+
switch (mediaType) {
|
|
55498
|
+
case "image/png":
|
|
55499
|
+
return isPng(atob(data));
|
|
55500
|
+
case "image/jpeg":
|
|
55501
|
+
return isJpeg(atob(data));
|
|
55502
|
+
case "image/gif":
|
|
55503
|
+
return isGif(atob(data));
|
|
55504
|
+
case "image/webp":
|
|
55505
|
+
return isWebp(atob(data));
|
|
55506
|
+
default:
|
|
55507
|
+
return false;
|
|
55508
|
+
}
|
|
55509
|
+
}
|
|
55510
|
+
function u16(data, offset) {
|
|
55511
|
+
return data.charCodeAt(offset) << 8 | data.charCodeAt(offset + 1);
|
|
55512
|
+
}
|
|
55513
|
+
function u32(data, offset) {
|
|
55514
|
+
return u16(data, offset) * 65536 + u16(data, offset + 2) >>> 0;
|
|
55515
|
+
}
|
|
55516
|
+
function little32(data, offset) {
|
|
55517
|
+
return (data.charCodeAt(offset) | data.charCodeAt(offset + 1) << 8 | data.charCodeAt(offset + 2) << 16 | data.charCodeAt(offset + 3) << 24) >>> 0;
|
|
55518
|
+
}
|
|
55519
|
+
var CRC_TABLE = Array.from({ length: 256 }, (_, value) => {
|
|
55520
|
+
for (let bit = 0; bit < 8; bit++) value = value & 1 ? 3988292384 ^ value >>> 1 : value >>> 1;
|
|
55521
|
+
return value >>> 0;
|
|
55522
|
+
});
|
|
55523
|
+
function crc32(data, start, end) {
|
|
55524
|
+
let crc = 4294967295;
|
|
55525
|
+
for (let i = start; i < end; i++)
|
|
55526
|
+
crc = CRC_TABLE[(crc ^ data.charCodeAt(i)) & 255] ^ crc >>> 8;
|
|
55527
|
+
return (crc ^ 4294967295) >>> 0;
|
|
55528
|
+
}
|
|
55529
|
+
function isPng(data) {
|
|
55530
|
+
if (!data.startsWith("\x89PNG\r\n\n")) return false;
|
|
55531
|
+
let offset = 8;
|
|
55532
|
+
let imageData = false;
|
|
55533
|
+
let dataEnded = false;
|
|
55534
|
+
let palette = false;
|
|
55535
|
+
let indexed = false;
|
|
55536
|
+
while (offset + 12 <= data.length) {
|
|
55537
|
+
const size = u32(data, offset);
|
|
55538
|
+
const type = data.slice(offset + 4, offset + 8);
|
|
55539
|
+
const end = offset + 8 + size;
|
|
55540
|
+
if (size > 2147483647 || end + 4 > data.length || !/^[A-Za-z]{2}[A-Z][A-Za-z]$/.test(type))
|
|
55541
|
+
return false;
|
|
55542
|
+
if (u32(data, end) !== crc32(data, offset + 4, end)) return false;
|
|
55543
|
+
if (offset === 8) {
|
|
55544
|
+
if (type !== "IHDR" || size !== 13 || !u32(data, offset + 8) || !u32(data, offset + 12))
|
|
55545
|
+
return false;
|
|
55546
|
+
const depth = data.charCodeAt(offset + 16);
|
|
55547
|
+
const color = data.charCodeAt(offset + 17);
|
|
55548
|
+
const validDepth = color === 0 ? [1, 2, 4, 8, 16] : color === 3 ? [1, 2, 4, 8] : [8, 16];
|
|
55549
|
+
if (![0, 2, 3, 4, 6].includes(color) || !validDepth.includes(depth)) return false;
|
|
55550
|
+
if (data.charCodeAt(offset + 18) !== 0 || data.charCodeAt(offset + 19) !== 0 || data.charCodeAt(offset + 20) > 1)
|
|
55551
|
+
return false;
|
|
55552
|
+
indexed = color === 3;
|
|
55553
|
+
} else if (type === "IHDR") return false;
|
|
55554
|
+
else if (type === "PLTE") {
|
|
55555
|
+
if (imageData || palette || size === 0 || size > 768 || size % 3 !== 0) return false;
|
|
55556
|
+
palette = true;
|
|
55557
|
+
} else if (type === "IDAT") {
|
|
55558
|
+
if (dataEnded || indexed && !palette) return false;
|
|
55559
|
+
imageData = imageData || size > 0;
|
|
55560
|
+
} else if (type === "IEND") return imageData && size === 0 && end + 4 === data.length;
|
|
55561
|
+
else if (type[0] === type[0].toUpperCase()) return false;
|
|
55562
|
+
if (imageData && type !== "IDAT") dataEnded = true;
|
|
55563
|
+
offset = end + 4;
|
|
55564
|
+
}
|
|
55565
|
+
return false;
|
|
55566
|
+
}
|
|
55567
|
+
function isJpeg(data) {
|
|
55568
|
+
if (!data.startsWith("\xFF\xD8")) return false;
|
|
55569
|
+
let offset = 2;
|
|
55570
|
+
let frame = false;
|
|
55571
|
+
let scan = false;
|
|
55572
|
+
while (offset < data.length) {
|
|
55573
|
+
if (data.charCodeAt(offset++) !== 255) return false;
|
|
55574
|
+
while (data.charCodeAt(offset) === 255) offset++;
|
|
55575
|
+
const marker = data.charCodeAt(offset++);
|
|
55576
|
+
if (marker === 217) return frame && scan && offset === data.length;
|
|
55577
|
+
if (marker === 0 || marker === 216 || marker >= 208 && marker <= 215) return false;
|
|
55578
|
+
if (marker === 1) continue;
|
|
55579
|
+
if (offset + 2 > data.length) return false;
|
|
55580
|
+
const size = u16(data, offset);
|
|
55581
|
+
if (size < 2 || offset + size > data.length) return false;
|
|
55582
|
+
if (marker >= 192 && marker <= 207 && ![196, 200, 204].includes(marker)) {
|
|
55583
|
+
if (size < 11 || size !== 8 + 3 * data.charCodeAt(offset + 7) || !u16(data, offset + 3) || !u16(data, offset + 5))
|
|
55584
|
+
return false;
|
|
55585
|
+
frame = true;
|
|
55586
|
+
}
|
|
55587
|
+
if (marker !== 218) {
|
|
55588
|
+
offset += size;
|
|
55589
|
+
continue;
|
|
55590
|
+
}
|
|
55591
|
+
if (!frame || size < 8 || size !== 6 + 2 * data.charCodeAt(offset + 2)) return false;
|
|
55592
|
+
offset += size;
|
|
55593
|
+
const start = offset;
|
|
55594
|
+
for (; ; ) {
|
|
55595
|
+
const next = data.indexOf("\xFF", offset);
|
|
55596
|
+
if (next < 0 || next + 1 >= data.length) return false;
|
|
55597
|
+
const following = data.charCodeAt(next + 1);
|
|
55598
|
+
if (following === 0 || following >= 208 && following <= 215) offset = next + 2;
|
|
55599
|
+
else if (following === 255) offset = next + 1;
|
|
55600
|
+
else {
|
|
55601
|
+
offset = next;
|
|
55602
|
+
break;
|
|
55603
|
+
}
|
|
55604
|
+
}
|
|
55605
|
+
if (offset <= start) return false;
|
|
55606
|
+
scan = true;
|
|
55607
|
+
}
|
|
55608
|
+
return false;
|
|
55609
|
+
}
|
|
55610
|
+
function skipGifBlocks(data, offset) {
|
|
55611
|
+
while (offset < data.length) {
|
|
55612
|
+
const size = data.charCodeAt(offset++);
|
|
55613
|
+
if (size === 0) return offset;
|
|
55614
|
+
offset += size;
|
|
55615
|
+
}
|
|
55616
|
+
return -1;
|
|
55617
|
+
}
|
|
55618
|
+
function isGif(data) {
|
|
55619
|
+
if (!data.startsWith("GIF87a") && !data.startsWith("GIF89a") || data.length < 13) return false;
|
|
55620
|
+
if (!(data.charCodeAt(6) | data.charCodeAt(7)) || !(data.charCodeAt(8) | data.charCodeAt(9)))
|
|
55621
|
+
return false;
|
|
55622
|
+
const packed = data.charCodeAt(10);
|
|
55623
|
+
let offset = 13 + (packed & 128 ? 3 * (2 << (packed & 7)) : 0);
|
|
55624
|
+
let image = false;
|
|
55625
|
+
while (offset < data.length) {
|
|
55626
|
+
const marker = data.charCodeAt(offset++);
|
|
55627
|
+
if (marker === 59) return image && offset === data.length;
|
|
55628
|
+
if (marker === 33) offset = skipGifBlocks(data, offset + 1);
|
|
55629
|
+
else if (marker === 44) {
|
|
55630
|
+
if (offset + 9 > data.length || !(data.charCodeAt(offset + 4) | data.charCodeAt(offset + 5)) || !(data.charCodeAt(offset + 6) | data.charCodeAt(offset + 7)))
|
|
55631
|
+
return false;
|
|
55632
|
+
const local = data.charCodeAt(offset + 8);
|
|
55633
|
+
offset += 9 + (local & 128 ? 3 * (2 << (local & 7)) : 0);
|
|
55634
|
+
const codeSize = data.charCodeAt(offset++);
|
|
55635
|
+
if (codeSize < 2 || codeSize > 8 || offset >= data.length || data.charCodeAt(offset) === 0)
|
|
55636
|
+
return false;
|
|
55637
|
+
offset = skipGifBlocks(data, offset);
|
|
55638
|
+
image = true;
|
|
55639
|
+
} else return false;
|
|
55640
|
+
if (offset < 0) return false;
|
|
55641
|
+
}
|
|
55642
|
+
return false;
|
|
55643
|
+
}
|
|
55644
|
+
function isWebp(data) {
|
|
55645
|
+
return data.startsWith("RIFF") && data.slice(8, 12) === "WEBP" && little32(data, 4) + 8 === data.length && webpChunks(data, 12, data.length, true);
|
|
55646
|
+
}
|
|
55647
|
+
function webpChunks(data, start, end, allowFrames) {
|
|
55648
|
+
let image = false;
|
|
55649
|
+
let offset = start;
|
|
55650
|
+
while (offset + 8 <= end) {
|
|
55651
|
+
const type = data.slice(offset, offset + 4);
|
|
55652
|
+
const size = little32(data, offset + 4);
|
|
55653
|
+
const body = offset + 8;
|
|
55654
|
+
const next = body + size + size % 2;
|
|
55655
|
+
if (next > end) return false;
|
|
55656
|
+
if (type === "VP8 ") {
|
|
55657
|
+
if (size < 11 || data.charCodeAt(body) & 1 || data.slice(body + 3, body + 6) !== "\x9D*")
|
|
55658
|
+
return false;
|
|
55659
|
+
if (!((data.charCodeAt(body + 6) | data.charCodeAt(body + 7) << 8) & 16383) || !((data.charCodeAt(body + 8) | data.charCodeAt(body + 9) << 8) & 16383))
|
|
55660
|
+
return false;
|
|
55661
|
+
image = true;
|
|
55662
|
+
} else if (type === "VP8L") {
|
|
55663
|
+
if (size < 6 || data.charCodeAt(body) !== 47 || data.charCodeAt(body + 4) & 224)
|
|
55664
|
+
return false;
|
|
55665
|
+
image = true;
|
|
55666
|
+
} else if (type === "VP8X") {
|
|
55667
|
+
if (size !== 10 || !allowFrames) return false;
|
|
55668
|
+
} else if (type === "ANMF") {
|
|
55669
|
+
if (!allowFrames || size < 16 || !webpChunks(data, body + 16, body + size, false))
|
|
55670
|
+
return false;
|
|
55671
|
+
image = true;
|
|
55672
|
+
}
|
|
55673
|
+
offset = next;
|
|
55674
|
+
}
|
|
55675
|
+
return image && offset === end;
|
|
55676
|
+
}
|
|
55677
|
+
|
|
55488
55678
|
// ../core/src/transcriptImages.ts
|
|
55489
55679
|
var IMAGE_DATA_URI_RE = /^data:(image\/[\w.+-]+);base64,([A-Za-z0-9+/=_-]*)$/;
|
|
55490
55680
|
function isImageDataUri(value) {
|
|
55491
|
-
|
|
55681
|
+
if (!value.startsWith("data:image/")) return false;
|
|
55682
|
+
const match = IMAGE_DATA_URI_RE.exec(value);
|
|
55683
|
+
return match !== null && isEncodedImage(match[2], match[1]);
|
|
55492
55684
|
}
|
|
55493
55685
|
function isImageBase64Payload(record2, key) {
|
|
55494
|
-
|
|
55495
|
-
if (
|
|
55496
|
-
if (key === "base64") return
|
|
55686
|
+
const data = record2[key];
|
|
55687
|
+
if (typeof data !== "string") return false;
|
|
55688
|
+
if (key === "data" && record2.type === "base64") return isEncodedImage(data, record2.media_type);
|
|
55689
|
+
if (key === "base64") return isEncodedImage(data, record2.type);
|
|
55497
55690
|
return false;
|
|
55498
55691
|
}
|
|
55499
|
-
function isImageMediaType(value) {
|
|
55500
|
-
return typeof value === "string" && value.startsWith("image/");
|
|
55501
|
-
}
|
|
55502
55692
|
function describeImageBlock(block2) {
|
|
55503
55693
|
if (block2.type !== "image") return null;
|
|
55504
55694
|
const source = asRecord(block2.source);
|
|
@@ -55700,10 +55890,21 @@ function createDigestTurnParser(options = {}) {
|
|
|
55700
55890
|
const turns = [];
|
|
55701
55891
|
let pendingTools = [];
|
|
55702
55892
|
let pendingUsage = null;
|
|
55893
|
+
let pendingAt = null;
|
|
55703
55894
|
let currentId = null;
|
|
55704
55895
|
let currentCarrier = null;
|
|
55705
55896
|
let currentBase = null;
|
|
55706
55897
|
let currentCounted = false;
|
|
55898
|
+
const envelopes = [];
|
|
55899
|
+
let pendingEnvelopes = [];
|
|
55900
|
+
let currentEnvelope = null;
|
|
55901
|
+
const landPending = (turn) => {
|
|
55902
|
+
for (const envelope of pendingEnvelopes) {
|
|
55903
|
+
envelope.turn = turn;
|
|
55904
|
+
envelopes.push(envelope);
|
|
55905
|
+
}
|
|
55906
|
+
pendingEnvelopes = [];
|
|
55907
|
+
};
|
|
55707
55908
|
return {
|
|
55708
55909
|
pushLine(line) {
|
|
55709
55910
|
const trimmed = line.trim();
|
|
@@ -55717,14 +55918,16 @@ function createDigestTurnParser(options = {}) {
|
|
|
55717
55918
|
return;
|
|
55718
55919
|
}
|
|
55719
55920
|
if (record2.isMeta === true) return;
|
|
55921
|
+
const at = recordTime(record2);
|
|
55720
55922
|
if (record2.type === "user") {
|
|
55721
55923
|
const text = userText(record2);
|
|
55722
|
-
if (text) turns.push({ kind: "human_prompt", text, toolCalls: [], usage: null });
|
|
55924
|
+
if (text) turns.push({ kind: "human_prompt", text, toolCalls: [], usage: null, at });
|
|
55723
55925
|
return;
|
|
55724
55926
|
}
|
|
55725
55927
|
if (record2.type !== "assistant") return;
|
|
55726
55928
|
const message2 = record2.message;
|
|
55727
55929
|
if (!message2 || message2.model === "<synthetic>" || !Array.isArray(message2.content)) return;
|
|
55930
|
+
pendingAt ??= at;
|
|
55728
55931
|
const id = messageIdOf(record2, message2);
|
|
55729
55932
|
if (id === null || id !== currentId) {
|
|
55730
55933
|
currentId = id;
|
|
@@ -55738,12 +55941,17 @@ function createDigestTurnParser(options = {}) {
|
|
|
55738
55941
|
const updated = mergeUsage(currentBase, parsedUsage);
|
|
55739
55942
|
if (currentCarrier) currentCarrier.usage = updated;
|
|
55740
55943
|
else pendingUsage = updated;
|
|
55944
|
+
if (currentEnvelope) currentEnvelope.usage = parsedUsage;
|
|
55741
55945
|
} else if (currentCarrier) {
|
|
55742
55946
|
currentCarrier.usage = mergeUsage(currentCarrier.usage, parsedUsage);
|
|
55743
55947
|
currentCounted = true;
|
|
55948
|
+
currentEnvelope = { turn: turns.indexOf(currentCarrier), at, usage: parsedUsage };
|
|
55949
|
+
envelopes.push(currentEnvelope);
|
|
55744
55950
|
} else {
|
|
55745
55951
|
pendingUsage = mergeUsage(pendingUsage, parsedUsage);
|
|
55746
55952
|
currentCounted = true;
|
|
55953
|
+
currentEnvelope = { turn: -1, at, usage: parsedUsage };
|
|
55954
|
+
pendingEnvelopes.push(currentEnvelope);
|
|
55747
55955
|
}
|
|
55748
55956
|
}
|
|
55749
55957
|
const model = typeof message2.model === "string" ? message2.model : null;
|
|
@@ -55758,32 +55966,50 @@ function createDigestTurnParser(options = {}) {
|
|
|
55758
55966
|
}
|
|
55759
55967
|
if (part.type === "text" && typeof part.text === "string" && part.text.trim() !== "") {
|
|
55760
55968
|
if (currentCarrier === null) {
|
|
55761
|
-
const turn = {
|
|
55969
|
+
const turn = {
|
|
55970
|
+
kind: "agent_text",
|
|
55971
|
+
text: part.text,
|
|
55972
|
+
toolCalls: pendingTools,
|
|
55973
|
+
usage: pendingUsage,
|
|
55974
|
+
at: pendingAt
|
|
55975
|
+
};
|
|
55762
55976
|
turns.push(turn);
|
|
55763
55977
|
currentCarrier = turn;
|
|
55764
55978
|
pendingUsage = null;
|
|
55979
|
+
landPending(turns.length - 1);
|
|
55765
55980
|
} else {
|
|
55766
55981
|
turns.push({
|
|
55767
55982
|
kind: "agent_text",
|
|
55768
55983
|
text: part.text,
|
|
55769
55984
|
toolCalls: pendingTools,
|
|
55770
|
-
usage: currentCounted ? zeroUsage(model) : null
|
|
55985
|
+
usage: currentCounted ? zeroUsage(model) : null,
|
|
55986
|
+
// A sibling block of the same record began when the record did.
|
|
55987
|
+
at: pendingAt ?? at
|
|
55771
55988
|
});
|
|
55772
55989
|
}
|
|
55773
55990
|
pendingTools = [];
|
|
55991
|
+
pendingAt = null;
|
|
55774
55992
|
}
|
|
55775
55993
|
}
|
|
55776
55994
|
},
|
|
55777
55995
|
finish() {
|
|
55778
55996
|
if (pendingTools.length > 0 || pendingUsage !== null) {
|
|
55779
|
-
turns.push({ kind: "agent_text", text: "", toolCalls: pendingTools, usage: pendingUsage });
|
|
55997
|
+
turns.push({ kind: "agent_text", text: "", toolCalls: pendingTools, usage: pendingUsage, at: pendingAt });
|
|
55998
|
+
landPending(turns.length - 1);
|
|
55780
55999
|
pendingTools = [];
|
|
55781
56000
|
pendingUsage = null;
|
|
56001
|
+
pendingAt = null;
|
|
55782
56002
|
}
|
|
55783
56003
|
return turns;
|
|
56004
|
+
},
|
|
56005
|
+
usageEnvelopes() {
|
|
56006
|
+
return envelopes;
|
|
55784
56007
|
}
|
|
55785
56008
|
};
|
|
55786
56009
|
}
|
|
56010
|
+
function recordTime(record2) {
|
|
56011
|
+
return typeof record2.timestamp === "string" && record2.timestamp !== "" ? record2.timestamp : null;
|
|
56012
|
+
}
|
|
55787
56013
|
function isRecord(value) {
|
|
55788
56014
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
55789
56015
|
}
|
|
@@ -55920,10 +56146,12 @@ function promptText(content) {
|
|
|
55920
56146
|
function createCodexTurnParser(options = {}) {
|
|
55921
56147
|
const turns = [];
|
|
55922
56148
|
let pendingTools = [];
|
|
56149
|
+
let pendingAt = null;
|
|
55923
56150
|
const flushPendingTools = () => {
|
|
55924
56151
|
if (pendingTools.length === 0) return;
|
|
55925
|
-
turns.push({ kind: "agent_text", text: "", toolCalls: pendingTools, usage: null });
|
|
56152
|
+
turns.push({ kind: "agent_text", text: "", toolCalls: pendingTools, usage: null, at: pendingAt });
|
|
55926
56153
|
pendingTools = [];
|
|
56154
|
+
pendingAt = null;
|
|
55927
56155
|
};
|
|
55928
56156
|
return {
|
|
55929
56157
|
pushLine(line) {
|
|
@@ -55938,6 +56166,7 @@ function createCodexTurnParser(options = {}) {
|
|
|
55938
56166
|
return;
|
|
55939
56167
|
}
|
|
55940
56168
|
if (record2.type !== "response_item") return;
|
|
56169
|
+
const at = typeof record2.timestamp === "string" && record2.timestamp !== "" ? record2.timestamp : null;
|
|
55941
56170
|
const item = record2.payload;
|
|
55942
56171
|
if (item === null || typeof item !== "object" || Array.isArray(item)) return;
|
|
55943
56172
|
const responseItem = item;
|
|
@@ -55949,6 +56178,7 @@ function createCodexTurnParser(options = {}) {
|
|
|
55949
56178
|
if (args !== null) call.input = boundedToolInput(args);
|
|
55950
56179
|
}
|
|
55951
56180
|
pendingTools.push(call);
|
|
56181
|
+
pendingAt ??= at;
|
|
55952
56182
|
return;
|
|
55953
56183
|
}
|
|
55954
56184
|
if (responseItem.type !== "message") return;
|
|
@@ -55956,15 +56186,16 @@ function createCodexTurnParser(options = {}) {
|
|
|
55956
56186
|
const text2 = promptText(responseItem.content);
|
|
55957
56187
|
if (text2 !== null) {
|
|
55958
56188
|
flushPendingTools();
|
|
55959
|
-
turns.push({ kind: "human_prompt", text: text2, toolCalls: [], usage: null });
|
|
56189
|
+
turns.push({ kind: "human_prompt", text: text2, toolCalls: [], usage: null, at });
|
|
55960
56190
|
}
|
|
55961
56191
|
return;
|
|
55962
56192
|
}
|
|
55963
56193
|
if (responseItem.role !== "assistant") return;
|
|
55964
56194
|
const text = outputTextBlocks(responseItem.content).join("\n");
|
|
55965
56195
|
if (text.trim() === "") return;
|
|
55966
|
-
turns.push({ kind: "agent_text", text, toolCalls: pendingTools, usage: null });
|
|
56196
|
+
turns.push({ kind: "agent_text", text, toolCalls: pendingTools, usage: null, at: pendingAt ?? at });
|
|
55967
56197
|
pendingTools = [];
|
|
56198
|
+
pendingAt = null;
|
|
55968
56199
|
},
|
|
55969
56200
|
finish() {
|
|
55970
56201
|
flushPendingTools();
|
|
@@ -55977,10 +56208,12 @@ function createCodexTurnParser(options = {}) {
|
|
|
55977
56208
|
function createCursorTurnParser(options = {}) {
|
|
55978
56209
|
const turns = [];
|
|
55979
56210
|
let pendingTools = [];
|
|
56211
|
+
let pendingAt = null;
|
|
55980
56212
|
const flushPendingTools = () => {
|
|
55981
56213
|
if (pendingTools.length === 0) return;
|
|
55982
|
-
turns.push({ kind: "agent_text", text: "", toolCalls: pendingTools, usage: null });
|
|
56214
|
+
turns.push({ kind: "agent_text", text: "", toolCalls: pendingTools, usage: null, at: pendingAt });
|
|
55983
56215
|
pendingTools = [];
|
|
56216
|
+
pendingAt = null;
|
|
55984
56217
|
};
|
|
55985
56218
|
return {
|
|
55986
56219
|
pushLine(line) {
|
|
@@ -55994,9 +56227,10 @@ function createCursorTurnParser(options = {}) {
|
|
|
55994
56227
|
} catch {
|
|
55995
56228
|
return;
|
|
55996
56229
|
}
|
|
56230
|
+
const at = typeof record2.createdAt === "string" && record2.createdAt !== "" ? record2.createdAt : null;
|
|
55997
56231
|
if (record2.type === "user" && typeof record2.text === "string" && record2.text.trim() !== "") {
|
|
55998
56232
|
flushPendingTools();
|
|
55999
|
-
turns.push({ kind: "human_prompt", text: record2.text, toolCalls: [], usage: null });
|
|
56233
|
+
turns.push({ kind: "human_prompt", text: record2.text, toolCalls: [], usage: null, at });
|
|
56000
56234
|
return;
|
|
56001
56235
|
}
|
|
56002
56236
|
if (record2.type === "tool" && typeof record2.name === "string") {
|
|
@@ -56006,11 +56240,13 @@ function createCursorTurnParser(options = {}) {
|
|
|
56006
56240
|
call.input = boundedToolInput(record2.args);
|
|
56007
56241
|
}
|
|
56008
56242
|
pendingTools.push(call);
|
|
56243
|
+
pendingAt ??= at;
|
|
56009
56244
|
return;
|
|
56010
56245
|
}
|
|
56011
56246
|
if (record2.type === "agent" && typeof record2.text === "string" && record2.text.trim() !== "") {
|
|
56012
|
-
turns.push({ kind: "agent_text", text: record2.text, toolCalls: pendingTools, usage: null });
|
|
56247
|
+
turns.push({ kind: "agent_text", text: record2.text, toolCalls: pendingTools, usage: null, at: pendingAt ?? at });
|
|
56013
56248
|
pendingTools = [];
|
|
56249
|
+
pendingAt = null;
|
|
56014
56250
|
}
|
|
56015
56251
|
},
|
|
56016
56252
|
finish() {
|
|
@@ -56076,7 +56312,7 @@ function parseCursorRunEventsDetailed(runs, options = {}) {
|
|
|
56076
56312
|
let text = "";
|
|
56077
56313
|
const flush2 = () => {
|
|
56078
56314
|
if (text !== "" || pendingTools.length > 0 || pendingTokens > 0) {
|
|
56079
|
-
turns.push({ kind: "agent_text", text, toolCalls: pendingTools, usage: null });
|
|
56315
|
+
turns.push({ kind: "agent_text", text, toolCalls: pendingTools, usage: null, at: null });
|
|
56080
56316
|
turnTokens.push(pendingTokens);
|
|
56081
56317
|
}
|
|
56082
56318
|
text = "";
|
|
@@ -56126,7 +56362,7 @@ function parseCursorRunEventsDetailed(runs, options = {}) {
|
|
|
56126
56362
|
const prompt = stringField(asRecord3(data?.userMessage), "text");
|
|
56127
56363
|
if (!prompt) continue;
|
|
56128
56364
|
if (text !== "") flush2();
|
|
56129
|
-
turns.push({ kind: "human_prompt", text: prompt, toolCalls: [], usage: null });
|
|
56365
|
+
turns.push({ kind: "human_prompt", text: prompt, toolCalls: [], usage: null, at: null });
|
|
56130
56366
|
turnTokens.push(0);
|
|
56131
56367
|
continue;
|
|
56132
56368
|
}
|
|
@@ -56183,10 +56419,16 @@ function normalizeUrl(raw) {
|
|
|
56183
56419
|
|
|
56184
56420
|
// ../core/src/sessionTurns.ts
|
|
56185
56421
|
function parseSessionTurns(source, transcript, options = {}) {
|
|
56186
|
-
|
|
56422
|
+
return parseSessionTurnsDetailed(source, transcript, options).turns;
|
|
56423
|
+
}
|
|
56424
|
+
function parseSessionTurnsDetailed(source, transcript, options = {}) {
|
|
56425
|
+
if (source === "cursor-cloud") {
|
|
56426
|
+
return { turns: parseCursorRunEvents(envelopeRuns(transcript), options), envelopes: [] };
|
|
56427
|
+
}
|
|
56187
56428
|
const parser = createSessionTurnParser(source, options);
|
|
56188
56429
|
for (const line of transcript.split("\n")) parser.pushLine(line);
|
|
56189
|
-
|
|
56430
|
+
const turns = parser.finish();
|
|
56431
|
+
return { turns, envelopes: parser.usageEnvelopes?.() ?? [] };
|
|
56190
56432
|
}
|
|
56191
56433
|
function createSessionTurnParser(source, options = {}) {
|
|
56192
56434
|
if (source === "codex") return createCodexTurnParser(options);
|
|
@@ -56336,6 +56578,38 @@ function scrubBreadcrumb(breadcrumb) {
|
|
|
56336
56578
|
return breadcrumb;
|
|
56337
56579
|
}
|
|
56338
56580
|
|
|
56581
|
+
// ../core/src/freshness.ts
|
|
56582
|
+
function describeDataAge(generatedAt, now) {
|
|
56583
|
+
const ageMs = now.getTime() - generatedAt.getTime();
|
|
56584
|
+
if (ageMs < 0) return "just now";
|
|
56585
|
+
const minutes = Math.floor(ageMs / 6e4);
|
|
56586
|
+
if (minutes < 1) return "just now";
|
|
56587
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
56588
|
+
const hours = Math.floor(minutes / 60);
|
|
56589
|
+
if (hours < 24) return `${hours}h ago`;
|
|
56590
|
+
const days = Math.floor(hours / 24);
|
|
56591
|
+
return `${days}d ago`;
|
|
56592
|
+
}
|
|
56593
|
+
|
|
56594
|
+
// ../core/src/onboarding.ts
|
|
56595
|
+
var SAME_HOUR_MS = 60 * 60 * 1e3;
|
|
56596
|
+
function describeSyncSignals(member, now, noUpload = "no sessions uploaded yet") {
|
|
56597
|
+
const session = member.lastSessionActivityAt;
|
|
56598
|
+
const poll = member.lastCheckedInAt;
|
|
56599
|
+
const error62 = member.lastSyncErrorAt;
|
|
56600
|
+
const paused = member.syncPaused === true;
|
|
56601
|
+
if (session === null && poll === null && error62 === null) return null;
|
|
56602
|
+
const age = (at) => describeDataAge(new Date(at), now);
|
|
56603
|
+
const apart = (a, b2) => b2 === null || Math.abs(Date.parse(a) - Date.parse(b2)) > SAME_HOUR_MS;
|
|
56604
|
+
const parts = [session === null ? noUpload : `last uploaded session ${age(session)}`];
|
|
56605
|
+
if (error62 !== null) parts.push(`reported sync error ${age(error62)}`);
|
|
56606
|
+
if (paused) parts.push("syncing paused");
|
|
56607
|
+
const pollSays = error62 !== null ? apart(poll ?? error62, error62) : paused || apart(poll ?? "", session);
|
|
56608
|
+
if (poll !== null && pollSays) parts.push(`last poll ${age(poll)}`);
|
|
56609
|
+
const line = parts.join(", ");
|
|
56610
|
+
return `${line.charAt(0).toUpperCase()}${line.slice(1)}.`;
|
|
56611
|
+
}
|
|
56612
|
+
|
|
56339
56613
|
// ../core/src/sessionIngest.ts
|
|
56340
56614
|
var SESSION_SOURCES = [
|
|
56341
56615
|
"claude-code",
|
|
@@ -83283,9 +83557,20 @@ var storedCredentialsSchema = external_exports.object({
|
|
|
83283
83557
|
* (`powerplant folders add`). */
|
|
83284
83558
|
scopePaths: external_exports.array(external_exports.string()).optional(),
|
|
83285
83559
|
/** macOS privacy roots the member explicitly allowed Powerplant to inspect
|
|
83286
|
-
* for Git metadata. This does not widen
|
|
83287
|
-
* which discovered repositories upload. */
|
|
83560
|
+
* for Git metadata, and that macOS let it read. This does not widen
|
|
83561
|
+
* capture: org policy still decides which discovered repositories upload. */
|
|
83288
83562
|
repoDiscoveryRoots: external_exports.array(external_exports.string()).optional(),
|
|
83563
|
+
/** When the member answered yes to Git metadata scans of macOS-protected
|
|
83564
|
+
* session folders (ISO 8601). One answer for this enrollment: later init
|
|
83565
|
+
* runs scan a newly seen root after an explanation, without asking again.
|
|
83566
|
+
* Absent = never asked, or never answered yes. */
|
|
83567
|
+
repoDiscoveryConsentedAt: external_exports.string().optional(),
|
|
83568
|
+
/** Protected roots the member allowed but macOS refused to let Powerplant
|
|
83569
|
+
* read (a declined Files and Folders dialog). macOS remembers that refusal
|
|
83570
|
+
* and shows no second dialog, so init re-reads these roots on every run
|
|
83571
|
+
* without a question and moves a root to repoDiscoveryRoots once the
|
|
83572
|
+
* member enables it in System Settings. */
|
|
83573
|
+
repoDiscoveryBlockedRoots: external_exports.array(external_exports.string()).optional(),
|
|
83289
83574
|
/** When this enrollment's deep first sync completed; absent = still owed,
|
|
83290
83575
|
* so the next sync sweeps DEEP_FIRST_SYNC_DAYS instead of the rolling window. */
|
|
83291
83576
|
deepSyncedAt: external_exports.string().optional()
|
|
@@ -83371,7 +83656,7 @@ import os6 from "node:os";
|
|
|
83371
83656
|
// ../../apps/collector/src/http.ts
|
|
83372
83657
|
var import_undici = __toESM(require_undici(), 1);
|
|
83373
83658
|
var CLI_VERSION_HEADER = "x-powerplant-cli-version";
|
|
83374
|
-
var cliVersion = true ? "0.4.
|
|
83659
|
+
var cliVersion = true ? "0.4.8" : "0.0.0-checkout";
|
|
83375
83660
|
function cliVersionHeaders() {
|
|
83376
83661
|
return { [CLI_VERSION_HEADER]: cliVersion, "user-agent": `powerplant-cli/${cliVersion}` };
|
|
83377
83662
|
}
|
|
@@ -83556,8 +83841,8 @@ function unconsentedProtectedRoot(candidate, environment = {}) {
|
|
|
83556
83841
|
);
|
|
83557
83842
|
return consented ? null : protectedRoot;
|
|
83558
83843
|
}
|
|
83559
|
-
var DENIED = { kind: "denied" };
|
|
83560
83844
|
var NONE = { kind: "none" };
|
|
83845
|
+
var deniedAt = (p) => ({ kind: "denied", path: p });
|
|
83561
83846
|
function consentGate(candidate, environment) {
|
|
83562
83847
|
const root = unconsentedProtectedRoot(candidate, environment);
|
|
83563
83848
|
return root === null ? null : { kind: "unconsented", root, path: path2.resolve(candidate) };
|
|
@@ -83566,7 +83851,7 @@ function readProbe(p) {
|
|
|
83566
83851
|
try {
|
|
83567
83852
|
return { kind: "found", value: readFileSync3(p, "utf8") };
|
|
83568
83853
|
} catch (cause) {
|
|
83569
|
-
return isPermissionError(cause) ?
|
|
83854
|
+
return isPermissionError(cause) ? deniedAt(p) : NONE;
|
|
83570
83855
|
}
|
|
83571
83856
|
}
|
|
83572
83857
|
function gitDirReferenceProbe(cwd, environment) {
|
|
@@ -83576,7 +83861,7 @@ function gitDirReferenceProbe(cwd, environment) {
|
|
|
83576
83861
|
for (; ; ) {
|
|
83577
83862
|
const gitPath = path2.join(dir, ".git");
|
|
83578
83863
|
const kind = statKind(gitPath);
|
|
83579
|
-
if (kind === "denied") return
|
|
83864
|
+
if (kind === "denied") return deniedAt(gitPath);
|
|
83580
83865
|
if (kind === "dir") return { kind: "found", value: gitPath };
|
|
83581
83866
|
if (kind === "file") {
|
|
83582
83867
|
const pointer = readProbe(gitPath);
|
|
@@ -83600,7 +83885,7 @@ function gitDirsProbe(cwd, environment) {
|
|
|
83600
83885
|
const gitDirGate = consentGate(gitDir, environment);
|
|
83601
83886
|
if (gitDirGate) return gitDirGate;
|
|
83602
83887
|
const commondir = readProbe(path2.join(gitDir, "commondir"));
|
|
83603
|
-
if (commondir.kind === "denied") return
|
|
83888
|
+
if (commondir.kind === "denied") return commondir;
|
|
83604
83889
|
const pointer = commondir.kind === "found" ? commondir.value.trim() : "";
|
|
83605
83890
|
const commonDir = pointer ? path2.resolve(gitDir, pointer) : gitDir;
|
|
83606
83891
|
const commonDirGate = consentGate(commonDir, environment);
|
|
@@ -83835,10 +84120,15 @@ function tokenRejected(route) {
|
|
|
83835
84120
|
`${route} rejected this machine's token (revoked or unknown). Re-enroll this machine (powerplant enroll, or pnpm collector enroll from the checkout).`
|
|
83836
84121
|
);
|
|
83837
84122
|
}
|
|
83838
|
-
|
|
84123
|
+
function capturePolicyUrl(apiUrl, request2) {
|
|
84124
|
+
const url2 = new URL(`${apiUrl}/api/collector/capture-policy`);
|
|
84125
|
+
if (request2.paused !== void 0) url2.searchParams.set("paused", String(request2.paused));
|
|
84126
|
+
return url2.toString();
|
|
84127
|
+
}
|
|
84128
|
+
async function fetchCapturePolicy(apiUrl, machineToken, request2 = {}) {
|
|
83839
84129
|
const response = await fetchWithRetry(
|
|
83840
84130
|
"fetching the org's capture policy",
|
|
83841
|
-
|
|
84131
|
+
capturePolicyUrl(apiUrl, request2),
|
|
83842
84132
|
{ headers: { authorization: `Bearer ${machineToken}` } }
|
|
83843
84133
|
);
|
|
83844
84134
|
if (response.status === 401) {
|
|
@@ -83869,6 +84159,19 @@ async function fetchCapturePolicy(apiUrl, machineToken) {
|
|
|
83869
84159
|
);
|
|
83870
84160
|
return result.data;
|
|
83871
84161
|
}
|
|
84162
|
+
var CHECK_IN_INTERVAL_MS = 30 * 6e4;
|
|
84163
|
+
async function reportCheckIn(apiUrl, machineToken, paused, fetcher = fetch) {
|
|
84164
|
+
try {
|
|
84165
|
+
const response = await fetcher(capturePolicyUrl(apiUrl, { paused }), {
|
|
84166
|
+
headers: { ...cliVersionHeaders(), authorization: `Bearer ${machineToken}` },
|
|
84167
|
+
signal: AbortSignal.timeout(3e3)
|
|
84168
|
+
});
|
|
84169
|
+
await response.body?.cancel();
|
|
84170
|
+
return response.ok;
|
|
84171
|
+
} catch {
|
|
84172
|
+
return false;
|
|
84173
|
+
}
|
|
84174
|
+
}
|
|
83872
84175
|
async function uploadSessions(apiUrl, machineToken, items) {
|
|
83873
84176
|
let upserted = 0;
|
|
83874
84177
|
let unchanged = 0;
|
|
@@ -84409,7 +84712,6 @@ function removeFolder(config2, rawPath, enrollCommand, log3, deps = defaultDeps)
|
|
|
84409
84712
|
|
|
84410
84713
|
// ../../apps/collector/src/diagnostics.ts
|
|
84411
84714
|
import { createHmac } from "node:crypto";
|
|
84412
|
-
var DIAGNOSTIC_INTERVAL_MS = 30 * 6e4;
|
|
84413
84715
|
function observeFileDiagnostic(observer, file2) {
|
|
84414
84716
|
try {
|
|
84415
84717
|
observer?.(file2);
|
|
@@ -84527,8 +84829,7 @@ function createDiagnosticsReporter(credentials, send = createDiagnosticSender())
|
|
|
84527
84829
|
let pending = Promise.resolve();
|
|
84528
84830
|
let enrollment = null;
|
|
84529
84831
|
let lastIssue = null;
|
|
84530
|
-
|
|
84531
|
-
const report2 = (detailed) => {
|
|
84832
|
+
const report2 = () => {
|
|
84532
84833
|
try {
|
|
84533
84834
|
const current = credentials();
|
|
84534
84835
|
if (!current) return;
|
|
@@ -84537,29 +84838,20 @@ function createDiagnosticsReporter(credentials, send = createDiagnosticSender())
|
|
|
84537
84838
|
lastIssue = null;
|
|
84538
84839
|
}
|
|
84539
84840
|
const matching = pass !== null && passEnrollments.get(pass) === current.token ? pass : null;
|
|
84540
|
-
if (
|
|
84541
|
-
|
|
84542
|
-
|
|
84543
|
-
|
|
84544
|
-
|
|
84545
|
-
}
|
|
84546
|
-
const body = collectorDiagnosticSchema.parse({
|
|
84547
|
-
state: matching?.outcome === "running" ? "syncing" : paused ? "paused" : "idle",
|
|
84548
|
-
pass: detailed ? matching : null
|
|
84549
|
-
});
|
|
84841
|
+
if (matching === null) return;
|
|
84842
|
+
const issue2 = issueSignature(matching);
|
|
84843
|
+
if (issue2 === lastIssue) return;
|
|
84844
|
+
lastIssue = issue2;
|
|
84845
|
+
const body = collectorDiagnosticSchema.parse({ state: "idle", pass: matching });
|
|
84550
84846
|
pending = send(current, body).catch(() => void 0);
|
|
84551
84847
|
} catch {
|
|
84552
84848
|
}
|
|
84553
84849
|
};
|
|
84554
84850
|
return {
|
|
84555
|
-
heartbeat: (isPaused = false) => {
|
|
84556
|
-
paused = isPaused;
|
|
84557
|
-
report2(false);
|
|
84558
|
-
},
|
|
84559
84851
|
flush: () => pending,
|
|
84560
84852
|
onDiagnostics: (diagnostic) => {
|
|
84561
84853
|
pass = diagnostic;
|
|
84562
|
-
if (diagnostic.outcome !== "running") report2(
|
|
84854
|
+
if (diagnostic.outcome !== "running") report2();
|
|
84563
84855
|
}
|
|
84564
84856
|
};
|
|
84565
84857
|
}
|
|
@@ -85071,6 +85363,7 @@ function readCodexTranscriptHints(home, roots) {
|
|
|
85071
85363
|
}
|
|
85072
85364
|
|
|
85073
85365
|
// ../../apps/collector/src/codexCli.ts
|
|
85366
|
+
import { closeSync as closeSync3, mkdirSync as mkdirSync2, openSync as openSync3, renameSync, rmSync, utimesSync, writeSync } from "node:fs";
|
|
85074
85367
|
import os8 from "node:os";
|
|
85075
85368
|
import path7 from "node:path";
|
|
85076
85369
|
import { setImmediate as yieldToEventLoop2 } from "node:timers/promises";
|
|
@@ -85145,19 +85438,96 @@ function parseCodexRollout(text) {
|
|
|
85145
85438
|
...typeof git?.repository_url === "string" && repoFullNameFrom(git.repository_url)
|
|
85146
85439
|
};
|
|
85147
85440
|
}
|
|
85148
|
-
function
|
|
85149
|
-
const
|
|
85150
|
-
|
|
85151
|
-
|
|
85441
|
+
function foldRolloutPages(pages) {
|
|
85442
|
+
const first = (key) => pages.find((page) => page[key] !== void 0)?.[key];
|
|
85443
|
+
const last = (key) => [...pages].reverse().find((page) => page[key] !== void 0)?.[key];
|
|
85444
|
+
const sum2 = (measured2) => measured2.length === 0 ? null : measured2.reduce((total, page) => addCodexTotals(total, page));
|
|
85445
|
+
const measured = pages.map((page) => page.totals).filter((totals) => totals !== null);
|
|
85446
|
+
const measuredFast = pages.filter((page) => page.totals !== null).map((page) => page.fastTotals);
|
|
85447
|
+
const sessionId = first("sessionId");
|
|
85448
|
+
const startedAt = first("startedAt");
|
|
85449
|
+
const endedAt = last("endedAt");
|
|
85450
|
+
const model = last("model");
|
|
85451
|
+
const speedMode = last("speedMode");
|
|
85452
|
+
const cwd = first("cwd");
|
|
85453
|
+
const gitBranch = first("gitBranch");
|
|
85454
|
+
const repoName = first("repoName");
|
|
85455
|
+
const repoFullName = first("repoFullName");
|
|
85456
|
+
return {
|
|
85457
|
+
kind: pages[0].kind,
|
|
85458
|
+
...sessionId !== void 0 && { sessionId },
|
|
85459
|
+
...startedAt !== void 0 && { startedAt },
|
|
85460
|
+
...endedAt !== void 0 && { endedAt },
|
|
85461
|
+
...model !== void 0 && { model },
|
|
85462
|
+
totals: sum2(measured),
|
|
85463
|
+
fastTotals: measuredFast.every((fast) => fast !== null) ? sum2(measuredFast) : null,
|
|
85464
|
+
...speedMode !== void 0 && { speedMode },
|
|
85465
|
+
...cwd !== void 0 && { cwd },
|
|
85466
|
+
...gitBranch !== void 0 && { gitBranch },
|
|
85467
|
+
...repoName !== void 0 && { repoName },
|
|
85468
|
+
...repoFullName !== void 0 && { repoFullName }
|
|
85469
|
+
};
|
|
85470
|
+
}
|
|
85471
|
+
function rolloutIdentity(rolloutPath) {
|
|
85472
|
+
const name = path7.basename(rolloutPath, ".jsonl");
|
|
85473
|
+
const [head, pageId, ...rest] = name.split("_");
|
|
85474
|
+
const threadId2 = head.slice(-36);
|
|
85475
|
+
const conventional = rest.length === 0 && head.startsWith("rollout-") && head.at(-37) === "-" && SESSION_UUID.test(threadId2) && (pageId === void 0 || SESSION_UUID.test(pageId));
|
|
85476
|
+
if (!conventional) return { threadId: name, page: false };
|
|
85477
|
+
return { threadId: threadId2.toLowerCase(), page: pageId !== void 0 };
|
|
85478
|
+
}
|
|
85479
|
+
function codexSpoolDir(home) {
|
|
85480
|
+
return path7.join(home, ".config", "powerplant", "codex");
|
|
85481
|
+
}
|
|
85482
|
+
function mergedSpoolIsFresh(spoolPath, mtimeMs) {
|
|
85483
|
+
const existing = statIfExists(spoolPath);
|
|
85484
|
+
return existing !== null && Math.abs(existing.mtimeMs - mtimeMs) < 1;
|
|
85485
|
+
}
|
|
85486
|
+
var MergedSpoolWriter = class {
|
|
85487
|
+
constructor(spoolPath) {
|
|
85488
|
+
this.spoolPath = spoolPath;
|
|
85489
|
+
mkdirSync2(path7.dirname(spoolPath), { recursive: true, mode: 448 });
|
|
85490
|
+
this.tmpPath = `${spoolPath}.${process.pid}.tmp`;
|
|
85491
|
+
this.fd = openSync3(this.tmpPath, "w", 384);
|
|
85492
|
+
}
|
|
85493
|
+
spoolPath;
|
|
85494
|
+
tmpPath;
|
|
85495
|
+
fd;
|
|
85496
|
+
bytes = 0;
|
|
85497
|
+
append(pageText) {
|
|
85498
|
+
if (this.fd === void 0) throw new Error("merged transcript already closed");
|
|
85499
|
+
const text = pageText.endsWith("\n") ? pageText : `${pageText}
|
|
85500
|
+
`;
|
|
85501
|
+
this.bytes += writeSync(this.fd, text);
|
|
85502
|
+
}
|
|
85503
|
+
/** Stamps the newest page's mtime and publishes the file; returns its size. */
|
|
85504
|
+
finish(mtimeMs) {
|
|
85505
|
+
this.close();
|
|
85506
|
+
utimesSync(this.tmpPath, mtimeMs / 1e3, mtimeMs / 1e3);
|
|
85507
|
+
renameSync(this.tmpPath, this.spoolPath);
|
|
85508
|
+
return this.bytes;
|
|
85509
|
+
}
|
|
85510
|
+
abandon() {
|
|
85511
|
+
this.close();
|
|
85512
|
+
rmSync(this.tmpPath, { force: true });
|
|
85513
|
+
}
|
|
85514
|
+
close() {
|
|
85515
|
+
if (this.fd === void 0) return;
|
|
85516
|
+
closeSync3(this.fd);
|
|
85517
|
+
this.fd = void 0;
|
|
85518
|
+
}
|
|
85519
|
+
};
|
|
85152
85520
|
function rolloutLocation(rolloutPath) {
|
|
85153
85521
|
const record2 = readFirstRecord(rolloutPath, (candidate) => candidate.type === "session_meta");
|
|
85154
85522
|
const meta3 = record2?.payload;
|
|
85155
85523
|
if (!meta3 || typeof meta3 !== "object" || Array.isArray(meta3)) return { kind: "session", totals: null, fastTotals: null };
|
|
85156
85524
|
const { cwd, id, session_id: fallbackId } = meta3;
|
|
85157
85525
|
const sessionId = id ?? fallbackId;
|
|
85526
|
+
const startedAt = record2?.timestamp;
|
|
85158
85527
|
return {
|
|
85159
85528
|
kind: isSidecarThread(meta3) ? "subagent" : "session",
|
|
85160
85529
|
...typeof sessionId === "string" && { sessionId },
|
|
85530
|
+
...typeof startedAt === "string" && { startedAt },
|
|
85161
85531
|
totals: null,
|
|
85162
85532
|
fastTotals: null,
|
|
85163
85533
|
...typeof cwd === "string" && cwd !== "" && { cwd }
|
|
@@ -85212,89 +85582,128 @@ async function collectCodexSessions(options = {}) {
|
|
|
85212
85582
|
const now = options.now ?? Date.now();
|
|
85213
85583
|
const capture = options.capture ?? captureEverythingUnresolved;
|
|
85214
85584
|
const maxBytes = options.maxTranscriptBytes ?? MAX_TRANSCRIPT_FILE_BYTES;
|
|
85585
|
+
const spoolDir = options.spoolDir ?? codexSpoolDir(options.homeDir ?? os8.homedir());
|
|
85215
85586
|
const items = [];
|
|
85216
85587
|
const candidates = [];
|
|
85217
85588
|
const unreadable = [];
|
|
85218
85589
|
const skips = { skippedOutOfScope: 0, skippedNoRepo: 0 };
|
|
85219
85590
|
let skippedSubagentThreads = 0;
|
|
85220
85591
|
let skippedTooLarge = 0;
|
|
85592
|
+
let mergedThreads = 0;
|
|
85593
|
+
let largestMergedBytes = 0;
|
|
85221
85594
|
const externalIdOf = (rolloutPath) => path7.basename(rolloutPath, ".jsonl");
|
|
85222
|
-
const
|
|
85223
|
-
const
|
|
85224
|
-
for (const rolloutPath of
|
|
85225
|
-
|
|
85595
|
+
const files = /* @__PURE__ */ new Map();
|
|
85596
|
+
const observe = async (paths, archived, hintSessionId) => {
|
|
85597
|
+
for (const rolloutPath of paths) {
|
|
85598
|
+
const known = files.get(rolloutPath);
|
|
85599
|
+
if (known !== void 0) {
|
|
85600
|
+
if (hintSessionId === void 0) delete known.hintSessionIds;
|
|
85601
|
+
else known.hintSessionIds?.push(hintSessionId);
|
|
85602
|
+
continue;
|
|
85603
|
+
}
|
|
85226
85604
|
await yieldToEventLoop2();
|
|
85227
85605
|
const stat = statIfExists(rolloutPath);
|
|
85228
85606
|
if (!stat) continue;
|
|
85229
|
-
|
|
85230
|
-
|
|
85231
|
-
|
|
85232
|
-
|
|
85607
|
+
files.set(rolloutPath, {
|
|
85608
|
+
rolloutPath,
|
|
85609
|
+
archived,
|
|
85610
|
+
identity: rolloutIdentity(rolloutPath),
|
|
85611
|
+
mtimeMs: stat.mtimeMs,
|
|
85612
|
+
changedMs: Math.max(stat.mtimeMs, stat.ctimeMs),
|
|
85613
|
+
sizeBytes: stat.size,
|
|
85614
|
+
...hintSessionId !== void 0 && { hintSessionIds: [hintSessionId] }
|
|
85615
|
+
});
|
|
85616
|
+
}
|
|
85617
|
+
};
|
|
85618
|
+
const archiveDir = codexArchivedSessionsDir(sessionsDir);
|
|
85619
|
+
const hintHome = options.homeDir ?? (options.codexDir === void 0 ? os8.homedir() : void 0);
|
|
85620
|
+
if (hintHome !== void 0) {
|
|
85621
|
+
for (const hint of readCodexTranscriptHints(hintHome, [sessionsDir, archiveDir])) {
|
|
85622
|
+
await observe([hint.rolloutPath], hint.archived, hint.sessionId);
|
|
85623
|
+
}
|
|
85624
|
+
}
|
|
85625
|
+
await observe(rolloutFiles(sessionsDir), false);
|
|
85626
|
+
await observe(rolloutFiles(archiveDir), true);
|
|
85627
|
+
const byThread = /* @__PURE__ */ new Map();
|
|
85628
|
+
for (const file2 of files.values()) {
|
|
85629
|
+
const thread = byThread.get(file2.identity.threadId);
|
|
85630
|
+
if (thread === void 0) byThread.set(file2.identity.threadId, [file2]);
|
|
85631
|
+
else thread.push(file2);
|
|
85632
|
+
}
|
|
85633
|
+
for (const [threadId2, observed] of byThread) {
|
|
85634
|
+
const stored = dedupeStoreCopies(observed);
|
|
85635
|
+
let externalId = externalIdOf((stored.find((file2) => !file2.identity.page) ?? stored[0]).rolloutPath);
|
|
85636
|
+
try {
|
|
85637
|
+
const spoolPath = stored.length > 1 ? path7.join(spoolDir, `${threadId2}.jsonl`) : void 0;
|
|
85638
|
+
const changedMs = Math.max(...stored.map((file2) => file2.changedMs));
|
|
85639
|
+
if (options.sinceMs !== void 0 && changedMs < options.sinceMs) {
|
|
85640
|
+
const neverMerged = spoolPath !== void 0 && !options.locationsOnly && statIfExists(spoolPath) === null;
|
|
85641
|
+
if (!neverMerged) continue;
|
|
85642
|
+
}
|
|
85643
|
+
await yieldToEventLoop2();
|
|
85644
|
+
const named = stored.filter((file2) => {
|
|
85645
|
+
if (file2.hintSessionIds === void 0) return true;
|
|
85646
|
+
const { sessionId } = rolloutLocation(file2.rolloutPath);
|
|
85647
|
+
return sessionId !== void 0 && file2.hintSessionIds.includes(sessionId);
|
|
85648
|
+
});
|
|
85649
|
+
if (named.length === 0) continue;
|
|
85650
|
+
const threadFiles = named.length > 1 ? orderPages(named) : named;
|
|
85651
|
+
const base = threadFiles[0];
|
|
85652
|
+
externalId = externalIdOf(base.rolloutPath);
|
|
85653
|
+
const mtimeMs = Math.max(...threadFiles.map((file2) => file2.mtimeMs));
|
|
85654
|
+
const sizeBytes = threadFiles.reduce((sum2, file2) => sum2 + file2.sizeBytes, 0);
|
|
85655
|
+
if (sizeBytes > maxBytes) {
|
|
85233
85656
|
skippedTooLarge += 1;
|
|
85234
85657
|
observeFileDiagnostic(options.onFileDiagnostic, {
|
|
85235
|
-
path: rolloutPath,
|
|
85658
|
+
path: base.rolloutPath,
|
|
85236
85659
|
reason: "too_large",
|
|
85237
|
-
sizeBytes
|
|
85660
|
+
sizeBytes
|
|
85238
85661
|
});
|
|
85239
85662
|
continue;
|
|
85240
85663
|
}
|
|
85241
|
-
|
|
85242
|
-
|
|
85243
|
-
|
|
85244
|
-
|
|
85245
|
-
|
|
85246
|
-
|
|
85247
|
-
|
|
85248
|
-
|
|
85249
|
-
|
|
85250
|
-
|
|
85251
|
-
|
|
85252
|
-
|
|
85253
|
-
|
|
85254
|
-
|
|
85255
|
-
|
|
85256
|
-
|
|
85257
|
-
|
|
85258
|
-
|
|
85259
|
-
|
|
85260
|
-
|
|
85261
|
-
|
|
85262
|
-
source: "codex",
|
|
85263
|
-
externalId: externalIdOf(rolloutPath),
|
|
85264
|
-
status: now - stat.mtimeMs < ACTIVE_GRACE_MS ? "active" : "inactive",
|
|
85265
|
-
archived,
|
|
85266
|
-
capturedVia: decision.via
|
|
85267
|
-
}
|
|
85268
|
-
});
|
|
85269
|
-
continue;
|
|
85270
|
-
}
|
|
85271
|
-
candidates.push({
|
|
85272
|
-
rolloutPath,
|
|
85273
|
-
archived,
|
|
85274
|
-
mtimeMs: stat.mtimeMs,
|
|
85664
|
+
const location = rolloutLocation(base.rolloutPath);
|
|
85665
|
+
if (location.kind === "subagent") {
|
|
85666
|
+
skippedSubagentThreads += 1;
|
|
85667
|
+
continue;
|
|
85668
|
+
}
|
|
85669
|
+
const decision = captureDecision(location.cwd, capture);
|
|
85670
|
+
if (!decision.capture) {
|
|
85671
|
+
countCaptureSkip(skips, decision.reason);
|
|
85672
|
+
continue;
|
|
85673
|
+
}
|
|
85674
|
+
const thread = {
|
|
85675
|
+
externalId,
|
|
85676
|
+
files: threadFiles,
|
|
85677
|
+
...threadFiles.length > 1 && spoolPath !== void 0 && { spoolPath },
|
|
85678
|
+
mtimeMs,
|
|
85679
|
+
archived: threadFiles.every((file2) => file2.archived)
|
|
85680
|
+
};
|
|
85681
|
+
if (options.locationsOnly) {
|
|
85682
|
+
items.push({
|
|
85683
|
+
mtimeMs,
|
|
85684
|
+
transcriptPath: base.rolloutPath,
|
|
85275
85685
|
...location.cwd && { cwd: location.cwd },
|
|
85276
|
-
|
|
85277
|
-
|
|
85278
|
-
|
|
85279
|
-
|
|
85280
|
-
|
|
85281
|
-
|
|
85282
|
-
|
|
85283
|
-
sizeBytes: null,
|
|
85284
|
-
errorCode: diagnosticErrorCode(error62)
|
|
85686
|
+
item: {
|
|
85687
|
+
source: "codex",
|
|
85688
|
+
externalId,
|
|
85689
|
+
status: now - mtimeMs < ACTIVE_GRACE_MS ? "active" : "inactive",
|
|
85690
|
+
archived: thread.archived,
|
|
85691
|
+
capturedVia: decision.via
|
|
85692
|
+
}
|
|
85285
85693
|
});
|
|
85694
|
+
continue;
|
|
85286
85695
|
}
|
|
85287
|
-
|
|
85288
|
-
|
|
85289
|
-
|
|
85290
|
-
|
|
85291
|
-
|
|
85292
|
-
|
|
85293
|
-
|
|
85696
|
+
candidates.push({ thread, ...location.cwd && { cwd: location.cwd }, decision });
|
|
85697
|
+
} catch (error62) {
|
|
85698
|
+
unreadable.push(`${externalId}: ${describeReadError(error62)}`);
|
|
85699
|
+
observeFileDiagnostic(options.onFileDiagnostic, {
|
|
85700
|
+
path: stored[0].rolloutPath,
|
|
85701
|
+
reason: "unreadable",
|
|
85702
|
+
sizeBytes: null,
|
|
85703
|
+
errorCode: diagnosticErrorCode(error62)
|
|
85704
|
+
});
|
|
85294
85705
|
}
|
|
85295
85706
|
}
|
|
85296
|
-
await admitFrom(rolloutFiles(sessionsDir), false);
|
|
85297
|
-
await admitFrom(rolloutFiles(archiveDir), true);
|
|
85298
85707
|
if (options.locationsOnly) {
|
|
85299
85708
|
return {
|
|
85300
85709
|
sessions: items.sort((a, b2) => b2.mtimeMs - a.mtimeMs),
|
|
@@ -85302,30 +85711,59 @@ async function collectCodexSessions(options = {}) {
|
|
|
85302
85711
|
skippedSubagentThreads,
|
|
85303
85712
|
skippedTooLarge,
|
|
85304
85713
|
unreadable,
|
|
85305
|
-
heldBack: 0
|
|
85714
|
+
heldBack: 0,
|
|
85715
|
+
mergedThreads: 0,
|
|
85716
|
+
largestMergedBytes: 0
|
|
85306
85717
|
};
|
|
85307
85718
|
}
|
|
85308
|
-
candidates.sort((a, b2) => b2.mtimeMs - a.mtimeMs);
|
|
85719
|
+
candidates.sort((a, b2) => b2.thread.mtimeMs - a.thread.mtimeMs);
|
|
85309
85720
|
const chosen = options.maxSessions === void 0 ? candidates : candidates.slice(0, options.maxSessions);
|
|
85310
85721
|
const threadNames = chosen.length > 0 ? loadThreadNames(sessionsDir) : /* @__PURE__ */ new Map();
|
|
85311
85722
|
for (const candidate of chosen) {
|
|
85312
85723
|
await yieldToEventLoop2();
|
|
85313
|
-
const {
|
|
85724
|
+
const { thread, cwd, decision } = candidate;
|
|
85725
|
+
let writer;
|
|
85314
85726
|
try {
|
|
85315
|
-
const
|
|
85316
|
-
if (
|
|
85727
|
+
const rewrite = thread.spoolPath !== void 0 && !mergedSpoolIsFresh(thread.spoolPath, thread.mtimeMs);
|
|
85728
|
+
if (thread.spoolPath !== void 0 && rewrite) writer = new MergedSpoolWriter(thread.spoolPath);
|
|
85729
|
+
const pages = [];
|
|
85730
|
+
let vanished = false;
|
|
85731
|
+
for (const file2 of thread.files) {
|
|
85732
|
+
const text = readIfExists(file2.rolloutPath);
|
|
85733
|
+
if (text === null) {
|
|
85734
|
+
vanished = true;
|
|
85735
|
+
break;
|
|
85736
|
+
}
|
|
85737
|
+
const page = parseCodexRollout(text);
|
|
85738
|
+
if (page === null) continue;
|
|
85739
|
+
pages.push(page);
|
|
85740
|
+
writer?.append(text);
|
|
85741
|
+
}
|
|
85742
|
+
if (vanished || pages.length === 0) {
|
|
85743
|
+
writer?.abandon();
|
|
85744
|
+
continue;
|
|
85745
|
+
}
|
|
85746
|
+
const summary = foldRolloutPages(pages);
|
|
85747
|
+
let transcriptPath = thread.files[0].rolloutPath;
|
|
85748
|
+
if (thread.spoolPath !== void 0) {
|
|
85749
|
+
const mergedBytes = writer ? writer.finish(thread.mtimeMs) : statIfExists(thread.spoolPath)?.size ?? 0;
|
|
85750
|
+
writer = void 0;
|
|
85751
|
+
transcriptPath = thread.spoolPath;
|
|
85752
|
+
mergedThreads += 1;
|
|
85753
|
+
largestMergedBytes = Math.max(largestMergedBytes, mergedBytes);
|
|
85754
|
+
}
|
|
85317
85755
|
const repoDir = summary.repoName ?? (cwd ? path7.basename(cwd) : void 0);
|
|
85318
85756
|
const repoFullName = decision.repoFullName ?? summary.repoFullName;
|
|
85319
85757
|
const title = summary.sessionId ? threadNames.get(summary.sessionId) : void 0;
|
|
85320
85758
|
items.push({
|
|
85321
|
-
mtimeMs,
|
|
85322
|
-
transcriptPath
|
|
85759
|
+
mtimeMs: thread.mtimeMs,
|
|
85760
|
+
transcriptPath,
|
|
85323
85761
|
...cwd && { cwd },
|
|
85324
85762
|
item: {
|
|
85325
85763
|
source: "codex",
|
|
85326
|
-
externalId:
|
|
85327
|
-
status: now - mtimeMs < ACTIVE_GRACE_MS ? "active" : "inactive",
|
|
85328
|
-
archived,
|
|
85764
|
+
externalId: thread.externalId,
|
|
85765
|
+
status: now - thread.mtimeMs < ACTIVE_GRACE_MS ? "active" : "inactive",
|
|
85766
|
+
archived: thread.archived,
|
|
85329
85767
|
...summary.startedAt && { startedAt: summary.startedAt },
|
|
85330
85768
|
...summary.endedAt && { endedAt: summary.endedAt },
|
|
85331
85769
|
...summary.model && { model: summary.model },
|
|
@@ -85350,9 +85788,10 @@ async function collectCodexSessions(options = {}) {
|
|
|
85350
85788
|
}
|
|
85351
85789
|
});
|
|
85352
85790
|
} catch (error62) {
|
|
85353
|
-
|
|
85791
|
+
writer?.abandon();
|
|
85792
|
+
unreadable.push(`${thread.externalId}: ${describeReadError(error62)}`);
|
|
85354
85793
|
observeFileDiagnostic(options.onFileDiagnostic, {
|
|
85355
|
-
path: rolloutPath,
|
|
85794
|
+
path: thread.files[0].rolloutPath,
|
|
85356
85795
|
reason: "unreadable",
|
|
85357
85796
|
sizeBytes: null,
|
|
85358
85797
|
errorCode: diagnosticErrorCode(error62)
|
|
@@ -85365,15 +85804,32 @@ async function collectCodexSessions(options = {}) {
|
|
|
85365
85804
|
skippedSubagentThreads,
|
|
85366
85805
|
skippedTooLarge,
|
|
85367
85806
|
unreadable,
|
|
85368
|
-
heldBack: candidates.length - chosen.length
|
|
85807
|
+
heldBack: candidates.length - chosen.length,
|
|
85808
|
+
mergedThreads,
|
|
85809
|
+
largestMergedBytes
|
|
85369
85810
|
};
|
|
85370
85811
|
}
|
|
85812
|
+
function dedupeStoreCopies(threadFiles) {
|
|
85813
|
+
const byName = /* @__PURE__ */ new Map();
|
|
85814
|
+
for (const file2 of threadFiles) {
|
|
85815
|
+
const name = path7.basename(file2.rolloutPath);
|
|
85816
|
+
const known = byName.get(name);
|
|
85817
|
+
if (known === void 0 || file2.archived) byName.set(name, file2);
|
|
85818
|
+
}
|
|
85819
|
+
return [...byName.values()];
|
|
85820
|
+
}
|
|
85821
|
+
function orderPages(threadFiles) {
|
|
85822
|
+
const orderOf = (file2) => rolloutLocation(file2.rolloutPath).startedAt ?? "";
|
|
85823
|
+
return threadFiles.map((file2) => ({ file: file2, order: orderOf(file2) })).sort(
|
|
85824
|
+
(a, b2) => Number(a.file.identity.page) - Number(b2.file.identity.page) || a.order.localeCompare(b2.order) || a.file.rolloutPath.localeCompare(b2.file.rolloutPath)
|
|
85825
|
+
).map((entry) => entry.file);
|
|
85826
|
+
}
|
|
85371
85827
|
function codexThreadCwd(threadId2, sessionsDir = path7.join(os8.homedir(), ".codex", "sessions")) {
|
|
85372
85828
|
if (!SESSION_UUID.test(threadId2)) return void 0;
|
|
85373
|
-
const
|
|
85829
|
+
const wanted = threadId2.toLowerCase();
|
|
85374
85830
|
for (const root of [sessionsDir, codexArchivedSessionsDir(sessionsDir)]) {
|
|
85375
85831
|
for (const rolloutPath of rolloutFiles(root)) {
|
|
85376
|
-
if (
|
|
85832
|
+
if (rolloutIdentity(rolloutPath).threadId !== wanted) continue;
|
|
85377
85833
|
const cwd = rolloutLocation(rolloutPath).cwd;
|
|
85378
85834
|
if (cwd !== void 0) return cwd;
|
|
85379
85835
|
}
|
|
@@ -85387,7 +85843,7 @@ import path10 from "node:path";
|
|
|
85387
85843
|
|
|
85388
85844
|
// ../../apps/collector/src/cursorLocal.ts
|
|
85389
85845
|
import { createHash as createHash4 } from "node:crypto";
|
|
85390
|
-
import { existsSync as existsSync2, mkdirSync as
|
|
85846
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync4, utimesSync as utimesSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
85391
85847
|
import os10 from "node:os";
|
|
85392
85848
|
import path9 from "node:path";
|
|
85393
85849
|
import { fileURLToPath } from "node:url";
|
|
@@ -85395,15 +85851,15 @@ import { fileURLToPath } from "node:url";
|
|
|
85395
85851
|
// ../../apps/collector/src/cursorTranscriptFile.ts
|
|
85396
85852
|
import { createHash as createHash3 } from "node:crypto";
|
|
85397
85853
|
import {
|
|
85398
|
-
closeSync as
|
|
85854
|
+
closeSync as closeSync4,
|
|
85399
85855
|
existsSync,
|
|
85400
85856
|
lstatSync as lstatSync2,
|
|
85401
|
-
mkdirSync as
|
|
85402
|
-
openSync as
|
|
85857
|
+
mkdirSync as mkdirSync3,
|
|
85858
|
+
openSync as openSync4,
|
|
85403
85859
|
readSync as readSync3,
|
|
85404
85860
|
readdirSync as readdirSync3,
|
|
85405
85861
|
realpathSync as realpathSync2,
|
|
85406
|
-
utimesSync,
|
|
85862
|
+
utimesSync as utimesSync2,
|
|
85407
85863
|
writeFileSync as writeFileSync2
|
|
85408
85864
|
} from "node:fs";
|
|
85409
85865
|
import os9 from "node:os";
|
|
@@ -85472,7 +85928,7 @@ var hintSchema2 = external_exports.object({
|
|
|
85472
85928
|
model: external_exports.string().max(256).optional()
|
|
85473
85929
|
});
|
|
85474
85930
|
function readBounded(file2, maxBytes) {
|
|
85475
|
-
const fd =
|
|
85931
|
+
const fd = openSync4(file2, "r");
|
|
85476
85932
|
try {
|
|
85477
85933
|
const chunks = [];
|
|
85478
85934
|
let bytes = 0;
|
|
@@ -85485,7 +85941,7 @@ function readBounded(file2, maxBytes) {
|
|
|
85485
85941
|
}
|
|
85486
85942
|
return null;
|
|
85487
85943
|
} finally {
|
|
85488
|
-
|
|
85944
|
+
closeSync4(fd);
|
|
85489
85945
|
}
|
|
85490
85946
|
}
|
|
85491
85947
|
function collectCursorTranscriptFiles(options, isDatabaseSession, result, databaseSpoolDir, spoolName) {
|
|
@@ -85589,9 +86045,9 @@ function collectCursorTranscriptFiles(options, isDatabaseSession, result, databa
|
|
|
85589
86045
|
result.skippedTooLarge++;
|
|
85590
86046
|
continue;
|
|
85591
86047
|
}
|
|
85592
|
-
|
|
86048
|
+
mkdirSync3(spoolDir, { recursive: true, mode: 448 });
|
|
85593
86049
|
writeFileSync2(spool, body, { mode: 384 });
|
|
85594
|
-
|
|
86050
|
+
utimesSync2(spool, stat.atime, stat.mtime);
|
|
85595
86051
|
}
|
|
85596
86052
|
result.sessions.push({
|
|
85597
86053
|
cwd,
|
|
@@ -85942,13 +86398,13 @@ function collectFromStore(db, userDir, options, result, knownIds) {
|
|
|
85942
86398
|
...transcript.bubblesOmitted > 0 && { bubblesOmitted: transcript.bubblesOmitted }
|
|
85943
86399
|
};
|
|
85944
86400
|
if (!spoolDirReady) {
|
|
85945
|
-
|
|
86401
|
+
mkdirSync4(spoolDir, { recursive: true });
|
|
85946
86402
|
spoolDirReady = true;
|
|
85947
86403
|
}
|
|
85948
86404
|
const body = [meta3, ...transcript.lines].map((line) => JSON.stringify(line)).join("\n");
|
|
85949
86405
|
writeFileSync3(spoolPath, `${body}
|
|
85950
86406
|
`, "utf8");
|
|
85951
|
-
|
|
86407
|
+
utimesSync3(spoolPath, new Date(lastUpdatedAt), new Date(lastUpdatedAt));
|
|
85952
86408
|
}
|
|
85953
86409
|
const repoSourceDir = facts.repoPath ?? cwd;
|
|
85954
86410
|
const startBranch = facts.branches[0];
|
|
@@ -86080,25 +86536,33 @@ function groupProtectedSessions(sessions, environment = {}) {
|
|
|
86080
86536
|
if (!session.cwd) continue;
|
|
86081
86537
|
const cwd = path10.resolve(session.cwd);
|
|
86082
86538
|
const directRoot = macPrivacyProtectedRoot(cwd, environment);
|
|
86083
|
-
|
|
86084
|
-
const
|
|
86085
|
-
|
|
86086
|
-
|
|
86087
|
-
|
|
86539
|
+
if (directRoot !== null) tally.add(directRoot, cwd, cwd);
|
|
86540
|
+
const gitDir = resolveGitDirReference(cwd, environment);
|
|
86541
|
+
const checkoutRoot = gitDir ? macPrivacyProtectedRoot(gitDir, environment) : null;
|
|
86542
|
+
if (checkoutRoot !== null && checkoutRoot !== directRoot) {
|
|
86543
|
+
tally.add(checkoutRoot, checkoutOfGitDir(gitDir), cwd);
|
|
86544
|
+
}
|
|
86088
86545
|
}
|
|
86089
86546
|
return tally.entries();
|
|
86090
86547
|
}
|
|
86091
86548
|
var defaultAllowDeps = {
|
|
86092
86549
|
load: loadCredentials,
|
|
86093
86550
|
persist: saveCredentials,
|
|
86094
|
-
probeRepo: repoProber
|
|
86551
|
+
probeRepo: repoProber,
|
|
86552
|
+
now: Date.now
|
|
86095
86553
|
};
|
|
86554
|
+
function protectedRootReview(credentials, root) {
|
|
86555
|
+
const resolved = path10.resolve(root);
|
|
86556
|
+
if ((credentials.repoDiscoveryRoots ?? []).includes(resolved)) return "allowed";
|
|
86557
|
+
if ((credentials.repoDiscoveryBlockedRoots ?? []).includes(resolved)) return "blocked";
|
|
86558
|
+
return "unreviewed";
|
|
86559
|
+
}
|
|
86096
86560
|
function allowProtectedRepoDiscovery(config2, found, environment = {}, deps = defaultAllowDeps) {
|
|
86097
86561
|
const enrollment = deps.load(config2.appEnv);
|
|
86098
86562
|
if (enrollment === null) {
|
|
86099
86563
|
throw new ExpectedCliError("This machine must be enrolled before protected folders can be reviewed.");
|
|
86100
86564
|
}
|
|
86101
|
-
const candidateRoots = found
|
|
86565
|
+
const candidateRoots = rootsOf(found);
|
|
86102
86566
|
const invalid = candidateRoots.find(
|
|
86103
86567
|
(root) => macPrivacyProtectedRoot(root, environment) !== root
|
|
86104
86568
|
);
|
|
@@ -86119,53 +86583,92 @@ function allowProtectedRepoDiscovery(config2, found, environment = {}, deps = de
|
|
|
86119
86583
|
needsReview: []
|
|
86120
86584
|
};
|
|
86121
86585
|
const needsReview = new ProtectedRootTally(environment);
|
|
86586
|
+
const refusedElsewhere = new ProtectedRootTally(environment);
|
|
86122
86587
|
for (const entry of found) {
|
|
86588
|
+
const root = path10.resolve(entry.root);
|
|
86123
86589
|
const probes = entry.folders.flatMap(
|
|
86124
86590
|
(folder) => folder.cwds.map((cwd) => ({ cwd, probe: probeOf(cwd), folder }))
|
|
86125
86591
|
);
|
|
86592
|
+
let deniedHere = false;
|
|
86126
86593
|
let unconsented = 0;
|
|
86127
86594
|
const countedFolderTargets = /* @__PURE__ */ new Set();
|
|
86595
|
+
const countOnce = (target, sessionCount) => {
|
|
86596
|
+
const counted = countedFolderTargets.has(target);
|
|
86597
|
+
countedFolderTargets.add(target);
|
|
86598
|
+
return counted ? 0 : sessionCount;
|
|
86599
|
+
};
|
|
86128
86600
|
for (const { cwd, probe, folder } of probes) {
|
|
86601
|
+
if (probe.kind === "denied") {
|
|
86602
|
+
const refusedRoot = macPrivacyProtectedRoot(probe.path, environment);
|
|
86603
|
+
if (refusedRoot === root) deniedHere = true;
|
|
86604
|
+
else if (refusedRoot !== null) {
|
|
86605
|
+
const checkout2 = checkoutOfGitDir(probe.path);
|
|
86606
|
+
const target2 = [folder.path, refusedRoot, checkout2].join("\0");
|
|
86607
|
+
refusedElsewhere.add(refusedRoot, checkout2, cwd, countOnce(target2, folder.sessionCount));
|
|
86608
|
+
}
|
|
86609
|
+
continue;
|
|
86610
|
+
}
|
|
86129
86611
|
if (probe.kind !== "unconsented") continue;
|
|
86130
86612
|
unconsented += 1;
|
|
86131
86613
|
const checkout = checkoutOfGitDir(probe.path);
|
|
86132
86614
|
const target = [folder.path, probe.root, checkout].join("\0");
|
|
86133
|
-
needsReview.add(
|
|
86134
|
-
probe.root,
|
|
86135
|
-
checkout,
|
|
86136
|
-
cwd,
|
|
86137
|
-
countedFolderTargets.has(target) ? 0 : folder.sessionCount
|
|
86138
|
-
);
|
|
86139
|
-
countedFolderTargets.add(target);
|
|
86615
|
+
needsReview.add(probe.root, checkout, cwd, countOnce(target, folder.sessionCount));
|
|
86140
86616
|
}
|
|
86141
86617
|
if (probes.some(({ probe }) => probe.kind === "found")) outcome.enabled.push(entry);
|
|
86142
|
-
else if (
|
|
86618
|
+
else if (deniedHere) outcome.blocked.push(entry);
|
|
86143
86619
|
else if (unconsented > 0) outcome.unreviewed.push(entry);
|
|
86144
86620
|
else outcome.unmatched.push(entry);
|
|
86145
86621
|
}
|
|
86622
|
+
const blockedRoots = new Set(rootsOf(outcome.blocked));
|
|
86623
|
+
outcome.blocked.push(
|
|
86624
|
+
...refusedElsewhere.entries().filter((entry) => !blockedRoots.has(entry.root))
|
|
86625
|
+
);
|
|
86146
86626
|
outcome.needsReview = needsReview.entries();
|
|
86147
|
-
const consented = [...outcome.enabled, ...outcome.unmatched];
|
|
86148
|
-
if (consented.length === 0) return outcome;
|
|
86149
86627
|
const credentials = deps.load(config2.appEnv);
|
|
86150
86628
|
if (credentials === null || credentials.token !== enrollment.token || credentials.enrolledAt !== enrollment.enrolledAt) {
|
|
86151
86629
|
throw new ExpectedCliError(
|
|
86152
|
-
"This machine's enrollment changed while the folders were being reviewed, so
|
|
86630
|
+
"This machine's enrollment changed while the folders were being reviewed, so nothing was saved, run `powerplant init` again to review them."
|
|
86153
86631
|
);
|
|
86154
86632
|
}
|
|
86155
|
-
const
|
|
86633
|
+
const next = credentialsAfterRepoDiscovery(credentials, outcome, deps.now);
|
|
86634
|
+
if (next !== null) deps.persist(config2.appEnv, next);
|
|
86635
|
+
return outcome;
|
|
86636
|
+
}
|
|
86637
|
+
function rootsOf(entries) {
|
|
86638
|
+
return entries.map((entry) => path10.resolve(entry.root));
|
|
86639
|
+
}
|
|
86640
|
+
function credentialsAfterRepoDiscovery(credentials, outcome, now) {
|
|
86641
|
+
const readable = rootsOf([...outcome.enabled, ...outcome.unmatched]);
|
|
86642
|
+
const denied = rootsOf(outcome.blocked).filter((root) => !readable.includes(root));
|
|
86643
|
+
const allowedRoots = [
|
|
86156
86644
|
.../* @__PURE__ */ new Set([
|
|
86157
|
-
...credentials.repoDiscoveryRoots ?? [],
|
|
86158
|
-
...
|
|
86645
|
+
...(credentials.repoDiscoveryRoots ?? []).filter((root) => !denied.includes(root)),
|
|
86646
|
+
...readable
|
|
86159
86647
|
])
|
|
86160
86648
|
];
|
|
86161
|
-
|
|
86162
|
-
|
|
86163
|
-
|
|
86164
|
-
|
|
86649
|
+
const blockedRoots = [
|
|
86650
|
+
.../* @__PURE__ */ new Set([
|
|
86651
|
+
...(credentials.repoDiscoveryBlockedRoots ?? []).filter((root) => !readable.includes(root)),
|
|
86652
|
+
...denied
|
|
86653
|
+
])
|
|
86654
|
+
];
|
|
86655
|
+
const unchanged = credentials.repoDiscoveryConsentedAt !== void 0 && sameList(allowedRoots, credentials.repoDiscoveryRoots ?? []) && sameList(blockedRoots, credentials.repoDiscoveryBlockedRoots ?? []);
|
|
86656
|
+
if (unchanged) return null;
|
|
86657
|
+
return {
|
|
86658
|
+
...credentials,
|
|
86659
|
+
repoDiscoveryConsentedAt: credentials.repoDiscoveryConsentedAt ?? new Date(now()).toISOString(),
|
|
86660
|
+
// Both lists clear on empty: keeping a stale allowed list would read a
|
|
86661
|
+
// root as consent after macOS refused it.
|
|
86662
|
+
repoDiscoveryRoots: allowedRoots.length > 0 ? allowedRoots : void 0,
|
|
86663
|
+
repoDiscoveryBlockedRoots: blockedRoots.length > 0 ? blockedRoots : void 0
|
|
86664
|
+
};
|
|
86665
|
+
}
|
|
86666
|
+
function sameList(a, b2) {
|
|
86667
|
+
return a.length === b2.length && a.every((root, index) => root === b2[index]);
|
|
86165
86668
|
}
|
|
86166
86669
|
|
|
86167
86670
|
// ../../apps/collector/src/hookInstall.ts
|
|
86168
|
-
import { existsSync as existsSync3, mkdirSync as
|
|
86671
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync6, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
86169
86672
|
import os12 from "node:os";
|
|
86170
86673
|
import path11 from "node:path";
|
|
86171
86674
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
@@ -86233,7 +86736,7 @@ function hookSourcePath(script) {
|
|
|
86233
86736
|
}
|
|
86234
86737
|
function installCollectorHooks(homeDir = os12.homedir()) {
|
|
86235
86738
|
const hookDir = path11.join(homeDir, ".config", "powerplant", "hooks");
|
|
86236
|
-
|
|
86739
|
+
mkdirSync5(hookDir, { recursive: true });
|
|
86237
86740
|
for (const script of HOOK_SCRIPTS) {
|
|
86238
86741
|
writeFileSync4(path11.join(hookDir, script), readFileSync6(hookSourcePath(script), "utf8"), {
|
|
86239
86742
|
mode: 493
|
|
@@ -86249,12 +86752,12 @@ function installCollectorHooks(homeDir = os12.homedir()) {
|
|
|
86249
86752
|
}
|
|
86250
86753
|
const merged = mergeCollectorHooks(settingsText, (script) => `node "${path11.join(hookDir, script)}"`);
|
|
86251
86754
|
if (merged.action === "write") {
|
|
86252
|
-
|
|
86755
|
+
mkdirSync5(settingsDir, { recursive: true });
|
|
86253
86756
|
writeFileSync4(settingsPath, merged.text);
|
|
86254
86757
|
}
|
|
86255
86758
|
if (merged.action !== "skip") {
|
|
86256
86759
|
for (const retired of RETIRED_HOOK_WIRINGS) {
|
|
86257
|
-
|
|
86760
|
+
rmSync2(path11.join(hookDir, retired.script), { force: true });
|
|
86258
86761
|
}
|
|
86259
86762
|
}
|
|
86260
86763
|
return {
|
|
@@ -86266,7 +86769,7 @@ function installCollectorHooks(homeDir = os12.homedir()) {
|
|
|
86266
86769
|
|
|
86267
86770
|
// ../../apps/collector/src/service.ts
|
|
86268
86771
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
86269
|
-
import { chmodSync as chmodSync2, existsSync as existsSync4, mkdirSync as
|
|
86772
|
+
import { chmodSync as chmodSync2, existsSync as existsSync4, mkdirSync as mkdirSync6, readFileSync as readFileSync7, rmSync as rmSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "node:fs";
|
|
86270
86773
|
import os13 from "node:os";
|
|
86271
86774
|
import path12 from "node:path";
|
|
86272
86775
|
var RESTART_THROTTLE_SECONDS = 60;
|
|
@@ -86346,11 +86849,11 @@ function installService(config2, command) {
|
|
|
86346
86849
|
);
|
|
86347
86850
|
}
|
|
86348
86851
|
const launcher = launcherPath(config2.appEnv);
|
|
86349
|
-
|
|
86852
|
+
mkdirSync6(path12.dirname(launcher), { recursive: true });
|
|
86350
86853
|
writeFileSync5(launcher, buildLauncherScript(command));
|
|
86351
86854
|
chmodSync2(launcher, 493);
|
|
86352
86855
|
const file2 = plistPath(config2.appEnv);
|
|
86353
|
-
|
|
86856
|
+
mkdirSync6(path12.dirname(file2), { recursive: true });
|
|
86354
86857
|
writeFileSync5(file2, buildLaunchdPlist(config2.appEnv, installerPathValue()));
|
|
86355
86858
|
runLaunchctl(["bootstrap", `gui/${uid()}`, file2]);
|
|
86356
86859
|
return `${file2} (watching for session changes; log: ${logPath(config2.appEnv)})`;
|
|
@@ -86388,7 +86891,7 @@ function serviceUpToDate(config2, command) {
|
|
|
86388
86891
|
function uninstallService(config2) {
|
|
86389
86892
|
requireMacos();
|
|
86390
86893
|
const existed = bootout(config2.appEnv);
|
|
86391
|
-
|
|
86894
|
+
rmSync3(launcherPath(config2.appEnv), { force: true });
|
|
86392
86895
|
if (existsSync4(plistPath(config2.appEnv))) {
|
|
86393
86896
|
unlinkSync2(plistPath(config2.appEnv));
|
|
86394
86897
|
return true;
|
|
@@ -86458,7 +86961,7 @@ function xmlEscape(value) {
|
|
|
86458
86961
|
}
|
|
86459
86962
|
|
|
86460
86963
|
// ../../apps/collector/src/status.ts
|
|
86461
|
-
import { mkdirSync as
|
|
86964
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync8, renameSync as renameSync2, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "node:fs";
|
|
86462
86965
|
import os14 from "node:os";
|
|
86463
86966
|
import path13 from "node:path";
|
|
86464
86967
|
var HEARTBEAT_INTERVAL_MS = 6e4;
|
|
@@ -86493,6 +86996,9 @@ var collectorStatusSchema = external_exports.object({
|
|
|
86493
86996
|
/** Last pass that completed without error — everything eligible had been
|
|
86494
86997
|
* uploaded as of this time. */
|
|
86495
86998
|
lastSyncAt: external_exports.string().optional(),
|
|
86999
|
+
/** Last pass that upserted at least one session; missing = no session
|
|
87000
|
+
* has uploaded in this daemon's lifetime, not "never". */
|
|
87001
|
+
lastUploadAt: external_exports.string().optional(),
|
|
86496
87002
|
consecutiveFailures: external_exports.number().int().nonnegative()
|
|
86497
87003
|
});
|
|
86498
87004
|
var controlSchema = external_exports.object({
|
|
@@ -86509,7 +87015,8 @@ var controlSchema = external_exports.object({
|
|
|
86509
87015
|
function interpretControlChange(previous, current) {
|
|
86510
87016
|
const transitioned = previous.paused !== current.paused;
|
|
86511
87017
|
const nudge = !current.paused && (transitioned || current.resumedAt !== previous.resumedAt);
|
|
86512
|
-
|
|
87018
|
+
const updateOptIn = current.autoUpdate === true && previous.autoUpdate !== true;
|
|
87019
|
+
return { transitioned, nudge, updateOptIn };
|
|
86513
87020
|
}
|
|
86514
87021
|
function collectorStateDir(homeDir = os14.homedir()) {
|
|
86515
87022
|
return path13.join(homeDir, ".config", "powerplant", "state");
|
|
@@ -86524,14 +87031,18 @@ function controlFilePath(appEnv, homeDir) {
|
|
|
86524
87031
|
return path13.join(collectorStateDir(homeDir), `collector-control-${appEnv}.json`);
|
|
86525
87032
|
}
|
|
86526
87033
|
function writeAtomically(file2, payload) {
|
|
86527
|
-
|
|
87034
|
+
mkdirSync7(path13.dirname(file2), { recursive: true });
|
|
86528
87035
|
const tmp = `${file2}.${process.pid}.tmp`;
|
|
86529
87036
|
writeFileSync6(tmp, payload);
|
|
86530
|
-
|
|
87037
|
+
renameSync2(tmp, file2);
|
|
86531
87038
|
}
|
|
86532
87039
|
function writeCollectorStatus(appEnv, status, homeDir) {
|
|
86533
87040
|
writeAtomically(statusFilePath(appEnv, homeDir), JSON.stringify(status, null, 2));
|
|
86534
87041
|
}
|
|
87042
|
+
function carriedUploadClock(previous, machineId) {
|
|
87043
|
+
if (previous.file !== "ok" || machineId === void 0) return void 0;
|
|
87044
|
+
return previous.status.machineId === machineId ? previous.status.lastUploadAt : void 0;
|
|
87045
|
+
}
|
|
86535
87046
|
function readCollectorStatus(appEnv, homeDir) {
|
|
86536
87047
|
let raw;
|
|
86537
87048
|
try {
|
|
@@ -86568,10 +87079,10 @@ function writeCollectorControl(appEnv, control, homeDir) {
|
|
|
86568
87079
|
writeAtomically(controlFilePath(appEnv, homeDir), JSON.stringify(control, null, 2));
|
|
86569
87080
|
}
|
|
86570
87081
|
function clearCollectorControl(appEnv, homeDir) {
|
|
86571
|
-
|
|
87082
|
+
rmSync4(controlFilePath(appEnv, homeDir), { force: true });
|
|
86572
87083
|
}
|
|
86573
87084
|
function clearCollectorStatus(appEnv, homeDir) {
|
|
86574
|
-
|
|
87085
|
+
rmSync4(statusFilePath(appEnv, homeDir), { force: true });
|
|
86575
87086
|
}
|
|
86576
87087
|
function describeDaemonState(read, probes = {}) {
|
|
86577
87088
|
if (read.file === "invalid") return { state: "unreadable" };
|
|
@@ -86944,7 +87455,10 @@ async function runSyncPassInternal(config2, options, diagnostic) {
|
|
|
86944
87455
|
const active = items.filter((s) => s.status === "active").length;
|
|
86945
87456
|
const tooLarge = (count2, what) => count2 > 0 ? ` \xB7 ${count2} ${what} skipped (over ${MAX_TRANSCRIPT_FILE_BYTES / (1024 * 1024)}MB, too large to read)` : "";
|
|
86946
87457
|
log3(
|
|
86947
|
-
`Found ${inWindow.length} local session(s) (${claude.sessions.length} claude, ${codex.sessions.length} codex, ${cursor.sessions.length} cursor) ` + describeScanReach(window2, options.narrowSinceMs) + (active > 0 ? ` (${active} still active)` : "") + (skippedOutOfScope > 0 ? ` \xB7 ${skippedOutOfScope} ${describeSkipReason(capture)}` : "") + (skippedNoRepo > 0 ? ` \xB7 ${skippedNoRepo} ${describeNoRepoSkipReason(capture)}` : "") + (codex.skippedSubagentThreads > 0 ? ` \xB7 ${codex.skippedSubagentThreads} codex subagent thread(s) skipped (spend unmeasured)` : "") + (cursor.skippedSubagentComposers > 0 ? ` \xB7 ${cursor.skippedSubagentComposers} cursor subagent thread(s) skipped` : "") + (cursor.skippedNonLocalComposers > 0 ? ` \xB7 ${cursor.skippedNonLocalComposers} cursor session(s) skipped (not local to this machine)` : "") + (cursor.skippedUnreadable > 0 ? ` \xB7 ${cursor.skippedUnreadable} cursor session(s) unreadable, skipped` : "") + tooLarge(claude.skippedTooLarge, "claude transcript(s)") + (claude.unreadable.length > 0 ? ` \xB7 ${claude.unreadable.length} claude transcript(s) unreadable, skipped` : "") + tooLarge(codex.skippedTooLarge, "codex rollout(s)") + (codex.unreadable.length > 0 ? ` \xB7 ${codex.unreadable.length} codex rollout(s) unreadable, skipped` : "") +
|
|
87458
|
+
`Found ${inWindow.length} local session(s) (${claude.sessions.length} claude, ${codex.sessions.length} codex, ${cursor.sessions.length} cursor) ` + describeScanReach(window2, options.narrowSinceMs) + (active > 0 ? ` (${active} still active)` : "") + (skippedOutOfScope > 0 ? ` \xB7 ${skippedOutOfScope} ${describeSkipReason(capture)}` : "") + (skippedNoRepo > 0 ? ` \xB7 ${skippedNoRepo} ${describeNoRepoSkipReason(capture)}` : "") + (codex.skippedSubagentThreads > 0 ? ` \xB7 ${codex.skippedSubagentThreads} codex subagent thread(s) skipped (spend unmeasured)` : "") + (cursor.skippedSubagentComposers > 0 ? ` \xB7 ${cursor.skippedSubagentComposers} cursor subagent thread(s) skipped` : "") + (cursor.skippedNonLocalComposers > 0 ? ` \xB7 ${cursor.skippedNonLocalComposers} cursor session(s) skipped (not local to this machine)` : "") + (cursor.skippedUnreadable > 0 ? ` \xB7 ${cursor.skippedUnreadable} cursor session(s) unreadable, skipped` : "") + tooLarge(claude.skippedTooLarge, "claude transcript(s)") + (claude.unreadable.length > 0 ? ` \xB7 ${claude.unreadable.length} claude transcript(s) unreadable, skipped` : "") + tooLarge(codex.skippedTooLarge, "codex rollout(s)") + (codex.unreadable.length > 0 ? ` \xB7 ${codex.unreadable.length} codex rollout(s) unreadable, skipped` : "") + // The measure for POW-79's follow-up: a merged thread re-uploads whole
|
|
87459
|
+
// on every change, so once the largest nears the cap, per-page upload
|
|
87460
|
+
// is due.
|
|
87461
|
+
(codex.mergedThreads > 0 ? ` \xB7 ${codex.mergedThreads} codex thread(s) merged from split rollouts (largest ${(codex.largestMergedBytes / (1024 * 1024)).toFixed(1)}MB of the ${MAX_TRANSCRIPT_FILE_BYTES / (1024 * 1024)}MB cap)` : "") + tooLarge(cursor.skippedTooLarge, "cursor transcript(s)") + (cursor.truncatedTranscripts > 0 ? ` \xB7 ${cursor.truncatedTranscripts} cursor transcript(s) cut at ${MAX_TRANSCRIPT_FILE_BYTES / (1024 * 1024)}MB (the rest of the conversation stays local)` : "") + (heldBack > 0 ? ` \xB7 ${heldBack} older session(s) held back, the first sync backfills the ${DEEP_FIRST_SYNC_MAX_SESSIONS} most recently active` : "") + "."
|
|
86948
87462
|
);
|
|
86949
87463
|
for (const failure of claude.unreadable) log3(` claude transcript skipped, ${failure}`);
|
|
86950
87464
|
for (const failure of codex.unreadable) log3(` codex rollout skipped, ${failure}`);
|
|
@@ -87191,10 +87705,10 @@ import path16 from "node:path";
|
|
|
87191
87705
|
import {
|
|
87192
87706
|
existsSync as existsSync5,
|
|
87193
87707
|
lstatSync as lstatSync3,
|
|
87194
|
-
mkdirSync as
|
|
87708
|
+
mkdirSync as mkdirSync8,
|
|
87195
87709
|
readFileSync as readFileSync10,
|
|
87196
|
-
renameSync as
|
|
87197
|
-
rmSync as
|
|
87710
|
+
renameSync as renameSync3,
|
|
87711
|
+
rmSync as rmSync5,
|
|
87198
87712
|
writeFileSync as writeFileSync7
|
|
87199
87713
|
} from "node:fs";
|
|
87200
87714
|
import path15 from "node:path";
|
|
@@ -87275,7 +87789,7 @@ function installProviderHook(provider, home) {
|
|
|
87275
87789
|
`${shellQuote2(process.execPath)} ${shellQuote2(scriptPath)}`
|
|
87276
87790
|
);
|
|
87277
87791
|
if (merged.action === "skip") return { provider, settings: "skip", detail: merged.reason };
|
|
87278
|
-
|
|
87792
|
+
mkdirSync8(hookDir, { recursive: true });
|
|
87279
87793
|
writeAtomic(scriptPath, readFileSync10(hookSourcePath(script), "utf8"), 448);
|
|
87280
87794
|
if (merged.action === "write") {
|
|
87281
87795
|
const current = existsSync5(configPath) ? readFileSync10(configPath, "utf8") : null;
|
|
@@ -87293,9 +87807,9 @@ function writeAtomic(file2, text, mode) {
|
|
|
87293
87807
|
const temporary = `${file2}.${randomUUID()}.tmp`;
|
|
87294
87808
|
try {
|
|
87295
87809
|
writeFileSync7(temporary, text, { mode, flag: "wx" });
|
|
87296
|
-
|
|
87810
|
+
renameSync3(temporary, file2);
|
|
87297
87811
|
} finally {
|
|
87298
|
-
|
|
87812
|
+
rmSync5(temporary, { force: true });
|
|
87299
87813
|
}
|
|
87300
87814
|
}
|
|
87301
87815
|
function installProviderHooks(home) {
|
|
@@ -87637,6 +88151,10 @@ function startWatch(config2, homeDir, runtime = { version: "checkout" }) {
|
|
|
87637
88151
|
const presentRoots = storeRoots.filter(({ dir }) => existsSync6(dir)).map(({ dir }) => dir);
|
|
87638
88152
|
const initialControl = readCollectorControl(config2.appEnv, home);
|
|
87639
88153
|
const pausedAtMs = initialControl.paused ? Date.parse(initialControl.pausedAt ?? "") : Number.NaN;
|
|
88154
|
+
const lastUploadAt = carriedUploadClock(
|
|
88155
|
+
readCollectorStatus(config2.appEnv, home),
|
|
88156
|
+
credentials?.machineId
|
|
88157
|
+
);
|
|
87640
88158
|
let status = {
|
|
87641
88159
|
pid: process.pid,
|
|
87642
88160
|
version: runtime.version,
|
|
@@ -87652,6 +88170,7 @@ function startWatch(config2, homeDir, runtime = { version: "checkout" }) {
|
|
|
87652
88170
|
},
|
|
87653
88171
|
watchedRoots: presentRoots,
|
|
87654
88172
|
paused: initialControl.paused,
|
|
88173
|
+
...lastUploadAt !== void 0 && { lastUploadAt },
|
|
87655
88174
|
consecutiveFailures: 0
|
|
87656
88175
|
};
|
|
87657
88176
|
let statusWriteFailed = false;
|
|
@@ -87670,9 +88189,19 @@ function startWatch(config2, homeDir, runtime = { version: "checkout" }) {
|
|
|
87670
88189
|
};
|
|
87671
88190
|
writeStatus({});
|
|
87672
88191
|
const diagnostics = createDiagnosticsReporter(() => loadCredentials(config2.appEnv));
|
|
87673
|
-
|
|
87674
|
-
|
|
87675
|
-
|
|
88192
|
+
const checkIn = () => {
|
|
88193
|
+
let current;
|
|
88194
|
+
try {
|
|
88195
|
+
current = loadCredentials(config2.appEnv);
|
|
88196
|
+
} catch {
|
|
88197
|
+
return;
|
|
88198
|
+
}
|
|
88199
|
+
if (current === null) return;
|
|
88200
|
+
void reportCheckIn(current.apiUrl, current.token, status.paused);
|
|
88201
|
+
};
|
|
88202
|
+
checkIn();
|
|
88203
|
+
const checkInTimer = setInterval(checkIn, CHECK_IN_INTERVAL_MS);
|
|
88204
|
+
checkInTimer.unref?.();
|
|
87676
88205
|
const heartbeat = setInterval(() => writeStatus({}), HEARTBEAT_INTERVAL_MS);
|
|
87677
88206
|
heartbeat.unref?.();
|
|
87678
88207
|
const noticeIfUnsupported = async () => {
|
|
@@ -87770,6 +88299,7 @@ function startWatch(config2, homeDir, runtime = { version: "checkout" }) {
|
|
|
87770
88299
|
...result.diagnostic && { diagnostic: result.diagnostic }
|
|
87771
88300
|
},
|
|
87772
88301
|
...result.ok && { lastSyncAt: new Date(result.at).toISOString() },
|
|
88302
|
+
...result.ok && result.upserted > 0 && { lastUploadAt: new Date(result.at).toISOString() },
|
|
87773
88303
|
consecutiveFailures: result.ok ? 0 : status.consecutiveFailures + 1
|
|
87774
88304
|
}),
|
|
87775
88305
|
reportError: reportCollectorError,
|
|
@@ -87801,16 +88331,21 @@ function startWatch(config2, homeDir, runtime = { version: "checkout" }) {
|
|
|
87801
88331
|
let lastControl = initialControl;
|
|
87802
88332
|
const controlPoll = setInterval(() => {
|
|
87803
88333
|
const control = readCollectorControl(config2.appEnv, home);
|
|
87804
|
-
const { transitioned, nudge } = interpretControlChange(lastControl, control);
|
|
88334
|
+
const { transitioned, nudge, updateOptIn } = interpretControlChange(lastControl, control);
|
|
87805
88335
|
lastControl = control;
|
|
87806
88336
|
if (transitioned) {
|
|
87807
88337
|
log3(control.paused ? "syncing paused via control file" : "syncing resumed");
|
|
87808
88338
|
writeStatus({ paused: control.paused });
|
|
88339
|
+
checkIn();
|
|
87809
88340
|
}
|
|
87810
88341
|
if (nudge) {
|
|
87811
88342
|
log3("running a catch-up pass to cover the pause");
|
|
87812
88343
|
loop.nudge();
|
|
87813
88344
|
}
|
|
88345
|
+
if (updateOptIn) {
|
|
88346
|
+
log3("automatic updates turned on, checking for a release now");
|
|
88347
|
+
void checkForUpdate();
|
|
88348
|
+
}
|
|
87814
88349
|
}, CONTROL_POLL_MS);
|
|
87815
88350
|
controlPoll.unref?.();
|
|
87816
88351
|
if (runtime.entryPath) {
|
|
@@ -87844,7 +88379,7 @@ import os16 from "node:os";
|
|
|
87844
88379
|
import path18 from "node:path";
|
|
87845
88380
|
|
|
87846
88381
|
// ../../apps/collector/src/codexReviewFacts.ts
|
|
87847
|
-
import { closeSync as
|
|
88382
|
+
import { closeSync as closeSync5, lstatSync as lstatSync4, openSync as openSync5, readSync as readSync4, realpathSync as realpathSync3 } from "node:fs";
|
|
87848
88383
|
import path17 from "node:path";
|
|
87849
88384
|
var pointerSchema = external_exports.object({
|
|
87850
88385
|
source: external_exports.unknown().optional(),
|
|
@@ -87882,7 +88417,7 @@ function readCodexReviewFacts(row, id, home) {
|
|
|
87882
88417
|
}
|
|
87883
88418
|
}
|
|
87884
88419
|
function readPrefix(file2) {
|
|
87885
|
-
const fd =
|
|
88420
|
+
const fd = openSync5(file2, "r");
|
|
87886
88421
|
const buffer = Buffer.alloc(MAX_PREFIX_BYTES);
|
|
87887
88422
|
let used = 0;
|
|
87888
88423
|
let first = -1;
|
|
@@ -87903,7 +88438,7 @@ function readPrefix(file2) {
|
|
|
87903
88438
|
used = end;
|
|
87904
88439
|
}
|
|
87905
88440
|
} finally {
|
|
87906
|
-
|
|
88441
|
+
closeSync5(fd);
|
|
87907
88442
|
}
|
|
87908
88443
|
if (first < 0 || second < 0) return null;
|
|
87909
88444
|
try {
|
|
@@ -88283,7 +88818,7 @@ async function probeClaudeDesktopSessions(wanted, installed, reportFault, root =
|
|
|
88283
88818
|
|
|
88284
88819
|
// src/cli.ts
|
|
88285
88820
|
import { spawn as spawn2, spawnSync as spawnSync5 } from "node:child_process";
|
|
88286
|
-
import { existsSync as existsSync12, mkdirSync as
|
|
88821
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync12, openSync as openSync7, realpathSync as realpathSync5, rmSync as rmSync7 } from "node:fs";
|
|
88287
88822
|
import path28 from "node:path";
|
|
88288
88823
|
import { parseArgs } from "node:util";
|
|
88289
88824
|
|
|
@@ -88291,7 +88826,7 @@ import { parseArgs } from "node:util";
|
|
|
88291
88826
|
import { execFileSync as execFileSync5, spawnSync } from "node:child_process";
|
|
88292
88827
|
import {
|
|
88293
88828
|
existsSync as existsSync9,
|
|
88294
|
-
mkdirSync as
|
|
88829
|
+
mkdirSync as mkdirSync9,
|
|
88295
88830
|
mkdtempSync,
|
|
88296
88831
|
readdirSync as readdirSync5,
|
|
88297
88832
|
readFileSync as readFileSync11,
|
|
@@ -89330,7 +89865,7 @@ function realReadTextFileStrict(filePath) {
|
|
|
89330
89865
|
}
|
|
89331
89866
|
}
|
|
89332
89867
|
function realWriteTextFile(filePath, content) {
|
|
89333
|
-
|
|
89868
|
+
mkdirSync9(path21.dirname(filePath), { recursive: true });
|
|
89334
89869
|
writeFileSync8(filePath, content);
|
|
89335
89870
|
}
|
|
89336
89871
|
function codexConfigPath(homeDir = os19.homedir()) {
|
|
@@ -89395,7 +89930,7 @@ function realRunInteractive(bin, args, env) {
|
|
|
89395
89930
|
|
|
89396
89931
|
// src/connections.ts
|
|
89397
89932
|
import { execFileSync as execFileSync6, spawnSync as spawnSync2 } from "node:child_process";
|
|
89398
|
-
import { mkdirSync as
|
|
89933
|
+
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "node:fs";
|
|
89399
89934
|
import path22 from "node:path";
|
|
89400
89935
|
function probeConnections(clients, deps) {
|
|
89401
89936
|
const reports = [];
|
|
@@ -89524,7 +90059,7 @@ function detectedClientsPath(homeDir) {
|
|
|
89524
90059
|
function saveDetectedClients(clients, pathValue, homeDir) {
|
|
89525
90060
|
try {
|
|
89526
90061
|
const file2 = detectedClientsPath(homeDir);
|
|
89527
|
-
|
|
90062
|
+
mkdirSync10(path22.dirname(file2), { recursive: true });
|
|
89528
90063
|
const record2 = {
|
|
89529
90064
|
detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
89530
90065
|
path: pathValue,
|
|
@@ -89608,7 +90143,7 @@ function whichAllWith(pathValue) {
|
|
|
89608
90143
|
}
|
|
89609
90144
|
|
|
89610
90145
|
// src/doctor.ts
|
|
89611
|
-
import { closeSync as
|
|
90146
|
+
import { closeSync as closeSync6, fstatSync, openSync as openSync6, readSync as readSync5 } from "node:fs";
|
|
89612
90147
|
|
|
89613
90148
|
// src/update.ts
|
|
89614
90149
|
import path24 from "node:path";
|
|
@@ -89633,7 +90168,7 @@ import { accessSync, constants as constants2 } from "node:fs";
|
|
|
89633
90168
|
import path23 from "node:path";
|
|
89634
90169
|
|
|
89635
90170
|
// src/version.ts
|
|
89636
|
-
var CLI_VERSION = true ? "0.4.
|
|
90171
|
+
var CLI_VERSION = true ? "0.4.8" : "0.0.0-checkout";
|
|
89637
90172
|
var CLI_RELEASE_BUILD = true ? true : false;
|
|
89638
90173
|
var CLI_PACKAGE_NAME = "@usepowerplant/cli";
|
|
89639
90174
|
|
|
@@ -89937,7 +90472,7 @@ function redactedLogTail(raw) {
|
|
|
89937
90472
|
function readDoctorLogTail(path29) {
|
|
89938
90473
|
let fd;
|
|
89939
90474
|
try {
|
|
89940
|
-
fd =
|
|
90475
|
+
fd = openSync6(path29, "r");
|
|
89941
90476
|
} catch (error62) {
|
|
89942
90477
|
if (error62.code === "ENOENT") return { path: path29, lines: null };
|
|
89943
90478
|
return { path: path29, lines: null, readError: error62 instanceof Error ? error62.message : String(error62) };
|
|
@@ -89953,7 +90488,7 @@ function readDoctorLogTail(path29) {
|
|
|
89953
90488
|
} catch (error62) {
|
|
89954
90489
|
return { path: path29, lines: null, readError: error62 instanceof Error ? error62.message : String(error62) };
|
|
89955
90490
|
} finally {
|
|
89956
|
-
|
|
90491
|
+
closeSync6(fd);
|
|
89957
90492
|
}
|
|
89958
90493
|
}
|
|
89959
90494
|
function renderDoctorReport(i) {
|
|
@@ -90205,6 +90740,9 @@ async function confirmResume(deps, log3, indent) {
|
|
|
90205
90740
|
}
|
|
90206
90741
|
}
|
|
90207
90742
|
|
|
90743
|
+
// src/protectedFolders.ts
|
|
90744
|
+
var MACOS_BLOCKED_FOLDER_REMEDY = "To allow it, open System Settings > Privacy & Security > Files and Folders, enable the folder for node, then run `powerplant init`.";
|
|
90745
|
+
|
|
90208
90746
|
// src/init.ts
|
|
90209
90747
|
function syncNote(credentials) {
|
|
90210
90748
|
const org = credentials.orgName ?? "your organization";
|
|
@@ -90249,7 +90787,7 @@ async function runInit(deps) {
|
|
|
90249
90787
|
}
|
|
90250
90788
|
log3("");
|
|
90251
90789
|
const orgName = credentials.orgName;
|
|
90252
|
-
let
|
|
90790
|
+
let repoDiscoveryDidWork = false;
|
|
90253
90791
|
let protectedFoldersNeedAttention = false;
|
|
90254
90792
|
let installed = false;
|
|
90255
90793
|
let menubarDidWork = false;
|
|
@@ -90262,56 +90800,68 @@ async function runInit(deps) {
|
|
|
90262
90800
|
"Checking recent session locations\u2026",
|
|
90263
90801
|
deps.findProtectedSessions
|
|
90264
90802
|
);
|
|
90265
|
-
const
|
|
90266
|
-
const
|
|
90267
|
-
|
|
90268
|
-
|
|
90269
|
-
|
|
90270
|
-
|
|
90271
|
-
|
|
90272
|
-
|
|
90273
|
-
|
|
90274
|
-
|
|
90275
|
-
|
|
90276
|
-
|
|
90277
|
-
|
|
90803
|
+
const reviewOf = (entry) => protectedRootReview(credentials, entry.root);
|
|
90804
|
+
const blocked = protectedSessions.filter((entry) => reviewOf(entry) === "blocked");
|
|
90805
|
+
const fresh = protectedSessions.filter((entry) => reviewOf(entry) === "unreviewed");
|
|
90806
|
+
if (blocked.length > 0 || fresh.length > 0) {
|
|
90807
|
+
const org = orgName ?? "your organization";
|
|
90808
|
+
const consented = credentials.repoDiscoveryConsentedAt !== void 0;
|
|
90809
|
+
const toProbe = [];
|
|
90810
|
+
let needsAttention = false;
|
|
90811
|
+
if (fresh.length > 0) {
|
|
90812
|
+
log3(" Coding agents recorded recent sessions in folders macOS is currently hiding from Powerplant:");
|
|
90813
|
+
logProtectedFolders(log3, fresh);
|
|
90814
|
+
if (!deps.promptAllowed) {
|
|
90815
|
+
needsAttention = true;
|
|
90816
|
+
logProtectedSessionsStayLocal(log3, fresh);
|
|
90817
|
+
log3(` Run \`powerplant init\` in a terminal to ${consented ? "scan" : "review"} them.`);
|
|
90818
|
+
} else {
|
|
90278
90819
|
log3(
|
|
90279
|
-
` If
|
|
90820
|
+
consented ? ` You already allowed Git metadata scans. Checking those folders for ${org}'s work repositories.` : ` If you allow it, Powerplant reads only Git metadata there, to find ${org}'s work repositories.`
|
|
90280
90821
|
);
|
|
90281
|
-
|
|
90822
|
+
if (consented || await confirmProtectedRepoDiscovery(deps)) toProbe.push(...fresh);
|
|
90823
|
+
else {
|
|
90824
|
+
needsAttention = true;
|
|
90825
|
+
logProtectedSessionsStayLocal(log3, fresh);
|
|
90826
|
+
}
|
|
90827
|
+
}
|
|
90828
|
+
}
|
|
90829
|
+
if (blocked.length > 0) {
|
|
90830
|
+
if (deps.promptAllowed) {
|
|
90831
|
+
log3(` Checking whether macOS still blocks ${displayRoots(blocked)}.`);
|
|
90832
|
+
toProbe.push(...blocked);
|
|
90833
|
+
} else {
|
|
90834
|
+
needsAttention = true;
|
|
90835
|
+
logBlockedByMacOS(log3, blocked);
|
|
90836
|
+
}
|
|
90837
|
+
}
|
|
90838
|
+
if (toProbe.length > 0) {
|
|
90839
|
+
log3(' macOS may label Powerplant as "node" because Powerplant runs on Node.js.');
|
|
90840
|
+
const probe = async (roots) => {
|
|
90841
|
+
const result2 = await deps.activity(
|
|
90282
90842
|
"Scanning recorded session folders for Git repositories\u2026",
|
|
90283
|
-
() => deps.allowProtectedRepoDiscovery(
|
|
90843
|
+
() => deps.allowProtectedRepoDiscovery(roots)
|
|
90284
90844
|
);
|
|
90285
|
-
|
|
90286
|
-
|
|
90287
|
-
if (
|
|
90288
|
-
log3(
|
|
90289
|
-
|
|
90290
|
-
|
|
90291
|
-
|
|
90292
|
-
|
|
90293
|
-
|
|
90294
|
-
|
|
90295
|
-
|
|
90296
|
-
|
|
90297
|
-
|
|
90298
|
-
|
|
90299
|
-
|
|
90300
|
-
|
|
90301
|
-
}
|
|
90302
|
-
} else {
|
|
90303
|
-
logProtectedSessionsStayLocal(log3, result.needsReview);
|
|
90304
|
-
needsAttention = true;
|
|
90305
|
-
}
|
|
90845
|
+
needsAttention = reportProtectedRepoDiscovery(log3, result2) || needsAttention;
|
|
90846
|
+
const unblocked = result2.unmatched.filter((entry) => reviewOf(entry) === "blocked");
|
|
90847
|
+
if (unblocked.length > 0) {
|
|
90848
|
+
log3(` \u2713 macOS now lets Powerplant read ${displayRoots(unblocked)}. No work repositories were found there.`);
|
|
90849
|
+
}
|
|
90850
|
+
if (result2.enabled.length > 0 || unblocked.length > 0) repoDiscoveryDidWork = true;
|
|
90851
|
+
return result2;
|
|
90852
|
+
};
|
|
90853
|
+
const result = await probe(toProbe);
|
|
90854
|
+
if (result.needsReview.length > 0) {
|
|
90855
|
+
log3(" Some of those sessions use Git checkouts in other folders macOS is hiding from Powerplant:");
|
|
90856
|
+
logProtectedFolders(log3, result.needsReview);
|
|
90857
|
+
const second = await probe([...result.unreviewed, ...result.needsReview]);
|
|
90858
|
+
if (second.unreviewed.length > 0) {
|
|
90859
|
+
logProtectedSessionsStayLocal(log3, second.unreviewed);
|
|
90860
|
+
needsAttention = true;
|
|
90306
90861
|
}
|
|
90307
|
-
protectedFoldersNeedAttention = needsAttention;
|
|
90308
|
-
} else {
|
|
90309
|
-
logProtectedSessionsStayLocal(log3, needsReview);
|
|
90310
90862
|
}
|
|
90311
|
-
} else {
|
|
90312
|
-
logProtectedSessionsStayLocal(log3, needsReview);
|
|
90313
|
-
log3(" Run `powerplant init` in a terminal to review them.");
|
|
90314
90863
|
}
|
|
90864
|
+
protectedFoldersNeedAttention = needsAttention;
|
|
90315
90865
|
}
|
|
90316
90866
|
}
|
|
90317
90867
|
if (deps.platform !== "darwin") {
|
|
@@ -90351,7 +90901,7 @@ async function runInit(deps) {
|
|
|
90351
90901
|
log3("");
|
|
90352
90902
|
const connectDidWork = outcome !== null && outcome.connected + outcome.configured > outcome.unchanged;
|
|
90353
90903
|
const connectSettled = outcome !== null && outcome.found > 0 && outcome.pending === 0 && outcome.failed === 0 && outcome.skipped === 0 && outcome.unchanged === outcome.found && !connectDidWork;
|
|
90354
|
-
if (!enrolled && !installed && !menubarDidWork && !
|
|
90904
|
+
if (!enrolled && !installed && !menubarDidWork && !repoDiscoveryDidWork && !protectedFoldersNeedAttention && pauseOffer === "not-paused" && connectSettled) {
|
|
90355
90905
|
log3("Everything is already set up.");
|
|
90356
90906
|
return;
|
|
90357
90907
|
}
|
|
@@ -90428,24 +90978,22 @@ function logProtectedFolders(log3, roots) {
|
|
|
90428
90978
|
}
|
|
90429
90979
|
function reportProtectedRepoDiscovery(log3, result) {
|
|
90430
90980
|
if (result.enabled.length > 0) {
|
|
90431
|
-
log3(
|
|
90432
|
-
` \u2713 Repository scanning enabled for ${result.enabled.map((entry) => entry.displayRoot).join(", ")}`
|
|
90433
|
-
);
|
|
90434
|
-
}
|
|
90435
|
-
if (result.blocked.length > 0) {
|
|
90436
|
-
log3(
|
|
90437
|
-
` ! macOS blocked Powerplant from reading ${result.blocked.map((entry) => entry.displayRoot).join(", ")}.`
|
|
90438
|
-
);
|
|
90439
|
-
log3(
|
|
90440
|
-
" Sessions there will stay local. To allow it, open System Settings > Privacy & Security > Files and Folders, enable the folder for node, then rerun `powerplant init`."
|
|
90441
|
-
);
|
|
90981
|
+
log3(` \u2713 Repository scanning enabled for ${displayRoots(result.enabled)}`);
|
|
90442
90982
|
}
|
|
90983
|
+
if (result.blocked.length > 0) logBlockedByMacOS(log3, result.blocked);
|
|
90443
90984
|
return result.blocked.length > 0;
|
|
90444
90985
|
}
|
|
90986
|
+
function logBlockedByMacOS(log3, roots) {
|
|
90987
|
+
log3(` ! macOS blocked Powerplant from reading ${displayRoots(roots)}.`);
|
|
90988
|
+
log3(` Sessions there will stay local. ${MACOS_BLOCKED_FOLDER_REMEDY}`);
|
|
90989
|
+
}
|
|
90990
|
+
function displayRoots(roots) {
|
|
90991
|
+
return roots.map((entry) => entry.displayRoot).join(", ");
|
|
90992
|
+
}
|
|
90445
90993
|
function logProtectedSessionsStayLocal(log3, found) {
|
|
90446
90994
|
const total = found.reduce((sum2, entry) => sum2 + entry.sessionCount, 0);
|
|
90447
90995
|
log3(
|
|
90448
|
-
` ! Repository matching for ${total} recent session${total === 1 ? "" : "s"} under ${found
|
|
90996
|
+
` ! Repository matching for ${total} recent session${total === 1 ? "" : "s"} under ${displayRoots(found)} was not enabled.`
|
|
90449
90997
|
);
|
|
90450
90998
|
log3(" Sessions outside folders you explicitly added will stay local.");
|
|
90451
90999
|
}
|
|
@@ -90489,6 +91037,8 @@ function renderMenu(i) {
|
|
|
90489
91037
|
}
|
|
90490
91038
|
if (items.length > 0) items.push({ type: "separator" });
|
|
90491
91039
|
const progress = updateProgressView(i.update.progress, i.cliVersion, i.now);
|
|
91040
|
+
let updateStart;
|
|
91041
|
+
let updateCount = 0;
|
|
90492
91042
|
const unsupported = isUnsupportedVersion(i.cliVersion, i.oldestSupportedVersion);
|
|
90493
91043
|
if (unsupported) {
|
|
90494
91044
|
items.push(
|
|
@@ -90497,7 +91047,9 @@ function renderMenu(i) {
|
|
|
90497
91047
|
`You have ${i.cliVersion}; the oldest supported is ${i.oldestSupportedVersion}. Update now.`
|
|
90498
91048
|
)
|
|
90499
91049
|
);
|
|
91050
|
+
updateStart ??= items.length;
|
|
90500
91051
|
items.push(...updateItems(i, progress, envArgs, problem));
|
|
91052
|
+
updateCount = items.length - updateStart;
|
|
90501
91053
|
items.push({ type: "separator" });
|
|
90502
91054
|
}
|
|
90503
91055
|
if (i.credentialsError !== void 0) {
|
|
@@ -90530,6 +91082,7 @@ function renderMenu(i) {
|
|
|
90530
91082
|
}
|
|
90531
91083
|
if (!unsupported && (isUpdateAvailable(i.cliVersion, i.latestVersion) || progress.kind !== "none")) {
|
|
90532
91084
|
items.push({ type: "separator" });
|
|
91085
|
+
updateStart = items.length;
|
|
90533
91086
|
if (progress.kind === "none") {
|
|
90534
91087
|
items.push({
|
|
90535
91088
|
type: "status",
|
|
@@ -90539,6 +91092,7 @@ function renderMenu(i) {
|
|
|
90539
91092
|
});
|
|
90540
91093
|
}
|
|
90541
91094
|
items.push(...updateItems(i, progress, envArgs, problem));
|
|
91095
|
+
updateCount = items.length - updateStart;
|
|
90542
91096
|
}
|
|
90543
91097
|
items.push({ type: "separator" });
|
|
90544
91098
|
items.push({ type: "action", title: "Open Powerplant", url: i.config.webUrl });
|
|
@@ -90548,6 +91102,17 @@ function renderMenu(i) {
|
|
|
90548
91102
|
const icon = attention ? "attention" : i.control.paused ? "paused" : "running";
|
|
90549
91103
|
return {
|
|
90550
91104
|
contract: MENU_CONTRACT,
|
|
91105
|
+
...i.update.inPlace && updateStart !== void 0 && {
|
|
91106
|
+
cliUpdate: {
|
|
91107
|
+
startIndex: updateStart,
|
|
91108
|
+
itemCount: updateCount,
|
|
91109
|
+
state: progress.kind === "none" ? "available" : progress.kind,
|
|
91110
|
+
target: progress.kind === "none" ? i.latestVersion : progress.target,
|
|
91111
|
+
...progress.kind === "failed" && { detail: progress.detail },
|
|
91112
|
+
run: ["update", "--background", ...envArgs],
|
|
91113
|
+
logPath: updateLogPath()
|
|
91114
|
+
}
|
|
91115
|
+
},
|
|
90551
91116
|
...i.desktopDiagnostic && { desktopDiagnostic: i.desktopDiagnostic },
|
|
90552
91117
|
appEnv: i.config.appEnv,
|
|
90553
91118
|
cliVersion: i.cliVersion,
|
|
@@ -91077,8 +91642,9 @@ function renderStatus(i) {
|
|
|
91077
91642
|
);
|
|
91078
91643
|
lines.push(`Syncing: ${describeStoredCapture(i.credentials)}.`);
|
|
91079
91644
|
for (const entry of i.protectedSessions ?? []) {
|
|
91645
|
+
const sessions = `${entry.sessionCount} recent agent session${entry.sessionCount === 1 ? "" : "s"}`;
|
|
91080
91646
|
lines.push(
|
|
91081
|
-
`\u26A0
|
|
91647
|
+
protectedRootReview(i.credentials, entry.root) === "blocked" ? `\u26A0 macOS is blocking Powerplant from reading ${entry.displayRoot} (${sessions}). Those sessions are staying local. ${MACOS_BLOCKED_FOLDER_REMEDY}` : `\u26A0 Repository matching is not enabled for ${sessions} under ${entry.displayRoot}. Sessions outside folders you explicitly added are staying local, run \`powerplant init\` to review Git repository scanning.`
|
|
91082
91648
|
);
|
|
91083
91649
|
}
|
|
91084
91650
|
}
|
|
@@ -91130,9 +91696,17 @@ function renderDaemon(i) {
|
|
|
91130
91696
|
` The daemon is on ${status.version}, this CLI is ${i.cliVersion}, it restarts onto the installed version within minutes, or now via: powerplant restart`
|
|
91131
91697
|
);
|
|
91132
91698
|
}
|
|
91133
|
-
|
|
91134
|
-
|
|
91699
|
+
const signals = describeSyncSignals(
|
|
91700
|
+
{
|
|
91701
|
+
lastSessionActivityAt: status.lastUploadAt ?? null,
|
|
91702
|
+
lastCheckedInAt: status.lastPass?.at ?? null,
|
|
91703
|
+
syncPaused: null,
|
|
91704
|
+
lastSyncErrorAt: status.lastPass && !status.lastPass.ok ? status.lastPass.at : null
|
|
91705
|
+
},
|
|
91706
|
+
new Date(i.now),
|
|
91707
|
+
"last upload unmeasured"
|
|
91135
91708
|
);
|
|
91709
|
+
lines.push(signals === null ? " No pass has run yet in this run." : ` ${signals}`);
|
|
91136
91710
|
if (status.lastPass && !status.lastPass.ok) {
|
|
91137
91711
|
lines.push(
|
|
91138
91712
|
` \u26A0 Last pass failed: ${status.lastPass.error ?? "unknown error"} (${status.consecutiveFailures} consecutive failure${status.consecutiveFailures === 1 ? "" : "s"}).`
|
|
@@ -91159,7 +91733,10 @@ function statusJson(i) {
|
|
|
91159
91733
|
},
|
|
91160
91734
|
protectedRepoDiscoveryNeeded: (i.protectedSessions ?? []).map((entry) => ({
|
|
91161
91735
|
displayRoot: entry.displayRoot,
|
|
91162
|
-
sessionCount: entry.sessionCount
|
|
91736
|
+
sessionCount: entry.sessionCount,
|
|
91737
|
+
// The member said yes and macOS refused: System Settings, not init,
|
|
91738
|
+
// is the fix, so a tool rendering this must not say "review".
|
|
91739
|
+
blockedByMacOS: i.credentials !== null && protectedRootReview(i.credentials, entry.root) === "blocked"
|
|
91163
91740
|
})),
|
|
91164
91741
|
...i.credentialsError !== void 0 && { credentialsError: i.credentialsError },
|
|
91165
91742
|
service: i.service,
|
|
@@ -91315,7 +91892,7 @@ function createTerminalOutput(options) {
|
|
|
91315
91892
|
}
|
|
91316
91893
|
|
|
91317
91894
|
// src/menubarApp.ts
|
|
91318
|
-
import { existsSync as existsSync11, mkdirSync as
|
|
91895
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync11, renameSync as renameSync4, rmSync as rmSync6 } from "node:fs";
|
|
91319
91896
|
import os21 from "node:os";
|
|
91320
91897
|
import path27 from "node:path";
|
|
91321
91898
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
@@ -91383,11 +91960,11 @@ function realMenubarAppOps(input2) {
|
|
|
91383
91960
|
},
|
|
91384
91961
|
replace: (zip, appPath) => {
|
|
91385
91962
|
const parent = path27.dirname(appPath);
|
|
91386
|
-
|
|
91963
|
+
mkdirSync11(parent, { recursive: true });
|
|
91387
91964
|
const staging = path27.join(parent, `.${MENUBAR_APP_NAME}.swap-${process.pid}`);
|
|
91388
91965
|
const retired = path27.join(parent, `.${MENUBAR_APP_NAME}.old-${process.pid}`);
|
|
91389
|
-
|
|
91390
|
-
|
|
91966
|
+
rmSync6(staging, { recursive: true, force: true });
|
|
91967
|
+
rmSync6(retired, { recursive: true, force: true });
|
|
91391
91968
|
try {
|
|
91392
91969
|
const extract = spawnSync4("ditto", ["-xk", zip, staging]);
|
|
91393
91970
|
if (extract.status !== 0) {
|
|
@@ -91400,16 +91977,16 @@ function realMenubarAppOps(input2) {
|
|
|
91400
91977
|
throw new Error(`${zip} does not contain ${MENUBAR_APP_NAME}`);
|
|
91401
91978
|
}
|
|
91402
91979
|
const hadOld = existsSync11(appPath);
|
|
91403
|
-
if (hadOld)
|
|
91980
|
+
if (hadOld) renameSync4(appPath, retired);
|
|
91404
91981
|
try {
|
|
91405
|
-
|
|
91982
|
+
renameSync4(extracted, appPath);
|
|
91406
91983
|
} catch (error62) {
|
|
91407
|
-
if (hadOld)
|
|
91984
|
+
if (hadOld) renameSync4(retired, appPath);
|
|
91408
91985
|
throw error62;
|
|
91409
91986
|
}
|
|
91410
91987
|
} finally {
|
|
91411
|
-
|
|
91412
|
-
|
|
91988
|
+
rmSync6(staging, { recursive: true, force: true });
|
|
91989
|
+
rmSync6(retired, { recursive: true, force: true });
|
|
91413
91990
|
}
|
|
91414
91991
|
},
|
|
91415
91992
|
launch: (appPath) => {
|
|
@@ -91754,7 +92331,11 @@ ${USAGE}`);
|
|
|
91754
92331
|
// Enrollment's own narration (org, token location, capture boundary)
|
|
91755
92332
|
// indents under stage 1.
|
|
91756
92333
|
enroll: () => enroll2((line) => terminal.log(` ${line}`)),
|
|
91757
|
-
|
|
92334
|
+
// Discovery may read a worktree pointer under a root the store
|
|
92335
|
+
// already allows, so a revoked checkout root still surfaces.
|
|
92336
|
+
findProtectedSessions: () => findRecentProtectedSessions({
|
|
92337
|
+
environment: { allowedProtectedRoots: loadCredentials(config2.appEnv)?.repoDiscoveryRoots ?? [] }
|
|
92338
|
+
}),
|
|
91758
92339
|
promptAllowed: pickerOn,
|
|
91759
92340
|
promptLine: realPromptLine,
|
|
91760
92341
|
allowProtectedRepoDiscovery: (found) => allowProtectedRepoDiscovery(config2, found),
|
|
@@ -91828,12 +92409,14 @@ The access you granted these agents stays on your Powerplant account until you r
|
|
|
91828
92409
|
}
|
|
91829
92410
|
case "status": {
|
|
91830
92411
|
const stored = loadCredentialsSafely(config2);
|
|
91831
|
-
const
|
|
92412
|
+
const credentials = stored.credentials;
|
|
91832
92413
|
const [latestVersion, oldestSupportedVersion, protectedSessions] = await Promise.all([
|
|
91833
|
-
fetchAvailableVersion(config2,
|
|
91834
|
-
oldestSupportedFor(
|
|
91835
|
-
process.platform === "darwin" &&
|
|
91836
|
-
|
|
92414
|
+
fetchAvailableVersion(config2, credentials),
|
|
92415
|
+
oldestSupportedFor(credentials),
|
|
92416
|
+
process.platform === "darwin" && credentials ? findRecentProtectedSessions({
|
|
92417
|
+
environment: { allowedProtectedRoots: credentials.repoDiscoveryRoots ?? [] }
|
|
92418
|
+
}).then(
|
|
92419
|
+
(found) => found.filter((entry) => protectedRootReview(credentials, entry.root) !== "allowed")
|
|
91837
92420
|
) : []
|
|
91838
92421
|
]);
|
|
91839
92422
|
const inputs = {
|
|
@@ -91976,7 +92559,7 @@ The access you granted these agents stays on your Powerplant account until you r
|
|
|
91976
92559
|
const on = parseAutomaticFlag(values.automatic);
|
|
91977
92560
|
setAutomaticUpdates(config2.appEnv, on);
|
|
91978
92561
|
console.log(
|
|
91979
|
-
on ? "Automatic updates on: the background sync installs each new
|
|
92562
|
+
on ? "Automatic updates on: the background sync checks for a release now and installs each new one within an hour of publication." : "Automatic updates off: powerplant status and the menu bar still say when a release is available."
|
|
91980
92563
|
);
|
|
91981
92564
|
return;
|
|
91982
92565
|
}
|
|
@@ -92139,8 +92722,8 @@ function startBackgroundUpdate(config2, target) {
|
|
|
92139
92722
|
readProgress: updateProgress.read,
|
|
92140
92723
|
writeProgress: updateProgress.write,
|
|
92141
92724
|
spawnChild: () => {
|
|
92142
|
-
|
|
92143
|
-
const log3 =
|
|
92725
|
+
mkdirSync12(path28.dirname(updateLogPath()), { recursive: true });
|
|
92726
|
+
const log3 = openSync7(updateLogPath(), "a");
|
|
92144
92727
|
const child = spawn2(process.execPath, [entryPath(), "update", ...envArgsFor(config2)], {
|
|
92145
92728
|
detached: true,
|
|
92146
92729
|
stdio: ["ignore", log3, log3],
|
|
@@ -92202,7 +92785,7 @@ function restartSyncOnNewCode(config2) {
|
|
|
92202
92785
|
var updateProgress = {
|
|
92203
92786
|
read: () => readUpdateProgress(realReadTextFile),
|
|
92204
92787
|
write: (record2) => realWriteTextFile(updateProgressPath(), JSON.stringify(record2, null, 2)),
|
|
92205
|
-
clear: () =>
|
|
92788
|
+
clear: () => rmSync7(updateProgressPath(), { force: true })
|
|
92206
92789
|
};
|
|
92207
92790
|
function envArgsFor(config2) {
|
|
92208
92791
|
return config2.appEnv === "production" ? [] : ["--env", config2.appEnv];
|