nterminal 1.3.4 → 1.3.6

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 (126) hide show
  1. package/README.md +68 -37
  2. package/assets/screenshot/accounts.png +0 -0
  3. package/assets/screenshot/history.png +0 -0
  4. package/assets/screenshot/mobile-panes.png +0 -0
  5. package/assets/screenshot/mobile.png +0 -0
  6. package/assets/screenshot/scr.png +0 -0
  7. package/assets/screenshot/subagents.png +0 -0
  8. package/bin/nterminal.js +10 -3
  9. package/dist/client/assets/MarkdownPreview-B-LrTjTM.js +1 -0
  10. package/dist/client/assets/{TranscriptMarkdownBody-H8ZsISc6.js → TranscriptMarkdownBody-3aYVmqQz.js} +1 -1
  11. package/dist/client/assets/index-D3LN5iAH.js +58 -0
  12. package/dist/client/assets/{index-DxfBq-4P.js → index-Dd_o8KCL.js} +1 -1
  13. package/dist/client/assets/index-W-zGfY1a.css +1 -0
  14. package/dist/client/index.html +2 -2
  15. package/dist/scripts/hooks.js +124 -0
  16. package/dist/scripts/hooks.js.map +1 -0
  17. package/dist/server/accounts/accountService.d.ts +23 -1
  18. package/dist/server/accounts/accountService.js +235 -103
  19. package/dist/server/accounts/accountService.js.map +1 -1
  20. package/dist/server/accounts/providerRequests.d.ts +12 -0
  21. package/dist/server/accounts/providerRequests.js +159 -0
  22. package/dist/server/accounts/providerRequests.js.map +1 -0
  23. package/dist/server/agent/agentRoutes.js +11 -0
  24. package/dist/server/agent/agentRoutes.js.map +1 -1
  25. package/dist/server/hooks/codexAppServer.d.ts +30 -0
  26. package/dist/server/hooks/codexAppServer.js +170 -0
  27. package/dist/server/hooks/codexAppServer.js.map +1 -0
  28. package/dist/server/hooks/codexTrust.d.ts +72 -0
  29. package/dist/server/hooks/codexTrust.js +116 -0
  30. package/dist/server/hooks/codexTrust.js.map +1 -0
  31. package/dist/server/hooks/helperJournal.d.ts +16 -0
  32. package/dist/server/hooks/helperJournal.js +74 -0
  33. package/dist/server/hooks/helperJournal.js.map +1 -0
  34. package/dist/server/hooks/hookBinding.d.ts +39 -0
  35. package/dist/server/hooks/hookBinding.js +91 -0
  36. package/dist/server/hooks/hookBinding.js.map +1 -0
  37. package/dist/server/hooks/hookFiles.d.ts +24 -0
  38. package/dist/server/hooks/hookFiles.js +160 -0
  39. package/dist/server/hooks/hookFiles.js.map +1 -0
  40. package/dist/server/hooks/hookInstallation.d.ts +108 -0
  41. package/dist/server/hooks/hookInstallation.js +1074 -0
  42. package/dist/server/hooks/hookInstallation.js.map +1 -0
  43. package/dist/server/hooks/hookProcess.d.ts +5 -0
  44. package/dist/server/hooks/hookProcess.js +85 -0
  45. package/dist/server/hooks/hookProcess.js.map +1 -0
  46. package/dist/server/hooks/hookReceiver.d.ts +66 -0
  47. package/dist/server/hooks/hookReceiver.js +549 -0
  48. package/dist/server/hooks/hookReceiver.js.map +1 -0
  49. package/dist/server/hooks/lifecycle.d.ts +40 -0
  50. package/dist/server/hooks/lifecycle.js +200 -0
  51. package/dist/server/hooks/lifecycle.js.map +1 -0
  52. package/dist/server/hooks/publicationLock.d.ts +3 -0
  53. package/dist/server/hooks/publicationLock.js +137 -0
  54. package/dist/server/hooks/publicationLock.js.map +1 -0
  55. package/dist/server/hooks/verifiedLock.d.ts +24 -0
  56. package/dist/server/hooks/verifiedLock.js +207 -0
  57. package/dist/server/hooks/verifiedLock.js.map +1 -0
  58. package/dist/server/http.d.ts +4 -0
  59. package/dist/server/http.js +6 -0
  60. package/dist/server/http.js.map +1 -1
  61. package/dist/server/index.js +71 -2
  62. package/dist/server/index.js.map +1 -1
  63. package/dist/server/notifications/agentCompletionMonitor.d.ts +16 -1
  64. package/dist/server/notifications/agentCompletionMonitor.js +261 -47
  65. package/dist/server/notifications/agentCompletionMonitor.js.map +1 -1
  66. package/dist/server/notifications/completionNotificationRouter.d.ts +2 -0
  67. package/dist/server/notifications/completionNotificationRouter.js +9 -0
  68. package/dist/server/notifications/completionNotificationRouter.js.map +1 -1
  69. package/dist/server/routes/accountRoutes.js +11 -0
  70. package/dist/server/routes/accountRoutes.js.map +1 -1
  71. package/dist/server/routes/activityRoutes.d.ts +13 -0
  72. package/dist/server/routes/activityRoutes.js +165 -0
  73. package/dist/server/routes/activityRoutes.js.map +1 -0
  74. package/dist/server/routes/agentManagementRoutes.js +8 -1
  75. package/dist/server/routes/agentManagementRoutes.js.map +1 -1
  76. package/dist/server/routes/hookRoutes.d.ts +16 -0
  77. package/dist/server/routes/hookRoutes.js +109 -0
  78. package/dist/server/routes/hookRoutes.js.map +1 -0
  79. package/dist/server/routes/terminalRoutes.js +4 -0
  80. package/dist/server/routes/terminalRoutes.js.map +1 -1
  81. package/dist/server/routes/transcriptQuery.d.ts +2 -1
  82. package/dist/server/routes/transcriptQuery.js +3 -1
  83. package/dist/server/routes/transcriptQuery.js.map +1 -1
  84. package/dist/server/terminal/PtyAdapter.d.ts +19 -0
  85. package/dist/server/terminal/PtyAdapter.js.map +1 -1
  86. package/dist/server/terminal/TerminalManager.d.ts +61 -0
  87. package/dist/server/terminal/TerminalManager.js +672 -12
  88. package/dist/server/terminal/TerminalManager.js.map +1 -1
  89. package/dist/server/terminal/TmuxPtyAdapter.d.ts +14 -2
  90. package/dist/server/terminal/TmuxPtyAdapter.js +101 -15
  91. package/dist/server/terminal/TmuxPtyAdapter.js.map +1 -1
  92. package/dist/server/terminal/activityChannel.d.ts +57 -0
  93. package/dist/server/terminal/activityChannel.js +166 -0
  94. package/dist/server/terminal/activityChannel.js.map +1 -0
  95. package/dist/server/terminal/agentTranscriptStatus.d.ts +12 -0
  96. package/dist/server/terminal/agentTranscriptStatus.js +78 -7
  97. package/dist/server/terminal/agentTranscriptStatus.js.map +1 -1
  98. package/dist/server/terminal/codexTranscriptSource.d.ts +3 -0
  99. package/dist/server/terminal/codexTranscriptSource.js +99 -39
  100. package/dist/server/terminal/codexTranscriptSource.js.map +1 -1
  101. package/dist/server/terminal/subagentSources.js +2 -1
  102. package/dist/server/terminal/subagentSources.js.map +1 -1
  103. package/dist/server/terminal/transcriptHeader.d.ts +85 -0
  104. package/dist/server/terminal/transcriptHeader.js +376 -0
  105. package/dist/server/terminal/transcriptHeader.js.map +1 -0
  106. package/dist/server/terminal/transcriptHistory.d.ts +16 -2
  107. package/dist/server/terminal/transcriptHistory.js +120 -21
  108. package/dist/server/terminal/transcriptHistory.js.map +1 -1
  109. package/dist/server/terminal/transcriptStore.d.ts +6 -0
  110. package/dist/server/terminal/transcriptStore.js +222 -26
  111. package/dist/server/terminal/transcriptStore.js.map +1 -1
  112. package/dist/shared/activityProbe.d.ts +10 -0
  113. package/dist/shared/activityProbe.js +24 -0
  114. package/dist/shared/activityProbe.js.map +1 -0
  115. package/dist/shared/protocol.d.ts +45 -0
  116. package/dist/shared/protocol.js.map +1 -1
  117. package/dist/shared/types.d.ts +9 -1
  118. package/docs/features.md +21 -1
  119. package/docs/provider-hooks.md +340 -0
  120. package/docs/terminal-history.md +10 -0
  121. package/package.json +2 -1
  122. package/scripts/nterminal-hook.cjs +171 -0
  123. package/scripts/nterminalctl +26 -2
  124. package/dist/client/assets/MarkdownPreview-DjDO4lEl.js +0 -1
  125. package/dist/client/assets/index-CIYazAGv.css +0 -1
  126. package/dist/client/assets/index-DwvLN_WJ.js +0 -56
