@bluecopa/harness 0.1.0-snapshot.101 → 0.1.0-snapshot.103
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/arc/app-adapter.d.ts +1 -1
- package/dist/arc/create-arc-agent.d.ts +1 -1
- package/dist/arc/create-arc-agent.js +383 -87
- package/dist/arc/create-arc-agent.js.map +1 -1
- package/dist/arc/profile-builder.d.ts +1 -1
- package/dist/arc/profile-builder.js.map +1 -1
- package/dist/loop/vercel-agent-loop.d.ts +1 -1
- package/dist/loop/vercel-agent-loop.js.map +1 -1
- package/dist/{types-g-3DvSSE.d.ts → types-BV-E1pvS.d.ts} +24 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as ArcLoopConfig, b as AgentMessage, A as ArcEvent, c as ArcRunResult, T as TraceEvent } from '../types-
|
|
1
|
+
import { a as ArcLoopConfig, b as AgentMessage, A as ArcEvent, c as ArcRunResult, T as TraceEvent } from '../types-BV-E1pvS.js';
|
|
2
2
|
import 'zod';
|
|
3
3
|
import 'ai';
|
|
4
4
|
|
|
@@ -231,13 +231,24 @@ var Remember = tool({
|
|
|
231
231
|
})
|
|
232
232
|
});
|
|
233
233
|
var ReadEpisode = tool({
|
|
234
|
-
description:
|
|
234
|
+
description: 'Retrieve prior episode context by ID. Default output is summary-first and includes structured output plus artifact handles when available. Set detail to "trace" or "artifacts" for richer detail.',
|
|
235
235
|
inputSchema: z.object({
|
|
236
236
|
id: z.string().describe("Episode ID to read"),
|
|
237
|
+
detail: z.enum(["summary", "trace", "artifacts"]).optional().describe("Level of detail to retrieve. Default: summary."),
|
|
238
|
+
artifactKey: z.string().optional().describe('Optional artifact handle to focus on when detail is "artifacts".'),
|
|
237
239
|
maxTokens: z.number().optional().describe("Max tokens to return (truncates from the end). Default: no limit.")
|
|
238
240
|
})
|
|
239
241
|
});
|
|
240
|
-
var
|
|
242
|
+
var ReadRollup = tool({
|
|
243
|
+
description: "Retrieve a rollup summary by ref or ID. Use to inspect compressed older history and discover which episode refs to hydrate next.",
|
|
244
|
+
inputSchema: z.object({
|
|
245
|
+
id: z.string().describe("Rollup ref or ID to read (for example: EE1)"),
|
|
246
|
+
detail: z.enum(["summary", "children"]).optional().describe("Whether to return just the rollup summary or include child refs/summaries. Default: summary."),
|
|
247
|
+
maxTokens: z.number().optional().describe("Max tokens to return (truncates from the end). Default: no limit.")
|
|
248
|
+
})
|
|
249
|
+
});
|
|
250
|
+
var orchestratorTools = { Thread, Check, Cancel, Remember, ReadEpisode, ReadRollup };
|
|
251
|
+
var processTools = { ...builtinTools, ReadEpisode };
|
|
241
252
|
tool({
|
|
242
253
|
description: "Ask the user a question and wait for their response.",
|
|
243
254
|
inputSchema: z.object({
|
|
@@ -320,6 +331,7 @@ var ContextWindow = class {
|
|
|
320
331
|
config;
|
|
321
332
|
turns = [];
|
|
322
333
|
episodes = [];
|
|
334
|
+
rollups = [];
|
|
323
335
|
cachedContextSize = null;
|
|
324
336
|
constructor(config) {
|
|
325
337
|
this.config = config;
|
|
@@ -351,6 +363,7 @@ var ContextWindow = class {
|
|
|
351
363
|
const contextWindowSize = await this.getContextWindowSize();
|
|
352
364
|
const totalBudget = contextWindowSize - this.config.outputReserve;
|
|
353
365
|
this.episodes = await this.config.episodeStore.getEpisodesByTask(this.config.taskId);
|
|
366
|
+
this.rollups = this.config.rollupStore ? await this.config.rollupStore.getRollupsByTask(this.config.taskId) : [];
|
|
354
367
|
const filesBeingTouched = this.extractFilesBeingTouched();
|
|
355
368
|
const systemTokens = estimateTokens(this.config.systemPrompt);
|
|
356
369
|
const memoryBudget = Math.min(Math.floor(totalBudget * 0.15), 4e3);
|
|
@@ -367,7 +380,7 @@ var ContextWindow = class {
|
|
|
367
380
|
totalUsed = systemTokens + memoryTokens + episodeTokens + conversationTokens;
|
|
368
381
|
}
|
|
369
382
|
const episodeBudget = Math.floor(totalBudget * 0.2);
|
|
370
|
-
if (episodeTokens > episodeBudget && this.episodes.length > 10) {
|
|
383
|
+
if (episodeTokens > episodeBudget && this.episodes.length > 10 && this.rollups.length === 0) {
|
|
371
384
|
const grouped = this.groupEpisodeSummaries();
|
|
372
385
|
episodeTokens = estimateMessagesTokens(grouped);
|
|
373
386
|
episodeMessages.length = 0;
|
|
@@ -410,14 +423,13 @@ var ContextWindow = class {
|
|
|
410
423
|
const contextWindowSize = await this.getContextWindowSize();
|
|
411
424
|
const totalBudget = contextWindowSize - this.config.outputReserve;
|
|
412
425
|
const systemTokens = estimateTokens(this.config.systemPrompt);
|
|
426
|
+
this.episodes = await this.config.episodeStore.getEpisodesByTask(this.config.taskId);
|
|
427
|
+
this.rollups = this.config.rollupStore ? await this.config.rollupStore.getRollupsByTask(this.config.taskId) : [];
|
|
413
428
|
let conversationTokens = 0;
|
|
414
429
|
for (const turn of this.turns) {
|
|
415
430
|
conversationTokens += turn.tokenEstimate;
|
|
416
431
|
}
|
|
417
|
-
|
|
418
|
-
for (const ep of this.episodes) {
|
|
419
|
-
episodeTokens += estimateTokens(ep.summary);
|
|
420
|
-
}
|
|
432
|
+
const episodeTokens = estimateMessagesTokens(this.buildEpisodeSummaries());
|
|
421
433
|
return {
|
|
422
434
|
limit: totalBudget,
|
|
423
435
|
used: systemTokens + conversationTokens + episodeTokens,
|
|
@@ -451,14 +463,40 @@ var ContextWindow = class {
|
|
|
451
463
|
}
|
|
452
464
|
buildEpisodeSummaries() {
|
|
453
465
|
if (this.episodes.length === 0) return [];
|
|
454
|
-
const
|
|
455
|
-
|
|
466
|
+
const recentEpisodes = this.episodes.slice(-5);
|
|
467
|
+
const recentStartIndex = recentEpisodes[0]?.index ?? Number.MAX_SAFE_INTEGER;
|
|
468
|
+
const rollupLines = this.buildRollupFrontier(recentStartIndex).map((rollup) => `${rollup.ref} [E${rollup.rangeStartIndex}-E${rollup.rangeEndIndex}]:
|
|
469
|
+
${rollup.summary}`);
|
|
470
|
+
const coveredEpisodeIds = new Set(
|
|
471
|
+
this.buildRollupFrontier(recentStartIndex).flatMap((rollup) => rollup.childEpisodeIds)
|
|
472
|
+
);
|
|
473
|
+
const olderLeafLines = this.episodes.filter((episode) => episode.index < recentStartIndex && !coveredEpisodeIds.has(episode.id)).map((episode) => `Episode E${episode.index} [${episode.id}] (${episode.success ? "ok" : "failed"}):
|
|
474
|
+
${episode.summary}`);
|
|
475
|
+
const recentLines = recentEpisodes.map((episode) => `Episode E${episode.index} [${episode.id}] (${episode.success ? "ok" : "failed"}):
|
|
476
|
+
${episode.summary}`);
|
|
477
|
+
const episodeText = [...rollupLines, ...olderLeafLines, ...recentLines].join("\n\n");
|
|
456
478
|
return [{
|
|
457
479
|
role: "system",
|
|
458
480
|
content: `Prior episodes for this task:
|
|
459
481
|
${episodeText}`
|
|
460
482
|
}];
|
|
461
483
|
}
|
|
484
|
+
buildRollupFrontier(beforeEpisodeIndex) {
|
|
485
|
+
if (this.rollups.length === 0) {
|
|
486
|
+
return [];
|
|
487
|
+
}
|
|
488
|
+
const eligible = this.rollups.filter((rollup) => rollup.rangeEndIndex < beforeEpisodeIndex).sort((a, b) => b.level - a.level || a.rangeStartIndex - b.rangeStartIndex);
|
|
489
|
+
const selected = [];
|
|
490
|
+
for (const rollup of eligible) {
|
|
491
|
+
const overlaps = selected.some(
|
|
492
|
+
(existing) => !(rollup.rangeEndIndex < existing.rangeStartIndex || rollup.rangeStartIndex > existing.rangeEndIndex)
|
|
493
|
+
);
|
|
494
|
+
if (!overlaps) {
|
|
495
|
+
selected.push(rollup);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return selected.sort((a, b) => a.rangeStartIndex - b.rangeStartIndex);
|
|
499
|
+
}
|
|
462
500
|
buildTurnMessages() {
|
|
463
501
|
const messages = [];
|
|
464
502
|
for (const turn of this.turns) {
|
|
@@ -732,6 +770,96 @@ ${repeatedErrors.map(([action, n]) => ` "${action}" failed ${n}x`).join("\n")}`
|
|
|
732
770
|
return { removed, merged };
|
|
733
771
|
}
|
|
734
772
|
};
|
|
773
|
+
var ROLLUP_GROUP_SIZE = 6;
|
|
774
|
+
function nextRollupRef(level, existing) {
|
|
775
|
+
const prefix = "E".repeat(level + 1);
|
|
776
|
+
const maxIndex = existing.filter((rollup) => rollup.level === level).map((rollup) => Number(rollup.ref.slice(prefix.length))).filter((value) => Number.isFinite(value)).reduce((max, value) => Math.max(max, value), 0);
|
|
777
|
+
return `${prefix}${maxIndex + 1}`;
|
|
778
|
+
}
|
|
779
|
+
function summarizeEpisodeBatch(episodes) {
|
|
780
|
+
const files = [...new Set(episodes.flatMap((episode) => [...episode.filesRead, ...episode.filesModified]))];
|
|
781
|
+
const childRefs = episodes.map((episode) => `E${episode.index}`);
|
|
782
|
+
const actions = episodes.map((episode) => `- E${episode.index}: ${episode.threadAction} (${episode.success ? "ok" : "failed"})`);
|
|
783
|
+
return [
|
|
784
|
+
`Summary of [${childRefs[0]}-${childRefs[childRefs.length - 1]}]`,
|
|
785
|
+
...actions,
|
|
786
|
+
...files.length > 0 ? [`Key files: ${files.join(", ")}`] : []
|
|
787
|
+
].join("\n");
|
|
788
|
+
}
|
|
789
|
+
function summarizeRollupBatch(rollups) {
|
|
790
|
+
const childRefs = rollups.map((rollup) => rollup.ref);
|
|
791
|
+
const files = [...new Set(rollups.flatMap((rollup) => rollup.keyFiles))];
|
|
792
|
+
const summaries = rollups.map((rollup) => `- ${rollup.ref}: ${rollup.summary.split("\n")[0] ?? rollup.summary}`);
|
|
793
|
+
return [
|
|
794
|
+
`Summary of [${childRefs[0]}-${childRefs[childRefs.length - 1]}]`,
|
|
795
|
+
...summaries,
|
|
796
|
+
...files.length > 0 ? [`Key files: ${files.join(", ")}`] : []
|
|
797
|
+
].join("\n");
|
|
798
|
+
}
|
|
799
|
+
async function buildEpisodeRollups(episodes, sessionId, rollupStore) {
|
|
800
|
+
const sortedEpisodes = [...episodes].sort((a, b) => a.index - b.index);
|
|
801
|
+
let rollups = await rollupStore.getRollupsBySession(sessionId);
|
|
802
|
+
const coveredEpisodeIds = new Set(rollups.flatMap((rollup) => rollup.childEpisodeIds));
|
|
803
|
+
const uncoveredEpisodes = sortedEpisodes.filter((episode) => !coveredEpisodeIds.has(episode.id));
|
|
804
|
+
const level1Chunks = [];
|
|
805
|
+
for (let index = 0; index + ROLLUP_GROUP_SIZE <= uncoveredEpisodes.length; index += ROLLUP_GROUP_SIZE) {
|
|
806
|
+
level1Chunks.push(uncoveredEpisodes.slice(index, index + ROLLUP_GROUP_SIZE));
|
|
807
|
+
}
|
|
808
|
+
for (const chunk of level1Chunks) {
|
|
809
|
+
const rollup = {
|
|
810
|
+
id: randomUUID(),
|
|
811
|
+
ref: nextRollupRef(1, rollups),
|
|
812
|
+
taskId: chunk[0].taskId,
|
|
813
|
+
sessionId,
|
|
814
|
+
level: 1,
|
|
815
|
+
summary: summarizeEpisodeBatch(chunk),
|
|
816
|
+
childEpisodeIds: chunk.map((episode) => episode.id),
|
|
817
|
+
childRollupIds: [],
|
|
818
|
+
rangeStartIndex: chunk[0].index,
|
|
819
|
+
rangeEndIndex: chunk[chunk.length - 1].index,
|
|
820
|
+
keyFiles: [...new Set(chunk.flatMap((episode) => [...episode.filesRead, ...episode.filesModified]))],
|
|
821
|
+
createdAt: Date.now()
|
|
822
|
+
};
|
|
823
|
+
await rollupStore.addRollup(rollup);
|
|
824
|
+
rollups = [...rollups, rollup];
|
|
825
|
+
}
|
|
826
|
+
let level = 1;
|
|
827
|
+
while (true) {
|
|
828
|
+
const allRollups = await rollupStore.getRollupsBySession(sessionId);
|
|
829
|
+
const parentedChildIds = new Set(
|
|
830
|
+
allRollups.flatMap((rollup) => rollup.childRollupIds)
|
|
831
|
+
);
|
|
832
|
+
const levelCandidates = allRollups.filter((rollup) => rollup.level === level && !parentedChildIds.has(rollup.id)).sort((a, b) => a.rangeStartIndex - b.rangeStartIndex);
|
|
833
|
+
if (levelCandidates.length < ROLLUP_GROUP_SIZE) {
|
|
834
|
+
break;
|
|
835
|
+
}
|
|
836
|
+
const batches = [];
|
|
837
|
+
for (let index = 0; index + ROLLUP_GROUP_SIZE <= levelCandidates.length; index += ROLLUP_GROUP_SIZE) {
|
|
838
|
+
batches.push(levelCandidates.slice(index, index + ROLLUP_GROUP_SIZE));
|
|
839
|
+
}
|
|
840
|
+
if (batches.length === 0) {
|
|
841
|
+
break;
|
|
842
|
+
}
|
|
843
|
+
for (const batch of batches) {
|
|
844
|
+
const rollup = {
|
|
845
|
+
id: randomUUID(),
|
|
846
|
+
ref: nextRollupRef(level + 1, allRollups),
|
|
847
|
+
taskId: batch[0].taskId,
|
|
848
|
+
sessionId,
|
|
849
|
+
level: level + 1,
|
|
850
|
+
summary: summarizeRollupBatch(batch),
|
|
851
|
+
childEpisodeIds: [],
|
|
852
|
+
childRollupIds: batch.map((child) => child.id),
|
|
853
|
+
rangeStartIndex: batch[0].rangeStartIndex,
|
|
854
|
+
rangeEndIndex: batch[batch.length - 1].rangeEndIndex,
|
|
855
|
+
keyFiles: [...new Set(batch.flatMap((rollupChild) => rollupChild.keyFiles))],
|
|
856
|
+
createdAt: Date.now()
|
|
857
|
+
};
|
|
858
|
+
await rollupStore.addRollup(rollup);
|
|
859
|
+
}
|
|
860
|
+
level += 1;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
735
863
|
async function consolidateEpisodes(episodes, sessionId, sessionMemoStore) {
|
|
736
864
|
if (episodes.length === 0) {
|
|
737
865
|
throw new Error("Cannot consolidate zero episodes");
|
|
@@ -770,9 +898,12 @@ async function consolidateEpisodes(episodes, sessionId, sessionMemoStore) {
|
|
|
770
898
|
await sessionMemoStore.addMemo(memo);
|
|
771
899
|
return memo;
|
|
772
900
|
}
|
|
773
|
-
async function runConsolidation(_taskId, sessionId, episodeStore, sessionMemoStore, _longTermStore) {
|
|
901
|
+
async function runConsolidation(_taskId, sessionId, episodeStore, sessionMemoStore, _longTermStore, rollupStore) {
|
|
774
902
|
const episodes = await episodeStore.getEpisodesBySession(sessionId);
|
|
775
903
|
if (episodes.length > 0) {
|
|
904
|
+
if (rollupStore) {
|
|
905
|
+
await buildEpisodeRollups(episodes, sessionId, rollupStore);
|
|
906
|
+
}
|
|
776
907
|
await consolidateEpisodes(episodes, sessionId, sessionMemoStore);
|
|
777
908
|
}
|
|
778
909
|
}
|
|
@@ -1064,6 +1195,7 @@ var EpisodeCompressor = class {
|
|
|
1064
1195
|
compress(input) {
|
|
1065
1196
|
const now = Date.now();
|
|
1066
1197
|
const id = randomUUID();
|
|
1198
|
+
const artifacts = extractArtifacts(input.messages);
|
|
1067
1199
|
const episode = {
|
|
1068
1200
|
id,
|
|
1069
1201
|
taskId: input.taskId,
|
|
@@ -1078,14 +1210,14 @@ var EpisodeCompressor = class {
|
|
|
1078
1210
|
steps: countSteps(input.messages),
|
|
1079
1211
|
success: input.success,
|
|
1080
1212
|
createdAt: now,
|
|
1081
|
-
parentEpisodeIds: input.parentEpisodeIds
|
|
1213
|
+
parentEpisodeIds: input.parentEpisodeIds,
|
|
1214
|
+
artifactKeys: artifacts.map((artifact) => artifact.key)
|
|
1082
1215
|
};
|
|
1083
1216
|
const trace = {
|
|
1084
1217
|
episodeId: id,
|
|
1085
1218
|
messages: input.messages,
|
|
1086
1219
|
createdAt: now
|
|
1087
1220
|
};
|
|
1088
|
-
const artifacts = extractArtifacts(input.messages);
|
|
1089
1221
|
return { episode, trace, artifacts };
|
|
1090
1222
|
}
|
|
1091
1223
|
async compressLLM(input, signal) {
|
|
@@ -1844,32 +1976,29 @@ async function buildSeedMessages(episodeIds, episodeStore) {
|
|
|
1844
1976
|
if (episodeIds.length === 0) return [];
|
|
1845
1977
|
const messages = [];
|
|
1846
1978
|
for (const id of episodeIds) {
|
|
1847
|
-
const
|
|
1848
|
-
if (!
|
|
1849
|
-
const
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
return `[${m.role}]
|
|
1867
|
-
${text}`;
|
|
1868
|
-
}).join("\n\n");
|
|
1979
|
+
const episode = await episodeStore.getEpisode(id);
|
|
1980
|
+
if (!episode) continue;
|
|
1981
|
+
const blocks = [
|
|
1982
|
+
`Action: ${episode.threadAction}`,
|
|
1983
|
+
`Status: ${episode.success ? "success" : "failed"}`,
|
|
1984
|
+
`Summary:
|
|
1985
|
+
${episode.summary}`
|
|
1986
|
+
];
|
|
1987
|
+
if (episode.structuredOutput && Object.keys(episode.structuredOutput).length > 0) {
|
|
1988
|
+
const structuredOutput = JSON.stringify(episode.structuredOutput, null, 2);
|
|
1989
|
+
const truncated = structuredOutput.length > 2e3 ? structuredOutput.slice(0, 2e3) + "\n... [truncated]" : structuredOutput;
|
|
1990
|
+
blocks.push(`Structured output:
|
|
1991
|
+
${truncated}`);
|
|
1992
|
+
}
|
|
1993
|
+
if (episode.artifactKeys && episode.artifactKeys.length > 0) {
|
|
1994
|
+
blocks.push(`Artifact handles:
|
|
1995
|
+
${episode.artifactKeys.join(", ")}`);
|
|
1996
|
+
}
|
|
1997
|
+
blocks.push('Use ReadEpisode with detail: "trace" or "artifacts" only if you need more than this summary.');
|
|
1869
1998
|
messages.push({
|
|
1870
1999
|
role: "system",
|
|
1871
2000
|
content: `Context from prior episode (${id}):
|
|
1872
|
-
${
|
|
2001
|
+
${blocks.join("\n\n")}`
|
|
1873
2002
|
});
|
|
1874
2003
|
}
|
|
1875
2004
|
return messages;
|
|
@@ -1897,6 +2026,114 @@ async function* drainNonBlocking(iterable) {
|
|
|
1897
2026
|
yield next.value;
|
|
1898
2027
|
}
|
|
1899
2028
|
}
|
|
2029
|
+
|
|
2030
|
+
// src/arc/episode-reader.ts
|
|
2031
|
+
function coerceDetail(value, fallback) {
|
|
2032
|
+
if (value === "summary" || value === "trace" || value === "artifacts") {
|
|
2033
|
+
return value;
|
|
2034
|
+
}
|
|
2035
|
+
return fallback;
|
|
2036
|
+
}
|
|
2037
|
+
function renderTraceText(messages) {
|
|
2038
|
+
return messages.map((message) => {
|
|
2039
|
+
const textContent = getTextContent(message.content);
|
|
2040
|
+
if (message.role === "assistant" && message.toolCalls?.length) {
|
|
2041
|
+
const calls = message.toolCalls.map((toolCall) => ` ${toolCall.toolName}(${JSON.stringify(toolCall.args)})`).join("\n");
|
|
2042
|
+
return `[assistant]
|
|
2043
|
+
${textContent}
|
|
2044
|
+
[tool calls]
|
|
2045
|
+
${calls}`;
|
|
2046
|
+
}
|
|
2047
|
+
if (message.role === "tool" && message.toolResults?.length) {
|
|
2048
|
+
const results = message.toolResults.map((toolResult) => {
|
|
2049
|
+
const output = toolResult.result.length > 500 ? toolResult.result.slice(0, 500) + "..." : toolResult.result;
|
|
2050
|
+
return ` ${toolResult.toolName}: ${toolResult.isError ? "ERROR: " : ""}${output}`;
|
|
2051
|
+
}).join("\n");
|
|
2052
|
+
return `[tool results]
|
|
2053
|
+
${results}`;
|
|
2054
|
+
}
|
|
2055
|
+
return `[${message.role}]
|
|
2056
|
+
${textContent}`;
|
|
2057
|
+
}).join("\n\n");
|
|
2058
|
+
}
|
|
2059
|
+
function formatSummaryBlock(episode) {
|
|
2060
|
+
const lines = [
|
|
2061
|
+
`Action: ${episode.threadAction}`,
|
|
2062
|
+
`Status: ${episode.success ? "success" : "failed"}`,
|
|
2063
|
+
`Summary:
|
|
2064
|
+
${episode.summary}`
|
|
2065
|
+
];
|
|
2066
|
+
if (episode.structuredOutput && Object.keys(episode.structuredOutput).length > 0) {
|
|
2067
|
+
const structuredOutput = JSON.stringify(episode.structuredOutput, null, 2);
|
|
2068
|
+
const truncated = structuredOutput.length > 2e3 ? structuredOutput.slice(0, 2e3) + "\n... [truncated]" : structuredOutput;
|
|
2069
|
+
lines.push(`Structured output:
|
|
2070
|
+
${truncated}`);
|
|
2071
|
+
}
|
|
2072
|
+
if (episode.artifactKeys && episode.artifactKeys.length > 0) {
|
|
2073
|
+
lines.push(`Artifact handles:
|
|
2074
|
+
${episode.artifactKeys.join(", ")}`);
|
|
2075
|
+
}
|
|
2076
|
+
lines.push(`Use ReadEpisode with detail: "trace" or "artifacts" for more detail from this episode.`);
|
|
2077
|
+
return lines.join("\n\n");
|
|
2078
|
+
}
|
|
2079
|
+
function formatArtifactsBlock(artifacts, artifactKey) {
|
|
2080
|
+
const filtered = artifactKey ? artifacts.filter((artifact) => artifact.key === artifactKey) : artifacts;
|
|
2081
|
+
if (filtered.length === 0) {
|
|
2082
|
+
return artifactKey ? `Artifact not found: ${artifactKey}` : "No artifact content is currently available for this episode.";
|
|
2083
|
+
}
|
|
2084
|
+
return filtered.map((artifact) => `[${artifact.key}]
|
|
2085
|
+
${artifact.content}`).join("\n\n");
|
|
2086
|
+
}
|
|
2087
|
+
function applyMaxTokens(text, maxTokens) {
|
|
2088
|
+
if (typeof maxTokens !== "number" || maxTokens <= 0) {
|
|
2089
|
+
return text;
|
|
2090
|
+
}
|
|
2091
|
+
const maxChars = maxTokens * 4;
|
|
2092
|
+
if (text.length <= maxChars) {
|
|
2093
|
+
return text;
|
|
2094
|
+
}
|
|
2095
|
+
return text.slice(0, maxChars) + "\n... [truncated]";
|
|
2096
|
+
}
|
|
2097
|
+
async function renderEpisodeReadResult(options) {
|
|
2098
|
+
const episodeId = String(options.args.id ?? "");
|
|
2099
|
+
const detail = coerceDetail(options.args.detail, options.defaultDetail ?? "summary");
|
|
2100
|
+
const artifactKey = options.args.artifactKey != null ? String(options.args.artifactKey) : void 0;
|
|
2101
|
+
const episode = await options.episodeStore.getEpisode(episodeId);
|
|
2102
|
+
if (!episode) {
|
|
2103
|
+
return `Episode not found: ${episodeId}`;
|
|
2104
|
+
}
|
|
2105
|
+
const proc = options.processes ? [...options.processes].find((process2) => process2.result?.episode.id === episodeId) : void 0;
|
|
2106
|
+
const liveArtifacts = proc?.result?.artifacts ?? [];
|
|
2107
|
+
let resultText = `Episode ${episodeId}
|
|
2108
|
+
|
|
2109
|
+
${formatSummaryBlock(episode)}`;
|
|
2110
|
+
if (detail === "artifacts") {
|
|
2111
|
+
resultText = `Episode ${episodeId}
|
|
2112
|
+
|
|
2113
|
+
${formatArtifactsBlock(liveArtifacts, artifactKey)}`;
|
|
2114
|
+
} else if (detail === "trace") {
|
|
2115
|
+
const trace = await options.episodeStore.getTrace(episodeId);
|
|
2116
|
+
if (!trace) {
|
|
2117
|
+
resultText += "\n\nTrace not found.";
|
|
2118
|
+
} else {
|
|
2119
|
+
resultText += `
|
|
2120
|
+
|
|
2121
|
+
--- Trace ---
|
|
2122
|
+
${renderTraceText(trace.messages)}`;
|
|
2123
|
+
}
|
|
2124
|
+
if (liveArtifacts.length > 0) {
|
|
2125
|
+
resultText += `
|
|
2126
|
+
|
|
2127
|
+
--- Artifacts ---
|
|
2128
|
+
${formatArtifactsBlock(liveArtifacts, artifactKey)}`;
|
|
2129
|
+
}
|
|
2130
|
+
} else if (artifactKey) {
|
|
2131
|
+
resultText += `
|
|
2132
|
+
|
|
2133
|
+
Requested artifact handle: ${artifactKey}`;
|
|
2134
|
+
}
|
|
2135
|
+
return applyMaxTokens(resultText, options.args.maxTokens);
|
|
2136
|
+
}
|
|
1900
2137
|
var FIELD_RE = /^(\w+)(?::(\w+)(\[\])?)(\?)?(?:\s*\(([^)]+)\))?$/;
|
|
1901
2138
|
function parseField(raw) {
|
|
1902
2139
|
const trimmed = raw.trim();
|
|
@@ -2054,7 +2291,7 @@ var ProcessManager = class {
|
|
|
2054
2291
|
dispatch(request, parentSignal) {
|
|
2055
2292
|
const { loopConfig } = this.config;
|
|
2056
2293
|
const profileConfig = request.profile ? loopConfig.processProfiles?.[request.profile] : void 0;
|
|
2057
|
-
const globalTools = loopConfig.processTools ??
|
|
2294
|
+
const globalTools = loopConfig.processTools ?? processTools;
|
|
2058
2295
|
const profile = profileConfig ? resolveProfile(profileConfig, globalTools) : void 0;
|
|
2059
2296
|
const defaultModel = this.config.modelMap[profile?.model ?? "medium"] ?? this.config.modelMap.medium;
|
|
2060
2297
|
const profileSkills = profileConfig && isProfileDeclaration(profileConfig) ? profileConfig.skills : void 0;
|
|
@@ -2103,9 +2340,32 @@ var ProcessManager = class {
|
|
|
2103
2340
|
...pickDefined(loopConfig, [
|
|
2104
2341
|
"hookRunner",
|
|
2105
2342
|
"permissionManager",
|
|
2106
|
-
"telemetry"
|
|
2107
|
-
|
|
2108
|
-
|
|
2343
|
+
"telemetry"
|
|
2344
|
+
]),
|
|
2345
|
+
executeToolAction: async (action) => {
|
|
2346
|
+
if (action.name === "ReadEpisode") {
|
|
2347
|
+
const allowedEpisodeIds = new Set(request.contextEpisodeIds ?? []);
|
|
2348
|
+
const requestedEpisodeId = String(action.args.id ?? "");
|
|
2349
|
+
if (!allowedEpisodeIds.has(requestedEpisodeId)) {
|
|
2350
|
+
return {
|
|
2351
|
+
success: false,
|
|
2352
|
+
output: "",
|
|
2353
|
+
error: `ReadEpisode is limited to contextEpisodeIds for this thread. Allowed episode IDs: ${[...allowedEpisodeIds].join(", ") || "(none)"}`
|
|
2354
|
+
};
|
|
2355
|
+
}
|
|
2356
|
+
const output = await renderEpisodeReadResult({
|
|
2357
|
+
episodeStore: loopConfig.episodeStore,
|
|
2358
|
+
args: action.args,
|
|
2359
|
+
defaultDetail: "summary",
|
|
2360
|
+
processes: this.processes.values()
|
|
2361
|
+
});
|
|
2362
|
+
return { success: true, output };
|
|
2363
|
+
}
|
|
2364
|
+
if (loopConfig.executeToolAction) {
|
|
2365
|
+
return loopConfig.executeToolAction(action);
|
|
2366
|
+
}
|
|
2367
|
+
return null;
|
|
2368
|
+
}
|
|
2109
2369
|
});
|
|
2110
2370
|
this.processes.set(proc.id, proc);
|
|
2111
2371
|
this.actionIndex.set(normalizeAction(request.action), proc.id);
|
|
@@ -2250,6 +2510,64 @@ var ProcessManager = class {
|
|
|
2250
2510
|
}
|
|
2251
2511
|
}
|
|
2252
2512
|
};
|
|
2513
|
+
|
|
2514
|
+
// src/arc/rollup-reader.ts
|
|
2515
|
+
function applyMaxTokens2(text, maxTokens) {
|
|
2516
|
+
if (typeof maxTokens !== "number" || maxTokens <= 0) {
|
|
2517
|
+
return text;
|
|
2518
|
+
}
|
|
2519
|
+
const maxChars = maxTokens * 4;
|
|
2520
|
+
if (text.length <= maxChars) {
|
|
2521
|
+
return text;
|
|
2522
|
+
}
|
|
2523
|
+
return text.slice(0, maxChars) + "\n... [truncated]";
|
|
2524
|
+
}
|
|
2525
|
+
function formatRollupSummary(rollup) {
|
|
2526
|
+
return [
|
|
2527
|
+
`Rollup ${rollup.ref}`,
|
|
2528
|
+
`Level: ${rollup.level}`,
|
|
2529
|
+
`Covers: E${rollup.rangeStartIndex}-E${rollup.rangeEndIndex}`,
|
|
2530
|
+
`Child episodes: ${rollup.childEpisodeIds.length}`,
|
|
2531
|
+
`Child rollups: ${rollup.childRollupIds.length}`,
|
|
2532
|
+
...rollup.keyFiles.length > 0 ? [`Key files: ${rollup.keyFiles.join(", ")}`] : [],
|
|
2533
|
+
"",
|
|
2534
|
+
rollup.summary
|
|
2535
|
+
].join("\n");
|
|
2536
|
+
}
|
|
2537
|
+
async function renderRollupReadResult(options) {
|
|
2538
|
+
const rollupId = String(options.args.id ?? "");
|
|
2539
|
+
const rollup = await options.rollupStore.getRollup(rollupId);
|
|
2540
|
+
if (!rollup) {
|
|
2541
|
+
return `Rollup not found: ${rollupId}`;
|
|
2542
|
+
}
|
|
2543
|
+
const detail = options.args.detail === "children" ? "children" : "summary";
|
|
2544
|
+
let resultText = formatRollupSummary(rollup);
|
|
2545
|
+
if (detail === "children") {
|
|
2546
|
+
const childSections = [];
|
|
2547
|
+
for (const childRollupId of rollup.childRollupIds) {
|
|
2548
|
+
const childRollup = await options.rollupStore.getRollup(childRollupId);
|
|
2549
|
+
if (childRollup) {
|
|
2550
|
+
childSections.push(`--- Child rollup ${childRollup.ref} ---
|
|
2551
|
+
${childRollup.summary}`);
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
for (const childEpisodeId of rollup.childEpisodeIds) {
|
|
2555
|
+
const episode = await options.episodeStore.getEpisode(childEpisodeId);
|
|
2556
|
+
if (episode) {
|
|
2557
|
+
childSections.push(`--- Episode E${episode.index} [${episode.id}] ---
|
|
2558
|
+
${episode.summary}`);
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
if (childSections.length > 0) {
|
|
2562
|
+
resultText += `
|
|
2563
|
+
|
|
2564
|
+
${childSections.join("\n\n")}`;
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
return applyMaxTokens2(resultText, options.args.maxTokens);
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2570
|
+
// src/arc/orchestrator-turn-runner.ts
|
|
2253
2571
|
var OrchestratorTurnRunner = class {
|
|
2254
2572
|
config;
|
|
2255
2573
|
constructor(config) {
|
|
@@ -2382,13 +2700,6 @@ var OrchestratorTurnRunner = class {
|
|
|
2382
2700
|
yield { type: "thread_rejected", action: request.action, reason: "duplicate (completed)" };
|
|
2383
2701
|
continue;
|
|
2384
2702
|
}
|
|
2385
|
-
const completedEpisodeIds = [...this.config.processManager.values()].filter((proc2) => proc2.status === "completed" && proc2.result?.episode).map((proc2) => proc2.result.episode.id);
|
|
2386
|
-
if (completedEpisodeIds.length > 0) {
|
|
2387
|
-
request.contextEpisodeIds = [
|
|
2388
|
-
...request.contextEpisodeIds ?? [],
|
|
2389
|
-
...completedEpisodeIds.filter((id) => !request.contextEpisodeIds?.includes(id))
|
|
2390
|
-
];
|
|
2391
|
-
}
|
|
2392
2703
|
const proc = this.config.processManager.dispatch(request, signal);
|
|
2393
2704
|
const resultText = `Process ${proc.id} dispatched: "${request.action}" (model: ${request.model ?? "medium"})`;
|
|
2394
2705
|
toolResultMessages.push({
|
|
@@ -2435,45 +2746,12 @@ ${proc.result.episode.summary}`;
|
|
|
2435
2746
|
continue;
|
|
2436
2747
|
}
|
|
2437
2748
|
if (call.toolName === "ReadEpisode") {
|
|
2438
|
-
const
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
}
|
|
2444
|
-
resultText = trace.messages.map((message) => {
|
|
2445
|
-
const textContent = getTextContent(message.content);
|
|
2446
|
-
if (message.role === "assistant" && message.toolCalls?.length) {
|
|
2447
|
-
const calls = message.toolCalls.map((toolCall) => ` ${toolCall.toolName}(${JSON.stringify(toolCall.args)})`).join("\n");
|
|
2448
|
-
return `[assistant]
|
|
2449
|
-
${textContent}
|
|
2450
|
-
[tool calls]
|
|
2451
|
-
${calls}`;
|
|
2452
|
-
}
|
|
2453
|
-
if (message.role === "tool" && message.toolResults?.length) {
|
|
2454
|
-
const results = message.toolResults.map((toolResult) => {
|
|
2455
|
-
const output = toolResult.result.length > 500 ? toolResult.result.slice(0, 500) + "..." : toolResult.result;
|
|
2456
|
-
return ` ${toolResult.toolName}: ${toolResult.isError ? "ERROR: " : ""}${output}`;
|
|
2457
|
-
}).join("\n");
|
|
2458
|
-
return `[tool results]
|
|
2459
|
-
${results}`;
|
|
2460
|
-
}
|
|
2461
|
-
return `[${message.role}]
|
|
2462
|
-
${textContent}`;
|
|
2463
|
-
}).join("\n\n");
|
|
2464
|
-
const proc = [...this.config.processManager.values()].find((process2) => process2.result?.episode.id === episodeId);
|
|
2465
|
-
if (proc?.result && proc.result.artifacts.length > 0) {
|
|
2466
|
-
resultText += "\n\n--- Artifacts ---\n";
|
|
2467
|
-
resultText += proc.result.artifacts.map((artifact) => `[${artifact.key}]
|
|
2468
|
-
${artifact.content}`).join("\n\n");
|
|
2469
|
-
}
|
|
2470
|
-
if (typeof call.args.maxTokens === "number" && call.args.maxTokens > 0) {
|
|
2471
|
-
const maxChars = call.args.maxTokens * 4;
|
|
2472
|
-
if (resultText.length > maxChars) {
|
|
2473
|
-
resultText = resultText.slice(0, maxChars) + "\n... [truncated]";
|
|
2474
|
-
}
|
|
2475
|
-
}
|
|
2476
|
-
}
|
|
2749
|
+
const resultText = await renderEpisodeReadResult({
|
|
2750
|
+
episodeStore: this.config.config.episodeStore,
|
|
2751
|
+
args: call.args,
|
|
2752
|
+
defaultDetail: "summary",
|
|
2753
|
+
processes: this.config.processManager.values()
|
|
2754
|
+
});
|
|
2477
2755
|
toolResultMessages.push({
|
|
2478
2756
|
role: "tool",
|
|
2479
2757
|
content: resultText,
|
|
@@ -2481,6 +2759,20 @@ ${artifact.content}`).join("\n\n");
|
|
|
2481
2759
|
});
|
|
2482
2760
|
continue;
|
|
2483
2761
|
}
|
|
2762
|
+
if (call.toolName === "ReadRollup") {
|
|
2763
|
+
const rollupStore = this.config.config.rollupStore;
|
|
2764
|
+
const resultText = rollupStore ? await renderRollupReadResult({
|
|
2765
|
+
rollupStore,
|
|
2766
|
+
episodeStore: this.config.config.episodeStore,
|
|
2767
|
+
args: call.args
|
|
2768
|
+
}) : "Rollup store is not configured.";
|
|
2769
|
+
toolResultMessages.push({
|
|
2770
|
+
role: "tool",
|
|
2771
|
+
content: resultText,
|
|
2772
|
+
toolResults: [{ toolCallId, toolName: "ReadRollup", result: resultText }]
|
|
2773
|
+
});
|
|
2774
|
+
continue;
|
|
2775
|
+
}
|
|
2484
2776
|
if (call.toolName === "Remember") {
|
|
2485
2777
|
const memOpts = {};
|
|
2486
2778
|
if (call.args.category != null) memOpts.category = String(call.args.category);
|
|
@@ -2630,6 +2922,7 @@ var DEFAULT_ORCHESTRATOR_PROMPT = `You are an orchestrator agent. You accomplish
|
|
|
2630
2922
|
- **Cancel**: Cancel a running process.
|
|
2631
2923
|
- **Remember**: Save information to persistent memory.
|
|
2632
2924
|
- **ReadEpisode**: Retrieve the full trace of a completed episode (detailed tool outputs, file contents, errors).
|
|
2925
|
+
- **ReadRollup**: Retrieve an indexed rollup of older episode history and discover which episode refs to hydrate next.
|
|
2633
2926
|
|
|
2634
2927
|
## When to dispatch threads
|
|
2635
2928
|
|
|
@@ -2712,6 +3005,7 @@ var ArcLoop = class {
|
|
|
2712
3005
|
outputReserve: config.outputReserve ?? 2e4,
|
|
2713
3006
|
systemPrompt: this.systemPrompt,
|
|
2714
3007
|
episodeStore: config.episodeStore,
|
|
3008
|
+
...config.rollupStore ? { rollupStore: config.rollupStore } : {},
|
|
2715
3009
|
memory: this.memory,
|
|
2716
3010
|
taskId: config.taskId,
|
|
2717
3011
|
createModel: this.createModel,
|
|
@@ -2868,7 +3162,8 @@ function createArcAgent(config) {
|
|
|
2868
3162
|
config.sessionId,
|
|
2869
3163
|
config.episodeStore,
|
|
2870
3164
|
config.sessionMemoStore,
|
|
2871
|
-
config.longTermStore
|
|
3165
|
+
config.longTermStore,
|
|
3166
|
+
config.rollupStore
|
|
2872
3167
|
);
|
|
2873
3168
|
} catch {
|
|
2874
3169
|
}
|
|
@@ -2899,7 +3194,8 @@ function createArcAgent(config) {
|
|
|
2899
3194
|
config.sessionId,
|
|
2900
3195
|
config.episodeStore,
|
|
2901
3196
|
config.sessionMemoStore,
|
|
2902
|
-
config.longTermStore
|
|
3197
|
+
config.longTermStore,
|
|
3198
|
+
config.rollupStore
|
|
2903
3199
|
);
|
|
2904
3200
|
} catch {
|
|
2905
3201
|
}
|