codeam-cli 2.4.39 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/index.js +1196 -1069
- 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.1",
|
|
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
|
|
@@ -4496,8 +4490,14 @@ var ClaudeService = class {
|
|
|
4496
4490
|
constructor(opts) {
|
|
4497
4491
|
this.opts = opts;
|
|
4498
4492
|
this.strategyOpts = {
|
|
4499
|
-
onData:
|
|
4500
|
-
|
|
4493
|
+
onData: (d3) => {
|
|
4494
|
+
if (!this.claudeReady && d3.length > 0) {
|
|
4495
|
+
this.claudeReady = true;
|
|
4496
|
+
setTimeout(() => this.drainPending(), 250);
|
|
4497
|
+
}
|
|
4498
|
+
(opts.onData ?? (() => {
|
|
4499
|
+
}))(d3);
|
|
4500
|
+
},
|
|
4501
4501
|
onExit: opts.onExit
|
|
4502
4502
|
};
|
|
4503
4503
|
}
|
|
@@ -4507,6 +4507,30 @@ var ClaudeService = class {
|
|
|
4507
4507
|
// Methods called before spawn() (e.g. early kill/SIGINT) no-op safely.
|
|
4508
4508
|
strategy = null;
|
|
4509
4509
|
strategyOpts;
|
|
4510
|
+
/**
|
|
4511
|
+
* Set once the PTY emits its FIRST batch of output — proxy for
|
|
4512
|
+
* "Claude has rendered its input box and is ready to read keystrokes."
|
|
4513
|
+
* Before this, remote `sendCommand`s are buffered (`pendingInputs`)
|
|
4514
|
+
* and replayed in order on first data. Without this guard, the very
|
|
4515
|
+
* first prompt right after `codeam pair` on Windows lands while
|
|
4516
|
+
* Claude's React Ink tree is still mounting — the input bytes are
|
|
4517
|
+
* accepted by the PTY but never make it to the input field, and
|
|
4518
|
+
* the prompt silently vanishes.
|
|
4519
|
+
*/
|
|
4520
|
+
claudeReady = false;
|
|
4521
|
+
pendingInputs = [];
|
|
4522
|
+
drainPending() {
|
|
4523
|
+
if (!this.strategy || this.pendingInputs.length === 0) return;
|
|
4524
|
+
const s = this.strategy;
|
|
4525
|
+
log.trace("claude", `drain pending=${this.pendingInputs.length}`);
|
|
4526
|
+
let offset = 0;
|
|
4527
|
+
for (const text of this.pendingInputs) {
|
|
4528
|
+
setTimeout(() => s.write(text), offset);
|
|
4529
|
+
setTimeout(() => s.write("\r"), offset + 50);
|
|
4530
|
+
offset += 200;
|
|
4531
|
+
}
|
|
4532
|
+
this.pendingInputs.length = 0;
|
|
4533
|
+
}
|
|
4510
4534
|
async spawn() {
|
|
4511
4535
|
let launch = buildClaudeLaunch();
|
|
4512
4536
|
if (!launch) {
|
|
@@ -4575,6 +4599,11 @@ var ClaudeService = class {
|
|
|
4575
4599
|
log.trace("claude", "sendCommand dropped (no strategy)");
|
|
4576
4600
|
return;
|
|
4577
4601
|
}
|
|
4602
|
+
if (!this.claudeReady) {
|
|
4603
|
+
log.trace("claude", `sendCommand buffered (not ready) text=${text.length}B`);
|
|
4604
|
+
this.pendingInputs.push(text);
|
|
4605
|
+
return;
|
|
4606
|
+
}
|
|
4578
4607
|
const s = this.strategy;
|
|
4579
4608
|
log.trace("claude", `sendCommand text=${text.length}B`);
|
|
4580
4609
|
s.write(text);
|
|
@@ -4645,10 +4674,6 @@ var ClaudeService = class {
|
|
|
4645
4674
|
}
|
|
4646
4675
|
};
|
|
4647
4676
|
|
|
4648
|
-
// src/services/output.service.ts
|
|
4649
|
-
var https2 = __toESM(require("https"));
|
|
4650
|
-
var http2 = __toESM(require("http"));
|
|
4651
|
-
|
|
4652
4677
|
// ../../packages/shared/src/protocol/parseChrome.ts
|
|
4653
4678
|
var SPINNER_RE = /^(?:[✳✢✶✻✽✴✷✸✹⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏◐◑◒◓▁▂▃▄▅▆▇█]|🔴|🟠|🟡|🟢|🔵|🟣|🟤|⚫|⚪|🌀|💭|✨)\s/u;
|
|
4654
4679
|
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 +5044,358 @@ function getContextWindow(model) {
|
|
|
5019
5044
|
return DEFAULT_CONTEXT_WINDOW;
|
|
5020
5045
|
}
|
|
5021
5046
|
|
|
5047
|
+
// src/services/output/chrome-tracker.ts
|
|
5048
|
+
var ChromeStepTracker = class {
|
|
5049
|
+
history = [];
|
|
5050
|
+
sentCount = 0;
|
|
5051
|
+
reset() {
|
|
5052
|
+
this.history = [];
|
|
5053
|
+
this.sentCount = 0;
|
|
5054
|
+
}
|
|
5055
|
+
/** Parse the rendered lines, append unseen steps to the cumulative history. */
|
|
5056
|
+
ingest(lines) {
|
|
5057
|
+
const visible = lines.filter((l) => isChromeLine(l)).map((l) => parseChromeLine(l)).filter((s) => s !== null);
|
|
5058
|
+
if (visible.length === 0) return;
|
|
5059
|
+
for (const step of visible) {
|
|
5060
|
+
const exists = this.history.some(
|
|
5061
|
+
(s) => s.tool === step.tool && s.label === step.label
|
|
5062
|
+
);
|
|
5063
|
+
if (!exists) this.history.push(step);
|
|
5064
|
+
}
|
|
5065
|
+
}
|
|
5066
|
+
/**
|
|
5067
|
+
* Returns the steps that have NOT yet been shipped on the wire,
|
|
5068
|
+
* marking them as shipped. Empty array means nothing new since
|
|
5069
|
+
* the last call. Caller forwards this as the chunk's
|
|
5070
|
+
* `appendSteps` payload.
|
|
5071
|
+
*/
|
|
5072
|
+
consumeDelta() {
|
|
5073
|
+
if (this.history.length === this.sentCount) return [];
|
|
5074
|
+
const delta = this.history.slice(this.sentCount);
|
|
5075
|
+
this.sentCount = this.history.length;
|
|
5076
|
+
return delta;
|
|
5077
|
+
}
|
|
5078
|
+
/** Snapshot of the cumulative unique-step history (debug + tests). */
|
|
5079
|
+
get cumulativeHistory() {
|
|
5080
|
+
return this.history;
|
|
5081
|
+
}
|
|
5082
|
+
};
|
|
5083
|
+
|
|
5084
|
+
// src/services/output/chunk-emitter.ts
|
|
5085
|
+
var https3 = __toESM(require("https"));
|
|
5086
|
+
var http3 = __toESM(require("http"));
|
|
5087
|
+
var API_BASE3 = process.env.CODEAM_API_URL ?? "https://codeagent-mobile-api.vercel.app";
|
|
5088
|
+
var ChunkEmitter = class {
|
|
5089
|
+
constructor(opts) {
|
|
5090
|
+
this.opts = opts;
|
|
5091
|
+
this.headers = {
|
|
5092
|
+
"Content-Type": "application/json",
|
|
5093
|
+
// Tell the backend which wire-format version we speak so
|
|
5094
|
+
// it can route legacy translations / 426 us when we're
|
|
5095
|
+
// too far behind. Bumped to 2.0.0 with the discriminated-
|
|
5096
|
+
// chunk + delta-chrome refactor in this release.
|
|
5097
|
+
"X-Codeam-Protocol-Version": "2.0.0"
|
|
5098
|
+
};
|
|
5099
|
+
if (opts.pluginAuthToken) {
|
|
5100
|
+
this.headers["X-Plugin-Auth-Token"] = opts.pluginAuthToken;
|
|
5101
|
+
}
|
|
5102
|
+
}
|
|
5103
|
+
opts;
|
|
5104
|
+
url = `${API_BASE3}/api/commands/output`;
|
|
5105
|
+
headers;
|
|
5106
|
+
/**
|
|
5107
|
+
* Send a chunk. `body` is the chunk fields minus `sessionId` /
|
|
5108
|
+
* `pluginId` — the emitter splices those in. `critical = true`
|
|
5109
|
+
* triggers up to 3 retries with linear backoff (200/400/600 ms);
|
|
5110
|
+
* non-critical sends are best-effort (a transient miss gets
|
|
5111
|
+
* superseded by the next tick's emission).
|
|
5112
|
+
*/
|
|
5113
|
+
async send(body, opts = {}) {
|
|
5114
|
+
const payload = JSON.stringify({
|
|
5115
|
+
sessionId: this.opts.sessionId,
|
|
5116
|
+
pluginId: this.opts.pluginId,
|
|
5117
|
+
...body
|
|
5118
|
+
});
|
|
5119
|
+
const maxRetries = opts.critical ? 3 : 0;
|
|
5120
|
+
log.trace(
|
|
5121
|
+
"chunkEmitter",
|
|
5122
|
+
`send type=${body.type ?? "(clear)"} bytes=${payload.length}`
|
|
5123
|
+
);
|
|
5124
|
+
return new Promise((resolve2) => {
|
|
5125
|
+
const attempt = (attemptsLeft) => {
|
|
5126
|
+
_transport2.post(this.url, this.headers, payload).then(({ statusCode, body: resBody }) => {
|
|
5127
|
+
if (statusCode === 410 || statusCode === 404 && /SESSION_NOT_FOUND|SESSION_GONE/.test(resBody)) {
|
|
5128
|
+
process.stderr.write("[codeam] session was deleted/disconnected \u2014 stopping output stream.\n");
|
|
5129
|
+
resolve2({ dead: true });
|
|
5130
|
+
return;
|
|
5131
|
+
}
|
|
5132
|
+
if (statusCode >= 400) {
|
|
5133
|
+
process.stderr.write(`[codeam] output API error ${statusCode}: ${resBody}
|
|
5134
|
+
`);
|
|
5135
|
+
}
|
|
5136
|
+
log.trace("chunkEmitter", `status=${statusCode}`);
|
|
5137
|
+
resolve2({ dead: false });
|
|
5138
|
+
}).catch((err) => {
|
|
5139
|
+
log.trace(
|
|
5140
|
+
"chunkEmitter",
|
|
5141
|
+
`error retries-left=${attemptsLeft}`,
|
|
5142
|
+
err
|
|
5143
|
+
);
|
|
5144
|
+
if (attemptsLeft > 0) {
|
|
5145
|
+
const delay = 200 * (maxRetries - attemptsLeft + 1);
|
|
5146
|
+
setTimeout(() => attempt(attemptsLeft - 1), delay);
|
|
5147
|
+
} else {
|
|
5148
|
+
resolve2({ dead: false });
|
|
5149
|
+
}
|
|
5150
|
+
});
|
|
5151
|
+
};
|
|
5152
|
+
attempt(maxRetries);
|
|
5153
|
+
});
|
|
5154
|
+
}
|
|
5155
|
+
};
|
|
5156
|
+
var _transport2 = {
|
|
5157
|
+
post: _post
|
|
5158
|
+
};
|
|
5159
|
+
function _post(url, headers, payload) {
|
|
5160
|
+
return new Promise((resolve2, reject) => {
|
|
5161
|
+
let settled = false;
|
|
5162
|
+
const u2 = new URL(url);
|
|
5163
|
+
const transport = u2.protocol === "https:" ? https3 : http3;
|
|
5164
|
+
const req = transport.request(
|
|
5165
|
+
{
|
|
5166
|
+
hostname: u2.hostname,
|
|
5167
|
+
port: u2.port || (u2.protocol === "https:" ? 443 : 80),
|
|
5168
|
+
path: u2.pathname,
|
|
5169
|
+
method: "POST",
|
|
5170
|
+
headers: {
|
|
5171
|
+
...headers,
|
|
5172
|
+
"Content-Length": Buffer.byteLength(payload)
|
|
5173
|
+
},
|
|
5174
|
+
timeout: 8e3
|
|
5175
|
+
},
|
|
5176
|
+
(res) => {
|
|
5177
|
+
let resData = "";
|
|
5178
|
+
res.on("data", (c2) => {
|
|
5179
|
+
resData += c2.toString();
|
|
5180
|
+
});
|
|
5181
|
+
res.on("end", () => {
|
|
5182
|
+
if (settled) return;
|
|
5183
|
+
settled = true;
|
|
5184
|
+
resolve2({ statusCode: res.statusCode ?? 0, body: resData });
|
|
5185
|
+
});
|
|
5186
|
+
}
|
|
5187
|
+
);
|
|
5188
|
+
req.on("error", (err) => {
|
|
5189
|
+
if (settled) return;
|
|
5190
|
+
settled = true;
|
|
5191
|
+
reject(err);
|
|
5192
|
+
});
|
|
5193
|
+
req.on("timeout", () => {
|
|
5194
|
+
req.destroy();
|
|
5195
|
+
});
|
|
5196
|
+
req.write(payload);
|
|
5197
|
+
req.end();
|
|
5198
|
+
});
|
|
5199
|
+
}
|
|
5200
|
+
|
|
5201
|
+
// src/services/output/pty-buffer.ts
|
|
5202
|
+
var PtyBuffer = class {
|
|
5203
|
+
raw = "";
|
|
5204
|
+
active = false;
|
|
5205
|
+
lastPushAt = 0;
|
|
5206
|
+
terminalInputPending = false;
|
|
5207
|
+
/** Whether to absorb pushes (`true`) or only watch for terminal input (`false`). */
|
|
5208
|
+
get isActive() {
|
|
5209
|
+
return this.active;
|
|
5210
|
+
}
|
|
5211
|
+
/** Bytes accumulated since the last reset. */
|
|
5212
|
+
get content() {
|
|
5213
|
+
return this.raw;
|
|
5214
|
+
}
|
|
5215
|
+
/** Wall-clock of the most recent printable push (`0` if none yet this turn). */
|
|
5216
|
+
get lastPushTime() {
|
|
5217
|
+
return this.lastPushAt;
|
|
5218
|
+
}
|
|
5219
|
+
/** Length of the accumulated buffer in raw bytes (debug + tests). */
|
|
5220
|
+
get size() {
|
|
5221
|
+
return this.raw.length;
|
|
5222
|
+
}
|
|
5223
|
+
activate() {
|
|
5224
|
+
this.active = true;
|
|
5225
|
+
this.raw = "";
|
|
5226
|
+
this.lastPushAt = 0;
|
|
5227
|
+
this.terminalInputPending = false;
|
|
5228
|
+
}
|
|
5229
|
+
deactivate() {
|
|
5230
|
+
this.active = false;
|
|
5231
|
+
}
|
|
5232
|
+
reset() {
|
|
5233
|
+
this.raw = "";
|
|
5234
|
+
this.lastPushAt = 0;
|
|
5235
|
+
}
|
|
5236
|
+
/**
|
|
5237
|
+
* Ingest a raw PTY frame. Returns whether the buffer was active
|
|
5238
|
+
* at the time (caller cares because rendering only matters for
|
|
5239
|
+
* active frames) and whether this push triggered the
|
|
5240
|
+
* terminal-initiated-turn signal.
|
|
5241
|
+
*/
|
|
5242
|
+
push(raw) {
|
|
5243
|
+
if (!this.active) {
|
|
5244
|
+
let terminalInputDetected = false;
|
|
5245
|
+
if (!this.terminalInputPending && hasPrintable(raw)) {
|
|
5246
|
+
this.terminalInputPending = true;
|
|
5247
|
+
terminalInputDetected = true;
|
|
5248
|
+
}
|
|
5249
|
+
return { active: false, terminalInputDetected };
|
|
5250
|
+
}
|
|
5251
|
+
this.raw += raw;
|
|
5252
|
+
if (hasPrintable(raw)) this.lastPushAt = Date.now();
|
|
5253
|
+
return { active: true, terminalInputDetected: false };
|
|
5254
|
+
}
|
|
5255
|
+
};
|
|
5256
|
+
function hasPrintable(raw) {
|
|
5257
|
+
const stripped = raw.replace(/\x1B\[[^@-~]*[@-~]/g, "").replace(/[\x00-\x1F\x7F]/g, "");
|
|
5258
|
+
return stripped.trim().length > 0;
|
|
5259
|
+
}
|
|
5260
|
+
|
|
5261
|
+
// src/services/output/turn-renderer.ts
|
|
5262
|
+
function renderLines(buffer) {
|
|
5263
|
+
return renderToLines(buffer);
|
|
5264
|
+
}
|
|
5265
|
+
function detectAnySelector(lines) {
|
|
5266
|
+
return detectSelector(lines) ?? detectListSelector(lines);
|
|
5267
|
+
}
|
|
5268
|
+
function extractContent(lines) {
|
|
5269
|
+
return filterChrome(lines).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
5270
|
+
}
|
|
5271
|
+
|
|
5022
5272
|
// src/services/output.service.ts
|
|
5023
|
-
var API_BASE4 = process.env.CODEAM_API_URL ?? "https://codeagent-mobile-api.vercel.app";
|
|
5024
5273
|
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 = "";
|
|
5274
|
+
pty = new PtyBuffer();
|
|
5275
|
+
steps = new ChromeStepTracker();
|
|
5276
|
+
emitter;
|
|
5038
5277
|
lastSentContent = "";
|
|
5039
|
-
lastSentChromeStepsJson = "";
|
|
5040
|
-
chromeStepsHistory = [];
|
|
5041
5278
|
pollTimer = null;
|
|
5042
5279
|
startTime = 0;
|
|
5043
|
-
active = false;
|
|
5044
5280
|
terminalTurnPending = false;
|
|
5045
|
-
lastPushTime = 0;
|
|
5046
5281
|
onSessionIdDetected;
|
|
5047
5282
|
onRateLimitDetected;
|
|
5048
5283
|
onTurnComplete;
|
|
5049
5284
|
onTerminalTurnDetected;
|
|
5285
|
+
/** Tick cadence — every 1 s while a turn is active. */
|
|
5050
5286
|
static POLL_MS = 1e3;
|
|
5287
|
+
/** Idle threshold for "the agent's text settled, finalize the turn". */
|
|
5051
5288
|
static IDLE_MS = 3e3;
|
|
5052
|
-
/**
|
|
5289
|
+
/** Same threshold but tighter for selectors (UI is ready to interact immediately). */
|
|
5053
5290
|
static SELECTOR_IDLE_MS = 1500;
|
|
5054
5291
|
/**
|
|
5055
|
-
* Grace period before
|
|
5056
|
-
*
|
|
5057
|
-
*
|
|
5058
|
-
* receiving the input, but we give a 1.5 s margin for loaded machines).
|
|
5292
|
+
* Grace period before tick processes anything — Claude needs ~100-
|
|
5293
|
+
* 200 ms after `\r` to clear the input echo and re-render the TUI.
|
|
5294
|
+
* 1.5 s is a comfortable margin on loaded machines.
|
|
5059
5295
|
*/
|
|
5060
5296
|
static WARMUP_MS = 1500;
|
|
5061
|
-
/** Max idle with
|
|
5297
|
+
/** Max idle with chrome-only output before we stop waiting on the agent. */
|
|
5062
5298
|
static EMPTY_TIMEOUT_MS = 6e4;
|
|
5299
|
+
/** Hard turn cap — pathological no-op turns get cut after 2 minutes. */
|
|
5063
5300
|
static MAX_MS = 12e4;
|
|
5301
|
+
constructor(sessionId, pluginId, onSessionIdDetected, onRateLimitDetected, onTurnComplete, onTerminalTurnDetected, pluginAuthToken) {
|
|
5302
|
+
this.onSessionIdDetected = onSessionIdDetected;
|
|
5303
|
+
this.onRateLimitDetected = onRateLimitDetected;
|
|
5304
|
+
this.onTurnComplete = onTurnComplete;
|
|
5305
|
+
this.onTerminalTurnDetected = onTerminalTurnDetected;
|
|
5306
|
+
this.emitter = new ChunkEmitter({
|
|
5307
|
+
sessionId,
|
|
5308
|
+
pluginId,
|
|
5309
|
+
pluginAuthToken
|
|
5310
|
+
});
|
|
5311
|
+
}
|
|
5312
|
+
// ─── Turn lifecycle ──────────────────────────────────────────────
|
|
5313
|
+
/**
|
|
5314
|
+
* Begin a turn driven by a mobile-side prompt. Resets the buffer
|
|
5315
|
+
* and emits the boundary chunks (clear → new_turn) that tell
|
|
5316
|
+
* clients to wipe the prior agent reply and show "Agent is
|
|
5317
|
+
* typing…".
|
|
5318
|
+
*/
|
|
5319
|
+
newTurn() {
|
|
5320
|
+
log.trace("outputSvc", "newTurn() \u2014 activating output stream");
|
|
5321
|
+
this.beginTurn();
|
|
5322
|
+
this.send({ type: "clear" }, { critical: true }).then(() => this.send({ type: "new_turn", done: false }, { critical: true })).catch(() => {
|
|
5323
|
+
});
|
|
5324
|
+
}
|
|
5064
5325
|
/**
|
|
5065
|
-
*
|
|
5066
|
-
*
|
|
5067
|
-
*
|
|
5068
|
-
*
|
|
5326
|
+
* Begin a turn driven by the user typing locally in their
|
|
5327
|
+
* terminal. Same shape as `newTurn` but additionally sends a
|
|
5328
|
+
* `user_message` so collaborators see the prompt attributed
|
|
5329
|
+
* correctly. `userText` is the prompt text scraped from the
|
|
5330
|
+
* Claude JSONL by `historySvc.waitForNewUserMessage`.
|
|
5069
5331
|
*/
|
|
5070
5332
|
async startTerminalTurn(userText) {
|
|
5071
5333
|
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 });
|
|
5334
|
+
this.beginTurn();
|
|
5335
|
+
await this.send({ type: "clear" }, { critical: true });
|
|
5081
5336
|
if (userText) {
|
|
5082
|
-
await this.
|
|
5337
|
+
await this.send({ type: "user_message", content: userText, done: true }, { critical: true });
|
|
5083
5338
|
}
|
|
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);
|
|
5339
|
+
await this.send({ type: "new_turn", done: false }, { critical: true });
|
|
5101
5340
|
}
|
|
5102
5341
|
/**
|
|
5103
|
-
*
|
|
5104
|
-
*
|
|
5105
|
-
*
|
|
5342
|
+
* Begin a turn after a `resume_session` request. Includes the
|
|
5343
|
+
* `resumedSessionId` so the client wipes its history and
|
|
5344
|
+
* re-fetches from the JSONL via `get_conversation`.
|
|
5106
5345
|
*/
|
|
5107
5346
|
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);
|
|
5347
|
+
this.beginTurn();
|
|
5348
|
+
await this.send({ type: "clear" }, { critical: true });
|
|
5349
|
+
await this.send(
|
|
5350
|
+
{ type: "new_turn", done: false, resumedSessionId },
|
|
5351
|
+
{ critical: true }
|
|
5352
|
+
);
|
|
5119
5353
|
}
|
|
5354
|
+
// ─── Pump ────────────────────────────────────────────────────────
|
|
5120
5355
|
push(raw) {
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
log.trace("outputSvc", `terminal-turn detected (idle, ${raw.length}B)`);
|
|
5127
|
-
this.onTerminalTurnDetected?.();
|
|
5128
|
-
}
|
|
5356
|
+
const result = this.pty.push(raw);
|
|
5357
|
+
if (!result.active) {
|
|
5358
|
+
if (result.terminalInputDetected && !this.terminalTurnPending) {
|
|
5359
|
+
this.terminalTurnPending = true;
|
|
5360
|
+
this.onTerminalTurnDetected?.();
|
|
5129
5361
|
}
|
|
5130
5362
|
log.trace("outputSvc", `push dropped (inactive, ${raw.length}B)`);
|
|
5131
5363
|
return;
|
|
5132
5364
|
}
|
|
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
5365
|
log.trace(
|
|
5141
5366
|
"outputSvc",
|
|
5142
|
-
`push +${raw.length}B (buf=${this.
|
|
5367
|
+
`push +${raw.length}B (buf=${this.pty.size}B)`
|
|
5143
5368
|
);
|
|
5369
|
+
this.tryExtractSessionId(raw);
|
|
5370
|
+
this.tryDetectRateLimit(raw);
|
|
5144
5371
|
}
|
|
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
|
-
}
|
|
5372
|
+
dispose() {
|
|
5373
|
+
this.stopPoll();
|
|
5374
|
+
this.pty.deactivate();
|
|
5160
5375
|
}
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5376
|
+
// ─── Internals ───────────────────────────────────────────────────
|
|
5377
|
+
beginTurn() {
|
|
5378
|
+
this.stopPoll();
|
|
5379
|
+
this.pty.activate();
|
|
5380
|
+
this.steps.reset();
|
|
5381
|
+
this.lastSentContent = "";
|
|
5382
|
+
this.startTime = Date.now();
|
|
5383
|
+
this.pollTimer = setInterval(() => this.tick(), _OutputService.POLL_MS);
|
|
5384
|
+
}
|
|
5385
|
+
async send(body, opts = {}) {
|
|
5386
|
+
const outcome = await this.emitter.send(body, opts);
|
|
5387
|
+
if (outcome.dead && this.pty.isActive) {
|
|
5388
|
+
this.dispose();
|
|
5166
5389
|
}
|
|
5167
5390
|
}
|
|
5168
|
-
|
|
5169
|
-
this.
|
|
5170
|
-
|
|
5391
|
+
stopPoll() {
|
|
5392
|
+
if (this.pollTimer) {
|
|
5393
|
+
clearInterval(this.pollTimer);
|
|
5394
|
+
this.pollTimer = null;
|
|
5395
|
+
}
|
|
5171
5396
|
}
|
|
5172
5397
|
tick() {
|
|
5173
|
-
if (!this.
|
|
5398
|
+
if (!this.pty.isActive) return;
|
|
5174
5399
|
const now = Date.now();
|
|
5175
5400
|
const elapsed = now - this.startTime;
|
|
5176
5401
|
if (elapsed >= _OutputService.MAX_MS) {
|
|
@@ -5178,36 +5403,51 @@ var OutputService = class _OutputService {
|
|
|
5178
5403
|
return;
|
|
5179
5404
|
}
|
|
5180
5405
|
if (elapsed < _OutputService.WARMUP_MS) return;
|
|
5181
|
-
const lines =
|
|
5182
|
-
this.
|
|
5183
|
-
const
|
|
5406
|
+
const lines = renderLines(this.pty.content);
|
|
5407
|
+
this.steps.ingest(lines);
|
|
5408
|
+
const stepsDelta = this.steps.consumeDelta();
|
|
5409
|
+
if (stepsDelta.length > 0) {
|
|
5410
|
+
this.send({ type: "chrome_steps", appendSteps: stepsDelta }).catch(() => {
|
|
5411
|
+
});
|
|
5412
|
+
}
|
|
5413
|
+
const selector = detectAnySelector(lines);
|
|
5184
5414
|
if (selector) {
|
|
5185
|
-
const idleMs2 = this.lastPushTime > 0 ? now - this.lastPushTime : elapsed;
|
|
5415
|
+
const idleMs2 = this.pty.lastPushTime > 0 ? now - this.pty.lastPushTime : elapsed;
|
|
5186
5416
|
log.trace(
|
|
5187
5417
|
"outputSvc",
|
|
5188
5418
|
`tick selector found (idleMs=${idleMs2}, options=${selector.options.length})`
|
|
5189
5419
|
);
|
|
5190
5420
|
if (idleMs2 >= _OutputService.SELECTOR_IDLE_MS) {
|
|
5191
5421
|
this.stopPoll();
|
|
5192
|
-
this.
|
|
5193
|
-
this.
|
|
5422
|
+
this.pty.deactivate();
|
|
5423
|
+
this.send(
|
|
5424
|
+
{
|
|
5425
|
+
type: "select_prompt",
|
|
5426
|
+
content: selector.question,
|
|
5427
|
+
options: selector.options,
|
|
5428
|
+
optionDescriptions: selector.optionDescriptions,
|
|
5429
|
+
currentIndex: selector.currentIndex,
|
|
5430
|
+
done: true
|
|
5431
|
+
},
|
|
5432
|
+
{ critical: true }
|
|
5433
|
+
).catch(() => {
|
|
5194
5434
|
});
|
|
5195
5435
|
}
|
|
5196
5436
|
return;
|
|
5197
5437
|
}
|
|
5198
|
-
const content =
|
|
5438
|
+
const content = extractContent(lines);
|
|
5199
5439
|
if (!content) {
|
|
5200
5440
|
log.trace(
|
|
5201
5441
|
"outputSvc",
|
|
5202
|
-
`tick empty content (raw=${this.
|
|
5442
|
+
`tick empty content (raw=${this.pty.size}B lines=${lines.length} elapsed=${elapsed}ms)`
|
|
5203
5443
|
);
|
|
5204
5444
|
if (elapsed >= _OutputService.EMPTY_TIMEOUT_MS) this.finalize();
|
|
5205
5445
|
return;
|
|
5206
5446
|
}
|
|
5207
|
-
const idleMs = this.lastPushTime > 0 ? now - this.lastPushTime : elapsed;
|
|
5447
|
+
const idleMs = this.pty.lastPushTime > 0 ? now - this.pty.lastPushTime : elapsed;
|
|
5208
5448
|
log.trace(
|
|
5209
5449
|
"outputSvc",
|
|
5210
|
-
`tick content (raw=${this.
|
|
5450
|
+
`tick content (raw=${this.pty.size}B lines=${lines.length} content=${content.length} idleMs=${idleMs})`
|
|
5211
5451
|
);
|
|
5212
5452
|
if (idleMs >= _OutputService.IDLE_MS) {
|
|
5213
5453
|
this.finalize();
|
|
@@ -5215,161 +5455,78 @@ var OutputService = class _OutputService {
|
|
|
5215
5455
|
}
|
|
5216
5456
|
if (content !== this.lastSentContent) {
|
|
5217
5457
|
this.lastSentContent = content;
|
|
5218
|
-
this.
|
|
5458
|
+
this.send({ type: "text", content, done: false }).catch(() => {
|
|
5219
5459
|
});
|
|
5220
5460
|
}
|
|
5221
5461
|
}
|
|
5222
5462
|
finalize() {
|
|
5223
|
-
const lines =
|
|
5224
|
-
this.
|
|
5225
|
-
const
|
|
5463
|
+
const lines = renderLines(this.pty.content);
|
|
5464
|
+
this.steps.ingest(lines);
|
|
5465
|
+
const stepsDelta = this.steps.consumeDelta();
|
|
5466
|
+
if (stepsDelta.length > 0) {
|
|
5467
|
+
this.send({ type: "chrome_steps", appendSteps: stepsDelta }).catch(() => {
|
|
5468
|
+
});
|
|
5469
|
+
}
|
|
5470
|
+
const selector = detectAnySelector(lines);
|
|
5226
5471
|
this.stopPoll();
|
|
5227
|
-
this.
|
|
5472
|
+
this.pty.deactivate();
|
|
5228
5473
|
if (selector) {
|
|
5229
|
-
this.
|
|
5474
|
+
this.send(
|
|
5475
|
+
{
|
|
5476
|
+
type: "select_prompt",
|
|
5477
|
+
content: selector.question,
|
|
5478
|
+
options: selector.options,
|
|
5479
|
+
optionDescriptions: selector.optionDescriptions,
|
|
5480
|
+
currentIndex: selector.currentIndex,
|
|
5481
|
+
done: true
|
|
5482
|
+
},
|
|
5483
|
+
{ critical: true }
|
|
5484
|
+
).catch(() => {
|
|
5230
5485
|
});
|
|
5231
5486
|
} else {
|
|
5232
|
-
const content =
|
|
5233
|
-
this.
|
|
5487
|
+
const content = extractContent(lines);
|
|
5488
|
+
this.send(
|
|
5489
|
+
{ type: "text", content, done: true },
|
|
5490
|
+
{ critical: true }
|
|
5491
|
+
).catch(() => {
|
|
5234
5492
|
});
|
|
5235
5493
|
this.onTurnComplete?.();
|
|
5236
5494
|
}
|
|
5237
5495
|
}
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
for (const
|
|
5249
|
-
const
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
this.chromeStepsHistory.push(step);
|
|
5254
|
-
changed = true;
|
|
5496
|
+
// ─── Side-channel observation (session id + rate limit) ──────────
|
|
5497
|
+
tryExtractSessionId(text) {
|
|
5498
|
+
if (!this.onSessionIdDetected) return;
|
|
5499
|
+
const printable = text.replace(/\x1B\[[^@-~]*[@-~]/g, "").replace(/[\x00-\x1F\x7F]/g, "");
|
|
5500
|
+
const patterns = [
|
|
5501
|
+
/Resuming session[:\s]+([a-f0-9-]{36})/i,
|
|
5502
|
+
/Session[:\s]+([a-f0-9-]{36})/i,
|
|
5503
|
+
/Conversation[:\s]+([a-f0-9-]{36})/i,
|
|
5504
|
+
/Session\s+ID[:\s]+([a-f0-9-]{36})/i
|
|
5505
|
+
];
|
|
5506
|
+
for (const pattern of patterns) {
|
|
5507
|
+
const match = printable.match(pattern);
|
|
5508
|
+
if (match) {
|
|
5509
|
+
this.onSessionIdDetected(match[1]);
|
|
5510
|
+
return;
|
|
5255
5511
|
}
|
|
5256
5512
|
}
|
|
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
5513
|
}
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
const
|
|
5267
|
-
const
|
|
5268
|
-
|
|
5269
|
-
|
|
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}`);
|
|
5514
|
+
tryDetectRateLimit(text) {
|
|
5515
|
+
if (!this.onRateLimitDetected) return;
|
|
5516
|
+
const printable = text.replace(/\x1B\[[^@-~]*[@-~]/g, "").replace(/[\x00-\x1F\x7F]/g, "");
|
|
5517
|
+
const match = printable.match(/hit your limit.*resets\s+(.+?)(?:\s*\(|$)/i) ?? printable.match(/rate.?limit.*resets\s+(.+?)(?:\s*\(|$)/i);
|
|
5518
|
+
if (match) {
|
|
5519
|
+
this.onRateLimitDetected(match[1].trim());
|
|
5286
5520
|
}
|
|
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
5521
|
}
|
|
5321
5522
|
};
|
|
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)
|
|
5339
|
-
},
|
|
5340
|
-
timeout: 8e3
|
|
5341
|
-
},
|
|
5342
|
-
(res) => {
|
|
5343
|
-
let resData = "";
|
|
5344
|
-
res.on("data", (c2) => {
|
|
5345
|
-
resData += c2.toString();
|
|
5346
|
-
});
|
|
5347
|
-
res.on("end", () => {
|
|
5348
|
-
if (settled) return;
|
|
5349
|
-
settled = true;
|
|
5350
|
-
resolve2({ statusCode: res.statusCode ?? 0, body: resData });
|
|
5351
|
-
});
|
|
5352
|
-
}
|
|
5353
|
-
);
|
|
5354
|
-
req.on("error", (err) => {
|
|
5355
|
-
if (settled) return;
|
|
5356
|
-
settled = true;
|
|
5357
|
-
reject(err);
|
|
5358
|
-
});
|
|
5359
|
-
req.on("timeout", () => {
|
|
5360
|
-
req.destroy();
|
|
5361
|
-
});
|
|
5362
|
-
req.write(payload);
|
|
5363
|
-
req.end();
|
|
5364
|
-
});
|
|
5365
|
-
}
|
|
5366
5523
|
|
|
5367
5524
|
// src/services/history.service.ts
|
|
5368
5525
|
var fs5 = __toESM(require("fs"));
|
|
5369
5526
|
var path8 = __toESM(require("path"));
|
|
5370
5527
|
var os6 = __toESM(require("os"));
|
|
5371
|
-
var
|
|
5372
|
-
var
|
|
5528
|
+
var https4 = __toESM(require("https"));
|
|
5529
|
+
var http4 = __toESM(require("http"));
|
|
5373
5530
|
var import_zod = require("zod");
|
|
5374
5531
|
var historyRecordSchema = import_zod.z.object({
|
|
5375
5532
|
type: import_zod.z.string().optional(),
|
|
@@ -5381,7 +5538,7 @@ var historyRecordSchema = import_zod.z.object({
|
|
|
5381
5538
|
content: import_zod.z.union([import_zod.z.string(), import_zod.z.array(import_zod.z.unknown())]).optional()
|
|
5382
5539
|
}).passthrough().optional()
|
|
5383
5540
|
}).passthrough();
|
|
5384
|
-
var
|
|
5541
|
+
var API_BASE4 = process.env.CODEAM_API_URL ?? "https://codeagent-mobile-api.vercel.app";
|
|
5385
5542
|
function encodeCwd(cwd) {
|
|
5386
5543
|
return cwd.replace(/[\\/:]/g, "-");
|
|
5387
5544
|
}
|
|
@@ -5454,8 +5611,8 @@ function parseJsonl(filePath) {
|
|
|
5454
5611
|
function post(endpoint, body) {
|
|
5455
5612
|
return new Promise((resolve2) => {
|
|
5456
5613
|
const payload = JSON.stringify(body);
|
|
5457
|
-
const u2 = new URL(`${
|
|
5458
|
-
const transport = u2.protocol === "https:" ?
|
|
5614
|
+
const u2 = new URL(`${API_BASE4}${endpoint}`);
|
|
5615
|
+
const transport = u2.protocol === "https:" ? https4 : http4;
|
|
5459
5616
|
const req = transport.request(
|
|
5460
5617
|
{
|
|
5461
5618
|
hostname: u2.hostname,
|
|
@@ -5834,6 +5991,161 @@ var HistoryService = class {
|
|
|
5834
5991
|
}
|
|
5835
5992
|
};
|
|
5836
5993
|
|
|
5994
|
+
// src/commands/start/quota-fetcher.ts
|
|
5995
|
+
var fs6 = __toESM(require("fs"));
|
|
5996
|
+
var os7 = __toESM(require("os"));
|
|
5997
|
+
var path9 = __toESM(require("path"));
|
|
5998
|
+
var import_child_process4 = require("child_process");
|
|
5999
|
+
var inProgress = false;
|
|
6000
|
+
var HELPER_SCRIPT = `import os,pty,sys,select,signal,struct,fcntl,termios,errno
|
|
6001
|
+
m,s=pty.openpty()
|
|
6002
|
+
try:
|
|
6003
|
+
fcntl.ioctl(s,termios.TIOCSWINSZ,struct.pack('HHHH',30,120,0,0))
|
|
6004
|
+
except Exception:pass
|
|
6005
|
+
pid=os.fork()
|
|
6006
|
+
if pid==0:
|
|
6007
|
+
os.close(m);os.setsid()
|
|
6008
|
+
try:fcntl.ioctl(s,termios.TIOCSCTTY,0)
|
|
6009
|
+
except Exception:pass
|
|
6010
|
+
for fd in[0,1,2]:os.dup2(s,fd)
|
|
6011
|
+
if s>2:os.close(s)
|
|
6012
|
+
os.execvp(sys.argv[1],sys.argv[1:])
|
|
6013
|
+
sys.exit(127)
|
|
6014
|
+
os.close(s)
|
|
6015
|
+
done=[False]
|
|
6016
|
+
def onchld(n,f):
|
|
6017
|
+
try:os.waitpid(pid,os.WNOHANG)
|
|
6018
|
+
except Exception:pass
|
|
6019
|
+
done[0]=True
|
|
6020
|
+
signal.signal(signal.SIGCHLD,onchld)
|
|
6021
|
+
i=sys.stdin.fileno();o=sys.stdout.fileno()
|
|
6022
|
+
while not done[0]:
|
|
6023
|
+
try:r,_,_=select.select([i,m],[],[],0.1)
|
|
6024
|
+
except OSError as e:
|
|
6025
|
+
if e.errno==errno.EINTR:continue
|
|
6026
|
+
break
|
|
6027
|
+
if i in r:
|
|
6028
|
+
try:
|
|
6029
|
+
d=os.read(i,4096)
|
|
6030
|
+
if d:os.write(m,d)
|
|
6031
|
+
else:break
|
|
6032
|
+
except OSError:break
|
|
6033
|
+
if m in r:
|
|
6034
|
+
try:
|
|
6035
|
+
d=os.read(m,4096)
|
|
6036
|
+
if d:os.write(o,d)
|
|
6037
|
+
except OSError:done[0]=True
|
|
6038
|
+
try:os.kill(pid,signal.SIGTERM)
|
|
6039
|
+
except Exception:pass
|
|
6040
|
+
try:
|
|
6041
|
+
_,st=os.waitpid(pid,0)
|
|
6042
|
+
sys.exit((st>>8)&0xFF)
|
|
6043
|
+
except Exception:sys.exit(0)
|
|
6044
|
+
`;
|
|
6045
|
+
function fetchQuotaUsage(historySvc) {
|
|
6046
|
+
if (inProgress) return;
|
|
6047
|
+
inProgress = true;
|
|
6048
|
+
const claudeCmd = findInPath("claude") ? "claude" : "claude-code";
|
|
6049
|
+
if (!claudeCmd) {
|
|
6050
|
+
inProgress = false;
|
|
6051
|
+
return;
|
|
6052
|
+
}
|
|
6053
|
+
const helperPath = path9.join(os7.tmpdir(), "codeam-quota-helper.py");
|
|
6054
|
+
fs6.writeFileSync(helperPath, HELPER_SCRIPT, { mode: 420 });
|
|
6055
|
+
const python = findInPath("python3") ?? findInPath("python");
|
|
6056
|
+
if (!python) {
|
|
6057
|
+
inProgress = false;
|
|
6058
|
+
return;
|
|
6059
|
+
}
|
|
6060
|
+
const proc = (0, import_child_process4.spawn)(python, [helperPath, claudeCmd, "--tools", ""], {
|
|
6061
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
6062
|
+
cwd: process.cwd(),
|
|
6063
|
+
env: { ...process.env, TERM: "dumb", COLUMNS: "120", LINES: "30" }
|
|
6064
|
+
});
|
|
6065
|
+
let output = "";
|
|
6066
|
+
proc.stdout?.on("data", (chunk) => {
|
|
6067
|
+
output += chunk.toString("utf8");
|
|
6068
|
+
});
|
|
6069
|
+
setTimeout(() => {
|
|
6070
|
+
proc.stdin?.write("/usage\r");
|
|
6071
|
+
setTimeout(() => {
|
|
6072
|
+
const clean = output.replace(/\x1B\[[^@-~]*[@-~]/g, "").replace(/[\x00-\x1F\x7F]/g, " ").replace(/\s+/g, " ");
|
|
6073
|
+
const weekMatch = clean.match(/(\d+)%\s*used/i) || clean.match(/(\d+)\s*%/);
|
|
6074
|
+
if (weekMatch) historySvc.setQuotaPercent(parseInt(weekMatch[1], 10));
|
|
6075
|
+
const resetMatch = clean.match(/resets\s+(.+?)(?:\s*\(|$)/im);
|
|
6076
|
+
if (resetMatch) historySvc.setRateLimitReset(resetMatch[1].trim());
|
|
6077
|
+
try {
|
|
6078
|
+
proc.kill();
|
|
6079
|
+
} catch {
|
|
6080
|
+
}
|
|
6081
|
+
try {
|
|
6082
|
+
fs6.unlinkSync(helperPath);
|
|
6083
|
+
} catch {
|
|
6084
|
+
}
|
|
6085
|
+
inProgress = false;
|
|
6086
|
+
}, 5e3);
|
|
6087
|
+
}, 8e3);
|
|
6088
|
+
proc.on("exit", () => {
|
|
6089
|
+
inProgress = false;
|
|
6090
|
+
});
|
|
6091
|
+
setTimeout(() => {
|
|
6092
|
+
try {
|
|
6093
|
+
proc.kill();
|
|
6094
|
+
} catch {
|
|
6095
|
+
}
|
|
6096
|
+
}, 2e4);
|
|
6097
|
+
}
|
|
6098
|
+
|
|
6099
|
+
// src/commands/start/keep-alive.ts
|
|
6100
|
+
var import_child_process5 = require("child_process");
|
|
6101
|
+
function buildKeepAlive(ctx) {
|
|
6102
|
+
let timer = null;
|
|
6103
|
+
async function setIdleTimeout(minutes) {
|
|
6104
|
+
if (!ctx.inCodespace || !ctx.codespaceName) return;
|
|
6105
|
+
await new Promise((resolve2) => {
|
|
6106
|
+
const proc = (0, import_child_process5.spawn)(
|
|
6107
|
+
"gh",
|
|
6108
|
+
[
|
|
6109
|
+
"api",
|
|
6110
|
+
"-X",
|
|
6111
|
+
"PATCH",
|
|
6112
|
+
`/user/codespaces/${ctx.codespaceName}`,
|
|
6113
|
+
"-F",
|
|
6114
|
+
`idle_timeout_minutes=${minutes}`
|
|
6115
|
+
],
|
|
6116
|
+
{ stdio: "ignore", detached: true }
|
|
6117
|
+
);
|
|
6118
|
+
proc.unref();
|
|
6119
|
+
proc.on("exit", () => resolve2());
|
|
6120
|
+
proc.on("error", () => resolve2());
|
|
6121
|
+
});
|
|
6122
|
+
}
|
|
6123
|
+
return {
|
|
6124
|
+
apply(enabled) {
|
|
6125
|
+
if (timer) {
|
|
6126
|
+
clearInterval(timer);
|
|
6127
|
+
timer = null;
|
|
6128
|
+
}
|
|
6129
|
+
if (!ctx.inCodespace || !ctx.codespaceName) return;
|
|
6130
|
+
if (!enabled) {
|
|
6131
|
+
void setIdleTimeout(30);
|
|
6132
|
+
return;
|
|
6133
|
+
}
|
|
6134
|
+
void setIdleTimeout(240);
|
|
6135
|
+
timer = setInterval(() => {
|
|
6136
|
+
void setIdleTimeout(240);
|
|
6137
|
+
}, 30 * 60 * 1e3);
|
|
6138
|
+
}
|
|
6139
|
+
};
|
|
6140
|
+
}
|
|
6141
|
+
|
|
6142
|
+
// src/commands/start/handlers.ts
|
|
6143
|
+
var fs9 = __toESM(require("fs"));
|
|
6144
|
+
var os8 = __toESM(require("os"));
|
|
6145
|
+
var path12 = __toESM(require("path"));
|
|
6146
|
+
var import_crypto = require("crypto");
|
|
6147
|
+
var import_child_process7 = require("child_process");
|
|
6148
|
+
|
|
5837
6149
|
// src/lib/payload.ts
|
|
5838
6150
|
var import_zod2 = require("zod");
|
|
5839
6151
|
var fileEntrySchema = import_zod2.z.object({
|
|
@@ -5869,8 +6181,8 @@ function parsePayload(schema, raw) {
|
|
|
5869
6181
|
}
|
|
5870
6182
|
|
|
5871
6183
|
// src/services/file-ops.service.ts
|
|
5872
|
-
var
|
|
5873
|
-
var
|
|
6184
|
+
var fs7 = __toESM(require("fs/promises"));
|
|
6185
|
+
var path10 = __toESM(require("path"));
|
|
5874
6186
|
var MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
5875
6187
|
var MAX_WALK_DEPTH = 6;
|
|
5876
6188
|
var MAX_VISITED_DIRS = 5e3;
|
|
@@ -5905,12 +6217,12 @@ var SUBDIR_IGNORE = /* @__PURE__ */ new Set([
|
|
|
5905
6217
|
"__pycache__"
|
|
5906
6218
|
]);
|
|
5907
6219
|
function isUnder(parent, candidate) {
|
|
5908
|
-
const rel =
|
|
5909
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
6220
|
+
const rel = path10.relative(parent, candidate);
|
|
6221
|
+
return rel === "" || !rel.startsWith("..") && !path10.isAbsolute(rel);
|
|
5910
6222
|
}
|
|
5911
6223
|
async function isExistingFile(absPath) {
|
|
5912
6224
|
try {
|
|
5913
|
-
const stat3 = await
|
|
6225
|
+
const stat3 = await fs7.stat(absPath);
|
|
5914
6226
|
return stat3.isFile();
|
|
5915
6227
|
} catch {
|
|
5916
6228
|
return false;
|
|
@@ -5923,13 +6235,13 @@ async function walkForSuffix(dir, needleVariants, depth, ctx) {
|
|
|
5923
6235
|
ctx.visited++;
|
|
5924
6236
|
let entries = [];
|
|
5925
6237
|
try {
|
|
5926
|
-
entries = await
|
|
6238
|
+
entries = await fs7.readdir(dir, { withFileTypes: true });
|
|
5927
6239
|
} catch {
|
|
5928
6240
|
return;
|
|
5929
6241
|
}
|
|
5930
6242
|
for (const e of entries) {
|
|
5931
6243
|
if (!e.isFile()) continue;
|
|
5932
|
-
const full =
|
|
6244
|
+
const full = path10.join(dir, e.name);
|
|
5933
6245
|
if (needleVariants.some((needle) => full.endsWith(needle))) {
|
|
5934
6246
|
ctx.matches.push(full);
|
|
5935
6247
|
if (ctx.matches.length >= ctx.cap) return;
|
|
@@ -5939,21 +6251,21 @@ async function walkForSuffix(dir, needleVariants, depth, ctx) {
|
|
|
5939
6251
|
if (!e.isDirectory()) continue;
|
|
5940
6252
|
if (SUBDIR_IGNORE.has(e.name)) continue;
|
|
5941
6253
|
if (e.name.startsWith(".") && SUBDIR_IGNORE.has(e.name)) continue;
|
|
5942
|
-
await walkForSuffix(
|
|
6254
|
+
await walkForSuffix(path10.join(dir, e.name), needleVariants, depth + 1, ctx);
|
|
5943
6255
|
if (ctx.matches.length >= ctx.cap) return;
|
|
5944
6256
|
}
|
|
5945
6257
|
}
|
|
5946
6258
|
async function findFile(rawPath) {
|
|
5947
6259
|
const cwd = process.cwd();
|
|
5948
|
-
if (
|
|
5949
|
-
const abs =
|
|
6260
|
+
if (path10.isAbsolute(rawPath)) {
|
|
6261
|
+
const abs = path10.normalize(rawPath);
|
|
5950
6262
|
if (isUnder(cwd, abs) && await isExistingFile(abs)) return abs;
|
|
5951
6263
|
}
|
|
5952
|
-
const direct =
|
|
6264
|
+
const direct = path10.resolve(cwd, rawPath);
|
|
5953
6265
|
if (isUnder(cwd, direct) && await isExistingFile(direct)) return direct;
|
|
5954
|
-
const normalized =
|
|
6266
|
+
const normalized = path10.normalize(rawPath).replace(/^[./\\]+/, "");
|
|
5955
6267
|
const needles = [
|
|
5956
|
-
`${
|
|
6268
|
+
`${path10.sep}${normalized}`,
|
|
5957
6269
|
`/${normalized}`
|
|
5958
6270
|
].filter((v, i, a) => a.indexOf(v) === i);
|
|
5959
6271
|
const ctx = { visited: 0, matches: [], cap: 16 };
|
|
@@ -5967,7 +6279,7 @@ async function findWriteTarget(rawPath) {
|
|
|
5967
6279
|
const found = await findFile(rawPath);
|
|
5968
6280
|
if (found) return found;
|
|
5969
6281
|
const cwd = process.cwd();
|
|
5970
|
-
const fallback =
|
|
6282
|
+
const fallback = path10.isAbsolute(rawPath) ? path10.normalize(rawPath) : path10.resolve(cwd, rawPath);
|
|
5971
6283
|
if (!isUnder(cwd, fallback)) return null;
|
|
5972
6284
|
return fallback;
|
|
5973
6285
|
}
|
|
@@ -5984,11 +6296,11 @@ async function readProjectFile(rawPath) {
|
|
|
5984
6296
|
if (!abs) {
|
|
5985
6297
|
return { error: `File not found in the project tree: ${rawPath}` };
|
|
5986
6298
|
}
|
|
5987
|
-
const stat3 = await
|
|
6299
|
+
const stat3 = await fs7.stat(abs);
|
|
5988
6300
|
if (stat3.size > MAX_FILE_BYTES) {
|
|
5989
6301
|
return { error: `File too large (${(stat3.size / 1024 / 1024).toFixed(1)} MB > ${MAX_FILE_BYTES / 1024 / 1024} MB).` };
|
|
5990
6302
|
}
|
|
5991
|
-
const buf = await
|
|
6303
|
+
const buf = await fs7.readFile(abs);
|
|
5992
6304
|
if (looksBinary(buf)) {
|
|
5993
6305
|
return { error: "Binary file \u2014 refusing to open in a code editor." };
|
|
5994
6306
|
}
|
|
@@ -6007,8 +6319,8 @@ async function writeProjectFile(rawPath, content) {
|
|
|
6007
6319
|
if (Buffer.byteLength(content, "utf-8") > MAX_FILE_BYTES) {
|
|
6008
6320
|
return { error: "Content too large." };
|
|
6009
6321
|
}
|
|
6010
|
-
await
|
|
6011
|
-
await
|
|
6322
|
+
await fs7.mkdir(path10.dirname(abs), { recursive: true });
|
|
6323
|
+
await fs7.writeFile(abs, content, "utf-8");
|
|
6012
6324
|
return { ok: true };
|
|
6013
6325
|
} catch (e) {
|
|
6014
6326
|
const msg = e instanceof Error ? e.message : "Write failed";
|
|
@@ -6017,11 +6329,11 @@ async function writeProjectFile(rawPath, content) {
|
|
|
6017
6329
|
}
|
|
6018
6330
|
|
|
6019
6331
|
// src/services/project-ops.service.ts
|
|
6020
|
-
var
|
|
6332
|
+
var import_child_process6 = require("child_process");
|
|
6021
6333
|
var import_util = require("util");
|
|
6022
|
-
var
|
|
6023
|
-
var
|
|
6024
|
-
var execFileP = (0, import_util.promisify)(
|
|
6334
|
+
var fs8 = __toESM(require("fs/promises"));
|
|
6335
|
+
var path11 = __toESM(require("path"));
|
|
6336
|
+
var execFileP = (0, import_util.promisify)(import_child_process6.execFile);
|
|
6025
6337
|
var PROJECT_IGNORE = /* @__PURE__ */ new Set([
|
|
6026
6338
|
"node_modules",
|
|
6027
6339
|
".git",
|
|
@@ -6068,7 +6380,7 @@ async function listProjectFiles(opts = {}) {
|
|
|
6068
6380
|
}
|
|
6069
6381
|
let entries = [];
|
|
6070
6382
|
try {
|
|
6071
|
-
entries = await
|
|
6383
|
+
entries = await fs8.readdir(dir, { withFileTypes: true });
|
|
6072
6384
|
} catch {
|
|
6073
6385
|
return;
|
|
6074
6386
|
}
|
|
@@ -6078,18 +6390,18 @@ async function listProjectFiles(opts = {}) {
|
|
|
6078
6390
|
return;
|
|
6079
6391
|
}
|
|
6080
6392
|
if (PROJECT_IGNORE.has(e.name)) continue;
|
|
6081
|
-
const full =
|
|
6393
|
+
const full = path11.join(dir, e.name);
|
|
6082
6394
|
if (e.isDirectory()) {
|
|
6083
6395
|
if (depth >= 12) continue;
|
|
6084
6396
|
await walk(full, depth + 1);
|
|
6085
6397
|
} else if (e.isFile()) {
|
|
6086
|
-
const rel =
|
|
6398
|
+
const rel = path11.relative(root, full);
|
|
6087
6399
|
if (q2 && !rel.toLowerCase().includes(q2) && !e.name.toLowerCase().includes(q2)) {
|
|
6088
6400
|
continue;
|
|
6089
6401
|
}
|
|
6090
6402
|
let size = 0;
|
|
6091
6403
|
try {
|
|
6092
|
-
const st3 = await
|
|
6404
|
+
const st3 = await fs8.stat(full);
|
|
6093
6405
|
size = st3.size;
|
|
6094
6406
|
} catch {
|
|
6095
6407
|
}
|
|
@@ -6191,8 +6503,8 @@ async function gitStatus(cwd) {
|
|
|
6191
6503
|
let hasMergeInProgress = false;
|
|
6192
6504
|
try {
|
|
6193
6505
|
const gitDir = (await git(["rev-parse", "--git-dir"], root)).stdout.trim();
|
|
6194
|
-
const mergeHead =
|
|
6195
|
-
await
|
|
6506
|
+
const mergeHead = path11.isAbsolute(gitDir) ? path11.join(gitDir, "MERGE_HEAD") : path11.join(root, gitDir, "MERGE_HEAD");
|
|
6507
|
+
await fs8.access(mergeHead);
|
|
6196
6508
|
hasMergeInProgress = true;
|
|
6197
6509
|
} catch {
|
|
6198
6510
|
}
|
|
@@ -6265,15 +6577,261 @@ async function gitResolve(file, side, cwd) {
|
|
|
6265
6577
|
return { ok: true };
|
|
6266
6578
|
}
|
|
6267
6579
|
|
|
6268
|
-
// src/commands/start.ts
|
|
6580
|
+
// src/commands/start/handlers.ts
|
|
6269
6581
|
function saveFilesTemp(files) {
|
|
6270
6582
|
return files.filter(({ base64 }) => base64 && base64.length > 0).map(({ filename, base64 }) => {
|
|
6271
6583
|
const safeName = filename.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80);
|
|
6272
|
-
const tmpPath =
|
|
6273
|
-
|
|
6584
|
+
const tmpPath = path12.join(os8.tmpdir(), `codeam-${(0, import_crypto.randomUUID)()}-${safeName}`);
|
|
6585
|
+
fs9.writeFileSync(tmpPath, Buffer.from(base64, "base64"));
|
|
6274
6586
|
return tmpPath;
|
|
6275
6587
|
});
|
|
6276
6588
|
}
|
|
6589
|
+
function dispatchPrompt(ctx, prompt) {
|
|
6590
|
+
ctx.outputSvc.newTurn();
|
|
6591
|
+
ctx.claude.sendCommand(prompt);
|
|
6592
|
+
}
|
|
6593
|
+
var startTask = (ctx, _cmd, parsed) => {
|
|
6594
|
+
const { prompt, files } = parsed;
|
|
6595
|
+
const effectivePrompt = prompt ?? "";
|
|
6596
|
+
if (files && files.length > 0) {
|
|
6597
|
+
const paths = saveFilesTemp(files);
|
|
6598
|
+
const atRefs = paths.map((p2) => `@${p2}`).join(" ");
|
|
6599
|
+
ctx.outputSvc.newTurn();
|
|
6600
|
+
ctx.claude.sendCommand(`${atRefs} ${effectivePrompt}`.trim());
|
|
6601
|
+
setTimeout(() => {
|
|
6602
|
+
for (const p2 of paths) {
|
|
6603
|
+
try {
|
|
6604
|
+
fs9.unlinkSync(p2);
|
|
6605
|
+
} catch {
|
|
6606
|
+
}
|
|
6607
|
+
}
|
|
6608
|
+
}, 12e4);
|
|
6609
|
+
} else if (effectivePrompt) {
|
|
6610
|
+
dispatchPrompt(ctx, effectivePrompt);
|
|
6611
|
+
}
|
|
6612
|
+
};
|
|
6613
|
+
var provideInput = (ctx, _cmd, parsed) => {
|
|
6614
|
+
if (parsed.input) dispatchPrompt(ctx, parsed.input);
|
|
6615
|
+
};
|
|
6616
|
+
var selectOption = (ctx, _cmd, parsed) => {
|
|
6617
|
+
const index = parsed.index ?? 0;
|
|
6618
|
+
const from = parsed.from ?? 0;
|
|
6619
|
+
ctx.outputSvc.newTurn();
|
|
6620
|
+
ctx.claude.selectOption(index, from);
|
|
6621
|
+
};
|
|
6622
|
+
var escapeKey = (ctx) => {
|
|
6623
|
+
ctx.outputSvc.newTurn();
|
|
6624
|
+
ctx.claude.sendEscape();
|
|
6625
|
+
};
|
|
6626
|
+
var stopTask = (ctx) => {
|
|
6627
|
+
ctx.claude.interrupt();
|
|
6628
|
+
};
|
|
6629
|
+
var resumeSession = async (ctx, _cmd, parsed) => {
|
|
6630
|
+
const { id, auto } = parsed;
|
|
6631
|
+
if (!id) return;
|
|
6632
|
+
ctx.historySvc.setCurrentConversationId(id);
|
|
6633
|
+
await ctx.historySvc.loadConversation(id);
|
|
6634
|
+
await ctx.outputSvc.newTurnResume(id);
|
|
6635
|
+
ctx.claude.restart(id, auto ?? false);
|
|
6636
|
+
};
|
|
6637
|
+
var getContext = async (ctx, cmd) => {
|
|
6638
|
+
const usage = ctx.historySvc.getCurrentUsage();
|
|
6639
|
+
const monthlyCost = ctx.historySvc.getMonthlyEstimatedCost();
|
|
6640
|
+
const rateLimitReset = ctx.historySvc.getRateLimitReset();
|
|
6641
|
+
const quotaPercent = ctx.historySvc.getQuotaPercent();
|
|
6642
|
+
const base = usage ? { ...usage, monthlyCost } : { used: 0, total: 2e5, percent: 0, model: null, outputTokens: 0, cacheReadTokens: 0, monthlyCost, error: "No usage data found" };
|
|
6643
|
+
const result = {
|
|
6644
|
+
...base,
|
|
6645
|
+
...rateLimitReset ? { rateLimitReset } : {},
|
|
6646
|
+
...quotaPercent !== null ? { quotaPercent } : {}
|
|
6647
|
+
};
|
|
6648
|
+
await ctx.relay.sendResult(cmd.id, "completed", result);
|
|
6649
|
+
};
|
|
6650
|
+
var getConversation = async (ctx, cmd) => {
|
|
6651
|
+
const currentId = ctx.historySvc.getCurrentConversationId();
|
|
6652
|
+
if (!currentId) {
|
|
6653
|
+
await ctx.relay.sendResult(cmd.id, "completed", { conversationId: null });
|
|
6654
|
+
return;
|
|
6655
|
+
}
|
|
6656
|
+
try {
|
|
6657
|
+
await ctx.historySvc.loadConversation(currentId);
|
|
6658
|
+
await ctx.relay.sendResult(cmd.id, "completed", { conversationId: currentId });
|
|
6659
|
+
} catch {
|
|
6660
|
+
await ctx.relay.sendResult(cmd.id, "failed", {});
|
|
6661
|
+
}
|
|
6662
|
+
};
|
|
6663
|
+
var listModels = async (ctx, cmd) => {
|
|
6664
|
+
const models = [
|
|
6665
|
+
{ id: "claude-opus-4-7", label: "Claude Opus 4.7", description: "Most capable", family: "claude", vendor: "anthropic", isDefault: false },
|
|
6666
|
+
{ id: "claude-opus-4-6", label: "Claude Opus 4.6", description: "Top tier", family: "claude", vendor: "anthropic", isDefault: false },
|
|
6667
|
+
{ id: "claude-sonnet-4-6", label: "Claude Sonnet 4.6", description: "Balanced", family: "claude", vendor: "anthropic", isDefault: true },
|
|
6668
|
+
{ id: "claude-haiku-4-5-20251001", label: "Claude Haiku 4.5", description: "Fastest", family: "claude", vendor: "anthropic", isDefault: false }
|
|
6669
|
+
];
|
|
6670
|
+
await ctx.relay.sendResult(cmd.id, "completed", { models });
|
|
6671
|
+
};
|
|
6672
|
+
var setKeepAlive = async (ctx, cmd) => {
|
|
6673
|
+
const enabled = !!cmd.payload.enabled;
|
|
6674
|
+
ctx.setKeepAlive(enabled);
|
|
6675
|
+
try {
|
|
6676
|
+
await ctx.relay.sendResult(
|
|
6677
|
+
cmd.id,
|
|
6678
|
+
"success",
|
|
6679
|
+
{
|
|
6680
|
+
enabled,
|
|
6681
|
+
applied: enabled && ctx.keepAliveCtx.inCodespace,
|
|
6682
|
+
runtime: ctx.keepAliveCtx.inCodespace ? "github-codespaces" : "local"
|
|
6683
|
+
}
|
|
6684
|
+
);
|
|
6685
|
+
} catch {
|
|
6686
|
+
}
|
|
6687
|
+
};
|
|
6688
|
+
var sessionTerminated = (ctx) => {
|
|
6689
|
+
showInfo("Session was deleted from the app \u2014 exiting.");
|
|
6690
|
+
try {
|
|
6691
|
+
ctx.claude.kill();
|
|
6692
|
+
} catch {
|
|
6693
|
+
}
|
|
6694
|
+
try {
|
|
6695
|
+
const proc = (0, import_child_process7.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
|
|
6696
|
+
detached: true,
|
|
6697
|
+
stdio: "ignore"
|
|
6698
|
+
});
|
|
6699
|
+
proc.unref();
|
|
6700
|
+
} catch {
|
|
6701
|
+
}
|
|
6702
|
+
ctx.outputSvc.dispose();
|
|
6703
|
+
ctx.relay.stop();
|
|
6704
|
+
process.exit(0);
|
|
6705
|
+
};
|
|
6706
|
+
var shutdownSession = async (ctx, cmd) => {
|
|
6707
|
+
try {
|
|
6708
|
+
await ctx.relay.sendResult(cmd.id, "success", { ok: true });
|
|
6709
|
+
} catch {
|
|
6710
|
+
}
|
|
6711
|
+
try {
|
|
6712
|
+
ctx.claude.kill();
|
|
6713
|
+
} catch {
|
|
6714
|
+
}
|
|
6715
|
+
if (ctx.keepAliveCtx.inCodespace && ctx.keepAliveCtx.codespaceName) {
|
|
6716
|
+
try {
|
|
6717
|
+
const stopProc = (0, import_child_process7.spawn)(
|
|
6718
|
+
"bash",
|
|
6719
|
+
["-lc", `sleep 1; gh codespace stop -c ${JSON.stringify(ctx.keepAliveCtx.codespaceName)} >/dev/null 2>&1 || true`],
|
|
6720
|
+
{ detached: true, stdio: "ignore" }
|
|
6721
|
+
);
|
|
6722
|
+
stopProc.unref();
|
|
6723
|
+
} catch {
|
|
6724
|
+
}
|
|
6725
|
+
}
|
|
6726
|
+
try {
|
|
6727
|
+
const proc = (0, import_child_process7.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
|
|
6728
|
+
detached: true,
|
|
6729
|
+
stdio: "ignore"
|
|
6730
|
+
});
|
|
6731
|
+
proc.unref();
|
|
6732
|
+
} catch {
|
|
6733
|
+
}
|
|
6734
|
+
ctx.outputSvc.dispose();
|
|
6735
|
+
ctx.relay.stop();
|
|
6736
|
+
process.exit(0);
|
|
6737
|
+
};
|
|
6738
|
+
var readFile2 = async (ctx, cmd, parsed) => {
|
|
6739
|
+
if (!parsed.path) {
|
|
6740
|
+
await ctx.relay.sendResult(cmd.id, "failed", { error: "Missing path" });
|
|
6741
|
+
return;
|
|
6742
|
+
}
|
|
6743
|
+
const result = await readProjectFile(parsed.path);
|
|
6744
|
+
await ctx.relay.sendResult(cmd.id, "completed", result);
|
|
6745
|
+
};
|
|
6746
|
+
var writeFile2 = async (ctx, cmd, parsed) => {
|
|
6747
|
+
if (!parsed.path || typeof parsed.content !== "string") {
|
|
6748
|
+
await ctx.relay.sendResult(cmd.id, "failed", { error: "Missing path or content" });
|
|
6749
|
+
return;
|
|
6750
|
+
}
|
|
6751
|
+
const result = await writeProjectFile(parsed.path, parsed.content);
|
|
6752
|
+
await ctx.relay.sendResult(cmd.id, "completed", result);
|
|
6753
|
+
};
|
|
6754
|
+
var listFiles = async (ctx, cmd, parsed) => {
|
|
6755
|
+
const result = await listProjectFiles({ query: parsed.query });
|
|
6756
|
+
await ctx.relay.sendResult(cmd.id, "completed", result);
|
|
6757
|
+
};
|
|
6758
|
+
var gitStatusH = async (ctx, cmd) => {
|
|
6759
|
+
const result = await gitStatus();
|
|
6760
|
+
await ctx.relay.sendResult(cmd.id, "completed", result);
|
|
6761
|
+
};
|
|
6762
|
+
var gitDiffH = async (ctx, cmd, parsed) => {
|
|
6763
|
+
const result = await gitDiff(parsed.path ?? null);
|
|
6764
|
+
await ctx.relay.sendResult(cmd.id, "completed", result);
|
|
6765
|
+
};
|
|
6766
|
+
var gitDiffStagedH = async (ctx, cmd, parsed) => {
|
|
6767
|
+
const result = await gitDiffStaged(parsed.path ?? null);
|
|
6768
|
+
await ctx.relay.sendResult(cmd.id, "completed", result);
|
|
6769
|
+
};
|
|
6770
|
+
var gitLogH = async (ctx, cmd, parsed) => {
|
|
6771
|
+
const result = await gitLog(parsed.limit ?? 30);
|
|
6772
|
+
await ctx.relay.sendResult(cmd.id, "completed", result);
|
|
6773
|
+
};
|
|
6774
|
+
var gitCommitH = async (ctx, cmd, parsed) => {
|
|
6775
|
+
if (!parsed.message) {
|
|
6776
|
+
await ctx.relay.sendResult(cmd.id, "failed", { error: "Missing message" });
|
|
6777
|
+
return;
|
|
6778
|
+
}
|
|
6779
|
+
const result = await gitCommit(parsed.message, parsed.paths);
|
|
6780
|
+
await ctx.relay.sendResult(cmd.id, "completed", result);
|
|
6781
|
+
};
|
|
6782
|
+
var gitPushH = async (ctx, cmd) => {
|
|
6783
|
+
const result = await gitPush();
|
|
6784
|
+
await ctx.relay.sendResult(cmd.id, "completed", result);
|
|
6785
|
+
};
|
|
6786
|
+
var gitPullH = async (ctx, cmd) => {
|
|
6787
|
+
const result = await gitPull();
|
|
6788
|
+
await ctx.relay.sendResult(cmd.id, "completed", result);
|
|
6789
|
+
};
|
|
6790
|
+
var gitResolveH = async (ctx, cmd, parsed) => {
|
|
6791
|
+
if (!parsed.path || !parsed.side) {
|
|
6792
|
+
await ctx.relay.sendResult(cmd.id, "failed", { error: "Missing path or side" });
|
|
6793
|
+
return;
|
|
6794
|
+
}
|
|
6795
|
+
const result = await gitResolve(parsed.path, parsed.side);
|
|
6796
|
+
await ctx.relay.sendResult(cmd.id, "completed", result);
|
|
6797
|
+
};
|
|
6798
|
+
var handlers = {
|
|
6799
|
+
start_task: startTask,
|
|
6800
|
+
provide_input: provideInput,
|
|
6801
|
+
select_option: selectOption,
|
|
6802
|
+
escape_key: escapeKey,
|
|
6803
|
+
stop_task: stopTask,
|
|
6804
|
+
resume_session: resumeSession,
|
|
6805
|
+
get_context: getContext,
|
|
6806
|
+
get_conversation: getConversation,
|
|
6807
|
+
list_models: listModels,
|
|
6808
|
+
set_keep_alive: setKeepAlive,
|
|
6809
|
+
session_terminated: sessionTerminated,
|
|
6810
|
+
shutdown_session: shutdownSession,
|
|
6811
|
+
read_file: readFile2,
|
|
6812
|
+
write_file: writeFile2,
|
|
6813
|
+
list_files: listFiles,
|
|
6814
|
+
git_status: gitStatusH,
|
|
6815
|
+
git_diff: gitDiffH,
|
|
6816
|
+
git_diff_staged: gitDiffStagedH,
|
|
6817
|
+
git_log: gitLogH,
|
|
6818
|
+
git_commit: gitCommitH,
|
|
6819
|
+
git_push: gitPushH,
|
|
6820
|
+
git_pull: gitPullH,
|
|
6821
|
+
git_resolve: gitResolveH
|
|
6822
|
+
};
|
|
6823
|
+
async function dispatchCommand(ctx, cmd) {
|
|
6824
|
+
const parsed = parsePayload(startCommandSchema, cmd.payload);
|
|
6825
|
+
if (!parsed) {
|
|
6826
|
+
showInfo(`Ignoring malformed ${cmd.type} payload.`);
|
|
6827
|
+
return;
|
|
6828
|
+
}
|
|
6829
|
+
const handler = handlers[cmd.type];
|
|
6830
|
+
if (!handler) return;
|
|
6831
|
+
await handler(ctx, cmd, parsed);
|
|
6832
|
+
}
|
|
6833
|
+
|
|
6834
|
+
// src/commands/start.ts
|
|
6277
6835
|
async function start() {
|
|
6278
6836
|
showIntro();
|
|
6279
6837
|
const session = getActiveSession();
|
|
@@ -6287,434 +6845,32 @@ async function start() {
|
|
|
6287
6845
|
showInfo(`${session.userName} \xB7 ${import_picocolors2.default.cyan(session.plan)}`);
|
|
6288
6846
|
showInfo("Launching Claude Code...\n");
|
|
6289
6847
|
const cwd = process.cwd();
|
|
6290
|
-
const ws = new WebSocketService(session.id, pluginId);
|
|
6291
6848
|
const historySvc = new HistoryService(pluginId, cwd);
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
fcntl.ioctl(s,termios.TIOCSWINSZ,struct.pack('HHHH',30,120,0,0))
|
|
6305
|
-
except Exception:pass
|
|
6306
|
-
pid=os.fork()
|
|
6307
|
-
if pid==0:
|
|
6308
|
-
os.close(m);os.setsid()
|
|
6309
|
-
try:fcntl.ioctl(s,termios.TIOCSCTTY,0)
|
|
6310
|
-
except Exception:pass
|
|
6311
|
-
for fd in[0,1,2]:os.dup2(s,fd)
|
|
6312
|
-
if s>2:os.close(s)
|
|
6313
|
-
os.execvp(sys.argv[1],sys.argv[1:])
|
|
6314
|
-
sys.exit(127)
|
|
6315
|
-
os.close(s)
|
|
6316
|
-
done=[False]
|
|
6317
|
-
def onchld(n,f):
|
|
6318
|
-
try:os.waitpid(pid,os.WNOHANG)
|
|
6319
|
-
except Exception:pass
|
|
6320
|
-
done[0]=True
|
|
6321
|
-
signal.signal(signal.SIGCHLD,onchld)
|
|
6322
|
-
i=sys.stdin.fileno();o=sys.stdout.fileno()
|
|
6323
|
-
while not done[0]:
|
|
6324
|
-
try:r,_,_=select.select([i,m],[],[],0.1)
|
|
6325
|
-
except OSError as e:
|
|
6326
|
-
if e.errno==errno.EINTR:continue
|
|
6327
|
-
break
|
|
6328
|
-
if i in r:
|
|
6329
|
-
try:
|
|
6330
|
-
d=os.read(i,4096)
|
|
6331
|
-
if d:os.write(m,d)
|
|
6332
|
-
else:break
|
|
6333
|
-
except OSError:break
|
|
6334
|
-
if m in r:
|
|
6335
|
-
try:
|
|
6336
|
-
d=os.read(m,4096)
|
|
6337
|
-
if d:os.write(o,d)
|
|
6338
|
-
except OSError:done[0]=True
|
|
6339
|
-
try:os.kill(pid,signal.SIGTERM)
|
|
6340
|
-
except Exception:pass
|
|
6341
|
-
try:
|
|
6342
|
-
_,st=os.waitpid(pid,0)
|
|
6343
|
-
sys.exit((st>>8)&0xFF)
|
|
6344
|
-
except Exception:sys.exit(0)
|
|
6345
|
-
`;
|
|
6346
|
-
const helperPath = path11.join(os7.tmpdir(), "codeam-quota-helper.py");
|
|
6347
|
-
fs8.writeFileSync(helperPath, helperScript, { mode: 420 });
|
|
6348
|
-
const python = findInPath("python3") ?? findInPath("python");
|
|
6349
|
-
if (!python) {
|
|
6350
|
-
quotaFetchInProgress = false;
|
|
6351
|
-
return;
|
|
6352
|
-
}
|
|
6353
|
-
const proc = (0, import_child_process5.spawn)(python, [helperPath, claudeCmd, "--tools", ""], {
|
|
6354
|
-
stdio: ["pipe", "pipe", "ignore"],
|
|
6355
|
-
cwd: process.cwd(),
|
|
6356
|
-
env: { ...process.env, TERM: "dumb", COLUMNS: "120", LINES: "30" }
|
|
6357
|
-
});
|
|
6358
|
-
let output = "";
|
|
6359
|
-
proc.stdout?.on("data", (chunk) => {
|
|
6360
|
-
output += chunk.toString("utf8");
|
|
6361
|
-
});
|
|
6362
|
-
setTimeout(() => {
|
|
6363
|
-
proc.stdin?.write("/usage\r");
|
|
6849
|
+
const keepAliveCtx = {
|
|
6850
|
+
inCodespace: process.env.CODESPACES === "true",
|
|
6851
|
+
codespaceName: process.env.CODESPACE_NAME
|
|
6852
|
+
};
|
|
6853
|
+
const { apply: setKeepAlive2 } = buildKeepAlive(keepAliveCtx);
|
|
6854
|
+
const outputSvc = new OutputService(
|
|
6855
|
+
session.id,
|
|
6856
|
+
pluginId,
|
|
6857
|
+
(conversationId) => historySvc.setCurrentConversationId(conversationId),
|
|
6858
|
+
(reset) => historySvc.setRateLimitReset(reset),
|
|
6859
|
+
() => {
|
|
6860
|
+
if (historySvc.isQuotaStale()) fetchQuotaUsage(historySvc);
|
|
6364
6861
|
setTimeout(() => {
|
|
6365
|
-
|
|
6366
|
-
|
|
6367
|
-
|
|
6368
|
-
historySvc.setQuotaPercent(parseInt(weekMatch[1], 10));
|
|
6369
|
-
}
|
|
6370
|
-
const resetMatch = clean.match(/resets\s+(.+?)(?:\s*\(|$)/im);
|
|
6371
|
-
if (resetMatch) {
|
|
6372
|
-
historySvc.setRateLimitReset(resetMatch[1].trim());
|
|
6373
|
-
}
|
|
6374
|
-
try {
|
|
6375
|
-
proc.kill();
|
|
6376
|
-
} catch {
|
|
6377
|
-
}
|
|
6378
|
-
try {
|
|
6379
|
-
fs8.unlinkSync(helperPath);
|
|
6380
|
-
} catch {
|
|
6381
|
-
}
|
|
6382
|
-
quotaFetchInProgress = false;
|
|
6383
|
-
}, 5e3);
|
|
6384
|
-
}, 8e3);
|
|
6385
|
-
proc.on("exit", () => {
|
|
6386
|
-
quotaFetchInProgress = false;
|
|
6387
|
-
});
|
|
6388
|
-
setTimeout(() => {
|
|
6389
|
-
try {
|
|
6390
|
-
proc.kill();
|
|
6391
|
-
} catch {
|
|
6392
|
-
}
|
|
6393
|
-
}, 2e4);
|
|
6394
|
-
}
|
|
6395
|
-
const outputSvc = new OutputService(session.id, pluginId, (conversationId) => {
|
|
6396
|
-
historySvc.setCurrentConversationId(conversationId);
|
|
6397
|
-
}, (reset) => {
|
|
6398
|
-
historySvc.setRateLimitReset(reset);
|
|
6399
|
-
}, () => {
|
|
6400
|
-
if (historySvc.isQuotaStale()) {
|
|
6401
|
-
fetchQuotaUsage();
|
|
6402
|
-
}
|
|
6403
|
-
setTimeout(() => {
|
|
6404
|
-
historySvc.uploadDelta().catch(() => {
|
|
6405
|
-
});
|
|
6406
|
-
}, 400);
|
|
6407
|
-
}, () => {
|
|
6408
|
-
const prevCount = historySvc.getCurrentMessageCount();
|
|
6409
|
-
historySvc.waitForNewUserMessage(prevCount).then((userText) => outputSvc.startTerminalTurn(userText ?? void 0)).catch(() => outputSvc.startTerminalTurn(void 0));
|
|
6410
|
-
}, session.pluginAuthToken);
|
|
6411
|
-
function sendPrompt(prompt) {
|
|
6412
|
-
outputSvc.newTurn();
|
|
6413
|
-
claude.sendCommand(prompt);
|
|
6414
|
-
}
|
|
6415
|
-
const relay = new CommandRelayService(pluginId, async (cmd) => {
|
|
6416
|
-
const parsed = parsePayload(startCommandSchema, cmd.payload);
|
|
6417
|
-
if (!parsed) {
|
|
6418
|
-
showInfo(`Ignoring malformed ${cmd.type} payload.`);
|
|
6419
|
-
return;
|
|
6420
|
-
}
|
|
6421
|
-
switch (cmd.type) {
|
|
6422
|
-
case "start_task": {
|
|
6423
|
-
const { prompt, files } = parsed;
|
|
6424
|
-
const effectivePrompt = prompt ?? "";
|
|
6425
|
-
if (files && files.length > 0) {
|
|
6426
|
-
const paths = saveFilesTemp(files);
|
|
6427
|
-
const atRefs = paths.map((p2) => `@${p2}`).join(" ");
|
|
6428
|
-
outputSvc.newTurn();
|
|
6429
|
-
claude.sendCommand(`${atRefs} ${effectivePrompt}`.trim());
|
|
6430
|
-
setTimeout(() => {
|
|
6431
|
-
for (const p2 of paths) {
|
|
6432
|
-
try {
|
|
6433
|
-
fs8.unlinkSync(p2);
|
|
6434
|
-
} catch {
|
|
6435
|
-
}
|
|
6436
|
-
}
|
|
6437
|
-
}, 12e4);
|
|
6438
|
-
} else if (effectivePrompt) {
|
|
6439
|
-
sendPrompt(effectivePrompt);
|
|
6440
|
-
}
|
|
6441
|
-
break;
|
|
6442
|
-
}
|
|
6443
|
-
case "provide_input": {
|
|
6444
|
-
const { input } = parsed;
|
|
6445
|
-
if (input) sendPrompt(input);
|
|
6446
|
-
break;
|
|
6447
|
-
}
|
|
6448
|
-
case "select_option": {
|
|
6449
|
-
const index = parsed.index ?? 0;
|
|
6450
|
-
const from = parsed.from ?? 0;
|
|
6451
|
-
outputSvc.newTurn();
|
|
6452
|
-
claude.selectOption(index, from);
|
|
6453
|
-
break;
|
|
6454
|
-
}
|
|
6455
|
-
case "escape_key":
|
|
6456
|
-
outputSvc.newTurn();
|
|
6457
|
-
claude.sendEscape();
|
|
6458
|
-
break;
|
|
6459
|
-
case "stop_task":
|
|
6460
|
-
claude.interrupt();
|
|
6461
|
-
break;
|
|
6462
|
-
case "set_keep_alive": {
|
|
6463
|
-
const enabled = !!cmd.payload.enabled;
|
|
6464
|
-
const inCodespaceEnv = process.env.CODESPACES === "true";
|
|
6465
|
-
setKeepAlive(enabled);
|
|
6466
|
-
try {
|
|
6467
|
-
await relay.sendResult(
|
|
6468
|
-
cmd.id,
|
|
6469
|
-
"success",
|
|
6470
|
-
{ enabled, applied: enabled && inCodespaceEnv, runtime: inCodespaceEnv ? "github-codespaces" : "local" }
|
|
6471
|
-
);
|
|
6472
|
-
} catch {
|
|
6473
|
-
}
|
|
6474
|
-
break;
|
|
6475
|
-
}
|
|
6476
|
-
case "session_terminated": {
|
|
6477
|
-
showInfo("Session was deleted from the app \u2014 exiting.");
|
|
6478
|
-
try {
|
|
6479
|
-
claude.kill();
|
|
6480
|
-
} catch {
|
|
6481
|
-
}
|
|
6482
|
-
try {
|
|
6483
|
-
const proc = (0, import_child_process5.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
|
|
6484
|
-
detached: true,
|
|
6485
|
-
stdio: "ignore"
|
|
6486
|
-
});
|
|
6487
|
-
proc.unref();
|
|
6488
|
-
} catch {
|
|
6489
|
-
}
|
|
6490
|
-
outputSvc.dispose();
|
|
6491
|
-
relay.stop();
|
|
6492
|
-
ws.disconnect();
|
|
6493
|
-
process.exit(0);
|
|
6494
|
-
}
|
|
6495
|
-
case "shutdown_session": {
|
|
6496
|
-
try {
|
|
6497
|
-
await relay.sendResult(cmd.id, "success", { ok: true });
|
|
6498
|
-
} catch {
|
|
6499
|
-
}
|
|
6500
|
-
try {
|
|
6501
|
-
claude.kill();
|
|
6502
|
-
} catch {
|
|
6503
|
-
}
|
|
6504
|
-
const codespaceName2 = process.env.CODESPACE_NAME;
|
|
6505
|
-
if (codespaceName2 && process.env.CODESPACES === "true") {
|
|
6506
|
-
try {
|
|
6507
|
-
const stopProc = (0, import_child_process5.spawn)(
|
|
6508
|
-
"bash",
|
|
6509
|
-
["-lc", `sleep 1; gh codespace stop -c ${JSON.stringify(codespaceName2)} >/dev/null 2>&1 || true`],
|
|
6510
|
-
{ detached: true, stdio: "ignore" }
|
|
6511
|
-
);
|
|
6512
|
-
stopProc.unref();
|
|
6513
|
-
} catch {
|
|
6514
|
-
}
|
|
6515
|
-
}
|
|
6516
|
-
try {
|
|
6517
|
-
const proc = (0, import_child_process5.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
|
|
6518
|
-
detached: true,
|
|
6519
|
-
stdio: "ignore"
|
|
6520
|
-
});
|
|
6521
|
-
proc.unref();
|
|
6522
|
-
} catch {
|
|
6523
|
-
}
|
|
6524
|
-
outputSvc.dispose();
|
|
6525
|
-
relay.stop();
|
|
6526
|
-
ws.disconnect();
|
|
6527
|
-
process.exit(0);
|
|
6528
|
-
}
|
|
6529
|
-
case "get_context": {
|
|
6530
|
-
const usage = historySvc.getCurrentUsage();
|
|
6531
|
-
const monthlyCost = historySvc.getMonthlyEstimatedCost();
|
|
6532
|
-
const rateLimitReset = historySvc.getRateLimitReset();
|
|
6533
|
-
const quotaPercent = historySvc.getQuotaPercent();
|
|
6534
|
-
const base = usage ? { ...usage, monthlyCost } : { used: 0, total: 2e5, percent: 0, model: null, outputTokens: 0, cacheReadTokens: 0, monthlyCost, error: "No usage data found" };
|
|
6535
|
-
const result = { ...base, ...rateLimitReset ? { rateLimitReset } : {}, ...quotaPercent !== null ? { quotaPercent } : {} };
|
|
6536
|
-
await relay.sendResult(cmd.id, "completed", result);
|
|
6537
|
-
break;
|
|
6538
|
-
}
|
|
6539
|
-
case "resume_session": {
|
|
6540
|
-
const { id, auto } = parsed;
|
|
6541
|
-
if (!id) break;
|
|
6542
|
-
historySvc.setCurrentConversationId(id);
|
|
6543
|
-
await historySvc.loadConversation(id);
|
|
6544
|
-
await outputSvc.newTurnResume(id);
|
|
6545
|
-
claude.restart(id, auto ?? false);
|
|
6546
|
-
break;
|
|
6547
|
-
}
|
|
6548
|
-
case "get_conversation": {
|
|
6549
|
-
const currentId = historySvc.getCurrentConversationId();
|
|
6550
|
-
if (currentId) {
|
|
6551
|
-
try {
|
|
6552
|
-
await historySvc.loadConversation(currentId);
|
|
6553
|
-
await relay.sendResult(cmd.id, "completed", { conversationId: currentId });
|
|
6554
|
-
} catch {
|
|
6555
|
-
await relay.sendResult(cmd.id, "failed", {});
|
|
6556
|
-
}
|
|
6557
|
-
} else {
|
|
6558
|
-
await relay.sendResult(cmd.id, "completed", { conversationId: null });
|
|
6559
|
-
}
|
|
6560
|
-
break;
|
|
6561
|
-
}
|
|
6562
|
-
case "list_models": {
|
|
6563
|
-
const models = [
|
|
6564
|
-
{ id: "claude-opus-4-7", label: "Claude Opus 4.7", description: "Most capable", family: "claude", vendor: "anthropic", isDefault: false },
|
|
6565
|
-
{ id: "claude-opus-4-6", label: "Claude Opus 4.6", description: "Top tier", family: "claude", vendor: "anthropic", isDefault: false },
|
|
6566
|
-
{ id: "claude-sonnet-4-6", label: "Claude Sonnet 4.6", description: "Balanced", family: "claude", vendor: "anthropic", isDefault: true },
|
|
6567
|
-
{ id: "claude-haiku-4-5-20251001", label: "Claude Haiku 4.5", description: "Fastest", family: "claude", vendor: "anthropic", isDefault: false }
|
|
6568
|
-
];
|
|
6569
|
-
await relay.sendResult(cmd.id, "completed", { models });
|
|
6570
|
-
break;
|
|
6571
|
-
}
|
|
6572
|
-
case "read_file": {
|
|
6573
|
-
const { path: filePath } = parsed;
|
|
6574
|
-
if (!filePath) {
|
|
6575
|
-
await relay.sendResult(cmd.id, "failed", { error: "Missing path" });
|
|
6576
|
-
break;
|
|
6577
|
-
}
|
|
6578
|
-
const result = await readProjectFile(filePath);
|
|
6579
|
-
await relay.sendResult(cmd.id, "completed", result);
|
|
6580
|
-
break;
|
|
6581
|
-
}
|
|
6582
|
-
case "write_file": {
|
|
6583
|
-
const { path: filePath, content } = parsed;
|
|
6584
|
-
if (!filePath || typeof content !== "string") {
|
|
6585
|
-
await relay.sendResult(cmd.id, "failed", { error: "Missing path or content" });
|
|
6586
|
-
break;
|
|
6587
|
-
}
|
|
6588
|
-
const result = await writeProjectFile(filePath, content);
|
|
6589
|
-
await relay.sendResult(cmd.id, "completed", result);
|
|
6590
|
-
break;
|
|
6591
|
-
}
|
|
6592
|
-
case "list_files": {
|
|
6593
|
-
const result = await listProjectFiles({ query: parsed.query });
|
|
6594
|
-
await relay.sendResult(cmd.id, "completed", result);
|
|
6595
|
-
break;
|
|
6596
|
-
}
|
|
6597
|
-
case "git_status": {
|
|
6598
|
-
const result = await gitStatus();
|
|
6599
|
-
await relay.sendResult(cmd.id, "completed", result);
|
|
6600
|
-
break;
|
|
6601
|
-
}
|
|
6602
|
-
case "git_diff": {
|
|
6603
|
-
const { path: filePath } = parsed;
|
|
6604
|
-
const result = await gitDiff(filePath ?? null);
|
|
6605
|
-
await relay.sendResult(cmd.id, "completed", result);
|
|
6606
|
-
break;
|
|
6607
|
-
}
|
|
6608
|
-
case "git_diff_staged": {
|
|
6609
|
-
const { path: filePath } = parsed;
|
|
6610
|
-
const result = await gitDiffStaged(filePath ?? null);
|
|
6611
|
-
await relay.sendResult(cmd.id, "completed", result);
|
|
6612
|
-
break;
|
|
6613
|
-
}
|
|
6614
|
-
case "git_log": {
|
|
6615
|
-
const result = await gitLog(parsed.limit ?? 30);
|
|
6616
|
-
await relay.sendResult(cmd.id, "completed", result);
|
|
6617
|
-
break;
|
|
6618
|
-
}
|
|
6619
|
-
case "git_commit": {
|
|
6620
|
-
if (!parsed.message) {
|
|
6621
|
-
await relay.sendResult(cmd.id, "failed", { error: "Missing message" });
|
|
6622
|
-
break;
|
|
6623
|
-
}
|
|
6624
|
-
const result = await gitCommit(parsed.message, parsed.paths);
|
|
6625
|
-
await relay.sendResult(cmd.id, "completed", result);
|
|
6626
|
-
break;
|
|
6627
|
-
}
|
|
6628
|
-
case "git_push": {
|
|
6629
|
-
const result = await gitPush();
|
|
6630
|
-
await relay.sendResult(cmd.id, "completed", result);
|
|
6631
|
-
break;
|
|
6632
|
-
}
|
|
6633
|
-
case "git_pull": {
|
|
6634
|
-
const result = await gitPull();
|
|
6635
|
-
await relay.sendResult(cmd.id, "completed", result);
|
|
6636
|
-
break;
|
|
6637
|
-
}
|
|
6638
|
-
case "git_resolve": {
|
|
6639
|
-
const { path: filePath, side } = parsed;
|
|
6640
|
-
if (!filePath || !side) {
|
|
6641
|
-
await relay.sendResult(cmd.id, "failed", { error: "Missing path or side" });
|
|
6642
|
-
break;
|
|
6643
|
-
}
|
|
6644
|
-
const result = await gitResolve(filePath, side);
|
|
6645
|
-
await relay.sendResult(cmd.id, "completed", result);
|
|
6646
|
-
break;
|
|
6647
|
-
}
|
|
6648
|
-
}
|
|
6649
|
-
});
|
|
6650
|
-
ws.addHandler({
|
|
6651
|
-
onConnected() {
|
|
6862
|
+
historySvc.uploadDelta().catch(() => {
|
|
6863
|
+
});
|
|
6864
|
+
}, 400);
|
|
6652
6865
|
},
|
|
6653
|
-
|
|
6866
|
+
() => {
|
|
6867
|
+
const prevCount = historySvc.getCurrentMessageCount();
|
|
6868
|
+
historySvc.waitForNewUserMessage(prevCount).then((userText) => outputSvc.startTerminalTurn(userText ?? void 0)).catch(() => outputSvc.startTerminalTurn(void 0));
|
|
6654
6869
|
},
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
const cmdType = typeof payload.type === "string" ? payload.type : null;
|
|
6658
|
-
if (!cmdType) return;
|
|
6659
|
-
const parsed = parsePayload(startCommandSchema, payload.payload ?? {});
|
|
6660
|
-
if (!parsed) {
|
|
6661
|
-
showInfo(`Ignoring malformed ${cmdType} payload (ws).`);
|
|
6662
|
-
return;
|
|
6663
|
-
}
|
|
6664
|
-
if (cmdType === "start_task") {
|
|
6665
|
-
const { prompt, files } = parsed;
|
|
6666
|
-
const effectivePrompt = prompt ?? "";
|
|
6667
|
-
if (files && files.length > 0) {
|
|
6668
|
-
const paths = saveFilesTemp(files);
|
|
6669
|
-
const atRefs = paths.map((p2) => `@${p2}`).join(" ");
|
|
6670
|
-
outputSvc.newTurn();
|
|
6671
|
-
claude.sendCommand(`${atRefs} ${effectivePrompt}`.trim());
|
|
6672
|
-
setTimeout(() => {
|
|
6673
|
-
for (const p2 of paths) {
|
|
6674
|
-
try {
|
|
6675
|
-
fs8.unlinkSync(p2);
|
|
6676
|
-
} catch {
|
|
6677
|
-
}
|
|
6678
|
-
}
|
|
6679
|
-
}, 12e4);
|
|
6680
|
-
} else if (effectivePrompt) {
|
|
6681
|
-
sendPrompt(effectivePrompt);
|
|
6682
|
-
}
|
|
6683
|
-
} else if (cmdType === "provide_input") {
|
|
6684
|
-
const { input } = parsed;
|
|
6685
|
-
if (input) sendPrompt(input);
|
|
6686
|
-
} else if (cmdType === "select_option") {
|
|
6687
|
-
const index = parsed.index ?? 0;
|
|
6688
|
-
const from = parsed.from ?? 0;
|
|
6689
|
-
outputSvc.newTurn();
|
|
6690
|
-
claude.selectOption(index, from);
|
|
6691
|
-
} else if (cmdType === "escape_key") {
|
|
6692
|
-
outputSvc.newTurn();
|
|
6693
|
-
claude.sendEscape();
|
|
6694
|
-
} else if (cmdType === "stop_task") {
|
|
6695
|
-
claude.interrupt();
|
|
6696
|
-
} else if (cmdType === "get_conversation") {
|
|
6697
|
-
const currentId = historySvc.getCurrentConversationId();
|
|
6698
|
-
if (currentId) {
|
|
6699
|
-
historySvc.loadConversation(currentId).catch(() => {
|
|
6700
|
-
});
|
|
6701
|
-
}
|
|
6702
|
-
} else if (cmdType === "resume_session") {
|
|
6703
|
-
const { id, auto } = parsed;
|
|
6704
|
-
if (id) {
|
|
6705
|
-
const autoFlag = auto ?? false;
|
|
6706
|
-
historySvc.loadConversation(id).then(() => outputSvc.newTurnResume(id)).then(() => {
|
|
6707
|
-
claude.restart(id, autoFlag);
|
|
6708
|
-
}).catch(() => {
|
|
6709
|
-
});
|
|
6710
|
-
}
|
|
6711
|
-
}
|
|
6712
|
-
}
|
|
6713
|
-
});
|
|
6714
|
-
ws.connect();
|
|
6715
|
-
relay.start();
|
|
6870
|
+
session.pluginAuthToken
|
|
6871
|
+
);
|
|
6716
6872
|
const claude = new ClaudeService({
|
|
6717
|
-
cwd
|
|
6873
|
+
cwd,
|
|
6718
6874
|
onData(raw) {
|
|
6719
6875
|
outputSvc.push(raw);
|
|
6720
6876
|
},
|
|
@@ -6722,19 +6878,30 @@ except Exception:sys.exit(0)
|
|
|
6722
6878
|
process.removeListener("SIGINT", sigintHandler);
|
|
6723
6879
|
outputSvc.dispose();
|
|
6724
6880
|
relay.stop();
|
|
6725
|
-
ws.disconnect();
|
|
6726
6881
|
process.exit(code);
|
|
6727
6882
|
}
|
|
6728
6883
|
});
|
|
6884
|
+
const ctx = {
|
|
6885
|
+
outputSvc,
|
|
6886
|
+
claude,
|
|
6887
|
+
historySvc,
|
|
6888
|
+
relay: void 0,
|
|
6889
|
+
setKeepAlive: setKeepAlive2,
|
|
6890
|
+
keepAliveCtx
|
|
6891
|
+
};
|
|
6892
|
+
const relay = new CommandRelayService(pluginId, async (cmd) => {
|
|
6893
|
+
await dispatchCommand(ctx, cmd);
|
|
6894
|
+
});
|
|
6895
|
+
ctx.relay = relay;
|
|
6729
6896
|
function sigintHandler() {
|
|
6730
6897
|
claude.kill();
|
|
6731
6898
|
outputSvc.dispose();
|
|
6732
6899
|
relay.stop();
|
|
6733
|
-
ws.disconnect();
|
|
6734
6900
|
process.exit(0);
|
|
6735
6901
|
}
|
|
6736
6902
|
process.once("SIGINT", sigintHandler);
|
|
6737
6903
|
await claude.spawn();
|
|
6904
|
+
relay.start();
|
|
6738
6905
|
setTimeout(() => {
|
|
6739
6906
|
historySvc.detectCurrentConversation();
|
|
6740
6907
|
historySvc.load().catch(() => {
|
|
@@ -6745,47 +6912,7 @@ except Exception:sys.exit(0)
|
|
|
6745
6912
|
});
|
|
6746
6913
|
}
|
|
6747
6914
|
}, 2e3);
|
|
6748
|
-
setTimeout(() =>
|
|
6749
|
-
fetchQuotaUsage();
|
|
6750
|
-
}, 5e3);
|
|
6751
|
-
const inCodespace = process.env.CODESPACES === "true";
|
|
6752
|
-
const codespaceName = process.env.CODESPACE_NAME;
|
|
6753
|
-
let keepAliveTimer = null;
|
|
6754
|
-
async function setIdleTimeout(minutes) {
|
|
6755
|
-
if (!inCodespace || !codespaceName) return;
|
|
6756
|
-
await new Promise((resolve2) => {
|
|
6757
|
-
const proc = (0, import_child_process5.spawn)(
|
|
6758
|
-
"gh",
|
|
6759
|
-
[
|
|
6760
|
-
"api",
|
|
6761
|
-
"-X",
|
|
6762
|
-
"PATCH",
|
|
6763
|
-
`/user/codespaces/${codespaceName}`,
|
|
6764
|
-
"-F",
|
|
6765
|
-
`idle_timeout_minutes=${minutes}`
|
|
6766
|
-
],
|
|
6767
|
-
{ stdio: "ignore", detached: true }
|
|
6768
|
-
);
|
|
6769
|
-
proc.unref();
|
|
6770
|
-
proc.on("exit", () => resolve2());
|
|
6771
|
-
proc.on("error", () => resolve2());
|
|
6772
|
-
});
|
|
6773
|
-
}
|
|
6774
|
-
function setKeepAlive(enabled) {
|
|
6775
|
-
if (keepAliveTimer) {
|
|
6776
|
-
clearInterval(keepAliveTimer);
|
|
6777
|
-
keepAliveTimer = null;
|
|
6778
|
-
}
|
|
6779
|
-
if (!inCodespace || !codespaceName) return;
|
|
6780
|
-
if (!enabled) {
|
|
6781
|
-
void setIdleTimeout(30);
|
|
6782
|
-
return;
|
|
6783
|
-
}
|
|
6784
|
-
void setIdleTimeout(240);
|
|
6785
|
-
keepAliveTimer = setInterval(() => {
|
|
6786
|
-
void setIdleTimeout(240);
|
|
6787
|
-
}, 30 * 60 * 1e3);
|
|
6788
|
-
}
|
|
6915
|
+
setTimeout(() => fetchQuotaUsage(historySvc), 5e3);
|
|
6789
6916
|
}
|
|
6790
6917
|
|
|
6791
6918
|
// src/commands/pair.ts
|
|
@@ -6966,19 +7093,19 @@ async function logout() {
|
|
|
6966
7093
|
}
|
|
6967
7094
|
|
|
6968
7095
|
// src/commands/deploy.ts
|
|
6969
|
-
var
|
|
6970
|
-
var
|
|
6971
|
-
var
|
|
6972
|
-
var
|
|
7096
|
+
var import_child_process12 = require("child_process");
|
|
7097
|
+
var fs10 = __toESM(require("fs"));
|
|
7098
|
+
var os9 = __toESM(require("os"));
|
|
7099
|
+
var path17 = __toESM(require("path"));
|
|
6973
7100
|
var import_util6 = require("util");
|
|
6974
7101
|
var import_picocolors9 = __toESM(require("picocolors"));
|
|
6975
7102
|
|
|
6976
7103
|
// src/services/providers/github-codespaces.ts
|
|
6977
|
-
var
|
|
7104
|
+
var import_child_process8 = require("child_process");
|
|
6978
7105
|
var import_util2 = require("util");
|
|
6979
7106
|
var import_picocolors7 = __toESM(require("picocolors"));
|
|
6980
|
-
var
|
|
6981
|
-
var execFileP2 = (0, import_util2.promisify)(
|
|
7107
|
+
var path13 = __toESM(require("path"));
|
|
7108
|
+
var execFileP2 = (0, import_util2.promisify)(import_child_process8.execFile);
|
|
6982
7109
|
var MAX_BUFFER = 8 * 1024 * 1024;
|
|
6983
7110
|
function resetStdinForChild() {
|
|
6984
7111
|
if (process.stdin.isTTY) {
|
|
@@ -7022,7 +7149,7 @@ var GitHubCodespacesProvider = class {
|
|
|
7022
7149
|
if (!isAuthed) {
|
|
7023
7150
|
resetStdinForChild();
|
|
7024
7151
|
await new Promise((resolve2, reject) => {
|
|
7025
|
-
const proc = (0,
|
|
7152
|
+
const proc = (0, import_child_process8.spawn)("gh", ["auth", "login", "-s", "codespace,repo,read:user"], {
|
|
7026
7153
|
stdio: "inherit"
|
|
7027
7154
|
});
|
|
7028
7155
|
proc.on("exit", (code) => {
|
|
@@ -7056,7 +7183,7 @@ var GitHubCodespacesProvider = class {
|
|
|
7056
7183
|
wt(noteLines.join("\n"), "One more permission needed");
|
|
7057
7184
|
resetStdinForChild();
|
|
7058
7185
|
const refreshCode = await new Promise((resolve2, reject) => {
|
|
7059
|
-
const proc = (0,
|
|
7186
|
+
const proc = (0, import_child_process8.spawn)(
|
|
7060
7187
|
"gh",
|
|
7061
7188
|
["auth", "refresh", "-h", "github.com", "-s", "codespace"],
|
|
7062
7189
|
{ stdio: "inherit" }
|
|
@@ -7206,7 +7333,7 @@ var GitHubCodespacesProvider = class {
|
|
|
7206
7333
|
O2.step(`Installing gh via ${installCmd.describe}\u2026`);
|
|
7207
7334
|
resetStdinForChild();
|
|
7208
7335
|
const ok = await new Promise((resolve2) => {
|
|
7209
|
-
const proc = (0,
|
|
7336
|
+
const proc = (0, import_child_process8.spawn)(installCmd.exe, installCmd.args, { stdio: "inherit" });
|
|
7210
7337
|
proc.on("exit", (code) => resolve2(code === 0));
|
|
7211
7338
|
proc.on("error", () => resolve2(false));
|
|
7212
7339
|
});
|
|
@@ -7233,7 +7360,7 @@ var GitHubCodespacesProvider = class {
|
|
|
7233
7360
|
);
|
|
7234
7361
|
resetStdinForChild();
|
|
7235
7362
|
await new Promise((resolve2, reject) => {
|
|
7236
|
-
const proc = (0,
|
|
7363
|
+
const proc = (0, import_child_process8.spawn)(
|
|
7237
7364
|
"gh",
|
|
7238
7365
|
["auth", "refresh", "-h", "github.com", "-s", "repo,read:org"],
|
|
7239
7366
|
{ stdio: "inherit" }
|
|
@@ -7411,7 +7538,7 @@ var GitHubCodespacesProvider = class {
|
|
|
7411
7538
|
async streamCommand(workspaceId, command2) {
|
|
7412
7539
|
resetStdinForChild();
|
|
7413
7540
|
return new Promise((resolve2, reject) => {
|
|
7414
|
-
const proc = (0,
|
|
7541
|
+
const proc = (0, import_child_process8.spawn)(
|
|
7415
7542
|
"gh",
|
|
7416
7543
|
["codespace", "ssh", "-c", workspaceId, "--", "-tt", command2],
|
|
7417
7544
|
{ stdio: "inherit" }
|
|
@@ -7438,11 +7565,11 @@ var GitHubCodespacesProvider = class {
|
|
|
7438
7565
|
`mkdir -p ${shellQuote(remoteDir)} && tar -xzf - -C ${shellQuote(remoteDir)}`
|
|
7439
7566
|
];
|
|
7440
7567
|
await new Promise((resolve2, reject) => {
|
|
7441
|
-
const tar = (0,
|
|
7568
|
+
const tar = (0, import_child_process8.spawn)("tar", tarArgs, {
|
|
7442
7569
|
stdio: ["ignore", "pipe", "pipe"],
|
|
7443
7570
|
env: tarEnv
|
|
7444
7571
|
});
|
|
7445
|
-
const ssh = (0,
|
|
7572
|
+
const ssh = (0, import_child_process8.spawn)("gh", sshArgs, {
|
|
7446
7573
|
stdio: [tar.stdout, "pipe", "pipe"]
|
|
7447
7574
|
});
|
|
7448
7575
|
let tarErr = "";
|
|
@@ -7466,7 +7593,7 @@ var GitHubCodespacesProvider = class {
|
|
|
7466
7593
|
});
|
|
7467
7594
|
}
|
|
7468
7595
|
async uploadFile(workspaceId, remotePath, contents, options = {}) {
|
|
7469
|
-
const remoteDir =
|
|
7596
|
+
const remoteDir = path13.posix.dirname(remotePath);
|
|
7470
7597
|
const parts = [
|
|
7471
7598
|
`mkdir -p ${shellQuote(remoteDir)}`,
|
|
7472
7599
|
`cat > ${shellQuote(remotePath)}`
|
|
@@ -7476,7 +7603,7 @@ var GitHubCodespacesProvider = class {
|
|
|
7476
7603
|
}
|
|
7477
7604
|
const cmd = parts.join(" && ");
|
|
7478
7605
|
await new Promise((resolve2, reject) => {
|
|
7479
|
-
const proc = (0,
|
|
7606
|
+
const proc = (0, import_child_process8.spawn)(
|
|
7480
7607
|
"gh",
|
|
7481
7608
|
["codespace", "ssh", "-c", workspaceId, "--", cmd],
|
|
7482
7609
|
{ stdio: ["pipe", "pipe", "pipe"] }
|
|
@@ -7534,11 +7661,11 @@ function shellQuote(s) {
|
|
|
7534
7661
|
}
|
|
7535
7662
|
|
|
7536
7663
|
// src/services/providers/gitpod.ts
|
|
7537
|
-
var
|
|
7664
|
+
var import_child_process9 = require("child_process");
|
|
7538
7665
|
var import_util3 = require("util");
|
|
7539
|
-
var
|
|
7666
|
+
var path14 = __toESM(require("path"));
|
|
7540
7667
|
var import_picocolors8 = __toESM(require("picocolors"));
|
|
7541
|
-
var execFileP3 = (0, import_util3.promisify)(
|
|
7668
|
+
var execFileP3 = (0, import_util3.promisify)(import_child_process9.execFile);
|
|
7542
7669
|
var MAX_BUFFER2 = 8 * 1024 * 1024;
|
|
7543
7670
|
function resetStdinForChild2() {
|
|
7544
7671
|
if (process.stdin.isTTY) {
|
|
@@ -7578,7 +7705,7 @@ var GitpodProvider = class {
|
|
|
7578
7705
|
);
|
|
7579
7706
|
resetStdinForChild2();
|
|
7580
7707
|
await new Promise((resolve2, reject) => {
|
|
7581
|
-
const proc = (0,
|
|
7708
|
+
const proc = (0, import_child_process9.spawn)("gitpod", ["login"], { stdio: "inherit" });
|
|
7582
7709
|
proc.on("exit", (code) => {
|
|
7583
7710
|
if (code === 0) resolve2();
|
|
7584
7711
|
else reject(new Error("gitpod login failed."));
|
|
@@ -7730,7 +7857,7 @@ var GitpodProvider = class {
|
|
|
7730
7857
|
async streamCommand(workspaceId, command2) {
|
|
7731
7858
|
resetStdinForChild2();
|
|
7732
7859
|
return new Promise((resolve2, reject) => {
|
|
7733
|
-
const proc = (0,
|
|
7860
|
+
const proc = (0, import_child_process9.spawn)(
|
|
7734
7861
|
"gitpod",
|
|
7735
7862
|
["workspace", "ssh", workspaceId, "--", "-tt", command2],
|
|
7736
7863
|
{ stdio: "inherit" }
|
|
@@ -7750,11 +7877,11 @@ var GitpodProvider = class {
|
|
|
7750
7877
|
const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
|
|
7751
7878
|
const remoteCmd = `mkdir -p ${shellQuote2(remoteDir)} && tar -xzf - -C ${shellQuote2(remoteDir)}`;
|
|
7752
7879
|
await new Promise((resolve2, reject) => {
|
|
7753
|
-
const tar = (0,
|
|
7880
|
+
const tar = (0, import_child_process9.spawn)("tar", tarArgs, {
|
|
7754
7881
|
stdio: ["ignore", "pipe", "pipe"],
|
|
7755
7882
|
env: tarEnv
|
|
7756
7883
|
});
|
|
7757
|
-
const ssh = (0,
|
|
7884
|
+
const ssh = (0, import_child_process9.spawn)(
|
|
7758
7885
|
"gitpod",
|
|
7759
7886
|
["workspace", "ssh", workspaceId, "--", remoteCmd],
|
|
7760
7887
|
{ stdio: [tar.stdout, "pipe", "pipe"] }
|
|
@@ -7776,7 +7903,7 @@ var GitpodProvider = class {
|
|
|
7776
7903
|
});
|
|
7777
7904
|
}
|
|
7778
7905
|
async uploadFile(workspaceId, remotePath, contents, options = {}) {
|
|
7779
|
-
const remoteDir =
|
|
7906
|
+
const remoteDir = path14.posix.dirname(remotePath);
|
|
7780
7907
|
const parts = [
|
|
7781
7908
|
`mkdir -p ${shellQuote2(remoteDir)}`,
|
|
7782
7909
|
`cat > ${shellQuote2(remotePath)}`
|
|
@@ -7786,7 +7913,7 @@ var GitpodProvider = class {
|
|
|
7786
7913
|
}
|
|
7787
7914
|
const cmd = parts.join(" && ");
|
|
7788
7915
|
await new Promise((resolve2, reject) => {
|
|
7789
|
-
const proc = (0,
|
|
7916
|
+
const proc = (0, import_child_process9.spawn)(
|
|
7790
7917
|
"gitpod",
|
|
7791
7918
|
["workspace", "ssh", workspaceId, "--", cmd],
|
|
7792
7919
|
{ stdio: ["pipe", "pipe", "pipe"] }
|
|
@@ -7810,10 +7937,10 @@ function shellQuote2(s) {
|
|
|
7810
7937
|
}
|
|
7811
7938
|
|
|
7812
7939
|
// src/services/providers/gitlab-workspaces.ts
|
|
7813
|
-
var
|
|
7940
|
+
var import_child_process10 = require("child_process");
|
|
7814
7941
|
var import_util4 = require("util");
|
|
7815
|
-
var
|
|
7816
|
-
var execFileP4 = (0, import_util4.promisify)(
|
|
7942
|
+
var path15 = __toESM(require("path"));
|
|
7943
|
+
var execFileP4 = (0, import_util4.promisify)(import_child_process10.execFile);
|
|
7817
7944
|
var MAX_BUFFER3 = 8 * 1024 * 1024;
|
|
7818
7945
|
var GITLAB_API_BASE = process.env.CODEAM_GITLAB_API_URL ?? "https://gitlab.com/api/v4";
|
|
7819
7946
|
function resetStdinForChild3() {
|
|
@@ -7855,7 +7982,7 @@ var GitLabWorkspacesProvider = class {
|
|
|
7855
7982
|
);
|
|
7856
7983
|
resetStdinForChild3();
|
|
7857
7984
|
await new Promise((resolve2, reject) => {
|
|
7858
|
-
const proc = (0,
|
|
7985
|
+
const proc = (0, import_child_process10.spawn)(
|
|
7859
7986
|
"glab",
|
|
7860
7987
|
["auth", "login", "--scopes", "api,read_user,read_repository"],
|
|
7861
7988
|
{ stdio: "inherit" }
|
|
@@ -8027,7 +8154,7 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
|
|
|
8027
8154
|
const sshHost = process.env.CODEAM_GITLAB_SSH_HOST ?? "workspaces.gitlab.com";
|
|
8028
8155
|
resetStdinForChild3();
|
|
8029
8156
|
return new Promise((resolve2, reject) => {
|
|
8030
|
-
const proc = (0,
|
|
8157
|
+
const proc = (0, import_child_process10.spawn)(
|
|
8031
8158
|
"ssh",
|
|
8032
8159
|
["-tt", "-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, command2],
|
|
8033
8160
|
{ stdio: "inherit" }
|
|
@@ -8048,8 +8175,8 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
|
|
|
8048
8175
|
const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
|
|
8049
8176
|
const remoteCmd = `mkdir -p ${shellQuote3(remoteDir)} && tar -xzf - -C ${shellQuote3(remoteDir)}`;
|
|
8050
8177
|
await new Promise((resolve2, reject) => {
|
|
8051
|
-
const tar = (0,
|
|
8052
|
-
const ssh = (0,
|
|
8178
|
+
const tar = (0, import_child_process10.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
|
|
8179
|
+
const ssh = (0, import_child_process10.spawn)(
|
|
8053
8180
|
"ssh",
|
|
8054
8181
|
["-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, remoteCmd],
|
|
8055
8182
|
{ stdio: [tar.stdout, "pipe", "pipe"] }
|
|
@@ -8072,14 +8199,14 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
|
|
|
8072
8199
|
}
|
|
8073
8200
|
async uploadFile(workspaceId, remotePath, contents, options = {}) {
|
|
8074
8201
|
const sshHost = process.env.CODEAM_GITLAB_SSH_HOST ?? "workspaces.gitlab.com";
|
|
8075
|
-
const remoteDir =
|
|
8202
|
+
const remoteDir = path15.posix.dirname(remotePath);
|
|
8076
8203
|
const parts = [`mkdir -p ${shellQuote3(remoteDir)}`, `cat > ${shellQuote3(remotePath)}`];
|
|
8077
8204
|
if (options.mode != null) {
|
|
8078
8205
|
parts.push(`chmod ${options.mode.toString(8)} ${shellQuote3(remotePath)}`);
|
|
8079
8206
|
}
|
|
8080
8207
|
const cmd = parts.join(" && ");
|
|
8081
8208
|
await new Promise((resolve2, reject) => {
|
|
8082
|
-
const proc = (0,
|
|
8209
|
+
const proc = (0, import_child_process10.spawn)(
|
|
8083
8210
|
"ssh",
|
|
8084
8211
|
["-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, cmd],
|
|
8085
8212
|
{ stdio: ["pipe", "pipe", "pipe"] }
|
|
@@ -8138,10 +8265,10 @@ function shellQuote3(s) {
|
|
|
8138
8265
|
}
|
|
8139
8266
|
|
|
8140
8267
|
// src/services/providers/railway.ts
|
|
8141
|
-
var
|
|
8268
|
+
var import_child_process11 = require("child_process");
|
|
8142
8269
|
var import_util5 = require("util");
|
|
8143
|
-
var
|
|
8144
|
-
var execFileP5 = (0, import_util5.promisify)(
|
|
8270
|
+
var path16 = __toESM(require("path"));
|
|
8271
|
+
var execFileP5 = (0, import_util5.promisify)(import_child_process11.execFile);
|
|
8145
8272
|
var MAX_BUFFER4 = 8 * 1024 * 1024;
|
|
8146
8273
|
function resetStdinForChild4() {
|
|
8147
8274
|
if (process.stdin.isTTY) {
|
|
@@ -8182,7 +8309,7 @@ var RailwayProvider = class {
|
|
|
8182
8309
|
);
|
|
8183
8310
|
resetStdinForChild4();
|
|
8184
8311
|
await new Promise((resolve2, reject) => {
|
|
8185
|
-
const proc = (0,
|
|
8312
|
+
const proc = (0, import_child_process11.spawn)("railway", ["login"], { stdio: "inherit" });
|
|
8186
8313
|
proc.on("exit", (code) => {
|
|
8187
8314
|
if (code === 0) resolve2();
|
|
8188
8315
|
else reject(new Error("railway login failed."));
|
|
@@ -8325,7 +8452,7 @@ var RailwayProvider = class {
|
|
|
8325
8452
|
}
|
|
8326
8453
|
resetStdinForChild4();
|
|
8327
8454
|
return new Promise((resolve2, reject) => {
|
|
8328
|
-
const proc = (0,
|
|
8455
|
+
const proc = (0, import_child_process11.spawn)(
|
|
8329
8456
|
"railway",
|
|
8330
8457
|
["shell", "--project", projectId, "--service", serviceId, "--command", command2],
|
|
8331
8458
|
{ stdio: "inherit" }
|
|
@@ -8349,8 +8476,8 @@ var RailwayProvider = class {
|
|
|
8349
8476
|
const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
|
|
8350
8477
|
const remoteCmd = `mkdir -p ${shellQuote4(remoteDir)} && tar -xzf - -C ${shellQuote4(remoteDir)}`;
|
|
8351
8478
|
await new Promise((resolve2, reject) => {
|
|
8352
|
-
const tar = (0,
|
|
8353
|
-
const sh = (0,
|
|
8479
|
+
const tar = (0, import_child_process11.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
|
|
8480
|
+
const sh = (0, import_child_process11.spawn)(
|
|
8354
8481
|
"railway",
|
|
8355
8482
|
["shell", "--project", projectId, "--service", serviceId, "--command", remoteCmd],
|
|
8356
8483
|
{ stdio: [tar.stdout, "pipe", "pipe"] }
|
|
@@ -8376,14 +8503,14 @@ var RailwayProvider = class {
|
|
|
8376
8503
|
if (!projectId || !serviceId) {
|
|
8377
8504
|
throw new Error("Invalid Railway workspace id (expected projectId/serviceId).");
|
|
8378
8505
|
}
|
|
8379
|
-
const remoteDir =
|
|
8506
|
+
const remoteDir = path16.posix.dirname(remotePath);
|
|
8380
8507
|
const parts = [`mkdir -p ${shellQuote4(remoteDir)}`, `cat > ${shellQuote4(remotePath)}`];
|
|
8381
8508
|
if (options.mode != null) {
|
|
8382
8509
|
parts.push(`chmod ${options.mode.toString(8)} ${shellQuote4(remotePath)}`);
|
|
8383
8510
|
}
|
|
8384
8511
|
const cmd = parts.join(" && ");
|
|
8385
8512
|
await new Promise((resolve2, reject) => {
|
|
8386
|
-
const proc = (0,
|
|
8513
|
+
const proc = (0, import_child_process11.spawn)(
|
|
8387
8514
|
"railway",
|
|
8388
8515
|
["shell", "--project", projectId, "--service", serviceId, "--command", cmd],
|
|
8389
8516
|
{ stdio: ["pipe", "pipe", "pipe"] }
|
|
@@ -8415,7 +8542,7 @@ var PROVIDERS = [
|
|
|
8415
8542
|
];
|
|
8416
8543
|
|
|
8417
8544
|
// src/commands/deploy.ts
|
|
8418
|
-
var execFileP6 = (0, import_util6.promisify)(
|
|
8545
|
+
var execFileP6 = (0, import_util6.promisify)(import_child_process12.execFile);
|
|
8419
8546
|
async function deploy() {
|
|
8420
8547
|
console.log();
|
|
8421
8548
|
mt(import_picocolors9.default.bgMagenta(import_picocolors9.default.white(" codeam deploy ")));
|
|
@@ -8568,7 +8695,7 @@ async function deploy() {
|
|
|
8568
8695
|
process.exit(1);
|
|
8569
8696
|
}
|
|
8570
8697
|
}
|
|
8571
|
-
const localClaudeDir =
|
|
8698
|
+
const localClaudeDir = path17.join(os9.homedir(), ".claude");
|
|
8572
8699
|
const localCredsKind = await detectLocalClaudeCredentials(localClaudeDir);
|
|
8573
8700
|
let bridged = "none";
|
|
8574
8701
|
if (localCredsKind !== "none") {
|
|
@@ -8612,7 +8739,7 @@ async function deploy() {
|
|
|
8612
8739
|
process.exit(1);
|
|
8613
8740
|
}
|
|
8614
8741
|
claudeStep.stop("\u2713 Claude CLI installed");
|
|
8615
|
-
const haveLocalClaude =
|
|
8742
|
+
const haveLocalClaude = fs10.existsSync(localClaudeDir) && fs10.statSync(localClaudeDir).isDirectory();
|
|
8616
8743
|
if (haveLocalClaude) {
|
|
8617
8744
|
const copyStep = fe();
|
|
8618
8745
|
copyStep.start("Copying local Claude config to workspace\u2026");
|
|
@@ -8666,10 +8793,10 @@ async function deploy() {
|
|
|
8666
8793
|
}
|
|
8667
8794
|
}
|
|
8668
8795
|
if (bridged !== "none") {
|
|
8669
|
-
const localClaudeJson =
|
|
8670
|
-
if (
|
|
8796
|
+
const localClaudeJson = path17.join(os9.homedir(), ".claude.json");
|
|
8797
|
+
if (fs10.existsSync(localClaudeJson)) {
|
|
8671
8798
|
try {
|
|
8672
|
-
const contents =
|
|
8799
|
+
const contents = fs10.readFileSync(localClaudeJson);
|
|
8673
8800
|
await provider.uploadFile(
|
|
8674
8801
|
workspace.id,
|
|
8675
8802
|
"/home/codespace/.claude.json",
|
|
@@ -8859,7 +8986,7 @@ async function runRemoteClaudeLogin(provider, workspaceId) {
|
|
|
8859
8986
|
}
|
|
8860
8987
|
}
|
|
8861
8988
|
async function detectLocalClaudeCredentials(localClaudeDir) {
|
|
8862
|
-
if (
|
|
8989
|
+
if (fs10.existsSync(path17.join(localClaudeDir, ".credentials.json"))) {
|
|
8863
8990
|
return "flat-file";
|
|
8864
8991
|
}
|
|
8865
8992
|
if (process.platform === "darwin") {
|
|
@@ -8892,8 +9019,8 @@ async function verifyClaudeAuth(provider, workspaceId) {
|
|
|
8892
9019
|
}
|
|
8893
9020
|
}
|
|
8894
9021
|
async function bridgeClaudeCredentials(provider, workspaceId, localClaudeDir) {
|
|
8895
|
-
const fileBased =
|
|
8896
|
-
if (
|
|
9022
|
+
const fileBased = path17.join(localClaudeDir, ".credentials.json");
|
|
9023
|
+
if (fs10.existsSync(fileBased)) return "flat-file";
|
|
8897
9024
|
if (process.platform === "darwin") {
|
|
8898
9025
|
try {
|
|
8899
9026
|
const { stdout } = await execFileP6(
|
|
@@ -9101,7 +9228,7 @@ async function stopWorkspaceFromLocal(target) {
|
|
|
9101
9228
|
// src/commands/version.ts
|
|
9102
9229
|
var import_picocolors11 = __toESM(require("picocolors"));
|
|
9103
9230
|
function version() {
|
|
9104
|
-
const v = true ? "2.
|
|
9231
|
+
const v = true ? "2.5.1" : "unknown";
|
|
9105
9232
|
console.log(`${import_picocolors11.default.bold("codeam-cli")} ${import_picocolors11.default.cyan(v)}`);
|
|
9106
9233
|
}
|
|
9107
9234
|
|
|
@@ -9138,22 +9265,22 @@ function help() {
|
|
|
9138
9265
|
}
|
|
9139
9266
|
|
|
9140
9267
|
// src/lib/updateNotifier.ts
|
|
9141
|
-
var
|
|
9142
|
-
var
|
|
9143
|
-
var
|
|
9144
|
-
var
|
|
9268
|
+
var fs11 = __toESM(require("fs"));
|
|
9269
|
+
var os10 = __toESM(require("os"));
|
|
9270
|
+
var path18 = __toESM(require("path"));
|
|
9271
|
+
var https5 = __toESM(require("https"));
|
|
9145
9272
|
var import_picocolors13 = __toESM(require("picocolors"));
|
|
9146
9273
|
var PKG_NAME = "codeam-cli";
|
|
9147
9274
|
var REGISTRY_URL = `https://registry.npmjs.org/${PKG_NAME}/latest`;
|
|
9148
9275
|
var TTL_MS = 24 * 60 * 60 * 1e3;
|
|
9149
9276
|
var REQUEST_TIMEOUT_MS = 1500;
|
|
9150
9277
|
function cachePath() {
|
|
9151
|
-
const dir =
|
|
9152
|
-
return
|
|
9278
|
+
const dir = path18.join(os10.homedir(), ".codeam");
|
|
9279
|
+
return path18.join(dir, "update-check.json");
|
|
9153
9280
|
}
|
|
9154
9281
|
function readCache() {
|
|
9155
9282
|
try {
|
|
9156
|
-
const raw =
|
|
9283
|
+
const raw = fs11.readFileSync(cachePath(), "utf8");
|
|
9157
9284
|
const parsed = JSON.parse(raw);
|
|
9158
9285
|
if (typeof parsed.fetchedAt !== "number" || typeof parsed.latest !== "string") return null;
|
|
9159
9286
|
return parsed;
|
|
@@ -9164,8 +9291,8 @@ function readCache() {
|
|
|
9164
9291
|
function writeCache(cache) {
|
|
9165
9292
|
try {
|
|
9166
9293
|
const file = cachePath();
|
|
9167
|
-
|
|
9168
|
-
|
|
9294
|
+
fs11.mkdirSync(path18.dirname(file), { recursive: true });
|
|
9295
|
+
fs11.writeFileSync(file, JSON.stringify(cache));
|
|
9169
9296
|
} catch {
|
|
9170
9297
|
}
|
|
9171
9298
|
}
|
|
@@ -9184,7 +9311,7 @@ function compareSemver(a, b) {
|
|
|
9184
9311
|
}
|
|
9185
9312
|
function fetchLatest() {
|
|
9186
9313
|
return new Promise((resolve2) => {
|
|
9187
|
-
const req =
|
|
9314
|
+
const req = https5.get(
|
|
9188
9315
|
REGISTRY_URL,
|
|
9189
9316
|
{ headers: { Accept: "application/json" }, timeout: REQUEST_TIMEOUT_MS },
|
|
9190
9317
|
(res) => {
|
|
@@ -9236,7 +9363,7 @@ function checkForUpdates() {
|
|
|
9236
9363
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
9237
9364
|
if (process.env.CI) return;
|
|
9238
9365
|
if (!process.stdout.isTTY) return;
|
|
9239
|
-
const current = true ? "2.
|
|
9366
|
+
const current = true ? "2.5.1" : null;
|
|
9240
9367
|
if (!current) return;
|
|
9241
9368
|
const cache = readCache();
|
|
9242
9369
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|