@borgee/agents-host 0.2.44 → 0.2.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +23 -27
  2. package/dist/agents-host.d.ts +26 -5
  3. package/dist/agents-host.js +163 -192
  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 +18 -1
  10. package/dist/context/claude-file-brief.d.ts +2 -0
  11. package/dist/context/claude-file-brief.js +83 -0
  12. package/dist/context/compaction.d.ts +20 -0
  13. package/dist/context/compaction.js +59 -0
  14. package/dist/context/injection.d.ts +39 -6
  15. package/dist/context/injection.js +317 -26
  16. package/dist/context/main-session-delegation.d.ts +1 -1
  17. package/dist/context/projection-strategy.d.ts +24 -0
  18. package/dist/context/projection-strategy.js +90 -0
  19. package/dist/context/prompt.d.ts +16 -1
  20. package/dist/context/prompt.js +456 -22
  21. package/dist/context/resolved-workspace.d.ts +2 -0
  22. package/dist/context/resolved-workspace.js +64 -0
  23. package/dist/context/skill-manual.d.ts +1 -0
  24. package/dist/context/skill-manual.js +4 -1
  25. package/dist/context/turn-preparation.d.ts +8 -2
  26. package/dist/context/turn-preparation.js +56 -14
  27. package/dist/gateway/localhost-gateway.js +2 -0
  28. package/dist/managed-daemon.js +122 -9
  29. package/dist/plugin-sdk.js +276 -359
  30. package/dist/plugin-sdk.js.map +4 -4
  31. package/dist/progress-to-activity.d.ts +16 -0
  32. package/dist/progress-to-activity.js +24 -0
  33. package/dist/projection-strategy-values.d.ts +4 -0
  34. package/dist/projection-strategy-values.js +28 -0
  35. package/dist/providers/acp-progress-collector.d.ts +44 -0
  36. package/dist/providers/acp-progress-collector.js +130 -0
  37. package/dist/providers/awaiting-user.d.ts +2 -3
  38. package/dist/providers/awaiting-user.js +5 -7
  39. package/dist/providers/claude/activity-metadata.d.ts +14 -0
  40. package/dist/providers/claude/activity-metadata.js +81 -0
  41. package/dist/providers/claude/cli-client.d.ts +1 -2
  42. package/dist/providers/claude/cli-client.js +190 -117
  43. package/dist/providers/codex/cli-client.js +3 -83
  44. package/dist/providers/codex/project-doc.js +12 -11
  45. package/dist/providers/copilot/cli-client.js +3 -83
  46. package/dist/providers/create-provider.d.ts +2 -0
  47. package/dist/providers/create-provider.js +20 -4
  48. package/dist/state-paths.d.ts +1 -1
  49. package/dist/state-paths.js +3 -3
  50. package/dist/task-thread-resolution.d.ts +3 -2
  51. package/dist/types.d.ts +130 -6
  52. package/package.json +2 -2
  53. package/skills/borgee-agent/SKILL.md +9 -1
  54. package/skills/borgee-agent/references/task-properties.md +5 -2
  55. package/dist/durable-cursor-store.d.ts +0 -5
  56. 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
12
  import { extractTaskIdFromTaskAssignmentContent, resolveChannelContextPayloadPath, } from './context/injection.js';
12
13
  import { AuthorizationAuditSink, } from './policy/authorization-audit.js';
13
14
  import { createProvider } from './providers/create-provider.js';
14
- import { resolveAttentionStatePath, resolveSharedProtocolKickoffDecisionPath, resolveSharedProtocolStatusPath, } from './state-paths.js';
15
+ import { resolveAttentionStatePath, resolveCompactionStatePath, resolveSharedProtocolKickoffDecisionPath, resolveSharedProtocolStatusPath, } from './state-paths.js';
15
16
  import { appendVisibleMention as appendVisibleBodyMention, extractVisibleMentionIds as extractCanonicalMentionIds, } from './visible-mentions.js';
16
17
  import { waitForTaskForThread } from './task-thread-resolution.js';
17
18
  import { buildHostedIncomingContentText, buildHostedTurnContentParts, extractHostedMessageAttachments, normalizeHostedMessageAttachment, } from './hosted-turn-content.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({
