praxis-agent 0.55.0 → 0.55.1
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 +1 -1
- package/dist/application/context-engine.js +4 -1
- package/dist/application/context-preparation.d.ts +60 -0
- package/dist/application/context-preparation.js +139 -0
- package/dist/application/session-service.d.ts +1 -1
- package/dist/application/session-service.js +93 -206
- package/dist/application/turn-lifecycle.d.ts +21 -9
- package/dist/application/turn-lifecycle.js +141 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -347,7 +347,7 @@ normal/low-capability full-frame p95 budgets of `<16.7/<33 ms`.
|
|
|
347
347
|
`npm run test:coverage` measures all production code under `src/**` with V8 and
|
|
348
348
|
enforces global floors of 79% statements, 70% branches, 85% functions, and 81% lines,
|
|
349
349
|
and rejects any production runtime module with zero covered statements (while allowing
|
|
350
|
-
type-only modules). `npm run test:fixtures` executes the
|
|
350
|
+
type-only modules). `npm run test:fixtures` executes the 71-behavior native contract; 63 behaviors
|
|
351
351
|
are qualified and 8 are explicitly excluded. `npm run verify:fixture-contracts`
|
|
352
352
|
performs the structural check and is part of `npm run check`.
|
|
353
353
|
`npm run test:core-completion` is retained as a compatibility alias for
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AgentRunCancelledError, } from '../core/runtime.js';
|
|
2
2
|
import { contextRecoveryMadeProgress, isPromptTooLongError, } from '../core/context-budget.js';
|
|
3
|
+
import { StaleContextGenerationError } from './context-preparation.js';
|
|
3
4
|
function assertSignal(signal) {
|
|
4
5
|
if (signal?.aborted)
|
|
5
6
|
throw new AgentRunCancelledError();
|
|
@@ -95,7 +96,9 @@ export class ContextEngine {
|
|
|
95
96
|
return { kind: 'retry', envelope: proposal.envelope };
|
|
96
97
|
}
|
|
97
98
|
catch (cause) {
|
|
98
|
-
if (signal?.aborted ||
|
|
99
|
+
if (signal?.aborted ||
|
|
100
|
+
cause instanceof AgentRunCancelledError ||
|
|
101
|
+
cause instanceof StaleContextGenerationError)
|
|
99
102
|
throw cause;
|
|
100
103
|
return { kind: 'exhausted', error };
|
|
101
104
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type ContextAssembler, type ContextAssemblyOptions } from '../core/context.js';
|
|
2
|
+
import type { ModelMessage, ModelToolDefinition } from '../core/runtime.js';
|
|
3
|
+
import type { ContextEnvelope } from './context-engine.js';
|
|
4
|
+
export interface ContextPreparationProjection {
|
|
5
|
+
readonly generation: number;
|
|
6
|
+
readonly envelope: ContextEnvelope;
|
|
7
|
+
readonly stableSystemMessageCount: number;
|
|
8
|
+
}
|
|
9
|
+
export interface ContextPreparationSources {
|
|
10
|
+
readonly history: () => readonly ModelMessage[];
|
|
11
|
+
readonly memory: () => readonly ModelMessage[];
|
|
12
|
+
readonly activeTools: () => readonly ModelToolDefinition[];
|
|
13
|
+
}
|
|
14
|
+
export interface ContextPreparationProjectOptions {
|
|
15
|
+
readonly includeHistory?: boolean;
|
|
16
|
+
readonly includeMemory?: boolean;
|
|
17
|
+
readonly pendingMessages?: readonly ModelMessage[];
|
|
18
|
+
}
|
|
19
|
+
export interface ContextHistoryReplacement {
|
|
20
|
+
readonly generation: number;
|
|
21
|
+
readonly envelope: ContextEnvelope;
|
|
22
|
+
readonly stableSystemMessageCount: number;
|
|
23
|
+
commit<T>(replace: () => Promise<T>): Promise<{
|
|
24
|
+
generation: number;
|
|
25
|
+
value: T;
|
|
26
|
+
}>;
|
|
27
|
+
}
|
|
28
|
+
export declare class StaleContextGenerationError extends Error {
|
|
29
|
+
readonly expectedGeneration: number;
|
|
30
|
+
readonly actualGeneration: number;
|
|
31
|
+
constructor(expectedGeneration: number, actualGeneration: number);
|
|
32
|
+
}
|
|
33
|
+
export interface ContextPreparationOptions {
|
|
34
|
+
readonly assembler?: ContextAssembler;
|
|
35
|
+
readonly sources: ContextPreparationSources;
|
|
36
|
+
readonly agentMentions?: () => {
|
|
37
|
+
readonly prompt: string;
|
|
38
|
+
readonly messages: readonly string[];
|
|
39
|
+
};
|
|
40
|
+
readonly initialGeneration?: number;
|
|
41
|
+
}
|
|
42
|
+
/** Owns provider-visible context projection and guarded history replacement. */
|
|
43
|
+
export declare class ContextPreparation {
|
|
44
|
+
private readonly assembler;
|
|
45
|
+
private readonly sources;
|
|
46
|
+
private readonly agentMentions;
|
|
47
|
+
private generation;
|
|
48
|
+
private prepared;
|
|
49
|
+
private replacementQueue;
|
|
50
|
+
constructor(options: ContextPreparationOptions);
|
|
51
|
+
refresh(options?: ContextAssemblyOptions): Promise<void>;
|
|
52
|
+
project(options?: ContextPreparationProjectOptions): ContextPreparationProjection;
|
|
53
|
+
proposeHistoryReplacement(input: {
|
|
54
|
+
readonly historyMessages: readonly ModelMessage[];
|
|
55
|
+
readonly pendingMessages?: readonly ModelMessage[];
|
|
56
|
+
}): ContextHistoryReplacement;
|
|
57
|
+
private projectWithMessages;
|
|
58
|
+
private decorate;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=context-preparation.d.ts.map
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { injectFirstUserMessageContext, projectContextSnapshot, } from '../core/context.js';
|
|
2
|
+
import { assembleContextSnapshot } from '../core/prompt-composer.js';
|
|
3
|
+
export class StaleContextGenerationError extends Error {
|
|
4
|
+
expectedGeneration;
|
|
5
|
+
actualGeneration;
|
|
6
|
+
constructor(expectedGeneration, actualGeneration) {
|
|
7
|
+
super(`Stale context generation: expected ${expectedGeneration}, actual ${actualGeneration}; prepare a new history replacement`);
|
|
8
|
+
this.name = 'StaleContextGenerationError';
|
|
9
|
+
this.expectedGeneration = expectedGeneration;
|
|
10
|
+
this.actualGeneration = actualGeneration;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function validateGeneration(generation) {
|
|
14
|
+
if (!Number.isSafeInteger(generation) || generation < 1) {
|
|
15
|
+
throw new TypeError('Context generation must be a positive safe integer');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function cloneTools(tools) {
|
|
19
|
+
return tools.map((tool) => ({ ...tool }));
|
|
20
|
+
}
|
|
21
|
+
/** Owns provider-visible context projection and guarded history replacement. */
|
|
22
|
+
export class ContextPreparation {
|
|
23
|
+
assembler;
|
|
24
|
+
sources;
|
|
25
|
+
agentMentions;
|
|
26
|
+
generation;
|
|
27
|
+
prepared;
|
|
28
|
+
replacementQueue = Promise.resolve();
|
|
29
|
+
constructor(options) {
|
|
30
|
+
this.assembler = options.assembler;
|
|
31
|
+
this.sources = options.sources;
|
|
32
|
+
this.agentMentions = options.agentMentions;
|
|
33
|
+
this.generation = options.initialGeneration ?? 1;
|
|
34
|
+
validateGeneration(this.generation);
|
|
35
|
+
}
|
|
36
|
+
async refresh(options = {}) {
|
|
37
|
+
const snapshot = await assembleContextSnapshot(this.assembler, options);
|
|
38
|
+
const projection = projectContextSnapshot(snapshot);
|
|
39
|
+
const stableCount = projection.stableSystemSectionCount;
|
|
40
|
+
const prepared = {
|
|
41
|
+
stableSystemMessages: projection.systemMessages.slice(0, stableCount),
|
|
42
|
+
volatileSystemMessages: projection.systemMessages.slice(stableCount),
|
|
43
|
+
...(projection.firstUserMessageContext === undefined
|
|
44
|
+
? {}
|
|
45
|
+
: { firstUserMessageContext: projection.firstUserMessageContext }),
|
|
46
|
+
stableSystemMessageCount: stableCount,
|
|
47
|
+
};
|
|
48
|
+
this.prepared = prepared;
|
|
49
|
+
}
|
|
50
|
+
project(options = {}) {
|
|
51
|
+
return this.projectWithMessages((options.includeHistory ?? true) ? this.sources.history() : [], (options.includeMemory ?? true) ? this.sources.memory() : [], options.pendingMessages ?? []);
|
|
52
|
+
}
|
|
53
|
+
proposeHistoryReplacement(input) {
|
|
54
|
+
const baseGeneration = this.generation;
|
|
55
|
+
if (baseGeneration === Number.MAX_SAFE_INTEGER) {
|
|
56
|
+
throw new RangeError('Context generation cannot exceed Number.MAX_SAFE_INTEGER');
|
|
57
|
+
}
|
|
58
|
+
const generation = baseGeneration + 1;
|
|
59
|
+
const projection = this.projectWithMessages(input.historyMessages, [], input.pendingMessages ?? []);
|
|
60
|
+
let committed = false;
|
|
61
|
+
return {
|
|
62
|
+
generation,
|
|
63
|
+
envelope: projection.envelope,
|
|
64
|
+
stableSystemMessageCount: projection.stableSystemMessageCount,
|
|
65
|
+
commit: async (replace) => {
|
|
66
|
+
const operation = this.replacementQueue.then(async () => {
|
|
67
|
+
if (this.generation !== baseGeneration) {
|
|
68
|
+
throw new StaleContextGenerationError(baseGeneration, this.generation);
|
|
69
|
+
}
|
|
70
|
+
if (committed) {
|
|
71
|
+
throw new StaleContextGenerationError(baseGeneration, this.generation);
|
|
72
|
+
}
|
|
73
|
+
const value = await replace();
|
|
74
|
+
this.generation = generation;
|
|
75
|
+
committed = true;
|
|
76
|
+
return { generation, value };
|
|
77
|
+
});
|
|
78
|
+
this.replacementQueue = operation.then(() => undefined, () => undefined);
|
|
79
|
+
return operation;
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
projectWithMessages(historyMessages, memoryMessages, pendingMessages) {
|
|
84
|
+
const prepared = this.prepared;
|
|
85
|
+
if (!prepared) {
|
|
86
|
+
throw new Error('ContextPreparation must be refreshed before projecting');
|
|
87
|
+
}
|
|
88
|
+
const history = [...historyMessages];
|
|
89
|
+
const memory = [...memoryMessages];
|
|
90
|
+
const pending = [...pendingMessages];
|
|
91
|
+
const decoratedHistory = this.decorate([...history, ...memory, ...pending]);
|
|
92
|
+
const messages = [
|
|
93
|
+
...prepared.stableSystemMessages,
|
|
94
|
+
...prepared.volatileSystemMessages,
|
|
95
|
+
...decoratedHistory,
|
|
96
|
+
];
|
|
97
|
+
return {
|
|
98
|
+
generation: this.generation,
|
|
99
|
+
envelope: {
|
|
100
|
+
messages,
|
|
101
|
+
tools: cloneTools(this.sources.activeTools()),
|
|
102
|
+
},
|
|
103
|
+
stableSystemMessageCount: prepared.stableSystemMessageCount,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
decorate(messages) {
|
|
107
|
+
const prepared = this.prepared;
|
|
108
|
+
if (!prepared) {
|
|
109
|
+
throw new Error('ContextPreparation must be refreshed before projecting');
|
|
110
|
+
}
|
|
111
|
+
const withFirstUserContext = injectFirstUserMessageContext(messages, prepared.firstUserMessageContext);
|
|
112
|
+
const mentionInput = this.agentMentions?.();
|
|
113
|
+
if (!mentionInput || mentionInput.messages.length === 0)
|
|
114
|
+
return withFirstUserContext;
|
|
115
|
+
let insertionIndex = withFirstUserContext.length;
|
|
116
|
+
let foundPrompt = false;
|
|
117
|
+
for (let index = withFirstUserContext.length - 1; index >= 0; index -= 1) {
|
|
118
|
+
const message = withFirstUserContext[index];
|
|
119
|
+
if (message?.role === 'user' &&
|
|
120
|
+
typeof message.content === 'string' &&
|
|
121
|
+
message.content.endsWith(mentionInput.prompt)) {
|
|
122
|
+
insertionIndex = index;
|
|
123
|
+
foundPrompt = true;
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (!foundPrompt)
|
|
128
|
+
return withFirstUserContext;
|
|
129
|
+
return [
|
|
130
|
+
...withFirstUserContext.slice(0, insertionIndex),
|
|
131
|
+
...mentionInput.messages.map((content) => ({
|
|
132
|
+
role: 'user',
|
|
133
|
+
content,
|
|
134
|
+
})),
|
|
135
|
+
...withFirstUserContext.slice(insertionIndex),
|
|
136
|
+
];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=context-preparation.js.map
|
|
@@ -236,7 +236,7 @@ export declare class ClaudeSessionService {
|
|
|
236
236
|
private readonly hookLifecycle;
|
|
237
237
|
private readonly leadOperations;
|
|
238
238
|
private readonly fileChangeWatcher;
|
|
239
|
-
private readonly
|
|
239
|
+
private readonly turnCoordinator;
|
|
240
240
|
private runtimeCwd;
|
|
241
241
|
constructor(options: ClaudeSessionServiceOptions);
|
|
242
242
|
nextScheduledPrompt(signal?: AbortSignal): Promise<ScheduledPrompt | null>;
|
|
@@ -21,9 +21,9 @@ import { BackgroundTaskRuntime, } from './background-task-runtime.js';
|
|
|
21
21
|
import { backgroundAgentNotificationMarkers, } from './background-agent-manager.js';
|
|
22
22
|
import { usageCostUsd } from '../core/usage.js';
|
|
23
23
|
import { isSessionId } from '../core/session.js';
|
|
24
|
-
import { ActiveTurnInputMailbox, } from '../core/active-turn-input.js';
|
|
25
24
|
import { ContextBudget, estimateModelRequestTokens, isPromptTooLongError, } from '../core/context-budget.js';
|
|
26
25
|
import { ContextEngine } from './context-engine.js';
|
|
26
|
+
import { ContextPreparation } from './context-preparation.js';
|
|
27
27
|
import { TurnMemoryCoordinator } from './turn-memory-coordinator.js';
|
|
28
28
|
import { injectFirstUserMessageContext, projectContextSnapshot, } from '../core/context.js';
|
|
29
29
|
import { assembleContextSnapshot } from '../core/prompt-composer.js';
|
|
@@ -38,7 +38,7 @@ import { SubagentLifecycleStore } from '../persistence/subagent-lifecycle-store.
|
|
|
38
38
|
import { ModelCompactor } from './model-compactor.js';
|
|
39
39
|
import { agentMemoryPrompt, ClaudeSubagentExecutor, StructuredOutputRegistry, } from './subagent-service.js';
|
|
40
40
|
import { ScheduledPromptManager, } from './scheduled-prompt-manager.js';
|
|
41
|
-
import {
|
|
41
|
+
import { TurnCoordinator } from './turn-lifecycle.js';
|
|
42
42
|
import { ClaudeScheduledToolRegistry } from '../tools/claude-scheduled-tools.js';
|
|
43
43
|
import { ClaudeTaskToolRegistry } from '../tools/claude-task-tools.js';
|
|
44
44
|
import { ClaudeWorkflowToolRegistry } from '../tools/claude-workflow-tools.js';
|
|
@@ -684,12 +684,16 @@ export class ClaudeSessionService {
|
|
|
684
684
|
hookLifecycle;
|
|
685
685
|
leadOperations;
|
|
686
686
|
fileChangeWatcher;
|
|
687
|
-
|
|
687
|
+
turnCoordinator;
|
|
688
688
|
runtimeCwd;
|
|
689
689
|
constructor(options) {
|
|
690
690
|
const dataPlane = options.dataPlane ?? 'native';
|
|
691
691
|
assertNativeDataPlane(dataPlane);
|
|
692
692
|
this.options = { ...options, dataPlane };
|
|
693
|
+
this.turnCoordinator = new TurnCoordinator({
|
|
694
|
+
eventSink: options.eventSink ?? (() => undefined),
|
|
695
|
+
createSteeringId: randomUUID,
|
|
696
|
+
});
|
|
693
697
|
this.assertNativeTranscriptOptions();
|
|
694
698
|
this.leadOperations = options.teamLeadOperations ?? null;
|
|
695
699
|
this.hookLifecycle = new HookLifecycle(options.hooks, options.eventSink);
|
|
@@ -899,18 +903,7 @@ export class ClaudeSessionService {
|
|
|
899
903
|
}
|
|
900
904
|
async close() {
|
|
901
905
|
this.closing = true;
|
|
902
|
-
|
|
903
|
-
if (!mailbox)
|
|
904
|
-
continue;
|
|
905
|
-
for (const item of mailbox.close()) {
|
|
906
|
-
this.options.eventSink?.({
|
|
907
|
-
type: 'user-input-rejected',
|
|
908
|
-
id: item.id,
|
|
909
|
-
content: item.content,
|
|
910
|
-
reason: 'closed',
|
|
911
|
-
});
|
|
912
|
-
}
|
|
913
|
-
}
|
|
906
|
+
this.turnCoordinator.close();
|
|
914
907
|
await this.fileChangeWatcher?.close(5_000);
|
|
915
908
|
await this.hookLifecycle.close();
|
|
916
909
|
await this.drainDetachedHookRuns(5_000);
|
|
@@ -1330,28 +1323,10 @@ export class ClaudeSessionService {
|
|
|
1330
1323
|
});
|
|
1331
1324
|
}
|
|
1332
1325
|
steer(sessionId, content) {
|
|
1333
|
-
|
|
1334
|
-
if (!active)
|
|
1335
|
-
return { kind: 'no-active-turn' };
|
|
1336
|
-
const mailbox = active.mailbox;
|
|
1337
|
-
if (!mailbox)
|
|
1338
|
-
return { kind: 'not-steerable' };
|
|
1339
|
-
const result = mailbox.enqueue(content);
|
|
1340
|
-
if (result.kind === 'accepted')
|
|
1341
|
-
return result;
|
|
1342
|
-
if (result.kind === 'empty')
|
|
1343
|
-
return result;
|
|
1344
|
-
return { kind: 'turn-completing' };
|
|
1326
|
+
return this.turnCoordinator.steer(sessionId, content);
|
|
1345
1327
|
}
|
|
1346
1328
|
withdrawSteering(sessionId, id) {
|
|
1347
|
-
|
|
1348
|
-
if (!active)
|
|
1349
|
-
return { kind: 'no-active-turn' };
|
|
1350
|
-
const mailbox = active.mailbox;
|
|
1351
|
-
if (!mailbox)
|
|
1352
|
-
return { kind: 'not-steerable' };
|
|
1353
|
-
const result = mailbox.withdraw(id);
|
|
1354
|
-
return result.kind === 'withdrawn' ? result : { kind: 'not-pending' };
|
|
1329
|
+
return this.turnCoordinator.withdrawSteering(sessionId, id);
|
|
1355
1330
|
}
|
|
1356
1331
|
async resumeShell(sessionId, command, signal, name, resumeSessionAt) {
|
|
1357
1332
|
this.worktreeManager?.bindSession(sessionId);
|
|
@@ -2535,29 +2510,8 @@ export class ClaudeSessionService {
|
|
|
2535
2510
|
const documents = submission.kind === 'prompt' ? (submission.documents ?? []) : [];
|
|
2536
2511
|
const shellCommand = submission.kind === 'shell' ? submission.command : undefined;
|
|
2537
2512
|
const skipUserPrompt = submission.kind === 'retry';
|
|
2538
|
-
|
|
2539
|
-
let activeTurnInput;
|
|
2540
|
-
let activeTurnRecord;
|
|
2541
|
-
try {
|
|
2513
|
+
return this.turnCoordinator.run(request, async ({ emit, steering }) => {
|
|
2542
2514
|
this.assertTurnWritable();
|
|
2543
|
-
if (prompt.length === 0 && images.length === 0 && documents.length === 0)
|
|
2544
|
-
throw new Error('Prompt must not be empty');
|
|
2545
|
-
if (name !== undefined && name.length === 0) {
|
|
2546
|
-
throw new Error('Session name must not be empty');
|
|
2547
|
-
}
|
|
2548
|
-
if (shellCommand !== undefined && shellCommand.trim().length === 0) {
|
|
2549
|
-
throw new Error('Shell command must not be empty');
|
|
2550
|
-
}
|
|
2551
|
-
if (this.activeTurnInputs.has(sessionId)) {
|
|
2552
|
-
throw new Error(`conflict: locked (session ${sessionId} already has an active turn)`);
|
|
2553
|
-
}
|
|
2554
|
-
activeTurnRecord =
|
|
2555
|
-
shellCommand === undefined
|
|
2556
|
-
? {
|
|
2557
|
-
mailbox: (activeTurnInput = new ActiveTurnInputMailbox(randomUUID)),
|
|
2558
|
-
}
|
|
2559
|
-
: {};
|
|
2560
|
-
this.activeTurnInputs.set(sessionId, activeTurnRecord);
|
|
2561
2515
|
await this.activateSessionCostTracker(sessionId);
|
|
2562
2516
|
await this.ensureFileResources(sessionId, signal);
|
|
2563
2517
|
this.worktreeManager?.bindSession(sessionId);
|
|
@@ -3071,7 +3025,7 @@ export class ClaudeSessionService {
|
|
|
3071
3025
|
deferPreToolUseOutcome: (call) => pendingRecoveryToolCallIds.has(call.id),
|
|
3072
3026
|
})
|
|
3073
3027
|
: null;
|
|
3074
|
-
const runtime = new AgentRuntime(provider,
|
|
3028
|
+
const runtime = new AgentRuntime(provider, emit, {
|
|
3075
3029
|
emitInitialContextState: false,
|
|
3076
3030
|
...(this.options.emitToolUseSummaries
|
|
3077
3031
|
? {
|
|
@@ -3413,19 +3367,30 @@ export class ClaudeSessionService {
|
|
|
3413
3367
|
let agentSystem = null;
|
|
3414
3368
|
let planModeMessage;
|
|
3415
3369
|
let sessionMemoryMessage = null;
|
|
3416
|
-
let
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3370
|
+
let agentMentionMessages = [];
|
|
3371
|
+
const contextPreparation = new ContextPreparation({
|
|
3372
|
+
...(this.options.contextAssembler
|
|
3373
|
+
? { assembler: this.options.contextAssembler }
|
|
3374
|
+
: {}),
|
|
3375
|
+
sources: {
|
|
3376
|
+
history: activeTurnMessages,
|
|
3377
|
+
memory: () => projectMemoryRecallMessages,
|
|
3378
|
+
activeTools: () => provider.capabilities.tools
|
|
3379
|
+
? (activeTurnTools?.definitions() ?? [])
|
|
3380
|
+
: [],
|
|
3381
|
+
},
|
|
3382
|
+
agentMentions: () => ({
|
|
3383
|
+
prompt: effectivePrompt,
|
|
3384
|
+
messages: agentMentionMessages,
|
|
3385
|
+
}),
|
|
3386
|
+
});
|
|
3422
3387
|
refreshRuntimeContext = async () => {
|
|
3423
3388
|
agentSystem = await this.mainAgentSystemPrompt(agent);
|
|
3424
3389
|
planModeMessage =
|
|
3425
3390
|
this.options.interactiveTools?.contextMessage(sessionId);
|
|
3426
3391
|
// DEBUG_PLAN_CONTEXT
|
|
3427
3392
|
sessionMemoryMessage = this.sessionMemoryMessage(await turnMemory.sessionSummary());
|
|
3428
|
-
|
|
3393
|
+
await contextPreparation.refresh({
|
|
3429
3394
|
cwd: this.activeCwd(),
|
|
3430
3395
|
lifecycleId: sessionId,
|
|
3431
3396
|
...(agentSystem
|
|
@@ -3442,10 +3407,6 @@ export class ClaudeSessionService {
|
|
|
3442
3407
|
: {}),
|
|
3443
3408
|
},
|
|
3444
3409
|
});
|
|
3445
|
-
contextProjection = projectContextSnapshot(assembledContext);
|
|
3446
|
-
stableSystemMessageCount =
|
|
3447
|
-
contextProjection.stableSystemSectionCount;
|
|
3448
|
-
contextMessages = [...contextProjection.systemMessages];
|
|
3449
3410
|
};
|
|
3450
3411
|
await refreshRuntimeContext();
|
|
3451
3412
|
const expansion = shouldSkipUserPrompt()
|
|
@@ -3475,9 +3436,6 @@ export class ClaudeSessionService {
|
|
|
3475
3436
|
let compactionDurationMs;
|
|
3476
3437
|
let compactionDurationWithoutRetriesMs;
|
|
3477
3438
|
let compactionModelUsage;
|
|
3478
|
-
const currentDefinitions = () => provider.capabilities.tools
|
|
3479
|
-
? (activeTurnTools?.definitions() ?? [])
|
|
3480
|
-
: [];
|
|
3481
3439
|
const budget = this.contextBudget(provider);
|
|
3482
3440
|
const contextEngine = new ContextEngine({
|
|
3483
3441
|
...(budget ? { budget } : {}),
|
|
@@ -3509,88 +3467,50 @@ export class ClaudeSessionService {
|
|
|
3509
3467
|
? { documents }
|
|
3510
3468
|
: {}),
|
|
3511
3469
|
}));
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
if (agentMentionMessages.length === 0)
|
|
3517
|
-
return [...messages];
|
|
3518
|
-
let insertionIndex = messages.length;
|
|
3519
|
-
let foundPrompt = false;
|
|
3520
|
-
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
3521
|
-
const message = messages[index];
|
|
3522
|
-
if (message?.role === 'user' &&
|
|
3523
|
-
typeof message.content === 'string' &&
|
|
3524
|
-
message.content.endsWith(effectivePrompt)) {
|
|
3525
|
-
insertionIndex = index;
|
|
3526
|
-
foundPrompt = true;
|
|
3527
|
-
break;
|
|
3528
|
-
}
|
|
3529
|
-
}
|
|
3530
|
-
if (!foundPrompt)
|
|
3531
|
-
return [...messages];
|
|
3532
|
-
return [
|
|
3533
|
-
...messages.slice(0, insertionIndex),
|
|
3534
|
-
...agentMentionMessages.map((content) => ({
|
|
3535
|
-
role: 'user',
|
|
3536
|
-
content,
|
|
3537
|
-
})),
|
|
3538
|
-
...messages.slice(insertionIndex),
|
|
3539
|
-
];
|
|
3540
|
-
};
|
|
3541
|
-
const injectDynamicContext = (messages) => injectFirstUserMessageContext(messages, contextProjection.firstUserMessageContext);
|
|
3542
|
-
const injectTurnContext = (messages) => injectAgentMentionContext(injectDynamicContext(messages));
|
|
3470
|
+
agentMentionMessages =
|
|
3471
|
+
shellCommand === undefined && !shouldSkipUserPrompt()
|
|
3472
|
+
? (this.options.extensions?.agentMentionMessages(effectivePrompt) ?? [])
|
|
3473
|
+
: [];
|
|
3543
3474
|
let compactionAnchorUuid = this.lastMessageUuid(snapshot.entries);
|
|
3544
3475
|
const contextTransitionPort = (pendingMessages = [], preservedUserMessages = []) => ({
|
|
3545
3476
|
current: () => {
|
|
3546
|
-
const
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
return {
|
|
3551
|
-
messages: [
|
|
3552
|
-
...contextMessages,
|
|
3553
|
-
...injectTurnContext([
|
|
3554
|
-
...historyMessages,
|
|
3555
|
-
...pendingMessages,
|
|
3556
|
-
]),
|
|
3557
|
-
],
|
|
3558
|
-
tools: currentDefinitions(),
|
|
3559
|
-
};
|
|
3477
|
+
const projection = contextPreparation.project({
|
|
3478
|
+
pendingMessages,
|
|
3479
|
+
});
|
|
3480
|
+
return projection.envelope;
|
|
3560
3481
|
},
|
|
3561
|
-
irreducible: () => ({
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
]),
|
|
3482
|
+
irreducible: () => contextPreparation.project({
|
|
3483
|
+
includeHistory: false,
|
|
3484
|
+
includeMemory: false,
|
|
3485
|
+
pendingMessages: [
|
|
3486
|
+
...pendingMessages,
|
|
3487
|
+
...preservedUserMessages.map((content) => ({
|
|
3488
|
+
role: 'user',
|
|
3489
|
+
content,
|
|
3490
|
+
})),
|
|
3571
3491
|
],
|
|
3572
|
-
|
|
3573
|
-
}),
|
|
3492
|
+
}).envelope,
|
|
3574
3493
|
propose: async () => {
|
|
3575
3494
|
const activeNativeLease = nativeLease;
|
|
3576
3495
|
if (!budget)
|
|
3577
3496
|
throw new Error('Context budget is unavailable');
|
|
3578
|
-
const definitions =
|
|
3497
|
+
const definitions = contextPreparation.project().envelope.tools;
|
|
3579
3498
|
const historyMessages = activeTurnMessages();
|
|
3580
3499
|
if (historyMessages.length === 0)
|
|
3581
3500
|
throw new Error('Cannot compact an empty native transcript');
|
|
3582
3501
|
if (unresolvedActiveToolCallIds(historyMessages).length > 0)
|
|
3583
3502
|
throw new Error('Cannot compact a native transcript with unresolved tool calls');
|
|
3584
|
-
const irreducibleMessages =
|
|
3585
|
-
|
|
3586
|
-
|
|
3503
|
+
const irreducibleMessages = contextPreparation.project({
|
|
3504
|
+
includeHistory: false,
|
|
3505
|
+
includeMemory: false,
|
|
3506
|
+
pendingMessages: [
|
|
3587
3507
|
...pendingMessages,
|
|
3588
3508
|
...preservedUserMessages.map((content) => ({
|
|
3589
3509
|
role: 'user',
|
|
3590
3510
|
content,
|
|
3591
3511
|
})),
|
|
3592
|
-
]
|
|
3593
|
-
|
|
3512
|
+
],
|
|
3513
|
+
}).envelope.messages;
|
|
3594
3514
|
let compactableMessages = historyMessages;
|
|
3595
3515
|
let preservedMessages = [];
|
|
3596
3516
|
let compactionLogicalParentId;
|
|
@@ -3757,14 +3677,15 @@ export class ClaudeSessionService {
|
|
|
3757
3677
|
};
|
|
3758
3678
|
let replayMessages = preservedMessages;
|
|
3759
3679
|
try {
|
|
3760
|
-
const replayReport = budget.evaluate(
|
|
3761
|
-
|
|
3762
|
-
|
|
3680
|
+
const replayReport = budget.evaluate(contextPreparation.project({
|
|
3681
|
+
includeHistory: false,
|
|
3682
|
+
includeMemory: false,
|
|
3683
|
+
pendingMessages: [
|
|
3763
3684
|
summaryMessage,
|
|
3764
3685
|
...replayMessages,
|
|
3765
3686
|
...pendingMessages,
|
|
3766
|
-
]
|
|
3767
|
-
|
|
3687
|
+
],
|
|
3688
|
+
}).envelope.messages, definitions);
|
|
3768
3689
|
if (replayReport.shouldCompact)
|
|
3769
3690
|
throw new Error('replay overflow');
|
|
3770
3691
|
}
|
|
@@ -3775,23 +3696,16 @@ export class ClaudeSessionService {
|
|
|
3775
3696
|
// contains its contents and user prompts remain available.
|
|
3776
3697
|
replayMessages = replayMessages.filter((message) => message.role === 'user' && !Array.isArray(message.content));
|
|
3777
3698
|
}
|
|
3778
|
-
const
|
|
3779
|
-
...
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
...replayMessages,
|
|
3783
|
-
...pendingMessages,
|
|
3784
|
-
]),
|
|
3785
|
-
];
|
|
3699
|
+
const replacement = contextPreparation.proposeHistoryReplacement({
|
|
3700
|
+
historyMessages: [summaryMessage, ...replayMessages],
|
|
3701
|
+
pendingMessages,
|
|
3702
|
+
});
|
|
3786
3703
|
return {
|
|
3787
|
-
envelope:
|
|
3788
|
-
messages: proposedMessages,
|
|
3789
|
-
tools: definitions,
|
|
3790
|
-
},
|
|
3704
|
+
envelope: replacement.envelope,
|
|
3791
3705
|
commit: async () => {
|
|
3792
3706
|
if (signal?.aborted)
|
|
3793
3707
|
throw new AgentRunCancelledError();
|
|
3794
|
-
const
|
|
3708
|
+
const committed = await replacement.commit(() => nativeLease.appendCompaction({
|
|
3795
3709
|
summary: compacted.summary,
|
|
3796
3710
|
trigger: 'auto',
|
|
3797
3711
|
preTokens,
|
|
@@ -3808,7 +3722,8 @@ export class ClaudeSessionService {
|
|
|
3808
3722
|
preservePrefix: false,
|
|
3809
3723
|
}
|
|
3810
3724
|
: {}),
|
|
3811
|
-
});
|
|
3725
|
+
}));
|
|
3726
|
+
const ids = committed.value;
|
|
3812
3727
|
await this.runAdvisoryHook(sessionId, 'PostCompact', { trigger: 'auto', compact_summary: compacted.summary }, 'auto', signal);
|
|
3813
3728
|
if (this.options.hooks) {
|
|
3814
3729
|
const outcome = await this.hookLifecycle.refresh(sessionId, hookSession, signal);
|
|
@@ -4095,25 +4010,24 @@ export class ClaudeSessionService {
|
|
|
4095
4010
|
};
|
|
4096
4011
|
}
|
|
4097
4012
|
if (shellCommand === undefined && budget) {
|
|
4098
|
-
const definitions = currentDefinitions();
|
|
4099
4013
|
await contextEngine.prepare(contextTransitionPort([], currentTurnUserMessages ?? []), signal);
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4014
|
+
const projection = contextPreparation.project({
|
|
4015
|
+
includeMemory: false,
|
|
4016
|
+
});
|
|
4017
|
+
budget.assertFits(budget.evaluate(projection.envelope.messages, projection.envelope.tools));
|
|
4104
4018
|
}
|
|
4105
4019
|
let stopHookActive = false;
|
|
4020
|
+
const initialProjection = contextPreparation.project({
|
|
4021
|
+
includeMemory: false,
|
|
4022
|
+
});
|
|
4106
4023
|
const runtimeRequest = {
|
|
4107
4024
|
sessionId,
|
|
4108
|
-
messages:
|
|
4109
|
-
|
|
4110
|
-
...injectTurnContext(activeTurnMessages()),
|
|
4111
|
-
],
|
|
4112
|
-
stableSystemMessageCount,
|
|
4025
|
+
messages: initialProjection.envelope.messages,
|
|
4026
|
+
stableSystemMessageCount: initialProjection.stableSystemMessageCount,
|
|
4113
4027
|
cwd: this.activeCwd(),
|
|
4114
4028
|
toolResultDirectory,
|
|
4115
4029
|
observer,
|
|
4116
|
-
...(
|
|
4030
|
+
...(steering ? { steering } : {}),
|
|
4117
4031
|
...(this.options.effort ? { effort: this.options.effort } : {}),
|
|
4118
4032
|
...(this.options.maxModelTurns !== undefined
|
|
4119
4033
|
? { maxModelTurns: this.options.maxModelTurns }
|
|
@@ -4138,14 +4052,10 @@ export class ClaudeSessionService {
|
|
|
4138
4052
|
if (shellCommand === undefined) {
|
|
4139
4053
|
await contextEngine.prepare(contextTransitionPort([], currentTurnUserMessages ?? []), signal);
|
|
4140
4054
|
}
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
...activeTurnMessages(),
|
|
4146
|
-
...projectMemoryRecallMessages,
|
|
4147
|
-
]),
|
|
4148
|
-
];
|
|
4055
|
+
const projection = contextPreparation.project();
|
|
4056
|
+
runtimeRequest.stableSystemMessageCount =
|
|
4057
|
+
projection.stableSystemMessageCount;
|
|
4058
|
+
return projection.envelope.messages;
|
|
4149
4059
|
},
|
|
4150
4060
|
...(this.options.hooks ||
|
|
4151
4061
|
subagentExecutor ||
|
|
@@ -4274,14 +4184,10 @@ export class ClaudeSessionService {
|
|
|
4274
4184
|
}
|
|
4275
4185
|
// The single reactive retry must use the compacted transcript, not
|
|
4276
4186
|
// the stale request copy captured before the compact boundary.
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
...projectMemoryRecallMessages,
|
|
4282
|
-
]),
|
|
4283
|
-
];
|
|
4284
|
-
runtimeRequest.stableSystemMessageCount = stableSystemMessageCount;
|
|
4187
|
+
const projection = contextPreparation.project();
|
|
4188
|
+
runtimeRequest.messages = projection.envelope.messages;
|
|
4189
|
+
runtimeRequest.stableSystemMessageCount =
|
|
4190
|
+
projection.stableSystemMessageCount;
|
|
4285
4191
|
runtimeRequest.deferFailureKinds = true;
|
|
4286
4192
|
try {
|
|
4287
4193
|
result = await attemptMainTurn();
|
|
@@ -4417,11 +4323,11 @@ export class ClaudeSessionService {
|
|
|
4417
4323
|
? projectNativeSessionEntries(nativeLease.activeEvents()).at(-1)
|
|
4418
4324
|
?.uuid
|
|
4419
4325
|
: undefined;
|
|
4420
|
-
const
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
const definitions =
|
|
4326
|
+
const providerProjection = contextPreparation.project({
|
|
4327
|
+
includeMemory: false,
|
|
4328
|
+
});
|
|
4329
|
+
const providerVisibleMessages = providerProjection.envelope.messages;
|
|
4330
|
+
const definitions = providerProjection.envelope.tools;
|
|
4425
4331
|
const currentContextTokens = contextEngine.report({
|
|
4426
4332
|
messages: providerVisibleMessages,
|
|
4427
4333
|
tools: definitions,
|
|
@@ -4507,27 +4413,8 @@ export class ClaudeSessionService {
|
|
|
4507
4413
|
return scratchResult.value;
|
|
4508
4414
|
});
|
|
4509
4415
|
}
|
|
4510
|
-
controller.complete();
|
|
4511
4416
|
return result;
|
|
4512
|
-
}
|
|
4513
|
-
catch (error) {
|
|
4514
|
-
controller.fail(error, signal);
|
|
4515
|
-
throw error;
|
|
4516
|
-
}
|
|
4517
|
-
finally {
|
|
4518
|
-
if (activeTurnInput !== undefined) {
|
|
4519
|
-
for (const item of activeTurnInput.close()) {
|
|
4520
|
-
this.options.eventSink?.({
|
|
4521
|
-
type: 'user-input-rejected',
|
|
4522
|
-
id: item.id,
|
|
4523
|
-
content: item.content,
|
|
4524
|
-
reason: signal?.aborted ? 'cancelled' : 'failed',
|
|
4525
|
-
});
|
|
4526
|
-
}
|
|
4527
|
-
}
|
|
4528
|
-
if (this.activeTurnInputs.get(sessionId) === activeTurnRecord)
|
|
4529
|
-
this.activeTurnInputs.delete(sessionId);
|
|
4530
|
-
}
|
|
4417
|
+
});
|
|
4531
4418
|
}
|
|
4532
4419
|
async ensureFileResources(sessionId, signal) {
|
|
4533
4420
|
const resources = this.options.fileResources ?? [];
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { type ModelDocument, type ModelImage, type RuntimeEventSink } from '../core/runtime.js';
|
|
2
|
-
import type
|
|
3
|
-
export type TurnTerminalState = Extract<LifecycleState, 'completed' | 'failed' | 'cancelled'>;
|
|
2
|
+
import { type ActiveTurnInputCommandResult, type ActiveTurnInputPort } from '../core/active-turn-input.js';
|
|
4
3
|
export type TurnActivation = {
|
|
5
4
|
kind: 'start';
|
|
6
5
|
sessionId: string;
|
|
@@ -28,13 +27,26 @@ export interface TurnRequest {
|
|
|
28
27
|
submission: TurnSubmission;
|
|
29
28
|
signal?: AbortSignal;
|
|
30
29
|
}
|
|
31
|
-
export
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
export interface TurnScope {
|
|
31
|
+
readonly emit: RuntimeEventSink;
|
|
32
|
+
readonly steering?: ActiveTurnInputPort;
|
|
33
|
+
}
|
|
34
|
+
export interface TurnCoordinatorOptions {
|
|
35
|
+
readonly eventSink: RuntimeEventSink;
|
|
36
|
+
readonly createSteeringId: () => string;
|
|
37
|
+
}
|
|
38
|
+
/** Owns the lifecycle and active-turn coordination for one session service. */
|
|
39
|
+
export declare class TurnCoordinator {
|
|
40
|
+
private readonly options;
|
|
41
|
+
private readonly activeTurns;
|
|
42
|
+
constructor(options: TurnCoordinatorOptions);
|
|
43
|
+
run<T>(request: TurnRequest, work: (scope: TurnScope) => Promise<T>): Promise<T>;
|
|
44
|
+
steer(sessionId: string, content: string): ActiveTurnInputCommandResult;
|
|
45
|
+
withdrawSteering(sessionId: string, id: string): ActiveTurnInputCommandResult;
|
|
46
|
+
close(): void;
|
|
47
|
+
private validateRequest;
|
|
48
|
+
private terminalState;
|
|
38
49
|
private transition;
|
|
50
|
+
private rejectPending;
|
|
39
51
|
}
|
|
40
52
|
//# sourceMappingURL=turn-lifecycle.d.ts.map
|
|
@@ -1,36 +1,150 @@
|
|
|
1
1
|
import { AgentRunCancelledError, ModelProviderError, } from '../core/runtime.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
2
|
+
import { ActiveTurnInputMailbox, } from '../core/active-turn-input.js';
|
|
3
|
+
/** Owns the lifecycle and active-turn coordination for one session service. */
|
|
4
|
+
export class TurnCoordinator {
|
|
5
|
+
options;
|
|
6
|
+
activeTurns = new Map();
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
}
|
|
10
|
+
async run(request, work) {
|
|
11
|
+
const { sessionId } = request.activation;
|
|
12
|
+
const mailbox = request.submission.kind === 'shell'
|
|
13
|
+
? undefined
|
|
14
|
+
: new ActiveTurnInputMailbox(this.options.createSteeringId);
|
|
15
|
+
const record = {
|
|
16
|
+
...(mailbox ? { mailbox } : {}),
|
|
17
|
+
terminal: false,
|
|
18
|
+
};
|
|
19
|
+
let terminalState = 'failed';
|
|
20
|
+
let pendingFailure;
|
|
21
|
+
const scope = {
|
|
22
|
+
emit: (event) => {
|
|
23
|
+
if (event.type === 'state' &&
|
|
24
|
+
(event.state === 'completed' ||
|
|
25
|
+
event.state === 'failed' ||
|
|
26
|
+
event.state === 'cancelled')) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
this.options.eventSink(event);
|
|
30
|
+
},
|
|
31
|
+
...(mailbox ? { steering: mailbox } : {}),
|
|
32
|
+
};
|
|
33
|
+
try {
|
|
34
|
+
this.validateRequest(request);
|
|
35
|
+
if (this.activeTurns.has(sessionId)) {
|
|
36
|
+
throw new Error(`conflict: locked (session ${sessionId} already has an active turn)`);
|
|
37
|
+
}
|
|
38
|
+
this.activeTurns.set(sessionId, record);
|
|
39
|
+
const result = await work(scope);
|
|
40
|
+
terminalState = 'completed';
|
|
41
|
+
this.transition(record, 'completed');
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (!record.terminal) {
|
|
46
|
+
terminalState = this.terminalState(error, request.signal);
|
|
47
|
+
this.transition(record, terminalState);
|
|
48
|
+
}
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
try {
|
|
53
|
+
if (mailbox) {
|
|
54
|
+
pendingFailure = this.rejectPending(mailbox.close(), terminalState === 'cancelled' ? 'cancelled' : 'failed');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
if (this.activeTurns.get(sessionId) === record) {
|
|
59
|
+
this.activeTurns.delete(sessionId);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (pendingFailure) {
|
|
63
|
+
// A rejected-input sink failure intentionally retains its prior precedence.
|
|
64
|
+
// eslint-disable-next-line no-unsafe-finally -- compatibility is covered by the sink-error regression
|
|
65
|
+
throw pendingFailure.error;
|
|
66
|
+
}
|
|
14
67
|
}
|
|
15
|
-
this.sink(event);
|
|
16
|
-
};
|
|
17
|
-
complete() {
|
|
18
|
-
this.transition('completed');
|
|
19
68
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
69
|
+
steer(sessionId, content) {
|
|
70
|
+
const active = this.activeTurns.get(sessionId);
|
|
71
|
+
if (!active)
|
|
72
|
+
return { kind: 'no-active-turn' };
|
|
73
|
+
if (!active.mailbox)
|
|
74
|
+
return { kind: 'not-steerable' };
|
|
75
|
+
const result = active.mailbox.enqueue(content);
|
|
76
|
+
if (result.kind === 'accepted' || result.kind === 'empty')
|
|
77
|
+
return result;
|
|
78
|
+
return { kind: 'turn-completing' };
|
|
79
|
+
}
|
|
80
|
+
withdrawSteering(sessionId, id) {
|
|
81
|
+
const active = this.activeTurns.get(sessionId);
|
|
82
|
+
if (!active)
|
|
83
|
+
return { kind: 'no-active-turn' };
|
|
84
|
+
if (!active.mailbox)
|
|
85
|
+
return { kind: 'not-steerable' };
|
|
86
|
+
const result = active.mailbox.withdraw(id);
|
|
87
|
+
return result.kind === 'withdrawn' ? result : { kind: 'not-pending' };
|
|
88
|
+
}
|
|
89
|
+
close() {
|
|
90
|
+
let firstFailure;
|
|
91
|
+
for (const active of this.activeTurns.values()) {
|
|
92
|
+
if (!active.mailbox)
|
|
93
|
+
continue;
|
|
94
|
+
const failure = this.rejectPending(active.mailbox.close(), 'closed');
|
|
95
|
+
firstFailure ??= failure;
|
|
96
|
+
}
|
|
97
|
+
if (firstFailure)
|
|
98
|
+
throw firstFailure.error;
|
|
99
|
+
}
|
|
100
|
+
validateRequest(request) {
|
|
101
|
+
const { activation, submission } = request;
|
|
102
|
+
const prompt = submission.kind === 'shell'
|
|
103
|
+
? `! ${submission.command}`
|
|
104
|
+
: submission.kind === 'retry'
|
|
105
|
+
? submission.prompt
|
|
106
|
+
: submission.text;
|
|
107
|
+
const images = submission.kind === 'prompt' ? (submission.images ?? []) : [];
|
|
108
|
+
const documents = submission.kind === 'prompt' ? (submission.documents ?? []) : [];
|
|
109
|
+
if (prompt.length === 0 && images.length === 0 && documents.length === 0) {
|
|
110
|
+
throw new Error('Prompt must not be empty');
|
|
111
|
+
}
|
|
112
|
+
if (activation.name !== undefined && activation.name.length === 0) {
|
|
113
|
+
throw new Error('Session name must not be empty');
|
|
114
|
+
}
|
|
115
|
+
if (submission.kind === 'shell' && submission.command.trim().length === 0) {
|
|
116
|
+
throw new Error('Shell command must not be empty');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
terminalState(error, signal) {
|
|
120
|
+
return signal?.aborted === true ||
|
|
24
121
|
error instanceof AgentRunCancelledError ||
|
|
25
|
-
(error instanceof ModelProviderError && error.kind === 'cancelled')
|
|
26
|
-
|
|
122
|
+
(error instanceof ModelProviderError && error.kind === 'cancelled')
|
|
123
|
+
? 'cancelled'
|
|
124
|
+
: 'failed';
|
|
125
|
+
}
|
|
126
|
+
transition(record, state) {
|
|
127
|
+
if (record.terminal)
|
|
128
|
+
return;
|
|
129
|
+
record.terminal = true;
|
|
130
|
+
this.options.eventSink({ type: 'state', state });
|
|
27
131
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
132
|
+
rejectPending(items, reason) {
|
|
133
|
+
let firstFailure;
|
|
134
|
+
for (const item of items) {
|
|
135
|
+
try {
|
|
136
|
+
this.options.eventSink({
|
|
137
|
+
type: 'user-input-rejected',
|
|
138
|
+
id: item.id,
|
|
139
|
+
content: item.content,
|
|
140
|
+
reason,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
firstFailure ??= { error };
|
|
145
|
+
}
|
|
31
146
|
}
|
|
32
|
-
|
|
33
|
-
this.sink({ type: 'state', state });
|
|
147
|
+
return firstFailure;
|
|
34
148
|
}
|
|
35
149
|
}
|
|
36
150
|
//# sourceMappingURL=turn-lifecycle.js.map
|