@indigoai-us/hq-cli 5.108.15 → 5.108.17
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 +33 -2
- package/dist/lib/mesh/api.js +6 -1
- package/dist/lib/mesh/client.js +26 -6
- package/dist/lib/mesh/live/daemon/index.d.ts +2 -2
- package/dist/lib/mesh/live/daemon/index.js +1 -1
- package/dist/lib/mesh/live/daemon/run.d.ts +1 -0
- package/dist/lib/mesh/live/daemon/run.js +21 -2
- package/dist/lib/mesh/live/daemon/transcript-watch.d.ts +114 -7
- package/dist/lib/mesh/live/daemon/transcript-watch.js +458 -187
- package/dist/lib/work-context/outbox.d.ts +23 -3
- package/dist/lib/work-context/outbox.js +143 -14
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.108.17] — 2026-09-07
|
|
6
|
+
|
|
7
|
+
## [5.108.16] — 2026-09-07
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Work Mesh Live daemon no longer retries every queued work-context outbox
|
|
12
|
+
operation on every flush cycle. Outbox replay now applies per-operation
|
|
13
|
+
exponential backoff (`nextAttemptAt`, 30s base → 6h cap with jitter), a
|
|
14
|
+
per-cycle cap (200 due ops), an attempt ceiling (50 → quarantine as
|
|
15
|
+
`<code>_MAX_ATTEMPTS`), cheaper listing that skips re-reading unchanged
|
|
16
|
+
outbox files, and classifies HTTP 404/409/410/412 (and other 4xx) from
|
|
17
|
+
transport errors as non-retryable instead of retrying them forever as
|
|
18
|
+
`TRANSPORT_ERROR`.
|
|
19
|
+
|
|
5
20
|
## [5.108.15] — 2026-09-07
|
|
6
21
|
|
|
7
22
|
### Fixed
|
|
@@ -26,6 +41,15 @@
|
|
|
26
41
|
at most every five minutes, and `held.jsonl` is capped at 20,000 lines with
|
|
27
42
|
the oldest overflow dead-lettered as `HELD_OVERFLOW` (loss-free under failure
|
|
28
43
|
and concurrent flushers). (#525)
|
|
44
|
+
- The Work Mesh Live daemon's transcript watcher no longer rescans every file
|
|
45
|
+
under `~/.claude/projects` and `~/.codex/sessions` every 15 seconds. It keeps a
|
|
46
|
+
per-directory mtime cache and only relists directories that changed, re-stats
|
|
47
|
+
recently active files, revalidates every cached directory at least every five
|
|
48
|
+
minutes (content appends do not change a directory's mtime, so a session that
|
|
49
|
+
resumes after idling is still picked up), backs the interval off from 30 s to
|
|
50
|
+
120 s on busy machines, caps the entries examined per tick with per-root
|
|
51
|
+
budgets, never runs two ticks at once, and bounds its cache. Capped ticks skip
|
|
52
|
+
disappearance reconciliation so an unvisited session is never ended falsely.
|
|
29
53
|
|
|
30
54
|
## [5.108.14] — 2026-09-07
|
|
31
55
|
|
|
@@ -57,6 +81,15 @@
|
|
|
57
81
|
`incomplete_install` context so the next occurrence is attributable. The drop
|
|
58
82
|
is wired both at the top-level boundary and in the shared `beforeSend`, so it
|
|
59
83
|
covers every capture route.
|
|
84
|
+
- The Work Mesh Live daemon's transcript watcher no longer rescans every file
|
|
85
|
+
under `~/.claude/projects` and `~/.codex/sessions` every 15 seconds. It keeps a
|
|
86
|
+
per-directory mtime cache and only relists directories that changed, re-stats
|
|
87
|
+
recently active files, revalidates every cached directory at least every five
|
|
88
|
+
minutes (content appends do not change a directory's mtime, so a session that
|
|
89
|
+
resumes after idling is still picked up), backs the interval off from 30 s to
|
|
90
|
+
120 s on busy machines, caps the entries examined per tick with per-root
|
|
91
|
+
budgets, never runs two ticks at once, and bounds its cache. Capped ticks skip
|
|
92
|
+
disappearance reconciliation so an unvisited session is never ended falsely.
|
|
60
93
|
|
|
61
94
|
## [5.108.13] — 2026-09-06
|
|
62
95
|
|
|
@@ -218,7 +251,6 @@
|
|
|
218
251
|
and `hq reindex` now prunes local session-log copies 7 days after the vault
|
|
219
252
|
confirms them (`HQ_SESSION_LOG_LOCAL_RETENTION_DAYS`, `off` to disable).
|
|
220
253
|
|
|
221
|
-
|
|
222
254
|
## [5.108.5] — 2026-09-04
|
|
223
255
|
|
|
224
256
|
### Added
|
|
@@ -1659,7 +1691,6 @@ All self-update behavior honors the existing `HQ_NO_UPDATE_CHECK=1` opt-out.
|
|
|
1659
1691
|
|
|
1660
1692
|
- **`hq db provision` / `hq db status --remote` use Cognito + vault API** to call the live control plane (`POST /v1/db/provision`, `GET /v1/db/status`). Team plan required for provision; local `status|sql|migrate` unchanged.
|
|
1661
1693
|
|
|
1662
|
-
|
|
1663
1694
|
## [5.55.1]
|
|
1664
1695
|
|
|
1665
1696
|
### Fixed
|
package/dist/lib/mesh/api.js
CHANGED
|
@@ -43,7 +43,12 @@ export async function meshJson(token, path, init = {}) {
|
|
|
43
43
|
}
|
|
44
44
|
if (!res.ok) {
|
|
45
45
|
const err = data;
|
|
46
|
-
|
|
46
|
+
// Always lead with the HTTP status: callers classify retryable vs permanent
|
|
47
|
+
// failures from the message (see createWorkSessionDeliverer). Without it a
|
|
48
|
+
// 400 whose body text matched no pattern was retried forever.
|
|
49
|
+
const detail = err.error || err.message || res.statusText || "request failed";
|
|
50
|
+
const code = typeof err.code === "string" && err.code ? `${err.code}: ` : "";
|
|
51
|
+
throw new Error(`${res.status} ${code}${detail}`);
|
|
47
52
|
}
|
|
48
53
|
return data;
|
|
49
54
|
}
|
package/dist/lib/mesh/client.js
CHANGED
|
@@ -46,17 +46,37 @@ export function createWorkSessionDeliverer(opts) {
|
|
|
46
46
|
}
|
|
47
47
|
catch (err) {
|
|
48
48
|
const message = err instanceof Error ? err.message : String(err);
|
|
49
|
-
// meshJson throws on !ok with error text; classify
|
|
50
|
-
|
|
49
|
+
// meshJson throws on !ok with error text; classify by leading status when present.
|
|
50
|
+
const statusMatch = /^(\d{3})\b/.exec(message);
|
|
51
|
+
if (statusMatch) {
|
|
52
|
+
const status = Number(statusMatch[1]);
|
|
53
|
+
if (status === 401 || status === 403) {
|
|
54
|
+
return { ok: false, retryable: false, code: "AUTH_DENIED" };
|
|
55
|
+
}
|
|
56
|
+
if (status === 400 || status === 422) {
|
|
57
|
+
return { ok: false, retryable: false, code: "VALIDATION_FAILED" };
|
|
58
|
+
}
|
|
59
|
+
if (status === 429 || (status >= 500 && status <= 599)) {
|
|
60
|
+
return { ok: false, retryable: true, code: `HTTP_${status}` };
|
|
61
|
+
}
|
|
62
|
+
if (status === 404 ||
|
|
63
|
+
status === 409 ||
|
|
64
|
+
status === 410 ||
|
|
65
|
+
status === 412 ||
|
|
66
|
+
(status >= 400 && status < 500)) {
|
|
67
|
+
return { ok: false, retryable: false, code: `HTTP_${status}` };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (/unauthorized|forbidden|auth/i.test(message)) {
|
|
51
71
|
return { ok: false, retryable: false, code: "AUTH_DENIED" };
|
|
52
72
|
}
|
|
53
|
-
if (
|
|
73
|
+
if (/invalid|validation/i.test(message)) {
|
|
54
74
|
return { ok: false, retryable: false, code: "VALIDATION_FAILED" };
|
|
55
75
|
}
|
|
56
|
-
if (
|
|
76
|
+
if (/ECONN|ENOTFOUND|ETIMEDOUT|network|fetch failed/i.test(message)) {
|
|
57
77
|
return { ok: false, retryable: true, code: "NETWORK_OR_5XX" };
|
|
58
78
|
}
|
|
59
|
-
//
|
|
79
|
+
// No status in the message: keep queued (fail open to retry, not quarantine).
|
|
60
80
|
return { ok: false, retryable: true, code: "TRANSPORT_ERROR" };
|
|
61
81
|
}
|
|
62
82
|
};
|
|
@@ -73,7 +93,7 @@ function mapRegisterResponse(status, body, operationId) {
|
|
|
73
93
|
if (status >= 500 || status === 429) {
|
|
74
94
|
return { ok: false, retryable: true, code: `HTTP_${status}` };
|
|
75
95
|
}
|
|
76
|
-
if (status
|
|
96
|
+
if (status >= 400 && status < 500) {
|
|
77
97
|
return { ok: false, retryable: false, code: `HTTP_${status}` };
|
|
78
98
|
}
|
|
79
99
|
if (status === 0) {
|
|
@@ -19,6 +19,6 @@ export type { DaemonDoctorDeps, DaemonDoctorReport } from "./doctor.js";
|
|
|
19
19
|
export { scopeBundleToAgentIdentity, } from "./credentials.js";
|
|
20
20
|
export { FLUSH_INTERVAL_MS, SPOOL_DEBOUNCE_MS, runMeshDaemon, } from "./run.js";
|
|
21
21
|
export type { DaemonHandle, DaemonRunDeps } from "./run.js";
|
|
22
|
-
export { TRANSCRIPT_ADAPTER_VERSION, TRANSCRIPT_EVENT_SOURCE, TRANSCRIPT_FRESH_MS, TRANSCRIPT_HOOK_COVER_MS, TRANSCRIPT_MARKER, TRANSCRIPT_SESSION_END_MS, TRANSCRIPT_TURN_QUIET_MS, TRANSCRIPT_WATCH_INTERVAL_MS, TranscriptWatcher, decodeClaudeProjectDirName, defaultTranscriptFs, discoverTranscripts, isTranscriptHookCovered, noteHookSessionsFromSpoolFile, resolveTranscriptRegistration, sessionIdFromClaudeTranscriptPath, sessionIdFromCodexTranscriptPath, } from "./transcript-watch.js";
|
|
23
|
-
export type { DiscoveredTranscript, TranscriptFs, TranscriptKind, TranscriptWatchDeps, TranscriptWatchTickResult, } from "./transcript-watch.js";
|
|
22
|
+
export { TRANSCRIPT_ADAPTER_VERSION, TRANSCRIPT_EVENT_SOURCE, TRANSCRIPT_FRESH_MS, TRANSCRIPT_HOOK_COVER_MS, TRANSCRIPT_MARKER, TRANSCRIPT_SESSION_END_MS, TRANSCRIPT_TURN_QUIET_MS, TRANSCRIPT_WATCH_BACKOFF_DURATION_MS, TRANSCRIPT_WATCH_BACKOFF_ENTRIES, TRANSCRIPT_WATCH_ENTRY_CAP, TRANSCRIPT_WATCH_INTERVAL_MAX_MS, TRANSCRIPT_WATCH_INTERVAL_MS, TRANSCRIPT_WATCH_REVALIDATE_MS, TranscriptWatcher, createTranscriptDiscoverCache, decodeClaudeProjectDirName, defaultTranscriptFs, discoverTranscripts, discoverTranscriptsWithMeta, isTranscriptHookCovered, nextTranscriptWatchIntervalMs, noteHookSessionsFromSpoolFile, resolveTranscriptRegistration, sessionIdFromClaudeTranscriptPath, sessionIdFromCodexTranscriptPath, sortDiscoveries, } from "./transcript-watch.js";
|
|
23
|
+
export type { DiscoveredTranscript, DiscoverScanMeta, DiscoverTranscriptsResult, TranscriptDiscoverCache, TranscriptFs, TranscriptKind, TranscriptWatchDeps, TranscriptWatchTickResult, } from "./transcript-watch.js";
|
|
24
24
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -10,5 +10,5 @@ export { buildInstallPaths, daemonServiceStatus, detectPlatform, installDaemonSe
|
|
|
10
10
|
export { UNHEALTHY_SPOOL_AGE_MS, collectDaemonDoctor, formatDaemonDoctor, } from "./doctor.js";
|
|
11
11
|
export { scopeBundleToAgentIdentity, } from "./credentials.js";
|
|
12
12
|
export { FLUSH_INTERVAL_MS, SPOOL_DEBOUNCE_MS, runMeshDaemon, } from "./run.js";
|
|
13
|
-
export { TRANSCRIPT_ADAPTER_VERSION, TRANSCRIPT_EVENT_SOURCE, TRANSCRIPT_FRESH_MS, TRANSCRIPT_HOOK_COVER_MS, TRANSCRIPT_MARKER, TRANSCRIPT_SESSION_END_MS, TRANSCRIPT_TURN_QUIET_MS, TRANSCRIPT_WATCH_INTERVAL_MS, TranscriptWatcher, decodeClaudeProjectDirName, defaultTranscriptFs, discoverTranscripts, isTranscriptHookCovered, noteHookSessionsFromSpoolFile, resolveTranscriptRegistration, sessionIdFromClaudeTranscriptPath, sessionIdFromCodexTranscriptPath, } from "./transcript-watch.js";
|
|
13
|
+
export { TRANSCRIPT_ADAPTER_VERSION, TRANSCRIPT_EVENT_SOURCE, TRANSCRIPT_FRESH_MS, TRANSCRIPT_HOOK_COVER_MS, TRANSCRIPT_MARKER, TRANSCRIPT_SESSION_END_MS, TRANSCRIPT_TURN_QUIET_MS, TRANSCRIPT_WATCH_BACKOFF_DURATION_MS, TRANSCRIPT_WATCH_BACKOFF_ENTRIES, TRANSCRIPT_WATCH_ENTRY_CAP, TRANSCRIPT_WATCH_INTERVAL_MAX_MS, TRANSCRIPT_WATCH_INTERVAL_MS, TRANSCRIPT_WATCH_REVALIDATE_MS, TranscriptWatcher, createTranscriptDiscoverCache, decodeClaudeProjectDirName, defaultTranscriptFs, discoverTranscripts, discoverTranscriptsWithMeta, isTranscriptHookCovered, nextTranscriptWatchIntervalMs, noteHookSessionsFromSpoolFile, resolveTranscriptRegistration, sessionIdFromClaudeTranscriptPath, sessionIdFromCodexTranscriptPath, sortDiscoveries, } from "./transcript-watch.js";
|
|
14
14
|
//# sourceMappingURL=index.js.map
|
|
@@ -255,7 +255,14 @@ export async function runMeshDaemon(deps = {}) {
|
|
|
255
255
|
noteHookSessionsFromSpoolFile(workMeshHeldPath(meshRoot), lastHookEventAt, at);
|
|
256
256
|
const summary = await flushFn();
|
|
257
257
|
try {
|
|
258
|
-
await replayFn();
|
|
258
|
+
const replay = await replayFn();
|
|
259
|
+
const delivered = replay.delivered ?? 0;
|
|
260
|
+
const queued = replay.queued ?? 0;
|
|
261
|
+
const quarantined = replay.quarantined ?? 0;
|
|
262
|
+
const skipped = replay.skipped ?? 0;
|
|
263
|
+
if (delivered > 0 || queued > 0 || quarantined > 0 || skipped > 0) {
|
|
264
|
+
log(dir, `outbox replay: delivered=${delivered} queued=${queued} quarantined=${quarantined} skipped=${skipped}`);
|
|
265
|
+
}
|
|
259
266
|
}
|
|
260
267
|
catch (err) {
|
|
261
268
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -351,6 +358,17 @@ export async function runMeshDaemon(deps = {}) {
|
|
|
351
358
|
return;
|
|
352
359
|
try {
|
|
353
360
|
const summary = await transcriptWatcher.tick();
|
|
361
|
+
if (summary.skippedConcurrent)
|
|
362
|
+
return;
|
|
363
|
+
if (summary.intervalChanged) {
|
|
364
|
+
log(dir, `transcript-watch interval ${summary.previousIntervalMs}ms -> ${summary.intervalMs}ms`);
|
|
365
|
+
}
|
|
366
|
+
if (summary.capEpisodeStart) {
|
|
367
|
+
log(dir, `transcript-watch entry cap hit (examined=${summary.entriesExamined})`);
|
|
368
|
+
}
|
|
369
|
+
if (summary.cacheOverflowCleared) {
|
|
370
|
+
log(dir, "transcript-watch cache overflow; cleared for full rescan");
|
|
371
|
+
}
|
|
354
372
|
if (summary.started > 0 ||
|
|
355
373
|
summary.turns > 0 ||
|
|
356
374
|
summary.ended > 0) {
|
|
@@ -370,7 +388,8 @@ export async function runMeshDaemon(deps = {}) {
|
|
|
370
388
|
void runTranscriptTick().finally(() => {
|
|
371
389
|
if (stopped)
|
|
372
390
|
return;
|
|
373
|
-
|
|
391
|
+
const delay = transcriptWatcher?.getIntervalMs() ?? TRANSCRIPT_WATCH_INTERVAL_MS;
|
|
392
|
+
transcriptIntervalHandle = timers.setTimeout(transcriptTick, delay);
|
|
374
393
|
});
|
|
375
394
|
}, TRANSCRIPT_WATCH_INTERVAL_MS);
|
|
376
395
|
}
|
|
@@ -2,7 +2,16 @@
|
|
|
2
2
|
* Transcript-watch fallback for hookless hosts (US-018 / AMEND-001).
|
|
3
3
|
*
|
|
4
4
|
* Enumerates ~/.claude/projects/ (recursive *.jsonl) and ~/.codex/sessions/
|
|
5
|
-
*
|
|
5
|
+
* on an adaptive interval (default 30s, backoff to 120s) using only readdir +
|
|
6
|
+
* stat (mtime). Never opens or reads transcript bytes.
|
|
7
|
+
*
|
|
8
|
+
* Scans are incremental: directory mtime is cached so unchanged directories are
|
|
9
|
+
* not re-listed; only files in changed directories (plus files still inside the
|
|
10
|
+
* active mtime window) are re-statted. Every TRANSCRIPT_WATCH_REVALIDATE_MS a
|
|
11
|
+
* cached directory is force-relisted so idle appends and coarse mtimes cannot
|
|
12
|
+
* hide changes forever. A per-tick entry cap (split across Claude/Codex roots)
|
|
13
|
+
* bounds pathological trees; capped ticks skip disappearance reconciliation.
|
|
14
|
+
* Ticks never overlap (a concurrent call is skipped).
|
|
6
15
|
*
|
|
7
16
|
* Session identity (deterministic):
|
|
8
17
|
* - Claude: ~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl
|
|
@@ -22,7 +31,21 @@ import { reconcileObservation, type ReconcileDeps } from "../../../work-context/
|
|
|
22
31
|
import { readSessionState, writeSessionState, type SessionStateFile } from "../../../work-context/state.js";
|
|
23
32
|
import { enqueueSessionEvent } from "../enqueue.js";
|
|
24
33
|
import type { SessionEventHarness } from "../format-spool-line.js";
|
|
25
|
-
export declare const TRANSCRIPT_WATCH_INTERVAL_MS =
|
|
34
|
+
export declare const TRANSCRIPT_WATCH_INTERVAL_MS = 30000;
|
|
35
|
+
/** Ceiling for adaptive backoff after heavy ticks. */
|
|
36
|
+
export declare const TRANSCRIPT_WATCH_INTERVAL_MAX_MS = 120000;
|
|
37
|
+
/** Double the interval when a tick examines more than this many entries. */
|
|
38
|
+
export declare const TRANSCRIPT_WATCH_BACKOFF_ENTRIES = 2000;
|
|
39
|
+
/** Double the interval when a tick takes longer than this many ms. */
|
|
40
|
+
export declare const TRANSCRIPT_WATCH_BACKOFF_DURATION_MS = 500;
|
|
41
|
+
/** Hard cap on entries examined (dir stats, readdir children, file stats) per tick. */
|
|
42
|
+
export declare const TRANSCRIPT_WATCH_ENTRY_CAP = 20000;
|
|
43
|
+
/**
|
|
44
|
+
* Even when a directory mtime matches the cache, force a full relist (readdir +
|
|
45
|
+
* re-stat) after this many ms so content appends and coarse mtime granularity
|
|
46
|
+
* cannot hide membership or mtime changes indefinitely.
|
|
47
|
+
*/
|
|
48
|
+
export declare const TRANSCRIPT_WATCH_REVALIDATE_MS: number;
|
|
26
49
|
export declare const TRANSCRIPT_TURN_QUIET_MS = 5000;
|
|
27
50
|
export declare const TRANSCRIPT_SESSION_END_MS: number;
|
|
28
51
|
export declare const TRANSCRIPT_HOOK_COVER_MS = 60000;
|
|
@@ -86,6 +109,8 @@ export interface TranscriptWatchDeps {
|
|
|
86
109
|
reconcile?: typeof reconcileObservation;
|
|
87
110
|
claudeProjectsDir?: string;
|
|
88
111
|
codexSessionsDir?: string;
|
|
112
|
+
/** Override entry cap (tests). Defaults to TRANSCRIPT_WATCH_ENTRY_CAP. */
|
|
113
|
+
entryCap?: number;
|
|
89
114
|
}
|
|
90
115
|
export interface TranscriptWatchTickResult {
|
|
91
116
|
discovered: number;
|
|
@@ -94,7 +119,43 @@ export interface TranscriptWatchTickResult {
|
|
|
94
119
|
ended: number;
|
|
95
120
|
skippedHookCovered: number;
|
|
96
121
|
skippedStale: number;
|
|
122
|
+
/** Dir stats + readdir children + file stats examined this tick. */
|
|
123
|
+
entriesExamined: number;
|
|
124
|
+
durationMs: number;
|
|
125
|
+
/** True when TRANSCRIPT_WATCH_ENTRY_CAP stopped the walk early. */
|
|
126
|
+
capped: boolean;
|
|
127
|
+
/** True on the first capped tick of a contiguous capped episode (for logging). */
|
|
128
|
+
capEpisodeStart: boolean;
|
|
129
|
+
/** True when the discover cache was cleared due to exceeding the file cap. */
|
|
130
|
+
cacheOverflowCleared: boolean;
|
|
131
|
+
/** Interval after this tick's adaptive backoff adjustment. */
|
|
132
|
+
intervalMs: number;
|
|
133
|
+
previousIntervalMs: number;
|
|
134
|
+
intervalChanged: boolean;
|
|
135
|
+
/** True when a previous tick was still running (this call was a no-op). */
|
|
136
|
+
skippedConcurrent: boolean;
|
|
137
|
+
}
|
|
138
|
+
/** Per-directory mtime + child cache for incremental discovery. */
|
|
139
|
+
export interface TranscriptDirCacheEntry {
|
|
140
|
+
mtimeMs: number;
|
|
141
|
+
files: DiscoveredTranscript[];
|
|
142
|
+
subdirs: string[];
|
|
143
|
+
/** Wall time of the last complete listing of this directory. */
|
|
144
|
+
lastFullStatAtMs: number;
|
|
145
|
+
}
|
|
146
|
+
export interface TranscriptDiscoverCache {
|
|
147
|
+
dirs: Map<string, TranscriptDirCacheEntry>;
|
|
97
148
|
}
|
|
149
|
+
export interface DiscoverScanMeta {
|
|
150
|
+
entriesExamined: number;
|
|
151
|
+
dirsListed: number;
|
|
152
|
+
dirsSkipped: number;
|
|
153
|
+
filesStatted: number;
|
|
154
|
+
capped: boolean;
|
|
155
|
+
/** True when cache file count exceeded the entry cap and the cache was cleared. */
|
|
156
|
+
cacheOverflowCleared: boolean;
|
|
157
|
+
}
|
|
158
|
+
export declare function createTranscriptDiscoverCache(): TranscriptDiscoverCache;
|
|
98
159
|
/**
|
|
99
160
|
* Claude Code encodes an absolute cwd as a directory name by replacing every
|
|
100
161
|
* "/" with "-". Decoding is best-effort (hyphenated path segments are ambiguous).
|
|
@@ -110,15 +171,41 @@ export declare function sessionIdFromClaudeTranscriptPath(filePath: string): str
|
|
|
110
171
|
* Rule: trailing UUID in basename, else basename without extension.
|
|
111
172
|
*/
|
|
112
173
|
export declare function sessionIdFromCodexTranscriptPath(filePath: string): string | null;
|
|
113
|
-
|
|
114
|
-
* Enumerate transcript files with mtime only (no content read).
|
|
115
|
-
*/
|
|
116
|
-
export declare function discoverTranscripts(opts: {
|
|
174
|
+
export interface DiscoverTranscriptsOptions {
|
|
117
175
|
home?: string;
|
|
118
176
|
fs?: TranscriptFs;
|
|
119
177
|
claudeProjectsDir?: string;
|
|
120
178
|
codexSessionsDir?: string;
|
|
121
|
-
|
|
179
|
+
/** When set, reuse/update this cache for incremental scans. */
|
|
180
|
+
cache?: TranscriptDiscoverCache;
|
|
181
|
+
/** Clock for active-window re-stat decisions. Defaults to Date.now(). */
|
|
182
|
+
nowMs?: number;
|
|
183
|
+
/** Override entry cap (tests). Defaults to TRANSCRIPT_WATCH_ENTRY_CAP. */
|
|
184
|
+
entryCap?: number;
|
|
185
|
+
/** Active mtime window for re-stat in unchanged dirs. */
|
|
186
|
+
activeWindowMs?: number;
|
|
187
|
+
}
|
|
188
|
+
export interface DiscoverTranscriptsResult {
|
|
189
|
+
transcripts: DiscoveredTranscript[];
|
|
190
|
+
meta: DiscoverScanMeta;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Enumerate transcript files with mtime only (no content read).
|
|
194
|
+
* Pass `cache` to skip readdir on directories whose mtime is unchanged
|
|
195
|
+
* (until TRANSCRIPT_WATCH_REVALIDATE_MS forces a relist).
|
|
196
|
+
*
|
|
197
|
+
* Each root gets half of `entryCap`; unused budget from the first root is
|
|
198
|
+
* available to the second so a huge Claude tree cannot starve Codex.
|
|
199
|
+
*/
|
|
200
|
+
export declare function discoverTranscriptsWithMeta(opts?: DiscoverTranscriptsOptions): DiscoverTranscriptsResult;
|
|
201
|
+
/**
|
|
202
|
+
* Enumerate transcript files with mtime only (no content read).
|
|
203
|
+
* Without a cache this is a full walk; with `cache` it is incremental.
|
|
204
|
+
*/
|
|
205
|
+
export declare function discoverTranscripts(opts?: DiscoverTranscriptsOptions): DiscoveredTranscript[];
|
|
206
|
+
/** Sort key for comparing discovery results independent of walk order. */
|
|
207
|
+
export declare function discoveryFingerprint(d: DiscoveredTranscript): string;
|
|
208
|
+
export declare function sortDiscoveries(items: DiscoveredTranscript[]): DiscoveredTranscript[];
|
|
122
209
|
/**
|
|
123
210
|
* Whether hooks already cover this session: durable hook-written state, or a
|
|
124
211
|
* hook event observed within the last TRANSCRIPT_HOOK_COVER_MS.
|
|
@@ -156,10 +243,23 @@ export declare class TranscriptWatcher {
|
|
|
156
243
|
private readonly readState;
|
|
157
244
|
private readonly writeState;
|
|
158
245
|
private readonly reconcileFn;
|
|
246
|
+
private readonly discoverCache;
|
|
247
|
+
private intervalMs;
|
|
248
|
+
private tickInFlight;
|
|
249
|
+
/** Tracks contiguous capped ticks so we log once per cap episode. */
|
|
250
|
+
private inCapEpisode;
|
|
159
251
|
constructor(deps: TranscriptWatchDeps);
|
|
252
|
+
/** Current adaptive poll interval (after the last tick's backoff). */
|
|
253
|
+
getIntervalMs(): number;
|
|
254
|
+
/** Expose discovery cache for tests. */
|
|
255
|
+
getDiscoverCache(): TranscriptDiscoverCache;
|
|
160
256
|
/** Record that a hook path emitted an event for sessionId (daemon flush). */
|
|
161
257
|
noteHookEvent(sessionId: string, atMs?: number): void;
|
|
162
258
|
tick(): Promise<TranscriptWatchTickResult>;
|
|
259
|
+
/**
|
|
260
|
+
* Heavy ticks double the poll interval up to MAX; a quiet tick resets to base.
|
|
261
|
+
*/
|
|
262
|
+
private applyIntervalBackoff;
|
|
163
263
|
/**
|
|
164
264
|
* Register via reconcileObservation (membership-validated slug→uid), then
|
|
165
265
|
* enqueue session_start. Never overwrites a hook-written state.
|
|
@@ -167,4 +267,11 @@ export declare class TranscriptWatcher {
|
|
|
167
267
|
private emitStart;
|
|
168
268
|
private emitKind;
|
|
169
269
|
}
|
|
270
|
+
/**
|
|
271
|
+
* Adaptive interval: busy ticks double up to MAX; quiet ticks return to base.
|
|
272
|
+
*/
|
|
273
|
+
export declare function nextTranscriptWatchIntervalMs(currentMs: number, opts: {
|
|
274
|
+
entriesExamined: number;
|
|
275
|
+
durationMs: number;
|
|
276
|
+
}): number;
|
|
170
277
|
//# sourceMappingURL=transcript-watch.d.ts.map
|