@raidiant/notifai 11.3.2 → 11.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 (117) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/agent-update-notice.js +3 -3
  3. package/dist/agent-update-notice.js.map +1 -1
  4. package/dist/claude-wake.d.ts +11 -3
  5. package/dist/claude-wake.js +74 -67
  6. package/dist/claude-wake.js.map +1 -1
  7. package/dist/cli-bin.js +2 -2
  8. package/dist/cli-bin.js.map +1 -1
  9. package/dist/cli-contract.d.ts +6 -2
  10. package/dist/cli-contract.js +11 -3
  11. package/dist/cli-contract.js.map +1 -1
  12. package/dist/cli-release.d.ts +31 -4
  13. package/dist/cli-release.js +45 -15
  14. package/dist/cli-release.js.map +1 -1
  15. package/dist/client.d.ts +23 -2
  16. package/dist/client.js +11 -5
  17. package/dist/client.js.map +1 -1
  18. package/dist/codex-wake.d.ts +12 -2
  19. package/dist/codex-wake.js +50 -13
  20. package/dist/codex-wake.js.map +1 -1
  21. package/dist/commands-acknowledge.d.ts +4 -2
  22. package/dist/commands-acknowledge.js +35 -4
  23. package/dist/commands-acknowledge.js.map +1 -1
  24. package/dist/commands-ask.js +2 -2
  25. package/dist/commands-ask.js.map +1 -1
  26. package/dist/commands-close.js +16 -4
  27. package/dist/commands-close.js.map +1 -1
  28. package/dist/commands-core.d.ts +10 -1
  29. package/dist/commands-core.js +4 -3
  30. package/dist/commands-core.js.map +1 -1
  31. package/dist/commands-doctor.js +49 -4
  32. package/dist/commands-doctor.js.map +1 -1
  33. package/dist/commands-hook-attend.d.ts +61 -0
  34. package/dist/commands-hook-attend.js +475 -0
  35. package/dist/commands-hook-attend.js.map +1 -0
  36. package/dist/commands-hook-diagnostics.js +2 -2
  37. package/dist/commands-hook-diagnostics.js.map +1 -1
  38. package/dist/commands-hook-install.js +1 -1
  39. package/dist/commands-hook-install.js.map +1 -1
  40. package/dist/commands-hook-run.js +59 -2
  41. package/dist/commands-hook-run.js.map +1 -1
  42. package/dist/commands-send.js +6 -1
  43. package/dist/commands-send.js.map +1 -1
  44. package/dist/commands-update-check.js +9 -6
  45. package/dist/commands-update-check.js.map +1 -1
  46. package/dist/commands-update.d.ts +2 -1
  47. package/dist/commands-update.js +74 -11
  48. package/dist/commands-update.js.map +1 -1
  49. package/dist/guidance-content.js +11 -37
  50. package/dist/guidance-content.js.map +1 -1
  51. package/dist/hook-acknowledgements.d.ts +25 -5
  52. package/dist/hook-acknowledgements.js +102 -45
  53. package/dist/hook-acknowledgements.js.map +1 -1
  54. package/dist/hook-events.d.ts +26 -1
  55. package/dist/hook-events.js +38 -2
  56. package/dist/hook-events.js.map +1 -1
  57. package/dist/hook-lifecycle.d.ts +6 -0
  58. package/dist/hook-lifecycle.js +309 -31
  59. package/dist/hook-lifecycle.js.map +1 -1
  60. package/dist/hook-question-lock.d.ts +18 -0
  61. package/dist/hook-question-lock.js +80 -57
  62. package/dist/hook-question-lock.js.map +1 -1
  63. package/dist/hook-question-retirement.d.ts +10 -2
  64. package/dist/hook-question-retirement.js +21 -4
  65. package/dist/hook-question-retirement.js.map +1 -1
  66. package/dist/hook-session-state.d.ts +90 -1
  67. package/dist/hook-session-state.js +201 -9
  68. package/dist/hook-session-state.js.map +1 -1
  69. package/dist/hook-types.d.ts +50 -2
  70. package/dist/injection-render.d.ts +36 -0
  71. package/dist/injection-render.js +61 -0
  72. package/dist/injection-render.js.map +1 -0
  73. package/dist/install-hooks.d.ts +14 -0
  74. package/dist/install-hooks.js +47 -2
  75. package/dist/install-hooks.js.map +1 -1
  76. package/dist/logging.d.ts +1 -1
  77. package/dist/logging.js +12 -0
  78. package/dist/logging.js.map +1 -1
  79. package/dist/process-identity.d.ts +26 -0
  80. package/dist/process-identity.js +82 -0
  81. package/dist/process-identity.js.map +1 -0
  82. package/dist/program.js +17 -6
  83. package/dist/program.js.map +1 -1
  84. package/dist/session-attendant-probe.d.ts +74 -0
  85. package/dist/session-attendant-probe.js +131 -0
  86. package/dist/session-attendant-probe.js.map +1 -0
  87. package/dist/session-attendant-state.d.ts +53 -0
  88. package/dist/session-attendant-state.js +175 -0
  89. package/dist/session-attendant-state.js.map +1 -0
  90. package/dist/session-attendant.d.ts +135 -0
  91. package/dist/session-attendant.js +493 -0
  92. package/dist/session-attendant.js.map +1 -0
  93. package/dist/session-delivery.d.ts +214 -0
  94. package/dist/session-delivery.js +560 -0
  95. package/dist/session-delivery.js.map +1 -0
  96. package/dist/session-handoff.d.ts +98 -0
  97. package/dist/session-handoff.js +118 -0
  98. package/dist/session-handoff.js.map +1 -0
  99. package/dist/session-message-handoff.d.ts +35 -0
  100. package/dist/session-message-handoff.js +99 -0
  101. package/dist/session-message-handoff.js.map +1 -0
  102. package/dist/skill-source/manifest.json +20 -4
  103. package/dist/skill-source/notifai/SKILL.md +28 -67
  104. package/dist/skill-source/notifai/references/diagnostics.md +31 -0
  105. package/dist/skill-source/notifai/references/harness-setup.md +3 -0
  106. package/dist/skill-source/notifai/references/notes-and-edits.md +38 -0
  107. package/dist/skill-source/notifai/references/send-details.md +14 -0
  108. package/dist/skill-source/notifai/references/writing-examples.md +56 -0
  109. package/dist/update-handoff.js +3 -3
  110. package/dist/update-handoff.js.map +1 -1
  111. package/dist/version.d.ts +18 -0
  112. package/dist/version.js +82 -1
  113. package/dist/version.js.map +1 -1
  114. package/dist/wake-support.d.ts +33 -1
  115. package/dist/wake-support.js +65 -8
  116. package/dist/wake-support.js.map +1 -1
  117. package/package.json +2 -2
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Hand one message into a running Agent Session, in place.
3
+ *
4
+ * The one harness write every hand-off shares: the answer waiter's wake routes
5
+ * and the Session Attendant's Session Messages both end here. It never cold
6
+ * resumes: a stopped session is reported as stopped, and only the answer route
7
+ * decides what to do about that (Session Messages never resume a session).
8
+ *
9
+ * `begin` is the commit point. It is called immediately before the
10
+ * irreversible write, after every check that can refuse without writing; when
11
+ * it returns false nothing is handed over. A `failed` result after `begin`
12
+ * means the write may or may not have reached the harness.
13
+ */
14
+ import { type ClaudeSessionDescriptor, type ClaudeWakeAdapters } from './claude-wake.js';
15
+ import { type CodexWakeAdapters } from './codex-wake.js';
16
+ import { type WriteGuard } from './wake-support.js';
17
+ export type SessionWriteResult = {
18
+ status: 'written';
19
+ route: 'inbox-socket' | 'session-queue';
20
+ /** Claude only: whether the session was idle or busy when the message was posted. */
21
+ sessionState?: 'live-idle' | 'live-busy';
22
+ }
23
+ /** Nothing was written: the exact session could not be proven reachable in place. */
24
+ | {
25
+ status: 'unavailable';
26
+ reason: string;
27
+ }
28
+ /** Claude only: no process hosts the session any more; nothing was written. */
29
+ | {
30
+ status: 'stopped';
31
+ }
32
+ /** `begin` refused (SessionEnd won, a claim lapsed, the lease cannot cover the write). */
33
+ | {
34
+ status: 'cancelled';
35
+ }
36
+ /** `begin` succeeded, then the write guard stopped the write before any byte left. */
37
+ | {
38
+ status: 'aborted';
39
+ reason: string;
40
+ }
41
+ /** The write started and failed; it may or may not have reached the harness. */
42
+ | {
43
+ status: 'failed';
44
+ reason: string;
45
+ error: unknown;
46
+ };
47
+ /**
48
+ * The Claude session descriptor of the process that will post, when it names
49
+ * this exact session. Read once, when the writer starts: a descriptor that
50
+ * appears later is not evidence that this process is that session's child.
51
+ */
52
+ export declare function claudeSourceDescriptor(sessionId: string, sourcePid: number, adapters: Pick<ClaudeWakeAdapters, 'readDescriptor'>): ClaudeSessionDescriptor | null;
53
+ /**
54
+ * Claude Code's own-child inbox route. Claude delivers an inbox-socket line
55
+ * only from a verified descendant of the receiving process, so the writer must
56
+ * be that exact session's child: `sourcePid` is its harness process, observed
57
+ * again right before the write.
58
+ *
59
+ * `holdAfterSend` keeps the posting process alive while macOS verifies the
60
+ * sender's ancestry. A resident writer (the Session Attendant) stays alive
61
+ * anyway and skips the wait.
62
+ */
63
+ export declare function deliverIntoClaudeSession(options: {
64
+ sessionId: string;
65
+ sourcePid: number;
66
+ sourceDescriptor: ClaudeSessionDescriptor | null;
67
+ adapters: ClaudeWakeAdapters;
68
+ text: string;
69
+ begin(): boolean;
70
+ holdAfterSend?: boolean;
71
+ /** Checked at the socket itself, after it connects and before its first byte. */
72
+ guard?: WriteGuard;
73
+ /** Names the posting process in refusal reasons, which logs keep verbatim. */
74
+ writer: string;
75
+ }): Promise<SessionWriteResult>;
76
+ /**
77
+ * Codex's durable per-thread inbox. Exit 0 proves the queue stored the
78
+ * message, never that the thread consumed it, and a stopped thread drains it
79
+ * the next time it opens.
80
+ */
81
+ export declare function deliverIntoCodexThread(options: {
82
+ threadId: string;
83
+ cwd: string;
84
+ env: NodeJS.ProcessEnv;
85
+ adapters: CodexWakeAdapters;
86
+ text: string;
87
+ begin(): boolean;
88
+ /**
89
+ * A claimed write's bound: checked after `begin`, immediately before the
90
+ * queue writer starts, and its `remainingMs` then bounds the writer's whole
91
+ * life. A writer still running at that deadline has its process group
92
+ * killed and is reported `failed`: it may have written.
93
+ */
94
+ guard?: WriteGuard;
95
+ /** Receives the queue writer's process group as soon as it exists. */
96
+ onSpawn?: (pgid: number) => void;
97
+ }): Promise<SessionWriteResult>;
98
+ export declare function claudeSocketLine(text: string): string;
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Hand one message into a running Agent Session, in place.
3
+ *
4
+ * The one harness write every hand-off shares: the answer waiter's wake routes
5
+ * and the Session Attendant's Session Messages both end here. It never cold
6
+ * resumes: a stopped session is reported as stopped, and only the answer route
7
+ * decides what to do about that (Session Messages never resume a session).
8
+ *
9
+ * `begin` is the commit point. It is called immediately before the
10
+ * irreversible write, after every check that can refuse without writing; when
11
+ * it returns false nothing is handed over. A `failed` result after `begin`
12
+ * means the write may or may not have reached the harness.
13
+ */
14
+ import { CLAUDE_POST_SEND_LIVENESS_MS, observeClaudeSession, parseDescriptor, } from './claude-wake.js';
15
+ import { inspectCodexQueue } from './codex-wake.js';
16
+ import { WriteAbortedError, WriteDeadlineError } from './wake-support.js';
17
+ /**
18
+ * The Claude session descriptor of the process that will post, when it names
19
+ * this exact session. Read once, when the writer starts: a descriptor that
20
+ * appears later is not evidence that this process is that session's child.
21
+ */
22
+ export function claudeSourceDescriptor(sessionId, sourcePid, adapters) {
23
+ try {
24
+ const parsed = parseDescriptor(adapters.readDescriptor(sourcePid));
25
+ return parsed !== null && parsed.sessionId === sessionId && parsed.pid === sourcePid
26
+ ? parsed
27
+ : null;
28
+ }
29
+ catch {
30
+ // Delivery fails closed below; a missing descriptor must never break the caller.
31
+ return null;
32
+ }
33
+ }
34
+ /**
35
+ * Claude Code's own-child inbox route. Claude delivers an inbox-socket line
36
+ * only from a verified descendant of the receiving process, so the writer must
37
+ * be that exact session's child: `sourcePid` is its harness process, observed
38
+ * again right before the write.
39
+ *
40
+ * `holdAfterSend` keeps the posting process alive while macOS verifies the
41
+ * sender's ancestry. A resident writer (the Session Attendant) stays alive
42
+ * anyway and skips the wait.
43
+ */
44
+ export async function deliverIntoClaudeSession(options) {
45
+ const { adapters, writer } = options;
46
+ const observation = await observeClaudeSession(options.sessionId, adapters);
47
+ if (observation.state === 'unknown')
48
+ return { status: 'unavailable', reason: observation.reason };
49
+ if (options.sourceDescriptor === null) {
50
+ return {
51
+ status: 'unavailable',
52
+ reason: `the ${writer} cannot prove exact Claude session ownership`,
53
+ };
54
+ }
55
+ if (observation.state === 'stopped')
56
+ return { status: 'stopped' };
57
+ if (observation.descriptor.pid !== options.sourcePid ||
58
+ options.sourceDescriptor.startedAt !== observation.descriptor.startedAt) {
59
+ return {
60
+ status: 'unavailable',
61
+ reason: `the ${writer} is not the observed exact Claude session child`,
62
+ };
63
+ }
64
+ if (!options.begin())
65
+ return { status: 'cancelled' };
66
+ try {
67
+ await adapters.sendSocket(observation.descriptor.messagingSocketPath, claudeSocketLine(options.text), options.guard);
68
+ }
69
+ catch (err) {
70
+ if (err instanceof WriteAbortedError)
71
+ return { status: 'aborted', reason: err.message };
72
+ return { status: 'failed', reason: err instanceof Error ? err.message : String(err), error: err };
73
+ }
74
+ if (options.holdAfterSend !== false)
75
+ await adapters.sleep(CLAUDE_POST_SEND_LIVENESS_MS);
76
+ return { status: 'written', route: 'inbox-socket', sessionState: observation.state };
77
+ }
78
+ /**
79
+ * Codex's durable per-thread inbox. Exit 0 proves the queue stored the
80
+ * message, never that the thread consumed it, and a stopped thread drains it
81
+ * the next time it opens.
82
+ */
83
+ export async function deliverIntoCodexThread(options) {
84
+ const readiness = inspectCodexQueue(options.threadId, options.env);
85
+ if (readiness.state === 'unavailable')
86
+ return { status: 'unavailable', reason: readiness.reason };
87
+ if (!options.begin())
88
+ return { status: 'cancelled' };
89
+ if (options.guard !== undefined && !options.guard.writable()) {
90
+ return { status: 'aborted', reason: 'the claim or lease lapsed before the queue writer started' };
91
+ }
92
+ const budget = options.guard?.remainingMs() ?? Number.POSITIVE_INFINITY;
93
+ const deadline = new AbortController();
94
+ const timer = Number.isFinite(budget) ? setTimeout(() => deadline.abort(), Math.max(0, budget)) : undefined;
95
+ try {
96
+ await options.adapters.queue(readiness.threadId, options.cwd, options.text, options.onSpawn, deadline.signal);
97
+ }
98
+ catch (err) {
99
+ return { status: 'failed', reason: err instanceof Error ? err.message : String(err), error: err };
100
+ }
101
+ finally {
102
+ if (timer !== undefined)
103
+ clearTimeout(timer);
104
+ }
105
+ if (deadline.signal.aborted) {
106
+ // The writer finished, but only after its claim's write boundary.
107
+ const error = new WriteDeadlineError('the queue writer finished after its claim deadline');
108
+ return { status: 'failed', reason: error.message, error };
109
+ }
110
+ return { status: 'written', route: 'session-queue' };
111
+ }
112
+ export function claudeSocketLine(text) {
113
+ return `${JSON.stringify({
114
+ type: 'user',
115
+ message: { role: 'user', content: text },
116
+ })}\n`;
117
+ }
118
+ //# sourceMappingURL=session-handoff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-handoff.js","sourceRoot":"","sources":["../src/session-handoff.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * How the Session Attendant hands Session Messages — Session Notes and
3
+ * post-delivery Answer Edits — into its running Agent Session.
4
+ *
5
+ * Each message is one claimed hand-off through the session's delivery
6
+ * sequencer, in the order the service accepted them: claim under the attendant's
7
+ * lease generation, record the acknowledgement it is owed, write the
8
+ * structured context in place, report. Nothing is written without a claim,
9
+ * nothing after the claim deadline or the attendant's own write margin, and an
10
+ * `unconfirmed` message is never written again.
11
+ */
12
+ import type { AttendanceMessage } from '@raidiant/notifai-protocol';
13
+ import { type SequencerDeps } from './session-delivery.js';
14
+ import type { AttendantHandle } from './session-attendant.js';
15
+ import type { SessionWriteResult } from './session-handoff.js';
16
+ import type { WriteGuard } from './wake-support.js';
17
+ /**
18
+ * - `done`: every message was handed off, settled elsewhere, or waits for the
19
+ * next exchange (which returns every message still claimable).
20
+ * - `retry-soon`: a message waits on something that resolves in seconds (its
21
+ * fenced answer's outcome, the delivery lock, a lease renewal); ask again
22
+ * without holding the exchange open.
23
+ */
24
+ export type MessageHandOffResult = 'done' | 'retry-soon';
25
+ export interface MessageHandOffDeps {
26
+ sequencer: SequencerDeps;
27
+ /**
28
+ * The in-place write: `begin` is its commit point and `guard` is checked at
29
+ * the write itself, before the first byte. A write made by a harness
30
+ * subprocess (`codex queue`) begins as `subprocess` and reports that
31
+ * subprocess's process group through `writerGroup` as soon as it exists.
32
+ */
33
+ write(text: string, begin: (writer?: 'subprocess') => boolean, guard: WriteGuard, writerGroup: (pgid: number) => void): Promise<SessionWriteResult>;
34
+ }
35
+ export declare function handOffSessionMessages(messages: readonly AttendanceMessage[], attendant: AttendantHandle, deps: MessageHandOffDeps): Promise<MessageHandOffResult>;
@@ -0,0 +1,99 @@
1
+ import { clearAcknowledgementObligation, recordMessageAcknowledgementDue } from './hook-acknowledgements.js';
2
+ import { sessionMessageContext } from './injection-render.js';
3
+ import { answerWriterGone, beginHandOff } from './session-delivery.js';
4
+ export async function handOffSessionMessages(messages, attendant, deps) {
5
+ const { sequencer } = deps;
6
+ const log = sequencer.log;
7
+ // The service lists messages in acceptance (revision) order, which is the
8
+ // User's order. Timestamps can tie and identifiers are random, so neither
9
+ // may reorder them: a later edit must never land before an earlier one.
10
+ for (const message of messages) {
11
+ const generation = attendant.generation();
12
+ if (generation === null || !attendant.mayWrite())
13
+ return 'retry-soon';
14
+ const handOff = await beginHandOff(sequencer, {
15
+ lease: { incarnation: attendant.incarnation(), generation },
16
+ subjects: [{ type: 'session_message', message_id: message.message_id }],
17
+ earlierAnswerWriterGone: () => message.kind === 'answer_edit' &&
18
+ answerWriterGone(sequencer.sessionId, sequencer.env, message.request_id, sequencer.liveness, sequencer.groupAlive),
19
+ mayWrite: () => attendant.mayWrite(),
20
+ });
21
+ if (handOff === null)
22
+ return 'retry-soon';
23
+ const refusal = handOff.refused[0];
24
+ if (refusal !== undefined) {
25
+ await handOff.finish('not-written');
26
+ switch (refusal.reason) {
27
+ case 'awaiting_earlier_answer':
28
+ case 'attempt_pending':
29
+ case 'unavailable':
30
+ // Later messages keep their order behind this one.
31
+ return 'retry-soon';
32
+ case 'generation_fenced':
33
+ // The lease moved; the attendant re-acquires before anything else.
34
+ return 'done';
35
+ case 'not_claimable':
36
+ case 'not_found':
37
+ continue;
38
+ }
39
+ }
40
+ let owed = false;
41
+ let result;
42
+ try {
43
+ result = await deps.write(sessionMessageContext(message), (writer) => {
44
+ recordMessageAcknowledgementDue(sequencer.sessionId, sequencer.env, {
45
+ message_id: message.message_id,
46
+ recorded_at: sequencer.wall(),
47
+ text_required: message.agent_acknowledgement_text_required,
48
+ });
49
+ owed = true;
50
+ if (handOff.begin(undefined, { subprocess: writer === 'subprocess' }))
51
+ return true;
52
+ clearAcknowledgementObligation(sequencer.sessionId, sequencer.env, message.message_id);
53
+ owed = false;
54
+ return false;
55
+ }, {
56
+ // The lease check probes the harness and can block: the claim
57
+ // deadline is judged after it, at the last moment before the byte.
58
+ writable: () => attendant.mayWrite() && handOff.writable(),
59
+ remainingMs: () => handOff.remainingMs(),
60
+ }, (pgid) => handOff.recordGroup(pgid));
61
+ }
62
+ catch (err) {
63
+ // Thrown before the write (state I/O): nothing reached the harness.
64
+ if (owed)
65
+ clearAcknowledgementObligation(sequencer.sessionId, sequencer.env, message.message_id);
66
+ await handOff.finish('not-written');
67
+ throw err;
68
+ }
69
+ log?.info('delivery.handoff', {
70
+ message_id: message.message_id,
71
+ kind: message.kind,
72
+ write: result.status,
73
+ ...(result.status === 'unavailable' || result.status === 'failed' ? { reason: result.reason } : {}),
74
+ });
75
+ switch (result.status) {
76
+ case 'written':
77
+ await handOff.finish('written');
78
+ continue;
79
+ case 'failed':
80
+ // It may have arrived: the debt stays and the service hears `unconfirmed`.
81
+ await handOff.finish('failed');
82
+ continue;
83
+ case 'cancelled':
84
+ await handOff.finish('not-written');
85
+ return 'retry-soon';
86
+ case 'aborted':
87
+ // Stopped at the socket before any byte left: nothing arrived, nothing is owed.
88
+ clearAcknowledgementObligation(sequencer.sessionId, sequencer.env, message.message_id);
89
+ await handOff.finish('aborted');
90
+ return 'retry-soon';
91
+ case 'unavailable':
92
+ case 'stopped':
93
+ await handOff.finish('not-written');
94
+ return 'done';
95
+ }
96
+ }
97
+ return 'done';
98
+ }
99
+ //# sourceMappingURL=session-message-handoff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-message-handoff.js","sourceRoot":"","sources":["../src/session-message-handoff.ts"],"names":[],"mappings":"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"}
@@ -1,20 +1,36 @@
1
1
  {
2
2
  "schema_version": 1,
3
- "package_version": "11.3.2",
3
+ "package_version": "11.4.0",
4
4
  "skill": "notifai",
5
- "digest": "sha256:6b5b2a43d5798a8d6095e3884046aeb0e6758fc9211a07c6130e01e99edd00d3",
5
+ "digest": "sha256:a94b7564764b023ec4fdf4aa5f6bdac0e47ce6d7da9bde4795fd177dd2725aa1",
6
6
  "files": [
7
7
  {
8
8
  "path": "SKILL.md",
9
- "sha256": "7769c166269c9612bd3fdfc24fe2d884321f68959c999f109a18d82a41a01803"
9
+ "sha256": "7bc8330caa4e5e86262708b05d35767e63b229d19786ecaebf43fb1ac09c4be6"
10
+ },
11
+ {
12
+ "path": "references/diagnostics.md",
13
+ "sha256": "a3ec112b116efdf8b1f496df14dd7f12ac79d7b7c071d08c88cc1bfa5035d5d9"
10
14
  },
11
15
  {
12
16
  "path": "references/harness-setup.md",
13
- "sha256": "e2ef0c2c1c81568ce7afa310d0c979795209ace5e276d65f2410976de0050a5d"
17
+ "sha256": "b00af6f76e7ce06d59bc2fe5fc4ac4312fd9c829c5bd2202e990aa83bc69c551"
18
+ },
19
+ {
20
+ "path": "references/notes-and-edits.md",
21
+ "sha256": "9bf5ac8fea62080a903dbb5c70044bc9644e02dc19d9e1a9b2f9b44dbf0a18a1"
22
+ },
23
+ {
24
+ "path": "references/send-details.md",
25
+ "sha256": "575011edbea8dc93b85ff119dfaef328574c765b3a6500c7a6eb132c5b9fb9f9"
14
26
  },
15
27
  {
16
28
  "path": "references/updates.md",
17
29
  "sha256": "55ee18d2c53923932605e9c30823400f7e494456bb18e0de8ea6c5206e632b79"
30
+ },
31
+ {
32
+ "path": "references/writing-examples.md",
33
+ "sha256": "ba0dc8eb849aaa513476067c4e405ea284a4228e2445b8ab7343bd3450b1eebf"
18
34
  }
19
35
  ]
20
36
  }