@@ -142,6 +139,7 @@ class DefaultHostRuntime {
142
139
  }, deps.logger, {
143
140
  compatibilityGates,
144
141
  authorizationAuditSink: this.authorizationAuditSink,
142
+ controlPlane: this.controlPlane,
145
143
  localhostGateway: localhostGatewayGateEnabled ? this.gateway.contextPublisher : undefined,
146
144
  });
147
145
  this.ensureStateRoot = deps.ensureStateRoot ?? ensurePrivateStateRoot;
@@ -258,113 +256,6 @@ function resolvePublicReplyText(reply) {
258
256
  }
259
257
  return reply.awaitingUser?.question ?? '';
260
258
  }
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
259
  /**
369
260
  * The registered task property agents-host writes to record which provider
370
261
  * session worked a task. It matches the server's registry key.
@@ -398,9 +289,12 @@ export class AgentsHost {
398
289
  */
399
290
  recordedAgentSessionByTask = new Map();
400
291
  attentionSnapshotByChannel = new Map();
292
+ compactionSnapshotByChannel = new Map();
293
+ activeCompactionByChannel = new Map();
294
+ compactionSnapshotLoadedChannels = new Set();
295
+ compactionSnapshotWriteQueueByChannel = new Map();
401
296
  missedCollaborationDiagnosticByChannel = new Map();
402
- progressChannelQueues = new Map();
403
- progressDrafts = new Map();
297
+ channelTurnQueues = new Map();
404
298
  collaborationDrafts = new Map();
405
299
  collaborationChannels = new Map();
406
300
  participantDirectoryByUserId = new Map();
@@ -508,20 +402,32 @@ export class AgentsHost {
508
402
  this.invalidateChannelTurn(channelId);
509
403
  this.clearProtocolFallback(this.collaborationChannels.get(channelId));
510
404
  }
511
- for (const draft of this.progressDrafts.values()) {
512
- draft.cancelProgress();
513
- }
514
405
  for (const channelId of [...this.collaborationDrafts.keys()]) {
515
406
  this.clearCollaborationDraft(channelId);
516
407
  }
517
408
  this.awaitingUserByChannel.clear();
518
409
  this.collaborationOutcomeByChannel.clear();
519
410
  this.attentionSnapshotByChannel.clear();
411
+ this.compactionSnapshotByChannel.clear();
412
+ this.activeCompactionByChannel.clear();
413
+ this.compactionSnapshotLoadedChannels.clear();
520
414
  this.missedCollaborationDiagnosticByChannel.clear();
521
415
  this.recordedAgentSessionByTask.clear();
522
416
  this.controlPlaneClosed = true;
523
- await this.runtime.stop();
417
+ const pendingCompactionSnapshotWrites = [...this.compactionSnapshotWriteQueueByChannel.values()];
418
+ let runtimeStopError;
419
+ try {
420
+ await this.runtime.stop();
421
+ }
422
+ catch (error) {
423
+ runtimeStopError = error;
424
+ }
524
425
  await Promise.allSettled([...this.activeTurns]);
426
+ await Promise.allSettled(pendingCompactionSnapshotWrites);
427
+ this.compactionSnapshotWriteQueueByChannel.clear();
428
+ if (runtimeStopError) {
429
+ throw runtimeStopError;
430
+ }
525
431
  }
