@fabric-harness/sdk 1.13.0 → 2.0.0
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/dist/action.d.ts +69 -0
- package/dist/action.d.ts.map +1 -0
- package/dist/action.js +119 -0
- package/dist/action.js.map +1 -0
- package/dist/agent-definition.d.ts +6 -0
- package/dist/agent-definition.d.ts.map +1 -1
- package/dist/agent-definition.js.map +1 -1
- package/dist/agent.d.ts +2 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +35 -1
- package/dist/agent.js.map +1 -1
- package/dist/attachment-store-contract.d.ts +34 -0
- package/dist/attachment-store-contract.d.ts.map +1 -0
- package/dist/attachment-store-contract.js +0 -0
- package/dist/attachment-store-contract.js.map +1 -0
- package/dist/attachment-store.d.ts +107 -0
- package/dist/attachment-store.d.ts.map +1 -0
- package/dist/attachment-store.js +164 -0
- package/dist/attachment-store.js.map +1 -0
- package/dist/channel.d.ts +84 -0
- package/dist/channel.d.ts.map +1 -0
- package/dist/channel.js +115 -0
- package/dist/channel.js.map +1 -0
- package/dist/context-budget.d.ts +16 -0
- package/dist/context-budget.d.ts.map +1 -1
- package/dist/context-budget.js +38 -3
- package/dist/context-budget.js.map +1 -1
- package/dist/conversation-projection.d.ts +52 -0
- package/dist/conversation-projection.d.ts.map +1 -0
- package/dist/conversation-projection.js +207 -0
- package/dist/conversation-projection.js.map +1 -0
- package/dist/conversation-stream-contract.d.ts +37 -0
- package/dist/conversation-stream-contract.d.ts.map +1 -0
- package/dist/conversation-stream-contract.js +453 -0
- package/dist/conversation-stream-contract.js.map +1 -0
- package/dist/conversation-stream.d.ts +141 -0
- package/dist/conversation-stream.d.ts.map +1 -0
- package/dist/conversation-stream.js +159 -0
- package/dist/conversation-stream.js.map +1 -0
- package/dist/cost-budget.d.ts +106 -1
- package/dist/cost-budget.d.ts.map +1 -1
- package/dist/cost-budget.js +48 -2
- package/dist/cost-budget.js.map +1 -1
- package/dist/delivered-message.d.ts +90 -0
- package/dist/delivered-message.d.ts.map +1 -0
- package/dist/delivered-message.js +154 -0
- package/dist/delivered-message.js.map +1 -0
- package/dist/dispatch.d.ts +108 -0
- package/dist/dispatch.d.ts.map +1 -0
- package/dist/dispatch.js +179 -0
- package/dist/dispatch.js.map +1 -0
- package/dist/error.d.ts +1 -1
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js.map +1 -1
- package/dist/history.d.ts +49 -2
- package/dist/history.d.ts.map +1 -1
- package/dist/history.js +163 -0
- package/dist/history.js.map +1 -1
- package/dist/in-memory-submission-store.d.ts +53 -0
- package/dist/in-memory-submission-store.d.ts.map +1 -0
- package/dist/in-memory-submission-store.js +340 -0
- package/dist/in-memory-submission-store.js.map +1 -0
- package/dist/index.d.ts +51 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -7
- package/dist/index.js.map +1 -1
- package/dist/loop.d.ts +29 -3
- package/dist/loop.d.ts.map +1 -1
- package/dist/loop.js +167 -24
- package/dist/loop.js.map +1 -1
- package/dist/model.d.ts +37 -0
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +250 -79
- package/dist/model.js.map +1 -1
- package/dist/otel-observer.d.ts +44 -0
- package/dist/otel-observer.d.ts.map +1 -0
- package/dist/otel-observer.js +165 -0
- package/dist/otel-observer.js.map +1 -0
- package/dist/persistence.d.ts +70 -0
- package/dist/persistence.d.ts.map +1 -0
- package/dist/persistence.js +17 -0
- package/dist/persistence.js.map +1 -0
- package/dist/persistent-agent.d.ts +86 -0
- package/dist/persistent-agent.d.ts.map +1 -0
- package/dist/persistent-agent.js +75 -0
- package/dist/persistent-agent.js.map +1 -0
- package/dist/pi-loop-runtime.d.ts.map +1 -1
- package/dist/pi-loop-runtime.js +7 -1
- package/dist/pi-loop-runtime.js.map +1 -1
- package/dist/profile.d.ts +33 -0
- package/dist/profile.d.ts.map +1 -0
- package/dist/profile.js +10 -0
- package/dist/profile.js.map +1 -0
- package/dist/providers.d.ts +14 -0
- package/dist/providers.d.ts.map +1 -1
- package/dist/providers.js +18 -1
- package/dist/providers.js.map +1 -1
- package/dist/result.d.ts +12 -0
- package/dist/result.d.ts.map +1 -1
- package/dist/result.js +26 -0
- package/dist/result.js.map +1 -1
- package/dist/retrieval.d.ts +61 -0
- package/dist/retrieval.d.ts.map +1 -0
- package/dist/retrieval.js +26 -0
- package/dist/retrieval.js.map +1 -0
- package/dist/runtime-mode.d.ts +1 -1
- package/dist/runtime-mode.d.ts.map +1 -1
- package/dist/runtime-mode.js +2 -1
- package/dist/runtime-mode.js.map +1 -1
- package/dist/scope-key.d.ts +25 -0
- package/dist/scope-key.d.ts.map +1 -0
- package/dist/scope-key.js +34 -0
- package/dist/scope-key.js.map +1 -0
- package/dist/session.d.ts +47 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +364 -35
- package/dist/session.js.map +1 -1
- package/dist/skills.d.ts +33 -0
- package/dist/skills.d.ts.map +1 -0
- package/dist/skills.js +65 -0
- package/dist/skills.js.map +1 -0
- package/dist/store.d.ts +36 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +83 -0
- package/dist/store.js.map +1 -1
- package/dist/stream-chunks.d.ts +42 -0
- package/dist/stream-chunks.d.ts.map +1 -0
- package/dist/stream-chunks.js +138 -0
- package/dist/stream-chunks.js.map +1 -0
- package/dist/strict.d.ts +40 -11
- package/dist/strict.d.ts.map +1 -1
- package/dist/strict.js +22 -7
- package/dist/strict.js.map +1 -1
- package/dist/submission-context.d.ts +26 -0
- package/dist/submission-context.d.ts.map +1 -0
- package/dist/submission-context.js +20 -0
- package/dist/submission-context.js.map +1 -0
- package/dist/submission-runner.d.ts +150 -0
- package/dist/submission-runner.d.ts.map +1 -0
- package/dist/submission-runner.js +767 -0
- package/dist/submission-runner.js.map +1 -0
- package/dist/submission-state.d.ts +50 -0
- package/dist/submission-state.d.ts.map +1 -0
- package/dist/submission-state.js +82 -0
- package/dist/submission-state.js.map +1 -0
- package/dist/submission-store-contract.d.ts +31 -0
- package/dist/submission-store-contract.d.ts.map +1 -0
- package/dist/submission-store-contract.js +730 -0
- package/dist/submission-store-contract.js.map +1 -0
- package/dist/submission-store.d.ts +365 -0
- package/dist/submission-store.d.ts.map +1 -0
- package/dist/submission-store.js +191 -0
- package/dist/submission-store.js.map +1 -0
- package/dist/submission-telemetry.d.ts +56 -0
- package/dist/submission-telemetry.d.ts.map +1 -0
- package/dist/submission-telemetry.js +36 -0
- package/dist/submission-telemetry.js.map +1 -0
- package/dist/testing-contracts.d.ts +17 -0
- package/dist/testing-contracts.d.ts.map +1 -0
- package/dist/testing-contracts.js +14 -0
- package/dist/testing-contracts.js.map +1 -0
- package/dist/tools.d.ts +32 -4
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +132 -6
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts +132 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +16 -8
package/dist/session.js
CHANGED
|
@@ -2,22 +2,75 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { CostBudgetTracker } from './cost-budget.js';
|
|
3
3
|
import { inMemorySessionMemory } from './session-memory.js';
|
|
4
4
|
import { FabricError } from './error.js';
|
|
5
|
-
import { buildModelMessagesFromHistory } from './history.js';
|
|
5
|
+
import { SessionHistory, buildModelMessagesFromHistory, findTrailingDanglingToolCalls, findTrailingUnfinishedTasks } from './history.js';
|
|
6
6
|
import { getLogger } from './logger.js';
|
|
7
7
|
import { defaultLoopRuntime } from './loop.js';
|
|
8
8
|
import { defaultModelProvider, generateWithRuntime } from './model.js';
|
|
9
9
|
import { clampCommandTimeout, evaluateCommandPolicy, evaluateToolCallPolicy } from './policy.js';
|
|
10
10
|
import { buildResultRetryPrompt, validateResult } from './result.js';
|
|
11
|
-
import { evaluateContextBudget, isContextOverflowError } from './context-budget.js';
|
|
11
|
+
import { estimateSessionEntryTokens, evaluateContextBudget, isContextOverflowError } from './context-budget.js';
|
|
12
12
|
import { policiedSandboxEnv } from './policied-sandbox.js';
|
|
13
13
|
import { createSandboxEnv, createScopedSandboxEnv } from './sandbox.js';
|
|
14
14
|
import { createFabricFs } from './filesystem.js';
|
|
15
15
|
import { registerSandbox, serializeSandboxRef, unregisterSandbox } from './sandbox-ref.js';
|
|
16
16
|
import path from 'node:path';
|
|
17
17
|
import { defaultSessionStore } from './store.js';
|
|
18
|
-
import { createBuiltinTools, createCommandTools, shellQuote } from './tools.js';
|
|
18
|
+
import { createActivateSkillTool, createBuiltinTools, createCommandTools, shellQuote } from './tools.js';
|
|
19
|
+
import { formatPackagedSkillResourceList, readPackagedSkillFile } from './skills.js';
|
|
19
20
|
import { MAX_TASK_DEPTH } from './types.js';
|
|
20
21
|
let nextTaskId = 0;
|
|
22
|
+
const CROCKFORD_ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
|
|
23
|
+
const AFFINITY_KEY_REGEX = /^aff_[0-7][0-9A-HJKMNP-TV-Z]{25}$/;
|
|
24
|
+
function encodeBase32(buf, length) {
|
|
25
|
+
let result = '';
|
|
26
|
+
let bitIndex = 0; // 0 = MSB of buf[0]
|
|
27
|
+
for (let i = 0; i < length; i++) {
|
|
28
|
+
let value = 0;
|
|
29
|
+
for (let j = 0; j < 5; j++) {
|
|
30
|
+
const byteIndex = Math.floor(bitIndex / 8);
|
|
31
|
+
const bitInByte = 7 - (bitIndex % 8);
|
|
32
|
+
const byte = buf[byteIndex] ?? 0;
|
|
33
|
+
const bit = (byte >> bitInByte) & 1;
|
|
34
|
+
value = (value << 1) | bit;
|
|
35
|
+
bitIndex++;
|
|
36
|
+
}
|
|
37
|
+
result += CROCKFORD_ALPHABET[value];
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Generate a deterministic `aff_\u003cULID\u003e` affinity key from an
|
|
43
|
+
* `(agentId, sessionId)` pair. The same pair always produces the same key,
|
|
44
|
+
* which is stable across restarts. Different pairs produce different keys
|
|
45
|
+
* with overwhelming probability.
|
|
46
|
+
*/
|
|
47
|
+
export function generateAffinityKey(agentId, sessionId) {
|
|
48
|
+
const hash = createHash('sha256').update(agentId).update('\0').update(sessionId).digest();
|
|
49
|
+
// Build a 128-bit buffer from the hash, clearing the top 5 bits of the
|
|
50
|
+
// first byte so the first Crockford base32 char is in [0-7].
|
|
51
|
+
const combined = Buffer.alloc(16);
|
|
52
|
+
combined[0] = (hash[0] ?? 0) & 0x07;
|
|
53
|
+
combined.set(hash.slice(1, 16), 1);
|
|
54
|
+
return `aff_${encodeBase32(combined, 26)}`;
|
|
55
|
+
}
|
|
56
|
+
export function isValidAffinityKey(key) {
|
|
57
|
+
return AFFINITY_KEY_REGEX.test(key);
|
|
58
|
+
}
|
|
59
|
+
const COMPACTION_SUMMARIZATION_SYSTEM_PROMPT = 'Summarize durable agent session history for future context. Preserve user goals, decisions, tool/command effects, files touched, errors, policy/approval events, and pending work. Do not include secrets.';
|
|
60
|
+
const TURN_PREFIX_SUMMARIZATION_PROMPT = `This is the PREFIX of a turn that was too large to keep. The SUFFIX (recent work) is retained.
|
|
61
|
+
|
|
62
|
+
Summarize the prefix to provide context for the retained suffix:
|
|
63
|
+
|
|
64
|
+
## Original Request
|
|
65
|
+
[What did the user ask for in this turn?]
|
|
66
|
+
|
|
67
|
+
## Early Progress
|
|
68
|
+
- [Key decisions and work done in the prefix]
|
|
69
|
+
|
|
70
|
+
## Context for Suffix
|
|
71
|
+
- [Information needed to understand the retained recent work]
|
|
72
|
+
|
|
73
|
+
Be concise. Focus on what's needed to understand the kept suffix.`;
|
|
21
74
|
export class StubFabricSession {
|
|
22
75
|
id;
|
|
23
76
|
agent;
|
|
@@ -49,6 +102,7 @@ export class StubFabricSession {
|
|
|
49
102
|
/** Set when this session has registered its sandbox in the in-process ref registry. */
|
|
50
103
|
ownedSandboxRefId;
|
|
51
104
|
costBudget;
|
|
105
|
+
skillCache = new Map();
|
|
52
106
|
constructor(agent, id, options = {}) {
|
|
53
107
|
this.agent = agent;
|
|
54
108
|
this.id = id;
|
|
@@ -91,11 +145,33 @@ export class StubFabricSession {
|
|
|
91
145
|
if (promptSubscriber)
|
|
92
146
|
this.eventSubscribers.add(promptSubscriber);
|
|
93
147
|
try {
|
|
148
|
+
await this.repairDanglingToolCalls();
|
|
94
149
|
await this.emit('prompt_start', { text, ...(role ? { role: role.name } : {}) });
|
|
95
150
|
const sandbox = createScopedSandboxEnv(await this.sandbox, options.cwd);
|
|
96
151
|
const tools = this.resolveTools(sandbox, options.tools, options.commands);
|
|
97
|
-
|
|
98
|
-
|
|
152
|
+
// Submission-correlated prompts are idempotent by submission id: a
|
|
153
|
+
// recovered attempt whose input entry already landed (crash after the
|
|
154
|
+
// canonical write) resumes from history instead of double-applying.
|
|
155
|
+
let inputAlreadyApplied = false;
|
|
156
|
+
if (options.submission) {
|
|
157
|
+
const prior = await this.history();
|
|
158
|
+
const submissionId = options.submission.submissionId;
|
|
159
|
+
inputAlreadyApplied = (prior.entries ?? []).some((entry) => entry.type === 'user_prompt' && entry.data?.submissionId === submissionId);
|
|
160
|
+
}
|
|
161
|
+
if (!inputAlreadyApplied) {
|
|
162
|
+
await this.recordEntry('user_prompt', {
|
|
163
|
+
text,
|
|
164
|
+
...(role ? { role: role.name } : {}),
|
|
165
|
+
...(options.submission ? { submissionId: options.submission.submissionId } : {}),
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
// Input-applied boundary: the canonical input entry is durable and no
|
|
169
|
+
// provider work has started. The submission runner's input-applied
|
|
170
|
+
// marker CAS hangs off this callback; its failure aborts the turn
|
|
171
|
+
// (the attempt lost ownership).
|
|
172
|
+
await options.submission?.onInputApplied?.();
|
|
173
|
+
const sessionData = await this.history();
|
|
174
|
+
let messages = buildModelMessagesFromHistory(sessionData, role);
|
|
99
175
|
if (!messages.some((message) => message.role === 'user' && message.content === text))
|
|
100
176
|
messages.push({ role: 'user', content: text });
|
|
101
177
|
messages = await this.autoCompactIfNeeded(messages, role, options);
|
|
@@ -105,14 +181,19 @@ export class StubFabricSession {
|
|
|
105
181
|
await this.emit('prompt_end', { text, result: results });
|
|
106
182
|
return validateResult(results, options.result, options.resultExtraction);
|
|
107
183
|
}
|
|
184
|
+
// Use tool-based result extraction when a validator is provided and the caller
|
|
185
|
+
// has not explicitly opted into delimiter-based extraction.
|
|
186
|
+
const useResultTools = options.result !== undefined && options.resultExtraction === undefined;
|
|
108
187
|
const runtime = options.loopRuntime ?? this.options.loopRuntime ?? defaultLoopRuntime;
|
|
109
188
|
const runRuntime = () => runtime.runPrompt({
|
|
110
189
|
sessionId: this.id,
|
|
190
|
+
...(sessionData.affinityKey !== undefined ? { affinityKey: sessionData.affinityKey } : {}),
|
|
111
191
|
text,
|
|
112
192
|
messages,
|
|
113
193
|
tools,
|
|
114
194
|
sandbox,
|
|
115
195
|
...(typeof (options.model ?? this.options.model ?? this.agent.model) === 'string' ? { model: (options.model ?? this.options.model ?? this.agent.model) } : {}),
|
|
196
|
+
...((options.thinkingLevel ?? this.options.thinkingLevel ?? this.agent.thinkingLevel) !== undefined ? { thinkingLevel: options.thinkingLevel ?? this.options.thinkingLevel ?? this.agent.thinkingLevel } : {}),
|
|
116
197
|
...((options.modelProvider ?? this.options.modelProvider) !== undefined ? { modelProvider: options.modelProvider ?? this.options.modelProvider } : {}),
|
|
117
198
|
...(options.maxIterations !== undefined ? { maxIterations: options.maxIterations } : {}),
|
|
118
199
|
...(options.modelTimeoutMs !== undefined ? { modelTimeoutMs: options.modelTimeoutMs } : {}),
|
|
@@ -125,7 +206,10 @@ export class StubFabricSession {
|
|
|
125
206
|
costBudget: this.costBudget,
|
|
126
207
|
requestCostLimitApproval: (ctx) => this.requestCostLimitApproval(ctx, options.approvalTimeoutMs ?? this.options.approvalTimeoutMs, options.onApproval),
|
|
127
208
|
} : {}),
|
|
128
|
-
|
|
209
|
+
...(useResultTools ? { resultValidator: options.result } : {}),
|
|
210
|
+
agentId: this.agent.id,
|
|
211
|
+
...(this.options.tenantId !== undefined ? { tenantId: this.options.tenantId } : {}),
|
|
212
|
+
executeToolCalls: (calls, policy, approvalTimeoutMs, attribution) => this.executeToolCalls(calls, tools, sandbox, policy, approvalTimeoutMs, options.onApproval, attribution),
|
|
129
213
|
recordEntry: (type, data) => this.recordEntry(type, data),
|
|
130
214
|
emit: (type, data) => this.emit(type, data),
|
|
131
215
|
});
|
|
@@ -147,6 +231,10 @@ export class StubFabricSession {
|
|
|
147
231
|
messages = buildModelMessagesFromHistory(await this.history(), role);
|
|
148
232
|
result = await runRuntime();
|
|
149
233
|
}
|
|
234
|
+
// If the loop produced a validated result via finish/give_up tools, return it directly.
|
|
235
|
+
if (result.validatedResult !== undefined) {
|
|
236
|
+
return result.validatedResult;
|
|
237
|
+
}
|
|
150
238
|
const resultRetries = options.resultRetries ?? (options.result ? 1 : 0);
|
|
151
239
|
for (let attempt = 0; attempt <= resultRetries; attempt += 1) {
|
|
152
240
|
try {
|
|
@@ -218,7 +306,8 @@ export class StubFabricSession {
|
|
|
218
306
|
if (!skill)
|
|
219
307
|
throw new FabricError({ code: 'SKILL_NOT_FOUND', message: `Skill not found: ${name}`, details: { skill: name } });
|
|
220
308
|
const argsText = options.args ? `\n\nArguments:\n${JSON.stringify(options.args, null, 2)}` : '';
|
|
221
|
-
const
|
|
309
|
+
const packagedText = skill.packaged ? formatPackagedSkillResourceList(skill.packaged) : '';
|
|
310
|
+
const result = await this.prompt(`${skill.content ?? ''}${argsText}${packagedText}`, options);
|
|
222
311
|
await this.recordEntry('skill_end', { skill: name });
|
|
223
312
|
await this.emit('skill_end', { skill: name });
|
|
224
313
|
return result;
|
|
@@ -380,10 +469,22 @@ export class StubFabricSession {
|
|
|
380
469
|
const timeout = clampCommandTimeout(options.timeout, policy);
|
|
381
470
|
if (timeout !== undefined)
|
|
382
471
|
execOptions.timeout = timeout;
|
|
383
|
-
await this.recordEntry('shell_command', { command });
|
|
384
472
|
await this.emit('command_start', { command });
|
|
385
473
|
const result = await sandbox.exec(command, execOptions);
|
|
386
|
-
|
|
474
|
+
const redactedCommand = redactEnvValues(command, options.env);
|
|
475
|
+
const toolCallId = `shell-${Date.now()}`;
|
|
476
|
+
await this.recordEntry('user_prompt', { text: `Run shell command: ${redactedCommand}` });
|
|
477
|
+
await this.recordEntry('tool_call', { tool: 'bash', input: { command: redactedCommand }, toolCallId, synthetic: true });
|
|
478
|
+
await this.recordEntry('tool_result', {
|
|
479
|
+
tool: 'bash',
|
|
480
|
+
output: {
|
|
481
|
+
stdout: redactEnvValues(result.stdout, options.env),
|
|
482
|
+
stderr: redactEnvValues(result.stderr, options.env),
|
|
483
|
+
exitCode: result.exitCode,
|
|
484
|
+
},
|
|
485
|
+
toolCallId,
|
|
486
|
+
synthetic: true,
|
|
487
|
+
});
|
|
387
488
|
await this.emit('command_end', { command, exitCode: result.exitCode, ...(result.durationMs !== undefined ? { durationMs: result.durationMs } : {}) });
|
|
388
489
|
return result;
|
|
389
490
|
}
|
|
@@ -439,6 +540,9 @@ export class StubFabricSession {
|
|
|
439
540
|
async history() {
|
|
440
541
|
return this.ensureData();
|
|
441
542
|
}
|
|
543
|
+
async createSignalEntry(data) {
|
|
544
|
+
return this.recordEntry('signal', data);
|
|
545
|
+
}
|
|
442
546
|
async sandboxRef(options) {
|
|
443
547
|
const sandbox = await this.sandbox;
|
|
444
548
|
const ref = registerSandbox(sandbox, { ownerSessionId: this.id });
|
|
@@ -521,13 +625,39 @@ export class StubFabricSession {
|
|
|
521
625
|
async compact(options = {}) {
|
|
522
626
|
const history = await this.history();
|
|
523
627
|
const entries = history.entries ?? [];
|
|
524
|
-
const
|
|
525
|
-
const
|
|
526
|
-
const
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
628
|
+
const settings = this.options.compaction;
|
|
629
|
+
const explicitKeepRecentEntries = options.keepRecentEntries ?? settings?.keepRecentEntries;
|
|
630
|
+
const explicitKeepRecentTokens = options.keepRecentTokens ?? settings?.keepRecentTokens;
|
|
631
|
+
let compacted = [];
|
|
632
|
+
let firstKeptEntry;
|
|
633
|
+
let prep;
|
|
634
|
+
if (explicitKeepRecentEntries !== undefined) {
|
|
635
|
+
// Backward-compatible count-based cut.
|
|
636
|
+
const compactedCount = Math.max(0, entries.length - explicitKeepRecentEntries);
|
|
637
|
+
firstKeptEntry = compactedCount > 0 ? entries[compactedCount] : undefined;
|
|
638
|
+
compacted = compactedCount > 0 ? entries.slice(0, compactedCount) : [];
|
|
639
|
+
}
|
|
640
|
+
else {
|
|
641
|
+
const effectiveKeepTokens = explicitKeepRecentTokens ?? 8_000;
|
|
642
|
+
prep = prepareCompaction(entries, effectiveKeepTokens);
|
|
643
|
+
if (prep && prep.firstKeptIndex > 0) {
|
|
644
|
+
compacted = [...prep.messagesToSummarize, ...prep.turnPrefixMessages];
|
|
645
|
+
firstKeptEntry = entries[prep.firstKeptIndex];
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
const fileRefs = prep ? prep.fileRefs : collectFileReferences(compacted);
|
|
649
|
+
const summaryInput = prep ?? {
|
|
650
|
+
firstKeptIndex: firstKeptEntry ? entries.indexOf(firstKeptEntry) : 0,
|
|
651
|
+
messagesToSummarize: compacted,
|
|
652
|
+
turnPrefixMessages: [],
|
|
653
|
+
isSplitTurn: false,
|
|
654
|
+
fileRefs,
|
|
655
|
+
};
|
|
656
|
+
const baseSummary = options.summary ?? (options.generateSummary
|
|
657
|
+
? await this.generateCompactionSummary(summaryInput)
|
|
658
|
+
: summarizeEntries(compacted));
|
|
530
659
|
const summary = appendFileReferenceTags(baseSummary, fileRefs);
|
|
660
|
+
const compactedEntries = compacted.length;
|
|
531
661
|
const entry = await this.recordEntry('compaction', {
|
|
532
662
|
summary,
|
|
533
663
|
compactedEntries,
|
|
@@ -549,6 +679,7 @@ export class StubFabricSession {
|
|
|
549
679
|
summary,
|
|
550
680
|
compactedEntries,
|
|
551
681
|
...(firstKeptEntry ? { firstKeptEntryId: firstKeptEntry.id } : {}),
|
|
682
|
+
details: { readFiles: fileRefs.readFiles, modifiedFiles: fileRefs.modifiedFiles },
|
|
552
683
|
};
|
|
553
684
|
}
|
|
554
685
|
async autoCompactIfNeeded(messages, role, options) {
|
|
@@ -559,18 +690,26 @@ export class StubFabricSession {
|
|
|
559
690
|
return messages;
|
|
560
691
|
const compactAtTokens = options.compactAtTokens ?? settings?.compactAtTokens;
|
|
561
692
|
const reserveTokens = options.reserveTokens ?? settings?.reserveTokens;
|
|
562
|
-
const keepRecentEntries = options.compactionKeepRecentEntries ?? settings?.keepRecentEntries
|
|
693
|
+
const keepRecentEntries = options.compactionKeepRecentEntries ?? settings?.keepRecentEntries;
|
|
563
694
|
const contextWindowTokens = options.contextWindowTokens ?? this.resolveModelContextWindow(options);
|
|
564
695
|
const budget = evaluateContextBudget(messages, {
|
|
565
696
|
...(contextWindowTokens !== undefined ? { contextWindowTokens } : {}),
|
|
566
697
|
...(compactAtTokens !== undefined ? { compactAtTokens } : {}),
|
|
567
698
|
...(reserveTokens !== undefined ? { reserveTokens } : {}),
|
|
699
|
+
...(settings?.keepRecentTokens !== undefined ? { keepRecentTokens: settings.keepRecentTokens } : {}),
|
|
568
700
|
});
|
|
569
701
|
if (!budget.shouldCompact)
|
|
570
702
|
return messages;
|
|
571
703
|
const messagesBefore = messages.length;
|
|
572
704
|
const tokensBefore = budget.estimatedTokens;
|
|
573
|
-
|
|
705
|
+
const compactOpts = { generateSummary: true, reason: 'threshold', tokensBefore, messagesBefore };
|
|
706
|
+
if (keepRecentEntries !== undefined) {
|
|
707
|
+
compactOpts.keepRecentEntries = keepRecentEntries;
|
|
708
|
+
}
|
|
709
|
+
else {
|
|
710
|
+
compactOpts.keepRecentTokens = settings?.keepRecentTokens ?? budget.keepRecentTokens ?? 8_000;
|
|
711
|
+
}
|
|
712
|
+
await this.compact(compactOpts);
|
|
574
713
|
const compacted = buildModelMessagesFromHistory(await this.history(), role);
|
|
575
714
|
return compacted;
|
|
576
715
|
}
|
|
@@ -591,23 +730,29 @@ export class StubFabricSession {
|
|
|
591
730
|
return undefined;
|
|
592
731
|
}
|
|
593
732
|
}
|
|
594
|
-
async generateCompactionSummary(
|
|
733
|
+
async generateCompactionSummary(prep) {
|
|
734
|
+
if (prep.isSplitTurn && prep.turnPrefixMessages.length > 0) {
|
|
735
|
+
const [historyResult, prefixResult] = await Promise.all([
|
|
736
|
+
this.summarizeEntrySet(prep.messagesToSummarize),
|
|
737
|
+
this.summarizeEntrySet(prep.turnPrefixMessages, TURN_PREFIX_SUMMARIZATION_PROMPT),
|
|
738
|
+
]);
|
|
739
|
+
return `${historyResult}\n\n---\n\n**Turn Context (split turn):**\n\n${prefixResult}`;
|
|
740
|
+
}
|
|
741
|
+
return this.summarizeEntrySet(prep.messagesToSummarize);
|
|
742
|
+
}
|
|
743
|
+
async summarizeEntrySet(entries, prefixPrompt) {
|
|
595
744
|
if (entries.length === 0)
|
|
596
745
|
return summarizeEntries(entries);
|
|
597
746
|
const deterministic = summarizeEntries(entries);
|
|
598
747
|
const provider = this.options.modelProvider ?? defaultModelProvider;
|
|
599
748
|
try {
|
|
749
|
+
const serialized = JSON.stringify(entries.map((entry) => ({ type: entry.type, timestamp: entry.timestamp, data: entry.data ?? {} })).slice(0, 200));
|
|
750
|
+
const userContent = prefixPrompt ? `${serialized}\n\n${prefixPrompt}` : serialized;
|
|
600
751
|
const response = await generateWithRuntime(provider, {
|
|
601
752
|
...(this.agent.model !== undefined ? { model: this.agent.model } : {}),
|
|
602
753
|
messages: [
|
|
603
|
-
{
|
|
604
|
-
|
|
605
|
-
content: 'Summarize durable agent session history for future context. Preserve user goals, decisions, tool/command effects, files touched, errors, policy/approval events, and pending work. Do not include secrets.',
|
|
606
|
-
},
|
|
607
|
-
{
|
|
608
|
-
role: 'user',
|
|
609
|
-
content: JSON.stringify(entries.map((entry) => ({ type: entry.type, timestamp: entry.timestamp, data: entry.data ?? {} })).slice(0, 200)),
|
|
610
|
-
},
|
|
754
|
+
{ role: 'system', content: COMPACTION_SUMMARIZATION_SYSTEM_PROMPT },
|
|
755
|
+
{ role: 'user', content: userContent },
|
|
611
756
|
],
|
|
612
757
|
}, { retries: 1, retryDelayMs: 250 });
|
|
613
758
|
return response.message?.content?.trim() || deterministic;
|
|
@@ -709,7 +854,7 @@ export class StubFabricSession {
|
|
|
709
854
|
const scoped = createScopedSandboxEnv(env, this.options.cwd);
|
|
710
855
|
return wrapWithPolicy(scoped, this.options.policy, this.options.bypassPolicyEnforcement);
|
|
711
856
|
}
|
|
712
|
-
async executeToolCalls(calls, tools, sandbox, callPolicy, approvalTimeoutMs, approvalHandler) {
|
|
857
|
+
async executeToolCalls(calls, tools, sandbox, callPolicy, approvalTimeoutMs, approvalHandler, attribution) {
|
|
713
858
|
const results = [];
|
|
714
859
|
const policy = callPolicy ?? this.options.policy;
|
|
715
860
|
for (const call of calls) {
|
|
@@ -781,13 +926,13 @@ export class StubFabricSession {
|
|
|
781
926
|
continue;
|
|
782
927
|
}
|
|
783
928
|
}
|
|
784
|
-
await this.recordEntry('tool_call', { tool: call.tool, input: call.input, ...(tool?.metadata?.effect ? { effect: tool.metadata.effect } : {}), ...(call.id ? { toolCallId: call.id } : {}) });
|
|
785
|
-
await this.emit('tool_start', { tool: call.tool });
|
|
929
|
+
await this.recordEntry('tool_call', { tool: call.tool, input: call.input, ...(tool?.metadata?.effect ? { effect: tool.metadata.effect } : {}), ...(call.id ? { toolCallId: call.id } : {}), ...(attribution ? { attribution: attribution } : {}) });
|
|
930
|
+
await this.emit('tool_start', { tool: call.tool, ...(attribution ? { attribution: attribution } : {}) });
|
|
786
931
|
if (!tool?.execute) {
|
|
787
932
|
const error = `Tool is not available: ${call.tool}`;
|
|
788
933
|
results.push({ tool: call.tool, input: call.input, error, ...(call.id ? { id: call.id } : {}) });
|
|
789
934
|
await this.recordEntry('error', { tool: call.tool, error, approvalRequired: false });
|
|
790
|
-
await this.emit('tool_end', { tool: call.tool, error });
|
|
935
|
+
await this.emit('tool_end', { tool: call.tool, error, ...(attribution ? { attribution: attribution } : {}) });
|
|
791
936
|
continue;
|
|
792
937
|
}
|
|
793
938
|
try {
|
|
@@ -796,14 +941,14 @@ export class StubFabricSession {
|
|
|
796
941
|
const output = await tool.execute(input, { sandbox });
|
|
797
942
|
const durationMs = Date.now() - startedAt;
|
|
798
943
|
results.push({ tool: call.tool, input, output, ...(call.id ? { id: call.id } : {}) });
|
|
799
|
-
await this.recordEntry('tool_result', { tool: call.tool, output: output, durationMs, ...(call.id ? { toolCallId: call.id } : {}) });
|
|
800
|
-
await this.emit('tool_end', { tool: call.tool, durationMs });
|
|
944
|
+
await this.recordEntry('tool_result', { tool: call.tool, output: output, durationMs, ...(call.id ? { toolCallId: call.id } : {}), ...(attribution ? { attribution: attribution } : {}) });
|
|
945
|
+
await this.emit('tool_end', { tool: call.tool, durationMs, ...(attribution ? { attribution: attribution } : {}) });
|
|
801
946
|
}
|
|
802
947
|
catch (error) {
|
|
803
948
|
const message = error instanceof Error ? error.message : String(error);
|
|
804
949
|
results.push({ tool: call.tool, input: call.input, error: message, ...(call.id ? { id: call.id } : {}) });
|
|
805
950
|
await this.recordEntry('error', { tool: call.tool, error: message });
|
|
806
|
-
await this.emit('tool_end', { tool: call.tool, error: message });
|
|
951
|
+
await this.emit('tool_end', { tool: call.tool, error: message, ...(attribution ? { attribution: attribution } : {}) });
|
|
807
952
|
}
|
|
808
953
|
}
|
|
809
954
|
return results;
|
|
@@ -929,7 +1074,7 @@ export class StubFabricSession {
|
|
|
929
1074
|
resolveTools(sandbox, promptTools = [], promptCommands = []) {
|
|
930
1075
|
const tools = new Map();
|
|
931
1076
|
const commandToolOptions = this.options.resolveSecret ? { resolveSecret: this.options.resolveSecret } : {};
|
|
932
|
-
for (const tool of createBuiltinTools(sandbox))
|
|
1077
|
+
for (const tool of createBuiltinTools(sandbox, this.options.packagedSkills))
|
|
933
1078
|
tools.set(tool.name, tool);
|
|
934
1079
|
for (const tool of createCommandTools(this.options.commands ?? [], commandToolOptions))
|
|
935
1080
|
tools.set(tool.name, tool);
|
|
@@ -939,8 +1084,40 @@ export class StubFabricSession {
|
|
|
939
1084
|
tools.set(tool.name, tool);
|
|
940
1085
|
for (const tool of promptTools)
|
|
941
1086
|
tools.set(tool.name, tool);
|
|
1087
|
+
const skills = this.options.skills ?? [];
|
|
1088
|
+
if (skills.length > 0) {
|
|
1089
|
+
const skillNames = skills.map((skill) => skill.name);
|
|
1090
|
+
const activateSkillTool = createActivateSkillTool(skillNames, (name) => this.activateSkillForTool(name));
|
|
1091
|
+
tools.set(activateSkillTool.name, activateSkillTool);
|
|
1092
|
+
}
|
|
942
1093
|
return tools;
|
|
943
1094
|
}
|
|
1095
|
+
async activateSkillForTool(name) {
|
|
1096
|
+
const cached = this.skillCache.get(name);
|
|
1097
|
+
if (cached !== undefined)
|
|
1098
|
+
return cached;
|
|
1099
|
+
const skill = this.resolveSkill(name);
|
|
1100
|
+
if (!skill)
|
|
1101
|
+
return `Error: Skill "${name}" not found.`;
|
|
1102
|
+
let content = skill.content;
|
|
1103
|
+
if (content === undefined && skill.path) {
|
|
1104
|
+
try {
|
|
1105
|
+
const sandbox = await this.sandbox;
|
|
1106
|
+
content = await sandbox.readFile(skill.path);
|
|
1107
|
+
}
|
|
1108
|
+
catch {
|
|
1109
|
+
return `Error: Skill "${name}" found but could not read content from path "${skill.path}".`;
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
if (content === undefined) {
|
|
1113
|
+
return `Error: Skill "${name}" found but has no content or readable path.`;
|
|
1114
|
+
}
|
|
1115
|
+
if (skill.packaged) {
|
|
1116
|
+
content += formatPackagedSkillResourceList(skill.packaged);
|
|
1117
|
+
}
|
|
1118
|
+
this.skillCache.set(name, content);
|
|
1119
|
+
return content;
|
|
1120
|
+
}
|
|
944
1121
|
resolveSkill(name) {
|
|
945
1122
|
const normalized = normalizeSkillReference(name);
|
|
946
1123
|
return this.options.skills?.find((skill) => {
|
|
@@ -986,10 +1163,22 @@ export class StubFabricSession {
|
|
|
986
1163
|
retryable: false,
|
|
987
1164
|
});
|
|
988
1165
|
}
|
|
1166
|
+
if (!existing.affinityKey) {
|
|
1167
|
+
existing.affinityKey = generateAffinityKey(this.agent.id, this.id);
|
|
1168
|
+
await this.store.save(existing);
|
|
1169
|
+
}
|
|
989
1170
|
return existing;
|
|
990
1171
|
}
|
|
991
1172
|
const now = new Date().toISOString();
|
|
992
|
-
const data = {
|
|
1173
|
+
const data = {
|
|
1174
|
+
id: this.id,
|
|
1175
|
+
agentId: this.agent.id,
|
|
1176
|
+
createdAt: now,
|
|
1177
|
+
updatedAt: now,
|
|
1178
|
+
events: [],
|
|
1179
|
+
entries: [],
|
|
1180
|
+
affinityKey: generateAffinityKey(this.agent.id, this.id),
|
|
1181
|
+
};
|
|
993
1182
|
if (this.options.metadata !== undefined)
|
|
994
1183
|
data.metadata = this.options.metadata;
|
|
995
1184
|
if (this.options.tenantId !== undefined)
|
|
@@ -997,6 +1186,61 @@ export class StubFabricSession {
|
|
|
997
1186
|
await this.store.save(data);
|
|
998
1187
|
return data;
|
|
999
1188
|
}
|
|
1189
|
+
/**
|
|
1190
|
+
* Settle dangling state left by an interrupted turn before applying new
|
|
1191
|
+
* input (v2 A2 — port of flue's repairTrailingPartialToolBatch).
|
|
1192
|
+
*
|
|
1193
|
+
* A crash/abort between recording a `tool_call` and its outcome leaves an
|
|
1194
|
+
* assistant tool_use with no tool_result, which providers reject on the
|
|
1195
|
+
* next turn. For each trailing dangling call this appends a synthetic
|
|
1196
|
+
* `tool_result` marked interrupted (never re-executes the tool — the real
|
|
1197
|
+
* outcome is unknown), settles trailing unfinished `task_start`s, and
|
|
1198
|
+
* appends one advisory `signal` entry so the model knows the previous turn
|
|
1199
|
+
* was cut short. Idempotent: a repaired window has no dangling calls.
|
|
1200
|
+
*/
|
|
1201
|
+
async repairDanglingToolCalls() {
|
|
1202
|
+
const data = await this.history();
|
|
1203
|
+
const path = SessionHistory.fromData(data).getActivePath();
|
|
1204
|
+
const dangling = findTrailingDanglingToolCalls(path);
|
|
1205
|
+
const unfinishedTasks = findTrailingUnfinishedTasks(path);
|
|
1206
|
+
if (dangling.length === 0 && unfinishedTasks.length === 0)
|
|
1207
|
+
return;
|
|
1208
|
+
for (const call of dangling) {
|
|
1209
|
+
const tool = typeof call.data?.tool === 'string' ? call.data.tool : 'unknown';
|
|
1210
|
+
const toolCallId = typeof call.data?.toolCallId === 'string' ? call.data.toolCallId : undefined;
|
|
1211
|
+
await this.recordEntry('tool_result', {
|
|
1212
|
+
tool,
|
|
1213
|
+
...(toolCallId ? { toolCallId } : {}),
|
|
1214
|
+
output: {
|
|
1215
|
+
type: 'interrupted',
|
|
1216
|
+
message: 'Tool execution was interrupted before completion. The outcome is unknown.',
|
|
1217
|
+
},
|
|
1218
|
+
isError: true,
|
|
1219
|
+
interrupted: true,
|
|
1220
|
+
synthetic: true,
|
|
1221
|
+
});
|
|
1222
|
+
await this.emit('tool_end', { tool, error: 'interrupted', synthetic: true });
|
|
1223
|
+
}
|
|
1224
|
+
for (const task of unfinishedTasks) {
|
|
1225
|
+
const taskId = typeof task.data?.taskId === 'string' ? task.data.taskId : 'unknown';
|
|
1226
|
+
await this.recordEntry('task_end', {
|
|
1227
|
+
taskId,
|
|
1228
|
+
status: 'interrupted',
|
|
1229
|
+
synthetic: true,
|
|
1230
|
+
// Keep the child transcript locatable from the settled record.
|
|
1231
|
+
...(typeof task.data?.agent === 'string' ? { agent: task.data.agent } : {}),
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1234
|
+
if (dangling.length > 0) {
|
|
1235
|
+
const tools = dangling
|
|
1236
|
+
.map((call) => (typeof call.data?.tool === 'string' ? call.data.tool : 'unknown'))
|
|
1237
|
+
.join(', ');
|
|
1238
|
+
await this.recordEntry('signal', {
|
|
1239
|
+
signalType: 'interrupted',
|
|
1240
|
+
content: `The previous turn was interrupted; ${dangling.length} tool call(s) did not complete and may or may not have taken effect: ${tools}. Verify their effects before repeating side-effecting operations.`,
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1000
1244
|
async recordEntry(type, data) {
|
|
1001
1245
|
await this.ensureData();
|
|
1002
1246
|
const current = await this.store.load(this.id);
|
|
@@ -1069,6 +1313,20 @@ function isJsonValue(value) {
|
|
|
1069
1313
|
return false;
|
|
1070
1314
|
return Object.values(value).every(isJsonValue);
|
|
1071
1315
|
}
|
|
1316
|
+
function redactEnvValues(text, env) {
|
|
1317
|
+
if (!env)
|
|
1318
|
+
return text;
|
|
1319
|
+
let result = text;
|
|
1320
|
+
for (const [key, value] of Object.entries(env)) {
|
|
1321
|
+
if (key.length > 0) {
|
|
1322
|
+
result = result.replaceAll(key, '[REDACTED]');
|
|
1323
|
+
}
|
|
1324
|
+
if (value.length > 0) {
|
|
1325
|
+
result = result.replaceAll(value, '[REDACTED]');
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
return result;
|
|
1329
|
+
}
|
|
1072
1330
|
function summarizeEntries(entries) {
|
|
1073
1331
|
if (entries.length === 0)
|
|
1074
1332
|
return 'No entries to compact.';
|
|
@@ -1108,6 +1366,77 @@ function appendFileReferenceTags(summary, refs) {
|
|
|
1108
1366
|
sections.push(`<modified-files>\n${refs.modifiedFiles.join('\n')}\n</modified-files>`);
|
|
1109
1367
|
return sections.join('\n\n');
|
|
1110
1368
|
}
|
|
1369
|
+
/** Valid cut points: user prompts and assistant messages. Never cut at tool_result. */
|
|
1370
|
+
function findValidCutPoints(entries, start, end) {
|
|
1371
|
+
const cutPoints = [];
|
|
1372
|
+
for (let i = start; i < end; i++) {
|
|
1373
|
+
const type = entries[i]?.type;
|
|
1374
|
+
if (type === 'user_prompt' || type === 'assistant_message') {
|
|
1375
|
+
cutPoints.push(i);
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
return cutPoints;
|
|
1379
|
+
}
|
|
1380
|
+
function findTurnStartIndex(entries, index, start) {
|
|
1381
|
+
for (let i = index; i >= start; i--) {
|
|
1382
|
+
if (entries[i]?.type === 'user_prompt')
|
|
1383
|
+
return i;
|
|
1384
|
+
}
|
|
1385
|
+
return -1;
|
|
1386
|
+
}
|
|
1387
|
+
function findCutPoint(entries, start, end, keepRecentTokens) {
|
|
1388
|
+
const cutPoints = findValidCutPoints(entries, start, end);
|
|
1389
|
+
if (cutPoints.length === 0) {
|
|
1390
|
+
return { firstKeptIndex: start, turnStartIndex: -1, isSplitTurn: false };
|
|
1391
|
+
}
|
|
1392
|
+
let accumulatedTokens = 0;
|
|
1393
|
+
let firstKeptIndex = start;
|
|
1394
|
+
for (let i = end - 1; i >= start; i--) {
|
|
1395
|
+
const entry = entries[i];
|
|
1396
|
+
if (!entry)
|
|
1397
|
+
continue;
|
|
1398
|
+
accumulatedTokens += estimateSessionEntryTokens(entry);
|
|
1399
|
+
if (accumulatedTokens >= keepRecentTokens) {
|
|
1400
|
+
for (const cutPoint of cutPoints) {
|
|
1401
|
+
if (cutPoint >= i) {
|
|
1402
|
+
firstKeptIndex = cutPoint;
|
|
1403
|
+
break;
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
break;
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
const isUserMessage = entries[firstKeptIndex]?.type === 'user_prompt';
|
|
1410
|
+
const turnStartIndex = isUserMessage ? -1 : findTurnStartIndex(entries, firstKeptIndex, start);
|
|
1411
|
+
return {
|
|
1412
|
+
firstKeptIndex,
|
|
1413
|
+
turnStartIndex,
|
|
1414
|
+
isSplitTurn: !isUserMessage && turnStartIndex !== -1,
|
|
1415
|
+
};
|
|
1416
|
+
}
|
|
1417
|
+
/** Pure function — no I/O. Finds the token-based cut point, extracts messages to summarize, and tracks file ops. */
|
|
1418
|
+
export function prepareCompaction(entries, keepRecentTokens, previousCompaction) {
|
|
1419
|
+
if (entries.length === 0)
|
|
1420
|
+
return undefined;
|
|
1421
|
+
const boundaryStart = previousCompaction ? previousCompaction.firstKeptIndex : 0;
|
|
1422
|
+
const boundaryEnd = entries.length;
|
|
1423
|
+
const cutPoint = findCutPoint(entries, boundaryStart, boundaryEnd, keepRecentTokens);
|
|
1424
|
+
if (cutPoint.firstKeptIndex <= boundaryStart)
|
|
1425
|
+
return undefined;
|
|
1426
|
+
const historyEnd = cutPoint.isSplitTurn ? cutPoint.turnStartIndex : cutPoint.firstKeptIndex;
|
|
1427
|
+
const messagesToSummarize = entries.slice(boundaryStart, historyEnd);
|
|
1428
|
+
const turnPrefixMessages = cutPoint.isSplitTurn
|
|
1429
|
+
? entries.slice(cutPoint.turnStartIndex, cutPoint.firstKeptIndex)
|
|
1430
|
+
: [];
|
|
1431
|
+
const fileRefs = collectFileReferences([...messagesToSummarize, ...turnPrefixMessages]);
|
|
1432
|
+
return {
|
|
1433
|
+
firstKeptIndex: cutPoint.firstKeptIndex,
|
|
1434
|
+
messagesToSummarize,
|
|
1435
|
+
turnPrefixMessages,
|
|
1436
|
+
isSplitTurn: cutPoint.isSplitTurn,
|
|
1437
|
+
fileRefs,
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1111
1440
|
function isReadOnlyFileTool(tool) {
|
|
1112
1441
|
return tool !== undefined && ['read', 'read_buffer', 'stat', 'readdir', 'exists', 'grep', 'glob'].includes(tool);
|
|
1113
1442
|
}
|