@beeos-ai/device-agent 0.5.1 → 0.7.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 (51) hide show
  1. package/.pi/extensions/android-tools.ts +24 -3
  2. package/dist/adapters/agent-gateway-client.d.ts +25 -0
  3. package/dist/adapters/agent-gateway-client.js +49 -0
  4. package/dist/adapters/agent-gateway-client.js.map +1 -1
  5. package/dist/adapters/user-file-uploader.d.ts +74 -0
  6. package/dist/adapters/user-file-uploader.js +78 -0
  7. package/dist/adapters/user-file-uploader.js.map +1 -0
  8. package/dist/app/in-flight-turn.d.ts +120 -0
  9. package/dist/app/in-flight-turn.js +158 -0
  10. package/dist/app/in-flight-turn.js.map +1 -0
  11. package/dist/app/jsonl-store.d.ts +109 -0
  12. package/dist/app/jsonl-store.js +402 -0
  13. package/dist/app/jsonl-store.js.map +1 -0
  14. package/dist/app/message-store.d.ts +82 -31
  15. package/dist/app/message-store.js +109 -48
  16. package/dist/app/message-store.js.map +1 -1
  17. package/dist/app/plugin-commands/registry.d.ts +41 -0
  18. package/dist/app/plugin-commands/registry.js +62 -0
  19. package/dist/app/plugin-commands/registry.js.map +1 -0
  20. package/dist/app/plugin-commands/screenshot-command.d.ts +32 -0
  21. package/dist/app/plugin-commands/screenshot-command.js +44 -0
  22. package/dist/app/plugin-commands/screenshot-command.js.map +1 -0
  23. package/dist/app/plugin-commands/types.d.ts +63 -0
  24. package/dist/app/plugin-commands/types.js +26 -0
  25. package/dist/app/plugin-commands/types.js.map +1 -0
  26. package/dist/app/screenshot-service.d.ts +68 -0
  27. package/dist/app/screenshot-service.js +64 -0
  28. package/dist/app/screenshot-service.js.map +1 -0
  29. package/dist/app/session-manager.d.ts +150 -55
  30. package/dist/app/session-manager.js +385 -122
  31. package/dist/app/session-manager.js.map +1 -1
  32. package/dist/bootstrap.js +5 -1
  33. package/dist/bootstrap.js.map +1 -1
  34. package/dist/config/paths.d.ts +48 -0
  35. package/dist/config/paths.js +59 -0
  36. package/dist/config/paths.js.map +1 -0
  37. package/dist/inbound/acp/handlers/device.d.ts +9 -1
  38. package/dist/inbound/acp/handlers/device.js +9 -1
  39. package/dist/inbound/acp/handlers/device.js.map +1 -1
  40. package/dist/inbound/acp/handlers/initialize.d.ts +17 -5
  41. package/dist/inbound/acp/handlers/initialize.js +19 -7
  42. package/dist/inbound/acp/handlers/initialize.js.map +1 -1
  43. package/dist/inbound/acp/handlers/session.d.ts +0 -1
  44. package/dist/inbound/acp/handlers/skills.d.ts +24 -2
  45. package/dist/inbound/acp/handlers/skills.js +31 -2
  46. package/dist/inbound/acp/handlers/skills.js.map +1 -1
  47. package/dist/orchestrator-system-prompt.js +2 -2
  48. package/dist/plugins/acp/index.d.ts +26 -1
  49. package/dist/plugins/acp/index.js +86 -3
  50. package/dist/plugins/acp/index.js.map +1 -1
  51. package/package.json +1 -1
@@ -6,18 +6,44 @@
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";
44
+ import type { PluginCommandRegistry } from "./plugin-commands/registry.js";
19
45
  export interface SessionUpdateNotifier {
20
- /** Called for every step during `session/prompt`. */
46
+ /** Called for every step during `session/prompt` (and during `session/load` replay). */
21
47
  send(sessionId: string, update: Record<string, unknown>): void;
22
48
  }
