@raidiant/notifai 7.0.1 → 8.0.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 (75) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/dist/commands-devices.d.ts +2 -1
  3. package/dist/commands-devices.js +20 -8
  4. package/dist/commands-devices.js.map +1 -1
  5. package/dist/commands-doctor.js +14 -12
  6. package/dist/commands-doctor.js.map +1 -1
  7. package/dist/commands-guidance.d.ts +23 -0
  8. package/dist/commands-guidance.js +128 -0
  9. package/dist/commands-guidance.js.map +1 -0
  10. package/dist/commands-hooks.d.ts +7 -1
  11. package/dist/commands-hooks.js +3 -6
  12. package/dist/commands-hooks.js.map +1 -1
  13. package/dist/commands-init.js +25 -22
  14. package/dist/commands-init.js.map +1 -1
  15. package/dist/commands-send-support.js +5 -3
  16. package/dist/commands-send-support.js.map +1 -1
  17. package/dist/commands-send.d.ts +0 -1
  18. package/dist/commands-send.js +45 -28
  19. package/dist/commands-send.js.map +1 -1
  20. package/dist/commands.d.ts +1 -0
  21. package/dist/commands.js +1 -0
  22. package/dist/commands.js.map +1 -1
  23. package/dist/config-schema.js +6 -15
  24. package/dist/config-schema.js.map +1 -1
  25. package/dist/config.d.ts +1 -3
  26. package/dist/config.js +0 -3
  27. package/dist/config.js.map +1 -1
  28. package/dist/guidance-content.d.ts +30 -0
  29. package/dist/guidance-content.js +175 -0
  30. package/dist/guidance-content.js.map +1 -0
  31. package/dist/guidance.d.ts +48 -0
  32. package/dist/guidance.js +118 -0
  33. package/dist/guidance.js.map +1 -0
  34. package/dist/hooks.js +7 -7
  35. package/dist/hooks.js.map +1 -1
  36. package/dist/interactive.d.ts +1 -1
  37. package/dist/interactive.js +13 -8
  38. package/dist/interactive.js.map +1 -1
  39. package/dist/main.js +3 -474
  40. package/dist/main.js.map +1 -1
  41. package/dist/program.d.ts +49 -0
  42. package/dist/program.js +571 -0
  43. package/dist/program.js.map +1 -0
  44. package/dist/send.d.ts +4 -9
  45. package/dist/send.js +48 -28
  46. package/dist/send.js.map +1 -1
  47. package/dist/session-labels.d.ts +9 -3
  48. package/dist/session-labels.js +38 -7
  49. package/dist/session-labels.js.map +1 -1
  50. package/package.json +5 -3
  51. package/src/commands-devices.ts +28 -9
  52. package/src/commands-doctor.ts +17 -12
  53. package/src/commands-guidance.ts +170 -0
  54. package/src/commands-hooks.ts +10 -8
  55. package/src/commands-init.ts +31 -22
  56. package/src/commands-send-support.ts +5 -3
  57. package/src/commands-send.ts +50 -28
  58. package/src/commands.test.ts +374 -63
  59. package/src/commands.ts +5 -0
  60. package/src/config-schema.ts +6 -17
  61. package/src/config.test.ts +56 -1
  62. package/src/config.ts +0 -5
  63. package/src/guidance-content.ts +190 -0
  64. package/src/guidance.test.ts +268 -0
  65. package/src/guidance.ts +151 -0
  66. package/src/hooks.test.ts +37 -5
  67. package/src/hooks.ts +7 -7
  68. package/src/interactive.ts +13 -8
  69. package/src/main.ts +4 -600
  70. package/src/program.test.ts +202 -0
  71. package/src/program.ts +723 -0
  72. package/src/send.ts +53 -32
  73. package/src/session-labels.test.ts +120 -3
  74. package/src/session-labels.ts +38 -7
  75. package/src/skill.test.ts +45 -8
