@raidiant/notifai 0.3.0 → 0.4.0

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 (80) hide show
  1. package/README.md +20 -4
  2. package/dist/atomic-file.d.ts +4 -0
  3. package/dist/atomic-file.js +60 -10
  4. package/dist/atomic-file.js.map +1 -1
  5. package/dist/client.d.ts +25 -2
  6. package/dist/client.js +45 -1
  7. package/dist/client.js.map +1 -1
  8. package/dist/commands.d.ts +56 -7
  9. package/dist/commands.js +980 -318
  10. package/dist/commands.js.map +1 -1
  11. package/dist/config-schema.d.ts +1 -1
  12. package/dist/config-schema.js +46 -8
  13. package/dist/config-schema.js.map +1 -1
  14. package/dist/config.d.ts +38 -3
  15. package/dist/config.js +61 -14
  16. package/dist/config.js.map +1 -1
  17. package/dist/file-lock.d.ts +26 -0
  18. package/dist/file-lock.js +268 -0
  19. package/dist/file-lock.js.map +1 -0
  20. package/dist/harnesses.d.ts +28 -0
  21. package/dist/harnesses.js +40 -0
  22. package/dist/harnesses.js.map +1 -0
  23. package/dist/hook-adapter.d.ts +27 -0
  24. package/dist/hook-adapter.js +188 -0
  25. package/dist/hook-adapter.js.map +1 -0
  26. package/dist/hook-input.d.ts +10 -0
  27. package/dist/hook-input.js +64 -0
  28. package/dist/hook-input.js.map +1 -0
  29. package/dist/hooks.d.ts +66 -10
  30. package/dist/hooks.js +751 -276
  31. package/dist/hooks.js.map +1 -1
  32. package/dist/install-hooks.d.ts +40 -16
  33. package/dist/install-hooks.js +217 -73
  34. package/dist/install-hooks.js.map +1 -1
  35. package/dist/interactive.d.ts +1 -1
  36. package/dist/interactive.js +3 -3
  37. package/dist/interactive.js.map +1 -1
  38. package/dist/logging.d.ts +142 -0
  39. package/dist/logging.js +533 -0
  40. package/dist/logging.js.map +1 -0
  41. package/dist/main.js +97 -36
  42. package/dist/main.js.map +1 -1
  43. package/dist/opencode-plugin.d.ts +7 -7
  44. package/dist/opencode-plugin.js +12 -13
  45. package/dist/opencode-plugin.js.map +1 -1
  46. package/dist/ui/config-view.js +3 -0
  47. package/dist/ui/config-view.js.map +1 -1
  48. package/dist/ui/theme.d.ts +1 -1
  49. package/package.json +3 -2
  50. package/src/atomic-file.ts +86 -7
  51. package/src/client.test.ts +12 -0
  52. package/src/client.ts +72 -4
  53. package/src/commands.test.ts +891 -43
  54. package/src/commands.ts +1133 -332
  55. package/src/config-schema.ts +51 -9
  56. package/src/config.test.ts +6 -5
  57. package/src/config.ts +85 -16
  58. package/src/file-lock-process-worker.test.ts +64 -0
  59. package/src/file-lock.test.ts +279 -0
  60. package/src/file-lock.ts +318 -0
  61. package/src/harnesses.test.ts +48 -0
  62. package/src/harnesses.ts +59 -0
  63. package/src/hook-adapter.test.ts +187 -0
  64. package/src/hook-adapter.ts +212 -0
  65. package/src/hook-input.test.ts +39 -0
  66. package/src/hook-input.ts +72 -0
  67. package/src/hooks.test.ts +1318 -240
  68. package/src/hooks.ts +1046 -296
  69. package/src/install-hooks.test.ts +90 -41
  70. package/src/install-hooks.ts +259 -74
  71. package/src/interactive.ts +3 -3
  72. package/src/logging-process-worker.test.ts +39 -0
  73. package/src/logging.test.ts +453 -0
  74. package/src/logging.ts +639 -0
  75. package/src/main.ts +113 -32
  76. package/src/opencode-plugin.ts +15 -16
  77. package/src/session-end-log-lock-worker.test.ts +35 -0
  78. package/src/skill.test.ts +11 -0
  79. package/src/ui/config-view.test.ts +4 -2
  80. package/src/ui/config-view.ts +3 -0
