@agentconnect.md/daemon 1.0.0-rc.123 → 1.0.0-rc.124
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +336 -49
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -12855,7 +12855,11 @@ const AuthOk = object({
|
|
|
12855
12855
|
* agent/*, and event/session. Its canonical string form is
|
|
12856
12856
|
* `${platform}:${channel}:${thread ?? "-"}`.
|
|
12857
12857
|
*/
|
|
12858
|
-
const Platform = _enum([
|
|
12858
|
+
const Platform = _enum([
|
|
12859
|
+
"slack",
|
|
12860
|
+
"telegram",
|
|
12861
|
+
"webchat"
|
|
12862
|
+
]);
|
|
12859
12863
|
const SessionKey = object({
|
|
12860
12864
|
platform: Platform,
|
|
12861
12865
|
channel: string(),
|
|
@@ -13509,6 +13513,99 @@ const FactsRuntimeProfile = object({
|
|
|
13509
13513
|
acpProtocolVersion: number().int().optional(),
|
|
13510
13514
|
toolCalling: boolean()
|
|
13511
13515
|
});
|
|
13516
|
+
/**
|
|
13517
|
+
* Full snapshot of the daemon's installed runtimes — D→C EVT (protocol §7.3a),
|
|
13518
|
+
* emitted on each register and again once the background probe sweep completes
|
|
13519
|
+
* (with the learned `models[]`). REPLACE semantics: the CP reconciles its
|
|
13520
|
+
* stored runtime list for the daemon to exactly `runtimes[]`, pruning runtimes
|
|
13521
|
+
* that are no longer installed. Idempotent (latest-wins). Supersedes the
|
|
13522
|
+
* per-runtime `facts/runtime-profile` upsert.
|
|
13523
|
+
*/
|
|
13524
|
+
const DaemonRuntimes = object({ runtimes: array(FactsRuntimeProfile) });
|
|
13525
|
+
/** Non-secret config update — C→D EVT, protocol §8.3 / frame #26. */
|
|
13526
|
+
const ConfigPush = object({ keys: record(string(), unknown()) });
|
|
13527
|
+
/** Fleet: drain+exit, supervisor restarts — C→D REQ, protocol §8.3 / frame #27. */
|
|
13528
|
+
const DaemonRestart = object({
|
|
13529
|
+
reason: string(),
|
|
13530
|
+
drainFirst: boolean().default(true)
|
|
13531
|
+
});
|
|
13532
|
+
/** Fleet: drain+exit for version bump — C→D REQ, protocol §8.3 / frame #28. */
|
|
13533
|
+
const DaemonUpgrade = object({
|
|
13534
|
+
targetVersion: string(),
|
|
13535
|
+
drainFirst: boolean().default(true)
|
|
13536
|
+
});
|
|
13537
|
+
/** Reply to a fleet restart/upgrade REQ (protocol §8.3). */
|
|
13538
|
+
const DaemonControlAck = object({
|
|
13539
|
+
accepted: boolean(),
|
|
13540
|
+
willDrainUntil: string().datetime().optional()
|
|
13541
|
+
});
|
|
13542
|
+
/**
|
|
13543
|
+
* Generic acknowledgement for REQs whose reply is just `ack` in the frame index
|
|
13544
|
+
* (cron/upsert, cron/remove, agent/stop). Kept permissive but typed.
|
|
13545
|
+
*/
|
|
13546
|
+
const Ack = object({
|
|
13547
|
+
ok: boolean(),
|
|
13548
|
+
reason: string().optional()
|
|
13549
|
+
});
|
|
13550
|
+
const ErrorFrame = object({
|
|
13551
|
+
code: _enum([
|
|
13552
|
+
"UNKNOWN_FRAME",
|
|
13553
|
+
"FRAME_TOO_LARGE",
|
|
13554
|
+
"PROTOCOL_STATE",
|
|
13555
|
+
"BAD_PAYLOAD",
|
|
13556
|
+
"AUTH_FAILED",
|
|
13557
|
+
"ATTESTATION_INVALID",
|
|
13558
|
+
"STALE_EPOCH",
|
|
13559
|
+
"STALE_LAUNCH",
|
|
13560
|
+
"NO_SESSION",
|
|
13561
|
+
"SCOPE_DENIED",
|
|
13562
|
+
"LEASE_EXPIRED",
|
|
13563
|
+
"LEASE_DENIED",
|
|
13564
|
+
"RATE_LIMITED",
|
|
13565
|
+
"INTERNAL"
|
|
13566
|
+
]),
|
|
13567
|
+
message: string(),
|
|
13568
|
+
retryable: boolean(),
|
|
13569
|
+
details: record(string(), unknown()).optional()
|
|
13570
|
+
});
|
|
13571
|
+
//#endregion
|
|
13572
|
+
//#region ../protocol/dist/frames/webchat.js
|
|
13573
|
+
const WebchatMessage = object({
|
|
13574
|
+
conversationId: string().uuid(),
|
|
13575
|
+
agentId: string().uuid(),
|
|
13576
|
+
text: string(),
|
|
13577
|
+
user: string().optional()
|
|
13578
|
+
});
|
|
13579
|
+
const WebchatAck = object({
|
|
13580
|
+
accepted: boolean(),
|
|
13581
|
+
turnId: string().uuid(),
|
|
13582
|
+
reason: _enum([
|
|
13583
|
+
"queued",
|
|
13584
|
+
"no_agent",
|
|
13585
|
+
"busy"
|
|
13586
|
+
]).optional()
|
|
13587
|
+
});
|
|
13588
|
+
const WebchatEvent = discriminatedUnion("kind", [
|
|
13589
|
+
object({
|
|
13590
|
+
kind: literal("message"),
|
|
13591
|
+
text: string()
|
|
13592
|
+
}),
|
|
13593
|
+
object({
|
|
13594
|
+
kind: literal("thinking"),
|
|
13595
|
+
text: string()
|
|
13596
|
+
}),
|
|
13597
|
+
object({
|
|
13598
|
+
kind: literal("tool_call"),
|
|
13599
|
+
toolCallId: string(),
|
|
13600
|
+
title: string(),
|
|
13601
|
+
status: string()
|
|
13602
|
+
}),
|
|
13603
|
+
object({
|
|
13604
|
+
kind: literal("tool_update"),
|
|
13605
|
+
toolCallId: string(),
|
|
13606
|
+
status: string()
|
|
13607
|
+
})
|
|
13608
|
+
]);
|
|
13512
13609
|
//#endregion
|
|
13513
13610
|
//#region ../protocol/dist/frame.js
|
|
13514
13611
|
/**
|
|
@@ -13535,6 +13632,24 @@ const FRAME_SCHEMAS = {
|
|
|
13535
13632
|
"agent/remove": AgentRemove,
|
|
13536
13633
|
"agent/activity": AgentActivity,
|
|
13537
13634
|
"agent/scope-denied": AgentScopeDenied,
|
|
13635
|
+
"webchat/message": WebchatMessage,
|
|
13636
|
+
"webchat/ack": WebchatAck,
|
|
13637
|
+
"webchat/output": object({
|
|
13638
|
+
conversationId: string().uuid(),
|
|
13639
|
+
turnId: string().uuid(),
|
|
13640
|
+
index: number().int(),
|
|
13641
|
+
event: WebchatEvent
|
|
13642
|
+
}),
|
|
13643
|
+
"webchat/done": object({
|
|
13644
|
+
conversationId: string().uuid(),
|
|
13645
|
+
turnId: string().uuid(),
|
|
13646
|
+
stopReason: string().optional(),
|
|
13647
|
+
usage: object({
|
|
13648
|
+
used: number().int().optional(),
|
|
13649
|
+
cost: number().optional()
|
|
13650
|
+
}).optional()
|
|
13651
|
+
}),
|
|
13652
|
+
"webchat/close": object({ conversationId: string().uuid() }),
|
|
13538
13653
|
"route/assign": RouteAssign,
|
|
13539
13654
|
"route/assign/ack": RouteAssignAck,
|
|
13540
13655
|
"route/update": RouteUpdate,
|
|
@@ -13556,7 +13671,7 @@ const FRAME_SCHEMAS = {
|
|
|
13556
13671
|
"event/session": EventSession,
|
|
13557
13672
|
"usage/report": UsageReport,
|
|
13558
13673
|
"facts/runtime-profile": FactsRuntimeProfile,
|
|
13559
|
-
"facts/daemon-runtimes":
|
|
13674
|
+
"facts/daemon-runtimes": DaemonRuntimes,
|
|
13560
13675
|
"session/list": SessionListReq,
|
|
13561
13676
|
"session/list/page": SessionListPage,
|
|
13562
13677
|
"session/history": SessionHistoryReq,
|
|
@@ -13571,44 +13686,12 @@ const FRAME_SCHEMAS = {
|
|
|
13571
13686
|
"workspace/gitstatus/result": WorkspaceGitStatus,
|
|
13572
13687
|
"workspace/gitpull": WorkspaceGitPullReq,
|
|
13573
13688
|
"workspace/gitpull/result": WorkspaceGitPullResult,
|
|
13574
|
-
"config/push":
|
|
13575
|
-
"daemon/restart":
|
|
13576
|
-
|
|
13577
|
-
|
|
13578
|
-
|
|
13579
|
-
|
|
13580
|
-
targetVersion: string(),
|
|
13581
|
-
drainFirst: boolean().default(true)
|
|
13582
|
-
}),
|
|
13583
|
-
"daemon/control/ack": object({
|
|
13584
|
-
accepted: boolean(),
|
|
13585
|
-
willDrainUntil: string().datetime().optional()
|
|
13586
|
-
}),
|
|
13587
|
-
ack: object({
|
|
13588
|
-
ok: boolean(),
|
|
13589
|
-
reason: string().optional()
|
|
13590
|
-
}),
|
|
13591
|
-
error: object({
|
|
13592
|
-
code: _enum([
|
|
13593
|
-
"UNKNOWN_FRAME",
|
|
13594
|
-
"FRAME_TOO_LARGE",
|
|
13595
|
-
"PROTOCOL_STATE",
|
|
13596
|
-
"BAD_PAYLOAD",
|
|
13597
|
-
"AUTH_FAILED",
|
|
13598
|
-
"ATTESTATION_INVALID",
|
|
13599
|
-
"STALE_EPOCH",
|
|
13600
|
-
"STALE_LAUNCH",
|
|
13601
|
-
"NO_SESSION",
|
|
13602
|
-
"SCOPE_DENIED",
|
|
13603
|
-
"LEASE_EXPIRED",
|
|
13604
|
-
"LEASE_DENIED",
|
|
13605
|
-
"RATE_LIMITED",
|
|
13606
|
-
"INTERNAL"
|
|
13607
|
-
]),
|
|
13608
|
-
message: string(),
|
|
13609
|
-
retryable: boolean(),
|
|
13610
|
-
details: record(string(), unknown()).optional()
|
|
13611
|
-
})
|
|
13689
|
+
"config/push": ConfigPush,
|
|
13690
|
+
"daemon/restart": DaemonRestart,
|
|
13691
|
+
"daemon/upgrade": DaemonUpgrade,
|
|
13692
|
+
"daemon/control/ack": DaemonControlAck,
|
|
13693
|
+
ack: Ack,
|
|
13694
|
+
error: ErrorFrame
|
|
13612
13695
|
};
|
|
13613
13696
|
Object.keys(FRAME_SCHEMAS);
|
|
13614
13697
|
/**
|
|
@@ -13638,6 +13721,11 @@ discriminatedUnion("type", [
|
|
|
13638
13721
|
frame("agent/remove", FRAME_SCHEMAS["agent/remove"]),
|
|
13639
13722
|
frame("agent/activity", FRAME_SCHEMAS["agent/activity"]),
|
|
13640
13723
|
frame("agent/scope-denied", FRAME_SCHEMAS["agent/scope-denied"]),
|
|
13724
|
+
frame("webchat/message", FRAME_SCHEMAS["webchat/message"]),
|
|
13725
|
+
frame("webchat/ack", FRAME_SCHEMAS["webchat/ack"]),
|
|
13726
|
+
frame("webchat/output", FRAME_SCHEMAS["webchat/output"]),
|
|
13727
|
+
frame("webchat/done", FRAME_SCHEMAS["webchat/done"]),
|
|
13728
|
+
frame("webchat/close", FRAME_SCHEMAS["webchat/close"]),
|
|
13641
13729
|
frame("route/assign", FRAME_SCHEMAS["route/assign"]),
|
|
13642
13730
|
frame("route/assign/ack", FRAME_SCHEMAS["route/assign/ack"]),
|
|
13643
13731
|
frame("route/update", FRAME_SCHEMAS["route/update"]),
|
|
@@ -24997,8 +25085,18 @@ const pickRule = (r, via) => ({
|
|
|
24997
25085
|
* (2) thread affinity (highest after explicit @; bypasses the kind filter, gated on
|
|
24998
25086
|
* reachable-bot count) → (3) CP per-sessionKey override → (4/5) kind precedence
|
|
24999
25087
|
* mention > dm > keyword > auto within the chosen layer.
|
|
25088
|
+
*
|
|
25089
|
+
* `explicitAgentId` short-circuits the whole ladder: a webchat turn names its target
|
|
25090
|
+
* agent (`webchat/message.agentId`), so there is nothing to arbitrate — it routes
|
|
25091
|
+
* directly to that agent (integrationId '' — webchat's data plane is the control WS,
|
|
25092
|
+
* not a platform integration). Null if that agentId isn't a servable rule here.
|
|
25000
25093
|
*/
|
|
25001
|
-
function routeRules(msg, rules, threadOwner) {
|
|
25094
|
+
function routeRules(msg, rules, threadOwner, explicitAgentId) {
|
|
25095
|
+
if (explicitAgentId !== void 0) return {
|
|
25096
|
+
agentId: explicitAgentId,
|
|
25097
|
+
integrationId: "",
|
|
25098
|
+
via: "mention"
|
|
25099
|
+
};
|
|
25002
25100
|
if (msg.sender.isBot) return null;
|
|
25003
25101
|
const authz = (r) => !r.allowedUserIds || r.allowedUserIds.length === 0 || r.allowedUserIds.includes(msg.sender.id);
|
|
25004
25102
|
const scopeCandidates = rules.filter((r) => scopeMatches(r, msg) && authz(r));
|
|
@@ -83053,6 +83151,22 @@ var CpClient = class {
|
|
|
83053
83151
|
if (this.state !== "READY" && this.state !== "DRAINING") return;
|
|
83054
83152
|
this.transport?.send(encode(buildEnvelope("cron/report", report)));
|
|
83055
83153
|
}
|
|
83154
|
+
/**
|
|
83155
|
+
* Emit one chunk of a webchat turn's reply stream (D→C `webchat/output` EVT).
|
|
83156
|
+
* Ordered per-connection (TCP); `output.index` is the per-turn assembly counter.
|
|
83157
|
+
* No-op unless READY/DRAINING — a dropped chunk is lost (unlike latest-wins
|
|
83158
|
+
* telemetry), so the daemon only emits while connected. Content is inline, bounded
|
|
83159
|
+
* by the 256 KiB frame cap; the caller chunks any oversize single event.
|
|
83160
|
+
*/
|
|
83161
|
+
emitWebchatOutput(output) {
|
|
83162
|
+
if (this.state !== "READY" && this.state !== "DRAINING") return;
|
|
83163
|
+
this.transport?.send(encode(buildEnvelope("webchat/output", output)));
|
|
83164
|
+
}
|
|
83165
|
+
/** Emit a webchat turn's completion (D→C `webchat/done` EVT). No-op unless READY/DRAINING. */
|
|
83166
|
+
emitWebchatDone(done) {
|
|
83167
|
+
if (this.state !== "READY" && this.state !== "DRAINING") return;
|
|
83168
|
+
this.transport?.send(encode(buildEnvelope("webchat/done", done)));
|
|
83169
|
+
}
|
|
83056
83170
|
request(type, payload) {
|
|
83057
83171
|
const frame = buildEnvelope(type, payload);
|
|
83058
83172
|
return this.correlator.request(frame, (e) => this.transport.send(e));
|
|
@@ -83229,6 +83343,20 @@ var CpClient = class {
|
|
|
83229
83343
|
case "workspace/gitpull":
|
|
83230
83344
|
this.deps.workspaceGit.pull(frame.payload.agentId).then((result) => this.reply(frame, "workspace/gitpull/result", result)).catch((err) => this.workspaceError(frame.id, "workspace/gitpull", err));
|
|
83231
83345
|
return;
|
|
83346
|
+
case "webchat/message":
|
|
83347
|
+
if (!this.deps.onWebchatMessage) {
|
|
83348
|
+
this.sendError(frame.id, "INTERNAL", "webchat not supported", false);
|
|
83349
|
+
return;
|
|
83350
|
+
}
|
|
83351
|
+
try {
|
|
83352
|
+
this.reply(frame, "webchat/ack", this.deps.onWebchatMessage(frame.payload));
|
|
83353
|
+
} catch (err) {
|
|
83354
|
+
this.sendError(frame.id, "INTERNAL", `webchat/message failed: ${err.message}`, false);
|
|
83355
|
+
}
|
|
83356
|
+
return;
|
|
83357
|
+
case "webchat/close":
|
|
83358
|
+
this.deps.onWebchatClose?.(frame.payload);
|
|
83359
|
+
return;
|
|
83232
83360
|
default:
|
|
83233
83361
|
this.deps.log.debug(`cp: ignoring ${frame.type}`);
|
|
83234
83362
|
return;
|
|
@@ -84111,6 +84239,28 @@ function formatErr(err) {
|
|
|
84111
84239
|
return e?.stack ?? String(err);
|
|
84112
84240
|
}
|
|
84113
84241
|
const MAX_QUEUED_PER_SESSION = 10;
|
|
84242
|
+
const WEBCHAT_CHUNK_BYTES = 32 * 1024;
|
|
84243
|
+
/** Split `text` into pieces whose UTF-8 byte length each stays under WEBCHAT_CHUNK_BYTES
|
|
84244
|
+
* so no single webchat/output frame exceeds the 256 KiB cap. Splits on byte budget by
|
|
84245
|
+
* character (never mid-code-point); short text returns a single piece. */
|
|
84246
|
+
function chunkText(text) {
|
|
84247
|
+
if (Buffer.byteLength(text) <= WEBCHAT_CHUNK_BYTES) return [text];
|
|
84248
|
+
const out = [];
|
|
84249
|
+
let buf = "";
|
|
84250
|
+
let bytes = 0;
|
|
84251
|
+
for (const ch of text) {
|
|
84252
|
+
const w = Buffer.byteLength(ch);
|
|
84253
|
+
if (bytes + w > WEBCHAT_CHUNK_BYTES && buf) {
|
|
84254
|
+
out.push(buf);
|
|
84255
|
+
buf = "";
|
|
84256
|
+
bytes = 0;
|
|
84257
|
+
}
|
|
84258
|
+
buf += ch;
|
|
84259
|
+
bytes += w;
|
|
84260
|
+
}
|
|
84261
|
+
if (buf) out.push(buf);
|
|
84262
|
+
return out;
|
|
84263
|
+
}
|
|
84114
84264
|
const IDLE_FLUSH_MS = 2e3;
|
|
84115
84265
|
/** Build the wire SessionKey (protocol §5) for a pending turn — what `drain/done`
|
|
84116
84266
|
* reports as released so the CP may reassign it. Uses the real `thread` (absent for
|
|
@@ -84590,6 +84740,61 @@ var Daemon = class Daemon {
|
|
|
84590
84740
|
this.log.info(`routing: ch=${msg.channel} → agent "${result.agentId}" (integration ${result.integrationId})`);
|
|
84591
84741
|
this.dispatch(result.agentId, msg, result.integrationId).catch((err) => this.log.error(`dispatch failed for agent "${result.agentId}": ${formatErr(err)}`));
|
|
84592
84742
|
}
|
|
84743
|
+
/**
|
|
84744
|
+
* Handle an inbound webchat turn (C→D `webchat/message`). webchat is the one
|
|
84745
|
+
* integration whose data plane is the daemon↔CP control WS itself — the far end is
|
|
84746
|
+
* the CP/browser, so the body arrives inline and the reply streams back over the
|
|
84747
|
+
* same WS. The message names its target agent explicitly (no trigger matching), so
|
|
84748
|
+
* this normalizes the turn and hands it to the same dispatch path as any session:
|
|
84749
|
+
* the turn is a REAL session (recorded to the transcript, visible in session/list),
|
|
84750
|
+
* only its transport differs. Returns the ack: accepted + a fresh turnId that
|
|
84751
|
+
* correlates the streamed webchat/output, or accepted:false / no_agent when the
|
|
84752
|
+
* named agent isn't runnable here.
|
|
84753
|
+
*/
|
|
84754
|
+
handleWebchatMessage(m) {
|
|
84755
|
+
const turnId = randomUUID();
|
|
84756
|
+
const result = routeRules({
|
|
84757
|
+
platform: "webchat",
|
|
84758
|
+
channel: m.conversationId
|
|
84759
|
+
}, this.mergedRules(), () => null, m.agentId);
|
|
84760
|
+
if (!result || !this.agents.has(result.agentId)) {
|
|
84761
|
+
this.log.warn(`webchat: no agent "${m.agentId}" on this daemon — rejecting turn`);
|
|
84762
|
+
return {
|
|
84763
|
+
accepted: false,
|
|
84764
|
+
turnId,
|
|
84765
|
+
reason: "no_agent"
|
|
84766
|
+
};
|
|
84767
|
+
}
|
|
84768
|
+
const msg = {
|
|
84769
|
+
msgId: `webchat:${m.conversationId}`,
|
|
84770
|
+
traceId: turnId,
|
|
84771
|
+
source: "user",
|
|
84772
|
+
platform: "webchat",
|
|
84773
|
+
channel: m.conversationId,
|
|
84774
|
+
sender: {
|
|
84775
|
+
id: m.user ?? "webchat",
|
|
84776
|
+
isBot: false
|
|
84777
|
+
},
|
|
84778
|
+
text: m.text,
|
|
84779
|
+
mentionedBots: [],
|
|
84780
|
+
isDm: true,
|
|
84781
|
+
trigger: "dm"
|
|
84782
|
+
};
|
|
84783
|
+
this.dispatch(result.agentId, msg, void 0, {
|
|
84784
|
+
conversationId: m.conversationId,
|
|
84785
|
+
turnId
|
|
84786
|
+
}).catch((err) => this.log.error(`webchat dispatch failed for agent "${result.agentId}": ${formatErr(err)}`));
|
|
84787
|
+
return {
|
|
84788
|
+
accepted: true,
|
|
84789
|
+
turnId
|
|
84790
|
+
};
|
|
84791
|
+
}
|
|
84792
|
+
/** Handle a webchat conversation close (C→D `webchat/close`). No live resources are
|
|
84793
|
+
* bound per-conversation (the session TTL-closes like any other), so this is
|
|
84794
|
+
* currently just observability — the in-flight turn, if any, runs to completion. */
|
|
84795
|
+
handleWebchatClose(m) {
|
|
84796
|
+
this.log.debug(`webchat: conversation ${m.conversationId} closed by client`);
|
|
84797
|
+
}
|
|
84593
84798
|
/** All of our own connections' bot identities (both `U…` user ids and `B…`
|
|
84594
84799
|
* bot_ids), so we recognize self-echoes regardless of which field Slack sends. */
|
|
84595
84800
|
ownBotIdentities() {
|
|
@@ -84733,14 +84938,14 @@ var Daemon = class Daemon {
|
|
|
84733
84938
|
for (const cpRule of this.cpRouting?.effectiveRules() ?? []) if (!this.resolveCpAgent(cpRule.agentId)) out.add(cpRule.agentId);
|
|
84734
84939
|
return [...out];
|
|
84735
84940
|
}
|
|
84736
|
-
async dispatch(agentId, msg, integrationId) {
|
|
84941
|
+
async dispatch(agentId, msg, integrationId, webchat) {
|
|
84737
84942
|
if (this.draining || this.drainingAgents.has(agentId)) {
|
|
84738
84943
|
this.log.debug(`dispatch: skipped for agent "${agentId}" (draining)`);
|
|
84739
84944
|
return null;
|
|
84740
84945
|
}
|
|
84741
84946
|
const conv = new OutputConverger(this.agents.get(agentId).output.mode);
|
|
84742
84947
|
const rec = new TranscriptRecorder();
|
|
84743
|
-
const replyConn = msg.headless ? void 0 : this.replyConnFor(agentId, integrationId);
|
|
84948
|
+
const replyConn = msg.headless || webchat ? void 0 : this.replyConnFor(agentId, integrationId);
|
|
84744
84949
|
const wasRunning = this.hostStarts.has(agentId);
|
|
84745
84950
|
const statusThread = msg.thread ?? msg.msgId;
|
|
84746
84951
|
const key = sessionKey(msg.platform, msg.channel, statusThread, agentId);
|
|
@@ -84767,13 +84972,18 @@ var Daemon = class Daemon {
|
|
|
84767
84972
|
conn: replyConn,
|
|
84768
84973
|
applyChain: Promise.resolve(),
|
|
84769
84974
|
done,
|
|
84770
|
-
resolveDone
|
|
84975
|
+
resolveDone,
|
|
84976
|
+
...webchat ? { webchat: {
|
|
84977
|
+
...webchat,
|
|
84978
|
+
index: 0,
|
|
84979
|
+
replyText: ""
|
|
84980
|
+
} } : {}
|
|
84771
84981
|
};
|
|
84772
84982
|
this.pending.set(sessionId, p);
|
|
84773
84983
|
try {
|
|
84774
84984
|
const host = await this.ensureHostAsync(agentId);
|
|
84775
84985
|
if (!wasRunning) replyConn?.setStatus(msg.channel, statusThread, "is thinking…");
|
|
84776
|
-
const { usage } = await host.prompt(sessionId, blocks);
|
|
84986
|
+
const { stopReason, usage } = await host.prompt(sessionId, blocks);
|
|
84777
84987
|
if (usage) this.store.setTokenUsage(key, {
|
|
84778
84988
|
totalTokens: usage.totalTokens,
|
|
84779
84989
|
inputTokens: usage.inputTokens,
|
|
@@ -84796,8 +85006,25 @@ var Daemon = class Daemon {
|
|
|
84796
85006
|
this.log.debug(`usage report emit failed (session ${sessionId}): ${err.message}`);
|
|
84797
85007
|
}
|
|
84798
85008
|
this.clearIdle(p);
|
|
84799
|
-
|
|
84800
|
-
|
|
85009
|
+
if (p.webchat) {
|
|
85010
|
+
if (p.webchat.replyText.trim()) this.store.appendTranscript({
|
|
85011
|
+
channel: p.channel,
|
|
85012
|
+
thread: statusThread,
|
|
85013
|
+
ts: String(Date.now()),
|
|
85014
|
+
sender: agentId,
|
|
85015
|
+
kind: "text",
|
|
85016
|
+
text: p.webchat.replyText
|
|
85017
|
+
});
|
|
85018
|
+
this.cpClient?.emitWebchatDone({
|
|
85019
|
+
conversationId: p.webchat.conversationId,
|
|
85020
|
+
turnId: p.webchat.turnId,
|
|
85021
|
+
...stopReason ? { stopReason } : {},
|
|
85022
|
+
...usage?.totalTokens !== void 0 ? { usage: { used: usage.totalTokens } } : {}
|
|
85023
|
+
});
|
|
85024
|
+
} else {
|
|
85025
|
+
const link = this.cfg.webAppUrl ? `${this.cfg.webAppUrl.replace(/\/$/, "")}/sessions/${sessionId}` : void 0;
|
|
85026
|
+
for (const action of conv.onFinal(link)) this.enqueueApply(p, action);
|
|
85027
|
+
}
|
|
84801
85028
|
for (const ev of rec.onFinal()) this.recordEvent(agentId, msg.channel, statusThread, ev);
|
|
84802
85029
|
await p.applyChain;
|
|
84803
85030
|
} finally {
|
|
@@ -84918,10 +85145,68 @@ var Daemon = class Daemon {
|
|
|
84918
85145
|
costCurrency: update.cost?.currency ?? void 0
|
|
84919
85146
|
});
|
|
84920
85147
|
if (update?.sessionUpdate === "session_info_update" && update.title !== void 0) this.store.setSessionTitle(p.sessionKey, update.title);
|
|
84921
|
-
|
|
84922
|
-
|
|
85148
|
+
if (p.webchat) this.emitWebchatUpdate(p, update);
|
|
85149
|
+
else {
|
|
85150
|
+
for (const action of p.conv.onUpdate(update)) this.enqueueApply(p, action);
|
|
85151
|
+
this.armIdle(p);
|
|
85152
|
+
}
|
|
84923
85153
|
for (const ev of p.rec.onUpdate(update)) this.recordEvent(p.agentId, p.channel, p.statusThread, ev);
|
|
84924
85154
|
}
|
|
85155
|
+
/**
|
|
85156
|
+
* Map one ACP SessionUpdate to a WebchatEvent and stream it to the CP as a
|
|
85157
|
+
* `webchat/output` EVT (webchat's "send"). Only the streamable chunk kinds map;
|
|
85158
|
+
* usage/plan/session_info are handled elsewhere or dropped. A single event whose
|
|
85159
|
+
* inline text would blow the 256 KiB frame cap is split across multiple chunks,
|
|
85160
|
+
* each with its own monotonic `index`.
|
|
85161
|
+
*/
|
|
85162
|
+
emitWebchatUpdate(p, update) {
|
|
85163
|
+
const wc = p.webchat;
|
|
85164
|
+
const emit = (event) => {
|
|
85165
|
+
this.cpClient?.emitWebchatOutput({
|
|
85166
|
+
conversationId: wc.conversationId,
|
|
85167
|
+
turnId: wc.turnId,
|
|
85168
|
+
index: wc.index++,
|
|
85169
|
+
event
|
|
85170
|
+
});
|
|
85171
|
+
};
|
|
85172
|
+
switch (update?.sessionUpdate) {
|
|
85173
|
+
case "agent_message_chunk": {
|
|
85174
|
+
const text = update.content?.type === "text" ? update.content.text ?? "" : "";
|
|
85175
|
+
if (text) {
|
|
85176
|
+
wc.replyText += text;
|
|
85177
|
+
for (const t of chunkText(text)) emit({
|
|
85178
|
+
kind: "message",
|
|
85179
|
+
text: t
|
|
85180
|
+
});
|
|
85181
|
+
}
|
|
85182
|
+
return;
|
|
85183
|
+
}
|
|
85184
|
+
case "agent_thought_chunk": {
|
|
85185
|
+
const text = update.content?.text ?? "";
|
|
85186
|
+
if (text) for (const t of chunkText(text)) emit({
|
|
85187
|
+
kind: "thinking",
|
|
85188
|
+
text: t
|
|
85189
|
+
});
|
|
85190
|
+
return;
|
|
85191
|
+
}
|
|
85192
|
+
case "tool_call":
|
|
85193
|
+
emit({
|
|
85194
|
+
kind: "tool_call",
|
|
85195
|
+
toolCallId: String(update.toolCallId ?? ""),
|
|
85196
|
+
title: String(update.title ?? update.toolCallId ?? "tool"),
|
|
85197
|
+
status: String(update.status ?? "pending")
|
|
85198
|
+
});
|
|
85199
|
+
return;
|
|
85200
|
+
case "tool_call_update":
|
|
85201
|
+
emit({
|
|
85202
|
+
kind: "tool_update",
|
|
85203
|
+
toolCallId: String(update.toolCallId ?? ""),
|
|
85204
|
+
status: String(update.status ?? "")
|
|
85205
|
+
});
|
|
85206
|
+
return;
|
|
85207
|
+
default: return;
|
|
85208
|
+
}
|
|
85209
|
+
}
|
|
84925
85210
|
/** Persist one internal activity event (tool/reasoning). Ordered by row `seq`, so its
|
|
84926
85211
|
* `ts` is just a wall-clock stamp for display — never used for replay/sorting. */
|
|
84927
85212
|
recordEvent(agentId, channel, thread, ev) {
|
|
@@ -85330,6 +85615,8 @@ var Daemon = class Daemon {
|
|
|
85330
85615
|
sessionRead: createSessionReader(this.store, (agentId) => this.replyConnFor(agentId)?.workspaceUrl),
|
|
85331
85616
|
workspaceRead: createWorkspaceReader((id) => this.agents.get(id)?.workspace.path),
|
|
85332
85617
|
workspaceGit: createWorkspaceGit((id) => this.agents.get(id)?.workspace.path),
|
|
85618
|
+
onWebchatMessage: (m) => this.handleWebchatMessage(m),
|
|
85619
|
+
onWebchatClose: (m) => this.handleWebchatClose(m),
|
|
85333
85620
|
clock: systemClock,
|
|
85334
85621
|
connect: () => ClientTransport.dial(url),
|
|
85335
85622
|
log: this.log
|