@@ -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 { lookupCodexTranscriptFromDaemonOrLogs, lookupCodexTranscriptFromOpenFds, resolveCodexTranscriptCandidates, processBinding, TRANSCRIPT_CANDIDATE_LIMIT } from './codexTranscriptSource.js';
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
- return this.transcriptResolutionInflight.run(sessionId, async () => {
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.transcriptResolutionCache.set(sessionId, source, TRANSCRIPT_RESOLUTION_CACHE_TTL_MS);
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);
@@ -1119,7 +1160,13 @@ export class TmuxPtyAdapter {
1119
1160
  candidates: [...candidates.values()].slice(0, TRANSCRIPT_CANDIDATE_LIMIT)
1120
1161
  };
1121
1162
  }
1122
- 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) {
1123
1170
  const panePid = this.panePidBySession.get(sessionId) ?? await this.readPanePidAsync(sessionId);
1124
1171
  if (panePid === null) {
1125
1172
  return null;
@@ -1140,6 +1187,7 @@ export class TmuxPtyAdapter {
1140
1187
  }
1141
1188
  });
1142
1189
  let entries = [];
1190
+ const pendingCodexDepths = [];
1143
1191
  let claudeSessionPending = false;
1144
1192
  for (const [pid, args] of argsByPid) {
1145
1193
  const processDepth = processDepthByPid.get(pid);
@@ -1170,8 +1218,10 @@ export class TmuxPtyAdapter {
1170
1218
  entries.push({ candidate: live.source, rank: 'live', processDepth });
1171
1219
  continue;
1172
1220
  }
1173
- if (live.status === 'pending' || live.status === 'ambiguous')
1174
- return null;
1221
+ if (live.status === 'pending' || live.status === 'ambiguous') {
1222
+ pendingCodexDepths.push(processDepth);
1223
+ continue;
1224
+ }
1175
1225
  // Launcher pids (the npm node wrapper) merely relay the binary's
1176
1226
  // argv; after /new their stale `resume <id>` args would conflict
1177
1227
  // with the live session the binary itself reports, so only the
@@ -1181,13 +1231,23 @@ export class TmuxPtyAdapter {
1181
1231
  }
1182
1232
  // A connected daemon is authoritative, even when its current
1183
1233
  // thread is pending/ambiguous. Never replace that with stale argv.
1184
- const lookup = await lookupCodexTranscriptFromDaemonOrLogs(pid);
1185
- if (lookup.status === 'pending' || lookup.status === 'ambiguous')
1186
- return null;
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
+ }
1187
1243
  const stated = lookup.status === 'matched' ? lookup.source
