@alfe.ai/openclaw-chat 0.8.1 → 0.9.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/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/plugin.cjs +2 -0
- package/dist/plugin.d.cts +33 -1
- package/dist/plugin.d.ts +33 -1
- package/dist/plugin.js +2 -2
- package/dist/plugin2.cjs +98 -10
- package/dist/plugin2.d.cts +2 -2
- package/dist/plugin2.d.ts +2 -2
- package/dist/plugin2.js +87 -11
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { S as SessionSummary, _ as AlfeChannelConfig, b as ChatMessage, f as plugin, g as createAlfeChannelPlugin, v as AlfePluginConfig, x as SessionData, y as AlfeResolvedAccount } from "./plugin.cjs";
|
|
2
2
|
export { type AlfeChannelConfig, type AlfePluginConfig, type AlfeResolvedAccount, type ChatMessage, type SessionData, type SessionSummary, createAlfeChannelPlugin, plugin as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { S as SessionSummary, _ as AlfeChannelConfig, b as ChatMessage, f as plugin, g as createAlfeChannelPlugin, v as AlfePluginConfig, x as SessionData, y as AlfeResolvedAccount } from "./plugin.js";
|
|
2
2
|
export { type AlfeChannelConfig, type AlfePluginConfig, type AlfeResolvedAccount, type ChatMessage, type SessionData, type SessionSummary, createAlfeChannelPlugin, plugin as default };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { m as createAlfeChannelPlugin, u as plugin } from "./plugin2.js";
|
|
2
2
|
export { createAlfeChannelPlugin, plugin as default };
|
package/dist/plugin.cjs
CHANGED
|
@@ -3,11 +3,13 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_plugin = require("./plugin2.cjs");
|
|
6
|
+
exports.VOICE_REPLY_SYSTEM_PROMPT = require_plugin.VOICE_REPLY_SYSTEM_PROMPT;
|
|
6
7
|
exports.__agentTurnQueueForTest = require_plugin.__agentTurnQueueForTest;
|
|
7
8
|
exports.__resetAgentTurnQueueForTest = require_plugin.__resetAgentTurnQueueForTest;
|
|
8
9
|
exports.__setActiveRunForTest = require_plugin.__setActiveRunForTest;
|
|
9
10
|
exports.admitAgentEvent = require_plugin.admitAgentEvent;
|
|
10
11
|
exports.buildA2ACompletePayload = require_plugin.buildA2ACompletePayload;
|
|
12
|
+
exports.buildOriginEnvelopeContext = require_plugin.buildOriginEnvelopeContext;
|
|
11
13
|
exports.buildToolActivity = require_plugin.buildToolActivity;
|
|
12
14
|
exports.default = require_plugin.plugin;
|
|
13
15
|
exports.joinAssistantText = require_plugin.joinAssistantText;
|
package/dist/plugin.d.cts
CHANGED
|
@@ -597,6 +597,38 @@ declare function __setActiveRunForTest(ref: ActiveRunRef | null): void;
|
|
|
597
597
|
* never abort another session's run on a shared daemon.
|
|
598
598
|
*/
|
|
599
599
|
declare function resolveAbortTargetKeys(active: ActiveRunRef | null, abortSessionKey: string | undefined): string[];
|
|
600
|
+
/**
|
|
601
|
+
* Speech-style reply guidance injected into the agent's SYSTEM PROMPT for a
|
|
602
|
+
* live voice-call turn (`origin:'voice'`). The user is speaking on a call and
|
|
603
|
+
* the reply is read aloud by TTS, so a huge markdown paragraph reads terribly.
|
|
604
|
+
* This biases STYLE only — explicitly NOT a hard cap: it must not suppress tool
|
|
605
|
+
* use or make the agent omit information the user actually needs.
|
|
606
|
+
*/
|
|
607
|
+
declare const VOICE_REPLY_SYSTEM_PROMPT: string;
|
|
608
|
+
/**
|
|
609
|
+
* Per-turn envelope context derived from the RPC `origin` flag. For a live
|
|
610
|
+
* voice call it returns a `GroupSystemPrompt` block, which is the PROVEN
|
|
611
|
+
* runtime-rendered seam for per-turn system-prompt guidance: dispatchInbound
|
|
612
|
+
* spreads `extraContext` into `finalizeInboundContext` → `sessionCtx.GroupSystemPrompt`
|
|
613
|
+
* → `extraSystemPrompt` → the model's system prompt (rendered under a
|
|
614
|
+
* "## Group Chat Context" heading; read unconditionally, NOT gated on group
|
|
615
|
+
* chats). Returns `{}` for every other origin, so text/A2A turns are unchanged.
|
|
616
|
+
*
|
|
617
|
+
* Why `GroupSystemPrompt` and not a bespoke key like `A2ASystemPrompt`: the
|
|
618
|
+
* OpenClaw runtime does NOT read arbitrary `extraContext` keys — a custom key
|
|
619
|
+
* only reaches the model if a persona/AGENTS.md template interpolates it, so it
|
|
620
|
+
* silently rots into a dead wire otherwise (the `thinking` param is the
|
|
621
|
+
* cautionary precedent). `GroupSystemPrompt` is verified to reach the system
|
|
622
|
+
* prompt via the runtime itself.
|
|
623
|
+
*
|
|
624
|
+
* Transcript stays clean: this only feeds the model's system prompt for THIS
|
|
625
|
+
* turn. The persisted user message (`addMessage(sessionId, 'user', message)`)
|
|
626
|
+
* and everything `sessions.get` returns are the raw user text — the guidance is
|
|
627
|
+
* never stored or broadcast.
|
|
628
|
+
*
|
|
629
|
+
* Exported pure for unit testing (mirrors the codebase's pure-helper pattern).
|
|
630
|
+
*/
|
|
631
|
+
declare function buildOriginEnvelopeContext(origin: string | undefined): Record<string, unknown>;
|
|
600
632
|
declare const plugin: {
|
|
601
633
|
id: string;
|
|
602
634
|
name: string;
|
|
@@ -606,4 +638,4 @@ declare const plugin: {
|
|
|
606
638
|
deactivate(api: PluginApi): Promise<void>;
|
|
607
639
|
};
|
|
608
640
|
//#endregion
|
|
609
|
-
export {
|
|
641
|
+
export { SessionSummary as S, AlfeChannelConfig as _, __resetAgentTurnQueueForTest as a, ChatMessage as b, buildA2ACompletePayload as c, joinAssistantText as d, plugin as f, createAlfeChannelPlugin as g, stripLeakedToolSummary as h, __agentTurnQueueForTest as i, buildOriginEnvelopeContext as l, rewriteAssistantText as m, RunEventGate as n, __setActiveRunForTest as o, resolveAbortTargetKeys as p, VOICE_REPLY_SYSTEM_PROMPT as r, admitAgentEvent as s, ActiveRunRef as t, buildToolActivity as u, AlfePluginConfig as v, SessionData as x, AlfeResolvedAccount as y };
|
package/dist/plugin.d.ts
CHANGED
|
@@ -597,6 +597,38 @@ declare function __setActiveRunForTest(ref: ActiveRunRef | null): void;
|
|
|
597
597
|
* never abort another session's run on a shared daemon.
|
|
598
598
|
*/
|
|
599
599
|
declare function resolveAbortTargetKeys(active: ActiveRunRef | null, abortSessionKey: string | undefined): string[];
|
|
600
|
+
/**
|
|
601
|
+
* Speech-style reply guidance injected into the agent's SYSTEM PROMPT for a
|
|
602
|
+
* live voice-call turn (`origin:'voice'`). The user is speaking on a call and
|
|
603
|
+
* the reply is read aloud by TTS, so a huge markdown paragraph reads terribly.
|
|
604
|
+
* This biases STYLE only — explicitly NOT a hard cap: it must not suppress tool
|
|
605
|
+
* use or make the agent omit information the user actually needs.
|
|
606
|
+
*/
|
|
607
|
+
declare const VOICE_REPLY_SYSTEM_PROMPT: string;
|
|
608
|
+
/**
|
|
609
|
+
* Per-turn envelope context derived from the RPC `origin` flag. For a live
|
|
610
|
+
* voice call it returns a `GroupSystemPrompt` block, which is the PROVEN
|
|
611
|
+
* runtime-rendered seam for per-turn system-prompt guidance: dispatchInbound
|
|
612
|
+
* spreads `extraContext` into `finalizeInboundContext` → `sessionCtx.GroupSystemPrompt`
|
|
613
|
+
* → `extraSystemPrompt` → the model's system prompt (rendered under a
|
|
614
|
+
* "## Group Chat Context" heading; read unconditionally, NOT gated on group
|
|
615
|
+
* chats). Returns `{}` for every other origin, so text/A2A turns are unchanged.
|
|
616
|
+
*
|
|
617
|
+
* Why `GroupSystemPrompt` and not a bespoke key like `A2ASystemPrompt`: the
|
|
618
|
+
* OpenClaw runtime does NOT read arbitrary `extraContext` keys — a custom key
|
|
619
|
+
* only reaches the model if a persona/AGENTS.md template interpolates it, so it
|
|
620
|
+
* silently rots into a dead wire otherwise (the `thinking` param is the
|
|
621
|
+
* cautionary precedent). `GroupSystemPrompt` is verified to reach the system
|
|
622
|
+
* prompt via the runtime itself.
|
|
623
|
+
*
|
|
624
|
+
* Transcript stays clean: this only feeds the model's system prompt for THIS
|
|
625
|
+
* turn. The persisted user message (`addMessage(sessionId, 'user', message)`)
|
|
626
|
+
* and everything `sessions.get` returns are the raw user text — the guidance is
|
|
627
|
+
* never stored or broadcast.
|
|
628
|
+
*
|
|
629
|
+
* Exported pure for unit testing (mirrors the codebase's pure-helper pattern).
|
|
630
|
+
*/
|
|
631
|
+
declare function buildOriginEnvelopeContext(origin: string | undefined): Record<string, unknown>;
|
|
600
632
|
declare const plugin: {
|
|
601
633
|
id: string;
|
|
602
634
|
name: string;
|
|
@@ -606,4 +638,4 @@ declare const plugin: {
|
|
|
606
638
|
deactivate(api: PluginApi): Promise<void>;
|
|
607
639
|
};
|
|
608
640
|
//#endregion
|
|
609
|
-
export {
|
|
641
|
+
export { SessionSummary as S, AlfeChannelConfig as _, __resetAgentTurnQueueForTest as a, ChatMessage as b, buildA2ACompletePayload as c, joinAssistantText as d, plugin as f, createAlfeChannelPlugin as g, stripLeakedToolSummary as h, __agentTurnQueueForTest as i, buildOriginEnvelopeContext as l, rewriteAssistantText as m, RunEventGate as n, __setActiveRunForTest as o, resolveAbortTargetKeys as p, VOICE_REPLY_SYSTEM_PROMPT as r, admitAgentEvent as s, ActiveRunRef as t, buildToolActivity as u, AlfePluginConfig as v, SessionData as x, AlfeResolvedAccount as y };
|
package/dist/plugin.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { __agentTurnQueueForTest, __resetAgentTurnQueueForTest, __setActiveRunForTest, admitAgentEvent, buildA2ACompletePayload, buildToolActivity, plugin as default, joinAssistantText, resolveAbortTargetKeys, rewriteAssistantText, stripLeakedToolSummary };
|
|
1
|
+
import { a as admitAgentEvent, c as buildToolActivity, d as resolveAbortTargetKeys, f as rewriteAssistantText, i as __setActiveRunForTest, l as joinAssistantText, n as __agentTurnQueueForTest, o as buildA2ACompletePayload, p as stripLeakedToolSummary, r as __resetAgentTurnQueueForTest, s as buildOriginEnvelopeContext, t as VOICE_REPLY_SYSTEM_PROMPT, u as plugin } from "./plugin2.js";
|
|
2
|
+
export { VOICE_REPLY_SYSTEM_PROMPT, __agentTurnQueueForTest, __resetAgentTurnQueueForTest, __setActiveRunForTest, admitAgentEvent, buildA2ACompletePayload, buildOriginEnvelopeContext, buildToolActivity, plugin as default, joinAssistantText, resolveAbortTargetKeys, rewriteAssistantText, stripLeakedToolSummary };
|
package/dist/plugin2.cjs
CHANGED
|
@@ -106,9 +106,9 @@ function extensionOf(p) {
|
|
|
106
106
|
function isRemoteUrl(ref) {
|
|
107
107
|
return /^https?:\/\//i.test(ref);
|
|
108
108
|
}
|
|
109
|
-
/** Non-http(s) URI schemes (data:, blob:,
|
|
110
|
-
*
|
|
111
|
-
*
|
|
109
|
+
/** Non-http(s) URI schemes (data:, blob:, …) — not local paths, nothing we
|
|
110
|
+
* can upload. Windows drive letters (C:\) are paths, not schemes.
|
|
111
|
+
* `media://inbound/<id>` is handled separately (resolveOpenClawMediaPath). */
|
|
112
112
|
function hasNonPathScheme(ref) {
|
|
113
113
|
if (/^file:\/\//i.test(ref)) return false;
|
|
114
114
|
if (/^[a-zA-Z]:[\\/]/.test(ref)) return false;
|
|
@@ -120,6 +120,34 @@ function resolveLocalMediaPath(ref) {
|
|
|
120
120
|
if (p === "~" || p.startsWith("~/")) p = (0, node_path.join)((0, node_os.homedir)(), p.slice(1));
|
|
121
121
|
return (0, node_path.isAbsolute)(p) ? p : (0, node_path.resolve)(process.cwd(), p);
|
|
122
122
|
}
|
|
123
|
+
const MEDIA_INBOUND_RE = /^media:\/\/inbound\/([^/\\]+)$/i;
|
|
124
|
+
/** Resolve a `media://inbound/<id>` URI to its media-store file, or null if
|
|
125
|
+
* the ref isn't one (or the id is unsafe). Traversal-hardened: the id must
|
|
126
|
+
* be a single path segment. */
|
|
127
|
+
function resolveOpenClawMediaPath(ref) {
|
|
128
|
+
const match = MEDIA_INBOUND_RE.exec(ref.trim());
|
|
129
|
+
if (!match) return null;
|
|
130
|
+
let id = match[1];
|
|
131
|
+
try {
|
|
132
|
+
id = decodeURIComponent(id);
|
|
133
|
+
} catch {}
|
|
134
|
+
if (!id || id === "." || id === ".." || /[/\\\0]/.test(id)) return null;
|
|
135
|
+
const stateDirOverride = process.env.OPENCLAW_STATE_DIR?.trim();
|
|
136
|
+
const home = process.env.OPENCLAW_HOME?.trim();
|
|
137
|
+
return (0, node_path.join)(stateDirOverride?.length ? stateDirOverride : (0, node_path.join)(home?.length ? home : (0, node_os.homedir)(), ".openclaw"), "media", "inbound", id);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Map an outbound ref to the local file we would upload: media-store path
|
|
141
|
+
* for `media://inbound/…`, filesystem path for path-shaped refs, null for
|
|
142
|
+
* remote URLs and unsupported schemes (data:, blob:, …).
|
|
143
|
+
*/
|
|
144
|
+
function toLocalUploadPath(ref) {
|
|
145
|
+
if (isRemoteUrl(ref)) return null;
|
|
146
|
+
const mediaStorePath = resolveOpenClawMediaPath(ref);
|
|
147
|
+
if (mediaStorePath) return mediaStorePath;
|
|
148
|
+
if (hasNonPathScheme(ref)) return null;
|
|
149
|
+
return resolveLocalMediaPath(ref);
|
|
150
|
+
}
|
|
123
151
|
async function uploadLocalFile(localPath, log, deps) {
|
|
124
152
|
const client = deps.getClient ? deps.getClient() : getUploadClient(log);
|
|
125
153
|
if (!client) return null;
|
|
@@ -197,11 +225,11 @@ function resolveOutboundMediaRef(ref, ctx, log, deps = {}) {
|
|
|
197
225
|
const trimmed = ref.trim();
|
|
198
226
|
if (!trimmed) return Promise.resolve(null);
|
|
199
227
|
if (isRemoteUrl(trimmed)) return Promise.resolve({ url: trimmed });
|
|
200
|
-
|
|
228
|
+
const localPath = toLocalUploadPath(trimmed);
|
|
229
|
+
if (!localPath) {
|
|
201
230
|
log.warn(`Outbound media ref has unsupported scheme — skipping: ${trimmed.slice(0, 120)}`);
|
|
202
231
|
return Promise.resolve(null);
|
|
203
232
|
}
|
|
204
|
-
const localPath = resolveLocalMediaPath(trimmed);
|
|
205
233
|
const cached = ctx.uploads.get(localPath);
|
|
206
234
|
if (cached) return cached;
|
|
207
235
|
const promise = ctx.uploads.size >= 10 ? (() => {
|
|
@@ -230,8 +258,8 @@ async function resolveOutboundMedia(refs, ctx, log, deps = {}) {
|
|
|
230
258
|
attachments.push(resolved);
|
|
231
259
|
continue;
|
|
232
260
|
}
|
|
233
|
-
|
|
234
|
-
|
|
261
|
+
const localPath = toLocalUploadPath(trimmed);
|
|
262
|
+
if (!localPath) continue;
|
|
235
263
|
if (!ctx.annotatedFailures.has(localPath)) {
|
|
236
264
|
ctx.annotatedFailures.add(localPath);
|
|
237
265
|
newFailures.push((0, node_path.basename)(localPath));
|
|
@@ -327,11 +355,16 @@ async function rewriteLocalMarkdownImages(text, ctx, log, deps = {}) {
|
|
|
327
355
|
const start = match.index;
|
|
328
356
|
out += seg.text.slice(cursor, start);
|
|
329
357
|
cursor = start + full.length;
|
|
330
|
-
if (isRemoteUrl(src)
|
|
358
|
+
if (isRemoteUrl(src)) {
|
|
331
359
|
out += full;
|
|
332
360
|
continue;
|
|
333
361
|
}
|
|
334
|
-
const
|
|
362
|
+
const localPath = toLocalUploadPath(src);
|
|
363
|
+
if (!localPath) {
|
|
364
|
+
out += full;
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
const ext = extensionOf(localPath.split("?")[0]);
|
|
335
368
|
if (!ext || !IMAGE_EXTENSIONS.has(ext)) {
|
|
336
369
|
out += full;
|
|
337
370
|
continue;
|
|
@@ -2255,6 +2288,48 @@ function handleChatAbort(request, log) {
|
|
|
2255
2288
|
} else log.warn("chat.abort: OpenClaw abort primitives unavailable — cannot hard-abort the running turn");
|
|
2256
2289
|
chatClient?.sendResponse(request.id, true, { aborted });
|
|
2257
2290
|
}
|
|
2291
|
+
/**
|
|
2292
|
+
* Speech-style reply guidance injected into the agent's SYSTEM PROMPT for a
|
|
2293
|
+
* live voice-call turn (`origin:'voice'`). The user is speaking on a call and
|
|
2294
|
+
* the reply is read aloud by TTS, so a huge markdown paragraph reads terribly.
|
|
2295
|
+
* This biases STYLE only — explicitly NOT a hard cap: it must not suppress tool
|
|
2296
|
+
* use or make the agent omit information the user actually needs.
|
|
2297
|
+
*/
|
|
2298
|
+
const VOICE_REPLY_SYSTEM_PROMPT = [
|
|
2299
|
+
"This message arrived over a LIVE VOICE CALL — your reply is read aloud by text-to-speech.",
|
|
2300
|
+
"Speak the way a helpful person would on a phone call:",
|
|
2301
|
+
"- Keep it brief and conversational — usually 1-3 short sentences.",
|
|
2302
|
+
"- Plain spoken language only: no markdown, no bullet or numbered lists, no headings, no code blocks, no URLs or link syntax.",
|
|
2303
|
+
"- If the full answer is long or detailed, give the short spoken version and offer to send the details to the chat.",
|
|
2304
|
+
"This is guidance on speaking STYLE only. Do NOT skip tool calls you would otherwise make, and never omit information the user actually needs — just deliver it the way you would say it out loud."
|
|
2305
|
+
].join("\n");
|
|
2306
|
+
/**
|
|
2307
|
+
* Per-turn envelope context derived from the RPC `origin` flag. For a live
|
|
2308
|
+
* voice call it returns a `GroupSystemPrompt` block, which is the PROVEN
|
|
2309
|
+
* runtime-rendered seam for per-turn system-prompt guidance: dispatchInbound
|
|
2310
|
+
* spreads `extraContext` into `finalizeInboundContext` → `sessionCtx.GroupSystemPrompt`
|
|
2311
|
+
* → `extraSystemPrompt` → the model's system prompt (rendered under a
|
|
2312
|
+
* "## Group Chat Context" heading; read unconditionally, NOT gated on group
|
|
2313
|
+
* chats). Returns `{}` for every other origin, so text/A2A turns are unchanged.
|
|
2314
|
+
*
|
|
2315
|
+
* Why `GroupSystemPrompt` and not a bespoke key like `A2ASystemPrompt`: the
|
|
2316
|
+
* OpenClaw runtime does NOT read arbitrary `extraContext` keys — a custom key
|
|
2317
|
+
* only reaches the model if a persona/AGENTS.md template interpolates it, so it
|
|
2318
|
+
* silently rots into a dead wire otherwise (the `thinking` param is the
|
|
2319
|
+
* cautionary precedent). `GroupSystemPrompt` is verified to reach the system
|
|
2320
|
+
* prompt via the runtime itself.
|
|
2321
|
+
*
|
|
2322
|
+
* Transcript stays clean: this only feeds the model's system prompt for THIS
|
|
2323
|
+
* turn. The persisted user message (`addMessage(sessionId, 'user', message)`)
|
|
2324
|
+
* and everything `sessions.get` returns are the raw user text — the guidance is
|
|
2325
|
+
* never stored or broadcast.
|
|
2326
|
+
*
|
|
2327
|
+
* Exported pure for unit testing (mirrors the codebase's pure-helper pattern).
|
|
2328
|
+
*/
|
|
2329
|
+
function buildOriginEnvelopeContext(origin) {
|
|
2330
|
+
if (origin === "voice") return { GroupSystemPrompt: VOICE_REPLY_SYSTEM_PROMPT };
|
|
2331
|
+
return {};
|
|
2332
|
+
}
|
|
2258
2333
|
async function handleAgentRequest(request, log) {
|
|
2259
2334
|
const runtime = pluginRuntime;
|
|
2260
2335
|
if (!runtime) {
|
|
@@ -2265,7 +2340,7 @@ async function handleAgentRequest(request, log) {
|
|
|
2265
2340
|
chatClient?.sendResponse(request.id, false, { message: "OpenClaw SDK not available — cannot dispatch" });
|
|
2266
2341
|
return;
|
|
2267
2342
|
}
|
|
2268
|
-
const { message, sessionKey: legacySessionKey, userId, conversationId, conversationType, tenantId, clientType, displayName, identityId, senderPermissions, attachments: rawAttachments, a2a, chatMessageId } = request.params;
|
|
2343
|
+
const { message, sessionKey: legacySessionKey, userId, conversationId, conversationType, tenantId, clientType, origin, displayName, identityId, senderPermissions, attachments: rawAttachments, a2a, chatMessageId } = request.params;
|
|
2269
2344
|
const isA2A = !!a2a;
|
|
2270
2345
|
if (!message && !rawAttachments?.length) {
|
|
2271
2346
|
chatClient?.sendResponse(request.id, false, { message: "Missing message" });
|
|
@@ -2487,6 +2562,7 @@ async function handleAgentRequest(request, log) {
|
|
|
2487
2562
|
...userId ? { UserId: userId } : {},
|
|
2488
2563
|
...senderPermissions?.length ? { SenderPermissions: senderPermissions } : {},
|
|
2489
2564
|
ChannelMode: channelMode,
|
|
2565
|
+
...buildOriginEnvelopeContext(origin),
|
|
2490
2566
|
...isA2A ? {
|
|
2491
2567
|
CallerType: "agent",
|
|
2492
2568
|
CallerAgentId: a2a.sourceAgentId,
|
|
@@ -2836,6 +2912,12 @@ const plugin = {
|
|
|
2836
2912
|
}
|
|
2837
2913
|
};
|
|
2838
2914
|
//#endregion
|
|
2915
|
+
Object.defineProperty(exports, "VOICE_REPLY_SYSTEM_PROMPT", {
|
|
2916
|
+
enumerable: true,
|
|
2917
|
+
get: function() {
|
|
2918
|
+
return VOICE_REPLY_SYSTEM_PROMPT;
|
|
2919
|
+
}
|
|
2920
|
+
});
|
|
2839
2921
|
Object.defineProperty(exports, "__agentTurnQueueForTest", {
|
|
2840
2922
|
enumerable: true,
|
|
2841
2923
|
get: function() {
|
|
@@ -2866,6 +2948,12 @@ Object.defineProperty(exports, "buildA2ACompletePayload", {
|
|
|
2866
2948
|
return buildA2ACompletePayload;
|
|
2867
2949
|
}
|
|
2868
2950
|
});
|
|
2951
|
+
Object.defineProperty(exports, "buildOriginEnvelopeContext", {
|
|
2952
|
+
enumerable: true,
|
|
2953
|
+
get: function() {
|
|
2954
|
+
return buildOriginEnvelopeContext;
|
|
2955
|
+
}
|
|
2956
|
+
});
|
|
2869
2957
|
Object.defineProperty(exports, "buildToolActivity", {
|
|
2870
2958
|
enumerable: true,
|
|
2871
2959
|
get: function() {
|
package/dist/plugin2.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { ActiveRunRef, RunEventGate, __agentTurnQueueForTest, __resetAgentTurnQueueForTest, __setActiveRunForTest, admitAgentEvent, buildA2ACompletePayload, buildToolActivity, plugin as default, joinAssistantText, resolveAbortTargetKeys, rewriteAssistantText, stripLeakedToolSummary };
|
|
1
|
+
import { a as __resetAgentTurnQueueForTest, c as buildA2ACompletePayload, d as joinAssistantText, f as plugin, h as stripLeakedToolSummary, i as __agentTurnQueueForTest, l as buildOriginEnvelopeContext, m as rewriteAssistantText, n as RunEventGate, o as __setActiveRunForTest, p as resolveAbortTargetKeys, r as VOICE_REPLY_SYSTEM_PROMPT, s as admitAgentEvent, t as ActiveRunRef, u as buildToolActivity } from "./plugin.cjs";
|
|
2
|
+
export { ActiveRunRef, RunEventGate, VOICE_REPLY_SYSTEM_PROMPT, __agentTurnQueueForTest, __resetAgentTurnQueueForTest, __setActiveRunForTest, admitAgentEvent, buildA2ACompletePayload, buildOriginEnvelopeContext, buildToolActivity, plugin as default, joinAssistantText, resolveAbortTargetKeys, rewriteAssistantText, stripLeakedToolSummary };
|
package/dist/plugin2.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { ActiveRunRef, RunEventGate, __agentTurnQueueForTest, __resetAgentTurnQueueForTest, __setActiveRunForTest, admitAgentEvent, buildA2ACompletePayload, buildToolActivity, plugin as default, joinAssistantText, resolveAbortTargetKeys, rewriteAssistantText, stripLeakedToolSummary };
|
|
1
|
+
import { a as __resetAgentTurnQueueForTest, c as buildA2ACompletePayload, d as joinAssistantText, f as plugin, h as stripLeakedToolSummary, i as __agentTurnQueueForTest, l as buildOriginEnvelopeContext, m as rewriteAssistantText, n as RunEventGate, o as __setActiveRunForTest, p as resolveAbortTargetKeys, r as VOICE_REPLY_SYSTEM_PROMPT, s as admitAgentEvent, t as ActiveRunRef, u as buildToolActivity } from "./plugin.js";
|
|
2
|
+
export { ActiveRunRef, RunEventGate, VOICE_REPLY_SYSTEM_PROMPT, __agentTurnQueueForTest, __resetAgentTurnQueueForTest, __setActiveRunForTest, admitAgentEvent, buildA2ACompletePayload, buildOriginEnvelopeContext, buildToolActivity, plugin as default, joinAssistantText, resolveAbortTargetKeys, rewriteAssistantText, stripLeakedToolSummary };
|
package/dist/plugin2.js
CHANGED
|
@@ -106,9 +106,9 @@ function extensionOf(p) {
|
|
|
106
106
|
function isRemoteUrl(ref) {
|
|
107
107
|
return /^https?:\/\//i.test(ref);
|
|
108
108
|
}
|
|
109
|
-
/** Non-http(s) URI schemes (data:, blob:,
|
|
110
|
-
*
|
|
111
|
-
*
|
|
109
|
+
/** Non-http(s) URI schemes (data:, blob:, …) — not local paths, nothing we
|
|
110
|
+
* can upload. Windows drive letters (C:\) are paths, not schemes.
|
|
111
|
+
* `media://inbound/<id>` is handled separately (resolveOpenClawMediaPath). */
|
|
112
112
|
function hasNonPathScheme(ref) {
|
|
113
113
|
if (/^file:\/\//i.test(ref)) return false;
|
|
114
114
|
if (/^[a-zA-Z]:[\\/]/.test(ref)) return false;
|
|
@@ -120,6 +120,34 @@ function resolveLocalMediaPath(ref) {
|
|
|
120
120
|
if (p === "~" || p.startsWith("~/")) p = join(homedir(), p.slice(1));
|
|
121
121
|
return isAbsolute(p) ? p : resolve(process.cwd(), p);
|
|
122
122
|
}
|
|
123
|
+
const MEDIA_INBOUND_RE = /^media:\/\/inbound\/([^/\\]+)$/i;
|
|
124
|
+
/** Resolve a `media://inbound/<id>` URI to its media-store file, or null if
|
|
125
|
+
* the ref isn't one (or the id is unsafe). Traversal-hardened: the id must
|
|
126
|
+
* be a single path segment. */
|
|
127
|
+
function resolveOpenClawMediaPath(ref) {
|
|
128
|
+
const match = MEDIA_INBOUND_RE.exec(ref.trim());
|
|
129
|
+
if (!match) return null;
|
|
130
|
+
let id = match[1];
|
|
131
|
+
try {
|
|
132
|
+
id = decodeURIComponent(id);
|
|
133
|
+
} catch {}
|
|
134
|
+
if (!id || id === "." || id === ".." || /[/\\\0]/.test(id)) return null;
|
|
135
|
+
const stateDirOverride = process.env.OPENCLAW_STATE_DIR?.trim();
|
|
136
|
+
const home = process.env.OPENCLAW_HOME?.trim();
|
|
137
|
+
return join(stateDirOverride?.length ? stateDirOverride : join(home?.length ? home : homedir(), ".openclaw"), "media", "inbound", id);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Map an outbound ref to the local file we would upload: media-store path
|
|
141
|
+
* for `media://inbound/…`, filesystem path for path-shaped refs, null for
|
|
142
|
+
* remote URLs and unsupported schemes (data:, blob:, …).
|
|
143
|
+
*/
|
|
144
|
+
function toLocalUploadPath(ref) {
|
|
145
|
+
if (isRemoteUrl(ref)) return null;
|
|
146
|
+
const mediaStorePath = resolveOpenClawMediaPath(ref);
|
|
147
|
+
if (mediaStorePath) return mediaStorePath;
|
|
148
|
+
if (hasNonPathScheme(ref)) return null;
|
|
149
|
+
return resolveLocalMediaPath(ref);
|
|
150
|
+
}
|
|
123
151
|
async function uploadLocalFile(localPath, log, deps) {
|
|
124
152
|
const client = deps.getClient ? deps.getClient() : getUploadClient(log);
|
|
125
153
|
if (!client) return null;
|
|
@@ -197,11 +225,11 @@ function resolveOutboundMediaRef(ref, ctx, log, deps = {}) {
|
|
|
197
225
|
const trimmed = ref.trim();
|
|
198
226
|
if (!trimmed) return Promise.resolve(null);
|
|
199
227
|
if (isRemoteUrl(trimmed)) return Promise.resolve({ url: trimmed });
|
|
200
|
-
|
|
228
|
+
const localPath = toLocalUploadPath(trimmed);
|
|
229
|
+
if (!localPath) {
|
|
201
230
|
log.warn(`Outbound media ref has unsupported scheme — skipping: ${trimmed.slice(0, 120)}`);
|
|
202
231
|
return Promise.resolve(null);
|
|
203
232
|
}
|
|
204
|
-
const localPath = resolveLocalMediaPath(trimmed);
|
|
205
233
|
const cached = ctx.uploads.get(localPath);
|
|
206
234
|
if (cached) return cached;
|
|
207
235
|
const promise = ctx.uploads.size >= 10 ? (() => {
|
|
@@ -230,8 +258,8 @@ async function resolveOutboundMedia(refs, ctx, log, deps = {}) {
|
|
|
230
258
|
attachments.push(resolved);
|
|
231
259
|
continue;
|
|
232
260
|
}
|
|
233
|
-
|
|
234
|
-
|
|
261
|
+
const localPath = toLocalUploadPath(trimmed);
|
|
262
|
+
if (!localPath) continue;
|
|
235
263
|
if (!ctx.annotatedFailures.has(localPath)) {
|
|
236
264
|
ctx.annotatedFailures.add(localPath);
|
|
237
265
|
newFailures.push(basename(localPath));
|
|
@@ -327,11 +355,16 @@ async function rewriteLocalMarkdownImages(text, ctx, log, deps = {}) {
|
|
|
327
355
|
const start = match.index;
|
|
328
356
|
out += seg.text.slice(cursor, start);
|
|
329
357
|
cursor = start + full.length;
|
|
330
|
-
if (isRemoteUrl(src)
|
|
358
|
+
if (isRemoteUrl(src)) {
|
|
359
|
+
out += full;
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
const localPath = toLocalUploadPath(src);
|
|
363
|
+
if (!localPath) {
|
|
331
364
|
out += full;
|
|
332
365
|
continue;
|
|
333
366
|
}
|
|
334
|
-
const ext = extensionOf(
|
|
367
|
+
const ext = extensionOf(localPath.split("?")[0]);
|
|
335
368
|
if (!ext || !IMAGE_EXTENSIONS.has(ext)) {
|
|
336
369
|
out += full;
|
|
337
370
|
continue;
|
|
@@ -2255,6 +2288,48 @@ function handleChatAbort(request, log) {
|
|
|
2255
2288
|
} else log.warn("chat.abort: OpenClaw abort primitives unavailable — cannot hard-abort the running turn");
|
|
2256
2289
|
chatClient?.sendResponse(request.id, true, { aborted });
|
|
2257
2290
|
}
|
|
2291
|
+
/**
|
|
2292
|
+
* Speech-style reply guidance injected into the agent's SYSTEM PROMPT for a
|
|
2293
|
+
* live voice-call turn (`origin:'voice'`). The user is speaking on a call and
|
|
2294
|
+
* the reply is read aloud by TTS, so a huge markdown paragraph reads terribly.
|
|
2295
|
+
* This biases STYLE only — explicitly NOT a hard cap: it must not suppress tool
|
|
2296
|
+
* use or make the agent omit information the user actually needs.
|
|
2297
|
+
*/
|
|
2298
|
+
const VOICE_REPLY_SYSTEM_PROMPT = [
|
|
2299
|
+
"This message arrived over a LIVE VOICE CALL — your reply is read aloud by text-to-speech.",
|
|
2300
|
+
"Speak the way a helpful person would on a phone call:",
|
|
2301
|
+
"- Keep it brief and conversational — usually 1-3 short sentences.",
|
|
2302
|
+
"- Plain spoken language only: no markdown, no bullet or numbered lists, no headings, no code blocks, no URLs or link syntax.",
|
|
2303
|
+
"- If the full answer is long or detailed, give the short spoken version and offer to send the details to the chat.",
|
|
2304
|
+
"This is guidance on speaking STYLE only. Do NOT skip tool calls you would otherwise make, and never omit information the user actually needs — just deliver it the way you would say it out loud."
|
|
2305
|
+
].join("\n");
|
|
2306
|
+
/**
|
|
2307
|
+
* Per-turn envelope context derived from the RPC `origin` flag. For a live
|
|
2308
|
+
* voice call it returns a `GroupSystemPrompt` block, which is the PROVEN
|
|
2309
|
+
* runtime-rendered seam for per-turn system-prompt guidance: dispatchInbound
|
|
2310
|
+
* spreads `extraContext` into `finalizeInboundContext` → `sessionCtx.GroupSystemPrompt`
|
|
2311
|
+
* → `extraSystemPrompt` → the model's system prompt (rendered under a
|
|
2312
|
+
* "## Group Chat Context" heading; read unconditionally, NOT gated on group
|
|
2313
|
+
* chats). Returns `{}` for every other origin, so text/A2A turns are unchanged.
|
|
2314
|
+
*
|
|
2315
|
+
* Why `GroupSystemPrompt` and not a bespoke key like `A2ASystemPrompt`: the
|
|
2316
|
+
* OpenClaw runtime does NOT read arbitrary `extraContext` keys — a custom key
|
|
2317
|
+
* only reaches the model if a persona/AGENTS.md template interpolates it, so it
|
|
2318
|
+
* silently rots into a dead wire otherwise (the `thinking` param is the
|
|
2319
|
+
* cautionary precedent). `GroupSystemPrompt` is verified to reach the system
|
|
2320
|
+
* prompt via the runtime itself.
|
|
2321
|
+
*
|
|
2322
|
+
* Transcript stays clean: this only feeds the model's system prompt for THIS
|
|
2323
|
+
* turn. The persisted user message (`addMessage(sessionId, 'user', message)`)
|
|
2324
|
+
* and everything `sessions.get` returns are the raw user text — the guidance is
|
|
2325
|
+
* never stored or broadcast.
|
|
2326
|
+
*
|
|
2327
|
+
* Exported pure for unit testing (mirrors the codebase's pure-helper pattern).
|
|
2328
|
+
*/
|
|
2329
|
+
function buildOriginEnvelopeContext(origin) {
|
|
2330
|
+
if (origin === "voice") return { GroupSystemPrompt: VOICE_REPLY_SYSTEM_PROMPT };
|
|
2331
|
+
return {};
|
|
2332
|
+
}
|
|
2258
2333
|
async function handleAgentRequest(request, log) {
|
|
2259
2334
|
const runtime = pluginRuntime;
|
|
2260
2335
|
if (!runtime) {
|
|
@@ -2265,7 +2340,7 @@ async function handleAgentRequest(request, log) {
|
|
|
2265
2340
|
chatClient?.sendResponse(request.id, false, { message: "OpenClaw SDK not available — cannot dispatch" });
|
|
2266
2341
|
return;
|
|
2267
2342
|
}
|
|
2268
|
-
const { message, sessionKey: legacySessionKey, userId, conversationId, conversationType, tenantId, clientType, displayName, identityId, senderPermissions, attachments: rawAttachments, a2a, chatMessageId } = request.params;
|
|
2343
|
+
const { message, sessionKey: legacySessionKey, userId, conversationId, conversationType, tenantId, clientType, origin, displayName, identityId, senderPermissions, attachments: rawAttachments, a2a, chatMessageId } = request.params;
|
|
2269
2344
|
const isA2A = !!a2a;
|
|
2270
2345
|
if (!message && !rawAttachments?.length) {
|
|
2271
2346
|
chatClient?.sendResponse(request.id, false, { message: "Missing message" });
|
|
@@ -2487,6 +2562,7 @@ async function handleAgentRequest(request, log) {
|
|
|
2487
2562
|
...userId ? { UserId: userId } : {},
|
|
2488
2563
|
...senderPermissions?.length ? { SenderPermissions: senderPermissions } : {},
|
|
2489
2564
|
ChannelMode: channelMode,
|
|
2565
|
+
...buildOriginEnvelopeContext(origin),
|
|
2490
2566
|
...isA2A ? {
|
|
2491
2567
|
CallerType: "agent",
|
|
2492
2568
|
CallerAgentId: a2a.sourceAgentId,
|
|
@@ -2836,4 +2912,4 @@ const plugin = {
|
|
|
2836
2912
|
}
|
|
2837
2913
|
};
|
|
2838
2914
|
//#endregion
|
|
2839
|
-
export {
|
|
2915
|
+
export { admitAgentEvent as a, buildToolActivity as c, resolveAbortTargetKeys as d, rewriteAssistantText as f, __setActiveRunForTest as i, joinAssistantText as l, createAlfeChannelPlugin as m, __agentTurnQueueForTest as n, buildA2ACompletePayload as o, stripLeakedToolSummary as p, __resetAgentTurnQueueForTest as r, buildOriginEnvelopeContext as s, VOICE_REPLY_SYSTEM_PROMPT as t, plugin as u };
|