@stagewhisper/stagewhisper 0.61.0 → 0.64.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/LICENSE +21 -0
- package/README.md +7 -0
- package/dist/index.js +225 -96
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 StageWhisper
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -68,6 +68,13 @@ The plugin reads from `channels.stagewhisper` or `plugins.entries.stagewhisper.c
|
|
|
68
68
|
| `relayToken` | Yes | Relay token from pairing |
|
|
69
69
|
| `label` | No | Display label (default: "StageWhisper") |
|
|
70
70
|
|
|
71
|
+
When the plugin accepts tasks over HTTP (loopback transport), two opt-in environment variables widen the default localhost-only posture for remote setups such as `tailscale serve`:
|
|
72
|
+
|
|
73
|
+
| Variable | Default | Purpose |
|
|
74
|
+
|----------|---------|---------|
|
|
75
|
+
| `STAGEWHISPER_ALLOW_INGRESS_HOSTS` | unset | Comma-separated `Host` names accepted in addition to localhost. Set to your tailnet name when reaching the plugin through `tailscale serve`. |
|
|
76
|
+
| `STAGEWHISPER_ALLOW_CALLBACK_URLS` | unset | Comma-separated exact origins (scheme + host + port) the plugin may POST replies to. Loopback is trusted implicitly only while `STAGEWHISPER_ALLOW_INGRESS_HOSTS` is unset; once remote ingress is enabled, every callback origin (loopback included) must be listed here. |
|
|
77
|
+
|
|
71
78
|
## Architecture
|
|
72
79
|
|
|
73
80
|
```
|
package/dist/index.js
CHANGED
|
@@ -3990,7 +3990,7 @@ var stagewhisperPlugin = {
|
|
|
3990
3990
|
// src/http-transport.ts
|
|
3991
3991
|
import http from "node:http";
|
|
3992
3992
|
import { Buffer as Buffer2 } from "node:buffer";
|
|
3993
|
-
import { timingSafeEqual } from "node:crypto";
|
|
3993
|
+
import { randomUUID, timingSafeEqual } from "node:crypto";
|
|
3994
3994
|
|
|
3995
3995
|
// src/core.ts
|
|
3996
3996
|
var TASK_ID_REGEX = /^[0-9a-f-]{36}$/;
|
|
@@ -4001,7 +4001,7 @@ var ALLOWED_REASONS = /* @__PURE__ */ new Set([
|
|
|
4001
4001
|
"system_prelude"
|
|
4002
4002
|
]);
|
|
4003
4003
|
var MAX_BODY_BYTES = 262144;
|
|
4004
|
-
var MAX_PAYLOAD_TEXT_CHARS =
|
|
4004
|
+
var MAX_PAYLOAD_TEXT_CHARS = 16e3;
|
|
4005
4005
|
var MAX_SESSION_ID_CHARS = 128;
|
|
4006
4006
|
var ALLOWED_HOSTNAMES = /* @__PURE__ */ new Set(["127.0.0.1", "localhost"]);
|
|
4007
4007
|
function isTestTask(task) {
|
|
@@ -4055,6 +4055,42 @@ async function dispatchTaskToAgent(options) {
|
|
|
4055
4055
|
function isLoopbackCallbackUrl(url) {
|
|
4056
4056
|
return LOOPBACK_CALLBACK_URL_REGEX.test(url);
|
|
4057
4057
|
}
|
|
4058
|
+
function normalizeOrigin(url) {
|
|
4059
|
+
let parsed;
|
|
4060
|
+
try {
|
|
4061
|
+
parsed = new URL(url);
|
|
4062
|
+
} catch {
|
|
4063
|
+
return null;
|
|
4064
|
+
}
|
|
4065
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
|
|
4066
|
+
return parsed.origin.toLowerCase();
|
|
4067
|
+
}
|
|
4068
|
+
function allowedCallbackOrigins() {
|
|
4069
|
+
const raw = process.env["STAGEWHISPER_ALLOW_CALLBACK_URLS"];
|
|
4070
|
+
if (!raw) return /* @__PURE__ */ new Set();
|
|
4071
|
+
const origins = raw.split(",").map((entry) => normalizeOrigin(entry.trim())).filter((origin) => origin !== null);
|
|
4072
|
+
return new Set(origins);
|
|
4073
|
+
}
|
|
4074
|
+
function isAllowedCallbackUrl(url) {
|
|
4075
|
+
let parsed;
|
|
4076
|
+
try {
|
|
4077
|
+
parsed = new URL(url);
|
|
4078
|
+
} catch {
|
|
4079
|
+
return false;
|
|
4080
|
+
}
|
|
4081
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return false;
|
|
4082
|
+
if (parsed.pathname !== "/" && parsed.pathname !== "") return false;
|
|
4083
|
+
if (parsed.search || parsed.hash) return false;
|
|
4084
|
+
if (allowedCallbackOrigins().has(parsed.origin.toLowerCase())) return true;
|
|
4085
|
+
if (allowedIngressHosts().size === 0 && isLoopbackCallbackUrl(url)) return true;
|
|
4086
|
+
return false;
|
|
4087
|
+
}
|
|
4088
|
+
function allowedIngressHosts() {
|
|
4089
|
+
const raw = process.env["STAGEWHISPER_ALLOW_INGRESS_HOSTS"];
|
|
4090
|
+
if (!raw) return /* @__PURE__ */ new Set();
|
|
4091
|
+
const hosts = raw.split(",").map((entry) => entry.trim().toLowerCase()).filter((entry) => entry.length > 0);
|
|
4092
|
+
return new Set(hosts);
|
|
4093
|
+
}
|
|
4058
4094
|
function isAllowedHostHeader(host) {
|
|
4059
4095
|
if (!host) return false;
|
|
4060
4096
|
const trimmed = host.trim().toLowerCase();
|
|
@@ -4062,9 +4098,9 @@ function isAllowedHostHeader(host) {
|
|
|
4062
4098
|
const colonIdx = trimmed.lastIndexOf(":");
|
|
4063
4099
|
const hostname = colonIdx === -1 ? trimmed : trimmed.slice(0, colonIdx);
|
|
4064
4100
|
const port = colonIdx === -1 ? "" : trimmed.slice(colonIdx + 1);
|
|
4065
|
-
if (!ALLOWED_HOSTNAMES.has(hostname)) return false;
|
|
4066
4101
|
if (port && !/^\d+$/.test(port)) return false;
|
|
4067
|
-
return true;
|
|
4102
|
+
if (ALLOWED_HOSTNAMES.has(hostname)) return true;
|
|
4103
|
+
return allowedIngressHosts().has(hostname);
|
|
4068
4104
|
}
|
|
4069
4105
|
function buildChatId(sessionId, reason) {
|
|
4070
4106
|
if (reason === "transcript_chunk") return `sw:${sessionId}:reasoning`;
|
|
@@ -4139,10 +4175,10 @@ function validateHttpTaskRequest(body) {
|
|
|
4139
4175
|
if (typeof cbUrl !== "string" || cbUrl.length === 0) {
|
|
4140
4176
|
return { ok: false, error: "callback.url must be a non-empty string" };
|
|
4141
4177
|
}
|
|
4142
|
-
if (!
|
|
4178
|
+
if (!isAllowedCallbackUrl(cbUrl)) {
|
|
4143
4179
|
return {
|
|
4144
4180
|
ok: false,
|
|
4145
|
-
error: "callback.url must be a loopback base URL (http://127.0.0.1:PORT or
|
|
4181
|
+
error: "callback.url must be a loopback base URL (http://127.0.0.1:PORT) or an origin listed in STAGEWHISPER_ALLOW_CALLBACK_URLS, with no path"
|
|
4146
4182
|
};
|
|
4147
4183
|
}
|
|
4148
4184
|
if (typeof cbToken !== "string" || cbToken.length < 16) {
|
|
@@ -4212,7 +4248,14 @@ var IDEMPOTENCY_TTL_MS = 5 * 60 * 1e3;
|
|
|
4212
4248
|
var CALLBACK_TIMEOUT_MS = 5e3;
|
|
4213
4249
|
var CALLBACK_MAX_ATTEMPTS = 4;
|
|
4214
4250
|
var CALLBACK_RETRY_BASE_MS = 250;
|
|
4215
|
-
var
|
|
4251
|
+
var POLL_INTERVAL_MS = 500;
|
|
4252
|
+
function resolveRuntimeEvents(api) {
|
|
4253
|
+
const runtime = api.runtime;
|
|
4254
|
+
const events = runtime?.events;
|
|
4255
|
+
if (!events) return null;
|
|
4256
|
+
if (typeof events.onSessionTranscriptUpdate !== "function") return null;
|
|
4257
|
+
return events;
|
|
4258
|
+
}
|
|
4216
4259
|
var INCOMING_PATH = "/v1/incoming";
|
|
4217
4260
|
var PING_PATH = "/v1/ping";
|
|
4218
4261
|
function constantTimeTokenEqual(provided, expected) {
|
|
@@ -4227,7 +4270,7 @@ function constantTimeTokenEqual(provided, expected) {
|
|
|
4227
4270
|
}
|
|
4228
4271
|
function extractBearerToken(header) {
|
|
4229
4272
|
if (!header) return null;
|
|
4230
|
-
const match = /^Bearer\s+(
|
|
4273
|
+
const match = /^Bearer\s+(\S.*)$/i.exec(header.trim());
|
|
4231
4274
|
if (!match) return null;
|
|
4232
4275
|
return match[1]?.trim() ?? null;
|
|
4233
4276
|
}
|
|
@@ -4320,34 +4363,34 @@ function createHttpTransport(options) {
|
|
|
4320
4363
|
if (prelude !== void 0) pendingPreludes.delete(sessionId);
|
|
4321
4364
|
return prelude;
|
|
4322
4365
|
}
|
|
4323
|
-
async function
|
|
4324
|
-
const
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4366
|
+
async function collectAssistantRepliesAfterMarker(sessionKey, markers) {
|
|
4367
|
+
const session = await api.runtime.subagent.getSessionMessages({
|
|
4368
|
+
sessionKey,
|
|
4369
|
+
limit: 100
|
|
4370
|
+
});
|
|
4371
|
+
const messages = session.messages;
|
|
4372
|
+
let markerIndex = -1;
|
|
4373
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
4374
|
+
const msg = messages[i];
|
|
4375
|
+
if (msg["role"] !== "user") continue;
|
|
4376
|
+
const text = extractContentFromMessage(msg) ?? "";
|
|
4377
|
+
if (markers.some((m) => text.includes(m))) {
|
|
4378
|
+
markerIndex = i;
|
|
4379
|
+
break;
|
|
4329
4380
|
}
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
const
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
const text = extractContentFromMessage(msg)
|
|
4339
|
-
if (
|
|
4340
|
-
for (let j = i + 1; j < messages.length; j++) {
|
|
4341
|
-
const reply = messages[j];
|
|
4342
|
-
const role = reply["role"];
|
|
4343
|
-
if (role === "assistant" || role === "model") {
|
|
4344
|
-
return extractContentFromMessage(reply);
|
|
4345
|
-
}
|
|
4346
|
-
if (role === "user") break;
|
|
4347
|
-
}
|
|
4381
|
+
}
|
|
4382
|
+
if (markerIndex === -1) return [];
|
|
4383
|
+
const replies = [];
|
|
4384
|
+
for (let j = markerIndex + 1; j < messages.length; j++) {
|
|
4385
|
+
const msg = messages[j];
|
|
4386
|
+
const role = msg["role"];
|
|
4387
|
+
if (role === "user") break;
|
|
4388
|
+
if (role === "assistant" || role === "model") {
|
|
4389
|
+
const text = extractContentFromMessage(msg);
|
|
4390
|
+
if (text) replies.push(text);
|
|
4348
4391
|
}
|
|
4349
4392
|
}
|
|
4350
|
-
return
|
|
4393
|
+
return replies;
|
|
4351
4394
|
}
|
|
4352
4395
|
async function postCallback(callback, taskId, body) {
|
|
4353
4396
|
const url = buildCallbackUrl(callback.url, taskId);
|
|
@@ -4428,11 +4471,27 @@ function createHttpTransport(options) {
|
|
|
4428
4471
|
});
|
|
4429
4472
|
const chatIdReason = kind === "chat" ? "chat_message" : "transcript_chunk";
|
|
4430
4473
|
const chatId = taskRequest.chat_id ?? buildChatId(taskRequest.session_id, chatIdReason);
|
|
4431
|
-
const
|
|
4432
|
-
|
|
4474
|
+
const markers = [`StageWhisper task: ${taskRequest.task_id}`];
|
|
4475
|
+
if (userMessageId) markers.push(`[StageWhisper chat: ${userMessageId}]`);
|
|
4476
|
+
let forwardedCount = 0;
|
|
4477
|
+
const postMessage = async (replyText) => {
|
|
4478
|
+
const messageId = randomUUID();
|
|
4479
|
+
const body = {
|
|
4480
|
+
task_id: taskRequest.task_id,
|
|
4481
|
+
session_id: taskRequest.session_id,
|
|
4482
|
+
chat_id: chatId,
|
|
4483
|
+
user_message_id: userMessageId ?? null,
|
|
4484
|
+
message_id: messageId,
|
|
4485
|
+
status: "message",
|
|
4486
|
+
reply_text: replyText,
|
|
4487
|
+
occurred_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
4488
|
+
};
|
|
4433
4489
|
recordTerminalCallback(taskRequest.task_id, callback, body);
|
|
4434
4490
|
try {
|
|
4435
4491
|
await postCallback(callback, taskRequest.task_id, body);
|
|
4492
|
+
api.logger.info(
|
|
4493
|
+
`[stagewhisper-http] ${kind} task ${taskRequest.task_id} forwarded message ${messageId}`
|
|
4494
|
+
);
|
|
4436
4495
|
} catch (err) {
|
|
4437
4496
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
4438
4497
|
api.logger.error(
|
|
@@ -4440,6 +4499,79 @@ function createHttpTransport(options) {
|
|
|
4440
4499
|
);
|
|
4441
4500
|
}
|
|
4442
4501
|
};
|
|
4502
|
+
const postTyping = async () => {
|
|
4503
|
+
try {
|
|
4504
|
+
await postCallback(callback, taskRequest.task_id, {
|
|
4505
|
+
task_id: taskRequest.task_id,
|
|
4506
|
+
session_id: taskRequest.session_id,
|
|
4507
|
+
chat_id: chatId,
|
|
4508
|
+
user_message_id: userMessageId ?? null,
|
|
4509
|
+
status: "typing",
|
|
4510
|
+
label: "thinking"
|
|
4511
|
+
});
|
|
4512
|
+
} catch (err) {
|
|
4513
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
4514
|
+
api.logger.warn(
|
|
4515
|
+
`[stagewhisper-http] typing callback failed for ${taskRequest.task_id}: ${errMsg}`
|
|
4516
|
+
);
|
|
4517
|
+
}
|
|
4518
|
+
};
|
|
4519
|
+
const events = resolveRuntimeEvents(api);
|
|
4520
|
+
let unsubscribeTranscript = null;
|
|
4521
|
+
let pollDone = false;
|
|
4522
|
+
let flushing = null;
|
|
4523
|
+
let flushQueued = false;
|
|
4524
|
+
let wakePoll = () => {
|
|
4525
|
+
};
|
|
4526
|
+
const sleepUntilPoll = () => new Promise((resolve) => {
|
|
4527
|
+
const timer = setTimeout(() => {
|
|
4528
|
+
wakePoll = () => {
|
|
4529
|
+
};
|
|
4530
|
+
resolve();
|
|
4531
|
+
}, POLL_INTERVAL_MS);
|
|
4532
|
+
wakePoll = () => {
|
|
4533
|
+
clearTimeout(timer);
|
|
4534
|
+
wakePoll = () => {
|
|
4535
|
+
};
|
|
4536
|
+
resolve();
|
|
4537
|
+
};
|
|
4538
|
+
});
|
|
4539
|
+
const runFlushOnce = async () => {
|
|
4540
|
+
try {
|
|
4541
|
+
const replies = await collectAssistantRepliesAfterMarker(
|
|
4542
|
+
sessionKey,
|
|
4543
|
+
markers
|
|
4544
|
+
);
|
|
4545
|
+
while (forwardedCount < replies.length) {
|
|
4546
|
+
const reply = replies[forwardedCount];
|
|
4547
|
+
forwardedCount += 1;
|
|
4548
|
+
if (reply.trim()) await postMessage(reply);
|
|
4549
|
+
}
|
|
4550
|
+
} catch (err) {
|
|
4551
|
+
api.logger.warn(
|
|
4552
|
+
`[stagewhisper-http] session flush failed for ${taskRequest.task_id}: ${err}`
|
|
4553
|
+
);
|
|
4554
|
+
}
|
|
4555
|
+
};
|
|
4556
|
+
const flushFromSession = async () => {
|
|
4557
|
+
if (flushing) {
|
|
4558
|
+
flushQueued = true;
|
|
4559
|
+
await flushing;
|
|
4560
|
+
return;
|
|
4561
|
+
}
|
|
4562
|
+
flushing = (async () => {
|
|
4563
|
+
await runFlushOnce();
|
|
4564
|
+
while (flushQueued) {
|
|
4565
|
+
flushQueued = false;
|
|
4566
|
+
await runFlushOnce();
|
|
4567
|
+
}
|
|
4568
|
+
})();
|
|
4569
|
+
try {
|
|
4570
|
+
await flushing;
|
|
4571
|
+
} finally {
|
|
4572
|
+
flushing = null;
|
|
4573
|
+
}
|
|
4574
|
+
};
|
|
4443
4575
|
try {
|
|
4444
4576
|
const dispatch = await dispatchTaskToAgent({
|
|
4445
4577
|
api,
|
|
@@ -4451,87 +4583,84 @@ function createHttpTransport(options) {
|
|
|
4451
4583
|
api.logger.info(
|
|
4452
4584
|
`[stagewhisper-http] ${kind} task ${taskRequest.task_id} dispatched (runId: ${dispatch.runId})`
|
|
4453
4585
|
);
|
|
4586
|
+
const runId = dispatch.runId;
|
|
4587
|
+
await postTyping();
|
|
4588
|
+
if (events?.onSessionTranscriptUpdate) {
|
|
4589
|
+
unsubscribeTranscript = events.onSessionTranscriptUpdate((update) => {
|
|
4590
|
+
if (update.sessionKey && update.sessionKey !== sessionKey) return;
|
|
4591
|
+
const message = update.message;
|
|
4592
|
+
const role = message?.["role"];
|
|
4593
|
+
if (role !== void 0 && role !== "assistant" && role !== "model") {
|
|
4594
|
+
return;
|
|
4595
|
+
}
|
|
4596
|
+
void flushFromSession();
|
|
4597
|
+
});
|
|
4598
|
+
}
|
|
4599
|
+
let pollLoop = Promise.resolve();
|
|
4600
|
+
if (!events?.onSessionTranscriptUpdate) {
|
|
4601
|
+
pollLoop = (async () => {
|
|
4602
|
+
while (!pollDone) {
|
|
4603
|
+
await sleepUntilPoll();
|
|
4604
|
+
if (pollDone) break;
|
|
4605
|
+
await flushFromSession();
|
|
4606
|
+
}
|
|
4607
|
+
})();
|
|
4608
|
+
}
|
|
4454
4609
|
const waitResult = await api.runtime.subagent.waitForRun({
|
|
4455
|
-
runId
|
|
4456
|
-
timeoutMs: SUBAGENT_WAIT_TIMEOUT_MS
|
|
4610
|
+
runId
|
|
4457
4611
|
});
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4612
|
+
pollDone = true;
|
|
4613
|
+
wakePoll();
|
|
4614
|
+
await pollLoop;
|
|
4615
|
+
await flushFromSession();
|
|
4616
|
+
if (waitResult.status === "error") {
|
|
4617
|
+
api.logger.error(
|
|
4618
|
+
`[stagewhisper-http] ${kind} task ${taskRequest.task_id} agent error: ${waitResult.error}`
|
|
4463
4619
|
);
|
|
4464
|
-
|
|
4465
|
-
await
|
|
4466
|
-
task_id: taskRequest.task_id,
|
|
4467
|
-
session_id: taskRequest.session_id,
|
|
4468
|
-
chat_id: chatId,
|
|
4469
|
-
user_message_id: userMessageId ?? null,
|
|
4470
|
-
status: "completed",
|
|
4471
|
-
reply_text: reply,
|
|
4472
|
-
occurred_at: occurredAt
|
|
4473
|
-
});
|
|
4474
|
-
api.logger.info(
|
|
4475
|
-
`[stagewhisper-http] ${kind} task ${taskRequest.task_id} callback delivered`
|
|
4476
|
-
);
|
|
4477
|
-
} else if (kind === "reasoning") {
|
|
4478
|
-
await sendCallback({
|
|
4479
|
-
task_id: taskRequest.task_id,
|
|
4480
|
-
session_id: taskRequest.session_id,
|
|
4481
|
-
chat_id: chatId,
|
|
4482
|
-
user_message_id: userMessageId ?? null,
|
|
4483
|
-
status: "silent",
|
|
4484
|
-
occurred_at: occurredAt
|
|
4485
|
-
});
|
|
4486
|
-
api.logger.info(
|
|
4487
|
-
`[stagewhisper-http] reasoning task ${taskRequest.task_id} produced no signal (silent)`
|
|
4488
|
-
);
|
|
4489
|
-
} else {
|
|
4490
|
-
await sendCallback({
|
|
4620
|
+
try {
|
|
4621
|
+
await postCallback(callback, taskRequest.task_id, {
|
|
4491
4622
|
task_id: taskRequest.task_id,
|
|
4492
4623
|
session_id: taskRequest.session_id,
|
|
4493
4624
|
chat_id: chatId,
|
|
4494
4625
|
user_message_id: userMessageId ?? null,
|
|
4495
4626
|
status: "errored",
|
|
4496
|
-
error_code: "
|
|
4497
|
-
error_message: "
|
|
4498
|
-
occurred_at: occurredAt
|
|
4627
|
+
error_code: "agent_error",
|
|
4628
|
+
error_message: waitResult.error ?? "agent run error"
|
|
4499
4629
|
});
|
|
4500
|
-
|
|
4501
|
-
|
|
4630
|
+
} catch (postErr) {
|
|
4631
|
+
api.logger.error(
|
|
4632
|
+
`[stagewhisper-http] errored callback failed for ${taskRequest.task_id}: ${postErr}`
|
|
4502
4633
|
);
|
|
4503
4634
|
}
|
|
4504
4635
|
} else {
|
|
4505
|
-
|
|
4636
|
+
api.logger.info(
|
|
4637
|
+
`[stagewhisper-http] ${kind} task ${taskRequest.task_id} turn settled (${waitResult.status})`
|
|
4638
|
+
);
|
|
4639
|
+
}
|
|
4640
|
+
} catch (err) {
|
|
4641
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
4642
|
+
api.logger.error(
|
|
4643
|
+
`[stagewhisper-http] ${kind} task ${taskRequest.task_id} threw: ${errMsg}`
|
|
4644
|
+
);
|
|
4645
|
+
try {
|
|
4646
|
+
await postCallback(callback, taskRequest.task_id, {
|
|
4506
4647
|
task_id: taskRequest.task_id,
|
|
4507
4648
|
session_id: taskRequest.session_id,
|
|
4508
4649
|
chat_id: chatId,
|
|
4509
4650
|
user_message_id: userMessageId ?? null,
|
|
4510
4651
|
status: "errored",
|
|
4511
|
-
error_code: "
|
|
4512
|
-
error_message:
|
|
4513
|
-
occurred_at: occurredAt
|
|
4652
|
+
error_code: "execution_error",
|
|
4653
|
+
error_message: errMsg
|
|
4514
4654
|
});
|
|
4655
|
+
} catch (postErr) {
|
|
4515
4656
|
api.logger.error(
|
|
4516
|
-
`[stagewhisper-http]
|
|
4657
|
+
`[stagewhisper-http] errored callback failed for ${taskRequest.task_id}: ${postErr}`
|
|
4517
4658
|
);
|
|
4518
4659
|
}
|
|
4519
|
-
} catch (err) {
|
|
4520
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
4521
|
-
api.logger.error(
|
|
4522
|
-
`[stagewhisper-http] ${kind} task ${taskRequest.task_id} threw: ${errMsg}`
|
|
4523
|
-
);
|
|
4524
|
-
await sendCallback({
|
|
4525
|
-
task_id: taskRequest.task_id,
|
|
4526
|
-
session_id: taskRequest.session_id,
|
|
4527
|
-
chat_id: chatId,
|
|
4528
|
-
user_message_id: userMessageId ?? null,
|
|
4529
|
-
status: "errored",
|
|
4530
|
-
error_code: "execution_error",
|
|
4531
|
-
error_message: errMsg,
|
|
4532
|
-
occurred_at: occurredAt
|
|
4533
|
-
});
|
|
4534
4660
|
} finally {
|
|
4661
|
+
pollDone = true;
|
|
4662
|
+
wakePoll();
|
|
4663
|
+
unsubscribeTranscript?.();
|
|
4535
4664
|
inflight.delete(taskRequest.task_id);
|
|
4536
4665
|
}
|
|
4537
4666
|
}
|
|
@@ -4848,7 +4977,7 @@ var getRuntime = runtimeStore.getRuntime;
|
|
|
4848
4977
|
|
|
4849
4978
|
// src/service.ts
|
|
4850
4979
|
init_client();
|
|
4851
|
-
import { randomUUID } from "node:crypto";
|
|
4980
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
4852
4981
|
|
|
4853
4982
|
// src/health.ts
|
|
4854
4983
|
var DEGRADED_THRESHOLD = 3;
|
|
@@ -5382,7 +5511,7 @@ function createRelayService(api) {
|
|
|
5382
5511
|
sessionKey,
|
|
5383
5512
|
message: messageContent,
|
|
5384
5513
|
deliver: false,
|
|
5385
|
-
idempotencyKey:
|
|
5514
|
+
idempotencyKey: randomUUID2()
|
|
5386
5515
|
});
|
|
5387
5516
|
api.logger.info(`Test task ${task.id} dispatched (runId: ${result.runId})`);
|
|
5388
5517
|
const waitResult = await api.runtime.subagent.waitForRun({
|
package/openclaw.plugin.json
CHANGED