codeam-cli 2.27.16 → 2.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/index.js +768 -507
- 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.0",
|
|
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",
|
|
@@ -712,6 +713,20 @@ async function requestCode(pluginId) {
|
|
|
712
713
|
return null;
|
|
713
714
|
}
|
|
714
715
|
}
|
|
716
|
+
async function fetchCurrentPluginAuthToken(sessionId, pluginId) {
|
|
717
|
+
try {
|
|
718
|
+
const result = await _transport.postJson(
|
|
719
|
+
`${API_BASE}/api/pairing/reconnect`,
|
|
720
|
+
{ sessionId, pluginId }
|
|
721
|
+
);
|
|
722
|
+
const data = result?.data;
|
|
723
|
+
if (!data?.paired) return null;
|
|
724
|
+
const token = data.pluginAuthToken;
|
|
725
|
+
return typeof token === "string" && token.length > 0 ? token : null;
|
|
726
|
+
} catch {
|
|
727
|
+
return null;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
715
730
|
function pollStatus(pluginId, onPaired, onTimeout) {
|
|
716
731
|
let stopped = false;
|
|
717
732
|
let pollTimer = null;
|
|
@@ -5873,7 +5888,7 @@ function readAnonId() {
|
|
|
5873
5888
|
}
|
|
5874
5889
|
function superProperties() {
|
|
5875
5890
|
return {
|
|
5876
|
-
cliVersion: true ? "2.
|
|
5891
|
+
cliVersion: true ? "2.28.0" : "0.0.0-dev",
|
|
5877
5892
|
nodeVersion: process.version,
|
|
5878
5893
|
platform: process.platform,
|
|
5879
5894
|
arch: process.arch,
|
|
@@ -14973,9 +14988,10 @@ var AcpPublisher = class {
|
|
|
14973
14988
|
this.envelope(body)
|
|
14974
14989
|
);
|
|
14975
14990
|
if (statusCode < 200 || statusCode >= 300) {
|
|
14991
|
+
const tok = this.opts.pluginAuthToken;
|
|
14976
14992
|
log.warn(
|
|
14977
14993
|
"acpPublisher",
|
|
14978
|
-
`output type=${String(body.type)} done=${body.done === true} status=${statusCode} body=${resBody.slice(0, 200)}`
|
|
14994
|
+
`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
14995
|
);
|
|
14980
14996
|
}
|
|
14981
14997
|
} catch (err) {
|
|
@@ -15113,122 +15129,391 @@ var AcpPublisher = class {
|
|
|
15113
15129
|
}
|
|
15114
15130
|
};
|
|
15115
15131
|
|
|
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 }];
|
|
15132
|
+
// src/services/terminal-ops.service.ts
|
|
15133
|
+
var import_child_process8 = require("child_process");
|
|
15134
|
+
var import_crypto2 = require("crypto");
|
|
15135
|
+
var import_path3 = __toESM(require("path"));
|
|
15136
|
+
var MAX_CONCURRENT_SESSIONS = 4;
|
|
15137
|
+
var nodePtyModule;
|
|
15138
|
+
function loadNodePty2() {
|
|
15139
|
+
if (nodePtyModule !== void 0) return nodePtyModule;
|
|
15140
|
+
const vendoredPath = import_path3.default.join(__dirname, "vendor", "node-pty");
|
|
15141
|
+
try {
|
|
15142
|
+
nodePtyModule = require(vendoredPath);
|
|
15143
|
+
return nodePtyModule;
|
|
15144
|
+
} catch {
|
|
15145
|
+
try {
|
|
15146
|
+
nodePtyModule = require("node-pty");
|
|
15147
|
+
return nodePtyModule;
|
|
15148
|
+
} catch {
|
|
15149
|
+
nodePtyModule = null;
|
|
15150
|
+
return nodePtyModule;
|
|
15144
15151
|
}
|
|
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
15152
|
}
|
|
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
15153
|
}
|
|
15182
|
-
|
|
15183
|
-
|
|
15184
|
-
|
|
15154
|
+
var sessions = /* @__PURE__ */ new Map();
|
|
15155
|
+
var onDataHandler = null;
|
|
15156
|
+
var onExitHandler = null;
|
|
15157
|
+
function registerTerminalHandlers(opts) {
|
|
15158
|
+
onDataHandler = opts.onData;
|
|
15159
|
+
onExitHandler = opts.onExit;
|
|
15185
15160
|
}
|
|
15186
|
-
function
|
|
15187
|
-
if (
|
|
15188
|
-
|
|
15189
|
-
const t2 = content.text;
|
|
15190
|
-
return typeof t2 === "string" && t2.length > 0 ? t2 : null;
|
|
15161
|
+
function defaultShell() {
|
|
15162
|
+
if (process.platform === "win32") {
|
|
15163
|
+
return process.env.COMSPEC ?? "powershell.exe";
|
|
15191
15164
|
}
|
|
15192
|
-
return
|
|
15165
|
+
return process.env.SHELL ?? "/bin/bash";
|
|
15193
15166
|
}
|
|
15194
|
-
|
|
15195
|
-
|
|
15196
|
-
|
|
15197
|
-
|
|
15198
|
-
|
|
15199
|
-
|
|
15167
|
+
var PYTHON_TERMINAL_HELPER = `import os,pty,sys,select,signal,struct,fcntl,termios,errno,re
|
|
15168
|
+
m,s=pty.openpty()
|
|
15169
|
+
try:
|
|
15170
|
+
cols=int(os.environ.get('COLUMNS','80'))
|
|
15171
|
+
rows=int(os.environ.get('LINES','24'))
|
|
15172
|
+
fcntl.ioctl(s,termios.TIOCSWINSZ,struct.pack('HHHH',rows,cols,0,0))
|
|
15173
|
+
except Exception:pass
|
|
15174
|
+
pid=os.fork()
|
|
15175
|
+
if pid==0:
|
|
15176
|
+
os.close(m)
|
|
15177
|
+
os.setsid()
|
|
15178
|
+
try:fcntl.ioctl(s,termios.TIOCSCTTY,0)
|
|
15179
|
+
except Exception:pass
|
|
15180
|
+
for fd in[0,1,2]:os.dup2(s,fd)
|
|
15181
|
+
if s>2:os.close(s)
|
|
15182
|
+
os.execvp(sys.argv[1],sys.argv[1:])
|
|
15183
|
+
sys.exit(127)
|
|
15184
|
+
os.close(s)
|
|
15185
|
+
done=[False]
|
|
15186
|
+
def onchld(n,f):
|
|
15187
|
+
try:os.waitpid(pid,os.WNOHANG)
|
|
15188
|
+
except Exception:pass
|
|
15189
|
+
done[0]=True
|
|
15190
|
+
signal.signal(signal.SIGCHLD,onchld)
|
|
15191
|
+
signal.signal(signal.SIGHUP,signal.SIG_IGN)
|
|
15192
|
+
i=sys.stdin.fileno()
|
|
15193
|
+
o=sys.stdout.fileno()
|
|
15194
|
+
in_buf=b''
|
|
15195
|
+
resize_re=re.compile(rb'\\x00CW (\\d+) (\\d+)\\n')
|
|
15196
|
+
while not done[0]:
|
|
15197
|
+
try:r,_,_=select.select([i,m],[],[],0.1)
|
|
15198
|
+
except OSError as e:
|
|
15199
|
+
if e.errno==errno.EINTR:continue
|
|
15200
|
+
break
|
|
15201
|
+
if i in r:
|
|
15202
|
+
try:
|
|
15203
|
+
d=os.read(i,4096)
|
|
15204
|
+
if not d:break
|
|
15205
|
+
in_buf+=d
|
|
15206
|
+
while True:
|
|
15207
|
+
mo=resize_re.search(in_buf)
|
|
15208
|
+
if not mo:break
|
|
15209
|
+
try:
|
|
15210
|
+
rows=int(mo.group(1));cols=int(mo.group(2))
|
|
15211
|
+
fcntl.ioctl(m,termios.TIOCSWINSZ,struct.pack('HHHH',rows,cols,0,0))
|
|
15212
|
+
except Exception:pass
|
|
15213
|
+
in_buf=in_buf[:mo.start()]+in_buf[mo.end():]
|
|
15214
|
+
if in_buf:
|
|
15215
|
+
# Don't forward a dangling NUL that might be the
|
|
15216
|
+
# start of an incomplete resize marker \u2014 hold it
|
|
15217
|
+
# until the next read so the regex matches.
|
|
15218
|
+
nul=in_buf.rfind(b'\\x00')
|
|
15219
|
+
if nul>=0 and len(in_buf)-nul<32:
|
|
15220
|
+
tail=in_buf[nul:];body=in_buf[:nul]
|
|
15221
|
+
if body:os.write(m,body)
|
|
15222
|
+
in_buf=tail
|
|
15223
|
+
else:
|
|
15224
|
+
os.write(m,in_buf);in_buf=b''
|
|
15225
|
+
except OSError:break
|
|
15226
|
+
if m in r:
|
|
15227
|
+
try:
|
|
15228
|
+
d=os.read(m,4096)
|
|
15229
|
+
if d:os.write(o,d)
|
|
15230
|
+
except OSError:done[0]=True
|
|
15231
|
+
try:os.kill(pid,signal.SIGTERM)
|
|
15232
|
+
except Exception:pass
|
|
15233
|
+
try:
|
|
15234
|
+
_,st=os.waitpid(pid,0)
|
|
15235
|
+
sys.exit((st>>8)&0xFF)
|
|
15236
|
+
except Exception:sys.exit(0)
|
|
15237
|
+
`;
|
|
15238
|
+
function findPython3() {
|
|
15239
|
+
for (const name of ["python3", "python"]) {
|
|
15200
15240
|
try {
|
|
15201
|
-
const
|
|
15202
|
-
if (
|
|
15203
|
-
return summary;
|
|
15241
|
+
const out2 = require("child_process").spawnSync("which", [name], { encoding: "utf8" });
|
|
15242
|
+
if (out2.status === 0 && out2.stdout?.trim()) return out2.stdout.trim();
|
|
15204
15243
|
} catch {
|
|
15205
|
-
return null;
|
|
15206
15244
|
}
|
|
15207
15245
|
}
|
|
15208
15246
|
return null;
|
|
15209
15247
|
}
|
|
15210
|
-
function
|
|
15211
|
-
const
|
|
15212
|
-
if (
|
|
15213
|
-
for
|
|
15214
|
-
|
|
15215
|
-
|
|
15216
|
-
|
|
15217
|
-
|
|
15218
|
-
|
|
15219
|
-
|
|
15220
|
-
|
|
15221
|
-
|
|
15222
|
-
|
|
15223
|
-
|
|
15248
|
+
function createPythonSession(id, shell, cwd, env, cols, rows) {
|
|
15249
|
+
const python = findPython3();
|
|
15250
|
+
if (!python) {
|
|
15251
|
+
return { error: "python3 not found on PATH \u2014 required for terminal sessions on Linux/macOS without node-pty." };
|
|
15252
|
+
}
|
|
15253
|
+
log.trace("terminal", `python helper spawn python=${python} shell=${shell} cwd=${cwd}`);
|
|
15254
|
+
let child;
|
|
15255
|
+
try {
|
|
15256
|
+
child = (0, import_child_process8.spawn)(python, ["-c", PYTHON_TERMINAL_HELPER, shell], {
|
|
15257
|
+
cwd,
|
|
15258
|
+
env: { ...env, COLUMNS: String(cols), LINES: String(rows) },
|
|
15259
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
15260
|
+
});
|
|
15261
|
+
} catch (e) {
|
|
15262
|
+
const msg = e instanceof Error ? e.message : "python spawn failed";
|
|
15263
|
+
log.warn("terminal", `python helper spawn failed: ${msg}`);
|
|
15264
|
+
return { error: msg };
|
|
15265
|
+
}
|
|
15266
|
+
child.stdout.on("data", (buf) => {
|
|
15267
|
+
onDataHandler?.({ sessionId: id, data: buf.toString("utf8") });
|
|
15268
|
+
});
|
|
15269
|
+
child.stderr.on("data", (buf) => {
|
|
15270
|
+
onDataHandler?.({ sessionId: id, data: buf.toString("utf8") });
|
|
15271
|
+
});
|
|
15272
|
+
child.on("exit", (code) => {
|
|
15273
|
+
onExitHandler?.({ sessionId: id, exitCode: code ?? 0 });
|
|
15274
|
+
sessions.delete(id);
|
|
15275
|
+
});
|
|
15276
|
+
return {
|
|
15277
|
+
id,
|
|
15278
|
+
write(data) {
|
|
15279
|
+
try {
|
|
15280
|
+
child.stdin.write(data);
|
|
15281
|
+
} catch {
|
|
15282
|
+
}
|
|
15283
|
+
},
|
|
15284
|
+
resize(cs, rs) {
|
|
15285
|
+
try {
|
|
15286
|
+
child.stdin.write(`\0CW ${rs} ${cs}
|
|
15287
|
+
`);
|
|
15288
|
+
} catch {
|
|
15289
|
+
}
|
|
15290
|
+
},
|
|
15291
|
+
kill() {
|
|
15292
|
+
try {
|
|
15293
|
+
child.kill("SIGTERM");
|
|
15294
|
+
} catch {
|
|
15224
15295
|
}
|
|
15225
15296
|
}
|
|
15226
|
-
}
|
|
15227
|
-
if (parts.length > 0) return parts.join("\n");
|
|
15228
|
-
const title = update.title?.trim();
|
|
15229
|
-
return title && title.length > 0 ? title : null;
|
|
15297
|
+
};
|
|
15230
15298
|
}
|
|
15231
|
-
function
|
|
15299
|
+
function openTerminal(opts) {
|
|
15300
|
+
if (sessions.size >= MAX_CONCURRENT_SESSIONS) {
|
|
15301
|
+
return { error: `Too many open terminals (max ${MAX_CONCURRENT_SESSIONS})` };
|
|
15302
|
+
}
|
|
15303
|
+
const shell = opts.shell ?? defaultShell();
|
|
15304
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
15305
|
+
const env = {
|
|
15306
|
+
...process.env,
|
|
15307
|
+
TERM: "xterm-256color",
|
|
15308
|
+
COLORTERM: "truecolor",
|
|
15309
|
+
FORCE_COLOR: "1"
|
|
15310
|
+
};
|
|
15311
|
+
const cols = Math.max(1, Math.min(opts.cols ?? 80, 500));
|
|
15312
|
+
const rows = Math.max(1, Math.min(opts.rows ?? 24, 200));
|
|
15313
|
+
const id = (0, import_crypto2.randomUUID)();
|
|
15314
|
+
const ptyMod = loadNodePty2();
|
|
15315
|
+
if (ptyMod) {
|
|
15316
|
+
try {
|
|
15317
|
+
log.trace("terminal", `node-pty spawn shell=${shell} cwd=${cwd} cols=${cols} rows=${rows}`);
|
|
15318
|
+
const term = ptyMod.spawn(shell, [], {
|
|
15319
|
+
name: "xterm-256color",
|
|
15320
|
+
cols,
|
|
15321
|
+
rows,
|
|
15322
|
+
cwd,
|
|
15323
|
+
env,
|
|
15324
|
+
useConpty: process.platform === "win32" ? true : void 0
|
|
15325
|
+
});
|
|
15326
|
+
const dataListener = term.onData((data) => {
|
|
15327
|
+
onDataHandler?.({ sessionId: id, data });
|
|
15328
|
+
});
|
|
15329
|
+
const exitListener = term.onExit(({ exitCode }) => {
|
|
15330
|
+
onExitHandler?.({ sessionId: id, exitCode });
|
|
15331
|
+
sessions.delete(id);
|
|
15332
|
+
});
|
|
15333
|
+
sessions.set(id, {
|
|
15334
|
+
id,
|
|
15335
|
+
write: (d3) => term.write(d3),
|
|
15336
|
+
resize: (cs, rs) => term.resize(cs, rs),
|
|
15337
|
+
kill: () => {
|
|
15338
|
+
dataListener.dispose();
|
|
15339
|
+
exitListener.dispose();
|
|
15340
|
+
term.kill();
|
|
15341
|
+
}
|
|
15342
|
+
});
|
|
15343
|
+
return { sessionId: id };
|
|
15344
|
+
} catch (e) {
|
|
15345
|
+
const msg = e instanceof Error ? e.message : "spawn failed";
|
|
15346
|
+
log.warn("terminal", `node-pty spawn failed: ${msg} (falling back to Python helper on ${process.platform})`);
|
|
15347
|
+
if (process.platform === "win32") {
|
|
15348
|
+
return { error: msg };
|
|
15349
|
+
}
|
|
15350
|
+
}
|
|
15351
|
+
} else {
|
|
15352
|
+
log.warn("terminal", `node-pty unavailable on ${process.platform}-${process.arch}; falling back to Python helper`);
|
|
15353
|
+
if (process.platform === "win32") {
|
|
15354
|
+
return {
|
|
15355
|
+
error: `node-pty native module unavailable on ${process.platform}-${process.arch}; terminal feature disabled for this platform`
|
|
15356
|
+
};
|
|
15357
|
+
}
|
|
15358
|
+
}
|
|
15359
|
+
const sess = createPythonSession(id, shell, cwd, env, cols, rows);
|
|
15360
|
+
if ("error" in sess) {
|
|
15361
|
+
log.warn("terminal", `createPythonSession failed: ${sess.error}`);
|
|
15362
|
+
return { error: sess.error };
|
|
15363
|
+
}
|
|
15364
|
+
sessions.set(id, sess);
|
|
15365
|
+
return { sessionId: id };
|
|
15366
|
+
}
|
|
15367
|
+
function writeTerminal(sessionId, data) {
|
|
15368
|
+
const s = sessions.get(sessionId);
|
|
15369
|
+
if (!s) return { ok: false, error: "No such session" };
|
|
15370
|
+
try {
|
|
15371
|
+
s.write(data);
|
|
15372
|
+
return { ok: true };
|
|
15373
|
+
} catch (e) {
|
|
15374
|
+
return { ok: false, error: e instanceof Error ? e.message : "write failed" };
|
|
15375
|
+
}
|
|
15376
|
+
}
|
|
15377
|
+
function resizeTerminal(sessionId, cols, rows) {
|
|
15378
|
+
const s = sessions.get(sessionId);
|
|
15379
|
+
if (!s) return { ok: false, error: "No such session" };
|
|
15380
|
+
try {
|
|
15381
|
+
s.resize?.(Math.max(1, Math.min(cols, 500)), Math.max(1, Math.min(rows, 200)));
|
|
15382
|
+
return { ok: true };
|
|
15383
|
+
} catch (e) {
|
|
15384
|
+
return { ok: false, error: e instanceof Error ? e.message : "resize failed" };
|
|
15385
|
+
}
|
|
15386
|
+
}
|
|
15387
|
+
function closeTerminal(sessionId) {
|
|
15388
|
+
const s = sessions.get(sessionId);
|
|
15389
|
+
if (!s) return { ok: true };
|
|
15390
|
+
try {
|
|
15391
|
+
s.kill();
|
|
15392
|
+
} catch {
|
|
15393
|
+
}
|
|
15394
|
+
sessions.delete(sessionId);
|
|
15395
|
+
return { ok: true };
|
|
15396
|
+
}
|
|
15397
|
+
function closeAllTerminals() {
|
|
15398
|
+
for (const id of Array.from(sessions.keys())) closeTerminal(id);
|
|
15399
|
+
}
|
|
15400
|
+
|
|
15401
|
+
// src/agents/acp/mappers.ts
|
|
15402
|
+
var import_node_crypto5 = require("crypto");
|
|
15403
|
+
function mapSessionUpdate(notification) {
|
|
15404
|
+
const update = notification.update;
|
|
15405
|
+
switch (update.sessionUpdate) {
|
|
15406
|
+
case "agent_message_chunk": {
|
|
15407
|
+
const text = extractText2(update.content);
|
|
15408
|
+
if (!text) return [];
|
|
15409
|
+
return [{ chunkId: messageChunkId(update.messageId), kind: "text", delta: text }];
|
|
15410
|
+
}
|
|
15411
|
+
case "agent_thought_chunk": {
|
|
15412
|
+
const text = extractText2(update.content);
|
|
15413
|
+
if (!text) return [];
|
|
15414
|
+
return [{ chunkId: messageChunkId(update.messageId), kind: "thinking", delta: text }];
|
|
15415
|
+
}
|
|
15416
|
+
case "tool_call": {
|
|
15417
|
+
const summary = describeToolCall(update);
|
|
15418
|
+
if (!summary) return [];
|
|
15419
|
+
return [{ chunkId: update.toolCallId, kind: "tool_use", delta: summary }];
|
|
15420
|
+
}
|
|
15421
|
+
case "tool_call_update": {
|
|
15422
|
+
if (update.status !== "completed" && update.status !== "failed") {
|
|
15423
|
+
return [];
|
|
15424
|
+
}
|
|
15425
|
+
const body = describeToolCallUpdate(update);
|
|
15426
|
+
if (!body) return [];
|
|
15427
|
+
const prefix = update.status === "failed" ? "[failed] " : "";
|
|
15428
|
+
return [{ chunkId: update.toolCallId, kind: "tool_result", delta: prefix + body }];
|
|
15429
|
+
}
|
|
15430
|
+
case "user_message_chunk":
|
|
15431
|
+
return [];
|
|
15432
|
+
case "plan":
|
|
15433
|
+
case "plan_update":
|
|
15434
|
+
case "plan_removed":
|
|
15435
|
+
case "available_commands_update":
|
|
15436
|
+
case "current_mode_update":
|
|
15437
|
+
case "config_option_update":
|
|
15438
|
+
case "session_info_update":
|
|
15439
|
+
case "usage_update":
|
|
15440
|
+
return [];
|
|
15441
|
+
default:
|
|
15442
|
+
return [];
|
|
15443
|
+
}
|
|
15444
|
+
}
|
|
15445
|
+
function mapPermissionRequest(request) {
|
|
15446
|
+
const prompt = describeToolCall(request.toolCall) ?? "The agent requested permission to continue.";
|
|
15447
|
+
const optionIdByLabel = {};
|
|
15448
|
+
const kindByLabel = {};
|
|
15449
|
+
const labels = [];
|
|
15450
|
+
for (const opt of request.options) {
|
|
15451
|
+
const label = opt.name?.trim() || humanizeKind(opt.kind);
|
|
15452
|
+
if (label in optionIdByLabel) continue;
|
|
15453
|
+
optionIdByLabel[label] = opt.optionId;
|
|
15454
|
+
kindByLabel[label] = opt.kind;
|
|
15455
|
+
labels.push(label);
|
|
15456
|
+
}
|
|
15457
|
+
return {
|
|
15458
|
+
event: {
|
|
15459
|
+
questionId: (0, import_node_crypto5.randomUUID)(),
|
|
15460
|
+
prompt,
|
|
15461
|
+
options: labels.length > 0 ? labels : void 0
|
|
15462
|
+
},
|
|
15463
|
+
optionIdByLabel,
|
|
15464
|
+
kindByLabel
|
|
15465
|
+
};
|
|
15466
|
+
}
|
|
15467
|
+
function messageChunkId(messageId) {
|
|
15468
|
+
if (typeof messageId === "string" && messageId.length > 0) return messageId;
|
|
15469
|
+
return (0, import_node_crypto5.randomUUID)();
|
|
15470
|
+
}
|
|
15471
|
+
function extractText2(content) {
|
|
15472
|
+
if (!content || typeof content !== "object") return null;
|
|
15473
|
+
if ("type" in content && content.type === "text") {
|
|
15474
|
+
const t2 = content.text;
|
|
15475
|
+
return typeof t2 === "string" && t2.length > 0 ? t2 : null;
|
|
15476
|
+
}
|
|
15477
|
+
return null;
|
|
15478
|
+
}
|
|
15479
|
+
function describeToolCall(call) {
|
|
15480
|
+
const title = call.title?.trim();
|
|
15481
|
+
const kind = call.kind?.trim();
|
|
15482
|
+
if (title && title.length > 0) return title;
|
|
15483
|
+
if (kind && kind.length > 0) return kind;
|
|
15484
|
+
if (call.rawInput && typeof call.rawInput === "object") {
|
|
15485
|
+
try {
|
|
15486
|
+
const summary = JSON.stringify(call.rawInput);
|
|
15487
|
+
if (summary.length > 240) return `${summary.slice(0, 240)}\u2026`;
|
|
15488
|
+
return summary;
|
|
15489
|
+
} catch {
|
|
15490
|
+
return null;
|
|
15491
|
+
}
|
|
15492
|
+
}
|
|
15493
|
+
return null;
|
|
15494
|
+
}
|
|
15495
|
+
function describeToolCallUpdate(update) {
|
|
15496
|
+
const parts = [];
|
|
15497
|
+
if (Array.isArray(update.content)) {
|
|
15498
|
+
for (const item of update.content) {
|
|
15499
|
+
if (!item || typeof item !== "object") continue;
|
|
15500
|
+
if (item.type === "content" && item.content) {
|
|
15501
|
+
const text = extractText2(item.content);
|
|
15502
|
+
if (text) parts.push(text);
|
|
15503
|
+
} else if (item.type === "diff") {
|
|
15504
|
+
const p2 = item.path;
|
|
15505
|
+
parts.push(p2 ? `diff: ${p2}` : "diff");
|
|
15506
|
+
} else if (item.type === "terminal") {
|
|
15507
|
+
const id = item.terminalId;
|
|
15508
|
+
parts.push(id ? `terminal: ${id}` : "terminal");
|
|
15509
|
+
}
|
|
15510
|
+
}
|
|
15511
|
+
}
|
|
15512
|
+
if (parts.length > 0) return parts.join("\n");
|
|
15513
|
+
const title = update.title?.trim();
|
|
15514
|
+
return title && title.length > 0 ? title : null;
|
|
15515
|
+
}
|
|
15516
|
+
function humanizeKind(kind) {
|
|
15232
15517
|
switch (kind) {
|
|
15233
15518
|
case "allow_once":
|
|
15234
15519
|
return "Allow once";
|
|
@@ -15420,7 +15705,7 @@ function parsePayload2(schema, raw) {
|
|
|
15420
15705
|
|
|
15421
15706
|
// src/services/file-ops.service.ts
|
|
15422
15707
|
var fs22 = __toESM(require("fs/promises"));
|
|
15423
|
-
var
|
|
15708
|
+
var path27 = __toESM(require("path"));
|
|
15424
15709
|
var MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
15425
15710
|
var MAX_WALK_DEPTH = 6;
|
|
15426
15711
|
var MAX_VISITED_DIRS = 5e3;
|
|
@@ -15455,8 +15740,8 @@ var SUBDIR_IGNORE = /* @__PURE__ */ new Set([
|
|
|
15455
15740
|
"__pycache__"
|
|
15456
15741
|
]);
|
|
15457
15742
|
function isUnder(parent, candidate) {
|
|
15458
|
-
const rel =
|
|
15459
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
15743
|
+
const rel = path27.relative(parent, candidate);
|
|
15744
|
+
return rel === "" || !rel.startsWith("..") && !path27.isAbsolute(rel);
|
|
15460
15745
|
}
|
|
15461
15746
|
async function isExistingFile(absPath) {
|
|
15462
15747
|
try {
|
|
@@ -15479,7 +15764,7 @@ async function walkForSuffix(dir, needleVariants, depth, ctx) {
|
|
|
15479
15764
|
}
|
|
15480
15765
|
for (const e of entries) {
|
|
15481
15766
|
if (!e.isFile()) continue;
|
|
15482
|
-
const full =
|
|
15767
|
+
const full = path27.join(dir, e.name);
|
|
15483
15768
|
if (needleVariants.some((needle) => full.endsWith(needle))) {
|
|
15484
15769
|
ctx.matches.push(full);
|
|
15485
15770
|
if (ctx.matches.length >= ctx.cap) return;
|
|
@@ -15489,21 +15774,21 @@ async function walkForSuffix(dir, needleVariants, depth, ctx) {
|
|
|
15489
15774
|
if (!e.isDirectory()) continue;
|
|
15490
15775
|
if (SUBDIR_IGNORE.has(e.name)) continue;
|
|
15491
15776
|
if (e.name.startsWith(".") && SUBDIR_IGNORE.has(e.name)) continue;
|
|
15492
|
-
await walkForSuffix(
|
|
15777
|
+
await walkForSuffix(path27.join(dir, e.name), needleVariants, depth + 1, ctx);
|
|
15493
15778
|
if (ctx.matches.length >= ctx.cap) return;
|
|
15494
15779
|
}
|
|
15495
15780
|
}
|
|
15496
15781
|
async function findFile(rawPath) {
|
|
15497
15782
|
const cwd = process.cwd();
|
|
15498
|
-
if (
|
|
15499
|
-
const abs =
|
|
15783
|
+
if (path27.isAbsolute(rawPath)) {
|
|
15784
|
+
const abs = path27.normalize(rawPath);
|
|
15500
15785
|
if (isUnder(cwd, abs) && await isExistingFile(abs)) return abs;
|
|
15501
15786
|
}
|
|
15502
|
-
const direct =
|
|
15787
|
+
const direct = path27.resolve(cwd, rawPath);
|
|
15503
15788
|
if (isUnder(cwd, direct) && await isExistingFile(direct)) return direct;
|
|
15504
|
-
const normalized =
|
|
15789
|
+
const normalized = path27.normalize(rawPath).replace(/^[./\\]+/, "");
|
|
15505
15790
|
const needles = [
|
|
15506
|
-
`${
|
|
15791
|
+
`${path27.sep}${normalized}`,
|
|
15507
15792
|
`/${normalized}`
|
|
15508
15793
|
].filter((v, i, a) => a.indexOf(v) === i);
|
|
15509
15794
|
const ctx = { visited: 0, matches: [], cap: 16 };
|
|
@@ -15517,7 +15802,7 @@ async function findWriteTarget(rawPath) {
|
|
|
15517
15802
|
const found = await findFile(rawPath);
|
|
15518
15803
|
if (found) return found;
|
|
15519
15804
|
const cwd = process.cwd();
|
|
15520
|
-
const fallback =
|
|
15805
|
+
const fallback = path27.isAbsolute(rawPath) ? path27.normalize(rawPath) : path27.resolve(cwd, rawPath);
|
|
15521
15806
|
if (!isUnder(cwd, fallback)) return null;
|
|
15522
15807
|
return fallback;
|
|
15523
15808
|
}
|
|
@@ -15557,7 +15842,7 @@ async function writeProjectFile(rawPath, content) {
|
|
|
15557
15842
|
if (Buffer.byteLength(content, "utf-8") > MAX_FILE_BYTES) {
|
|
15558
15843
|
return { error: "Content too large." };
|
|
15559
15844
|
}
|
|
15560
|
-
await fs22.mkdir(
|
|
15845
|
+
await fs22.mkdir(path27.dirname(abs), { recursive: true });
|
|
15561
15846
|
await fs22.writeFile(abs, content, "utf-8");
|
|
15562
15847
|
return { ok: true };
|
|
15563
15848
|
} catch (e) {
|
|
@@ -15567,11 +15852,11 @@ async function writeProjectFile(rawPath, content) {
|
|
|
15567
15852
|
}
|
|
15568
15853
|
|
|
15569
15854
|
// src/services/project-ops.service.ts
|
|
15570
|
-
var
|
|
15855
|
+
var import_child_process9 = require("child_process");
|
|
15571
15856
|
var import_util2 = require("util");
|
|
15572
15857
|
var fs23 = __toESM(require("fs/promises"));
|
|
15573
|
-
var
|
|
15574
|
-
var execFileP3 = (0, import_util2.promisify)(
|
|
15858
|
+
var path28 = __toESM(require("path"));
|
|
15859
|
+
var execFileP3 = (0, import_util2.promisify)(import_child_process9.execFile);
|
|
15575
15860
|
var PROJECT_IGNORE = /* @__PURE__ */ new Set([
|
|
15576
15861
|
"node_modules",
|
|
15577
15862
|
".git",
|
|
@@ -15628,12 +15913,12 @@ async function listProjectFiles(opts = {}) {
|
|
|
15628
15913
|
return;
|
|
15629
15914
|
}
|
|
15630
15915
|
if (PROJECT_IGNORE.has(e.name)) continue;
|
|
15631
|
-
const full =
|
|
15916
|
+
const full = path28.join(dir, e.name);
|
|
15632
15917
|
if (e.isDirectory()) {
|
|
15633
15918
|
if (depth >= 12) continue;
|
|
15634
15919
|
await walk(full, depth + 1);
|
|
15635
15920
|
} else if (e.isFile()) {
|
|
15636
|
-
const rel =
|
|
15921
|
+
const rel = path28.relative(root, full);
|
|
15637
15922
|
if (q2 && !rel.toLowerCase().includes(q2) && !e.name.toLowerCase().includes(q2)) {
|
|
15638
15923
|
continue;
|
|
15639
15924
|
}
|
|
@@ -15741,7 +16026,7 @@ async function gitStatus(cwd) {
|
|
|
15741
16026
|
let hasMergeInProgress = false;
|
|
15742
16027
|
try {
|
|
15743
16028
|
const gitDir = (await git(["rev-parse", "--git-dir"], root)).stdout.trim();
|
|
15744
|
-
const mergeHead =
|
|
16029
|
+
const mergeHead = path28.isAbsolute(gitDir) ? path28.join(gitDir, "MERGE_HEAD") : path28.join(root, gitDir, "MERGE_HEAD");
|
|
15745
16030
|
await fs23.access(mergeHead);
|
|
15746
16031
|
hasMergeInProgress = true;
|
|
15747
16032
|
} catch {
|
|
@@ -15790,381 +16075,124 @@ async function gitCommit(message, files, cwd) {
|
|
|
15790
16075
|
return { error: "Commit message is required." };
|
|
15791
16076
|
}
|
|
15792
16077
|
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" };
|
|
15802
|
-
}
|
|
15803
|
-
const head = await git(["rev-parse", "HEAD"], cwd);
|
|
15804
|
-
return { ok: true, commit: head.stdout.trim() };
|
|
15805
|
-
}
|
|
15806
|
-
async function gitPush(cwd) {
|
|
15807
|
-
const r = await git(["push"], cwd);
|
|
15808
|
-
if (r.code !== 0) return { error: r.stderr.trim() || "git push failed" };
|
|
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(".");
|
|
15841
|
-
}
|
|
15842
|
-
for (const p2 of opts.exclude ?? []) args2.push(`:!${p2}`);
|
|
15843
|
-
const r = await git(args2, cwd);
|
|
15844
|
-
if (r.code !== 0 && r.code !== 1) {
|
|
15845
|
-
return jsSearchFiles(opts, cwd, cap);
|
|
15846
|
-
}
|
|
15847
|
-
const hits = [];
|
|
15848
|
-
const lines = r.stdout.split("\n");
|
|
15849
|
-
let truncated = false;
|
|
15850
|
-
let byteBudget = MAX_SEARCH_BYTES;
|
|
15851
|
-
for (const line of lines) {
|
|
15852
|
-
if (!line) continue;
|
|
15853
|
-
if (hits.length >= cap) {
|
|
15854
|
-
truncated = true;
|
|
15855
|
-
break;
|
|
15856
|
-
}
|
|
15857
|
-
if (byteBudget <= 0) {
|
|
15858
|
-
truncated = true;
|
|
15859
|
-
break;
|
|
15860
|
-
}
|
|
15861
|
-
byteBudget -= line.length;
|
|
15862
|
-
const m = line.match(/^([^]+?):(\d+):(\d+):(.*)$/);
|
|
15863
|
-
if (!m) continue;
|
|
15864
|
-
const filePath = m[1] ?? "";
|
|
15865
|
-
const lineNo = parseInt(m[2] ?? "0", 10);
|
|
15866
|
-
const col = parseInt(m[3] ?? "1", 10);
|
|
15867
|
-
const text = (m[4] ?? "").slice(0, 400);
|
|
15868
|
-
if (!filePath) continue;
|
|
15869
|
-
hits.push({
|
|
15870
|
-
path: filePath,
|
|
15871
|
-
line: lineNo,
|
|
15872
|
-
column: col,
|
|
15873
|
-
text,
|
|
15874
|
-
matchLength: opts.query.length
|
|
15875
|
-
});
|
|
15876
|
-
}
|
|
15877
|
-
return { hits, total: hits.length, truncated };
|
|
15878
|
-
}
|
|
15879
|
-
async function jsSearchFiles(opts, cwd, cap) {
|
|
15880
|
-
const files = await listProjectFiles({ cwd, cap: 2e3 });
|
|
15881
|
-
const hits = [];
|
|
15882
|
-
const needle = opts.caseSensitive ? opts.query : opts.query.toLowerCase();
|
|
15883
|
-
let truncated = files.truncated;
|
|
15884
|
-
for (const f of files.files) {
|
|
15885
|
-
if (hits.length >= cap) {
|
|
15886
|
-
truncated = true;
|
|
15887
|
-
break;
|
|
15888
|
-
}
|
|
15889
|
-
let content = "";
|
|
15890
|
-
try {
|
|
15891
|
-
content = await fs23.readFile(path27.join(cwd, f.path), "utf8");
|
|
15892
|
-
} catch {
|
|
15893
|
-
continue;
|
|
15894
|
-
}
|
|
15895
|
-
const lines = content.split("\n");
|
|
15896
|
-
for (let i = 0; i < lines.length && hits.length < cap; i++) {
|
|
15897
|
-
const line = lines[i] ?? "";
|
|
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
|
-
});
|
|
15908
|
-
}
|
|
15909
|
-
}
|
|
15910
|
-
return { hits, total: hits.length, truncated };
|
|
15911
|
-
}
|
|
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");
|
|
15922
|
-
try {
|
|
15923
|
-
nodePtyModule = require(vendoredPath);
|
|
15924
|
-
return nodePtyModule;
|
|
15925
|
-
} catch {
|
|
15926
|
-
try {
|
|
15927
|
-
nodePtyModule = require("node-pty");
|
|
15928
|
-
return nodePtyModule;
|
|
15929
|
-
} catch {
|
|
15930
|
-
nodePtyModule = null;
|
|
15931
|
-
return nodePtyModule;
|
|
15932
|
-
}
|
|
15933
|
-
}
|
|
15934
|
-
}
|
|
15935
|
-
var sessions = /* @__PURE__ */ new Map();
|
|
15936
|
-
var onDataHandler = null;
|
|
15937
|
-
var onExitHandler = null;
|
|
15938
|
-
function registerTerminalHandlers(opts) {
|
|
15939
|
-
onDataHandler = opts.onData;
|
|
15940
|
-
onExitHandler = opts.onExit;
|
|
15941
|
-
}
|
|
15942
|
-
function defaultShell() {
|
|
15943
|
-
if (process.platform === "win32") {
|
|
15944
|
-
return process.env.COMSPEC ?? "powershell.exe";
|
|
15945
|
-
}
|
|
15946
|
-
return process.env.SHELL ?? "/bin/bash";
|
|
15947
|
-
}
|
|
15948
|
-
var PYTHON_TERMINAL_HELPER = `import os,pty,sys,select,signal,struct,fcntl,termios,errno,re
|
|
15949
|
-
m,s=pty.openpty()
|
|
15950
|
-
try:
|
|
15951
|
-
cols=int(os.environ.get('COLUMNS','80'))
|
|
15952
|
-
rows=int(os.environ.get('LINES','24'))
|
|
15953
|
-
fcntl.ioctl(s,termios.TIOCSWINSZ,struct.pack('HHHH',rows,cols,0,0))
|
|
15954
|
-
except Exception:pass
|
|
15955
|
-
pid=os.fork()
|
|
15956
|
-
if pid==0:
|
|
15957
|
-
os.close(m)
|
|
15958
|
-
os.setsid()
|
|
15959
|
-
try:fcntl.ioctl(s,termios.TIOCSCTTY,0)
|
|
15960
|
-
except Exception:pass
|
|
15961
|
-
for fd in[0,1,2]:os.dup2(s,fd)
|
|
15962
|
-
if s>2:os.close(s)
|
|
15963
|
-
os.execvp(sys.argv[1],sys.argv[1:])
|
|
15964
|
-
sys.exit(127)
|
|
15965
|
-
os.close(s)
|
|
15966
|
-
done=[False]
|
|
15967
|
-
def onchld(n,f):
|
|
15968
|
-
try:os.waitpid(pid,os.WNOHANG)
|
|
15969
|
-
except Exception:pass
|
|
15970
|
-
done[0]=True
|
|
15971
|
-
signal.signal(signal.SIGCHLD,onchld)
|
|
15972
|
-
signal.signal(signal.SIGHUP,signal.SIG_IGN)
|
|
15973
|
-
i=sys.stdin.fileno()
|
|
15974
|
-
o=sys.stdout.fileno()
|
|
15975
|
-
in_buf=b''
|
|
15976
|
-
resize_re=re.compile(rb'\\x00CW (\\d+) (\\d+)\\n')
|
|
15977
|
-
while not done[0]:
|
|
15978
|
-
try:r,_,_=select.select([i,m],[],[],0.1)
|
|
15979
|
-
except OSError as e:
|
|
15980
|
-
if e.errno==errno.EINTR:continue
|
|
15981
|
-
break
|
|
15982
|
-
if i in r:
|
|
15983
|
-
try:
|
|
15984
|
-
d=os.read(i,4096)
|
|
15985
|
-
if not d:break
|
|
15986
|
-
in_buf+=d
|
|
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"]) {
|
|
16021
|
-
try {
|
|
16022
|
-
const out2 = require("child_process").spawnSync("which", [name], { encoding: "utf8" });
|
|
16023
|
-
if (out2.status === 0 && out2.stdout?.trim()) return out2.stdout.trim();
|
|
16024
|
-
} catch {
|
|
16025
|
-
}
|
|
16026
|
-
}
|
|
16027
|
-
return null;
|
|
16028
|
-
}
|
|
16029
|
-
function createPythonSession(id, shell, cwd, env, cols, rows) {
|
|
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;
|
|
16035
|
-
try {
|
|
16036
|
-
child = (0, import_child_process9.spawn)(python, ["-c", PYTHON_TERMINAL_HELPER, shell], {
|
|
16037
|
-
cwd,
|
|
16038
|
-
env: { ...env, COLUMNS: String(cols), LINES: String(rows) },
|
|
16039
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
16040
|
-
});
|
|
16041
|
-
} catch (e) {
|
|
16042
|
-
return { error: e instanceof Error ? e.message : "python spawn failed" };
|
|
16043
|
-
}
|
|
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
|
-
}
|
|
16077
|
-
function openTerminal(opts) {
|
|
16078
|
-
if (sessions.size >= MAX_CONCURRENT_SESSIONS) {
|
|
16079
|
-
return { error: `Too many open terminals (max ${MAX_CONCURRENT_SESSIONS})` };
|
|
16080
|
-
}
|
|
16081
|
-
const shell = opts.shell ?? defaultShell();
|
|
16082
|
-
const cwd = opts.cwd ?? process.cwd();
|
|
16083
|
-
const env = {
|
|
16084
|
-
...process.env,
|
|
16085
|
-
TERM: "xterm-256color",
|
|
16086
|
-
COLORTERM: "truecolor",
|
|
16087
|
-
FORCE_COLOR: "1"
|
|
16088
|
-
};
|
|
16089
|
-
const cols = Math.max(1, Math.min(opts.cols ?? 80, 500));
|
|
16090
|
-
const rows = Math.max(1, Math.min(opts.rows ?? 24, 200));
|
|
16091
|
-
const id = (0, import_crypto2.randomUUID)();
|
|
16092
|
-
const ptyMod = loadNodePty2();
|
|
16093
|
-
if (ptyMod) {
|
|
16094
|
-
try {
|
|
16095
|
-
const term = ptyMod.spawn(shell, [], {
|
|
16096
|
-
name: "xterm-256color",
|
|
16097
|
-
cols,
|
|
16098
|
-
rows,
|
|
16099
|
-
cwd,
|
|
16100
|
-
env,
|
|
16101
|
-
useConpty: process.platform === "win32" ? true : void 0
|
|
16102
|
-
});
|
|
16103
|
-
const dataListener = term.onData((data) => {
|
|
16104
|
-
onDataHandler?.({ sessionId: id, data });
|
|
16105
|
-
});
|
|
16106
|
-
const exitListener = term.onExit(({ exitCode }) => {
|
|
16107
|
-
onExitHandler?.({ sessionId: id, exitCode });
|
|
16108
|
-
sessions.delete(id);
|
|
16109
|
-
});
|
|
16110
|
-
sessions.set(id, {
|
|
16111
|
-
id,
|
|
16112
|
-
write: (d3) => term.write(d3),
|
|
16113
|
-
resize: (cs, rs) => term.resize(cs, rs),
|
|
16114
|
-
kill: () => {
|
|
16115
|
-
dataListener.dispose();
|
|
16116
|
-
exitListener.dispose();
|
|
16117
|
-
term.kill();
|
|
16118
|
-
}
|
|
16119
|
-
});
|
|
16120
|
-
return { sessionId: id };
|
|
16121
|
-
} catch (e) {
|
|
16122
|
-
if (process.platform === "win32") {
|
|
16123
|
-
return { error: e instanceof Error ? e.message : "spawn failed" };
|
|
16124
|
-
}
|
|
16125
|
-
}
|
|
16126
|
-
} else if (process.platform === "win32") {
|
|
16127
|
-
return {
|
|
16128
|
-
error: `node-pty native module unavailable on ${process.platform}-${process.arch}; terminal feature disabled for this platform`
|
|
16129
|
-
};
|
|
16078
|
+
const r2 = await git(["add", "--", ...files], cwd);
|
|
16079
|
+
if (r2.code !== 0) return { error: `git add failed: ${r2.stderr.trim()}` };
|
|
16080
|
+
} else {
|
|
16081
|
+
const r2 = await git(["add", "-A"], cwd);
|
|
16082
|
+
if (r2.code !== 0) return { error: `git add failed: ${r2.stderr.trim()}` };
|
|
16130
16083
|
}
|
|
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" };
|
|
16084
|
+
const r = await git(["commit", "-m", message], cwd);
|
|
16085
|
+
if (r.code !== 0) {
|
|
16086
|
+
return { error: r.stderr.trim() || "git commit failed" };
|
|
16144
16087
|
}
|
|
16088
|
+
const head = await git(["rev-parse", "HEAD"], cwd);
|
|
16089
|
+
return { ok: true, commit: head.stdout.trim() };
|
|
16145
16090
|
}
|
|
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
|
-
}
|
|
16091
|
+
async function gitPush(cwd) {
|
|
16092
|
+
const r = await git(["push"], cwd);
|
|
16093
|
+
if (r.code !== 0) return { error: r.stderr.trim() || "git push failed" };
|
|
16094
|
+
return { ok: true, output: (r.stdout + r.stderr).trim() };
|
|
16155
16095
|
}
|
|
16156
|
-
function
|
|
16157
|
-
const
|
|
16158
|
-
if (
|
|
16159
|
-
|
|
16160
|
-
|
|
16161
|
-
|
|
16162
|
-
}
|
|
16163
|
-
|
|
16096
|
+
async function gitPull(cwd) {
|
|
16097
|
+
const r = await git(["pull", "--ff-only"], cwd);
|
|
16098
|
+
if (r.code !== 0) return { error: r.stderr.trim() || "git pull failed" };
|
|
16099
|
+
return { ok: true, output: (r.stdout + r.stderr).trim() };
|
|
16100
|
+
}
|
|
16101
|
+
async function gitResolve(file, side, cwd) {
|
|
16102
|
+
const r = await git(["checkout", `--${side}`, "--", file], cwd);
|
|
16103
|
+
if (r.code !== 0) return { error: r.stderr.trim() || `git checkout --${side} failed` };
|
|
16104
|
+
const add = await git(["add", "--", file], cwd);
|
|
16105
|
+
if (add.code !== 0) return { error: add.stderr.trim() || "git add (resolve) failed" };
|
|
16164
16106
|
return { ok: true };
|
|
16165
16107
|
}
|
|
16166
|
-
|
|
16167
|
-
|
|
16108
|
+
var MAX_SEARCH_HITS = 500;
|
|
16109
|
+
var MAX_SEARCH_BYTES = 256 * 1024;
|
|
16110
|
+
async function searchFiles(opts) {
|
|
16111
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
16112
|
+
const cap = Math.min(opts.maxResults ?? MAX_SEARCH_HITS, MAX_SEARCH_HITS);
|
|
16113
|
+
if (!opts.query.trim()) return { hits: [], total: 0, truncated: false };
|
|
16114
|
+
const args2 = ["grep", "-n", "--column", "-I"];
|
|
16115
|
+
if (!opts.caseSensitive) args2.push("-i");
|
|
16116
|
+
if (opts.wholeWord) args2.push("-w");
|
|
16117
|
+
if (opts.regex) args2.push("-E");
|
|
16118
|
+
else args2.push("-F");
|
|
16119
|
+
args2.push(opts.query);
|
|
16120
|
+
if (opts.include && opts.include.length > 0) {
|
|
16121
|
+
args2.push("--");
|
|
16122
|
+
for (const p2 of opts.include) args2.push(p2);
|
|
16123
|
+
} else if (opts.exclude && opts.exclude.length > 0) {
|
|
16124
|
+
args2.push("--");
|
|
16125
|
+
args2.push(".");
|
|
16126
|
+
}
|
|
16127
|
+
for (const p2 of opts.exclude ?? []) args2.push(`:!${p2}`);
|
|
16128
|
+
const r = await git(args2, cwd);
|
|
16129
|
+
if (r.code !== 0 && r.code !== 1) {
|
|
16130
|
+
return jsSearchFiles(opts, cwd, cap);
|
|
16131
|
+
}
|
|
16132
|
+
const hits = [];
|
|
16133
|
+
const lines = r.stdout.split("\n");
|
|
16134
|
+
let truncated = false;
|
|
16135
|
+
let byteBudget = MAX_SEARCH_BYTES;
|
|
16136
|
+
for (const line of lines) {
|
|
16137
|
+
if (!line) continue;
|
|
16138
|
+
if (hits.length >= cap) {
|
|
16139
|
+
truncated = true;
|
|
16140
|
+
break;
|
|
16141
|
+
}
|
|
16142
|
+
if (byteBudget <= 0) {
|
|
16143
|
+
truncated = true;
|
|
16144
|
+
break;
|
|
16145
|
+
}
|
|
16146
|
+
byteBudget -= line.length;
|
|
16147
|
+
const m = line.match(/^([^]+?):(\d+):(\d+):(.*)$/);
|
|
16148
|
+
if (!m) continue;
|
|
16149
|
+
const filePath = m[1] ?? "";
|
|
16150
|
+
const lineNo = parseInt(m[2] ?? "0", 10);
|
|
16151
|
+
const col = parseInt(m[3] ?? "1", 10);
|
|
16152
|
+
const text = (m[4] ?? "").slice(0, 400);
|
|
16153
|
+
if (!filePath) continue;
|
|
16154
|
+
hits.push({
|
|
16155
|
+
path: filePath,
|
|
16156
|
+
line: lineNo,
|
|
16157
|
+
column: col,
|
|
16158
|
+
text,
|
|
16159
|
+
matchLength: opts.query.length
|
|
16160
|
+
});
|
|
16161
|
+
}
|
|
16162
|
+
return { hits, total: hits.length, truncated };
|
|
16163
|
+
}
|
|
16164
|
+
async function jsSearchFiles(opts, cwd, cap) {
|
|
16165
|
+
const files = await listProjectFiles({ cwd, cap: 2e3 });
|
|
16166
|
+
const hits = [];
|
|
16167
|
+
const needle = opts.caseSensitive ? opts.query : opts.query.toLowerCase();
|
|
16168
|
+
let truncated = files.truncated;
|
|
16169
|
+
for (const f of files.files) {
|
|
16170
|
+
if (hits.length >= cap) {
|
|
16171
|
+
truncated = true;
|
|
16172
|
+
break;
|
|
16173
|
+
}
|
|
16174
|
+
let content = "";
|
|
16175
|
+
try {
|
|
16176
|
+
content = await fs23.readFile(path28.join(cwd, f.path), "utf8");
|
|
16177
|
+
} catch {
|
|
16178
|
+
continue;
|
|
16179
|
+
}
|
|
16180
|
+
const lines = content.split("\n");
|
|
16181
|
+
for (let i = 0; i < lines.length && hits.length < cap; i++) {
|
|
16182
|
+
const line = lines[i] ?? "";
|
|
16183
|
+
const hay = opts.caseSensitive ? line : line.toLowerCase();
|
|
16184
|
+
const idx = hay.indexOf(needle);
|
|
16185
|
+
if (idx === -1) continue;
|
|
16186
|
+
hits.push({
|
|
16187
|
+
path: f.path,
|
|
16188
|
+
line: i + 1,
|
|
16189
|
+
column: idx + 1,
|
|
16190
|
+
text: line.slice(0, 400),
|
|
16191
|
+
matchLength: opts.query.length
|
|
16192
|
+
});
|
|
16193
|
+
}
|
|
16194
|
+
}
|
|
16195
|
+
return { hits, total: hits.length, truncated };
|
|
16168
16196
|
}
|
|
16169
16197
|
|
|
16170
16198
|
// src/services/apply-file-review.service.ts
|
|
@@ -17876,6 +17904,7 @@ var import_child_process13 = require("child_process");
|
|
|
17876
17904
|
var fs30 = __toESM(require("fs"));
|
|
17877
17905
|
var os24 = __toESM(require("os"));
|
|
17878
17906
|
var path35 = __toESM(require("path"));
|
|
17907
|
+
var import_ignore = __toESM(require("ignore"));
|
|
17879
17908
|
|
|
17880
17909
|
// src/services/file-watcher/diff-parser.ts
|
|
17881
17910
|
var HUNK_HEADER_RE = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/;
|
|
@@ -18099,6 +18128,17 @@ var FileWatcherService = class {
|
|
|
18099
18128
|
* doesn't hammer `fs.statSync` for every event.
|
|
18100
18129
|
*/
|
|
18101
18130
|
gitRootByDir = /* @__PURE__ */ new Map();
|
|
18131
|
+
/**
|
|
18132
|
+
* Per-repo `.gitignore` matcher. On first encounter of a git root we
|
|
18133
|
+
* collect every `.gitignore` file under it, parse them through the
|
|
18134
|
+
* `ignore` package, and store the resulting matcher here keyed by
|
|
18135
|
+
* absolute repo path. Subsequent file events in the same repo reuse
|
|
18136
|
+
* the matcher in O(1). The hard-coded IGNORED_PATH_PATTERN above
|
|
18137
|
+
* catches conventional dirs (node_modules, dist, Pods, …); this
|
|
18138
|
+
* matcher layers the repo's own ignore rules on top so per-project
|
|
18139
|
+
* artifacts (ios/, .env*, build outputs) stop polluting the queue.
|
|
18140
|
+
*/
|
|
18141
|
+
gitIgnoreMatcherByRoot = /* @__PURE__ */ new Map();
|
|
18102
18142
|
stopped = false;
|
|
18103
18143
|
/**
|
|
18104
18144
|
* Cross-file coalescing buffer. Keyed by absPath so multiple
|
|
@@ -18324,9 +18364,17 @@ var FileWatcherService = class {
|
|
|
18324
18364
|
);
|
|
18325
18365
|
return;
|
|
18326
18366
|
}
|
|
18327
|
-
this.opts.onRepoDirty?.(gitRoot);
|
|
18328
18367
|
const relPathInRepo = path35.relative(gitRoot, absPath);
|
|
18329
18368
|
if (!relPathInRepo || relPathInRepo.startsWith("..")) return;
|
|
18369
|
+
const matcher = this.getGitIgnoreMatcher(gitRoot);
|
|
18370
|
+
if (matcher && matcher.ignores(relPathInRepo)) {
|
|
18371
|
+
log.trace(
|
|
18372
|
+
"fileWatcher",
|
|
18373
|
+
`${relPathInRepo} ignored by ${path35.basename(gitRoot)}/.gitignore \u2014 suppressing emit`
|
|
18374
|
+
);
|
|
18375
|
+
return;
|
|
18376
|
+
}
|
|
18377
|
+
this.opts.onRepoDirty?.(gitRoot);
|
|
18330
18378
|
const repoPath = path35.relative(this.opts.workingDir, gitRoot);
|
|
18331
18379
|
const repoName = path35.basename(gitRoot);
|
|
18332
18380
|
let diffText = "";
|
|
@@ -18462,6 +18510,79 @@ var FileWatcherService = class {
|
|
|
18462
18510
|
async postReviewBlame(body) {
|
|
18463
18511
|
await this.postWithRetries(`${this.apiBase}/api/review/blame`, body);
|
|
18464
18512
|
}
|
|
18513
|
+
/**
|
|
18514
|
+
* Lazily build and cache a per-repo `.gitignore` matcher. We walk the
|
|
18515
|
+
* repo collecting every `.gitignore` file (skipping the same dirs
|
|
18516
|
+
* IGNORED_PATH_PATTERN already filters at chokidar level, so we
|
|
18517
|
+
* don't read inside node_modules / Pods / etc.) and feed each file
|
|
18518
|
+
* into a single `ignore` matcher anchored at the git root. Subsequent
|
|
18519
|
+
* calls return the cached matcher; failures fall back to `null`,
|
|
18520
|
+
* which the caller treats as "no extra filtering" — so a malformed
|
|
18521
|
+
* .gitignore degrades to the prior pre-fix behaviour rather than
|
|
18522
|
+
* silently dropping every event.
|
|
18523
|
+
*/
|
|
18524
|
+
getGitIgnoreMatcher(gitRoot) {
|
|
18525
|
+
if (this.gitIgnoreMatcherByRoot.has(gitRoot)) {
|
|
18526
|
+
return this.gitIgnoreMatcherByRoot.get(gitRoot) ?? null;
|
|
18527
|
+
}
|
|
18528
|
+
const matcher = (0, import_ignore.default)();
|
|
18529
|
+
let added = 0;
|
|
18530
|
+
try {
|
|
18531
|
+
this.collectGitignoreFiles(gitRoot, gitRoot, matcher);
|
|
18532
|
+
added = 1;
|
|
18533
|
+
} catch (err) {
|
|
18534
|
+
log.warn(
|
|
18535
|
+
"fileWatcher",
|
|
18536
|
+
`failed to build gitignore matcher for ${gitRoot}: ${err.message}`
|
|
18537
|
+
);
|
|
18538
|
+
}
|
|
18539
|
+
const result = added > 0 ? matcher : null;
|
|
18540
|
+
this.gitIgnoreMatcherByRoot.set(gitRoot, result);
|
|
18541
|
+
return result;
|
|
18542
|
+
}
|
|
18543
|
+
/**
|
|
18544
|
+
* Walk the repo recursively collecting every `.gitignore` file and
|
|
18545
|
+
* add its rules to `matcher`, with the path prefix that anchors them
|
|
18546
|
+
* to the right subdirectory (so a `.gitignore` inside `apps/api`
|
|
18547
|
+
* scopes to `apps/api/*`, not the whole repo). Skips heavy dirs the
|
|
18548
|
+
* static IGNORED_PATH_PATTERN already filters — we don't want to
|
|
18549
|
+
* stat into `node_modules/` looking for buried .gitignore files.
|
|
18550
|
+
*/
|
|
18551
|
+
collectGitignoreFiles(repoRoot, dir, matcher) {
|
|
18552
|
+
let entries;
|
|
18553
|
+
try {
|
|
18554
|
+
entries = fs30.readdirSync(dir, { withFileTypes: true });
|
|
18555
|
+
} catch {
|
|
18556
|
+
return;
|
|
18557
|
+
}
|
|
18558
|
+
const gitignoreEntry = entries.find(
|
|
18559
|
+
(e) => e.isFile() && e.name === ".gitignore"
|
|
18560
|
+
);
|
|
18561
|
+
if (gitignoreEntry) {
|
|
18562
|
+
try {
|
|
18563
|
+
const body = fs30.readFileSync(path35.join(dir, ".gitignore"), "utf8");
|
|
18564
|
+
const rel = path35.relative(repoRoot, dir).replace(/\\/g, "/");
|
|
18565
|
+
const prefixed = body.split(/\r?\n/).map((line) => {
|
|
18566
|
+
const trimmed = line.trim();
|
|
18567
|
+
if (!trimmed || trimmed.startsWith("#")) return line;
|
|
18568
|
+
if (!rel) return line;
|
|
18569
|
+
if (trimmed.startsWith("!")) {
|
|
18570
|
+
return "!" + path35.posix.join(rel, trimmed.slice(1));
|
|
18571
|
+
}
|
|
18572
|
+
return path35.posix.join(rel, trimmed);
|
|
18573
|
+
}).join("\n");
|
|
18574
|
+
matcher.add(prefixed);
|
|
18575
|
+
} catch {
|
|
18576
|
+
}
|
|
18577
|
+
}
|
|
18578
|
+
for (const entry of entries) {
|
|
18579
|
+
if (!entry.isDirectory()) continue;
|
|
18580
|
+
if (entry.name === ".git") continue;
|
|
18581
|
+
const childAbs = path35.join(dir, entry.name);
|
|
18582
|
+
if (isIgnoredFilePath(childAbs)) continue;
|
|
18583
|
+
this.collectGitignoreFiles(repoRoot, childAbs, matcher);
|
|
18584
|
+
}
|
|
18585
|
+
}
|
|
18465
18586
|
async postWithRetries(url, body) {
|
|
18466
18587
|
const payload = JSON.stringify(body);
|
|
18467
18588
|
const headers = {
|
|
@@ -19425,6 +19546,24 @@ async function runAcpSession(opts) {
|
|
|
19425
19546
|
pluginAuthToken: opts.pluginAuthToken
|
|
19426
19547
|
});
|
|
19427
19548
|
const streaming = new StreamingState(publisher);
|
|
19549
|
+
registerTerminalHandlers({
|
|
19550
|
+
onData: ({ sessionId, data }) => {
|
|
19551
|
+
void publisher.publishOutput({
|
|
19552
|
+
type: "terminal_data",
|
|
19553
|
+
terminalSessionId: sessionId,
|
|
19554
|
+
data,
|
|
19555
|
+
done: false
|
|
19556
|
+
});
|
|
19557
|
+
},
|
|
19558
|
+
onExit: ({ sessionId, exitCode }) => {
|
|
19559
|
+
void publisher.publishOutput({
|
|
19560
|
+
type: "terminal_exit",
|
|
19561
|
+
terminalSessionId: sessionId,
|
|
19562
|
+
exitCode,
|
|
19563
|
+
done: true
|
|
19564
|
+
});
|
|
19565
|
+
}
|
|
19566
|
+
});
|
|
19428
19567
|
let updateCount = 0;
|
|
19429
19568
|
const client2 = new AcpClient({
|
|
19430
19569
|
adapter: opts.adapter,
|
|
@@ -19502,20 +19641,37 @@ async function runAcpSession(opts) {
|
|
|
19502
19641
|
const runtime = createInteractiveAgentStrategy(opts.agent, createOsStrategy());
|
|
19503
19642
|
const models = await runtime.listModels();
|
|
19504
19643
|
const history = new AcpHistory(publisher, { agent: opts.agent, acpSessionId });
|
|
19505
|
-
const
|
|
19644
|
+
const turnFiles = new TurnFileAggregator({
|
|
19506
19645
|
workingDir: opts.cwd,
|
|
19507
19646
|
sessionId: opts.sessionId,
|
|
19508
19647
|
pluginId: opts.pluginId,
|
|
19509
|
-
pluginAuthToken: opts.pluginAuthToken
|
|
19648
|
+
pluginAuthToken: opts.pluginAuthToken,
|
|
19649
|
+
agentId: opts.agent
|
|
19510
19650
|
});
|
|
19511
|
-
const
|
|
19651
|
+
const REPO_DIRTY_FLUSH_DEBOUNCE_MS = 2e3;
|
|
19652
|
+
let repoDirtyTimer = null;
|
|
19653
|
+
const fileWatcher = new FileWatcherService({
|
|
19512
19654
|
workingDir: opts.cwd,
|
|
19513
19655
|
sessionId: opts.sessionId,
|
|
19514
19656
|
pluginId: opts.pluginId,
|
|
19515
19657
|
pluginAuthToken: opts.pluginAuthToken,
|
|
19516
|
-
|
|
19658
|
+
onRepoDirty: () => {
|
|
19659
|
+
if (repoDirtyTimer) clearTimeout(repoDirtyTimer);
|
|
19660
|
+
repoDirtyTimer = setTimeout(() => {
|
|
19661
|
+
repoDirtyTimer = null;
|
|
19662
|
+
log.info("acpRunner", "onRepoDirty debounce fired \u2014 running flushTurn");
|
|
19663
|
+
turnFiles.flushTurn().catch((err) => {
|
|
19664
|
+
log.warn("acpRunner", `flushTurn from onRepoDirty failed: ${describeError(err)}`);
|
|
19665
|
+
});
|
|
19666
|
+
}, REPO_DIRTY_FLUSH_DEBOUNCE_MS);
|
|
19667
|
+
}
|
|
19517
19668
|
});
|
|
19518
|
-
fileWatcher.start().
|
|
19669
|
+
fileWatcher.start().then(() => {
|
|
19670
|
+
log.info(
|
|
19671
|
+
"acpRunner",
|
|
19672
|
+
`fileWatcher started \u2014 watching cwd=${opts.cwd} for file changes (debounce ${REPO_DIRTY_FLUSH_DEBOUNCE_MS}ms before flushTurn)`
|
|
19673
|
+
);
|
|
19674
|
+
}).catch((err) => {
|
|
19519
19675
|
log.warn("acpRunner", `fileWatcher.start failed: ${describeError(err)}`);
|
|
19520
19676
|
});
|
|
19521
19677
|
const relay = new CommandRelayService(
|
|
@@ -19542,6 +19698,7 @@ async function runAcpSession(opts) {
|
|
|
19542
19698
|
relay.stop();
|
|
19543
19699
|
void fileWatcher.stop();
|
|
19544
19700
|
turnFiles.stop();
|
|
19701
|
+
closeAllTerminals();
|
|
19545
19702
|
await client2.stop();
|
|
19546
19703
|
process.exit(0);
|
|
19547
19704
|
};
|
|
@@ -19756,6 +19913,7 @@ async function handleCommand(cmd, client2, relay, acpSessionId, models, streamin
|
|
|
19756
19913
|
}
|
|
19757
19914
|
}
|
|
19758
19915
|
relay.stop();
|
|
19916
|
+
closeAllTerminals();
|
|
19759
19917
|
await client2.stop();
|
|
19760
19918
|
process.exit(0);
|
|
19761
19919
|
return;
|
|
@@ -19765,26 +19923,58 @@ async function handleCommand(cmd, client2, relay, acpSessionId, models, streamin
|
|
|
19765
19923
|
case "preview_stop":
|
|
19766
19924
|
case "save_preview_config": {
|
|
19767
19925
|
const runtime = createInteractiveAgentStrategy(opts.agent, createOsStrategy());
|
|
19768
|
-
|
|
19926
|
+
let previewHandlerAcked = false;
|
|
19927
|
+
const ackingRelay = new Proxy(relay, {
|
|
19928
|
+
get(target, prop, receiver) {
|
|
19929
|
+
if (prop === "sendResult") {
|
|
19930
|
+
return (commandId, status2, result) => {
|
|
19931
|
+
if (commandId === cmd.id) previewHandlerAcked = true;
|
|
19932
|
+
return target.sendResult(commandId, status2, result);
|
|
19933
|
+
};
|
|
19934
|
+
}
|
|
19935
|
+
return Reflect.get(target, prop, receiver);
|
|
19936
|
+
}
|
|
19937
|
+
});
|
|
19938
|
+
const ctx = buildLegacyContextForACP(opts, ackingRelay, runtime);
|
|
19769
19939
|
try {
|
|
19770
19940
|
await dispatchCommand(ctx, cmd);
|
|
19771
|
-
|
|
19941
|
+
if (!previewHandlerAcked) {
|
|
19942
|
+
await relay.sendResult(cmd.id, "completed", {});
|
|
19943
|
+
}
|
|
19772
19944
|
} catch (err) {
|
|
19773
19945
|
log.warn("acpRunner", `${cmd.type} failed: ${describeError(err)}`);
|
|
19774
|
-
|
|
19946
|
+
if (!previewHandlerAcked) {
|
|
19947
|
+
await relay.sendResult(cmd.id, "failed", { error: describeError(err) });
|
|
19948
|
+
}
|
|
19775
19949
|
}
|
|
19776
19950
|
return;
|
|
19777
19951
|
}
|
|
19778
19952
|
default:
|
|
19779
19953
|
if (handlers[cmd.type]) {
|
|
19780
19954
|
const runtime = createInteractiveAgentStrategy(opts.agent, createOsStrategy());
|
|
19781
|
-
|
|
19955
|
+
let handlerAcked = false;
|
|
19956
|
+
const ackingRelay = new Proxy(relay, {
|
|
19957
|
+
get(target, prop, receiver) {
|
|
19958
|
+
if (prop === "sendResult") {
|
|
19959
|
+
return (commandId, status2, result) => {
|
|
19960
|
+
if (commandId === cmd.id) handlerAcked = true;
|
|
19961
|
+
return target.sendResult(commandId, status2, result);
|
|
19962
|
+
};
|
|
19963
|
+
}
|
|
19964
|
+
return Reflect.get(target, prop, receiver);
|
|
19965
|
+
}
|
|
19966
|
+
});
|
|
19967
|
+
const legacyCtx = buildLegacyContextForACP(opts, ackingRelay, runtime);
|
|
19782
19968
|
try {
|
|
19783
19969
|
await dispatchCommand(legacyCtx, cmd);
|
|
19784
|
-
|
|
19970
|
+
if (!handlerAcked) {
|
|
19971
|
+
await relay.sendResult(cmd.id, "completed", {});
|
|
19972
|
+
}
|
|
19785
19973
|
} catch (err) {
|
|
19786
19974
|
log.warn("acpRunner", `legacy handler "${cmd.type}" threw: ${describeError(err)}`);
|
|
19787
|
-
|
|
19975
|
+
if (!handlerAcked) {
|
|
19976
|
+
await relay.sendResult(cmd.id, "failed", { error: describeError(err) });
|
|
19977
|
+
}
|
|
19788
19978
|
}
|
|
19789
19979
|
return;
|
|
19790
19980
|
}
|
|
@@ -21551,6 +21741,19 @@ async function start(requestedAgent) {
|
|
|
21551
21741
|
requestedAgent: requestedAgent ?? null
|
|
21552
21742
|
});
|
|
21553
21743
|
const cwd = process.cwd();
|
|
21744
|
+
const refreshed = await fetchCurrentPluginAuthToken(session.id, pluginId);
|
|
21745
|
+
if (refreshed && refreshed !== session.pluginAuthToken) {
|
|
21746
|
+
addSession({ ...session, pluginAuthToken: refreshed });
|
|
21747
|
+
session.pluginAuthToken = refreshed;
|
|
21748
|
+
showInfo("Reconnected \u2014 refreshed plugin auth token.");
|
|
21749
|
+
} else if (refreshed) {
|
|
21750
|
+
showInfo("Reconnected previous session.");
|
|
21751
|
+
}
|
|
21752
|
+
const tokenForLog = session.pluginAuthToken ?? "(unset)";
|
|
21753
|
+
log.trace(
|
|
21754
|
+
"pluginAuth",
|
|
21755
|
+
`boot triple sessionId=${session.id} pluginId=${pluginId} tokenLen=${tokenForLog.length} tokenHead=${tokenForLog.slice(0, 12)} tokenTail=${tokenForLog.slice(-8)} mintedEqualsCached=${refreshed === session.pluginAuthToken}`
|
|
21756
|
+
);
|
|
21554
21757
|
const acpDisabled = process.env.CODEAM_ACP_DISABLED === "1";
|
|
21555
21758
|
if (!acpDisabled && session.pluginAuthToken) {
|
|
21556
21759
|
const adapter = getAcpAdapter(session.agent);
|
|
@@ -24467,7 +24670,7 @@ function checkChokidar() {
|
|
|
24467
24670
|
}
|
|
24468
24671
|
async function doctor(args2 = []) {
|
|
24469
24672
|
const json = args2.includes("--json");
|
|
24470
|
-
const cliVersion = true ? "2.
|
|
24673
|
+
const cliVersion = true ? "2.28.0" : "0.0.0-dev";
|
|
24471
24674
|
const apiBase = resolveApiBaseUrl();
|
|
24472
24675
|
const diagnosticId = (0, import_node_crypto8.randomUUID)();
|
|
24473
24676
|
log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
|
|
@@ -24666,7 +24869,7 @@ async function completion(args2) {
|
|
|
24666
24869
|
// src/commands/version.ts
|
|
24667
24870
|
var import_picocolors13 = __toESM(require("picocolors"));
|
|
24668
24871
|
function version2() {
|
|
24669
|
-
const v = true ? "2.
|
|
24872
|
+
const v = true ? "2.28.0" : "unknown";
|
|
24670
24873
|
console.log(`${import_picocolors13.default.bold("codeam-cli")} ${import_picocolors13.default.cyan(v)}`);
|
|
24671
24874
|
}
|
|
24672
24875
|
|
|
@@ -24798,6 +25001,7 @@ var fs35 = __toESM(require("fs"));
|
|
|
24798
25001
|
var os27 = __toESM(require("os"));
|
|
24799
25002
|
var path44 = __toESM(require("path"));
|
|
24800
25003
|
var https7 = __toESM(require("https"));
|
|
25004
|
+
var import_node_child_process12 = require("child_process");
|
|
24801
25005
|
var import_picocolors16 = __toESM(require("picocolors"));
|
|
24802
25006
|
var PKG_NAME = "codeam-cli";
|
|
24803
25007
|
var REGISTRY_URL = `https://registry.npmjs.org/${PKG_NAME}/latest`;
|
|
@@ -24889,17 +25093,74 @@ function notifyIfStale(currentVersion, latest) {
|
|
|
24889
25093
|
];
|
|
24890
25094
|
process.stderr.write(lines.join("\n"));
|
|
24891
25095
|
}
|
|
25096
|
+
function isLinkedInstall() {
|
|
25097
|
+
try {
|
|
25098
|
+
const root = (0, import_node_child_process12.execSync)("npm root -g", {
|
|
25099
|
+
encoding: "utf8",
|
|
25100
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
25101
|
+
timeout: 2e3
|
|
25102
|
+
}).trim();
|
|
25103
|
+
if (!root) return false;
|
|
25104
|
+
const pkgPath = path44.join(root, PKG_NAME);
|
|
25105
|
+
return fs35.lstatSync(pkgPath).isSymbolicLink();
|
|
25106
|
+
} catch {
|
|
25107
|
+
return false;
|
|
25108
|
+
}
|
|
25109
|
+
}
|
|
25110
|
+
function maybeAutoUpdate(currentVersion, latest) {
|
|
25111
|
+
if (compareSemver(latest, currentVersion) <= 0) return;
|
|
25112
|
+
if (process.env.CODEAM_NO_AUTO_UPDATE === "1") {
|
|
25113
|
+
notifyIfStale(currentVersion, latest);
|
|
25114
|
+
return;
|
|
25115
|
+
}
|
|
25116
|
+
if (isLinkedInstall()) {
|
|
25117
|
+
notifyIfStale(currentVersion, latest);
|
|
25118
|
+
return;
|
|
25119
|
+
}
|
|
25120
|
+
process.stderr.write(
|
|
25121
|
+
`
|
|
25122
|
+
${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)}...
|
|
25123
|
+
|
|
25124
|
+
`
|
|
25125
|
+
);
|
|
25126
|
+
const install = (0, import_node_child_process12.spawnSync)("npm", ["install", "-g", `${PKG_NAME}@latest`], {
|
|
25127
|
+
stdio: "inherit",
|
|
25128
|
+
env: process.env
|
|
25129
|
+
});
|
|
25130
|
+
if (install.status !== 0) {
|
|
25131
|
+
process.stderr.write(
|
|
25132
|
+
`
|
|
25133
|
+
${import_picocolors16.default.red("!")} Update failed (exit ${install.status ?? "?"}). Continuing on ${currentVersion}.
|
|
25134
|
+
Run ${import_picocolors16.default.cyan("npm install -g codeam-cli")} manually to retry.
|
|
25135
|
+
|
|
25136
|
+
`
|
|
25137
|
+
);
|
|
25138
|
+
return;
|
|
25139
|
+
}
|
|
25140
|
+
try {
|
|
25141
|
+
fs35.unlinkSync(cachePath());
|
|
25142
|
+
} catch {
|
|
25143
|
+
}
|
|
25144
|
+
process.stderr.write(` ${import_picocolors16.default.green("\u2713")} Updated. Resuming session...
|
|
25145
|
+
|
|
25146
|
+
`);
|
|
25147
|
+
const child = (0, import_node_child_process12.spawnSync)("codeam", process.argv.slice(2), {
|
|
25148
|
+
stdio: "inherit",
|
|
25149
|
+
env: process.env
|
|
25150
|
+
});
|
|
25151
|
+
process.exit(child.status ?? 0);
|
|
25152
|
+
}
|
|
24892
25153
|
function checkForUpdates() {
|
|
24893
25154
|
if (process.env.NODE_ENV === "test") return;
|
|
24894
25155
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
24895
25156
|
if (process.env.CI) return;
|
|
24896
25157
|
if (!process.stdout.isTTY) return;
|
|
24897
|
-
const current = true ? "2.
|
|
25158
|
+
const current = true ? "2.28.0" : null;
|
|
24898
25159
|
if (!current) return;
|
|
24899
25160
|
const cache = readCache();
|
|
24900
25161
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
24901
25162
|
if (fresh && cache) {
|
|
24902
|
-
|
|
25163
|
+
maybeAutoUpdate(current, cache.latest);
|
|
24903
25164
|
return;
|
|
24904
25165
|
}
|
|
24905
25166
|
void fetchLatest().then((latest) => {
|