@bubblebrain-ai/bubble 0.0.28 → 0.0.29
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 +21 -0
- package/dist/agent/categories.d.ts +2 -0
- package/dist/agent/categories.js +4 -0
- package/dist/agent/child-runner.d.ts +5 -1
- package/dist/agent/child-runner.js +35 -2
- package/dist/agent/profiles.js +3 -0
- package/dist/agent/structured-output.d.ts +37 -0
- package/dist/agent/structured-output.js +193 -0
- package/dist/agent/subagent-control.d.ts +3 -0
- package/dist/agent/subagent-scheduler.d.ts +10 -0
- package/dist/agent/subagent-scheduler.js +31 -0
- package/dist/agent/workflow/control.d.ts +37 -0
- package/dist/agent/workflow/control.js +20 -0
- package/dist/agent/workflow/errors.d.ts +16 -0
- package/dist/agent/workflow/errors.js +24 -0
- package/dist/agent/workflow/runtime.d.ts +75 -0
- package/dist/agent/workflow/runtime.js +237 -0
- package/dist/agent.d.ts +105 -0
- package/dist/agent.js +425 -17
- package/dist/context/compact-llm.d.ts +10 -1
- package/dist/context/compact-llm.js +13 -5
- package/dist/context/compact.d.ts +30 -0
- package/dist/context/compact.js +34 -17
- package/dist/network/provider-transport.d.ts +9 -0
- package/dist/network/provider-transport.js +19 -1
- package/dist/provider.d.ts +14 -0
- package/dist/provider.js +24 -0
- package/dist/session.d.ts +16 -0
- package/dist/session.js +33 -1
- package/dist/slash-commands/commands.js +47 -1
- package/dist/slash-commands/types.d.ts +16 -1
- package/dist/tools/agent-lifecycle.d.ts +6 -0
- package/dist/tools/agent-lifecycle.js +285 -0
- package/dist/tools/child-tools.d.ts +10 -0
- package/dist/tools/child-tools.js +12 -0
- package/dist/tools/read.d.ts +1 -1
- package/dist/tools/read.js +9 -0
- package/dist/tui/image-display.d.ts +6 -0
- package/dist/tui/image-display.js +26 -1
- package/dist/tui-ink/app.js +84 -6
- package/dist/tui-ink/compaction-progress.d.ts +19 -0
- package/dist/tui-ink/compaction-progress.js +74 -0
- package/dist/tui-ink/input-box.d.ts +7 -1
- package/dist/tui-ink/input-box.js +48 -15
- package/dist/tui-ink/markdown.d.ts +18 -0
- package/dist/tui-ink/markdown.js +172 -16
- package/dist/tui-ink/message-list.js +38 -94
- package/dist/tui-ink/run.js +5 -0
- package/dist/tui-ink/subagent-inspector.d.ts +17 -0
- package/dist/tui-ink/subagent-inspector.js +189 -0
- package/dist/tui-ink/subagent-view.d.ts +47 -0
- package/dist/tui-ink/subagent-view.js +163 -0
- package/dist/tui-ink/terminal-env.d.ts +15 -0
- package/dist/tui-ink/terminal-env.js +22 -0
- package/dist/tui-ink/use-terminal-size.js +33 -6
- package/dist/tui-ink/width.d.ts +18 -0
- package/dist/tui-ink/width.js +130 -0
- package/dist/types.d.ts +35 -0
- package/package.json +2 -1
package/dist/agent.js
CHANGED
|
@@ -16,10 +16,13 @@ import { buildDeferredToolsReminder, buildToolFreezeReminder, reminderForMode }
|
|
|
16
16
|
import { HookBus } from "./orchestrator/hooks.js";
|
|
17
17
|
import { normalizeHookInput, truncateHookText, } from "./hooks/index.js";
|
|
18
18
|
import { createDefaultHooks } from "./orchestrator/default-hooks.js";
|
|
19
|
-
import { mergeAgentCategories, resolveModelRoute, resolveSubagentRoute } from "./agent/categories.js";
|
|
19
|
+
import { mergeAgentCategories, parseThinkingLevel, resolveModelRoute, resolveSubagentRoute } from "./agent/categories.js";
|
|
20
|
+
import { appendOutputSchemaInstructions, buildSchemaCorrectionPrompt, validateStructuredSummary } from "./agent/structured-output.js";
|
|
21
|
+
import { runWorkflow, WorkflowConcurrencyGate } from "./agent/workflow/runtime.js";
|
|
22
|
+
import { buildWorkflowDeliveryNotice } from "./agent/workflow/control.js";
|
|
20
23
|
import { getSubtaskPolicy } from "./agent/subtask-policy.js";
|
|
21
24
|
import { composeAbortSignals, computeChildTokenCap, DEFAULT_CHILD_TOKEN_CAP, PARENT_POOL_RESERVE_RATIO } from "./agent/budget-ledger.js";
|
|
22
|
-
import { assignAgentNickname, builtinAgentProfiles, validateAgentProfileTools } from "./agent/profiles.js";
|
|
25
|
+
import { assignAgentNickname, builtinAgentProfiles, discoverAgentProfiles, findAgentProfile, validateAgentProfileTools } from "./agent/profiles.js";
|
|
23
26
|
import { snapshotSubagentThread, subagentResultFromThread } from "./agent/subagent-control.js";
|
|
24
27
|
import { SubagentStore } from "./agent/subagent-store.js";
|
|
25
28
|
import { SubagentScheduler } from "./agent/subagent-scheduler.js";
|
|
@@ -27,7 +30,7 @@ import { ChildRunner, classifySubagentAbortReason } from "./agent/child-runner.j
|
|
|
27
30
|
import { ResultIntegrator } from "./agent/result-integrator.js";
|
|
28
31
|
import { AgentAbortError, EMPTY_ASSISTANT_FALLBACK, SubagentAbortError } from "./agent/abort-errors.js";
|
|
29
32
|
import { createSubagentWorktree, finalizeSubagentWorktree } from "./agent/worktree.js";
|
|
30
|
-
import { createWorktreeChildTools } from "./tools/child-tools.js";
|
|
33
|
+
import { createWorktreeChildTools, isolateReadonlyChildFileTools } from "./tools/child-tools.js";
|
|
31
34
|
import { isHiddenToolResult } from "./agent/discovery-barrier.js";
|
|
32
35
|
import { createStreamingInternalReminderSanitizer, sanitizeAssistantProviderMetadata, sanitizeInternalReasoningText, sanitizeInternalReminderBlocks, } from "./agent/internal-reminder-sanitizer.js";
|
|
33
36
|
import { buildSystemPrompt } from "./system-prompt.js";
|
|
@@ -118,6 +121,10 @@ export class Agent {
|
|
|
118
121
|
subagentScheduler;
|
|
119
122
|
childRunner;
|
|
120
123
|
resultIntegrator = new ResultIntegrator();
|
|
124
|
+
/** Background dynamic-workflow runs (option C Phase 4), keyed by runId. */
|
|
125
|
+
workflowRuns = new Map();
|
|
126
|
+
/** runIds whose completed result should be ingested at the next turn. */
|
|
127
|
+
pendingWorkflowDeliveries = new Set();
|
|
121
128
|
subagentsConfig;
|
|
122
129
|
rateLimitPolicy;
|
|
123
130
|
pendingSubagentUpdates = [];
|
|
@@ -166,6 +173,8 @@ export class Agent {
|
|
|
166
173
|
launchIntervalMs: this.subagentsConfig.launchIntervalMs,
|
|
167
174
|
rateLimitMaxAttempts: this.subagentsConfig.rateLimitMaxAttempts,
|
|
168
175
|
rateLimitBackoffMs: this.subagentsConfig.rateLimitBackoffMs,
|
|
176
|
+
transportRetryMaxAttempts: this.subagentsConfig.transportRetryMaxAttempts,
|
|
177
|
+
transportRetryBackoffMs: this.subagentsConfig.transportRetryBackoffMs,
|
|
169
178
|
getCategoryLimit: (category) => mergeAgentCategories(this.agentCategories)[category]?.maxConcurrent,
|
|
170
179
|
});
|
|
171
180
|
this.childRunner = new ChildRunner({
|
|
@@ -185,8 +194,12 @@ export class Agent {
|
|
|
185
194
|
record.toolNotes.push(`worktree: changes left in ${record.worktree.path} — review the diff before applying`);
|
|
186
195
|
}
|
|
187
196
|
}
|
|
188
|
-
|
|
189
|
-
|
|
197
|
+
// Workflow-internal agents are not persisted (they never re-import into
|
|
198
|
+
// the store on restart) and never ingest into parent context (option C).
|
|
199
|
+
if (!record.workflowInternal) {
|
|
200
|
+
this.subagentStore.persist(record);
|
|
201
|
+
this.maybeEnqueueIngestion(record, options);
|
|
202
|
+
}
|
|
190
203
|
},
|
|
191
204
|
});
|
|
192
205
|
if (options.systemPrompt) {
|
|
@@ -533,6 +546,7 @@ export class Agent {
|
|
|
533
546
|
// Background child completions surface before the next inference turn
|
|
534
547
|
// without requiring a wait_agent call (design §5).
|
|
535
548
|
this.flushSubagentIngestions();
|
|
549
|
+
this.flushWorkflowDeliveries();
|
|
536
550
|
for (const update of this.drainSubagentToolUpdates())
|
|
537
551
|
yield emit(update);
|
|
538
552
|
for (const event of await applyPendingInputs())
|
|
@@ -1351,6 +1365,39 @@ export class Agent {
|
|
|
1351
1365
|
// If LLM compaction failed for any reason, leave this.messages alone —
|
|
1352
1366
|
// the projector's algorithmic budgeted-mode passes will still try.
|
|
1353
1367
|
}
|
|
1368
|
+
/**
|
|
1369
|
+
* Stream a 9-section handoff summary of `oldMessages` from the session model.
|
|
1370
|
+
* Powers the manual `/compact` command: streaming (rather than `complete()`)
|
|
1371
|
+
* is what lets the TUI show live progress as the summary is produced.
|
|
1372
|
+
*
|
|
1373
|
+
* `onDelta` receives the full accumulated text and the latest delta on each
|
|
1374
|
+
* chunk. Returns the trimmed summary, or "" if the model produced nothing
|
|
1375
|
+
* (the caller falls back to heuristic compaction in that case). Throws only
|
|
1376
|
+
* if the provider stream itself errors.
|
|
1377
|
+
*/
|
|
1378
|
+
async summarizeForCompaction(oldMessages, onDelta, abortSignal) {
|
|
1379
|
+
if (oldMessages.length === 0)
|
|
1380
|
+
return "";
|
|
1381
|
+
const { buildCompactionPromptMessages } = await import("./context/compact-llm.js");
|
|
1382
|
+
const promptMessages = buildCompactionPromptMessages(oldMessages);
|
|
1383
|
+
const stream = this.provider.streamChat(promptMessages, {
|
|
1384
|
+
model: this.apiModel,
|
|
1385
|
+
temperature: 0.2,
|
|
1386
|
+
thinkingLevel: "off",
|
|
1387
|
+
abortSignal,
|
|
1388
|
+
});
|
|
1389
|
+
let full = "";
|
|
1390
|
+
for await (const chunk of stream) {
|
|
1391
|
+
if (chunk.type === "text" && chunk.content) {
|
|
1392
|
+
full += chunk.content;
|
|
1393
|
+
onDelta?.(full, chunk.content);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
// Strip any internal reminder markup the summarizer may have reproduced from
|
|
1397
|
+
// the transcript: this summary is both displayed in the compaction card and
|
|
1398
|
+
// re-injected as a `Previous conversation summary` system message.
|
|
1399
|
+
return sanitizeInternalReminderBlocks(full).trim();
|
|
1400
|
+
}
|
|
1354
1401
|
async runSubtask(input, cwd, options) {
|
|
1355
1402
|
const subtaskType = options?.subtaskType;
|
|
1356
1403
|
const profile = builtinAgentProfiles().find((item) => item.subtaskType === (subtaskType ?? "general_readonly"))
|
|
@@ -1420,7 +1467,7 @@ export class Agent {
|
|
|
1420
1467
|
task: typeof input === "string" ? input : "(multimodal task)",
|
|
1421
1468
|
parentToolCallId: options.parentToolCallId,
|
|
1422
1469
|
parentToolName: "spawn_agent",
|
|
1423
|
-
route: options.route ?? this.resolveRouteForSubagent(options.profile, options.category),
|
|
1470
|
+
route: options.route ?? this.resolveRouteForSubagent(options.profile, options.category, { model: options.model, effort: options.effort }),
|
|
1424
1471
|
});
|
|
1425
1472
|
this.subagentStore.set(record);
|
|
1426
1473
|
const approval = options.approval ?? record.profile.approval;
|
|
@@ -1529,7 +1576,9 @@ export class Agent {
|
|
|
1529
1576
|
return this.snapshotSubagent(record);
|
|
1530
1577
|
}
|
|
1531
1578
|
listSubAgents() {
|
|
1532
|
-
return this.subagentStore.values()
|
|
1579
|
+
return this.subagentStore.values()
|
|
1580
|
+
.filter((record) => !record.workflowInternal)
|
|
1581
|
+
.map((record) => this.snapshotSubagent(record));
|
|
1533
1582
|
}
|
|
1534
1583
|
/**
|
|
1535
1584
|
* Homogeneous map fan-out (design §1.2): one profile, one template, N items.
|
|
@@ -1554,7 +1603,7 @@ export class Agent {
|
|
|
1554
1603
|
}
|
|
1555
1604
|
}
|
|
1556
1605
|
const approval = options.approval ?? options.profile.approval;
|
|
1557
|
-
const route = this.resolveRouteForSubagent(options.profile, options.category);
|
|
1606
|
+
const route = this.resolveRouteForSubagent(options.profile, options.category, { model: options.model, effort: options.effort });
|
|
1558
1607
|
const records = options.items.map((item) => this.createSubagentThreadRecord({
|
|
1559
1608
|
profile: options.profile,
|
|
1560
1609
|
task: options.promptTemplate.split("{{item}}").join(item),
|
|
@@ -1587,6 +1636,333 @@ export class Agent {
|
|
|
1587
1636
|
this.subagentStore.markDelivered(record.agentId);
|
|
1588
1637
|
return records.map((record) => this.snapshotSubagent(record));
|
|
1589
1638
|
}
|
|
1639
|
+
/**
|
|
1640
|
+
* Heterogeneous fan-out (design v2 §1.3): N independent specs, each with its
|
|
1641
|
+
* own task, profile, and per-call model/effort, dispatched concurrently as a
|
|
1642
|
+
* SINGLE tool call. Unlike runAgentTeam (one template over N items), members
|
|
1643
|
+
* differ. Like the team, every member goes through the same scheduler
|
|
1644
|
+
* dispatch and the tool blocks until all are final, returning in spec order.
|
|
1645
|
+
* Keeping fan-out inside one tool call (rather than N parallel spawn_agent
|
|
1646
|
+
* tool_calls) avoids the provider parallel-tool_call bug (Kimi 400 / lost
|
|
1647
|
+
* responses).
|
|
1648
|
+
*/
|
|
1649
|
+
async runAgentBatch(cwd, options) {
|
|
1650
|
+
// Budget pre-check mirrors runAgentTeam: members × per-member cap must fit
|
|
1651
|
+
// in what remains of a limited pool after the parent's reserve.
|
|
1652
|
+
const limit = this.budgetLedger?.poolLimit;
|
|
1653
|
+
if (this.budgetLedger && limit !== undefined) {
|
|
1654
|
+
const reserve = Math.floor(limit * PARENT_POOL_RESERVE_RATIO);
|
|
1655
|
+
const available = Math.max(0, (this.budgetLedger.remaining() ?? 0) - reserve);
|
|
1656
|
+
const memberCap = this.subagentsConfig.childTokenCap ?? DEFAULT_CHILD_TOKEN_CAP;
|
|
1657
|
+
const affordable = Math.floor(available / memberCap);
|
|
1658
|
+
if (options.specs.length > affordable) {
|
|
1659
|
+
throw new Error([
|
|
1660
|
+
`agent_batch rejected: the remaining token budget affords at most ${affordable} member${affordable === 1 ? "" : "s"}`,
|
|
1661
|
+
`but ${options.specs.length} were requested. Reduce specs or run smaller batches sequentially.`,
|
|
1662
|
+
].join(" "));
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
const records = options.specs.map((spec) => {
|
|
1666
|
+
const approval = options.approval ?? spec.profile.approval;
|
|
1667
|
+
const task = spec.outputSchema !== undefined
|
|
1668
|
+
? appendOutputSchemaInstructions(spec.task, spec.outputSchema)
|
|
1669
|
+
: spec.task;
|
|
1670
|
+
const record = this.createSubagentThreadRecord({
|
|
1671
|
+
profile: spec.profile,
|
|
1672
|
+
task,
|
|
1673
|
+
parentToolCallId: options.parentToolCallId,
|
|
1674
|
+
parentToolName: "agent_batch",
|
|
1675
|
+
route: this.resolveRouteForSubagent(spec.profile, spec.category, { model: spec.model, effort: spec.effort }),
|
|
1676
|
+
});
|
|
1677
|
+
return { record, approval, spec };
|
|
1678
|
+
});
|
|
1679
|
+
const promises = records.map(({ record, approval }) => {
|
|
1680
|
+
this.subagentStore.set(record);
|
|
1681
|
+
const admissionError = this.admitSubagentProfile(record, approval);
|
|
1682
|
+
if (admissionError) {
|
|
1683
|
+
this.finalizeSubagentBlocked(record, admissionError, { directEmit: options.emitUpdate });
|
|
1684
|
+
return Promise.resolve();
|
|
1685
|
+
}
|
|
1686
|
+
record.promise = this.dispatchSubagentRun(record, record.task, cwd, {
|
|
1687
|
+
approval,
|
|
1688
|
+
abortSignal: options.abortSignal,
|
|
1689
|
+
directEmit: options.emitUpdate,
|
|
1690
|
+
});
|
|
1691
|
+
void record.promise.finally(() => this.subagentStore.notifyWaiters(record));
|
|
1692
|
+
return record.promise;
|
|
1693
|
+
});
|
|
1694
|
+
await Promise.all(promises);
|
|
1695
|
+
// Structured-output validation + one corrective retry (design v2 §1.2):
|
|
1696
|
+
// a member whose schema'd summary does not validate gets a single
|
|
1697
|
+
// send_input correction, reusing the existing resume path.
|
|
1698
|
+
for (const { record, spec } of records) {
|
|
1699
|
+
if (spec.outputSchema === undefined)
|
|
1700
|
+
continue;
|
|
1701
|
+
if (record.status !== "completed")
|
|
1702
|
+
continue;
|
|
1703
|
+
if (validateStructuredSummary(record.summary, spec.outputSchema).ok)
|
|
1704
|
+
continue;
|
|
1705
|
+
const correction = buildSchemaCorrectionPrompt(spec.outputSchema, record.summary);
|
|
1706
|
+
try {
|
|
1707
|
+
await this.sendSubAgentInput(record.agentId, correction, cwd, { abortSignal: options.abortSignal });
|
|
1708
|
+
await record.promise?.catch(() => undefined);
|
|
1709
|
+
}
|
|
1710
|
+
catch {
|
|
1711
|
+
// resume failed; leave the original summary and surface the mismatch below
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
for (const { record } of records)
|
|
1715
|
+
this.subagentStore.markDelivered(record.agentId);
|
|
1716
|
+
return records.map(({ record }) => this.snapshotSubagent(record));
|
|
1717
|
+
}
|
|
1718
|
+
/**
|
|
1719
|
+
* Dynamic workflow (option C): runs an LLM-authored JS orchestration script in
|
|
1720
|
+
* a QuickJS sandbox. Each agent() call in the script becomes a real scheduled
|
|
1721
|
+
* subagent (same route resolution, ChildRunner, scheduler, schema validation
|
|
1722
|
+
* as spawn_agent), so the script expresses deterministic control flow while
|
|
1723
|
+
* the runtime keeps owning concurrency/budget/retry.
|
|
1724
|
+
*
|
|
1725
|
+
* Foreground entry point (used by `-p`/headless and tests): awaits to
|
|
1726
|
+
* completion and returns the result. Background runs go through startWorkflow.
|
|
1727
|
+
*/
|
|
1728
|
+
async runWorkflow(cwd, options) {
|
|
1729
|
+
return this.executeWorkflow(cwd, {
|
|
1730
|
+
script: options.script,
|
|
1731
|
+
args: options.args,
|
|
1732
|
+
parentToolCallId: options.parentToolCallId,
|
|
1733
|
+
abortSignal: options.abortSignal,
|
|
1734
|
+
directEmit: options.emitUpdate,
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
/**
|
|
1738
|
+
* Starts a workflow in the BACKGROUND (option C Phase 4): returns a runId
|
|
1739
|
+
* immediately; the script runs detached, its agents stream progress through
|
|
1740
|
+
* the queued channel (drained at turn boundaries like spawn_agent), and its
|
|
1741
|
+
* result is ingested at the next turn. Collect explicitly with waitWorkflow.
|
|
1742
|
+
*/
|
|
1743
|
+
startWorkflow(cwd, options) {
|
|
1744
|
+
const runId = randomUUID();
|
|
1745
|
+
const abortController = new AbortController();
|
|
1746
|
+
const composed = composeAbortSignals([options.abortSignal, abortController.signal]);
|
|
1747
|
+
if (composed) {
|
|
1748
|
+
composed.addEventListener("abort", () => abortController.abort(composed.reason), { once: true });
|
|
1749
|
+
}
|
|
1750
|
+
const record = {
|
|
1751
|
+
runId,
|
|
1752
|
+
title: options.title ?? "workflow",
|
|
1753
|
+
status: "running",
|
|
1754
|
+
agentCount: 0,
|
|
1755
|
+
snapshots: [],
|
|
1756
|
+
logs: [],
|
|
1757
|
+
abortController,
|
|
1758
|
+
waiters: new Set(),
|
|
1759
|
+
createdAt: Date.now(),
|
|
1760
|
+
parentToolCallId: options.parentToolCallId,
|
|
1761
|
+
};
|
|
1762
|
+
this.workflowRuns.set(runId, record);
|
|
1763
|
+
record.promise = this.executeWorkflow(cwd, {
|
|
1764
|
+
script: options.script,
|
|
1765
|
+
args: options.args,
|
|
1766
|
+
parentToolCallId: options.parentToolCallId,
|
|
1767
|
+
abortSignal: abortController.signal,
|
|
1768
|
+
queueUpdates: true,
|
|
1769
|
+
}).then((out) => {
|
|
1770
|
+
record.agentCount = out.agentCount;
|
|
1771
|
+
record.snapshots = out.snapshots;
|
|
1772
|
+
record.logs = out.logs;
|
|
1773
|
+
record.result = out.result;
|
|
1774
|
+
record.status = out.result.ok ? "completed" : (abortController.signal.aborted ? "cancelled" : "failed");
|
|
1775
|
+
}, (error) => {
|
|
1776
|
+
record.result = { ok: false, error: error?.message || String(error) };
|
|
1777
|
+
record.status = "failed";
|
|
1778
|
+
}).finally(() => {
|
|
1779
|
+
record.updatedAt = Date.now();
|
|
1780
|
+
this.pendingWorkflowDeliveries.add(runId);
|
|
1781
|
+
for (const waiter of record.waiters)
|
|
1782
|
+
waiter();
|
|
1783
|
+
record.waiters.clear();
|
|
1784
|
+
});
|
|
1785
|
+
return { runId, title: record.title };
|
|
1786
|
+
}
|
|
1787
|
+
/** Blocks until a background workflow reaches a final state (or times out). */
|
|
1788
|
+
async waitWorkflow(runId, timeoutMs) {
|
|
1789
|
+
const record = this.workflowRuns.get(runId);
|
|
1790
|
+
if (!record)
|
|
1791
|
+
return undefined;
|
|
1792
|
+
if (record.status === "running") {
|
|
1793
|
+
const limit = normalizeWaitTimeout(timeoutMs);
|
|
1794
|
+
let waiter;
|
|
1795
|
+
await Promise.race([
|
|
1796
|
+
new Promise((resolve) => { waiter = resolve; record.waiters.add(resolve); }),
|
|
1797
|
+
new Promise((resolve) => setTimeout(resolve, limit)),
|
|
1798
|
+
]).finally(() => { if (waiter)
|
|
1799
|
+
record.waiters.delete(waiter); });
|
|
1800
|
+
}
|
|
1801
|
+
if (record.status !== "running")
|
|
1802
|
+
this.pendingWorkflowDeliveries.delete(runId);
|
|
1803
|
+
return this.snapshotWorkflow(record);
|
|
1804
|
+
}
|
|
1805
|
+
/** Cancels a running background workflow. */
|
|
1806
|
+
closeWorkflow(runId) {
|
|
1807
|
+
const record = this.workflowRuns.get(runId);
|
|
1808
|
+
if (!record)
|
|
1809
|
+
return undefined;
|
|
1810
|
+
if (record.status === "running")
|
|
1811
|
+
record.abortController.abort(new Error("workflow cancelled"));
|
|
1812
|
+
return this.snapshotWorkflow(record);
|
|
1813
|
+
}
|
|
1814
|
+
listWorkflows() {
|
|
1815
|
+
return [...this.workflowRuns.values()].map((record) => this.snapshotWorkflow(record));
|
|
1816
|
+
}
|
|
1817
|
+
snapshotWorkflow(record) {
|
|
1818
|
+
return {
|
|
1819
|
+
runId: record.runId,
|
|
1820
|
+
title: record.title,
|
|
1821
|
+
status: record.status,
|
|
1822
|
+
agentCount: record.agentCount,
|
|
1823
|
+
result: record.result,
|
|
1824
|
+
logs: record.logs,
|
|
1825
|
+
snapshots: record.snapshots,
|
|
1826
|
+
};
|
|
1827
|
+
}
|
|
1828
|
+
/** Injects completed background-workflow results before the next turn (§5 analog). */
|
|
1829
|
+
flushWorkflowDeliveries() {
|
|
1830
|
+
if (this.pendingWorkflowDeliveries.size === 0)
|
|
1831
|
+
return;
|
|
1832
|
+
for (const runId of [...this.pendingWorkflowDeliveries]) {
|
|
1833
|
+
this.pendingWorkflowDeliveries.delete(runId);
|
|
1834
|
+
const record = this.workflowRuns.get(runId);
|
|
1835
|
+
if (!record || record.status === "running" || record.deliveredAt)
|
|
1836
|
+
continue;
|
|
1837
|
+
record.deliveredAt = Date.now();
|
|
1838
|
+
this.injectSystemReminder(buildWorkflowDeliveryNotice(this.snapshotWorkflow(record)));
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
async executeWorkflow(cwd, options) {
|
|
1842
|
+
const profiles = discoverAgentProfiles(cwd, "both").profiles;
|
|
1843
|
+
const runRecords = [];
|
|
1844
|
+
const logs = [];
|
|
1845
|
+
// Per-run isolation (option C review): a token ceiling that aborts only this
|
|
1846
|
+
// run (never the parent) and a concurrency sub-cap below the global limit so
|
|
1847
|
+
// a workflow can't starve interactive subagents.
|
|
1848
|
+
const poolLimit = this.budgetLedger?.poolLimit;
|
|
1849
|
+
const runTokenCeiling = poolLimit !== undefined
|
|
1850
|
+
? Math.max(1, Math.floor(poolLimit * (1 - PARENT_POOL_RESERVE_RATIO)))
|
|
1851
|
+
: Number.POSITIVE_INFINITY;
|
|
1852
|
+
const runSpent = () => runRecords.reduce((sum, r) => sum + (r.usage ? r.usage.promptTokens + r.usage.completionTokens : 0), 0);
|
|
1853
|
+
const interactiveReserve = 2;
|
|
1854
|
+
const globalCap = Math.max(1, this.subagentsConfig.maxActiveSubagents ?? 8);
|
|
1855
|
+
const workflowConcurrency = Math.max(1, globalCap - interactiveReserve);
|
|
1856
|
+
const gate = new WorkflowConcurrencyGate(workflowConcurrency);
|
|
1857
|
+
const dispatchAgent = async (spec) => {
|
|
1858
|
+
if (runSpent() >= runTokenCeiling) {
|
|
1859
|
+
return { ok: false, error: "workflow token ceiling reached; not launching more agents" };
|
|
1860
|
+
}
|
|
1861
|
+
const baseProfile = findAgentProfile(profiles, spec.opts.agentType ?? "default")
|
|
1862
|
+
?? findAgentProfile(profiles, "default");
|
|
1863
|
+
if (!baseProfile)
|
|
1864
|
+
return { ok: false, error: "no default subagent profile available" };
|
|
1865
|
+
// Workflow agents are readonly-by-default; mode upgrades come only from the
|
|
1866
|
+
// profile, never from the script (security invariant).
|
|
1867
|
+
const unsupported = baseProfile.mode !== "readonly" && baseProfile.mode !== "write_worktree";
|
|
1868
|
+
if (unsupported)
|
|
1869
|
+
return { ok: false, error: `profile "${baseProfile.name}" mode ${baseProfile.mode} not supported` };
|
|
1870
|
+
// Default-no-network: unattended orchestration of net-capable agents is new
|
|
1871
|
+
// authority in aggregate (option C review), so strip web tools unless the
|
|
1872
|
+
// script opts in with agentType pointing at a profile that includes them.
|
|
1873
|
+
const profile = {
|
|
1874
|
+
...baseProfile,
|
|
1875
|
+
tools: { ...baseProfile.tools, exclude: [...(baseProfile.tools.exclude ?? []), "web_fetch", "web_search"] },
|
|
1876
|
+
};
|
|
1877
|
+
let route;
|
|
1878
|
+
try {
|
|
1879
|
+
route = this.resolveRouteForSubagent(profile, spec.opts.category, {
|
|
1880
|
+
model: spec.opts.model,
|
|
1881
|
+
effort: parseThinkingLevel(spec.opts.effort),
|
|
1882
|
+
});
|
|
1883
|
+
}
|
|
1884
|
+
catch (error) {
|
|
1885
|
+
return { ok: false, error: error?.message || String(error) };
|
|
1886
|
+
}
|
|
1887
|
+
const baseTask = spec.opts.schema !== undefined
|
|
1888
|
+
? appendOutputSchemaInstructions(spec.prompt, spec.opts.schema)
|
|
1889
|
+
: spec.prompt;
|
|
1890
|
+
const record = this.createSubagentThreadRecord({
|
|
1891
|
+
profile,
|
|
1892
|
+
task: baseTask,
|
|
1893
|
+
parentToolCallId: options.parentToolCallId,
|
|
1894
|
+
parentToolName: "run_workflow",
|
|
1895
|
+
route,
|
|
1896
|
+
workflowInternal: true,
|
|
1897
|
+
});
|
|
1898
|
+
runRecords.push(record);
|
|
1899
|
+
this.subagentStore.set(record);
|
|
1900
|
+
const admissionError = this.admitSubagentProfile(record, profile.approval);
|
|
1901
|
+
if (admissionError) {
|
|
1902
|
+
this.finalizeSubagentBlocked(record, admissionError, { directEmit: options.directEmit, queueUpdates: options.queueUpdates });
|
|
1903
|
+
return { ok: false, error: admissionError };
|
|
1904
|
+
}
|
|
1905
|
+
// Leaf-only concurrency permit (option C review M5): held ONLY around this
|
|
1906
|
+
// agent's dispatch, never across parallel/pipeline composition.
|
|
1907
|
+
await gate.acquire();
|
|
1908
|
+
try {
|
|
1909
|
+
record.promise = this.dispatchSubagentRun(record, baseTask, cwd, {
|
|
1910
|
+
approval: profile.approval,
|
|
1911
|
+
abortSignal: options.abortSignal,
|
|
1912
|
+
directEmit: options.directEmit,
|
|
1913
|
+
queueUpdates: options.queueUpdates,
|
|
1914
|
+
});
|
|
1915
|
+
await record.promise;
|
|
1916
|
+
}
|
|
1917
|
+
finally {
|
|
1918
|
+
gate.release();
|
|
1919
|
+
}
|
|
1920
|
+
this.subagentStore.markDelivered(record.agentId);
|
|
1921
|
+
if (record.status !== "completed") {
|
|
1922
|
+
return { ok: false, error: record.error || `agent ${record.nickname} ended: ${record.finalReason ?? record.status}` };
|
|
1923
|
+
}
|
|
1924
|
+
if (spec.opts.schema === undefined) {
|
|
1925
|
+
return { ok: true, value: record.summary };
|
|
1926
|
+
}
|
|
1927
|
+
// Structured output: validate, one corrective retry, then fall back to raw.
|
|
1928
|
+
let validated = validateStructuredSummary(record.summary, spec.opts.schema);
|
|
1929
|
+
if (!validated.ok) {
|
|
1930
|
+
try {
|
|
1931
|
+
await this.sendSubAgentInput(record.agentId, buildSchemaCorrectionPrompt(spec.opts.schema, record.summary), cwd, { abortSignal: options.abortSignal });
|
|
1932
|
+
await record.promise?.catch(() => undefined);
|
|
1933
|
+
validated = validateStructuredSummary(record.summary, spec.opts.schema);
|
|
1934
|
+
}
|
|
1935
|
+
catch {
|
|
1936
|
+
// resume failed; fall through to raw summary
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
return { ok: true, value: validated.ok ? validated.value : record.summary };
|
|
1940
|
+
};
|
|
1941
|
+
const result = await runWorkflow({
|
|
1942
|
+
script: options.script,
|
|
1943
|
+
args: options.args,
|
|
1944
|
+
dispatchAgent,
|
|
1945
|
+
onLog: (message) => logs.push(message),
|
|
1946
|
+
onPhase: (title) => logs.push(`— phase: ${title} —`),
|
|
1947
|
+
budget: {
|
|
1948
|
+
total: this.budgetLedger?.poolLimit ?? null,
|
|
1949
|
+
spent: () => runRecords.reduce((sum, r) => sum + (r.usage ? r.usage.promptTokens + r.usage.completionTokens : 0), 0),
|
|
1950
|
+
remaining: () => {
|
|
1951
|
+
const limit = this.budgetLedger?.poolLimit;
|
|
1952
|
+
if (limit === undefined)
|
|
1953
|
+
return Number.POSITIVE_INFINITY;
|
|
1954
|
+
return Math.max(0, (this.budgetLedger?.remaining() ?? 0));
|
|
1955
|
+
},
|
|
1956
|
+
},
|
|
1957
|
+
signal: options.abortSignal,
|
|
1958
|
+
});
|
|
1959
|
+
return {
|
|
1960
|
+
result,
|
|
1961
|
+
agentCount: runRecords.length,
|
|
1962
|
+
logs,
|
|
1963
|
+
snapshots: runRecords.map((record) => this.snapshotSubagent(record)),
|
|
1964
|
+
};
|
|
1965
|
+
}
|
|
1590
1966
|
/** Marks a child's full summary as delivered to parent context (design §3.3). */
|
|
1591
1967
|
markSubagentDelivered(agentId) {
|
|
1592
1968
|
this.subagentStore.markDelivered(agentId);
|
|
@@ -1672,6 +2048,19 @@ export class Agent {
|
|
|
1672
2048
|
this.subagentStore.notifyWaiters(record);
|
|
1673
2049
|
this.maybeEnqueueIngestion(record, options);
|
|
1674
2050
|
},
|
|
2051
|
+
onTransportRetryExhausted: (attempts) => {
|
|
2052
|
+
record.status = "failed";
|
|
2053
|
+
// failed_transient stays resumable, so the parent can still send_input
|
|
2054
|
+
// to recover the child with its context intact.
|
|
2055
|
+
record.finalReason = "failed_transient";
|
|
2056
|
+
record.error = `Provider transport error persisted after ${attempts} attempts.`;
|
|
2057
|
+
record.updatedAt = Date.now();
|
|
2058
|
+
void this.runSubagentLifecycleHookFor(record, cwd, "SubagentStop", record.status, record.error);
|
|
2059
|
+
this.emitSubagentLifecycle(record, options, "failed", undefined, record.error);
|
|
2060
|
+
this.subagentStore.persist(record);
|
|
2061
|
+
this.subagentStore.notifyWaiters(record);
|
|
2062
|
+
this.maybeEnqueueIngestion(record, options);
|
|
2063
|
+
},
|
|
1675
2064
|
});
|
|
1676
2065
|
}
|
|
1677
2066
|
emitSubagentLifecycle(record, options, status, event, message) {
|
|
@@ -1701,7 +2090,13 @@ export class Agent {
|
|
|
1701
2090
|
// Subagent lifecycle hooks are observe-only; never fail the subagent.
|
|
1702
2091
|
}
|
|
1703
2092
|
}
|
|
1704
|
-
|
|
2093
|
+
/**
|
|
2094
|
+
* Resolves a child's model route. Priority, highest first (design v2 §1.1):
|
|
2095
|
+
* call-site override (model/effort) > profile.model > category > inherit parent.
|
|
2096
|
+
* The call-site override is what lets the model say "opus for this reviewer,
|
|
2097
|
+
* haiku for these twenty scouts" per spawn/batch member at request time.
|
|
2098
|
+
*/
|
|
2099
|
+
resolveRouteForSubagent(profile, category, override) {
|
|
1705
2100
|
const parentRoute = {
|
|
1706
2101
|
providerId: this.providerId,
|
|
1707
2102
|
model: this.apiModel,
|
|
@@ -1713,18 +2108,24 @@ export class Agent {
|
|
|
1713
2108
|
if ("error" in resolved) {
|
|
1714
2109
|
throw new Error(resolved.error);
|
|
1715
2110
|
}
|
|
2111
|
+
let route = resolved.route;
|
|
1716
2112
|
if (profile.model && profile.model !== "inherit") {
|
|
1717
2113
|
const model = resolveModelRoute(profile.model, parentRoute.providerId);
|
|
1718
2114
|
if (model.model !== "inherit") {
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
2115
|
+
route = { ...route, providerId: model.providerId, model: model.model, inherited: false };
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
// Call-site override beats profile and category.
|
|
2119
|
+
if (override?.model) {
|
|
2120
|
+
const model = resolveModelRoute(override.model, route.providerId);
|
|
2121
|
+
if (model.model !== "inherit") {
|
|
2122
|
+
route = { ...route, providerId: model.providerId, model: model.model, inherited: false };
|
|
1725
2123
|
}
|
|
1726
2124
|
}
|
|
1727
|
-
|
|
2125
|
+
if (override?.effort) {
|
|
2126
|
+
route = { ...route, thinkingLevel: override.effort, inherited: false };
|
|
2127
|
+
}
|
|
2128
|
+
return route;
|
|
1728
2129
|
}
|
|
1729
2130
|
createSubagentThreadRecord(options) {
|
|
1730
2131
|
const now = Date.now();
|
|
@@ -1736,6 +2137,7 @@ export class Agent {
|
|
|
1736
2137
|
profile: options.profile,
|
|
1737
2138
|
category: options.route?.category,
|
|
1738
2139
|
route: options.route,
|
|
2140
|
+
workflowInternal: options.workflowInternal,
|
|
1739
2141
|
parentToolCallId: options.parentToolCallId,
|
|
1740
2142
|
parentToolName: options.parentToolName,
|
|
1741
2143
|
status: "queued",
|
|
@@ -1765,6 +2167,12 @@ export class Agent {
|
|
|
1765
2167
|
childMode = "default";
|
|
1766
2168
|
tools = createWorktreeChildTools(childCwd, record.profile.tools.include);
|
|
1767
2169
|
}
|
|
2170
|
+
else {
|
|
2171
|
+
// Readonly children share the parent's tool instances; isolate the only
|
|
2172
|
+
// one with mutable file state (read → its FileStateTracker) so concurrent
|
|
2173
|
+
// fan-out members never race shared tool state (design v2 §2).
|
|
2174
|
+
tools = isolateReadonlyChildFileTools(tools);
|
|
2175
|
+
}
|
|
1768
2176
|
const childToolNames = tools.map((tool) => tool.name);
|
|
1769
2177
|
const route = record.route ?? {
|
|
1770
2178
|
providerId: this.providerId,
|
|
@@ -1916,7 +2324,7 @@ export class Agent {
|
|
|
1916
2324
|
}
|
|
1917
2325
|
resolveSubagentTargets(agentIds) {
|
|
1918
2326
|
if (!agentIds || agentIds.length === 0) {
|
|
1919
|
-
return this.subagentStore.values().filter((record) => record.status !== "closed");
|
|
2327
|
+
return this.subagentStore.values().filter((record) => record.status !== "closed" && !record.workflowInternal);
|
|
1920
2328
|
}
|
|
1921
2329
|
return agentIds.map((id) => {
|
|
1922
2330
|
const record = this.subagentStore.get(id);
|
|
@@ -7,10 +7,19 @@
|
|
|
7
7
|
* fails.
|
|
8
8
|
*/
|
|
9
9
|
import type { CompactOptions, CompactResult } from "./compact.js";
|
|
10
|
-
import type { Message, Provider } from "../types.js";
|
|
10
|
+
import type { Message, Provider, ProviderMessage } from "../types.js";
|
|
11
11
|
export interface LLMCompactOptions extends CompactOptions {
|
|
12
12
|
provider: Provider;
|
|
13
13
|
model: string;
|
|
14
14
|
thinkingLevel?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
|
|
15
15
|
}
|
|
16
|
+
export declare const COMPACT_SYSTEM_PROMPT = "You are a conversation summarizer. Your job is to produce a structured\nsummary of an earlier portion of a software-engineering assistant's\nconversation so that the assistant can continue working without the full\nhistory. Preserve fidelity over brevity where the user's intent, file\npaths, or decisions are concerned. Output ONLY the summary, no preamble.";
|
|
17
|
+
export declare const COMPACT_INSTRUCTIONS = "Summarize the conversation above using exactly these 9 sections, each\npreceded by the literal heading on its own line. If a section has no\ncontent, write \"None\".\n\n1. Primary Request and Intent\n - What the user ultimately wants, in their own framing.\n\n2. Key Technical Concepts\n - Libraries, frameworks, architectural patterns referenced.\n\n3. Files and Code Sections\n - Files read, written, or discussed. Include full paths and a one-line note.\n\n4. Errors and Fixes\n - Bugs encountered and how they were resolved.\n\n5. Problem Solving\n - Non-trivial debugging or design decisions.\n\n6. All User Messages\n - Every user message, verbatim, in order. Do not summarize here.\n\n7. Pending Tasks\n - Work that was planned but not yet completed.\n\n8. Current Work\n - What was being actively worked on when the summary was taken.\n\n9. Optional Next Step\n - The single most natural next action, if obvious.";
|
|
16
18
|
export declare function compactMessagesWithLLM(messages: Message[], options: LLMCompactOptions): Promise<CompactResult>;
|
|
19
|
+
/**
|
|
20
|
+
* Build the two-message prompt that asks the model for a 9-section summary of
|
|
21
|
+
* `oldMessages`. Shared by the non-streaming overflow path (`generateSummary`)
|
|
22
|
+
* and the streaming manual `/compact` path (`Agent.summarizeForCompaction`).
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildCompactionPromptMessages(oldMessages: Message[]): ProviderMessage[];
|
|
25
|
+
export declare function serializeTranscript(messages: Message[]): string;
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
* fails.
|
|
8
8
|
*/
|
|
9
9
|
import { compactMessages as compactMessagesHeuristic } from "./compact.js";
|
|
10
|
-
const COMPACT_SYSTEM_PROMPT = `You are a conversation summarizer. Your job is to produce a structured
|
|
10
|
+
export const COMPACT_SYSTEM_PROMPT = `You are a conversation summarizer. Your job is to produce a structured
|
|
11
11
|
summary of an earlier portion of a software-engineering assistant's
|
|
12
12
|
conversation so that the assistant can continue working without the full
|
|
13
13
|
history. Preserve fidelity over brevity where the user's intent, file
|
|
14
14
|
paths, or decisions are concerned. Output ONLY the summary, no preamble.`;
|
|
15
|
-
const COMPACT_INSTRUCTIONS = `Summarize the conversation above using exactly these 9 sections, each
|
|
15
|
+
export const COMPACT_INSTRUCTIONS = `Summarize the conversation above using exactly these 9 sections, each
|
|
16
16
|
preceded by the literal heading on its own line. If a section has no
|
|
17
17
|
content, write "None".
|
|
18
18
|
|
|
@@ -79,22 +79,30 @@ export async function compactMessagesWithLLM(messages, options) {
|
|
|
79
79
|
droppedEntries: oldMessages.length,
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Build the two-message prompt that asks the model for a 9-section summary of
|
|
84
|
+
* `oldMessages`. Shared by the non-streaming overflow path (`generateSummary`)
|
|
85
|
+
* and the streaming manual `/compact` path (`Agent.summarizeForCompaction`).
|
|
86
|
+
*/
|
|
87
|
+
export function buildCompactionPromptMessages(oldMessages) {
|
|
83
88
|
const transcript = serializeTranscript(oldMessages);
|
|
84
|
-
|
|
89
|
+
return [
|
|
85
90
|
{ role: "system", content: COMPACT_SYSTEM_PROMPT },
|
|
86
91
|
{
|
|
87
92
|
role: "user",
|
|
88
93
|
content: `Conversation to summarize:\n\n${transcript}\n\n---\n\n${COMPACT_INSTRUCTIONS}`,
|
|
89
94
|
},
|
|
90
95
|
];
|
|
96
|
+
}
|
|
97
|
+
async function generateSummary(oldMessages, options) {
|
|
98
|
+
const messages = buildCompactionPromptMessages(oldMessages);
|
|
91
99
|
return options.provider.complete(messages, {
|
|
92
100
|
model: options.model,
|
|
93
101
|
temperature: 0.2,
|
|
94
102
|
thinkingLevel: options.thinkingLevel ?? "off",
|
|
95
103
|
});
|
|
96
104
|
}
|
|
97
|
-
function serializeTranscript(messages) {
|
|
105
|
+
export function serializeTranscript(messages) {
|
|
98
106
|
const lines = [];
|
|
99
107
|
for (const message of messages) {
|
|
100
108
|
switch (message.role) {
|
|
@@ -11,6 +11,36 @@ export interface CompactResult {
|
|
|
11
11
|
messages?: Message[];
|
|
12
12
|
droppedEntries?: number;
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* The split of a session log into (metadata, old-to-summarize, kept-verbatim)
|
|
16
|
+
* when it is large enough to compact. `compactable: false` means there aren't
|
|
17
|
+
* enough turns past the last summary to bother.
|
|
18
|
+
*
|
|
19
|
+
* Extracted so callers that supply their OWN summary (e.g. the LLM-backed
|
|
20
|
+
* manual `/compact`) can reuse the exact same turn-boundary logic instead of
|
|
21
|
+
* forking it. `compactSessionEntries` is just this plan + a heuristic summary.
|
|
22
|
+
*/
|
|
23
|
+
export type SessionCompactionPlan = {
|
|
24
|
+
compactable: false;
|
|
25
|
+
} | {
|
|
26
|
+
compactable: true;
|
|
27
|
+
metadataEntries: SessionLogEntry[];
|
|
28
|
+
oldEntries: SessionLogEntry[];
|
|
29
|
+
keptEntries: SessionLogEntry[];
|
|
30
|
+
};
|
|
31
|
+
export declare function planSessionCompaction(entries: SessionLogEntry[], options?: CompactOptions): SessionCompactionPlan;
|
|
32
|
+
/**
|
|
33
|
+
* Assemble the post-compaction entry list from a plan and a (possibly
|
|
34
|
+
* LLM-generated) summary string. The summary entry is keyed off the full
|
|
35
|
+
* original `entries` so its id never collides with a prior summary.
|
|
36
|
+
*/
|
|
37
|
+
export declare function buildCompactedEntries(entries: SessionLogEntry[], plan: Extract<SessionCompactionPlan, {
|
|
38
|
+
compactable: true;
|
|
39
|
+
}>, summary: string): SessionLogEntry[];
|
|
40
|
+
/** Flatten a plan's old entries into messages for an external summarizer. */
|
|
41
|
+
export declare function planOldMessages(plan: Extract<SessionCompactionPlan, {
|
|
42
|
+
compactable: true;
|
|
43
|
+
}>): Message[];
|
|
14
44
|
export declare function compactSessionEntries(entries: SessionLogEntry[], options?: CompactOptions): CompactResult;
|
|
15
45
|
export declare function compactMessages(messages: Message[], options?: CompactOptions): CompactResult;
|
|
16
46
|
/**
|