@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.
- package/.pi/extensions/android-tools.ts +24 -3
- package/dist/adapters/agent-gateway-client.d.ts +25 -0
- package/dist/adapters/agent-gateway-client.js +49 -0
- package/dist/adapters/agent-gateway-client.js.map +1 -1
- package/dist/adapters/user-file-uploader.d.ts +74 -0
- package/dist/adapters/user-file-uploader.js +78 -0
- package/dist/adapters/user-file-uploader.js.map +1 -0
- 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/plugin-commands/registry.d.ts +41 -0
- package/dist/app/plugin-commands/registry.js +62 -0
- package/dist/app/plugin-commands/registry.js.map +1 -0
- package/dist/app/plugin-commands/screenshot-command.d.ts +32 -0
- package/dist/app/plugin-commands/screenshot-command.js +44 -0
- package/dist/app/plugin-commands/screenshot-command.js.map +1 -0
- package/dist/app/plugin-commands/types.d.ts +63 -0
- package/dist/app/plugin-commands/types.js +26 -0
- package/dist/app/plugin-commands/types.js.map +1 -0
- package/dist/app/screenshot-service.d.ts +68 -0
- package/dist/app/screenshot-service.js +64 -0
- package/dist/app/screenshot-service.js.map +1 -0
- package/dist/app/session-manager.d.ts +150 -55
- package/dist/app/session-manager.js +385 -122
- package/dist/app/session-manager.js.map +1 -1
- package/dist/bootstrap.js +5 -1
- package/dist/bootstrap.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/inbound/acp/handlers/device.d.ts +9 -1
- package/dist/inbound/acp/handlers/device.js +9 -1
- package/dist/inbound/acp/handlers/device.js.map +1 -1
- package/dist/inbound/acp/handlers/initialize.d.ts +17 -5
- package/dist/inbound/acp/handlers/initialize.js +19 -7
- package/dist/inbound/acp/handlers/initialize.js.map +1 -1
- package/dist/inbound/acp/handlers/session.d.ts +0 -1
- package/dist/inbound/acp/handlers/skills.d.ts +24 -2
- package/dist/inbound/acp/handlers/skills.js +31 -2
- package/dist/inbound/acp/handlers/skills.js.map +1 -1
- package/dist/orchestrator-system-prompt.js +2 -2
- package/dist/plugins/acp/index.d.ts +26 -1
- package/dist/plugins/acp/index.js +86 -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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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PluginCommandRegistry — name → handler lookup with prefix matching.
|
|
3
|
+
*
|
|
4
|
+
* Simplified port of OpenClaw's `matchPluginCommand`
|
|
5
|
+
* (`openclaw/src/plugins/commands.ts:62-100`). Drops some of the
|
|
6
|
+
* upstream high-end features (hyphen/underscore aliases, alternate
|
|
7
|
+
* invocation keys) — the device-agent only needs `/screenshot` today
|
|
8
|
+
* and we'll port more as needed.
|
|
9
|
+
*
|
|
10
|
+
* Match contract:
|
|
11
|
+
* - Input MUST start with `/`. Whitespace-only / non-slash strings
|
|
12
|
+
* short-circuit to `null` so natural-language prompts ("截屏发给
|
|
13
|
+
* 我") fall through unchanged to the AgentRunner.
|
|
14
|
+
* - Command name extraction: `/<name>` or `/<name> <args...>`.
|
|
15
|
+
* Names match case-insensitively (typing `/Screenshot` still
|
|
16
|
+
* hits the registered handler).
|
|
17
|
+
* - Argument gating: when the matched command has `acceptsArgs:
|
|
18
|
+
* false` (default), passing arguments DROPS the match — the line
|
|
19
|
+
* falls through to the AgentRunner so a misspelled builtin
|
|
20
|
+
* doesn't accidentally swallow real user intent.
|
|
21
|
+
*/
|
|
22
|
+
import type { PluginCommand } from "./types.js";
|
|
23
|
+
export interface PluginCommandMatch {
|
|
24
|
+
command: PluginCommand;
|
|
25
|
+
args?: string;
|
|
26
|
+
}
|
|
27
|
+
export declare class PluginCommandRegistry {
|
|
28
|
+
private readonly commands;
|
|
29
|
+
/** Register (or replace) a command. Names are case-insensitive. */
|
|
30
|
+
register(cmd: PluginCommand): void;
|
|
31
|
+
/**
|
|
32
|
+
* Match a free-form prompt string against the registry. Returns the
|
|
33
|
+
* matched command + parsed args, or `null` when the prompt is not
|
|
34
|
+
* a slash command or doesn't match any registered name.
|
|
35
|
+
*/
|
|
36
|
+
match(text: string): PluginCommandMatch | null;
|
|
37
|
+
/** Snapshot of registered commands, in insertion order. */
|
|
38
|
+
list(): PluginCommand[];
|
|
39
|
+
/** Number of registered commands — convenience for tests. */
|
|
40
|
+
size(): number;
|
|
41
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PluginCommandRegistry — name → handler lookup with prefix matching.
|
|
3
|
+
*
|
|
4
|
+
* Simplified port of OpenClaw's `matchPluginCommand`
|
|
5
|
+
* (`openclaw/src/plugins/commands.ts:62-100`). Drops some of the
|
|
6
|
+
* upstream high-end features (hyphen/underscore aliases, alternate
|
|
7
|
+
* invocation keys) — the device-agent only needs `/screenshot` today
|
|
8
|
+
* and we'll port more as needed.
|
|
9
|
+
*
|
|
10
|
+
* Match contract:
|
|
11
|
+
* - Input MUST start with `/`. Whitespace-only / non-slash strings
|
|
12
|
+
* short-circuit to `null` so natural-language prompts ("截屏发给
|
|
13
|
+
* 我") fall through unchanged to the AgentRunner.
|
|
14
|
+
* - Command name extraction: `/<name>` or `/<name> <args...>`.
|
|
15
|
+
* Names match case-insensitively (typing `/Screenshot` still
|
|
16
|
+
* hits the registered handler).
|
|
17
|
+
* - Argument gating: when the matched command has `acceptsArgs:
|
|
18
|
+
* false` (default), passing arguments DROPS the match — the line
|
|
19
|
+
* falls through to the AgentRunner so a misspelled builtin
|
|
20
|
+
* doesn't accidentally swallow real user intent.
|
|
21
|
+
*/
|
|
22
|
+
export class PluginCommandRegistry {
|
|
23
|
+
commands = new Map();
|
|
24
|
+
/** Register (or replace) a command. Names are case-insensitive. */
|
|
25
|
+
register(cmd) {
|
|
26
|
+
this.commands.set(cmd.name.toLowerCase(), cmd);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Match a free-form prompt string against the registry. Returns the
|
|
30
|
+
* matched command + parsed args, or `null` when the prompt is not
|
|
31
|
+
* a slash command or doesn't match any registered name.
|
|
32
|
+
*/
|
|
33
|
+
match(text) {
|
|
34
|
+
const trimmed = text.trim();
|
|
35
|
+
if (!trimmed.startsWith("/"))
|
|
36
|
+
return null;
|
|
37
|
+
const spaceIdx = trimmed.indexOf(" ");
|
|
38
|
+
const rawName = spaceIdx === -1 ? trimmed.slice(1) : trimmed.slice(1, spaceIdx);
|
|
39
|
+
const name = rawName.toLowerCase();
|
|
40
|
+
if (!name)
|
|
41
|
+
return null;
|
|
42
|
+
const args = spaceIdx === -1 ? undefined : trimmed.slice(spaceIdx + 1).trim();
|
|
43
|
+
const command = this.commands.get(name);
|
|
44
|
+
if (!command)
|
|
45
|
+
return null;
|
|
46
|
+
// Reject extra args for commands that don't accept them — the line
|
|
47
|
+
// falls through to the AgentRunner so e.g. `/screenshot please`
|
|
48
|
+
// doesn't get swallowed silently.
|
|
49
|
+
if (args && !command.acceptsArgs)
|
|
50
|
+
return null;
|
|
51
|
+
return { command, args: args || undefined };
|
|
52
|
+
}
|
|
53
|
+
/** Snapshot of registered commands, in insertion order. */
|
|
54
|
+
list() {
|
|
55
|
+
return Array.from(this.commands.values());
|
|
56
|
+
}
|
|
57
|
+
/** Number of registered commands — convenience for tests. */
|
|
58
|
+
size() {
|
|
59
|
+
return this.commands.size;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/app/plugin-commands/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AASH,MAAM,OAAO,qBAAqB;IACf,QAAQ,GAAG,IAAI,GAAG,EAAyB,CAAC;IAE7D,mEAAmE;IACnE,QAAQ,CAAC,GAAkB;QACzB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,GAAG,CAAC,CAAC;IACjD,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,IAAY;QAChB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QAE1C,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACtC,MAAM,OAAO,GACX,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;QAClE,MAAM,IAAI,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;QACnC,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QAEvB,MAAM,IAAI,GACR,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAEnE,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAE1B,mEAAmE;QACnE,gEAAgE;QAChE,kCAAkC;QAClC,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC,WAAW;YAAE,OAAO,IAAI,CAAC;QAE9C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,IAAI,SAAS,EAAE,CAAC;IAC9C,CAAC;IAED,2DAA2D;IAC3D,IAAI;QACF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED,6DAA6D;IAC7D,IAAI;QACF,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC5B,CAAC;CACF"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/screenshot` plugin command handler.
|
|
3
|
+
*
|
|
4
|
+
* Adapter layer between the ACP-unaware `ScreenshotService` and the
|
|
5
|
+
* session/notifier surface. Responsibilities are intentionally narrow:
|
|
6
|
+
*
|
|
7
|
+
* 1. Drive `ScreenshotService.captureAndUpload()` to stage the
|
|
8
|
+
* current device frame as a `beeos-file://${fileId}` attachment.
|
|
9
|
+
* 2. Translate the resulting `ChatAttachment` into an ACP
|
|
10
|
+
* `agent_message_chunk` carrying a `resource_link` block.
|
|
11
|
+
* 3. Push that update onto the caller's session via the injected
|
|
12
|
+
* `SessionUpdateNotifier`.
|
|
13
|
+
*
|
|
14
|
+
* Strict separation-of-concerns boundary:
|
|
15
|
+
* - DOES NOT touch MCP / `Buffer` / upload internals — those live
|
|
16
|
+
* entirely inside `ScreenshotService` + `UserFileUploader`.
|
|
17
|
+
* - Errors propagate up to `SessionManager` which renders them as a
|
|
18
|
+
* `system_message_chunk` notice (no toast logic here).
|
|
19
|
+
*/
|
|
20
|
+
import type { ScreenshotService } from "../screenshot-service.js";
|
|
21
|
+
import type { SessionUpdateNotifier } from "../session-manager.js";
|
|
22
|
+
import type { PluginCommand, PluginCommandContext } from "./types.js";
|
|
23
|
+
export declare class ScreenshotCommandHandler implements PluginCommand {
|
|
24
|
+
private readonly service;
|
|
25
|
+
private readonly notifier;
|
|
26
|
+
readonly name = "screenshot";
|
|
27
|
+
readonly description = "Take a device screenshot and send it to the chat";
|
|
28
|
+
readonly emoji = "\uD83D\uDCF8";
|
|
29
|
+
readonly acceptsArgs = false;
|
|
30
|
+
constructor(service: ScreenshotService, notifier: SessionUpdateNotifier);
|
|
31
|
+
handle(ctx: PluginCommandContext): Promise<void>;
|
|
32
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/screenshot` plugin command handler.
|
|
3
|
+
*
|
|
4
|
+
* Adapter layer between the ACP-unaware `ScreenshotService` and the
|
|
5
|
+
* session/notifier surface. Responsibilities are intentionally narrow:
|
|
6
|
+
*
|
|
7
|
+
* 1. Drive `ScreenshotService.captureAndUpload()` to stage the
|
|
8
|
+
* current device frame as a `beeos-file://${fileId}` attachment.
|
|
9
|
+
* 2. Translate the resulting `ChatAttachment` into an ACP
|
|
10
|
+
* `agent_message_chunk` carrying a `resource_link` block.
|
|
11
|
+
* 3. Push that update onto the caller's session via the injected
|
|
12
|
+
* `SessionUpdateNotifier`.
|
|
13
|
+
*
|
|
14
|
+
* Strict separation-of-concerns boundary:
|
|
15
|
+
* - DOES NOT touch MCP / `Buffer` / upload internals — those live
|
|
16
|
+
* entirely inside `ScreenshotService` + `UserFileUploader`.
|
|
17
|
+
* - Errors propagate up to `SessionManager` which renders them as a
|
|
18
|
+
* `system_message_chunk` notice (no toast logic here).
|
|
19
|
+
*/
|
|
20
|
+
export class ScreenshotCommandHandler {
|
|
21
|
+
service;
|
|
22
|
+
notifier;
|
|
23
|
+
name = "screenshot";
|
|
24
|
+
description = "Take a device screenshot and send it to the chat";
|
|
25
|
+
emoji = "📸";
|
|
26
|
+
acceptsArgs = false;
|
|
27
|
+
constructor(service, notifier) {
|
|
28
|
+
this.service = service;
|
|
29
|
+
this.notifier = notifier;
|
|
30
|
+
}
|
|
31
|
+
async handle(ctx) {
|
|
32
|
+
const att = await this.service.captureAndUpload();
|
|
33
|
+
this.notifier.send(ctx.sessionId, {
|
|
34
|
+
sessionUpdate: "agent_message_chunk",
|
|
35
|
+
content: {
|
|
36
|
+
type: "resource_link",
|
|
37
|
+
uri: att.uri,
|
|
38
|
+
name: att.fileName,
|
|
39
|
+
mimeType: att.mimeType,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=screenshot-command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screenshot-command.js","sourceRoot":"","sources":["../../../src/app/plugin-commands/screenshot-command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AASH,MAAM,OAAO,wBAAwB;IAOhB;IACA;IAPV,IAAI,GAAG,YAAY,CAAC;IACpB,WAAW,GAAG,kDAAkD,CAAC;IACjE,KAAK,GAAG,IAAI,CAAC;IACb,WAAW,GAAG,KAAK,CAAC;IAE7B,YACmB,OAA0B,EAC1B,QAA+B;QAD/B,YAAO,GAAP,OAAO,CAAmB;QAC1B,aAAQ,GAAR,QAAQ,CAAuB;IAC/C,CAAC;IAEJ,KAAK,CAAC,MAAM,CAAC,GAAyB;QACpC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC;QAClD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE;YAChC,aAAa,EAAE,qBAAqB;YACpC,OAAO,EAAE;gBACP,IAAI,EAAE,eAAe;gBACrB,GAAG,EAAE,GAAG,CAAC,GAAG;gBACZ,IAAI,EAAE,GAAG,CAAC,QAAQ;gBAClB,QAAQ,EAAE,GAAG,CAAC,QAAQ;aACvB;SACF,CAAC,CAAC;IACL,CAAC;CACF"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Command abstraction — server-side, channel-agnostic slash
|
|
3
|
+
* commands that bypass the LLM / AgentRunner.
|
|
4
|
+
*
|
|
5
|
+
* Modelled directly after OpenClaw's `OpenClawPluginCommandDefinition`
|
|
6
|
+
* (see `openclaw/src/plugins/types.ts:1946-1980`):
|
|
7
|
+
*
|
|
8
|
+
* - Each command bundles its own `handle()` so the SessionManager
|
|
9
|
+
* only has to dispatch by name.
|
|
10
|
+
* - All channels (ACP web chat, future Telegram / Discord / CLI)
|
|
11
|
+
* deliver these as plain `/name [args]` text in `session/prompt`,
|
|
12
|
+
* pre-LLM. The registry is responsible for prefix matching.
|
|
13
|
+
* - Surface metadata (`name`, `description`, `emoji`) lives on the
|
|
14
|
+
* command itself so `skills/list` can be generated from a single
|
|
15
|
+
* source of truth.
|
|
16
|
+
*
|
|
17
|
+
* Strict separation-of-concerns boundary:
|
|
18
|
+
* - This module imports NOTHING from `inbound/acp/*` — the registry
|
|
19
|
+
* / commands stay protocol-agnostic so a future HTTP or CLI surface
|
|
20
|
+
* can drive them too.
|
|
21
|
+
* - Concrete command implementations (`screenshot-command.ts`, etc.)
|
|
22
|
+
* are free to import session-side ports (notifier) — they're the
|
|
23
|
+
* adapter layer between business capabilities and the protocol.
|
|
24
|
+
*/
|
|
25
|
+
export interface PluginCommandContext {
|
|
26
|
+
/**
|
|
27
|
+
* The session to deliver the command's output to. SessionManager
|
|
28
|
+
* passes its own session id through; handlers use this when calling
|
|
29
|
+
* `notifier.notify(sessionId, …)` to push their result.
|
|
30
|
+
*/
|
|
31
|
+
sessionId: string;
|
|
32
|
+
/**
|
|
33
|
+
* Trimmed argument string after the command name. Empty / undefined
|
|
34
|
+
* when the command was invoked bare (`/screenshot`). Only forwarded
|
|
35
|
+
* to commands that opt in via `acceptsArgs: true`.
|
|
36
|
+
*/
|
|
37
|
+
args?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface PluginCommand {
|
|
40
|
+
/** Command name without the leading slash (e.g. `"screenshot"`). */
|
|
41
|
+
readonly name: string;
|
|
42
|
+
/** Description shown in `skills/list` and slash-autocomplete UI. */
|
|
43
|
+
readonly description: string;
|
|
44
|
+
/** Optional emoji prefix for surface presentation (Web slash, /help). */
|
|
45
|
+
readonly emoji?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Whether the command accepts arguments after its name. Commands
|
|
48
|
+
* with `acceptsArgs: false` only match bare `/name` invocations —
|
|
49
|
+
* `/name foo` falls through to the AgentRunner so a misspelled
|
|
50
|
+
* builtin doesn't accidentally swallow user intent.
|
|
51
|
+
*/
|
|
52
|
+
readonly acceptsArgs?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Execute the command. Bypasses LLM / AgentRunner — the handler is
|
|
55
|
+
* responsible for emitting any session-side notifications it wants
|
|
56
|
+
* the user to see (typically through the injected notifier).
|
|
57
|
+
*
|
|
58
|
+
* Throwing here surfaces as a `system_message_chunk` error notice
|
|
59
|
+
* via SessionManager's central error handler — handlers SHOULD let
|
|
60
|
+
* exceptions propagate rather than swallow them silently.
|
|
61
|
+
*/
|
|
62
|
+
handle(ctx: PluginCommandContext): Promise<void>;
|
|
63
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Command abstraction — server-side, channel-agnostic slash
|
|
3
|
+
* commands that bypass the LLM / AgentRunner.
|
|
4
|
+
*
|
|
5
|
+
* Modelled directly after OpenClaw's `OpenClawPluginCommandDefinition`
|
|
6
|
+
* (see `openclaw/src/plugins/types.ts:1946-1980`):
|
|
7
|
+
*
|
|
8
|
+
* - Each command bundles its own `handle()` so the SessionManager
|
|
9
|
+
* only has to dispatch by name.
|
|
10
|
+
* - All channels (ACP web chat, future Telegram / Discord / CLI)
|
|
11
|
+
* deliver these as plain `/name [args]` text in `session/prompt`,
|
|
12
|
+
* pre-LLM. The registry is responsible for prefix matching.
|
|
13
|
+
* - Surface metadata (`name`, `description`, `emoji`) lives on the
|
|
14
|
+
* command itself so `skills/list` can be generated from a single
|
|
15
|
+
* source of truth.
|
|
16
|
+
*
|
|
17
|
+
* Strict separation-of-concerns boundary:
|
|
18
|
+
* - This module imports NOTHING from `inbound/acp/*` — the registry
|
|
19
|
+
* / commands stay protocol-agnostic so a future HTTP or CLI surface
|
|
20
|
+
* can drive them too.
|
|
21
|
+
* - Concrete command implementations (`screenshot-command.ts`, etc.)
|
|
22
|
+
* are free to import session-side ports (notifier) — they're the
|
|
23
|
+
* adapter layer between business capabilities and the protocol.
|
|
24
|
+
*/
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/app/plugin-commands/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ScreenshotService — business-capability layer for "take a device
|
|
3
|
+
* screenshot and stage it as a user-visible chat attachment".
|
|
4
|
+
*
|
|
5
|
+
* mcp.screenshot() ──▶ bytes ──▶ uploader.upload() ──▶ ChatAttachment
|
|
6
|
+
*
|
|
7
|
+
* Strict separation-of-concerns boundary:
|
|
8
|
+
*
|
|
9
|
+
* - Inputs / outputs are platform-neutral. No `AcpMessage`, no
|
|
10
|
+
* `sessionId`, no `notifier` ever crosses this module.
|
|
11
|
+
* - The returned `ChatAttachment` is the lingua franca passed up to
|
|
12
|
+
* whoever needs to render the result on a session: the ACP plugin
|
|
13
|
+
* command adapter (`ScreenshotCommandHandler`), a future HTTP
|
|
14
|
+
* handler, the AgentLoop, etc.
|
|
15
|
+
*
|
|
16
|
+
* This is the device-agent equivalent of beeos-claw's `uploadLocalFile`
|
|
17
|
+
* pipeline (`agents/beeos-claw/src/upload/upload-file.ts`) — same
|
|
18
|
+
* 3-step Agent Gateway flow (presign + PUT + confirm), different
|
|
19
|
+
* source (live device frame instead of a local file).
|
|
20
|
+
*/
|
|
21
|
+
import type { IMcpRuntime } from "../mcp-client/runtime.js";
|
|
22
|
+
import type { UserFileUploader } from "../adapters/user-file-uploader.js";
|
|
23
|
+
export interface ChatAttachment {
|
|
24
|
+
/** Stable platform fileId returned by the Agent Gateway after confirm. */
|
|
25
|
+
fileId: string;
|
|
26
|
+
/** `beeos-file://${fileId}` — what goes into ACP `resource_link.uri`. */
|
|
27
|
+
uri: string;
|
|
28
|
+
mimeType: string;
|
|
29
|
+
fileName: string;
|
|
30
|
+
format: "png" | "jpeg";
|
|
31
|
+
/** Decoded pixel width (matches `b64` payload, may differ from device click frame). */
|
|
32
|
+
width?: number;
|
|
33
|
+
/** Decoded pixel height. */
|
|
34
|
+
height?: number;
|
|
35
|
+
}
|
|
36
|
+
export interface CaptureOpts {
|
|
37
|
+
/**
|
|
38
|
+
* Storage key prefix override. Defaults to `device-screenshots/` so
|
|
39
|
+
* S3 lifecycle rules can target screenshots independently of other
|
|
40
|
+
* user-attachment categories.
|
|
41
|
+
*/
|
|
42
|
+
keyPrefix?: string;
|
|
43
|
+
}
|
|
44
|
+
/** Test seam — defaults to `() => new Date()`. */
|
|
45
|
+
type Clock = () => Date;
|
|
46
|
+
export interface ScreenshotServiceOptions {
|
|
47
|
+
/** Test seam. Defaults to `() => new Date()`. */
|
|
48
|
+
now?: Clock;
|
|
49
|
+
}
|
|
50
|
+
export declare class ScreenshotService {
|
|
51
|
+
private readonly mcp;
|
|
52
|
+
private readonly uploader;
|
|
53
|
+
private readonly now;
|
|
54
|
+
constructor(mcp: IMcpRuntime, uploader: UserFileUploader, opts?: ScreenshotServiceOptions);
|
|
55
|
+
/**
|
|
56
|
+
* Capture the current device screen and stage it as a user-visible
|
|
57
|
+
* chat attachment. Returns a `ChatAttachment` with a stable
|
|
58
|
+
* `beeos-file://` URI and metadata. ACP / session knowledge stays
|
|
59
|
+
* out of this method by design.
|
|
60
|
+
*
|
|
61
|
+
* Errors:
|
|
62
|
+
* - MCP runtime errors propagate from `mcp.screenshot()`.
|
|
63
|
+
* - Upload errors propagate from `uploader.upload()` (typically
|
|
64
|
+
* `AgentGatewayError` with the failing step's HTTP status).
|
|
65
|
+
*/
|
|
66
|
+
captureAndUpload(opts?: CaptureOpts): Promise<ChatAttachment>;
|
|
67
|
+
}
|
|
68
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ScreenshotService — business-capability layer for "take a device
|
|
3
|
+
* screenshot and stage it as a user-visible chat attachment".
|
|
4
|
+
*
|
|
5
|
+
* mcp.screenshot() ──▶ bytes ──▶ uploader.upload() ──▶ ChatAttachment
|
|
6
|
+
*
|
|
7
|
+
* Strict separation-of-concerns boundary:
|
|
8
|
+
*
|
|
9
|
+
* - Inputs / outputs are platform-neutral. No `AcpMessage`, no
|
|
10
|
+
* `sessionId`, no `notifier` ever crosses this module.
|
|
11
|
+
* - The returned `ChatAttachment` is the lingua franca passed up to
|
|
12
|
+
* whoever needs to render the result on a session: the ACP plugin
|
|
13
|
+
* command adapter (`ScreenshotCommandHandler`), a future HTTP
|
|
14
|
+
* handler, the AgentLoop, etc.
|
|
15
|
+
*
|
|
16
|
+
* This is the device-agent equivalent of beeos-claw's `uploadLocalFile`
|
|
17
|
+
* pipeline (`agents/beeos-claw/src/upload/upload-file.ts`) — same
|
|
18
|
+
* 3-step Agent Gateway flow (presign + PUT + confirm), different
|
|
19
|
+
* source (live device frame instead of a local file).
|
|
20
|
+
*/
|
|
21
|
+
export class ScreenshotService {
|
|
22
|
+
mcp;
|
|
23
|
+
uploader;
|
|
24
|
+
now;
|
|
25
|
+
constructor(mcp, uploader, opts = {}) {
|
|
26
|
+
this.mcp = mcp;
|
|
27
|
+
this.uploader = uploader;
|
|
28
|
+
this.now = opts.now ?? (() => new Date());
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Capture the current device screen and stage it as a user-visible
|
|
32
|
+
* chat attachment. Returns a `ChatAttachment` with a stable
|
|
33
|
+
* `beeos-file://` URI and metadata. ACP / session knowledge stays
|
|
34
|
+
* out of this method by design.
|
|
35
|
+
*
|
|
36
|
+
* Errors:
|
|
37
|
+
* - MCP runtime errors propagate from `mcp.screenshot()`.
|
|
38
|
+
* - Upload errors propagate from `uploader.upload()` (typically
|
|
39
|
+
* `AgentGatewayError` with the failing step's HTTP status).
|
|
40
|
+
*/
|
|
41
|
+
async captureAndUpload(opts = {}) {
|
|
42
|
+
const ss = await this.mcp.screenshot();
|
|
43
|
+
const bytes = Buffer.from(ss.b64, "base64");
|
|
44
|
+
const mimeType = ss.format === "png" ? "image/png" : "image/jpeg";
|
|
45
|
+
const ts = this.now().toISOString().replace(/[:.]/g, "-");
|
|
46
|
+
const fileName = `screenshot-${ts}.${ss.format}`;
|
|
47
|
+
const uploaded = await this.uploader.upload({
|
|
48
|
+
bytes,
|
|
49
|
+
mimeType,
|
|
50
|
+
fileName,
|
|
51
|
+
keyPrefix: opts.keyPrefix ?? "device-screenshots/",
|
|
52
|
+
});
|
|
53
|
+
return {
|
|
54
|
+
fileId: uploaded.fileId,
|
|
55
|
+
uri: uploaded.uri,
|
|
56
|
+
mimeType: uploaded.mimeType,
|
|
57
|
+
fileName: uploaded.fileName,
|
|
58
|
+
format: ss.format,
|
|
59
|
+
width: ss.width,
|
|
60
|
+
height: ss.height,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=screenshot-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screenshot-service.js","sourceRoot":"","sources":["../../src/app/screenshot-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAoCH,MAAM,OAAO,iBAAiB;IAIT;IACA;IAJF,GAAG,CAAQ;IAE5B,YACmB,GAAgB,EAChB,QAA0B,EAC3C,OAAiC,EAAE;QAFlB,QAAG,GAAH,GAAG,CAAa;QAChB,aAAQ,GAAR,QAAQ,CAAkB;QAG3C,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED;;;;;;;;;;OAUG;IACH,KAAK,CAAC,gBAAgB,CAAC,OAAoB,EAAE;QAC3C,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC;QAEvC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QAC5C,MAAM,QAAQ,GACZ,EAAE,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,YAAY,CAAC;QACnD,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QAC1D,MAAM,QAAQ,GAAG,cAAc,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC;QAEjD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;YAC1C,KAAK;YACL,QAAQ;YACR,QAAQ;YACR,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,qBAAqB;SACnD,CAAC,CAAC;QAEH,OAAO;YACL,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,GAAG,EAAE,QAAQ,CAAC,GAAG;YACjB,QAAQ,EAAE,QAAQ,CAAC,QAAQ;YAC3B,QAAQ,EAAE,QAAQ,CAAC,QAAQ;YAC3B,MAAM,EAAE,EAAE,CAAC,MAAM;YACjB,KAAK,EAAE,EAAE,CAAC,KAAK;YACf,MAAM,EAAE,EAAE,CAAC,MAAM;SAClB,CAAC;IACJ,CAAC;CACF"}
|