@openeryc/pi-coding-agent 0.75.58 → 0.75.59
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 +19 -0
- package/dist/core/agent-session.d.ts +11 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +32 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/session-manager.d.ts +4 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +19 -2
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts +2 -0
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +8 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/modes/interactive/goal-evaluator.d.ts +23 -0
- package/dist/modes/interactive/goal-evaluator.d.ts.map +1 -0
- package/dist/modes/interactive/goal-evaluator.js +68 -0
- package/dist/modes/interactive/goal-evaluator.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts +6 -6
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +133 -79
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +12 -12
- package/package.json +4 -4
|
@@ -62,7 +62,7 @@ import { ToolExecutionComponent } from "./components/tool-execution.js";
|
|
|
62
62
|
import { TreeSelectorComponent } from "./components/tree-selector.js";
|
|
63
63
|
import { UserMessageComponent } from "./components/user-message.js";
|
|
64
64
|
import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
|
|
65
|
-
import {
|
|
65
|
+
import { evaluateGoalCondition } from "./goal-evaluator.js";
|
|
66
66
|
import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, initTheme, onThemeChange, setRegisteredThemes, setTheme, setThemeInstance, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
|
|
67
67
|
function isExpandable(obj) {
|
|
68
68
|
return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
|
|
@@ -182,13 +182,12 @@ export class InteractiveMode {
|
|
|
182
182
|
goalRunnerAbortController = undefined;
|
|
183
183
|
goalRunnerRetryAttempt = 0;
|
|
184
184
|
goalRunnerStatusDisplayed = false;
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
GOAL_RUNNER_TURN_DELAY_MS =
|
|
190
|
-
|
|
191
|
-
GOAL_RUNNER_MAX_DUPLICATE_STREAK = 3;
|
|
185
|
+
goalRunnerTurnCount = 0;
|
|
186
|
+
goalRunnerStartTime = 0;
|
|
187
|
+
goalRunnerTokenSpend = 0;
|
|
188
|
+
goalRunnerLastEvalReason = "";
|
|
189
|
+
GOAL_RUNNER_TURN_DELAY_MS = 1200;
|
|
190
|
+
GOAL_RUNNER_RETRY_DELAY_MS = 5000;
|
|
192
191
|
// Extension UI state
|
|
193
192
|
extensionSelector = undefined;
|
|
194
193
|
extensionInput = undefined;
|
|
@@ -2077,8 +2076,10 @@ export class InteractiveMode {
|
|
|
2077
2076
|
// Handle commands (with special case for /goal which continues to submit)
|
|
2078
2077
|
if (text.startsWith("/goal")) {
|
|
2079
2078
|
const goal = text.replace(/^\/goal\s*/, "").trim();
|
|
2080
|
-
this.handleGoalCommand(text);
|
|
2079
|
+
const shouldSubmit = await this.handleGoalCommand(text);
|
|
2081
2080
|
this.editor.setText("");
|
|
2081
|
+
if (!shouldSubmit)
|
|
2082
|
+
return;
|
|
2082
2083
|
if (goal)
|
|
2083
2084
|
text = goal;
|
|
2084
2085
|
}
|
|
@@ -4593,64 +4594,115 @@ export class InteractiveMode {
|
|
|
4593
4594
|
this.chatContainer.addChild(new Text(theme.fg("dim", `Session name set: ${name}`), 1, 0));
|
|
4594
4595
|
this.ui.requestRender();
|
|
4595
4596
|
}
|
|
4596
|
-
handleGoalCommand(text) {
|
|
4597
|
+
async handleGoalCommand(text) {
|
|
4597
4598
|
const goal = text.replace(/^\/goal\s*/, "").trim();
|
|
4598
4599
|
if (!goal) {
|
|
4600
|
+
// Show goal status (like Claude Code's /goal with no args)
|
|
4599
4601
|
const currentGoal = this.session.sessionGoal;
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
+
if (!currentGoal) {
|
|
4603
|
+
this.showWarning("Usage: /goal <condition>\n /goal clear|stop|off|reset|cancel - clear goal");
|
|
4604
|
+
this.ui.requestRender();
|
|
4605
|
+
return false;
|
|
4606
|
+
}
|
|
4607
|
+
const elapsed = this.goalRunnerStartTime > 0 ? this.formatDuration(Date.now() - this.goalRunnerStartTime) : "-";
|
|
4608
|
+
const turns = this.goalRunnerTurnCount > 0 ? String(this.goalRunnerTurnCount) : "-";
|
|
4609
|
+
const tokens = this.goalRunnerTokenSpend > 0 ? `${this.goalRunnerTokenSpend.toLocaleString()} tokens` : "-";
|
|
4610
|
+
let info = `${theme.bold(theme.fg("accent", "Session Goal"))}\n\n`;
|
|
4611
|
+
info += `${theme.fg("dim", "Condition:")} ${currentGoal}\n`;
|
|
4612
|
+
info += `${theme.fg("dim", "Running:")} ${elapsed}\n`;
|
|
4613
|
+
info += `${theme.fg("dim", "Turns:")} ${turns}\n`;
|
|
4614
|
+
info += `${theme.fg("dim", "Tokens:")} ${tokens}\n`;
|
|
4615
|
+
if (this.goalRunnerLastEvalReason) {
|
|
4616
|
+
info += `\n${theme.fg("dim", "Last evaluation:")} ${this.goalRunnerLastEvalReason}\n`;
|
|
4617
|
+
}
|
|
4618
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
4619
|
+
this.chatContainer.addChild(new Markdown(info, 1, 0, this.getMarkdownThemeWithSettings()));
|
|
4620
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
4621
|
+
this.chatContainer.addChild(new Text(theme.fg("dim", ` /goal clear - clear goal`), 1, 0));
|
|
4622
|
+
this.ui.requestRender();
|
|
4623
|
+
return false;
|
|
4624
|
+
}
|
|
4625
|
+
// Handle clear / stop / off / reset / cancel aliases (like Claude Code)
|
|
4626
|
+
const lower = goal.toLowerCase();
|
|
4627
|
+
const clearAliases = new Set(["clear", "stop", "off", "reset", "none", "cancel"]);
|
|
4628
|
+
if (clearAliases.has(lower)) {
|
|
4629
|
+
const prevGoal = this.session.sessionGoal;
|
|
4630
|
+
if (prevGoal) {
|
|
4631
|
+
this.goalRunnerEnabled = false;
|
|
4632
|
+
this.goalRunnerAbortController?.abort();
|
|
4633
|
+
this.goalRunnerTurnCount = 0;
|
|
4634
|
+
this.goalRunnerStartTime = 0;
|
|
4635
|
+
this.goalRunnerTokenSpend = 0;
|
|
4636
|
+
this.goalRunnerLastEvalReason = "";
|
|
4637
|
+
// Clear the goal from session and rebuild system prompt
|
|
4638
|
+
this.session.clearSessionGoal();
|
|
4602
4639
|
this.chatContainer.addChild(new Spacer(1));
|
|
4603
|
-
this.chatContainer.addChild(new Text(theme.fg("dim", `
|
|
4604
|
-
this.chatContainer.addChild(new Text(theme.fg("dim", ` /goal on - enable goal runner`), 1, 0));
|
|
4605
|
-
this.chatContainer.addChild(new Text(theme.fg("dim", ` /goal off - pause goal runner`), 1, 0));
|
|
4640
|
+
this.chatContainer.addChild(new Text(theme.fg("dim", `Goal cleared: "${prevGoal}"`), 1, 0));
|
|
4606
4641
|
}
|
|
4607
4642
|
else {
|
|
4608
|
-
this.showWarning("
|
|
4643
|
+
this.showWarning("No active goal to clear.");
|
|
4609
4644
|
}
|
|
4610
4645
|
this.ui.requestRender();
|
|
4611
|
-
return;
|
|
4646
|
+
return false;
|
|
4612
4647
|
}
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
this.goalRunnerEnabled = lower === "on";
|
|
4616
|
-
if (lower === "on") {
|
|
4617
|
-
this.goalRunnerRecentOutputs = [];
|
|
4618
|
-
}
|
|
4648
|
+
if (lower === "on") {
|
|
4649
|
+
this.goalRunnerEnabled = true;
|
|
4619
4650
|
const currentGoal = this.session.sessionGoal;
|
|
4620
4651
|
if (currentGoal) {
|
|
4621
|
-
const label = lower === "on" ? theme.fg("success", "enabled") : theme.fg("warning", "paused");
|
|
4622
4652
|
this.chatContainer.addChild(new Spacer(1));
|
|
4623
|
-
this.chatContainer.addChild(new Text(theme.fg("
|
|
4653
|
+
this.chatContainer.addChild(new Text(theme.fg("success", `Goal runner enabled for: "${currentGoal}"`), 1, 0));
|
|
4624
4654
|
}
|
|
4625
4655
|
else {
|
|
4626
|
-
this.showWarning("No goal is set. Use /goal <
|
|
4656
|
+
this.showWarning("No goal is set. Use /goal <condition> to set one first.");
|
|
4627
4657
|
}
|
|
4628
4658
|
this.ui.requestRender();
|
|
4629
|
-
return;
|
|
4659
|
+
return false;
|
|
4630
4660
|
}
|
|
4661
|
+
// Set new goal — like Claude Code, the goal text is submitted as the next user message
|
|
4631
4662
|
this.session.setSessionGoal(goal);
|
|
4632
4663
|
this.goalRunnerEnabled = true;
|
|
4633
|
-
this.
|
|
4664
|
+
this.goalRunnerTurnCount = 0;
|
|
4665
|
+
this.goalRunnerStartTime = Date.now();
|
|
4666
|
+
this.goalRunnerTokenSpend = 0;
|
|
4667
|
+
this.goalRunnerLastEvalReason = "";
|
|
4634
4668
|
this.chatContainer.addChild(new Spacer(1));
|
|
4635
|
-
this.chatContainer.addChild(new Text(theme.fg("dim", `Session goal set: ${goal}
|
|
4669
|
+
this.chatContainer.addChild(new Text(theme.fg("dim", `Session goal set: "${goal}" — working toward it...`), 1, 0));
|
|
4636
4670
|
this.ui.requestRender();
|
|
4671
|
+
return true;
|
|
4672
|
+
}
|
|
4673
|
+
formatDuration(ms) {
|
|
4674
|
+
const seconds = Math.floor(ms / 1000);
|
|
4675
|
+
if (seconds < 60)
|
|
4676
|
+
return `${seconds}s`;
|
|
4677
|
+
const minutes = Math.floor(seconds / 60);
|
|
4678
|
+
const secs = seconds % 60;
|
|
4679
|
+
if (minutes < 60)
|
|
4680
|
+
return `${minutes}m ${secs}s`;
|
|
4681
|
+
const hours = Math.floor(minutes / 60);
|
|
4682
|
+
const mins = minutes % 60;
|
|
4683
|
+
return `${hours}h ${mins}m ${secs}s`;
|
|
4637
4684
|
}
|
|
4638
4685
|
/**
|
|
4639
|
-
* Goal runner continuation loop.
|
|
4640
|
-
*
|
|
4641
|
-
*
|
|
4686
|
+
* Goal runner continuation loop — Claude Code-style evaluator loop.
|
|
4687
|
+
*
|
|
4688
|
+
* After each turn:
|
|
4689
|
+
* 1. Runs the evaluator to check if the condition is met
|
|
4690
|
+
* 2. If met: shows achievement and stops
|
|
4691
|
+
* 3. If not met: continues with next turn
|
|
4692
|
+
*
|
|
4693
|
+
* Handles errors with exponential backoff.
|
|
4694
|
+
* User can interrupt with the interrupt key.
|
|
4642
4695
|
* Survives crashes: on next startup, the persisted session goal triggers auto-resume.
|
|
4643
4696
|
*/
|
|
4644
4697
|
async goalRunnerContinuationLoop(goal) {
|
|
4645
|
-
// Abort any previous goal runner controller
|
|
4646
4698
|
this.goalRunnerAbortController?.abort();
|
|
4647
4699
|
this.goalRunnerAbortController = new AbortController();
|
|
4648
4700
|
const signal = this.goalRunnerAbortController.signal;
|
|
4701
|
+
let nextDirection;
|
|
4649
4702
|
try {
|
|
4650
4703
|
while (this.goalRunnerEnabled && this.session.sessionGoal) {
|
|
4651
4704
|
if (signal.aborted)
|
|
4652
4705
|
break;
|
|
4653
|
-
// Small delay between turns so the user can see what happened
|
|
4654
4706
|
try {
|
|
4655
4707
|
await sleep(this.GOAL_RUNNER_TURN_DELAY_MS, signal);
|
|
4656
4708
|
}
|
|
@@ -4659,67 +4711,69 @@ export class InteractiveMode {
|
|
|
4659
4711
|
}
|
|
4660
4712
|
if (signal.aborted)
|
|
4661
4713
|
break;
|
|
4662
|
-
|
|
4663
|
-
if (this.session.isStreaming || this.session.isCompacting) {
|
|
4714
|
+
if (this.session.isStreaming || this.session.isCompacting)
|
|
4664
4715
|
continue;
|
|
4665
|
-
}
|
|
4666
|
-
// Show status on first iteration
|
|
4667
4716
|
if (!this.goalRunnerStatusDisplayed) {
|
|
4668
4717
|
this.goalRunnerStatusDisplayed = true;
|
|
4669
|
-
this.showStatus(`
|
|
4718
|
+
this.showStatus(`Goal: "${goal}" (${keyText("app.interrupt")} to pause, /goal clear to clear)`);
|
|
4670
4719
|
}
|
|
4671
4720
|
try {
|
|
4672
|
-
|
|
4673
|
-
|
|
4721
|
+
// Use evaluator's nextDirection, or fall back to generic prompt
|
|
4722
|
+
const promptText = nextDirection
|
|
4723
|
+
? nextDirection
|
|
4724
|
+
: `Work toward the goal: "${goal}". This goal must be completed. Explore, plan, and execute. Do not stop until it is done.`;
|
|
4725
|
+
nextDirection = undefined;
|
|
4726
|
+
await this.session.prompt(promptText);
|
|
4674
4727
|
this.goalRunnerRetryAttempt = 0;
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
if (!isNearDuplicateGoalOutput(window[i - 1], window[i])) {
|
|
4695
|
-
allDup = false;
|
|
4696
|
-
break;
|
|
4697
|
-
}
|
|
4698
|
-
}
|
|
4699
|
-
if (allDup) {
|
|
4728
|
+
this.goalRunnerTurnCount++;
|
|
4729
|
+
// Track token spend from last assistant message if available
|
|
4730
|
+
const lastMsg = this.session.getLastAssistantMessage();
|
|
4731
|
+
if (lastMsg?.usage) {
|
|
4732
|
+
this.goalRunnerTokenSpend += lastMsg.usage.input + lastMsg.usage.output;
|
|
4733
|
+
}
|
|
4734
|
+
// Run evaluator to check if condition is met
|
|
4735
|
+
const model = this.session.model;
|
|
4736
|
+
if (model) {
|
|
4737
|
+
const auth = await this.session.modelRegistry.getApiKeyAndHeaders(model);
|
|
4738
|
+
if (auth.ok) {
|
|
4739
|
+
const lastText = this.session.getLastAssistantText();
|
|
4740
|
+
const evaluation = await evaluateGoalCondition(goal, lastText, model, {
|
|
4741
|
+
apiKey: auth.apiKey,
|
|
4742
|
+
headers: auth.headers,
|
|
4743
|
+
signal,
|
|
4744
|
+
});
|
|
4745
|
+
this.goalRunnerLastEvalReason = evaluation.reason;
|
|
4746
|
+
if (evaluation.met) {
|
|
4700
4747
|
this.goalRunnerEnabled = false;
|
|
4701
|
-
this.
|
|
4702
|
-
this.
|
|
4748
|
+
const elapsed = this.formatDuration(Date.now() - this.goalRunnerStartTime);
|
|
4749
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
4750
|
+
this.chatContainer.addChild(new Text(theme.fg("success", `Goal achieved! (${this.goalRunnerTurnCount} turns, ${elapsed})`), 1, 0));
|
|
4751
|
+
this.chatContainer.addChild(new Text(theme.fg("dim", ` ${evaluation.reason}`), 1, 0));
|
|
4752
|
+
this.ui.requestRender();
|
|
4703
4753
|
break;
|
|
4704
4754
|
}
|
|
4755
|
+
// Capture next direction from evaluator
|
|
4756
|
+
nextDirection = evaluation.nextDirection;
|
|
4757
|
+
// Update status with evaluator reason
|
|
4758
|
+
this.showStatus(`Goal: "${goal}" — ${evaluation.reason} (${this.goalRunnerTurnCount} turn(s), ${keyText("app.interrupt")} to pause)`);
|
|
4759
|
+
}
|
|
4760
|
+
else {
|
|
4761
|
+
nextDirection = undefined;
|
|
4762
|
+
this.showStatus(`Goal: "${goal}" — continuing (${this.goalRunnerTurnCount} turn(s), ${keyText("app.interrupt")} to pause)`);
|
|
4705
4763
|
}
|
|
4706
4764
|
}
|
|
4765
|
+
else {
|
|
4766
|
+
nextDirection = undefined;
|
|
4767
|
+
this.showStatus(`Goal: "${goal}" — continuing (${this.goalRunnerTurnCount} turn(s), ${keyText("app.interrupt")} to pause)`);
|
|
4768
|
+
}
|
|
4707
4769
|
}
|
|
4708
4770
|
catch (error) {
|
|
4771
|
+
// Error occurred — retry indefinitely, never stop
|
|
4709
4772
|
this.goalRunnerRetryAttempt++;
|
|
4710
4773
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
4711
|
-
|
|
4712
|
-
this.goalRunnerEnabled = false;
|
|
4713
|
-
this.showError(`Goal runner paused after ${this.GOAL_RUNNER_MAX_RETRIES} consecutive errors. ` +
|
|
4714
|
-
`Last error: ${errorMessage}. Use /goal on to resume.`);
|
|
4715
|
-
break;
|
|
4716
|
-
}
|
|
4717
|
-
// Exponential backoff
|
|
4718
|
-
const delay = this.GOAL_RUNNER_BASE_DELAY_MS * 2 ** (this.goalRunnerRetryAttempt - 1);
|
|
4719
|
-
this.showStatus(`Goal runner error, retrying in ${Math.ceil(delay / 1000)}s ` +
|
|
4720
|
-
`(${this.goalRunnerRetryAttempt}/${this.GOAL_RUNNER_MAX_RETRIES})... (${keyText("app.interrupt")} to pause)`);
|
|
4774
|
+
this.showStatus(`Goal runner error (attempt ${this.goalRunnerRetryAttempt}), retrying in ${Math.ceil(this.GOAL_RUNNER_RETRY_DELAY_MS / 1000)}s: ${errorMessage} — (${keyText("app.interrupt")} to pause)`);
|
|
4721
4775
|
try {
|
|
4722
|
-
await sleep(
|
|
4776
|
+
await sleep(this.GOAL_RUNNER_RETRY_DELAY_MS, signal);
|
|
4723
4777
|
}
|
|
4724
4778
|
catch {
|
|
4725
4779
|
break;
|