@borgee/agents-host 0.2.35 → 0.2.56
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/README.md +23 -27
- package/dist/agents-host.d.ts +28 -5
- package/dist/agents-host.js +274 -236
- package/dist/chat/chat-control-plane.d.ts +3 -0
- package/dist/chat/sdk-chat-control-plane.d.ts +4 -3
- package/dist/chat/sdk-chat-control-plane.js +6 -0
- package/dist/cli.js +1 -5
- package/dist/compatibility-gates.d.ts +4 -0
- package/dist/compatibility-gates.js +18 -1
- package/dist/context/claude-file-brief.d.ts +2 -0
- package/dist/context/claude-file-brief.js +83 -0
- package/dist/context/compaction.d.ts +20 -0
- package/dist/context/compaction.js +59 -0
- package/dist/context/injection.d.ts +44 -9
- package/dist/context/injection.js +360 -53
- package/dist/context/main-session-delegation.d.ts +1 -1
- package/dist/context/projection-strategy.d.ts +24 -0
- package/dist/context/projection-strategy.js +90 -0
- package/dist/context/prompt.d.ts +16 -1
- package/dist/context/prompt.js +475 -53
- package/dist/context/resolved-workspace.d.ts +2 -0
- package/dist/context/resolved-workspace.js +64 -0
- package/dist/context/skill-manual.d.ts +14 -0
- package/dist/context/skill-manual.js +21 -0
- package/dist/context/turn-preparation.d.ts +8 -2
- package/dist/context/turn-preparation.js +68 -17
- package/dist/gateway/localhost-gateway.js +18 -8
- package/dist/hosted-turn-content.d.ts +15 -0
- package/dist/hosted-turn-content.js +50 -0
- package/dist/managed-daemon.d.ts +3 -2
- package/dist/managed-daemon.js +198 -37
- package/dist/plugin-sdk.js +276 -359
- package/dist/plugin-sdk.js.map +4 -4
- package/dist/progress-to-activity.d.ts +16 -0
- package/dist/progress-to-activity.js +24 -0
- package/dist/projection-strategy-values.d.ts +4 -0
- package/dist/projection-strategy-values.js +28 -0
- package/dist/providers/acp-progress-collector.d.ts +44 -0
- package/dist/providers/acp-progress-collector.js +130 -0
- package/dist/providers/awaiting-user.d.ts +2 -3
- package/dist/providers/awaiting-user.js +5 -7
- package/dist/providers/claude/activity-metadata.d.ts +14 -0
- package/dist/providers/claude/activity-metadata.js +81 -0
- package/dist/providers/claude/adapter.d.ts +3 -1
- package/dist/providers/claude/adapter.js +10 -0
- package/dist/providers/claude/cli-client.d.ts +9 -1
- package/dist/providers/claude/cli-client.js +270 -126
- package/dist/providers/codex/adapter.d.ts +3 -1
- package/dist/providers/codex/adapter.js +10 -0
- package/dist/providers/codex/cli-client.d.ts +10 -2
- package/dist/providers/codex/cli-client.js +88 -104
- package/dist/providers/codex/project-doc.js +22 -37
- package/dist/providers/copilot/adapter.d.ts +3 -1
- package/dist/providers/copilot/adapter.js +10 -0
- package/dist/providers/copilot/cli-client.d.ts +9 -1
- package/dist/providers/copilot/cli-client.js +68 -85
- package/dist/providers/create-provider.d.ts +3 -1
- package/dist/providers/create-provider.js +36 -9
- package/dist/providers/provider-adapter.d.ts +35 -0
- package/dist/providers/provider-adapter.js +44 -1
- package/dist/state-paths.d.ts +10 -2
- package/dist/state-paths.js +25 -6
- package/dist/task-thread-resolution.d.ts +3 -2
- package/dist/types.d.ts +163 -8
- package/package.json +2 -2
- package/skills/borgee-agent/SKILL.md +127 -38
- package/skills/borgee-agent/references/errors.md +38 -0
- package/skills/borgee-agent/references/task-properties.md +33 -0
- package/skills/borgee-agent/scripts/borgee-agent.mjs +553 -0
- package/skills/borgee-agent/scripts/borgee-agent.py +547 -0
- package/dist/durable-cursor-store.d.ts +0 -5
- package/dist/durable-cursor-store.js +0 -7
- package/skills/borgee-agent/borgee-agent.mjs +0 -562
- package/skills/borgee-agent/borgee-agent.py +0 -469
package/dist/agents-host.js
CHANGED
|
@@ -3,19 +3,20 @@ import { chmod, link, mkdir, readFile, readdir, rename, unlink, writeFile } from
|
|
|
3
3
|
import { dirname, join, resolve } from 'node:path';
|
|
4
4
|
import { SdkChatControlPlane } from './chat/sdk-chat-control-plane.js';
|
|
5
5
|
import { HostLogger, summarizeError } from './debug.js';
|
|
6
|
-
import {
|
|
6
|
+
import { toReportedActivity } from './progress-to-activity.js';
|
|
7
7
|
import { ATTENTION_FOLLOW_SEMANTICS_COMPATIBILITY_GATE, COLLABORATION_CAPABILITIES_DIAGNOSTICS_COMPATIBILITY_GATE, COLLABORATION_OUTCOME_MODEL_COMPATIBILITY_GATE, COLLABORATION_SKILL_FIRST_COMPATIBILITY_GATE, CONTEXT_INJECTION_COMPATIBILITY_GATE, LOCALHOST_GATEWAY_COMPATIBILITY_GATE, POLICY_AUDIT_ENFORCEMENT_COMPATIBILITY_GATE, resolveInternalPolicyMode, resolveInternalCompatibilityGates, SKILL_RUNTIME_COMPATIBILITY_GATE, TASK_THREAD_COLLABORATION_CONTRACT_COMPATIBILITY_GATE, TOKEN_BINDING_COMPATIBILITY_GATE, } from './compatibility-gates.js';
|
|
8
8
|
import { buildNormalizedAttentionSnapshot, parsePersistedAttentionSnapshot, } from './context/attention.js';
|
|
9
|
+
import { mergeCompactionSnapshot, parsePersistedCompactionSnapshot, } from './context/compaction.js';
|
|
9
10
|
import { projectTaskThreadCollaborationContract } from './context/task-thread-collaboration.js';
|
|
10
11
|
import { createLocalhostGatewayController, LOCALHOST_GATEWAY_COLLABORATION_TARGET_COOLDOWN_MS, } from './gateway/localhost-gateway.js';
|
|
11
12
|
import { extractTaskIdFromTaskAssignmentContent, resolveChannelContextPayloadPath, } from './context/injection.js';
|
|
12
13
|
import { AuthorizationAuditSink, } from './policy/authorization-audit.js';
|
|
13
14
|
import { createProvider } from './providers/create-provider.js';
|
|
14
|
-
import { resolveAttentionStatePath, resolveSharedProtocolKickoffDecisionPath, resolveSharedProtocolStatusPath, } from './state-paths.js';
|
|
15
|
+
import { resolveAttentionStatePath, resolveCompactionStatePath, resolveSharedProtocolKickoffDecisionPath, resolveSharedProtocolStatusPath, } from './state-paths.js';
|
|
15
16
|
import { appendVisibleMention as appendVisibleBodyMention, extractVisibleMentionIds as extractCanonicalMentionIds, } from './visible-mentions.js';
|
|
16
17
|
import { waitForTaskForThread } from './task-thread-resolution.js';
|
|
18
|
+
import { buildHostedIncomingContentText, buildHostedTurnContentParts, extractHostedMessageAttachments, normalizeHostedMessageAttachment, } from './hosted-turn-content.js';
|
|
17
19
|
const PRIVATE_STATE_ROOT_MODE = 0o700;
|
|
18
|
-
const STREAM_PROGRESS_EDIT_THROTTLE_MS = 150;
|
|
19
20
|
const PROTOCOL_STATE_DIRNAME = 'protocol-state';
|
|
20
21
|
const PROTOCOL_PROVIDER_TURN_TIMEOUT_MS = 120_000;
|
|
21
22
|
const PROTOCOL_KICKOFF_DECISION_TIMEOUT_MS = PROTOCOL_PROVIDER_TURN_TIMEOUT_MS;
|
|
@@ -103,9 +104,6 @@ class DefaultHostRuntime {
|
|
|
103
104
|
this.controlPlane =
|
|
104
105
|
deps.borgee ??
|
|
105
106
|
new SdkChatControlPlane(config.borgeeBaseUrl, config.agent.agentApiKey, undefined, {
|
|
106
|
-
cursorStore: createDurableCursorStore({
|
|
107
|
-
stateRootDir: config.stateRootDir,
|
|
108
|
-
}),
|
|
109
107
|
pluginId: `agents-host:${config.agent.provider}`,
|
|
110
108
|
});
|
|
111
109
|
this.gateway = createLocalhostGatewayController({
|
|
@@ -126,7 +124,9 @@ class DefaultHostRuntime {
|
|
|
126
124
|
deps.provider ??
|
|
127
125
|
createProvider({
|
|
128
126
|
provider: config.agent.provider,
|
|
127
|
+
borgeeBaseUrl: config.borgeeBaseUrl,
|
|
129
128
|
stateRootDir: config.stateRootDir,
|
|
129
|
+
agentApiKey: config.agent.agentApiKey,
|
|
130
130
|
resolveStableAgentId: deps.resolveStableAgentId,
|
|
131
131
|
claudeCommand: config.claudeCommand,
|
|
132
132
|
claudeArgs: config.claudeArgs,
|
|
@@ -139,6 +139,7 @@ class DefaultHostRuntime {
|
|
|
139
139
|
}, deps.logger, {
|
|
140
140
|
compatibilityGates,
|
|
141
141
|
authorizationAuditSink: this.authorizationAuditSink,
|
|
142
|
+
controlPlane: this.controlPlane,
|
|
142
143
|
localhostGateway: localhostGatewayGateEnabled ? this.gateway.contextPublisher : undefined,
|
|
143
144
|
});
|
|
144
145
|
this.ensureStateRoot = deps.ensureStateRoot ?? ensurePrivateStateRoot;
|
|
@@ -255,113 +256,6 @@ function resolvePublicReplyText(reply) {
|
|
|
255
256
|
}
|
|
256
257
|
return reply.awaitingUser?.question ?? '';
|
|
257
258
|
}
|
|
258
|
-
function providerUsesDraftProgress(provider) {
|
|
259
|
-
return provider === 'claude' || provider === 'codex' || provider === 'copilot';
|
|
260
|
-
}
|
|
261
|
-
class DraftMessageController {
|
|
262
|
-
borgee;
|
|
263
|
-
channelId;
|
|
264
|
-
canWrite;
|
|
265
|
-
logger;
|
|
266
|
-
creationMetadata;
|
|
267
|
-
messageId = null;
|
|
268
|
-
currentBody = '';
|
|
269
|
-
pendingBody = null;
|
|
270
|
-
timer = null;
|
|
271
|
-
sealed = false;
|
|
272
|
-
writeChain = Promise.resolve();
|
|
273
|
-
constructor(borgee, channelId, canWrite, logger, creationMetadata) {
|
|
274
|
-
this.borgee = borgee;
|
|
275
|
-
this.channelId = channelId;
|
|
276
|
-
this.canWrite = canWrite;
|
|
277
|
-
this.logger = logger;
|
|
278
|
-
this.creationMetadata = creationMetadata;
|
|
279
|
-
}
|
|
280
|
-
update(text) {
|
|
281
|
-
if (this.sealed || !hasVisibleText(text)) {
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
this.pendingBody = text;
|
|
285
|
-
if (this.timer) {
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
this.timer = setTimeout(() => {
|
|
289
|
-
this.timer = null;
|
|
290
|
-
void this.flushPending().catch((error) => {
|
|
291
|
-
this.logger.error('failed to flush draft progress', error);
|
|
292
|
-
});
|
|
293
|
-
}, STREAM_PROGRESS_EDIT_THROTTLE_MS);
|
|
294
|
-
}
|
|
295
|
-
cancelProgress() {
|
|
296
|
-
if (this.timer) {
|
|
297
|
-
clearTimeout(this.timer);
|
|
298
|
-
this.timer = null;
|
|
299
|
-
}
|
|
300
|
-
this.sealed = true;
|
|
301
|
-
}
|
|
302
|
-
async finalize(finalText) {
|
|
303
|
-
this.sealed = true;
|
|
304
|
-
if (this.timer) {
|
|
305
|
-
clearTimeout(this.timer);
|
|
306
|
-
this.timer = null;
|
|
307
|
-
}
|
|
308
|
-
if (hasVisibleText(finalText)) {
|
|
309
|
-
this.pendingBody = finalText;
|
|
310
|
-
await this.flushPending();
|
|
311
|
-
return this.messageId;
|
|
312
|
-
}
|
|
313
|
-
await this.enqueueWrite(async () => {
|
|
314
|
-
if (!this.canWrite() || !this.messageId) {
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
await this.borgee.deleteMessage(this.messageId);
|
|
318
|
-
this.messageId = null;
|
|
319
|
-
this.currentBody = '';
|
|
320
|
-
this.pendingBody = null;
|
|
321
|
-
});
|
|
322
|
-
return null;
|
|
323
|
-
}
|
|
324
|
-
async discard() {
|
|
325
|
-
this.sealed = true;
|
|
326
|
-
if (this.timer) {
|
|
327
|
-
clearTimeout(this.timer);
|
|
328
|
-
this.timer = null;
|
|
329
|
-
}
|
|
330
|
-
this.pendingBody = null;
|
|
331
|
-
await this.enqueueWrite(async () => {
|
|
332
|
-
if (!this.messageId) {
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
335
|
-
await this.borgee.deleteMessage(this.messageId);
|
|
336
|
-
this.messageId = null;
|
|
337
|
-
this.currentBody = '';
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
async flushPending() {
|
|
341
|
-
await this.enqueueWrite(async () => {
|
|
342
|
-
const body = this.pendingBody;
|
|
343
|
-
if (!this.canWrite() || !body || body === this.currentBody) {
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
if (this.messageId) {
|
|
347
|
-
await this.borgee.editMessage(this.messageId, body);
|
|
348
|
-
}
|
|
349
|
-
else {
|
|
350
|
-
const posted = await this.borgee.postMessage({
|
|
351
|
-
channelId: this.channelId,
|
|
352
|
-
body,
|
|
353
|
-
replyToId: this.creationMetadata?.replyToId,
|
|
354
|
-
});
|
|
355
|
-
this.messageId = posted.messageId;
|
|
356
|
-
}
|
|
357
|
-
this.currentBody = body;
|
|
358
|
-
});
|
|
359
|
-
}
|
|
360
|
-
enqueueWrite(operation) {
|
|
361
|
-
this.writeChain = this.writeChain.then(operation, operation);
|
|
362
|
-
return this.writeChain;
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
259
|
/**
|
|
366
260
|
* The registered task property agents-host writes to record which provider
|
|
367
261
|
* session worked a task. It matches the server's registry key.
|
|
@@ -395,9 +289,12 @@ export class AgentsHost {
|
|
|
395
289
|
*/
|
|
396
290
|
recordedAgentSessionByTask = new Map();
|
|
397
291
|
attentionSnapshotByChannel = new Map();
|
|
292
|
+
compactionSnapshotByChannel = new Map();
|
|
293
|
+
activeCompactionByChannel = new Map();
|
|
294
|
+
compactionSnapshotLoadedChannels = new Set();
|
|
295
|
+
compactionSnapshotWriteQueueByChannel = new Map();
|
|
398
296
|
missedCollaborationDiagnosticByChannel = new Map();
|
|
399
|
-
|
|
400
|
-
progressDrafts = new Map();
|
|
297
|
+
channelTurnQueues = new Map();
|
|
401
298
|
collaborationDrafts = new Map();
|
|
402
299
|
collaborationChannels = new Map();
|
|
403
300
|
participantDirectoryByUserId = new Map();
|
|
@@ -505,20 +402,32 @@ export class AgentsHost {
|
|
|
505
402
|
this.invalidateChannelTurn(channelId);
|
|
506
403
|
this.clearProtocolFallback(this.collaborationChannels.get(channelId));
|
|
507
404
|
}
|
|
508
|
-
for (const draft of this.progressDrafts.values()) {
|
|
509
|
-
draft.cancelProgress();
|
|
510
|
-
}
|
|
511
405
|
for (const channelId of [...this.collaborationDrafts.keys()]) {
|
|
512
406
|
this.clearCollaborationDraft(channelId);
|
|
513
407
|
}
|
|
514
408
|
this.awaitingUserByChannel.clear();
|
|
515
409
|
this.collaborationOutcomeByChannel.clear();
|
|
516
410
|
this.attentionSnapshotByChannel.clear();
|
|
411
|
+
this.compactionSnapshotByChannel.clear();
|
|
412
|
+
this.activeCompactionByChannel.clear();
|
|
413
|
+
this.compactionSnapshotLoadedChannels.clear();
|
|
517
414
|
this.missedCollaborationDiagnosticByChannel.clear();
|
|
518
415
|
this.recordedAgentSessionByTask.clear();
|
|
519
416
|
this.controlPlaneClosed = true;
|
|
520
|
-
|
|
417
|
+
const pendingCompactionSnapshotWrites = [...this.compactionSnapshotWriteQueueByChannel.values()];
|
|
418
|
+
let runtimeStopError;
|
|
419
|
+
try {
|
|
420
|
+
await this.runtime.stop();
|
|
421
|
+
}
|
|
422
|
+
catch (error) {
|
|
423
|
+
runtimeStopError = error;
|
|
424
|
+
}
|
|
521
425
|
await Promise.allSettled([...this.activeTurns]);
|
|
426
|
+
await Promise.allSettled(pendingCompactionSnapshotWrites);
|
|
427
|
+
this.compactionSnapshotWriteQueueByChannel.clear();
|
|
428
|
+
if (runtimeStopError) {
|
|
429
|
+
throw runtimeStopError;
|
|
430
|
+
}
|
|
522
431
|
}
|
|
523
432
|
trackActiveTurn(task) {
|
|
524
433
|
this.activeTurns.add(task);
|
|
@@ -529,9 +438,7 @@ export class AgentsHost {
|
|
|
529
438
|
dispatchMessage(message) {
|
|
530
439
|
const task = this.collaborationEnabled
|
|
531
440
|
? this.handleCollaborationMessage(message)
|
|
532
|
-
:
|
|
533
|
-
? this.enqueueProgressChannelTurn(message.channel_id, () => this.handleLegacyMessage(message))
|
|
534
|
-
: this.handleLegacyMessage(message);
|
|
441
|
+
: this.enqueueChannelTurn(message.channel_id, () => this.handleLegacyMessage(message));
|
|
535
442
|
this.trackActiveTurn(task);
|
|
536
443
|
}
|
|
537
444
|
async handleLegacyMessage(msg) {
|
|
@@ -559,7 +466,7 @@ export class AgentsHost {
|
|
|
559
466
|
}
|
|
560
467
|
}
|
|
561
468
|
}
|
|
562
|
-
await this.runUngatedTurn(msg, normalized.authorId, normalized.content, taskThreadContext, attentionSnapshot);
|
|
469
|
+
await this.runUngatedTurn(msg, normalized.authorId, normalized.content, taskThreadContext, attentionSnapshot, normalized.incomingParts);
|
|
563
470
|
}
|
|
564
471
|
async handleCollaborationMessage(msg) {
|
|
565
472
|
const normalized = await this.normalizeMessage(msg);
|
|
@@ -608,6 +515,7 @@ export class AgentsHost {
|
|
|
608
515
|
authorId: normalized.authorId,
|
|
609
516
|
authorKind: 'unknown',
|
|
610
517
|
content: normalized.content,
|
|
518
|
+
incomingParts: normalized.incomingParts,
|
|
611
519
|
attentionSnapshot,
|
|
612
520
|
taskThreadContext,
|
|
613
521
|
});
|
|
@@ -626,6 +534,7 @@ export class AgentsHost {
|
|
|
626
534
|
authorId: normalized.authorId,
|
|
627
535
|
authorKind: 'human',
|
|
628
536
|
content: normalized.content,
|
|
537
|
+
incomingParts: normalized.incomingParts,
|
|
629
538
|
attentionSnapshot,
|
|
630
539
|
taskThreadContext,
|
|
631
540
|
wakeBlockedState,
|
|
@@ -658,6 +567,7 @@ export class AgentsHost {
|
|
|
658
567
|
authorId: normalized.authorId,
|
|
659
568
|
authorKind,
|
|
660
569
|
content: normalized.content,
|
|
570
|
+
incomingParts: normalized.incomingParts,
|
|
661
571
|
attentionSnapshot,
|
|
662
572
|
taskThreadContext,
|
|
663
573
|
});
|
|
@@ -752,6 +662,7 @@ export class AgentsHost {
|
|
|
752
662
|
const executed = await this.executeTurn(msg, normalized.authorId, normalized.content, {
|
|
753
663
|
kickoff: protocolCandidate,
|
|
754
664
|
silent: true,
|
|
665
|
+
incomingParts: normalized.incomingParts,
|
|
755
666
|
});
|
|
756
667
|
if (executed.stale) {
|
|
757
668
|
return null;
|
|
@@ -912,6 +823,7 @@ export class AgentsHost {
|
|
|
912
823
|
authorId: normalized.authorId,
|
|
913
824
|
authorKind: 'human',
|
|
914
825
|
content: normalized.content,
|
|
826
|
+
incomingParts: normalized.incomingParts,
|
|
915
827
|
});
|
|
916
828
|
await this.ensureCollaborationProcessor(msg.channel_id, channelState);
|
|
917
829
|
return;
|
|
@@ -924,6 +836,7 @@ export class AgentsHost {
|
|
|
924
836
|
ownerAgentId: selfAgentId,
|
|
925
837
|
anchorAuthorId: normalized.authorId,
|
|
926
838
|
anchorContent: normalized.content,
|
|
839
|
+
anchorIncomingParts: normalized.incomingParts,
|
|
927
840
|
anchorMessageId: msg.message_id ?? randomUUID(),
|
|
928
841
|
participantIds: protocolRequest.participantIds,
|
|
929
842
|
issuerId: protocolRequest.issuerId,
|
|
@@ -946,6 +859,7 @@ export class AgentsHost {
|
|
|
946
859
|
authorId: normalized.authorId,
|
|
947
860
|
authorKind: 'human',
|
|
948
861
|
content: normalized.content,
|
|
862
|
+
incomingParts: normalized.incomingParts,
|
|
949
863
|
});
|
|
950
864
|
await this.ensureCollaborationProcessor(msg.channel_id, channelState);
|
|
951
865
|
return;
|
|
@@ -1014,6 +928,7 @@ export class AgentsHost {
|
|
|
1014
928
|
authorId: normalized.authorId,
|
|
1015
929
|
authorKind: 'agent',
|
|
1016
930
|
content: normalized.content,
|
|
931
|
+
incomingParts: normalized.incomingParts,
|
|
1017
932
|
});
|
|
1018
933
|
await this.ensureCollaborationProcessor(msg.channel_id, channelState);
|
|
1019
934
|
}
|
|
@@ -1131,6 +1046,7 @@ export class AgentsHost {
|
|
|
1131
1046
|
channelState.activeTurn = activeTurn;
|
|
1132
1047
|
const executed = await this.executeTurn(message.raw, message.authorId, this.buildIncomingContent(message), {
|
|
1133
1048
|
activeTurn,
|
|
1049
|
+
incomingParts: message.incomingParts,
|
|
1134
1050
|
incomingAuthorKind: message.authorKind,
|
|
1135
1051
|
attentionSnapshot,
|
|
1136
1052
|
taskThreadContext,
|
|
@@ -1234,6 +1150,7 @@ export class AgentsHost {
|
|
|
1234
1150
|
channelState.activeTurn = activeTurn;
|
|
1235
1151
|
const executed = await this.executeTurn(message.raw, message.authorId, this.buildIncomingContent(message), {
|
|
1236
1152
|
activeTurn,
|
|
1153
|
+
incomingParts: message.incomingParts,
|
|
1237
1154
|
incomingAuthorKind: message.authorKind,
|
|
1238
1155
|
attentionSnapshot,
|
|
1239
1156
|
taskThreadContext,
|
|
@@ -1541,59 +1458,52 @@ export class AgentsHost {
|
|
|
1541
1458
|
}
|
|
1542
1459
|
async buildResumedProtocolMessage(channelId, protocolState) {
|
|
1543
1460
|
if (protocolState.currentTurnIndex === 0) {
|
|
1461
|
+
const anchorIncomingParts = await this.ensureProtocolAnchorIncomingParts(channelId, protocolState);
|
|
1462
|
+
const anchorIncomingContent = buildHostedIncomingContentText(anchorIncomingParts);
|
|
1544
1463
|
return {
|
|
1545
1464
|
kind: 'protocol-turn',
|
|
1546
1465
|
raw: {
|
|
1547
1466
|
channel_id: channelId,
|
|
1548
1467
|
message_id: protocolState.anchorMessageId,
|
|
1549
1468
|
user_id: protocolState.anchorAuthorId,
|
|
1550
|
-
content:
|
|
1551
|
-
body:
|
|
1469
|
+
content: anchorIncomingContent,
|
|
1470
|
+
body: anchorIncomingContent,
|
|
1471
|
+
attachments: extractHostedMessageAttachments(anchorIncomingParts),
|
|
1552
1472
|
type: 'message',
|
|
1553
1473
|
},
|
|
1554
1474
|
authorId: protocolState.anchorAuthorId,
|
|
1555
1475
|
authorKind: 'human',
|
|
1556
|
-
content:
|
|
1476
|
+
content: anchorIncomingContent,
|
|
1477
|
+
incomingParts: anchorIncomingParts,
|
|
1557
1478
|
};
|
|
1558
1479
|
}
|
|
1559
1480
|
if (!protocolState.lastProtocolMessageId) {
|
|
1560
1481
|
return null;
|
|
1561
1482
|
}
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
const
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
limit: 200,
|
|
1483
|
+
const priorMessage = await this.findChannelHistoryMessageById(channelId, protocolState.lastProtocolMessageId);
|
|
1484
|
+
if (priorMessage) {
|
|
1485
|
+
const incomingParts = buildHostedTurnContentParts({
|
|
1486
|
+
text: priorMessage.body,
|
|
1487
|
+
attachments: priorMessage.attachments,
|
|
1568
1488
|
});
|
|
1569
|
-
const
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
}
|
|
1587
|
-
if (history.length < 200) {
|
|
1588
|
-
break;
|
|
1589
|
-
}
|
|
1590
|
-
const oldestCreatedAt = history.reduce((oldest, entry) => {
|
|
1591
|
-
return oldest == null || entry.createdAt < oldest ? entry.createdAt : oldest;
|
|
1592
|
-
}, null);
|
|
1593
|
-
if (oldestCreatedAt == null || (before != null && oldestCreatedAt >= before)) {
|
|
1594
|
-
break;
|
|
1595
|
-
}
|
|
1596
|
-
before = oldestCreatedAt;
|
|
1489
|
+
const incomingContent = buildHostedIncomingContentText(incomingParts);
|
|
1490
|
+
return {
|
|
1491
|
+
kind: 'protocol-turn',
|
|
1492
|
+
raw: {
|
|
1493
|
+
channel_id: channelId,
|
|
1494
|
+
message_id: priorMessage.id,
|
|
1495
|
+
user_id: priorMessage.authorId,
|
|
1496
|
+
content: incomingContent,
|
|
1497
|
+
body: incomingContent,
|
|
1498
|
+
attachments: priorMessage.attachments,
|
|
1499
|
+
type: 'message',
|
|
1500
|
+
created_at: priorMessage.createdAt,
|
|
1501
|
+
},
|
|
1502
|
+
authorId: priorMessage.authorId,
|
|
1503
|
+
authorKind: 'agent',
|
|
1504
|
+
content: incomingContent,
|
|
1505
|
+
incomingParts,
|
|
1506
|
+
};
|
|
1597
1507
|
}
|
|
1598
1508
|
return null;
|
|
1599
1509
|
}
|
|
@@ -1604,6 +1514,7 @@ export class AgentsHost {
|
|
|
1604
1514
|
ownerAgentId,
|
|
1605
1515
|
anchorAuthorId: protocolState.anchorAuthorId,
|
|
1606
1516
|
anchorContent: protocolState.anchorContent,
|
|
1517
|
+
anchorIncomingParts: protocolState.anchorIncomingParts,
|
|
1607
1518
|
anchorMessageId: protocolState.anchorMessageId,
|
|
1608
1519
|
participantIds: protocolState.participantIds,
|
|
1609
1520
|
issuerId: protocolState.issuerId,
|
|
@@ -1682,18 +1593,36 @@ export class AgentsHost {
|
|
|
1682
1593
|
await this.persistProtocolState(channelId, protocolState);
|
|
1683
1594
|
return;
|
|
1684
1595
|
}
|
|
1596
|
+
const anchorIncomingParts = await this.ensureProtocolAnchorIncomingParts(channelId, protocolState);
|
|
1597
|
+
const anchorIncomingContent = buildHostedIncomingContentText(anchorIncomingParts);
|
|
1685
1598
|
const fallbackMessage = {
|
|
1686
1599
|
channel_id: channelId,
|
|
1687
1600
|
message_id: protocolState.anchorMessageId,
|
|
1688
1601
|
user_id: protocolState.anchorAuthorId,
|
|
1689
|
-
content:
|
|
1690
|
-
body:
|
|
1602
|
+
content: anchorIncomingContent,
|
|
1603
|
+
body: anchorIncomingContent,
|
|
1604
|
+
attachments: extractHostedMessageAttachments(anchorIncomingParts),
|
|
1691
1605
|
type: 'message',
|
|
1692
1606
|
};
|
|
1693
1607
|
this.cancelProtocolState(channelId, channelState);
|
|
1694
1608
|
const taskThreadContext = await this.buildTurnTaskThreadContext(fallbackMessage, channelId);
|
|
1695
1609
|
const attentionSnapshot = this.getAttentionSnapshotForTurn(channelId, taskThreadContext);
|
|
1696
|
-
await this.runUngatedTurn(fallbackMessage, protocolState.anchorAuthorId,
|
|
1610
|
+
await this.runUngatedTurn(fallbackMessage, protocolState.anchorAuthorId, anchorIncomingContent, taskThreadContext, attentionSnapshot, anchorIncomingParts);
|
|
1611
|
+
}
|
|
1612
|
+
async ensureProtocolAnchorIncomingParts(channelId, protocolState) {
|
|
1613
|
+
if (protocolState.anchorIncomingParts) {
|
|
1614
|
+
return protocolState.anchorIncomingParts;
|
|
1615
|
+
}
|
|
1616
|
+
const anchorMessage = await this.findChannelHistoryMessageById(channelId, protocolState.anchorMessageId);
|
|
1617
|
+
if (!anchorMessage) {
|
|
1618
|
+
return buildHostedTurnContentParts({ text: protocolState.anchorContent });
|
|
1619
|
+
}
|
|
1620
|
+
const anchorIncomingParts = buildHostedTurnContentParts({
|
|
1621
|
+
text: protocolState.anchorContent,
|
|
1622
|
+
attachments: anchorMessage.attachments,
|
|
1623
|
+
});
|
|
1624
|
+
protocolState.anchorIncomingParts = anchorIncomingParts;
|
|
1625
|
+
return anchorIncomingParts;
|
|
1697
1626
|
}
|
|
1698
1627
|
finishProtocolState(channelId, channelState, protocolState, lastMessageId) {
|
|
1699
1628
|
protocolState.active = false;
|
|
@@ -1733,15 +1662,125 @@ export class AgentsHost {
|
|
|
1733
1662
|
}
|
|
1734
1663
|
return matched;
|
|
1735
1664
|
}
|
|
1665
|
+
async findChannelHistoryMessageById(channelId, messageId) {
|
|
1666
|
+
let before;
|
|
1667
|
+
for (;;) {
|
|
1668
|
+
const history = await this.borgee.readChannelHistory({
|
|
1669
|
+
channelId,
|
|
1670
|
+
before,
|
|
1671
|
+
limit: 200,
|
|
1672
|
+
});
|
|
1673
|
+
const matched = history.find((entry) => entry.id === messageId);
|
|
1674
|
+
if (matched) {
|
|
1675
|
+
return {
|
|
1676
|
+
...matched,
|
|
1677
|
+
...(matched.attachments
|
|
1678
|
+
? { attachments: matched.attachments.map(normalizeHostedMessageAttachment) }
|
|
1679
|
+
: {}),
|
|
1680
|
+
};
|
|
1681
|
+
}
|
|
1682
|
+
if (history.length < 200) {
|
|
1683
|
+
break;
|
|
1684
|
+
}
|
|
1685
|
+
const oldestCreatedAt = history.reduce((oldest, entry) => {
|
|
1686
|
+
return oldest == null || entry.createdAt < oldest ? entry.createdAt : oldest;
|
|
1687
|
+
}, null);
|
|
1688
|
+
if (oldestCreatedAt == null || (before != null && oldestCreatedAt >= before)) {
|
|
1689
|
+
break;
|
|
1690
|
+
}
|
|
1691
|
+
before = oldestCreatedAt;
|
|
1692
|
+
}
|
|
1693
|
+
return null;
|
|
1694
|
+
}
|
|
1736
1695
|
resolveAttentionStatePath(channelId) {
|
|
1737
1696
|
return resolveAttentionStatePath(this.config.stateRootDir, channelId);
|
|
1738
1697
|
}
|
|
1698
|
+
resolveCompactionStatePath(channelId) {
|
|
1699
|
+
return resolveCompactionStatePath(this.config.stateRootDir, channelId);
|
|
1700
|
+
}
|
|
1739
1701
|
buildDefaultAttentionSnapshot(options) {
|
|
1740
1702
|
return buildNormalizedAttentionSnapshot({
|
|
1741
1703
|
observedAt: options?.observedAt,
|
|
1742
1704
|
turnExecutionId: options?.turnExecutionId,
|
|
1743
1705
|
});
|
|
1744
1706
|
}
|
|
1707
|
+
async ensureCompactionSnapshotLoaded(channelId) {
|
|
1708
|
+
if (this.compactionSnapshotLoadedChannels.has(channelId)) {
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
this.compactionSnapshotLoadedChannels.add(channelId);
|
|
1712
|
+
try {
|
|
1713
|
+
const content = await readFile(this.resolveCompactionStatePath(channelId), 'utf8');
|
|
1714
|
+
const snapshot = parsePersistedCompactionSnapshot(JSON.parse(content));
|
|
1715
|
+
if (snapshot) {
|
|
1716
|
+
this.compactionSnapshotByChannel.set(channelId, snapshot);
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
catch (error) {
|
|
1720
|
+
const code = error.code;
|
|
1721
|
+
if (code !== 'ENOENT') {
|
|
1722
|
+
this.logger.error('failed to load compaction state', {
|
|
1723
|
+
channelId,
|
|
1724
|
+
error: summarizeError(error),
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
this.compactionSnapshotByChannel.delete(channelId);
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
async commitCompactionSnapshot(channelId, snapshot) {
|
|
1731
|
+
if (snapshot.stage === 'started') {
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1734
|
+
await writePrivateJsonFile(this.resolveCompactionStatePath(channelId), snapshot);
|
|
1735
|
+
}
|
|
1736
|
+
enqueueCompactionSnapshotCommit(channelId, snapshot) {
|
|
1737
|
+
const previous = this.compactionSnapshotWriteQueueByChannel.get(channelId) ?? Promise.resolve();
|
|
1738
|
+
const next = previous
|
|
1739
|
+
.catch(() => { })
|
|
1740
|
+
.then(() => this.commitCompactionSnapshot(channelId, snapshot));
|
|
1741
|
+
this.compactionSnapshotWriteQueueByChannel.set(channelId, next);
|
|
1742
|
+
void next.catch((error) => {
|
|
1743
|
+
this.logger.error('failed to persist compaction state', {
|
|
1744
|
+
channelId,
|
|
1745
|
+
error: summarizeError(error),
|
|
1746
|
+
});
|
|
1747
|
+
}).finally(() => {
|
|
1748
|
+
if (this.compactionSnapshotWriteQueueByChannel.get(channelId) === next) {
|
|
1749
|
+
this.compactionSnapshotWriteQueueByChannel.delete(channelId);
|
|
1750
|
+
}
|
|
1751
|
+
});
|
|
1752
|
+
}
|
|
1753
|
+
noteCompactionProgress(channelId, update, options) {
|
|
1754
|
+
const baseline = update.stage === 'started'
|
|
1755
|
+
? undefined
|
|
1756
|
+
: this.activeCompactionByChannel.get(channelId)
|
|
1757
|
+
?? this.compactionSnapshotByChannel.get(channelId);
|
|
1758
|
+
const nextSnapshot = mergeCompactionSnapshot(baseline, {
|
|
1759
|
+
provider: update.provider,
|
|
1760
|
+
stage: update.stage,
|
|
1761
|
+
observedAt: Date.now(),
|
|
1762
|
+
turnExecutionId: options?.turnExecutionId,
|
|
1763
|
+
usedTokens: update.usedTokens,
|
|
1764
|
+
contextWindowTokens: update.contextWindowTokens,
|
|
1765
|
+
failureReason: update.failureReason,
|
|
1766
|
+
});
|
|
1767
|
+
this.logger.debug('provider compaction update', {
|
|
1768
|
+
channelId,
|
|
1769
|
+
provider: nextSnapshot.provider,
|
|
1770
|
+
stage: nextSnapshot.stage,
|
|
1771
|
+
usedTokens: nextSnapshot.usedTokens,
|
|
1772
|
+
contextWindowTokens: nextSnapshot.contextWindowTokens,
|
|
1773
|
+
failureReason: nextSnapshot.failureReason,
|
|
1774
|
+
turnExecutionId: nextSnapshot.turnExecutionId,
|
|
1775
|
+
});
|
|
1776
|
+
if (nextSnapshot.stage === 'started') {
|
|
1777
|
+
this.activeCompactionByChannel.set(channelId, nextSnapshot);
|
|
1778
|
+
return;
|
|
1779
|
+
}
|
|
1780
|
+
this.activeCompactionByChannel.delete(channelId);
|
|
1781
|
+
this.compactionSnapshotByChannel.set(channelId, nextSnapshot);
|
|
1782
|
+
this.enqueueCompactionSnapshotCommit(channelId, nextSnapshot);
|
|
1783
|
+
}
|
|
1745
1784
|
/**
|
|
1746
1785
|
* Record which provider session worked this task, as agent.session_id.
|
|
1747
1786
|
*
|
|
@@ -1997,14 +2036,15 @@ export class AgentsHost {
|
|
|
1997
2036
|
taskThreadContextActive: taskThreadContext.state === 'active',
|
|
1998
2037
|
});
|
|
1999
2038
|
}
|
|
2000
|
-
resolveCollaborationCapabilityDeclarationForTurn() {
|
|
2039
|
+
resolveCollaborationCapabilityDeclarationForTurn(taskThreadContext) {
|
|
2001
2040
|
if (!this.collaborationCapabilitiesDiagnosticsEnabled) {
|
|
2002
2041
|
return undefined;
|
|
2003
2042
|
}
|
|
2004
2043
|
return {
|
|
2005
2044
|
...(this.collaborationOutcomeModelEnabled ? { collaborationOutcome: true } : {}),
|
|
2006
2045
|
...(this.attentionFollowSemanticsEnabled ? { attentionSnapshot: true } : {}),
|
|
2007
|
-
...(this.
|
|
2046
|
+
...(this.contextInjectionEnabled ? { compactionSnapshot: true } : {}),
|
|
2047
|
+
...(this.taskThreadCollaborationContractEnabled && taskThreadContext.state === 'active'
|
|
2008
2048
|
? { taskThreadCollaborationContract: true }
|
|
2009
2049
|
: {}),
|
|
2010
2050
|
missedCollaborationDiagnostic: true,
|
|
@@ -2037,10 +2077,11 @@ export class AgentsHost {
|
|
|
2037
2077
|
reason: 'wake-suppressed-attention-policy',
|
|
2038
2078
|
});
|
|
2039
2079
|
}
|
|
2040
|
-
async runUngatedTurn(msg, authorId, content, taskThreadContext, attentionSnapshot) {
|
|
2080
|
+
async runUngatedTurn(msg, authorId, content, taskThreadContext, attentionSnapshot, incomingParts) {
|
|
2041
2081
|
const executed = await this.executeTurn(msg, authorId, content, {
|
|
2042
2082
|
attentionSnapshot,
|
|
2043
2083
|
taskThreadContext,
|
|
2084
|
+
incomingParts,
|
|
2044
2085
|
});
|
|
2045
2086
|
if (executed.completed && executed.reply) {
|
|
2046
2087
|
this.updateAwaitingUserState(msg.channel_id, executed.reply.awaitingUser);
|
|
@@ -2100,18 +2141,12 @@ export class AgentsHost {
|
|
|
2100
2141
|
const turnExecutionId = activeTurn?.turnExecutionId ?? null;
|
|
2101
2142
|
const stopTyping = silentTurn ? () => { } : this.borgee.startTyping(channelId);
|
|
2102
2143
|
const collaborationGrounding = await this.buildCollaborationGrounding(authorId, options?.incomingAuthorKind, activeTurn, options?.kickoff);
|
|
2103
|
-
const
|
|
2104
|
-
!deferDelivery &&
|
|
2105
|
-
!this.collaborationEnabled &&
|
|
2106
|
-
options?.incomingAuthorKind !== 'agent' &&
|
|
2107
|
-
providerUsesDraftProgress(this.config.agent.provider) &&
|
|
2108
|
-
activeTurn?.protocol === undefined;
|
|
2109
|
-
const usePrivateCollaborationDraftProgress = !silentTurn &&
|
|
2144
|
+
const publishesCollaborationDraft = !silentTurn &&
|
|
2110
2145
|
!deferDelivery &&
|
|
2111
2146
|
this.collaborationEnabled &&
|
|
2112
2147
|
turnExecutionId != null &&
|
|
2113
|
-
providerUsesDraftProgress(this.config.agent.provider) &&
|
|
2114
2148
|
activeTurn?.protocol === undefined;
|
|
2149
|
+
const observeProviderProgress = publishesCollaborationDraft || this.config.agent.provider === 'claude';
|
|
2115
2150
|
const turnContext = {
|
|
2116
2151
|
eventType: msg.type,
|
|
2117
2152
|
channelId,
|
|
@@ -2132,7 +2167,15 @@ export class AgentsHost {
|
|
|
2132
2167
|
? this.collaborationOutcomeByChannel.get(channelId)
|
|
2133
2168
|
: undefined;
|
|
2134
2169
|
const missedCollaborationDiagnostic = this.resolveMissedCollaborationDiagnosticForTurn(channelId);
|
|
2135
|
-
const collaborationCapabilities = this.resolveCollaborationCapabilityDeclarationForTurn();
|
|
2170
|
+
const collaborationCapabilities = this.resolveCollaborationCapabilityDeclarationForTurn(taskThreadContext);
|
|
2171
|
+
const incomingParts = options?.incomingParts
|
|
2172
|
+
?? buildHostedTurnContentParts({
|
|
2173
|
+
text: String(msg.content ?? msg.body ?? content),
|
|
2174
|
+
attachments: msg.attachments,
|
|
2175
|
+
});
|
|
2176
|
+
const activityReporter = this.beginTurnActivity(channelId, activeTurn?.generation);
|
|
2177
|
+
await this.ensureCompactionSnapshotLoaded(channelId);
|
|
2178
|
+
const compactionSnapshot = this.compactionSnapshotByChannel.get(channelId);
|
|
2136
2179
|
try {
|
|
2137
2180
|
reply = await this.provider.generateReply({
|
|
2138
2181
|
agentName: this.config.agent.agentName,
|
|
@@ -2140,6 +2183,7 @@ export class AgentsHost {
|
|
|
2140
2183
|
channelId,
|
|
2141
2184
|
incomingAuthorId: authorId,
|
|
2142
2185
|
incomingContent: content,
|
|
2186
|
+
incomingParts,
|
|
2143
2187
|
incomingEventKind: msg.type,
|
|
2144
2188
|
incomingMessageType: msg.message_type,
|
|
2145
2189
|
collaboration: turnExecutionId || options?.kickoff
|
|
@@ -2154,17 +2198,30 @@ export class AgentsHost {
|
|
|
2154
2198
|
: undefined,
|
|
2155
2199
|
collaborationOutcome,
|
|
2156
2200
|
attentionSnapshot,
|
|
2201
|
+
compactionSnapshot,
|
|
2157
2202
|
...(taskThreadCollaborationContract ? { taskThreadCollaborationContract } : {}),
|
|
2158
2203
|
...(collaborationCapabilities ? { collaborationCapabilities } : {}),
|
|
2159
2204
|
...(missedCollaborationDiagnostic ? { missedCollaborationDiagnostic } : {}),
|
|
2160
|
-
},
|
|
2205
|
+
}, observeProviderProgress
|
|
2161
2206
|
? {
|
|
2162
2207
|
onProgress: (update) => {
|
|
2163
2208
|
if (!this.canAcceptTurnOutput(channelId, activeTurn?.generation)) {
|
|
2164
2209
|
return;
|
|
2165
2210
|
}
|
|
2166
|
-
if (
|
|
2167
|
-
this.
|
|
2211
|
+
if (update.type === 'compaction') {
|
|
2212
|
+
this.noteCompactionProgress(channelId, update.compaction, {
|
|
2213
|
+
turnExecutionId: activeTurn?.turnExecutionId ?? undefined,
|
|
2214
|
+
});
|
|
2215
|
+
return;
|
|
2216
|
+
}
|
|
2217
|
+
const reportedActivity = toReportedActivity(update);
|
|
2218
|
+
if (reportedActivity) {
|
|
2219
|
+
activityReporter.progress(reportedActivity);
|
|
2220
|
+
}
|
|
2221
|
+
if (update.type !== 'text' || update.stream !== 'answer') {
|
|
2222
|
+
return;
|
|
2223
|
+
}
|
|
2224
|
+
if (!publishesCollaborationDraft) {
|
|
2168
2225
|
return;
|
|
2169
2226
|
}
|
|
2170
2227
|
this.publishCollaborationDraft(channelId, activeTurn, update.text);
|
|
@@ -2187,7 +2244,6 @@ export class AgentsHost {
|
|
|
2187
2244
|
turnExecutionId: activeTurn?.turnExecutionId ?? undefined,
|
|
2188
2245
|
});
|
|
2189
2246
|
if (!(await this.canAcceptCompletedTurnOutput(channelId, activeTurn))) {
|
|
2190
|
-
await this.discardDraft(channelId);
|
|
2191
2247
|
if (activeTurn?.turnExecutionId) {
|
|
2192
2248
|
this.clearCollaborationDraft(channelId, {
|
|
2193
2249
|
generation: activeTurn.generation,
|
|
@@ -2236,35 +2292,7 @@ export class AgentsHost {
|
|
|
2236
2292
|
completed: true,
|
|
2237
2293
|
};
|
|
2238
2294
|
}
|
|
2239
|
-
if (
|
|
2240
|
-
const draft = this.progressDrafts.get(channelId);
|
|
2241
|
-
if (!this.started) {
|
|
2242
|
-
await draft?.discard().catch(() => { });
|
|
2243
|
-
this.progressDrafts.delete(channelId);
|
|
2244
|
-
return { reply, stale: true, completed: false };
|
|
2245
|
-
}
|
|
2246
|
-
const deliveredMessageId = await (draft ?? this.ensureProgressDraft(channelId, activeTurn?.generation, activeTurn?.delivery)).finalize(publicReplyText);
|
|
2247
|
-
this.progressDrafts.delete(channelId);
|
|
2248
|
-
this.logger.debug('completed turn', {
|
|
2249
|
-
...turnContext,
|
|
2250
|
-
durationMs: Date.now() - turnStartedAt,
|
|
2251
|
-
replyLength: publicReplyText.length,
|
|
2252
|
-
controlKind: reply?.control?.kind ?? null,
|
|
2253
|
-
controlMalformed: reply?.controlMalformed ?? false,
|
|
2254
|
-
delivery: 'draft',
|
|
2255
|
-
});
|
|
2256
|
-
return {
|
|
2257
|
-
reply,
|
|
2258
|
-
deliveredMessageId,
|
|
2259
|
-
publicReplyText,
|
|
2260
|
-
visibleReplyBody,
|
|
2261
|
-
deliveryState: deliveredMessageId ? 'draft-finalized' : 'not-delivered',
|
|
2262
|
-
appliedAttentionSnapshot,
|
|
2263
|
-
stale: false,
|
|
2264
|
-
completed: true,
|
|
2265
|
-
};
|
|
2266
|
-
}
|
|
2267
|
-
else if (this.started && !this.controlPlaneClosed && hasVisibleText(publicReplyText)) {
|
|
2295
|
+
if (this.started && !this.controlPlaneClosed && hasVisibleText(publicReplyText)) {
|
|
2268
2296
|
const posted = await this.borgee.postMessage({
|
|
2269
2297
|
channelId,
|
|
2270
2298
|
body: visibleReplyBody,
|
|
@@ -2278,7 +2306,7 @@ export class AgentsHost {
|
|
|
2278
2306
|
controlMalformed: reply?.controlMalformed ?? false,
|
|
2279
2307
|
delivery: 'post',
|
|
2280
2308
|
});
|
|
2281
|
-
if (
|
|
2309
|
+
if (publishesCollaborationDraft) {
|
|
2282
2310
|
this.finalizeCollaborationDraft(channelId, activeTurn, posted.messageId, visibleReplyBody);
|
|
2283
2311
|
}
|
|
2284
2312
|
return {
|
|
@@ -2296,9 +2324,9 @@ export class AgentsHost {
|
|
|
2296
2324
|
...turnContext,
|
|
2297
2325
|
durationMs: Date.now() - turnStartedAt,
|
|
2298
2326
|
replyLength: publicReplyText.length,
|
|
2299
|
-
delivery:
|
|
2327
|
+
delivery: 'post',
|
|
2300
2328
|
});
|
|
2301
|
-
if (
|
|
2329
|
+
if (publishesCollaborationDraft && activeTurn?.turnExecutionId) {
|
|
2302
2330
|
this.clearCollaborationDraft(channelId, {
|
|
2303
2331
|
generation: activeTurn.generation,
|
|
2304
2332
|
turnExecutionId: activeTurn.turnExecutionId,
|
|
@@ -2316,7 +2344,6 @@ export class AgentsHost {
|
|
|
2316
2344
|
};
|
|
2317
2345
|
}
|
|
2318
2346
|
catch (error) {
|
|
2319
|
-
await this.discardDraft(channelId);
|
|
2320
2347
|
if (activeTurn?.turnExecutionId) {
|
|
2321
2348
|
this.clearCollaborationDraft(channelId, {
|
|
2322
2349
|
generation: activeTurn.generation,
|
|
@@ -2342,8 +2369,31 @@ export class AgentsHost {
|
|
|
2342
2369
|
}
|
|
2343
2370
|
finally {
|
|
2344
2371
|
stopTyping();
|
|
2372
|
+
activityReporter.end();
|
|
2345
2373
|
}
|
|
2346
2374
|
}
|
|
2375
|
+
/**
|
|
2376
|
+
* Opens this turn's report on the activity rail.
|
|
2377
|
+
*
|
|
2378
|
+
* The generation is captured here, where the turn's own is, and every report
|
|
2379
|
+
* is checked against the channel's current turn before it leaves. That is what
|
|
2380
|
+
* keeps a superseded turn from announcing a boundary for a turn that no longer
|
|
2381
|
+
* exists — including its ending, which a window opened before it was replaced
|
|
2382
|
+
* would otherwise carry out on its behalf.
|
|
2383
|
+
*
|
|
2384
|
+
* Every turn reports, including the silent and deferred ones that deliver
|
|
2385
|
+
* nothing to the channel. The rail writes nothing anywhere, and the question
|
|
2386
|
+
* it answers — whether anything is happening in this channel — is worth the
|
|
2387
|
+
* same answer for a turn that keeps its reply to itself.
|
|
2388
|
+
*/
|
|
2389
|
+
beginTurnActivity(channelId, generation) {
|
|
2390
|
+
const reporter = this.borgee.reportTurnActivity({
|
|
2391
|
+
channelId,
|
|
2392
|
+
canReport: () => this.canAcceptTurnOutput(channelId, generation),
|
|
2393
|
+
});
|
|
2394
|
+
reporter.start();
|
|
2395
|
+
return reporter;
|
|
2396
|
+
}
|
|
2347
2397
|
canAcceptTurnOutput(channelId, generation) {
|
|
2348
2398
|
if (!this.started || this.controlPlaneClosed) {
|
|
2349
2399
|
return false;
|
|
@@ -2386,27 +2436,19 @@ export class AgentsHost {
|
|
|
2386
2436
|
this.clearProtocolFallback(channelState);
|
|
2387
2437
|
return false;
|
|
2388
2438
|
}
|
|
2389
|
-
|
|
2390
|
-
const previous = this.
|
|
2439
|
+
enqueueChannelTurn(channelId, task) {
|
|
2440
|
+
const previous = this.channelTurnQueues.get(channelId) ?? Promise.resolve();
|
|
2391
2441
|
const next = previous
|
|
2392
2442
|
.catch(() => { })
|
|
2393
2443
|
.then(task)
|
|
2394
2444
|
.finally(() => {
|
|
2395
|
-
if (this.
|
|
2396
|
-
this.
|
|
2445
|
+
if (this.channelTurnQueues.get(channelId) === next) {
|
|
2446
|
+
this.channelTurnQueues.delete(channelId);
|
|
2397
2447
|
}
|
|
2398
2448
|
});
|
|
2399
|
-
this.
|
|
2449
|
+
this.channelTurnQueues.set(channelId, next);
|
|
2400
2450
|
return next;
|
|
2401
2451
|
}
|
|
2402
|
-
ensureProgressDraft(channelId, generation, creationMetadata) {
|
|
2403
|
-
let draft = this.progressDrafts.get(channelId);
|
|
2404
|
-
if (!draft) {
|
|
2405
|
-
draft = new DraftMessageController(this.borgee, channelId, () => !this.controlPlaneClosed && this.canAcceptTurnOutput(channelId, generation), this.logger, creationMetadata);
|
|
2406
|
-
this.progressDrafts.set(channelId, draft);
|
|
2407
|
-
}
|
|
2408
|
-
return draft;
|
|
2409
|
-
}
|
|
2410
2452
|
publishCollaborationDraft(channelId, activeTurn, body) {
|
|
2411
2453
|
if (!activeTurn?.turnExecutionId || !hasVisibleText(body)) {
|
|
2412
2454
|
return;
|
|
@@ -2495,14 +2537,6 @@ export class AgentsHost {
|
|
|
2495
2537
|
}
|
|
2496
2538
|
return { ...draft.snapshot };
|
|
2497
2539
|
}
|
|
2498
|
-
async discardDraft(channelId) {
|
|
2499
|
-
const draft = this.progressDrafts.get(channelId);
|
|
2500
|
-
if (!draft) {
|
|
2501
|
-
return;
|
|
2502
|
-
}
|
|
2503
|
-
this.progressDrafts.delete(channelId);
|
|
2504
|
-
await draft.discard().catch(() => { });
|
|
2505
|
-
}
|
|
2506
2540
|
supersedeActiveTurn(channelId, activeTurn) {
|
|
2507
2541
|
if (activeTurn.superseded) {
|
|
2508
2542
|
return;
|
|
@@ -2520,7 +2554,6 @@ export class AgentsHost {
|
|
|
2520
2554
|
}
|
|
2521
2555
|
activeTurn.superseded = true;
|
|
2522
2556
|
activeTurn.turnExecutionId = null;
|
|
2523
|
-
void this.discardDraft(channelId);
|
|
2524
2557
|
}
|
|
2525
2558
|
invalidateChannelTurn(channelId) {
|
|
2526
2559
|
const activeTurn = this.collaborationChannels.get(channelId)?.activeTurn;
|
|
@@ -2683,17 +2716,22 @@ export class AgentsHost {
|
|
|
2683
2716
|
});
|
|
2684
2717
|
return null;
|
|
2685
2718
|
}
|
|
2686
|
-
const
|
|
2687
|
-
|
|
2719
|
+
const incomingParts = buildHostedTurnContentParts({
|
|
2720
|
+
text: String(msg.content ?? msg.body ?? ''),
|
|
2721
|
+
attachments: msg.attachments,
|
|
2722
|
+
});
|
|
2723
|
+
if (incomingParts.length === 0) {
|
|
2688
2724
|
this.logger.debug('ignored empty message', {
|
|
2689
2725
|
channelId: msg.channel_id,
|
|
2690
2726
|
messageId: msg.message_id,
|
|
2691
2727
|
});
|
|
2692
2728
|
return null;
|
|
2693
2729
|
}
|
|
2730
|
+
const content = buildHostedIncomingContentText(incomingParts);
|
|
2694
2731
|
return {
|
|
2695
2732
|
authorId,
|
|
2696
2733
|
content,
|
|
2734
|
+
incomingParts,
|
|
2697
2735
|
};
|
|
2698
2736
|
}
|
|
2699
2737
|
// Only a task assigned to this agent drives a task-assignment turn: it keeps the message a
|