@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.
- package/README.md +23 -27
- package/dist/agents-host.d.ts +26 -5
- package/dist/agents-host.js +163 -192
- package/dist/chat/chat-control-plane.d.ts +3 -0
- package/dist/chat/sdk-chat-control-plane.d.ts +4 -3
- package/dist/chat/sdk-chat-control-plane.js +3 -0
- package/dist/cli.js +1 -5
- package/dist/compatibility-gates.d.ts +4 -0
- package/dist/compatibility-gates.js +18 -1
- package/dist/context/claude-file-brief.d.ts +2 -0
- package/dist/context/claude-file-brief.js +83 -0
- package/dist/context/compaction.d.ts +20 -0
- package/dist/context/compaction.js +59 -0
- package/dist/context/injection.d.ts +39 -6
- package/dist/context/injection.js +317 -26
- package/dist/context/main-session-delegation.d.ts +1 -1
- package/dist/context/projection-strategy.d.ts +24 -0
- package/dist/context/projection-strategy.js +90 -0
- package/dist/context/prompt.d.ts +16 -1
- package/dist/context/prompt.js +456 -22
- package/dist/context/resolved-workspace.d.ts +2 -0
- package/dist/context/resolved-workspace.js +64 -0
- package/dist/context/skill-manual.d.ts +1 -0
- package/dist/context/skill-manual.js +4 -1
- package/dist/context/turn-preparation.d.ts +8 -2
- package/dist/context/turn-preparation.js +56 -14
- package/dist/gateway/localhost-gateway.js +2 -0
- package/dist/managed-daemon.js +122 -9
- package/dist/plugin-sdk.js +276 -359
- package/dist/plugin-sdk.js.map +4 -4
- package/dist/progress-to-activity.d.ts +16 -0
- package/dist/progress-to-activity.js +24 -0
- package/dist/projection-strategy-values.d.ts +4 -0
- package/dist/projection-strategy-values.js +28 -0
- package/dist/providers/acp-progress-collector.d.ts +44 -0
- package/dist/providers/acp-progress-collector.js +130 -0
- package/dist/providers/awaiting-user.d.ts +2 -3
- package/dist/providers/awaiting-user.js +5 -7
- package/dist/providers/claude/activity-metadata.d.ts +14 -0
- package/dist/providers/claude/activity-metadata.js +81 -0
- package/dist/providers/claude/cli-client.d.ts +1 -2
- package/dist/providers/claude/cli-client.js +190 -117
- package/dist/providers/codex/cli-client.js +3 -83
- package/dist/providers/codex/project-doc.js +12 -11
- package/dist/providers/copilot/cli-client.js +3 -83
- package/dist/providers/create-provider.d.ts +2 -0
- package/dist/providers/create-provider.js +20 -4
- package/dist/state-paths.d.ts +1 -1
- package/dist/state-paths.js +3 -3
- package/dist/task-thread-resolution.d.ts +3 -2
- package/dist/types.d.ts +130 -6
- package/package.json +2 -2
- package/skills/borgee-agent/SKILL.md +9 -1
- package/skills/borgee-agent/references/task-properties.md +5 -2
- package/dist/durable-cursor-store.d.ts +0 -5
- package/dist/durable-cursor-store.js +0 -7
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { Readable, Writable } from 'node:stream';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
2
3
|
import { createRequire } from 'node:module';
|
|
3
4
|
import { dirname, resolve } from 'node:path';
|
|
4
5
|
import spawn from 'cross-spawn';
|
|
6
|
+
import { DEFAULT_PROJECTION_STRATEGY, normalizeProjectionStrategy, } from '../../projection-strategy-values.js';
|
|
5
7
|
import { assertClaudeCommandCompatibility, DEFAULT_CLAUDE_COMMAND, isLegacyClaudeCompatibilityAlias, } from '../../config.js';
|
|
6
8
|
import { PROTOCOL_VERSION, client, methods, ndJsonStream, } from '@agentclientprotocol/sdk';
|
|
7
9
|
import { HostLogger, summarizeChildStderr, summarizeError } from '../../debug.js';
|
|
10
|
+
import { AcpProgressCollector } from '../acp-progress-collector.js';
|
|
11
|
+
import { readClaudeActivityMetadata } from './activity-metadata.js';
|
|
8
12
|
import { resolveCopilotPermissionResponse } from '../../policy/copilot-permission.js';
|
|
9
|
-
import {
|
|
13
|
+
import { buildClaudeSessionPromptAppend } from '../../context/prompt.js';
|
|
10
14
|
import { IDLE_BACKEND_SHUTDOWN_DISABLED_MS, IdleBackendShutdownScheduler, } from '../idle-backend-shutdown.js';
|
|
11
15
|
const SESSION_TAINTED_ERRORS = new WeakSet();
|
|
12
16
|
const IDLE_BACKEND_STOPPED_MESSAGE = 'Claude ACP backend stopped after idle timeout';
|
|
@@ -24,6 +28,7 @@ const DEFAULT_SESSION_CAPABILITIES = {
|
|
|
24
28
|
loadSession: false,
|
|
25
29
|
resumeSession: false,
|
|
26
30
|
};
|
|
31
|
+
const CLAUDE_SDK_COMPACTION_EXPERIMENT_CONTEXT_TOKEN_THRESHOLD_ENV = 'CLAUDE_SDK_COMPACTION_EXPERIMENT_CONTEXT_TOKEN_THRESHOLD';
|
|
27
32
|
const require = createRequire(import.meta.url);
|
|
28
33
|
const DEFAULT_RUNTIME = {
|
|
29
34
|
spawn,
|
|
@@ -111,82 +116,95 @@ class ClaudeCliProcessError extends Error {
|
|
|
111
116
|
this.stderrLineCount = stderrLineCount;
|
|
112
117
|
}
|
|
113
118
|
}
|
|
114
|
-
function
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
case 'completed':
|
|
118
|
-
return normalizedTitle ? `Completed ${normalizedTitle}` : 'Completed tool call';
|
|
119
|
-
case 'failed':
|
|
120
|
-
return normalizedTitle ? `Failed ${normalizedTitle}` : 'Tool call failed';
|
|
121
|
-
case 'pending':
|
|
122
|
-
case 'in_progress':
|
|
123
|
-
case undefined:
|
|
124
|
-
case null:
|
|
125
|
-
return normalizedTitle ? `Running ${normalizedTitle}…` : 'Running tool…';
|
|
126
|
-
default:
|
|
127
|
-
return normalizedTitle ? `${status} ${normalizedTitle}` : status;
|
|
119
|
+
function parseCompactionBanner(text) {
|
|
120
|
+
if (/(?:^|\n\n)Compacting\.\.\.\s*$/u.test(text)) {
|
|
121
|
+
return { stage: 'started' };
|
|
128
122
|
}
|
|
123
|
+
if (/(?:^|\n\n)Compacting completed\.\s*$/u.test(text)) {
|
|
124
|
+
return { stage: 'completed' };
|
|
125
|
+
}
|
|
126
|
+
const match = /(?:^|\n\n)Compacting failed(?::\s*(.+?)|\.)\s*$/u.exec(text);
|
|
127
|
+
if (!match) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
stage: 'failed',
|
|
132
|
+
...(match?.[1] ? { failureReason: match[1].trim() } : {}),
|
|
133
|
+
};
|
|
129
134
|
}
|
|
130
|
-
|
|
131
|
-
const current = entries.find((entry) => entry.status === 'in_progress') ??
|
|
132
|
-
entries.find((entry) => entry.status === 'pending') ??
|
|
133
|
-
entries[0];
|
|
134
|
-
return hasVisibleText(current?.content) ? `Plan: ${current.content.trim()}` : null;
|
|
135
|
-
}
|
|
136
|
-
class ClaudeProgressCollector {
|
|
135
|
+
class ClaudeCompactionObserver {
|
|
137
136
|
onProgress;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
137
|
+
answerText = '';
|
|
138
|
+
compactionState = 'idle';
|
|
139
|
+
lastCompactionBannerKey = null;
|
|
141
140
|
constructor(onProgress) {
|
|
142
141
|
this.onProgress = onProgress;
|
|
143
142
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (update.update.content.type !== 'text') {
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
this.publicText += update.update.content.text;
|
|
151
|
-
this.publish(this.publicText);
|
|
152
|
-
return;
|
|
153
|
-
case 'tool_call':
|
|
154
|
-
this.toolTitles.set(update.update.toolCallId, update.update.title);
|
|
155
|
-
this.publishFallback(formatToolProgress(update.update.title, update.update.status));
|
|
156
|
-
return;
|
|
157
|
-
case 'tool_call_update': {
|
|
158
|
-
const nextTitle = update.update.title ?? this.toolTitles.get(update.update.toolCallId);
|
|
159
|
-
if (hasVisibleText(nextTitle)) {
|
|
160
|
-
this.toolTitles.set(update.update.toolCallId, nextTitle);
|
|
161
|
-
}
|
|
162
|
-
this.publishFallback(formatToolProgress(nextTitle, update.update.status));
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
case 'plan':
|
|
166
|
-
this.publishFallback(formatPlanProgress(update.update.entries.map((entry) => ({
|
|
167
|
-
content: entry.content,
|
|
168
|
-
status: entry.status,
|
|
169
|
-
}))));
|
|
170
|
-
return;
|
|
171
|
-
default:
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
getFinalText() {
|
|
176
|
-
return this.publicText.trim();
|
|
143
|
+
consumeAnswerChunk(chunk) {
|
|
144
|
+
this.answerText += chunk;
|
|
145
|
+
this.publishCompactionFromText(this.answerText);
|
|
177
146
|
}
|
|
178
|
-
|
|
179
|
-
if (
|
|
147
|
+
consumeUsageUpdate(usedTokens, contextWindowTokens) {
|
|
148
|
+
if (!this.onProgress || this.compactionState !== 'completed-awaiting-usage') {
|
|
180
149
|
return;
|
|
181
150
|
}
|
|
182
|
-
this.
|
|
151
|
+
this.compactionState = 'idle';
|
|
152
|
+
this.onProgress({
|
|
153
|
+
type: 'compaction',
|
|
154
|
+
compaction: {
|
|
155
|
+
provider: 'claude',
|
|
156
|
+
stage: 'completed',
|
|
157
|
+
usedTokens,
|
|
158
|
+
contextWindowTokens,
|
|
159
|
+
},
|
|
160
|
+
});
|
|
183
161
|
}
|
|
184
|
-
|
|
185
|
-
if (!this.onProgress
|
|
162
|
+
publishCompactionFromText(text) {
|
|
163
|
+
if (!this.onProgress) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const banner = parseCompactionBanner(text);
|
|
167
|
+
if (!banner) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const bannerKey = `${banner.stage}:${banner.stage === 'failed' ? banner.failureReason ?? '' : ''}:${text.trimEnd().length}`;
|
|
171
|
+
if (bannerKey === this.lastCompactionBannerKey) {
|
|
186
172
|
return;
|
|
187
173
|
}
|
|
188
|
-
this.
|
|
189
|
-
|
|
174
|
+
this.lastCompactionBannerKey = bannerKey;
|
|
175
|
+
switch (banner.stage) {
|
|
176
|
+
case 'started':
|
|
177
|
+
this.compactionState = 'active';
|
|
178
|
+
this.onProgress({
|
|
179
|
+
type: 'compaction',
|
|
180
|
+
compaction: {
|
|
181
|
+
provider: 'claude',
|
|
182
|
+
stage: 'started',
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
return;
|
|
186
|
+
case 'completed':
|
|
187
|
+
this.compactionState = 'completed-awaiting-usage';
|
|
188
|
+
this.onProgress({
|
|
189
|
+
type: 'compaction',
|
|
190
|
+
compaction: {
|
|
191
|
+
provider: 'claude',
|
|
192
|
+
stage: 'completed',
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
return;
|
|
196
|
+
case 'failed':
|
|
197
|
+
this.compactionState = 'idle';
|
|
198
|
+
this.onProgress({
|
|
199
|
+
type: 'compaction',
|
|
200
|
+
compaction: {
|
|
201
|
+
provider: 'claude',
|
|
202
|
+
stage: 'failed',
|
|
203
|
+
...(banner.failureReason ? { failureReason: banner.failureReason } : {}),
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
190
208
|
}
|
|
191
209
|
}
|
|
192
210
|
function createDeferredTurn(channelId, preparedTurn, sessionPersistence, options) {
|
|
@@ -290,39 +308,65 @@ function resolveClaudeLaunch(command, args, baseCwd) {
|
|
|
290
308
|
args: args.map((arg) => (isRelativePathLike(arg) ? resolve(baseCwd, arg) : arg)),
|
|
291
309
|
};
|
|
292
310
|
}
|
|
293
|
-
|
|
294
|
-
|
|
311
|
+
function resolveClaudeSdkCompactionControl() {
|
|
312
|
+
const rawThreshold = process.env[CLAUDE_SDK_COMPACTION_EXPERIMENT_CONTEXT_TOKEN_THRESHOLD_ENV];
|
|
313
|
+
if (!hasVisibleText(rawThreshold)) {
|
|
295
314
|
return undefined;
|
|
296
315
|
}
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
if (promptContext.skillRuntime && promptContext.gatewayCredentialPath) {
|
|
301
|
-
lines.push(...buildSkillManualLines(promptContext.skillRuntime));
|
|
302
|
-
}
|
|
303
|
-
if (promptContext.localhostGateway && promptContext.gatewayCredentialPath) {
|
|
304
|
-
lines.push(`Gateway credential file for this session: ${promptContext.gatewayCredentialPath}`);
|
|
305
|
-
}
|
|
306
|
-
if (promptContext.taskWorkspace) {
|
|
307
|
-
lines.push(`Task-scoped writable workspace root: ${promptContext.taskWorkspace.rootPath}.`);
|
|
308
|
-
lines.push('This writable workspace is local to agents-host for the current task thread and does not imply that the target repository is already checked out there.');
|
|
309
|
-
}
|
|
310
|
-
if (promptContext.taskAssignmentContext?.currentTaskId) {
|
|
311
|
-
lines.push(`Current assigned task id: ${promptContext.taskAssignmentContext.currentTaskId}.`);
|
|
316
|
+
const parsedThreshold = Number(rawThreshold.trim());
|
|
317
|
+
if (!Number.isSafeInteger(parsedThreshold) || parsedThreshold <= 0) {
|
|
318
|
+
throw new Error(`Invalid ${CLAUDE_SDK_COMPACTION_EXPERIMENT_CONTEXT_TOKEN_THRESHOLD_ENV}: expected a positive integer context token threshold for the experiment-scoped Claude SDK compaction opt-in`);
|
|
312
319
|
}
|
|
313
|
-
return
|
|
320
|
+
return {
|
|
321
|
+
enabled: true,
|
|
322
|
+
context_token_threshold: parsedThreshold,
|
|
323
|
+
};
|
|
314
324
|
}
|
|
315
|
-
function buildClaudeSessionMeta(
|
|
316
|
-
const
|
|
317
|
-
if (!systemPromptAppend) {
|
|
325
|
+
function buildClaudeSessionMeta(systemPromptAppend) {
|
|
326
|
+
const sdkCompactionControl = resolveClaudeSdkCompactionControl();
|
|
327
|
+
if (!systemPromptAppend && !sdkCompactionControl) {
|
|
318
328
|
return undefined;
|
|
319
329
|
}
|
|
320
330
|
return {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
331
|
+
...(systemPromptAppend
|
|
332
|
+
? {
|
|
333
|
+
systemPrompt: {
|
|
334
|
+
append: systemPromptAppend,
|
|
335
|
+
},
|
|
336
|
+
}
|
|
337
|
+
: {}),
|
|
338
|
+
...(sdkCompactionControl
|
|
339
|
+
? {
|
|
340
|
+
claudeCode: {
|
|
341
|
+
options: {
|
|
342
|
+
compaction_control: sdkCompactionControl,
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
}
|
|
346
|
+
: {}),
|
|
324
347
|
};
|
|
325
348
|
}
|
|
349
|
+
function buildFallbackClaudeSessionPrompt(agentName, promptContext, promptStrategy) {
|
|
350
|
+
if (!promptContext) {
|
|
351
|
+
return undefined;
|
|
352
|
+
}
|
|
353
|
+
return buildClaudeSessionPromptAppend({
|
|
354
|
+
agentName: agentName ?? 'Borgee agent',
|
|
355
|
+
promptContext,
|
|
356
|
+
promptStrategy,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
function resolveProjectionStrategy(promptStrategy) {
|
|
360
|
+
return normalizeProjectionStrategy(promptStrategy) ?? DEFAULT_PROJECTION_STRATEGY;
|
|
361
|
+
}
|
|
362
|
+
function resolveTurnSessionPromptAppend(turn) {
|
|
363
|
+
const promptStrategy = resolveProjectionStrategy(turn.projectionStrategy);
|
|
364
|
+
if (promptStrategy === 'turn-full') {
|
|
365
|
+
return undefined;
|
|
366
|
+
}
|
|
367
|
+
return turn.claudeSessionPromptAppend
|
|
368
|
+
?? buildFallbackClaudeSessionPrompt(turn.agentName, turn.promptContext, promptStrategy);
|
|
369
|
+
}
|
|
326
370
|
function resolveSessionAdditionalDirectories(promptContext) {
|
|
327
371
|
const directories = new Set();
|
|
328
372
|
if (promptContext?.channelContextPayloadPath) {
|
|
@@ -336,27 +380,48 @@ function resolveSessionAdditionalDirectories(promptContext) {
|
|
|
336
380
|
}
|
|
337
381
|
return directories.size > 0 ? [...directories].sort((left, right) => left.localeCompare(right)) : undefined;
|
|
338
382
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
383
|
+
function hashSessionAppend(systemPromptAppend) {
|
|
384
|
+
if (!systemPromptAppend) {
|
|
385
|
+
return undefined;
|
|
386
|
+
}
|
|
387
|
+
return createHash('sha256').update(systemPromptAppend).digest('hex');
|
|
388
|
+
}
|
|
389
|
+
function resolveSessionVisibilityKey(promptContext, systemPromptAppend, promptStrategy) {
|
|
390
|
+
const resolvedPromptStrategy = resolveProjectionStrategy(promptStrategy);
|
|
391
|
+
const explicitPromptStrategy = resolvedPromptStrategy === DEFAULT_PROJECTION_STRATEGY && promptStrategy == null
|
|
392
|
+
? undefined
|
|
393
|
+
: resolvedPromptStrategy === DEFAULT_PROJECTION_STRATEGY
|
|
394
|
+
? undefined
|
|
395
|
+
: resolvedPromptStrategy;
|
|
347
396
|
const additionalDirectories = resolveSessionAdditionalDirectories(promptContext);
|
|
348
|
-
|
|
397
|
+
const sessionAppendHash = hashSessionAppend(systemPromptAppend);
|
|
398
|
+
const projectedBriefHash = promptContext?.claudeProjectedBriefHash;
|
|
399
|
+
if (!additionalDirectories
|
|
400
|
+
&& !sessionAppendHash
|
|
401
|
+
&& !projectedBriefHash
|
|
402
|
+
&& !promptContext?.gatewayCredentialPath
|
|
403
|
+
&& !explicitPromptStrategy) {
|
|
349
404
|
return undefined;
|
|
350
405
|
}
|
|
406
|
+
if (sessionAppendHash) {
|
|
407
|
+
return JSON.stringify({
|
|
408
|
+
...(additionalDirectories ? { additionalDirectories } : {}),
|
|
409
|
+
sessionAppendHash,
|
|
410
|
+
...(projectedBriefHash ? { projectedBriefHash } : {}),
|
|
411
|
+
...(explicitPromptStrategy ? { promptStrategy: explicitPromptStrategy } : {}),
|
|
412
|
+
});
|
|
413
|
+
}
|
|
351
414
|
return JSON.stringify({
|
|
352
|
-
additionalDirectories,
|
|
415
|
+
...(additionalDirectories ? { additionalDirectories } : {}),
|
|
353
416
|
...(promptContext?.gatewayCredentialPath
|
|
354
417
|
? { gatewayCredentialPath: promptContext.gatewayCredentialPath }
|
|
355
418
|
: {}),
|
|
419
|
+
...(projectedBriefHash ? { projectedBriefHash } : {}),
|
|
420
|
+
...(explicitPromptStrategy ? { promptStrategy: explicitPromptStrategy } : {}),
|
|
356
421
|
});
|
|
357
422
|
}
|
|
358
|
-
function buildSessionRequest(cwd,
|
|
359
|
-
const meta = buildClaudeSessionMeta(
|
|
423
|
+
function buildSessionRequest(cwd, systemPromptAppend, additionalDirectories) {
|
|
424
|
+
const meta = buildClaudeSessionMeta(systemPromptAppend);
|
|
360
425
|
return {
|
|
361
426
|
cwd,
|
|
362
427
|
mcpServers: [],
|
|
@@ -656,10 +721,11 @@ export class ClaudeCliClient {
|
|
|
656
721
|
state.activeTurn = turn;
|
|
657
722
|
try {
|
|
658
723
|
state.cwd = this.resolveSessionCwd(turn.preparedTurn.promptContext);
|
|
659
|
-
|
|
724
|
+
const sessionPromptAppend = resolveTurnSessionPromptAppend(turn.preparedTurn);
|
|
725
|
+
state.visibilityKey = resolveSessionVisibilityKey(turn.preparedTurn.promptContext, sessionPromptAppend, turn.preparedTurn.projectionStrategy);
|
|
660
726
|
await this.ensureStarted();
|
|
661
727
|
await this.recycleSessionIfScopeChanged(channelId, state);
|
|
662
|
-
const session = await this.getOrCreateSession(channelId, state, turn.preparedTurn
|
|
728
|
+
const session = await this.getOrCreateSession(channelId, state, turn.preparedTurn, sessionPromptAppend);
|
|
663
729
|
let reply;
|
|
664
730
|
try {
|
|
665
731
|
const promptInput = await this.buildPromptInput(turn.preparedTurn);
|
|
@@ -703,7 +769,7 @@ export class ClaudeCliClient {
|
|
|
703
769
|
}
|
|
704
770
|
})();
|
|
705
771
|
}
|
|
706
|
-
async getOrCreateSession(channelId, state,
|
|
772
|
+
async getOrCreateSession(channelId, state, turn, systemPromptAppend) {
|
|
707
773
|
if (state.session) {
|
|
708
774
|
return state.session;
|
|
709
775
|
}
|
|
@@ -717,10 +783,10 @@ export class ClaudeCliClient {
|
|
|
717
783
|
const persistedSessionId = state.sessionPersistence === 'persistent'
|
|
718
784
|
? await this.readPersistedSessionId(channelId, cwd, state.visibilityKey)
|
|
719
785
|
: undefined;
|
|
720
|
-
const additionalDirectories = resolveSessionAdditionalDirectories(promptContext);
|
|
786
|
+
const additionalDirectories = resolveSessionAdditionalDirectories(turn.promptContext);
|
|
721
787
|
const sessionPromise = persistedSessionId
|
|
722
|
-
? this.restoreOrCreateSession(channelId, persistedSessionId, cwd,
|
|
723
|
-
: this.startFreshSession(channelId, cwd,
|
|
788
|
+
? this.restoreOrCreateSession(channelId, persistedSessionId, cwd, systemPromptAppend, additionalDirectories)
|
|
789
|
+
: this.startFreshSession(channelId, cwd, systemPromptAppend, additionalDirectories);
|
|
724
790
|
this.pendingSessionStarts.add(sessionPromise);
|
|
725
791
|
state.sessionPromise = sessionPromise;
|
|
726
792
|
let sessionAdopted = false;
|
|
@@ -759,11 +825,11 @@ export class ClaudeCliClient {
|
|
|
759
825
|
}
|
|
760
826
|
}
|
|
761
827
|
}
|
|
762
|
-
async startFreshSession(channelId, cwd,
|
|
828
|
+
async startFreshSession(channelId, cwd, systemPromptAppend, additionalDirectories) {
|
|
763
829
|
if (!this.connection) {
|
|
764
830
|
throw new Error('Claude ACP connection is not available');
|
|
765
831
|
}
|
|
766
|
-
const session = await this.connection.agent.buildSession(buildSessionRequest(cwd,
|
|
832
|
+
const session = await this.connection.agent.buildSession(buildSessionRequest(cwd, systemPromptAppend, additionalDirectories)).start();
|
|
767
833
|
this.logger?.debug('started fresh Claude ACP session', { channelId, cwd });
|
|
768
834
|
const state = this.channels.get(channelId);
|
|
769
835
|
if (state?.sessionPersistence === 'persistent') {
|
|
@@ -771,13 +837,13 @@ export class ClaudeCliClient {
|
|
|
771
837
|
}
|
|
772
838
|
return session;
|
|
773
839
|
}
|
|
774
|
-
async restoreOrCreateSession(channelId, sessionId, cwd,
|
|
840
|
+
async restoreOrCreateSession(channelId, sessionId, cwd, systemPromptAppend, additionalDirectories) {
|
|
775
841
|
if (!this.sessionCapabilities.resumeSession) {
|
|
776
842
|
this.logger?.debug('Claude ACP restore unsupported; starting fresh session', { channelId });
|
|
777
|
-
return this.startFreshSession(channelId, cwd,
|
|
843
|
+
return this.startFreshSession(channelId, cwd, systemPromptAppend, additionalDirectories);
|
|
778
844
|
}
|
|
779
845
|
try {
|
|
780
|
-
const session = await this.restoreSession(sessionId, cwd,
|
|
846
|
+
const session = await this.restoreSession(sessionId, cwd, systemPromptAppend, additionalDirectories);
|
|
781
847
|
this.logger?.debug('restored Claude ACP session', {
|
|
782
848
|
channelId,
|
|
783
849
|
restoreMethod: this.sessionCapabilities.resumeSession ? 'session/resume' : 'session/load',
|
|
@@ -790,10 +856,10 @@ export class ClaudeCliClient {
|
|
|
790
856
|
}
|
|
791
857
|
this.logger?.debug('discarded stale Claude ACP session and started fresh', { channelId });
|
|
792
858
|
await this.clearPersistedSessionBestEffort(channelId);
|
|
793
|
-
return this.startFreshSession(channelId, cwd,
|
|
859
|
+
return this.startFreshSession(channelId, cwd, systemPromptAppend, additionalDirectories);
|
|
794
860
|
}
|
|
795
861
|
}
|
|
796
|
-
async restoreSession(sessionId, cwd,
|
|
862
|
+
async restoreSession(sessionId, cwd, systemPromptAppend, additionalDirectories) {
|
|
797
863
|
if (!this.connection) {
|
|
798
864
|
throw new Error('Claude ACP connection is not available');
|
|
799
865
|
}
|
|
@@ -805,7 +871,7 @@ export class ClaudeCliClient {
|
|
|
805
871
|
try {
|
|
806
872
|
const request = {
|
|
807
873
|
sessionId,
|
|
808
|
-
...buildSessionRequest(cwd,
|
|
874
|
+
...buildSessionRequest(cwd, systemPromptAppend, additionalDirectories),
|
|
809
875
|
};
|
|
810
876
|
if (this.sessionCapabilities.resumeSession) {
|
|
811
877
|
await this.connection.agent.request(this.runtime.methods.agent.session.resume, request);
|
|
@@ -830,7 +896,7 @@ export class ClaudeCliClient {
|
|
|
830
896
|
}
|
|
831
897
|
}
|
|
832
898
|
resolveSessionCwd(promptContext) {
|
|
833
|
-
return promptContext?.
|
|
899
|
+
return promptContext?.resolvedWorkspace?.rootPath ?? this.runtime.cwd;
|
|
834
900
|
}
|
|
835
901
|
async recycleSessionIfScopeChanged(channelId, state) {
|
|
836
902
|
const session = state.session;
|
|
@@ -862,7 +928,8 @@ export class ClaudeCliClient {
|
|
|
862
928
|
const promptFailure = new Promise((_, reject) => {
|
|
863
929
|
void promptPromise.catch((error) => reject(markSessionTainted(error)));
|
|
864
930
|
});
|
|
865
|
-
const collector = new
|
|
931
|
+
const collector = new AcpProgressCollector(options?.onProgress, readClaudeActivityMetadata);
|
|
932
|
+
const compactionObserver = new ClaudeCompactionObserver(options?.onProgress);
|
|
866
933
|
for (;;) {
|
|
867
934
|
let update;
|
|
868
935
|
try {
|
|
@@ -888,6 +955,12 @@ export class ClaudeCliClient {
|
|
|
888
955
|
}
|
|
889
956
|
return output;
|
|
890
957
|
}
|
|
958
|
+
if (update.update.sessionUpdate === 'agent_message_chunk' && update.update.content.type === 'text') {
|
|
959
|
+
compactionObserver.consumeAnswerChunk(update.update.content.text);
|
|
960
|
+
}
|
|
961
|
+
if (update.update.sessionUpdate === 'usage_update') {
|
|
962
|
+
compactionObserver.consumeUsageUpdate(update.update.used, update.update.size);
|
|
963
|
+
}
|
|
891
964
|
collector.consume(update);
|
|
892
965
|
}
|
|
893
966
|
}
|
|
@@ -5,6 +5,7 @@ import { createRequire } from 'node:module';
|
|
|
5
5
|
import spawn from 'cross-spawn';
|
|
6
6
|
import { PROTOCOL_VERSION, client, methods, ndJsonStream, } from '@agentclientprotocol/sdk';
|
|
7
7
|
import { HostLogger, summarizeChildStderr, summarizeError } from '../../debug.js';
|
|
8
|
+
import { AcpProgressCollector } from '../acp-progress-collector.js';
|
|
8
9
|
import { assertCodexProjectDocumentSize, buildCodexProjectDocument } from './project-doc.js';
|
|
9
10
|
import { isGatewayCredentialSidecarBasename } from '../../context/injection.js';
|
|
10
11
|
import { IDLE_BACKEND_SHUTDOWN_DISABLED_MS, IdleBackendShutdownScheduler, } from '../idle-backend-shutdown.js';
|
|
@@ -60,9 +61,6 @@ const DEFAULT_RUNTIME = {
|
|
|
60
61
|
},
|
|
61
62
|
fetch: async (input, init) => fetch(input, init),
|
|
62
63
|
};
|
|
63
|
-
function hasVisibleText(value) {
|
|
64
|
-
return typeof value === 'string' && value.trim().length > 0;
|
|
65
|
-
}
|
|
66
64
|
function cloneDirectories(directories) {
|
|
67
65
|
return directories ? [...directories] : undefined;
|
|
68
66
|
}
|
|
@@ -103,84 +101,6 @@ function serializePersistedSessionRecord(record) {
|
|
|
103
101
|
...(record.cwd ? { cwd: record.cwd } : {}),
|
|
104
102
|
});
|
|
105
103
|
}
|
|
106
|
-
function formatToolProgress(title, status) {
|
|
107
|
-
const normalizedTitle = hasVisibleText(title) ? title.trim() : null;
|
|
108
|
-
switch (status) {
|
|
109
|
-
case 'completed':
|
|
110
|
-
return normalizedTitle ? `Completed ${normalizedTitle}` : 'Completed tool call';
|
|
111
|
-
case 'failed':
|
|
112
|
-
return normalizedTitle ? `Failed ${normalizedTitle}` : 'Tool call failed';
|
|
113
|
-
case 'pending':
|
|
114
|
-
case 'in_progress':
|
|
115
|
-
case undefined:
|
|
116
|
-
case null:
|
|
117
|
-
return normalizedTitle ? `Running ${normalizedTitle}…` : 'Running tool…';
|
|
118
|
-
default:
|
|
119
|
-
return normalizedTitle ? `${status} ${normalizedTitle}` : status;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
function formatPlanProgress(entries) {
|
|
123
|
-
const current = entries.find((entry) => entry.status === 'in_progress') ??
|
|
124
|
-
entries.find((entry) => entry.status === 'pending') ??
|
|
125
|
-
entries[0];
|
|
126
|
-
return hasVisibleText(current?.content) ? `Plan: ${current.content.trim()}` : null;
|
|
127
|
-
}
|
|
128
|
-
class CodexProgressCollector {
|
|
129
|
-
onProgress;
|
|
130
|
-
publicText = '';
|
|
131
|
-
lastPublished = null;
|
|
132
|
-
toolTitles = new Map();
|
|
133
|
-
constructor(onProgress) {
|
|
134
|
-
this.onProgress = onProgress;
|
|
135
|
-
}
|
|
136
|
-
consume(update) {
|
|
137
|
-
switch (update.update.sessionUpdate) {
|
|
138
|
-
case 'agent_message_chunk':
|
|
139
|
-
if (update.update.content.type !== 'text') {
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
this.publicText += update.update.content.text;
|
|
143
|
-
this.publish(this.publicText);
|
|
144
|
-
return;
|
|
145
|
-
case 'tool_call':
|
|
146
|
-
this.toolTitles.set(update.update.toolCallId, update.update.title);
|
|
147
|
-
this.publishFallback(formatToolProgress(update.update.title, update.update.status));
|
|
148
|
-
return;
|
|
149
|
-
case 'tool_call_update': {
|
|
150
|
-
const nextTitle = update.update.title ?? this.toolTitles.get(update.update.toolCallId);
|
|
151
|
-
if (hasVisibleText(nextTitle)) {
|
|
152
|
-
this.toolTitles.set(update.update.toolCallId, nextTitle);
|
|
153
|
-
}
|
|
154
|
-
this.publishFallback(formatToolProgress(nextTitle, update.update.status));
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
case 'plan':
|
|
158
|
-
this.publishFallback(formatPlanProgress(update.update.entries.map((entry) => ({
|
|
159
|
-
content: entry.content,
|
|
160
|
-
status: entry.status,
|
|
161
|
-
}))));
|
|
162
|
-
return;
|
|
163
|
-
default:
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
getFinalText() {
|
|
168
|
-
return this.publicText.trim();
|
|
169
|
-
}
|
|
170
|
-
publishFallback(text) {
|
|
171
|
-
if (hasVisibleText(this.publicText)) {
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
this.publish(text);
|
|
175
|
-
}
|
|
176
|
-
publish(text) {
|
|
177
|
-
if (!this.onProgress || !hasVisibleText(text) || text === this.lastPublished) {
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
this.lastPublished = text;
|
|
181
|
-
this.onProgress({ text });
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
104
|
function createDeferredTurn(channelId, preparedTurn, sessionPersistence, options) {
|
|
185
105
|
let settled = false;
|
|
186
106
|
let resolvePromise;
|
|
@@ -734,7 +654,7 @@ export class CodexCliClient {
|
|
|
734
654
|
}
|
|
735
655
|
}
|
|
736
656
|
async resolveSessionCwd(promptContext) {
|
|
737
|
-
return promptContext?.
|
|
657
|
+
return promptContext?.resolvedWorkspace?.rootPath ?? this.runtime.cwd;
|
|
738
658
|
}
|
|
739
659
|
async recycleSessionIfInjectionScopeChanged(channelId, state) {
|
|
740
660
|
const session = state.session;
|
|
@@ -906,7 +826,7 @@ export class CodexCliClient {
|
|
|
906
826
|
const promptFailure = new Promise((_, reject) => {
|
|
907
827
|
void promptPromise.catch((error) => reject(markSessionTainted(error)));
|
|
908
828
|
});
|
|
909
|
-
const collector = new
|
|
829
|
+
const collector = new AcpProgressCollector(options?.onProgress);
|
|
910
830
|
for (;;) {
|
|
911
831
|
let update;
|
|
912
832
|
try {
|
|
@@ -2,17 +2,20 @@ import { buildAttentionSummaryLines } from '../../context/attention.js';
|
|
|
2
2
|
import { buildCollaborationCapabilityDeclarationSummaryLines, buildMissedCollaborationDiagnosticSummaryLines, } from '../../context/collaboration-capabilities-diagnostics.js';
|
|
3
3
|
import { buildCollaborationOutcomeSummaryLines } from '../../context/collaboration-outcome.js';
|
|
4
4
|
import { MAIN_SESSION_DELEGATION_LINES } from '../../context/main-session-delegation.js';
|
|
5
|
-
import {
|
|
5
|
+
import { buildResolvedWorkspaceGuidanceLines } from '../../context/resolved-workspace.js';
|
|
6
|
+
import { buildSkillManualLines, buildSkillManualReadLine } from '../../context/skill-manual.js';
|
|
6
7
|
import { buildTaskThreadCollaborationSummaryLines } from '../../context/task-thread-collaboration.js';
|
|
7
8
|
const PROJECT_DOC_MAX_BYTES = 32 * 1024;
|
|
8
9
|
// Every command the CLI carries needs the gateway credential file, so the capability is only
|
|
9
10
|
// worth naming in a workspace that also names one; otherwise the model is handed an invocation
|
|
10
11
|
// template it cannot fill.
|
|
11
12
|
function buildSkillRuntimeLines(context) {
|
|
12
|
-
if (!context?.skillRuntime
|
|
13
|
+
if (!context?.skillRuntime) {
|
|
13
14
|
return [];
|
|
14
15
|
}
|
|
15
|
-
return
|
|
16
|
+
return context.gatewayCredentialPath
|
|
17
|
+
? buildSkillManualLines(context.skillRuntime)
|
|
18
|
+
: [buildSkillManualReadLine(context.skillRuntime)];
|
|
16
19
|
}
|
|
17
20
|
// The credential file is the whole handoff: it carries the gateway's base URL along with the
|
|
18
21
|
// channel id and the token, and the CLI reads all three out of it. Naming the base URL separately
|
|
@@ -24,20 +27,18 @@ function buildGatewayCredentialLines(context) {
|
|
|
24
27
|
return [`Gateway credential file: ${context.gatewayCredentialPath}`];
|
|
25
28
|
}
|
|
26
29
|
export function buildCodexProjectDocument(context) {
|
|
30
|
+
const sessionDelegationLines = context?.skillRuntime ? [] : MAIN_SESSION_DELEGATION_LINES;
|
|
27
31
|
const lines = [
|
|
28
32
|
'# Borgee channel workspace',
|
|
29
33
|
'',
|
|
30
34
|
'This workspace belongs to one Borgee channel session hosted by agents-host.',
|
|
31
35
|
'Keep visible replies concise, do not claim actions you did not perform, and prefer the per-turn prompt when it provides newer turn-local details.',
|
|
32
36
|
'',
|
|
33
|
-
...
|
|
34
|
-
...(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
'That writable task workspace is local to agents-host for the current task thread and does not imply that the target repository has already been checked out there.',
|
|
39
|
-
]
|
|
40
|
-
: []),
|
|
37
|
+
...sessionDelegationLines,
|
|
38
|
+
...(() => {
|
|
39
|
+
const workspaceLines = buildResolvedWorkspaceGuidanceLines(context);
|
|
40
|
+
return workspaceLines.length > 0 ? ['', ...workspaceLines] : [];
|
|
41
|
+
})(),
|
|
41
42
|
...(() => {
|
|
42
43
|
const collaborationOutcomeLines = buildCollaborationOutcomeSummaryLines(context?.collaborationOutcome);
|
|
43
44
|
return collaborationOutcomeLines.length > 0 ? ['', ...collaborationOutcomeLines] : [];
|