@code-yeongyu/senpi 2026.5.24 → 2026.5.29-2

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 (118) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/cli/args.d.ts +0 -6
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +1 -2
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/config.js +15 -2
  8. package/dist/config.js.map +1 -1
  9. package/dist/core/agent-session.d.ts +4 -0
  10. package/dist/core/agent-session.d.ts.map +1 -1
  11. package/dist/core/agent-session.js +116 -80
  12. package/dist/core/agent-session.js.map +1 -1
  13. package/dist/core/compaction/compaction.d.ts.map +1 -1
  14. package/dist/core/compaction/compaction.js +18 -24
  15. package/dist/core/compaction/compaction.js.map +1 -1
  16. package/dist/core/extensions/builtin/gpt-apply-patch/streaming-render.d.ts.map +1 -1
  17. package/dist/core/extensions/builtin/gpt-apply-patch/streaming-render.js +4 -2
  18. package/dist/core/extensions/builtin/gpt-apply-patch/streaming-render.js.map +1 -1
  19. package/dist/core/extensions/builtin/session-observer/overlay.d.ts.map +1 -1
  20. package/dist/core/extensions/builtin/session-observer/overlay.js +0 -5
  21. package/dist/core/extensions/builtin/session-observer/overlay.js.map +1 -1
  22. package/dist/core/extensions/builtin/session-observer/scanner.d.ts.map +1 -1
  23. package/dist/core/extensions/builtin/session-observer/scanner.js +2 -0
  24. package/dist/core/extensions/builtin/session-observer/scanner.js.map +1 -1
  25. package/dist/core/extensions/loader.d.ts.map +1 -1
  26. package/dist/core/extensions/loader.js +21 -9
  27. package/dist/core/extensions/loader.js.map +1 -1
  28. package/dist/core/output-guard.d.ts +1 -0
  29. package/dist/core/output-guard.d.ts.map +1 -1
  30. package/dist/core/output-guard.js +52 -22
  31. package/dist/core/output-guard.js.map +1 -1
  32. package/dist/core/session-work-barrier.d.ts +9 -0
  33. package/dist/core/session-work-barrier.d.ts.map +1 -0
  34. package/dist/core/session-work-barrier.js +50 -0
  35. package/dist/core/session-work-barrier.js.map +1 -0
  36. package/dist/main.d.ts.map +1 -1
  37. package/dist/main.js +0 -15
  38. package/dist/main.js.map +1 -1
  39. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  40. package/dist/modes/interactive/components/user-message.js +1 -1
  41. package/dist/modes/interactive/components/user-message.js.map +1 -1
  42. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  43. package/dist/modes/interactive/interactive-mode.js +6 -0
  44. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  45. package/dist/modes/rpc/rpc-client.d.ts +3 -0
  46. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  47. package/dist/modes/rpc/rpc-client.js +64 -7
  48. package/dist/modes/rpc/rpc-client.js.map +1 -1
  49. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  50. package/dist/modes/rpc/rpc-mode.js +15 -3
  51. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  52. package/docs/settings.md +3 -1
  53. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.d.ts.map +1 -1
  54. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js +18 -24
  55. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js.map +1 -1
  56. package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
  57. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +249 -39
  58. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
  59. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +349 -144
  60. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js.map +1 -1
  61. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  62. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js +1 -1
  63. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js.map +1 -1
  64. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
  65. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js +1 -1
  66. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
  67. package/node_modules/@earendil-works/pi-ai/dist/providers/images/openrouter.d.ts.map +1 -1
  68. package/node_modules/@earendil-works/pi-ai/dist/providers/images/openrouter.js +1 -1
  69. package/node_modules/@earendil-works/pi-ai/dist/providers/images/openrouter.js.map +1 -1
  70. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
  71. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.js +46 -29
  72. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
  73. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  74. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js +1 -1
  75. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js.map +1 -1
  76. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  77. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js +1 -1
  78. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js.map +1 -1
  79. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts +2 -1
  80. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts.map +1 -1
  81. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js +5 -2
  82. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js.map +1 -1
  83. package/node_modules/@earendil-works/pi-ai/package.json +1 -1
  84. package/node_modules/@earendil-works/pi-tui/dist/autocomplete.d.ts.map +1 -1
  85. package/node_modules/@earendil-works/pi-tui/dist/autocomplete.js +2 -17
  86. package/node_modules/@earendil-works/pi-tui/dist/autocomplete.js.map +1 -1
  87. package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +1 -1
  88. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +40 -55
  89. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +1 -1
  90. package/node_modules/@earendil-works/pi-tui/dist/components/input.d.ts.map +1 -1
  91. package/node_modules/@earendil-works/pi-tui/dist/components/input.js +2 -2
  92. package/node_modules/@earendil-works/pi-tui/dist/components/input.js.map +1 -1
  93. package/node_modules/@earendil-works/pi-tui/dist/components/markdown.d.ts +7 -1
  94. package/node_modules/@earendil-works/pi-tui/dist/components/markdown.d.ts.map +1 -1
  95. package/node_modules/@earendil-works/pi-tui/dist/components/markdown.js +12 -2
  96. package/node_modules/@earendil-works/pi-tui/dist/components/markdown.js.map +1 -1
  97. package/node_modules/@earendil-works/pi-tui/dist/index.d.ts +1 -1
  98. package/node_modules/@earendil-works/pi-tui/dist/index.d.ts.map +1 -1
  99. package/node_modules/@earendil-works/pi-tui/dist/index.js.map +1 -1
  100. package/node_modules/@earendil-works/pi-tui/dist/slash-command-autocomplete.d.ts +3 -0
  101. package/node_modules/@earendil-works/pi-tui/dist/slash-command-autocomplete.d.ts.map +1 -0
  102. package/node_modules/@earendil-works/pi-tui/dist/slash-command-autocomplete.js +38 -0
  103. package/node_modules/@earendil-works/pi-tui/dist/slash-command-autocomplete.js.map +1 -0
  104. package/node_modules/@earendil-works/pi-tui/dist/terminal-image.d.ts.map +1 -1
  105. package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js +4 -1
  106. package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js.map +1 -1
  107. package/node_modules/@earendil-works/pi-tui/dist/utils.d.ts +5 -1
  108. package/node_modules/@earendil-works/pi-tui/dist/utils.d.ts.map +1 -1
  109. package/node_modules/@earendil-works/pi-tui/dist/utils.js +66 -14
  110. package/node_modules/@earendil-works/pi-tui/dist/utils.js.map +1 -1
  111. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  112. package/npm-shrinkwrap.json +13 -13
  113. package/package.json +6 -7
  114. package/dist/modes/neo-mode.d.ts +0 -43
  115. package/dist/modes/neo-mode.d.ts.map +0 -1
  116. package/dist/modes/neo-mode.js +0 -142
  117. package/dist/modes/neo-mode.js.map +0 -1
  118. package/dist/neo-tui-bin/senpi-neo-tui-linux-x64 +0 -0
@@ -33,6 +33,7 @@ import { filterContextExcludedMessages } from "./messages.js";
33
33
  import { getModelNarrowingPatterns, resolveModelScope } from "./model-resolver.js";
34
34
  import { expandPromptTemplate } from "./prompt-templates.js";
35
35
  import { buildSessionContext, CURRENT_SESSION_VERSION, getLatestCompactionEntry, } from "./session-manager.js";
36
+ import { SessionWorkBarrier } from "./session-work-barrier.js";
36
37
  import { createSyntheticSourceInfo } from "./source-info.js";
37
38
  import { getSupportedThinkingLevels, supportsMax, supportsXhigh } from "./thinking-levels.js";
38
39
  import { createLocalBashOperations } from "./tools/bash.js";
@@ -88,6 +89,7 @@ export class AgentSession {
88
89
  // Compaction state
89
90
  _compactionAbortController = undefined;
90
91
  _autoCompactionAbortController = undefined;
92
+ _sessionWorkBarrier = new SessionWorkBarrier();
91
93
  _overflowRecoveryAttempted = false;
92
94
  _messageRevision = 0;
93
95
  // Branch summarization state
@@ -275,6 +277,9 @@ export class AgentSession {
275
277
  getMessageRevision() {
276
278
  return this._messageRevision;
277
279
  }
280
+ async _waitForSettledSessionWork() {
281
+ await this._sessionWorkBarrier.waitForSettled(() => this._agentEventQueue);
282
+ }
278
283
  _modelSelectionChangesContext(previousModel, nextModel) {
279
284
  if (!modelsAreEqual(previousModel, nextModel))
280
285
  return true;
@@ -807,6 +812,11 @@ export class AgentSession {
807
812
  if (userAbortPromise) {
808
813
  await userAbortPromise;
809
814
  }
815
+ const shouldWaitForSessionWork = options?.source !== "extension";
816
+ if (shouldWaitForSessionWork &&
817
+ (!this.isStreaming || this.isCompacting || this._sessionWorkBarrier.hasActiveWork)) {
818
+ await this._waitForSettledSessionWork();
819
+ }
810
820
  const expandPromptTemplates = options?.expandPromptTemplates ?? true;
811
821
  const preflightResult = options?.preflightResult;
812
822
  let messages;
@@ -926,7 +936,12 @@ export class AgentSession {
926
936
  preflightResult?.(true);
927
937
  await this.agent.prompt(messages);
928
938
  await this.waitForRetry();
929
- await this._agentEventQueue;
939
+ if (shouldWaitForSessionWork) {
940
+ await this._waitForSettledSessionWork();
941
+ }
942
+ else {
943
+ await this.agent.waitForIdle();
944
+ }
930
945
  }
931
946
  /**
932
947
  * Try to execute an extension command. Returns true if command was found and executed.
@@ -1532,83 +1547,89 @@ export class AgentSession {
1532
1547
  this._compactionAbortController = undefined;
1533
1548
  }
1534
1549
  async _executeCompaction(request) {
1535
- if (!this.model) {
1536
- throw new Error(formatNoModelSelectedMessage());
1537
- }
1538
- const requestId = randomUUID();
1539
- const pathEntries = this.sessionManager.getBranch();
1540
- const settings = this.settingsManager.getCompactionSettings();
1541
- const signal = this._compactionAbortController?.signal ?? this._autoCompactionAbortController?.signal;
1542
- if (!signal) {
1543
- throw new Error("Compaction abort controller unavailable");
1544
- }
1545
- let compactionResult = request.precomputed;
1546
- let fromExtension = request.precomputed !== undefined;
1547
- if (!compactionResult) {
1548
- const preparation = prepareCompaction(pathEntries, settings);
1549
- if (!preparation) {
1550
- const lastEntry = pathEntries[pathEntries.length - 1];
1551
- if (lastEntry?.type === "compaction") {
1552
- throw new Error("Already compacted");
1550
+ const finishCompactionWork = this._sessionWorkBarrier.begin();
1551
+ try {
1552
+ if (!this.model) {
1553
+ throw new Error(formatNoModelSelectedMessage());
1554
+ }
1555
+ const requestId = randomUUID();
1556
+ const pathEntries = this.sessionManager.getBranch();
1557
+ const settings = this.settingsManager.getCompactionSettings();
1558
+ const signal = this._compactionAbortController?.signal ?? this._autoCompactionAbortController?.signal;
1559
+ if (!signal) {
1560
+ throw new Error("Compaction abort controller unavailable");
1561
+ }
1562
+ let compactionResult = request.precomputed;
1563
+ let fromExtension = request.precomputed !== undefined;
1564
+ if (!compactionResult) {
1565
+ const preparation = prepareCompaction(pathEntries, settings);
1566
+ if (!preparation) {
1567
+ const lastEntry = pathEntries[pathEntries.length - 1];
1568
+ if (lastEntry?.type === "compaction") {
1569
+ throw new Error("Already compacted");
1570
+ }
1571
+ throw new Error("Nothing to compact (session too small)");
1553
1572
  }
1554
- throw new Error("Nothing to compact (session too small)");
1555
- }
1556
- if (this._extensionRunner.hasHandlers("session_before_compact")) {
1557
- const extensionResult = (await this._extensionRunner.emit({
1558
- type: "session_before_compact",
1559
- reason: request.reason,
1560
- willRetry: request.willRetry,
1561
- requestId,
1562
- preparation,
1563
- branchEntries: pathEntries,
1564
- customInstructions: request.customInstructions,
1565
- signal,
1566
- }));
1567
- if (extensionResult?.cancel) {
1568
- return await this._rejectCompaction(request, requestId, "cancelled-by-extension", true);
1573
+ if (this._extensionRunner.hasHandlers("session_before_compact")) {
1574
+ const extensionResult = (await this._extensionRunner.emit({
1575
+ type: "session_before_compact",
1576
+ reason: request.reason,
1577
+ willRetry: request.willRetry,
1578
+ requestId,
1579
+ preparation,
1580
+ branchEntries: pathEntries,
1581
+ customInstructions: request.customInstructions,
1582
+ signal,
1583
+ }));
1584
+ if (extensionResult?.cancel) {
1585
+ return await this._rejectCompaction(request, requestId, "cancelled-by-extension", true);
1586
+ }
1587
+ if (extensionResult?.compaction) {
1588
+ compactionResult = extensionResult.compaction;
1589
+ fromExtension = true;
1590
+ }
1569
1591
  }
1570
- if (extensionResult?.compaction) {
1571
- compactionResult = extensionResult.compaction;
1572
- fromExtension = true;
1592
+ if (!compactionResult) {
1593
+ const { apiKey, headers, extraBody } = await this._getCompactionRequestAuth(this.model);
1594
+ compactionResult = await compact(preparation, this.model, apiKey, headers, request.customInstructions, signal, extraBody, this.thinkingLevel, this.agent.streamFn);
1573
1595
  }
1574
1596
  }
1575
- if (!compactionResult) {
1576
- const { apiKey, headers, extraBody } = await this._getCompactionRequestAuth(this.model);
1577
- compactionResult = await compact(preparation, this.model, apiKey, headers, request.customInstructions, signal, extraBody, this.thinkingLevel, this.agent.streamFn);
1597
+ if (signal.aborted) {
1598
+ throw new Error("Compaction cancelled");
1578
1599
  }
1600
+ if (this._wouldCompactionOverflow(pathEntries, compactionResult, fromExtension)) {
1601
+ return await this._rejectCompaction(request, requestId, "would-overflow", false);
1602
+ }
1603
+ const compactionEntryId = this.sessionManager.appendCompaction(compactionResult.summary, compactionResult.firstKeptEntryId, compactionResult.tokensBefore, compactionResult.details, fromExtension);
1604
+ const savedEntry = this.sessionManager.getEntry(compactionEntryId);
1605
+ if (!savedEntry || savedEntry.type !== "compaction") {
1606
+ throw new Error("Compaction entry was not saved");
1607
+ }
1608
+ const sessionContext = this.sessionManager.buildSessionContext();
1609
+ this.agent.state.messages = sessionContext.messages;
1610
+ this._incrementMessageRevision();
1611
+ await this._extensionRunner.emit({
1612
+ type: "session_compact",
1613
+ reason: request.reason,
1614
+ requestId,
1615
+ accepted: true,
1616
+ compactionEntry: savedEntry,
1617
+ fromExtension,
1618
+ });
1619
+ this._emit({
1620
+ type: "compaction_end",
1621
+ reason: request.reason,
1622
+ result: compactionResult,
1623
+ aborted: false,
1624
+ willRetry: request.willRetry,
1625
+ requestId,
1626
+ accepted: true,
1627
+ });
1628
+ return { accepted: true, requestId, result: compactionResult, compactionEntry: savedEntry, fromExtension };
1579
1629
  }
1580
- if (signal.aborted) {
1581
- throw new Error("Compaction cancelled");
1582
- }
1583
- if (this._wouldCompactionOverflow(pathEntries, compactionResult, fromExtension)) {
1584
- return await this._rejectCompaction(request, requestId, "would-overflow", false);
1585
- }
1586
- const compactionEntryId = this.sessionManager.appendCompaction(compactionResult.summary, compactionResult.firstKeptEntryId, compactionResult.tokensBefore, compactionResult.details, fromExtension);
1587
- const savedEntry = this.sessionManager.getEntry(compactionEntryId);
1588
- if (!savedEntry || savedEntry.type !== "compaction") {
1589
- throw new Error("Compaction entry was not saved");
1630
+ finally {
1631
+ finishCompactionWork();
1590
1632
  }
1591
- const sessionContext = this.sessionManager.buildSessionContext();
1592
- this.agent.state.messages = sessionContext.messages;
1593
- this._incrementMessageRevision();
1594
- await this._extensionRunner.emit({
1595
- type: "session_compact",
1596
- reason: request.reason,
1597
- requestId,
1598
- accepted: true,
1599
- compactionEntry: savedEntry,
1600
- fromExtension,
1601
- });
1602
- this._emit({
1603
- type: "compaction_end",
1604
- reason: request.reason,
1605
- result: compactionResult,
1606
- aborted: false,
1607
- willRetry: request.willRetry,
1608
- requestId,
1609
- accepted: true,
1610
- });
1611
- return { accepted: true, requestId, result: compactionResult, compactionEntry: savedEntry, fromExtension };
1612
1633
  }
1613
1634
  _wouldCompactionOverflow(pathEntries, compactionResult, fromExtension) {
1614
1635
  const currentLeaf = pathEntries[pathEntries.length - 1];
@@ -1792,10 +1813,24 @@ export class AgentSession {
1792
1813
  this._compactionAbortController = undefined;
1793
1814
  }
1794
1815
  }
1816
+ async _continueAgentAfterCurrentRun() {
1817
+ await this.agent.waitForIdle();
1818
+ try {
1819
+ await this.agent.continue();
1820
+ return true;
1821
+ }
1822
+ catch (error) {
1823
+ if (error instanceof Error) {
1824
+ return false;
1825
+ }
1826
+ throw error;
1827
+ }
1828
+ }
1795
1829
  /**
1796
1830
  * Internal: Run auto-compaction with events.
1797
1831
  */
1798
1832
  async _runAutoCompaction(reason, willRetry) {
1833
+ const finishCompactionWork = this._sessionWorkBarrier.begin();
1799
1834
  this._emit({ type: "compaction_start", reason });
1800
1835
  this._autoCompactionAbortController = new AbortController();
1801
1836
  try {
@@ -1850,17 +1885,12 @@ export class AgentSession {
1850
1885
  this.agent.state.messages = messages.slice(0, -1);
1851
1886
  this._incrementMessageRevision();
1852
1887
  }
1853
- setTimeout(() => {
1854
- this.agent.continue().catch(() => { });
1855
- }, 100);
1856
- return true;
1888
+ return await this._continueAgentAfterCurrentRun();
1889
+ }
1890
+ else if (this.pendingMessageCount > 0) {
1891
+ return await this._continueAgentAfterCurrentRun();
1857
1892
  }
1858
1893
  else if (this.agent.hasQueuedMessages()) {
1859
- // Auto-compaction can complete while follow-up/steering/custom messages are waiting.
1860
- // Kick the loop so queued messages are actually delivered.
1861
- setTimeout(() => {
1862
- this.agent.continue().catch(() => { });
1863
- }, 100);
1864
1894
  return true;
1865
1895
  }
1866
1896
  return false;
@@ -1886,6 +1916,7 @@ export class AgentSession {
1886
1916
  }
1887
1917
  finally {
1888
1918
  this._autoCompactionAbortController = undefined;
1919
+ finishCompactionWork();
1889
1920
  }
1890
1921
  }
1891
1922
  /**
@@ -2250,6 +2281,9 @@ export class AgentSession {
2250
2281
  // =========================================================================
2251
2282
  // Auto-Retry
2252
2283
  // =========================================================================
2284
+ _isNonRetryableProviderLimitError(errorMessage) {
2285
+ return /GoUsageLimitError|FreeUsageLimitError|Monthly usage limit reached|available balance|insufficient_quota|out of budget|quota exceeded|billing/i.test(errorMessage);
2286
+ }
2253
2287
  /**
2254
2288
  * Check if an error is retryable (overloaded, rate limit, server errors).
2255
2289
  * Context overflow errors are NOT retryable (handled by compaction instead).
@@ -2267,6 +2301,8 @@ export class AgentSession {
2267
2301
  }
2268
2302
  if (message.stopReason !== "error")
2269
2303
  return false;
2304
+ if (this._isNonRetryableProviderLimitError(err))
2305
+ return false;
2270
2306
  // Match: overloaded_error, provider returned error, rate limit, 429, 500, 502, 503, 504, service unavailable, network/connection errors (including connection lost), WebSocket transport closes/errors, fetch failed, premature stream endings, HTTP/2 closed before response, terminated, retry delay exceeded
2271
2307
  return /overloaded|provider.?returned.?error|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|network.?error|connection.?error|connection.?refused|connection.?lost|websocket.?closed|websocket.?error|other side closed|fetch failed|upstream.?connect|reset before headers|socket hang up|ended without|stream ended before message_stop|http2 request did not get a response|timed? out|timeout|terminated|retry delay/i.test(err);
2272
2308
  }