1188
1244
  : lookup.status === 'absent' ? extractCodexArgsCandidate(args) : null;
1189
1245
  if (stated) {
1190
- entries.push({ candidate: stated, rank: 'stated', processDepth });
1246
+ const described = daemon.status === 'matched' ? await describeTranscriptClient(pid, args) : null;
1247
+ const client = described && daemon.status === 'matched' && daemon.soleClientInCwd
1248
+ ? { ...described, soleDaemonClientInCwd: true } : described;
1249
+ entries.push({ candidate: stated, rank: daemon.status === 'matched' ? 'daemon' : 'stated', processDepth,
1250
+ ...(client ? { client } : {}) });
1191
1251
  }
1192
1252
  }
1193
1253
  }
@@ -1198,15 +1258,23 @@ export class TmuxPtyAdapter {
1198
1258
  // other session's file) is wrong by construction.
1199
1259
  entries = entries.filter((entry) => entry.candidate.provider !== 'claude' || entry.rank === 'live');
1200
1260
  }
1201
- const candidate = selectTranscriptCandidate(entries);
1202
- if (!candidate) {
1261
+ // Uncertain child jobs cannot veto a closer write-open TUI owner. At
1262
+ // the same or shallower depth uncertainty still blocks all fallback.
1263
+ const closestLiveCodex = Math.min(...entries.filter(entry => entry.rank === 'live' && entry.candidate.provider === 'codex')
1264
+ .map(entry => entry.processDepth ?? Infinity));
1265
+ if (pendingCodexDepths.some(depth => depth <= closestLiveCodex))
1266
+ return null;
1267
+ const selected = selectTranscriptCandidateEntry(entries);
1268
+ if (!selected) {
1203
1269
  return null;
1204
1270
  }
1271
+ const candidate = selected.candidate;
1205
1272
  const matches = candidate.filePath ? [candidate.filePath] : await findTranscriptFilesAsync(candidate.provider, candidate.sessionId);
1206
1273
  if (matches.length !== 1) {
1207
1274
  return null;
1208
1275
  }
1209
- return { provider: candidate.provider, sessionId: candidate.sessionId, filePath: matches[0] };
1276
+ return { provider: candidate.provider, sessionId: candidate.sessionId, filePath: matches[0], evidence: selected.rank,
1277
+ ...(selected.client ? { client: selected.client } : {}) };
1210
1278
  }
