@khorsheed/dsh-room 0.2.3 → 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 +18 -0
- package/lib/client.js +2309 -1127
- package/lib/index.js +572 -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 +60 -0
- package/lib/types/dispatch.js +136 -1
- package/lib/types/index.d.ts +69 -1
- package/lib/types/index.js +368 -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.
|
|
1955
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.
|
|
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,
|
|
@@ -2351,7 +2465,28 @@ var DispatchEngine = class {
|
|
|
2351
2465
|
* coordinator for plan work, else the dispatch's `replyTo`), at most once
|
|
2352
2466
|
* per delivery. Public so a removal can settle the waits it breaks.
|
|
2353
2467
|
*/
|
|
2468
|
+
/** Report-gate decisions per recipient, one at a time (see reportOutcome). */
|
|
2469
|
+
gateQueues = /* @__PURE__ */ new Map();
|
|
2470
|
+
/**
|
|
2471
|
+
* Gate one arrived report for the member waiting on it. Decisions for the
|
|
2472
|
+
* same recipient run one at a time: a decision-model judgement takes a
|
|
2473
|
+
* second or two, and a later report deciding meanwhile (the 「最后一个结果」
|
|
2474
|
+
* rule) would hand over its batch without the one still being judged —
|
|
2475
|
+
* which then got held behind a round that had already answered.
|
|
2476
|
+
*/
|
|
2354
2477
|
async reportOutcome(room, dispatchSeq, deliveryId, fromId, outcome, text) {
|
|
2478
|
+
const source = room.snapshotEvents().find((event) => event.seq === dispatchSeq);
|
|
2479
|
+
const key = `${room.id} ${source?.type === "room/dispatch" ? source.data.replyTo ?? "" : ""}`;
|
|
2480
|
+
const next = (this.gateQueues.get(key) ?? Promise.resolve()).catch(() => {}).then(() => this.gateReport(room, dispatchSeq, deliveryId, fromId, outcome, text));
|
|
2481
|
+
this.gateQueues.set(key, next);
|
|
2482
|
+
next.then(() => {
|
|
2483
|
+
if (this.gateQueues.get(key) === next) this.gateQueues.delete(key);
|
|
2484
|
+
}, () => {
|
|
2485
|
+
if (this.gateQueues.get(key) === next) this.gateQueues.delete(key);
|
|
2486
|
+
});
|
|
2487
|
+
return next;
|
|
2488
|
+
}
|
|
2489
|
+
async gateReport(room, dispatchSeq, deliveryId, fromId, outcome, text) {
|
|
2355
2490
|
const events = room.snapshotEvents();
|
|
2356
2491
|
const source = events.find((event) => event.seq === dispatchSeq);
|
|
2357
2492
|
if (source?.type !== "room/dispatch" || source.data.origin !== "coordinator" && source.data.origin !== "member" || source.data.replyTo === void 0) return;
|
|
@@ -2649,6 +2784,7 @@ var DispatchEngine = class {
|
|
|
2649
2784
|
...lastAssistantMessageId(agent.session, before) === void 0 ? {} : { messageId: lastAssistantMessageId(agent.session, before) },
|
|
2650
2785
|
...reportedTo === void 0 ? {} : { reportedTo }
|
|
2651
2786
|
});
|
|
2787
|
+
floatRunningCards(room, replay(room.snapshotEvents()).members.find((entry) => entry.id === member.id)?.name ?? member.name);
|
|
2652
2788
|
await this.ctx.sessions.flush(room);
|
|
2653
2789
|
return {
|
|
2654
2790
|
state: "done",
|
|
@@ -2750,16 +2886,22 @@ var DispatchEngine = class {
|
|
|
2750
2886
|
const interrupted = result.stopReason === "completed" ? void 0 : result.stopReason === "aborted" ? "cancelled" : "failed";
|
|
2751
2887
|
const reportedTo = dispatchSeq === void 0 ? void 0 : reportRecipient(room.snapshotEvents(), dispatchSeq)?.name;
|
|
2752
2888
|
const closingMessageId = lastAssistantMessageId(this.ctx.sessions.get(run.id));
|
|
2753
|
-
|
|
2889
|
+
const asks = askOffsets(room.snapshotEvents(), runId, output);
|
|
2890
|
+
const speech = output !== "" || interrupted === void 0 ? room.append("room/speech", {
|
|
2754
2891
|
member: replay(room.snapshotEvents()).members.find((entry) => entry.id === member.id)?.name ?? member.name,
|
|
2755
2892
|
text: output,
|
|
2893
|
+
...asks.length === 0 ? {} : { asks },
|
|
2756
2894
|
childSessionId: run.id,
|
|
2757
2895
|
runId,
|
|
2758
2896
|
...closingMessageId === void 0 ? {} : { messageId: closingMessageId },
|
|
2759
2897
|
durationMs: Date.now() - startedAt,
|
|
2760
2898
|
...interrupted === void 0 ? {} : { interrupted },
|
|
2761
2899
|
...reportedTo === void 0 ? {} : { reportedTo }
|
|
2762
|
-
}) : void 0
|
|
2900
|
+
}) : void 0;
|
|
2901
|
+
if (speech !== void 0) {
|
|
2902
|
+
floatRunningCards(room, speech.data.member);
|
|
2903
|
+
await this.ctx.sessions.flush(room);
|
|
2904
|
+
}
|
|
2763
2905
|
if (result.stopReason === "completed") {
|
|
2764
2906
|
await this.ctx.sessions.flush(room);
|
|
2765
2907
|
return {
|
|
@@ -3426,6 +3568,64 @@ var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializ
|
|
|
3426
3568
|
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
3427
3569
|
done = true;
|
|
3428
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
|
+
});
|
|
3429
3629
|
var __rewriteRelativeImportExtension = function(path, preserveJsx) {
|
|
3430
3630
|
if (typeof path === "string" && /^\.\.?\//.test(path)) return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function(m, tsx, d, ext, cm) {
|
|
3431
3631
|
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : d + ext + "." + cm.toLowerCase() + "js";
|
|
@@ -3497,6 +3697,44 @@ function parseQuestions(raw) {
|
|
|
3497
3697
|
if (new Set(questions.map((question) => question.id)).size !== questions.length) return "Question ids must be unique";
|
|
3498
3698
|
return questions;
|
|
3499
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
|
+
}
|
|
3500
3738
|
function messageReceipt(receipt, kind) {
|
|
3501
3739
|
if (receipt.held !== void 0) return `pending-confirm (${receipt.held === "paused" ? "房间已暂停,恢复后自动投递" : receipt.held === "hop-limit" ? "这条消息链已到跳数上限,等房间主人决定是否继续" : "房间设置为成员间消息需房间主人确认"})`;
|
|
3502
3740
|
return kind === "notify" ? `sent #${receipt.messageId}(知会,不会回报)` : `sent #${receipt.messageId}(${receipt.member} 做完后结果会自动回报给你;不要 sleep 等待,也不要用 room_read 轮询)`;
|
|
@@ -3540,6 +3778,7 @@ function handoffText(_events, state, sessionId, from, to) {
|
|
|
3540
3778
|
let RoomService = (() => {
|
|
3541
3779
|
let _classSuper = TypertRemoteService;
|
|
3542
3780
|
let _instanceExtraInitializers = [];
|
|
3781
|
+
let _pendingCards_decorators;
|
|
3543
3782
|
let _isRoom_decorators;
|
|
3544
3783
|
let _listProviders_decorators;
|
|
3545
3784
|
let _setTaskHint_decorators;
|
|
@@ -3569,6 +3808,7 @@ let RoomService = (() => {
|
|
|
3569
3808
|
return class RoomService extends _classSuper {
|
|
3570
3809
|
static {
|
|
3571
3810
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
3811
|
+
_pendingCards_decorators = [Remote("pendingCards")];
|
|
3572
3812
|
_isRoom_decorators = [Remote("isRoom")];
|
|
3573
3813
|
_listProviders_decorators = [Remote("listProviders")];
|
|
3574
3814
|
_setTaskHint_decorators = [Remote("setTaskHint")];
|
|
@@ -3595,6 +3835,17 @@ let RoomService = (() => {
|
|
|
3595
3835
|
_setGoal_decorators = [Remote("setGoal")];
|
|
3596
3836
|
_cancel_decorators = [Remote("cancel")];
|
|
3597
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);
|
|
3598
3849
|
__esDecorate(this, null, _isRoom_decorators, {
|
|
3599
3850
|
kind: "method",
|
|
3600
3851
|
name: "isRoom",
|
|
@@ -4014,6 +4265,12 @@ let RoomService = (() => {
|
|
|
4014
4265
|
messages: []
|
|
4015
4266
|
};
|
|
4016
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
|
+
});
|
|
4017
4274
|
const scanned = /* @__PURE__ */ new WeakSet();
|
|
4018
4275
|
ctx.on("agent/pre-step", async ({ agent, step }, next) => {
|
|
4019
4276
|
const decision = await next();
|
|
@@ -4141,6 +4398,84 @@ let RoomService = (() => {
|
|
|
4141
4398
|
* @param request - session identity.
|
|
4142
4399
|
* @returns true when the session carries the marker (false for unknown sessions too).
|
|
4143
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
|
+
}
|
|
4144
4479
|
async isRoom(request) {
|
|
4145
4480
|
const session = this.ctx.sessions.get(request.sessionId);
|
|
4146
4481
|
if (session !== void 0) return isRoomLog(session.snapshotEvents());
|
|
@@ -4463,7 +4798,7 @@ let RoomService = (() => {
|
|
|
4463
4798
|
};
|
|
4464
4799
|
const body = parsed.targets.length > 0 ? parsed.text : text;
|
|
4465
4800
|
const filed = await this.fileHumanMessage(session, edit.data.id, text, task);
|
|
4466
|
-
this.moveOnFromLostRounds(session, targets);
|
|
4801
|
+
const carry = this.moveOnFromLostRounds(session, targets);
|
|
4467
4802
|
const dispatch = session.append("room/dispatch", {
|
|
4468
4803
|
id: requestId,
|
|
4469
4804
|
targets,
|
|
@@ -4473,7 +4808,7 @@ let RoomService = (() => {
|
|
|
4473
4808
|
targetIds: targets.map((name) => memberId(session.snapshotEvents(), current.members.find((member) => member.name === name)))
|
|
4474
4809
|
});
|
|
4475
4810
|
await this.ctx.sessions.flush(session);
|
|
4476
|
-
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 });
|
|
4477
4812
|
return { ok: true };
|
|
4478
4813
|
}
|
|
4479
4814
|
/**
|
|
@@ -4482,10 +4817,12 @@ let RoomService = (() => {
|
|
|
4482
4817
|
* message runs instead of queueing behind a reconciliation. Formal-goal
|
|
4483
4818
|
* attempts keep their explicit reconciliation.
|
|
4484
4819
|
*/
|
|
4485
|
-
moveOnFromLostRounds(session, targets, why =
|
|
4820
|
+
moveOnFromLostRounds(session, targets, why = MOVED_ON_TEXT) {
|
|
4486
4821
|
const events = session.snapshotEvents();
|
|
4487
4822
|
const state = replay(events);
|
|
4488
|
-
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();
|
|
4489
4826
|
for (const row of state.deliveries ?? []) {
|
|
4490
4827
|
if (row.status !== "uncertain" || row.plan?.taskId !== void 0 || !ids.has(row.memberId)) continue;
|
|
4491
4828
|
session.append("room/delivery-state", {
|
|
@@ -4503,7 +4840,31 @@ let RoomService = (() => {
|
|
|
4503
4840
|
state: "cancelled",
|
|
4504
4841
|
...this.terminalRunStateFields(session, state, run.member)
|
|
4505
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
|
+
}
|
|
4506
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
|
+
};
|
|
4507
4868
|
}
|
|
4508
4869
|
/**
|
|
4509
4870
|
* Where a member's next send belongs: the task and delivery it is working
|
|
@@ -4576,6 +4937,9 @@ let RoomService = (() => {
|
|
|
4576
4937
|
ok: false,
|
|
4577
4938
|
error: loaded.error
|
|
4578
4939
|
};
|
|
4940
|
+
if (this.ctx.sessions.get(request.sessionId) === void 0 && (loaded.state.deliveries ?? []).some((row) => row.status === "held")) this.ensureLive(request.sessionId).catch((error) => {
|
|
4941
|
+
this.ctx.logger.warn(`room: reviving held reports failed: ${String(error)}`);
|
|
4942
|
+
});
|
|
4579
4943
|
return {
|
|
4580
4944
|
ok: true,
|
|
4581
4945
|
value: {
|
|
@@ -5226,13 +5590,15 @@ let RoomService = (() => {
|
|
|
5226
5590
|
ok: false,
|
|
5227
5591
|
error: loaded.error
|
|
5228
5592
|
};
|
|
5229
|
-
|
|
5593
|
+
const attached = request.attachments ?? [];
|
|
5594
|
+
if (request.text.trim() === "" && attached.length === 0) return {
|
|
5230
5595
|
ok: false,
|
|
5231
5596
|
error: { code: "empty-text" }
|
|
5232
5597
|
};
|
|
5233
5598
|
const requestSignature = createHash("sha256").update(JSON.stringify({
|
|
5234
5599
|
text: request.text.trim(),
|
|
5235
|
-
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}`) }
|
|
5236
5602
|
})).digest("hex");
|
|
5237
5603
|
if (request.requestId !== void 0) {
|
|
5238
5604
|
if (request.requestId.trim() === "" || request.requestId.length > 128) return {
|
|
@@ -5252,7 +5618,7 @@ let RoomService = (() => {
|
|
|
5252
5618
|
await this.ctx.sessions.flush(loaded.session);
|
|
5253
5619
|
for (const target of original.targetIds ?? []) {
|
|
5254
5620
|
const member = replay(loaded.session.snapshotEvents()).members.find((member) => memberId(loaded.session.snapshotEvents(), member) === target);
|
|
5255
|
-
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), {
|
|
5256
5622
|
dispatchSeq: previous.seq,
|
|
5257
5623
|
targetId: target
|
|
5258
5624
|
});
|
|
@@ -5285,8 +5651,8 @@ let RoomService = (() => {
|
|
|
5285
5651
|
}
|
|
5286
5652
|
const said = parsed.targets.length > 0 ? parsed.text : request.text.trim();
|
|
5287
5653
|
const outside = [...said.matchAll(/@\[((?:\\.|[^\\\]\n])+)\]\((dsh-session:[^)\s]+)\)/gu)];
|
|
5288
|
-
const text = said;
|
|
5289
|
-
const prompt = outside.length === 0 ?
|
|
5654
|
+
const text = said === "" && attached.length > 0 ? "(见附件)" : said;
|
|
5655
|
+
const prompt = outside.length === 0 ? text : [
|
|
5290
5656
|
said,
|
|
5291
5657
|
"",
|
|
5292
5658
|
`(人点名了房间外的会话:${outside.map((match) => `「${match[1].replace(/\\(.)/gu, "$1")}」`).join("、")}。用 room_help 去问:session 参数原样传那个 @[…](dsh-session:…) 引用;标签是「成员(房间)」时会交给那个成员。不要转给别的成员代问。)`
|
|
@@ -5304,16 +5670,38 @@ let RoomService = (() => {
|
|
|
5304
5670
|
names: unknown
|
|
5305
5671
|
}
|
|
5306
5672
|
};
|
|
5673
|
+
const admitted = await this.admitAttachments(request.sessionId, attached);
|
|
5674
|
+
if (!admitted.ok) return {
|
|
5675
|
+
ok: false,
|
|
5676
|
+
error: admitted.error
|
|
5677
|
+
};
|
|
5307
5678
|
const human = createUserMessage({
|
|
5308
|
-
content: [{
|
|
5679
|
+
content: [...admitted.blocks, ...request.text.trim() === "" ? [] : [{
|
|
5309
5680
|
type: "text",
|
|
5310
5681
|
text: request.text.trim()
|
|
5311
|
-
}],
|
|
5682
|
+
}]],
|
|
5312
5683
|
source: { kind: "user" }
|
|
5313
5684
|
});
|
|
5314
|
-
|
|
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);
|
|
5315
5703
|
const filed = await this.fileHumanMessage(loaded.session, human.id, request.text.trim(), request.task ?? this.consumeTaskHint(request.sessionId));
|
|
5316
|
-
this.moveOnFromLostRounds(loaded.session, targets);
|
|
5704
|
+
const carry = this.moveOnFromLostRounds(loaded.session, targets);
|
|
5317
5705
|
const dispatch = loaded.session.append("room/dispatch", {
|
|
5318
5706
|
id: request.requestId ?? randomUUID(),
|
|
5319
5707
|
requestSignature,
|
|
@@ -5321,10 +5709,11 @@ let RoomService = (() => {
|
|
|
5321
5709
|
text,
|
|
5322
5710
|
origin: "human",
|
|
5323
5711
|
task: filed.task,
|
|
5712
|
+
...admitted.records.length === 0 ? {} : { attachments: admitted.records },
|
|
5324
5713
|
targetIds: targets.map((name) => memberId(loaded.session.snapshotEvents(), loaded.state.members.find((member) => member.name === name)))
|
|
5325
5714
|
});
|
|
5326
5715
|
await this.ctx.sessions.flush(loaded.session);
|
|
5327
|
-
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 });
|
|
5328
5717
|
return {
|
|
5329
5718
|
ok: true,
|
|
5330
5719
|
value: {
|
|
@@ -5337,6 +5726,95 @@ let RoomService = (() => {
|
|
|
5337
5726
|
};
|
|
5338
5727
|
}
|
|
5339
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
|
+
/**
|
|
5340
5818
|
* Send one message from a member to another (host-only — the `room_message`
|
|
5341
5819
|
* tool and the bridge's `member_message`/`room_message` land here; the
|
|
5342
5820
|
* human's path is postMessage). Any member may message any other: the
|
|
@@ -5538,7 +6016,7 @@ let RoomService = (() => {
|
|
|
5538
6016
|
header: "求助",
|
|
5539
6017
|
question: `${asker.name} 想向会话「${target.title}」求助:${text.length > 80 ? `${text.slice(0, 80)}…` : text}`,
|
|
5540
6018
|
options: [{ label: "允许" }, { label: "不允许" }]
|
|
5541
|
-
}], 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.`;
|
|
5542
6020
|
}
|
|
5543
6021
|
const help = {
|
|
5544
6022
|
id: randomUUID().slice(0, 8),
|
|
@@ -5583,7 +6061,7 @@ let RoomService = (() => {
|
|
|
5583
6061
|
roomTitle: this.roomTitle(room),
|
|
5584
6062
|
from: help.fromName
|
|
5585
6063
|
};
|
|
5586
|
-
this.moveOnFromLostRounds(other.session, [member.name], "另一个协作间来求助;被重启打断的这一轮按没做完处理");
|
|
6064
|
+
const carry = this.moveOnFromLostRounds(other.session, [member.name], "另一个协作间来求助;被重启打断的这一轮按没做完处理");
|
|
5587
6065
|
const sent = sendOrHold(other.session, {
|
|
5588
6066
|
to: member,
|
|
5589
6067
|
text,
|
|
@@ -5609,7 +6087,7 @@ let RoomService = (() => {
|
|
|
5609
6087
|
state: sent.delivered ? "running" : "queued"
|
|
5610
6088
|
});
|
|
5611
6089
|
await this.ctx.sessions.flush(room);
|
|
5612
|
-
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 });
|
|
5613
6091
|
const who = `${member.name} in the room "${help.target.title}"`;
|
|
5614
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.`;
|
|
5615
6093
|
}
|
|
@@ -5993,7 +6471,7 @@ let RoomService = (() => {
|
|
|
5993
6471
|
* @param timeoutMs - how long the call waits before it gives up.
|
|
5994
6472
|
* @returns the answers, or undefined when nobody answered.
|
|
5995
6473
|
*/
|
|
5996
|
-
async askQuestions(session, member, questions, timeoutMs) {
|
|
6474
|
+
async askQuestions(session, member, questions, timeoutMs, consent = false) {
|
|
5997
6475
|
const id = randomUUID();
|
|
5998
6476
|
const answered = new Promise((resolve) => {
|
|
5999
6477
|
this.questionWaiters.set(id, resolve);
|
|
@@ -6001,11 +6479,17 @@ let RoomService = (() => {
|
|
|
6001
6479
|
resolve(void 0);
|
|
6002
6480
|
}, timeoutMs).unref?.();
|
|
6003
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);
|
|
6004
6485
|
session.append("room/question", {
|
|
6005
6486
|
id,
|
|
6006
6487
|
member: member.name,
|
|
6007
6488
|
memberId: memberId(session.snapshotEvents(), member),
|
|
6008
|
-
questions
|
|
6489
|
+
questions,
|
|
6490
|
+
...consent ? { consent: true } : {},
|
|
6491
|
+
...run?.runId === void 0 ? {} : { runId: run.runId },
|
|
6492
|
+
...before.trim() === "" ? {} : { before }
|
|
6009
6493
|
});
|
|
6010
6494
|
await this.ctx.sessions.flush(session);
|
|
6011
6495
|
const answers = await answered;
|
|
@@ -6032,10 +6516,7 @@ let RoomService = (() => {
|
|
|
6032
6516
|
};
|
|
6033
6517
|
const waiter = this.questionWaiters.get(request.id);
|
|
6034
6518
|
const pending = loaded.state.questions?.find((entry) => entry.id === request.id);
|
|
6035
|
-
if (waiter === void 0 || pending === void 0) return
|
|
6036
|
-
ok: false,
|
|
6037
|
-
error: { code: "question-not-pending" }
|
|
6038
|
-
};
|
|
6519
|
+
if (waiter === void 0 || pending === void 0) return this.answerExpiredQuestion(loaded.session, request);
|
|
6039
6520
|
const answers = pending.questions.map((question) => {
|
|
6040
6521
|
const given = request.answers.find((answer) => answer.id === question.id);
|
|
6041
6522
|
const custom = given?.custom?.trim();
|
|
@@ -6054,6 +6535,61 @@ let RoomService = (() => {
|
|
|
6054
6535
|
waiter(answers);
|
|
6055
6536
|
return { ok: true };
|
|
6056
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
|
+
}
|
|
6057
6593
|
/** Shared backend for external MCP room tools. Not exposed as a browser Remote. */
|
|
6058
6594
|
async receiveMemberCommand(actor, command) {
|
|
6059
6595
|
try {
|
|
@@ -6619,4 +7155,4 @@ let RoomService = (() => {
|
|
|
6619
7155
|
};
|
|
6620
7156
|
})();
|
|
6621
7157
|
//#endregion
|
|
6622
|
-
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 };
|