@borgee/agents-host 0.2.44 → 0.2.62
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 +24 -27
- package/dist/agents-host.d.ts +27 -5
- package/dist/agents-host.js +266 -201
- 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 +3 -0
- package/dist/cli.js +1 -5
- package/dist/compatibility-gates.d.ts +4 -0
- package/dist/compatibility-gates.js +20 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.js +21 -0
- 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 +58 -7
- package/dist/context/injection.js +370 -36
- 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 +464 -26
- package/dist/context/resolved-workspace.d.ts +3 -0
- package/dist/context/resolved-workspace.js +106 -0
- package/dist/context/skill-manual.d.ts +1 -0
- package/dist/context/skill-manual.js +4 -1
- package/dist/context/turn-preparation.d.ts +8 -2
- package/dist/context/turn-preparation.js +64 -14
- package/dist/gateway/localhost-gateway.js +4 -5
- package/dist/local-config.js +11 -1
- package/dist/managed-daemon.js +127 -9
- package/dist/plugin-sdk.js +264 -364
- package/dist/plugin-sdk.js.map +4 -4
- package/dist/policy/copilot-permission.d.ts +1 -0
- package/dist/policy/copilot-permission.js +18 -0
- package/dist/policy/gateway-authorization.js +2 -2
- 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/cli-client.d.ts +2 -3
- package/dist/providers/claude/cli-client.js +220 -120
- package/dist/providers/codex/cli-client.d.ts +2 -0
- package/dist/providers/codex/cli-client.js +28 -104
- package/dist/providers/codex/project-doc.js +12 -11
- package/dist/providers/copilot/cli-client.d.ts +1 -1
- package/dist/providers/copilot/cli-client.js +12 -86
- package/dist/providers/create-provider.d.ts +2 -0
- package/dist/providers/create-provider.js +22 -4
- package/dist/state-paths.d.ts +1 -1
- package/dist/state-paths.js +3 -3
- package/dist/task-thread-resolution.d.ts +3 -2
- package/dist/types.d.ts +143 -6
- package/package.json +2 -2
- package/skills/borgee-agent/SKILL.md +12 -4
- package/skills/borgee-agent/references/errors.md +2 -2
- package/skills/borgee-agent/references/task-properties.md +6 -2
- package/dist/durable-cursor-store.d.ts +0 -5
- package/dist/durable-cursor-store.js +0 -7
package/dist/agents-host.js
CHANGED
|
@@ -3,20 +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
|
-
import { extractTaskIdFromTaskAssignmentContent,
|
|
12
|
+
import { extractTaskIdFromTaskAssignmentContent, resolveTaskAssignmentStatePath, } 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
|
-
import { waitForTaskForThread } from './task-thread-resolution.js';
|
|
17
17
|
import { buildHostedIncomingContentText, buildHostedTurnContentParts, extractHostedMessageAttachments, normalizeHostedMessageAttachment, } from './hosted-turn-content.js';
|
|
18
|
+
import { waitForTaskForThread } from './task-thread-resolution.js';
|
|
18
19
|
const PRIVATE_STATE_ROOT_MODE = 0o700;
|
|
19
|
-
const STREAM_PROGRESS_EDIT_THROTTLE_MS = 150;
|
|
20
20
|
const PROTOCOL_STATE_DIRNAME = 'protocol-state';
|
|
21
21
|
const PROTOCOL_PROVIDER_TURN_TIMEOUT_MS = 120_000;
|
|
22
22
|
const PROTOCOL_KICKOFF_DECISION_TIMEOUT_MS = PROTOCOL_PROVIDER_TURN_TIMEOUT_MS;
|
|
@@ -104,9 +104,6 @@ class DefaultHostRuntime {
|
|
|
104
104
|
this.controlPlane =
|
|
105
105
|
deps.borgee ??
|
|
106
106
|
new SdkChatControlPlane(config.borgeeBaseUrl, config.agent.agentApiKey, undefined, {
|
|
107
|
-
cursorStore: createDurableCursorStore({
|
|
108
|
-
stateRootDir: config.stateRootDir,
|
|
109
|
-
}),
|
|
110
107
|
pluginId: `agents-host:${config.agent.provider}`,
|
|
111
108
|
});
|
|
112
109
|
this.gateway = createLocalhostGatewayController({
|
|
@@ -139,9 +136,11 @@ class DefaultHostRuntime {
|
|
|
139
136
|
copilotArgs: config.copilotArgs,
|
|
140
137
|
copilotSessionTtlMinutes: config.copilotSessionTtlMinutes,
|
|
141
138
|
providerIdleShutdownMinutes: config.providerIdleShutdownMinutes,
|
|
139
|
+
allowCrossAgentIndependentWorkspaceHandoff: config.allowCrossAgentIndependentWorkspaceHandoff,
|
|
142
140
|
}, deps.logger, {
|
|
143
141
|
compatibilityGates,
|
|
144
142
|
authorizationAuditSink: this.authorizationAuditSink,
|
|
143
|
+
controlPlane: this.controlPlane,
|
|
145
144
|
localhostGateway: localhostGatewayGateEnabled ? this.gateway.contextPublisher : undefined,
|
|
146
145
|
});
|
|
147
146
|
this.ensureStateRoot = deps.ensureStateRoot ?? ensurePrivateStateRoot;
|
|
@@ -258,113 +257,6 @@ function resolvePublicReplyText(reply) {
|
|
|
258
257
|
}
|
|
259
258
|
return reply.awaitingUser?.question ?? '';
|
|
260
259
|
}
|
|
261
|
-
function providerUsesDraftProgress(provider) {
|
|
262
|
-
return provider === 'claude' || provider === 'codex' || provider === 'copilot';
|
|
263
|
-
}
|
|
264
|
-
class DraftMessageController {
|
|
265
|
-
borgee;
|
|
266
|
-
channelId;
|
|
267
|
-
canWrite;
|
|
268
|
-
logger;
|
|
269
|
-
creationMetadata;
|
|
270
|
-
messageId = null;
|
|
271
|
-
currentBody = '';
|
|
272
|
-
pendingBody = null;
|
|
273
|
-
timer = null;
|
|
274
|
-
sealed = false;
|
|
275
|
-
writeChain = Promise.resolve();
|
|
276
|
-
constructor(borgee, channelId, canWrite, logger, creationMetadata) {
|
|
277
|
-
this.borgee = borgee;
|
|
278
|
-
this.channelId = channelId;
|
|
279
|
-
this.canWrite = canWrite;
|
|
280
|
-
this.logger = logger;
|
|
281
|
-
this.creationMetadata = creationMetadata;
|
|
282
|
-
}
|
|
283
|
-
update(text) {
|
|
284
|
-
if (this.sealed || !hasVisibleText(text)) {
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
this.pendingBody = text;
|
|
288
|
-
if (this.timer) {
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
this.timer = setTimeout(() => {
|
|
292
|
-
this.timer = null;
|
|
293
|
-
void this.flushPending().catch((error) => {
|
|
294
|
-
this.logger.error('failed to flush draft progress', error);
|
|
295
|
-
});
|
|
296
|
-
}, STREAM_PROGRESS_EDIT_THROTTLE_MS);
|
|
297
|
-
}
|
|
298
|
-
cancelProgress() {
|
|
299
|
-
if (this.timer) {
|
|
300
|
-
clearTimeout(this.timer);
|
|
301
|
-
this.timer = null;
|
|
302
|
-
}
|
|
303
|
-
this.sealed = true;
|
|
304
|
-
}
|
|
305
|
-
async finalize(finalText) {
|
|
306
|
-
this.sealed = true;
|
|
307
|
-
if (this.timer) {
|
|
308
|
-
clearTimeout(this.timer);
|
|
309
|
-
this.timer = null;
|
|
310
|
-
}
|
|
311
|
-
if (hasVisibleText(finalText)) {
|
|
312
|
-
this.pendingBody = finalText;
|
|
313
|
-
await this.flushPending();
|
|
314
|
-
return this.messageId;
|
|
315
|
-
}
|
|
316
|
-
await this.enqueueWrite(async () => {
|
|
317
|
-
if (!this.canWrite() || !this.messageId) {
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
await this.borgee.deleteMessage(this.messageId);
|
|
321
|
-
this.messageId = null;
|
|
322
|
-
this.currentBody = '';
|
|
323
|
-
this.pendingBody = null;
|
|
324
|
-
});
|
|
325
|
-
return null;
|
|
326
|
-
}
|
|
327
|
-
async discard() {
|
|
328
|
-
this.sealed = true;
|
|
329
|
-
if (this.timer) {
|
|
330
|
-
clearTimeout(this.timer);
|
|
331
|
-
this.timer = null;
|
|
332
|
-
}
|
|
333
|
-
this.pendingBody = null;
|
|
334
|
-
await this.enqueueWrite(async () => {
|
|
335
|
-
if (!this.messageId) {
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
await this.borgee.deleteMessage(this.messageId);
|
|
339
|
-
this.messageId = null;
|
|
340
|
-
this.currentBody = '';
|
|
341
|
-
});
|
|
342
|
-
}
|
|
343
|
-
async flushPending() {
|
|
344
|
-
await this.enqueueWrite(async () => {
|
|
345
|
-
const body = this.pendingBody;
|
|
346
|
-
if (!this.canWrite() || !body || body === this.currentBody) {
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
if (this.messageId) {
|
|
350
|
-
await this.borgee.editMessage(this.messageId, body);
|
|
351
|
-
}
|
|
352
|
-
else {
|
|
353
|
-
const posted = await this.borgee.postMessage({
|
|
354
|
-
channelId: this.channelId,
|
|
355
|
-
body,
|
|
356
|
-
replyToId: this.creationMetadata?.replyToId,
|
|
357
|
-
});
|
|
358
|
-
this.messageId = posted.messageId;
|
|
359
|
-
}
|
|
360
|
-
this.currentBody = body;
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
enqueueWrite(operation) {
|
|
364
|
-
this.writeChain = this.writeChain.then(operation, operation);
|
|
365
|
-
return this.writeChain;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
260
|
/**
|
|
369
261
|
* The registered task property agents-host writes to record which provider
|
|
370
262
|
* session worked a task. It matches the server's registry key.
|
|
@@ -398,9 +290,12 @@ export class AgentsHost {
|
|
|
398
290
|
*/
|
|
399
291
|
recordedAgentSessionByTask = new Map();
|
|
400
292
|
attentionSnapshotByChannel = new Map();
|
|
293
|
+
compactionSnapshotByChannel = new Map();
|
|
294
|
+
activeCompactionByChannel = new Map();
|
|
295
|
+
compactionSnapshotLoadedChannels = new Set();
|
|
296
|
+
compactionSnapshotWriteQueueByChannel = new Map();
|
|
401
297
|
missedCollaborationDiagnosticByChannel = new Map();
|
|
402
|
-
|
|
403
|
-
progressDrafts = new Map();
|
|
298
|
+
channelTurnQueues = new Map();
|
|
404
299
|
collaborationDrafts = new Map();
|
|
405
300
|
collaborationChannels = new Map();
|
|
406
301
|
participantDirectoryByUserId = new Map();
|
|
@@ -508,20 +403,32 @@ export class AgentsHost {
|
|
|
508
403
|
this.invalidateChannelTurn(channelId);
|
|
509
404
|
this.clearProtocolFallback(this.collaborationChannels.get(channelId));
|
|
510
405
|
}
|
|
511
|
-
for (const draft of this.progressDrafts.values()) {
|
|
512
|
-
draft.cancelProgress();
|
|
513
|
-
}
|
|
514
406
|
for (const channelId of [...this.collaborationDrafts.keys()]) {
|
|
515
407
|
this.clearCollaborationDraft(channelId);
|
|
516
408
|
}
|
|
517
409
|
this.awaitingUserByChannel.clear();
|
|
518
410
|
this.collaborationOutcomeByChannel.clear();
|
|
519
411
|
this.attentionSnapshotByChannel.clear();
|
|
412
|
+
this.compactionSnapshotByChannel.clear();
|
|
413
|
+
this.activeCompactionByChannel.clear();
|
|
414
|
+
this.compactionSnapshotLoadedChannels.clear();
|
|
520
415
|
this.missedCollaborationDiagnosticByChannel.clear();
|
|
521
416
|
this.recordedAgentSessionByTask.clear();
|
|
522
417
|
this.controlPlaneClosed = true;
|
|
523
|
-
|
|
418
|
+
const pendingCompactionSnapshotWrites = [...this.compactionSnapshotWriteQueueByChannel.values()];
|
|
419
|
+
let runtimeStopError;
|
|
420
|
+
try {
|
|
421
|
+
await this.runtime.stop();
|
|
422
|
+
}
|
|
423
|
+
catch (error) {
|
|
424
|
+
runtimeStopError = error;
|
|
425
|
+
}
|
|
524
426
|
await Promise.allSettled([...this.activeTurns]);
|
|
427
|
+
await Promise.allSettled(pendingCompactionSnapshotWrites);
|
|
428
|
+
this.compactionSnapshotWriteQueueByChannel.clear();
|
|
429
|
+
if (runtimeStopError) {
|
|
430
|
+
throw runtimeStopError;
|
|
431
|
+
}
|
|
525
432
|
}
|
|
526
433
|
trackActiveTurn(task) {
|
|
527
434
|
this.activeTurns.add(task);
|
|
@@ -532,9 +439,7 @@ export class AgentsHost {
|
|
|
532
439
|
dispatchMessage(message) {
|
|
533
440
|
const task = this.collaborationEnabled
|
|
534
441
|
? this.handleCollaborationMessage(message)
|
|
535
|
-
:
|
|
536
|
-
? this.enqueueProgressChannelTurn(message.channel_id, () => this.handleLegacyMessage(message))
|
|
537
|
-
: this.handleLegacyMessage(message);
|
|
442
|
+
: this.enqueueChannelTurn(message.channel_id, () => this.handleLegacyMessage(message));
|
|
538
443
|
this.trackActiveTurn(task);
|
|
539
444
|
}
|
|
540
445
|
async handleLegacyMessage(msg) {
|
|
@@ -557,6 +462,9 @@ export class AgentsHost {
|
|
|
557
462
|
const authorKind = await this.classifyAuthor(normalized.authorId, false);
|
|
558
463
|
await this.ensureAttentionSnapshotLoaded(msg.channel_id, channelState);
|
|
559
464
|
attentionSnapshot = this.getAttentionSnapshotForTurn(msg.channel_id, taskThreadContext);
|
|
465
|
+
if (this.isOrdinaryAgentWakeFiltered(msg, authorKind)) {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
560
468
|
if (this.isHumanWakeFiltered(msg, authorKind, attentionSnapshot)) {
|
|
561
469
|
return;
|
|
562
470
|
}
|
|
@@ -649,6 +557,9 @@ export class AgentsHost {
|
|
|
649
557
|
}
|
|
650
558
|
}
|
|
651
559
|
}
|
|
560
|
+
if (this.isOrdinaryAgentWakeFiltered(msg, authorKind)) {
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
652
563
|
if (this.isHumanWakeFiltered(msg, authorKind, attentionSnapshot)) {
|
|
653
564
|
return;
|
|
654
565
|
}
|
|
@@ -1791,12 +1702,92 @@ export class AgentsHost {
|
|
|
1791
1702
|
resolveAttentionStatePath(channelId) {
|
|
1792
1703
|
return resolveAttentionStatePath(this.config.stateRootDir, channelId);
|
|
1793
1704
|
}
|
|
1705
|
+
resolveCompactionStatePath(channelId) {
|
|
1706
|
+
return resolveCompactionStatePath(this.config.stateRootDir, channelId);
|
|
1707
|
+
}
|
|
1794
1708
|
buildDefaultAttentionSnapshot(options) {
|
|
1795
1709
|
return buildNormalizedAttentionSnapshot({
|
|
1796
1710
|
observedAt: options?.observedAt,
|
|
1797
1711
|
turnExecutionId: options?.turnExecutionId,
|
|
1798
1712
|
});
|
|
1799
1713
|
}
|
|
1714
|
+
async ensureCompactionSnapshotLoaded(channelId) {
|
|
1715
|
+
if (this.compactionSnapshotLoadedChannels.has(channelId)) {
|
|
1716
|
+
return;
|
|
1717
|
+
}
|
|
1718
|
+
this.compactionSnapshotLoadedChannels.add(channelId);
|
|
1719
|
+
try {
|
|
1720
|
+
const content = await readFile(this.resolveCompactionStatePath(channelId), 'utf8');
|
|
1721
|
+
const snapshot = parsePersistedCompactionSnapshot(JSON.parse(content));
|
|
1722
|
+
if (snapshot) {
|
|
1723
|
+
this.compactionSnapshotByChannel.set(channelId, snapshot);
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
catch (error) {
|
|
1727
|
+
const code = error.code;
|
|
1728
|
+
if (code !== 'ENOENT') {
|
|
1729
|
+
this.logger.error('failed to load compaction state', {
|
|
1730
|
+
channelId,
|
|
1731
|
+
error: summarizeError(error),
|
|
1732
|
+
});
|
|
1733
|
+
}
|
|
1734
|
+
this.compactionSnapshotByChannel.delete(channelId);
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
async commitCompactionSnapshot(channelId, snapshot) {
|
|
1738
|
+
if (snapshot.stage === 'started') {
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
await writePrivateJsonFile(this.resolveCompactionStatePath(channelId), snapshot);
|
|
1742
|
+
}
|
|
1743
|
+
enqueueCompactionSnapshotCommit(channelId, snapshot) {
|
|
1744
|
+
const previous = this.compactionSnapshotWriteQueueByChannel.get(channelId) ?? Promise.resolve();
|
|
1745
|
+
const next = previous
|
|
1746
|
+
.catch(() => { })
|
|
1747
|
+
.then(() => this.commitCompactionSnapshot(channelId, snapshot));
|
|
1748
|
+
this.compactionSnapshotWriteQueueByChannel.set(channelId, next);
|
|
1749
|
+
void next.catch((error) => {
|
|
1750
|
+
this.logger.error('failed to persist compaction state', {
|
|
1751
|
+
channelId,
|
|
1752
|
+
error: summarizeError(error),
|
|
1753
|
+
});
|
|
1754
|
+
}).finally(() => {
|
|
1755
|
+
if (this.compactionSnapshotWriteQueueByChannel.get(channelId) === next) {
|
|
1756
|
+
this.compactionSnapshotWriteQueueByChannel.delete(channelId);
|
|
1757
|
+
}
|
|
1758
|
+
});
|
|
1759
|
+
}
|
|
1760
|
+
noteCompactionProgress(channelId, update, options) {
|
|
1761
|
+
const baseline = update.stage === 'started'
|
|
1762
|
+
? undefined
|
|
1763
|
+
: this.activeCompactionByChannel.get(channelId)
|
|
1764
|
+
?? this.compactionSnapshotByChannel.get(channelId);
|
|
1765
|
+
const nextSnapshot = mergeCompactionSnapshot(baseline, {
|
|
1766
|
+
provider: update.provider,
|
|
1767
|
+
stage: update.stage,
|
|
1768
|
+
observedAt: Date.now(),
|
|
1769
|
+
turnExecutionId: options?.turnExecutionId,
|
|
1770
|
+
usedTokens: update.usedTokens,
|
|
1771
|
+
contextWindowTokens: update.contextWindowTokens,
|
|
1772
|
+
failureReason: update.failureReason,
|
|
1773
|
+
});
|
|
1774
|
+
this.logger.debug('provider compaction update', {
|
|
1775
|
+
channelId,
|
|
1776
|
+
provider: nextSnapshot.provider,
|
|
1777
|
+
stage: nextSnapshot.stage,
|
|
1778
|
+
usedTokens: nextSnapshot.usedTokens,
|
|
1779
|
+
contextWindowTokens: nextSnapshot.contextWindowTokens,
|
|
1780
|
+
failureReason: nextSnapshot.failureReason,
|
|
1781
|
+
turnExecutionId: nextSnapshot.turnExecutionId,
|
|
1782
|
+
});
|
|
1783
|
+
if (nextSnapshot.stage === 'started') {
|
|
1784
|
+
this.activeCompactionByChannel.set(channelId, nextSnapshot);
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1787
|
+
this.activeCompactionByChannel.delete(channelId);
|
|
1788
|
+
this.compactionSnapshotByChannel.set(channelId, nextSnapshot);
|
|
1789
|
+
this.enqueueCompactionSnapshotCommit(channelId, nextSnapshot);
|
|
1790
|
+
}
|
|
1800
1791
|
/**
|
|
1801
1792
|
* Record which provider session worked this task, as agent.session_id.
|
|
1802
1793
|
*
|
|
@@ -1840,19 +1831,74 @@ export class AgentsHost {
|
|
|
1840
1831
|
if (!this.contextInjectionEnabled) {
|
|
1841
1832
|
return { state: 'inactive' };
|
|
1842
1833
|
}
|
|
1834
|
+
const recoverFromControlPlane = async (preferredTaskId) => {
|
|
1835
|
+
try {
|
|
1836
|
+
if (preferredTaskId) {
|
|
1837
|
+
try {
|
|
1838
|
+
const preferredTask = await this.borgee.getTask({ taskId: preferredTaskId });
|
|
1839
|
+
if (preferredTask.threadId === channelId) {
|
|
1840
|
+
return preferredTask.id.trim().length > 0
|
|
1841
|
+
? { state: 'recovered', currentTaskId: preferredTask.id }
|
|
1842
|
+
: { state: 'recovered' };
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
catch {
|
|
1846
|
+
// Fall through to the visibility-based scan.
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
const channels = await this.borgee.listChannels();
|
|
1850
|
+
const currentChannel = channels.find((channel) => channel.id === channelId);
|
|
1851
|
+
if (currentChannel && currentChannel.kind !== 'thread') {
|
|
1852
|
+
return { state: 'inactive' };
|
|
1853
|
+
}
|
|
1854
|
+
let sawTaskListSuccess = false;
|
|
1855
|
+
let sawTaskListFailure = false;
|
|
1856
|
+
for (const channel of channels) {
|
|
1857
|
+
let tasks;
|
|
1858
|
+
try {
|
|
1859
|
+
tasks = await this.borgee.listTasks({ channelId: channel.id });
|
|
1860
|
+
sawTaskListSuccess = true;
|
|
1861
|
+
}
|
|
1862
|
+
catch {
|
|
1863
|
+
sawTaskListFailure = true;
|
|
1864
|
+
continue;
|
|
1865
|
+
}
|
|
1866
|
+
for (const task of tasks) {
|
|
1867
|
+
if (task.threadId === channelId) {
|
|
1868
|
+
return task.id.trim().length > 0 ? { state: 'recovered', currentTaskId: task.id } : { state: 'recovered' };
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
return sawTaskListSuccess && !sawTaskListFailure ? { state: 'inactive' } : { state: 'unavailable' };
|
|
1873
|
+
}
|
|
1874
|
+
catch {
|
|
1875
|
+
return { state: 'unavailable' };
|
|
1876
|
+
}
|
|
1877
|
+
};
|
|
1843
1878
|
try {
|
|
1844
|
-
const content = await readFile(
|
|
1879
|
+
const content = await readFile(resolveTaskAssignmentStatePath(this.config.stateRootDir, channelId), 'utf8');
|
|
1845
1880
|
const payload = JSON.parse(content);
|
|
1846
1881
|
if (payload.taskAssignmentContext?.active !== true) {
|
|
1847
|
-
return
|
|
1882
|
+
return await recoverFromControlPlane();
|
|
1848
1883
|
}
|
|
1849
1884
|
const currentTaskId = typeof payload.taskAssignmentContext.currentTaskId === 'string'
|
|
1850
1885
|
? payload.taskAssignmentContext.currentTaskId.trim()
|
|
1851
1886
|
: '';
|
|
1852
|
-
|
|
1887
|
+
if (currentTaskId) {
|
|
1888
|
+
try {
|
|
1889
|
+
const task = await this.borgee.getTask({ taskId: currentTaskId });
|
|
1890
|
+
if (task.threadId === channelId) {
|
|
1891
|
+
return { state: 'active', currentTaskId: task.id };
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
catch {
|
|
1895
|
+
// Fall through to the recovery scan below.
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
return await recoverFromControlPlane(currentTaskId || undefined);
|
|
1853
1899
|
}
|
|
1854
1900
|
catch {
|
|
1855
|
-
return
|
|
1901
|
+
return await recoverFromControlPlane();
|
|
1856
1902
|
}
|
|
1857
1903
|
}
|
|
1858
1904
|
async ensureAttentionSnapshotLoaded(channelId, channelState) {
|
|
@@ -1952,11 +1998,28 @@ export class AgentsHost {
|
|
|
1952
1998
|
if (!ORDINARY_HUMAN_EVENT_KINDS.has(eventKind)) {
|
|
1953
1999
|
return false;
|
|
1954
2000
|
}
|
|
1955
|
-
if (
|
|
2001
|
+
if (msg.channel_type === 'dm'
|
|
2002
|
+
&& attentionSnapshot?.wakeMode !== 'mute-non-mention') {
|
|
1956
2003
|
return false;
|
|
1957
2004
|
}
|
|
1958
|
-
|
|
1959
|
-
|
|
2005
|
+
if (attentionSnapshot?.wakeMode === 'mute-non-mention') {
|
|
2006
|
+
this.noteWakeSuppressedAttentionPolicy(msg, authorKind);
|
|
2007
|
+
return true;
|
|
2008
|
+
}
|
|
2009
|
+
return false;
|
|
2010
|
+
}
|
|
2011
|
+
isOrdinaryAgentWakeFiltered(msg, authorKind) {
|
|
2012
|
+
if (authorKind !== 'agent') {
|
|
2013
|
+
return false;
|
|
2014
|
+
}
|
|
2015
|
+
if (msg.message_type === 'task_assignment') {
|
|
2016
|
+
return false;
|
|
2017
|
+
}
|
|
2018
|
+
if (msg.type?.trim() === 'mention') {
|
|
2019
|
+
return false;
|
|
2020
|
+
}
|
|
2021
|
+
const eventKind = msg.type?.trim() ?? '';
|
|
2022
|
+
return ORDINARY_HUMAN_EVENT_KINDS.has(eventKind);
|
|
1960
2023
|
}
|
|
1961
2024
|
applyAttentionUpdate(channelId, baseSnapshot, attentionUpdate, taskThreadContext, options) {
|
|
1962
2025
|
if (!this.attentionFollowSemanticsEnabled) {
|
|
@@ -2052,14 +2115,15 @@ export class AgentsHost {
|
|
|
2052
2115
|
taskThreadContextActive: taskThreadContext.state === 'active',
|
|
2053
2116
|
});
|
|
2054
2117
|
}
|
|
2055
|
-
resolveCollaborationCapabilityDeclarationForTurn() {
|
|
2118
|
+
resolveCollaborationCapabilityDeclarationForTurn(taskThreadContext) {
|
|
2056
2119
|
if (!this.collaborationCapabilitiesDiagnosticsEnabled) {
|
|
2057
2120
|
return undefined;
|
|
2058
2121
|
}
|
|
2059
2122
|
return {
|
|
2060
2123
|
...(this.collaborationOutcomeModelEnabled ? { collaborationOutcome: true } : {}),
|
|
2061
2124
|
...(this.attentionFollowSemanticsEnabled ? { attentionSnapshot: true } : {}),
|
|
2062
|
-
...(this.
|
|
2125
|
+
...(this.contextInjectionEnabled ? { compactionSnapshot: true } : {}),
|
|
2126
|
+
...(this.taskThreadCollaborationContractEnabled && taskThreadContext.state === 'active'
|
|
2063
2127
|
? { taskThreadCollaborationContract: true }
|
|
2064
2128
|
: {}),
|
|
2065
2129
|
missedCollaborationDiagnostic: true,
|
|
@@ -2156,18 +2220,12 @@ export class AgentsHost {
|
|
|
2156
2220
|
const turnExecutionId = activeTurn?.turnExecutionId ?? null;
|
|
2157
2221
|
const stopTyping = silentTurn ? () => { } : this.borgee.startTyping(channelId);
|
|
2158
2222
|
const collaborationGrounding = await this.buildCollaborationGrounding(authorId, options?.incomingAuthorKind, activeTurn, options?.kickoff);
|
|
2159
|
-
const
|
|
2160
|
-
!deferDelivery &&
|
|
2161
|
-
!this.collaborationEnabled &&
|
|
2162
|
-
options?.incomingAuthorKind !== 'agent' &&
|
|
2163
|
-
providerUsesDraftProgress(this.config.agent.provider) &&
|
|
2164
|
-
activeTurn?.protocol === undefined;
|
|
2165
|
-
const usePrivateCollaborationDraftProgress = !silentTurn &&
|
|
2223
|
+
const publishesCollaborationDraft = !silentTurn &&
|
|
2166
2224
|
!deferDelivery &&
|
|
2167
2225
|
this.collaborationEnabled &&
|
|
2168
2226
|
turnExecutionId != null &&
|
|
2169
|
-
providerUsesDraftProgress(this.config.agent.provider) &&
|
|
2170
2227
|
activeTurn?.protocol === undefined;
|
|
2228
|
+
const observeProviderProgress = publishesCollaborationDraft || this.config.agent.provider === 'claude';
|
|
2171
2229
|
const turnContext = {
|
|
2172
2230
|
eventType: msg.type,
|
|
2173
2231
|
channelId,
|
|
@@ -2188,12 +2246,15 @@ export class AgentsHost {
|
|
|
2188
2246
|
? this.collaborationOutcomeByChannel.get(channelId)
|
|
2189
2247
|
: undefined;
|
|
2190
2248
|
const missedCollaborationDiagnostic = this.resolveMissedCollaborationDiagnosticForTurn(channelId);
|
|
2191
|
-
const collaborationCapabilities = this.resolveCollaborationCapabilityDeclarationForTurn();
|
|
2249
|
+
const collaborationCapabilities = this.resolveCollaborationCapabilityDeclarationForTurn(taskThreadContext);
|
|
2192
2250
|
const incomingParts = options?.incomingParts
|
|
2193
2251
|
?? buildHostedTurnContentParts({
|
|
2194
2252
|
text: String(msg.content ?? msg.body ?? content),
|
|
2195
2253
|
attachments: msg.attachments,
|
|
2196
2254
|
});
|
|
2255
|
+
const activityReporter = this.beginTurnActivity(channelId, activeTurn?.generation);
|
|
2256
|
+
await this.ensureCompactionSnapshotLoaded(channelId);
|
|
2257
|
+
const compactionSnapshot = this.compactionSnapshotByChannel.get(channelId);
|
|
2197
2258
|
try {
|
|
2198
2259
|
reply = await this.provider.generateReply({
|
|
2199
2260
|
agentName: this.config.agent.agentName,
|
|
@@ -2216,17 +2277,45 @@ export class AgentsHost {
|
|
|
2216
2277
|
: undefined,
|
|
2217
2278
|
collaborationOutcome,
|
|
2218
2279
|
attentionSnapshot,
|
|
2280
|
+
compactionSnapshot,
|
|
2219
2281
|
...(taskThreadCollaborationContract ? { taskThreadCollaborationContract } : {}),
|
|
2220
2282
|
...(collaborationCapabilities ? { collaborationCapabilities } : {}),
|
|
2221
2283
|
...(missedCollaborationDiagnostic ? { missedCollaborationDiagnostic } : {}),
|
|
2222
|
-
|
|
2284
|
+
...((taskThreadContext.state === 'active'
|
|
2285
|
+
|| taskThreadContext.state === 'recovered'
|
|
2286
|
+
|| taskThreadContext.state === 'unavailable')
|
|
2287
|
+
? {
|
|
2288
|
+
taskAssignmentContextOverride: {
|
|
2289
|
+
active: true,
|
|
2290
|
+
...(taskThreadContext.state === 'active' && taskThreadContext.currentTaskId
|
|
2291
|
+
? { currentTaskId: taskThreadContext.currentTaskId }
|
|
2292
|
+
: {}),
|
|
2293
|
+
},
|
|
2294
|
+
...(taskThreadContext.state === 'active'
|
|
2295
|
+
? { taskAssignmentContextOverridePersistence: 'persist' }
|
|
2296
|
+
: { taskAssignmentContextOverridePersistence: 'ephemeral' }),
|
|
2297
|
+
}
|
|
2298
|
+
: {}),
|
|
2299
|
+
}, observeProviderProgress
|
|
2223
2300
|
? {
|
|
2224
2301
|
onProgress: (update) => {
|
|
2225
2302
|
if (!this.canAcceptTurnOutput(channelId, activeTurn?.generation)) {
|
|
2226
2303
|
return;
|
|
2227
2304
|
}
|
|
2228
|
-
if (
|
|
2229
|
-
this.
|
|
2305
|
+
if (update.type === 'compaction') {
|
|
2306
|
+
this.noteCompactionProgress(channelId, update.compaction, {
|
|
2307
|
+
turnExecutionId: activeTurn?.turnExecutionId ?? undefined,
|
|
2308
|
+
});
|
|
2309
|
+
return;
|
|
2310
|
+
}
|
|
2311
|
+
const reportedActivity = toReportedActivity(update);
|
|
2312
|
+
if (reportedActivity) {
|
|
2313
|
+
activityReporter.progress(reportedActivity);
|
|
2314
|
+
}
|
|
2315
|
+
if (update.type !== 'text' || update.stream !== 'answer') {
|
|
2316
|
+
return;
|
|
2317
|
+
}
|
|
2318
|
+
if (!publishesCollaborationDraft) {
|
|
2230
2319
|
return;
|
|
2231
2320
|
}
|
|
2232
2321
|
this.publishCollaborationDraft(channelId, activeTurn, update.text);
|
|
@@ -2249,7 +2338,6 @@ export class AgentsHost {
|
|
|
2249
2338
|
turnExecutionId: activeTurn?.turnExecutionId ?? undefined,
|
|
2250
2339
|
});
|
|
2251
2340
|
if (!(await this.canAcceptCompletedTurnOutput(channelId, activeTurn))) {
|
|
2252
|
-
await this.discardDraft(channelId);
|
|
2253
2341
|
if (activeTurn?.turnExecutionId) {
|
|
2254
2342
|
this.clearCollaborationDraft(channelId, {
|
|
2255
2343
|
generation: activeTurn.generation,
|
|
@@ -2298,35 +2386,7 @@ export class AgentsHost {
|
|
|
2298
2386
|
completed: true,
|
|
2299
2387
|
};
|
|
2300
2388
|
}
|
|
2301
|
-
if (
|
|
2302
|
-
const draft = this.progressDrafts.get(channelId);
|
|
2303
|
-
if (!this.started) {
|
|
2304
|
-
await draft?.discard().catch(() => { });
|
|
2305
|
-
this.progressDrafts.delete(channelId);
|
|
2306
|
-
return { reply, stale: true, completed: false };
|
|
2307
|
-
}
|
|
2308
|
-
const deliveredMessageId = await (draft ?? this.ensureProgressDraft(channelId, activeTurn?.generation, activeTurn?.delivery)).finalize(publicReplyText);
|
|
2309
|
-
this.progressDrafts.delete(channelId);
|
|
2310
|
-
this.logger.debug('completed turn', {
|
|
2311
|
-
...turnContext,
|
|
2312
|
-
durationMs: Date.now() - turnStartedAt,
|
|
2313
|
-
replyLength: publicReplyText.length,
|
|
2314
|
-
controlKind: reply?.control?.kind ?? null,
|
|
2315
|
-
controlMalformed: reply?.controlMalformed ?? false,
|
|
2316
|
-
delivery: 'draft',
|
|
2317
|
-
});
|
|
2318
|
-
return {
|
|
2319
|
-
reply,
|
|
2320
|
-
deliveredMessageId,
|
|
2321
|
-
publicReplyText,
|
|
2322
|
-
visibleReplyBody,
|
|
2323
|
-
deliveryState: deliveredMessageId ? 'draft-finalized' : 'not-delivered',
|
|
2324
|
-
appliedAttentionSnapshot,
|
|
2325
|
-
stale: false,
|
|
2326
|
-
completed: true,
|
|
2327
|
-
};
|
|
2328
|
-
}
|
|
2329
|
-
else if (this.started && !this.controlPlaneClosed && hasVisibleText(publicReplyText)) {
|
|
2389
|
+
if (this.started && !this.controlPlaneClosed && hasVisibleText(publicReplyText)) {
|
|
2330
2390
|
const posted = await this.borgee.postMessage({
|
|
2331
2391
|
channelId,
|
|
2332
2392
|
body: visibleReplyBody,
|
|
@@ -2340,7 +2400,7 @@ export class AgentsHost {
|
|
|
2340
2400
|
controlMalformed: reply?.controlMalformed ?? false,
|
|
2341
2401
|
delivery: 'post',
|
|
2342
2402
|
});
|
|
2343
|
-
if (
|
|
2403
|
+
if (publishesCollaborationDraft) {
|
|
2344
2404
|
this.finalizeCollaborationDraft(channelId, activeTurn, posted.messageId, visibleReplyBody);
|
|
2345
2405
|
}
|
|
2346
2406
|
return {
|
|
@@ -2358,9 +2418,9 @@ export class AgentsHost {
|
|
|
2358
2418
|
...turnContext,
|
|
2359
2419
|
durationMs: Date.now() - turnStartedAt,
|
|
2360
2420
|
replyLength: publicReplyText.length,
|
|
2361
|
-
delivery:
|
|
2421
|
+
delivery: 'post',
|
|
2362
2422
|
});
|
|
2363
|
-
if (
|
|
2423
|
+
if (publishesCollaborationDraft && activeTurn?.turnExecutionId) {
|
|
2364
2424
|
this.clearCollaborationDraft(channelId, {
|
|
2365
2425
|
generation: activeTurn.generation,
|
|
2366
2426
|
turnExecutionId: activeTurn.turnExecutionId,
|
|
@@ -2378,7 +2438,6 @@ export class AgentsHost {
|
|
|
2378
2438
|
};
|
|
2379
2439
|
}
|
|
2380
2440
|
catch (error) {
|
|
2381
|
-
await this.discardDraft(channelId);
|
|
2382
2441
|
if (activeTurn?.turnExecutionId) {
|
|
2383
2442
|
this.clearCollaborationDraft(channelId, {
|
|
2384
2443
|
generation: activeTurn.generation,
|
|
@@ -2404,8 +2463,31 @@ export class AgentsHost {
|
|
|
2404
2463
|
}
|
|
2405
2464
|
finally {
|
|
2406
2465
|
stopTyping();
|
|
2466
|
+
activityReporter.end();
|
|
2407
2467
|
}
|
|
2408
2468
|
}
|
|
2469
|
+
/**
|
|
2470
|
+
* Opens this turn's report on the activity rail.
|
|
2471
|
+
*
|
|
2472
|
+
* The generation is captured here, where the turn's own is, and every report
|
|
2473
|
+
* is checked against the channel's current turn before it leaves. That is what
|
|
2474
|
+
* keeps a superseded turn from announcing a boundary for a turn that no longer
|
|
2475
|
+
* exists — including its ending, which a window opened before it was replaced
|
|
2476
|
+
* would otherwise carry out on its behalf.
|
|
2477
|
+
*
|
|
2478
|
+
* Every turn reports, including the silent and deferred ones that deliver
|
|
2479
|
+
* nothing to the channel. The rail writes nothing anywhere, and the question
|
|
2480
|
+
* it answers — whether anything is happening in this channel — is worth the
|
|
2481
|
+
* same answer for a turn that keeps its reply to itself.
|
|
2482
|
+
*/
|
|
2483
|
+
beginTurnActivity(channelId, generation) {
|
|
2484
|
+
const reporter = this.borgee.reportTurnActivity({
|
|
2485
|
+
channelId,
|
|
2486
|
+
canReport: () => this.canAcceptTurnOutput(channelId, generation),
|
|
2487
|
+
});
|
|
2488
|
+
reporter.start();
|
|
2489
|
+
return reporter;
|
|
2490
|
+
}
|
|
2409
2491
|
canAcceptTurnOutput(channelId, generation) {
|
|
2410
2492
|
if (!this.started || this.controlPlaneClosed) {
|
|
2411
2493
|
return false;
|
|
@@ -2448,27 +2530,19 @@ export class AgentsHost {
|
|
|
2448
2530
|
this.clearProtocolFallback(channelState);
|
|
2449
2531
|
return false;
|
|
2450
2532
|
}
|
|
2451
|
-
|
|
2452
|
-
const previous = this.
|
|
2533
|
+
enqueueChannelTurn(channelId, task) {
|
|
2534
|
+
const previous = this.channelTurnQueues.get(channelId) ?? Promise.resolve();
|
|
2453
2535
|
const next = previous
|
|
2454
2536
|
.catch(() => { })
|
|
2455
2537
|
.then(task)
|
|
2456
2538
|
.finally(() => {
|
|
2457
|
-
if (this.
|
|
2458
|
-
this.
|
|
2539
|
+
if (this.channelTurnQueues.get(channelId) === next) {
|
|
2540
|
+
this.channelTurnQueues.delete(channelId);
|
|
2459
2541
|
}
|
|
2460
2542
|
});
|
|
2461
|
-
this.
|
|
2543
|
+
this.channelTurnQueues.set(channelId, next);
|
|
2462
2544
|
return next;
|
|
2463
2545
|
}
|
|
2464
|
-
ensureProgressDraft(channelId, generation, creationMetadata) {
|
|
2465
|
-
let draft = this.progressDrafts.get(channelId);
|
|
2466
|
-
if (!draft) {
|
|
2467
|
-
draft = new DraftMessageController(this.borgee, channelId, () => !this.controlPlaneClosed && this.canAcceptTurnOutput(channelId, generation), this.logger, creationMetadata);
|
|
2468
|
-
this.progressDrafts.set(channelId, draft);
|
|
2469
|
-
}
|
|
2470
|
-
return draft;
|
|
2471
|
-
}
|
|
2472
2546
|
publishCollaborationDraft(channelId, activeTurn, body) {
|
|
2473
2547
|
if (!activeTurn?.turnExecutionId || !hasVisibleText(body)) {
|
|
2474
2548
|
return;
|
|
@@ -2557,14 +2631,6 @@ export class AgentsHost {
|
|
|
2557
2631
|
}
|
|
2558
2632
|
return { ...draft.snapshot };
|
|
2559
2633
|
}
|
|
2560
|
-
async discardDraft(channelId) {
|
|
2561
|
-
const draft = this.progressDrafts.get(channelId);
|
|
2562
|
-
if (!draft) {
|
|
2563
|
-
return;
|
|
2564
|
-
}
|
|
2565
|
-
this.progressDrafts.delete(channelId);
|
|
2566
|
-
await draft.discard().catch(() => { });
|
|
2567
|
-
}
|
|
2568
2634
|
supersedeActiveTurn(channelId, activeTurn) {
|
|
2569
2635
|
if (activeTurn.superseded) {
|
|
2570
2636
|
return;
|
|
@@ -2582,7 +2648,6 @@ export class AgentsHost {
|
|
|
2582
2648
|
}
|
|
2583
2649
|
activeTurn.superseded = true;
|
|
2584
2650
|
activeTurn.turnExecutionId = null;
|
|
2585
|
-
void this.discardDraft(channelId);
|
|
2586
2651
|
}
|
|
2587
2652
|
invalidateChannelTurn(channelId) {
|
|
2588
2653
|
const activeTurn = this.collaborationChannels.get(channelId)?.activeTurn;
|