@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,175 @@
|
|
|
1
|
+
/** Local files of the Session Attendant: its exclusive claim and its readable status. */
|
|
2
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { atomicWriteFileSync } from './atomic-file.js';
|
|
5
|
+
import { sanitizeSessionId, stateDir } from './config.js';
|
|
6
|
+
import { withFileLock } from './file-lock.js';
|
|
7
|
+
import { claimHolderMayRun, readClaimFile } from './hook-question-lock.js';
|
|
8
|
+
/** One live attendant per Agent Session: PID + process start time + incarnation. */
|
|
9
|
+
export function attendantClaimPath(sessionId, env) {
|
|
10
|
+
return path.join(stateDir(env), 'sessions', `${sanitizeSessionId(sessionId)}.attendant`);
|
|
11
|
+
}
|
|
12
|
+
export function attendantStatusPath(sessionId, env) {
|
|
13
|
+
return path.join(stateDir(env), 'sessions', `${sanitizeSessionId(sessionId)}.attendance.json`);
|
|
14
|
+
}
|
|
15
|
+
export function writeAttendantStatus(sessionId, env, status) {
|
|
16
|
+
atomicWriteFileSync(attendantStatusPath(sessionId, env), `${JSON.stringify({ session_id: sessionId, pid: process.pid, ...status })}\n`);
|
|
17
|
+
}
|
|
18
|
+
const PHASES = new Set([
|
|
19
|
+
'dormant',
|
|
20
|
+
'unsupported',
|
|
21
|
+
'acquiring',
|
|
22
|
+
'attending',
|
|
23
|
+
'waiting-for-lease',
|
|
24
|
+
'uncertain',
|
|
25
|
+
'exited',
|
|
26
|
+
]);
|
|
27
|
+
/**
|
|
28
|
+
* Every attendant status on this machine, newest first. An attendant whose
|
|
29
|
+
* process is gone without writing `exited` is reported as not alive.
|
|
30
|
+
*/
|
|
31
|
+
export function listAttendantReports(env) {
|
|
32
|
+
const directory = path.join(stateDir(env), 'sessions');
|
|
33
|
+
if (!existsSync(directory))
|
|
34
|
+
return [];
|
|
35
|
+
const reports = [];
|
|
36
|
+
for (const name of readdirSync(directory)) {
|
|
37
|
+
if (!name.endsWith('.attendance.json'))
|
|
38
|
+
continue;
|
|
39
|
+
try {
|
|
40
|
+
const parsed = JSON.parse(readFileSync(path.join(directory, name), 'utf8'));
|
|
41
|
+
const sessionId = parsed['session_id'];
|
|
42
|
+
const phase = parsed['phase'];
|
|
43
|
+
if (typeof sessionId !== 'string' || typeof phase !== 'string' || !PHASES.has(phase))
|
|
44
|
+
continue;
|
|
45
|
+
const claim = readClaimFile(attendantClaimPath(sessionId, env));
|
|
46
|
+
const alive = phase !== 'exited' && claim !== null && claim['pid'] === parsed['pid'] && claimHolderMayRun(claim);
|
|
47
|
+
reports.push({
|
|
48
|
+
session_id: sessionId,
|
|
49
|
+
phase: phase,
|
|
50
|
+
alive,
|
|
51
|
+
generation: typeof parsed['generation'] === 'number' ? parsed['generation'] : null,
|
|
52
|
+
activity: typeof parsed['activity'] === 'string' ? parsed['activity'] : null,
|
|
53
|
+
reason: typeof parsed['reason'] === 'string' ? parsed['reason'] : null,
|
|
54
|
+
accepts_messages: parsed['accepts_messages'] === true,
|
|
55
|
+
updated_at: typeof parsed['updated_at'] === 'number' ? parsed['updated_at'] : 0,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// A half-written or foreign file is not an attendant.
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return reports.sort((a, b) => b.updated_at - a.updated_at);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The lease a writer beside the attendant (the answer waiter) may claim under:
|
|
66
|
+
* the generation the live attendant of this session holds right now, or null.
|
|
67
|
+
* The service still decides; a stale read is refused as `generation_fenced`.
|
|
68
|
+
*/
|
|
69
|
+
export function readAttendantLease(sessionId, env) {
|
|
70
|
+
try {
|
|
71
|
+
const status = JSON.parse(readFileSync(attendantStatusPath(sessionId, env), 'utf8'));
|
|
72
|
+
const generation = status['generation'];
|
|
73
|
+
const incarnation = status['incarnation'];
|
|
74
|
+
if (status['phase'] !== 'attending' ||
|
|
75
|
+
typeof generation !== 'number' ||
|
|
76
|
+
!Number.isInteger(generation) ||
|
|
77
|
+
generation < 1 ||
|
|
78
|
+
typeof incarnation !== 'string') {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
const claim = readClaimFile(attendantClaimPath(sessionId, env));
|
|
82
|
+
if (claim === null || claim['pid'] !== status['pid'] || !claimHolderMayRun(claim))
|
|
83
|
+
return null;
|
|
84
|
+
return { incarnation, generation };
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The lease a live attendant of this session holds in any phase short of
|
|
92
|
+
* exiting, for the one writer that ends it on the attendant's behalf: Codex's
|
|
93
|
+
* SessionEnd, after which Codex kills the attendant before it can report.
|
|
94
|
+
*/
|
|
95
|
+
export function readAttendantHeldLease(sessionId, env) {
|
|
96
|
+
try {
|
|
97
|
+
const status = JSON.parse(readFileSync(attendantStatusPath(sessionId, env), 'utf8'));
|
|
98
|
+
const generation = status['generation'];
|
|
99
|
+
const incarnation = status['incarnation'];
|
|
100
|
+
if (status['phase'] === 'exited' ||
|
|
101
|
+
typeof generation !== 'number' ||
|
|
102
|
+
!Number.isInteger(generation) ||
|
|
103
|
+
generation < 1 ||
|
|
104
|
+
typeof incarnation !== 'string') {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
const claim = readClaimFile(attendantClaimPath(sessionId, env));
|
|
108
|
+
if (claim === null || claim['pid'] !== status['pid'] || !claimHolderMayRun(claim))
|
|
109
|
+
return null;
|
|
110
|
+
return { incarnation, generation };
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* A thread's own turn boundaries, for a harness that publishes no activity of
|
|
118
|
+
* its own (Codex), scoped to one session incarnation (its stable start key).
|
|
119
|
+
*
|
|
120
|
+
* Starts are recorded by the synchronous prompt hook, which Codex runs before
|
|
121
|
+
* each turn and one turn at a time, so recorded starts keep the thread's own
|
|
122
|
+
* order. Ends come from asynchronous hooks (Stop, Interrupt) that may run late
|
|
123
|
+
* or before the start they close, so they are matched by turn id. A start of a
|
|
124
|
+
* turn already seen — started or ended — is stale and never replaces the
|
|
125
|
+
* current turn. The thread is working while its current turn has no end.
|
|
126
|
+
*/
|
|
127
|
+
export function turnActivityPath(sessionId, env) {
|
|
128
|
+
return path.join(stateDir(env), 'sessions', `${sanitizeSessionId(sessionId)}.turns.json`);
|
|
129
|
+
}
|
|
130
|
+
/** Turn ids kept to recognise a late hook; a late hook trails its turn by seconds. */
|
|
131
|
+
const TURNS_KEPT = 32;
|
|
132
|
+
function readTurns(file) {
|
|
133
|
+
const ids = (value) => Array.isArray(value) ? value.filter((turn) => typeof turn === 'string') : [];
|
|
134
|
+
try {
|
|
135
|
+
const parsed = JSON.parse(readFileSync(file, 'utf8'));
|
|
136
|
+
return {
|
|
137
|
+
key: typeof parsed['key'] === 'string' ? parsed['key'] : null,
|
|
138
|
+
current: typeof parsed['current'] === 'string' ? parsed['current'] : null,
|
|
139
|
+
started: ids(parsed['started']),
|
|
140
|
+
ended: ids(parsed['ended']),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return { key: null, current: null, started: [], ended: [] };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function updateTurns(sessionId, env, update) {
|
|
148
|
+
const file = turnActivityPath(sessionId, env);
|
|
149
|
+
withFileLock(`${file}.lock`, () => {
|
|
150
|
+
const next = update(readTurns(file));
|
|
151
|
+
atomicWriteFileSync(file, `${JSON.stringify({ session_id: sessionId, ...next })}\n`);
|
|
152
|
+
},
|
|
153
|
+
// The prompt hook runs in front of the User's turn; activity is only a hint.
|
|
154
|
+
{ waitMs: 500 });
|
|
155
|
+
}
|
|
156
|
+
const keep = (ids, id) => [...ids.filter((turn) => turn !== id), id].slice(-TURNS_KEPT);
|
|
157
|
+
/** A turn of incarnation `key` started. Ignored when that turn was already seen. */
|
|
158
|
+
export function recordTurnStart(sessionId, env, key, turnId) {
|
|
159
|
+
updateTurns(sessionId, env, (record) => {
|
|
160
|
+
const same = record.key === key ? record : { key, current: null, started: [], ended: record.ended };
|
|
161
|
+
if (same.started.includes(turnId) || same.ended.includes(turnId))
|
|
162
|
+
return same;
|
|
163
|
+
return { ...same, current: turnId, started: keep(same.started, turnId) };
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
/** A turn ended (or was interrupted), whenever its hook got to run. */
|
|
167
|
+
export function recordTurnEnd(sessionId, env, turnId) {
|
|
168
|
+
updateTurns(sessionId, env, (record) => ({ ...record, ended: keep(record.ended, turnId) }));
|
|
169
|
+
}
|
|
170
|
+
/** Working while incarnation `key`'s current turn has no recorded end. */
|
|
171
|
+
export function readTurnActivity(sessionId, env, key) {
|
|
172
|
+
const { key: recorded, current, ended } = readTurns(turnActivityPath(sessionId, env));
|
|
173
|
+
return recorded === key && current !== null && !ended.includes(current) ? 'working' : 'idle';
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=session-attendant-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-attendant-state.js","sourceRoot":"","sources":["../src/session-attendant-state.ts"],"names":[],"mappings":"AAAA,yFAAyF;AACzF,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAC/D,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AACtD,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAK1E,oFAAoF;AACpF,MAAM,UAAU,kBAAkB,CAAC,SAAiB,EAAE,GAAsB;IAC1E,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,UAAU,EAAE,GAAG,iBAAiB,CAAC,SAAS,CAAC,YAAY,CAAC,CAAA;AAC1F,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,SAAiB,EAAE,GAAsB;IAC3E,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,UAAU,EAAE,GAAG,iBAAiB,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAA;AAChG,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,SAAiB,EACjB,GAAsB,EACtB,MAAuB;IAEvB,mBAAmB,CACjB,mBAAmB,CAAC,SAAS,EAAE,GAAG,CAAC,EACnC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,GAAG,MAAM,EAAE,CAAC,IAAI,CAC9E,CAAA;AACH,CAAC;AAcD,MAAM,MAAM,GAAwB,IAAI,GAAG,CAAiB;IAC1D,SAAS;IACT,aAAa;IACb,WAAW;IACX,WAAW;IACX,mBAAmB;IACnB,WAAW;IACX,QAAQ;CACT,CAAC,CAAA;AAEF;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAsB;IACzD,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,UAAU,CAAC,CAAA;IACtD,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,EAAE,CAAA;IACrC,MAAM,OAAO,GAAsB,EAAE,CAAA;IACrC,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,SAAS,CAAC,EAAE,CAAC;QAC1C,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC;YAAE,SAAQ;QAChD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAA4B,CAAA;YACtG,MAAM,SAAS,GAAG,MAAM,CAAC,YAAY,CAAC,CAAA;YACtC,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,CAAA;YAC7B,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,SAAQ;YAC9F,MAAM,KAAK,GAAG,aAAa,CAAC,kBAAkB,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,CAAA;YAC/D,MAAM,KAAK,GACT,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC,KAAK,CAAC,IAAI,iBAAiB,CAAC,KAAK,CAAC,CAAA;YACpG,OAAO,CAAC,IAAI,CAAC;gBACX,UAAU,EAAE,SAAS;gBACrB,KAAK,EAAE,KAAuB;gBAC9B,KAAK;gBACL,UAAU,EAAE,OAAO,MAAM,CAAC,YAAY,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI;gBAClF,QAAQ,EAAE,OAAO,MAAM,CAAC,UAAU,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI;gBAC5E,MAAM,EAAE,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI;gBACtE,gBAAgB,EAAE,MAAM,CAAC,kBAAkB,CAAC,KAAK,IAAI;gBACrD,UAAU,EAAE,OAAO,MAAM,CAAC,YAAY,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;aAChF,CAAC,CAAA;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,sDAAsD;QACxD,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,UAAU,CAAC,CAAA;AAC5D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,SAAiB,EAAE,GAAsB;IAC1E,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,mBAAmB,CAAC,SAAS,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,CAA4B,CAAA;QAC/G,MAAM,UAAU,GAAG,MAAM,CAAC,YAAY,CAAC,CAAA;QACvC,MAAM,WAAW,GAAG,MAAM,CAAC,aAAa,CAAC,CAAA;QACzC,IACE,MAAM,CAAC,OAAO,CAAC,KAAK,WAAW;YAC/B,OAAO,UAAU,KAAK,QAAQ;YAC9B,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC;YAC7B,UAAU,GAAG,CAAC;YACd,OAAO,WAAW,KAAK,QAAQ,EAC/B,CAAC;YACD,OAAO,IAAI,CAAA;QACb,CAAC;QACD,MAAM,KAAK,GAAG,aAAa,CAAC,kBAAkB,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,CAAA;QAC/D,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAA;QAC9F,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,CAAA;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,SAAiB,EAAE,GAAsB;IAC9E,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,mBAAmB,CAAC,SAAS,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,CAA4B,CAAA;QAC/G,MAAM,UAAU,GAAG,MAAM,CAAC,YAAY,CAAC,CAAA;QACvC,MAAM,WAAW,GAAG,MAAM,CAAC,aAAa,CAAC,CAAA;QACzC,IACE,MAAM,CAAC,OAAO,CAAC,KAAK,QAAQ;YAC5B,OAAO,UAAU,KAAK,QAAQ;YAC9B,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC;YAC7B,UAAU,GAAG,CAAC;YACd,OAAO,WAAW,KAAK,QAAQ,EAC/B,CAAC;YACD,OAAO,IAAI,CAAA;QACb,CAAC;QACD,MAAM,KAAK,GAAG,aAAa,CAAC,kBAAkB,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,CAAA;QAC/D,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAA;QAC9F,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,CAAA;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,gBAAgB,CAAC,SAAiB,EAAE,GAAsB;IACxE,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,UAAU,EAAE,GAAG,iBAAiB,CAAC,SAAS,CAAC,aAAa,CAAC,CAAA;AAC3F,CAAC;AAWD,sFAAsF;AACtF,MAAM,UAAU,GAAG,EAAE,CAAA;AAErB,SAAS,SAAS,CAAC,IAAY;IAC7B,MAAM,GAAG,GAAG,CAAC,KAAc,EAAY,EAAE,CACvC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAC9F,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAA4B,CAAA;QAChF,OAAO;YACL,GAAG,EAAE,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI;YAC7D,OAAO,EAAE,OAAO,MAAM,CAAC,SAAS,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI;YACzE,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAC/B,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;SAC5B,CAAA;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAA;IAC7D,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAAC,SAAiB,EAAE,GAAsB,EAAE,MAA0C;IACxG,MAAM,IAAI,GAAG,gBAAgB,CAAC,SAAS,EAAE,GAAG,CAAC,CAAA;IAC7C,YAAY,CACV,GAAG,IAAI,OAAO,EACd,GAAG,EAAE;QACH,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAA;QACpC,mBAAmB,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,CAAC,IAAI,CAAC,CAAA;IACtF,CAAC;IACD,6EAA6E;IAC7E,EAAE,MAAM,EAAE,GAAG,EAAE,CAChB,CAAA;AACH,CAAC;AAED,MAAM,IAAI,GAAG,CAAC,GAAa,EAAE,EAAU,EAAY,EAAE,CAAC,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,CAAA;AAEnH,oFAAoF;AACpF,MAAM,UAAU,eAAe,CAAC,SAAiB,EAAE,GAAsB,EAAE,GAAW,EAAE,MAAc;IACpG,WAAW,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE,EAAE;QACrC,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAA;QACnG,IAAI,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,OAAO,IAAI,CAAA;QAC7E,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,CAAA;IAC1E,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,aAAa,CAAC,SAAiB,EAAE,GAAsB,EAAE,MAAc;IACrF,WAAW,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAA;AAC7F,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,gBAAgB,CAAC,SAAiB,EAAE,GAAsB,EAAE,GAAW;IACrF,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,SAAS,CAAC,gBAAgB,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,CAAA;IACrF,OAAO,QAAQ,KAAK,GAAG,IAAI,OAAO,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAA;AAC9F,CAAC"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Session Attendant: one long-lived CLI process per Agent Session.
|
|
3
|
+
*
|
|
4
|
+
* It starts as that session's own asynchronous hook child, proves the harness
|
|
5
|
+
* process alive every two seconds by local evidence only, and holds the
|
|
6
|
+
* session's Session Presence lease with the service. It is resident from its
|
|
7
|
+
* start but makes no network call until the session had a Notification
|
|
8
|
+
* Request accepted, so sessions that never notified are never reported.
|
|
9
|
+
*
|
|
10
|
+
* Nothing ends it from outside. Claude Code neither signals nor waits for a
|
|
11
|
+
* resident async hook on `/exit` or when the harness is killed, so the local
|
|
12
|
+
* probe is the one universal end signal; SessionEnd's marker is a fast path,
|
|
13
|
+
* and a SIGTERM (terminal close) is treated as "ending, report once".
|
|
14
|
+
*
|
|
15
|
+
* Local only: PIDs, process start times, descriptor paths, and harness homes
|
|
16
|
+
* never leave this machine. The service sees an opaque incarnation id.
|
|
17
|
+
*/
|
|
18
|
+
import type { AttendanceMessage, SessionActivity } from '@raidiant/notifai-protocol';
|
|
19
|
+
import { type ApiClient } from './client.js';
|
|
20
|
+
import type { Logger } from './logging.js';
|
|
21
|
+
/** Local probe cadence. */
|
|
22
|
+
export declare const ATTENDANT_PROBE_INTERVAL_MS = 2000;
|
|
23
|
+
/** Harness writes stop this long before the lease ends by the attendant's own clock. */
|
|
24
|
+
export declare const ATTENDANT_WRITE_MARGIN_MS = 15000;
|
|
25
|
+
/** A wall-clock move this far from the monotonic clock means sleep or a clock jump. */
|
|
26
|
+
export declare const ATTENDANT_CLOCK_JUMP_MS = 5000;
|
|
27
|
+
/** Best-effort bound on the final `ended` or `withdrawn` report. */
|
|
28
|
+
export declare const ATTENDANT_FINAL_REPORT_MS = 3000;
|
|
29
|
+
/** How often an unsupported service is asked again. */
|
|
30
|
+
export declare const ATTENDANT_UNSUPPORTED_RECHECK_MS: number;
|
|
31
|
+
/** How long to wait before asking again about a session the service does not know yet. */
|
|
32
|
+
export declare const ATTENDANT_UNKNOWN_SESSION_RETRY_MS = 60000;
|
|
33
|
+
/** First pause before re-asking for a message that waits on something brief; doubles to the hold. */
|
|
34
|
+
export declare const ATTENDANT_MESSAGE_RETRY_MS = 2000;
|
|
35
|
+
export type HarnessProbe = {
|
|
36
|
+
state: 'running';
|
|
37
|
+
activity: SessionActivity;
|
|
38
|
+
}
|
|
39
|
+
/** Evidence is missing or ambiguous: no renewal, no deliveries, never `running`. */
|
|
40
|
+
| {
|
|
41
|
+
state: 'uncertain';
|
|
42
|
+
reason: string;
|
|
43
|
+
} | {
|
|
44
|
+
state: 'ended';
|
|
45
|
+
reason: 'harness-gone' | 'session-replaced' | 'session-end-hook';
|
|
46
|
+
};
|
|
47
|
+
export type GateResult = {
|
|
48
|
+
ok: true;
|
|
49
|
+
} | {
|
|
50
|
+
ok: false;
|
|
51
|
+
reason: string;
|
|
52
|
+
};
|
|
53
|
+
export type AttendantPhase =
|
|
54
|
+
/** Resident, no network: the session has not notified yet. */
|
|
55
|
+
'dormant'
|
|
56
|
+
/** The service does not offer Session Attendance; resident without reporting. */
|
|
57
|
+
| 'unsupported' | 'acquiring' | 'attending'
|
|
58
|
+
/** Another incarnation holds the lease. */
|
|
59
|
+
| 'waiting-for-lease' | 'uncertain' | 'exited';
|
|
60
|
+
export type AttendantExitReason = 'harness-gone' | 'session-replaced' | 'session-end-hook' | 'signal' | 'superseded' | 'claim-lost' | 'withdrawn-by-service' | 'unauthorized' | `gate:${string}`;
|
|
61
|
+
export interface AttendantStatus {
|
|
62
|
+
phase: AttendantPhase;
|
|
63
|
+
incarnation: string;
|
|
64
|
+
generation: number | null;
|
|
65
|
+
activity: SessionActivity | null;
|
|
66
|
+
reason: string | null;
|
|
67
|
+
/** Whether this attendant hands Session Messages in, so the service accepts notes. */
|
|
68
|
+
accepts_messages: boolean;
|
|
69
|
+
updated_at: number;
|
|
70
|
+
}
|
|
71
|
+
export interface AttendantClock {
|
|
72
|
+
/** Monotonic milliseconds; never moves with the wall clock. */
|
|
73
|
+
monotonic(): number;
|
|
74
|
+
wall(): number;
|
|
75
|
+
sleep(milliseconds: number, signal: AbortSignal): Promise<void>;
|
|
76
|
+
}
|
|
77
|
+
export interface SessionAttendantOptions {
|
|
78
|
+
sessionId: string;
|
|
79
|
+
/** The opaque incarnation this attendant serves; see `incarnationNow`. */
|
|
80
|
+
incarnation: string;
|
|
81
|
+
/** The incarnation currently recorded for the session, read fresh each probe. */
|
|
82
|
+
incarnationNow(): string | null;
|
|
83
|
+
/** Replace a refused incarnation id; null when the record moved on. */
|
|
84
|
+
rotateIncarnation(expected: string): string | null;
|
|
85
|
+
probe(): HarnessProbe;
|
|
86
|
+
/** Whether this attendant still holds its exclusive local claim. */
|
|
87
|
+
claimHeld(): boolean;
|
|
88
|
+
/** Whether this session had a Notification Request accepted on this machine. */
|
|
89
|
+
notified(): boolean;
|
|
90
|
+
/** Project Enablement and the installed CLI/hook contract. */
|
|
91
|
+
gates(): GateResult;
|
|
92
|
+
/** Authenticated client, or null when this machine is not paired. */
|
|
93
|
+
client(): ApiClient | null;
|
|
94
|
+
/** Whether the service advertises Session Attendance. */
|
|
95
|
+
serverSupportsAttendance(client: ApiClient): Promise<boolean>;
|
|
96
|
+
/** Whether this attendant can hand Session Messages into the session. */
|
|
97
|
+
acceptsMessages: boolean;
|
|
98
|
+
/**
|
|
99
|
+
* Hands claimable Session Messages into the session. `retry-soon` asks for
|
|
100
|
+
* the next exchange without holding it open: a message is waiting on
|
|
101
|
+
* something that resolves in seconds.
|
|
102
|
+
*/
|
|
103
|
+
onMessages?: (messages: AttendanceMessage[], attendant: AttendantHandle) => Promise<'done' | 'retry-soon'>;
|
|
104
|
+
clock: AttendantClock;
|
|
105
|
+
logger: Logger;
|
|
106
|
+
/** Persist the local status `notifai doctor` reads. */
|
|
107
|
+
writeStatus(status: AttendantStatus): void;
|
|
108
|
+
/** Periodic housekeeping (e.g. keep markers younger than pruning). */
|
|
109
|
+
heartbeat?: () => void;
|
|
110
|
+
/** Resolves when a termination signal arrived. */
|
|
111
|
+
signalled?: Promise<void>;
|
|
112
|
+
probeIntervalMs?: number;
|
|
113
|
+
waitSeconds?: number;
|
|
114
|
+
}
|
|
115
|
+
/** What a harness writer asks before each write. */
|
|
116
|
+
export interface AttendantHandle {
|
|
117
|
+
/**
|
|
118
|
+
* True only while, right now: this attendant still owns its claim and
|
|
119
|
+
* incarnation, fresh local evidence shows the session running, every gate
|
|
120
|
+
* passes, no clock discontinuity happened since the lease was granted, and
|
|
121
|
+
* the lease cannot lapse before the write ends by the monotonic clock.
|
|
122
|
+
*/
|
|
123
|
+
mayWrite(): boolean;
|
|
124
|
+
generation(): number | null;
|
|
125
|
+
incarnation(): string;
|
|
126
|
+
}
|
|
127
|
+
export interface AttendantResult {
|
|
128
|
+
reason: AttendantExitReason;
|
|
129
|
+
/** Whether a final report was attempted. */
|
|
130
|
+
reported: 'ended' | 'withdrawn' | null;
|
|
131
|
+
}
|
|
132
|
+
/** Runs one Session Attendant until its session ends or a gate fails. */
|
|
133
|
+
export declare function runSessionAttendant(options: SessionAttendantOptions): Promise<AttendantResult>;
|
|
134
|
+
/** Production clocks: `performance.now()` pauses with the machine; `Date.now()` does not. */
|
|
135
|
+
export declare const systemAttendantClock: AttendantClock;
|