package/README.md CHANGED
@@ -42,10 +42,10 @@ The agent registers the question, ends its turn, and your answer comes
42
42
  back on its next turn. On iPhone the choices are buttons on the banner
43
43
  itself.
44
44
 
45
- By default a question stays in your terminal while you are actually at
46
- the keyboard, and only reaches your devices once you have stepped away —
47
- a push you are sitting in front of is just noise. `require_idle = false`
48
- turns that off if you would rather always be notified.
45
+ By default a question reaches your devices when the agent turn ends, even if
46
+ you are at the keyboard (`require_idle = false`, `ask_grace_seconds = 0`). Turn
47
+ on `require_idle` if you want active keyboard or mouse use to keep questions in
48
+ the terminal, or set a positive grace period for a terminal-only answer window.
49
49
 
50
50
  For a harness that cannot resume an idle agent turn, `notifai send
51
51
  --reply` blocks and waits for the answer instead.
@@ -61,6 +61,22 @@ is what lets an agent's question reach your phone without the agent
61
61
  having to cooperate — no question detection, no state in a context window
62
62
  that compaction will eat.
63
63
 
64
+ Every harness definition calls one stable user-level adapter at
65
+ `~/.notifai/bin/hook-adapter`. Node, package manager, CLI version, checkout,
66
+ XDG directories, and notification preferences are resolved behind it, so
67
+ upgrades and configuration changes do not rewrite trusted hook definitions.
68
+ The first migration may require one Codex `/hooks` approval; later repairs keep
69
+ the same identity. Codex's Stop definition omits its timeout so the host default
70
+ remains authoritative; prompt-submit and session-end retain fixed short limits.
71
+
72
+ `notifai doctor` compares Codex trust on a best-effort basis against Codex's
73
+ current persisted representation. Notifai never writes that trust store;
74
+ Codex's `/hooks` review is authoritative if the diagnostic and UI disagree.
75
+
76
+ Uninstall removes only the selected harness definition. It intentionally keeps
77
+ the shared adapter because definitions for other harnesses or projects may
78
+ still invoke it and cannot all be discovered from one working directory.
79
+
64
80
  ## Checking it works
65
81
 
