@quantiya/codevibe-codex-plugin 2.0.11 → 2.0.14

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.
Files changed (23) hide show
  1. package/dist/server.js +36 -27
  2. package/hooks/common.sh +21 -9
  3. package/libexec/codex-hook-config.js +190 -0
  4. package/libexec/companion-launcher +9 -158
  5. package/node_modules/@quantiya/codevibe-core/dist/__tests__/gate-ux-regressions.test.d.ts +1 -0
  6. package/node_modules/@quantiya/codevibe-core/dist/index.d.ts +1 -1
  7. package/node_modules/@quantiya/codevibe-core/dist/index.js +240 -240
  8. package/node_modules/@quantiya/codevibe-core/dist/local-executor/workspace-shadow.d.ts +7 -0
  9. package/node_modules/@quantiya/codevibe-core/dist/local-model/manager.d.ts +1 -1
  10. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/audit-runner.d.ts +2 -3
  11. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/cli.d.ts +2 -2
  12. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/cli.js +158 -28
  13. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/quorum-loop.d.ts +22 -3
  14. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/task-progress.d.ts +3 -0
  15. package/node_modules/@quantiya/codevibe-core/dist/reduced-trust-notice.d.ts +44 -0
  16. package/node_modules/@quantiya/codevibe-core/dist/tool-activity/consolidator.d.ts +7 -0
  17. package/node_modules/@quantiya/codevibe-core/dist/tool-activity/ledger.d.ts +33 -13
  18. package/node_modules/@quantiya/codevibe-core/package.json +1 -1
  19. package/node_modules/fs-ext/build/Makefile +2 -2
  20. package/node_modules/fs-ext/build/Release/fs_ext.node +0 -0
  21. package/node_modules/fs-ext/build/Release/obj.target/fs_ext/fs-ext.o +0 -0
  22. package/node_modules/fs-ext/build/config.gypi +1 -1
  23. package/package.json +5 -2
package/hooks/common.sh CHANGED
@@ -117,9 +117,9 @@ is_codevibe_codex_daemon() {
117
117
  esac
118
118
  }
119
119
 
120
- # (#638 G5/H2/H3) THE identity-guarded reap: TERM → bounded wait → identity
120
+ # (#638 G5/H2/H3) THE identity-guarded reap: graceful signal → bounded wait → identity
121
121
  # re-check → KILL → settle. Identity (is_codevibe_codex_daemon) is re-verified
122
- # immediately before EVERY signal — the PID can die after the SIGTERM and be
122
+ # immediately before EVERY signal — the PID can die after the graceful signal and be
123
123
  # recycled to an unrelated process during the settle poll, so signaling blind
124
124
  # could kill a foreign process. Works for BOTH our own background child (`wait`
125
125
  # collects its status instantly, closing the PID-reuse window) and a non-child
