fix(deepseek): review fixes for the transcript reader — docker/remote gate, poll memo, honest pairing docs

Four review findings on the worker-transcript feature:

- Docker and remote-SSH dsh sessions now keep the pane segmenter: their
  transcripts live in the container's / remote host's own ~/.dsh, which
  the local reader can never see, so the transcript path returned
  'nothing said yet' forever and an agent polling such a worker starved
  on an answer that existed. Gated on !session.docker && !session.remote
  (statically pinned) and documented in the integration guide.

- last-response reads are memoized on (path, mtime, size, blocks): the
  skill's last_text polls once per second, and each poll decompressed and
  reparsed the whole file on the event loop even when nothing had been
  appended. An unchanged poll now costs one stat.

- The pairing ladder's comment claimed /new is served by step 2; in truth
  the boot-window transcript wins for as long as it exists (deliberately:
  preferring newest-eligible would hand a worker its busier sibling's
  reply). The comment now states the real tradeoff instead of the
  aspirational one. Same for decodeZstdFrames' 'skipped' wording — a
  corrupt frame truncates the decode there, which is the safe behavior.

- stripReasoningPrefix no longer runs on user prompt text, so a prompt
  containing a literal </think> renders whole in blocks view.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-08-25 18:30:41 +02:00
parent 6261b6f655
commit 015b865f56
5 changed files with 112 additions and 12 deletions
+12
View File
@@ -249,6 +249,18 @@ describe('DeepSeek status bridge', () => {
expect(server).toContain("if (!session || session.mode !== 'claude') return;");
});
it('keeps the transcript reader off docker and remote-SSH sessions', () => {
// A docker case's harness writes its transcript inside the CONTAINER's
// ~/.dsh and a remote-SSH case's lives on the remote host, so the local
// reader would scan a $DSH_HOME that can never hold the file and return
// "nothing said yet" forever — starving an agent that polls the worker.
// Those sessions must keep the pane segmenter. Static, because standing up
// a docker/remote session in the unit harness is exactly what the tmux
// test-mode mocks exist to avoid.
const routes = readFileSync(join(process.cwd(), 'src/web/routes/session-routes.ts'), 'utf-8');
expect(routes).toMatch(/session\.mode === 'deepseek' && !session\.docker && !session\.remote/);
});
it('maps the harness lifecycle states onto real hook events', () => {
expect(DEEPSEEK_STATE_TO_HOOK_EVENT.idle).toBe('stop');
expect(DEEPSEEK_STATE_TO_HOOK_EVENT.blocked).toBe('permission_prompt');
+34
View File
@@ -23,6 +23,7 @@ import {
findDeepSeekTranscript,
parseDeepSeekTranscript,
readDeepSeekLastResponse,
resetDeepSeekTranscriptMemoForTest,
resolveDeepSeekHome,
zstdFrameRanges,
zstdSupported,
@@ -358,4 +359,37 @@ describe.skipIf(!zstdSupported())('session-to-transcript pairing', () => {
});
expect(result).toEqual({ text: '', timestamp: '', blocks: [] });
});
it('serves an unchanged transcript from the memo and re-reads when the stat moves', async () => {
const home = await mkdtemp(join(tmpdir(), 'dsh-home-'));
try {
const dir = join(home, 'sessions', '--home-tester-cases-worker-1--', 'memo');
await mkdir(dir, { recursive: true });
const path = join(dir, 'session.jsonl');
const stamp = new Date(sessionStart + 1_000);
const read = () =>
readDeepSeekLastResponse({ workingDir: workspace, createdAt: sessionStart, deepSeekHomeOverride: home });
const body = (answer: string) =>
`${[sessionHeader(workspace, sessionStart + 1_000), assistantMessage(answer), turnEnd(1, { kind: 'completed' })].join('\n')}\n`;
resetDeepSeekTranscriptMemoForTest();
await writeFile(path, body('AAAA'));
await utimes(path, stamp, stamp);
expect((await read())?.text).toBe('AAAA');
// Same byte length, same forced mtime: indistinguishable from unchanged
// by stat, and deliberately served from the memo — the 1s/poll skill loop
// must not decode an unchanged file, and dsh only ever APPENDS, so a
// same-stat rewrite does not exist outside a test.
await writeFile(path, body('BBBB'));
await utimes(path, stamp, stamp);
expect((await read())?.text).toBe('AAAA');
// An append moves mtime (and normally size), which is the invalidation.
await utimes(path, new Date(sessionStart + 2_000), new Date(sessionStart + 2_000));
expect((await read())?.text).toBe('BBBB');
} finally {
await rm(home, { recursive: true, force: true });
}
});
});