nterminal 1.3.4 → 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.
Files changed (113) hide show
  1. package/bin/nterminal.js +8 -3
  2. package/dist/client/assets/MarkdownPreview-BIrwUAaa.js +1 -0
  3. package/dist/client/assets/{TranscriptMarkdownBody-H8ZsISc6.js → TranscriptMarkdownBody-CI0ztvMX.js} +1 -1
  4. package/dist/client/assets/{index-DxfBq-4P.js → index-EN8ngMgv.js} +1 -1
  5. package/dist/client/assets/index-JQJ1Eet1.js +58 -0
  6. package/dist/client/assets/index-xkL4h3qt.css +1 -0
  7. package/dist/client/index.html +2 -2
  8. package/dist/scripts/hooks.js +83 -0
  9. package/dist/scripts/hooks.js.map +1 -0
  10. package/dist/server/accounts/accountService.d.ts +23 -1
  11. package/dist/server/accounts/accountService.js +235 -103
  12. package/dist/server/accounts/accountService.js.map +1 -1
  13. package/dist/server/accounts/providerRequests.d.ts +12 -0
  14. package/dist/server/accounts/providerRequests.js +159 -0
  15. package/dist/server/accounts/providerRequests.js.map +1 -0
  16. package/dist/server/agent/agentRoutes.js +11 -0
  17. package/dist/server/agent/agentRoutes.js.map +1 -1
  18. package/dist/server/hooks/helperJournal.d.ts +16 -0
  19. package/dist/server/hooks/helperJournal.js +74 -0
  20. package/dist/server/hooks/helperJournal.js.map +1 -0
  21. package/dist/server/hooks/hookBinding.d.ts +39 -0
  22. package/dist/server/hooks/hookBinding.js +91 -0
  23. package/dist/server/hooks/hookBinding.js.map +1 -0
  24. package/dist/server/hooks/hookFiles.d.ts +24 -0
  25. package/dist/server/hooks/hookFiles.js +160 -0
  26. package/dist/server/hooks/hookFiles.js.map +1 -0
  27. package/dist/server/hooks/hookInstallation.d.ts +79 -0
  28. package/dist/server/hooks/hookInstallation.js +978 -0
  29. package/dist/server/hooks/hookInstallation.js.map +1 -0
  30. package/dist/server/hooks/hookProcess.d.ts +5 -0
  31. package/dist/server/hooks/hookProcess.js +85 -0
  32. package/dist/server/hooks/hookProcess.js.map +1 -0
  33. package/dist/server/hooks/hookReceiver.d.ts +66 -0
  34. package/dist/server/hooks/hookReceiver.js +549 -0
  35. package/dist/server/hooks/hookReceiver.js.map +1 -0
  36. package/dist/server/hooks/lifecycle.d.ts +40 -0
  37. package/dist/server/hooks/lifecycle.js +200 -0
  38. package/dist/server/hooks/lifecycle.js.map +1 -0
  39. package/dist/server/hooks/publicationLock.d.ts +3 -0
  40. package/dist/server/hooks/publicationLock.js +137 -0
  41. package/dist/server/hooks/publicationLock.js.map +1 -0
  42. package/dist/server/hooks/verifiedLock.d.ts +24 -0
  43. package/dist/server/hooks/verifiedLock.js +207 -0
  44. package/dist/server/hooks/verifiedLock.js.map +1 -0
  45. package/dist/server/http.d.ts +4 -0
  46. package/dist/server/http.js +6 -0
  47. package/dist/server/http.js.map +1 -1
  48. package/dist/server/index.js +69 -2
  49. package/dist/server/index.js.map +1 -1
  50. package/dist/server/notifications/agentCompletionMonitor.d.ts +16 -1
  51. package/dist/server/notifications/agentCompletionMonitor.js +261 -47
  52. package/dist/server/notifications/agentCompletionMonitor.js.map +1 -1
  53. package/dist/server/notifications/completionNotificationRouter.d.ts +2 -0
  54. package/dist/server/notifications/completionNotificationRouter.js +9 -0
  55. package/dist/server/notifications/completionNotificationRouter.js.map +1 -1
  56. package/dist/server/routes/accountRoutes.js +11 -0
  57. package/dist/server/routes/accountRoutes.js.map +1 -1
  58. package/dist/server/routes/activityRoutes.d.ts +13 -0
  59. package/dist/server/routes/activityRoutes.js +165 -0
  60. package/dist/server/routes/activityRoutes.js.map +1 -0
  61. package/dist/server/routes/agentManagementRoutes.js +8 -1
  62. package/dist/server/routes/agentManagementRoutes.js.map +1 -1
  63. package/dist/server/routes/hookRoutes.d.ts +16 -0
  64. package/dist/server/routes/hookRoutes.js +77 -0
  65. package/dist/server/routes/hookRoutes.js.map +1 -0
  66. package/dist/server/routes/terminalRoutes.js +4 -0
  67. package/dist/server/routes/terminalRoutes.js.map +1 -1
  68. package/dist/server/routes/transcriptQuery.d.ts +2 -1
  69. package/dist/server/routes/transcriptQuery.js +3 -1
  70. package/dist/server/routes/transcriptQuery.js.map +1 -1
  71. package/dist/server/terminal/PtyAdapter.d.ts +18 -0
  72. package/dist/server/terminal/PtyAdapter.js.map +1 -1
  73. package/dist/server/terminal/TerminalManager.d.ts +61 -0
  74. package/dist/server/terminal/TerminalManager.js +665 -12
  75. package/dist/server/terminal/TerminalManager.js.map +1 -1
  76. package/dist/server/terminal/TmuxPtyAdapter.d.ts +14 -2
  77. package/dist/server/terminal/TmuxPtyAdapter.js +99 -15
  78. package/dist/server/terminal/TmuxPtyAdapter.js.map +1 -1
  79. package/dist/server/terminal/activityChannel.d.ts +57 -0
  80. package/dist/server/terminal/activityChannel.js +166 -0
  81. package/dist/server/terminal/activityChannel.js.map +1 -0
  82. package/dist/server/terminal/agentTranscriptStatus.d.ts +12 -0
  83. package/dist/server/terminal/agentTranscriptStatus.js +78 -7
  84. package/dist/server/terminal/agentTranscriptStatus.js.map +1 -1
  85. package/dist/server/terminal/codexTranscriptSource.d.ts +2 -0
  86. package/dist/server/terminal/codexTranscriptSource.js +41 -37
  87. package/dist/server/terminal/codexTranscriptSource.js.map +1 -1
  88. package/dist/server/terminal/subagentSources.js +2 -1
  89. package/dist/server/terminal/subagentSources.js.map +1 -1
  90. package/dist/server/terminal/transcriptHeader.d.ts +85 -0
  91. package/dist/server/terminal/transcriptHeader.js +376 -0
  92. package/dist/server/terminal/transcriptHeader.js.map +1 -0
  93. package/dist/server/terminal/transcriptHistory.d.ts +16 -2
  94. package/dist/server/terminal/transcriptHistory.js +120 -21
  95. package/dist/server/terminal/transcriptHistory.js.map +1 -1
  96. package/dist/server/terminal/transcriptStore.d.ts +6 -0
  97. package/dist/server/terminal/transcriptStore.js +222 -26
  98. package/dist/server/terminal/transcriptStore.js.map +1 -1
  99. package/dist/shared/activityProbe.d.ts +10 -0
  100. package/dist/shared/activityProbe.js +24 -0
  101. package/dist/shared/activityProbe.js.map +1 -0
  102. package/dist/shared/protocol.d.ts +45 -0
  103. package/dist/shared/protocol.js.map +1 -1
  104. package/dist/shared/types.d.ts +9 -1
  105. package/docs/features.md +10 -0
  106. package/docs/provider-hooks.md +287 -0
  107. package/docs/terminal-history.md +10 -0
  108. package/package.json +2 -1
  109. package/scripts/nterminal-hook.cjs +171 -0
  110. package/scripts/nterminalctl +26 -2
  111. package/dist/client/assets/MarkdownPreview-DjDO4lEl.js +0 -1
  112. package/dist/client/assets/index-CIYazAGv.css +0 -1
  113. package/dist/client/assets/index-DwvLN_WJ.js +0 -56