@@ -5,8 +5,8 @@ description: Use when work needs a User decision, approval, sign-in, credential
5
5
 
6
6
  # Notifai
7
7
 
8
- Use `notifai`; never hand-roll HTTP, hooks, or polling. It owns routing,
9
- retries, and delivery evidence.
8
+ Use `notifai` for routing, retries, and delivery evidence; never hand-roll
9
+ HTTP, hooks, or polling.
10
10
 
11
11
  If missing: `npm install -g @raidiant/notifai`. Pinned
12
12
  `npx --yes @raidiant/notifai@<version>` is only for users refusing a global binary,
@@ -96,9 +96,8 @@ notifai send --kind done \
96
96
  --body "Sign-up, email verification, and login work end to end on staging. Next: password reset, unless you want something else first."
97
97
  ```
98
98
 
99
- Outside a Project, or when the User explicitly asks for a Projectless
100
- notification, pass `--projectless`. This deliberate override prevents cwd or
101
- saved config from inventing a Project and never enables one.
99
+ Outside a Project, or when the User asks for a Projectless notification, pass
100
+ `--projectless`; it keeps cwd or config from inventing a Project.
102
101
 
103
102
  `--kind` is required, and it is the most consequential word you choose:
104
103
 
@@ -125,21 +124,11 @@ Use one-way blocked only when no User reply would resume the work.
125
124
  Summary's information plus useful detail; focused views show Body or Summary,
126
125
  never both. Omit it when Summary is enough.
127
126
 
128
- Use readable Markdown structure. Summary has no Markdown or media markup.
127
+ Use `--body-file <path|->` for long content.
129
128
 
130
- Use `--body-file <path|->` for long content. Keep wording channel-neutral.
131
-
132
- Other controls: `--thread-id` groups; `--collapse-key` replaces your earlier
133
- matching notification; `--ttl` bounds useful delivery.
134
- Use `notifai capabilities --platform <platform>` for destination contracts.
135
-
136
- Repeat `--image <path|url>` (up to 8), paired with `--image-alt`; all reach the
137
- gallery. Reference any or all in Body with Markdown image syntax:
138
- `- Bob: ![front](media:1) ![side](media:2)`. Bare `media:1`, `[…](media:1)`,
139
- or a position with no `--image` is an error.
140
-
141
- `--sound`, `--level`, and `--device` belong to the User.
142
- `--sound` takes a shipped name or a custom name/id from `notifai sounds`.
129
+ Images (`--image`, referenced in Body as `media:1`), grouping, replacement,
130
+ and the User-owned `--sound`, `--level`, and `--device`:
131
+ [Sending details](references/send-details.md).
143
132
  Use `--retry` only for the same unresolved Agent Event; the CLI reuses one
144
133
  opaque attempt or refuses ambiguity.
145
134
 
@@ -151,7 +140,7 @@ It is safe to repeat the same `--session-label` on every send and ask; the first
151
140
  accepted semantic name remains authoritative. Only a generated fallback name
152
141
  can be replaced by a later semantic name.
153
142
 
154
- User renames sync across Companion Apps and future Requests. An agent may run
143
+ An agent may run
155
144
  `notifai session rename "New job"` only when its job changed completely and its
156
145
  current name would now mislead. Never rename for milestones, ordinary progress,
157
146
  or same-job refinement. The command accepts no Agent Session id and fails unless
@@ -196,16 +185,15 @@ notifai status <question_id> --json
196
185
 
197
186
  States: `local`, `frozen`, `live`, `answered`, `withdrawn`, `retired`.
198
187
 
199
- **Registering is not the end of the turn.** In that same turn, ask the question
200
- in the conversation and say what each answer will make you do, then end your
201
- turn to start submission. Ordinary sends need no turn boundary. Continue
202
- independent work in later turns; preserve supervision.
188
+ **Registering is not the end of the turn.** Ask in plain conversational text,
189
+ say what each answer will make you do, then end the turn to start submission.
190
+ A second harness form can remain pending after a reply. Follow
191
+ higher-priority rules; never claim either path retires the other.
203
192
 
204
193
  **Never say where the answer must arrive** ("tell me here"). The harness owns routing.
205
194
 
206
- Use `--multi` for combined answers, `--body`/`--body-file` for Body,
207
- `--image`/`--image-alt` for evidence, or `--form <path|->` for up to 10 questions
208
- with a set-level `summary`.
195
+ Use `--multi` for combined answers, `--body-file` for Body, `--image` for
196
+ evidence, or `--form <path|->` for up to 10 questions with one `summary`.
209
197
 
210
198
  Register independent questions separately. Retire an obsolete registration or
211
199
  one they answer in the conversation with
@@ -250,10 +238,9 @@ resume later. Never create a duplicate. On exit 0, act on the answer.
250
238
 
251
239
  ## When the answer arrives
252
240
 
253
- The latest reply is the user's current word: a later one corrects an earlier
254
- one, and a typed answer arriving in parts is read together, in order. A relayed
255
- answer reaches you as the chosen label's text; `notifai replies <request_id>
256
- --json` has the stable choice ids.
241
+ The latest reply is the user's current word: later choices correct earlier
242
+ ones; typed parts are read together in order. A relayed answer uses the chosen
243
+ label; `notifai replies <request_id> --json` has stable choice ids.
257
244
 
258
245
  Questions normally remain answerable for a day. When resuming without a relayed
259
246
  answer, inspect the original `question_id`. If it is lost, list outstanding
@@ -264,16 +251,16 @@ notifai replies --pending --json
264
251
  ```