1211
1279
  async readPaneMetricsAsync(sessionId) {
1212
1280
  try {
@@ -1296,6 +1364,10 @@ export class TmuxPtyAdapter {
1296
1364
  return false;
1297
1365
  }
1298
1366
  }
1367
+ getSessionPid(sessionId) {
1368
+ const cached = this.panePidBySession.get(sessionId);
1369
+ return cached ? Promise.resolve(cached) : this.readPanePidAsync(sessionId);
1370
+ }
1299
1371
  async readPanePidAsync(sessionId) {
1300
1372
  try {
1301
1373
  const result = await this.commandRunner('tmux', [
@@ -1718,6 +1790,17 @@ async function readProcessArgsAsync(pid) {
1718
1790
  return [];
1719
1791
  }
1720
1792
  }
1793
+ // Launch facts for the TUI behind a daemon association. Missing facts stay
1794
+ // missing (never guessed), so corroboration fails closed.
1795
+ async function describeTranscriptClient(pid, args) {
1796
+ const startTicks = await readProcessStartTicksAsync(pid);
1797
+ if (startTicks === null)
1798
+ return null;
1799
+ const startedAtSeconds = await processStartTicksToSecondsAsync(startTicks);
1800
+ const stated = extractCodexArgsCandidate(args)?.sessionId;
1801
+ return { pid, startTicks, startedAtMs: startedAtSeconds === null ? null : Math.round(startedAtSeconds * 1000),
1802
+ ...(stated ? { statedSessionId: stated } : {}) };
1803
+ }
1721
1804
  function extractCodexArgsCandidate(args) {
1722
1805
  const codexIndex = findCommandArg(args, 'codex');
1723
1806
  if (codexIndex === -1) {
@@ -1826,7 +1909,10 @@ function hasCodexDescendant(tree, root, argsByPid) {
1826
1909
  // tier returns null — falling back to a lower tier would resolve precisely
1827
1910
  // when the best information says the answer is uncertain.
1828
1911
  export function selectTranscriptCandidate(entries) {
1829
- for (const rank of ['live', 'stated', 'inferred']) {
1912
+ return selectTranscriptCandidateEntry(entries)?.candidate ?? null;
1913
+ }
1914
+ function selectTranscriptCandidateEntry(entries) {
1915
+ for (const rank of ['live', 'daemon', 'stated', 'inferred']) {
1830
1916
  let pool = entries.filter((entry) => entry.rank === rank);
1831
1917
  if (pool.length === 0) {
1832
1918
  continue;
@@ -1852,7 +1938,7 @@ export function selectTranscriptCandidate(entries) {
1852
1938
  if (unique.size !== 1) {
1853
1939
  return null;
1854
1940
  }
1855
- return [...unique.values()][0];
1941
+ return { candidate: [...unique.values()][0], rank };
1856
1942
  }
1857
1943
  return null;
1858
1944
  }