@openeryc/pi-coding-agent 0.75.45 → 0.75.46
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 +7 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +6 -2
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/http-dispatcher.d.ts +2 -0
- package/dist/core/http-dispatcher.d.ts.map +1 -1
- package/dist/core/http-dispatcher.js +10 -1
- package/dist/core/http-dispatcher.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +3 -3
- package/dist/core/sdk.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +8 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +121 -1
- 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
|
@@ -30,6 +30,7 @@ import { parseGitUrl } from "../../utils/git.js";
|
|
|
30
30
|
import { getCwdRelativePath } from "../../utils/paths.js";
|
|
31
31
|
import { getPiUserAgent } from "../../utils/pi-user-agent.js";
|
|
32
32
|
import { killTrackedDetachedChildren } from "../../utils/shell.js";
|
|
33
|
+
import { sleep } from "../../utils/sleep.js";
|
|
33
34
|
import { ensureTool } from "../../utils/tools-manager.js";
|
|
34
35
|
import { checkForNewPiVersion } from "../../utils/version-check.js";
|
|
35
36
|
import { ArminComponent } from "./components/armin.js";
|
|
@@ -172,6 +173,15 @@ export class InteractiveMode {
|
|
|
172
173
|
compactionQueuedMessages = [];
|
|
173
174
|
// Shutdown state
|
|
174
175
|
shutdownRequested = false;
|
|
176
|
+
// Goal runner state: auto-continues working on a session goal across turns,
|
|
177
|
+
// retries on errors, and survives process restarts via session persistence.
|
|
178
|
+
goalRunnerEnabled = true;
|
|
179
|
+
goalRunnerAbortController = undefined;
|
|
180
|
+
goalRunnerRetryAttempt = 0;
|
|
181
|
+
goalRunnerStatusDisplayed = false;
|
|
182
|
+
GOAL_RUNNER_MAX_RETRIES = 10;
|
|
183
|
+
GOAL_RUNNER_BASE_DELAY_MS = 3000;
|
|
184
|
+
GOAL_RUNNER_TURN_DELAY_MS = 800;
|
|
175
185
|
// Extension UI state
|
|
176
186
|
extensionSelector = undefined;
|
|
177
187
|
extensionInput = undefined;
|
|
@@ -507,6 +517,16 @@ export class InteractiveMode {
|
|
|
507
517
|
*/
|
|
508
518
|
async run() {
|
|
509
519
|
await this.init();
|
|
520
|
+
// If resuming a session that has a goal, auto-continue from where it left off.
|
|
521
|
+
// This handles crash recovery and manual resume scenarios.
|
|
522
|
+
const resumedGoal = this.session.sessionGoal;
|
|
523
|
+
if (resumedGoal && this.session.state.messages.length > 0) {
|
|
524
|
+
this.goalRunnerEnabled = true;
|
|
525
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
526
|
+
this.chatContainer.addChild(new Text(theme.fg("dim", `Resuming goal: "${resumedGoal}" (${keyText("app.interrupt")} to pause)`), 1, 0));
|
|
527
|
+
this.ui.requestRender();
|
|
528
|
+
await this.goalRunnerContinuationLoop(resumedGoal);
|
|
529
|
+
}
|
|
510
530
|
// Start version check asynchronously
|
|
511
531
|
checkForNewPiVersion(this.version).then((newRelease) => {
|
|
512
532
|
if (newRelease) {
|
|
@@ -562,6 +582,7 @@ export class InteractiveMode {
|
|
|
562
582
|
// Main interactive loop
|
|
563
583
|
while (true) {
|
|
564
584
|
const userInput = await this.getUserInput();
|
|
585
|
+
this.goalRunnerEnabled = true; // Re-enable on any user input
|
|
565
586
|
try {
|
|
566
587
|
await this.session.prompt(userInput);
|
|
567
588
|
}
|
|
@@ -569,6 +590,13 @@ export class InteractiveMode {
|
|
|
569
590
|
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
570
591
|
this.showError(errorMessage);
|
|
571
592
|
}
|
|
593
|
+
// After the prompt completes (or errors), check if goal runner should auto-continue.
|
|
594
|
+
if (this.goalRunnerEnabled) {
|
|
595
|
+
const goal = this.session.sessionGoal;
|
|
596
|
+
if (goal) {
|
|
597
|
+
await this.goalRunnerContinuationLoop(goal);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
572
600
|
}
|
|
573
601
|
}
|
|
574
602
|
async checkForPackageUpdates() {
|
|
@@ -1884,6 +1912,14 @@ export class InteractiveMode {
|
|
|
1884
1912
|
if (this.session.isStreaming) {
|
|
1885
1913
|
this.restoreQueuedMessagesToEditor({ abort: true });
|
|
1886
1914
|
}
|
|
1915
|
+
else if (this.goalRunnerAbortController) {
|
|
1916
|
+
// Pause goal runner without clearing the goal
|
|
1917
|
+
this.goalRunnerEnabled = false;
|
|
1918
|
+
this.goalRunnerAbortController.abort();
|
|
1919
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
1920
|
+
this.chatContainer.addChild(new Text(theme.fg("dim", "Goal runner paused. Use /goal on to resume."), 1, 0));
|
|
1921
|
+
this.ui.requestRender();
|
|
1922
|
+
}
|
|
1887
1923
|
else if (this.session.isBashRunning) {
|
|
1888
1924
|
this.session.abortBash();
|
|
1889
1925
|
}
|
|
@@ -4365,9 +4401,12 @@ export class InteractiveMode {
|
|
|
4365
4401
|
const goal = text.replace(/^\/goal\s*/, "").trim();
|
|
4366
4402
|
if (!goal) {
|
|
4367
4403
|
const currentGoal = this.session.sessionGoal;
|
|
4404
|
+
const runnerStatus = this.goalRunnerEnabled ? "enabled" : "paused";
|
|
4368
4405
|
if (currentGoal) {
|
|
4369
4406
|
this.chatContainer.addChild(new Spacer(1));
|
|
4370
|
-
this.chatContainer.addChild(new Text(theme.fg("dim", `Session goal: ${currentGoal}`), 1, 0));
|
|
4407
|
+
this.chatContainer.addChild(new Text(theme.fg("dim", `Session goal: ${currentGoal} (goal runner: ${runnerStatus})`), 1, 0));
|
|
4408
|
+
this.chatContainer.addChild(new Text(theme.fg("dim", ` /goal on - enable goal runner`), 1, 0));
|
|
4409
|
+
this.chatContainer.addChild(new Text(theme.fg("dim", ` /goal off - pause goal runner`), 1, 0));
|
|
4371
4410
|
}
|
|
4372
4411
|
else {
|
|
4373
4412
|
this.showWarning("Usage: /goal <goal description>");
|
|
@@ -4375,11 +4414,92 @@ export class InteractiveMode {
|
|
|
4375
4414
|
this.ui.requestRender();
|
|
4376
4415
|
return;
|
|
4377
4416
|
}
|
|
4417
|
+
const lower = goal.toLowerCase();
|
|
4418
|
+
if (lower === "on" || lower === "off") {
|
|
4419
|
+
this.goalRunnerEnabled = lower === "on";
|
|
4420
|
+
const currentGoal = this.session.sessionGoal;
|
|
4421
|
+
if (currentGoal) {
|
|
4422
|
+
const label = lower === "on" ? theme.fg("success", "enabled") : theme.fg("warning", "paused");
|
|
4423
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
4424
|
+
this.chatContainer.addChild(new Text(theme.fg("dim", `Goal runner ${label} for goal: "${currentGoal}"`), 1, 0));
|
|
4425
|
+
}
|
|
4426
|
+
else {
|
|
4427
|
+
this.showWarning("No goal is set. Use /goal <description> to set one first.");
|
|
4428
|
+
}
|
|
4429
|
+
this.ui.requestRender();
|
|
4430
|
+
return;
|
|
4431
|
+
}
|
|
4378
4432
|
this.session.setSessionGoal(goal);
|
|
4433
|
+
this.goalRunnerEnabled = true;
|
|
4379
4434
|
this.chatContainer.addChild(new Spacer(1));
|
|
4380
4435
|
this.chatContainer.addChild(new Text(theme.fg("dim", `Session goal set: ${goal}`), 1, 0));
|
|
4381
4436
|
this.ui.requestRender();
|
|
4382
4437
|
}
|
|
4438
|
+
/**
|
|
4439
|
+
* Goal runner continuation loop.
|
|
4440
|
+
* After each turn completes, auto-prompts the agent to continue pursuing the goal.
|
|
4441
|
+
* Handles errors with exponential backoff and respects user interruption.
|
|
4442
|
+
* Survives crashes: on next startup, the persisted session goal triggers auto-resume.
|
|
4443
|
+
*/
|
|
4444
|
+
async goalRunnerContinuationLoop(goal) {
|
|
4445
|
+
// Abort any previous goal runner controller
|
|
4446
|
+
this.goalRunnerAbortController?.abort();
|
|
4447
|
+
this.goalRunnerAbortController = new AbortController();
|
|
4448
|
+
const signal = this.goalRunnerAbortController.signal;
|
|
4449
|
+
try {
|
|
4450
|
+
while (this.goalRunnerEnabled && this.session.sessionGoal) {
|
|
4451
|
+
if (signal.aborted)
|
|
4452
|
+
break;
|
|
4453
|
+
// Small delay between turns so the user can see what happened
|
|
4454
|
+
try {
|
|
4455
|
+
await sleep(this.GOAL_RUNNER_TURN_DELAY_MS, signal);
|
|
4456
|
+
}
|
|
4457
|
+
catch {
|
|
4458
|
+
break;
|
|
4459
|
+
}
|
|
4460
|
+
if (signal.aborted)
|
|
4461
|
+
break;
|
|
4462
|
+
// Skip if the agent is already streaming or compacting
|
|
4463
|
+
if (this.session.isStreaming || this.session.isCompacting) {
|
|
4464
|
+
continue;
|
|
4465
|
+
}
|
|
4466
|
+
// Show status on first iteration
|
|
4467
|
+
if (!this.goalRunnerStatusDisplayed) {
|
|
4468
|
+
this.goalRunnerStatusDisplayed = true;
|
|
4469
|
+
this.showStatus(`Pursuing goal: "${goal}" (${keyText("app.interrupt")} to pause)`);
|
|
4470
|
+
}
|
|
4471
|
+
try {
|
|
4472
|
+
await this.session.prompt(`Continue pursuing the goal: "${goal}". If you have fully completed this goal, report what was accomplished. If you cannot make further progress, explain what blocked you.`);
|
|
4473
|
+
// Successful turn completed, reset retry counter
|
|
4474
|
+
this.goalRunnerRetryAttempt = 0;
|
|
4475
|
+
}
|
|
4476
|
+
catch (error) {
|
|
4477
|
+
this.goalRunnerRetryAttempt++;
|
|
4478
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
4479
|
+
if (this.goalRunnerRetryAttempt >= this.GOAL_RUNNER_MAX_RETRIES) {
|
|
4480
|
+
this.goalRunnerEnabled = false;
|
|
4481
|
+
this.showError(`Goal runner paused after ${this.GOAL_RUNNER_MAX_RETRIES} consecutive errors. ` +
|
|
4482
|
+
`Last error: ${errorMessage}. Use /goal on to resume.`);
|
|
4483
|
+
break;
|
|
4484
|
+
}
|
|
4485
|
+
// Exponential backoff
|
|
4486
|
+
const delay = this.GOAL_RUNNER_BASE_DELAY_MS * 2 ** (this.goalRunnerRetryAttempt - 1);
|
|
4487
|
+
this.showStatus(`Goal runner error, retrying in ${Math.ceil(delay / 1000)}s ` +
|
|
4488
|
+
`(${this.goalRunnerRetryAttempt}/${this.GOAL_RUNNER_MAX_RETRIES})... (${keyText("app.interrupt")} to pause)`);
|
|
4489
|
+
try {
|
|
4490
|
+
await sleep(delay, signal);
|
|
4491
|
+
}
|
|
4492
|
+
catch {
|
|
4493
|
+
break;
|
|
4494
|
+
}
|
|
4495
|
+
}
|
|
4496
|
+
}
|
|
4497
|
+
}
|
|
4498
|
+
finally {
|
|
4499
|
+
this.goalRunnerAbortController = undefined;
|
|
4500
|
+
this.goalRunnerStatusDisplayed = false;
|
|
4501
|
+
}
|
|
4502
|
+
}
|
|
4383
4503
|
handleSessionCommand() {
|
|
4384
4504
|
const stats = this.session.getSessionStats();
|
|
4385
4505
|
const sessionName = this.sessionManager.getSessionName();
|