pi-invisible-continue 0.2.1 → 0.2.3

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.
Files changed (2) hide show
  1. package/continue.ts +44 -11
  2. package/package.json +1 -1
package/continue.ts CHANGED
@@ -41,9 +41,24 @@ Agent.prototype.subscribe = function (this: Agent, ...args: any[]) {
41
41
  // producing "Agent is already processing".
42
42
  let _continueInProgress = false;
43
43
 
44
+ // Timestamp of the last completed invisible continue.
45
+ // Used to avoid double continuation when continue() unblocks after
46
+ // triggerInvisibleContinue just ran.
47
+ let _lastInvisibleContinueTime = 0;
48
+
44
49
  // Monkey-patch continue() so the session's built-in loop cooperates with
45
- // our mutex. Without this, the session's continue() could race our
46
- // prompt([]) call and throw "Agent is already processing".
50
+ // our mutex AND can convert the "Cannot continue from assistant" error
51
+ // into a prompt([]) call when the agent was mid-task.
52
+ // Without this, the session's continue() would throw when the last message
53
+ // is an assistant (common after compaction), and the agent loop would die
54
+ // leaving mid-task work unfinished.
55
+ //
56
+ // When continue() throws "Cannot continue from message role: assistant":
57
+ // - stopReason "stop" → agent finished cleanly, don't continue
58
+ // - stopReason "aborted" → user cancelled, don't continue
59
+ // - stopReason "error" → pi-retry handles errors, don't race it
60
+ // - stopReason "toolUse" or "length" → mid-task, fall back to prompt([])
61
+ //
47
62
  // Chains the previous patch (pi-retry, pi-vcc) so all mutexes are respected.
48
63
  const _origContinue = Agent.prototype.continue;
49
64
  Agent.prototype.continue = function (this: Agent) {
@@ -56,15 +71,31 @@ Agent.prototype.continue = function (this: Agent) {
56
71
  await _origContinue.call(self);
57
72
  } catch (e: any) {
58
73
  const msg = e?.message ?? '';
59
- // After an invisible continue finishes, the transcript ends with a
60
- // fresh assistant message. The session's continue() sees this and
61
- // would throw. Catch and swallow the while-loop will poll
62
- // _handlePostAgentRun() again, find no error, and exit cleanly.
63
- if (
64
- msg.includes('Cannot continue from message role') ||
65
- msg.includes('Cannot continue from an assistant message') ||
66
- msg.includes('Agent is already processing')
67
- ) {
74
+ if (msg.includes('Cannot continue from message role') ||
75
+ msg.includes('Cannot continue from an assistant message')) {
76
+ // Check stopReason only continue if the agent was mid-task
77
+ const lastMsg = self.state.messages[self.state.messages.length - 1];
78
+ if (lastMsg?.role === 'assistant' &&
79
+ lastMsg.stopReason !== 'stop' &&
80
+ lastMsg.stopReason !== 'aborted' &&
81
+ lastMsg.stopReason !== 'error') {
82
+ // Agent was mid-task — fall back to prompt([])
83
+ // Guard: if an invisible continue just completed, don't double-run
84
+ if (!_continueInProgress && Date.now() - _lastInvisibleContinueTime > 500) {
85
+ _continueInProgress = true;
86
+ try {
87
+ await self.prompt([]);
88
+ } catch {
89
+ // Agent already processing or other transient error
90
+ } finally {
91
+ _continueInProgress = false;
92
+ }
93
+ }
94
+ }
95
+ // For stop/aborted/error: return void, the session loop exits naturally
96
+ return;
97
+ }
98
+ if (msg.includes('Agent is already processing')) {
68
99
  return;
69
100
  }
70
101
  throw e;
@@ -82,6 +113,7 @@ export default function (pi: ExtensionAPI) {
82
113
 
83
114
  pi.on("session_start", () => {
84
115
  _continueInProgress = false;
116
+ _lastInvisibleContinueTime = 0;
85
117
  });
86
118
  }
87
119
 
@@ -148,5 +180,6 @@ async function runContinueCommand(
148
180
  }
149
181
  } finally {
150
182
  _continueInProgress = false;
183
+ _lastInvisibleContinueTime = Date.now();
151
184
  }
152
185
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-invisible-continue",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Invisible session continuation for pi — resume the agentic loop without sending ANY prompt the LLM can see",
5
5
  "type": "module",
6
6
  "author": "Tom X Nguyen",