aicodeman 1.28.2 → 1.29.1
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 +58 -31
- package/README.zh-CN.md +207 -51
- package/dist/attachment-registry.d.ts +20 -0
- package/dist/attachment-registry.d.ts.map +1 -1
- package/dist/attachment-registry.js +75 -10
- package/dist/attachment-registry.js.map +1 -1
- package/dist/config/cli-registry/schema.d.ts +25 -0
- package/dist/config/cli-registry/schema.d.ts.map +1 -1
- package/dist/config/cli-registry/schema.js +43 -0
- package/dist/config/cli-registry/schema.js.map +1 -1
- package/dist/config/cli-registry/stock.d.ts.map +1 -1
- package/dist/config/cli-registry/stock.js +157 -2
- package/dist/config/cli-registry/stock.js.map +1 -1
- package/dist/config/cli-registry/types.d.ts +60 -0
- package/dist/config/cli-registry/types.d.ts.map +1 -1
- package/dist/custom-model-hosts.d.ts +39 -0
- package/dist/custom-model-hosts.d.ts.map +1 -0
- package/dist/custom-model-hosts.js +35 -0
- package/dist/custom-model-hosts.js.map +1 -0
- package/dist/custom-model-injection-apply.d.ts +49 -0
- package/dist/custom-model-injection-apply.d.ts.map +1 -0
- package/dist/custom-model-injection-apply.js +77 -0
- package/dist/custom-model-injection-apply.js.map +1 -0
- package/dist/custom-model-injection.d.ts +79 -0
- package/dist/custom-model-injection.d.ts.map +1 -0
- package/dist/custom-model-injection.js +169 -0
- package/dist/custom-model-injection.js.map +1 -0
- package/dist/docker-hosts.d.ts +42 -0
- package/dist/docker-hosts.d.ts.map +1 -1
- package/dist/docker-hosts.js +18 -0
- package/dist/docker-hosts.js.map +1 -1
- package/dist/generated-artifact-attachments.d.ts +3 -0
- package/dist/generated-artifact-attachments.d.ts.map +1 -1
- package/dist/generated-artifact-attachments.js +18 -8
- package/dist/generated-artifact-attachments.js.map +1 -1
- package/dist/mux-interface.d.ts +7 -0
- package/dist/mux-interface.d.ts.map +1 -1
- package/dist/remote-files.d.ts +145 -0
- package/dist/remote-files.d.ts.map +1 -0
- package/dist/remote-files.js +347 -0
- package/dist/remote-files.js.map +1 -0
- package/dist/remote-hosts.d.ts +12 -0
- package/dist/remote-hosts.d.ts.map +1 -1
- package/dist/remote-hosts.js +7 -1
- package/dist/remote-hosts.js.map +1 -1
- package/dist/remote-ssh-limiter.d.ts +42 -0
- package/dist/remote-ssh-limiter.d.ts.map +1 -0
- package/dist/remote-ssh-limiter.js +84 -0
- package/dist/remote-ssh-limiter.js.map +1 -0
- package/dist/session-auto-name.d.ts +74 -0
- package/dist/session-auto-name.d.ts.map +1 -0
- package/dist/session-auto-name.js +339 -0
- package/dist/session-auto-name.js.map +1 -0
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/session-manager.js +8 -1
- package/dist/session-manager.js.map +1 -1
- package/dist/session.d.ts +123 -5
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +237 -9
- package/dist/session.js.map +1 -1
- package/dist/tmux-manager.d.ts.map +1 -1
- package/dist/tmux-manager.js +27 -13
- package/dist/tmux-manager.js.map +1 -1
- package/dist/types/api.d.ts +6 -0
- package/dist/types/api.d.ts.map +1 -1
- package/dist/types/api.js.map +1 -1
- package/dist/types/session.d.ts +48 -0
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/session.js.map +1 -1
- package/dist/web/middleware/auth.d.ts +11 -1
- package/dist/web/middleware/auth.d.ts.map +1 -1
- package/dist/web/middleware/auth.js +69 -1
- package/dist/web/middleware/auth.js.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.78bf0bf8.js → app.556be563.js} +7 -7
- package/dist/web/public/app.556be563.js.br +0 -0
- package/dist/web/public/app.556be563.js.gz +0 -0
- package/dist/web/public/approvals-ui.js.gz +0 -0
- package/dist/web/public/constants.258b140f.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/home-sessions.js.gz +0 -0
- package/dist/web/public/i18n.5f897ed5.js +1 -0
- package/dist/web/public/i18n.5f897ed5.js.br +0 -0
- package/dist/web/public/i18n.5f897ed5.js.gz +0 -0
- package/dist/web/public/image-input.cd4b97c4.js.gz +0 -0
- package/dist/web/public/index.html +23 -9
- 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 +1 -0
- package/dist/web/public/input-cjk.8bc46081.js.br +0 -0
- package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
- package/dist/web/public/{keyboard-accessory.1d0dda9b.js → keyboard-accessory.2cf04f17.js} +38 -3
- package/dist/web/public/keyboard-accessory.2cf04f17.js.br +0 -0
- package/dist/web/public/keyboard-accessory.2cf04f17.js.gz +0 -0
- package/dist/web/public/mobile-handlers.6f354a87.js.gz +0 -0
- package/dist/web/public/mobile-overview.js.gz +0 -0
- package/dist/web/public/mobile.e9d0b53e.css.gz +0 -0
- package/dist/web/public/notification-manager.36ea4624.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/panels-ui.5b07ad14.js.gz +0 -0
- package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
- package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
- package/dist/web/public/readmymind-ui.js.gz +0 -0
- package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
- package/dist/web/public/session-lineage.js.gz +0 -0
- package/dist/web/public/{session-ui.5d0a7b15.js → session-ui.42b81477.js} +13 -13
- package/dist/web/public/session-ui.42b81477.js.br +0 -0
- package/dist/web/public/session-ui.42b81477.js.gz +0 -0
- package/dist/web/public/settings-ui.58f1d756.js +67 -0
- package/dist/web/public/settings-ui.58f1d756.js.br +0 -0
- package/dist/web/public/settings-ui.58f1d756.js.gz +0 -0
- package/dist/web/public/{styles.5d36726d.css → styles.6add175d.css} +1 -1
- package/dist/web/public/styles.6add175d.css.br +0 -0
- package/dist/web/public/{styles.5d36726d.css.gz → styles.6add175d.css.gz} +0 -0
- package/dist/web/public/subagent-windows.e6ca799f.js.gz +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/tab-rail-resize.42c24949.js.gz +0 -0
- package/dist/web/public/terminal-keycode229-recovery.6ea8fe37.js.gz +0 -0
- package/dist/web/public/terminal-ui.38c49244.js +2 -0
- package/dist/web/public/terminal-ui.38c49244.js.br +0 -0
- package/dist/web/public/terminal-ui.38c49244.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/upload.html.gz +0 -0
- package/dist/web/public/vendor/dompurify.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.bd6882b8.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.6fee72f2.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.c4b51eb6.js.gz +0 -0
- package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
- package/dist/web/public/webview-tabs.js +56 -4
- package/dist/web/public/webview-tabs.js.br +0 -0
- package/dist/web/public/webview-tabs.js.gz +0 -0
- package/dist/web/route-helpers.d.ts +20 -0
- package/dist/web/route-helpers.d.ts.map +1 -1
- package/dist/web/route-helpers.js +29 -2
- package/dist/web/route-helpers.js.map +1 -1
- package/dist/web/routes/case-routes.d.ts.map +1 -1
- package/dist/web/routes/case-routes.js +23 -6
- package/dist/web/routes/case-routes.js.map +1 -1
- package/dist/web/routes/custom-model-routes.d.ts +18 -0
- package/dist/web/routes/custom-model-routes.d.ts.map +1 -0
- package/dist/web/routes/custom-model-routes.js +136 -0
- package/dist/web/routes/custom-model-routes.js.map +1 -0
- package/dist/web/routes/file-routes.d.ts.map +1 -1
- package/dist/web/routes/file-routes.js +484 -100
- package/dist/web/routes/file-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/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +93 -7
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/routes/ws-routes.d.ts.map +1 -1
- package/dist/web/routes/ws-routes.js +28 -3
- package/dist/web/routes/ws-routes.js.map +1 -1
- package/dist/web/schemas.d.ts +21 -0
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +38 -0
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/server.d.ts +8 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +70 -3
- package/dist/web/server.js.map +1 -1
- package/dist/web/session-listener-wiring.d.ts +6 -2
- package/dist/web/session-listener-wiring.d.ts.map +1 -1
- package/dist/web/session-listener-wiring.js +33 -2
- package/dist/web/session-listener-wiring.js.map +1 -1
- package/dist/web/webview-proxy.d.ts +41 -0
- package/dist/web/webview-proxy.d.ts.map +1 -1
- package/dist/web/webview-proxy.js +102 -1
- package/dist/web/webview-proxy.js.map +1 -1
- package/package.json +2 -2
- package/dist/web/public/app.78bf0bf8.js.br +0 -0
- package/dist/web/public/app.78bf0bf8.js.gz +0 -0
- package/dist/web/public/i18n.561ca09c.js +0 -1
- package/dist/web/public/i18n.561ca09c.js.br +0 -0
- package/dist/web/public/i18n.561ca09c.js.gz +0 -0
- package/dist/web/public/input-cjk.87f14251.js +0 -1
- package/dist/web/public/input-cjk.87f14251.js.br +0 -0
- package/dist/web/public/input-cjk.87f14251.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.1d0dda9b.js.br +0 -0
- package/dist/web/public/keyboard-accessory.1d0dda9b.js.gz +0 -0
- package/dist/web/public/session-ui.5d0a7b15.js.br +0 -0
- package/dist/web/public/session-ui.5d0a7b15.js.gz +0 -0
- package/dist/web/public/settings-ui.e0c7f6b4.js +0 -67
- package/dist/web/public/settings-ui.e0c7f6b4.js.br +0 -0
- package/dist/web/public/settings-ui.e0c7f6b4.js.gz +0 -0
- package/dist/web/public/styles.5d36726d.css.br +0 -0
- package/dist/web/public/terminal-ui.0cbe8637.js +0 -2
- package/dist/web/public/terminal-ui.0cbe8637.js.br +0 -0
- package/dist/web/public/terminal-ui.0cbe8637.js.gz +0 -0
package/dist/session.js
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* ralph-tracker (todo/completion parsing), bash-tool-parser (tool invocation tracking),
|
|
24
24
|
* task-tracker (background tasks), mux-interface (tmux abstraction)
|
|
25
25
|
* @consumedby session-manager, web/server, respawn-controller
|
|
26
|
-
* @emits session:terminal, session:idle, session:working, session:completion, session:exit
|
|
26
|
+
* @emits session:terminal, session:idle, session:working, session:completion, session:promptSubmitted, session:exit
|
|
27
27
|
*
|
|
28
28
|
* @module session
|
|
29
29
|
*/
|
|
@@ -52,6 +52,7 @@ import { SessionAutoOps } from './session-auto-ops.js';
|
|
|
52
52
|
import { detectUsageLimitPause } from './usage-limit-patterns.js';
|
|
53
53
|
import { SessionTaskCache } from './session-task-cache.js';
|
|
54
54
|
import { InteractivePtyExitBreaker } from './session-pty-exit-breaker.js';
|
|
55
|
+
import { isGeneratedSessionName, SubmittedPromptTracker } from './session-auto-name.js';
|
|
55
56
|
import { parseTerminalAttachmentRequests } from './attachment-magic.js';
|
|
56
57
|
import { sanitizeAttachmentHistory, upsertAttachmentHistory as upsertAttachmentHistoryList, } from './session-attachment-history.js';
|
|
57
58
|
/** Line buffer flush interval (100ms) - forces processing of partial lines */
|
|
@@ -288,6 +289,15 @@ export class Session extends EventEmitter {
|
|
|
288
289
|
// Task description cache (extracted to SessionTaskCache)
|
|
289
290
|
_taskCache = new SessionTaskCache();
|
|
290
291
|
_name;
|
|
292
|
+
_nameSource;
|
|
293
|
+
/**
|
|
294
|
+
* Reconstructs the composer draft from USER keystrokes so the first real
|
|
295
|
+
* prompt can name the tab. Fed only when a write says `fromUser`, and never
|
|
296
|
+
* for a CLI whose Enter runs a command rather than submitting a prompt
|
|
297
|
+
* (`startMode: 'shell'`), so a shell tab is not renamed after every `ls`.
|
|
298
|
+
*/
|
|
299
|
+
_submittedPromptTracker = new SubmittedPromptTracker();
|
|
300
|
+
_acceptsPrompts;
|
|
291
301
|
ptyProcess = null;
|
|
292
302
|
_pid = null;
|
|
293
303
|
_status = 'idle';
|
|
@@ -423,6 +433,18 @@ export class Session extends EventEmitter {
|
|
|
423
433
|
// user can still switch in-session via /effort (incl. ultracode). Never carried as
|
|
424
434
|
// the CLAUDE_CODE_EFFORT_LEVEL env var, which would hard-lock the session.
|
|
425
435
|
_effort;
|
|
436
|
+
// Custom Model Endpoint Profiles (docs/custom-model-endpoints-plan.md). `envKeys`,
|
|
437
|
+
// `configDir` and `launchModel` are internal bookkeeping ONLY (never surfaced via
|
|
438
|
+
// toState()/the customModel getter): they are what setCustomModel() needs to undo a
|
|
439
|
+
// previous injection (remove exactly the env keys it added, delete a previous isolated
|
|
440
|
+
// config dir) without guessing what it once wrote. Persisted disk-only (`__customModel`).
|
|
441
|
+
_customModel;
|
|
442
|
+
// Env keys a retired custom-model selection injected that the NEXT respawn must
|
|
443
|
+
// `tmux setenv -u`. Deleting a key from `_envOverrides` alone does nothing to the
|
|
444
|
+
// tmux session, which keeps every `setenv` and hands it to `respawn-pane`, so the
|
|
445
|
+
// relaunched CLI would come back still pointed at the old endpoint (measured, see
|
|
446
|
+
// TmuxManager.applyEnvOverrides). Drained after a successful respawn.
|
|
447
|
+
_pendingEnvUnsets = new Set();
|
|
426
448
|
// tmux history-limit (scrollback lines) allocated when this session's pane is created.
|
|
427
449
|
_tmuxHistoryLimit;
|
|
428
450
|
// Remote execution metadata, present when this session runs over SSH through local tmux.
|
|
@@ -465,6 +487,9 @@ export class Session extends EventEmitter {
|
|
|
465
487
|
this.createdAt = config.createdAt || Date.now();
|
|
466
488
|
this.mode = config.mode || 'claude';
|
|
467
489
|
this._name = config.name || '';
|
|
490
|
+
this._nameSource =
|
|
491
|
+
config.nameSource ?? (!this._name || isGeneratedSessionName(this._name) ? 'placeholder' : 'manual');
|
|
492
|
+
this._acceptsPrompts = getCli(this.mode)?.capabilities.startMode !== 'shell';
|
|
468
493
|
this._resumeSessionId = config.resumeSessionId;
|
|
469
494
|
// NOW, not `createdAt`: recovery passes the ORIGINAL creation time of a
|
|
470
495
|
// days-old tmux session, and seeding last-activity from it would report a
|
|
@@ -936,6 +961,62 @@ export class Session extends EventEmitter {
|
|
|
936
961
|
this._color = color;
|
|
937
962
|
}
|
|
938
963
|
}
|
|
964
|
+
// Custom Model Endpoint Profiles (docs/custom-model-endpoints-plan.md) — public-safe
|
|
965
|
+
// subset only (never envKeys/configDir/launchModel, the bookkeeping for setCustomModel).
|
|
966
|
+
get customModel() {
|
|
967
|
+
if (!this._customModel)
|
|
968
|
+
return undefined;
|
|
969
|
+
const { endpointId, modelId, label } = this._customModel;
|
|
970
|
+
return { endpointId, modelId, label };
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* The full selection incl. bookkeeping, for state.json ONLY (`__customModel`, the
|
|
974
|
+
* same disk-only convention as `getEnvOverridesForPersist()`). Carries no env values,
|
|
975
|
+
* so nothing secret lands on disk; recovery re-derives them from the endpoint store.
|
|
976
|
+
* Without this a Codeman restart left the pane on the custom endpoint (tmux keeps
|
|
977
|
+
* its `setenv`s) while `customModel` came back undefined, so the state was wrong and
|
|
978
|
+
* clearing had nothing to unset. Must NOT be included in any API-bound serializer.
|
|
979
|
+
*/
|
|
980
|
+
getCustomModelForPersist() {
|
|
981
|
+
return this._customModel ? { ...this._customModel, envKeys: [...this._customModel.envKeys] } : undefined;
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* Update this session's custom-model selection and merge the endpoint's injected env
|
|
985
|
+
* vars into `_envOverrides` — first UNDOING whatever the previous selection injected
|
|
986
|
+
* (removing exactly those env keys), so switching endpoints, or clearing back to the
|
|
987
|
+
* harness's native cloud default, never leaves a stale key behind. Synchronous and
|
|
988
|
+
* side-effect-free beyond mutating state, matching `setNice`/`setColor` above — this
|
|
989
|
+
* class does no file IO, so it reports the PREVIOUS `configDir` (if any) for the
|
|
990
|
+
* caller to clean up on disk (custom-model-injection.ts's configDir kind).
|
|
991
|
+
*
|
|
992
|
+
* Keys the previous selection injected that the new one does not re-set are queued
|
|
993
|
+
* for `tmux setenv -u` on the next respawn (`_pendingEnvUnsets`, threaded through
|
|
994
|
+
* `_buildRespawnPaneOptions().unsetEnvKeys`): the tmux session inherits every
|
|
995
|
+
* `setenv` into `respawn-pane`, so dropping them from the map alone would relaunch
|
|
996
|
+
* the CLI still pointed at the old endpoint — and for the `configDir` kinds, at a
|
|
997
|
+
* `HOME`/`CODEX_HOME`/`GROK_HOME` the caller has just deleted.
|
|
998
|
+
*/
|
|
999
|
+
setCustomModel(next, envOverrides) {
|
|
1000
|
+
const previousConfigDir = this._customModel?.configDir;
|
|
1001
|
+
const removedEnvKeys = [];
|
|
1002
|
+
if (this._customModel) {
|
|
1003
|
+
for (const key of this._customModel.envKeys) {
|
|
1004
|
+
if (this._envOverrides)
|
|
1005
|
+
delete this._envOverrides[key];
|
|
1006
|
+
removedEnvKeys.push(key);
|
|
1007
|
+
this._pendingEnvUnsets.add(key);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
this._customModel = next ? { ...next, envKeys: [...next.envKeys] } : undefined;
|
|
1011
|
+
if (envOverrides && Object.keys(envOverrides).length > 0) {
|
|
1012
|
+
this._envOverrides = { ...(this._envOverrides ?? {}), ...envOverrides };
|
|
1013
|
+
// A key the new selection sets again does not need an unset (applyEnvOverrides
|
|
1014
|
+
// would set it right back anyway); keep the list to what actually goes away.
|
|
1015
|
+
for (const key of Object.keys(envOverrides))
|
|
1016
|
+
this._pendingEnvUnsets.delete(key);
|
|
1017
|
+
}
|
|
1018
|
+
return { removedEnvKeys, previousConfigDir };
|
|
1019
|
+
}
|
|
939
1020
|
// Token tracking getters and setters
|
|
940
1021
|
get totalTokens() {
|
|
941
1022
|
return this._totalInputTokens + this._totalOutputTokens;
|
|
@@ -979,8 +1060,32 @@ export class Session extends EventEmitter {
|
|
|
979
1060
|
get name() {
|
|
980
1061
|
return this._name;
|
|
981
1062
|
}
|
|
1063
|
+
/** An explicit rename: the name is the user's from here on and auto-naming never touches it. */
|
|
982
1064
|
set name(value) {
|
|
983
1065
|
this._name = value;
|
|
1066
|
+
this._nameSource = 'manual';
|
|
1067
|
+
}
|
|
1068
|
+
/**
|
|
1069
|
+
* Names the tab after its first prompt. Only a placeholder is eligible, and
|
|
1070
|
+
* the session stops being one whether or not the string changed: "first
|
|
1071
|
+
* prompt" means the first, not "every prompt until a rename". Returns
|
|
1072
|
+
* whether the name changed, so the caller knows whether to persist and
|
|
1073
|
+
* broadcast.
|
|
1074
|
+
*/
|
|
1075
|
+
applyAutoName(value) {
|
|
1076
|
+
if (this._nameSource !== 'placeholder')
|
|
1077
|
+
return false;
|
|
1078
|
+
const name = value.trim();
|
|
1079
|
+
if (!name)
|
|
1080
|
+
return false;
|
|
1081
|
+
this._nameSource = 'auto';
|
|
1082
|
+
if (this._name === name)
|
|
1083
|
+
return false;
|
|
1084
|
+
this._name = name;
|
|
1085
|
+
return true;
|
|
1086
|
+
}
|
|
1087
|
+
get nameSource() {
|
|
1088
|
+
return this._nameSource;
|
|
984
1089
|
}
|
|
985
1090
|
setAutoClear(enabled, threshold) {
|
|
986
1091
|
this._autoOps.setAutoClear(enabled, threshold);
|
|
@@ -1100,6 +1205,7 @@ export class Session extends EventEmitter {
|
|
|
1100
1205
|
// attach repaint, so the home screens' quiet ordering survives a restart.
|
|
1101
1206
|
lastActivityAt: this._wireActivityAt,
|
|
1102
1207
|
name: this._name,
|
|
1208
|
+
nameSource: this._nameSource,
|
|
1103
1209
|
mode: this.mode,
|
|
1104
1210
|
autoClearEnabled: this._autoOps.autoClearEnabled,
|
|
1105
1211
|
autoClearThreshold: this._autoOps.autoClearThreshold,
|
|
@@ -1138,6 +1244,7 @@ export class Session extends EventEmitter {
|
|
|
1138
1244
|
ompConfig: this._ompConfig,
|
|
1139
1245
|
resumeSessionId: this._resumeSessionId,
|
|
1140
1246
|
effort: this._effort,
|
|
1247
|
+
customModel: this.customModel,
|
|
1141
1248
|
// COD-118: runtime-only — surfaced so the frontend can require explicit user
|
|
1142
1249
|
// intent before restarting a crash-looped session. Deliberately NOT restored
|
|
1143
1250
|
// by the constructor: a Codeman restart starts with a fresh breaker so boot
|
|
@@ -1270,6 +1377,7 @@ export class Session extends EventEmitter {
|
|
|
1270
1377
|
needsNewSession = true;
|
|
1271
1378
|
}
|
|
1272
1379
|
else {
|
|
1380
|
+
this._pendingEnvUnsets.clear();
|
|
1273
1381
|
// Wait a moment for the respawned process to fully start
|
|
1274
1382
|
await new Promise((resolve) => setTimeout(resolve, MUX_STARTUP_DELAY_MS));
|
|
1275
1383
|
}
|
|
@@ -1358,14 +1466,66 @@ export class Session extends EventEmitter {
|
|
|
1358
1466
|
console.log('[Session] reattachRemote: reattached remote session', this._muxSession.muxName, 'pid', newPid);
|
|
1359
1467
|
return true;
|
|
1360
1468
|
}
|
|
1469
|
+
/**
|
|
1470
|
+
* Kill and relaunch this session's CLI process IN PLACE — same pane, same tmux
|
|
1471
|
+
* session, fresh env/args from current state. Custom Model Endpoint Profiles
|
|
1472
|
+
* (docs/custom-model-endpoints-plan.md) is the first caller: after `setCustomModel()` merges new
|
|
1473
|
+
* env vars into `_envOverrides`, the running CLI process still has the OLD env
|
|
1474
|
+
* (inherited at its own process start, not live-reloaded), so switching a
|
|
1475
|
+
* session's model/endpoint requires this restart to actually take effect.
|
|
1476
|
+
*
|
|
1477
|
+
* A GENERALIZED {@link reattachRemote} with the `!this._remote` guard dropped —
|
|
1478
|
+
* `_buildRespawnPaneOptions()` already passes `remote: this._remote` through
|
|
1479
|
+
* unconditionally, so `mux.respawnPane()` builds the right command either way
|
|
1480
|
+
* (a local session gets `respawn-pane -k` + the real launch line, which is the
|
|
1481
|
+
* kill-and-relaunch this method exists for; a remote session gets the existing
|
|
1482
|
+
* reattach-to-durable-tmux behavior). Deliberately does NOT check `isBusy()` —
|
|
1483
|
+
* that's the caller's job (mirrors `/interactive`'s guard), since a raw restart
|
|
1484
|
+
* primitive shouldn't itself decide when it's safe to use.
|
|
1485
|
+
*
|
|
1486
|
+
* @returns true if the pane was respawned, false otherwise (no mux session, or
|
|
1487
|
+
* the mux session is gone — see {@link reattachRemote} for that reasoning).
|
|
1488
|
+
*/
|
|
1489
|
+
async restartCli() {
|
|
1490
|
+
if (!this._useMux || !this._mux || !this._muxSession)
|
|
1491
|
+
return false;
|
|
1492
|
+
const mux = this._mux;
|
|
1493
|
+
if (!mux.muxSessionExists(this._muxSession.muxName)) {
|
|
1494
|
+
console.log('[Session] restartCli: mux session gone, skipping:', this._muxSession.muxName);
|
|
1495
|
+
return false;
|
|
1496
|
+
}
|
|
1497
|
+
this._pinOmpRespawnId();
|
|
1498
|
+
const options = this._buildRespawnPaneOptions();
|
|
1499
|
+
// Unlike the dead-pane respawn, this one kills a WORKING pane whose conversation
|
|
1500
|
+
// already has a transcript, and a CLI that launches with `--session-id <id>` refuses
|
|
1501
|
+
// an id that is already in use (claude: `Error: Session ID ... is already in use.`),
|
|
1502
|
+
// which turned an endpoint switch into a dead pane and a lost session. A launch that
|
|
1503
|
+
// declares a `fallback` chain renders `resume || new` once a resume id is set, the
|
|
1504
|
+
// same `--resume <id> || --session-id <id>` shape the docker and remote pane commands
|
|
1505
|
+
// already use, so pin the live conversation id for THIS respawn only. The registry
|
|
1506
|
+
// shape is the gate, not the CLI's name: an entry whose resume id is minted by the
|
|
1507
|
+
// CLI itself (codex/pi/omp/grok) never declares that chain, and its resume field is
|
|
1508
|
+
// read from its own `<Mode>Config` rather than this top-level one anyway.
|
|
1509
|
+
if (!options.resumeSessionId && getCli(this.mode)?.launch.chain === 'fallback') {
|
|
1510
|
+
options.resumeSessionId = this._claudeSessionId ?? this.id;
|
|
1511
|
+
}
|
|
1512
|
+
const newPid = await mux.respawnPane(options);
|
|
1513
|
+
if (!newPid) {
|
|
1514
|
+
console.error('[Session] restartCli: respawnPane failed for', this._muxSession.muxName);
|
|
1515
|
+
return false;
|
|
1516
|
+
}
|
|
1517
|
+
this._pendingEnvUnsets.clear();
|
|
1518
|
+
console.log('[Session] restartCli: restarted CLI for', this._muxSession.muxName, 'pid', newPid);
|
|
1519
|
+
return true;
|
|
1520
|
+
}
|
|
1361
1521
|
/**
|
|
1362
1522
|
* Assemble the {@link RespawnPaneOptions} for this session. Single source of
|
|
1363
1523
|
* truth shared by interactive start, shell start (via their inline copies),
|
|
1364
|
-
* and {@link
|
|
1524
|
+
* {@link reattachRemote}, and {@link restartCli} so no respawn path can drift from
|
|
1365
1525
|
* the spawn path.
|
|
1366
1526
|
*/
|
|
1367
1527
|
_buildRespawnPaneOptions() {
|
|
1368
|
-
|
|
1528
|
+
const options = {
|
|
1369
1529
|
sessionId: this.id,
|
|
1370
1530
|
workingDir: this.workingDir,
|
|
1371
1531
|
mode: this.mode,
|
|
@@ -1393,12 +1553,37 @@ export class Session extends EventEmitter {
|
|
|
1393
1553
|
ompConfig: this._ompConfig,
|
|
1394
1554
|
resumeSessionId: this._resumeSessionId,
|
|
1395
1555
|
envOverrides: this._envOverrides,
|
|
1556
|
+
unsetEnvKeys: this._pendingEnvUnsets.size > 0 ? [...this._pendingEnvUnsets] : undefined,
|
|
1396
1557
|
effort: this._effort,
|
|
1397
1558
|
historyLimit: this._tmuxHistoryLimit,
|
|
1398
1559
|
remote: this._remote,
|
|
1399
1560
|
docker: this._docker,
|
|
1400
1561
|
owner: this._owner,
|
|
1401
1562
|
};
|
|
1563
|
+
return this._withCustomModelLaunchModel(options);
|
|
1564
|
+
}
|
|
1565
|
+
/**
|
|
1566
|
+
* Force the custom-model selection's `launchModel` (pi/omp `custom/<id>`, grok's
|
|
1567
|
+
* `[model.<name>]` block name) onto the CLI's `model` launch param. Where that param
|
|
1568
|
+
* lives is registry DATA — the entry's `legacyConfigField` (`piConfig`, `grokConfig`,
|
|
1569
|
+
* ...) or the top-level `model` for an entry that declares none — so this stays a
|
|
1570
|
+
* generic reader rather than a branch per CLI. Applied on the OPTIONS only: the stored
|
|
1571
|
+
* `<Mode>Config` keeps whatever model the user chose at create, which is exactly what a
|
|
1572
|
+
* later clear must fall back to.
|
|
1573
|
+
*/
|
|
1574
|
+
_withCustomModelLaunchModel(options) {
|
|
1575
|
+
const launchModel = this._customModel?.launchModel;
|
|
1576
|
+
if (!launchModel)
|
|
1577
|
+
return options;
|
|
1578
|
+
const entry = getCli(this.mode);
|
|
1579
|
+
if (!entry)
|
|
1580
|
+
return options;
|
|
1581
|
+
const field = entry.launch.legacyConfigField;
|
|
1582
|
+
if (!field)
|
|
1583
|
+
return { ...options, model: launchModel };
|
|
1584
|
+
const bag = options;
|
|
1585
|
+
const existing = (bag[field] ?? {});
|
|
1586
|
+
return { ...options, [field]: { ...existing, model: launchModel } };
|
|
1402
1587
|
}
|
|
1403
1588
|
/**
|
|
1404
1589
|
* OMP-only: resolve and PIN the exact conversation to continue when
|
|
@@ -2879,11 +3064,12 @@ export class Session extends EventEmitter {
|
|
|
2879
3064
|
* discards the data, but it used to do so with no signal at all — which is how
|
|
2880
3065
|
* input could disappear while the caller believed it had been delivered.
|
|
2881
3066
|
*/
|
|
2882
|
-
write(data) {
|
|
2883
|
-
this._trackSubmit(data);
|
|
3067
|
+
write(data, options = {}) {
|
|
3068
|
+
const submittedPrompt = this._trackSubmit(data, options);
|
|
2884
3069
|
if (!this.ptyProcess)
|
|
2885
3070
|
return false;
|
|
2886
3071
|
this.ptyProcess.write(data);
|
|
3072
|
+
this._emitSubmittedPrompt(submittedPrompt);
|
|
2887
3073
|
return true;
|
|
2888
3074
|
}
|
|
2889
3075
|
// ── Conversation tracking ─────────────────────────────────────────────
|
|
@@ -2897,10 +3083,34 @@ export class Session extends EventEmitter {
|
|
|
2897
3083
|
get lastSubmitAt() {
|
|
2898
3084
|
return this._lastSubmitAt;
|
|
2899
3085
|
}
|
|
2900
|
-
|
|
3086
|
+
/**
|
|
3087
|
+
* Stamps the pane's last Enter for EVERY write, and feeds the auto-name
|
|
3088
|
+
* tracker only for user-originated input on a prompt-taking CLI. Ralph
|
|
3089
|
+
* kick-starts, respawn `/clear`s, cron launches, approval answers and the
|
|
3090
|
+
* trust-dialog keys all arrive without `fromUser` and so can never name a tab.
|
|
3091
|
+
*/
|
|
3092
|
+
_trackSubmit(data, options) {
|
|
3093
|
+
const submitted = options.fromUser && this._acceptsPrompts ? this._submittedPromptTracker.feed(data) : [];
|
|
2901
3094
|
if (data.includes('\r') || data.includes('\n')) {
|
|
2902
3095
|
this._lastSubmitAt = Date.now();
|
|
2903
3096
|
}
|
|
3097
|
+
return submitted;
|
|
3098
|
+
}
|
|
3099
|
+
/**
|
|
3100
|
+
* Feeds user input that reaches the pane AROUND the write paths: the
|
|
3101
|
+
* send-key route injects Shift+Enter's line feed through `tmux send-keys -H`
|
|
3102
|
+
* directly, and without this the two lines of a prompt joined with no
|
|
3103
|
+
* separator. Reports submissions like a write would (a line feed never is one).
|
|
3104
|
+
*/
|
|
3105
|
+
trackUserInput(data) {
|
|
3106
|
+
if (!this._acceptsPrompts)
|
|
3107
|
+
return;
|
|
3108
|
+
this._emitSubmittedPrompt(this._submittedPromptTracker.feed(data));
|
|
3109
|
+
}
|
|
3110
|
+
_emitSubmittedPrompt(prompts) {
|
|
3111
|
+
for (const prompt of prompts) {
|
|
3112
|
+
this.emit('promptSubmitted', prompt);
|
|
3113
|
+
}
|
|
2904
3114
|
}
|
|
2905
3115
|
/**
|
|
2906
3116
|
* A prompt was submitted, reported by the CLI's own UserPromptSubmit hook.
|
|
@@ -2933,6 +3143,20 @@ export class Session extends EventEmitter {
|
|
|
2933
3143
|
* half-open socket silently drops frames with no error) would type a prompt
|
|
2934
3144
|
* twice whenever an ACK is lost after the write landed.
|
|
2935
3145
|
*/
|
|
3146
|
+
/**
|
|
3147
|
+
* The highest input seq recorded for `clientId`, or 0 when this session has
|
|
3148
|
+
* never seen it.
|
|
3149
|
+
*
|
|
3150
|
+
* Reported back on a REJECTED (duplicate) frame so the client can lift its own
|
|
3151
|
+
* counter above this watermark. Without that number a client whose persisted
|
|
3152
|
+
* counter fell behind ours has no way to find its way out: every fresh
|
|
3153
|
+
* keystroke it sends lands at or below the watermark, is dropped as a
|
|
3154
|
+
* duplicate, and is ACKed anyway — so the UI looks healthy while nothing is
|
|
3155
|
+
* delivered, and a reload restores the same stale counter from localStorage.
|
|
3156
|
+
*/
|
|
3157
|
+
lastInputSeq(clientId) {
|
|
3158
|
+
return this._appliedInputSeq.get(clientId) ?? 0;
|
|
3159
|
+
}
|
|
2936
3160
|
shouldApplyInput(clientId, seq) {
|
|
2937
3161
|
const last = this._appliedInputSeq.get(clientId);
|
|
2938
3162
|
if (last !== undefined && seq <= last)
|
|
@@ -2980,14 +3204,18 @@ export class Session extends EventEmitter {
|
|
|
2980
3204
|
* session.writeViaMux('/init\r'); // Send /init command
|
|
2981
3205
|
* ```
|
|
2982
3206
|
*/
|
|
2983
|
-
async writeViaMux(data) {
|
|
2984
|
-
this._trackSubmit(data);
|
|
3207
|
+
async writeViaMux(data, options = {}) {
|
|
3208
|
+
const submittedPrompt = this._trackSubmit(data, options);
|
|
2985
3209
|
if (this._mux && this._muxSession) {
|
|
2986
|
-
|
|
3210
|
+
const sent = await this._mux.sendInput(this.id, data);
|
|
3211
|
+
if (sent)
|
|
3212
|
+
this._emitSubmittedPrompt(submittedPrompt);
|
|
3213
|
+
return sent;
|
|
2987
3214
|
}
|
|
2988
3215
|
// Fallback to PTY write
|
|
2989
3216
|
if (this.ptyProcess) {
|
|
2990
3217
|
this.ptyProcess.write(data);
|
|
3218
|
+
this._emitSubmittedPrompt(submittedPrompt);
|
|
2991
3219
|
return true;
|
|
2992
3220
|
}
|
|
2993
3221
|
return false;
|