Skip to content

Dev#1

Merged
im4codes merged 24 commits intomasterfrom
dev
Apr 5, 2026
Merged

Dev#1
im4codes merged 24 commits intomasterfrom
dev

Conversation

@im4codes
Copy link
Copy Markdown
Owner

@im4codes im4codes commented Apr 5, 2026

merge

IM.codes and others added 24 commits April 5, 2026 18:29
…imeout

- PDF: replace ?raw blob URL with globalThis.pdfjsWorker bypass (avoids nginx MIME + blob import issues)
- Sub-session: merge metadata updates in subsession.created handler instead of skipping existing IDs
- P2P: always save hopTimeoutMinutes explicitly, fix null guards in send paths
- Cleanup: remove duplicate P2P config block in SessionControls

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ndow

- Add runtimeType to sessionInfo so SessionControls renders Stop for transport sessions
- Expand UsageFooter render condition to include plan/quota badge metadata

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove subSessionsFull stripping memo, pass full SubSession[] to PanelRenderContext
- Import SubSession type directly in PanelRenderContext (prevents future type drift)
- Add compact UsageFooter to pinned SubSessionContent with model/plan/quota display
- Document compact panel contract (intentional inclusions/exclusions)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…le store data

- buildSubSessionSync: compute planLabel/quotaLabel/quotaUsageLabel fresh via
  getQwenDisplayMetadata (same as buildSessionList for main sessions)
- refreshQwenQuotaUsageLabels: re-sync sub-sessions to browser on quota update
- Revert parent-session inheritance hack — daemon provides fresh data directly

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Combine quotaLabel + quotaUsageLabel into single inline text (9px, gray)
- Plan badge (Free/Paid/BYO) stays as pill badge on the right
- Layout: "1,000/day · today 12/1000" [Free]

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Plan/quota (e.g. "1,000/day · today 12/1000  Free") now inline small text
  in shortcuts row, left of model switcher and Solo button
- Removed from UsageFooter to avoid duplication
- Pinned panel: compact inline badge row (no SessionControls there)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…refix

On Windows with ConPTY, respawnSession was prepending
  `export IMCODES_SESSION='...'; `
to the command, then passing it through cmd.exe /c — which doesn't
understand POSIX `export` syntax, causing the respawn to fail.

Fix: extend respawnPane / conptyRespawnPane / conptyNewSession to accept
an env map.  When BACKEND === 'conpty', pass mergedEnv directly to
conptyNewSession so node-pty injects it as proper process env vars.
Keep the existing envPrefix bash-string path for tmux/wezterm.

Tests: 4 new assertions lock the conptyRespawnPane env injection contract.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e flake on macOS

codex-watcher-refresh: after writing newerSameUuid, explicitly advance its
mtime +2s via utimes() so checkNewer() works correctly on HFS+ (1-second
mtime granularity). Without this the test is flaky on macOS CI when both
writes land within the same second.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
emitRecentHistory was added unconditionally to startWatchingSpecificFile
(codex-watcher) and activateFile (jsonl-watcher) in the no-text refresh
commit, causing a full replay of previous session messages whenever codex
or claude-code sessions are respawned by the health poller.

Fix: add replayHistory option (default false) to startWatchingSpecificFile,
startWatchingFile, startWatching, and activateFile. Only the daemon-restart
restore paths in restoreFromStore() pass replayHistory: true — these are the
cases where the browser genuinely needs to see recent history after
reconnecting. Session respawn/restart paths do not replay.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add regression tests to both codex-watcher and jsonl-watcher that assert:
- startWatchingSpecificFile / startWatchingFile do NOT emit pre-existing
  content by default (replayHistory defaults to false)
- Only when replayHistory: true is explicitly passed (daemon restore path)
  is historical content replayed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
History is persisted in the timeline store and served to browsers via
the timeline replay endpoint. Replaying JSONL/rollout files at watcher
startup was always wrong — it duplicates events the browser already
received, regardless of whether the daemon or session restarted.

Remove replayHistory option and all emitRecentHistory calls from
startWatchingSpecificFile, startWatchingFile, startWatching, and
activateFile. Watchers now exclusively track new content from fileOffset.

Tests updated to assert: watcher startup never emits pre-existing content.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…m startup

