@agentconnect.md/daemon 1.0.0-rc.122 → 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 -52
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7651,7 +7651,6 @@ const SlackConfigSchema = object({
|
|
|
7651
7651
|
signingSecret: string().optional(),
|
|
7652
7652
|
botUserId: string().optional(),
|
|
7653
7653
|
allowedUserIds: array(string()).default([]),
|
|
7654
|
-
notificationChannelId: string().optional(),
|
|
7655
7654
|
bindRules: array(BindRuleConfigSchema).default([])
|
|
7656
7655
|
});
|
|
7657
7656
|
const IntegrationSchema = object({
|
|
@@ -12856,7 +12855,11 @@ const AuthOk = object({
|
|
|
12856
12855
|
* agent/*, and event/session. Its canonical string form is
|
|
12857
12856
|
* `${platform}:${channel}:${thread ?? "-"}`.
|
|
12858
12857
|
*/
|
|
12859
|
-
const Platform = _enum([
|
|
12858
|
+
const Platform = _enum([
|
|
12859
|
+
"slack",
|
|
12860
|
+
"telegram",
|
|
12861
|
+
"webchat"
|
|
12862
|
+
]);
|
|
12860
12863
|
const SessionKey = object({
|
|
12861
12864
|
platform: Platform,
|
|
12862
12865
|
channel: string(),
|
|
@@ -13144,7 +13147,6 @@ const IntegrationSlackConfig = object({
|
|
|
13144
13147
|
botToken: string(),
|
|
13145
13148
|
appToken: string(),
|
|
13146
13149
|
allowedUserIds: array(string()).default([]),
|
|
13147
|
-
notificationChannelId: string().optional(),
|
|
13148
13150
|
bindRules: array(IntegrationBindRule).default([])
|
|
13149
13151
|
});
|
|
13150
13152
|
/**
|
|
@@ -13511,6 +13513,99 @@ const FactsRuntimeProfile = object({
|
|
|
13511
13513
|
acpProtocolVersion: number().int().optional(),
|
|
13512
13514
|
toolCalling: boolean()
|
|
13513
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
|
+
]);
|
|
13514
13609
|
//#endregion
|
|
13515
13610
|
//#region ../protocol/dist/frame.js
|
|
13516
13611
|
/**
|
|
@@ -13537,6 +13632,24 @@ const FRAME_SCHEMAS = {
|
|
|
13537
13632
|
"agent/remove": AgentRemove,
|
|
13538
13633
|
"agent/activity": AgentActivity,
|
|
13539
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() }),
|
|
13540
13653
|
"route/assign": RouteAssign,
|
|
13541
13654
|
"route/assign/ack": RouteAssignAck,
|
|
13542
13655
|
"route/update": RouteUpdate,
|
|
@@ -13558,7 +13671,7 @@ const FRAME_SCHEMAS = {
|
|
|
13558
13671
|
"event/session": EventSession,
|
|
13559
13672
|
"usage/report": UsageReport,
|
|
13560
13673
|
"facts/runtime-profile": FactsRuntimeProfile,
|
|
13561
|
-
"facts/daemon-runtimes":
|
|
13674
|
+
"facts/daemon-runtimes": DaemonRuntimes,
|
|
13562
13675
|
"session/list": SessionListReq,
|
|
13563
13676
|
"session/list/page": SessionListPage,
|
|
13564
13677
|
"session/history": SessionHistoryReq,
|
|
@@ -13573,44 +13686,12 @@ const FRAME_SCHEMAS = {
|
|
|
13573
13686
|
"workspace/gitstatus/result": WorkspaceGitStatus,
|
|
13574
13687
|
"workspace/gitpull": WorkspaceGitPullReq,
|
|
13575
13688
|
"workspace/gitpull/result": WorkspaceGitPullResult,
|
|
13576
|
-
"config/push":
|
|
13577
|
-
"daemon/restart":
|
|
13578
|
-
|
|
13579
|
-
|
|
13580
|
-
|
|
13581
|
-
|
|
13582
|
-
targetVersion: string(),
|
|
13583
|
-
drainFirst: boolean().default(true)
|
|
13584
|
-
}),
|
|
13585
|
-
"daemon/control/ack": object({
|
|
13586
|
-
accepted: boolean(),
|
|
13587
|
-
willDrainUntil: string().datetime().optional()
|
|
13588
|
-
}),
|
|
13589
|
-
ack: object({
|
|
13590
|
-
ok: boolean(),
|
|
13591
|
-
reason: string().optional()
|
|
13592
|
-
}),
|
|
13593
|
-
error: object({
|
|
13594
|
-
code: _enum([
|
|
13595
|
-
"UNKNOWN_FRAME",
|
|
13596
|
-
"FRAME_TOO_LARGE",
|
|
13597
|
-
"PROTOCOL_STATE",
|
|
13598
|
-
"BAD_PAYLOAD",
|
|
13599
|
-
"AUTH_FAILED",
|
|
13600
|
-
"ATTESTATION_INVALID",
|
|
13601
|
-
"STALE_EPOCH",
|
|
13602
|
-
"STALE_LAUNCH",
|
|
13603
|
-
"NO_SESSION",
|
|
13604
|
-
"SCOPE_DENIED",
|
|
13605
|
-
"LEASE_EXPIRED",
|
|
13606
|
-
"LEASE_DENIED",
|
|
13607
|
-
"RATE_LIMITED",
|
|
13608
|
-
"INTERNAL"
|
|
13609
|
-
]),
|
|
13610
|
-
message: string(),
|
|
13611
|
-
retryable: boolean(),
|
|
13612
|
-
details: record(string(), unknown()).optional()
|
|
13613
|
-
})
|
|
13689
|
+
"config/push": ConfigPush,
|
|
13690
|
+
"daemon/restart": DaemonRestart,
|
|
13691
|
+
"daemon/upgrade": DaemonUpgrade,
|
|
13692
|
+
"daemon/control/ack": DaemonControlAck,
|
|
13693
|
+
ack: Ack,
|
|
13694
|
+
error: ErrorFrame
|
|
13614
13695
|
};
|
|
13615
13696
|
Object.keys(FRAME_SCHEMAS);
|
|
13616
13697
|
/**
|
|
@@ -13640,6 +13721,11 @@ discriminatedUnion("type", [
|
|
|
13640
13721
|
frame("agent/remove", FRAME_SCHEMAS["agent/remove"]),
|
|
13641
13722
|
frame("agent/activity", FRAME_SCHEMAS["agent/activity"]),
|
|
13642
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"]),
|
|
13643
13729
|
frame("route/assign", FRAME_SCHEMAS["route/assign"]),
|
|
13644
13730
|
frame("route/assign/ack", FRAME_SCHEMAS["route/assign/ack"]),
|
|
13645
13731
|
frame("route/update", FRAME_SCHEMAS["route/update"]),
|
|
@@ -24999,8 +25085,18 @@ const pickRule = (r, via) => ({
|
|
|
24999
25085
|
* (2) thread affinity (highest after explicit @; bypasses the kind filter, gated on
|
|
25000
25086
|
* reachable-bot count) → (3) CP per-sessionKey override → (4/5) kind precedence
|
|
25001
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.
|
|
25002
25093
|
*/
|
|
25003
|
-
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
|
+
};
|
|
25004
25100
|
if (msg.sender.isBot) return null;
|
|
25005
25101
|
const authz = (r) => !r.allowedUserIds || r.allowedUserIds.length === 0 || r.allowedUserIds.includes(msg.sender.id);
|
|
25006
25102
|
const scopeCandidates = rules.filter((r) => scopeMatches(r, msg) && authz(r));
|
|
@@ -83055,6 +83151,22 @@ var CpClient = class {
|
|
|
83055
83151
|
if (this.state !== "READY" && this.state !== "DRAINING") return;
|
|
83056
83152
|
this.transport?.send(encode(buildEnvelope("cron/report", report)));
|
|
83057
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
|
+
}
|
|
83058
83170
|
request(type, payload) {
|
|
83059
83171
|
const frame = buildEnvelope(type, payload);
|
|
83060
83172
|
return this.correlator.request(frame, (e) => this.transport.send(e));
|
|
@@ -83231,6 +83343,20 @@ var CpClient = class {
|
|
|
83231
83343
|
case "workspace/gitpull":
|
|
83232
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));
|
|
83233
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;
|
|
83234
83360
|
default:
|
|
83235
83361
|
this.deps.log.debug(`cp: ignoring ${frame.type}`);
|
|
83236
83362
|
return;
|
|
@@ -83951,7 +84077,6 @@ function toIntegration(spec) {
|
|
|
83951
84077
|
botToken: spec.slack.botToken,
|
|
83952
84078
|
appToken: spec.slack.appToken,
|
|
83953
84079
|
allowedUserIds: spec.slack.allowedUserIds,
|
|
83954
|
-
...spec.slack.notificationChannelId ? { notificationChannelId: spec.slack.notificationChannelId } : {},
|
|
83955
84080
|
bindRules: spec.slack.bindRules
|
|
83956
84081
|
}
|
|
83957
84082
|
};
|
|
@@ -84114,6 +84239,28 @@ function formatErr(err) {
|
|
|
84114
84239
|
return e?.stack ?? String(err);
|
|
84115
84240
|
}
|
|
84116
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
|
+
}
|
|
84117
84264
|
const IDLE_FLUSH_MS = 2e3;
|
|
84118
84265
|
/** Build the wire SessionKey (protocol §5) for a pending turn — what `drain/done`
|
|
84119
84266
|
* reports as released so the CP may reassign it. Uses the real `thread` (absent for
|
|
@@ -84593,6 +84740,61 @@ var Daemon = class Daemon {
|
|
|
84593
84740
|
this.log.info(`routing: ch=${msg.channel} → agent "${result.agentId}" (integration ${result.integrationId})`);
|
|
84594
84741
|
this.dispatch(result.agentId, msg, result.integrationId).catch((err) => this.log.error(`dispatch failed for agent "${result.agentId}": ${formatErr(err)}`));
|
|
84595
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
|
+
}
|
|
84596
84798
|
/** All of our own connections' bot identities (both `U…` user ids and `B…`
|
|
84597
84799
|
* bot_ids), so we recognize self-echoes regardless of which field Slack sends. */
|
|
84598
84800
|
ownBotIdentities() {
|
|
@@ -84736,14 +84938,14 @@ var Daemon = class Daemon {
|
|
|
84736
84938
|
for (const cpRule of this.cpRouting?.effectiveRules() ?? []) if (!this.resolveCpAgent(cpRule.agentId)) out.add(cpRule.agentId);
|
|
84737
84939
|
return [...out];
|
|
84738
84940
|
}
|
|
84739
|
-
async dispatch(agentId, msg, integrationId) {
|
|
84941
|
+
async dispatch(agentId, msg, integrationId, webchat) {
|
|
84740
84942
|
if (this.draining || this.drainingAgents.has(agentId)) {
|
|
84741
84943
|
this.log.debug(`dispatch: skipped for agent "${agentId}" (draining)`);
|
|
84742
84944
|
return null;
|
|
84743
84945
|
}
|
|
84744
84946
|
const conv = new OutputConverger(this.agents.get(agentId).output.mode);
|
|
84745
84947
|
const rec = new TranscriptRecorder();
|
|
84746
|
-
const replyConn = msg.headless ? void 0 : this.replyConnFor(agentId, integrationId);
|
|
84948
|
+
const replyConn = msg.headless || webchat ? void 0 : this.replyConnFor(agentId, integrationId);
|
|
84747
84949
|
const wasRunning = this.hostStarts.has(agentId);
|
|
84748
84950
|
const statusThread = msg.thread ?? msg.msgId;
|
|
84749
84951
|
const key = sessionKey(msg.platform, msg.channel, statusThread, agentId);
|
|
@@ -84770,13 +84972,18 @@ var Daemon = class Daemon {
|
|
|
84770
84972
|
conn: replyConn,
|
|
84771
84973
|
applyChain: Promise.resolve(),
|
|
84772
84974
|
done,
|
|
84773
|
-
resolveDone
|
|
84975
|
+
resolveDone,
|
|
84976
|
+
...webchat ? { webchat: {
|
|
84977
|
+
...webchat,
|
|
84978
|
+
index: 0,
|
|
84979
|
+
replyText: ""
|
|
84980
|
+
} } : {}
|
|
84774
84981
|
};
|
|
84775
84982
|
this.pending.set(sessionId, p);
|
|
84776
84983
|
try {
|
|
84777
84984
|
const host = await this.ensureHostAsync(agentId);
|
|
84778
84985
|
if (!wasRunning) replyConn?.setStatus(msg.channel, statusThread, "is thinking…");
|
|
84779
|
-
const { usage } = await host.prompt(sessionId, blocks);
|
|
84986
|
+
const { stopReason, usage } = await host.prompt(sessionId, blocks);
|
|
84780
84987
|
if (usage) this.store.setTokenUsage(key, {
|
|
84781
84988
|
totalTokens: usage.totalTokens,
|
|
84782
84989
|
inputTokens: usage.inputTokens,
|
|
@@ -84799,8 +85006,25 @@ var Daemon = class Daemon {
|
|
|
84799
85006
|
this.log.debug(`usage report emit failed (session ${sessionId}): ${err.message}`);
|
|
84800
85007
|
}
|
|
84801
85008
|
this.clearIdle(p);
|
|
84802
|
-
|
|
84803
|
-
|
|
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
|
+
}
|
|
84804
85028
|
for (const ev of rec.onFinal()) this.recordEvent(agentId, msg.channel, statusThread, ev);
|
|
84805
85029
|
await p.applyChain;
|
|
84806
85030
|
} finally {
|
|
@@ -84921,10 +85145,68 @@ var Daemon = class Daemon {
|
|
|
84921
85145
|
costCurrency: update.cost?.currency ?? void 0
|
|
84922
85146
|
});
|
|
84923
85147
|
if (update?.sessionUpdate === "session_info_update" && update.title !== void 0) this.store.setSessionTitle(p.sessionKey, update.title);
|
|
84924
|
-
|
|
84925
|
-
|
|
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
|
+
}
|
|
84926
85153
|
for (const ev of p.rec.onUpdate(update)) this.recordEvent(p.agentId, p.channel, p.statusThread, ev);
|
|
84927
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
|
+
}
|
|
84928
85210
|
/** Persist one internal activity event (tool/reasoning). Ordered by row `seq`, so its
|
|
84929
85211
|
* `ts` is just a wall-clock stamp for display — never used for replay/sorting. */
|
|
84930
85212
|
recordEvent(agentId, channel, thread, ev) {
|
|
@@ -85333,6 +85615,8 @@ var Daemon = class Daemon {
|
|
|
85333
85615
|
sessionRead: createSessionReader(this.store, (agentId) => this.replyConnFor(agentId)?.workspaceUrl),
|
|
85334
85616
|
workspaceRead: createWorkspaceReader((id) => this.agents.get(id)?.workspace.path),
|
|
85335
85617
|
workspaceGit: createWorkspaceGit((id) => this.agents.get(id)?.workspace.path),
|
|
85618
|
+
onWebchatMessage: (m) => this.handleWebchatMessage(m),
|
|
85619
|
+
onWebchatClose: (m) => this.handleWebchatClose(m),
|
|
85336
85620
|
clock: systemClock,
|
|
85337
85621
|
connect: () => ClientTransport.dial(url),
|
|
85338
85622
|
log: this.log
|