@vinihcrosa/lumem-os 0.2.0 → 0.3.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/bin/lumem.mjs +15 -3
- package/dist/server/main.mjs +520 -137
- package/dist/web/assets/{codemirror-setup-qkhLO-HV.js → codemirror-setup-CHk_6JnB.js} +1 -1
- package/dist/web/assets/{index-BkIky5Mw.css → index-C5PMQN0Q.css} +1 -1
- package/dist/web/assets/index-Cft1ygtm.js +101 -0
- package/dist/web/index.html +2 -2
- package/drizzle/0013_consumo_por_agente.sql +1 -0
- package/drizzle/meta/0013_snapshot.json +1688 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +1 -1
- package/dist/web/assets/index-CKedNrvw.js +0 -101
package/dist/server/main.mjs
CHANGED
|
@@ -13993,7 +13993,7 @@ var require_redact = __commonJS({
|
|
|
13993
13993
|
} = options;
|
|
13994
13994
|
validatePaths(paths);
|
|
13995
13995
|
const pathStructure = buildPathStructure(paths);
|
|
13996
|
-
return function
|
|
13996
|
+
return function redact2(obj) {
|
|
13997
13997
|
if (strict && (obj === null || typeof obj !== "object")) {
|
|
13998
13998
|
if (obj === null || obj === void 0) {
|
|
13999
13999
|
return serialize ? serialize(obj) : obj;
|
|
@@ -17554,7 +17554,7 @@ var require_pino = __commonJS({
|
|
|
17554
17554
|
const { opts, stream } = normalize5(instance, caller(), ...args);
|
|
17555
17555
|
if (opts.level && typeof opts.level === "string" && DEFAULT_LEVELS[opts.level.toLowerCase()] !== void 0) opts.level = opts.level.toLowerCase();
|
|
17556
17556
|
const {
|
|
17557
|
-
redact,
|
|
17557
|
+
redact: redact2,
|
|
17558
17558
|
crlf,
|
|
17559
17559
|
serializers: serializers2,
|
|
17560
17560
|
timestamp,
|
|
@@ -17588,8 +17588,8 @@ var require_pino = __commonJS({
|
|
|
17588
17588
|
const stringifyFn = stringify2.bind({
|
|
17589
17589
|
[stringifySafeSym]: stringifySafe
|
|
17590
17590
|
});
|
|
17591
|
-
const stringifiers =
|
|
17592
|
-
const formatOpts =
|
|
17591
|
+
const stringifiers = redact2 ? redaction(redact2, stringifyFn) : {};
|
|
17592
|
+
const formatOpts = redact2 ? { stringify: stringifiers[redactFmtSym] } : { stringify: stringifyFn };
|
|
17593
17593
|
const end = "}" + (crlf ? "\r\n" : "\n");
|
|
17594
17594
|
const coreChindings = asChindings.bind(null, {
|
|
17595
17595
|
[chindingsSym]: "",
|
|
@@ -55132,7 +55132,7 @@ var require_content_disposition = __commonJS({
|
|
|
55132
55132
|
"use strict";
|
|
55133
55133
|
module.exports = contentDisposition;
|
|
55134
55134
|
module.exports.parse = parse5;
|
|
55135
|
-
var
|
|
55135
|
+
var basename5 = __require("path").basename;
|
|
55136
55136
|
var Buffer2 = require_safe_buffer().Buffer;
|
|
55137
55137
|
var ENCODE_URL_ATTR_CHAR_REGEXP = /[\x00-\x20"'()*,/:;<=>?@[\\\]{}\x7f]/g;
|
|
55138
55138
|
var HEX_ESCAPE_REGEXP = /%[0-9A-Fa-f]{2}/;
|
|
@@ -55168,9 +55168,9 @@ var require_content_disposition = __commonJS({
|
|
|
55168
55168
|
if (typeof fallback === "string" && NON_LATIN1_REGEXP.test(fallback)) {
|
|
55169
55169
|
throw new TypeError("fallback must be ISO-8859-1 string");
|
|
55170
55170
|
}
|
|
55171
|
-
var name =
|
|
55171
|
+
var name = basename5(filename);
|
|
55172
55172
|
var isQuotedString = TEXT_REGEXP.test(name);
|
|
55173
|
-
var fallbackName = typeof fallback !== "string" ? fallback && getlatin1(name) :
|
|
55173
|
+
var fallbackName = typeof fallback !== "string" ? fallback && getlatin1(name) : basename5(fallback);
|
|
55174
55174
|
var hasFallback = typeof fallbackName === "string" && fallbackName !== name;
|
|
55175
55175
|
if (hasFallback || !isQuotedString || HEX_ESCAPE_REGEXP.test(name)) {
|
|
55176
55176
|
params["filename*"] = name;
|
|
@@ -60404,16 +60404,46 @@ function encodeAcpServerMessage(message) {
|
|
|
60404
60404
|
return JSON.stringify(message);
|
|
60405
60405
|
}
|
|
60406
60406
|
|
|
60407
|
+
// ../shared/src/adapters.ts
|
|
60408
|
+
function adapterInstallCommand(spec) {
|
|
60409
|
+
return spec.package === null ? null : `npm i -g ${spec.package}@${spec.pinnedVersion}`;
|
|
60410
|
+
}
|
|
60411
|
+
var CLAUDE_ADAPTER = {
|
|
60412
|
+
id: "claude",
|
|
60413
|
+
label: "Claude Code",
|
|
60414
|
+
package: "@agentclientprotocol/claude-agent-acp",
|
|
60415
|
+
// Duas strings porque não são a mesma string, e é exatamente essa a armadilha:
|
|
60416
|
+
// o pacote é escopado e o binário não.
|
|
60417
|
+
command: "claude-agent-acp",
|
|
60418
|
+
pinnedVersion: "0.40.0",
|
|
60419
|
+
cli: { command: "claude", install: null },
|
|
60420
|
+
apiKeyEnv: ["ANTHROPIC_API_KEY"]
|
|
60421
|
+
};
|
|
60422
|
+
var CODEX_ADAPTER = {
|
|
60423
|
+
id: "codex",
|
|
60424
|
+
label: "Codex",
|
|
60425
|
+
package: "@agentclientprotocol/codex-acp",
|
|
60426
|
+
command: "codex-acp",
|
|
60427
|
+
// Medido na fase 0, em 2026-09-06: é o que ele reportou como
|
|
60428
|
+
// `agentInfo.version`.
|
|
60429
|
+
pinnedVersion: "1.10.0",
|
|
60430
|
+
// Null, e não `{ command: "codex" }` por simetria: ele traz o próprio.
|
|
60431
|
+
cli: null,
|
|
60432
|
+
apiKeyEnv: ["CODEX_API_KEY", "OPENAI_API_KEY"]
|
|
60433
|
+
};
|
|
60434
|
+
var ADAPTERS = [CLAUDE_ADAPTER, CODEX_ADAPTER];
|
|
60435
|
+
var DEFAULT_ADAPTER_ID = CLAUDE_ADAPTER.id;
|
|
60436
|
+
function adapterById(id) {
|
|
60437
|
+
return ADAPTERS.find((spec) => spec.id === id) ?? null;
|
|
60438
|
+
}
|
|
60439
|
+
function adapterByCommand(command) {
|
|
60440
|
+
return ADAPTERS.find((spec) => spec.command === command) ?? null;
|
|
60441
|
+
}
|
|
60442
|
+
|
|
60407
60443
|
// ../shared/src/constants.ts
|
|
60408
|
-
var LUMEM_VERSION = "0.
|
|
60444
|
+
var LUMEM_VERSION = "0.3.0";
|
|
60409
60445
|
var DEFAULT_SERVER_PORT = 4317;
|
|
60410
|
-
var ACP_ADAPTER_COMMAND = "claude-agent-acp";
|
|
60411
|
-
var ACP_ADAPTER_PACKAGE = "@agentclientprotocol/claude-agent-acp";
|
|
60412
|
-
var ACP_ADAPTER_INSTALL = `npm i -g ${ACP_ADAPTER_PACKAGE}`;
|
|
60413
|
-
var CLAUDE_CLI_COMMAND = "claude";
|
|
60414
|
-
var ANTHROPIC_API_KEY_ENV = "ANTHROPIC_API_KEY";
|
|
60415
60446
|
var MIN_GIT_VERSION = { major: 2, minor: 30 };
|
|
60416
|
-
var ACP_ADAPTER_PINNED_VERSION = "0.40.0";
|
|
60417
60447
|
var ADAPTERS_DIR_NAME = "adapters";
|
|
60418
60448
|
var ACP_AUTH_REQUIRED_CODE = -32e3;
|
|
60419
60449
|
|
|
@@ -66888,6 +66918,20 @@ var sessionUsage = sqliteTable(
|
|
|
66888
66918
|
projectId: text("project_id").notNull(),
|
|
66889
66919
|
/** A worktree, quando a sessão rodou numa. `''` quando ela é do projeto. */
|
|
66890
66920
|
worktreeId: text("worktree_id").notNull().default(""),
|
|
66921
|
+
/**
|
|
66922
|
+
* Qual agente gastou (`second-agent`, F5).
|
|
66923
|
+
*
|
|
66924
|
+
* Resolvido na escrita, como o projeto e a worktree, e pela mesma razão: a
|
|
66925
|
+
* pergunta "quanto cada agente custou" não pode depender de um join com a
|
|
66926
|
+
* `session`, que é uma tabela que muda e de onde a linha pode sumir.
|
|
66927
|
+
*
|
|
66928
|
+
* Anulável, e sem chave estrangeira. Anulável porque a sessão de shell e a de
|
|
66929
|
+
* script não têm agente, e porque a linha gravada **antes** desta coluna não
|
|
66930
|
+
* ganha um agente inventado — ela fica de fora do agrupamento, o que é a
|
|
66931
|
+
* verdade. Sem estrangeira porque consumo é histórico: apagar a configuração
|
|
66932
|
+
* de ontem não pode apagar o que ela gastou.
|
|
66933
|
+
*/
|
|
66934
|
+
agentConfigId: text("agent_config_id"),
|
|
66891
66935
|
/** A variação da janela de contexto neste turno. Nunca negativa. */
|
|
66892
66936
|
tokens: integer("tokens").notNull().default(0),
|
|
66893
66937
|
/**
|
|
@@ -67046,95 +67090,8 @@ async function withConstraints(run3, translations) {
|
|
|
67046
67090
|
}
|
|
67047
67091
|
}
|
|
67048
67092
|
|
|
67049
|
-
// src/repositories/agentConfig.ts
|
|
67050
|
-
var DEFAULT_AGENT_CONFIG = {
|
|
67051
|
-
name: "claude-code",
|
|
67052
|
-
command: "claude",
|
|
67053
|
-
args: [],
|
|
67054
|
-
env: {},
|
|
67055
|
-
transport: "pty"
|
|
67056
|
-
};
|
|
67057
|
-
function conflicts(name) {
|
|
67058
|
-
return {
|
|
67059
|
-
"unique:agent_config.name": {
|
|
67060
|
-
code: "DUPLICATE",
|
|
67061
|
-
message: `j\xE1 existe uma configura\xE7\xE3o chamada "${name}"`
|
|
67062
|
-
},
|
|
67063
|
-
foreignKey: {
|
|
67064
|
-
code: "IN_USE",
|
|
67065
|
-
message: "a configura\xE7\xE3o ainda est\xE1 em uso por alguma sess\xE3o"
|
|
67066
|
-
},
|
|
67067
|
-
// Without this the CHECK surfaces as a raw SQLite error, which reads as a
|
|
67068
|
-
// daemon defect rather than as the one thing the caller got wrong.
|
|
67069
|
-
"check:agent_config_adapter_version": {
|
|
67070
|
-
code: "INVALID_ARGUMENT",
|
|
67071
|
-
message: "configura\xE7\xE3o ACP precisa de uma vers\xE3o de adaptador fixa, e configura\xE7\xE3o PTY n\xE3o pode ter uma"
|
|
67072
|
-
},
|
|
67073
|
-
"check:agent_config_transport": {
|
|
67074
|
-
code: "INVALID_ARGUMENT",
|
|
67075
|
-
message: "transporte precisa ser pty ou acp"
|
|
67076
|
-
}
|
|
67077
|
-
};
|
|
67078
|
-
}
|
|
67079
|
-
function createAgentConfigRepository(db) {
|
|
67080
|
-
async function require_(id) {
|
|
67081
|
-
const found = await db.query.agentConfig.findFirst({ where: eq(agentConfig.id, id) });
|
|
67082
|
-
if (!found) throw new DomainError("NOT_FOUND", `configura\xE7\xE3o ${id} n\xE3o existe`);
|
|
67083
|
-
return found;
|
|
67084
|
-
}
|
|
67085
|
-
return {
|
|
67086
|
-
async create({ name, command, args = [], env = {}, transport = "pty", adapterVersion = null }) {
|
|
67087
|
-
const [row] = await withConstraints(
|
|
67088
|
-
() => db.insert(agentConfig).values({ id: newId(), name, command, args, env, transport, adapterVersion }).returning(),
|
|
67089
|
-
conflicts(name)
|
|
67090
|
-
);
|
|
67091
|
-
return row;
|
|
67092
|
-
},
|
|
67093
|
-
list() {
|
|
67094
|
-
return db.select().from(agentConfig).orderBy(asc(agentConfig.name));
|
|
67095
|
-
},
|
|
67096
|
-
findById(id) {
|
|
67097
|
-
return db.query.agentConfig.findFirst({ where: eq(agentConfig.id, id) });
|
|
67098
|
-
},
|
|
67099
|
-
findByName(name) {
|
|
67100
|
-
return db.query.agentConfig.findFirst({ where: eq(agentConfig.name, name) });
|
|
67101
|
-
},
|
|
67102
|
-
async update(id, input) {
|
|
67103
|
-
const current = await require_(id);
|
|
67104
|
-
const [row] = await withConstraints(
|
|
67105
|
-
() => db.update(agentConfig).set({ ...input, updatedAt: /* @__PURE__ */ new Date() }).where(eq(agentConfig.id, id)).returning(),
|
|
67106
|
-
conflicts(input.name ?? current.name)
|
|
67107
|
-
);
|
|
67108
|
-
return row;
|
|
67109
|
-
},
|
|
67110
|
-
async remove(id) {
|
|
67111
|
-
await require_(id);
|
|
67112
|
-
await withConstraints(
|
|
67113
|
-
() => db.delete(agentConfig).where(eq(agentConfig.id, id)).returning(),
|
|
67114
|
-
conflicts("")
|
|
67115
|
-
);
|
|
67116
|
-
},
|
|
67117
|
-
async seedDefaults() {
|
|
67118
|
-
const existing = await db.query.agentConfig.findFirst({
|
|
67119
|
-
where: eq(agentConfig.name, DEFAULT_AGENT_CONFIG.name)
|
|
67120
|
-
});
|
|
67121
|
-
if (existing) return;
|
|
67122
|
-
await db.insert(agentConfig).values({
|
|
67123
|
-
id: newId(),
|
|
67124
|
-
name: DEFAULT_AGENT_CONFIG.name,
|
|
67125
|
-
command: DEFAULT_AGENT_CONFIG.command,
|
|
67126
|
-
args: DEFAULT_AGENT_CONFIG.args ?? [],
|
|
67127
|
-
env: DEFAULT_AGENT_CONFIG.env ?? {},
|
|
67128
|
-
// Still PTY. The default configuration changes transport when the
|
|
67129
|
-
// conversation can render one end to end, not when the column exists.
|
|
67130
|
-
transport: DEFAULT_AGENT_CONFIG.transport ?? "pty"
|
|
67131
|
-
});
|
|
67132
|
-
}
|
|
67133
|
-
};
|
|
67134
|
-
}
|
|
67135
|
-
|
|
67136
67093
|
// src/repositories/project.ts
|
|
67137
|
-
function
|
|
67094
|
+
function conflicts(name, path) {
|
|
67138
67095
|
return {
|
|
67139
67096
|
"unique:project.workspace_id,project.name": {
|
|
67140
67097
|
code: "DUPLICATE",
|
|
@@ -67162,7 +67119,7 @@ function createProjectRepository(db) {
|
|
|
67162
67119
|
async create(input) {
|
|
67163
67120
|
const [row] = await withConstraints(
|
|
67164
67121
|
() => db.insert(project).values({ id: newId(), ...input }).returning(),
|
|
67165
|
-
|
|
67122
|
+
conflicts(input.name, input.path)
|
|
67166
67123
|
);
|
|
67167
67124
|
return row;
|
|
67168
67125
|
},
|
|
@@ -67183,7 +67140,7 @@ function createProjectRepository(db) {
|
|
|
67183
67140
|
await require_(id);
|
|
67184
67141
|
const [row] = await withConstraints(
|
|
67185
67142
|
() => db.update(project).set({ name, updatedAt: /* @__PURE__ */ new Date() }).where(eq(project.id, id)).returning(),
|
|
67186
|
-
|
|
67143
|
+
conflicts(name)
|
|
67187
67144
|
);
|
|
67188
67145
|
return row;
|
|
67189
67146
|
},
|
|
@@ -67333,7 +67290,7 @@ function createWorkspaceRepository(db) {
|
|
|
67333
67290
|
}
|
|
67334
67291
|
|
|
67335
67292
|
// src/repositories/worktree.ts
|
|
67336
|
-
function
|
|
67293
|
+
function conflicts2(name) {
|
|
67337
67294
|
return {
|
|
67338
67295
|
"unique:worktree.project_id,worktree.name": {
|
|
67339
67296
|
code: "DUPLICATE",
|
|
@@ -67356,7 +67313,7 @@ function createWorktreeRepository(db) {
|
|
|
67356
67313
|
async create(input) {
|
|
67357
67314
|
const [row] = await withConstraints(
|
|
67358
67315
|
() => db.insert(worktree).values({ id: newId(), ...input }).returning(),
|
|
67359
|
-
|
|
67316
|
+
conflicts2(input.name)
|
|
67360
67317
|
);
|
|
67361
67318
|
return row;
|
|
67362
67319
|
},
|
|
@@ -67374,7 +67331,7 @@ function createWorktreeRepository(db) {
|
|
|
67374
67331
|
await require_(id);
|
|
67375
67332
|
const [row] = await withConstraints(
|
|
67376
67333
|
() => db.update(worktree).set({ state, updatedAt: /* @__PURE__ */ new Date() }).where(eq(worktree.id, id)).returning(),
|
|
67377
|
-
|
|
67334
|
+
conflicts2("")
|
|
67378
67335
|
);
|
|
67379
67336
|
return row;
|
|
67380
67337
|
},
|
|
@@ -67382,7 +67339,7 @@ function createWorktreeRepository(db) {
|
|
|
67382
67339
|
await require_(id);
|
|
67383
67340
|
const [row] = await withConstraints(
|
|
67384
67341
|
() => db.update(worktree).set({ path, updatedAt: /* @__PURE__ */ new Date() }).where(eq(worktree.id, id)).returning(),
|
|
67385
|
-
|
|
67342
|
+
conflicts2("")
|
|
67386
67343
|
);
|
|
67387
67344
|
return row;
|
|
67388
67345
|
},
|
|
@@ -67600,7 +67557,6 @@ async function subdirectories(path) {
|
|
|
67600
67557
|
return entries.filter((entry) => entry.isDirectory()).map((entry) => join4(path, entry.name));
|
|
67601
67558
|
}
|
|
67602
67559
|
async function reconcileOnBoot(options) {
|
|
67603
|
-
await createAgentConfigRepository(options.db).seedDefaults();
|
|
67604
67560
|
const layout = await migrateWorktreeLayout(options);
|
|
67605
67561
|
const clones = await reconcileClones(options).catch(() => 0);
|
|
67606
67562
|
const worktrees = await reconcileWorktrees(options);
|
|
@@ -82698,6 +82654,9 @@ var legacyClientNotificationMethods = /* @__PURE__ */ new Set([
|
|
|
82698
82654
|
CLIENT_METHODS.elicitation_complete
|
|
82699
82655
|
]);
|
|
82700
82656
|
|
|
82657
|
+
// src/acp/AcpManager.ts
|
|
82658
|
+
import { basename as basename3 } from "node:path";
|
|
82659
|
+
|
|
82701
82660
|
// src/agents/availability.ts
|
|
82702
82661
|
import { accessSync, constants } from "node:fs";
|
|
82703
82662
|
import { delimiter, isAbsolute, join as join5 } from "node:path";
|
|
@@ -83945,6 +83904,82 @@ var AcpManager = class {
|
|
|
83945
83904
|
* the spike measured both at zero: nothing is generated until `session/prompt`,
|
|
83946
83905
|
* which never happens here.
|
|
83947
83906
|
*/
|
|
83907
|
+
/**
|
|
83908
|
+
* Entrar no agente, pelo método que o próprio agente ofereceu (F2, T10).
|
|
83909
|
+
*
|
|
83910
|
+
* O caminho que a `agent-login` construiu roda um **comando** num PTY, e ele
|
|
83911
|
+
* continua valendo — é o que o `claude-agent-acp` oferece. Medido na fase 0
|
|
83912
|
+
* (§4.2): o `codex-acp` não oferece comando nenhum. Os métodos dele são
|
|
83913
|
+
* `api-key`, `chat-gpt` e `chat-gpt-device-code`, e os três se atravessam por
|
|
83914
|
+
* **uma chamada**.
|
|
83915
|
+
*
|
|
83916
|
+
* Um processo por tentativa, e ele morre no fim. A credencial não fica aqui: é
|
|
83917
|
+
* o adaptador que a escreve na casa dele — `~/.codex`, no caso — e é por isso
|
|
83918
|
+
* que um processo curto basta. O que o Lumem guarda de tudo isto é nada.
|
|
83919
|
+
*
|
|
83920
|
+
* **Sem `withTimeout` no `authenticate`.** Todos os outros passos do protocolo
|
|
83921
|
+
* são conversa entre dois programas e um limite de 15 s é generoso; este espera
|
|
83922
|
+
* uma pessoa autorizar em outro lugar, e um limite aqui seria o daemon
|
|
83923
|
+
* desistindo de alguém que está digitando um código. Quem desiste é quem
|
|
83924
|
+
* clicou: o `signal` mata o processo.
|
|
83925
|
+
*/
|
|
83926
|
+
async authenticate(options) {
|
|
83927
|
+
if (options.methodId.trim() === "") {
|
|
83928
|
+
throw new DomainError("INVALID_ARGUMENT", "sem o m\xE9todo n\xE3o h\xE1 como entrar");
|
|
83929
|
+
}
|
|
83930
|
+
const { session: session2, child } = this.launch(options, { probe: true });
|
|
83931
|
+
session2.elicit = options.onElicitation;
|
|
83932
|
+
session2.elicitDone = options.onElicitationDone;
|
|
83933
|
+
const abort = () => child.kill();
|
|
83934
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
83935
|
+
try {
|
|
83936
|
+
const initialize = await this.initialize(session2);
|
|
83937
|
+
if (initialize.protocolVersion !== ACP_PROTOCOL_VERSION) {
|
|
83938
|
+
throw new DomainError(
|
|
83939
|
+
"SPAWN_FAILED",
|
|
83940
|
+
`o adaptador fala a vers\xE3o ${initialize.protocolVersion} do protocolo, e este daemon fala a ${ACP_PROTOCOL_VERSION}`
|
|
83941
|
+
);
|
|
83942
|
+
}
|
|
83943
|
+
const offered = (initialize.authMethods ?? []).some(
|
|
83944
|
+
(method) => method.id === options.methodId
|
|
83945
|
+
);
|
|
83946
|
+
if (!offered) {
|
|
83947
|
+
throw new DomainError(
|
|
83948
|
+
"NOT_FOUND",
|
|
83949
|
+
`o adaptador n\xE3o oferece o m\xE9todo de login "${options.methodId}"`
|
|
83950
|
+
);
|
|
83951
|
+
}
|
|
83952
|
+
await session2.connection.agent.request("authenticate", {
|
|
83953
|
+
methodId: options.methodId,
|
|
83954
|
+
/*
|
|
83955
|
+
* A chave viaja no `_meta` e não fica em lugar nenhum.
|
|
83956
|
+
*
|
|
83957
|
+
* Medido no pacote: `authenticateWithApiKey` lê
|
|
83958
|
+
* `_meta["api-key"].apiKey` e, na falta dele, o ambiente
|
|
83959
|
+
* (`CODEX_API_KEY`, `OPENAI_API_KEY`). O daemon é o carteiro — ele não
|
|
83960
|
+
* grava, não loga e não devolve.
|
|
83961
|
+
*/
|
|
83962
|
+
...options.apiKey === void 0 ? {} : { _meta: { "api-key": { apiKey: options.apiKey } } }
|
|
83963
|
+
});
|
|
83964
|
+
try {
|
|
83965
|
+
await this.withTimeout(
|
|
83966
|
+
session2.connection.agent.request("session/new", { cwd: options.cwd, mcpServers: [] }),
|
|
83967
|
+
"session/new"
|
|
83968
|
+
);
|
|
83969
|
+
} catch (error40) {
|
|
83970
|
+
if (isAuthRequired(error40)) {
|
|
83971
|
+
throw new DomainError(
|
|
83972
|
+
"BLOCKED",
|
|
83973
|
+
"o adaptador aceitou o login e continua pedindo credencial \u2014 a conta n\xE3o serve para esta sess\xE3o"
|
|
83974
|
+
);
|
|
83975
|
+
}
|
|
83976
|
+
throw error40;
|
|
83977
|
+
}
|
|
83978
|
+
} finally {
|
|
83979
|
+
options.signal?.removeEventListener("abort", abort);
|
|
83980
|
+
child.kill();
|
|
83981
|
+
}
|
|
83982
|
+
}
|
|
83948
83983
|
async probe(options) {
|
|
83949
83984
|
const startedAt = this.now();
|
|
83950
83985
|
const { session: session2, child } = this.launch(options, { probe: true });
|
|
@@ -84052,6 +84087,8 @@ var AcpManager = class {
|
|
|
84052
84087
|
},
|
|
84053
84088
|
process: child,
|
|
84054
84089
|
connection: void 0,
|
|
84090
|
+
elicit: void 0,
|
|
84091
|
+
elicitDone: void 0,
|
|
84055
84092
|
listeners: /* @__PURE__ */ new Set(),
|
|
84056
84093
|
openToolCalls: /* @__PURE__ */ new Set(),
|
|
84057
84094
|
pendingPermissions: /* @__PURE__ */ new Map(),
|
|
@@ -84367,6 +84404,21 @@ var AcpManager = class {
|
|
|
84367
84404
|
}).onRequest("terminal/release", ({ params }) => {
|
|
84368
84405
|
this.requireTerminals(session2).release(params.terminalId);
|
|
84369
84406
|
return {};
|
|
84407
|
+
}).onRequest("elicitation/create", ({ params }) => {
|
|
84408
|
+
const record2 = params;
|
|
84409
|
+
const url2 = record2["url"];
|
|
84410
|
+
if (session2.elicit === void 0 || params.mode !== "url" || typeof url2 !== "string") {
|
|
84411
|
+
return { action: "decline" };
|
|
84412
|
+
}
|
|
84413
|
+
session2.elicit({
|
|
84414
|
+
elicitationId: String(record2["elicitationId"] ?? ""),
|
|
84415
|
+
url: url2,
|
|
84416
|
+
message: params.message,
|
|
84417
|
+
code: codeIn(params.message)
|
|
84418
|
+
});
|
|
84419
|
+
return { action: "accept" };
|
|
84420
|
+
}).onNotification("elicitation/complete", ({ params }) => {
|
|
84421
|
+
session2.elicitDone?.(params.elicitationId);
|
|
84370
84422
|
}).onRequest("session/request_permission", ({ params }) => {
|
|
84371
84423
|
const requestId = newId();
|
|
84372
84424
|
const options = params.options.map((option) => ({
|
|
@@ -84518,7 +84570,20 @@ var AcpManager = class {
|
|
|
84518
84570
|
* Gated on the `PtyManager` too: the methods it offers are commands, and
|
|
84519
84571
|
* a client that cannot run one has no business being offered it.
|
|
84520
84572
|
*/
|
|
84521
|
-
...this.ptyManager ? { auth: { terminal: true } } : {}
|
|
84573
|
+
...this.ptyManager ? { auth: { terminal: true } } : {},
|
|
84574
|
+
/*
|
|
84575
|
+
* "Eu sei mostrar uma URL", declarado porque os dois métodos existem.
|
|
84576
|
+
*
|
|
84577
|
+
* Medido na fase 0 da `second-agent` (§4.2): sem isto, o `codex-acp`
|
|
84578
|
+
* não oferece `chat-gpt-device-code` — o único método dele que **não**
|
|
84579
|
+
* abre um navegador na máquina do daemon. Declarar é o que faz o
|
|
84580
|
+
* método aparecer, e é por isso que ele não é opcional aqui.
|
|
84581
|
+
*
|
|
84582
|
+
* Não é gatilhado no `ptyManager` como o `auth.terminal`: mostrar uma
|
|
84583
|
+
* URL não precisa de terminal nenhum, e o que responde ao pedido é o
|
|
84584
|
+
* `elicitation/create` acima, que existe sempre.
|
|
84585
|
+
*/
|
|
84586
|
+
elicitation: { url: {} }
|
|
84522
84587
|
},
|
|
84523
84588
|
...this.ptyManager ? { _meta: { "terminal-auth": true } } : {},
|
|
84524
84589
|
clientInfo: { name: "lumem", version: LUMEM_CLIENT_VERSION }
|
|
@@ -84618,6 +84683,9 @@ var AcpManager = class {
|
|
|
84618
84683
|
modeId: value
|
|
84619
84684
|
});
|
|
84620
84685
|
session2.info.mode = value;
|
|
84686
|
+
session2.info.configOptions = session2.info.configOptions.map(
|
|
84687
|
+
(option) => option.id === MODE_OPTION ? { ...option, currentValue: value } : option
|
|
84688
|
+
);
|
|
84621
84689
|
this.emitConfig(session2);
|
|
84622
84690
|
return;
|
|
84623
84691
|
}
|
|
@@ -84832,7 +84900,8 @@ function commandOf(toolCall) {
|
|
|
84832
84900
|
}
|
|
84833
84901
|
function notInstalled(command, adapterVersion) {
|
|
84834
84902
|
const pinned = adapterVersion ?? "";
|
|
84835
|
-
const
|
|
84903
|
+
const spec = adapterByCommand(basename3(command));
|
|
84904
|
+
const remedy = spec !== null && spec.package !== null ? `npm i -g ${spec.package}@${pinned || spec.pinnedVersion}` : `instale o adaptador e deixe "${command}" no PATH`;
|
|
84836
84905
|
const version3 = pinned ? ` Esta sess\xE3o fixa a vers\xE3o ${pinned}.` : "";
|
|
84837
84906
|
return new DomainError(
|
|
84838
84907
|
"SPAWN_FAILED",
|
|
@@ -84848,6 +84917,10 @@ function launchFailure(command, adapterVersion, cause) {
|
|
|
84848
84917
|
{ cause }
|
|
84849
84918
|
);
|
|
84850
84919
|
}
|
|
84920
|
+
function codeIn(message) {
|
|
84921
|
+
const match = /\b([A-Z0-9]{4}-[A-Z0-9]{4}|[A-Z0-9]{6,12})\b/.exec(message);
|
|
84922
|
+
return match?.[1] ?? null;
|
|
84923
|
+
}
|
|
84851
84924
|
function isAuthRequired(error40) {
|
|
84852
84925
|
const code = error40.code;
|
|
84853
84926
|
return code === ACP_AUTH_REQUIRED_CODE;
|
|
@@ -86666,6 +86739,70 @@ function parseWrite(requested) {
|
|
|
86666
86739
|
throw new DomainError("INVALID_ARGUMENT", `${field} \u2014 ${issue2?.message ?? "inv\xE1lido"}`);
|
|
86667
86740
|
}
|
|
86668
86741
|
|
|
86742
|
+
// src/repositories/agentConfig.ts
|
|
86743
|
+
function conflicts3(name) {
|
|
86744
|
+
return {
|
|
86745
|
+
"unique:agent_config.name": {
|
|
86746
|
+
code: "DUPLICATE",
|
|
86747
|
+
message: `j\xE1 existe uma configura\xE7\xE3o chamada "${name}"`
|
|
86748
|
+
},
|
|
86749
|
+
foreignKey: {
|
|
86750
|
+
code: "IN_USE",
|
|
86751
|
+
message: "a configura\xE7\xE3o ainda est\xE1 em uso por alguma sess\xE3o"
|
|
86752
|
+
},
|
|
86753
|
+
// Without this the CHECK surfaces as a raw SQLite error, which reads as a
|
|
86754
|
+
// daemon defect rather than as the one thing the caller got wrong.
|
|
86755
|
+
"check:agent_config_adapter_version": {
|
|
86756
|
+
code: "INVALID_ARGUMENT",
|
|
86757
|
+
message: "configura\xE7\xE3o ACP precisa de uma vers\xE3o de adaptador fixa, e configura\xE7\xE3o PTY n\xE3o pode ter uma"
|
|
86758
|
+
},
|
|
86759
|
+
"check:agent_config_transport": {
|
|
86760
|
+
code: "INVALID_ARGUMENT",
|
|
86761
|
+
message: "transporte precisa ser pty ou acp"
|
|
86762
|
+
}
|
|
86763
|
+
};
|
|
86764
|
+
}
|
|
86765
|
+
function createAgentConfigRepository(db) {
|
|
86766
|
+
async function require_(id) {
|
|
86767
|
+
const found = await db.query.agentConfig.findFirst({ where: eq(agentConfig.id, id) });
|
|
86768
|
+
if (!found) throw new DomainError("NOT_FOUND", `configura\xE7\xE3o ${id} n\xE3o existe`);
|
|
86769
|
+
return found;
|
|
86770
|
+
}
|
|
86771
|
+
return {
|
|
86772
|
+
async create({ name, command, args = [], env = {}, transport = "pty", adapterVersion = null }) {
|
|
86773
|
+
const [row] = await withConstraints(
|
|
86774
|
+
() => db.insert(agentConfig).values({ id: newId(), name, command, args, env, transport, adapterVersion }).returning(),
|
|
86775
|
+
conflicts3(name)
|
|
86776
|
+
);
|
|
86777
|
+
return row;
|
|
86778
|
+
},
|
|
86779
|
+
list() {
|
|
86780
|
+
return db.select().from(agentConfig).orderBy(asc(agentConfig.name));
|
|
86781
|
+
},
|
|
86782
|
+
findById(id) {
|
|
86783
|
+
return db.query.agentConfig.findFirst({ where: eq(agentConfig.id, id) });
|
|
86784
|
+
},
|
|
86785
|
+
findByName(name) {
|
|
86786
|
+
return db.query.agentConfig.findFirst({ where: eq(agentConfig.name, name) });
|
|
86787
|
+
},
|
|
86788
|
+
async update(id, input) {
|
|
86789
|
+
const current = await require_(id);
|
|
86790
|
+
const [row] = await withConstraints(
|
|
86791
|
+
() => db.update(agentConfig).set({ ...input, updatedAt: /* @__PURE__ */ new Date() }).where(eq(agentConfig.id, id)).returning(),
|
|
86792
|
+
conflicts3(input.name ?? current.name)
|
|
86793
|
+
);
|
|
86794
|
+
return row;
|
|
86795
|
+
},
|
|
86796
|
+
async remove(id) {
|
|
86797
|
+
await require_(id);
|
|
86798
|
+
await withConstraints(
|
|
86799
|
+
() => db.delete(agentConfig).where(eq(agentConfig.id, id)).returning(),
|
|
86800
|
+
conflicts3("")
|
|
86801
|
+
);
|
|
86802
|
+
}
|
|
86803
|
+
};
|
|
86804
|
+
}
|
|
86805
|
+
|
|
86669
86806
|
// src/memory/projection.ts
|
|
86670
86807
|
import { relative as relative4, isAbsolute as isAbsolute5 } from "node:path";
|
|
86671
86808
|
var MAX_FILES = 12;
|
|
@@ -87155,6 +87292,7 @@ function trackSessionUsage({ db, acpManager, log }) {
|
|
|
87155
87292
|
sessionId,
|
|
87156
87293
|
projectId: scope.projectId,
|
|
87157
87294
|
worktreeId: scope.worktreeId,
|
|
87295
|
+
agentConfigId: scope.agentConfigId,
|
|
87158
87296
|
tokens,
|
|
87159
87297
|
...cost === null ? {} : { cost: cost.amount, currency: cost.currency }
|
|
87160
87298
|
}).run();
|
|
@@ -87170,10 +87308,95 @@ function trackSessionUsage({ db, acpManager, log }) {
|
|
|
87170
87308
|
async function scopeOf(db, sessionId) {
|
|
87171
87309
|
const row = await createSessionRepository(db).findById(sessionId);
|
|
87172
87310
|
if (row === void 0) return null;
|
|
87173
|
-
|
|
87311
|
+
const agentConfigId = row.agentConfigId ?? null;
|
|
87312
|
+
if (row.scopeType === "project") {
|
|
87313
|
+
return { projectId: row.scopeId, worktreeId: "", agentConfigId };
|
|
87314
|
+
}
|
|
87174
87315
|
const worktree2 = await createWorktreeRepository(db).findById(row.scopeId);
|
|
87175
87316
|
if (worktree2 === void 0) return null;
|
|
87176
|
-
return { projectId: worktree2.projectId, worktreeId: worktree2.id };
|
|
87317
|
+
return { projectId: worktree2.projectId, worktreeId: worktree2.id, agentConfigId };
|
|
87318
|
+
}
|
|
87319
|
+
|
|
87320
|
+
// src/setup/agent-auth.ts
|
|
87321
|
+
function redact(message, secret) {
|
|
87322
|
+
if (secret === void 0 || secret === "") return message;
|
|
87323
|
+
return message.split(secret).join("\u2022\u2022\u2022");
|
|
87324
|
+
}
|
|
87325
|
+
function createAgentAuthService({
|
|
87326
|
+
acpManager
|
|
87327
|
+
}) {
|
|
87328
|
+
const attempts = /* @__PURE__ */ new Map();
|
|
87329
|
+
function require_(id) {
|
|
87330
|
+
const attempt = attempts.get(id);
|
|
87331
|
+
if (attempt === void 0) {
|
|
87332
|
+
throw new DomainError("NOT_FOUND", `n\xE3o existe login em andamento com o id ${id}`);
|
|
87333
|
+
}
|
|
87334
|
+
return attempt;
|
|
87335
|
+
}
|
|
87336
|
+
function view({ id, state, elicitation, message }) {
|
|
87337
|
+
return { id, state, elicitation, message };
|
|
87338
|
+
}
|
|
87339
|
+
return {
|
|
87340
|
+
start(options) {
|
|
87341
|
+
const attempt = {
|
|
87342
|
+
id: newId(),
|
|
87343
|
+
state: "running",
|
|
87344
|
+
elicitation: null,
|
|
87345
|
+
message: null,
|
|
87346
|
+
controller: new AbortController()
|
|
87347
|
+
};
|
|
87348
|
+
attempts.set(attempt.id, attempt);
|
|
87349
|
+
void acpManager.authenticate({
|
|
87350
|
+
command: options.command,
|
|
87351
|
+
...options.args ? { args: options.args } : {},
|
|
87352
|
+
cwd: options.cwd,
|
|
87353
|
+
methodId: options.methodId,
|
|
87354
|
+
...options.apiKey === void 0 ? {} : { apiKey: options.apiKey },
|
|
87355
|
+
...options.adapterVersion === void 0 ? {} : { adapterVersion: options.adapterVersion },
|
|
87356
|
+
onElicitation: (elicitation) => {
|
|
87357
|
+
attempt.elicitation = elicitation;
|
|
87358
|
+
},
|
|
87359
|
+
onElicitationDone: (elicitationId) => {
|
|
87360
|
+
if (attempt.elicitation?.elicitationId === elicitationId) {
|
|
87361
|
+
attempt.elicitation = null;
|
|
87362
|
+
}
|
|
87363
|
+
},
|
|
87364
|
+
signal: attempt.controller.signal
|
|
87365
|
+
}).then(() => {
|
|
87366
|
+
if (attempt.state === "running") attempt.state = "ok";
|
|
87367
|
+
attempt.elicitation = null;
|
|
87368
|
+
}).catch((error40) => {
|
|
87369
|
+
if (attempt.state === "cancelled") return;
|
|
87370
|
+
attempt.state = "failed";
|
|
87371
|
+
attempt.elicitation = null;
|
|
87372
|
+
attempt.message = redact(
|
|
87373
|
+
error40 instanceof Error ? error40.message : "o adaptador recusou o login",
|
|
87374
|
+
options.apiKey
|
|
87375
|
+
);
|
|
87376
|
+
});
|
|
87377
|
+
return view(attempt);
|
|
87378
|
+
},
|
|
87379
|
+
status(id) {
|
|
87380
|
+
return view(require_(id));
|
|
87381
|
+
},
|
|
87382
|
+
cancel(id) {
|
|
87383
|
+
const attempt = require_(id);
|
|
87384
|
+
if (attempt.state === "running") {
|
|
87385
|
+
attempt.state = "cancelled";
|
|
87386
|
+
attempt.elicitation = null;
|
|
87387
|
+
attempt.controller.abort();
|
|
87388
|
+
}
|
|
87389
|
+
return view(attempt);
|
|
87390
|
+
},
|
|
87391
|
+
cancelAll() {
|
|
87392
|
+
for (const attempt of attempts.values()) {
|
|
87393
|
+
if (attempt.state === "running") {
|
|
87394
|
+
attempt.state = "cancelled";
|
|
87395
|
+
attempt.controller.abort();
|
|
87396
|
+
}
|
|
87397
|
+
}
|
|
87398
|
+
}
|
|
87399
|
+
};
|
|
87177
87400
|
}
|
|
87178
87401
|
|
|
87179
87402
|
// src/memory/skill.ts
|
|
@@ -94543,7 +94766,7 @@ function allows(options, strategy) {
|
|
|
94543
94766
|
// src/routers/project.ts
|
|
94544
94767
|
import { existsSync as existsSync5 } from "node:fs";
|
|
94545
94768
|
import { rename as rename4, rm as rm7 } from "node:fs/promises";
|
|
94546
|
-
import { basename as
|
|
94769
|
+
import { basename as basename4, dirname as dirname11, isAbsolute as isAbsolute6, normalize as normalize4 } from "node:path";
|
|
94547
94770
|
|
|
94548
94771
|
// src/git/clone.ts
|
|
94549
94772
|
import { spawn as spawn2 } from "node:child_process";
|
|
@@ -94950,7 +95173,7 @@ var projectRouter = router({
|
|
|
94950
95173
|
registerProject(ctx, {
|
|
94951
95174
|
workspaceId: input.workspaceId,
|
|
94952
95175
|
path: input.path,
|
|
94953
|
-
name: input.name ??
|
|
95176
|
+
name: input.name ?? basename4(input.path)
|
|
94954
95177
|
})
|
|
94955
95178
|
)
|
|
94956
95179
|
)
|
|
@@ -95448,42 +95671,86 @@ async function detectAgents({
|
|
|
95448
95671
|
path = process.env["PATH"],
|
|
95449
95672
|
env = process.env,
|
|
95450
95673
|
run: run3 = runCommand,
|
|
95674
|
+
specs = ADAPTERS,
|
|
95451
95675
|
installedAt
|
|
95452
95676
|
} = {}) {
|
|
95453
|
-
const
|
|
95454
|
-
|
|
95455
|
-
|
|
95677
|
+
const adapters = await Promise.all(
|
|
95678
|
+
specs.map(async (spec) => reportFor(spec, { path, env, run: run3, installedAt }))
|
|
95679
|
+
);
|
|
95680
|
+
return { adapters };
|
|
95681
|
+
}
|
|
95682
|
+
async function reportFor(spec, {
|
|
95683
|
+
path,
|
|
95684
|
+
env,
|
|
95685
|
+
run: run3,
|
|
95686
|
+
installedAt
|
|
95687
|
+
}) {
|
|
95688
|
+
const [adapter, cli] = await Promise.all([
|
|
95689
|
+
inspect(spec.command, adapterInstallCommand(spec), {
|
|
95690
|
+
path,
|
|
95691
|
+
run: run3,
|
|
95692
|
+
...installedAt === void 0 ? {} : { preferred: installedAt(spec) }
|
|
95693
|
+
}),
|
|
95694
|
+
spec.cli === null ? Promise.resolve(null) : inspect(spec.cli.command, spec.cli.install, { path, run: run3 })
|
|
95456
95695
|
]);
|
|
95457
|
-
|
|
95458
|
-
|
|
95696
|
+
return {
|
|
95697
|
+
id: spec.id,
|
|
95698
|
+
label: spec.label,
|
|
95699
|
+
adapter,
|
|
95700
|
+
cli,
|
|
95701
|
+
apiKeyEnv: spec.apiKeyEnv.find((name) => (env[name] ?? "").trim() !== "") ?? null
|
|
95702
|
+
};
|
|
95459
95703
|
}
|
|
95460
95704
|
|
|
95461
95705
|
// src/setup/install-adapter.ts
|
|
95462
95706
|
import { existsSync as existsSync6 } from "node:fs";
|
|
95463
95707
|
import { mkdir as mkdir8 } from "node:fs/promises";
|
|
95464
95708
|
import { join as join18 } from "node:path";
|
|
95465
|
-
function
|
|
95466
|
-
return join18(dir,
|
|
95709
|
+
function adapterDir(dir, spec = CLAUDE_ADAPTER) {
|
|
95710
|
+
return join18(dir, spec.id);
|
|
95711
|
+
}
|
|
95712
|
+
function adapterBinaryPath(dir, spec = CLAUDE_ADAPTER) {
|
|
95713
|
+
return join18(adapterDir(dir, spec), "node_modules", ".bin", spec.command);
|
|
95714
|
+
}
|
|
95715
|
+
function legacyAdapterBinaryPath(dir, spec) {
|
|
95716
|
+
return join18(dir, "node_modules", ".bin", spec.command);
|
|
95467
95717
|
}
|
|
95468
95718
|
async function installAdapter({
|
|
95719
|
+
spec = CLAUDE_ADAPTER,
|
|
95469
95720
|
dir,
|
|
95470
95721
|
run: run3 = runCommand,
|
|
95471
|
-
timeoutMs = 12e4
|
|
95722
|
+
timeoutMs = 12e4,
|
|
95723
|
+
resolve: resolve6 = resolveCommandPath
|
|
95472
95724
|
}) {
|
|
95473
|
-
|
|
95725
|
+
if (spec.package === null) {
|
|
95726
|
+
const found = resolve6(spec.command);
|
|
95727
|
+
if (found === null) {
|
|
95728
|
+
throw new DomainError(
|
|
95729
|
+
"NOT_FOUND",
|
|
95730
|
+
`${spec.label} n\xE3o tem adaptador para instalar: o bin\xE1rio ${spec.command} tem que estar no PATH, e n\xE3o est\xE1`
|
|
95731
|
+
);
|
|
95732
|
+
}
|
|
95733
|
+
return { path: found, version: spec.pinnedVersion, alreadyInstalled: true };
|
|
95734
|
+
}
|
|
95735
|
+
const target = adapterDir(dir, spec);
|
|
95736
|
+
const binary = adapterBinaryPath(dir, spec);
|
|
95474
95737
|
if (existsSync6(binary)) {
|
|
95475
|
-
return { path: binary, version:
|
|
95738
|
+
return { path: binary, version: spec.pinnedVersion, alreadyInstalled: true };
|
|
95739
|
+
}
|
|
95740
|
+
const legacy = legacyAdapterBinaryPath(dir, spec);
|
|
95741
|
+
if (existsSync6(legacy)) {
|
|
95742
|
+
return { path: legacy, version: spec.pinnedVersion, alreadyInstalled: true };
|
|
95476
95743
|
}
|
|
95477
|
-
await mkdir8(
|
|
95744
|
+
await mkdir8(target, { recursive: true });
|
|
95478
95745
|
const outcome = await run3(
|
|
95479
95746
|
"npm",
|
|
95480
95747
|
[
|
|
95481
95748
|
"install",
|
|
95482
95749
|
"--prefix",
|
|
95483
|
-
|
|
95750
|
+
target,
|
|
95484
95751
|
"--no-fund",
|
|
95485
95752
|
"--no-audit",
|
|
95486
|
-
`${
|
|
95753
|
+
`${spec.package}@${spec.pinnedVersion}`
|
|
95487
95754
|
],
|
|
95488
95755
|
{ timeoutMs }
|
|
95489
95756
|
);
|
|
@@ -95500,10 +95767,10 @@ ${outcome.output}`.trim()
|
|
|
95500
95767
|
if (!existsSync6(binary)) {
|
|
95501
95768
|
throw new DomainError(
|
|
95502
95769
|
"SPAWN_FAILED",
|
|
95503
|
-
`o npm terminou sem erro mas ${binary} n\xE3o existe \u2014 o pacote ${
|
|
95770
|
+
`o npm terminou sem erro mas ${binary} n\xE3o existe \u2014 o pacote ${spec.package} pode ter mudado de layout`
|
|
95504
95771
|
);
|
|
95505
95772
|
}
|
|
95506
|
-
return { path: binary, version:
|
|
95773
|
+
return { path: binary, version: spec.pinnedVersion, alreadyInstalled: false };
|
|
95507
95774
|
}
|
|
95508
95775
|
|
|
95509
95776
|
// src/setup/login.ts
|
|
@@ -95698,6 +95965,17 @@ function diskCheck(config2, freeBytes) {
|
|
|
95698
95965
|
}
|
|
95699
95966
|
|
|
95700
95967
|
// src/routers/setup.ts
|
|
95968
|
+
function adaptersDir(stateDir) {
|
|
95969
|
+
return join19(stateDir, ADAPTERS_DIR_NAME);
|
|
95970
|
+
}
|
|
95971
|
+
function specOf(id) {
|
|
95972
|
+
const spec = adapterById(id ?? DEFAULT_ADAPTER_ID);
|
|
95973
|
+
if (spec === null) {
|
|
95974
|
+
throw new DomainError("INVALID_ARGUMENT", `n\xE3o existe adaptador "${id}" no cat\xE1logo`);
|
|
95975
|
+
}
|
|
95976
|
+
return spec;
|
|
95977
|
+
}
|
|
95978
|
+
var adapterInput = external_exports.object({ adapterId: external_exports.string().trim().min(1).optional() }).optional();
|
|
95701
95979
|
var setupRouter = router({
|
|
95702
95980
|
/** The five checks, each one able to fail without the others. */
|
|
95703
95981
|
preflight: publicProcedure.query(({ ctx }) => preflight({ config: ctx.config })),
|
|
@@ -95709,7 +95987,9 @@ var setupRouter = router({
|
|
|
95709
95987
|
* have it globally, and the flow must not ask twice for the same thing.
|
|
95710
95988
|
*/
|
|
95711
95989
|
agents: publicProcedure.query(
|
|
95712
|
-
({ ctx }) => detectAgents({
|
|
95990
|
+
({ ctx }) => detectAgents({
|
|
95991
|
+
installedAt: (spec) => adapterBinaryPath(adaptersDir(ctx.config.stateDir), spec)
|
|
95992
|
+
})
|
|
95713
95993
|
),
|
|
95714
95994
|
/**
|
|
95715
95995
|
* Installs the adapter into the daemon's own directory, at the pinned version.
|
|
@@ -95718,9 +95998,9 @@ var setupRouter = router({
|
|
|
95718
95998
|
* costs is named in `install-adapter.ts` — the daemon runs a package manager and
|
|
95719
95999
|
* then executes what it downloaded.
|
|
95720
96000
|
*/
|
|
95721
|
-
installAdapter: publicProcedure.mutation(
|
|
95722
|
-
({ ctx }) => domainSafeAsync(
|
|
95723
|
-
() => installAdapter({ dir:
|
|
96001
|
+
installAdapter: publicProcedure.input(adapterInput).mutation(
|
|
96002
|
+
({ ctx, input }) => domainSafeAsync(
|
|
96003
|
+
() => installAdapter({ spec: specOf(input?.adapterId), dir: adaptersDir(ctx.config.stateDir) })
|
|
95724
96004
|
)
|
|
95725
96005
|
),
|
|
95726
96006
|
/**
|
|
@@ -95736,6 +96016,8 @@ var setupRouter = router({
|
|
|
95736
96016
|
methodId: external_exports.string().trim().min(1),
|
|
95737
96017
|
/** Which adapter to ask. Defaults to what the flow installed or found. */
|
|
95738
96018
|
command: external_exports.string().trim().min(1).optional(),
|
|
96019
|
+
/** Which catalogued adapter, when no explicit command is given. */
|
|
96020
|
+
adapterId: external_exports.string().trim().min(1).optional(),
|
|
95739
96021
|
/**
|
|
95740
96022
|
* And its arguments, because a command without them is a different program.
|
|
95741
96023
|
*
|
|
@@ -95748,7 +96030,7 @@ var setupRouter = router({
|
|
|
95748
96030
|
})
|
|
95749
96031
|
).mutation(
|
|
95750
96032
|
({ ctx, input }) => domainSafeAsync(async () => {
|
|
95751
|
-
const command = input.command ??
|
|
96033
|
+
const command = input.command ?? specOf(input.adapterId).command;
|
|
95752
96034
|
const cwd = join19(ctx.config.stateDir, "probe");
|
|
95753
96035
|
const report = await ctx.acpManager.probe({
|
|
95754
96036
|
command,
|
|
@@ -95778,6 +96060,60 @@ var setupRouter = router({
|
|
|
95778
96060
|
});
|
|
95779
96061
|
})
|
|
95780
96062
|
),
|
|
96063
|
+
/**
|
|
96064
|
+
* Entrar no agente, pela chamada que o próprio agente ofereceu (T10, T11).
|
|
96065
|
+
*
|
|
96066
|
+
* Três procedimentos e nenhum bloqueante, porque um deles espera **uma
|
|
96067
|
+
* pessoa**: o `authenticate` de um método de navegador fica pendurado até
|
|
96068
|
+
* alguém autorizar noutro lugar, e o pedido de mostrar uma URL chega no meio
|
|
96069
|
+
* dessa espera. Começar e perguntar é o mesmo desenho do login por comando, que
|
|
96070
|
+
* devolvia um `ptySessionId` para o cliente acompanhar.
|
|
96071
|
+
*
|
|
96072
|
+
* O `command` continua sendo conferido contra o handshake — o cliente manda um
|
|
96073
|
+
* `methodId`, e o que roda é o que o adaptador declarou para aquele id.
|
|
96074
|
+
*/
|
|
96075
|
+
authenticate: publicProcedure.input(
|
|
96076
|
+
external_exports.object({
|
|
96077
|
+
methodId: external_exports.string().trim().min(1),
|
|
96078
|
+
/** Qual adaptador, quando não vem um comando explícito. */
|
|
96079
|
+
adapterId: external_exports.string().trim().min(1).optional(),
|
|
96080
|
+
command: external_exports.string().trim().min(1).optional(),
|
|
96081
|
+
args: external_exports.array(external_exports.string()).optional(),
|
|
96082
|
+
/**
|
|
96083
|
+
* A chave, quando o método pede uma.
|
|
96084
|
+
*
|
|
96085
|
+
* Ela entra por aqui, atravessa o daemon e vai para o adaptador. Não é
|
|
96086
|
+
* gravada, não é logada e **não volta** em nenhuma resposta — o que volta
|
|
96087
|
+
* é o estado da tentativa.
|
|
96088
|
+
*/
|
|
96089
|
+
apiKey: external_exports.string().min(1).optional()
|
|
96090
|
+
})
|
|
96091
|
+
).mutation(
|
|
96092
|
+
({ ctx, input }) => domainSafeAsync(() => {
|
|
96093
|
+
const spec = specOf(input.adapterId);
|
|
96094
|
+
const cwd = join19(ctx.config.stateDir, "probe");
|
|
96095
|
+
mkdirSync4(cwd, { recursive: true });
|
|
96096
|
+
return Promise.resolve(
|
|
96097
|
+
ctx.agentAuth.start({
|
|
96098
|
+
command: input.command ?? spec.command,
|
|
96099
|
+
...input.args === void 0 ? {} : { args: input.args },
|
|
96100
|
+
cwd,
|
|
96101
|
+
methodId: input.methodId,
|
|
96102
|
+
...input.apiKey === void 0 ? {} : { apiKey: input.apiKey },
|
|
96103
|
+
adapterVersion: spec.pinnedVersion
|
|
96104
|
+
})
|
|
96105
|
+
);
|
|
96106
|
+
})
|
|
96107
|
+
),
|
|
96108
|
+
/** O estado de uma tentativa de login. É o que a tela pergunta enquanto espera. */
|
|
96109
|
+
authState: publicProcedure.input(external_exports.object({ loginId: external_exports.string().trim().min(1) })).query(({ ctx, input }) => domainSafe(() => ctx.agentAuth.status(input.loginId))),
|
|
96110
|
+
/**
|
|
96111
|
+
* Desistir: mata o adaptador.
|
|
96112
|
+
*
|
|
96113
|
+
* Não há "cancelar" no protocolo — o `authenticate` está esperando uma pessoa,
|
|
96114
|
+
* e a única forma de parar de esperar é o processo acabar. Ele é do daemon.
|
|
96115
|
+
*/
|
|
96116
|
+
cancelAuth: publicProcedure.input(external_exports.object({ loginId: external_exports.string().trim().min(1) })).mutation(({ ctx, input }) => domainSafe(() => ctx.agentAuth.cancel(input.loginId))),
|
|
95781
96117
|
/**
|
|
95782
96118
|
* One handshake, then the process dies.
|
|
95783
96119
|
*
|
|
@@ -95789,11 +96125,13 @@ var setupRouter = router({
|
|
|
95789
96125
|
external_exports.object({
|
|
95790
96126
|
/** Defaults to the adapter the flow installs. */
|
|
95791
96127
|
command: external_exports.string().trim().min(1).optional(),
|
|
96128
|
+
/** Which catalogued adapter, when no explicit command is given. */
|
|
96129
|
+
adapterId: external_exports.string().trim().min(1).optional(),
|
|
95792
96130
|
args: external_exports.array(external_exports.string()).optional()
|
|
95793
96131
|
}).optional()
|
|
95794
96132
|
).query(
|
|
95795
96133
|
({ ctx, input }) => domainSafeAsync(async () => {
|
|
95796
|
-
const command = input?.command ??
|
|
96134
|
+
const command = input?.command ?? specOf(input?.adapterId).command;
|
|
95797
96135
|
const cwd = join19(ctx.config.stateDir, "probe");
|
|
95798
96136
|
try {
|
|
95799
96137
|
mkdirSync4(cwd, { recursive: true });
|
|
@@ -95871,6 +96209,32 @@ function usageByWorktree(db, { projectId, period: period2, now }) {
|
|
|
95871
96209
|
and(eq(sessionUsage.worktreeId, worktree.id), gte(sessionUsage.createdAt, since))
|
|
95872
96210
|
).where(eq(worktree.projectId, projectId)).groupBy(worktree.id).orderBy(sql`${SUM.tokens} desc`, worktree.name).all();
|
|
95873
96211
|
}
|
|
96212
|
+
function usageByProjectAndAgent(db, { workspaceId, period: period2, now }) {
|
|
96213
|
+
const since = windowStart(period2, now);
|
|
96214
|
+
return db.select({
|
|
96215
|
+
projectId: sessionUsage.projectId,
|
|
96216
|
+
agentConfigId: sessionUsage.agentConfigId,
|
|
96217
|
+
// `max` e não `min`: dá no mesmo dentro de um grupo — o join é por id — e
|
|
96218
|
+
// é o mesmo truque que a moeda já usa para atravessar um `GROUP BY`.
|
|
96219
|
+
name: sql`max(${agentConfig.name})`,
|
|
96220
|
+
tokens: SUM.tokens,
|
|
96221
|
+
cost: SUM.cost,
|
|
96222
|
+
currency: SUM.currency,
|
|
96223
|
+
turns: SUM.turns
|
|
96224
|
+
}).from(sessionUsage).innerJoin(project, eq(project.id, sessionUsage.projectId)).leftJoin(agentConfig, eq(agentConfig.id, sessionUsage.agentConfigId)).where(and(eq(project.workspaceId, workspaceId), gte(sessionUsage.createdAt, since))).groupBy(sessionUsage.projectId, sessionUsage.agentConfigId).orderBy(sql`${SUM.tokens} desc`).all();
|
|
96225
|
+
}
|
|
96226
|
+
function usageByWorktreeAndAgent(db, { projectId, period: period2, now }) {
|
|
96227
|
+
const since = windowStart(period2, now);
|
|
96228
|
+
return db.select({
|
|
96229
|
+
worktreeId: sessionUsage.worktreeId,
|
|
96230
|
+
agentConfigId: sessionUsage.agentConfigId,
|
|
96231
|
+
name: sql`max(${agentConfig.name})`,
|
|
96232
|
+
tokens: SUM.tokens,
|
|
96233
|
+
cost: SUM.cost,
|
|
96234
|
+
currency: SUM.currency,
|
|
96235
|
+
turns: SUM.turns
|
|
96236
|
+
}).from(sessionUsage).leftJoin(agentConfig, eq(agentConfig.id, sessionUsage.agentConfigId)).where(and(eq(sessionUsage.projectId, projectId), gte(sessionUsage.createdAt, since))).groupBy(sessionUsage.worktreeId, sessionUsage.agentConfigId).orderBy(sql`${SUM.tokens} desc`).all();
|
|
96237
|
+
}
|
|
95874
96238
|
function usageOutsideWorktrees(db, { projectId, period: period2, now }) {
|
|
95875
96239
|
const since = windowStart(period2, now);
|
|
95876
96240
|
const [row] = db.select({ tokens: SUM.tokens, cost: SUM.cost, currency: SUM.currency, turns: SUM.turns }).from(sessionUsage).where(
|
|
@@ -95907,7 +96271,21 @@ var usageRouter = router({
|
|
|
95907
96271
|
projectId: input.projectId,
|
|
95908
96272
|
period: input.period
|
|
95909
96273
|
})
|
|
95910
|
-
}))
|
|
96274
|
+
})),
|
|
96275
|
+
/*
|
|
96276
|
+
* O mesmo consumo por agente (`second-agent`, F5).
|
|
96277
|
+
*
|
|
96278
|
+
* Procedimentos separados, e não um `groupBy` nos dois de cima: a resposta
|
|
96279
|
+
* agrupada tem uma linha por par, e enfiá-la na mesma chamada mudaria a forma
|
|
96280
|
+
* do que a tela do workspace já lê. A tela só pede isto quando há **mais de um
|
|
96281
|
+
* agente** — com um, a coluna não existe e a chamada não acontece (C5).
|
|
96282
|
+
*/
|
|
96283
|
+
byProjectAndAgent: publicProcedure.input(external_exports.object({ workspaceId: external_exports.string().min(1), period })).query(
|
|
96284
|
+
({ ctx, input }) => usageByProjectAndAgent(ctx.db, { workspaceId: input.workspaceId, period: input.period })
|
|
96285
|
+
),
|
|
96286
|
+
byWorktreeAndAgent: publicProcedure.input(external_exports.object({ projectId: external_exports.string().min(1), period })).query(
|
|
96287
|
+
({ ctx, input }) => usageByWorktreeAndAgent(ctx.db, { projectId: input.projectId, period: input.period })
|
|
96288
|
+
)
|
|
95911
96289
|
});
|
|
95912
96290
|
|
|
95913
96291
|
// src/routers/workspace.ts
|
|
@@ -96142,6 +96520,7 @@ async function createServer2({
|
|
|
96142
96520
|
// noutra aba aparecer nesta sem esperar o próximo ciclo.
|
|
96143
96521
|
onChange: (projectId) => events.emit({ type: "pr.changed", projectId })
|
|
96144
96522
|
}),
|
|
96523
|
+
agentAuth = createAgentAuthService({ acpManager }),
|
|
96145
96524
|
logger = false
|
|
96146
96525
|
}) {
|
|
96147
96526
|
const app = (0, import_fastify2.default)({
|
|
@@ -96162,6 +96541,7 @@ async function createServer2({
|
|
|
96162
96541
|
clones,
|
|
96163
96542
|
pr,
|
|
96164
96543
|
prHost,
|
|
96544
|
+
agentAuth,
|
|
96165
96545
|
events
|
|
96166
96546
|
});
|
|
96167
96547
|
await app.register(fastifyTRPCPlugin, {
|
|
@@ -96297,6 +96677,7 @@ async function bootstrap({
|
|
|
96297
96677
|
askUrl: `http://${config2.host}:${String(config2.port)}/memory/ask`
|
|
96298
96678
|
})
|
|
96299
96679
|
});
|
|
96680
|
+
const agentAuth = createAgentAuthService({ acpManager: acp });
|
|
96300
96681
|
const sessionStore = createSessionStore({
|
|
96301
96682
|
db: openedDatabase.db,
|
|
96302
96683
|
ptyManager,
|
|
@@ -96359,6 +96740,7 @@ async function bootstrap({
|
|
|
96359
96740
|
}
|
|
96360
96741
|
}),
|
|
96361
96742
|
events,
|
|
96743
|
+
agentAuth,
|
|
96362
96744
|
logger
|
|
96363
96745
|
});
|
|
96364
96746
|
bootedApp = app;
|
|
@@ -96370,6 +96752,7 @@ async function bootstrap({
|
|
|
96370
96752
|
stopUsageTracking();
|
|
96371
96753
|
await ptyManager.killAll();
|
|
96372
96754
|
await acp.killAll();
|
|
96755
|
+
agentAuth.cancelAll();
|
|
96373
96756
|
if (beforeClose) await beforeClose();
|
|
96374
96757
|
await app.close();
|
|
96375
96758
|
if (owned) openedDatabase.close();
|