@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.
Files changed (65) hide show
  1. package/README.md +24 -27
  2. package/dist/agents-host.d.ts +27 -5
  3. package/dist/agents-host.js +266 -201
  4. package/dist/chat/chat-control-plane.d.ts +3 -0
  5. package/dist/chat/sdk-chat-control-plane.d.ts +4 -3
  6. package/dist/chat/sdk-chat-control-plane.js +3 -0
  7. package/dist/cli.js +1 -5
  8. package/dist/compatibility-gates.d.ts +4 -0
  9. package/dist/compatibility-gates.js +20 -1
  10. package/dist/config.d.ts +2 -0
  11. package/dist/config.js +21 -0
  12. package/dist/context/claude-file-brief.d.ts +2 -0
  13. package/dist/context/claude-file-brief.js +83 -0
  14. package/dist/context/compaction.d.ts +20 -0
  15. package/dist/context/compaction.js +59 -0
  16. package/dist/context/injection.d.ts +58 -7
  17. package/dist/context/injection.js +370 -36
  18. package/dist/context/main-session-delegation.d.ts +1 -1
  19. package/dist/context/projection-strategy.d.ts +24 -0
  20. package/dist/context/projection-strategy.js +90 -0
  21. package/dist/context/prompt.d.ts +16 -1
  22. package/dist/context/prompt.js +464 -26
  23. package/dist/context/resolved-workspace.d.ts +3 -0
  24. package/dist/context/resolved-workspace.js +106 -0
  25. package/dist/context/skill-manual.d.ts +1 -0
  26. package/dist/context/skill-manual.js +4 -1
  27. package/dist/context/turn-preparation.d.ts +8 -2
  28. package/dist/context/turn-preparation.js +64 -14
  29. package/dist/gateway/localhost-gateway.js +4 -5
  30. package/dist/local-config.js +11 -1
  31. package/dist/managed-daemon.js +127 -9
  32. package/dist/plugin-sdk.js +264 -364
  33. package/dist/plugin-sdk.js.map +4 -4
  34. package/dist/policy/copilot-permission.d.ts +1 -0
  35. package/dist/policy/copilot-permission.js +18 -0
  36. package/dist/policy/gateway-authorization.js +2 -2
  37. package/dist/progress-to-activity.d.ts +16 -0
  38. package/dist/progress-to-activity.js +24 -0
  39. package/dist/projection-strategy-values.d.ts +4 -0
  40. package/dist/projection-strategy-values.js +28 -0
  41. package/dist/providers/acp-progress-collector.d.ts +44 -0
  42. package/dist/providers/acp-progress-collector.js +130 -0
  43. package/dist/providers/awaiting-user.d.ts +2 -3
  44. package/dist/providers/awaiting-user.js +5 -7
  45. package/dist/providers/claude/activity-metadata.d.ts +14 -0
  46. package/dist/providers/claude/activity-metadata.js +81 -0
  47. package/dist/providers/claude/cli-client.d.ts +2 -3
  48. package/dist/providers/claude/cli-client.js +220 -120
  49. package/dist/providers/codex/cli-client.d.ts +2 -0
  50. package/dist/providers/codex/cli-client.js +28 -104
  51. package/dist/providers/codex/project-doc.js +12 -11
  52. package/dist/providers/copilot/cli-client.d.ts +1 -1
  53. package/dist/providers/copilot/cli-client.js +12 -86
  54. package/dist/providers/create-provider.d.ts +2 -0
  55. package/dist/providers/create-provider.js +22 -4
  56. package/dist/state-paths.d.ts +1 -1
  57. package/dist/state-paths.js +3 -3
  58. package/dist/task-thread-resolution.d.ts +3 -2
  59. package/dist/types.d.ts +143 -6
  60. package/package.json +2 -2
  61. package/skills/borgee-agent/SKILL.md +12 -4
  62. package/skills/borgee-agent/references/errors.md +2 -2
  63. package/skills/borgee-agent/references/task-properties.md +6 -2
  64. package/dist/durable-cursor-store.d.ts +0 -5
  65. package/dist/durable-cursor-store.js +0 -7
@@ -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 { createDurableCursorStore } from './durable-cursor-store.js';
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, resolveChannelContextPayloadPath, } from './context/injection.js';
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
- progressChannelQueues = new Map();
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
- await this.runtime.stop();
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
- : providerUsesDraftProgress(this.config.agent.provider)
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(resolveChannelContextPayloadPath(this.config.stateRootDir, channelId), 'utf8');
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 { state: 'inactive' };
1882
+ return await recoverFromControlPlane();
1848
1883
  }
1849
1884
  const currentTaskId = typeof payload.taskAssignmentContext.currentTaskId === 'string'
1850
1885
  ? payload.taskAssignmentContext.currentTaskId.trim()
1851
1886
  : '';
1852
- return currentTaskId ? { state: 'active', currentTaskId } : { state: 'active' };
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 { state: 'unavailable' };
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 (attentionSnapshot?.wakeMode === 'follow-visible-human') {
2001
+ if (msg.channel_type === 'dm'
2002
+ && attentionSnapshot?.wakeMode !== 'mute-non-mention') {
1956
2003
  return false;
1957
2004
  }
1958
- this.noteWakeSuppressedAttentionPolicy(msg, authorKind);
1959
- return true;
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.taskThreadCollaborationContractEnabled
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 usePublicDraftProgress = !silentTurn &&
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
- }, usePublicDraftProgress || usePrivateCollaborationDraftProgress
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 (usePublicDraftProgress) {
2229
- this.ensureProgressDraft(channelId, activeTurn?.generation, activeTurn?.delivery).update(update.text);
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 (usePublicDraftProgress) {
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 (usePrivateCollaborationDraftProgress) {
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: usePublicDraftProgress ? 'draft' : 'post',
2421
+ delivery: 'post',
2362
2422
  });
2363
- if (usePrivateCollaborationDraftProgress && activeTurn?.turnExecutionId) {
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
- enqueueProgressChannelTurn(channelId, task) {
2452
- const previous = this.progressChannelQueues.get(channelId) ?? Promise.resolve();
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.progressChannelQueues.get(channelId) === next) {
2458
- this.progressChannelQueues.delete(channelId);
2539
+ if (this.channelTurnQueues.get(channelId) === next) {
2540
+ this.channelTurnQueues.delete(channelId);
2459
2541
  }
2460
2542
  });
2461
- this.progressChannelQueues.set(channelId, next);
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;