526
432
  trackActiveTurn(task) {
527
433
  this.activeTurns.add(task);
@@ -532,9 +438,7 @@ export class AgentsHost {
532
438
  dispatchMessage(message) {
533
439
  const task = this.collaborationEnabled
534
440
  ? this.handleCollaborationMessage(message)
535
- : providerUsesDraftProgress(this.config.agent.provider)
536
- ? this.enqueueProgressChannelTurn(message.channel_id, () => this.handleLegacyMessage(message))
537
- : this.handleLegacyMessage(message);
441
+ : this.enqueueChannelTurn(message.channel_id, () => this.handleLegacyMessage(message));
538
442
  this.trackActiveTurn(task);
539
443
  }
540
444
  async handleLegacyMessage(msg) {
@@ -1791,12 +1695,92 @@ export class AgentsHost {
1791
1695
  resolveAttentionStatePath(channelId) {
1792
1696
  return resolveAttentionStatePath(this.config.stateRootDir, channelId);
1793
1697
  }
1698
+ resolveCompactionStatePath(channelId) {
1699
+ return resolveCompactionStatePath(this.config.stateRootDir, channelId);
1700
+ }
1794
1701
  buildDefaultAttentionSnapshot(options) {
1795
1702
  return buildNormalizedAttentionSnapshot({
1796
1703
  observedAt: options?.observedAt,
1797
1704
  turnExecutionId: options?.turnExecutionId,
1798
1705
  });
1799
1706
  }
1707
+ async ensureCompactionSnapshotLoaded(channelId) {
1708
+ if (this.compactionSnapshotLoadedChannels.has(channelId)) {
1709
+ return;
1710
+ }
1711
+ this.compactionSnapshotLoadedChannels.add(channelId);
1712
+ try {
1713
+ const content = await readFile(this.resolveCompactionStatePath(channelId), 'utf8');
1714
+ const snapshot = parsePersistedCompactionSnapshot(JSON.parse(content));
1715
+ if (snapshot) {
1716
+ this.compactionSnapshotByChannel.set(channelId, snapshot);
1717
+ }
1718
+ }
1719
+ catch (error) {
1720
+ const code = error.code;
1721
+ if (code !== 'ENOENT') {
1722
+ this.logger.error('failed to load compaction state', {
1723
+ channelId,
1724
+ error: summarizeError(error),
1725
+ });
1726
+ }
1727
+ this.compactionSnapshotByChannel.delete(channelId);
1728
+ }
1729
+ }
1730
+ async commitCompactionSnapshot(channelId, snapshot) {
1731
+ if (snapshot.stage === 'started') {
1732
+ return;
1733
+ }
1734
+ await writePrivateJsonFile(this.resolveCompactionStatePath(channelId), snapshot);
1735
+ }
1736
+ enqueueCompactionSnapshotCommit(channelId, snapshot) {
1737
+ const previous = this.compactionSnapshotWriteQueueByChannel.get(channelId) ?? Promise.resolve();
1738
+ const next = previous
1739
+ .catch(() => { })
1740
+ .then(() => this.commitCompactionSnapshot(channelId, snapshot));
1741
+ this.compactionSnapshotWriteQueueByChannel.set(channelId, next);
1742
+ void next.catch((error) => {
1743
+ this.logger.error('failed to persist compaction state', {
1744
+ channelId,
1745
+ error: summarizeError(error),
1746
+ });
1747
+ }).finally(() => {
1748
+ if (this.compactionSnapshotWriteQueueByChannel.get(channelId) === next) {
1749
+ this.compactionSnapshotWriteQueueByChannel.delete(channelId);
1750
+ }
1751
+ });
1752
+ }
1753
+ noteCompactionProgress(channelId, update, options) {
1754
+ const baseline = update.stage === 'started'
1755
+ ? undefined
1756
+ : this.activeCompactionByChannel.get(channelId)
1757
+ ?? this.compactionSnapshotByChannel.get(channelId);
1758
+ const nextSnapshot = mergeCompactionSnapshot(baseline, {
1759
+ provider: update.provider,
1760
+ stage: update.stage,
1761
+ observedAt: Date.now(),
1762
+ turnExecutionId: options?.turnExecutionId,
1763
+ usedTokens: update.usedTokens,
1764
+ contextWindowTokens: update.contextWindowTokens,
1765
+ failureReason: update.failureReason,
1766
+ });
1767
+ this.logger.debug('provider compaction update', {
1768
+ channelId,
1769
+ provider: nextSnapshot.provider,
1770
+ stage: nextSnapshot.stage,
1771
+ usedTokens: nextSnapshot.usedTokens,
1772
+ contextWindowTokens: nextSnapshot.contextWindowTokens,
1773
+ failureReason: nextSnapshot.failureReason,
1774
+ turnExecutionId: nextSnapshot.turnExecutionId,
1775
+ });
1776
+ if (nextSnapshot.stage === 'started') {
1777
+ this.activeCompactionByChannel.set(channelId, nextSnapshot);
1778
+ return;
1779
+ }
1780
+ this.activeCompactionByChannel.delete(channelId);
1781
+ this.compactionSnapshotByChannel.set(channelId, nextSnapshot);
1782
+ this.enqueueCompactionSnapshotCommit(channelId, nextSnapshot);
1783
+ }
1800
1784
  /**
1801
1785
  * Record which provider session worked this task, as agent.session_id.
1802
1786
  *
@@ -2052,14 +2036,15 @@ export class AgentsHost {
2052
2036
  taskThreadContextActive: taskThreadContext.state === 'active',
2053
2037
  });
2054
2038
  }
2055
- resolveCollaborationCapabilityDeclarationForTurn() {
2039
+ resolveCollaborationCapabilityDeclarationForTurn(taskThreadContext) {
2056
2040
  if (!this.collaborationCapabilitiesDiagnosticsEnabled) {
2057
2041
  return undefined;
2058
2042
  }
2059
2043
  return {
2060
2044
  ...(this.collaborationOutcomeModelEnabled ? { collaborationOutcome: true } : {}),
2061
2045
  ...(this.attentionFollowSemanticsEnabled ? { attentionSnapshot: true } : {}),
2062
- ...(this.taskThreadCollaborationContractEnabled
2046
+ ...(this.contextInjectionEnabled ? { compactionSnapshot: true } : {}),
2047
+ ...(this.taskThreadCollaborationContractEnabled && taskThreadContext.state === 'active'
2063
2048
  ? { taskThreadCollaborationContract: true }
2064
2049
  : {}),
2065
2050
  missedCollaborationDiagnostic: true,
@@ -2156,18 +2141,12 @@ export class AgentsHost {
2156
2141
  const turnExecutionId = activeTurn?.turnExecutionId ?? null;
2157
2142
  const stopTyping = silentTurn ? () => { } : this.borgee.startTyping(channelId);
2158
2143
  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 &&
2144
+ const publishesCollaborationDraft = !silentTurn &&
2166
2145
  !deferDelivery &&
2167
2146
  this.collaborationEnabled &&
2168
2147
  turnExecutionId != null &&
2169
- providerUsesDraftProgress(this.config.agent.provider) &&
2170
2148
  activeTurn?.protocol === undefined;
2149
+ const observeProviderProgress = publishesCollaborationDraft || this.config.agent.provider === 'claude';
2171
2150
  const turnContext = {
2172
2151
  eventType: msg.type,
2173
2152
  channelId,
@@ -2188,12 +2167,15 @@ export class AgentsHost {
2188
2167
  ? this.collaborationOutcomeByChannel.get(channelId)
2189
2168
  : undefined;
2190
2169
  const missedCollaborationDiagnostic = this.resolveMissedCollaborationDiagnosticForTurn(channelId);
2191
- const collaborationCapabilities = this.resolveCollaborationCapabilityDeclarationForTurn();
2170
+ const collaborationCapabilities = this.resolveCollaborationCapabilityDeclarationForTurn(taskThreadContext);
2192
2171
  const incomingParts = options?.incomingParts
2193
2172
  ?? buildHostedTurnContentParts({
2194
2173
  text: String(msg.content ?? msg.body ?? content),
2195
2174
  attachments: msg.attachments,
2196
2175
  });
2176
+ const activityReporter = this.beginTurnActivity(channelId, activeTurn?.generation);
2177
+ await this.ensureCompactionSnapshotLoaded(channelId);
2178
+ const compactionSnapshot = this.compactionSnapshotByChannel.get(channelId);
2197
2179
  try {
2198
2180
  reply = await this.provider.generateReply({
2199
2181
  agentName: this.config.agent.agentName,
@@ -2216,17 +2198,30 @@ export class AgentsHost {
2216
2198
  : undefined,
2217
2199
  collaborationOutcome,
2218
2200
  attentionSnapshot,
2201
+ compactionSnapshot,
2219
2202
  ...(taskThreadCollaborationContract ? { taskThreadCollaborationContract } : {}),
2220
2203
  ...(collaborationCapabilities ? { collaborationCapabilities } : {}),
2221
2204
  ...(missedCollaborationDiagnostic ? { missedCollaborationDiagnostic } : {}),
2222
- }, usePublicDraftProgress || usePrivateCollaborationDraftProgress
2205
+ }, observeProviderProgress
2223
2206
  ? {
2224
2207
  onProgress: (update) => {
2225
2208
  if (!this.canAcceptTurnOutput(channelId, activeTurn?.generation)) {
2226
2209
  return;
2227
2210
  }
2228
- if (usePublicDraftProgress) {
2229
- this.ensureProgressDraft(channelId, activeTurn?.generation, activeTurn?.delivery).update(update.text);
2211
+ if (update.type === 'compaction') {
2212
+ this.noteCompactionProgress(channelId, update.compaction, {
2213
+ turnExecutionId: activeTurn?.turnExecutionId ?? undefined,
2214
+ });
2215
+ return;
2216
+ }
2217
+ const reportedActivity = toReportedActivity(update);
2218
+ if (reportedActivity) {
2219
+ activityReporter.progress(reportedActivity);
2220
+ }
2221
+ if (update.type !== 'text' || update.stream !== 'answer') {
2222
+ return;
2223
+ }
2224
+ if (!publishesCollaborationDraft) {
2230
2225
  return;
2231
2226
  }
2232
2227
  this.publishCollaborationDraft(channelId, activeTurn, update.text);
@@ -2249,7 +2244,6 @@ export class AgentsHost {
2249
2244
  turnExecutionId: activeTurn?.turnExecutionId ?? undefined,
2250
2245
  });
2251
2246
  if (!(await this.canAcceptCompletedTurnOutput(channelId, activeTurn))) {
2252
- await this.discardDraft(channelId);
2253
2247
  if (activeTurn?.turnExecutionId) {
2254
2248
  this.clearCollaborationDraft(channelId, {
2255
2249
  generation: activeTurn.generation,
@@ -2298,35 +2292,7 @@ export class AgentsHost {
2298
2292
  completed: true,
2299
2293
  };
2300
2294
  }
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)) {
2295
+ if (this.started && !this.controlPlaneClosed && hasVisibleText(publicReplyText)) {
2330
2296
  const posted = await this.borgee.postMessage({
2331
2297
  channelId,
2332
2298
  body: visibleReplyBody,
@@ -2340,7 +2306,7 @@ export class AgentsHost {
2340
2306
  controlMalformed: reply?.controlMalformed ?? false,
2341
2307
  delivery: 'post',
2342
2308
  });
2343
- if (usePrivateCollaborationDraftProgress) {
2309
+ if (publishesCollaborationDraft) {
2344
2310
  this.finalizeCollaborationDraft(channelId, activeTurn, posted.messageId, visibleReplyBody);
2345
2311
  }
2346
2312
  return {
@@ -2358,9 +2324,9 @@ export class AgentsHost {
2358
2324
  ...turnContext,
2359
2325
  durationMs: Date.now() - turnStartedAt,
2360
2326
  replyLength: publicReplyText.length,
2361
- delivery: usePublicDraftProgress ? 'draft' : 'post',
2327
+ delivery: 'post',
2362
2328
  });
2363
- if (usePrivateCollaborationDraftProgress && activeTurn?.turnExecutionId) {
2329
+ if (publishesCollaborationDraft && activeTurn?.turnExecutionId) {
2364
2330
  this.clearCollaborationDraft(channelId, {
2365
2331
  generation: activeTurn.generation,
2366
2332
  turnExecutionId: activeTurn.turnExecutionId,
@@ -2378,7 +2344,6 @@ export class AgentsHost {
2378
2344
  };
2379
2345
  }
2380
2346
  catch (error) {
2381
- await this.discardDraft(channelId);
2382
2347
  if (activeTurn?.turnExecutionId) {
2383
2348
  this.clearCollaborationDraft(channelId, {
2384
2349
  generation: activeTurn.generation,
@@ -2404,8 +2369,31 @@ export class AgentsHost {
2404
2369
  }
2405
2370
  finally {
2406
2371
  stopTyping();
2372
+ activityReporter.end();
2407
2373
  }
2408
2374
  }
2375
+ /**
2376
+ * Opens this turn's report on the activity rail.
2377
+ *
2378
+ * The generation is captured here, where the turn's own is, and every report
2379
+ * is checked against the channel's current turn before it leaves. That is what
2380
+ * keeps a superseded turn from announcing a boundary for a turn that no longer
2381
+ * exists — including its ending, which a window opened before it was replaced
2382
+ * would otherwise carry out on its behalf.
2383
+ *
2384
+ * Every turn reports, including the silent and deferred ones that deliver
2385
+ * nothing to the channel. The rail writes nothing anywhere, and the question
2386
+ * it answers — whether anything is happening in this channel — is worth the
2387
+ * same answer for a turn that keeps its reply to itself.
2388
+ */
2389
+ beginTurnActivity(channelId, generation) {
2390
+ const reporter = this.borgee.reportTurnActivity({
2391
+ channelId,
2392
+ canReport: () => this.canAcceptTurnOutput(channelId, generation),
2393
+ });
2394
+ reporter.start();
2395
+ return reporter;
2396
+ }
2409
2397
  canAcceptTurnOutput(channelId, generation) {
2410
2398
  if (!this.started || this.controlPlaneClosed) {
2411
2399
  return false;
@@ -2448,27 +2436,19 @@ export class AgentsHost {
2448
2436
  this.clearProtocolFallback(channelState);
2449
2437
  return false;
2450
2438
  }
2451
- enqueueProgressChannelTurn(channelId, task) {
2452
- const previous = this.progressChannelQueues.get(channelId) ?? Promise.resolve();
2439
+ enqueueChannelTurn(channelId, task) {
2440
+ const previous = this.channelTurnQueues.get(channelId) ?? Promise.resolve();
2453
2441
  const next = previous
2454
2442
  .catch(() => { })
2455
2443
  .then(task)
2456
2444
  .finally(() => {
2457
- if (this.progressChannelQueues.get(channelId) === next) {
2458
- this.progressChannelQueues.delete(channelId);
2445
+ if (this.channelTurnQueues.get(channelId) === next) {
2446
+ this.channelTurnQueues.delete(channelId);
2459
2447
  }
2460
2448
  });
2461
- this.progressChannelQueues.set(channelId, next);
2449
+ this.channelTurnQueues.set(channelId, next);
2462
2450
  return next;
2463
2451
  }
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
2452
  publishCollaborationDraft(channelId, activeTurn, body) {
2473
2453
  if (!activeTurn?.turnExecutionId || !hasVisibleText(body)) {
2474
2454
  return;
@@ -2557,14 +2537,6 @@ export class AgentsHost {
2557
2537
  }
2558
2538
  return { ...draft.snapshot };
2559
2539
  }
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
2540
  supersedeActiveTurn(channelId, activeTurn) {
2569
2541
  if (activeTurn.superseded) {
2570
2542
  return;
@@ -2582,7 +2554,6 @@ export class AgentsHost {
2582
2554
  }
2583
2555
  activeTurn.superseded = true;
2584
2556
  activeTurn.turnExecutionId = null;
2585
- void this.discardDraft(channelId);
2586
2557
  }
2587
2558
  invalidateChannelTurn(channelId) {
2588
2559
  const activeTurn = this.collaborationChannels.get(channelId)?.activeTurn;
@@ -1,3 +1,4 @@
1
+ import type { ReportTurnActivityInput, TurnActivityReporter } from '../plugin-sdk.js';
1
2
  import type { ChannelSummary, ChannelHistoryEntry, ChannelMessageEvent, CreateTaskInput, DirectoryUser, MeResponseUser, PostMessageInput, PostedMessage, ReadChannelHistoryInput, Task, UpdateTaskInput } from '../types.js';
2
3
  export interface ChatControlPlane {
3
4
  connect(onMessage: (message: ChannelMessageEvent) => void): Promise<void>;
@@ -6,6 +7,8 @@ export interface ChatControlPlane {
6
7
  editMessage(messageId: string, content: string): Promise<void>;
7
8
  deleteMessage(messageId: string): Promise<void>;
8
9
  startTyping(channelId: string): () => void;
10
+ /** Opens one turn's report on the activity rail. Fire-and-forget: nothing recovers what it sends. */
11
+ reportTurnActivity(input: ReportTurnActivityInput): TurnActivityReporter;
9
12
  getMe(): Promise<MeResponseUser>;
10
13
  listUsers(): Promise<DirectoryUser[]>;
11
14
  listChannels(): Promise<ChannelSummary[]>;
@@ -1,9 +1,9 @@
1
- import { type BorgeePluginClient, type BorgeePluginOptions, type InboundMessageEvent } from '../plugin-sdk.js';
1
+ import { type BorgeePluginClient, type BorgeePluginOptions, type InboundMessageEvent, type ReportTurnActivityInput, type TurnActivityReporter } from '../plugin-sdk.js';
2
2
  import type { ChannelSummary, ChannelHistoryEntry, ChannelMessageEvent, CreateTaskInput, DirectoryUser, MeResponseUser, PostMessageInput, PostedMessage, ReadChannelHistoryInput, Task, UpdateTaskInput } from '../types.js';
3
3
  import type { ChatControlPlane } from './chat-control-plane.js';
4
- type PluginClientLike = Pick<BorgeePluginClient, 'agentId' | 'close' | 'connect' | 'createTask' | 'deleteMessage' | 'editMessage' | 'getMe' | 'getTask' | 'listChannels' | 'listTasks' | 'listUsers' | 'on' | 'readHistory' | 'sendMessage' | 'startTyping' | 'updateTask' | 'setTaskProperty' | 'deleteTaskProperty'>;
4
+ type PluginClientLike = Pick<BorgeePluginClient, 'agentId' | 'close' | 'connect' | 'createTask' | 'deleteMessage' | 'editMessage' | 'getMe' | 'getTask' | 'listChannels' | 'listTasks' | 'listUsers' | 'on' | 'readHistory' | 'reportTurnActivity' | 'sendMessage' | 'startTyping' | 'updateTask' | 'setTaskProperty' | 'deleteTaskProperty'>;
5
5
  type PluginClientFactory = (options: BorgeePluginOptions) => PluginClientLike;
6
- type SdkChatControlPlaneOptions = Pick<BorgeePluginOptions, 'cursorStore' | 'pluginId'>;
6
+ type SdkChatControlPlaneOptions = Pick<BorgeePluginOptions, 'pluginId'>;
7
7
  /**
8
8
  * Thin adapter over `@borgee/plugin-sdk` (the same BPP/`/ws/plugin` SDK used by
9
9
  * the OpenClaw plugin) that implements the minimal `ChatControlPlane` surface
@@ -22,6 +22,7 @@ export declare class SdkChatControlPlane implements ChatControlPlane {
22
22
  editMessage(messageId: string, content: string): Promise<void>;
23
23
  deleteMessage(messageId: string): Promise<void>;
24
24
  startTyping(channelId: string): () => void;
25
+ reportTurnActivity(input: ReportTurnActivityInput): TurnActivityReporter;
25
26
  getMe(): Promise<MeResponseUser>;
26
27
  listUsers(): Promise<DirectoryUser[]>;
27
28
  readChannelHistory(input: ReadChannelHistoryInput): Promise<ChannelHistoryEntry[]>;
@@ -65,6 +65,9 @@ export class SdkChatControlPlane {
65
65
  startTyping(channelId) {
66
66
  return this.client.startTyping(channelId);
67
67
  }
68
+ reportTurnActivity(input) {
69
+ return this.client.reportTurnActivity(input);
70
+ }
68
71
  async getMe() {
69
72
  if (this.me) {
70
73
  return this.me;
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { realpathSync } from 'node:fs';
3
- import { dirname, resolve } from 'node:path';
3
+ import { resolve } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { assertNoUpdateArgs, CliUsageError, parseApplyManagedArgs, parseDaemonArgs, parseDescribeArgs, parseDescribeManagedArgs, parseCleanupManagedArgs, parseGenerateConfigArgs, parseLogArgs, parseManagedStartArgs, parsePrintLayoutArgs, parseStartArgs, parseValidateArgs, USAGE, } from './cli-args.js';
6
6
  import { resolveAgentsHostDebugMode } from './debug.js';
@@ -15,7 +15,6 @@ function formatErrorMessage(error) {
15
15
  }
16
16
  return String(error);
17
17
  }
18
- const TASK_WORKSPACE_ROOT_DIR_ENV = 'AGENTS_HOST_INTERNAL_TASK_WORKSPACE_ROOT_DIR';
19
18
  export async function dispatchCli(argv, deps = {}) {
20
19
  const [command, ...rest] = argv;
21
20
  const env = deps.env ?? process.env;
@@ -31,7 +30,6 @@ export async function dispatchCli(argv, deps = {}) {
31
30
  const applyManagedSpecImpl = deps.applyManagedSpec ?? applyManagedSpec;
32
31
  const startManagedDaemonImpl = deps.startManagedDaemon ??
33
32
  (async (rootPath, debug) => {
34
- env[TASK_WORKSPACE_ROOT_DIR_ENV] = rootPath;
35
33
  const daemon = new ManagedAgentsHostDaemon(rootPath, debug, {
36
34
  logPath: env.AGENTS_HOST_MANAGED_DAEMON_LOG_PATH,
37
35
  });
@@ -63,7 +61,6 @@ export async function dispatchCli(argv, deps = {}) {
63
61
  await runMainImpl({ debug });
64
62
  return;
65
63
  }
66
- env[TASK_WORKSPACE_ROOT_DIR_ENV] = dirname(resolve(parsed.configPath));
67
64
  await runMainImpl({ configPath: parsed.configPath, debug });
68
65
  return;
69
66
  }
@@ -167,7 +164,6 @@ export async function dispatchCli(argv, deps = {}) {
167
164
  }
168
165
  if (command === 'daemon') {
169
166
  const parsed = parseDaemonArgs(rest);
170
- env[TASK_WORKSPACE_ROOT_DIR_ENV] = parsed.rootPath;
171
167
  await startManagedDaemonImpl(parsed.rootPath, parsed.debug);
172
168
  return;
173
169
  }
@@ -1,3 +1,4 @@
1
+ import type { ProjectionStrategy } from './types.js';
1
2
  export declare const CLAUDE_PROVIDER_V2_COMPATIBILITY_GATE = "claude-provider-v2";
2
3
  export declare const COPILOT_PROVIDER_V2_COMPATIBILITY_GATE = "copilot-provider-v2";
3
4
  export declare const CODEX_PROVIDER_COMPATIBILITY_GATE = "codex-provider";
@@ -18,6 +19,7 @@ export declare const COMPATIBILITY_GATES_ENV = "AGENTS_HOST_INTERNAL_COMPATIBILI
18
19
  export declare const INTERNAL_DISABLED_COMPATIBILITY_GATES_ENV = "AGENTS_HOST_INTERNAL_DISABLED_COMPATIBILITY_GATES";
19
20
  export declare const INTERNAL_POLICY_MODE_ENV = "AGENTS_HOST_INTERNAL_POLICY_MODE";
20
21
  export declare const INTERNAL_PROVIDER_IMPLEMENTATIONS_ENV = "AGENTS_HOST_INTERNAL_PROVIDER_IMPLEMENTATIONS";
22
+ export declare const INTERNAL_CLAUDE_PROMPT_STRATEGY_ENV = "AGENTS_HOST_INTERNAL_CLAUDE_PROMPT_STRATEGY";
21
23
  export declare const MANAGED_RUNTIME_SETTINGS_SCHEMA_VERSION = 1;
22
24
  export type InternalPolicyMode = 'audit-only' | 'enforce';
23
25
  export type InternalProviderImplementationPath = 'v1' | 'v2';
@@ -28,8 +30,10 @@ export interface ManagedRuntimeSettingsSnapshot {
28
30
  compatibilityGates: string[];
29
31
  providerImplementationOverrides: string[];
30
32
  internalPolicyMode: InternalPolicyMode;
33
+ projectionStrategy: ProjectionStrategy;
31
34
  }
32
35
  export declare const DEFAULT_INTERNAL_COMPATIBILITY_GATES: readonly string[];
36
+ export declare function resolveInternalProjectionStrategy(env?: NodeJS.ProcessEnv): ProjectionStrategy;
33
37
  export declare function parseCompatibilityGates(rawValue: string | undefined): ReadonlySet<string>;
34
38
  export declare function resolveInternalCompatibilityGates(env?: NodeJS.ProcessEnv): ReadonlySet<string>;
35
39
  export declare function resolveDisabledDefaultCompatibilityGates(compatibilityGates: Iterable<string>): string[];