nterminal 1.3.3 → 1.3.5
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/bin/nterminal.js +8 -3
- package/dist/client/assets/MarkdownPreview-BIrwUAaa.js +1 -0
- package/dist/client/assets/TranscriptMarkdownBody-CI0ztvMX.js +3 -0
- package/dist/client/assets/{index-DDCpqDNb.js → index-EN8ngMgv.js} +1 -1
- package/dist/client/assets/index-JQJ1Eet1.js +58 -0
- package/dist/client/assets/index-xkL4h3qt.css +1 -0
- package/dist/client/index.html +2 -2
- package/dist/scripts/hooks.js +83 -0
- package/dist/scripts/hooks.js.map +1 -0
- package/dist/server/accounts/accountService.d.ts +23 -1
- package/dist/server/accounts/accountService.js +235 -103
- package/dist/server/accounts/accountService.js.map +1 -1
- package/dist/server/accounts/providerRequests.d.ts +12 -0
- package/dist/server/accounts/providerRequests.js +159 -0
- package/dist/server/accounts/providerRequests.js.map +1 -0
- package/dist/server/agent/agentRoutes.js +11 -0
- package/dist/server/agent/agentRoutes.js.map +1 -1
- package/dist/server/hooks/helperJournal.d.ts +16 -0
- package/dist/server/hooks/helperJournal.js +74 -0
- package/dist/server/hooks/helperJournal.js.map +1 -0
- package/dist/server/hooks/hookBinding.d.ts +39 -0
- package/dist/server/hooks/hookBinding.js +91 -0
- package/dist/server/hooks/hookBinding.js.map +1 -0
- package/dist/server/hooks/hookFiles.d.ts +24 -0
- package/dist/server/hooks/hookFiles.js +160 -0
- package/dist/server/hooks/hookFiles.js.map +1 -0
- package/dist/server/hooks/hookInstallation.d.ts +79 -0
- package/dist/server/hooks/hookInstallation.js +978 -0
- package/dist/server/hooks/hookInstallation.js.map +1 -0
- package/dist/server/hooks/hookProcess.d.ts +5 -0
- package/dist/server/hooks/hookProcess.js +85 -0
- package/dist/server/hooks/hookProcess.js.map +1 -0
- package/dist/server/hooks/hookReceiver.d.ts +66 -0
- package/dist/server/hooks/hookReceiver.js +549 -0
- package/dist/server/hooks/hookReceiver.js.map +1 -0
- package/dist/server/hooks/lifecycle.d.ts +40 -0
- package/dist/server/hooks/lifecycle.js +200 -0
- package/dist/server/hooks/lifecycle.js.map +1 -0
- package/dist/server/hooks/publicationLock.d.ts +3 -0
- package/dist/server/hooks/publicationLock.js +137 -0
- package/dist/server/hooks/publicationLock.js.map +1 -0
- package/dist/server/hooks/verifiedLock.d.ts +24 -0
- package/dist/server/hooks/verifiedLock.js +207 -0
- package/dist/server/hooks/verifiedLock.js.map +1 -0
- package/dist/server/http.d.ts +4 -0
- package/dist/server/http.js +6 -0
- package/dist/server/http.js.map +1 -1
- package/dist/server/index.js +69 -2
- package/dist/server/index.js.map +1 -1
- package/dist/server/notifications/agentCompletionMonitor.d.ts +16 -1
- package/dist/server/notifications/agentCompletionMonitor.js +261 -47
- package/dist/server/notifications/agentCompletionMonitor.js.map +1 -1
- package/dist/server/notifications/completionNotificationRouter.d.ts +2 -0
- package/dist/server/notifications/completionNotificationRouter.js +9 -0
- package/dist/server/notifications/completionNotificationRouter.js.map +1 -1
- package/dist/server/routes/accountRoutes.js +11 -0
- package/dist/server/routes/accountRoutes.js.map +1 -1
- package/dist/server/routes/activityRoutes.d.ts +13 -0
- package/dist/server/routes/activityRoutes.js +165 -0
- package/dist/server/routes/activityRoutes.js.map +1 -0
- package/dist/server/routes/agentManagementRoutes.js +8 -1
- package/dist/server/routes/agentManagementRoutes.js.map +1 -1
- package/dist/server/routes/hookRoutes.d.ts +16 -0
- package/dist/server/routes/hookRoutes.js +77 -0
- package/dist/server/routes/hookRoutes.js.map +1 -0
- package/dist/server/routes/terminalRoutes.js +4 -0
- package/dist/server/routes/terminalRoutes.js.map +1 -1
- package/dist/server/routes/transcriptQuery.d.ts +2 -1
- package/dist/server/routes/transcriptQuery.js +3 -1
- package/dist/server/routes/transcriptQuery.js.map +1 -1
- package/dist/server/terminal/PtyAdapter.d.ts +18 -0
- package/dist/server/terminal/PtyAdapter.js.map +1 -1
- package/dist/server/terminal/TerminalManager.d.ts +61 -0
- package/dist/server/terminal/TerminalManager.js +665 -12
- package/dist/server/terminal/TerminalManager.js.map +1 -1
- package/dist/server/terminal/TmuxPtyAdapter.d.ts +14 -2
- package/dist/server/terminal/TmuxPtyAdapter.js +104 -16
- package/dist/server/terminal/TmuxPtyAdapter.js.map +1 -1
- package/dist/server/terminal/activityChannel.d.ts +57 -0
- package/dist/server/terminal/activityChannel.js +166 -0
- package/dist/server/terminal/activityChannel.js.map +1 -0
- package/dist/server/terminal/agentTranscriptStatus.d.ts +12 -0
- package/dist/server/terminal/agentTranscriptStatus.js +78 -7
- package/dist/server/terminal/agentTranscriptStatus.js.map +1 -1
- package/dist/server/terminal/codexTranscriptSource.d.ts +2 -0
- package/dist/server/terminal/codexTranscriptSource.js +53 -46
- package/dist/server/terminal/codexTranscriptSource.js.map +1 -1
- package/dist/server/terminal/subagentSources.js +2 -1
- package/dist/server/terminal/subagentSources.js.map +1 -1
- package/dist/server/terminal/transcriptHeader.d.ts +85 -0
- package/dist/server/terminal/transcriptHeader.js +376 -0
- package/dist/server/terminal/transcriptHeader.js.map +1 -0
- package/dist/server/terminal/transcriptHistory.d.ts +16 -2
- package/dist/server/terminal/transcriptHistory.js +120 -21
- package/dist/server/terminal/transcriptHistory.js.map +1 -1
- package/dist/server/terminal/transcriptStore.d.ts +6 -0
- package/dist/server/terminal/transcriptStore.js +222 -26
- package/dist/server/terminal/transcriptStore.js.map +1 -1
- package/dist/shared/activityProbe.d.ts +10 -0
- package/dist/shared/activityProbe.js +24 -0
- package/dist/shared/activityProbe.js.map +1 -0
- package/dist/shared/protocol.d.ts +45 -0
- package/dist/shared/protocol.js.map +1 -1
- package/dist/shared/types.d.ts +9 -1
- package/docs/features.md +10 -0
- package/docs/provider-hooks.md +287 -0
- package/docs/terminal-history.md +10 -0
- package/package.json +2 -1
- package/scripts/nterminal-hook.cjs +171 -0
- package/scripts/nterminalctl +26 -2
- package/dist/client/assets/MarkdownPreview-CWK2qIZp.js +0 -1
- package/dist/client/assets/TranscriptMarkdownBody-Ddi-rw6X.js +0 -3
- package/dist/client/assets/index-CcAQThtj.js +0 -56
- package/dist/client/assets/index-DTTX24tu.css +0 -1
|
@@ -7,7 +7,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
7
7
|
import * as nodePty from 'node-pty';
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { normalizeTerminalTitle, PtySessionUnavailableError } from './PtyAdapter.js';
|
|
10
|
-
import {
|
|
10
|
+
import { lookupCodexTranscriptFromDaemon, lookupCodexTranscriptFromProcessLogs, lookupCodexTranscriptFromOpenFds, resolveCodexTranscriptCandidates, processBinding, TRANSCRIPT_CANDIDATE_LIMIT } from './codexTranscriptSource.js';
|
|
11
11
|
import { isSessionId } from './sessionId.js';
|
|
12
12
|
import { processStartTicksToSecondsAsync, readProcessCwdAsync, readProcessEnvValuesAsync, readProcessStartTicksAsync } from './procfs.js';
|
|
13
13
|
import { TtlCache } from './ttlCache.js';
|
|
@@ -26,6 +26,8 @@ const TRANSCRIPT_FILE_NEGATIVE_CACHE_TTL_MS = 20_000;
|
|
|
26
26
|
// letting a history-paging burst (several page requests within seconds)
|
|
27
27
|
// reuse one resolution instead of re-forking tmux/ps per page.
|
|
28
28
|
const TRANSCRIPT_RESOLUTION_CACHE_TTL_MS = 3_000;
|
|
29
|
+
// Diagnostics outlive a few status polls, not the pane's next resolution.
|
|
30
|
+
const TRANSCRIPT_AMBIGUITY_TTL_MS = 90_000;
|
|
29
31
|
const TRANSCRIPT_CANDIDATE_CACHE_TTL_MS = 500;
|
|
30
32
|
const PROCESS_TREE_CACHE_TTL_MS = 1_000;
|
|
31
33
|
const CLAUDE_TRANSCRIPT_START_SKEW_MS = 10_000;
|
|
@@ -75,6 +77,9 @@ export class TmuxPtyAdapter {
|
|
|
75
77
|
commandRunner;
|
|
76
78
|
ptySpawner;
|
|
77
79
|
transcriptResolutionCache = new TtlCache();
|
|
80
|
+
transcriptResolutionGeneration = new Map();
|
|
81
|
+
// Last discovery outcome per pane, for status reporting only (bounded by panes).
|
|
82
|
+
transcriptAmbiguity = new Map();
|
|
78
83
|
transcriptResolutionInflight = new KeyedSingleFlight();
|
|
79
84
|
transcriptCandidateInflight = new KeyedSingleFlight();
|
|
80
85
|
processTreeCache = new TtlCache();
|
|
@@ -397,6 +402,8 @@ export class TmuxPtyAdapter {
|
|
|
397
402
|
this.historyOutputGenerationBySession.delete(sessionId);
|
|
398
403
|
this.historyOutputUnreliableSessions.delete(sessionId);
|
|
399
404
|
this.transcriptResolutionCache.delete(sessionId);
|
|
405
|
+
this.transcriptResolutionGeneration.delete(sessionId);
|
|
406
|
+
this.transcriptAmbiguity.delete(sessionId);
|
|
400
407
|
}
|
|
401
408
|
isInflightFinalCreation(sessionId, marker) {
|
|
402
409
|
const inflightMarker = this.inflightFinalCreationMarkers.get(sessionId);
|
|
@@ -1039,16 +1046,50 @@ export class TmuxPtyAdapter {
|
|
|
1039
1046
|
}
|
|
1040
1047
|
}
|
|
1041
1048
|
async resolveTranscriptSource(sessionId) {
|
|
1049
|
+
return (await this.resolveTranscriptSourceEvidence(sessionId))?.source ?? null;
|
|
1050
|
+
}
|
|
1051
|
+
async resolveTranscriptSourceEvidence(sessionId, options = {}) {
|
|
1052
|
+
const resolved = options.fresh ? await this.resolveTranscriptSourceUncached(sessionId, true)
|
|
1053
|
+
: await this.resolveTranscriptSourceCached(sessionId);
|
|
1054
|
+
if (!resolved)
|
|
1055
|
+
return null;
|
|
1056
|
+
const { evidence = 'inferred', client, ...source } = resolved;
|
|
1057
|
+
return { source, evidence, ...(client ? { client } : {}) };
|
|
1058
|
+
}
|
|
1059
|
+
getTranscriptAmbiguity(sessionId) {
|
|
1060
|
+
const entry = this.transcriptAmbiguity.get(sessionId);
|
|
1061
|
+
if (!entry)
|
|
1062
|
+
return null;
|
|
1063
|
+
if (Date.now() - entry.at > TRANSCRIPT_AMBIGUITY_TTL_MS) {
|
|
1064
|
+
this.transcriptAmbiguity.delete(sessionId);
|
|
1065
|
+
return null;
|
|
1066
|
+
}
|
|
1067
|
+
return entry.reason;
|
|
1068
|
+
}
|
|
1069
|
+
noteTranscriptAmbiguity(sessionId, ambiguous) {
|
|
1070
|
+
if (ambiguous)
|
|
1071
|
+
this.transcriptAmbiguity.set(sessionId, { reason: 'codex_shared_daemon_same_cwd', at: Date.now() });
|
|
1072
|
+
else
|
|
1073
|
+
this.transcriptAmbiguity.delete(sessionId);
|
|
1074
|
+
}
|
|
1075
|
+
async resolveTranscriptSourceCached(sessionId) {
|
|
1042
1076
|
const cached = this.transcriptResolutionCache.get(sessionId);
|
|
1043
1077
|
if (cached) {
|
|
1044
1078
|
return cached.value;
|
|
1045
1079
|
}
|
|
1046
|
-
|
|
1080
|
+
const generation = this.transcriptResolutionGeneration.get(sessionId) ?? 0;
|
|
1081
|
+
return this.transcriptResolutionInflight.run(`${sessionId}:${generation}`, async () => {
|
|
1047
1082
|
const source = await this.resolveTranscriptSourceUncached(sessionId);
|
|
1048
|
-
this.
|
|
1083
|
+
if ((this.transcriptResolutionGeneration.get(sessionId) ?? 0) === generation) {
|
|
1084
|
+
this.transcriptResolutionCache.set(sessionId, source, TRANSCRIPT_RESOLUTION_CACHE_TTL_MS);
|
|
1085
|
+
}
|
|
1049
1086
|
return source;
|
|
1050
1087
|
});
|
|
1051
1088
|
}
|
|
1089
|
+
invalidateTranscriptSource(sessionId) {
|
|
1090
|
+
this.transcriptResolutionCache.delete(sessionId);
|
|
1091
|
+
this.transcriptResolutionGeneration.set(sessionId, (this.transcriptResolutionGeneration.get(sessionId) ?? 0) + 1);
|
|
1092
|
+
}
|
|
1052
1093
|
async resolveTranscriptCandidates(sessionId) {
|
|
1053
1094
|
return this.transcriptCandidateInflight.run(sessionId, async () => {
|
|
1054
1095
|
const result = await this.resolveTranscriptCandidatesUncached(sessionId);
|
|
@@ -1071,6 +1112,10 @@ export class TmuxPtyAdapter {
|
|
|
1071
1112
|
await mapWithConcurrency(processes, 8, async ({ pid }) => argsByPid.set(pid, await readProcessArgsAsync(pid)));
|
|
1072
1113
|
const discoveries = [];
|
|
1073
1114
|
for (const { pid, depth } of processes) {
|
|
1115
|
+
// Breadth-first traversal: deeper workers cannot affect the selected
|
|
1116
|
+
// owner, including a worker exiting while its procfs identity is read.
|
|
1117
|
+
if (discoveries.length && depth > discoveries[0].depth)
|
|
1118
|
+
continue;
|
|
1074
1119
|
const args = argsByPid.get(pid);
|
|
1075
1120
|
if (findCommandArg(args, 'claude') !== -1) {
|
|
1076
1121
|
if (isNonInteractiveClaudeInvocation(args))
|
|
@@ -1111,11 +1156,17 @@ export class TmuxPtyAdapter {
|
|
|
1111
1156
|
}
|
|
1112
1157
|
}
|
|
1113
1158
|
return {
|
|
1114
|
-
binding: `pane:${panePid}:${paneStart}|${discoveries.map(({ result }) => result.binding).sort().join('|')}`,
|
|
1159
|
+
binding: `pane:${panePid}:${paneStart}|${discoveries.filter(({ depth }) => depth === closest).map(({ result }) => result.binding).sort().join('|')}`,
|
|
1115
1160
|
candidates: [...candidates.values()].slice(0, TRANSCRIPT_CANDIDATE_LIMIT)
|
|
1116
1161
|
};
|
|
1117
1162
|
}
|
|
1118
|
-
async resolveTranscriptSourceUncached(sessionId) {
|
|
1163
|
+
async resolveTranscriptSourceUncached(sessionId, fresh = false) {
|
|
1164
|
+
const state = { daemonAmbiguous: false };
|
|
1165
|
+
const resolved = await this.resolveTranscriptSourceUncachedInner(sessionId, fresh, state);
|
|
1166
|
+
this.noteTranscriptAmbiguity(sessionId, !resolved && state.daemonAmbiguous);
|
|
1167
|
+
return resolved;
|
|
1168
|
+
}
|
|
1169
|
+
async resolveTranscriptSourceUncachedInner(sessionId, fresh, state) {
|
|
1119
1170
|
const panePid = this.panePidBySession.get(sessionId) ?? await this.readPanePidAsync(sessionId);
|
|
1120
1171
|
if (panePid === null) {
|
|
1121
1172
|
return null;
|
|
@@ -1136,6 +1187,7 @@ export class TmuxPtyAdapter {
|
|
|
1136
1187
|
}
|
|
1137
1188
|
});
|
|
1138
1189
|
let entries = [];
|
|
1190
|
+
const pendingCodexDepths = [];
|
|
1139
1191
|
let claudeSessionPending = false;
|
|
1140
1192
|
for (const [pid, args] of argsByPid) {
|
|
1141
1193
|
const processDepth = processDepthByPid.get(pid);
|
|
@@ -1166,8 +1218,10 @@ export class TmuxPtyAdapter {
|
|
|
1166
1218
|
entries.push({ candidate: live.source, rank: 'live', processDepth });
|
|
1167
1219
|
continue;
|
|
1168
1220
|
}
|
|
1169
|
-
if (live.status === 'pending' || live.status === 'ambiguous')
|
|
1170
|
-
|
|
1221
|
+
if (live.status === 'pending' || live.status === 'ambiguous') {
|
|
1222
|
+
pendingCodexDepths.push(processDepth);
|
|
1223
|
+
continue;
|
|
1224
|
+
}
|
|
1171
1225
|
// Launcher pids (the npm node wrapper) merely relay the binary's
|
|
1172
1226
|
// argv; after /new their stale `resume <id>` args would conflict
|
|
1173
1227
|
// with the live session the binary itself reports, so only the
|
|
@@ -1177,13 +1231,21 @@ export class TmuxPtyAdapter {
|
|
|
1177
1231
|
}
|
|
1178
1232
|
// A connected daemon is authoritative, even when its current
|
|
1179
1233
|
// thread is pending/ambiguous. Never replace that with stale argv.
|
|
1180
|
-
const
|
|
1181
|
-
|
|
1182
|
-
|
|
1234
|
+
const daemon = await lookupCodexTranscriptFromDaemon(pid, fresh ? { bypassCache: true } : undefined);
|
|
1235
|
+
const lookup = daemon.status === 'absent' || daemon.status === 'unavailable'
|
|
1236
|
+
? await lookupCodexTranscriptFromProcessLogs(pid) : daemon;
|
|
1237
|
+
if (lookup.status === 'pending' || lookup.status === 'ambiguous') {
|
|
1238
|
+
if (daemon.status === 'ambiguous')
|
|
1239
|
+
state.daemonAmbiguous = true;
|
|
1240
|
+
pendingCodexDepths.push(processDepth);
|
|
1241
|
+
continue;
|
|
1242
|
+
}
|
|
1183
1243
|
const stated = lookup.status === 'matched' ? lookup.source
|
|
1184
1244
|
: lookup.status === 'absent' ? extractCodexArgsCandidate(args) : null;
|
|
1185
1245
|
if (stated) {
|
|
1186
|
-
|
|
1246
|
+
const client = daemon.status === 'matched' ? await describeTranscriptClient(pid, args) : null;
|
|
1247
|
+
entries.push({ candidate: stated, rank: daemon.status === 'matched' ? 'daemon' : 'stated', processDepth,
|
|
1248
|
+
...(client ? { client } : {}) });
|
|
1187
1249
|
}
|
|
1188
1250
|
}
|
|
1189
1251
|
}
|
|
@@ -1194,15 +1256,23 @@ export class TmuxPtyAdapter {
|
|
|
1194
1256
|
// other session's file) is wrong by construction.
|
|
1195
1257
|
entries = entries.filter((entry) => entry.candidate.provider !== 'claude' || entry.rank === 'live');
|
|
1196
1258
|
}
|
|
1197
|
-
|
|
1198
|
-
|
|
1259
|
+
// Uncertain child jobs cannot veto a closer write-open TUI owner. At
|
|
1260
|
+
// the same or shallower depth uncertainty still blocks all fallback.
|
|
1261
|
+
const closestLiveCodex = Math.min(...entries.filter(entry => entry.rank === 'live' && entry.candidate.provider === 'codex')
|
|
1262
|
+
.map(entry => entry.processDepth ?? Infinity));
|
|
1263
|
+
if (pendingCodexDepths.some(depth => depth <= closestLiveCodex))
|
|
1264
|
+
return null;
|
|
1265
|
+
const selected = selectTranscriptCandidateEntry(entries);
|
|
1266
|
+
if (!selected) {
|
|
1199
1267
|
return null;
|
|
1200
1268
|
}
|
|
1269
|
+
const candidate = selected.candidate;
|
|
1201
1270
|
const matches = candidate.filePath ? [candidate.filePath] : await findTranscriptFilesAsync(candidate.provider, candidate.sessionId);
|
|
1202
1271
|
if (matches.length !== 1) {
|
|
1203
1272
|
return null;
|
|
1204
1273
|
}
|
|
1205
|
-
return { provider: candidate.provider, sessionId: candidate.sessionId, filePath: matches[0]
|
|
1274
|
+
return { provider: candidate.provider, sessionId: candidate.sessionId, filePath: matches[0], evidence: selected.rank,
|
|
1275
|
+
...(selected.client ? { client: selected.client } : {}) };
|
|
1206
1276
|
}
|
|
1207
1277
|
async readPaneMetricsAsync(sessionId) {
|
|
1208
1278
|
try {
|
|
@@ -1292,6 +1362,10 @@ export class TmuxPtyAdapter {
|
|
|
1292
1362
|
return false;
|
|
1293
1363
|
}
|
|
1294
1364
|
}
|
|
1365
|
+
getSessionPid(sessionId) {
|
|
1366
|
+
const cached = this.panePidBySession.get(sessionId);
|
|
1367
|
+
return cached ? Promise.resolve(cached) : this.readPanePidAsync(sessionId);
|
|
1368
|
+
}
|
|
1295
1369
|
async readPanePidAsync(sessionId) {
|
|
1296
1370
|
try {
|
|
1297
1371
|
const result = await this.commandRunner('tmux', [
|
|
@@ -1714,6 +1788,17 @@ async function readProcessArgsAsync(pid) {
|
|
|
1714
1788
|
return [];
|
|
1715
1789
|
}
|
|
1716
1790
|
}
|
|
1791
|
+
// Launch facts for the TUI behind a daemon association. Missing facts stay
|
|
1792
|
+
// missing (never guessed), so corroboration fails closed.
|
|
1793
|
+
async function describeTranscriptClient(pid, args) {
|
|
1794
|
+
const startTicks = await readProcessStartTicksAsync(pid);
|
|
1795
|
+
if (startTicks === null)
|
|
1796
|
+
return null;
|
|
1797
|
+
const startedAtSeconds = await processStartTicksToSecondsAsync(startTicks);
|
|
1798
|
+
const stated = extractCodexArgsCandidate(args)?.sessionId;
|
|
1799
|
+
return { pid, startTicks, startedAtMs: startedAtSeconds === null ? null : Math.round(startedAtSeconds * 1000),
|
|
1800
|
+
...(stated ? { statedSessionId: stated } : {}) };
|
|
1801
|
+
}
|
|
1717
1802
|
function extractCodexArgsCandidate(args) {
|
|
1718
1803
|
const codexIndex = findCommandArg(args, 'codex');
|
|
1719
1804
|
if (codexIndex === -1) {
|
|
@@ -1822,7 +1907,10 @@ function hasCodexDescendant(tree, root, argsByPid) {
|
|
|
1822
1907
|
// tier returns null — falling back to a lower tier would resolve precisely
|
|
1823
1908
|
// when the best information says the answer is uncertain.
|
|
1824
1909
|
export function selectTranscriptCandidate(entries) {
|
|
1825
|
-
|
|
1910
|
+
return selectTranscriptCandidateEntry(entries)?.candidate ?? null;
|
|
1911
|
+
}
|
|
1912
|
+
function selectTranscriptCandidateEntry(entries) {
|
|
1913
|
+
for (const rank of ['live', 'daemon', 'stated', 'inferred']) {
|
|
1826
1914
|
let pool = entries.filter((entry) => entry.rank === rank);
|
|
1827
1915
|
if (pool.length === 0) {
|
|
1828
1916
|
continue;
|
|
@@ -1848,7 +1936,7 @@ export function selectTranscriptCandidate(entries) {
|
|
|
1848
1936
|
if (unique.size !== 1) {
|
|
1849
1937
|
return null;
|
|
1850
1938
|
}
|
|
1851
|
-
return [...unique.values()][0];
|
|
1939
|
+
return { candidate: [...unique.values()][0], rank };
|
|
1852
1940
|
}
|
|
1853
1941
|
return null;
|
|
1854
1942
|
}
|