@phenx-inc/ctlsurf 0.3.16 → 0.5.0
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 +309 -38
- package/out/headless/index.mjs.map +4 -4
- package/out/main/index.js +277 -7
- package/out/preload/index.js +2 -0
- package/out/renderer/assets/{cssMode-D5dPwEy5.js → cssMode-DkmdBgO7.js} +3 -3
- package/out/renderer/assets/{freemarker2-c5jJjQ9s.js → freemarker2-CI-gkP-3.js} +1 -1
- package/out/renderer/assets/{handlebars-BTbmOxx9.js → handlebars-D5tEqanR.js} +1 -1
- package/out/renderer/assets/{html-3cIIQcxO.js → html-fH93EYfn.js} +1 -1
- package/out/renderer/assets/{htmlMode-DYbpW1yY.js → htmlMode-CRicxcwK.js} +3 -3
- package/out/renderer/assets/{index-D2MUZin7.js → index-BOOvUI7u.js} +192 -23
- package/out/renderer/assets/{index-6KvOnYL1.css → index-ezC-iarf.css} +40 -0
- package/out/renderer/assets/{javascript-CDuCMm-6.js → javascript-D1Baz4fV.js} +2 -2
- package/out/renderer/assets/{jsonMode-COLqbq0s.js → jsonMode-Bquqf3QN.js} +3 -3
- package/out/renderer/assets/{liquid-BFcqZizB.js → liquid-ByOcPjBF.js} +1 -1
- package/out/renderer/assets/{lspLanguageFeatures-CbkEcL-z.js → lspLanguageFeatures-BxPLl0yy.js} +1 -1
- package/out/renderer/assets/{mdx-DyK93oEE.js → mdx-yuNgx0rM.js} +1 -1
- package/out/renderer/assets/{python-D4lCwSVr.js → python-2OakgLlA.js} +1 -1
- package/out/renderer/assets/{razor-DdkE9XVt.js → razor-DnIVMSwa.js} +1 -1
- package/out/renderer/assets/{tsMode-BrQ4Fsc-.js → tsMode-CRIrHuii.js} +1 -1
- package/out/renderer/assets/{typescript-BakbYMnC.js → typescript-DJ3C8Yly.js} +1 -1
- package/out/renderer/assets/{xml-DHDW9Xhp.js → xml-CalvD5_C.js} +1 -1
- package/out/renderer/assets/{yaml-1Ayv_J3q.js → yaml-Cgs8pdVp.js} +1 -1
- package/out/renderer/index.html +2 -2
- package/package.json +1 -1
- package/src/main/index.ts +8 -1
- package/src/main/orchestrator.ts +35 -7
- package/src/main/transcripts.ts +341 -0
- package/src/preload/index.ts +4 -0
- package/src/renderer/App.tsx +1 -0
- package/src/renderer/components/TerminalPanel.tsx +95 -2
- package/src/renderer/lib/tableToHtml.ts +146 -0
- package/src/renderer/styles.css +40 -0
package/out/headless/index.mjs
CHANGED
|
@@ -5445,19 +5445,19 @@ var require_addon_serialize = __commonJS({
|
|
|
5445
5445
|
// node_modules/electron/index.js
|
|
5446
5446
|
var require_electron = __commonJS({
|
|
5447
5447
|
"node_modules/electron/index.js"(exports, module) {
|
|
5448
|
-
var
|
|
5449
|
-
var
|
|
5450
|
-
var pathFile =
|
|
5448
|
+
var fs3 = __require("fs");
|
|
5449
|
+
var path4 = __require("path");
|
|
5450
|
+
var pathFile = path4.join(__dirname, "path.txt");
|
|
5451
5451
|
function getElectronPath() {
|
|
5452
5452
|
let executablePath;
|
|
5453
|
-
if (
|
|
5454
|
-
executablePath =
|
|
5453
|
+
if (fs3.existsSync(pathFile)) {
|
|
5454
|
+
executablePath = fs3.readFileSync(pathFile, "utf-8");
|
|
5455
5455
|
}
|
|
5456
5456
|
if (process.env.ELECTRON_OVERRIDE_DIST_PATH) {
|
|
5457
|
-
return
|
|
5457
|
+
return path4.join(process.env.ELECTRON_OVERRIDE_DIST_PATH, executablePath || "electron");
|
|
5458
5458
|
}
|
|
5459
5459
|
if (executablePath) {
|
|
5460
|
-
return
|
|
5460
|
+
return path4.join(__dirname, "dist", executablePath);
|
|
5461
5461
|
} else {
|
|
5462
5462
|
throw new Error("Electron failed to install correctly, please delete node_modules/electron and try installing again");
|
|
5463
5463
|
}
|
|
@@ -5471,7 +5471,7 @@ var require_package = __commonJS({
|
|
|
5471
5471
|
"package.json"(exports, module) {
|
|
5472
5472
|
module.exports = {
|
|
5473
5473
|
name: "@phenx-inc/ctlsurf",
|
|
5474
|
-
version: "0.
|
|
5474
|
+
version: "0.5.0",
|
|
5475
5475
|
description: "Agent-agnostic terminal and desktop app for ctlsurf \u2014 run Claude Code, Codex, or any coding agent with live session logging and remote control",
|
|
5476
5476
|
main: "out/main/index.js",
|
|
5477
5477
|
bin: {
|
|
@@ -5562,9 +5562,9 @@ function log(...args) {
|
|
|
5562
5562
|
}
|
|
5563
5563
|
|
|
5564
5564
|
// src/main/orchestrator.ts
|
|
5565
|
-
import
|
|
5566
|
-
import
|
|
5567
|
-
import
|
|
5565
|
+
import path2 from "path";
|
|
5566
|
+
import fs2 from "fs";
|
|
5567
|
+
import os4 from "os";
|
|
5568
5568
|
|
|
5569
5569
|
// src/main/pty.ts
|
|
5570
5570
|
import { createRequire } from "module";
|
|
@@ -5700,8 +5700,8 @@ var CtlsurfApi = class {
|
|
|
5700
5700
|
}
|
|
5701
5701
|
return h;
|
|
5702
5702
|
}
|
|
5703
|
-
async request(method,
|
|
5704
|
-
const url = `${this.baseUrl}${
|
|
5703
|
+
async request(method, path4, body) {
|
|
5704
|
+
const url = `${this.baseUrl}${path4}`;
|
|
5705
5705
|
const opts = {
|
|
5706
5706
|
method,
|
|
5707
5707
|
headers: this.headers()
|
|
@@ -5712,7 +5712,7 @@ var CtlsurfApi = class {
|
|
|
5712
5712
|
const res = await fetch(url, opts);
|
|
5713
5713
|
if (!res.ok) {
|
|
5714
5714
|
const text = await res.text();
|
|
5715
|
-
throw new Error(`ctlsurf API ${method} ${
|
|
5715
|
+
throw new Error(`ctlsurf API ${method} ${path4}: ${res.status} ${text}`);
|
|
5716
5716
|
}
|
|
5717
5717
|
return res.json();
|
|
5718
5718
|
}
|
|
@@ -6090,8 +6090,255 @@ function trimToLogLimit(str) {
|
|
|
6090
6090
|
${str.slice(str.length - MAX_LOG_CHARS)}`;
|
|
6091
6091
|
}
|
|
6092
6092
|
|
|
6093
|
-
// src/main/
|
|
6093
|
+
// src/main/transcripts.ts
|
|
6094
|
+
import fs from "fs";
|
|
6095
|
+
import path from "path";
|
|
6094
6096
|
import os from "os";
|
|
6097
|
+
function supportsTranscriptLogging(agentId) {
|
|
6098
|
+
return agentId === "claude" || agentId === "codex";
|
|
6099
|
+
}
|
|
6100
|
+
var POLL_INTERVAL_MS = 1e3;
|
|
6101
|
+
var DISCOVERY_SLACK_MS = 1e4;
|
|
6102
|
+
var READ_CHUNK_BYTES = 64 * 1024;
|
|
6103
|
+
var MAX_ENTRY_CHARS = 2e4;
|
|
6104
|
+
var TranscriptTailer = class {
|
|
6105
|
+
agentId;
|
|
6106
|
+
cwd;
|
|
6107
|
+
sink;
|
|
6108
|
+
claudeProjectsDir;
|
|
6109
|
+
codexSessionsDir;
|
|
6110
|
+
files = /* @__PURE__ */ new Map();
|
|
6111
|
+
pollTimer = null;
|
|
6112
|
+
sinceMs = 0;
|
|
6113
|
+
constructor(options) {
|
|
6114
|
+
this.agentId = options.agentId;
|
|
6115
|
+
this.cwd = stripTrailingSep(options.cwd);
|
|
6116
|
+
this.sink = options.sink;
|
|
6117
|
+
this.claudeProjectsDir = options.claudeProjectsDir || path.join(os.homedir(), ".claude", "projects");
|
|
6118
|
+
this.codexSessionsDir = options.codexSessionsDir || path.join(os.homedir(), ".codex", "sessions");
|
|
6119
|
+
}
|
|
6120
|
+
start() {
|
|
6121
|
+
if (this.pollTimer) return;
|
|
6122
|
+
this.sinceMs = Date.now();
|
|
6123
|
+
this.files.clear();
|
|
6124
|
+
this.pollTimer = setInterval(() => this.poll(), POLL_INTERVAL_MS);
|
|
6125
|
+
console.log(`[transcripts] Tailing ${this.agentId} transcripts for ${this.cwd}`);
|
|
6126
|
+
}
|
|
6127
|
+
stop() {
|
|
6128
|
+
if (!this.pollTimer) return;
|
|
6129
|
+
clearInterval(this.pollTimer);
|
|
6130
|
+
this.pollTimer = null;
|
|
6131
|
+
this.poll();
|
|
6132
|
+
this.files.clear();
|
|
6133
|
+
console.log("[transcripts] Stopped");
|
|
6134
|
+
}
|
|
6135
|
+
poll() {
|
|
6136
|
+
try {
|
|
6137
|
+
this.discover();
|
|
6138
|
+
for (const [filePath, tail] of this.files) {
|
|
6139
|
+
if (!tail.excluded) this.drainFile(filePath, tail);
|
|
6140
|
+
}
|
|
6141
|
+
} catch (err) {
|
|
6142
|
+
console.error("[transcripts] Poll error:", err);
|
|
6143
|
+
}
|
|
6144
|
+
}
|
|
6145
|
+
// ─── Discovery ──────────────────────────────────
|
|
6146
|
+
/**
|
|
6147
|
+
* Track every transcript file with recent activity, not just the first
|
|
6148
|
+
* match: /clear (Claude) or /new (Codex) starts a new session file in the
|
|
6149
|
+
* middle of one PTY run, and tailing all active candidates handles the
|
|
6150
|
+
* switch without special cases. Old idle files never match (stale mtime).
|
|
6151
|
+
*/
|
|
6152
|
+
discover() {
|
|
6153
|
+
const dirs = this.agentId === "claude" ? [this.claudeProjectsDirForCwd()] : this.codexDateDirs();
|
|
6154
|
+
for (const dir of dirs) {
|
|
6155
|
+
let names;
|
|
6156
|
+
try {
|
|
6157
|
+
names = fs.readdirSync(dir);
|
|
6158
|
+
} catch {
|
|
6159
|
+
continue;
|
|
6160
|
+
}
|
|
6161
|
+
for (const name of names) {
|
|
6162
|
+
if (!name.endsWith(".jsonl")) continue;
|
|
6163
|
+
if (this.agentId === "codex" && !name.startsWith("rollout-")) continue;
|
|
6164
|
+
const filePath = path.join(dir, name);
|
|
6165
|
+
if (this.files.has(filePath)) continue;
|
|
6166
|
+
try {
|
|
6167
|
+
const stat = fs.statSync(filePath);
|
|
6168
|
+
if (stat.mtimeMs >= this.sinceMs - DISCOVERY_SLACK_MS) {
|
|
6169
|
+
this.files.set(filePath, { offset: 0, remainder: "", excluded: false });
|
|
6170
|
+
}
|
|
6171
|
+
} catch {
|
|
6172
|
+
}
|
|
6173
|
+
}
|
|
6174
|
+
}
|
|
6175
|
+
}
|
|
6176
|
+
claudeProjectsDirForCwd() {
|
|
6177
|
+
const slug = this.cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
6178
|
+
return path.join(this.claudeProjectsDir, slug);
|
|
6179
|
+
}
|
|
6180
|
+
codexDateDirs() {
|
|
6181
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
6182
|
+
for (const ms of [this.sinceMs, Date.now()]) {
|
|
6183
|
+
const d = new Date(ms);
|
|
6184
|
+
const yyyy = String(d.getFullYear());
|
|
6185
|
+
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
|
6186
|
+
const dd = String(d.getDate()).padStart(2, "0");
|
|
6187
|
+
dirs.add(path.join(this.codexSessionsDir, yyyy, mm, dd));
|
|
6188
|
+
}
|
|
6189
|
+
return [...dirs];
|
|
6190
|
+
}
|
|
6191
|
+
// ─── Tailing ────────────────────────────────────
|
|
6192
|
+
drainFile(filePath, tail) {
|
|
6193
|
+
let size;
|
|
6194
|
+
try {
|
|
6195
|
+
size = fs.statSync(filePath).size;
|
|
6196
|
+
} catch {
|
|
6197
|
+
return;
|
|
6198
|
+
}
|
|
6199
|
+
if (size <= tail.offset) return;
|
|
6200
|
+
let fd;
|
|
6201
|
+
try {
|
|
6202
|
+
fd = fs.openSync(filePath, "r");
|
|
6203
|
+
} catch {
|
|
6204
|
+
return;
|
|
6205
|
+
}
|
|
6206
|
+
try {
|
|
6207
|
+
const buf = Buffer.alloc(READ_CHUNK_BYTES);
|
|
6208
|
+
while (tail.offset < size && !tail.excluded) {
|
|
6209
|
+
const bytesRead = fs.readSync(fd, buf, 0, READ_CHUNK_BYTES, tail.offset);
|
|
6210
|
+
if (bytesRead <= 0) break;
|
|
6211
|
+
tail.offset += bytesRead;
|
|
6212
|
+
tail.remainder += buf.toString("utf-8", 0, bytesRead);
|
|
6213
|
+
const lines = tail.remainder.split("\n");
|
|
6214
|
+
tail.remainder = lines.pop() || "";
|
|
6215
|
+
for (const line of lines) {
|
|
6216
|
+
this.handleLine(line, tail);
|
|
6217
|
+
if (tail.excluded) break;
|
|
6218
|
+
}
|
|
6219
|
+
}
|
|
6220
|
+
} catch (err) {
|
|
6221
|
+
console.error(`[transcripts] Read error for ${filePath}:`, err);
|
|
6222
|
+
} finally {
|
|
6223
|
+
try {
|
|
6224
|
+
fs.closeSync(fd);
|
|
6225
|
+
} catch {
|
|
6226
|
+
}
|
|
6227
|
+
}
|
|
6228
|
+
}
|
|
6229
|
+
handleLine(line, tail) {
|
|
6230
|
+
const trimmed = line.trim();
|
|
6231
|
+
if (!trimmed) return;
|
|
6232
|
+
let obj;
|
|
6233
|
+
try {
|
|
6234
|
+
obj = JSON.parse(trimmed);
|
|
6235
|
+
} catch {
|
|
6236
|
+
return;
|
|
6237
|
+
}
|
|
6238
|
+
const entry = this.agentId === "claude" ? this.parseClaudeLine(obj) : this.parseCodexLine(obj, tail);
|
|
6239
|
+
if (!entry) return;
|
|
6240
|
+
const ms = Date.parse(entry.ts);
|
|
6241
|
+
if (Number.isFinite(ms) && ms < this.sinceMs - DISCOVERY_SLACK_MS) return;
|
|
6242
|
+
this.sink({ ...entry, content: capLength(entry.content) });
|
|
6243
|
+
}
|
|
6244
|
+
// ─── Claude Code format ─────────────────────────
|
|
6245
|
+
parseClaudeLine(obj) {
|
|
6246
|
+
if (!obj || typeof obj !== "object") return null;
|
|
6247
|
+
if (obj.isMeta) return null;
|
|
6248
|
+
if (obj.type !== "user" && obj.type !== "assistant") return null;
|
|
6249
|
+
if (typeof obj.cwd === "string" && stripTrailingSep(obj.cwd) !== this.cwd) return null;
|
|
6250
|
+
if (obj.isSidechain) return null;
|
|
6251
|
+
const message = obj.message;
|
|
6252
|
+
if (!message) return null;
|
|
6253
|
+
const text = extractClaudeText(message.content, obj.type === "user");
|
|
6254
|
+
if (!text) return null;
|
|
6255
|
+
return {
|
|
6256
|
+
ts: typeof obj.timestamp === "string" ? obj.timestamp : (/* @__PURE__ */ new Date()).toISOString(),
|
|
6257
|
+
type: obj.type === "user" ? "user_input" : "terminal_output",
|
|
6258
|
+
content: text
|
|
6259
|
+
};
|
|
6260
|
+
}
|
|
6261
|
+
// ─── Codex CLI format ───────────────────────────
|
|
6262
|
+
parseCodexLine(obj, tail) {
|
|
6263
|
+
if (!obj || typeof obj !== "object") return null;
|
|
6264
|
+
const payload = obj.payload;
|
|
6265
|
+
if (obj.type === "session_meta") {
|
|
6266
|
+
const metaCwd = payload?.cwd;
|
|
6267
|
+
if (typeof metaCwd === "string" && stripTrailingSep(metaCwd) !== this.cwd) {
|
|
6268
|
+
tail.excluded = true;
|
|
6269
|
+
}
|
|
6270
|
+
return null;
|
|
6271
|
+
}
|
|
6272
|
+
if (obj.type !== "event_msg" || !payload || typeof payload !== "object") return null;
|
|
6273
|
+
let type;
|
|
6274
|
+
if (payload.type === "user_message") {
|
|
6275
|
+
type = "user_input";
|
|
6276
|
+
} else if (payload.type === "agent_message") {
|
|
6277
|
+
type = "terminal_output";
|
|
6278
|
+
} else {
|
|
6279
|
+
return null;
|
|
6280
|
+
}
|
|
6281
|
+
const text = typeof payload.message === "string" ? payload.message.trim() : "";
|
|
6282
|
+
if (!text || isCodexNoise(text)) return null;
|
|
6283
|
+
return {
|
|
6284
|
+
ts: typeof obj.timestamp === "string" ? obj.timestamp : (/* @__PURE__ */ new Date()).toISOString(),
|
|
6285
|
+
type,
|
|
6286
|
+
content: text
|
|
6287
|
+
};
|
|
6288
|
+
}
|
|
6289
|
+
};
|
|
6290
|
+
var CLAUDE_NOISE_PREFIXES = [
|
|
6291
|
+
"<local-command-caveat>",
|
|
6292
|
+
"<command-name>",
|
|
6293
|
+
"<command-message>",
|
|
6294
|
+
"<local-command-stdout>",
|
|
6295
|
+
"<bash-input>",
|
|
6296
|
+
"<bash-stdout>",
|
|
6297
|
+
"<bash-stderr>",
|
|
6298
|
+
"<system-reminder>",
|
|
6299
|
+
"<task-notification>",
|
|
6300
|
+
"caveat: the messages below",
|
|
6301
|
+
"[request interrupted"
|
|
6302
|
+
];
|
|
6303
|
+
var CODEX_NOISE_PREFIXES = [
|
|
6304
|
+
"<environment_context>",
|
|
6305
|
+
"<user_instructions>",
|
|
6306
|
+
"<permissions instructions>",
|
|
6307
|
+
"<turn_aborted>"
|
|
6308
|
+
];
|
|
6309
|
+
function isClaudeNoise(text) {
|
|
6310
|
+
const lower = text.trimStart().toLowerCase();
|
|
6311
|
+
return CLAUDE_NOISE_PREFIXES.some((p) => lower.startsWith(p));
|
|
6312
|
+
}
|
|
6313
|
+
function isCodexNoise(text) {
|
|
6314
|
+
const lower = text.trimStart().toLowerCase();
|
|
6315
|
+
return CODEX_NOISE_PREFIXES.some((p) => lower.startsWith(p));
|
|
6316
|
+
}
|
|
6317
|
+
function extractClaudeText(content, isUser) {
|
|
6318
|
+
if (typeof content === "string") {
|
|
6319
|
+
const text = content.trim();
|
|
6320
|
+
return text && !isClaudeNoise(text) ? text : "";
|
|
6321
|
+
}
|
|
6322
|
+
if (!Array.isArray(content)) return "";
|
|
6323
|
+
if (isUser && content.some((b) => b?.type === "tool_result")) return "";
|
|
6324
|
+
const parts = [];
|
|
6325
|
+
for (const block of content) {
|
|
6326
|
+
if (block?.type !== "text" || typeof block.text !== "string") continue;
|
|
6327
|
+
const text = block.text.trim();
|
|
6328
|
+
if (text && !isClaudeNoise(text)) parts.push(text);
|
|
6329
|
+
}
|
|
6330
|
+
return parts.join("\n\n");
|
|
6331
|
+
}
|
|
6332
|
+
function stripTrailingSep(p) {
|
|
6333
|
+
return p.length > 1 ? p.replace(/[/\\]+$/, "") : p;
|
|
6334
|
+
}
|
|
6335
|
+
function capLength(str) {
|
|
6336
|
+
if (str.length <= MAX_ENTRY_CHARS) return str;
|
|
6337
|
+
return str.slice(0, MAX_ENTRY_CHARS) + `\u2026 [truncated, ${str.length} total chars]`;
|
|
6338
|
+
}
|
|
6339
|
+
|
|
6340
|
+
// src/main/workerWs.ts
|
|
6341
|
+
import os2 from "os";
|
|
6095
6342
|
import crypto from "crypto";
|
|
6096
6343
|
import WsModule from "ws";
|
|
6097
6344
|
var WS = typeof WebSocket !== "undefined" ? WebSocket : WsModule;
|
|
@@ -6131,7 +6378,7 @@ var WorkerWsClient = class {
|
|
|
6131
6378
|
// single worker server-side. cwd is included so the same folder maps to the
|
|
6132
6379
|
// same worker across restarts.
|
|
6133
6380
|
generateFingerprint(cwd) {
|
|
6134
|
-
const data = `${
|
|
6381
|
+
const data = `${os2.hostname()}:${os2.userInfo().username}:${os2.platform()}:${os2.arch()}:${cwd}`;
|
|
6135
6382
|
return crypto.createHash("sha256").update(data).digest("hex").slice(0, 32);
|
|
6136
6383
|
}
|
|
6137
6384
|
setStatus(status) {
|
|
@@ -6340,7 +6587,7 @@ var WorkerWsClient = class {
|
|
|
6340
6587
|
};
|
|
6341
6588
|
|
|
6342
6589
|
// src/main/timeTracker.ts
|
|
6343
|
-
import
|
|
6590
|
+
import os3 from "os";
|
|
6344
6591
|
import { randomUUID } from "crypto";
|
|
6345
6592
|
var DATASTORE_TITLE = "Time Tracking";
|
|
6346
6593
|
var AGENT_DATASTORE_PAGE_TITLE = "Agent Datastore";
|
|
@@ -6445,7 +6692,7 @@ var TimeTracker = class {
|
|
|
6445
6692
|
"Active Time": Math.round(s.activeMs / 6e4),
|
|
6446
6693
|
"Last Updated": (/* @__PURE__ */ new Date()).toISOString(),
|
|
6447
6694
|
Agent: s.agentName,
|
|
6448
|
-
Worker:
|
|
6695
|
+
Worker: os3.hostname(),
|
|
6449
6696
|
Session: s.sessionUuid,
|
|
6450
6697
|
Notes: ""
|
|
6451
6698
|
});
|
|
@@ -6884,6 +7131,7 @@ var Orchestrator = class {
|
|
|
6884
7131
|
noProjectPollTimer = null;
|
|
6885
7132
|
noProjectPollCwd = null;
|
|
6886
7133
|
currentProjectName = null;
|
|
7134
|
+
transcriptTailer = null;
|
|
6887
7135
|
constructor(settingsDir, events) {
|
|
6888
7136
|
this.settingsDir = settingsDir;
|
|
6889
7137
|
this.events = events;
|
|
@@ -6935,11 +7183,36 @@ var Orchestrator = class {
|
|
|
6935
7183
|
this.saveSettings();
|
|
6936
7184
|
this.bridge.setLoggingEnabled(enabled);
|
|
6937
7185
|
if (!enabled) {
|
|
6938
|
-
this.
|
|
7186
|
+
this.stopChatLogging();
|
|
6939
7187
|
} else if (this.activeTabId) {
|
|
7188
|
+
const tab = this.tabs.get(this.activeTabId);
|
|
7189
|
+
if (tab) this.startChatLogging(tab.agent, tab.cwd);
|
|
7190
|
+
}
|
|
7191
|
+
}
|
|
7192
|
+
// Agents with native session transcripts (Claude Code, Codex) get exact
|
|
7193
|
+
// chat text tailed from their JSONL logs; everything else falls back to
|
|
7194
|
+
// the terminal screen-scraper bridge.
|
|
7195
|
+
startChatLogging(agent, cwd) {
|
|
7196
|
+
this.stopChatLogging();
|
|
7197
|
+
if (!this.settings.logChat) return;
|
|
7198
|
+
if (supportsTranscriptLogging(agent.id)) {
|
|
7199
|
+
this.transcriptTailer = new TranscriptTailer({
|
|
7200
|
+
agentId: agent.id,
|
|
7201
|
+
cwd,
|
|
7202
|
+
sink: (entry) => this.workerWs.sendChatLog(entry)
|
|
7203
|
+
});
|
|
7204
|
+
this.transcriptTailer.start();
|
|
7205
|
+
} else {
|
|
6940
7206
|
this.bridge.startSession();
|
|
6941
7207
|
}
|
|
6942
7208
|
}
|
|
7209
|
+
stopChatLogging() {
|
|
7210
|
+
if (this.transcriptTailer) {
|
|
7211
|
+
this.transcriptTailer.stop();
|
|
7212
|
+
this.transcriptTailer = null;
|
|
7213
|
+
}
|
|
7214
|
+
this.bridge.endSession();
|
|
7215
|
+
}
|
|
6943
7216
|
// ─── Settings ───────────────────────────────────
|
|
6944
7217
|
getActiveProfile() {
|
|
6945
7218
|
return this.settings.profiles[this.settings.activeProfile] || this.settings.profiles.production || DEFAULT_PROFILES.production;
|
|
@@ -6989,13 +7262,13 @@ var Orchestrator = class {
|
|
|
6989
7262
|
}
|
|
6990
7263
|
loadSettings() {
|
|
6991
7264
|
try {
|
|
6992
|
-
|
|
7265
|
+
fs2.mkdirSync(this.settingsDir, { recursive: true });
|
|
6993
7266
|
} catch {
|
|
6994
7267
|
}
|
|
6995
|
-
const settingsPath =
|
|
7268
|
+
const settingsPath = path2.join(this.settingsDir, "settings.json");
|
|
6996
7269
|
try {
|
|
6997
|
-
if (
|
|
6998
|
-
const raw = JSON.parse(
|
|
7270
|
+
if (fs2.existsSync(settingsPath)) {
|
|
7271
|
+
const raw = JSON.parse(fs2.readFileSync(settingsPath, "utf-8"));
|
|
6999
7272
|
if (!raw.profiles) {
|
|
7000
7273
|
this.settings = {
|
|
7001
7274
|
activeProfile: "production",
|
|
@@ -7032,10 +7305,10 @@ var Orchestrator = class {
|
|
|
7032
7305
|
this.bridge.setLoggingEnabled(!!this.settings.logChat);
|
|
7033
7306
|
}
|
|
7034
7307
|
saveSettings() {
|
|
7035
|
-
const settingsPath =
|
|
7308
|
+
const settingsPath = path2.join(this.settingsDir, "settings.json");
|
|
7036
7309
|
try {
|
|
7037
|
-
|
|
7038
|
-
|
|
7310
|
+
fs2.mkdirSync(this.settingsDir, { recursive: true });
|
|
7311
|
+
fs2.writeFileSync(settingsPath, JSON.stringify(this.settings, null, 2));
|
|
7039
7312
|
} catch (err) {
|
|
7040
7313
|
log("[settings] Failed to save:", err.message);
|
|
7041
7314
|
}
|
|
@@ -7151,7 +7424,7 @@ var Orchestrator = class {
|
|
|
7151
7424
|
this.events.onPtyExit(tabId, exitCode);
|
|
7152
7425
|
await this.timeTracker.endSession(tabId);
|
|
7153
7426
|
if (tabId === this.activeTabId) {
|
|
7154
|
-
this.
|
|
7427
|
+
this.stopChatLogging();
|
|
7155
7428
|
if (this.currentAgent && isCodingAgent(this.currentAgent)) {
|
|
7156
7429
|
this.workerWs.disconnect();
|
|
7157
7430
|
}
|
|
@@ -7159,9 +7432,7 @@ var Orchestrator = class {
|
|
|
7159
7432
|
const t = this.tabs.get(tabId);
|
|
7160
7433
|
if (t?.termStreamTimer) clearTimeout(t.termStreamTimer);
|
|
7161
7434
|
});
|
|
7162
|
-
|
|
7163
|
-
this.bridge.startSession();
|
|
7164
|
-
}
|
|
7435
|
+
this.startChatLogging(agent, cwd);
|
|
7165
7436
|
const profile = this.getActiveProfile();
|
|
7166
7437
|
const shouldTrack = opts?.trackTime !== void 0 ? opts.trackTime : profile.trackTime !== false;
|
|
7167
7438
|
if (shouldTrack) {
|
|
@@ -7201,7 +7472,7 @@ var Orchestrator = class {
|
|
|
7201
7472
|
tab.ptyManager.kill();
|
|
7202
7473
|
this.tabs.delete(tabId);
|
|
7203
7474
|
if (tabId === this.activeTabId) {
|
|
7204
|
-
this.
|
|
7475
|
+
this.stopChatLogging();
|
|
7205
7476
|
if (isCodingAgent(tab.agent)) {
|
|
7206
7477
|
this.workerWs.disconnect();
|
|
7207
7478
|
}
|
|
@@ -7282,7 +7553,7 @@ var Orchestrator = class {
|
|
|
7282
7553
|
}
|
|
7283
7554
|
this.stopNoProjectPolling();
|
|
7284
7555
|
this.workerWs.connect({
|
|
7285
|
-
machine:
|
|
7556
|
+
machine: os4.hostname(),
|
|
7286
7557
|
cwd,
|
|
7287
7558
|
agent: agent.name
|
|
7288
7559
|
});
|
|
@@ -7352,7 +7623,7 @@ var Orchestrator = class {
|
|
|
7352
7623
|
// ─── Shutdown ───────────────────────────────────
|
|
7353
7624
|
async shutdown() {
|
|
7354
7625
|
this.stopNoProjectPolling();
|
|
7355
|
-
this.
|
|
7626
|
+
this.stopChatLogging();
|
|
7356
7627
|
await this.timeTracker.endAll();
|
|
7357
7628
|
for (const [, tab] of this.tabs) {
|
|
7358
7629
|
if (tab.termStreamTimer) clearTimeout(tab.termStreamTimer);
|
|
@@ -7364,18 +7635,18 @@ var Orchestrator = class {
|
|
|
7364
7635
|
};
|
|
7365
7636
|
|
|
7366
7637
|
// src/main/settingsDir.ts
|
|
7367
|
-
import
|
|
7368
|
-
import
|
|
7638
|
+
import path3 from "path";
|
|
7639
|
+
import os5 from "os";
|
|
7369
7640
|
function getSettingsDir(useElectron) {
|
|
7370
7641
|
if (useElectron) {
|
|
7371
7642
|
const { app } = require_electron();
|
|
7372
7643
|
return app.getPath("userData");
|
|
7373
7644
|
}
|
|
7374
7645
|
if (process.platform === "darwin") {
|
|
7375
|
-
return
|
|
7646
|
+
return path3.join(os5.homedir(), "Library", "Application Support", "ctlsurf-worker");
|
|
7376
7647
|
}
|
|
7377
|
-
return
|
|
7378
|
-
process.env.XDG_CONFIG_HOME ||
|
|
7648
|
+
return path3.join(
|
|
7649
|
+
process.env.XDG_CONFIG_HOME || path3.join(os5.homedir(), ".config"),
|
|
7379
7650
|
"ctlsurf-worker"
|
|
7380
7651
|
);
|
|
7381
7652
|
}
|