@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.
Files changed (45) hide show
  1. package/.pi/extensions/android-tools.ts +24 -3
  2. package/dist/adapters/identity-file.d.ts +33 -5
  3. package/dist/adapters/identity-file.js +52 -6
  4. package/dist/adapters/identity-file.js.map +1 -1
  5. package/dist/app/in-flight-turn.d.ts +120 -0
  6. package/dist/app/in-flight-turn.js +158 -0
  7. package/dist/app/in-flight-turn.js.map +1 -0
  8. package/dist/app/jsonl-store.d.ts +109 -0
  9. package/dist/app/jsonl-store.js +402 -0
  10. package/dist/app/jsonl-store.js.map +1 -0
  11. package/dist/app/message-store.d.ts +82 -31
  12. package/dist/app/message-store.js +109 -48
  13. package/dist/app/message-store.js.map +1 -1
  14. package/dist/app/session-manager.d.ts +120 -57
  15. package/dist/app/session-manager.js +319 -123
  16. package/dist/app/session-manager.js.map +1 -1
  17. package/dist/config/paths.d.ts +48 -0
  18. package/dist/config/paths.js +59 -0
  19. package/dist/config/paths.js.map +1 -0
  20. package/dist/fleet/adapters/process/node-process-manager.js +97 -17
  21. package/dist/fleet/adapters/process/node-process-manager.js.map +1 -1
  22. package/dist/fleet/adapters/state/beeos-cli-store.d.ts +75 -9
  23. package/dist/fleet/adapters/state/beeos-cli-store.js +201 -17
  24. package/dist/fleet/adapters/state/beeos-cli-store.js.map +1 -1
  25. package/dist/fleet/domain/device.d.ts +9 -0
  26. package/dist/fleet/domain/device.js.map +1 -1
  27. package/dist/fleet/domain/fsm.js +34 -0
  28. package/dist/fleet/domain/fsm.js.map +1 -1
  29. package/dist/fleet/installer/spawn-env.d.ts +3 -0
  30. package/dist/fleet/installer/spawn-env.js +186 -3
  31. package/dist/fleet/installer/spawn-env.js.map +1 -1
  32. package/dist/fleet/state/state-json.d.ts +116 -0
  33. package/dist/fleet/state/state-json.js +87 -0
  34. package/dist/fleet/state/state-json.js.map +1 -0
  35. package/dist/fleet/supervisor.js +14 -2
  36. package/dist/fleet/supervisor.js.map +1 -1
  37. package/dist/inbound/acp/handlers/initialize.d.ts +8 -0
  38. package/dist/inbound/acp/handlers/initialize.js +9 -1
  39. package/dist/inbound/acp/handlers/initialize.js.map +1 -1
  40. package/dist/inbound/acp/handlers/session.d.ts +0 -1
  41. package/dist/orchestrator-system-prompt.js +2 -2
  42. package/dist/plugins/acp/index.d.ts +18 -1
  43. package/dist/plugins/acp/index.js +49 -3
  44. package/dist/plugins/acp/index.js.map +1 -1
  45. 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
- * Mirrors the surface of the Python `MessageStore` (`add_message`,
5
- * `get_messages_as_dicts`, `list_sessions`, etc.) so the ACP handlers stay
6
- * byte-for-byte compatible. Persistence is left out of the MVP; `~/.beeos`
7
- * SQLite can be re-added later behind the same interface (pluggable adapter).
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
- maxPerSession;
27
+ maxRecordsPerSession;
12
28
  maxSessions;
13
29
  sessions = new Map();
14
- messages = new Map();
30
+ records = new Map();
15
31
  constructor(opts = {}) {
16
- this.maxPerSession = opts.maxMessagesPerSession ?? 100;
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
- s.title = title;
36
- s.updatedAt = Date.now();
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.messages.delete(sessionId);
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
- clearSession(sessionId) {
50
- this.messages.delete(sessionId);
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
- addMessage(sessionId, role, content, messageType = "text", metadata = {}) {
53
- const id = randomUUID();
54
- const now = Date.now();
55
- const entry = { id, sessionId, role, content, messageType, metadata, createdAt: now };
56
- const arr = this.messages.get(sessionId) ?? [];
57
- arr.push(entry);
58
- if (arr.length > this.maxPerSession)
59
- arr.splice(0, arr.length - this.maxPerSession);
60
- this.messages.set(sessionId, arr);
61
- const s = this.sessions.get(sessionId);
62
- if (s)
63
- s.updatedAt = now;
64
- else
65
- this.upsertSession(sessionId);
66
- return id;
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
- getMessages(sessionId, limit) {
69
- const arr = this.messages.get(sessionId) ?? [];
70
- const n = Math.min(limit ?? this.maxPerSession, arr.length);
71
- return arr.slice(arr.length - n);
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
- getMessagesAsDicts(sessionId, limit) {
74
- return this.getMessages(sessionId, limit).map((m) => {
75
- const out = {
76
- role: m.role,
77
- content: m.content,
78
- type: m.messageType,
79
- createdAt: m.createdAt,
80
- };
81
- if (Object.keys(m.metadata).length > 0)
82
- out.metadata = m.metadata;
83
- return out;
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 we exceed maxSessions. */
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.messages.delete(id);
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":"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"}
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
- * SessionManager itself is protocol-agnostic w.r.t. the agent core — it
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
- * Surface mirrors the Python `SessionHandler` so ACP handlers remain
15
- * thin pass-throughs.
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, type MessageHistoryEntry } from "./message-store.js";
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 adapter
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
- * Handles five legitimate ACP-emitting sources plus structural events:
100
- * - `task:text` — streamed user-visible reply (agent_message_chunk)
101
- * - `task:reasoning` — streaming "thinking" delta (agent_thought_chunk):
102
- * planner plan/replan descriptions and per-step
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
- * Every emitted envelope carries `_turnId` (groups all chunks of a
136
- * single user prompt) and `messageId` (the per-task authoritative id
137
- * shared by every chunk / thought / tool / notice in this turn). The
138
- * web `chat-store` sidecar resolver pins thoughts and tool calls to
139
- * the assistant bubble bearing the matching `_messageId`.
140
- *
141
- * Events for tasks not owned by this SessionManager are ignored —
142
- * other plugins (HTTP debug, custom) may submit tasks through the
143
- * same host bus.
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
  }