@code-yeongyu/senpi 2026.10.21-1 → 2026.10.21-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 (204) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/bundle/chunks/{anthropic-messages-S5BTP7U2.js → anthropic-messages-2IQD75YN.js} +1 -1
  3. package/dist/bundle/chunks/args-SV2KM4KA.js +2 -0
  4. package/dist/bundle/chunks/azure-openai-responses-HFI6SDH5.js +2 -0
  5. package/dist/bundle/chunks/{chunk-WQLMQGOW.js → chunk-2AJB5S4C.js} +1 -1
  6. package/dist/bundle/chunks/{chunk-XSRM367N.js → chunk-3AK3WUN4.js} +2 -2
  7. package/dist/bundle/chunks/{chunk-6TL72TUD.js → chunk-4HJTPWZY.js} +1 -1
  8. package/dist/bundle/chunks/chunk-4NPBDGC4.js +26 -0
  9. package/dist/bundle/chunks/{chunk-E4WOCULF.js → chunk-4XR5HDWC.js} +72 -69
  10. package/dist/bundle/chunks/{chunk-6IWSMXBA.js → chunk-5GC4IQ3A.js} +1 -1
  11. package/dist/bundle/chunks/{chunk-7I7ZMSBF.js → chunk-A4B4CMOR.js} +1 -1
  12. package/dist/bundle/chunks/chunk-B4N4N7BR.js +8 -0
  13. package/dist/bundle/chunks/{chunk-X23XXKMH.js → chunk-BVFJAXWO.js} +1 -1
  14. package/dist/bundle/chunks/{chunk-25B6JQC4.js → chunk-C3BJRXEV.js} +1 -1
  15. package/dist/bundle/chunks/{chunk-DKBJM22C.js → chunk-CQDU6S6C.js} +2 -2
  16. package/dist/bundle/chunks/{chunk-MF57DS3V.js → chunk-EV4TWAJI.js} +1 -1
  17. package/dist/bundle/chunks/{chunk-JJXDWX46.js → chunk-F3JGTOLH.js} +1 -1
  18. package/dist/bundle/chunks/{chunk-CRY5CP3B.js → chunk-G42JHX5Q.js} +3 -3
  19. package/dist/bundle/chunks/{chunk-5VRI3CME.js → chunk-G7VYQRE7.js} +2 -2
  20. package/dist/bundle/chunks/chunk-GVSBCJFI.js +2 -0
  21. package/dist/bundle/chunks/{chunk-KUZPBZUK.js → chunk-HKDGQE6D.js} +2 -2
  22. package/dist/bundle/chunks/{chunk-HD67U37N.js → chunk-HRV7NEOJ.js} +1 -1
  23. package/dist/bundle/chunks/chunk-JDETIKL7.js +7 -0
  24. package/dist/bundle/chunks/{chunk-MVNVDGYF.js → chunk-JGMG227A.js} +1 -1
  25. package/dist/bundle/chunks/chunk-MHD7SSHF.js +2 -0
  26. package/dist/bundle/chunks/{chunk-CEQMTVYS.js → chunk-MXLEWTWC.js} +2 -2
  27. package/dist/bundle/chunks/{chunk-C5W3D56I.js → chunk-N6UN2SDS.js} +3 -3
  28. package/dist/bundle/chunks/chunk-NTK2MHJ5.js +2 -0
  29. package/dist/bundle/chunks/chunk-OMN4QWFA.js +3 -0
  30. package/dist/bundle/chunks/chunk-PLQ5WRXN.js +2 -0
  31. package/dist/bundle/chunks/{chunk-2MOGCBPT.js → chunk-PUMCQ5HS.js} +1 -1
  32. package/dist/bundle/chunks/{chunk-55YTTFNY.js → chunk-PUQFKAB4.js} +1 -1
  33. package/dist/bundle/chunks/{chunk-IJT3GSB2.js → chunk-SN7PTZTC.js} +1 -1
  34. package/dist/bundle/chunks/{chunk-FWHSGCWR.js → chunk-T2IIUI3J.js} +3 -3
  35. package/dist/bundle/chunks/{chunk-5AOKDC6G.js → chunk-TSETMVJP.js} +1 -1
  36. package/dist/bundle/chunks/{chunk-SR7DBNWN.js → chunk-V6AYNGMX.js} +1 -1
  37. package/dist/bundle/chunks/chunk-VDPJ7W52.js +11 -0
  38. package/dist/bundle/chunks/{chunk-EPQK674G.js → chunk-VLKULV6F.js} +1 -1
  39. package/dist/bundle/chunks/{chunk-SL7TSFR5.js → chunk-VXACHCLB.js} +1 -1
  40. package/dist/bundle/chunks/{chunk-FM7NDZR5.js → chunk-VYSRUDEU.js} +1 -1
  41. package/dist/bundle/chunks/chunk-YMFGBSO3.js +2 -0
  42. package/dist/bundle/chunks/cli-main-RA6EBOOU.js +3 -0
  43. package/dist/bundle/chunks/{cloudflare-workers-ai-system-one-4QEQ7MGK.js → cloudflare-workers-ai-system-one-BVENEDT6.js} +1 -1
  44. package/dist/bundle/chunks/cursor-agent.js +3 -3
  45. package/dist/bundle/chunks/google-generative-ai-ONS2KSGN.js +2 -0
  46. package/dist/bundle/chunks/google-vertex-5VS7ZESH.js +2 -0
  47. package/dist/bundle/chunks/help-fast-path-FZY53UXB.js +2 -0
  48. package/dist/bundle/chunks/host-command-TNUIHEFP.js +2 -0
  49. package/dist/bundle/chunks/{host-lifecycle-EZQ5VCQV.js → host-lifecycle-LDOHAYLK.js} +1 -1
  50. package/dist/bundle/chunks/host-session-command-OKASUQPA.js +20 -0
  51. package/dist/bundle/chunks/interactive-mode-PSGTSURY.js +2 -0
  52. package/dist/bundle/chunks/{mistral-conversations-5EHLE7PZ.js → mistral-conversations-RLDMF2BI.js} +1 -1
  53. package/dist/bundle/chunks/{multi-session-host-OMJZ5VXM.js → multi-session-host-J5BVVUG3.js} +7 -6
  54. package/dist/bundle/chunks/openai-codex-responses-VRIJIZJG.js +10 -0
  55. package/dist/bundle/chunks/{openai-completions-NHDMDAS7.js → openai-completions-EZBOIXHP.js} +2 -2
  56. package/dist/bundle/chunks/{openai-images-PNXQDBSP.js → openai-images-KKM343F2.js} +1 -1
  57. package/dist/bundle/chunks/openai-responses-CMBH3DCY.js +3 -0
  58. package/dist/bundle/chunks/{package-manager-cli-7GQDB5DX.js → package-manager-cli-JHVRG35I.js} +1 -1
  59. package/dist/bundle/chunks/{role-WQWYL4ZD.js → role-LNF5MFGC.js} +1 -1
  60. package/dist/bundle/chunks/rotation-stream-JK3Q4IWI.js +2 -0
  61. package/dist/bundle/chunks/rpc-mode-HVSPPRTT.js +2 -0
  62. package/dist/bundle/chunks/{rpc-monitor-source-FI7H7LGY.js → rpc-monitor-source-2DQZLFFJ.js} +1 -1
  63. package/dist/bundle/chunks/{runtime-session-IQAKNRTV.js → runtime-session-CLNODAYE.js} +1 -1
  64. package/dist/bundle/chunks/{schedule-command-XXJC4QQN.js → schedule-command-OMI4VWUV.js} +1 -1
  65. package/dist/bundle/chunks/session-control-endpoint-UPYF4OFP.js +3 -0
  66. package/dist/bundle/chunks/session-picker-NR7CRVNQ.js +2 -0
  67. package/dist/bundle/chunks/session-worker.js +143 -139
  68. package/dist/bundle/chunks/{typesafe-system-one-W2ZTYF7X.js → typesafe-system-one-PJIWA5SP.js} +1 -1
  69. package/dist/bundle/cli.js +2 -2
  70. package/dist/bundle/index.js +1 -1
  71. package/dist/bundle/rpc-entry.js +1 -1
  72. package/dist/bundle/runtime-manifest.json +1 -1
  73. package/dist/cli/host-command.d.ts +2 -0
  74. package/dist/cli/host-command.js +14 -19
  75. package/dist/cli/host-handover-args.d.ts +4 -0
  76. package/dist/cli/host-handover-args.js +20 -0
  77. package/dist/core/agent-session.d.ts +29 -1
  78. package/dist/core/agent-session.js +74 -19
  79. package/dist/core/auth-storage.d.ts +44 -1
  80. package/dist/core/auth-storage.js +120 -3
  81. package/dist/core/client-message-identity.d.ts +3 -0
  82. package/dist/core/credential-accounts.d.ts +2 -0
  83. package/dist/core/credential-accounts.js +8 -1
  84. package/dist/core/credential-pool/rotation-slots.js +3 -2
  85. package/dist/core/extensions/builtin/account/index.js +6 -1
  86. package/dist/core/extensions/builtin/compaction/openai-remote.d.ts +2 -1
  87. package/dist/core/extensions/builtin/compaction/openai-remote.js +6 -5
  88. package/dist/core/extensions/builtin/goal/provider-retry.d.ts +2 -2
  89. package/dist/core/extensions/builtin/goal/provider-retry.js +18 -6
  90. package/dist/core/extensions/builtin/terminal/command-monitor-source.d.ts +17 -4
  91. package/dist/core/extensions/builtin/terminal/command-monitor-source.js +33 -6
  92. package/dist/core/extensions/builtin/terminal/durable-command.js +1 -0
  93. package/dist/core/extensions/builtin/terminal/extension.js +28 -0
  94. package/dist/core/extensions/builtin/terminal/monitor-notify-details.d.ts +6 -0
  95. package/dist/core/extensions/builtin/terminal/monitor-notify-details.js +19 -0
  96. package/dist/core/extensions/builtin/terminal/monitor-notify-format.d.ts +18 -0
  97. package/dist/core/extensions/builtin/terminal/monitor-notify-format.js +52 -0
  98. package/dist/core/extensions/builtin/terminal/monitor-notify.d.ts +8 -1
  99. package/dist/core/extensions/builtin/terminal/monitor-notify.js +65 -119
  100. package/dist/core/extensions/builtin/terminal/monitor-overflow-file.d.ts +7 -0
  101. package/dist/core/extensions/builtin/terminal/monitor-overflow-file.js +67 -0
  102. package/dist/core/extensions/builtin/terminal/monitor-overflow.d.ts +37 -0
  103. package/dist/core/extensions/builtin/terminal/monitor-overflow.js +75 -0
  104. package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts +10 -0
  105. package/dist/core/extensions/builtin/terminal/monitor-registry.js +24 -1
  106. package/dist/core/extensions/builtin/terminal/monitor-types.d.ts +5 -0
  107. package/dist/core/extensions/builtin/terminal/monitor-wake-fit.d.ts +9 -0
  108. package/dist/core/extensions/builtin/terminal/monitor-wake-fit.js +34 -0
  109. package/dist/core/extensions/builtin/terminal/monitor-wake-pacing.d.ts +10 -0
  110. package/dist/core/extensions/builtin/terminal/monitor-wake-pacing.js +41 -0
  111. package/dist/core/extensions/builtin/terminal/restore-session.js +2 -0
  112. package/dist/core/extensions/builtin/terminal/rpc-monitor-observer.js +5 -1
  113. package/dist/core/extensions/builtin/terminal/settings.d.ts +10 -0
  114. package/dist/core/extensions/builtin/terminal/settings.js +13 -1
  115. package/dist/core/extensions/builtin/terminal/shared.d.ts +17 -10
  116. package/dist/core/extensions/builtin/terminal/shared.js +19 -10
  117. package/dist/core/extensions/builtin/terminal/terminal-manifest-model.d.ts +3 -0
  118. package/dist/core/extensions/builtin/terminal/terminal-manifest-parse.js +7 -0
  119. package/dist/core/extensions/builtin/terminal/terminal-manifest.js +1 -0
  120. package/dist/core/extensions/builtin/terminal/tools/monitor-schema.d.ts +1 -0
  121. package/dist/core/extensions/builtin/terminal/tools/monitor-schema.js +6 -1
  122. package/dist/core/extensions/builtin/terminal/tools/monitor.d.ts +1 -0
  123. package/dist/core/extensions/builtin/terminal/tools/monitor.js +9 -1
  124. package/dist/core/extensions/builtin/tps.js +1 -8
  125. package/dist/core/extensions/loader.js +3 -1
  126. package/dist/core/extensions/runner.d.ts +1 -0
  127. package/dist/core/extensions/runner.js +12 -1
  128. package/dist/core/extensions/types.d.ts +3 -1
  129. package/dist/core/session-manager.d.ts +10 -1
  130. package/dist/core/session-manager.js +4 -3
  131. package/dist/core/terminal-settings.d.ts +3 -0
  132. package/dist/index.d.ts +1 -1
  133. package/dist/main.d.ts +14 -0
  134. package/dist/main.js +25 -8
  135. package/dist/modes/index.d.ts +1 -0
  136. package/dist/modes/index.js +1 -0
  137. package/dist/modes/interactive/components/footer.js +4 -2
  138. package/dist/modes/interactive/interactive-mode.d.ts +5 -0
  139. package/dist/modes/interactive/interactive-mode.js +13 -1
  140. package/dist/modes/rpc/client-admission-record.d.ts +5 -0
  141. package/dist/modes/rpc/client-admission-record.js +2 -0
  142. package/dist/modes/rpc/connection-handler.d.ts +3 -0
  143. package/dist/modes/rpc/connection-handler.js +33 -5
  144. package/dist/modes/rpc/connection-roles.d.ts +30 -0
  145. package/dist/modes/rpc/connection-roles.js +53 -0
  146. package/dist/modes/rpc/custom-capability.d.ts +6 -0
  147. package/dist/modes/rpc/custom-capability.js +6 -0
  148. package/dist/modes/rpc/host-capabilities.js +2 -1
  149. package/dist/modes/rpc/host-decision.d.ts +4 -1
  150. package/dist/modes/rpc/host-decision.js +6 -3
  151. package/dist/modes/rpc/host-ensure-liveness.d.ts +11 -0
  152. package/dist/modes/rpc/host-ensure-liveness.js +26 -0
  153. package/dist/modes/rpc/host-ensure.js +2 -3
  154. package/dist/modes/rpc/host-holder.d.ts +27 -0
  155. package/dist/modes/rpc/host-holder.js +18 -0
  156. package/dist/modes/rpc/host-runner.d.ts +2 -0
  157. package/dist/modes/rpc/host-runner.js +8 -1
  158. package/dist/modes/rpc/host-status-all.js +1 -0
  159. package/dist/modes/rpc/host-status.d.ts +1 -0
  160. package/dist/modes/rpc/host-status.js +5 -0
  161. package/dist/modes/rpc/host-stop-generation.d.ts +22 -0
  162. package/dist/modes/rpc/host-stop-generation.js +36 -0
  163. package/dist/modes/rpc/host-stop-start-time.d.ts +15 -0
  164. package/dist/modes/rpc/host-stop-start-time.js +30 -0
  165. package/dist/modes/rpc/host-stop.d.ts +13 -1
  166. package/dist/modes/rpc/host-stop.js +78 -9
  167. package/dist/modes/rpc/rpc-client.d.ts +9 -0
  168. package/dist/modes/rpc/rpc-client.js +15 -0
  169. package/dist/modes/rpc/rpc-types.d.ts +18 -0
  170. package/dist/modes/rpc/rpc-types.js +2 -0
  171. package/dist/modes/rpc/session-binding.d.ts +1 -1
  172. package/dist/modes/rpc/session-command-router.d.ts +1 -0
  173. package/dist/modes/rpc/session-command-router.js +13 -0
  174. package/dist/modes/rpc/session-worker-client.d.ts +1 -1
  175. package/dist/modes/rpc/session-worker-client.js +12 -1
  176. package/dist/modes/rpc/session-worker-protocol.d.ts +3 -0
  177. package/dist/modes/rpc/session-worker.js +9 -0
  178. package/docs/rpc.md +54 -2
  179. package/docs/session-format.md +5 -0
  180. package/package.json +6 -6
  181. package/dist/bundle/chunks/args-CRXNC7IS.js +0 -2
  182. package/dist/bundle/chunks/azure-openai-responses-2DCY6QKO.js +0 -2
  183. package/dist/bundle/chunks/chunk-5ZBDN3LB.js +0 -26
  184. package/dist/bundle/chunks/chunk-AEJUOQ7U.js +0 -2
  185. package/dist/bundle/chunks/chunk-LAWS3FYQ.js +0 -11
  186. package/dist/bundle/chunks/chunk-LM76EMEJ.js +0 -7
  187. package/dist/bundle/chunks/chunk-M237EDHB.js +0 -2
  188. package/dist/bundle/chunks/chunk-V6RY5A6T.js +0 -2
  189. package/dist/bundle/chunks/chunk-WIAXG52U.js +0 -2
  190. package/dist/bundle/chunks/chunk-WTFD4TII.js +0 -8
  191. package/dist/bundle/chunks/chunk-Z33PKFXM.js +0 -3
  192. package/dist/bundle/chunks/cli-main-RKC5Z25H.js +0 -3
  193. package/dist/bundle/chunks/google-generative-ai-3SKUSCIL.js +0 -2
  194. package/dist/bundle/chunks/google-vertex-YHSIIE4G.js +0 -2
  195. package/dist/bundle/chunks/help-fast-path-FWS5ARO5.js +0 -2
  196. package/dist/bundle/chunks/host-command-L66Y7ZL5.js +0 -2
  197. package/dist/bundle/chunks/host-session-command-2YNQNTEH.js +0 -20
  198. package/dist/bundle/chunks/interactive-mode-XOKTNBLA.js +0 -2
  199. package/dist/bundle/chunks/openai-codex-responses-4BIPACKV.js +0 -10
  200. package/dist/bundle/chunks/openai-responses-2UOPEJLP.js +0 -3
  201. package/dist/bundle/chunks/rotation-stream-J2NUPRMG.js +0 -2
  202. package/dist/bundle/chunks/rpc-mode-WM5WH7TH.js +0 -2
  203. package/dist/bundle/chunks/session-control-endpoint-MTOAYRRY.js +0 -3
  204. package/dist/bundle/chunks/session-picker-LU7BPLNI.js +0 -2
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Per-monitor wake pacing: a token bucket of `rate` tokens refilled at `rate` an hour, one token
3
+ * per wake. Monitors without a rate are unpaced and always ready.
4
+ */
5
+ const HOUR_MS = 3_600_000;
6
+ export class WakePacer {
7
+ #rateOf;
8
+ #buckets = new Map();
9
+ constructor(rateOf) {
10
+ this.#rateOf = rateOf;
11
+ }
12
+ /** When `id` next holds a whole token: `now` for an unpaced monitor or one with a token left. */
13
+ readyAt(id, now) {
14
+ const paced = this.#refill(id, now);
15
+ if (paced === undefined || paced.bucket.tokens >= 1)
16
+ return now;
17
+ return now + Math.ceil(((1 - paced.bucket.tokens) * HOUR_MS) / paced.rate);
18
+ }
19
+ spend(id, now) {
20
+ const paced = this.#refill(id, now);
21
+ if (paced !== undefined)
22
+ paced.bucket.tokens = Math.max(0, paced.bucket.tokens - 1);
23
+ }
24
+ forget(id) {
25
+ this.#buckets.delete(id);
26
+ }
27
+ clear() {
28
+ this.#buckets.clear();
29
+ }
30
+ #refill(id, now) {
31
+ const rate = this.#rateOf(id);
32
+ if (rate === undefined || !Number.isFinite(rate) || rate < 1)
33
+ return undefined;
34
+ const bucket = this.#buckets.get(id) ?? { tokens: rate, at: now };
35
+ bucket.tokens = Math.min(rate, bucket.tokens + ((now - bucket.at) * rate) / HOUR_MS);
36
+ bucket.at = now;
37
+ this.#buckets.set(id, bucket);
38
+ return { bucket, rate };
39
+ }
40
+ }
41
+ //# sourceMappingURL=monitor-wake-pacing.js.map
@@ -10,6 +10,7 @@ import { existsSync } from "node:fs";
10
10
  import { encodedSessionId } from "../../../session-sidecar-store.js";
11
11
  import { createLeaseKeeper } from "./lease-keeper.js";
12
12
  import { acquireTerminalLease, releaseTerminalLease, retireLeaseToken, } from "./manifest-lease.js";
13
+ import { sweepMonitorOverflowFiles } from "./monitor-overflow-file.js";
13
14
  import { removeMonitorStateDir, terminalStateDir } from "./monitor-state-dir.js";
14
15
  import { ownProcessStartedAtMs, processBootAtMs } from "./process-identity.js";
15
16
  import { restoreTerminalState } from "./restore.js";
@@ -114,6 +115,7 @@ export async function startPersistence({ pi, state, toolCtx, sessionKey }) {
114
115
  const self = { pid: process.pid, bootAtMs: processBootAtMs(), processStartedAtMs: ownProcessStartedAtMs() };
115
116
  // Housekeeping only: a failed sweep leaves the files for the next start to reclaim.
116
117
  void sweepTerminalStateDir(dir, { self, keep: new Set([`${encoded}.lease`, `${encoded}.json`]) }).catch(() => undefined);
118
+ void sweepMonitorOverflowFiles(dir).catch(() => undefined);
117
119
  flushRestoreDigest(pi, state);
118
120
  decided();
119
121
  };
@@ -6,6 +6,7 @@
6
6
  * reads the stream itself.
