@indigoai-us/hq-cli 5.345.12 → 5.345.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.345.13] — 2026-10-06
6
+ - Stream provider transcript scans and account-move comparisons so large session files do not require whole-file reads.
7
+
5
8
  ## [5.345.12] — 2026-10-06
6
9
  - Route rebuild-index, pin, scheduled-job commands, and worker-skill lint through the fast CLI entry path to reduce command startup time.
7
10
  - `hq daemon` on desktop machines no longer starts a lane reconcile process every 15 seconds when no lane needs it. It checks the lane store first: live lanes, keep-alive lanes, pending resumes and pending questions keep the 15-second cadence; a store with only settled lanes is reconciled once a minute, as before 15-second polling; an HQ folder with no lanes, questions, lead registry or lanes config is not reconciled at all. Outposts and self-hosted machines are unchanged.
@@ -65,11 +65,11 @@ done
65
65
  [ -f "$MANIFEST" ] || die "manifest not found: $MANIFEST"
66
66
  jq -e . "$MANIFEST" >/dev/null 2>&1 || die "manifest is not valid JSON: $MANIFEST"
67
67
 
68
- COMPANY="$(jq -r '.company // empty' "$MANIFEST")"
69
- PROJECT="$(jq -r '.project.name // empty' "$MANIFEST")"
70
- PRINCIPAL="$(jq -r '.to.principal // empty' "$MANIFEST")"
71
- DISPLAY="$(jq -r '.to.displayName // .to.principal // empty' "$MANIFEST")"
72
- STATUS="$(jq -r '.status // empty' "$MANIFEST")"
68
+ COMPANY="$(jq -r '.company // empty' "$MANIFEST" | tr -d '\r')"
69
+ PROJECT="$(jq -r '.project.name // empty' "$MANIFEST" | tr -d '\r')"
70
+ PRINCIPAL="$(jq -r '.to.principal // empty' "$MANIFEST" | tr -d '\r')"
71
+ DISPLAY="$(jq -r '.to.displayName // .to.principal // empty' "$MANIFEST" | tr -d '\r')"
72
+ STATUS="$(jq -r '.status // empty' "$MANIFEST" | tr -d '\r')"
73
73
  [ -n "$COMPANY" ] || die "manifest has no company"
74
74
  [ -n "$PROJECT" ] || die "manifest has no project.name"
75
75
  [ -n "$PRINCIPAL" ] || die "manifest has no to.principal"
@@ -96,7 +96,7 @@ case "$PRINCIPAL" in
96
96
  TAIL="$(printf '%s' "$PRINCIPAL" | tail -c 9 | tr '[:upper:]' '[:lower:]')"
97
97
  LEGACY_GROUP="grp_dlg-$TAIL"
98
98
  NEXT_GROUP="$(agent_group_id "$PRINCIPAL")"
99
- RECORDED_GROUP="$(jq -r '.grantPrincipal // empty' "$MANIFEST")"
99
+ RECORDED_GROUP="$(jq -r '.grantPrincipal // empty' "$MANIFEST" | tr -d '\r')"
100
100
  case "$RECORDED_GROUP" in
101
101
  "$LEGACY_GROUP"|"$NEXT_GROUP") GRANT_PRINCIPAL="$RECORDED_GROUP" ;;
102
102
  *) GRANT_PRINCIPAL="$NEXT_GROUP" ;;
@@ -109,7 +109,7 @@ case "$STATUS" in
109
109
  *) die "manifest status is '$STATUS' — grants run from 'building' (or re-run from 'granted'), not from there" ;;
110
110
  esac
111
111
 
112
- PREFIX_COUNT="$(jq '.vaultPrefixes | length' "$MANIFEST")"
112
+ PREFIX_COUNT="$(jq '.vaultPrefixes | length' "$MANIFEST" | tr -d '\r')"
113
113
  [ "$PREFIX_COUNT" -gt 0 ] || die "manifest has no vaultPrefixes"
114
114
 
115
115
  # --- validate every prefix BEFORE touching anything --------------------------
@@ -118,7 +118,7 @@ PREFIX_COUNT="$(jq '.vaultPrefixes | length' "$MANIFEST")"
118
118
  # not exist in the bucket. Either is a hard error, never a silent grant.
119
119
 
120
120
  BAD="$(jq -r '.vaultPrefixes[] | .prefix
121
- | select((endswith("/") | not) or startswith("companies/") or startswith("/"))' "$MANIFEST")"
121
+ | select((endswith("/") | not) or startswith("companies/") or startswith("/"))' "$MANIFEST" | tr -d '\r')"
122
122
  if [ -n "$BAD" ]; then