23
49
  export interface SessionManagerOptions {
@@ -26,23 +52,42 @@ export interface SessionManagerOptions {
26
52
  notifier?: SessionUpdateNotifier;
27
53
  /**
28
54
  * 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`).
55
+ * `provider` may be empty when the caller doesn't pin a specific
56
+ * adapter (auto-detect from `model`).
31
57
  */
32
58
  onSetModel?: (provider: string, model: string) => Promise<void> | void;
59
+ /**
60
+ * If set, single-session mode is active: every session-management
61
+ * method aliases to this sid, `multipleSessions` should be advertised
62
+ * as `false` in `initialize`, and `delete` collapses to `clear`.
63
+ */
64
+ fixedSessionId?: string;
33
65
  maxSessions?: number;
34
66
  sessionTtlMs?: number;
67
+ /**
68
+ * Optional plugin command registry. When set, `prompt()` does a
69
+ * prefix match against incoming text BEFORE dispatching to the
70
+ * AgentRunner — `/screenshot` and friends short-circuit straight
71
+ * to their handlers, bypassing the LLM entirely (mirrors OpenClaw
72
+ * `matchPluginCommand` semantics, see
73
+ * `openclaw/src/plugins/commands.ts`).
74
+ */
75
+ pluginCommands?: PluginCommandRegistry;
35
76
  }
36
77
  export declare class SessionManager {
37
78
  private readonly host;
38
79
  private readonly store;
39
80
  private readonly notifier;
40
81
  private readonly onSetModel;
82
+ private readonly fixedSessionId;
41
83
  private readonly maxSessions;
42
84
  private readonly sessionTtlMs;
85
+ private readonly pluginCommands;
43
86
  private readonly sessions;
44
87
  /** taskId → sessionId for routing host events to the right ACP session. */
45
88
  private readonly taskToSession;
89
+ /** Per-task in-flight aggregators. Keyed by taskId. */
90
+ private readonly inFlightByTask;
46
91
  private activeSessionId;
47
92
  private currentTask;
48
93
  private unsubscribe;
@@ -52,9 +97,22 @@ export declare class SessionManager {
52
97
  newSession(): {
53
98
  sessionId: string;
54
99
  };
100
+ /**
101
+ * Load a session.
102
+ *
103
+ * Push-replay model: emit one `history_turn` envelope per persisted
104
+ * turn so the chat-store sidecar resolver can reconstruct the bubble
105
+ * graph identically to a fresh streaming run. If a task is currently
106
+ * in-flight on this sid, additionally emit a streaming-style
107
+ * snapshot (`agent_message_chunk` + `tool_call` + `tool_call_update`)
108
+ * WITHOUT the closing `stream_complete` so the client knows the turn
109
+ * is still active.
110
+ *
111
+ * Returns `{ sessionId }` only — the legacy `history` field has been
112
+ * dropped because no client reads it (see explore audit).
113
+ */
55
114
  loadSession(sessionId: string): {
56
115
  sessionId: string;
57
- history: MessageHistoryEntry[];
58
116
  };
59
117
  listSessions(): {
60
118
  sessions: {
@@ -67,6 +125,14 @@ export declare class SessionManager {
67
125
  sessionId: string;
68
126
  title: string;
69
127
  };
128
+ /**
129
+ * Delete a session.
130
+ *
131
+ * In single-session mode, this collapses to `clearSession` because
132
+ * the one-and-only session can't be removed without losing the
133
+ * device's identity. Otherwise drops the in-memory + persisted state
134
+ * for `sessionId`.
135
+ */
70
136
  deleteSession(sessionId: string): {
71
137
  deleted: boolean;
72
138
  };
@@ -94,56 +160,85 @@ export declare class SessionManager {
94
160
  /** Cancel the in-flight task for the active session, if any. */
95
161
  cancel(): Promise<boolean>;
96
162
  /**
97
- * Translate host `AgentEvent`s into ACP `session/update` envelopes.
163
+ * Dispatch a `/command` prompt to its registered handler, bypassing
164
+ * the AgentRunner / LLM. The wire shape stays compatible with the
165
+ * regular prompt path:
166
+ *
167
+ * 1. user message persisted (so reload sees the typed `/screenshot`)
168
+ * 2. `agent_status: running`
169
+ * 3. handler runs — typically emits its own `agent_message_chunk`
170
+ * with a `resource_link` (see `ScreenshotCommandHandler`)
171
+ * 4. `stream_complete` + `agent_status: idle`
98
172
  *
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_对齐`.
173
+ * Errors from the handler are converted into a `system_message_chunk`
174
+ * notice (level=error) instead of crashing the whole session.
134
175
  *
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`.
176
+ * NOTE: assistant turns produced by plugin commands are intentionally
177
+ * NOT persisted — slash command output is time-sensitive (a stale
178
+ * screenshot helps no one) and the user should re-issue the command
179
+ * on reconnect. The user-side `/screenshot` text IS persisted so the
180
+ * conversation log makes sense.
181
+ */
182
+ private handlePluginCommand;
183
+ /**
184
+ * Translate host `AgentEvent`s into ACP `session/update` envelopes
185
+ * AND accumulate the assistant turn into the in-memory aggregator
186
+ * for this task. The aggregator is finalised on `task:end` and
187
+ * persisted as a single `assistant_turn` JSONL row — chunk-level
188
+ * persistence is intentionally NOT done (it produces fragmented
189
+ * transcripts that can't be reconstructed cleanly on reload).
140
190
  *
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.
191
+ * Source events:
192
+ * - `task:start` — allocate the in-flight aggregator.
193
+ * - `task:text` — append delta to in-flight + emit
194
+ * `agent_message_chunk`.
195
+ * - `task:reasoning` — emit `agent_thought_chunk` only (NOT
196
+ * persisted — bloats LLM context on resume).
197
+ * - `task:tool` — track in in-flight + emit
198
+ * `tool_call` / `tool_call_update`. Mid-flight
199
+ * progress is forwarded as `in_progress`.
200
+ * - `task:notice` — `warn` is dropped entirely; `error` becomes
201
+ * a `system_message_chunk` with
202
+ * `metadata.kind = "agent_notice"` AND is
203
+ * captured as the in-flight `error` field so
204
+ * the persisted turn carries the failure
205
+ * text.
206
+ * - `task:step` — structural; no ACP emission.
207
+ * - `task:end` — finalise the in-flight, persist a single
208
+ * `assistant_turn` row, emit `stream_complete`
209
+ * BEFORE the closing `agent_status: idle`
210
+ * (chat-store relies on this order).
144
211
  */
145
212
  private handleHostEvent;
146
213
  private resolveSession;
214
+ /** Map any sid the client passes onto the canonical sid in fixed mode. */
215
+ private aliasSessionId;
216
+ /** Materialise the fixed session in-memory + on disk if it isn't yet. */
217
+ private ensureFixedSession;
218
+ /**
219
+ * Lazily create an in-flight aggregator for events that arrive
220
+ * without a preceding `task:start` (defensive — agent-host always
221
+ * fires start, but tests + alternate adapters may not).
222
+ */
223
+ private ensureInflight;
224
+ private findInflightForSession;
225
+ /**
226
+ * Emit a single persisted record as a `history_turn` envelope.
227
+ *
228
+ * Mirrors beeos-claw `bridge-core.replayAssistantTurnAsStream` —
229
+ * `history_turn` is a stamped-done snapshot of an entire turn that
230
+ * the chat-store reducer renders as a complete bubble (no streaming
231
+ * cursor). For assistant turns, we serialise tool calls into the
232
+ * shape `apply-update.buildToolCalls` accepts.
233
+ */
234
+ private emitHistoryTurn;
235
+ /**
236
+ * Emit the current state of an in-flight turn as streaming-style
237
+ * envelopes WITHOUT the closing `stream_complete`. The client uses
238
+ * the absence of `stream_complete` to recognise the turn is still
239
+ * generating, matching beeos-claw's `emitCurrentStreamSnapshot`.
240
+ */
241
+ private emitInflightSnapshot;
147
242
  private notify;
148
243
  private evictExpired;
149
244
  }