@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.
- package/CHANGELOG.md +22 -0
- package/dist/agent-update-notice.js +3 -3
- package/dist/agent-update-notice.js.map +1 -1
- package/dist/claude-wake.d.ts +11 -3
- package/dist/claude-wake.js +74 -67
- package/dist/claude-wake.js.map +1 -1
- package/dist/cli-bin.js +2 -2
- package/dist/cli-bin.js.map +1 -1
- package/dist/cli-contract.d.ts +6 -2
- package/dist/cli-contract.js +11 -3
- package/dist/cli-contract.js.map +1 -1
- package/dist/cli-release.d.ts +31 -4
- package/dist/cli-release.js +45 -15
- package/dist/cli-release.js.map +1 -1
- package/dist/client.d.ts +23 -2
- package/dist/client.js +11 -5
- package/dist/client.js.map +1 -1
- package/dist/codex-wake.d.ts +12 -2
- package/dist/codex-wake.js +50 -13
- package/dist/codex-wake.js.map +1 -1
- package/dist/commands-acknowledge.d.ts +4 -2
- package/dist/commands-acknowledge.js +35 -4
- package/dist/commands-acknowledge.js.map +1 -1
- package/dist/commands-ask.js +2 -2
- package/dist/commands-ask.js.map +1 -1
- package/dist/commands-close.js +16 -4
- package/dist/commands-close.js.map +1 -1
- package/dist/commands-core.d.ts +10 -1
- package/dist/commands-core.js +4 -3
- package/dist/commands-core.js.map +1 -1
- package/dist/commands-doctor.js +49 -4
- package/dist/commands-doctor.js.map +1 -1
- package/dist/commands-hook-attend.d.ts +61 -0
- package/dist/commands-hook-attend.js +475 -0
- package/dist/commands-hook-attend.js.map +1 -0
- package/dist/commands-hook-diagnostics.js +2 -2
- package/dist/commands-hook-diagnostics.js.map +1 -1
- package/dist/commands-hook-install.js +1 -1
- package/dist/commands-hook-install.js.map +1 -1
- package/dist/commands-hook-run.js +59 -2
- package/dist/commands-hook-run.js.map +1 -1
- package/dist/commands-send.js +6 -1
- package/dist/commands-send.js.map +1 -1
- package/dist/commands-update-check.js +9 -6
- package/dist/commands-update-check.js.map +1 -1
- package/dist/commands-update.d.ts +2 -1
- package/dist/commands-update.js +74 -11
- package/dist/commands-update.js.map +1 -1
- package/dist/guidance-content.js +11 -37
- package/dist/guidance-content.js.map +1 -1
- package/dist/hook-acknowledgements.d.ts +25 -5
- package/dist/hook-acknowledgements.js +102 -45
- package/dist/hook-acknowledgements.js.map +1 -1
- package/dist/hook-events.d.ts +26 -1
- package/dist/hook-events.js +38 -2
- package/dist/hook-events.js.map +1 -1
- package/dist/hook-lifecycle.d.ts +6 -0
- package/dist/hook-lifecycle.js +309 -31
- package/dist/hook-lifecycle.js.map +1 -1
- package/dist/hook-question-lock.d.ts +18 -0
- package/dist/hook-question-lock.js +80 -57
- package/dist/hook-question-lock.js.map +1 -1
- package/dist/hook-question-retirement.d.ts +10 -2
- package/dist/hook-question-retirement.js +21 -4
- package/dist/hook-question-retirement.js.map +1 -1
- package/dist/hook-session-state.d.ts +90 -1
- package/dist/hook-session-state.js +201 -9
- package/dist/hook-session-state.js.map +1 -1
- package/dist/hook-types.d.ts +50 -2
- package/dist/injection-render.d.ts +36 -0
- package/dist/injection-render.js +61 -0
- package/dist/injection-render.js.map +1 -0
- package/dist/install-hooks.d.ts +14 -0
- package/dist/install-hooks.js +47 -2
- package/dist/install-hooks.js.map +1 -1
- package/dist/logging.d.ts +1 -1
- package/dist/logging.js +12 -0
- package/dist/logging.js.map +1 -1
- package/dist/process-identity.d.ts +26 -0
- package/dist/process-identity.js +82 -0
- package/dist/process-identity.js.map +1 -0
- package/dist/program.js +17 -6
- package/dist/program.js.map +1 -1
- package/dist/session-attendant-probe.d.ts +74 -0
- package/dist/session-attendant-probe.js +131 -0
- package/dist/session-attendant-probe.js.map +1 -0
- package/dist/session-attendant-state.d.ts +53 -0
- package/dist/session-attendant-state.js +175 -0
- package/dist/session-attendant-state.js.map +1 -0
- package/dist/session-attendant.d.ts +135 -0
- package/dist/session-attendant.js +493 -0
- package/dist/session-attendant.js.map +1 -0
- package/dist/session-delivery.d.ts +214 -0
- package/dist/session-delivery.js +560 -0
- package/dist/session-delivery.js.map +1 -0
- package/dist/session-handoff.d.ts +98 -0
- package/dist/session-handoff.js +118 -0
- package/dist/session-handoff.js.map +1 -0
- package/dist/session-message-handoff.d.ts +35 -0
- package/dist/session-message-handoff.js +99 -0
- package/dist/session-message-handoff.js.map +1 -0
- package/dist/skill-source/manifest.json +20 -4
- package/dist/skill-source/notifai/SKILL.md +28 -67
- package/dist/skill-source/notifai/references/diagnostics.md +31 -0
- package/dist/skill-source/notifai/references/harness-setup.md +3 -0
- package/dist/skill-source/notifai/references/notes-and-edits.md +38 -0
- package/dist/skill-source/notifai/references/send-details.md +14 -0
- package/dist/skill-source/notifai/references/writing-examples.md +56 -0
- package/dist/update-handoff.js +3 -3
- package/dist/update-handoff.js.map +1 -1
- package/dist/version.d.ts +18 -0
- package/dist/version.js +82 -1
- package/dist/version.js.map +1 -1
- package/dist/wake-support.d.ts +33 -1
- package/dist/wake-support.js +65 -8
- package/dist/wake-support.js.map +1 -1
- 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
|
+
"package_version": "11.4.0",
|
|
4
4
|
"skill": "notifai",
|
|
5
|
-
"digest": "sha256:
|
|
5
|
+
"digest": "sha256:a94b7564764b023ec4fdf4aa5f6bdac0e47ce6d7da9bde4795fd177dd2725aa1",
|
|
6
6
|
"files": [
|
|
7
7
|
{
|
|
8
8
|
"path": "SKILL.md",
|
|
9
|
-
"sha256": "
|
|
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": "
|
|
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
|
|
9
|
-
|
|
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
|
|
100
|
-
|
|
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
|
|
127
|
+
Use `--body-file <path|->` for long content.
|
|
129
128
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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:  `. 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
|
-
|
|
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.**
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
|
207
|
-
|
|
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:
|
|
254
|
-
|
|
255
|
-
|
|
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
|
|
268
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
360
|
-
|
|
361
|
-
|
|
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.
|