123
123
  die "prefix violates the hq-files prefix conventions (must be company-relative folder form ending in '/'): $BAD"
124
124
  fi
@@ -130,8 +130,8 @@ if [ "$GRANT_PRINCIPAL" != "$PRINCIPAL" ]; then
130
130
  echo " 0. Agent recipient: grants flow through group $GRANT_PRINCIPAL (created if absent, containing exactly $DISPLAY)"
131
131
  fi
132
132
  echo " 1. Push companies/$COMPANY/projects/$PROJECT/ to the vault (on-conflict keep)"
133
- jq -r '.vaultPrefixes[] | " 2. Grant \(.permission) on \(.prefix) — \(.reason // "")"' "$MANIFEST"
134
- WRITE_PREFIXES="$(jq -r '.vaultPrefixes[] | select(.permission == "write") | .prefix' "$MANIFEST")"
133
+ jq -r '.vaultPrefixes[] | " 2. Grant \(.permission) on \(.prefix) — \(.reason // "")"' "$MANIFEST" | tr -d '\r'
134
+ WRITE_PREFIXES="$(jq -r '.vaultPrefixes[] | select(.permission == "write") | .prefix' "$MANIFEST" | tr -d '\r')"
135
135
  if [ -n "$WRITE_PREFIXES" ]; then
136
136
  echo
137
137
  echo "NOTE: granting 'write' is a privilege escalation. The recipient will be able"
@@ -155,7 +155,7 @@ hq sync push "companies/$COMPANY/projects/$PROJECT/" --company "$COMPANY" --on-c
155
155
 
156
156
  jq -r --arg co "$COMPANY" \
157
157
  '((.knowledge // []) + (.policies // []))[] | select(startswith("companies/" + $co + "/"))' \
158
- "$MANIFEST" | while IFS= read -r kpath; do
158
+ "$MANIFEST" | tr -d '\r' | while IFS= read -r kpath; do
159
159
  if [ -e "$HQ_ROOT/$kpath" ]; then
160
160
  hq sync push "$kpath" --company "$COMPANY" --on-conflict keep \
161
161
  || die "vault push failed for referenced knowledge: $kpath"
@@ -195,8 +195,8 @@ case "$GRANT_PRINCIPAL" in
195
195
  if printf '%s' "$RESOLVED" | jq -e '.status == "found" and (.person.personUid | type == "string") and (.person.companyUid | type == "string")' >/dev/null; then
196
196
  [ "$RESOLVE_RC" -eq 0 ] || die "recipient resolution failed"
197
197
  EXPECTED_TYPE=person
198
- EXPECTED_ID="$(printf '%s' "$RESOLVED" | jq -r '.person.personUid')"
199
- EXPECTED_COMPANY="$(printf '%s' "$RESOLVED" | jq -r '.person.companyUid')"
198
+ EXPECTED_ID="$(printf '%s' "$RESOLVED" | jq -r '.person.personUid' | tr -d '\r')"
199
+ EXPECTED_COMPANY="$(printf '%s' "$RESOLVED" | jq -r '.person.companyUid' | tr -d '\r')"
200
200
  elif printf '%s' "$RESOLVED" | jq -e '.status == "not_found"' >/dev/null; then
201
201
  case "$PRINCIPAL" in *@*) EXPECTED_TYPE=email; EXPECTED_ID="$(printf '%s' "$PRINCIPAL" | tr '[:upper:]' '[:lower:]' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')" ;; *) die "unresolved recipient" ;; esac
202
202
  else