Export emitRecentHistory so tests can call it directly. Update
emitRecentHistory and stable-eventId tests to not rely on watcher
startup triggering history replay (which was removed per no-replay rule).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On macOS APFS (nanosecond mtime) fileB was newer than fileA because it
was written a few ns later in beforeEach. watchFile saw fileB as newer
and switched the 'jsonl-a' watcher away from fileA, so refresh() drained
fileB (empty) and never emitted the expected event.

Fix: create fileB first, then fileA, and use utimes to advance fileA's
mtime +2s past fileB — same technique used for the codex-watcher HFS+
fix. Guards both nanosecond-precision APFS and 1-second HFS+ resolvers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…indows

When the daemon launches via a Windows Scheduled Task or Startup shortcut,
the restricted environment cannot resolve 'cmd.exe' through PATH, causing
CreateProcess to fail with 'File not found' and all sessions to enter the
error state with a runaway restart loop.

Fix: resolve cmd.exe via COMSPEC env var (always set by Windows), falling
back to %SystemRoot%\system32\cmd.exe. Also convert CWD backslashes to
forward slashes to avoid node-pty's internal path.resolve error 267 on
some versions.

Tests: 3 new cases for COMSPEC resolution, fallback path, and CWD slash
normalization.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
conpty: update 7 spawn assertions from bare 'cmd.exe' to
stringMatching(/cmd\.exe$/i) so they pass on actual Windows where the
absolute COMSPEC path is used (e.g. C:\Windows\system32\cmd.exe).

p2p-orchestrator: afterEach now cancels all active runs before deleting
the temp dir. Background async ops (idle polls, file reads) from parallel
hops were still in flight when the dir was deleted, producing unhandled
ENOENT promise rejections that Vitest classified as test failures.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…codex-watcher-retrack

checkNewer() returns false when both files are created within the same second on
HFS+ (1s resolution). Use utimes() to set runningFile mtime +2s past oldFile
so maybeSwitchActiveFile() reliably detects it as newer on all platforms.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three root causes fixed:

1. **Duplicate watchdog loops**: restartWindowsDaemon only killed the daemon
   node process, leaving the old watchdog cmd.exe alive. The old watchdog
   respawned the daemon (with stale code) while a new watchdog was also
   spawned — causing duplicate loops and version-mismatch restarts.
   Fix: kill the entire watchdog process tree (taskkill /f /t) before
   spawning a fresh hidden watchdog.

2. **schtasks /Create with visible window**: bind created the scheduled
   task with a bare node command (/TR "node.exe ... start --foreground"),
   then tried /Change to VBS. If /Change failed silently, every login
   launched a visible cmd.exe window.
   Fix: create the task directly with /TR "wscript launcher.vbs".

3. **Upgrade cleanup popups**: upgrade batch used `start "" cmd /c cleanup`
   which flashes a visible window.
   Fix: use `start "" /min cmd /c cleanup` to minimize.

Also: VBS launcher now prioritized over scheduled task in restart flow,
and all spawn() calls use windowsHide: true.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…unused vars

- Rewrite windows-daemon.test.ts to match new killWatchdogTree behavior
  (wmic parent lookup + taskkill /t tree-kill instead of bare taskkill)
- Update test priority: VBS first, then schtask, then startup shortcut
- Add test for force-kill fallback when tree-kill misses daemon
- Remove unused nodeExe/imcodesScript in bind-flow (schtasks now uses VBS directly)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@im4codes im4codes merged commit 2a3a352 into master Apr 5, 2026
37 of 38 checks passed
im4codes pushed a commit that referenced this pull request Apr 21, 2026
…of being overridden by them

Observed failure: user set global rule "Always commit and push if asked!"
in the supervision defaults. A session hit idle with uncommitted work;
user asked "提交了么?" (did you commit?), agent answered "还没提交" (no).
Supervisor returned `complete` — rule was never enforced.

Root cause — two heuristics in the decision-prompt rule list were
structurally able to defeat any user rule:

  1. "A factual answer to a user question ... is typically complete for
     that turn; the user asked a question, the agent answered it. Do not
     treat state reports as proposed work."
  2. "A user-set supervision rule phrased conditionally ('if asked',
     'when X') is conditional. Check whether the condition actually
     fires in the current turn before using it to justify continue."