7
7
  */
8
8
  import { createConnection } from "node:net";
9
+ import { OBSERVE_REQUEST_FIELD } from "../../../../modes/rpc/host-observe-request.js";
9
10
  import { attachJsonlLineReader, MAX_RPC_LINE_CHARACTERS } from "../../../../modes/rpc/jsonl.js";
10
11
  import { readSocketSecret, resolveSocketTransportAddress, sendSocketHandshake, socketSecretPath, } from "../../../../modes/rpc/socket-transport.js";
11
12
  import { socketNeedsHandshake } from "../../../../modes/rpc/tui-socket.js";
@@ -25,7 +26,10 @@ export async function openRpcObserver(socketPath, subscription, handlers) {
25
26
  socket.once("connect", () => {
26
27
  if (secret !== undefined)
27
28
  sendSocketHandshake(socket, secret);
28
- socket.write(`${JSON.stringify({ ...subscription, id: SUBSCRIBE_ID, type: "subscribe_sessions" })}\n`);
29
+ // Marked observing (senpi#3956): without it the supervisor counts this watch as an attached client, so a
30
+ // watched host never idle-exits and a `children`/`all` watch pins every task shard host on the machine.
31
+ const frame = { ...subscription, [OBSERVE_REQUEST_FIELD]: true, id: SUBSCRIBE_ID, type: "subscribe_sessions" };
32
+ socket.write(`${JSON.stringify(frame)}\n`);
29
33
  });
30
34
  const detach = attachJsonlLineReader(socket, (line) => {
31
35
  let record;
@@ -7,6 +7,13 @@ export interface MonitorDeliverySettings {
7
7
  readonly maxLinesPerInjection: number;
8
8
  readonly maxCharsPerInjection: number;
9
9
  readonly wakeBudget: number;
10
+ /** Average wakes an hour (and burst size) of a persistent command watch; more lines batch. */
11
+ readonly wakesPerHour: number;
12
+ }
13
+ /** Runaway guard of a persistent command watch: pause after this many lines a minute for this many minutes. */
14
+ export interface MonitorRunawayGuard {
15
+ readonly linesPerMinute: number;
16
+ readonly minutes: number;
10
17
  }
11
18
  export interface ResolvedTerminalSettings {
12
19
  readonly defaultCols: number;
@@ -16,10 +23,13 @@ export interface ResolvedTerminalSettings {
16
23
  readonly timeoutAction: TimeoutAction;
17
24
  readonly notify: NotifyMode;
18
25
  readonly monitor: MonitorDeliverySettings;
26
+ readonly monitorRunaway: MonitorRunawayGuard;
19
27
  /** Admission cap on persistent (restart-surviving) monitors per session. */
20
28
  readonly maxDurableMonitors: number | "unlimited";
21
29
  }
22
30
  export declare const TERMINAL_SETTINGS_DEFAULTS: ResolvedTerminalSettings;
31
+ /** Upper bound of a wake rate (setting or per-monitor override): one wake a second on average. */
32
+ export declare const MAX_MONITOR_WAKES_PER_HOUR = 3600;
23
33
  /** Resolve terminal-tool config from a raw `terminal` settings block, filling defaults. */
24
34
  export declare function resolveTerminalSettings(raw: TerminalSettings | undefined): ResolvedTerminalSettings;
25
35
  /** Load and merge terminal-tool settings from global + project settings.json. */
@@ -1,4 +1,4 @@
1
- import { DEFAULT_COLS, DEFAULT_MAX_SESSIONS, DEFAULT_ROWS, DEFAULT_SCROLLBACK } from "./shared.js";
1
+ import { DEFAULT_COLS, DEFAULT_MAX_SESSIONS, DEFAULT_MONITOR_RUNAWAY_LINES_PER_MINUTE, DEFAULT_MONITOR_RUNAWAY_MINUTES, DEFAULT_MONITOR_WAKES_PER_HOUR, DEFAULT_ROWS, DEFAULT_SCROLLBACK, } from "./shared.js";
2
2
  export const TERMINAL_SETTINGS_DEFAULTS = {
3
3
  defaultCols: DEFAULT_COLS,
4
4
  defaultRows: DEFAULT_ROWS,
@@ -12,9 +12,16 @@ export const TERMINAL_SETTINGS_DEFAULTS = {
12
12
  maxLinesPerInjection: 50,
13
13
  maxCharsPerInjection: 4096,
14
14
  wakeBudget: 5,
15
+ wakesPerHour: DEFAULT_MONITOR_WAKES_PER_HOUR,
16
+ },
17
+ monitorRunaway: {
18
+ linesPerMinute: DEFAULT_MONITOR_RUNAWAY_LINES_PER_MINUTE,
19
+ minutes: DEFAULT_MONITOR_RUNAWAY_MINUTES,
15
20
  },
16
21
  maxDurableMonitors: "unlimited",
17
22
  };
23
+ /** Upper bound of a wake rate (setting or per-monitor override): one wake a second on average. */
24
+ export const MAX_MONITOR_WAKES_PER_HOUR = 3600;
18
25
  function positiveInt(value, fallback, maximum = Number.MAX_SAFE_INTEGER) {
19
26
  if (typeof value !== "number" || !Number.isFinite(value) || value < 1)
20
27
  return fallback;
@@ -51,6 +58,11 @@ export function resolveTerminalSettings(raw) {
51
58
  maxLinesPerInjection: positiveInt(raw.monitorMaxLinesPerInjection, TERMINAL_SETTINGS_DEFAULTS.monitor.maxLinesPerInjection, 200),
52
59
  maxCharsPerInjection: positiveInt(raw.monitorMaxCharsPerInjection, TERMINAL_SETTINGS_DEFAULTS.monitor.maxCharsPerInjection, 16_384),
53
60
  wakeBudget: positiveInt(raw.monitorWakeBudget, TERMINAL_SETTINGS_DEFAULTS.monitor.wakeBudget, 100),
61
+ wakesPerHour: positiveInt(raw.monitorWakesPerHour, TERMINAL_SETTINGS_DEFAULTS.monitor.wakesPerHour, MAX_MONITOR_WAKES_PER_HOUR),
62
+ },
63
+ monitorRunaway: {
64
+ linesPerMinute: positiveInt(raw.monitorRunawayLinesPerMinute, TERMINAL_SETTINGS_DEFAULTS.monitorRunaway.linesPerMinute),
65
+ minutes: positiveInt(raw.monitorRunawayMinutes, TERMINAL_SETTINGS_DEFAULTS.monitorRunaway.minutes, 1440),
54
66
  },
55
67
  maxDurableMonitors: durableMonitorCap(raw.maxDurableMonitors),
56
68
  };
@@ -44,20 +44,27 @@ export declare const KILLED_SESSION_EXIT_GRACE_MS = 5000;
44
44
  */
45
45
  export declare const DURABLE_MONITOR_EXPIRY_MS: number;
46
46
  /**
47
- * Per-monitor rolling fire budget: one durable command watch may deliver at most this many
48
- * matched line events inside any FIRE_BUDGET_WINDOW_MS window before the registry auto-mutes
49
- * it. The session-global wake budget only counts consecutive line-only wakes on an idle
50
- * session, so a busy session needs this per-monitor backstop against a runaway watch.
47
+ * Width of a durable command watch's rolling fire window: the persisted count of matched lines it
48
+ * delivered inside the current window. It is a statistic only; nothing mutes on it.
51
49
  */
52
- export declare const DEFAULT_DURABLE_MONITOR_FIRE_BUDGET = 200;
53
- /** Width of the rolling fire-budget window: the budget replenishes after this span elapses. */
54
50
  export declare const FIRE_BUDGET_WINDOW_MS: number;
55
51
  /**
56
- * The ONE summary emitted when the fire budget mutes a watch. Machine-consumed: the monitor
57
- * notifier exempts exactly this summary from its completion handling, so the mute must not
58
- * clear the session-global wake streak.
52
+ * Wake pacing of a persistent command watch: at most this many wakes an hour on average, with a
53
+ * burst of the same size. Lines matched between wakes are batched into the next one, never dropped.
59
54
  */
60
- export declare const FIRE_BUDGET_AUTO_MUTE_SUMMARY = "auto-muted: fire budget (200/24h) reached; rearm to resume";
55
+ export declare const DEFAULT_MONITOR_WAKES_PER_HOUR = 12;
56
+ /** Runaway guard: a persistent command watch matching more lines than this in each minute... */
57
+ export declare const DEFAULT_MONITOR_RUNAWAY_LINES_PER_MINUTE = 120;
58
+ /** ...for this many consecutive minutes is paused with a visible notice until it is re-armed. */
59
+ export declare const DEFAULT_MONITOR_RUNAWAY_MINUTES = 5;
60
+ /** One per-monitor overflow file grows to this size before it rotates to a single `.1` copy. */
61
+ export declare const MONITOR_OVERFLOW_FILE_MAX_BYTES: number;
62
+ /**
63
+ * Machine-consumed prefix of the runaway-guard pause summary: the monitor notifier treats a summary
64
+ * that starts with it as a pause, not a completion, so it must not clear the session wake streak.
65
+ */
66
+ export declare const RUNAWAY_PAUSE_SUMMARY_PREFIX = "paused: runaway output";
67
+ export declare function runawayPauseSummary(bashId: string, linesPerMinute: number, minutes: number): string;
61
68
  /**
62
69
  * Non-interactive environment for foreground one-shot commands (codex-style):
63
70
  * cooperative tools (`gh`, `git`, pagers, color libs) skip spinners/colors at
@@ -50,20 +50,29 @@ export const KILLED_SESSION_EXIT_GRACE_MS = 5000;
50
50
  */
51
51
  export const DURABLE_MONITOR_EXPIRY_MS = 7 * 24 * 60 * 60 * 1000;
52
52
  /**
53
- * Per-monitor rolling fire budget: one durable command watch may deliver at most this many
54
- * matched line events inside any FIRE_BUDGET_WINDOW_MS window before the registry auto-mutes
55
- * it. The session-global wake budget only counts consecutive line-only wakes on an idle
56
- * session, so a busy session needs this per-monitor backstop against a runaway watch.
53
+ * Width of a durable command watch's rolling fire window: the persisted count of matched lines it
54
+ * delivered inside the current window. It is a statistic only; nothing mutes on it.
57
55
  */
58
- export const DEFAULT_DURABLE_MONITOR_FIRE_BUDGET = 200;
59
- /** Width of the rolling fire-budget window: the budget replenishes after this span elapses. */
60
56
  export const FIRE_BUDGET_WINDOW_MS = 24 * 60 * 60 * 1000;
61
57
  /**
62
- * The ONE summary emitted when the fire budget mutes a watch. Machine-consumed: the monitor
63
- * notifier exempts exactly this summary from its completion handling, so the mute must not
64
- * clear the session-global wake streak.
58
+ * Wake pacing of a persistent command watch: at most this many wakes an hour on average, with a
59
+ * burst of the same size. Lines matched between wakes are batched into the next one, never dropped.
65
60
  */
66
- export const FIRE_BUDGET_AUTO_MUTE_SUMMARY = `auto-muted: fire budget (${DEFAULT_DURABLE_MONITOR_FIRE_BUDGET}/24h) reached; rearm to resume`;
61
+ export const DEFAULT_MONITOR_WAKES_PER_HOUR = 12;
62
+ /** Runaway guard: a persistent command watch matching more lines than this in each minute... */
63
+ export const DEFAULT_MONITOR_RUNAWAY_LINES_PER_MINUTE = 120;
64
+ /** ...for this many consecutive minutes is paused with a visible notice until it is re-armed. */
65
+ export const DEFAULT_MONITOR_RUNAWAY_MINUTES = 5;
66
+ /** One per-monitor overflow file grows to this size before it rotates to a single `.1` copy. */
67
+ export const MONITOR_OVERFLOW_FILE_MAX_BYTES = 4 * 1024 * 1024;
68
+ /**
69
+ * Machine-consumed prefix of the runaway-guard pause summary: the monitor notifier treats a summary
70
+ * that starts with it as a pause, not a completion, so it must not clear the session wake streak.
71
+ */
72
+ export const RUNAWAY_PAUSE_SUMMARY_PREFIX = "paused: runaway output";
73
+ export function runawayPauseSummary(bashId, linesPerMinute, minutes) {
74
+ return `${RUNAWAY_PAUSE_SUMMARY_PREFIX} (more than ${linesPerMinute} matched lines a minute for ${minutes} minute${minutes === 1 ? "" : "s"}); later lines stay in bash_output. Resume with monitor({ action: "rearm", bash_id: "${bashId}" }).`;
75
+ }
67
76
  /**
68
77
  * Non-interactive environment for foreground one-shot commands (codex-style):
69
78
  * cooperative tools (`gh`, `git`, pagers, color libs) skip spinners/colors at
@@ -49,6 +49,8 @@ export interface ManifestMonitor {
49
49
  readonly filter?: string;
50
50
  /** Command watch whose output lines are a person's words; absent on older entries. */
51
51
  readonly humanInput?: boolean;
52
+ /** Per-monitor wake-rate override of a persistent command watch; absent uses the setting. */
53
+ readonly wakesPerHour?: number;
52
54
  readonly cwd?: string;
53
55
  readonly approvedParent?: string;
54
56
  /** Rpc watches: the target as written, the record kinds reported and dropped. */
@@ -91,6 +93,7 @@ export interface CommandMonitorSpec {
91
93
  readonly command: string;
92
94
  readonly filter?: string;
93
95
  readonly humanInput?: boolean;
96
+ readonly wakesPerHour?: number;
94
97
  readonly cwd?: string;
95
98
  readonly persistent: boolean;
96
99
  }
@@ -44,6 +44,12 @@ function bool(raw, field) {
44
44
  function opt(raw, field, required) {
45
45
  return raw[field] === undefined ? undefined : required(raw, field);
46
46
  }
47
+ function wakeRate(raw, field) {
48
+ const value = raw[field];
49
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > 3600)
50
+ invalid(`field ${field} must be an integer from 1 to 3600`);
51
+ return value;
52
+ }
47
53
  function oneOf(values, raw, field) {
48
54
  const value = raw[field];
49
55
  if (typeof value !== "string" || !values.includes(value))
@@ -127,6 +133,7 @@ function parseMonitor(entry) {
127
133
  event: opt(entry, "event", (raw, field) => oneOf(FILE_EVENTS, raw, field)),
128
134
  filter: opt(entry, "filter", str),
129
135
  humanInput: opt(entry, "humanInput", bool),
136
+ ...(entry.wakesPerHour === undefined ? {} : { wakesPerHour: wakeRate(entry, "wakesPerHour") }),
130
137
  cwd: opt(entry, "cwd", str),
131
138
  approvedParent: opt(entry, "approvedParent", str),
132
139
  target: opt(entry, "target", str),
@@ -227,6 +227,7 @@ export class TerminalManifestWriter {
227
227
  event: spec.kind === "file" ? spec.event : undefined,
228
228
  filter: spec.kind === "file" ? undefined : spec.filter,
229
229
  humanInput: spec.kind === "command" && spec.humanInput === true ? true : undefined,
230
+ ...(spec.kind === "command" && spec.wakesPerHour !== undefined ? { wakesPerHour: spec.wakesPerHour } : {}),
230
231
  cwd: spec.kind === "rpc" ? undefined : spec.cwd,
231
232
  ...(spec.kind === "rpc"
232
233
  ? {
@@ -21,6 +21,7 @@ export declare const monitorSchema: Type.TObject<{
21
21
  timeout_ms: Type.TOptional<Type.TNumber>;
22
22
  persistent: Type.TOptional<Type.TBoolean>;
23
23
  bash_id: Type.TOptional<Type.TString>;
24
+ wakes_per_hour: Type.TOptional<Type.TInteger>;
24
25
  human_input: Type.TOptional<Type.TBoolean>;
25
26
  }>;
26
27
  export type MonitorInput = Static<typeof monitorSchema>;
@@ -10,7 +10,7 @@ export const MAX_MONITOR_TIMEOUT_MS = 3_600_000;
10
10
  */
11
11
  export const monitorSchema = Type.Object({
12
12
  action: Type.Optional(StringEnum(["create", "rearm"], {
13
- description: "Defaults to create. rearm resumes a monitor paused by the wake budget.",
13
+ description: "Defaults to create. rearm resumes a monitor paused by the wake budget or the runaway guard.",
14
14
  })),
15
15
  type: Type.Optional(StringEnum(["command", "path", "rpc"], {
16
16
  description: "Create: which source to watch; inferred when omitted (command -> command, path -> path, target -> rpc).",
@@ -50,6 +50,11 @@ export const monitorSchema = Type.Object({
50
50
  description: "Standing watch: no deadline, and it survives a session restart (command re-run once, file rescanned and any detached change reported). Expires 7 days after creation; no per-session cap unless the terminal.maxDurableMonitors setting sets one; stop one with kill_bash.",
51
51
  })),
52
52
  bash_id: Type.Optional(Type.String({ description: "Rearm: paused monitor id (mon_ or bash_id) to resume; omit for all paused." })),
53
+ wakes_per_hour: Type.Optional(Type.Integer({
54
+ minimum: 1,
55
+ maximum: 3600,
56
+ description: "Persistent command branch only: average wakes an hour for this watch (default: terminal.monitorWakesPerHour, 12). Lines matched between wakes are batched into the next one, never dropped; raise it for a chat bridge that needs quicker replies.",
57
+ })),
53
58
  human_input: Type.Optional(Type.Boolean({
54
59
  description: "Command branch only: set true when each output line is a message a person wrote (a chat or inbox bridge). Its deliveries then count as new user input, so a running goal keeps going; leave unset for machine status.",
55
60
  })),
@@ -24,6 +24,7 @@ export declare function createMonitorTool(ctx: TerminalToolContext): {
24
24
  timeout_ms: import("typebox").TOptional<import("typebox").TNumber>;
25
25
  persistent: import("typebox").TOptional<import("typebox").TBoolean>;
26
26
  bash_id: import("typebox").TOptional<import("typebox").TString>;
27
+ wakes_per_hour: import("typebox").TOptional<import("typebox").TInteger>;
27
28
  human_input: import("typebox").TOptional<import("typebox").TBoolean>;
28
29
  }>;
29
30
  renderCall: typeof renderMonitorCall;
@@ -50,7 +50,9 @@ async function createMonitor(ctx, registry, input, execCtx) {
50
50
  persistent: input.persistent === true,
51
51
  deadlineMs,
52
52
  ...(input.human_input === true ? { humanInput: true } : {}),
53
- // Only persistent command watches are restartable-command durable: those carry the fire budget.
53
+ ...(input.wakes_per_hour !== undefined ? { wakesPerHour: input.wakes_per_hour } : {}),
54
+ // Only persistent command watches are restartable-command durable: those carry the fire window
55
+ // and the runaway guard, and only they are wake-paced.
54
56
  durabilityClass: input.persistent === true ? "restartable-command" : "ephemeral",
55
57
  // Same deadline the manifest writer persists, so the footer warns off the live record too.
56
58
  ...(input.persistent === true ? { expiresAt: Date.now() + DURABLE_MONITOR_EXPIRY_MS } : {}),
@@ -67,6 +69,7 @@ async function createMonitor(ctx, registry, input, execCtx) {
67
69
  command: input.command,
68
70
  filter: input.filter,
69
71
  ...(input.human_input === true ? { humanInput: true } : {}),
72
+ ...(input.wakes_per_hour !== undefined ? { wakesPerHour: input.wakes_per_hour } : {}),
70
73
  cwd,
71
74
  persistent: input.persistent === true,
72
75
  },
@@ -130,6 +133,11 @@ export function createMonitorTool(ctx) {
130
133
  const source = monitorSourceOf(input);
131
134
  if (typeof source !== "string")
132
135
  return errorResult(source.error);
136
+ if (input.wakes_per_hour !== undefined && (source !== "command" || input.persistent !== true))
137
+ return errorResult("wakes_per_hour applies to persistent command monitors (command with persistent: true); other watches are not wake-paced.");
138
+ if (input.wakes_per_hour !== undefined &&
139
+ (!Number.isInteger(input.wakes_per_hour) || input.wakes_per_hour < 1 || input.wakes_per_hour > 3600))
140
+ return errorResult("wakes_per_hour must be an integer from 1 to 3600.");
133
141
  if (source === "rpc") {
134
142
  if (input.human_input === true)
135
143
  return errorResult("human_input applies to command monitors; an rpc watch reports session status, not a person's text.");
@@ -1,17 +1,10 @@
1
+ import { reasonedBeforeFirstDelta, STREAMED_DELTA_TYPES } from "@earendil-works/pi-agent-core";
1
2
  function isAssistantMessage(message) {
2
3
  if (!message || typeof message !== "object")
3
4
  return false;
4
5
  const role = message.role;
5
6
  return role === "assistant";
6
7
  }
7
- function reasonedBeforeFirstDelta(message, thinkingStreamed) {
8
- if ((message.usage?.reasoning ?? 0) > 0)
9
- return true;
10
- if (thinkingStreamed)
11
- return false;
12
- return message.content?.some((block) => block.type === "thinking") ?? false;
13
- }
14
- const STREAMED_DELTA_TYPES = new Set(["text_delta", "thinking_delta", "toolcall_delta"]);
15
8
  /** Below this much streamed generation time a rate is mostly timer noise, so none is shown (senpi#3393). */
16
9
  const MIN_GENERATION_MS = 500;
17
10
  export default function (pi) {
@@ -900,7 +900,9 @@ function discoverExtensionsInDir(dir) {
900
900
  }
901
901
  const discovered = [];
902
902
  try {
903
- const entries = fs.readdirSync(dir, { withFileTypes: true });
903
+ const entries = fs
904
+ .readdirSync(dir, { withFileTypes: true })
905
+ .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
904
906
  for (const entry of entries) {
905
907
  const entryPath = path.join(dir, entry.name);
906
908
  // 1. Direct files: *.ts or *.js
@@ -142,6 +142,7 @@ export declare class ExtensionRunner {
142
142
  private engineTurnsHeldFn;
143
143
  private noteHeldWakeFn;
144
144
  private questionInput;
145
+ private personAnswerPending;
145
146
  private isProjectTrustedFn;
146
147
  private getSignalFn;
147
148
  private waitForIdleFn;
@@ -249,6 +249,7 @@ export class ExtensionRunner {
249
249
  this.isIdleFn = () => true;
250
250
  this.engineTurnsHeldFn = () => false;
251
251
  this.noteHeldWakeFn = () => { };
252
+ this.personAnswerPending = false;
252
253
  this.isProjectTrustedFn = () => true;
253
254
  this.getSignalFn = () => undefined;
254
255
  this.waitForIdleFn = async () => { };
@@ -336,7 +337,12 @@ export class ExtensionRunner {
336
337
  else
337
338
  actions.sendMessage(message, options);
338
339
  };
339
- this.runtime.sendUserMessage = (content, options) => this.sessionStartTurns.admit(() => actions.sendUserMessage(content, options));
340
+ this.runtime.sendUserMessage = (content, options) => {
341
+ // Captured at admission: a deferred answer keeps the origin it was delivered with.
342
+ const inputOrigin = this.personAnswerPending ? "person" : "extension";
343
+ this.personAnswerPending = false;
344
+ this.sessionStartTurns.admit(() => actions.sendUserMessage(content, options, inputOrigin));
345
+ };
340
346
  this.runtime.appendEntry = actions.appendEntry;
341
347
  this.runtime.setSessionName = actions.setSessionName;
342
348
  this.runtime.getSessionName = actions.getSessionName;
@@ -1248,6 +1254,11 @@ export class ExtensionRunner {
1248
1254
  if (this.questionInput) {
1249
1255
  bindQuestionInput(context, () => {
1250
1256
  runner.assertActive();
1257
+ // The answer's own sendUserMessage follows synchronously and takes this token at admission.
1258
+ runner.personAnswerPending = true;
1259
+ queueMicrotask(() => {
1260
+ runner.personAnswerPending = false;
1261
+ });
1251
1262
  runner.questionInput?.();
1252
1263
  });
1253
1264
  }
@@ -7,6 +7,7 @@
7
7
  * - Register commands, keyboard shortcuts, and CLI flags
8
8
  * - Interact with the user via UI primitives
9
9
  */
10
+ import type { InputOrigin } from "../input-origin.ts";
10
11
  import type { UploadRedaction, UploadRedactionOptions } from "../share/upload-redaction.ts";
11
12
  import type { ContextEvent } from "./context-event.ts";
12
13
  export type { ContextEvent } from "./context-event.ts";
@@ -2507,7 +2508,8 @@ export interface ExtensionRuntimeState {
2507
2508
  */
2508
2509
  export interface ExtensionActions {
2509
2510
  sendMessage: SendMessageHandler;
2510
- sendUserMessage: SendUserMessageHandler;
2511
+ /** The runner passes the origin it captured when the extension called `sendUserMessage` (a person's answer is `person`). */
2512
+ sendUserMessage: (content: Parameters<SendUserMessageHandler>[0], options?: Parameters<SendUserMessageHandler>[1], inputOrigin?: InputOrigin) => void;
2511
2513
  appendEntry: AppendEntryHandler;
2512
2514
  setSessionName: SetSessionNameHandler;
2513
2515
  getSessionName: GetSessionNameHandler;
@@ -1,6 +1,7 @@
1
1
  import type { AgentMessage } from "@earendil-works/pi-agent-core";
2
2
  import type { AssistantMessage, ImageContent, Message, SystemMessage, TextContent, ThinkingSelection, ToolResultMessage, Usage, UserMessage } from "@earendil-works/pi-ai";
3
3
  import { EventLoopSlicer } from "./event-loop-slicer.ts";
4
+ import type { InputSource } from "./extensions/types.ts";
4
5
  import type { ModelChangeOrigin, ModelChangeSource } from "./model-change-origin.ts";
5
6
  import type { RepositoryIdentity } from "./repository-identity.ts";
6
7
  import { type SessionListProgress } from "./session-discovery.ts";
@@ -53,6 +54,12 @@ export interface SessionEntryBase {
53
54
  export interface SessionMessageEntry extends SessionEntryBase {
54
55
  type: "message";
55
56
  message: AgentMessage;
57
+ /**
58
+ * Where a user message came from: the `source` its `input` event reported. Stored on the entry, never on
59
+ * the message, so it stays in the session record and out of provider requests. Absent on every other
60
+ * entry and on user input that ran no `input` event (restored, moved or edited input): unknown origin.
61
+ */
62
+ inputSource?: InputSource;
56
63
  }
57
64
  export interface ThinkingLevelChangeEntry extends SessionEntryBase {
58
65
  type: "thinking_level_change";
@@ -480,7 +487,9 @@ export declare class SessionManager {
480
487
  * instead of a full JSON copy (see `_shareMessage` for when it falls back). `appendMessage` keeps the
481
488
  * full copy: a caller may still change its object after appending.
482
489
  */
483
- appendOwnedMessage(message: Message | CustomMessage | BashExecutionMessage): string;
490
+ appendOwnedMessage(message: Message | CustomMessage | BashExecutionMessage, options?: {
491
+ readonly inputSource?: InputSource | undefined;
492
+ }): string;
484
493
  private _appendMessage;
485
494
  /** Runtime append position for this exact persisted message object, if known. */
486
495
  getMessageEntryPosition(message: AgentMessage): Readonly<{
@@ -1369,16 +1369,17 @@ export class SessionManager {
1369
1369
  * instead of a full JSON copy (see `_shareMessage` for when it falls back). `appendMessage` keeps the
1370
1370
  * full copy: a caller may still change its object after appending.
1371
1371
  */
1372
- appendOwnedMessage(message) {
1373
- return this._appendMessage(message, true);
1372
+ appendOwnedMessage(message, options) {
1373
+ return this._appendMessage(message, true, options?.inputSource);
1374
1374
  }
1375
- _appendMessage(message, owned) {
1375
+ _appendMessage(message, owned, inputSource) {
1376
1376
  const entry = {
1377
1377
  type: "message",
1378
1378
  id: generateId(this.idsInUse),
1379
1379
  parentId: this.leafId,
1380
1380
  timestamp: new Date().toISOString(),
1381
1381
  message,
1382
+ ...(inputSource === undefined ? {} : { inputSource }),
1382
1383
  };
1383
1384
  this._appendEntry(entry, owned ? message : undefined);
1384
1385
  const order = this.entryOrdersById.get(entry.id);
@@ -21,6 +21,9 @@ export interface TerminalSettings {
21
21
  monitorMaxLinesPerInjection?: number;
22
22
  monitorMaxCharsPerInjection?: number;
23
23
  monitorWakeBudget?: number;
24
+ monitorWakesPerHour?: number;
25
+ monitorRunawayLinesPerMinute?: number;
26
+ monitorRunawayMinutes?: number;
24
27
  maxDurableMonitors?: number | "unlimited";
25
28
  }
26
29
  export interface BranchSummarySettings {
package/dist/index.d.ts CHANGED
@@ -37,7 +37,7 @@ export { UnknownCommandError, type UnknownCommandReason } from "./core/unknown-c
37
37
  export { type ModelRoute, type ModelRouteReason, type ModelRouteRequest, VIRTUAL_MODEL_STATE_ENTRY, type VirtualModelDefinition, type VirtualModelStateData, } from "./core/virtual-models.ts";
38
38
  export { connectWebViewService, type WebViewServiceConnection } from "./core/webview/webview-broker.ts";
39
39
  export { type MainOptions, main } from "./main.ts";
40
- export { classifyEndpointLiveness, createHostDaemonPaths, DEFAULT_HOST_LAUNCH_SPEC, daemonDirectoryName, decideHostAction, ENDPOINT_REGISTRY_VERSION, type EndpointKind, type EndpointLiveness, type EnsuredHost, type EnsureHostOptions, endpointProbeTimeoutMs, ensureHost, GENERATION_HANDOFF_CAPABILITY, gcHostEndpoints, type HandoffHostOptions, type HandoffRefusal, type HandoffResult, HOST_EXIT_ERROR, HOST_EXIT_FALLBACK, HOST_EXIT_OK, HOST_EXIT_REFUSED, HOST_EXIT_USAGE, HOST_PROTOCOL_VERSION, type HostAction, type HostDaemonPaths, type HostDecision, type HostDecisionClient, type HostDecisionPolicy, type HostDecisionWarning, type HostEndpointEntry, type HostEndpointIdentitySource, type HostEndpointStatus, HostEnsureRefusedError, type HostGcOptions, type HostGcResult, type HostGenerationRow, type HostLaunchSpec, type HostLaunchSpecCore, HostLaunchSpecError, type HostLaunchSpecRefusal, type HostOutcome, type HostProtocolInfo, type HostRefusalReason, type HostRequest, type HostSessionCounts, type HostStatusOptions, type HostStatusReport, type HostTarget, type HostUpgradePolicy, handoffHost, InteractiveMode, type InteractiveModeOptions, isTransportGoneError, type JsonAgentSessionEvent, listHostEndpoints, loadHostLaunchSpec, type ModelInfo, PINNED_HOST_CLIENT_CAPABILITIES, type PrintModeOptions, type ProbeHostOptions, parseHostLaunchSpec, parseHostProtocolInfo, probeHost, REQUIRED_HOST_CAPABILITIES, type ResolvedHostLaunchSpec, RpcClient, type RpcClientEvent, RpcClientOpenInFlightError, type RpcClientOptions, type RpcCommand, type RpcEvalCell, type RpcEvalCellSnapshot, type RpcEventListener, type RpcExtensionEvent, type RpcExtensionUIRequest, type RpcExtensionUIResponse, type RpcResponse, type RpcSessionState, RpcTransportGoneError, readAllHostStatus, readHostStatus, runHostRequest, runPrintMode, runRpcMode, type ShardKind, type StopHostOptions, type StopHostResult, shardKey, shardSocketPath, shardSocketPathForKey, stopHost, TUI_PROBE_TIMEOUT_MS, } from "./modes/index.ts";
40
+ export { classifyEndpointLiveness, createHostDaemonPaths, DEFAULT_HOST_LAUNCH_SPEC, daemonDirectoryName, decideHostAction, ENDPOINT_REGISTRY_VERSION, type EndpointKind, type EndpointLiveness, type EnsuredHost, type EnsureHostOptions, endpointProbeTimeoutMs, ensureHost, GENERATION_HANDOFF_CAPABILITY, gcHostEndpoints, type HandoffHostOptions, type HandoffRefusal, type HandoffResult, HOST_EXIT_ERROR, HOST_EXIT_FALLBACK, HOST_EXIT_OK, HOST_EXIT_REFUSED, HOST_EXIT_USAGE, HOST_PROTOCOL_VERSION, type HostAction, type HostDaemonPaths, type HostDecision, type HostDecisionClient, type HostDecisionPolicy, type HostDecisionWarning, type HostEndpointEntry, type HostEndpointIdentitySource, type HostEndpointStatus, HostEnsureRefusedError, type HostGcOptions, type HostGcResult, type HostGenerationRow, type HostHolder, type HostLaunchSpec, type HostLaunchSpecCore, HostLaunchSpecError, type HostLaunchSpecRefusal, type HostOutcome, type HostProtocolInfo, type HostRefusalReason, type HostRequest, type HostSessionCounts, type HostStatusOptions, type HostStatusReport, type HostTarget, type HostUpgradePolicy, handoffHost, InteractiveMode, type InteractiveModeOptions, isTransportGoneError, type JsonAgentSessionEvent, listHostEndpoints, loadHostLaunchSpec, type ModelInfo, PINNED_HOST_CLIENT_CAPABILITIES, type PrintModeOptions, type ProbeHostOptions, parseHostLaunchSpec, parseHostProtocolInfo, probeHost, REQUIRED_HOST_CAPABILITIES, type ResolvedHostLaunchSpec, RpcClient, type RpcClientEvent, RpcClientOpenInFlightError, type RpcClientOptions, type RpcCommand, type RpcEvalCell, type RpcEvalCellSnapshot, type RpcEventListener, type RpcExtensionEvent, type RpcExtensionUIRequest, type RpcExtensionUIResponse, type RpcResponse, type RpcSessionState, RpcTransportGoneError, readAllHostStatus, readHostStatus, runHostRequest, runPrintMode, runRpcMode, type ShardKind, type StopHostOptions, type StopHostResult, shardKey, shardSocketPath, shardSocketPathForKey, stopHost, TUI_PROBE_TIMEOUT_MS, } from "./modes/index.ts";
41
41
  export { ArminComponent, AssistantMessageComponent, BashExecutionComponent, BorderedLoader, BranchSummaryMessageComponent, CompactionSummaryMessageComponent, CustomEditor, type CustomEditorOptions, CustomMessageComponent, DynamicBorder, ExtensionEditorComponent, ExtensionInputComponent, ExtensionSelectorComponent, FooterComponent, keyHint, keyText, LoginDialogComponent, ModelSelectorComponent, OAuthSelectorComponent, type RenderDiffOptions, rawKeyHint, renderDiff, SessionSelectorComponent, type SettingsCallbacks, type SettingsConfig, SettingsSelectorComponent, ShowImagesSelectorComponent, SkillInvocationMessageComponent, ThemeSelectorComponent, ThinkingSelectorComponent, ToolExecutionComponent, type ToolExecutionOptions, TreeSelectorComponent, truncateToVisualLines, UserMessageComponent, UserMessageSelectorComponent, type VisualTruncateResult, } from "./modes/interactive/components/index.ts";
42
42
  export { getLanguageFromPath, getMarkdownTheme, getSelectListTheme, getSettingsListTheme, highlightCode, initTheme, Theme, type ThemeAppearance, type ThemeBg, type ThemeColor, type ThemeStyle, type ThemeToken, } from "./modes/interactive/theme/theme.ts";
43
43
  export { collectOrphanedChildren } from "./modes/rpc/child-reaper.ts";
package/dist/main.d.ts CHANGED
@@ -8,12 +8,26 @@ import { type Args } from "./cli/args.ts";
8
8
  import { createStartupLoadingIndicator } from "./cli/startup-loading-indicator.ts";
9
9
  import { type ToolRuntimeKind } from "./cli/tool-runtime.ts";
10
10
  import { type AgentSessionRuntimeDiagnostic } from "./core/agent-session-services.ts";
11
+ import { AuthStorage } from "./core/auth-storage.ts";
11
12
  import type { InlineExtension } from "./core/extensions/types.ts";
12
13
  import { ModelRuntime } from "./core/model-runtime.ts";
13
14
  import { type AppMode } from "./core/project-trust.ts";
14
15
  import { SessionManager } from "./core/session-manager.ts";
15
16
  import { SettingsManager } from "./core/settings-manager.ts";
16
17
  import type { PreparableRuntimeFactory } from "./modes/rpc/host-warm.ts";
18
+ /**
19
+ * Auth problems for a run's diagnostics. Each credential notice appears once per process; with
20
+ * `persistSeen` (a run a person reads) it is also recorded as seen, so later runs stay quiet until
21
+ * the problem changes (senpi#3486).
22
+ */
23
+ export declare function collectAuthDiagnostics(authStorage: AuthStorage, context: string, options: {
24
+ persistSeen: boolean;
25
+ }): AgentSessionRuntimeDiagnostic[];
26
+ /** The TUI shows auth problems at startup; credential notices are marked seen once shown (senpi#3486). */
27
+ export declare function interactiveAuthNotices(authStorage: AuthStorage): {
28
+ messages: string[];
29
+ markSeen: () => void;
30
+ };
17
31
  export declare function collectExtensionLoadDiagnostics(errors: readonly {
18
32
  path: string;
19
33
  error: string;
package/dist/main.js CHANGED
@@ -74,11 +74,27 @@ import { isLocalPath, normalizePath, resolvePath } from "./utils/paths.js";
74
74
  import { readPipedStdin } from "./utils/piped-stdin.js";
75
75
  import { cleanupWindowsSelfUpdateQuarantine } from "./utils/windows-self-update.js";
76
76
  const EXTENSION_LOAD_FAILURE_HINT = `Hint: Start without extensions using "${APP_NAME} -ne".`;
77
- function collectAuthDiagnostics(authStorage, context) {
78
- return authStorage.drainErrors().map((error) => ({
79
- type: "warning",
80
- message: `(${context}, auth storage) ${error.message}`,
81
- }));
77
+ /**
78
+ * Auth problems for a run's diagnostics. Each credential notice appears once per process; with
79
+ * `persistSeen` (a run a person reads) it is also recorded as seen, so later runs stay quiet until
80
+ * the problem changes (senpi#3486).
81
+ */
82
+ export function collectAuthDiagnostics(authStorage, context, options) {
83
+ return [
84
+ ...authStorage.drainErrors().map((error) => error.message),
85
+ ...authStorage.takePendingNotices(options).map((notice) => notice.message),
86
+ ].map((message) => ({ type: "warning", message: `(${context}, auth storage) ${message}` }));
87
+ }
88
+ /** The TUI shows auth problems at startup; credential notices are marked seen once shown (senpi#3486). */
89
+ export function interactiveAuthNotices(authStorage) {
90
+ const pending = authStorage.takePendingNotices({ persistSeen: false });
91
+ return {
92
+ messages: [
93
+ ...authStorage.drainErrors().map((error) => error.message),
94
+ ...pending.map((notice) => notice.message),
95
+ ],
96
+ markSeen: () => authStorage.markNoticesSeen(pending.map((notice) => notice.id)),
97
+ };
82
98
  }
83
99
  export function collectExtensionLoadDiagnostics(errors) {
84
100
  return errors.map(({ path, error }) => ({
@@ -1020,7 +1036,7 @@ export async function main(args, options) {
1020
1036
  reportDiagnostics([
1021
1037
  ...services.diagnostics,
1022
1038
  ...collectSettingsDiagnosticsWithContext(services.settingsManager, "model listing"),
1023
- ...collectAuthDiagnostics(services.authStorage, "model listing"),
1039
+ ...collectAuthDiagnostics(services.authStorage, "model listing", { persistSeen: true }),
1024
1040
  ]);
1025
1041
  const searchPattern = typeof parsed.listModels === "string" ? parsed.listModels : undefined;
1026
1042
  await printThenExit(() => listModels(services.modelRuntime, searchPattern), 0);
@@ -1216,7 +1232,7 @@ export async function main(args, options) {
1216
1232
  type: "warning",
1217
1233
  message: diagnostic.path ? `${diagnostic.path}: ${diagnostic.message}` : diagnostic.message,
1218
1234
  })));
1219
- reportDiagnostics(collectAuthDiagnostics(services.authStorage, "runtime creation"));
1235
+ reportDiagnostics(collectAuthDiagnostics(services.authStorage, "runtime creation", { persistSeen: appMode !== "rpc" }));
1220
1236
  }
1221
1237
  if (runtime.diagnostics.some((diagnostic) => diagnostic.type === "error")) {
1222
1238
  if (runtime.diagnostics.some((diagnostic) => diagnostic.message.includes("Failed to load extension"))) {
@@ -1266,6 +1282,7 @@ export async function main(args, options) {
1266
1282
  const { InteractiveMode } = await import("./modes/interactive/interactive-mode.js");
1267
1283
  const interactiveMode = new InteractiveMode(runtime, {
1268
1284
  migratedProviders,
1285
+ authNotices: interactiveAuthNotices(services.authStorage),
1269
1286
  legacyPiEditNotice: legacyPiEditStartupNotice(),
1270
1287
  modelFallbackMessage,
1271
1288
  autoTrustOnReloadCwd,
@@ -1308,7 +1325,7 @@ export async function main(args, options) {
1308
1325
  initialMessage,
1309
1326
  initialImages,
1310
1327
  });
1311
- reportDiagnostics(collectAuthDiagnostics(services.authStorage, "print mode"));
1328
+ reportDiagnostics(collectAuthDiagnostics(services.authStorage, "print mode", { persistSeen: true }));
1312
1329
  stopChildReaper();
1313
1330
  stopThemeWatcher();
1314
1331
  restoreStdout();