@raidiant/notifai 11.5.0 → 11.6.0-beta.3
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 -0
- package/dist/commands-acknowledge.js +5 -2
- package/dist/commands-acknowledge.js.map +1 -1
- package/dist/commands-ask.js +9 -2
- package/dist/commands-ask.js.map +1 -1
- package/dist/commands-doctor.js +21 -15
- package/dist/commands-doctor.js.map +1 -1
- package/dist/commands-hook-attend.js +42 -6
- package/dist/commands-hook-attend.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 +79 -8
- package/dist/commands-hook-run.js.map +1 -1
- package/dist/commands-send-support.js +3 -0
- package/dist/commands-send-support.js.map +1 -1
- package/dist/commands-update-check.d.ts +1 -1
- package/dist/commands-update-check.js +12 -4
- package/dist/commands-update-check.js.map +1 -1
- package/dist/commands-update-resume.d.ts +7 -0
- package/dist/commands-update-resume.js +133 -0
- package/dist/commands-update-resume.js.map +1 -0
- package/dist/commands-update.js +25 -11
- package/dist/commands-update.js.map +1 -1
- package/dist/commands.d.ts +1 -0
- package/dist/commands.js +1 -0
- package/dist/commands.js.map +1 -1
- package/dist/harnesses.d.ts +8 -3
- package/dist/harnesses.js +16 -4
- package/dist/harnesses.js.map +1 -1
- package/dist/hermes-attendant.d.ts +38 -0
- package/dist/hermes-attendant.js +333 -0
- package/dist/hermes-attendant.js.map +1 -0
- package/dist/hermes-plugin.js +142 -4
- package/dist/hermes-plugin.js.map +1 -1
- package/dist/hook-lifecycle.d.ts +1 -1
- package/dist/hook-lifecycle.js +22 -11
- package/dist/hook-lifecycle.js.map +1 -1
- package/dist/hook-types.d.ts +3 -0
- package/dist/integration-health.d.ts +26 -0
- package/dist/integration-health.js +133 -0
- package/dist/integration-health.js.map +1 -0
- package/dist/invocation-context.d.ts +1 -0
- package/dist/invocation-context.js +3 -0
- package/dist/invocation-context.js.map +1 -1
- package/dist/native-skills.d.ts +1 -0
- package/dist/native-skills.js +30 -3
- package/dist/native-skills.js.map +1 -1
- package/dist/openclaw-generation.d.ts +5 -1
- package/dist/openclaw-generation.js +27 -5
- package/dist/openclaw-generation.js.map +1 -1
- package/dist/openclaw-message-bridge.d.ts +8 -0
- package/dist/openclaw-message-bridge.js +93 -0
- package/dist/openclaw-message-bridge.js.map +1 -0
- package/dist/openclaw-pending.d.ts +1 -1
- package/dist/openclaw-pending.js +1 -6
- package/dist/openclaw-pending.js.map +1 -1
- package/dist/openclaw-plugin.js +385 -14
- package/dist/openclaw-plugin.js.map +1 -1
- package/dist/openclaw-session-access.d.ts +2 -0
- package/dist/openclaw-session-access.js +10 -0
- package/dist/openclaw-session-access.js.map +1 -1
- package/dist/program.d.ts +2 -1
- package/dist/program.js +17 -10
- package/dist/program.js.map +1 -1
- package/dist/session-activation.js +1 -0
- package/dist/session-activation.js.map +1 -1
- package/dist/session-attendant-probe.d.ts +1 -0
- package/dist/session-attendant-probe.js +11 -1
- package/dist/session-attendant-probe.js.map +1 -1
- package/dist/session-attendant-state.d.ts +3 -0
- package/dist/session-attendant-state.js +8 -0
- package/dist/session-attendant-state.js.map +1 -1
- package/dist/session-attendant.d.ts +6 -0
- package/dist/session-attendant.js +12 -0
- package/dist/session-attendant.js.map +1 -1
- package/dist/session-labels.d.ts +2 -0
- package/dist/session-labels.js +5 -0
- package/dist/session-labels.js.map +1 -1
- package/dist/session-message-handoff.d.ts +3 -1
- package/dist/session-message-handoff.js +2 -1
- package/dist/session-message-handoff.js.map +1 -1
- package/dist/skill-integrity.d.ts +8 -3
- package/dist/skill-integrity.js +18 -8
- package/dist/skill-integrity.js.map +1 -1
- package/dist/skill-source/manifest.json +7 -7
- package/dist/skill-source/notifai/SKILL.md +66 -61
- package/dist/skill-source/notifai/references/diagnostics.md +5 -0
- package/dist/skill-source/notifai/references/harness-setup.md +24 -8
- package/dist/skill-source/notifai/references/notes-and-edits.md +6 -0
- package/dist/skill-source/notifai/references/updates.md +44 -10
- package/dist/update-handoff.js +1 -1
- package/dist/update-handoff.js.map +1 -1
- package/package.json +1 -1
package/dist/hermes-plugin.js
CHANGED
|
@@ -14,12 +14,140 @@ export function hermesPluginDir(env = process.env) {
|
|
|
14
14
|
export function hermesPluginSource(adapterPath, nodePath) {
|
|
15
15
|
const command = nodePath === undefined ? [adapterPath] : [nodePath, adapterPath];
|
|
16
16
|
return `${HERMES_PLUGIN_MARKER}
|
|
17
|
-
"""Notifai activation for a
|
|
17
|
+
"""Notifai activation and Session Attendant for a local Hermes CLI session."""
|
|
18
|
+
import atexit
|
|
18
19
|
import json
|
|
19
20
|
import os
|
|
21
|
+
import select
|
|
20
22
|
import subprocess
|
|
23
|
+
import threading
|
|
24
|
+
import time
|
|
21
25
|
|
|
22
26
|
COMMAND = ${JSON.stringify(command)}
|
|
27
|
+
_attendants = {}
|
|
28
|
+
_lock = threading.RLock()
|
|
29
|
+
|
|
30
|
+
def _attached_session(ctx):
|
|
31
|
+
cli = getattr(ctx._manager, "_cli_ref", None)
|
|
32
|
+
return str(getattr(cli, "session_id", "") or "") if cli is not None else ""
|
|
33
|
+
|
|
34
|
+
def _run_attendant(ctx, session_id, cwd, stopped):
|
|
35
|
+
proc = None
|
|
36
|
+
wall_anchor = time.time_ns()
|
|
37
|
+
mono_anchor = time.monotonic_ns()
|
|
38
|
+
try:
|
|
39
|
+
proc = subprocess.Popen(COMMAND + ["hook", "hermes-attend", "--owner", "notifai", "--harness", "hermes"],
|
|
40
|
+
cwd=cwd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
|
41
|
+
stderr=subprocess.DEVNULL, text=True, bufsize=1)
|
|
42
|
+
with _lock:
|
|
43
|
+
_attendants[session_id] = (stopped, proc)
|
|
44
|
+
def send(frame):
|
|
45
|
+
with _lock:
|
|
46
|
+
if proc.stdin is None or proc.stdin.closed:
|
|
47
|
+
return False
|
|
48
|
+
try:
|
|
49
|
+
proc.stdin.write(json.dumps(frame, ensure_ascii=False) + "\\n")
|
|
50
|
+
proc.stdin.flush()
|
|
51
|
+
return True
|
|
52
|
+
except (OSError, ValueError):
|
|
53
|
+
return False
|
|
54
|
+
if not send({"type": "hello", "session_id": session_id, "cwd": cwd, "pid": os.getpid()}):
|
|
55
|
+
return
|
|
56
|
+
pending = bytearray()
|
|
57
|
+
while not stopped.is_set() and proc.poll() is None:
|
|
58
|
+
with _lock:
|
|
59
|
+
current = _attached_session(ctx)
|
|
60
|
+
working = bool(getattr(ctx._manager._cli_ref, "_agent_running", False)) if current == session_id else False
|
|
61
|
+
if current != session_id:
|
|
62
|
+
break
|
|
63
|
+
if not send({"type": "state", "session_id": session_id,
|
|
64
|
+
"activity": "working" if working else "idle"}):
|
|
65
|
+
break
|
|
66
|
+
readable, _, _ = select.select([proc.stdout], [], [], 1.0)
|
|
67
|
+
if not readable:
|
|
68
|
+
continue
|
|
69
|
+
chunk = os.read(proc.stdout.fileno(), 65536)
|
|
70
|
+
if not chunk:
|
|
71
|
+
break
|
|
72
|
+
pending.extend(chunk)
|
|
73
|
+
if len(pending) > 2 * 1024 * 1024:
|
|
74
|
+
break
|
|
75
|
+
while b"\\n" in pending:
|
|
76
|
+
raw, _, rest = pending.partition(b"\\n")
|
|
77
|
+
pending = bytearray(rest)
|
|
78
|
+
try:
|
|
79
|
+
frame = json.loads(raw.decode("utf-8"))
|
|
80
|
+
except (UnicodeDecodeError, ValueError):
|
|
81
|
+
continue
|
|
82
|
+
if frame.get("type") != "write" or not isinstance(frame.get("id"), int):
|
|
83
|
+
continue
|
|
84
|
+
accepted = False
|
|
85
|
+
with _lock:
|
|
86
|
+
wall_now = time.time_ns()
|
|
87
|
+
mono_now = time.monotonic_ns()
|
|
88
|
+
# Fail closed after a backward wall-clock step. Node's
|
|
89
|
+
# deadline is wall time because its monotonic origin may
|
|
90
|
+
# differ from Python's on the same macOS host.
|
|
91
|
+
clock_stable = wall_now - wall_anchor + 100_000_000 >= mono_now - mono_anchor
|
|
92
|
+
deadline = frame.get("deadline_ms")
|
|
93
|
+
if (not stopped.is_set() and frame.get("session_id") == session_id and
|
|
94
|
+
_attached_session(ctx) == session_id and
|
|
95
|
+
type(deadline) is int and wall_now < deadline * 1_000_000 and clock_stable):
|
|
96
|
+
try:
|
|
97
|
+
accepted = bool(ctx.inject_message(frame.get("text", "")))
|
|
98
|
+
except Exception:
|
|
99
|
+
pass
|
|
100
|
+
send({"type": "result", "id": frame["id"], "accepted": accepted,
|
|
101
|
+
"reason": "session-replaced" if not accepted else ""})
|
|
102
|
+
send({"type": "end", "session_id": session_id})
|
|
103
|
+
except (OSError, ValueError):
|
|
104
|
+
pass
|
|
105
|
+
finally:
|
|
106
|
+
with _lock:
|
|
107
|
+
if _attendants.get(session_id, (None, None))[0] is stopped:
|
|
108
|
+
_attendants.pop(session_id, None)
|
|
109
|
+
if proc is not None:
|
|
110
|
+
try:
|
|
111
|
+
if proc.stdin is not None:
|
|
112
|
+
proc.stdin.close()
|
|
113
|
+
proc.wait(timeout=4)
|
|
114
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
115
|
+
proc.terminate()
|
|
116
|
+
try:
|
|
117
|
+
proc.wait(timeout=2)
|
|
118
|
+
except subprocess.TimeoutExpired:
|
|
119
|
+
proc.kill()
|
|
120
|
+
|
|
121
|
+
def _start_attendant(ctx, session_id, cwd):
|
|
122
|
+
if os.name == "nt":
|
|
123
|
+
return
|
|
124
|
+
with _lock:
|
|
125
|
+
if _attached_session(ctx) != session_id or os.environ.get("HERMES_SESSION_ID") != session_id:
|
|
126
|
+
return
|
|
127
|
+
if session_id in _attendants:
|
|
128
|
+
return
|
|
129
|
+
stopped = threading.Event()
|
|
130
|
+
_attendants[session_id] = (stopped, None)
|
|
131
|
+
threading.Thread(target=_run_attendant, args=(ctx, session_id, cwd, stopped),
|
|
132
|
+
daemon=True, name="notifai-hermes-attendant").start()
|
|
133
|
+
|
|
134
|
+
def _stop_attendant(session_id):
|
|
135
|
+
with _lock:
|
|
136
|
+
item = _attendants.get(session_id)
|
|
137
|
+
if item is None:
|
|
138
|
+
return
|
|
139
|
+
stopped, proc = item
|
|
140
|
+
stopped.set()
|
|
141
|
+
if proc is not None and proc.stdin is not None and not proc.stdin.closed:
|
|
142
|
+
try:
|
|
143
|
+
proc.stdin.write(json.dumps({"type": "end", "session_id": session_id}) + "\\n")
|
|
144
|
+
proc.stdin.flush()
|
|
145
|
+
except (OSError, ValueError):
|
|
146
|
+
pass
|
|
147
|
+
|
|
148
|
+
def _stop_all():
|
|
149
|
+
for session_id in list(_attendants):
|
|
150
|
+
_stop_attendant(session_id)
|
|
23
151
|
|
|
24
152
|
def _activation(info):
|
|
25
153
|
platform = str(info.get("platform") or "")
|
|
@@ -27,7 +155,9 @@ def _activation(info):
|
|
|
27
155
|
return ""
|
|
28
156
|
if os.environ.get("HERMES_SESSION_SOURCE", "").lower() not in ("", "cli"):
|
|
29
157
|
return ""
|
|
30
|
-
|
|
158
|
+
# Hermes may import gateway.run inside a classic CLI and set its process
|
|
159
|
+
# marker there. The attached CLI object and exact session are the proof.
|
|
160
|
+
if os.environ.get("HERMES_TUI_ACTIVE_SESSION_FILE"):
|
|
31
161
|
return ""
|
|
32
162
|
if os.environ.get("TERMINAL_ENV", "").lower() not in ("", "local"):
|
|
33
163
|
return ""
|
|
@@ -57,11 +187,19 @@ def _activation(info):
|
|
|
57
187
|
timeout=10, check=False)
|
|
58
188
|
except (OSError, subprocess.TimeoutExpired):
|
|
59
189
|
return ""
|
|
60
|
-
|
|
190
|
+
content = result.stdout.strip() if result.returncode == 0 else ""
|
|
191
|
+
if content and not worker:
|
|
192
|
+
_start_attendant(_ctx, session_id, cwd)
|
|
193
|
+
return content
|
|
61
194
|
|
|
62
195
|
def register(ctx):
|
|
196
|
+
global _ctx
|
|
197
|
+
_ctx = ctx
|
|
63
198
|
ctx.register_system_prompt_section("notifai.activation", _activation,
|
|
64
199
|
position="after_memory", max_chars=4000)
|
|
200
|
+
ctx.register_hook("on_session_finalize", lambda session_id=None, **_: _stop_attendant(str(session_id or "")))
|
|
201
|
+
ctx.register_hook("on_session_reset", lambda session_id=None, **_: _stop_all())
|
|
202
|
+
atexit.register(_stop_all)
|
|
65
203
|
`;
|
|
66
204
|
}
|
|
67
205
|
export function isOurHermesPlugin(dir) {
|
|
@@ -121,7 +259,7 @@ export function installHermesPlugin(adapterPath, env = process.env, nodePath) {
|
|
|
121
259
|
else {
|
|
122
260
|
const temp = mkdtempSync(path.join(os.tmpdir(), 'notifai-hermes-plugin-'));
|
|
123
261
|
try {
|
|
124
|
-
writeFileSync(path.join(temp, 'plugin.yaml'), 'name: notifai\nversion: "1.0.0"\ndescription: "Notifai
|
|
262
|
+
writeFileSync(path.join(temp, 'plugin.yaml'), 'name: notifai\nversion: "1.0.0"\ndescription: "Notifai activation and Session Attendant"\nprovides_hooks:\n - on_session_finalize\n - on_session_reset\n');
|
|
125
263
|
writeFileSync(path.join(temp, '__init__.py'), source);
|
|
126
264
|
const git = (args) => {
|
|
127
265
|
const result = spawnSync('git', args, { cwd: temp, env, encoding: 'utf8', timeout: 10_000 });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hermes-plugin.js","sourceRoot":"","sources":["../src/hermes-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACjG,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAE3C,MAAM,CAAC,MAAM,gBAAgB,GAAG,SAAS,CAAA;AACzC,MAAM,CAAC,MAAM,oBAAoB,GAAG,oCAAoC,CAAA;AAExE,MAAM,UAAU,eAAe,CAAC,MAAyB,OAAO,CAAC,GAAG;IAClE,MAAM,IAAI,GAAG,GAAG,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,OAAO,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAC,CAAA;IACnG,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,gBAAgB,CAAC,CAAA;AACrD,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,WAAmB,EAAE,QAAiB;IACvE,MAAM,OAAO,GAAG,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAA;IAChF,OAAO,GAAG,oBAAoB
|
|
1
|
+
{"version":3,"file":"hermes-plugin.js","sourceRoot":"","sources":["../src/hermes-plugin.ts"],"names":[],"mappings":"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"}
|
package/dist/hook-lifecycle.d.ts
CHANGED
|
@@ -65,7 +65,7 @@ export declare function reportHandedOffAfterTheFact(ctx: HookContext, sessionId:
|
|
|
65
65
|
* hook for this session will ever run again. A question whose
|
|
66
66
|
* agent just exited can receive no answer, so it is orphaned as `expired`.
|
|
67
67
|
*/
|
|
68
|
-
export declare function handleSessionEnd(env: NodeJS.ProcessEnv, envelope: HookEnvelope, now?: number): HookOutcome;
|
|
68
|
+
export declare function handleSessionEnd(env: NodeJS.ProcessEnv, envelope: HookEnvelope, now?: number, preserveAccepted?: boolean): HookOutcome;
|
|
69
69
|
/**
|
|
70
70
|
* Registrations still waiting for their first push. More than this in one turn
|
|
71
71
|
* means an agent is looping, not asking, and related questions belong in one
|
package/dist/hook-lifecycle.js
CHANGED
|
@@ -8,7 +8,7 @@ import { clearMatchingProjectSession, writeProjectSession } from './hook-project
|
|
|
8
8
|
import { claimHandoffState, claimQuestionPush, releaseQuestionPush } from './hook-question-lock.js';
|
|
9
9
|
import { closeQuietly, drainOrphanRetirements, drainRetirements, finalizeReplies, selectedForClaimedDelivery, orphanRetirements, pendingAnsweredByPrompt, retirePendings, retiringQuestion, } from './hook-question-retirement.js';
|
|
10
10
|
import { admitQueuedQuestion, clearFrozenSubmission, dropPendingQuestion, isSamePending, pendingQuestions, questionSubmissionAttempted, queuedQuestionStillEligible, rememberQuestionState, sourceContextAtHookEvent, summarizeRequestIds, } from './hook-question-state.js';
|
|
11
|
-
import { clearSessionState, markSessionEnded, pendingList, readSessionState, sessionHasEnded, sessionStatePath, updateSessionState, writeSessionState, writeSessionStateUnlocked, recordSessionNotified, } from './hook-session-state.js';
|
|
11
|
+
import { clearSessionState, markSessionEnded, pendingList, readSessionState, readSessionIncarnation, sessionHasEnded, sessionStatePath, updateSessionState, writeSessionState, writeSessionStateUnlocked, recordSessionNotified, } from './hook-session-state.js';
|
|
12
12
|
import { currentProcessIdentity } from './process-identity.js';
|
|
13
13
|
import { userPromptContextOutput } from './session-activation.js';
|
|
14
14
|
import { answersAlreadyWritten, beginHandOff, recordUnclaimedHandOffs, recoverDeliveryJournal, sweepDeliveryJournals, } from './session-delivery.js';
|
|
@@ -313,7 +313,7 @@ function answerCloseDisposition(ctx, route) {
|
|
|
313
313
|
// question, but an immediate reply can beat the first lease exchange. The
|
|
314
314
|
// service retains the exact-session owner and retries after the lease is
|
|
315
315
|
// ready, so preserve the server's selected delivery claim at close.
|
|
316
|
-
if (ctx.harness === 'openclaw' && route.kind === 'session-queue')
|
|
316
|
+
if ((ctx.harness === 'openclaw' || ctx.harness === 'hermes') && route.kind === 'session-queue')
|
|
317
317
|
return 'deliver';
|
|
318
318
|
return claimableRoute(route) && ctx.answerClaims?.lease() != null ? 'deliver' : undefined;
|
|
319
319
|
}
|
|
@@ -442,7 +442,9 @@ function withReminder(reminder, context) {
|
|
|
442
442
|
* the service, or null when none is.
|
|
443
443
|
*/
|
|
444
444
|
async function messageAcknowledgementReminder(ctx, sessionId) {
|
|
445
|
-
const
|
|
445
|
+
const generation = ctx.harness === 'openclaw' ? readSessionIncarnation(sessionId, ctx.env)?.openclaw_generation : undefined;
|
|
446
|
+
const owed = presentedMessageAcknowledgements(readSessionState(sessionId, ctx.env))
|
|
447
|
+
.filter((entry) => ctx.harness !== 'openclaw' || (generation !== undefined && entry.openclaw_generation === generation));
|
|
446
448
|
if (owed.length === 0)
|
|
447
449
|
return null;
|
|
448
450
|
const still = await reconcileAcknowledgementObligations(ctx, sessionId, owed);
|
|
@@ -982,9 +984,11 @@ async function deliverAcceptedAnswers(ctx, sessionId, route, accepted, notes, cw
|
|
|
982
984
|
}
|
|
983
985
|
await handOff?.finish(delivered.acknowledgement === 'delivered'
|
|
984
986
|
? 'written'
|
|
985
|
-
: delivered.log?.['reason'] ===
|
|
986
|
-
? '
|
|
987
|
-
: '
|
|
987
|
+
: delivered.log?.['reason'] === 'write-unconfirmed'
|
|
988
|
+
? 'failed'
|
|
989
|
+
: delivered.log?.['reason'] === WRITE_ABORTED_REASON
|
|
990
|
+
? 'aborted'
|
|
991
|
+
: 'not-written');
|
|
988
992
|
if (delivered.acknowledgement === 'delivered' && deliveryCommitted) {
|
|
989
993
|
await reportUnclaimedHandOff(ctx, sessionId, answered, handOff);
|
|
990
994
|
}
|
|
@@ -1178,8 +1182,13 @@ async function claimAnswerHandOff(ctx, sessionId, route, answered) {
|
|
|
1178
1182
|
// attendant has had a moment to re-acquire; any other refusal is final.
|
|
1179
1183
|
const fenced = handOff.claimed.length === 0 &&
|
|
1180
1184
|
handOff.refused.every((refusal) => refusal.reason === 'generation_fenced');
|
|
1181
|
-
if (!fenced || round >= ANSWER_CLAIM_ROUNDS)
|
|
1185
|
+
if (!fenced || round >= ANSWER_CLAIM_ROUNDS) {
|
|
1186
|
+
if (ctx.harness === 'hermes' && handOff.claimed.length !== subjects.length) {
|
|
1187
|
+
await handOff.finish('not-written');
|
|
1188
|
+
return null;
|
|
1189
|
+
}
|
|
1182
1190
|
return handOff;
|
|
1191
|
+
}
|
|
1183
1192
|
await handOff.finish('not-written');
|
|
1184
1193
|
await ctx.sleep(ANSWER_CLAIM_RETRY_MS);
|
|
1185
1194
|
}
|
|
@@ -1791,7 +1800,7 @@ async function escalate(ctx, envelope, sessionId, unasked, alreadyLive, notes, h
|
|
|
1791
1800
|
* hook for this session will ever run again. A question whose
|
|
1792
1801
|
* agent just exited can receive no answer, so it is orphaned as `expired`.
|
|
1793
1802
|
*/
|
|
1794
|
-
export function handleSessionEnd(env, envelope, now = Date.now()) {
|
|
1803
|
+
export function handleSessionEnd(env, envelope, now = Date.now(), preserveAccepted = true) {
|
|
1795
1804
|
const notes = [];
|
|
1796
1805
|
const sessionId = envelope.session_id;
|
|
1797
1806
|
if (!sessionId)
|
|
@@ -1840,10 +1849,12 @@ export function handleSessionEnd(env, envelope, now = Date.now()) {
|
|
|
1840
1849
|
orphanRetirements(env, orphans, now);
|
|
1841
1850
|
notes.push(`queued ${orphans.length} question${orphans.length > 1 ? 's' : ''} for retirement on the next hook`);
|
|
1842
1851
|
}
|
|
1843
|
-
if (state.accepted !== undefined ||
|
|
1852
|
+
if ((preserveAccepted && state.accepted !== undefined) ||
|
|
1844
1853
|
(state.acknowledgement_due?.length ?? 0) > 0 ||
|
|
1845
1854
|
(state.message_acknowledgement_due?.length ?? 0) > 0) {
|
|
1846
1855
|
const preserved = { ...stateWithHistory };
|
|
1856
|
+
if (!preserveAccepted)
|
|
1857
|
+
delete preserved.accepted;
|
|
1847
1858
|
delete preserved.pending;
|
|
1848
1859
|
delete preserved.retiring;
|
|
1849
1860
|
delete preserved.acknowledgement_blocks;
|
|
@@ -1854,13 +1865,13 @@ export function handleSessionEnd(env, envelope, now = Date.now()) {
|
|
|
1854
1865
|
delete preserved.message_acknowledgement_due;
|
|
1855
1866
|
}
|
|
1856
1867
|
writeSessionState(sessionId, env, preserved);
|
|
1857
|
-
notes.push(state.accepted !== undefined
|
|
1868
|
+
notes.push(preserveAccepted && state.accepted !== undefined
|
|
1858
1869
|
? 'preserved an accepted device answer for this exact session to resume'
|
|
1859
1870
|
: 'preserved required Agent Acknowledgement obligations for this exact session');
|
|
1860
1871
|
return {
|
|
1861
1872
|
notes,
|
|
1862
1873
|
log: {
|
|
1863
|
-
outcome: state.accepted !== undefined ? 'answer-preserved' : 'acknowledgement-preserved',
|
|
1874
|
+
outcome: preserveAccepted && state.accepted !== undefined ? 'answer-preserved' : 'acknowledgement-preserved',
|
|
1864
1875
|
queued_retirements: orphans.length,
|
|
1865
1876
|
accepted_answers: state.accepted?.answers.length ?? 0,
|
|
1866
1877
|
acknowledgement_due: state.acknowledgement_due?.length ?? 0,
|