@raidiant/notifai 9.2.1 → 9.3.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.
- package/CHANGELOG.md +33 -0
- package/README.md +8 -5
- package/dist/claude-wake.js +11 -0
- package/dist/claude-wake.js.map +1 -1
- package/dist/codex-wake.js +9 -0
- package/dist/codex-wake.js.map +1 -1
- package/dist/commands-core.d.ts +0 -3
- package/dist/commands-core.js.map +1 -1
- package/dist/commands-doctor.js +41 -2
- package/dist/commands-doctor.js.map +1 -1
- package/dist/commands-harness-context.d.ts +2 -2
- package/dist/commands-harness-context.js +1 -1
- package/dist/commands-hook-shape.d.ts +4 -3
- package/dist/commands-hook-shape.js +19 -10
- package/dist/commands-hook-shape.js.map +1 -1
- package/dist/commands-hooks.js +66 -15
- package/dist/commands-hooks.js.map +1 -1
- package/dist/commands-init.js +12 -1
- package/dist/commands-init.js.map +1 -1
- package/dist/commands-project.d.ts +4 -0
- package/dist/commands-project.js +41 -0
- package/dist/commands-project.js.map +1 -0
- package/dist/commands-send-support.js +1 -22
- package/dist/commands-send-support.js.map +1 -1
- package/dist/commands-send.js +11 -5
- package/dist/commands-send.js.map +1 -1
- package/dist/commands-skill.js +58 -0
- package/dist/commands-skill.js.map +1 -1
- package/dist/commands.d.ts +1 -0
- package/dist/commands.js +1 -0
- package/dist/commands.js.map +1 -1
- package/dist/config-schema.js +2 -2
- package/dist/config-schema.js.map +1 -1
- package/dist/config.d.ts +8 -8
- package/dist/config.js +6 -9
- package/dist/config.js.map +1 -1
- package/dist/guidance-content.d.ts +3 -0
- package/dist/guidance-content.js +20 -0
- package/dist/guidance-content.js.map +1 -1
- package/dist/guidance-render.js +3 -3
- package/dist/guidance-render.js.map +1 -1
- package/dist/harnesses.js +2 -2
- package/dist/harnesses.js.map +1 -1
- package/dist/hooks.d.ts +26 -50
- package/dist/hooks.js +391 -150
- package/dist/hooks.js.map +1 -1
- package/dist/install-hooks.d.ts +7 -28
- package/dist/install-hooks.js +18 -37
- package/dist/install-hooks.js.map +1 -1
- package/dist/invocation-context.d.ts +1 -1
- package/dist/invocation-context.js +2 -2
- package/dist/invocation-context.js.map +1 -1
- package/dist/program.d.ts +4 -1
- package/dist/program.js +33 -15
- package/dist/program.js.map +1 -1
- package/dist/project-enablement.d.ts +15 -0
- package/dist/project-enablement.js +87 -0
- package/dist/project-enablement.js.map +1 -0
- package/dist/question-timing.d.ts +46 -0
- package/dist/question-timing.js +48 -0
- package/dist/question-timing.js.map +1 -0
- package/dist/readiness.d.ts +7 -0
- package/dist/readiness.js.map +1 -1
- package/dist/send.d.ts +4 -2
- package/dist/send.js +6 -1
- package/dist/send.js.map +1 -1
- package/dist/session-activation.d.ts +1 -1
- package/dist/session-activation.js +3 -4
- package/dist/session-activation.js.map +1 -1
- package/package.json +2 -2
- package/src/claude-wake.test.ts +1 -0
- package/src/claude-wake.ts +10 -0
- package/src/codex-wake.test.ts +1 -0
- package/src/codex-wake.ts +9 -0
- package/src/commands-core.ts +0 -3
- package/src/commands-doctor.ts +48 -4
- package/src/commands-harness-context.ts +2 -2
- package/src/commands-hook-shape.ts +26 -10
- package/src/commands-hooks.ts +62 -16
- package/src/commands-init.ts +11 -1
- package/src/commands-project.ts +47 -0
- package/src/commands-send-support.ts +1 -27
- package/src/commands-send.ts +9 -4
- package/src/commands-skill.test.ts +44 -0
- package/src/commands-skill.ts +57 -0
- package/src/commands.test.ts +47 -17
- package/src/commands.ts +5 -0
- package/src/config-schema.ts +2 -2
- package/src/config.test.ts +2 -3
- package/src/config.ts +12 -15
- package/src/guidance-content.ts +22 -0
- package/src/guidance-render.ts +3 -3
- package/src/harnesses.ts +2 -2
- package/src/hook-adapter.test.ts +27 -8
- package/src/hooks.test.ts +808 -80
- package/src/hooks.ts +525 -181
- package/src/install-hooks.test.ts +8 -12
- package/src/install-hooks.ts +21 -37
- package/src/invocation-context.test.ts +1 -1
- package/src/invocation-context.ts +2 -2
- package/src/program.test.ts +18 -0
- package/src/program.ts +36 -14
- package/src/project-enablement.test.ts +55 -0
- package/src/project-enablement.ts +99 -0
- package/src/question-timing.ts +56 -0
- package/src/readiness.ts +8 -1
- package/src/send.ts +10 -3
- package/src/session-activation.ts +3 -4
- package/src/skill.test.ts +2 -2
- package/dist/orca-session-title.d.ts +0 -10
- package/dist/orca-session-title.js +0 -99
- package/dist/orca-session-title.js.map +0 -1
- package/src/orca-session-title.test.ts +0 -95
- package/src/orca-session-title.ts +0 -105
package/src/config.test.ts
CHANGED
|
@@ -141,10 +141,9 @@ describe('harness config layers', () => {
|
|
|
141
141
|
expect(config.ask_grace_seconds).toEqual({ value: 0, source: 'default' })
|
|
142
142
|
})
|
|
143
143
|
|
|
144
|
-
it('bounds the terminal-first window
|
|
144
|
+
it('bounds the terminal-first window to the supported preference', () => {
|
|
145
145
|
// Config is readable from a repository, so an unbounded grace is hostile
|
|
146
|
-
// input
|
|
147
|
-
// server would accept an answer into.
|
|
146
|
+
// input rather than permission to hold question delivery arbitrarily.
|
|
148
147
|
const { env, cwd } = setup({ globalToml: 'ask_grace_seconds = 100000\n' })
|
|
149
148
|
expect(loadConfig({ cwd, env }).ask_grace_seconds.value).toBe(360)
|
|
150
149
|
})
|
package/src/config.ts
CHANGED
|
@@ -21,8 +21,8 @@ import { normalizeOrigin } from './url-policy.js'
|
|
|
21
21
|
* preference that evaporates mid-session is worse than none.
|
|
22
22
|
*
|
|
23
23
|
* `project-local` is a personal file under the user's config directory, keyed
|
|
24
|
-
* by the
|
|
25
|
-
* `.notifai/config.toml` stays in the tree for committed
|
|
24
|
+
* by the Project root so a checkout does not have to gitignore anything. Shared
|
|
25
|
+
* `.notifai/config.toml` stays in the tree for committed Project identity.
|
|
26
26
|
*
|
|
27
27
|
* `config show --explain` surfaces the winning layer per key.
|
|
28
28
|
*/
|
|
@@ -70,8 +70,8 @@ export interface CliConfig {
|
|
|
70
70
|
* elapses does the question reach companion devices.
|
|
71
71
|
*
|
|
72
72
|
* A timer, and only a timer. Whether the user happens to be at this keyboard
|
|
73
|
-
* decides nothing
|
|
74
|
-
*
|
|
73
|
+
* decides nothing; the harness-specific owner continues waiting either in
|
|
74
|
+
* the held turn or out of band.
|
|
75
75
|
*/
|
|
76
76
|
ask_grace_seconds: ResolvedValue<number>
|
|
77
77
|
/**
|
|
@@ -90,7 +90,7 @@ export interface CliConfig {
|
|
|
90
90
|
* usefulness.
|
|
91
91
|
*/
|
|
92
92
|
log_max_bytes: ResolvedValue<number>
|
|
93
|
-
/** How long the server
|
|
93
|
+
/** How long the server accepts an answer and Question Routing owns its return path. */
|
|
94
94
|
reply_window_seconds: ResolvedValue<number>
|
|
95
95
|
/** Log files kept in total, the active one included. */
|
|
96
96
|
log_max_files: ResolvedValue<number>
|
|
@@ -245,9 +245,9 @@ export function sanitizeSessionId(sessionId: string): string {
|
|
|
245
245
|
}
|
|
246
246
|
|
|
247
247
|
/**
|
|
248
|
-
* Pointer from a
|
|
248
|
+
* Pointer from a Project directory to the Agent Session currently working in
|
|
249
249
|
* it, written by the UserPromptSubmit hook. Without it `notifai ask` has no way
|
|
250
|
-
* to learn its own
|
|
250
|
+
* to learn its own Agent Session id: an ordinary agent shell command receives no hook
|
|
251
251
|
* payload, and no harness exports one into the environment.
|
|
252
252
|
*/
|
|
253
253
|
export function projectSessionPointerPath(cwd: string, env: NodeJS.ProcessEnv = process.env): string {
|
|
@@ -330,17 +330,14 @@ export function findProjectLocalConfigPath(
|
|
|
330
330
|
|
|
331
331
|
/**
|
|
332
332
|
* Ranges every layer is clamped to. Config is readable from a repository, so an
|
|
333
|
-
* out-of-range value is attacker input, not a typo
|
|
334
|
-
* would consume the waiter's whole ceiling and silently leave no time at all in
|
|
335
|
-
* which the user's answer could still be accepted.
|
|
333
|
+
* out-of-range value is attacker input, not a typo.
|
|
336
334
|
*/
|
|
337
335
|
const NUMERIC_BOUNDS: Partial<Record<ConfigKey, { min: number; max: number }>> = {
|
|
338
336
|
wait_seconds: { min: 0, max: 300 },
|
|
339
337
|
ttl_seconds: { min: 0, max: 7 * 24 * 3600 },
|
|
340
|
-
//
|
|
341
|
-
//
|
|
342
|
-
//
|
|
343
|
-
// will accept — with room to spare for a timer that wakes late.
|
|
338
|
+
// Six minutes is the supported terminal-first preference bound. The Stop
|
|
339
|
+
// owner carries separate startup headroom, so this never shortens the answer
|
|
340
|
+
// window that begins after submission.
|
|
344
341
|
ask_grace_seconds: { min: 0, max: 360 },
|
|
345
342
|
// The floor is the shortest window the wire contract accepts; the ceiling is
|
|
346
343
|
// the point past which a question would outlive the retained content it
|
|
@@ -420,7 +417,7 @@ export function loadConfig(options: {
|
|
|
420
417
|
cwd?: string
|
|
421
418
|
env?: NodeJS.ProcessEnv
|
|
422
419
|
flags?: FlagOverrides
|
|
423
|
-
/**
|
|
420
|
+
/** Agent Session id; enables the Agent Session override layer when present. */
|
|
424
421
|
sessionId?: string | undefined
|
|
425
422
|
}): CliConfig {
|
|
426
423
|
const env = options.env ?? process.env
|
package/src/guidance-content.ts
CHANGED
|
@@ -24,6 +24,28 @@ export interface GuidanceTopic {
|
|
|
24
24
|
content: string
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/** Canonical lifecycle vocabulary, always emitted from trusted CLI source. */
|
|
28
|
+
export const AGENT_TERMS_PREAMBLE = `# Notifai terms
|
|
29
|
+
|
|
30
|
+
An **Agent Event** is something that happened in the agent's work: completion,
|
|
31
|
+
failure, a blocker, a question that needs the User, or another meaningful
|
|
32
|
+
change. It stays inside the agent team until the Notification Request owner
|
|
33
|
+
decides it should reach the User.
|
|
34
|
+
|
|
35
|
+
A **Notification Request** is the deliberate User-visible message or question
|
|
36
|
+
submitted through Notifai about an Agent Event. It is not every event and it
|
|
37
|
+
is not an internal worker report.
|
|
38
|
+
|
|
39
|
+
The root agent owns Notification Requests by default. Ordinary workers report
|
|
40
|
+
Agent Events to their parent and do not notify the User themselves. Explicit
|
|
41
|
+
delegation transfers that ownership for the delegated work.`
|
|
42
|
+
|
|
43
|
+
export const AGENT_TERMS_FALLBACK = `# Notifai terms
|
|
44
|
+
|
|
45
|
+
An **Agent Event** is a meaningful occurrence in agent work. A **Notification
|
|
46
|
+
Request** is a deliberate User-visible message about one. Root owns requests;
|
|
47
|
+
workers report events unless ownership is explicitly delegated.`
|
|
48
|
+
|
|
27
49
|
const WHEN_TO_NOTIFY = `# When to notify
|
|
28
50
|
|
|
29
51
|
Notify when something changed for the user:
|
package/src/guidance-render.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer'
|
|
2
2
|
import { describeSource } from './config-schema.js'
|
|
3
|
-
import { GUIDANCE_TRUST_PREAMBLE } from './guidance-content.js'
|
|
3
|
+
import { AGENT_TERMS_FALLBACK, AGENT_TERMS_PREAMBLE, GUIDANCE_TRUST_PREAMBLE } from './guidance-content.js'
|
|
4
4
|
import { resolveGuidance, type GuidanceAuthority, type ResolvedGuidanceTopic } from './guidance.js'
|
|
5
5
|
|
|
6
6
|
const AUTHORITY_LABEL: Record<GuidanceAuthority, string> = {
|
|
@@ -24,7 +24,7 @@ export function renderGuidance(topics: readonly ResolvedGuidanceTopic[]): string
|
|
|
24
24
|
`${filePath === null ? '' : ` file=${encodeURIComponent(filePath)}`} -->`
|
|
25
25
|
return `${marker}\n${markerSafe(topic.content).trimEnd()}`
|
|
26
26
|
})
|
|
27
|
-
return [GUIDANCE_TRUST_PREAMBLE, ...blocks].join('\n\n')
|
|
27
|
+
return [GUIDANCE_TRUST_PREAMBLE, AGENT_TERMS_PREAMBLE, ...blocks].join('\n\n')
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
/** Hard ceiling for model-visible lifecycle context, including repository topics. */
|
|
@@ -48,7 +48,7 @@ export function boundedEffectiveGuidance(options: {
|
|
|
48
48
|
bytes,
|
|
49
49
|
max_bytes: maxBytes,
|
|
50
50
|
fallback:
|
|
51
|
-
`${GUIDANCE_TRUST_PREAMBLE}\n\n` +
|
|
51
|
+
`${GUIDANCE_TRUST_PREAMBLE}\n\n${AGENT_TERMS_FALLBACK}\n\n` +
|
|
52
52
|
`Notifai guidance is ${bytes} bytes, above the ${maxBytes}-byte lifecycle limit. ` +
|
|
53
53
|
'Before deciding whether or how to notify, run `notifai guidance` once in this session. ' +
|
|
54
54
|
'Do not infer or partially follow the omitted topics.',
|
package/src/harnesses.ts
CHANGED
|
@@ -38,13 +38,13 @@ export const HARNESS_CAPABILITIES: Record<Harness, HarnessCapability> = {
|
|
|
38
38
|
stopContinuation: 'decision-block',
|
|
39
39
|
deliveryRoutes: ['hook-continuation', 'inbox-socket', 'cold-resume', 'hold-for-next-turn'],
|
|
40
40
|
deliveryContract:
|
|
41
|
-
'the Stop hook returns at once and waits out of band, then posts the answer into this same session over its own inbox socket; a session that has stopped is resumed only once a liveness probe proves it stopped',
|
|
41
|
+
'the Stop hook returns at once and waits out of band through the complete answer window, then posts the answer into this same session over its own inbox socket; a session that has stopped is resumed only once a liveness probe proves it stopped',
|
|
42
42
|
},
|
|
43
43
|
codex: {
|
|
44
44
|
stopContinuation: 'decision-block',
|
|
45
45
|
deliveryRoutes: ['hook-continuation', 'cold-resume', 'hold-for-next-turn'],
|
|
46
46
|
deliveryContract:
|
|
47
|
-
'live Stop continuation while the turn is held;
|
|
47
|
+
'live Stop continuation while the turn is held through the complete answer window; crash recovery may resume only a stopped thread behind its writer lock',
|
|
48
48
|
},
|
|
49
49
|
cursor: {
|
|
50
50
|
stopContinuation: 'unsupported',
|
package/src/hook-adapter.test.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
isNpxAdapterTarget,
|
|
22
22
|
} from './hook-adapter.js'
|
|
23
23
|
import { buildHookConfig, codexHookIdentityHash, type InstalledHandler } from './install-hooks.js'
|
|
24
|
+
import { enableProject, projectBinding } from './project-enablement.js'
|
|
24
25
|
|
|
25
26
|
function isolated(): { root: string; homeDir: string } {
|
|
26
27
|
const root = mkdtempSync(path.join(os.tmpdir(), 'notifai-hook-adapter-'))
|
|
@@ -28,22 +29,40 @@ function isolated(): { root: string; homeDir: string } {
|
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
describe('stable hook adapter', () => {
|
|
31
|
-
it('
|
|
32
|
+
it('keeps the installed adapter silent until the Project is enabled', () => {
|
|
32
33
|
const { root, homeDir } = isolated()
|
|
34
|
+
const env = {
|
|
35
|
+
...process.env,
|
|
36
|
+
HOME: homeDir,
|
|
37
|
+
XDG_CONFIG_HOME: path.join(root, 'config'),
|
|
38
|
+
XDG_STATE_HOME: path.join(root, 'state'),
|
|
39
|
+
}
|
|
33
40
|
const scriptPath = fileURLToPath(new URL('../dist/main.js', import.meta.url))
|
|
34
41
|
const installed = installHookAdapter({ execPath: process.execPath, scriptPath }, homeDir)
|
|
42
|
+
const disabled = spawnSync(
|
|
43
|
+
installed.path,
|
|
44
|
+
['hook', 'session-start', '--owner', 'notifai', '--harness', 'codex'],
|
|
45
|
+
{
|
|
46
|
+
encoding: 'utf8',
|
|
47
|
+
input: JSON.stringify({ session_id: 'adapter-session', cwd: root, source: 'startup' }),
|
|
48
|
+
env,
|
|
49
|
+
},
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
expect(disabled.status).toBe(0)
|
|
53
|
+
expect(disabled.stderr).toBe('')
|
|
54
|
+
expect(disabled.stdout).toBe('')
|
|
55
|
+
|
|
56
|
+
const binding = projectBinding(root, env)
|
|
57
|
+
if (binding === null) throw new Error('test Project binding unavailable')
|
|
58
|
+
enableProject(binding)
|
|
35
59
|
const run = spawnSync(
|
|
36
60
|
installed.path,
|
|
37
61
|
['hook', 'session-start', '--owner', 'notifai', '--harness', 'codex'],
|
|
38
62
|
{
|
|
39
63
|
encoding: 'utf8',
|
|
40
64
|
input: JSON.stringify({ session_id: 'adapter-session', cwd: root, source: 'startup' }),
|
|
41
|
-
env
|
|
42
|
-
...process.env,
|
|
43
|
-
HOME: homeDir,
|
|
44
|
-
XDG_CONFIG_HOME: path.join(root, 'config'),
|
|
45
|
-
XDG_STATE_HOME: path.join(root, 'state'),
|
|
46
|
-
},
|
|
65
|
+
env,
|
|
47
66
|
},
|
|
48
67
|
)
|
|
49
68
|
|
|
@@ -52,7 +71,7 @@ describe('stable hook adapter', () => {
|
|
|
52
71
|
expect(JSON.parse(run.stdout)).toMatchObject({
|
|
53
72
|
hookSpecificOutput: {
|
|
54
73
|
hookEventName: 'SessionStart',
|
|
55
|
-
additionalContext: expect.stringMatching(/Notifai.*
|
|
74
|
+
additionalContext: expect.stringMatching(/Notifai.*enabled.*Project/i),
|
|
56
75
|
},
|
|
57
76
|
})
|
|
58
77
|
})
|