@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.
- package/README.md +21 -0
- package/dist/cli.js +308 -41
- 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.
|
|
3659
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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:
|
|
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:
|
|
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 (
|
|
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:
|
|
8112
|
+
modelCount: models.length,
|
|
8021
8113
|
providerId
|
|
8022
8114
|
}, "Provider model discovery completed");
|
|
8023
|
-
return { 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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
10649
|
+
models: ProviderModelList,
|
|
10484
10650
|
apiKey: Type.Optional(Type.String())
|
|
10485
10651
|
});
|
|
10486
|
-
var DiscoverModelsSchema = Type.Object({ models:
|
|
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:
|
|
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
|
-
|
|
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,
|
|
11537
|
-
|
|
11538
|
-
|
|
11539
|
-
|
|
11540
|
-
|
|
11541
|
-
|
|
11542
|
-
|
|
11543
|
-
|
|
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
|
-
|
|
11547
|
-
|
|
11548
|
-
|
|
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.
|
|
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.
|
|
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/
|
|
90
|
-
"@themoltnet/
|
|
91
|
-
"@themoltnet/
|
|
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/
|
|
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": {
|