mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 10:39:32 +01:00
fix: remove the em-dashes this release added (release review)
105 new em-dashes sat in comments, test names and one test fixture from the foreign tmux adoption work. Replaced by hand with commas, colons or parentheses, one reading at a time. The fixture now quotes the real refusal text, which says "It may still be running, try again." Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9d2f9663cb
commit
9442ea8cf4
27 files changed
+99
-99
No files matched your search
@@ -10962,7 +10962,7 @@ class CodemanApp {
|
||||
// only thing Codeman owns; the server's killSession refuses to reach the
|
||||
// foreign server at all (see the adopted early return in tmux-manager). So
|
||||
// the red "Terminate the session completely" option is not merely redundant
|
||||
// here, it is a false promise about someone else's live work — and the kind
|
||||
// here, it is a false promise about someone else's live work, and the kind
|
||||
// of false promise that stops a user closing the tab at all. Hide it, and say
|
||||
// what actually happens instead.
|
||||
const killBtn = document.getElementById('closeConfirmKillBtn');
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
*
|
||||
* The list is a poll, not an SSE stream: the server caches a local scan on a TTL,
|
||||
* so N tabs polling costs one scan per TTL no matter how many are open. But an
|
||||
* open tab that has LEFT the home screen must stop — otherwise every background
|
||||
* open tab that has LEFT the home screen must stop, otherwise every background
|
||||
* tab keeps sweeping tmux sockets forever. `startForeignPolling` /
|
||||
* `stopForeignPolling` are called from `showWelcome`/`hideWelcome`.
|
||||
*
|
||||
* ⚠️ Docker and remote locations are NOT in that poll. Each costs a `docker exec`
|
||||
* or a full ssh handshake per target, each with a ~12s timeout, so riding them
|
||||
* along on an 8s tick stacks connections on a slow host — the one cost the
|
||||
* along on an 8s tick stacks connections on a slow host, the one cost the
|
||||
* server-side design explicitly refuses. The wide scan is therefore ONE-SHOT:
|
||||
* turning the toggle on fetches once, and after that it is the user's Rescan
|
||||
* button. The periodic poll stays local-only forever.
|
||||
@@ -35,7 +35,7 @@
|
||||
*
|
||||
* ⚠️ Adoption creates a REAL session, so the button locks while in flight and the
|
||||
* result goes through the app's normal idempotent create path
|
||||
* (`_onSessionCreated` then `selectSession`) — never by inserting a tab here.
|
||||
* (`_onSessionCreated` then `selectSession`), never by inserting a tab here.
|
||||
*
|
||||
* @mixin Extends CodemanApp.prototype via Object.assign
|
||||
* @dependency app.js (this.sessions, _onSessionCreated, selectSession)
|
||||
@@ -80,12 +80,12 @@ Object.assign(CodemanApp.prototype, {
|
||||
if (this._foreignPollTimer || this._foreignRefused) return;
|
||||
void this.loadForeignSessions();
|
||||
this._foreignPollTimer = setInterval(() => {
|
||||
// Local only — see @fileoverview. The wide scan never rides the timer.
|
||||
// Local only: see @fileoverview. The wide scan never rides the timer.
|
||||
void this.loadForeignSessions();
|
||||
}, this._foreignPollMs || FOREIGN_POLL_FALLBACK_MS);
|
||||
},
|
||||
|
||||
/** Stop polling. MUST run on leaving the home screen — see @fileoverview. */
|
||||
/** Stop polling. MUST run on leaving the home screen, see @fileoverview. */
|
||||
stopForeignPolling() {
|
||||
if (!this._foreignPollTimer) return;
|
||||
clearInterval(this._foreignPollTimer);
|
||||
@@ -125,7 +125,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
|
||||
/**
|
||||
* Fetch the list. `wide` asks the server for containers and ssh hosts too, and
|
||||
* ONLY an explicit user action ever passes it — never the interval.
|
||||
* ONLY an explicit user action ever passes it, never the interval.
|
||||
*/
|
||||
async loadForeignSessions({ wide = false } = {}) {
|
||||
if (this._foreignRefused) return;
|
||||
@@ -207,8 +207,8 @@ Object.assign(CodemanApp.prototype, {
|
||||
}
|
||||
// ⚠️ `_api`, not `_apiJson`: the latter folds a network failure, a non-2xx
|
||||
// and `success:false` all into null, and the server deliberately tells
|
||||
// two different stories here — "that tmux session is no longer there" vs
|
||||
// "could not reach it just now (…) — try again". Collapsing them showed a
|
||||
// two different stories here: "that tmux session is no longer there" vs
|
||||
// "could not reach it just now (…), try again". Collapsing them showed a
|
||||
// live remote session as deleted every time the link flickered.
|
||||
const res = await this._api('/api/sessions/adopt', { method: 'POST', body });
|
||||
const payload = await this._readJsonBody(res);
|
||||
@@ -225,7 +225,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
this._onSessionCreated?.(data.session);
|
||||
await this.selectSession(data.session.id);
|
||||
// Still one explicit user action, so a wide row's "Open" refreshes the
|
||||
// wide list too — otherwise the row just adopted keeps reading as free.
|
||||
// wide list too, otherwise the row just adopted keeps reading as free.
|
||||
await this.loadForeignSessions({ wide: this.foreignScanRemote() });
|
||||
} finally {
|
||||
this._foreignAdoptInFlight = false;
|
||||
@@ -259,7 +259,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
},
|
||||
|
||||
/**
|
||||
* Render into one container. The ONLY row builder — see @fileoverview.
|
||||
* Render into one container. The ONLY row builder, see @fileoverview.
|
||||
*
|
||||
* Everything is built with DOM APIs and `textContent`: a session name and a
|
||||
* command line come from another user's process, so they are never allowed
|
||||
@@ -277,14 +277,14 @@ Object.assign(CodemanApp.prototype, {
|
||||
// Hidden rather than shown-empty: on a first run there is usually nothing,
|
||||
// and an empty block on the welcome screen reads as a broken feature.
|
||||
// A note is a REASON the list is empty, so a block carrying one must stay
|
||||
// visible — hiding it is exactly how "where did my session go" becomes
|
||||
// visible: hiding it is exactly how "where did my session go" becomes
|
||||
// unanswerable.
|
||||
const hasNotes =
|
||||
(Array.isArray(this._foreignNotes) && this._foreignNotes.length > 0) ||
|
||||
(Array.isArray(this._foreignWideNotes) && this._foreignWideNotes.length > 0);
|
||||
// ⚠️ `canScanWide` must keep the block visible even with nothing to show.
|
||||
// The scan toggle lives in the header, so hiding an empty block also hides
|
||||
// the only control that could fill it — on a host with containers but no
|
||||
// the only control that could fill it: on a host with containers but no
|
||||
// local tmux sessions that made the whole feature unreachable.
|
||||
container.hidden = !this.foreignScanRemote() && !this._foreignError && !hasNotes && !this._foreignCanScanWide;
|
||||
if (!container.hidden) {
|
||||
@@ -435,7 +435,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
|
||||
/**
|
||||
* One delegated listener per container, wired once. Delegation matters here
|
||||
* because the list is fully rebuilt on every poll — per-button listeners would
|
||||
* because the list is fully rebuilt on every poll: per-button listeners would
|
||||
* leak one set per tick.
|
||||
*/
|
||||
wireForeignSessions() {
|
||||
|
||||
@@ -509,7 +509,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
);
|
||||
|
||||
// Sessions a human opened outside Codeman. Its own container, rebuilt by the
|
||||
// ONE renderer in foreign-sessions.js — the phone must not grow a second row
|
||||
// ONE renderer in foreign-sessions.js: the phone must not grow a second row
|
||||
// builder that could describe the same session differently from the desktop.
|
||||
const foreign = document.createElement('div');
|
||||
foreign.className = 'foreign-sessions mobile-foreign-sessions';
|
||||
|
||||
@@ -4219,7 +4219,7 @@ html[data-session-list="sidebar"] .session-sidebar .session-tab .tab-close {
|
||||
|
||||
/* Foreign sessions block inside the phone overview (foreign-sessions.js).
|
||||
The desktop block sits inside the welcome column; here it is a full-width
|
||||
section between CURRENT and PAST, so it only needs the surrounding spacing —
|
||||
section between CURRENT and PAST, so it only needs the surrounding spacing:
|
||||
every row style is shared with styles.css on purpose. */
|
||||
.mobile-foreign-sessions {
|
||||
margin: 0.75rem 0.75rem 0;
|
||||
|
||||
@@ -22134,7 +22134,7 @@ html[data-tile-anim="soft"] .tile-grid-ghosts.tile-grid-ghosts--release:not(.til
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════
|
||||
Foreign sessions — tmux sessions a human started outside Codeman
|
||||
Foreign sessions: tmux sessions a human started outside Codeman
|
||||
(foreign-sessions.js). Rendered on the welcome screen and, with the
|
||||
same row builder, inside the phone overview.
|
||||
|
||||
|
||||
@@ -2961,7 +2961,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
// the column without overlapping the content leaves it hidden.
|
||||
this.showHomeSessions?.();
|
||||
// Sessions a human opened outside Codeman. Polls only while this screen is
|
||||
// up (stopped in hideWelcome) — see foreign-sessions.js.
|
||||
// up (stopped in hideWelcome), see foreign-sessions.js.
|
||||
this.wireForeignSessions?.();
|
||||
this.startForeignPolling?.();
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
* discovery of FOREIGN tmux sessions (ones a human started outside Codeman).
|
||||
*
|
||||
* Discovery lives here rather than beside the adopt endpoint on purpose: like
|
||||
* every other route in this file it exposes cross-user process state — other
|
||||
* people's session names, commands and working directories — so it inherits the
|
||||
* every other route in this file it exposes cross-user process state (other
|
||||
* people's session names, commands and working directories), so it inherits the
|
||||
* admin gate this file already applies. Adoption is a session CREATE and stays in
|
||||
* `session-routes.ts`, where the owner, capacity and case-space gates live.
|
||||
*/
|
||||
@@ -66,7 +66,7 @@ export function registerMuxRoutes(app: FastifyInstance, ctx: InfraPort & Session
|
||||
const wantRemote = q.remote === '1' || q.remote === 'true';
|
||||
|
||||
// Read the registries either way: `canScanWide` tells the browser whether the
|
||||
// expensive scan has anywhere to go. Without it the UI hides an empty block —
|
||||
// expensive scan has anywhere to go. Without it the UI hides an empty block,
|
||||
// and with it the toggle that is the ONLY way to populate that block, which on
|
||||
// a host with containers but no local tmux sessions made the feature invisible.
|
||||
const dockerCases = await readAllDockerCases();
|
||||
|
||||
@@ -72,7 +72,7 @@ export function registerRalphRoutes(
|
||||
// ⚠️ Adoption gate, kept SEPARATE from the external-CLI gate above: an adopted
|
||||
// session can be `mode: 'claude'` and still be a process we never launched.
|
||||
// Everything below drives the pane on the assumption Codeman owns what runs
|
||||
// in it — sending `/clear`, killing and relaunching the agent — which against
|
||||
// in it (sending `/clear`, killing and relaunching the agent), which against
|
||||
// someone else's live session is destructive, not merely unsupported.
|
||||
if (session.isAdopted) {
|
||||
return createErrorResponse(
|
||||
|
||||
@@ -101,7 +101,7 @@ export function registerRespawnRoutes(
|
||||
// ⚠️ Adoption gate, kept SEPARATE from the external-CLI gate above: an adopted
|
||||
// session can be `mode: 'claude'` and still be a process we never launched.
|
||||
// Everything below drives the pane on the assumption Codeman owns what runs
|
||||
// in it — sending `/clear`, killing and relaunching the agent — which against
|
||||
// in it (sending `/clear`, killing and relaunching the agent), which against
|
||||
// someone else's live session is destructive, not merely unsupported.
|
||||
if (session.isAdopted) {
|
||||
return createErrorResponse(
|
||||
@@ -255,7 +255,7 @@ export function registerRespawnRoutes(
|
||||
// ⚠️ Adoption gate, kept SEPARATE from the external-CLI gate above: an adopted
|
||||
// session can be `mode: 'claude'` and still be a process we never launched.
|
||||
// Everything below drives the pane on the assumption Codeman owns what runs
|
||||
// in it — sending `/clear`, killing and relaunching the agent — which against
|
||||
// in it (sending `/clear`, killing and relaunching the agent), which against
|
||||
// someone else's live session is destructive, not merely unsupported.
|
||||
if (session.isAdopted) {
|
||||
return createErrorResponse(
|
||||
@@ -335,7 +335,7 @@ export function registerRespawnRoutes(
|
||||
// ⚠️ Adoption gate, kept SEPARATE from the external-CLI gate above: an adopted
|
||||
// session can be `mode: 'claude'` and still be a process we never launched.
|
||||
// Everything below drives the pane on the assumption Codeman owns what runs
|
||||
// in it — sending `/clear`, killing and relaunching the agent — which against
|
||||
// in it (sending `/clear`, killing and relaunching the agent), which against
|
||||
// someone else's live session is destructive, not merely unsupported.
|
||||
if (session.isAdopted) {
|
||||
return createErrorResponse(
|
||||
|
||||
@@ -1657,8 +1657,8 @@ export function registerSessionRoutes(
|
||||
// pane's cwd. That cwd is an observation about another machine: it can carry
|
||||
// characters outside SAFE_PATH_PATTERN (`~/c++`, `Program Files (x86)`), which
|
||||
// makes `createSession` throw so the tab never starts at all, and when it is a
|
||||
// container or ssh path every local consumer of `workingDir` — the Files panel,
|
||||
// the boot hook sweep, the fs watchers — reads it as a path on THIS host. The
|
||||
// container or ssh path every local consumer of `workingDir` (the Files panel,
|
||||
// the boot hook sweep, the fs watchers) reads it as a path on THIS host. The
|
||||
// foreign cwd stays on `adopt.paneCurrentPath`, which is for display only.
|
||||
// `/tmp` is the same neutral cwd `resolveMuxAttachCwd` already gives the pane.
|
||||
const session = new Session({
|
||||
|
||||
+2
-2
@@ -4164,7 +4164,7 @@ export class WebServer extends EventEmitter {
|
||||
// and one more: it is the ONLY thing that marks this session as
|
||||
// wrapping a process Codeman never launched. Dropping it on recovery
|
||||
// silently re-enabled respawn, Ralph and hook-backed waits against
|
||||
// someone else's live tmux session after every server restart —
|
||||
// someone else's live tmux session after every server restart:
|
||||
// measured, not hypothetical. The mux record is preferred because it
|
||||
// is what `killSession`'s detach-not-kill guard already reads.
|
||||
adopt: muxSession.adopt ?? savedState?.adopt,
|
||||
@@ -4455,7 +4455,7 @@ export class WebServer extends EventEmitter {
|
||||
// connection facts live on `adopt.remote` / `adopt.docker`, so a session
|
||||
// wrapping someone's in-container or over-ssh claude looks local here.
|
||||
// Its workspace was never Codeman's to begin with, so installing hooks
|
||||
// there writes `.claude/settings.local.json` into the human's own repo —
|
||||
// there writes `.claude/settings.local.json` into the human's own repo,
|
||||
// on EVERY restart, since this sweep runs from boot recovery.
|
||||
if (session.isAdopted) continue;
|
||||
if (session.docker && !session.docker.hooksEnabled) continue;
|
||||
|
||||
@@ -208,7 +208,7 @@ export interface HookCapabilityOptions {
|
||||
* be `mode: 'claude'` and still have no hooks, because hooks are installed into
|
||||
* a WORKSPACE at session-create time (`applyWorkspaceHooks`) and we never
|
||||
* created this one. Answering from the mode there would promise `stop` and
|
||||
* `blocked` for a process that can never post either — the exact
|
||||
* `blocked` for a process that can never post either, the exact
|
||||
* infinite-wait-dressed-as-a-timeout this predicate exists to prevent.
|
||||
*/
|
||||
adopted?: boolean;
|
||||
|
||||
Reference in new issue
Block a user