265
252
 
266
253
  **Acknowledge before you resume.** The user needs to know their reply was read.
267
- Notifai tells you the exact command; run it once per answered request that
268
- arrived through Notifai, before any of the work it unblocks:
254
+ Notifai tells you the exact command; run it once per answered request or
255
+ [note or edit](references/notes-and-edits.md) (`sm_…`) that arrived through
256
+ Notifai, before the work it unblocks:
269
257
 
270
258
  ```bash
271
259
  notifai acknowledge <request_id> --text "Rolling out to staging now; I'll report the health checks."
272
260
  ```
273
261
 
274
- Keep it under 200 characters — it is a receipt, not a report.
275
-
276
- Name only the concrete work their reply causes.
262
+ Keep it under 200 characters and name only the concrete work their reply
263
+ causes: it is a receipt, not a report.
277
264
 
278
265
  If the written reply is off, Notifai prints the command without `--text`; run
279
266
  exactly that. The acknowledgement is never optional.
@@ -313,9 +300,6 @@ Lifecycle wiring has no scope: one install per harness for this machine;
313
300
  notifai init <--hooks|--no-hooks> [--skills --skills-scope <project|global>] --json
314
301
  ```
315
302
 
316
- `init` never creates `.notifai/config.toml`; use it only for tracked shared
317
- overrides.
318
-
319
303
  Branch on `states`, `can_send`, and `question_routing_ready`.
320
304
  `direct_wake_ready` assesses the route, not consumption;
321
305
  optional when a held continuation or journal recovery owns the answer,
@@ -330,11 +314,6 @@ that document has the words.
330
314
  Never emulate User-owned actions, claim to approve hooks yourself, or claim an
331
315
  unlisted harness. Harness trust wording lives in the setup reference.
332
316
 
333
- When sign-in looks fine but nothing sends, `notifai auth status --json` and
334
- `notifai auth access --json` separate a pairing problem from an account without
335
- an active plan — report which one it is instead of calling it a delivery
336
- failure.
337
-
338
317
  On `no_active_devices`, run `notifai init --json`, close its gap, then repeat the
339
318
  exact original send with `--retry`. A verification Notification does not deliver
340
319
  the original Agent Event.
@@ -347,7 +326,7 @@ installing hooks or diagnosing routing, not before.
347
326
 
348
327
  ## Check what happened
349
328
 
350
- Warning-free ordinary sends are silent on success; use `--json` for a receipt.
329
+ Ordinary sends are silent on success; `--json` gives a receipt.
351
330
  Check `status` before calling delivery unconfirmed. Provider Acceptance proves
352
331
  acceptance, a Companion Receipt proves receipt, neither proves the User read it.
353
332
  `unknown` is not failure.
@@ -356,28 +335,10 @@ acceptance, a Companion Receipt proves receipt, neither proves the User read it.
356
335
  notifai status <question_id|request_id> # state, promotion, and evidence
357
336
  ```
