@aspectlylabs/mikka 0.0.140 → 0.0.142

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/main.js CHANGED
@@ -3780,7 +3780,7 @@ import { join as join5 } from "node:path";
3780
3780
  // package.json
3781
3781
  var package_default = {
3782
3782
  name: "@mikka/daemon",
3783
- version: "0.0.140",
3783
+ version: "0.0.142",
3784
3784
  description: "Local Mikka daemon: runs agents through the user's own CLIs",
3785
3785
  type: "module",
3786
3786
  private: true,
@@ -3840,7 +3840,7 @@ var package_default = {
3840
3840
  engines: {
3841
3841
  node: ">=24"
3842
3842
  },
3843
- gitHead: "0a6a350447fd54013e1439ccb59443a44a123d74"
3843
+ gitHead: "eba283d612639ecf9c4654327153c5118a2c27bb"
3844
3844
  };
3845
3845
 
3846
3846
  // src/paths.ts
@@ -4708,6 +4708,15 @@ var setupRecipeSchema = z15.object({
4708
4708
  /** Approved once per repo by the user; unapproved recipes never run. */
4709
4709
  approved: z15.boolean()
4710
4710
  });
4711
+ var credentialKey = z15.string().min(1).max(4096).refine((value) => !/[\r\n\0]/.test(value));
4712
+ var credentialBaseUrl = z15.url().max(500).refine((value) => {
4713
+ const url = new URL(value);
4714
+ return url.protocol === "https:" && !url.username && !url.password && !url.search && !url.hash;
4715
+ });
4716
+ var runModelCredentialSchema = z15.discriminatedUnion("kind", [
4717
+ z15.object({ kind: z15.literal("api_key"), key: credentialKey }).strict(),
4718
+ z15.object({ kind: z15.literal("provider"), protocol: z15.enum(["anthropic", "openai"]), baseUrl: credentialBaseUrl, key: credentialKey }).strict()
4719
+ ]);
4711
4720
  var deviceRepoSchema = z15.object({
4712
4721
  repoId: id2,
4713
4722
  name: z15.string().max(500),
@@ -4755,6 +4764,12 @@ var runAssignmentSchema = z15.object({
4755
4764
  consumerId: z15.uuid(),
4756
4765
  route: z15.enum(["codex_cli", "claude_code_cli"])
4757
4766
  }).strict().optional(),
4767
+ /**
4768
+ * The run's own API key, opened for this claim only. Never with
4769
+ * `subscriptionBinding`. The daemon keeps it in memory and hands it only
4770
+ * to the run's Agent process or container; it is never journaled.
4771
+ */
4772
+ modelCredential: runModelCredentialSchema.optional(),
4758
4773
  /** Local host authority comes only from the server's owner-bound Bot run. */
4759
4774
  localComputer: z15.object({
4760
4775
  botId: z15.uuid(),
@@ -4872,7 +4887,7 @@ var runAssignmentSchema = z15.object({
4872
4887
  skillReceipt: skillAssignmentReceiptSchema.optional(),
4873
4888
  /** Frozen by the server benchmark ledger; never extracted from model output. */
4874
4889
  benchmark: runBenchmarkContext.optional()
4875
- });
4890
+ }).refine((value) => !(value.modelCredential && value.subscriptionBinding), { message: "A run has one model credential", path: ["modelCredential"] });
4876
4891
  var runPatchSchema = z15.object({
4877
4892
  state: z15.enum([
4878
4893
  "queued",
@@ -6445,15 +6460,15 @@ sandbox_mode = "read-only"
6445
6460
  // src/main.ts
6446
6461
  import { randomBytes as randomBytes4, randomUUID as randomUUID9 } from "node:crypto";
6447
6462
  import {
6448
- chmodSync as chmodSync5,
6463
+ chmodSync as chmodSync6,
6449
6464
  existsSync as existsSync9,
6450
- mkdirSync as mkdirSync18,
6465
+ mkdirSync as mkdirSync19,
6451
6466
  readFileSync as readFileSync12,
6452
- rmSync as rmSync20,
6467
+ rmSync as rmSync21,
6453
6468
  realpathSync as realpathSync12
6454
6469
  } from "node:fs";
6455
6470
  import { arch as arch2, hostname, platform as platform3 } from "node:os";
6456
- import { join as join31 } from "node:path";
6471
+ import { join as join32 } from "node:path";
6457
6472
  import { fileURLToPath as fileURLToPath4 } from "node:url";
6458
6473
 
6459
6474
  // src/adapters/claude.ts
@@ -7330,7 +7345,8 @@ var ClaudeAdapter = class {
7330
7345
  },
7331
7346
  ...mode === "new" ? { sessionId } : { resume: sessionId },
7332
7347
  ...options.model ? { model: options.model } : {},
7333
- ...outerLocal ? { spawnClaudeCodeProcess: spawnOuter } : this.options.spawnHosted && externalAuth ? { spawnClaudeCodeProcess: (options2) => this.options.spawnHosted(options2, nativeToken) } : binary || externalAuth ? { spawnClaudeCodeProcess: spawnBinary(binary, nativeToken) } : {},
7348
+ // A hosted runtime always spawns into its own container; an API-key run there holds the placeholder too.
7349
+ ...outerLocal ? { spawnClaudeCodeProcess: spawnOuter } : this.options.spawnHosted ? { spawnClaudeCodeProcess: (options2) => this.options.spawnHosted(options2, nativeToken ?? NATIVE_AUTH_PLACEHOLDER) } : binary || externalAuth ? { spawnClaudeCodeProcess: spawnBinary(binary, nativeToken) } : {},
7334
7350
  stderr: (data) => {
7335
7351
  if (!externalAuth) stderr = (stderr + data).slice(-STDERR_TAIL2 * 4);
7336
7352
  }
@@ -10538,7 +10554,10 @@ function withoutTaskEnv(assignment) {
10538
10554
 
10539
10555
  // src/runs/journal.ts
10540
10556
  function persisted(entries) {
10541
- return Object.fromEntries(Object.entries(entries).map(([id4, entry]) => [id4, { ...entry, assignment: withoutTaskEnv(entry.assignment) }]));
10557
+ return Object.fromEntries(Object.entries(entries).map(([id4, entry]) => {
10558
+ const { modelCredential, ...assignment } = withoutTaskEnv(entry.assignment);
10559
+ return [id4, { ...entry, assignment, ...modelCredential ? { credentialWithheld: true } : {} }];
10560
+ }));
10542
10561
  }
10543
10562
  var RunJournal = class {
10544
10563
  constructor(file) {
@@ -11303,10 +11322,10 @@ var RunSupervisor = class {
11303
11322
  }
11304
11323
  const existing = this.deps.journal.get(assignment.runId);
11305
11324
  if (existing) return this.summary(existing);
11306
- const masker = createRunMasker((assignment.taskSecretNames ?? []).flatMap((name) => {
11325
+ const masker = createRunMasker([...(assignment.taskSecretNames ?? []).flatMap((name) => {
11307
11326
  const value = assignment.taskEnv?.[name];
11308
11327
  return typeof value === "string" ? [{ name, value }] : [];
11309
- }));
11328
+ }), ...assignment.modelCredential ? [{ name: "MODEL_API_KEY", value: assignment.modelCredential.key }] : []]);
11310
11329
  if (masker) this.maskers.set(assignment.runId, masker);
11311
11330
  let repo;
11312
11331
  try {
@@ -11406,6 +11425,10 @@ var RunSupervisor = class {
11406
11425
  }
11407
11426
  continue;
11408
11427
  }
11428
+ if (entry.credentialWithheld && !entry.assignment.modelCredential) {
11429
+ this.finish(entry.assignment.runId, "error", "The run stopped with the daemon; its API key is not kept. Retry the thread.");
11430
+ continue;
11431
+ }
11409
11432
  if (entry.assignment.leaseExpiresAt)
11410
11433
  this.leases.set(entry.assignment.runId, Date.now() + DEFAULT_LEASE_MS);
11411
11434
  this.launch(entry.assignment.runId, repo);
@@ -11983,7 +12006,9 @@ var RunSupervisor = class {
11983
12006
  } : void 0,
11984
12007
  fixture: assignment.fixture,
11985
12008
  model: assignment.model,
11986
- mailbox
12009
+ mailbox,
12010
+ // In memory only: the adapter hands it to the run's CLI environment.
12011
+ credential: assignment.modelCredential
11987
12012
  };
11988
12013
  const resuming = entry.sessionId !== null && adapter.capabilities.supportsResume;
11989
12014
  const inEarlierWorktree = continued !== null || entry.worktree !== null && resolve11(entry.worktree) !== resolve11(this.deps.worktrees.pathFor(runId));
@@ -12008,8 +12033,9 @@ var RunSupervisor = class {
12008
12033
  const previousExecution = entry.execution;
12009
12034
  const previousRuntime = entry.runtime;
12010
12035
  let session;
12036
+ if (assignment.subscriptionBinding && assignment.modelCredential) throw new Error("A run has one model credential");
12011
12037
  try {
12012
- session = assignment.subscriptionBinding ? this.deps.startSubscription ? await this.deps.startSubscription(assignment, { ...options, prompt: assignment.prompt }) : (() => {
12038
+ session = assignment.modelCredential && this.deps.startCredential ? await this.deps.startCredential(assignment, { ...options, prompt: assignment.prompt }) : assignment.subscriptionBinding ? this.deps.startSubscription ? await this.deps.startSubscription(assignment, { ...options, prompt: assignment.prompt }) : (() => {
12013
12039
  throw new Error("subscription_hosted_runtime_unavailable");
12014
12040
  })() : resuming ? await adapter.resume(entry.sessionId, options) : await (async () => {
12015
12041
  if (assignment.requireSessionResume)
@@ -12425,16 +12451,141 @@ import { randomUUID as randomUUID7 } from "node:crypto";
12425
12451
  import {
12426
12452
  existsSync as existsSync8,
12427
12453
  lstatSync as lstatSync10,
12428
- mkdirSync as mkdirSync17,
12429
- mkdtempSync as mkdtempSync8,
12454
+ mkdirSync as mkdirSync18,
12455
+ mkdtempSync as mkdtempSync9,
12430
12456
  readdirSync as readdirSync9,
12431
12457
  realpathSync as realpathSync11,
12432
- rmSync as rmSync19
12458
+ rmSync as rmSync20
12433
12459
  } from "node:fs";
12434
12460
  import { createRequire } from "node:module";
12435
- import { delimiter as delimiter3, dirname as dirname15, join as join30 } from "node:path";
12461
+ import { delimiter as delimiter3, dirname as dirname15, join as join31 } from "node:path";
12436
12462
  import { fileURLToPath as fileURLToPath3 } from "node:url";
12437
12463
  import { promisify as promisify6 } from "node:util";
12464
+
12465
+ // src/api-key-broker.ts
12466
+ import { chmodSync as chmodSync4, mkdirSync as mkdirSync17, mkdtempSync as mkdtempSync8, rmSync as rmSync19 } from "node:fs";
12467
+ import { createServer as createServer4 } from "node:http";
12468
+ import { join as join30 } from "node:path";
12469
+ import { once as once2 } from "node:events";
12470
+ var DEFAULT_BASE = { anthropic: "https://api.anthropic.com", openai: "https://api.openai.com/v1" };
12471
+ var PATHS = {
12472
+ anthropic: /* @__PURE__ */ new Set(["/v1/messages", "/v1/messages/count_tokens"]),
12473
+ openai: /* @__PURE__ */ new Set(["/responses", "/responses/compact"])
12474
+ };
12475
+ var FORWARD = ["content-type", "accept", "anthropic-version", "anthropic-beta", "openai-beta", "user-agent"];
12476
+ var RETURN = ["content-type", "retry-after", "request-id", "x-request-id"];
12477
+ var MAX_BODY = 32 * 1024 * 1024;
12478
+ async function createApiKeyBroker(host) {
12479
+ const { credential, protocol } = host;
12480
+ if (credential.kind === "provider" && credential.protocol !== protocol) throw new Error("api_key_broker_unavailable");
12481
+ const base = new URL(credential.kind === "provider" ? credential.baseUrl : DEFAULT_BASE[protocol]);
12482
+ if (base.protocol !== "https:" || base.username || base.password || !credential.key || /[\r\n\0]/.test(credential.key))
12483
+ throw new Error("api_key_broker_unavailable");
12484
+ const origin = base.href.replace(/\/+$/, "");
12485
+ let key2 = credential.key;
12486
+ const send = host.fetch ?? fetch;
12487
+ await host.check();
12488
+ mkdirSync17(host.root, { recursive: true, mode: 448 });
12489
+ const dir = mkdtempSync8(join30(host.root, "api-key-broker-"));
12490
+ chmodSync4(dir, 448);
12491
+ const socket = join30(dir, "broker.sock");
12492
+ const pending = /* @__PURE__ */ new Set();
12493
+ let halted = false;
12494
+ const server = createServer4(async (request, response) => {
12495
+ const abort = new AbortController();
12496
+ pending.add(abort);
12497
+ response.on("close", () => {
12498
+ if (!response.writableFinished) abort.abort();
12499
+ });
12500
+ try {
12501
+ const url = new URL(request.url ?? "/", "http://broker");
12502
+ if (halted || !key2 || request.method !== "POST" || (request.url ?? "").length > 256 || !PATHS[protocol].has(url.pathname) || url.search && url.search !== "?beta=true") {
12503
+ response.writeHead(404, { "content-type": "application/json", "cache-control": "no-store" }).end('{"error":"not_found"}');
12504
+ return;
12505
+ }
12506
+ const chunks = [];
12507
+ let length = 0;
12508
+ for await (const part of request) {
12509
+ length += part.length;
12510
+ if (length > MAX_BODY) throw new Error("request refused");
12511
+ chunks.push(part);
12512
+ }
12513
+ await host.check();
12514
+ if (halted || !key2 || abort.signal.aborted) throw new Error("request refused");
12515
+ const headers = {};
12516
+ for (const name of FORWARD) {
12517
+ const value = request.headers[name];
12518
+ if (typeof value === "string") headers[name] = value;
12519
+ }
12520
+ if (headers["anthropic-beta"]) {
12521
+ const betas = headers["anthropic-beta"].split(",").map((item) => item.trim()).filter((item) => item && !item.startsWith("oauth-"));
12522
+ if (betas.length) headers["anthropic-beta"] = betas.join(",");
12523
+ else delete headers["anthropic-beta"];
12524
+ }
12525
+ if (protocol === "anthropic") headers["x-api-key"] = key2;
12526
+ else headers.authorization = `Bearer ${key2}`;
12527
+ const upstream = await send(origin + url.pathname + url.search, {
12528
+ method: "POST",
12529
+ headers,
12530
+ body: Buffer.concat(chunks),
12531
+ redirect: "error",
12532
+ signal: abort.signal,
12533
+ cache: "no-store"
12534
+ });
12535
+ const returned = { "cache-control": "no-store" };
12536
+ for (const name of RETURN) {
12537
+ const value = upstream.headers.get(name);
12538
+ if (value) returned[name] = value;
12539
+ }
12540
+ response.writeHead(upstream.status, returned);
12541
+ if (upstream.body) {
12542
+ const reader = upstream.body.getReader();
12543
+ try {
12544
+ for (; ; ) {
12545
+ const { done, value } = await reader.read();
12546
+ if (done) break;
12547
+ if (abort.signal.aborted) throw new Error("request cancelled");
12548
+ if (!response.write(value)) await once2(response, "drain", { signal: abort.signal });
12549
+ }
12550
+ } finally {
12551
+ await reader.cancel().catch(() => void 0);
12552
+ reader.releaseLock();
12553
+ }
12554
+ }
12555
+ response.end();
12556
+ } catch {
12557
+ abort.abort();
12558
+ if (response.headersSent) response.destroy();
12559
+ else response.writeHead(502, { "content-type": "application/json", "cache-control": "no-store" }).end(JSON.stringify({ type: "error", error: { type: "api_key_unavailable", message: "API key unavailable" } }));
12560
+ } finally {
12561
+ pending.delete(abort);
12562
+ }
12563
+ });
12564
+ try {
12565
+ await new Promise((resolve13, reject) => {
12566
+ server.once("error", reject);
12567
+ server.listen(socket, resolve13);
12568
+ });
12569
+ chmodSync4(socket, 384);
12570
+ } catch {
12571
+ key2 = void 0;
12572
+ rmSync19(dir, { recursive: true, force: true });
12573
+ throw new Error("api_key_broker_unavailable");
12574
+ }
12575
+ return {
12576
+ socket,
12577
+ async close() {
12578
+ halted = true;
12579
+ key2 = void 0;
12580
+ for (const abort of pending) abort.abort();
12581
+ server.closeAllConnections();
12582
+ await new Promise((resolve13) => server.close(() => resolve13()));
12583
+ rmSync19(dir, { recursive: true, force: true });
12584
+ }
12585
+ };
12586
+ }
12587
+
12588
+ // src/subscription-hosted-runner.ts
12438
12589
  var exec3 = promisify6(execFile6);
12439
12590
  var image = HOSTED_NATIVE_IMAGE;
12440
12591
  var bootstrap = fileURLToPath3(
@@ -12459,8 +12610,8 @@ async function createHostedBotRunner(host) {
12459
12610
  }).catch(() => {
12460
12611
  throw unavailable3();
12461
12612
  });
12462
- const resources = join30(host.stateDir, "native-resources");
12463
- mkdirSync17(resources, { recursive: true, mode: 448 });
12613
+ const resources = join31(host.stateDir, "native-resources");
12614
+ mkdirSync18(resources, { recursive: true, mode: 448 });
12464
12615
  const removeResource = async (file, record) => {
12465
12616
  if (!/^mikka-native-(?:verify-)?[a-f0-9-]{36}$/.test(record.name) || !/^[a-f0-9-]{36}$/.test(record.nonce))
12466
12617
  throw unavailable3();
@@ -12480,9 +12631,9 @@ async function createHostedBotRunner(host) {
12480
12631
  if (record.state === "created" && /No such (?:container|object)/i.test(
12481
12632
  String(error.stderr)
12482
12633
  )) {
12483
- if (record.assets?.startsWith(join30(host.stateDir, "native-verify-git-")))
12484
- rmSync19(record.assets, { recursive: true, force: true });
12485
- rmSync19(file, { force: true });
12634
+ if (record.assets?.startsWith(join31(host.stateDir, "native-verify-git-")))
12635
+ rmSync20(record.assets, { recursive: true, force: true });
12636
+ rmSync20(file, { force: true });
12486
12637
  return;
12487
12638
  }
12488
12639
  throw new Error("native_runtime_cleanup_unconfirmed");
@@ -12491,13 +12642,13 @@ async function createHostedBotRunner(host) {
12491
12642
  if (labels?.["com.mikka.native.resource"] !== record.nonce || record.id && record.id !== id4)
12492
12643
  throw unavailable3();
12493
12644
  await exec3("docker", ["rm", "-f", id4], { env: bareEnv, timeout: 3e4 });
12494
- if (record.assets?.startsWith(join30(host.stateDir, "native-verify-git-")))
12495
- rmSync19(record.assets, { recursive: true, force: true });
12496
- rmSync19(file, { force: true });
12645
+ if (record.assets?.startsWith(join31(host.stateDir, "native-verify-git-")))
12646
+ rmSync20(record.assets, { recursive: true, force: true });
12647
+ rmSync20(file, { force: true });
12497
12648
  };
12498
12649
  for (const name of readdirSync9(resources))
12499
12650
  if (name.endsWith(".json")) {
12500
- const file = join30(resources, name);
12651
+ const file = join31(resources, name);
12501
12652
  const record = readJson(file);
12502
12653
  if (!record) throw unavailable3();
12503
12654
  await removeResource(file, record);
@@ -12508,7 +12659,7 @@ async function createHostedBotRunner(host) {
12508
12659
  const bundled = createRequire(
12509
12660
  require2.resolve("@anthropic-ai/claude-agent-sdk")
12510
12661
  );
12511
- claude ??= join30(
12662
+ claude ??= join31(
12512
12663
  dirname15(
12513
12664
  bundled.resolve(
12514
12665
  "@anthropic-ai/claude-agent-sdk-linux-x64/package.json"
@@ -12518,7 +12669,7 @@ async function createHostedBotRunner(host) {
12518
12669
  );
12519
12670
  } catch {
12520
12671
  }
12521
- const codex = host.binaries?.codex ?? (process.env.PATH ?? "").split(delimiter3).map((dir) => join30(dir, "codex")).find((path) => existsSync8(path));
12672
+ const codex = host.binaries?.codex ?? (process.env.PATH ?? "").split(delimiter3).map((dir) => join31(dir, "codex")).find((path) => existsSync8(path));
12522
12673
  const binaries = { codex, claude_code: claude };
12523
12674
  for (const kind of ["codex", "claude_code"])
12524
12675
  if (binaries[kind]) {
@@ -12532,12 +12683,12 @@ async function createHostedBotRunner(host) {
12532
12683
  if (!binaries.codex && !binaries.claude_code) throw unavailable3();
12533
12684
  const command = async (worktree, script, readOnly, timeoutMs, signal) => {
12534
12685
  const name = `mikka-native-verify-${randomUUID7()}`;
12535
- const file = join30(resources, name + ".json");
12686
+ const file = join31(resources, name + ".json");
12536
12687
  const resource = { name, nonce: randomUUID7(), state: "creating" };
12537
12688
  const timing = { createMs: 0, runMs: 0 };
12538
12689
  let phase = "create";
12539
12690
  let began = performance.now();
12540
- const gitCopy = mkdtempSync8(join30(host.stateDir, "native-verify-git-"));
12691
+ const gitCopy = mkdtempSync9(join31(host.stateDir, "native-verify-git-"));
12541
12692
  resource.assets = gitCopy;
12542
12693
  try {
12543
12694
  const cwd = realpathSync11(worktree);
@@ -12553,7 +12704,7 @@ async function createHostedBotRunner(host) {
12553
12704
  "--no-checkout",
12554
12705
  "--",
12555
12706
  cwd,
12556
- join30(gitCopy, "repo")
12707
+ join31(gitCopy, "repo")
12557
12708
  ],
12558
12709
  {
12559
12710
  env: {
@@ -12568,8 +12719,8 @@ async function createHostedBotRunner(host) {
12568
12719
  maxBuffer: 1024 * 1024
12569
12720
  }
12570
12721
  );
12571
- const safeGit = join30(gitCopy, "repo", ".git");
12572
- const protectedGit = lstatSync10(join30(cwd, ".git")).isFile() ? join30(cwd, ".git") : safeGit;
12722
+ const safeGit = join31(gitCopy, "repo", ".git");
12723
+ const protectedGit = lstatSync10(join31(cwd, ".git")).isFile() ? join31(cwd, ".git") : safeGit;
12573
12724
  writeJsonAtomic(file, resource);
12574
12725
  const created = await exec3(
12575
12726
  "docker",
@@ -12657,7 +12808,7 @@ async function createHostedBotRunner(host) {
12657
12808
  };
12658
12809
  } finally {
12659
12810
  if (existsSync8(file)) await removeResource(file, resource);
12660
- else rmSync19(gitCopy, { recursive: true, force: true });
12811
+ else rmSync20(gitCopy, { recursive: true, force: true });
12661
12812
  }
12662
12813
  };
12663
12814
  return {
@@ -12668,12 +12819,14 @@ async function createHostedBotRunner(host) {
12668
12819
  async start(assignment, options) {
12669
12820
  const gate = requireRunToolApproval(options.runId, options.toolApproval);
12670
12821
  const selected = assignment.subscriptionBinding;
12822
+ const apiKey = assignment.modelCredential;
12671
12823
  const kind = assignment.harness;
12672
- if (!selected || options.runId !== assignment.runId || kind !== "codex" && kind !== "claude_code" || !binaries[kind] || selected.consumerId.length !== 36 || selected.route !== (kind === "codex" ? "codex_cli" : "claude_code_cli") || options.continueSession)
12824
+ const protocol = kind === "codex" ? "openai" : "anthropic";
12825
+ if (Boolean(selected) === Boolean(apiKey) || options.runId !== assignment.runId || kind !== "codex" && kind !== "claude_code" || !binaries[kind] || selected && (selected.consumerId.length !== 36 || selected.route !== (kind === "codex" ? "codex_cli" : "claude_code_cli")) || apiKey?.kind === "provider" && apiKey.protocol !== protocol || options.continueSession)
12673
12826
  throw unavailable3();
12674
12827
  const name = `mikka-native-${randomUUID7()}`;
12675
12828
  const nonce = randomUUID7();
12676
- const resourceFile = join30(resources, name + ".json");
12829
+ const resourceFile = join31(resources, name + ".json");
12677
12830
  const resource = { name, nonce, state: "creating" };
12678
12831
  const lifetime = new AbortController();
12679
12832
  let closed = false;
@@ -12731,25 +12884,29 @@ async function createHostedBotRunner(host) {
12731
12884
  });
12732
12885
  try {
12733
12886
  await live();
12734
- const context = () => host.link.nativeContext(assignment.deviceId, assignment.runId, lifetime.signal);
12735
- const first = nativeBrokerContextSchema.parse(await context());
12736
- await live();
12737
- if (first.route !== selected.route || first.modelId !== options.model)
12738
- throw unavailable3();
12739
- const revision = first.revision;
12740
- const check = async () => {
12741
- await live();
12742
- const current = await context();
12887
+ const subscription = selected ? await (async () => {
12888
+ const context = () => host.link.nativeContext(assignment.deviceId, assignment.runId, lifetime.signal);
12889
+ const first = nativeBrokerContextSchema.parse(await context());
12743
12890
  await live();
12744
- if (current.revision !== revision || current.accountId !== first.accountId || current.route !== first.route || current.modelId !== first.modelId)
12891
+ if (first.route !== selected.route || first.modelId !== options.model)
12745
12892
  throw unavailable3();
12746
- return current;
12747
- };
12893
+ const revision = first.revision;
12894
+ const check2 = async () => {
12895
+ await live();
12896
+ const current = await context();
12897
+ await live();
12898
+ if (current.revision !== revision || current.accountId !== first.accountId || current.route !== first.route || current.modelId !== first.modelId)
12899
+ throw unavailable3();
12900
+ return current;
12901
+ };
12902
+ return { first, revision, check: check2 };
12903
+ })() : void 0;
12904
+ const check = subscription?.check ?? live;
12748
12905
  const paths = runPaths(host.stateDir, assignment.runId);
12749
- mkdirSync17(paths.runDir, { recursive: true, mode: 448 });
12750
- const shadow = join30(paths.runDir, "runtime-git");
12751
- const emptyHome = join30(paths.runDir, "git-bootstrap-home");
12752
- mkdirSync17(emptyHome, { recursive: true, mode: 448 });
12906
+ mkdirSync18(paths.runDir, { recursive: true, mode: 448 });
12907
+ const shadow = join31(paths.runDir, "runtime-git");
12908
+ const emptyHome = join31(paths.runDir, "git-bootstrap-home");
12909
+ mkdirSync18(emptyHome, { recursive: true, mode: 448 });
12753
12910
  if (!existsSync8(shadow))
12754
12911
  await exec3(
12755
12912
  "git",
@@ -12779,35 +12936,40 @@ async function createHostedBotRunner(host) {
12779
12936
  }
12780
12937
  );
12781
12938
  await live();
12782
- if (!lstatSync10(shadow).isDirectory() || !lstatSync10(join30(shadow, ".git")).isDirectory() || !realpathSync11(shadow).startsWith(realpathSync11(paths.runDir) + "/"))
12939
+ if (!lstatSync10(shadow).isDirectory() || !lstatSync10(join31(shadow, ".git")).isDirectory() || !realpathSync11(shadow).startsWith(realpathSync11(paths.runDir) + "/"))
12783
12940
  throw unavailable3();
12784
- const ledger = join30(
12941
+ const ledger = join31(
12785
12942
  host.stateDir,
12786
12943
  "native-ledgers",
12787
12944
  assignment.runId + ".json"
12788
12945
  );
12789
- const scope = {
12946
+ const scope = selected && subscription ? {
12790
12947
  runId: assignment.runId,
12791
12948
  bindingId: selected.bindingId,
12792
12949
  ownerId: selected.ownerId,
12793
12950
  workspaceId: selected.workspaceId,
12794
12951
  consumerId: selected.consumerId,
12795
12952
  route: selected.route,
12796
- revision,
12797
- accountId: first.accountId,
12798
- modelId: first.modelId
12799
- };
12800
- const previous = readJson(ledger);
12801
- if (existsSync8(ledger) && (!previous || JSON.stringify(previous.scope) !== JSON.stringify(scope) || typeof previous.sessionId !== "string"))
12953
+ revision: subscription.revision,
12954
+ accountId: subscription.first.accountId,
12955
+ modelId: subscription.first.modelId
12956
+ } : void 0;
12957
+ const previous = scope ? readJson(ledger) : void 0;
12958
+ if (scope && existsSync8(ledger) && (!previous || JSON.stringify(previous.scope) !== JSON.stringify(scope) || typeof previous.sessionId !== "string"))
12802
12959
  throw unavailable3();
12803
- mkdirSync17(dirname15(ledger), { recursive: true, mode: 448 });
12960
+ if (scope) mkdirSync18(dirname15(ledger), { recursive: true, mode: 448 });
12804
12961
  const record = async (sessionId) => {
12805
12962
  assertOpen();
12806
- writeJsonAtomic(ledger, { scope, sessionId });
12963
+ if (scope) writeJsonAtomic(ledger, { scope, sessionId });
12807
12964
  };
12808
- brokerStarting = createNativeSubscriptionBroker({
12809
- root: join30(host.stateDir, "native-brokers"),
12810
- revision,
12965
+ brokerStarting = apiKey ? createApiKeyBroker({
12966
+ root: join31(host.stateDir, "native-brokers"),
12967
+ protocol,
12968
+ credential: apiKey,
12969
+ check
12970
+ }) : createNativeSubscriptionBroker({
12971
+ root: join31(host.stateDir, "native-brokers"),
12972
+ revision: subscription.revision,
12811
12973
  check,
12812
12974
  worker: {
12813
12975
  nativeRequest: async (_revision, payload, signal) => {
@@ -12833,7 +12995,7 @@ async function createHostedBotRunner(host) {
12833
12995
  });
12834
12996
  const readyBroker = await brokerStarting;
12835
12997
  await live();
12836
- const githubCli = options.github ? (process.env.PATH ?? "").split(delimiter3).map((dir) => join30(dir, "gh")).find((file) => existsSync8(file)) : void 0;
12998
+ const githubCli = options.github ? (process.env.PATH ?? "").split(delimiter3).map((dir) => join31(dir, "gh")).find((file) => existsSync8(file)) : void 0;
12837
12999
  if (options.github && !githubCli) throw new Error("Install GitHub CLI (gh) in the cloud worker image");
12838
13000
  const githubDomains = options.github ? ["github.com", "api.github.com", new URL(options.github.endpoint).host] : [];
12839
13001
  const egressMaximum = runDomains(runPolicy(options.policy), [...options.networkAllow ?? [], ...githubDomains]);
@@ -12850,13 +13012,13 @@ async function createHostedBotRunner(host) {
12850
13012
  }
12851
13013
  const cwd = realpathSync11(options.cwd);
12852
13014
  const runDir = realpathSync11(paths.runDir);
12853
- const protectedGit = lstatSync10(join30(cwd, ".git")).isFile() ? join30(cwd, ".git") : join30(shadow, ".git");
13015
+ const protectedGit = lstatSync10(join31(cwd, ".git")).isFile() ? join31(cwd, ".git") : join31(shadow, ".git");
12854
13016
  if (!lstatSync10(runDir).isDirectory() || !runDir.startsWith(realpathSync11(host.stateDir) + "/runs/")) {
12855
13017
  await close();
12856
13018
  throw unavailable3();
12857
13019
  }
12858
13020
  const computer = assignment.computer;
12859
- if (computer && (!host.computerBroker || computer.ownerId !== selected.ownerId)) {
13021
+ if (computer && (!host.computerBroker || selected && computer.ownerId !== selected.ownerId)) {
12860
13022
  await close();
12861
13023
  throw unavailable3();
12862
13024
  }
@@ -12904,7 +13066,7 @@ async function createHostedBotRunner(host) {
12904
13066
  `${MCP_SERVER_SCRIPT}:${MCP_SERVER_SCRIPT}:ro`,
12905
13067
  ...githubCli ? ["-v", `${githubCli}:/usr/local/bin/mikka-gh:ro`, "-v", `${GITHUB_CREDENTIAL_SCRIPT}:${GITHUB_CREDENTIAL_SCRIPT}:ro`] : [],
12906
13068
  "-v",
12907
- `${join30(dirname15(MCP_SERVER_SCRIPT), "capture-copy.mjs")}:${join30(dirname15(MCP_SERVER_SCRIPT), "capture-copy.mjs")}:ro`,
13069
+ `${join31(dirname15(MCP_SERVER_SCRIPT), "capture-copy.mjs")}:${join31(dirname15(MCP_SERVER_SCRIPT), "capture-copy.mjs")}:ro`,
12908
13070
  "-v",
12909
13071
  `${readyBroker.socket}:/run/mikka-native.sock:ro`,
12910
13072
  ...computerGrant ? [
@@ -12947,13 +13109,13 @@ async function createHostedBotRunner(host) {
12947
13109
  }
12948
13110
  const spawnHosted = (config) => {
12949
13111
  assertOpen();
12950
- const home = join30(runDir, "runtime-home");
12951
- mkdirSync17(home, { recursive: true, mode: 448 });
13112
+ const home = join31(runDir, "runtime-home");
13113
+ mkdirSync18(home, { recursive: true, mode: 448 });
12952
13114
  const env = {
12953
13115
  ...config.env,
12954
13116
  HOME: kind === "claude_code" ? config.env.HOME ?? home : home,
12955
- PATH: `${options.github ? join30(runDir, "github", "bin") + ":" : ""}/usr/local/bin:/usr/bin:/bin`,
12956
- GIT_DIR: join30(shadow, ".git"),
13117
+ PATH: `${options.github ? join31(runDir, "github", "bin") + ":" : ""}/usr/local/bin:/usr/bin:/bin`,
13118
+ GIT_DIR: join31(shadow, ".git"),
12957
13119
  GIT_WORK_TREE: cwd,
12958
13120
  GIT_CONFIG_GLOBAL: "/dev/null",
12959
13121
  GIT_CONFIG_SYSTEM: "/dev/null",
@@ -12962,7 +13124,7 @@ async function createHostedBotRunner(host) {
12962
13124
  for (const [key2, value] of Object.entries(env))
12963
13125
  if (value !== void 0 && (/INFISICAL|E2B_API_KEY|REFRESH_TOKEN|^AWS_|^GOOGLE_|^OPENAI_|^ANTHROPIC_.*(?:KEY|TOKEN)|SECRET|PASSWORD/.test(
12964
13126
  key2
12965
- ) || key2 === "CLAUDE_CODE_OAUTH_TOKEN" && value !== placeholder))
13127
+ ) || (key2 === "CLAUDE_CODE_OAUTH_TOKEN" || key2 === "MIKKA_MODEL_API_KEY") && value !== placeholder))
12966
13128
  throw unavailable3();
12967
13129
  const task = withTaskEnv(env, assignment.taskEnv);
12968
13130
  if (task.dropped.length) console.warn(`run ${assignment.runId}: skipped reserved task variables ${task.dropped.join(", ")}`);
@@ -12972,7 +13134,7 @@ async function createHostedBotRunner(host) {
12972
13134
  signal: AbortSignal.any([lifetime.signal, ...config.signal ? [config.signal] : []])
12973
13135
  });
12974
13136
  child.stdin.write(
12975
- JSON.stringify({ kind, args: config.args, cwd, env: task.env, publicEgress: Boolean(egress), ...options.github ? { githubBin: join30(runDir, "github", "bin") } : {} }) + "\n"
13137
+ JSON.stringify({ kind, args: config.args, cwd, env: task.env, publicEgress: Boolean(egress), ...options.github ? { githubBin: join31(runDir, "github", "bin") } : {} }) + "\n"
12976
13138
  );
12977
13139
  return child;
12978
13140
  };
@@ -13001,19 +13163,27 @@ async function createHostedBotRunner(host) {
13001
13163
  };
13002
13164
  };
13003
13165
  try {
13004
- const adapter = kind === "codex" ? new CodexAdapter({
13166
+ const placeholderCredential = apiKey && kind === "codex" ? { kind: "provider", protocol: "openai", baseUrl: apiKey.kind === "provider" ? apiKey.baseUrl : "https://api.openai.com/v1", key: placeholder } : void 0;
13167
+ const adapter = apiKey ? kind === "codex" ? new CodexAdapter({ stateDir: host.stateDir, sandbox: blockedSandbox, spawnCli: codexSpawn, computerMcp }) : new ClaudeAdapter({
13168
+ stateDir: host.stateDir,
13169
+ computerMcp,
13170
+ spawnHosted: (config, token) => {
13171
+ if (token !== placeholder) throw unavailable3();
13172
+ return spawnHosted(config);
13173
+ }
13174
+ }) : kind === "codex" ? new CodexAdapter({
13005
13175
  stateDir: host.stateDir,
13006
13176
  sandbox: blockedSandbox,
13007
13177
  spawnCli: codexSpawn,
13008
13178
  onHostedSession: record,
13009
13179
  computerMcp,
13010
13180
  subscriptionAuth: {
13011
- accountId: first.accountId,
13181
+ accountId: subscription.first.accountId,
13012
13182
  verify: async () => {
13013
13183
  await check();
13014
13184
  },
13015
13185
  token: async () => {
13016
- const current = await check();
13186
+ const current = await subscription.check();
13017
13187
  return {
13018
13188
  accessToken: placeholder,
13019
13189
  chatgptAccountId: current.accountId,
@@ -13031,12 +13201,12 @@ async function createHostedBotRunner(host) {
13031
13201
  },
13032
13202
  subscriptionAuth: {
13033
13203
  brokered: true,
13034
- accountId: first.accountId,
13204
+ accountId: subscription.first.accountId,
13035
13205
  verify: async () => {
13036
13206
  await check();
13037
13207
  },
13038
13208
  token: async () => {
13039
- const current = await check();
13209
+ const current = await subscription.check();
13040
13210
  return {
13041
13211
  accessToken: placeholder,
13042
13212
  accountId: current.accountId,
@@ -13045,7 +13215,7 @@ async function createHostedBotRunner(host) {
13045
13215
  }
13046
13216
  }
13047
13217
  });
13048
- const runtimeOptions = { ...options, ...options.github ? { githubRuntime: { nodeBinary: "/usr/local/bin/node", ghBinary: "/usr/local/bin/mikka-gh" } } : {} };
13218
+ const runtimeOptions = { ...options, credential: placeholderCredential, ...options.github ? { githubRuntime: { nodeBinary: "/usr/local/bin/node", ghBinary: "/usr/local/bin/mikka-gh" } } : {} };
13049
13219
  const session = previous ? await adapter.resume(previous.sessionId, runtimeOptions) : await adapter.start(runtimeOptions);
13050
13220
  let checking = false;
13051
13221
  if (!closed) timer = setInterval(() => {
@@ -13107,8 +13277,8 @@ import {
13107
13277
  createHash as createHash9,
13108
13278
  timingSafeEqual as timingSafeEqual2
13109
13279
  } from "node:crypto";
13110
- import { chmodSync as chmodSync4 } from "node:fs";
13111
- import { createServer as createServer4 } from "node:net";
13280
+ import { chmodSync as chmodSync5 } from "node:fs";
13281
+ import { createServer as createServer5 } from "node:net";
13112
13282
 
13113
13283
  // src/computer/driver.mjs
13114
13284
  import { spawn as spawn6 } from "node:child_process";
@@ -13328,7 +13498,7 @@ var ComputerBroker = class {
13328
13498
  }
13329
13499
  async listen() {
13330
13500
  if (this.suspended) throw new Error("Broker suspended");
13331
- this.server = createServer4((socket) => {
13501
+ this.server = createServer5((socket) => {
13332
13502
  this.sockets.add(socket);
13333
13503
  socket.on("close", () => this.sockets.delete(socket));
13334
13504
  let text3 = "", submitted = false;
@@ -13371,7 +13541,7 @@ var ComputerBroker = class {
13371
13541
  else this.server.listen(this.socketPath, resolve13);
13372
13542
  });
13373
13543
  if (this.transport === "tcp") this.socketPath = `tcp://127.0.0.1:${this.server.address().port}`;
13374
- else chmodSync4(this.socketPath, 384);
13544
+ else chmodSync5(this.socketPath, 384);
13375
13545
  }
13376
13546
  request(token, value, signal) {
13377
13547
  const grant = this.grants.get(token);
@@ -13617,6 +13787,9 @@ function loadDeviceFile(raw) {
13617
13787
  return file;
13618
13788
  }
13619
13789
  var HARNESS_RESCAN_MS = 5 * 6e4;
13790
+ function credentialOwner(assignment) {
13791
+ return assignment.subscriptionBinding?.ownerId ?? (assignment.modelCredential ? assignment.computer?.ownerId : void 0);
13792
+ }
13620
13793
  var SANDBOX_REPAIR_RETRY_MS = 60 * 6e4;
13621
13794
  function missingToolsUnchanged(issue) {
13622
13795
  return !issue?.missing.length || missingSandboxTools().length === issue.missing.length;
@@ -13692,8 +13865,8 @@ var Daemon = class {
13692
13865
  }
13693
13866
  async startInternal() {
13694
13867
  const { paths } = this;
13695
- mkdirSync18(paths.dataDir, { recursive: true, mode: 448 });
13696
- if (process.platform !== "win32") chmodSync5(paths.dataDir, 448);
13868
+ mkdirSync19(paths.dataDir, { recursive: true, mode: 448 });
13869
+ if (process.platform !== "win32") chmodSync6(paths.dataDir, 448);
13697
13870
  this.releaseLock = await acquireLock(
13698
13871
  paths.dataDir,
13699
13872
  paths.lock,
@@ -13706,7 +13879,7 @@ var Daemon = class {
13706
13879
  if (this.options.credential)
13707
13880
  this.saveDevice({ credential: this.options.credential });
13708
13881
  this.journal = new RunJournal(paths.journal);
13709
- this.captureUploader = new CaptureUploader((runId) => join31(runPaths(paths.adapterState, runId).runDir, CAPTURES_DIR), this.options.link, this.log, join31(paths.dataDir, "capture-pending"));
13882
+ this.captureUploader = new CaptureUploader((runId) => join32(runPaths(paths.adapterState, runId).runDir, CAPTURES_DIR), this.options.link, this.log, join32(paths.dataDir, "capture-pending"));
13710
13883
  this.outbox = new Outbox(
13711
13884
  paths.outbox,
13712
13885
  this.options.link,
@@ -13723,7 +13896,7 @@ var Daemon = class {
13723
13896
  if (this.localComputerDriver && this.options.link.syncComputerReviews && this.options.link.localComputerAuthority) {
13724
13897
  this.localComputerBroker = new ComputerBroker({
13725
13898
  driver: this.localComputerDriver,
13726
- socketPath: join31(paths.dataDir, "local-computer.sock"),
13899
+ socketPath: join32(paths.dataDir, "local-computer.sock"),
13727
13900
  transport: "tcp",
13728
13901
  permitted: (runId) => {
13729
13902
  const assignment = this.journal.get(runId)?.assignment;
@@ -13756,7 +13929,7 @@ var Daemon = class {
13756
13929
  const liveComputer = (runId, display) => {
13757
13930
  const entry = this.journal.get(runId);
13758
13931
  const computer = entry?.assignment.computer;
13759
- if (this.stopped || !this.registered || !computer || computer.display !== display || computer.ownerId !== entry?.assignment.subscriptionBinding?.ownerId || !this.supervisor.computerRunAllowed(runId)) throw new Error("Cloud computer run unavailable");
13932
+ if (this.stopped || !this.registered || !computer || computer.display !== display || !entry || computer.ownerId !== credentialOwner(entry.assignment) || !this.supervisor.computerRunAllowed(runId)) throw new Error("Cloud computer run unavailable");
13760
13933
  return computer;
13761
13934
  };
13762
13935
  const executeOrdinary = async (current, call, display, runId, assertLive) => {
@@ -13765,7 +13938,7 @@ var Daemon = class {
13765
13938
  };
13766
13939
  this.cloudComputerBroker = new ComputerBroker({
13767
13940
  driver,
13768
- socketPath: join31(paths.dataDir, "computer.sock"),
13941
+ socketPath: join32(paths.dataDir, "computer.sock"),
13769
13942
  permitted: (runId) => {
13770
13943
  const entry = this.journal.get(runId);
13771
13944
  return Boolean(!this.stopped && this.registered && entry?.assignment.computer && this.supervisor?.computerRunAllowed(runId));
@@ -13833,6 +14006,7 @@ var Daemon = class {
13833
14006
  worktrees: this.worktrees,
13834
14007
  adapters,
13835
14008
  startSubscription: hosted?.start ?? this.localSubscriptions()?.start,
14009
+ startCredential: hosted?.start,
13836
14010
  prepareRunMailbox: this.options.link.prepareRunMailbox?.bind(this.options.link),
13837
14011
  prepareRunToolApproval: this.options.link.prepareRunToolApproval?.bind(this.options.link),
13838
14012
  verifyRunPolicy: this.options.link.verifyRunPolicy ? async (assignment, signal) => {
@@ -13892,7 +14066,7 @@ var Daemon = class {
13892
14066
  worktree,
13893
14067
  tree,
13894
14068
  github: { ...assignment.github, runToken: assignment.mcp.token, issueId: assignment.issueId },
13895
- privateDir: join31(paths.adapterState, "pids"),
14069
+ privateDir: join32(paths.adapterState, "pids"),
13896
14070
  ...hosted ? { hosted: true } : {},
13897
14071
  env: githubReceiptEnvironment()
13898
14072
  }) : publishRun({
@@ -14252,11 +14426,11 @@ var Daemon = class {
14252
14426
  return runVerify(sandbox, {
14253
14427
  command,
14254
14428
  worktree: realpathSync12(worktree),
14255
- tmpRoot: join31(this.paths.adapterState, "verify", runId),
14429
+ tmpRoot: join32(this.paths.adapterState, "verify", runId),
14256
14430
  privateDir: paths.privateDir,
14257
14431
  pidFile: paths.pidFile.replace(/\.pgid$/, ".verify.pgid"),
14258
14432
  networkAllow: repo.networkAllow,
14259
- env: { ...buildChildEnv(), MIKKA_HANDOFF_FILE: join31(paths.runDir, "handoff.json") },
14433
+ env: { ...buildChildEnv(), MIKKA_HANDOFF_FILE: join32(paths.runDir, "handoff.json") },
14260
14434
  timeoutMs,
14261
14435
  signal,
14262
14436
  readOnly
@@ -14441,7 +14615,7 @@ var Daemon = class {
14441
14615
  setImmediate(() => {
14442
14616
  void this.stop().then(() => uninstallService(() => {
14443
14617
  this.saveDevice({ credential: void 0 });
14444
- rmSync20(this.paths.secret, { force: true });
14618
+ rmSync21(this.paths.secret, { force: true });
14445
14619
  })).catch(() => {
14446
14620
  this.options.onDrained?.();
14447
14621
  });
@@ -14559,7 +14733,7 @@ var Daemon = class {
14559
14733
  throw new DaemonError(DAEMON_RPC_ERRORS.unauthorized, message);
14560
14734
  }
14561
14735
  }
14562
- if (assignment.computer && (!this.cloudComputerBroker || !assignment.subscriptionBinding || assignment.computer.ownerId !== assignment.subscriptionBinding.ownerId)) throw new Error("Cloud computer runtime unavailable");
14736
+ if (assignment.computer && (!this.cloudComputerBroker || assignment.computer.ownerId !== credentialOwner(assignment))) throw new Error("Cloud computer runtime unavailable");
14563
14737
  return await this.supervisor.start(assignment);
14564
14738
  }
14565
14739
  /**
@@ -14766,7 +14940,7 @@ var Daemon = class {
14766
14940
  if (!job || controller.signal.aborted) return;
14767
14941
  await runNativeCoordinatorJob(job, {
14768
14942
  deviceId,
14769
- stateDir: join31(this.paths.dataDir, "coordinator"),
14943
+ stateDir: join32(this.paths.dataDir, "coordinator"),
14770
14944
  link,
14771
14945
  signal: controller.signal,
14772
14946
  sandbox,
@@ -14809,7 +14983,7 @@ var Daemon = class {
14809
14983
  }, void 0, () => !this.stopped);
14810
14984
  });
14811
14985
  } else if (!this.updateCheck && !this.updateVersion) {
14812
- this.updateCheck = checkLatestVersion(join31(this.paths.dataDir, "auto-update.json"), DAEMON_VERSION, runtime).then((version) => {
14986
+ this.updateCheck = checkLatestVersion(join32(this.paths.dataDir, "auto-update.json"), DAEMON_VERSION, runtime).then((version) => {
14813
14987
  this.updateVersion = version;
14814
14988
  }).catch(() => {
14815
14989
  this.log.warn("Daemon automatic update check failed");