Skip to content

emrg: ask the daemon where a GUI session lives before opening it - #2485

Merged
argszero merged 3 commits into
masterfrom
feature/gui-ask-daemon-session-cwd
Oct 22, 2026
Merged

argszero merged 3 commits into
masterfrom
feature/gui-ask-daemon-session-cwd

Conversation

@argszero

@argszero argszero commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

Closes #2484

The GUI never asked the daemon where a session lives. emrg:switchSession worked it out locally from ~/.emrg/sessions_index.json (resolveSessionCwd in main.js), and when that lookup returned nothing every caller fell back to resolveSessionCwd(sid) || os.homedir(). emrg:sendMessage then opened its connection with resume: false, so the daemon's ghost-session check — compare the requested cwd against the global index when meta.json is missing or message_count == 0, in _handle_resume_session — never ran, and the task's cwd reached the daemon's open-or-create path as it stood.

What changed

  • daemon_client.js: resolve_session_cwd joins the command→response map (session_cwd_result), and pickSessionCwd(frame, fallback) states the reading the daemon's two answers deserve — index is a fact, unknown is not, and a frame that cannot be read is neither.
  • main.js: askSessionCwd(sessionId, candidateCwd) asks the daemon on its own connection (3 s bound; an error or a timeout answers null, the same reading as unknown) and returns null rather than a directory.
  • emrg:switchSession opens at asked || projectPath || resolveSessionCwd(sid) — the daemon's answer, then the caller's project, then the local guess, and nothing at all when none of the three can place the session (the daemon answers not_found, which the existing fallback already handles).
  • emrg:sendMessage's pre-open asks before opening and sends the answered cwd with the task, so the resume: false window no longer decides where a session is created.

Not in this change

The other dozen resolveSessionCwd(sid) || DEFAULT_CWD call sites (delete, rename, clear, compact, history, memory, sandbox, images) run against a session the host already has open, where openSessions holds its project and resolveSessionCwd returns that first. The window this closes is the pre-open one.

Verification

  • cd emrg/gui && npm test — 167 tests, 159 pass, 8 skipped (the integration legs skip without an isolated daemon).
  • uv run pytest tests/ -q — 5587 passed, 34 skipped (this tree carries master 837fad4f, which added one).
  • The three new guards were each armed and restored: reverting the precedence chain, moving the ask after the open, and returning DEFAULT_CWD from the failure branch each redden exactly one guard, and the tree is green again once restored.
  • scripts/check-node-test-count.py --write moved the documented GUI count 161 → 166; its per-file breakdown (daemon_client 98 → 100, boot-contract 6 → 9) is synced in the same commit, and tests/test_doc_counts.py passes.

The Windows leg, and why it was red

test-windows failed on the previous head, in this file's own case issue #2484: askSessionCwd asks, judges, and never falls back to the home directory: AssertionError: it must send the daemon's own command, operator: match, actual: ''.

The cause was the reader, not the assertion. That case slices a function body out of main.js with src.slice(start, src.indexOf("\n }\n", start) + 4). A Windows runner checks out CRLF, so the sentinel never occurs, indexOf answers -1, and slice(start, 3) is the empty string — every assert.match over the body then read ''. read() now normalises \r\n to \n once, the shape daemon_client.test.js already uses for the source it reads, and no assertion was weakened.

Verified on both line endings, because a local green proves nothing about this class. A script ran this test's own extractor over main.js as LF (a 557-character body), over the same file forced to CRLF (0 characters — the reported failure, reproduced) and over the CRLF copy after normalisation (557 characters, the LF slice exactly), with the three assertions of the failing case holding on that text. npm test on this tree: 167 tests, 159 pass, 0 fail, 8 skipped.

No sibling newline sentinel exists in emrg/gui/test/ (measured by grep over that directory), so nothing else carries this shape.

The head also carries master (837fad4f), which the previous one did not: it was one commit behind, so a green run there would have described a tree that could no longer be merged. The branch had no standing votes, so this refresh spent none.

CI: pending.

@argszero

Copy link
Copy Markdown
Owner Author

CI cause for the Windows leg, measured from job 119765557482 (no verdict — this head is parked for a later cycle).

test-windows fails in the GUI unit tests: test/boot-contract.test.js:277, case
issue #2484: askSessionCwd asks, judges, and never falls back to the home directory
(AssertionError: it must send the daemon's own command, operator: match, actual: '').

The subject body is delimited by

src.slice(start, src.indexOf("\n  }\n", start) + 4)

The Windows runner checks out with CRLF, so that sentinel does not occur, indexOf answers -1,
and slice(start, 3) is the empty string. Every assert.match over that body then reads ''.
The Linux and macOS legs pass because their checkout is LF, and the Python tests step passed on
Windows as well, so this is the only failing assertion in the run.

A fix that does not depend on the line ending: normalise once in the test's read() helper
(text.replace(/\r\n/g, "\n")), or delimit on /\n \}\r?\n/.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20261022-183149

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20261022-185533

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20261022-190957

The diff is the GUI open path only (main.js, daemon_client.js) plus its two test files and the
Agent.md GUI test-count line, whose parts add up to its total (161 → 166, the five new tests).
pickSessionCwd treats a non-empty index answer as a fact and returns the caller's candidate for
anything else — an unknown, an empty or non-string cwd, a frame it cannot read — so a guess never
acquires the authority of the daemon's answer, and the home directory is never a return value.

No assertion was removed or weakened: the only pre-existing line replaced in boot-contract.test.js
is the test helper's getter (read), which now normalises CRLF; every assert.match in that file
still reads the text it read before. Both CI legs pass (test 2m7s, test-windows 5m17s).

@argszero
argszero merged commit 67e9cfe into master Oct 22, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

emrg: the GUI never asks the daemon where a session lives, so an unplaced id opens at the home directory

1 participant