@themoltnet/agent-daemon 0.57.1 → 0.59.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.
Files changed (3) hide show
  1. package/README.md +21 -0
  2. package/dist/cli.js +308 -41
  3. package/package.json +8 -8
package/README.md CHANGED
@@ -8,6 +8,27 @@ and finalization in both cases.
8
8
 
9
9
  ## Install
10
10
 
11
+ ### MoltNet Agent for Mac
12
+
13
+ On an Apple Silicon Mac running macOS 13 or newer, download the desktop app
14
+ from [themolt.net/download](https://themolt.net/download). Opening the app:
15
+
16
+ 1. installs or updates the publisher-verified Agent CLI bundle under
17
+ `~/.local/share/moltnet/agent`;
18
+ 2. asks before adding the per-user local HTTPS CA to the login Keychain;
19
+ 3. starts a foreground, supervised Agent Server; and
20
+ 4. opens Console for process-scoped pairing and runtime management.
21
+
22
+ Closing the status window hides it. **Quit and Stop Server** stops the owned
23
+ server process before the app exits. Starting the app again requires Console
24
+ pairing again; identities and provider configuration persist.
25
+
26
+ Agent CLI updates and desktop-app updates use independent signed channels and
27
+ always require consent. Removing the Agent CLI bundle preserves
28
+ `~/.config/moltnet`. Removing the local CA is a separate opt-in action.
29
+
30
+ ### Agent CLI
31
+
11
32
  Install the signed bundle, then use `moltnet-agent` for normal operation:
12
33
 
13
34
  ```bash
package/dist/cli.js CHANGED
@@ -27,7 +27,7 @@ import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
27
27
  import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
28
28
  import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
29
29
  import { homedir, platform, tmpdir } from "node:os";
30
- import { writePiConfig } from "@themoltnet/pi-runtime/pi-config";
30
+ import { PI_MODEL_MODALITIES, writePiConfig } from "@themoltnet/pi-runtime/pi-config";
31
31
  import { AsyncLocalStorage } from "node:async_hooks";
32
32
  import { mkdir, open, readFile, realpath, stat, writeFile } from "node:fs/promises";
33
33
  import { pipeline } from "node:stream/promises";
@@ -3165,7 +3165,8 @@ var TaskMessageKind = Type.Union([
3165
3165
  Type.Literal("tool_call_end"),
3166
3166
  Type.Literal("turn_end"),
3167
3167
  Type.Literal("error"),
3168
- Type.Literal("info")
3168
+ Type.Literal("info"),
3169
+ Type.Literal("tool_policy_decision")
3169
3170
  ], { $id: "TaskMessageKind" });
3170
3171
  var Uuid = Type.String({ format: "uuid" });
3171
3172
  var Cid = Type.String({ minLength: 1 });
@@ -3653,10 +3654,15 @@ Options:
3653
3654
  Default: https://api.themolt.net.
3654
3655
  --heartbeat-interval-ms <n> Child reporter heartbeat cadence. Default: 60000.
3655
3656
  --warm-retention-sec <n> Child session/workspace retention. Default: 1800.
3657
+ --supervised Also stop gracefully when stdin reaches EOF.
3656
3658
 
3657
3659
  On macOS, the first interactive run asks to trust a per-user local CA in the
3658
- login keychain and serves HTTPS. Run \`agent-daemon server trust --remove\` to
3659
- remove that exact CA. Linux continues to use the Chromium PNA HTTP path.
3660
+ login keychain and serves HTTPS. Native supervisors use:
3661
+ server trust --status --json
3662
+ server trust --yes --json
3663
+ server trust --remove --yes --json
3664
+ Run \`agent-daemon server trust --remove\` interactively to remove that exact
3665
+ CA. Linux continues to use the Chromium PNA HTTP path.
3660
3666
  `;
3661
3667
  var PROVIDERS_HELP = `\
3662
3668
  moltnet-agent providers — manage local model providers.
@@ -3665,6 +3671,7 @@ Usage:
3665
3671
  moltnet-agent providers list [--json] [--root <path>]
3666
3672
  moltnet-agent providers set <id> [--base-url <url>] [--api <pi-api-kind>]
3667
3673
  [--model <id> ... | --clear-models]
3674
+ [--model-input <id>=text,image ...]
3668
3675
  [--api-key-stdin | --clear-api-key] [--root <path>]
3669
3676
  moltnet-agent providers discover <id> [--save] [--json] [--root <path>]
3670
3677
  moltnet-agent providers remove <id> [--yes] [--root <path>]
@@ -3675,6 +3682,11 @@ Usage:
3675
3682
  The default root is ~/.config/moltnet. MOLTNET_AGENT_SERVER_ROOT remains the
3676
3683
  environment override. API keys are accepted only from redirected stdin; they
3677
3684
  are stored separately and providers.json contains only a secret reference.
3685
+
3686
+ --model declares a text-only model. --model-input declares a model together
3687
+ with the input modalities it accepts, and is what makes a vision model usable:
3688
+ a model with no declared modalities is text-only to Pi, which drops image
3689
+ content parts before the request leaves the runtime.
3678
3690
  `;
3679
3691
  //#endregion
3680
3692
  //#region src/lib/identity-pin.ts
@@ -5424,6 +5436,17 @@ function resolveAgentServerRoot(input) {
5424
5436
  if (override) return override;
5425
5437
  return getConfigDir();
5426
5438
  }
5439
+ /**
5440
+ * Copy a model entry, dropping an empty `input` so it never reaches the wire.
5441
+ * Not a compatibility shim: `providers.json` has exactly one model shape, and
5442
+ * `validateProviders` rejects anything else on read.
5443
+ */
5444
+ function copyProviderModel(entry) {
5445
+ return {
5446
+ id: entry.id,
5447
+ ...entry.input && entry.input.length > 0 ? { input: [...entry.input] } : {}
5448
+ };
5449
+ }
5427
5450
  function providerEnvName(providerId) {
5428
5451
  return `MOLTNET_PROVIDER_${assertProviderId(providerId).replaceAll("-", "_").toUpperCase()}_API_KEY`;
5429
5452
  }
@@ -5622,6 +5645,7 @@ var AgentServerStore = class {
5622
5645
  for (const [id, provider] of Object.entries(state)) {
5623
5646
  assertProviderId(id);
5624
5647
  assertProviderEnvName(id, provider.envName);
5648
+ for (const model of provider.models ?? []) if (typeof model !== "object" || model === null || !model.id) throw new AgentServerStoreError("invalid_state", `provider "${id}" has a model entry that is not { id, input? }; rewrite providers.json entries as objects`);
5625
5649
  }
5626
5650
  }
5627
5651
  runDir(id) {
@@ -6509,7 +6533,7 @@ function makeTurnEventHandler(base, context = {}) {
6509
6533
  });
6510
6534
  return (event, summary) => {
6511
6535
  if (event === "text_delta") return;
6512
- log[event === "error" ? "warn" : event === "turn_end" ? "info" : "debug"]({
6536
+ log[event === "error" || event === "tool_policy_decision" && summary.decision === "blocked" ? "warn" : event === "turn_end" || event === "tool_policy_decision" ? "info" : "debug"]({
6513
6537
  event,
6514
6538
  ...summary
6515
6539
  }, `turn.${event}`);
@@ -7791,14 +7815,41 @@ var AgentServerModelDiscoveryError = class extends Error {
7791
7815
  this.statusCode = statusCode;
7792
7816
  }
7793
7817
  };
7818
+ /**
7819
+ * The Ollama capability that means a model accepts image input. Ollama also
7820
+ * reports `completion`, `tools`, `thinking` and `embedding`; none of those map
7821
+ * onto a Pi input modality, so only this one is read.
7822
+ */
7823
+ var OLLAMA_VISION_CAPABILITY = "vision";
7824
+ /**
7825
+ * Read Ollama's `capabilities` array, when the endpoint supplies one.
7826
+ *
7827
+ * Returns `undefined` when the field is absent — which is meaningfully
7828
+ * different from "present and without vision". A local Ollama returns
7829
+ * capabilities from `/api/tags`; Ollama Cloud does not, and needs a per-model
7830
+ * `/api/show` probe. Only an explicit absence should trigger that probe.
7831
+ */
7832
+ function readOllamaModalities(value) {
7833
+ if (!isRecord(value) || !Array.isArray(value["capabilities"])) return void 0;
7834
+ return value["capabilities"].includes(OLLAMA_VISION_CAPABILITY) ? ["text", "image"] : [];
7835
+ }
7794
7836
  var ModelDiscoveryCollector = class {
7795
- models = /* @__PURE__ */ new Set();
7837
+ /**
7838
+ * Model id → declared input modalities. `undefined` means the id is known but
7839
+ * its capabilities are not, so it is still a probe candidate; `[]` means the
7840
+ * source answered and the model is text-only.
7841
+ */
7842
+ models = /* @__PURE__ */ new Map();
7843
+ record(id, input) {
7844
+ if (input === void 0 && this.models.has(id)) return;
7845
+ this.models.set(id, input);
7846
+ }
7796
7847
  addOpenAiResponse(value) {
7797
7848
  if (!isRecord(value) || !Array.isArray(value["data"])) return;
7798
7849
  for (const candidate of value["data"]) {
7799
7850
  if (!isRecord(candidate)) continue;
7800
7851
  const id = candidate["id"];
7801
- if (typeof id === "string" && id.length > 0) this.models.add(id);
7852
+ if (typeof id === "string" && id.length > 0) this.record(id, void 0);
7802
7853
  }
7803
7854
  }
7804
7855
  addOllamaResponse(value) {
@@ -7806,16 +7857,28 @@ var ModelDiscoveryCollector = class {
7806
7857
  for (const candidate of value["models"]) {
7807
7858
  if (!isRecord(candidate)) continue;
7808
7859
  const name = candidate["name"];
7809
- if (typeof name === "string" && name.length > 0) this.models.add(name);
7860
+ if (typeof name === "string" && name.length > 0) this.record(name, readOllamaModalities(candidate));
7810
7861
  }
7811
7862
  }
7863
+ /** Attach modalities learned after collection, e.g. from an `/api/show` probe. */
7864
+ setModalities(id, input) {
7865
+ if (this.models.has(id)) this.models.set(id, input);
7866
+ }
7812
7867
  get size() {
7813
7868
  return this.models.size;
7814
7869
  }
7815
7870
  result(providerId, failures) {
7816
7871
  if (this.models.size === 0) throw discoveryFailure(providerId, failures);
7872
+ const ids = [...this.models.keys()].sort().slice(0, 500);
7817
7873
  return {
7818
- models: [...this.models].sort().slice(0, 500),
7874
+ models: ids.map((id) => {
7875
+ const input = this.models.get(id);
7876
+ return input && input.length > 0 ? {
7877
+ id,
7878
+ input: [...input]
7879
+ } : { id };
7880
+ }),
7881
+ unresolved: ids.filter((id) => this.models.get(id) === void 0),
7819
7882
  discoveredCount: this.models.size
7820
7883
  };
7821
7884
  }
@@ -7876,6 +7939,12 @@ function safeErrorToken(value) {
7876
7939
  //#endregion
7877
7940
  //#region src/lib/provider-configuration.ts
7878
7941
  var DEFAULT_PROVIDER_API = "openai-completions";
7942
+ /**
7943
+ * Concurrent `/api/show` probes. Small on purpose: this runs against a
7944
+ * third-party endpoint on an operator's behalf, and discovery is interactive,
7945
+ * so the cap favours being a polite client over shaving a second.
7946
+ */
7947
+ var MODALITY_PROBE_CONCURRENCY = 5;
7879
7948
  var ProviderConfigurationError = class extends Error {
7880
7949
  name = "ProviderConfigurationError";
7881
7950
  constructor(code, message, statusCode, options) {
@@ -7913,7 +7982,7 @@ var ProviderConfigurationService = class {
7913
7982
  api: input.api ?? previous?.api ?? DEFAULT_PROVIDER_API,
7914
7983
  baseUrl,
7915
7984
  envName: assertProviderEnvName(providerId, input.envName ?? previous?.envName ?? providerEnvName(providerId)),
7916
- models: [...input.models ?? previous?.models ?? []],
7985
+ models: (input.models ?? previous?.models ?? []).map(copyProviderModel),
7917
7986
  ...!input.clearApiKey && previous?.apiKeyRef ? { apiKeyRef: previous.apiKeyRef } : {}
7918
7987
  };
7919
7988
  const key = `pi-provider/${providerId}`;
@@ -8014,13 +8083,65 @@ var ProviderConfigurationService = class {
8014
8083
  providerId,
8015
8084
  returnedCount: 500
8016
8085
  }, "Provider model discovery result was truncated");
8017
- if (options.save) await this.set(providerId, { models: result.models }, options);
8086
+ if (isOllamaProvider(providerId, parsed) && result.unresolved.length > 0) await this.resolveOllamaModalities({
8087
+ collector,
8088
+ headers,
8089
+ ids: result.unresolved,
8090
+ origin: parsed.origin,
8091
+ providerId,
8092
+ signal: options.signal
8093
+ });
8094
+ const resolved = collector.result(providerId, failures);
8095
+ const declared = new Map(provider.models.map((model) => [model.id, model.input]));
8096
+ const models = resolved.models.map((model) => {
8097
+ const override = declared.get(model.id);
8098
+ return override && override.length > 0 ? {
8099
+ id: model.id,
8100
+ input: [...override]
8101
+ } : model;
8102
+ });
8103
+ const detected = models.filter((model) => model.input?.includes("image") && !declared.get(model.id)?.includes("image"));
8104
+ if (detected.length > 0) this.logger.info({
8105
+ code: "agent_server_provider_discovery_modalities_detected",
8106
+ models: detected.map((model) => model.id),
8107
+ providerId
8108
+ }, "Provider models reported image input support");
8109
+ if (options.save) await this.set(providerId, { models }, options);
8018
8110
  this.logger.info({
8019
8111
  code: "agent_server_provider_discovery_completed",
8020
- modelCount: result.models.length,
8112
+ modelCount: models.length,
8021
8113
  providerId
8022
8114
  }, "Provider model discovery completed");
8023
- return { models: result.models };
8115
+ return { models };
8116
+ }
8117
+ /**
8118
+ * Fill in modalities Ollama Cloud's `/api/tags` omits, one `/api/show` per
8119
+ * still-unknown model.
8120
+ *
8121
+ * Failures here are deliberately not pushed into the discovery `failures`
8122
+ * array: that array decides the error code of a *failed* discovery, so a
8123
+ * probe rejection must not relabel an otherwise-successful one. A model whose
8124
+ * probe fails simply stays text-only.
8125
+ */
8126
+ async resolveOllamaModalities(input) {
8127
+ const url = `${input.origin}/api/show`;
8128
+ for (let index = 0; index < input.ids.length; index += MODALITY_PROBE_CONCURRENCY) {
8129
+ if (input.signal?.aborted) throw new ProviderConfigurationError("operation_aborted", `provider "${input.providerId}" discovery was cancelled`, 408, { cause: input.signal.reason });
8130
+ const batch = input.ids.slice(index, index + MODALITY_PROBE_CONCURRENCY);
8131
+ await Promise.all(batch.map(async (id) => {
8132
+ const modalities = readOllamaModalities(await this.requestDiscoveryEndpoint({
8133
+ body: { model: id },
8134
+ endpoint: "ollama_show",
8135
+ failures: [],
8136
+ headers: input.headers,
8137
+ method: "POST",
8138
+ providerId: input.providerId,
8139
+ signal: input.signal,
8140
+ url
8141
+ }));
8142
+ if (modalities) input.collector.setModalities(id, modalities);
8143
+ }));
8144
+ }
8024
8145
  }
8025
8146
  async resolveApiKey(providerId, provider) {
8026
8147
  if (!provider.apiKeyRef) return void 0;
@@ -8041,7 +8162,14 @@ var ProviderConfigurationService = class {
8041
8162
  let response;
8042
8163
  try {
8043
8164
  response = await this.fetchImpl(input.url, {
8044
- headers: input.headers,
8165
+ ...input.body ? {
8166
+ body: JSON.stringify(input.body),
8167
+ method: input.method
8168
+ } : {},
8169
+ headers: input.body ? {
8170
+ ...input.headers,
8171
+ "content-type": "application/json"
8172
+ } : input.headers,
8045
8173
  redirect: "error",
8046
8174
  signal: input.signal ? AbortSignal.any([input.signal, timeout]) : timeout
8047
8175
  });
@@ -8108,7 +8236,7 @@ function providerView(provider) {
8108
8236
  api: provider.api,
8109
8237
  baseUrl: provider.baseUrl,
8110
8238
  envName: provider.envName,
8111
- models: [...provider.models],
8239
+ models: provider.models.map(copyProviderModel),
8112
8240
  hasApiKey: Boolean(provider.apiKeyRef)
8113
8241
  };
8114
8242
  }
@@ -8130,6 +8258,31 @@ function providerAbortSource(reason) {
8130
8258
  }
8131
8259
  //#endregion
8132
8260
  //#region src/cli/providers.ts
8261
+ var MODEL_MODALITIES$1 = PI_MODEL_MODALITIES;
8262
+ /**
8263
+ * Build the model list from `--model <id>` (text-only) and
8264
+ * `--model-input <id>=text,image` (declares modalities). Model ids contain
8265
+ * colons, so `=` separates the id from its modality list. A `--model-input`
8266
+ * entry also declares the model, and overrides a bare `--model` for that id.
8267
+ */
8268
+ function parseModelArgs(models, modelInputs) {
8269
+ if (!models && !modelInputs) return void 0;
8270
+ const entries = /* @__PURE__ */ new Map();
8271
+ for (const id of models ?? []) entries.set(id, { id });
8272
+ for (const raw of modelInputs ?? []) {
8273
+ const separator = raw.indexOf("=");
8274
+ if (separator <= 0) throw new ProviderCliError("invalid_arguments", `--model-input expects <model-id>=<modality>[,<modality>], received "${raw}"`);
8275
+ const id = raw.slice(0, separator);
8276
+ const input = raw.slice(separator + 1).split(",").map((value) => value.trim()).filter((value) => value.length > 0);
8277
+ if (input.length === 0) throw new ProviderCliError("invalid_arguments", `--model-input for "${id}" declared no modality`);
8278
+ for (const modality of input) if (!MODEL_MODALITIES$1.includes(modality)) throw new ProviderCliError("invalid_arguments", `--model-input for "${id}" has unknown modality "${modality}"; expected ${MODEL_MODALITIES$1.join(" or ")}`);
8279
+ entries.set(id, {
8280
+ id,
8281
+ input
8282
+ });
8283
+ }
8284
+ return [...entries.values()];
8285
+ }
8133
8286
  async function runProviders(argv, dependencies = {}) {
8134
8287
  if (isHelpFlag(argv) || argv.length === 0) {
8135
8288
  (dependencies.stdout ?? console.log)(PROVIDERS_HELP);
@@ -8203,6 +8356,10 @@ function parseProviderArgs(command, args) {
8203
8356
  type: "string",
8204
8357
  multiple: true
8205
8358
  },
8359
+ "model-input": {
8360
+ type: "string",
8361
+ multiple: true
8362
+ },
8206
8363
  "clear-models": { type: "boolean" },
8207
8364
  "api-key-stdin": { type: "boolean" },
8208
8365
  "clear-api-key": { type: "boolean" }
@@ -8212,6 +8369,7 @@ function parseProviderArgs(command, args) {
8212
8369
  });
8213
8370
  requirePositionals(positionals, 1, "providers set <id>");
8214
8371
  if (values.model && values["clear-models"]) throw new ProviderCliError("invalid_arguments", "--model and --clear-models cannot be used together");
8372
+ if (values["model-input"] && values["clear-models"]) throw new ProviderCliError("invalid_arguments", "--model-input and --clear-models cannot be used together");
8215
8373
  if (values["api-key-stdin"] && values["clear-api-key"]) throw new ProviderCliError("invalid_arguments", "--api-key-stdin and --clear-api-key cannot be used together");
8216
8374
  return {
8217
8375
  command,
@@ -8219,7 +8377,7 @@ function parseProviderArgs(command, args) {
8219
8377
  providerId: positionals[0],
8220
8378
  baseUrl: values["base-url"],
8221
8379
  api: values.api,
8222
- models: values["clear-models"] ? [] : values.model,
8380
+ models: values["clear-models"] ? [] : parseModelArgs(values.model, values["model-input"]),
8223
8381
  apiKeyStdin: values["api-key-stdin"] ?? false,
8224
8382
  clearApiKey: values["clear-api-key"] ?? false
8225
8383
  };
@@ -8374,7 +8532,7 @@ async function discoverProvider(context, parsed) {
8374
8532
  signal: context.signal
8375
8533
  });
8376
8534
  if (parsed.json) context.stdout(JSON.stringify(result));
8377
- else for (const model of result.models) context.stdout(model);
8535
+ else for (const model of result.models) context.stdout(model.input && model.input.length > 0 ? `${model.id}\t${model.input.join(",")}` : model.id);
8378
8536
  return 0;
8379
8537
  }
8380
8538
  async function removeProvider(context, parsed) {
@@ -9694,7 +9852,6 @@ var INHERITED_MOLTNET_ENV_NAMES = new Set([
9694
9852
  "MOLTNET_CLI_LINUX_BINARY",
9695
9853
  "MOLTNET_CREDENTIAL_BINDINGS",
9696
9854
  "MOLTNET_CREDENTIAL_ENFORCEMENT",
9697
- "MOLTNET_DIARY_ID",
9698
9855
  "MOLTNET_GIT_AUTHOR",
9699
9856
  "MOLTNET_OTEL_ENDPOINT",
9700
9857
  "MOLTNET_PI_VM_INTEGRATION",
@@ -9748,7 +9905,8 @@ var RunManager = class {
9748
9905
  XDG_CACHE_HOME: join(homeDir, ".cache"),
9749
9906
  XDG_CONFIG_HOME: join(homeDir, ".config"),
9750
9907
  XDG_DATA_HOME: join(homeDir, ".local", "share"),
9751
- MOLTNET_TEAM_ID: spec.teamId
9908
+ MOLTNET_TEAM_ID: spec.teamId,
9909
+ ...spec.diaryId ? { MOLTNET_DIARY_ID: spec.diaryId } : {}
9752
9910
  };
9753
9911
  const target = activation.source === "managed" ? {
9754
9912
  agentName: activation.alias,
@@ -10371,6 +10529,13 @@ function writeRegistry(path, entries) {
10371
10529
  //#region src/lib/agent-server/protocol.ts
10372
10530
  var DateTime = Type.String({ format: "date-time" });
10373
10531
  var StringList = Type.Array(Type.String());
10532
+ /** A model the provider offers, with the input modalities it accepts. */
10533
+ var ProviderModelSchema = Type.Object({
10534
+ id: Type.String(),
10535
+ input: Type.Optional(Type.Array(Type.Union(PI_MODEL_MODALITIES.map((modality) => Type.Literal(modality))), { minItems: 1 }))
10536
+ });
10537
+ /** One shape on the wire, for both requests and responses. */
10538
+ var ProviderModelList = Type.Array(ProviderModelSchema);
10374
10539
  function schemaRef(schema) {
10375
10540
  const id = schema.$id;
10376
10541
  if (typeof id !== "string" || id.length === 0) throw new Error("Agent Server protocol schemas must have an identifier");
@@ -10403,13 +10568,14 @@ var AgentServerProviderSchema = Type.Object({
10403
10568
  api: Type.String(),
10404
10569
  baseUrl: Type.String({ format: "uri" }),
10405
10570
  envName: Type.String(),
10406
- models: StringList,
10571
+ models: ProviderModelList,
10407
10572
  hasApiKey: Type.Boolean()
10408
10573
  }, { $id: "AgentServerProvider" });
10409
10574
  var AgentServerRunRecordSchema = Type.Object({
10410
10575
  id: Type.String(),
10411
10576
  agent: Type.String(),
10412
10577
  teamId: Type.String(),
10578
+ diaryId: Type.Optional(Type.String()),
10413
10579
  profiles: StringList,
10414
10580
  taskTypes: StringList,
10415
10581
  mode: Type.Union([Type.Literal("poll"), Type.Literal("drain")]),
@@ -10480,13 +10646,14 @@ var PutProviderSchema = Type.Object({
10480
10646
  api: Type.String(),
10481
10647
  baseUrl: Type.String({ format: "uri" }),
10482
10648
  envName: Type.String(),
10483
- models: StringList,
10649
+ models: ProviderModelList,
10484
10650
  apiKey: Type.Optional(Type.String())
10485
10651
  });
10486
- var DiscoverModelsSchema = Type.Object({ models: StringList }, { $id: "DiscoveredModels" });
10652
+ var DiscoverModelsSchema = Type.Object({ models: ProviderModelList }, { $id: "DiscoveredModels" });
10487
10653
  var StartRunSchema = Type.Object({
10488
10654
  agent: Type.String(),
10489
10655
  teamId: Type.String(),
10656
+ diaryId: Type.Optional(Type.String()),
10490
10657
  profiles: StringList,
10491
10658
  taskTypes: Type.Array(AgentServerTaskTypeSchema),
10492
10659
  mode: Type.Union([Type.Literal("poll"), Type.Literal("drain")])
@@ -10798,6 +10965,27 @@ function stringArray(body, field, options = {}) {
10798
10965
  if (!Array.isArray(value) || !options.allowEmpty && value.length === 0 || value.some((item) => typeof item !== "string" || item.length === 0)) throw new AgentServerHttpError(400, "invalid_body", `"${field}" must be ${options.allowEmpty ? "a" : "a non-empty"} string array`);
10799
10966
  return value;
10800
10967
  }
10968
+ var MODEL_MODALITIES = new Set(PI_MODEL_MODALITIES);
10969
+ /** Parse the provider `models` field: `{ id, input? }` entries only. */
10970
+ function modelArray(body, field) {
10971
+ const value = body[field];
10972
+ const invalid = (detail) => {
10973
+ throw new AgentServerHttpError(400, "invalid_body", detail);
10974
+ };
10975
+ if (!Array.isArray(value)) return invalid(`"${field}" must be an array of { id, input? } entries`);
10976
+ return value.map((item) => {
10977
+ if (typeof item !== "object" || item === null || Array.isArray(item)) return invalid(`"${field}" entries must be an { id, input? } object`);
10978
+ const entry = item;
10979
+ const id = entry.id;
10980
+ if (typeof id !== "string" || id.length === 0) return invalid(`"${field}" entries must carry a non-empty "id"`);
10981
+ if (entry.input === void 0) return { id };
10982
+ if (!Array.isArray(entry.input) || entry.input.length === 0 || entry.input.some((modality) => typeof modality !== "string" || !MODEL_MODALITIES.has(modality))) return invalid(`"${field}" entry "${id}" must declare "input" as a non-empty array of "text" or "image"`);
10983
+ return {
10984
+ id,
10985
+ input: entry.input
10986
+ };
10987
+ });
10988
+ }
10801
10989
  function requestOperationSignal(request, shutdownSignal) {
10802
10990
  const disconnected = new AbortController();
10803
10991
  if (request.raw.aborted) disconnected.abort({ source: "request" });
@@ -11018,7 +11206,7 @@ function registerProviderRoutes(app, options, requirePairedOrigin) {
11018
11206
  api: requireString(body, "api"),
11019
11207
  baseUrl: requireString(body, "baseUrl"),
11020
11208
  envName: requireString(body, "envName"),
11021
- models: stringArray(body, "models", { allowEmpty: true }),
11209
+ models: modelArray(body, "models"),
11022
11210
  ...optionalString(body, "apiKey") ? { apiKey: optionalString(body, "apiKey") } : {}
11023
11211
  });
11024
11212
  return reply.code(200).send(entry);
@@ -11078,9 +11266,11 @@ function registerRunRoutes(app, options, requirePairedOrigin) {
11078
11266
  }, async (request, reply) => {
11079
11267
  requirePairedOrigin(request);
11080
11268
  const body = requireBody(request);
11269
+ const diaryId = optionalString(body, "diaryId");
11081
11270
  const record = await runs.start({
11082
11271
  agent: requireString(body, "agent"),
11083
11272
  teamId: requireString(body, "teamId"),
11273
+ ...diaryId ? { diaryId } : {},
11084
11274
  profiles: stringArray(body, "profiles"),
11085
11275
  taskTypes: stringArray(body, "taskTypes"),
11086
11276
  mode: requireString(body, "mode")
@@ -11423,6 +11613,7 @@ async function runAgentServer(argv) {
11423
11613
  const trustRequested = argv[0] === "trust";
11424
11614
  const commandArgs = trustRequested ? argv.slice(1) : argv;
11425
11615
  const envConfig = loadAgentServerEnvConfig();
11616
+ if (trustRequested) return runTrustCommand(commandArgs, resolveAgentServerRoot({ root: envConfig.root }));
11426
11617
  const { values } = parseArgs({
11427
11618
  args: commandArgs,
11428
11619
  options: {
@@ -11432,7 +11623,7 @@ async function runAgentServer(argv) {
11432
11623
  "api-url": { type: "string" },
11433
11624
  "heartbeat-interval-ms": { type: "string" },
11434
11625
  "warm-retention-sec": { type: "string" },
11435
- remove: { type: "boolean" }
11626
+ supervised: { type: "boolean" }
11436
11627
  }
11437
11628
  });
11438
11629
  const port = Number.parseInt(values.port ?? (envConfig.port || `${DEFAULT_PORT}`), 10);
@@ -11445,7 +11636,6 @@ async function runAgentServer(argv) {
11445
11636
  const defaultApiUrl = values["api-url"] ?? (envConfig.apiUrl || DEFAULT_API_URL);
11446
11637
  const runtimeSettings = parseLocalOperationalSettings(values);
11447
11638
  const store = new AgentServerStore(root).ensure();
11448
- if (trustRequested) return runTrustCommand(commandArgs, root);
11449
11639
  const { logger, shutdown: shutdownLogger } = createRootLogger({
11450
11640
  name: "agent-daemon.server",
11451
11641
  level: envConfig.logLevel || "info"
@@ -11512,7 +11702,7 @@ async function runAgentServer(argv) {
11512
11702
  console.error(`config root: ${root}`);
11513
11703
  console.error(`allowed origins: ${allowedOrigins.join(", ")}`);
11514
11704
  console.error("Pair from the Console \"Local runtime\" page; approve the one-click prompt this server opens.");
11515
- return await waitForAgentServerShutdown(runs, app, shutdownController);
11705
+ return await waitForAgentServerShutdown(runs, app, shutdownController, Boolean(values.supervised));
11516
11706
  } catch (cause) {
11517
11707
  await app.close().catch(() => void 0);
11518
11708
  throw cause;
@@ -11533,19 +11723,80 @@ async function runAgentServer(argv) {
11533
11723
  await shutdownLogger();
11534
11724
  }
11535
11725
  }
11536
- async function runTrustCommand(argv, root) {
11537
- if (!isMacos()) {
11538
- console.error("Local HTTPS trust setup is currently supported on macOS only.");
11539
- return 1;
11540
- }
11541
- if (argv.includes("--remove")) {
11542
- await removeLocalCa(root);
11543
- console.error("Removed the MoltNet local CA from your login keychain.");
11726
+ async function runTrustCommand(argv, defaultRoot) {
11727
+ try {
11728
+ const { values } = parseArgs({
11729
+ args: argv,
11730
+ options: {
11731
+ root: { type: "string" },
11732
+ remove: { type: "boolean" },
11733
+ status: { type: "boolean" },
11734
+ yes: { type: "boolean" },
11735
+ json: { type: "boolean" }
11736
+ }
11737
+ });
11738
+ const root = values.root ?? defaultRoot;
11739
+ const statusRequested = Boolean(values.status);
11740
+ const removeRequested = Boolean(values.remove);
11741
+ const yes = Boolean(values.yes);
11742
+ const json = Boolean(values.json);
11743
+ if (statusRequested && (removeRequested || yes)) {
11744
+ console.error("Usage: moltnet-agent server trust --status [--json]");
11745
+ return 1;
11746
+ }
11747
+ if (!isMacos()) {
11748
+ if (json) {
11749
+ printTrustStatus({
11750
+ supported: false,
11751
+ trusted: false,
11752
+ fingerprint: null
11753
+ });
11754
+ return 0;
11755
+ }
11756
+ console.error("Local HTTPS trust setup is currently supported on macOS only.");
11757
+ return 1;
11758
+ }
11759
+ const material = await ensureLocalTlsMaterial(root);
11760
+ if (statusRequested) {
11761
+ const trusted = await isLocalCaTrusted(root);
11762
+ if (json) printTrustStatus({
11763
+ supported: true,
11764
+ trusted,
11765
+ fingerprint: material.fingerprint
11766
+ });
11767
+ else console.log(trusted ? `MoltNet local CA ${material.fingerprint} is trusted.` : `MoltNet local CA ${material.fingerprint} is not trusted.`);
11768
+ return 0;
11769
+ }
11770
+ if (json && !yes) {
11771
+ console.error("Machine-readable trust changes require --yes after native app consent.");
11772
+ return 1;
11773
+ }
11774
+ if (removeRequested) {
11775
+ await removeLocalCa(root);
11776
+ if (json) printTrustStatus({
11777
+ supported: true,
11778
+ trusted: false,
11779
+ fingerprint: material.fingerprint
11780
+ });
11781
+ else console.log("Removed the MoltNet local CA from your login keychain.");
11782
+ return 0;
11783
+ }
11784
+ if (yes) await trustLocalCa(root);
11785
+ else await ensureTrustedLocalTls(root);
11786
+ if (json) printTrustStatus({
11787
+ supported: true,
11788
+ trusted: await isLocalCaTrusted(root),
11789
+ fingerprint: material.fingerprint
11790
+ });
11791
+ else console.log("MoltNet local HTTPS trust is ready for this macOS user.");
11544
11792
  return 0;
11793
+ } catch (cause) {
11794
+ console.error(`Agent Server trust command failed: ${cause instanceof Error ? cause.message : String(cause)}`);
11795
+ return 1;
11545
11796
  }
11546
- await ensureTrustedLocalTls(root);
11547
- console.error("MoltNet local HTTPS trust is ready for this macOS user.");
11548
- return 0;
11797
+ }
11798
+ function printTrustStatus(status) {
11799
+ console.log(JSON.stringify(status));
11549
11800
  }
11550
11801
  async function ensureTrustedLocalTls(root) {
11551
11802
  const material = await ensureLocalTlsMaterial(root);
@@ -11564,12 +11815,13 @@ async function ensureTrustedLocalTls(root) {
11564
11815
  await trustLocalCa(root);
11565
11816
  return material;
11566
11817
  }
11567
- function waitForAgentServerShutdown(runs, app, shutdownController) {
11818
+ function waitForAgentServerShutdown(runs, app, shutdownController, supervised) {
11568
11819
  return new Promise((resolvePromise) => {
11569
11820
  let shuttingDown = false;
11570
- const shutdown = () => {
11821
+ const shutdown = (source) => {
11571
11822
  if (shuttingDown) return;
11572
11823
  shuttingDown = true;
11824
+ if (source === "stdin") console.error("shutting down: stdin EOF");
11573
11825
  shutdownController.abort({ source: "shutdown" });
11574
11826
  (async () => {
11575
11827
  app.server.closeAllConnections();
@@ -11595,16 +11847,31 @@ function waitForAgentServerShutdown(runs, app, shutdownController) {
11595
11847
  const failures = results.filter((result) => result.status === "rejected");
11596
11848
  for (const failure of failures) console.error(`shutdown cleanup failed: ${failure.reason.message}`);
11597
11849
  handlers.dispose();
11850
+ stdinGuard.dispose();
11598
11851
  const exitCode = typeof process.exitCode === "number" ? process.exitCode : 0;
11599
11852
  resolvePromise(failures.length > 0 ? 1 : exitCode);
11600
11853
  })();
11601
11854
  };
11602
11855
  const handlers = installShutdownSignalHandlers({
11603
11856
  logDrain: () => console.error("shutting down: stopping runs…"),
11604
- drain: shutdown
11857
+ drain: () => shutdown()
11858
+ });
11859
+ const stdinGuard = installSupervisedStdinGuard({
11860
+ enabled: supervised,
11861
+ shutdown: () => shutdown("stdin")
11605
11862
  });
11606
11863
  });
11607
11864
  }
11865
+ /** Makes the supervising process's stdin pipe part of the server lifecycle. */
11866
+ function installSupervisedStdinGuard(options) {
11867
+ if (!options.enabled) return { dispose: () => void 0 };
11868
+ const input = options.input ?? process.stdin;
11869
+ const onEnd = () => options.shutdown();
11870
+ input.once("end", onEnd);
11871
+ input.resume();
11872
+ if (input.readableEnded) queueMicrotask(onEnd);
11873
+ return { dispose: () => input.off("end", onEnd) };
11874
+ }
11608
11875
  //#endregion
11609
11876
  //#region src/lib/runtime-session-sync.ts
11610
11877
  async function syncRuntimeSessions(deps, input) {
@@ -11942,7 +12209,7 @@ async function writeCache(cache) {
11942
12209
  }
11943
12210
  //#endregion
11944
12211
  //#region src/version.ts
11945
- var DAEMON_VERSION = "0.57.1";
12212
+ var DAEMON_VERSION = "0.59.0";
11946
12213
  //#endregion
11947
12214
  //#region src/cli.ts
11948
12215
  async function runAgentDaemonCli(options) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@themoltnet/agent-daemon",
3
- "version": "0.57.1",
3
+ "version": "0.59.0",
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.",
@@ -86,9 +86,9 @@
86
86
  "reflect-metadata": "^0.2.2",
87
87
  "typebox": "^1.2.8",
88
88
  "@themoltnet/agent-runtime": "1.0.3",
89
- "@themoltnet/sdk": "0.142.0",
90
- "@themoltnet/pi-runtime": "0.16.0",
91
- "@themoltnet/os-keyring": "0.3.0"
89
+ "@themoltnet/pi-runtime": "0.18.0",
90
+ "@themoltnet/os-keyring": "0.3.0",
91
+ "@themoltnet/sdk": "0.142.0"
92
92
  },
93
93
  "devDependencies": {
94
94
  "@fastify/swagger": "^9.6.1",
@@ -98,16 +98,16 @@
98
98
  "vite": "^8.0.0",
99
99
  "vite-plugin-dts": "^4.5.4",
100
100
  "vitest": "^3.0.0",
101
- "@moltnet/api-client": "0.1.0",
102
101
  "@moltnet/agent-eval": "0.1.0",
103
- "@moltnet/execution-integrations": "0.1.0",
102
+ "@moltnet/bootstrap": "0.1.0",
104
103
  "@moltnet/crypto-service": "0.1.0",
104
+ "@moltnet/api-client": "0.1.0",
105
+ "@moltnet/execution-integrations": "0.1.0",
105
106
  "@moltnet/execution-plan": "0.1.0",
106
107
  "@moltnet/loopback-companion": "0.1.0",
107
108
  "@moltnet/observability": "0.1.0",
108
- "@moltnet/models": "0.1.0",
109
- "@moltnet/bootstrap": "0.1.0",
110
109
  "@moltnet/runtime-profiles": "0.1.0",
110
+ "@moltnet/models": "0.1.0",
111
111
  "@moltnet/tasks": "0.1.0"
112
112
  },
113
113
  "nx": {