The arbiter LLM took "Always commit and push if asked!" at heuristic #2's
narrowest reading ("the user didn't literally command 'commit it' this
turn → condition didn't fire") and combined it with heuristic #1 to
justify `complete` on the Q-and-A turn. Result: the user's enforce-this
rule was silently downgraded to "advice the arbiter may ignore".

Fix — reorder and rewrite:

- New top-of-list clause: "USER-SET SUPERVISION RULES ARE AUTHORITATIVE."
  This is the first decision rule the arbiter reads. It says the user-
  rules block overrides the generic heuristics below it, gives concrete
  worked examples for:
    * commit/push rules (matches the current failure mode verbatim)
    * blanket wording ("always", "每次", "必须", "绝不") → unconditional
    * conditional wording ("if asked", "when X", "如果", "当") →
      interpret GENEROUSLY in the user's favor: the topic appearing in
      the conversation IS the condition firing.
- Heuristic #1 ("factual Q&A → complete") now explicitly reads
  "typically complete for that turn IF no user-set rule applies" — so
  it still covers ordinary questions but stops poaching turns that a
  user rule governs.
- Heuristic #2 (the conditional-rule escape hatch) is removed; its
  responsibility is folded into the authoritative clause, which now
  owns all conditional-rule handling from the user-rules-always-win
  side.
- Repair prompt mirrors the same clause so JSON-invalid fallbacks
  can't drop back into the old behavior.

All 71 existing supervision prompt / config / broker tests stay green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
im4codes pushed a commit that referenced this pull request Apr 25, 2026
…"Agent working" for shell

Two related root causes for "shell terminal 断流, 必须刷新页面才能恢复":

1) Server's `handleQueueOverflow` called `removeBrowserSessionSubscription`
   on every overflow. Heavy shell stdout (or a slow browser socket) created
   a churn cycle:
     stdout flood → 1MB queue full → overflow → server unsubscribes →
     daemon stops pipe-pane → client receives stream_reset → resubscribes →
     daemon restarts pipe → flood again → overflow again …
   Each cycle bumped the client's resetState count; once cooldown engaged
   (8 resets in 60s) the terminal sat frozen for 5s, and if output kept
   flowing the cooldown re-engaged indefinitely. Refresh recovered because
   it bypassed the accumulated cooldown state.

   Fix: on overflow, send terminal.stream_reset and replace the per-(session,
   ws) queue with a fresh accounting state, but DO NOT unsubscribe.
   Subscription stays alive so the daemon never tears down its pipe and the
   client receives subsequent frames immediately. Orphaned in-flight
   callbacks from the old queue still decrement only their own counter
   (harmless, GC'd later).

2) Shell + script sessions are not "agents" — they're plain terminals. The
   daemon's terminal-streamer fires `session.state(running)` on any pipe
   output (idle detection runs without a structured watcher), so running
   `top`, `tail -f`, anything chatty, surfaced as "🤖 Agent working..." in
   the footer. Confusing wording, and combined with #1's broken stream the
   user saw a frozen terminal labeled "Agent working" — a worst-of-both-
   worlds experience.

   Fix: gate the entire UsageFooter live-status block on `agentType !==
   'shell' && !== 'script'`. Also early-out shouldShowFooter for these
   types so the footer doesn't render at all (shell has no token usage,
   no quota, no cost — the footer was always content-empty for shell).

Client-side defensive change: on `terminal.stream_reset`, request a fresh
`terminal.snapshot_request` synchronously before the existing resubscribe
backoff fires. With #1 fixed the subscription is healthy across overflow,
so a snapshot is the cheapest way to redraw the dropped frames within one
round-trip. During cooldown we skip the request to avoid hammering the
server when it's already overwhelmed.

Regression tests added (7):
  - server: overflow does NOT send terminal.unsubscribe to daemon, and
    a fresh queue continues to forward subsequent frames
  - server: overflow → fresh-queue path keeps the second forward alive
  - ws-client: stream_reset fires terminal.snapshot_request synchronously
  - ws-client: cooldown suppresses snapshot_request to avoid hammering
  - usage-footer: shell session with sessionState=running shows no
    "Agent working..." text and no live-status inline span
  - usage-footer: same for script
  - usage-footer: shell idle does not show "Agent idle..." either

Full suite: 3816 pass (was 3809), daemon test:unit clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant