codeam-cli 2.4.38 → 2.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/dist/index.js +1230 -1078
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -519,9 +519,9 @@ var require_windowsPtyAgent = __commonJS({
|
|
|
519
519
|
"use strict";
|
|
520
520
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
521
521
|
exports2.argsToCommandLine = exports2.WindowsPtyAgent = void 0;
|
|
522
|
-
var
|
|
523
|
-
var
|
|
524
|
-
var
|
|
522
|
+
var fs12 = require("fs");
|
|
523
|
+
var os11 = require("os");
|
|
524
|
+
var path19 = require("path");
|
|
525
525
|
var child_process_1 = require("child_process");
|
|
526
526
|
var net_1 = require("net");
|
|
527
527
|
var windowsConoutConnection_1 = require_windowsConoutConnection();
|
|
@@ -557,7 +557,7 @@ var require_windowsPtyAgent = __commonJS({
|
|
|
557
557
|
}
|
|
558
558
|
}
|
|
559
559
|
this._ptyNative = this._useConpty ? conptyNative : winptyNative;
|
|
560
|
-
cwd =
|
|
560
|
+
cwd = path19.resolve(cwd);
|
|
561
561
|
var commandLine = argsToCommandLine(file, args2);
|
|
562
562
|
var term;
|
|
563
563
|
if (this._useConpty) {
|
|
@@ -578,7 +578,7 @@ var require_windowsPtyAgent = __commonJS({
|
|
|
578
578
|
this._outSocket.on("connect", function() {
|
|
579
579
|
_this._outSocket.emit("ready_datapipe");
|
|
580
580
|
});
|
|
581
|
-
var inSocketFD =
|
|
581
|
+
var inSocketFD = fs12.openSync(term.conin, "w");
|
|
582
582
|
this._inSocket = new net_1.Socket({
|
|
583
583
|
fd: inSocketFD,
|
|
584
584
|
readable: false,
|
|
@@ -679,7 +679,7 @@ var require_windowsPtyAgent = __commonJS({
|
|
|
679
679
|
WindowsPtyAgent2.prototype._getConsoleProcessList = function() {
|
|
680
680
|
var _this = this;
|
|
681
681
|
return new Promise(function(resolve2) {
|
|
682
|
-
var agent = child_process_1.fork(
|
|
682
|
+
var agent = child_process_1.fork(path19.join(__dirname, "conpty_console_list_agent"), [_this._innerPid.toString()]);
|
|
683
683
|
agent.on("message", function(message) {
|
|
684
684
|
clearTimeout(timeout);
|
|
685
685
|
resolve2(message.consoleProcessList);
|
|
@@ -702,7 +702,7 @@ var require_windowsPtyAgent = __commonJS({
|
|
|
702
702
|
configurable: true
|
|
703
703
|
});
|
|
704
704
|
WindowsPtyAgent2.prototype._getWindowsBuildNumber = function() {
|
|
705
|
-
var osVersion = /(\d+)\.(\d+)\.(\d+)/g.exec(
|
|
705
|
+
var osVersion = /(\d+)\.(\d+)\.(\d+)/g.exec(os11.release());
|
|
706
706
|
var buildNumber = 0;
|
|
707
707
|
if (osVersion && osVersion.length === 4) {
|
|
708
708
|
buildNumber = parseInt(osVersion[3]);
|
|
@@ -1012,15 +1012,15 @@ var require_unixTerminal = __commonJS({
|
|
|
1012
1012
|
})();
|
|
1013
1013
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
1014
1014
|
exports2.UnixTerminal = void 0;
|
|
1015
|
-
var
|
|
1016
|
-
var
|
|
1015
|
+
var fs12 = require("fs");
|
|
1016
|
+
var path19 = require("path");
|
|
1017
1017
|
var tty = require("tty");
|
|
1018
1018
|
var terminal_1 = require_terminal();
|
|
1019
1019
|
var utils_1 = require_utils();
|
|
1020
1020
|
var native = utils_1.loadNativeModule("pty");
|
|
1021
1021
|
var pty = native.module;
|
|
1022
1022
|
var helperPath = native.dir + "/spawn-helper";
|
|
1023
|
-
helperPath =
|
|
1023
|
+
helperPath = path19.resolve(__dirname, helperPath);
|
|
1024
1024
|
helperPath = helperPath.replace("app.asar", "app.asar.unpacked");
|
|
1025
1025
|
helperPath = helperPath.replace("node_modules.asar", "node_modules.asar.unpacked");
|
|
1026
1026
|
var DEFAULT_FILE = "sh";
|
|
@@ -1275,7 +1275,7 @@ var require_unixTerminal = __commonJS({
|
|
|
1275
1275
|
return;
|
|
1276
1276
|
}
|
|
1277
1277
|
var task = this._writeQueue[0];
|
|
1278
|
-
|
|
1278
|
+
fs12.write(this._fd, task.buffer, task.offset, function(err, written) {
|
|
1279
1279
|
if (err) {
|
|
1280
1280
|
if ("code" in err && err.code === "EAGAIN") {
|
|
1281
1281
|
_this._writeImmediate = setImmediate(function() {
|
|
@@ -1313,10 +1313,10 @@ var require_lib = __commonJS({
|
|
|
1313
1313
|
} else {
|
|
1314
1314
|
terminalCtor = require_unixTerminal().UnixTerminal;
|
|
1315
1315
|
}
|
|
1316
|
-
function
|
|
1316
|
+
function spawn10(file, args2, opt) {
|
|
1317
1317
|
return new terminalCtor(file, args2, opt);
|
|
1318
1318
|
}
|
|
1319
|
-
exports2.spawn =
|
|
1319
|
+
exports2.spawn = spawn10;
|
|
1320
1320
|
function fork(file, args2, opt) {
|
|
1321
1321
|
return new terminalCtor(file, args2, opt);
|
|
1322
1322
|
}
|
|
@@ -1390,11 +1390,6 @@ var require_src = __commonJS({
|
|
|
1390
1390
|
});
|
|
1391
1391
|
|
|
1392
1392
|
// src/commands/start.ts
|
|
1393
|
-
var fs8 = __toESM(require("fs"));
|
|
1394
|
-
var os7 = __toESM(require("os"));
|
|
1395
|
-
var path11 = __toESM(require("path"));
|
|
1396
|
-
var import_crypto = require("crypto");
|
|
1397
|
-
var import_child_process5 = require("child_process");
|
|
1398
1393
|
var import_picocolors2 = __toESM(require("picocolors"));
|
|
1399
1394
|
|
|
1400
1395
|
// src/config.ts
|
|
@@ -1482,7 +1477,7 @@ var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
|
|
|
1482
1477
|
// package.json
|
|
1483
1478
|
var package_default = {
|
|
1484
1479
|
name: "codeam-cli",
|
|
1485
|
-
version: "2.
|
|
1480
|
+
version: "2.5.0",
|
|
1486
1481
|
description: "Remote control Claude Code (and other AI coding agents) from your mobile phone. Pair your device, send prompts, stream responses in real-time, and approve commands \u2014 from anywhere.",
|
|
1487
1482
|
type: "commonjs",
|
|
1488
1483
|
main: "dist/index.js",
|
|
@@ -1601,8 +1596,14 @@ function showPairingCode(code, expiresAt) {
|
|
|
1601
1596
|
console.log("");
|
|
1602
1597
|
}
|
|
1603
1598
|
|
|
1604
|
-
// src/services/
|
|
1605
|
-
var
|
|
1599
|
+
// src/services/command-relay.service.ts
|
|
1600
|
+
var https2 = __toESM(require("https"));
|
|
1601
|
+
var http2 = __toESM(require("http"));
|
|
1602
|
+
|
|
1603
|
+
// src/services/pairing.service.ts
|
|
1604
|
+
var https = __toESM(require("https"));
|
|
1605
|
+
var http = __toESM(require("http"));
|
|
1606
|
+
var os2 = __toESM(require("os"));
|
|
1606
1607
|
|
|
1607
1608
|
// src/lib/poll-delay.ts
|
|
1608
1609
|
var MAX_DELAY_MS = 3e4;
|
|
@@ -1612,175 +1613,17 @@ function computePollDelay({ baseMs, failures }) {
|
|
|
1612
1613
|
return Math.round(jitter);
|
|
1613
1614
|
}
|
|
1614
1615
|
|
|
1615
|
-
// src/services/logger.ts
|
|
1616
|
-
var fs2 = __toESM(require("fs"));
|
|
1617
|
-
var os2 = __toESM(require("os"));
|
|
1618
|
-
var path2 = __toESM(require("path"));
|
|
1619
|
-
var LEVELS = { silent: 0, error: 1, warn: 2, info: 3, debug: 4, trace: 5 };
|
|
1620
|
-
function currentLevel() {
|
|
1621
|
-
if (process.env.CODEAM_DEBUG === "1") return LEVELS.trace;
|
|
1622
|
-
const raw = (process.env.CODEAM_LOG ?? "error").toLowerCase();
|
|
1623
|
-
return LEVELS[raw] ?? LEVELS.error;
|
|
1624
|
-
}
|
|
1625
|
-
var fileEnabled = process.env.CODEAM_DEBUG === "1" || process.env.CODEAM_LOG === "debug" || process.env.CODEAM_LOG === "trace";
|
|
1626
|
-
var debugFilePath = path2.join(os2.homedir(), ".codeam", "debug.log");
|
|
1627
|
-
var fileInitialized = false;
|
|
1628
|
-
function appendToFile(line) {
|
|
1629
|
-
if (!fileEnabled) return;
|
|
1630
|
-
try {
|
|
1631
|
-
if (!fileInitialized) {
|
|
1632
|
-
fs2.mkdirSync(path2.dirname(debugFilePath), { recursive: true });
|
|
1633
|
-
fs2.writeFileSync(
|
|
1634
|
-
debugFilePath,
|
|
1635
|
-
`=== codeam debug log \u2014 pid ${process.pid} \u2014 ${(/* @__PURE__ */ new Date()).toISOString()} ===
|
|
1636
|
-
platform=${process.platform} node=${process.version} cwd=${process.cwd()}
|
|
1637
|
-
|
|
1638
|
-
`
|
|
1639
|
-
);
|
|
1640
|
-
fileInitialized = true;
|
|
1641
|
-
}
|
|
1642
|
-
fs2.appendFileSync(debugFilePath, line);
|
|
1643
|
-
} catch {
|
|
1644
|
-
}
|
|
1645
|
-
}
|
|
1646
|
-
function emit(level, tag, msg, err) {
|
|
1647
|
-
if (LEVELS[level] > currentLevel()) return;
|
|
1648
|
-
const detail = err instanceof Error ? `: ${err.message}` : err !== void 0 ? `: ${String(err)}` : "";
|
|
1649
|
-
const line = `[codeam:${level}] ${tag} \u2014 ${msg}${detail}
|
|
1650
|
-
`;
|
|
1651
|
-
process.stderr.write(line);
|
|
1652
|
-
if (LEVELS[level] >= LEVELS.debug) {
|
|
1653
|
-
appendToFile(`${(/* @__PURE__ */ new Date()).toISOString()} ${line}`);
|
|
1654
|
-
}
|
|
1655
|
-
}
|
|
1656
|
-
var log = {
|
|
1657
|
-
error: (tag, msg, err) => emit("error", tag, msg, err),
|
|
1658
|
-
warn: (tag, msg, err) => emit("warn", tag, msg, err),
|
|
1659
|
-
info: (tag, msg, err) => emit("info", tag, msg, err),
|
|
1660
|
-
debug: (tag, msg, err) => emit("debug", tag, msg, err),
|
|
1661
|
-
/**
|
|
1662
|
-
* Verbose pipeline breadcrumb. Only fires when CODEAM_LOG=trace or
|
|
1663
|
-
* CODEAM_DEBUG=1, so call sites can be liberal — they have zero
|
|
1664
|
-
* cost in normal runs.
|
|
1665
|
-
*/
|
|
1666
|
-
trace: (tag, msg, err) => emit("trace", tag, msg, err)
|
|
1667
|
-
};
|
|
1668
|
-
|
|
1669
|
-
// src/services/websocket.service.ts
|
|
1670
|
-
var API_BASE = process.env.CODEAM_API_URL ?? "https://codeagent-mobile-api.vercel.app";
|
|
1671
|
-
var WS_URL = API_BASE.replace("https://", "wss://").replace("http://", "ws://") + "/api/ws";
|
|
1672
|
-
var HEARTBEAT_MS = 3e4;
|
|
1673
|
-
var MAX_RECONNECT = 10;
|
|
1674
|
-
var WebSocketService = class {
|
|
1675
|
-
constructor(sessionId, pluginId) {
|
|
1676
|
-
this.sessionId = sessionId;
|
|
1677
|
-
this.pluginId = pluginId;
|
|
1678
|
-
}
|
|
1679
|
-
sessionId;
|
|
1680
|
-
pluginId;
|
|
1681
|
-
client = null;
|
|
1682
|
-
heartbeat = null;
|
|
1683
|
-
reconnectTimer = null;
|
|
1684
|
-
reconnectAttempts = 0;
|
|
1685
|
-
handlers = [];
|
|
1686
|
-
_connected = false;
|
|
1687
|
-
get connected() {
|
|
1688
|
-
return this._connected;
|
|
1689
|
-
}
|
|
1690
|
-
addHandler(h) {
|
|
1691
|
-
this.handlers.push(h);
|
|
1692
|
-
}
|
|
1693
|
-
connect() {
|
|
1694
|
-
this.disconnect();
|
|
1695
|
-
try {
|
|
1696
|
-
this.client = new import_ws.default(WS_URL);
|
|
1697
|
-
this.client.on("open", () => {
|
|
1698
|
-
log.trace("ws", `connected to ${WS_URL}`);
|
|
1699
|
-
this._connected = true;
|
|
1700
|
-
this.reconnectAttempts = 0;
|
|
1701
|
-
this.client.send(JSON.stringify({
|
|
1702
|
-
type: "auth",
|
|
1703
|
-
payload: { sessionId: this.sessionId, pluginId: this.pluginId },
|
|
1704
|
-
timestamp: Date.now()
|
|
1705
|
-
}));
|
|
1706
|
-
this.startHeartbeat();
|
|
1707
|
-
this.handlers.forEach((h) => h.onConnected());
|
|
1708
|
-
});
|
|
1709
|
-
this.client.on("message", (raw) => {
|
|
1710
|
-
try {
|
|
1711
|
-
const msg = JSON.parse(raw.toString());
|
|
1712
|
-
if (msg.type === "pong" || msg.type === "auth_success" || msg.type === "auth_error") {
|
|
1713
|
-
log.trace("ws", `meta msg type=${msg.type}`);
|
|
1714
|
-
return;
|
|
1715
|
-
}
|
|
1716
|
-
log.trace("ws", `dispatch msg type=${msg.type}`);
|
|
1717
|
-
this.handlers.forEach((h) => h.onMessage(msg.type, msg.payload ?? {}));
|
|
1718
|
-
} catch (err) {
|
|
1719
|
-
log.trace("ws", "malformed message", err);
|
|
1720
|
-
}
|
|
1721
|
-
});
|
|
1722
|
-
this.client.on("close", (code, reason) => {
|
|
1723
|
-
log.trace("ws", `closed code=${code} reason=${reason?.toString() || "(empty)"}`);
|
|
1724
|
-
this._connected = false;
|
|
1725
|
-
this.stopHeartbeat();
|
|
1726
|
-
this.handlers.forEach((h) => h.onDisconnected());
|
|
1727
|
-
if (this.reconnectAttempts < MAX_RECONNECT) {
|
|
1728
|
-
this.reconnectAttempts++;
|
|
1729
|
-
const delay = computePollDelay({ baseMs: 1e3, failures: this.reconnectAttempts });
|
|
1730
|
-
this.reconnectTimer = setTimeout(() => this.connect(), delay);
|
|
1731
|
-
}
|
|
1732
|
-
});
|
|
1733
|
-
this.client.on("error", (err) => {
|
|
1734
|
-
log.trace("ws", "error", err);
|
|
1735
|
-
});
|
|
1736
|
-
} catch (err) {
|
|
1737
|
-
log.trace("ws", "sync connect threw", err);
|
|
1738
|
-
}
|
|
1739
|
-
}
|
|
1740
|
-
send(type, payload) {
|
|
1741
|
-
if (!this._connected || !this.client) return;
|
|
1742
|
-
this.client.send(JSON.stringify({ type, payload, timestamp: Date.now() }));
|
|
1743
|
-
}
|
|
1744
|
-
disconnect() {
|
|
1745
|
-
if (this.reconnectTimer) {
|
|
1746
|
-
clearTimeout(this.reconnectTimer);
|
|
1747
|
-
this.reconnectTimer = null;
|
|
1748
|
-
}
|
|
1749
|
-
this.reconnectAttempts = 0;
|
|
1750
|
-
this.stopHeartbeat();
|
|
1751
|
-
this.client?.removeAllListeners();
|
|
1752
|
-
this.client?.close();
|
|
1753
|
-
this.client = null;
|
|
1754
|
-
this._connected = false;
|
|
1755
|
-
}
|
|
1756
|
-
startHeartbeat() {
|
|
1757
|
-
this.stopHeartbeat();
|
|
1758
|
-
this.heartbeat = setInterval(() => {
|
|
1759
|
-
if (this._connected) this.client?.send(JSON.stringify({ type: "ping", timestamp: Date.now() }));
|
|
1760
|
-
}, HEARTBEAT_MS);
|
|
1761
|
-
}
|
|
1762
|
-
stopHeartbeat() {
|
|
1763
|
-
if (this.heartbeat) {
|
|
1764
|
-
clearInterval(this.heartbeat);
|
|
1765
|
-
this.heartbeat = null;
|
|
1766
|
-
}
|
|
1767
|
-
}
|
|
1768
|
-
};
|
|
1769
|
-
|
|
1770
1616
|
// src/services/pairing.service.ts
|
|
1771
|
-
var
|
|
1772
|
-
var http = __toESM(require("http"));
|
|
1773
|
-
var os3 = __toESM(require("os"));
|
|
1774
|
-
var API_BASE2 = process.env.CODEAM_API_URL ?? "https://codeagent-mobile-api.vercel.app";
|
|
1617
|
+
var API_BASE = process.env.CODEAM_API_URL ?? "https://codeagent-mobile-api.vercel.app";
|
|
1775
1618
|
async function requestCode(pluginId) {
|
|
1776
1619
|
try {
|
|
1777
1620
|
const runtime = process.env.CODESPACES === "true" ? "github-codespaces" : "local";
|
|
1778
1621
|
const codespaceName = process.env.CODESPACE_NAME;
|
|
1779
|
-
const result = await _transport.postJson(`${
|
|
1622
|
+
const result = await _transport.postJson(`${API_BASE}/api/pairing/code`, {
|
|
1780
1623
|
pluginId,
|
|
1781
1624
|
ideName: "Terminal (codeam-cli)",
|
|
1782
1625
|
ideVersion: package_default.version,
|
|
1783
|
-
hostname:
|
|
1626
|
+
hostname: os2.hostname(),
|
|
1784
1627
|
runtime,
|
|
1785
1628
|
...codespaceName ? { codespaceName } : {}
|
|
1786
1629
|
});
|
|
@@ -1799,7 +1642,7 @@ function pollStatus(pluginId, onPaired, onTimeout) {
|
|
|
1799
1642
|
if (stopped) return;
|
|
1800
1643
|
try {
|
|
1801
1644
|
const result = await _transport.getJson(
|
|
1802
|
-
`${
|
|
1645
|
+
`${API_BASE}/api/pairing/status?pluginId=${pluginId}`
|
|
1803
1646
|
);
|
|
1804
1647
|
consecutiveFailures = 0;
|
|
1805
1648
|
const data = result?.data;
|
|
@@ -1932,8 +1775,62 @@ async function _getJson(url) {
|
|
|
1932
1775
|
});
|
|
1933
1776
|
}
|
|
1934
1777
|
|
|
1778
|
+
// src/services/logger.ts
|
|
1779
|
+
var fs2 = __toESM(require("fs"));
|
|
1780
|
+
var os3 = __toESM(require("os"));
|
|
1781
|
+
var path2 = __toESM(require("path"));
|
|
1782
|
+
var LEVELS = { silent: 0, error: 1, warn: 2, info: 3, debug: 4, trace: 5 };
|
|
1783
|
+
function currentLevel() {
|
|
1784
|
+
if (process.env.CODEAM_DEBUG === "1") return LEVELS.trace;
|
|
1785
|
+
const raw = (process.env.CODEAM_LOG ?? "error").toLowerCase();
|
|
1786
|
+
return LEVELS[raw] ?? LEVELS.error;
|
|
1787
|
+
}
|
|
1788
|
+
var fileEnabled = process.env.CODEAM_DEBUG === "1" || process.env.CODEAM_LOG === "debug" || process.env.CODEAM_LOG === "trace";
|
|
1789
|
+
var debugFilePath = path2.join(os3.homedir(), ".codeam", "debug.log");
|
|
1790
|
+
var fileInitialized = false;
|
|
1791
|
+
function appendToFile(line) {
|
|
1792
|
+
if (!fileEnabled) return;
|
|
1793
|
+
try {
|
|
1794
|
+
if (!fileInitialized) {
|
|
1795
|
+
fs2.mkdirSync(path2.dirname(debugFilePath), { recursive: true });
|
|
1796
|
+
fs2.writeFileSync(
|
|
1797
|
+
debugFilePath,
|
|
1798
|
+
`=== codeam debug log \u2014 pid ${process.pid} \u2014 ${(/* @__PURE__ */ new Date()).toISOString()} ===
|
|
1799
|
+
platform=${process.platform} node=${process.version} cwd=${process.cwd()}
|
|
1800
|
+
|
|
1801
|
+
`
|
|
1802
|
+
);
|
|
1803
|
+
fileInitialized = true;
|
|
1804
|
+
}
|
|
1805
|
+
fs2.appendFileSync(debugFilePath, line);
|
|
1806
|
+
} catch {
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
function emit(level, tag, msg, err) {
|
|
1810
|
+
if (LEVELS[level] > currentLevel()) return;
|
|
1811
|
+
const detail = err instanceof Error ? `: ${err.message}` : err !== void 0 ? `: ${String(err)}` : "";
|
|
1812
|
+
const line = `[codeam:${level}] ${tag} \u2014 ${msg}${detail}
|
|
1813
|
+
`;
|
|
1814
|
+
process.stderr.write(line);
|
|
1815
|
+
if (LEVELS[level] >= LEVELS.debug) {
|
|
1816
|
+
appendToFile(`${(/* @__PURE__ */ new Date()).toISOString()} ${line}`);
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
var log = {
|
|
1820
|
+
error: (tag, msg, err) => emit("error", tag, msg, err),
|
|
1821
|
+
warn: (tag, msg, err) => emit("warn", tag, msg, err),
|
|
1822
|
+
info: (tag, msg, err) => emit("info", tag, msg, err),
|
|
1823
|
+
debug: (tag, msg, err) => emit("debug", tag, msg, err),
|
|
1824
|
+
/**
|
|
1825
|
+
* Verbose pipeline breadcrumb. Only fires when CODEAM_LOG=trace or
|
|
1826
|
+
* CODEAM_DEBUG=1, so call sites can be liberal — they have zero
|
|
1827
|
+
* cost in normal runs.
|
|
1828
|
+
*/
|
|
1829
|
+
trace: (tag, msg, err) => emit("trace", tag, msg, err)
|
|
1830
|
+
};
|
|
1831
|
+
|
|
1935
1832
|
// src/services/command-relay.service.ts
|
|
1936
|
-
var
|
|
1833
|
+
var API_BASE2 = process.env.CODEAM_API_URL ?? "https://codeagent-mobile-api.vercel.app";
|
|
1937
1834
|
var CommandRelayService = class {
|
|
1938
1835
|
constructor(pluginId, onCommand) {
|
|
1939
1836
|
this.pluginId = pluginId;
|
|
@@ -1942,108 +1839,179 @@ var CommandRelayService = class {
|
|
|
1942
1839
|
pluginId;
|
|
1943
1840
|
onCommand;
|
|
1944
1841
|
_running = false;
|
|
1945
|
-
pollTimer = null;
|
|
1946
1842
|
heartbeatTimer = null;
|
|
1947
1843
|
agentsTimer = null;
|
|
1948
|
-
consecutiveFailures = 0;
|
|
1949
1844
|
/** True once `/api/plugin/agents` has accepted at least one report. */
|
|
1950
1845
|
agentsRegistered = false;
|
|
1846
|
+
/** SSE connection (null when on the polling fallback or stopped). */
|
|
1847
|
+
sseRequest = null;
|
|
1848
|
+
/** Polling backoff state (only used on the fallback). */
|
|
1849
|
+
pollTimer = null;
|
|
1850
|
+
pollFailures = 0;
|
|
1851
|
+
/** Reconnect backoff state for the SSE stream. */
|
|
1852
|
+
sseFailures = 0;
|
|
1853
|
+
sseReconnectTimer = null;
|
|
1951
1854
|
start() {
|
|
1952
|
-
|
|
1953
|
-
clearTimeout(this.pollTimer);
|
|
1954
|
-
this.pollTimer = null;
|
|
1955
|
-
}
|
|
1956
|
-
if (this.heartbeatTimer) {
|
|
1957
|
-
clearInterval(this.heartbeatTimer);
|
|
1958
|
-
this.heartbeatTimer = null;
|
|
1959
|
-
}
|
|
1960
|
-
if (this.agentsTimer) {
|
|
1961
|
-
clearInterval(this.agentsTimer);
|
|
1962
|
-
this.agentsTimer = null;
|
|
1963
|
-
}
|
|
1855
|
+
this.cleanup();
|
|
1964
1856
|
this._running = true;
|
|
1965
1857
|
this.agentsRegistered = false;
|
|
1966
1858
|
this.sendHeartbeat(true);
|
|
1967
1859
|
this.heartbeatTimer = setInterval(() => this.sendHeartbeat(true), 2e4);
|
|
1968
|
-
void this.pollLoop();
|
|
1969
|
-
this.reportAgents();
|
|
1970
1860
|
this.agentsTimer = setInterval(() => {
|
|
1971
1861
|
if (this._running && !this.agentsRegistered) this.reportAgents();
|
|
1972
1862
|
}, 5e3);
|
|
1863
|
+
this.reportAgents();
|
|
1864
|
+
if (process.env.NODE_ENV === "test" || process.env.CODEAM_DISABLE_SSE_PULL === "1") {
|
|
1865
|
+
this.startPollingFallback();
|
|
1866
|
+
} else {
|
|
1867
|
+
this.connectSSE();
|
|
1868
|
+
}
|
|
1973
1869
|
}
|
|
1974
1870
|
stop() {
|
|
1975
1871
|
if (!this._running) return;
|
|
1976
1872
|
this._running = false;
|
|
1977
|
-
|
|
1978
|
-
clearTimeout(this.pollTimer);
|
|
1979
|
-
this.pollTimer = null;
|
|
1980
|
-
}
|
|
1981
|
-
if (this.heartbeatTimer) {
|
|
1982
|
-
clearInterval(this.heartbeatTimer);
|
|
1983
|
-
this.heartbeatTimer = null;
|
|
1984
|
-
}
|
|
1985
|
-
if (this.agentsTimer) {
|
|
1986
|
-
clearInterval(this.agentsTimer);
|
|
1987
|
-
this.agentsTimer = null;
|
|
1988
|
-
}
|
|
1873
|
+
this.cleanup();
|
|
1989
1874
|
this.sendHeartbeat(false).catch(() => {
|
|
1990
1875
|
});
|
|
1991
1876
|
}
|
|
1992
1877
|
async sendResult(commandId, status2, result) {
|
|
1993
|
-
await _postJson(`${
|
|
1878
|
+
await _postJson(`${API_BASE2}/api/commands/result`, { commandId, status: status2, result });
|
|
1879
|
+
}
|
|
1880
|
+
// ─── SSE pull (primary) ──────────────────────────────────────────
|
|
1881
|
+
connectSSE() {
|
|
1882
|
+
if (!this._running) return;
|
|
1883
|
+
const url = new URL(`${API_BASE2}/api/commands/pending/stream`);
|
|
1884
|
+
url.searchParams.set("pluginId", this.pluginId);
|
|
1885
|
+
const transport = url.protocol === "https:" ? https2 : http2;
|
|
1886
|
+
log.trace("relay", `sse connect ${url.pathname}`);
|
|
1887
|
+
const req = transport.request(
|
|
1888
|
+
{
|
|
1889
|
+
hostname: url.hostname,
|
|
1890
|
+
port: url.port || (url.protocol === "https:" ? 443 : 80),
|
|
1891
|
+
path: `${url.pathname}${url.search}`,
|
|
1892
|
+
method: "GET",
|
|
1893
|
+
headers: { Accept: "text/event-stream", "Cache-Control": "no-cache" },
|
|
1894
|
+
timeout: 35e3
|
|
1895
|
+
},
|
|
1896
|
+
(res) => {
|
|
1897
|
+
if (res.statusCode !== 200) {
|
|
1898
|
+
log.trace("relay", `sse status=${res.statusCode}`);
|
|
1899
|
+
res.resume();
|
|
1900
|
+
this.sseFailures += 1;
|
|
1901
|
+
if (this.sseFailures >= 2) {
|
|
1902
|
+
log.trace("relay", "sse unavailable, falling back to polling");
|
|
1903
|
+
this.startPollingFallback();
|
|
1904
|
+
return;
|
|
1905
|
+
}
|
|
1906
|
+
this.scheduleSseReconnect();
|
|
1907
|
+
return;
|
|
1908
|
+
}
|
|
1909
|
+
this.sseFailures = 0;
|
|
1910
|
+
let buffer = "";
|
|
1911
|
+
res.setEncoding("utf8");
|
|
1912
|
+
res.on("data", (chunk) => {
|
|
1913
|
+
buffer += chunk;
|
|
1914
|
+
let frameEnd;
|
|
1915
|
+
while ((frameEnd = buffer.indexOf("\n\n")) !== -1) {
|
|
1916
|
+
const frame = buffer.slice(0, frameEnd);
|
|
1917
|
+
buffer = buffer.slice(frameEnd + 2);
|
|
1918
|
+
this.handleSseFrame(frame);
|
|
1919
|
+
}
|
|
1920
|
+
});
|
|
1921
|
+
res.on("end", () => {
|
|
1922
|
+
if (this._running) this.scheduleSseReconnect();
|
|
1923
|
+
});
|
|
1924
|
+
res.on("error", () => {
|
|
1925
|
+
if (this._running) this.scheduleSseReconnect();
|
|
1926
|
+
});
|
|
1927
|
+
}
|
|
1928
|
+
);
|
|
1929
|
+
req.on("error", (err) => {
|
|
1930
|
+
log.trace("relay", "sse req error", err);
|
|
1931
|
+
this.sseFailures += 1;
|
|
1932
|
+
if (this.sseFailures >= 2) {
|
|
1933
|
+
this.startPollingFallback();
|
|
1934
|
+
return;
|
|
1935
|
+
}
|
|
1936
|
+
this.scheduleSseReconnect();
|
|
1937
|
+
});
|
|
1938
|
+
req.on("timeout", () => {
|
|
1939
|
+
req.destroy();
|
|
1940
|
+
});
|
|
1941
|
+
req.end();
|
|
1942
|
+
this.sseRequest = req;
|
|
1943
|
+
}
|
|
1944
|
+
handleSseFrame(frame) {
|
|
1945
|
+
let event = "message";
|
|
1946
|
+
let data = "";
|
|
1947
|
+
for (const line of frame.split("\n")) {
|
|
1948
|
+
if (line.startsWith("event: ")) event = line.slice(7).trim();
|
|
1949
|
+
else if (line.startsWith("data: ")) data += line.slice(6);
|
|
1950
|
+
}
|
|
1951
|
+
if (event !== "commands" || !data) return;
|
|
1952
|
+
try {
|
|
1953
|
+
const parsed = JSON.parse(data);
|
|
1954
|
+
const commands = parsed.commands ?? [];
|
|
1955
|
+
if (commands.length === 0) return;
|
|
1956
|
+
log.trace("relay", `sse received ${commands.length} command(s)`);
|
|
1957
|
+
void this.dispatchCommands(commands);
|
|
1958
|
+
} catch (err) {
|
|
1959
|
+
log.trace("relay", "sse parse error", err);
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
scheduleSseReconnect() {
|
|
1963
|
+
if (this.sseReconnectTimer) return;
|
|
1964
|
+
const delay = computePollDelay({ baseMs: 1e3, failures: this.sseFailures });
|
|
1965
|
+
this.sseReconnectTimer = setTimeout(() => {
|
|
1966
|
+
this.sseReconnectTimer = null;
|
|
1967
|
+
this.connectSSE();
|
|
1968
|
+
}, delay);
|
|
1969
|
+
}
|
|
1970
|
+
// ─── Polling fallback ────────────────────────────────────────────
|
|
1971
|
+
startPollingFallback() {
|
|
1972
|
+
if (this.pollTimer) return;
|
|
1973
|
+
void this.pollLoop();
|
|
1994
1974
|
}
|
|
1995
1975
|
async pollLoop() {
|
|
1996
1976
|
if (!this._running) return;
|
|
1997
|
-
await this.
|
|
1977
|
+
await this.pollOnce();
|
|
1998
1978
|
if (this._running) {
|
|
1999
|
-
const delay = computePollDelay({
|
|
2000
|
-
baseMs: 2e3,
|
|
2001
|
-
failures: this.consecutiveFailures
|
|
2002
|
-
});
|
|
1979
|
+
const delay = computePollDelay({ baseMs: 2e3, failures: this.pollFailures });
|
|
2003
1980
|
this.pollTimer = setTimeout(() => this.pollLoop(), delay);
|
|
2004
1981
|
}
|
|
2005
1982
|
}
|
|
2006
|
-
async
|
|
1983
|
+
async pollOnce() {
|
|
2007
1984
|
try {
|
|
2008
|
-
const data = await _getJson(
|
|
2009
|
-
`${API_BASE3}/api/commands/pending?pluginId=${this.pluginId}`
|
|
2010
|
-
);
|
|
1985
|
+
const data = await _getJson(`${API_BASE2}/api/commands/pending?pluginId=${this.pluginId}`);
|
|
2011
1986
|
const commands = data?.data;
|
|
2012
|
-
this.
|
|
2013
|
-
if (!Array.isArray(commands)) return;
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
}
|
|
2017
|
-
for (const obj of commands) {
|
|
2018
|
-
try {
|
|
2019
|
-
log.trace("relay", `dispatch type=${obj.type} id=${obj.id}`);
|
|
2020
|
-
await this.onCommand({
|
|
2021
|
-
id: obj.id,
|
|
2022
|
-
sessionId: obj.sessionId,
|
|
2023
|
-
type: obj.type,
|
|
2024
|
-
payload: obj.payload ?? {}
|
|
2025
|
-
});
|
|
2026
|
-
} catch (err) {
|
|
2027
|
-
log.trace("relay", `command handler threw`, err);
|
|
2028
|
-
}
|
|
2029
|
-
}
|
|
1987
|
+
this.pollFailures = 0;
|
|
1988
|
+
if (!Array.isArray(commands) || commands.length === 0) return;
|
|
1989
|
+
log.trace("relay", `poll received ${commands.length} command(s)`);
|
|
1990
|
+
await this.dispatchCommands(commands);
|
|
2030
1991
|
} catch (err) {
|
|
2031
|
-
this.
|
|
2032
|
-
log.trace(
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
1992
|
+
this.pollFailures += 1;
|
|
1993
|
+
log.trace("relay", `poll failed (failures=${this.pollFailures})`, err);
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
async dispatchCommands(commands) {
|
|
1997
|
+
for (const cmd of commands) {
|
|
1998
|
+
try {
|
|
1999
|
+
log.trace("relay", `dispatch type=${cmd.type} id=${cmd.id}`);
|
|
2000
|
+
await this.onCommand(cmd);
|
|
2001
|
+
} catch (err) {
|
|
2002
|
+
log.trace("relay", "command handler threw", err);
|
|
2003
|
+
}
|
|
2037
2004
|
}
|
|
2038
2005
|
}
|
|
2006
|
+
// ─── Heartbeat + agents ──────────────────────────────────────────
|
|
2039
2007
|
async sendHeartbeat(online) {
|
|
2040
|
-
await _postJson(`${
|
|
2008
|
+
await _postJson(`${API_BASE2}/api/plugin/heartbeat`, {
|
|
2041
2009
|
pluginId: this.pluginId,
|
|
2042
2010
|
online
|
|
2043
2011
|
}).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
|
|
2044
2012
|
}
|
|
2045
2013
|
reportAgents() {
|
|
2046
|
-
_postJson(`${
|
|
2014
|
+
_postJson(`${API_BASE2}/api/plugin/agents`, {
|
|
2047
2015
|
pluginId: this.pluginId,
|
|
2048
2016
|
agents: [{ id: "claude-code", name: "Claude Code", icon: "\u{1F916}", installed: true }]
|
|
2049
2017
|
}).then(() => {
|
|
@@ -2051,6 +2019,32 @@ var CommandRelayService = class {
|
|
|
2051
2019
|
}).catch(() => {
|
|
2052
2020
|
});
|
|
2053
2021
|
}
|
|
2022
|
+
// ─── Lifecycle ───────────────────────────────────────────────────
|
|
2023
|
+
cleanup() {
|
|
2024
|
+
if (this.pollTimer) {
|
|
2025
|
+
clearTimeout(this.pollTimer);
|
|
2026
|
+
this.pollTimer = null;
|
|
2027
|
+
}
|
|
2028
|
+
if (this.heartbeatTimer) {
|
|
2029
|
+
clearInterval(this.heartbeatTimer);
|
|
2030
|
+
this.heartbeatTimer = null;
|
|
2031
|
+
}
|
|
2032
|
+
if (this.agentsTimer) {
|
|
2033
|
+
clearInterval(this.agentsTimer);
|
|
2034
|
+
this.agentsTimer = null;
|
|
2035
|
+
}
|
|
2036
|
+
if (this.sseReconnectTimer) {
|
|
2037
|
+
clearTimeout(this.sseReconnectTimer);
|
|
2038
|
+
this.sseReconnectTimer = null;
|
|
2039
|
+
}
|
|
2040
|
+
if (this.sseRequest) {
|
|
2041
|
+
try {
|
|
2042
|
+
this.sseRequest.destroy();
|
|
2043
|
+
} catch {
|
|
2044
|
+
}
|
|
2045
|
+
this.sseRequest = null;
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2054
2048
|
};
|
|
2055
2049
|
|
|
2056
2050
|
// src/services/pty/unix.strategy.ts
|
|
@@ -4645,10 +4639,6 @@ var ClaudeService = class {
|
|
|
4645
4639
|
}
|
|
4646
4640
|
};
|
|
4647
4641
|
|
|
4648
|
-
// src/services/output.service.ts
|
|
4649
|
-
var https2 = __toESM(require("https"));
|
|
4650
|
-
var http2 = __toESM(require("http"));
|
|
4651
|
-
|
|
4652
4642
|
// ../../packages/shared/src/protocol/parseChrome.ts
|
|
4653
4643
|
var SPINNER_RE = /^(?:[✳✢✶✻✽✴✷✸✹⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏◐◑◒◓▁▂▃▄▅▆▇█]|🔴|🟠|🟡|🟢|🔵|🟣|🟤|⚫|⚪|🌀|💭|✨)\s/u;
|
|
4654
4644
|
var BULLET_TOOL_RE = /^•\s+(?:Read(?:ing)?|Edit(?:ing)?|Writ(?:e|ing)|Bash|Runn(?:ing)?|Search(?:ing)?|Glob(?:bing)?|Grep(?:ping)?|Creat(?:e|ing)|Execut(?:e|ing)|Task|Agent|NotebookEdit)\b/i;
|
|
@@ -5019,158 +5009,358 @@ function getContextWindow(model) {
|
|
|
5019
5009
|
return DEFAULT_CONTEXT_WINDOW;
|
|
5020
5010
|
}
|
|
5021
5011
|
|
|
5012
|
+
// src/services/output/chrome-tracker.ts
|
|
5013
|
+
var ChromeStepTracker = class {
|
|
5014
|
+
history = [];
|
|
5015
|
+
sentCount = 0;
|
|
5016
|
+
reset() {
|
|
5017
|
+
this.history = [];
|
|
5018
|
+
this.sentCount = 0;
|
|
5019
|
+
}
|
|
5020
|
+
/** Parse the rendered lines, append unseen steps to the cumulative history. */
|
|
5021
|
+
ingest(lines) {
|
|
5022
|
+
const visible = lines.filter((l) => isChromeLine(l)).map((l) => parseChromeLine(l)).filter((s) => s !== null);
|
|
5023
|
+
if (visible.length === 0) return;
|
|
5024
|
+
for (const step of visible) {
|
|
5025
|
+
const exists = this.history.some(
|
|
5026
|
+
(s) => s.tool === step.tool && s.label === step.label
|
|
5027
|
+
);
|
|
5028
|
+
if (!exists) this.history.push(step);
|
|
5029
|
+
}
|
|
5030
|
+
}
|
|
5031
|
+
/**
|
|
5032
|
+
* Returns the steps that have NOT yet been shipped on the wire,
|
|
5033
|
+
* marking them as shipped. Empty array means nothing new since
|
|
5034
|
+
* the last call. Caller forwards this as the chunk's
|
|
5035
|
+
* `appendSteps` payload.
|
|
5036
|
+
*/
|
|
5037
|
+
consumeDelta() {
|
|
5038
|
+
if (this.history.length === this.sentCount) return [];
|
|
5039
|
+
const delta = this.history.slice(this.sentCount);
|
|
5040
|
+
this.sentCount = this.history.length;
|
|
5041
|
+
return delta;
|
|
5042
|
+
}
|
|
5043
|
+
/** Snapshot of the cumulative unique-step history (debug + tests). */
|
|
5044
|
+
get cumulativeHistory() {
|
|
5045
|
+
return this.history;
|
|
5046
|
+
}
|
|
5047
|
+
};
|
|
5048
|
+
|
|
5049
|
+
// src/services/output/chunk-emitter.ts
|
|
5050
|
+
var https3 = __toESM(require("https"));
|
|
5051
|
+
var http3 = __toESM(require("http"));
|
|
5052
|
+
var API_BASE3 = process.env.CODEAM_API_URL ?? "https://codeagent-mobile-api.vercel.app";
|
|
5053
|
+
var ChunkEmitter = class {
|
|
5054
|
+
constructor(opts) {
|
|
5055
|
+
this.opts = opts;
|
|
5056
|
+
this.headers = {
|
|
5057
|
+
"Content-Type": "application/json",
|
|
5058
|
+
// Tell the backend which wire-format version we speak so
|
|
5059
|
+
// it can route legacy translations / 426 us when we're
|
|
5060
|
+
// too far behind. Bumped to 2.0.0 with the discriminated-
|
|
5061
|
+
// chunk + delta-chrome refactor in this release.
|
|
5062
|
+
"X-Codeam-Protocol-Version": "2.0.0"
|
|
5063
|
+
};
|
|
5064
|
+
if (opts.pluginAuthToken) {
|
|
5065
|
+
this.headers["X-Plugin-Auth-Token"] = opts.pluginAuthToken;
|
|
5066
|
+
}
|
|
5067
|
+
}
|
|
5068
|
+
opts;
|
|
5069
|
+
url = `${API_BASE3}/api/commands/output`;
|
|
5070
|
+
headers;
|
|
5071
|
+
/**
|
|
5072
|
+
* Send a chunk. `body` is the chunk fields minus `sessionId` /
|
|
5073
|
+
* `pluginId` — the emitter splices those in. `critical = true`
|
|
5074
|
+
* triggers up to 3 retries with linear backoff (200/400/600 ms);
|
|
5075
|
+
* non-critical sends are best-effort (a transient miss gets
|
|
5076
|
+
* superseded by the next tick's emission).
|
|
5077
|
+
*/
|
|
5078
|
+
async send(body, opts = {}) {
|
|
5079
|
+
const payload = JSON.stringify({
|
|
5080
|
+
sessionId: this.opts.sessionId,
|
|
5081
|
+
pluginId: this.opts.pluginId,
|
|
5082
|
+
...body
|
|
5083
|
+
});
|
|
5084
|
+
const maxRetries = opts.critical ? 3 : 0;
|
|
5085
|
+
log.trace(
|
|
5086
|
+
"chunkEmitter",
|
|
5087
|
+
`send type=${body.type ?? "(clear)"} bytes=${payload.length}`
|
|
5088
|
+
);
|
|
5089
|
+
return new Promise((resolve2) => {
|
|
5090
|
+
const attempt = (attemptsLeft) => {
|
|
5091
|
+
_transport2.post(this.url, this.headers, payload).then(({ statusCode, body: resBody }) => {
|
|
5092
|
+
if (statusCode === 410 || statusCode === 404 && /SESSION_NOT_FOUND|SESSION_GONE/.test(resBody)) {
|
|
5093
|
+
process.stderr.write("[codeam] session was deleted/disconnected \u2014 stopping output stream.\n");
|
|
5094
|
+
resolve2({ dead: true });
|
|
5095
|
+
return;
|
|
5096
|
+
}
|
|
5097
|
+
if (statusCode >= 400) {
|
|
5098
|
+
process.stderr.write(`[codeam] output API error ${statusCode}: ${resBody}
|
|
5099
|
+
`);
|
|
5100
|
+
}
|
|
5101
|
+
log.trace("chunkEmitter", `status=${statusCode}`);
|
|
5102
|
+
resolve2({ dead: false });
|
|
5103
|
+
}).catch((err) => {
|
|
5104
|
+
log.trace(
|
|
5105
|
+
"chunkEmitter",
|
|
5106
|
+
`error retries-left=${attemptsLeft}`,
|
|
5107
|
+
err
|
|
5108
|
+
);
|
|
5109
|
+
if (attemptsLeft > 0) {
|
|
5110
|
+
const delay = 200 * (maxRetries - attemptsLeft + 1);
|
|
5111
|
+
setTimeout(() => attempt(attemptsLeft - 1), delay);
|
|
5112
|
+
} else {
|
|
5113
|
+
resolve2({ dead: false });
|
|
5114
|
+
}
|
|
5115
|
+
});
|
|
5116
|
+
};
|
|
5117
|
+
attempt(maxRetries);
|
|
5118
|
+
});
|
|
5119
|
+
}
|
|
5120
|
+
};
|
|
5121
|
+
var _transport2 = {
|
|
5122
|
+
post: _post
|
|
5123
|
+
};
|
|
5124
|
+
function _post(url, headers, payload) {
|
|
5125
|
+
return new Promise((resolve2, reject) => {
|
|
5126
|
+
let settled = false;
|
|
5127
|
+
const u2 = new URL(url);
|
|
5128
|
+
const transport = u2.protocol === "https:" ? https3 : http3;
|
|
5129
|
+
const req = transport.request(
|
|
5130
|
+
{
|
|
5131
|
+
hostname: u2.hostname,
|
|
5132
|
+
port: u2.port || (u2.protocol === "https:" ? 443 : 80),
|
|
5133
|
+
path: u2.pathname,
|
|
5134
|
+
method: "POST",
|
|
5135
|
+
headers: {
|
|
5136
|
+
...headers,
|
|
5137
|
+
"Content-Length": Buffer.byteLength(payload)
|
|
5138
|
+
},
|
|
5139
|
+
timeout: 8e3
|
|
5140
|
+
},
|
|
5141
|
+
(res) => {
|
|
5142
|
+
let resData = "";
|
|
5143
|
+
res.on("data", (c2) => {
|
|
5144
|
+
resData += c2.toString();
|
|
5145
|
+
});
|
|
5146
|
+
res.on("end", () => {
|
|
5147
|
+
if (settled) return;
|
|
5148
|
+
settled = true;
|
|
5149
|
+
resolve2({ statusCode: res.statusCode ?? 0, body: resData });
|
|
5150
|
+
});
|
|
5151
|
+
}
|
|
5152
|
+
);
|
|
5153
|
+
req.on("error", (err) => {
|
|
5154
|
+
if (settled) return;
|
|
5155
|
+
settled = true;
|
|
5156
|
+
reject(err);
|
|
5157
|
+
});
|
|
5158
|
+
req.on("timeout", () => {
|
|
5159
|
+
req.destroy();
|
|
5160
|
+
});
|
|
5161
|
+
req.write(payload);
|
|
5162
|
+
req.end();
|
|
5163
|
+
});
|
|
5164
|
+
}
|
|
5165
|
+
|
|
5166
|
+
// src/services/output/pty-buffer.ts
|
|
5167
|
+
var PtyBuffer = class {
|
|
5168
|
+
raw = "";
|
|
5169
|
+
active = false;
|
|
5170
|
+
lastPushAt = 0;
|
|
5171
|
+
terminalInputPending = false;
|
|
5172
|
+
/** Whether to absorb pushes (`true`) or only watch for terminal input (`false`). */
|
|
5173
|
+
get isActive() {
|
|
5174
|
+
return this.active;
|
|
5175
|
+
}
|
|
5176
|
+
/** Bytes accumulated since the last reset. */
|
|
5177
|
+
get content() {
|
|
5178
|
+
return this.raw;
|
|
5179
|
+
}
|
|
5180
|
+
/** Wall-clock of the most recent printable push (`0` if none yet this turn). */
|
|
5181
|
+
get lastPushTime() {
|
|
5182
|
+
return this.lastPushAt;
|
|
5183
|
+
}
|
|
5184
|
+
/** Length of the accumulated buffer in raw bytes (debug + tests). */
|
|
5185
|
+
get size() {
|
|
5186
|
+
return this.raw.length;
|
|
5187
|
+
}
|
|
5188
|
+
activate() {
|
|
5189
|
+
this.active = true;
|
|
5190
|
+
this.raw = "";
|
|
5191
|
+
this.lastPushAt = 0;
|
|
5192
|
+
this.terminalInputPending = false;
|
|
5193
|
+
}
|
|
5194
|
+
deactivate() {
|
|
5195
|
+
this.active = false;
|
|
5196
|
+
}
|
|
5197
|
+
reset() {
|
|
5198
|
+
this.raw = "";
|
|
5199
|
+
this.lastPushAt = 0;
|
|
5200
|
+
}
|
|
5201
|
+
/**
|
|
5202
|
+
* Ingest a raw PTY frame. Returns whether the buffer was active
|
|
5203
|
+
* at the time (caller cares because rendering only matters for
|
|
5204
|
+
* active frames) and whether this push triggered the
|
|
5205
|
+
* terminal-initiated-turn signal.
|
|
5206
|
+
*/
|
|
5207
|
+
push(raw) {
|
|
5208
|
+
if (!this.active) {
|
|
5209
|
+
let terminalInputDetected = false;
|
|
5210
|
+
if (!this.terminalInputPending && hasPrintable(raw)) {
|
|
5211
|
+
this.terminalInputPending = true;
|
|
5212
|
+
terminalInputDetected = true;
|
|
5213
|
+
}
|
|
5214
|
+
return { active: false, terminalInputDetected };
|
|
5215
|
+
}
|
|
5216
|
+
this.raw += raw;
|
|
5217
|
+
if (hasPrintable(raw)) this.lastPushAt = Date.now();
|
|
5218
|
+
return { active: true, terminalInputDetected: false };
|
|
5219
|
+
}
|
|
5220
|
+
};
|
|
5221
|
+
function hasPrintable(raw) {
|
|
5222
|
+
const stripped = raw.replace(/\x1B\[[^@-~]*[@-~]/g, "").replace(/[\x00-\x1F\x7F]/g, "");
|
|
5223
|
+
return stripped.trim().length > 0;
|
|
5224
|
+
}
|
|
5225
|
+
|
|
5226
|
+
// src/services/output/turn-renderer.ts
|
|
5227
|
+
function renderLines(buffer) {
|
|
5228
|
+
return renderToLines(buffer);
|
|
5229
|
+
}
|
|
5230
|
+
function detectAnySelector(lines) {
|
|
5231
|
+
return detectSelector(lines) ?? detectListSelector(lines);
|
|
5232
|
+
}
|
|
5233
|
+
function extractContent(lines) {
|
|
5234
|
+
return filterChrome(lines).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
5235
|
+
}
|
|
5236
|
+
|
|
5022
5237
|
// src/services/output.service.ts
|
|
5023
|
-
var API_BASE4 = process.env.CODEAM_API_URL ?? "https://codeagent-mobile-api.vercel.app";
|
|
5024
5238
|
var OutputService = class _OutputService {
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
this.pluginAuthToken = pluginAuthToken;
|
|
5029
|
-
this.onSessionIdDetected = onSessionIdDetected;
|
|
5030
|
-
this.onRateLimitDetected = onRateLimitDetected;
|
|
5031
|
-
this.onTurnComplete = onTurnComplete;
|
|
5032
|
-
this.onTerminalTurnDetected = onTerminalTurnDetected;
|
|
5033
|
-
}
|
|
5034
|
-
sessionId;
|
|
5035
|
-
pluginId;
|
|
5036
|
-
pluginAuthToken;
|
|
5037
|
-
rawBuffer = "";
|
|
5239
|
+
pty = new PtyBuffer();
|
|
5240
|
+
steps = new ChromeStepTracker();
|
|
5241
|
+
emitter;
|
|
5038
5242
|
lastSentContent = "";
|
|
5039
|
-
lastSentChromeStepsJson = "";
|
|
5040
|
-
chromeStepsHistory = [];
|
|
5041
5243
|
pollTimer = null;
|
|
5042
5244
|
startTime = 0;
|
|
5043
|
-
active = false;
|
|
5044
5245
|
terminalTurnPending = false;
|
|
5045
|
-
lastPushTime = 0;
|
|
5046
5246
|
onSessionIdDetected;
|
|
5047
5247
|
onRateLimitDetected;
|
|
5048
5248
|
onTurnComplete;
|
|
5049
5249
|
onTerminalTurnDetected;
|
|
5250
|
+
/** Tick cadence — every 1 s while a turn is active. */
|
|
5050
5251
|
static POLL_MS = 1e3;
|
|
5252
|
+
/** Idle threshold for "the agent's text settled, finalize the turn". */
|
|
5051
5253
|
static IDLE_MS = 3e3;
|
|
5052
|
-
/**
|
|
5254
|
+
/** Same threshold but tighter for selectors (UI is ready to interact immediately). */
|
|
5053
5255
|
static SELECTOR_IDLE_MS = 1500;
|
|
5054
5256
|
/**
|
|
5055
|
-
* Grace period before
|
|
5056
|
-
*
|
|
5057
|
-
*
|
|
5058
|
-
* receiving the input, but we give a 1.5 s margin for loaded machines).
|
|
5257
|
+
* Grace period before tick processes anything — Claude needs ~100-
|
|
5258
|
+
* 200 ms after `\r` to clear the input echo and re-render the TUI.
|
|
5259
|
+
* 1.5 s is a comfortable margin on loaded machines.
|
|
5059
5260
|
*/
|
|
5060
5261
|
static WARMUP_MS = 1500;
|
|
5061
|
-
/** Max idle with
|
|
5262
|
+
/** Max idle with chrome-only output before we stop waiting on the agent. */
|
|
5062
5263
|
static EMPTY_TIMEOUT_MS = 6e4;
|
|
5264
|
+
/** Hard turn cap — pathological no-op turns get cut after 2 minutes. */
|
|
5063
5265
|
static MAX_MS = 12e4;
|
|
5266
|
+
constructor(sessionId, pluginId, onSessionIdDetected, onRateLimitDetected, onTurnComplete, onTerminalTurnDetected, pluginAuthToken) {
|
|
5267
|
+
this.onSessionIdDetected = onSessionIdDetected;
|
|
5268
|
+
this.onRateLimitDetected = onRateLimitDetected;
|
|
5269
|
+
this.onTurnComplete = onTurnComplete;
|
|
5270
|
+
this.onTerminalTurnDetected = onTerminalTurnDetected;
|
|
5271
|
+
this.emitter = new ChunkEmitter({
|
|
5272
|
+
sessionId,
|
|
5273
|
+
pluginId,
|
|
5274
|
+
pluginAuthToken
|
|
5275
|
+
});
|
|
5276
|
+
}
|
|
5277
|
+
// ─── Turn lifecycle ──────────────────────────────────────────────
|
|
5278
|
+
/**
|
|
5279
|
+
* Begin a turn driven by a mobile-side prompt. Resets the buffer
|
|
5280
|
+
* and emits the boundary chunks (clear → new_turn) that tell
|
|
5281
|
+
* clients to wipe the prior agent reply and show "Agent is
|
|
5282
|
+
* typing…".
|
|
5283
|
+
*/
|
|
5284
|
+
newTurn() {
|
|
5285
|
+
log.trace("outputSvc", "newTurn() \u2014 activating output stream");
|
|
5286
|
+
this.beginTurn();
|
|
5287
|
+
this.send({ type: "clear" }, { critical: true }).then(() => this.send({ type: "new_turn", done: false }, { critical: true })).catch(() => {
|
|
5288
|
+
});
|
|
5289
|
+
}
|
|
5064
5290
|
/**
|
|
5065
|
-
*
|
|
5066
|
-
*
|
|
5067
|
-
*
|
|
5068
|
-
*
|
|
5291
|
+
* Begin a turn driven by the user typing locally in their
|
|
5292
|
+
* terminal. Same shape as `newTurn` but additionally sends a
|
|
5293
|
+
* `user_message` so collaborators see the prompt attributed
|
|
5294
|
+
* correctly. `userText` is the prompt text scraped from the
|
|
5295
|
+
* Claude JSONL by `historySvc.waitForNewUserMessage`.
|
|
5069
5296
|
*/
|
|
5070
5297
|
async startTerminalTurn(userText) {
|
|
5071
5298
|
this.terminalTurnPending = false;
|
|
5072
|
-
this.
|
|
5073
|
-
this.
|
|
5074
|
-
this.lastSentContent = "";
|
|
5075
|
-
this.lastSentChromeStepsJson = "";
|
|
5076
|
-
this.chromeStepsHistory = [];
|
|
5077
|
-
this.lastPushTime = 0;
|
|
5078
|
-
this.active = true;
|
|
5079
|
-
this.startTime = Date.now();
|
|
5080
|
-
await this.postChunk({ clear: true });
|
|
5299
|
+
this.beginTurn();
|
|
5300
|
+
await this.send({ type: "clear" }, { critical: true });
|
|
5081
5301
|
if (userText) {
|
|
5082
|
-
await this.
|
|
5302
|
+
await this.send({ type: "user_message", content: userText, done: true }, { critical: true });
|
|
5083
5303
|
}
|
|
5084
|
-
await this.
|
|
5085
|
-
this.pollTimer = setInterval(() => this.tick(), _OutputService.POLL_MS);
|
|
5086
|
-
}
|
|
5087
|
-
newTurn() {
|
|
5088
|
-
log.trace("outputSvc", "newTurn() \u2014 activating output stream");
|
|
5089
|
-
this.stopPoll();
|
|
5090
|
-
this.rawBuffer = "";
|
|
5091
|
-
this.lastSentContent = "";
|
|
5092
|
-
this.lastSentChromeStepsJson = "";
|
|
5093
|
-
this.chromeStepsHistory = [];
|
|
5094
|
-
this.lastPushTime = 0;
|
|
5095
|
-
this.active = true;
|
|
5096
|
-
this.terminalTurnPending = false;
|
|
5097
|
-
this.startTime = Date.now();
|
|
5098
|
-
this.postChunk({ clear: true }).then(() => this.postChunk({ type: "new_turn", content: "", done: false })).catch(() => {
|
|
5099
|
-
});
|
|
5100
|
-
this.pollTimer = setInterval(() => this.tick(), _OutputService.POLL_MS);
|
|
5304
|
+
await this.send({ type: "new_turn", done: false }, { critical: true });
|
|
5101
5305
|
}
|
|
5102
5306
|
/**
|
|
5103
|
-
*
|
|
5104
|
-
*
|
|
5105
|
-
*
|
|
5307
|
+
* Begin a turn after a `resume_session` request. Includes the
|
|
5308
|
+
* `resumedSessionId` so the client wipes its history and
|
|
5309
|
+
* re-fetches from the JSONL via `get_conversation`.
|
|
5106
5310
|
*/
|
|
5107
5311
|
async newTurnResume(resumedSessionId) {
|
|
5108
|
-
this.
|
|
5109
|
-
this.
|
|
5110
|
-
this.
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
this.active = true;
|
|
5115
|
-
this.startTime = Date.now();
|
|
5116
|
-
await this.postChunk({ clear: true });
|
|
5117
|
-
await this.postChunk({ type: "new_turn", resumedSessionId, content: "", done: false });
|
|
5118
|
-
this.pollTimer = setInterval(() => this.tick(), _OutputService.POLL_MS);
|
|
5312
|
+
this.beginTurn();
|
|
5313
|
+
await this.send({ type: "clear" }, { critical: true });
|
|
5314
|
+
await this.send(
|
|
5315
|
+
{ type: "new_turn", done: false, resumedSessionId },
|
|
5316
|
+
{ critical: true }
|
|
5317
|
+
);
|
|
5119
5318
|
}
|
|
5319
|
+
// ─── Pump ────────────────────────────────────────────────────────
|
|
5120
5320
|
push(raw) {
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
log.trace("outputSvc", `terminal-turn detected (idle, ${raw.length}B)`);
|
|
5127
|
-
this.onTerminalTurnDetected?.();
|
|
5128
|
-
}
|
|
5321
|
+
const result = this.pty.push(raw);
|
|
5322
|
+
if (!result.active) {
|
|
5323
|
+
if (result.terminalInputDetected && !this.terminalTurnPending) {
|
|
5324
|
+
this.terminalTurnPending = true;
|
|
5325
|
+
this.onTerminalTurnDetected?.();
|
|
5129
5326
|
}
|
|
5130
5327
|
log.trace("outputSvc", `push dropped (inactive, ${raw.length}B)`);
|
|
5131
5328
|
return;
|
|
5132
5329
|
}
|
|
5133
|
-
this.rawBuffer += raw;
|
|
5134
|
-
const printable = raw.replace(/\x1B\[[^@-~]*[@-~]/g, "").replace(/[\x00-\x1F\x7F]/g, "");
|
|
5135
|
-
if (printable.trim()) {
|
|
5136
|
-
this.lastPushTime = Date.now();
|
|
5137
|
-
this.tryExtractSessionId(printable);
|
|
5138
|
-
this.tryDetectRateLimit(printable);
|
|
5139
|
-
}
|
|
5140
5330
|
log.trace(
|
|
5141
5331
|
"outputSvc",
|
|
5142
|
-
`push +${raw.length}B (buf=${this.
|
|
5332
|
+
`push +${raw.length}B (buf=${this.pty.size}B)`
|
|
5143
5333
|
);
|
|
5334
|
+
this.tryExtractSessionId(raw);
|
|
5335
|
+
this.tryDetectRateLimit(raw);
|
|
5144
5336
|
}
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
/Resuming session[:\s]+([a-f0-9-]{36})/i,
|
|
5149
|
-
/Session[:\s]+([a-f0-9-]{36})/i,
|
|
5150
|
-
/Conversation[:\s]+([a-f0-9-]{36})/i,
|
|
5151
|
-
/Session\s+ID[:\s]+([a-f0-9-]{36})/i
|
|
5152
|
-
];
|
|
5153
|
-
for (const pattern of patterns) {
|
|
5154
|
-
const match = text.match(pattern);
|
|
5155
|
-
if (match && this.onSessionIdDetected) {
|
|
5156
|
-
this.onSessionIdDetected(match[1]);
|
|
5157
|
-
return;
|
|
5158
|
-
}
|
|
5159
|
-
}
|
|
5337
|
+
dispose() {
|
|
5338
|
+
this.stopPoll();
|
|
5339
|
+
this.pty.deactivate();
|
|
5160
5340
|
}
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5341
|
+
// ─── Internals ───────────────────────────────────────────────────
|
|
5342
|
+
beginTurn() {
|
|
5343
|
+
this.stopPoll();
|
|
5344
|
+
this.pty.activate();
|
|
5345
|
+
this.steps.reset();
|
|
5346
|
+
this.lastSentContent = "";
|
|
5347
|
+
this.startTime = Date.now();
|
|
5348
|
+
this.pollTimer = setInterval(() => this.tick(), _OutputService.POLL_MS);
|
|
5349
|
+
}
|
|
5350
|
+
async send(body, opts = {}) {
|
|
5351
|
+
const outcome = await this.emitter.send(body, opts);
|
|
5352
|
+
if (outcome.dead && this.pty.isActive) {
|
|
5353
|
+
this.dispose();
|
|
5166
5354
|
}
|
|
5167
5355
|
}
|
|
5168
|
-
|
|
5169
|
-
this.
|
|
5170
|
-
|
|
5356
|
+
stopPoll() {
|
|
5357
|
+
if (this.pollTimer) {
|
|
5358
|
+
clearInterval(this.pollTimer);
|
|
5359
|
+
this.pollTimer = null;
|
|
5360
|
+
}
|
|
5171
5361
|
}
|
|
5172
5362
|
tick() {
|
|
5173
|
-
if (!this.
|
|
5363
|
+
if (!this.pty.isActive) return;
|
|
5174
5364
|
const now = Date.now();
|
|
5175
5365
|
const elapsed = now - this.startTime;
|
|
5176
5366
|
if (elapsed >= _OutputService.MAX_MS) {
|
|
@@ -5178,36 +5368,51 @@ var OutputService = class _OutputService {
|
|
|
5178
5368
|
return;
|
|
5179
5369
|
}
|
|
5180
5370
|
if (elapsed < _OutputService.WARMUP_MS) return;
|
|
5181
|
-
const lines =
|
|
5182
|
-
this.
|
|
5183
|
-
const
|
|
5371
|
+
const lines = renderLines(this.pty.content);
|
|
5372
|
+
this.steps.ingest(lines);
|
|
5373
|
+
const stepsDelta = this.steps.consumeDelta();
|
|
5374
|
+
if (stepsDelta.length > 0) {
|
|
5375
|
+
this.send({ type: "chrome_steps", appendSteps: stepsDelta }).catch(() => {
|
|
5376
|
+
});
|
|
5377
|
+
}
|
|
5378
|
+
const selector = detectAnySelector(lines);
|
|
5184
5379
|
if (selector) {
|
|
5185
|
-
const idleMs2 = this.lastPushTime > 0 ? now - this.lastPushTime : elapsed;
|
|
5380
|
+
const idleMs2 = this.pty.lastPushTime > 0 ? now - this.pty.lastPushTime : elapsed;
|
|
5186
5381
|
log.trace(
|
|
5187
5382
|
"outputSvc",
|
|
5188
5383
|
`tick selector found (idleMs=${idleMs2}, options=${selector.options.length})`
|
|
5189
5384
|
);
|
|
5190
5385
|
if (idleMs2 >= _OutputService.SELECTOR_IDLE_MS) {
|
|
5191
5386
|
this.stopPoll();
|
|
5192
|
-
this.
|
|
5193
|
-
this.
|
|
5387
|
+
this.pty.deactivate();
|
|
5388
|
+
this.send(
|
|
5389
|
+
{
|
|
5390
|
+
type: "select_prompt",
|
|
5391
|
+
content: selector.question,
|
|
5392
|
+
options: selector.options,
|
|
5393
|
+
optionDescriptions: selector.optionDescriptions,
|
|
5394
|
+
currentIndex: selector.currentIndex,
|
|
5395
|
+
done: true
|
|
5396
|
+
},
|
|
5397
|
+
{ critical: true }
|
|
5398
|
+
).catch(() => {
|
|
5194
5399
|
});
|
|
5195
5400
|
}
|
|
5196
5401
|
return;
|
|
5197
5402
|
}
|
|
5198
|
-
const content =
|
|
5403
|
+
const content = extractContent(lines);
|
|
5199
5404
|
if (!content) {
|
|
5200
5405
|
log.trace(
|
|
5201
5406
|
"outputSvc",
|
|
5202
|
-
`tick empty content (raw=${this.
|
|
5407
|
+
`tick empty content (raw=${this.pty.size}B lines=${lines.length} elapsed=${elapsed}ms)`
|
|
5203
5408
|
);
|
|
5204
5409
|
if (elapsed >= _OutputService.EMPTY_TIMEOUT_MS) this.finalize();
|
|
5205
5410
|
return;
|
|
5206
5411
|
}
|
|
5207
|
-
const idleMs = this.lastPushTime > 0 ? now - this.lastPushTime : elapsed;
|
|
5412
|
+
const idleMs = this.pty.lastPushTime > 0 ? now - this.pty.lastPushTime : elapsed;
|
|
5208
5413
|
log.trace(
|
|
5209
5414
|
"outputSvc",
|
|
5210
|
-
`tick content (raw=${this.
|
|
5415
|
+
`tick content (raw=${this.pty.size}B lines=${lines.length} content=${content.length} idleMs=${idleMs})`
|
|
5211
5416
|
);
|
|
5212
5417
|
if (idleMs >= _OutputService.IDLE_MS) {
|
|
5213
5418
|
this.finalize();
|
|
@@ -5215,161 +5420,78 @@ var OutputService = class _OutputService {
|
|
|
5215
5420
|
}
|
|
5216
5421
|
if (content !== this.lastSentContent) {
|
|
5217
5422
|
this.lastSentContent = content;
|
|
5218
|
-
this.
|
|
5423
|
+
this.send({ type: "text", content, done: false }).catch(() => {
|
|
5219
5424
|
});
|
|
5220
5425
|
}
|
|
5221
5426
|
}
|
|
5222
5427
|
finalize() {
|
|
5223
|
-
const lines =
|
|
5224
|
-
this.
|
|
5225
|
-
const
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
if (selector) {
|
|
5229
|
-
this.postChunk({ type: "select_prompt", content: selector.question, options: selector.options, optionDescriptions: selector.optionDescriptions, currentIndex: selector.currentIndex, done: true }).catch(() => {
|
|
5230
|
-
});
|
|
5231
|
-
} else {
|
|
5232
|
-
const content = filterChrome(lines).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
5233
|
-
this.postChunk({ type: "text", content, done: true }).catch(() => {
|
|
5428
|
+
const lines = renderLines(this.pty.content);
|
|
5429
|
+
this.steps.ingest(lines);
|
|
5430
|
+
const stepsDelta = this.steps.consumeDelta();
|
|
5431
|
+
if (stepsDelta.length > 0) {
|
|
5432
|
+
this.send({ type: "chrome_steps", appendSteps: stepsDelta }).catch(() => {
|
|
5234
5433
|
});
|
|
5235
|
-
this.onTurnComplete?.();
|
|
5236
5434
|
}
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
this.
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
const exists = this.chromeStepsHistory.some(
|
|
5250
|
-
(s) => s.tool === step.tool && s.label === step.label
|
|
5251
|
-
);
|
|
5252
|
-
if (!exists) {
|
|
5253
|
-
this.chromeStepsHistory.push(step);
|
|
5254
|
-
changed = true;
|
|
5255
|
-
}
|
|
5256
|
-
}
|
|
5257
|
-
if (!changed) return;
|
|
5258
|
-
const json = JSON.stringify(this.chromeStepsHistory);
|
|
5259
|
-
if (json === this.lastSentChromeStepsJson) return;
|
|
5260
|
-
this.lastSentChromeStepsJson = json;
|
|
5261
|
-
this.postChunk({ type: "chrome_steps", content: "", steps: [...this.chromeStepsHistory] }).catch(() => {
|
|
5262
|
-
});
|
|
5263
|
-
}
|
|
5264
|
-
postChunk(body) {
|
|
5265
|
-
const isCritical = body.clear === true || body.type === "new_turn" || body.type === "user_message" || body.done === true;
|
|
5266
|
-
const maxRetries = isCritical ? 3 : 0;
|
|
5267
|
-
const payload = JSON.stringify({
|
|
5268
|
-
sessionId: this.sessionId,
|
|
5269
|
-
pluginId: this.pluginId,
|
|
5270
|
-
...body
|
|
5271
|
-
});
|
|
5272
|
-
const headers = {
|
|
5273
|
-
"Content-Type": "application/json"
|
|
5274
|
-
};
|
|
5275
|
-
if (this.pluginAuthToken) {
|
|
5276
|
-
headers["X-Plugin-Auth-Token"] = this.pluginAuthToken;
|
|
5277
|
-
}
|
|
5278
|
-
const chunkType = body.type ?? "(clear)";
|
|
5279
|
-
log.trace(
|
|
5280
|
-
"outputSvc",
|
|
5281
|
-
`postChunk type=${chunkType} done=${body.done === true} bytes=${payload.length}`
|
|
5282
|
-
);
|
|
5283
|
-
if (chunkType === "select_prompt" || chunkType === "new_turn" || body.type === "text" && body.done === true) {
|
|
5284
|
-
const preview = payload.length > 2048 ? payload.slice(0, 2048) + "\u2026(truncated)" : payload;
|
|
5285
|
-
log.trace("outputSvc", `payload ${preview}`);
|
|
5286
|
-
}
|
|
5287
|
-
return new Promise((resolve2) => {
|
|
5288
|
-
const attempt = (attemptsLeft) => {
|
|
5289
|
-
_transport2.sendOutputChunk(`${API_BASE4}/api/commands/output`, headers, payload).then(({ statusCode, body: resBody }) => {
|
|
5290
|
-
log.trace("outputSvc", `postChunk status=${statusCode}`);
|
|
5291
|
-
if (statusCode === 410 || statusCode === 404 && /SESSION_NOT_FOUND|SESSION_GONE/.test(resBody)) {
|
|
5292
|
-
if (this.active) {
|
|
5293
|
-
process.stderr.write("[codeam] session was deleted/disconnected \u2014 stopping output stream.\n");
|
|
5294
|
-
this.dispose();
|
|
5295
|
-
}
|
|
5296
|
-
resolve2();
|
|
5297
|
-
return;
|
|
5298
|
-
}
|
|
5299
|
-
if (statusCode >= 400) {
|
|
5300
|
-
process.stderr.write(`[codeam] output API error ${statusCode}: ${resBody}
|
|
5301
|
-
`);
|
|
5302
|
-
}
|
|
5303
|
-
resolve2();
|
|
5304
|
-
}).catch((err) => {
|
|
5305
|
-
log.trace(
|
|
5306
|
-
"outputSvc",
|
|
5307
|
-
`postChunk error (retries left=${attemptsLeft})`,
|
|
5308
|
-
err
|
|
5309
|
-
);
|
|
5310
|
-
if (attemptsLeft > 0) {
|
|
5311
|
-
const delay = 200 * (maxRetries - attemptsLeft + 1);
|
|
5312
|
-
setTimeout(() => attempt(attemptsLeft - 1), delay);
|
|
5313
|
-
} else {
|
|
5314
|
-
resolve2();
|
|
5315
|
-
}
|
|
5316
|
-
});
|
|
5317
|
-
};
|
|
5318
|
-
attempt(maxRetries);
|
|
5319
|
-
});
|
|
5320
|
-
}
|
|
5321
|
-
};
|
|
5322
|
-
var _transport2 = {
|
|
5323
|
-
sendOutputChunk: _sendOutputChunk
|
|
5324
|
-
};
|
|
5325
|
-
function _sendOutputChunk(url, headers, payload) {
|
|
5326
|
-
return new Promise((resolve2, reject) => {
|
|
5327
|
-
let settled = false;
|
|
5328
|
-
const u2 = new URL(url);
|
|
5329
|
-
const transport = u2.protocol === "https:" ? https2 : http2;
|
|
5330
|
-
const req = transport.request(
|
|
5331
|
-
{
|
|
5332
|
-
hostname: u2.hostname,
|
|
5333
|
-
port: u2.port || (u2.protocol === "https:" ? 443 : 80),
|
|
5334
|
-
path: u2.pathname,
|
|
5335
|
-
method: "POST",
|
|
5336
|
-
headers: {
|
|
5337
|
-
...headers,
|
|
5338
|
-
"Content-Length": Buffer.byteLength(payload)
|
|
5435
|
+
const selector = detectAnySelector(lines);
|
|
5436
|
+
this.stopPoll();
|
|
5437
|
+
this.pty.deactivate();
|
|
5438
|
+
if (selector) {
|
|
5439
|
+
this.send(
|
|
5440
|
+
{
|
|
5441
|
+
type: "select_prompt",
|
|
5442
|
+
content: selector.question,
|
|
5443
|
+
options: selector.options,
|
|
5444
|
+
optionDescriptions: selector.optionDescriptions,
|
|
5445
|
+
currentIndex: selector.currentIndex,
|
|
5446
|
+
done: true
|
|
5339
5447
|
},
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
}
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5448
|
+
{ critical: true }
|
|
5449
|
+
).catch(() => {
|
|
5450
|
+
});
|
|
5451
|
+
} else {
|
|
5452
|
+
const content = extractContent(lines);
|
|
5453
|
+
this.send(
|
|
5454
|
+
{ type: "text", content, done: true },
|
|
5455
|
+
{ critical: true }
|
|
5456
|
+
).catch(() => {
|
|
5457
|
+
});
|
|
5458
|
+
this.onTurnComplete?.();
|
|
5459
|
+
}
|
|
5460
|
+
}
|
|
5461
|
+
// ─── Side-channel observation (session id + rate limit) ──────────
|
|
5462
|
+
tryExtractSessionId(text) {
|
|
5463
|
+
if (!this.onSessionIdDetected) return;
|
|
5464
|
+
const printable = text.replace(/\x1B\[[^@-~]*[@-~]/g, "").replace(/[\x00-\x1F\x7F]/g, "");
|
|
5465
|
+
const patterns = [
|
|
5466
|
+
/Resuming session[:\s]+([a-f0-9-]{36})/i,
|
|
5467
|
+
/Session[:\s]+([a-f0-9-]{36})/i,
|
|
5468
|
+
/Conversation[:\s]+([a-f0-9-]{36})/i,
|
|
5469
|
+
/Session\s+ID[:\s]+([a-f0-9-]{36})/i
|
|
5470
|
+
];
|
|
5471
|
+
for (const pattern of patterns) {
|
|
5472
|
+
const match = printable.match(pattern);
|
|
5473
|
+
if (match) {
|
|
5474
|
+
this.onSessionIdDetected(match[1]);
|
|
5475
|
+
return;
|
|
5352
5476
|
}
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
}
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
});
|
|
5365
|
-
}
|
|
5477
|
+
}
|
|
5478
|
+
}
|
|
5479
|
+
tryDetectRateLimit(text) {
|
|
5480
|
+
if (!this.onRateLimitDetected) return;
|
|
5481
|
+
const printable = text.replace(/\x1B\[[^@-~]*[@-~]/g, "").replace(/[\x00-\x1F\x7F]/g, "");
|
|
5482
|
+
const match = printable.match(/hit your limit.*resets\s+(.+?)(?:\s*\(|$)/i) ?? printable.match(/rate.?limit.*resets\s+(.+?)(?:\s*\(|$)/i);
|
|
5483
|
+
if (match) {
|
|
5484
|
+
this.onRateLimitDetected(match[1].trim());
|
|
5485
|
+
}
|
|
5486
|
+
}
|
|
5487
|
+
};
|
|
5366
5488
|
|
|
5367
5489
|
// src/services/history.service.ts
|
|
5368
5490
|
var fs5 = __toESM(require("fs"));
|
|
5369
5491
|
var path8 = __toESM(require("path"));
|
|
5370
5492
|
var os6 = __toESM(require("os"));
|
|
5371
|
-
var
|
|
5372
|
-
var
|
|
5493
|
+
var https4 = __toESM(require("https"));
|
|
5494
|
+
var http4 = __toESM(require("http"));
|
|
5373
5495
|
var import_zod = require("zod");
|
|
5374
5496
|
var historyRecordSchema = import_zod.z.object({
|
|
5375
5497
|
type: import_zod.z.string().optional(),
|
|
@@ -5381,7 +5503,7 @@ var historyRecordSchema = import_zod.z.object({
|
|
|
5381
5503
|
content: import_zod.z.union([import_zod.z.string(), import_zod.z.array(import_zod.z.unknown())]).optional()
|
|
5382
5504
|
}).passthrough().optional()
|
|
5383
5505
|
}).passthrough();
|
|
5384
|
-
var
|
|
5506
|
+
var API_BASE4 = process.env.CODEAM_API_URL ?? "https://codeagent-mobile-api.vercel.app";
|
|
5385
5507
|
function encodeCwd(cwd) {
|
|
5386
5508
|
return cwd.replace(/[\\/:]/g, "-");
|
|
5387
5509
|
}
|
|
@@ -5454,8 +5576,8 @@ function parseJsonl(filePath) {
|
|
|
5454
5576
|
function post(endpoint, body) {
|
|
5455
5577
|
return new Promise((resolve2) => {
|
|
5456
5578
|
const payload = JSON.stringify(body);
|
|
5457
|
-
const u2 = new URL(`${
|
|
5458
|
-
const transport = u2.protocol === "https:" ?
|
|
5579
|
+
const u2 = new URL(`${API_BASE4}${endpoint}`);
|
|
5580
|
+
const transport = u2.protocol === "https:" ? https4 : http4;
|
|
5459
5581
|
const req = transport.request(
|
|
5460
5582
|
{
|
|
5461
5583
|
hostname: u2.hostname,
|
|
@@ -5499,6 +5621,14 @@ var HistoryService = class {
|
|
|
5499
5621
|
_rateLimitReset = null;
|
|
5500
5622
|
_quotaPercent = null;
|
|
5501
5623
|
_quotaFetchedAt = 0;
|
|
5624
|
+
/**
|
|
5625
|
+
* Per-conversation marker of the last message uuid we successfully
|
|
5626
|
+
* uploaded to the backend. `uploadDelta()` reads the JSONL,
|
|
5627
|
+
* filters out everything up to and including this uuid, and
|
|
5628
|
+
* uploads only the tail. Resets per conversation so a session
|
|
5629
|
+
* resume re-uploads the full transcript on first call.
|
|
5630
|
+
*/
|
|
5631
|
+
lastUploadedUuid = /* @__PURE__ */ new Map();
|
|
5502
5632
|
/** Store rate limit reset info detected from Claude Code output */
|
|
5503
5633
|
setRateLimitReset(reset) {
|
|
5504
5634
|
this._rateLimitReset = reset;
|
|
@@ -5775,9 +5905,212 @@ var HistoryService = class {
|
|
|
5775
5905
|
throw new Error(`Failed to upload conversation batch ${i + 1}/${totalBatches} after all retries`);
|
|
5776
5906
|
}
|
|
5777
5907
|
}
|
|
5908
|
+
const last = messages[messages.length - 1];
|
|
5909
|
+
if (last) this.lastUploadedUuid.set(sessionId, last.id);
|
|
5910
|
+
}
|
|
5911
|
+
/**
|
|
5912
|
+
* Incremental upload — ships only the messages added since the last
|
|
5913
|
+
* `loadConversation` / `uploadDelta` call for this conversation.
|
|
5914
|
+
* Used by `onTurnComplete` after every turn so the backend's
|
|
5915
|
+
* conversation table stays fresh enough for the SSE consumers
|
|
5916
|
+
* (mobile + web dashboard) to fetch the canonical markdown via
|
|
5917
|
+
* `?last=N` and replace the streaming-from-PTY approximation —
|
|
5918
|
+
* which lacks the markdown ``` fences the parser needs to surface
|
|
5919
|
+
* the rich CodeBlock / DiffBlock / etc. components.
|
|
5920
|
+
*
|
|
5921
|
+
* Posts under `mode: 'append'` so the server merges by uuid
|
|
5922
|
+
* instead of replacing the full conversation. Idempotent — if
|
|
5923
|
+
* called twice in a row the second call sees zero new messages
|
|
5924
|
+
* and is a no-op.
|
|
5925
|
+
*
|
|
5926
|
+
* Returns the number of messages uploaded (0 means nothing new).
|
|
5927
|
+
*/
|
|
5928
|
+
async uploadDelta() {
|
|
5929
|
+
if (!this.currentConversationId) return 0;
|
|
5930
|
+
const sessionId = this.currentConversationId;
|
|
5931
|
+
const filePath = path8.join(this.projectDir, `${sessionId}.jsonl`);
|
|
5932
|
+
const messages = parseJsonl(filePath);
|
|
5933
|
+
if (messages.length === 0) return 0;
|
|
5934
|
+
const marker = this.lastUploadedUuid.get(sessionId);
|
|
5935
|
+
let newMessages = messages;
|
|
5936
|
+
if (marker) {
|
|
5937
|
+
const idx = messages.findIndex((m) => m.id === marker);
|
|
5938
|
+
if (idx >= 0) {
|
|
5939
|
+
newMessages = messages.slice(idx + 1);
|
|
5940
|
+
}
|
|
5941
|
+
}
|
|
5942
|
+
if (newMessages.length === 0) return 0;
|
|
5943
|
+
const body = {
|
|
5944
|
+
pluginId: this.pluginId,
|
|
5945
|
+
sessionId,
|
|
5946
|
+
messages: newMessages,
|
|
5947
|
+
mode: "append"
|
|
5948
|
+
};
|
|
5949
|
+
const ok = await post("/api/sessions/claude-conversation", body);
|
|
5950
|
+
if (ok) {
|
|
5951
|
+
const last = newMessages[newMessages.length - 1];
|
|
5952
|
+
this.lastUploadedUuid.set(sessionId, last.id);
|
|
5953
|
+
return newMessages.length;
|
|
5954
|
+
}
|
|
5955
|
+
return 0;
|
|
5778
5956
|
}
|
|
5779
5957
|
};
|
|
5780
5958
|
|
|
5959
|
+
// src/commands/start/quota-fetcher.ts
|
|
5960
|
+
var fs6 = __toESM(require("fs"));
|
|
5961
|
+
var os7 = __toESM(require("os"));
|
|
5962
|
+
var path9 = __toESM(require("path"));
|
|
5963
|
+
var import_child_process4 = require("child_process");
|
|
5964
|
+
var inProgress = false;
|
|
5965
|
+
var HELPER_SCRIPT = `import os,pty,sys,select,signal,struct,fcntl,termios,errno
|
|
5966
|
+
m,s=pty.openpty()
|
|
5967
|
+
try:
|
|
5968
|
+
fcntl.ioctl(s,termios.TIOCSWINSZ,struct.pack('HHHH',30,120,0,0))
|
|
5969
|
+
except Exception:pass
|
|
5970
|
+
pid=os.fork()
|
|
5971
|
+
if pid==0:
|
|
5972
|
+
os.close(m);os.setsid()
|
|
5973
|
+
try:fcntl.ioctl(s,termios.TIOCSCTTY,0)
|
|
5974
|
+
except Exception:pass
|
|
5975
|
+
for fd in[0,1,2]:os.dup2(s,fd)
|
|
5976
|
+
if s>2:os.close(s)
|
|
5977
|
+
os.execvp(sys.argv[1],sys.argv[1:])
|
|
5978
|
+
sys.exit(127)
|
|
5979
|
+
os.close(s)
|
|
5980
|
+
done=[False]
|
|
5981
|
+
def onchld(n,f):
|
|
5982
|
+
try:os.waitpid(pid,os.WNOHANG)
|
|
5983
|
+
except Exception:pass
|
|
5984
|
+
done[0]=True
|
|
5985
|
+
signal.signal(signal.SIGCHLD,onchld)
|
|
5986
|
+
i=sys.stdin.fileno();o=sys.stdout.fileno()
|
|
5987
|
+
while not done[0]:
|
|
5988
|
+
try:r,_,_=select.select([i,m],[],[],0.1)
|
|
5989
|
+
except OSError as e:
|
|
5990
|
+
if e.errno==errno.EINTR:continue
|
|
5991
|
+
break
|
|
5992
|
+
if i in r:
|
|
5993
|
+
try:
|
|
5994
|
+
d=os.read(i,4096)
|
|
5995
|
+
if d:os.write(m,d)
|
|
5996
|
+
else:break
|
|
5997
|
+
except OSError:break
|
|
5998
|
+
if m in r:
|
|
5999
|
+
try:
|
|
6000
|
+
d=os.read(m,4096)
|
|
6001
|
+
if d:os.write(o,d)
|
|
6002
|
+
except OSError:done[0]=True
|
|
6003
|
+
try:os.kill(pid,signal.SIGTERM)
|
|
6004
|
+
except Exception:pass
|
|
6005
|
+
try:
|
|
6006
|
+
_,st=os.waitpid(pid,0)
|
|
6007
|
+
sys.exit((st>>8)&0xFF)
|
|
6008
|
+
except Exception:sys.exit(0)
|
|
6009
|
+
`;
|
|
6010
|
+
function fetchQuotaUsage(historySvc) {
|
|
6011
|
+
if (inProgress) return;
|
|
6012
|
+
inProgress = true;
|
|
6013
|
+
const claudeCmd = findInPath("claude") ? "claude" : "claude-code";
|
|
6014
|
+
if (!claudeCmd) {
|
|
6015
|
+
inProgress = false;
|
|
6016
|
+
return;
|
|
6017
|
+
}
|
|
6018
|
+
const helperPath = path9.join(os7.tmpdir(), "codeam-quota-helper.py");
|
|
6019
|
+
fs6.writeFileSync(helperPath, HELPER_SCRIPT, { mode: 420 });
|
|
6020
|
+
const python = findInPath("python3") ?? findInPath("python");
|
|
6021
|
+
if (!python) {
|
|
6022
|
+
inProgress = false;
|
|
6023
|
+
return;
|
|
6024
|
+
}
|
|
6025
|
+
const proc = (0, import_child_process4.spawn)(python, [helperPath, claudeCmd, "--tools", ""], {
|
|
6026
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
6027
|
+
cwd: process.cwd(),
|
|
6028
|
+
env: { ...process.env, TERM: "dumb", COLUMNS: "120", LINES: "30" }
|
|
6029
|
+
});
|
|
6030
|
+
let output = "";
|
|
6031
|
+
proc.stdout?.on("data", (chunk) => {
|
|
6032
|
+
output += chunk.toString("utf8");
|
|
6033
|
+
});
|
|
6034
|
+
setTimeout(() => {
|
|
6035
|
+
proc.stdin?.write("/usage\r");
|
|
6036
|
+
setTimeout(() => {
|
|
6037
|
+
const clean = output.replace(/\x1B\[[^@-~]*[@-~]/g, "").replace(/[\x00-\x1F\x7F]/g, " ").replace(/\s+/g, " ");
|
|
6038
|
+
const weekMatch = clean.match(/(\d+)%\s*used/i) || clean.match(/(\d+)\s*%/);
|
|
6039
|
+
if (weekMatch) historySvc.setQuotaPercent(parseInt(weekMatch[1], 10));
|
|
6040
|
+
const resetMatch = clean.match(/resets\s+(.+?)(?:\s*\(|$)/im);
|
|
6041
|
+
if (resetMatch) historySvc.setRateLimitReset(resetMatch[1].trim());
|
|
6042
|
+
try {
|
|
6043
|
+
proc.kill();
|
|
6044
|
+
} catch {
|
|
6045
|
+
}
|
|
6046
|
+
try {
|
|
6047
|
+
fs6.unlinkSync(helperPath);
|
|
6048
|
+
} catch {
|
|
6049
|
+
}
|
|
6050
|
+
inProgress = false;
|
|
6051
|
+
}, 5e3);
|
|
6052
|
+
}, 8e3);
|
|
6053
|
+
proc.on("exit", () => {
|
|
6054
|
+
inProgress = false;
|
|
6055
|
+
});
|
|
6056
|
+
setTimeout(() => {
|
|
6057
|
+
try {
|
|
6058
|
+
proc.kill();
|
|
6059
|
+
} catch {
|
|
6060
|
+
}
|
|
6061
|
+
}, 2e4);
|
|
6062
|
+
}
|
|
6063
|
+
|
|
6064
|
+
// src/commands/start/keep-alive.ts
|
|
6065
|
+
var import_child_process5 = require("child_process");
|
|
6066
|
+
function buildKeepAlive(ctx) {
|
|
6067
|
+
let timer = null;
|
|
6068
|
+
async function setIdleTimeout(minutes) {
|
|
6069
|
+
if (!ctx.inCodespace || !ctx.codespaceName) return;
|
|
6070
|
+
await new Promise((resolve2) => {
|
|
6071
|
+
const proc = (0, import_child_process5.spawn)(
|
|
6072
|
+
"gh",
|
|
6073
|
+
[
|
|
6074
|
+
"api",
|
|
6075
|
+
"-X",
|
|
6076
|
+
"PATCH",
|
|
6077
|
+
`/user/codespaces/${ctx.codespaceName}`,
|
|
6078
|
+
"-F",
|
|
6079
|
+
`idle_timeout_minutes=${minutes}`
|
|
6080
|
+
],
|
|
6081
|
+
{ stdio: "ignore", detached: true }
|
|
6082
|
+
);
|
|
6083
|
+
proc.unref();
|
|
6084
|
+
proc.on("exit", () => resolve2());
|
|
6085
|
+
proc.on("error", () => resolve2());
|
|
6086
|
+
});
|
|
6087
|
+
}
|
|
6088
|
+
return {
|
|
6089
|
+
apply(enabled) {
|
|
6090
|
+
if (timer) {
|
|
6091
|
+
clearInterval(timer);
|
|
6092
|
+
timer = null;
|
|
6093
|
+
}
|
|
6094
|
+
if (!ctx.inCodespace || !ctx.codespaceName) return;
|
|
6095
|
+
if (!enabled) {
|
|
6096
|
+
void setIdleTimeout(30);
|
|
6097
|
+
return;
|
|
6098
|
+
}
|
|
6099
|
+
void setIdleTimeout(240);
|
|
6100
|
+
timer = setInterval(() => {
|
|
6101
|
+
void setIdleTimeout(240);
|
|
6102
|
+
}, 30 * 60 * 1e3);
|
|
6103
|
+
}
|
|
6104
|
+
};
|
|
6105
|
+
}
|
|
6106
|
+
|
|
6107
|
+
// src/commands/start/handlers.ts
|
|
6108
|
+
var fs9 = __toESM(require("fs"));
|
|
6109
|
+
var os8 = __toESM(require("os"));
|
|
6110
|
+
var path12 = __toESM(require("path"));
|
|
6111
|
+
var import_crypto = require("crypto");
|
|
6112
|
+
var import_child_process7 = require("child_process");
|
|
6113
|
+
|
|
5781
6114
|
// src/lib/payload.ts
|
|
5782
6115
|
var import_zod2 = require("zod");
|
|
5783
6116
|
var fileEntrySchema = import_zod2.z.object({
|
|
@@ -5813,8 +6146,8 @@ function parsePayload(schema, raw) {
|
|
|
5813
6146
|
}
|
|
5814
6147
|
|
|
5815
6148
|
// src/services/file-ops.service.ts
|
|
5816
|
-
var
|
|
5817
|
-
var
|
|
6149
|
+
var fs7 = __toESM(require("fs/promises"));
|
|
6150
|
+
var path10 = __toESM(require("path"));
|
|
5818
6151
|
var MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
5819
6152
|
var MAX_WALK_DEPTH = 6;
|
|
5820
6153
|
var MAX_VISITED_DIRS = 5e3;
|
|
@@ -5849,12 +6182,12 @@ var SUBDIR_IGNORE = /* @__PURE__ */ new Set([
|
|
|
5849
6182
|
"__pycache__"
|
|
5850
6183
|
]);
|
|
5851
6184
|
function isUnder(parent, candidate) {
|
|
5852
|
-
const rel =
|
|
5853
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
6185
|
+
const rel = path10.relative(parent, candidate);
|
|
6186
|
+
return rel === "" || !rel.startsWith("..") && !path10.isAbsolute(rel);
|
|
5854
6187
|
}
|
|
5855
6188
|
async function isExistingFile(absPath) {
|
|
5856
6189
|
try {
|
|
5857
|
-
const stat3 = await
|
|
6190
|
+
const stat3 = await fs7.stat(absPath);
|
|
5858
6191
|
return stat3.isFile();
|
|
5859
6192
|
} catch {
|
|
5860
6193
|
return false;
|
|
@@ -5867,13 +6200,13 @@ async function walkForSuffix(dir, needleVariants, depth, ctx) {
|
|
|
5867
6200
|
ctx.visited++;
|
|
5868
6201
|
let entries = [];
|
|
5869
6202
|
try {
|
|
5870
|
-
entries = await
|
|
6203
|
+
entries = await fs7.readdir(dir, { withFileTypes: true });
|
|
5871
6204
|
} catch {
|
|
5872
6205
|
return;
|
|
5873
6206
|
}
|
|
5874
6207
|
for (const e of entries) {
|
|
5875
6208
|
if (!e.isFile()) continue;
|
|
5876
|
-
const full =
|
|
6209
|
+
const full = path10.join(dir, e.name);
|
|
5877
6210
|
if (needleVariants.some((needle) => full.endsWith(needle))) {
|
|
5878
6211
|
ctx.matches.push(full);
|
|
5879
6212
|
if (ctx.matches.length >= ctx.cap) return;
|
|
@@ -5883,21 +6216,21 @@ async function walkForSuffix(dir, needleVariants, depth, ctx) {
|
|
|
5883
6216
|
if (!e.isDirectory()) continue;
|
|
5884
6217
|
if (SUBDIR_IGNORE.has(e.name)) continue;
|
|
5885
6218
|
if (e.name.startsWith(".") && SUBDIR_IGNORE.has(e.name)) continue;
|
|
5886
|
-
await walkForSuffix(
|
|
6219
|
+
await walkForSuffix(path10.join(dir, e.name), needleVariants, depth + 1, ctx);
|
|
5887
6220
|
if (ctx.matches.length >= ctx.cap) return;
|
|
5888
6221
|
}
|
|
5889
6222
|
}
|
|
5890
6223
|
async function findFile(rawPath) {
|
|
5891
6224
|
const cwd = process.cwd();
|
|
5892
|
-
if (
|
|
5893
|
-
const abs =
|
|
6225
|
+
if (path10.isAbsolute(rawPath)) {
|
|
6226
|
+
const abs = path10.normalize(rawPath);
|
|
5894
6227
|
if (isUnder(cwd, abs) && await isExistingFile(abs)) return abs;
|
|
5895
6228
|
}
|
|
5896
|
-
const direct =
|
|
6229
|
+
const direct = path10.resolve(cwd, rawPath);
|
|
5897
6230
|
if (isUnder(cwd, direct) && await isExistingFile(direct)) return direct;
|
|
5898
|
-
const normalized =
|
|
6231
|
+
const normalized = path10.normalize(rawPath).replace(/^[./\\]+/, "");
|
|
5899
6232
|
const needles = [
|
|
5900
|
-
`${
|
|
6233
|
+
`${path10.sep}${normalized}`,
|
|
5901
6234
|
`/${normalized}`
|
|
5902
6235
|
].filter((v, i, a) => a.indexOf(v) === i);
|
|
5903
6236
|
const ctx = { visited: 0, matches: [], cap: 16 };
|
|
@@ -5911,7 +6244,7 @@ async function findWriteTarget(rawPath) {
|
|
|
5911
6244
|
const found = await findFile(rawPath);
|
|
5912
6245
|
if (found) return found;
|
|
5913
6246
|
const cwd = process.cwd();
|
|
5914
|
-
const fallback =
|
|
6247
|
+
const fallback = path10.isAbsolute(rawPath) ? path10.normalize(rawPath) : path10.resolve(cwd, rawPath);
|
|
5915
6248
|
if (!isUnder(cwd, fallback)) return null;
|
|
5916
6249
|
return fallback;
|
|
5917
6250
|
}
|
|
@@ -5928,11 +6261,11 @@ async function readProjectFile(rawPath) {
|
|
|
5928
6261
|
if (!abs) {
|
|
5929
6262
|
return { error: `File not found in the project tree: ${rawPath}` };
|
|
5930
6263
|
}
|
|
5931
|
-
const stat3 = await
|
|
6264
|
+
const stat3 = await fs7.stat(abs);
|
|
5932
6265
|
if (stat3.size > MAX_FILE_BYTES) {
|
|
5933
6266
|
return { error: `File too large (${(stat3.size / 1024 / 1024).toFixed(1)} MB > ${MAX_FILE_BYTES / 1024 / 1024} MB).` };
|
|
5934
6267
|
}
|
|
5935
|
-
const buf = await
|
|
6268
|
+
const buf = await fs7.readFile(abs);
|
|
5936
6269
|
if (looksBinary(buf)) {
|
|
5937
6270
|
return { error: "Binary file \u2014 refusing to open in a code editor." };
|
|
5938
6271
|
}
|
|
@@ -5951,8 +6284,8 @@ async function writeProjectFile(rawPath, content) {
|
|
|
5951
6284
|
if (Buffer.byteLength(content, "utf-8") > MAX_FILE_BYTES) {
|
|
5952
6285
|
return { error: "Content too large." };
|
|
5953
6286
|
}
|
|
5954
|
-
await
|
|
5955
|
-
await
|
|
6287
|
+
await fs7.mkdir(path10.dirname(abs), { recursive: true });
|
|
6288
|
+
await fs7.writeFile(abs, content, "utf-8");
|
|
5956
6289
|
return { ok: true };
|
|
5957
6290
|
} catch (e) {
|
|
5958
6291
|
const msg = e instanceof Error ? e.message : "Write failed";
|
|
@@ -5961,11 +6294,11 @@ async function writeProjectFile(rawPath, content) {
|
|
|
5961
6294
|
}
|
|
5962
6295
|
|
|
5963
6296
|
// src/services/project-ops.service.ts
|
|
5964
|
-
var
|
|
6297
|
+
var import_child_process6 = require("child_process");
|
|
5965
6298
|
var import_util = require("util");
|
|
5966
|
-
var
|
|
5967
|
-
var
|
|
5968
|
-
var execFileP = (0, import_util.promisify)(
|
|
6299
|
+
var fs8 = __toESM(require("fs/promises"));
|
|
6300
|
+
var path11 = __toESM(require("path"));
|
|
6301
|
+
var execFileP = (0, import_util.promisify)(import_child_process6.execFile);
|
|
5969
6302
|
var PROJECT_IGNORE = /* @__PURE__ */ new Set([
|
|
5970
6303
|
"node_modules",
|
|
5971
6304
|
".git",
|
|
@@ -6012,7 +6345,7 @@ async function listProjectFiles(opts = {}) {
|
|
|
6012
6345
|
}
|
|
6013
6346
|
let entries = [];
|
|
6014
6347
|
try {
|
|
6015
|
-
entries = await
|
|
6348
|
+
entries = await fs8.readdir(dir, { withFileTypes: true });
|
|
6016
6349
|
} catch {
|
|
6017
6350
|
return;
|
|
6018
6351
|
}
|
|
@@ -6022,18 +6355,18 @@ async function listProjectFiles(opts = {}) {
|
|
|
6022
6355
|
return;
|
|
6023
6356
|
}
|
|
6024
6357
|
if (PROJECT_IGNORE.has(e.name)) continue;
|
|
6025
|
-
const full =
|
|
6358
|
+
const full = path11.join(dir, e.name);
|
|
6026
6359
|
if (e.isDirectory()) {
|
|
6027
6360
|
if (depth >= 12) continue;
|
|
6028
6361
|
await walk(full, depth + 1);
|
|
6029
6362
|
} else if (e.isFile()) {
|
|
6030
|
-
const rel =
|
|
6363
|
+
const rel = path11.relative(root, full);
|
|
6031
6364
|
if (q2 && !rel.toLowerCase().includes(q2) && !e.name.toLowerCase().includes(q2)) {
|
|
6032
6365
|
continue;
|
|
6033
6366
|
}
|
|
6034
6367
|
let size = 0;
|
|
6035
6368
|
try {
|
|
6036
|
-
const st3 = await
|
|
6369
|
+
const st3 = await fs8.stat(full);
|
|
6037
6370
|
size = st3.size;
|
|
6038
6371
|
} catch {
|
|
6039
6372
|
}
|
|
@@ -6135,8 +6468,8 @@ async function gitStatus(cwd) {
|
|
|
6135
6468
|
let hasMergeInProgress = false;
|
|
6136
6469
|
try {
|
|
6137
6470
|
const gitDir = (await git(["rev-parse", "--git-dir"], root)).stdout.trim();
|
|
6138
|
-
const mergeHead =
|
|
6139
|
-
await
|
|
6471
|
+
const mergeHead = path11.isAbsolute(gitDir) ? path11.join(gitDir, "MERGE_HEAD") : path11.join(root, gitDir, "MERGE_HEAD");
|
|
6472
|
+
await fs8.access(mergeHead);
|
|
6140
6473
|
hasMergeInProgress = true;
|
|
6141
6474
|
} catch {
|
|
6142
6475
|
}
|
|
@@ -6209,15 +6542,261 @@ async function gitResolve(file, side, cwd) {
|
|
|
6209
6542
|
return { ok: true };
|
|
6210
6543
|
}
|
|
6211
6544
|
|
|
6545
|
+
// src/commands/start/handlers.ts
|
|
6546
|
+
function saveFilesTemp(files) {
|
|
6547
|
+
return files.filter(({ base64 }) => base64 && base64.length > 0).map(({ filename, base64 }) => {
|
|
6548
|
+
const safeName = filename.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80);
|
|
6549
|
+
const tmpPath = path12.join(os8.tmpdir(), `codeam-${(0, import_crypto.randomUUID)()}-${safeName}`);
|
|
6550
|
+
fs9.writeFileSync(tmpPath, Buffer.from(base64, "base64"));
|
|
6551
|
+
return tmpPath;
|
|
6552
|
+
});
|
|
6553
|
+
}
|
|
6554
|
+
function dispatchPrompt(ctx, prompt) {
|
|
6555
|
+
ctx.outputSvc.newTurn();
|
|
6556
|
+
ctx.claude.sendCommand(prompt);
|
|
6557
|
+
}
|
|
6558
|
+
var startTask = (ctx, _cmd, parsed) => {
|
|
6559
|
+
const { prompt, files } = parsed;
|
|
6560
|
+
const effectivePrompt = prompt ?? "";
|
|
6561
|
+
if (files && files.length > 0) {
|
|
6562
|
+
const paths = saveFilesTemp(files);
|
|
6563
|
+
const atRefs = paths.map((p2) => `@${p2}`).join(" ");
|
|
6564
|
+
ctx.outputSvc.newTurn();
|
|
6565
|
+
ctx.claude.sendCommand(`${atRefs} ${effectivePrompt}`.trim());
|
|
6566
|
+
setTimeout(() => {
|
|
6567
|
+
for (const p2 of paths) {
|
|
6568
|
+
try {
|
|
6569
|
+
fs9.unlinkSync(p2);
|
|
6570
|
+
} catch {
|
|
6571
|
+
}
|
|
6572
|
+
}
|
|
6573
|
+
}, 12e4);
|
|
6574
|
+
} else if (effectivePrompt) {
|
|
6575
|
+
dispatchPrompt(ctx, effectivePrompt);
|
|
6576
|
+
}
|
|
6577
|
+
};
|
|
6578
|
+
var provideInput = (ctx, _cmd, parsed) => {
|
|
6579
|
+
if (parsed.input) dispatchPrompt(ctx, parsed.input);
|
|
6580
|
+
};
|
|
6581
|
+
var selectOption = (ctx, _cmd, parsed) => {
|
|
6582
|
+
const index = parsed.index ?? 0;
|
|
6583
|
+
const from = parsed.from ?? 0;
|
|
6584
|
+
ctx.outputSvc.newTurn();
|
|
6585
|
+
ctx.claude.selectOption(index, from);
|
|
6586
|
+
};
|
|
6587
|
+
var escapeKey = (ctx) => {
|
|
6588
|
+
ctx.outputSvc.newTurn();
|
|
6589
|
+
ctx.claude.sendEscape();
|
|
6590
|
+
};
|
|
6591
|
+
var stopTask = (ctx) => {
|
|
6592
|
+
ctx.claude.interrupt();
|
|
6593
|
+
};
|
|
6594
|
+
var resumeSession = async (ctx, _cmd, parsed) => {
|
|
6595
|
+
const { id, auto } = parsed;
|
|
6596
|
+
if (!id) return;
|
|
6597
|
+
ctx.historySvc.setCurrentConversationId(id);
|
|
6598
|
+
await ctx.historySvc.loadConversation(id);
|
|
6599
|
+
await ctx.outputSvc.newTurnResume(id);
|
|
6600
|
+
ctx.claude.restart(id, auto ?? false);
|
|
6601
|
+
};
|
|
6602
|
+
var getContext = async (ctx, cmd) => {
|
|
6603
|
+
const usage = ctx.historySvc.getCurrentUsage();
|
|
6604
|
+
const monthlyCost = ctx.historySvc.getMonthlyEstimatedCost();
|
|
6605
|
+
const rateLimitReset = ctx.historySvc.getRateLimitReset();
|
|
6606
|
+
const quotaPercent = ctx.historySvc.getQuotaPercent();
|
|
6607
|
+
const base = usage ? { ...usage, monthlyCost } : { used: 0, total: 2e5, percent: 0, model: null, outputTokens: 0, cacheReadTokens: 0, monthlyCost, error: "No usage data found" };
|
|
6608
|
+
const result = {
|
|
6609
|
+
...base,
|
|
6610
|
+
...rateLimitReset ? { rateLimitReset } : {},
|
|
6611
|
+
...quotaPercent !== null ? { quotaPercent } : {}
|
|
6612
|
+
};
|
|
6613
|
+
await ctx.relay.sendResult(cmd.id, "completed", result);
|
|
6614
|
+
};
|
|
6615
|
+
var getConversation = async (ctx, cmd) => {
|
|
6616
|
+
const currentId = ctx.historySvc.getCurrentConversationId();
|
|
6617
|
+
if (!currentId) {
|
|
6618
|
+
await ctx.relay.sendResult(cmd.id, "completed", { conversationId: null });
|
|
6619
|
+
return;
|
|
6620
|
+
}
|
|
6621
|
+
try {
|
|
6622
|
+
await ctx.historySvc.loadConversation(currentId);
|
|
6623
|
+
await ctx.relay.sendResult(cmd.id, "completed", { conversationId: currentId });
|
|
6624
|
+
} catch {
|
|
6625
|
+
await ctx.relay.sendResult(cmd.id, "failed", {});
|
|
6626
|
+
}
|
|
6627
|
+
};
|
|
6628
|
+
var listModels = async (ctx, cmd) => {
|
|
6629
|
+
const models = [
|
|
6630
|
+
{ id: "claude-opus-4-7", label: "Claude Opus 4.7", description: "Most capable", family: "claude", vendor: "anthropic", isDefault: false },
|
|
6631
|
+
{ id: "claude-opus-4-6", label: "Claude Opus 4.6", description: "Top tier", family: "claude", vendor: "anthropic", isDefault: false },
|
|
6632
|
+
{ id: "claude-sonnet-4-6", label: "Claude Sonnet 4.6", description: "Balanced", family: "claude", vendor: "anthropic", isDefault: true },
|
|
6633
|
+
{ id: "claude-haiku-4-5-20251001", label: "Claude Haiku 4.5", description: "Fastest", family: "claude", vendor: "anthropic", isDefault: false }
|
|
6634
|
+
];
|
|
6635
|
+
await ctx.relay.sendResult(cmd.id, "completed", { models });
|
|
6636
|
+
};
|
|
6637
|
+
var setKeepAlive = async (ctx, cmd) => {
|
|
6638
|
+
const enabled = !!cmd.payload.enabled;
|
|
6639
|
+
ctx.setKeepAlive(enabled);
|
|
6640
|
+
try {
|
|
6641
|
+
await ctx.relay.sendResult(
|
|
6642
|
+
cmd.id,
|
|
6643
|
+
"success",
|
|
6644
|
+
{
|
|
6645
|
+
enabled,
|
|
6646
|
+
applied: enabled && ctx.keepAliveCtx.inCodespace,
|
|
6647
|
+
runtime: ctx.keepAliveCtx.inCodespace ? "github-codespaces" : "local"
|
|
6648
|
+
}
|
|
6649
|
+
);
|
|
6650
|
+
} catch {
|
|
6651
|
+
}
|
|
6652
|
+
};
|
|
6653
|
+
var sessionTerminated = (ctx) => {
|
|
6654
|
+
showInfo("Session was deleted from the app \u2014 exiting.");
|
|
6655
|
+
try {
|
|
6656
|
+
ctx.claude.kill();
|
|
6657
|
+
} catch {
|
|
6658
|
+
}
|
|
6659
|
+
try {
|
|
6660
|
+
const proc = (0, import_child_process7.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
|
|
6661
|
+
detached: true,
|
|
6662
|
+
stdio: "ignore"
|
|
6663
|
+
});
|
|
6664
|
+
proc.unref();
|
|
6665
|
+
} catch {
|
|
6666
|
+
}
|
|
6667
|
+
ctx.outputSvc.dispose();
|
|
6668
|
+
ctx.relay.stop();
|
|
6669
|
+
process.exit(0);
|
|
6670
|
+
};
|
|
6671
|
+
var shutdownSession = async (ctx, cmd) => {
|
|
6672
|
+
try {
|
|
6673
|
+
await ctx.relay.sendResult(cmd.id, "success", { ok: true });
|
|
6674
|
+
} catch {
|
|
6675
|
+
}
|
|
6676
|
+
try {
|
|
6677
|
+
ctx.claude.kill();
|
|
6678
|
+
} catch {
|
|
6679
|
+
}
|
|
6680
|
+
if (ctx.keepAliveCtx.inCodespace && ctx.keepAliveCtx.codespaceName) {
|
|
6681
|
+
try {
|
|
6682
|
+
const stopProc = (0, import_child_process7.spawn)(
|
|
6683
|
+
"bash",
|
|
6684
|
+
["-lc", `sleep 1; gh codespace stop -c ${JSON.stringify(ctx.keepAliveCtx.codespaceName)} >/dev/null 2>&1 || true`],
|
|
6685
|
+
{ detached: true, stdio: "ignore" }
|
|
6686
|
+
);
|
|
6687
|
+
stopProc.unref();
|
|
6688
|
+
} catch {
|
|
6689
|
+
}
|
|
6690
|
+
}
|
|
6691
|
+
try {
|
|
6692
|
+
const proc = (0, import_child_process7.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
|
|
6693
|
+
detached: true,
|
|
6694
|
+
stdio: "ignore"
|
|
6695
|
+
});
|
|
6696
|
+
proc.unref();
|
|
6697
|
+
} catch {
|
|
6698
|
+
}
|
|
6699
|
+
ctx.outputSvc.dispose();
|
|
6700
|
+
ctx.relay.stop();
|
|
6701
|
+
process.exit(0);
|
|
6702
|
+
};
|
|
6703
|
+
var readFile2 = async (ctx, cmd, parsed) => {
|
|
6704
|
+
if (!parsed.path) {
|
|
6705
|
+
await ctx.relay.sendResult(cmd.id, "failed", { error: "Missing path" });
|
|
6706
|
+
return;
|
|
6707
|
+
}
|
|
6708
|
+
const result = await readProjectFile(parsed.path);
|
|
6709
|
+
await ctx.relay.sendResult(cmd.id, "completed", result);
|
|
6710
|
+
};
|
|
6711
|
+
var writeFile2 = async (ctx, cmd, parsed) => {
|
|
6712
|
+
if (!parsed.path || typeof parsed.content !== "string") {
|
|
6713
|
+
await ctx.relay.sendResult(cmd.id, "failed", { error: "Missing path or content" });
|
|
6714
|
+
return;
|
|
6715
|
+
}
|
|
6716
|
+
const result = await writeProjectFile(parsed.path, parsed.content);
|
|
6717
|
+
await ctx.relay.sendResult(cmd.id, "completed", result);
|
|
6718
|
+
};
|
|
6719
|
+
var listFiles = async (ctx, cmd, parsed) => {
|
|
6720
|
+
const result = await listProjectFiles({ query: parsed.query });
|
|
6721
|
+
await ctx.relay.sendResult(cmd.id, "completed", result);
|
|
6722
|
+
};
|
|
6723
|
+
var gitStatusH = async (ctx, cmd) => {
|
|
6724
|
+
const result = await gitStatus();
|
|
6725
|
+
await ctx.relay.sendResult(cmd.id, "completed", result);
|
|
6726
|
+
};
|
|
6727
|
+
var gitDiffH = async (ctx, cmd, parsed) => {
|
|
6728
|
+
const result = await gitDiff(parsed.path ?? null);
|
|
6729
|
+
await ctx.relay.sendResult(cmd.id, "completed", result);
|
|
6730
|
+
};
|
|
6731
|
+
var gitDiffStagedH = async (ctx, cmd, parsed) => {
|
|
6732
|
+
const result = await gitDiffStaged(parsed.path ?? null);
|
|
6733
|
+
await ctx.relay.sendResult(cmd.id, "completed", result);
|
|
6734
|
+
};
|
|
6735
|
+
var gitLogH = async (ctx, cmd, parsed) => {
|
|
6736
|
+
const result = await gitLog(parsed.limit ?? 30);
|
|
6737
|
+
await ctx.relay.sendResult(cmd.id, "completed", result);
|
|
6738
|
+
};
|
|
6739
|
+
var gitCommitH = async (ctx, cmd, parsed) => {
|
|
6740
|
+
if (!parsed.message) {
|
|
6741
|
+
await ctx.relay.sendResult(cmd.id, "failed", { error: "Missing message" });
|
|
6742
|
+
return;
|
|
6743
|
+
}
|
|
6744
|
+
const result = await gitCommit(parsed.message, parsed.paths);
|
|
6745
|
+
await ctx.relay.sendResult(cmd.id, "completed", result);
|
|
6746
|
+
};
|
|
6747
|
+
var gitPushH = async (ctx, cmd) => {
|
|
6748
|
+
const result = await gitPush();
|
|
6749
|
+
await ctx.relay.sendResult(cmd.id, "completed", result);
|
|
6750
|
+
};
|
|
6751
|
+
var gitPullH = async (ctx, cmd) => {
|
|
6752
|
+
const result = await gitPull();
|
|
6753
|
+
await ctx.relay.sendResult(cmd.id, "completed", result);
|
|
6754
|
+
};
|
|
6755
|
+
var gitResolveH = async (ctx, cmd, parsed) => {
|
|
6756
|
+
if (!parsed.path || !parsed.side) {
|
|
6757
|
+
await ctx.relay.sendResult(cmd.id, "failed", { error: "Missing path or side" });
|
|
6758
|
+
return;
|
|
6759
|
+
}
|
|
6760
|
+
const result = await gitResolve(parsed.path, parsed.side);
|
|
6761
|
+
await ctx.relay.sendResult(cmd.id, "completed", result);
|
|
6762
|
+
};
|
|
6763
|
+
var handlers = {
|
|
6764
|
+
start_task: startTask,
|
|
6765
|
+
provide_input: provideInput,
|
|
6766
|
+
select_option: selectOption,
|
|
6767
|
+
escape_key: escapeKey,
|
|
6768
|
+
stop_task: stopTask,
|
|
6769
|
+
resume_session: resumeSession,
|
|
6770
|
+
get_context: getContext,
|
|
6771
|
+
get_conversation: getConversation,
|
|
6772
|
+
list_models: listModels,
|
|
6773
|
+
set_keep_alive: setKeepAlive,
|
|
6774
|
+
session_terminated: sessionTerminated,
|
|
6775
|
+
shutdown_session: shutdownSession,
|
|
6776
|
+
read_file: readFile2,
|
|
6777
|
+
write_file: writeFile2,
|
|
6778
|
+
list_files: listFiles,
|
|
6779
|
+
git_status: gitStatusH,
|
|
6780
|
+
git_diff: gitDiffH,
|
|
6781
|
+
git_diff_staged: gitDiffStagedH,
|
|
6782
|
+
git_log: gitLogH,
|
|
6783
|
+
git_commit: gitCommitH,
|
|
6784
|
+
git_push: gitPushH,
|
|
6785
|
+
git_pull: gitPullH,
|
|
6786
|
+
git_resolve: gitResolveH
|
|
6787
|
+
};
|
|
6788
|
+
async function dispatchCommand(ctx, cmd) {
|
|
6789
|
+
const parsed = parsePayload(startCommandSchema, cmd.payload);
|
|
6790
|
+
if (!parsed) {
|
|
6791
|
+
showInfo(`Ignoring malformed ${cmd.type} payload.`);
|
|
6792
|
+
return;
|
|
6793
|
+
}
|
|
6794
|
+
const handler = handlers[cmd.type];
|
|
6795
|
+
if (!handler) return;
|
|
6796
|
+
await handler(ctx, cmd, parsed);
|
|
6797
|
+
}
|
|
6798
|
+
|
|
6212
6799
|
// src/commands/start.ts
|
|
6213
|
-
function saveFilesTemp(files) {
|
|
6214
|
-
return files.filter(({ base64 }) => base64 && base64.length > 0).map(({ filename, base64 }) => {
|
|
6215
|
-
const safeName = filename.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80);
|
|
6216
|
-
const tmpPath = path11.join(os7.tmpdir(), `codeam-${(0, import_crypto.randomUUID)()}-${safeName}`);
|
|
6217
|
-
fs8.writeFileSync(tmpPath, Buffer.from(base64, "base64"));
|
|
6218
|
-
return tmpPath;
|
|
6219
|
-
});
|
|
6220
|
-
}
|
|
6221
6800
|
async function start() {
|
|
6222
6801
|
showIntro();
|
|
6223
6802
|
const session = getActiveSession();
|
|
@@ -6231,430 +6810,32 @@ async function start() {
|
|
|
6231
6810
|
showInfo(`${session.userName} \xB7 ${import_picocolors2.default.cyan(session.plan)}`);
|
|
6232
6811
|
showInfo("Launching Claude Code...\n");
|
|
6233
6812
|
const cwd = process.cwd();
|
|
6234
|
-
const ws = new WebSocketService(session.id, pluginId);
|
|
6235
6813
|
const historySvc = new HistoryService(pluginId, cwd);
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
fcntl.ioctl(s,termios.TIOCSWINSZ,struct.pack('HHHH',30,120,0,0))
|
|
6249
|
-
except Exception:pass
|
|
6250
|
-
pid=os.fork()
|
|
6251
|
-
if pid==0:
|
|
6252
|
-
os.close(m);os.setsid()
|
|
6253
|
-
try:fcntl.ioctl(s,termios.TIOCSCTTY,0)
|
|
6254
|
-
except Exception:pass
|
|
6255
|
-
for fd in[0,1,2]:os.dup2(s,fd)
|
|
6256
|
-
if s>2:os.close(s)
|
|
6257
|
-
os.execvp(sys.argv[1],sys.argv[1:])
|
|
6258
|
-
sys.exit(127)
|
|
6259
|
-
os.close(s)
|
|
6260
|
-
done=[False]
|
|
6261
|
-
def onchld(n,f):
|
|
6262
|
-
try:os.waitpid(pid,os.WNOHANG)
|
|
6263
|
-
except Exception:pass
|
|
6264
|
-
done[0]=True
|
|
6265
|
-
signal.signal(signal.SIGCHLD,onchld)
|
|
6266
|
-
i=sys.stdin.fileno();o=sys.stdout.fileno()
|
|
6267
|
-
while not done[0]:
|
|
6268
|
-
try:r,_,_=select.select([i,m],[],[],0.1)
|
|
6269
|
-
except OSError as e:
|
|
6270
|
-
if e.errno==errno.EINTR:continue
|
|
6271
|
-
break
|
|
6272
|
-
if i in r:
|
|
6273
|
-
try:
|
|
6274
|
-
d=os.read(i,4096)
|
|
6275
|
-
if d:os.write(m,d)
|
|
6276
|
-
else:break
|
|
6277
|
-
except OSError:break
|
|
6278
|
-
if m in r:
|
|
6279
|
-
try:
|
|
6280
|
-
d=os.read(m,4096)
|
|
6281
|
-
if d:os.write(o,d)
|
|
6282
|
-
except OSError:done[0]=True
|
|
6283
|
-
try:os.kill(pid,signal.SIGTERM)
|
|
6284
|
-
except Exception:pass
|
|
6285
|
-
try:
|
|
6286
|
-
_,st=os.waitpid(pid,0)
|
|
6287
|
-
sys.exit((st>>8)&0xFF)
|
|
6288
|
-
except Exception:sys.exit(0)
|
|
6289
|
-
`;
|
|
6290
|
-
const helperPath = path11.join(os7.tmpdir(), "codeam-quota-helper.py");
|
|
6291
|
-
fs8.writeFileSync(helperPath, helperScript, { mode: 420 });
|
|
6292
|
-
const python = findInPath("python3") ?? findInPath("python");
|
|
6293
|
-
if (!python) {
|
|
6294
|
-
quotaFetchInProgress = false;
|
|
6295
|
-
return;
|
|
6296
|
-
}
|
|
6297
|
-
const proc = (0, import_child_process5.spawn)(python, [helperPath, claudeCmd, "--tools", ""], {
|
|
6298
|
-
stdio: ["pipe", "pipe", "ignore"],
|
|
6299
|
-
cwd: process.cwd(),
|
|
6300
|
-
env: { ...process.env, TERM: "dumb", COLUMNS: "120", LINES: "30" }
|
|
6301
|
-
});
|
|
6302
|
-
let output = "";
|
|
6303
|
-
proc.stdout?.on("data", (chunk) => {
|
|
6304
|
-
output += chunk.toString("utf8");
|
|
6305
|
-
});
|
|
6306
|
-
setTimeout(() => {
|
|
6307
|
-
proc.stdin?.write("/usage\r");
|
|
6814
|
+
const keepAliveCtx = {
|
|
6815
|
+
inCodespace: process.env.CODESPACES === "true",
|
|
6816
|
+
codespaceName: process.env.CODESPACE_NAME
|
|
6817
|
+
};
|
|
6818
|
+
const { apply: setKeepAlive2 } = buildKeepAlive(keepAliveCtx);
|
|
6819
|
+
const outputSvc = new OutputService(
|
|
6820
|
+
session.id,
|
|
6821
|
+
pluginId,
|
|
6822
|
+
(conversationId) => historySvc.setCurrentConversationId(conversationId),
|
|
6823
|
+
(reset) => historySvc.setRateLimitReset(reset),
|
|
6824
|
+
() => {
|
|
6825
|
+
if (historySvc.isQuotaStale()) fetchQuotaUsage(historySvc);
|
|
6308
6826
|
setTimeout(() => {
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
historySvc.setQuotaPercent(parseInt(weekMatch[1], 10));
|
|
6313
|
-
}
|
|
6314
|
-
const resetMatch = clean.match(/resets\s+(.+?)(?:\s*\(|$)/im);
|
|
6315
|
-
if (resetMatch) {
|
|
6316
|
-
historySvc.setRateLimitReset(resetMatch[1].trim());
|
|
6317
|
-
}
|
|
6318
|
-
try {
|
|
6319
|
-
proc.kill();
|
|
6320
|
-
} catch {
|
|
6321
|
-
}
|
|
6322
|
-
try {
|
|
6323
|
-
fs8.unlinkSync(helperPath);
|
|
6324
|
-
} catch {
|
|
6325
|
-
}
|
|
6326
|
-
quotaFetchInProgress = false;
|
|
6327
|
-
}, 5e3);
|
|
6328
|
-
}, 8e3);
|
|
6329
|
-
proc.on("exit", () => {
|
|
6330
|
-
quotaFetchInProgress = false;
|
|
6331
|
-
});
|
|
6332
|
-
setTimeout(() => {
|
|
6333
|
-
try {
|
|
6334
|
-
proc.kill();
|
|
6335
|
-
} catch {
|
|
6336
|
-
}
|
|
6337
|
-
}, 2e4);
|
|
6338
|
-
}
|
|
6339
|
-
const outputSvc = new OutputService(session.id, pluginId, (conversationId) => {
|
|
6340
|
-
historySvc.setCurrentConversationId(conversationId);
|
|
6341
|
-
}, (reset) => {
|
|
6342
|
-
historySvc.setRateLimitReset(reset);
|
|
6343
|
-
}, () => {
|
|
6344
|
-
if (historySvc.isQuotaStale()) {
|
|
6345
|
-
fetchQuotaUsage();
|
|
6346
|
-
}
|
|
6347
|
-
}, () => {
|
|
6348
|
-
const prevCount = historySvc.getCurrentMessageCount();
|
|
6349
|
-
historySvc.waitForNewUserMessage(prevCount).then((userText) => outputSvc.startTerminalTurn(userText ?? void 0)).catch(() => outputSvc.startTerminalTurn(void 0));
|
|
6350
|
-
}, session.pluginAuthToken);
|
|
6351
|
-
function sendPrompt(prompt) {
|
|
6352
|
-
outputSvc.newTurn();
|
|
6353
|
-
claude.sendCommand(prompt);
|
|
6354
|
-
}
|
|
6355
|
-
const relay = new CommandRelayService(pluginId, async (cmd) => {
|
|
6356
|
-
const parsed = parsePayload(startCommandSchema, cmd.payload);
|
|
6357
|
-
if (!parsed) {
|
|
6358
|
-
showInfo(`Ignoring malformed ${cmd.type} payload.`);
|
|
6359
|
-
return;
|
|
6360
|
-
}
|
|
6361
|
-
switch (cmd.type) {
|
|
6362
|
-
case "start_task": {
|
|
6363
|
-
const { prompt, files } = parsed;
|
|
6364
|
-
const effectivePrompt = prompt ?? "";
|
|
6365
|
-
if (files && files.length > 0) {
|
|
6366
|
-
const paths = saveFilesTemp(files);
|
|
6367
|
-
const atRefs = paths.map((p2) => `@${p2}`).join(" ");
|
|
6368
|
-
outputSvc.newTurn();
|
|
6369
|
-
claude.sendCommand(`${atRefs} ${effectivePrompt}`.trim());
|
|
6370
|
-
setTimeout(() => {
|
|
6371
|
-
for (const p2 of paths) {
|
|
6372
|
-
try {
|
|
6373
|
-
fs8.unlinkSync(p2);
|
|
6374
|
-
} catch {
|
|
6375
|
-
}
|
|
6376
|
-
}
|
|
6377
|
-
}, 12e4);
|
|
6378
|
-
} else if (effectivePrompt) {
|
|
6379
|
-
sendPrompt(effectivePrompt);
|
|
6380
|
-
}
|
|
6381
|
-
break;
|
|
6382
|
-
}
|
|
6383
|
-
case "provide_input": {
|
|
6384
|
-
const { input } = parsed;
|
|
6385
|
-
if (input) sendPrompt(input);
|
|
6386
|
-
break;
|
|
6387
|
-
}
|
|
6388
|
-
case "select_option": {
|
|
6389
|
-
const index = parsed.index ?? 0;
|
|
6390
|
-
const from = parsed.from ?? 0;
|
|
6391
|
-
outputSvc.newTurn();
|
|
6392
|
-
claude.selectOption(index, from);
|
|
6393
|
-
break;
|
|
6394
|
-
}
|
|
6395
|
-
case "escape_key":
|
|
6396
|
-
outputSvc.newTurn();
|
|
6397
|
-
claude.sendEscape();
|
|
6398
|
-
break;
|
|
6399
|
-
case "stop_task":
|
|
6400
|
-
claude.interrupt();
|
|
6401
|
-
break;
|
|
6402
|
-
case "set_keep_alive": {
|
|
6403
|
-
const enabled = !!cmd.payload.enabled;
|
|
6404
|
-
const inCodespaceEnv = process.env.CODESPACES === "true";
|
|
6405
|
-
setKeepAlive(enabled);
|
|
6406
|
-
try {
|
|
6407
|
-
await relay.sendResult(
|
|
6408
|
-
cmd.id,
|
|
6409
|
-
"success",
|
|
6410
|
-
{ enabled, applied: enabled && inCodespaceEnv, runtime: inCodespaceEnv ? "github-codespaces" : "local" }
|
|
6411
|
-
);
|
|
6412
|
-
} catch {
|
|
6413
|
-
}
|
|
6414
|
-
break;
|
|
6415
|
-
}
|
|
6416
|
-
case "session_terminated": {
|
|
6417
|
-
showInfo("Session was deleted from the app \u2014 exiting.");
|
|
6418
|
-
try {
|
|
6419
|
-
claude.kill();
|
|
6420
|
-
} catch {
|
|
6421
|
-
}
|
|
6422
|
-
try {
|
|
6423
|
-
const proc = (0, import_child_process5.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
|
|
6424
|
-
detached: true,
|
|
6425
|
-
stdio: "ignore"
|
|
6426
|
-
});
|
|
6427
|
-
proc.unref();
|
|
6428
|
-
} catch {
|
|
6429
|
-
}
|
|
6430
|
-
outputSvc.dispose();
|
|
6431
|
-
relay.stop();
|
|
6432
|
-
ws.disconnect();
|
|
6433
|
-
process.exit(0);
|
|
6434
|
-
}
|
|
6435
|
-
case "shutdown_session": {
|
|
6436
|
-
try {
|
|
6437
|
-
await relay.sendResult(cmd.id, "success", { ok: true });
|
|
6438
|
-
} catch {
|
|
6439
|
-
}
|
|
6440
|
-
try {
|
|
6441
|
-
claude.kill();
|
|
6442
|
-
} catch {
|
|
6443
|
-
}
|
|
6444
|
-
const codespaceName2 = process.env.CODESPACE_NAME;
|
|
6445
|
-
if (codespaceName2 && process.env.CODESPACES === "true") {
|
|
6446
|
-
try {
|
|
6447
|
-
const stopProc = (0, import_child_process5.spawn)(
|
|
6448
|
-
"bash",
|
|
6449
|
-
["-lc", `sleep 1; gh codespace stop -c ${JSON.stringify(codespaceName2)} >/dev/null 2>&1 || true`],
|
|
6450
|
-
{ detached: true, stdio: "ignore" }
|
|
6451
|
-
);
|
|
6452
|
-
stopProc.unref();
|
|
6453
|
-
} catch {
|
|
6454
|
-
}
|
|
6455
|
-
}
|
|
6456
|
-
try {
|
|
6457
|
-
const proc = (0, import_child_process5.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
|
|
6458
|
-
detached: true,
|
|
6459
|
-
stdio: "ignore"
|
|
6460
|
-
});
|
|
6461
|
-
proc.unref();
|
|
6462
|
-
} catch {
|
|
6463
|
-
}
|
|
6464
|
-
outputSvc.dispose();
|
|
6465
|
-
relay.stop();
|
|
6466
|
-
ws.disconnect();
|
|
6467
|
-
process.exit(0);
|
|
6468
|
-
}
|
|
6469
|
-
case "get_context": {
|
|
6470
|
-
const usage = historySvc.getCurrentUsage();
|
|
6471
|
-
const monthlyCost = historySvc.getMonthlyEstimatedCost();
|
|
6472
|
-
const rateLimitReset = historySvc.getRateLimitReset();
|
|
6473
|
-
const quotaPercent = historySvc.getQuotaPercent();
|
|
6474
|
-
const base = usage ? { ...usage, monthlyCost } : { used: 0, total: 2e5, percent: 0, model: null, outputTokens: 0, cacheReadTokens: 0, monthlyCost, error: "No usage data found" };
|
|
6475
|
-
const result = { ...base, ...rateLimitReset ? { rateLimitReset } : {}, ...quotaPercent !== null ? { quotaPercent } : {} };
|
|
6476
|
-
await relay.sendResult(cmd.id, "completed", result);
|
|
6477
|
-
break;
|
|
6478
|
-
}
|
|
6479
|
-
case "resume_session": {
|
|
6480
|
-
const { id, auto } = parsed;
|
|
6481
|
-
if (!id) break;
|
|
6482
|
-
historySvc.setCurrentConversationId(id);
|
|
6483
|
-
await historySvc.loadConversation(id);
|
|
6484
|
-
await outputSvc.newTurnResume(id);
|
|
6485
|
-
claude.restart(id, auto ?? false);
|
|
6486
|
-
break;
|
|
6487
|
-
}
|
|
6488
|
-
case "get_conversation": {
|
|
6489
|
-
const currentId = historySvc.getCurrentConversationId();
|
|
6490
|
-
if (currentId) {
|
|
6491
|
-
try {
|
|
6492
|
-
await historySvc.loadConversation(currentId);
|
|
6493
|
-
await relay.sendResult(cmd.id, "completed", { conversationId: currentId });
|
|
6494
|
-
} catch {
|
|
6495
|
-
await relay.sendResult(cmd.id, "failed", {});
|
|
6496
|
-
}
|
|
6497
|
-
} else {
|
|
6498
|
-
await relay.sendResult(cmd.id, "completed", { conversationId: null });
|
|
6499
|
-
}
|
|
6500
|
-
break;
|
|
6501
|
-
}
|
|
6502
|
-
case "list_models": {
|
|
6503
|
-
const models = [
|
|
6504
|
-
{ id: "claude-opus-4-7", label: "Claude Opus 4.7", description: "Most capable", family: "claude", vendor: "anthropic", isDefault: false },
|
|
6505
|
-
{ id: "claude-opus-4-6", label: "Claude Opus 4.6", description: "Top tier", family: "claude", vendor: "anthropic", isDefault: false },
|
|
6506
|
-
{ id: "claude-sonnet-4-6", label: "Claude Sonnet 4.6", description: "Balanced", family: "claude", vendor: "anthropic", isDefault: true },
|
|
6507
|
-
{ id: "claude-haiku-4-5-20251001", label: "Claude Haiku 4.5", description: "Fastest", family: "claude", vendor: "anthropic", isDefault: false }
|
|
6508
|
-
];
|
|
6509
|
-
await relay.sendResult(cmd.id, "completed", { models });
|
|
6510
|
-
break;
|
|
6511
|
-
}
|
|
6512
|
-
case "read_file": {
|
|
6513
|
-
const { path: filePath } = parsed;
|
|
6514
|
-
if (!filePath) {
|
|
6515
|
-
await relay.sendResult(cmd.id, "failed", { error: "Missing path" });
|
|
6516
|
-
break;
|
|
6517
|
-
}
|
|
6518
|
-
const result = await readProjectFile(filePath);
|
|
6519
|
-
await relay.sendResult(cmd.id, "completed", result);
|
|
6520
|
-
break;
|
|
6521
|
-
}
|
|
6522
|
-
case "write_file": {
|
|
6523
|
-
const { path: filePath, content } = parsed;
|
|
6524
|
-
if (!filePath || typeof content !== "string") {
|
|
6525
|
-
await relay.sendResult(cmd.id, "failed", { error: "Missing path or content" });
|
|
6526
|
-
break;
|
|
6527
|
-
}
|
|
6528
|
-
const result = await writeProjectFile(filePath, content);
|
|
6529
|
-
await relay.sendResult(cmd.id, "completed", result);
|
|
6530
|
-
break;
|
|
6531
|
-
}
|
|
6532
|
-
case "list_files": {
|
|
6533
|
-
const result = await listProjectFiles({ query: parsed.query });
|
|
6534
|
-
await relay.sendResult(cmd.id, "completed", result);
|
|
6535
|
-
break;
|
|
6536
|
-
}
|
|
6537
|
-
case "git_status": {
|
|
6538
|
-
const result = await gitStatus();
|
|
6539
|
-
await relay.sendResult(cmd.id, "completed", result);
|
|
6540
|
-
break;
|
|
6541
|
-
}
|
|
6542
|
-
case "git_diff": {
|
|
6543
|
-
const { path: filePath } = parsed;
|
|
6544
|
-
const result = await gitDiff(filePath ?? null);
|
|
6545
|
-
await relay.sendResult(cmd.id, "completed", result);
|
|
6546
|
-
break;
|
|
6547
|
-
}
|
|
6548
|
-
case "git_diff_staged": {
|
|
6549
|
-
const { path: filePath } = parsed;
|
|
6550
|
-
const result = await gitDiffStaged(filePath ?? null);
|
|
6551
|
-
await relay.sendResult(cmd.id, "completed", result);
|
|
6552
|
-
break;
|
|
6553
|
-
}
|
|
6554
|
-
case "git_log": {
|
|
6555
|
-
const result = await gitLog(parsed.limit ?? 30);
|
|
6556
|
-
await relay.sendResult(cmd.id, "completed", result);
|
|
6557
|
-
break;
|
|
6558
|
-
}
|
|
6559
|
-
case "git_commit": {
|
|
6560
|
-
if (!parsed.message) {
|
|
6561
|
-
await relay.sendResult(cmd.id, "failed", { error: "Missing message" });
|
|
6562
|
-
break;
|
|
6563
|
-
}
|
|
6564
|
-
const result = await gitCommit(parsed.message, parsed.paths);
|
|
6565
|
-
await relay.sendResult(cmd.id, "completed", result);
|
|
6566
|
-
break;
|
|
6567
|
-
}
|
|
6568
|
-
case "git_push": {
|
|
6569
|
-
const result = await gitPush();
|
|
6570
|
-
await relay.sendResult(cmd.id, "completed", result);
|
|
6571
|
-
break;
|
|
6572
|
-
}
|
|
6573
|
-
case "git_pull": {
|
|
6574
|
-
const result = await gitPull();
|
|
6575
|
-
await relay.sendResult(cmd.id, "completed", result);
|
|
6576
|
-
break;
|
|
6577
|
-
}
|
|
6578
|
-
case "git_resolve": {
|
|
6579
|
-
const { path: filePath, side } = parsed;
|
|
6580
|
-
if (!filePath || !side) {
|
|
6581
|
-
await relay.sendResult(cmd.id, "failed", { error: "Missing path or side" });
|
|
6582
|
-
break;
|
|
6583
|
-
}
|
|
6584
|
-
const result = await gitResolve(filePath, side);
|
|
6585
|
-
await relay.sendResult(cmd.id, "completed", result);
|
|
6586
|
-
break;
|
|
6587
|
-
}
|
|
6588
|
-
}
|
|
6589
|
-
});
|
|
6590
|
-
ws.addHandler({
|
|
6591
|
-
onConnected() {
|
|
6827
|
+
historySvc.uploadDelta().catch(() => {
|
|
6828
|
+
});
|
|
6829
|
+
}, 400);
|
|
6592
6830
|
},
|
|
6593
|
-
|
|
6831
|
+
() => {
|
|
6832
|
+
const prevCount = historySvc.getCurrentMessageCount();
|
|
6833
|
+
historySvc.waitForNewUserMessage(prevCount).then((userText) => outputSvc.startTerminalTurn(userText ?? void 0)).catch(() => outputSvc.startTerminalTurn(void 0));
|
|
6594
6834
|
},
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
const cmdType = typeof payload.type === "string" ? payload.type : null;
|
|
6598
|
-
if (!cmdType) return;
|
|
6599
|
-
const parsed = parsePayload(startCommandSchema, payload.payload ?? {});
|
|
6600
|
-
if (!parsed) {
|
|
6601
|
-
showInfo(`Ignoring malformed ${cmdType} payload (ws).`);
|
|
6602
|
-
return;
|
|
6603
|
-
}
|
|
6604
|
-
if (cmdType === "start_task") {
|
|
6605
|
-
const { prompt, files } = parsed;
|
|
6606
|
-
const effectivePrompt = prompt ?? "";
|
|
6607
|
-
if (files && files.length > 0) {
|
|
6608
|
-
const paths = saveFilesTemp(files);
|
|
6609
|
-
const atRefs = paths.map((p2) => `@${p2}`).join(" ");
|
|
6610
|
-
outputSvc.newTurn();
|
|
6611
|
-
claude.sendCommand(`${atRefs} ${effectivePrompt}`.trim());
|
|
6612
|
-
setTimeout(() => {
|
|
6613
|
-
for (const p2 of paths) {
|
|
6614
|
-
try {
|
|
6615
|
-
fs8.unlinkSync(p2);
|
|
6616
|
-
} catch {
|
|
6617
|
-
}
|
|
6618
|
-
}
|
|
6619
|
-
}, 12e4);
|
|
6620
|
-
} else if (effectivePrompt) {
|
|
6621
|
-
sendPrompt(effectivePrompt);
|
|
6622
|
-
}
|
|
6623
|
-
} else if (cmdType === "provide_input") {
|
|
6624
|
-
const { input } = parsed;
|
|
6625
|
-
if (input) sendPrompt(input);
|
|
6626
|
-
} else if (cmdType === "select_option") {
|
|
6627
|
-
const index = parsed.index ?? 0;
|
|
6628
|
-
const from = parsed.from ?? 0;
|
|
6629
|
-
outputSvc.newTurn();
|
|
6630
|
-
claude.selectOption(index, from);
|
|
6631
|
-
} else if (cmdType === "escape_key") {
|
|
6632
|
-
outputSvc.newTurn();
|
|
6633
|
-
claude.sendEscape();
|
|
6634
|
-
} else if (cmdType === "stop_task") {
|
|
6635
|
-
claude.interrupt();
|
|
6636
|
-
} else if (cmdType === "get_conversation") {
|
|
6637
|
-
const currentId = historySvc.getCurrentConversationId();
|
|
6638
|
-
if (currentId) {
|
|
6639
|
-
historySvc.loadConversation(currentId).catch(() => {
|
|
6640
|
-
});
|
|
6641
|
-
}
|
|
6642
|
-
} else if (cmdType === "resume_session") {
|
|
6643
|
-
const { id, auto } = parsed;
|
|
6644
|
-
if (id) {
|
|
6645
|
-
const autoFlag = auto ?? false;
|
|
6646
|
-
historySvc.loadConversation(id).then(() => outputSvc.newTurnResume(id)).then(() => {
|
|
6647
|
-
claude.restart(id, autoFlag);
|
|
6648
|
-
}).catch(() => {
|
|
6649
|
-
});
|
|
6650
|
-
}
|
|
6651
|
-
}
|
|
6652
|
-
}
|
|
6653
|
-
});
|
|
6654
|
-
ws.connect();
|
|
6655
|
-
relay.start();
|
|
6835
|
+
session.pluginAuthToken
|
|
6836
|
+
);
|
|
6656
6837
|
const claude = new ClaudeService({
|
|
6657
|
-
cwd
|
|
6838
|
+
cwd,
|
|
6658
6839
|
onData(raw) {
|
|
6659
6840
|
outputSvc.push(raw);
|
|
6660
6841
|
},
|
|
@@ -6662,18 +6843,29 @@ except Exception:sys.exit(0)
|
|
|
6662
6843
|
process.removeListener("SIGINT", sigintHandler);
|
|
6663
6844
|
outputSvc.dispose();
|
|
6664
6845
|
relay.stop();
|
|
6665
|
-
ws.disconnect();
|
|
6666
6846
|
process.exit(code);
|
|
6667
6847
|
}
|
|
6668
6848
|
});
|
|
6849
|
+
const ctx = {
|
|
6850
|
+
outputSvc,
|
|
6851
|
+
claude,
|
|
6852
|
+
historySvc,
|
|
6853
|
+
relay: void 0,
|
|
6854
|
+
setKeepAlive: setKeepAlive2,
|
|
6855
|
+
keepAliveCtx
|
|
6856
|
+
};
|
|
6857
|
+
const relay = new CommandRelayService(pluginId, async (cmd) => {
|
|
6858
|
+
await dispatchCommand(ctx, cmd);
|
|
6859
|
+
});
|
|
6860
|
+
ctx.relay = relay;
|
|
6669
6861
|
function sigintHandler() {
|
|
6670
6862
|
claude.kill();
|
|
6671
6863
|
outputSvc.dispose();
|
|
6672
6864
|
relay.stop();
|
|
6673
|
-
ws.disconnect();
|
|
6674
6865
|
process.exit(0);
|
|
6675
6866
|
}
|
|
6676
6867
|
process.once("SIGINT", sigintHandler);
|
|
6868
|
+
relay.start();
|
|
6677
6869
|
await claude.spawn();
|
|
6678
6870
|
setTimeout(() => {
|
|
6679
6871
|
historySvc.detectCurrentConversation();
|
|
@@ -6685,47 +6877,7 @@ except Exception:sys.exit(0)
|
|
|
6685
6877
|
});
|
|
6686
6878
|
}
|
|
6687
6879
|
}, 2e3);
|
|
6688
|
-
setTimeout(() =>
|
|
6689
|
-
fetchQuotaUsage();
|
|
6690
|
-
}, 5e3);
|
|
6691
|
-
const inCodespace = process.env.CODESPACES === "true";
|
|
6692
|
-
const codespaceName = process.env.CODESPACE_NAME;
|
|
6693
|
-
let keepAliveTimer = null;
|
|
6694
|
-
async function setIdleTimeout(minutes) {
|
|
6695
|
-
if (!inCodespace || !codespaceName) return;
|
|
6696
|
-
await new Promise((resolve2) => {
|
|
6697
|
-
const proc = (0, import_child_process5.spawn)(
|
|
6698
|
-
"gh",
|
|
6699
|
-
[
|
|
6700
|
-
"api",
|
|
6701
|
-
"-X",
|
|
6702
|
-
"PATCH",
|
|
6703
|
-
`/user/codespaces/${codespaceName}`,
|
|
6704
|
-
"-F",
|
|
6705
|
-
`idle_timeout_minutes=${minutes}`
|
|
6706
|
-
],
|
|
6707
|
-
{ stdio: "ignore", detached: true }
|
|
6708
|
-
);
|
|
6709
|
-
proc.unref();
|
|
6710
|
-
proc.on("exit", () => resolve2());
|
|
6711
|
-
proc.on("error", () => resolve2());
|
|
6712
|
-
});
|
|
6713
|
-
}
|
|
6714
|
-
function setKeepAlive(enabled) {
|
|
6715
|
-
if (keepAliveTimer) {
|
|
6716
|
-
clearInterval(keepAliveTimer);
|
|
6717
|
-
keepAliveTimer = null;
|
|
6718
|
-
}
|
|
6719
|
-
if (!inCodespace || !codespaceName) return;
|
|
6720
|
-
if (!enabled) {
|
|
6721
|
-
void setIdleTimeout(30);
|
|
6722
|
-
return;
|
|
6723
|
-
}
|
|
6724
|
-
void setIdleTimeout(240);
|
|
6725
|
-
keepAliveTimer = setInterval(() => {
|
|
6726
|
-
void setIdleTimeout(240);
|
|
6727
|
-
}, 30 * 60 * 1e3);
|
|
6728
|
-
}
|
|
6880
|
+
setTimeout(() => fetchQuotaUsage(historySvc), 5e3);
|
|
6729
6881
|
}
|
|
6730
6882
|
|
|
6731
6883
|
// src/commands/pair.ts
|
|
@@ -6906,19 +7058,19 @@ async function logout() {
|
|
|
6906
7058
|
}
|
|
6907
7059
|
|
|
6908
7060
|
// src/commands/deploy.ts
|
|
6909
|
-
var
|
|
6910
|
-
var
|
|
6911
|
-
var
|
|
6912
|
-
var
|
|
7061
|
+
var import_child_process12 = require("child_process");
|
|
7062
|
+
var fs10 = __toESM(require("fs"));
|
|
7063
|
+
var os9 = __toESM(require("os"));
|
|
7064
|
+
var path17 = __toESM(require("path"));
|
|
6913
7065
|
var import_util6 = require("util");
|
|
6914
7066
|
var import_picocolors9 = __toESM(require("picocolors"));
|
|
6915
7067
|
|
|
6916
7068
|
// src/services/providers/github-codespaces.ts
|
|
6917
|
-
var
|
|
7069
|
+
var import_child_process8 = require("child_process");
|
|
6918
7070
|
var import_util2 = require("util");
|
|
6919
7071
|
var import_picocolors7 = __toESM(require("picocolors"));
|
|
6920
|
-
var
|
|
6921
|
-
var execFileP2 = (0, import_util2.promisify)(
|
|
7072
|
+
var path13 = __toESM(require("path"));
|
|
7073
|
+
var execFileP2 = (0, import_util2.promisify)(import_child_process8.execFile);
|
|
6922
7074
|
var MAX_BUFFER = 8 * 1024 * 1024;
|
|
6923
7075
|
function resetStdinForChild() {
|
|
6924
7076
|
if (process.stdin.isTTY) {
|
|
@@ -6962,7 +7114,7 @@ var GitHubCodespacesProvider = class {
|
|
|
6962
7114
|
if (!isAuthed) {
|
|
6963
7115
|
resetStdinForChild();
|
|
6964
7116
|
await new Promise((resolve2, reject) => {
|
|
6965
|
-
const proc = (0,
|
|
7117
|
+
const proc = (0, import_child_process8.spawn)("gh", ["auth", "login", "-s", "codespace,repo,read:user"], {
|
|
6966
7118
|
stdio: "inherit"
|
|
6967
7119
|
});
|
|
6968
7120
|
proc.on("exit", (code) => {
|
|
@@ -6996,7 +7148,7 @@ var GitHubCodespacesProvider = class {
|
|
|
6996
7148
|
wt(noteLines.join("\n"), "One more permission needed");
|
|
6997
7149
|
resetStdinForChild();
|
|
6998
7150
|
const refreshCode = await new Promise((resolve2, reject) => {
|
|
6999
|
-
const proc = (0,
|
|
7151
|
+
const proc = (0, import_child_process8.spawn)(
|
|
7000
7152
|
"gh",
|
|
7001
7153
|
["auth", "refresh", "-h", "github.com", "-s", "codespace"],
|
|
7002
7154
|
{ stdio: "inherit" }
|
|
@@ -7146,7 +7298,7 @@ var GitHubCodespacesProvider = class {
|
|
|
7146
7298
|
O2.step(`Installing gh via ${installCmd.describe}\u2026`);
|
|
7147
7299
|
resetStdinForChild();
|
|
7148
7300
|
const ok = await new Promise((resolve2) => {
|
|
7149
|
-
const proc = (0,
|
|
7301
|
+
const proc = (0, import_child_process8.spawn)(installCmd.exe, installCmd.args, { stdio: "inherit" });
|
|
7150
7302
|
proc.on("exit", (code) => resolve2(code === 0));
|
|
7151
7303
|
proc.on("error", () => resolve2(false));
|
|
7152
7304
|
});
|
|
@@ -7173,7 +7325,7 @@ var GitHubCodespacesProvider = class {
|
|
|
7173
7325
|
);
|
|
7174
7326
|
resetStdinForChild();
|
|
7175
7327
|
await new Promise((resolve2, reject) => {
|
|
7176
|
-
const proc = (0,
|
|
7328
|
+
const proc = (0, import_child_process8.spawn)(
|
|
7177
7329
|
"gh",
|
|
7178
7330
|
["auth", "refresh", "-h", "github.com", "-s", "repo,read:org"],
|
|
7179
7331
|
{ stdio: "inherit" }
|
|
@@ -7351,7 +7503,7 @@ var GitHubCodespacesProvider = class {
|
|
|
7351
7503
|
async streamCommand(workspaceId, command2) {
|
|
7352
7504
|
resetStdinForChild();
|
|
7353
7505
|
return new Promise((resolve2, reject) => {
|
|
7354
|
-
const proc = (0,
|
|
7506
|
+
const proc = (0, import_child_process8.spawn)(
|
|
7355
7507
|
"gh",
|
|
7356
7508
|
["codespace", "ssh", "-c", workspaceId, "--", "-tt", command2],
|
|
7357
7509
|
{ stdio: "inherit" }
|
|
@@ -7378,11 +7530,11 @@ var GitHubCodespacesProvider = class {
|
|
|
7378
7530
|
`mkdir -p ${shellQuote(remoteDir)} && tar -xzf - -C ${shellQuote(remoteDir)}`
|
|
7379
7531
|
];
|
|
7380
7532
|
await new Promise((resolve2, reject) => {
|
|
7381
|
-
const tar = (0,
|
|
7533
|
+
const tar = (0, import_child_process8.spawn)("tar", tarArgs, {
|
|
7382
7534
|
stdio: ["ignore", "pipe", "pipe"],
|
|
7383
7535
|
env: tarEnv
|
|
7384
7536
|
});
|
|
7385
|
-
const ssh = (0,
|
|
7537
|
+
const ssh = (0, import_child_process8.spawn)("gh", sshArgs, {
|
|
7386
7538
|
stdio: [tar.stdout, "pipe", "pipe"]
|
|
7387
7539
|
});
|
|
7388
7540
|
let tarErr = "";
|
|
@@ -7406,7 +7558,7 @@ var GitHubCodespacesProvider = class {
|
|
|
7406
7558
|
});
|
|
7407
7559
|
}
|
|
7408
7560
|
async uploadFile(workspaceId, remotePath, contents, options = {}) {
|
|
7409
|
-
const remoteDir =
|
|
7561
|
+
const remoteDir = path13.posix.dirname(remotePath);
|
|
7410
7562
|
const parts = [
|
|
7411
7563
|
`mkdir -p ${shellQuote(remoteDir)}`,
|
|
7412
7564
|
`cat > ${shellQuote(remotePath)}`
|
|
@@ -7416,7 +7568,7 @@ var GitHubCodespacesProvider = class {
|
|
|
7416
7568
|
}
|
|
7417
7569
|
const cmd = parts.join(" && ");
|
|
7418
7570
|
await new Promise((resolve2, reject) => {
|
|
7419
|
-
const proc = (0,
|
|
7571
|
+
const proc = (0, import_child_process8.spawn)(
|
|
7420
7572
|
"gh",
|
|
7421
7573
|
["codespace", "ssh", "-c", workspaceId, "--", cmd],
|
|
7422
7574
|
{ stdio: ["pipe", "pipe", "pipe"] }
|
|
@@ -7474,11 +7626,11 @@ function shellQuote(s) {
|
|
|
7474
7626
|
}
|
|
7475
7627
|
|
|
7476
7628
|
// src/services/providers/gitpod.ts
|
|
7477
|
-
var
|
|
7629
|
+
var import_child_process9 = require("child_process");
|
|
7478
7630
|
var import_util3 = require("util");
|
|
7479
|
-
var
|
|
7631
|
+
var path14 = __toESM(require("path"));
|
|
7480
7632
|
var import_picocolors8 = __toESM(require("picocolors"));
|
|
7481
|
-
var execFileP3 = (0, import_util3.promisify)(
|
|
7633
|
+
var execFileP3 = (0, import_util3.promisify)(import_child_process9.execFile);
|
|
7482
7634
|
var MAX_BUFFER2 = 8 * 1024 * 1024;
|
|
7483
7635
|
function resetStdinForChild2() {
|
|
7484
7636
|
if (process.stdin.isTTY) {
|
|
@@ -7518,7 +7670,7 @@ var GitpodProvider = class {
|
|
|
7518
7670
|
);
|
|
7519
7671
|
resetStdinForChild2();
|
|
7520
7672
|
await new Promise((resolve2, reject) => {
|
|
7521
|
-
const proc = (0,
|
|
7673
|
+
const proc = (0, import_child_process9.spawn)("gitpod", ["login"], { stdio: "inherit" });
|
|
7522
7674
|
proc.on("exit", (code) => {
|
|
7523
7675
|
if (code === 0) resolve2();
|
|
7524
7676
|
else reject(new Error("gitpod login failed."));
|
|
@@ -7670,7 +7822,7 @@ var GitpodProvider = class {
|
|
|
7670
7822
|
async streamCommand(workspaceId, command2) {
|
|
7671
7823
|
resetStdinForChild2();
|
|
7672
7824
|
return new Promise((resolve2, reject) => {
|
|
7673
|
-
const proc = (0,
|
|
7825
|
+
const proc = (0, import_child_process9.spawn)(
|
|
7674
7826
|
"gitpod",
|
|
7675
7827
|
["workspace", "ssh", workspaceId, "--", "-tt", command2],
|
|
7676
7828
|
{ stdio: "inherit" }
|
|
@@ -7690,11 +7842,11 @@ var GitpodProvider = class {
|
|
|
7690
7842
|
const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
|
|
7691
7843
|
const remoteCmd = `mkdir -p ${shellQuote2(remoteDir)} && tar -xzf - -C ${shellQuote2(remoteDir)}`;
|
|
7692
7844
|
await new Promise((resolve2, reject) => {
|
|
7693
|
-
const tar = (0,
|
|
7845
|
+
const tar = (0, import_child_process9.spawn)("tar", tarArgs, {
|
|
7694
7846
|
stdio: ["ignore", "pipe", "pipe"],
|
|
7695
7847
|
env: tarEnv
|
|
7696
7848
|
});
|
|
7697
|
-
const ssh = (0,
|
|
7849
|
+
const ssh = (0, import_child_process9.spawn)(
|
|
7698
7850
|
"gitpod",
|
|
7699
7851
|
["workspace", "ssh", workspaceId, "--", remoteCmd],
|
|
7700
7852
|
{ stdio: [tar.stdout, "pipe", "pipe"] }
|
|
@@ -7716,7 +7868,7 @@ var GitpodProvider = class {
|
|
|
7716
7868
|
});
|
|
7717
7869
|
}
|
|
7718
7870
|
async uploadFile(workspaceId, remotePath, contents, options = {}) {
|
|
7719
|
-
const remoteDir =
|
|
7871
|
+
const remoteDir = path14.posix.dirname(remotePath);
|
|
7720
7872
|
const parts = [
|
|
7721
7873
|
`mkdir -p ${shellQuote2(remoteDir)}`,
|
|
7722
7874
|
`cat > ${shellQuote2(remotePath)}`
|
|
@@ -7726,7 +7878,7 @@ var GitpodProvider = class {
|
|
|
7726
7878
|
}
|
|
7727
7879
|
const cmd = parts.join(" && ");
|
|
7728
7880
|
await new Promise((resolve2, reject) => {
|
|
7729
|
-
const proc = (0,
|
|
7881
|
+
const proc = (0, import_child_process9.spawn)(
|
|
7730
7882
|
"gitpod",
|
|
7731
7883
|
["workspace", "ssh", workspaceId, "--", cmd],
|
|
7732
7884
|
{ stdio: ["pipe", "pipe", "pipe"] }
|
|
@@ -7750,10 +7902,10 @@ function shellQuote2(s) {
|
|
|
7750
7902
|
}
|
|
7751
7903
|
|
|
7752
7904
|
// src/services/providers/gitlab-workspaces.ts
|
|
7753
|
-
var
|
|
7905
|
+
var import_child_process10 = require("child_process");
|
|
7754
7906
|
var import_util4 = require("util");
|
|
7755
|
-
var
|
|
7756
|
-
var execFileP4 = (0, import_util4.promisify)(
|
|
7907
|
+
var path15 = __toESM(require("path"));
|
|
7908
|
+
var execFileP4 = (0, import_util4.promisify)(import_child_process10.execFile);
|
|
7757
7909
|
var MAX_BUFFER3 = 8 * 1024 * 1024;
|
|
7758
7910
|
var GITLAB_API_BASE = process.env.CODEAM_GITLAB_API_URL ?? "https://gitlab.com/api/v4";
|
|
7759
7911
|
function resetStdinForChild3() {
|
|
@@ -7795,7 +7947,7 @@ var GitLabWorkspacesProvider = class {
|
|
|
7795
7947
|
);
|
|
7796
7948
|
resetStdinForChild3();
|
|
7797
7949
|
await new Promise((resolve2, reject) => {
|
|
7798
|
-
const proc = (0,
|
|
7950
|
+
const proc = (0, import_child_process10.spawn)(
|
|
7799
7951
|
"glab",
|
|
7800
7952
|
["auth", "login", "--scopes", "api,read_user,read_repository"],
|
|
7801
7953
|
{ stdio: "inherit" }
|
|
@@ -7967,7 +8119,7 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
|
|
|
7967
8119
|
const sshHost = process.env.CODEAM_GITLAB_SSH_HOST ?? "workspaces.gitlab.com";
|
|
7968
8120
|
resetStdinForChild3();
|
|
7969
8121
|
return new Promise((resolve2, reject) => {
|
|
7970
|
-
const proc = (0,
|
|
8122
|
+
const proc = (0, import_child_process10.spawn)(
|
|
7971
8123
|
"ssh",
|
|
7972
8124
|
["-tt", "-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, command2],
|
|
7973
8125
|
{ stdio: "inherit" }
|
|
@@ -7988,8 +8140,8 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
|
|
|
7988
8140
|
const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
|
|
7989
8141
|
const remoteCmd = `mkdir -p ${shellQuote3(remoteDir)} && tar -xzf - -C ${shellQuote3(remoteDir)}`;
|
|
7990
8142
|
await new Promise((resolve2, reject) => {
|
|
7991
|
-
const tar = (0,
|
|
7992
|
-
const ssh = (0,
|
|
8143
|
+
const tar = (0, import_child_process10.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
|
|
8144
|
+
const ssh = (0, import_child_process10.spawn)(
|
|
7993
8145
|
"ssh",
|
|
7994
8146
|
["-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, remoteCmd],
|
|
7995
8147
|
{ stdio: [tar.stdout, "pipe", "pipe"] }
|
|
@@ -8012,14 +8164,14 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
|
|
|
8012
8164
|
}
|
|
8013
8165
|
async uploadFile(workspaceId, remotePath, contents, options = {}) {
|
|
8014
8166
|
const sshHost = process.env.CODEAM_GITLAB_SSH_HOST ?? "workspaces.gitlab.com";
|
|
8015
|
-
const remoteDir =
|
|
8167
|
+
const remoteDir = path15.posix.dirname(remotePath);
|
|
8016
8168
|
const parts = [`mkdir -p ${shellQuote3(remoteDir)}`, `cat > ${shellQuote3(remotePath)}`];
|
|
8017
8169
|
if (options.mode != null) {
|
|
8018
8170
|
parts.push(`chmod ${options.mode.toString(8)} ${shellQuote3(remotePath)}`);
|
|
8019
8171
|
}
|
|
8020
8172
|
const cmd = parts.join(" && ");
|
|
8021
8173
|
await new Promise((resolve2, reject) => {
|
|
8022
|
-
const proc = (0,
|
|
8174
|
+
const proc = (0, import_child_process10.spawn)(
|
|
8023
8175
|
"ssh",
|
|
8024
8176
|
["-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, cmd],
|
|
8025
8177
|
{ stdio: ["pipe", "pipe", "pipe"] }
|
|
@@ -8078,10 +8230,10 @@ function shellQuote3(s) {
|
|
|
8078
8230
|
}
|
|
8079
8231
|
|
|
8080
8232
|
// src/services/providers/railway.ts
|
|
8081
|
-
var
|
|
8233
|
+
var import_child_process11 = require("child_process");
|
|
8082
8234
|
var import_util5 = require("util");
|
|
8083
|
-
var
|
|
8084
|
-
var execFileP5 = (0, import_util5.promisify)(
|
|
8235
|
+
var path16 = __toESM(require("path"));
|
|
8236
|
+
var execFileP5 = (0, import_util5.promisify)(import_child_process11.execFile);
|
|
8085
8237
|
var MAX_BUFFER4 = 8 * 1024 * 1024;
|
|
8086
8238
|
function resetStdinForChild4() {
|
|
8087
8239
|
if (process.stdin.isTTY) {
|
|
@@ -8122,7 +8274,7 @@ var RailwayProvider = class {
|
|
|
8122
8274
|
);
|
|
8123
8275
|
resetStdinForChild4();
|
|
8124
8276
|
await new Promise((resolve2, reject) => {
|
|
8125
|
-
const proc = (0,
|
|
8277
|
+
const proc = (0, import_child_process11.spawn)("railway", ["login"], { stdio: "inherit" });
|
|
8126
8278
|
proc.on("exit", (code) => {
|
|
8127
8279
|
if (code === 0) resolve2();
|
|
8128
8280
|
else reject(new Error("railway login failed."));
|
|
@@ -8265,7 +8417,7 @@ var RailwayProvider = class {
|
|
|
8265
8417
|
}
|
|
8266
8418
|
resetStdinForChild4();
|
|
8267
8419
|
return new Promise((resolve2, reject) => {
|
|
8268
|
-
const proc = (0,
|
|
8420
|
+
const proc = (0, import_child_process11.spawn)(
|
|
8269
8421
|
"railway",
|
|
8270
8422
|
["shell", "--project", projectId, "--service", serviceId, "--command", command2],
|
|
8271
8423
|
{ stdio: "inherit" }
|
|
@@ -8289,8 +8441,8 @@ var RailwayProvider = class {
|
|
|
8289
8441
|
const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
|
|
8290
8442
|
const remoteCmd = `mkdir -p ${shellQuote4(remoteDir)} && tar -xzf - -C ${shellQuote4(remoteDir)}`;
|
|
8291
8443
|
await new Promise((resolve2, reject) => {
|
|
8292
|
-
const tar = (0,
|
|
8293
|
-
const sh = (0,
|
|
8444
|
+
const tar = (0, import_child_process11.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
|
|
8445
|
+
const sh = (0, import_child_process11.spawn)(
|
|
8294
8446
|
"railway",
|
|
8295
8447
|
["shell", "--project", projectId, "--service", serviceId, "--command", remoteCmd],
|
|
8296
8448
|
{ stdio: [tar.stdout, "pipe", "pipe"] }
|
|
@@ -8316,14 +8468,14 @@ var RailwayProvider = class {
|
|
|
8316
8468
|
if (!projectId || !serviceId) {
|
|
8317
8469
|
throw new Error("Invalid Railway workspace id (expected projectId/serviceId).");
|
|
8318
8470
|
}
|
|
8319
|
-
const remoteDir =
|
|
8471
|
+
const remoteDir = path16.posix.dirname(remotePath);
|
|
8320
8472
|
const parts = [`mkdir -p ${shellQuote4(remoteDir)}`, `cat > ${shellQuote4(remotePath)}`];
|
|
8321
8473
|
if (options.mode != null) {
|
|
8322
8474
|
parts.push(`chmod ${options.mode.toString(8)} ${shellQuote4(remotePath)}`);
|
|
8323
8475
|
}
|
|
8324
8476
|
const cmd = parts.join(" && ");
|
|
8325
8477
|
await new Promise((resolve2, reject) => {
|
|
8326
|
-
const proc = (0,
|
|
8478
|
+
const proc = (0, import_child_process11.spawn)(
|
|
8327
8479
|
"railway",
|
|
8328
8480
|
["shell", "--project", projectId, "--service", serviceId, "--command", cmd],
|
|
8329
8481
|
{ stdio: ["pipe", "pipe", "pipe"] }
|
|
@@ -8355,7 +8507,7 @@ var PROVIDERS = [
|
|
|
8355
8507
|
];
|
|
8356
8508
|
|
|
8357
8509
|
// src/commands/deploy.ts
|
|
8358
|
-
var execFileP6 = (0, import_util6.promisify)(
|
|
8510
|
+
var execFileP6 = (0, import_util6.promisify)(import_child_process12.execFile);
|
|
8359
8511
|
async function deploy() {
|
|
8360
8512
|
console.log();
|
|
8361
8513
|
mt(import_picocolors9.default.bgMagenta(import_picocolors9.default.white(" codeam deploy ")));
|
|
@@ -8508,7 +8660,7 @@ async function deploy() {
|
|
|
8508
8660
|
process.exit(1);
|
|
8509
8661
|
}
|
|
8510
8662
|
}
|
|
8511
|
-
const localClaudeDir =
|
|
8663
|
+
const localClaudeDir = path17.join(os9.homedir(), ".claude");
|
|
8512
8664
|
const localCredsKind = await detectLocalClaudeCredentials(localClaudeDir);
|
|
8513
8665
|
let bridged = "none";
|
|
8514
8666
|
if (localCredsKind !== "none") {
|
|
@@ -8552,7 +8704,7 @@ async function deploy() {
|
|
|
8552
8704
|
process.exit(1);
|
|
8553
8705
|
}
|
|
8554
8706
|
claudeStep.stop("\u2713 Claude CLI installed");
|
|
8555
|
-
const haveLocalClaude =
|
|
8707
|
+
const haveLocalClaude = fs10.existsSync(localClaudeDir) && fs10.statSync(localClaudeDir).isDirectory();
|
|
8556
8708
|
if (haveLocalClaude) {
|
|
8557
8709
|
const copyStep = fe();
|
|
8558
8710
|
copyStep.start("Copying local Claude config to workspace\u2026");
|
|
@@ -8606,10 +8758,10 @@ async function deploy() {
|
|
|
8606
8758
|
}
|
|
8607
8759
|
}
|
|
8608
8760
|
if (bridged !== "none") {
|
|
8609
|
-
const localClaudeJson =
|
|
8610
|
-
if (
|
|
8761
|
+
const localClaudeJson = path17.join(os9.homedir(), ".claude.json");
|
|
8762
|
+
if (fs10.existsSync(localClaudeJson)) {
|
|
8611
8763
|
try {
|
|
8612
|
-
const contents =
|
|
8764
|
+
const contents = fs10.readFileSync(localClaudeJson);
|
|
8613
8765
|
await provider.uploadFile(
|
|
8614
8766
|
workspace.id,
|
|
8615
8767
|
"/home/codespace/.claude.json",
|
|
@@ -8799,7 +8951,7 @@ async function runRemoteClaudeLogin(provider, workspaceId) {
|
|
|
8799
8951
|
}
|
|
8800
8952
|
}
|
|
8801
8953
|
async function detectLocalClaudeCredentials(localClaudeDir) {
|
|
8802
|
-
if (
|
|
8954
|
+
if (fs10.existsSync(path17.join(localClaudeDir, ".credentials.json"))) {
|
|
8803
8955
|
return "flat-file";
|
|
8804
8956
|
}
|
|
8805
8957
|
if (process.platform === "darwin") {
|
|
@@ -8832,8 +8984,8 @@ async function verifyClaudeAuth(provider, workspaceId) {
|
|
|
8832
8984
|
}
|
|
8833
8985
|
}
|
|
8834
8986
|
async function bridgeClaudeCredentials(provider, workspaceId, localClaudeDir) {
|
|
8835
|
-
const fileBased =
|
|
8836
|
-
if (
|
|
8987
|
+
const fileBased = path17.join(localClaudeDir, ".credentials.json");
|
|
8988
|
+
if (fs10.existsSync(fileBased)) return "flat-file";
|
|
8837
8989
|
if (process.platform === "darwin") {
|
|
8838
8990
|
try {
|
|
8839
8991
|
const { stdout } = await execFileP6(
|
|
@@ -9041,7 +9193,7 @@ async function stopWorkspaceFromLocal(target) {
|
|
|
9041
9193
|
// src/commands/version.ts
|
|
9042
9194
|
var import_picocolors11 = __toESM(require("picocolors"));
|
|
9043
9195
|
function version() {
|
|
9044
|
-
const v = true ? "2.
|
|
9196
|
+
const v = true ? "2.5.0" : "unknown";
|
|
9045
9197
|
console.log(`${import_picocolors11.default.bold("codeam-cli")} ${import_picocolors11.default.cyan(v)}`);
|
|
9046
9198
|
}
|
|
9047
9199
|
|
|
@@ -9078,22 +9230,22 @@ function help() {
|
|
|
9078
9230
|
}
|
|
9079
9231
|
|
|
9080
9232
|
// src/lib/updateNotifier.ts
|
|
9081
|
-
var
|
|
9082
|
-
var
|
|
9083
|
-
var
|
|
9084
|
-
var
|
|
9233
|
+
var fs11 = __toESM(require("fs"));
|
|
9234
|
+
var os10 = __toESM(require("os"));
|
|
9235
|
+
var path18 = __toESM(require("path"));
|
|
9236
|
+
var https5 = __toESM(require("https"));
|
|
9085
9237
|
var import_picocolors13 = __toESM(require("picocolors"));
|
|
9086
9238
|
var PKG_NAME = "codeam-cli";
|
|
9087
9239
|
var REGISTRY_URL = `https://registry.npmjs.org/${PKG_NAME}/latest`;
|
|
9088
9240
|
var TTL_MS = 24 * 60 * 60 * 1e3;
|
|
9089
9241
|
var REQUEST_TIMEOUT_MS = 1500;
|
|
9090
9242
|
function cachePath() {
|
|
9091
|
-
const dir =
|
|
9092
|
-
return
|
|
9243
|
+
const dir = path18.join(os10.homedir(), ".codeam");
|
|
9244
|
+
return path18.join(dir, "update-check.json");
|
|
9093
9245
|
}
|
|
9094
9246
|
function readCache() {
|
|
9095
9247
|
try {
|
|
9096
|
-
const raw =
|
|
9248
|
+
const raw = fs11.readFileSync(cachePath(), "utf8");
|
|
9097
9249
|
const parsed = JSON.parse(raw);
|
|
9098
9250
|
if (typeof parsed.fetchedAt !== "number" || typeof parsed.latest !== "string") return null;
|
|
9099
9251
|
return parsed;
|
|
@@ -9104,8 +9256,8 @@ function readCache() {
|
|
|
9104
9256
|
function writeCache(cache) {
|
|
9105
9257
|
try {
|
|
9106
9258
|
const file = cachePath();
|
|
9107
|
-
|
|
9108
|
-
|
|
9259
|
+
fs11.mkdirSync(path18.dirname(file), { recursive: true });
|
|
9260
|
+
fs11.writeFileSync(file, JSON.stringify(cache));
|
|
9109
9261
|
} catch {
|
|
9110
9262
|
}
|
|
9111
9263
|
}
|
|
@@ -9124,7 +9276,7 @@ function compareSemver(a, b) {
|
|
|
9124
9276
|
}
|
|
9125
9277
|
function fetchLatest() {
|
|
9126
9278
|
return new Promise((resolve2) => {
|
|
9127
|
-
const req =
|
|
9279
|
+
const req = https5.get(
|
|
9128
9280
|
REGISTRY_URL,
|
|
9129
9281
|
{ headers: { Accept: "application/json" }, timeout: REQUEST_TIMEOUT_MS },
|
|
9130
9282
|
(res) => {
|
|
@@ -9176,7 +9328,7 @@ function checkForUpdates() {
|
|
|
9176
9328
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
9177
9329
|
if (process.env.CI) return;
|
|
9178
9330
|
if (!process.stdout.isTTY) return;
|
|
9179
|
-
const current = true ? "2.
|
|
9331
|
+
const current = true ? "2.5.0" : null;
|
|
9180
9332
|
if (!current) return;
|
|
9181
9333
|
const cache = readCache();
|
|
9182
9334
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|