@jpkme/diff-review 0.1.16 → 0.1.18
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/README.md +22 -14
- package/bin/diff-review.mjs +220 -29
- package/lib/acp.mjs +86 -10
- package/lib/agent-login.mjs +848 -36
- package/lib/agent-login.test.mjs +538 -0
- package/lib/agents.mjs +0 -39
- package/lib/agents.test.mjs +17 -24
- package/lib/extract-cache.test.mjs +162 -162
- package/lib/git.mjs +25 -9
- package/lib/model-label.mjs +20 -4
- package/lib/model-label.test.mjs +15 -2
- package/lib/prompt.mjs +12 -10
- package/lib/prompt.test.mjs +12 -0
- package/lib/review-meta.mjs +11 -3
- package/lib/review-meta.test.mjs +19 -2
- package/lib/review-prep.mjs +63 -63
- package/lib/validate.mjs +1 -1
- package/lib/wait-output.test.mjs +1 -1
- package/package.json +2 -2
- package/web/build/client/_app/immutable/assets/0.DdOl5Uve.css +1 -0
- package/web/build/client/_app/immutable/assets/0.DdOl5Uve.css.br +0 -0
- package/web/build/client/_app/immutable/assets/0.DdOl5Uve.css.gz +0 -0
- package/web/build/client/_app/immutable/assets/2.CQmxVXnC.css +1 -0
- package/web/build/client/_app/immutable/assets/2.CQmxVXnC.css.br +0 -0
- package/web/build/client/_app/immutable/assets/2.CQmxVXnC.css.gz +0 -0
- package/web/build/client/_app/immutable/assets/3.BVJgADrM.css +1 -0
- package/web/build/client/_app/immutable/assets/3.BVJgADrM.css.br +0 -0
- package/web/build/client/_app/immutable/assets/3.BVJgADrM.css.gz +0 -0
- package/web/build/client/_app/immutable/assets/storage.Ztmr3s84.css +1 -0
- package/web/build/client/_app/immutable/assets/storage.Ztmr3s84.css.br +0 -0
- package/web/build/client/_app/immutable/assets/storage.Ztmr3s84.css.gz +0 -0
- package/web/build/client/_app/immutable/chunks/{cYiFfRa3.js → -cMMFbSG.js} +1 -1
- package/web/build/client/_app/immutable/chunks/-cMMFbSG.js.br +2 -0
- package/web/build/client/_app/immutable/chunks/-cMMFbSG.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/0vBZPdKl.js +215 -0
- package/web/build/client/_app/immutable/chunks/0vBZPdKl.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/0vBZPdKl.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/B-2kfLeA.js +1 -0
- package/web/build/client/_app/immutable/chunks/B-2kfLeA.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/B-2kfLeA.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/{DHO7NTh1.js → BMoYpzpA.js} +2 -2
- package/web/build/client/_app/immutable/chunks/BMoYpzpA.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/BMoYpzpA.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/{CXhIC34H.js → DA0YnpGR.js} +3 -3
- package/web/build/client/_app/immutable/chunks/DA0YnpGR.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/DA0YnpGR.js.gz +0 -0
- package/web/build/client/_app/immutable/entry/app.CT6P0WMK.js +2 -0
- package/web/build/client/_app/immutable/entry/app.CT6P0WMK.js.br +0 -0
- package/web/build/client/_app/immutable/entry/app.CT6P0WMK.js.gz +0 -0
- package/web/build/client/_app/immutable/entry/start.U0TDB-jm.js +1 -0
- package/web/build/client/_app/immutable/entry/start.U0TDB-jm.js.br +0 -0
- package/web/build/client/_app/immutable/entry/start.U0TDB-jm.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/0.ii5weUjH.js +1 -0
- package/web/build/client/_app/immutable/nodes/0.ii5weUjH.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/0.ii5weUjH.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/1.CM10dYkJ.js +1 -0
- package/web/build/client/_app/immutable/nodes/1.CM10dYkJ.js.br +2 -0
- package/web/build/client/_app/immutable/nodes/1.CM10dYkJ.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/2.B1LHk3LQ.js +12 -0
- package/web/build/client/_app/immutable/nodes/2.B1LHk3LQ.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/2.B1LHk3LQ.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/3.BQOdjQBQ.js +340 -0
- package/web/build/client/_app/immutable/nodes/3.BQOdjQBQ.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/3.BQOdjQBQ.js.gz +0 -0
- package/web/build/client/_app/version.json +1 -1
- package/web/build/client/_app/version.json.br +0 -0
- package/web/build/client/_app/version.json.gz +0 -0
- package/web/build/handler.js +3 -3
- package/web/build/index.js +3 -3
- package/web/build/server/chunks/0-BUdytb9b.js +11 -0
- package/web/build/server/chunks/{0-D7NSxn1-.js.map → 0-BUdytb9b.js.map} +1 -1
- package/web/build/server/chunks/1-DRokSBkY.js +7 -0
- package/web/build/server/chunks/{1-DboYz8Fw.js.map → 1-DRokSBkY.js.map} +1 -1
- package/web/build/server/chunks/2-BxyzaJTR.js +7 -0
- package/web/build/server/chunks/2-BxyzaJTR.js.map +1 -0
- package/web/build/server/chunks/3-D8o0VWkj.js +7 -0
- package/web/build/server/chunks/{3-DgJ_LmTL.js.map → 3-D8o0VWkj.js.map} +1 -1
- package/web/build/server/chunks/{_server.ts-CP5cRLhW.js → _server.ts-kax5Szn0.js} +5 -4
- package/web/build/server/chunks/_server.ts-kax5Szn0.js.map +1 -0
- package/web/build/server/chunks/_server.ts-vS3A9t3f.js +123 -0
- package/web/build/server/chunks/_server.ts-vS3A9t3f.js.map +1 -0
- package/web/build/server/chunks/{agent-sessions-2XGojSJ_.js → agent-sessions-BArYLTW6.js} +1023 -135
- package/web/build/server/chunks/agent-sessions-BArYLTW6.js.map +1 -0
- package/web/build/server/chunks/{git-review-jZ_JJYfv.js → git-review-hZSqk_1P.js} +54 -21
- package/web/build/server/chunks/{git-review-jZ_JJYfv.js.map → git-review-hZSqk_1P.js.map} +1 -1
- package/web/build/server/chunks/{handler-B_8yUXji.js → handler-DbFqzg7l.js} +3 -3
- package/web/build/server/chunks/{handler-B_8yUXji.js.map → handler-DbFqzg7l.js.map} +1 -1
- package/web/build/server/chunks/{hooks.server-rF_eBCff.js → hooks.server-BEl7rVad.js} +4 -3
- package/web/build/server/chunks/{hooks.server-rF_eBCff.js.map → hooks.server-BEl7rVad.js.map} +1 -1
- package/web/build/server/chunks/{index.js-CGw8_Q7V.js → index.js-BB9DZBdR.js} +4 -4
- package/web/build/server/chunks/index.js-BB9DZBdR.js.map +1 -0
- package/web/build/server/chunks/manifest.js-Dk6YX1jw.js +65 -0
- package/web/build/server/chunks/manifest.js-Dk6YX1jw.js.map +1 -0
- package/web/build/server/chunks/{remote-1g4ayag-BMpjk8UD.js → remote-1g4ayag-Bz1vj8X1.js} +6 -6
- package/web/build/server/chunks/remote-1g4ayag-Bz1vj8X1.js.map +1 -0
- package/web/build/server/chunks/{remote-wh6t6j-dYLCg8ks.js → remote-wh6t6j-DSwM3S9h.js} +44 -10
- package/web/build/server/chunks/remote-wh6t6j-DSwM3S9h.js.map +1 -0
- package/web/build/server/chunks/{validate-Da6e71V3.js → validate-CXjTRGnR.js} +34 -17
- package/web/build/server/chunks/validate-CXjTRGnR.js.map +1 -0
- package/web/build/client/_app/immutable/assets/0.BKRG1f36.css +0 -1
- package/web/build/client/_app/immutable/assets/0.BKRG1f36.css.br +0 -0
- package/web/build/client/_app/immutable/assets/0.BKRG1f36.css.gz +0 -0
- package/web/build/client/_app/immutable/assets/2.C4whE4xz.css +0 -1
- package/web/build/client/_app/immutable/assets/2.C4whE4xz.css.br +0 -0
- package/web/build/client/_app/immutable/assets/2.C4whE4xz.css.gz +0 -0
- package/web/build/client/_app/immutable/assets/3.DB-bJJq2.css +0 -1
- package/web/build/client/_app/immutable/assets/3.DB-bJJq2.css.br +0 -0
- package/web/build/client/_app/immutable/assets/3.DB-bJJq2.css.gz +0 -0
- package/web/build/client/_app/immutable/assets/storage.BxR6930R.css +0 -1
- package/web/build/client/_app/immutable/assets/storage.BxR6930R.css.br +0 -0
- package/web/build/client/_app/immutable/assets/storage.BxR6930R.css.gz +0 -0
- package/web/build/client/_app/immutable/chunks/BLMWhW3J.js +0 -1
- package/web/build/client/_app/immutable/chunks/BLMWhW3J.js.br +0 -1
- package/web/build/client/_app/immutable/chunks/BLMWhW3J.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/CXhIC34H.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/CXhIC34H.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/DHO7NTh1.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/DHO7NTh1.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/DpxY75GB.js +0 -215
- package/web/build/client/_app/immutable/chunks/DpxY75GB.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/DpxY75GB.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/cYiFfRa3.js.br +0 -1
- package/web/build/client/_app/immutable/chunks/cYiFfRa3.js.gz +0 -0
- package/web/build/client/_app/immutable/entry/app.CCv57mvp.js +0 -2
- package/web/build/client/_app/immutable/entry/app.CCv57mvp.js.br +0 -0
- package/web/build/client/_app/immutable/entry/app.CCv57mvp.js.gz +0 -0
- package/web/build/client/_app/immutable/entry/start.B012TSbW.js +0 -1
- package/web/build/client/_app/immutable/entry/start.B012TSbW.js.br +0 -1
- package/web/build/client/_app/immutable/entry/start.B012TSbW.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/0.slrnBioC.js +0 -1
- package/web/build/client/_app/immutable/nodes/0.slrnBioC.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/0.slrnBioC.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/1.Bs5JdO4l.js +0 -1
- package/web/build/client/_app/immutable/nodes/1.Bs5JdO4l.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/1.Bs5JdO4l.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/2.D5ULH3EX.js +0 -10
- package/web/build/client/_app/immutable/nodes/2.D5ULH3EX.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/2.D5ULH3EX.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/3.hjzQQgyj.js +0 -173
- package/web/build/client/_app/immutable/nodes/3.hjzQQgyj.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/3.hjzQQgyj.js.gz +0 -0
- package/web/build/server/chunks/0-D7NSxn1-.js +0 -11
- package/web/build/server/chunks/1-DboYz8Fw.js +0 -7
- package/web/build/server/chunks/2-B7LX817Q.js +0 -7
- package/web/build/server/chunks/2-B7LX817Q.js.map +0 -1
- package/web/build/server/chunks/3-DgJ_LmTL.js +0 -7
- package/web/build/server/chunks/_server.ts-CP5cRLhW.js.map +0 -1
- package/web/build/server/chunks/agent-sessions-2XGojSJ_.js.map +0 -1
- package/web/build/server/chunks/index.js-CGw8_Q7V.js.map +0 -1
- package/web/build/server/chunks/manifest.js-CImZFS6G.js +0 -58
- package/web/build/server/chunks/manifest.js-CImZFS6G.js.map +0 -1
- package/web/build/server/chunks/remote-1g4ayag-BMpjk8UD.js.map +0 -1
- package/web/build/server/chunks/remote-wh6t6j-dYLCg8ks.js.map +0 -1
- package/web/build/server/chunks/validate-Da6e71V3.js.map +0 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { p as getRemoteUrl, e as ensureReviewDir, w as writeDiffCache, q as readDiffCache, u as extractBranchDiff, v as loadReviewConfig, x as outputRelPath, m as makeOutputFilename, O as OUTPUT_DIR, y as writeSessionRef, c as companionPaths, d as decodePayload, t as tryResolveGitRoot, f as loadBranchDiff, z as validateAgainst, A as reviewIssues, R as REVIEW_SCHEMA, C as globMatch, D as writeDiffText, E as writeHandoffChat } from './validate-
|
|
2
|
-
import { existsSync,
|
|
3
|
-
import { spawn } from 'node:child_process';
|
|
1
|
+
import { p as getRemoteUrl, e as ensureReviewDir, w as writeDiffCache, q as readDiffCache, u as extractBranchDiff, v as loadReviewConfig, x as outputRelPath, m as makeOutputFilename, O as OUTPUT_DIR, y as writeSessionRef, c as companionPaths, d as decodePayload, t as tryResolveGitRoot, f as loadBranchDiff, z as validateAgainst, A as reviewIssues, R as REVIEW_SCHEMA, C as globMatch, D as writeDiffText, E as writeHandoffChat } from './validate-CXjTRGnR.js';
|
|
2
|
+
import { existsSync, statSync, accessSync, constants, readFileSync, readdirSync, mkdirSync, writeFileSync, copyFileSync, rmSync, realpathSync, unlinkSync } from 'node:fs';
|
|
3
|
+
import { spawnSync, spawn } from 'node:child_process';
|
|
4
4
|
import { homedir, tmpdir } from 'node:os';
|
|
5
5
|
import { join, delimiter, isAbsolute, resolve, basename, dirname, normalize } from 'node:path';
|
|
6
6
|
import { Writable, Readable } from 'node:stream';
|
|
7
7
|
import * as acp from '@agentclientprotocol/sdk';
|
|
8
|
+
import { randomBytes } from 'node:crypto';
|
|
8
9
|
|
|
9
10
|
//#region ../lib/agents.mjs
|
|
10
11
|
/**
|
|
@@ -40,15 +41,6 @@ function readJson(file) {
|
|
|
40
41
|
return null;
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
|
-
/** Primer `model = "..."` de nivel raíz de un TOML (antes de cualquier `[sección]`). */
|
|
44
|
-
function topLevelTomlModel(text) {
|
|
45
|
-
for (const line of String(text).split(/\r?\n/)) {
|
|
46
|
-
if (/^\s*\[/.test(line)) break;
|
|
47
|
-
const m = line.match(/^\s*model\s*=\s*["']([^"']+)["']/);
|
|
48
|
-
if (m) return m[1];
|
|
49
|
-
}
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
44
|
/** Versiones tipo `2.1.286` de mayor a menor (las que no parsean, al final). */
|
|
53
45
|
function byVersionDesc(a, b) {
|
|
54
46
|
const pa = a.split(".").map(Number);
|
|
@@ -182,30 +174,6 @@ var AGENTS = [
|
|
|
182
174
|
};
|
|
183
175
|
}
|
|
184
176
|
},
|
|
185
|
-
{
|
|
186
|
-
id: "codex",
|
|
187
|
-
label: "Codex",
|
|
188
|
-
bins: ["codex"],
|
|
189
|
-
models: [],
|
|
190
|
-
configuredModel: ({ home }) => {
|
|
191
|
-
try {
|
|
192
|
-
const model = topLevelTomlModel(readFileSync(join(home, ".codex", "config.toml"), "utf8"));
|
|
193
|
-
return model ? {
|
|
194
|
-
model,
|
|
195
|
-
source: "~/.codex/config.toml"
|
|
196
|
-
} : null;
|
|
197
|
-
} catch {
|
|
198
|
-
return null;
|
|
199
|
-
}
|
|
200
|
-
},
|
|
201
|
-
args: ({ model }) => [
|
|
202
|
-
"exec",
|
|
203
|
-
"--sandbox",
|
|
204
|
-
"workspace-write",
|
|
205
|
-
...model ? ["-m", model] : [],
|
|
206
|
-
"-"
|
|
207
|
-
]
|
|
208
|
-
},
|
|
209
177
|
{
|
|
210
178
|
id: "cursor",
|
|
211
179
|
label: "Cursor CLI",
|
|
@@ -266,21 +234,6 @@ var AGENTS = [
|
|
|
266
234
|
file: path,
|
|
267
235
|
args: ["--acp"]
|
|
268
236
|
})
|
|
269
|
-
},
|
|
270
|
-
{
|
|
271
|
-
id: "gemini",
|
|
272
|
-
label: "Gemini CLI",
|
|
273
|
-
bins: ["gemini"],
|
|
274
|
-
models: [],
|
|
275
|
-
configuredModel: ({ home }) => {
|
|
276
|
-
const model = readJson(join(home, ".gemini", "settings.json"))?.model;
|
|
277
|
-
const name = typeof model === "string" ? model : model?.name;
|
|
278
|
-
return typeof name === "string" && name ? {
|
|
279
|
-
model: name,
|
|
280
|
-
source: "~/.gemini/settings.json"
|
|
281
|
-
} : null;
|
|
282
|
-
},
|
|
283
|
-
args: ({ model }) => [...model ? ["-m", model] : [], "--yolo"]
|
|
284
237
|
}
|
|
285
238
|
];
|
|
286
239
|
/** @param {string} file @param {NodeJS.Platform} platform */
|
|
@@ -1066,48 +1019,139 @@ function permissionOutcome(options, allow, opts = {}) {
|
|
|
1066
1019
|
//#endregion
|
|
1067
1020
|
//#region ../lib/agent-login.mjs
|
|
1068
1021
|
/**
|
|
1069
|
-
* Login de agentes ACP: mensajes claros
|
|
1022
|
+
* Login de agentes ACP: clasificación de authMethods, mensajes claros y lanzamiento del CLI de auth.
|
|
1023
|
+
*
|
|
1024
|
+
* Preferencia de launch:
|
|
1025
|
+
* 1. Misma TTY (stdio inherit) si stdin/stdout son TTY — CLI interactiva, exit code real.
|
|
1026
|
+
* 2. Proceso hijo oculto (sin ventana de terminal) si no hay TTY — p. ej. web; OAuth abre el browser.
|
|
1027
|
+
* 3. Terminal externa solo con `external: true` (raro: login que exige TTY interactiva).
|
|
1028
|
+
*
|
|
1029
|
+
* En Windows, `cmd /c start /wait` **no** propaga el exit code del proceso iniciado (siempre 0
|
|
1030
|
+
* hacia Node). El fallback externo usa un archivo sentinel, igual que macOS/Linux.
|
|
1031
|
+
*/
|
|
1032
|
+
/**
|
|
1033
|
+
* @typedef {{ bin: string, args: string[], display: string, env?: Record<string, string> }} LoginCommand
|
|
1034
|
+
* @typedef {'agent' | 'terminal' | 'terminal-meta' | 'unknown'} AuthMethodKind
|
|
1035
|
+
* @typedef {'inherit' | 'hidden' | 'external'} LoginLaunchMode
|
|
1070
1036
|
*/
|
|
1071
1037
|
/**
|
|
1072
|
-
*
|
|
1038
|
+
* Hostname de Copilot (Enterprise). Vacío / `github.com` = cuenta en github.com.
|
|
1039
|
+
* @param {string | null | undefined} host
|
|
1040
|
+
* @returns {string}
|
|
1073
1041
|
*/
|
|
1042
|
+
function normalizeCopilotLoginHost(host) {
|
|
1043
|
+
const h = String(host || "").trim().replace(/^https?:\/\//i, "").replace(/\/.*$/, "");
|
|
1044
|
+
if (!h || /^github\.com$/i.test(h)) return "";
|
|
1045
|
+
return h;
|
|
1046
|
+
}
|
|
1074
1047
|
/**
|
|
1075
|
-
*
|
|
1048
|
+
* Agrega `--host` a un comando `copilot login` cuando el usuario eligió Enterprise.
|
|
1049
|
+
* No fuerza nada por env: el host lo elige quien llama (CLI/UI).
|
|
1050
|
+
* @param {LoginCommand} cmd
|
|
1051
|
+
* @param {string | null | undefined} host
|
|
1052
|
+
* @returns {LoginCommand}
|
|
1053
|
+
*/
|
|
1054
|
+
function withCopilotLoginHost(cmd, host) {
|
|
1055
|
+
if (!cmd || cmd.bin == null) return cmd;
|
|
1056
|
+
const h = normalizeCopilotLoginHost(host);
|
|
1057
|
+
if (!h) return cmd;
|
|
1058
|
+
const args = Array.isArray(cmd.args) ? [...cmd.args] : [];
|
|
1059
|
+
const hostIdx = args.findIndex((a) => a === "--host");
|
|
1060
|
+
if (hostIdx >= 0) args[hostIdx + 1] = h;
|
|
1061
|
+
else args.push("--host", h);
|
|
1062
|
+
const withoutHost = String(cmd.display || "").replace(/\s+--host\s+\S+/g, "").trim();
|
|
1063
|
+
return {
|
|
1064
|
+
...cmd,
|
|
1065
|
+
args,
|
|
1066
|
+
display: `${withoutHost} --host ${h}`.trim()
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
/**
|
|
1070
|
+
* Comando de login interactivo por agente (fallback si no hay authMethod usable).
|
|
1076
1071
|
* @param {string} agentId
|
|
1072
|
+
* @param {{ host?: string | null }} [opts] solo Copilot: host Enterprise opcional
|
|
1077
1073
|
* @returns {LoginCommand | null}
|
|
1078
1074
|
*/
|
|
1079
|
-
function loginCommand(agentId) {
|
|
1075
|
+
function loginCommand(agentId, opts = {}) {
|
|
1080
1076
|
switch (agentId) {
|
|
1081
1077
|
case "claude": return {
|
|
1082
1078
|
bin: "claude",
|
|
1083
|
-
args: ["/login"],
|
|
1084
|
-
display: "claude /login"
|
|
1085
|
-
};
|
|
1086
|
-
case "copilot": return {
|
|
1087
|
-
bin: "gh",
|
|
1088
1079
|
args: ["auth", "login"],
|
|
1089
|
-
display: "
|
|
1080
|
+
display: "claude auth login"
|
|
1090
1081
|
};
|
|
1091
|
-
case "
|
|
1092
|
-
bin: "
|
|
1082
|
+
case "copilot": return withCopilotLoginHost({
|
|
1083
|
+
bin: "copilot",
|
|
1093
1084
|
args: ["login"],
|
|
1094
|
-
display: "
|
|
1095
|
-
};
|
|
1096
|
-
case "
|
|
1097
|
-
bin: "
|
|
1098
|
-
args: [],
|
|
1099
|
-
display: "
|
|
1085
|
+
display: "copilot login"
|
|
1086
|
+
}, opts.host);
|
|
1087
|
+
case "cursor": return {
|
|
1088
|
+
bin: "cursor-agent",
|
|
1089
|
+
args: ["login"],
|
|
1090
|
+
display: "cursor-agent login"
|
|
1100
1091
|
};
|
|
1101
1092
|
default: return null;
|
|
1102
1093
|
}
|
|
1103
1094
|
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Args de `claude auth login …` a partir de un authMethod ACP.
|
|
1097
|
+
* El adaptador anuncia `["--cli", "auth", "login", "--claudeai"]` (o lo mismo tras el path del
|
|
1098
|
+
* adapter en `_meta.terminal-auth.args`); `--cli` solo entiende el wrapper ACP, no `claude`.
|
|
1099
|
+
* @param {any} [method]
|
|
1100
|
+
* @returns {string[]}
|
|
1101
|
+
*/
|
|
1102
|
+
function claudeCliLoginArgs(method) {
|
|
1103
|
+
const meta = method?._meta?.["terminal-auth"];
|
|
1104
|
+
const raw = Array.isArray(meta?.args) ? meta.args.map(String) : Array.isArray(method?.args) ? method.args.map(String) : [];
|
|
1105
|
+
const cliIdx = raw.indexOf("--cli");
|
|
1106
|
+
const after = cliIdx >= 0 ? raw.slice(cliIdx + 1) : raw;
|
|
1107
|
+
if (after[0] === "auth") return after;
|
|
1108
|
+
return [
|
|
1109
|
+
"auth",
|
|
1110
|
+
"login",
|
|
1111
|
+
...after.filter((a) => a.startsWith("--"))
|
|
1112
|
+
];
|
|
1113
|
+
}
|
|
1114
|
+
/**
|
|
1115
|
+
* Clasifica un authMethod de `initialize` según ACP v1 + extensión Copilot `_meta.terminal-auth`.
|
|
1116
|
+
* @param {any} method
|
|
1117
|
+
* @returns {AuthMethodKind}
|
|
1118
|
+
*/
|
|
1119
|
+
function classifyAuthMethod(method) {
|
|
1120
|
+
if (!method || typeof method.id !== "string" || !method.id) return "unknown";
|
|
1121
|
+
if (method._meta != null && method._meta["terminal-auth"] != null) return "terminal-meta";
|
|
1122
|
+
if (method.type === "terminal") return "terminal";
|
|
1123
|
+
if (!method.type || method.type === "agent") return "agent";
|
|
1124
|
+
return "unknown";
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* @param {any[]} authMethods
|
|
1128
|
+
* @returns {{ agent: any[], terminal: any[], terminalMeta: any[], unknown: any[] }}
|
|
1129
|
+
*/
|
|
1130
|
+
function groupAuthMethods(authMethods) {
|
|
1131
|
+
/** @type {{ agent: any[], terminal: any[], terminalMeta: any[], unknown: any[] }} */
|
|
1132
|
+
const groups = {
|
|
1133
|
+
agent: [],
|
|
1134
|
+
terminal: [],
|
|
1135
|
+
terminalMeta: [],
|
|
1136
|
+
unknown: []
|
|
1137
|
+
};
|
|
1138
|
+
for (const method of authMethods || []) {
|
|
1139
|
+
const kind = classifyAuthMethod(method);
|
|
1140
|
+
if (kind === "agent") groups.agent.push(method);
|
|
1141
|
+
else if (kind === "terminal") groups.terminal.push(method);
|
|
1142
|
+
else if (kind === "terminal-meta") groups.terminalMeta.push(method);
|
|
1143
|
+
else groups.unknown.push(method);
|
|
1144
|
+
}
|
|
1145
|
+
return groups;
|
|
1146
|
+
}
|
|
1104
1147
|
/** @param {string} text */
|
|
1105
1148
|
function looksLikeAuthFailure(text) {
|
|
1106
1149
|
const s = String(text || "").toLowerCase();
|
|
1107
|
-
return /(not\s+)?auth|login|unauthor|not logged|sign[\s-]?in|credentials|token.*(invalid|expired|missing)|please\s+run/.test(s);
|
|
1150
|
+
return /(not\s+)?auth|login|unauthor|not logged|sign[\s-]?in|credentials|token.*(invalid|expired|missing)|please\s+run|auth_required/.test(s);
|
|
1108
1151
|
}
|
|
1109
1152
|
/**
|
|
1110
1153
|
* Mensaje en español si el agente murió / falló por falta de login.
|
|
1154
|
+
* Sin comandos CLI: el CTA abre el login por el usuario.
|
|
1111
1155
|
* @param {string} agentId
|
|
1112
1156
|
* @param {string} agentLabel
|
|
1113
1157
|
* @param {string} detail
|
|
@@ -1115,38 +1159,280 @@ function looksLikeAuthFailure(text) {
|
|
|
1115
1159
|
*/
|
|
1116
1160
|
function authFailureMessage$1(agentId, agentLabel, detail) {
|
|
1117
1161
|
if (!looksLikeAuthFailure(detail)) return null;
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1162
|
+
return `Necesitás iniciar sesión en ${String(agentLabel || "").trim() || "el agente"}.`;
|
|
1163
|
+
}
|
|
1164
|
+
/** Fallo del proceso de login (exit ≠ 0 o cancelado a medias). */
|
|
1165
|
+
function loginIncompleteMessage() {
|
|
1166
|
+
return "No se pudo completar el inicio de sesión. Probá de nuevo.";
|
|
1167
|
+
}
|
|
1168
|
+
/** Estado mientras corre el login (TTY, proceso oculto o terminal externa). */
|
|
1169
|
+
function loginWaitingMessage() {
|
|
1170
|
+
return "Esperando que completes el inicio de sesión…";
|
|
1122
1171
|
}
|
|
1123
1172
|
/**
|
|
1124
|
-
*
|
|
1125
|
-
*
|
|
1173
|
+
* Método interactivo a abrir: `_meta.terminal-auth` (Copilot) o `type: terminal` (Claude).
|
|
1174
|
+
* Si solo hay métodos `agent` (Cursor), devolvemos null → `resolveLoginCommand` usa el fallback CLI.
|
|
1175
|
+
* @param {any[]} [methods]
|
|
1176
|
+
* @returns {any | null}
|
|
1177
|
+
*/
|
|
1178
|
+
function pickLoginAuthMethod(methods) {
|
|
1179
|
+
const grouped = groupAuthMethods(methods || []);
|
|
1180
|
+
return grouped.terminalMeta[0] ?? grouped.terminal[0] ?? null;
|
|
1181
|
+
}
|
|
1182
|
+
/**
|
|
1183
|
+
* ¿Podemos correr el login en la misma terminal (stdio inherit)?
|
|
1184
|
+
* La web / procesos sin TTY usan proceso oculto (o `external: true` → ventana).
|
|
1185
|
+
* @param {{ external?: boolean }} [opts]
|
|
1186
|
+
*/
|
|
1187
|
+
function canInheritLoginTty(opts = {}) {
|
|
1188
|
+
if (opts.external) return false;
|
|
1189
|
+
return Boolean(process.stdin?.isTTY && process.stdout?.isTTY);
|
|
1190
|
+
}
|
|
1191
|
+
/**
|
|
1192
|
+
* Modo de launch: inherit (CLI con TTY) → hidden (web / OAuth en browser) → external (forzado).
|
|
1193
|
+
* @param {{ wait?: boolean, external?: boolean }} [opts]
|
|
1194
|
+
* @returns {LoginLaunchMode}
|
|
1195
|
+
*/
|
|
1196
|
+
function resolveLoginLaunchMode(opts = {}) {
|
|
1197
|
+
if (Boolean(opts.wait) && canInheritLoginTty(opts)) return "inherit";
|
|
1198
|
+
if (opts.external) return "external";
|
|
1199
|
+
return "hidden";
|
|
1200
|
+
}
|
|
1201
|
+
/**
|
|
1202
|
+
* Arma el comando de login a partir de un authMethod (o fallback por agentId).
|
|
1203
|
+
* @param {{ def: { id: string, acp?: Function }, path: string }} agent
|
|
1204
|
+
* @param {any} [method]
|
|
1205
|
+
* @returns {LoginCommand}
|
|
1206
|
+
*/
|
|
1207
|
+
function resolveLoginCommand(agent, method) {
|
|
1208
|
+
const kind = method ? classifyAuthMethod(method) : "unknown";
|
|
1209
|
+
const meta = method?._meta?.["terminal-auth"];
|
|
1210
|
+
if (agent?.def?.id === "claude" && method && (kind === "terminal" || kind === "terminal-meta")) {
|
|
1211
|
+
const args = claudeCliLoginArgs(method);
|
|
1212
|
+
return {
|
|
1213
|
+
bin: typeof agent.path === "string" && agent.path.trim() ? agent.path : "claude",
|
|
1214
|
+
args,
|
|
1215
|
+
display: `claude ${args.join(" ")}`.trim(),
|
|
1216
|
+
...method?.env && typeof method.env === "object" ? { env: method.env } : {}
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
if ((kind === "terminal-meta" || kind === "terminal") && meta && typeof meta === "object") {
|
|
1220
|
+
const command = typeof meta.command === "string" ? meta.command.trim() : "";
|
|
1221
|
+
if (command) {
|
|
1222
|
+
const args = Array.isArray(meta.args) ? meta.args.map(String) : [];
|
|
1223
|
+
return {
|
|
1224
|
+
bin: command,
|
|
1225
|
+
args,
|
|
1226
|
+
display: (typeof meta.label === "string" && meta.label.trim() ? meta.label.trim() : "") || `${command}${args.length ? ` ${args.join(" ")}` : ""}`.trim(),
|
|
1227
|
+
...method?.env && typeof method.env === "object" ? { env: method.env } : {}
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
if (kind === "terminal" && method && typeof agent?.def?.acp === "function") {
|
|
1232
|
+
const launch = agent.def.acp({ path: agent.path });
|
|
1233
|
+
const extra = Array.isArray(method.args) ? method.args.map(String) : [];
|
|
1234
|
+
const env = {
|
|
1235
|
+
...launch.env && typeof launch.env === "object" ? launch.env : {},
|
|
1236
|
+
...method.env && typeof method.env === "object" ? method.env : {}
|
|
1237
|
+
};
|
|
1238
|
+
const args = [...launch.args || [], ...extra];
|
|
1239
|
+
return {
|
|
1240
|
+
bin: launch.file,
|
|
1241
|
+
args,
|
|
1242
|
+
display: `${launch.file}${args.length ? ` ${args.join(" ")}` : ""}`.trim(),
|
|
1243
|
+
...Object.keys(env).length ? { env } : {}
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
const fallback = loginCommand(agent?.def?.id);
|
|
1247
|
+
if (fallback) {
|
|
1248
|
+
if (agent?.def?.id === "claude" && typeof agent.path === "string" && agent.path.trim()) return {
|
|
1249
|
+
...fallback,
|
|
1250
|
+
bin: agent.path
|
|
1251
|
+
};
|
|
1252
|
+
return fallback;
|
|
1253
|
+
}
|
|
1254
|
+
throw new Error("Este agente no tiene un inicio de sesión conocido.");
|
|
1255
|
+
}
|
|
1256
|
+
/** @param {import('node:child_process').ChildProcess} child */
|
|
1257
|
+
function waitExit(child) {
|
|
1258
|
+
return new Promise((resolve, reject) => {
|
|
1259
|
+
child.once("error", reject);
|
|
1260
|
+
child.once("exit", (code, signal) => resolve(signal ? 1 : code ?? 1));
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
/**
|
|
1264
|
+
* Lanza el login en background (proceso oculto; no espera). Compat con UI/CTA simples (web sin sesión).
|
|
1265
|
+
* @param {string | LoginCommand} agentIdOrCmd
|
|
1126
1266
|
* @returns {{ display: string }}
|
|
1127
1267
|
*/
|
|
1128
|
-
function openLoginTerminal(
|
|
1129
|
-
const cmd = loginCommand(
|
|
1130
|
-
if (!cmd) throw new Error(
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
"
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1268
|
+
function openLoginTerminal(agentIdOrCmd) {
|
|
1269
|
+
const cmd = typeof agentIdOrCmd === "string" ? loginCommand(agentIdOrCmd) : agentIdOrCmd;
|
|
1270
|
+
if (!cmd) throw new Error((typeof agentIdOrCmd === "string" ? agentIdOrCmd : "") === "cursor" ? "Necesitás iniciar sesión en Cursor." : "Este agente no tiene un inicio de sesión conocido.");
|
|
1271
|
+
launchLoginProcess(cmd, {
|
|
1272
|
+
wait: false,
|
|
1273
|
+
mode: "hidden"
|
|
1274
|
+
});
|
|
1275
|
+
return { display: cmd.display };
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Lanza el login. Con TTY y `wait: true`, corre en esta terminal (inherit); si no, proceso oculto
|
|
1279
|
+
* (OAuth en browser). Solo con `external: true` abre una ventana de terminal del OS.
|
|
1280
|
+
* @param {LoginCommand} cmd
|
|
1281
|
+
* @param {{ wait?: boolean, cwd?: string, external?: boolean }} [opts]
|
|
1282
|
+
* @returns {Promise<{ display: string, code: number | null, mode: LoginLaunchMode }>}
|
|
1283
|
+
*/
|
|
1284
|
+
async function runLoginTerminal(cmd, opts = {}) {
|
|
1285
|
+
const wait = Boolean(opts.wait);
|
|
1286
|
+
const mode = resolveLoginLaunchMode(opts);
|
|
1287
|
+
const code = await launchLoginProcess(cmd, {
|
|
1288
|
+
wait,
|
|
1289
|
+
cwd: opts.cwd,
|
|
1290
|
+
mode
|
|
1291
|
+
});
|
|
1292
|
+
return {
|
|
1293
|
+
display: cmd.display,
|
|
1294
|
+
code: wait ? code : null,
|
|
1295
|
+
mode
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
/** @param {string} arg */
|
|
1299
|
+
function quoteBatArg(arg) {
|
|
1300
|
+
const s = String(arg);
|
|
1301
|
+
if (/^[\w./:\\@=-]+$/.test(s)) return s;
|
|
1302
|
+
return `"${s.replace(/"/g, "\"\"")}"`;
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1305
|
+
* Espera a que el sentinel tenga un exit code (macOS/Linux/Windows externo).
|
|
1306
|
+
* @param {string} sentinel
|
|
1307
|
+
* @param {string} display
|
|
1308
|
+
* @returns {Promise<number>}
|
|
1309
|
+
*/
|
|
1310
|
+
async function waitForExitSentinel(sentinel, display) {
|
|
1311
|
+
const deadline = Date.now() + 1800 * 1e3;
|
|
1312
|
+
while (Date.now() < deadline) {
|
|
1313
|
+
try {
|
|
1314
|
+
const raw = readFileSync(sentinel, "utf8").trim();
|
|
1315
|
+
if (raw !== "") {
|
|
1316
|
+
const code = Number.parseInt(raw, 10);
|
|
1317
|
+
try {
|
|
1318
|
+
unlinkSync(sentinel);
|
|
1319
|
+
} catch {}
|
|
1320
|
+
return Number.isFinite(code) ? code : 1;
|
|
1321
|
+
}
|
|
1322
|
+
} catch {}
|
|
1323
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
1324
|
+
}
|
|
1325
|
+
try {
|
|
1326
|
+
unlinkSync(sentinel);
|
|
1327
|
+
} catch {}
|
|
1328
|
+
throw new Error(`Timeout esperando el login (${display}).`);
|
|
1329
|
+
}
|
|
1330
|
+
/**
|
|
1331
|
+
* Proceso hijo sin ventana de terminal (web / OAuth). Exit code real vía waitExit.
|
|
1332
|
+
* @param {LoginCommand} cmd
|
|
1333
|
+
* @param {{ wait: boolean, cwd?: string, env: NodeJS.ProcessEnv }} opts
|
|
1334
|
+
* @returns {Promise<number>}
|
|
1335
|
+
*/
|
|
1336
|
+
async function launchHiddenLogin(cmd, { wait, cwd, env }) {
|
|
1337
|
+
const child = spawnCommand(cmd.bin, cmd.args, {
|
|
1338
|
+
cwd,
|
|
1339
|
+
env,
|
|
1142
1340
|
stdio: "ignore",
|
|
1143
|
-
windowsHide:
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1341
|
+
windowsHide: true,
|
|
1342
|
+
...wait ? {} : { detached: true }
|
|
1343
|
+
});
|
|
1344
|
+
if (!wait) {
|
|
1345
|
+
child.unref();
|
|
1346
|
+
return 0;
|
|
1347
|
+
}
|
|
1348
|
+
return waitExit(child);
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* @param {LoginCommand} cmd
|
|
1352
|
+
* @param {{ wait: boolean, cwd?: string, mode: LoginLaunchMode }} opts
|
|
1353
|
+
* @returns {Promise<number>}
|
|
1354
|
+
*/
|
|
1355
|
+
async function launchLoginProcess(cmd, { wait, cwd, mode }) {
|
|
1356
|
+
const env = {
|
|
1357
|
+
...process.env,
|
|
1358
|
+
...cmd.env || {}
|
|
1359
|
+
};
|
|
1360
|
+
if (mode === "inherit") return waitExit(spawnCommand(cmd.bin, cmd.args, {
|
|
1361
|
+
cwd,
|
|
1362
|
+
env,
|
|
1363
|
+
stdio: "inherit"
|
|
1364
|
+
}));
|
|
1365
|
+
if (mode === "hidden") return launchHiddenLogin(cmd, {
|
|
1366
|
+
wait,
|
|
1367
|
+
cwd,
|
|
1368
|
+
env
|
|
1369
|
+
});
|
|
1370
|
+
const platform = process.platform;
|
|
1371
|
+
if (platform === "win32") {
|
|
1372
|
+
if (!wait) {
|
|
1373
|
+
spawn("cmd.exe", [
|
|
1374
|
+
"/c",
|
|
1375
|
+
"start",
|
|
1376
|
+
"diff-review login",
|
|
1377
|
+
"cmd.exe",
|
|
1378
|
+
"/k",
|
|
1379
|
+
cmd.bin,
|
|
1380
|
+
...cmd.args
|
|
1381
|
+
], {
|
|
1382
|
+
cwd,
|
|
1383
|
+
env,
|
|
1384
|
+
detached: true,
|
|
1385
|
+
stdio: "ignore",
|
|
1386
|
+
windowsHide: false
|
|
1387
|
+
}).unref();
|
|
1388
|
+
return 0;
|
|
1389
|
+
}
|
|
1390
|
+
const id = randomBytes(8).toString("hex");
|
|
1391
|
+
const sentinel = join(tmpdir(), `diff-review-login-${id}.exit`);
|
|
1392
|
+
const bat = join(tmpdir(), `diff-review-login-${id}.bat`);
|
|
1393
|
+
const line = [quoteBatArg(cmd.bin), ...cmd.args.map(quoteBatArg)].join(" ");
|
|
1394
|
+
const sentPath = sentinel.replace(/"/g, "\"\"");
|
|
1395
|
+
writeFileSync(bat, [
|
|
1396
|
+
`@echo off`,
|
|
1397
|
+
`call ${line}`,
|
|
1398
|
+
`> "${sentPath}" echo %ERRORLEVEL%`,
|
|
1399
|
+
``
|
|
1400
|
+
].join("\r\n"));
|
|
1401
|
+
try {
|
|
1402
|
+
writeFileSync(sentinel, "");
|
|
1403
|
+
} catch {}
|
|
1404
|
+
const child = spawn("cmd.exe", [
|
|
1405
|
+
"/c",
|
|
1406
|
+
"start",
|
|
1407
|
+
"/wait",
|
|
1408
|
+
"diff-review login",
|
|
1409
|
+
"cmd.exe",
|
|
1410
|
+
"/c",
|
|
1411
|
+
bat
|
|
1412
|
+
], {
|
|
1413
|
+
cwd,
|
|
1414
|
+
env,
|
|
1415
|
+
stdio: "ignore",
|
|
1416
|
+
windowsHide: false
|
|
1417
|
+
});
|
|
1418
|
+
try {
|
|
1419
|
+
await waitExit(child);
|
|
1420
|
+
return await waitForExitSentinel(sentinel, cmd.display);
|
|
1421
|
+
} finally {
|
|
1422
|
+
try {
|
|
1423
|
+
unlinkSync(bat);
|
|
1424
|
+
} catch {}
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
if (!wait) {
|
|
1428
|
+
if (platform === "darwin") {
|
|
1429
|
+
spawn("osascript", ["-e", `tell application "Terminal" to do script ${JSON.stringify(`${cmd.bin} ${cmd.args.join(" ")}`.trim())}`], {
|
|
1430
|
+
detached: true,
|
|
1431
|
+
stdio: "ignore",
|
|
1432
|
+
env
|
|
1433
|
+
}).unref();
|
|
1434
|
+
return 0;
|
|
1435
|
+
}
|
|
1150
1436
|
const line = `${cmd.bin} ${cmd.args.join(" ")}`.trim();
|
|
1151
1437
|
const tried = [
|
|
1152
1438
|
["x-terminal-emulator", [
|
|
@@ -1174,18 +1460,375 @@ function openLoginTerminal(agentId) {
|
|
|
1174
1460
|
`${line}; exec bash`
|
|
1175
1461
|
]]
|
|
1176
1462
|
];
|
|
1177
|
-
let ok = false;
|
|
1178
1463
|
for (const [bin, args] of tried) try {
|
|
1179
1464
|
spawn(bin, args, {
|
|
1180
1465
|
detached: true,
|
|
1181
|
-
stdio: "ignore"
|
|
1466
|
+
stdio: "ignore",
|
|
1467
|
+
env,
|
|
1468
|
+
cwd
|
|
1182
1469
|
}).unref();
|
|
1470
|
+
return 0;
|
|
1471
|
+
} catch {}
|
|
1472
|
+
throw new Error(`No pude abrir una terminal. Corré a mano: ${cmd.display}`);
|
|
1473
|
+
}
|
|
1474
|
+
const sentinel = join(tmpdir(), `diff-review-login-${randomBytes(8).toString("hex")}.exit`);
|
|
1475
|
+
try {
|
|
1476
|
+
writeFileSync(sentinel, "");
|
|
1477
|
+
} catch {}
|
|
1478
|
+
const line = `${[cmd.bin, ...cmd.args].map((a) => JSON.stringify(a)).join(" ")}; echo $? > ${JSON.stringify(sentinel)}`;
|
|
1479
|
+
if (platform === "darwin") spawn("osascript", ["-e", `tell application "Terminal" to do script ${JSON.stringify(line)}`], {
|
|
1480
|
+
stdio: "ignore",
|
|
1481
|
+
env,
|
|
1482
|
+
cwd
|
|
1483
|
+
});
|
|
1484
|
+
else {
|
|
1485
|
+
const tried = [
|
|
1486
|
+
["gnome-terminal", [
|
|
1487
|
+
"--wait",
|
|
1488
|
+
"--",
|
|
1489
|
+
"bash",
|
|
1490
|
+
"-lc",
|
|
1491
|
+
line
|
|
1492
|
+
]],
|
|
1493
|
+
["x-terminal-emulator", [
|
|
1494
|
+
"-e",
|
|
1495
|
+
"bash",
|
|
1496
|
+
"-lc",
|
|
1497
|
+
line
|
|
1498
|
+
]],
|
|
1499
|
+
["konsole", [
|
|
1500
|
+
"-e",
|
|
1501
|
+
"bash",
|
|
1502
|
+
"-lc",
|
|
1503
|
+
line
|
|
1504
|
+
]],
|
|
1505
|
+
["xterm", [
|
|
1506
|
+
"-e",
|
|
1507
|
+
"bash",
|
|
1508
|
+
"-lc",
|
|
1509
|
+
line
|
|
1510
|
+
]]
|
|
1511
|
+
];
|
|
1512
|
+
let ok = false;
|
|
1513
|
+
for (const [bin, args] of tried) try {
|
|
1514
|
+
await waitExit(spawn(bin, args, {
|
|
1515
|
+
stdio: "ignore",
|
|
1516
|
+
env,
|
|
1517
|
+
cwd
|
|
1518
|
+
}));
|
|
1183
1519
|
ok = true;
|
|
1184
1520
|
break;
|
|
1185
1521
|
} catch {}
|
|
1186
1522
|
if (!ok) throw new Error(`No pude abrir una terminal. Corré a mano: ${cmd.display}`);
|
|
1187
1523
|
}
|
|
1188
|
-
return
|
|
1524
|
+
return waitForExitSentinel(sentinel, cmd.display);
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* Cuenta activa del agente (para el header de la web).
|
|
1528
|
+
* @param {string} agentId
|
|
1529
|
+
* @param {{ env?: NodeJS.ProcessEnv, run?: typeof spawnSync }} [opts]
|
|
1530
|
+
* @returns {{
|
|
1531
|
+
* agentId: string,
|
|
1532
|
+
* loggedIn: boolean,
|
|
1533
|
+
* accountLabel: string | null,
|
|
1534
|
+
* detail: string | null,
|
|
1535
|
+
* source: 'gh' | 'claude' | 'env' | 'unknown' | null,
|
|
1536
|
+
* warnings: string[],
|
|
1537
|
+
* }}
|
|
1538
|
+
*/
|
|
1539
|
+
function agentAccountStatus(agentId, opts = {}) {
|
|
1540
|
+
const id = String(agentId || "").trim();
|
|
1541
|
+
const env = opts.env || process.env;
|
|
1542
|
+
const run = opts.run || spawnSync;
|
|
1543
|
+
/** @type {string[]} */
|
|
1544
|
+
const warnings = [];
|
|
1545
|
+
if (id === "copilot") {
|
|
1546
|
+
const envHits = copilotTokenEnvOverrides(env);
|
|
1547
|
+
if (envHits.length) {
|
|
1548
|
+
warnings.push(`${envHits.join(", ")} en el entorno (pisa otras cuentas).`);
|
|
1549
|
+
return {
|
|
1550
|
+
agentId: id,
|
|
1551
|
+
loggedIn: true,
|
|
1552
|
+
accountLabel: "Token de entorno",
|
|
1553
|
+
detail: envHits.join(", "),
|
|
1554
|
+
source: "env",
|
|
1555
|
+
warnings
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
const accounts = listGhAuthAccounts({
|
|
1559
|
+
env,
|
|
1560
|
+
run
|
|
1561
|
+
});
|
|
1562
|
+
const active = accounts.find((a) => a.active) || accounts[0] || null;
|
|
1563
|
+
if (active) return {
|
|
1564
|
+
agentId: id,
|
|
1565
|
+
loggedIn: true,
|
|
1566
|
+
accountLabel: active.user,
|
|
1567
|
+
detail: active.host,
|
|
1568
|
+
source: "gh",
|
|
1569
|
+
warnings
|
|
1570
|
+
};
|
|
1571
|
+
return {
|
|
1572
|
+
agentId: id,
|
|
1573
|
+
loggedIn: false,
|
|
1574
|
+
accountLabel: null,
|
|
1575
|
+
detail: null,
|
|
1576
|
+
source: null,
|
|
1577
|
+
warnings
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
if (id === "claude") {
|
|
1581
|
+
const r = run("claude", [
|
|
1582
|
+
"auth",
|
|
1583
|
+
"status",
|
|
1584
|
+
"--json"
|
|
1585
|
+
], {
|
|
1586
|
+
encoding: "utf8",
|
|
1587
|
+
env,
|
|
1588
|
+
windowsHide: true
|
|
1589
|
+
});
|
|
1590
|
+
if (r.error) return {
|
|
1591
|
+
agentId: id,
|
|
1592
|
+
loggedIn: false,
|
|
1593
|
+
accountLabel: null,
|
|
1594
|
+
detail: null,
|
|
1595
|
+
source: null,
|
|
1596
|
+
warnings: [r.error.message]
|
|
1597
|
+
};
|
|
1598
|
+
try {
|
|
1599
|
+
const parsed = JSON.parse(String(r.stdout || "{}"));
|
|
1600
|
+
const loggedIn = Boolean(parsed?.loggedIn);
|
|
1601
|
+
const email = String(parsed?.email || "").trim() || null;
|
|
1602
|
+
const org = String(parsed?.orgName || "").trim() || null;
|
|
1603
|
+
return {
|
|
1604
|
+
agentId: id,
|
|
1605
|
+
loggedIn,
|
|
1606
|
+
accountLabel: loggedIn ? email || "Claude" : null,
|
|
1607
|
+
detail: loggedIn ? org || String(parsed?.authMethod || "") || null : null,
|
|
1608
|
+
source: loggedIn ? "claude" : null,
|
|
1609
|
+
warnings
|
|
1610
|
+
};
|
|
1611
|
+
} catch {
|
|
1612
|
+
return {
|
|
1613
|
+
agentId: id,
|
|
1614
|
+
loggedIn: false,
|
|
1615
|
+
accountLabel: null,
|
|
1616
|
+
detail: null,
|
|
1617
|
+
source: null,
|
|
1618
|
+
warnings: ["No pude leer el estado de Claude."]
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
if (id === "cursor") return {
|
|
1623
|
+
agentId: id,
|
|
1624
|
+
loggedIn: true,
|
|
1625
|
+
accountLabel: "Cursor",
|
|
1626
|
+
detail: "Sesión del CLI / app",
|
|
1627
|
+
source: "unknown",
|
|
1628
|
+
warnings
|
|
1629
|
+
};
|
|
1630
|
+
return {
|
|
1631
|
+
agentId: id,
|
|
1632
|
+
loggedIn: false,
|
|
1633
|
+
accountLabel: null,
|
|
1634
|
+
detail: null,
|
|
1635
|
+
source: null,
|
|
1636
|
+
warnings
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
/**
|
|
1640
|
+
* Env vars que Copilot usa antes que OAuth / gh (impiden un cambio de cuenta real).
|
|
1641
|
+
* @param {NodeJS.ProcessEnv} [env]
|
|
1642
|
+
* @returns {string[]}
|
|
1643
|
+
*/
|
|
1644
|
+
function copilotTokenEnvOverrides(env = process.env) {
|
|
1645
|
+
/** @type {string[]} */
|
|
1646
|
+
const hit = [];
|
|
1647
|
+
for (const key of [
|
|
1648
|
+
"COPILOT_GITHUB_TOKEN",
|
|
1649
|
+
"GH_TOKEN",
|
|
1650
|
+
"GITHUB_TOKEN"
|
|
1651
|
+
]) if (String(env[key] || "").trim()) hit.push(key);
|
|
1652
|
+
return hit;
|
|
1653
|
+
}
|
|
1654
|
+
/**
|
|
1655
|
+
* Cuentas conocidas por `gh auth status --json hosts`.
|
|
1656
|
+
* @param {{ env?: NodeJS.ProcessEnv, run?: typeof spawnSync }} [opts]
|
|
1657
|
+
* @returns {{ host: string, user: string, active: boolean }[]}
|
|
1658
|
+
*/
|
|
1659
|
+
function listGhAuthAccounts(opts = {}) {
|
|
1660
|
+
const env = opts.env || process.env;
|
|
1661
|
+
const r = (opts.run || spawnSync)("gh", [
|
|
1662
|
+
"auth",
|
|
1663
|
+
"status",
|
|
1664
|
+
"--json",
|
|
1665
|
+
"hosts"
|
|
1666
|
+
], {
|
|
1667
|
+
encoding: "utf8",
|
|
1668
|
+
env,
|
|
1669
|
+
windowsHide: true
|
|
1670
|
+
});
|
|
1671
|
+
if (r.error) return [];
|
|
1672
|
+
try {
|
|
1673
|
+
const parsed = JSON.parse(String(r.stdout || "{}"));
|
|
1674
|
+
const hosts = parsed?.hosts && typeof parsed.hosts === "object" ? parsed.hosts : {};
|
|
1675
|
+
/** @type {{ host: string, user: string, active: boolean }[]} */
|
|
1676
|
+
const out = [];
|
|
1677
|
+
for (const [host, accounts] of Object.entries(hosts)) {
|
|
1678
|
+
const list = Array.isArray(accounts) ? accounts : [];
|
|
1679
|
+
for (const acc of list) {
|
|
1680
|
+
const user = String(acc?.login || acc?.user || "").trim();
|
|
1681
|
+
if (!user) continue;
|
|
1682
|
+
out.push({
|
|
1683
|
+
host: String(host),
|
|
1684
|
+
user,
|
|
1685
|
+
active: Boolean(acc?.active)
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
return out;
|
|
1690
|
+
} catch {
|
|
1691
|
+
return [];
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
/**
|
|
1695
|
+
* Cierra sesiones de `gh` (Copilot las reusa si no hay OAuth propio).
|
|
1696
|
+
* @param {{ env?: NodeJS.ProcessEnv, run?: typeof spawnSync, host?: string | null }} [opts]
|
|
1697
|
+
* @returns {{ ok: boolean, loggedOut: { host: string, user: string }[], warnings: string[] }}
|
|
1698
|
+
*/
|
|
1699
|
+
function logoutGhAuthAccounts(opts = {}) {
|
|
1700
|
+
const env = opts.env || process.env;
|
|
1701
|
+
const run = opts.run || spawnSync;
|
|
1702
|
+
const onlyHost = normalizeCopilotLoginHost(opts.host) || null;
|
|
1703
|
+
const accounts = listGhAuthAccounts({
|
|
1704
|
+
env,
|
|
1705
|
+
run
|
|
1706
|
+
}).filter((a) => onlyHost ? a.host === onlyHost || a.host === `https://${onlyHost}` : true);
|
|
1707
|
+
/** @type {{ host: string, user: string }[]} */
|
|
1708
|
+
const loggedOut = [];
|
|
1709
|
+
/** @type {string[]} */
|
|
1710
|
+
const warnings = [];
|
|
1711
|
+
for (const acc of accounts) {
|
|
1712
|
+
const r = run("gh", [
|
|
1713
|
+
"auth",
|
|
1714
|
+
"logout",
|
|
1715
|
+
"--hostname",
|
|
1716
|
+
acc.host,
|
|
1717
|
+
"--user",
|
|
1718
|
+
acc.user
|
|
1719
|
+
], {
|
|
1720
|
+
encoding: "utf8",
|
|
1721
|
+
env,
|
|
1722
|
+
windowsHide: true
|
|
1723
|
+
});
|
|
1724
|
+
if (r.error) {
|
|
1725
|
+
warnings.push(`No pude cerrar ${acc.user}@${acc.host}: ${r.error.message}`);
|
|
1726
|
+
continue;
|
|
1727
|
+
}
|
|
1728
|
+
if (r.status !== 0) {
|
|
1729
|
+
const err = String(r.stderr || r.stdout || "").trim() || `exit ${r.status}`;
|
|
1730
|
+
warnings.push(`No pude cerrar ${acc.user}@${acc.host}: ${err}`);
|
|
1731
|
+
continue;
|
|
1732
|
+
}
|
|
1733
|
+
loggedOut.push({
|
|
1734
|
+
host: acc.host,
|
|
1735
|
+
user: acc.user
|
|
1736
|
+
});
|
|
1737
|
+
}
|
|
1738
|
+
const envHits = copilotTokenEnvOverrides(env);
|
|
1739
|
+
if (envHits.length) warnings.push(`Hay ${envHits.join(", ")} en el entorno: Copilot seguirá usando ese token hasta que lo saques.`);
|
|
1740
|
+
return {
|
|
1741
|
+
ok: warnings.length === 0 || loggedOut.length > 0,
|
|
1742
|
+
loggedOut,
|
|
1743
|
+
warnings
|
|
1744
|
+
};
|
|
1745
|
+
}
|
|
1746
|
+
/**
|
|
1747
|
+
* Comando de logout CLI por agente (sin depender de gh).
|
|
1748
|
+
* @param {string} agentId
|
|
1749
|
+
* @returns {LoginCommand | null}
|
|
1750
|
+
*/
|
|
1751
|
+
function logoutCommand(agentId) {
|
|
1752
|
+
switch (agentId) {
|
|
1753
|
+
case "claude": return {
|
|
1754
|
+
bin: "claude",
|
|
1755
|
+
args: ["auth", "logout"],
|
|
1756
|
+
display: "claude auth logout"
|
|
1757
|
+
};
|
|
1758
|
+
default: return null;
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
/**
|
|
1762
|
+
* Cierra la sesión del agente para poder elegir otra cuenta.
|
|
1763
|
+
* Copilot: también desloguea cuentas de `gh` (si solo hacés logout OAuth, gh sigue ganando).
|
|
1764
|
+
*
|
|
1765
|
+
* @param {string} agentId
|
|
1766
|
+
* @param {{
|
|
1767
|
+
* host?: string | null,
|
|
1768
|
+
* env?: NodeJS.ProcessEnv,
|
|
1769
|
+
* run?: typeof spawnSync,
|
|
1770
|
+
* runTerminal?: typeof runLoginTerminal,
|
|
1771
|
+
* cwd?: string,
|
|
1772
|
+
* }} [opts]
|
|
1773
|
+
* @returns {Promise<{ ok: boolean, steps: string[], warnings: string[] }>}
|
|
1774
|
+
*/
|
|
1775
|
+
async function runAgentLogout(agentId, opts = {}) {
|
|
1776
|
+
const id = String(agentId || "").trim();
|
|
1777
|
+
/** @type {string[]} */
|
|
1778
|
+
const steps = [];
|
|
1779
|
+
/** @type {string[]} */
|
|
1780
|
+
const warnings = [];
|
|
1781
|
+
const env = opts.env || process.env;
|
|
1782
|
+
const run = opts.run || spawnSync;
|
|
1783
|
+
const runTerm = opts.runTerminal || runLoginTerminal;
|
|
1784
|
+
if (id === "copilot") {
|
|
1785
|
+
const gh = logoutGhAuthAccounts({
|
|
1786
|
+
env,
|
|
1787
|
+
run,
|
|
1788
|
+
host: opts.host
|
|
1789
|
+
});
|
|
1790
|
+
for (const a of gh.loggedOut) steps.push(`Cerré ${a.user} en ${a.host} (GitHub CLI)`);
|
|
1791
|
+
warnings.push(...gh.warnings);
|
|
1792
|
+
if (!gh.loggedOut.length && !gh.warnings.length) steps.push("No había cuentas de GitHub CLI activas.");
|
|
1793
|
+
return {
|
|
1794
|
+
ok: true,
|
|
1795
|
+
steps,
|
|
1796
|
+
warnings
|
|
1797
|
+
};
|
|
1798
|
+
}
|
|
1799
|
+
const cmd = logoutCommand(id);
|
|
1800
|
+
if (!cmd) return {
|
|
1801
|
+
ok: false,
|
|
1802
|
+
steps,
|
|
1803
|
+
warnings: ["Este agente no tiene cierre de sesión desde la app."]
|
|
1804
|
+
};
|
|
1805
|
+
try {
|
|
1806
|
+
const result = await runTerm(cmd, {
|
|
1807
|
+
wait: true,
|
|
1808
|
+
cwd: opts.cwd
|
|
1809
|
+
});
|
|
1810
|
+
if (result.code !== 0) {
|
|
1811
|
+
warnings.push(`El logout terminó con código ${result.code ?? "?"}.`);
|
|
1812
|
+
return {
|
|
1813
|
+
ok: false,
|
|
1814
|
+
steps,
|
|
1815
|
+
warnings
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1818
|
+
steps.push(`Cerré la sesión (${cmd.display}).`);
|
|
1819
|
+
return {
|
|
1820
|
+
ok: true,
|
|
1821
|
+
steps,
|
|
1822
|
+
warnings
|
|
1823
|
+
};
|
|
1824
|
+
} catch (err) {
|
|
1825
|
+
warnings.push(err instanceof Error ? err.message : String(err));
|
|
1826
|
+
return {
|
|
1827
|
+
ok: false,
|
|
1828
|
+
steps,
|
|
1829
|
+
warnings
|
|
1830
|
+
};
|
|
1831
|
+
}
|
|
1189
1832
|
}
|
|
1190
1833
|
/** @returns {string} */
|
|
1191
1834
|
function modelsCachePath(home = homedir()) {
|
|
@@ -1261,6 +1904,23 @@ function writeModelsCache(agentId, data, opts = {}) {
|
|
|
1261
1904
|
* review y después para las preguntas y los cambios que se le pidan desde la web. Cada pedido de permiso pasa por
|
|
1262
1905
|
* `decide` (ver `acp-policy.mjs`): no hay nadie mirando una terminal para aprobar.
|
|
1263
1906
|
*/
|
|
1907
|
+
/**
|
|
1908
|
+
* session/new (u otra op) requiere login interactivo (terminal / terminal-meta).
|
|
1909
|
+
* No es un fallo genérico: la UI debe abrir el login y reintentar.
|
|
1910
|
+
*/
|
|
1911
|
+
var AcpAuthRequiredError = class extends Error {
|
|
1912
|
+
/**
|
|
1913
|
+
* @param {string} message
|
|
1914
|
+
* @param {{ methods?: any[], agentId?: string, agentLabel?: string }} [info]
|
|
1915
|
+
*/
|
|
1916
|
+
constructor(message, info = {}) {
|
|
1917
|
+
super(message);
|
|
1918
|
+
this.name = "AcpAuthRequiredError";
|
|
1919
|
+
this.methods = Array.isArray(info.methods) ? info.methods : [];
|
|
1920
|
+
this.agentId = info.agentId || "";
|
|
1921
|
+
this.agentLabel = info.agentLabel || "";
|
|
1922
|
+
}
|
|
1923
|
+
};
|
|
1264
1924
|
/** El agente tiene un modo ACP declarado (no todos los agentes lo traen). */
|
|
1265
1925
|
function supportsAcp(agent) {
|
|
1266
1926
|
return typeof agent?.def?.acp === "function";
|
|
@@ -1291,7 +1951,7 @@ var ACP_START_TIMEOUT_MS = 9e4;
|
|
|
1291
1951
|
* @returns {Promise<AcpSession>}
|
|
1292
1952
|
*/
|
|
1293
1953
|
async function startAcpSession(agent, opts) {
|
|
1294
|
-
if (!supportsAcp(agent)) throw new Error(`${agent.def.label} no
|
|
1954
|
+
if (!supportsAcp(agent)) throw new Error(`${agent.def.label} no se puede usar desde la web.`);
|
|
1295
1955
|
const { file, args, env, sessionMeta } = agent.def.acp({ path: agent.path });
|
|
1296
1956
|
const child = spawnCommand(file, args, {
|
|
1297
1957
|
cwd: opts.cwd,
|
|
@@ -1369,16 +2029,21 @@ async function startAcpSession(agent, opts) {
|
|
|
1369
2029
|
readTextFile: false,
|
|
1370
2030
|
writeTextFile: false
|
|
1371
2031
|
},
|
|
1372
|
-
terminal: false
|
|
2032
|
+
terminal: false,
|
|
2033
|
+
auth: { terminal: true },
|
|
2034
|
+
_meta: { "terminal-auth": true }
|
|
1373
2035
|
},
|
|
1374
2036
|
clientInfo: {
|
|
1375
2037
|
name: "diff-review",
|
|
1376
2038
|
version: "0"
|
|
1377
2039
|
}
|
|
1378
2040
|
});
|
|
1379
|
-
|
|
1380
|
-
const
|
|
1381
|
-
/**
|
|
2041
|
+
const authMethods = Array.isArray(init.authMethods) ? init.authMethods : [];
|
|
2042
|
+
const grouped = groupAuthMethods(authMethods);
|
|
2043
|
+
/** Solo type agent (p. ej. Cursor `cursor_login`). Nunca terminal ni Copilot `_meta.terminal-auth`. */
|
|
2044
|
+
const agentAuthMethods = grouped.agent;
|
|
2045
|
+
const interactiveAuthMethods = [...grouped.terminal, ...grouped.terminalMeta];
|
|
2046
|
+
/** Si session/new falla por auth, pedimos `authenticate` una vez (métodos agent) y reintentamos. */
|
|
1382
2047
|
const tryAuthenticate = async () => {
|
|
1383
2048
|
for (const method of agentAuthMethods) try {
|
|
1384
2049
|
await conn.agent.request(acp.methods.agent.authenticate, { methodId: method.id });
|
|
@@ -1386,6 +2051,14 @@ async function startAcpSession(agent, opts) {
|
|
|
1386
2051
|
} catch {}
|
|
1387
2052
|
return false;
|
|
1388
2053
|
};
|
|
2054
|
+
const authRequired = (msg) => {
|
|
2055
|
+
const label = agent.def.label;
|
|
2056
|
+
return new AcpAuthRequiredError(authFailureMessage$1(agent.def.id, label, msg) || `Necesitás iniciar sesión en ${label}.`, {
|
|
2057
|
+
methods: interactiveAuthMethods.length ? interactiveAuthMethods : authMethods,
|
|
2058
|
+
agentId: agent.def.id,
|
|
2059
|
+
agentLabel: label
|
|
2060
|
+
});
|
|
2061
|
+
};
|
|
1389
2062
|
const openNewSession = async () => {
|
|
1390
2063
|
try {
|
|
1391
2064
|
return await conn.agent.request(acp.methods.agent.session.new, {
|
|
@@ -1395,11 +2068,13 @@ async function startAcpSession(agent, opts) {
|
|
|
1395
2068
|
});
|
|
1396
2069
|
} catch (err) {
|
|
1397
2070
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1398
|
-
if (
|
|
2071
|
+
if (!looksLikeAuthFailure(msg)) throw err;
|
|
2072
|
+
if (agentAuthMethods.length && await tryAuthenticate()) return await conn.agent.request(acp.methods.agent.session.new, {
|
|
1399
2073
|
cwd: opts.cwd,
|
|
1400
2074
|
mcpServers: [],
|
|
1401
2075
|
...sessionMeta ? { _meta: sessionMeta } : {}
|
|
1402
2076
|
});
|
|
2077
|
+
if (interactiveAuthMethods.length || agentAuthMethods.length || looksLikeAuthFailure(msg)) throw authRequired(msg);
|
|
1403
2078
|
throw err;
|
|
1404
2079
|
}
|
|
1405
2080
|
};
|
|
@@ -1431,7 +2106,7 @@ async function startAcpSession(agent, opts) {
|
|
|
1431
2106
|
if (!wanted || wanted === currentModel) return { currentModel };
|
|
1432
2107
|
if (!models) return {
|
|
1433
2108
|
currentModel,
|
|
1434
|
-
warning: `El agente no deja elegir el modelo
|
|
2109
|
+
warning: `El agente no deja elegir el modelo; usa el suyo.`
|
|
1435
2110
|
};
|
|
1436
2111
|
if (!models.options.some((o) => o.value === wanted)) return {
|
|
1437
2112
|
currentModel,
|
|
@@ -1451,6 +2126,7 @@ async function startAcpSession(agent, opts) {
|
|
|
1451
2126
|
const applied = await setModel(initialWanted);
|
|
1452
2127
|
if (applied.warning) modelWarning = applied.warning;
|
|
1453
2128
|
}
|
|
2129
|
+
const supportsLogout = Boolean(init.agentCapabilities?.auth?.logout);
|
|
1454
2130
|
clearTimeout(timer);
|
|
1455
2131
|
return {
|
|
1456
2132
|
sessionId,
|
|
@@ -1459,6 +2135,7 @@ async function startAcpSession(agent, opts) {
|
|
|
1459
2135
|
get currentModel() {
|
|
1460
2136
|
return currentModel;
|
|
1461
2137
|
},
|
|
2138
|
+
supportsLogout,
|
|
1462
2139
|
...modelWarning ? { modelWarning } : {},
|
|
1463
2140
|
setModel,
|
|
1464
2141
|
prompt: async (text) => {
|
|
@@ -1472,6 +2149,11 @@ async function startAcpSession(agent, opts) {
|
|
|
1472
2149
|
return { stopReason: String(res.stopReason) };
|
|
1473
2150
|
},
|
|
1474
2151
|
cancel: () => conn.agent.notify(acp.methods.agent.session.cancel, { sessionId }),
|
|
2152
|
+
/** ACP `logout` si el agente lo anunció; no garantiza nada sobre credenciales de `gh`. */
|
|
2153
|
+
logout: async () => {
|
|
2154
|
+
if (!supportsLogout) throw new Error(`${agent.def.label} no admite cerrar sesión desde acá.`);
|
|
2155
|
+
await conn.agent.request(acp.methods.agent.logout, {});
|
|
2156
|
+
},
|
|
1475
2157
|
close,
|
|
1476
2158
|
closed: conn.closed,
|
|
1477
2159
|
stderrTail: () => stderr
|
|
@@ -1479,13 +2161,24 @@ async function startAcpSession(agent, opts) {
|
|
|
1479
2161
|
} catch (err) {
|
|
1480
2162
|
clearTimeout(timer);
|
|
1481
2163
|
close();
|
|
2164
|
+
if (err instanceof AcpAuthRequiredError) throw err;
|
|
1482
2165
|
const tail = stderr.trim().split(/\r?\n/).slice(-8).join("\n");
|
|
1483
2166
|
if (timedOut) {
|
|
1484
2167
|
const msg = `Timeout (${Math.round(timeoutMs / 1e3)}s) al lanzar ${agent.def.label}. ¿Está autenticado el CLI?`;
|
|
1485
|
-
|
|
2168
|
+
const full = tail ? `${msg}\n${tail}` : msg;
|
|
2169
|
+
if (looksLikeAuthFailure(full)) throw new AcpAuthRequiredError(authFailureMessage$1(agent.def.id, agent.def.label, full) || full, {
|
|
2170
|
+
agentId: agent.def.id,
|
|
2171
|
+
agentLabel: agent.def.label
|
|
2172
|
+
});
|
|
2173
|
+
throw new Error(full);
|
|
1486
2174
|
}
|
|
1487
2175
|
const reason = err instanceof Error ? err.message : String(err);
|
|
1488
|
-
|
|
2176
|
+
const full = tail ? `${reason}\n${tail}` : reason;
|
|
2177
|
+
if (looksLikeAuthFailure(full)) throw new AcpAuthRequiredError(authFailureMessage$1(agent.def.id, agent.def.label, full) || full, {
|
|
2178
|
+
agentId: agent.def.id,
|
|
2179
|
+
agentLabel: agent.def.label
|
|
2180
|
+
});
|
|
2181
|
+
throw new Error(full);
|
|
1489
2182
|
}
|
|
1490
2183
|
}
|
|
1491
2184
|
/**
|
|
@@ -1701,12 +2394,15 @@ function formatCheckReport(file, command, result) {
|
|
|
1701
2394
|
/**
|
|
1702
2395
|
* `agentSession` = la sesión ACP que generó la review, para seguir conversando con el agente desde la web.
|
|
1703
2396
|
* @param {string} filePath
|
|
1704
|
-
*
|
|
2397
|
+
* `modelLabel` = nombre legible que el agente da al modelo ("Haiku 5.5"): `model` puede ser un alias
|
|
2398
|
+
* (`haiku`) que más adelante apunte a otra versión.
|
|
2399
|
+
* @param {{ agent?: string, model?: string, modelLabel?: string, agentSession?: { agentId: string, sessionId: string, outputFilename: string } }} provenance
|
|
1705
2400
|
* @returns {boolean}
|
|
1706
2401
|
*/
|
|
1707
|
-
function stampReviewProvenance(filePath, { agent, model, agentSession } = {}) {
|
|
2402
|
+
function stampReviewProvenance(filePath, { agent, model, modelLabel, agentSession } = {}) {
|
|
1708
2403
|
const agentName = String(agent || "").trim();
|
|
1709
2404
|
const modelId = String(model || "").trim();
|
|
2405
|
+
const label = String(modelLabel || "").trim();
|
|
1710
2406
|
if (!agentName && !modelId && !agentSession) return false;
|
|
1711
2407
|
let payload;
|
|
1712
2408
|
try {
|
|
@@ -1718,7 +2414,11 @@ function stampReviewProvenance(filePath, { agent, model, agentSession } = {}) {
|
|
|
1718
2414
|
const current = payload.meta;
|
|
1719
2415
|
const meta = current && typeof current === "object" && !Array.isArray(current) ? { ...current } : {};
|
|
1720
2416
|
if (agentName) meta.agent = agentName;
|
|
1721
|
-
if (modelId)
|
|
2417
|
+
if (modelId) {
|
|
2418
|
+
meta.model = modelId;
|
|
2419
|
+
if (label && label !== modelId) meta.modelLabel = label;
|
|
2420
|
+
else delete meta.modelLabel;
|
|
2421
|
+
}
|
|
1722
2422
|
if (agentSession) meta.agentSession = { ...agentSession };
|
|
1723
2423
|
payload.meta = meta;
|
|
1724
2424
|
writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
@@ -1747,7 +2447,7 @@ var OUTPUT_SCHEMA_BLOCK = `{
|
|
|
1747
2447
|
"id": "t1",
|
|
1748
2448
|
"kind": "feat|fix|refactor|perf|test|chore|docs|infra",
|
|
1749
2449
|
"title": "string, aim for ≤72 chars, imperative, no kind prefix",
|
|
1750
|
-
"intent": "string"
|
|
2450
|
+
"intent": "string: why this theme exists + what it changes for the reader (not a restatement of the title)"
|
|
1751
2451
|
}
|
|
1752
2452
|
],
|
|
1753
2453
|
"blocks": [
|
|
@@ -1760,8 +2460,8 @@ var OUTPUT_SCHEMA_BLOCK = `{
|
|
|
1760
2460
|
"start": 40,
|
|
1761
2461
|
"end": 58,
|
|
1762
2462
|
"op": "add|mod|del|rename",
|
|
1763
|
-
"what": "string
|
|
1764
|
-
"why": "string
|
|
2463
|
+
"what": "string: concrete change + effect (aim ≤120 chars; longer OK if needed for clarity)",
|
|
2464
|
+
"why": "string: motivation / why this change belongs in the branch (aim ≤120 chars)",
|
|
1765
2465
|
"source": "code|commit|pr|issue|inferred"
|
|
1766
2466
|
}
|
|
1767
2467
|
],
|
|
@@ -1775,8 +2475,8 @@ var OUTPUT_SCHEMA_BLOCK = `{
|
|
|
1775
2475
|
"file": "relative/path.ext",
|
|
1776
2476
|
"line": 87,
|
|
1777
2477
|
"block": "b1",
|
|
1778
|
-
"what": "string
|
|
1779
|
-
"fix": "string
|
|
2478
|
+
"what": "string: cause + why it is a problem + observable consequence (aim ≤120 chars; prefer clarity over brevity)",
|
|
2479
|
+
"fix": "string: suggested change + why that change resolves the problem / what it avoids (aim ≤120 chars)"
|
|
1780
2480
|
}
|
|
1781
2481
|
],
|
|
1782
2482
|
"skipped": [
|
|
@@ -1790,13 +2490,15 @@ var RULES = `- Ids: themes use \`t1\`, \`t2\`, …; findings use \`f1\`, \`f2\`,
|
|
|
1790
2490
|
- Line anchors: "lines"/"start"/"end"/"line" MUST match the chosen "side" ("new" = post-image / right side of @@; "old" = pre-image, typically deletions). One block per coherent change span, not one per line. Point findings at that block; leave "block" empty only if cross-cutting.
|
|
1791
2491
|
- Do NOT report: pre-existing code outside the diff; linter/formatter noise; naming/style prefs with no real consequence; intentional branch behavior; silenced lint rules; "missing docs/coverage" without a concrete broken scenario.
|
|
1792
2492
|
- Quality findings must name a concrete cost, not a vague feeling.
|
|
1793
|
-
-
|
|
1794
|
-
-
|
|
2493
|
+
- Theme "intent": explain why the theme exists and what it changes for the reader — not a paraphrase of "title". One short sentence is too little; aim for 1–3 sentences with cause and scope.
|
|
2494
|
+
- Top-level "intent": 2–4 sentences on what the branch solves and why it matters.
|
|
2495
|
+
- Finding "what": cover (1) the concrete cause in the diff, (2) why it is a problem (failure mode, risk, or cost), and (3) the observable consequence. Do not stop at a symptom label.
|
|
2496
|
+
- Finding "fix": cover (1) the suggested change and (2) why that change resolves the problem / what the reader gains or avoids. Vague advice ("mejorar el manejo") is not enough.
|
|
1795
2497
|
- Signal over volume: prefer fewer high-signal findings. Order findings by severity (high, then nit). Use "nit" sparingly.
|
|
1796
2498
|
- "blocking": true ONLY if the branch should not merge as-is. Use only with class "risk".
|
|
1797
|
-
-
|
|
2499
|
+
- Parallelize: when several read-only lookups are independent (Read, Grep/search, Glob, etc.), issue them in the same turn if your tools allow parallel calls. Do not serialize independent work — finish faster by batching.
|
|
1798
2500
|
- Write human-readable strings (intent, title, what, why, fix, notes) in Spanish. Keep enums/ids/paths/JSON keys exactly as in the schema.
|
|
1799
|
-
- Length is a soft target, not a check: aim for title ≤72 characters and what/why/fix ≤120 characters. Prefer clarity over shortening. Longer text is still valid. Do not rewrite the file only to shorten it.
|
|
2501
|
+
- Length is a soft target, not a check: aim for title ≤72 characters and what/why/fix ≤120 characters when possible. Prefer clarity over shortening — thin one-liners that omit cause, impact, or why-the-fix-works are worse than a slightly longer explanation. Longer text is still valid. Do not rewrite the file only to shorten it.
|
|
1800
2502
|
- If the output JSON already exists and findings have \`resolution\` ({ kind: ignored|fix|corrected, detail?: string }), preserve those fields on rewrite (coverage / edit / regenerate). Keep existing finding ids when possible; do not drop or reshuffle resolutions.`;
|
|
1801
2503
|
/** @param {{ hasDiff?: boolean }} [ctx] */
|
|
1802
2504
|
function workflowLocal(base, branch, ctx = {}) {
|
|
@@ -1946,7 +2648,7 @@ Write the result to \`${outputPath}\` (relative to the repo root; use forward sl
|
|
|
1946
2648
|
|
|
1947
2649
|
${outputSchemaBlock(meta)}
|
|
1948
2650
|
|
|
1949
|
-
Include "meta" with the source/repo/branch/base above (and origin "remoteUrl" when local). "agent" and "model" are required: set "agent" to the product you are (Claude Code, Cursor,
|
|
2651
|
+
Include "meta" with the source/repo/branch/base above (and origin "remoteUrl" when local). "agent" and "model" are required: set "agent" to the product you are (Claude Code, Cursor, Copilot, …) and "model" to the exact model id you are running. Do not leave placeholders or omit them; validation fails without both. "generatedAt" is optional (ISO-8601 now).
|
|
1950
2652
|
|
|
1951
2653
|
Do NOT include diffs or a "files" array. The tool loads the companion \`.diff\` (or computes with git if missing) when opening the report. Do not invent hunks or paste patches into the JSON. If you are rewriting an existing review JSON, copy through any per-finding \`resolution\` already present.
|
|
1952
2654
|
|
|
@@ -2114,7 +2816,7 @@ function agentModels(agentId, cwd, opts) {
|
|
|
2114
2816
|
function findAgent(agentId) {
|
|
2115
2817
|
const agent = detectAgents().find((a) => a.def.id === agentId);
|
|
2116
2818
|
if (!agent) throw new Error(`No encontré el agente "${agentId}" instalado.`);
|
|
2117
|
-
if (!supportsAcp(agent)) throw new Error(`${agent.def.label} no
|
|
2819
|
+
if (!supportsAcp(agent)) throw new Error(`${agent.def.label} no se puede usar desde la web.`);
|
|
2118
2820
|
return agent;
|
|
2119
2821
|
}
|
|
2120
2822
|
function info(entry) {
|
|
@@ -2138,9 +2840,137 @@ function info(entry) {
|
|
|
2138
2840
|
function authFailureMessage(agentId, agentLabel, detail) {
|
|
2139
2841
|
return authFailureMessage$1(agentId, agentLabel, detail);
|
|
2140
2842
|
}
|
|
2141
|
-
/**
|
|
2142
|
-
|
|
2143
|
-
|
|
2843
|
+
/**
|
|
2844
|
+
* Abre el login del agente.
|
|
2845
|
+
* - Con `sessionId`: comando según authMethods ACP (proceso oculto sin TTY), espera exit 0, reconecta y retoma.
|
|
2846
|
+
* - Solo `agentId`: lanza el comando fallback en background (p. ej. fallo al listar modelos).
|
|
2847
|
+
*/
|
|
2848
|
+
async function openAgentLogin(input) {
|
|
2849
|
+
const sessionId = String(input?.sessionId || "").trim();
|
|
2850
|
+
const agentId = String(input?.agentId || "").trim();
|
|
2851
|
+
const host = input?.host != null ? String(input.host) : void 0;
|
|
2852
|
+
if (sessionId) {
|
|
2853
|
+
const entry = sessions.get(sessionId);
|
|
2854
|
+
if (!entry) throw new Error("La sesión del agente ya no existe. Recargá la página.");
|
|
2855
|
+
const method = pickLoginAuthMethod(entry.pendingAuthMethods || []);
|
|
2856
|
+
let cmd;
|
|
2857
|
+
try {
|
|
2858
|
+
cmd = resolveLoginCommand(entry.agent, method);
|
|
2859
|
+
} catch {
|
|
2860
|
+
cmd = loginCommand(entry.agentId, { host });
|
|
2861
|
+
if (!cmd) throw new Error("Este agente no tiene un inicio de sesión conocido.");
|
|
2862
|
+
}
|
|
2863
|
+
if (entry.agentId === "copilot" || agentId === "copilot") cmd = withCopilotLoginHost(cmd, host);
|
|
2864
|
+
setStatus(entry, "starting", loginWaitingMessage());
|
|
2865
|
+
entry.busy = true;
|
|
2866
|
+
try {
|
|
2867
|
+
const result = await runLoginTerminal(cmd, {
|
|
2868
|
+
wait: true,
|
|
2869
|
+
cwd: entry.repo
|
|
2870
|
+
});
|
|
2871
|
+
if (result.code !== 0) {
|
|
2872
|
+
entry.busy = false;
|
|
2873
|
+
const msg = loginIncompleteMessage();
|
|
2874
|
+
setStatus(entry, "needs_auth", msg);
|
|
2875
|
+
throw new Error(msg);
|
|
2876
|
+
}
|
|
2877
|
+
try {
|
|
2878
|
+
entry.acp?.close();
|
|
2879
|
+
} catch {}
|
|
2880
|
+
entry.acp = null;
|
|
2881
|
+
entry.pendingAuthMethods = null;
|
|
2882
|
+
const resume = entry.authResume || "chat";
|
|
2883
|
+
entry.authResume = null;
|
|
2884
|
+
entry.busy = false;
|
|
2885
|
+
if (resume === "review") {
|
|
2886
|
+
setStatus(entry, "starting", "Login ok. Reintentando la review…");
|
|
2887
|
+
continueReviewAfterDiff(entry, {
|
|
2888
|
+
repo: entry.repo,
|
|
2889
|
+
branch: entry.branch,
|
|
2890
|
+
base: entry.base,
|
|
2891
|
+
outputFilename: entry.outputFilename,
|
|
2892
|
+
model: entry.model || void 0
|
|
2893
|
+
});
|
|
2894
|
+
} else {
|
|
2895
|
+
setStatus(entry, "starting", "Login ok. Reconectando con el agente…");
|
|
2896
|
+
try {
|
|
2897
|
+
await ensureAcp(entry, entry.model || void 0);
|
|
2898
|
+
setStatus(entry, "idle");
|
|
2899
|
+
} catch (err) {
|
|
2900
|
+
entry.busy = false;
|
|
2901
|
+
if (isAuthError(err)) markNeedsAuth(entry, err, "chat");
|
|
2902
|
+
else {
|
|
2903
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
2904
|
+
setStatus(entry, "error", authFailureMessage(entry.agentId, entry.agentLabel, raw) || raw);
|
|
2905
|
+
}
|
|
2906
|
+
throw err;
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
return {
|
|
2910
|
+
display: result.display,
|
|
2911
|
+
resumed: true,
|
|
2912
|
+
code: result.code
|
|
2913
|
+
};
|
|
2914
|
+
} catch (err) {
|
|
2915
|
+
entry.busy = false;
|
|
2916
|
+
if (entry.status !== "needs_auth" && entry.status !== "error" && entry.status !== "starting") setStatus(entry, "needs_auth", err instanceof Error ? err.message : String(err));
|
|
2917
|
+
throw err;
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
if (!agentId) throw new Error("Falta agentId o sessionId.");
|
|
2921
|
+
const cmd = loginCommand(agentId, { host });
|
|
2922
|
+
if (!cmd) throw new Error("Este agente no tiene un inicio de sesión conocido.");
|
|
2923
|
+
return openLoginTerminal(cmd);
|
|
2924
|
+
}
|
|
2925
|
+
/**
|
|
2926
|
+
* Cierra la sesión del agente (y cuentas `gh` si es Copilot) para poder elegir otra cuenta.
|
|
2927
|
+
* Deja la entry en `needs_auth` lista para un nuevo login.
|
|
2928
|
+
*/
|
|
2929
|
+
async function openAgentLogout(input) {
|
|
2930
|
+
const sessionId = String(input?.sessionId || "").trim();
|
|
2931
|
+
const host = input?.host != null ? String(input.host) : void 0;
|
|
2932
|
+
let agentId = String(input?.agentId || "").trim();
|
|
2933
|
+
/** @type {Entry | undefined} */
|
|
2934
|
+
let entry;
|
|
2935
|
+
if (sessionId) {
|
|
2936
|
+
entry = sessions.get(sessionId);
|
|
2937
|
+
if (!entry) throw new Error("La sesión del agente ya no existe. Recargá la página.");
|
|
2938
|
+
agentId = entry.agentId || agentId;
|
|
2939
|
+
}
|
|
2940
|
+
if (!agentId) throw new Error("Falta agentId o sessionId.");
|
|
2941
|
+
/** @type {string[]} */
|
|
2942
|
+
const steps = [];
|
|
2943
|
+
/** @type {string[]} */
|
|
2944
|
+
const warnings = [];
|
|
2945
|
+
if (entry?.acp?.supportsLogout) try {
|
|
2946
|
+
await entry.acp.logout();
|
|
2947
|
+
steps.push("Cerré la sesión del agente.");
|
|
2948
|
+
} catch (err) {
|
|
2949
|
+
warnings.push(err instanceof Error ? err.message : String(err));
|
|
2950
|
+
}
|
|
2951
|
+
try {
|
|
2952
|
+
entry?.acp?.close();
|
|
2953
|
+
} catch {}
|
|
2954
|
+
if (entry) {
|
|
2955
|
+
entry.acp = null;
|
|
2956
|
+
entry.starting = null;
|
|
2957
|
+
entry.busy = false;
|
|
2958
|
+
entry.pendingAuthMethods = null;
|
|
2959
|
+
entry.authResume = entry.authResume || (entry.seed || entry.diffText ? "review" : "chat");
|
|
2960
|
+
}
|
|
2961
|
+
const cli = await runAgentLogout(agentId, {
|
|
2962
|
+
host,
|
|
2963
|
+
cwd: entry?.repo
|
|
2964
|
+
});
|
|
2965
|
+
steps.push(...cli.steps);
|
|
2966
|
+
warnings.push(...cli.warnings);
|
|
2967
|
+
const msg = warnings.length && !cli.ok ? warnings[0] : "Sesión cerrada. Elegí con qué cuenta iniciar de nuevo.";
|
|
2968
|
+
if (entry) setStatus(entry, "needs_auth", msg);
|
|
2969
|
+
return {
|
|
2970
|
+
ok: cli.ok || steps.length > 0,
|
|
2971
|
+
steps,
|
|
2972
|
+
warnings
|
|
2973
|
+
};
|
|
2144
2974
|
}
|
|
2145
2975
|
/** Escribe seed (JSON + .diff) en `.diff-review/` para que el agente los lea. */
|
|
2146
2976
|
function materializeSeed(entry) {
|
|
@@ -2291,14 +3121,36 @@ function emit(entry, event) {
|
|
|
2291
3121
|
}
|
|
2292
3122
|
function setStatus(entry, status, message) {
|
|
2293
3123
|
entry.status = status;
|
|
2294
|
-
if (status === "error") entry.statusMessage = message || entry.statusMessage || "Error del agente.";
|
|
3124
|
+
if (status === "error" || status === "needs_auth") entry.statusMessage = message || entry.statusMessage || "Error del agente.";
|
|
2295
3125
|
else if (message !== void 0) delete entry.statusMessage;
|
|
2296
3126
|
emit(entry, {
|
|
2297
3127
|
type: "status",
|
|
2298
3128
|
status,
|
|
2299
|
-
...message || status === "error" ? { message: message || entry.statusMessage } : {}
|
|
3129
|
+
...message || status === "error" || status === "needs_auth" ? { message: message || entry.statusMessage } : {}
|
|
2300
3130
|
});
|
|
2301
3131
|
}
|
|
3132
|
+
function markNeedsAuth(entry, err, resume) {
|
|
3133
|
+
const authErr = err instanceof AcpAuthRequiredError ? err : null;
|
|
3134
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
3135
|
+
const msg = authErr?.message || authFailureMessage(entry.agentId, entry.agentLabel, raw) || raw;
|
|
3136
|
+
entry.pendingAuthMethods = authErr?.methods?.length ? authErr.methods : entry.pendingAuthMethods;
|
|
3137
|
+
entry.authResume = resume;
|
|
3138
|
+
entry.acp = null;
|
|
3139
|
+
setStatus(entry, "needs_auth", msg);
|
|
3140
|
+
}
|
|
3141
|
+
function isAuthError(err) {
|
|
3142
|
+
if (err instanceof AcpAuthRequiredError) return true;
|
|
3143
|
+
return looksLikeAuthFailure(err instanceof Error ? err.message : String(err));
|
|
3144
|
+
}
|
|
3145
|
+
/** Sin sesión CLI y con login desde la app (Claude / Copilot / Cursor). */
|
|
3146
|
+
function needsInteractiveLogin(agentId) {
|
|
3147
|
+
if (!loginCommand(agentId)) return false;
|
|
3148
|
+
try {
|
|
3149
|
+
return !agentAccountStatus(agentId).loggedIn;
|
|
3150
|
+
} catch {
|
|
3151
|
+
return false;
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
2302
3154
|
/** SessionUpdate de ACP → evento de la web (lo que no se muestra se descarta). */
|
|
2303
3155
|
function toEvent(update) {
|
|
2304
3156
|
switch (update.sessionUpdate) {
|
|
@@ -2382,13 +3234,13 @@ async function ensureAcp(entry, model) {
|
|
|
2382
3234
|
entry.busy = false;
|
|
2383
3235
|
const auth = authFailureMessage(entry.agentId, entry.agentLabel, stderr);
|
|
2384
3236
|
if (auth) {
|
|
2385
|
-
|
|
3237
|
+
markNeedsAuth(entry, new Error(auth), entry.mode === "review" ? "review" : "chat");
|
|
2386
3238
|
return;
|
|
2387
3239
|
}
|
|
2388
3240
|
const raw = stderr.trim() || `código ${code ?? "?"}`;
|
|
2389
3241
|
const authFromCode = authFailureMessage(entry.agentId, entry.agentLabel, raw);
|
|
2390
3242
|
if (authFromCode) {
|
|
2391
|
-
|
|
3243
|
+
markNeedsAuth(entry, new Error(authFromCode), entry.mode === "review" ? "review" : "chat");
|
|
2392
3244
|
return;
|
|
2393
3245
|
}
|
|
2394
3246
|
const tail = stderr.trim().split(/\r?\n/).slice(-3).join(" · ");
|
|
@@ -2506,6 +3358,7 @@ async function checkOutput(entry, since, retriesLeft) {
|
|
|
2506
3358
|
stampReviewProvenance(path, {
|
|
2507
3359
|
agent: entry.agentLabel,
|
|
2508
3360
|
model: entry.model,
|
|
3361
|
+
modelLabel: entry.acp?.models.find((o) => o.value === entry.model)?.name,
|
|
2509
3362
|
agentSession: {
|
|
2510
3363
|
agentId: entry.agentId,
|
|
2511
3364
|
sessionId: entry.acpSessionId,
|
|
@@ -2573,6 +3426,11 @@ async function runTurn(entry, text, mode, opts) {
|
|
|
2573
3426
|
mode: mode === "review" ? "review" : mode
|
|
2574
3427
|
});
|
|
2575
3428
|
try {
|
|
3429
|
+
if (!entry.acp && needsInteractiveLogin(entry.agentId)) {
|
|
3430
|
+
entry.busy = false;
|
|
3431
|
+
markNeedsAuth(entry, /* @__PURE__ */ new Error("not logged in"), mode === "review" ? "review" : "chat");
|
|
3432
|
+
return;
|
|
3433
|
+
}
|
|
2576
3434
|
if (!entry.acp) setStatus(entry, "starting", `Lanzando ${entry.agentLabel}…`);
|
|
2577
3435
|
const acp = await ensureAcp(entry, opts.model);
|
|
2578
3436
|
if (entry.cancelled) {
|
|
@@ -2604,6 +3462,10 @@ async function runTurn(entry, text, mode, opts) {
|
|
|
2604
3462
|
setStatus(entry, "idle", "Cancelado.");
|
|
2605
3463
|
return;
|
|
2606
3464
|
}
|
|
3465
|
+
if (isAuthError(err)) {
|
|
3466
|
+
markNeedsAuth(entry, err, mode === "review" ? "review" : "chat");
|
|
3467
|
+
return;
|
|
3468
|
+
}
|
|
2607
3469
|
const raw = err instanceof Error ? err.message : String(err);
|
|
2608
3470
|
setStatus(entry, "error", authFailureMessage(entry.agentId, entry.agentLabel, raw) || raw);
|
|
2609
3471
|
} finally {
|
|
@@ -2641,7 +3503,9 @@ function newEntry(agent, fields) {
|
|
|
2641
3503
|
diffPaths: null,
|
|
2642
3504
|
pendingPermissions: /* @__PURE__ */ new Map(),
|
|
2643
3505
|
sessionAllowDenied: false,
|
|
2644
|
-
cancelled: false
|
|
3506
|
+
cancelled: false,
|
|
3507
|
+
pendingAuthMethods: null,
|
|
3508
|
+
authResume: null
|
|
2645
3509
|
};
|
|
2646
3510
|
sessions.set(entry.id, entry);
|
|
2647
3511
|
return entry;
|
|
@@ -2746,13 +3610,20 @@ async function openChat(input) {
|
|
|
2746
3610
|
if (handoffNote) entry.handoffNote = handoffNote;
|
|
2747
3611
|
if (handoffChat != null) entry.handoffChatMarkdown = handoffChat;
|
|
2748
3612
|
if (!sessionId) entry.needsContext = true;
|
|
3613
|
+
if (needsInteractiveLogin(entry.agentId)) {
|
|
3614
|
+
markNeedsAuth(entry, /* @__PURE__ */ new Error("not logged in"), "chat");
|
|
3615
|
+
return info(entry);
|
|
3616
|
+
}
|
|
2749
3617
|
try {
|
|
2750
3618
|
if (!entry.acp) setStatus(entry, "starting", `Lanzando ${entry.agentLabel}…`);
|
|
2751
3619
|
await ensureAcp(entry, input.model);
|
|
2752
3620
|
if (entry.status === "starting") setStatus(entry, "idle");
|
|
2753
3621
|
} catch (err) {
|
|
2754
|
-
|
|
2755
|
-
|
|
3622
|
+
if (isAuthError(err)) markNeedsAuth(entry, err, "chat");
|
|
3623
|
+
else {
|
|
3624
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
3625
|
+
setStatus(entry, "error", authFailureMessage(entry.agentId, entry.agentLabel, raw) || raw);
|
|
3626
|
+
}
|
|
2756
3627
|
}
|
|
2757
3628
|
return info(entry);
|
|
2758
3629
|
}
|
|
@@ -2787,6 +3658,23 @@ function retryReview(id) {
|
|
|
2787
3658
|
const entry = sessions.get(String(id || ""));
|
|
2788
3659
|
if (!entry) throw new Error("La sesión del agente ya no existe. Recargá la página.");
|
|
2789
3660
|
if (entry.busy) throw new Error("El agente todavía está trabajando en el pedido anterior.");
|
|
3661
|
+
if (entry.status === "needs_auth" || entry.status === "error" && looksLikeAuthFailure(entry.statusMessage || "")) {
|
|
3662
|
+
entry.pendingAuthMethods = null;
|
|
3663
|
+
entry.authResume = null;
|
|
3664
|
+
try {
|
|
3665
|
+
entry.acp?.close();
|
|
3666
|
+
} catch {}
|
|
3667
|
+
entry.acp = null;
|
|
3668
|
+
setStatus(entry, "starting", "Reintentando la review…");
|
|
3669
|
+
continueReviewAfterDiff(entry, {
|
|
3670
|
+
repo: entry.repo,
|
|
3671
|
+
branch: entry.branch,
|
|
3672
|
+
base: entry.base,
|
|
3673
|
+
outputFilename: entry.outputFilename,
|
|
3674
|
+
model: entry.model || void 0
|
|
3675
|
+
});
|
|
3676
|
+
return info(entry);
|
|
3677
|
+
}
|
|
2790
3678
|
if (entry.status === "error") throw new Error(entry.statusMessage || "La sesión está en error; iniciá una nueva review.");
|
|
2791
3679
|
const posixRel = outputRelPath(entry.outputFilename).replace(/\\/g, "/");
|
|
2792
3680
|
const existing = checkEntryReport(entry);
|
|
@@ -2945,5 +3833,5 @@ function startPendingAgentRun() {
|
|
|
2945
3833
|
}
|
|
2946
3834
|
}
|
|
2947
3835
|
|
|
2948
|
-
export { agentModels as a, startReview as b, sendMessage as c, setPermissionTrust as d, cancelTurn as e, closeChat as f, retryReview as g,
|
|
2949
|
-
//# sourceMappingURL=agent-sessions-
|
|
3836
|
+
export { agentModels as a, startReview as b, sendMessage as c, setPermissionTrust as d, cancelTurn as e, closeChat as f, retryReview as g, agentAccountStatus as h, openAgentLogin as i, openAgentLogout as j, subscribe as k, listAcpAgents as l, startPendingAgentRun as m, openChat as o, respondPermission as r, sessionInfo as s };
|
|
3837
|
+
//# sourceMappingURL=agent-sessions-BArYLTW6.js.map
|