@@ -127,13 +127,15 @@ is_codevibe_codex_daemon() {
127
127
  # provides the settle). Returns 0 when the daemon is provably gone, 1 when the
128
128
  # PID was not (or stopped being) our daemon and was left alone.
129
129
  _reap_codevibe_codex_daemon_pid() {
130
- local pid="$1" why="$2"
130
+ local pid="$1" why="$2" graceful_signal="${3:-TERM}"
131
131
  [ -n "$pid" ] || return 1
132
132
  if ! is_codevibe_codex_daemon "$pid"; then
133
133
  log "WARN" "reap($why): pid $pid is not our daemon (recycled/replaced) — not signaling"
134
134
  return 1
135
135
  fi
136
- kill "$pid" 2>/dev/null # SIGTERM: let it run its graceful shutdown
136
+ # TERM is a real terminal/session shutdown. USR2 is a daemon handoff: the
137
+ # replacement must reclaim the still-ACTIVE hosted row and its history.
138
+ kill -s "$graceful_signal" "$pid" 2>/dev/null
137
139
  local reap=0
138
140
  while [ $reap -lt 10 ] && ps -p "$pid" > /dev/null 2>&1; do
139
141
  sleep 0.2
@@ -439,7 +441,7 @@ ensure_codex_daemon() {
439
441
  ;;
440
442
  *)
441
443
  log "WARN" "ensure_codex_daemon: reaping wedged daemon pid $wedged_pid on excluded port $exclude_port (via $wedged_src) before relaunch (#638 H2)"
442
- _reap_codevibe_codex_daemon_pid "$wedged_pid" "wedged /event on port $exclude_port"
444
+ _reap_codevibe_codex_daemon_pid "$wedged_pid" "wedged /event on port $exclude_port" USR2
443
445
  ;;
444
446
  esac
445
447
  fi
@@ -528,10 +530,13 @@ send_to_server() {
528
530
  # (accepts TCP, never responds) must not hang the hook indefinitely — hooks
529
531
  # block the agent UI. --connect-timeout 2 bounds connection establishment;
530
532
  # --max-time 10 bounds the whole POST (generous for a slow-network POST).
531
- response=$(curl -s --connect-timeout 2 --max-time 10 -w "\n%{http_code}" \
533
+ # Stream the payload over stdin. Passing a screenshot-bearing PostToolUse
534
+ # body through `-d "$json_data"` puts it in argv and can fail locally with
535
+ # E2BIG before curl reaches the healthy daemon.
536
+ response=$(printf '%s' "$json_data" | curl -s --connect-timeout 2 --max-time 10 -w "\n%{http_code}" \
532
537
  -X POST \
533
538
  -H "Content-Type: application/json" \
534
- -d "$json_data" \
539
+ --data-binary @- \
535
540
  "$server_url/$endpoint" 2>&1)
536
541
  curl_exit=$?
537
542
  http_code=$(echo "$response" | tail -n 1)
@@ -551,6 +556,13 @@ send_to_server() {
551
556
  log "WARN" "No server URL (port file missing) — will attempt daemon relaunch"
552
557
  fi
553
558
 
559
+ # A local exec failure means curl never contacted the daemon. Replacing a
560
+ # health-passing process in response would retire or disrupt a valid session.
561
+ if [ "$curl_exit" -eq 126 ] || [ "$curl_exit" -eq 127 ]; then
562
+ log "ERROR" "Local curl invocation failed (exit $curl_exit) — not replacing the Codex daemon"
563
+ return 1
564
+ fi
565
+
554
566
  # (#638) TRANSPORT failure = curl non-zero OR HTTP 000 (dead daemon / missing
555
567
  # port file). Relaunch the daemon ONCE and retry the POST once (one relaunch
556
568
  # max, no loops). NOT for app 4xx/5xx — the daemon is alive and rejected the
@@ -574,10 +586,10 @@ send_to_server() {
574
586
  local new_port retry
575
587
  if new_port=$(ensure_codex_daemon "$failed_port" "$failed_pid"); then
576
588
  log "INFO" "Relaunched Codex daemon on port $new_port, retrying POST /$endpoint"
577
- retry=$(curl -s --connect-timeout 2 --max-time 10 -w "\n%{http_code}" \
589
+ retry=$(printf '%s' "$json_data" | curl -s --connect-timeout 2 --max-time 10 -w "\n%{http_code}" \
578
590
  -X POST \
579
591
  -H "Content-Type: application/json" \
580
- -d "$json_data" \
592
+ --data-binary @- \
581
593
  "http://localhost:${new_port}/$endpoint" 2>&1)
582
594
  curl_exit=$?
583
595
  http_code=$(echo "$retry" | tail -n 1)
@@ -0,0 +1,190 @@
1
+ #!/usr/bin/env node
2
+
3
+ 'use strict';
4
+
5
+ const fs = require('fs');
6
+
7
+ const OUR_HOOK_FILES = new Set([
8
+ 'session-start.sh',
9
+ 'user-prompt.sh',
10
+ 'pre-tool-use.sh',
11
+ 'permission-request.sh',
12
+ 'post-tool-use.sh',
13
+ 'stop.sh',
14
+ ]);
15
+
16
+ function hookScriptPath(command) {
17
+ if (typeof command !== 'string') return null;
18
+ const match = command.trim().match(/^bash\s+(?:"([^"]+)"|'([^']+)'|(\S+))$/);
19
+ return match ? (match[1] || match[2] || match[3]) : null;
20
+ }
21
+
22
+ function isOurCommand(command) {
23
+ const scriptPath = hookScriptPath(command);
24
+ if (!scriptPath) return false;
25
+
26
+ const filename = scriptPath.slice(scriptPath.lastIndexOf('/') + 1);
27
+ if (!OUR_HOOK_FILES.has(filename) || !scriptPath.endsWith(`/hooks/${filename}`)) {
28
+ return false;
29
+ }
30
+
31
+ if (scriptPath.includes('/codevibe-codex-plugin/hooks/')) {
32
+ return true;
33
+ }
34
+
35
+ // Development links can use task-specific repository names rather than the
36
+ // canonical package directory. Keep this deliberately limited to CodeVibe's
37
+ // agent-worktree layout so an unrelated hook with the same filename survives.
38
+ return /\/\.agent-worktrees\/[^/]+\/codevibe-codex-[^/]+\/hooks\/[^/]+$/.test(scriptPath);
39
+ }
40
+
41
+ function stripOurHooksFromEntry(entry) {
42
+ if (!entry || typeof entry !== 'object' || !Array.isArray(entry.hooks)) {
43
+ return entry;
44
+ }
45
+ const filtered = entry.hooks.filter((hook) => !(hook && isOurCommand(hook.command)));
46
+ if (filtered.length === entry.hooks.length) return entry;
47
+ if (filtered.length === 0) return null;
48
+ return { ...entry, hooks: filtered };
49
+ }
50
+
51
+ function ownedCommands(entries) {
52
+ return entries
53
+ .flatMap((entry) => entry && typeof entry === 'object' && Array.isArray(entry.hooks)
54
+ ? entry.hooks
55
+ : [])
56
+ .map((hook) => hook && hook.command)
57
+ .filter(isOurCommand)
58
+ .sort();
59
+ }
60
+
61
+ function mergeHookConfig(existingObj, nextObj, existed = true) {
62
+ const existingHooks = existingObj && typeof existingObj === 'object'
63
+ && existingObj.hooks && typeof existingObj.hooks === 'object'
64
+ ? existingObj.hooks
65
+ : {};
66
+ const nextHooks = nextObj && typeof nextObj === 'object'
67
+ && nextObj.hooks && typeof nextObj.hooks === 'object'
68
+ ? nextObj.hooks
69
+ : {};
70
+
71
+ let allPresent = existed;
72
+ for (const key of Object.keys(nextHooks)) {
73
+ const existingEntries = Array.isArray(existingHooks[key]) ? existingHooks[key] : [];
74
+ const nextEntries = Array.isArray(nextHooks[key]) ? nextHooks[key] : [];
75
+ if (JSON.stringify(ownedCommands(existingEntries)) !== JSON.stringify(ownedCommands(nextEntries))) {
76
+ allPresent = false;
77
+ break;
78
+ }
79
+ }
80
+ if (allPresent) {
81
+ return { outcome: 'already_installed', config: existingObj };
82
+ }
83
+
84
+ const mergedHooks = { ...existingHooks };
85
+ for (const key of Object.keys(nextHooks)) {
86
+ const existingEntries = Array.isArray(existingHooks[key]) ? existingHooks[key] : [];
87
+ const cleanedExisting = existingEntries
88
+ .map(stripOurHooksFromEntry)
89
+ .filter((entry) => entry !== null && entry !== undefined);
90
+ const nextEntries = Array.isArray(nextHooks[key]) ? nextHooks[key] : [];
91
+ mergedHooks[key] = [...cleanedExisting, ...nextEntries];
92
+ }
93
+
94
+ return {
95
+ outcome: existed ? 'merged' : 'fresh_install',
96
+ config: existingObj && typeof existingObj === 'object'
97
+ ? { ...existingObj, hooks: mergedHooks }
98
+ : { hooks: mergedHooks },
99
+ };
100
+ }
101
+
102
+ function cleanHookConfig(existingObj) {
103
+ if (!existingObj || typeof existingObj !== 'object'
104
+ || !existingObj.hooks || typeof existingObj.hooks !== 'object') {
105
+ return existingObj;
106
+ }
107
+
108
+ const hooks = { ...existingObj.hooks };
109
+ for (const key of Object.keys(hooks)) {
110
+ if (!Array.isArray(hooks[key])) continue;
111
+ hooks[key] = hooks[key]
112
+ .map(stripOurHooksFromEntry)
113
+ .filter((entry) => entry !== null && entry !== undefined);
114
+ }
115
+ return { ...existingObj, hooks };
116
+ }
117
+
118
+ function writeAtomic(target, value) {
119
+ const temporary = `${target}.${process.pid}.${Date.now()}.tmp`;
120
+ try {
121
+ fs.writeFileSync(temporary, JSON.stringify(value, null, 2));
122
+ fs.renameSync(temporary, target);
123
+ } catch (error) {
124
+ try { fs.unlinkSync(temporary); } catch {}
125
+ throw error;
126
+ }
127
+ }
128
+
129
+ function runMerge() {
130
+ const target = process.env.CV_EXISTING;
131
+ let existingObj = null;
132
+ let existed = false;
133
+ try {
134
+ if (fs.existsSync(target)) {
135
+ existed = true;
136
+ existingObj = JSON.parse(fs.readFileSync(target, 'utf8'));
137
+ }
138
+ } catch (error) {
139
+ process.stderr.write(`PARSE_EXISTING_FAILED:${error.message || String(error)}`);
140
+ return 2;
141
+ }
142
+
143
+ let nextObj;
144
+ try {
145
+ nextObj = JSON.parse(process.env.CV_NEW);
146
+ } catch (error) {
147
+ process.stderr.write(`PARSE_NEW_FAILED:${error.message || String(error)}`);
148
+ return 3;
149
+ }
150
+
151
+ const result = mergeHookConfig(existingObj, nextObj, existed);
152
+ if (result.outcome !== 'already_installed') {
153
+ try {
154
+ writeAtomic(target, result.config);
155
+ } catch (error) {
156
+ process.stderr.write(`WRITE_FAILED:${error.message || String(error)}`);
157
+ return 4;
158
+ }
159
+ }
160
+ process.stdout.write(`OUTCOME:${result.outcome}\n`);
161
+ return 0;
162
+ }
163
+
164
+ function runCleanup() {
165
+ const target = process.env.CV_HOOKS_FILE;
166
+ try {
167
+ const existingObj = JSON.parse(fs.readFileSync(target, 'utf8'));
168
+ writeAtomic(target, cleanHookConfig(existingObj));
169
+ return 0;
170
+ } catch (error) {
171
+ process.stderr.write(`CLEAN_FAILED:${error.message || String(error)}`);
172
+ return 1;
173
+ }
174
+ }
175
+
176
+ if (require.main === module) {
177
+ const action = process.argv[2];
178
+ const exitCode = action === 'merge'
179
+ ? runMerge()
180
+ : action === 'cleanup'
181
+ ? runCleanup()
182
+ : 64;
183
+ process.exit(exitCode);
184
+ }
185
+
186
+ module.exports = {
187
+ cleanHookConfig,
188
+ isOurCommand,
189
+ mergeHookConfig,
190
+ };
@@ -259,55 +259,10 @@ cleanup() {
259
259
  # Only remove if no other codevibe-codex sessions are running
260
260
  if [ "$(pgrep -f 'codevibe-codex' | wc -l)" -le 1 ]; then
261
261
  log "Removing CodeVibe hooks from $HOME/.codex/hooks.json"
262
- # Cleanup mirrors install: same ownership predicate + the
263
- # same INNER-hooks[] split so a matcher entry mixing user
264
- # hooks with ours keeps the user hook. Same per-process
265
- # tmp suffix avoids two concurrent cleanups racing.
266
- if CV_HOOKS_FILE="$HOME/.codex/hooks.json" node -e '
267
- const fs = require("fs");
268
- const OUR_HOOK_FILES = new Set([
269
- "session-start.sh",
270
- "user-prompt.sh",
271
- "pre-tool-use.sh",
272
- "post-tool-use.sh",
273
- "stop.sh",
274
- ]);
275
- const OWN_PATH_MARKER = "codevibe-codex-plugin/hooks/";
276
- const isOurCommand = (command) => {
277
- if (typeof command !== "string") return false;
278
- if (command.indexOf(OWN_PATH_MARKER) === -1) return false;
279
- for (const f of OUR_HOOK_FILES) {
280
- if (command.endsWith("/" + f)) return true;
281
- }
282
- return false;
283
- };
284
- const stripOurHooksFromEntry = (entry) => {
285
- if (!entry || typeof entry !== "object" || !Array.isArray(entry.hooks)) return entry;
286
- const filtered = entry.hooks.filter((h) => !(h && isOurCommand(h.command)));
287
- if (filtered.length === entry.hooks.length) return entry;
288
- if (filtered.length === 0) return null;
289
- return { ...entry, hooks: filtered };
290
- };
291
- try {
292
- const target = process.env.CV_HOOKS_FILE;
293
- const data = JSON.parse(fs.readFileSync(target, "utf8"));
294
- if (data && typeof data === "object" && data.hooks && typeof data.hooks === "object") {
295
- for (const k of Object.keys(data.hooks)) {
296
- if (Array.isArray(data.hooks[k])) {
297
- data.hooks[k] = data.hooks[k]
298
- .map(stripOurHooksFromEntry)
299
- .filter((entry) => entry !== null && entry !== undefined);
300
- }
301
- }
302
- }
303
- const tmp = target + "." + process.pid + "." + Date.now() + ".tmp";
304
- fs.writeFileSync(tmp, JSON.stringify(data, null, 2));
305
- fs.renameSync(tmp, target);
306
- } catch (e) {
307
- process.stderr.write("CLEAN_FAILED:" + (e && e.message ? e.message : String(e)));
308
- process.exit(1);
309
- }
310
- ' 2>>"$LOG_FILE"; then
262
+ # The shared helper keeps install and cleanup ownership rules in
263
+ # lockstep, including task-named CodeVibe agent worktrees.
264
+ if CV_HOOKS_FILE="$HOME/.codex/hooks.json" \
265
+ node "$PLUGIN_DIR/libexec/codex-hook-config.js" cleanup 2>>"$LOG_FILE"; then
311
266
  :
312
267
  else
313
268
  log "WARN: failed to clean CodeVibe hooks from hooks.json (left as-is)"
@@ -539,9 +494,9 @@ _CV_HOOKS_REASON=""
539
494
  # keeps the user hook (only the CodeVibe hooks get stripped, not
540
495
  # the surrounding entry).
541
496
  #
542
- # - Ownership predicate: path-prefix `codevibe-codex-plugin/hooks/`
543
- # AND filename tail in the allow-list of our hook script names.
544
- # Rejects user paths like /Users/codevibe-codex/foo.sh.
497
+ # - Ownership predicate: canonical package paths or CodeVibe's strict
498
+ # `.agent-worktrees/<agent>/codevibe-codex-*/hooks/` layout, plus a
499
+ # filename allowlist. Rejects unrelated user hook paths.
545
500
  #
546
501
  # - Per-process tmp file (.<pid>.<time>.tmp): two concurrent installs
547
502
  # won't clobber each other's intermediate state. Final rename is
@@ -555,112 +510,8 @@ _CV_HOOKS_REASON=""
555
510
  GENERATED_HOOKS=$(generate_hooks_json)
556
511
  if INSTALLER_OUTPUT=$(CV_EXISTING="$CODEX_HOOKS_FILE" \
557
512
  CV_NEW="$GENERATED_HOOKS" \
558
- node -e '
559
- const fs = require("fs");
560
- const OUR_HOOK_FILES = new Set([
561
- "session-start.sh",
562
- "user-prompt.sh",
563
- "pre-tool-use.sh",
564
- "permission-request.sh",
565
- "post-tool-use.sh",
566
- "stop.sh",
567
- ]);
568
- const OWN_PATH_MARKER = "codevibe-codex-plugin/hooks/";
569
- const isOurCommand = (command) => {
570
- if (typeof command !== "string") return false;
571
- if (command.indexOf(OWN_PATH_MARKER) === -1) return false;
572
- for (const f of OUR_HOOK_FILES) {
573
- if (command.endsWith("/" + f)) return true;
574
- }
575
- return false;
576
- };
577
- // Strip our hooks from a single matcher entrys inner hooks[] array.
578
- // Returns null if NO non-owned hooks remain (whole entry should be
579
- // dropped); otherwise returns a new entry with only the user-owned
580
- // hooks. Returns the original entry unchanged if it contains none of
581
- // ours (preserves identity for unrelated entries).
582
- const stripOurHooksFromEntry = (entry) => {
583
- if (!entry || typeof entry !== "object" || !Array.isArray(entry.hooks)) {
584
- return entry;
585
- }
586
- const filtered = entry.hooks.filter((h) => !(h && isOurCommand(h.command)));
587
- if (filtered.length === entry.hooks.length) return entry;
588
- if (filtered.length === 0) return null;
589
- return { ...entry, hooks: filtered };
590
- };
591
- const ownedCommands = (entries) => entries
592
- .flatMap((entry) => entry && typeof entry === "object" && Array.isArray(entry.hooks)
593
- ? entry.hooks
594
- : [])
595
- .map((hook) => hook && hook.command)
596
- .filter(isOurCommand)
597
- .sort();
598
-
599
- let existingObj = null;
600
- let existed = false;
601
- try {
602
- if (fs.existsSync(process.env.CV_EXISTING)) {
603
- existed = true;
604
- existingObj = JSON.parse(fs.readFileSync(process.env.CV_EXISTING, "utf8"));
605
- }
606
- } catch (e) {
607
- process.stderr.write("PARSE_EXISTING_FAILED:" + (e && e.message ? e.message : String(e)));
608
- process.exit(2);
609
- }
610
- let nextObj;
611
- try {
612
- nextObj = JSON.parse(process.env.CV_NEW);
613
- } catch (e) {
614
- process.stderr.write("PARSE_NEW_FAILED:" + (e && e.message ? e.message : String(e)));
615
- process.exit(3);
616
- }
617
-
618
- const existingHooks = (existingObj && typeof existingObj === "object" &&
619
- existingObj.hooks && typeof existingObj.hooks === "object") ? existingObj.hooks : {};
620
- const nextHooks = (nextObj && typeof nextObj === "object" &&
621
- nextObj.hooks && typeof nextObj.hooks === "object") ? nextObj.hooks : {};
622
- let allPresent = existed;
623
- for (const k of Object.keys(nextHooks)) {
624
- const existingArr = Array.isArray(existingHooks[k]) ? existingHooks[k] : [];
625
- const nextArr = Array.isArray(nextHooks[k]) ? nextHooks[k] : [];
626
- if (JSON.stringify(ownedCommands(existingArr)) !== JSON.stringify(ownedCommands(nextArr))) {
627
- allPresent = false;
628
- break;
629
- }
630
- }
631
- if (allPresent) {
632
- process.stdout.write("OUTCOME:already_installed\n");
633
- process.exit(0);
634
- }
635
-
636
- const mergedHooks = { ...existingHooks };
637
- for (const k of Object.keys(nextHooks)) {
638
- const existingArr = Array.isArray(existingHooks[k]) ? existingHooks[k] : [];
639
- const cleanedExisting = existingArr
640
- .map(stripOurHooksFromEntry)
641
- .filter((entry) => entry !== null && entry !== undefined);
642
- const nextArr = Array.isArray(nextHooks[k]) ? nextHooks[k] : [];
643
- mergedHooks[k] = [...cleanedExisting, ...nextArr];
644
- }
645
-
646
- const out = (existingObj && typeof existingObj === "object")
647
- ? { ...existingObj, hooks: mergedHooks }
648
- : { hooks: mergedHooks };
649
- const outcome = existed ? "merged" : "fresh_install";
650
- const target = process.env.CV_EXISTING;
651
- // Per-process tmp suffix avoids two concurrent installs both writing
652
- // ${target}.tmp and one mvs the others half-written file.
653
- const tmp = target + "." + process.pid + "." + Date.now() + ".tmp";
654
- try {
655
- fs.writeFileSync(tmp, JSON.stringify(out, null, 2));
656
- fs.renameSync(tmp, target);
657
- } catch (e) {
658
- try { fs.unlinkSync(tmp); } catch {}
659
- process.stderr.write("WRITE_FAILED:" + (e && e.message ? e.message : String(e)));
660
- process.exit(4);
661
- }
662
- process.stdout.write("OUTCOME:" + outcome + "\n");
663
- ' 2>>"$LOG_FILE"); then
513
+ node "$PLUGIN_DIR/libexec/codex-hook-config.js" merge \
514
+ 2>>"$LOG_FILE"); then
664
515
  INSTALLER_RC=0
665
516
  else
666
517
  INSTALLER_RC=$?
@@ -43,7 +43,7 @@ export { ToolActivityOutbox, type ToolActivityOutboxOptions, type ListPendingRes
43
43
  export { ToolActivityLedger, LedgerEntrySchema, LedgerFileSchema, LegacyOwnedFileSchema, type LedgerEntry, type LedgerFile, type LegacyOwnedFile, type OccurrenceRef, type ToolActivityLedgerOptions, } from './tool-activity';
44
44
  export { ToolReplayCursor, ReplayFrameTooLargeError, ReplayCursorStateSchema, DEFAULT_REPLAY_READ_BYTES, DEFAULT_REPLAY_MAX_FRAME_BYTES, type ReplayCursorState, type ReplayFrame, type ReadFramesResult, type ToolReplayCursorOptions, } from './tool-activity';
45
45
  export { ToolActivitySessionMap, InProcessRolloutLock, SessionMapRecordSchema, type RolloutLock, type RolloutOwnership, type SessionMapRecord, type ToolActivitySessionMapOptions, } from './tool-activity';
46
- export { ToolActivityConsolidator, isSessionKeyStaleError, type ObservedOccurrence, type ObserveDisposition, type ToolActivityTransport, type ToolActivityConsolidatorOptions, } from './tool-activity';
46
+ export { ToolActivityConsolidator, isSessionKeyStaleError, isTerminalSessionError, type ObservedOccurrence, type ObserveDisposition, type ToolActivityTransport, type ToolActivityConsolidatorOptions, } from './tool-activity';
47
47
  export { SocketRolloutLock, type SocketRolloutLockOptions, FlockRolloutLock, type FlockRolloutLockOptions, type FlockSyncFn, type FlockFlag, createRolloutLock, type CreateRolloutLockOptions, } from './tool-activity';
48
48
  export { ToolActivityOccurrenceSchema, ToolActivityDetailSchema, ToolActivityGroupSchema, ToolActivityManifestSchema, ToolActivityOutboxEnvelopeSchema, type ToolActivityOccurrence, type ToolActivityDetail, type ToolActivityGroup, type ToolActivityManifest, type ToolActivityOutboxEnvelope, } from './tool-activity';
49
49
  export { digestOf, stableStringify } from './tool-activity';