66
82
  ```sh
@@ -1,6 +1,10 @@
1
1
  export interface AtomicWriteOptions {
2
2
  /** Mode for a newly created file. Existing regular files keep their mode. */
3
3
  mode?: number;
4
+ /** False when a managed executable must keep the requested mode on repair. */
5
+ preserveMode?: boolean;
6
+ /** Reject a target owned by another user instead of replacing it. */
7
+ requireCurrentUserOwner?: boolean;
4
8
  }
5
9
  /**
6
10
  * Replace one regular file without exposing a truncated intermediate state.
@@ -1,5 +1,5 @@
1
1
  import { randomBytes } from 'node:crypto';
2
- import { closeSync, fsyncSync, lstatSync, mkdirSync, openSync, renameSync, unlinkSync, writeFileSync, } from 'node:fs';
2
+ import { chmodSync, closeSync, fsyncSync, lstatSync, mkdirSync, openSync, renameSync, unlinkSync, writeFileSync, } from 'node:fs';
3
3
  import path from 'node:path';
4
4
  /**
5
5
  * Replace one regular file without exposing a truncated intermediate state.
@@ -9,16 +9,21 @@ import path from 'node:path';
9
9
  * symlink target is refused instead of silently writing through it.
10
10
  */
11
11
  export function atomicWriteFileSync(file, contents, options = {}) {
12
- mkdirSync(path.dirname(file), { recursive: true });
13
- const mode = targetMode(file, options.mode ?? 0o600);
14
- const temp = path.join(path.dirname(file), `.${path.basename(file)}.notifai-${process.pid}-${randomBytes(6).toString('hex')}.tmp`);
12
+ const directory = path.dirname(file);
13
+ mkdirSync(directory, { recursive: true });
14
+ const parent = safeDirectory(directory, options.requireCurrentUserOwner ?? false);
15
+ const target = targetMetadata(file, options.mode ?? 0o600, options.preserveMode ?? true, options.requireCurrentUserOwner ?? false);
16
+ const temp = path.join(directory, `.${path.basename(file)}.notifai-${process.pid}-${randomBytes(6).toString('hex')}.tmp`);
15
17
  let handle;
16
18
  try {
17
- handle = openSync(temp, 'wx', mode);
19
+ handle = openSync(temp, 'wx', target.mode);
18
20
  writeFileSync(handle, contents);
19
21
  fsyncSync(handle);
20
22
  closeSync(handle);
21
23
  handle = undefined;
24
+ chmodSync(temp, target.mode);
25
+ assertSameDirectory(directory, parent);
26
+ assertUnchangedTarget(file, target);
22
27
  renameSync(temp, file);
23
28
  }
24
29
  catch (err) {
@@ -33,15 +38,15 @@ export function atomicWriteFileSync(file, contents, options = {}) {
33
38
  throw err;
34
39
  }
35
40
  }
36
- /** Mode to preserve, or the mode for a new file. Refuses a non-regular target. */
37
- function targetMode(file, fallback) {
41
+ function targetMetadata(file, fallback, preserveMode, requireCurrentUserOwner) {
38
42
  let stat;
39
43
  try {
40
44
  stat = lstatSync(file);
41
45
  }
42
46
  catch (err) {
43
- if (err.code === 'ENOENT')
44
- return fallback;
47
+ if (err.code === 'ENOENT') {
48
+ return { mode: fallback, identity: null };
49
+ }
45
50
  throw err;
46
51
  }
47
52
  if (stat.isSymbolicLink()) {
@@ -50,6 +55,51 @@ function targetMode(file, fallback) {
50
55
  }
51
56
  if (!stat.isFile())
52
57
  throw new Error(`${file} is not a regular file; refusing to replace it.`);
53
- return stat.mode & 0o777;
58
+ if (requireCurrentUserOwner)
59
+ assertCurrentUserOwns(file, stat.uid);
60
+ return {
61
+ mode: preserveMode ? stat.mode & 0o777 : fallback,
62
+ identity: { dev: stat.dev, ino: stat.ino },
63
+ };
64
+ }
65
+ function assertUnchangedTarget(file, target) {
66
+ let current;
67
+ try {
68
+ current = lstatSync(file);
69
+ }
70
+ catch (err) {
71
+ if (err.code === 'ENOENT' && target.identity === null)
72
+ return;
73
+ throw new Error(`${file} changed during the atomic write; refusing to replace it.`);
74
+ }
75
+ if (target.identity === null ||
76
+ current.dev !== target.identity.dev ||
77
+ current.ino !== target.identity.ino) {
78
+ throw new Error(`${file} changed during the atomic write; refusing to replace it.`);
79
+ }
80
+ }
81
+ function safeDirectory(directory, requireCurrentUserOwner) {
82
+ const stat = lstatSync(directory);
83
+ if (stat.isSymbolicLink() || !stat.isDirectory()) {
84
+ throw new Error(`${directory} is not a regular directory; refusing to write inside it.`);
85
+ }
86
+ if (requireCurrentUserOwner)
87
+ assertCurrentUserOwns(directory, stat.uid);
88
+ return { dev: stat.dev, ino: stat.ino };
89
+ }
90
+ function assertSameDirectory(directory, expected) {
91
+ const current = lstatSync(directory);
92
+ if (current.isSymbolicLink() ||
93
+ !current.isDirectory() ||
94
+ current.dev !== expected.dev ||
95
+ current.ino !== expected.ino) {
96
+ throw new Error(`${directory} changed during the write; refusing to publish the file.`);
97
+ }
98
+ }
99
+ function assertCurrentUserOwns(file, owner) {
100
+ const uid = typeof process.getuid === 'function' ? process.getuid() : undefined;
101
+ if (uid !== undefined && owner !== uid) {
102
+ throw new Error(`${file} is owned by uid ${owner}, not the current user; refusing to replace it.`);
103
+ }
54
104
  }
55
105
  //# sourceMappingURL=atomic-file.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"atomic-file.js","sourceRoot":"","sources":["../src/atomic-file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EACL,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,EACT,QAAQ,EACR,UAAU,EACV,UAAU,EACV,aAAa,GACd,MAAM,SAAS,CAAA;AAChB,OAAO,IAAI,MAAM,WAAW,CAAA;AAO5B;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAAY,EACZ,QAAgB,EAChB,UAA8B,EAAE;IAEhC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAClD,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,KAAK,CAAC,CAAA;IACpD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CACpB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAClB,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAY,OAAO,CAAC,GAAG,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CACvF,CAAA;IACD,IAAI,MAA0B,CAAA;IAC9B,IAAI,CAAC;QACH,MAAM,GAAG,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;QACnC,aAAa,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;QAC/B,SAAS,CAAC,MAAM,CAAC,CAAA;QACjB,SAAS,CAAC,MAAM,CAAC,CAAA;QACjB,MAAM,GAAG,SAAS,CAAA;QAClB,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IACxB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,MAAM,KAAK,SAAS;YAAE,SAAS,CAAC,MAAM,CAAC,CAAA;QAC3C,IAAI,CAAC;YACH,UAAU,CAAC,IAAI,CAAC,CAAA;QAClB,CAAC;QAAC,MAAM,CAAC;YACP,mEAAmE;QACrE,CAAC;QACD,MAAM,GAAG,CAAA;IACX,CAAC;AACH,CAAC;AAED,kFAAkF;AAClF,SAAS,UAAU,CAAC,IAAY,EAAE,QAAgB;IAChD,IAAI,IAAI,CAAA;IACR,IAAI,CAAC;QACH,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAA;IACxB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,QAAQ,CAAA;QACrE,MAAM,GAAG,CAAA;IACX,CAAC;IACD,IAAI,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,+EAA+E;YACpF,qCAAqC,CACxC,CAAA;IACH,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,iDAAiD,CAAC,CAAA;IAC7F,OAAO,IAAI,CAAC,IAAI,GAAG,KAAK,CAAA;AAC1B,CAAC"}
1
+ {"version":3,"file":"atomic-file.js","sourceRoot":"","sources":["../src/atomic-file.ts"],"names":[],"mappings":"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"}
package/dist/client.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { AccountAccessResponse, BeginPairingResponse, CapabilityDocument, CreateMediaUploadRequestT, CreateMediaUploadResponse, EvidenceSnapshot, ListDevicesResponse, ListRepliesResponse, Platform, PollPairingResponse, SubmissionReceipt, SubmitNotificationRequestT } from '@raidiant/notifai-protocol';
2
+ import type { Logger } from './logging.js';
2
3
  export declare class ApiCallError extends Error {
3
4
  status: number;
4
5
  code: string;
@@ -8,6 +9,13 @@ export declare class ApiCallError extends Error {
8
9
  }
9
10
  export declare class NetworkError extends Error {
10
11
  }
12
+ /**
13
+ * Whether a replies-poll failure is temporary while the durable request stays
14
+ * live. Transport faults, throttling, request timeouts, and server failures
15
+ * can recover on a later poll; client errors cannot be repaired by retrying
16
+ * the same authenticated request.
17
+ */
18
+ export declare function isRetryableReplyPollError(err: unknown): boolean;
11
19
  export interface ApiClient {
12
20
  beginPairing(body: {
13
21
  machine_name: string;
@@ -24,8 +32,8 @@ export interface ApiClient {
24
32
  waitSeconds: number;
25
33
  afterSeq: number;
26
34
  }): Promise<ListRepliesResponse>;
27
- /** Retire a question so a late device answer is rejected rather than lost. */
28
- closeReplies(requestId: string): Promise<void>;
35
+ /** Retire a question and return the replies committed before the close fence. */
36
+ closeReplies(requestId: string): Promise<ListRepliesResponse>;
29
37
  createMediaUpload(body: CreateMediaUploadRequestT): Promise<CreateMediaUploadResponse>;
30
38
  uploadMedia(grant: CreateMediaUploadResponse, bytes: Uint8Array): Promise<void>;
31
39
  health(): Promise<boolean>;
@@ -37,5 +45,20 @@ export interface ClientOptions {
37
45
  * whole network path stays inside the budget the harness allows them.
38
46
  */
39
47
  timeoutMs?: number;
48
+ /**
49
+ * Absolute wall-clock ceiling for the caller's whole operation. This is
50
+ * intentionally separate from `timeoutMs`: a late long poll must not get a
51
+ * fresh per-request allowance after its owning harness hook is nearly done.
52
+ */
53
+ deadlineAt?: number;
54
+ /** Test seam for `deadlineAt`; production uses the wall clock. */
55
+ now?: () => number;
56
+ /**
57
+ * Records each call locally. Worth having because the two failures that cost
58
+ * the most time — a server older than this CLI, and a network path that only
59
+ * fails inside a hook — both look identical from outside and are told apart
60
+ * instantly by a status code and a duration.
61
+ */
62
+ logger?: Pick<Logger, 'debug' | 'error'>;
40
63
  }
41
64
  export declare function createClient(baseUrl: string, bearer: string | null, options?: ClientOptions): ApiClient;
package/dist/client.js CHANGED
@@ -13,6 +13,19 @@ export class ApiCallError extends Error {
13
13
  }
14
14
  export class NetworkError extends Error {
15
15
  }
16
+ /**
17
+ * Whether a replies-poll failure is temporary while the durable request stays
18
+ * live. Transport faults, throttling, request timeouts, and server failures
19
+ * can recover on a later poll; client errors cannot be repaired by retrying
20
+ * the same authenticated request.
21
+ */
22
+ export function isRetryableReplyPollError(err) {
23
+ if (err instanceof NetworkError)
24
+ return true;
25
+ if (!(err instanceof ApiCallError))
26
+ return false;
27
+ return err.status >= 500 || err.status === 429 || err.status === 408;
28
+ }
16
29
  /**
17
30
  * A deadline that fired reads as an abort, which says nothing useful on its
18
31
  * own. Naming the timeout is what lets someone tell a hung server apart from a
@@ -30,14 +43,45 @@ const DEFAULT_TIMEOUT_MS = 20_000;
30
43
  export function createClient(baseUrl, bearer, options = {}) {
31
44
  const root = baseUrl.replace(/\/$/, '');
32
45
  const budgetMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
46
+ const now = options.now ?? Date.now;
47
+ const logger = options.logger;
33
48
  async function call(method, apiPath, body,
34
49
  /** Seconds the server was asked to hold the connection open. */
35
50
  serverWaitSeconds = 0) {
51
+ const startedAt = Date.now();
52
+ try {
53
+ const result = await callOnce(method, apiPath, body, serverWaitSeconds);
54
+ logger?.debug('http.call', {
55
+ method,
56
+ path: apiPath,
57
+ ok: true,
58
+ duration_ms: Date.now() - startedAt,
59
+ });
60
+ return result;
61
+ }
62
+ catch (err) {
63
+ logger?.error('http.call', {
64
+ method,
65
+ path: apiPath,
66
+ ok: false,
67
+ duration_ms: Date.now() - startedAt,
68
+ ...(err instanceof ApiCallError
69
+ ? { status: err.status, code: err.code, message: err.message, details: err.details }
70
+ : { message: String(err) }),
71
+ });
72
+ throw err;
73
+ }
74
+ }
75
+ async function callOnce(method, apiPath, body, serverWaitSeconds = 0) {
36
76
  // Without this, a server that accepts the connection and then never answers
37
77
  // hangs until the harness kills the whole hook — and the reply-poll deadline
38
78
  // cannot interrupt an individual fetch. The signal covers the
39
79
  // body read too, not just the response headers.
40
- const limitMs = budgetMs + serverWaitSeconds * 1000;
80
+ const requestBudgetMs = budgetMs + serverWaitSeconds * 1000;
81
+ const remainingMs = options.deadlineAt === undefined
82
+ ? requestBudgetMs
83
+ : Math.max(1, options.deadlineAt - now());
84
+ const limitMs = Math.max(1, Math.min(requestBudgetMs, remainingMs));
41
85
  const signal = AbortSignal.timeout(limitMs);
42
86
  let response;
43
87
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAgBA,MAAM,OAAO,YAAa,SAAQ,KAAK;IAE5B;IACA;IAEA;IACA;IALT,YACS,MAAc,EACd,IAAY,EACnB,OAAe,EACR,aAA4B,IAAI,EAChC,UAAmB,IAAI;QAE9B,KAAK,CAAC,OAAO,CAAC,CAAA;QANP,WAAM,GAAN,MAAM,CAAQ;QACd,SAAI,GAAJ,IAAI,CAAQ;QAEZ,eAAU,GAAV,UAAU,CAAsB;QAChC,YAAO,GAAP,OAAO,CAAgB;IAGhC,CAAC;CACF;AAED,MAAM,OAAO,YAAa,SAAQ,KAAK;CAAG;AAE1C;;;;GAIG;AACH,SAAS,cAAc,CAAC,GAAY,EAAE,IAAY,EAAE,OAAe;IACjE,MAAM,IAAI,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAA;IACjD,IAAI,IAAI,KAAK,cAAc,IAAI,IAAI,KAAK,YAAY,EAAE,CAAC;QACrD,OAAO,IAAI,YAAY,CAAC,GAAG,IAAI,2BAA2B,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAA;IAC1F,CAAC;IACD,OAAO,IAAI,YAAY,CAAC,mBAAmB,IAAI,KAAK,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;AACpE,CAAC;AAkCD,iFAAiF;AACjF,MAAM,kBAAkB,GAAG,MAAM,CAAA;AAEjC,MAAM,UAAU,YAAY,CAC1B,OAAe,EACf,MAAqB,EACrB,UAAyB,EAAE;IAE3B,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;IACvC,MAAM,QAAQ,GAAG,OAAO,CAAC,SAAS,IAAI,kBAAkB,CAAA;IAExD,KAAK,UAAU,IAAI,CACjB,MAAc,EACd,OAAe,EACf,IAAc;IACd,gEAAgE;IAChE,iBAAiB,GAAG,CAAC;QAErB,4EAA4E;QAC5E,6EAA6E;QAC7E,8DAA8D;QAC9D,gDAAgD;QAChD,MAAM,OAAO,GAAG,QAAQ,GAAG,iBAAiB,GAAG,IAAI,CAAA;QACnD,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;QAC3C,IAAI,QAAkB,CAAA;QACtB,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,GAAG,OAAO,EAAE,EAAE;gBAC1C,MAAM;gBACN,OAAO,EAAE;oBACP,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACrE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBAC7C;gBACD,MAAM;gBACN,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC9D,CAAC,CAAA;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,cAAc,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,CAAA;QAC1C,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,IAAI,MAAM,GAAwB,IAAI,CAAA;YACtC,IAAI,CAAC;gBACH,MAAM,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAiB,CAAA;YAClD,CAAC;YAAC,MAAM,CAAC;gBACP,gBAAgB;YAClB,CAAC;YACD,MAAM,IAAI,YAAY,CACpB,QAAQ,CAAC,MAAM,EACf,MAAM,EAAE,KAAK,CAAC,IAAI,IAAI,gBAAgB,EACtC,MAAM,EAAE,KAAK,CAAC,OAAO,IAAI,8BAA8B,QAAQ,CAAC,MAAM,EAAE,EACxE,MAAM,EAAE,KAAK,CAAC,WAAW,IAAI,IAAI,EACjC,MAAM,EAAE,KAAK,CAAC,OAAO,IAAI,IAAI,CAC9B,CAAA;QACH,CAAC;QACD,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG;YAAE,OAAO,SAAc,CAAA;QAClD,IAAI,CAAC;YACH,OAAO,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAM,CAAA;QACrC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,uEAAuE;YACvE,wEAAwE;YACxE,MAAM,cAAc,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,CAAA;QAC1C,CAAC;IACH,CAAC;IAED,OAAO;QACL,YAAY,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,kBAAkB,EAAE,IAAI,CAAC;QAC9D,WAAW,EAAE,CAAC,SAAS,EAAE,YAAY,EAAE,EAAE,CACvC,IAAI,CAAC,MAAM,EAAE,oBAAoB,SAAS,OAAO,EAAE,EAAE,aAAa,EAAE,YAAY,EAAE,CAAC;QACrF,YAAY,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,wBAAwB,CAAC;QACzD,WAAW,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,iBAAiB,CAAC;QACjD,YAAY,EAAE,CAAC,QAAQ,GAAG,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,wBAAwB,QAAQ,EAAE,CAAC;QACnF,MAAM,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,CAC5B,IAAI,CAAC,MAAM,EAAE,sCAAsC,WAAW,EAAE,EAAE,IAAI,EAAE,WAAW,CAAC;QACtF,QAAQ,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,yBAAyB,SAAS,EAAE,CAAC;QAC1E,OAAO,EAAE,CAAC,SAAS,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,EAAE,CAChD,IAAI,CACF,KAAK,EACL,yBAAyB,kBAAkB,CAAC,SAAS,CAAC,yBAAyB,WAAW,cAAc,QAAQ,EAAE,EAClH,SAAS,EACT,WAAW,CACZ;QACH,YAAY,EAAE,CAAC,SAAS,EAAE,EAAE,CAC1B,IAAI,CAAC,MAAM,EAAE,yBAAyB,kBAAkB,CAAC,SAAS,CAAC,gBAAgB,CAAC;QACtF,iBAAiB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,CAAC;QAChE,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;YAClC,IAAI,QAAkB,CAAA;YACtB,IAAI,CAAC;gBACH,QAAQ,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,UAAU,EAAE;oBACvC,MAAM,EAAE,KAAK;oBACb,OAAO,EAAE,KAAK,CAAC,cAAc;oBAC7B,IAAI,EAAE,KAAK;oBACX,qEAAqE;oBACrE,+CAA+C;oBAC/C,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;iBACxD,CAAC,CAAA;YACJ,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,IAAI,YAAY,CAAC,kBAAkB,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;YACzD,CAAC;YACD,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,IAAI,YAAY,CAAC,+BAA+B,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;YAC1E,CAAC;QACH,CAAC;QACD,MAAM,EAAE,KAAK,IAAI,EAAE;YACjB,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,CAAA;gBAC7B,OAAO,IAAI,CAAA;YACb,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,KAAK,CAAA;YACd,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -4,6 +4,7 @@ import type { CredentialStore } from './credentials.js';
4
4
  import { type Readiness } from './readiness.js';
5
5
  import type { Tone } from './ui/theme.js';
6
6
  import { type HookHarness } from './hooks.js';
7
+ import { type Logger } from './logging.js';
7
8
  import { type SendFlags } from './send.js';
8
9
  import type { NativeSkills, SkillScope } from './native-skills.js';
9
10
  export interface CommandIo {
@@ -47,6 +48,8 @@ export interface CommandDeps {
47
48
  store: CredentialStore;
48
49
  env: NodeJS.ProcessEnv;
49
50
  cwd: string;
51
+ /** Test seam; production fixes the hook adapter under os.homedir(). */
52
+ hookAdapterHome?: string;
50
53
  /** Test seam; production uses fetch against base_url. */
51
54
  clientFactory?: (baseUrl: string, bearer: string | null, options?: ClientOptions) => ApiClient;
52
55
  /** Test seam for bounded polling without wall-clock sleeps. */
@@ -57,7 +60,14 @@ export interface CommandDeps {
57
60
  idleSeconds?: () => number | null;
58
61
  /** Test seam and production adapter for the external native skills installer. */
59
62
  nativeSkills?: NativeSkills;
63
+ /**
64
+ * The local record of what this invocation did. Optional so a test fake need
65
+ * not carry one; `log(deps)` supplies a logger that records nothing.
66
+ */
67
+ logger?: Logger;
60
68
  }
69
+ /** The invocation's logger, or one that discards everything. */
70
+ export declare function log(deps: CommandDeps): Logger;
61
71
  export declare const EXIT: {
62
72
  readonly ok: 0;
63
73
  readonly failed: 1;
@@ -147,6 +157,8 @@ export declare function statusCommand(deps: CommandDeps, requestId: string, flag
147
157
  }): Promise<number>;
148
158
  export declare const HOOK_EVENTS: readonly ["user-prompt-submit", "stop", "session-end"];
149
159
  export type HookEvent = (typeof HOOK_EVENTS)[number];
160
+ /** SessionEnd cleanup must precede every diagnostic that can wait on a file lock. */
161
+ export declare function hookDefersDiagnosticsUntilAfterCleanup(event: unknown): event is 'session-end';
150
162
  /**
151
163
  * Runs one harness hook. Contract with every harness: hook JSON arrives on
152
164
  * stdin, the decision (if any) goes to stdout, diagnostics go to stderr, and
@@ -197,9 +209,9 @@ export declare function buildQuestions(flags: AskFlags, question: string | undef
197
209
  };
198
210
  /**
199
211
  * Registers a question for turn-end routing under the user's presence config.
200
- * Returns immediately so the agent can ask in prose and end its turn. With the
201
- * default presence gate, recent keyboard or mouse activity keeps it local;
202
- * `require_idle = false` intentionally permits a push while the user is active.
212
+ * Returns immediately so the agent can ask in prose and end its turn. Presence
213
+ * does not block the shipped default; `require_idle = true` intentionally keeps
214
+ * the question local while the user is active.
203
215
  */
204
216
  export declare function askCommand(deps: CommandDeps, question: string | undefined, flags: AskFlags): number;
205
217
  /** Retire a question so a late answer is rejected rather than silently lost. */
@@ -221,9 +233,10 @@ export declare function configShowCommand(deps: CommandDeps, flags: {
221
233
  /**
222
234
  * One setting, explained in full.
223
235
  *
224
- * The gap this closes: `config show` prints `require_idle = true` and there was
225
- * nowhere to go from there. Every key already had a careful explanation — in a
226
- * TypeScript comment, read by everyone except the person who needed it.
236
+ * The gap this closes: `config show` prints a value and provenance but that is
237
+ * not enough to explain the setting's consequences. Every key already had a
238
+ * careful explanation — in a TypeScript comment, read by everyone except the
239
+ * person who needed it.
227
240
  */
228
241
  export declare function configExplainCommand(deps: CommandDeps, key: string, flags?: {
229
242
  json?: boolean;
@@ -234,12 +247,48 @@ export declare function configSetCommand(deps: CommandDeps, key: string, rawValu
234
247
  session?: string;
235
248
  yes?: boolean;
236
249
  }): Promise<number>;
250
+ type ConfigMutationFlags = {
251
+ project?: boolean;
252
+ local?: boolean;
253
+ session?: string;
254
+ yes?: boolean;
255
+ };
256
+ export declare function configUnsetCommand(deps: CommandDeps, key: string, flags: ConfigMutationFlags): Promise<number>;
257
+ export interface LogsFlags {
258
+ json?: boolean;
259
+ limit?: number;
260
+ all?: boolean;
261
+ since?: string;
262
+ level?: string;
263
+ event?: string[];
264
+ run?: string;
265
+ request?: string;
266
+ session?: string;
267
+ project?: string;
268
+ allProjects?: boolean;
269
+ grep?: string;
270
+ path?: boolean;
271
+ clear?: boolean;
272
+ }
273
+ /** `10m`, `2h`, `1d`, or an ISO 8601 instant. */
274
+ export declare function parseSince(raw: string, now: number): number | null;
275
+ /**
276
+ * What this machine recorded about itself.
277
+ *
278
+ * The retrieval half of the local log, and the half that decides whether the
279
+ * log is worth having. Three things make it usable by an agent rather than
280
+ * merely available to one: the default is bounded and scoped to this project,
281
+ * the filters match the questions actually asked ("that run", "that
282
+ * notification", "just the failures"), and `--json` gives the machine the
283
+ * records untouched on stdout while every word of explanation goes to stderr.
284
+ */
285
+ export declare function logsCommand(deps: CommandDeps, flags: LogsFlags): number;
237
286
  /**
238
287
  * Where `npx skills add` fetches the optional agent skill from. In skills CLI
239
288
  * 1.5.x, `owner/repo@name` selects a skill; a Git ref belongs after `#`.
240
289
  * Keep this immutable and public because the command is printed to users.
241
290
  */
242
- export declare const SKILLS_SOURCE = "Raidiant-io/notifai#v0.3.0";
291
+ export declare const SKILLS_SOURCE = "Raidiant-io/notifai#v0.4.0";
243
292
  /** Derive a contract-valid project slug from a directory name. */
244
293
  export declare function projectSlugFrom(name: string): string;
245
294
  export interface InitFlags {