358
337
 
359
- When something did not happen and you cannot see why — most of all after `ask`,
360
- whose push happens later inside a hook the harness swallows — the local log is
361
- the only account:
362
-
363
- ```bash
364
- notifai logs # recent record for this project
365
- notifai logs --level error # only what failed
366
- notifai logs --request <id> # everything about one notification
367
- notifai logs --since 10m --json # JSONL on stdout, for parsing
368
- ```
369
-
370
- An empty record can mean `log_level` is `off` — check it before concluding a
371
- hook never ran.
372
-
373
- `hook.gate` records carry a fixed `reason` — `notifications-off`,
374
- `claimed-elsewhere`, `no-question`, `no-session`, `answered`,
375
- `acknowledgement-required`, `acknowledgement-abandoned`, `harness-cannot-continue`,
376
- `continuation-repeat`,
377
- `continuation-limit`,
378
- `delivery-limit`, `proceeding` — so filter on that, never on the wording of a
379
- message. `notifications-off` is the one the user deliberately never sees, which
380
- is why it is worth ruling out before concluding anything is broken.
338
+ When something did not happen and you cannot see why — most of all after
339
+ `ask` — `notifai logs` is the only account: its `hook.gate` records carry a
340
+ fixed reason. Read [Diagnosing what happened](references/diagnostics.md) for
341
+ filters, the reasons, and sign-in versus plan problems.
381
342
 
