apex-code 0.5.1 → 0.6.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 +24 -0
- package/README.md +41 -2
- package/dist/core/agent-session.d.ts +37 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +264 -60
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/default-tool-names.d.ts +7 -0
- package/dist/core/default-tool-names.d.ts.map +1 -0
- package/dist/core/default-tool-names.js +14 -0
- package/dist/core/default-tool-names.js.map +1 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +2 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +3 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/keybindings.d.ts +11 -1
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +6 -1
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/permissions/startup.d.ts +1 -1
- package/dist/core/permissions/startup.d.ts.map +1 -1
- package/dist/core/permissions/startup.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +13 -9
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/settings-manager.d.ts +4 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +13 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/tools/contract.d.ts +1 -0
- package/dist/core/tools/contract.d.ts.map +1 -1
- package/dist/core/tools/contract.js.map +1 -1
- package/dist/core/tools/index.d.ts +1 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/plan-present.d.ts +13 -12
- package/dist/core/tools/plan-present.d.ts.map +1 -1
- package/dist/core/tools/plan-present.js +32 -19
- package/dist/core/tools/plan-present.js.map +1 -1
- package/dist/core/tools/renderers/index.d.ts +1 -1
- package/dist/core/tools/renderers/index.d.ts.map +1 -1
- package/dist/core/tools/renderers/index.js +2 -0
- package/dist/core/tools/renderers/index.js.map +1 -1
- package/dist/core/tools/renderers/todo-write.d.ts +7 -0
- package/dist/core/tools/renderers/todo-write.d.ts.map +1 -0
- package/dist/core/tools/renderers/todo-write.js +58 -0
- package/dist/core/tools/renderers/todo-write.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/components/first-use-hints.d.ts +1 -1
- package/dist/modes/interactive/components/first-use-hints.d.ts.map +1 -1
- package/dist/modes/interactive/components/first-use-hints.js +1 -0
- package/dist/modes/interactive/components/first-use-hints.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +3 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +25 -11
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +5 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +20 -1
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/task-panel.d.ts +27 -0
- package/dist/modes/interactive/components/task-panel.d.ts.map +1 -0
- package/dist/modes/interactive/components/task-panel.js +92 -0
- package/dist/modes/interactive/components/task-panel.js.map +1 -0
- package/dist/modes/interactive/components/turn-summary.d.ts +6 -0
- package/dist/modes/interactive/components/turn-summary.d.ts.map +1 -0
- package/dist/modes/interactive/components/turn-summary.js +12 -0
- package/dist/modes/interactive/components/turn-summary.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts +9 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +162 -11
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +31 -72
- package/dist/package-manager-cli.js.map +1 -1
- package/docs/keybindings.md +19 -1
- package/docs/settings.md +8 -0
- package/npm-shrinkwrap.json +5 -5
- package/package.json +2 -2
|
@@ -27,6 +27,7 @@ import { generateBugReportSummary } from "./bug-report.js";
|
|
|
27
27
|
import { createSessionCheckpoints } from "./checkpoints/session-checkpoints.js";
|
|
28
28
|
import { calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, estimateProjectedContextTokens, estimateTokens, generateBranchSummary, prepareCompaction, shouldCompact, } from "./compaction/index.js";
|
|
29
29
|
import { evictionBudget, installContextPipeline, isDefaultStreamFunction } from "./context/pipeline.js";
|
|
30
|
+
import { configuredDefaultToolNames } from "./default-tool-names.js";
|
|
30
31
|
import { DEFAULT_THINKING_LEVEL, THINKING_LEVEL_OPTIONS } from "./defaults.js";
|
|
31
32
|
import { RESUME_CHILD_PROMPT, runDelegation } from "./delegation/runtime.js";
|
|
32
33
|
import { SessionEvidenceSink } from "./evidence.js";
|
|
@@ -51,6 +52,7 @@ import { buildSystemPrompt, buildSystemPromptSections, diffSystemPromptSections,
|
|
|
51
52
|
import { createLocalBashOperations } from "./tools/bash.js";
|
|
52
53
|
import { buildToolContractSnapshot, isUnclassifiedTool, } from "./tools/contract-snapshot.js";
|
|
53
54
|
import { createAllToolDefinitions } from "./tools/index.js";
|
|
55
|
+
import { createPlanPresentToolDefinition, defaultPlanPresenter } from "./tools/plan-present.js";
|
|
54
56
|
import { createSkillSearchToolDefinition } from "./tools/skill-search.js";
|
|
55
57
|
import { createTodoWriteToolDefinition } from "./tools/todo-write.js";
|
|
56
58
|
import { createToolDefinitionFromAgentTool } from "./tools/tool-definition-wrapper.js";
|
|
@@ -165,9 +167,13 @@ export class AgentSession {
|
|
|
165
167
|
_initialActiveToolNames;
|
|
166
168
|
_allowedToolNames;
|
|
167
169
|
_excludedToolNames;
|
|
170
|
+
_implicitDefaultToolNames;
|
|
168
171
|
_baseToolsOverride;
|
|
169
172
|
_sessionStartEvent;
|
|
170
173
|
_permissionGate;
|
|
174
|
+
_interactivePermissionMode;
|
|
175
|
+
_interactivePermissionModeGeneration = 0;
|
|
176
|
+
_startupPermissionMode;
|
|
171
177
|
_evidenceSink;
|
|
172
178
|
_diagnosticsOperations;
|
|
173
179
|
_lspOperations;
|
|
@@ -206,6 +212,13 @@ export class AgentSession {
|
|
|
206
212
|
_baseSystemPromptOptions;
|
|
207
213
|
/** Prompt options after before_agent_start mutations for the active run. */
|
|
208
214
|
_runSystemPromptOptions;
|
|
215
|
+
_requestedToolNames = [];
|
|
216
|
+
_runRequestedToolNames;
|
|
217
|
+
_planToolMode;
|
|
218
|
+
_planToolSyncGeneration = 0;
|
|
219
|
+
_pendingPlanToolSync;
|
|
220
|
+
_planToolSyncChange = new AbortController();
|
|
221
|
+
_planPresentationGeneration = 0;
|
|
209
222
|
constructor(config) {
|
|
210
223
|
this.agent = config.agent;
|
|
211
224
|
this.sessionManager = config.sessionManager;
|
|
@@ -224,8 +237,26 @@ export class AgentSession {
|
|
|
224
237
|
this._allowedToolNames = config.allowedToolNames ? new Set(config.allowedToolNames) : undefined;
|
|
225
238
|
this._excludedToolNames = config.excludedToolNames ? new Set(config.excludedToolNames) : undefined;
|
|
226
239
|
this._baseToolsOverride = config.baseToolsOverride;
|
|
240
|
+
this._implicitDefaultToolNames = config.implicitDefaultToolNames
|
|
241
|
+
? [...config.implicitDefaultToolNames]
|
|
242
|
+
: undefined;
|
|
227
243
|
this._sessionStartEvent = config.sessionStartEvent ?? { type: "session_start", reason: "startup" };
|
|
228
|
-
|
|
244
|
+
if (config.permissionGate) {
|
|
245
|
+
const configuredGate = config.permissionGate;
|
|
246
|
+
try {
|
|
247
|
+
const startupMode = configuredGate.getMode();
|
|
248
|
+
if (typeof startupMode === "string")
|
|
249
|
+
this._planToolMode = startupMode;
|
|
250
|
+
this._startupPermissionMode = Promise.resolve(startupMode).then((mode) => ({ ok: true, mode }), (error) => ({ ok: false, error }));
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
this._startupPermissionMode = Promise.resolve({ ok: false, error });
|
|
254
|
+
}
|
|
255
|
+
this._permissionGate = {
|
|
256
|
+
...configuredGate,
|
|
257
|
+
getMode: () => this._interactivePermissionMode ?? configuredGate.getMode(),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
229
260
|
this._evidenceSink = config.evidenceSink ?? new SessionEvidenceSink(this.sessionManager);
|
|
230
261
|
this._diagnosticsOperations = config.diagnosticsOperations;
|
|
231
262
|
this._lspOperations = config.lspOperations;
|
|
@@ -280,6 +311,15 @@ export class AgentSession {
|
|
|
280
311
|
});
|
|
281
312
|
if (this._initialActiveToolNames === undefined)
|
|
282
313
|
this._restoreToolsFromTranscript();
|
|
314
|
+
const generation = this._planToolSyncGeneration;
|
|
315
|
+
void this._startupPermissionMode
|
|
316
|
+
?.then((startup) => {
|
|
317
|
+
if (startup.ok && generation === this._planToolSyncGeneration && this._lifecycle !== "closed") {
|
|
318
|
+
this._planToolMode = startup.mode;
|
|
319
|
+
this._applyRequestedTools();
|
|
320
|
+
}
|
|
321
|
+
})
|
|
322
|
+
.catch(() => { });
|
|
283
323
|
}
|
|
284
324
|
get modelRuntime() {
|
|
285
325
|
return this._modelRuntime;
|
|
@@ -547,6 +587,7 @@ export class AgentSession {
|
|
|
547
587
|
? async (_turn, signal) => await this.agent.prepareNextTurn?.(signal)
|
|
548
588
|
: undefined);
|
|
549
589
|
this.agent.prepareNextTurnWithContext = async (turn, signal) => {
|
|
590
|
+
await this._syncPlanToolMode();
|
|
550
591
|
const context = await this._compactBeforeNextAssistantResponse({
|
|
551
592
|
...turn.context,
|
|
552
593
|
messages: this.sessionManager.buildSessionProjection().messages,
|
|
@@ -556,7 +597,7 @@ export class AgentSession {
|
|
|
556
597
|
const runOptions = this._runSystemPromptOptions ?? this._baseSystemPromptOptions;
|
|
557
598
|
const options = normalizeBuildSystemPromptOptions({
|
|
558
599
|
...runOptions,
|
|
559
|
-
selectedTools: this.
|
|
600
|
+
selectedTools: this._projectToolNames(this._runRequestedToolNames ?? this._requestedToolNames),
|
|
560
601
|
toolSnippets: { ...this._baseSystemPromptOptions.toolSnippets, ...runOptions.toolSnippets },
|
|
561
602
|
toolGuidelines: { ...this._baseSystemPromptOptions.toolGuidelines, ...runOptions.toolGuidelines },
|
|
562
603
|
});
|
|
@@ -1262,6 +1303,10 @@ export class AgentSession {
|
|
|
1262
1303
|
if (this._lifecycle === "closed")
|
|
1263
1304
|
return;
|
|
1264
1305
|
this._lifecycle = "closed";
|
|
1306
|
+
this._planPresentationGeneration++;
|
|
1307
|
+
this._planToolSyncGeneration++;
|
|
1308
|
+
this._setPendingPlanToolSync();
|
|
1309
|
+
this._interactivePermissionModeGeneration++;
|
|
1265
1310
|
try {
|
|
1266
1311
|
this.abortRetry();
|
|
1267
1312
|
this.abortCompaction();
|
|
@@ -1332,6 +1377,14 @@ export class AgentSession {
|
|
|
1332
1377
|
get retryAttempt() {
|
|
1333
1378
|
return this._retryAttempt;
|
|
1334
1379
|
}
|
|
1380
|
+
getImplicitDefaultToolNames() {
|
|
1381
|
+
return this._implicitDefaultToolNames
|
|
1382
|
+
? [...this._implicitDefaultToolNames]
|
|
1383
|
+
: configuredDefaultToolNames({
|
|
1384
|
+
baseNames: this._baseToolsOverride ? Object.keys(this._baseToolsOverride) : undefined,
|
|
1385
|
+
lsp: Boolean(this._lspOperations),
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1335
1388
|
/**
|
|
1336
1389
|
* Get the names of currently active tools.
|
|
1337
1390
|
* Returns the names of tools currently set on the agent.
|
|
@@ -1372,17 +1425,90 @@ export class AgentSession {
|
|
|
1372
1425
|
* Changes take effect on the next agent turn.
|
|
1373
1426
|
*/
|
|
1374
1427
|
setActiveToolsByName(toolNames) {
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1428
|
+
this._requestedToolNames = [...new Set(toolNames)].filter((name) => this._toolRegistry.has(name));
|
|
1429
|
+
this._runRequestedToolNames = undefined;
|
|
1430
|
+
this._applyRequestedTools();
|
|
1431
|
+
}
|
|
1432
|
+
_projectToolNames(names) {
|
|
1433
|
+
const projected = [...new Set(names)].filter((name) => this._toolRegistry.has(name));
|
|
1434
|
+
if (this._planToolMode === "plan" &&
|
|
1435
|
+
this._toolRegistry.has("plan_present") &&
|
|
1436
|
+
!projected.includes("plan_present"))
|
|
1437
|
+
projected.push("plan_present");
|
|
1438
|
+
return projected;
|
|
1439
|
+
}
|
|
1440
|
+
_applyRequestedTools() {
|
|
1441
|
+
const names = this._projectToolNames(this._runRequestedToolNames ?? this._requestedToolNames);
|
|
1442
|
+
this.agent.state.tools = names.flatMap((name) => {
|
|
1378
1443
|
const tool = this._toolRegistry.get(name);
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1444
|
+
return tool ? [tool] : [];
|
|
1445
|
+
});
|
|
1446
|
+
this._rebuildSystemPrompt(this._projectToolNames(this._requestedToolNames));
|
|
1447
|
+
}
|
|
1448
|
+
_setPendingPlanToolSync(task) {
|
|
1449
|
+
this._pendingPlanToolSync = task;
|
|
1450
|
+
const previous = this._planToolSyncChange;
|
|
1451
|
+
this._planToolSyncChange = new AbortController();
|
|
1452
|
+
previous.abort();
|
|
1453
|
+
}
|
|
1454
|
+
async _syncPlanToolMode() {
|
|
1455
|
+
const generation = ++this._planToolSyncGeneration;
|
|
1456
|
+
const task = (async () => {
|
|
1457
|
+
const mode = await this._permissionGate?.getMode();
|
|
1458
|
+
if (generation !== this._planToolSyncGeneration || this._lifecycle === "closed")
|
|
1459
|
+
return;
|
|
1460
|
+
this._planToolMode = mode;
|
|
1461
|
+
this._applyRequestedTools();
|
|
1462
|
+
})();
|
|
1463
|
+
this._setPendingPlanToolSync(task);
|
|
1464
|
+
let pending = task;
|
|
1465
|
+
for (;;) {
|
|
1466
|
+
const signal = this._planToolSyncChange.signal;
|
|
1467
|
+
let wake;
|
|
1468
|
+
const changed = new Promise((resolve) => {
|
|
1469
|
+
wake = resolve;
|
|
1470
|
+
signal.addEventListener("abort", wake, { once: true });
|
|
1471
|
+
});
|
|
1472
|
+
try {
|
|
1473
|
+
await Promise.race([pending, changed]);
|
|
1382
1474
|
}
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1475
|
+
catch (error) {
|
|
1476
|
+
if (pending === this._pendingPlanToolSync)
|
|
1477
|
+
throw error;
|
|
1478
|
+
}
|
|
1479
|
+
finally {
|
|
1480
|
+
signal.removeEventListener("abort", wake);
|
|
1481
|
+
}
|
|
1482
|
+
if (!this._pendingPlanToolSync || pending === this._pendingPlanToolSync)
|
|
1483
|
+
return;
|
|
1484
|
+
pending = this._pendingPlanToolSync;
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
_schedulePlanToolSync() {
|
|
1488
|
+
void this._syncPlanToolMode().catch(() => { });
|
|
1489
|
+
}
|
|
1490
|
+
async _presentPlan(plan, ctx, signal) {
|
|
1491
|
+
const generation = this._planPresentationGeneration;
|
|
1492
|
+
const modeGeneration = this._interactivePermissionModeGeneration;
|
|
1493
|
+
const obsolete = () => signal?.aborted ||
|
|
1494
|
+
this._lifecycle === "closed" ||
|
|
1495
|
+
generation !== this._planPresentationGeneration ||
|
|
1496
|
+
modeGeneration !== this._interactivePermissionModeGeneration;
|
|
1497
|
+
const decision = await defaultPlanPresenter.present(plan, ctx, signal);
|
|
1498
|
+
if (obsolete())
|
|
1499
|
+
throw new Error("Plan approval was cancelled because its dialog is obsolete.");
|
|
1500
|
+
if (!decision.approved)
|
|
1501
|
+
return decision;
|
|
1502
|
+
if (!this._permissionGate)
|
|
1503
|
+
throw new Error("Cannot apply the approved plan mode without a permission gate.");
|
|
1504
|
+
const resolution = await this._setInteractivePermissionMode(decision.nextMode, () => !signal?.aborted && this._lifecycle !== "closed" && generation === this._planPresentationGeneration);
|
|
1505
|
+
if (signal?.aborted ||
|
|
1506
|
+
generation !== this._planPresentationGeneration ||
|
|
1507
|
+
modeGeneration + 1 !== this._interactivePermissionModeGeneration ||
|
|
1508
|
+
resolution?.mode !== decision.nextMode ||
|
|
1509
|
+
resolution.origin !== "interactive")
|
|
1510
|
+
throw new Error("Plan approval was cancelled because its dialog is obsolete.");
|
|
1511
|
+
return decision;
|
|
1386
1512
|
}
|
|
1387
1513
|
/** Whether compaction or branch summarization is currently running */
|
|
1388
1514
|
get isCompacting() {
|
|
@@ -1483,7 +1609,7 @@ export class AgentSession {
|
|
|
1483
1609
|
* {@link _installAgentForcedPromptProjection}.
|
|
1484
1610
|
*/
|
|
1485
1611
|
_preparePromptAndToolLoadout(options, messages = this.agent.state.messages) {
|
|
1486
|
-
options.selectedTools =
|
|
1612
|
+
options.selectedTools = this._projectToolNames(options.selectedTools);
|
|
1487
1613
|
this.agent.state.tools = options.selectedTools.flatMap((name) => {
|
|
1488
1614
|
const tool = this._toolRegistry.get(name);
|
|
1489
1615
|
return tool ? [tool] : [];
|
|
@@ -1523,14 +1649,13 @@ export class AgentSession {
|
|
|
1523
1649
|
const current = getCurrentSystemMessage(this.sessionManager.buildSessionContext().messages);
|
|
1524
1650
|
if (!current)
|
|
1525
1651
|
return;
|
|
1526
|
-
const
|
|
1652
|
+
const explicitPlanTool = this._requestedToolNames.includes("plan_present");
|
|
1653
|
+
this._requestedToolNames = (current.toolsAdded ?? [])
|
|
1527
1654
|
.map((tool) => tool.name)
|
|
1528
|
-
.filter((name) => this._toolRegistry.has(name));
|
|
1529
|
-
this.
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
});
|
|
1533
|
-
this._rebuildSystemPrompt(toolNames);
|
|
1655
|
+
.filter((name) => this._toolRegistry.has(name) && (name !== "plan_present" || explicitPlanTool));
|
|
1656
|
+
if (explicitPlanTool && !this._requestedToolNames.includes("plan_present"))
|
|
1657
|
+
this._requestedToolNames.push("plan_present");
|
|
1658
|
+
this._applyRequestedTools();
|
|
1534
1659
|
}
|
|
1535
1660
|
// =========================================================================
|
|
1536
1661
|
// Prompting
|
|
@@ -1558,6 +1683,8 @@ export class AgentSession {
|
|
|
1558
1683
|
if (this._agentRunAbortRequested)
|
|
1559
1684
|
this._finishCancelledRetry();
|
|
1560
1685
|
this._runSystemPromptOptions = undefined;
|
|
1686
|
+
this._runRequestedToolNames = undefined;
|
|
1687
|
+
this._applyRequestedTools();
|
|
1561
1688
|
this._flushPendingBashMessages();
|
|
1562
1689
|
this._flushPendingCustomMessages();
|
|
1563
1690
|
await this._emitAgentSettled();
|
|
@@ -1768,6 +1895,7 @@ export class AgentSession {
|
|
|
1768
1895
|
if (lastAssistant) {
|
|
1769
1896
|
await this._checkCompaction(lastAssistant, false);
|
|
1770
1897
|
}
|
|
1898
|
+
await this._syncPlanToolMode();
|
|
1771
1899
|
// Emit before_agent_start before normalizing images so extension-driven model
|
|
1772
1900
|
// selection determines the resize profile used for the request and history.
|
|
1773
1901
|
const selectedToolsBefore = this._baseSystemPromptOptions.selectedTools;
|
|
@@ -1777,8 +1905,11 @@ export class AgentSession {
|
|
|
1777
1905
|
// loadout is authoritative, so a setActiveTools() call is not undone here.
|
|
1778
1906
|
const handlerEditedTools = result.systemPromptOptions.selectedTools.length !== selectedToolsBefore.length ||
|
|
1779
1907
|
result.systemPromptOptions.selectedTools.some((name, index) => name !== selectedToolsBefore[index]);
|
|
1780
|
-
|
|
1781
|
-
|
|
1908
|
+
const inheritedImplicitPlanTool = selectedToolsBefore.includes("plan_present") && !this._requestedToolNames.includes("plan_present");
|
|
1909
|
+
this._runRequestedToolNames = handlerEditedTools
|
|
1910
|
+
? result.systemPromptOptions.selectedTools.filter((name) => name !== "plan_present" || !inheritedImplicitPlanTool)
|
|
1911
|
+
: undefined;
|
|
1912
|
+
result.systemPromptOptions.selectedTools = this._projectToolNames(this._runRequestedToolNames ?? this._requestedToolNames);
|
|
1782
1913
|
const normalized = await this._normalizePromptImages(currentImages);
|
|
1783
1914
|
const userText = normalized.hints.length > 0 ? `${expandedText}\n\n${normalized.hints.join("\n")}` : expandedText;
|
|
1784
1915
|
// Build messages only after hooks and image normalization have completed.
|
|
@@ -2108,6 +2239,8 @@ export class AgentSession {
|
|
|
2108
2239
|
* Abort current operation and wait for agent to become idle.
|
|
2109
2240
|
*/
|
|
2110
2241
|
async abort() {
|
|
2242
|
+
this._planPresentationGeneration++;
|
|
2243
|
+
this._interactivePermissionModeGeneration++;
|
|
2111
2244
|
if (this._activePrompt && this._lifecycle !== "closed")
|
|
2112
2245
|
this._lifecycle = "interrupted";
|
|
2113
2246
|
if (this._isAgentRunActive) {
|
|
@@ -2152,9 +2285,60 @@ export class AgentSession {
|
|
|
2152
2285
|
async getPermissionMode() {
|
|
2153
2286
|
if (!this._permissionGate)
|
|
2154
2287
|
return undefined;
|
|
2288
|
+
if (this._interactivePermissionMode !== undefined) {
|
|
2289
|
+
return { mode: this._interactivePermissionMode, origin: "interactive" };
|
|
2290
|
+
}
|
|
2155
2291
|
const snapshot = await this._permissionGate.store.snapshot();
|
|
2292
|
+
if (this._interactivePermissionMode !== undefined) {
|
|
2293
|
+
return { mode: this._interactivePermissionMode, origin: "interactive" };
|
|
2294
|
+
}
|
|
2156
2295
|
return resolveEffectiveModeWithOrigin(this._permissionGate.flagMode, snapshot.modesBySource);
|
|
2157
2296
|
}
|
|
2297
|
+
getInteractivePermissionMode() {
|
|
2298
|
+
return this._interactivePermissionMode;
|
|
2299
|
+
}
|
|
2300
|
+
async getInteractivePermissionModeCycle() {
|
|
2301
|
+
if (!this._permissionGate)
|
|
2302
|
+
return [];
|
|
2303
|
+
const modes = ["default", "acceptEdits", "plan"];
|
|
2304
|
+
const startup = await this._startupPermissionMode;
|
|
2305
|
+
if (startup?.ok === false)
|
|
2306
|
+
throw startup.error;
|
|
2307
|
+
if (startup?.mode === "bypassPermissions")
|
|
2308
|
+
modes.push("bypassPermissions");
|
|
2309
|
+
return modes;
|
|
2310
|
+
}
|
|
2311
|
+
async setInteractivePermissionMode(mode) {
|
|
2312
|
+
return this._setInteractivePermissionMode(mode);
|
|
2313
|
+
}
|
|
2314
|
+
async _setInteractivePermissionMode(mode, canApply) {
|
|
2315
|
+
if (!this._permissionGate)
|
|
2316
|
+
return undefined;
|
|
2317
|
+
const generation = ++this._interactivePermissionModeGeneration;
|
|
2318
|
+
const startup = await this._startupPermissionMode;
|
|
2319
|
+
if (startup?.ok === false)
|
|
2320
|
+
throw startup.error;
|
|
2321
|
+
if (generation !== this._interactivePermissionModeGeneration)
|
|
2322
|
+
return this.getPermissionMode();
|
|
2323
|
+
if (mode === "bypassPermissions" && startup?.mode !== "bypassPermissions") {
|
|
2324
|
+
throw new Error("Interactive bypassPermissions requires a session that started with bypassPermissions.");
|
|
2325
|
+
}
|
|
2326
|
+
if (canApply && !canApply())
|
|
2327
|
+
throw new Error("Plan approval was cancelled because its dialog is obsolete.");
|
|
2328
|
+
this._interactivePermissionMode = mode;
|
|
2329
|
+
this._planToolSyncGeneration++;
|
|
2330
|
+
this._planToolMode = mode;
|
|
2331
|
+
this._applyRequestedTools();
|
|
2332
|
+
this._setPendingPlanToolSync();
|
|
2333
|
+
return { mode, origin: "interactive" };
|
|
2334
|
+
}
|
|
2335
|
+
clearInteractivePermissionMode() {
|
|
2336
|
+
this._interactivePermissionModeGeneration++;
|
|
2337
|
+
this._interactivePermissionMode = undefined;
|
|
2338
|
+
this._planToolMode = undefined;
|
|
2339
|
+
this._applyRequestedTools();
|
|
2340
|
+
this._schedulePlanToolSync();
|
|
2341
|
+
}
|
|
2158
2342
|
/**
|
|
2159
2343
|
* Persist a permission mode to user scope (`~/.apex-code/agent/permissions.json`).
|
|
2160
2344
|
* Returns the resolution that is actually in force afterwards, which is *not*
|
|
@@ -2165,6 +2349,7 @@ export class AgentSession {
|
|
|
2165
2349
|
if (!this._permissionGate)
|
|
2166
2350
|
return undefined;
|
|
2167
2351
|
await this._permissionGate.store.apply({ type: "setMode", destination: "user", mode });
|
|
2352
|
+
await this._syncPlanToolMode();
|
|
2168
2353
|
return this.getPermissionMode();
|
|
2169
2354
|
}
|
|
2170
2355
|
/**
|
|
@@ -3095,6 +3280,7 @@ export class AgentSession {
|
|
|
3095
3280
|
}
|
|
3096
3281
|
async bindExtensions(bindings) {
|
|
3097
3282
|
if (bindings.uiContext !== undefined) {
|
|
3283
|
+
this._planPresentationGeneration++;
|
|
3098
3284
|
this._extensionUIContext = bindings.uiContext;
|
|
3099
3285
|
}
|
|
3100
3286
|
if (bindings.mode !== undefined) {
|
|
@@ -3293,7 +3479,7 @@ export class AgentSession {
|
|
|
3293
3479
|
}
|
|
3294
3480
|
_refreshToolRegistry(options) {
|
|
3295
3481
|
const previousRegistryNames = new Set(this._toolRegistry.keys());
|
|
3296
|
-
const previousActiveToolNames = this.
|
|
3482
|
+
const previousActiveToolNames = this._requestedToolNames;
|
|
3297
3483
|
const allowedToolNames = this._allowedToolNames;
|
|
3298
3484
|
const excludedToolNames = this._excludedToolNames;
|
|
3299
3485
|
const isAllowedTool = (name) => (!allowedToolNames || allowedToolNames.has(name)) && !excludedToolNames?.has(name);
|
|
@@ -3374,7 +3560,8 @@ export class AgentSession {
|
|
|
3374
3560
|
if (hasDeferredActiveTool && this._toolRegistry.has("tool_schema")) {
|
|
3375
3561
|
nextActiveToolNames.push("tool_schema");
|
|
3376
3562
|
}
|
|
3377
|
-
this.
|
|
3563
|
+
this._requestedToolNames = [...new Set(nextActiveToolNames)].filter((name) => this._toolRegistry.has(name));
|
|
3564
|
+
this._applyRequestedTools();
|
|
3378
3565
|
}
|
|
3379
3566
|
_buildRuntime(options) {
|
|
3380
3567
|
const autoResizeImages = this.settingsManager.getImageAutoResize();
|
|
@@ -3409,6 +3596,9 @@ export class AgentSession {
|
|
|
3409
3596
|
this._loadedDeferredSchemas.add(name);
|
|
3410
3597
|
},
|
|
3411
3598
|
});
|
|
3599
|
+
baseToolDefinitions.plan_present = createPlanPresentToolDefinition({
|
|
3600
|
+
present: (plan, ctx, signal) => this._presentPlan(plan, ctx, signal),
|
|
3601
|
+
});
|
|
3412
3602
|
baseToolDefinitions.todo_write = createTodoWriteToolDefinition({
|
|
3413
3603
|
write: (todos) => {
|
|
3414
3604
|
this.sessionManager.appendCustomEntry(TODO_CUSTOM_ENTRY_TYPE, todos);
|
|
@@ -3433,11 +3623,10 @@ export class AgentSession {
|
|
|
3433
3623
|
}
|
|
3434
3624
|
this._bindExtensionCore(this._extensionRunner);
|
|
3435
3625
|
this._applyExtensionBindings(this._extensionRunner);
|
|
3436
|
-
const defaultActiveToolNames =
|
|
3437
|
-
? Object.keys(this._baseToolsOverride)
|
|
3438
|
-
: this._lspOperations
|
|
3439
|
-
|
|
3440
|
-
: ["read", "bash", "edit", "write"];
|
|
3626
|
+
const defaultActiveToolNames = configuredDefaultToolNames({
|
|
3627
|
+
baseNames: this._baseToolsOverride ? Object.keys(this._baseToolsOverride) : undefined,
|
|
3628
|
+
lsp: Boolean(this._lspOperations),
|
|
3629
|
+
});
|
|
3441
3630
|
const baseActiveToolNames = options.activeToolNames ?? defaultActiveToolNames;
|
|
3442
3631
|
this._refreshToolRegistry({
|
|
3443
3632
|
activeToolNames: baseActiveToolNames,
|
|
@@ -3445,6 +3634,8 @@ export class AgentSession {
|
|
|
3445
3634
|
});
|
|
3446
3635
|
}
|
|
3447
3636
|
async reload(options) {
|
|
3637
|
+
this._planPresentationGeneration++;
|
|
3638
|
+
this.clearInteractivePermissionMode();
|
|
3448
3639
|
const oldRunner = this._extensionRunner;
|
|
3449
3640
|
const previousFlagValues = oldRunner.getFlagValues();
|
|
3450
3641
|
await emitSessionShutdownEvent(oldRunner, { type: "session_shutdown", reason: "reload" });
|
|
@@ -3454,7 +3645,7 @@ export class AgentSession {
|
|
|
3454
3645
|
resetApiProviders();
|
|
3455
3646
|
await this._resourceLoader.reload();
|
|
3456
3647
|
this._buildRuntime({
|
|
3457
|
-
activeToolNames: this.
|
|
3648
|
+
activeToolNames: this._requestedToolNames,
|
|
3458
3649
|
flagValues: previousFlagValues,
|
|
3459
3650
|
includeAllExtensionTools: true,
|
|
3460
3651
|
});
|
|
@@ -3519,6 +3710,7 @@ export class AgentSession {
|
|
|
3519
3710
|
success: false,
|
|
3520
3711
|
attempt,
|
|
3521
3712
|
finalError: "Retry cancelled",
|
|
3713
|
+
aborted: true,
|
|
3522
3714
|
});
|
|
3523
3715
|
}
|
|
3524
3716
|
/**
|
|
@@ -3868,51 +4060,63 @@ export class AgentSession {
|
|
|
3868
4060
|
this._resolveIdleWaitIfIdle();
|
|
3869
4061
|
}
|
|
3870
4062
|
}
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
async _resolveTreeWorkspaceStep(targetId, policy) {
|
|
3879
|
-
const unchanged = { policy, outcome: "unchanged", warnings: [] };
|
|
3880
|
-
if (policy === "keep")
|
|
3881
|
-
return { outcome: unchanged };
|
|
4063
|
+
async previewTreeWorkspace(targetId) {
|
|
4064
|
+
const inspection = await this._inspectTreeWorkspace(targetId);
|
|
4065
|
+
return inspection.state === "unavailable"
|
|
4066
|
+
? { state: inspection.state, reason: inspection.reason }
|
|
4067
|
+
: { state: inspection.state };
|
|
4068
|
+
}
|
|
4069
|
+
async _inspectTreeWorkspace(targetId) {
|
|
3882
4070
|
try {
|
|
3883
4071
|
const engine = await this._checkpoints.engine();
|
|
3884
4072
|
if (!engine) {
|
|
3885
4073
|
return {
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
warnings: ["checkpoints are unavailable; workspace left unchanged"],
|
|
3890
|
-
},
|
|
4074
|
+
state: "unavailable",
|
|
4075
|
+
reason: "checkpoints are unavailable; workspace left unchanged",
|
|
4076
|
+
navigationOutcome: "missing-checkpoint",
|
|
3891
4077
|
};
|
|
3892
4078
|
}
|
|
3893
4079
|
const checkpoint = await engine.lookup(targetId);
|
|
3894
4080
|
if (!checkpoint)
|
|
3895
|
-
return {
|
|
4081
|
+
return { state: "no-checkpoint" };
|
|
3896
4082
|
const matches = await engine.matchesWorktree(checkpoint);
|
|
3897
4083
|
if (matches === undefined) {
|
|
3898
4084
|
return {
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
warnings: ["could not compare the workspace against the checkpoint"],
|
|
3903
|
-
},
|
|
4085
|
+
state: "unavailable",
|
|
4086
|
+
reason: "could not compare the workspace against the checkpoint",
|
|
4087
|
+
navigationOutcome: "failed",
|
|
3904
4088
|
};
|
|
3905
4089
|
}
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
4090
|
+
return { state: matches ? "matches" : "differs", engine, checkpoint };
|
|
4091
|
+
}
|
|
4092
|
+
catch (error) {
|
|
4093
|
+
return {
|
|
4094
|
+
state: "unavailable",
|
|
4095
|
+
reason: `workspace policy check failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
4096
|
+
navigationOutcome: "failed",
|
|
4097
|
+
};
|
|
4098
|
+
}
|
|
4099
|
+
}
|
|
4100
|
+
async _resolveTreeWorkspaceStep(targetId, policy) {
|
|
4101
|
+
const unchanged = { policy, outcome: "unchanged", warnings: [] };
|
|
4102
|
+
if (policy === "keep")
|
|
4103
|
+
return { outcome: unchanged };
|
|
4104
|
+
const inspection = await this._inspectTreeWorkspace(targetId);
|
|
4105
|
+
if (inspection.state === "no-checkpoint") {
|
|
4106
|
+
return { outcome: { policy, outcome: "missing-checkpoint", warnings: [] } };
|
|
4107
|
+
}
|
|
4108
|
+
if (inspection.state === "unavailable") {
|
|
4109
|
+
return { outcome: { policy, outcome: inspection.navigationOutcome, warnings: [inspection.reason] } };
|
|
4110
|
+
}
|
|
4111
|
+
if (inspection.state === "matches")
|
|
4112
|
+
return { outcome: unchanged };
|
|
4113
|
+
if (policy === "fail-if-drifted" || policy === "cancel") {
|
|
4114
|
+
return { cancelled: true, outcome: { policy, outcome: "refused-drifted", warnings: [] } };
|
|
4115
|
+
}
|
|
4116
|
+
try {
|
|
4117
|
+
const preRestore = await inspection.engine.restore(inspection.checkpoint);
|
|
3912
4118
|
if (!preRestore) {
|
|
3913
|
-
return {
|
|
3914
|
-
outcome: { policy, outcome: "failed", warnings: ["restore failed; workspace left unchanged"] },
|
|
3915
|
-
};
|
|
4119
|
+
return { outcome: { policy, outcome: "failed", warnings: ["restore failed; workspace left unchanged"] } };
|
|
3916
4120
|
}
|
|
3917
4121
|
return {
|
|
3918
4122
|
outcome: {
|