@@ -223,15 +223,15 @@ read_acl() {
223
223
  NOW="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
224
224
  i=0
225
225
  while [ "$i" -lt "$PREFIX_COUNT" ]; do
226
- PFX="$(jq -r ".vaultPrefixes[$i].prefix" "$MANIFEST")"
227
- PERM="$(jq -r ".vaultPrefixes[$i].permission" "$MANIFEST")"
226
+ PFX="$(jq -r ".vaultPrefixes[$i].prefix" "$MANIFEST" | tr -d '\r')"
227
+ PERM="$(jq -r ".vaultPrefixes[$i].permission" "$MANIFEST" | tr -d '\r')"
228
228
  # Always address the recursive "folder/*" pattern explicitly. Since hq-pro
229
229
  # #3662 (2026-09-23) the server echoes the raw pattern back on read, and a
230
230
  # bare trailing "/" can mean a PRIVATE folder pattern when that flag is on.
231
231
  # A delegation grant is always the recursive share, never a private folder.
232
232
  ACL_PATTERN="${PFX}*"
233
233
  BEFORE="$(read_acl "$ACL_PATTERN")" || die "ACL preflight failed; no share attempted for $PFX"
234
- EXPECTED_COMPANY="$(printf '%s' "$BEFORE" | jq -r '.companyUid')"
234
+ EXPECTED_COMPANY="$(printf '%s' "$BEFORE" | jq -r '.companyUid' | tr -d '\r')"
235
235
  SHARE_RC=0
236
236
  hq files share "$ACL_PATTERN" --with "$GRANT_PRINCIPAL" --permission "$PERM" --company "$COMPANY" || SHARE_RC=$?
237
237
  [ "$SHARE_RC" -eq 0 ] || echo "hq-delegate-grant: share reported an error on $PFX; checking exact grant" >&2
@@ -7,7 +7,7 @@ import { serializeJqJson } from "../lib/core-utils/jq-json-serializer.js";
7
7
  import { shellDisplayPath } from "../lib/core-utils/shell-display-path.js";
8
8
  import { matchingSecretPatterns } from "../lib/core-utils/secret-patterns.js";
9
9
  import { describeCoreCommand } from "./core-command-catalog.js";
10
- import { commandExists, isRecord, jsonAt, jqFallback, jqRawPath, jqRawPaths, moveFile, readJsonFile, subprocess, writeJsonAtomically, } from "./core-delegate.js";
10
+ import { commandExists, delegateDiagnosticPath, isRecord, jsonAt, jqFallback, jqRawPath, jqRawPaths, moveFile, readJsonFile, subprocess, writeJsonAtomically, } from "./core-delegate.js";
11
11
  import { resolveLiveRoot } from "../utils/hq-roots.js";
12
12
  import { ACL_SOURCE_DISPLAY_LABELS } from "../utils/files-browse-acl.js";
13
13
  import { runBundledScript } from "../utils/run-bundled-script.js";
@@ -179,11 +179,16 @@ function bashPath(value) {
179
179
  const drivePath = /^([A-Za-z]):\/(.*)$/u.exec(normalized);
180
180
  return drivePath ? `/${drivePath[1].toLowerCase()}/${drivePath[2]}` : normalized;
181
181
  }
182
+ function shellFallbackArgs(args) {
183
+ if (process.platform !== "win32")
184
+ return args;
185
+ return args.map((arg, index) => args[index - 1] === "--manifest" && /^[a-z]:[\\/]/i.test(arg) ? bashPath(arg) : arg);
186
+ }
182
187
  function runShellFallback(name, args, context) {
183
188
  const root = context.root ?? context.cwd;
184
189
  const result = runBundledScript({
185
190
  asset: ASSETS[name],
186
- args,
191
+ args: shellFallbackArgs(args),
187
192
  cwd: context.cwd,
188
193
  hqRoot: bashPath(root),
189
194
  captureOutput: true,
@@ -404,16 +409,16 @@ function runGrant(args, context) {
404
409
  if (!manifestPath)
405
410
  return fail("grant", "--manifest is required");
406
411
  if (!fs.existsSync(manifestPath) || !fs.statSync(manifestPath).isFile())
407
- return fail("grant", `manifest not found: ${shellDisplayPath(manifestPath)}`);
412
+ return fail("grant", `manifest not found: ${delegateDiagnosticPath(manifestPath)}`);
408
413
  let manifest;
409
414
  try {
410
415
  manifest = readJsonFile(manifestPath);
411
416
  }
412
417
  catch {
413
- return fail("grant", `manifest is not valid JSON: ${shellDisplayPath(manifestPath)}`);
418
+ return fail("grant", `manifest is not valid JSON: ${delegateDiagnosticPath(manifestPath)}`);
414
419
  }
415
420
  if (manifest === null || manifest === false)
416
- return fail("grant", `manifest is not valid JSON: ${shellDisplayPath(manifestPath)}`);
421
+ return fail("grant", `manifest is not valid JSON: ${delegateDiagnosticPath(manifestPath)}`);
417
422
  if (!isRecord(manifest))
418
423
  return fail("grant", "manifest has no company");
419
424
  const company = jqRawPath(manifest, "company");
@@ -790,10 +795,10 @@ function runSend(args, context) {
790
795
  if (!manifestPath)
791
796
  return fail("send", "--manifest is required");
792
797
  if (!fs.existsSync(manifestPath) || !fs.statSync(manifestPath).isFile())
793
- return fail("send", `manifest not found: ${shellDisplayPath(manifestPath)}`);
798
+ return fail("send", `manifest not found: ${delegateDiagnosticPath(manifestPath)}`);
794
799
  const manifest = jsonFile(manifestPath);
795
800
  if (!manifest)
796
- return fail("send", `manifest is not valid JSON: ${shellDisplayPath(manifestPath)}`);
801
+ return fail("send", `manifest is not valid JSON: ${delegateDiagnosticPath(manifestPath)}`);
797
802
  if (send) {
798
803
  const publishedPath = path.join(context.root ?? context.cwd, jqRawPath(manifest, "publication.manifestPath"));
799
804
  if (malformedJsonFile(publishedPath)) {
@@ -1047,12 +1052,12 @@ function runVerify(args, context) {
1047
1052
  if (!manifestPath)
1048
1053
  return fail("verify", "--manifest is required (or --dry-run with --company/--project/--to)");
1049
1054
  if (!fs.existsSync(manifestPath) || !fs.statSync(manifestPath).isFile())
1050
- return fail("verify", `manifest not found: ${shellDisplayPath(manifestPath)}`);
1055
+ return fail("verify", `manifest not found: ${delegateDiagnosticPath(manifestPath)}`);
1051
1056
  if (malformedJsonFile(manifestPath))
1052
1057
  return runShellFallback("verify", args, context);
1053
1058
  const manifest = jsonFile(manifestPath);
1054
1059
  if (!manifest)
1055
- return fail("verify", `jq: error: ${shellDisplayPath(manifestPath)}: Invalid JSON`);
1060
+ return fail("verify", `jq: error: ${delegateDiagnosticPath(manifestPath)}: Invalid JSON`);
1056
1061
  const lock = `${manifestPath}.send-lock`;
1057
1062
  try {
1058
1063
  fs.mkdirSync(lock);
@@ -40,6 +40,7 @@ export declare function jqRawPaths(value: unknown, dottedPaths: string[], fallba
40
40
  export declare function requireRecord(value: unknown, property: string): JsonRecord;
41
41
  export declare function moveFile(source: string, target: string): void;
42
42
  export declare function writeJsonAtomically(target: string, value: unknown, siblingTemp?: boolean): void;
43
+ export declare function delegateDiagnosticPath(argument: string): string;
43
44
  /** Fast-entry adapter; family gating and shell fallback happen before import. */
44
45
  export declare function runDelegateFast(args: readonly string[], _stdin: string, cwd: string, hqRoot: string | undefined, commandName: string): number;
45
46
  /** Register the Wave 1a delegate commands and preserve the shell rollback path. */
@@ -188,6 +188,7 @@ export function moveFile(source, target) {
188
188
  }
189
189
  export function writeJsonAtomically(target, value, siblingTemp = false) {
190
190
  const serialized = serializeJqJson(value, "pretty");
191
+ // Native delegate JSON writers use LF on every platform.
191
192
  if (siblingTemp) {
192
193
  const temporary = `${target}.tmp`;
193
194
  fs.writeFileSync(temporary, serialized);
@@ -557,6 +558,13 @@ function parseStandard(name, args) {
557
558
  }
558
559
  return { values };
559
560
  }
561
+ export function delegateDiagnosticPath(argument) {
562
+ // Git Bash receives drive-qualified native paths in MSYS spelling. Preserve
563
+ // relative and already-MSYS path arguments exactly as supplied.
564
+ if (process.platform === "win32" && /^[a-z]:[\\/]/i.test(argument))
565
+ return shellDisplayPath(argument);
566
+ return argument;
567
+ }
560
568
  function existingManifest(name, args, context) {
561
569
  const prefix = `hq-delegate-${name}:`;
562
570
  const parsed = parseStandard(name, args);
@@ -575,11 +583,11 @@ function existingManifest(name, args, context) {
575
583
  }
576
584
  const file = path.resolve(context.cwd, manifestArg);
577
585
  if (!fs.existsSync(file) || !fs.statSync(file).isFile()) {
578
- writeErr(`${prefix} manifest not found: ${shellDisplayPath(manifestArg)}\n`);
586
+ writeErr(`${prefix} manifest not found: ${delegateDiagnosticPath(manifestArg)}\n`);
579
587
  return { code: 1, parsed };
580
588
  }
581
589
  if (!jqValid(file)) {
582
- writeErr(`${prefix} manifest is not valid JSON: ${shellDisplayPath(manifestArg)}\n`);
590
+ writeErr(`${prefix} manifest is not valid JSON: ${delegateDiagnosticPath(manifestArg)}\n`);
583
591
  return { code: 1, parsed };
584
592
  }
585
593
  return { file, parsed, document: readJsonFile(file) };
@@ -53,7 +53,7 @@ export interface ProviderCompactionDelta {
53
53
  replaced: boolean;
54
54
  }
55
55
  /** Read newly appended, newline-complete marker records after a saved byte offset. */
56
- export declare function probeProviderCompactionsSince(transcriptPath: string, provider: LaneProvider, startOffset: number): ProviderCompactionDelta;
56
+ export declare function probeProviderCompactionsSince(transcriptPath: string, provider: LaneProvider, startOffset: number, maxLineBytes?: number): ProviderCompactionDelta;
57
57
  export declare function providerTranscriptSize(transcriptPath: string): number;
58
58
  /**
59
59
  * Read only provider compaction markers from a lane transcript. Claude writes
@@ -64,7 +64,7 @@ export declare function probeProviderCompactions(transcriptPath: string | null,
64
64
  /** Locate `<home>/projects/<cwd-slug>/<sid>.jsonl` without deriving the slug. */
65
65
  export declare function findSessionTranscript(home: string, sessionId: string): string | null;
66
66
  /** Count compact boundaries in a transcript. Other lines are never parsed. */
67
- export declare function probeCompactBoundaries(transcriptPath: string | null): CompactBoundaryProbe;
67
+ export declare function probeCompactBoundaries(transcriptPath: string | null, maxLineBytes?: number): CompactBoundaryProbe;
68
68
  interface CompactionCommandResult {
69
69
  status: number | null;
70
70
  error?: string;
@@ -49,10 +49,116 @@ export function compactionDue(lane, nowMs, thresholdMs) {
49
49
  threshold_ms: thresholdMs,
50
50
  };
51
51
  }
52
- function isProviderMarkerLine(line, lineNumber, transcriptPath, provider) {
52
+ function readJsonStringAt(input, start) {
53
+ if (input[start] !== '"')
54
+ return null;
55
+ let index = start + 1;
56
+ while (index < input.length) {
57
+ if (input[index] === "\\") {
58
+ index += 2;
59
+ continue;
60
+ }
61
+ if (input[index] === '"') {
62
+ try {
63
+ return { value: JSON.parse(input.slice(start, index + 1)), end: index + 1 };
64
+ }
65
+ catch {
66
+ return null;
67
+ }
68
+ }
69
+ index += 1;
70
+ }
71
+ return null;
72
+ }
73
+ function skipJsonWhitespace(input, start) {
74
+ let index = start;
75
+ while (/\s/.test(input[index] ?? ""))
76
+ index += 1;
77
+ return index;
78
+ }
79
+ function skipJsonValue(input, start) {
80
+ const first = input[start];
81
+ if (first === '"')
82
+ return readJsonStringAt(input, start)?.end ?? null;
83
+ if (first !== "{" && first !== "[") {
84
+ let index = start;
85
+ while (index < input.length && input[index] !== "," && input[index] !== "}" && input[index] !== "]")
86
+ index += 1;
87
+ return index < input.length ? index : null;
88
+ }
89
+ const stack = [first === "{" ? "}" : "]"];
90
+ let index = start + 1;
91
+ while (index < input.length) {
92
+ const char = input[index];
93
+ if (char === '"') {
94
+ const parsed = readJsonStringAt(input, index);
95
+ if (!parsed)
96
+ return null;
97
+ index = parsed.end;
98
+ continue;
99
+ }
100
+ if (char === "{" || char === "[")
101
+ stack.push(char === "{" ? "}" : "]");
102
+ else if (char === "}" || char === "]") {
103
+ if (stack.pop() !== char)
104
+ return null;
105
+ if (stack.length === 0)
106
+ return index + 1;
107
+ }
108
+ index += 1;
109
+ }
110
+ return null;
111
+ }
112
+ /** Extract a top-level JSON value from a bounded prefix, even if its tail is truncated. */
113
+ function topLevelJsonValue(input, targetKey) {
114
+ let index = skipJsonWhitespace(input, 0);
115
+ if (input[index++] !== "{")
116
+ return null;
117
+ while (index < input.length) {
118
+ index = skipJsonWhitespace(input, index);
119
+ if (input[index] === "}")
120
+ return null;
121
+ const key = readJsonStringAt(input, index);
122
+ if (!key)
123
+ return null;
124
+ index = skipJsonWhitespace(input, key.end);
125
+ if (input[index++] !== ":")
126
+ return null;
127
+ index = skipJsonWhitespace(input, index);
128
+ const end = skipJsonValue(input, index);
129
+ if (key.value === targetKey)
130
+ return end === null ? null : input.slice(index, end);
131
+ if (end === null)
132
+ return null;
133
+ index = skipJsonWhitespace(input, end);
134
+ if (input[index] === ",")
135
+ index += 1;
136
+ else
137
+ return null;
138
+ }
139
+ return null;
140
+ }
141
+ function topLevelString(input, targetKey) {
142
+ const raw = topLevelJsonValue(input, targetKey);
143
+ if (!raw?.startsWith('"'))
144
+ return null;
145
+ try {
146
+ return JSON.parse(raw);
147
+ }
148
+ catch {
149
+ return null;
150
+ }
151
+ }
152
+ function isProviderMarkerLine(line, lineNumber, transcriptPath, provider, oversized = false) {
53
153
  const needle = provider === "claude" ? "compact_boundary" : "compacted";
54
154
  if (!line.includes(needle))
55
155
  return false;
156
+ if (oversized) {
157
+ const type = topLevelString(line, "type");
158
+ const subtype = topLevelString(line, "subtype");
159
+ return (provider === "claude" && type === "system" && subtype === "compact_boundary") ||
160
+ (provider === "codex" && type === "compacted");
161
+ }
56
162
  let record;
57
163
  try {
58
164
  const parsed = JSON.parse(line);
@@ -67,25 +173,61 @@ function isProviderMarkerLine(line, lineNumber, transcriptPath, provider) {
67
173
  return ((provider === "claude" && record.type === "system" && record.subtype === "compact_boundary") ||
68
174
  (provider === "codex" && record.type === "compacted"));
69
175
  }
70
- /** Read newly appended, newline-complete marker records after a saved byte offset. */
71
- export function probeProviderCompactionsSince(transcriptPath, provider, startOffset) {
72
- if (provider === "grok")
73
- return { count: 0, next_offset: startOffset, replaced: false };
176
+ const TRANSCRIPT_READ_CHUNK_BYTES = 1024 * 1024;
177
+ const TRANSCRIPT_MAX_LINE_BYTES = 64 * 1024 * 1024;
178
+ const TRANSCRIPT_OVERSIZED_PREFIX_BYTES = 64 * 1024;
179
+ /** Read JSONL incrementally, retaining at most one bounded line plus one chunk. */
180
+ function scanTranscriptLines(transcriptPath, startOffset, includeUnterminatedLast, onLine, maxLineBytes = TRANSCRIPT_MAX_LINE_BYTES) {
74
181
  const fd = fs.openSync(transcriptPath, "r");
75
182
  try {
76
- const size = fs.fstatSync(fd).size;
77
- if (startOffset > size)
78
- return { count: 0, next_offset: size, replaced: true };
79
- if (startOffset === size)
80
- return { count: 0, next_offset: size, replaced: false };
81
- const chunkSize = 1024 * 1024;
82
- const lineParts = [];
83
- let count = 0;
183
+ const fileSize = fs.fstatSync(fd).size;
184
+ if (startOffset > fileSize) {
185
+ return { file_size: fileSize, next_offset: fileSize, replaced: true };
186
+ }
84
187
  let position = startOffset;
85
- let nextOffset = startOffset;
86
188
  let lineNumber = 0;
87
- while (position < size) {
88
- const chunk = Buffer.allocUnsafe(Math.min(chunkSize, size - position));
189
+ let lineParts = [];
190
+ let lineBytes = 0;
191
+ let skipLongLine = false;
192
+ let nextOffset = startOffset;
193
+ const appendPart = (part) => {
194
+ if (part.length === 0 || skipLongLine)
195
+ return;
196
+ lineBytes += part.length;
197
+ if (lineBytes > maxLineBytes) {
198
+ skipLongLine = true;
199
+ const prefix = Buffer.alloc(Math.min(TRANSCRIPT_OVERSIZED_PREFIX_BYTES, lineBytes));
200
+ let copied = 0;
201
+ for (const previous of lineParts) {
202
+ const length = Math.min(previous.length, prefix.length - copied);
203
+ if (length <= 0)
204
+ break;
205
+ previous.copy(prefix, copied, 0, length);
206
+ copied += length;
207
+ }
208
+ const partLength = Math.min(part.length, prefix.length - copied);
209
+ if (partLength > 0)
210
+ part.copy(prefix, copied, 0, partLength);
211
+ lineParts = prefix.length > 0 ? [prefix] : [];
212
+ lineBytes = prefix.length;
213
+ return;
214
+ }
215
+ lineParts.push(part);
216
+ };
217
+ const finishLine = (endOffset) => {
218
+ lineNumber += 1;
219
+ const recognizedMarker = onLine(Buffer.concat(lineParts, lineBytes).toString("utf8"), lineNumber, skipLongLine);
220
+ if (skipLongLine && recognizedMarker !== true) {
221
+ process.stderr.write(`hq lanes: skipped oversized transcript line ${lineNumber} in ${transcriptPath} (over ${maxLineBytes} bytes)\n`);
222
+ }
223
+ lineParts = [];
224
+ lineBytes = 0;
225
+ skipLongLine = false;
226
+ nextOffset = endOffset;
227
+ };
228
+ while (position < fileSize) {
229
+ const chunk = Buffer.allocUnsafe(Math.min(TRANSCRIPT_READ_CHUNK_BYTES, fileSize - position));
230
+ const chunkStart = position;
89
231
  const read = fs.readSync(fd, chunk, 0, chunk.length, position);
90
232
  if (read === 0)
91
233
  break;
@@ -94,28 +236,34 @@ export function probeProviderCompactionsSince(transcriptPath, provider, startOff
94
236
  for (let index = 0; index < read; index += 1) {
95
237
  if (chunk[index] !== 0x0a)
96
238
  continue;
97
- lineParts.push(chunk.subarray(lineStart, index));
98
- const line = Buffer.concat(lineParts).toString("utf8");
99
- lineParts.length = 0;
100
- lineNumber += 1;
101
- if (isProviderMarkerLine(line, lineNumber, transcriptPath, provider))
102
- count += 1;
239
+ appendPart(chunk.subarray(lineStart, index));
240
+ finishLine(chunkStart + index + 1);
103
241
  lineStart = index + 1;
104
- nextOffset = position - (read - lineStart);
105
242
  }
106
243
  if (lineStart < read)
107
- lineParts.push(chunk.subarray(lineStart, read));
244
+ appendPart(chunk.subarray(lineStart, read));
108
245
  }
109
- return {
110
- count,
111
- next_offset: nextOffset,
112
- replaced: false,
113
- };
246
+ if (includeUnterminatedLast && (lineBytes > 0 || skipLongLine))
247
+ finishLine(fileSize);
248
+ return { file_size: fileSize, next_offset: nextOffset, replaced: false };
114
249
  }
115
250
  finally {
116
251
  fs.closeSync(fd);
117
252
  }
118
253
  }
254
+ /** Read newly appended, newline-complete marker records after a saved byte offset. */
255
+ export function probeProviderCompactionsSince(transcriptPath, provider, startOffset, maxLineBytes = TRANSCRIPT_MAX_LINE_BYTES) {
256
+ if (provider === "grok")
257
+ return { count: 0, next_offset: startOffset, replaced: false };
258
+ let count = 0;
259
+ const scan = scanTranscriptLines(transcriptPath, startOffset, false, (line, lineNumber, oversized) => {
260
+ const marker = isProviderMarkerLine(line, lineNumber, transcriptPath, provider, oversized);
261
+ if (marker)
262
+ count += 1;
263
+ return marker;
264
+ }, maxLineBytes);
265
+ return { count, next_offset: scan.next_offset, replaced: scan.replaced };
266
+ }
119
267
  export function providerTranscriptSize(transcriptPath) {
120
268
  try {
121
269
  return fs.statSync(transcriptPath).size;
@@ -166,39 +314,58 @@ export function findSessionTranscript(home, sessionId) {
166
314
  return null;
167
315
  }
168
316
  /** Count compact boundaries in a transcript. Other lines are never parsed. */
169
- export function probeCompactBoundaries(transcriptPath) {
317
+ export function probeCompactBoundaries(transcriptPath, maxLineBytes = TRANSCRIPT_MAX_LINE_BYTES) {
170
318
  if (transcriptPath === null)
171
319
  return { count: 0, last_pre_tokens: null };
172
- let text;
320
+ let count = 0;
321
+ let last = null;
173
322
  try {
174
- text = fs.readFileSync(transcriptPath, "utf8");
323
+ scanTranscriptLines(transcriptPath, 0, true, (line, lineNumber, oversized) => {
324
+ if (oversized) {
325
+ const marker = topLevelString(line, "type") === "system" && topLevelString(line, "subtype") === "compact_boundary";
326
+ if (!marker)
327
+ return false;
328
+ count += 1;
329
+ const rawMetadata = topLevelJsonValue(line, "compactMetadata");
330
+ if (rawMetadata) {
331
+ try {
332
+ const meta = JSON.parse(rawMetadata);
333
+ const pre = meta.preTokens;
334
+ last = typeof pre === "number" && Number.isFinite(pre) ? pre : null;
335
+ }
336
+ catch {
337
+ last = null;
338
+ }
339
+ }
340
+ else {
341
+ last = null;
342
+ }
343
+ return true;
344
+ }
345
+ if (!line.includes('"compact_boundary"'))
346
+ return;
347
+ let rec;
348
+ try {
349
+ rec = JSON.parse(line);
350
+ }
351
+ catch (err) {
352
+ process.stderr.write(`hq lanes: skipped an unparseable line in ${transcriptPath}: ${err instanceof Error ? err.message : String(err)}\n`);
353
+ return false;
354
+ }
355
+ if (rec.type !== "system" || rec.subtype !== "compact_boundary")
356
+ return false;
357
+ count += 1;
358
+ const meta = rec.compactMetadata;
359
+ const pre = meta?.preTokens;
360
+ last = typeof pre === "number" && Number.isFinite(pre) ? pre : null;
361
+ return true;
362
+ }, maxLineBytes);
175
363
  }
176
364
  catch (err) {
177
- const code = err.code;
178
- if (code === "ENOENT")
365
+ if (err.code === "ENOENT")
179
366
  return { count: 0, last_pre_tokens: null };
180
367
  throw err;
181
368
  }
182
- let count = 0;
183
- let last = null;
184
- for (const line of text.split("\n")) {
185
- if (!line.includes('"compact_boundary"'))
186
- continue;
187
- let rec;
188
- try {
189
- rec = JSON.parse(line);
190
- }
191
- catch (err) {
192
- process.stderr.write(`hq lanes: skipped an unparseable line in ${transcriptPath}: ${err instanceof Error ? err.message : String(err)}\n`);
193
- continue;
194
- }
195
- if (rec.type !== "system" || rec.subtype !== "compact_boundary")
196
- continue;
197
- count += 1;
198
- const meta = rec.compactMetadata;
199
- const pre = meta?.preTokens;
200
- last = typeof pre === "number" && Number.isFinite(pre) ? pre : null;
201
- }
202
369
  return { count, last_pre_tokens: last };
203
370
  }
204
371
  /** Synchronous `/compact`. stdout is discarded; stderr goes to the run dir. */
@@ -123,16 +123,39 @@ function refuseCredential(provider, file) {
123
123
  }
124
124
  }
125
125
  function sameContent(a, b) {
126
+ const chunkBytes = 1024 * 1024;
127
+ let aFd;
128
+ let bFd;
126
129
  try {
127
130
  const sa = fs.statSync(a);
128
131
  const sb = fs.statSync(b);
129
132
  if (sa.size !== sb.size)
130
133
  return false;
131
- return fs.readFileSync(a).equals(fs.readFileSync(b));
134
+ aFd = fs.openSync(a, "r");
135
+ bFd = fs.openSync(b, "r");
136
+ const aChunk = Buffer.allocUnsafe(chunkBytes);
137
+ const bChunk = Buffer.allocUnsafe(chunkBytes);
138
+ let position = 0;
139
+ while (position < sa.size) {
140
+ const requested = Math.min(chunkBytes, sa.size - position);
141
+ const aRead = fs.readSync(aFd, aChunk, 0, requested, position);
142
+ const bRead = fs.readSync(bFd, bChunk, 0, requested, position);
143
+ if (aRead !== requested || bRead !== requested || !aChunk.subarray(0, aRead).equals(bChunk.subarray(0, bRead))) {
144
+ return false;
145
+ }
146
+ position += requested;
147
+ }
148
+ return true;
132
149
  }
133
150
  catch {
134
151
  return false;
135
152
  }
153
+ finally {
154
+ if (aFd !== undefined)
155
+ fs.closeSync(aFd);
156
+ if (bFd !== undefined)
157
+ fs.closeSync(bFd);
158
+ }
136
159
  }
137
160
  /**
138
161
  * Copy one file via a temp name and rename, so a partial copy is never a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.345.12",
3
+ "version": "5.345.13",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {