Commit Graph
2016 Commits
Author SHA1 Message Date
Devvyn 021641911e fix(discord): make the table separator test linear, record a posted message only after it posts, say which CLIs raise a turn end 2026-10-30 21:28:35 +08:00
Devvyn e5d82eb044 feat(discord): post a Markdown table as an aligned code block 2026-10-30 19:58:44 +08:00
Devvyn 093f242662 feat(discord): post a finished turn as the CLI's whole last message, stale thread cleanup, types moved to src/types 2026-10-30 18:26:35 +08:00
Devvyn d6c7d30bd4 fix(discord): tidy a thread made in the old channel, keep the two pending sets in step, settle the hint when the settings are off 2026-10-30 16:42:33 +08:00
Devvyn 97c698b249 fix(discord): ignore gateway frames after stop, hold the typed hint while a choice is pending, name the Discord group in the settings reference 2026-10-30 15:42:48 +08:00
Devvyn 9f18b78857 fix(discord): replace a gateway that gave up when the same config is saved again 2026-10-30 14:27:36 +08:00
Devvyn d044e49635 fix(discord): show the real reason a thread fails, keep the gateway across a save, refund the rate limit for a refused file 2026-10-30 12:58:03 +08:00
Devvyn a698729fb6 fix(discord): keep the thread of a discarded rebuild, close one made for a closed tab, unique tmp names, wider prune keep set 2026-10-30 11:43:09 +08:00
Devvyn b68792f988 fix(discord): say when the agent has exited, keep replies in order, settle a stalled response, prune dead tabs
- a reply to a tab whose agent has exited is answered in the thread, with no tick
- inbound messages run on one chain per thread, so a reply with a file cannot be overtaken
- postJson settles when a response stalls after its headers
- the thread map is pruned against the live sessions once after boot
2026-10-30 10:33:34 +08:00
Devvyn 212989b349 fix(discord): digit replies without Enter, start the gateway after restore, hide other users' session ids, fix docs
- a lone digit reply is typed without Enter while the tab has a pending permission or question
- the gateway starts after restoreMuxSessions (hold and release), so a reply during restore is not told the tab is gone
- discord-notify resolves an id prefix only among sessions the caller may see
- restore docs/architecture-invariants.md from base and add only the Discord section
- docs: self-ignoring folder, no logging, Notifications group, Send test, local-tab-only Claude hint, typedHint wording
2026-10-30 07:58:33 +08:00
Devvyn dc22519adb fix(discord): discard a thread created for a replaced parent channel, test the notify timeout, document the bridge invariants
- a thread creation that finishes after the parent channel changed is dropped, and a finished creation only clears its own pending entry
- postJson takes an optional timeout, pinned by a test against a server that never answers
- drop the flat discord-bridge.json fallback
- add a Discord bridge section to architecture-invariants
2026-10-30 06:42:17 +08:00
Devvyn 1ab54b9e9b fix(discord): report a refused post as a failure, bound the file probe, drop threads of a previous channel
- codeman notify exits 1 when the route answers 200 with data.ok false
- notify request times out at 30 s instead of hanging
- fileProblem probes the file with the bounded probe before realpath
- the thread map records its parent channel; a new channel starts afresh
- threadFor returns its failure instead of sharing this.last
2026-10-30 04:58:17 +08:00
Devvyn 0db9b55961 fix(discord): rate limit discord-notify, bound the folder probe, stop sendTest swapping the live config
- discord-notify: per-session token bucket (6 a minute), 429 RATE_LIMITED, plain line from codeman notify
- fileProblem probes the working folder with the bounded probe before realpath
- sendTest uses its own DiscordApi and leaves this.cfg alone
- drop the resumed-tab check from the typed hint (only Claude's field was read)
- first sessionRenamed per session records the name without a PATCH
- boot goes through ensureLoaded, so one gateway starts
- a thread created without an id reports the failure
2026-10-30 03:47:25 +08:00
Devvyn 2d4036cf1e test(discord): give the bare cleanup server the bridge its delete path now calls 2026-10-30 01:50:24 +08:00
Devvyn 6d4357502b feat(discord): make the typed first-message hint a separate opt-in, off by default
Claude keeps the silent system-prompt hint (startupHint, on). Other CLIs are sent the typed message only with typedHint, which costs a model turn per tab.
2026-10-30 01:38:37 +08:00
Devvyn e95d38a944 fix(discord): load the thread map once, so alerts that race at startup keep their threads 2026-10-29 23:04:20 +08:00
Devvyn eb0553a645 fix(discord): address review on #741
Builds the alert tail only for a post that goes out; separates deduped from disabled and never dedupes a deliberate notify; types the startup hint with fromUser false and skips resumed, remote and Docker tabs; clears per-tab maps and closes the bridge on stop; acts only on a real delete, not a detach; serialises thread-map writes; reloads the bridge after a discord.json restore; probes and self-ignores .codeman-discord; validates the message id; reacts when a reply is rate limited; documents what leaves the machine.
2026-10-29 23:03:26 +08:00
Devvyn 902d4491e5 feat(discord): testable codeman notify, short-id route, wiki page, docs and glue tests
Moves the notify command into src/cli-notify.ts with tests, lets discord-notify take the 8-character id a Docker case exports, adds the wiki page and index entries, rewrites the stale changeset and security notes, and pins the WebServer wiring.
2026-10-29 21:46:06 +08:00
Devvyn dcf51b1715 fix(discord): post the rendered screen, not the raw stream, as the alert tail
A full-screen UI repaints in place, so the raw output stream read as garbage. Read capture-pane instead and drop rules, blanks and escape codes.
2026-10-29 21:31:37 +08:00
Devvyn 1c4338e8b5 feat(discord): codeman notify, startup hint for every CLI, and file send/receive
Adds the codeman notify command, a startup hint (Claude via --append-system-prompt, other CLIs once on first idle), files saved from thread attachments into the tab folder, and --file uploads limited to the session folder.
2026-10-29 21:27:23 +08:00
Devvyn 61bf26cfdf feat(discord): two-way Discord bridge with a thread per tab
Alerts for permission prompts, questions, idle and errors post into a per-tab thread; a reply from an allowlisted user is typed into that tab. Thread creation, thread cleanup on tab delete and pinging are configurable. POST /api/sessions/:id/discord-notify lets a CLI ask for the user. Setup is in docs/discord-bridge.md.
2026-10-29 21:04:18 +08:00
Aamer Akhter f23e18b1fc fix(terminal): report the pane directory in the workspace's own spelling
tmux reads a pane's directory from the process cwd, which is always the
resolved path, while workingDir keeps the spelling the case was created
with. For a workspace reached through a symlink every reading looked
like it was outside the workspace, so the browser sent each relative
link through the attachment route instead of the workspace routes.

Each session now resolves workingDir once in the background, bounded by
probePathKind() and a timed realpath(), and setCwd() writes a reading at
or under the resolved path back in workingDir's spelling. Until that
settles only a reading lexically inside workingDir is taken. An unknown
probe or a failed realpath leaves readings untranslated and retries
after WORKSPACE_REALPATH_RETRY_MS.

Also corrects two comments: cwd is written to state.json but never read
back, and a blank pane path is usually a pane whose program exited.
2026-10-28 19:29:05 -04:00
Aamer Akhter 54c1692350 fix(terminal): clear the pane directory when a read says the session has none
applyPaneExits() only applied the manager's directory when it returned a
value, so after a successful read a session that was split into several
panes, or was no longer on the socket, kept its stale cwd. The manager
already keeps its map across a failed or empty read, so the server now
applies its answer as given, undefined included, and setCwd() broadcasts
the clear once.

A blank #{pane_current_path} on a single-pane session (tmux could not read
the process cwd that tick) now carries the previous entry forward in
derivePaneCurrentPaths(), so only a split or unlisted session clears.

