@lyriel/openclaw-plugin 0.4.0 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +181 -0
- package/dist/api.js.map +1 -0
- package/dist/inbox.js +206 -0
- package/dist/inbox.js.map +1 -0
- package/dist/index.js +132 -0
- package/dist/index.js.map +1 -0
- package/dist/pending.js +106 -0
- package/dist/pending.js.map +1 -0
- package/dist/primer.js +27 -0
- package/dist/primer.js.map +1 -0
- package/dist/surfacing.js +100 -0
- package/dist/surfacing.js.map +1 -0
- package/dist/telegram.js +63 -0
- package/dist/telegram.js.map +1 -0
- package/dist/tools.js +463 -0
- package/dist/tools.js.map +1 -0
- package/package.json +4 -2
package/dist/api.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// HTTP client for Lyriel's substrate API. Uses global fetch (Node 22+).
|
|
2
|
+
//
|
|
3
|
+
// Five operations:
|
|
4
|
+
// - longPollInbox(): GET /api/inbox with Bearer auth; returns parsed dispatches
|
|
5
|
+
// - sendAsk(to, prompt): POST /api/asks; returns ask_id
|
|
6
|
+
// - sendCallback(callbackUrl, callbackToken, response): POST the per-ask
|
|
7
|
+
// callback URL embedded in an inbound dispatch envelope
|
|
8
|
+
// - sendPlan / sendPlanCallback / lockPlan: group-plan endpoints
|
|
9
|
+
//
|
|
10
|
+
// Config (apiKey, baseUrl) is passed in rather than read from env so the
|
|
11
|
+
// OpenClaw plugin manifest (openclaw.plugin.json -> configSchema) owns the
|
|
12
|
+
// source of truth and the operator edits one place.
|
|
13
|
+
export class LyrielApiError extends Error {
|
|
14
|
+
status;
|
|
15
|
+
constructor(message, status) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = "LyrielApiError";
|
|
18
|
+
this.status = status;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function trimSlash(s) {
|
|
22
|
+
return s.endsWith("/") ? s.slice(0, -1) : s;
|
|
23
|
+
}
|
|
24
|
+
async function request(method, url, opts = {}) {
|
|
25
|
+
const controller = new AbortController();
|
|
26
|
+
const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 30_000);
|
|
27
|
+
try {
|
|
28
|
+
const res = await fetch(url, {
|
|
29
|
+
method,
|
|
30
|
+
headers: {
|
|
31
|
+
"content-type": "application/json",
|
|
32
|
+
...opts.headers,
|
|
33
|
+
},
|
|
34
|
+
body: opts.body === undefined ? undefined : JSON.stringify(opts.body),
|
|
35
|
+
signal: controller.signal,
|
|
36
|
+
});
|
|
37
|
+
const text = await res.text();
|
|
38
|
+
return { status: res.status, body: text };
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
clearTimeout(timer);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function parseJson(body, action) {
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(body);
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
throw new LyrielApiError(`${action} response was not JSON: ${err.message}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export async function longPollInbox(cfg, timeoutMs = 30_000) {
|
|
53
|
+
const url = `${trimSlash(cfg.baseUrl)}/api/inbox`;
|
|
54
|
+
const { status, body } = await request("GET", url, {
|
|
55
|
+
headers: { authorization: `Bearer ${cfg.apiKey}` },
|
|
56
|
+
timeoutMs,
|
|
57
|
+
});
|
|
58
|
+
if (status !== 200) {
|
|
59
|
+
throw new LyrielApiError(`Inbox poll failed (${status}): ${body.slice(0, 200)}`, status);
|
|
60
|
+
}
|
|
61
|
+
const parsed = parseJson(body, "inbox");
|
|
62
|
+
return parsed.dispatches ?? [];
|
|
63
|
+
}
|
|
64
|
+
export async function sendAsk(cfg, to, prompt) {
|
|
65
|
+
const url = `${trimSlash(cfg.baseUrl)}/api/asks`;
|
|
66
|
+
const { status, body } = await request("POST", url, {
|
|
67
|
+
headers: { authorization: `Bearer ${cfg.apiKey}` },
|
|
68
|
+
body: { to: to.replace(/^@/, ""), prompt },
|
|
69
|
+
});
|
|
70
|
+
if (status !== 200 && status !== 202) {
|
|
71
|
+
throw new LyrielApiError(`sendAsk failed (${status}): ${body.slice(0, 300)}`, status);
|
|
72
|
+
}
|
|
73
|
+
return parseJson(body, "sendAsk");
|
|
74
|
+
}
|
|
75
|
+
export async function getAsk(cfg, askId) {
|
|
76
|
+
const url = `${trimSlash(cfg.baseUrl)}/api/asks/${askId}`;
|
|
77
|
+
const { status, body } = await request("GET", url, {
|
|
78
|
+
headers: { authorization: `Bearer ${cfg.apiKey}` },
|
|
79
|
+
timeoutMs: 2_000,
|
|
80
|
+
});
|
|
81
|
+
if (status !== 200) {
|
|
82
|
+
throw new LyrielApiError(`getAsk failed (${status})`, status);
|
|
83
|
+
}
|
|
84
|
+
return parseJson(body, "getAsk");
|
|
85
|
+
}
|
|
86
|
+
export async function sendCallback(callbackUrl, callbackToken, response) {
|
|
87
|
+
const { status, body } = await request("POST", callbackUrl, {
|
|
88
|
+
headers: { authorization: `Bearer ${callbackToken}` },
|
|
89
|
+
body: { response },
|
|
90
|
+
});
|
|
91
|
+
if (status !== 200 && status !== 410) {
|
|
92
|
+
throw new LyrielApiError(`callback failed (${status}): ${body.slice(0, 300)}`, status);
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
return JSON.parse(body);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Some non-2xx-but-acceptable paths may return plain text — surface raw
|
|
99
|
+
// body for diagnostics rather than throw a second error.
|
|
100
|
+
return { raw: body, status };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
export async function sendPlan(cfg, participants, description, maxMessages) {
|
|
104
|
+
const url = `${trimSlash(cfg.baseUrl)}/api/plans`;
|
|
105
|
+
const body = {
|
|
106
|
+
participants: participants.map((p) => p.replace(/^@/, "")),
|
|
107
|
+
description,
|
|
108
|
+
};
|
|
109
|
+
if (maxMessages !== undefined) {
|
|
110
|
+
body.max_messages = maxMessages;
|
|
111
|
+
}
|
|
112
|
+
const { status, body: respBody } = await request("POST", url, {
|
|
113
|
+
headers: { authorization: `Bearer ${cfg.apiKey}` },
|
|
114
|
+
body,
|
|
115
|
+
});
|
|
116
|
+
if (status !== 200 && status !== 202) {
|
|
117
|
+
throw new LyrielApiError(`sendPlan failed (${status}): ${respBody.slice(0, 300)}`, status);
|
|
118
|
+
}
|
|
119
|
+
return parseJson(respBody, "sendPlan");
|
|
120
|
+
}
|
|
121
|
+
export async function sendPlanCallback(callbackUrl, callbackToken, content, staySilent) {
|
|
122
|
+
const body = { content };
|
|
123
|
+
if (staySilent) {
|
|
124
|
+
body.stay_silent = true;
|
|
125
|
+
}
|
|
126
|
+
const { status, body: respBody } = await request("POST", callbackUrl, {
|
|
127
|
+
headers: { authorization: `Bearer ${callbackToken}` },
|
|
128
|
+
body,
|
|
129
|
+
});
|
|
130
|
+
if (status !== 200 && status !== 409) {
|
|
131
|
+
throw new LyrielApiError(`plan callback failed (${status}): ${respBody.slice(0, 300)}`, status);
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
return JSON.parse(respBody);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return { raw: respBody, status };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
export async function updateProfile(cfg, fields) {
|
|
141
|
+
const url = `${trimSlash(cfg.baseUrl)}/api/me/profile`;
|
|
142
|
+
const { status, body: respBody } = await request("PATCH", url, {
|
|
143
|
+
headers: { authorization: `Bearer ${cfg.apiKey}` },
|
|
144
|
+
body: fields,
|
|
145
|
+
});
|
|
146
|
+
if (status !== 200) {
|
|
147
|
+
throw new LyrielApiError(`updateProfile failed (${status}): ${respBody.slice(0, 300)}`, status);
|
|
148
|
+
}
|
|
149
|
+
return parseJson(respBody, "updateProfile");
|
|
150
|
+
}
|
|
151
|
+
export async function cancelPlan(cfg, planId, reason) {
|
|
152
|
+
const url = `${trimSlash(cfg.baseUrl)}/api/plans/${planId}/cancel`;
|
|
153
|
+
const body = {};
|
|
154
|
+
if (reason) {
|
|
155
|
+
body.reason = reason;
|
|
156
|
+
}
|
|
157
|
+
const { status, body: respBody } = await request("POST", url, {
|
|
158
|
+
headers: { authorization: `Bearer ${cfg.apiKey}` },
|
|
159
|
+
body,
|
|
160
|
+
});
|
|
161
|
+
if (status !== 200) {
|
|
162
|
+
throw new LyrielApiError(`cancelPlan failed (${status}): ${respBody.slice(0, 300)}`, status);
|
|
163
|
+
}
|
|
164
|
+
return parseJson(respBody, "cancelPlan");
|
|
165
|
+
}
|
|
166
|
+
export async function lockPlan(cfg, planId, summary, details) {
|
|
167
|
+
const url = `${trimSlash(cfg.baseUrl)}/api/plans/${planId}/lock`;
|
|
168
|
+
const body = { summary };
|
|
169
|
+
if (details) {
|
|
170
|
+
body.details = details;
|
|
171
|
+
}
|
|
172
|
+
const { status, body: respBody } = await request("POST", url, {
|
|
173
|
+
headers: { authorization: `Bearer ${cfg.apiKey}` },
|
|
174
|
+
body,
|
|
175
|
+
});
|
|
176
|
+
if (status !== 200) {
|
|
177
|
+
throw new LyrielApiError(`lockPlan failed (${status}): ${respBody.slice(0, 300)}`, status);
|
|
178
|
+
}
|
|
179
|
+
return parseJson(respBody, "lockPlan");
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../api.ts"],"names":[],"mappings":"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"}
|
package/dist/inbox.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// Background long-poll: hits Lyriel /api/inbox in a recursive setTimeout
|
|
2
|
+
// loop, then routes each dispatch by direction:
|
|
3
|
+
//
|
|
4
|
+
// - dispatch / forward (1:1 asks) — surface to the user via Telegram
|
|
5
|
+
// push (if configured) or next-turn injection into the active
|
|
6
|
+
// OpenClaw session.
|
|
7
|
+
//
|
|
8
|
+
// - plan rounds (non-terminal) — schedule a background agent turn
|
|
9
|
+
// via api.session.workflow.scheduleSessionTurn with deliveryMode:
|
|
10
|
+
// "none". The user is NOT shown the round; their agent processes
|
|
11
|
+
// it autonomously, calls lyriel_plan_reply, and the loop moves
|
|
12
|
+
// on. Load-bearing for agent-mediated plan coordination — users
|
|
13
|
+
// see the lock outcome, not the back-and-forth.
|
|
14
|
+
//
|
|
15
|
+
// - plan_locked / plan_cancelled — surface (final outcome the user
|
|
16
|
+
// needs to see).
|
|
17
|
+
//
|
|
18
|
+
// - friend events — surface (user needs to know).
|
|
19
|
+
//
|
|
20
|
+
// Lifecycle: started on the `gateway_start` plugin hook, stopped on
|
|
21
|
+
// `gateway_stop`. Loop control state lives on a `state` object (rather than
|
|
22
|
+
// a captured `let`) so static analysis can see that the start/stop methods
|
|
23
|
+
// mutate the condition the while-loop reads.
|
|
24
|
+
import * as api from "./api.js";
|
|
25
|
+
import * as pending from "./pending.js";
|
|
26
|
+
import { formatDispatch } from "./surfacing.js";
|
|
27
|
+
import { sendTelegramMessage } from "./telegram.js";
|
|
28
|
+
const RECONNECT_BACKOFF_MS = 5_000;
|
|
29
|
+
const INBOX_TIMEOUT_MS = 30_000;
|
|
30
|
+
const POLL_INTERVAL_AFTER_BATCH_MS = 500;
|
|
31
|
+
// Prepended to every plan-round dispatch before handing to the LLM.
|
|
32
|
+
// Tight wording — every token is paid for on every plan round.
|
|
33
|
+
// Mirror of BACKGROUND_PROMPT_PREFIX in
|
|
34
|
+
// clients/hermes-plugin/lyriel_hermes/background.py. Kept in sync by
|
|
35
|
+
// convention; if either drifts, cross-runtime plan behavior drifts.
|
|
36
|
+
export const BACKGROUND_PROMPT_PREFIX = "[Lyriel background plan turn — automated, no user present.\n\n" +
|
|
37
|
+
"CRITICAL: You MUST call the lyriel_plan_reply tool to respond. " +
|
|
38
|
+
"Text-only responses do nothing — the substrate ignores them, " +
|
|
39
|
+
"the round times out, and the plan stalls. The tool call IS " +
|
|
40
|
+
"the response. There is no other way to participate in this " +
|
|
41
|
+
"round.\n\n" +
|
|
42
|
+
"Your user is NOT watching this turn. Do NOT ask clarifying " +
|
|
43
|
+
"questions. Use what you already know about them (calendar, " +
|
|
44
|
+
"preferences, dietary, location, budget). If a constraint is " +
|
|
45
|
+
"missing, accept wide constraints and let other participants " +
|
|
46
|
+
"narrow.\n\n" +
|
|
47
|
+
"Call lyriel_plan_reply now. Pass stay_silent=true if you " +
|
|
48
|
+
"genuinely have nothing to add. Keep `content` concise — " +
|
|
49
|
+
"every token costs your user money.]\n\n";
|
|
50
|
+
/**
|
|
51
|
+
* Detect a locked or cancelled plan envelope.
|
|
52
|
+
*
|
|
53
|
+
* Accepts either separator the server might use between "plan" and
|
|
54
|
+
* the status word — comma is current, em-dash is the older form.
|
|
55
|
+
* Strict match-the-current-server caused a real bug on the Hermes
|
|
56
|
+
* side (cf. lyriel-hermes 0.6.3) where the plugin ran a useless
|
|
57
|
+
* background turn on a locked envelope and the lock notification
|
|
58
|
+
* never surfaced. Same permissive form here.
|
|
59
|
+
*/
|
|
60
|
+
export function isTerminalPlanEnvelope(prompt) {
|
|
61
|
+
return (prompt.includes("[Lyriel group plan, LOCKED]") ||
|
|
62
|
+
prompt.includes("[Lyriel group plan — LOCKED]") ||
|
|
63
|
+
prompt.includes("[Lyriel group plan, CANCELLED]") ||
|
|
64
|
+
prompt.includes("[Lyriel group plan — CANCELLED]"));
|
|
65
|
+
}
|
|
66
|
+
export function createInboxRunner(opts) {
|
|
67
|
+
const state = { running: false, currentLoop: null };
|
|
68
|
+
async function pollOnce() {
|
|
69
|
+
return api.longPollInbox(opts.client, INBOX_TIMEOUT_MS);
|
|
70
|
+
}
|
|
71
|
+
async function loop() {
|
|
72
|
+
opts.logger.info(`lyriel inbox poll loop started (base=${opts.client.baseUrl})`);
|
|
73
|
+
while (state.running) {
|
|
74
|
+
let dispatches = [];
|
|
75
|
+
try {
|
|
76
|
+
dispatches = await pollOnce();
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
if (!state.running) {
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
opts.logger.warn(`inbox poll error: ${err.message}`);
|
|
83
|
+
await sleep(RECONNECT_BACKOFF_MS, () => state.running);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
for (const d of dispatches) {
|
|
87
|
+
const askId = d.ask_id;
|
|
88
|
+
const planId = d.plan_id;
|
|
89
|
+
const direction = d.direction ?? "?";
|
|
90
|
+
const promptText = d.prompt ?? "";
|
|
91
|
+
const correlation = planId ?? askId ?? "?";
|
|
92
|
+
if (direction === "dispatch" && askId) {
|
|
93
|
+
if (!pending.rememberAsk(askId, promptText)) {
|
|
94
|
+
opts.logger.warn(`could not extract callback URL/token for ask ${askId} — user replies will fail`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (direction === "plan" && planId) {
|
|
98
|
+
if (!pending.rememberPlan(planId, promptText)) {
|
|
99
|
+
const isTerminal = isTerminalPlanEnvelope(promptText);
|
|
100
|
+
if (!isTerminal) {
|
|
101
|
+
opts.logger.warn(`could not extract callback for plan ${planId} — user contributions will fail`);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
pending.forgetPlan(planId);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Synchronous-completion suppression. If lyriel_send_ask absorbed
|
|
109
|
+
// the response inline (system_responder case — @lyriel responds
|
|
110
|
+
// instantly), skip the duplicate forward surface.
|
|
111
|
+
if (direction === "forward" && askId && pending.wasAbsorbed(askId)) {
|
|
112
|
+
opts.logger.info(`skipped forward ask_id=${askId} (already absorbed by tool)`);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
// Plan-round dispatches: handle in a background agent turn
|
|
116
|
+
// without surfacing to the user. scheduleSessionTurn with
|
|
117
|
+
// deliveryMode="none" fires the LLM with the round prompt;
|
|
118
|
+
// the agent picks up lyriel_plan_reply via its toolset and
|
|
119
|
+
// responds autonomously. Plan_locked / plan_cancelled ARE
|
|
120
|
+
// surfaced (terminal outcomes the user needs to see).
|
|
121
|
+
if (direction === "plan" && planId) {
|
|
122
|
+
const isTerminal = isTerminalPlanEnvelope(promptText);
|
|
123
|
+
if (!isTerminal) {
|
|
124
|
+
const bgSession = opts.resolveSurfaceSession();
|
|
125
|
+
if (!bgSession) {
|
|
126
|
+
opts.logger.warn(`no session available for background plan turn plan_id=${planId} — round will go unanswered until user engages the agent`);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
await opts.scheduleBackgroundTurn({
|
|
131
|
+
sessionKey: bgSession,
|
|
132
|
+
message: BACKGROUND_PROMPT_PREFIX + promptText,
|
|
133
|
+
});
|
|
134
|
+
opts.logger.info(`scheduled background plan turn for plan_id=${planId} on session ${bgSession}`);
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
opts.logger.error(`background plan turn failed for plan_id=${planId}: ${err.message}`);
|
|
138
|
+
}
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const surfaceText = formatDispatch(d);
|
|
143
|
+
// Path A — proactive Telegram push (preferred). Matches the Hermes
|
|
144
|
+
// plugin's "1 bot, 1 chat" UX: the dispatch lands in the user's
|
|
145
|
+
// chat as a real push notification, with no need to engage the
|
|
146
|
+
// agent first.
|
|
147
|
+
const tg = opts.resolveTelegramSurface();
|
|
148
|
+
if (tg.botToken && tg.chatId) {
|
|
149
|
+
const result = await sendTelegramMessage(tg, surfaceText);
|
|
150
|
+
if (result.ok) {
|
|
151
|
+
opts.logger.info(`surfaced ${direction} id=${correlation} via Telegram chat ${tg.chatId}`);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
opts.logger.warn(`Telegram push for ${direction} id=${correlation} failed: ${result.error ?? "unknown"} — falling back to next-turn injection`);
|
|
155
|
+
}
|
|
156
|
+
// Path B — next-turn injection fallback. Used when Telegram isn't
|
|
157
|
+
// configured or push failed. User has to engage the agent for the
|
|
158
|
+
// dispatch to surface.
|
|
159
|
+
const sessionKey = opts.resolveSurfaceSession();
|
|
160
|
+
if (!sessionKey) {
|
|
161
|
+
opts.logger.warn(`no surface available for ${direction} id=${correlation} — dispatch claimed but won't surface until user engages the agent or Telegram is configured`);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
await opts.inject({ sessionKey, text: surfaceText });
|
|
166
|
+
opts.logger.info(`surfaced ${direction} id=${correlation} into session ${sessionKey}`);
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
opts.logger.error(`surfacing ${direction} ${correlation} failed: ${err.message}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (dispatches.length > 0 && state.running) {
|
|
173
|
+
await sleep(POLL_INTERVAL_AFTER_BATCH_MS, () => state.running);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
opts.logger.info("lyriel inbox poll loop exited");
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
start() {
|
|
180
|
+
if (state.running) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
state.running = true;
|
|
184
|
+
state.currentLoop = loop().catch((err) => {
|
|
185
|
+
opts.logger.error(`inbox loop crashed: ${err.message}`);
|
|
186
|
+
});
|
|
187
|
+
},
|
|
188
|
+
async stop() {
|
|
189
|
+
state.running = false;
|
|
190
|
+
if (state.currentLoop) {
|
|
191
|
+
await state.currentLoop;
|
|
192
|
+
state.currentLoop = null;
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function sleep(ms, stillRunning) {
|
|
198
|
+
return new Promise((resolve) => {
|
|
199
|
+
if (!stillRunning()) {
|
|
200
|
+
resolve();
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
setTimeout(resolve, ms);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=inbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbox.js","sourceRoot":"","sources":["../inbox.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// Lyriel plugin for OpenClaw.
|
|
2
|
+
//
|
|
3
|
+
// Wires Lyriel (https://lyriel.ai) into the OpenClaw runtime. Cross-provider
|
|
4
|
+
// parity with clients/hermes-plugin/: a Hermes user and an OpenClaw user can
|
|
5
|
+
// coordinate through Lyriel without either knowing what runtime the other is
|
|
6
|
+
// on, because both runtimes integrate via Lyriel's HTTP substrate API.
|
|
7
|
+
//
|
|
8
|
+
// Three pieces wire up at register():
|
|
9
|
+
//
|
|
10
|
+
// 1. Five LLM tools (lyriel_send_ask, lyriel_reply, lyriel_plan_send,
|
|
11
|
+
// lyriel_plan_reply, lyriel_plan_lock) so the user drives Lyriel in
|
|
12
|
+
// natural language. Schemas live in tools.ts.
|
|
13
|
+
//
|
|
14
|
+
// 2. A background long-poll started on the `gateway_start` lifecycle
|
|
15
|
+
// hook. The loop hits Lyriel's /api/inbox, parses dispatches, stores
|
|
16
|
+
// callback tokens by ask_id / plan_id (so the reply tools have them
|
|
17
|
+
// when the user wants to respond), and pushes a human-readable
|
|
18
|
+
// surface message into the user's active session via
|
|
19
|
+
// api.session.workflow.enqueueNextTurnInjection.
|
|
20
|
+
//
|
|
21
|
+
// 3. A `message_received` hook that tracks the user's most recent
|
|
22
|
+
// session key so the poll loop knows which session to surface into.
|
|
23
|
+
// Operators can pin a specific session via
|
|
24
|
+
// `plugins.entries.lyriel.config.surfaceSessionKey` instead.
|
|
25
|
+
//
|
|
26
|
+
// Required config (plugins.entries.lyriel.config in openclaw config):
|
|
27
|
+
// apiKey — your lyk_... key from /me/agent setup (required)
|
|
28
|
+
// baseUrl — defaults to https://lyriel.ai; override for dev
|
|
29
|
+
// surfaceSessionKey — optional pinned session key (otherwise auto-tracked)
|
|
30
|
+
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
31
|
+
import { createInboxRunner } from "./inbox.js";
|
|
32
|
+
import { resolveTelegramSurface } from "./telegram.js";
|
|
33
|
+
import { buildToolDescriptors } from "./tools.js";
|
|
34
|
+
function parseConfig(raw) {
|
|
35
|
+
if (!raw || typeof raw !== "object") {
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
const o = raw;
|
|
39
|
+
return {
|
|
40
|
+
apiKey: typeof o.apiKey === "string" ? o.apiKey : undefined,
|
|
41
|
+
baseUrl: typeof o.baseUrl === "string" ? o.baseUrl : undefined,
|
|
42
|
+
surfaceSessionKey: typeof o.surfaceSessionKey === "string" ? o.surfaceSessionKey : undefined,
|
|
43
|
+
surfaceTelegramChatId: typeof o.surfaceTelegramChatId === "string" ? o.surfaceTelegramChatId : undefined,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export default definePluginEntry({
|
|
47
|
+
id: "lyriel",
|
|
48
|
+
name: "Lyriel",
|
|
49
|
+
description: "Lyriel plugin for OpenClaw — agent-mediated communication substrate. Long-polls /api/inbox, surfaces dispatches into the active session, and exposes five tools for asks and group plans.",
|
|
50
|
+
register(api) {
|
|
51
|
+
const config = parseConfig(api.pluginConfig);
|
|
52
|
+
const baseUrl = (config.baseUrl ?? "https://lyriel.ai").trim();
|
|
53
|
+
const apiKey = (config.apiKey ?? "").trim();
|
|
54
|
+
if (!apiKey) {
|
|
55
|
+
api.logger.warn("Lyriel plugin: apiKey missing in plugins.entries.lyriel.config — tools and inbox poll will fail until set.");
|
|
56
|
+
}
|
|
57
|
+
const client = { apiKey, baseUrl };
|
|
58
|
+
// ─── LLM tools ──────────────────────────────────────────────────────
|
|
59
|
+
for (const tool of buildToolDescriptors(client)) {
|
|
60
|
+
api.registerTool({
|
|
61
|
+
name: tool.name,
|
|
62
|
+
label: tool.label,
|
|
63
|
+
description: tool.description,
|
|
64
|
+
parameters: tool.parameters,
|
|
65
|
+
execute: tool.execute,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
// ─── Surface-session tracking ───────────────────────────────────────
|
|
69
|
+
// The user's most recently active session, populated lazily by the
|
|
70
|
+
// message_received hook. Cleared on gateway_stop.
|
|
71
|
+
let mostRecentSessionKey = null;
|
|
72
|
+
api.on("message_received", async (_event, ctx) => {
|
|
73
|
+
const sk = ctx.sessionKey;
|
|
74
|
+
if (sk && typeof sk === "string") {
|
|
75
|
+
mostRecentSessionKey = sk;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
function resolveSurfaceSession() {
|
|
79
|
+
if (config.surfaceSessionKey) {
|
|
80
|
+
return config.surfaceSessionKey;
|
|
81
|
+
}
|
|
82
|
+
return mostRecentSessionKey;
|
|
83
|
+
}
|
|
84
|
+
// ─── Inbox poll loop ────────────────────────────────────────────────
|
|
85
|
+
const childLogger = api.logger;
|
|
86
|
+
const inbox = createInboxRunner({
|
|
87
|
+
client,
|
|
88
|
+
resolveSurfaceSession,
|
|
89
|
+
resolveTelegramSurface() {
|
|
90
|
+
return resolveTelegramSurface(api.config, config.surfaceTelegramChatId);
|
|
91
|
+
},
|
|
92
|
+
logger: childLogger,
|
|
93
|
+
async inject({ sessionKey, text }) {
|
|
94
|
+
await api.session.workflow.enqueueNextTurnInjection({
|
|
95
|
+
sessionKey,
|
|
96
|
+
text,
|
|
97
|
+
placement: "append_context",
|
|
98
|
+
ttlMs: 5 * 60_000,
|
|
99
|
+
});
|
|
100
|
+
},
|
|
101
|
+
// Background plan turn: scheduleSessionTurn with delayMs=0 fires
|
|
102
|
+
// the LLM immediately with the supplied message as the turn
|
|
103
|
+
// input. deliveryMode: "none" suppresses any user-visible
|
|
104
|
+
// notification for the scheduled turn — the agent processes the
|
|
105
|
+
// round, calls lyriel_plan_reply via its toolset, and returns
|
|
106
|
+
// without the user seeing anything until the plan locks.
|
|
107
|
+
async scheduleBackgroundTurn({ sessionKey, message }) {
|
|
108
|
+
await api.session.workflow.scheduleSessionTurn({
|
|
109
|
+
sessionKey,
|
|
110
|
+
message,
|
|
111
|
+
delayMs: 0,
|
|
112
|
+
deliveryMode: "none",
|
|
113
|
+
name: "lyriel.plan-round",
|
|
114
|
+
tag: "lyriel.background",
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
api.on("gateway_start", async () => {
|
|
119
|
+
if (!apiKey) {
|
|
120
|
+
api.logger.warn("Lyriel plugin: gateway_start received but apiKey is empty — not starting inbox poll loop.");
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
inbox.start();
|
|
124
|
+
api.logger.info("Lyriel plugin registered: 5 tools (ask, reply, plan_send, plan_reply, plan_lock) + inbox poll loop");
|
|
125
|
+
});
|
|
126
|
+
api.on("gateway_stop", async () => {
|
|
127
|
+
await inbox.stop();
|
|
128
|
+
mostRecentSessionKey = null;
|
|
129
|
+
});
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"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"}
|
package/dist/pending.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// In-process state shared between the inbox poll loop and the LLM tools.
|
|
2
|
+
//
|
|
3
|
+
// Two maps, both lost on Gateway restart (acceptable v0 limitation — same as
|
|
4
|
+
// the Hermes plugin's pending.py). For a long-lived deployment, swap these
|
|
5
|
+
// for api.runtime.state.openKeyedStore so callbacks survive restart.
|
|
6
|
+
//
|
|
7
|
+
// pendingAsks: ask_id -> callback URL + single-use Bearer token
|
|
8
|
+
// pendingPlans: plan_id -> current-round callback (overwritten each round)
|
|
9
|
+
// absorbedAsks: ask_id -> timestamp; tracks asks whose response was inlined
|
|
10
|
+
// into the lyriel_send_ask tool result (system_responder case) so the
|
|
11
|
+
// inbox poll suppresses the duplicate forward surface.
|
|
12
|
+
const pendingAsks = new Map();
|
|
13
|
+
const pendingPlans = new Map();
|
|
14
|
+
const absorbedAsks = new Map();
|
|
15
|
+
const ABSORBED_TTL_MS = 60_000;
|
|
16
|
+
// The sealed-ask envelope (web/src/lib/server/agents/prompt.ts) embeds the
|
|
17
|
+
// callback in plain text: `POST <url>` then `Bearer <token>`. Asks use
|
|
18
|
+
// lct_<...> tokens; plans use plt_<...> tokens.
|
|
19
|
+
const RE_CALLBACK_URL = /POST\s+(https?:\/\/\S+)/;
|
|
20
|
+
const RE_CALLBACK_TOKEN = /Bearer\s+((?:lct|plt)_\S+)/;
|
|
21
|
+
const RE_ASK_TEXT = /The ask:\s*"""\s*([\s\S]+?)\s*"""/;
|
|
22
|
+
const RE_PLAN_DESCRIPTION = /THE PLAN:\s*"""\s*([\s\S]+?)\s*"""/;
|
|
23
|
+
function stripTrailingPunct(s) {
|
|
24
|
+
return s.replace(/[,.;]+$/, "");
|
|
25
|
+
}
|
|
26
|
+
export function rememberAsk(askId, prompt) {
|
|
27
|
+
const urlMatch = prompt.match(RE_CALLBACK_URL);
|
|
28
|
+
const tokenMatch = prompt.match(RE_CALLBACK_TOKEN);
|
|
29
|
+
if (!urlMatch || !tokenMatch) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
const askMatch = prompt.match(RE_ASK_TEXT);
|
|
33
|
+
const excerpt = (askMatch ? askMatch[1].trim() : prompt.slice(0, 80)).trim();
|
|
34
|
+
pendingAsks.set(askId, {
|
|
35
|
+
callbackUrl: stripTrailingPunct(urlMatch[1]),
|
|
36
|
+
callbackToken: stripTrailingPunct(tokenMatch[1]),
|
|
37
|
+
receivedAt: Date.now(),
|
|
38
|
+
promptExcerpt: excerpt.slice(0, 200),
|
|
39
|
+
});
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
export function takeAsk(askId) {
|
|
43
|
+
const entry = pendingAsks.get(askId);
|
|
44
|
+
if (entry) {
|
|
45
|
+
pendingAsks.delete(askId);
|
|
46
|
+
}
|
|
47
|
+
return entry;
|
|
48
|
+
}
|
|
49
|
+
export function restoreAsk(askId, entry) {
|
|
50
|
+
pendingAsks.set(askId, entry);
|
|
51
|
+
}
|
|
52
|
+
export function listPendingAsks() {
|
|
53
|
+
return Object.fromEntries(pendingAsks);
|
|
54
|
+
}
|
|
55
|
+
export function markAbsorbed(askId) {
|
|
56
|
+
absorbedAsks.set(askId, Date.now());
|
|
57
|
+
pruneAbsorbed();
|
|
58
|
+
}
|
|
59
|
+
export function wasAbsorbed(askId) {
|
|
60
|
+
pruneAbsorbed();
|
|
61
|
+
return absorbedAsks.has(askId);
|
|
62
|
+
}
|
|
63
|
+
function pruneAbsorbed() {
|
|
64
|
+
const cutoff = Date.now() - ABSORBED_TTL_MS;
|
|
65
|
+
for (const [id, ts] of absorbedAsks) {
|
|
66
|
+
if (ts < cutoff) {
|
|
67
|
+
absorbedAsks.delete(id);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Plans are multi-round: each round Lyriel sends a fresh envelope with a
|
|
72
|
+
// fresh single-use callback token. Always overwrite — the previous round's
|
|
73
|
+
// token is dead anyway.
|
|
74
|
+
export function rememberPlan(planId, prompt) {
|
|
75
|
+
const urlMatch = prompt.match(RE_CALLBACK_URL);
|
|
76
|
+
const tokenMatch = prompt.match(RE_CALLBACK_TOKEN);
|
|
77
|
+
if (!urlMatch || !tokenMatch) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
const descMatch = prompt.match(RE_PLAN_DESCRIPTION);
|
|
81
|
+
const excerpt = (descMatch ? descMatch[1].trim() : prompt.slice(0, 80)).trim();
|
|
82
|
+
pendingPlans.set(planId, {
|
|
83
|
+
callbackUrl: stripTrailingPunct(urlMatch[1]),
|
|
84
|
+
callbackToken: stripTrailingPunct(tokenMatch[1]),
|
|
85
|
+
receivedAt: Date.now(),
|
|
86
|
+
promptExcerpt: excerpt.slice(0, 200),
|
|
87
|
+
});
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
export function takePlan(planId) {
|
|
91
|
+
const entry = pendingPlans.get(planId);
|
|
92
|
+
if (entry) {
|
|
93
|
+
pendingPlans.delete(planId);
|
|
94
|
+
}
|
|
95
|
+
return entry;
|
|
96
|
+
}
|
|
97
|
+
export function restorePlan(planId, entry) {
|
|
98
|
+
pendingPlans.set(planId, entry);
|
|
99
|
+
}
|
|
100
|
+
export function listPendingPlans() {
|
|
101
|
+
return Object.fromEntries(pendingPlans);
|
|
102
|
+
}
|
|
103
|
+
export function forgetPlan(planId) {
|
|
104
|
+
pendingPlans.delete(planId);
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=pending.js.map
|