@themoltnet/agent-daemon 0.48.0 → 0.49.1

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.
Files changed (2) hide show
  1. package/dist/cli.js +564 -40
  2. package/package.json +9 -8
package/dist/cli.js CHANGED
@@ -12,7 +12,7 @@ import { AgentRuntime, ApiTaskReporter, ApiTaskSource, PollingApiTaskSource, cre
12
12
  import { GuestEnvironmentBoundaryError, assertGuestEnvironmentBoundary, createPiRetryTriage, findMainWorktree, isResolvedPathInsideRoot, normalizeRetryTriageResult, redactRetryTriageSecrets, resolveRuntimeProfileModel } from "@themoltnet/pi-runtime";
13
13
  import { FILE_SECRET_PROVIDER, FileSecretProvider, connect, createNodeSecretProviderRegistry } from "@themoltnet/sdk/node";
14
14
  import { execFile, execFileSync, spawn } from "node:child_process";
15
- import { constants, createReadStream, createWriteStream, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
15
+ import { constants, createReadStream, createWriteStream, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
16
16
  import { AuthenticationError, MoltNetError, agentKeyKey, assertTrustedConfigApiUrl, createExecutorAttestor, deriveMcpUrl, formatSecretReferenceString, identitySeedKey, parseSecretReferenceString, readConfig, register, requireSecureCredentialApiUrl, resolveAgentKey, resolveEnvSecretReference, resolveIdentitySeed, resolveOAuth2ClientSecret } from "@themoltnet/sdk";
17
17
  import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
18
18
  import { once } from "node:events";
@@ -30,6 +30,8 @@ import { mkdir, open, realpath, stat } from "node:fs/promises";
30
30
  import { pipeline } from "node:stream/promises";
31
31
  import cors from "@fastify/cors";
32
32
  import helmet from "@fastify/helmet";
33
+ import { getOAuthProviders } from "@earendil-works/pi-ai/oauth";
34
+ import { AuthStorage } from "@earendil-works/pi-coding-agent";
33
35
  import { Transform, Writable } from "node:stream";
34
36
  import { writePiConfig } from "@themoltnet/pi-runtime/pi-config";
35
37
  import { homedir } from "node:os";
@@ -4424,13 +4426,11 @@ function attestPreparedRuntime(prepared, signingPrivateKey) {
4424
4426
  //#endregion
4425
4427
  //#region src/lib/retry-triage.ts
4426
4428
  var RETRYABLE_CODES = new Set([
4427
- "checkpoint_upload_failed",
4428
4429
  "complete_call_failed",
4429
4430
  "daemon_abort",
4430
4431
  "dispatch_expired",
4431
4432
  "lease_expired",
4432
4433
  "llm_api_error",
4433
- "runtime_session_checkpoint_failed",
4434
4434
  "session_prompt_failed"
4435
4435
  ]);
4436
4436
  var NON_RETRYABLE_CODES = new Set([
@@ -4480,7 +4480,7 @@ var NON_RETRYABLE_MESSAGE_PATTERNS = [
4480
4480
  async function classifyAttemptFailure(input) {
4481
4481
  const deterministic = classifyDeterministically(input.error);
4482
4482
  if (input.remainingAttempts !== null && input.remainingAttempts !== void 0 && input.remainingAttempts <= 0) {
4483
- const deterministicReason = deterministic === "ambiguous" ? "" : ` Deterministic policy classified the failure as ${deterministic}.`;
4483
+ const deterministicReason = deterministic === "ambiguous" ? "" : deterministic === "retryable" ? " The failure type is retryable, but no attempts remain." : " The failure type is non-retryable.";
4484
4484
  return {
4485
4485
  error: withRetryInfo(input.error, {
4486
4486
  retryable: false,
@@ -4546,6 +4546,7 @@ async function classifyAttemptFailure(input) {
4546
4546
  function classifyDeterministically(error) {
4547
4547
  const code = error.code.toLowerCase();
4548
4548
  const message = error.message;
4549
+ if (code === "runtime_session_upload_failed") return error.retryable === true ? "retryable" : "non_retryable";
4549
4550
  if (NON_RETRYABLE_CODES.has(code)) return "non_retryable";
4550
4551
  if (NON_RETRYABLE_MESSAGE_PATTERNS.some((pattern) => pattern.test(message))) return "non_retryable";
4551
4552
  if (RETRYABLE_CODES.has(code)) return "retryable";
@@ -5539,15 +5540,29 @@ function applyRuntimeSessionUploadFailure(output, err) {
5539
5540
  error: {
5540
5541
  code: "runtime_session_upload_failed",
5541
5542
  message: "Task completed, but durable runtime session checkpoint upload failed: " + (err instanceof Error ? err.message : String(err)),
5542
- retryable: true
5543
+ retryable: isTransientUploadError(err)
5543
5544
  },
5544
5545
  output: null,
5545
5546
  outputCid: null,
5546
5547
  status: "failed"
5547
5548
  };
5548
5549
  }
5550
+ /**
5551
+ * Transient faults worth retrying in-attempt: network-level errors
5552
+ * (no HTTP status at all) and 5xx/429 responses. A 4xx (auth,
5553
+ * validation, not-found) will not heal on retry.
5554
+ */
5555
+ function isTransientUploadError(error) {
5556
+ if (!(error instanceof Error)) return false;
5557
+ const statusCode = error.statusCode;
5558
+ if (typeof statusCode !== "number") return true;
5559
+ return statusCode >= 500 || statusCode === 429;
5560
+ }
5561
+ var defaultSleep = (ms) => new Promise((resolve) => {
5562
+ setTimeout(resolve, ms);
5563
+ });
5549
5564
  function createApiRuntimeSessionStore(args) {
5550
- const { agent } = args;
5565
+ const { agent, logger, uploadRetry } = args;
5551
5566
  return {
5552
5567
  async findRuntimeSessionByTaskAttempt(teamId, taskId, attemptN) {
5553
5568
  return agent.runtimeSessions.getForAttempt({
@@ -5568,18 +5583,40 @@ function createApiRuntimeSessionStore(args) {
5568
5583
  async uploadAttemptFinal(input) {
5569
5584
  const sessionPath = resolveLatestPiSessionPath(input.sessionDir);
5570
5585
  if (!sessionPath) throw new Error(`Cannot upload runtime session for ${input.taskId}/${input.attemptN}: no local session file in ${input.sessionDir}`);
5571
- await agent.runtimeSessions.upload({
5572
- attemptN: input.attemptN,
5573
- taskId: input.taskId
5574
- }, createReadStream(sessionPath), {
5575
- parentSessionId: input.parentSessionId ?? void 0,
5576
- sessionKind: input.sessionKind,
5577
- sourceRuntimeProfileId: input.sourceRuntimeProfileId ?? void 0,
5578
- sourceSlotId: input.sourceSlotId ?? void 0
5579
- }, { teamId: input.teamId });
5586
+ const maxTries = uploadRetry?.maxTries ?? 3;
5587
+ const baseDelayMs = uploadRetry?.baseDelayMs ?? 750;
5588
+ const sleep = uploadRetry?.sleep ?? defaultSleep;
5589
+ for (let tryN = 1;; tryN += 1) try {
5590
+ await agent.runtimeSessions.upload({
5591
+ attemptN: input.attemptN,
5592
+ taskId: input.taskId
5593
+ }, createReadStream(sessionPath), {
5594
+ parentSessionId: input.parentSessionId ?? void 0,
5595
+ sessionKind: input.sessionKind,
5596
+ sourceRuntimeProfileId: input.sourceRuntimeProfileId ?? void 0,
5597
+ sourceSlotId: input.sourceSlotId ?? void 0
5598
+ }, { teamId: input.teamId });
5599
+ return;
5600
+ } catch (error) {
5601
+ if (tryN >= maxTries || !isTransientUploadError(error)) throw error;
5602
+ const delayMs = baseDelayMs * tryN;
5603
+ logger?.warn({
5604
+ event: "agent-daemon.runtime_session_upload_retry",
5605
+ attemptN: input.attemptN,
5606
+ delayMs,
5607
+ statusCode: uploadStatusCode(error),
5608
+ taskId: input.taskId,
5609
+ tryN
5610
+ }, "Retrying durable runtime session upload");
5611
+ await sleep(delayMs);
5612
+ }
5580
5613
  }
5581
5614
  };
5582
5615
  }
5616
+ function uploadStatusCode(error) {
5617
+ const statusCode = error?.statusCode;
5618
+ return typeof statusCode === "number" ? statusCode : void 0;
5619
+ }
5583
5620
  function resolveContinueFrom(claimedTask) {
5584
5621
  return claimedTask.task.input.continueFrom;
5585
5622
  }
@@ -5956,7 +5993,10 @@ async function runPolling(opts) {
5956
5993
  throw new Error(`No safe runtime profiles remain. ${details}`);
5957
5994
  }
5958
5995
  const slotRegistry = createApiRuntimeSlotStore({ agent: ctx.agent });
5959
- const runtimeSessionStore = createApiRuntimeSessionStore({ agent: ctx.agent });
5996
+ const runtimeSessionStore = createApiRuntimeSessionStore({
5997
+ agent: ctx.agent,
5998
+ logger: { warn: (context, message) => rootLogger.warn(context, message) }
5999
+ });
5960
6000
  const sourceAttemptResolver = createApiSourceAttemptResolver({ agent: ctx.agent });
5961
6001
  const runtimeInstanceId = createRuntimeInstanceId();
5962
6002
  const runtimes = /* @__PURE__ */ new Map();
@@ -6607,7 +6647,10 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
6607
6647
  activatePiCodingAgentDir(piAgentDir.path);
6608
6648
  const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
6609
6649
  const slotRegistry = createApiRuntimeSlotStore({ agent: ctx.agent });
6610
- const runtimeSessionStore = createApiRuntimeSessionStore({ agent: ctx.agent });
6650
+ const runtimeSessionStore = createApiRuntimeSessionStore({
6651
+ agent: ctx.agent,
6652
+ logger: { warn: (context, message) => rootLogger.warn(context, message) }
6653
+ });
6611
6654
  const sourceAttemptResolver = createApiSourceAttemptResolver({ agent: ctx.agent });
6612
6655
  const runtimeInstanceId = createRuntimeInstanceId();
6613
6656
  const slotIdentity = {
@@ -7277,6 +7320,258 @@ function renderPairingResultPage(input) {
7277
7320
  </html>
7278
7321
  `;
7279
7322
  }
7323
+ //#endregion
7324
+ //#region src/lib/serve/provider-login.ts
7325
+ /**
7326
+ * Subscription-provider OAuth brokering for `serve` (#2061 slice 4).
7327
+ *
7328
+ * The console clicks "Connect"; serve runs the Pi OAuth flow host-side via
7329
+ * `AuthStorage.login()` (which owns persistence into the shared
7330
+ * `pi/auth.json` and token rotation thereafter). The browser only ever sees
7331
+ * the provider's authorize URL or device code — never tokens.
7332
+ *
7333
+ * Provider ids come from pi-ai's own OAuth registry (`getOAuthProviders`),
7334
+ * so serve stays in lockstep with what Pi can actually authenticate
7335
+ * (anthropic, openai-codex, github-copilot, …) without hardcoding.
7336
+ */
7337
+ var LOGIN_TTL_MS = 600 * 1e3;
7338
+ /** How long `start()` waits for the flow to surface a URL / device code. */
7339
+ var START_INFO_TIMEOUT_MS = 5e3;
7340
+ var ServeSubscriptionError = class extends Error {
7341
+ name = "ServeSubscriptionError";
7342
+ constructor(code, message) {
7343
+ super(message);
7344
+ this.code = code;
7345
+ }
7346
+ };
7347
+ var silentLogger = {
7348
+ info: () => void 0,
7349
+ warn: () => void 0,
7350
+ error: () => void 0
7351
+ };
7352
+ var ProviderLoginService = class {
7353
+ logins = /* @__PURE__ */ new Map();
7354
+ authStorage;
7355
+ logger;
7356
+ constructor(options) {
7357
+ this.options = options;
7358
+ this.authStorage = options.authStorage ?? AuthStorage.create(this.options.authPath);
7359
+ this.logger = options.logger ?? silentLogger;
7360
+ }
7361
+ now() {
7362
+ return this.options.now?.() ?? Date.now();
7363
+ }
7364
+ providers() {
7365
+ return this.options.listProviders?.() ?? getOAuthProviders().map((provider) => ({
7366
+ id: provider.id,
7367
+ name: provider.name
7368
+ }));
7369
+ }
7370
+ connected(providerId) {
7371
+ if (this.options.isConnected) return this.options.isConnected(providerId);
7372
+ try {
7373
+ return (this.options.authStorage ? this.authStorage : AuthStorage.create(this.options.authPath)).getAuthStatus(providerId).configured;
7374
+ } catch (error) {
7375
+ this.logger.warn({
7376
+ event: "serve.subscription_auth_read_failed",
7377
+ providerId,
7378
+ ...safeLoginError(error)
7379
+ }, "Could not read subscription authentication state");
7380
+ return false;
7381
+ }
7382
+ }
7383
+ list() {
7384
+ this.sweep();
7385
+ return this.providers().map((provider) => ({
7386
+ ...provider,
7387
+ connected: this.connected(provider.id)
7388
+ }));
7389
+ }
7390
+ sweep() {
7391
+ const now = this.now();
7392
+ for (const [id, login] of this.logins) if (login.startedAt + LOGIN_TTL_MS <= now) {
7393
+ this.invalidate(login, "expired");
7394
+ this.logins.delete(id);
7395
+ }
7396
+ }
7397
+ restoreCredential(login) {
7398
+ try {
7399
+ if (login.previousCredential) this.authStorage.set(login.providerId, login.previousCredential);
7400
+ else this.authStorage.logout(login.providerId);
7401
+ } catch (error) {
7402
+ this.logger.error({
7403
+ event: "serve.subscription_login_cleanup_failed",
7404
+ operationId: login.operationId,
7405
+ providerId: login.providerId,
7406
+ ...safeLoginError(error)
7407
+ }, "Could not restore subscription credentials after an invalidated login");
7408
+ }
7409
+ }
7410
+ invalidate(login, transition) {
7411
+ if (login.invalidated) return;
7412
+ login.invalidated = true;
7413
+ login.abort.abort(/* @__PURE__ */ new Error(`subscription login ${transition}`));
7414
+ this.restoreCredential(login);
7415
+ login.infoArrived();
7416
+ this.logger.info({
7417
+ event: "serve.subscription_login_transition",
7418
+ operationId: login.operationId,
7419
+ providerId: login.providerId,
7420
+ transition
7421
+ }, "Subscription login invalidated");
7422
+ }
7423
+ status(providerId) {
7424
+ this.sweep();
7425
+ const login = this.logins.get(providerId);
7426
+ if (!login) throw new ServeSubscriptionError("login_not_found", `no login in progress for "${providerId}"`);
7427
+ return snapshot(login);
7428
+ }
7429
+ /**
7430
+ * Start (or return the in-flight) login for a provider. Resolves once the
7431
+ * flow has surfaced an authorize URL / device code, completed, or the
7432
+ * start window elapsed — whichever comes first.
7433
+ */
7434
+ async start(providerId) {
7435
+ this.sweep();
7436
+ if (!this.providers().some((provider) => provider.id === providerId)) throw new ServeSubscriptionError("provider_unknown", `"${providerId}" is not a known subscription provider`);
7437
+ const existing = this.logins.get(providerId);
7438
+ if (existing && existing.status === "pending") return snapshot(existing);
7439
+ let infoArrived = () => void 0;
7440
+ const infoPromise = new Promise((resolvePromise) => {
7441
+ infoArrived = () => resolvePromise();
7442
+ });
7443
+ const abort = new AbortController();
7444
+ const login = {
7445
+ providerId,
7446
+ status: "pending",
7447
+ operationId: randomUUID(),
7448
+ startedAt: this.now(),
7449
+ infoArrived,
7450
+ abort,
7451
+ invalidated: false,
7452
+ previousCredential: this.authStorage.get(providerId)
7453
+ };
7454
+ this.logins.set(providerId, login);
7455
+ this.logger.info({
7456
+ event: "serve.subscription_login_transition",
7457
+ operationId: login.operationId,
7458
+ providerId,
7459
+ transition: "started"
7460
+ }, "Subscription login started");
7461
+ const callbacks = createLoginCallbacks(login, this.logger);
7462
+ (this.options.runLogin ?? ((id, loginCallbacks) => this.authStorage.login(id, loginCallbacks)))(providerId, callbacks).then(() => {
7463
+ if (login.invalidated) {
7464
+ this.restoreCredential(login);
7465
+ return;
7466
+ }
7467
+ if (!this.options.runLogin && !this.connected(providerId)) {
7468
+ login.status = "failed";
7469
+ login.error = "Subscription sign-in completed, but credentials were not persisted. Start again to retry.";
7470
+ this.logger.error({
7471
+ event: "serve.subscription_login_transition",
7472
+ operationId: login.operationId,
7473
+ providerId,
7474
+ transition: "persistence_failed"
7475
+ }, "Subscription login credentials were not persisted");
7476
+ } else {
7477
+ login.status = "completed";
7478
+ this.logger.info({
7479
+ event: "serve.subscription_login_transition",
7480
+ operationId: login.operationId,
7481
+ providerId,
7482
+ transition: "completed"
7483
+ }, "Subscription login completed");
7484
+ }
7485
+ login.infoArrived();
7486
+ }, (error) => {
7487
+ if (login.invalidated) {
7488
+ this.restoreCredential(login);
7489
+ return;
7490
+ }
7491
+ login.status = "failed";
7492
+ login.error = publicLoginError(error);
7493
+ this.logger.warn({
7494
+ event: "serve.subscription_login_transition",
7495
+ operationId: login.operationId,
7496
+ providerId,
7497
+ transition: "failed",
7498
+ ...safeLoginError(error)
7499
+ }, "Subscription login failed");
7500
+ login.infoArrived();
7501
+ });
7502
+ await Promise.race([infoPromise, new Promise((resolvePromise) => {
7503
+ setTimeout(resolvePromise, START_INFO_TIMEOUT_MS).unref?.();
7504
+ })]);
7505
+ return snapshot(login);
7506
+ }
7507
+ /** Abort an in-flight login and forget it. */
7508
+ cancel(providerId) {
7509
+ const login = this.logins.get(providerId);
7510
+ if (!login) throw new ServeSubscriptionError("login_not_found", `no login in progress for "${providerId}"`);
7511
+ this.invalidate(login, "cancelled");
7512
+ this.logins.delete(providerId);
7513
+ return {
7514
+ providerId,
7515
+ status: "cancelled"
7516
+ };
7517
+ }
7518
+ /** Abort every pending flow during supervisor shutdown. */
7519
+ close() {
7520
+ for (const login of this.logins.values()) this.invalidate(login, "shutdown");
7521
+ this.logins.clear();
7522
+ }
7523
+ };
7524
+ function createLoginCallbacks(login, logger) {
7525
+ return {
7526
+ onAuth: (info) => {
7527
+ login.authUrl = info.url;
7528
+ if (info.instructions) login.instructions = info.instructions;
7529
+ logger.info({
7530
+ event: "serve.subscription_login_transition",
7531
+ operationId: login.operationId,
7532
+ providerId: login.providerId,
7533
+ transition: "authorization_ready"
7534
+ }, "Subscription authorization URL ready");
7535
+ login.infoArrived();
7536
+ },
7537
+ onDeviceCode: (info) => {
7538
+ login.userCode = info.userCode;
7539
+ login.verificationUri = info.verificationUri;
7540
+ logger.info({
7541
+ event: "serve.subscription_login_transition",
7542
+ operationId: login.operationId,
7543
+ providerId: login.providerId,
7544
+ transition: "device_code_ready"
7545
+ }, "Subscription device code ready");
7546
+ login.infoArrived();
7547
+ },
7548
+ onPrompt: () => Promise.reject(new ServeSubscriptionError("login_unsupported_prompt", "This provider flow needs an interactive prompt; run `pi /login` in a terminal instead")),
7549
+ onSelect: (prompt) => Promise.resolve((prompt.options.find((option) => /device/i.test(option.id)) ?? prompt.options[0])?.id),
7550
+ signal: login.abort.signal
7551
+ };
7552
+ }
7553
+ function publicLoginError(error) {
7554
+ if (error instanceof ServeSubscriptionError) return error.message;
7555
+ return "Subscription sign-in failed. Start again to retry.";
7556
+ }
7557
+ function safeLoginError(error) {
7558
+ const result = { errorType: error instanceof Error ? error.name : typeof error };
7559
+ const code = error?.code;
7560
+ if (typeof code === "string" && /^[a-z0-9_:-]{1,64}$/iu.test(code)) result["applicationCode"] = code;
7561
+ return result;
7562
+ }
7563
+ function snapshot(login) {
7564
+ const { providerId, status, authUrl, instructions, userCode, verificationUri, error } = login;
7565
+ return {
7566
+ providerId,
7567
+ status,
7568
+ ...authUrl ? { authUrl } : {},
7569
+ ...instructions ? { instructions } : {},
7570
+ ...userCode ? { userCode } : {},
7571
+ ...verificationUri ? { verificationUri } : {},
7572
+ ...error ? { error } : {}
7573
+ };
7574
+ }
7280
7575
  var NAME_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
7281
7576
  var PROVIDER_ID_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
7282
7577
  function assertStoreName(kind, value) {
@@ -7340,11 +7635,17 @@ var ServeStore = class {
7340
7635
  agentsDir;
7341
7636
  runsDir;
7342
7637
  secretsDir;
7638
+ /** Shared Pi credential dir; `auth.json` inside is pi-managed (lockfiled). */
7639
+ piDir;
7343
7640
  constructor(root) {
7344
7641
  this.root = root;
7345
7642
  this.agentsDir = join(root, "agents");
7346
7643
  this.runsDir = join(root, "runs");
7347
7644
  this.secretsDir = join(root, "secrets");
7645
+ this.piDir = join(root, "pi");
7646
+ }
7647
+ get piAuthJsonPath() {
7648
+ return join(this.piDir, "auth.json");
7348
7649
  }
7349
7650
  /** Create the directory layout (0700) if missing. Idempotent. */
7350
7651
  ensure() {
@@ -7369,13 +7670,13 @@ var ServeStore = class {
7369
7670
  pendingRegistrations: {},
7370
7671
  activations: {}
7371
7672
  };
7372
- if (!isRecord(state) || state.version !== 1) throw new ServeStoreError("invalid_state", `serve.json version ${String(isRecord(state) ? state.version : void 0)} is not supported`);
7673
+ if (!isRecord$1(state) || state.version !== 1) throw new ServeStoreError("invalid_state", `serve.json version ${String(isRecord$1(state) ? state.version : void 0)} is not supported`);
7373
7674
  if ("pairedOrigins" in state) throw new ServeStoreError("invalid_state", "serve.json uses the obsolete pairing format; clear the unreleased serve store and reconfigure it");
7374
- if (!isRecord(state.pendingRegistrations) || !isRecord(state.activations)) throw new ServeStoreError("invalid_state", "serve.json is missing the version 1 activation map; clear the unreleased serve store and reconfigure it");
7675
+ if (!isRecord$1(state.pendingRegistrations) || !isRecord$1(state.activations)) throw new ServeStoreError("invalid_state", "serve.json is missing the version 1 activation map; clear the unreleased serve store and reconfigure it");
7375
7676
  for (const [alias, activation] of Object.entries(state.activations)) validateActivation(alias, activation);
7376
7677
  for (const [alias, registration] of Object.entries(state.pendingRegistrations)) {
7377
7678
  assertStoreName("agent name", alias);
7378
- if (!isRecord(registration) || typeof registration.apiUrl !== "string" || registration.apiUrl.length === 0 || typeof registration.createdAt !== "string" || registration.createdAt.length === 0) throw new ServeStoreError("invalid_state", `pending registration "${alias}" is not valid`);
7679
+ if (!isRecord$1(registration) || typeof registration.apiUrl !== "string" || registration.apiUrl.length === 0 || typeof registration.createdAt !== "string" || registration.createdAt.length === 0) throw new ServeStoreError("invalid_state", `pending registration "${alias}" is not valid`);
7379
7680
  }
7380
7681
  return {
7381
7682
  version: 1,
@@ -7549,7 +7850,7 @@ function directoryBytes(path) {
7549
7850
  for (const entry of readdirSync(path, { withFileTypes: true })) total += directoryBytes(join(path, entry.name));
7550
7851
  return total;
7551
7852
  }
7552
- function isRecord(value) {
7853
+ function isRecord$1(value) {
7553
7854
  return typeof value === "object" && value !== null && !Array.isArray(value);
7554
7855
  }
7555
7856
  function storeChildPath(root, kind, value, suffix = "") {
@@ -7567,7 +7868,7 @@ function validateActivation(alias, value) {
7567
7868
  const invalid = () => {
7568
7869
  throw new ServeStoreError("invalid_state", `activation "${alias}" is not a valid version 1 activation`);
7569
7870
  };
7570
- if (!isRecord(value)) invalid();
7871
+ if (!isRecord$1(value)) invalid();
7571
7872
  const activation = value;
7572
7873
  if (activation.alias !== alias) invalid();
7573
7874
  if (![
@@ -7612,8 +7913,9 @@ function reserveAlias(store, alias) {
7612
7913
  pending.add(alias);
7613
7914
  return () => pending.delete(alias);
7614
7915
  }
7615
- async function createManagedAgent(store, secrets, input) {
7916
+ async function createManagedAgent(store, secrets, input, connectAgent = connect) {
7616
7917
  const alias = assertStoreName("agent name", input.name);
7918
+ if (!input.enrollmentToken.trim()) throw new ServeIdentityError("enrollment_required", "an enrollment token from the target team is required — a self-registered agent would be stranded in its own personal team");
7617
7919
  const releaseAlias = reserveAlias(store, alias);
7618
7920
  let registeredIdentityId;
7619
7921
  try {
@@ -7627,7 +7929,7 @@ async function createManagedAgent(store, secrets, input) {
7627
7929
  const result = await register({
7628
7930
  credentialType: "agent_key",
7629
7931
  apiUrl,
7630
- ...input.enrollmentToken ? { enrollmentToken: input.enrollmentToken } : {},
7932
+ enrollmentToken: input.enrollmentToken,
7631
7933
  signal: boundedIdentitySignal(input.signal)
7632
7934
  });
7633
7935
  if (result.credentials.type !== "agent_key") throw new ServeIdentityError("unsupported_credential", `registration returned credential type "${result.credentials.type}"; serve manages agent-key credentials only`);
@@ -7656,22 +7958,34 @@ async function createManagedAgent(store, secrets, input) {
7656
7958
  mcp: deriveMcpUrl(result.apiUrl)
7657
7959
  }
7658
7960
  };
7961
+ store.writeAgentConfig(alias, config);
7962
+ await secrets.write(agentKeyReference.key, result.credentials.secret);
7963
+ await secrets.write(seedReference.key, privateKey);
7964
+ const whoami = await callWhoami(connectAgent, {
7965
+ agentKey: result.credentials.secret,
7966
+ apiUrl: result.apiUrl
7967
+ }, store.agentPath(alias), input.signal);
7968
+ assertIdentityMatches(whoami, {
7969
+ identityId,
7970
+ publicKey,
7971
+ fingerprint
7972
+ }, "authenticated whoami", `new managed agent "${alias}"`);
7973
+ const boundTeamId = boundTeamIdFromWhoami(whoami);
7659
7974
  const activation = {
7660
7975
  alias,
7661
7976
  source: "managed",
7662
7977
  identityId,
7663
7978
  publicKey,
7664
7979
  fingerprint,
7980
+ ...boundTeamId ? { boundTeamId } : {},
7665
7981
  createdAt: now,
7666
7982
  apiUrl: result.apiUrl
7667
7983
  };
7668
- store.writeAgentConfig(alias, config);
7669
- await secrets.write(agentKeyReference.key, result.credentials.secret);
7670
- await secrets.write(seedReference.key, privateKey);
7671
7984
  store.writeActivation(activation);
7672
7985
  return {
7673
7986
  activation,
7674
- config
7987
+ config,
7988
+ ...boundTeamId ? { boundTeamId } : {}
7675
7989
  };
7676
7990
  } catch (cause) {
7677
7991
  if (!registeredIdentityId && cause instanceof MoltNetError && cause.statusCode !== void 0 && cause.statusCode >= 400 && cause.statusCode < 500) {
@@ -7708,21 +8022,25 @@ async function reconcileManagedRegistration(store, secrets, aliasInput, action,
7708
8022
  if (!agentKey || privateKeyState !== "present") throw new ServeIdentityError("registration_incomplete", `pending registration for "${alias}" is missing persisted secret material`);
7709
8023
  const identity = identityFromConfig(config);
7710
8024
  const apiUrl = requireConfigApiUrl(config, store.agentPath(alias));
7711
- assertIdentityMatches(await callWhoami(connectAgent, {
8025
+ const whoami = await callWhoami(connectAgent, {
7712
8026
  agentKey,
7713
8027
  apiUrl
7714
- }, store.agentPath(alias), signal), identity, "authenticated whoami", `pending registration "${alias}" config`);
8028
+ }, store.agentPath(alias), signal);
8029
+ assertIdentityMatches(whoami, identity, "authenticated whoami", `pending registration "${alias}" config`);
8030
+ const boundTeamId = boundTeamIdFromWhoami(whoami);
7715
8031
  const recovered = {
7716
8032
  alias,
7717
8033
  source: "managed",
7718
8034
  ...identity,
8035
+ ...boundTeamId ? { boundTeamId } : {},
7719
8036
  createdAt: config.registered_at,
7720
8037
  apiUrl
7721
8038
  };
7722
8039
  store.writeActivation(recovered);
7723
8040
  return {
7724
8041
  activation: recovered,
7725
- config
8042
+ config,
8043
+ ...recovered.boundTeamId ? { boundTeamId: recovered.boundTeamId } : {}
7726
8044
  };
7727
8045
  }
7728
8046
  async function attachExternalAgent(store, secretProviders, input, connectAgent = connect) {
@@ -7738,10 +8056,12 @@ async function attachExternalAgent(store, secretProviders, input, connectAgent =
7738
8056
  const whoami = await authenticateConfig(input.configDir, effectiveApiUrl, secretProviders, connectAgent, input.signal);
7739
8057
  const identity = identityFromConfig(config);
7740
8058
  assertIdentityMatches(identity, whoami, `external config ${configPath}`, "authenticated whoami");
8059
+ const boundTeamId = boundTeamIdFromWhoami(whoami);
7741
8060
  const activation = {
7742
8061
  alias,
7743
8062
  source: "external",
7744
8063
  ...identity,
8064
+ ...boundTeamId ? { boundTeamId } : {},
7745
8065
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
7746
8066
  configPath,
7747
8067
  configApiUrl,
@@ -7750,7 +8070,8 @@ async function attachExternalAgent(store, secretProviders, input, connectAgent =
7750
8070
  store.writeActivation(activation);
7751
8071
  return {
7752
8072
  activation,
7753
- config
8073
+ config,
8074
+ ...activation.boundTeamId ? { boundTeamId: activation.boundTeamId } : {}
7754
8075
  };
7755
8076
  } finally {
7756
8077
  releaseAlias();
@@ -7761,9 +8082,12 @@ async function verifyAgentActivation(store, alias, managedSecretProviders, exter
7761
8082
  const activation = requireActivation(store, alias);
7762
8083
  const verified = activation.source === "managed" ? await verifyManagedActivation(store, activation, managedSecretProviders, connectAgent, signal) : await verifyExternalActivation(activation, externalSecretProviders, connectAgent, signal);
7763
8084
  assertIdentityMatches(verified.whoami, activation, "authenticated whoami", `agent "${activation.alias}" pinned activation`);
8085
+ const boundTeamId = boundTeamIdFromWhoami(verified.whoami);
8086
+ if (activation.boundTeamId !== boundTeamId) throw new ServeIdentityError("verification_failed", `authenticated whoami team binding does not match agent "${activation.alias}" pinned activation`);
7764
8087
  return {
7765
8088
  activation,
7766
- config: verified.config
8089
+ config: verified.config,
8090
+ ...boundTeamId ? { boundTeamId } : {}
7767
8091
  };
7768
8092
  }
7769
8093
  async function verifyManagedActivation(store, activation, secretProviders, connectAgent, signal) {
@@ -7902,6 +8226,7 @@ function publicAgentView(store, activation) {
7902
8226
  agentName: activation.alias,
7903
8227
  identityId: activation.identityId,
7904
8228
  fingerprint: activation.fingerprint,
8229
+ ...activation.boundTeamId ? { teamId: activation.boundTeamId } : {},
7905
8230
  apiUrl: activation.apiUrl,
7906
8231
  createdAt: activation.createdAt,
7907
8232
  hasAgentKey: Boolean(config?.agent_key_ref),
@@ -7915,9 +8240,13 @@ function publicAgentView(store, activation) {
7915
8240
  ...activation.apiUrl ? { apiUrl: activation.apiUrl } : {},
7916
8241
  identityId: activation.identityId,
7917
8242
  fingerprint: activation.fingerprint,
8243
+ ...activation.boundTeamId ? { teamId: activation.boundTeamId } : {},
7918
8244
  createdAt: activation.createdAt
7919
8245
  };
7920
8246
  }
8247
+ function boundTeamIdFromWhoami(whoami) {
8248
+ return whoami.credentialBinding?.bindingScope === "team" ? whoami.credentialBinding.boundTeamId ?? void 0 : void 0;
8249
+ }
7921
8250
  function requireActivation(store, alias) {
7922
8251
  const activation = store.readActivation(alias);
7923
8252
  if (!activation) throw new ServeStoreError("not_found", `agent "${alias}" is not configured`);
@@ -8087,6 +8416,7 @@ var RunManager = class {
8087
8416
  this.assertStartOpen(signal);
8088
8417
  const agent = await (this.options.verifyActivationImpl ?? verifyAgentActivation)(this.store, spec.agent, this.options.secretProviders, this.options.externalSecretProviders, void 0, signal);
8089
8418
  this.assertStartOpen(signal);
8419
+ if (agent.boundTeamId && agent.boundTeamId !== spec.teamId) throw new ServeRunError("invalid_spec", `agent "${spec.agent}" has a key bound to team ${agent.boundTeamId}; start the run with that team, or create a new agent with an enrollment token from team ${spec.teamId}`);
8090
8420
  const id = `${Date.now().toString(36)}-${randomBytes(4).toString("hex")}`;
8091
8421
  const runDir = this.store.runDir(id);
8092
8422
  const piDir = join(runDir, "pi");
@@ -8116,6 +8446,11 @@ var RunManager = class {
8116
8446
  models: provider.models
8117
8447
  }]))
8118
8448
  });
8449
+ try {
8450
+ (this.options.symlinkImpl ?? symlinkSync)(this.store.piAuthJsonPath, join(piDir, "auth.json"));
8451
+ } catch (cause) {
8452
+ throw new ServeStoreError("io_error", "could not link subscription credentials into the run", { cause });
8453
+ }
8119
8454
  const entry = this.entrypoint();
8120
8455
  logStream = createWriteStream(logPath, {
8121
8456
  fd: openSync(logPath, "a", 384),
@@ -8470,6 +8805,62 @@ async function withServeLock(root, work, options) {
8470
8805
  await held.release();
8471
8806
  }
8472
8807
  }
8808
+ var ServeModelDiscoveryError = class extends Error {
8809
+ name = "ServeModelDiscoveryError";
8810
+ constructor(code, message, statusCode, options) {
8811
+ super(message, options);
8812
+ this.code = code;
8813
+ this.statusCode = statusCode;
8814
+ }
8815
+ };
8816
+ var ModelDiscoveryCollector = class {
8817
+ models = /* @__PURE__ */ new Set();
8818
+ addOpenAiResponse(value) {
8819
+ if (!isRecord(value) || !Array.isArray(value["data"])) return;
8820
+ for (const candidate of value["data"]) {
8821
+ if (!isRecord(candidate)) continue;
8822
+ const id = candidate["id"];
8823
+ if (typeof id === "string" && id.length > 0) this.models.add(id);
8824
+ }
8825
+ }
8826
+ addOllamaResponse(value) {
8827
+ if (!isRecord(value) || !Array.isArray(value["models"])) return;
8828
+ for (const candidate of value["models"]) {
8829
+ if (!isRecord(candidate)) continue;
8830
+ const name = candidate["name"];
8831
+ if (typeof name === "string" && name.length > 0) this.models.add(name);
8832
+ }
8833
+ }
8834
+ get size() {
8835
+ return this.models.size;
8836
+ }
8837
+ result(providerId, failures) {
8838
+ if (this.models.size === 0) throw discoveryFailure(providerId, failures);
8839
+ return {
8840
+ models: [...this.models].sort().slice(0, 500),
8841
+ discoveredCount: this.models.size
8842
+ };
8843
+ }
8844
+ };
8845
+ function parseProviderBaseUrl(value, providerId) {
8846
+ let parsed;
8847
+ try {
8848
+ parsed = new URL(value);
8849
+ } catch (cause) {
8850
+ throw new ServeModelDiscoveryError("invalid_provider", `provider "${providerId}" has an invalid base URL`, 400, { cause });
8851
+ }
8852
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:" || parsed.username || parsed.password || parsed.search || parsed.hash) throw new ServeModelDiscoveryError("invalid_provider", `provider "${providerId}" base URL must be HTTP(S) without credentials, query, or fragment`, 400);
8853
+ return parsed;
8854
+ }
8855
+ function discoveryFailure(providerId, failures) {
8856
+ if (failures.some((failure) => failure.kind === "http" && (failure.status === 401 || failure.status === 403))) return new ServeModelDiscoveryError("discovery_unauthorized", `provider "${providerId}" rejected model discovery; check its API key`, 502);
8857
+ if (failures.some((failure) => failure.kind === "network")) return new ServeModelDiscoveryError("discovery_unavailable", `provider "${providerId}" could not be reached for model discovery`, 502);
8858
+ if (failures.some((failure) => failure.kind === "invalid_response")) return new ServeModelDiscoveryError("discovery_invalid_response", `provider "${providerId}" returned an invalid model response`, 502);
8859
+ return new ServeModelDiscoveryError("discovery_failed", `no models discovered for provider "${providerId}"`, 502);
8860
+ }
8861
+ function isRecord(value) {
8862
+ return typeof value === "object" && value !== null && !Array.isArray(value);
8863
+ }
8473
8864
  //#endregion
8474
8865
  //#region src/lib/serve/server.ts
8475
8866
  /**
@@ -8562,9 +8953,9 @@ function optionalString(body, field) {
8562
8953
  if (typeof value !== "string" || value.trim().length === 0) throw new ServeHttpError(400, "invalid_body", `"${field}" must be a non-empty string when present`);
8563
8954
  return value.trim();
8564
8955
  }
8565
- function stringArray(body, field) {
8956
+ function stringArray(body, field, options = {}) {
8566
8957
  const value = body[field];
8567
- if (!Array.isArray(value) || value.length === 0 || value.some((item) => typeof item !== "string" || item.length === 0)) throw new ServeHttpError(400, "invalid_body", `"${field}" must be a non-empty string array`);
8958
+ if (!Array.isArray(value) || !options.allowEmpty && value.length === 0 || value.some((item) => typeof item !== "string" || item.length === 0)) throw new ServeHttpError(400, "invalid_body", `"${field}" must be ${options.allowEmpty ? "a" : "a non-empty"} string array`);
8568
8959
  return value;
8569
8960
  }
8570
8961
  function requestOperationSignal(request, shutdownSignal) {
@@ -8622,7 +9013,11 @@ function buildServeServer(options) {
8622
9013
  registerStatusRoute(app, options, requirePairedOrigin);
8623
9014
  registerAgentRoutes(app, options, requirePairedOrigin);
8624
9015
  registerProviderRoutes(app, options, requirePairedOrigin);
9016
+ registerSubscriptionRoutes(app, options, requirePairedOrigin);
8625
9017
  registerRunRoutes(app, options, requirePairedOrigin);
9018
+ app.addHook("onClose", () => {
9019
+ options.subscriptions.close();
9020
+ });
8626
9021
  app.setNotFoundHandler(async (_request, reply) => reply.code(404).send({
8627
9022
  code: "not_found",
8628
9023
  message: "Route is not available"
@@ -8680,6 +9075,7 @@ function registerStatusRoute(app, options, requirePairedOrigin) {
8680
9075
  return {
8681
9076
  version: options.version,
8682
9077
  platform: process.platform,
9078
+ subscriptions: options.subscriptions.list(),
8683
9079
  agents: store.listActivations().map((activation) => publicAgentView(store, activation)),
8684
9080
  providers: Object.fromEntries(Object.entries(store.readProviders()).map(([id, provider]) => [id, providerView(provider)])),
8685
9081
  runs: runViews(runs)
@@ -8698,11 +9094,11 @@ function registerAgentRoutes(app, options, requirePairedOrigin) {
8698
9094
  const signal = requestOperationSignal(request, options.shutdownSignal);
8699
9095
  const kind = requireString(body, "kind");
8700
9096
  if (kind === "managed") {
8701
- const enrollmentToken = optionalString(body, "enrollmentToken");
9097
+ if (body["apiUrl"] !== void 0) throw new ServeHttpError(400, "invalid_body", "managed agent registration uses the configured MoltNet API URL; apiUrl cannot be overridden");
8702
9098
  const entry = await createManagedAgent(store, options.secrets, {
8703
9099
  name: requireString(body, "name"),
8704
9100
  apiUrl: options.defaultApiUrl,
8705
- ...enrollmentToken ? { enrollmentToken } : {},
9101
+ enrollmentToken: requireString(body, "enrollmentToken"),
8706
9102
  signal
8707
9103
  });
8708
9104
  return reply.code(201).send(publicAgentView(store, entry.activation));
@@ -8740,16 +9136,106 @@ function registerProviderRoutes(app, options, requirePairedOrigin) {
8740
9136
  requirePairedOrigin(request);
8741
9137
  return Object.fromEntries(Object.entries(store.readProviders()).map(([id, provider]) => [id, providerView(provider)]));
8742
9138
  });
9139
+ app.post("/v1/providers/:providerId/discover-models", async (request) => {
9140
+ requirePairedOrigin(request);
9141
+ const { providerId: rawProviderId } = request.params;
9142
+ const providerId = assertProviderId(rawProviderId);
9143
+ const provider = store.readProviders()[providerId];
9144
+ if (!provider) throw new ServeHttpError(404, "provider_not_found", `provider "${providerId}" was not found`);
9145
+ const parsed = parseProviderBaseUrl(provider.baseUrl, providerId);
9146
+ const baseUrl = parsed.href.replace(/\/$/u, "");
9147
+ let apiKey;
9148
+ if (provider.apiKeyRef) try {
9149
+ apiKey = await options.secretProviders.resolve(parseSecretReferenceString(provider.apiKeyRef));
9150
+ } catch (error) {
9151
+ request.log.warn({
9152
+ ...safeErrorContext(error),
9153
+ code: "serve_provider_secret_unavailable",
9154
+ providerId
9155
+ }, "Provider API key could not be resolved for model discovery");
9156
+ throw new ServeHttpError(400, "provider_secret_unavailable", `provider "${providerId}" API key could not be resolved`);
9157
+ }
9158
+ const headers = apiKey ? { authorization: `Bearer ${apiKey}` } : {};
9159
+ const fetchImpl = options.discoverFetch ?? fetch;
9160
+ const failures = [];
9161
+ const collector = new ModelDiscoveryCollector();
9162
+ const tryJson = async (endpoint, url) => {
9163
+ let response;
9164
+ try {
9165
+ response = await fetchImpl(url, {
9166
+ headers,
9167
+ redirect: "error",
9168
+ signal: AbortSignal.timeout(1e4)
9169
+ });
9170
+ } catch (error) {
9171
+ const errorType = error instanceof Error ? error.name : typeof error;
9172
+ failures.push({
9173
+ kind: "network",
9174
+ errorType
9175
+ });
9176
+ request.log.warn({
9177
+ code: "serve_provider_discovery_request_failed",
9178
+ endpoint,
9179
+ errorType,
9180
+ providerId
9181
+ }, "Provider model discovery request failed");
9182
+ return null;
9183
+ }
9184
+ if (!response.ok) {
9185
+ failures.push({
9186
+ kind: "http",
9187
+ status: response.status
9188
+ });
9189
+ const context = {
9190
+ code: "serve_provider_discovery_upstream_error",
9191
+ endpoint,
9192
+ providerId,
9193
+ statusCode: response.status
9194
+ };
9195
+ if (response.status >= 500 || response.status === 401 || response.status === 403) request.log.warn(context, "Provider model discovery was rejected");
9196
+ else request.log.info(context, "Provider model discovery endpoint unavailable");
9197
+ return null;
9198
+ }
9199
+ try {
9200
+ return await response.json();
9201
+ } catch {
9202
+ failures.push({ kind: "invalid_response" });
9203
+ request.log.warn({
9204
+ code: "serve_provider_discovery_invalid_json",
9205
+ endpoint,
9206
+ providerId
9207
+ }, "Provider model discovery returned invalid JSON");
9208
+ return null;
9209
+ }
9210
+ };
9211
+ collector.addOpenAiResponse(await tryJson("openai_models", `${baseUrl}/models`));
9212
+ if (collector.size === 0) collector.addOllamaResponse(await tryJson("ollama_tags", `${parsed.origin}/api/tags`));
9213
+ const result = collector.result(providerId, failures);
9214
+ if (result.discoveredCount > result.models.length) request.log.warn({
9215
+ code: "serve_provider_discovery_truncated",
9216
+ discoveredCount: result.discoveredCount,
9217
+ providerId,
9218
+ returnedCount: 500
9219
+ }, "Provider model discovery result was truncated");
9220
+ request.log.info({
9221
+ code: "serve_provider_discovery_completed",
9222
+ modelCount: result.models.length,
9223
+ providerId
9224
+ }, "Provider model discovery completed");
9225
+ return { models: result.models };
9226
+ });
8743
9227
  app.put("/v1/providers/:providerId", async (request, reply) => {
8744
9228
  requirePairedOrigin(request);
8745
9229
  const { providerId: rawProviderId } = request.params;
8746
9230
  const providerId = assertProviderId(rawProviderId);
8747
9231
  const body = requireBody(request);
9232
+ const baseUrl = requireString(body, "baseUrl");
9233
+ parseProviderBaseUrl(baseUrl, providerId);
8748
9234
  const entry = {
8749
9235
  api: requireString(body, "api"),
8750
- baseUrl: requireString(body, "baseUrl"),
9236
+ baseUrl,
8751
9237
  envName: assertProviderEnvName(providerId, requireString(body, "envName")),
8752
- models: stringArray(body, "models")
9238
+ models: stringArray(body, "models", { allowEmpty: true })
8753
9239
  };
8754
9240
  const apiKey = optionalString(body, "apiKey");
8755
9241
  await serialize(async () => {
@@ -8774,6 +9260,28 @@ function runViews(runs) {
8774
9260
  active: runs.isActive(record.id)
8775
9261
  }));
8776
9262
  }
9263
+ function registerSubscriptionRoutes(app, options, requirePairedOrigin) {
9264
+ app.get("/v1/subscriptions", async (request) => {
9265
+ requirePairedOrigin(request);
9266
+ return options.subscriptions.list();
9267
+ });
9268
+ app.post("/v1/subscriptions/:providerId/login", async (request, reply) => {
9269
+ requirePairedOrigin(request);
9270
+ const { providerId } = request.params;
9271
+ const login = await options.subscriptions.start(providerId);
9272
+ return reply.code(201).send(login);
9273
+ });
9274
+ app.get("/v1/subscriptions/:providerId/login", async (request) => {
9275
+ requirePairedOrigin(request);
9276
+ const { providerId } = request.params;
9277
+ return options.subscriptions.status(providerId);
9278
+ });
9279
+ app.delete("/v1/subscriptions/:providerId/login", async (request) => {
9280
+ requirePairedOrigin(request);
9281
+ const { providerId } = request.params;
9282
+ return options.subscriptions.cancel(providerId);
9283
+ });
9284
+ }
8777
9285
  function registerRunRoutes(app, options, requirePairedOrigin) {
8778
9286
  const { runs } = options;
8779
9287
  app.get("/v1/runs", async (request) => {
@@ -8944,6 +9452,16 @@ function normalizeServeError(error) {
8944
9452
  code: error.code,
8945
9453
  message: error.message
8946
9454
  };
9455
+ if (error instanceof ServeSubscriptionError) return {
9456
+ statusCode: error.code === "login_not_found" ? 404 : error.code === "provider_unknown" ? 404 : 400,
9457
+ code: error.code,
9458
+ message: error.message
9459
+ };
9460
+ if (error instanceof ServeModelDiscoveryError) return {
9461
+ statusCode: error.statusCode,
9462
+ code: error.code,
9463
+ message: error.message
9464
+ };
8947
9465
  if (error instanceof ServeIdentityError) return {
8948
9466
  statusCode: error.code === "agent_exists" ? 409 : 400,
8949
9467
  code: error.code,
@@ -8952,7 +9470,7 @@ function normalizeServeError(error) {
8952
9470
  return {
8953
9471
  statusCode: 500,
8954
9472
  code: "internal_error",
8955
- message: "Request failed"
9473
+ message: "The local supervisor could not complete the request."
8956
9474
  };
8957
9475
  }
8958
9476
  //#endregion
@@ -9009,6 +9527,10 @@ async function runServe(argv) {
9009
9527
  const externalSecretProviders = createNodeSecretProviderRegistry();
9010
9528
  const pairing = new PairingService();
9011
9529
  const shutdownController = new AbortController();
9530
+ const subscriptions = new ProviderLoginService({
9531
+ authPath: store.piAuthJsonPath,
9532
+ logger
9533
+ });
9012
9534
  const runs = new RunManager({
9013
9535
  store,
9014
9536
  secretProviders,
@@ -9019,9 +9541,11 @@ async function runServe(argv) {
9019
9541
  const app = buildServeServer({
9020
9542
  store,
9021
9543
  secrets,
9544
+ secretProviders,
9022
9545
  externalSecretProviders,
9023
9546
  pairing,
9024
9547
  runs,
9548
+ subscriptions,
9025
9549
  allowedOrigins,
9026
9550
  selfOrigin: `http://127.0.0.1:${port}`,
9027
9551
  defaultApiUrl,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@themoltnet/agent-daemon",
3
- "version": "0.48.0",
3
+ "version": "0.49.1",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "description": "Universal MoltNet agent daemon host with a built-in Pi/Gondolin runtime and support for trusted operator-owned runtime modules. CLI: moltnet-agent.",
@@ -48,7 +48,8 @@
48
48
  },
49
49
  "dependencies": {
50
50
  "@earendil-works/gondolin": "^0.12.0",
51
- "@earendil-works/pi-coding-agent": "0.84.4",
51
+ "@earendil-works/pi-ai": "0.79.4",
52
+ "@earendil-works/pi-coding-agent": "0.79.4",
52
53
  "@fastify/cors": "^11.0.0",
53
54
  "@fastify/helmet": "^13.0.1",
54
55
  "@fastify/otel": "^0.18.0",
@@ -82,10 +83,10 @@
82
83
  "pino-pretty": "^13.1.3",
83
84
  "proper-lockfile": "4.1.2",
84
85
  "typebox": "^1.2.8",
86
+ "@themoltnet/agent-runtime": "0.45.2",
85
87
  "@themoltnet/os-keyring": "0.3.0",
86
- "@themoltnet/sdk": "0.140.0",
87
- "@themoltnet/pi-runtime": "0.14.0",
88
- "@themoltnet/agent-runtime": "0.45.2"
88
+ "@themoltnet/pi-runtime": "0.14.1",
89
+ "@themoltnet/sdk": "0.140.0"
89
90
  },
90
91
  "devDependencies": {
91
92
  "@types/proper-lockfile": "^4.1.4",
@@ -95,15 +96,15 @@
95
96
  "vite-plugin-dts": "^4.5.4",
96
97
  "vitest": "^3.0.0",
97
98
  "@moltnet/agent-eval": "0.1.0",
98
- "@moltnet/bootstrap": "0.1.0",
99
99
  "@moltnet/crypto-service": "0.1.0",
100
100
  "@moltnet/execution-integrations": "0.1.0",
101
101
  "@moltnet/execution-plan": "0.1.0",
102
+ "@moltnet/bootstrap": "0.1.0",
102
103
  "@moltnet/loopback-companion": "0.1.0",
104
+ "@moltnet/observability": "0.1.0",
103
105
  "@moltnet/models": "0.1.0",
104
- "@moltnet/tasks": "0.1.0",
105
106
  "@moltnet/runtime-profiles": "0.1.0",
106
- "@moltnet/observability": "0.1.0"
107
+ "@moltnet/tasks": "0.1.0"
107
108
  },
108
109
  "nx": {
109
110
  "projectType": "application",