@youdie006/prodex 0.37.1 → 0.38.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1736,6 +1736,31 @@ async function dispatchArrowKey(cdp, key) {
1736
1736
  await cdp.send("Input.dispatchKeyEvent", { type: "rawKeyDown", key, code, windowsVirtualKeyCode: virtualKey });
1737
1737
  await cdp.send("Input.dispatchKeyEvent", { type: "keyUp", key, code, windowsVirtualKeyCode: virtualKey });
1738
1738
  }
1739
+ /**
1740
+ * How to read every step of the slider and leave it as it was.
1741
+ *
1742
+ * The control shows one step at a time, and that single visible label was all
1743
+ * `pro browser models` ever reported - which is how a machine on this same
1744
+ * account concluded "Pro does not exist here" from five listings that each read
1745
+ * Instant / GPT-5.6 Sol / GPT-5.5, with the slider parked on Instant every
1746
+ * time. Seeing the rest means walking it, and walking someone's setting is only
1747
+ * acceptable if it is put back, so the plan always ends where it started.
1748
+ *
1749
+ * Refuses bounds that do not look like this control, so a misread never becomes
1750
+ * thousands of keystrokes in someone's browser.
1751
+ */
1752
+ export function sliderWalkPlan(state) {
1753
+ const { position, min, max } = state;
1754
+ if (!Number.isInteger(position) || !Number.isInteger(min) || !Number.isInteger(max))
1755
+ return undefined;
1756
+ const steps = max - min;
1757
+ if (steps < 1 || steps > 12)
1758
+ return undefined;
1759
+ if (position < min || position > max)
1760
+ return undefined;
1761
+ const offset = position - min;
1762
+ return { toBottom: offset, climb: steps, back: offset };
1763
+ }
1739
1764
  async function selectModelReasoning(cdp, options, selectionWarnings = []) {
1740
1765
  if (!options.model && !options.proMode && !options.effort)
1741
1766
  return;
@@ -3228,6 +3253,52 @@ export async function listChatGptSidebarProjects(input = {}) {
3228
3253
  }
3229
3254
  return { url: status.url, projects };
3230
3255
  }
