@phenx-inc/ctlsurf 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/headless/index.mjs +344 -55
- package/out/headless/index.mjs.map +4 -4
- package/out/main/index.js +273 -7
- package/package.json +1 -1
- package/src/main/orchestrator.ts +35 -7
- package/src/main/transcripts.ts +341 -0
- package/src/main/tui.ts +39 -17
package/out/main/index.js
CHANGED
|
@@ -6040,6 +6040,248 @@ function trimToLogLimit(str) {
|
|
|
6040
6040
|
return `[truncated]
|
|
6041
6041
|
${str.slice(str.length - MAX_LOG_CHARS)}`;
|
|
6042
6042
|
}
|
|
6043
|
+
function supportsTranscriptLogging(agentId) {
|
|
6044
|
+
return agentId === "claude" || agentId === "codex";
|
|
6045
|
+
}
|
|
6046
|
+
const POLL_INTERVAL_MS = 1e3;
|
|
6047
|
+
const DISCOVERY_SLACK_MS = 1e4;
|
|
6048
|
+
const READ_CHUNK_BYTES = 64 * 1024;
|
|
6049
|
+
const MAX_ENTRY_CHARS = 2e4;
|
|
6050
|
+
class TranscriptTailer {
|
|
6051
|
+
agentId;
|
|
6052
|
+
cwd;
|
|
6053
|
+
sink;
|
|
6054
|
+
claudeProjectsDir;
|
|
6055
|
+
codexSessionsDir;
|
|
6056
|
+
files = /* @__PURE__ */ new Map();
|
|
6057
|
+
pollTimer = null;
|
|
6058
|
+
sinceMs = 0;
|
|
6059
|
+
constructor(options) {
|
|
6060
|
+
this.agentId = options.agentId;
|
|
6061
|
+
this.cwd = stripTrailingSep(options.cwd);
|
|
6062
|
+
this.sink = options.sink;
|
|
6063
|
+
this.claudeProjectsDir = options.claudeProjectsDir || path.join(os.homedir(), ".claude", "projects");
|
|
6064
|
+
this.codexSessionsDir = options.codexSessionsDir || path.join(os.homedir(), ".codex", "sessions");
|
|
6065
|
+
}
|
|
6066
|
+
start() {
|
|
6067
|
+
if (this.pollTimer) return;
|
|
6068
|
+
this.sinceMs = Date.now();
|
|
6069
|
+
this.files.clear();
|
|
6070
|
+
this.pollTimer = setInterval(() => this.poll(), POLL_INTERVAL_MS);
|
|
6071
|
+
console.log(`[transcripts] Tailing ${this.agentId} transcripts for ${this.cwd}`);
|
|
6072
|
+
}
|
|
6073
|
+
stop() {
|
|
6074
|
+
if (!this.pollTimer) return;
|
|
6075
|
+
clearInterval(this.pollTimer);
|
|
6076
|
+
this.pollTimer = null;
|
|
6077
|
+
this.poll();
|
|
6078
|
+
this.files.clear();
|
|
6079
|
+
console.log("[transcripts] Stopped");
|
|
6080
|
+
}
|
|
6081
|
+
poll() {
|
|
6082
|
+
try {
|
|
6083
|
+
this.discover();
|
|
6084
|
+
for (const [filePath, tail] of this.files) {
|
|
6085
|
+
if (!tail.excluded) this.drainFile(filePath, tail);
|
|
6086
|
+
}
|
|
6087
|
+
} catch (err) {
|
|
6088
|
+
console.error("[transcripts] Poll error:", err);
|
|
6089
|
+
}
|
|
6090
|
+
}
|
|
6091
|
+
// ─── Discovery ──────────────────────────────────
|
|
6092
|
+
/**
|
|
6093
|
+
* Track every transcript file with recent activity, not just the first
|
|
6094
|
+
* match: /clear (Claude) or /new (Codex) starts a new session file in the
|
|
6095
|
+
* middle of one PTY run, and tailing all active candidates handles the
|
|
6096
|
+
* switch without special cases. Old idle files never match (stale mtime).
|
|
6097
|
+
*/
|
|
6098
|
+
discover() {
|
|
6099
|
+
const dirs = this.agentId === "claude" ? [this.claudeProjectsDirForCwd()] : this.codexDateDirs();
|
|
6100
|
+
for (const dir of dirs) {
|
|
6101
|
+
let names;
|
|
6102
|
+
try {
|
|
6103
|
+
names = fs.readdirSync(dir);
|
|
6104
|
+
} catch {
|
|
6105
|
+
continue;
|
|
6106
|
+
}
|
|
6107
|
+
for (const name of names) {
|
|
6108
|
+
if (!name.endsWith(".jsonl")) continue;
|
|
6109
|
+
if (this.agentId === "codex" && !name.startsWith("rollout-")) continue;
|
|
6110
|
+
const filePath = path.join(dir, name);
|
|
6111
|
+
if (this.files.has(filePath)) continue;
|
|
6112
|
+
try {
|
|
6113
|
+
const stat = fs.statSync(filePath);
|
|
6114
|
+
if (stat.mtimeMs >= this.sinceMs - DISCOVERY_SLACK_MS) {
|
|
6115
|
+
this.files.set(filePath, { offset: 0, remainder: "", excluded: false });
|
|
6116
|
+
}
|
|
6117
|
+
} catch {
|
|
6118
|
+
}
|
|
6119
|
+
}
|
|
6120
|
+
}
|
|
6121
|
+
}
|
|
6122
|
+
claudeProjectsDirForCwd() {
|
|
6123
|
+
const slug = this.cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
6124
|
+
return path.join(this.claudeProjectsDir, slug);
|
|
6125
|
+
}
|
|
6126
|
+
codexDateDirs() {
|
|
6127
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
6128
|
+
for (const ms of [this.sinceMs, Date.now()]) {
|
|
6129
|
+
const d = new Date(ms);
|
|
6130
|
+
const yyyy = String(d.getFullYear());
|
|
6131
|
+
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
|
6132
|
+
const dd = String(d.getDate()).padStart(2, "0");
|
|
6133
|
+
dirs.add(path.join(this.codexSessionsDir, yyyy, mm, dd));
|
|
6134
|
+
}
|
|
6135
|
+
return [...dirs];
|
|
6136
|
+
}
|
|
6137
|
+
// ─── Tailing ────────────────────────────────────
|
|
6138
|
+
drainFile(filePath, tail) {
|
|
6139
|
+
let size;
|
|
6140
|
+
try {
|
|
6141
|
+
size = fs.statSync(filePath).size;
|
|
6142
|
+
} catch {
|
|
6143
|
+
return;
|
|
6144
|
+
}
|
|
6145
|
+
if (size <= tail.offset) return;
|
|
6146
|
+
let fd;
|
|
6147
|
+
try {
|
|
6148
|
+
fd = fs.openSync(filePath, "r");
|
|
6149
|
+
} catch {
|
|
6150
|
+
return;
|
|
6151
|
+
}
|
|
6152
|
+
try {
|
|
6153
|
+
const buf = Buffer.alloc(READ_CHUNK_BYTES);
|
|
6154
|
+
while (tail.offset < size && !tail.excluded) {
|
|
6155
|
+
const bytesRead = fs.readSync(fd, buf, 0, READ_CHUNK_BYTES, tail.offset);
|
|
6156
|
+
if (bytesRead <= 0) break;
|
|
6157
|
+
tail.offset += bytesRead;
|
|
6158
|
+
tail.remainder += buf.toString("utf-8", 0, bytesRead);
|
|
6159
|
+
const lines = tail.remainder.split("\n");
|
|
6160
|
+
tail.remainder = lines.pop() || "";
|
|
6161
|
+
for (const line of lines) {
|
|
6162
|
+
this.handleLine(line, tail);
|
|
6163
|
+
if (tail.excluded) break;
|
|
6164
|
+
}
|
|
6165
|
+
}
|
|
6166
|
+
} catch (err) {
|
|
6167
|
+
console.error(`[transcripts] Read error for ${filePath}:`, err);
|
|
6168
|
+
} finally {
|
|
6169
|
+
try {
|
|
6170
|
+
fs.closeSync(fd);
|
|
6171
|
+
} catch {
|
|
6172
|
+
}
|
|
6173
|
+
}
|
|
6174
|
+
}
|
|
6175
|
+
handleLine(line, tail) {
|
|
6176
|
+
const trimmed = line.trim();
|
|
6177
|
+
if (!trimmed) return;
|
|
6178
|
+
let obj;
|
|
6179
|
+
try {
|
|
6180
|
+
obj = JSON.parse(trimmed);
|
|
6181
|
+
} catch {
|
|
6182
|
+
return;
|
|
6183
|
+
}
|
|
6184
|
+
const entry = this.agentId === "claude" ? this.parseClaudeLine(obj) : this.parseCodexLine(obj, tail);
|
|
6185
|
+
if (!entry) return;
|
|
6186
|
+
const ms = Date.parse(entry.ts);
|
|
6187
|
+
if (Number.isFinite(ms) && ms < this.sinceMs - DISCOVERY_SLACK_MS) return;
|
|
6188
|
+
this.sink({ ...entry, content: capLength(entry.content) });
|
|
6189
|
+
}
|
|
6190
|
+
// ─── Claude Code format ─────────────────────────
|
|
6191
|
+
parseClaudeLine(obj) {
|
|
6192
|
+
if (!obj || typeof obj !== "object") return null;
|
|
6193
|
+
if (obj.isMeta) return null;
|
|
6194
|
+
if (obj.type !== "user" && obj.type !== "assistant") return null;
|
|
6195
|
+
if (typeof obj.cwd === "string" && stripTrailingSep(obj.cwd) !== this.cwd) return null;
|
|
6196
|
+
if (obj.isSidechain) return null;
|
|
6197
|
+
const message = obj.message;
|
|
6198
|
+
if (!message) return null;
|
|
6199
|
+
const text = extractClaudeText(message.content, obj.type === "user");
|
|
6200
|
+
if (!text) return null;
|
|
6201
|
+
return {
|
|
6202
|
+
ts: typeof obj.timestamp === "string" ? obj.timestamp : (/* @__PURE__ */ new Date()).toISOString(),
|
|
6203
|
+
type: obj.type === "user" ? "user_input" : "terminal_output",
|
|
6204
|
+
content: text
|
|
6205
|
+
};
|
|
6206
|
+
}
|
|
6207
|
+
// ─── Codex CLI format ───────────────────────────
|
|
6208
|
+
parseCodexLine(obj, tail) {
|
|
6209
|
+
if (!obj || typeof obj !== "object") return null;
|
|
6210
|
+
const payload = obj.payload;
|
|
6211
|
+
if (obj.type === "session_meta") {
|
|
6212
|
+
const metaCwd = payload?.cwd;
|
|
6213
|
+
if (typeof metaCwd === "string" && stripTrailingSep(metaCwd) !== this.cwd) {
|
|
6214
|
+
tail.excluded = true;
|
|
6215
|
+
}
|
|
6216
|
+
return null;
|
|
6217
|
+
}
|
|
6218
|
+
if (obj.type !== "event_msg" || !payload || typeof payload !== "object") return null;
|
|
6219
|
+
let type;
|
|
6220
|
+
if (payload.type === "user_message") {
|
|
6221
|
+
type = "user_input";
|
|
6222
|
+
} else if (payload.type === "agent_message") {
|
|
6223
|
+
type = "terminal_output";
|
|
6224
|
+
} else {
|
|
6225
|
+
return null;
|
|
6226
|
+
}
|
|
6227
|
+
const text = typeof payload.message === "string" ? payload.message.trim() : "";
|
|
6228
|
+
if (!text || isCodexNoise(text)) return null;
|
|
6229
|
+
return {
|
|
6230
|
+
ts: typeof obj.timestamp === "string" ? obj.timestamp : (/* @__PURE__ */ new Date()).toISOString(),
|
|
6231
|
+
type,
|
|
6232
|
+
content: text
|
|
6233
|
+
};
|
|
6234
|
+
}
|
|
6235
|
+
}
|
|
6236
|
+
const CLAUDE_NOISE_PREFIXES = [
|
|
6237
|
+
"<local-command-caveat>",
|
|
6238
|
+
"<command-name>",
|
|
6239
|
+
"<command-message>",
|
|
6240
|
+
"<local-command-stdout>",
|
|
6241
|
+
"<bash-input>",
|
|
6242
|
+
"<bash-stdout>",
|
|
6243
|
+
"<bash-stderr>",
|
|
6244
|
+
"<system-reminder>",
|
|
6245
|
+
"<task-notification>",
|
|
6246
|
+
"caveat: the messages below",
|
|
6247
|
+
"[request interrupted"
|
|
6248
|
+
];
|
|
6249
|
+
const CODEX_NOISE_PREFIXES = [
|
|
6250
|
+
"<environment_context>",
|
|
6251
|
+
"<user_instructions>",
|
|
6252
|
+
"<permissions instructions>",
|
|
6253
|
+
"<turn_aborted>"
|
|
6254
|
+
];
|
|
6255
|
+
function isClaudeNoise(text) {
|
|
6256
|
+
const lower = text.trimStart().toLowerCase();
|
|
6257
|
+
return CLAUDE_NOISE_PREFIXES.some((p2) => lower.startsWith(p2));
|
|
6258
|
+
}
|
|
6259
|
+
function isCodexNoise(text) {
|
|
6260
|
+
const lower = text.trimStart().toLowerCase();
|
|
6261
|
+
return CODEX_NOISE_PREFIXES.some((p2) => lower.startsWith(p2));
|
|
6262
|
+
}
|
|
6263
|
+
function extractClaudeText(content, isUser) {
|
|
6264
|
+
if (typeof content === "string") {
|
|
6265
|
+
const text = content.trim();
|
|
6266
|
+
return text && !isClaudeNoise(text) ? text : "";
|
|
6267
|
+
}
|
|
6268
|
+
if (!Array.isArray(content)) return "";
|
|
6269
|
+
if (isUser && content.some((b2) => b2?.type === "tool_result")) return "";
|
|
6270
|
+
const parts = [];
|
|
6271
|
+
for (const block of content) {
|
|
6272
|
+
if (block?.type !== "text" || typeof block.text !== "string") continue;
|
|
6273
|
+
const text = block.text.trim();
|
|
6274
|
+
if (text && !isClaudeNoise(text)) parts.push(text);
|
|
6275
|
+
}
|
|
6276
|
+
return parts.join("\n\n");
|
|
6277
|
+
}
|
|
6278
|
+
function stripTrailingSep(p2) {
|
|
6279
|
+
return p2.length > 1 ? p2.replace(/[/\\]+$/, "") : p2;
|
|
6280
|
+
}
|
|
6281
|
+
function capLength(str) {
|
|
6282
|
+
if (str.length <= MAX_ENTRY_CHARS) return str;
|
|
6283
|
+
return str.slice(0, MAX_ENTRY_CHARS) + `… [truncated, ${str.length} total chars]`;
|
|
6284
|
+
}
|
|
6043
6285
|
var bufferUtil = { exports: {} };
|
|
6044
6286
|
var constants;
|
|
6045
6287
|
var hasRequiredConstants;
|
|
@@ -10719,6 +10961,7 @@ class Orchestrator {
|
|
|
10719
10961
|
noProjectPollTimer = null;
|
|
10720
10962
|
noProjectPollCwd = null;
|
|
10721
10963
|
currentProjectName = null;
|
|
10964
|
+
transcriptTailer = null;
|
|
10722
10965
|
constructor(settingsDir, events) {
|
|
10723
10966
|
this.settingsDir = settingsDir;
|
|
10724
10967
|
this.events = events;
|
|
@@ -10770,11 +11013,36 @@ class Orchestrator {
|
|
|
10770
11013
|
this.saveSettings();
|
|
10771
11014
|
this.bridge.setLoggingEnabled(enabled);
|
|
10772
11015
|
if (!enabled) {
|
|
10773
|
-
this.
|
|
11016
|
+
this.stopChatLogging();
|
|
10774
11017
|
} else if (this.activeTabId) {
|
|
11018
|
+
const tab = this.tabs.get(this.activeTabId);
|
|
11019
|
+
if (tab) this.startChatLogging(tab.agent, tab.cwd);
|
|
11020
|
+
}
|
|
11021
|
+
}
|
|
11022
|
+
// Agents with native session transcripts (Claude Code, Codex) get exact
|
|
11023
|
+
// chat text tailed from their JSONL logs; everything else falls back to
|
|
11024
|
+
// the terminal screen-scraper bridge.
|
|
11025
|
+
startChatLogging(agent, cwd) {
|
|
11026
|
+
this.stopChatLogging();
|
|
11027
|
+
if (!this.settings.logChat) return;
|
|
11028
|
+
if (supportsTranscriptLogging(agent.id)) {
|
|
11029
|
+
this.transcriptTailer = new TranscriptTailer({
|
|
11030
|
+
agentId: agent.id,
|
|
11031
|
+
cwd,
|
|
11032
|
+
sink: (entry) => this.workerWs.sendChatLog(entry)
|
|
11033
|
+
});
|
|
11034
|
+
this.transcriptTailer.start();
|
|
11035
|
+
} else {
|
|
10775
11036
|
this.bridge.startSession();
|
|
10776
11037
|
}
|
|
10777
11038
|
}
|
|
11039
|
+
stopChatLogging() {
|
|
11040
|
+
if (this.transcriptTailer) {
|
|
11041
|
+
this.transcriptTailer.stop();
|
|
11042
|
+
this.transcriptTailer = null;
|
|
11043
|
+
}
|
|
11044
|
+
this.bridge.endSession();
|
|
11045
|
+
}
|
|
10778
11046
|
// ─── Settings ───────────────────────────────────
|
|
10779
11047
|
getActiveProfile() {
|
|
10780
11048
|
return this.settings.profiles[this.settings.activeProfile] || this.settings.profiles.production || DEFAULT_PROFILES.production;
|
|
@@ -10986,7 +11254,7 @@ class Orchestrator {
|
|
|
10986
11254
|
this.events.onPtyExit(tabId, exitCode);
|
|
10987
11255
|
await this.timeTracker.endSession(tabId);
|
|
10988
11256
|
if (tabId === this.activeTabId) {
|
|
10989
|
-
this.
|
|
11257
|
+
this.stopChatLogging();
|
|
10990
11258
|
if (this.currentAgent && isCodingAgent(this.currentAgent)) {
|
|
10991
11259
|
this.workerWs.disconnect();
|
|
10992
11260
|
}
|
|
@@ -10994,9 +11262,7 @@ class Orchestrator {
|
|
|
10994
11262
|
const t = this.tabs.get(tabId);
|
|
10995
11263
|
if (t?.termStreamTimer) clearTimeout(t.termStreamTimer);
|
|
10996
11264
|
});
|
|
10997
|
-
|
|
10998
|
-
this.bridge.startSession();
|
|
10999
|
-
}
|
|
11265
|
+
this.startChatLogging(agent, cwd);
|
|
11000
11266
|
const profile = this.getActiveProfile();
|
|
11001
11267
|
const shouldTrack = opts?.trackTime !== void 0 ? opts.trackTime : profile.trackTime !== false;
|
|
11002
11268
|
if (shouldTrack) {
|
|
@@ -11036,7 +11302,7 @@ class Orchestrator {
|
|
|
11036
11302
|
tab.ptyManager.kill();
|
|
11037
11303
|
this.tabs.delete(tabId);
|
|
11038
11304
|
if (tabId === this.activeTabId) {
|
|
11039
|
-
this.
|
|
11305
|
+
this.stopChatLogging();
|
|
11040
11306
|
if (isCodingAgent(tab.agent)) {
|
|
11041
11307
|
this.workerWs.disconnect();
|
|
11042
11308
|
}
|
|
@@ -11187,7 +11453,7 @@ class Orchestrator {
|
|
|
11187
11453
|
// ─── Shutdown ───────────────────────────────────
|
|
11188
11454
|
async shutdown() {
|
|
11189
11455
|
this.stopNoProjectPolling();
|
|
11190
|
-
this.
|
|
11456
|
+
this.stopChatLogging();
|
|
11191
11457
|
await this.timeTracker.endAll();
|
|
11192
11458
|
for (const [, tab] of this.tabs) {
|
|
11193
11459
|
if (tab.termStreamTimer) clearTimeout(tab.termStreamTimer);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phenx-inc/ctlsurf",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Agent-agnostic terminal and desktop app for ctlsurf — run Claude Code, Codex, or any coding agent with live session logging and remote control",
|
|
5
5
|
"main": "out/main/index.js",
|
|
6
6
|
"bin": {
|
package/src/main/orchestrator.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { PtyManager } from './pty'
|
|
|
6
6
|
import { AgentConfig, isCodingAgent } from './agents'
|
|
7
7
|
import { CtlsurfApi } from './ctlsurfApi'
|
|
8
8
|
import { ConversationBridge } from './bridge'
|
|
9
|
+
import { TranscriptTailer, supportsTranscriptLogging } from './transcripts'
|
|
9
10
|
import { WorkerWsClient, type WorkerWsStatus, type IncomingMessage } from './workerWs'
|
|
10
11
|
import { TimeTracker } from './timeTracker'
|
|
11
12
|
import { TicketStore, type TicketInput } from './ticketStore'
|
|
@@ -93,6 +94,7 @@ export class Orchestrator {
|
|
|
93
94
|
private noProjectPollTimer: ReturnType<typeof setInterval> | null = null
|
|
94
95
|
private noProjectPollCwd: string | null = null
|
|
95
96
|
private currentProjectName: string | null = null
|
|
97
|
+
private transcriptTailer: TranscriptTailer | null = null
|
|
96
98
|
|
|
97
99
|
constructor(settingsDir: string, events: OrchestratorEvents) {
|
|
98
100
|
this.settingsDir = settingsDir
|
|
@@ -151,12 +153,40 @@ export class Orchestrator {
|
|
|
151
153
|
this.saveSettings()
|
|
152
154
|
this.bridge.setLoggingEnabled(enabled)
|
|
153
155
|
if (!enabled) {
|
|
154
|
-
this.
|
|
156
|
+
this.stopChatLogging()
|
|
155
157
|
} else if (this.activeTabId) {
|
|
158
|
+
const tab = this.tabs.get(this.activeTabId)
|
|
159
|
+
if (tab) this.startChatLogging(tab.agent, tab.cwd)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Agents with native session transcripts (Claude Code, Codex) get exact
|
|
164
|
+
// chat text tailed from their JSONL logs; everything else falls back to
|
|
165
|
+
// the terminal screen-scraper bridge.
|
|
166
|
+
private startChatLogging(agent: AgentConfig, cwd: string): void {
|
|
167
|
+
this.stopChatLogging()
|
|
168
|
+
if (!this.settings.logChat) return
|
|
169
|
+
|
|
170
|
+
if (supportsTranscriptLogging(agent.id)) {
|
|
171
|
+
this.transcriptTailer = new TranscriptTailer({
|
|
172
|
+
agentId: agent.id,
|
|
173
|
+
cwd,
|
|
174
|
+
sink: (entry) => this.workerWs.sendChatLog(entry),
|
|
175
|
+
})
|
|
176
|
+
this.transcriptTailer.start()
|
|
177
|
+
} else {
|
|
156
178
|
this.bridge.startSession()
|
|
157
179
|
}
|
|
158
180
|
}
|
|
159
181
|
|
|
182
|
+
private stopChatLogging(): void {
|
|
183
|
+
if (this.transcriptTailer) {
|
|
184
|
+
this.transcriptTailer.stop()
|
|
185
|
+
this.transcriptTailer = null
|
|
186
|
+
}
|
|
187
|
+
this.bridge.endSession()
|
|
188
|
+
}
|
|
189
|
+
|
|
160
190
|
// ─── Settings ───────────────────────────────────
|
|
161
191
|
|
|
162
192
|
getActiveProfile(): Profile {
|
|
@@ -406,7 +436,7 @@ export class Orchestrator {
|
|
|
406
436
|
this.events.onPtyExit(tabId, exitCode)
|
|
407
437
|
await this.timeTracker.endSession(tabId)
|
|
408
438
|
if (tabId === this.activeTabId) {
|
|
409
|
-
this.
|
|
439
|
+
this.stopChatLogging()
|
|
410
440
|
if (this.currentAgent && isCodingAgent(this.currentAgent)) {
|
|
411
441
|
this.workerWs.disconnect()
|
|
412
442
|
}
|
|
@@ -416,9 +446,7 @@ export class Orchestrator {
|
|
|
416
446
|
if (t?.termStreamTimer) clearTimeout(t.termStreamTimer)
|
|
417
447
|
})
|
|
418
448
|
|
|
419
|
-
|
|
420
|
-
this.bridge.startSession()
|
|
421
|
-
}
|
|
449
|
+
this.startChatLogging(agent, cwd)
|
|
422
450
|
|
|
423
451
|
const profile = this.getActiveProfile()
|
|
424
452
|
const shouldTrack = opts?.trackTime !== undefined ? opts.trackTime : (profile.trackTime !== false)
|
|
@@ -463,7 +491,7 @@ export class Orchestrator {
|
|
|
463
491
|
tab.ptyManager.kill()
|
|
464
492
|
this.tabs.delete(tabId)
|
|
465
493
|
if (tabId === this.activeTabId) {
|
|
466
|
-
this.
|
|
494
|
+
this.stopChatLogging()
|
|
467
495
|
if (isCodingAgent(tab.agent)) {
|
|
468
496
|
this.workerWs.disconnect()
|
|
469
497
|
}
|
|
@@ -632,7 +660,7 @@ export class Orchestrator {
|
|
|
632
660
|
|
|
633
661
|
async shutdown(): Promise<void> {
|
|
634
662
|
this.stopNoProjectPolling()
|
|
635
|
-
this.
|
|
663
|
+
this.stopChatLogging()
|
|
636
664
|
await this.timeTracker.endAll()
|
|
637
665
|
for (const [, tab] of this.tabs) {
|
|
638
666
|
if (tab.termStreamTimer) clearTimeout(tab.termStreamTimer)
|