@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.
- package/CHANGELOG.md +9 -2
- 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/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 +6 -0
- package/dist/commands-core.js.map +1 -1
- package/dist/commands-doctor.js +46 -1
- 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/guidance-content.js +9 -35
- 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 +297 -26
- 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 +6 -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 +19 -57
- 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/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.4.0
|
|
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
|
}
|
|
@@ -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
|
|
@@ -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
|
|
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
|
|
@@ -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
|
|
267
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
359
|
-
|
|
360
|
-
|
|
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:  `. 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`.
|