greprag 5.76.0 → 5.78.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -24,9 +24,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
 
25
25
  // src/opencode-plugin.ts
26
26
  var crypto5 = __toESM(require("crypto"));
27
- var fs7 = __toESM(require("fs"));
28
- var os2 = __toESM(require("os"));
29
- var path7 = __toESM(require("path"));
27
+ var fs8 = __toESM(require("fs"));
28
+ var os3 = __toESM(require("os"));
29
+ var path8 = __toESM(require("path"));
30
30
 
31
31
  // src/opencode-plugin-helpers.ts
32
32
  var crypto = __toESM(require("crypto"));
@@ -2438,15 +2438,15 @@ var loadoutRegistrarModule = {
2438
2438
  // src/commands/delivery-reminder.ts
2439
2439
  function coordinationLine(platform) {
2440
2440
  if (platform === "codex") {
2441
- return "Codex: at the merge/deploy gate, call codex_app.list_threads unfiltered, filter to peers in the same repo, then use codex_app.send_message_to_thread once for ready or overlapping work. Delivery owner: rename this task `DEPLOY: <existing title>`; transfer the prefix on handoff; remove the prefix when delivery is done.";
2441
+ return "Codex: at the merge/deploy gate, call codex_app.list_threads unfiltered, filter to peers in the same repo, and inspect their current summaries/status. Use codex_app.send_message_to_thread only for visible evidence of an unlanded ready commit, overlapping integration, or a concrete blocker. Peers with nothing actionable stay silent; do not solicit or send negative acknowledgements or routine completion follow-ups. Delivery owner: rename this task `DEPLOY: <existing title>` for operator visibility only; transfer the prefix on handoff; remove it before reporting delivery complete or abandoned.";
2442
2442
  }
2443
2443
  if (platform === "claude-code") {
2444
- return "Claude Code: at the merge/deploy gate, discover peers in the same repo and send one concise delivery notice to peers with ready or overlapping work.";
2444
+ return "Claude Code: at the merge/deploy gate, inspect peers in the same repo and send one concise delivery notice only for visible evidence of an unlanded ready commit, overlapping integration, or a concrete blocker. Peers with nothing actionable stay silent; do not solicit or send negative acknowledgements or routine completion follow-ups.";
2445
2445
  }
2446
2446
  if (platform === "opencode") {
2447
- return "OpenCode: at the merge/deploy gate, use the GrepRAG project/session registry and send one concise delivery notice to peers with ready or overlapping work.";
2447
+ return "OpenCode: at the merge/deploy gate, use the GrepRAG project/session registry and send one concise notice only for visible evidence of an unlanded ready commit, overlapping integration, or a concrete blocker. Peers with nothing actionable stay silent; do not solicit or send negative acknowledgements or routine completion follow-ups.";
2448
2448
  }
2449
- return "At the merge/deploy gate, coordinate once with peers in the same repo who have ready or overlapping work.";
2449
+ return "At the merge/deploy gate, inspect peers in the same repo and send one concise notice only for visible evidence of an unlanded ready commit, overlapping integration, or a concrete blocker. Peers with nothing actionable stay silent; do not solicit or send negative acknowledgements or routine completion follow-ups.";
2450
2450
  }
2451
2451
  function buildDeliveryAnnounce(env) {
2452
2452
  if (!env.deliveryRepo)
@@ -2456,7 +2456,7 @@ function buildDeliveryAnnounce(env) {
2456
2456
  `[DELIVERY \u2014 ${project}]`,
2457
2457
  "Commit useful, passing work.",
2458
2458
  coordinationLine(env.platform),
2459
- "Include ready peer commits and clear known blockers, then merge to the default branch and deploy immediately. Missing replies and unfinished peer work do not block delivery.",
2459
+ "Include visible ready peer commits and clear known blockers, then merge to the default branch and deploy immediately. Silence means nothing actionable and does not block delivery.",
2460
2460
  "Use this repo's delivery profile and verify production."
2461
2461
  ].join("\n");
2462
2462
  }
@@ -2467,6 +2467,14 @@ var deliveryControlModule = {
2467
2467
  reminder: () => null
2468
2468
  };
2469
2469
 
2470
+ // src/commands/persona-reminder.ts
2471
+ var personaAnnounceModule = {
2472
+ id: "persona-announce",
2473
+ detect: () => ({ tier: "silent" }),
2474
+ announce: (env) => env.personaAnnounce || null,
2475
+ reminder: () => null
2476
+ };
2477
+
2470
2478
  // src/commands/reminder-registry.ts
2471
2479
  var REGISTRY = [
2472
2480
  osPrimerModule,
@@ -2488,6 +2496,8 @@ var REGISTRY = [
2488
2496
  // active project procedures, rendered generically from operator-owned installs
2489
2497
  deliveryControlModule,
2490
2498
  // repo-scoped announce-first Delivery System pilot (all harnesses)
2499
+ personaAnnounceModule,
2500
+ // tenant-scoped Persona, shared across projects/harnesses
2491
2501
  loadoutRegistrarModule,
2492
2502
  // equipped loadouts: one advisor announce each + per-turn keyword Match (docs/loadout.md)
2493
2503
  setupWarningModule,
@@ -2562,6 +2572,216 @@ function collectAnnounces(env, registry = REGISTRY) {
2562
2572
  return out;
2563
2573
  }
2564
2574
 
2575
+ // src/app-settings.ts
2576
+ var fs3 = __toESM(require("node:fs"));
2577
+ var os2 = __toESM(require("node:os"));
2578
+ var path3 = __toESM(require("node:path"));
2579
+
2580
+ // src/project-anchor.ts
2581
+ var path2 = __toESM(require("path"));
2582
+ var fs2 = __toESM(require("fs"));
2583
+ var crypto2 = __toESM(require("crypto"));
2584
+ var os = __toESM(require("os"));
2585
+ var ANCHOR_DIR = ".greprag";
2586
+ var ANCHOR_FILE = "project.json";
2587
+ var LEGACY_ANCHOR_DIR = ".claude";
2588
+ function anchorPathIn(dir) {
2589
+ return path2.join(dir, ANCHOR_DIR, ANCHOR_FILE);
2590
+ }
2591
+ function legacyAnchorPathIn(dir) {
2592
+ return path2.join(dir, LEGACY_ANCHOR_DIR, ANCHOR_FILE);
2593
+ }
2594
+ function globalAnchorPath() {
2595
+ return anchorPathIn(os.homedir());
2596
+ }
2597
+ function legacyGlobalAnchorPath() {
2598
+ return legacyAnchorPathIn(os.homedir());
2599
+ }
2600
+ function findExistingAnchor(startDir) {
2601
+ const homeAnchor = globalAnchorPath();
2602
+ const legacyHomeAnchor = legacyGlobalAnchorPath();
2603
+ let dir = path2.resolve(startDir);
2604
+ while (true) {
2605
+ const candidate = anchorPathIn(dir);
2606
+ if (candidate !== homeAnchor && fs2.existsSync(candidate))
2607
+ return candidate;
2608
+ const legacyCandidate = legacyAnchorPathIn(dir);
2609
+ if (legacyCandidate !== legacyHomeAnchor && fs2.existsSync(legacyCandidate))
2610
+ return legacyCandidate;
2611
+ const parent = path2.dirname(dir);
2612
+ if (parent === dir)
2613
+ return null;
2614
+ dir = parent;
2615
+ }
2616
+ }
2617
+ function isEphemeralCwd2(cwd) {
2618
+ const norm = path2.resolve(cwd).replace(/\\/g, "/").toLowerCase();
2619
+ if (norm.includes("/appdata/roaming/claude/local-agent-mode-sessions/"))
2620
+ return true;
2621
+ if (norm.includes("/appdata/local/claude/local-agent-mode-sessions/"))
2622
+ return true;
2623
+ if (norm.startsWith("/tmp/"))
2624
+ return true;
2625
+ if (norm.startsWith("/var/tmp/"))
2626
+ return true;
2627
+ if (norm.startsWith("/private/tmp/"))
2628
+ return true;
2629
+ return false;
2630
+ }
2631
+ function computeGitDerivedProjectId2(workingDir) {
2632
+ try {
2633
+ const out = safeExecSync("git rev-list --max-parents=0 HEAD", {
2634
+ cwd: workingDir,
2635
+ encoding: "utf-8",
2636
+ stdio: ["pipe", "pipe", "pipe"]
2637
+ });
2638
+ const roots = out.trim().split(/\s+/).filter(Boolean).sort();
2639
+ if (roots.length === 0)
2640
+ return null;
2641
+ const hash = crypto2.createHash("sha256").update(roots.join("\n")).digest("hex");
2642
+ return [
2643
+ hash.slice(0, 8),
2644
+ hash.slice(8, 12),
2645
+ "4" + hash.slice(13, 16),
2646
+ // version 4 nibble
2647
+ "8" + hash.slice(17, 20),
2648
+ // variant nibble
2649
+ hash.slice(20, 32)
2650
+ ].join("-");
2651
+ } catch {
2652
+ return null;
2653
+ }
2654
+ }
2655
+ function deterministicProjectId2(workingDir) {
2656
+ const normalized = path2.resolve(workingDir).toLowerCase();
2657
+ const hash = crypto2.createHash("sha256").update(normalized).digest("hex");
2658
+ return [
2659
+ hash.slice(0, 8),
2660
+ hash.slice(8, 12),
2661
+ "4" + hash.slice(13, 16),
2662
+ // version 4 nibble
2663
+ "8" + hash.slice(17, 20),
2664
+ // variant nibble
2665
+ hash.slice(20, 32)
2666
+ ].join("-");
2667
+ }
2668
+ function tryReadAnchorFileContents(filePath) {
2669
+ try {
2670
+ const raw = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
2671
+ const notifyRaw = raw.inbox_notify;
2672
+ const inboxNotify = notifyRaw === "off" || notifyRaw === "session_start_only" ? notifyRaw : "every_turn";
2673
+ return {
2674
+ projectId: raw.project_id,
2675
+ projectName: raw.project_name,
2676
+ created: raw.created,
2677
+ memoryCapture: raw.memory_capture !== false,
2678
+ sessionStartRecap: raw.session_start_recap !== false,
2679
+ inboxNotify,
2680
+ emailDir: typeof raw.email_dir === "string" ? raw.email_dir : void 0,
2681
+ emailAutosave: raw.email_autosave === true,
2682
+ role: typeof raw.role === "string" ? raw.role : void 0
2683
+ };
2684
+ } catch {
2685
+ return null;
2686
+ }
2687
+ }
2688
+ function readAnchor2(cwd) {
2689
+ const existingPath = findExistingAnchor(cwd);
2690
+ const fileContents = existingPath ? tryReadAnchorFileContents(existingPath) : null;
2691
+ if (existingPath && fileContents && fileContents.projectId && fileContents.projectName) {
2692
+ return {
2693
+ projectId: fileContents.projectId,
2694
+ projectName: fileContents.projectName,
2695
+ initialized: true,
2696
+ source: "file",
2697
+ anchorPath: existingPath,
2698
+ created: fileContents.created,
2699
+ memoryCapture: fileContents.memoryCapture,
2700
+ sessionStartRecap: fileContents.sessionStartRecap,
2701
+ inboxNotify: fileContents.inboxNotify,
2702
+ emailDir: fileContents.emailDir,
2703
+ emailAutosave: fileContents.emailAutosave,
2704
+ role: fileContents.role
2705
+ };
2706
+ }
2707
+ const gitId = computeGitDerivedProjectId2(cwd);
2708
+ if (gitId) {
2709
+ const root2 = path2.resolve(cwd);
2710
+ return {
2711
+ projectId: gitId,
2712
+ projectName: fileContents?.projectName || path2.basename(root2).toLowerCase(),
2713
+ initialized: true,
2714
+ source: "git",
2715
+ anchorPath: existingPath || anchorPathIn(root2),
2716
+ created: fileContents?.created,
2717
+ memoryCapture: fileContents?.memoryCapture ?? true,
2718
+ sessionStartRecap: fileContents?.sessionStartRecap ?? true,
2719
+ inboxNotify: fileContents?.inboxNotify ?? "every_turn",
2720
+ emailDir: fileContents?.emailDir,
2721
+ emailAutosave: fileContents?.emailAutosave,
2722
+ role: fileContents?.role
2723
+ };
2724
+ }
2725
+ if (isEphemeralCwd2(cwd)) {
2726
+ const globalPath = fs2.existsSync(globalAnchorPath()) ? globalAnchorPath() : legacyGlobalAnchorPath();
2727
+ const globalContents = tryReadAnchorFileContents(globalPath);
2728
+ if (globalContents && globalContents.projectId && globalContents.projectName) {
2729
+ return {
2730
+ projectId: globalContents.projectId,
2731
+ projectName: globalContents.projectName,
2732
+ initialized: true,
2733
+ source: "global",
2734
+ anchorPath: globalPath,
2735
+ created: globalContents.created,
2736
+ memoryCapture: globalContents.memoryCapture,
2737
+ sessionStartRecap: globalContents.sessionStartRecap,
2738
+ inboxNotify: globalContents.inboxNotify,
2739
+ emailDir: globalContents.emailDir,
2740
+ emailAutosave: globalContents.emailAutosave,
2741
+ role: globalContents.role
2742
+ };
2743
+ }
2744
+ }
2745
+ const root = path2.resolve(cwd);
2746
+ return {
2747
+ projectId: deterministicProjectId2(root),
2748
+ projectName: fileContents?.projectName || path2.basename(root).toLowerCase(),
2749
+ initialized: false,
2750
+ source: "hash",
2751
+ anchorPath: existingPath || anchorPathIn(root),
2752
+ created: fileContents?.created,
2753
+ memoryCapture: fileContents?.memoryCapture ?? true,
2754
+ sessionStartRecap: fileContents?.sessionStartRecap ?? true,
2755
+ inboxNotify: fileContents?.inboxNotify ?? "every_turn",
2756
+ emailDir: fileContents?.emailDir,
2757
+ emailAutosave: fileContents?.emailAutosave,
2758
+ role: fileContents?.role
2759
+ };
2760
+ }
2761
+
2762
+ // src/app-settings.ts
2763
+ function readJson(file) {
2764
+ try {
2765
+ const parsed = JSON.parse(fs3.readFileSync(file, "utf8"));
2766
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
2767
+ } catch {
2768
+ return {};
2769
+ }
2770
+ }
2771
+ function localAppSettingsPath(homeDir = os2.homedir()) {
2772
+ return path3.join(homeDir, ".greprag", "settings.json");
2773
+ }
2774
+ function readLocalAppSettings(homeDir = os2.homedir()) {
2775
+ const raw = readJson(localAppSettingsPath(homeDir));
2776
+ const interrupts = raw.interrupts && typeof raw.interrupts === "object" ? raw.interrupts : {};
2777
+ const disabled = Array.isArray(interrupts.disabled) ? [...new Set(interrupts.disabled.filter((id) => typeof id === "string" && /^[a-z0-9-]{1,80}$/.test(id)))] : [];
2778
+ return { version: 1, interrupts: { disabled } };
2779
+ }
2780
+ function filterEnabledInterrupts(modules, settings = readLocalAppSettings()) {
2781
+ const disabled = new Set(settings.interrupts.disabled);
2782
+ return modules.filter((module2) => !disabled.has(module2.id));
2783
+ }
2784
+
2565
2785
  // src/commands/opencode-interrupt.ts
2566
2786
  var opencodeRegistry = harnessModules("opencode");
2567
2787
  var OPENCODE_QUOTA_WAITLIST_ANNOUNCE = [
@@ -2591,19 +2811,23 @@ function buildOpenCodeEnv(params) {
2591
2811
  corpusApiDocs: params.corpusApiDocs ?? [],
2592
2812
  updateAvailable: params.updateAvailable ?? null,
2593
2813
  procedureAnnounces: params.procedureAnnounces ?? [],
2594
- mirroredSkills: params.mirroredSkills
2814
+ mirroredSkills: params.mirroredSkills,
2815
+ personaAnnounce: params.personaAnnounce ?? null
2595
2816
  };
2596
2817
  }
2597
2818
  function getOpenCodeAnnounces(env) {
2598
- return [...collectAnnounces(env, opencodeRegistry), OPENCODE_QUOTA_WAITLIST_ANNOUNCE];
2819
+ return [...collectAnnounces(env, filterEnabledInterrupts(opencodeRegistry)), OPENCODE_QUOTA_WAITLIST_ANNOUNCE];
2820
+ }
2821
+ function getOpenCodePersistentAnnounces(env) {
2822
+ return collectAnnounces(env, filterEnabledInterrupts(opencodeRegistry.filter((m) => m.id === "persona-announce")));
2599
2823
  }
2600
2824
  function getOpenCodeReminders(env) {
2601
- return collectReminders(env, opencodeRegistry);
2825
+ return collectReminders(env, filterEnabledInterrupts(opencodeRegistry));
2602
2826
  }
2603
2827
 
2604
2828
  // src/procedure.ts
2605
- var path2 = __toESM(require("path"));
2606
- var fs2 = __toESM(require("fs"));
2829
+ var path4 = __toESM(require("path"));
2830
+ var fs4 = __toESM(require("fs"));
2607
2831
 
2608
2832
  // src/delivery-lifecycle.ts
2609
2833
  var DELIVERY_LIFECYCLE_VERBS = [
@@ -2622,14 +2846,14 @@ function isDeliveryLifecycleVerb(verb) {
2622
2846
  var PROCEDURE_STORE_VERSION = "2";
2623
2847
  function stateDir() {
2624
2848
  const home = process.env.HOME || process.env.USERPROFILE || "";
2625
- return path2.join(home, ".greprag", "state");
2849
+ return path4.join(home, ".greprag", "state");
2626
2850
  }
2627
2851
  function procedureStorePath(projectId) {
2628
- return path2.join(stateDir(), `procedures-${projectId}.json`);
2852
+ return path4.join(stateDir(), `procedures-${projectId}.json`);
2629
2853
  }
2630
2854
  function readProcedureStore(projectId) {
2631
2855
  try {
2632
- const raw = fs2.readFileSync(procedureStorePath(projectId), "utf-8");
2856
+ const raw = fs4.readFileSync(procedureStorePath(projectId), "utf-8");
2633
2857
  const parsed = JSON.parse(raw);
2634
2858
  if (parsed && Array.isArray(parsed.procedures)) {
2635
2859
  return normalizeProcedureStore({
@@ -2862,191 +3086,9 @@ function activeProcedureAnnounces(store) {
2862
3086
  // src/procedure-runtime.ts
2863
3087
  var crypto4 = __toESM(require("crypto"));
2864
3088
 
2865
- // src/project-anchor.ts
2866
- var path3 = __toESM(require("path"));
2867
- var fs3 = __toESM(require("fs"));
2868
- var crypto2 = __toESM(require("crypto"));
2869
- var os = __toESM(require("os"));
2870
- var ANCHOR_DIR = ".greprag";
2871
- var ANCHOR_FILE = "project.json";
2872
- var LEGACY_ANCHOR_DIR = ".claude";
2873
- function anchorPathIn(dir) {
2874
- return path3.join(dir, ANCHOR_DIR, ANCHOR_FILE);
2875
- }
2876
- function legacyAnchorPathIn(dir) {
2877
- return path3.join(dir, LEGACY_ANCHOR_DIR, ANCHOR_FILE);
2878
- }
2879
- function globalAnchorPath() {
2880
- return anchorPathIn(os.homedir());
2881
- }
2882
- function legacyGlobalAnchorPath() {
2883
- return legacyAnchorPathIn(os.homedir());
2884
- }
2885
- function findExistingAnchor(startDir) {
2886
- const homeAnchor = globalAnchorPath();
2887
- const legacyHomeAnchor = legacyGlobalAnchorPath();
2888
- let dir = path3.resolve(startDir);
2889
- while (true) {
2890
- const candidate = anchorPathIn(dir);
2891
- if (candidate !== homeAnchor && fs3.existsSync(candidate))
2892
- return candidate;
2893
- const legacyCandidate = legacyAnchorPathIn(dir);
2894
- if (legacyCandidate !== legacyHomeAnchor && fs3.existsSync(legacyCandidate))
2895
- return legacyCandidate;
2896
- const parent = path3.dirname(dir);
2897
- if (parent === dir)
2898
- return null;
2899
- dir = parent;
2900
- }
2901
- }
2902
- function isEphemeralCwd2(cwd) {
2903
- const norm = path3.resolve(cwd).replace(/\\/g, "/").toLowerCase();
2904
- if (norm.includes("/appdata/roaming/claude/local-agent-mode-sessions/"))
2905
- return true;
2906
- if (norm.includes("/appdata/local/claude/local-agent-mode-sessions/"))
2907
- return true;
2908
- if (norm.startsWith("/tmp/"))
2909
- return true;
2910
- if (norm.startsWith("/var/tmp/"))
2911
- return true;
2912
- if (norm.startsWith("/private/tmp/"))
2913
- return true;
2914
- return false;
2915
- }
2916
- function computeGitDerivedProjectId2(workingDir) {
2917
- try {
2918
- const out = safeExecSync("git rev-list --max-parents=0 HEAD", {
2919
- cwd: workingDir,
2920
- encoding: "utf-8",
2921
- stdio: ["pipe", "pipe", "pipe"]
2922
- });
2923
- const roots = out.trim().split(/\s+/).filter(Boolean).sort();
2924
- if (roots.length === 0)
2925
- return null;
2926
- const hash = crypto2.createHash("sha256").update(roots.join("\n")).digest("hex");
2927
- return [
2928
- hash.slice(0, 8),
2929
- hash.slice(8, 12),
2930
- "4" + hash.slice(13, 16),
2931
- // version 4 nibble
2932
- "8" + hash.slice(17, 20),
2933
- // variant nibble
2934
- hash.slice(20, 32)
2935
- ].join("-");
2936
- } catch {
2937
- return null;
2938
- }
2939
- }
2940
- function deterministicProjectId2(workingDir) {
2941
- const normalized = path3.resolve(workingDir).toLowerCase();
2942
- const hash = crypto2.createHash("sha256").update(normalized).digest("hex");
2943
- return [
2944
- hash.slice(0, 8),
2945
- hash.slice(8, 12),
2946
- "4" + hash.slice(13, 16),
2947
- // version 4 nibble
2948
- "8" + hash.slice(17, 20),
2949
- // variant nibble
2950
- hash.slice(20, 32)
2951
- ].join("-");
2952
- }
2953
- function tryReadAnchorFileContents(filePath) {
2954
- try {
2955
- const raw = JSON.parse(fs3.readFileSync(filePath, "utf-8"));
2956
- const notifyRaw = raw.inbox_notify;
2957
- const inboxNotify = notifyRaw === "off" || notifyRaw === "session_start_only" ? notifyRaw : "every_turn";
2958
- return {
2959
- projectId: raw.project_id,
2960
- projectName: raw.project_name,
2961
- created: raw.created,
2962
- memoryCapture: raw.memory_capture !== false,
2963
- sessionStartRecap: raw.session_start_recap !== false,
2964
- inboxNotify,
2965
- emailDir: typeof raw.email_dir === "string" ? raw.email_dir : void 0,
2966
- emailAutosave: raw.email_autosave === true,
2967
- role: typeof raw.role === "string" ? raw.role : void 0
2968
- };
2969
- } catch {
2970
- return null;
2971
- }
2972
- }
2973
- function readAnchor2(cwd) {
2974
- const existingPath = findExistingAnchor(cwd);
2975
- const fileContents = existingPath ? tryReadAnchorFileContents(existingPath) : null;
2976
- if (existingPath && fileContents && fileContents.projectId && fileContents.projectName) {
2977
- return {
2978
- projectId: fileContents.projectId,
2979
- projectName: fileContents.projectName,
2980
- initialized: true,
2981
- source: "file",
2982
- anchorPath: existingPath,
2983
- created: fileContents.created,
2984
- memoryCapture: fileContents.memoryCapture,
2985
- sessionStartRecap: fileContents.sessionStartRecap,
2986
- inboxNotify: fileContents.inboxNotify,
2987
- emailDir: fileContents.emailDir,
2988
- emailAutosave: fileContents.emailAutosave,
2989
- role: fileContents.role
2990
- };
2991
- }
2992
- const gitId = computeGitDerivedProjectId2(cwd);
2993
- if (gitId) {
2994
- const root2 = path3.resolve(cwd);
2995
- return {
2996
- projectId: gitId,
2997
- projectName: fileContents?.projectName || path3.basename(root2).toLowerCase(),
2998
- initialized: true,
2999
- source: "git",
3000
- anchorPath: existingPath || anchorPathIn(root2),
3001
- created: fileContents?.created,
3002
- memoryCapture: fileContents?.memoryCapture ?? true,
3003
- sessionStartRecap: fileContents?.sessionStartRecap ?? true,
3004
- inboxNotify: fileContents?.inboxNotify ?? "every_turn",
3005
- emailDir: fileContents?.emailDir,
3006
- emailAutosave: fileContents?.emailAutosave,
3007
- role: fileContents?.role
3008
- };
3009
- }
3010
- if (isEphemeralCwd2(cwd)) {
3011
- const globalPath = fs3.existsSync(globalAnchorPath()) ? globalAnchorPath() : legacyGlobalAnchorPath();
3012
- const globalContents = tryReadAnchorFileContents(globalPath);
3013
- if (globalContents && globalContents.projectId && globalContents.projectName) {
3014
- return {
3015
- projectId: globalContents.projectId,
3016
- projectName: globalContents.projectName,
3017
- initialized: true,
3018
- source: "global",
3019
- anchorPath: globalPath,
3020
- created: globalContents.created,
3021
- memoryCapture: globalContents.memoryCapture,
3022
- sessionStartRecap: globalContents.sessionStartRecap,
3023
- inboxNotify: globalContents.inboxNotify,
3024
- emailDir: globalContents.emailDir,
3025
- emailAutosave: globalContents.emailAutosave,
3026
- role: globalContents.role
3027
- };
3028
- }
3029
- }
3030
- const root = path3.resolve(cwd);
3031
- return {
3032
- projectId: deterministicProjectId2(root),
3033
- projectName: fileContents?.projectName || path3.basename(root).toLowerCase(),
3034
- initialized: false,
3035
- source: "hash",
3036
- anchorPath: existingPath || anchorPathIn(root),
3037
- created: fileContents?.created,
3038
- memoryCapture: fileContents?.memoryCapture ?? true,
3039
- sessionStartRecap: fileContents?.sessionStartRecap ?? true,
3040
- inboxNotify: fileContents?.inboxNotify ?? "every_turn",
3041
- emailDir: fileContents?.emailDir,
3042
- emailAutosave: fileContents?.emailAutosave,
3043
- role: fileContents?.role
3044
- };
3045
- }
3046
-
3047
3089
  // src/procedure-watch.ts
3048
- var path4 = __toESM(require("path"));
3049
- var fs4 = __toESM(require("fs"));
3090
+ var path5 = __toESM(require("path"));
3091
+ var fs5 = __toESM(require("fs"));
3050
3092
  var TIER1_LEARN_TRIGGERS = [
3051
3093
  { verb: "deploy", triggers: ["deploy", "deploy the api", "deploy the worker", "redeploy"], steps: "", status: "seeded" },
3052
3094
  { verb: "push", triggers: ["push", "git push", "push to remote", "push it up", "push upstream"], steps: "", status: "seeded", destructive: true },
@@ -3079,24 +3121,24 @@ function openWatch(verb, phase, openedAt, shadowRunId) {
3079
3121
  }
3080
3122
  function stateDir2() {
3081
3123
  const home = process.env.HOME || process.env.USERPROFILE || "";
3082
- return path4.join(home, ".greprag", "state");
3124
+ return path5.join(home, ".greprag", "state");
3083
3125
  }
3084
3126
  function procedureWatchPath(projectId) {
3085
- return path4.join(stateDir2(), `procedure-watch-${projectId}.json`);
3127
+ return path5.join(stateDir2(), `procedure-watch-${projectId}.json`);
3086
3128
  }
3087
3129
  function hasProcedureWatch(projectId) {
3088
3130
  try {
3089
- return fs4.existsSync(procedureWatchPath(projectId));
3131
+ return fs5.existsSync(procedureWatchPath(projectId));
3090
3132
  } catch {
3091
3133
  return false;
3092
3134
  }
3093
3135
  }
3094
3136
  function writeProcedureWatch(projectId, watch) {
3095
3137
  const file = procedureWatchPath(projectId);
3096
- const dir = path4.dirname(file);
3097
- if (!fs4.existsSync(dir))
3098
- fs4.mkdirSync(dir, { recursive: true });
3099
- fs4.writeFileSync(file, JSON.stringify(watch, null, 2) + "\n");
3138
+ const dir = path5.dirname(file);
3139
+ if (!fs5.existsSync(dir))
3140
+ fs5.mkdirSync(dir, { recursive: true });
3141
+ fs5.writeFileSync(file, JSON.stringify(watch, null, 2) + "\n");
3100
3142
  }
3101
3143
  function openWatchIfIdle(projectId, verb, phase, shadowRunId) {
3102
3144
  if (hasProcedureWatch(projectId))
@@ -3107,20 +3149,20 @@ function openWatchIfIdle(projectId, verb, phase, shadowRunId) {
3107
3149
 
3108
3150
  // src/procedure-shadow.ts
3109
3151
  var crypto3 = __toESM(require("crypto"));
3110
- var fs5 = __toESM(require("fs"));
3111
- var path5 = __toESM(require("path"));
3152
+ var fs6 = __toESM(require("fs"));
3153
+ var path6 = __toESM(require("path"));
3112
3154
  function stateDir3() {
3113
3155
  const home = process.env.HOME || process.env.USERPROFILE || "";
3114
- return path5.join(home, ".greprag", "state");
3156
+ return path6.join(home, ".greprag", "state");
3115
3157
  }
3116
3158
  function procedureShadowPath(projectId) {
3117
- return path5.join(stateDir3(), `procedure-shadow-${projectId}.jsonl`);
3159
+ return path6.join(stateDir3(), `procedure-shadow-${projectId}.jsonl`);
3118
3160
  }
3119
3161
  function appendShadowEvent(projectId, event) {
3120
3162
  try {
3121
3163
  const file = procedureShadowPath(projectId);
3122
- fs5.mkdirSync(path5.dirname(file), { recursive: true });
3123
- fs5.appendFileSync(file, JSON.stringify(event) + "\n");
3164
+ fs6.mkdirSync(path6.dirname(file), { recursive: true });
3165
+ fs6.appendFileSync(file, JSON.stringify(event) + "\n");
3124
3166
  } catch {
3125
3167
  }
3126
3168
  }
@@ -3840,29 +3882,29 @@ function recodeMessagesToPng(messages, opts) {
3840
3882
  }
3841
3883
 
3842
3884
  // src/skill-activation-manifest.ts
3843
- var fs6 = __toESM(require("fs"));
3844
- var path6 = __toESM(require("path"));
3885
+ var fs7 = __toESM(require("fs"));
3886
+ var path7 = __toESM(require("path"));
3845
3887
  var MAX_NATIVE_SKILL_FILES = 1500;
3846
3888
  var MAX_SCAN_DEPTH = 7;
3847
3889
  function homeRoots(homeDir, platform) {
3848
3890
  if (platform === "claude-code") {
3849
- return [path6.join(homeDir, ".claude", "skills")];
3891
+ return [path7.join(homeDir, ".claude", "skills")];
3850
3892
  }
3851
3893
  if (platform === "codex") {
3852
3894
  return [
3853
- path6.join(homeDir, ".codex", "skills"),
3854
- path6.join(homeDir, ".agents", "skills"),
3855
- path6.join(homeDir, ".codex", "plugins", "cache")
3895
+ path7.join(homeDir, ".codex", "skills"),
3896
+ path7.join(homeDir, ".agents", "skills"),
3897
+ path7.join(homeDir, ".codex", "plugins", "cache")
3856
3898
  ];
3857
3899
  }
3858
- return [path6.join(homeDir, ".config", "opencode", "skills")];
3900
+ return [path7.join(homeDir, ".config", "opencode", "skills")];
3859
3901
  }
3860
3902
  function ancestorDirs(cwd) {
3861
3903
  const dirs = [];
3862
- let current = path6.resolve(cwd);
3904
+ let current = path7.resolve(cwd);
3863
3905
  for (let depth = 0; depth < 16; depth++) {
3864
3906
  dirs.push(current);
3865
- const parent = path6.dirname(current);
3907
+ const parent = path7.dirname(current);
3866
3908
  if (parent === current)
3867
3909
  break;
3868
3910
  current = parent;
@@ -3872,11 +3914,11 @@ function ancestorDirs(cwd) {
3872
3914
  function repoRoots(cwd, platform) {
3873
3915
  return ancestorDirs(cwd).flatMap((dir) => {
3874
3916
  if (platform === "claude-code")
3875
- return [path6.join(dir, ".claude", "skills")];
3917
+ return [path7.join(dir, ".claude", "skills")];
3876
3918
  if (platform === "codex") {
3877
- return [path6.join(dir, ".codex", "skills"), path6.join(dir, ".agents", "skills")];
3919
+ return [path7.join(dir, ".codex", "skills"), path7.join(dir, ".agents", "skills")];
3878
3920
  }
3879
- return [path6.join(dir, ".opencode", "skills")];
3921
+ return [path7.join(dir, ".opencode", "skills")];
3880
3922
  });
3881
3923
  }
3882
3924
  function frontmatterName(content) {
@@ -3892,14 +3934,14 @@ function readNativeSkillNames(params) {
3892
3934
  return;
3893
3935
  let entries;
3894
3936
  try {
3895
- entries = fs6.readdirSync(dir, { withFileTypes: true });
3937
+ entries = fs7.readdirSync(dir, { withFileTypes: true });
3896
3938
  } catch {
3897
3939
  return;
3898
3940
  }
3899
3941
  for (const entry of entries) {
3900
3942
  if (visited >= MAX_NATIVE_SKILL_FILES)
3901
3943
  return;
3902
- const full = path6.join(dir, entry.name);
3944
+ const full = path7.join(dir, entry.name);
3903
3945
  if (entry.isDirectory()) {
3904
3946
  walk(full, depth + 1);
3905
3947
  continue;
@@ -3907,9 +3949,9 @@ function readNativeSkillNames(params) {
3907
3949
  if (!entry.isFile() || entry.name.toLowerCase() !== "skill.md")
3908
3950
  continue;
3909
3951
  visited++;
3910
- names.add(path6.basename(path6.dirname(full)).toLowerCase());
3952
+ names.add(path7.basename(path7.dirname(full)).toLowerCase());
3911
3953
  try {
3912
- const declared = frontmatterName(fs6.readFileSync(full, "utf8"));
3954
+ const declared = frontmatterName(fs7.readFileSync(full, "utf8"));
3913
3955
  if (declared)
3914
3956
  names.add(declared.toLowerCase());
3915
3957
  } catch {
@@ -3941,14 +3983,14 @@ function activationFromApiRows(params) {
3941
3983
  }
3942
3984
 
3943
3985
  // src/opencode-plugin.ts
3944
- var DEBUG_LOG_PATH = path7.join(os2.homedir(), ".greprag", "opencode-plugin-debug.log");
3986
+ var DEBUG_LOG_PATH = path8.join(os3.homedir(), ".greprag", "opencode-plugin-debug.log");
3945
3987
  var _debugLogReady = false;
3946
3988
  function dlogInit() {
3947
3989
  if (_debugLogReady)
3948
3990
  return;
3949
3991
  _debugLogReady = true;
3950
3992
  try {
3951
- fs7.writeFileSync(DEBUG_LOG_PATH, "");
3993
+ fs8.writeFileSync(DEBUG_LOG_PATH, "");
3952
3994
  } catch {
3953
3995
  }
3954
3996
  }
@@ -3957,7 +3999,7 @@ function dlog(msg) {
3957
3999
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [greprag-memory] ${msg}
3958
4000
  `;
3959
4001
  try {
3960
- fs7.appendFileSync(DEBUG_LOG_PATH, line);
4002
+ fs8.appendFileSync(DEBUG_LOG_PATH, line);
3961
4003
  } catch {
3962
4004
  }
3963
4005
  }
@@ -3965,9 +4007,9 @@ dlog(`module top reached: pid=${process.pid} argv0=${process.argv[0]} distPath=$
3965
4007
  var API_URL = "https://api.greprag.com";
3966
4008
  function loadEnvFile(filePath) {
3967
4009
  try {
3968
- if (!fs7.existsSync(filePath))
4010
+ if (!fs8.existsSync(filePath))
3969
4011
  return;
3970
- const raw = fs7.readFileSync(filePath, "utf-8");
4012
+ const raw = fs8.readFileSync(filePath, "utf-8");
3971
4013
  for (const line of raw.split(/\r?\n/)) {
3972
4014
  const trimmed = line.trim();
3973
4015
  if (!trimmed || trimmed.startsWith("#"))
@@ -3987,12 +4029,12 @@ function loadEnvFile(filePath) {
3987
4029
  }
3988
4030
  }
3989
4031
  function loadGrepragEnv() {
3990
- loadEnvFile(path7.join(HOME, ".greprag", ".env"));
4032
+ loadEnvFile(path8.join(HOME, ".greprag", ".env"));
3991
4033
  try {
3992
- const p = path7.join(HOME, ".claude", "settings.json");
3993
- if (!fs7.existsSync(p))
4034
+ const p = path8.join(HOME, ".claude", "settings.json");
4035
+ if (!fs8.existsSync(p))
3994
4036
  return;
3995
- const data = JSON.parse(fs7.readFileSync(p, "utf-8"));
4037
+ const data = JSON.parse(fs8.readFileSync(p, "utf-8"));
3996
4038
  if (data && data.env && typeof data.env === "object") {
3997
4039
  for (const [key, val] of Object.entries(data.env)) {
3998
4040
  if (!process.env[key] && typeof val === "string") {
@@ -4005,11 +4047,11 @@ function loadGrepragEnv() {
4005
4047
  }
4006
4048
  loadGrepragEnv();
4007
4049
  dlog(`env loaded: MEMORY_HOOK_ENABLED=${process.env.MEMORY_HOOK_ENABLED || "<unset>"} GREPRAG_API_KEY=${process.env.GREPRAG_API_KEY ? "set" : "<unset>"} GREPRAG_OPENCODE_CAPTURE=${process.env.GREPRAG_OPENCODE_CAPTURE || "<unset>"}`);
4008
- var WATCHER_LOCK = path7.join(HOME || os2.homedir(), ".greprag", "opencode-watch.lock");
4050
+ var WATCHER_LOCK = path8.join(HOME || os3.homedir(), ".greprag", "opencode-watch.lock");
4009
4051
  var WATCHER_STALE_MS = 3e4;
4010
4052
  function findGrepragBinary() {
4011
4053
  const override = process.env.GREPRAG_BIN;
4012
- if (override && fs7.existsSync(override))
4054
+ if (override && fs8.existsSync(override))
4013
4055
  return override;
4014
4056
  const binaryName = process.platform === "win32" ? "greprag.cmd" : "greprag";
4015
4057
  try {
@@ -4018,14 +4060,14 @@ function findGrepragBinary() {
4018
4060
  const candidate = line.trim();
4019
4061
  if (!candidate)
4020
4062
  continue;
4021
- if (!fs7.existsSync(candidate))
4063
+ if (!fs8.existsSync(candidate))
4022
4064
  continue;
4023
4065
  if (process.platform === "win32") {
4024
- const ext = path7.extname(candidate).toLowerCase();
4066
+ const ext = path8.extname(candidate).toLowerCase();
4025
4067
  if (ext === ".cmd" || ext === ".exe" || ext === ".bat")
4026
4068
  return candidate;
4027
4069
  const cmdSibling = candidate + ".cmd";
4028
- if (fs7.existsSync(cmdSibling))
4070
+ if (fs8.existsSync(cmdSibling))
4029
4071
  return cmdSibling;
4030
4072
  continue;
4031
4073
  }
@@ -4034,19 +4076,19 @@ function findGrepragBinary() {
4034
4076
  } catch {
4035
4077
  }
4036
4078
  const candidates = process.platform === "win32" ? [
4037
- path7.join(HOME, "AppData", "Roaming", "npm", "greprag.cmd"),
4038
- path7.join(HOME, "AppData", "Roaming", "npm", "greprag"),
4039
- path7.join(HOME, "AppData", "Local", "Yarn", "bin", "greprag.cmd"),
4040
- path7.join(HOME, "AppData", "Local", "pnpm", "bin", "greprag.cmd")
4079
+ path8.join(HOME, "AppData", "Roaming", "npm", "greprag.cmd"),
4080
+ path8.join(HOME, "AppData", "Roaming", "npm", "greprag"),
4081
+ path8.join(HOME, "AppData", "Local", "Yarn", "bin", "greprag.cmd"),
4082
+ path8.join(HOME, "AppData", "Local", "pnpm", "bin", "greprag.cmd")
4041
4083
  ] : [
4042
4084
  "/usr/local/bin/greprag",
4043
4085
  "/opt/homebrew/bin/greprag",
4044
- path7.join(HOME || "", ".local", "bin", "greprag"),
4045
- path7.join(HOME || "", ".yarn", "bin", "greprag")
4086
+ path8.join(HOME || "", ".local", "bin", "greprag"),
4087
+ path8.join(HOME || "", ".yarn", "bin", "greprag")
4046
4088
  ];
4047
4089
  for (const c of candidates) {
4048
4090
  try {
4049
- if (c && fs7.existsSync(c))
4091
+ if (c && fs8.existsSync(c))
4050
4092
  return c;
4051
4093
  } catch {
4052
4094
  }
@@ -4055,18 +4097,18 @@ function findGrepragBinary() {
4055
4097
  }
4056
4098
  function tryClaimWatcherLock() {
4057
4099
  try {
4058
- const fd = fs7.openSync(WATCHER_LOCK, "wx");
4100
+ const fd = fs8.openSync(WATCHER_LOCK, "wx");
4059
4101
  return { ok: true, fd };
4060
4102
  } catch (err) {
4061
4103
  if (err.code !== "EEXIST") {
4062
4104
  return { ok: false, reason: "lock-create-failed" };
4063
4105
  }
4064
4106
  try {
4065
- const stat = fs7.statSync(WATCHER_LOCK);
4107
+ const stat = fs8.statSync(WATCHER_LOCK);
4066
4108
  const ageMs = Date.now() - stat.mtimeMs;
4067
4109
  let pidAlive = false;
4068
4110
  try {
4069
- const pid = parseInt(fs7.readFileSync(WATCHER_LOCK, "utf-8").trim(), 10);
4111
+ const pid = parseInt(fs8.readFileSync(WATCHER_LOCK, "utf-8").trim(), 10);
4070
4112
  pidAlive = pid > 0 && isPidAlive(pid);
4071
4113
  } catch {
4072
4114
  }
@@ -4074,10 +4116,10 @@ function tryClaimWatcherLock() {
4074
4116
  return { ok: false, reason: "busy" };
4075
4117
  }
4076
4118
  try {
4077
- fs7.unlinkSync(WATCHER_LOCK);
4119
+ fs8.unlinkSync(WATCHER_LOCK);
4078
4120
  } catch {
4079
4121
  }
4080
- const fd = fs7.openSync(WATCHER_LOCK, "wx");
4122
+ const fd = fs8.openSync(WATCHER_LOCK, "wx");
4081
4123
  return { ok: true, fd };
4082
4124
  } catch (err2) {
4083
4125
  return { ok: false, reason: "lock-stale-replace-failed" };
@@ -4118,11 +4160,11 @@ function startCaptureWatcher() {
4118
4160
  return;
4119
4161
  }
4120
4162
  try {
4121
- fs7.writeSync(claim.fd, String(process.pid));
4122
- fs7.closeSync(claim.fd);
4163
+ fs8.writeSync(claim.fd, String(process.pid));
4164
+ fs8.closeSync(claim.fd);
4123
4165
  } catch {
4124
4166
  try {
4125
- fs7.unlinkSync(WATCHER_LOCK);
4167
+ fs8.unlinkSync(WATCHER_LOCK);
4126
4168
  } catch {
4127
4169
  }
4128
4170
  return;
@@ -4138,11 +4180,11 @@ function startCaptureWatcher() {
4138
4180
  const claim2 = tryClaimWatcherLock();
4139
4181
  if (claim2.ok) {
4140
4182
  try {
4141
- fs7.writeSync(claim2.fd, String(process.pid));
4142
- fs7.closeSync(claim2.fd);
4183
+ fs8.writeSync(claim2.fd, String(process.pid));
4184
+ fs8.closeSync(claim2.fd);
4143
4185
  } catch {
4144
4186
  try {
4145
- fs7.unlinkSync(WATCHER_LOCK);
4187
+ fs8.unlinkSync(WATCHER_LOCK);
4146
4188
  } catch {
4147
4189
  }
4148
4190
  return false;
@@ -4185,7 +4227,7 @@ function startCaptureWatcher() {
4185
4227
  childStartTime = Date.now();
4186
4228
  dlog(`startCaptureWatcher: spawned 'greprag opencode watch' pid=${child.pid ?? "<none>"}`);
4187
4229
  try {
4188
- fs7.writeFileSync(WATCHER_LOCK, String(child.pid));
4230
+ fs8.writeFileSync(WATCHER_LOCK, String(child.pid));
4189
4231
  } catch {
4190
4232
  }
4191
4233
  child.stderr?.on("data", (chunk2) => {
@@ -4197,9 +4239,9 @@ function startCaptureWatcher() {
4197
4239
  child.on("exit", (code, signal) => {
4198
4240
  dlog(`startCaptureWatcher: child pid=${child.pid ?? "<none>"} exited code=${code} signal=${signal || "none"}`);
4199
4241
  try {
4200
- const current = fs7.readFileSync(WATCHER_LOCK, "utf-8").trim();
4242
+ const current = fs8.readFileSync(WATCHER_LOCK, "utf-8").trim();
4201
4243
  if (current === String(child.pid))
4202
- fs7.unlinkSync(WATCHER_LOCK);
4244
+ fs8.unlinkSync(WATCHER_LOCK);
4203
4245
  } catch {
4204
4246
  }
4205
4247
  currentChild = null;
@@ -4228,7 +4270,7 @@ function startCaptureWatcher() {
4228
4270
  `
4229
4271
  );
4230
4272
  try {
4231
- fs7.unlinkSync(WATCHER_LOCK);
4273
+ fs8.unlinkSync(WATCHER_LOCK);
4232
4274
  } catch {
4233
4275
  }
4234
4276
  return;
@@ -4260,7 +4302,7 @@ function startCaptureWatcher() {
4260
4302
  }, 2e3).unref();
4261
4303
  }
4262
4304
  try {
4263
- fs7.unlinkSync(WATCHER_LOCK);
4305
+ fs8.unlinkSync(WATCHER_LOCK);
4264
4306
  } catch {
4265
4307
  }
4266
4308
  };
@@ -4296,6 +4338,8 @@ async function fetchRecapInbox(anchor) {
4296
4338
  }
4297
4339
  var relayArmedBySession = /* @__PURE__ */ new Set();
4298
4340
  var announcedBySession = /* @__PURE__ */ new Set();
4341
+ var personaAnnounceBySession = /* @__PURE__ */ new Map();
4342
+ var PERSONA_SESSION_CACHE_MAX = 100;
4299
4343
  var turnCountBySession = /* @__PURE__ */ new Map();
4300
4344
  var procedurePromptKeyBySession = /* @__PURE__ */ new Map();
4301
4345
  var cachedRecap = null;
@@ -4333,6 +4377,32 @@ async function fetchSkillActivation(apiKey, cwd) {
4333
4377
  return void 0;
4334
4378
  }
4335
4379
  }
4380
+ async function fetchPersonaAnnounce(apiKey) {
4381
+ try {
4382
+ const response = await fetch(`${API_URL}/v1/persona`, {
4383
+ headers: { Authorization: `Bearer ${apiKey}` }
4384
+ });
4385
+ if (!response.ok)
4386
+ return null;
4387
+ const data = await response.json();
4388
+ return typeof data.announce === "string" && data.announce.length > 0 ? data.announce : null;
4389
+ } catch {
4390
+ return null;
4391
+ }
4392
+ }
4393
+ async function personaAnnounceForSession(sessionId, apiKey) {
4394
+ if (personaAnnounceBySession.has(sessionId)) {
4395
+ return personaAnnounceBySession.get(sessionId) ?? null;
4396
+ }
4397
+ const announce = await fetchPersonaAnnounce(apiKey);
4398
+ if (personaAnnounceBySession.size >= PERSONA_SESSION_CACHE_MAX) {
4399
+ const oldest = personaAnnounceBySession.keys().next().value;
4400
+ if (oldest)
4401
+ personaAnnounceBySession.delete(oldest);
4402
+ }
4403
+ personaAnnounceBySession.set(sessionId, announce);
4404
+ return announce;
4405
+ }
4336
4406
  function startSessionRelay(sessionId, serverUrl) {
4337
4407
  if (process.env.GREPRAG_OPENCODE_RELAY === "0")
4338
4408
  return;
@@ -4369,11 +4439,11 @@ function startSessionRelay(sessionId, serverUrl) {
4369
4439
  return;
4370
4440
  }
4371
4441
  try {
4372
- fs7.writeSync(claim.fd, String(process.pid));
4373
- fs7.closeSync(claim.fd);
4442
+ fs8.writeSync(claim.fd, String(process.pid));
4443
+ fs8.closeSync(claim.fd);
4374
4444
  } catch {
4375
4445
  try {
4376
- fs7.unlinkSync(lockPath);
4446
+ fs8.unlinkSync(lockPath);
4377
4447
  } catch {
4378
4448
  }
4379
4449
  return;
@@ -4388,11 +4458,11 @@ function startSessionRelay(sessionId, serverUrl) {
4388
4458
  const claim2 = tryClaimRelayLock(lockPath);
4389
4459
  if (claim2.ok) {
4390
4460
  try {
4391
- fs7.writeSync(claim2.fd, String(process.pid));
4392
- fs7.closeSync(claim2.fd);
4461
+ fs8.writeSync(claim2.fd, String(process.pid));
4462
+ fs8.closeSync(claim2.fd);
4393
4463
  } catch {
4394
4464
  try {
4395
- fs7.unlinkSync(lockPath);
4465
+ fs8.unlinkSync(lockPath);
4396
4466
  } catch {
4397
4467
  }
4398
4468
  return false;
@@ -4428,7 +4498,7 @@ function startSessionRelay(sessionId, serverUrl) {
4428
4498
  childStartTime = Date.now();
4429
4499
  dlog(`startSessionRelay: spawned 'greprag opencode relay' pid=${child.pid ?? "<none>"}`);
4430
4500
  try {
4431
- fs7.writeFileSync(lockPath, String(child.pid));
4501
+ fs8.writeFileSync(lockPath, String(child.pid));
4432
4502
  } catch {
4433
4503
  }
4434
4504
  child.stderr?.on("data", (chunk2) => {
@@ -4439,9 +4509,9 @@ function startSessionRelay(sessionId, serverUrl) {
4439
4509
  });
4440
4510
  child.on("exit", (code, signal) => {
4441
4511
  try {
4442
- const current = fs7.readFileSync(lockPath, "utf-8").trim();
4512
+ const current = fs8.readFileSync(lockPath, "utf-8").trim();
4443
4513
  if (current === String(child.pid))
4444
- fs7.unlinkSync(lockPath);
4514
+ fs8.unlinkSync(lockPath);
4445
4515
  } catch {
4446
4516
  }
4447
4517
  currentChild = null;
@@ -4468,7 +4538,7 @@ function startSessionRelay(sessionId, serverUrl) {
4468
4538
  `
4469
4539
  );
4470
4540
  try {
4471
- fs7.unlinkSync(lockPath);
4541
+ fs8.unlinkSync(lockPath);
4472
4542
  } catch {
4473
4543
  }
4474
4544
  return;
@@ -4500,7 +4570,7 @@ function startSessionRelay(sessionId, serverUrl) {
4500
4570
  }, 2e3).unref();
4501
4571
  }
4502
4572
  try {
4503
- fs7.unlinkSync(lockPath);
4573
+ fs8.unlinkSync(lockPath);
4504
4574
  } catch {
4505
4575
  }
4506
4576
  };
@@ -4667,6 +4737,7 @@ ALWAYS pass \`--from-session ${sid82}\` when you run \`greprag send\`, so recipi
4667
4737
  if (sid8) {
4668
4738
  const turnCount = (turnCountBySession.get(sid8) ?? 0) + 1;
4669
4739
  turnCountBySession.set(sid8, turnCount);
4740
+ const firstAnnounce = !announcedBySession.has(sid8);
4670
4741
  const env = buildOpenCodeEnv({
4671
4742
  short: sid8,
4672
4743
  turnCount,
@@ -4682,9 +4753,12 @@ ALWAYS pass \`--from-session ${sid82}\` when you run \`greprag send\`, so recipi
4682
4753
  return [];
4683
4754
  }
4684
4755
  })(),
4685
- mirroredSkills: !announcedBySession.has(sid8) ? await fetchSkillActivation(apiKey, workingDir) : void 0
4756
+ mirroredSkills: firstAnnounce ? await fetchSkillActivation(apiKey, workingDir) : void 0,
4757
+ // output.system is fresh on EVERY transform call. Resolve Persona
4758
+ // once/session, then replay the cached shared-registry announce.
4759
+ personaAnnounce: await personaAnnounceForSession(sid8, apiKey)
4686
4760
  });
4687
- if (!announcedBySession.has(sid8)) {
4761
+ if (firstAnnounce) {
4688
4762
  announcedBySession.add(sid8);
4689
4763
  const announces = getOpenCodeAnnounces(env);
4690
4764
  if (announces.length > 0) {
@@ -4693,6 +4767,13 @@ ALWAYS pass \`--from-session ${sid82}\` when you run \`greprag send\`, so recipi
4693
4767
  }
4694
4768
  dlog(`interrupt: pushed ${announces.length} announce(s) for sid=${sid8}`);
4695
4769
  }
4770
+ } else {
4771
+ const persistent = getOpenCodePersistentAnnounces(env);
4772
+ for (const line of persistent)
4773
+ pushSystemPrompt(output, line);
4774
+ if (persistent.length > 0) {
4775
+ dlog(`interrupt: replayed ${persistent.length} persistent announce(s) for sid=${sid8}`);
4776
+ }
4696
4777
  }
4697
4778
  const reminders = getOpenCodeReminders(env);
4698
4779
  if (reminders.length > 0) {