@diffexai/diffex 0.3.0 → 0.3.2
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 +45 -0
- package/dist/cli/args.d.ts +2 -0
- package/dist/cli/args.js +39 -0
- package/dist/core/agent-session-services.d.ts +1 -0
- package/dist/core/agent-session-services.js +1 -0
- package/dist/core/agent-session.d.ts +4 -5
- package/dist/core/agent-session.js +11 -62
- package/dist/core/compaction/branch-summarization.js +3 -2
- package/dist/core/compaction/compaction.js +9 -7
- package/dist/core/compaction/utils.d.ts +3 -0
- package/dist/core/compaction/utils.js +21 -0
- package/dist/core/current-plan-state.d.ts +3 -2
- package/dist/core/current-plan-state.js +23 -6
- package/dist/core/plan-snapshot.d.ts +3 -0
- package/dist/core/plan-snapshot.js +23 -0
- package/dist/core/sdk.d.ts +3 -0
- package/dist/core/sdk.js +1 -0
- package/dist/core/session-manager.js +21 -1
- package/dist/core/subagents/services.d.ts +2 -0
- package/dist/core/subagents/services.js +2 -0
- package/dist/core/tools/bash.js +47 -12
- package/dist/core/tools/update-plan.d.ts +27 -6
- package/dist/core/tools/update-plan.js +122 -18
- package/dist/index.d.ts +1 -0
- package/dist/main.js +4 -0
- package/dist/modes/interactive/components/footer.js +16 -5
- package/dist/modes/interactive/components/tool-group.d.ts +1 -0
- package/dist/modes/interactive/components/tool-group.js +26 -7
- package/dist/modes/print-mode.js +16 -13
- package/dist/node_modules/@diffexai/diffex-agent-core/dist/agent-loop.js +21 -3
- package/dist/node_modules/@diffexai/diffex-agent-core/dist/types.d.ts +17 -10
- package/dist/node_modules/@diffexai/diffex-agent-core/distribution-components.json +4 -4
- package/dist/node_modules/@diffexai/diffex-agent-core/distribution-files.json +3 -3
- package/dist/node_modules/@diffexai/diffex-agent-core/package.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/.manifest.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/amazon-bedrock.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/anthropic.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/azure-openai-responses.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/cerebras.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/cloudflare-ai-gateway.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/cloudflare-workers-ai.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/fireworks.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/github-copilot.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/google-vertex.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/google.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/huggingface.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/minimax-cn.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/minimax.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/mistral.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/moonshotai-cn.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/moonshotai.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/nvidia.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/openai.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/opencode-go.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/together.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/dist/providers/data/xai.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/xiaomi-token-plan-ams.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/xiaomi-token-plan-cn.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/xiaomi-token-plan-sgp.json +1 -1
- package/dist/node_modules/@diffexai/diffex-ai/dist/providers/data/xiaomi.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 +30 -30
- 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/child-process.d.ts +5 -1
- package/dist/utils/child-process.js +9 -1
- package/dist/utils/owned-shell-processes.d.ts +23 -2
- package/dist/utils/owned-shell-processes.js +149 -39
- package/dist/utils/shell-process-identity.d.ts +11 -0
- package/dist/utils/shell-process-identity.js +61 -0
- package/dist/utils/shell.d.ts +1 -1
- package/dist/utils/shell.js +5 -5
- package/distribution-components.json +11 -11
- package/distribution-files.json +101 -85
- 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,50 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.2] - 2026-09-29
|
|
4
|
+
|
|
5
|
+
### New Features
|
|
6
|
+
|
|
7
|
+
- Added persistent plan context with stable step IDs and optional success criteria, attached to every model request and restored across session resume, compaction, and tree navigation.
|
|
8
|
+
See [Plan Mode](docs/usage.md#plan-mode) ([#249](https://github.com/diffexai/diffex/pull/249)).
|
|
9
|
+
- Added parallel tool execution phases that overlap independent calls around sequential tool barriers.
|
|
10
|
+
See [Extensions](docs/extensions.md) ([#250](https://github.com/diffexai/diffex/pull/250)).
|
|
11
|
+
- Added a cache hit rate to the interactive footer alongside context usage ([#249](https://github.com/diffexai/diffex/pull/249)).
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Added persistent plan step identity with stable IDs and optional success criteria, attached as hidden current-task context to every model request and restored across session resume, compaction, and tree navigation.
|
|
16
|
+
See [Plan Mode](docs/usage.md#plan-mode) and [SDK tools](docs/sdk.md#tools) ([#249](https://github.com/diffexai/diffex/pull/249)).
|
|
17
|
+
- Added a cache hit rate to the interactive footer alongside context usage ([#249](https://github.com/diffexai/diffex/pull/249)).
|
|
18
|
+
- Added parallel tool execution phases that overlap consecutive parallel-capable calls around individual sequential barriers, such as `update_plan`.
|
|
19
|
+
See [Extensions](docs/extensions.md#tool_execution_start--tool_execution_update--tool_execution_end) ([#250](https://github.com/diffexai/diffex/pull/250)).
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Excluded plan tool history and saved plan snapshots from compaction and branch summaries; the latest attached snapshot supersedes retained plan context ([#251](https://github.com/diffexai/diffex/pull/251)).
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- Fixed plan cache-prefix preservation so ordinary requests reuse the existing prompt prefix ([#249](https://github.com/diffexai/diffex/pull/249)).
|
|
28
|
+
- Fixed plan restoration on tree navigation to use the destination branch's authoritative plan snapshot ([#249](https://github.com/diffexai/diffex/pull/249)).
|
|
29
|
+
- Fixed settled tool output being remeasured on every keystroke, keeping typing responsive as session history grows ([#249](https://github.com/diffexai/diffex/pull/249)).
|
|
30
|
+
|
|
31
|
+
## [0.3.1] - 2026-09-20
|
|
32
|
+
|
|
33
|
+
### New Features
|
|
34
|
+
|
|
35
|
+
- Added opt-in background services that survive CLI and subagent exit, with explicit environment ownership.
|
|
36
|
+
See [Background process lifetime](docs/background-processes.md) ([#242](https://github.com/diffexai/diffex/pull/242)).
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- Added CLI and SDK options for environment-owned background processes on Linux and macOS, with durable ownership records and inherited policy for child sessions.
|
|
41
|
+
See [Background process lifetime](docs/background-processes.md) ([#242](https://github.com/diffexai/diffex/pull/242)).
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
- Fixed successful background services being terminated after ownership transfers to the environment, including on later failures, interruption, session replacement, and disposal ([#242](https://github.com/diffexai/diffex/pull/242)).
|
|
46
|
+
- Fixed print-mode shutdown to await shared runtime disposal and handle SIGINT cleanup ([#242](https://github.com/diffexai/diffex/pull/242)).
|
|
47
|
+
|
|
3
48
|
## [0.3.0] - 2026-09-20
|
|
4
49
|
|
|
5
50
|
### New Features
|
package/dist/cli/args.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ import type { ExtensionFlag } from "../core/extensions/types.ts";
|
|
|
6
6
|
import type { TuiMode } from "../core/settings-manager.ts";
|
|
7
7
|
export type Mode = "text" | "json" | "rpc";
|
|
8
8
|
export interface Args {
|
|
9
|
+
backgroundProcessLifetime?: "session" | "environment";
|
|
10
|
+
backgroundProcessOwnershipDir?: string;
|
|
9
11
|
provider?: string;
|
|
10
12
|
model?: string;
|
|
11
13
|
apiKey?: string;
|
package/dist/cli/args.js
CHANGED
|
@@ -28,6 +28,29 @@ export function parseArgs(args) {
|
|
|
28
28
|
result.mode = mode;
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
+
else if (arg === "--background-process-lifetime" || arg.startsWith("--background-process-lifetime=")) {
|
|
32
|
+
const value = arg.includes("=") ? arg.slice(arg.indexOf("=") + 1) : args[i + 1];
|
|
33
|
+
if (!arg.includes("=") && value !== undefined && !value.startsWith("-"))
|
|
34
|
+
i++;
|
|
35
|
+
if (value === "session" || value === "environment")
|
|
36
|
+
result.backgroundProcessLifetime = value;
|
|
37
|
+
else
|
|
38
|
+
result.diagnostics.push({
|
|
39
|
+
type: "error",
|
|
40
|
+
message: "--background-process-lifetime requires session or environment",
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
else if (arg === "--background-process-ownership-dir" ||
|
|
44
|
+
arg.startsWith("--background-process-ownership-dir=")) {
|
|
45
|
+
const value = arg.includes("=") ? arg.slice(arg.indexOf("=") + 1) : args[i + 1];
|
|
46
|
+
if (value && !value.startsWith("-")) {
|
|
47
|
+
result.backgroundProcessOwnershipDir = value;
|
|
48
|
+
if (!arg.includes("="))
|
|
49
|
+
i++;
|
|
50
|
+
}
|
|
51
|
+
else
|
|
52
|
+
result.diagnostics.push({ type: "error", message: "--background-process-ownership-dir requires a path" });
|
|
53
|
+
}
|
|
31
54
|
else if (arg === "--continue" || arg === "-c") {
|
|
32
55
|
result.continue = true;
|
|
33
56
|
}
|
|
@@ -213,6 +236,18 @@ export function parseArgs(args) {
|
|
|
213
236
|
result.messages.push(arg);
|
|
214
237
|
}
|
|
215
238
|
}
|
|
239
|
+
if (result.backgroundProcessLifetime === "environment" && !result.backgroundProcessOwnershipDir) {
|
|
240
|
+
result.diagnostics.push({
|
|
241
|
+
type: "error",
|
|
242
|
+
message: "Environment lifetime requires --background-process-ownership-dir",
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
if (result.backgroundProcessOwnershipDir && result.backgroundProcessLifetime !== "environment") {
|
|
246
|
+
result.diagnostics.push({
|
|
247
|
+
type: "error",
|
|
248
|
+
message: "--background-process-ownership-dir requires environment lifetime",
|
|
249
|
+
});
|
|
250
|
+
}
|
|
216
251
|
return result;
|
|
217
252
|
}
|
|
218
253
|
export function printHelp(extensionFlags) {
|
|
@@ -248,6 +283,10 @@ ${chalk.bold("Options:")}
|
|
|
248
283
|
--append-system-prompt <text> Append text or file contents to the system prompt (can be used multiple times)
|
|
249
284
|
--mode <mode> Output mode: text (default), json, or rpc
|
|
250
285
|
--print, -p Non-interactive mode: process prompt and exit
|
|
286
|
+
--background-process-lifetime <session|environment>
|
|
287
|
+
Background shell ownership (default: session)
|
|
288
|
+
--background-process-ownership-dir <path>
|
|
289
|
+
Supervisor handoff records (required for environment lifetime)
|
|
251
290
|
--continue, -c Continue previous session
|
|
252
291
|
--resume, -r Select a session to resume
|
|
253
292
|
--session <path|id> Use specific session file or partial UUID
|
|
@@ -46,6 +46,7 @@ export interface CreateAgentSessionServicesOptions {
|
|
|
46
46
|
* have been resolved against those services.
|
|
47
47
|
*/
|
|
48
48
|
export interface CreateAgentSessionFromServicesOptions {
|
|
49
|
+
backgroundProcessPolicy?: CreateAgentSessionOptions["backgroundProcessPolicy"];
|
|
49
50
|
services: AgentSessionServices;
|
|
50
51
|
sessionManager: SessionManager;
|
|
51
52
|
sessionStartEvent?: SessionStartEvent;
|
|
@@ -137,6 +137,7 @@ export async function createAgentSessionFromServices(options) {
|
|
|
137
137
|
export async function createInternalAgentSessionFromServices(options, internalCustomTools = []) {
|
|
138
138
|
registerSubagentRuntimeDiagnosticSink(options.sessionManager, options.services.diagnostics);
|
|
139
139
|
return createInternalAgentSession({
|
|
140
|
+
backgroundProcessPolicy: options.backgroundProcessPolicy,
|
|
140
141
|
cwd: options.services.cwd,
|
|
141
142
|
agentDir: options.services.agentDir,
|
|
142
143
|
modelRuntime: options.services.modelRuntime,
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { type Agent, type AgentEvent, type AgentMessage, type AgentState, type AgentTool, type TelemetryContext, type ThinkingLevel } from "@diffexai/diffex-agent-core";
|
|
16
16
|
import type { ImageContent, Model, TextContent } from "@diffexai/diffex-ai/compat";
|
|
17
17
|
import type { HarnessStateStore, HarnessVersion, HarnessVersionBundle, HarnessVersionList, HarnessWorkspaceIdentity } from "@diffexai/diffex-harness-state";
|
|
18
|
+
import { type BackgroundProcessPolicy } from "../utils/owned-shell-processes.ts";
|
|
18
19
|
import type { AgentSessionLifecycleObserver } from "./agent-session-lifecycle.ts";
|
|
19
20
|
import { type BashResult } from "./bash-executor.ts";
|
|
20
21
|
import { CHANGE_COLLABORATION_MODE, type CollaborationMode, type CollaborationModeTransitionResult, GET_COLLABORATION_MODE, IS_COLLABORATION_MODE_BUSY, SUBSCRIBE_PROMPT_ADMISSION } from "./collaboration-mode.ts";
|
|
@@ -135,6 +136,8 @@ export type AgentSessionEvent = Exclude<AgentEvent, {
|
|
|
135
136
|
/** Listener function for agent session events */
|
|
136
137
|
export type AgentSessionEventListener = (event: AgentSessionEvent) => void;
|
|
137
138
|
export interface AgentSessionConfig {
|
|
139
|
+
/** Lifetime of background groups launched by successful local shell commands. */
|
|
140
|
+
backgroundProcessPolicy?: BackgroundProcessPolicy;
|
|
138
141
|
agent: Agent;
|
|
139
142
|
sessionManager: SessionManager;
|
|
140
143
|
settingsManager: SettingsManager;
|
|
@@ -250,7 +253,6 @@ export declare class AgentSession {
|
|
|
250
253
|
private _followUpMessages;
|
|
251
254
|
/** Messages queued to be included with the next user prompt as context ("asides"). */
|
|
252
255
|
private _pendingNextTurnMessages;
|
|
253
|
-
private _pendingCurrentPlanRestoration;
|
|
254
256
|
private readonly _skillReferenceSnapshots;
|
|
255
257
|
private readonly _skillEnablementOverrides;
|
|
256
258
|
private _activeSkillTrace;
|
|
@@ -334,10 +336,6 @@ export declare class AgentSession {
|
|
|
334
336
|
*/
|
|
335
337
|
private _installAgentToolHooks;
|
|
336
338
|
private _installAgentNextTurnRefresh;
|
|
337
|
-
private _installAgentContextRestoration;
|
|
338
|
-
private _armInitialCurrentPlanRestoration;
|
|
339
|
-
private _armCurrentPlanRestorationIfActive;
|
|
340
|
-
private _consumePendingCurrentPlanRestoration;
|
|
341
339
|
/** Emit an event to all listeners */
|
|
342
340
|
private _emit;
|
|
343
341
|
private _emitQueueUpdate;
|
|
@@ -565,6 +563,7 @@ export declare class AgentSession {
|
|
|
565
563
|
private _abort;
|
|
566
564
|
/** Stop and verify all shell processes owned by this session, including background jobs. */
|
|
567
565
|
terminateOwnedShellProcesses(): Promise<void>;
|
|
566
|
+
get backgroundProcessPolicy(): BackgroundProcessPolicy;
|
|
568
567
|
get ownedShellProcessCount(): number;
|
|
569
568
|
waitForIdle(): Promise<void>;
|
|
570
569
|
private _emitModelSelect;
|
|
@@ -26,7 +26,7 @@ import { formatNoApiKeyFoundMessage, formatNoModelSelectedMessage } from "./auth
|
|
|
26
26
|
import { executeBashWithOperations } from "./bash-executor.js";
|
|
27
27
|
import { CHANGE_COLLABORATION_MODE, COLLABORATION_MODE_CUSTOM_TYPE, GET_COLLABORATION_MODE, IS_COLLABORATION_MODE_BUSY, PLAN_MODE_SYSTEM_PROMPT, PLAN_MODE_TOOL_NAMES, planHandoffSettlementGuards, SUBSCRIBE_PROMPT_ADMISSION, } from "./collaboration-mode.js";
|
|
28
28
|
import { calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, estimateTokens, generateBranchSummary, prepareCompaction, shouldCompact, } from "./compaction/index.js";
|
|
29
|
-
import { createSessionUpdatePlanToolDefinition,
|
|
29
|
+
import { createSessionUpdatePlanToolDefinition, formatCurrentPlanSnapshot } from "./current-plan-state.js";
|
|
30
30
|
import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
|
|
31
31
|
import { exportSessionToHtml } from "./export-html/index.js";
|
|
32
32
|
import { createToolHtmlRenderer } from "./export-html/tool-renderer.js";
|
|
@@ -109,8 +109,6 @@ function estimateMessagesTokens(messages) {
|
|
|
109
109
|
// ============================================================================
|
|
110
110
|
/** Standard thinking levels */
|
|
111
111
|
const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high"];
|
|
112
|
-
const CURRENT_PLAN_RESTORATION_CUSTOM_TYPE = "diffex.current-plan-restoration";
|
|
113
|
-
const CURRENT_PLAN_RESTORATION_REMINDER = "Reminder: restored context is not proof of completion. Verify current state before claiming work is done.";
|
|
114
112
|
const MAX_PENDING_SUBAGENT_ESCALATION_CHARACTERS = 8_000;
|
|
115
113
|
const INCOMPATIBLE_SUBAGENT_PARENT_HOOKS = [
|
|
116
114
|
"resources_discover",
|
|
@@ -145,7 +143,6 @@ export class AgentSession {
|
|
|
145
143
|
_followUpMessages = [];
|
|
146
144
|
/** Messages queued to be included with the next user prompt as context ("asides"). */
|
|
147
145
|
_pendingNextTurnMessages = [];
|
|
148
|
-
_pendingCurrentPlanRestoration = false;
|
|
149
146
|
_skillReferenceSnapshots = new Map();
|
|
150
147
|
_skillEnablementOverrides = new Map();
|
|
151
148
|
_activeSkillTrace;
|
|
@@ -162,7 +159,7 @@ export class AgentSession {
|
|
|
162
159
|
_bashAbortControllers = new Set();
|
|
163
160
|
_pendingBashMessages = [];
|
|
164
161
|
_ownedOperations = new Set();
|
|
165
|
-
_shellProcesses
|
|
162
|
+
_shellProcesses;
|
|
166
163
|
_bashOperations = new Set();
|
|
167
164
|
// Extension system
|
|
168
165
|
_extensionRunner;
|
|
@@ -220,6 +217,7 @@ export class AgentSession {
|
|
|
220
217
|
_agentRunAborted = false;
|
|
221
218
|
_disposePromise;
|
|
222
219
|
constructor(config) {
|
|
220
|
+
this._shellProcesses = new OwnedShellProcesses(config.backgroundProcessPolicy);
|
|
223
221
|
this.agent = config.agent;
|
|
224
222
|
this.sessionManager = config.sessionManager;
|
|
225
223
|
this.settingsManager = config.settingsManager;
|
|
@@ -244,12 +242,10 @@ export class AgentSession {
|
|
|
244
242
|
: createEvolvedSkillCatalog(config.harnessVersionBundle, config.workspaceIdentity));
|
|
245
243
|
this._telemetryContext = config.telemetryContext ?? NOOP_TELEMETRY_CONTEXT;
|
|
246
244
|
this._lifecycleObserver = config.lifecycleObserver;
|
|
247
|
-
this._armInitialCurrentPlanRestoration();
|
|
248
245
|
// Always subscribe to agent events for internal handling
|
|
249
246
|
// (session persistence, extensions, auto-compaction, retry logic)
|
|
250
247
|
this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);
|
|
251
248
|
this._installAgentToolHooks();
|
|
252
|
-
this._installAgentContextRestoration();
|
|
253
249
|
this._installAgentNextTurnRefresh();
|
|
254
250
|
this._buildRuntime({
|
|
255
251
|
activeToolNames: this._initialActiveToolNames,
|
|
@@ -433,55 +429,6 @@ export class AgentSession {
|
|
|
433
429
|
};
|
|
434
430
|
};
|
|
435
431
|
}
|
|
436
|
-
_installAgentContextRestoration() {
|
|
437
|
-
const previousTransformContext = this.agent.transformContext;
|
|
438
|
-
this.agent.transformContext = async (messages, signal) => {
|
|
439
|
-
const transformedMessages = previousTransformContext
|
|
440
|
-
? await previousTransformContext(messages, signal)
|
|
441
|
-
: messages;
|
|
442
|
-
return this._consumePendingCurrentPlanRestoration(transformedMessages);
|
|
443
|
-
};
|
|
444
|
-
}
|
|
445
|
-
_armInitialCurrentPlanRestoration() {
|
|
446
|
-
if (this._sessionStartEvent.reason !== "startup" && this._sessionStartEvent.reason !== "resume") {
|
|
447
|
-
return;
|
|
448
|
-
}
|
|
449
|
-
this._armCurrentPlanRestorationIfActive();
|
|
450
|
-
}
|
|
451
|
-
_armCurrentPlanRestorationIfActive() {
|
|
452
|
-
if (resolveCurrentPlanState(this.sessionManager).status === "active") {
|
|
453
|
-
this._pendingCurrentPlanRestoration = true;
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
_consumePendingCurrentPlanRestoration(messages) {
|
|
457
|
-
if (!this._pendingCurrentPlanRestoration) {
|
|
458
|
-
return messages;
|
|
459
|
-
}
|
|
460
|
-
const state = resolveCurrentPlanState(this.sessionManager);
|
|
461
|
-
this._pendingCurrentPlanRestoration = false;
|
|
462
|
-
if (state.status !== "active") {
|
|
463
|
-
return messages;
|
|
464
|
-
}
|
|
465
|
-
const lines = [
|
|
466
|
-
"Restored unfinished update_plan checklist from the current session. Use this hidden context to continue the work.",
|
|
467
|
-
];
|
|
468
|
-
if (state.operation.explanation !== undefined) {
|
|
469
|
-
lines.push("", "Explanation:", state.operation.explanation);
|
|
470
|
-
}
|
|
471
|
-
lines.push("", "Checklist:");
|
|
472
|
-
for (const [index, item] of state.operation.plan.entries()) {
|
|
473
|
-
lines.push(`${index + 1}. [${item.status}] ${item.step}`);
|
|
474
|
-
}
|
|
475
|
-
lines.push("", CURRENT_PLAN_RESTORATION_REMINDER);
|
|
476
|
-
const restoredContext = {
|
|
477
|
-
role: "custom",
|
|
478
|
-
customType: CURRENT_PLAN_RESTORATION_CUSTOM_TYPE,
|
|
479
|
-
content: [{ type: "text", text: lines.join("\n") }],
|
|
480
|
-
display: false,
|
|
481
|
-
timestamp: Date.now(),
|
|
482
|
-
};
|
|
483
|
-
return [...messages, restoredContext];
|
|
484
|
-
}
|
|
485
432
|
// =========================================================================
|
|
486
433
|
// Event Subscription
|
|
487
434
|
// =========================================================================
|
|
@@ -1808,7 +1755,6 @@ export class AgentSession {
|
|
|
1808
1755
|
return;
|
|
1809
1756
|
pendingCompaction.commit();
|
|
1810
1757
|
pendingCompaction = undefined;
|
|
1811
|
-
this._armCurrentPlanRestorationIfActive();
|
|
1812
1758
|
};
|
|
1813
1759
|
const rollbackPendingCompaction = () => {
|
|
1814
1760
|
if (!pendingCompaction)
|
|
@@ -2401,6 +2347,9 @@ export class AgentSession {
|
|
|
2401
2347
|
async terminateOwnedShellProcesses() {
|
|
2402
2348
|
await this._shellProcesses.terminateAll();
|
|
2403
2349
|
}
|
|
2350
|
+
get backgroundProcessPolicy() {
|
|
2351
|
+
return this._shellProcesses.policy;
|
|
2352
|
+
}
|
|
2404
2353
|
get ownedShellProcessCount() {
|
|
2405
2354
|
return this._shellProcesses.count;
|
|
2406
2355
|
}
|
|
@@ -2725,7 +2674,8 @@ export class AgentSession {
|
|
|
2725
2674
|
this._branchSummaryAbortController?.abort();
|
|
2726
2675
|
}
|
|
2727
2676
|
async _recordSuccessfulCompaction(options) {
|
|
2728
|
-
const
|
|
2677
|
+
const summary = `${options.summary}\n\n${formatCurrentPlanSnapshot(this.sessionManager)}`;
|
|
2678
|
+
const transaction = appendCompactionTransaction(this.sessionManager, summary, options.firstKeptEntryId, options.tokensBefore, options.details, options.fromExtension, options.usage);
|
|
2729
2679
|
const sessionContext = this.sessionManager.buildSessionContext();
|
|
2730
2680
|
this.agent.state.messages = sessionContext.messages;
|
|
2731
2681
|
const estimatedTokensAfter = estimateMessagesTokens(sessionContext.messages);
|
|
@@ -2759,15 +2709,13 @@ export class AgentSession {
|
|
|
2759
2709
|
else
|
|
2760
2710
|
transaction.commit();
|
|
2761
2711
|
const compactionResult = {
|
|
2762
|
-
summary
|
|
2712
|
+
summary,
|
|
2763
2713
|
firstKeptEntryId: options.firstKeptEntryId,
|
|
2764
2714
|
tokensBefore: options.tokensBefore,
|
|
2765
2715
|
estimatedTokensAfter,
|
|
2766
2716
|
usage: options.usage,
|
|
2767
2717
|
details: options.details,
|
|
2768
2718
|
};
|
|
2769
|
-
if (!options.deferCommit)
|
|
2770
|
-
this._armCurrentPlanRestorationIfActive();
|
|
2771
2719
|
return compactionResult;
|
|
2772
2720
|
}
|
|
2773
2721
|
/**
|
|
@@ -3755,7 +3703,8 @@ export class AgentSession {
|
|
|
3755
3703
|
let summaryEntry;
|
|
3756
3704
|
const mutateLeaf = () => {
|
|
3757
3705
|
if (summaryText) {
|
|
3758
|
-
const
|
|
3706
|
+
const summary = `${summaryText}\n\nThe plan below is authoritative for the selected conversation branch. Plan references in the exploration summary above are historical.\n${formatCurrentPlanSnapshot(this.sessionManager, newLeafId)}`;
|
|
3707
|
+
const summaryId = this.sessionManager.branchWithSummary(newLeafId, summary, summaryDetails, fromExtension, summaryUsage);
|
|
3759
3708
|
summaryEntry = this.sessionManager.getEntry(summaryId);
|
|
3760
3709
|
if (label)
|
|
3761
3710
|
this.sessionManager.appendLabelChange(summaryId, label);
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { contentText } from "@diffexai/diffex-ai";
|
|
8
8
|
import { convertToLlm, createBranchSummaryMessage, createCompactionSummaryMessage, createCustomMessage, } from "../messages.js";
|
|
9
9
|
import { completeSummarization, estimateTokens } from "./compaction.js";
|
|
10
|
-
import { computeFileLists, createFileOps, extractFileOpsFromMessage, formatFileOperations, SUMMARIZATION_SYSTEM_PROMPT, serializeConversation, } from "./utils.js";
|
|
10
|
+
import { computeFileLists, createFileOps, excludePlanItems, extractFileOpsFromMessage, formatFileOperations, SUMMARIZATION_SYSTEM_PROMPT, serializeConversation, } from "./utils.js";
|
|
11
11
|
// ============================================================================
|
|
12
12
|
// Entry Collection
|
|
13
13
|
// ============================================================================
|
|
@@ -120,7 +120,8 @@ export function prepareBranchEntries(entries, tokenBudget = 0) {
|
|
|
120
120
|
// Second pass: walk from newest to oldest, adding messages until token budget
|
|
121
121
|
for (let i = entries.length - 1; i >= 0; i--) {
|
|
122
122
|
const entry = entries[i];
|
|
123
|
-
const
|
|
123
|
+
const rawMessage = getMessageFromEntry(entry);
|
|
124
|
+
const message = rawMessage ? excludePlanItems([rawMessage])[0] : undefined;
|
|
124
125
|
if (!message)
|
|
125
126
|
continue;
|
|
126
127
|
// Extract file ops from assistant messages (tool calls)
|
|
@@ -8,8 +8,9 @@ import { NOOP_TELEMETRY_CONTEXT, startAiSpan, } from "@diffexai/diffex-agent-cor
|
|
|
8
8
|
import { contentText, retryAssistantCall, uuidv7 } from "@diffexai/diffex-ai";
|
|
9
9
|
import { completeSimple } from "@diffexai/diffex-ai/compat";
|
|
10
10
|
import { convertToLlm } from "../messages.js";
|
|
11
|
+
import { stripCurrentPlanSnapshot } from "../plan-snapshot.js";
|
|
11
12
|
import { buildSessionContext, sessionEntryToContextMessages, } from "../session-manager.js";
|
|
12
|
-
import { computeFileLists, createFileOps, extractFileOpsFromMessage, formatFileOperations, SUMMARIZATION_SYSTEM_PROMPT, serializeConversation, } from "./utils.js";
|
|
13
|
+
import { computeFileLists, createFileOps, excludePlanItems, extractFileOpsFromMessage, formatFileOperations, SUMMARIZATION_SYSTEM_PROMPT, serializeConversation, } from "./utils.js";
|
|
13
14
|
/**
|
|
14
15
|
* Extract file operations from messages and previous compaction entries.
|
|
15
16
|
*/
|
|
@@ -494,13 +495,14 @@ export async function generateSummary(currentMessages, model, reserveTokens, api
|
|
|
494
495
|
export async function generateSummaryWithUsage(currentMessages, model, reserveTokens, apiKey, headers, signal, customInstructions, previousSummary, thinkingLevel, streamFn, env, retry, callbacks, telemetryContext) {
|
|
495
496
|
const maxTokens = Math.min(Math.floor(0.8 * reserveTokens), model.maxTokens > 0 ? model.maxTokens : Number.POSITIVE_INFINITY);
|
|
496
497
|
// Use update prompt if we have a previous summary, otherwise initial prompt
|
|
498
|
+
previousSummary = previousSummary === undefined ? undefined : stripCurrentPlanSnapshot(previousSummary);
|
|
497
499
|
let basePrompt = previousSummary ? UPDATE_SUMMARIZATION_PROMPT : SUMMARIZATION_PROMPT;
|
|
498
500
|
if (customInstructions) {
|
|
499
501
|
basePrompt = `${basePrompt}\n\nAdditional focus: ${customInstructions}`;
|
|
500
502
|
}
|
|
501
503
|
// Serialize conversation to text so model doesn't try to continue it
|
|
502
504
|
// Convert to LLM messages first (handles custom types like bashExecution, custom, etc.)
|
|
503
|
-
const llmMessages = convertToLlm(currentMessages);
|
|
505
|
+
const llmMessages = convertToLlm(excludePlanItems(currentMessages));
|
|
504
506
|
const conversationText = serializeConversation(llmMessages);
|
|
505
507
|
// Build the prompt with conversation wrapped in tags
|
|
506
508
|
let promptText = `<conversation>\n${conversationText}\n</conversation>\n\n`;
|
|
@@ -538,7 +540,7 @@ export function prepareCompaction(pathEntries, settings) {
|
|
|
538
540
|
let boundaryStart = 0;
|
|
539
541
|
if (prevCompactionIndex >= 0) {
|
|
540
542
|
const prevCompaction = pathEntries[prevCompactionIndex];
|
|
541
|
-
previousSummary = prevCompaction.summary;
|
|
543
|
+
previousSummary = stripCurrentPlanSnapshot(prevCompaction.summary);
|
|
542
544
|
const firstKeptEntryIndex = pathEntries.findIndex((entry) => entry.id === prevCompaction.firstKeptEntryId);
|
|
543
545
|
boundaryStart = firstKeptEntryIndex >= 0 ? firstKeptEntryIndex : prevCompactionIndex + 1;
|
|
544
546
|
}
|
|
@@ -581,8 +583,8 @@ export function prepareCompaction(pathEntries, settings) {
|
|
|
581
583
|
}
|
|
582
584
|
return {
|
|
583
585
|
firstKeptEntryId,
|
|
584
|
-
messagesToSummarize,
|
|
585
|
-
turnPrefixMessages,
|
|
586
|
+
messagesToSummarize: excludePlanItems(messagesToSummarize),
|
|
587
|
+
turnPrefixMessages: excludePlanItems(turnPrefixMessages),
|
|
586
588
|
isSplitTurn: cutPoint.isSplitTurn,
|
|
587
589
|
tokensBefore,
|
|
588
590
|
previousSummary,
|
|
@@ -620,7 +622,7 @@ export async function compact(preparation, model, apiKey, headers, customInstruc
|
|
|
620
622
|
let summary;
|
|
621
623
|
let summaryUsage;
|
|
622
624
|
if (isSplitTurn && turnPrefixMessages.length > 0) {
|
|
623
|
-
let historyText = "No prior history.";
|
|
625
|
+
let historyText = previousSummary ?? "No prior history.";
|
|
624
626
|
let historyUsage;
|
|
625
627
|
if (messagesToSummarize.length > 0) {
|
|
626
628
|
const historyResult = await generateSummaryWithUsage(messagesToSummarize, model, settings.reserveTokens, apiKey, headers, signal, customInstructions, previousSummary, thinkingLevel, streamFn, env, retry, callbacks, telemetryContext);
|
|
@@ -657,7 +659,7 @@ export async function compact(preparation, model, apiKey, headers, customInstruc
|
|
|
657
659
|
*/
|
|
658
660
|
async function generateTurnPrefixSummary(messages, model, reserveTokens, apiKey, headers, env, signal, thinkingLevel, streamFn, retry, callbacks, telemetryContext) {
|
|
659
661
|
const maxTokens = Math.min(Math.floor(0.5 * reserveTokens), model.maxTokens > 0 ? model.maxTokens : Number.POSITIVE_INFINITY); // Smaller budget for turn prefix
|
|
660
|
-
const llmMessages = convertToLlm(messages);
|
|
662
|
+
const llmMessages = convertToLlm(excludePlanItems(messages));
|
|
661
663
|
const conversationText = serializeConversation(llmMessages);
|
|
662
664
|
const promptText = `<conversation>\n${conversationText}\n</conversation>\n\n${TURN_PREFIX_SUMMARIZATION_PROMPT}`;
|
|
663
665
|
const summarizationMessages = [
|
|
@@ -25,6 +25,8 @@ export declare function computeFileLists(fileOps: FileOperations): {
|
|
|
25
25
|
* Format file operations as XML tags for summary.
|
|
26
26
|
*/
|
|
27
27
|
export declare function formatFileOperations(readFiles: string[], modifiedFiles: string[]): string;
|
|
28
|
+
/** Exclude plan data from a context projection without changing persisted messages. */
|
|
29
|
+
export declare function excludePlanItems(messages: AgentMessage[]): AgentMessage[];
|
|
28
30
|
/**
|
|
29
31
|
* Serialize LLM messages to text for summarization.
|
|
30
32
|
* This prevents the model from treating it as a conversation to continue.
|
|
@@ -32,6 +34,7 @@ export declare function formatFileOperations(readFiles: string[], modifiedFiles:
|
|
|
32
34
|
*
|
|
33
35
|
* Tool results are truncated to keep the summarization request within
|
|
34
36
|
* reasonable token budgets. Full content is not needed for summarization.
|
|
37
|
+
* Plan calls and results are omitted because the latest plan is attached separately.
|
|
35
38
|
*/
|
|
36
39
|
export declare function serializeConversation(messages: Message[]): string;
|
|
37
40
|
export declare const SUMMARIZATION_SYSTEM_PROMPT = "You are a context summarization assistant. Your task is to read a conversation between a user and an AI assistant, then produce a structured summary following the exact format specified.\n\nDo NOT continue the conversation. Do NOT respond to any questions in the conversation. ONLY output the structured summary.";
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Shared utilities for compaction and branch summarization.
|
|
3
3
|
*/
|
|
4
4
|
import { contentText } from "@diffexai/diffex-ai";
|
|
5
|
+
import { stripCurrentPlanSnapshot } from "../plan-snapshot.js";
|
|
5
6
|
export function createFileOps() {
|
|
6
7
|
return {
|
|
7
8
|
read: new Set(),
|
|
@@ -71,6 +72,21 @@ export function formatFileOperations(readFiles, modifiedFiles) {
|
|
|
71
72
|
// ============================================================================
|
|
72
73
|
// Message Serialization
|
|
73
74
|
// ============================================================================
|
|
75
|
+
/** Exclude plan data from a context projection without changing persisted messages. */
|
|
76
|
+
export function excludePlanItems(messages) {
|
|
77
|
+
return messages.flatMap((message) => {
|
|
78
|
+
if (message.role === "toolResult" && message.toolName === "update_plan")
|
|
79
|
+
return [];
|
|
80
|
+
if (message.role === "assistant") {
|
|
81
|
+
const content = message.content.filter((block) => block.type !== "toolCall" || block.name !== "update_plan");
|
|
82
|
+
return content.length > 0 ? [{ ...message, content }] : [];
|
|
83
|
+
}
|
|
84
|
+
if (message.role === "compactionSummary" || message.role === "branchSummary") {
|
|
85
|
+
return [{ ...message, summary: stripCurrentPlanSnapshot(message.summary) }];
|
|
86
|
+
}
|
|
87
|
+
return [message];
|
|
88
|
+
});
|
|
89
|
+
}
|
|
74
90
|
/** Maximum characters for a tool result in serialized summaries. */
|
|
75
91
|
const TOOL_RESULT_MAX_CHARS = 2000;
|
|
76
92
|
/**
|
|
@@ -90,6 +106,7 @@ function truncateForSummary(text, maxChars) {
|
|
|
90
106
|
*
|
|
91
107
|
* Tool results are truncated to keep the summarization request within
|
|
92
108
|
* reasonable token budgets. Full content is not needed for summarization.
|
|
109
|
+
* Plan calls and results are omitted because the latest plan is attached separately.
|
|
93
110
|
*/
|
|
94
111
|
export function serializeConversation(messages) {
|
|
95
112
|
const parts = [];
|
|
@@ -107,6 +124,8 @@ export function serializeConversation(messages) {
|
|
|
107
124
|
thinkingParts.push(block.thinking);
|
|
108
125
|
}
|
|
109
126
|
else if (block.type === "toolCall") {
|
|
127
|
+
if (block.name === "update_plan")
|
|
128
|
+
continue;
|
|
110
129
|
const args = block.arguments;
|
|
111
130
|
const argsStr = Object.entries(args)
|
|
112
131
|
.map(([k, v]) => `${k}=${JSON.stringify(v)}`)
|
|
@@ -125,6 +144,8 @@ export function serializeConversation(messages) {
|
|
|
125
144
|
}
|
|
126
145
|
}
|
|
127
146
|
else if (msg.role === "toolResult") {
|
|
147
|
+
if (msg.toolName === "update_plan")
|
|
148
|
+
continue;
|
|
128
149
|
const content = contentText(msg.content, "");
|
|
129
150
|
if (content) {
|
|
130
151
|
parts.push(`[Tool result]: ${truncateForSummary(content, TOOL_RESULT_MAX_CHARS)}`);
|
|
@@ -15,5 +15,6 @@ export type CurrentPlanState = {
|
|
|
15
15
|
operation: UpdatePlanToolDetails;
|
|
16
16
|
};
|
|
17
17
|
export declare function appendCurrentPlanRecord(sessionManager: Pick<SessionManager, "appendCustomEntry">, operation: UpdatePlanToolDetails): string;
|
|
18
|
-
export declare function resolveCurrentPlanState(sessionManager: Pick<SessionManager, "getBranch"
|
|
19
|
-
export declare function
|
|
18
|
+
export declare function resolveCurrentPlanState(sessionManager: Pick<SessionManager, "getBranch">, fromId?: string | null): CurrentPlanState;
|
|
19
|
+
export declare function formatCurrentPlanSnapshot(sessionManager: Pick<SessionManager, "getBranch">, fromId?: string | null): string;
|
|
20
|
+
export declare function createSessionUpdatePlanToolDefinition(sessionManager: Pick<SessionManager, "appendCustomEntry" | "getBranch">): ReturnType<typeof createUpdatePlanToolDefinition>;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CURRENT_PLAN_SNAPSHOT_PREFIX } from "./plan-snapshot.js";
|
|
2
|
+
import { createUpdatePlanToolDefinition, resolveUpdatePlanOperation, } from "./tools/update-plan.js";
|
|
2
3
|
export const CURRENT_PLAN_CUSTOM_ENTRY_TYPE = "diffex.current-plan";
|
|
3
4
|
export function appendCurrentPlanRecord(sessionManager, operation) {
|
|
4
|
-
return sessionManager.appendCustomEntry(CURRENT_PLAN_CUSTOM_ENTRY_TYPE,
|
|
5
|
+
return sessionManager.appendCustomEntry(CURRENT_PLAN_CUSTOM_ENTRY_TYPE, resolveUpdatePlanOperation(operation));
|
|
5
6
|
}
|
|
6
|
-
export function resolveCurrentPlanState(sessionManager) {
|
|
7
|
-
const branch = sessionManager.getBranch();
|
|
7
|
+
export function resolveCurrentPlanState(sessionManager, fromId) {
|
|
8
|
+
const branch = fromId === null ? [] : sessionManager.getBranch(fromId);
|
|
8
9
|
for (let index = branch.length - 1; index >= 0; index--) {
|
|
9
10
|
const entry = branch[index];
|
|
10
11
|
if (entry.type !== "custom" || entry.customType !== CURRENT_PLAN_CUSTOM_ENTRY_TYPE) {
|
|
@@ -12,7 +13,7 @@ export function resolveCurrentPlanState(sessionManager) {
|
|
|
12
13
|
}
|
|
13
14
|
let operation;
|
|
14
15
|
try {
|
|
15
|
-
operation =
|
|
16
|
+
operation = resolveUpdatePlanOperation(entry.data);
|
|
16
17
|
}
|
|
17
18
|
catch {
|
|
18
19
|
return { status: "corrupt" };
|
|
@@ -24,8 +25,24 @@ export function resolveCurrentPlanState(sessionManager) {
|
|
|
24
25
|
}
|
|
25
26
|
return { status: "absent" };
|
|
26
27
|
}
|
|
28
|
+
export function formatCurrentPlanSnapshot(sessionManager, fromId) {
|
|
29
|
+
const state = resolveCurrentPlanState(sessionManager, fromId);
|
|
30
|
+
const plan = state.status === "active"
|
|
31
|
+
? state.operation.plan
|
|
32
|
+
: state.status === "inactive" && "plan" in state.operation
|
|
33
|
+
? state.operation.plan
|
|
34
|
+
: [];
|
|
35
|
+
return `${CURRENT_PLAN_SNAPSHOT_PREFIX}${JSON.stringify({ plan }, null, 2)}`;
|
|
36
|
+
}
|
|
27
37
|
export function createSessionUpdatePlanToolDefinition(sessionManager) {
|
|
28
|
-
const definition = createUpdatePlanToolDefinition()
|
|
38
|
+
const definition = createUpdatePlanToolDefinition(undefined, () => {
|
|
39
|
+
const state = resolveCurrentPlanState(sessionManager);
|
|
40
|
+
if (state.status === "active")
|
|
41
|
+
return state.operation.plan;
|
|
42
|
+
if (state.status === "inactive" && "plan" in state.operation)
|
|
43
|
+
return state.operation.plan;
|
|
44
|
+
return undefined;
|
|
45
|
+
});
|
|
29
46
|
const execute = definition.execute;
|
|
30
47
|
return {
|
|
31
48
|
...definition,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const CURRENT_PLAN_SNAPSHOT_PREFIX = "Current task context (latest plan):\n";
|
|
2
|
+
// Branch navigation attaches this authority paragraph with the snapshot.
|
|
3
|
+
const BRANCH_PLAN_AUTHORITY = "The plan below is authoritative for the selected conversation branch. Plan references in the exploration summary above are historical.\n";
|
|
4
|
+
/** Remove only the structured plan snapshot appended to a summary. */
|
|
5
|
+
export function stripCurrentPlanSnapshot(summary) {
|
|
6
|
+
const index = summary.lastIndexOf(CURRENT_PLAN_SNAPSHOT_PREFIX);
|
|
7
|
+
const authorityStart = index - BRANCH_PLAN_AUTHORITY.length;
|
|
8
|
+
const hasBranchAuthority = authorityStart >= 0 && summary.slice(authorityStart, index) === BRANCH_PLAN_AUTHORITY;
|
|
9
|
+
const attachmentStart = hasBranchAuthority ? authorityStart : index;
|
|
10
|
+
if (index < 0 || (attachmentStart > 0 && summary.slice(attachmentStart - 2, attachmentStart) !== "\n\n")) {
|
|
11
|
+
return summary;
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const snapshot = JSON.parse(summary.slice(index + CURRENT_PLAN_SNAPSHOT_PREFIX.length));
|
|
15
|
+
if (typeof snapshot !== "object" || snapshot === null || !("plan" in snapshot) || !Array.isArray(snapshot.plan)) {
|
|
16
|
+
return summary;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return summary;
|
|
21
|
+
}
|
|
22
|
+
return summary.slice(0, attachmentStart === 0 ? 0 : attachmentStart - 2);
|
|
23
|
+
}
|
package/dist/core/sdk.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type TelemetryContext, type ThinkingLevel } from "@diffexai/diffex-agent-core";
|
|
2
2
|
import { type Model } from "@diffexai/diffex-ai/compat";
|
|
3
3
|
import type { HarnessStateStore, HarnessVersionBundle } from "@diffexai/diffex-harness-state";
|
|
4
|
+
import type { BackgroundProcessPolicy } from "../utils/owned-shell-processes.ts";
|
|
4
5
|
import { AgentSession } from "./agent-session.ts";
|
|
5
6
|
import type { AgentSessionLifecycleObserver } from "./agent-session-lifecycle.ts";
|
|
6
7
|
import type { LoadExtensionsResult, SessionStartEvent, ToolDefinition } from "./extensions/index.ts";
|
|
@@ -15,6 +16,8 @@ export interface SubagentOptions extends SubagentSupervisionOptions {
|
|
|
15
16
|
maxConcurrent?: number;
|
|
16
17
|
}
|
|
17
18
|
export interface CreateAgentSessionOptions {
|
|
19
|
+
/** Local shell background lifetime. Defaults to session-owned cleanup. */
|
|
20
|
+
backgroundProcessPolicy?: BackgroundProcessPolicy;
|
|
18
21
|
/** Working directory for project-local discovery. Default: process.cwd() */
|
|
19
22
|
cwd?: string;
|
|
20
23
|
/** Global config directory. Default: ~/.diffex/agent */
|
package/dist/core/sdk.js
CHANGED
|
@@ -436,6 +436,7 @@ export async function createInternalAgentSession(options = {}, internalCustomToo
|
|
|
436
436
|
sessionManager.appendThinkingLevelChange(thinkingLevel);
|
|
437
437
|
}
|
|
438
438
|
const session = new AgentSession({
|
|
439
|
+
backgroundProcessPolicy: options.backgroundProcessPolicy,
|
|
439
440
|
agent,
|
|
440
441
|
sessionManager,
|
|
441
442
|
settingsManager,
|