aicodeman 1.43.0 → 1.44.0
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/README.md +3 -2
- package/dist/cli-agent.d.ts +3 -3
- package/dist/cli-agent.d.ts.map +1 -1
- package/dist/cli-agent.js +8 -6
- package/dist/cli-agent.js.map +1 -1
- package/dist/config/cli-registry/stock.d.ts.map +1 -1
- package/dist/config/cli-registry/stock.js +8 -3
- package/dist/config/cli-registry/stock.js.map +1 -1
- package/dist/config/cli-registry/types.d.ts +2 -2
- package/dist/config/map-limits.d.ts +13 -1
- package/dist/config/map-limits.d.ts.map +1 -1
- package/dist/config/map-limits.js +28 -1
- package/dist/config/map-limits.js.map +1 -1
- package/dist/config-backup.d.ts +279 -0
- package/dist/config-backup.d.ts.map +1 -0
- package/dist/config-backup.js +794 -0
- package/dist/config-backup.js.map +1 -0
- package/dist/cron/cron-service.d.ts +5 -5
- package/dist/cron/cron-service.js +6 -6
- package/dist/cron/cron-service.js.map +1 -1
- package/dist/custom-model-hosts.d.ts +16 -0
- package/dist/custom-model-hosts.d.ts.map +1 -1
- package/dist/custom-model-hosts.js.map +1 -1
- package/dist/hooks-config.d.ts +15 -0
- package/dist/hooks-config.d.ts.map +1 -1
- package/dist/hooks-config.js +35 -0
- package/dist/hooks-config.js.map +1 -1
- package/dist/intent-store.d.ts +2 -0
- package/dist/intent-store.d.ts.map +1 -1
- package/dist/intent-store.js +9 -0
- package/dist/intent-store.js.map +1 -1
- package/dist/mux-interface.d.ts +21 -4
- package/dist/mux-interface.d.ts.map +1 -1
- package/dist/prompt-cache-keepwarm.d.ts +193 -0
- package/dist/prompt-cache-keepwarm.d.ts.map +1 -0
- package/dist/prompt-cache-keepwarm.js +359 -0
- package/dist/prompt-cache-keepwarm.js.map +1 -0
- package/dist/respawn-controller.d.ts.map +1 -1
- package/dist/respawn-controller.js +6 -5
- package/dist/respawn-controller.js.map +1 -1
- package/dist/session-auto-ops.d.ts.map +1 -1
- package/dist/session-auto-ops.js +6 -1
- package/dist/session-auto-ops.js.map +1 -1
- package/dist/session-cli-registry-bridge.d.ts +13 -0
- package/dist/session-cli-registry-bridge.d.ts.map +1 -1
- package/dist/session-cli-registry-bridge.js +20 -3
- package/dist/session-cli-registry-bridge.js.map +1 -1
- package/dist/session-submit-verifier.d.ts +19 -0
- package/dist/session-submit-verifier.d.ts.map +1 -1
- package/dist/session-submit-verifier.js +88 -0
- package/dist/session-submit-verifier.js.map +1 -1
- package/dist/session.d.ts +61 -5
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +112 -7
- package/dist/session.js.map +1 -1
- package/dist/state-store.d.ts +10 -0
- package/dist/state-store.d.ts.map +1 -1
- package/dist/state-store.js +35 -0
- package/dist/state-store.js.map +1 -1
- package/dist/tab-layout-service.d.ts +33 -0
- package/dist/tab-layout-service.d.ts.map +1 -1
- package/dist/tab-layout-service.js +75 -3
- package/dist/tab-layout-service.js.map +1 -1
- package/dist/tmux-manager.d.ts +50 -8
- package/dist/tmux-manager.d.ts.map +1 -1
- package/dist/tmux-manager.js +146 -30
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tmux-prefix-table.d.ts +54 -0
- package/dist/tmux-prefix-table.d.ts.map +1 -0
- package/dist/tmux-prefix-table.js +137 -0
- package/dist/tmux-prefix-table.js.map +1 -0
- package/dist/tui/tui-app.d.ts +9 -1
- package/dist/tui/tui-app.d.ts.map +1 -1
- package/dist/tui/tui-app.js +21 -8
- package/dist/tui/tui-app.js.map +1 -1
- package/dist/tui/tui-client.d.ts +3 -2
- package/dist/tui/tui-client.d.ts.map +1 -1
- package/dist/tui/tui-client.js +3 -2
- package/dist/tui/tui-client.js.map +1 -1
- package/dist/types/app-state.d.ts +6 -0
- package/dist/types/app-state.d.ts.map +1 -1
- package/dist/types/app-state.js.map +1 -1
- package/dist/types/session.d.ts +40 -1
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/session.js.map +1 -1
- package/dist/usage-telemetry.d.ts +15 -0
- package/dist/usage-telemetry.d.ts.map +1 -1
- package/dist/usage-telemetry.js +45 -2
- package/dist/usage-telemetry.js.map +1 -1
- package/dist/utils/copilot-plan-usage.d.ts +46 -0
- package/dist/utils/copilot-plan-usage.d.ts.map +1 -0
- package/dist/utils/copilot-plan-usage.js +95 -0
- package/dist/utils/copilot-plan-usage.js.map +1 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/single-line.d.ts +22 -0
- package/dist/utils/single-line.d.ts.map +1 -0
- package/dist/utils/single-line.js +30 -0
- package/dist/utils/single-line.js.map +1 -0
- package/dist/web/approval-inbox.d.ts +6 -0
- package/dist/web/approval-inbox.d.ts.map +1 -1
- package/dist/web/approval-inbox.js +2 -2
- package/dist/web/approval-inbox.js.map +1 -1
- package/dist/web/plan-usage-latest.d.ts +2 -1
- package/dist/web/plan-usage-latest.d.ts.map +1 -1
- package/dist/web/plan-usage-latest.js +12 -2
- package/dist/web/plan-usage-latest.js.map +1 -1
- package/dist/web/ports/index.d.ts +1 -0
- package/dist/web/ports/index.d.ts.map +1 -1
- package/dist/web/ports/keep-warm-port.d.ts +9 -0
- package/dist/web/ports/keep-warm-port.d.ts.map +1 -0
- package/dist/web/ports/keep-warm-port.js +6 -0
- package/dist/web/ports/keep-warm-port.js.map +1 -0
- package/dist/web/ports/session-port.d.ts +3 -1
- package/dist/web/ports/session-port.d.ts.map +1 -1
- package/dist/web/public/admin-ui.js.gz +0 -0
- package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
- package/dist/web/public/app.9314ee60.js +43 -0
- package/dist/web/public/app.9314ee60.js.br +0 -0
- package/dist/web/public/app.9314ee60.js.gz +0 -0
- package/dist/web/public/approvals-ui.js.gz +0 -0
- package/dist/web/public/{constants.e5e588db.js → constants.4866ecc3.js} +49 -1
- package/dist/web/public/constants.4866ecc3.js.br +0 -0
- package/dist/web/public/constants.4866ecc3.js.gz +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/entrance-animations.js.gz +0 -0
- package/dist/web/public/git-status-ui.js.gz +0 -0
- package/dist/web/public/home-sessions.js.gz +0 -0
- package/dist/web/public/host-wake-ui.js.gz +0 -0
- package/dist/web/public/i18n.22ca902b.js +2 -0
- package/dist/web/public/i18n.22ca902b.js.br +0 -0
- package/dist/web/public/i18n.22ca902b.js.gz +0 -0
- package/dist/web/public/image-input.941386e7.js.gz +0 -0
- package/dist/web/public/index.html +99 -14
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
- package/dist/web/public/{keyboard-accessory.c283e827.js → keyboard-accessory.4e8b20ac.js} +23 -5
- package/dist/web/public/keyboard-accessory.4e8b20ac.js.br +0 -0
- package/dist/web/public/keyboard-accessory.4e8b20ac.js.gz +0 -0
- package/dist/web/public/{mobile-handlers.0ef9c311.js → mobile-handlers.44adaa07.js} +48 -16
- package/dist/web/public/mobile-handlers.44adaa07.js.br +0 -0
- package/dist/web/public/mobile-handlers.44adaa07.js.gz +0 -0
- package/dist/web/public/mobile-ime-preview.2dbaf262.js.gz +0 -0
- package/dist/web/public/mobile-overview.js.gz +0 -0
- package/dist/web/public/mobile.12fb18de.css +1 -0
- package/dist/web/public/mobile.12fb18de.css.br +0 -0
- package/dist/web/public/mobile.12fb18de.css.gz +0 -0
- package/dist/web/public/notification-manager.feb82c04.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/{panels-ui.b7bfb9da.js → panels-ui.cbb24ba1.js} +65 -65
- package/dist/web/public/panels-ui.cbb24ba1.js.br +0 -0
- package/dist/web/public/panels-ui.cbb24ba1.js.gz +0 -0
- package/dist/web/public/ralph-panel.b350e2ef.js.gz +0 -0
- package/dist/web/public/ralph-wizard.b6c0dd19.js.gz +0 -0
- package/dist/web/public/readmymind-ui.js.gz +0 -0
- package/dist/web/public/reboot-restore-ui.js.gz +0 -0
- package/dist/web/public/respawn-ui.d7adcf6a.js.gz +0 -0
- package/dist/web/public/sanitize-html.e7147e76.js.gz +0 -0
- package/dist/web/public/session-lineage.js.gz +0 -0
- package/dist/web/public/{session-ui.be076c88.js → session-ui.9c1f009b.js} +7 -3
- package/dist/web/public/session-ui.9c1f009b.js.br +0 -0
- package/dist/web/public/session-ui.9c1f009b.js.gz +0 -0
- package/dist/web/public/settings-ui.fea9ddae.js +106 -0
- package/dist/web/public/settings-ui.fea9ddae.js.br +0 -0
- package/dist/web/public/settings-ui.fea9ddae.js.gz +0 -0
- package/dist/web/public/spreadsheet-preview-worker.js.gz +0 -0
- package/dist/web/public/spreadsheet-preview.75c751de.js.gz +0 -0
- package/dist/web/public/spreadsheet-xlsx-core.js.gz +0 -0
- package/dist/web/public/styles.e096fcd6.css +1 -0
- package/dist/web/public/styles.e096fcd6.css.br +0 -0
- package/dist/web/public/styles.e096fcd6.css.gz +0 -0
- package/dist/web/public/subagent-windows.07277a58.js.gz +0 -0
- package/dist/web/public/sw.js +2 -2
- package/dist/web/public/sw.js.br +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/tab-layout-browser.2d6189f7.js.gz +0 -0
- package/dist/web/public/tab-rail-resize.7157e0bd.js.gz +0 -0
- package/dist/web/public/terminal-keycode229-recovery.af09f99a.js.gz +0 -0
- package/dist/web/public/terminal-split.js.gz +0 -0
- package/dist/web/public/terminal-tile.js +41 -22
- package/dist/web/public/terminal-tile.js.br +0 -0
- package/dist/web/public/terminal-tile.js.gz +0 -0
- package/dist/web/public/terminal-ui.68dbd352.js +2 -0
- package/dist/web/public/terminal-ui.68dbd352.js.br +0 -0
- package/dist/web/public/terminal-ui.68dbd352.js.gz +0 -0
- package/dist/web/public/tile-grid.js.gz +0 -0
- package/dist/web/public/ultracode-panel.js.gz +0 -0
- package/dist/web/public/ultracode-windows.js.gz +0 -0
- package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
- package/dist/web/public/vendor/exceljs.min.js.gz +0 -0
- package/dist/web/public/vendor/fflate.min.js.gz +0 -0
- package/dist/web/public/vendor/marked.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-predictive-echo.ae5e3d5d.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.0b69623c.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/voice-input.2342c37b.js.gz +0 -0
- package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
- package/dist/web/public/webview-tabs.js.gz +0 -0
- package/dist/web/public/whats-new.json +9 -9
- package/dist/web/routes/admin-routes.d.ts +2 -1
- package/dist/web/routes/admin-routes.d.ts.map +1 -1
- package/dist/web/routes/admin-routes.js +3 -0
- package/dist/web/routes/admin-routes.js.map +1 -1
- package/dist/web/routes/case-routes.d.ts +2 -2
- package/dist/web/routes/case-routes.d.ts.map +1 -1
- package/dist/web/routes/case-routes.js +81 -3
- package/dist/web/routes/case-routes.js.map +1 -1
- package/dist/web/routes/config-backup-routes.d.ts +54 -0
- package/dist/web/routes/config-backup-routes.d.ts.map +1 -0
- package/dist/web/routes/config-backup-routes.js +268 -0
- package/dist/web/routes/config-backup-routes.js.map +1 -0
- package/dist/web/routes/custom-model-routes.d.ts +17 -1
- package/dist/web/routes/custom-model-routes.d.ts.map +1 -1
- package/dist/web/routes/custom-model-routes.js +118 -13
- package/dist/web/routes/custom-model-routes.js.map +1 -1
- package/dist/web/routes/hook-event-routes.d.ts.map +1 -1
- package/dist/web/routes/hook-event-routes.js +26 -5
- package/dist/web/routes/hook-event-routes.js.map +1 -1
- package/dist/web/routes/index.d.ts +1 -0
- package/dist/web/routes/index.d.ts.map +1 -1
- package/dist/web/routes/index.js +1 -0
- package/dist/web/routes/index.js.map +1 -1
- package/dist/web/routes/ralph-routes.js +2 -2
- package/dist/web/routes/ralph-routes.js.map +1 -1
- package/dist/web/routes/reboot-restore-routes.d.ts.map +1 -1
- package/dist/web/routes/reboot-restore-routes.js +2 -0
- package/dist/web/routes/reboot-restore-routes.js.map +1 -1
- package/dist/web/routes/session-routes.d.ts +2 -2
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +86 -15
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/routes/status-telemetry-routes.d.ts +2 -2
- package/dist/web/routes/status-telemetry-routes.d.ts.map +1 -1
- package/dist/web/routes/status-telemetry-routes.js +8 -4
- package/dist/web/routes/status-telemetry-routes.js.map +1 -1
- package/dist/web/routes/system-routes.d.ts +2 -2
- package/dist/web/routes/system-routes.d.ts.map +1 -1
- package/dist/web/routes/system-routes.js +7 -0
- package/dist/web/routes/system-routes.js.map +1 -1
- package/dist/web/routes/webview-routes.d.ts.map +1 -1
- package/dist/web/routes/webview-routes.js +1 -0
- package/dist/web/routes/webview-routes.js.map +1 -1
- package/dist/web/schemas.d.ts +19 -0
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +31 -2
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/server.d.ts +12 -2
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +116 -5
- package/dist/web/server.js.map +1 -1
- package/dist/web/session-listener-wiring.d.ts +6 -0
- package/dist/web/session-listener-wiring.d.ts.map +1 -1
- package/dist/web/session-listener-wiring.js +10 -3
- package/dist/web/session-listener-wiring.js.map +1 -1
- package/dist/web/sse-events.d.ts +5 -2
- package/dist/web/sse-events.d.ts.map +1 -1
- package/dist/web/sse-events.js +5 -2
- package/dist/web/sse-events.js.map +1 -1
- package/dist/web/webview-proxy.d.ts +8 -3
- package/dist/web/webview-proxy.d.ts.map +1 -1
- package/dist/web/webview-proxy.js +23 -5
- package/dist/web/webview-proxy.js.map +1 -1
- package/package.json +1 -1
- package/skills/codeman/SKILL.md +20 -9
- package/skills/codeman/preamble.sh +12 -2
- package/skills/codeman/reference/endpoints.md +8 -7
- package/skills/codeman/reference/recipes.md +1 -1
- package/skills/codeman/reference/verbs.md +21 -13
- package/dist/web/public/app.ae50029a.js +0 -43
- package/dist/web/public/app.ae50029a.js.br +0 -0
- package/dist/web/public/app.ae50029a.js.gz +0 -0
- package/dist/web/public/constants.e5e588db.js.br +0 -0
- package/dist/web/public/constants.e5e588db.js.gz +0 -0
- package/dist/web/public/i18n.8f291f06.js +0 -2
- package/dist/web/public/i18n.8f291f06.js.br +0 -0
- package/dist/web/public/i18n.8f291f06.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.c283e827.js.br +0 -0
- package/dist/web/public/keyboard-accessory.c283e827.js.gz +0 -0
- package/dist/web/public/mobile-handlers.0ef9c311.js.br +0 -0
- package/dist/web/public/mobile-handlers.0ef9c311.js.gz +0 -0
- package/dist/web/public/mobile.13514863.css +0 -1
- package/dist/web/public/mobile.13514863.css.br +0 -0
- package/dist/web/public/mobile.13514863.css.gz +0 -0
- package/dist/web/public/panels-ui.b7bfb9da.js.br +0 -0
- package/dist/web/public/panels-ui.b7bfb9da.js.gz +0 -0
- package/dist/web/public/session-ui.be076c88.js.br +0 -0
- package/dist/web/public/session-ui.be076c88.js.gz +0 -0
- package/dist/web/public/settings-ui.fc4cc50a.js +0 -97
- package/dist/web/public/settings-ui.fc4cc50a.js.br +0 -0
- package/dist/web/public/settings-ui.fc4cc50a.js.gz +0 -0
- package/dist/web/public/styles.dcfa907f.css +0 -1
- package/dist/web/public/styles.dcfa907f.css.br +0 -0
- package/dist/web/public/styles.dcfa907f.css.gz +0 -0
- package/dist/web/public/terminal-ui.ae01a7d1.js +0 -2
- package/dist/web/public/terminal-ui.ae01a7d1.js.br +0 -0
- package/dist/web/public/terminal-ui.ae01a7d1.js.gz +0 -0
package/dist/mux-interface.d.ts
CHANGED
|
@@ -39,9 +39,15 @@ export interface MuxSession {
|
|
|
39
39
|
ralphEnabled?: boolean;
|
|
40
40
|
/** Whether the current launch carries the statusLine exporter; set on every create and respawn. */
|
|
41
41
|
statusLine?: StatusLineExporter;
|
|
42
|
+
/**
|
|
43
|
+
* Whether the current launch carries `idleCompaction: false` in its `--settings` JSON (keep-warm
|
|
44
|
+
* on at launch, the exporter injected, Claude Code new enough); set on every create and respawn.
|
|
45
|
+
*/
|
|
46
|
+
idleCompactionOff?: boolean;
|
|
42
47
|
/**
|
|
43
48
|
* This record was rebuilt from the tmux socket rather than from Codeman's own
|
|
44
|
-
* bookkeeping, so everything on it but the name
|
|
49
|
+
* bookkeeping, so everything on it but the name, the pid and, when tmux
|
|
50
|
+
* reported a usable one, the directory is a guess. Its
|
|
45
51
|
* synthetic `restored-<fragment>` id cannot find the session's `state.json`
|
|
46
52
|
* entry either, which means a remote or docker session rediscovered this way
|
|
47
53
|
* arrives with no `remote`/`docker` metadata and looks local. Anything that
|
|
@@ -99,6 +105,10 @@ export interface CreateSessionOptions {
|
|
|
99
105
|
effort?: EffortLevel;
|
|
100
106
|
/** Claude advisor model, merged into the same `--settings` JSON (overridable via /advisor in-session) */
|
|
101
107
|
advisorModel?: string;
|
|
108
|
+
/** The session's own prompt-cache keep-warm choice (undefined = app default); decides the `idleCompaction` opt-out. */
|
|
109
|
+
keepWarm?: boolean;
|
|
110
|
+
/** Spawned by another session: it does not inherit the keep-warm app default (`keepWarmOn`). */
|
|
111
|
+
spawnedByAgent?: boolean;
|
|
102
112
|
/** tmux history-limit (scrollback lines) allocated when this session is created. */
|
|
103
113
|
historyLimit?: number;
|
|
104
114
|
/** Remote execution metadata for local tmux sessions wrapping SSH */
|
|
@@ -151,6 +161,10 @@ export interface RespawnPaneOptions {
|
|
|
151
161
|
effort?: EffortLevel;
|
|
152
162
|
/** Claude advisor model (preserved across respawns, merged into the same `--settings` JSON) */
|
|
153
163
|
advisorModel?: string;
|
|
164
|
+
/** The session's own prompt-cache keep-warm choice, re-read at every relaunch (see CreateSessionOptions). */
|
|
165
|
+
keepWarm?: boolean;
|
|
166
|
+
/** Spawned by another session (see CreateSessionOptions). */
|
|
167
|
+
spawnedByAgent?: boolean;
|
|
154
168
|
/** Original tmux history-limit retained for config parity; respawn cannot resize the existing pane. */
|
|
155
169
|
historyLimit?: number;
|
|
156
170
|
/** Remote execution metadata for local tmux sessions wrapping SSH */
|
|
@@ -344,12 +358,15 @@ export interface TerminalMultiplexer extends EventEmitter {
|
|
|
344
358
|
*/
|
|
345
359
|
captureActivePaneBuffer?(muxName: string, opts?: PaneCaptureOptions): string | null;
|
|
346
360
|
/**
|
|
347
|
-
* Plain text of the visible frame: no
|
|
348
|
-
* reconstruction
|
|
361
|
+
* Plain text of the visible frame: no cursor query, no repaint
|
|
362
|
+
* reconstruction, and no styles unless `styles` asks for them (SGR only, lines
|
|
363
|
+
* still `\n`-separated). Deliberately cheaper than `capturePaneBuffer` because idle
|
|
349
364
|
* detection calls it on a timer: it only needs to read what the CLI is
|
|
350
365
|
* currently rendering, never to replay it into an xterm. Returns null when the
|
|
351
366
|
* pane cannot be read.
|
|
352
367
|
*/
|
|
353
|
-
capturePaneText?(muxName: string, paneTarget?: string
|
|
368
|
+
capturePaneText?(muxName: string, paneTarget?: string, opts?: {
|
|
369
|
+
styles?: boolean;
|
|
370
|
+
}): string | null;
|
|
354
371
|
}
|
|
355
372
|
//# sourceMappingURL=mux-interface.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mux-interface.d.ts","sourceRoot":"","sources":["../src/mux-interface.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EACV,YAAY,EACZ,sBAAsB,EACtB,UAAU,EACV,UAAU,EACV,WAAW,EACX,cAAc,EACd,WAAW,EACX,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,QAAQ,EACR,UAAU,EACV,aAAa,EACb,cAAc,EACd,SAAS,EACT,aAAa,EACb,aAAa,EACb,QAAQ,EACR,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAEpB;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,yBAAyB;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,OAAO,EAAE,MAAM,CAAC;IAChB,kBAAkB;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,6BAA6B;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,wBAAwB;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,6FAA6F;IAC7F,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mBAAmB;IACnB,IAAI,EAAE,WAAW,CAAC;IAClB,oDAAoD;IACpD,QAAQ,EAAE,OAAO,CAAC;IAClB,sCAAsC;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,aAAa,CAAC,EAAE,sBAAsB,CAAC;IACvC,+CAA+C;IAC/C,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,mGAAmG;IACnG,UAAU,CAAC,EAAE,kBAAkB,CAAC;IAChC
|
|
1
|
+
{"version":3,"file":"mux-interface.d.ts","sourceRoot":"","sources":["../src/mux-interface.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Prompt-cache keep-warm: a one-line keep-alive turn shortly before an idle
|
|
3
|
+
* claude session's prompt cache would expire, so the next real prompt reads the conversation
|
|
4
|
+
* from cache instead of re-processing it at the cache-write rate.
|
|
5
|
+
*
|
|
6
|
+
* Why it exists: Claude Code gives a subscription's main conversation a one-hour cache and
|
|
7
|
+
* lets it expire after an hour idle. Measured on real sessions, nearly every gap over an
|
|
8
|
+
* hour went cold and the first turn back re-wrote the whole context at 2x; a keep-alive turn
|
|
9
|
+
* costs a cache read plus a one-word reply, a small fraction of the miss it prevents, and the
|
|
10
|
+
* payoff is concentrated in gaps of one to four hours, which is why there is a cap after the
|
|
11
|
+
* last real prompt rather than an indefinite loop.
|
|
12
|
+
*
|
|
13
|
+
* Codeman owns the idle policy for these sessions (Discussion #554): a launch with keep-warm
|
|
14
|
+
* on also carries `idleCompaction: false` in its one `--settings` JSON, so Claude Code's own
|
|
15
|
+
* idle compaction (which fires at 90% of the TTL, a minute before the nudge) cannot replace
|
|
16
|
+
* the conversation with a summary first. That is decided once per launch in
|
|
17
|
+
* `TmuxManager._resolveIdleCompactionOptOut()` and recorded on the mux record; a session
|
|
18
|
+
* switched on after launch is kept warm best-effort and says so.
|
|
19
|
+
*
|
|
20
|
+
* Where the facts come from: `Session.promptCache` (the statusline's `prompt_cache` object,
|
|
21
|
+
* see usage-telemetry.ts) carries `expires_at`, the TTL and the tokens at stake, so nothing
|
|
22
|
+
* here infers the lifetime from idle detection or the plan tier.
|
|
23
|
+
*
|
|
24
|
+
* Shape: `decideKeepWarm()` is the pure rulebook (every guard in one place, tested as a
|
|
25
|
+
* table); `PromptCacheKeepWarmService` owns the per-session timer and the ports it needs
|
|
26
|
+
* from the server. It never touches the session except through `beginKeepAliveTurn()` and
|
|
27
|
+
* `writeViaMux()`: the window `beginKeepAliveTurn()` opens is what keeps the nudge off the
|
|
28
|
+
* real-prompt clock and opens its stop hook and idle prompt pre-acknowledged
|
|
29
|
+
* (hook-event-routes.ts). The nudge is still a submit, so it moves `lastSubmitAt`.
|
|
30
|
+
*
|
|
31
|
+
* @module prompt-cache-keepwarm
|
|
32
|
+
*/
|
|
33
|
+
import type { KeepWarmStatus, PromptCacheStatus, StatusLineExporter } from './types/index.js';
|
|
34
|
+
/** The nudge. Under 100 chars on purpose: a longer burst reads as a paste to Claude Code. */
|
|
35
|
+
export declare const KEEP_WARM_NUDGE = "Codeman cache keep-alive: no action needed, reply with just \"ok\".";
|
|
36
|
+
/** Nudge this long before `expires_at`. The nudge's own request is what refreshes the timer. */
|
|
37
|
+
export declare const KEEP_WARM_LEAD_MS: number;
|
|
38
|
+
/** Re-check cadence while a transient guard (working, dialog, draft in the composer) defers. */
|
|
39
|
+
export declare const KEEP_WARM_RETRY_MS = 60000;
|
|
40
|
+
/** Give up this close to expiry: a turn takes seconds to land and the Enter may be re-pressed. */
|
|
41
|
+
export declare const KEEP_WARM_LAST_CHANCE_MS = 60000;
|
|
42
|
+
/** Below this many tokens at stake a cold rewrite is cheap and a turn is not worth it. */
|
|
43
|
+
export declare const KEEP_WARM_MIN_TOKENS = 20000;
|
|
44
|
+
/** A person typed this recently: they may be about to submit, which refreshes the cache for free. */
|
|
45
|
+
export declare const KEEP_WARM_RECENT_INPUT_MS: number;
|
|
46
|
+
/**
|
|
47
|
+
* Ceiling on the keep-alive window. The window normally closes on the turn's own idle prompt
|
|
48
|
+
* (Claude Code sends it about a minute after the turn ends, so a slow turn must not outlast a
|
|
49
|
+
* short fixed window) or earlier, at any other submit; the ceiling covers a turn whose idle
|
|
50
|
+
* prompt never arrives, such as a workspace without Codeman's hooks.
|
|
51
|
+
*/
|
|
52
|
+
export declare const KEEP_WARM_TURN_WINDOW_MS: number;
|
|
53
|
+
/** Default cap: hours after the last real prompt during which nudges are sent. */
|
|
54
|
+
export declare const DEFAULT_KEEP_WARM_MAX_HOURS = 2;
|
|
55
|
+
/**
|
|
56
|
+
* Whether keep-warm is on for a session: its own choice, else the app-wide default. A session
|
|
57
|
+
* an agent spawned (`parentSessionId`) does not inherit the default: a worker that finished
|
|
58
|
+
* its task is idle exactly like one that will be prompted again, and most never are, so only
|
|
59
|
+
* its own switch keeps it warm. One function because the launch (the `idleCompaction`
|
|
60
|
+
* opt-out) and the service must agree.
|
|
61
|
+
*/
|
|
62
|
+
export declare function keepWarmOn(choice: boolean | undefined, appDefault: boolean, spawnedByAgent: boolean): boolean;
|
|
63
|
+
export interface KeepWarmFacts {
|
|
64
|
+
now: number;
|
|
65
|
+
/** The session's own choice; undefined = the app-wide default applies. */
|
|
66
|
+
choice: boolean | undefined;
|
|
67
|
+
appDefault: boolean;
|
|
68
|
+
/** Spawned by another session (`parentSessionId`); see `keepWarmOn`. */
|
|
69
|
+
spawnedByAgent: boolean;
|
|
70
|
+
/** Whether this launch carries the statusLine exporter; undefined when unknown. */
|
|
71
|
+
statusLine?: StatusLineExporter;
|
|
72
|
+
cache: PromptCacheStatus | null | undefined;
|
|
73
|
+
/** Idle by Codeman's own probe and not mid-turn. */
|
|
74
|
+
idle: boolean;
|
|
75
|
+
/** A pid and no recorded agent exit (`paneExit`: an exited agent's pane keeps its pid). */
|
|
76
|
+
paneAlive: boolean;
|
|
77
|
+
/** Auto-resume's pause, or a usage-limit message on screen whose reset is still ahead. */
|
|
78
|
+
limitPaused: boolean;
|
|
79
|
+
/** Respawn running, a Ralph loop active, a task assigned, or an agent long-polling the session. */
|
|
80
|
+
automationActive: boolean;
|
|
81
|
+
/** A permission, question or elicitation dialog is open (never the idle item). */
|
|
82
|
+
pendingApproval: boolean;
|
|
83
|
+
/** The composer line holds no text; null when the pane could not be read. */
|
|
84
|
+
composerEmpty: boolean | null;
|
|
85
|
+
/** Last keystroke a person sent, epoch ms; 0 when none seen. */
|
|
86
|
+
lastHumanInputAt: number;
|
|
87
|
+
/** Start of the keep-warm window: the last real (non-keep-alive) prompt, else the session start. */
|
|
88
|
+
budgetAnchorAt: number;
|
|
89
|
+
maxKeepWarmMs: number;
|
|
90
|
+
}
|
|
91
|
+
export type KeepWarmDecision = {
|
|
92
|
+
action: 'nudge';
|
|
93
|
+
} | {
|
|
94
|
+
action: 'wait';
|
|
95
|
+
until: number;
|
|
96
|
+
reason: string;
|
|
97
|
+
} | {
|
|
98
|
+
action: 'stop';
|
|
99
|
+
reason: string;
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* The rulebook. Hard stops first (nothing to do until the next cache report or a flag
|
|
103
|
+
* change), then the schedule, then the transient guards that defer by `KEEP_WARM_RETRY_MS`
|
|
104
|
+
* and never past the last-chance line. Pure: `now` is passed in.
|
|
105
|
+
*/
|
|
106
|
+
export declare function decideKeepWarm(f: KeepWarmFacts): KeepWarmDecision;
|
|
107
|
+
/** What the service needs from a session; `Session` satisfies it structurally. */
|
|
108
|
+
export interface KeepWarmSessionView {
|
|
109
|
+
readonly id: string;
|
|
110
|
+
readonly createdAt: number;
|
|
111
|
+
readonly pid: number | null;
|
|
112
|
+
/** Set when the agent exited in a pane that stays open (`remain-on-exit`); the pid stays. */
|
|
113
|
+
readonly paneExit?: unknown;
|
|
114
|
+
readonly parentSessionId?: string;
|
|
115
|
+
readonly promptCache: PromptCacheStatus | null;
|
|
116
|
+
readonly statusLineExporter?: StatusLineExporter;
|
|
117
|
+
/** Whether this launch carries `idleCompaction: false` (`MuxSession.idleCompactionOff`). */
|
|
118
|
+
readonly idleCompactionOptOut: boolean;
|
|
119
|
+
readonly keepWarmEnabled: boolean | undefined;
|
|
120
|
+
readonly isWorking: boolean;
|
|
121
|
+
readonly isLimitPaused: boolean;
|
|
122
|
+
readonly lastHumanInputAt: number;
|
|
123
|
+
/** Last submit that was not the keep-alive's own (persisted): the cap's clock. */
|
|
124
|
+
readonly lastRealPromptAt: number;
|
|
125
|
+
isIdle(): boolean;
|
|
126
|
+
beginKeepAliveTurn(windowMs: number): void;
|
|
127
|
+
/** Close the window `beginKeepAliveTurn()` opened, when the nudge never went out. */
|
|
128
|
+
endKeepAliveTurn(): void;
|
|
129
|
+
writeViaMux(data: string): Promise<boolean>;
|
|
130
|
+
}
|
|
131
|
+
export interface KeepWarmSettings {
|
|
132
|
+
/** App-wide default for sessions that have not chosen (`promptCacheKeepWarm`). */
|
|
133
|
+
keepWarmDefault: boolean;
|
|
134
|
+
/** Cap in hours after the last real prompt (`promptCacheKeepWarmMaxHours`). */
|
|
135
|
+
maxHours: number;
|
|
136
|
+
}
|
|
137
|
+
export interface KeepWarmPorts {
|
|
138
|
+
getSession(id: string): KeepWarmSessionView | undefined;
|
|
139
|
+
sessionIds(): Iterable<string>;
|
|
140
|
+
readSettings(): Promise<KeepWarmSettings>;
|
|
141
|
+
isAutomationActive(id: string): boolean;
|
|
142
|
+
hasPendingApproval(id: string): boolean;
|
|
143
|
+
/**
|
|
144
|
+
* Read the pane once a nudge is otherwise due: whether the composer is empty (null when it
|
|
145
|
+
* cannot be read), and whether a usage-limit message with a reset still ahead is on screen
|
|
146
|
+
* (auto-resume's own pause tracking only runs while auto-resume is on).
|
|
147
|
+
*/
|
|
148
|
+
readPane(session: KeepWarmSessionView): {
|
|
149
|
+
composerEmpty: boolean | null;
|
|
150
|
+
limitShown: boolean;
|
|
151
|
+
};
|
|
152
|
+
/** Store and broadcast the status (the server dedupes against the session's last one). */
|
|
153
|
+
publish(id: string, status: KeepWarmStatus): void;
|
|
154
|
+
log?(message: string): void;
|
|
155
|
+
}
|
|
156
|
+
/** The scheduler the routes see: one method per trigger, nothing else. */
|
|
157
|
+
export interface KeepWarmScheduler {
|
|
158
|
+
/** Re-evaluate a session: after a cache report, a flag change, or a settings change. Resolves once the status is published. */
|
|
159
|
+
schedule(sessionId: string): Promise<void>;
|
|
160
|
+
/** Re-read the app-wide default and cap, then re-evaluate every session (either one changed). */
|
|
161
|
+
rescheduleAll(): void;
|
|
162
|
+
/** The session is gone. */
|
|
163
|
+
remove(sessionId: string): void;
|
|
164
|
+
}
|
|
165
|
+
export declare class PromptCacheKeepWarmService implements KeepWarmScheduler {
|
|
166
|
+
private readonly ports;
|
|
167
|
+
private readonly timers;
|
|
168
|
+
private readonly tallies;
|
|
169
|
+
/** One evaluation per session; `remove()` and `stop()` cancel it at its next await. */
|
|
170
|
+
private readonly inFlight;
|
|
171
|
+
/** The evaluation running per session, and the one follow-up queued behind it. */
|
|
172
|
+
private readonly running;
|
|
173
|
+
private readonly followUps;
|
|
174
|
+
/**
|
|
175
|
+
* The default and cap, read once and kept: every statusline report schedules an evaluation,
|
|
176
|
+
* and reading settings.json on each would cost every install, the feature off included.
|
|
177
|
+
* `rescheduleAll()` drops it, and the settings PUT calls that whenever either key changes.
|
|
178
|
+
*/
|
|
179
|
+
private settings;
|
|
180
|
+
/** The last settings read failure logged, so one that persists is logged once; cleared by a good read. */
|
|
181
|
+
private settingsFailure;
|
|
182
|
+
private stopped;
|
|
183
|
+
constructor(ports: KeepWarmPorts);
|
|
184
|
+
schedule(sessionId: string): Promise<void>;
|
|
185
|
+
rescheduleAll(): void;
|
|
186
|
+
remove(sessionId: string): void;
|
|
187
|
+
stop(): void;
|
|
188
|
+
private readSettings;
|
|
189
|
+
private clearTimer;
|
|
190
|
+
private arm;
|
|
191
|
+
private evaluate;
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=prompt-cache-keepwarm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt-cache-keepwarm.d.ts","sourceRoot":"","sources":["../src/prompt-cache-keepwarm.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Prompt-cache keep-warm: a one-line keep-alive turn shortly before an idle
|
|
3
|
+
* claude session's prompt cache would expire, so the next real prompt reads the conversation
|
|
4
|
+
* from cache instead of re-processing it at the cache-write rate.
|
|
5
|
+
*
|
|
6
|
+
* Why it exists: Claude Code gives a subscription's main conversation a one-hour cache and
|
|
7
|
+
* lets it expire after an hour idle. Measured on real sessions, nearly every gap over an
|
|
8
|
+
* hour went cold and the first turn back re-wrote the whole context at 2x; a keep-alive turn
|
|
9
|
+
* costs a cache read plus a one-word reply, a small fraction of the miss it prevents, and the
|
|
10
|
+
* payoff is concentrated in gaps of one to four hours, which is why there is a cap after the
|
|
11
|
+
* last real prompt rather than an indefinite loop.
|
|
12
|
+
*
|
|
13
|
+
* Codeman owns the idle policy for these sessions (Discussion #554): a launch with keep-warm
|
|
14
|
+
* on also carries `idleCompaction: false` in its one `--settings` JSON, so Claude Code's own
|
|
15
|
+
* idle compaction (which fires at 90% of the TTL, a minute before the nudge) cannot replace
|
|
16
|
+
* the conversation with a summary first. That is decided once per launch in
|
|
17
|
+
* `TmuxManager._resolveIdleCompactionOptOut()` and recorded on the mux record; a session
|
|
18
|
+
* switched on after launch is kept warm best-effort and says so.
|
|
19
|
+
*
|
|
20
|
+
* Where the facts come from: `Session.promptCache` (the statusline's `prompt_cache` object,
|
|
21
|
+
* see usage-telemetry.ts) carries `expires_at`, the TTL and the tokens at stake, so nothing
|
|
22
|
+
* here infers the lifetime from idle detection or the plan tier.
|
|
23
|
+
*
|
|
24
|
+
* Shape: `decideKeepWarm()` is the pure rulebook (every guard in one place, tested as a
|
|
25
|
+
* table); `PromptCacheKeepWarmService` owns the per-session timer and the ports it needs
|
|
26
|
+
* from the server. It never touches the session except through `beginKeepAliveTurn()` and
|
|
27
|
+
* `writeViaMux()`: the window `beginKeepAliveTurn()` opens is what keeps the nudge off the
|
|
28
|
+
* real-prompt clock and opens its stop hook and idle prompt pre-acknowledged
|
|
29
|
+
* (hook-event-routes.ts). The nudge is still a submit, so it moves `lastSubmitAt`.
|
|
30
|
+
*
|
|
31
|
+
* @module prompt-cache-keepwarm
|
|
32
|
+
*/
|
|
33
|
+
/** The nudge. Under 100 chars on purpose: a longer burst reads as a paste to Claude Code. */
|
|
34
|
+
export const KEEP_WARM_NUDGE = 'Codeman cache keep-alive: no action needed, reply with just "ok".';
|
|
35
|
+
/** Nudge this long before `expires_at`. The nudge's own request is what refreshes the timer. */
|
|
36
|
+
export const KEEP_WARM_LEAD_MS = 5 * 60_000;
|
|
37
|
+
/** Re-check cadence while a transient guard (working, dialog, draft in the composer) defers. */
|
|
38
|
+
export const KEEP_WARM_RETRY_MS = 60_000;
|
|
39
|
+
/** Give up this close to expiry: a turn takes seconds to land and the Enter may be re-pressed. */
|
|
40
|
+
export const KEEP_WARM_LAST_CHANCE_MS = 60_000;
|
|
41
|
+
/** Below this many tokens at stake a cold rewrite is cheap and a turn is not worth it. */
|
|
42
|
+
export const KEEP_WARM_MIN_TOKENS = 20_000;
|
|
43
|
+
/** A person typed this recently: they may be about to submit, which refreshes the cache for free. */
|
|
44
|
+
export const KEEP_WARM_RECENT_INPUT_MS = 3 * 60_000;
|
|
45
|
+
/**
|
|
46
|
+
* Ceiling on the keep-alive window. The window normally closes on the turn's own idle prompt
|
|
47
|
+
* (Claude Code sends it about a minute after the turn ends, so a slow turn must not outlast a
|
|
48
|
+
* short fixed window) or earlier, at any other submit; the ceiling covers a turn whose idle
|
|
49
|
+
* prompt never arrives, such as a workspace without Codeman's hooks.
|
|
50
|
+
*/
|
|
51
|
+
export const KEEP_WARM_TURN_WINDOW_MS = 10 * 60_000;
|
|
52
|
+
/** Default cap: hours after the last real prompt during which nudges are sent. */
|
|
53
|
+
export const DEFAULT_KEEP_WARM_MAX_HOURS = 2;
|
|
54
|
+
/** Longest delay `setTimeout` honours; Node fires anything longer after 1 ms. */
|
|
55
|
+
const MAX_TIMER_MS = 2 ** 31 - 1;
|
|
56
|
+
/**
|
|
57
|
+
* Whether keep-warm is on for a session: its own choice, else the app-wide default. A session
|
|
58
|
+
* an agent spawned (`parentSessionId`) does not inherit the default: a worker that finished
|
|
59
|
+
* its task is idle exactly like one that will be prompted again, and most never are, so only
|
|
60
|
+
* its own switch keeps it warm. One function because the launch (the `idleCompaction`
|
|
61
|
+
* opt-out) and the service must agree.
|
|
62
|
+
*/
|
|
63
|
+
export function keepWarmOn(choice, appDefault, spawnedByAgent) {
|
|
64
|
+
return choice ?? (appDefault && !spawnedByAgent);
|
|
65
|
+
}
|
|
66
|
+
const stop = (reason) => ({ action: 'stop', reason });
|
|
67
|
+
const wait = (until, reason) => ({ action: 'wait', until, reason });
|
|
68
|
+
/**
|
|
69
|
+
* The rulebook. Hard stops first (nothing to do until the next cache report or a flag
|
|
70
|
+
* change), then the schedule, then the transient guards that defer by `KEEP_WARM_RETRY_MS`
|
|
71
|
+
* and never past the last-chance line. Pure: `now` is passed in.
|
|
72
|
+
*/
|
|
73
|
+
export function decideKeepWarm(f) {
|
|
74
|
+
if (!keepWarmOn(f.choice, f.appDefault, f.spawnedByAgent)) {
|
|
75
|
+
return stop(f.choice === undefined && f.appDefault
|
|
76
|
+
? 'an agent-spawned session follows the app-wide default only when switched on for itself'
|
|
77
|
+
: 'off');
|
|
78
|
+
}
|
|
79
|
+
// Only a launch that carries the exporter ever reports `prompt_cache`; the readout names the
|
|
80
|
+
// cause from `statusLineExporter`. This is also what stops a CLI without cache telemetry.
|
|
81
|
+
if (f.statusLine !== 'injected')
|
|
82
|
+
return stop(`no cache reports on this launch (${f.statusLine ?? 'unknown'})`);
|
|
83
|
+
const c = f.cache;
|
|
84
|
+
if (!c)
|
|
85
|
+
return stop('no cache report yet');
|
|
86
|
+
if (!c.warm || !c.expiresAt)
|
|
87
|
+
return stop('cache is cold; the next prompt rebuilds it');
|
|
88
|
+
// Compacted this gap (`/compact`, or Claude Code's own idle compaction on a launch without
|
|
89
|
+
// the opt-out): the full context is already gone, so keeping the summary warm buys little.
|
|
90
|
+
// The stamp is dropped at the next request, which re-opens the next gap.
|
|
91
|
+
if (c.compactedAt != null)
|
|
92
|
+
return stop('the context was compacted; resumes after the next prompt');
|
|
93
|
+
if (c.ttl === '5m') {
|
|
94
|
+
return stop('5-minute cache lifetime; a nudge every four minutes costs more than the miss it prevents');
|
|
95
|
+
}
|
|
96
|
+
if ((c.recacheTokensIfCold ?? 0) < KEEP_WARM_MIN_TOKENS)
|
|
97
|
+
return stop('context too small to be worth a turn');
|
|
98
|
+
if (!f.paneAlive)
|
|
99
|
+
return stop('no live pane');
|
|
100
|
+
if (f.limitPaused)
|
|
101
|
+
return stop('usage-limit pause');
|
|
102
|
+
if (f.now >= f.budgetAnchorAt + f.maxKeepWarmMs)
|
|
103
|
+
return stop('keep-warm window after the last prompt has passed');
|
|
104
|
+
const dueAt = c.expiresAt - KEEP_WARM_LEAD_MS;
|
|
105
|
+
if (f.now < dueAt)
|
|
106
|
+
return wait(dueAt, 'not due yet');
|
|
107
|
+
const lastChance = c.expiresAt - KEEP_WARM_LAST_CHANCE_MS;
|
|
108
|
+
if (f.now >= lastChance)
|
|
109
|
+
return stop('too close to expiry to land a turn');
|
|
110
|
+
const retryAt = Math.min(f.now + KEEP_WARM_RETRY_MS, lastChance);
|
|
111
|
+
if (!f.idle)
|
|
112
|
+
return wait(retryAt, 'session is working');
|
|
113
|
+
if (f.pendingApproval)
|
|
114
|
+
return wait(retryAt, 'a dialog is open');
|
|
115
|
+
// Transient, and nothing else re-triggers an idle session: an agent's long-poll waiter ends
|
|
116
|
+
// without a turn, and so does a capture that failed once.
|
|
117
|
+
if (f.automationActive)
|
|
118
|
+
return wait(retryAt, 'respawn, Ralph or an agent is driving the session');
|
|
119
|
+
if (f.composerEmpty === null)
|
|
120
|
+
return wait(retryAt, 'cannot read the composer');
|
|
121
|
+
if (!f.composerEmpty)
|
|
122
|
+
return wait(retryAt, 'the composer holds text');
|
|
123
|
+
if (f.lastHumanInputAt && f.now - f.lastHumanInputAt < KEEP_WARM_RECENT_INPUT_MS) {
|
|
124
|
+
return wait(retryAt, 'someone typed a moment ago');
|
|
125
|
+
}
|
|
126
|
+
return { action: 'nudge' };
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Whether a session could be nudged once the settings are known: its own switch is not off, its
|
|
130
|
+
* launch reports the cache, and the last report is warm. Only such a session has anything to
|
|
131
|
+
* lose while the settings cannot be read, so only it retries on its own.
|
|
132
|
+
*/
|
|
133
|
+
function couldNudge(s) {
|
|
134
|
+
return (s.keepWarmEnabled !== false &&
|
|
135
|
+
s.statusLineExporter === 'injected' &&
|
|
136
|
+
!!s.promptCache?.warm &&
|
|
137
|
+
!!s.promptCache.expiresAt);
|
|
138
|
+
}
|
|
139
|
+
export class PromptCacheKeepWarmService {
|
|
140
|
+
ports;
|
|
141
|
+
timers = new Map();
|
|
142
|
+
tallies = new Map();
|
|
143
|
+
/** One evaluation per session; `remove()` and `stop()` cancel it at its next await. */
|
|
144
|
+
inFlight = new Map();
|
|
145
|
+
/** The evaluation running per session, and the one follow-up queued behind it. */
|
|
146
|
+
running = new Map();
|
|
147
|
+
followUps = new Map();
|
|
148
|
+
/**
|
|
149
|
+
* The default and cap, read once and kept: every statusline report schedules an evaluation,
|
|
150
|
+
* and reading settings.json on each would cost every install, the feature off included.
|
|
151
|
+
* `rescheduleAll()` drops it, and the settings PUT calls that whenever either key changes.
|
|
152
|
+
*/
|
|
153
|
+
settings;
|
|
154
|
+
/** The last settings read failure logged, so one that persists is logged once; cleared by a good read. */
|
|
155
|
+
settingsFailure;
|
|
156
|
+
stopped = false;
|
|
157
|
+
constructor(ports) {
|
|
158
|
+
this.ports = ports;
|
|
159
|
+
}
|
|
160
|
+
schedule(sessionId) {
|
|
161
|
+
if (this.stopped)
|
|
162
|
+
return Promise.resolve();
|
|
163
|
+
const running = this.running.get(sessionId);
|
|
164
|
+
if (running) {
|
|
165
|
+
// A trigger landing mid-evaluation (a toggle while the nudge is written, a settings change)
|
|
166
|
+
// carries what that evaluation read too early, so one follow-up runs after it, shared by
|
|
167
|
+
// every trigger that arrives meanwhile.
|
|
168
|
+
let next = this.followUps.get(sessionId);
|
|
169
|
+
if (!next) {
|
|
170
|
+
next = running.then(() => {
|
|
171
|
+
// `remove()` drops the queued follow-up: a deleted session gets no evaluation.
|
|
172
|
+
if (this.followUps.get(sessionId) !== next)
|
|
173
|
+
return;
|
|
174
|
+
this.followUps.delete(sessionId);
|
|
175
|
+
return this.schedule(sessionId);
|
|
176
|
+
});
|
|
177
|
+
this.followUps.set(sessionId, next);
|
|
178
|
+
}
|
|
179
|
+
return next;
|
|
180
|
+
}
|
|
181
|
+
const run = this.evaluate(sessionId)
|
|
182
|
+
.catch((err) => {
|
|
183
|
+
this.ports.log?.(`[KeepWarm] ${sessionId.slice(0, 8)}: ${err instanceof Error ? err.message : String(err)}`);
|
|
184
|
+
// A failed write or pane read is transient, and an idle session gets no other trigger
|
|
185
|
+
// until its next turn. (A failed settings read is handled in evaluate().)
|
|
186
|
+
// Not after `remove()`, which drops this run: the session is being deleted.
|
|
187
|
+
if (!this.stopped && this.running.get(sessionId) === run)
|
|
188
|
+
this.arm(sessionId, Date.now() + KEEP_WARM_RETRY_MS);
|
|
189
|
+
})
|
|
190
|
+
.finally(() => {
|
|
191
|
+
if (this.running.get(sessionId) === run)
|
|
192
|
+
this.running.delete(sessionId);
|
|
193
|
+
});
|
|
194
|
+
this.running.set(sessionId, run);
|
|
195
|
+
return run;
|
|
196
|
+
}
|
|
197
|
+
rescheduleAll() {
|
|
198
|
+
this.settings = undefined;
|
|
199
|
+
for (const id of this.ports.sessionIds())
|
|
200
|
+
void this.schedule(id);
|
|
201
|
+
}
|
|
202
|
+
remove(sessionId) {
|
|
203
|
+
this.clearTimer(sessionId);
|
|
204
|
+
this.tallies.delete(sessionId);
|
|
205
|
+
this.inFlight.delete(sessionId);
|
|
206
|
+
this.running.delete(sessionId);
|
|
207
|
+
this.followUps.delete(sessionId);
|
|
208
|
+
}
|
|
209
|
+
stop() {
|
|
210
|
+
this.stopped = true;
|
|
211
|
+
for (const id of [...this.timers.keys()])
|
|
212
|
+
this.clearTimer(id);
|
|
213
|
+
}
|
|
214
|
+
readSettings() {
|
|
215
|
+
if (!this.settings) {
|
|
216
|
+
const read = this.ports.readSettings();
|
|
217
|
+
this.settings = read;
|
|
218
|
+
// A failed read is not kept: the next evaluation tries again.
|
|
219
|
+
read.catch(() => {
|
|
220
|
+
if (this.settings === read)
|
|
221
|
+
this.settings = undefined;
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
return this.settings;
|
|
225
|
+
}
|
|
226
|
+
clearTimer(id) {
|
|
227
|
+
const t = this.timers.get(id);
|
|
228
|
+
if (t)
|
|
229
|
+
clearTimeout(t);
|
|
230
|
+
this.timers.delete(id);
|
|
231
|
+
}
|
|
232
|
+
arm(id, at) {
|
|
233
|
+
this.clearTimer(id);
|
|
234
|
+
const t = setTimeout(() => void this.schedule(id), Math.min(Math.max(0, at - Date.now()), MAX_TIMER_MS));
|
|
235
|
+
t.unref?.();
|
|
236
|
+
this.timers.set(id, t);
|
|
237
|
+
}
|
|
238
|
+
async evaluate(id) {
|
|
239
|
+
if (this.stopped || this.inFlight.has(id))
|
|
240
|
+
return;
|
|
241
|
+
const token = Symbol(id);
|
|
242
|
+
this.inFlight.set(id, token);
|
|
243
|
+
// A session removed (or the service stopped) during an await gets nothing after it: no
|
|
244
|
+
// nudge, no tally, no timer, no status.
|
|
245
|
+
const cancelled = () => this.stopped || this.inFlight.get(id) !== token;
|
|
246
|
+
try {
|
|
247
|
+
this.clearTimer(id);
|
|
248
|
+
const session = this.ports.getSession(id);
|
|
249
|
+
if (!session)
|
|
250
|
+
return;
|
|
251
|
+
let settings;
|
|
252
|
+
try {
|
|
253
|
+
settings = await this.readSettings();
|
|
254
|
+
}
|
|
255
|
+
catch (err) {
|
|
256
|
+
// A read that lands mid-write is retried. A settings.json that stays broken (a hand edit
|
|
257
|
+
// with a trailing comma) fails every read until it is fixed, so it is logged once rather
|
|
258
|
+
// than once a minute per session, and only a session that could nudge retries.
|
|
259
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
260
|
+
if (message !== this.settingsFailure) {
|
|
261
|
+
this.settingsFailure = message;
|
|
262
|
+
this.ports.log?.(`[KeepWarm] ${id.slice(0, 8)}: cannot read the settings: ${message}`);
|
|
263
|
+
}
|
|
264
|
+
if (!cancelled() && couldNudge(session))
|
|
265
|
+
this.arm(id, Date.now() + KEEP_WARM_RETRY_MS);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
this.settingsFailure = undefined;
|
|
269
|
+
if (cancelled())
|
|
270
|
+
return;
|
|
271
|
+
const tally = this.tallies.get(id) ?? { nudges: 0 };
|
|
272
|
+
const facts = {
|
|
273
|
+
now: Date.now(),
|
|
274
|
+
choice: session.keepWarmEnabled,
|
|
275
|
+
appDefault: settings.keepWarmDefault,
|
|
276
|
+
spawnedByAgent: !!session.parentSessionId,
|
|
277
|
+
statusLine: session.statusLineExporter,
|
|
278
|
+
cache: session.promptCache,
|
|
279
|
+
idle: session.isIdle() && !session.isWorking,
|
|
280
|
+
paneAlive: session.pid !== null && !session.paneExit,
|
|
281
|
+
limitPaused: session.isLimitPaused,
|
|
282
|
+
automationActive: this.ports.isAutomationActive(id),
|
|
283
|
+
pendingApproval: this.ports.hasPendingApproval(id),
|
|
284
|
+
// Optimistic until everything else says "due": the pane read is the one costly fact.
|
|
285
|
+
composerEmpty: true,
|
|
286
|
+
lastHumanInputAt: session.lastHumanInputAt,
|
|
287
|
+
budgetAnchorAt: session.lastRealPromptAt || session.createdAt,
|
|
288
|
+
maxKeepWarmMs: settings.maxHours * 3_600_000,
|
|
289
|
+
};
|
|
290
|
+
let decision = decideKeepWarm(facts);
|
|
291
|
+
if (decision.action === 'nudge') {
|
|
292
|
+
const pane = this.ports.readPane(session);
|
|
293
|
+
decision = decideKeepWarm({
|
|
294
|
+
...facts,
|
|
295
|
+
composerEmpty: pane.composerEmpty,
|
|
296
|
+
limitPaused: facts.limitPaused || pane.limitShown,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
const enabled = keepWarmOn(facts.choice, facts.appDefault, facts.spawnedByAgent);
|
|
300
|
+
const base = {
|
|
301
|
+
enabled,
|
|
302
|
+
idleCompactionOff: session.idleCompactionOptOut,
|
|
303
|
+
nudges: tally.nudges,
|
|
304
|
+
lastNudgeAt: tally.lastNudgeAt,
|
|
305
|
+
// Off, the window means nothing, and it moves with every real prompt: carrying it would
|
|
306
|
+
// broadcast a change on every turn of every session.
|
|
307
|
+
...(enabled && { budgetEndsAt: facts.budgetAnchorAt + facts.maxKeepWarmMs }),
|
|
308
|
+
};
|
|
309
|
+
if (decision.action === 'nudge') {
|
|
310
|
+
session.beginKeepAliveTurn(KEEP_WARM_TURN_WINDOW_MS);
|
|
311
|
+
const ok = await session.writeViaMux(`${KEEP_WARM_NUDGE}\r`);
|
|
312
|
+
if (cancelled())
|
|
313
|
+
return;
|
|
314
|
+
if (ok) {
|
|
315
|
+
tally.nudges += 1;
|
|
316
|
+
tally.lastNudgeAt = Date.now();
|
|
317
|
+
this.tallies.set(id, tally);
|
|
318
|
+
}
|
|
319
|
+
this.ports.log?.(`[KeepWarm] ${ok ? 'nudged' : 'failed to nudge'} ${id.slice(0, 8)} with ${facts.cache?.recacheTokensIfCold ?? '?'} tokens at stake (nudge ${tally.nudges})`);
|
|
320
|
+
// Sent: the nudge's own cache report re-schedules, and this timer is the fallback if none
|
|
321
|
+
// arrives. Not sent: no turn is running, so the window closes now rather than at its
|
|
322
|
+
// ceiling, and the retry lands inside the last minutes before the expiry, where a nudge
|
|
323
|
+
// can still go out (a lead time later would be past it).
|
|
324
|
+
let nextCheckAt = Date.now() + KEEP_WARM_LEAD_MS;
|
|
325
|
+
if (!ok) {
|
|
326
|
+
session.endKeepAliveTurn();
|
|
327
|
+
const expiresAt = facts.cache?.expiresAt ?? 0;
|
|
328
|
+
nextCheckAt = Math.min(Date.now() + KEEP_WARM_RETRY_MS, expiresAt - KEEP_WARM_LAST_CHANCE_MS);
|
|
329
|
+
}
|
|
330
|
+
this.arm(id, nextCheckAt);
|
|
331
|
+
this.ports.publish(id, {
|
|
332
|
+
...base,
|
|
333
|
+
nudges: tally.nudges,
|
|
334
|
+
lastNudgeAt: tally.lastNudgeAt,
|
|
335
|
+
state: ok ? 'nudged' : 'waiting',
|
|
336
|
+
nextCheckAt,
|
|
337
|
+
reason: ok ? undefined : 'the keep-alive could not be written',
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
else if (decision.action === 'wait') {
|
|
341
|
+
this.arm(id, decision.until);
|
|
342
|
+
this.ports.publish(id, {
|
|
343
|
+
...base,
|
|
344
|
+
state: decision.reason === 'not due yet' ? 'armed' : 'waiting',
|
|
345
|
+
nextCheckAt: decision.until,
|
|
346
|
+
reason: decision.reason === 'not due yet' ? undefined : decision.reason,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
this.ports.publish(id, { ...base, state: enabled ? 'stopped' : 'off', reason: decision.reason });
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
finally {
|
|
354
|
+
if (this.inFlight.get(id) === token)
|
|
355
|
+
this.inFlight.delete(id);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
//# sourceMappingURL=prompt-cache-keepwarm.js.map
|