@@ -0,0 +1,175 @@
1
+ /**
2
+ * The shipped notification-writing guidance: what agents follow when the user
3
+ * has not said otherwise.
4
+ *
5
+ * Each topic is a small Markdown document named for the moment an agent
6
+ * consults it. Users override a topic by placing a file with the same name in
7
+ * a guidance directory — resolution and layering live in `guidance.ts`, and
8
+ * the topics ship here as source rather than as packaged files so the packed
9
+ * CLI cannot lose them.
10
+ *
11
+ * The governing principle, stated once: the user hired the outcome, not the
12
+ * pipeline. Tests, coverage, review passes, hashes and exit codes are how the
13
+ * work got done; a notification carries what the work means — what is now
14
+ * finished, what went wrong, what needs them. A detail earns its place only
15
+ * when it changes what the user does next.
16
+ */
17
+ const WHEN_TO_NOTIFY = `# When to notify
18
+
19
+ Notify when something changed for the user:
20
+
21
+ - Long-running work finished — succeeded or failed.
22
+ - You are blocked on something only they can give: a key, an approval, a
23
+ decision.
24
+ - You found something that needs their attention soon.
25
+
26
+ Never notify for:
27
+
28
+ - Routine progress: starting, still working, one more subtask done.
29
+ - Anything they will see in the terminal within seconds.
30
+ - A problem you hit and fixed yourself.
31
+
32
+ One notification per event. When a status changes, replace your stale
33
+ notification rather than stacking a new one.
34
+ `;
35
+ const TITLES = `# Titles
36
+
37
+ The user hired the outcome, not the pipeline. A title says what the work means
38
+ to them — what is now finished, what went wrong, what needs them — in about 40
39
+ characters, understandable alone. Never put the kind or the project in it;
40
+ both travel as their own fields.
41
+
42
+ Good:
43
+
44
+ - \`Users can now create accounts\` — the capability, not the process behind it
45
+ - \`Password reset emails aren't sending\` — the failure as the user experiences it
46
+ - \`Refund flow needs your Stripe key\` — what is stuck and what unblocks it
47
+ - \`Found why checkout was flaky\` — the finding they were waiting on
48
+ - \`3 orders didn't import and need review\` — a count that earns its place
49
+ because acting on it is theirs to do
50
+
51
+ Bad:
52
+
53
+ - \`All 42 tests passed\` — machinery; say what work is done
54
+ - \`Adversarial review found 3 issues\` — process report; say what it means for
55
+ what ships
56
+ - \`Migration 0007 failed\` — internal identifier; say what the user lost
57
+ - \`Task complete\` / \`Build failed\` / \`Need input\` — no substance, and the
58
+ kind restated
59
+ - \`Exit code 1\` — mechanics, not meaning
60
+ `;
61
+ const BODIES = `# Bodies
62
+
63
+ The first sentence is what the lock screen shows — make it say what this means
64
+ for the user. The rest is what changes for them and what is needed from them,
65
+ never how many tests ran, how long a step took, or which internal stage
66
+ produced it. Keep wording channel-neutral: no device names, no gestures.
67
+
68
+ Work finished:
69
+
70
+ > Account creation works end to end now: sign-up, email verification, and
71
+ > login are live on staging.
72
+ >
73
+ > Next I'll start on password reset unless you want something else first.
74
+
75
+ Work failed:
76
+
77
+ > The new pricing page isn't live. The deploy failed and I rolled it back, so
78
+ > the site still shows the old page — nothing is broken for users.
79
+ >
80
+ > The blocker is on my side and I'm fixing it; I'll retry and confirm when the
81
+ > page is up. Nothing needed from you.
82
+
83
+ Blocked:
84
+
85
+ > The refund flow is built and working against the test environment. Going
86
+ > live needs the Stripe key that only you can issue.
87
+ >
88
+ > That's the last open item — everything else is done.
89
+
90
+ Bad bodies: a build report (\`42/42 green, coverage 87%, rebased onto main\`)
91
+ where the news is "users can now create accounts"; the journey ("first I
92
+ tried…") instead of the result; a log with the conclusion at the bottom; "see
93
+ terminal for details".
94
+
95
+ Add a summary line only when the body is long enough that its first line is
96
+ not a fair summary of what is inside: one short line answering what the title
97
+ raises, such as \`Rolled back cleanly; production is untouched\` over a long
98
+ failure report.
99
+ `;
100
+ const QUESTIONS = `# Questions
101
+
102
+ A question is answerable from the notification alone, in the user's terms, not
103
+ the machinery's. One askable sentence; reasoning and stakes follow as context.
104
+ Offer closed choices whose wording carries its own consequence.
105
+
106
+ Good:
107
+
108
+ - \`The schema change is ready. It touches live order data — deploy now or
109
+ wait for off-peak?\` with choices \`Deploy now\` / \`Wait for off-peak\`
110
+ - \`The API key in .env.example is live. Revoke it now, or wait until the
111
+ replacement is provisioned?\` with \`Revoke now\` / \`Wait for the new key\`
112
+
113
+ Bad:
114
+
115
+ - \`What should I do?\` — nothing to answer without the terminal
116
+ - \`Deploy to staging and notify the team?\` with \`Yes\` / \`No\` — two
117
+ decisions, one answer
118
+ - \`Retry with --force-with-lease?\` — machinery; the real decision is
119
+ \`Overwrite the remote branch, or keep both versions?\`
120
+ - Choices \`Option A\` / \`Option B\` — labels that point back into the body
121
+ - \`Reply here with your choice\` — never name where the answer must arrive
122
+ `;
123
+ const ACKNOWLEDGEMENTS = `# Acknowledgements
124
+
125
+ An acknowledgement names the concrete work the reply sets in motion — only
126
+ work you will actually do, and nothing generic.
127
+
128
+ Good:
129
+
130
+ - \`Rolling out to staging now; I'll report the health checks.\`
131
+ - \`Holding the deploy. I'll re-raise it once staging is green.\`
132
+ - \`Shipping the fix; I'll confirm when password reset works again.\`
133
+
134
+ Bad:
135
+
136
+ - \`Acknowledged.\` / \`Got it!\` — a receipt that shows nothing was understood
137
+ - \`You chose "Deploy now".\` — echoes the reply instead of naming the work
138
+ - \`Deploying now, and I'll also refactor the retry logic.\` — promises work
139
+ the reply did not cause
140
+ `;
141
+ /**
142
+ * Topic order is reading order: decide first, then each thing an agent writes,
143
+ * in the order it writes them.
144
+ */
145
+ export const SHIPPED_GUIDANCE = [
146
+ {
147
+ name: 'when-to-notify',
148
+ summary: 'What is worth a notification at all',
149
+ content: WHEN_TO_NOTIFY,
150
+ },
151
+ {
152
+ name: 'titles',
153
+ summary: 'What a title carries, with examples',
154
+ content: TITLES,
155
+ },
156
+ {
157
+ name: 'bodies',
158
+ summary: 'What a body and its summary line carry, with examples',
159
+ content: BODIES,
160
+ },
161
+ {
162
+ name: 'questions',
163
+ summary: 'How a question and its choices are worded',
164
+ content: QUESTIONS,
165
+ },
166
+ {
167
+ name: 'acknowledgements',
168
+ summary: 'How an acknowledgement is worded',
169
+ content: ACKNOWLEDGEMENTS,
170
+ },
171
+ ];
172
+ export function shippedGuidanceTopic(name) {
173
+ return SHIPPED_GUIDANCE.find((topic) => topic.name === name);
174
+ }
175
+ //# sourceMappingURL=guidance-content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guidance-content.js","sourceRoot":"","sources":["../src/guidance-content.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAWH,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;CAiBtB,CAAA;AAED,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;CAyBd,CAAA;AAED,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsCd,CAAA;AAED,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;;;;;CAsBjB,CAAA;AAED,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;CAiBxB,CAAA;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAA6B;IACxD;QACE,IAAI,EAAE,gBAAgB;QACtB,OAAO,EAAE,qCAAqC;QAC9C,OAAO,EAAE,cAAc;KACxB;IACD;QACE,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,qCAAqC;QAC9C,OAAO,EAAE,MAAM;KAChB;IACD;QACE,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,uDAAuD;QAChE,OAAO,EAAE,MAAM;KAChB;IACD;QACE,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,2CAA2C;QACpD,OAAO,EAAE,SAAS;KACnB;IACD;QACE,IAAI,EAAE,kBAAkB;QACxB,OAAO,EAAE,kCAAkC;QAC3C,OAAO,EAAE,gBAAgB;KAC1B;CACF,CAAA;AAED,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;AAC9D,CAAC"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Layered notification-writing guidance with provenance. Most specific wins,
3
+ * per topic, exactly the discipline config keys follow:
4
+ *
5
+ * project-local > project > global > shipped default
6
+ *
7
+ * A topic is one Markdown file named `<topic>.md`. A user file replaces the
8
+ * shipped topic of the same name wholesale — their words outrank the default,
9
+ * not annotate it — and a file whose name matches no shipped topic is purely
10
+ * additive house rules. There is no session layer: an instruction given in
11
+ * conversation tunes the session by being followed, not persisted.
12
+ */
13
+ export type GuidanceSource = `project-local:${string}` | `project:${string}` | `global:${string}` | 'default';
14
+ export interface ResolvedGuidanceTopic {
15
+ name: string;
16
+ source: GuidanceSource;
17
+ /** One line for lists; user-authored topics describe themselves by content. */
18
+ summary: string;
19
+ content: string;
20
+ }
21
+ /**
22
+ * Topic names double as filenames, and the project layer arrives from a
23
+ * repository — untrusted input. Anything that does not match is not a topic,
24
+ * the same way `coerce` drops an unrecognised config value.
25
+ */
26
+ export declare const GUIDANCE_TOPIC_PATTERN: RegExp;
27
+ /**
28
+ * One topic is capped at the body limit. Guidance is read into agent context
29
+ * on every session, and a repository could otherwise feed it without bound.
30
+ */
31
+ export declare const GUIDANCE_TOPIC_MAX_BYTES = 16000;
32
+ export declare function globalGuidanceDir(env?: NodeJS.ProcessEnv): string;
33
+ /** Walk up from cwd looking for `.notifai/guidance` (shared, committed). */
34
+ export declare function findProjectGuidanceDir(startDir: string): string | null;
35
+ /**
36
+ * Personal project guidance lives beside the personal project config file,
37
+ * under the user's config home rather than the repository:
38
+ * `$XDG_CONFIG_HOME/notifai/projects/<identity>/guidance`.
39
+ */
40
+ export declare function personalProjectGuidanceDir(cwd: string, env?: NodeJS.ProcessEnv): string;
41
+ /**
42
+ * Every effective topic, shipped reading order first, additive user topics
43
+ * after it in name order. Each carries the layer that supplied it.
44
+ */
45
+ export declare function resolveGuidance(options: {
46
+ cwd?: string;
47
+ env?: NodeJS.ProcessEnv;
48
+ }): ResolvedGuidanceTopic[];
@@ -0,0 +1,118 @@
1
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { globalConfigDir, personalProjectIdentity } from './config.js';
4
+ import { SHIPPED_GUIDANCE } from './guidance-content.js';
5
+ /**
6
+ * Topic names double as filenames, and the project layer arrives from a
7
+ * repository — untrusted input. Anything that does not match is not a topic,
8
+ * the same way `coerce` drops an unrecognised config value.
9
+ */
10
+ export const GUIDANCE_TOPIC_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/;
11
+ /**
12
+ * One topic is capped at the body limit. Guidance is read into agent context
13
+ * on every session, and a repository could otherwise feed it without bound.
14
+ */
15
+ export const GUIDANCE_TOPIC_MAX_BYTES = 16_000;
16
+ export function globalGuidanceDir(env = process.env) {
17
+ return path.join(globalConfigDir(env), 'guidance');
18
+ }
19
+ /** Walk up from cwd looking for `.notifai/guidance` (shared, committed). */
20
+ export function findProjectGuidanceDir(startDir) {
21
+ let dir = path.resolve(startDir);
22
+ for (;;) {
23
+ const candidate = path.join(dir, '.notifai', 'guidance');
24
+ if (existsSync(candidate))
25
+ return candidate;
26
+ const parent = path.dirname(dir);
27
+ if (parent === dir)
28
+ return null;
29
+ dir = parent;
30
+ }
31
+ }
32
+ /**
33
+ * Personal project guidance lives beside the personal project config file,
34
+ * under the user's config home rather than the repository:
35
+ * `$XDG_CONFIG_HOME/notifai/projects/<identity>/guidance`.
36
+ */
37
+ export function personalProjectGuidanceDir(cwd, env = process.env) {
38
+ return path.join(globalConfigDir(env), 'projects', personalProjectIdentity(cwd), 'guidance');
39
+ }
40
+ function topicsInDir(dir) {
41
+ const topics = new Map();
42
+ if (!existsSync(dir))
43
+ return topics;
44
+ let entries;
45
+ try {
46
+ entries = readdirSync(dir);
47
+ }
48
+ catch {
49
+ return topics;
50
+ }
51
+ for (const entry of entries.sort()) {
52
+ if (!entry.endsWith('.md'))
53
+ continue;
54
+ const name = entry.slice(0, -'.md'.length);
55
+ if (!GUIDANCE_TOPIC_PATTERN.test(name))
56
+ continue;
57
+ topics.set(name, path.join(dir, entry));
58
+ }
59
+ return topics;
60
+ }
61
+ function readTopicFile(filePath) {
62
+ let content;
63
+ try {
64
+ content = readFileSync(filePath, 'utf8');
65
+ }
66
+ catch {
67
+ return null;
68
+ }
69
+ if (content.trim() === '')
70
+ return null;
71
+ if (content.length > GUIDANCE_TOPIC_MAX_BYTES) {
72
+ return `${content.slice(0, GUIDANCE_TOPIC_MAX_BYTES)}\n\n[Truncated: this guidance file exceeds ${GUIDANCE_TOPIC_MAX_BYTES} bytes.]\n`;
73
+ }
74
+ return content;
75
+ }
76
+ /**
77
+ * Every effective topic, shipped reading order first, additive user topics
78
+ * after it in name order. Each carries the layer that supplied it.
79
+ */
80
+ export function resolveGuidance(options) {
81
+ const env = options.env ?? process.env;
82
+ const cwd = options.cwd ?? process.cwd();
83
+ // Ascending precedence: a later layer overwrites an earlier one per topic.
84
+ const layers = [
85
+ { dir: globalGuidanceDir(env), label: 'global' },
86
+ { dir: findProjectGuidanceDir(cwd), label: 'project' },
87
+ { dir: personalProjectGuidanceDir(cwd, env), label: 'project-local' },
88
+ ];
89
+ const winners = new Map();
90
+ for (const layer of layers) {
91
+ if (layer.dir === null)
92
+ continue;
93
+ for (const [name, filePath] of topicsInDir(layer.dir)) {
94
+ const content = readTopicFile(filePath);
95
+ if (content === null)
96
+ continue;
97
+ winners.set(name, { source: `${layer.label}:${filePath}`, content });
98
+ }
99
+ }
100
+ const resolved = SHIPPED_GUIDANCE.map((topic) => {
101
+ const override = winners.get(topic.name);
102
+ winners.delete(topic.name);
103
+ return override === undefined
104
+ ? { name: topic.name, source: 'default', summary: topic.summary, content: topic.content }
105
+ : { name: topic.name, source: override.source, summary: topic.summary, content: override.content };
106
+ });
107
+ for (const name of [...winners.keys()].sort()) {
108
+ const extra = winners.get(name);
109
+ resolved.push({
110
+ name,
111
+ source: extra.source,
112
+ summary: 'User-added guidance',
113
+ content: extra.content,
114
+ });
115
+ }
116
+ return resolved;
117
+ }
118
+ //# sourceMappingURL=guidance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guidance.js","sourceRoot":"","sources":["../src/guidance.ts"],"names":[],"mappings":"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"}
package/dist/hooks.js CHANGED
@@ -331,8 +331,9 @@ function claimPath(sessionId, env) {
331
331
  /**
332
332
  * A question is stored so a later hook can push it, and it reaches us from a
333
333
  * shell command, so its size is whatever the agent typed. The push itself is
334
- * bounded by the 4096-byte APNs envelope; this bounds what sits on disk in the
335
- * meantime, and keeps one runaway agent from writing megabytes per session.
334
+ * bounded by the current 4096-byte push-provider envelopes; this bounds what
335
+ * sits on disk in the meantime, and keeps one runaway agent from writing
336
+ * megabytes per session.
336
337
  */
337
338
  const MAX_STORED_QUESTION_CHARS = 2000;
338
339
  function storedProjectSessionPointer(candidate) {
@@ -623,15 +624,14 @@ function permanentReplyFailureNote(failures) {
623
624
  const details = [...failures].map(([requestId, failure]) => `${requestId}: ${failure}`).join('; ');
624
625
  return `reply polling stopped after a permanent server rejection (${details}); the affected question will be retired before its continuation owner exits`;
625
626
  }
626
- /**
627
- * Healthy companion devices that implement replies. Both the iOS app and the
628
- * macOS app register reply categories and submit answers directly.
629
- */
627
+ /** Healthy Companion devices that advertise the current answer job. */
630
628
  async function answerableDevices(ctx) {
631
629
  const configured = ctx.config.devices.value;
632
630
  const { devices } = await ctx.client.listDevices();
633
631
  return devices
634
- .filter((device) => (device.platform === 'ios' || device.platform === 'macos') &&
632
+ .filter((device) => (device.platform === 'ios' ||
633
+ device.platform === 'macos' ||
634
+ device.platform === 'android') &&
635
635
  device.registration_healthy &&
636
636
  device.capabilities?.includes('answer') === true &&
637
637
  device.derived_status !== 'must_update')