@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
@@ -1,4 +1,4 @@
1
- import type { PreparedPromptContext, ProviderKind } from '../types.js';
1
+ import type { ProjectionStrategy, PreparedPromptContext, ProviderKind } from '../types.js';
2
2
  /**
3
3
  * Builds the per-turn prompt. Conversation memory is intentionally NOT
4
4
  * assembled here: each provider's CLI client resumes a native per-channel
@@ -16,3 +16,18 @@ export declare function buildPrompt(params: {
16
16
  incomingMessageType?: string;
17
17
  promptContext?: PreparedPromptContext;
18
18
  }): string;
19
+ export declare function buildClaudeSessionPromptAppend(params: {
20
+ agentName: string;
21
+ promptContext?: PreparedPromptContext;
22
+ promptStrategy?: ProjectionStrategy;
23
+ }): string | undefined;
24
+ export declare function buildClaudeTurnPrompt(params: {
25
+ agentName?: string;
26
+ channelId: string;
27
+ incomingAuthorId: string;
28
+ incomingContent: string;
29
+ incomingEventKind?: string;
30
+ incomingMessageType?: string;
31
+ promptContext?: PreparedPromptContext;
32
+ promptStrategy?: ProjectionStrategy;
33
+ }): string;
@@ -1,9 +1,11 @@
1
+ import { DEFAULT_PROJECTION_STRATEGY, normalizeProjectionStrategy, } from '../projection-strategy-values.js';
1
2
  import { AWAITING_USER_CONTROL_PREFIX } from '../providers/awaiting-user.js';
2
3
  import { buildAttentionSummaryLines } from './attention.js';
3
4
  import { buildCollaborationCapabilityDeclarationSummaryLines, buildMissedCollaborationDiagnosticSummaryLines, } from './collaboration-capabilities-diagnostics.js';
4
5
  import { buildCollaborationOutcomeSummaryLines } from './collaboration-outcome.js';
5
6
  import { MAIN_SESSION_DELEGATION_LINES } from './main-session-delegation.js';
6
- import { buildSkillManualLines } from './skill-manual.js';
7
+ import { buildResolvedWorkspaceGuidanceLines } from './resolved-workspace.js';
8
+ import { buildSkillManualLines, buildSkillManualReadLine } from './skill-manual.js';
7
9
  import { buildTaskThreadCollaborationSummaryLines } from './task-thread-collaboration.js';
8
10
  function providerLabel(provider) {
9
11
  if (provider === 'copilot') {
@@ -37,23 +39,55 @@ function buildLocalhostGatewayPromptLines(context) {
37
39
  if (!context.skillRuntime) {
38
40
  return lines;
39
41
  }
40
- lines.push(context.taskAssignmentContext?.active === true
42
+ const taskThreadActive = context.taskAssignmentContext?.active === true
43
+ || context.runtimeSurface?.task.currentThread === 'task-assignment-thread';
44
+ lines.push(taskThreadActive
41
45
  ? 'This turn runs inside a task assignment thread.'
42
46
  : 'This turn runs in a parent channel, not a task thread.');
47
+ if (context.runtimeSurface?.task.currentThread === 'parent-channel'
48
+ && (context.collaborationTurnMode ?? 'ordinary') === 'ordinary') {
49
+ if (context.runtimeSurface.task.parentChannelTaskCollection === 'available') {
50
+ lines.push('Task list and task create are available in this parent channel.');
51
+ lines.push(`For parent-channel task operations on this turn, read the packaged borgee-agent manual at ${context.skillRuntime.skillMarkdownPath} and follow that command grammar exactly.`);
52
+ lines.push('For task creation and task updates in this parent channel, use the packaged borgee-agent task rail through this gateway credential file.');
53
+ lines.push('Do not invent a bare task or bare borgee command.');
54
+ }
55
+ if (context.runtimeSurface.task.currentTaskShorthand === 'requires-task-id') {
56
+ lines.push('In this parent channel, task get, task update, task history, and task property commands require an explicit task id.');
57
+ }
58
+ }
59
+ else if (context.runtimeSurface?.task.currentThread === 'task-assignment-thread'
60
+ && (context.collaborationTurnMode ?? 'ordinary') === 'ordinary') {
61
+ lines.push('In this task thread, task list and task create stay on the parent channel.');
62
+ }
43
63
  const collaborationLive = context.localhostGateway.collaboration?.enabled === true
44
64
  && (context.collaborationTurnMode ?? 'ordinary') === 'ordinary';
45
65
  if (!collaborationLive) {
46
- lines.push('Collaboration commands are not available this turn.');
66
+ lines.push('Auxiliary collaboration commands (draft, send, mention) are not available this turn.');
47
67
  }
48
68
  else if (context.collaborationTurnExecutionId) {
49
69
  lines.push(`Pass --turn-execution-id ${context.collaborationTurnExecutionId} on send, mention and draft.`);
50
70
  }
71
+ if (context.runtimeSurface?.response.mainVisibleReply === 'model-visible-reply') {
72
+ lines.push('Keep the main visible completion in this thread.');
73
+ }
74
+ if (context.runtimeSurface?.response.auxiliaryMessageUsage === 'targeted-escalation-only') {
75
+ lines.push('Use auxiliary sends only for intentional targeted escalation, mention, or reply notices when the injected runtimeSurface allows them.');
76
+ }
51
77
  return lines;
52
78
  }
53
79
  function buildTaskAssignmentPromptLines(params) {
54
80
  const isTaskAssignmentTurn = params.incomingMessageType?.trim() === 'task_assignment';
55
81
  const taskAssignmentContext = params.promptContext?.taskAssignmentContext;
56
- if (!isTaskAssignmentTurn && taskAssignmentContext?.active !== true) {
82
+ const taskAssignmentThreadActive = taskAssignmentContext?.active === true
83
+ || params.promptContext?.runtimeSurface?.task.currentThread === 'task-assignment-thread';
84
+ if (!isTaskAssignmentTurn && !taskAssignmentThreadActive) {
85
+ return [];
86
+ }
87
+ if (params.provider === 'claude'
88
+ && params.forceInlineForClaude !== true
89
+ && !isTaskAssignmentTurn
90
+ && taskAssignmentThreadActive) {
57
91
  return [];
58
92
  }
59
93
  const lines = isTaskAssignmentTurn
@@ -65,19 +99,167 @@ function buildTaskAssignmentPromptLines(params) {
65
99
  'This thread is continuing an existing task assignment.',
66
100
  'Keep the main work in this thread.',
67
101
  ];
68
- if (taskAssignmentContext?.currentTaskId) {
102
+ if ((params.provider !== 'claude' || params.forceInlineForClaude === true) && params.promptContext?.resolvedWorkspace) {
103
+ lines.push(...buildResolvedWorkspacePromptLines(params.provider, params.promptContext));
104
+ }
105
+ if ((params.provider !== 'claude' || params.forceInlineForClaude === true) && taskAssignmentContext?.currentTaskId) {
69
106
  lines.push(`Current assigned task id: ${taskAssignmentContext.currentTaskId}.`);
70
- if (params.promptContext?.taskWorkspace) {
71
- lines.push(`Task-scoped writable workspace root: ${params.promptContext.taskWorkspace.rootPath}.`);
72
- lines.push('This writable workspace is local to agents-host for the current task thread. It does not imply that the target repository has already been checked out there.');
73
- }
74
107
  }
75
- else if (taskAssignmentContext?.active === true) {
108
+ else if ((params.provider !== 'claude' || params.forceInlineForClaude === true) && taskAssignmentContext?.active === true) {
76
109
  lines.push('No current task id is persisted in the injected thread context for this task thread.');
77
110
  }
78
- lines.push('Return your normal final response in this thread, and do not move the main work back to the parent channel.');
111
+ if (params.provider !== 'claude' || params.forceInlineForClaude === true) {
112
+ lines.push('Return your normal final response in this thread, and do not move the main work back to the parent channel.');
113
+ }
79
114
  return lines;
80
115
  }
116
+ function buildResolvedWorkspacePromptLines(provider, context) {
117
+ return buildResolvedWorkspaceGuidanceLines(context, provider);
118
+ }
119
+ function buildClaudeOrdinaryGatewayTaskLines(context) {
120
+ if (!context?.skillRuntime || !context.localhostGateway || !context.gatewayCredentialPath) {
121
+ return [];
122
+ }
123
+ if ((context.collaborationTurnMode ?? 'ordinary') !== 'ordinary') {
124
+ return [];
125
+ }
126
+ if (context.runtimeSurface?.task.currentThread !== 'parent-channel') {
127
+ return [];
128
+ }
129
+ if (context.runtimeSurface.task.parentChannelTaskCollection !== 'available') {
130
+ return [];
131
+ }
132
+ const lines = [
133
+ `Gateway credential file for this turn: ${context.gatewayCredentialPath}`,
134
+ 'This turn runs in a parent channel, not a task thread.',
135
+ 'Task list and task create are available in this parent channel.',
136
+ `For parent-channel task operations on this turn, read the packaged borgee-agent manual at ${context.skillRuntime.skillMarkdownPath} and follow that command grammar exactly.`,
137
+ 'For task creation and task updates in this parent channel, use the packaged borgee-agent task rail through this gateway credential file.',
138
+ 'Do not invent a bare task or bare borgee command.',
139
+ ];
140
+ if (context.runtimeSurface.task.currentTaskShorthand === 'requires-task-id') {
141
+ lines.push('In this parent channel, task get, task update, task history, and task property commands require an explicit task id.');
142
+ }
143
+ return lines;
144
+ }
145
+ function buildClaudeProjectedBriefSessionLines(context) {
146
+ if (!context?.claudeProjectedBriefPath) {
147
+ return [];
148
+ }
149
+ const lines = [
150
+ `Read the stable Borgee runtime brief for this session at ${context.claudeProjectedBriefPath} before you act on this channel; it is authoritative for the stable platform rules on this channel.`,
151
+ ];
152
+ if (context.skillRuntime) {
153
+ lines.push(`For command grammar and CLI inventory, read the packaged borgee-agent manual at ${context.skillRuntime.skillMarkdownPath}.`);
154
+ }
155
+ lines.push(...buildClaudeOrdinarySessionControlLines());
156
+ return lines;
157
+ }
158
+ function buildClaudeProjectedBriefTurnDeltaLines(context) {
159
+ if (!context?.localhostGateway || !context.gatewayCredentialPath) {
160
+ return [];
161
+ }
162
+ const lines = [];
163
+ if ((context.collaborationTurnMode ?? 'ordinary') !== 'ordinary') {
164
+ return [`Gateway credential file for this turn: ${context.gatewayCredentialPath}`];
165
+ }
166
+ if (context.runtimeSurface?.task.currentThread === 'parent-channel') {
167
+ const taskLines = buildClaudeOrdinaryGatewayTaskLines(context);
168
+ if (taskLines.length > 0) {
169
+ lines.push(...taskLines);
170
+ }
171
+ else {
172
+ lines.push(`Gateway credential file for this turn: ${context.gatewayCredentialPath}`, 'This turn runs in a parent channel, not a task thread.');
173
+ if (context.runtimeSurface.task.parentChannelTaskCollection === 'available') {
174
+ lines.push('Task list and task create are available in this parent channel.');
175
+ }
176
+ }
177
+ }
178
+ else if (context.runtimeSurface?.task.currentThread === 'task-assignment-thread') {
179
+ lines.push(`Gateway credential file for this turn: ${context.gatewayCredentialPath}`, 'This turn runs inside a task assignment thread.', 'In this task thread, task list and task create stay on the parent channel.');
180
+ }
181
+ else {
182
+ lines.push(`Gateway credential file for this turn: ${context.gatewayCredentialPath}`);
183
+ }
184
+ const collaborationLive = context.localhostGateway.collaboration?.enabled === true
185
+ && (context.collaborationTurnMode ?? 'ordinary') === 'ordinary';
186
+ if (!collaborationLive) {
187
+ lines.push('Auxiliary collaboration commands (draft, send, mention) are not available this turn.');
188
+ }
189
+ else if (context.collaborationTurnExecutionId) {
190
+ lines.push(`Pass --turn-execution-id ${context.collaborationTurnExecutionId} on send, mention and draft.`);
191
+ }
192
+ if (context.runtimeSurface?.response.mainVisibleReply === 'model-visible-reply') {
193
+ lines.push('Keep the main visible completion in this thread.');
194
+ }
195
+ if (context.runtimeSurface?.response.auxiliaryMessageUsage === 'targeted-escalation-only') {
196
+ lines.push('Use auxiliary sends only for intentional targeted escalation, mention, or reply notices when the injected runtimeSurface allows them.');
197
+ }
198
+ return lines;
199
+ }
200
+ function buildClaudeProjectedBriefTurnPrompt(params) {
201
+ if (isClaudeZeroTurnFallbackRequired(params)) {
202
+ return buildClaudeZeroTurnPrompt(params);
203
+ }
204
+ if (!params.promptContext?.claudeProjectedBriefPath) {
205
+ return [
206
+ ...buildClaudeTurnControlPromptLines(params.promptContext),
207
+ ...buildPromptBodyLines({
208
+ provider: 'claude',
209
+ channelId: params.channelId,
210
+ incomingAuthorId: params.incomingAuthorId,
211
+ incomingContent: params.incomingContent,
212
+ incomingEventKind: params.incomingEventKind,
213
+ incomingMessageType: params.incomingMessageType,
214
+ promptContext: params.promptContext,
215
+ }),
216
+ ].join('\n');
217
+ }
218
+ const deltaLines = buildClaudeProjectedBriefTurnDeltaLines(params.promptContext);
219
+ return [
220
+ ...buildClaudeTurnControlPromptLines(params.promptContext),
221
+ `Channel: ${params.channelId}`,
222
+ ...buildInboundMetadataLines({
223
+ provider: 'claude',
224
+ incomingEventKind: params.incomingEventKind,
225
+ incomingMessageType: params.incomingMessageType,
226
+ promptContext: params.promptContext,
227
+ }),
228
+ ...(() => {
229
+ const lines = buildCollaborationOutcomeSummaryLines(params.promptContext?.collaborationOutcome);
230
+ return lines.length > 0 ? ['', ...lines] : [];
231
+ })(),
232
+ ...(() => {
233
+ const lines = buildAttentionSummaryLines(params.promptContext?.attentionSnapshot);
234
+ return lines.length > 0 ? ['', ...lines] : [];
235
+ })(),
236
+ ...(() => {
237
+ const lines = buildCollaborationCapabilityDeclarationSummaryLines(params.promptContext?.collaborationCapabilities);
238
+ return lines.length > 0 ? ['', ...lines] : [];
239
+ })(),
240
+ ...(() => {
241
+ const lines = buildMissedCollaborationDiagnosticSummaryLines(params.promptContext?.missedCollaborationDiagnostic);
242
+ return lines.length > 0 ? ['', ...lines] : [];
243
+ })(),
244
+ ...(deltaLines.length > 0 ? ['', ...deltaLines] : []),
245
+ '',
246
+ `New message from ${params.incomingAuthorId}:`,
247
+ params.incomingContent,
248
+ ].join('\n');
249
+ }
250
+ function buildOrdinaryCopilotWorkspacePromptLines(params) {
251
+ if (params.provider !== 'copilot') {
252
+ return [];
253
+ }
254
+ if (!params.promptContext?.resolvedWorkspace) {
255
+ return [];
256
+ }
257
+ if (params.incomingMessageType?.trim() === 'task_assignment'
258
+ || params.promptContext.taskAssignmentContext?.active === true) {
259
+ return [];
260
+ }
261
+ return buildResolvedWorkspacePromptLines(params.provider, params.promptContext);
262
+ }
81
263
  function buildInboundMetadataLines(params) {
82
264
  return [
83
265
  `Incoming transport event kind: ${params.incomingEventKind?.trim() || 'message'}`,
@@ -86,7 +268,16 @@ function buildInboundMetadataLines(params) {
86
268
  const lines = buildTaskThreadCollaborationSummaryLines(params.promptContext?.taskThreadCollaborationContract);
87
269
  return lines.length > 0 ? ['', ...lines] : [];
88
270
  })(),
89
- ...buildTaskAssignmentPromptLines(params),
271
+ ...(() => {
272
+ const lines = buildOrdinaryCopilotWorkspacePromptLines(params);
273
+ return lines.length > 0 ? ['', ...lines] : [];
274
+ })(),
275
+ ...buildTaskAssignmentPromptLines({
276
+ provider: params.provider,
277
+ incomingMessageType: params.incomingMessageType,
278
+ promptContext: params.promptContext,
279
+ forceInlineForClaude: params.forceClaudeTaskAssignmentInline,
280
+ }),
90
281
  ];
91
282
  }
92
283
  function describeIdentity(identity) {
@@ -97,15 +288,15 @@ function describeIdentity(identity) {
97
288
  const label = name && name.length > 0 ? `${name} (${identity.id})` : identity.id;
98
289
  return `${label}, kind=${identity.kind}`;
99
290
  }
100
- function buildTurnControlPromptLines(context) {
291
+ function buildTurnControlAttentionLines(context) {
101
292
  const taskThreadAttentionAvailable = context?.taskAssignmentContext?.active === true
102
293
  && typeof context.taskAssignmentContext.currentTaskId === 'string'
103
294
  && context.taskAssignmentContext.currentTaskId.trim().length > 0;
104
295
  const attentionLines = context?.attentionSnapshot
105
296
  ? [
106
297
  'Attention controls are projection-only host hints. They do not bypass server-side mention policy or widen message delivery.',
107
- `To follow ordinary delivered human messages in this channel, add "attentionUpdate":"follow-channel".`,
108
- `To stop follow-based human wake for this channel, add "attentionUpdate":"unfollow-channel".`,
298
+ `To pin a durable follow hint for ordinary delivered human messages in this channel, add "attentionUpdate":"follow-channel".`,
299
+ `To clear an explicit follow hint and return to server-policy wake for this channel, add "attentionUpdate":"unfollow-channel".`,
109
300
  `To mute ordinary delivered human wake while keeping explicit mentions and task assignments untouched, add "attentionUpdate":"mute-channel".`,
110
301
  `To set a durable channel-level attention hint, add "attentionUpdate":"claim-channel".`,
111
302
  ...(taskThreadAttentionAvailable
@@ -123,6 +314,10 @@ function buildTurnControlPromptLines(context) {
123
314
  `For an attention-only reply, keep a non-empty visible body and append: ${AWAITING_USER_CONTROL_PREFIX}{"kind":"attention-only","attentionUpdate":"follow-channel"}`,
124
315
  ]
125
316
  : [];
317
+ return { attentionLines, attentionOnlyLine };
318
+ }
319
+ function buildTurnControlPromptLines(context) {
320
+ const { attentionLines, attentionOnlyLine } = buildTurnControlAttentionLines(context);
126
321
  if (!context) {
127
322
  return [
128
323
  `Only when you need host-local turn control, append exactly one final non-empty line starting with ${AWAITING_USER_CONTROL_PREFIX}.`,
@@ -195,14 +390,69 @@ function buildTurnControlPromptLines(context) {
195
390
  'Do not emit multiple control lines, and do not use a control footer for ordinary answers.',
196
391
  ];
197
392
  }
198
- /**
199
- * Builds the per-turn prompt. Conversation memory is intentionally NOT
200
- * assembled here: each provider's CLI client resumes a native per-channel
201
- * session (Claude `--resume`, Copilot `--session-id`), so the CLI itself
202
- * already has the prior turns. This only needs to carry the agent's
203
- * identity/instructions plus the new incoming message.
204
- */
205
- export function buildPrompt(params) {
393
+ function buildClaudeTurnControlPromptLines(context) {
394
+ const { attentionLines, attentionOnlyLine } = buildTurnControlAttentionLines(context);
395
+ if (context?.collaborationTurnMode === 'protocol-managed' && context.protocol) {
396
+ const selfIdentity = describeIdentity(context.grounding?.self);
397
+ const incomingIdentity = describeIdentity(context.grounding?.incomingAuthor);
398
+ const peerIdentity = describeIdentity(context.grounding?.peer);
399
+ return [
400
+ `Protocol-managed collaboration is already active for this turn. Your host-managed role is ${context.protocol.role}, and your target peer is ${context.protocol.targetPeerId}.`,
401
+ ...(selfIdentity ? [`Your self identity for this turn: ${selfIdentity}.`] : []),
402
+ ...(peerIdentity ? [`Your peer for this turn: ${peerIdentity}.`] : []),
403
+ ...(incomingIdentity ? [`The incoming author for this turn is ${incomingIdentity}.`] : []),
404
+ 'The host will deliver the visible protocol reply body for you; you only decide the body plus exactly one control footer.',
405
+ 'Do not use auxiliary collaboration send commands during this turn, even if other turns can use them.',
406
+ 'Do not attempt a localhost gateway message post during this turn.',
407
+ `When handing off to the named peer, use: ${AWAITING_USER_CONTROL_PREFIX}{"kind":"continue-to-peer"}`,
408
+ `When finishing locally, use: ${AWAITING_USER_CONTROL_PREFIX}{"kind":"conclude-locally"}`,
409
+ `If you are blocked on the human instead, use: ${AWAITING_USER_CONTROL_PREFIX}{"kind":"awaiting-user","question":"<short question>","reason":"<short reason>"}`,
410
+ ...(context.attentionSnapshot
411
+ ? ['You may add one attentionUpdate field to continue-to-peer, conclude-locally, or awaiting-user when you also need an attention change for this channel.']
412
+ : []),
413
+ ...attentionLines,
414
+ 'Do not use start-protocol while host-managed collaboration is already active.',
415
+ ];
416
+ }
417
+ if (context?.collaborationTurnMode === 'silent-kickoff' && context.kickoff) {
418
+ const kickoff = context.kickoff;
419
+ const otherParticipantId = kickoff.participantIds.find((participantId) => participantId !== kickoff.issuerId);
420
+ const selfIdentity = describeIdentity(context.grounding?.self);
421
+ const incomingIdentity = describeIdentity(context.grounding?.incomingAuthor);
422
+ const peerIdentity = describeIdentity(context.grounding?.peer);
423
+ const participantSummary = context.grounding?.participants
424
+ ?.map((participant) => describeIdentity(participant))
425
+ .filter((participant) => participant != null)
426
+ .join('; ');
427
+ return [
428
+ `Silent protocol kickoff evaluation is active for anchor ${kickoff.anchorMessageId}. The issuer is ${kickoff.issuerId}${otherParticipantId ? ` and the other participant is ${otherParticipantId}` : ''}.`,
429
+ ...(context.grounding?.hostRecognizedKickoffCandidate
430
+ ? [
431
+ 'The host has already recognized this message as a structurally valid kickoff candidate and is asking you to decide whether host-managed collaboration should start.',
432
+ ]
433
+ : []),
434
+ ...(selfIdentity ? [`Your self identity for this decision: ${selfIdentity}.`] : []),
435
+ ...(peerIdentity ? [`Your peer candidate for this decision: ${peerIdentity}.`] : []),
436
+ ...(incomingIdentity
437
+ ? [`The incoming author for this decision is ${incomingIdentity}.`]
438
+ : []),
439
+ ...(participantSummary ? [`Visible kickoff participants: ${participantSummary}.`] : []),
440
+ 'This evaluation is read-only and silent: do not use auxiliary collaboration send commands, and do not draft a user-visible reply body for this turn.',
441
+ `If the anchor message is genuinely requesting host-managed collaboration between the named agents, use: ${AWAITING_USER_CONTROL_PREFIX}{"kind":"start-protocol","rounds":<positive integer>}`,
442
+ 'Do not combine start-protocol with any attentionUpdate field.',
443
+ 'If collaboration should not start, omit the control footer entirely.',
444
+ ];
445
+ }
446
+ return [
447
+ 'Do not claim to have performed actions you did not actually perform.',
448
+ ...(context?.attentionSnapshot
449
+ ? ['You may add one attentionUpdate field to awaiting-user when you also need an attention change for this channel.']
450
+ : []),
451
+ ...attentionLines,
452
+ ...attentionOnlyLine,
453
+ ];
454
+ }
455
+ function buildPromptHeaderLines(params) {
206
456
  return [
207
457
  `You are ${params.agentName}, a helpful AI teammate inside Borgee.`,
208
458
  `Your current backend provider is ${providerLabel(params.provider)} (${params.provider}).`,
@@ -210,14 +460,34 @@ export function buildPrompt(params) {
210
460
  'Be concise, helpful, and honest about uncertainty.',
211
461
  'Do not claim to have performed actions you did not actually perform.',
212
462
  'Keep the visible reply text concise.',
213
- ...MAIN_SESSION_DELEGATION_LINES,
214
- ...buildTurnControlPromptLines(params.promptContext),
215
- `If the user asks who you are, what powers you, or which backend/provider you use, mention that you are currently running on ${providerLabel(params.provider)}.`,
463
+ ];
464
+ }
465
+ function buildProviderIdentityReminderLine(provider) {
466
+ return `If the user asks who you are, what powers you, or which backend/provider you use, mention that you are currently running on ${providerLabel(provider)}.`;
467
+ }
468
+ function buildSessionDelegationPromptLines(provider, context) {
469
+ if (provider === 'copilot') {
470
+ return MAIN_SESSION_DELEGATION_LINES;
471
+ }
472
+ return context?.skillRuntime ? [] : MAIN_SESSION_DELEGATION_LINES;
473
+ }
474
+ function buildClaudeOrdinarySessionControlLines() {
475
+ return [
476
+ `Only when you need host-local turn control, append exactly one final non-empty line starting with ${AWAITING_USER_CONTROL_PREFIX}.`,
477
+ `For ordinary blocked-on-human turns, use: ${AWAITING_USER_CONTROL_PREFIX}{"kind":"awaiting-user","question":"<short question>","reason":"<short reason>"}`,
478
+ 'Do not emit multiple control lines.',
479
+ 'Do not use a control footer for ordinary answers.',
480
+ ];
481
+ }
482
+ function buildPromptBodyLines(params) {
483
+ return [
216
484
  `Channel: ${params.channelId}`,
217
485
  ...buildInboundMetadataLines({
486
+ provider: params.provider,
218
487
  incomingEventKind: params.incomingEventKind,
219
488
  incomingMessageType: params.incomingMessageType,
220
489
  promptContext: params.promptContext,
490
+ forceClaudeTaskAssignmentInline: params.forceClaudeTaskAssignmentInline,
221
491
  }),
222
492
  ...(() => {
223
493
  const lines = buildCollaborationOutcomeSummaryLines(params.promptContext?.collaborationOutcome);
@@ -240,5 +510,173 @@ export function buildPrompt(params) {
240
510
  '',
241
511
  `New message from ${params.incomingAuthorId}:`,
242
512
  params.incomingContent,
513
+ ];
514
+ }
515
+ function resolveProjectionStrategy(strategy) {
516
+ return normalizeProjectionStrategy(strategy) ?? DEFAULT_PROJECTION_STRATEGY;
517
+ }
518
+ function isClaudeZeroTurnFallbackRequired(params) {
519
+ return (params.incomingMessageType?.trim() === 'task_assignment'
520
+ || params.promptContext?.collaborationTurnMode === 'protocol-managed'
521
+ || params.promptContext?.collaborationTurnMode === 'silent-kickoff');
522
+ }
523
+ function buildClaudeLegacyTurnPrompt(params) {
524
+ const controlLines = params.promptContext?.collaborationTurnMode
525
+ && params.promptContext.collaborationTurnMode !== 'ordinary'
526
+ ? buildClaudeTurnControlPromptLines(params.promptContext)
527
+ : buildTurnControlPromptLines(params.promptContext);
528
+ const environmentLines = buildClaudeSessionEnvironmentLines(params.promptContext);
529
+ const sessionDelegationLines = params.promptContext?.skillRuntime ? [] : MAIN_SESSION_DELEGATION_LINES;
530
+ return [
531
+ ...buildPromptHeaderLines({
532
+ agentName: params.agentName ?? 'Borgee agent',
533
+ provider: 'claude',
534
+ }),
535
+ ...sessionDelegationLines,
536
+ ...(environmentLines.length > 0 ? ['', ...environmentLines] : []),
537
+ ...controlLines,
538
+ buildProviderIdentityReminderLine('claude'),
539
+ ...buildPromptBodyLines({
540
+ provider: 'claude',
541
+ channelId: params.channelId,
542
+ incomingAuthorId: params.incomingAuthorId,
543
+ incomingContent: params.incomingContent,
544
+ incomingEventKind: params.incomingEventKind,
545
+ incomingMessageType: params.incomingMessageType,
546
+ promptContext: params.promptContext,
547
+ forceClaudeTaskAssignmentInline: true,
548
+ }),
549
+ ].join('\n');
550
+ }
551
+ function buildClaudeZeroTurnPrompt(params) {
552
+ if (isClaudeZeroTurnFallbackRequired(params)) {
553
+ return [
554
+ ...buildClaudeTurnControlPromptLines(params.promptContext),
555
+ ...buildPromptBodyLines({
556
+ provider: 'claude',
557
+ channelId: params.channelId,
558
+ incomingAuthorId: params.incomingAuthorId,
559
+ incomingContent: params.incomingContent,
560
+ incomingEventKind: params.incomingEventKind,
561
+ incomingMessageType: params.incomingMessageType,
562
+ promptContext: params.promptContext,
563
+ }),
564
+ ].join('\n');
565
+ }
566
+ const gatewayTaskLines = buildClaudeOrdinaryGatewayTaskLines(params.promptContext);
567
+ return [
568
+ 'Do not claim to have performed actions you did not actually perform.',
569
+ ...(gatewayTaskLines.length > 0 ? [...gatewayTaskLines, ''] : []),
570
+ `New message from ${params.incomingAuthorId}:`,
571
+ params.incomingContent,
572
+ ].join('\n');
573
+ }
574
+ function buildClaudeMinimalTurnPrompt(params) {
575
+ if (isClaudeZeroTurnFallbackRequired(params)) {
576
+ return buildClaudeZeroTurnPrompt(params);
577
+ }
578
+ const gatewayTaskLines = buildClaudeOrdinaryGatewayTaskLines(params.promptContext);
579
+ return [
580
+ `You are ${params.agentName ?? 'Borgee agent'}, a helpful AI teammate inside Borgee.`,
581
+ 'You are replying inside a shared collaboration channel.',
582
+ 'Do not claim to have performed actions you did not actually perform.',
583
+ 'Keep the visible reply text concise.',
584
+ ...(gatewayTaskLines.length > 0 ? ['', ...gatewayTaskLines] : []),
585
+ '',
586
+ `New message from ${params.incomingAuthorId}:`,
587
+ params.incomingContent,
588
+ ].join('\n');
589
+ }
590
+ function buildClaudeSessionEnvironmentLines(context) {
591
+ if (!context) {
592
+ return [];
593
+ }
594
+ const lines = [];
595
+ if (context.skillRuntime) {
596
+ lines.push(...(context.gatewayCredentialPath
597
+ ? buildSkillManualLines(context.skillRuntime)
598
+ : [buildSkillManualReadLine(context.skillRuntime)]));
599
+ }
600
+ if (context.localhostGateway && context.gatewayCredentialPath) {
601
+ lines.push(`Gateway credential file for this session: ${context.gatewayCredentialPath}`);
602
+ }
603
+ lines.push(...buildResolvedWorkspaceGuidanceLines(context));
604
+ if (context.taskAssignmentContext?.currentTaskId) {
605
+ lines.push(`Current assigned task id: ${context.taskAssignmentContext.currentTaskId}.`);
606
+ }
607
+ else if (context.taskAssignmentContext?.active === true) {
608
+ lines.push('No current task id is persisted in the injected thread context for this task thread.');
609
+ }
610
+ if (context.taskAssignmentContext?.active === true) {
611
+ lines.push('This session is an active task assignment thread. Keep the main work in this thread.', 'Return your normal final response in this thread, and do not move the main work back to the parent channel.');
612
+ }
613
+ return lines;
614
+ }
615
+ /**
616
+ * Builds the per-turn prompt. Conversation memory is intentionally NOT
617
+ * assembled here: each provider's CLI client resumes a native per-channel
618
+ * session (Claude `--resume`, Copilot `--session-id`), so the CLI itself
619
+ * already has the prior turns. This only needs to carry the agent's
620
+ * identity/instructions plus the new incoming message.
621
+ */
622
+ export function buildPrompt(params) {
623
+ return [
624
+ ...buildPromptHeaderLines(params),
625
+ ...buildSessionDelegationPromptLines(params.provider, params.promptContext),
626
+ ...buildTurnControlPromptLines(params.promptContext),
627
+ buildProviderIdentityReminderLine(params.provider),
628
+ ...buildPromptBodyLines(params),
629
+ ].join('\n');
630
+ }
631
+ export function buildClaudeSessionPromptAppend(params) {
632
+ const promptStrategy = resolveProjectionStrategy(params.promptStrategy);
633
+ if (promptStrategy === 'turn-full') {
634
+ return undefined;
635
+ }
636
+ if (promptStrategy === 'file-brief' && params.promptContext?.claudeProjectedBriefPath) {
637
+ const bootstrapLines = buildClaudeProjectedBriefSessionLines(params.promptContext);
638
+ return [
639
+ ...buildPromptHeaderLines({ agentName: params.agentName, provider: 'claude' }),
640
+ ...(bootstrapLines.length > 0 ? ['', ...bootstrapLines] : []),
641
+ '',
642
+ buildProviderIdentityReminderLine('claude'),
643
+ ].join('\n');
644
+ }
645
+ const environmentLines = buildClaudeSessionEnvironmentLines(params.promptContext);
646
+ const sessionDelegationLines = params.promptContext?.skillRuntime ? [] : MAIN_SESSION_DELEGATION_LINES;
647
+ return [
648
+ ...buildPromptHeaderLines({ agentName: params.agentName, provider: 'claude' }),
649
+ ...sessionDelegationLines,
650
+ ...(environmentLines.length > 0 ? ['', ...environmentLines] : []),
651
+ '',
652
+ ...buildClaudeOrdinarySessionControlLines(),
653
+ buildProviderIdentityReminderLine('claude'),
654
+ ].join('\n');
655
+ }
656
+ export function buildClaudeTurnPrompt(params) {
657
+ const promptStrategy = resolveProjectionStrategy(params.promptStrategy);
658
+ if (promptStrategy === 'turn-full') {
659
+ return buildClaudeLegacyTurnPrompt(params);
660
+ }
661
+ if (promptStrategy === 'message-only') {
662
+ return buildClaudeZeroTurnPrompt(params);
663
+ }
664
+ if (promptStrategy === 'turn-thin') {
665
+ return buildClaudeMinimalTurnPrompt(params);
666
+ }
667
+ if (promptStrategy === 'file-brief') {
668
+ return buildClaudeProjectedBriefTurnPrompt(params);
669
+ }
670
+ return [
671
+ ...buildClaudeTurnControlPromptLines(params.promptContext),
672
+ ...buildPromptBodyLines({
673
+ provider: 'claude',
674
+ channelId: params.channelId,
675
+ incomingAuthorId: params.incomingAuthorId,
676
+ incomingContent: params.incomingContent,
677
+ incomingEventKind: params.incomingEventKind,
678
+ incomingMessageType: params.incomingMessageType,
679
+ promptContext: params.promptContext,
680
+ }),
243
681
  ].join('\n');
244
682
  }
@@ -0,0 +1,3 @@
1
+ import type { PreparedPromptContext, ProviderKind } from '../types.js';
2
+ export declare function buildResolvedWorkspaceGuidanceLines(context: PreparedPromptContext | undefined, provider?: ProviderKind): string[];
3
+ export declare function isDiscussionOnlyResolvedWorkspace(context: PreparedPromptContext | undefined): boolean;