@khorsheed/dsh-room 0.2.4 → 0.2.5
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/CHANGELOG.md +13 -0
- package/lib/client.js +2256 -1074
- package/lib/index.js +548 -36
- package/lib/typert.host.js +249 -65
- package/lib/typert.remote-client.d.ts +4 -1
- package/lib/typert.remote-client.js +208 -53
- package/lib/types/client/InviteDialog.js +11 -4
- package/lib/types/client/RoomAttachments.d.ts +20 -0
- package/lib/types/client/RoomAttachments.js +36 -0
- package/lib/types/client/RoomComposer.d.ts +6 -12
- package/lib/types/client/RoomComposer.js +116 -12
- package/lib/types/client/RoomQuestionView.d.ts +10 -2
- package/lib/types/client/RoomQuestionView.js +18 -5
- package/lib/types/client/RoomRunView.d.ts +1 -1
- package/lib/types/client/RoomRunView.js +15 -5
- package/lib/types/client/RoomSpeechView.d.ts +24 -1
- package/lib/types/client/RoomSpeechView.js +39 -3
- package/lib/types/client/RoomStatsLine.d.ts +3 -1
- package/lib/types/client/RoomStatsLine.js +3 -1
- package/lib/types/client/RoomTaskCapsule.d.ts +6 -1
- package/lib/types/client/RoomTaskCapsule.js +2 -2
- package/lib/types/client/TasksView.js +1 -1
- package/lib/types/client/TeamView.js +1 -1
- package/lib/types/client/icons.d.ts +2 -1
- package/lib/types/client/icons.js +1 -0
- package/lib/types/client/index.js +94 -15
- package/lib/types/client/locales.d.ts +44 -0
- package/lib/types/client/locales.js +44 -0
- package/lib/types/client/nodes.d.ts +22 -3
- package/lib/types/client/nodes.js +28 -5
- package/lib/types/client/path-mentions.d.ts +34 -0
- package/lib/types/client/path-mentions.js +39 -0
- package/lib/types/client/slots.d.ts +51 -1
- package/lib/types/dispatch.d.ts +50 -0
- package/lib/types/dispatch.js +115 -1
- package/lib/types/index.d.ts +69 -1
- package/lib/types/index.js +362 -20
- package/lib/types/jobs.js +7 -3
- package/lib/types/journal.d.ts +1 -1
- package/lib/types/journal.js +18 -2
- package/lib/types/types.d.ts +77 -0
- package/package.json +10 -6
- package/lib/journal-B4FOGO6S.js +0 -482
- package/lib/journal-BWL3-i0s.js +0 -462
- package/lib/journal-Rid9AAnD.js +0 -485
package/lib/index.js
CHANGED
|
@@ -700,10 +700,14 @@ function replayJobs(events, deliveries) {
|
|
|
700
700
|
...event.data.probability === void 0 ? {} : { probability: event.data.probability }
|
|
701
701
|
});
|
|
702
702
|
else if (event.type === "room/job-stopped") stopped.add(event.data.task);
|
|
703
|
-
else if (event.type === "room/dispatch" && event.data.id !== void 0)
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
703
|
+
else if (event.type === "room/dispatch" && event.data.id !== void 0) {
|
|
704
|
+
const from = event.data.origin !== "human" && event.data.from !== void 0 ? event.data.from : void 0;
|
|
705
|
+
dispatchById.set(event.data.id, {
|
|
706
|
+
time: event.time,
|
|
707
|
+
text: event.data.text,
|
|
708
|
+
...from === void 0 ? {} : { from }
|
|
709
|
+
});
|
|
710
|
+
}
|
|
707
711
|
const byTask = /* @__PURE__ */ new Map();
|
|
708
712
|
for (const row of deliveries) {
|
|
709
713
|
if (row.task === void 0) continue;
|
|
@@ -735,7 +739,8 @@ function replayJobs(events, deliveries) {
|
|
|
735
739
|
messages: [{
|
|
736
740
|
id,
|
|
737
741
|
time: root.time,
|
|
738
|
-
text: root.text
|
|
742
|
+
text: root.text,
|
|
743
|
+
...root.from === void 0 ? {} : { from: root.from }
|
|
739
744
|
}, ...follow].sort((a, b) => a.time - b.time),
|
|
740
745
|
status,
|
|
741
746
|
startedAt: root.time,
|
|
@@ -856,6 +861,7 @@ const ROOM_EVENT_TYPES = [
|
|
|
856
861
|
"room/dispatch",
|
|
857
862
|
"room/speech",
|
|
858
863
|
"room/run-state",
|
|
864
|
+
"room/run-moved",
|
|
859
865
|
"room/execution-metadata",
|
|
860
866
|
"room/relay",
|
|
861
867
|
"room/relay-resolved",
|
|
@@ -937,6 +943,7 @@ function replay(events) {
|
|
|
937
943
|
const relays = [];
|
|
938
944
|
const approvals = /* @__PURE__ */ new Map();
|
|
939
945
|
const questions = /* @__PURE__ */ new Map();
|
|
946
|
+
const asks = /* @__PURE__ */ new Map();
|
|
940
947
|
const relayById = /* @__PURE__ */ new Map();
|
|
941
948
|
const tasks = [];
|
|
942
949
|
const taskById = /* @__PURE__ */ new Map();
|
|
@@ -1090,10 +1097,27 @@ function replay(events) {
|
|
|
1090
1097
|
questions: event.data.questions,
|
|
1091
1098
|
time: event.time
|
|
1092
1099
|
});
|
|
1100
|
+
asks.set(event.data.id, {
|
|
1101
|
+
id: event.data.id,
|
|
1102
|
+
member: event.data.member,
|
|
1103
|
+
questions: event.data.questions,
|
|
1104
|
+
state: "pending",
|
|
1105
|
+
time: event.time,
|
|
1106
|
+
...event.data.runId === void 0 ? {} : { runId: event.data.runId },
|
|
1107
|
+
...event.data.consent === true ? { consent: true } : {}
|
|
1108
|
+
});
|
|
1093
1109
|
break;
|
|
1094
|
-
case "room/question-resolved":
|
|
1110
|
+
case "room/question-resolved": {
|
|
1095
1111
|
questions.delete(event.data.id);
|
|
1112
|
+
const ask = asks.get(event.data.id);
|
|
1113
|
+
if (ask !== void 0) asks.set(ask.id, {
|
|
1114
|
+
...ask,
|
|
1115
|
+
state: event.data.outcome,
|
|
1116
|
+
...event.data.answers === void 0 ? {} : { answers: event.data.answers },
|
|
1117
|
+
...event.data.late === true ? { late: true } : {}
|
|
1118
|
+
});
|
|
1096
1119
|
break;
|
|
1120
|
+
}
|
|
1097
1121
|
case "room/approval-resolved":
|
|
1098
1122
|
approvals.delete(event.data.id);
|
|
1099
1123
|
break;
|
|
@@ -1216,6 +1240,7 @@ function replay(events) {
|
|
|
1216
1240
|
relays,
|
|
1217
1241
|
...approvals.size === 0 ? {} : { approvals: [...approvals.values()] },
|
|
1218
1242
|
...questions.size === 0 ? {} : { questions: [...questions.values()] },
|
|
1243
|
+
...asks.size === 0 ? {} : { askLog: [...asks.values()] },
|
|
1219
1244
|
tasks,
|
|
1220
1245
|
...executions.size === 0 ? {} : { executions: [...executions.values()] },
|
|
1221
1246
|
...plan === void 0 ? {} : { plan },
|
|
@@ -1948,11 +1973,89 @@ function roomSource() {
|
|
|
1948
1973
|
return Object.freeze({ kind: ROOM_KIND });
|
|
1949
1974
|
}
|
|
1950
1975
|
/**
|
|
1951
|
-
*
|
|
1952
|
-
*
|
|
1953
|
-
*
|
|
1954
|
-
*
|
|
1976
|
+
* The text a member's current round has said so far in its own session: the
|
|
1977
|
+
* assistant text blocks since the latest turn start, in order.
|
|
1978
|
+
* @param events - the member's child session log.
|
|
1979
|
+
* @returns the round's text so far.
|
|
1980
|
+
*/
|
|
1981
|
+
function roundTextSoFar(events) {
|
|
1982
|
+
let start = -1;
|
|
1983
|
+
for (let index = events.length - 1; index >= 0; index -= 1) if (events[index].type === "turn/start") {
|
|
1984
|
+
start = index;
|
|
1985
|
+
break;
|
|
1986
|
+
}
|
|
1987
|
+
const parts = [];
|
|
1988
|
+
for (const event of events.slice(start + 1)) {
|
|
1989
|
+
if (event.type !== "assistant/message") continue;
|
|
1990
|
+
const content = event.data.message?.content ?? [];
|
|
1991
|
+
for (const block of content) if (block.type === "text" && typeof block.text === "string") parts.push(block.text);
|
|
1992
|
+
}
|
|
1993
|
+
return parts.join("");
|
|
1994
|
+
}
|
|
1995
|
+
/**
|
|
1996
|
+
* Where each question a round asked sits in its final reply: just after the
|
|
1997
|
+
* text the round had said when it asked (its recorded tail, shortened until
|
|
1998
|
+
* found). Asked before saying anything → 0; tail not found → -1 (shown
|
|
1999
|
+
* first, marked as asked mid-round). Offsets never go backwards.
|
|
2000
|
+
* @param events - the room log.
|
|
2001
|
+
* @param runId - the round.
|
|
2002
|
+
* @param text - the round's reply.
|
|
2003
|
+
* @returns the question ids with their offsets.
|
|
2004
|
+
*/
|
|
2005
|
+
function askOffsets(events, runId, text) {
|
|
2006
|
+
const out = [];
|
|
2007
|
+
let floor = 0;
|
|
2008
|
+
for (const event of events) {
|
|
2009
|
+
if (event.type !== "room/question" || event.data.runId !== runId) continue;
|
|
2010
|
+
const tail = (event.data.before ?? "").trimEnd();
|
|
2011
|
+
let at = tail === "" ? 0 : -1;
|
|
2012
|
+
for (const size of [
|
|
2013
|
+
80,
|
|
2014
|
+
40,
|
|
2015
|
+
20
|
|
2016
|
+
]) {
|
|
2017
|
+
if (tail === "") break;
|
|
2018
|
+
const probe = tail.slice(-size);
|
|
2019
|
+
if (probe.length < Math.min(size, 8)) break;
|
|
2020
|
+
const found = text.indexOf(probe, floor);
|
|
2021
|
+
if (found >= 0) {
|
|
2022
|
+
at = found + probe.length;
|
|
2023
|
+
break;
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
if (at > 0) floor = at;
|
|
2027
|
+
out.push({
|
|
2028
|
+
id: event.data.id,
|
|
2029
|
+
at
|
|
2030
|
+
});
|
|
2031
|
+
}
|
|
2032
|
+
return out;
|
|
2033
|
+
}
|
|
2034
|
+
/** Why a human message's move-on closes a restart-lost round (index.ts moveOnFromLostRounds). */
|
|
2035
|
+
const MOVED_ON_TEXT = "你发了新消息;被重启打断的这一轮按没做完处理";
|
|
2036
|
+
/** A message that queued behind a lost round and rode along with the move-on message. */
|
|
2037
|
+
const MERGED_BACKLOG_TEXT = "已并入下一条消息一起送达";
|
|
2038
|
+
/** A message that queued behind a lost round an earlier move-on skipped: closed, not replayed. */
|
|
2039
|
+
const SUPERSEDED_BACKLOG_TEXT = "被重启打断前排队,已被后续消息取代";
|
|
2040
|
+
/**
|
|
2041
|
+
* Whether a queued delivery sat behind a restart-lost round of the same
|
|
2042
|
+
* member that a human message has since moved on from (its dispatch lies
|
|
2043
|
+
* between the lost round and the move-on edge).
|
|
2044
|
+
* @param events - the room journal.
|
|
2045
|
+
* @param targetId - the member id.
|
|
2046
|
+
* @param dispatchSeq - the queued delivery's dispatch seq.
|
|
2047
|
+
* @returns whether the move-on superseded it.
|
|
1955
2048
|
*/
|
|
2049
|
+
function supersededByMoveOn(events, targetId, dispatchSeq) {
|
|
2050
|
+
return events.some((event) => event.type === "room/delivery-state" && event.data.memberId === targetId && event.data.state === "cancelled" && event.data.text === "你发了新消息;被重启打断的这一轮按没做完处理" && event.data.dispatchSeq < dispatchSeq && dispatchSeq < event.seq);
|
|
2051
|
+
}
|
|
2052
|
+
function floatRunningCards(room, except) {
|
|
2053
|
+
const running = replay(room.snapshotEvents()).runs.filter((run) => run.state === "running" && run.member !== except).sort((left, right) => left.startedAt - right.startedAt);
|
|
2054
|
+
for (const run of running) room.append("room/run-moved", {
|
|
2055
|
+
member: run.member,
|
|
2056
|
+
startedAt: run.startedAt
|
|
2057
|
+
});
|
|
2058
|
+
}
|
|
1956
2059
|
function runChildSessionId(roomId, member) {
|
|
1957
2060
|
if (member?.kind === "main-agent") return roomId;
|
|
1958
2061
|
return member?.childSessionId;
|
|
@@ -2272,6 +2375,17 @@ var DispatchEngine = class {
|
|
|
2272
2375
|
} else await this.reportOutcome(room, dispatch.seq, id, targetId, last.data.state, last.data.text ?? last.data.error ?? "");
|
|
2273
2376
|
continue;
|
|
2274
2377
|
}
|
|
2378
|
+
if (supersededByMoveOn(events, targetId, dispatch.seq)) {
|
|
2379
|
+
room.append("room/delivery-state", {
|
|
2380
|
+
id,
|
|
2381
|
+
dispatchSeq: dispatch.seq,
|
|
2382
|
+
memberId: targetId,
|
|
2383
|
+
state: "cancelled",
|
|
2384
|
+
text: SUPERSEDED_BACKLOG_TEXT
|
|
2385
|
+
});
|
|
2386
|
+
await this.ctx.sessions.flush(room);
|
|
2387
|
+
continue;
|
|
2388
|
+
}
|
|
2275
2389
|
const target = state.members.find((member) => memberId(events, member) === targetId);
|
|
2276
2390
|
if (target !== void 0 && !state.deliveries?.some((row) => row.memberId === targetId && (row.status === "running" || row.status === "uncertain"))) this.dispatch(room, target.name, dispatch.data.text, {
|
|
2277
2391
|
dispatchSeq: dispatch.seq,
|
|
@@ -2670,6 +2784,7 @@ var DispatchEngine = class {
|
|
|
2670
2784
|
...lastAssistantMessageId(agent.session, before) === void 0 ? {} : { messageId: lastAssistantMessageId(agent.session, before) },
|
|
2671
2785
|
...reportedTo === void 0 ? {} : { reportedTo }
|
|
2672
2786
|
});
|
|
2787
|
+
floatRunningCards(room, replay(room.snapshotEvents()).members.find((entry) => entry.id === member.id)?.name ?? member.name);
|
|
2673
2788
|
await this.ctx.sessions.flush(room);
|
|
2674
2789
|
return {
|
|
2675
2790
|
state: "done",
|
|
@@ -2771,16 +2886,22 @@ var DispatchEngine = class {
|
|
|
2771
2886
|
const interrupted = result.stopReason === "completed" ? void 0 : result.stopReason === "aborted" ? "cancelled" : "failed";
|
|
2772
2887
|
const reportedTo = dispatchSeq === void 0 ? void 0 : reportRecipient(room.snapshotEvents(), dispatchSeq)?.name;
|
|
2773
2888
|
const closingMessageId = lastAssistantMessageId(this.ctx.sessions.get(run.id));
|
|
2774
|
-
|
|
2889
|
+
const asks = askOffsets(room.snapshotEvents(), runId, output);
|
|
2890
|
+
const speech = output !== "" || interrupted === void 0 ? room.append("room/speech", {
|
|
2775
2891
|
member: replay(room.snapshotEvents()).members.find((entry) => entry.id === member.id)?.name ?? member.name,
|
|
2776
2892
|
text: output,
|
|
2893
|
+
...asks.length === 0 ? {} : { asks },
|
|
2777
2894
|
childSessionId: run.id,
|
|
2778
2895
|
runId,
|
|
2779
2896
|
...closingMessageId === void 0 ? {} : { messageId: closingMessageId },
|
|
2780
2897
|
durationMs: Date.now() - startedAt,
|
|
2781
2898
|
...interrupted === void 0 ? {} : { interrupted },
|
|
2782
2899
|
...reportedTo === void 0 ? {} : { reportedTo }
|
|
2783
|
-
}) : void 0
|
|
2900
|
+
}) : void 0;
|
|
2901
|
+
if (speech !== void 0) {
|
|
2902
|
+
floatRunningCards(room, speech.data.member);
|
|
2903
|
+
await this.ctx.sessions.flush(room);
|
|
2904
|
+
}
|
|
2784
2905
|
if (result.stopReason === "completed") {
|
|
2785
2906
|
await this.ctx.sessions.flush(room);
|
|
2786
2907
|
return {
|
|
@@ -3447,6 +3568,64 @@ var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializ
|
|
|
3447
3568
|
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
3448
3569
|
done = true;
|
|
3449
3570
|
};
|
|
3571
|
+
var __addDisposableResource = function(env, value, async) {
|
|
3572
|
+
if (value !== null && value !== void 0) {
|
|
3573
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
3574
|
+
var dispose, inner;
|
|
3575
|
+
if (async) {
|
|
3576
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
3577
|
+
dispose = value[Symbol.asyncDispose];
|
|
3578
|
+
}
|
|
3579
|
+
if (dispose === void 0) {
|
|
3580
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
3581
|
+
dispose = value[Symbol.dispose];
|
|
3582
|
+
if (async) inner = dispose;
|
|
3583
|
+
}
|
|
3584
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
3585
|
+
if (inner) dispose = function() {
|
|
3586
|
+
try {
|
|
3587
|
+
inner.call(this);
|
|
3588
|
+
} catch (e) {
|
|
3589
|
+
return Promise.reject(e);
|
|
3590
|
+
}
|
|
3591
|
+
};
|
|
3592
|
+
env.stack.push({
|
|
3593
|
+
value,
|
|
3594
|
+
dispose,
|
|
3595
|
+
async
|
|
3596
|
+
});
|
|
3597
|
+
} else if (async) env.stack.push({ async: true });
|
|
3598
|
+
return value;
|
|
3599
|
+
};
|
|
3600
|
+
var __disposeResources = (function(SuppressedError) {
|
|
3601
|
+
return function(env) {
|
|
3602
|
+
function fail(e) {
|
|
3603
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
3604
|
+
env.hasError = true;
|
|
3605
|
+
}
|
|
3606
|
+
var r, s = 0;
|
|
3607
|
+
function next() {
|
|
3608
|
+
while (r = env.stack.pop()) try {
|
|
3609
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
3610
|
+
if (r.dispose) {
|
|
3611
|
+
var result = r.dispose.call(r.value);
|
|
3612
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) {
|
|
3613
|
+
fail(e);
|
|
3614
|
+
return next();
|
|
3615
|
+
});
|
|
3616
|
+
} else s |= 1;
|
|
3617
|
+
} catch (e) {
|
|
3618
|
+
fail(e);
|
|
3619
|
+
}
|
|
3620
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
3621
|
+
if (env.hasError) throw env.error;
|
|
3622
|
+
}
|
|
3623
|
+
return next();
|
|
3624
|
+
};
|
|
3625
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) {
|
|
3626
|
+
var e = new Error(message);
|
|
3627
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
3628
|
+
});
|
|
3450
3629
|
var __rewriteRelativeImportExtension = function(path, preserveJsx) {
|
|
3451
3630
|
if (typeof path === "string" && /^\.\.?\//.test(path)) return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function(m, tsx, d, ext, cm) {
|
|
3452
3631
|
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : d + ext + "." + cm.toLowerCase() + "js";
|
|
@@ -3518,6 +3697,44 @@ function parseQuestions(raw) {
|
|
|
3518
3697
|
if (new Set(questions.map((question) => question.id)).size !== questions.length) return "Question ids must be unique";
|
|
3519
3698
|
return questions;
|
|
3520
3699
|
}
|
|
3700
|
+
/**
|
|
3701
|
+
* The message a late answer reaches its member as: each question with the
|
|
3702
|
+
* human's pick, so the member can go on (or correct what it assumed).
|
|
3703
|
+
* @param questions - the card's questions.
|
|
3704
|
+
* @param answers - the human's answers, one per question.
|
|
3705
|
+
* @returns the dispatch text.
|
|
3706
|
+
*/
|
|
3707
|
+
function lateAnswerText(questions, answers) {
|
|
3708
|
+
return [
|
|
3709
|
+
"你之前用 ask_user_question 问的问题没等到回答就过期了,人现在补答了:",
|
|
3710
|
+
...questions.map((question) => {
|
|
3711
|
+
const answer = answers.find((entry) => entry.id === question.id);
|
|
3712
|
+
const picked = [...answer?.selected ?? [], ...answer?.custom === void 0 ? [] : [answer.custom]].join(";");
|
|
3713
|
+
return `- ${question.header === void 0 ? "" : `${question.header}:`}${question.question} → ${picked === "" ? "(没选)" : picked}`;
|
|
3714
|
+
}),
|
|
3715
|
+
"",
|
|
3716
|
+
"按这些答案继续。如果你已经按自己的假设往下做了,对照答案调整,并说一下改了什么。"
|
|
3717
|
+
].join("\n");
|
|
3718
|
+
}
|
|
3719
|
+
/**
|
|
3720
|
+
* The dispatch text with the message's attachments appended as local paths:
|
|
3721
|
+
* every member — CLI or native — opens them with its own read tool, so one
|
|
3722
|
+
* form reaches all of them.
|
|
3723
|
+
* @param text - the dispatched text.
|
|
3724
|
+
* @param attachments - the admitted attachments.
|
|
3725
|
+
* @returns the text members receive.
|
|
3726
|
+
*/
|
|
3727
|
+
function withAttachmentPaths(text, attachments) {
|
|
3728
|
+
if (attachments === void 0 || attachments.length === 0) return text;
|
|
3729
|
+
return [
|
|
3730
|
+
text,
|
|
3731
|
+
"",
|
|
3732
|
+
"人随消息附了这些附件,用你自己的读取工具打开(图片用能看图的读取工具):",
|
|
3733
|
+
...attachments.map((entry) => {
|
|
3734
|
+
return `- ${`${entry.kind === "image" ? "图片" : "文件"}${entry.name === void 0 ? "" : ` ${entry.name}`}`}:${entry.path ?? "(宿主没有给出本地路径,打不开就告诉人)"}`;
|
|
3735
|
+
})
|
|
3736
|
+
].join("\n");
|
|
3737
|
+
}
|
|
3521
3738
|
function messageReceipt(receipt, kind) {
|
|
3522
3739
|
if (receipt.held !== void 0) return `pending-confirm (${receipt.held === "paused" ? "房间已暂停,恢复后自动投递" : receipt.held === "hop-limit" ? "这条消息链已到跳数上限,等房间主人决定是否继续" : "房间设置为成员间消息需房间主人确认"})`;
|
|
3523
3740
|
return kind === "notify" ? `sent #${receipt.messageId}(知会,不会回报)` : `sent #${receipt.messageId}(${receipt.member} 做完后结果会自动回报给你;不要 sleep 等待,也不要用 room_read 轮询)`;
|
|
@@ -3561,6 +3778,7 @@ function handoffText(_events, state, sessionId, from, to) {
|
|
|
3561
3778
|
let RoomService = (() => {
|
|
3562
3779
|
let _classSuper = TypertRemoteService;
|
|
3563
3780
|
let _instanceExtraInitializers = [];
|
|
3781
|
+
let _pendingCards_decorators;
|
|
3564
3782
|
let _isRoom_decorators;
|
|
3565
3783
|
let _listProviders_decorators;
|
|
3566
3784
|
let _setTaskHint_decorators;
|
|
@@ -3590,6 +3808,7 @@ let RoomService = (() => {
|
|
|
3590
3808
|
return class RoomService extends _classSuper {
|
|
3591
3809
|
static {
|
|
3592
3810
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
3811
|
+
_pendingCards_decorators = [Remote("pendingCards")];
|
|
3593
3812
|
_isRoom_decorators = [Remote("isRoom")];
|
|
3594
3813
|
_listProviders_decorators = [Remote("listProviders")];
|
|
3595
3814
|
_setTaskHint_decorators = [Remote("setTaskHint")];
|
|
@@ -3616,6 +3835,17 @@ let RoomService = (() => {
|
|
|
3616
3835
|
_setGoal_decorators = [Remote("setGoal")];
|
|
3617
3836
|
_cancel_decorators = [Remote("cancel")];
|
|
3618
3837
|
_cancelChild_decorators = [Remote("cancelChild")];
|
|
3838
|
+
__esDecorate(this, null, _pendingCards_decorators, {
|
|
3839
|
+
kind: "method",
|
|
3840
|
+
name: "pendingCards",
|
|
3841
|
+
static: false,
|
|
3842
|
+
private: false,
|
|
3843
|
+
access: {
|
|
3844
|
+
has: (obj) => "pendingCards" in obj,
|
|
3845
|
+
get: (obj) => obj.pendingCards
|
|
3846
|
+
},
|
|
3847
|
+
metadata: _metadata
|
|
3848
|
+
}, null, _instanceExtraInitializers);
|
|
3619
3849
|
__esDecorate(this, null, _isRoom_decorators, {
|
|
3620
3850
|
kind: "method",
|
|
3621
3851
|
name: "isRoom",
|
|
@@ -4035,6 +4265,12 @@ let RoomService = (() => {
|
|
|
4035
4265
|
messages: []
|
|
4036
4266
|
};
|
|
4037
4267
|
});
|
|
4268
|
+
ctx.on("session/event", (session, event) => {
|
|
4269
|
+
this.observeActivity(session, event);
|
|
4270
|
+
});
|
|
4271
|
+
ctx.on("agent/status", ({ agent, status }) => {
|
|
4272
|
+
if (status !== "running" && (this.activeRuns.get(String(agent.id))?.size ?? 0) > 0) this.emitStatus(String(agent.id), true);
|
|
4273
|
+
});
|
|
4038
4274
|
const scanned = /* @__PURE__ */ new WeakSet();
|
|
4039
4275
|
ctx.on("agent/pre-step", async ({ agent, step }, next) => {
|
|
4040
4276
|
const decision = await next();
|
|
@@ -4162,6 +4398,84 @@ let RoomService = (() => {
|
|
|
4162
4398
|
* @param request - session identity.
|
|
4163
4399
|
* @returns true when the session carries the marker (false for unknown sessions too).
|
|
4164
4400
|
*/
|
|
4401
|
+
/** Run ids of the members working in each room (the session list's 运行中). */
|
|
4402
|
+
activeRuns = /* @__PURE__ */ new Map();
|
|
4403
|
+
/** Question/approval cards waiting for the human, per room. */
|
|
4404
|
+
waitingCards = /* @__PURE__ */ new Map();
|
|
4405
|
+
/**
|
|
4406
|
+
* Follow a room's run and card edges as they are journaled: a room turns
|
|
4407
|
+
* running when its first member starts and settles when its last one ends
|
|
4408
|
+
* (the host's `api-session/status`, so the list shows 运行中 and then
|
|
4409
|
+
* 已完成), and its waiting cards are kept for {@link pendingCards}.
|
|
4410
|
+
* @param session - the session the event landed in.
|
|
4411
|
+
* @param event - the journaled event.
|
|
4412
|
+
*/
|
|
4413
|
+
observeActivity(session, event) {
|
|
4414
|
+
const id = String(session.id);
|
|
4415
|
+
switch (event.type) {
|
|
4416
|
+
case "room/run-state": {
|
|
4417
|
+
const runs = this.activeRuns.get(id) ?? /* @__PURE__ */ new Set();
|
|
4418
|
+
const before = runs.size;
|
|
4419
|
+
const key = event.data.runId ?? `${event.data.member}:${event.data.startedAt}`;
|
|
4420
|
+
if (event.data.state === "running") runs.add(key);
|
|
4421
|
+
else runs.delete(key);
|
|
4422
|
+
if (runs.size === 0) this.activeRuns.delete(id);
|
|
4423
|
+
else this.activeRuns.set(id, runs);
|
|
4424
|
+
if (event.data.state !== "running") this.expireCardsOf(id, event.data.member);
|
|
4425
|
+
if (before === 0 && runs.size > 0) this.emitStatus(id, true);
|
|
4426
|
+
else if (before > 0 && runs.size === 0 && this.ctx.agents.get(session.id)?.status !== "running") this.emitStatus(id, false);
|
|
4427
|
+
return;
|
|
4428
|
+
}
|
|
4429
|
+
case "room/question":
|
|
4430
|
+
case "room/approval": {
|
|
4431
|
+
const cards = this.waitingCards.get(id) ?? /* @__PURE__ */ new Map();
|
|
4432
|
+
cards.set(event.data.id, {
|
|
4433
|
+
kind: event.type === "room/question" ? "question" : "approval",
|
|
4434
|
+
member: event.data.member
|
|
4435
|
+
});
|
|
4436
|
+
this.waitingCards.set(id, cards);
|
|
4437
|
+
return;
|
|
4438
|
+
}
|
|
4439
|
+
case "room/question-resolved":
|
|
4440
|
+
case "room/approval-resolved": {
|
|
4441
|
+
const cards = this.waitingCards.get(id);
|
|
4442
|
+
cards?.delete(event.data.id);
|
|
4443
|
+
if (cards?.size === 0) this.waitingCards.delete(id);
|
|
4444
|
+
return;
|
|
4445
|
+
}
|
|
4446
|
+
}
|
|
4447
|
+
}
|
|
4448
|
+
/**
|
|
4449
|
+
* End the waits behind one member's open cards (its round settled): the
|
|
4450
|
+
* question/approval calls resolve as unanswered, which journals them
|
|
4451
|
+
* expired — still answerable, by a new message (answerExpiredQuestion).
|
|
4452
|
+
* @param roomId - the room session.
|
|
4453
|
+
* @param member - the member whose round ended.
|
|
4454
|
+
*/
|
|
4455
|
+
expireCardsOf(roomId, member) {
|
|
4456
|
+
for (const [cardId, card] of this.waitingCards.get(roomId) ?? []) {
|
|
4457
|
+
if (card.member !== member) continue;
|
|
4458
|
+
if (card.kind === "question") this.questionWaiters.get(cardId)?.(void 0);
|
|
4459
|
+
else this.approvalWaiters.get(cardId)?.("cancelled");
|
|
4460
|
+
}
|
|
4461
|
+
}
|
|
4462
|
+
/** Broadcast a room's running flip on the host's session-status channel. */
|
|
4463
|
+
emitStatus(sessionId, running) {
|
|
4464
|
+
this.ctx.emit("api-session/status", SessionId(sessionId), running);
|
|
4465
|
+
}
|
|
4466
|
+
/**
|
|
4467
|
+
* The question/approval cards waiting for the human across live rooms —
|
|
4468
|
+
* the browser publishes them as the session list's 等你处理 status.
|
|
4469
|
+
* @param request - optionally one room only.
|
|
4470
|
+
* @returns the waiting cards.
|
|
4471
|
+
*/
|
|
4472
|
+
async pendingCards(request) {
|
|
4473
|
+
return (request.sessionId === void 0 ? [...this.waitingCards.keys()] : [String(request.sessionId)]).flatMap((sessionId) => [...this.waitingCards.get(sessionId) ?? []].map(([id, card]) => ({
|
|
4474
|
+
sessionId,
|
|
4475
|
+
id,
|
|
4476
|
+
kind: card.kind
|
|
4477
|
+
})));
|
|
4478
|
+
}
|
|
4165
4479
|
async isRoom(request) {
|
|
4166
4480
|
const session = this.ctx.sessions.get(request.sessionId);
|
|
4167
4481
|
if (session !== void 0) return isRoomLog(session.snapshotEvents());
|
|
@@ -4484,7 +4798,7 @@ let RoomService = (() => {
|
|
|
4484
4798
|
};
|
|
4485
4799
|
const body = parsed.targets.length > 0 ? parsed.text : text;
|
|
4486
4800
|
const filed = await this.fileHumanMessage(session, edit.data.id, text, task);
|
|
4487
|
-
this.moveOnFromLostRounds(session, targets);
|
|
4801
|
+
const carry = this.moveOnFromLostRounds(session, targets);
|
|
4488
4802
|
const dispatch = session.append("room/dispatch", {
|
|
4489
4803
|
id: requestId,
|
|
4490
4804
|
targets,
|
|
@@ -4494,7 +4808,7 @@ let RoomService = (() => {
|
|
|
4494
4808
|
targetIds: targets.map((name) => memberId(session.snapshotEvents(), current.members.find((member) => member.name === name)))
|
|
4495
4809
|
});
|
|
4496
4810
|
await this.ctx.sessions.flush(session);
|
|
4497
|
-
for (const target of targets) this.engine.dispatch(session, target, body, { dispatchSeq: dispatch.seq });
|
|
4811
|
+
for (const target of targets) this.engine.dispatch(session, target, carry(target, body), { dispatchSeq: dispatch.seq });
|
|
4498
4812
|
return { ok: true };
|
|
4499
4813
|
}
|
|
4500
4814
|
/**
|
|
@@ -4503,10 +4817,12 @@ let RoomService = (() => {
|
|
|
4503
4817
|
* message runs instead of queueing behind a reconciliation. Formal-goal
|
|
4504
4818
|
* attempts keep their explicit reconciliation.
|
|
4505
4819
|
*/
|
|
4506
|
-
moveOnFromLostRounds(session, targets, why =
|
|
4820
|
+
moveOnFromLostRounds(session, targets, why = MOVED_ON_TEXT) {
|
|
4507
4821
|
const events = session.snapshotEvents();
|
|
4508
4822
|
const state = replay(events);
|
|
4509
|
-
const
|
|
4823
|
+
const members = state.members.filter((member) => targets.includes(member.name));
|
|
4824
|
+
const ids = new Set(members.map((member) => memberId(events, member)));
|
|
4825
|
+
const backlog = /* @__PURE__ */ new Map();
|
|
4510
4826
|
for (const row of state.deliveries ?? []) {
|
|
4511
4827
|
if (row.status !== "uncertain" || row.plan?.taskId !== void 0 || !ids.has(row.memberId)) continue;
|
|
4512
4828
|
session.append("room/delivery-state", {
|
|
@@ -4524,7 +4840,31 @@ let RoomService = (() => {
|
|
|
4524
4840
|
state: "cancelled",
|
|
4525
4841
|
...this.terminalRunStateFields(session, state, run.member)
|
|
4526
4842
|
});
|
|
4843
|
+
const name = members.find((member) => memberId(events, member) === row.memberId)?.name;
|
|
4844
|
+
if (name === void 0) continue;
|
|
4845
|
+
for (const queued of state.deliveries ?? []) {
|
|
4846
|
+
if (queued.status !== "queued" || queued.memberId !== row.memberId || queued.dispatchSeq <= row.dispatchSeq || queued.plan?.taskId !== void 0) continue;
|
|
4847
|
+
session.append("room/delivery-state", {
|
|
4848
|
+
id: queued.id,
|
|
4849
|
+
dispatchSeq: queued.dispatchSeq,
|
|
4850
|
+
memberId: queued.memberId,
|
|
4851
|
+
state: "done",
|
|
4852
|
+
text: MERGED_BACKLOG_TEXT
|
|
4853
|
+
});
|
|
4854
|
+
const from = queued.from === void 0 ? void 0 : state.members.find((member) => memberId(events, member) === queued.from)?.name;
|
|
4855
|
+
backlog.set(name, [...backlog.get(name) ?? [], `- ${from === void 0 ? "" : `来自 ${from}:`}${queued.text}`]);
|
|
4856
|
+
}
|
|
4527
4857
|
}
|
|
4858
|
+
return (member, text) => {
|
|
4859
|
+
const lines = backlog.get(member);
|
|
4860
|
+
return lines === void 0 ? text : [
|
|
4861
|
+
text,
|
|
4862
|
+
"",
|
|
4863
|
+
"(你被重启打断期间,房间里还有这些消息排着没送到,一并给你:",
|
|
4864
|
+
...lines,
|
|
4865
|
+
")"
|
|
4866
|
+
].join("\n");
|
|
4867
|
+
};
|
|
4528
4868
|
}
|
|
4529
4869
|
/**
|
|
4530
4870
|
* Where a member's next send belongs: the task and delivery it is working
|
|
@@ -5250,13 +5590,15 @@ let RoomService = (() => {
|
|
|
5250
5590
|
ok: false,
|
|
5251
5591
|
error: loaded.error
|
|
5252
5592
|
};
|
|
5253
|
-
|
|
5593
|
+
const attached = request.attachments ?? [];
|
|
5594
|
+
if (request.text.trim() === "" && attached.length === 0) return {
|
|
5254
5595
|
ok: false,
|
|
5255
5596
|
error: { code: "empty-text" }
|
|
5256
5597
|
};
|
|
5257
5598
|
const requestSignature = createHash("sha256").update(JSON.stringify({
|
|
5258
5599
|
text: request.text.trim(),
|
|
5259
|
-
targets: [...new Set(request.targets ?? [])].sort()
|
|
5600
|
+
targets: [...new Set(request.targets ?? [])].sort(),
|
|
5601
|
+
...attached.length === 0 ? {} : { attachments: attached.map((entry) => entry.type === "file" ? entry.receiptId : `${entry.mediaType}:${entry.data.length}`) }
|
|
5260
5602
|
})).digest("hex");
|
|
5261
5603
|
if (request.requestId !== void 0) {
|
|
5262
5604
|
if (request.requestId.trim() === "" || request.requestId.length > 128) return {
|
|
@@ -5276,7 +5618,7 @@ let RoomService = (() => {
|
|
|
5276
5618
|
await this.ctx.sessions.flush(loaded.session);
|
|
5277
5619
|
for (const target of original.targetIds ?? []) {
|
|
5278
5620
|
const member = replay(loaded.session.snapshotEvents()).members.find((member) => memberId(loaded.session.snapshotEvents(), member) === target);
|
|
5279
|
-
if (member !== void 0) this.engine.dispatch(loaded.session, member.name, original.text, {
|
|
5621
|
+
if (member !== void 0) this.engine.dispatch(loaded.session, member.name, withAttachmentPaths(original.text, original.attachments), {
|
|
5280
5622
|
dispatchSeq: previous.seq,
|
|
5281
5623
|
targetId: target
|
|
5282
5624
|
});
|
|
@@ -5309,8 +5651,8 @@ let RoomService = (() => {
|
|
|
5309
5651
|
}
|
|
5310
5652
|
const said = parsed.targets.length > 0 ? parsed.text : request.text.trim();
|
|
5311
5653
|
const outside = [...said.matchAll(/@\[((?:\\.|[^\\\]\n])+)\]\((dsh-session:[^)\s]+)\)/gu)];
|
|
5312
|
-
const text = said;
|
|
5313
|
-
const prompt = outside.length === 0 ?
|
|
5654
|
+
const text = said === "" && attached.length > 0 ? "(见附件)" : said;
|
|
5655
|
+
const prompt = outside.length === 0 ? text : [
|
|
5314
5656
|
said,
|
|
5315
5657
|
"",
|
|
5316
5658
|
`(人点名了房间外的会话:${outside.map((match) => `「${match[1].replace(/\\(.)/gu, "$1")}」`).join("、")}。用 room_help 去问:session 参数原样传那个 @[…](dsh-session:…) 引用;标签是「成员(房间)」时会交给那个成员。不要转给别的成员代问。)`
|
|
@@ -5328,16 +5670,38 @@ let RoomService = (() => {
|
|
|
5328
5670
|
names: unknown
|
|
5329
5671
|
}
|
|
5330
5672
|
};
|
|
5673
|
+
const admitted = await this.admitAttachments(request.sessionId, attached);
|
|
5674
|
+
if (!admitted.ok) return {
|
|
5675
|
+
ok: false,
|
|
5676
|
+
error: admitted.error
|
|
5677
|
+
};
|
|
5331
5678
|
const human = createUserMessage({
|
|
5332
|
-
content: [{
|
|
5679
|
+
content: [...admitted.blocks, ...request.text.trim() === "" ? [] : [{
|
|
5333
5680
|
type: "text",
|
|
5334
5681
|
text: request.text.trim()
|
|
5335
|
-
}],
|
|
5682
|
+
}]],
|
|
5336
5683
|
source: { kind: "user" }
|
|
5337
5684
|
});
|
|
5338
|
-
|
|
5685
|
+
{
|
|
5686
|
+
const env_1 = {
|
|
5687
|
+
stack: [],
|
|
5688
|
+
error: void 0,
|
|
5689
|
+
hasError: false
|
|
5690
|
+
};
|
|
5691
|
+
try {
|
|
5692
|
+
const binding = __addDisposableResource(env_1, admitted.bind(request.requestId ?? String(human.id)), false);
|
|
5693
|
+
loaded.session.append("user/message", human, { surfaceOp: "append" });
|
|
5694
|
+
binding?.commit();
|
|
5695
|
+
} catch (e_1) {
|
|
5696
|
+
env_1.error = e_1;
|
|
5697
|
+
env_1.hasError = true;
|
|
5698
|
+
} finally {
|
|
5699
|
+
__disposeResources(env_1);
|
|
5700
|
+
}
|
|
5701
|
+
}
|
|
5702
|
+
floatRunningCards(loaded.session);
|
|
5339
5703
|
const filed = await this.fileHumanMessage(loaded.session, human.id, request.text.trim(), request.task ?? this.consumeTaskHint(request.sessionId));
|
|
5340
|
-
this.moveOnFromLostRounds(loaded.session, targets);
|
|
5704
|
+
const carry = this.moveOnFromLostRounds(loaded.session, targets);
|
|
5341
5705
|
const dispatch = loaded.session.append("room/dispatch", {
|
|
5342
5706
|
id: request.requestId ?? randomUUID(),
|
|
5343
5707
|
requestSignature,
|
|
@@ -5345,10 +5709,11 @@ let RoomService = (() => {
|
|
|
5345
5709
|
text,
|
|
5346
5710
|
origin: "human",
|
|
5347
5711
|
task: filed.task,
|
|
5712
|
+
...admitted.records.length === 0 ? {} : { attachments: admitted.records },
|
|
5348
5713
|
targetIds: targets.map((name) => memberId(loaded.session.snapshotEvents(), loaded.state.members.find((member) => member.name === name)))
|
|
5349
5714
|
});
|
|
5350
5715
|
await this.ctx.sessions.flush(loaded.session);
|
|
5351
|
-
for (const target of targets) this.engine.dispatch(loaded.session, target, prompt, { dispatchSeq: dispatch.seq });
|
|
5716
|
+
for (const target of targets) this.engine.dispatch(loaded.session, target, carry(target, withAttachmentPaths(prompt, admitted.records)), { dispatchSeq: dispatch.seq });
|
|
5352
5717
|
return {
|
|
5353
5718
|
ok: true,
|
|
5354
5719
|
value: {
|
|
@@ -5361,6 +5726,95 @@ let RoomService = (() => {
|
|
|
5361
5726
|
};
|
|
5362
5727
|
}
|
|
5363
5728
|
/**
|
|
5729
|
+
* Admit a human message's composer attachments the way the host's own
|
|
5730
|
+
* prompt path does: file receipts resolve against this room session's
|
|
5731
|
+
* uploads, images are stored, and every attachment becomes a durable
|
|
5732
|
+
* content block for the message — plus the local path members read it by.
|
|
5733
|
+
* @param sessionId - the room session (its agent owns the uploads).
|
|
5734
|
+
* @param inputs - the wire attachments.
|
|
5735
|
+
* @returns the content blocks, the dispatch records, and the receipt binder; or a refusal.
|
|
5736
|
+
*/
|
|
5737
|
+
async admitAttachments(sessionId, inputs) {
|
|
5738
|
+
if (inputs.length === 0) return {
|
|
5739
|
+
ok: true,
|
|
5740
|
+
blocks: [],
|
|
5741
|
+
records: [],
|
|
5742
|
+
bind: () => void 0
|
|
5743
|
+
};
|
|
5744
|
+
const probe = this.ctx.get;
|
|
5745
|
+
const services = {
|
|
5746
|
+
attachments: probe("attachments"),
|
|
5747
|
+
fileUploads: probe("fileUploads")
|
|
5748
|
+
};
|
|
5749
|
+
const store = services.attachments;
|
|
5750
|
+
let agent = this.ctx.agents.get(sessionId);
|
|
5751
|
+
if (agent === void 0) {
|
|
5752
|
+
const resolved = await this.ctx.get("sessionController")?.resolveAgent?.(sessionId).catch(() => void 0);
|
|
5753
|
+
if (resolved !== void 0 && "agent" in resolved) agent = resolved.agent;
|
|
5754
|
+
}
|
|
5755
|
+
if (store === void 0 || agent === void 0) return {
|
|
5756
|
+
ok: false,
|
|
5757
|
+
error: {
|
|
5758
|
+
code: "attachment-invalid",
|
|
5759
|
+
message: "attachments are unavailable on this host"
|
|
5760
|
+
}
|
|
5761
|
+
};
|
|
5762
|
+
const receipts = [];
|
|
5763
|
+
const parts = [];
|
|
5764
|
+
for (const input of inputs) {
|
|
5765
|
+
if (input.type === "image") {
|
|
5766
|
+
parts.push({
|
|
5767
|
+
type: "image",
|
|
5768
|
+
data: input.data,
|
|
5769
|
+
mediaType: input.mediaType,
|
|
5770
|
+
...input.name === void 0 ? {} : { name: input.name }
|
|
5771
|
+
});
|
|
5772
|
+
continue;
|
|
5773
|
+
}
|
|
5774
|
+
const ref = services.fileUploads?.resolve(agent, input.receiptId);
|
|
5775
|
+
if (ref === void 0) return {
|
|
5776
|
+
ok: false,
|
|
5777
|
+
error: {
|
|
5778
|
+
code: "attachment-invalid",
|
|
5779
|
+
message: "a file was not uploaded to this room"
|
|
5780
|
+
}
|
|
5781
|
+
};
|
|
5782
|
+
receipts.push(input.receiptId);
|
|
5783
|
+
parts.push({
|
|
5784
|
+
type: "file",
|
|
5785
|
+
attachment: ref
|
|
5786
|
+
});
|
|
5787
|
+
}
|
|
5788
|
+
let blocks;
|
|
5789
|
+
try {
|
|
5790
|
+
blocks = await store.admitPromptContent(parts);
|
|
5791
|
+
} catch (error) {
|
|
5792
|
+
return {
|
|
5793
|
+
ok: false,
|
|
5794
|
+
error: {
|
|
5795
|
+
code: "attachment-invalid",
|
|
5796
|
+
message: error instanceof Error ? error.message : String(error)
|
|
5797
|
+
}
|
|
5798
|
+
};
|
|
5799
|
+
}
|
|
5800
|
+
const records = blocks.flatMap((block) => {
|
|
5801
|
+
if (block.type !== "image" && block.type !== "file") return [];
|
|
5802
|
+
const path = block.type === "image" ? store.imageHostPath?.(block.attachment) : store.fileHostPath?.(block.attachment);
|
|
5803
|
+
return [{
|
|
5804
|
+
kind: block.type,
|
|
5805
|
+
...block.attachment?.name === void 0 ? {} : { name: block.attachment.name },
|
|
5806
|
+
...path === void 0 ? {} : { path }
|
|
5807
|
+
}];
|
|
5808
|
+
});
|
|
5809
|
+
const uploads = services.fileUploads;
|
|
5810
|
+
return {
|
|
5811
|
+
ok: true,
|
|
5812
|
+
blocks,
|
|
5813
|
+
records,
|
|
5814
|
+
bind: (requestId) => receipts.length === 0 || uploads === void 0 ? void 0 : uploads.bindPrompt(agent, receipts, requestId)
|
|
5815
|
+
};
|
|
5816
|
+
}
|
|
5817
|
+
/**
|
|
5364
5818
|
* Send one message from a member to another (host-only — the `room_message`
|
|
5365
5819
|
* tool and the bridge's `member_message`/`room_message` land here; the
|
|
5366
5820
|
* human's path is postMessage). Any member may message any other: the
|
|
@@ -5562,7 +6016,7 @@ let RoomService = (() => {
|
|
|
5562
6016
|
header: "求助",
|
|
5563
6017
|
question: `${asker.name} 想向会话「${target.title}」求助:${text.length > 80 ? `${text.slice(0, 80)}…` : text}`,
|
|
5564
6018
|
options: [{ label: "允许" }, { label: "不允许" }]
|
|
5565
|
-
}], 6e5))?.[0]?.selected[0] !== "允许") return `The human did not allow asking "${target.title}". Do not retry unless they @ that session in the room.`;
|
|
6019
|
+
}], 6e5, true))?.[0]?.selected[0] !== "允许") return `The human did not allow asking "${target.title}". Do not retry unless they @ that session in the room.`;
|
|
5566
6020
|
}
|
|
5567
6021
|
const help = {
|
|
5568
6022
|
id: randomUUID().slice(0, 8),
|
|
@@ -5607,7 +6061,7 @@ let RoomService = (() => {
|
|
|
5607
6061
|
roomTitle: this.roomTitle(room),
|
|
5608
6062
|
from: help.fromName
|
|
5609
6063
|
};
|
|
5610
|
-
this.moveOnFromLostRounds(other.session, [member.name], "另一个协作间来求助;被重启打断的这一轮按没做完处理");
|
|
6064
|
+
const carry = this.moveOnFromLostRounds(other.session, [member.name], "另一个协作间来求助;被重启打断的这一轮按没做完处理");
|
|
5611
6065
|
const sent = sendOrHold(other.session, {
|
|
5612
6066
|
to: member,
|
|
5613
6067
|
text,
|
|
@@ -5633,7 +6087,7 @@ let RoomService = (() => {
|
|
|
5633
6087
|
state: sent.delivered ? "running" : "queued"
|
|
5634
6088
|
});
|
|
5635
6089
|
await this.ctx.sessions.flush(room);
|
|
5636
|
-
if (sent.delivered) this.engine.dispatch(other.session, member.name, text, { dispatchSeq: sent.seq });
|
|
6090
|
+
if (sent.delivered) this.engine.dispatch(other.session, member.name, carry(member.name, text), { dispatchSeq: sent.seq });
|
|
5637
6091
|
const who = `${member.name} in the room "${help.target.title}"`;
|
|
5638
6092
|
return `${sent.delivered ? `Sent to ${who}.` : `Sent to ${who}; that room is paused, so the request waits until it resumes.`} Help id: ${help.id}. The reply reaches you on its own — end your turn now; do not poll.`;
|
|
5639
6093
|
}
|
|
@@ -6017,7 +6471,7 @@ let RoomService = (() => {
|
|
|
6017
6471
|
* @param timeoutMs - how long the call waits before it gives up.
|
|
6018
6472
|
* @returns the answers, or undefined when nobody answered.
|
|
6019
6473
|
*/
|
|
6020
|
-
async askQuestions(session, member, questions, timeoutMs) {
|
|
6474
|
+
async askQuestions(session, member, questions, timeoutMs, consent = false) {
|
|
6021
6475
|
const id = randomUUID();
|
|
6022
6476
|
const answered = new Promise((resolve) => {
|
|
6023
6477
|
this.questionWaiters.set(id, resolve);
|
|
@@ -6025,11 +6479,17 @@ let RoomService = (() => {
|
|
|
6025
6479
|
resolve(void 0);
|
|
6026
6480
|
}, timeoutMs).unref?.();
|
|
6027
6481
|
});
|
|
6482
|
+
const run = replay(session.snapshotEvents()).runs.find((entry) => entry.member === member.name && entry.state === "running");
|
|
6483
|
+
const child = member.childSessionId === void 0 ? void 0 : this.ctx.sessions.get(member.childSessionId);
|
|
6484
|
+
const before = child === void 0 ? "" : roundTextSoFar(child.snapshotEvents()).slice(-160);
|
|
6028
6485
|
session.append("room/question", {
|
|
6029
6486
|
id,
|
|
6030
6487
|
member: member.name,
|
|
6031
6488
|
memberId: memberId(session.snapshotEvents(), member),
|
|
6032
|
-
questions
|
|
6489
|
+
questions,
|
|
6490
|
+
...consent ? { consent: true } : {},
|
|
6491
|
+
...run?.runId === void 0 ? {} : { runId: run.runId },
|
|
6492
|
+
...before.trim() === "" ? {} : { before }
|
|
6033
6493
|
});
|
|
6034
6494
|
await this.ctx.sessions.flush(session);
|
|
6035
6495
|
const answers = await answered;
|
|
@@ -6056,10 +6516,7 @@ let RoomService = (() => {
|
|
|
6056
6516
|
};
|
|
6057
6517
|
const waiter = this.questionWaiters.get(request.id);
|
|
6058
6518
|
const pending = loaded.state.questions?.find((entry) => entry.id === request.id);
|
|
6059
|
-
if (waiter === void 0 || pending === void 0) return
|
|
6060
|
-
ok: false,
|
|
6061
|
-
error: { code: "question-not-pending" }
|
|
6062
|
-
};
|
|
6519
|
+
if (waiter === void 0 || pending === void 0) return this.answerExpiredQuestion(loaded.session, request);
|
|
6063
6520
|
const answers = pending.questions.map((question) => {
|
|
6064
6521
|
const given = request.answers.find((answer) => answer.id === question.id);
|
|
6065
6522
|
const custom = given?.custom?.trim();
|
|
@@ -6078,6 +6535,61 @@ let RoomService = (() => {
|
|
|
6078
6535
|
waiter(answers);
|
|
6079
6536
|
return { ok: true };
|
|
6080
6537
|
}
|
|
6538
|
+
/**
|
|
6539
|
+
* Answer a card whose waiting call is gone (it timed out, or a restart
|
|
6540
|
+
* ended the member's process): the card records the answers, and they
|
|
6541
|
+
* reach the member as a new message that wakes it with its own context —
|
|
6542
|
+
* the member asked so it could go on, so a late answer is still worth it.
|
|
6543
|
+
* A consent gate is never answered late: its call already decided.
|
|
6544
|
+
* @param session - the live room session.
|
|
6545
|
+
* @param request - card id and the human's answers.
|
|
6546
|
+
* @returns ok, or `question-not-pending` when the card is not an expired, unanswered question.
|
|
6547
|
+
*/
|
|
6548
|
+
async answerExpiredQuestion(session, request) {
|
|
6549
|
+
const events = session.snapshotEvents();
|
|
6550
|
+
const asked = events.find((event) => event.type === "room/question" && event.data.id === request.id);
|
|
6551
|
+
if (asked?.type !== "room/question" || asked.data.consent === true) return {
|
|
6552
|
+
ok: false,
|
|
6553
|
+
error: { code: "question-not-pending" }
|
|
6554
|
+
};
|
|
6555
|
+
const last = events.filter((event) => event.type === "room/question-resolved" && event.data.id === request.id).at(-1);
|
|
6556
|
+
if (last !== void 0 && (last.type !== "room/question-resolved" || last.data.outcome !== "cancelled")) return {
|
|
6557
|
+
ok: false,
|
|
6558
|
+
error: { code: "question-not-pending" }
|
|
6559
|
+
};
|
|
6560
|
+
const member = replay(events).members.find((entry) => memberId(events, entry) === asked.data.memberId);
|
|
6561
|
+
if (member === void 0) return {
|
|
6562
|
+
ok: false,
|
|
6563
|
+
error: { code: "question-not-pending" }
|
|
6564
|
+
};
|
|
6565
|
+
const answers = asked.data.questions.map((question) => {
|
|
6566
|
+
const given = request.answers.find((answer) => answer.id === question.id);
|
|
6567
|
+
const custom = given?.custom?.trim();
|
|
6568
|
+
return {
|
|
6569
|
+
id: question.id,
|
|
6570
|
+
selected: [...given?.selected ?? []].filter((label) => typeof label === "string"),
|
|
6571
|
+
...custom === void 0 || custom === "" ? {} : { custom }
|
|
6572
|
+
};
|
|
6573
|
+
});
|
|
6574
|
+
session.append("room/question-resolved", {
|
|
6575
|
+
id: request.id,
|
|
6576
|
+
outcome: "answered",
|
|
6577
|
+
answers,
|
|
6578
|
+
late: true
|
|
6579
|
+
});
|
|
6580
|
+
const text = lateAnswerText(asked.data.questions, answers);
|
|
6581
|
+
const carry = this.moveOnFromLostRounds(session, [member.name]);
|
|
6582
|
+
const dispatch = session.append("room/dispatch", {
|
|
6583
|
+
id: randomUUID(),
|
|
6584
|
+
targets: [member.name],
|
|
6585
|
+
targetIds: [memberId(events, member)],
|
|
6586
|
+
text,
|
|
6587
|
+
origin: "human"
|
|
6588
|
+
});
|
|
6589
|
+
await this.ctx.sessions.flush(session);
|
|
6590
|
+
this.engine.dispatch(session, member.name, carry(member.name, text), { dispatchSeq: dispatch.seq });
|
|
6591
|
+
return { ok: true };
|
|
6592
|
+
}
|
|
6081
6593
|
/** Shared backend for external MCP room tools. Not exposed as a browser Remote. */
|
|
6082
6594
|
async receiveMemberCommand(actor, command) {
|
|
6083
6595
|
try {
|
|
@@ -6643,4 +7155,4 @@ let RoomService = (() => {
|
|
|
6643
7155
|
};
|
|
6644
7156
|
})();
|
|
6645
7157
|
//#endregion
|
|
6646
|
-
export { MAIN_AGENT_MEMBER, ROOM_EVENT_TYPES, RoomService, RoomService as default, handoffText, parseQuestions };
|
|
7158
|
+
export { MAIN_AGENT_MEMBER, ROOM_EVENT_TYPES, RoomService, RoomService as default, handoffText, lateAnswerText, parseQuestions, withAttachmentPaths };
|