@diffexai/diffex 0.2.6 → 0.3.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/CHANGELOG.md +23 -0
- package/dist/core/agent-session.d.ts +5 -0
- package/dist/core/agent-session.js +89 -19
- package/dist/core/compaction/compaction.js +3 -1
- package/dist/core/model-runtime.js +3 -3
- package/dist/core/provider-composer.js +1 -1
- package/dist/core/sdk.d.ts +2 -1
- package/dist/core/sdk.js +18 -6
- package/dist/core/subagents/controller.d.ts +4 -3
- package/dist/core/subagents/controller.js +173 -37
- package/dist/core/subagents/types.d.ts +13 -0
- package/dist/core/tools/bash.d.ts +4 -0
- package/dist/core/tools/bash.js +14 -5
- package/dist/core/tools/subagents.d.ts +12 -10
- package/dist/core/tools/subagents.js +61 -49
- package/dist/node_modules/@diffexai/diffex-agent-core/distribution-components.json +4 -4
- package/dist/node_modules/@diffexai/diffex-agent-core/distribution-files.json +1 -1
- package/dist/node_modules/@diffexai/diffex-agent-core/package.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/api/lazy.d.ts +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/api/lazy.js +8 -8
- package/dist/node_modules/@diffexai/diffex-ai/dist/models.js +4 -4
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/.manifest.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/opencode.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/openrouter.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/distribution-components.json +3 -3
- package/dist/node_modules/@diffexai/diffex-ai/distribution-files.json +8 -8
- package/dist/node_modules/@diffexai/diffex-ai/package.json +1 -1
- package/dist/node_modules/@diffexai/diffex-client/distribution-components.json +3 -3
- package/dist/node_modules/@diffexai/diffex-client/distribution-files.json +1 -1
- package/dist/node_modules/@diffexai/diffex-client/package.json +1 -1
- package/dist/node_modules/@diffexai/diffex-harness-state/distribution-components.json +2 -2
- package/dist/node_modules/@diffexai/diffex-harness-state/distribution-files.json +1 -1
- package/dist/node_modules/@diffexai/diffex-harness-state/package.json +1 -1
- package/dist/node_modules/@diffexai/diffex-protocol/distribution-components.json +2 -2
- package/dist/node_modules/@diffexai/diffex-protocol/distribution-files.json +1 -1
- package/dist/node_modules/@diffexai/diffex-protocol/package.json +1 -1
- package/dist/node_modules/@diffexai/diffex-telemetry/distribution-components.json +2 -2
- package/dist/node_modules/@diffexai/diffex-telemetry/distribution-files.json +1 -1
- package/dist/node_modules/@diffexai/diffex-telemetry/package.json +1 -1
- package/dist/node_modules/@diffexai/diffex-tui/distribution-components.json +2 -2
- package/dist/node_modules/@diffexai/diffex-tui/distribution-files.json +1 -1
- package/dist/node_modules/@diffexai/diffex-tui/package.json +1 -1
- package/dist/utils/owned-shell-processes.d.ts +8 -0
- package/dist/utils/owned-shell-processes.js +61 -0
- package/distribution-components.json +11 -11
- package/distribution-files.json +51 -43
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/release/distribution-manifest.json +4 -4
- package/release/install-package-lock.json +5 -5
- package/release/install-package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.0] - 2026-09-20
|
|
4
|
+
|
|
5
|
+
### New Features
|
|
6
|
+
|
|
7
|
+
- Added nonblocking `agent_status` for immediate child-status and result inspection, with automatic coordination for child completion. See [Automatic Sub-agent Coordination](docs/usage.md#automatic-sub-agent-coordination) ([#234](https://github.com/diffexai/diffex/pull/234)).
|
|
8
|
+
- Added automatic subagent supervision with renewable work allowances and parent-deadline bounds. See [SDK tools](docs/sdk.md#tools) ([#233](https://github.com/diffexai/diffex/pull/233)).
|
|
9
|
+
|
|
10
|
+
### Breaking Changes
|
|
11
|
+
|
|
12
|
+
- Replaced the blocking `wait_for_agents` tool with nonblocking `agent_status`; rely on automatic child-result coordination instead of tool-managed waits ([#234](https://github.com/diffexai/diffex/pull/234)).
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Fixed automatic compaction to check complete tool-result batches ([#233](https://github.com/diffexai/diffex/pull/233)).
|
|
17
|
+
- Fixed subagent completion and cancellation, including renewable work-allowance supervision ([#233](https://github.com/diffexai/diffex/pull/233)).
|
|
18
|
+
- Fixed session and subagent cancellation to terminate owned background shell processes ([#233](https://github.com/diffexai/diffex/pull/233)).
|
|
19
|
+
|
|
20
|
+
## [0.2.7] - 2026-09-18
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- Improved stability and fixed UI issues.
|
|
25
|
+
|
|
3
26
|
## [0.2.6] - 2026-09-18
|
|
4
27
|
|
|
5
28
|
### Fixed
|
|
@@ -263,6 +263,8 @@ export declare class AgentSession {
|
|
|
263
263
|
private readonly _bashAbortControllers;
|
|
264
264
|
private _pendingBashMessages;
|
|
265
265
|
private readonly _ownedOperations;
|
|
266
|
+
private readonly _shellProcesses;
|
|
267
|
+
private readonly _bashOperations;
|
|
266
268
|
private _extensionRunner;
|
|
267
269
|
private _turnIndex;
|
|
268
270
|
private _resourceLoader;
|
|
@@ -561,6 +563,9 @@ export declare class AgentSession {
|
|
|
561
563
|
*/
|
|
562
564
|
abort(): Promise<void>;
|
|
563
565
|
private _abort;
|
|
566
|
+
/** Stop and verify all shell processes owned by this session, including background jobs. */
|
|
567
|
+
terminateOwnedShellProcesses(): Promise<void>;
|
|
568
|
+
get ownedShellProcessCount(): number;
|
|
564
569
|
waitForIdle(): Promise<void>;
|
|
565
570
|
private _emitModelSelect;
|
|
566
571
|
/**
|
|
@@ -18,6 +18,7 @@ import { NOOP_TELEMETRY_CONTEXT, uuidv7, } from "@diffexai/diffex-agent-core";
|
|
|
18
18
|
import { contentText } from "@diffexai/diffex-ai";
|
|
19
19
|
import { clampThinkingLevel, cleanupSessionResources, getSupportedThinkingLevels, isContextOverflow, isRecoverableLength, isRetryableAssistantError, modelsAreEqual, resetApiProviders, streamSimple, } from "@diffexai/diffex-ai/compat";
|
|
20
20
|
import { getThemeByName, theme } from "../modes/interactive/theme/theme.js";
|
|
21
|
+
import { OwnedShellProcesses } from "../utils/owned-shell-processes.js";
|
|
21
22
|
import { canonicalizePath, resolvePath } from "../utils/paths.js";
|
|
22
23
|
import { sleep } from "../utils/sleep.js";
|
|
23
24
|
import { normalizeToolResultImages } from "../utils/tool-result-images.js";
|
|
@@ -161,6 +162,8 @@ export class AgentSession {
|
|
|
161
162
|
_bashAbortControllers = new Set();
|
|
162
163
|
_pendingBashMessages = [];
|
|
163
164
|
_ownedOperations = new Set();
|
|
165
|
+
_shellProcesses = new OwnedShellProcesses();
|
|
166
|
+
_bashOperations = new Set();
|
|
164
167
|
// Extension system
|
|
165
168
|
_extensionRunner;
|
|
166
169
|
_turnIndex = 0;
|
|
@@ -402,10 +405,25 @@ export class AgentSession {
|
|
|
402
405
|
this.agent.prepareNextTurnWithContext = async (turn, signal) => {
|
|
403
406
|
const previousSnapshot = await previousPrepareNextTurnWithContext?.(turn, signal);
|
|
404
407
|
const previousContext = previousSnapshot?.context ?? turn.context;
|
|
408
|
+
let messages = previousContext.messages;
|
|
409
|
+
// Tool output is part of the next request even though the model's usage
|
|
410
|
+
// only describes the request before that output. Check every complete batch.
|
|
411
|
+
if (turn.toolResults.length > 0 && !signal?.aborted && this.model) {
|
|
412
|
+
const settings = this.settingsManager.getCompactionSettings();
|
|
413
|
+
if (shouldCompact(estimateContextTokens(messages).tokens, this.model.contextWindow, settings)) {
|
|
414
|
+
const beforeCompaction = this.agent.state.messages;
|
|
415
|
+
await this._runAutoCompaction("threshold", true, signal);
|
|
416
|
+
if (this.agent.state.messages !== beforeCompaction) {
|
|
417
|
+
messages = this.agent.state.messages;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
signal?.throwIfAborted();
|
|
405
422
|
return {
|
|
406
423
|
...previousSnapshot,
|
|
407
424
|
context: {
|
|
408
425
|
...previousContext,
|
|
426
|
+
messages,
|
|
409
427
|
systemPrompt: this._activeRunCollaborationState?.effectiveSystemPrompt ??
|
|
410
428
|
this._deriveEffectivePrompt(this._systemPromptOverride ?? this._baseSystemPrompt, this._activeRunCollaborationMode ?? this._collaborationMode),
|
|
411
429
|
tools: (this._activeRunCollaborationState?.tools ?? this.agent.state.tools).slice(),
|
|
@@ -836,7 +854,7 @@ export class AgentSession {
|
|
|
836
854
|
failures.push(error);
|
|
837
855
|
}
|
|
838
856
|
const ownedOperationsIdle = this._waitForOwnedOperations();
|
|
839
|
-
const cleanupResults = await Promise.allSettled([childCleanup, rootIdle, ownedOperationsIdle].filter((promise) => promise !== undefined));
|
|
857
|
+
const cleanupResults = await Promise.allSettled([childCleanup, rootIdle, ownedOperationsIdle, this.terminateOwnedShellProcesses()].filter((promise) => promise !== undefined));
|
|
840
858
|
for (const result of cleanupResults) {
|
|
841
859
|
if (result.status !== "rejected")
|
|
842
860
|
continue;
|
|
@@ -1429,7 +1447,10 @@ export class AgentSession {
|
|
|
1429
1447
|
else
|
|
1430
1448
|
await this.agent.continue();
|
|
1431
1449
|
firstRun = false;
|
|
1432
|
-
|
|
1450
|
+
const next = await this._handlePostAgentRun();
|
|
1451
|
+
if (next === "failed")
|
|
1452
|
+
break;
|
|
1453
|
+
if (next === "continue") {
|
|
1433
1454
|
if (this._agentRunAborted) {
|
|
1434
1455
|
const coordination = this._subagentCoordination;
|
|
1435
1456
|
if (coordination) {
|
|
@@ -1605,10 +1626,10 @@ export class AgentSession {
|
|
|
1605
1626
|
const msg = this._lastAssistantMessage;
|
|
1606
1627
|
this._lastAssistantMessage = undefined;
|
|
1607
1628
|
if (!msg) {
|
|
1608
|
-
return
|
|
1629
|
+
return "settled";
|
|
1609
1630
|
}
|
|
1610
1631
|
if (this._isRetryableError(msg) && (await this._prepareRetry(msg))) {
|
|
1611
|
-
return
|
|
1632
|
+
return "continue";
|
|
1612
1633
|
}
|
|
1613
1634
|
if (msg.stopReason === "error" && this._retryAttempt > 0) {
|
|
1614
1635
|
this._emit({
|
|
@@ -1620,12 +1641,27 @@ export class AgentSession {
|
|
|
1620
1641
|
this._retryAttempt = 0;
|
|
1621
1642
|
}
|
|
1622
1643
|
if (await this._checkCompaction(msg)) {
|
|
1623
|
-
return
|
|
1644
|
+
return "continue";
|
|
1645
|
+
}
|
|
1646
|
+
if (msg.stopReason === "error") {
|
|
1647
|
+
// Recovery may have removed the failed response before giving up.
|
|
1648
|
+
if (!this.agent.state.messages.includes(msg))
|
|
1649
|
+
this.agent.state.messages.push(msg);
|
|
1650
|
+
while (true) {
|
|
1651
|
+
const coordination = this._getOrCreateSubagentCoordination();
|
|
1652
|
+
if (!coordination)
|
|
1653
|
+
break;
|
|
1654
|
+
this._exitSubagentCoordination("cancellation");
|
|
1655
|
+
await this._waitForSubagentCoordination(coordination);
|
|
1656
|
+
}
|
|
1657
|
+
// Queued reports and input must not bypass an exhausted recovery budget.
|
|
1658
|
+
// Leave them available for an explicit prompt or queue recovery by the caller.
|
|
1659
|
+
return "failed";
|
|
1624
1660
|
}
|
|
1625
1661
|
// Reports already queued by this run must enter context before a new batch is frozen
|
|
1626
1662
|
// or an escalation-suspended coordination resumes waiting.
|
|
1627
1663
|
if (this.agent.hasQueuedMessages())
|
|
1628
|
-
return
|
|
1664
|
+
return "continue";
|
|
1629
1665
|
while (true) {
|
|
1630
1666
|
const coordination = this._getOrCreateSubagentCoordination();
|
|
1631
1667
|
if (!coordination)
|
|
@@ -1633,28 +1669,44 @@ export class AgentSession {
|
|
|
1633
1669
|
if (coordination.exitReason === "cancellation" || this._agentRunAborted) {
|
|
1634
1670
|
this._exitSubagentCoordination("cancellation");
|
|
1635
1671
|
}
|
|
1672
|
+
else if (!coordination.completionChecked) {
|
|
1673
|
+
coordination.completionChecked = true;
|
|
1674
|
+
const active = this.getSubagents().filter((child) => coordination.names.includes(child.name) && (child.status === "running" || child.status === "queued"));
|
|
1675
|
+
if (active.length > 0) {
|
|
1676
|
+
await this.sendCustomMessage({
|
|
1677
|
+
customType: "subagent_completion_check",
|
|
1678
|
+
content: JSON.stringify({
|
|
1679
|
+
type: "subagent_completion_check",
|
|
1680
|
+
agents: active,
|
|
1681
|
+
instruction: "Review active children before finishing: cancel obsolete work with agent_action interrupt. If needed work remains, end your response to wait automatically. Renew only justified work.",
|
|
1682
|
+
}),
|
|
1683
|
+
display: false,
|
|
1684
|
+
}, { deliverAs: "steer" });
|
|
1685
|
+
return "continue";
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1636
1688
|
const delivery = await this._waitForSubagentCoordination(coordination);
|
|
1637
1689
|
if (delivery.reason === "cancellation")
|
|
1638
|
-
return
|
|
1690
|
+
return "settled";
|
|
1639
1691
|
if (delivery.reason === "escalation") {
|
|
1640
1692
|
if (coordination.exitReason === "cancellation" || this._agentRunAborted) {
|
|
1641
1693
|
this._exitSubagentCoordination("cancellation");
|
|
1642
1694
|
await this._waitForSubagentCoordination(coordination);
|
|
1643
|
-
return
|
|
1695
|
+
return "settled";
|
|
1644
1696
|
}
|
|
1645
|
-
return
|
|
1697
|
+
return "continue";
|
|
1646
1698
|
}
|
|
1647
1699
|
if (delivery.results.length > 0) {
|
|
1648
1700
|
const message = this._createSubagentResultMessage(delivery.results);
|
|
1649
1701
|
await this.sendCustomMessage(message, { deliverAs: "steer" });
|
|
1650
|
-
return
|
|
1702
|
+
return "continue";
|
|
1651
1703
|
}
|
|
1652
1704
|
// Explicit agents consumption can empty this batch while the same continuation
|
|
1653
1705
|
// starts another coordinated child. Freeze that pending run before settling.
|
|
1654
1706
|
}
|
|
1655
1707
|
// The agent loop drains both queues before emitting agent_end. Any messages
|
|
1656
1708
|
// here were queued by agent_end extension handlers and need a continuation.
|
|
1657
|
-
return this.agent.hasQueuedMessages();
|
|
1709
|
+
return this.agent.hasQueuedMessages() ? "continue" : "settled";
|
|
1658
1710
|
}
|
|
1659
1711
|
/**
|
|
1660
1712
|
* Send a prompt to the agent.
|
|
@@ -2336,11 +2388,21 @@ export class AgentSession {
|
|
|
2336
2388
|
}
|
|
2337
2389
|
async _abort() {
|
|
2338
2390
|
this.abortRetry();
|
|
2391
|
+
this.abortCompaction();
|
|
2392
|
+
this.abortBranchSummary();
|
|
2393
|
+
this.abortBash();
|
|
2339
2394
|
if (this._isAgentRunActive)
|
|
2340
2395
|
this._agentRunAborted = true;
|
|
2341
2396
|
this._exitSubagentCoordination("cancellation");
|
|
2342
2397
|
this.agent.abort();
|
|
2343
|
-
await this.waitForIdle();
|
|
2398
|
+
await Promise.all([this.waitForIdle(), this.terminateOwnedShellProcesses(), ...this._bashOperations]);
|
|
2399
|
+
}
|
|
2400
|
+
/** Stop and verify all shell processes owned by this session, including background jobs. */
|
|
2401
|
+
async terminateOwnedShellProcesses() {
|
|
2402
|
+
await this._shellProcesses.terminateAll();
|
|
2403
|
+
}
|
|
2404
|
+
get ownedShellProcessCount() {
|
|
2405
|
+
return this._shellProcesses.count;
|
|
2344
2406
|
}
|
|
2345
2407
|
async waitForIdle() {
|
|
2346
2408
|
if (this.isIdle) {
|
|
@@ -2798,7 +2860,9 @@ export class AgentSession {
|
|
|
2798
2860
|
contextTokens = directContextTokens;
|
|
2799
2861
|
}
|
|
2800
2862
|
if (shouldCompact(contextTokens, contextWindow, settings)) {
|
|
2801
|
-
|
|
2863
|
+
const queuedContinuation = await this._runAutoCompaction("threshold", false, signal, deferCommit);
|
|
2864
|
+
// Threshold compaction does not recover a provider failure.
|
|
2865
|
+
return assistantMessage.stopReason !== "error" && queuedContinuation;
|
|
2802
2866
|
}
|
|
2803
2867
|
return false;
|
|
2804
2868
|
}
|
|
@@ -2827,6 +2891,9 @@ export class AgentSession {
|
|
|
2827
2891
|
return false;
|
|
2828
2892
|
}
|
|
2829
2893
|
this._autoCompactionAbortController = new AbortController();
|
|
2894
|
+
const compactionSignal = signal
|
|
2895
|
+
? AbortSignal.any([signal, this._autoCompactionAbortController.signal])
|
|
2896
|
+
: this._autoCompactionAbortController.signal;
|
|
2830
2897
|
this._emit({ type: "compaction_start", reason });
|
|
2831
2898
|
started = true;
|
|
2832
2899
|
let extensionCompaction;
|
|
@@ -2839,7 +2906,7 @@ export class AgentSession {
|
|
|
2839
2906
|
customInstructions: undefined,
|
|
2840
2907
|
reason,
|
|
2841
2908
|
willRetry,
|
|
2842
|
-
signal:
|
|
2909
|
+
signal: compactionSignal,
|
|
2843
2910
|
}));
|
|
2844
2911
|
if (extensionResult?.cancel) {
|
|
2845
2912
|
this._emit({
|
|
@@ -2871,14 +2938,14 @@ export class AgentSession {
|
|
|
2871
2938
|
}
|
|
2872
2939
|
else {
|
|
2873
2940
|
// Generate compaction result
|
|
2874
|
-
const compactResult = await compact(preparation, requestModel, apiKey, headers, undefined,
|
|
2941
|
+
const compactResult = await compact(preparation, requestModel, apiKey, headers, undefined, compactionSignal, this.thinkingLevel, this.agent.streamFunction, env, this.settingsManager.getRetrySettings(), this._summarizationRetryCallbacks({ source: "compaction", reason }, telemetryContext), telemetryContext);
|
|
2875
2942
|
summary = compactResult.summary;
|
|
2876
2943
|
firstKeptEntryId = compactResult.firstKeptEntryId;
|
|
2877
2944
|
tokensBefore = compactResult.tokensBefore;
|
|
2878
2945
|
usage = compactResult.usage;
|
|
2879
2946
|
details = compactResult.details;
|
|
2880
2947
|
}
|
|
2881
|
-
if (
|
|
2948
|
+
if (compactionSignal.aborted) {
|
|
2882
2949
|
this._emit({
|
|
2883
2950
|
type: "compaction_end",
|
|
2884
2951
|
reason,
|
|
@@ -3237,7 +3304,7 @@ export class AgentSession {
|
|
|
3237
3304
|
: {
|
|
3238
3305
|
...createAllToolDefinitions(this._cwd, {
|
|
3239
3306
|
read: { autoResizeImages },
|
|
3240
|
-
bash: { commandPrefix: shellCommandPrefix, shellPath },
|
|
3307
|
+
bash: { commandPrefix: shellCommandPrefix, shellPath, owner: this._shellProcesses },
|
|
3241
3308
|
}),
|
|
3242
3309
|
update_plan: createSessionUpdatePlanToolDefinition(this.sessionManager),
|
|
3243
3310
|
};
|
|
@@ -3438,7 +3505,7 @@ export class AgentSession {
|
|
|
3438
3505
|
* @param options.operations Custom BashOperations for remote execution
|
|
3439
3506
|
*/
|
|
3440
3507
|
executeBash(command, onChunk, options) {
|
|
3441
|
-
|
|
3508
|
+
const operation = this._runOwnedOperation(() => this._traceOperation("bash", () => startCodingAgentSpan(this.agent.telemetryContext, "diffex.coding_agent.terminal", { "diffex.session.id": this.sessionId }, async (span) => {
|
|
3442
3509
|
const result = await this._executeBash(command, onChunk, options);
|
|
3443
3510
|
const outcome = result.cancelled ? "cancelled" : result.exitCode === 0 ? "completed" : "failed";
|
|
3444
3511
|
span.setAttributes({
|
|
@@ -3449,6 +3516,9 @@ export class AgentSession {
|
|
|
3449
3516
|
span.setStatus({ status: "error" });
|
|
3450
3517
|
return result;
|
|
3451
3518
|
})));
|
|
3519
|
+
this._bashOperations.add(operation);
|
|
3520
|
+
void operation.then(() => this._bashOperations.delete(operation), () => this._bashOperations.delete(operation));
|
|
3521
|
+
return operation;
|
|
3452
3522
|
}
|
|
3453
3523
|
async _executeBash(command, onChunk, options) {
|
|
3454
3524
|
const abortController = new AbortController();
|
|
@@ -3458,7 +3528,7 @@ export class AgentSession {
|
|
|
3458
3528
|
const shellPath = this.settingsManager.getShellPath();
|
|
3459
3529
|
const resolvedCommand = prefix ? `${prefix}\n${command}` : command;
|
|
3460
3530
|
try {
|
|
3461
|
-
const result = await executeBashWithOperations(resolvedCommand, this.sessionManager.getCwd(), options?.operations ?? createLocalBashOperations({ shellPath }), {
|
|
3531
|
+
const result = await executeBashWithOperations(resolvedCommand, this.sessionManager.getCwd(), options?.operations ?? createLocalBashOperations({ shellPath, owner: this._shellProcesses }), {
|
|
3462
3532
|
onChunk: (delta) => {
|
|
3463
3533
|
onChunk?.(delta);
|
|
3464
3534
|
this._emit({ type: "bash_execution_update", id: options?.id, delta });
|
|
@@ -322,7 +322,9 @@ export function findCutPoint(entries, startIndex, endIndex, keepRecentTokens) {
|
|
|
322
322
|
accumulatedTokens += messageTokens;
|
|
323
323
|
// Check if we've exceeded the budget
|
|
324
324
|
if (accumulatedTokens >= keepRecentTokens) {
|
|
325
|
-
//
|
|
325
|
+
// A completed tool batch can end the transcript. If no cut point follows
|
|
326
|
+
// its results, keep the batch's assistant message and all paired results.
|
|
327
|
+
cutIndex = cutPoints[cutPoints.length - 1];
|
|
326
328
|
for (let c = 0; c < cutPoints.length; c++) {
|
|
327
329
|
if (cutPoints[c] >= i) {
|
|
328
330
|
cutIndex = cutPoints[c];
|
|
@@ -453,7 +453,7 @@ export class ModelRuntime {
|
|
|
453
453
|
return lazyStream(model, async () => {
|
|
454
454
|
const prepared = await this.prepareRequest(model, options);
|
|
455
455
|
return prepared.provider.stream(prepared.model, context, prepared.options);
|
|
456
|
-
});
|
|
456
|
+
}, options?.signal);
|
|
457
457
|
}
|
|
458
458
|
complete(model, context, options) {
|
|
459
459
|
return this.stream(model, context, options).result();
|
|
@@ -462,7 +462,7 @@ export class ModelRuntime {
|
|
|
462
462
|
return lazyStream(model, async () => {
|
|
463
463
|
const prepared = await this.prepareRequest(model, options);
|
|
464
464
|
return prepared.provider.streamSimple(prepared.model, context, prepared.options);
|
|
465
|
-
});
|
|
465
|
+
}, options?.signal);
|
|
466
466
|
}
|
|
467
467
|
completeSimple(model, context, options) {
|
|
468
468
|
return this.streamSimple(model, context, options).result();
|
|
@@ -474,7 +474,7 @@ export class ModelRuntime {
|
|
|
474
474
|
throw new ModelsError("provider", `Provider ${model.provider} does not support deferred responses`);
|
|
475
475
|
}
|
|
476
476
|
return prepared.provider.fetchDeferred(prepared.model, handle, prepared.options);
|
|
477
|
-
}).result();
|
|
477
|
+
}, options?.signal).result();
|
|
478
478
|
}
|
|
479
479
|
async cancelDeferred(model, handle, options) {
|
|
480
480
|
const prepared = await this.prepareRequest(model, options);
|
|
@@ -321,7 +321,7 @@ export function composeModelProvider(providerId, base, modelConfig, extension) {
|
|
|
321
321
|
return simple
|
|
322
322
|
? api.streamSimple(model, context, options)
|
|
323
323
|
: api.stream(model, context, options);
|
|
324
|
-
});
|
|
324
|
+
}, options?.signal);
|
|
325
325
|
const provider = {
|
|
326
326
|
id: providerId,
|
|
327
327
|
name: extension?.name ?? config?.name ?? base?.name ?? extension?.oauth?.name ?? providerId,
|
package/dist/core/sdk.d.ts
CHANGED
|
@@ -8,8 +8,9 @@ import { ModelRuntime } from "./model-runtime.ts";
|
|
|
8
8
|
import type { ResourceLoader } from "./resource-loader.ts";
|
|
9
9
|
import { SessionManager } from "./session-manager.ts";
|
|
10
10
|
import { SettingsManager } from "./settings-manager.ts";
|
|
11
|
+
import type { SubagentSupervisionOptions } from "./subagents/types.ts";
|
|
11
12
|
import { createBashTool, createCodingTools, createEditTool, createFindTool, createGrepTool, createLsTool, createReadOnlyTools, createReadTool, createUpdatePlanTool, createWriteTool, withFileMutationQueue } from "./tools/index.ts";
|
|
12
|
-
export interface SubagentOptions {
|
|
13
|
+
export interface SubagentOptions extends SubagentSupervisionOptions {
|
|
13
14
|
/** Maximum number of child agents that may run concurrently. Default: 4 */
|
|
14
15
|
maxConcurrent?: number;
|
|
15
16
|
}
|
package/dist/core/sdk.js
CHANGED
|
@@ -29,7 +29,7 @@ import { createBashTool, createCodingTools, createEditTool, createFindTool, crea
|
|
|
29
29
|
// provider-agnostic and does not import Diffex AI compat itself.
|
|
30
30
|
setDefaultStreamFn(streamSimple);
|
|
31
31
|
const DEFAULT_SUBAGENT_CONCURRENCY = 4;
|
|
32
|
-
const RESERVED_SUBAGENT_TOOL_NAMES = new Set(["spawn_agent", "agent_action", "
|
|
32
|
+
const RESERVED_SUBAGENT_TOOL_NAMES = new Set(["spawn_agent", "agent_action", "agent_status", "escalate_to_parent"]);
|
|
33
33
|
// Re-exports
|
|
34
34
|
export * from "./agent-session-runtime.js";
|
|
35
35
|
export { withFileMutationQueue,
|
|
@@ -48,7 +48,7 @@ function normalizeSubagentOptions(value) {
|
|
|
48
48
|
const options = value;
|
|
49
49
|
if (options) {
|
|
50
50
|
for (const key of Object.keys(options)) {
|
|
51
|
-
if (
|
|
51
|
+
if (!["maxConcurrent", "leaseMs", "supervisionIntervalMs", "parentDeadline"].includes(key)) {
|
|
52
52
|
throw new Error(`Unknown subagents option: ${key}`);
|
|
53
53
|
}
|
|
54
54
|
}
|
|
@@ -57,7 +57,19 @@ function normalizeSubagentOptions(value) {
|
|
|
57
57
|
if (!Number.isInteger(maxConcurrent) || maxConcurrent <= 0) {
|
|
58
58
|
throw new RangeError("subagents.maxConcurrent must be a positive integer");
|
|
59
59
|
}
|
|
60
|
-
return {
|
|
60
|
+
return {
|
|
61
|
+
maxConcurrent,
|
|
62
|
+
leaseMs: options?.leaseMs ??
|
|
63
|
+
(process.env.DIFFEX_SUBAGENT_LEASE_MS === undefined
|
|
64
|
+
? undefined
|
|
65
|
+
: Number(process.env.DIFFEX_SUBAGENT_LEASE_MS)),
|
|
66
|
+
supervisionIntervalMs: options?.supervisionIntervalMs ??
|
|
67
|
+
(process.env.DIFFEX_SUBAGENT_SUPERVISION_MS === undefined
|
|
68
|
+
? undefined
|
|
69
|
+
: Number(process.env.DIFFEX_SUBAGENT_SUPERVISION_MS)),
|
|
70
|
+
parentDeadline: options?.parentDeadline ??
|
|
71
|
+
(process.env.DIFFEX_TASK_DEADLINE_MS === undefined ? undefined : Number(process.env.DIFFEX_TASK_DEADLINE_MS)),
|
|
72
|
+
};
|
|
61
73
|
}
|
|
62
74
|
function assertNoReservedSubagentTools(customTools) {
|
|
63
75
|
for (const tool of customTools ?? []) {
|
|
@@ -66,10 +78,10 @@ function assertNoReservedSubagentTools(customTools) {
|
|
|
66
78
|
}
|
|
67
79
|
}
|
|
68
80
|
}
|
|
69
|
-
function createRootSubagentController(
|
|
81
|
+
function createRootSubagentController(options, agentDir, telemetryContext, parentSessionManager) {
|
|
70
82
|
const sessionManagerFactory = new RootLocalEphemeralSubagentSessionManagerFactory();
|
|
71
83
|
return new SubagentController({
|
|
72
|
-
|
|
84
|
+
...options,
|
|
73
85
|
telemetryContext,
|
|
74
86
|
restoreRoster: (controller, parent) => restoreRootSubagents(controller, parent, agentDir, parentSessionManager),
|
|
75
87
|
captureSession: (parent, childName, origin) => {
|
|
@@ -239,7 +251,7 @@ export async function createInternalAgentSession(options = {}, internalCustomToo
|
|
|
239
251
|
const sessionManager = options.sessionManager ?? SessionManager.create(cwd, getDefaultSessionDir(cwd, agentDir));
|
|
240
252
|
const subagentController = subagentOptions === false
|
|
241
253
|
? undefined
|
|
242
|
-
: createRootSubagentController(subagentOptions
|
|
254
|
+
: createRootSubagentController(subagentOptions, agentDir, telemetryContext, sessionManager);
|
|
243
255
|
const customTools = [
|
|
244
256
|
...(options.customTools ?? []),
|
|
245
257
|
...(subagentController?.createToolDefinitions() ?? []),
|
|
@@ -2,14 +2,14 @@ import { type TelemetryContext } from "@diffexai/diffex-agent-core";
|
|
|
2
2
|
import type { AgentSession } from "../agent-session.ts";
|
|
3
3
|
import type { ToolDefinition } from "../extensions/types.ts";
|
|
4
4
|
import type { ChildSnapshotV1 } from "./persistence.ts";
|
|
5
|
-
import type { CapturedSubagentSession, CaptureSubagentSession, CreateSubagentSession, SubagentActionResult, SubagentBatchResult, SubagentBatchWaitOptions, SubagentControllerSnapshot, SubagentDeliveryBatch, SubagentLifecycleListener, SubagentPermit, SubagentQueryOptions, SubagentQueryResult, SubagentSpawnOrigin, SubagentSummary, SubagentWaitOptions, SubagentWaitResult } from "./types.ts";
|
|
5
|
+
import type { CapturedSubagentSession, CaptureSubagentSession, CreateSubagentSession, SubagentActionResult, SubagentBatchResult, SubagentBatchWaitOptions, SubagentControllerSnapshot, SubagentDeliveryBatch, SubagentLifecycleListener, SubagentPermit, SubagentQueryOptions, SubagentQueryResult, SubagentSpawnOrigin, SubagentSummary, SubagentSupervisionOptions, SubagentWaitOptions, SubagentWaitResult } from "./types.ts";
|
|
6
6
|
export interface PreparedSubagentWake extends CapturedSubagentSession {
|
|
7
7
|
readonly warning?: string;
|
|
8
8
|
}
|
|
9
9
|
export type PrepareSubagentWake = () => Promise<PreparedSubagentWake>;
|
|
10
10
|
export declare class SubagentController {
|
|
11
11
|
#private;
|
|
12
|
-
constructor(options: {
|
|
12
|
+
constructor(options: SubagentSupervisionOptions & {
|
|
13
13
|
maxConcurrent: number;
|
|
14
14
|
captureSession?: CaptureSubagentSession;
|
|
15
15
|
telemetryContext?: TelemetryContext;
|
|
@@ -23,8 +23,9 @@ export declare class SubagentController {
|
|
|
23
23
|
restore(snapshot: ChildSnapshotV1, prepareWake: PrepareSubagentWake, isolationError?: string): void;
|
|
24
24
|
restoreIsolated(name: string, error: string, prepareWake: PrepareSubagentWake): void;
|
|
25
25
|
spawn(name: string, task: string, createSession: CreateSubagentSession, origin?: SubagentSpawnOrigin, telemetryContext?: TelemetryContext, captured?: CapturedSubagentSession): void;
|
|
26
|
-
message(name: string, message: string, wake?: boolean, coordinated?: boolean): Promise<SubagentActionResult>;
|
|
26
|
+
message(name: string, message: string, wake?: boolean, coordinated?: boolean, delivery?: "steer" | "follow_up"): Promise<SubagentActionResult>;
|
|
27
27
|
interrupt(name: string): Promise<SubagentActionResult>;
|
|
28
|
+
renew(name: string, reason: string): Promise<SubagentActionResult>;
|
|
28
29
|
attachSession(name: string, session: AgentSession): void;
|
|
29
30
|
trackRun(name: string, run: Promise<void>): void;
|
|
30
31
|
markRunning(name: string): void;
|