3256
+ /**
3257
+ * Every step of the effort slider, read by walking it and putting it back.
3258
+ *
3259
+ * Best effort: a slider that cannot be focused, read or walked leaves the
3260
+ * listing exactly as it was rather than failing a read-only command.
3261
+ */
3262
+ async function readEffortSteps(cdp) {
3263
+ try {
3264
+ const focused = await cdp.evaluate(focusPowerSliderExpression());
3265
+ if (!focused?.ok)
3266
+ return undefined;
3267
+ const start = await cdp.evaluate(powerSliderStateExpression());
3268
+ if (!start?.ok)
3269
+ return undefined;
3270
+ const plan = sliderWalkPlan({ position: start.position, min: start.min, max: start.max });
3271
+ if (!plan)
3272
+ return undefined;
3273
+ const current = start.effort ?? null;
3274
+ for (let i = 0; i < plan.toBottom; i += 1) {
3275
+ await dispatchArrowKey(cdp, "ArrowLeft");
3276
+ await sleep(120);
3277
+ }
3278
+ const labels = [];
3279
+ const record = async () => {
3280
+ const state = await cdp.evaluate(powerSliderStateExpression());
3281
+ const label = state?.effort?.trim();
3282
+ if (label && !labels.includes(label))
3283
+ labels.push(label);
3284
+ };
3285
+ await record();
3286
+ for (let i = 0; i < plan.climb; i += 1) {
3287
+ await dispatchArrowKey(cdp, "ArrowRight");
3288
+ await sleep(120);
3289
+ await record();
3290
+ }
3291
+ // Put it back. Reading someone's setting must not change it.
3292
+ for (let i = 0; i < plan.climb - plan.back; i += 1) {
3293
+ await dispatchArrowKey(cdp, "ArrowLeft");
3294
+ await sleep(120);
3295
+ }
3296
+ return labels.length > 0 ? { labels, current } : undefined;
3297
+ }
3298
+ catch {
3299
+ return undefined;
3300
+ }
3301
+ }
3231
3302
  export async function listChatGptModelOptions(input = {}) {
3232
3303
  const port = resolveCdpPort(input.port);
3233
3304
  const timeoutMs = input.timeoutMs ?? 15_000;
@@ -3261,7 +3332,8 @@ export async function listChatGptModelOptions(input = {}) {
3261
3332
  if (!opened)
3262
3333
  throw new Error("ChatGPT model menu did not open after clicking the selector");
3263
3334
  const options = await cdp.evaluate(modelMenuOptionsExpression());
3264
- return { url: status.url, options };
3335
+ const effortSteps = await readEffortSteps(cdp);
3336
+ return { url: status.url, options, ...(effortSteps ? { effortSteps } : {}) };
3265
3337
  }
3266
3338
  finally {
3267
3339
  try {
package/dist/cli-pro.js CHANGED
@@ -447,7 +447,17 @@ export async function runProCommand(rest, io, runCliFn) {
447
447
  for (const option of listed.options) {
448
448
  io.stdout(formatModelMenuOption(option));
449
449
  }
450
- io.stdout("An arrow shows what that row is set to now; --model / --effort reach into those submenus (e.g. --model Pro).");
450
+ if (listed.effortSteps) {
451
+ io.stdout("");
452
+ io.stdout("Effort steps on this account (the slider was walked and put back):");
453
+ for (const label of listed.effortSteps.labels) {
454
+ io.stdout(`${label === listed.effortSteps.current ? "*" : " "} ${label}`);
455
+ }
456
+ io.stdout("Pass any of these to --effort. The list is complete: the slider shows one step at a time, so reading it any other way sees only the current one.");
457
+ }
458
+ else {
459
+ io.stdout("An arrow shows what that row is set to now; --model / --effort reach into those submenus (e.g. --model Pro).");
460
+ }
451
461
  return 0;
452
462
  }
453
463
  if (browserSubcommand === "projects") {
@@ -0,0 +1,210 @@
1
+ // Write a bridge record into a directory the kernel has pinned for us.
2
+ //
3
+ // The store's normal path renders an open directory handle as /proc/self/fd/N
4
+ // and joins the file name onto it, so the write lands in the directory that was
5
+ // validated rather than in one a symlink swap redirected. macOS has no
6
+ // traversable equivalent - /dev/fd/N stands in for the descriptor, not for a
7
+ // walkable directory - so that path fails there with a bare ENOENT and takes the
8
+ // whole ledger with it.
9
+ //
10
+ // This process buys the same guarantee a different way. It is spawned with its
11
+ // cwd already set to the directory to descend from, it refuses to continue
12
+ // unless "." is the very inode the parent validated, and it descends by chdir
13
+ // one no-symlink segment at a time, re-checking the inode after each step. The
14
+ // kernel holds the cwd's vnode, so once a step is confirmed nothing can redirect
15
+ // the relative paths that follow. That is precisely what the fd path bought.
16
+ //
17
+ // Everything after the anchoring uses the same helpers the in-process path uses,
18
+ // on relative names.
19
+ import { closeSync, constants, fstatSync, openSync } from "node:fs";
20
+ import { pathToFileURL } from "node:url";
21
+ import { link, lstat, readdir, rename, rm } from "node:fs/promises";
22
+ import { randomUUID } from "node:crypto";
23
+ import { writeVerifiedUtf8File } from "./safe-file.js";
24
+ function identityOfOpenDirectory(fd) {
25
+ const stat = fstatSync(fd, { bigint: true });
26
+ if (!stat.isDirectory())
27
+ throw new Error("Anchored writer expected a directory");
28
+ return { dev: stat.dev.toString(), ino: stat.ino.toString() };
29
+ }
30
+ function sameIdentity(a, b) {
31
+ return a.dev === b.dev && a.ino === b.ino;
32
+ }
33
+ /** Open a name relative to the cwd, refusing symlinks and non-directories. */
34
+ function openDirectoryHere(name) {
35
+ const directoryFlag = typeof constants.O_DIRECTORY === "number" ? constants.O_DIRECTORY : 0;
36
+ const noFollowFlag = typeof constants.O_NOFOLLOW === "number" ? constants.O_NOFOLLOW : 0;
37
+ return openSync(name, constants.O_RDONLY | directoryFlag | noFollowFlag);
38
+ }
39
+ function currentDirectoryIdentity() {
40
+ const fd = openDirectoryHere(".");
41
+ try {
42
+ return identityOfOpenDirectory(fd);
43
+ }
44
+ finally {
45
+ closeSync(fd);
46
+ }
47
+ }
48
+ /**
49
+ * Confirm the cwd is the directory the parent meant, then descend the segments
50
+ * so that the cwd ends up pinned to the directory the record belongs in.
51
+ */
52
+ export function anchorCurrentDirectory(anchor, segments) {
53
+ let here = currentDirectoryIdentity();
54
+ if (!sameIdentity(here, anchor)) {
55
+ throw new Error("Anchored writer was not started in the directory the caller validated");
56
+ }
57
+ for (const segment of segments) {
58
+ if (segment.length === 0 || segment === "." || segment === ".." || segment.includes("/")) {
59
+ throw new Error(`Anchored writer refuses to descend into ${JSON.stringify(segment)}`);
60
+ }
61
+ // O_NOFOLLOW proves the name is a real directory rather than a symlink, and
62
+ // the identity check after chdir proves we landed on that same directory
63
+ // and not on something swapped in between the two calls.
64
+ const fd = openDirectoryHere(segment);
65
+ let expected;
66
+ try {
67
+ expected = identityOfOpenDirectory(fd);
68
+ }
69
+ finally {
70
+ closeSync(fd);
71
+ }
72
+ process.chdir(segment);
73
+ here = currentDirectoryIdentity();
74
+ if (!sameIdentity(here, expected)) {
75
+ throw new Error(`Anchored writer landed somewhere other than ${JSON.stringify(segment)}`);
76
+ }
77
+ }
78
+ return here;
79
+ }
80
+ async function assertRegularFileIfExists(name) {
81
+ try {
82
+ const stat = await lstat(name);
83
+ if (stat.isSymbolicLink() || !stat.isFile()) {
84
+ throw new Error("Bridge record path must be a regular file and must not be a symlink");
85
+ }
86
+ }
87
+ catch (error) {
88
+ if (error.code !== "ENOENT")
89
+ throw error;
90
+ }
91
+ }
92
+ function temporaryName(fileName) {
93
+ return `.${fileName}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`;
94
+ }
95
+ export async function runAnchoredJob(job) {
96
+ const pinned = anchorCurrentDirectory(job.anchor, job.segments);
97
+ const stillPinned = async () => {
98
+ if (!sameIdentity(currentDirectoryIdentity(), pinned)) {
99
+ throw new Error("Anchored writer's directory changed underneath it");
100
+ }
101
+ };
102
+ const { fileName } = job;
103
+ if (fileName.length === 0 || fileName.includes("/") || fileName === "." || fileName === "..") {
104
+ throw new Error(`Anchored writer refuses the file name ${JSON.stringify(fileName)}`);
105
+ }
106
+ if (job.op === "deleteIfPresent") {
107
+ let stat;
108
+ try {
109
+ stat = await lstat(fileName);
110
+ }
111
+ catch (error) {
112
+ if (error.code === "ENOENT")
113
+ return { ok: true };
114
+ throw error;
115
+ }
116
+ if (stat.isSymbolicLink() || !stat.isFile()) {
117
+ throw new Error("Bridge record path must be a regular file and must not be a symlink");
118
+ }
119
+ await rm(fileName, { force: true });
120
+ return { ok: true };
121
+ }
122
+ if (job.op === "cleanupTempHardLinks") {
123
+ let targetStat;
124
+ try {
125
+ targetStat = await lstat(fileName);
126
+ }
127
+ catch (error) {
128
+ if (error.code === "ENOENT")
129
+ return { ok: true };
130
+ throw error;
131
+ }
132
+ if (targetStat.isSymbolicLink() || !targetStat.isFile() || targetStat.nlink <= 1)
133
+ return { ok: true };
134
+ const prefix = `.${fileName}.`;
135
+ for (const entry of await readdir(".", { withFileTypes: true })) {
136
+ if (!entry.isFile() || !entry.name.startsWith(prefix) || !entry.name.endsWith(".tmp"))
137
+ continue;
138
+ const tempStat = await lstat(entry.name).catch(() => undefined);
139
+ if (!tempStat?.isFile() || tempStat.isSymbolicLink())
140
+ continue;
141
+ if (tempStat.dev === targetStat.dev && tempStat.ino === targetStat.ino) {
142
+ await rm(entry.name, { force: true });
143
+ }
144
+ }
145
+ return { ok: true };
146
+ }
147
+ const content = job.content ?? "";
148
+ const tmpName = temporaryName(fileName);
149
+ if (job.op === "writeByRename") {
150
+ await assertRegularFileIfExists(fileName);
151
+ try {
152
+ await writeVerifiedUtf8File(tmpName, content, stillPinned, { create: true, mode: job.mode });
153
+ await rename(tmpName, fileName);
154
+ await assertRegularFileIfExists(fileName);
155
+ }
156
+ catch (error) {
157
+ await rm(tmpName, { force: true }).catch(() => undefined);
158
+ throw error;
159
+ }
160
+ return { ok: true };
161
+ }
162
+ // linkIfAbsent: the hard link is what makes "create only if absent" atomic.
163
+ let linked = false;
164
+ try {
165
+ await writeVerifiedUtf8File(tmpName, content, stillPinned, { create: true, exclusive: true, mode: job.mode });
166
+ try {
167
+ await link(tmpName, fileName);
168
+ }
169
+ catch (error) {
170
+ if (error.code === "EEXIST") {
171
+ await rm(tmpName, { force: true }).catch(() => undefined);
172
+ return { ok: true, created: false };
173
+ }
174
+ throw error;
175
+ }
176
+ linked = true;
177
+ await rm(tmpName, { force: true });
178
+ await assertRegularFileIfExists(fileName);
179
+ }
180
+ catch (error) {
181
+ if (!linked)
182
+ await rm(tmpName, { force: true }).catch(() => undefined);
183
+ throw error;
184
+ }
185
+ return { ok: true, created: true };
186
+ }
187
+ async function readAllStdin() {
188
+ const chunks = [];
189
+ for await (const chunk of process.stdin)
190
+ chunks.push(Buffer.from(chunk));
191
+ return Buffer.concat(chunks).toString("utf8");
192
+ }
193
+ async function main() {
194
+ let outcome;
195
+ try {
196
+ outcome = await runAnchoredJob(JSON.parse(await readAllStdin()));
197
+ }
198
+ catch (error) {
199
+ const maybe = error;
200
+ outcome = { ok: false, error: maybe.message ?? String(error), code: maybe.code };
201
+ }
202
+ process.stdout.write(`${JSON.stringify(outcome)}\n`);
203
+ if (!outcome.ok)
204
+ process.exitCode = 1;
205
+ }
206
+ // Only run when this file is the entry point, so the exported pieces stay
207
+ // importable from tests without the process trying to read stdin.
208
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
209
+ await main();
210
+ }
package/dist/store.js CHANGED
@@ -1,8 +1,12 @@
1
1
  import { createHash, createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
2
+ import { createRequire } from "node:module";
2
3
  import { registerBridgeRoot } from "./registry.js";
3
- import { constants, existsSync } from "node:fs";
4
+ import { closeSync, constants, existsSync, openSync, readdirSync } from "node:fs";
5
+ import { tmpdir } from "node:os";
4
6
  import { link, lstat, mkdir, open, readdir, realpath, rename, rm, stat } from "node:fs/promises";
5
7
  import path from "node:path";
8
+ import { spawn } from "node:child_process";
9
+ import { fileURLToPath, pathToFileURL } from "node:url";
6
10
  import { assertRepoRelativePath } from "./repo.js";
7
11
  import { readVerifiedUtf8File, writeVerifiedUtf8File } from "./safe-file.js";
8
12
  import { makeBridgeId, nowIso, ReceiptSchema, ResultSchema, SCHEMA_VERSION, SessionSchema, TaskSchema } from "./schema.js";
@@ -656,6 +660,16 @@ export class BridgeStore {
656
660
  const artifactPath = this.resolveArtifactPath(relativePath);
657
661
  const parentPath = path.dirname(artifactPath);
658
662
  await this.assertArtifactParentDirectory(parentPath);
663
+ if (!hasStableDirectoryFdPaths()) {
664
+ const segments = path.relative(this.bridgeDir, parentPath).split(path.sep).filter((part) => part.length > 0);
665
+ await runAnchoredWrite(this.bridgeDir, segments, {
666
+ op: "deleteIfPresent",
667
+ fileName: path.basename(artifactPath),
668
+ mode: BRIDGE_FILE_MODE
669
+ });
670
+ await this.assertArtifactParentDirectory(parentPath);
671
+ return;
672
+ }
659
673
  const parentHandle = await openNoFollowDirectory(parentPath, "Artifact directory");
660
674
  try {
661
675
  const targetPath = path.join(directoryFdPath(parentHandle.fd), path.basename(artifactPath));
@@ -975,13 +989,31 @@ export class BridgeStore {
975
989
  await this.writeTextByStableStorageRename(kind, filePath, content);
976
990
  return;
977
991
  }
978
- throw new Error("Bridge record writes require stable directory file descriptor paths on this platform.");
992
+ await this.runAnchoredRecordJob(kind, filePath, { op: "writeByRename", content });
979
993
  }
980
994
  async writeTextByCreateExclusive(kind, filePath, content) {
981
995
  if (hasStableDirectoryFdPaths()) {
982
996
  return await this.writeTextByStableStorageLinkIfAbsent(kind, filePath, content);
983
997
  }
984
- throw new Error("Bridge record writes require stable directory file descriptor paths on this platform.");
998
+ const outcome = await this.runAnchoredRecordJob(kind, filePath, { op: "linkIfAbsent", content });
999
+ return outcome.created !== false;
1000
+ }
1001
+ /**
1002
+ * Run one record operation in the anchored child, with the same storage
1003
+ * directory checks the in-process path makes on either side of it.
1004
+ */
1005
+ async runAnchoredRecordJob(kind, filePath, job) {
1006
+ if (path.dirname(filePath) !== this.dir(kind)) {
1007
+ throw new Error(`Bridge record path must stay under .bridge/${kind}`);
1008
+ }
1009
+ await this.assertStorageDirIsRealDirectory(kind);
1010
+ const outcome = await runAnchoredWrite(this.bridgeDir, [kind], {
1011
+ ...job,
1012
+ fileName: path.basename(filePath),
1013
+ mode: BRIDGE_FILE_MODE
1014
+ });
1015
+ await this.assertStorageDirIsRealDirectory(kind);
1016
+ return outcome.ok ? outcome : {};
985
1017
  }
986
1018
  async deleteRecordIfPresent(kind, id) {
987
1019
  const filePath = this.pathFor(kind, id);
@@ -990,6 +1022,10 @@ export class BridgeStore {
990
1022
  if (path.dirname(filePath) !== expectedDir) {
991
1023
  throw new Error(`Bridge record path must stay under .bridge/${kind}`);
992
1024
  }
1025
+ if (!hasStableDirectoryFdPaths()) {
1026
+ await this.runAnchoredRecordJob(kind, filePath, { op: "deleteIfPresent" });
1027
+ return;
1028
+ }
993
1029
  const bridgeHandle = await openNoFollowDirectory(this.bridgeDir, "Bridge directory");
994
1030
  try {
995
1031
  const storageHandle = await openNoFollowDirectory(path.join(directoryFdPath(bridgeHandle.fd), kind), `Bridge storage directory .bridge/${kind}`);
@@ -1115,6 +1151,10 @@ export class BridgeStore {
1115
1151
  if (path.dirname(filePath) !== expectedDir) {
1116
1152
  throw new Error(`Bridge record path must stay under .bridge/${kind}`);
1117
1153
  }
1154
+ if (!hasStableDirectoryFdPaths()) {
1155
+ await this.runAnchoredRecordJob(kind, filePath, { op: "cleanupTempHardLinks" });
1156
+ return;
1157
+ }
1118
1158
  const bridgeHandle = await openNoFollowDirectory(this.bridgeDir, "Bridge directory");
1119
1159
  try {
1120
1160
  const storageHandle = await openNoFollowDirectory(path.join(directoryFdPath(bridgeHandle.fd), kind), `Bridge storage directory .bridge/${kind}`);
@@ -1552,14 +1592,112 @@ function directoryFdPath(fd) {
1552
1592
  }
1553
1593
  return `${base}/${fd}`;
1554
1594
  }
1595
+ // Whether a base can be USED, not merely whether it exists. macOS has /dev/fd,
1596
+ // so an existence check accepted it, and every record write then failed with
1597
+ // ENOENT on a path like /dev/fd/12/receipts - measured there, /dev/fd exists and
1598
+ // is not traversable, which took down the whole bridge write surface while the
1599
+ // same checks pass on Linux. Existence was never the property being relied on.
1600
+ let directoryFdBaseProbe;
1601
+ function probeDirectoryFdBase() {
1602
+ for (const base of ["/proc/self/fd", "/dev/fd"]) {
1603
+ if (!existsSync(base))
1604
+ continue;
1605
+ let fd;
1606
+ try {
1607
+ fd = openSync(tmpdir(), constants.O_RDONLY | (constants.O_DIRECTORY ?? 0));
1608
+ // Reading the directory THROUGH the rendered path is exactly what the
1609
+ // writes do, so that is what gets tested.
1610
+ readdirSync(`${base}/${fd}`);
1611
+ return base;
1612
+ }
1613
+ catch {
1614
+ // this base cannot be walked here; try the next
1615
+ }
1616
+ finally {
1617
+ if (fd !== undefined) {
1618
+ try {
1619
+ closeSync(fd);
1620
+ }
1621
+ catch {
1622
+ // best effort
1623
+ }
1624
+ }
1625
+ }
1626
+ }
1627
+ return undefined;
1628
+ }
1629
+ /** Exported so a test can hold the probe to what the running platform can do. */
1630
+ export function directoryFdPathsUsable() {
1631
+ return hasStableDirectoryFdPaths();
1632
+ }
1633
+ // Running the write in a child process whose cwd the kernel has pinned is how
1634
+ // platforms without a traversable /proc/self/fd keep the guarantee the fd path
1635
+ // gives everywhere else. See src/store-writer.ts for what the child checks.
1636
+ function anchoredWriterCommand() {
1637
+ const here = path.dirname(fileURLToPath(import.meta.url));
1638
+ const built = path.join(here, "store-writer.js");
1639
+ if (existsSync(built))
1640
+ return { command: process.execPath, args: [built] };
1641
+ const source = path.join(here, "store-writer.ts");
1642
+ if (existsSync(source)) {
1643
+ // Only reachable when running from TypeScript sources. The child's cwd is
1644
+ // the bridge directory, so tsx has to be named by absolute URL or Node
1645
+ // looks for it next to the records.
1646
+ const tsx = pathToFileURL(createRequire(import.meta.url).resolve("tsx/esm")).href;
1647
+ return { command: process.execPath, args: ["--import", tsx, source] };
1648
+ }
1649
+ throw new Error("Bridge record writes need the anchored writer, which is missing from this installation.");
1650
+ }
1651
+ async function runAnchoredWrite(bridgeDir, segments, job) {
1652
+ const bridgeHandle = await openNoFollowDirectory(bridgeDir, "Bridge directory");
1653
+ let anchor;
1654
+ try {
1655
+ const stat = await bridgeHandle.stat({ bigint: true });
1656
+ anchor = { dev: stat.dev.toString(), ino: stat.ino.toString() };
1657
+ }
1658
+ finally {
1659
+ await bridgeHandle.close();
1660
+ }
1661
+ const { command, args } = anchoredWriterCommand();
1662
+ // cwd is resolved by path here, which is exactly the lookup an attacker could
1663
+ // redirect - so the child refuses to proceed unless the directory it landed in
1664
+ // is the inode just measured through the no-follow handle.
1665
+ const child = spawn(command, args, { cwd: bridgeDir, stdio: ["pipe", "pipe", "pipe"] });
1666
+ const stdout = [];
1667
+ const stderr = [];
1668
+ child.stdout.on("data", (chunk) => stdout.push(chunk));
1669
+ child.stderr.on("data", (chunk) => stderr.push(chunk));
1670
+ const finished = new Promise((resolve, reject) => {
1671
+ child.once("error", reject);
1672
+ child.once("close", () => resolve());
1673
+ });
1674
+ child.stdin.end(JSON.stringify({ ...job, anchor, segments }));
1675
+ await finished;
1676
+ const text = Buffer.concat(stdout).toString("utf8").trim();
1677
+ let outcome;
1678
+ try {
1679
+ outcome = text.length > 0 ? JSON.parse(text) : undefined;
1680
+ }
1681
+ catch {
1682
+ outcome = undefined;
1683
+ }
1684
+ if (!outcome) {
1685
+ const detail = Buffer.concat(stderr).toString("utf8").trim() || text || "no output";
1686
+ throw new Error(`Bridge record write helper failed: ${detail}`);
1687
+ }
1688
+ if (!outcome.ok) {
1689
+ const error = new Error(outcome.error);
1690
+ if (outcome.code)
1691
+ error.code = outcome.code;
1692
+ throw error;
1693
+ }
1694
+ return outcome;
1695
+ }
1555
1696
  function directoryFdPathBase() {
1556
1697
  if (storeTestHooks.disableDirectoryFdPaths)
1557
1698
  return undefined;
1558
- if (existsSync("/proc/self/fd"))
1559
- return "/proc/self/fd";
1560
- if (existsSync("/dev/fd"))
1561
- return "/dev/fd";
1562
- return undefined;
1699
+ directoryFdBaseProbe ??= { base: probeDirectoryFdBase() };
1700
+ return directoryFdBaseProbe.base;
1563
1701
  }
1564
1702
  function assertBridgeRecordId(kind, id) {
1565
1703
  if (!isBridgeRecordId(kind, id)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@youdie006/prodex",
3
- "version": "0.37.1",
3
+ "version": "0.38.1",
4
4
  "description": "Local receipt bus for coordinating Codex execution with ChatGPT Pro/Projects consultation.",
5
5
  "author": "youdie006",
6
6
  "license": "MIT",