@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.
@@ -13993,7 +13993,7 @@ var require_redact = __commonJS({
13993
13993
  } = options;
13994
13994
  validatePaths(paths);
13995
13995
  const pathStructure = buildPathStructure(paths);
13996
- return function redact(obj) {
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 = redact ? redaction(redact, stringifyFn) : {};
17592
- const formatOpts = redact ? { stringify: stringifiers[redactFmtSym] } : { stringify: stringifyFn };
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 basename4 = __require("path").basename;
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 = basename4(filename);
55171
+ var name = basename5(filename);
55172
55172
  var isQuotedString = TEXT_REGEXP.test(name);
55173
- var fallbackName = typeof fallback !== "string" ? fallback && getlatin1(name) : basename4(fallback);
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.2.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 conflicts2(name, path) {
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
- conflicts2(input.name, input.path)
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
- conflicts2(name)
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 conflicts3(name) {
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
- conflicts3(input.name)
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
- conflicts3("")
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
- conflicts3("")
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 remedy = pinned ? `npm i -g @agentclientprotocol/claude-agent-acp@${pinned}` : `instale o adaptador e deixe "${command}" no PATH`;
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
- if (row.scopeType === "project") return { projectId: row.scopeId, worktreeId: "" };
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 basename3, dirname as dirname11, isAbsolute as isAbsolute6, normalize as normalize4 } from "node:path";
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 ?? basename3(input.path)
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 [claude, adapter] = await Promise.all([
95454
- inspect(CLAUDE_CLI_COMMAND, null, { path, run: run3 }),
95455
- inspect(ACP_ADAPTER_COMMAND, ACP_ADAPTER_INSTALL, { path, run: run3, preferred: installedAt })
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
- const key = env[ANTHROPIC_API_KEY_ENV];
95458
- return { claude, adapter, apiKeyInEnv: key !== void 0 && key.trim() !== "" };
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 adapterBinaryPath(dir) {
95466
- return join18(dir, "node_modules", ".bin", ACP_ADAPTER_COMMAND);
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
- const binary = adapterBinaryPath(dir);
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: ACP_ADAPTER_PINNED_VERSION, alreadyInstalled: true };
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(dir, { recursive: true });
95744
+ await mkdir8(target, { recursive: true });
95478
95745
  const outcome = await run3(
95479
95746
  "npm",
95480
95747
  [
95481
95748
  "install",
95482
95749
  "--prefix",
95483
- dir,
95750
+ target,
95484
95751
  "--no-fund",
95485
95752
  "--no-audit",
95486
- `${ACP_ADAPTER_PACKAGE}@${ACP_ADAPTER_PINNED_VERSION}`
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 ${ACP_ADAPTER_PACKAGE} pode ter mudado de layout`
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: ACP_ADAPTER_PINNED_VERSION, alreadyInstalled: false };
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({ installedAt: adapterBinaryPath(join19(ctx.config.stateDir, ADAPTERS_DIR_NAME)) })
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: join19(ctx.config.stateDir, ADAPTERS_DIR_NAME) })
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 ?? ACP_ADAPTER_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 ?? ACP_ADAPTER_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();