382
343
  The log never leaves the machine, and it contains the user's own answers. Treat
383
344
  it like any other private file of theirs.
@@ -0,0 +1,31 @@
1
+ # Diagnosing what happened
2
+
3
+ When something did not happen and you cannot see why — most of all after `ask`,
4
+ whose push happens later inside a hook the harness swallows — the local log is
5
+ the only account:
6
+
7
+ ```bash
8
+ notifai logs # recent record for this project
9
+ notifai logs --level error # only what failed
10
+ notifai logs --request <id> # everything about one notification
11
+ notifai logs --since 10m --json # JSONL on stdout, for parsing
12
+ ```
13
+
14
+ An empty record can mean `log_level` is `off` — check it before concluding a
15
+ hook never ran.
16
+
17
+ `hook.gate` records carry a fixed `reason` — `notifications-off`,
18
+ `claimed-elsewhere`, `no-question`, `no-session`, `answered`,
19
+ `acknowledgement-required`, `acknowledgement-abandoned`, `harness-cannot-continue`,
20
+ `continuation-repeat`, `continuation-limit`, `delivery-limit`, `proceeding` —
21
+ so filter on that, never on the wording of a message. `notifications-off` is
22
+ the one the user deliberately never sees, which is why it is worth ruling out
23
+ before concluding anything is broken.
24
+
25
+ When sign-in looks fine but nothing sends, `notifai auth status --json` and
26
+ `notifai auth access --json` separate a pairing problem from an account without
27
+ an active plan — report which one it is instead of calling it a delivery
28
+ failure.
29
+
30
+ The log never leaves the machine, and it contains the user's own answers. Treat
31
+ it like any other private file of theirs.
@@ -310,3 +310,6 @@ journal is crash recovery, not the ordinary delivery path.
310
310
  want in CI or any shell with nobody at it. `NOTIFAI_CREDENTIALS=file` stores the
311
311
  machine credential in a plaintext file rather than the OS keychain — only when
312
312
  the user has asked for it, and never on a shared machine.
313
+
314
+ `notifai init` never creates `.notifai/config.toml`; that file is only for
315
+ tracked, shared overrides a Project chooses to commit.