@beeos-ai/device-agent 0.5.0 → 0.6.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/.pi/extensions/android-tools.ts +24 -3
- package/dist/adapters/identity-file.d.ts +33 -5
- package/dist/adapters/identity-file.js +52 -6
- package/dist/adapters/identity-file.js.map +1 -1
- package/dist/app/in-flight-turn.d.ts +120 -0
- package/dist/app/in-flight-turn.js +158 -0
- package/dist/app/in-flight-turn.js.map +1 -0
- package/dist/app/jsonl-store.d.ts +109 -0
- package/dist/app/jsonl-store.js +402 -0
- package/dist/app/jsonl-store.js.map +1 -0
- package/dist/app/message-store.d.ts +82 -31
- package/dist/app/message-store.js +109 -48
- package/dist/app/message-store.js.map +1 -1
- package/dist/app/session-manager.d.ts +120 -57
- package/dist/app/session-manager.js +319 -123
- package/dist/app/session-manager.js.map +1 -1
- package/dist/config/paths.d.ts +48 -0
- package/dist/config/paths.js +59 -0
- package/dist/config/paths.js.map +1 -0
- package/dist/fleet/adapters/process/node-process-manager.js +97 -17
- package/dist/fleet/adapters/process/node-process-manager.js.map +1 -1
- package/dist/fleet/adapters/state/beeos-cli-store.d.ts +75 -9
- package/dist/fleet/adapters/state/beeos-cli-store.js +201 -17
- package/dist/fleet/adapters/state/beeos-cli-store.js.map +1 -1
- package/dist/fleet/domain/device.d.ts +9 -0
- package/dist/fleet/domain/device.js.map +1 -1
- package/dist/fleet/domain/fsm.js +34 -0
- package/dist/fleet/domain/fsm.js.map +1 -1
- package/dist/fleet/installer/spawn-env.d.ts +3 -0
- package/dist/fleet/installer/spawn-env.js +186 -3
- package/dist/fleet/installer/spawn-env.js.map +1 -1
- package/dist/fleet/state/state-json.d.ts +116 -0
- package/dist/fleet/state/state-json.js +87 -0
- package/dist/fleet/state/state-json.js.map +1 -0
- package/dist/fleet/supervisor.js +14 -2
- package/dist/fleet/supervisor.js.map +1 -1
- package/dist/inbound/acp/handlers/initialize.d.ts +8 -0
- package/dist/inbound/acp/handlers/initialize.js +9 -1
- package/dist/inbound/acp/handlers/initialize.js.map +1 -1
- package/dist/inbound/acp/handlers/session.d.ts +0 -1
- package/dist/orchestrator-system-prompt.js +2 -2
- package/dist/plugins/acp/index.d.ts +18 -1
- package/dist/plugins/acp/index.js +49 -3
- package/dist/plugins/acp/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,21 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* In-memory MessageStore.
|
|
2
|
+
* In-memory MessageStore — typed turn-grained transcript log.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Each session holds an ordered list of `PersistedRecord`s, one per
|
|
5
|
+
* logical turn:
|
|
6
|
+
*
|
|
7
|
+
* - `user_message` — emitted on `session/prompt` entry.
|
|
8
|
+
* - `assistant_turn` — emitted exactly once per `task:end` (replaces
|
|
9
|
+
* the legacy "one row per chunk" model that produced fragmented
|
|
10
|
+
* transcripts on reload). Carries the full `text`, every tool call
|
|
11
|
+
* in invocation order, the stop reason, and an optional terminal
|
|
12
|
+
* error string.
|
|
13
|
+
*
|
|
14
|
+
* This base class is pure in-memory; persistence is added by
|
|
15
|
+
* `JsonlMessageStore` which extends this and forwards every mutation
|
|
16
|
+
* to disk before returning.
|
|
17
|
+
*
|
|
18
|
+
* The shape is deliberately aligned with OpenClaw's per-agent JSONL
|
|
19
|
+
* (`~/.openclaw/agents/<id>/sessions/<sid>.jsonl`) so operational
|
|
20
|
+
* tooling that already knows that layout works here too.
|
|
8
21
|
*/
|
|
9
22
|
import { randomUUID } from "node:crypto";
|
|
23
|
+
/** Schema version. Bumped only on incompatible disk format changes. */
|
|
24
|
+
export const RECORD_SCHEMA_VERSION = 1;
|
|
25
|
+
/** Mutable in-memory message store. */
|
|
10
26
|
export class MessageStore {
|
|
11
|
-
|
|
27
|
+
maxRecordsPerSession;
|
|
12
28
|
maxSessions;
|
|
13
29
|
sessions = new Map();
|
|
14
|
-
|
|
30
|
+
records = new Map();
|
|
15
31
|
constructor(opts = {}) {
|
|
16
|
-
this.
|
|
32
|
+
this.maxRecordsPerSession = opts.maxRecordsPerSession ?? 200;
|
|
17
33
|
this.maxSessions = opts.maxSessions ?? 200;
|
|
18
34
|
}
|
|
35
|
+
// ── session metadata ─────────────────────────────────────────────────────
|
|
19
36
|
upsertSession(sessionId, title = "New chat") {
|
|
20
37
|
const now = Date.now();
|
|
21
38
|
const cur = this.sessions.get(sessionId);
|
|
@@ -31,14 +48,21 @@ export class MessageStore {
|
|
|
31
48
|
}
|
|
32
49
|
renameSession(sessionId, title) {
|
|
33
50
|
const s = this.sessions.get(sessionId);
|
|
34
|
-
if (s)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
51
|
+
if (!s)
|
|
52
|
+
return;
|
|
53
|
+
s.title = title;
|
|
54
|
+
s.updatedAt = Date.now();
|
|
38
55
|
}
|
|
39
56
|
deleteSession(sessionId) {
|
|
40
57
|
this.sessions.delete(sessionId);
|
|
41
|
-
this.
|
|
58
|
+
this.records.delete(sessionId);
|
|
59
|
+
}
|
|
60
|
+
/** Drop every record in a session but keep the session record itself. */
|
|
61
|
+
clearSession(sessionId) {
|
|
62
|
+
this.records.delete(sessionId);
|
|
63
|
+
const s = this.sessions.get(sessionId);
|
|
64
|
+
if (s)
|
|
65
|
+
s.updatedAt = Date.now();
|
|
42
66
|
}
|
|
43
67
|
listSessions(limit = 50) {
|
|
44
68
|
const all = Array.from(this.sessions.values())
|
|
@@ -46,44 +70,81 @@ export class MessageStore {
|
|
|
46
70
|
.slice(0, limit);
|
|
47
71
|
return all.map((s) => ({ sessionId: s.sessionId, title: s.title, updatedAt: s.updatedAt }));
|
|
48
72
|
}
|
|
49
|
-
|
|
50
|
-
|
|
73
|
+
// ── record append ────────────────────────────────────────────────────────
|
|
74
|
+
/** Append a user prompt row. Returns the persisted record. */
|
|
75
|
+
appendUserMessage(sessionId, content, opts = {}) {
|
|
76
|
+
const ts = opts.now ?? Date.now();
|
|
77
|
+
const record = {
|
|
78
|
+
v: RECORD_SCHEMA_VERSION,
|
|
79
|
+
type: "user_message",
|
|
80
|
+
id: randomUUID(),
|
|
81
|
+
ts,
|
|
82
|
+
content,
|
|
83
|
+
...(opts.turnId ? { turnId: opts.turnId } : {}),
|
|
84
|
+
...(opts.messageId ? { messageId: opts.messageId } : {}),
|
|
85
|
+
};
|
|
86
|
+
this.persistRecord(sessionId, record, ts);
|
|
87
|
+
return record;
|
|
51
88
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
89
|
+
/** Append a finalized assistant turn. Returns the persisted record. */
|
|
90
|
+
appendAssistantTurn(sessionId, turn, opts = {}) {
|
|
91
|
+
const ts = opts.now ?? turn.completedAt ?? Date.now();
|
|
92
|
+
const record = {
|
|
93
|
+
...turn,
|
|
94
|
+
v: RECORD_SCHEMA_VERSION,
|
|
95
|
+
type: "assistant_turn",
|
|
96
|
+
id: randomUUID(),
|
|
97
|
+
ts,
|
|
98
|
+
};
|
|
99
|
+
this.persistRecord(sessionId, record, ts);
|
|
100
|
+
return record;
|
|
101
|
+
}
|
|
102
|
+
/** Pre-loaded record (used by `JsonlMessageStore.open` to seed memory). */
|
|
103
|
+
loadRecord(sessionId, record) {
|
|
104
|
+
this.persistRecord(sessionId, record, record.ts, /*touch*/ false);
|
|
105
|
+
}
|
|
106
|
+
/** All records for a session in chronological insertion order. */
|
|
107
|
+
getTurns(sessionId) {
|
|
108
|
+
return [...(this.records.get(sessionId) ?? [])];
|
|
67
109
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
return arr
|
|
110
|
+
/** True iff at least one record exists for this session. */
|
|
111
|
+
hasRecords(sessionId) {
|
|
112
|
+
const arr = this.records.get(sessionId);
|
|
113
|
+
return !!arr && arr.length > 0;
|
|
72
114
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
115
|
+
// ── internals ────────────────────────────────────────────────────────────
|
|
116
|
+
/**
|
|
117
|
+
* Append a record to memory and bump session.updatedAt.
|
|
118
|
+
* Subclasses (JsonlMessageStore) override to additionally persist.
|
|
119
|
+
*
|
|
120
|
+
* `touch=false` is used by `loadRecord` so a cold-start replay of a
|
|
121
|
+
* year-old transcript doesn't artificially bump updatedAt to "now".
|
|
122
|
+
*/
|
|
123
|
+
persistRecord(sessionId, record, ts, touch = true) {
|
|
124
|
+
const arr = this.records.get(sessionId) ?? [];
|
|
125
|
+
arr.push(record);
|
|
126
|
+
if (arr.length > this.maxRecordsPerSession) {
|
|
127
|
+
arr.splice(0, arr.length - this.maxRecordsPerSession);
|
|
128
|
+
}
|
|
129
|
+
this.records.set(sessionId, arr);
|
|
130
|
+
const s = this.sessions.get(sessionId);
|
|
131
|
+
if (s) {
|
|
132
|
+
if (touch)
|
|
133
|
+
s.updatedAt = ts;
|
|
134
|
+
else if (ts > s.updatedAt)
|
|
135
|
+
s.updatedAt = ts;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
this.sessions.set(sessionId, {
|
|
139
|
+
sessionId,
|
|
140
|
+
title: "New chat",
|
|
141
|
+
createdAt: ts,
|
|
142
|
+
updatedAt: ts,
|
|
143
|
+
});
|
|
144
|
+
this.evictSessionLru();
|
|
145
|
+
}
|
|
85
146
|
}
|
|
86
|
-
/** Drop the least-recently-updated sessions when
|
|
147
|
+
/** Drop the least-recently-updated sessions when exceeding maxSessions. */
|
|
87
148
|
evictSessionLru() {
|
|
88
149
|
if (this.sessions.size <= this.maxSessions)
|
|
89
150
|
return;
|
|
@@ -92,7 +153,7 @@ export class MessageStore {
|
|
|
92
153
|
for (let i = 0; i < overflow; i++) {
|
|
93
154
|
const id = sorted[i].sessionId;
|
|
94
155
|
this.sessions.delete(id);
|
|
95
|
-
this.
|
|
156
|
+
this.records.delete(id);
|
|
96
157
|
}
|
|
97
158
|
}
|
|
98
159
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message-store.js","sourceRoot":"","sources":["../../src/app/message-store.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"message-store.js","sourceRoot":"","sources":["../../src/app/message-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,18 +6,43 @@
|
|
|
6
6
|
* `session/update` envelopes are produced by translating `AgentEvent`s
|
|
7
7
|
* received from the host bus.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
* speaks `AgentHost` only. The translation of host events into ACP
|
|
11
|
-
* envelopes lives here so swapping `IAgentCore` (single-layer / pi
|
|
12
|
-
* orchestrator / mock) doesn't change ACP semantics.
|
|
9
|
+
* Persistence model — turn-grained:
|
|
13
10
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
11
|
+
* - User prompts persist immediately on entry to `prompt()` (one
|
|
12
|
+
* `user_message` JSONL row).
|
|
13
|
+
* - Assistant chunks (`task:text`) and tool events (`task:tool`) are
|
|
14
|
+
* accumulated in an in-memory `InFlightTurnAggregator`; nothing is
|
|
15
|
+
* written until `task:end`, at which point a single
|
|
16
|
+
* `assistant_turn` JSONL row captures the entire turn (full text,
|
|
17
|
+
* every tool call in invocation order, stop reason, optional
|
|
18
|
+
* terminal error). This matches OpenClaw's "one line = one
|
|
19
|
+
* complete message" model and avoids the fragmented transcript
|
|
20
|
+
* produced by the legacy "one row per chunk" implementation.
|
|
21
|
+
*
|
|
22
|
+
* Single-session mode (`fixedSessionId`):
|
|
23
|
+
*
|
|
24
|
+
* When a fixedSessionId is supplied, every ACP session-management
|
|
25
|
+
* method (`new`/`load`/`list`/`rename`/`delete`) collapses onto that
|
|
26
|
+
* one sid. The mobile/web client receives `multipleSessions: false`
|
|
27
|
+
* in the initialize handshake so the UI hides session-switching
|
|
28
|
+
* affordances. This is the production configuration for device-agent
|
|
29
|
+
* — a single physical device serialises tasks anyway, and a fixed
|
|
30
|
+
* sid keeps the JSONL transcript stable across reconnects.
|
|
31
|
+
*
|
|
32
|
+
* Replay model — push-style:
|
|
33
|
+
*
|
|
34
|
+
* `session/load` does NOT return history in the RPC `result` (the
|
|
35
|
+
* web/mobile SDKs ignore that field). Instead, it pushes one
|
|
36
|
+
* `history_turn` envelope per persisted turn followed by an
|
|
37
|
+
* in-flight snapshot (streaming-style, WITHOUT `stream_complete`)
|
|
38
|
+
* when a task is mid-flight. This matches beeos-claw's
|
|
39
|
+
* `handleSessionLoad` exactly so the same chat-store reducer paths
|
|
40
|
+
* handle both agents.
|
|
16
41
|
*/
|
|
17
|
-
import { MessageStore
|
|
42
|
+
import { MessageStore } from "./message-store.js";
|
|
18
43
|
import type { AgentHost } from "../ports/agent-host.js";
|
|
19
44
|
export interface SessionUpdateNotifier {
|
|
20
|
-
/** Called for every step during `session/prompt
|
|
45
|
+
/** Called for every step during `session/prompt` (and during `session/load` replay). */
|
|
21
46
|
send(sessionId: string, update: Record<string, unknown>): void;
|
|
22
47
|
}
|
|
23
48
|
export interface SessionManagerOptions {
|
|
@@ -26,10 +51,16 @@ export interface SessionManagerOptions {
|
|
|
26
51
|
notifier?: SessionUpdateNotifier;
|
|
27
52
|
/**
|
|
28
53
|
* Called when the user asks to switch the underlying agent model.
|
|
29
|
-
* `provider` may be empty when the caller doesn't pin a specific
|
|
30
|
-
* (auto-detect from `model`).
|
|
54
|
+
* `provider` may be empty when the caller doesn't pin a specific
|
|
55
|
+
* adapter (auto-detect from `model`).
|
|
31
56
|
*/
|
|
32
57
|
onSetModel?: (provider: string, model: string) => Promise<void> | void;
|
|
58
|
+
/**
|
|
59
|
+
* If set, single-session mode is active: every session-management
|
|
60
|
+
* method aliases to this sid, `multipleSessions` should be advertised
|
|
61
|
+
* as `false` in `initialize`, and `delete` collapses to `clear`.
|
|
62
|
+
*/
|
|
63
|
+
fixedSessionId?: string;
|
|
33
64
|
maxSessions?: number;
|
|
34
65
|
sessionTtlMs?: number;
|
|
35
66
|
}
|
|
@@ -38,11 +69,14 @@ export declare class SessionManager {
|
|
|
38
69
|
private readonly store;
|
|
39
70
|
private readonly notifier;
|
|
40
71
|
private readonly onSetModel;
|
|
72
|
+
private readonly fixedSessionId;
|
|
41
73
|
private readonly maxSessions;
|
|
42
74
|
private readonly sessionTtlMs;
|
|
43
75
|
private readonly sessions;
|
|
44
76
|
/** taskId → sessionId for routing host events to the right ACP session. */
|
|
45
77
|
private readonly taskToSession;
|
|
78
|
+
/** Per-task in-flight aggregators. Keyed by taskId. */
|
|
79
|
+
private readonly inFlightByTask;
|
|
46
80
|
private activeSessionId;
|
|
47
81
|
private currentTask;
|
|
48
82
|
private unsubscribe;
|
|
@@ -52,9 +86,22 @@ export declare class SessionManager {
|
|
|
52
86
|
newSession(): {
|
|
53
87
|
sessionId: string;
|
|
54
88
|
};
|
|
89
|
+
/**
|
|
90
|
+
* Load a session.
|
|
91
|
+
*
|
|
92
|
+
* Push-replay model: emit one `history_turn` envelope per persisted
|
|
93
|
+
* turn so the chat-store sidecar resolver can reconstruct the bubble
|
|
94
|
+
* graph identically to a fresh streaming run. If a task is currently
|
|
95
|
+
* in-flight on this sid, additionally emit a streaming-style
|
|
96
|
+
* snapshot (`agent_message_chunk` + `tool_call` + `tool_call_update`)
|
|
97
|
+
* WITHOUT the closing `stream_complete` so the client knows the turn
|
|
98
|
+
* is still active.
|
|
99
|
+
*
|
|
100
|
+
* Returns `{ sessionId }` only — the legacy `history` field has been
|
|
101
|
+
* dropped because no client reads it (see explore audit).
|
|
102
|
+
*/
|
|
55
103
|
loadSession(sessionId: string): {
|
|
56
104
|
sessionId: string;
|
|
57
|
-
history: MessageHistoryEntry[];
|
|
58
105
|
};
|
|
59
106
|
listSessions(): {
|
|
60
107
|
sessions: {
|
|
@@ -67,6 +114,14 @@ export declare class SessionManager {
|
|
|
67
114
|
sessionId: string;
|
|
68
115
|
title: string;
|
|
69
116
|
};
|
|
117
|
+
/**
|
|
118
|
+
* Delete a session.
|
|
119
|
+
*
|
|
120
|
+
* In single-session mode, this collapses to `clearSession` because
|
|
121
|
+
* the one-and-only session can't be removed without losing the
|
|
122
|
+
* device's identity. Otherwise drops the in-memory + persisted state
|
|
123
|
+
* for `sessionId`.
|
|
124
|
+
*/
|
|
70
125
|
deleteSession(sessionId: string): {
|
|
71
126
|
deleted: boolean;
|
|
72
127
|
};
|
|
@@ -94,56 +149,64 @@ export declare class SessionManager {
|
|
|
94
149
|
/** Cancel the in-flight task for the active session, if any. */
|
|
95
150
|
cancel(): Promise<boolean>;
|
|
96
151
|
/**
|
|
97
|
-
* Translate host `AgentEvent`s into ACP `session/update` envelopes
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* VLM rationale.
|
|
104
|
-
* - `task:tool` — start / update / end (tool_call / tool_call_update).
|
|
105
|
-
* `update` carries partial progress for long-running
|
|
106
|
-
* tools (large APK install, long shell streams,
|
|
107
|
-
* large screenshots) so the chat-store tool card
|
|
108
|
-
* can refresh in-place without waiting for `end`.
|
|
109
|
-
* Mirrors beeos-claw's tool_call_update in_progress
|
|
110
|
-
* semantics (see acp-gateway/tool-call-payload-builder.ts).
|
|
111
|
-
* - `task:notice` — out-of-band protocol notice. level=warn
|
|
112
|
-
* (transient retry hints) is dropped — noise
|
|
113
|
-
* the user can't act on. level=error (terminal
|
|
114
|
-
* failure) becomes a `system_message_chunk`
|
|
115
|
-
* with `metadata.kind = "agent_notice"`; the
|
|
116
|
-
* web client merges it into the matching
|
|
117
|
-
* assistant bubble's footer. NOT persisted.
|
|
118
|
-
* - `task:step` — structural step record (no ACP emission, kept
|
|
119
|
-
* for HTTP debug / future plugins).
|
|
120
|
-
* - `task:end` — terminal lifecycle event. We emit the canonical
|
|
121
|
-
* ACP `stream_complete` envelope here (BEFORE the
|
|
122
|
-
* `prompt()` finally block fires `agent_status:
|
|
123
|
-
* "idle"`). chat-store's `applyIncrementalUpdate`
|
|
124
|
-
* relies on `stream_complete` to (a) collapse
|
|
125
|
-
* streaming text part `state` from "streaming"
|
|
126
|
-
* back to "done" so the cursor caret stops
|
|
127
|
-
* rendering, and (b) trim `pendingMap` /
|
|
128
|
-
* `currentAsstId` so history-replay paths don't
|
|
129
|
-
* re-flag the just-finished turn as processing.
|
|
130
|
-
* Without it the bubble caret never collapses
|
|
131
|
-
* and reload paths see a stale `processing`
|
|
132
|
-
* pending — see plan
|
|
133
|
-
* `device-agent_stream_complete_对齐`.
|
|
152
|
+
* Translate host `AgentEvent`s into ACP `session/update` envelopes
|
|
153
|
+
* AND accumulate the assistant turn into the in-memory aggregator
|
|
154
|
+
* for this task. The aggregator is finalised on `task:end` and
|
|
155
|
+
* persisted as a single `assistant_turn` JSONL row — chunk-level
|
|
156
|
+
* persistence is intentionally NOT done (it produces fragmented
|
|
157
|
+
* transcripts that can't be reconstructed cleanly on reload).
|
|
134
158
|
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
159
|
+
* Source events:
|
|
160
|
+
* - `task:start` — allocate the in-flight aggregator.
|
|
161
|
+
* - `task:text` — append delta to in-flight + emit
|
|
162
|
+
* `agent_message_chunk`.
|
|
163
|
+
* - `task:reasoning` — emit `agent_thought_chunk` only (NOT
|
|
164
|
+
* persisted — bloats LLM context on resume).
|
|
165
|
+
* - `task:tool` — track in in-flight + emit
|
|
166
|
+
* `tool_call` / `tool_call_update`. Mid-flight
|
|
167
|
+
* progress is forwarded as `in_progress`.
|
|
168
|
+
* - `task:notice` — `warn` is dropped entirely; `error` becomes
|
|
169
|
+
* a `system_message_chunk` with
|
|
170
|
+
* `metadata.kind = "agent_notice"` AND is
|
|
171
|
+
* captured as the in-flight `error` field so
|
|
172
|
+
* the persisted turn carries the failure
|
|
173
|
+
* text.
|
|
174
|
+
* - `task:step` — structural; no ACP emission.
|
|
175
|
+
* - `task:end` — finalise the in-flight, persist a single
|
|
176
|
+
* `assistant_turn` row, emit `stream_complete`
|
|
177
|
+
* BEFORE the closing `agent_status: idle`
|
|
178
|
+
* (chat-store relies on this order).
|
|
144
179
|
*/
|
|
145
180
|
private handleHostEvent;
|
|
146
181
|
private resolveSession;
|
|
182
|
+
/** Map any sid the client passes onto the canonical sid in fixed mode. */
|
|
183
|
+
private aliasSessionId;
|
|
184
|
+
/** Materialise the fixed session in-memory + on disk if it isn't yet. */
|
|
185
|
+
private ensureFixedSession;
|
|
186
|
+
/**
|
|
187
|
+
* Lazily create an in-flight aggregator for events that arrive
|
|
188
|
+
* without a preceding `task:start` (defensive — agent-host always
|
|
189
|
+
* fires start, but tests + alternate adapters may not).
|
|
190
|
+
*/
|
|
191
|
+
private ensureInflight;
|
|
192
|
+
private findInflightForSession;
|
|
193
|
+
/**
|
|
194
|
+
* Emit a single persisted record as a `history_turn` envelope.
|
|
195
|
+
*
|
|
196
|
+
* Mirrors beeos-claw `bridge-core.replayAssistantTurnAsStream` —
|
|
197
|
+
* `history_turn` is a stamped-done snapshot of an entire turn that
|
|
198
|
+
* the chat-store reducer renders as a complete bubble (no streaming
|
|
199
|
+
* cursor). For assistant turns, we serialise tool calls into the
|
|
200
|
+
* shape `apply-update.buildToolCalls` accepts.
|
|
201
|
+
*/
|
|
202
|
+
private emitHistoryTurn;
|
|
203
|
+
/**
|
|
204
|
+
* Emit the current state of an in-flight turn as streaming-style
|
|
205
|
+
* envelopes WITHOUT the closing `stream_complete`. The client uses
|
|
206
|
+
* the absence of `stream_complete` to recognise the turn is still
|
|
207
|
+
* generating, matching beeos-claw's `emitCurrentStreamSnapshot`.
|
|
208
|
+
*/
|
|
209
|
+
private emitInflightSnapshot;
|
|
147
210
|
private notify;
|
|
148
211
|
private evictExpired;
|
|
149
212
|
}
|