@fml-inc/fml 0.8.12 → 0.8.13-rc.2

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.
@@ -10,7 +10,7 @@ var __export = (target, all) => {
10
10
 
11
11
  // src/dirs.ts
12
12
  import os from "node:os";
13
- import path14 from "node:path";
13
+ import path16 from "node:path";
14
14
  function isPresent(value) {
15
15
  return value !== void 0 && value.trim().length > 0;
16
16
  }
@@ -31,7 +31,7 @@ function platformBaseDirectory(value, pathApi) {
31
31
  function resolveFmlDirectories(options = {}) {
32
32
  const platform = options.platform ?? process.platform;
33
33
  const env = options.env ?? process.env;
34
- const pathApi = platform === "win32" ? path14.win32 : path14.posix;
34
+ const pathApi = platform === "win32" ? path16.win32 : path16.posix;
35
35
  const homeDir = normalizeAbsolutePath(
36
36
  options.homeDir ?? os.homedir(),
37
37
  "home directory",
@@ -123,41 +123,41 @@ var init_dirs = __esm({
123
123
  FML_LOG_DIR = DIRECTORIES.logDir;
124
124
  FML_OBSERVER_DIR = DIRECTORIES.observerDir;
125
125
  FML_OBSERVER_PORT = observerPort();
126
- FML_OBSERVER_STATE_FILE = path14.join(
126
+ FML_OBSERVER_STATE_FILE = path16.join(
127
127
  FML_OBSERVER_DIR,
128
128
  "observer.pid.json"
129
129
  );
130
- FML_OBSERVER_LOCK_FILE = path14.join(
130
+ FML_OBSERVER_LOCK_FILE = path16.join(
131
131
  FML_OBSERVER_DIR,
132
132
  "observer.lock"
133
133
  );
134
- FML_OBSERVER_AUTH_TOKEN_FILE = path14.join(
134
+ FML_OBSERVER_AUTH_TOKEN_FILE = path16.join(
135
135
  FML_OBSERVER_DIR,
136
136
  "auth-token"
137
137
  );
138
- FML_OBSERVER_ENDPOINT_FILE = path14.join(
138
+ FML_OBSERVER_ENDPOINT_FILE = path16.join(
139
139
  FML_OBSERVER_DIR,
140
140
  "endpoint.json"
141
141
  );
142
- FML_OBSERVER_LOG_FILE = path14.join(FML_LOG_DIR, "observer.log");
143
- FML_UPDATE_LOG_FILE = path14.join(FML_LOG_DIR, "update.log");
144
- FML_INSTALLATION_ID_FILE = path14.join(
142
+ FML_OBSERVER_LOG_FILE = path16.join(FML_LOG_DIR, "observer.log");
143
+ FML_UPDATE_LOG_FILE = path16.join(FML_LOG_DIR, "update.log");
144
+ FML_INSTALLATION_ID_FILE = path16.join(
145
145
  FML_STATE_DIR,
146
146
  "installation_id"
147
147
  );
148
- FML_PANOPTICON_DB_FILE = path14.join(FML_STATE_DIR, "panopticon.db");
148
+ FML_PANOPTICON_DB_FILE = path16.join(FML_STATE_DIR, "panopticon.db");
149
149
  FML_PANOPTICON_DB_OBSERVER_STATE_FILE = `${FML_PANOPTICON_DB_FILE}.observer.pid.json`;
150
150
  FML_PANOPTICON_DB_OBSERVER_LOCK_FILE = `${FML_PANOPTICON_DB_FILE}.observer.lock`;
151
- FML_LEGACY_OBSERVER_DB_FILE = path14.join(
151
+ FML_LEGACY_OBSERVER_DB_FILE = path16.join(
152
152
  FML_OBSERVER_DIR,
153
153
  "observer.db"
154
154
  );
155
- FML_HOOK_INBOX_DIR = path14.join(FML_OBSERVER_DIR, "hook-inbox");
156
- FML_HOOK_CONTEXT_STATE_DIR = path14.join(
155
+ FML_HOOK_INBOX_DIR = path16.join(FML_OBSERVER_DIR, "hook-inbox");
156
+ FML_HOOK_CONTEXT_STATE_DIR = path16.join(
157
157
  FML_OBSERVER_DIR,
158
158
  "hook-context-state"
159
159
  );
160
- FML_HOOK_CAPTURE_DISABLED_FILE = path14.join(
160
+ FML_HOOK_CAPTURE_DISABLED_FILE = path16.join(
161
161
  FML_OBSERVER_DIR,
162
162
  "hook-capture.disabled"
163
163
  );
@@ -165,8 +165,8 @@ var init_dirs = __esm({
165
165
  });
166
166
 
167
167
  // src/config.ts
168
- import fs6 from "node:fs";
169
- import path15 from "node:path";
168
+ import fs7 from "node:fs";
169
+ import path17 from "node:path";
170
170
  function isHostedBackendUrl(url2) {
171
171
  let normalized;
172
172
  try {
@@ -195,7 +195,7 @@ function normalizeAppUrl(value) {
195
195
  function readEnvConfig() {
196
196
  try {
197
197
  const parsed = JSON.parse(
198
- fs6.readFileSync(ENV_CONFIG_PATH, "utf8")
198
+ fs7.readFileSync(ENV_CONFIG_PATH, "utf8")
199
199
  );
200
200
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
201
201
  throw new Error("invalid environment config");
@@ -256,7 +256,7 @@ function getActiveEnv() {
256
256
  return { name, convexUrl, appUrl };
257
257
  }
258
258
  function authStorePathFor(envName) {
259
- return path15.join(FML_DATA_DIR, `auth.${envName}.json`);
259
+ return path17.join(FML_DATA_DIR, `auth.${envName}.json`);
260
260
  }
261
261
  function getSiteUrlForConvexUrl(convexUrl) {
262
262
  const explicit = process.env.CONVEX_SITE_URL;
@@ -274,7 +274,7 @@ var init_config = __esm({
274
274
  LEGACY_HOSTED_BACKEND_URLS = [
275
275
  "https://trustworthy-chihuahua-382.convex.site"
276
276
  ];
277
- ENV_CONFIG_PATH = path15.join(FML_DATA_DIR, "env.json");
277
+ ENV_CONFIG_PATH = path17.join(FML_DATA_DIR, "env.json");
278
278
  ENV_NAME_RE = /^[A-Za-z0-9_-]+$/;
279
279
  CONVEX_URL = (process.env.FML_CONVEX_URL ?? getActiveEnv().convexUrl ?? DEFAULT_PROD_URL).replace(/\/$/, "");
280
280
  WORKOS_API_URL = "https://api.workos.com";
@@ -307,9 +307,9 @@ __export(telemetry_config_exports, {
307
307
  refreshCliTelemetryConfig: () => refreshCliTelemetryConfig,
308
308
  resolveCliSentryConfig: () => resolveCliSentryConfig
309
309
  });
310
- import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
311
- import fs7 from "node:fs";
312
- import path16 from "node:path";
310
+ import { createHash as createHash4, randomUUID as randomUUID3 } from "node:crypto";
311
+ import fs8 from "node:fs";
312
+ import path18 from "node:path";
313
313
  function parseSentryConfig(value) {
314
314
  if (value === null) return null;
315
315
  if (!value || typeof value !== "object")
@@ -330,16 +330,16 @@ function parseSentryConfig(value) {
330
330
  }
331
331
  function cachePathFor(siteUrl) {
332
332
  const key = isHostedBackendUrl(siteUrl) ? DEFAULT_SYNC_URL : siteUrl;
333
- return path16.join(
333
+ return path18.join(
334
334
  FML_DATA_DIR,
335
335
  "telemetry-config",
336
- `${createHash3("sha256").update(key).digest("hex")}.json`
336
+ `${createHash4("sha256").update(key).digest("hex")}.json`
337
337
  );
338
338
  }
339
339
  function readCache(siteUrl) {
340
340
  try {
341
341
  const value = JSON.parse(
342
- fs7.readFileSync(cachePathFor(siteUrl), "utf8")
342
+ fs8.readFileSync(cachePathFor(siteUrl), "utf8")
343
343
  );
344
344
  if (!value || typeof value !== "object") return void 0;
345
345
  const entry = value;
@@ -356,15 +356,15 @@ function readCache(siteUrl) {
356
356
  }
357
357
  function writeCache(entry) {
358
358
  const cachePath = cachePathFor(entry.siteUrl);
359
- const temporaryPath = `${cachePath}.${randomUUID2()}.tmp`;
359
+ const temporaryPath = `${cachePath}.${randomUUID3()}.tmp`;
360
360
  try {
361
- fs7.mkdirSync(path16.dirname(cachePath), { recursive: true, mode: 448 });
362
- fs7.writeFileSync(temporaryPath, JSON.stringify(entry), { mode: 384 });
363
- fs7.renameSync(temporaryPath, cachePath);
361
+ fs8.mkdirSync(path18.dirname(cachePath), { recursive: true, mode: 448 });
362
+ fs8.writeFileSync(temporaryPath, JSON.stringify(entry), { mode: 384 });
363
+ fs8.renameSync(temporaryPath, cachePath);
364
364
  } catch {
365
365
  } finally {
366
366
  try {
367
- fs7.rmSync(temporaryPath, { force: true });
367
+ fs8.rmSync(temporaryPath, { force: true });
368
368
  } catch {
369
369
  }
370
370
  }
@@ -450,10 +450,10 @@ var init_telemetry_config = __esm({
450
450
  });
451
451
 
452
452
  // src/auth/token-store.ts
453
- import { createHash as createHash4, randomUUID as randomUUID3 } from "node:crypto";
454
- import { execFileSync } from "node:child_process";
455
- import fs8 from "node:fs";
456
- import path17 from "node:path";
453
+ import { createHash as createHash5, randomUUID as randomUUID4 } from "node:crypto";
454
+ import { execFileSync as execFileSync2 } from "node:child_process";
455
+ import fs9 from "node:fs";
456
+ import path19 from "node:path";
457
457
  function getCurrentProcessIdentity() {
458
458
  let cache = processGlobals[PROCESS_IDENTITY_KEY];
459
459
  if (!cache) {
@@ -473,36 +473,36 @@ function resolveEnvName(envName) {
473
473
  return envName ?? getActiveEnv().name;
474
474
  }
475
475
  function ensureDir(envName) {
476
- const dir = path17.dirname(storePathFor(envName));
477
- fs8.mkdirSync(dir, { recursive: true, mode: 448 });
476
+ const dir = path19.dirname(storePathFor(envName));
477
+ fs9.mkdirSync(dir, { recursive: true, mode: 448 });
478
478
  try {
479
- fs8.chmodSync(dir, 448);
479
+ fs9.chmodSync(dir, 448);
480
480
  } catch {
481
481
  }
482
482
  }
483
483
  function writeSecretFile(filePath, data) {
484
- const temporaryFile = `${filePath}.${process.pid}.${randomUUID3()}.tmp`;
484
+ const temporaryFile = `${filePath}.${process.pid}.${randomUUID4()}.tmp`;
485
485
  let descriptor = null;
486
486
  try {
487
- descriptor = fs8.openSync(temporaryFile, "wx", 384);
488
- fs8.writeFileSync(descriptor, data, { encoding: "utf8" });
489
- fs8.fsyncSync(descriptor);
490
- fs8.closeSync(descriptor);
487
+ descriptor = fs9.openSync(temporaryFile, "wx", 384);
488
+ fs9.writeFileSync(descriptor, data, { encoding: "utf8" });
489
+ fs9.fsyncSync(descriptor);
490
+ fs9.closeSync(descriptor);
491
491
  descriptor = null;
492
- fs8.renameSync(temporaryFile, filePath);
492
+ fs9.renameSync(temporaryFile, filePath);
493
493
  try {
494
- fs8.chmodSync(filePath, 384);
494
+ fs9.chmodSync(filePath, 384);
495
495
  } catch {
496
496
  }
497
497
  } finally {
498
498
  if (descriptor !== null) {
499
499
  try {
500
- fs8.closeSync(descriptor);
500
+ fs9.closeSync(descriptor);
501
501
  } catch {
502
502
  }
503
503
  }
504
504
  try {
505
- fs8.unlinkSync(temporaryFile);
505
+ fs9.unlinkSync(temporaryFile);
506
506
  } catch {
507
507
  }
508
508
  }
@@ -510,7 +510,7 @@ function writeSecretFile(filePath, data) {
510
510
  function readTokens(envName) {
511
511
  let raw;
512
512
  try {
513
- raw = fs8.readFileSync(storePathFor(envName), "utf-8");
513
+ raw = fs9.readFileSync(storePathFor(envName), "utf-8");
514
514
  } catch {
515
515
  return null;
516
516
  }
@@ -534,7 +534,7 @@ function parseStoredAuthShape(parsed) {
534
534
  function readTokensForReconciliation(envName) {
535
535
  let raw;
536
536
  try {
537
- raw = fs8.readFileSync(storePathFor(envName), "utf8");
537
+ raw = fs9.readFileSync(storePathFor(envName), "utf8");
538
538
  } catch (error62) {
539
539
  if (error62.code === "ENOENT") return null;
540
540
  throw error62;
@@ -561,7 +561,7 @@ function writeTokensUnlocked(auth, envName) {
561
561
  writeSecretFile(storePathFor(envName), JSON.stringify(auth, null, 2));
562
562
  }
563
563
  function credentialHash(value) {
564
- return createHash4("sha256").update(value).digest("hex");
564
+ return createHash5("sha256").update(value).digest("hex");
565
565
  }
566
566
  function describeError(error62) {
567
567
  return error62 instanceof Error ? error62.message : String(error62);
@@ -639,7 +639,7 @@ function refreshLockPath(envName) {
639
639
  return `${storePathFor(envName)}.refresh.lock`;
640
640
  }
641
641
  function pendingOAuthRefreshPrefix(envName) {
642
- return `${path17.basename(storePathFor(envName))}.refresh-result.`;
642
+ return `${path19.basename(storePathFor(envName))}.refresh-result.`;
643
643
  }
644
644
  function parsePendingOAuthRefresh(raw) {
645
645
  try {
@@ -655,7 +655,7 @@ function parsePendingOAuthRefresh(raw) {
655
655
  }
656
656
  function stagePendingOAuthRefresh(previousRefreshToken, refreshed, envName) {
657
657
  ensureDir(envName);
658
- const resultPath = `${storePathFor(envName)}.refresh-result.${Date.now()}.${randomUUID3()}.json`;
658
+ const resultPath = `${storePathFor(envName)}.refresh-result.${Date.now()}.${randomUUID4()}.json`;
659
659
  const pending = {
660
660
  version: 1,
661
661
  previousRefreshTokenHash: credentialHash(previousRefreshToken),
@@ -668,7 +668,7 @@ function stagePendingOAuthRefresh(previousRefreshToken, refreshed, envName) {
668
668
  }
669
669
  function removePendingOAuthRefresh(resultPath) {
670
670
  try {
671
- fs8.unlinkSync(resultPath);
671
+ fs9.unlinkSync(resultPath);
672
672
  } catch (error62) {
673
673
  if (error62.code !== "ENOENT") {
674
674
  console.error(
@@ -679,11 +679,11 @@ function removePendingOAuthRefresh(resultPath) {
679
679
  }
680
680
  function reconcilePendingOAuthRefreshes(envName, unreadableStorePolicy = "preserve-staged") {
681
681
  const storePath = storePathFor(envName);
682
- const directory = path17.dirname(storePath);
682
+ const directory = path19.dirname(storePath);
683
683
  const prefix = pendingOAuthRefreshPrefix(envName);
684
684
  let resultPaths;
685
685
  try {
686
- resultPaths = fs8.readdirSync(directory).filter((entry) => entry.startsWith(prefix) && entry.endsWith(".json")).map((entry) => path17.join(directory, entry));
686
+ resultPaths = fs9.readdirSync(directory).filter((entry) => entry.startsWith(prefix) && entry.endsWith(".json")).map((entry) => path19.join(directory, entry));
687
687
  } catch (error62) {
688
688
  if (error62.code === "ENOENT") return;
689
689
  throw error62;
@@ -691,7 +691,7 @@ function reconcilePendingOAuthRefreshes(envName, unreadableStorePolicy = "preser
691
691
  const pendingResults = resultPaths.map((resultPath) => {
692
692
  let pending = null;
693
693
  try {
694
- pending = parsePendingOAuthRefresh(fs8.readFileSync(resultPath, "utf8"));
694
+ pending = parsePendingOAuthRefresh(fs9.readFileSync(resultPath, "utf8"));
695
695
  } catch (error62) {
696
696
  if (error62.code !== "ENOENT") throw error62;
697
697
  }
@@ -746,13 +746,13 @@ function parseRefreshLockOwner(raw) {
746
746
  function getProcessIdentity(pid) {
747
747
  try {
748
748
  if (process.platform === "linux") {
749
- const stat = fs8.readFileSync(`/proc/${pid}/stat`, "utf8");
749
+ const stat = fs9.readFileSync(`/proc/${pid}/stat`, "utf8");
750
750
  const fieldsAfterCommand = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
751
751
  const startTimeTicks = fieldsAfterCommand[19];
752
752
  return startTimeTicks ? `linux:${startTimeTicks}` : null;
753
753
  }
754
754
  if (process.platform === "darwin") {
755
- const startedAt = execFileSync(
755
+ const startedAt = execFileSync2(
756
756
  "ps",
757
757
  ["-p", String(pid), "-o", "lstart="],
758
758
  {
@@ -764,7 +764,7 @@ function getProcessIdentity(pid) {
764
764
  return startedAt ? `darwin:${startedAt}` : null;
765
765
  }
766
766
  if (process.platform === "win32") {
767
- const startedAt = execFileSync(
767
+ const startedAt = execFileSync2(
768
768
  "powershell.exe",
769
769
  [
770
770
  "-NoProfile",
@@ -798,7 +798,7 @@ function parseRecoveryMarkerOwner(raw) {
798
798
  function snapshotLeaseFile(filePath) {
799
799
  let stat;
800
800
  try {
801
- stat = fs8.lstatSync(filePath);
801
+ stat = fs9.lstatSync(filePath);
802
802
  } catch (error62) {
803
803
  if (error62.code === "ENOENT") return null;
804
804
  throw error62;
@@ -807,7 +807,7 @@ function snapshotLeaseFile(filePath) {
807
807
  try {
808
808
  return {
809
809
  stat,
810
- raw: fs8.readFileSync(path17.join(filePath, "owner.json"), "utf8")
810
+ raw: fs9.readFileSync(path19.join(filePath, "owner.json"), "utf8")
811
811
  };
812
812
  } catch (error62) {
813
813
  if (error62.code === "ENOENT") {
@@ -817,7 +817,7 @@ function snapshotLeaseFile(filePath) {
817
817
  }
818
818
  }
819
819
  try {
820
- return { stat, raw: fs8.readFileSync(filePath, "utf8") };
820
+ return { stat, raw: fs9.readFileSync(filePath, "utf8") };
821
821
  } catch (error62) {
822
822
  if (error62.code === "ENOENT") return null;
823
823
  throw error62;
@@ -829,21 +829,21 @@ function snapshotsMatch(observed, current) {
829
829
  function restoreMovedLease(movedPath, originalPath) {
830
830
  let movedStat;
831
831
  try {
832
- movedStat = fs8.lstatSync(movedPath);
832
+ movedStat = fs9.lstatSync(movedPath);
833
833
  } catch (error62) {
834
834
  if (error62.code === "ENOENT") return;
835
835
  throw error62;
836
836
  }
837
837
  if (!movedStat.isFile()) return;
838
838
  try {
839
- fs8.linkSync(movedPath, originalPath);
839
+ fs9.linkSync(movedPath, originalPath);
840
840
  } catch (error62) {
841
841
  const code = error62.code;
842
842
  if (code === "EEXIST" || code === "ENOENT") return;
843
843
  throw error62;
844
844
  }
845
845
  try {
846
- fs8.unlinkSync(movedPath);
846
+ fs9.unlinkSync(movedPath);
847
847
  } catch (error62) {
848
848
  if (error62.code !== "ENOENT") throw error62;
849
849
  }
@@ -857,27 +857,27 @@ function pathMatchesClaim(filePath, claimedStat, ownerId, parseOwner) {
857
857
  function markClaimReleased(filePath, claimedStat, owner, parseOwner) {
858
858
  let descriptor;
859
859
  try {
860
- descriptor = fs8.openSync(filePath, "r+");
860
+ descriptor = fs9.openSync(filePath, "r+");
861
861
  } catch (error62) {
862
862
  if (error62.code === "ENOENT") return false;
863
863
  throw error62;
864
864
  }
865
865
  try {
866
- const currentStat = fs8.fstatSync(descriptor);
866
+ const currentStat = fs9.fstatSync(descriptor);
867
867
  if (currentStat.dev !== claimedStat.dev || currentStat.ino !== claimedStat.ino) {
868
868
  return false;
869
869
  }
870
- const currentOwner = parseOwner(fs8.readFileSync(descriptor, "utf8"));
870
+ const currentOwner = parseOwner(fs9.readFileSync(descriptor, "utf8"));
871
871
  if (currentOwner?.ownerId !== owner.ownerId) return false;
872
872
  const serialized = Buffer.from(
873
873
  `${JSON.stringify({ ...owner, releasedAtMs: Date.now() })}
874
874
  `,
875
875
  "utf8"
876
876
  );
877
- fs8.ftruncateSync(descriptor, 0);
877
+ fs9.ftruncateSync(descriptor, 0);
878
878
  let offset = 0;
879
879
  while (offset < serialized.length) {
880
- offset += fs8.writeSync(
880
+ offset += fs9.writeSync(
881
881
  descriptor,
882
882
  serialized,
883
883
  offset,
@@ -885,19 +885,19 @@ function markClaimReleased(filePath, claimedStat, owner, parseOwner) {
885
885
  offset
886
886
  );
887
887
  }
888
- fs8.fsyncSync(descriptor);
888
+ fs9.fsyncSync(descriptor);
889
889
  return true;
890
890
  } finally {
891
- fs8.closeSync(descriptor);
891
+ fs9.closeSync(descriptor);
892
892
  }
893
893
  }
894
894
  function releaseOwnedLease(filePath, claimedStat, owner, parseOwner, description) {
895
- const releasedPath = `${filePath}.released.${randomUUID3()}`;
895
+ const releasedPath = `${filePath}.released.${randomUUID4()}`;
896
896
  let moved = false;
897
897
  try {
898
898
  if (!markClaimReleased(filePath, claimedStat, owner, parseOwner)) return;
899
899
  try {
900
- fs8.renameSync(filePath, releasedPath);
900
+ fs9.renameSync(filePath, releasedPath);
901
901
  moved = true;
902
902
  } catch (error62) {
903
903
  if (error62.code === "ENOENT") return;
@@ -910,7 +910,7 @@ function releaseOwnedLease(filePath, claimedStat, owner, parseOwner, description
910
910
  moved = false;
911
911
  return;
912
912
  }
913
- fs8.rmSync(releasedPath, {
913
+ fs9.rmSync(releasedPath, {
914
914
  recursive: released.stat.isDirectory(),
915
915
  force: true
916
916
  });
@@ -984,9 +984,9 @@ function ownerPidWasReused(owner, lockMtimeMs) {
984
984
  return currentIdentity !== null && currentIdentity !== owner.processIdentity;
985
985
  }
986
986
  function writeClaimToDescriptor(descriptor, serializedOwner) {
987
- const claimedStat = fs8.fstatSync(descriptor);
988
- fs8.writeFileSync(descriptor, serializedOwner, { encoding: "utf8" });
989
- fs8.fsyncSync(descriptor);
987
+ const claimedStat = fs9.fstatSync(descriptor);
988
+ fs9.writeFileSync(descriptor, serializedOwner, { encoding: "utf8" });
989
+ fs9.fsyncSync(descriptor);
990
990
  return claimedStat;
991
991
  }
992
992
  function recoverAbandonedRecoveryMarker(recoveryPath) {
@@ -996,15 +996,15 @@ function recoverAbandonedRecoveryMarker(recoveryPath) {
996
996
  const ownerAlive = owner ? isProcessAlive(owner.pid) : false;
997
997
  const reclaimable = owner ? owner.releasedAtMs !== void 0 || !ownerAlive || ownerAlive && ownerPidWasReused(owner, observed.stat.mtimeMs) : Date.now() - observed.stat.mtimeMs > OAUTH_REFRESH_RECOVERY_STALE_MS;
998
998
  if (!reclaimable) return;
999
- const abandonedPath = `${recoveryPath}.stale.${randomUUID3()}`;
999
+ const abandonedPath = `${recoveryPath}.stale.${randomUUID4()}`;
1000
1000
  try {
1001
- fs8.renameSync(recoveryPath, abandonedPath);
1001
+ fs9.renameSync(recoveryPath, abandonedPath);
1002
1002
  const moved = snapshotLeaseFile(abandonedPath);
1003
1003
  if (!moved || !snapshotsMatch(observed, moved)) {
1004
1004
  restoreMovedLease(abandonedPath, recoveryPath);
1005
1005
  return;
1006
1006
  }
1007
- fs8.rmSync(abandonedPath, {
1007
+ fs9.rmSync(abandonedPath, {
1008
1008
  recursive: moved.stat.isDirectory(),
1009
1009
  force: true
1010
1010
  });
@@ -1013,7 +1013,7 @@ function recoverAbandonedRecoveryMarker(recoveryPath) {
1013
1013
  }
1014
1014
  }
1015
1015
  function acquireRecoveryMarker(recoveryPath) {
1016
- const ownerId = randomUUID3();
1016
+ const ownerId = randomUUID4();
1017
1017
  const owner = {
1018
1018
  version: 1,
1019
1019
  ownerId,
@@ -1024,7 +1024,7 @@ function acquireRecoveryMarker(recoveryPath) {
1024
1024
  };
1025
1025
  let descriptor;
1026
1026
  try {
1027
- descriptor = fs8.openSync(recoveryPath, "wx", 384);
1027
+ descriptor = fs9.openSync(recoveryPath, "wx", 384);
1028
1028
  } catch (error62) {
1029
1029
  if (error62.code === "EEXIST") return null;
1030
1030
  throw error62;
@@ -1037,7 +1037,7 @@ function acquireRecoveryMarker(recoveryPath) {
1037
1037
  `
1038
1038
  );
1039
1039
  } finally {
1040
- fs8.closeSync(descriptor);
1040
+ fs9.closeSync(descriptor);
1041
1041
  }
1042
1042
  const isOwned = () => pathMatchesClaim(
1043
1043
  recoveryPath,
@@ -1081,15 +1081,15 @@ function recoverStaleRefreshLock(lockPath) {
1081
1081
  }
1082
1082
  if (!reclaimable) return;
1083
1083
  if (!recovery.isOwned()) return;
1084
- const abandonedPath = `${lockPath}.stale.${randomUUID3()}`;
1084
+ const abandonedPath = `${lockPath}.stale.${randomUUID4()}`;
1085
1085
  try {
1086
- fs8.renameSync(lockPath, abandonedPath);
1086
+ fs9.renameSync(lockPath, abandonedPath);
1087
1087
  const moved = snapshotLeaseFile(abandonedPath);
1088
1088
  if (!moved || !snapshotsMatch(observed, moved)) {
1089
1089
  restoreMovedLease(abandonedPath, lockPath);
1090
1090
  return;
1091
1091
  }
1092
- fs8.rmSync(abandonedPath, {
1092
+ fs9.rmSync(abandonedPath, {
1093
1093
  recursive: moved.stat.isDirectory(),
1094
1094
  force: true
1095
1095
  });
@@ -1116,7 +1116,7 @@ async function acquireRefreshLock(envName, signal) {
1116
1116
  if (signal?.aborted) {
1117
1117
  throw signal.reason ?? new DOMException("The operation was aborted", "AbortError");
1118
1118
  }
1119
- if (fs8.existsSync(recoveryPath)) {
1119
+ if (fs9.existsSync(recoveryPath)) {
1120
1120
  try {
1121
1121
  recoverAbandonedRecoveryMarker(recoveryPath);
1122
1122
  } catch (recoveryError) {
@@ -1130,7 +1130,7 @@ async function acquireRefreshLock(envName, signal) {
1130
1130
  }
1131
1131
  let descriptor;
1132
1132
  try {
1133
- descriptor = fs8.openSync(lockPath, "wx", 384);
1133
+ descriptor = fs9.openSync(lockPath, "wx", 384);
1134
1134
  } catch (error62) {
1135
1135
  if (error62.code !== "EEXIST") {
1136
1136
  console.error(
@@ -1149,7 +1149,7 @@ async function acquireRefreshLock(envName, signal) {
1149
1149
  await sleep(OAUTH_REFRESH_LOCK_POLL_MS, signal);
1150
1150
  continue;
1151
1151
  }
1152
- const ownerId = randomUUID3();
1152
+ const ownerId = randomUUID4();
1153
1153
  const owner = {
1154
1154
  version: 1,
1155
1155
  ownerId,
@@ -1166,13 +1166,13 @@ async function acquireRefreshLock(envName, signal) {
1166
1166
  `
1167
1167
  );
1168
1168
  } catch (error62) {
1169
- fs8.closeSync(descriptor);
1169
+ fs9.closeSync(descriptor);
1170
1170
  console.error(
1171
1171
  `[fml] Auth: unable to initialize refresh lock: ${error62 instanceof Error ? error62.message : String(error62)}`
1172
1172
  );
1173
1173
  return null;
1174
1174
  }
1175
- fs8.closeSync(descriptor);
1175
+ fs9.closeSync(descriptor);
1176
1176
  const isOwned = () => pathMatchesClaim(lockPath, claimedStat, ownerId, parseRefreshLockOwner);
1177
1177
  const releaseClaim = () => releaseOwnedLease(
1178
1178
  lockPath,
@@ -1187,7 +1187,7 @@ async function acquireRefreshLock(envName, signal) {
1187
1187
  }
1188
1188
  let entryBlocked;
1189
1189
  try {
1190
- entryBlocked = fs8.existsSync(recoveryPath) || !isOwned();
1190
+ entryBlocked = fs9.existsSync(recoveryPath) || !isOwned();
1191
1191
  } catch (error62) {
1192
1192
  console.error(
1193
1193
  `[fml] Auth: unable to verify refresh lock ownership: ${describeError(error62)}`
@@ -1600,7 +1600,7 @@ var init_token_store = __esm({
1600
1600
  PROCESS_IDENTITY_KEY = /* @__PURE__ */ Symbol.for("@fml-inc/fml:auth-process-identity");
1601
1601
  processGlobals = globalThis;
1602
1602
  existingProcessInstanceId = processGlobals[PROCESS_INSTANCE_KEY];
1603
- CURRENT_PROCESS_INSTANCE_ID = typeof existingProcessInstanceId === "string" ? existingProcessInstanceId : randomUUID3();
1603
+ CURRENT_PROCESS_INSTANCE_ID = typeof existingProcessInstanceId === "string" ? existingProcessInstanceId : randomUUID4();
1604
1604
  processGlobals[PROCESS_INSTANCE_KEY] = CURRENT_PROCESS_INSTANCE_ID;
1605
1605
  CorruptStoredCredentialsError = class extends Error {
1606
1606
  };
@@ -1612,30 +1612,537 @@ var init_token_store = __esm({
1612
1612
  }
1613
1613
  });
1614
1614
 
1615
+ // ../../packages/agent-targets/src/git-state.ts
1616
+ import { execFile, execFileSync } from "node:child_process";
1617
+ import { createHash, randomUUID } from "node:crypto";
1618
+ import fs from "node:fs";
1619
+ import path from "node:path";
1620
+
1621
+ // ../../packages/shared/src/github-repository.ts
1622
+ var MAX_REPOSITORY_BYTES = 201;
1623
+ var UTF8_ENCODER = new TextEncoder();
1624
+ function utf8Bytes(value) {
1625
+ return UTF8_ENCODER.encode(value).byteLength;
1626
+ }
1627
+ function githubRepositoryPath(pathname) {
1628
+ const parts = pathname.replace(/^\/+|\/+$/g, "").split("/");
1629
+ if (parts.length !== 2) return null;
1630
+ const owner = parts[0];
1631
+ const repository = parts[1]?.replace(/\.git$/i, "");
1632
+ if (!owner || !repository) return null;
1633
+ const segmentPattern = /^[A-Za-z0-9_.-]+$/;
1634
+ if (!segmentPattern.test(owner) || !segmentPattern.test(repository)) {
1635
+ return null;
1636
+ }
1637
+ const canonical = `${owner.toLowerCase()}/${repository.toLowerCase()}`;
1638
+ if (canonical.startsWith("./") || canonical.startsWith("../") || repository === "." || repository === ".." || repository.toLowerCase().endsWith(".git")) {
1639
+ return null;
1640
+ }
1641
+ return utf8Bytes(canonical) <= MAX_REPOSITORY_BYTES ? canonical : null;
1642
+ }
1643
+ function canonicalGitHubRepository(remote) {
1644
+ const trimmed = remote.trim();
1645
+ if (!trimmed || trimmed.includes("\0")) return null;
1646
+ const scp = trimmed.match(/^git@github\.com:([^?#]+)$/i);
1647
+ if (scp?.[1]) return githubRepositoryPath(scp[1]);
1648
+ let url2;
1649
+ try {
1650
+ url2 = new URL(trimmed);
1651
+ } catch {
1652
+ return null;
1653
+ }
1654
+ if (url2.hostname.toLowerCase() !== "github.com") return null;
1655
+ if (url2.search || url2.hash || url2.password || url2.port) return null;
1656
+ if (url2.protocol === "https:" || url2.protocol === "http:") {
1657
+ if (url2.username) return null;
1658
+ } else if (url2.protocol === "ssh:") {
1659
+ if (url2.username && url2.username !== "git") return null;
1660
+ } else {
1661
+ return null;
1662
+ }
1663
+ return githubRepositoryPath(url2.pathname);
1664
+ }
1665
+
1666
+ // ../../packages/shared/src/git-state.ts
1667
+ function isRecord(value) {
1668
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1669
+ }
1670
+ function validGitText(value) {
1671
+ return typeof value === "string" && value.length > 0 && value.length <= 4096 && !new RegExp("\\p{Cc}", "u").test(value);
1672
+ }
1673
+ function parseGitState(value) {
1674
+ if (!isRecord(value)) return null;
1675
+ const {
1676
+ version: version2,
1677
+ observedAtMs,
1678
+ repository,
1679
+ worktreeRoot,
1680
+ gitDir,
1681
+ commonDir,
1682
+ headRef,
1683
+ branch,
1684
+ commitHash,
1685
+ detached,
1686
+ unborn,
1687
+ isLinkedWorktree
1688
+ } = value;
1689
+ if (version2 !== 1 || typeof observedAtMs !== "number" || !Number.isSafeInteger(observedAtMs) || observedAtMs < 0 || !(repository === null || typeof repository === "string" && repository.length <= 201 && canonicalGitHubRepository(`https://github.com/${repository}`) === repository) || !validGitText(worktreeRoot) || !validGitText(gitDir) || !validGitText(commonDir) || !(headRef === null || validGitText(headRef)) || !(branch === null || validGitText(branch)) || !(commitHash === null || typeof commitHash === "string" && /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(commitHash)) || typeof detached !== "boolean" || typeof unborn !== "boolean" || typeof isLinkedWorktree !== "boolean")
1690
+ return null;
1691
+ return {
1692
+ version: version2,
1693
+ observedAtMs,
1694
+ repository,
1695
+ worktreeRoot,
1696
+ gitDir,
1697
+ commonDir,
1698
+ headRef,
1699
+ branch,
1700
+ commitHash,
1701
+ detached,
1702
+ unborn,
1703
+ isLinkedWorktree
1704
+ };
1705
+ }
1706
+ var MAX_GIT_STATES_BYTES = 16 * 1024;
1707
+
1708
+ // ../../packages/agent-targets/src/git-state.ts
1709
+ function isRecord2(value) {
1710
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1711
+ }
1712
+ var GIT_STATE_CACHE_FILENAME = "git-state.cache.json";
1713
+ var GIT_STATE_PAYLOAD_KEY = "_fmlGitState";
1714
+ var DEFAULT_TTL_MS = 5e3;
1715
+ var DEFAULT_TIMEOUT_MS = 1e3;
1716
+ var DEFAULT_MAX_ENTRIES = 512;
1717
+ var MAX_OUTPUT_BYTES = 16 * 1024;
1718
+ var GIT_DISCOVERY_OVERRIDES = [
1719
+ "GIT_DIR",
1720
+ "GIT_COMMON_DIR",
1721
+ "GIT_WORK_TREE",
1722
+ "GIT_CEILING_DIRECTORIES",
1723
+ "GIT_DISCOVERY_ACROSS_FILESYSTEM"
1724
+ ];
1725
+ function* readState(cwd, knownHeadRef) {
1726
+ const layout = yield {
1727
+ args: [
1728
+ "rev-parse",
1729
+ "--show-toplevel",
1730
+ "--absolute-git-dir",
1731
+ "--git-common-dir",
1732
+ "--verify",
1733
+ "HEAD"
1734
+ ],
1735
+ // An unborn HEAD still emits the three validated checkout paths.
1736
+ allowedExit: 128,
1737
+ keepOutputOnAllowedExit: true
1738
+ };
1739
+ const [worktreePath, gitPath, commonPath, revision] = layout?.split("\n") ?? [];
1740
+ if (!worktreePath || !gitPath || !commonPath) return null;
1741
+ const worktreeRoot = path.resolve(cwd, worktreePath);
1742
+ const gitDir = path.resolve(cwd, gitPath);
1743
+ const commonDir = path.resolve(cwd, commonPath);
1744
+ const headRef = knownHeadRef === void 0 ? yield { args: ["symbolic-ref", "--quiet", "HEAD"], allowedExit: 1 } : knownHeadRef;
1745
+ const commitHash = revision ?? null;
1746
+ if (!headRef && !commitHash) return null;
1747
+ const remote = yield {
1748
+ args: ["remote", "get-url", "origin"],
1749
+ allowedExit: 2
1750
+ };
1751
+ return {
1752
+ version: 1,
1753
+ repository: remote ? canonicalGitHubRepository(remote) : null,
1754
+ worktreeRoot,
1755
+ gitDir,
1756
+ commonDir,
1757
+ headRef,
1758
+ branch: headRef?.startsWith("refs/heads/") ? headRef.slice(11) : null,
1759
+ commitHash: commitHash?.toLowerCase() ?? null,
1760
+ detached: headRef === null,
1761
+ unborn: headRef !== null && commitHash === null,
1762
+ isLinkedWorktree: gitDir !== commonDir
1763
+ };
1764
+ }
1765
+ function remaining(deadline) {
1766
+ const milliseconds = deadline - performance.now();
1767
+ if (milliseconds <= 0) throw new Error("Git lookup timed out");
1768
+ return Math.max(1, Math.ceil(milliseconds));
1769
+ }
1770
+ function runSync(cwd, command, deadline) {
1771
+ try {
1772
+ return execFileSync("git", ["-C", cwd, ...command.args], {
1773
+ encoding: "utf8",
1774
+ timeout: remaining(deadline),
1775
+ maxBuffer: MAX_OUTPUT_BYTES,
1776
+ stdio: ["ignore", "pipe", "ignore"],
1777
+ windowsHide: true
1778
+ }).trim() || null;
1779
+ } catch (error62) {
1780
+ if (command.allowedExit !== void 0 && error62 && typeof error62 === "object" && "status" in error62 && error62.status === command.allowedExit) {
1781
+ return command.keepOutputOnAllowedExit && "stdout" in error62 && typeof error62.stdout === "string" ? error62.stdout.trim() || null : null;
1782
+ }
1783
+ throw error62;
1784
+ }
1785
+ }
1786
+ async function runAsync(cwd, command, deadline, signal) {
1787
+ return await new Promise((resolve, reject) => {
1788
+ execFile(
1789
+ "git",
1790
+ ["-C", cwd, ...command.args],
1791
+ {
1792
+ encoding: "utf8",
1793
+ timeout: remaining(deadline),
1794
+ maxBuffer: MAX_OUTPUT_BYTES,
1795
+ windowsHide: true,
1796
+ signal
1797
+ },
1798
+ (error62, stdout) => {
1799
+ if (signal?.aborted)
1800
+ reject(signal.reason ?? new Error("Git lookup aborted"));
1801
+ else if (error62 && error62.code !== command.allowedExit) reject(error62);
1802
+ else
1803
+ resolve(
1804
+ error62 && !command.keepOutputOnAllowedExit ? null : stdout.trim() || null
1805
+ );
1806
+ }
1807
+ );
1808
+ });
1809
+ }
1810
+ function inspectCheckout(cwd) {
1811
+ if (!cwd || cwd.trim() !== cwd || new RegExp("\\p{Cc}", "u").test(cwd) || Buffer.byteLength(cwd) > 4096)
1812
+ return null;
1813
+ try {
1814
+ const directory = fs.realpathSync(cwd);
1815
+ if (!fs.statSync(directory).isDirectory()) return null;
1816
+ if (GIT_DISCOVERY_OVERRIDES.some((key) => process.env[key] !== void 0))
1817
+ return { key: directory, signature: null };
1818
+ const layout = findGitDirectory(directory);
1819
+ if (!layout) return null;
1820
+ if (process.env.GIT_CONFIG !== void 0)
1821
+ return { key: layout.worktreeRoot, signature: null };
1822
+ const local = checkoutSignature(layout);
1823
+ const environment = Object.keys(process.env).filter(
1824
+ (key) => key.startsWith("GIT_") || key === "HOME" || key === "XDG_CONFIG_HOME"
1825
+ ).sort().map((key) => [key, process.env[key]]);
1826
+ return {
1827
+ key: layout.worktreeRoot,
1828
+ headRef: local.headRef,
1829
+ signature: local.signature === null ? null : createHash("sha256").update(JSON.stringify([local.signature, environment])).digest("hex")
1830
+ };
1831
+ } catch {
1832
+ return null;
1833
+ }
1834
+ }
1835
+ function createGitStateResolver(options = {}) {
1836
+ const entries = /* @__PURE__ */ new Map();
1837
+ const pending = /* @__PURE__ */ new Map();
1838
+ const now = options.nowMs ?? Date.now;
1839
+ const maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
1840
+ const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
1841
+ const readDisk = () => {
1842
+ if (!options.cacheFile) return;
1843
+ try {
1844
+ const file2 = readRegularFile(options.cacheFile, 8 * 1024 * 1024).toString(
1845
+ "utf8"
1846
+ );
1847
+ const parsed = JSON.parse(file2);
1848
+ if (!isRecord2(parsed) || parsed.version !== 1 || !Array.isArray(parsed.entries) || parsed.entries.length > maxEntries)
1849
+ return;
1850
+ const timestamp = now();
1851
+ for (const pair of parsed.entries) {
1852
+ if (!Array.isArray(pair) || pair.length !== 2) continue;
1853
+ const cwd = pair[0];
1854
+ const entry = pair[1];
1855
+ if (typeof cwd !== "string" || !isRecord2(entry)) continue;
1856
+ const state = parseGitState(entry.state);
1857
+ if (state && typeof entry.signature === "string" && typeof entry.expiresAtMs === "number" && Number.isFinite(entry.expiresAtMs) && entry.expiresAtMs > timestamp && state.observedAtMs <= timestamp && entry.expiresAtMs <= state.observedAtMs + ttlMs) {
1858
+ entries.set(cwd, {
1859
+ signature: entry.signature,
1860
+ state,
1861
+ expiresAtMs: entry.expiresAtMs
1862
+ });
1863
+ }
1864
+ }
1865
+ trim();
1866
+ } catch {
1867
+ }
1868
+ };
1869
+ const trim = () => {
1870
+ while (entries.size > Math.max(0, maxEntries)) {
1871
+ const key = entries.keys().next().value;
1872
+ if (key === void 0) break;
1873
+ entries.delete(key);
1874
+ }
1875
+ };
1876
+ const cached2 = (cwd, signature) => {
1877
+ if (!signature || ttlMs <= 0) return void 0;
1878
+ if (!entries.has(cwd)) readDisk();
1879
+ const entry = entries.get(cwd);
1880
+ if (!entry || entry.signature !== signature || entry.expiresAtMs <= now() || entry.state.observedAtMs > now())
1881
+ return void 0;
1882
+ entries.delete(cwd);
1883
+ entries.set(cwd, entry);
1884
+ return entry.state;
1885
+ };
1886
+ const finish = (cwd, signature, value) => {
1887
+ const state = parseGitState(value && { ...value, observedAtMs: now() });
1888
+ if (!state) return null;
1889
+ const after = inspectCheckout(cwd)?.signature ?? null;
1890
+ if (signature !== after) return null;
1891
+ if (signature && maxEntries > 0 && ttlMs > 0) {
1892
+ entries.delete(cwd);
1893
+ entries.set(cwd, {
1894
+ signature,
1895
+ state,
1896
+ expiresAtMs: state.observedAtMs + ttlMs
1897
+ });
1898
+ trim();
1899
+ if (options.cacheFile) {
1900
+ const temporary = `${options.cacheFile}.${process.pid}.${randomUUID()}.tmp`;
1901
+ try {
1902
+ fs.mkdirSync(path.dirname(options.cacheFile), {
1903
+ recursive: true,
1904
+ mode: 448
1905
+ });
1906
+ fs.writeFileSync(
1907
+ temporary,
1908
+ JSON.stringify({ version: 1, entries: [...entries] }),
1909
+ { mode: 384 }
1910
+ );
1911
+ fs.renameSync(temporary, options.cacheFile);
1912
+ } catch {
1913
+ try {
1914
+ fs.rmSync(temporary, { force: true });
1915
+ } catch {
1916
+ }
1917
+ }
1918
+ }
1919
+ }
1920
+ return state;
1921
+ };
1922
+ return {
1923
+ resolve(cwd, lookup = {}) {
1924
+ lookup.signal?.throwIfAborted();
1925
+ const snapshot = inspectCheckout(cwd);
1926
+ if (!snapshot) return null;
1927
+ const { key, signature } = snapshot;
1928
+ const hit = cached2(key, signature);
1929
+ if (hit) return hit;
1930
+ try {
1931
+ const deadline = performance.now() + (lookup.timeoutMs ?? DEFAULT_TIMEOUT_MS);
1932
+ const recipe = readState(key, snapshot.headRef);
1933
+ let step = recipe.next();
1934
+ while (!step.done) {
1935
+ lookup.signal?.throwIfAborted();
1936
+ step = recipe.next(runSync(key, step.value, deadline));
1937
+ }
1938
+ return finish(key, signature, step.value);
1939
+ } catch {
1940
+ lookup.signal?.throwIfAborted();
1941
+ return null;
1942
+ }
1943
+ },
1944
+ async resolveAsync(cwd, lookup = {}) {
1945
+ lookup.signal?.throwIfAborted();
1946
+ const snapshot = inspectCheckout(cwd);
1947
+ if (!snapshot) return null;
1948
+ const { key, signature } = snapshot;
1949
+ const hit = cached2(key, signature);
1950
+ if (hit) return hit;
1951
+ const existing = !lookup.signal ? pending.get(key) : void 0;
1952
+ if (existing) return await existing;
1953
+ const work = (async () => {
1954
+ try {
1955
+ const deadline = performance.now() + (lookup.timeoutMs ?? DEFAULT_TIMEOUT_MS);
1956
+ const recipe = readState(key, snapshot.headRef);
1957
+ let step = recipe.next();
1958
+ while (!step.done)
1959
+ step = recipe.next(
1960
+ await runAsync(key, step.value, deadline, lookup.signal)
1961
+ );
1962
+ return finish(key, signature, step.value);
1963
+ } catch {
1964
+ lookup.signal?.throwIfAborted();
1965
+ return null;
1966
+ }
1967
+ })();
1968
+ if (!lookup.signal) pending.set(key, work);
1969
+ try {
1970
+ return await work;
1971
+ } finally {
1972
+ if (pending.get(key) === work) pending.delete(key);
1973
+ }
1974
+ }
1975
+ };
1976
+ }
1977
+ function readRegularFile(filePath, maxBytes) {
1978
+ const fd = fs.openSync(
1979
+ filePath,
1980
+ fs.constants.O_RDONLY | fs.constants.O_NONBLOCK
1981
+ );
1982
+ try {
1983
+ const stat = fs.fstatSync(fd);
1984
+ if (!stat.isFile() || stat.size > maxBytes)
1985
+ throw new Error("Git metadata is not a bounded regular file");
1986
+ const buffer = Buffer.alloc(stat.size + 1);
1987
+ let length = 0;
1988
+ while (length < buffer.length) {
1989
+ const bytes = fs.readSync(
1990
+ fd,
1991
+ buffer,
1992
+ length,
1993
+ buffer.length - length,
1994
+ null
1995
+ );
1996
+ if (bytes === 0) break;
1997
+ length += bytes;
1998
+ }
1999
+ if (length > stat.size) throw new Error("Git metadata grew during read");
2000
+ return buffer.subarray(0, length);
2001
+ } finally {
2002
+ fs.closeSync(fd);
2003
+ }
2004
+ }
2005
+ function fileContentIdentity(filePath) {
2006
+ try {
2007
+ const contents = readRegularFile(filePath, 4 * 1024 * 1024);
2008
+ return `sha256:${createHash("sha256").update(contents).digest("hex")}`;
2009
+ } catch (error62) {
2010
+ const code = error62 && typeof error62 === "object" && "code" in error62 ? error62.code : void 0;
2011
+ return code === "ENOENT" || code === "ENOTDIR" ? "missing" : null;
2012
+ }
2013
+ }
2014
+ function fileStatIdentity(filePath) {
2015
+ try {
2016
+ const stat = fs.statSync(filePath, { bigint: true });
2017
+ if (!stat.isFile() || stat.ino === 0n) return null;
2018
+ return [stat.dev, stat.ino, stat.size, stat.mtimeNs, stat.ctimeNs].join(
2019
+ ":"
2020
+ );
2021
+ } catch (error62) {
2022
+ const code = error62 && typeof error62 === "object" && "code" in error62 ? error62.code : void 0;
2023
+ return code === "ENOENT" || code === "ENOTDIR" ? "missing" : null;
2024
+ }
2025
+ }
2026
+ function readSmallText(filePath) {
2027
+ try {
2028
+ return readRegularFile(filePath, MAX_OUTPUT_BYTES).toString("utf8").trim();
2029
+ } catch {
2030
+ return null;
2031
+ }
2032
+ }
2033
+ function findGitDirectory(cwd) {
2034
+ let current = path.resolve(cwd);
2035
+ for (; ; ) {
2036
+ const candidate = path.join(current, ".git");
2037
+ let stat;
2038
+ try {
2039
+ stat = fs.statSync(candidate);
2040
+ } catch {
2041
+ stat = void 0;
2042
+ }
2043
+ if (stat?.isDirectory()) {
2044
+ return { worktreeRoot: current, gitDir: candidate, commonDir: candidate };
2045
+ }
2046
+ if (stat?.isFile()) {
2047
+ const pointer = readSmallText(candidate);
2048
+ const match = pointer ? /^gitdir:\s*(.+)$/u.exec(pointer) : null;
2049
+ if (!match?.[1]) return null;
2050
+ const gitDir = path.resolve(current, match[1]);
2051
+ const common = readSmallText(path.join(gitDir, "commondir"));
2052
+ return {
2053
+ worktreeRoot: current,
2054
+ gitDir,
2055
+ commonDir: common ? path.resolve(gitDir, common) : gitDir
2056
+ };
2057
+ }
2058
+ const parent = path.dirname(current);
2059
+ if (parent === current) return null;
2060
+ try {
2061
+ if (fs.statSync(current).dev !== fs.statSync(parent).dev) return null;
2062
+ } catch {
2063
+ return null;
2064
+ }
2065
+ current = parent;
2066
+ }
2067
+ }
2068
+ function checkoutSignature(layout) {
2069
+ const head = readSmallText(path.join(layout.gitDir, "HEAD"));
2070
+ const configIdentity = [
2071
+ fileStatIdentity(path.join(layout.commonDir, "config")),
2072
+ fileStatIdentity(path.join(layout.gitDir, "config.worktree"))
2073
+ ];
2074
+ if (head === null || configIdentity.some((value) => value === null))
2075
+ return { signature: null };
2076
+ const parts = [layout.gitDir, layout.commonDir, head, ...configIdentity];
2077
+ const ref = /^ref:\s*(.+)$/u.exec(head)?.[1];
2078
+ if (ref) {
2079
+ const looseRefPath = path.join(layout.commonDir, ref);
2080
+ const looseRef = readSmallText(looseRefPath);
2081
+ if (/^ref:/u.test(looseRef ?? "")) return { signature: null };
2082
+ const packedIdentity = fileStatIdentity(
2083
+ path.join(layout.commonDir, "packed-refs")
2084
+ );
2085
+ const reftableIdentity = fileContentIdentity(
2086
+ path.join(layout.commonDir, "reftable", "tables.list")
2087
+ );
2088
+ const looseIdentity = looseRef ?? fileStatIdentity(looseRefPath);
2089
+ if (looseIdentity === null || packedIdentity === null || reftableIdentity === null)
2090
+ return { signature: null };
2091
+ if (looseRef === null && looseIdentity !== "missing")
2092
+ return { signature: null };
2093
+ parts.push(looseIdentity, packedIdentity, reftableIdentity);
2094
+ return {
2095
+ // A fresh unborn checkout has no refs yet. Cache its missing-file
2096
+ // identities after Git validates the snapshot; the first ref invalidates it.
2097
+ signature: parts.join("\0"),
2098
+ ...reftableIdentity === "missing" ? { headRef: ref } : {}
2099
+ };
2100
+ }
2101
+ return {
2102
+ signature: parts.join("\0"),
2103
+ .../^(?:[a-f0-9]{40}|[a-f0-9]{64})$/i.test(head) ? { headRef: null } : {}
2104
+ };
2105
+ }
2106
+
1615
2107
  // src/pi/extension.ts
1616
- import { randomUUID as randomUUID4 } from "node:crypto";
1617
- import fs9 from "node:fs";
2108
+ import { randomUUID as randomUUID5 } from "node:crypto";
2109
+ import fs10 from "node:fs";
1618
2110
  import os2 from "node:os";
1619
- import path19 from "node:path";
2111
+ import path22 from "node:path";
2112
+
2113
+ // ../../packages/agent-targets/src/pi-agent-dir.ts
2114
+ import path2 from "node:path";
2115
+ function resolvePiAgentDir(context) {
2116
+ const pathApi = (context.platform ?? process.platform) === "win32" ? path2.win32 : path2.posix;
2117
+ const configured = context.env.PI_CODING_AGENT_DIR?.trim();
2118
+ if (!configured) {
2119
+ return pathApi.join(context.homeDir, ".pi", "agent");
2120
+ }
2121
+ if (configured === "~") return context.homeDir;
2122
+ if (configured.startsWith("~/") || configured.startsWith("~\\")) {
2123
+ return pathApi.join(context.homeDir, configured.slice(2));
2124
+ }
2125
+ return pathApi.normalize(configured);
2126
+ }
1620
2127
 
1621
2128
  // ../../packages/agent-targets/src/artifacts/targets/claude.ts
1622
- import path3 from "node:path";
2129
+ import path5 from "node:path";
1623
2130
 
1624
2131
  // ../../packages/agent-targets/src/config-paths.ts
1625
- import path from "node:path";
2132
+ import path3 from "node:path";
1626
2133
  function claudeConfigDirectory(context) {
1627
2134
  const configuredDir = context.claudeConfigDir ?? context.env?.CLAUDE_CONFIG_DIR;
1628
- return typeof configuredDir === "string" && configuredDir.trim().length > 0 ? path.resolve(configuredDir) : path.join(context.homeDir, ".claude");
2135
+ return typeof configuredDir === "string" && configuredDir.trim().length > 0 ? path3.resolve(configuredDir) : path3.join(context.homeDir, ".claude");
1629
2136
  }
1630
2137
  function geminiConfigDirectory(context) {
1631
2138
  const configuredHome = context.env.GEMINI_CLI_HOME;
1632
- const homeRoot = typeof configuredHome === "string" && configuredHome.trim().length > 0 ? path.resolve(configuredHome) : context.homeDir;
1633
- return path.join(homeRoot, ".gemini");
2139
+ const homeRoot = typeof configuredHome === "string" && configuredHome.trim().length > 0 ? path3.resolve(configuredHome) : context.homeDir;
2140
+ return path3.join(homeRoot, ".gemini");
1634
2141
  }
1635
2142
 
1636
2143
  // ../../packages/agent-targets/src/artifacts/filesystem.ts
1637
- import fs from "node:fs";
1638
- import path2 from "node:path";
2144
+ import fs2 from "node:fs";
2145
+ import path4 from "node:path";
1639
2146
  var DEFAULT_SKIP_DIRS = /* @__PURE__ */ new Set([
1640
2147
  "node_modules",
1641
2148
  "dist",
@@ -1652,11 +2159,11 @@ var DEFAULT_SKIP_DIRS = /* @__PURE__ */ new Set([
1652
2159
  "designqc-captures"
1653
2160
  ]);
1654
2161
  function normalizeFilePath(filePath) {
1655
- return path2.resolve(filePath);
2162
+ return path4.resolve(filePath);
1656
2163
  }
1657
2164
  function safeReaddir(dir) {
1658
2165
  try {
1659
- return fs.readdirSync(dir, { withFileTypes: true });
2166
+ return fs2.readdirSync(dir, { withFileTypes: true });
1660
2167
  } catch {
1661
2168
  return [];
1662
2169
  }
@@ -1669,7 +2176,7 @@ function walkFiles(input2) {
1669
2176
  function walk(dir, depth) {
1670
2177
  if (depth > input2.maxDepth) return;
1671
2178
  for (const entry of safeReaddir(dir)) {
1672
- const entryPath = path2.join(dir, entry.name);
2179
+ const entryPath = path4.join(dir, entry.name);
1673
2180
  if (entry.isDirectory()) {
1674
2181
  if (!skipDirs.has(entry.name)) walk(entryPath, depth + 1);
1675
2182
  continue;
@@ -1705,7 +2212,7 @@ function transcriptCandidate(input2) {
1705
2212
  }
1706
2213
 
1707
2214
  // ../../packages/agent-targets/src/artifacts/harness-install-id.ts
1708
- import fs2 from "node:fs";
2215
+ import fs3 from "node:fs";
1709
2216
  var MAX_HARNESS_INSTALL_ID_LENGTH = 128;
1710
2217
  function normalizeHarnessInstallId(value) {
1711
2218
  if (typeof value !== "string") return null;
@@ -1716,7 +2223,7 @@ function normalizeHarnessInstallId(value) {
1716
2223
  }
1717
2224
  function readHarnessInstallIdFile(filePath) {
1718
2225
  try {
1719
- return normalizeHarnessInstallId(fs2.readFileSync(filePath, "utf8"));
2226
+ return normalizeHarnessInstallId(fs3.readFileSync(filePath, "utf8"));
1720
2227
  } catch {
1721
2228
  return null;
1722
2229
  }
@@ -1725,7 +2232,7 @@ var claudeMachineIdCache = /* @__PURE__ */ new Map();
1725
2232
  function readClaudeMachineId(configFile) {
1726
2233
  let stat;
1727
2234
  try {
1728
- stat = fs2.statSync(configFile);
2235
+ stat = fs3.statSync(configFile);
1729
2236
  } catch {
1730
2237
  claudeMachineIdCache.delete(configFile);
1731
2238
  return null;
@@ -1736,7 +2243,7 @@ function readClaudeMachineId(configFile) {
1736
2243
  }
1737
2244
  let machineId = null;
1738
2245
  try {
1739
- const parsed = JSON.parse(fs2.readFileSync(configFile, "utf8"));
2246
+ const parsed = JSON.parse(fs3.readFileSync(configFile, "utf8"));
1740
2247
  if (parsed && typeof parsed === "object") {
1741
2248
  machineId = normalizeHarnessInstallId(
1742
2249
  parsed.machineID
@@ -1754,55 +2261,8 @@ function readClaudeMachineId(configFile) {
1754
2261
  }
1755
2262
 
1756
2263
  // ../../packages/agent-targets/src/artifacts/repository-context.ts
1757
- import { createHash } from "node:crypto";
1758
- import fs3 from "node:fs";
1759
-
1760
- // ../../packages/shared/src/github-repository.ts
1761
- var MAX_REPOSITORY_BYTES = 201;
1762
- var UTF8_ENCODER = new TextEncoder();
1763
- function utf8Bytes(value) {
1764
- return UTF8_ENCODER.encode(value).byteLength;
1765
- }
1766
- function githubRepositoryPath(pathname) {
1767
- const parts = pathname.replace(/^\/+|\/+$/g, "").split("/");
1768
- if (parts.length !== 2) return null;
1769
- const owner = parts[0];
1770
- const repository = parts[1]?.replace(/\.git$/i, "");
1771
- if (!owner || !repository) return null;
1772
- const segmentPattern = /^[A-Za-z0-9_.-]+$/;
1773
- if (!segmentPattern.test(owner) || !segmentPattern.test(repository)) {
1774
- return null;
1775
- }
1776
- const canonical = `${owner.toLowerCase()}/${repository.toLowerCase()}`;
1777
- if (canonical.startsWith("./") || canonical.startsWith("../") || repository === "." || repository === ".." || repository.toLowerCase().endsWith(".git")) {
1778
- return null;
1779
- }
1780
- return utf8Bytes(canonical) <= MAX_REPOSITORY_BYTES ? canonical : null;
1781
- }
1782
- function canonicalGitHubRepository(remote) {
1783
- const trimmed = remote.trim();
1784
- if (!trimmed || trimmed.includes("\0")) return null;
1785
- const scp = trimmed.match(/^git@github\.com:([^?#]+)$/i);
1786
- if (scp?.[1]) return githubRepositoryPath(scp[1]);
1787
- let url2;
1788
- try {
1789
- url2 = new URL(trimmed);
1790
- } catch {
1791
- return null;
1792
- }
1793
- if (url2.hostname.toLowerCase() !== "github.com") return null;
1794
- if (url2.search || url2.hash || url2.password || url2.port) return null;
1795
- if (url2.protocol === "https:" || url2.protocol === "http:") {
1796
- if (url2.username) return null;
1797
- } else if (url2.protocol === "ssh:") {
1798
- if (url2.username && url2.username !== "git") return null;
1799
- } else {
1800
- return null;
1801
- }
1802
- return githubRepositoryPath(url2.pathname);
1803
- }
1804
-
1805
- // ../../packages/agent-targets/src/artifacts/repository-context.ts
2264
+ import { createHash as createHash2 } from "node:crypto";
2265
+ import fs4 from "node:fs";
1806
2266
  var MAX_ARTIFACT_CONTEXT_PREFIX_BYTES = 256 * 1024;
1807
2267
  var MAX_ARTIFACT_CONTEXT_RECORDS = 64;
1808
2268
  var ARTIFACT_CONTEXT_READ_CHUNK_BYTES = 32 * 1024;
@@ -1825,12 +2285,12 @@ function recordValue(value) {
1825
2285
  function readJsonlContextRecords(sourcePath) {
1826
2286
  let file2;
1827
2287
  try {
1828
- file2 = fs3.openSync(sourcePath, "r");
2288
+ file2 = fs4.openSync(sourcePath, "r");
1829
2289
  } catch {
1830
2290
  return [];
1831
2291
  }
1832
2292
  try {
1833
- const stat = fs3.fstatSync(file2);
2293
+ const stat = fs4.fstatSync(file2);
1834
2294
  const byteLimit = Math.min(stat.size, MAX_ARTIFACT_CONTEXT_PREFIX_BYTES);
1835
2295
  if (byteLimit <= 0) return [];
1836
2296
  const records = [];
@@ -1848,7 +2308,7 @@ function readJsonlContextRecords(sourcePath) {
1848
2308
  const chunk = Buffer.allocUnsafe(ARTIFACT_CONTEXT_READ_CHUNK_BYTES);
1849
2309
  while (position < byteLimit && records.length < MAX_ARTIFACT_CONTEXT_RECORDS) {
1850
2310
  const want = Math.min(chunk.length, byteLimit - position);
1851
- const bytesRead = fs3.readSync(file2, chunk, 0, want, position);
2311
+ const bytesRead = fs4.readSync(file2, chunk, 0, want, position);
1852
2312
  if (bytesRead <= 0) break;
1853
2313
  position += bytesRead;
1854
2314
  const buffered = Buffer.concat([carry, chunk.subarray(0, bytesRead)]);
@@ -1871,7 +2331,7 @@ function readJsonlContextRecords(sourcePath) {
1871
2331
  } catch {
1872
2332
  return [];
1873
2333
  } finally {
1874
- fs3.closeSync(file2);
2334
+ fs4.closeSync(file2);
1875
2335
  }
1876
2336
  }
1877
2337
  function contextTimestampMs(value, fallback) {
@@ -1899,7 +2359,7 @@ function repositoryObservation(input2) {
1899
2359
  branch ?? null,
1900
2360
  commitHash ?? null
1901
2361
  ]);
1902
- const id = `repo_observation:${createHash("sha256").update(identity).digest("hex")}`;
2362
+ const id = `repo_observation:${createHash2("sha256").update(identity).digest("hex")}`;
1903
2363
  return {
1904
2364
  id,
1905
2365
  repository: input2.repository,
@@ -1910,14 +2370,29 @@ function repositoryObservation(input2) {
1910
2370
  ...commitHash ? { commitHash } : {}
1911
2371
  };
1912
2372
  }
2373
+ function localGitStateMetadata(states, source = "transcript_discovery") {
2374
+ const bounded = [];
2375
+ for (const state of states) {
2376
+ if (!state || Buffer.byteLength(
2377
+ JSON.stringify({ version: 1, source, states: [...bounded, state] }),
2378
+ "utf8"
2379
+ ) > 4 * 1024)
2380
+ continue;
2381
+ bounded.push(state);
2382
+ }
2383
+ return bounded.length ? { localGitStates: { version: 1, source, states: bounded } } : {};
2384
+ }
1913
2385
 
1914
2386
  // ../../packages/agent-targets/src/artifacts/targets/claude.ts
1915
2387
  function claudeTranscriptCandidate(sourcePath, context, harnessInstallId) {
1916
2388
  const fallbackObservedAtMs = (context.nowMs ?? Date.now)();
2389
+ const states = /* @__PURE__ */ new Map();
1917
2390
  const observations = /* @__PURE__ */ new Map();
1918
2391
  for (const record2 of readJsonlContextRecords(sourcePath)) {
1919
2392
  if (typeof record2.cwd !== "string" || !record2.cwd.trim()) continue;
1920
2393
  const origin = context.resolveGitHubOrigin?.(record2.cwd);
2394
+ const state = origin?.gitState ?? context.resolveGitState?.(record2.cwd);
2395
+ if (state) states.set(state.worktreeRoot, state);
1921
2396
  if (!origin) continue;
1922
2397
  const observation = repositoryObservation({
1923
2398
  repository: origin.repository,
@@ -1932,7 +2407,10 @@ function claudeTranscriptCandidate(sourcePath, context, harnessInstallId) {
1932
2407
  return transcriptCandidate({
1933
2408
  targetId: "claude",
1934
2409
  sourcePath,
1935
- metadata: harnessInstallId ? { harnessInstallId } : void 0,
2410
+ metadata: {
2411
+ ...harnessInstallId ? { harnessInstallId } : {},
2412
+ ...localGitStateMetadata([...states.values()])
2413
+ },
1936
2414
  repositoryObservations: [...observations.values()]
1937
2415
  });
1938
2416
  }
@@ -1941,9 +2419,9 @@ var claudeArtifactDiscovery = Object.freeze({
1941
2419
  discover: (context) => {
1942
2420
  const { homeDir, env } = context;
1943
2421
  const configDir = claudeConfigDirectory({ homeDir, env });
1944
- const harnessInstallId = readClaudeMachineId(path3.join(configDir, ".claude.json")) ?? readClaudeMachineId(path3.join(homeDir, ".claude.json"));
2422
+ const harnessInstallId = readClaudeMachineId(path5.join(configDir, ".claude.json")) ?? readClaudeMachineId(path5.join(homeDir, ".claude.json"));
1945
2423
  return walkFiles({
1946
- root: path3.join(configDir, "projects"),
2424
+ root: path5.join(configDir, "projects"),
1947
2425
  maxDepth: 6,
1948
2426
  includeFile: (filePath) => filePath.endsWith(".jsonl")
1949
2427
  }).map(
@@ -1953,10 +2431,10 @@ var claudeArtifactDiscovery = Object.freeze({
1953
2431
  });
1954
2432
 
1955
2433
  // ../../packages/agent-targets/src/artifacts/targets/codex.ts
1956
- import path4 from "node:path";
2434
+ import path6 from "node:path";
1957
2435
  var CODEX_ROLLOUT_ID = /-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl(?:\.zst)?$/i;
1958
2436
  function codexRolloutId(sourcePath) {
1959
- return CODEX_ROLLOUT_ID.exec(path4.basename(sourcePath))?.[1]?.toLowerCase() ?? null;
2437
+ return CODEX_ROLLOUT_ID.exec(path6.basename(sourcePath))?.[1]?.toLowerCase() ?? null;
1960
2438
  }
1961
2439
  function codexTranscriptCandidate(sourcePath, readPath, store, context, harnessInstallId) {
1962
2440
  const rolloutId = codexRolloutId(sourcePath);
@@ -1969,9 +2447,10 @@ function codexTranscriptCandidate(sourcePath, readPath, store, context, harnessI
1969
2447
  if (!payload) continue;
1970
2448
  const sessionId = typeof payload.id === "string" && payload.id.trim() ? payload.id.trim() : null;
1971
2449
  const cwd = typeof payload.cwd === "string" ? payload.cwd : void 0;
1972
- const git2 = recordValue(payload.git);
1973
- const nativeRepository = typeof git2?.repository_url === "string" ? canonicalGitHubRepository(git2.repository_url) : null;
2450
+ const git = recordValue(payload.git);
2451
+ const nativeRepository = typeof git?.repository_url === "string" ? canonicalGitHubRepository(git.repository_url) : null;
1974
2452
  const origin = cwd ? context.resolveGitHubOrigin?.(cwd) : null;
2453
+ const state = origin?.gitState ?? (cwd ? context.resolveGitState?.(cwd) : null);
1975
2454
  const observations = [];
1976
2455
  if (nativeRepository) {
1977
2456
  observations.push(
@@ -1983,8 +2462,8 @@ function codexTranscriptCandidate(sourcePath, readPath, store, context, harnessI
1983
2462
  fallbackObservedAtMs
1984
2463
  ),
1985
2464
  cwd,
1986
- branch: git2?.branch,
1987
- commitHash: git2?.commit_hash
2465
+ branch: git?.branch,
2466
+ commitHash: git?.commit_hash
1988
2467
  })
1989
2468
  );
1990
2469
  }
@@ -2006,7 +2485,11 @@ function codexTranscriptCandidate(sourcePath, readPath, store, context, harnessI
2006
2485
  identityKey,
2007
2486
  readPath,
2008
2487
  sessionId,
2009
- metadata: { store, ...identityMetadata },
2488
+ metadata: {
2489
+ store,
2490
+ ...identityMetadata,
2491
+ ...localGitStateMetadata([state])
2492
+ },
2010
2493
  repositoryObservations: observations
2011
2494
  });
2012
2495
  }
@@ -2026,15 +2509,15 @@ var codexArtifactDiscovery = Object.freeze({
2026
2509
  discover: (context) => {
2027
2510
  const { homeDir, env } = context;
2028
2511
  const configuredHome = env.CODEX_HOME?.trim();
2029
- const codexHome = configuredHome ? path4.resolve(configuredHome) : path4.join(homeDir, ".codex");
2512
+ const codexHome = configuredHome ? path6.resolve(configuredHome) : path6.join(homeDir, ".codex");
2030
2513
  const harnessInstallId = readHarnessInstallIdFile(
2031
- path4.join(codexHome, "installation_id")
2514
+ path6.join(codexHome, "installation_id")
2032
2515
  );
2033
2516
  const roots = [
2034
- { store: "sessions", root: path4.join(codexHome, "sessions") },
2517
+ { store: "sessions", root: path6.join(codexHome, "sessions") },
2035
2518
  {
2036
2519
  store: "archived_sessions",
2037
- root: path4.join(codexHome, "archived_sessions")
2520
+ root: path6.join(codexHome, "archived_sessions")
2038
2521
  }
2039
2522
  ];
2040
2523
  const files = roots.flatMap(
@@ -2072,27 +2555,27 @@ var codexArtifactDiscovery = Object.freeze({
2072
2555
  });
2073
2556
 
2074
2557
  // ../../packages/agent-targets/src/artifacts/targets/copilot-cli.ts
2075
- import path5 from "node:path";
2558
+ import path7 from "node:path";
2076
2559
  var copilotCliArtifactDiscovery = Object.freeze({
2077
2560
  targetId: "copilot-cli",
2078
2561
  discover: ({ homeDir }) => walkFiles({
2079
- root: path5.join(homeDir, ".copilot", "session-state"),
2562
+ root: path7.join(homeDir, ".copilot", "session-state"),
2080
2563
  maxDepth: 3,
2081
- includeFile: (filePath) => path5.basename(filePath) === "events.jsonl"
2564
+ includeFile: (filePath) => path7.basename(filePath) === "events.jsonl"
2082
2565
  }).map(
2083
2566
  (sourcePath) => transcriptCandidate({ targetId: "copilot-cli", sourcePath })
2084
2567
  )
2085
2568
  });
2086
2569
 
2087
2570
  // ../../packages/agent-targets/src/artifacts/targets/cursor.ts
2088
- import path6 from "node:path";
2571
+ import path8 from "node:path";
2089
2572
  function hasPathSegment(filePath, segment) {
2090
- return normalizeFilePath(filePath).split(path6.sep).includes(segment);
2573
+ return normalizeFilePath(filePath).split(path8.sep).includes(segment);
2091
2574
  }
2092
2575
  var cursorArtifactDiscovery = Object.freeze({
2093
2576
  targetId: "cursor",
2094
2577
  discover: ({ homeDir }) => walkFiles({
2095
- root: path6.join(homeDir, ".cursor", "projects"),
2578
+ root: path8.join(homeDir, ".cursor", "projects"),
2096
2579
  maxDepth: 5,
2097
2580
  includeFile: (filePath) => filePath.endsWith(".jsonl") && hasPathSegment(filePath, "agent-transcripts")
2098
2581
  }).map(
@@ -2101,11 +2584,11 @@ var cursorArtifactDiscovery = Object.freeze({
2101
2584
  });
2102
2585
 
2103
2586
  // ../../packages/agent-targets/src/artifacts/targets/factory-droid.ts
2104
- import path7 from "node:path";
2587
+ import path9 from "node:path";
2105
2588
  var factoryDroidArtifactDiscovery = Object.freeze({
2106
2589
  targetId: "factory-droid",
2107
2590
  discover: ({ homeDir }) => walkFiles({
2108
- root: path7.join(homeDir, ".factory", "sessions"),
2591
+ root: path9.join(homeDir, ".factory", "sessions"),
2109
2592
  maxDepth: 4,
2110
2593
  includeFile: (filePath) => filePath.endsWith(".jsonl")
2111
2594
  }).map(
@@ -2114,7 +2597,7 @@ var factoryDroidArtifactDiscovery = Object.freeze({
2114
2597
  });
2115
2598
 
2116
2599
  // ../../packages/agent-targets/src/artifacts/targets/gemini.ts
2117
- import path8 from "node:path";
2600
+ import path10 from "node:path";
2118
2601
  var GEMINI_DISCOVER_SKIP_DIRS = /* @__PURE__ */ new Set([
2119
2602
  "bin",
2120
2603
  "checkpoints",
@@ -2129,15 +2612,15 @@ var geminiArtifactDiscovery = Object.freeze({
2129
2612
  discover: ({ homeDir, env }) => {
2130
2613
  const geminiDir = geminiConfigDirectory({ homeDir, env });
2131
2614
  const harnessInstallId = readHarnessInstallIdFile(
2132
- path8.join(geminiDir, "installation_id")
2615
+ path10.join(geminiDir, "installation_id")
2133
2616
  );
2134
- const root = path8.join(geminiDir, "tmp");
2617
+ const root = path10.join(geminiDir, "tmp");
2135
2618
  const files = [];
2136
2619
  const seen = /* @__PURE__ */ new Set();
2137
2620
  function walk(dir, depth) {
2138
2621
  if (depth > 4) return;
2139
2622
  for (const entry of safeReaddir(dir)) {
2140
- const entryPath = path8.join(dir, entry.name);
2623
+ const entryPath = path10.join(dir, entry.name);
2141
2624
  if (!entry.isDirectory()) continue;
2142
2625
  if (GEMINI_DISCOVER_SKIP_DIRS.has(entry.name)) continue;
2143
2626
  if (entry.name === "chats") {
@@ -2146,7 +2629,7 @@ var geminiArtifactDiscovery = Object.freeze({
2146
2629
  continue;
2147
2630
  }
2148
2631
  const sourcePath = normalizeFilePath(
2149
- path8.join(entryPath, chatEntry.name)
2632
+ path10.join(entryPath, chatEntry.name)
2150
2633
  );
2151
2634
  if (seen.has(sourcePath)) continue;
2152
2635
  seen.add(sourcePath);
@@ -2169,52 +2652,52 @@ var geminiArtifactDiscovery = Object.freeze({
2169
2652
  });
2170
2653
 
2171
2654
  // ../../packages/agent-targets/src/artifacts/targets/grok.ts
2172
- import path9 from "node:path";
2655
+ import path11 from "node:path";
2173
2656
  function grokHome(homeDir, env) {
2174
2657
  const configuredHome = env.GROK_HOME?.trim();
2175
- return configuredHome ? path9.resolve(configuredHome) : path9.join(homeDir, ".grok");
2658
+ return configuredHome ? path11.resolve(configuredHome) : path11.join(homeDir, ".grok");
2176
2659
  }
2177
2660
  var grokArtifactDiscovery = Object.freeze({
2178
2661
  targetId: "grok",
2179
2662
  discover: ({ homeDir, env }) => walkFiles({
2180
- root: path9.join(grokHome(homeDir, env), "sessions"),
2663
+ root: path11.join(grokHome(homeDir, env), "sessions"),
2181
2664
  maxDepth: 5,
2182
- includeFile: (filePath) => path9.basename(filePath) === "updates.jsonl"
2665
+ includeFile: (filePath) => path11.basename(filePath) === "updates.jsonl"
2183
2666
  }).map(
2184
2667
  (sourcePath) => transcriptCandidate({ targetId: "grok", sourcePath })
2185
2668
  )
2186
2669
  });
2187
2670
 
2188
2671
  // ../../packages/agent-targets/src/artifacts/targets/mini-swe-agent.ts
2189
- import fs4 from "node:fs";
2190
- import path10 from "node:path";
2672
+ import fs5 from "node:fs";
2673
+ import path12 from "node:path";
2191
2674
  var SESSION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2192
2675
  function dataDirectory({ homeDir, env }) {
2193
2676
  if (env.FML_DATA_DIR?.trim()) {
2194
- if (!path10.isAbsolute(env.FML_DATA_DIR)) {
2677
+ if (!path12.isAbsolute(env.FML_DATA_DIR)) {
2195
2678
  throw new Error("FML_DATA_DIR must be an absolute path");
2196
2679
  }
2197
- return path10.normalize(env.FML_DATA_DIR);
2680
+ return path12.normalize(env.FML_DATA_DIR);
2198
2681
  }
2199
2682
  if (process.platform === "darwin") {
2200
- return path10.join(homeDir, "Library", "Application Support", "fml");
2683
+ return path12.join(homeDir, "Library", "Application Support", "fml");
2201
2684
  }
2202
2685
  if (process.platform === "win32") {
2203
- const roaming = env.APPDATA && path10.isAbsolute(env.APPDATA) ? env.APPDATA : path10.join(homeDir, "AppData", "Roaming");
2204
- return path10.join(roaming, "fml");
2686
+ const roaming = env.APPDATA && path12.isAbsolute(env.APPDATA) ? env.APPDATA : path12.join(homeDir, "AppData", "Roaming");
2687
+ return path12.join(roaming, "fml");
2205
2688
  }
2206
- const xdgDataHome = env.XDG_DATA_HOME && path10.isAbsolute(env.XDG_DATA_HOME) ? env.XDG_DATA_HOME : path10.join(homeDir, ".local", "share");
2207
- return path10.join(xdgDataHome, "fml");
2689
+ const xdgDataHome = env.XDG_DATA_HOME && path12.isAbsolute(env.XDG_DATA_HOME) ? env.XDG_DATA_HOME : path12.join(homeDir, ".local", "share");
2690
+ return path12.join(xdgDataHome, "fml");
2208
2691
  }
2209
2692
  function snapshotCandidate(sourcePath, context) {
2210
2693
  let file2;
2211
2694
  try {
2212
- file2 = fs4.openSync(sourcePath, "r");
2695
+ file2 = fs5.openSync(sourcePath, "r");
2213
2696
  } catch {
2214
2697
  return null;
2215
2698
  }
2216
2699
  try {
2217
- const stat = fs4.fstatSync(file2);
2700
+ const stat = fs5.fstatSync(file2);
2218
2701
  if (!stat.isFile()) return null;
2219
2702
  if (context.maxFileBytes !== void 0 && stat.size > context.maxFileBytes) {
2220
2703
  return transcriptCandidate({ targetId: "mini-swe-agent", sourcePath });
@@ -2222,7 +2705,7 @@ function snapshotCandidate(sourcePath, context) {
2222
2705
  const bytes = Buffer.allocUnsafe(stat.size);
2223
2706
  let offset = 0;
2224
2707
  while (offset < bytes.length) {
2225
- const read = fs4.readSync(
2708
+ const read = fs5.readSync(
2226
2709
  file2,
2227
2710
  bytes,
2228
2711
  offset,
@@ -2232,7 +2715,7 @@ function snapshotCandidate(sourcePath, context) {
2232
2715
  if (read <= 0) return null;
2233
2716
  offset += read;
2234
2717
  }
2235
- if (fs4.fstatSync(file2).size !== stat.size) return null;
2718
+ if (fs5.fstatSync(file2).size !== stat.size) return null;
2236
2719
  const value = JSON.parse(bytes.toString("utf8"));
2237
2720
  if (typeof value !== "object" || value === null || !("fml" in value)) {
2238
2721
  return null;
@@ -2262,13 +2745,13 @@ function snapshotCandidate(sourcePath, context) {
2262
2745
  } catch {
2263
2746
  return null;
2264
2747
  } finally {
2265
- fs4.closeSync(file2);
2748
+ fs5.closeSync(file2);
2266
2749
  }
2267
2750
  }
2268
2751
  var miniSweAgentArtifactDiscovery = Object.freeze({
2269
2752
  targetId: "mini-swe-agent",
2270
2753
  discover: (context) => walkFiles({
2271
- root: path10.join(dataDirectory(context), "mini-swe-agent", "trajectories"),
2754
+ root: path12.join(dataDirectory(context), "mini-swe-agent", "trajectories"),
2272
2755
  maxDepth: 0,
2273
2756
  includeFile: (filePath) => filePath.endsWith(".traj.json")
2274
2757
  }).flatMap((sourcePath) => {
@@ -2278,7 +2761,7 @@ var miniSweAgentArtifactDiscovery = Object.freeze({
2278
2761
  });
2279
2762
 
2280
2763
  // ../../packages/agent-targets/src/artifacts/targets/pi.ts
2281
- import path11 from "node:path";
2764
+ import path13 from "node:path";
2282
2765
 
2283
2766
  // ../../node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/classic/external.js
2284
2767
  var external_exports = {};
@@ -3097,10 +3580,10 @@ function mergeDefs(...defs) {
3097
3580
  function cloneDef(schema) {
3098
3581
  return mergeDefs(schema._zod.def);
3099
3582
  }
3100
- function getElementAtPath(obj, path20) {
3101
- if (!path20)
3583
+ function getElementAtPath(obj, path23) {
3584
+ if (!path23)
3102
3585
  return obj;
3103
- return path20.reduce((acc, key) => acc?.[key], obj);
3586
+ return path23.reduce((acc, key) => acc?.[key], obj);
3104
3587
  }
3105
3588
  function promiseAllObject(promisesObj) {
3106
3589
  const keys = Object.keys(promisesObj);
@@ -3440,11 +3923,11 @@ function explicitlyAborted(x, startIndex = 0) {
3440
3923
  }
3441
3924
  return false;
3442
3925
  }
3443
- function prefixIssues(path20, issues) {
3926
+ function prefixIssues(path23, issues) {
3444
3927
  return issues.map((iss) => {
3445
3928
  var _a3;
3446
3929
  (_a3 = iss).path ?? (_a3.path = []);
3447
- iss.path.unshift(path20);
3930
+ iss.path.unshift(path23);
3448
3931
  return iss;
3449
3932
  });
3450
3933
  }
@@ -3906,16 +4389,16 @@ function flattenError(error62, mapper = (issue2) => issue2.message) {
3906
4389
  }
3907
4390
  function formatError(error62, mapper = (issue2) => issue2.message) {
3908
4391
  const fieldErrors = { _errors: [] };
3909
- const processError = (error63, path20 = []) => {
4392
+ const processError = (error63, path23 = []) => {
3910
4393
  for (const issue2 of error63.issues) {
3911
4394
  if (issue2.code === "invalid_union" && issue2.errors.length) {
3912
- issue2.errors.map((issues) => processError({ issues }, [...path20, ...issue2.path]));
4395
+ issue2.errors.map((issues) => processError({ issues }, [...path23, ...issue2.path]));
3913
4396
  } else if (issue2.code === "invalid_key") {
3914
- processError({ issues: issue2.issues }, [...path20, ...issue2.path]);
4397
+ processError({ issues: issue2.issues }, [...path23, ...issue2.path]);
3915
4398
  } else if (issue2.code === "invalid_element") {
3916
- processError({ issues: issue2.issues }, [...path20, ...issue2.path]);
4399
+ processError({ issues: issue2.issues }, [...path23, ...issue2.path]);
3917
4400
  } else {
3918
- const fullpath = [...path20, ...issue2.path];
4401
+ const fullpath = [...path23, ...issue2.path];
3919
4402
  if (fullpath.length === 0) {
3920
4403
  fieldErrors._errors.push(mapper(issue2));
3921
4404
  } else {
@@ -3954,17 +4437,17 @@ function formatError(error62, mapper = (issue2) => issue2.message) {
3954
4437
  }
3955
4438
  function treeifyError(error62, mapper = (issue2) => issue2.message) {
3956
4439
  const result = { errors: [] };
3957
- const processError = (error63, path20 = []) => {
4440
+ const processError = (error63, path23 = []) => {
3958
4441
  var _a3;
3959
4442
  for (const issue2 of error63.issues) {
3960
4443
  if (issue2.code === "invalid_union" && issue2.errors.length) {
3961
- issue2.errors.map((issues) => processError({ issues }, [...path20, ...issue2.path]));
4444
+ issue2.errors.map((issues) => processError({ issues }, [...path23, ...issue2.path]));
3962
4445
  } else if (issue2.code === "invalid_key") {
3963
- processError({ issues: issue2.issues }, [...path20, ...issue2.path]);
4446
+ processError({ issues: issue2.issues }, [...path23, ...issue2.path]);
3964
4447
  } else if (issue2.code === "invalid_element") {
3965
- processError({ issues: issue2.issues }, [...path20, ...issue2.path]);
4448
+ processError({ issues: issue2.issues }, [...path23, ...issue2.path]);
3966
4449
  } else {
3967
- const fullpath = [...path20, ...issue2.path];
4450
+ const fullpath = [...path23, ...issue2.path];
3968
4451
  if (fullpath.length === 0) {
3969
4452
  result.errors.push(mapper(issue2));
3970
4453
  continue;
@@ -4003,8 +4486,8 @@ function treeifyError(error62, mapper = (issue2) => issue2.message) {
4003
4486
  }
4004
4487
  function toDotPath(_path) {
4005
4488
  const segs = [];
4006
- const path20 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
4007
- for (const seg of path20) {
4489
+ const path23 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
4490
+ for (const seg of path23) {
4008
4491
  if (typeof seg === "number")
4009
4492
  segs.push(`[${seg}]`);
4010
4493
  else if (typeof seg === "symbol")
@@ -21142,13 +21625,13 @@ function resolveRef(ref, ctx) {
21142
21625
  if (!ref.startsWith("#")) {
21143
21626
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
21144
21627
  }
21145
- const path20 = ref.slice(1).split("/").filter(Boolean);
21146
- if (path20.length === 0) {
21628
+ const path23 = ref.slice(1).split("/").filter(Boolean);
21629
+ if (path23.length === 0) {
21147
21630
  return ctx.rootSchema;
21148
21631
  }
21149
21632
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
21150
- if (path20[0] === defsKey) {
21151
- const key = path20[1] === void 0 ? void 0 : decodeJSONPointerSegment(path20[1]);
21633
+ if (path23[0] === defsKey) {
21634
+ const key = path23[1] === void 0 ? void 0 : decodeJSONPointerSegment(path23[1]);
21152
21635
  if (!key || !ctx.defs[key]) {
21153
21636
  throw new Error(`Reference not found: ${ref}`);
21154
21637
  }
@@ -22002,6 +22485,12 @@ var PI_REPOSITORY_ORIGIN_ENTRY_TYPE = "fml-repository-origin";
22002
22485
  var RepositorySlugSchema = external_exports.string().regex(/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u).transform((slug) => canonicalGitHubRepository(`https://github.com/${slug}`)).pipe(external_exports.string());
22003
22486
  var PiRepositoryOriginDataSchema = external_exports.object({
22004
22487
  version: external_exports.literal(1),
22488
+ gitState: external_exports.unknown().transform((value, ctx) => {
22489
+ const state = parseGitState(value);
22490
+ if (state) return state;
22491
+ ctx.addIssue({ code: "custom", message: "Invalid Git state snapshot" });
22492
+ return external_exports.NEVER;
22493
+ }).nullable().optional(),
22005
22494
  repository: RepositorySlugSchema.nullable(),
22006
22495
  cwd: external_exports.string().min(1).refine(
22007
22496
  (cwd) => cwd.trim().length > 0 && !new RegExp("\\p{Cc}", "u").test(cwd) && Buffer.byteLength(cwd, "utf8") <= 4096
@@ -22043,12 +22532,15 @@ function piTranscriptCandidate(sourcePath) {
22043
22532
  targetId: "pi",
22044
22533
  sourcePath,
22045
22534
  sessionId,
22535
+ metadata: localGitStateMetadata([snapshot?.gitState], "pi_origin"),
22046
22536
  repositoryObservations: snapshot?.repository ? [
22047
22537
  repositoryObservation({
22048
22538
  repository: snapshot.repository,
22049
22539
  source: "git_origin",
22050
22540
  observedAtMs: snapshot.observedAtMs,
22051
- cwd: snapshot.cwd
22541
+ cwd: snapshot.cwd,
22542
+ branch: snapshot.gitState?.branch,
22543
+ commitHash: snapshot.gitState?.commitHash
22052
22544
  })
22053
22545
  ] : []
22054
22546
  });
@@ -22056,9 +22548,8 @@ function piTranscriptCandidate(sourcePath) {
22056
22548
  var piArtifactDiscovery = Object.freeze({
22057
22549
  targetId: "pi",
22058
22550
  discover: (context) => {
22059
- const { homeDir } = context;
22060
22551
  return walkFiles({
22061
- root: path11.join(homeDir, ".pi", "agent", "sessions"),
22552
+ root: path13.join(resolvePiAgentDir(context), "sessions"),
22062
22553
  maxDepth: 8,
22063
22554
  includeFile: (filePath) => filePath.endsWith(".jsonl"),
22064
22555
  skipDirs: PI_SKIP_DIRS
@@ -22084,9 +22575,9 @@ var MAX_MATERIALIZED_ARTIFACT_BYTES = 100 * 1024 * 1024;
22084
22575
  var DEFAULT_MATERIALIZED_CACHE_BYTES = 2 * 1024 * 1024 * 1024;
22085
22576
 
22086
22577
  // ../../packages/agent-targets/src/install/pi/index.ts
22087
- import fs5 from "node:fs";
22088
- import path12 from "node:path";
22089
- import { randomUUID } from "node:crypto";
22578
+ import fs6 from "node:fs";
22579
+ import path14 from "node:path";
22580
+ import { randomUUID as randomUUID2 } from "node:crypto";
22090
22581
  var FML_PI_EXTENSION_MARKER = "__FML_PI_EXTENSION_V1__";
22091
22582
  var LEGACY_PI_EXTENSION_MARKER = "__panopticonPiExtensionTest";
22092
22583
  var LEGACY_PI_EXTENSION_SIGNATURES = Object.freeze([
@@ -22098,22 +22589,11 @@ var LEGACY_PI_EXTENSION_SIGNATURES = Object.freeze([
22098
22589
  "SessionEnd"
22099
22590
  ]);
22100
22591
  function pathApiFor(platform) {
22101
- return platform === "win32" ? path12.win32 : path12.posix;
22102
- }
22103
- function resolvePiAgentDir(context, pathApi) {
22104
- const configured = context.env.PI_CODING_AGENT_DIR?.trim();
22105
- if (!configured) {
22106
- return pathApi.join(context.homeDir, ".pi", "agent");
22107
- }
22108
- if (configured === "~") return context.homeDir;
22109
- if (configured.startsWith("~/") || configured.startsWith("~\\")) {
22110
- return pathApi.join(context.homeDir, configured.slice(2));
22111
- }
22112
- return pathApi.normalize(configured);
22592
+ return platform === "win32" ? path14.win32 : path14.posix;
22113
22593
  }
22114
22594
  function readFileIfPresent(filePath) {
22115
22595
  try {
22116
- return fs5.readFileSync(filePath, "utf8");
22596
+ return fs6.readFileSync(filePath, "utf8");
22117
22597
  } catch (error62) {
22118
22598
  if (error62.code === "ENOENT") return null;
22119
22599
  throw error62;
@@ -22130,30 +22610,30 @@ function removeLegacyExtension(filePath) {
22130
22610
  if (!isOwnedExtension(source, true)) {
22131
22611
  return { removed: false, preserved: true };
22132
22612
  }
22133
- fs5.rmSync(filePath);
22613
+ fs6.rmSync(filePath);
22134
22614
  return { removed: true, preserved: false };
22135
22615
  }
22136
22616
  function writeExtensionAtomically(filePath, source) {
22137
- fs5.mkdirSync(path12.dirname(filePath), { recursive: true, mode: 448 });
22138
- const temporaryPath = path12.join(
22139
- path12.dirname(filePath),
22140
- `.${path12.basename(filePath)}.${process.pid}.${randomUUID()}.tmp`
22617
+ fs6.mkdirSync(path14.dirname(filePath), { recursive: true, mode: 448 });
22618
+ const temporaryPath = path14.join(
22619
+ path14.dirname(filePath),
22620
+ `.${path14.basename(filePath)}.${process.pid}.${randomUUID2()}.tmp`
22141
22621
  );
22142
22622
  try {
22143
- fs5.writeFileSync(temporaryPath, source, { encoding: "utf8", mode: 384 });
22144
- if (process.platform === "win32" && fs5.existsSync(filePath)) {
22145
- fs5.rmSync(filePath);
22623
+ fs6.writeFileSync(temporaryPath, source, { encoding: "utf8", mode: 384 });
22624
+ if (process.platform === "win32" && fs6.existsSync(filePath)) {
22625
+ fs6.rmSync(filePath);
22146
22626
  }
22147
- fs5.renameSync(temporaryPath, filePath);
22627
+ fs6.renameSync(temporaryPath, filePath);
22148
22628
  } finally {
22149
- fs5.rmSync(temporaryPath, { force: true });
22629
+ fs6.rmSync(temporaryPath, { force: true });
22150
22630
  }
22151
22631
  }
22152
22632
  var PI_EXTENSION_INSTALLER = Object.freeze({
22153
22633
  resolvePaths(context) {
22154
22634
  const pathApi = pathApiFor(context.platform);
22155
22635
  const extensionsDir = pathApi.join(
22156
- resolvePiAgentDir(context, pathApi),
22636
+ resolvePiAgentDir(context),
22157
22637
  "extensions"
22158
22638
  );
22159
22639
  return {
@@ -22169,7 +22649,7 @@ var PI_EXTENSION_INSTALLER = Object.freeze({
22169
22649
  };
22170
22650
  },
22171
22651
  install(paths) {
22172
- const source = fs5.readFileSync(paths.sourcePath, "utf8");
22652
+ const source = fs6.readFileSync(paths.sourcePath, "utf8");
22173
22653
  if (!source.includes(FML_PI_EXTENSION_MARKER)) {
22174
22654
  throw new Error(
22175
22655
  `Packaged Pi extension is invalid: ${paths.sourcePath} does not contain the FML ownership marker`
@@ -22218,7 +22698,7 @@ var PI_EXTENSION_INSTALLER = Object.freeze({
22218
22698
  legacyPreserved: legacy.preserved
22219
22699
  };
22220
22700
  }
22221
- fs5.rmSync(paths.destinationPath);
22701
+ fs6.rmSync(paths.destinationPath);
22222
22702
  return {
22223
22703
  status: "removed",
22224
22704
  destinationPath: paths.destinationPath,
@@ -22229,11 +22709,11 @@ var PI_EXTENSION_INSTALLER = Object.freeze({
22229
22709
  });
22230
22710
 
22231
22711
  // src/hooks/context.ts
22232
- import { createHash as createHash5 } from "node:crypto";
22233
- import path18 from "node:path";
22712
+ import { createHash as createHash6 } from "node:crypto";
22713
+ import path21 from "node:path";
22234
22714
 
22235
22715
  // ../../packages/agent-targets/src/hooks/normalize.ts
22236
- import { createHash as createHash2 } from "node:crypto";
22716
+ import { createHash as createHash3 } from "node:crypto";
22237
22717
 
22238
22718
  // ../../packages/agent-targets/src/hooks/types.ts
22239
22719
  var HOOK_TARGETS = Object.freeze([
@@ -22349,7 +22829,7 @@ var MINI_SWE_AGENT_HOOK_EVENTS = Object.freeze([
22349
22829
  ]);
22350
22830
 
22351
22831
  // ../../packages/agent-targets/src/hooks/read-command.ts
22352
- import path13 from "node:path";
22832
+ import path15 from "node:path";
22353
22833
  var SHELL_TOOL_NAMES = /* @__PURE__ */ new Set([
22354
22834
  "bash",
22355
22835
  "exec_command",
@@ -22465,12 +22945,12 @@ function splitCommandSegments(tokens) {
22465
22945
  return segments;
22466
22946
  }
22467
22947
  function stripSimpleOptions(args, flagsWithValues = /* @__PURE__ */ new Set(), patternFlags = /* @__PURE__ */ new Set()) {
22468
- const remaining = [];
22948
+ const remaining2 = [];
22469
22949
  let consumedPatternFlag = false;
22470
22950
  for (let index = 0; index < args.length; index += 1) {
22471
22951
  const argument = args[index];
22472
22952
  if (argument === "--") {
22473
- remaining.push(...args.slice(index + 1));
22953
+ remaining2.push(...args.slice(index + 1));
22474
22954
  break;
22475
22955
  }
22476
22956
  if (argument.startsWith("-") && argument.length > 1) {
@@ -22480,9 +22960,9 @@ function stripSimpleOptions(args, flagsWithValues = /* @__PURE__ */ new Set(), p
22480
22960
  }
22481
22961
  continue;
22482
22962
  }
22483
- remaining.push(argument);
22963
+ remaining2.push(argument);
22484
22964
  }
22485
- return { args: remaining, consumedPatternFlag };
22965
+ return { args: remaining2, consumedPatternFlag };
22486
22966
  }
22487
22967
  function readCommandFileArgs(args, flagsWithValues, patternFlags = flagsWithValues) {
22488
22968
  const stripped = stripSimpleOptions(args, flagsWithValues, patternFlags);
@@ -22493,7 +22973,7 @@ function isPlausibleReadPath(argument) {
22493
22973
  if (COMMAND_SEPARATORS.has(argument)) return false;
22494
22974
  if (/[\0*?[{}`$]/.test(argument)) return false;
22495
22975
  if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(argument)) return false;
22496
- return path13.posix.isAbsolute(argument) || path13.win32.isAbsolute(argument) || argument.startsWith("~/") || argument.startsWith("./") || argument.startsWith("../") || argument.includes("/") || /\.[A-Za-z0-9][A-Za-z0-9_-]*$/.test(argument) || /^[A-Z][A-Za-z0-9_.-]*$/.test(argument);
22976
+ return path15.posix.isAbsolute(argument) || path15.win32.isAbsolute(argument) || argument.startsWith("~/") || argument.startsWith("./") || argument.startsWith("../") || argument.includes("/") || /\.[A-Za-z0-9][A-Za-z0-9_-]*$/.test(argument) || /^[A-Z][A-Za-z0-9_.-]*$/.test(argument);
22497
22977
  }
22498
22978
  function filePathFromSegment(tokens) {
22499
22979
  const commandTokens = [...tokens];
@@ -22564,7 +23044,7 @@ function extractReadFilePathFromCommand(command) {
22564
23044
 
22565
23045
  // ../../packages/agent-targets/src/hooks/normalize.ts
22566
23046
  var MAX_EXTRACTED_TEXT_BYTES = 64 * 1024;
22567
- function isRecord(value) {
23047
+ function isRecord3(value) {
22568
23048
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
22569
23049
  }
22570
23050
  function firstString(record2, keys) {
@@ -22611,7 +23091,7 @@ function boundedExtractedText(value, field) {
22611
23091
  if (value === void 0) return void 0;
22612
23092
  const originalBytes = Buffer.byteLength(value, "utf8");
22613
23093
  if (originalBytes <= MAX_EXTRACTED_TEXT_BYTES) return value;
22614
- const digest = createHash2("sha256").update(value).digest("hex");
23094
+ const digest = createHash3("sha256").update(value).digest("hex");
22615
23095
  const marker = `
22616
23096
  [fml:${field}:truncated originalBytes=${originalBytes} sha256=${digest}]`;
22617
23097
  return `${utf8Prefix(
@@ -22622,7 +23102,7 @@ function boundedExtractedText(value, field) {
22622
23102
  function nestedRecord(record2, keys) {
22623
23103
  for (const key of keys) {
22624
23104
  const value = record2[key];
22625
- if (isRecord(value)) return value;
23105
+ if (isRecord3(value)) return value;
22626
23106
  }
22627
23107
  return void 0;
22628
23108
  }
@@ -22680,9 +23160,9 @@ function grokEventType(eventType) {
22680
23160
  }
22681
23161
  function geminiUserPrompt(payload) {
22682
23162
  const request = payload.llm_request;
22683
- if (!isRecord(request) || !Array.isArray(request.messages)) return void 0;
23163
+ if (!isRecord3(request) || !Array.isArray(request.messages)) return void 0;
22684
23164
  const userMessage = [...request.messages].reverse().find(
22685
- (message) => isRecord(message) && message.role === "user"
23165
+ (message) => isRecord3(message) && message.role === "user"
22686
23166
  );
22687
23167
  if (!userMessage) return void 0;
22688
23168
  if (typeof userMessage.content === "string") {
@@ -22690,7 +23170,7 @@ function geminiUserPrompt(payload) {
22690
23170
  }
22691
23171
  if (!Array.isArray(userMessage.content)) return void 0;
22692
23172
  const text = userMessage.content.filter(
22693
- (part) => isRecord(part) && part.type === "text" && typeof part.text === "string"
23173
+ (part) => isRecord3(part) && part.type === "text" && typeof part.text === "string"
22694
23174
  ).map((part) => part.text).join("\n");
22695
23175
  return text || void 0;
22696
23176
  }
@@ -22789,51 +23269,15 @@ init_token_store();
22789
23269
  init_dirs();
22790
23270
 
22791
23271
  // src/repo.ts
22792
- import { execFile, execFileSync as execFileSync2 } from "node:child_process";
22793
- var REPO_CACHE = /* @__PURE__ */ new Map();
22794
- var GIT_TIMEOUT_MS = 5e3;
22795
- function cachedRepo(cwd) {
22796
- const entry = REPO_CACHE.get(cwd);
22797
- if (!entry) return { hit: false };
22798
- if (entry.expiresAtMs <= Date.now()) {
22799
- REPO_CACHE.delete(cwd);
22800
- return { hit: false };
22801
- }
22802
- REPO_CACHE.delete(cwd);
22803
- REPO_CACHE.set(cwd, entry);
22804
- return { hit: true, value: entry.value };
22805
- }
22806
- function git(cwd, args, timeoutMs) {
22807
- try {
22808
- return execFileSync2("git", ["-C", cwd, ...args], {
22809
- encoding: "utf8",
22810
- timeout: timeoutMs,
22811
- stdio: ["ignore", "pipe", "ignore"],
22812
- windowsHide: true
22813
- }).trim() || null;
22814
- } catch {
22815
- return null;
22816
- }
22817
- }
22818
- function repositorySlug(remote) {
22819
- const ssh = remote.match(/^[^@]+@[^:]+:([^/]+\/[^/]+?)(?:\.git)?$/);
22820
- if (ssh?.[1]) return ssh[1].toLowerCase();
22821
- try {
22822
- const url2 = new URL(remote);
22823
- const slug = url2.pathname.replace(/^\/+|\/+$/g, "").replace(/\.git$/, "");
22824
- return /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(slug) ? slug.toLowerCase() : null;
22825
- } catch {
22826
- return null;
22827
- }
22828
- }
22829
- function resolveRepositorySlugFromCwd(cwd, timeoutMs = GIT_TIMEOUT_MS) {
22830
- const cached2 = cachedRepo(cwd);
22831
- if (cached2.hit) return cached2.value?.repo ?? null;
22832
- const remote = git(cwd, ["remote", "get-url", "origin"], timeoutMs);
22833
- return remote ? repositorySlug(remote) : null;
23272
+ import path20 from "node:path";
23273
+ init_dirs();
23274
+ var CACHE_FILE = path20.join(FML_OBSERVER_DIR, GIT_STATE_CACHE_FILENAME);
23275
+ var resolver = createGitStateResolver({ cacheFile: CACHE_FILE });
23276
+ function resolveGitStateFromCwd(cwd, timeoutMs = 1e3) {
23277
+ return resolver.resolve(cwd, { timeoutMs });
22834
23278
  }
22835
- function resolveRepositoryRootFromCwd(cwd, timeoutMs = GIT_TIMEOUT_MS) {
22836
- return git(cwd, ["rev-parse", "--show-toplevel"], timeoutMs);
23279
+ function resolveRepositoryRootFromCwd(cwd, timeoutMs = 1e3) {
23280
+ return resolveGitStateFromCwd(cwd, timeoutMs)?.worktreeRoot ?? null;
22837
23281
  }
22838
23282
 
22839
23283
  // src/hooks/sync-loop.ts
@@ -22918,14 +23362,14 @@ function sameDestination(left, right) {
22918
23362
  return left.environment === right.environment && left.targetUrl === right.targetUrl && left.destinationIdentity === right.destinationIdentity;
22919
23363
  }
22920
23364
  function hookContextInjectionId(target, syncId) {
22921
- const digest = createHash5("sha256").update(`${INJECTION_ID_VERSION}\0${target}\0${syncId}`).digest("base64url");
23365
+ const digest = createHash6("sha256").update(`${INJECTION_ID_VERSION}\0${target}\0${syncId}`).digest("base64url");
22922
23366
  return `${INJECTION_ID_VERSION}_${digest}`;
22923
23367
  }
22924
23368
  function canonicalHookContextFilePath(filePath, cwd, repositoryRoot) {
22925
23369
  const windows = [filePath, cwd, repositoryRoot].some(
22926
23370
  (value) => value !== void 0 && value !== null && /^[A-Za-z]:[\\/]/.test(value)
22927
23371
  );
22928
- const pathApi = windows ? path18.win32 : path18.posix;
23372
+ const pathApi = windows ? path21.win32 : path21.posix;
22929
23373
  const normalized = pathApi.normalize(filePath);
22930
23374
  const resolved = pathApi.isAbsolute(normalized) ? normalized : cwd && pathApi.isAbsolute(cwd) ? pathApi.resolve(cwd, normalized) : normalized;
22931
23375
  if (repositoryRoot && pathApi.isAbsolute(resolved)) {
@@ -23085,16 +23529,16 @@ function wrapReceiptDetail(label, detail) {
23085
23529
  const firstPrefix = `\u2502 ${label}: `;
23086
23530
  const continuationPrefix = "\u2502 ";
23087
23531
  const lines = [];
23088
- let remaining = detail;
23532
+ let remaining2 = detail;
23089
23533
  let prefix = firstPrefix;
23090
- while (remaining) {
23534
+ while (remaining2) {
23091
23535
  const available = RECEIPT_LINE_WIDTH - receiptDisplayWidth(prefix);
23092
- if (receiptDisplayWidth(remaining) <= available) {
23093
- lines.push(`${prefix}${remaining}`);
23536
+ if (receiptDisplayWidth(remaining2) <= available) {
23537
+ lines.push(`${prefix}${remaining2}`);
23094
23538
  break;
23095
23539
  }
23096
- const tokenEnd = remaining.search(/\s/u);
23097
- const token = tokenEnd === -1 ? remaining : remaining.slice(0, tokenEnd);
23540
+ const tokenEnd = remaining2.search(/\s/u);
23541
+ const token = tokenEnd === -1 ? remaining2 : remaining2.slice(0, tokenEnd);
23098
23542
  const inlineCodeToken = splitInlineCodeToken(token);
23099
23543
  const inlineCodeWidth = inlineCodeToken ? receiptDisplayWidth(inlineCodeToken.inlineCode) + receiptDisplayWidth(inlineCodeToken.trailingPunctuation) : 0;
23100
23544
  if (inlineCodeToken && inlineCodeWidth > available) {
@@ -23106,7 +23550,7 @@ function wrapReceiptDetail(label, detail) {
23106
23550
  codeWidth
23107
23551
  )}${inlineCodeToken.trailingPunctuation}`
23108
23552
  );
23109
- remaining = tokenEnd === -1 ? "" : remaining.slice(tokenEnd).trimStart();
23553
+ remaining2 = tokenEnd === -1 ? "" : remaining2.slice(tokenEnd).trimStart();
23110
23554
  } else {
23111
23555
  lines.push(
23112
23556
  `${prefix}${truncateInlineCodeToken(
@@ -23114,13 +23558,13 @@ function wrapReceiptDetail(label, detail) {
23114
23558
  available
23115
23559
  )}`
23116
23560
  );
23117
- remaining = `${inlineCodeToken.trailingPunctuation}${tokenEnd === -1 ? "" : ` ${remaining.slice(tokenEnd).trimStart()}`}`;
23561
+ remaining2 = `${inlineCodeToken.trailingPunctuation}${tokenEnd === -1 ? "" : ` ${remaining2.slice(tokenEnd).trimStart()}`}`;
23118
23562
  }
23119
23563
  } else {
23120
- const { head, tail } = splitAtDisplayWidth(remaining, available);
23564
+ const { head, tail } = splitAtDisplayWidth(remaining2, available);
23121
23565
  if (!head) return [];
23122
23566
  lines.push(`${prefix}${head}`);
23123
- remaining = tail;
23567
+ remaining2 = tail;
23124
23568
  }
23125
23569
  prefix = continuationPrefix;
23126
23570
  }
@@ -23254,17 +23698,17 @@ function absoluteEnvPath(value) {
23254
23698
  const trimmed = value.trim();
23255
23699
  if (trimmed === "~") return os2.homedir();
23256
23700
  if (trimmed.startsWith("~/") || trimmed.startsWith("~\\")) {
23257
- return path19.join(os2.homedir(), trimmed.slice(2));
23701
+ return path22.join(os2.homedir(), trimmed.slice(2));
23258
23702
  }
23259
- return path19.isAbsolute(trimmed) ? path19.normalize(trimmed) : void 0;
23703
+ return path22.isAbsolute(trimmed) ? path22.normalize(trimmed) : void 0;
23260
23704
  }
23261
23705
  function observerDirectory() {
23262
23706
  const fmlHome = absoluteEnvPath(process.env.FML_HOME);
23263
- return absoluteEnvPath(process.env.FML_OBSERVER_DIR) ?? absoluteEnvPath(process.env.PANOPTICON_HOME) ?? (fmlHome ? path19.join(fmlHome, "panopticon") : path19.join(os2.homedir(), ".fml", "panopticon"));
23707
+ return absoluteEnvPath(process.env.FML_OBSERVER_DIR) ?? absoluteEnvPath(process.env.PANOPTICON_HOME) ?? (fmlHome ? path22.join(fmlHome, "panopticon") : path22.join(os2.homedir(), ".fml", "panopticon"));
23264
23708
  }
23265
23709
  function readObserverEndpoint(filePath) {
23266
23710
  try {
23267
- const parsed = JSON.parse(fs9.readFileSync(filePath, "utf8"));
23711
+ const parsed = JSON.parse(fs10.readFileSync(filePath, "utf8"));
23268
23712
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
23269
23713
  return null;
23270
23714
  }
@@ -23279,15 +23723,15 @@ function readObserverEndpoint(filePath) {
23279
23723
  }
23280
23724
  function readObserverAuthToken(filePath) {
23281
23725
  try {
23282
- return fs9.readFileSync(filePath, "utf8").trim() || null;
23726
+ return fs10.readFileSync(filePath, "utf8").trim() || null;
23283
23727
  } catch {
23284
23728
  return null;
23285
23729
  }
23286
23730
  }
23287
23731
  async function deliver(event, repository) {
23288
23732
  const directory = observerDirectory();
23289
- const endpoint = readObserverEndpoint(path19.join(directory, "endpoint.json"));
23290
- const authToken = readObserverAuthToken(path19.join(directory, "auth-token"));
23733
+ const endpoint = readObserverEndpoint(path22.join(directory, "endpoint.json"));
23734
+ const authToken = readObserverAuthToken(path22.join(directory, "auth-token"));
23291
23735
  if (!endpoint || !authToken) return;
23292
23736
  const host = endpoint.host === "::1" ? "[::1]" : endpoint.host;
23293
23737
  try {
@@ -23299,7 +23743,10 @@ async function deliver(event, repository) {
23299
23743
  },
23300
23744
  body: JSON.stringify({
23301
23745
  target: "pi",
23302
- payload: event,
23746
+ payload: {
23747
+ ...event,
23748
+ [GIT_STATE_PAYLOAD_KEY]: event.cwd ? resolveGitStateFromCwd(event.cwd, 250) : null
23749
+ },
23303
23750
  ...event.cwd ? { cwd: event.cwd } : {},
23304
23751
  ...repository ? { repository } : {}
23305
23752
  }),
@@ -23357,16 +23804,16 @@ var RECEIPT_GRAPHEME_SEGMENTER = new Intl.Segmenter(void 0, {
23357
23804
  });
23358
23805
  function wrapReceiptLine(text, width) {
23359
23806
  if (width < 1) return [""];
23360
- let remaining = text.trim();
23361
- if (!remaining) return [""];
23807
+ let remaining2 = text.trim();
23808
+ if (!remaining2) return [""];
23362
23809
  const lines = [];
23363
- while (remaining) {
23810
+ while (remaining2) {
23364
23811
  let line = "";
23365
23812
  let lineWidth = 0;
23366
23813
  let end = 0;
23367
23814
  let whitespaceEnd = 0;
23368
23815
  for (const { segment, index } of RECEIPT_GRAPHEME_SEGMENTER.segment(
23369
- remaining
23816
+ remaining2
23370
23817
  )) {
23371
23818
  const segmentWidth = receiptDisplayWidth(segment);
23372
23819
  if (segmentWidth > width) {
@@ -23381,16 +23828,16 @@ function wrapReceiptLine(text, width) {
23381
23828
  end = index + segment.length;
23382
23829
  if (/^\s$/u.test(segment)) whitespaceEnd = end;
23383
23830
  }
23384
- if (end === remaining.length) {
23831
+ if (end === remaining2.length) {
23385
23832
  lines.push(line.trimEnd());
23386
23833
  break;
23387
23834
  }
23388
23835
  if (whitespaceEnd > 0) {
23389
- lines.push(remaining.slice(0, whitespaceEnd).trimEnd());
23390
- remaining = remaining.slice(whitespaceEnd).trimStart();
23836
+ lines.push(remaining2.slice(0, whitespaceEnd).trimEnd());
23837
+ remaining2 = remaining2.slice(whitespaceEnd).trimStart();
23391
23838
  } else {
23392
23839
  lines.push(line);
23393
- remaining = remaining.slice(end);
23840
+ remaining2 = remaining2.slice(end);
23394
23841
  }
23395
23842
  }
23396
23843
  return lines;
@@ -23430,7 +23877,7 @@ var PiReceiptComponent = class {
23430
23877
  }
23431
23878
  };
23432
23879
  function fmlPiExtension(pi) {
23433
- let sessionId = randomUUID4();
23880
+ let sessionId = randomUUID5();
23434
23881
  let initializedSessionId = null;
23435
23882
  let cwd;
23436
23883
  let repository = null;
@@ -23555,7 +24002,7 @@ function fmlPiExtension(pi) {
23555
24002
  }
23556
24003
  repository = null;
23557
24004
  try {
23558
- repository = cwd ? resolveRepositorySlugFromCwd(cwd, 250) : null;
24005
+ repository = cwd ? resolveGitStateFromCwd(cwd, 250)?.repository ?? null : null;
23559
24006
  } catch {
23560
24007
  }
23561
24008
  if (newIdentity && evidence?.status === "absent" && (event.reason === "startup" || event.reason === "new" || event.reason === "resume") && entries.every(
@@ -23564,7 +24011,8 @@ function fmlPiExtension(pi) {
23564
24011
  const origin = PiRepositoryOriginDataSchema.safeParse({
23565
24012
  version: 1,
23566
24013
  repository,
23567
- cwd
24014
+ cwd,
24015
+ gitState: cwd ? resolveGitStateFromCwd(cwd, 250) : null
23568
24016
  });
23569
24017
  if (origin.success) {
23570
24018
  capturedRepository = origin.data.repository;