@borgee/agents-host 0.2.26 → 0.2.29
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 +27 -4
- package/dist/agents-host.d.ts +25 -0
- package/dist/agents-host.js +627 -14
- package/dist/compatibility-gates.d.ts +8 -1
- package/dist/compatibility-gates.js +43 -3
- package/dist/config.js +3 -3
- package/dist/context/attention.d.ts +12 -0
- package/dist/context/attention.js +137 -0
- package/dist/context/collaboration-capabilities-diagnostics.d.ts +3 -0
- package/dist/context/collaboration-capabilities-diagnostics.js +18 -0
- package/dist/context/collaboration-outcome.d.ts +2 -0
- package/dist/context/collaboration-outcome.js +26 -0
- package/dist/context/injection.d.ts +26 -1
- package/dist/context/injection.js +7 -2
- package/dist/context/prompt.js +61 -0
- package/dist/context/task-thread-collaboration.d.ts +6 -0
- package/dist/context/task-thread-collaboration.js +31 -0
- package/dist/context/turn-preparation.js +30 -1
- package/dist/managed-daemon.js +183 -47
- package/dist/providers/awaiting-user.js +46 -5
- package/dist/providers/codex/project-doc.js +24 -0
- package/dist/state-paths.d.ts +1 -0
- package/dist/state-paths.js +3 -0
- package/dist/types.d.ts +97 -4
- package/package.json +1 -1
package/dist/agents-host.js
CHANGED
|
@@ -4,12 +4,14 @@ 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
6
|
import { createDurableCursorStore } from './durable-cursor-store.js';
|
|
7
|
-
import { COLLABORATION_SKILL_FIRST_COMPATIBILITY_GATE, CONTEXT_INJECTION_COMPATIBILITY_GATE, LOCALHOST_GATEWAY_COMPATIBILITY_GATE, POLICY_AUDIT_ENFORCEMENT_COMPATIBILITY_GATE, resolveInternalPolicyMode, resolveInternalCompatibilityGates, SKILL_RUNTIME_COMPATIBILITY_GATE, TOKEN_BINDING_COMPATIBILITY_GATE, } from './compatibility-gates.js';
|
|
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
|
+
import { buildNormalizedAttentionSnapshot, parsePersistedAttentionSnapshot, } from './context/attention.js';
|
|
9
|
+
import { projectTaskThreadCollaborationContract } from './context/task-thread-collaboration.js';
|
|
8
10
|
import { createLocalhostGatewayController, LOCALHOST_GATEWAY_COLLABORATION_TARGET_COOLDOWN_MS, } from './gateway/localhost-gateway.js';
|
|
9
|
-
import { extractTaskIdFromTaskAssignmentContent } from './context/injection.js';
|
|
11
|
+
import { extractTaskIdFromTaskAssignmentContent, resolveChannelContextPayloadPath, } from './context/injection.js';
|
|
10
12
|
import { AuthorizationAuditSink, } from './policy/authorization-audit.js';
|
|
11
13
|
import { createProvider } from './providers/create-provider.js';
|
|
12
|
-
import { resolveSharedProtocolKickoffDecisionPath, resolveSharedProtocolStatusPath, } from './state-paths.js';
|
|
14
|
+
import { resolveAttentionStatePath, resolveSharedProtocolKickoffDecisionPath, resolveSharedProtocolStatusPath, } from './state-paths.js';
|
|
13
15
|
import { appendVisibleMention as appendVisibleBodyMention, extractVisibleMentionIds as extractCanonicalMentionIds, } from './visible-mentions.js';
|
|
14
16
|
import { waitForTaskForThread } from './task-thread-resolution.js';
|
|
15
17
|
const PRIVATE_STATE_ROOT_MODE = 0o700;
|
|
@@ -20,6 +22,7 @@ const PROTOCOL_KICKOFF_DECISION_TIMEOUT_MS = PROTOCOL_PROVIDER_TURN_TIMEOUT_MS;
|
|
|
20
22
|
const PROTOCOL_KICKOFF_DECISION_POLL_INTERVAL_MS = 50;
|
|
21
23
|
const PROTOCOL_KICKOFF_ROUND_LIMIT = 20;
|
|
22
24
|
const COLLABORATION_LATE_SEND_GRACE_MS = 5_000;
|
|
25
|
+
const ORDINARY_HUMAN_EVENT_KINDS = new Set(['', 'message']);
|
|
23
26
|
async function ensurePrivateStateRoot(path) {
|
|
24
27
|
await mkdir(path, { recursive: true, mode: PRIVATE_STATE_ROOT_MODE });
|
|
25
28
|
await chmod(path, PRIVATE_STATE_ROOT_MODE);
|
|
@@ -371,9 +374,17 @@ export class AgentsHost {
|
|
|
371
374
|
provider;
|
|
372
375
|
borgee;
|
|
373
376
|
logger;
|
|
377
|
+
contextInjectionEnabled;
|
|
374
378
|
collaborationEnabled;
|
|
379
|
+
collaborationOutcomeModelEnabled;
|
|
380
|
+
attentionFollowSemanticsEnabled;
|
|
381
|
+
taskThreadCollaborationContractEnabled;
|
|
382
|
+
collaborationCapabilitiesDiagnosticsEnabled;
|
|
375
383
|
activeTurns = new Set();
|
|
376
384
|
awaitingUserByChannel = new Map();
|
|
385
|
+
collaborationOutcomeByChannel = new Map();
|
|
386
|
+
attentionSnapshotByChannel = new Map();
|
|
387
|
+
missedCollaborationDiagnosticByChannel = new Map();
|
|
377
388
|
progressChannelQueues = new Map();
|
|
378
389
|
progressDrafts = new Map();
|
|
379
390
|
collaborationDrafts = new Map();
|
|
@@ -393,6 +404,7 @@ export class AgentsHost {
|
|
|
393
404
|
this.logger = new HostLogger(deps?.runtimeOptions);
|
|
394
405
|
const compatibilityGates = resolveInternalCompatibilityGates();
|
|
395
406
|
const contextInjectionGateEnabled = compatibilityGates.has(CONTEXT_INJECTION_COMPATIBILITY_GATE);
|
|
407
|
+
this.contextInjectionEnabled = contextInjectionGateEnabled;
|
|
396
408
|
const skillRuntimeGateEnabled = compatibilityGates.has(SKILL_RUNTIME_COMPATIBILITY_GATE);
|
|
397
409
|
const localhostGatewayGateEnabled = contextInjectionGateEnabled &&
|
|
398
410
|
skillRuntimeGateEnabled &&
|
|
@@ -403,6 +415,14 @@ export class AgentsHost {
|
|
|
403
415
|
compatibilityGates.has(COLLABORATION_SKILL_FIRST_COMPATIBILITY_GATE) &&
|
|
404
416
|
policyAuditGateEnabled &&
|
|
405
417
|
resolveInternalPolicyMode(policyAuditGateEnabled) === 'enforce';
|
|
418
|
+
this.collaborationOutcomeModelEnabled = compatibilityGates.has(COLLABORATION_OUTCOME_MODEL_COMPATIBILITY_GATE);
|
|
419
|
+
this.attentionFollowSemanticsEnabled = compatibilityGates.has(ATTENTION_FOLLOW_SEMANTICS_COMPATIBILITY_GATE);
|
|
420
|
+
this.taskThreadCollaborationContractEnabled =
|
|
421
|
+
contextInjectionGateEnabled
|
|
422
|
+
&& compatibilityGates.has(TASK_THREAD_COLLABORATION_CONTRACT_COMPATIBILITY_GATE);
|
|
423
|
+
this.collaborationCapabilitiesDiagnosticsEnabled =
|
|
424
|
+
contextInjectionGateEnabled
|
|
425
|
+
&& compatibilityGates.has(COLLABORATION_CAPABILITIES_DIAGNOSTICS_COMPATIBILITY_GATE);
|
|
406
426
|
this.runtime = createDefaultHostRuntime(config, {
|
|
407
427
|
logger: this.logger,
|
|
408
428
|
provider: deps?.provider,
|
|
@@ -481,6 +501,9 @@ export class AgentsHost {
|
|
|
481
501
|
this.clearCollaborationDraft(channelId);
|
|
482
502
|
}
|
|
483
503
|
this.awaitingUserByChannel.clear();
|
|
504
|
+
this.collaborationOutcomeByChannel.clear();
|
|
505
|
+
this.attentionSnapshotByChannel.clear();
|
|
506
|
+
this.missedCollaborationDiagnosticByChannel.clear();
|
|
484
507
|
this.controlPlaneClosed = true;
|
|
485
508
|
await this.runtime.stop();
|
|
486
509
|
await Promise.allSettled([...this.activeTurns]);
|
|
@@ -504,10 +527,25 @@ export class AgentsHost {
|
|
|
504
527
|
if (!normalized) {
|
|
505
528
|
return;
|
|
506
529
|
}
|
|
530
|
+
let taskThreadContext = { state: 'inactive' };
|
|
531
|
+
let attentionSnapshot;
|
|
532
|
+
if (this.attentionFollowSemanticsEnabled
|
|
533
|
+
|| this.taskThreadCollaborationContractEnabled) {
|
|
534
|
+
taskThreadContext = await this.buildTurnTaskThreadContext(msg, msg.channel_id);
|
|
535
|
+
if (this.attentionFollowSemanticsEnabled) {
|
|
536
|
+
const channelState = this.ensureCollaborationChannelState(msg.channel_id);
|
|
537
|
+
const authorKind = await this.classifyAuthor(normalized.authorId, false);
|
|
538
|
+
await this.ensureAttentionSnapshotLoaded(msg.channel_id, channelState);
|
|
539
|
+
attentionSnapshot = this.getAttentionSnapshotForTurn(msg.channel_id, taskThreadContext);
|
|
540
|
+
if (this.isHumanWakeFiltered(msg, authorKind, attentionSnapshot)) {
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
507
545
|
if (msg.message_type === 'task_assignment') {
|
|
508
546
|
await this.markAssignedTaskInProgress(msg.channel_id, normalized.content);
|
|
509
547
|
}
|
|
510
|
-
await this.runUngatedTurn(msg, normalized.authorId, normalized.content);
|
|
548
|
+
await this.runUngatedTurn(msg, normalized.authorId, normalized.content, taskThreadContext, attentionSnapshot);
|
|
511
549
|
}
|
|
512
550
|
async handleCollaborationMessage(msg) {
|
|
513
551
|
const normalized = await this.normalizeMessage(msg);
|
|
@@ -519,8 +557,11 @@ export class AgentsHost {
|
|
|
519
557
|
}
|
|
520
558
|
const channelState = this.ensureCollaborationChannelState(msg.channel_id);
|
|
521
559
|
await this.ensureProtocolStateLoaded(msg.channel_id, channelState);
|
|
560
|
+
await this.ensureAttentionSnapshotLoaded(msg.channel_id, channelState);
|
|
522
561
|
const selfAgentId = await this.ensureSelfAgentId();
|
|
523
562
|
const authorKind = await this.classifyAuthor(normalized.authorId, false);
|
|
563
|
+
const taskThreadContext = await this.buildTurnTaskThreadContext(msg, msg.channel_id);
|
|
564
|
+
const attentionSnapshot = this.getAttentionSnapshotForTurn(msg.channel_id, taskThreadContext);
|
|
524
565
|
if (channelState.protocol?.active) {
|
|
525
566
|
if (msg.message_id && authorKind === 'agent') {
|
|
526
567
|
await this.handleActiveProtocolParticipantMessage(msg, normalized, channelState, channelState.protocol, selfAgentId);
|
|
@@ -542,6 +583,7 @@ export class AgentsHost {
|
|
|
542
583
|
const blockedAuthorKind = await this.classifyBlockedWakeAuthor(normalized.authorId, channelState.blocked);
|
|
543
584
|
if (blockedAuthorKind === 'agent') {
|
|
544
585
|
channelState.blocked.suppressedAgentMessages += 1;
|
|
586
|
+
this.recordBlockedSuppressionOutcome(msg.channel_id, channelState);
|
|
545
587
|
return;
|
|
546
588
|
}
|
|
547
589
|
if (blockedAuthorKind === 'unknown') {
|
|
@@ -550,6 +592,8 @@ export class AgentsHost {
|
|
|
550
592
|
authorId: normalized.authorId,
|
|
551
593
|
authorKind: 'unknown',
|
|
552
594
|
content: normalized.content,
|
|
595
|
+
attentionSnapshot,
|
|
596
|
+
taskThreadContext,
|
|
553
597
|
});
|
|
554
598
|
const resumed = await this.tryResumeBlockedFromQueue(msg.channel_id, channelState);
|
|
555
599
|
if (resumed) {
|
|
@@ -566,6 +610,8 @@ export class AgentsHost {
|
|
|
566
610
|
authorId: normalized.authorId,
|
|
567
611
|
authorKind: 'human',
|
|
568
612
|
content: normalized.content,
|
|
613
|
+
attentionSnapshot,
|
|
614
|
+
taskThreadContext,
|
|
569
615
|
wakeBlockedState,
|
|
570
616
|
},
|
|
571
617
|
];
|
|
@@ -582,6 +628,9 @@ export class AgentsHost {
|
|
|
582
628
|
}
|
|
583
629
|
}
|
|
584
630
|
}
|
|
631
|
+
if (this.isHumanWakeFiltered(msg, authorKind, attentionSnapshot)) {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
585
634
|
if (authorKind !== 'agent' && channelState.activeTurn) {
|
|
586
635
|
this.supersedeActiveTurn(msg.channel_id, channelState.activeTurn);
|
|
587
636
|
}
|
|
@@ -593,6 +642,8 @@ export class AgentsHost {
|
|
|
593
642
|
authorId: normalized.authorId,
|
|
594
643
|
authorKind,
|
|
595
644
|
content: normalized.content,
|
|
645
|
+
attentionSnapshot,
|
|
646
|
+
taskThreadContext,
|
|
596
647
|
});
|
|
597
648
|
await this.ensureCollaborationProcessor(msg.channel_id, channelState);
|
|
598
649
|
}
|
|
@@ -681,6 +732,7 @@ export class AgentsHost {
|
|
|
681
732
|
};
|
|
682
733
|
}
|
|
683
734
|
async evaluateIssuerProtocolKickoff(msg, normalized, protocolCandidate, pendingDecision) {
|
|
735
|
+
const priorMissedCollaborationDiagnostic = this.resolveMissedCollaborationDiagnosticForTurn(msg.channel_id);
|
|
684
736
|
const executed = await this.executeTurn(msg, normalized.authorId, normalized.content, {
|
|
685
737
|
kickoff: protocolCandidate,
|
|
686
738
|
silent: true,
|
|
@@ -688,6 +740,11 @@ export class AgentsHost {
|
|
|
688
740
|
if (executed.stale) {
|
|
689
741
|
return null;
|
|
690
742
|
}
|
|
743
|
+
this.recordCollaborationOutcome(msg.channel_id, {
|
|
744
|
+
responseState: executed.completed ? 'responded' : 'failed',
|
|
745
|
+
deliveryState: executed.deliveryState ?? 'not-delivered',
|
|
746
|
+
});
|
|
747
|
+
this.updateMissedCollaborationDiagnostic(msg.channel_id, priorMissedCollaborationDiagnostic);
|
|
691
748
|
const reply = executed.reply;
|
|
692
749
|
const rounds = reply?.control?.kind === 'start-protocol'
|
|
693
750
|
? this.validateProtocolKickoffRounds(reply.control.rounds)
|
|
@@ -958,6 +1015,7 @@ export class AgentsHost {
|
|
|
958
1015
|
queue: [],
|
|
959
1016
|
running: false,
|
|
960
1017
|
protocolLoaded: false,
|
|
1018
|
+
attentionLoaded: false,
|
|
961
1019
|
};
|
|
962
1020
|
this.collaborationChannels.set(channelId, state);
|
|
963
1021
|
}
|
|
@@ -1010,6 +1068,7 @@ export class AgentsHost {
|
|
|
1010
1068
|
: await this.classifyQueuedBlockedWakeCandidate(queued, blocked);
|
|
1011
1069
|
if (classified === 'agent') {
|
|
1012
1070
|
blocked.suppressedAgentMessages += 1;
|
|
1071
|
+
this.recordBlockedSuppressionOutcome(channelId, channelState);
|
|
1013
1072
|
continue;
|
|
1014
1073
|
}
|
|
1015
1074
|
if (classified === 'unknown') {
|
|
@@ -1037,6 +1096,12 @@ export class AgentsHost {
|
|
|
1037
1096
|
await this.runProtocolManagedTurn(channelId, channelState, message);
|
|
1038
1097
|
return;
|
|
1039
1098
|
}
|
|
1099
|
+
const taskThreadContext = await this.buildTurnTaskThreadContext(message.raw, channelId);
|
|
1100
|
+
const attentionSnapshot = this.getAttentionSnapshotForTurn(channelId, taskThreadContext);
|
|
1101
|
+
if (!message.wakeBlockedState
|
|
1102
|
+
&& this.isHumanWakeFiltered(message.raw, message.authorKind, attentionSnapshot)) {
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1040
1105
|
channelState.generation += 1;
|
|
1041
1106
|
const activeTurn = {
|
|
1042
1107
|
generation: channelState.generation,
|
|
@@ -1051,24 +1116,66 @@ export class AgentsHost {
|
|
|
1051
1116
|
const executed = await this.executeTurn(message.raw, message.authorId, this.buildIncomingContent(message), {
|
|
1052
1117
|
activeTurn,
|
|
1053
1118
|
incomingAuthorKind: message.authorKind,
|
|
1119
|
+
attentionSnapshot,
|
|
1120
|
+
taskThreadContext,
|
|
1054
1121
|
});
|
|
1055
1122
|
channelState.activeTurn = undefined;
|
|
1056
1123
|
if (executed.stale) {
|
|
1057
1124
|
return;
|
|
1058
1125
|
}
|
|
1059
1126
|
if (executed.completed) {
|
|
1127
|
+
await this.commitAttentionSnapshot(channelId, executed.appliedAttentionSnapshot ?? attentionSnapshot);
|
|
1060
1128
|
this.rememberRecentTurn(channelState, activeTurn);
|
|
1061
1129
|
}
|
|
1062
|
-
if (executed.reply?.awaitingUser) {
|
|
1130
|
+
if (executed.completed && executed.reply?.awaitingUser) {
|
|
1063
1131
|
channelState.blocked = {
|
|
1064
1132
|
awaitingUser: executed.reply.awaitingUser,
|
|
1065
1133
|
suppressedAgentMessages: 0,
|
|
1066
1134
|
};
|
|
1067
1135
|
this.awaitingUserByChannel.set(channelId, executed.reply.awaitingUser);
|
|
1136
|
+
this.recordCollaborationOutcome(channelId, {
|
|
1137
|
+
responseState: 'blocked',
|
|
1138
|
+
deliveryState: executed.deliveryState ?? 'not-delivered',
|
|
1139
|
+
wakeState: 'waiting',
|
|
1140
|
+
blockedDetails: {
|
|
1141
|
+
question: executed.reply.awaitingUser.question,
|
|
1142
|
+
...(executed.reply.awaitingUser.reason
|
|
1143
|
+
? { reason: executed.reply.awaitingUser.reason }
|
|
1144
|
+
: {}),
|
|
1145
|
+
},
|
|
1146
|
+
turnExecutionId: activeTurn.turnExecutionId ?? undefined,
|
|
1147
|
+
});
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
if (!executed.completed && message.wakeBlockedState) {
|
|
1151
|
+
const existing = this.collaborationOutcomeByChannel.get(channelId);
|
|
1152
|
+
channelState.blocked = {
|
|
1153
|
+
awaitingUser: message.wakeBlockedState,
|
|
1154
|
+
suppressedAgentMessages: 0,
|
|
1155
|
+
};
|
|
1156
|
+
this.awaitingUserByChannel.set(channelId, message.wakeBlockedState);
|
|
1157
|
+
this.recordCollaborationOutcome(channelId, {
|
|
1158
|
+
responseState: 'blocked',
|
|
1159
|
+
deliveryState: existing?.responseState === 'blocked' ? existing.deliveryState : 'not-delivered',
|
|
1160
|
+
wakeState: 'waiting',
|
|
1161
|
+
blockedDetails: {
|
|
1162
|
+
question: message.wakeBlockedState.question,
|
|
1163
|
+
...(message.wakeBlockedState.reason
|
|
1164
|
+
? { reason: message.wakeBlockedState.reason }
|
|
1165
|
+
: {}),
|
|
1166
|
+
},
|
|
1167
|
+
...(existing?.turnExecutionId ? { turnExecutionId: existing.turnExecutionId } : {}),
|
|
1168
|
+
});
|
|
1068
1169
|
return;
|
|
1069
1170
|
}
|
|
1070
1171
|
channelState.blocked = undefined;
|
|
1071
1172
|
this.awaitingUserByChannel.delete(channelId);
|
|
1173
|
+
this.recordCollaborationOutcome(channelId, {
|
|
1174
|
+
responseState: executed.completed ? 'responded' : 'failed',
|
|
1175
|
+
deliveryState: executed.deliveryState ?? 'not-delivered',
|
|
1176
|
+
...(message.wakeBlockedState ? { wakeState: 'resumed-human' } : {}),
|
|
1177
|
+
turnExecutionId: activeTurn.turnExecutionId ?? undefined,
|
|
1178
|
+
});
|
|
1072
1179
|
}
|
|
1073
1180
|
async runProtocolManagedTurn(channelId, channelState, message) {
|
|
1074
1181
|
const protocolState = channelState.protocol;
|
|
@@ -1083,6 +1190,10 @@ export class AgentsHost {
|
|
|
1083
1190
|
const currentTurnIndex = protocolState.currentTurnIndex + 1;
|
|
1084
1191
|
const peerId = this.otherProtocolParticipant(protocolState, selfAgentId);
|
|
1085
1192
|
const isFinalTurn = currentTurnIndex >= protocolState.maxTurnCount;
|
|
1193
|
+
const taskThreadContext = message.taskThreadContext
|
|
1194
|
+
?? await this.buildTurnTaskThreadContext(message.raw, channelId);
|
|
1195
|
+
const attentionSnapshot = message.attentionSnapshot
|
|
1196
|
+
?? this.getAttentionSnapshotForTurn(channelId, taskThreadContext);
|
|
1086
1197
|
const activeTurn = {
|
|
1087
1198
|
generation: channelState.generation,
|
|
1088
1199
|
turnExecutionId: randomUUID(),
|
|
@@ -1108,25 +1219,40 @@ export class AgentsHost {
|
|
|
1108
1219
|
const executed = await this.executeTurn(message.raw, message.authorId, this.buildIncomingContent(message), {
|
|
1109
1220
|
activeTurn,
|
|
1110
1221
|
incomingAuthorKind: message.authorKind,
|
|
1222
|
+
attentionSnapshot,
|
|
1223
|
+
taskThreadContext,
|
|
1111
1224
|
});
|
|
1112
1225
|
channelState.activeTurn = undefined;
|
|
1113
1226
|
if (executed.stale || !protocolState.active) {
|
|
1114
1227
|
return;
|
|
1115
1228
|
}
|
|
1116
1229
|
if (executed.completed) {
|
|
1230
|
+
await this.commitAttentionSnapshot(channelId, executed.appliedAttentionSnapshot ?? attentionSnapshot);
|
|
1117
1231
|
this.rememberRecentTurn(channelState, activeTurn);
|
|
1118
1232
|
}
|
|
1119
1233
|
const deliveredMessageId = executed.deliveredMessageId ?? null;
|
|
1120
1234
|
const visibleMessageId = deliveredMessageId ?? protocolState.lastProtocolMessageId;
|
|
1121
1235
|
if (executed.reply?.controlMalformed) {
|
|
1236
|
+
this.recordCollaborationOutcome(channelId, {
|
|
1237
|
+
responseState: executed.completed ? 'responded' : 'failed',
|
|
1238
|
+
deliveryState: executed.deliveryState ?? 'not-delivered',
|
|
1239
|
+
...(message.wakeBlockedState ? { wakeState: 'resumed-human' } : {}),
|
|
1240
|
+
turnExecutionId: activeTurn.turnExecutionId ?? undefined,
|
|
1241
|
+
});
|
|
1122
1242
|
this.finishProtocolState(channelId, channelState, protocolState, visibleMessageId);
|
|
1123
1243
|
return;
|
|
1124
1244
|
}
|
|
1125
1245
|
if (!executed.reply?.control) {
|
|
1246
|
+
this.recordCollaborationOutcome(channelId, {
|
|
1247
|
+
responseState: executed.completed ? 'responded' : 'failed',
|
|
1248
|
+
deliveryState: executed.deliveryState ?? 'not-delivered',
|
|
1249
|
+
...(message.wakeBlockedState ? { wakeState: 'resumed-human' } : {}),
|
|
1250
|
+
turnExecutionId: activeTurn.turnExecutionId ?? undefined,
|
|
1251
|
+
});
|
|
1126
1252
|
this.finishProtocolState(channelId, channelState, protocolState, visibleMessageId);
|
|
1127
1253
|
return;
|
|
1128
1254
|
}
|
|
1129
|
-
if (executed.reply.control.kind === 'awaiting-user') {
|
|
1255
|
+
if (executed.completed && executed.reply.control.kind === 'awaiting-user') {
|
|
1130
1256
|
channelState.blocked = {
|
|
1131
1257
|
awaitingUser: executed.reply.awaitingUser ?? {
|
|
1132
1258
|
question: executed.reply.control.question,
|
|
@@ -1135,10 +1261,40 @@ export class AgentsHost {
|
|
|
1135
1261
|
suppressedAgentMessages: 0,
|
|
1136
1262
|
};
|
|
1137
1263
|
this.awaitingUserByChannel.set(channelId, channelState.blocked.awaitingUser);
|
|
1264
|
+
this.recordCollaborationOutcome(channelId, {
|
|
1265
|
+
responseState: 'blocked',
|
|
1266
|
+
deliveryState: executed.deliveryState ?? 'not-delivered',
|
|
1267
|
+
wakeState: 'waiting',
|
|
1268
|
+
blockedDetails: {
|
|
1269
|
+
question: channelState.blocked.awaitingUser.question,
|
|
1270
|
+
...(channelState.blocked.awaitingUser.reason
|
|
1271
|
+
? { reason: channelState.blocked.awaitingUser.reason }
|
|
1272
|
+
: {}),
|
|
1273
|
+
},
|
|
1274
|
+
turnExecutionId: activeTurn.turnExecutionId ?? undefined,
|
|
1275
|
+
});
|
|
1138
1276
|
this.finishProtocolState(channelId, channelState, protocolState, visibleMessageId);
|
|
1139
1277
|
return;
|
|
1140
1278
|
}
|
|
1141
1279
|
if (executed.reply.control.kind === 'start-protocol') {
|
|
1280
|
+
this.recordCollaborationOutcome(channelId, {
|
|
1281
|
+
responseState: executed.completed ? 'responded' : 'failed',
|
|
1282
|
+
deliveryState: executed.deliveryState ?? 'not-delivered',
|
|
1283
|
+
...(message.wakeBlockedState ? { wakeState: 'resumed-human' } : {}),
|
|
1284
|
+
turnExecutionId: activeTurn.turnExecutionId ?? undefined,
|
|
1285
|
+
});
|
|
1286
|
+
this.updateMissedCollaborationDiagnostic(channelId, undefined);
|
|
1287
|
+
this.finishProtocolState(channelId, channelState, protocolState, visibleMessageId);
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
if (executed.reply.control.kind === 'attention-only') {
|
|
1291
|
+
this.recordCollaborationOutcome(channelId, {
|
|
1292
|
+
responseState: executed.completed ? 'responded' : 'failed',
|
|
1293
|
+
deliveryState: executed.deliveryState ?? 'not-delivered',
|
|
1294
|
+
...(message.wakeBlockedState ? { wakeState: 'resumed-human' } : {}),
|
|
1295
|
+
turnExecutionId: activeTurn.turnExecutionId ?? undefined,
|
|
1296
|
+
});
|
|
1297
|
+
this.updateMissedCollaborationDiagnostic(channelId, undefined);
|
|
1142
1298
|
this.finishProtocolState(channelId, channelState, protocolState, visibleMessageId);
|
|
1143
1299
|
return;
|
|
1144
1300
|
}
|
|
@@ -1146,6 +1302,12 @@ export class AgentsHost {
|
|
|
1146
1302
|
this.finishProtocolState(channelId, channelState, protocolState);
|
|
1147
1303
|
this.awaitingUserByChannel.delete(channelId);
|
|
1148
1304
|
channelState.blocked = undefined;
|
|
1305
|
+
this.recordCollaborationOutcome(channelId, {
|
|
1306
|
+
responseState: 'failed',
|
|
1307
|
+
deliveryState: executed.deliveryState ?? 'not-delivered',
|
|
1308
|
+
...(message.wakeBlockedState ? { wakeState: 'resumed-human' } : {}),
|
|
1309
|
+
turnExecutionId: activeTurn.turnExecutionId ?? undefined,
|
|
1310
|
+
});
|
|
1149
1311
|
return;
|
|
1150
1312
|
}
|
|
1151
1313
|
protocolState.currentTurnIndex = currentTurnIndex;
|
|
@@ -1156,12 +1318,24 @@ export class AgentsHost {
|
|
|
1156
1318
|
this.finishProtocolState(channelId, channelState, protocolState, visibleMessageId);
|
|
1157
1319
|
this.awaitingUserByChannel.delete(channelId);
|
|
1158
1320
|
channelState.blocked = undefined;
|
|
1321
|
+
this.recordCollaborationOutcome(channelId, {
|
|
1322
|
+
responseState: 'responded',
|
|
1323
|
+
deliveryState: executed.deliveryState ?? 'not-delivered',
|
|
1324
|
+
...(message.wakeBlockedState ? { wakeState: 'resumed-human' } : {}),
|
|
1325
|
+
turnExecutionId: activeTurn.turnExecutionId ?? undefined,
|
|
1326
|
+
});
|
|
1159
1327
|
return;
|
|
1160
1328
|
}
|
|
1161
1329
|
if (currentTurnIndex >= protocolState.maxTurnCount) {
|
|
1162
1330
|
this.finishProtocolState(channelId, channelState, protocolState, visibleMessageId);
|
|
1163
1331
|
this.awaitingUserByChannel.delete(channelId);
|
|
1164
1332
|
channelState.blocked = undefined;
|
|
1333
|
+
this.recordCollaborationOutcome(channelId, {
|
|
1334
|
+
responseState: 'responded',
|
|
1335
|
+
deliveryState: executed.deliveryState ?? 'not-delivered',
|
|
1336
|
+
...(message.wakeBlockedState ? { wakeState: 'resumed-human' } : {}),
|
|
1337
|
+
turnExecutionId: activeTurn.turnExecutionId ?? undefined,
|
|
1338
|
+
});
|
|
1165
1339
|
return;
|
|
1166
1340
|
}
|
|
1167
1341
|
protocolState.expectedNextSpeakerId = peerId;
|
|
@@ -1172,6 +1346,12 @@ export class AgentsHost {
|
|
|
1172
1346
|
}
|
|
1173
1347
|
channelState.blocked = undefined;
|
|
1174
1348
|
this.awaitingUserByChannel.delete(channelId);
|
|
1349
|
+
this.recordCollaborationOutcome(channelId, {
|
|
1350
|
+
responseState: 'responded',
|
|
1351
|
+
deliveryState: executed.deliveryState ?? 'not-delivered',
|
|
1352
|
+
...(message.wakeBlockedState ? { wakeState: 'resumed-human' } : {}),
|
|
1353
|
+
turnExecutionId: activeTurn.turnExecutionId ?? undefined,
|
|
1354
|
+
});
|
|
1175
1355
|
}
|
|
1176
1356
|
buildIncomingContent(message) {
|
|
1177
1357
|
if (!message.wakeBlockedState) {
|
|
@@ -1495,7 +1675,9 @@ export class AgentsHost {
|
|
|
1495
1675
|
type: 'message',
|
|
1496
1676
|
};
|
|
1497
1677
|
this.cancelProtocolState(channelId, channelState);
|
|
1498
|
-
await this.
|
|
1678
|
+
const taskThreadContext = await this.buildTurnTaskThreadContext(fallbackMessage, channelId);
|
|
1679
|
+
const attentionSnapshot = this.getAttentionSnapshotForTurn(channelId, taskThreadContext);
|
|
1680
|
+
await this.runUngatedTurn(fallbackMessage, protocolState.anchorAuthorId, protocolState.anchorContent, taskThreadContext, attentionSnapshot);
|
|
1499
1681
|
}
|
|
1500
1682
|
finishProtocolState(channelId, channelState, protocolState, lastMessageId) {
|
|
1501
1683
|
protocolState.active = false;
|
|
@@ -1535,15 +1717,328 @@ export class AgentsHost {
|
|
|
1535
1717
|
}
|
|
1536
1718
|
return matched;
|
|
1537
1719
|
}
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1720
|
+
resolveAttentionStatePath(channelId) {
|
|
1721
|
+
return resolveAttentionStatePath(this.config.stateRootDir, channelId);
|
|
1722
|
+
}
|
|
1723
|
+
buildDefaultAttentionSnapshot(options) {
|
|
1724
|
+
return buildNormalizedAttentionSnapshot({
|
|
1725
|
+
observedAt: options?.observedAt,
|
|
1726
|
+
turnExecutionId: options?.turnExecutionId,
|
|
1727
|
+
});
|
|
1728
|
+
}
|
|
1729
|
+
async buildTurnTaskThreadContext(msg, channelId) {
|
|
1730
|
+
if (msg.message_type === 'task_assignment') {
|
|
1731
|
+
const currentTaskId = extractTaskIdFromTaskAssignmentContent(String(msg.content ?? msg.body ?? ''));
|
|
1732
|
+
return currentTaskId ? { state: 'active', currentTaskId } : { state: 'active' };
|
|
1733
|
+
}
|
|
1734
|
+
if (!this.contextInjectionEnabled) {
|
|
1735
|
+
return { state: 'inactive' };
|
|
1736
|
+
}
|
|
1737
|
+
try {
|
|
1738
|
+
const content = await readFile(resolveChannelContextPayloadPath(this.config.stateRootDir, channelId), 'utf8');
|
|
1739
|
+
const payload = JSON.parse(content);
|
|
1740
|
+
if (payload.taskAssignmentContext?.active !== true) {
|
|
1741
|
+
return { state: 'inactive' };
|
|
1742
|
+
}
|
|
1743
|
+
const currentTaskId = typeof payload.taskAssignmentContext.currentTaskId === 'string'
|
|
1744
|
+
? payload.taskAssignmentContext.currentTaskId.trim()
|
|
1745
|
+
: '';
|
|
1746
|
+
return currentTaskId ? { state: 'active', currentTaskId } : { state: 'active' };
|
|
1747
|
+
}
|
|
1748
|
+
catch {
|
|
1749
|
+
return { state: 'unavailable' };
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
async ensureAttentionSnapshotLoaded(channelId, channelState) {
|
|
1753
|
+
if (!this.attentionFollowSemanticsEnabled) {
|
|
1754
|
+
return;
|
|
1755
|
+
}
|
|
1756
|
+
if (channelState?.attentionLoaded) {
|
|
1757
|
+
return;
|
|
1758
|
+
}
|
|
1759
|
+
if (channelState) {
|
|
1760
|
+
channelState.attentionLoaded = true;
|
|
1761
|
+
}
|
|
1762
|
+
try {
|
|
1763
|
+
const content = await readFile(this.resolveAttentionStatePath(channelId), 'utf8');
|
|
1764
|
+
this.attentionSnapshotByChannel.set(channelId, parsePersistedAttentionSnapshot(JSON.parse(content)));
|
|
1765
|
+
}
|
|
1766
|
+
catch (error) {
|
|
1767
|
+
const code = error.code;
|
|
1768
|
+
if (code !== 'ENOENT') {
|
|
1769
|
+
this.logger.error('failed to load attention state', {
|
|
1770
|
+
channelId,
|
|
1771
|
+
error: summarizeError(error),
|
|
1772
|
+
});
|
|
1773
|
+
}
|
|
1774
|
+
this.attentionSnapshotByChannel.set(channelId, this.buildDefaultAttentionSnapshot());
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
revalidateAttentionSnapshotForTurn(channelId, taskThreadContext, options) {
|
|
1778
|
+
const current = this.attentionSnapshotByChannel.get(channelId)
|
|
1779
|
+
?? this.buildDefaultAttentionSnapshot();
|
|
1780
|
+
if (current.claimContext?.scope !== 'task-thread') {
|
|
1781
|
+
return current;
|
|
1782
|
+
}
|
|
1783
|
+
if (!this.contextInjectionEnabled) {
|
|
1784
|
+
const next = this.buildDefaultAttentionSnapshot({
|
|
1785
|
+
observedAt: options?.observedAt ?? Date.now(),
|
|
1786
|
+
turnExecutionId: options?.turnExecutionId,
|
|
1787
|
+
});
|
|
1788
|
+
this.attentionSnapshotByChannel.set(channelId, next);
|
|
1789
|
+
return next;
|
|
1790
|
+
}
|
|
1791
|
+
if (taskThreadContext.state === 'active' &&
|
|
1792
|
+
taskThreadContext.currentTaskId &&
|
|
1793
|
+
taskThreadContext.currentTaskId === current.claimContext.taskId) {
|
|
1794
|
+
const next = buildNormalizedAttentionSnapshot({
|
|
1795
|
+
...current,
|
|
1796
|
+
deliveryMode: 'follow',
|
|
1797
|
+
claimState: 'claimed',
|
|
1798
|
+
claimActivation: 'active',
|
|
1799
|
+
observedAt: options?.observedAt ?? Date.now(),
|
|
1800
|
+
turnExecutionId: options?.turnExecutionId,
|
|
1801
|
+
claimContext: current.claimContext,
|
|
1802
|
+
});
|
|
1803
|
+
this.attentionSnapshotByChannel.set(channelId, next);
|
|
1804
|
+
return next;
|
|
1805
|
+
}
|
|
1806
|
+
if (taskThreadContext.state === 'unavailable') {
|
|
1807
|
+
const next = buildNormalizedAttentionSnapshot({
|
|
1808
|
+
...current,
|
|
1809
|
+
deliveryMode: 'default',
|
|
1810
|
+
claimState: 'claimed',
|
|
1811
|
+
claimActivation: 'dormant',
|
|
1812
|
+
observedAt: options?.observedAt ?? Date.now(),
|
|
1813
|
+
turnExecutionId: options?.turnExecutionId,
|
|
1814
|
+
claimContext: current.claimContext,
|
|
1815
|
+
});
|
|
1816
|
+
this.attentionSnapshotByChannel.set(channelId, next);
|
|
1817
|
+
return next;
|
|
1818
|
+
}
|
|
1819
|
+
const next = this.buildDefaultAttentionSnapshot({
|
|
1820
|
+
observedAt: options?.observedAt ?? Date.now(),
|
|
1821
|
+
turnExecutionId: options?.turnExecutionId,
|
|
1822
|
+
});
|
|
1823
|
+
this.attentionSnapshotByChannel.set(channelId, next);
|
|
1824
|
+
return next;
|
|
1825
|
+
}
|
|
1826
|
+
getAttentionSnapshotForTurn(channelId, taskThreadContext, options) {
|
|
1827
|
+
if (!this.attentionFollowSemanticsEnabled) {
|
|
1828
|
+
return undefined;
|
|
1829
|
+
}
|
|
1830
|
+
return this.revalidateAttentionSnapshotForTurn(channelId, taskThreadContext, options);
|
|
1831
|
+
}
|
|
1832
|
+
isHumanWakeFiltered(msg, authorKind, attentionSnapshot) {
|
|
1833
|
+
if (!this.attentionFollowSemanticsEnabled || authorKind === 'agent') {
|
|
1834
|
+
return false;
|
|
1835
|
+
}
|
|
1836
|
+
if (msg.message_type === 'task_assignment') {
|
|
1837
|
+
return false;
|
|
1838
|
+
}
|
|
1839
|
+
if (this.awaitingUserByChannel.has(msg.channel_id)) {
|
|
1840
|
+
return false;
|
|
1841
|
+
}
|
|
1842
|
+
if (msg.type?.trim() === 'mention') {
|
|
1843
|
+
return false;
|
|
1844
|
+
}
|
|
1845
|
+
const eventKind = msg.type?.trim() ?? '';
|
|
1846
|
+
if (!ORDINARY_HUMAN_EVENT_KINDS.has(eventKind)) {
|
|
1847
|
+
return false;
|
|
1848
|
+
}
|
|
1849
|
+
if (attentionSnapshot?.wakeMode === 'follow-visible-human') {
|
|
1850
|
+
return false;
|
|
1851
|
+
}
|
|
1852
|
+
this.noteWakeSuppressedAttentionPolicy(msg, authorKind);
|
|
1853
|
+
return true;
|
|
1854
|
+
}
|
|
1855
|
+
applyAttentionUpdate(channelId, baseSnapshot, attentionUpdate, taskThreadContext, options) {
|
|
1856
|
+
if (!this.attentionFollowSemanticsEnabled) {
|
|
1857
|
+
return undefined;
|
|
1858
|
+
}
|
|
1859
|
+
if (!attentionUpdate) {
|
|
1860
|
+
return baseSnapshot ?? this.attentionSnapshotByChannel.get(channelId);
|
|
1861
|
+
}
|
|
1862
|
+
const observedAt = options?.observedAt ?? Date.now();
|
|
1863
|
+
const turnExecutionId = options?.turnExecutionId;
|
|
1864
|
+
const current = baseSnapshot
|
|
1865
|
+
?? this.attentionSnapshotByChannel.get(channelId)
|
|
1866
|
+
?? this.buildDefaultAttentionSnapshot();
|
|
1867
|
+
const clearClaim = (deliveryMode) => buildNormalizedAttentionSnapshot({
|
|
1868
|
+
deliveryMode,
|
|
1869
|
+
claimState: 'unclaimed',
|
|
1870
|
+
claimActivation: 'inactive',
|
|
1871
|
+
observedAt,
|
|
1872
|
+
turnExecutionId,
|
|
1873
|
+
});
|
|
1874
|
+
switch (attentionUpdate) {
|
|
1875
|
+
case 'follow-channel':
|
|
1876
|
+
return clearClaim('follow');
|
|
1877
|
+
case 'unfollow-channel':
|
|
1878
|
+
return clearClaim('default');
|
|
1879
|
+
case 'mute-channel':
|
|
1880
|
+
return clearClaim('muted');
|
|
1881
|
+
case 'claim-channel':
|
|
1882
|
+
return buildNormalizedAttentionSnapshot({
|
|
1883
|
+
deliveryMode: 'follow',
|
|
1884
|
+
claimState: 'claimed',
|
|
1885
|
+
claimActivation: 'active',
|
|
1886
|
+
observedAt,
|
|
1887
|
+
turnExecutionId,
|
|
1888
|
+
});
|
|
1889
|
+
case 'claim-task-thread':
|
|
1890
|
+
if (!this.contextInjectionEnabled
|
|
1891
|
+
|| taskThreadContext.state !== 'active'
|
|
1892
|
+
|| !taskThreadContext.currentTaskId) {
|
|
1893
|
+
return current;
|
|
1894
|
+
}
|
|
1895
|
+
return buildNormalizedAttentionSnapshot({
|
|
1896
|
+
deliveryMode: 'follow',
|
|
1897
|
+
claimState: 'claimed',
|
|
1898
|
+
claimActivation: 'active',
|
|
1899
|
+
observedAt,
|
|
1900
|
+
turnExecutionId,
|
|
1901
|
+
claimContext: {
|
|
1902
|
+
scope: 'task-thread',
|
|
1903
|
+
taskId: taskThreadContext.currentTaskId,
|
|
1904
|
+
},
|
|
1905
|
+
});
|
|
1906
|
+
case 'unclaim-channel':
|
|
1907
|
+
return buildNormalizedAttentionSnapshot({
|
|
1908
|
+
deliveryMode: current.deliveryMode,
|
|
1909
|
+
claimState: 'unclaimed',
|
|
1910
|
+
claimActivation: 'inactive',
|
|
1911
|
+
observedAt,
|
|
1912
|
+
turnExecutionId,
|
|
1913
|
+
});
|
|
1914
|
+
case 'unclaim-task-thread':
|
|
1915
|
+
if (!this.contextInjectionEnabled
|
|
1916
|
+
|| current.claimContext?.scope !== 'task-thread'
|
|
1917
|
+
|| taskThreadContext.state !== 'active'
|
|
1918
|
+
|| !taskThreadContext.currentTaskId
|
|
1919
|
+
|| taskThreadContext.currentTaskId !== current.claimContext.taskId) {
|
|
1920
|
+
return current;
|
|
1921
|
+
}
|
|
1922
|
+
return buildNormalizedAttentionSnapshot({
|
|
1923
|
+
deliveryMode: current.deliveryMode,
|
|
1924
|
+
claimState: 'unclaimed',
|
|
1925
|
+
claimActivation: 'inactive',
|
|
1926
|
+
observedAt,
|
|
1927
|
+
turnExecutionId,
|
|
1928
|
+
});
|
|
1929
|
+
default:
|
|
1930
|
+
return current;
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
async commitAttentionSnapshot(channelId, snapshot) {
|
|
1934
|
+
if (!this.attentionFollowSemanticsEnabled || !snapshot) {
|
|
1935
|
+
return;
|
|
1936
|
+
}
|
|
1937
|
+
this.attentionSnapshotByChannel.set(channelId, snapshot);
|
|
1938
|
+
await writePrivateJsonFile(this.resolveAttentionStatePath(channelId), snapshot);
|
|
1939
|
+
}
|
|
1940
|
+
resolveTaskThreadCollaborationContractForTurn(msg, taskThreadContext) {
|
|
1941
|
+
if (!this.taskThreadCollaborationContractEnabled) {
|
|
1942
|
+
return undefined;
|
|
1943
|
+
}
|
|
1944
|
+
return projectTaskThreadCollaborationContract({
|
|
1945
|
+
incomingMessageType: msg.message_type,
|
|
1946
|
+
taskThreadContextActive: taskThreadContext.state === 'active',
|
|
1947
|
+
});
|
|
1948
|
+
}
|
|
1949
|
+
resolveCollaborationCapabilityDeclarationForTurn() {
|
|
1950
|
+
if (!this.collaborationCapabilitiesDiagnosticsEnabled) {
|
|
1951
|
+
return undefined;
|
|
1952
|
+
}
|
|
1953
|
+
return {
|
|
1954
|
+
...(this.collaborationOutcomeModelEnabled ? { collaborationOutcome: true } : {}),
|
|
1955
|
+
...(this.attentionFollowSemanticsEnabled ? { attentionSnapshot: true } : {}),
|
|
1956
|
+
...(this.taskThreadCollaborationContractEnabled
|
|
1957
|
+
? { taskThreadCollaborationContract: true }
|
|
1958
|
+
: {}),
|
|
1959
|
+
missedCollaborationDiagnostic: true,
|
|
1960
|
+
recoveryExplanation: 'runtime-local-only',
|
|
1961
|
+
};
|
|
1962
|
+
}
|
|
1963
|
+
resolveMissedCollaborationDiagnosticForTurn(channelId) {
|
|
1964
|
+
if (!this.collaborationCapabilitiesDiagnosticsEnabled) {
|
|
1965
|
+
return undefined;
|
|
1966
|
+
}
|
|
1967
|
+
return this.missedCollaborationDiagnosticByChannel.get(channelId);
|
|
1968
|
+
}
|
|
1969
|
+
updateMissedCollaborationDiagnostic(channelId, diagnostic) {
|
|
1970
|
+
if (!this.collaborationCapabilitiesDiagnosticsEnabled || !diagnostic) {
|
|
1971
|
+
this.missedCollaborationDiagnosticByChannel.delete(channelId);
|
|
1972
|
+
return;
|
|
1973
|
+
}
|
|
1974
|
+
this.missedCollaborationDiagnosticByChannel.set(channelId, diagnostic);
|
|
1975
|
+
}
|
|
1976
|
+
noteWakeSuppressedAttentionPolicy(msg, authorKind) {
|
|
1977
|
+
if (authorKind === 'agent' || msg.message_type === 'task_assignment') {
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
const eventKind = msg.type?.trim() ?? '';
|
|
1981
|
+
if (msg.type?.trim() === 'mention' || !ORDINARY_HUMAN_EVENT_KINDS.has(eventKind)) {
|
|
1982
|
+
return;
|
|
1983
|
+
}
|
|
1984
|
+
this.updateMissedCollaborationDiagnostic(msg.channel_id, {
|
|
1985
|
+
stage: 'wake',
|
|
1986
|
+
reason: 'wake-suppressed-attention-policy',
|
|
1987
|
+
});
|
|
1988
|
+
}
|
|
1989
|
+
async runUngatedTurn(msg, authorId, content, taskThreadContext, attentionSnapshot) {
|
|
1990
|
+
const executed = await this.executeTurn(msg, authorId, content, {
|
|
1991
|
+
attentionSnapshot,
|
|
1992
|
+
taskThreadContext,
|
|
1993
|
+
});
|
|
1994
|
+
if (executed.completed && executed.reply) {
|
|
1541
1995
|
this.updateAwaitingUserState(msg.channel_id, executed.reply.awaitingUser);
|
|
1542
1996
|
}
|
|
1997
|
+
if (executed.completed) {
|
|
1998
|
+
await this.commitAttentionSnapshot(msg.channel_id, executed.appliedAttentionSnapshot ?? attentionSnapshot);
|
|
1999
|
+
}
|
|
2000
|
+
if (!executed.completed) {
|
|
2001
|
+
const awaitingUser = this.awaitingUserByChannel.get(msg.channel_id);
|
|
2002
|
+
if (awaitingUser) {
|
|
2003
|
+
const existing = this.collaborationOutcomeByChannel.get(msg.channel_id);
|
|
2004
|
+
this.recordCollaborationOutcome(msg.channel_id, {
|
|
2005
|
+
responseState: 'blocked',
|
|
2006
|
+
deliveryState: existing?.responseState === 'blocked' ? existing.deliveryState : 'not-delivered',
|
|
2007
|
+
wakeState: 'waiting',
|
|
2008
|
+
blockedDetails: {
|
|
2009
|
+
question: awaitingUser.question,
|
|
2010
|
+
...(awaitingUser.reason ? { reason: awaitingUser.reason } : {}),
|
|
2011
|
+
},
|
|
2012
|
+
...(existing?.turnExecutionId ? { turnExecutionId: existing.turnExecutionId } : {}),
|
|
2013
|
+
});
|
|
2014
|
+
return;
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
this.recordCollaborationOutcome(msg.channel_id, {
|
|
2018
|
+
responseState: executed.completed && executed.reply?.awaitingUser
|
|
2019
|
+
? 'blocked'
|
|
2020
|
+
: executed.completed
|
|
2021
|
+
? 'responded'
|
|
2022
|
+
: 'failed',
|
|
2023
|
+
deliveryState: executed.deliveryState ?? 'not-delivered',
|
|
2024
|
+
...(executed.completed && executed.reply?.awaitingUser
|
|
2025
|
+
? {
|
|
2026
|
+
wakeState: 'waiting',
|
|
2027
|
+
blockedDetails: {
|
|
2028
|
+
question: executed.reply.awaitingUser.question,
|
|
2029
|
+
...(executed.reply.awaitingUser.reason
|
|
2030
|
+
? { reason: executed.reply.awaitingUser.reason }
|
|
2031
|
+
: {}),
|
|
2032
|
+
},
|
|
2033
|
+
}
|
|
2034
|
+
: {}),
|
|
2035
|
+
});
|
|
1543
2036
|
}
|
|
1544
2037
|
async executeTurn(msg, authorId, content, options) {
|
|
1545
2038
|
const channelId = msg.channel_id;
|
|
1546
2039
|
const activeTurn = options?.activeTurn;
|
|
2040
|
+
const attentionSnapshot = options?.attentionSnapshot;
|
|
2041
|
+
const taskThreadContext = options?.taskThreadContext ?? { state: 'inactive' };
|
|
1547
2042
|
const silentTurn = options?.silent === true;
|
|
1548
2043
|
const deferDelivery = options?.deferDelivery === true;
|
|
1549
2044
|
const collaborationTurnMode = activeTurn?.protocol
|
|
@@ -1578,8 +2073,17 @@ export class AgentsHost {
|
|
|
1578
2073
|
};
|
|
1579
2074
|
const turnStartedAt = Date.now();
|
|
1580
2075
|
this.logger.debug('starting turn', turnContext);
|
|
2076
|
+
let reply;
|
|
2077
|
+
let publicReplyText;
|
|
2078
|
+
let visibleReplyBody;
|
|
2079
|
+
const taskThreadCollaborationContract = this.resolveTaskThreadCollaborationContractForTurn(msg, taskThreadContext);
|
|
2080
|
+
const collaborationOutcome = this.collaborationOutcomeModelEnabled
|
|
2081
|
+
? this.collaborationOutcomeByChannel.get(channelId)
|
|
2082
|
+
: undefined;
|
|
2083
|
+
const missedCollaborationDiagnostic = this.resolveMissedCollaborationDiagnosticForTurn(channelId);
|
|
2084
|
+
const collaborationCapabilities = this.resolveCollaborationCapabilityDeclarationForTurn();
|
|
1581
2085
|
try {
|
|
1582
|
-
|
|
2086
|
+
reply = await this.provider.generateReply({
|
|
1583
2087
|
agentName: this.config.agent.agentName,
|
|
1584
2088
|
provider: this.config.agent.provider,
|
|
1585
2089
|
channelId,
|
|
@@ -1597,6 +2101,11 @@ export class AgentsHost {
|
|
|
1597
2101
|
...(collaborationGrounding ? { grounding: collaborationGrounding } : {}),
|
|
1598
2102
|
}
|
|
1599
2103
|
: undefined,
|
|
2104
|
+
collaborationOutcome,
|
|
2105
|
+
attentionSnapshot,
|
|
2106
|
+
...(taskThreadCollaborationContract ? { taskThreadCollaborationContract } : {}),
|
|
2107
|
+
...(collaborationCapabilities ? { collaborationCapabilities } : {}),
|
|
2108
|
+
...(missedCollaborationDiagnostic ? { missedCollaborationDiagnostic } : {}),
|
|
1600
2109
|
}, usePublicDraftProgress || usePrivateCollaborationDraftProgress
|
|
1601
2110
|
? {
|
|
1602
2111
|
onProgress: (update) => {
|
|
@@ -1611,8 +2120,16 @@ export class AgentsHost {
|
|
|
1611
2120
|
},
|
|
1612
2121
|
}
|
|
1613
2122
|
: undefined);
|
|
1614
|
-
|
|
1615
|
-
|
|
2123
|
+
publicReplyText = resolvePublicReplyText(reply);
|
|
2124
|
+
visibleReplyBody = this.resolveVisibleTurnBody(activeTurn, reply, publicReplyText);
|
|
2125
|
+
const appliedAttentionSnapshot = this.applyAttentionUpdate(channelId, attentionSnapshot, reply.controlMalformed
|
|
2126
|
+
? undefined
|
|
2127
|
+
: (reply.control && 'attentionUpdate' in reply.control
|
|
2128
|
+
? reply.control.attentionUpdate
|
|
2129
|
+
: undefined), taskThreadContext, {
|
|
2130
|
+
observedAt: Date.now(),
|
|
2131
|
+
turnExecutionId: activeTurn?.turnExecutionId ?? undefined,
|
|
2132
|
+
});
|
|
1616
2133
|
if (!(await this.canAcceptCompletedTurnOutput(channelId, activeTurn))) {
|
|
1617
2134
|
await this.discardDraft(channelId);
|
|
1618
2135
|
if (activeTurn?.turnExecutionId) {
|
|
@@ -1637,6 +2154,8 @@ export class AgentsHost {
|
|
|
1637
2154
|
deliveredMessageId: null,
|
|
1638
2155
|
publicReplyText,
|
|
1639
2156
|
visibleReplyBody,
|
|
2157
|
+
deliveryState: 'not-delivered',
|
|
2158
|
+
appliedAttentionSnapshot,
|
|
1640
2159
|
stale: false,
|
|
1641
2160
|
completed: true,
|
|
1642
2161
|
};
|
|
@@ -1655,6 +2174,8 @@ export class AgentsHost {
|
|
|
1655
2174
|
deliveredMessageId: null,
|
|
1656
2175
|
publicReplyText,
|
|
1657
2176
|
visibleReplyBody,
|
|
2177
|
+
deliveryState: 'not-delivered',
|
|
2178
|
+
appliedAttentionSnapshot,
|
|
1658
2179
|
stale: false,
|
|
1659
2180
|
completed: true,
|
|
1660
2181
|
};
|
|
@@ -1681,6 +2202,8 @@ export class AgentsHost {
|
|
|
1681
2202
|
deliveredMessageId,
|
|
1682
2203
|
publicReplyText,
|
|
1683
2204
|
visibleReplyBody,
|
|
2205
|
+
deliveryState: deliveredMessageId ? 'draft-finalized' : 'not-delivered',
|
|
2206
|
+
appliedAttentionSnapshot,
|
|
1684
2207
|
stale: false,
|
|
1685
2208
|
completed: true,
|
|
1686
2209
|
};
|
|
@@ -1707,6 +2230,8 @@ export class AgentsHost {
|
|
|
1707
2230
|
deliveredMessageId: posted.messageId,
|
|
1708
2231
|
publicReplyText,
|
|
1709
2232
|
visibleReplyBody,
|
|
2233
|
+
deliveryState: 'posted',
|
|
2234
|
+
appliedAttentionSnapshot,
|
|
1710
2235
|
stale: false,
|
|
1711
2236
|
completed: true,
|
|
1712
2237
|
};
|
|
@@ -1728,6 +2253,8 @@ export class AgentsHost {
|
|
|
1728
2253
|
deliveredMessageId: null,
|
|
1729
2254
|
publicReplyText,
|
|
1730
2255
|
visibleReplyBody,
|
|
2256
|
+
deliveryState: 'not-delivered',
|
|
2257
|
+
appliedAttentionSnapshot,
|
|
1731
2258
|
stale: false,
|
|
1732
2259
|
completed: true,
|
|
1733
2260
|
};
|
|
@@ -1748,7 +2275,14 @@ export class AgentsHost {
|
|
|
1748
2275
|
...turnContext,
|
|
1749
2276
|
error: summarizeError(error),
|
|
1750
2277
|
});
|
|
1751
|
-
return {
|
|
2278
|
+
return {
|
|
2279
|
+
reply,
|
|
2280
|
+
publicReplyText,
|
|
2281
|
+
visibleReplyBody,
|
|
2282
|
+
deliveryState: reply ? 'delivery-failed' : 'not-delivered',
|
|
2283
|
+
stale: activeTurn?.superseded === true,
|
|
2284
|
+
completed: false,
|
|
2285
|
+
};
|
|
1752
2286
|
}
|
|
1753
2287
|
finally {
|
|
1754
2288
|
stopTyping();
|
|
@@ -1917,6 +2451,11 @@ export class AgentsHost {
|
|
|
1917
2451
|
if (activeTurn.superseded) {
|
|
1918
2452
|
return;
|
|
1919
2453
|
}
|
|
2454
|
+
this.recordCollaborationOutcome(channelId, {
|
|
2455
|
+
responseState: 'superseded',
|
|
2456
|
+
deliveryState: 'not-delivered',
|
|
2457
|
+
turnExecutionId: activeTurn.turnExecutionId ?? undefined,
|
|
2458
|
+
});
|
|
1920
2459
|
if (activeTurn.turnExecutionId) {
|
|
1921
2460
|
this.clearCollaborationDraft(channelId, {
|
|
1922
2461
|
generation: activeTurn.generation,
|
|
@@ -1975,6 +2514,80 @@ export class AgentsHost {
|
|
|
1975
2514
|
}
|
|
1976
2515
|
this.awaitingUserByChannel.delete(channelId);
|
|
1977
2516
|
}
|
|
2517
|
+
recordBlockedSuppressionOutcome(channelId, channelState) {
|
|
2518
|
+
const blocked = channelState.blocked;
|
|
2519
|
+
if (!blocked) {
|
|
2520
|
+
return;
|
|
2521
|
+
}
|
|
2522
|
+
const existing = this.collaborationOutcomeByChannel.get(channelId);
|
|
2523
|
+
this.recordCollaborationOutcome(channelId, {
|
|
2524
|
+
responseState: 'blocked',
|
|
2525
|
+
deliveryState: existing?.deliveryState ?? 'not-delivered',
|
|
2526
|
+
wakeState: 'suppressed-agent',
|
|
2527
|
+
blockedDetails: {
|
|
2528
|
+
question: blocked.awaitingUser.question,
|
|
2529
|
+
...(blocked.awaitingUser.reason ? { reason: blocked.awaitingUser.reason } : {}),
|
|
2530
|
+
},
|
|
2531
|
+
turnExecutionId: channelState.activeTurn?.turnExecutionId
|
|
2532
|
+
?? channelState.recentTurn?.turnExecutionId
|
|
2533
|
+
?? existing?.turnExecutionId,
|
|
2534
|
+
});
|
|
2535
|
+
}
|
|
2536
|
+
recordCollaborationOutcome(channelId, outcome) {
|
|
2537
|
+
this.collaborationOutcomeByChannel.set(channelId, {
|
|
2538
|
+
source: 'host-observed',
|
|
2539
|
+
responseState: outcome.responseState,
|
|
2540
|
+
deliveryState: outcome.deliveryState,
|
|
2541
|
+
...(outcome.wakeState ? { wakeState: outcome.wakeState } : {}),
|
|
2542
|
+
...(outcome.blockedDetails ? { blockedDetails: outcome.blockedDetails } : {}),
|
|
2543
|
+
observedAt: outcome.observedAt ?? Date.now(),
|
|
2544
|
+
...(outcome.turnExecutionId ? { turnExecutionId: outcome.turnExecutionId } : {}),
|
|
2545
|
+
});
|
|
2546
|
+
this.syncMissedCollaborationDiagnosticFromOutcome(channelId);
|
|
2547
|
+
}
|
|
2548
|
+
syncMissedCollaborationDiagnosticFromOutcome(channelId) {
|
|
2549
|
+
if (!this.collaborationCapabilitiesDiagnosticsEnabled) {
|
|
2550
|
+
this.missedCollaborationDiagnosticByChannel.delete(channelId);
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
2553
|
+
const outcome = this.collaborationOutcomeByChannel.get(channelId);
|
|
2554
|
+
if (!outcome) {
|
|
2555
|
+
this.missedCollaborationDiagnosticByChannel.delete(channelId);
|
|
2556
|
+
return;
|
|
2557
|
+
}
|
|
2558
|
+
if (outcome.responseState === 'blocked'
|
|
2559
|
+
&& outcome.wakeState === 'waiting'
|
|
2560
|
+
&& outcome.blockedDetails) {
|
|
2561
|
+
this.updateMissedCollaborationDiagnostic(channelId, {
|
|
2562
|
+
stage: 'response',
|
|
2563
|
+
reason: 'blocked-awaiting-user',
|
|
2564
|
+
});
|
|
2565
|
+
return;
|
|
2566
|
+
}
|
|
2567
|
+
if (outcome.responseState === 'superseded') {
|
|
2568
|
+
this.updateMissedCollaborationDiagnostic(channelId, undefined);
|
|
2569
|
+
return;
|
|
2570
|
+
}
|
|
2571
|
+
if (!this.collaborationOutcomeModelEnabled) {
|
|
2572
|
+
this.updateMissedCollaborationDiagnostic(channelId, undefined);
|
|
2573
|
+
return;
|
|
2574
|
+
}
|
|
2575
|
+
if (outcome.deliveryState === 'delivery-failed') {
|
|
2576
|
+
this.updateMissedCollaborationDiagnostic(channelId, {
|
|
2577
|
+
stage: 'delivery',
|
|
2578
|
+
reason: 'delivery-failed',
|
|
2579
|
+
});
|
|
2580
|
+
return;
|
|
2581
|
+
}
|
|
2582
|
+
if (outcome.deliveryState === 'not-delivered' && outcome.responseState !== 'failed') {
|
|
2583
|
+
this.updateMissedCollaborationDiagnostic(channelId, {
|
|
2584
|
+
stage: 'delivery',
|
|
2585
|
+
reason: 'not-delivered',
|
|
2586
|
+
});
|
|
2587
|
+
return;
|
|
2588
|
+
}
|
|
2589
|
+
this.updateMissedCollaborationDiagnostic(channelId, undefined);
|
|
2590
|
+
}
|
|
1978
2591
|
resolveVisibleTurnBody(activeTurn, reply, publicReplyText) {
|
|
1979
2592
|
if (!activeTurn?.protocol || !this.shouldRelayProtocolReply(activeTurn, reply)) {
|
|
1980
2593
|
return publicReplyText;
|