@@ -0,0 +1,24 @@
1
+ // Transport controls are deliberately not activity journal or monitor events.
2
+ export const activityProbeCapability = 'nonce-v1';
3
+ export const activityProbeMaxBytes = 512;
4
+ export const activityProbeIntervalMs = 30_000;
5
+ export const activityProbeDeadlineMs = 10_000;
6
+ export function decodeActivityProbe(text) {
7
+ if (text.length > activityProbeMaxBytes || new TextEncoder().encode(text).byteLength > activityProbeMaxBytes)
8
+ return null;
9
+ try {
10
+ const value = JSON.parse(text);
11
+ if (!value || typeof value !== 'object' || Array.isArray(value))
12
+ return null;
13
+ const probe = value;
14
+ if (Object.keys(probe).length !== 3 || !Object.hasOwn(probe, 'type') || !Object.hasOwn(probe, 'version')
15
+ || !Object.hasOwn(probe, 'nonce') || !['activity.ping', 'activity.pong'].includes(probe.type)
16
+ || probe.version !== 1 || typeof probe.nonce !== 'string' || !/^[A-Za-z0-9_-]{1,64}$/.test(probe.nonce))
17
+ return null;
18
+ return probe;
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ }
24
+ //# sourceMappingURL=activityProbe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activityProbe.js","sourceRoot":"","sources":["../../src/shared/activityProbe.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,MAAM,CAAC,MAAM,uBAAuB,GAAG,UAAU,CAAC;AAClD,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAG,CAAC;AACzC,MAAM,CAAC,MAAM,uBAAuB,GAAG,MAAM,CAAC;AAC9C,MAAM,CAAC,MAAM,uBAAuB,GAAG,MAAM,CAAC;AAQ9C,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,IAAI,IAAI,CAAC,MAAM,GAAG,qBAAqB,IAAI,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,UAAU,GAAG,qBAAqB;QAAE,OAAO,IAAI,CAAC;IAC1H,IAAI,CAAC;QACH,MAAM,KAAK,GAAY,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAC7E,MAAM,KAAK,GAAG,KAAsB,CAAC;QACrC,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC;eACnG,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,eAAe,EAAE,eAAe,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC;eAC1F,KAAK,CAAC,OAAO,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACvH,OAAO,KAAK,CAAC;IACf,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,IAAI,CAAC;IAAC,CAAC;AAC1B,CAAC"}
@@ -225,6 +225,31 @@ export interface CodeServerStopRequest {
225
225
  instanceId?: string;
226
226
  }
227
227
  export type AccountProvider = 'codex' | 'claude';
228
+ export interface AccountRefreshStatus {
229
+ state: 'success' | 'retrying' | 'reauth_required' | 'deferred';
230
+ lastAttemptAt: string | null;
231
+ lastSuccessAt: string | null;
232
+ nextRetryAt: string | null;
233
+ code: string | null;
234
+ httpStatus: number | null;
235
+ }
236
+ export interface AccountUsage {
237
+ status: 'available' | 'unavailable' | 'reauth_required' | 'unsupported';
238
+ checkedAt: string;
239
+ code: string | null;
240
+ windows: Array<{
241
+ kind: 'weekly' | 'session';
242
+ usedPercent: number;
243
+ resetsAt: string | null;
244
+ }>;
245
+ }
246
+ export interface AccountUsageResponse {
247
+ provider: AccountProvider;
248
+ profiles: Array<{
249
+ profileId: string;
250
+ usage: AccountUsage;
251
+ }>;
252
+ }
228
253
  export interface AccountProfile {
229
254
  id: string;
230
255
  provider: AccountProvider;
@@ -233,6 +258,7 @@ export interface AccountProfile {
233
258
  updatedAt: string;
234
259
  expiresAt: string | null;
235
260
  refreshTokenExpiresAt: string | null;
261
+ refresh?: AccountRefreshStatus;
236
262
  }
237
263
  export interface AccountRunningPane {
238
264
  terminalId: TerminalId;
@@ -312,8 +338,27 @@ export interface TerminalTranscriptHistoryResponse {
312
338
  cursor: string | null;
313
339
  hasMore: boolean;
314
340
  output: string[];
341
+ page?: {
342
+ identity: string;
343
+ snapshot: string;
344
+ /** Settled readable end, excluding any incomplete trailing JSON. */
345
+ snapshotSize: number;
346
+ snapshotMtime: number;
347
+ startOffset: number;
348
+ endOffset: number;
349
+ stableBefore: number;
350
+ /** Display-capped blocks in ascending source order, without archive trailer. */
351
+ records: Array<{
352
+ id: string;
353
+ startOffset: number;
354
+ endOffset: number;
355
+ block: string;
356
+ }>;
357
+ unchanged?: boolean;
358
+ };
315
359
  }
316
360
  export interface TerminalTranscriptHistoryRequest {
361
+ ifSnapshot?: string;
317
362
  cursor?: string;
318
363
  limit?: number;
319
364
  maxLines?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/shared/protocol.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/shared/protocol.ts"],"names":[],"mappings":"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"}
@@ -16,7 +16,15 @@ export interface TerminalSummary {
16
16
  agentStatus?: {
17
17
  provider: 'codex' | 'claude';
18
18
  sessionId?: string;
19
- state: 'working' | 'done' | 'stopped' | 'idle' | 'unknown';
19
+ state: 'working' | 'finishing' | 'waiting' | 'done' | 'stopped' | 'idle' | 'unknown';
20
+ source?: 'hook' | 'transcript';
21
+ revision?: number;
22
+ epoch?: string;
23
+ completion?: {
24
+ id: string;
25
+ outcome: TerminalCompletionOutcome;
26
+ occurredAt: string;
27
+ };
20
28
  };
21
29
  agentCache?: {
22
30
  source: {
package/docs/features.md CHANGED
@@ -8,6 +8,7 @@
8
8
  - Soft close detaches terminals into restorable pills.
9
9
  - Tab rename, drag reorder, and drag-to-split controls.
10
10
  - Activity dots and completion badges show whether a tab is running, idle, exited, or finished in the background.
11
+ - Working indicators are green and unread completion indicators are blue across themes, including mobile and favorites.
11
12
  - Detached terminal destroy uses a themed confirmation modal.
12
13
  - Tab titles sync with tmux window names when tmux persistence is active.
13
14
 
@@ -31,6 +32,15 @@
31
32
  - Trusted networks can be added by exact IP or CIDR to skip the re-auth prompt.
32
33
  - Security settings can revoke trusted devices, remove trusted networks, and disable TOTP.
33
34
 
35
+ ## Provider Accounts
36
+
37
+ - Each server keeps its own encrypted Codex and Claude credential vault. Active credentials remain in the CLI's native file, not a second vault copy.
38
+ - Account dialogs show access-token expiry, weekly usage remaining, reset time, and the last usage check. Quota checks are read-only, cached briefly, and never swap the active credential file.
39
+ - Saved accounts renew independently, including while another account is running or a new login is unfinished. Refresh outcomes include the last attempt, success, retry time, and a redacted provider error code.
40
+ - NTerminal defers direct refresh of an active credential while a native CLI owns it. An idle conversation does not mean authentication activity has stopped; native-owner refresh delegation is not implemented yet.
41
+ - Expired access tokens and revoked refresh tokens are different states. A provider-revoked refresh token requires a new login, not repeated automatic renewal attempts.
42
+ - Optional [provider lifecycle hooks](provider-hooks.md) can be installed from each server's account dialog or `nterminal hooks`. They provide verified session/activity signals without replacing native CLI authentication.
43
+
34
44
  ## Terminal Appearance And Settings
35
45
 
36
46
  - Settings is organized into Terminal, Shortcuts, Notifications, Security, and Servers sections.
@@ -0,0 +1,287 @@
1
+ # Provider Lifecycle Hooks
2
+
3
+ Lifecycle hooks are optional, per runtime user and per server. They improve
4
+ Codex/Claude session identification and activity notifications without forwarding
5
+ terminal output or changing the conversation. Existing transcript/process
6
+ detection remains available when hooks are absent or a binding is ambiguous.
7
+
8
+ ## Enable
9
+
10
+ Open the server's Codex or Claude account dialog, then choose **Install hooks**
11
+ and confirm. Main and secondary servers use the same control, including outbound
12
+ secondaries. Preview cannot change provider configuration.
13
+
14
+ The equivalent commands, run as the NTerminal runtime user, are:
15
+
16
+ ```sh
17
+ nterminal hooks status --provider codex
18
+ nterminal hooks install --provider codex
19
+ nterminal hooks install --provider claude
20
+ nterminal hooks remove --provider codex
21
+ nterminal hooks remove --provider claude
22
+ ```
23
+
24
+ Codex requires native approval in `/hooks`. NTerminal does not write trust hashes
25
+ or bypass provider policy. An unchanged hook definition retains its native trust;
26
+ an edited definition may require review again. Reinstalling or upgrading updates
27
+ NTerminal's hook groups in place, so their position (part of Codex's trust key)
28
+ and the position of your own hooks do not change. A new command (for example
29
+ after Node.js moved) still needs one review.
30
+
31
+ Configuration defaults are `~/.codex/hooks.json` and `~/.claude/settings.json`.
32
+ Runtime environment variables `CODEX_HOME` and `CLAUDE_CONFIG_DIR` override the
33
+ respective homes. Provider homes and settings files may be symlinks you own (for
34
+ example a dotfiles checkout); NTerminal edits the resolved file and keeps the
35
+ link. A symlink into a directory owned by another user or writable by others is
36
+ refused, and the error names that path. The state directory (`~/.nterminal`) may
37
+ also be a symlink. Provider policy can disable hooks even when the entries exist.
38
+ A configured hook is not necessarily active; the account dialog separately
39
+ reports whether verified events have been observed. The CLI reads the same
40
+ runtime `.env` as the launcher, including `export` assignments. Inherited
41
+ environment values take precedence; relative paths resolve from the application
42
+ directory, not the caller's working directory.
43
+
44
+ NTerminal never restarts a CLI to activate hooks. A CLI that does not reload its
45
+ configuration must be reopened by the operator. Use current provider versions
46
+ that support the installed events (Codex 0.158 and Claude Code 2.1.284 were
47
+ checked).
48
+
49
+ ### What Gets Installed
50
+
51
+ One hook group per event, each running the same command:
52
+
53
+ ```sh
54
+ [ -x '<node>' ] && [ -f '<state>/hooks/forward.cjs' ] && exec '<node>' '<state>/hooks/forward.cjs' '--provider' '<provider>' '--endpoint' '<state>/hooks/endpoint.json' || exit 0
55
+ ```
56
+
57
+ `<state>` is the resolved (realpath) state directory. The command uses only
58
+ POSIX `[`, `exec`, and `exit`, which `sh`, `bash`, `zsh`, and `fish` accept
59
+ (Codex runs hooks with the user's shell, Claude with bash or `$SHELL`). If
60
+ Node.js moved (for example an `nvm` upgrade) or the state directory was deleted,
61
+ the hook exits 0 without output instead of showing a hook error. Reinstall to
62
+ point the hooks at the current Node.js.
63
+
64
+ - Codex: `SessionStart`, `UserPromptSubmit`, `PreToolUse`, `PostToolUse`,
65
+ `PermissionRequest`, `Stop`, `Interrupt`, `SessionEnd`.
66
+ - Claude: `SessionStart`, `UserPromptSubmit`, `PreToolUse`, `PostToolUse`,
67
+ `PostToolUseFailure`, `PermissionRequest`, `Notification`, `Stop`,
68
+ `StopFailure`, `SessionEnd`.
69
+
70
+ NTerminal never uses a hook's response, so tool, permission, notification, and
71
+ interrupt hooks are `async: true`: the CLI does not wait for them. Only the turn
72
+ and session boundaries (`SessionStart`, `UserPromptSubmit`, `Stop`,
73
+ `StopFailure`, `SessionEnd`) stay synchronous, bounded by a 1-second timeout.
74
+ Codex does not allow asynchronous `SessionEnd`, and a CLI may exit right after a
75
+ boundary event. Events carry the helper's start time, so an asynchronous tool
76
+ event that finishes late is still ordered before a later `Stop`. `Subagent*`
77
+ events are not registered because child lifecycle events do not change a pane's
78
+ status.
79
+
80
+ Codex `notify` is not used. `hooks.json` provides `Stop`, and the session's
81
+ transcript `task_complete` record confirms the turn within the regular status
82
+ poll. `notify` is a single slot that may belong to you, and Codex passes its JSON
83
+ as one command-line argument, which fails for payloads over 128 KiB. An existing
84
+ `notify` (root or `[profiles.*]`) is left untouched and reported as
85
+ `notify_foreign`. Earlier NTerminal versions installed a `# BEGIN/END NTerminal
86
+ notify` block in `config.toml`; reinstalling or removing deletes that block (it
87
+ is recognised even after Codex rewrote other parts of the file) and nothing else.
88
+
89
+ ## Remove And Uninstall
90
+
91
+ `nterminal hooks remove --provider codex|claude` deletes only the hook groups
92
+ recorded in NTerminal's ownership manifest. Your own hooks and settings stay as
93
+ they were, including key order, indentation, and line endings. NTerminal also
94
+ deletes the Codex trust records (`[hooks.state."<hooks.json>:<event>:<group>:0"]`)
95
+ of the groups it removed, and nothing else in `config.toml`. A `hooks.json` or
96
+ `settings.json` that NTerminal created is deleted when it becomes empty. When
97
+ NTerminal's group is removed, your hooks listed after it in the same event move
98
+ up one position; Codex may then ask you to review them again.
99
+
100
+ `nterminal clean --force` and `nterminal uninstall --force` first remove
101
+ NTerminal's hooks from both providers. This is best effort: a failure prints a
102
+ warning and the command continues. Run `nterminal hooks status --provider
103
+ <provider>` afterwards if a warning appears. Uninstall NTerminal's hooks before
104
+ `npm uninstall -g nterminal`, because the hooks command is part of the package.
105
+
106
+ ## Troubleshooting
107
+
108
+ `nterminal hooks status --provider <provider>` prints `reason` and `warnings`:
109
+
110
+ - `node_unavailable`: the recorded Node.js no longer exists. The hooks exit 0
111
+ silently; reinstall to use the current Node.js.
112
+ - `helper_missing`: `<state>/hooks/forward.cjs` is gone. Reinstall.
113
+ - `unsafe_path` with `unsafePath`: that path is a symlink to a location NTerminal
114
+ will not edit, or a symlink inside the state directory.
115
+ - `hooks_disabled`: hooks are off in `config.toml` (`[features] hooks = false`,
116
+ also in the active `profile`), in `/etc/codex/requirements.toml` or
117
+ `/etc/codex/managed_config.toml` (`allow_managed_hooks_only`, `features`), in
118
+ `settings.json` (`disableAllHooks`, `allowManagedHooksOnly`), or in Claude's
119
+ managed `/etc/claude-code/managed-settings.json` (and
120
+ `managed-settings.d/*.json`).
121
+ - `ownership_conflict`: an NTerminal hook group or legacy notify command was
122
+ edited. NTerminal does not adopt edited entries; restore or delete them by
123
+ hand.
124
+ - Warnings: `legacy_definitions` (installed by an earlier version; reinstall to
125
+ get asynchronous tool hooks and the guarded command), `notify_foreign`,
126
+ `notify_profile_override` (a profile's `notify` overrides NTerminal's legacy
127
+ notify), `profile_hooks_disabled` (a non-default Codex profile disables hooks),
128
+ and `managed_policy_unreadable`.
129
+
130
+ Not checked: Claude project settings (`.claude/settings.json` and
131
+ `.claude/settings.local.json` in a repository) can disable hooks for that
132
+ project, and Codex hook trust is only visible in `/hooks`.
133
+
134
+ ## State And Notifications
135
+
136
+ - Idle retains the theme's existing idle color.
137
+ - Working is green; completion awaiting confirmation also stays active.
138
+ - Unread completion is blue in tabs, workspaces, and favorites. Displaying the
139
+ completed pane clears its unread mark. On mobile, other split panes remain
140
+ unread until displayed; focusing an editor does not mark a hidden terminal
141
+ read. A background server's last selected tab is not considered visible.
142
+ - Permission/input waits are not successful completion.
143
+ - A Codex `Stop` is only a completion candidate. The rollout's matching
144
+ `task_complete` marker confirms the turn on the next status poll (about 2 s).
145
+ Installs from earlier NTerminal versions may still send the legacy `notify`
146
+ `agent-turn-complete`, which is honored as confirmation too; current installs
147
+ no longer add a `notify` command.
148
+ - A Claude `Stop` is also provisional because another hook can continue the
149
+ turn. A matching post-turn `turn_duration` record confirms it when no pending
150
+ background agents/workflows remain. The record shape was checked against
151
+ Claude Code 2.1.283. A subsequent native idle notification can also confirm a
152
+ pending Stop. Missing/unknown evidence is not turned into success by a timer.
153
+ - A lost prompt hook can recover from a later Stop and fresh post-turn evidence.
154
+ A competing still-active turn is not replaced by anonymous evidence; it stays
155
+ pending until its identity can be verified. A failure recorded for that
156
+ candidate remains a stopped outcome when the identity is later confirmed.
157
+ - Errors and interruption produce a stopped outcome, not successful completion.
158
+ - Child/background hooks do not finish the parent session. A completed foreground
159
+ turn does not imply that a long-running goal or every detached task is finished.
160
+
161
+ Completion IDs are shared by live status, unread badges, animation notifications,
162
+ and push routing. The activity stream works independently of an open pane socket.
163
+ Reconnects replay a bounded recent metadata journal. A journal gap or server
164
+ restart sends a non-notifying snapshot instead of replaying historical alarms.
165
+ Missing entries in an activity snapshot never imply a terminal exit: remote
166
+ snapshots can be retained during an outage. Explicit terminal lifecycle events
167
+ and terminal discovery remain responsible for that state.
168
+ Initial REST loading preserves newer live state, local layout edits, and explicit
169
+ terminal deletion. It does not generate historical completion notifications.
170
+
171
+ The browser checks the separate activity connection while visible and on resume.
172
+ An unresponsive connection is replaced without reconnecting terminal output,
173
+ replaying input, resizing panes, or changing keyboard focus. These checks cannot
174
+ run while the browser suspends JavaScript; server-side push remains independent.
175
+
176
+ ## Ownership And Resource Limits
177
+
178
+ The installed helper is kept under the persistent NTerminal state directory,
179
+ outside the npm package. Updates refresh only a verified, previously opted-in
180
+ helper; provider configuration and approval definitions are not silently changed.
181
+ Removal deletes only entries whose ownership still matches the installation
182
+ manifest. Concurrent edits and unknown existing commands fail closed.
183
+ Interrupted installations retain an ownership journal. Installer locks are
184
+ reclaimed only after verifying a dead owner; PID reuse, boot identity, and foreign
185
+ hosts are checked. Without a stable machine ID, recovery is limited to the same
186
+ boot. Unverifiable old locks are left intact, not removed based on age.
187
+
188
+ Helper upgrades and rollback use one durable ownership journal shared by both
189
+ providers. A failed configuration rollback retains any helper that a remaining
190
+ hook may reference. User-modified helper files are never silently adopted.
191
+
192
+ The receiver also recovers publication locks whose recorded owner is provably
193
+ dead. An old, empty `hooks/.endpoint-lock` directory has no ownership record and
194
+ cannot be recovered automatically. If that specific legacy marker blocks hook
195
+ activation, stop every runtime sharing the state directory, verify the marker
196
+ is an empty directory, and remove only that empty directory with `rmdir` before
197
+ starting again. Do not delete ownership records or a lock held by a running
198
+ runtime. Ordinary transcript/process detection remains available when hook
199
+ activation is unavailable.
200
+
201
+ Events are sent through a private Unix socket, authenticated with a runtime
202
+ capability. The receiver checks the live helper/provider process ancestry and
203
+ process start identities. A session is assigned to a pane using verified process
204
+ ownership or an exact native binding, never just the working directory. Shared
205
+ daemon ambiguity falls back to native discovery/manual history selection rather
206
+ than assigning another pane's session. Claude hooks run as children of the
207
+ pane's Claude process, so their binding is exact. Codex needs the extra rules in
208
+ [Codex Shared Background Server](#codex-shared-background-server).
209
+ A transient discovery gap does not retire an existing hook session. Old resume
210
+ arguments cannot replace it, and process ownership is rechecked after asynchronous
211
+ validation before publishing status or completion.
212
+ Nested provider processes do not take over the parent pane's binding, including
213
+ children sharing its foreground process group. Normal Codex npm launchers and
214
+ provider commands that replace the pane shell remain supported.
215
+
216
+ Transcript identity checks use 64 KiB chunks with a 512 KiB header limit, not a
217
+ whole-history read. A bounded cache retains identity proofs rather than message
218
+ text; changed files require prefix revalidation. Oversized headers need an exact
219
+ live writable-file association. Incomplete, conflicting, or unprovable fork
220
+ metadata stays unconfirmed instead of selecting a different conversation.
221
+
222
+ Only lifecycle metadata is forwarded. Prompts, tool arguments, assistant bodies,
223
+ credentials, and tokens are not included in event payloads or diagnostic logs.
224
+ Queues, messages, sockets, journal size, and helper duration are bounded. Hook
225
+ failures return neutral output so NTerminal cannot block or steer the native CLI.
226
+ Under load, per-tool events (`PreToolUse`/`PostToolUse`, synchronous or
227
+ asynchronous) are coalesced per session and dropped first. Start, stop, and
228
+ completion events use a reserved part of the queue and evict waiting tool
229
+ events, so a `Stop` is not refused while tool events are queued. Tool events for
230
+ a session no pane is bound to never trigger a Codex daemon lookup. A failed
231
+ lookup for a session is reused for 5 seconds unless a `SessionStart` or
232
+ `UserPromptSubmit` for it arrives.
233
+ JSONL remains the source for history, token usage, and completion reconciliation;
234
+ hook-backed terminals use a slower usage refresh and a small cached tail check
235
+ while confirming completion.
236
+
237
+ ## Codex Shared Background Server
238
+
239
+ Codex 0.158 hands every interactive session to one shared background server
240
+ (`codex app-server --managed-daemon`). Its hooks run as children of that
241
+ server, not of the pane, so process ancestry cannot identify the pane. NTerminal
242
+ instead follows each pane's Codex connection to the server and matches the
243
+ server's open sessions by working directory. That match alone is not enough to
244
+ bind a session to a pane, because another Codex client, such as another
245
+ terminal or VS Code, can have a session open in the same directory.
246
+
247
+ A new Codex binding therefore also needs evidence tied to that pane's Codex
248
+ process:
249
+
250
+ - the first binding after Codex starts: `codex resume <id>` names the session,
251
+ or the session was created while that Codex process was starting (Codex
252
+ session IDs record their creation time);
253
+ - a later switch in the same process (`/new`, `/resume`): the session's
254
+ `SessionStart` arrived after the pane's last bound event.
255
+
256
+ A session already bound to one pane is never moved to another pane. If several
257
+ panes qualify, none is bound. A binding created by a completion event does not
258
+ notify for that turn, because NTerminal did not see the turn start.
259
+
260
+ Limits:
261
+
262
+ - **One Codex per directory.** Hook binding needs one Codex session per working
263
+ directory on the shared server. With two sessions in the same directory, both
264
+ panes stay unbound. The account dialog lists these panes as not linked to hook
265
+ events, and they keep transcript-based status. To run several Codex sessions
266
+ in one directory with hooks, start them with `codex --no-daemon` (also
267
+ `codex resume --no-daemon <id>`). That runs the session inside the pane's own
268
+ Codex process, where binding is exact.
269
+ - **About 60 seconds after `/new` or `/resume`.** The server keeps the previous
270
+ session loaded for about a minute. Until it unloads, the pane has two
271
+ sessions in the same directory and stays on its previous binding. Hook status
272
+ resumes with the next prompt or stop event after that. Codex also creates a
273
+ new session's file only when the session is first saved, so a new session has
274
+ nothing to match until its first prompt.
275
+ - **After an NTerminal restart.** A Codex pane that switched sessions with
276
+ `/new` or `/resume` before the restart cannot be tied to its process again
277
+ until its next `/new` or `/resume`. It keeps transcript-based status until
278
+ then.
279
+ - **Clients outside NTerminal.** NTerminal can only rule out other NTerminal
280
+ panes. If a Codex client outside NTerminal starts a session in the same
281
+ directory within 30 seconds of a new pane starting, or while a pane is
282
+ switching sessions and has nothing saved yet, NTerminal cannot tell the two
283
+ apart. One Codex per directory, or `--no-daemon`, avoids this.
284
+
285
+ The local receiver currently requires Linux procfs and Unix sockets. Unsupported
286
+ environments retain transcript/process detection. This integration does not
287
+ change credential-switch or credential-refresh safety rules.
@@ -45,10 +45,20 @@ JSONL records are loaded by cursor and rendered into readable user, assistant, a
45
45
 
46
46
  Loading older pages keeps previously loaded pages in the dialog, so ordinary scrolling can return to recent conversations without fetching them again.
47
47
 
48
+ Closing History retains its conversation pages and reading position in a browser-memory cache. Reopening the same server/terminal/session restores them immediately while checking the latest source snapshot. Unchanged sources need no conversation payload. If output was appended, bounded backward pages bridge the previous stable prefix before replacing the mutable tail; disconnected ranges are never joined as if contiguous. Source replacement or truncation resets the snapshot. Text selection defers applying refreshed content. Search-result excerpts are kept separate from the browsing snapshot.
49
+
50
+ The browser cache is cleared on logout or authentication loss, and late requests cannot refill it. Its 64 MiB approximate text budget and 30-minute idle expiry reclaim closed readers on cache activity, never truncate an active reader. Nothing is written to localStorage or IndexedDB. A full page reload therefore still fetches History from the server's disk cache.
51
+
48
52
  ### Derived Conversation Cache
49
53
 
50
54
  History and search share private derived files below the state directory's `transcript-cache` directory. Recent records are returned first. After a user opens or searches a session, a background worker fills the remaining archive, one approximately 1 MiB source window at a time with a pause between windows. Foreground requests take priority over subsequent background work. Completed archives are checked for changes every 30 seconds; appended JSONL output updates the tail while keeping valid older ranges. Source replacement, truncation, or a parser version change invalidates affected cache data. Previously visited sources are remembered in private cache metadata so prebuilding resumes after a server restart.
51
55
 
56
+ Source extraction windows are independent of storage size. Background compaction merges adjacent stable segments toward 1 MiB of derived data, retaining original offsets and empty coverage. Mutable tail segments are excluded. Merges require balanced input sizes so small appends cannot repeatedly rewrite a large segment. Each pass handles at most 16 files and yields at file boundaries when foreground work arrives; deferred merges remain pending rather than being marked complete. New files are published through an atomic manifest replacement before old files are removed; existing cache data is reused without reparsing the JSONL. Read requests reuse parsed segments, avoid permission writes when modes are already private, and do not rewrite unchanged manifests. Access timestamps are persisted at most once per minute. Fragmented caches limit a page to 32 windows and retain a continuation cursor rather than performing unbounded small-file I/O.
57
+
58
+ Files retired by invalidation or compaction are reclaimed by the bounded background maintenance scan, not by the request that replaces the manifest. They remain included in disk usage until cleanup accounts for their deletion. A parser upgrade therefore does not make the first History request wait for thousands of old cache files to be deleted.
59
+
60
+ Parser v3 bounds Codex duplicate evidence to the same 64 KiB context used for stable-tail invalidation. Forward distances use the ends of the JSON payloads, excluding trailing whitespace and line delimiters, so a bounded read or a later newline cannot change the result. Duplicate evidence outside that range remains visible instead of changing old content depending on page size or future appends. Existing v2 Codex cache data is regenerated once for this semantic correction; v2 Claude records are reused without re-extraction.
61
+
52
62
  The cache survives an NTerminal restart and uses private directory/file permissions. There is no session-count or per-session size quota. Maintenance expires caches seven days after the last user History or search request, and evicts least recently used caches at an approximately 5 GiB total safety limit. Background activity does not renew retention. New writes pause when less than 512 MiB of disk space is available; cached reads and original JSONL reads remain available. Maintenance normally runs every five minutes, and also when the total limit is reached. It contains conversation text and source paths, so treat the state directory as sensitive. Source JSONL files remain authoritative and are never changed. Missing or invalid cache files are rebuilt on demand.
53
63
 
54
64
  Cache storage failures fall back to reading the source JSONL. Search continuation does not depend on the cache remaining available. Search text is extracted before display truncation; a result deep inside a long message opens an excerpt around the match.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nterminal",
3
- "version": "1.3.4",
3
+ "version": "1.3.5",
4
4
  "license": "GPL-3.0-only",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.7",
@@ -22,6 +22,7 @@
22
22
  "docs/",
23
23
  "public/",
24
24
  "scripts/nterminalctl",
25
+ "scripts/nterminal-hook.cjs",
25
26
  "scripts/nterminal-tmux.conf"
26
27
  ],
27
28
  "scripts": {