codeam-cli 2.27.16 → 2.28.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 +23 -0
- package/dist/index.js +1094 -824
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -498,7 +498,7 @@ var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
|
|
|
498
498
|
// package.json
|
|
499
499
|
var package_default = {
|
|
500
500
|
name: "codeam-cli",
|
|
501
|
-
version: "2.
|
|
501
|
+
version: "2.28.1",
|
|
502
502
|
description: "Workflow-continuity bridge for AI coding agents. Wrap Claude Code or Codex in a PTY and supervise, approve, and redirect the session from any device \u2014 async. The terminal companion for CodeAgent Mobile.",
|
|
503
503
|
type: "commonjs",
|
|
504
504
|
main: "dist/index.js",
|
|
@@ -573,6 +573,7 @@ var package_default = {
|
|
|
573
573
|
"@agentclientprotocol/sdk": "^0.25.0",
|
|
574
574
|
"@clack/prompts": "^1.2.0",
|
|
575
575
|
chokidar: "^3.6.0",
|
|
576
|
+
ignore: "^5.3.2",
|
|
576
577
|
picocolors: "^1.1.0",
|
|
577
578
|
"qrcode-terminal": "^0.12.0",
|
|
578
579
|
which: "^5.0.0",
|
|
@@ -691,12 +692,13 @@ function computePollDelay({ baseMs, failures }) {
|
|
|
691
692
|
|
|
692
693
|
// src/services/pairing.service.ts
|
|
693
694
|
var API_BASE = resolveApiBaseUrl();
|
|
695
|
+
var REQUEST_CODE_TIMEOUT_MS = 1e4;
|
|
694
696
|
async function requestCode(pluginId) {
|
|
695
697
|
try {
|
|
696
698
|
const runtime = process.env.CODESPACES === "true" ? "github-codespaces" : "local";
|
|
697
699
|
const codespaceName = process.env.CODESPACE_NAME;
|
|
698
700
|
const branch = detectCurrentBranch();
|
|
699
|
-
const
|
|
701
|
+
const post2 = _transport.postJson(`${API_BASE}/api/pairing/code`, {
|
|
700
702
|
pluginId,
|
|
701
703
|
ideName: "Terminal (codeam-cli)",
|
|
702
704
|
ideVersion: package_default.version,
|
|
@@ -705,6 +707,14 @@ async function requestCode(pluginId) {
|
|
|
705
707
|
branch,
|
|
706
708
|
...codespaceName ? { codespaceName } : {}
|
|
707
709
|
});
|
|
710
|
+
let timer;
|
|
711
|
+
const timeoutSentinel = /* @__PURE__ */ Symbol("request-code-timeout");
|
|
712
|
+
const timeoutPromise = new Promise((resolve6) => {
|
|
713
|
+
timer = setTimeout(() => resolve6(timeoutSentinel), REQUEST_CODE_TIMEOUT_MS);
|
|
714
|
+
});
|
|
715
|
+
const result = await Promise.race([post2, timeoutPromise]);
|
|
716
|
+
clearTimeout(timer);
|
|
717
|
+
if (result === timeoutSentinel) return null;
|
|
708
718
|
const data = result?.data;
|
|
709
719
|
if (!data?.code) return null;
|
|
710
720
|
return { code: data.code, expiresAt: data.expiresAt };
|
|
@@ -712,6 +722,20 @@ async function requestCode(pluginId) {
|
|
|
712
722
|
return null;
|
|
713
723
|
}
|
|
714
724
|
}
|
|
725
|
+
async function fetchCurrentPluginAuthToken(sessionId, pluginId) {
|
|
726
|
+
try {
|
|
727
|
+
const result = await _transport.postJson(
|
|
728
|
+
`${API_BASE}/api/pairing/reconnect`,
|
|
729
|
+
{ sessionId, pluginId }
|
|
730
|
+
);
|
|
731
|
+
const data = result?.data;
|
|
732
|
+
if (!data?.paired) return null;
|
|
733
|
+
const token = data.pluginAuthToken;
|
|
734
|
+
return typeof token === "string" && token.length > 0 ? token : null;
|
|
735
|
+
} catch {
|
|
736
|
+
return null;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
715
739
|
function pollStatus(pluginId, onPaired, onTimeout) {
|
|
716
740
|
let stopped = false;
|
|
717
741
|
let pollTimer = null;
|
|
@@ -5873,7 +5897,7 @@ function readAnonId() {
|
|
|
5873
5897
|
}
|
|
5874
5898
|
function superProperties() {
|
|
5875
5899
|
return {
|
|
5876
|
-
cliVersion: true ? "2.
|
|
5900
|
+
cliVersion: true ? "2.28.1" : "0.0.0-dev",
|
|
5877
5901
|
nodeVersion: process.version,
|
|
5878
5902
|
platform: process.platform,
|
|
5879
5903
|
arch: process.arch,
|
|
@@ -14973,9 +14997,10 @@ var AcpPublisher = class {
|
|
|
14973
14997
|
this.envelope(body)
|
|
14974
14998
|
);
|
|
14975
14999
|
if (statusCode < 200 || statusCode >= 300) {
|
|
15000
|
+
const tok = this.opts.pluginAuthToken;
|
|
14976
15001
|
log.warn(
|
|
14977
15002
|
"acpPublisher",
|
|
14978
|
-
`output type=${String(body.type)} done=${body.done === true} status=${statusCode} body=${resBody.slice(0, 200)}`
|
|
15003
|
+
`output type=${String(body.type)} done=${body.done === true} status=${statusCode} body=${resBody.slice(0, 200)} | sentSessionId=${this.opts.sessionId} sentPluginId=${this.opts.pluginId} tokenLen=${tok.length} tokenHead=${tok.slice(0, 12)} tokenTail=${tok.slice(-8)}`
|
|
14979
15004
|
);
|
|
14980
15005
|
}
|
|
14981
15006
|
} catch (err) {
|
|
@@ -15113,151 +15138,420 @@ var AcpPublisher = class {
|
|
|
15113
15138
|
}
|
|
15114
15139
|
};
|
|
15115
15140
|
|
|
15116
|
-
// src/
|
|
15117
|
-
var
|
|
15118
|
-
|
|
15119
|
-
|
|
15120
|
-
|
|
15121
|
-
|
|
15122
|
-
|
|
15123
|
-
|
|
15124
|
-
|
|
15125
|
-
|
|
15126
|
-
|
|
15127
|
-
|
|
15128
|
-
|
|
15129
|
-
|
|
15130
|
-
|
|
15131
|
-
|
|
15132
|
-
|
|
15133
|
-
|
|
15134
|
-
return
|
|
15135
|
-
}
|
|
15136
|
-
case "tool_call_update": {
|
|
15137
|
-
if (update.status !== "completed" && update.status !== "failed") {
|
|
15138
|
-
return [];
|
|
15139
|
-
}
|
|
15140
|
-
const body = describeToolCallUpdate(update);
|
|
15141
|
-
if (!body) return [];
|
|
15142
|
-
const prefix = update.status === "failed" ? "[failed] " : "";
|
|
15143
|
-
return [{ chunkId: update.toolCallId, kind: "tool_result", delta: prefix + body }];
|
|
15141
|
+
// src/services/terminal-ops.service.ts
|
|
15142
|
+
var import_child_process8 = require("child_process");
|
|
15143
|
+
var import_crypto2 = require("crypto");
|
|
15144
|
+
var import_path3 = __toESM(require("path"));
|
|
15145
|
+
var MAX_CONCURRENT_SESSIONS = 4;
|
|
15146
|
+
var nodePtyModule;
|
|
15147
|
+
function loadNodePty2() {
|
|
15148
|
+
if (nodePtyModule !== void 0) return nodePtyModule;
|
|
15149
|
+
const vendoredPath = import_path3.default.join(__dirname, "vendor", "node-pty");
|
|
15150
|
+
try {
|
|
15151
|
+
nodePtyModule = require(vendoredPath);
|
|
15152
|
+
return nodePtyModule;
|
|
15153
|
+
} catch {
|
|
15154
|
+
try {
|
|
15155
|
+
nodePtyModule = require("node-pty");
|
|
15156
|
+
return nodePtyModule;
|
|
15157
|
+
} catch {
|
|
15158
|
+
nodePtyModule = null;
|
|
15159
|
+
return nodePtyModule;
|
|
15144
15160
|
}
|
|
15145
|
-
case "user_message_chunk":
|
|
15146
|
-
return [];
|
|
15147
|
-
case "plan":
|
|
15148
|
-
case "plan_update":
|
|
15149
|
-
case "plan_removed":
|
|
15150
|
-
case "available_commands_update":
|
|
15151
|
-
case "current_mode_update":
|
|
15152
|
-
case "config_option_update":
|
|
15153
|
-
case "session_info_update":
|
|
15154
|
-
case "usage_update":
|
|
15155
|
-
return [];
|
|
15156
|
-
default:
|
|
15157
|
-
return [];
|
|
15158
|
-
}
|
|
15159
|
-
}
|
|
15160
|
-
function mapPermissionRequest(request) {
|
|
15161
|
-
const prompt = describeToolCall(request.toolCall) ?? "The agent requested permission to continue.";
|
|
15162
|
-
const optionIdByLabel = {};
|
|
15163
|
-
const kindByLabel = {};
|
|
15164
|
-
const labels = [];
|
|
15165
|
-
for (const opt of request.options) {
|
|
15166
|
-
const label = opt.name?.trim() || humanizeKind(opt.kind);
|
|
15167
|
-
if (label in optionIdByLabel) continue;
|
|
15168
|
-
optionIdByLabel[label] = opt.optionId;
|
|
15169
|
-
kindByLabel[label] = opt.kind;
|
|
15170
|
-
labels.push(label);
|
|
15171
15161
|
}
|
|
15172
|
-
return {
|
|
15173
|
-
event: {
|
|
15174
|
-
questionId: (0, import_node_crypto5.randomUUID)(),
|
|
15175
|
-
prompt,
|
|
15176
|
-
options: labels.length > 0 ? labels : void 0
|
|
15177
|
-
},
|
|
15178
|
-
optionIdByLabel,
|
|
15179
|
-
kindByLabel
|
|
15180
|
-
};
|
|
15181
15162
|
}
|
|
15182
|
-
|
|
15183
|
-
|
|
15184
|
-
|
|
15163
|
+
var sessions = /* @__PURE__ */ new Map();
|
|
15164
|
+
var onDataHandler = null;
|
|
15165
|
+
var onExitHandler = null;
|
|
15166
|
+
function registerTerminalHandlers(opts) {
|
|
15167
|
+
onDataHandler = opts.onData;
|
|
15168
|
+
onExitHandler = opts.onExit;
|
|
15185
15169
|
}
|
|
15186
|
-
function
|
|
15187
|
-
if (
|
|
15188
|
-
|
|
15189
|
-
const t2 = content.text;
|
|
15190
|
-
return typeof t2 === "string" && t2.length > 0 ? t2 : null;
|
|
15170
|
+
function defaultShell() {
|
|
15171
|
+
if (process.platform === "win32") {
|
|
15172
|
+
return process.env.COMSPEC ?? "powershell.exe";
|
|
15191
15173
|
}
|
|
15192
|
-
return
|
|
15174
|
+
return process.env.SHELL ?? "/bin/bash";
|
|
15193
15175
|
}
|
|
15194
|
-
|
|
15195
|
-
|
|
15196
|
-
|
|
15197
|
-
|
|
15198
|
-
|
|
15199
|
-
|
|
15176
|
+
var PYTHON_TERMINAL_HELPER = `import os,pty,sys,select,signal,struct,fcntl,termios,errno,re
|
|
15177
|
+
m,s=pty.openpty()
|
|
15178
|
+
try:
|
|
15179
|
+
cols=int(os.environ.get('COLUMNS','80'))
|
|
15180
|
+
rows=int(os.environ.get('LINES','24'))
|
|
15181
|
+
fcntl.ioctl(s,termios.TIOCSWINSZ,struct.pack('HHHH',rows,cols,0,0))
|
|
15182
|
+
except Exception:pass
|
|
15183
|
+
pid=os.fork()
|
|
15184
|
+
if pid==0:
|
|
15185
|
+
os.close(m)
|
|
15186
|
+
os.setsid()
|
|
15187
|
+
try:fcntl.ioctl(s,termios.TIOCSCTTY,0)
|
|
15188
|
+
except Exception:pass
|
|
15189
|
+
for fd in[0,1,2]:os.dup2(s,fd)
|
|
15190
|
+
if s>2:os.close(s)
|
|
15191
|
+
os.execvp(sys.argv[1],sys.argv[1:])
|
|
15192
|
+
sys.exit(127)
|
|
15193
|
+
os.close(s)
|
|
15194
|
+
done=[False]
|
|
15195
|
+
def onchld(n,f):
|
|
15196
|
+
try:os.waitpid(pid,os.WNOHANG)
|
|
15197
|
+
except Exception:pass
|
|
15198
|
+
done[0]=True
|
|
15199
|
+
signal.signal(signal.SIGCHLD,onchld)
|
|
15200
|
+
signal.signal(signal.SIGHUP,signal.SIG_IGN)
|
|
15201
|
+
i=sys.stdin.fileno()
|
|
15202
|
+
o=sys.stdout.fileno()
|
|
15203
|
+
in_buf=b''
|
|
15204
|
+
resize_re=re.compile(rb'\\x00CW (\\d+) (\\d+)\\n')
|
|
15205
|
+
while not done[0]:
|
|
15206
|
+
try:r,_,_=select.select([i,m],[],[],0.1)
|
|
15207
|
+
except OSError as e:
|
|
15208
|
+
if e.errno==errno.EINTR:continue
|
|
15209
|
+
break
|
|
15210
|
+
if i in r:
|
|
15211
|
+
try:
|
|
15212
|
+
d=os.read(i,4096)
|
|
15213
|
+
if not d:break
|
|
15214
|
+
in_buf+=d
|
|
15215
|
+
while True:
|
|
15216
|
+
mo=resize_re.search(in_buf)
|
|
15217
|
+
if not mo:break
|
|
15218
|
+
try:
|
|
15219
|
+
rows=int(mo.group(1));cols=int(mo.group(2))
|
|
15220
|
+
fcntl.ioctl(m,termios.TIOCSWINSZ,struct.pack('HHHH',rows,cols,0,0))
|
|
15221
|
+
except Exception:pass
|
|
15222
|
+
in_buf=in_buf[:mo.start()]+in_buf[mo.end():]
|
|
15223
|
+
if in_buf:
|
|
15224
|
+
# Don't forward a dangling NUL that might be the
|
|
15225
|
+
# start of an incomplete resize marker \u2014 hold it
|
|
15226
|
+
# until the next read so the regex matches.
|
|
15227
|
+
nul=in_buf.rfind(b'\\x00')
|
|
15228
|
+
if nul>=0 and len(in_buf)-nul<32:
|
|
15229
|
+
tail=in_buf[nul:];body=in_buf[:nul]
|
|
15230
|
+
if body:os.write(m,body)
|
|
15231
|
+
in_buf=tail
|
|
15232
|
+
else:
|
|
15233
|
+
os.write(m,in_buf);in_buf=b''
|
|
15234
|
+
except OSError:break
|
|
15235
|
+
if m in r:
|
|
15236
|
+
try:
|
|
15237
|
+
d=os.read(m,4096)
|
|
15238
|
+
if d:os.write(o,d)
|
|
15239
|
+
except OSError:done[0]=True
|
|
15240
|
+
try:os.kill(pid,signal.SIGTERM)
|
|
15241
|
+
except Exception:pass
|
|
15242
|
+
try:
|
|
15243
|
+
_,st=os.waitpid(pid,0)
|
|
15244
|
+
sys.exit((st>>8)&0xFF)
|
|
15245
|
+
except Exception:sys.exit(0)
|
|
15246
|
+
`;
|
|
15247
|
+
function findPython3() {
|
|
15248
|
+
for (const name of ["python3", "python"]) {
|
|
15200
15249
|
try {
|
|
15201
|
-
const
|
|
15202
|
-
if (
|
|
15203
|
-
return summary;
|
|
15250
|
+
const out2 = require("child_process").spawnSync("which", [name], { encoding: "utf8" });
|
|
15251
|
+
if (out2.status === 0 && out2.stdout?.trim()) return out2.stdout.trim();
|
|
15204
15252
|
} catch {
|
|
15205
|
-
return null;
|
|
15206
15253
|
}
|
|
15207
15254
|
}
|
|
15208
15255
|
return null;
|
|
15209
15256
|
}
|
|
15210
|
-
function
|
|
15211
|
-
const
|
|
15212
|
-
if (
|
|
15213
|
-
for
|
|
15214
|
-
if (!item || typeof item !== "object") continue;
|
|
15215
|
-
if (item.type === "content" && item.content) {
|
|
15216
|
-
const text = extractText2(item.content);
|
|
15217
|
-
if (text) parts.push(text);
|
|
15218
|
-
} else if (item.type === "diff") {
|
|
15219
|
-
const p2 = item.path;
|
|
15220
|
-
parts.push(p2 ? `diff: ${p2}` : "diff");
|
|
15221
|
-
} else if (item.type === "terminal") {
|
|
15222
|
-
const id = item.terminalId;
|
|
15223
|
-
parts.push(id ? `terminal: ${id}` : "terminal");
|
|
15224
|
-
}
|
|
15225
|
-
}
|
|
15257
|
+
function createPythonSession(id, shell, cwd, env, cols, rows) {
|
|
15258
|
+
const python = findPython3();
|
|
15259
|
+
if (!python) {
|
|
15260
|
+
return { error: "python3 not found on PATH \u2014 required for terminal sessions on Linux/macOS without node-pty." };
|
|
15226
15261
|
}
|
|
15227
|
-
|
|
15228
|
-
|
|
15229
|
-
|
|
15230
|
-
|
|
15231
|
-
|
|
15232
|
-
|
|
15233
|
-
|
|
15234
|
-
|
|
15235
|
-
|
|
15236
|
-
|
|
15237
|
-
|
|
15238
|
-
|
|
15239
|
-
case "reject_always":
|
|
15240
|
-
return "Always reject";
|
|
15241
|
-
default:
|
|
15242
|
-
return kind;
|
|
15262
|
+
log.trace("terminal", `python helper spawn python=${python} shell=${shell} cwd=${cwd}`);
|
|
15263
|
+
let child;
|
|
15264
|
+
try {
|
|
15265
|
+
child = (0, import_child_process8.spawn)(python, ["-c", PYTHON_TERMINAL_HELPER, shell], {
|
|
15266
|
+
cwd,
|
|
15267
|
+
env: { ...env, COLUMNS: String(cols), LINES: String(rows) },
|
|
15268
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
15269
|
+
});
|
|
15270
|
+
} catch (e) {
|
|
15271
|
+
const msg = e instanceof Error ? e.message : "python spawn failed";
|
|
15272
|
+
log.warn("terminal", `python helper spawn failed: ${msg}`);
|
|
15273
|
+
return { error: msg };
|
|
15243
15274
|
}
|
|
15244
|
-
|
|
15245
|
-
|
|
15246
|
-
|
|
15247
|
-
|
|
15248
|
-
|
|
15249
|
-
|
|
15250
|
-
|
|
15251
|
-
|
|
15252
|
-
|
|
15253
|
-
|
|
15254
|
-
|
|
15255
|
-
|
|
15256
|
-
|
|
15257
|
-
|
|
15258
|
-
|
|
15259
|
-
|
|
15260
|
-
|
|
15275
|
+
child.stdout.on("data", (buf) => {
|
|
15276
|
+
onDataHandler?.({ sessionId: id, data: buf.toString("utf8") });
|
|
15277
|
+
});
|
|
15278
|
+
child.stderr.on("data", (buf) => {
|
|
15279
|
+
onDataHandler?.({ sessionId: id, data: buf.toString("utf8") });
|
|
15280
|
+
});
|
|
15281
|
+
child.on("exit", (code) => {
|
|
15282
|
+
onExitHandler?.({ sessionId: id, exitCode: code ?? 0 });
|
|
15283
|
+
sessions.delete(id);
|
|
15284
|
+
});
|
|
15285
|
+
return {
|
|
15286
|
+
id,
|
|
15287
|
+
write(data) {
|
|
15288
|
+
try {
|
|
15289
|
+
child.stdin.write(data);
|
|
15290
|
+
} catch {
|
|
15291
|
+
}
|
|
15292
|
+
},
|
|
15293
|
+
resize(cs, rs) {
|
|
15294
|
+
try {
|
|
15295
|
+
child.stdin.write(`\0CW ${rs} ${cs}
|
|
15296
|
+
`);
|
|
15297
|
+
} catch {
|
|
15298
|
+
}
|
|
15299
|
+
},
|
|
15300
|
+
kill() {
|
|
15301
|
+
try {
|
|
15302
|
+
child.kill("SIGTERM");
|
|
15303
|
+
} catch {
|
|
15304
|
+
}
|
|
15305
|
+
}
|
|
15306
|
+
};
|
|
15307
|
+
}
|
|
15308
|
+
function openTerminal(opts) {
|
|
15309
|
+
if (sessions.size >= MAX_CONCURRENT_SESSIONS) {
|
|
15310
|
+
return { error: `Too many open terminals (max ${MAX_CONCURRENT_SESSIONS})` };
|
|
15311
|
+
}
|
|
15312
|
+
const shell = opts.shell ?? defaultShell();
|
|
15313
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
15314
|
+
const env = {
|
|
15315
|
+
...process.env,
|
|
15316
|
+
TERM: "xterm-256color",
|
|
15317
|
+
COLORTERM: "truecolor",
|
|
15318
|
+
FORCE_COLOR: "1"
|
|
15319
|
+
};
|
|
15320
|
+
const cols = Math.max(1, Math.min(opts.cols ?? 80, 500));
|
|
15321
|
+
const rows = Math.max(1, Math.min(opts.rows ?? 24, 200));
|
|
15322
|
+
const id = (0, import_crypto2.randomUUID)();
|
|
15323
|
+
const ptyMod = loadNodePty2();
|
|
15324
|
+
if (ptyMod) {
|
|
15325
|
+
try {
|
|
15326
|
+
log.trace("terminal", `node-pty spawn shell=${shell} cwd=${cwd} cols=${cols} rows=${rows}`);
|
|
15327
|
+
const term = ptyMod.spawn(shell, [], {
|
|
15328
|
+
name: "xterm-256color",
|
|
15329
|
+
cols,
|
|
15330
|
+
rows,
|
|
15331
|
+
cwd,
|
|
15332
|
+
env,
|
|
15333
|
+
useConpty: process.platform === "win32" ? true : void 0
|
|
15334
|
+
});
|
|
15335
|
+
const dataListener = term.onData((data) => {
|
|
15336
|
+
onDataHandler?.({ sessionId: id, data });
|
|
15337
|
+
});
|
|
15338
|
+
const exitListener = term.onExit(({ exitCode }) => {
|
|
15339
|
+
onExitHandler?.({ sessionId: id, exitCode });
|
|
15340
|
+
sessions.delete(id);
|
|
15341
|
+
});
|
|
15342
|
+
sessions.set(id, {
|
|
15343
|
+
id,
|
|
15344
|
+
write: (d3) => term.write(d3),
|
|
15345
|
+
resize: (cs, rs) => term.resize(cs, rs),
|
|
15346
|
+
kill: () => {
|
|
15347
|
+
dataListener.dispose();
|
|
15348
|
+
exitListener.dispose();
|
|
15349
|
+
term.kill();
|
|
15350
|
+
}
|
|
15351
|
+
});
|
|
15352
|
+
return { sessionId: id };
|
|
15353
|
+
} catch (e) {
|
|
15354
|
+
const msg = e instanceof Error ? e.message : "spawn failed";
|
|
15355
|
+
log.warn("terminal", `node-pty spawn failed: ${msg} (falling back to Python helper on ${process.platform})`);
|
|
15356
|
+
if (process.platform === "win32") {
|
|
15357
|
+
return { error: msg };
|
|
15358
|
+
}
|
|
15359
|
+
}
|
|
15360
|
+
} else {
|
|
15361
|
+
log.warn("terminal", `node-pty unavailable on ${process.platform}-${process.arch}; falling back to Python helper`);
|
|
15362
|
+
if (process.platform === "win32") {
|
|
15363
|
+
return {
|
|
15364
|
+
error: `node-pty native module unavailable on ${process.platform}-${process.arch}; terminal feature disabled for this platform`
|
|
15365
|
+
};
|
|
15366
|
+
}
|
|
15367
|
+
}
|
|
15368
|
+
const sess = createPythonSession(id, shell, cwd, env, cols, rows);
|
|
15369
|
+
if ("error" in sess) {
|
|
15370
|
+
log.warn("terminal", `createPythonSession failed: ${sess.error}`);
|
|
15371
|
+
return { error: sess.error };
|
|
15372
|
+
}
|
|
15373
|
+
sessions.set(id, sess);
|
|
15374
|
+
return { sessionId: id };
|
|
15375
|
+
}
|
|
15376
|
+
function writeTerminal(sessionId, data) {
|
|
15377
|
+
const s = sessions.get(sessionId);
|
|
15378
|
+
if (!s) return { ok: false, error: "No such session" };
|
|
15379
|
+
try {
|
|
15380
|
+
s.write(data);
|
|
15381
|
+
return { ok: true };
|
|
15382
|
+
} catch (e) {
|
|
15383
|
+
return { ok: false, error: e instanceof Error ? e.message : "write failed" };
|
|
15384
|
+
}
|
|
15385
|
+
}
|
|
15386
|
+
function resizeTerminal(sessionId, cols, rows) {
|
|
15387
|
+
const s = sessions.get(sessionId);
|
|
15388
|
+
if (!s) return { ok: false, error: "No such session" };
|
|
15389
|
+
try {
|
|
15390
|
+
s.resize?.(Math.max(1, Math.min(cols, 500)), Math.max(1, Math.min(rows, 200)));
|
|
15391
|
+
return { ok: true };
|
|
15392
|
+
} catch (e) {
|
|
15393
|
+
return { ok: false, error: e instanceof Error ? e.message : "resize failed" };
|
|
15394
|
+
}
|
|
15395
|
+
}
|
|
15396
|
+
function closeTerminal(sessionId) {
|
|
15397
|
+
const s = sessions.get(sessionId);
|
|
15398
|
+
if (!s) return { ok: true };
|
|
15399
|
+
try {
|
|
15400
|
+
s.kill();
|
|
15401
|
+
} catch {
|
|
15402
|
+
}
|
|
15403
|
+
sessions.delete(sessionId);
|
|
15404
|
+
return { ok: true };
|
|
15405
|
+
}
|
|
15406
|
+
function closeAllTerminals() {
|
|
15407
|
+
for (const id of Array.from(sessions.keys())) closeTerminal(id);
|
|
15408
|
+
}
|
|
15409
|
+
|
|
15410
|
+
// src/agents/acp/mappers.ts
|
|
15411
|
+
var import_node_crypto5 = require("crypto");
|
|
15412
|
+
function mapSessionUpdate(notification) {
|
|
15413
|
+
const update = notification.update;
|
|
15414
|
+
switch (update.sessionUpdate) {
|
|
15415
|
+
case "agent_message_chunk": {
|
|
15416
|
+
const text = extractText2(update.content);
|
|
15417
|
+
if (!text) return [];
|
|
15418
|
+
return [{ chunkId: messageChunkId(update.messageId), kind: "text", delta: text }];
|
|
15419
|
+
}
|
|
15420
|
+
case "agent_thought_chunk": {
|
|
15421
|
+
const text = extractText2(update.content);
|
|
15422
|
+
if (!text) return [];
|
|
15423
|
+
return [{ chunkId: messageChunkId(update.messageId), kind: "thinking", delta: text }];
|
|
15424
|
+
}
|
|
15425
|
+
case "tool_call": {
|
|
15426
|
+
const summary = describeToolCall(update);
|
|
15427
|
+
if (!summary) return [];
|
|
15428
|
+
return [{ chunkId: update.toolCallId, kind: "tool_use", delta: summary }];
|
|
15429
|
+
}
|
|
15430
|
+
case "tool_call_update": {
|
|
15431
|
+
if (update.status !== "completed" && update.status !== "failed") {
|
|
15432
|
+
return [];
|
|
15433
|
+
}
|
|
15434
|
+
const body = describeToolCallUpdate(update);
|
|
15435
|
+
if (!body) return [];
|
|
15436
|
+
const prefix = update.status === "failed" ? "[failed] " : "";
|
|
15437
|
+
return [{ chunkId: update.toolCallId, kind: "tool_result", delta: prefix + body }];
|
|
15438
|
+
}
|
|
15439
|
+
case "user_message_chunk":
|
|
15440
|
+
return [];
|
|
15441
|
+
case "plan":
|
|
15442
|
+
case "plan_update":
|
|
15443
|
+
case "plan_removed":
|
|
15444
|
+
case "available_commands_update":
|
|
15445
|
+
case "current_mode_update":
|
|
15446
|
+
case "config_option_update":
|
|
15447
|
+
case "session_info_update":
|
|
15448
|
+
case "usage_update":
|
|
15449
|
+
return [];
|
|
15450
|
+
default:
|
|
15451
|
+
return [];
|
|
15452
|
+
}
|
|
15453
|
+
}
|
|
15454
|
+
function mapPermissionRequest(request) {
|
|
15455
|
+
const prompt = describeToolCall(request.toolCall) ?? "The agent requested permission to continue.";
|
|
15456
|
+
const optionIdByLabel = {};
|
|
15457
|
+
const kindByLabel = {};
|
|
15458
|
+
const labels = [];
|
|
15459
|
+
for (const opt of request.options) {
|
|
15460
|
+
const label = opt.name?.trim() || humanizeKind(opt.kind);
|
|
15461
|
+
if (label in optionIdByLabel) continue;
|
|
15462
|
+
optionIdByLabel[label] = opt.optionId;
|
|
15463
|
+
kindByLabel[label] = opt.kind;
|
|
15464
|
+
labels.push(label);
|
|
15465
|
+
}
|
|
15466
|
+
return {
|
|
15467
|
+
event: {
|
|
15468
|
+
questionId: (0, import_node_crypto5.randomUUID)(),
|
|
15469
|
+
prompt,
|
|
15470
|
+
options: labels.length > 0 ? labels : void 0
|
|
15471
|
+
},
|
|
15472
|
+
optionIdByLabel,
|
|
15473
|
+
kindByLabel
|
|
15474
|
+
};
|
|
15475
|
+
}
|
|
15476
|
+
function messageChunkId(messageId) {
|
|
15477
|
+
if (typeof messageId === "string" && messageId.length > 0) return messageId;
|
|
15478
|
+
return (0, import_node_crypto5.randomUUID)();
|
|
15479
|
+
}
|
|
15480
|
+
function extractText2(content) {
|
|
15481
|
+
if (!content || typeof content !== "object") return null;
|
|
15482
|
+
if ("type" in content && content.type === "text") {
|
|
15483
|
+
const t2 = content.text;
|
|
15484
|
+
return typeof t2 === "string" && t2.length > 0 ? t2 : null;
|
|
15485
|
+
}
|
|
15486
|
+
return null;
|
|
15487
|
+
}
|
|
15488
|
+
function describeToolCall(call) {
|
|
15489
|
+
const title = call.title?.trim();
|
|
15490
|
+
const kind = call.kind?.trim();
|
|
15491
|
+
if (title && title.length > 0) return title;
|
|
15492
|
+
if (kind && kind.length > 0) return kind;
|
|
15493
|
+
if (call.rawInput && typeof call.rawInput === "object") {
|
|
15494
|
+
try {
|
|
15495
|
+
const summary = JSON.stringify(call.rawInput);
|
|
15496
|
+
if (summary.length > 240) return `${summary.slice(0, 240)}\u2026`;
|
|
15497
|
+
return summary;
|
|
15498
|
+
} catch {
|
|
15499
|
+
return null;
|
|
15500
|
+
}
|
|
15501
|
+
}
|
|
15502
|
+
return null;
|
|
15503
|
+
}
|
|
15504
|
+
function describeToolCallUpdate(update) {
|
|
15505
|
+
const parts = [];
|
|
15506
|
+
if (Array.isArray(update.content)) {
|
|
15507
|
+
for (const item of update.content) {
|
|
15508
|
+
if (!item || typeof item !== "object") continue;
|
|
15509
|
+
if (item.type === "content" && item.content) {
|
|
15510
|
+
const text = extractText2(item.content);
|
|
15511
|
+
if (text) parts.push(text);
|
|
15512
|
+
} else if (item.type === "diff") {
|
|
15513
|
+
const p2 = item.path;
|
|
15514
|
+
parts.push(p2 ? `diff: ${p2}` : "diff");
|
|
15515
|
+
} else if (item.type === "terminal") {
|
|
15516
|
+
const id = item.terminalId;
|
|
15517
|
+
parts.push(id ? `terminal: ${id}` : "terminal");
|
|
15518
|
+
}
|
|
15519
|
+
}
|
|
15520
|
+
}
|
|
15521
|
+
if (parts.length > 0) return parts.join("\n");
|
|
15522
|
+
const title = update.title?.trim();
|
|
15523
|
+
return title && title.length > 0 ? title : null;
|
|
15524
|
+
}
|
|
15525
|
+
function humanizeKind(kind) {
|
|
15526
|
+
switch (kind) {
|
|
15527
|
+
case "allow_once":
|
|
15528
|
+
return "Allow once";
|
|
15529
|
+
case "allow_always":
|
|
15530
|
+
return "Always allow";
|
|
15531
|
+
case "reject_once":
|
|
15532
|
+
return "Reject";
|
|
15533
|
+
case "reject_always":
|
|
15534
|
+
return "Always reject";
|
|
15535
|
+
default:
|
|
15536
|
+
return kind;
|
|
15537
|
+
}
|
|
15538
|
+
}
|
|
15539
|
+
|
|
15540
|
+
// src/agents/acp/selectPromptExtractor.ts
|
|
15541
|
+
var MAX_OPTIONS = 6;
|
|
15542
|
+
var MAX_OPTION_BODY_CHARS = 200;
|
|
15543
|
+
var NUMBERED_LINE_RE = /^\s*(\d+)[.)\]]\s+(.+)$/;
|
|
15544
|
+
function extractSelectPrompt(text) {
|
|
15545
|
+
if (text.length === 0) return null;
|
|
15546
|
+
const lines = text.split("\n");
|
|
15547
|
+
let endIdx = lines.length - 1;
|
|
15548
|
+
while (endIdx >= 0 && lines[endIdx].trim() === "") endIdx--;
|
|
15549
|
+
if (endIdx < 0) return null;
|
|
15550
|
+
const items = [];
|
|
15551
|
+
let i = endIdx;
|
|
15552
|
+
while (i >= 0) {
|
|
15553
|
+
const m = lines[i].match(NUMBERED_LINE_RE);
|
|
15554
|
+
if (!m) break;
|
|
15261
15555
|
const body = m[2].trim();
|
|
15262
15556
|
if (body.length === 0 || body.length > MAX_OPTION_BODY_CHARS) {
|
|
15263
15557
|
return null;
|
|
@@ -15420,159 +15714,11 @@ function parsePayload2(schema, raw) {
|
|
|
15420
15714
|
|
|
15421
15715
|
// src/services/file-ops.service.ts
|
|
15422
15716
|
var fs22 = __toESM(require("fs/promises"));
|
|
15423
|
-
var path26 = __toESM(require("path"));
|
|
15424
|
-
var MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
15425
|
-
var MAX_WALK_DEPTH = 6;
|
|
15426
|
-
var MAX_VISITED_DIRS = 5e3;
|
|
15427
|
-
var SUBDIR_IGNORE = /* @__PURE__ */ new Set([
|
|
15428
|
-
"node_modules",
|
|
15429
|
-
".git",
|
|
15430
|
-
".next",
|
|
15431
|
-
".expo",
|
|
15432
|
-
"dist",
|
|
15433
|
-
"build",
|
|
15434
|
-
"out",
|
|
15435
|
-
".cache",
|
|
15436
|
-
"coverage",
|
|
15437
|
-
".turbo",
|
|
15438
|
-
".parcel-cache",
|
|
15439
|
-
".idea",
|
|
15440
|
-
".vscode",
|
|
15441
|
-
".vscode-test",
|
|
15442
|
-
"ios",
|
|
15443
|
-
"android",
|
|
15444
|
-
// expo-managed native dirs are huge and rarely interesting
|
|
15445
|
-
".gradle",
|
|
15446
|
-
".cxx",
|
|
15447
|
-
".intellijPlatform",
|
|
15448
|
-
".kotlin",
|
|
15449
|
-
"tmp",
|
|
15450
|
-
"target",
|
|
15451
|
-
"venv",
|
|
15452
|
-
".venv",
|
|
15453
|
-
".mypy_cache",
|
|
15454
|
-
".pytest_cache",
|
|
15455
|
-
"__pycache__"
|
|
15456
|
-
]);
|
|
15457
|
-
function isUnder(parent, candidate) {
|
|
15458
|
-
const rel = path26.relative(parent, candidate);
|
|
15459
|
-
return rel === "" || !rel.startsWith("..") && !path26.isAbsolute(rel);
|
|
15460
|
-
}
|
|
15461
|
-
async function isExistingFile(absPath) {
|
|
15462
|
-
try {
|
|
15463
|
-
const stat3 = await fs22.stat(absPath);
|
|
15464
|
-
return stat3.isFile();
|
|
15465
|
-
} catch {
|
|
15466
|
-
return false;
|
|
15467
|
-
}
|
|
15468
|
-
}
|
|
15469
|
-
async function walkForSuffix(dir, needleVariants, depth, ctx) {
|
|
15470
|
-
if (depth > MAX_WALK_DEPTH) return;
|
|
15471
|
-
if (ctx.visited > MAX_VISITED_DIRS) return;
|
|
15472
|
-
if (ctx.matches.length >= ctx.cap) return;
|
|
15473
|
-
ctx.visited++;
|
|
15474
|
-
let entries = [];
|
|
15475
|
-
try {
|
|
15476
|
-
entries = await fs22.readdir(dir, { withFileTypes: true });
|
|
15477
|
-
} catch {
|
|
15478
|
-
return;
|
|
15479
|
-
}
|
|
15480
|
-
for (const e of entries) {
|
|
15481
|
-
if (!e.isFile()) continue;
|
|
15482
|
-
const full = path26.join(dir, e.name);
|
|
15483
|
-
if (needleVariants.some((needle) => full.endsWith(needle))) {
|
|
15484
|
-
ctx.matches.push(full);
|
|
15485
|
-
if (ctx.matches.length >= ctx.cap) return;
|
|
15486
|
-
}
|
|
15487
|
-
}
|
|
15488
|
-
for (const e of entries) {
|
|
15489
|
-
if (!e.isDirectory()) continue;
|
|
15490
|
-
if (SUBDIR_IGNORE.has(e.name)) continue;
|
|
15491
|
-
if (e.name.startsWith(".") && SUBDIR_IGNORE.has(e.name)) continue;
|
|
15492
|
-
await walkForSuffix(path26.join(dir, e.name), needleVariants, depth + 1, ctx);
|
|
15493
|
-
if (ctx.matches.length >= ctx.cap) return;
|
|
15494
|
-
}
|
|
15495
|
-
}
|
|
15496
|
-
async function findFile(rawPath) {
|
|
15497
|
-
const cwd = process.cwd();
|
|
15498
|
-
if (path26.isAbsolute(rawPath)) {
|
|
15499
|
-
const abs = path26.normalize(rawPath);
|
|
15500
|
-
if (isUnder(cwd, abs) && await isExistingFile(abs)) return abs;
|
|
15501
|
-
}
|
|
15502
|
-
const direct = path26.resolve(cwd, rawPath);
|
|
15503
|
-
if (isUnder(cwd, direct) && await isExistingFile(direct)) return direct;
|
|
15504
|
-
const normalized = path26.normalize(rawPath).replace(/^[./\\]+/, "");
|
|
15505
|
-
const needles = [
|
|
15506
|
-
`${path26.sep}${normalized}`,
|
|
15507
|
-
`/${normalized}`
|
|
15508
|
-
].filter((v, i, a) => a.indexOf(v) === i);
|
|
15509
|
-
const ctx = { visited: 0, matches: [], cap: 16 };
|
|
15510
|
-
await walkForSuffix(cwd, needles, 0, ctx);
|
|
15511
|
-
const candidates = ctx.matches.filter((c2) => isUnder(cwd, c2));
|
|
15512
|
-
if (candidates.length === 0) return null;
|
|
15513
|
-
candidates.sort((a, b) => a.length - b.length);
|
|
15514
|
-
return candidates[0];
|
|
15515
|
-
}
|
|
15516
|
-
async function findWriteTarget(rawPath) {
|
|
15517
|
-
const found = await findFile(rawPath);
|
|
15518
|
-
if (found) return found;
|
|
15519
|
-
const cwd = process.cwd();
|
|
15520
|
-
const fallback = path26.isAbsolute(rawPath) ? path26.normalize(rawPath) : path26.resolve(cwd, rawPath);
|
|
15521
|
-
if (!isUnder(cwd, fallback)) return null;
|
|
15522
|
-
return fallback;
|
|
15523
|
-
}
|
|
15524
|
-
function looksBinary(buf) {
|
|
15525
|
-
const sample = buf.subarray(0, Math.min(8192, buf.length));
|
|
15526
|
-
for (let i = 0; i < sample.length; i++) {
|
|
15527
|
-
if (sample[i] === 0) return true;
|
|
15528
|
-
}
|
|
15529
|
-
return false;
|
|
15530
|
-
}
|
|
15531
|
-
async function readProjectFile(rawPath) {
|
|
15532
|
-
try {
|
|
15533
|
-
const abs = await findFile(rawPath);
|
|
15534
|
-
if (!abs) {
|
|
15535
|
-
return { error: `File not found in the project tree: ${rawPath}` };
|
|
15536
|
-
}
|
|
15537
|
-
const stat3 = await fs22.stat(abs);
|
|
15538
|
-
if (stat3.size > MAX_FILE_BYTES) {
|
|
15539
|
-
return { error: `File too large (${(stat3.size / 1024 / 1024).toFixed(1)} MB > ${MAX_FILE_BYTES / 1024 / 1024} MB).` };
|
|
15540
|
-
}
|
|
15541
|
-
const buf = await fs22.readFile(abs);
|
|
15542
|
-
if (looksBinary(buf)) {
|
|
15543
|
-
return { error: "Binary file \u2014 refusing to open in a code editor." };
|
|
15544
|
-
}
|
|
15545
|
-
return { content: buf.toString("utf-8") };
|
|
15546
|
-
} catch (e) {
|
|
15547
|
-
const msg = e instanceof Error ? e.message : "Read failed";
|
|
15548
|
-
return { error: msg };
|
|
15549
|
-
}
|
|
15550
|
-
}
|
|
15551
|
-
async function writeProjectFile(rawPath, content) {
|
|
15552
|
-
try {
|
|
15553
|
-
const abs = await findWriteTarget(rawPath);
|
|
15554
|
-
if (!abs) {
|
|
15555
|
-
return { error: `Path escapes the project root: ${rawPath}` };
|
|
15556
|
-
}
|
|
15557
|
-
if (Buffer.byteLength(content, "utf-8") > MAX_FILE_BYTES) {
|
|
15558
|
-
return { error: "Content too large." };
|
|
15559
|
-
}
|
|
15560
|
-
await fs22.mkdir(path26.dirname(abs), { recursive: true });
|
|
15561
|
-
await fs22.writeFile(abs, content, "utf-8");
|
|
15562
|
-
return { ok: true };
|
|
15563
|
-
} catch (e) {
|
|
15564
|
-
const msg = e instanceof Error ? e.message : "Write failed";
|
|
15565
|
-
return { error: msg };
|
|
15566
|
-
}
|
|
15567
|
-
}
|
|
15568
|
-
|
|
15569
|
-
// src/services/project-ops.service.ts
|
|
15570
|
-
var import_child_process8 = require("child_process");
|
|
15571
|
-
var import_util2 = require("util");
|
|
15572
|
-
var fs23 = __toESM(require("fs/promises"));
|
|
15573
15717
|
var path27 = __toESM(require("path"));
|
|
15574
|
-
var
|
|
15575
|
-
var
|
|
15718
|
+
var MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
15719
|
+
var MAX_WALK_DEPTH = 6;
|
|
15720
|
+
var MAX_VISITED_DIRS = 5e3;
|
|
15721
|
+
var SUBDIR_IGNORE = /* @__PURE__ */ new Set([
|
|
15576
15722
|
"node_modules",
|
|
15577
15723
|
".git",
|
|
15578
15724
|
".next",
|
|
@@ -15589,6 +15735,7 @@ var PROJECT_IGNORE = /* @__PURE__ */ new Set([
|
|
|
15589
15735
|
".vscode-test",
|
|
15590
15736
|
"ios",
|
|
15591
15737
|
"android",
|
|
15738
|
+
// expo-managed native dirs are huge and rarely interesting
|
|
15592
15739
|
".gradle",
|
|
15593
15740
|
".cxx",
|
|
15594
15741
|
".intellijPlatform",
|
|
@@ -15599,572 +15746,462 @@ var PROJECT_IGNORE = /* @__PURE__ */ new Set([
|
|
|
15599
15746
|
".venv",
|
|
15600
15747
|
".mypy_cache",
|
|
15601
15748
|
".pytest_cache",
|
|
15602
|
-
"__pycache__"
|
|
15603
|
-
".DS_Store"
|
|
15749
|
+
"__pycache__"
|
|
15604
15750
|
]);
|
|
15605
|
-
|
|
15606
|
-
|
|
15607
|
-
|
|
15608
|
-
async function listProjectFiles(opts = {}) {
|
|
15609
|
-
const root = opts.cwd ?? process.cwd();
|
|
15610
|
-
const cap = opts.cap ?? MAX_TREE_FILES;
|
|
15611
|
-
const q2 = (opts.query ?? "").trim().toLowerCase();
|
|
15612
|
-
const out2 = [];
|
|
15613
|
-
let truncated = false;
|
|
15614
|
-
async function walk(dir, depth) {
|
|
15615
|
-
if (out2.length >= cap) {
|
|
15616
|
-
truncated = true;
|
|
15617
|
-
return;
|
|
15618
|
-
}
|
|
15619
|
-
let entries = [];
|
|
15620
|
-
try {
|
|
15621
|
-
entries = await fs23.readdir(dir, { withFileTypes: true });
|
|
15622
|
-
} catch {
|
|
15623
|
-
return;
|
|
15624
|
-
}
|
|
15625
|
-
for (const e of entries) {
|
|
15626
|
-
if (out2.length >= cap) {
|
|
15627
|
-
truncated = true;
|
|
15628
|
-
return;
|
|
15629
|
-
}
|
|
15630
|
-
if (PROJECT_IGNORE.has(e.name)) continue;
|
|
15631
|
-
const full = path27.join(dir, e.name);
|
|
15632
|
-
if (e.isDirectory()) {
|
|
15633
|
-
if (depth >= 12) continue;
|
|
15634
|
-
await walk(full, depth + 1);
|
|
15635
|
-
} else if (e.isFile()) {
|
|
15636
|
-
const rel = path27.relative(root, full);
|
|
15637
|
-
if (q2 && !rel.toLowerCase().includes(q2) && !e.name.toLowerCase().includes(q2)) {
|
|
15638
|
-
continue;
|
|
15639
|
-
}
|
|
15640
|
-
let size = 0;
|
|
15641
|
-
try {
|
|
15642
|
-
const st3 = await fs23.stat(full);
|
|
15643
|
-
size = st3.size;
|
|
15644
|
-
} catch {
|
|
15645
|
-
}
|
|
15646
|
-
out2.push({ path: rel, name: e.name, size });
|
|
15647
|
-
}
|
|
15648
|
-
}
|
|
15649
|
-
}
|
|
15650
|
-
await walk(root, 0);
|
|
15651
|
-
out2.sort((a, b) => a.path.localeCompare(b.path));
|
|
15652
|
-
return { files: out2, truncated, root };
|
|
15751
|
+
function isUnder(parent, candidate) {
|
|
15752
|
+
const rel = path27.relative(parent, candidate);
|
|
15753
|
+
return rel === "" || !rel.startsWith("..") && !path27.isAbsolute(rel);
|
|
15653
15754
|
}
|
|
15654
|
-
async function
|
|
15755
|
+
async function isExistingFile(absPath) {
|
|
15655
15756
|
try {
|
|
15656
|
-
const
|
|
15657
|
-
|
|
15658
|
-
|
|
15659
|
-
|
|
15660
|
-
});
|
|
15661
|
-
return { stdout, stderr, code: 0 };
|
|
15662
|
-
} catch (err) {
|
|
15663
|
-
const e = err;
|
|
15664
|
-
return {
|
|
15665
|
-
stdout: e.stdout ?? "",
|
|
15666
|
-
stderr: e.stderr ?? e.message ?? "git failed",
|
|
15667
|
-
code: typeof e.code === "number" ? e.code : 1
|
|
15668
|
-
};
|
|
15757
|
+
const stat3 = await fs22.stat(absPath);
|
|
15758
|
+
return stat3.isFile();
|
|
15759
|
+
} catch {
|
|
15760
|
+
return false;
|
|
15669
15761
|
}
|
|
15670
15762
|
}
|
|
15671
|
-
async function
|
|
15672
|
-
|
|
15673
|
-
|
|
15674
|
-
if (
|
|
15675
|
-
|
|
15676
|
-
|
|
15677
|
-
upstream: null,
|
|
15678
|
-
ahead: 0,
|
|
15679
|
-
behind: 0,
|
|
15680
|
-
entries: [],
|
|
15681
|
-
hasMergeInProgress: false,
|
|
15682
|
-
error: r.stderr.trim()
|
|
15683
|
-
};
|
|
15684
|
-
}
|
|
15685
|
-
const lines = r.stdout.split("\n").filter(Boolean);
|
|
15686
|
-
let branch = null;
|
|
15687
|
-
let upstream = null;
|
|
15688
|
-
let ahead = 0;
|
|
15689
|
-
let behind = 0;
|
|
15690
|
-
const entries = [];
|
|
15691
|
-
for (const line of lines) {
|
|
15692
|
-
if (line.startsWith("# branch.head ")) branch = line.slice("# branch.head ".length).trim();
|
|
15693
|
-
else if (line.startsWith("# branch.upstream ")) upstream = line.slice("# branch.upstream ".length).trim();
|
|
15694
|
-
else if (line.startsWith("# branch.ab ")) {
|
|
15695
|
-
const m = line.match(/\+(\d+)\s+-(\d+)/);
|
|
15696
|
-
if (m) {
|
|
15697
|
-
ahead = parseInt(m[1], 10);
|
|
15698
|
-
behind = parseInt(m[2], 10);
|
|
15699
|
-
}
|
|
15700
|
-
} else if (line.startsWith("1 ")) {
|
|
15701
|
-
const parts = line.split(" ");
|
|
15702
|
-
const xy = parts[1];
|
|
15703
|
-
const p2 = parts.slice(8).join(" ");
|
|
15704
|
-
entries.push({
|
|
15705
|
-
code: xy,
|
|
15706
|
-
path: p2,
|
|
15707
|
-
staged: xy[0] !== ".",
|
|
15708
|
-
conflict: false
|
|
15709
|
-
});
|
|
15710
|
-
} else if (line.startsWith("2 ")) {
|
|
15711
|
-
const parts = line.split(" ");
|
|
15712
|
-
const xy = parts[1];
|
|
15713
|
-
const tail = parts.slice(9).join(" ");
|
|
15714
|
-
const [newPath, oldPath] = tail.split(" ");
|
|
15715
|
-
entries.push({
|
|
15716
|
-
code: xy,
|
|
15717
|
-
path: newPath ?? "",
|
|
15718
|
-
oldPath: oldPath ?? void 0,
|
|
15719
|
-
staged: xy[0] !== ".",
|
|
15720
|
-
conflict: false
|
|
15721
|
-
});
|
|
15722
|
-
} else if (line.startsWith("? ")) {
|
|
15723
|
-
entries.push({
|
|
15724
|
-
code: "??",
|
|
15725
|
-
path: line.slice(2),
|
|
15726
|
-
staged: false,
|
|
15727
|
-
conflict: false
|
|
15728
|
-
});
|
|
15729
|
-
} else if (line.startsWith("u ")) {
|
|
15730
|
-
const parts = line.split(" ");
|
|
15731
|
-
const xy = parts[1];
|
|
15732
|
-
const p2 = parts.slice(10).join(" ");
|
|
15733
|
-
entries.push({
|
|
15734
|
-
code: xy,
|
|
15735
|
-
path: p2,
|
|
15736
|
-
staged: false,
|
|
15737
|
-
conflict: true
|
|
15738
|
-
});
|
|
15739
|
-
}
|
|
15740
|
-
}
|
|
15741
|
-
let hasMergeInProgress = false;
|
|
15763
|
+
async function walkForSuffix(dir, needleVariants, depth, ctx) {
|
|
15764
|
+
if (depth > MAX_WALK_DEPTH) return;
|
|
15765
|
+
if (ctx.visited > MAX_VISITED_DIRS) return;
|
|
15766
|
+
if (ctx.matches.length >= ctx.cap) return;
|
|
15767
|
+
ctx.visited++;
|
|
15768
|
+
let entries = [];
|
|
15742
15769
|
try {
|
|
15743
|
-
|
|
15744
|
-
const mergeHead = path27.isAbsolute(gitDir) ? path27.join(gitDir, "MERGE_HEAD") : path27.join(root, gitDir, "MERGE_HEAD");
|
|
15745
|
-
await fs23.access(mergeHead);
|
|
15746
|
-
hasMergeInProgress = true;
|
|
15770
|
+
entries = await fs22.readdir(dir, { withFileTypes: true });
|
|
15747
15771
|
} catch {
|
|
15772
|
+
return;
|
|
15748
15773
|
}
|
|
15749
|
-
|
|
15750
|
-
|
|
15751
|
-
|
|
15752
|
-
|
|
15753
|
-
|
|
15754
|
-
|
|
15755
|
-
|
|
15756
|
-
return { diff: "", truncated: false, error: r.stderr.trim() };
|
|
15757
|
-
}
|
|
15758
|
-
const truncated = r.stdout.length >= MAX_DIFF_BYTES;
|
|
15759
|
-
return { diff: r.stdout.slice(0, MAX_DIFF_BYTES), truncated };
|
|
15760
|
-
}
|
|
15761
|
-
async function gitDiffStaged(file, cwd) {
|
|
15762
|
-
const args2 = ["diff", "--cached", "--no-color", "--patch"];
|
|
15763
|
-
if (file) args2.push("--", file);
|
|
15764
|
-
const r = await git(args2, cwd);
|
|
15765
|
-
if (r.code !== 0 && !r.stdout) {
|
|
15766
|
-
return { diff: "", truncated: false, error: r.stderr.trim() };
|
|
15767
|
-
}
|
|
15768
|
-
const truncated = r.stdout.length >= MAX_DIFF_BYTES;
|
|
15769
|
-
return { diff: r.stdout.slice(0, MAX_DIFF_BYTES), truncated };
|
|
15770
|
-
}
|
|
15771
|
-
async function gitLog(limit = 30, cwd) {
|
|
15772
|
-
const SEP = "";
|
|
15773
|
-
const fmt = `%H${SEP}%s${SEP}%an${SEP}%ct${SEP}%D`;
|
|
15774
|
-
const r = await git(["log", `-n${Math.min(limit, 200)}`, `--pretty=format:${fmt}`], cwd);
|
|
15775
|
-
if (r.code !== 0) return { commits: [], error: r.stderr.trim() };
|
|
15776
|
-
const commits = r.stdout.split("\n").filter(Boolean).map((line) => {
|
|
15777
|
-
const [sha, subject, author, ts, refs] = line.split(SEP);
|
|
15778
|
-
return {
|
|
15779
|
-
sha: sha ?? "",
|
|
15780
|
-
subject: subject ?? "",
|
|
15781
|
-
author: author ?? "",
|
|
15782
|
-
timestamp: parseInt(ts ?? "0", 10) * 1e3,
|
|
15783
|
-
refs: (refs ?? "").split(",").map((s) => s.trim().replace(/^HEAD -> /, "")).filter((s) => s.length > 0)
|
|
15784
|
-
};
|
|
15785
|
-
});
|
|
15786
|
-
return { commits };
|
|
15787
|
-
}
|
|
15788
|
-
async function gitCommit(message, files, cwd) {
|
|
15789
|
-
if (!message || message.trim().length === 0) {
|
|
15790
|
-
return { error: "Commit message is required." };
|
|
15791
|
-
}
|
|
15792
|
-
if (files && files.length > 0) {
|
|
15793
|
-
const r2 = await git(["add", "--", ...files], cwd);
|
|
15794
|
-
if (r2.code !== 0) return { error: `git add failed: ${r2.stderr.trim()}` };
|
|
15795
|
-
} else {
|
|
15796
|
-
const r2 = await git(["add", "-A"], cwd);
|
|
15797
|
-
if (r2.code !== 0) return { error: `git add failed: ${r2.stderr.trim()}` };
|
|
15798
|
-
}
|
|
15799
|
-
const r = await git(["commit", "-m", message], cwd);
|
|
15800
|
-
if (r.code !== 0) {
|
|
15801
|
-
return { error: r.stderr.trim() || "git commit failed" };
|
|
15774
|
+
for (const e of entries) {
|
|
15775
|
+
if (!e.isFile()) continue;
|
|
15776
|
+
const full = path27.join(dir, e.name);
|
|
15777
|
+
if (needleVariants.some((needle) => full.endsWith(needle))) {
|
|
15778
|
+
ctx.matches.push(full);
|
|
15779
|
+
if (ctx.matches.length >= ctx.cap) return;
|
|
15780
|
+
}
|
|
15802
15781
|
}
|
|
15803
|
-
const
|
|
15804
|
-
|
|
15805
|
-
|
|
15806
|
-
|
|
15807
|
-
|
|
15808
|
-
|
|
15809
|
-
return { ok: true, output: (r.stdout + r.stderr).trim() };
|
|
15810
|
-
}
|
|
15811
|
-
async function gitPull(cwd) {
|
|
15812
|
-
const r = await git(["pull", "--ff-only"], cwd);
|
|
15813
|
-
if (r.code !== 0) return { error: r.stderr.trim() || "git pull failed" };
|
|
15814
|
-
return { ok: true, output: (r.stdout + r.stderr).trim() };
|
|
15815
|
-
}
|
|
15816
|
-
async function gitResolve(file, side, cwd) {
|
|
15817
|
-
const r = await git(["checkout", `--${side}`, "--", file], cwd);
|
|
15818
|
-
if (r.code !== 0) return { error: r.stderr.trim() || `git checkout --${side} failed` };
|
|
15819
|
-
const add = await git(["add", "--", file], cwd);
|
|
15820
|
-
if (add.code !== 0) return { error: add.stderr.trim() || "git add (resolve) failed" };
|
|
15821
|
-
return { ok: true };
|
|
15822
|
-
}
|
|
15823
|
-
var MAX_SEARCH_HITS = 500;
|
|
15824
|
-
var MAX_SEARCH_BYTES = 256 * 1024;
|
|
15825
|
-
async function searchFiles(opts) {
|
|
15826
|
-
const cwd = opts.cwd ?? process.cwd();
|
|
15827
|
-
const cap = Math.min(opts.maxResults ?? MAX_SEARCH_HITS, MAX_SEARCH_HITS);
|
|
15828
|
-
if (!opts.query.trim()) return { hits: [], total: 0, truncated: false };
|
|
15829
|
-
const args2 = ["grep", "-n", "--column", "-I"];
|
|
15830
|
-
if (!opts.caseSensitive) args2.push("-i");
|
|
15831
|
-
if (opts.wholeWord) args2.push("-w");
|
|
15832
|
-
if (opts.regex) args2.push("-E");
|
|
15833
|
-
else args2.push("-F");
|
|
15834
|
-
args2.push(opts.query);
|
|
15835
|
-
if (opts.include && opts.include.length > 0) {
|
|
15836
|
-
args2.push("--");
|
|
15837
|
-
for (const p2 of opts.include) args2.push(p2);
|
|
15838
|
-
} else if (opts.exclude && opts.exclude.length > 0) {
|
|
15839
|
-
args2.push("--");
|
|
15840
|
-
args2.push(".");
|
|
15782
|
+
for (const e of entries) {
|
|
15783
|
+
if (!e.isDirectory()) continue;
|
|
15784
|
+
if (SUBDIR_IGNORE.has(e.name)) continue;
|
|
15785
|
+
if (e.name.startsWith(".") && SUBDIR_IGNORE.has(e.name)) continue;
|
|
15786
|
+
await walkForSuffix(path27.join(dir, e.name), needleVariants, depth + 1, ctx);
|
|
15787
|
+
if (ctx.matches.length >= ctx.cap) return;
|
|
15841
15788
|
}
|
|
15842
|
-
|
|
15843
|
-
|
|
15844
|
-
|
|
15845
|
-
|
|
15789
|
+
}
|
|
15790
|
+
async function findFile(rawPath) {
|
|
15791
|
+
const cwd = process.cwd();
|
|
15792
|
+
if (path27.isAbsolute(rawPath)) {
|
|
15793
|
+
const abs = path27.normalize(rawPath);
|
|
15794
|
+
if (isUnder(cwd, abs) && await isExistingFile(abs)) return abs;
|
|
15846
15795
|
}
|
|
15847
|
-
const
|
|
15848
|
-
|
|
15849
|
-
|
|
15850
|
-
|
|
15851
|
-
|
|
15852
|
-
|
|
15853
|
-
|
|
15854
|
-
|
|
15855
|
-
|
|
15856
|
-
|
|
15857
|
-
|
|
15858
|
-
|
|
15859
|
-
|
|
15860
|
-
|
|
15861
|
-
|
|
15862
|
-
|
|
15863
|
-
|
|
15864
|
-
|
|
15865
|
-
|
|
15866
|
-
|
|
15867
|
-
|
|
15868
|
-
|
|
15869
|
-
|
|
15870
|
-
|
|
15871
|
-
|
|
15872
|
-
|
|
15873
|
-
text,
|
|
15874
|
-
matchLength: opts.query.length
|
|
15875
|
-
});
|
|
15796
|
+
const direct = path27.resolve(cwd, rawPath);
|
|
15797
|
+
if (isUnder(cwd, direct) && await isExistingFile(direct)) return direct;
|
|
15798
|
+
const normalized = path27.normalize(rawPath).replace(/^[./\\]+/, "");
|
|
15799
|
+
const needles = [
|
|
15800
|
+
`${path27.sep}${normalized}`,
|
|
15801
|
+
`/${normalized}`
|
|
15802
|
+
].filter((v, i, a) => a.indexOf(v) === i);
|
|
15803
|
+
const ctx = { visited: 0, matches: [], cap: 16 };
|
|
15804
|
+
await walkForSuffix(cwd, needles, 0, ctx);
|
|
15805
|
+
const candidates = ctx.matches.filter((c2) => isUnder(cwd, c2));
|
|
15806
|
+
if (candidates.length === 0) return null;
|
|
15807
|
+
candidates.sort((a, b) => a.length - b.length);
|
|
15808
|
+
return candidates[0];
|
|
15809
|
+
}
|
|
15810
|
+
async function findWriteTarget(rawPath) {
|
|
15811
|
+
const found = await findFile(rawPath);
|
|
15812
|
+
if (found) return found;
|
|
15813
|
+
const cwd = process.cwd();
|
|
15814
|
+
const fallback = path27.isAbsolute(rawPath) ? path27.normalize(rawPath) : path27.resolve(cwd, rawPath);
|
|
15815
|
+
if (!isUnder(cwd, fallback)) return null;
|
|
15816
|
+
return fallback;
|
|
15817
|
+
}
|
|
15818
|
+
function looksBinary(buf) {
|
|
15819
|
+
const sample = buf.subarray(0, Math.min(8192, buf.length));
|
|
15820
|
+
for (let i = 0; i < sample.length; i++) {
|
|
15821
|
+
if (sample[i] === 0) return true;
|
|
15876
15822
|
}
|
|
15877
|
-
return
|
|
15823
|
+
return false;
|
|
15878
15824
|
}
|
|
15879
|
-
async function
|
|
15880
|
-
|
|
15881
|
-
|
|
15882
|
-
|
|
15883
|
-
|
|
15884
|
-
for (const f of files.files) {
|
|
15885
|
-
if (hits.length >= cap) {
|
|
15886
|
-
truncated = true;
|
|
15887
|
-
break;
|
|
15825
|
+
async function readProjectFile(rawPath) {
|
|
15826
|
+
try {
|
|
15827
|
+
const abs = await findFile(rawPath);
|
|
15828
|
+
if (!abs) {
|
|
15829
|
+
return { error: `File not found in the project tree: ${rawPath}` };
|
|
15888
15830
|
}
|
|
15889
|
-
|
|
15890
|
-
|
|
15891
|
-
|
|
15892
|
-
} catch {
|
|
15893
|
-
continue;
|
|
15831
|
+
const stat3 = await fs22.stat(abs);
|
|
15832
|
+
if (stat3.size > MAX_FILE_BYTES) {
|
|
15833
|
+
return { error: `File too large (${(stat3.size / 1024 / 1024).toFixed(1)} MB > ${MAX_FILE_BYTES / 1024 / 1024} MB).` };
|
|
15894
15834
|
}
|
|
15895
|
-
const
|
|
15896
|
-
|
|
15897
|
-
|
|
15898
|
-
const hay = opts.caseSensitive ? line : line.toLowerCase();
|
|
15899
|
-
const idx = hay.indexOf(needle);
|
|
15900
|
-
if (idx === -1) continue;
|
|
15901
|
-
hits.push({
|
|
15902
|
-
path: f.path,
|
|
15903
|
-
line: i + 1,
|
|
15904
|
-
column: idx + 1,
|
|
15905
|
-
text: line.slice(0, 400),
|
|
15906
|
-
matchLength: opts.query.length
|
|
15907
|
-
});
|
|
15835
|
+
const buf = await fs22.readFile(abs);
|
|
15836
|
+
if (looksBinary(buf)) {
|
|
15837
|
+
return { error: "Binary file \u2014 refusing to open in a code editor." };
|
|
15908
15838
|
}
|
|
15839
|
+
return { content: buf.toString("utf-8") };
|
|
15840
|
+
} catch (e) {
|
|
15841
|
+
const msg = e instanceof Error ? e.message : "Read failed";
|
|
15842
|
+
return { error: msg };
|
|
15909
15843
|
}
|
|
15910
|
-
return { hits, total: hits.length, truncated };
|
|
15911
15844
|
}
|
|
15912
|
-
|
|
15913
|
-
// src/services/terminal-ops.service.ts
|
|
15914
|
-
var import_child_process9 = require("child_process");
|
|
15915
|
-
var import_crypto2 = require("crypto");
|
|
15916
|
-
var import_path3 = __toESM(require("path"));
|
|
15917
|
-
var MAX_CONCURRENT_SESSIONS = 4;
|
|
15918
|
-
var nodePtyModule;
|
|
15919
|
-
function loadNodePty2() {
|
|
15920
|
-
if (nodePtyModule !== void 0) return nodePtyModule;
|
|
15921
|
-
const vendoredPath = import_path3.default.join(__dirname, "vendor", "node-pty");
|
|
15845
|
+
async function writeProjectFile(rawPath, content) {
|
|
15922
15846
|
try {
|
|
15923
|
-
|
|
15924
|
-
|
|
15925
|
-
|
|
15926
|
-
try {
|
|
15927
|
-
nodePtyModule = require("node-pty");
|
|
15928
|
-
return nodePtyModule;
|
|
15929
|
-
} catch {
|
|
15930
|
-
nodePtyModule = null;
|
|
15931
|
-
return nodePtyModule;
|
|
15847
|
+
const abs = await findWriteTarget(rawPath);
|
|
15848
|
+
if (!abs) {
|
|
15849
|
+
return { error: `Path escapes the project root: ${rawPath}` };
|
|
15932
15850
|
}
|
|
15851
|
+
if (Buffer.byteLength(content, "utf-8") > MAX_FILE_BYTES) {
|
|
15852
|
+
return { error: "Content too large." };
|
|
15853
|
+
}
|
|
15854
|
+
await fs22.mkdir(path27.dirname(abs), { recursive: true });
|
|
15855
|
+
await fs22.writeFile(abs, content, "utf-8");
|
|
15856
|
+
return { ok: true };
|
|
15857
|
+
} catch (e) {
|
|
15858
|
+
const msg = e instanceof Error ? e.message : "Write failed";
|
|
15859
|
+
return { error: msg };
|
|
15933
15860
|
}
|
|
15934
15861
|
}
|
|
15935
|
-
|
|
15936
|
-
|
|
15937
|
-
var
|
|
15938
|
-
|
|
15939
|
-
|
|
15940
|
-
|
|
15941
|
-
|
|
15942
|
-
|
|
15943
|
-
|
|
15944
|
-
|
|
15945
|
-
|
|
15946
|
-
|
|
15947
|
-
|
|
15948
|
-
|
|
15949
|
-
|
|
15950
|
-
|
|
15951
|
-
|
|
15952
|
-
|
|
15953
|
-
|
|
15954
|
-
|
|
15955
|
-
|
|
15956
|
-
|
|
15957
|
-
|
|
15958
|
-
|
|
15959
|
-
|
|
15960
|
-
|
|
15961
|
-
|
|
15962
|
-
|
|
15963
|
-
|
|
15964
|
-
|
|
15965
|
-
|
|
15966
|
-
|
|
15967
|
-
|
|
15968
|
-
|
|
15969
|
-
|
|
15970
|
-
|
|
15971
|
-
|
|
15972
|
-
|
|
15973
|
-
|
|
15974
|
-
|
|
15975
|
-
|
|
15976
|
-
|
|
15977
|
-
|
|
15978
|
-
|
|
15979
|
-
|
|
15980
|
-
|
|
15981
|
-
|
|
15982
|
-
if
|
|
15983
|
-
|
|
15984
|
-
|
|
15985
|
-
|
|
15986
|
-
|
|
15987
|
-
while True:
|
|
15988
|
-
mo=resize_re.search(in_buf)
|
|
15989
|
-
if not mo:break
|
|
15990
|
-
try:
|
|
15991
|
-
rows=int(mo.group(1));cols=int(mo.group(2))
|
|
15992
|
-
fcntl.ioctl(m,termios.TIOCSWINSZ,struct.pack('HHHH',rows,cols,0,0))
|
|
15993
|
-
except Exception:pass
|
|
15994
|
-
in_buf=in_buf[:mo.start()]+in_buf[mo.end():]
|
|
15995
|
-
if in_buf:
|
|
15996
|
-
# Don't forward a dangling NUL that might be the
|
|
15997
|
-
# start of an incomplete resize marker \u2014 hold it
|
|
15998
|
-
# until the next read so the regex matches.
|
|
15999
|
-
nul=in_buf.rfind(b'\\x00')
|
|
16000
|
-
if nul>=0 and len(in_buf)-nul<32:
|
|
16001
|
-
tail=in_buf[nul:];body=in_buf[:nul]
|
|
16002
|
-
if body:os.write(m,body)
|
|
16003
|
-
in_buf=tail
|
|
16004
|
-
else:
|
|
16005
|
-
os.write(m,in_buf);in_buf=b''
|
|
16006
|
-
except OSError:break
|
|
16007
|
-
if m in r:
|
|
16008
|
-
try:
|
|
16009
|
-
d=os.read(m,4096)
|
|
16010
|
-
if d:os.write(o,d)
|
|
16011
|
-
except OSError:done[0]=True
|
|
16012
|
-
try:os.kill(pid,signal.SIGTERM)
|
|
16013
|
-
except Exception:pass
|
|
16014
|
-
try:
|
|
16015
|
-
_,st=os.waitpid(pid,0)
|
|
16016
|
-
sys.exit((st>>8)&0xFF)
|
|
16017
|
-
except Exception:sys.exit(0)
|
|
16018
|
-
`;
|
|
16019
|
-
function findPython3() {
|
|
16020
|
-
for (const name of ["python3", "python"]) {
|
|
15862
|
+
|
|
15863
|
+
// src/services/project-ops.service.ts
|
|
15864
|
+
var import_child_process9 = require("child_process");
|
|
15865
|
+
var import_util2 = require("util");
|
|
15866
|
+
var fs23 = __toESM(require("fs/promises"));
|
|
15867
|
+
var path28 = __toESM(require("path"));
|
|
15868
|
+
var execFileP3 = (0, import_util2.promisify)(import_child_process9.execFile);
|
|
15869
|
+
var PROJECT_IGNORE = /* @__PURE__ */ new Set([
|
|
15870
|
+
"node_modules",
|
|
15871
|
+
".git",
|
|
15872
|
+
".next",
|
|
15873
|
+
".expo",
|
|
15874
|
+
"dist",
|
|
15875
|
+
"build",
|
|
15876
|
+
"out",
|
|
15877
|
+
".cache",
|
|
15878
|
+
"coverage",
|
|
15879
|
+
".turbo",
|
|
15880
|
+
".parcel-cache",
|
|
15881
|
+
".idea",
|
|
15882
|
+
".vscode",
|
|
15883
|
+
".vscode-test",
|
|
15884
|
+
"ios",
|
|
15885
|
+
"android",
|
|
15886
|
+
".gradle",
|
|
15887
|
+
".cxx",
|
|
15888
|
+
".intellijPlatform",
|
|
15889
|
+
".kotlin",
|
|
15890
|
+
"tmp",
|
|
15891
|
+
"target",
|
|
15892
|
+
"venv",
|
|
15893
|
+
".venv",
|
|
15894
|
+
".mypy_cache",
|
|
15895
|
+
".pytest_cache",
|
|
15896
|
+
"__pycache__",
|
|
15897
|
+
".DS_Store"
|
|
15898
|
+
]);
|
|
15899
|
+
var MAX_TREE_FILES = 5e4;
|
|
15900
|
+
var MAX_DIFF_BYTES = 512 * 1024;
|
|
15901
|
+
var MAX_GIT_OUTPUT = 256 * 1024;
|
|
15902
|
+
async function listProjectFiles(opts = {}) {
|
|
15903
|
+
const root = opts.cwd ?? process.cwd();
|
|
15904
|
+
const cap = opts.cap ?? MAX_TREE_FILES;
|
|
15905
|
+
const q2 = (opts.query ?? "").trim().toLowerCase();
|
|
15906
|
+
const out2 = [];
|
|
15907
|
+
let truncated = false;
|
|
15908
|
+
async function walk(dir, depth) {
|
|
15909
|
+
if (out2.length >= cap) {
|
|
15910
|
+
truncated = true;
|
|
15911
|
+
return;
|
|
15912
|
+
}
|
|
15913
|
+
let entries = [];
|
|
16021
15914
|
try {
|
|
16022
|
-
|
|
16023
|
-
if (out2.status === 0 && out2.stdout?.trim()) return out2.stdout.trim();
|
|
15915
|
+
entries = await fs23.readdir(dir, { withFileTypes: true });
|
|
16024
15916
|
} catch {
|
|
15917
|
+
return;
|
|
15918
|
+
}
|
|
15919
|
+
for (const e of entries) {
|
|
15920
|
+
if (out2.length >= cap) {
|
|
15921
|
+
truncated = true;
|
|
15922
|
+
return;
|
|
15923
|
+
}
|
|
15924
|
+
if (PROJECT_IGNORE.has(e.name)) continue;
|
|
15925
|
+
const full = path28.join(dir, e.name);
|
|
15926
|
+
if (e.isDirectory()) {
|
|
15927
|
+
if (depth >= 12) continue;
|
|
15928
|
+
await walk(full, depth + 1);
|
|
15929
|
+
} else if (e.isFile()) {
|
|
15930
|
+
const rel = path28.relative(root, full);
|
|
15931
|
+
if (q2 && !rel.toLowerCase().includes(q2) && !e.name.toLowerCase().includes(q2)) {
|
|
15932
|
+
continue;
|
|
15933
|
+
}
|
|
15934
|
+
let size = 0;
|
|
15935
|
+
try {
|
|
15936
|
+
const st3 = await fs23.stat(full);
|
|
15937
|
+
size = st3.size;
|
|
15938
|
+
} catch {
|
|
15939
|
+
}
|
|
15940
|
+
out2.push({ path: rel, name: e.name, size });
|
|
15941
|
+
}
|
|
16025
15942
|
}
|
|
16026
15943
|
}
|
|
16027
|
-
|
|
15944
|
+
await walk(root, 0);
|
|
15945
|
+
out2.sort((a, b) => a.path.localeCompare(b.path));
|
|
15946
|
+
return { files: out2, truncated, root };
|
|
16028
15947
|
}
|
|
16029
|
-
function
|
|
16030
|
-
const python = findPython3();
|
|
16031
|
-
if (!python) {
|
|
16032
|
-
return { error: "python3 not found on PATH \u2014 required for terminal sessions on Linux/macOS without node-pty." };
|
|
16033
|
-
}
|
|
16034
|
-
let child;
|
|
15948
|
+
async function git(args2, cwd) {
|
|
16035
15949
|
try {
|
|
16036
|
-
|
|
16037
|
-
cwd,
|
|
16038
|
-
|
|
16039
|
-
|
|
15950
|
+
const { stdout, stderr } = await execFileP3("git", args2, {
|
|
15951
|
+
cwd: cwd ?? process.cwd(),
|
|
15952
|
+
maxBuffer: MAX_GIT_OUTPUT,
|
|
15953
|
+
timeout: 3e4
|
|
16040
15954
|
});
|
|
16041
|
-
|
|
16042
|
-
|
|
15955
|
+
return { stdout, stderr, code: 0 };
|
|
15956
|
+
} catch (err) {
|
|
15957
|
+
const e = err;
|
|
15958
|
+
return {
|
|
15959
|
+
stdout: e.stdout ?? "",
|
|
15960
|
+
stderr: e.stderr ?? e.message ?? "git failed",
|
|
15961
|
+
code: typeof e.code === "number" ? e.code : 1
|
|
15962
|
+
};
|
|
16043
15963
|
}
|
|
16044
|
-
child.stdout.on("data", (buf) => {
|
|
16045
|
-
onDataHandler?.({ sessionId: id, data: buf.toString("utf8") });
|
|
16046
|
-
});
|
|
16047
|
-
child.stderr.on("data", (buf) => {
|
|
16048
|
-
onDataHandler?.({ sessionId: id, data: buf.toString("utf8") });
|
|
16049
|
-
});
|
|
16050
|
-
child.on("exit", (code) => {
|
|
16051
|
-
onExitHandler?.({ sessionId: id, exitCode: code ?? 0 });
|
|
16052
|
-
sessions.delete(id);
|
|
16053
|
-
});
|
|
16054
|
-
return {
|
|
16055
|
-
id,
|
|
16056
|
-
write(data) {
|
|
16057
|
-
try {
|
|
16058
|
-
child.stdin.write(data);
|
|
16059
|
-
} catch {
|
|
16060
|
-
}
|
|
16061
|
-
},
|
|
16062
|
-
resize(cs, rs) {
|
|
16063
|
-
try {
|
|
16064
|
-
child.stdin.write(`\0CW ${rs} ${cs}
|
|
16065
|
-
`);
|
|
16066
|
-
} catch {
|
|
16067
|
-
}
|
|
16068
|
-
},
|
|
16069
|
-
kill() {
|
|
16070
|
-
try {
|
|
16071
|
-
child.kill("SIGTERM");
|
|
16072
|
-
} catch {
|
|
16073
|
-
}
|
|
16074
|
-
}
|
|
16075
|
-
};
|
|
16076
15964
|
}
|
|
16077
|
-
function
|
|
16078
|
-
|
|
16079
|
-
|
|
15965
|
+
async function gitStatus(cwd) {
|
|
15966
|
+
const root = cwd ?? process.cwd();
|
|
15967
|
+
const r = await git(["status", "--porcelain=v2", "--branch"], root);
|
|
15968
|
+
if (r.code !== 0) {
|
|
15969
|
+
return {
|
|
15970
|
+
branch: null,
|
|
15971
|
+
upstream: null,
|
|
15972
|
+
ahead: 0,
|
|
15973
|
+
behind: 0,
|
|
15974
|
+
entries: [],
|
|
15975
|
+
hasMergeInProgress: false,
|
|
15976
|
+
error: r.stderr.trim()
|
|
15977
|
+
};
|
|
16080
15978
|
}
|
|
16081
|
-
const
|
|
16082
|
-
|
|
16083
|
-
|
|
16084
|
-
|
|
16085
|
-
|
|
16086
|
-
|
|
16087
|
-
|
|
16088
|
-
|
|
16089
|
-
|
|
16090
|
-
|
|
16091
|
-
|
|
16092
|
-
|
|
16093
|
-
|
|
16094
|
-
|
|
16095
|
-
|
|
16096
|
-
|
|
16097
|
-
|
|
16098
|
-
|
|
16099
|
-
|
|
16100
|
-
|
|
16101
|
-
|
|
15979
|
+
const lines = r.stdout.split("\n").filter(Boolean);
|
|
15980
|
+
let branch = null;
|
|
15981
|
+
let upstream = null;
|
|
15982
|
+
let ahead = 0;
|
|
15983
|
+
let behind = 0;
|
|
15984
|
+
const entries = [];
|
|
15985
|
+
for (const line of lines) {
|
|
15986
|
+
if (line.startsWith("# branch.head ")) branch = line.slice("# branch.head ".length).trim();
|
|
15987
|
+
else if (line.startsWith("# branch.upstream ")) upstream = line.slice("# branch.upstream ".length).trim();
|
|
15988
|
+
else if (line.startsWith("# branch.ab ")) {
|
|
15989
|
+
const m = line.match(/\+(\d+)\s+-(\d+)/);
|
|
15990
|
+
if (m) {
|
|
15991
|
+
ahead = parseInt(m[1], 10);
|
|
15992
|
+
behind = parseInt(m[2], 10);
|
|
15993
|
+
}
|
|
15994
|
+
} else if (line.startsWith("1 ")) {
|
|
15995
|
+
const parts = line.split(" ");
|
|
15996
|
+
const xy = parts[1];
|
|
15997
|
+
const p2 = parts.slice(8).join(" ");
|
|
15998
|
+
entries.push({
|
|
15999
|
+
code: xy,
|
|
16000
|
+
path: p2,
|
|
16001
|
+
staged: xy[0] !== ".",
|
|
16002
|
+
conflict: false
|
|
16102
16003
|
});
|
|
16103
|
-
|
|
16104
|
-
|
|
16004
|
+
} else if (line.startsWith("2 ")) {
|
|
16005
|
+
const parts = line.split(" ");
|
|
16006
|
+
const xy = parts[1];
|
|
16007
|
+
const tail = parts.slice(9).join(" ");
|
|
16008
|
+
const [newPath, oldPath] = tail.split(" ");
|
|
16009
|
+
entries.push({
|
|
16010
|
+
code: xy,
|
|
16011
|
+
path: newPath ?? "",
|
|
16012
|
+
oldPath: oldPath ?? void 0,
|
|
16013
|
+
staged: xy[0] !== ".",
|
|
16014
|
+
conflict: false
|
|
16105
16015
|
});
|
|
16106
|
-
|
|
16107
|
-
|
|
16108
|
-
|
|
16016
|
+
} else if (line.startsWith("? ")) {
|
|
16017
|
+
entries.push({
|
|
16018
|
+
code: "??",
|
|
16019
|
+
path: line.slice(2),
|
|
16020
|
+
staged: false,
|
|
16021
|
+
conflict: false
|
|
16109
16022
|
});
|
|
16110
|
-
|
|
16111
|
-
|
|
16112
|
-
|
|
16113
|
-
|
|
16114
|
-
|
|
16115
|
-
|
|
16116
|
-
|
|
16117
|
-
|
|
16118
|
-
|
|
16023
|
+
} else if (line.startsWith("u ")) {
|
|
16024
|
+
const parts = line.split(" ");
|
|
16025
|
+
const xy = parts[1];
|
|
16026
|
+
const p2 = parts.slice(10).join(" ");
|
|
16027
|
+
entries.push({
|
|
16028
|
+
code: xy,
|
|
16029
|
+
path: p2,
|
|
16030
|
+
staged: false,
|
|
16031
|
+
conflict: true
|
|
16119
16032
|
});
|
|
16120
|
-
return { sessionId: id };
|
|
16121
|
-
} catch (e) {
|
|
16122
|
-
if (process.platform === "win32") {
|
|
16123
|
-
return { error: e instanceof Error ? e.message : "spawn failed" };
|
|
16124
|
-
}
|
|
16125
16033
|
}
|
|
16126
|
-
}
|
|
16034
|
+
}
|
|
16035
|
+
let hasMergeInProgress = false;
|
|
16036
|
+
try {
|
|
16037
|
+
const gitDir = (await git(["rev-parse", "--git-dir"], root)).stdout.trim();
|
|
16038
|
+
const mergeHead = path28.isAbsolute(gitDir) ? path28.join(gitDir, "MERGE_HEAD") : path28.join(root, gitDir, "MERGE_HEAD");
|
|
16039
|
+
await fs23.access(mergeHead);
|
|
16040
|
+
hasMergeInProgress = true;
|
|
16041
|
+
} catch {
|
|
16042
|
+
}
|
|
16043
|
+
return { branch, upstream, ahead, behind, entries, hasMergeInProgress };
|
|
16044
|
+
}
|
|
16045
|
+
async function gitDiff(file, cwd) {
|
|
16046
|
+
const args2 = ["diff", "--no-color", "--patch"];
|
|
16047
|
+
if (file) args2.push("--", file);
|
|
16048
|
+
const r = await git(args2, cwd);
|
|
16049
|
+
if (r.code !== 0 && !r.stdout) {
|
|
16050
|
+
return { diff: "", truncated: false, error: r.stderr.trim() };
|
|
16051
|
+
}
|
|
16052
|
+
const truncated = r.stdout.length >= MAX_DIFF_BYTES;
|
|
16053
|
+
return { diff: r.stdout.slice(0, MAX_DIFF_BYTES), truncated };
|
|
16054
|
+
}
|
|
16055
|
+
async function gitDiffStaged(file, cwd) {
|
|
16056
|
+
const args2 = ["diff", "--cached", "--no-color", "--patch"];
|
|
16057
|
+
if (file) args2.push("--", file);
|
|
16058
|
+
const r = await git(args2, cwd);
|
|
16059
|
+
if (r.code !== 0 && !r.stdout) {
|
|
16060
|
+
return { diff: "", truncated: false, error: r.stderr.trim() };
|
|
16061
|
+
}
|
|
16062
|
+
const truncated = r.stdout.length >= MAX_DIFF_BYTES;
|
|
16063
|
+
return { diff: r.stdout.slice(0, MAX_DIFF_BYTES), truncated };
|
|
16064
|
+
}
|
|
16065
|
+
async function gitLog(limit = 30, cwd) {
|
|
16066
|
+
const SEP = "";
|
|
16067
|
+
const fmt = `%H${SEP}%s${SEP}%an${SEP}%ct${SEP}%D`;
|
|
16068
|
+
const r = await git(["log", `-n${Math.min(limit, 200)}`, `--pretty=format:${fmt}`], cwd);
|
|
16069
|
+
if (r.code !== 0) return { commits: [], error: r.stderr.trim() };
|
|
16070
|
+
const commits = r.stdout.split("\n").filter(Boolean).map((line) => {
|
|
16071
|
+
const [sha, subject, author, ts, refs] = line.split(SEP);
|
|
16127
16072
|
return {
|
|
16128
|
-
|
|
16073
|
+
sha: sha ?? "",
|
|
16074
|
+
subject: subject ?? "",
|
|
16075
|
+
author: author ?? "",
|
|
16076
|
+
timestamp: parseInt(ts ?? "0", 10) * 1e3,
|
|
16077
|
+
refs: (refs ?? "").split(",").map((s) => s.trim().replace(/^HEAD -> /, "")).filter((s) => s.length > 0)
|
|
16129
16078
|
};
|
|
16079
|
+
});
|
|
16080
|
+
return { commits };
|
|
16081
|
+
}
|
|
16082
|
+
async function gitCommit(message, files, cwd) {
|
|
16083
|
+
if (!message || message.trim().length === 0) {
|
|
16084
|
+
return { error: "Commit message is required." };
|
|
16085
|
+
}
|
|
16086
|
+
if (files && files.length > 0) {
|
|
16087
|
+
const r2 = await git(["add", "--", ...files], cwd);
|
|
16088
|
+
if (r2.code !== 0) return { error: `git add failed: ${r2.stderr.trim()}` };
|
|
16089
|
+
} else {
|
|
16090
|
+
const r2 = await git(["add", "-A"], cwd);
|
|
16091
|
+
if (r2.code !== 0) return { error: `git add failed: ${r2.stderr.trim()}` };
|
|
16130
16092
|
}
|
|
16131
|
-
const
|
|
16132
|
-
if (
|
|
16133
|
-
|
|
16134
|
-
return { sessionId: id };
|
|
16135
|
-
}
|
|
16136
|
-
function writeTerminal(sessionId, data) {
|
|
16137
|
-
const s = sessions.get(sessionId);
|
|
16138
|
-
if (!s) return { ok: false, error: "No such session" };
|
|
16139
|
-
try {
|
|
16140
|
-
s.write(data);
|
|
16141
|
-
return { ok: true };
|
|
16142
|
-
} catch (e) {
|
|
16143
|
-
return { ok: false, error: e instanceof Error ? e.message : "write failed" };
|
|
16093
|
+
const r = await git(["commit", "-m", message], cwd);
|
|
16094
|
+
if (r.code !== 0) {
|
|
16095
|
+
return { error: r.stderr.trim() || "git commit failed" };
|
|
16144
16096
|
}
|
|
16097
|
+
const head = await git(["rev-parse", "HEAD"], cwd);
|
|
16098
|
+
return { ok: true, commit: head.stdout.trim() };
|
|
16145
16099
|
}
|
|
16146
|
-
function
|
|
16147
|
-
const
|
|
16148
|
-
if (
|
|
16149
|
-
|
|
16150
|
-
s.resize?.(Math.max(1, Math.min(cols, 500)), Math.max(1, Math.min(rows, 200)));
|
|
16151
|
-
return { ok: true };
|
|
16152
|
-
} catch (e) {
|
|
16153
|
-
return { ok: false, error: e instanceof Error ? e.message : "resize failed" };
|
|
16154
|
-
}
|
|
16100
|
+
async function gitPush(cwd) {
|
|
16101
|
+
const r = await git(["push"], cwd);
|
|
16102
|
+
if (r.code !== 0) return { error: r.stderr.trim() || "git push failed" };
|
|
16103
|
+
return { ok: true, output: (r.stdout + r.stderr).trim() };
|
|
16155
16104
|
}
|
|
16156
|
-
function
|
|
16157
|
-
const
|
|
16158
|
-
if (
|
|
16159
|
-
|
|
16160
|
-
|
|
16161
|
-
|
|
16162
|
-
}
|
|
16163
|
-
|
|
16105
|
+
async function gitPull(cwd) {
|
|
16106
|
+
const r = await git(["pull", "--ff-only"], cwd);
|
|
16107
|
+
if (r.code !== 0) return { error: r.stderr.trim() || "git pull failed" };
|
|
16108
|
+
return { ok: true, output: (r.stdout + r.stderr).trim() };
|
|
16109
|
+
}
|
|
16110
|
+
async function gitResolve(file, side, cwd) {
|
|
16111
|
+
const r = await git(["checkout", `--${side}`, "--", file], cwd);
|
|
16112
|
+
if (r.code !== 0) return { error: r.stderr.trim() || `git checkout --${side} failed` };
|
|
16113
|
+
const add = await git(["add", "--", file], cwd);
|
|
16114
|
+
if (add.code !== 0) return { error: add.stderr.trim() || "git add (resolve) failed" };
|
|
16164
16115
|
return { ok: true };
|
|
16165
16116
|
}
|
|
16166
|
-
|
|
16167
|
-
|
|
16117
|
+
var MAX_SEARCH_HITS = 500;
|
|
16118
|
+
var MAX_SEARCH_BYTES = 256 * 1024;
|
|
16119
|
+
async function searchFiles(opts) {
|
|
16120
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
16121
|
+
const cap = Math.min(opts.maxResults ?? MAX_SEARCH_HITS, MAX_SEARCH_HITS);
|
|
16122
|
+
if (!opts.query.trim()) return { hits: [], total: 0, truncated: false };
|
|
16123
|
+
const args2 = ["grep", "-n", "--column", "-I"];
|
|
16124
|
+
if (!opts.caseSensitive) args2.push("-i");
|
|
16125
|
+
if (opts.wholeWord) args2.push("-w");
|
|
16126
|
+
if (opts.regex) args2.push("-E");
|
|
16127
|
+
else args2.push("-F");
|
|
16128
|
+
args2.push(opts.query);
|
|
16129
|
+
if (opts.include && opts.include.length > 0) {
|
|
16130
|
+
args2.push("--");
|
|
16131
|
+
for (const p2 of opts.include) args2.push(p2);
|
|
16132
|
+
} else if (opts.exclude && opts.exclude.length > 0) {
|
|
16133
|
+
args2.push("--");
|
|
16134
|
+
args2.push(".");
|
|
16135
|
+
}
|
|
16136
|
+
for (const p2 of opts.exclude ?? []) args2.push(`:!${p2}`);
|
|
16137
|
+
const r = await git(args2, cwd);
|
|
16138
|
+
if (r.code !== 0 && r.code !== 1) {
|
|
16139
|
+
return jsSearchFiles(opts, cwd, cap);
|
|
16140
|
+
}
|
|
16141
|
+
const hits = [];
|
|
16142
|
+
const lines = r.stdout.split("\n");
|
|
16143
|
+
let truncated = false;
|
|
16144
|
+
let byteBudget = MAX_SEARCH_BYTES;
|
|
16145
|
+
for (const line of lines) {
|
|
16146
|
+
if (!line) continue;
|
|
16147
|
+
if (hits.length >= cap) {
|
|
16148
|
+
truncated = true;
|
|
16149
|
+
break;
|
|
16150
|
+
}
|
|
16151
|
+
if (byteBudget <= 0) {
|
|
16152
|
+
truncated = true;
|
|
16153
|
+
break;
|
|
16154
|
+
}
|
|
16155
|
+
byteBudget -= line.length;
|
|
16156
|
+
const m = line.match(/^([^]+?):(\d+):(\d+):(.*)$/);
|
|
16157
|
+
if (!m) continue;
|
|
16158
|
+
const filePath = m[1] ?? "";
|
|
16159
|
+
const lineNo = parseInt(m[2] ?? "0", 10);
|
|
16160
|
+
const col = parseInt(m[3] ?? "1", 10);
|
|
16161
|
+
const text = (m[4] ?? "").slice(0, 400);
|
|
16162
|
+
if (!filePath) continue;
|
|
16163
|
+
hits.push({
|
|
16164
|
+
path: filePath,
|
|
16165
|
+
line: lineNo,
|
|
16166
|
+
column: col,
|
|
16167
|
+
text,
|
|
16168
|
+
matchLength: opts.query.length
|
|
16169
|
+
});
|
|
16170
|
+
}
|
|
16171
|
+
return { hits, total: hits.length, truncated };
|
|
16172
|
+
}
|
|
16173
|
+
async function jsSearchFiles(opts, cwd, cap) {
|
|
16174
|
+
const files = await listProjectFiles({ cwd, cap: 2e3 });
|
|
16175
|
+
const hits = [];
|
|
16176
|
+
const needle = opts.caseSensitive ? opts.query : opts.query.toLowerCase();
|
|
16177
|
+
let truncated = files.truncated;
|
|
16178
|
+
for (const f of files.files) {
|
|
16179
|
+
if (hits.length >= cap) {
|
|
16180
|
+
truncated = true;
|
|
16181
|
+
break;
|
|
16182
|
+
}
|
|
16183
|
+
let content = "";
|
|
16184
|
+
try {
|
|
16185
|
+
content = await fs23.readFile(path28.join(cwd, f.path), "utf8");
|
|
16186
|
+
} catch {
|
|
16187
|
+
continue;
|
|
16188
|
+
}
|
|
16189
|
+
const lines = content.split("\n");
|
|
16190
|
+
for (let i = 0; i < lines.length && hits.length < cap; i++) {
|
|
16191
|
+
const line = lines[i] ?? "";
|
|
16192
|
+
const hay = opts.caseSensitive ? line : line.toLowerCase();
|
|
16193
|
+
const idx = hay.indexOf(needle);
|
|
16194
|
+
if (idx === -1) continue;
|
|
16195
|
+
hits.push({
|
|
16196
|
+
path: f.path,
|
|
16197
|
+
line: i + 1,
|
|
16198
|
+
column: idx + 1,
|
|
16199
|
+
text: line.slice(0, 400),
|
|
16200
|
+
matchLength: opts.query.length
|
|
16201
|
+
});
|
|
16202
|
+
}
|
|
16203
|
+
}
|
|
16204
|
+
return { hits, total: hits.length, truncated };
|
|
16168
16205
|
}
|
|
16169
16206
|
|
|
16170
16207
|
// src/services/apply-file-review.service.ts
|
|
@@ -17876,6 +17913,7 @@ var import_child_process13 = require("child_process");
|
|
|
17876
17913
|
var fs30 = __toESM(require("fs"));
|
|
17877
17914
|
var os24 = __toESM(require("os"));
|
|
17878
17915
|
var path35 = __toESM(require("path"));
|
|
17916
|
+
var import_ignore = __toESM(require("ignore"));
|
|
17879
17917
|
|
|
17880
17918
|
// src/services/file-watcher/diff-parser.ts
|
|
17881
17919
|
var HUNK_HEADER_RE = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/;
|
|
@@ -18099,6 +18137,17 @@ var FileWatcherService = class {
|
|
|
18099
18137
|
* doesn't hammer `fs.statSync` for every event.
|
|
18100
18138
|
*/
|
|
18101
18139
|
gitRootByDir = /* @__PURE__ */ new Map();
|
|
18140
|
+
/**
|
|
18141
|
+
* Per-repo `.gitignore` matcher. On first encounter of a git root we
|
|
18142
|
+
* collect every `.gitignore` file under it, parse them through the
|
|
18143
|
+
* `ignore` package, and store the resulting matcher here keyed by
|
|
18144
|
+
* absolute repo path. Subsequent file events in the same repo reuse
|
|
18145
|
+
* the matcher in O(1). The hard-coded IGNORED_PATH_PATTERN above
|
|
18146
|
+
* catches conventional dirs (node_modules, dist, Pods, …); this
|
|
18147
|
+
* matcher layers the repo's own ignore rules on top so per-project
|
|
18148
|
+
* artifacts (ios/, .env*, build outputs) stop polluting the queue.
|
|
18149
|
+
*/
|
|
18150
|
+
gitIgnoreMatcherByRoot = /* @__PURE__ */ new Map();
|
|
18102
18151
|
stopped = false;
|
|
18103
18152
|
/**
|
|
18104
18153
|
* Cross-file coalescing buffer. Keyed by absPath so multiple
|
|
@@ -18324,9 +18373,17 @@ var FileWatcherService = class {
|
|
|
18324
18373
|
);
|
|
18325
18374
|
return;
|
|
18326
18375
|
}
|
|
18327
|
-
this.opts.onRepoDirty?.(gitRoot);
|
|
18328
18376
|
const relPathInRepo = path35.relative(gitRoot, absPath);
|
|
18329
18377
|
if (!relPathInRepo || relPathInRepo.startsWith("..")) return;
|
|
18378
|
+
const matcher = this.getGitIgnoreMatcher(gitRoot);
|
|
18379
|
+
if (matcher && matcher.ignores(relPathInRepo)) {
|
|
18380
|
+
log.trace(
|
|
18381
|
+
"fileWatcher",
|
|
18382
|
+
`${relPathInRepo} ignored by ${path35.basename(gitRoot)}/.gitignore \u2014 suppressing emit`
|
|
18383
|
+
);
|
|
18384
|
+
return;
|
|
18385
|
+
}
|
|
18386
|
+
this.opts.onRepoDirty?.(gitRoot);
|
|
18330
18387
|
const repoPath = path35.relative(this.opts.workingDir, gitRoot);
|
|
18331
18388
|
const repoName = path35.basename(gitRoot);
|
|
18332
18389
|
let diffText = "";
|
|
@@ -18462,6 +18519,79 @@ var FileWatcherService = class {
|
|
|
18462
18519
|
async postReviewBlame(body) {
|
|
18463
18520
|
await this.postWithRetries(`${this.apiBase}/api/review/blame`, body);
|
|
18464
18521
|
}
|
|
18522
|
+
/**
|
|
18523
|
+
* Lazily build and cache a per-repo `.gitignore` matcher. We walk the
|
|
18524
|
+
* repo collecting every `.gitignore` file (skipping the same dirs
|
|
18525
|
+
* IGNORED_PATH_PATTERN already filters at chokidar level, so we
|
|
18526
|
+
* don't read inside node_modules / Pods / etc.) and feed each file
|
|
18527
|
+
* into a single `ignore` matcher anchored at the git root. Subsequent
|
|
18528
|
+
* calls return the cached matcher; failures fall back to `null`,
|
|
18529
|
+
* which the caller treats as "no extra filtering" — so a malformed
|
|
18530
|
+
* .gitignore degrades to the prior pre-fix behaviour rather than
|
|
18531
|
+
* silently dropping every event.
|
|
18532
|
+
*/
|
|
18533
|
+
getGitIgnoreMatcher(gitRoot) {
|
|
18534
|
+
if (this.gitIgnoreMatcherByRoot.has(gitRoot)) {
|
|
18535
|
+
return this.gitIgnoreMatcherByRoot.get(gitRoot) ?? null;
|
|
18536
|
+
}
|
|
18537
|
+
const matcher = (0, import_ignore.default)();
|
|
18538
|
+
let added = 0;
|
|
18539
|
+
try {
|
|
18540
|
+
this.collectGitignoreFiles(gitRoot, gitRoot, matcher);
|
|
18541
|
+
added = 1;
|
|
18542
|
+
} catch (err) {
|
|
18543
|
+
log.warn(
|
|
18544
|
+
"fileWatcher",
|
|
18545
|
+
`failed to build gitignore matcher for ${gitRoot}: ${err.message}`
|
|
18546
|
+
);
|
|
18547
|
+
}
|
|
18548
|
+
const result = added > 0 ? matcher : null;
|
|
18549
|
+
this.gitIgnoreMatcherByRoot.set(gitRoot, result);
|
|
18550
|
+
return result;
|
|
18551
|
+
}
|
|
18552
|
+
/**
|
|
18553
|
+
* Walk the repo recursively collecting every `.gitignore` file and
|
|
18554
|
+
* add its rules to `matcher`, with the path prefix that anchors them
|
|
18555
|
+
* to the right subdirectory (so a `.gitignore` inside `apps/api`
|
|
18556
|
+
* scopes to `apps/api/*`, not the whole repo). Skips heavy dirs the
|
|
18557
|
+
* static IGNORED_PATH_PATTERN already filters — we don't want to
|
|
18558
|
+
* stat into `node_modules/` looking for buried .gitignore files.
|
|
18559
|
+
*/
|
|
18560
|
+
collectGitignoreFiles(repoRoot, dir, matcher) {
|
|
18561
|
+
let entries;
|
|
18562
|
+
try {
|
|
18563
|
+
entries = fs30.readdirSync(dir, { withFileTypes: true });
|
|
18564
|
+
} catch {
|
|
18565
|
+
return;
|
|
18566
|
+
}
|
|
18567
|
+
const gitignoreEntry = entries.find(
|
|
18568
|
+
(e) => e.isFile() && e.name === ".gitignore"
|
|
18569
|
+
);
|
|
18570
|
+
if (gitignoreEntry) {
|
|
18571
|
+
try {
|
|
18572
|
+
const body = fs30.readFileSync(path35.join(dir, ".gitignore"), "utf8");
|
|
18573
|
+
const rel = path35.relative(repoRoot, dir).replace(/\\/g, "/");
|
|
18574
|
+
const prefixed = body.split(/\r?\n/).map((line) => {
|
|
18575
|
+
const trimmed = line.trim();
|
|
18576
|
+
if (!trimmed || trimmed.startsWith("#")) return line;
|
|
18577
|
+
if (!rel) return line;
|
|
18578
|
+
if (trimmed.startsWith("!")) {
|
|
18579
|
+
return "!" + path35.posix.join(rel, trimmed.slice(1));
|
|
18580
|
+
}
|
|
18581
|
+
return path35.posix.join(rel, trimmed);
|
|
18582
|
+
}).join("\n");
|
|
18583
|
+
matcher.add(prefixed);
|
|
18584
|
+
} catch {
|
|
18585
|
+
}
|
|
18586
|
+
}
|
|
18587
|
+
for (const entry of entries) {
|
|
18588
|
+
if (!entry.isDirectory()) continue;
|
|
18589
|
+
if (entry.name === ".git") continue;
|
|
18590
|
+
const childAbs = path35.join(dir, entry.name);
|
|
18591
|
+
if (isIgnoredFilePath(childAbs)) continue;
|
|
18592
|
+
this.collectGitignoreFiles(repoRoot, childAbs, matcher);
|
|
18593
|
+
}
|
|
18594
|
+
}
|
|
18465
18595
|
async postWithRetries(url, body) {
|
|
18466
18596
|
const payload = JSON.stringify(body);
|
|
18467
18597
|
const headers = {
|
|
@@ -19425,6 +19555,24 @@ async function runAcpSession(opts) {
|
|
|
19425
19555
|
pluginAuthToken: opts.pluginAuthToken
|
|
19426
19556
|
});
|
|
19427
19557
|
const streaming = new StreamingState(publisher);
|
|
19558
|
+
registerTerminalHandlers({
|
|
19559
|
+
onData: ({ sessionId, data }) => {
|
|
19560
|
+
void publisher.publishOutput({
|
|
19561
|
+
type: "terminal_data",
|
|
19562
|
+
terminalSessionId: sessionId,
|
|
19563
|
+
data,
|
|
19564
|
+
done: false
|
|
19565
|
+
});
|
|
19566
|
+
},
|
|
19567
|
+
onExit: ({ sessionId, exitCode }) => {
|
|
19568
|
+
void publisher.publishOutput({
|
|
19569
|
+
type: "terminal_exit",
|
|
19570
|
+
terminalSessionId: sessionId,
|
|
19571
|
+
exitCode,
|
|
19572
|
+
done: true
|
|
19573
|
+
});
|
|
19574
|
+
}
|
|
19575
|
+
});
|
|
19428
19576
|
let updateCount = 0;
|
|
19429
19577
|
const client2 = new AcpClient({
|
|
19430
19578
|
adapter: opts.adapter,
|
|
@@ -19502,20 +19650,37 @@ async function runAcpSession(opts) {
|
|
|
19502
19650
|
const runtime = createInteractiveAgentStrategy(opts.agent, createOsStrategy());
|
|
19503
19651
|
const models = await runtime.listModels();
|
|
19504
19652
|
const history = new AcpHistory(publisher, { agent: opts.agent, acpSessionId });
|
|
19505
|
-
const
|
|
19653
|
+
const turnFiles = new TurnFileAggregator({
|
|
19506
19654
|
workingDir: opts.cwd,
|
|
19507
19655
|
sessionId: opts.sessionId,
|
|
19508
19656
|
pluginId: opts.pluginId,
|
|
19509
|
-
pluginAuthToken: opts.pluginAuthToken
|
|
19657
|
+
pluginAuthToken: opts.pluginAuthToken,
|
|
19658
|
+
agentId: opts.agent
|
|
19510
19659
|
});
|
|
19511
|
-
const
|
|
19660
|
+
const REPO_DIRTY_FLUSH_DEBOUNCE_MS = 2e3;
|
|
19661
|
+
let repoDirtyTimer = null;
|
|
19662
|
+
const fileWatcher = new FileWatcherService({
|
|
19512
19663
|
workingDir: opts.cwd,
|
|
19513
19664
|
sessionId: opts.sessionId,
|
|
19514
19665
|
pluginId: opts.pluginId,
|
|
19515
19666
|
pluginAuthToken: opts.pluginAuthToken,
|
|
19516
|
-
|
|
19667
|
+
onRepoDirty: () => {
|
|
19668
|
+
if (repoDirtyTimer) clearTimeout(repoDirtyTimer);
|
|
19669
|
+
repoDirtyTimer = setTimeout(() => {
|
|
19670
|
+
repoDirtyTimer = null;
|
|
19671
|
+
log.info("acpRunner", "onRepoDirty debounce fired \u2014 running flushTurn");
|
|
19672
|
+
turnFiles.flushTurn().catch((err) => {
|
|
19673
|
+
log.warn("acpRunner", `flushTurn from onRepoDirty failed: ${describeError(err)}`);
|
|
19674
|
+
});
|
|
19675
|
+
}, REPO_DIRTY_FLUSH_DEBOUNCE_MS);
|
|
19676
|
+
}
|
|
19517
19677
|
});
|
|
19518
|
-
fileWatcher.start().
|
|
19678
|
+
fileWatcher.start().then(() => {
|
|
19679
|
+
log.info(
|
|
19680
|
+
"acpRunner",
|
|
19681
|
+
`fileWatcher started \u2014 watching cwd=${opts.cwd} for file changes (debounce ${REPO_DIRTY_FLUSH_DEBOUNCE_MS}ms before flushTurn)`
|
|
19682
|
+
);
|
|
19683
|
+
}).catch((err) => {
|
|
19519
19684
|
log.warn("acpRunner", `fileWatcher.start failed: ${describeError(err)}`);
|
|
19520
19685
|
});
|
|
19521
19686
|
const relay = new CommandRelayService(
|
|
@@ -19542,6 +19707,7 @@ async function runAcpSession(opts) {
|
|
|
19542
19707
|
relay.stop();
|
|
19543
19708
|
void fileWatcher.stop();
|
|
19544
19709
|
turnFiles.stop();
|
|
19710
|
+
closeAllTerminals();
|
|
19545
19711
|
await client2.stop();
|
|
19546
19712
|
process.exit(0);
|
|
19547
19713
|
};
|
|
@@ -19756,6 +19922,7 @@ async function handleCommand(cmd, client2, relay, acpSessionId, models, streamin
|
|
|
19756
19922
|
}
|
|
19757
19923
|
}
|
|
19758
19924
|
relay.stop();
|
|
19925
|
+
closeAllTerminals();
|
|
19759
19926
|
await client2.stop();
|
|
19760
19927
|
process.exit(0);
|
|
19761
19928
|
return;
|
|
@@ -19765,26 +19932,58 @@ async function handleCommand(cmd, client2, relay, acpSessionId, models, streamin
|
|
|
19765
19932
|
case "preview_stop":
|
|
19766
19933
|
case "save_preview_config": {
|
|
19767
19934
|
const runtime = createInteractiveAgentStrategy(opts.agent, createOsStrategy());
|
|
19768
|
-
|
|
19935
|
+
let previewHandlerAcked = false;
|
|
19936
|
+
const ackingRelay = new Proxy(relay, {
|
|
19937
|
+
get(target, prop, receiver) {
|
|
19938
|
+
if (prop === "sendResult") {
|
|
19939
|
+
return (commandId, status2, result) => {
|
|
19940
|
+
if (commandId === cmd.id) previewHandlerAcked = true;
|
|
19941
|
+
return target.sendResult(commandId, status2, result);
|
|
19942
|
+
};
|
|
19943
|
+
}
|
|
19944
|
+
return Reflect.get(target, prop, receiver);
|
|
19945
|
+
}
|
|
19946
|
+
});
|
|
19947
|
+
const ctx = buildLegacyContextForACP(opts, ackingRelay, runtime);
|
|
19769
19948
|
try {
|
|
19770
19949
|
await dispatchCommand(ctx, cmd);
|
|
19771
|
-
|
|
19950
|
+
if (!previewHandlerAcked) {
|
|
19951
|
+
await relay.sendResult(cmd.id, "completed", {});
|
|
19952
|
+
}
|
|
19772
19953
|
} catch (err) {
|
|
19773
19954
|
log.warn("acpRunner", `${cmd.type} failed: ${describeError(err)}`);
|
|
19774
|
-
|
|
19955
|
+
if (!previewHandlerAcked) {
|
|
19956
|
+
await relay.sendResult(cmd.id, "failed", { error: describeError(err) });
|
|
19957
|
+
}
|
|
19775
19958
|
}
|
|
19776
19959
|
return;
|
|
19777
19960
|
}
|
|
19778
19961
|
default:
|
|
19779
19962
|
if (handlers[cmd.type]) {
|
|
19780
19963
|
const runtime = createInteractiveAgentStrategy(opts.agent, createOsStrategy());
|
|
19781
|
-
|
|
19964
|
+
let handlerAcked = false;
|
|
19965
|
+
const ackingRelay = new Proxy(relay, {
|
|
19966
|
+
get(target, prop, receiver) {
|
|
19967
|
+
if (prop === "sendResult") {
|
|
19968
|
+
return (commandId, status2, result) => {
|
|
19969
|
+
if (commandId === cmd.id) handlerAcked = true;
|
|
19970
|
+
return target.sendResult(commandId, status2, result);
|
|
19971
|
+
};
|
|
19972
|
+
}
|
|
19973
|
+
return Reflect.get(target, prop, receiver);
|
|
19974
|
+
}
|
|
19975
|
+
});
|
|
19976
|
+
const legacyCtx = buildLegacyContextForACP(opts, ackingRelay, runtime);
|
|
19782
19977
|
try {
|
|
19783
19978
|
await dispatchCommand(legacyCtx, cmd);
|
|
19784
|
-
|
|
19979
|
+
if (!handlerAcked) {
|
|
19980
|
+
await relay.sendResult(cmd.id, "completed", {});
|
|
19981
|
+
}
|
|
19785
19982
|
} catch (err) {
|
|
19786
19983
|
log.warn("acpRunner", `legacy handler "${cmd.type}" threw: ${describeError(err)}`);
|
|
19787
|
-
|
|
19984
|
+
if (!handlerAcked) {
|
|
19985
|
+
await relay.sendResult(cmd.id, "failed", { error: describeError(err) });
|
|
19986
|
+
}
|
|
19788
19987
|
}
|
|
19789
19988
|
return;
|
|
19790
19989
|
}
|
|
@@ -21551,6 +21750,19 @@ async function start(requestedAgent) {
|
|
|
21551
21750
|
requestedAgent: requestedAgent ?? null
|
|
21552
21751
|
});
|
|
21553
21752
|
const cwd = process.cwd();
|
|
21753
|
+
const refreshed = await fetchCurrentPluginAuthToken(session.id, pluginId);
|
|
21754
|
+
if (refreshed && refreshed !== session.pluginAuthToken) {
|
|
21755
|
+
addSession({ ...session, pluginAuthToken: refreshed });
|
|
21756
|
+
session.pluginAuthToken = refreshed;
|
|
21757
|
+
showInfo("Reconnected \u2014 refreshed plugin auth token.");
|
|
21758
|
+
} else if (refreshed) {
|
|
21759
|
+
showInfo("Reconnected previous session.");
|
|
21760
|
+
}
|
|
21761
|
+
const tokenForLog = session.pluginAuthToken ?? "(unset)";
|
|
21762
|
+
log.trace(
|
|
21763
|
+
"pluginAuth",
|
|
21764
|
+
`boot triple sessionId=${session.id} pluginId=${pluginId} tokenLen=${tokenForLog.length} tokenHead=${tokenForLog.slice(0, 12)} tokenTail=${tokenForLog.slice(-8)} mintedEqualsCached=${refreshed === session.pluginAuthToken}`
|
|
21765
|
+
);
|
|
21554
21766
|
const acpDisabled = process.env.CODEAM_ACP_DISABLED === "1";
|
|
21555
21767
|
if (!acpDisabled && session.pluginAuthToken) {
|
|
21556
21768
|
const adapter = getAcpAdapter(session.agent);
|
|
@@ -24467,7 +24679,7 @@ function checkChokidar() {
|
|
|
24467
24679
|
}
|
|
24468
24680
|
async function doctor(args2 = []) {
|
|
24469
24681
|
const json = args2.includes("--json");
|
|
24470
|
-
const cliVersion = true ? "2.
|
|
24682
|
+
const cliVersion = true ? "2.28.1" : "0.0.0-dev";
|
|
24471
24683
|
const apiBase = resolveApiBaseUrl();
|
|
24472
24684
|
const diagnosticId = (0, import_node_crypto8.randomUUID)();
|
|
24473
24685
|
log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
|
|
@@ -24666,7 +24878,7 @@ async function completion(args2) {
|
|
|
24666
24878
|
// src/commands/version.ts
|
|
24667
24879
|
var import_picocolors13 = __toESM(require("picocolors"));
|
|
24668
24880
|
function version2() {
|
|
24669
|
-
const v = true ? "2.
|
|
24881
|
+
const v = true ? "2.28.1" : "unknown";
|
|
24670
24882
|
console.log(`${import_picocolors13.default.bold("codeam-cli")} ${import_picocolors13.default.cyan(v)}`);
|
|
24671
24883
|
}
|
|
24672
24884
|
|
|
@@ -24798,6 +25010,7 @@ var fs35 = __toESM(require("fs"));
|
|
|
24798
25010
|
var os27 = __toESM(require("os"));
|
|
24799
25011
|
var path44 = __toESM(require("path"));
|
|
24800
25012
|
var https7 = __toESM(require("https"));
|
|
25013
|
+
var import_node_child_process12 = require("child_process");
|
|
24801
25014
|
var import_picocolors16 = __toESM(require("picocolors"));
|
|
24802
25015
|
var PKG_NAME = "codeam-cli";
|
|
24803
25016
|
var REGISTRY_URL = `https://registry.npmjs.org/${PKG_NAME}/latest`;
|
|
@@ -24889,17 +25102,74 @@ function notifyIfStale(currentVersion, latest) {
|
|
|
24889
25102
|
];
|
|
24890
25103
|
process.stderr.write(lines.join("\n"));
|
|
24891
25104
|
}
|
|
25105
|
+
function isLinkedInstall() {
|
|
25106
|
+
try {
|
|
25107
|
+
const root = (0, import_node_child_process12.execSync)("npm root -g", {
|
|
25108
|
+
encoding: "utf8",
|
|
25109
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
25110
|
+
timeout: 2e3
|
|
25111
|
+
}).trim();
|
|
25112
|
+
if (!root) return false;
|
|
25113
|
+
const pkgPath = path44.join(root, PKG_NAME);
|
|
25114
|
+
return fs35.lstatSync(pkgPath).isSymbolicLink();
|
|
25115
|
+
} catch {
|
|
25116
|
+
return false;
|
|
25117
|
+
}
|
|
25118
|
+
}
|
|
25119
|
+
function maybeAutoUpdate(currentVersion, latest) {
|
|
25120
|
+
if (compareSemver(latest, currentVersion) <= 0) return;
|
|
25121
|
+
if (process.env.CODEAM_NO_AUTO_UPDATE === "1") {
|
|
25122
|
+
notifyIfStale(currentVersion, latest);
|
|
25123
|
+
return;
|
|
25124
|
+
}
|
|
25125
|
+
if (isLinkedInstall()) {
|
|
25126
|
+
notifyIfStale(currentVersion, latest);
|
|
25127
|
+
return;
|
|
25128
|
+
}
|
|
25129
|
+
process.stderr.write(
|
|
25130
|
+
`
|
|
25131
|
+
${import_picocolors16.default.yellow("\u25CF")} ${import_picocolors16.default.bold("Updating codeam-cli")} ${import_picocolors16.default.dim(currentVersion)} ${import_picocolors16.default.dim("\u2192")} ${import_picocolors16.default.green(latest)}...
|
|
25132
|
+
|
|
25133
|
+
`
|
|
25134
|
+
);
|
|
25135
|
+
const install = (0, import_node_child_process12.spawnSync)("npm", ["install", "-g", `${PKG_NAME}@latest`], {
|
|
25136
|
+
stdio: "inherit",
|
|
25137
|
+
env: process.env
|
|
25138
|
+
});
|
|
25139
|
+
if (install.status !== 0) {
|
|
25140
|
+
process.stderr.write(
|
|
25141
|
+
`
|
|
25142
|
+
${import_picocolors16.default.red("!")} Update failed (exit ${install.status ?? "?"}). Continuing on ${currentVersion}.
|
|
25143
|
+
Run ${import_picocolors16.default.cyan("npm install -g codeam-cli")} manually to retry.
|
|
25144
|
+
|
|
25145
|
+
`
|
|
25146
|
+
);
|
|
25147
|
+
return;
|
|
25148
|
+
}
|
|
25149
|
+
try {
|
|
25150
|
+
fs35.unlinkSync(cachePath());
|
|
25151
|
+
} catch {
|
|
25152
|
+
}
|
|
25153
|
+
process.stderr.write(` ${import_picocolors16.default.green("\u2713")} Updated. Resuming session...
|
|
25154
|
+
|
|
25155
|
+
`);
|
|
25156
|
+
const child = (0, import_node_child_process12.spawnSync)("codeam", process.argv.slice(2), {
|
|
25157
|
+
stdio: "inherit",
|
|
25158
|
+
env: process.env
|
|
25159
|
+
});
|
|
25160
|
+
process.exit(child.status ?? 0);
|
|
25161
|
+
}
|
|
24892
25162
|
function checkForUpdates() {
|
|
24893
25163
|
if (process.env.NODE_ENV === "test") return;
|
|
24894
25164
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
24895
25165
|
if (process.env.CI) return;
|
|
24896
25166
|
if (!process.stdout.isTTY) return;
|
|
24897
|
-
const current = true ? "2.
|
|
25167
|
+
const current = true ? "2.28.1" : null;
|
|
24898
25168
|
if (!current) return;
|
|
24899
25169
|
const cache = readCache();
|
|
24900
25170
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
24901
25171
|
if (fresh && cache) {
|
|
24902
|
-
|
|
25172
|
+
maybeAutoUpdate(current, cache.latest);
|
|
24903
25173
|
return;
|
|
24904
25174
|
}
|
|
24905
25175
|
void fetchLatest().then((latest) => {
|