@prestyj/cli 5.30.2 → 5.31.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/dist/app-sidecar/app-settings.d.ts +6 -2
- package/dist/app-sidecar/app-settings.js +39 -20
- package/dist/app-sidecar/app-settings.js.map +1 -1
- package/dist/app-sidecar/git-target.d.ts +17 -0
- package/dist/app-sidecar/git-target.js +31 -0
- package/dist/app-sidecar/git-target.js.map +1 -0
- package/dist/app-sidecar/goal-routes.d.ts +6 -0
- package/dist/app-sidecar/goal-routes.js +360 -0
- package/dist/app-sidecar/goal-routes.js.map +1 -0
- package/dist/app-sidecar/history-routes.d.ts +13 -0
- package/dist/app-sidecar/history-routes.js +37 -27
- package/dist/app-sidecar/history-routes.js.map +1 -1
- package/dist/app-sidecar/mcp-routes.js +1 -2
- package/dist/app-sidecar/mcp-routes.js.map +1 -1
- package/dist/app-sidecar/prompt-routes.js +1 -2
- package/dist/app-sidecar/prompt-routes.js.map +1 -1
- package/dist/app-sidecar/route-context.d.ts +12 -0
- package/dist/app-sidecar/run-control-routes.js +2 -1
- package/dist/app-sidecar/run-control-routes.js.map +1 -1
- package/dist/app-sidecar/session-routes.js +41 -0
- package/dist/app-sidecar/session-routes.js.map +1 -1
- package/dist/app-sidecar/session-types.d.ts +2 -0
- package/dist/app-sidecar/workspace-routes.js +13 -95
- package/dist/app-sidecar/workspace-routes.js.map +1 -1
- package/dist/app-sidecar.js +219 -67
- package/dist/app-sidecar.js.map +1 -1
- package/dist/cli/shared.js +1 -1
- package/dist/cli/shared.js.map +1 -1
- package/dist/core/acceptance-checks.js +17 -4
- package/dist/core/acceptance-checks.js.map +1 -1
- package/dist/core/agent-notifications.d.ts +1 -1
- package/dist/core/agent-session.d.ts +90 -5
- package/dist/core/agent-session.js +185 -63
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/ask-user.d.ts +11 -0
- package/dist/core/ask-user.js +6 -1
- package/dist/core/ask-user.js.map +1 -1
- package/dist/core/auth-providers.d.ts +2 -80
- package/dist/core/auth-providers.js +4 -183
- package/dist/core/auth-providers.js.map +1 -1
- package/dist/core/cache-diagnostics.d.ts +1 -1
- package/dist/core/cache-diagnostics.js +2 -1
- package/dist/core/cache-diagnostics.js.map +1 -1
- package/dist/core/compaction/tool-result-pruner.d.ts +10 -0
- package/dist/core/compaction/tool-result-pruner.js +12 -0
- package/dist/core/compaction/tool-result-pruner.js.map +1 -1
- package/dist/core/context-files.d.ts +8 -0
- package/dist/core/context-files.js +46 -0
- package/dist/core/context-files.js.map +1 -0
- package/dist/core/custom-commands.d.ts +6 -0
- package/dist/core/custom-commands.js +17 -0
- package/dist/core/custom-commands.js.map +1 -1
- package/dist/core/env-delta.d.ts +9 -1
- package/dist/core/env-delta.js +20 -0
- package/dist/core/env-delta.js.map +1 -1
- package/dist/core/goal-summary.d.ts +32 -0
- package/dist/core/goal-summary.js +104 -0
- package/dist/core/goal-summary.js.map +1 -0
- package/dist/core/goals-store.d.ts +208 -0
- package/dist/core/goals-store.js +521 -0
- package/dist/core/goals-store.js.map +1 -0
- package/dist/core/local-endpoint-store.js +15 -11
- package/dist/core/local-endpoint-store.js.map +1 -1
- package/dist/core/plan-handoff.d.ts +8 -0
- package/dist/core/plan-handoff.js +11 -0
- package/dist/core/plan-handoff.js.map +1 -1
- package/dist/core/project-discovery.d.ts +7 -0
- package/dist/core/project-discovery.js +9 -3
- package/dist/core/project-discovery.js.map +1 -1
- package/dist/core/radio.d.ts +2 -3
- package/dist/core/radio.js.map +1 -1
- package/dist/core/session-manager.d.ts +7 -1
- package/dist/core/session-manager.js +17 -7
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-storage.d.ts +3 -0
- package/dist/core/session-storage.js +8 -2
- package/dist/core/session-storage.js.map +1 -1
- package/dist/core/steroids.d.ts +8 -0
- package/dist/core/steroids.js +19 -3
- package/dist/core/steroids.js.map +1 -1
- package/dist/core/subagent-manager.d.ts +51 -0
- package/dist/core/subagent-manager.js +103 -16
- package/dist/core/subagent-manager.js.map +1 -1
- package/dist/core/subagent-store.js +2 -1
- package/dist/core/subagent-store.js.map +1 -1
- package/dist/goals-agent/goal-team.d.ts +2 -0
- package/dist/goals-agent/goal-team.js +83 -0
- package/dist/goals-agent/goal-team.js.map +1 -0
- package/dist/goals-agent/goals-agent.d.ts +90 -0
- package/dist/goals-agent/goals-agent.js +246 -0
- package/dist/goals-agent/goals-agent.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/interactive.js +4 -3
- package/dist/interactive.js.map +1 -1
- package/dist/modes/acp-autopilot.d.ts +27 -0
- package/dist/modes/acp-autopilot.js +54 -0
- package/dist/modes/acp-autopilot.js.map +1 -0
- package/dist/modes/acp-goals.d.ts +48 -0
- package/dist/modes/acp-goals.js +103 -0
- package/dist/modes/acp-goals.js.map +1 -0
- package/dist/modes/acp-mode.d.ts +128 -3
- package/dist/modes/acp-mode.js +1082 -52
- package/dist/modes/acp-mode.js.map +1 -1
- package/dist/modes/subagent-worker-mode.d.ts +14 -0
- package/dist/modes/subagent-worker-mode.js +32 -1
- package/dist/modes/subagent-worker-mode.js.map +1 -1
- package/dist/system-prompt.d.ts +8 -0
- package/dist/system-prompt.js +21 -9
- package/dist/system-prompt.js.map +1 -1
- package/dist/tools/bash-read-evidence.d.ts +13 -0
- package/dist/tools/bash-read-evidence.js +48 -1
- package/dist/tools/bash-read-evidence.js.map +1 -1
- package/dist/tools/grep-scan.d.ts +96 -0
- package/dist/tools/grep-scan.js +283 -0
- package/dist/tools/grep-scan.js.map +1 -0
- package/dist/tools/grep.d.ts +10 -0
- package/dist/tools/grep.js +418 -10
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/index.d.ts +7 -1
- package/dist/tools/index.js +10 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/read-tracker.d.ts +6 -12
- package/dist/tools/read-tracker.js +21 -36
- package/dist/tools/read-tracker.js.map +1 -1
- package/dist/tools/read.js +2 -2
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/subagent-control.js +22 -3
- package/dist/tools/subagent-control.js.map +1 -1
- package/dist/tools/write.js +110 -8
- package/dist/tools/write.js.map +1 -1
- package/dist/ui/components/ActivityIndicator.d.ts +5 -5
- package/dist/ui/components/InputArea.d.ts +2 -2
- package/dist/ui/components/InputArea.js +3 -3
- package/dist/ui/components/InputArea.js.map +1 -1
- package/dist/ui/components/ToolExecution.d.ts +2 -2
- package/dist/ui/hooks/useContextCompaction.js +38 -31
- package/dist/ui/hooks/useContextCompaction.js.map +1 -1
- package/dist/utils/format.d.ts +0 -4
- package/dist/utils/format.js +0 -7
- package/dist/utils/format.js.map +1 -1
- package/package.json +4 -4
|
@@ -5,7 +5,7 @@ import { flagUntrustedToolResult } from "./injection-detect.js";
|
|
|
5
5
|
import { COMPLETION_REVIEW_STATE_KIND, } from "./completion-review.js";
|
|
6
6
|
import { SlashCommandRegistry, createBuiltinCommands, } from "./slash-commands.js";
|
|
7
7
|
import { PROMPT_COMMANDS, getPromptCommand } from "./prompt-commands.js";
|
|
8
|
-
import {
|
|
8
|
+
import { loadWorkspaceCustomCommands } from "./custom-commands.js";
|
|
9
9
|
import { expandPromptCommand } from "./prompt-command-expansion.js";
|
|
10
10
|
import { SettingsManager } from "./settings-manager.js";
|
|
11
11
|
import { AuthStorage } from "./auth-storage.js";
|
|
@@ -27,7 +27,7 @@ import { partitionToolsByTier } from "../tools/tool-tiers.js";
|
|
|
27
27
|
import { formatImpactForVerification } from "./test-impact.js";
|
|
28
28
|
import { buildProcessCompletionFollowUp } from "./process-gate.js";
|
|
29
29
|
import { PLAN_SUBMISSION_NUDGE, shouldNudgePlanSubmission } from "./plan-submission-gate.js";
|
|
30
|
-
import { buildSubAgentCompletionFollowUp } from "./subagent-manager.js";
|
|
30
|
+
import { buildSubAgentCompletionFollowUp, } from "./subagent-manager.js";
|
|
31
31
|
import { applyAsyncSubagentPolicy } from "./subagent-policy.js";
|
|
32
32
|
import { resolveResponsesLite, resolveStrictTools, } from "./codex-request-shape.js";
|
|
33
33
|
import { subAgentDescription } from "../tools/subagent.js";
|
|
@@ -46,14 +46,14 @@ import { decidePlanStepCompaction, DEFAULT_CACHE_WRITE_READ_RATIO, PLAN_STEP_KEE
|
|
|
46
46
|
import { extractPlanSteps, findCompletedMarkers } from "../utils/plan-steps.js";
|
|
47
47
|
import { readFileSync } from "node:fs";
|
|
48
48
|
import { clampThinkingForPlanMode } from "./thinking-level.js";
|
|
49
|
-
import { pruneStaleToolResults } from "./compaction/tool-result-pruner.js";
|
|
49
|
+
import { pruneStaleToolResults, shouldPruneToolResults } from "./compaction/tool-result-pruner.js";
|
|
50
50
|
import { discoverAgents } from "./agents.js";
|
|
51
51
|
import { enhancePrompt } from "../utils/prompt-enhancer.js";
|
|
52
52
|
import { detectLanguages, detectProjectStack } from "./language-detector.js";
|
|
53
53
|
import { buildLoopBreakMessage } from "./loop-breaker.js";
|
|
54
54
|
import { buildRegroundingMessage, requestTextForRegrounding } from "./regrounding.js";
|
|
55
55
|
import { buildSemanticLoopJudgePrompt, buildSemanticLoopMessage, parseSemanticLoopVerdict, SEMANTIC_LOOP_JUDGE_TIMEOUT_MS, withJudgeTimeout, } from "./semantic-loop-check.js";
|
|
56
|
-
import { buildEnvDeltaMessage } from "./env-delta.js";
|
|
56
|
+
import { buildEnvDeltaMessage, buildLanguageUpdateMessage } from "./env-delta.js";
|
|
57
57
|
import { wrapSteeringText, buildNotificationSteeringText, STEERING_PREFIX } from "./steering.js";
|
|
58
58
|
import { AgentNotificationQueue } from "./agent-notifications.js";
|
|
59
59
|
import { VerificationGate, VERIFICATION_STATE_KIND } from "./verification-gate.js";
|
|
@@ -220,6 +220,9 @@ export class AgentSession {
|
|
|
220
220
|
* for no benefit.
|
|
221
221
|
*/
|
|
222
222
|
activeLanguages = new Set();
|
|
223
|
+
/** Languages the model has already been given guidance for, via the system
|
|
224
|
+
* prompt or an appended language update. */
|
|
225
|
+
announcedLanguages = new Set();
|
|
223
226
|
/** Shared with the tool layer so plan-mode restrictions read live state. */
|
|
224
227
|
planModeRef = { current: false };
|
|
225
228
|
/** Path of the approved plan currently being implemented, or undefined. When
|
|
@@ -309,7 +312,10 @@ export class AgentSession {
|
|
|
309
312
|
// Session manager. Agent-specific roots keep chat and coder histories isolated.
|
|
310
313
|
this.sessionManager = new SessionManager(this.opts.sessionRootDir ?? paths.sessionsDir);
|
|
311
314
|
const projectCustomization = this.opts.projectCustomization !== false;
|
|
312
|
-
|
|
315
|
+
// A sub-agent worker never creates them: the session that owns the
|
|
316
|
+
// project does. EZ Goals' workers work in the user's attached folders,
|
|
317
|
+
// which mustn't gain empty .ezcoder folders as a side effect.
|
|
318
|
+
if (projectCustomization && !this.opts.subagentWorker) {
|
|
313
319
|
// Ensure project-local .ezcoder directories exist.
|
|
314
320
|
const localEzDir = path.join(this.cwd, ".ezcoder");
|
|
315
321
|
await fs.mkdir(path.join(localEzDir, "skills"), { recursive: true });
|
|
@@ -330,16 +336,20 @@ export class AgentSession {
|
|
|
330
336
|
}
|
|
331
337
|
// Discover agents and create tools (with sub-agent support). Chat sessions
|
|
332
338
|
// can retain bundled workers without loading project/global customization.
|
|
333
|
-
const agents =
|
|
334
|
-
?
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
:
|
|
339
|
+
const agents = this.opts.subAgents
|
|
340
|
+
? [...this.opts.subAgents]
|
|
341
|
+
: projectCustomization
|
|
342
|
+
? await discoverAgents({
|
|
343
|
+
globalAgentsDir: paths.agentsDir,
|
|
344
|
+
projectDir: this.cwd,
|
|
345
|
+
})
|
|
346
|
+
: this.opts.globalSubagents
|
|
347
|
+
? await discoverAgents({ globalAgentsDir: paths.agentsDir })
|
|
348
|
+
: [];
|
|
341
349
|
const { tools: builtInTools, processManager, rebuildReadTool, clearReadTracker, recordBashReads, lspManager, testImpact, debugManager, subAgentManager, } = await createTools(this.cwd, {
|
|
342
350
|
agents,
|
|
351
|
+
shareWorkspaceRoots: this.opts.shareRootsWithSubAgents,
|
|
352
|
+
defaultSubAgent: this.opts.defaultSubAgent,
|
|
343
353
|
skills: this.skills,
|
|
344
354
|
contextLimits: this.contextLimits,
|
|
345
355
|
provider: this.provider,
|
|
@@ -381,6 +391,7 @@ export class AgentSession {
|
|
|
381
391
|
getBaseUrl: () => this.baseUrl,
|
|
382
392
|
getCacheKey: () => this.getPromptCacheKey(),
|
|
383
393
|
getMaxPerModel: () => this.settingsManager.get("subagentMaxPerModel"),
|
|
394
|
+
getChildModel: this.opts.getChildModel,
|
|
384
395
|
// A persistent child is already the single allowed fan-out level. Blocking
|
|
385
396
|
// `subagent` here created a timeout sandwich: the nested call could consume
|
|
386
397
|
// the child's entire 10-minute turn budget, discarding all nested results.
|
|
@@ -408,6 +419,16 @@ export class AgentSession {
|
|
|
408
419
|
// a hallucinated call can't mutate the repo — and buildSystemPrompt below is
|
|
409
420
|
// fed the same filtered names so the Tools section matches exactly.
|
|
410
421
|
this.tools = this.opts.allowedTools ? tools.filter((t) => this.isToolAllowed(t.name)) : tools;
|
|
422
|
+
const without = new Set(this.opts.withoutTools ?? []);
|
|
423
|
+
if (without.size > 0)
|
|
424
|
+
this.tools = this.tools.filter((t) => !without.has(t.name));
|
|
425
|
+
// A host may wrap built-ins (EZ Goals holds back its own editing once a
|
|
426
|
+
// goal is underway). Same names and schemas, so the cached tool block and
|
|
427
|
+
// the system prompt's Tools section are unchanged.
|
|
428
|
+
if (this.opts.wrapTool) {
|
|
429
|
+
const wrap = this.opts.wrapTool;
|
|
430
|
+
this.tools = this.tools.map((t) => wrap(t));
|
|
431
|
+
}
|
|
411
432
|
// Both delegation tools carry the agent roster; when both survive the
|
|
412
433
|
// allow-list, `subagent` points at `spawn_agent`'s copy instead.
|
|
413
434
|
if (this.tools.some((t) => t.name === "spawn_agent")) {
|
|
@@ -422,7 +443,14 @@ export class AgentSession {
|
|
|
422
443
|
// catalog must hold the wrapped tool so a promoted spawn still loads
|
|
423
444
|
// wait_agent.
|
|
424
445
|
this.promoteFollowUpTools();
|
|
425
|
-
const
|
|
446
|
+
const partition = partitionToolsByTier(this.tools);
|
|
447
|
+
// A session can keep chosen built-ins in its core set (EZ Goals
|
|
448
|
+
// orchestrates, so its agent tools are always at hand). Fixed for the
|
|
449
|
+
// session, so the cached tool block stays byte-stable.
|
|
450
|
+
const keep = new Set(this.opts.coreTools ?? []);
|
|
451
|
+
const deferred = partition.deferred.filter((t) => !keep.has(t.name));
|
|
452
|
+
// Everything not deferred, in the original relative order.
|
|
453
|
+
const core = keep.size > 0 ? this.tools.filter((t) => !deferred.includes(t)) : partition.core;
|
|
426
454
|
if (deferred.length > 0) {
|
|
427
455
|
// Append-only: `core` preserves the original relative order and
|
|
428
456
|
// tool_search is pushed after it, so the serialized tool block that
|
|
@@ -454,6 +482,9 @@ export class AgentSession {
|
|
|
454
482
|
else {
|
|
455
483
|
await this.connectMcpServers();
|
|
456
484
|
}
|
|
485
|
+
// Detect languages before the first render so the opening prompt already
|
|
486
|
+
// carries their packs and only a genuinely new language needs an update.
|
|
487
|
+
this.refreshActiveLanguages();
|
|
457
488
|
const basePrompt = await this.buildBasePrompt(false, undefined);
|
|
458
489
|
this.baseSystemPrompt = basePrompt;
|
|
459
490
|
this.messages = [{ role: "system", content: this.withSystemPromptTail(basePrompt) }];
|
|
@@ -530,7 +561,6 @@ export class AgentSession {
|
|
|
530
561
|
const helpCmd = this.slashCommands.get("help");
|
|
531
562
|
if (helpCmd) {
|
|
532
563
|
const registry = this.slashCommands;
|
|
533
|
-
const cwd = this.cwd;
|
|
534
564
|
helpCmd.execute = async () => {
|
|
535
565
|
const all = registry.getAll();
|
|
536
566
|
const lines = all.map((c) => ` /${c.name}${c.aliases.length ? ` (${c.aliases.map((a) => "/" + a).join(", ")})` : ""} — ${c.description}`);
|
|
@@ -540,7 +570,7 @@ export class AgentSession {
|
|
|
540
570
|
lines.push(` /${cmd.name}${cmd.aliases.length ? ` (${cmd.aliases.map((a) => "/" + a).join(", ")})` : ""} — ${cmd.description}`);
|
|
541
571
|
}
|
|
542
572
|
}
|
|
543
|
-
const customCmds = await loadCustomCommands(
|
|
573
|
+
const customCmds = await this.loadCustomCommands();
|
|
544
574
|
if (customCmds.length > 0) {
|
|
545
575
|
lines.push("", "Custom commands:");
|
|
546
576
|
for (const cmd of customCmds)
|
|
@@ -748,7 +778,7 @@ export class AgentSession {
|
|
|
748
778
|
return null;
|
|
749
779
|
// EZ Coder alone can resolve its prompt-template and project commands.
|
|
750
780
|
const builtinPromptCmd = coderCommands ? getPromptCommand(parsed.name) : undefined;
|
|
751
|
-
const customCmds = coderCommands ? await loadCustomCommands(
|
|
781
|
+
const customCmds = coderCommands ? await this.loadCustomCommands() : [];
|
|
752
782
|
const customPromptCmd = !builtinPromptCmd
|
|
753
783
|
? customCmds.find((c) => c.name === parsed.name)
|
|
754
784
|
: undefined;
|
|
@@ -786,6 +816,7 @@ export class AgentSession {
|
|
|
786
816
|
if (slash?.kind === "template") {
|
|
787
817
|
// Prompt templates remain human-originated: the user invoked the command.
|
|
788
818
|
const userMessage = { role: "user", content: slash.fullPrompt, provenance };
|
|
819
|
+
await this.announceNewLanguages();
|
|
789
820
|
this.messages.push(userMessage);
|
|
790
821
|
await this.persistMessage(userMessage);
|
|
791
822
|
this.lastPersistedIndex = this.messages.length;
|
|
@@ -802,6 +833,7 @@ export class AgentSession {
|
|
|
802
833
|
}
|
|
803
834
|
// Push user message
|
|
804
835
|
const userMessage = { role: "user", content, provenance };
|
|
836
|
+
await this.announceNewLanguages();
|
|
805
837
|
this.messages.push(userMessage);
|
|
806
838
|
await this.persistMessage(userMessage);
|
|
807
839
|
this.lastPersistedIndex = this.messages.length;
|
|
@@ -826,6 +858,7 @@ export class AgentSession {
|
|
|
826
858
|
content: parts,
|
|
827
859
|
provenance: { source: "human", kind: "prompt", visibility: "transcript" },
|
|
828
860
|
};
|
|
861
|
+
await this.announceNewLanguages();
|
|
829
862
|
this.messages.push(userMessage);
|
|
830
863
|
await this.persistMessage(userMessage);
|
|
831
864
|
this.lastPersistedIndex = this.messages.length;
|
|
@@ -1713,36 +1746,6 @@ export class AgentSession {
|
|
|
1713
1746
|
if (!force) {
|
|
1714
1747
|
if (!this.settingsManager.get("autoCompact"))
|
|
1715
1748
|
return messages;
|
|
1716
|
-
// Cheap stale-tool-output pruning before the expensive LLM
|
|
1717
|
-
// compaction check. In-place mutation preserves anchors; drop the
|
|
1718
|
-
// retained usage afterwards since it counted the pruned content.
|
|
1719
|
-
const pruneResult = pruneStaleToolResults(messages);
|
|
1720
|
-
if (pruneResult.pruned) {
|
|
1721
|
-
this.cacheDiagnostics.noteEdit("tool_prune", pruneResult.freedTokens);
|
|
1722
|
-
this.providerContext = null;
|
|
1723
|
-
log("INFO", "compaction", "Pruned stale tool outputs", {
|
|
1724
|
-
prunedResults: String(pruneResult.prunedResults),
|
|
1725
|
-
compactedToolCalls: String(pruneResult.compactedToolCalls),
|
|
1726
|
-
freedTokens: String(pruneResult.freedTokens),
|
|
1727
|
-
});
|
|
1728
|
-
}
|
|
1729
|
-
if (Date.now() < this.compactionRetryAfter)
|
|
1730
|
-
return messages;
|
|
1731
|
-
// The turn's own usage also counted the pruned content — after a
|
|
1732
|
-
// prune, fall back to estimating the (now smaller) history so the
|
|
1733
|
-
// freed tokens actually defer the LLM compaction.
|
|
1734
|
-
let usage = pruneResult.pruned ? undefined : transformOpts.usage;
|
|
1735
|
-
let pendingMessages = transformOpts.pendingMessages;
|
|
1736
|
-
if (!usage && this.providerContext) {
|
|
1737
|
-
const anchorIndex = messages.lastIndexOf(this.providerContext.anchor);
|
|
1738
|
-
if (anchorIndex >= 0) {
|
|
1739
|
-
usage = this.providerContext.usage;
|
|
1740
|
-
pendingMessages = messages.slice(anchorIndex + 1);
|
|
1741
|
-
}
|
|
1742
|
-
else {
|
|
1743
|
-
this.providerContext = null;
|
|
1744
|
-
}
|
|
1745
|
-
}
|
|
1746
1749
|
const contextWindow = getContextWindow(this.model, {
|
|
1747
1750
|
provider: this.provider,
|
|
1748
1751
|
accountId,
|
|
@@ -1755,10 +1758,48 @@ export class AgentSession {
|
|
|
1755
1758
|
accountId,
|
|
1756
1759
|
approvedPlanPath: this.approvedPlanPath,
|
|
1757
1760
|
});
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1761
|
+
// Provider usage when there is some, else an estimate of the history.
|
|
1762
|
+
const measureActiveTokens = () => {
|
|
1763
|
+
let usage = transformOpts.usage;
|
|
1764
|
+
let pendingMessages = transformOpts.pendingMessages;
|
|
1765
|
+
if (!usage && this.providerContext) {
|
|
1766
|
+
const anchorIndex = messages.lastIndexOf(this.providerContext.anchor);
|
|
1767
|
+
if (anchorIndex >= 0) {
|
|
1768
|
+
usage = this.providerContext.usage;
|
|
1769
|
+
pendingMessages = messages.slice(anchorIndex + 1);
|
|
1770
|
+
}
|
|
1771
|
+
else {
|
|
1772
|
+
this.providerContext = null;
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
return calculateActiveContextTokens(messages, { usage, pendingMessages });
|
|
1776
|
+
};
|
|
1777
|
+
let activeTokens = measureActiveTokens();
|
|
1778
|
+
// Cheap stale-tool-output pruning before the expensive LLM
|
|
1779
|
+
// compaction check, once the context is big enough for the freed
|
|
1780
|
+
// tokens to be worth a cache rewrite. In-place mutation preserves
|
|
1781
|
+
// anchors; drop the retained usage afterwards since it counted the
|
|
1782
|
+
// pruned content.
|
|
1783
|
+
if (shouldPruneToolResults(activeTokens, policy.targetTokens)) {
|
|
1784
|
+
const pruneResult = pruneStaleToolResults(messages);
|
|
1785
|
+
if (pruneResult.pruned) {
|
|
1786
|
+
this.cacheDiagnostics.noteEdit("tool_prune", pruneResult.freedTokens);
|
|
1787
|
+
this.providerContext = null;
|
|
1788
|
+
log("INFO", "compaction", "Pruned stale tool outputs", {
|
|
1789
|
+
prunedResults: String(pruneResult.prunedResults),
|
|
1790
|
+
compactedToolCalls: String(pruneResult.compactedToolCalls),
|
|
1791
|
+
freedTokens: String(pruneResult.freedTokens),
|
|
1792
|
+
});
|
|
1793
|
+
// The turn's own usage also counted the pruned content — estimate
|
|
1794
|
+
// the (now smaller) history so the freed tokens actually defer
|
|
1795
|
+
// the LLM compaction.
|
|
1796
|
+
activeTokens = calculateActiveContextTokens(messages, {
|
|
1797
|
+
pendingMessages: transformOpts.pendingMessages,
|
|
1798
|
+
});
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
if (Date.now() < this.compactionRetryAfter)
|
|
1802
|
+
return messages;
|
|
1762
1803
|
// An approved plan runs as ONE run, so step boundaries are only
|
|
1763
1804
|
// visible here, between model steps — not after the run ends.
|
|
1764
1805
|
const planStep = this.observePlanStepProgress(messages, contextWindow, activeTokens);
|
|
@@ -2510,6 +2551,9 @@ export class AgentSession {
|
|
|
2510
2551
|
// new session's /history, and get re-persisted into the new file by the
|
|
2511
2552
|
// next compaction — the cross-session duplicate-marker propagation bug.
|
|
2512
2553
|
this.markers.clear();
|
|
2554
|
+
// Detect languages before the first render so the opening prompt already
|
|
2555
|
+
// carries their packs and only a genuinely new language needs an update.
|
|
2556
|
+
this.refreshActiveLanguages();
|
|
2513
2557
|
const basePrompt = await this.buildBasePrompt(false, undefined);
|
|
2514
2558
|
this.baseSystemPrompt = basePrompt;
|
|
2515
2559
|
this.messages = [{ role: "system", content: this.withSystemPromptTail(basePrompt) }];
|
|
@@ -2713,6 +2757,19 @@ export class AgentSession {
|
|
|
2713
2757
|
.map((m) => m.text)
|
|
2714
2758
|
.join("\n\n");
|
|
2715
2759
|
}
|
|
2760
|
+
/**
|
|
2761
|
+
* Tell the agent a new fact at its next turn, as a short hidden message
|
|
2762
|
+
* appended after the cached prompt (the same channel as a finished worker),
|
|
2763
|
+
* so a mid-session change reaches it without re-rendering the prompt. A
|
|
2764
|
+
* later note with the same id replaces one not yet delivered.
|
|
2765
|
+
*/
|
|
2766
|
+
notify(id, text) {
|
|
2767
|
+
this.notifications.enqueue("goal", id, text, { terminal: true });
|
|
2768
|
+
}
|
|
2769
|
+
/** This session's sub-agents (workers), newest-state, including ones restored after a restart. */
|
|
2770
|
+
listSubAgents() {
|
|
2771
|
+
return this.subAgentManager?.list() ?? [];
|
|
2772
|
+
}
|
|
2716
2773
|
/** Snapshot of background processes (bash run_in_background), newest-state. */
|
|
2717
2774
|
listBackgroundProcesses() {
|
|
2718
2775
|
return this.processManager?.list() ?? [];
|
|
@@ -2780,6 +2837,14 @@ export class AgentSession {
|
|
|
2780
2837
|
getAdditionalRoots() {
|
|
2781
2838
|
return [...this.additionalRoots];
|
|
2782
2839
|
}
|
|
2840
|
+
/**
|
|
2841
|
+
* The custom `.ezcoder/commands` slash commands this session can run: the
|
|
2842
|
+
* project's, then those of every added root (a goal's attached repos too).
|
|
2843
|
+
* Read fresh, so a new command file is picked up without a restart.
|
|
2844
|
+
*/
|
|
2845
|
+
loadCustomCommands() {
|
|
2846
|
+
return loadWorkspaceCustomCommands([this.cwd, ...this.additionalRoots]);
|
|
2847
|
+
}
|
|
2783
2848
|
/**
|
|
2784
2849
|
* Add another workspace root. Tools already accept absolute paths, so this
|
|
2785
2850
|
* only widens the write guard and tells the model the root exists. Rebuilding
|
|
@@ -2811,7 +2876,7 @@ export class AgentSession {
|
|
|
2811
2876
|
return relative.startsWith("..") || path.isAbsolute(relative);
|
|
2812
2877
|
});
|
|
2813
2878
|
this.additionalRoots.push(resolved);
|
|
2814
|
-
await this.
|
|
2879
|
+
await this.refreshPromptForRoots();
|
|
2815
2880
|
return { ok: true, root: resolved };
|
|
2816
2881
|
}
|
|
2817
2882
|
/** Remove an exact root previously added with `/add-dir`. */
|
|
@@ -2822,9 +2887,20 @@ export class AgentSession {
|
|
|
2822
2887
|
return { ok: false, error: `Not an additional workspace root: ${resolved}` };
|
|
2823
2888
|
}
|
|
2824
2889
|
this.additionalRoots.splice(index, 1);
|
|
2825
|
-
await this.
|
|
2890
|
+
await this.refreshPromptForRoots();
|
|
2826
2891
|
return { ok: true, root: resolved };
|
|
2827
2892
|
}
|
|
2893
|
+
/**
|
|
2894
|
+
* Name a root change in the prompt only while there is no conversation yet.
|
|
2895
|
+
* Mid-conversation the env-delta note tells the model instead: the roots sit
|
|
2896
|
+
* in the Environment section, and rewriting it would invalidate every cached
|
|
2897
|
+
* message after it (a goal attaching a folder re-sent ~75k tokens).
|
|
2898
|
+
*/
|
|
2899
|
+
async refreshPromptForRoots() {
|
|
2900
|
+
if (this.messages.some((message) => message.role !== "system"))
|
|
2901
|
+
return;
|
|
2902
|
+
await this.rebuildSystemPromptInPlace();
|
|
2903
|
+
}
|
|
2828
2904
|
/**
|
|
2829
2905
|
* Names to advertise as available-on-demand. A tool the model already
|
|
2830
2906
|
* promoted lives in `this.tools` and carries its own schema, so it drops out
|
|
@@ -2865,6 +2941,8 @@ export class AgentSession {
|
|
|
2865
2941
|
return this.customSystemPrompt;
|
|
2866
2942
|
const toolNames = this.tools.map((tool) => tool.name);
|
|
2867
2943
|
const deferredToolNames = this.deferredToolNamesForPrompt(toolNames);
|
|
2944
|
+
// Whatever is rendered now is what the model has been told.
|
|
2945
|
+
this.announcedLanguages = new Set(this.activeLanguages);
|
|
2868
2946
|
if (this.agentPrompt !== undefined) {
|
|
2869
2947
|
return buildSubAgentSystemPrompt(this.agentPrompt, {
|
|
2870
2948
|
cwd: this.cwd,
|
|
@@ -2874,29 +2952,73 @@ export class AgentSession {
|
|
|
2874
2952
|
role: this.opts.agentRole,
|
|
2875
2953
|
environment: this.recordRenderedEnvironment(),
|
|
2876
2954
|
contextLimits: this.contextLimits,
|
|
2955
|
+
...(this.languagesApply() ? { activeLanguages: this.activeLanguages } : {}),
|
|
2877
2956
|
});
|
|
2878
2957
|
}
|
|
2879
2958
|
return buildSystemPrompt(this.cwd, this.skills, planMode, approvedPlanPath, toolNames, this.activeLanguages, this.provider, this.recordRenderedEnvironment(), deferredToolNames, this.contextLimits);
|
|
2880
2959
|
}
|
|
2881
|
-
/**
|
|
2882
|
-
* Add newly detected project languages. Returns true when the set grew and
|
|
2883
|
-
* the standard prompt therefore needs a rebuild. Custom and sub-agent
|
|
2884
|
-
* prompts never render packs, so detection is skipped for them.
|
|
2885
|
-
*/
|
|
2886
2960
|
/**
|
|
2887
2961
|
* Bring the system prompt to the exact state the next provider request will
|
|
2888
2962
|
* send. Shared by real runs and {@link prewarm} so both see one prefix.
|
|
2963
|
+
*
|
|
2964
|
+
* Languages are re-detected at each task boundary. Before the first reply
|
|
2965
|
+
* nothing is cached worth keeping, so a new language simply rebuilds the
|
|
2966
|
+
* prompt. After that the prompt is a cached prefix and is left alone: the
|
|
2967
|
+
* new language is announced by {@link announceNewLanguages} instead.
|
|
2889
2968
|
*/
|
|
2890
2969
|
async prepareSystemPromptForRequest() {
|
|
2891
|
-
|
|
2892
|
-
// during the previous turn gets its packs; the prompt is rebuilt only when
|
|
2893
|
-
// the set grows, keeping the cached prefix stable otherwise.
|
|
2894
|
-
if (this.refreshActiveLanguages())
|
|
2970
|
+
if (this.refreshActiveLanguages() && !this.hasReply()) {
|
|
2895
2971
|
await this.rebuildSystemPromptInPlace();
|
|
2972
|
+
}
|
|
2896
2973
|
this.refreshSystemPromptTail();
|
|
2897
2974
|
}
|
|
2975
|
+
hasReply() {
|
|
2976
|
+
return this.messages.some((m) => m.role === "assistant");
|
|
2977
|
+
}
|
|
2978
|
+
/**
|
|
2979
|
+
* Give the model style packs for languages that appeared after its prompt
|
|
2980
|
+
* was cached (e.g. the previous task scaffolded a TypeScript project), as a
|
|
2981
|
+
* hidden message appended to the history rather than a prompt re-render that
|
|
2982
|
+
* would throw away the whole cache. Runs just before a new user message is
|
|
2983
|
+
* added, so the user's request stays the last thing the model reads and the
|
|
2984
|
+
* update is saved in order with the rest of the conversation.
|
|
2985
|
+
*/
|
|
2986
|
+
async announceNewLanguages() {
|
|
2987
|
+
if (!this.hasReply())
|
|
2988
|
+
return; // the next request rebuilds the prompt instead
|
|
2989
|
+
this.refreshActiveLanguages();
|
|
2990
|
+
const fresh = new Set([...this.activeLanguages].filter((id) => !this.announcedLanguages.has(id)));
|
|
2991
|
+
if (fresh.size === 0)
|
|
2992
|
+
return;
|
|
2993
|
+
for (const id of fresh)
|
|
2994
|
+
this.announcedLanguages.add(id);
|
|
2995
|
+
const update = buildLanguageUpdateMessage(fresh, this.cwd);
|
|
2996
|
+
if (!update)
|
|
2997
|
+
return;
|
|
2998
|
+
this.messages.push(update);
|
|
2999
|
+
await this.persistMessage(update);
|
|
3000
|
+
this.lastPersistedIndex = this.messages.length;
|
|
3001
|
+
log("INFO", "language", "Appended style packs for new languages", {
|
|
3002
|
+
added: [...fresh].join(","),
|
|
3003
|
+
});
|
|
3004
|
+
}
|
|
3005
|
+
/**
|
|
3006
|
+
* Whether this session renders language packs: the standard prompt, and a
|
|
3007
|
+
* code-writing child (a sub-agent prompt with project context). A verbatim
|
|
3008
|
+
* custom prompt never does, nor do user-facing specialist agents (Motion,
|
|
3009
|
+
* goals) or recon children without project context.
|
|
3010
|
+
*/
|
|
3011
|
+
languagesApply() {
|
|
3012
|
+
if (this.customSystemPrompt)
|
|
3013
|
+
return false;
|
|
3014
|
+
if (this.agentPrompt === undefined)
|
|
3015
|
+
return true;
|
|
3016
|
+
return ((this.opts.agentRole ?? "subagent") === "subagent" &&
|
|
3017
|
+
(this.opts.agentContext ?? "project") === "project");
|
|
3018
|
+
}
|
|
3019
|
+
/** Add newly detected project languages. Returns true when the set grew. */
|
|
2898
3020
|
refreshActiveLanguages() {
|
|
2899
|
-
if (this.
|
|
3021
|
+
if (!this.languagesApply())
|
|
2900
3022
|
return false;
|
|
2901
3023
|
let grew = false;
|
|
2902
3024
|
try {
|