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
@@ -0,0 +1,74 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { HookControlError, replace, sameSnapshot, snapshot, syncSnapshot } from './hookFiles.js';
3
+ function hash(value) { return value === null ? null : createHash('sha256').update(value.text).digest('hex'); }
4
+ function validHash(value) { return value === null || (typeof value === 'string' && /^[a-f0-9]{64}$/.test(value)); }
5
+ /** All methods require the runtime installation lock, including recovery. */
6
+ export class HelperJournal {
7
+ helperPath;
8
+ recordPath;
9
+ constructor(helperPath, recordPath) {
10
+ this.helperPath = helperPath;
11
+ this.recordPath = recordPath;
12
+ }
13
+ parse(saved) {
14
+ if (!saved)
15
+ return null;
16
+ let value;
17
+ try {
18
+ value = JSON.parse(saved.text);
19
+ }
20
+ catch {
21
+ throw new HookControlError('manifest_malformed');
22
+ }
23
+ if (!value || typeof value !== 'object' || Array.isArray(value) || value.helperPath !== this.helperPath
24
+ || !(value.version === 1 && typeof value.sha256 === 'string' && validHash(value.sha256)
25
+ || value.version === 2 && (value.phase === 'committed' && validHash(value.sha256)
26
+ || value.phase === 'prepared' && validHash(value.oldSha256) && validHash(value.newSha256)))) {
27
+ throw new HookControlError('manifest_malformed');
28
+ }
29
+ return value;
30
+ }
31
+ committed(sha256) {
32
+ return JSON.stringify({ version: 2, helperPath: this.helperPath, phase: 'committed', sha256 }, null, 2) + '\n';
33
+ }
34
+ async resolve() {
35
+ let saved = await snapshot(this.recordPath);
36
+ const owner = this.parse(saved);
37
+ const helper = await snapshot(this.helperPath);
38
+ if (!owner)
39
+ return { helper, record: saved, owned: false };
40
+ const actual = hash(helper);
41
+ if (owner.version === 2 && owner.phase === 'prepared') {
42
+ if (actual !== owner.oldSha256 && actual !== owner.newSha256)
43
+ throw new HookControlError('helper_modified');
44
+ // Finish the helper durability barrier even if the previous writer exited
45
+ // between rename/unlink and directory fsync. Recovery never needs H2 bytes.
46
+ await syncSnapshot(this.helperPath, helper);
47
+ saved = await replace(this.recordPath, saved, this.committed(actual), { durable: true });
48
+ }
49
+ else if (actual !== owner.sha256) {
50
+ throw new HookControlError(helper ? 'helper_modified' : 'helper_missing');
51
+ }
52
+ return { helper, record: saved, owned: true };
53
+ }
54
+ async transition(before, text) {
55
+ if (!before.owned && before.helper && before.helper.text !== text)
56
+ throw new HookControlError('helper_conflict');
57
+ if (before.owned && (before.helper?.text ?? null) === text) {
58
+ await syncSnapshot(this.helperPath, before.helper);
59
+ await syncSnapshot(this.recordPath, before.record);
60
+ // The helper may change while the ownership record's durability barrier runs.
61
+ if (!sameSnapshot(await snapshot(this.helperPath), before.helper))
62
+ throw new HookControlError('config_changed');
63
+ return before;
64
+ }
65
+ const newSha256 = text === null ? null : createHash('sha256').update(text).digest('hex');
66
+ const prepared = JSON.stringify({ version: 2, helperPath: this.helperPath, phase: 'prepared',
67
+ oldSha256: hash(before.helper), newSha256 }, null, 2) + '\n';
68
+ const pending = await replace(this.recordPath, before.record, prepared, { durable: true });
69
+ const helper = await replace(this.helperPath, before.helper, text, { durable: true });
70
+ const record = await replace(this.recordPath, pending, this.committed(newSha256), { durable: true });
71
+ return { helper, record, owned: true };
72
+ }
73
+ }
74
+ //# sourceMappingURL=helperJournal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helperJournal.js","sourceRoot":"","sources":["../../../src/server/hooks/helperJournal.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import type { PtyTranscriptSource } from '../terminal/PtyAdapter.js';
2
+ import { type TranscriptHeaderReader, type TranscriptHeaderProof, type TranscriptHeaderResult, type WritableTranscriptFdProof } from '../terminal/transcriptHeader.js';
3
+ export interface HookProcessIdentity {
4
+ pid: number;
5
+ startTicks: string;
6
+ }
7
+ export interface HookOwner extends HookProcessIdentity {
8
+ provider: 'codex' | 'claude';
9
+ foreground: boolean;
10
+ shared?: boolean;
11
+ }
12
+ export declare function readHookOwner(identity: HookProcessIdentity, provider: 'codex' | 'claude'): Promise<HookOwner | null>;
13
+ export type HookTranscriptValidation = {
14
+ status: 'valid';
15
+ source: PtyTranscriptSource;
16
+ proof: TranscriptHeaderProof;
17
+ verification: 'header' | 'native';
18
+ } | Exclude<TranscriptHeaderResult, {
19
+ status: 'valid';
20
+ }> | {
21
+ status: 'pending';
22
+ reason: 'owner_changed';
23
+ };
24
+ export interface HookTranscriptProofRequest {
25
+ owner: HookOwner;
26
+ proof: TranscriptHeaderProof;
27
+ reason: 'limit' | 'unresolved';
28
+ ancestorSessionIds: readonly string[];
29
+ }
30
+ export interface HookTranscriptValidationOptions {
31
+ /** Trusted internal overrides, including isolated fixtures; never request parameters. */
32
+ rootPath?: string;
33
+ procRoot?: string;
34
+ readOwner?: typeof readHookOwner;
35
+ reader?: TranscriptHeaderReader;
36
+ resolveNativeProof?: (request: HookTranscriptProofRequest) => Promise<WritableTranscriptFdProof | null>;
37
+ }
38
+ export declare function inspectHookTranscript(owner: HookOwner, sessionId: string, filePath: string, options?: HookTranscriptValidationOptions): Promise<HookTranscriptValidation>;
39
+ export declare function validateHookTranscript(owner: HookOwner, sessionId: string, filePath: string, options?: HookTranscriptValidationOptions): Promise<PtyTranscriptSource | null>;
@@ -0,0 +1,91 @@
1
+ import { readFile, readlink } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import { identifyProcess } from '../system/processIdentity.js';
5
+ import { readProcessEnvValuesAsync } from '../terminal/procfs.js';
6
+ import { findWritableTranscriptFd, sharedTranscriptHeaderReader, revalidateTranscriptHeaderProof, writableTranscriptFdCurrent } from '../terminal/transcriptHeader.js';
7
+ export async function readHookOwner(identity, provider) {
8
+ try {
9
+ const raw = await readFile(`/proc/${identity.pid}/stat`, 'utf8');
10
+ const fields = raw.slice(raw.lastIndexOf(')') + 2).trim().split(/\s+/);
11
+ if (fields[19] !== identity.startTicks || fields[0] === 'Z' || fields[0] === 'X')
12
+ return null;
13
+ const executable = await readlink(`/proc/${identity.pid}/exe`);
14
+ const args = (await readFile(`/proc/${identity.pid}/cmdline`, 'utf8')).split('\0');
15
+ const process = identifyProcess('', executable, args, false);
16
+ if (process.name !== provider)
17
+ return null;
18
+ return { ...identity, provider, foreground: fields[4] !== '0' && fields[2] === fields[5] && process.category !== 'shared',
19
+ shared: process.category === 'shared' };
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ }
25
+ function sameHookOwner(current, expected) {
26
+ return current !== null && current.pid === expected.pid && current.startTicks === expected.startTicks
27
+ && current.provider === expected.provider && current.foreground === expected.foreground
28
+ && (current.shared ?? false) === (expected.shared ?? false);
29
+ }
30
+ // Call only after pane ownership is established. Shared daemons additionally
31
+ // require the Manager's exact native client/session gate, not an ancestry match.
32
+ export async function inspectHookTranscript(owner, sessionId, filePath, options = {}) {
33
+ const readOwner = options.readOwner ?? readHookOwner;
34
+ try {
35
+ if (!sameHookOwner(await readOwner(owner, owner.provider), owner))
36
+ return { status: 'pending', reason: 'owner_changed' };
37
+ let rootPath = options.rootPath;
38
+ if (rootPath === undefined) {
39
+ const env = await readProcessEnvValuesAsync(owner.pid, ['HOME', 'CODEX_HOME', 'CLAUDE_CONFIG_DIR']);
40
+ const home = env.get('HOME') || os.homedir();
41
+ rootPath = owner.provider === 'codex'
42
+ ? path.join(env.get('CODEX_HOME') || path.join(home, '.codex'), 'sessions')
43
+ : path.join(env.get('CLAUDE_CONFIG_DIR') || path.join(home, '.claude'), 'projects');
44
+ }
45
+ const request = { provider: owner.provider, sessionId, filePath, rootPath };
46
+ const result = await (options.reader ?? sharedTranscriptHeaderReader).read(request);
47
+ if (result.status === 'invalid')
48
+ return result;
49
+ let verification = 'header';
50
+ let native = null;
51
+ if (result.status === 'pending') {
52
+ if (!result.proof || (result.reason !== 'limit' && result.reason !== 'unresolved'))
53
+ return result;
54
+ native = options.resolveNativeProof
55
+ ? await options.resolveNativeProof({ owner, proof: { ...result.proof }, reason: result.reason,
56
+ ancestorSessionIds: result.ancestorSessionIds ?? [] })
57
+ : await findWritableTranscriptFd(owner, result.proof, options.procRoot);
58
+ if (!native || native.kind !== 'writable-fd')
59
+ return result;
60
+ if (native.provider !== owner.provider || native.sessionId !== sessionId)
61
+ return { status: 'invalid', reason: 'identity_conflict' };
62
+ if (native.ownerPid !== owner.pid || native.ownerStartTicks !== owner.startTicks || native.filePath !== result.proof.filePath
63
+ || native.dev !== result.proof.dev || native.ino !== result.proof.ino)
64
+ return result;
65
+ const lineage = native.ancestorSessionIds ?? result.lineageSessionIds;
66
+ if (result.ancestorSessionIds?.length && (!lineage || lineage.length > 32
67
+ || !result.ancestorSessionIds.every(id => lineage.includes(id))))
68
+ return result;
69
+ if (!await writableTranscriptFdCurrent(native, options.procRoot))
70
+ return result;
71
+ verification = 'native';
72
+ }
73
+ const proof = result.proof;
74
+ if (!sameHookOwner(await readOwner(owner, owner.provider), owner))
75
+ return { status: 'pending', reason: 'owner_changed' };
76
+ if (native && !await writableTranscriptFdCurrent(native, options.procRoot))
77
+ return { status: 'pending', reason: 'changed' };
78
+ const currentProof = await revalidateTranscriptHeaderProof(proof, request, result.status === 'valid' || result.reason === 'limit');
79
+ if (!currentProof)
80
+ return { status: 'pending', reason: 'changed' };
81
+ return { status: 'valid', source: { provider: owner.provider, sessionId, filePath: currentProof.filePath }, proof: currentProof, verification };
82
+ }
83
+ catch {
84
+ return { status: 'pending', reason: 'unavailable' };
85
+ }
86
+ }
87
+ export async function validateHookTranscript(owner, sessionId, filePath, options = {}) {
88
+ const result = await inspectHookTranscript(owner, sessionId, filePath, options);
89
+ return result.status === 'valid' ? result.source : null;
90
+ }
91
+ //# sourceMappingURL=hookBinding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hookBinding.js","sourceRoot":"","sources":["../../../src/server/hooks/hookBinding.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ export declare class HookControlError extends Error {
2
+ readonly code: string;
3
+ readonly path?: string | undefined;
4
+ /** Offending filesystem path for operator diagnostics; never file contents. */
5
+ constructor(code: string, path?: string | undefined);
6
+ }
7
+ export type FileSnapshot = {
8
+ text: string;
9
+ mode: number;
10
+ ino: number;
11
+ dev: number;
12
+ } | null;
13
+ export declare const MAX_FILE_BYTES: number;
14
+ export declare function missing(error: unknown): boolean;
15
+ export declare function sameSnapshot(a: FileSnapshot, b: FileSnapshot): boolean;
16
+ export declare function safeParents(target: string): Promise<void>;
17
+ export declare function ensureDirectory(directory: string): Promise<void>;
18
+ export declare function snapshot(target: string, owned?: boolean): Promise<FileSnapshot>;
19
+ export declare function syncDirectory(directory: string): Promise<void>;
20
+ export declare function syncSnapshot(target: string, expected: FileSnapshot): Promise<void>;
21
+ export declare function replace(target: string, expected: FileSnapshot, text: string | null, options?: {
22
+ durable?: boolean;
23
+ onApplied?: (after: FileSnapshot) => void;
24
+ }): Promise<FileSnapshot>;
@@ -0,0 +1,160 @@
1
+ import { constants } from 'node:fs';
2
+ import { lstat, mkdir, open, rename, unlink } from 'node:fs/promises';
3
+ import { randomUUID } from 'node:crypto';
4
+ import path from 'node:path';
5
+ export class HookControlError extends Error {
6
+ code;
7
+ path;
8
+ /** Offending filesystem path for operator diagnostics; never file contents. */
9
+ constructor(code, path) {
10
+ super(code);
11
+ this.code = code;
12
+ this.path = path;
13
+ }
14
+ }
15
+ export const MAX_FILE_BYTES = 1024 * 1024;
16
+ export function missing(error) { return error?.code === 'ENOENT'; }
17
+ export function sameSnapshot(a, b) {
18
+ return a === null || b === null ? a === b : a.text === b.text && a.mode === b.mode && a.ino === b.ino && a.dev === b.dev;
19
+ }
20
+ export async function safeParents(target) {
21
+ let directory = path.dirname(target);
22
+ while (true) {
23
+ try {
24
+ const info = await lstat(directory);
25
+ if (!info.isDirectory() || info.isSymbolicLink())
26
+ throw new HookControlError('unsafe_path', directory);
27
+ }
28
+ catch (error) {
29
+ if (!missing(error))
30
+ throw error;
31
+ }
32
+ const parent = path.dirname(directory);
33
+ if (parent === directory)
34
+ return;
35
+ directory = parent;
36
+ }
37
+ }
38
+ export async function ensureDirectory(directory) {
39
+ await safeParents(path.join(directory, '_'));
40
+ await mkdir(directory, { recursive: true, mode: 0o700 });
41
+ // A previous interrupted mkdir may leave existing but unsynced ancestors.
42
+ // Persist the entire reachable chain before publishing external references.
43
+ let current = path.resolve(directory);
44
+ while (true) {
45
+ await syncDirectory(current);
46
+ const parent = path.dirname(current);
47
+ if (parent === current)
48
+ return;
49
+ current = parent;
50
+ }
51
+ }
52
+ export async function snapshot(target, owned = true) {
53
+ await safeParents(target);
54
+ let handle;
55
+ try {
56
+ handle = await open(target, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
57
+ }
58
+ catch (error) {
59
+ if (missing(error))
60
+ return null;
61
+ if (error.code === 'ELOOP')
62
+ throw new HookControlError('unsafe_path', target);
63
+ throw error;
64
+ }
65
+ try {
66
+ const info = await handle.stat();
67
+ if (!info.isFile() || (owned && (info.nlink !== 1 || (process.getuid && info.uid !== process.getuid()))))
68
+ throw new HookControlError('unsafe_path', target);
69
+ if (info.size > MAX_FILE_BYTES)
70
+ throw new HookControlError('config_too_large');
71
+ const buffer = Buffer.alloc(MAX_FILE_BYTES + 1);
72
+ let length = 0;
73
+ while (length < buffer.length) {
74
+ const { bytesRead } = await handle.read(buffer, length, buffer.length - length, null);
75
+ if (!bytesRead)
76
+ break;
77
+ length += bytesRead;
78
+ }
79
+ if (length > MAX_FILE_BYTES)
80
+ throw new HookControlError('config_too_large');
81
+ const text = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(buffer.subarray(0, length));
82
+ return { text, mode: info.mode & 0o777, ino: info.ino, dev: info.dev };
83
+ }
84
+ finally {
85
+ await handle.close();
86
+ }
87
+ }
88
+ export async function syncDirectory(directory) {
89
+ await safeParents(path.join(directory, '_'));
90
+ const handle = await open(directory, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
91
+ try {
92
+ await handle.sync();
93
+ }
94
+ finally {
95
+ await handle.close();
96
+ }
97
+ }
98
+ export async function syncSnapshot(target, expected) {
99
+ if (!sameSnapshot(await snapshot(target), expected))
100
+ throw new HookControlError('config_changed');
101
+ if (expected) {
102
+ const handle = await open(target, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
103
+ try {
104
+ const info = await handle.stat();
105
+ if (info.ino !== expected.ino || info.dev !== expected.dev)
106
+ throw new HookControlError('config_changed');
107
+ await handle.sync();
108
+ }
109
+ finally {
110
+ await handle.close();
111
+ }
112
+ }
113
+ await syncDirectory(path.dirname(target));
114
+ if (!sameSnapshot(await snapshot(target), expected))
115
+ throw new HookControlError('config_changed');
116
+ }
117
+ export async function replace(target, expected, text, options = {}) {
118
+ if (text !== null && Buffer.byteLength(text) > MAX_FILE_BYTES)
119
+ throw new HookControlError('config_too_large');
120
+ const current = await snapshot(target);
121
+ if (!sameSnapshot(current, expected))
122
+ throw new HookControlError('config_changed');
123
+ if (current?.text === text || (current === null && text === null)) {
124
+ if (options.durable)
125
+ await syncSnapshot(target, current);
126
+ return current;
127
+ }
128
+ const temp = `${target}.nterminal-${randomUUID()}.tmp`;
129
+ try {
130
+ let after = null;
131
+ if (text !== null) {
132
+ const file = await open(temp, 'wx', expected?.mode ?? 0o600);
133
+ try {
134
+ await file.writeFile(text);
135
+ await file.chmod(expected?.mode ?? 0o600);
136
+ await file.sync();
137
+ const info = await file.stat();
138
+ after = { text, mode: info.mode & 0o777, ino: info.ino, dev: info.dev };
139
+ }
140
+ finally {
141
+ await file.close();
142
+ }
143
+ }
144
+ if (!sameSnapshot(await snapshot(target), expected))
145
+ throw new HookControlError('config_changed');
146
+ if (text === null)
147
+ await unlink(target);
148
+ else
149
+ await rename(temp, target);
150
+ options.onApplied?.(after);
151
+ if (options.durable)
152
+ await syncDirectory(path.dirname(target));
153
+ return after;
154
+ }
155
+ finally {
156
+ await unlink(temp).catch((error) => { if (!missing(error))
157
+ throw error; });
158
+ }
159
+ }
160
+ //# sourceMappingURL=hookFiles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hookFiles.js","sourceRoot":"","sources":["../../../src/server/hooks/hookFiles.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,108 @@
1
+ import { type CodexHookTrust } from './codexTrust.js';
2
+ export type { CodexHookTrust } from './codexTrust.js';
3
+ export type HookProvider = 'codex' | 'claude';
4
+ export interface ProviderHookInstallation {
5
+ provider: HookProvider;
6
+ installed: boolean;
7
+ configPath: string;
8
+ notifyConfigPath?: string;
9
+ approvalRequired: boolean | null;
10
+ reason?: string;
11
+ /** Offending path for `unsafe_path`; never file contents. */
12
+ unsafePath?: string;
13
+ /** Non-fatal, bounded reason codes that can explain why installed hooks may not run. */
14
+ warnings?: string[];
15
+ /** Installed Codex hooks: how many NTerminal definitions exist and the helper command they run. */
16
+ hookCount?: number;
17
+ hookCommand?: string;
18
+ /** Codex's own trust report, from the last trust operation or check while the files are unchanged. */
19
+ trust?: CodexHookTrust;
20
+ }
21
+ export type CodexHookTrustResult = ProviderHookInstallation & {
22
+ trust: CodexHookTrust;
23
+ };
24
+ export interface HookInstallationOptions {
25
+ stateDir: string;
26
+ homeDir?: string;
27
+ /** Explicit effective provider homes, including values loaded from a runtime env file. */
28
+ providerHomes?: Partial<Record<HookProvider, string>>;
29
+ /** Resolve relative homes/state paths against the runtime's working directory. */
30
+ baseDir?: string;
31
+ /** Procfs identity source; isolated tests provide a synthetic tree. */
32
+ procRoot?: string;
33
+ /** Optional stable host identity source; unavailable IDs restrict recovery to this boot. */
34
+ machineIdPath?: string;
35
+ previewMode?: boolean;
36
+ /** Path to the packaged standalone helper, not JavaScript source text. */
37
+ helperSource?: string;
38
+ nodeExecutable?: string;
39
+ /** Read-only managed policy locations; isolated tests provide synthetic directories. */
40
+ codexSystemDir?: string;
41
+ claudeManagedDir?: string;
42
+ /** Codex CLI for trust operations; default: `codex` resolved like the user's shell PATH. */
43
+ codexExecutable?: string;
44
+ /** Environment for the short-lived Codex app-server (tests); default: process.env. */
45
+ codexEnv?: NodeJS.ProcessEnv;
46
+ /** Hard limit for one Codex app-server session, including startup. */
47
+ codexTimeoutMs?: number;
48
+ }
49
+ export interface ManagedHelperRefresh {
50
+ updated: boolean;
51
+ reason?: string;
52
+ }
53
+ export declare class HookInstallation {
54
+ private readonly options;
55
+ private hooksDir;
56
+ private readonly lexicalHooksDir;
57
+ private helperPath;
58
+ private helperOwnershipPath;
59
+ private helperJournal;
60
+ private endpointPath;
61
+ private readonly nodeExecutable;
62
+ private readonly homes;
63
+ private resolved;
64
+ /** Last successful Codex trust report, valid only while hooks.json, config.toml, and the manifest are unchanged. */
65
+ private codexTrustCache;
66
+ private codexTrustCheck;
67
+ constructor(options: HookInstallationOptions);
68
+ /**
69
+ * Resolve symlinks once per operation. The receiver authenticates the helper
70
+ * by comparing its argv with realpath(stateDir)/hooks, so commands must use the
71
+ * resolved path. Provider homes may be user-owned symlinks (dotfiles); all I/O
72
+ * then targets the resolved path, which has no symlink components left.
73
+ */
74
+ private prepare;
75
+ status(provider: HookProvider): Promise<ProviderHookInstallation>;
76
+ install(provider: HookProvider): Promise<ProviderHookInstallation>;
77
+ remove(provider: HookProvider): Promise<ProviderHookInstallation>;
78
+ /**
79
+ * After explicit operator confirmation: ask Codex (a private, short-lived
80
+ * `codex app-server`) to trust exactly NTerminal's installed hooks, as
81
+ * pressing Trust in `/hooks` would. Serialized with install/remove.
82
+ */
83
+ trustCodexHooks(): Promise<CodexHookTrustResult>;
84
+ /** Read-only `hooks/list` report, reused while the relevant files are unchanged. */
85
+ checkCodexTrust(): Promise<CodexHookTrustResult>;
86
+ private codexTrust;
87
+ private codexFingerprint;
88
+ refreshManagedHelper(): Promise<ManagedHelperRefresh>;
89
+ private helperReferenced;
90
+ private displayPaths;
91
+ private context;
92
+ private result;
93
+ /** Current definitions: guarded POSIX command, async where the response is unused. */
94
+ private entries;
95
+ private argv;
96
+ /** Every definition shape an earlier NTerminal version could have written for this node. */
97
+ private generations;
98
+ /** Exact argv arrays of NTerminal's own legacy notify commands recorded in this manifest. */
99
+ private ownedNotify;
100
+ /** Root `notify = [...]` whose decoded argv exactly equals an owned legacy command, however Codex reformatted it. */
101
+ private isOwnedNotify;
102
+ private referencesHelper;
103
+ private manifest;
104
+ /** Cheap, read-only policy checks. Unknown managed state is a warning, never an install blocker. */
105
+ private codexPolicy;
106
+ private claudePolicy;
107
+ private change;
108
+ }