The server tests now drive the real TmuxManager with only the tmux read
faked. docs/api-reference.md documents SessionState.cwd.
2026-10-28 15:28:59 -04:00
JD f91cd343be fix(mobile): keep the press transition to the press's own properties
`all` also sped up Run's mode colours, Stop's confirming state and the armed Ctrl key. The browser test now reads the transition at rest, since its 250ms hold would also pass the old 0.2s one.
2026-10-28 13:39:13 -04:00
JD ee97daa4f0 test(docker): leave appdata root-owned when the runner cannot chown it
Root without CAP_CHOWN (--cap-drop ALL, as Codeman's own Docker case containers run) or a single-uid user namespace makes chownSync throw, which failed all five smoke tests in harness setup instead of master's two.
2026-10-28 13:33:45 -04:00
Devvyn 3847e377d8 fix(links): preserve missing-path and labelled-wrap behaviour 2026-10-29 01:32:17 +08:00
Devvyn 168c15ae08 fix(links): scope comma handling to parenthesised paths 2026-10-29 00:18:20 +08:00
Devvyn fd0e52496d fix(links): preserve workspace openers after lookup refusals 2026-10-28 23:03:45 +08:00
Devvyn 97d53f6621 fix(links): resolve ambiguous hard wraps through guarded lookup 2026-10-28 21:45:49 +08:00
Aamer Akhter 1607b1e488 feat(terminal): resolve relative path links against the pane's current directory
A relative path printed in the terminal was resolved against the session's
workingDir, which is fixed at create time and never follows a cd. In a shell
that moved into a subdirectory or a sibling repo, the link opened the wrong
file or reported "File not found".

The pane-exit watcher's batched list-panes read already carries
#{pane_current_path}. derivePaneCurrentPaths() turns those rows into one
directory per single-pane session, TmuxManager.getPaneCurrentPath() serves it
from memory, and the server applies it on the existing paneExitsUpdated tick
as SessionState.cwd, broadcasting only on a change. No new tmux call, no new
SSE event. A failed read keeps the previous answers.

Session.setCwd() uses the paneExit scoping rule: remote and Docker sessions
never get a cwd (their local pane runs the ssh or docker exec client), nor do
direct-PTY or rediscovered sessions. The value is runtime-only.

In the browser, relativeResolutionBase() picks the live cwd when known and
workingDir otherwise. It only changes the base a relative path joins: the
result is routed as before, so a target outside the workspace still goes
through the attachment routes, and file-content/file-raw are unchanged.
2026-10-28 09:38:06 -04:00
Devvyn 5ce8213726 fix(links): preserve wrapped configuration filenames 2026-10-28 21:17:50 +08:00
Codeman maintainer 8074f67c6a fix: keep the soft-keyboard recovery on Android with a fine pointer (release review, #726)
#726 attaches the keyCode 229 recovery only when (pointer: coarse)
matches, which keeps a desktop dead key from arriving twice. Chromium
reports the PRIMARY pointer as fine once a mouse, a trackpad cover or a
stylus is attached, so on such an Android device the module was off
while the text still came from the soft keyboard, and the #441 dropped
last character and the #541 autocorrect duplication came back.

The gate is now a coarse pointer OR an Android user agent. Android ran
the recovery unconditionally before #726, so this only keeps that; a
desktop (fine pointer, not Android) and a touchscreen laptop stay off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:35:20 +01:00
Codeman maintainer ca0f891c2b fix: never read a remote fix-plan path from the local disk (release review, #722)
setRemoteWorkingDir() records the path for authority only, and today
nothing else reads it for a remote session (loadFixPlanFromDisk's only
callers are setWorkingDir and the local watcher). The watcher now
remembers the remote mode and refuses both the local read and the local
watch for it, so a future caller cannot import a same-named local twin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:34:05 +01:00
Codeman maintainer 9f7cba07ab fix: strip nested paste markers until none is left (release review, #726)
Ctrl+V's hand-built bracketed paste (#726) and the phone composer's
dropped CSI 200~/201~ markers from the text in ONE pass, so a clipboard
carrying ESC[20 ESC[201~ 1~ came out of it with an ESC[201~ that ended
the frame early: everything after it was typed as keys and each CR ran.
Both now strip until nothing matches. The composer's copy predates this
release; it is the same one-liner.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:30:15 +01:00
Codeman maintainer 1ad9db7606 fix: keep a remote session's fix plan authoritative (release review, #722)
#722 stopped handing a remote session's workingDir to the Ralph tracker,
which also left isFileAuthoritative false for remote sessions only. That
flag is what keeps the output-based completion guessers off, and it is
true for every local session with a working directory, so a remote loop
could end, and mark every todo done, on a prose line such as "All tasks
completed" while a local one cannot.

setRemoteWorkingDir() records the path without reading or watching the
local disk, so the fix plan stays authoritative as for a local session;
the file still reaches the tracker only through fix-plan/read, over ssh.
The test pins both, and fails on the previous head.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:25:50 +01:00
Codeman maintainer 593b2eb2ea Merge pull request #721: feat(tabs): L-shaped header strip, wrapped rows run under the toolbar 2026-10-28 09:10:09 +01:00
Codeman maintainer 5add56eaf6 Merge pull request #726: fix(terminal): don't double a desktop dead key, and bracket Ctrl+V after replay 2026-10-28 09:10:09 +01:00
Codeman maintainer 9ee6df027d Merge pull request #730: fix(file-preview): let rendered Markdown fill the resized window 2026-10-28 09:10:08 +01:00
Codeman maintainer f1df8f8c0a Merge pull request #731: fix(response-viewer): a middle click on a file path opens it instead of the dashboard root 2026-10-28 09:10:07 +01:00
Codeman maintainer ad3f6328e6 Merge pull request #725: fix(web): accept empty chunked API POST requests 2026-10-28 09:10:07 +01:00
Codeman maintainer 6d640b08ce Merge pull request #728: fix: read a remote session's @fix_plan.md on its host, refuse remote Ralph writes 2026-10-28 09:10:06 +01:00
Codeman maintainer 9fec26009f Merge pull request #727: fix: don't point the image watcher at a remote session's directory 2026-10-28 09:10:06 +01:00
Codeman maintainer 66528962ee Merge pull request #722: fix: don't point the Ralph @fix_plan.md watcher at a remote session's directory 2026-10-28 09:10:05 +01:00
Codeman maintainer c708312a3f fix: apply the review items for #730 at merge
Set --rv-content-max: none on the File Viewer's rendered document, so
ASCII-diagram blocks (capped by that variable with a 720px fallback the
Response Viewer sets) follow the window like the rest of the document,
and pin it with a third assertion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:09:37 +01:00
Codeman maintainer 74fc3ea6ae fix: apply the review items for #722 at merge
Note in docs/remote-sessions.md that the @fix_plan.md auto-load does not
run for remote sessions, and rewrap the test's header comment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:07:28 +01:00
DDTandDDT 6f820ca990 fix: deliver Ctrl+V paste through terminal.input
Hand the bracketed frame to the target terminal with input() so onData
scrolls to the bottom, clears the selection, and delivers the bytes.
The paste trap no longer writes the frame itself.

Fixes #716

Co-authored-by: DDT <1786035110@qq.com>
2026-10-28 07:09:35 +00:00
JD a1406f1753 fix(settings): stop a failed settings write from saying it was saved
App Settings' save handled only the multi-user admin-only 403 and the tunnel's no-password 403, so a 409 for a settings.json that does not parse, a 400, a 500 or a dropped connection (`_apiPut` answers null) still toasted "Settings saved" and closed the modal, and the run mode and respawn preset writes failed with no word at all. Those failures now warn with the server's reason (else the HTTP status, else that the server could not be reached), the modal stays open, and nothing that assumes the server stored the save runs: Apply's refresh, the tunnel-starting toast and the gesture-control reload. Since the modal now invites a second Save, the plan-usage collection flip and the gesture reload are judged against the settings the failed save started from until a save lands, so the retry still sends the flip and still reloads.

A save with the tunnel switch on is read as a tunnel refusal only on a 403, so another failure no longer turns the switch off or shows the no-password message, and an accepted no-password retry now resends the whole save instead of the tunnel keys alone, which had left every other setting unsaved behind "Settings saved". Run mode and respawn presets stay saved on this device as before and now say they were not saved on the server. Stopping the tunnel from the welcome screen or the tunnel panel no longer says "Tunnel stopped" when the server refused the write, since the tunnel is then still running.
2026-10-28 01:32:47 -04:00
JD 8d75e6bd15 fix(mobile): show a press on the phone toolbar and accessory keys
On every skin but og the skin block outranks the phone :active backgrounds of the toolbar buttons, so Run showed no press at all and the others at most a faint tint, eased in over their 0.2s transition, so a quick tap showed almost nothing. A press now scales and brightens (darkens on the light skins) over 80ms, two properties nothing on these buttons sets at a higher specificity, and honours prefers-reduced-motion.

A browser test presses every phone bar control on all seven skins and pins the computed transform and filter, so a later rule that takes the press away again shows up in the browser suite.
2026-10-28 01:04:15 -04:00
JD affe91d7ed test(docker): give the Update-Codeman.sh smoke harness a non-root appdata owner
Both Update-Codeman.sh and Start-Codeman.sh derive PUID:PGID from the CODEMAN_APPDATA_PATH owner and refuse a root-owned directory. The smoke harness created that directory with mkdirSync, so under a root test runner (CI or agent containers) it was owned 0:0 and the script exited at that guard before its `build --no-cache` and `down`, failing the default and --keep-volumes smoke tests with an empty build/down log. The guard is correct; the harness now chowns appdata to 99:100 (docker/README.md's own example) when running as root, matching a real deployment.
2026-10-28 00:39:13 -04:00
JD b63935fa66 fix(response-viewer): a middle click on a file path opens it instead of the dashboard root
Path links and in-document links carry a placeholder href (#, #fragment) that the click delegate resolves itself, but a middle click fires auxclick, never click, so the browser followed the placeholder and opened the dashboard root in a new tab. A middle click on those links now does what a click does; real URLs keep the browser's own middle click.
2026-10-28 00:34:50 -04:00