mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 10:39:32 +01:00
fix(edit-history): remote reads get time for their size, follow CLAUDE_CONFIG_DIR, keep the conversation after /clear
Review findings on the remote edit history: - A forward read may carry 13 MiB and "Load older" 29 MiB; the flat 30 s read timeout cut those off below ~1 MiB/s. The timeout now adds the transfer time at 256 KiB/s (remoteEditSourcesTimeoutMs). It ends a hung read, it does not cut a slow one. - The transcript lookup looked only under $HOME/.claude. It now uses an absolute CLAUDE_CONFIG_DIR of the ssh command's environment, else $HOME/.claude, for both the projects and the live sessions/<pid>.json. A value set only in .zshrc stays invisible (the lookup is a plain sh); documented, and the panel's remote no-transcript note says so instead of promising the next prompt (a remote host sends no hooks). - After a remote /clear claudeSessionId stays the launch id, so once the remote claude exited the lookup fell back to the cleared conversation and the panel flipped back. The sync now hands the lookup the stored conversation, tried right after the live one (remoteTranscriptIds). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
c4ba14235f
commit
0a6a877415
10 files changed
+169
-36
No files matched your search
@@ -84,8 +84,12 @@ export interface EditHistorySession {
|
||||
}
|
||||
|
||||
export interface EditHistorySyncDeps {
|
||||
/** The session's main transcript path, or null when it has none yet. */
|
||||
transcriptPath(session: EditHistorySession): Promise<string | null>;
|
||||
/**
|
||||
* The session's main transcript path, or null when it has none yet. `storedTranscriptId` is
|
||||
* the conversation the stored history holds (null before the first read); see
|
||||
* {@link remoteTranscriptIds}.
|
||||
*/
|
||||
transcriptPath(session: EditHistorySession, storedTranscriptId: string | null): Promise<string | null>;
|
||||
/** The session's transcripts (a remote session's on its host). */
|
||||
readSources(
|
||||
session: EditHistorySession,
|
||||
@@ -108,6 +112,18 @@ export interface SyncOptions {
|
||||
older?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* The conversation ids a REMOTE session's transcript lookup tries, in order, after the one its
|
||||
* claude writes now. No hook tells Codeman of a remote `/clear`, so `claudeSessionId` stays the
|
||||
* launch id; tried first, it would flip the panel back to the cleared conversation as soon as
|
||||
* the remote claude exits. The stored history's conversation is the newest the panel has read.
|
||||
*/
|
||||
export function remoteTranscriptIds(session: EditHistorySession, storedTranscriptId: string | null): string[] {
|
||||
return [...new Set([storedTranscriptId, session.claudeSessionId || session.id, session.id])].filter(
|
||||
(id): id is string => !!id
|
||||
);
|
||||
}
|
||||
|
||||
/** Why a session has no edit history, or undefined when it can have one. */
|
||||
export function unsupportedReason(session: EditHistorySession): string | undefined {
|
||||
if (session.docker) return 'docker';
|
||||
@@ -329,7 +345,7 @@ export class EditHistorySync {
|
||||
try {
|
||||
// This slot's store: a run still in flight after `remove()` cannot reach a later one.
|
||||
const store = await this.storeOf(session.id, slot);
|
||||
const path = slot.gone ? null : await this.deps.transcriptPath(session);
|
||||
const path = slot.gone ? null : await this.deps.transcriptPath(session, store.getTranscriptId());
|
||||
let found: boolean;
|
||||
if (!path) {
|
||||
found = false;
|
||||
|
||||
@@ -363,17 +363,18 @@ Object.assign(CodemanApp.prototype, {
|
||||
return;
|
||||
}
|
||||
if (data.state === 'unavailable') {
|
||||
body.append(
|
||||
el(
|
||||
'div',
|
||||
'edit-history-note edit-history-note--warn',
|
||||
data.reason === 'no-transcript'
|
||||
// A remote host sends no hooks: its transcript is only ever found by the lookup over ssh.
|
||||
const note =
|
||||
data.reason !== 'no-transcript'
|
||||
? t('Showing saved entries only: {reason}', { reason: data.reason || '' })
|
||||
: this.sessions?.get?.(h.sessionId)?.remote
|
||||
? t(
|
||||
'No transcript found for this session yet. It is picked up with the next prompt; until then a CLAUDE_CONFIG_DIR set outside Codeman cannot be found.'
|
||||
'No transcript found on the remote host yet (Claude writes it with the first prompt). A CLAUDE_CONFIG_DIR set only in an interactive shell profile (.zshrc) cannot be found.'
|
||||
)
|
||||
: t('Showing saved entries only: {reason}', { reason: data.reason || '' })
|
||||
)
|
||||
);
|
||||
: t(
|
||||
'No transcript found for this session yet. It is picked up with the next prompt; until then a CLAUDE_CONFIG_DIR set outside Codeman cannot be found.'
|
||||
);
|
||||
body.append(el('div', 'edit-history-note edit-history-note--warn', note));
|
||||
}
|
||||
|
||||
const all = Array.isArray(data.entries) ? data.entries : [];
|
||||
|
||||
@@ -570,6 +570,8 @@
|
||||
'Showing saved entries only: {reason}': '仅显示已保存的条目:{reason}',
|
||||
'No transcript found for this session yet. It is picked up with the next prompt; until then a CLAUDE_CONFIG_DIR set outside Codeman cannot be found.':
|
||||
'尚未找到此会话的记录。它会在下一次提示时被识别;在此之前,在 Codeman 之外设置的 CLAUDE_CONFIG_DIR 无法被找到。',
|
||||
'No transcript found on the remote host yet (Claude writes it with the first prompt). A CLAUDE_CONFIG_DIR set only in an interactive shell profile (.zshrc) cannot be found.':
|
||||
'远程主机上尚未找到记录(Claude 会在第一次提示时写入)。仅在交互式 shell 配置文件(.zshrc)中设置的 CLAUDE_CONFIG_DIR 无法被找到。',
|
||||
'No edits yet.': '暂无编辑。',
|
||||
'No edits by the main agent.': '主智能体没有编辑。',
|
||||
'Load older': '加载更早的编辑',
|
||||
|
||||
+15
-8
@@ -137,7 +137,7 @@ import { probePathKind } from '../utils/bounded-path-probe.js';
|
||||
import { defaultDockerWorkspaces } from './routes/git-status-routes.js';
|
||||
import { composerIsEmpty, plainFrame } from '../session-submit-verifier.js';
|
||||
import { detectUsageLimitPause } from '../usage-limit-patterns.js';
|
||||
import { EditHistorySync } from './edit-history-sync.js';
|
||||
import { EditHistorySync, remoteTranscriptIds } from './edit-history-sync.js';
|
||||
import { EditHistoryStore } from '../edit-history-store.js';
|
||||
import { probeLocalPaths, readClaudeEditSources, resolveClaudeTranscript } from '../edit-history-local-source.js';
|
||||
import { remoteLocateClaudeTranscript, remoteProbePaths, remoteReadClaudeEditSources } from '../remote-files.js';
|
||||
@@ -362,15 +362,22 @@ export class WebServer extends EventEmitter {
|
||||
* A local session's main transcript is the one the transcript watcher follows (hook-reported
|
||||
* path), or, before the first hook after a restart, a lookup under the session's claude
|
||||
* config dir. A remote session sends no hooks: its host is asked over ssh for the transcript
|
||||
* the pane's claude writes (remote-files.ts), and is never woken for it.
|
||||
* the pane's claude writes, else the stored conversation's (`remoteTranscriptIds`), and is
|
||||
* never woken for it (remote-files.ts).
|
||||
*/
|
||||
private readonly editHistorySync = new EditHistorySync({
|
||||
transcriptPath: (session) => {
|
||||
const ids = [session.claudeSessionId || session.id, session.id];
|
||||
return session.remote
|
||||
? remoteLocateClaudeTranscript(session.remote, ids, remoteTmuxLocation(session.remote, session.id))
|
||||
: resolveClaudeTranscript(this.transcriptWatchers.get(session.id)?.getPath(), ids, session.claudeConfigDir);
|
||||
},
|
||||
transcriptPath: (session, storedTranscriptId) =>
|
||||
session.remote
|
||||
? remoteLocateClaudeTranscript(
|
||||
session.remote,
|
||||
remoteTranscriptIds(session, storedTranscriptId),
|
||||
remoteTmuxLocation(session.remote, session.id)
|
||||
)
|
||||
: resolveClaudeTranscript(
|
||||
this.transcriptWatchers.get(session.id)?.getPath(),
|
||||
[session.claudeSessionId || session.id, session.id],
|
||||
session.claudeConfigDir
|
||||
),
|
||||
readSources: (session, mainPath, requests, budget, opts) =>
|
||||
session.remote
|
||||
? remoteReadClaudeEditSources(session.remote, mainPath, requests, budget, opts)
|
||||
|
||||
Reference in new issue
Block a user