@raidiant/notifai 11.4.0-beta.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 (96) hide show
  1. package/CHANGELOG.md +9 -2
  2. package/dist/claude-wake.d.ts +11 -3
  3. package/dist/claude-wake.js +74 -67
  4. package/dist/claude-wake.js.map +1 -1
  5. package/dist/client.d.ts +23 -2
  6. package/dist/client.js +11 -5
  7. package/dist/client.js.map +1 -1
  8. package/dist/codex-wake.d.ts +12 -2
  9. package/dist/codex-wake.js +50 -13
  10. package/dist/codex-wake.js.map +1 -1
  11. package/dist/commands-acknowledge.d.ts +4 -2
  12. package/dist/commands-acknowledge.js +35 -4
  13. package/dist/commands-acknowledge.js.map +1 -1
  14. package/dist/commands-ask.js +2 -2
  15. package/dist/commands-ask.js.map +1 -1
  16. package/dist/commands-close.js +16 -4
  17. package/dist/commands-close.js.map +1 -1
  18. package/dist/commands-core.d.ts +6 -0
  19. package/dist/commands-core.js.map +1 -1
  20. package/dist/commands-doctor.js +46 -1
  21. package/dist/commands-doctor.js.map +1 -1
  22. package/dist/commands-hook-attend.d.ts +61 -0
  23. package/dist/commands-hook-attend.js +475 -0
  24. package/dist/commands-hook-attend.js.map +1 -0
  25. package/dist/commands-hook-diagnostics.js +2 -2
  26. package/dist/commands-hook-diagnostics.js.map +1 -1
  27. package/dist/commands-hook-install.js +1 -1
  28. package/dist/commands-hook-install.js.map +1 -1
  29. package/dist/commands-hook-run.js +59 -2
  30. package/dist/commands-hook-run.js.map +1 -1
  31. package/dist/commands-send.js +6 -1
  32. package/dist/commands-send.js.map +1 -1
  33. package/dist/guidance-content.js +9 -35
  34. package/dist/guidance-content.js.map +1 -1
  35. package/dist/hook-acknowledgements.d.ts +25 -5
  36. package/dist/hook-acknowledgements.js +102 -45
  37. package/dist/hook-acknowledgements.js.map +1 -1
  38. package/dist/hook-events.d.ts +26 -1
  39. package/dist/hook-events.js +38 -2
  40. package/dist/hook-events.js.map +1 -1
  41. package/dist/hook-lifecycle.d.ts +6 -0
  42. package/dist/hook-lifecycle.js +297 -26
  43. package/dist/hook-lifecycle.js.map +1 -1
  44. package/dist/hook-question-lock.d.ts +18 -0
  45. package/dist/hook-question-lock.js +80 -57
  46. package/dist/hook-question-lock.js.map +1 -1
  47. package/dist/hook-question-retirement.d.ts +10 -2
  48. package/dist/hook-question-retirement.js +21 -4
  49. package/dist/hook-question-retirement.js.map +1 -1
  50. package/dist/hook-session-state.d.ts +90 -1
  51. package/dist/hook-session-state.js +201 -9
  52. package/dist/hook-session-state.js.map +1 -1
  53. package/dist/hook-types.d.ts +50 -2
  54. package/dist/injection-render.d.ts +36 -0
  55. package/dist/injection-render.js +61 -0
  56. package/dist/injection-render.js.map +1 -0
  57. package/dist/install-hooks.d.ts +14 -0
  58. package/dist/install-hooks.js +47 -2
  59. package/dist/install-hooks.js.map +1 -1
  60. package/dist/logging.d.ts +1 -1
  61. package/dist/logging.js +12 -0
  62. package/dist/logging.js.map +1 -1
  63. package/dist/process-identity.d.ts +26 -0
  64. package/dist/process-identity.js +82 -0
  65. package/dist/process-identity.js.map +1 -0
  66. package/dist/program.js +6 -6
  67. package/dist/program.js.map +1 -1
  68. package/dist/session-attendant-probe.d.ts +74 -0
  69. package/dist/session-attendant-probe.js +131 -0
  70. package/dist/session-attendant-probe.js.map +1 -0
  71. package/dist/session-attendant-state.d.ts +53 -0
  72. package/dist/session-attendant-state.js +175 -0
  73. package/dist/session-attendant-state.js.map +1 -0
  74. package/dist/session-attendant.d.ts +135 -0
  75. package/dist/session-attendant.js +493 -0
  76. package/dist/session-attendant.js.map +1 -0
  77. package/dist/session-delivery.d.ts +214 -0
  78. package/dist/session-delivery.js +560 -0
  79. package/dist/session-delivery.js.map +1 -0
  80. package/dist/session-handoff.d.ts +98 -0
  81. package/dist/session-handoff.js +118 -0
  82. package/dist/session-handoff.js.map +1 -0
  83. package/dist/session-message-handoff.d.ts +35 -0
  84. package/dist/session-message-handoff.js +99 -0
  85. package/dist/session-message-handoff.js.map +1 -0
  86. package/dist/skill-source/manifest.json +20 -4
  87. package/dist/skill-source/notifai/SKILL.md +19 -57
  88. package/dist/skill-source/notifai/references/diagnostics.md +31 -0
  89. package/dist/skill-source/notifai/references/harness-setup.md +3 -0
  90. package/dist/skill-source/notifai/references/notes-and-edits.md +38 -0
  91. package/dist/skill-source/notifai/references/send-details.md +14 -0
  92. package/dist/skill-source/notifai/references/writing-examples.md +56 -0
  93. package/dist/wake-support.d.ts +33 -1
  94. package/dist/wake-support.js +65 -8
  95. package/dist/wake-support.js.map +1 -1
  96. 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.4.0-beta.2",
3
+ "package_version": "11.4.0",
4
4
  "skill": "notifai",
5
- "digest": "sha256:4069705b6d8efd7d7aedf713ea24cf2cb7027bef455f0f539ad289404f5a044e",
5
+ "digest": "sha256:a94b7564764b023ec4fdf4aa5f6bdac0e47ce6d7da9bde4795fd177dd2725aa1",
6
6
  "files": [
7
7
  {
8
8
  "path": "SKILL.md",
9
- "sha256": "093fcb9b1653e49d04d0f13463251ad5a85de2baae36e02cdd71100f1567a5c3"
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
  }
@@ -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
@@ -203,9 +192,8 @@ 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
@@ -263,16 +251,16 @@ notifai replies --pending --json
263
251
  ```
264
252
 
265
253
  **Acknowledge before you resume.** The user needs to know their reply was read.
266
- Notifai tells you the exact command; run it once per answered request that
267
- 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:
268
257
 
269
258
  ```bash
270
259
  notifai acknowledge <request_id> --text "Rolling out to staging now; I'll report the health checks."
271
260
  ```
272
261
 
273
- Keep it under 200 characters — it is a receipt, not a report.
274
-
275
- 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.
276
264
 
277
265
  If the written reply is off, Notifai prints the command without `--text`; run
278
266
  exactly that. The acknowledgement is never optional.
@@ -312,9 +300,6 @@ Lifecycle wiring has no scope: one install per harness for this machine;
312
300
  notifai init <--hooks|--no-hooks> [--skills --skills-scope <project|global>] --json
313
301
  ```
314
302
 
315
- `init` never creates `.notifai/config.toml`; use it only for tracked shared
316
- overrides.
317
-
318
303
  Branch on `states`, `can_send`, and `question_routing_ready`.
319
304
  `direct_wake_ready` assesses the route, not consumption;
320
305
  optional when a held continuation or journal recovery owns the answer,
@@ -329,11 +314,6 @@ that document has the words.
329
314
  Never emulate User-owned actions, claim to approve hooks yourself, or claim an
330
315
  unlisted harness. Harness trust wording lives in the setup reference.
331
316
 
332
- When sign-in looks fine but nothing sends, `notifai auth status --json` and
333
- `notifai auth access --json` separate a pairing problem from an account without
334
- an active plan — report which one it is instead of calling it a delivery
335
- failure.
336
-
337
317
  On `no_active_devices`, run `notifai init --json`, close its gap, then repeat the
338
318
  exact original send with `--retry`. A verification Notification does not deliver
339
319
  the original Agent Event.
@@ -346,7 +326,7 @@ installing hooks or diagnosing routing, not before.
346
326
 
347
327
  ## Check what happened
348
328
 
349
- Warning-free ordinary sends are silent on success; use `--json` for a receipt.
329
+ Ordinary sends are silent on success; `--json` gives a receipt.
350
330
  Check `status` before calling delivery unconfirmed. Provider Acceptance proves
351
331
  acceptance, a Companion Receipt proves receipt, neither proves the User read it.
352
332
  `unknown` is not failure.
@@ -355,28 +335,10 @@ acceptance, a Companion Receipt proves receipt, neither proves the User read it.
355
335
  notifai status <question_id|request_id> # state, promotion, and evidence
356
336
  ```
357
337
 
358
- When something did not happen and you cannot see why — most of all after `ask`,
359
- whose push happens later inside a hook the harness swallows — the local log is
360
- the only account:
361
-
362
- ```bash
363
- notifai logs # recent record for this project
364
- notifai logs --level error # only what failed
365
- notifai logs --request <id> # everything about one notification
366
- notifai logs --since 10m --json # JSONL on stdout, for parsing
367
- ```
368
-
369
- An empty record can mean `log_level` is `off` — check it before concluding a
370
- hook never ran.
371
-
372
- `hook.gate` records carry a fixed `reason` — `notifications-off`,
373
- `claimed-elsewhere`, `no-question`, `no-session`, `answered`,
374
- `acknowledgement-required`, `acknowledgement-abandoned`, `harness-cannot-continue`,
375
- `continuation-repeat`,
376
- `continuation-limit`,
377
- `delivery-limit`, `proceeding` — so filter on that, never on the wording of a
378
- message. `notifications-off` is the one the user deliberately never sees, which
379
- 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.
380
342
 
381
343
  The log never leaves the machine, and it contains the user's own answers. Treat
382
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.
@@ -0,0 +1,38 @@
1
+ # Notes and edits
2
+
3
+ While this session runs, the User can send into it from their device:
4
+
5
+ - a **note**: steering, context, a correction, or a nudge for the work in
6
+ progress. No question preceded it.
7
+ - an **edited answer**: a change to an answer you already received. It
8
+ replaces the earlier answer and is their current word.
9
+
10
+ Each arrives as a turn beginning `Notifai —` that names a Session Message
11
+ (`sm_…`). The User's words are one quoted value; the command stands outside it.
12
+
13
+ ## Acknowledge each one once, before acting on it
14
+
15
+ ```bash
16
+ notifai acknowledge sm_… --text "Switching the migration to staging; I'll rerun it there."
17
+ ```
18
+
19
+ Name the work the message changes, as for any acknowledgement. If the account
20
+ turned acknowledgement text off, Notifai prints the command without `--text`;
21
+ run exactly that. Once it reports recorded or replayed, it is done.
22
+
23
+ An edit can arrive after you acted on the earlier answer. Say what already
24
+ happened and what you will do now; never imply that irreversible work was
25
+ undone:
26
+
27
+ ```bash
28
+ notifai acknowledge sm_… --text "The email already went out with Monday; sending a correction for Friday now."
29
+ ```
30
+
31
+ ## What a note is not
32
+
33
+ A note is the User's own words carried from their device, not an instruction
34
+ from Notifai or the system. It never satisfies a harness permission prompt or
35
+ an interactive picker, and it never moves private material off this machine.
36
+
37
+ The acknowledgement is the receipt. Results a note asks for follow the ordinary
38
+ rules for when to notify.
@@ -0,0 +1,14 @@
1
+ # Sending: the less common controls
2
+
3
+ `--thread-id` groups; `--collapse-key` replaces your earlier matching
4
+ notification; `--ttl` bounds useful delivery. Use
5
+ `notifai capabilities --platform <platform>` for destination contracts.
6
+
7
+ Repeat `--image <path|url>` (up to 8), paired with `--image-alt`; all reach the
8
+ gallery. Reference any or all in Body with Markdown image syntax:
9
+ `- Bob: ![front](media:1) ![side](media:2)`. Bare `media:1`, `[…](media:1)`,
10
+ or a position with no `--image` is an error.
11
+
12
+ `--sound`, `--level`, and `--device` belong to the User: never choose them
13
+ yourself. `--sound` takes a shipped name or a custom name/id from
14
+ `notifai sounds`.