@themoltnet/agent-daemon 0.58.0 → 0.60.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 +23 -11
- package/dist/cli.js +124 -85
- package/package.json +12 -12
package/README.md
CHANGED
|
@@ -114,7 +114,7 @@ All config flows from environment variables. The daemon reads them in
|
|
|
114
114
|
| Var | Required | Purpose |
|
|
115
115
|
| --------------------- | ---------------------------------- | ------------------------------------------------------------------------- |
|
|
116
116
|
| `GIT_CONFIG_GLOBAL` | config-based | Optional git identity path; not needed for configless startup. |
|
|
117
|
-
| `MOLTNET_AGENT_NAME` | yes |
|
|
117
|
+
| `MOLTNET_AGENT_NAME` | yes | Central identity alias (`identities/<name>/`). |
|
|
118
118
|
| `MOLTNET_API_URL` | configless only | Explicit API endpoint; configless runs never read it from `moltnet.json`. |
|
|
119
119
|
| `MOLTNET_AGENT_KEY` | no | Team- or identity-scoped agent key. Overrides `moltnet.json`. |
|
|
120
120
|
| `MOLTNET_PRIVATE_KEY` | configless `once`, `poll`, `drain` | Base64 Ed25519 seed used by daemon-owned executor attestation. |
|
|
@@ -133,18 +133,19 @@ its gitconfig in `.moltnet/<agent>/`. Three paths create it:
|
|
|
133
133
|
does the same for coding agents that need git and GitHub.
|
|
134
134
|
|
|
135
135
|
**The daemon runs on an agent key only.** OAuth2 client_credentials is not
|
|
136
|
-
accepted: it hands the daemon the full
|
|
136
|
+
accepted: it hands the daemon the full agent OAuth2 grant against a least-privilege
|
|
137
137
|
need, and a Hydra token cannot be a Talos derivation parent. A `moltnet.json`
|
|
138
|
-
without `agent_key_ref` is refused at startup with the command that fixes it.
|
|
138
|
+
without `agent_key_ref` or `agent_key_refs` is refused at startup with the command that fixes it.
|
|
139
139
|
|
|
140
140
|
The key reaches the daemon two ways, and `MOLTNET_AGENT_KEY` wins when both are
|
|
141
141
|
present:
|
|
142
142
|
|
|
143
143
|
- **Configless** — `MOLTNET_AGENT_KEY` (or `MOLTNET_AGENT_KEY_REF`) in the
|
|
144
144
|
environment. No agent files are read at all.
|
|
145
|
-
- **From `moltnet.json`** —
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
- **From `moltnet.json`** — `agent_key_refs[teamId]` selects the run team's
|
|
146
|
+
provider reference. `agent_key_ref` is used only if that team has no entry.
|
|
147
|
+
A configured entry that fails never falls back. The plaintext secret never
|
|
148
|
+
lands in the file.
|
|
148
149
|
|
|
149
150
|
Mint or rotate the key with the CLI, which is separate operator tooling and
|
|
150
151
|
keeps using OAuth2 for its own authentication:
|
|
@@ -155,14 +156,21 @@ moltnet agents keys create --agent-id <uuid> --team-id <uuid> \
|
|
|
155
156
|
moltnet agents keys rotate <key-id> --team-id <uuid> --store
|
|
156
157
|
```
|
|
157
158
|
|
|
159
|
+
To enroll the same identity into another team, use
|
|
160
|
+
`moltnet teams join --code <code> --issue-agent-key --store --idempotency-key <uuid>`.
|
|
161
|
+
Each Agent Server run selects its `teamId` before activation and profile lookup;
|
|
162
|
+
concurrent runs do not change a shared team selector. New runs reload credentials,
|
|
163
|
+
so restart active runs after rotating a key. See the
|
|
164
|
+
[enrollment and recovery guide](../../docs/operate/agent-keys.md#enroll-an-existing-agent-into-another-team).
|
|
165
|
+
|
|
158
166
|
The daemon reconciles a team-bound key against `--team` at startup; an
|
|
159
167
|
identity-scoped key may select any team where the agent is authorized. It fails
|
|
160
168
|
fast if the key is rejected, is not an agent, or a team binding mismatches. See
|
|
161
169
|
[Run the daemon with an agent key](../../docs/operate/agent-keys.md#run-the-daemon-with-an-agent-key).
|
|
162
170
|
|
|
163
171
|
Daemon authentication and the guest boundary are two separate concerns. Where
|
|
164
|
-
the agent key comes from (the environment, or
|
|
165
|
-
|
|
172
|
+
the agent key comes from (the environment, or a selected key reference in the
|
|
173
|
+
central identity's `moltnet.json` resolved through the host secret provider)
|
|
166
174
|
decides how the host-side SDK `Agent` is built. The guest boundary is fixed: **the guest never
|
|
167
175
|
receives MoltNet credential material.** No `.moltnet` file, gitconfig, SSH
|
|
168
176
|
signing key, GitHub App PEM, or MoltNet environment credential is injected into
|
|
@@ -196,9 +204,13 @@ An agent key used by the daemon needs this least-privilege scope set:
|
|
|
196
204
|
agent:profile crypto:sign runtime:read task:read task:claim task:execute
|
|
197
205
|
```
|
|
198
206
|
|
|
199
|
-
The
|
|
200
|
-
|
|
201
|
-
|
|
207
|
+
The daemon also uses `diary:read`, `team:read` and `team:join` when the key
|
|
208
|
+
carries them -- read access to the agent's teams and their diaries, and
|
|
209
|
+
enrollment into a team. Startup does not check for them: a key without them
|
|
210
|
+
claims and runs work, and the daemon reports which are absent.
|
|
211
|
+
|
|
212
|
+
The Console selects the full set by default. Knowledge-enabled workers must add
|
|
213
|
+
`diary:write`, `pack:read`, and `pack:write` when the key is issued.
|
|
202
214
|
|
|
203
215
|
`crypto:sign` is in the minimum because host-capability signing runs on the
|
|
204
216
|
daemon's own credential: the local seed signer calls the signing-request
|
package/dist/cli.js
CHANGED
|
@@ -14,7 +14,7 @@ import { AgentRuntime, ApiTaskReporter, ApiTaskSource, PollingApiTaskSource, cre
|
|
|
14
14
|
import { GuestEnvironmentBoundaryError, assertGuestEnvironmentBoundary, createPiRetryTriage, findMainWorktree, isResolvedPathInsideRoot, normalizeRetryTriageResult, redactRetryTriageSecrets, resolveRuntimeProfileModel } from "@themoltnet/pi-runtime";
|
|
15
15
|
import { FILE_SECRET_PROVIDER, FileSecretProvider, RegisterIdentityError, boundedIdentitySignal, connect, createNodeSecretProviderRegistry, register } from "@themoltnet/sdk/node";
|
|
16
16
|
import { constants, copyFileSync, createReadStream, createWriteStream, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
17
|
-
import { AuthenticationError, IDENTITY_ALIAS_PATTERN, MoltNetError, assertIdentityAlias, assertTrustedConfigApiUrl, createExecutorAttestor, formatSecretReferenceString, getConfigDir, getIdentityDir, isCanonicalConfig, parseSecretReferenceString, readConfig, requireSecureCredentialApiUrl, resolveAgentKey, resolveEnvSecretReference, resolveIdentitySeed } from "@themoltnet/sdk";
|
|
17
|
+
import { AuthenticationError, IDENTITY_ALIAS_PATTERN, MoltNetError, assertIdentityAlias, assertTrustedConfigApiUrl, createExecutorAttestor, formatSecretReferenceString, getConfigDir, getIdentityDir, hasAgentKeyConfiguration, isCanonicalConfig, parseSecretReferenceString, readConfig, requireSecureCredentialApiUrl, resolveAgentKey, resolveEnvSecretReference, resolveIdentitySeed, selectAgentKeyReference } from "@themoltnet/sdk";
|
|
18
18
|
import { execFile, spawn } from "node:child_process";
|
|
19
19
|
import { X509Certificate, createHash, createPrivateKey, createPublicKey, randomBytes, randomUUID, timingSafeEqual, webcrypto } from "node:crypto";
|
|
20
20
|
import { once } from "node:events";
|
|
@@ -774,13 +774,24 @@ var CREDENTIAL_SCOPES = {
|
|
|
774
774
|
TaskManage: "task:manage",
|
|
775
775
|
TaskRead: "task:read",
|
|
776
776
|
TaskWrite: "task:write",
|
|
777
|
+
TeamJoin: "team:join",
|
|
777
778
|
TeamManage: "team:manage",
|
|
778
779
|
TeamRead: "team:read"
|
|
779
780
|
};
|
|
780
781
|
var ALL_CREDENTIAL_SCOPES = Object.freeze(Object.values(CREDENTIAL_SCOPES));
|
|
781
782
|
/**
|
|
782
|
-
*
|
|
783
|
-
*
|
|
783
|
+
* What the agent daemon cannot run without, checked against
|
|
784
|
+
* `GET /agents/whoami` at startup. Task credentials attenuate it further to
|
|
785
|
+
* `task:execute` alone.
|
|
786
|
+
*
|
|
787
|
+
* This is the **boot floor**, and deliberately not the same list as
|
|
788
|
+
* `AGENT_CREDENTIAL_SCOPES`. A credential's scopes are fixed when it is minted
|
|
789
|
+
* and `POST /agent-keys` caps a new key at the scopes of the credential
|
|
790
|
+
* requesting it, so no key can ever widen itself. A scope added here therefore
|
|
791
|
+
* stops every daemon already in the field, and only a human with a Console
|
|
792
|
+
* session can mint the replacement. Add one only when the daemon genuinely
|
|
793
|
+
* cannot work without it; anything a caller merely benefits from belongs in
|
|
794
|
+
* `DAEMON_OPTIONAL_SCOPES`, where absence costs a capability instead.
|
|
784
795
|
*
|
|
785
796
|
* `crypto:sign` is part of the minimum because host-capability signing runs on
|
|
786
797
|
* the daemon's own credential: the local seed signer calls the signing-request
|
|
@@ -788,7 +799,7 @@ var ALL_CREDENTIAL_SCOPES = Object.freeze(Object.values(CREDENTIAL_SCOPES));
|
|
|
788
799
|
* cleanly and then fails the first time guest code signs a diary entry or a
|
|
789
800
|
* commit.
|
|
790
801
|
*/
|
|
791
|
-
var
|
|
802
|
+
var DAEMON_MINIMUM_SCOPES = [
|
|
792
803
|
CREDENTIAL_SCOPES.AgentProfile,
|
|
793
804
|
CREDENTIAL_SCOPES.CryptoSign,
|
|
794
805
|
CREDENTIAL_SCOPES.RuntimeRead,
|
|
@@ -796,6 +807,22 @@ var AGENT_CREDENTIAL_SCOPES = [
|
|
|
796
807
|
CREDENTIAL_SCOPES.TaskClaim,
|
|
797
808
|
CREDENTIAL_SCOPES.TaskExecute
|
|
798
809
|
];
|
|
810
|
+
/**
|
|
811
|
+
* Read and enrollment authority a daemon uses when it has it, and runs without
|
|
812
|
+
* when it does not: reading the teams it belongs to and their diaries, and
|
|
813
|
+
* joining a team it is not yet a member of.
|
|
814
|
+
*
|
|
815
|
+
* Which product surface each one enables is deliberately not recorded here.
|
|
816
|
+
* That mapping belongs to whatever consumes the scope and changes with it,
|
|
817
|
+
* while the scope names are the contract and do not.
|
|
818
|
+
*/
|
|
819
|
+
var DAEMON_OPTIONAL_SCOPES = [
|
|
820
|
+
CREDENTIAL_SCOPES.DiaryRead,
|
|
821
|
+
CREDENTIAL_SCOPES.TeamRead,
|
|
822
|
+
CREDENTIAL_SCOPES.TeamJoin
|
|
823
|
+
];
|
|
824
|
+
/** What a newly issued agent key should carry: the floor plus the rest. */
|
|
825
|
+
var AGENT_CREDENTIAL_SCOPES = [...DAEMON_MINIMUM_SCOPES, ...DAEMON_OPTIONAL_SCOPES];
|
|
799
826
|
CREDENTIAL_SCOPES.AgentProfile, CREDENTIAL_SCOPES.TaskRead, CREDENTIAL_SCOPES.TaskWrite;
|
|
800
827
|
CREDENTIAL_SCOPES.AgentProfile, CREDENTIAL_SCOPES.DiaryRead, CREDENTIAL_SCOPES.PackRead, CREDENTIAL_SCOPES.RuntimeRead, CREDENTIAL_SCOPES.TaskRead, CREDENTIAL_SCOPES.TeamRead;
|
|
801
828
|
Object.freeze(ALL_CREDENTIAL_SCOPES.filter((scope) => scope !== CREDENTIAL_SCOPES.HumanProfile));
|
|
@@ -819,6 +846,7 @@ var MCP_CLIENT_SCOPES = [
|
|
|
819
846
|
CREDENTIAL_SCOPES.TaskManage,
|
|
820
847
|
CREDENTIAL_SCOPES.TaskRead,
|
|
821
848
|
CREDENTIAL_SCOPES.TaskWrite,
|
|
849
|
+
CREDENTIAL_SCOPES.TeamJoin,
|
|
822
850
|
CREDENTIAL_SCOPES.TeamManage,
|
|
823
851
|
CREDENTIAL_SCOPES.TeamRead
|
|
824
852
|
];
|
|
@@ -1172,17 +1200,16 @@ Type.Object({
|
|
|
1172
1200
|
Type.Literal("executor"),
|
|
1173
1201
|
Type.Literal("member")
|
|
1174
1202
|
])),
|
|
1175
|
-
maxUses: Type.Optional(Type.Integer({
|
|
1176
|
-
minimum: 1,
|
|
1177
|
-
default: 1
|
|
1178
|
-
})),
|
|
1179
1203
|
expiresInHours: Type.Optional(Type.Integer({
|
|
1180
1204
|
minimum: 1,
|
|
1181
1205
|
maximum: 720,
|
|
1182
1206
|
default: 168
|
|
1183
1207
|
}))
|
|
1184
1208
|
});
|
|
1185
|
-
Type.Object({
|
|
1209
|
+
Type.Object({
|
|
1210
|
+
code: Type.String({ minLength: 1 }),
|
|
1211
|
+
issueAgentKey: Type.Optional(Type.Literal(true))
|
|
1212
|
+
});
|
|
1186
1213
|
Type.Object({ role: Type.Union([
|
|
1187
1214
|
Type.Literal("manager"),
|
|
1188
1215
|
Type.Literal("executor"),
|
|
@@ -1207,8 +1234,7 @@ Type.Object({
|
|
|
1207
1234
|
Type.Literal("executor"),
|
|
1208
1235
|
Type.Literal("member")
|
|
1209
1236
|
]),
|
|
1210
|
-
|
|
1211
|
-
useCount: Type.Integer(),
|
|
1237
|
+
usedAt: Type.Union([Type.String({ format: "date-time" }), Type.Null()]),
|
|
1212
1238
|
expiresAt: DateTimeUnsafe,
|
|
1213
1239
|
createdAt: DateTimeUnsafe
|
|
1214
1240
|
});
|
|
@@ -1239,11 +1265,7 @@ Type.Object({
|
|
|
1239
1265
|
});
|
|
1240
1266
|
Type.Object({
|
|
1241
1267
|
teamId: UuidSchema,
|
|
1242
|
-
role:
|
|
1243
|
-
Type.Literal("manager"),
|
|
1244
|
-
Type.Literal("executor"),
|
|
1245
|
-
Type.Literal("member")
|
|
1246
|
-
])
|
|
1268
|
+
role: TeamRoleSchema
|
|
1247
1269
|
});
|
|
1248
1270
|
Type.Object({
|
|
1249
1271
|
updated: Type.Boolean(),
|
|
@@ -3165,7 +3187,8 @@ var TaskMessageKind = Type.Union([
|
|
|
3165
3187
|
Type.Literal("tool_call_end"),
|
|
3166
3188
|
Type.Literal("turn_end"),
|
|
3167
3189
|
Type.Literal("error"),
|
|
3168
|
-
Type.Literal("info")
|
|
3190
|
+
Type.Literal("info"),
|
|
3191
|
+
Type.Literal("tool_policy_decision")
|
|
3169
3192
|
], { $id: "TaskMessageKind" });
|
|
3170
3193
|
var Uuid = Type.String({ format: "uuid" });
|
|
3171
3194
|
var Cid = Type.String({ minLength: 1 });
|
|
@@ -3500,10 +3523,11 @@ Run \`agent-daemon <command> --help\` for command-specific flags.
|
|
|
3500
3523
|
Prerequisites:
|
|
3501
3524
|
- configless: MOLTNET_AGENT_KEY (or MOLTNET_AGENT_KEY_REF) and
|
|
3502
3525
|
MOLTNET_PRIVATE_KEY (or MOLTNET_PRIVATE_KEY_REF); no agent files
|
|
3503
|
-
- config-based
|
|
3504
|
-
carrying agent_key_ref (OAuth2
|
|
3526
|
+
- config-based: ~/.config/moltnet/identities/<agent>/moltnet.json
|
|
3527
|
+
carrying agent_key_refs or agent_key_ref (OAuth2 is not accepted)
|
|
3528
|
+
--agent-root explicitly selects a legacy .moltnet/<agent> bundle
|
|
3505
3529
|
|
|
3506
|
-
No key yet? Mint one with the CLI (--store writes
|
|
3530
|
+
No key yet? Mint one with the CLI (--store writes the team slot into
|
|
3507
3531
|
moltnet.json and keeps the secret in a provider):
|
|
3508
3532
|
|
|
3509
3533
|
moltnet teams list # find the team id
|
|
@@ -3711,6 +3735,10 @@ function assessAgentStartupPin(current, expected) {
|
|
|
3711
3735
|
};
|
|
3712
3736
|
return { ok: true };
|
|
3713
3737
|
}
|
|
3738
|
+
/** A configured team slot must authenticate with that exact team ceiling. */
|
|
3739
|
+
function matchesCredentialTeam(current, credentialTeamId) {
|
|
3740
|
+
return !credentialTeamId || current.credentialBinding?.bindingScope === "team" && current.credentialBinding.boundTeamId === credentialTeamId;
|
|
3741
|
+
}
|
|
3714
3742
|
//#endregion
|
|
3715
3743
|
//#region src/lib/agent-context.ts
|
|
3716
3744
|
/**
|
|
@@ -3754,7 +3782,7 @@ function detectCredentialSource(env) {
|
|
|
3754
3782
|
function assessStartupBinding(whoami, teamId) {
|
|
3755
3783
|
if (whoami.subjectType !== "agent") return {
|
|
3756
3784
|
ok: false,
|
|
3757
|
-
reason: `the daemon must authenticate as an agent, but whoami reported subjectType "${whoami.subjectType}". Provide agent credentials (an agent key
|
|
3785
|
+
reason: `the daemon must authenticate as an agent, but whoami reported subjectType "${whoami.subjectType}". Provide agent credentials (an agent key).`
|
|
3758
3786
|
};
|
|
3759
3787
|
const boundTeamId = whoami.credentialBinding?.bindingScope === "team" ? whoami.credentialBinding.boundTeamId : void 0;
|
|
3760
3788
|
if (teamId && boundTeamId && boundTeamId !== teamId) return {
|
|
@@ -3767,8 +3795,7 @@ function assessStartupBinding(whoami, teamId) {
|
|
|
3767
3795
|
* Validate at startup — after `connect()`, before polling — that the connected
|
|
3768
3796
|
* credential can operate as `teamId`, failing fast with an actionable message
|
|
3769
3797
|
* instead of letting an obscure 401/403 surface mid-poll. Runs in both auth
|
|
3770
|
-
*
|
|
3771
|
-
* subject-type check. Returns the `whoami` so the caller can log the resolved
|
|
3798
|
+
* credential sources and also checks API reachability and subject type. Returns the `whoami` so the caller can log the resolved
|
|
3772
3799
|
* identity (never the secret).
|
|
3773
3800
|
*/
|
|
3774
3801
|
async function validateStartupBinding(options) {
|
|
@@ -3784,6 +3811,7 @@ async function validateStartupBinding(options) {
|
|
|
3784
3811
|
setTimeout(resolve, 100 * attempt);
|
|
3785
3812
|
});
|
|
3786
3813
|
}
|
|
3814
|
+
if (!matchesCredentialTeam(whoami, options.credentialTeamId)) throw new Error("Daemon startup validation failed: selected team credential has a different binding.");
|
|
3787
3815
|
const assessment = assessStartupBinding(whoami, options.teamId);
|
|
3788
3816
|
if (!assessment.ok) throw new Error(`Daemon startup validation failed: ${assessment.reason}`);
|
|
3789
3817
|
const expected = options.expectedAgent;
|
|
@@ -3806,9 +3834,9 @@ async function resolveAgentContext(agentName, options = {}) {
|
|
|
3806
3834
|
credentialSource: "environment"
|
|
3807
3835
|
};
|
|
3808
3836
|
const config = await readConfig(agentDir);
|
|
3809
|
-
if (!config
|
|
3837
|
+
if (!config || !hasAgentKeyConfiguration(config)) throw new Error(agentKeyRequiredMessage(agentDir, agentName));
|
|
3810
3838
|
const secretProviders = createNodeSecretProviderRegistry();
|
|
3811
|
-
const agentKey = await resolveAgentKey(config, secretProviders);
|
|
3839
|
+
const agentKey = await resolveAgentKey(config, secretProviders, options.teamId);
|
|
3812
3840
|
if (!agentKey) throw new Error(agentKeyRequiredMessage(agentDir, agentName));
|
|
3813
3841
|
return {
|
|
3814
3842
|
agentDir,
|
|
@@ -3819,7 +3847,8 @@ async function resolveAgentContext(agentName, options = {}) {
|
|
|
3819
3847
|
agentKey,
|
|
3820
3848
|
apiUrl: resolveConfigApiUrl(config, options.envApiUrl)
|
|
3821
3849
|
}),
|
|
3822
|
-
credentialSource: "config"
|
|
3850
|
+
credentialSource: "config",
|
|
3851
|
+
credentialTeamId: selectAgentKeyReference(config, options.teamId)?.teamId
|
|
3823
3852
|
};
|
|
3824
3853
|
}
|
|
3825
3854
|
/**
|
|
@@ -3830,7 +3859,7 @@ async function resolveAgentContext(agentName, options = {}) {
|
|
|
3830
3859
|
*/
|
|
3831
3860
|
function agentKeyRequiredMessage(agentDir, agentName) {
|
|
3832
3861
|
return [
|
|
3833
|
-
`${join(agentDir, "moltnet.json")} has no "agent_key_ref". The daemon`,
|
|
3862
|
+
`${join(agentDir, "moltnet.json")} has no "agent_key_ref" or "agent_key_refs". The daemon`,
|
|
3834
3863
|
`requires a team-bound agent key; OAuth2 client_credentials is no longer`,
|
|
3835
3864
|
`accepted.`,
|
|
3836
3865
|
``,
|
|
@@ -3843,7 +3872,7 @@ function agentKeyRequiredMessage(agentDir, agentName) {
|
|
|
3843
3872
|
` --scopes ${DAEMON_KEY_SCOPES.join(",")} \\`,
|
|
3844
3873
|
` --store`,
|
|
3845
3874
|
``,
|
|
3846
|
-
`--store writes "
|
|
3875
|
+
`--store writes the team slot in "agent_key_refs" and keeps the secret in`,
|
|
3847
3876
|
`a provider, so the key itself never lands in the file. Omit --scopes to`,
|
|
3848
3877
|
`get the same daemon minimum by default.`,
|
|
3849
3878
|
``,
|
|
@@ -4557,7 +4586,14 @@ function recoverScratchWorkspacePath(producer, stateDirs) {
|
|
|
4557
4586
|
}
|
|
4558
4587
|
//#endregion
|
|
4559
4588
|
//#region src/lib/executor-attestation.ts
|
|
4560
|
-
|
|
4589
|
+
/**
|
|
4590
|
+
* The startup gate, which is the boot floor rather than the issuance default.
|
|
4591
|
+
* Gating on the wider `AGENT_CREDENTIAL_SCOPES` would refuse every key minted
|
|
4592
|
+
* before a scope was added to it, and a key cannot widen itself.
|
|
4593
|
+
*/
|
|
4594
|
+
var DAEMON_REQUIRED_SCOPES = DAEMON_MINIMUM_SCOPES;
|
|
4595
|
+
/** What `moltnet agents keys create` should mint for a new daemon. */
|
|
4596
|
+
var DAEMON_RECOMMENDED_SCOPES = AGENT_CREDENTIAL_SCOPES;
|
|
4561
4597
|
async function resolveExecutorSigningPrivateKey(input) {
|
|
4562
4598
|
if (input.credentialSource === "environment") {
|
|
4563
4599
|
const privateKey = input.configuredPrivateKey.trim();
|
|
@@ -4584,7 +4620,7 @@ async function resolveExecutorSigningPrivateKey(input) {
|
|
|
4584
4620
|
function validateDaemonScopes(whoami) {
|
|
4585
4621
|
const available = new Set(whoami.scopes ?? []);
|
|
4586
4622
|
const missing = DAEMON_REQUIRED_SCOPES.filter((scope) => !available.has(scope));
|
|
4587
|
-
if (missing.length > 0) throw new Error(`Daemon startup credential is missing required scopes: ${missing.join(" ")}. Issue a replacement credential with ${
|
|
4623
|
+
if (missing.length > 0) throw new Error(`Daemon startup credential is missing required scopes: ${missing.join(" ")}. Issue a replacement credential with ${DAEMON_RECOMMENDED_SCOPES.join(" ")}.`);
|
|
4588
4624
|
}
|
|
4589
4625
|
async function validateExecutorSigningIdentity(input) {
|
|
4590
4626
|
let publicKey;
|
|
@@ -6532,7 +6568,7 @@ function makeTurnEventHandler(base, context = {}) {
|
|
|
6532
6568
|
});
|
|
6533
6569
|
return (event, summary) => {
|
|
6534
6570
|
if (event === "text_delta") return;
|
|
6535
|
-
log[event === "error" ? "warn" : event === "turn_end" ? "info" : "debug"]({
|
|
6571
|
+
log[event === "error" || event === "tool_policy_decision" && summary.decision === "blocked" ? "warn" : event === "turn_end" || event === "tool_policy_decision" ? "info" : "debug"]({
|
|
6536
6572
|
event,
|
|
6537
6573
|
...summary
|
|
6538
6574
|
}, `turn.${event}`);
|
|
@@ -6631,11 +6667,13 @@ async function runPolling(opts) {
|
|
|
6631
6667
|
const resolvedContext = await resolveAgentContext(identity.agent, {
|
|
6632
6668
|
agentRootDir: explicitAgentRootDir,
|
|
6633
6669
|
credentialSource: cfg.credentialSource,
|
|
6634
|
-
envApiUrl: cfg.apiUrl
|
|
6670
|
+
envApiUrl: cfg.apiUrl,
|
|
6671
|
+
teamId
|
|
6635
6672
|
});
|
|
6636
6673
|
gate = "authenticate_and_bind";
|
|
6637
6674
|
const whoami = await validateStartupBinding({
|
|
6638
6675
|
agent: resolvedContext.agent,
|
|
6676
|
+
credentialTeamId: resolvedContext.credentialTeamId,
|
|
6639
6677
|
teamId,
|
|
6640
6678
|
expectedAgent: cfg.expectedAgent
|
|
6641
6679
|
});
|
|
@@ -7238,11 +7276,13 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
|
|
|
7238
7276
|
const resolvedContext = await resolveAgentContext(identity.agent, {
|
|
7239
7277
|
agentRootDir: explicitAgentRootDir,
|
|
7240
7278
|
credentialSource: cfg.credentialSource,
|
|
7241
|
-
envApiUrl: cfg.apiUrl
|
|
7279
|
+
envApiUrl: cfg.apiUrl,
|
|
7280
|
+
teamId: values.team
|
|
7242
7281
|
});
|
|
7243
7282
|
gate = "authenticate_and_bind";
|
|
7244
7283
|
const whoami = await validateStartupBinding({
|
|
7245
7284
|
agent: resolvedContext.agent,
|
|
7285
|
+
credentialTeamId: resolvedContext.credentialTeamId,
|
|
7246
7286
|
teamId: values.team,
|
|
7247
7287
|
expectedAgent: cfg.expectedAgent
|
|
7248
7288
|
});
|
|
@@ -9539,9 +9579,10 @@ async function createManagedAgent(store, secrets, input, connectAgent = connect)
|
|
|
9539
9579
|
*/
|
|
9540
9580
|
function incompleteRegistrationMessage(alias, known) {
|
|
9541
9581
|
const endpoint = `POST /v1/agents/${alias}/reconcile`;
|
|
9582
|
+
const recovery = known.recoveryPath ? ` Inspect the protected recovery file at ${known.recoveryPath}; it may be incomplete.` : "";
|
|
9542
9583
|
const reconcile = known.configPath ? `Finish it with ${endpoint} and {"action":"resume"}, or discard the local record with {"action":"abandon"}.` : `No local config was written, so it cannot be resumed; discard the local record with ${endpoint} and {"action":"abandon"}.`;
|
|
9543
|
-
if (known.subjectId) return `the remote agent ${known.subjectId} was registered but local activation is incomplete. ${reconcile}`;
|
|
9544
|
-
return `registration for "${alias}" may have completed on the server${known.fingerprint ? ` (fingerprint ${known.fingerprint})` : ""}; look the agent up first. ${reconcile}`;
|
|
9584
|
+
if (known.subjectId) return `the remote agent ${known.subjectId} was registered but local activation is incomplete. ${reconcile}${recovery}`;
|
|
9585
|
+
return `registration for "${alias}" may have completed on the server${known.fingerprint ? ` (fingerprint ${known.fingerprint})` : ""}; look the agent up first. ${reconcile}${recovery}`;
|
|
9545
9586
|
}
|
|
9546
9587
|
/** Resume a fully persisted registration or explicitly abandon local recovery. */
|
|
9547
9588
|
async function reconcileManagedRegistration(store, secrets, aliasInput, action, connectAgent = connect, signal) {
|
|
@@ -9562,8 +9603,9 @@ async function reconcileManagedRegistration(store, secrets, aliasInput, action,
|
|
|
9562
9603
|
store.clearPendingRegistration(alias);
|
|
9563
9604
|
return null;
|
|
9564
9605
|
}
|
|
9565
|
-
|
|
9566
|
-
|
|
9606
|
+
const reference = config ? selectAgentKeyReference(config)?.reference : void 0;
|
|
9607
|
+
if (!config || !reference || reference.provider !== FILE_SECRET_PROVIDER || config.keys.private_key_ref?.provider !== FILE_SECRET_PROVIDER) throw new AgentServerIdentityError("registration_incomplete", `pending registration for "${alias}" does not have complete managed references`);
|
|
9608
|
+
const [agentKey, privateKeyState] = await Promise.all([secrets.read(reference.key), secrets.probe(config.keys.private_key_ref.key)]);
|
|
9567
9609
|
if (!agentKey || privateKeyState !== "present") throw new AgentServerIdentityError("registration_incomplete", `pending registration for "${alias}" is missing persisted secret material`);
|
|
9568
9610
|
const apiUrl = requireConfigApiUrl(config, store.agentPath(alias));
|
|
9569
9611
|
const whoami = await callWhoami(connectAgent, {
|
|
@@ -9599,9 +9641,9 @@ async function attachExternalAgent(store, secretProviders, input, connectAgent =
|
|
|
9599
9641
|
try {
|
|
9600
9642
|
if (!central) externalAgentLocation(configPath);
|
|
9601
9643
|
const config = await readCurrentConfig(configPath);
|
|
9602
|
-
if (central && !config
|
|
9644
|
+
if (central && !hasAgentKeyConfiguration(config)) throw new AgentServerIdentityError("unsupported_credential", `central identity "${alias}" needs a stored agent key before the Agent Server can run it`);
|
|
9603
9645
|
const configApiUrl = requireTrustedConfigApiUrl(config, configPath);
|
|
9604
|
-
const whoami = await authenticateConfig(config, configPath, requireTrustedApiOverride(input.apiUrl, configApiUrl, configPath), secretProviders, connectAgent, input.signal);
|
|
9646
|
+
const whoami = await authenticateConfig(config, configPath, requireTrustedApiOverride(input.apiUrl, configApiUrl, configPath), secretProviders, connectAgent, input.signal, input.teamId);
|
|
9605
9647
|
const identity = identityFromConfig(config);
|
|
9606
9648
|
assertIdentityMatches(identity, whoami, `external config ${configPath}`, "authenticated whoami");
|
|
9607
9649
|
assertSubjectMatches(whoami, config, "authenticated whoami", `external config ${configPath}`);
|
|
@@ -9628,15 +9670,16 @@ async function attachExternalAgent(store, secretProviders, input, connectAgent =
|
|
|
9628
9670
|
}
|
|
9629
9671
|
}
|
|
9630
9672
|
/** Load and authenticate the current config, then refresh its derived pin. */
|
|
9631
|
-
async function verifyAgentActivation(store, alias, managedSecretProviders, externalSecretProviders, connectAgent = connect, signal) {
|
|
9673
|
+
async function verifyAgentActivation(store, alias, managedSecretProviders, externalSecretProviders, connectAgent = connect, signal, teamId) {
|
|
9632
9674
|
const activation = requireActivation(store, alias);
|
|
9633
|
-
|
|
9675
|
+
teamId ??= activation.boundTeamId;
|
|
9676
|
+
const verified = activation.source === "managed" ? await verifyManagedActivation(store, activation, managedSecretProviders, connectAgent, signal, teamId) : await verifyExternalActivation(store, activation, externalSecretProviders, connectAgent, signal, teamId);
|
|
9634
9677
|
assertSubjectMatches(verified.whoami, verified.config, "authenticated whoami", `agent "${activation.alias}" config`);
|
|
9635
9678
|
if (verified.whoami.subjectId !== activation.subjectId) throw new AgentServerIdentityError("verification_failed", `authenticated whoami subject does not match agent "${activation.alias}" pinned activation`);
|
|
9636
9679
|
const identity = identityFromConfig(verified.config);
|
|
9637
9680
|
assertIdentityMatches(verified.whoami, identity, "authenticated whoami", `agent "${activation.alias}" config`);
|
|
9638
9681
|
const boundTeamId = boundTeamIdFromWhoami(verified.whoami);
|
|
9639
|
-
if (activation.boundTeamId !== boundTeamId) throw new AgentServerIdentityError("verification_failed", `authenticated whoami team binding does not match agent "${activation.alias}" pinned activation`);
|
|
9682
|
+
if (!selectAgentKeyReference(verified.config, teamId)?.teamId && activation.boundTeamId !== boundTeamId) throw new AgentServerIdentityError("verification_failed", `authenticated whoami team binding does not match agent "${activation.alias}" pinned activation`);
|
|
9640
9683
|
const refreshed = {
|
|
9641
9684
|
...activation,
|
|
9642
9685
|
...identity
|
|
@@ -9651,36 +9694,26 @@ async function verifyAgentActivation(store, alias, managedSecretProviders, exter
|
|
|
9651
9694
|
...boundTeamId ? { boundTeamId } : {}
|
|
9652
9695
|
};
|
|
9653
9696
|
}
|
|
9654
|
-
async function verifyManagedActivation(store, activation, secretProviders, connectAgent, signal) {
|
|
9697
|
+
async function verifyManagedActivation(store, activation, secretProviders, connectAgent, signal, teamId) {
|
|
9655
9698
|
const configPath = store.agentPath(activation.alias);
|
|
9656
9699
|
const config = await readCurrentConfig(configPath);
|
|
9657
9700
|
assertActivatedConfig(config, activation, configPath, requireConfigApiUrl(config, configPath), activation.apiUrl);
|
|
9658
|
-
let agentKey;
|
|
9659
|
-
try {
|
|
9660
|
-
agentKey = await resolveAgentKey(config, secretProviders);
|
|
9661
|
-
} catch (cause) {
|
|
9662
|
-
throw verificationError(`could not resolve the managed agent key for "${activation.alias}"`, cause);
|
|
9663
|
-
}
|
|
9664
|
-
if (!agentKey) throw new AgentServerIdentityError("verification_failed", `managed config for "${activation.alias}" has no agent_key_ref`);
|
|
9665
9701
|
return {
|
|
9666
9702
|
config,
|
|
9667
|
-
whoami: await
|
|
9668
|
-
agentKey,
|
|
9669
|
-
apiUrl: activation.apiUrl
|
|
9670
|
-
}, configPath, signal)
|
|
9703
|
+
whoami: await authenticateConfig(config, configPath, activation.apiUrl, secretProviders, connectAgent, signal, teamId)
|
|
9671
9704
|
};
|
|
9672
9705
|
}
|
|
9673
|
-
async function verifyExternalActivation(store, activation, secretProviders, connectAgent, signal) {
|
|
9706
|
+
async function verifyExternalActivation(store, activation, secretProviders, connectAgent, signal, teamId) {
|
|
9674
9707
|
const central = activation.configPath === store.agentPath(activation.alias);
|
|
9675
9708
|
if (!central) externalAgentLocation(activation.configPath);
|
|
9676
9709
|
assertTrustedConfigApiUrl(activation.configApiUrl);
|
|
9677
9710
|
const config = await readCurrentConfig(activation.configPath);
|
|
9678
|
-
if (central && !config
|
|
9711
|
+
if (central && !hasAgentKeyConfiguration(config)) throw new AgentServerIdentityError("unsupported_credential", `central identity "${activation.alias}" needs a stored agent key before the Agent Server can run it`);
|
|
9679
9712
|
assertActivatedConfig(config, activation, activation.configPath, requireTrustedConfigApiUrl(config, activation.configPath), activation.configApiUrl);
|
|
9680
9713
|
const effectiveApiUrl = requireTrustedApiOverride(activation.apiUrl, activation.configApiUrl, activation.configPath);
|
|
9681
9714
|
return {
|
|
9682
9715
|
config,
|
|
9683
|
-
whoami: await authenticateConfig(config, activation.configPath, effectiveApiUrl, secretProviders, connectAgent, signal)
|
|
9716
|
+
whoami: await authenticateConfig(config, activation.configPath, effectiveApiUrl, secretProviders, connectAgent, signal, teamId)
|
|
9684
9717
|
};
|
|
9685
9718
|
}
|
|
9686
9719
|
/** Never let request or activation metadata redirect persisted credentials. */
|
|
@@ -9740,18 +9773,20 @@ function externalAgentLocation(configPath) {
|
|
|
9740
9773
|
agentRoot: dirname(moltnetDir)
|
|
9741
9774
|
};
|
|
9742
9775
|
}
|
|
9743
|
-
async function authenticateConfig(config, configPath, apiUrl, secretProviders, connectAgent, signal) {
|
|
9776
|
+
async function authenticateConfig(config, configPath, apiUrl, secretProviders, connectAgent, signal, teamId) {
|
|
9744
9777
|
let agentKey;
|
|
9745
9778
|
try {
|
|
9746
|
-
agentKey = await resolveAgentKey(config, secretProviders);
|
|
9779
|
+
agentKey = await resolveAgentKey(config, secretProviders, teamId);
|
|
9747
9780
|
} catch (cause) {
|
|
9748
|
-
throw verificationError(`could not resolve the daemon agent key from ${configPath}`, cause);
|
|
9781
|
+
throw verificationError(`could not resolve the daemon agent key from ${configPath}; select a team and enroll or repair its stored key`, cause);
|
|
9749
9782
|
}
|
|
9750
9783
|
if (!agentKey) throw new AgentServerIdentityError("unsupported_credential", `external daemon config at ${configPath} must contain an agent_key_ref`);
|
|
9751
|
-
|
|
9784
|
+
const whoami = await callWhoami(connectAgent, {
|
|
9752
9785
|
agentKey,
|
|
9753
9786
|
...apiUrl ? { apiUrl } : {}
|
|
9754
9787
|
}, configPath, signal);
|
|
9788
|
+
if (!matchesCredentialTeam(whoami, selectAgentKeyReference(config, teamId)?.teamId)) throw new AgentServerIdentityError("verification_failed", "authenticated credential team binding does not match the selected team");
|
|
9789
|
+
return whoami;
|
|
9755
9790
|
}
|
|
9756
9791
|
async function callWhoami(connectAgent, options, source, signal) {
|
|
9757
9792
|
try {
|
|
@@ -9796,7 +9831,7 @@ function publicAgentView(store, activation) {
|
|
|
9796
9831
|
...activation.boundTeamId ? { teamId: activation.boundTeamId } : {},
|
|
9797
9832
|
apiUrl: activation.apiUrl,
|
|
9798
9833
|
createdAt: activation.createdAt,
|
|
9799
|
-
hasAgentKey: Boolean(config
|
|
9834
|
+
hasAgentKey: Boolean(config && hasAgentKeyConfiguration(config)),
|
|
9800
9835
|
hasPrivateKey: Boolean(config?.keys.private_key_ref)
|
|
9801
9836
|
};
|
|
9802
9837
|
}
|
|
@@ -9851,7 +9886,6 @@ var INHERITED_MOLTNET_ENV_NAMES = new Set([
|
|
|
9851
9886
|
"MOLTNET_CLI_LINUX_BINARY",
|
|
9852
9887
|
"MOLTNET_CREDENTIAL_BINDINGS",
|
|
9853
9888
|
"MOLTNET_CREDENTIAL_ENFORCEMENT",
|
|
9854
|
-
"MOLTNET_DIARY_ID",
|
|
9855
9889
|
"MOLTNET_GIT_AUTHOR",
|
|
9856
9890
|
"MOLTNET_OTEL_ENDPOINT",
|
|
9857
9891
|
"MOLTNET_PI_VM_INTEGRATION",
|
|
@@ -9905,7 +9939,8 @@ var RunManager = class {
|
|
|
9905
9939
|
XDG_CACHE_HOME: join(homeDir, ".cache"),
|
|
9906
9940
|
XDG_CONFIG_HOME: join(homeDir, ".config"),
|
|
9907
9941
|
XDG_DATA_HOME: join(homeDir, ".local", "share"),
|
|
9908
|
-
MOLTNET_TEAM_ID: spec.teamId
|
|
9942
|
+
MOLTNET_TEAM_ID: spec.teamId,
|
|
9943
|
+
...spec.diaryId ? { MOLTNET_DIARY_ID: spec.diaryId } : {}
|
|
9909
9944
|
};
|
|
9910
9945
|
const target = activation.source === "managed" ? {
|
|
9911
9946
|
agentName: activation.alias,
|
|
@@ -9941,15 +9976,16 @@ var RunManager = class {
|
|
|
9941
9976
|
...target.extraArgs
|
|
9942
9977
|
];
|
|
9943
9978
|
if (activation.source === "managed") {
|
|
9944
|
-
|
|
9979
|
+
const reference = selectAgentKeyReference(config, spec.teamId)?.reference;
|
|
9980
|
+
if (!reference || !config.keys.private_key_ref) throw new AgentServerRunError("invalid_spec", `managed config for "${activation.alias}" is missing canonical secret references`);
|
|
9945
9981
|
env["MOLTNET_API_URL"] = activation.apiUrl;
|
|
9946
|
-
env["MOLTNET_AGENT_KEY_REF"] = formatSecretReferenceString(
|
|
9982
|
+
env["MOLTNET_AGENT_KEY_REF"] = formatSecretReferenceString(reference);
|
|
9947
9983
|
env["MOLTNET_PRIVATE_KEY_REF"] = formatSecretReferenceString(config.keys.private_key_ref);
|
|
9948
9984
|
env["MOLTNET_SECRET_ROOT"] = this.store.secretsDir;
|
|
9949
9985
|
} else {
|
|
9950
9986
|
env["MOLTNET_API_URL"] = activation.apiUrl ?? activation.configApiUrl;
|
|
9951
9987
|
try {
|
|
9952
|
-
const agentKey = await resolveAgentKey(config, this.options.externalSecretProviders);
|
|
9988
|
+
const agentKey = await resolveAgentKey(config, this.options.externalSecretProviders, spec.teamId);
|
|
9953
9989
|
if (!agentKey) throw new Error("external daemon config has no agent key");
|
|
9954
9990
|
env["MOLTNET_AGENT_KEY"] = agentKey;
|
|
9955
9991
|
env["MOLTNET_PRIVATE_KEY"] = await resolveIdentitySeed(config, this.options.externalSecretProviders);
|
|
@@ -9989,7 +10025,10 @@ var RunManager = class {
|
|
|
9989
10025
|
}
|
|
9990
10026
|
async startReserved(spec, signal) {
|
|
9991
10027
|
this.assertStartOpen(signal);
|
|
9992
|
-
const agent = await (this.options.verifyActivationImpl ?? verifyAgentActivation)(this.store, spec.agent, this.options.secretProviders, this.options.externalSecretProviders, void 0, signal)
|
|
10028
|
+
const agent = await (this.options.verifyActivationImpl ?? verifyAgentActivation)(this.store, spec.agent, this.options.secretProviders, this.options.externalSecretProviders, void 0, signal, spec.teamId).catch((cause) => {
|
|
10029
|
+
if (cause instanceof AgentServerIdentityError && cause.code === "verification_failed") throw new AgentServerIdentityError(cause.code, `Cannot start agent "${spec.agent}" for team "${spec.teamId}": credential verification failed. Check the selected team key and activation.`, { cause });
|
|
10030
|
+
throw cause;
|
|
10031
|
+
});
|
|
9993
10032
|
this.assertStartOpen(signal);
|
|
9994
10033
|
if (agent.boundTeamId && agent.boundTeamId !== spec.teamId) throw new AgentServerRunError("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}`);
|
|
9995
10034
|
const id = `${Date.now().toString(36)}-${randomBytes(4).toString("hex")}`;
|
|
@@ -10118,7 +10157,7 @@ var RunManager = class {
|
|
|
10118
10157
|
}
|
|
10119
10158
|
async resolveRuntimeModule(spec, activated, cwd) {
|
|
10120
10159
|
const profiles = await resolveRuntimeProfiles({
|
|
10121
|
-
agent: await this.connectActivatedAgent(activated),
|
|
10160
|
+
agent: await this.connectActivatedAgent(activated, spec.teamId),
|
|
10122
10161
|
profiles: spec.profiles,
|
|
10123
10162
|
teamId: spec.teamId,
|
|
10124
10163
|
cwd
|
|
@@ -10140,21 +10179,13 @@ var RunManager = class {
|
|
|
10140
10179
|
if (kind === "gondolin_pi") return void 0;
|
|
10141
10180
|
throw new AgentServerRunError("invalid_spec", `No local runtime is registered for profile kind "${kind}".`);
|
|
10142
10181
|
}
|
|
10143
|
-
async connectActivatedAgent(activated) {
|
|
10182
|
+
async connectActivatedAgent(activated, teamId) {
|
|
10144
10183
|
const { activation, config } = activated;
|
|
10145
|
-
|
|
10146
|
-
|
|
10147
|
-
if (!agentKey) throw new AgentServerRunError("invalid_spec", `managed agent "${activation.alias}" has no agent key`);
|
|
10148
|
-
return connect({
|
|
10149
|
-
agentKey,
|
|
10150
|
-
apiUrl: activation.apiUrl
|
|
10151
|
-
});
|
|
10152
|
-
}
|
|
10153
|
-
const agentKey = await resolveAgentKey(config, this.options.externalSecretProviders);
|
|
10154
|
-
if (!agentKey) throw new AgentServerRunError("invalid_spec", `external agent "${activation.alias}" has no agent key`);
|
|
10184
|
+
const agentKey = await resolveAgentKey(config, activation.source === "managed" ? this.options.secretProviders : this.options.externalSecretProviders, teamId);
|
|
10185
|
+
if (!agentKey) throw new AgentServerRunError("invalid_spec", `agent "${activation.alias}" has no agent key`);
|
|
10155
10186
|
return connect({
|
|
10156
10187
|
agentKey,
|
|
10157
|
-
apiUrl: activation.apiUrl ?? activation.configApiUrl
|
|
10188
|
+
apiUrl: activation.source === "managed" ? activation.apiUrl : activation.apiUrl ?? activation.configApiUrl
|
|
10158
10189
|
});
|
|
10159
10190
|
}
|
|
10160
10191
|
stop(id) {
|
|
@@ -10574,6 +10605,7 @@ var AgentServerRunRecordSchema = Type.Object({
|
|
|
10574
10605
|
id: Type.String(),
|
|
10575
10606
|
agent: Type.String(),
|
|
10576
10607
|
teamId: Type.String(),
|
|
10608
|
+
diaryId: Type.Optional(Type.String()),
|
|
10577
10609
|
profiles: StringList,
|
|
10578
10610
|
taskTypes: StringList,
|
|
10579
10611
|
mode: Type.Union([Type.Literal("poll"), Type.Literal("drain")]),
|
|
@@ -10636,7 +10668,8 @@ var CreateAgentSchema = Type.Union([Type.Object({
|
|
|
10636
10668
|
enrollmentToken: Type.String()
|
|
10637
10669
|
}), Type.Object({
|
|
10638
10670
|
kind: Type.Literal("external"),
|
|
10639
|
-
identityAlias: Type.String()
|
|
10671
|
+
identityAlias: Type.String(),
|
|
10672
|
+
teamId: Type.Optional(Type.String({ format: "uuid" }))
|
|
10640
10673
|
})]);
|
|
10641
10674
|
var ReconcileAgentSchema = Type.Object({ action: Type.Union([Type.Literal("resume"), Type.Literal("abandon")]) });
|
|
10642
10675
|
var ReconcileAgentResultSchema = Type.Union([schemaRef(AgentServerAgentSchema), Type.Object({ abandoned: Type.Literal(true) })], { $id: "ReconcileAgentResult" });
|
|
@@ -10651,6 +10684,7 @@ var DiscoverModelsSchema = Type.Object({ models: ProviderModelList }, { $id: "Di
|
|
|
10651
10684
|
var StartRunSchema = Type.Object({
|
|
10652
10685
|
agent: Type.String(),
|
|
10653
10686
|
teamId: Type.String(),
|
|
10687
|
+
diaryId: Type.Optional(Type.String()),
|
|
10654
10688
|
profiles: StringList,
|
|
10655
10689
|
taskTypes: Type.Array(AgentServerTaskTypeSchema),
|
|
10656
10690
|
mode: Type.Union([Type.Literal("poll"), Type.Literal("drain")])
|
|
@@ -11148,6 +11182,7 @@ function registerAgentRoutes(app, options, requirePairedOrigin) {
|
|
|
11148
11182
|
const entry = await attachExternalAgent(store, options.externalSecretProviders, {
|
|
11149
11183
|
name: identityAlias,
|
|
11150
11184
|
configDir: store.identityDir(identityAlias),
|
|
11185
|
+
...typeof body["teamId"] === "string" ? { teamId: body["teamId"] } : {},
|
|
11151
11186
|
signal
|
|
11152
11187
|
});
|
|
11153
11188
|
return reply.code(201).send(publicAgentView(store, entry.activation));
|
|
@@ -11179,7 +11214,7 @@ function identityViews(store) {
|
|
|
11179
11214
|
return store.listIdentityAliases().map((alias) => ({
|
|
11180
11215
|
alias,
|
|
11181
11216
|
activated: activated.has(alias),
|
|
11182
|
-
hasAgentKey:
|
|
11217
|
+
hasAgentKey: hasAgentKeyConfiguration(store.readAgentConfig(alias) ?? {})
|
|
11183
11218
|
}));
|
|
11184
11219
|
}
|
|
11185
11220
|
function registerProviderRoutes(app, options, requirePairedOrigin) {
|
|
@@ -11263,9 +11298,11 @@ function registerRunRoutes(app, options, requirePairedOrigin) {
|
|
|
11263
11298
|
}, async (request, reply) => {
|
|
11264
11299
|
requirePairedOrigin(request);
|
|
11265
11300
|
const body = requireBody(request);
|
|
11301
|
+
const diaryId = optionalString(body, "diaryId");
|
|
11266
11302
|
const record = await runs.start({
|
|
11267
11303
|
agent: requireString(body, "agent"),
|
|
11268
11304
|
teamId: requireString(body, "teamId"),
|
|
11305
|
+
...diaryId ? { diaryId } : {},
|
|
11269
11306
|
profiles: stringArray(body, "profiles"),
|
|
11270
11307
|
taskTypes: stringArray(body, "taskTypes"),
|
|
11271
11308
|
mode: requireString(body, "mode")
|
|
@@ -12015,11 +12052,13 @@ async function runSyncSessions(argv) {
|
|
|
12015
12052
|
const ctx = await resolveAgentContext(identity.agent, {
|
|
12016
12053
|
credentialSource: cfg.credentialSource,
|
|
12017
12054
|
envApiUrl: cfg.apiUrl,
|
|
12055
|
+
teamId: values.team,
|
|
12018
12056
|
agentRootDir: explicitAgentRootDir
|
|
12019
12057
|
});
|
|
12020
12058
|
await validateStartupBinding({
|
|
12021
12059
|
agent: ctx.agent,
|
|
12022
|
-
teamId: values.team
|
|
12060
|
+
teamId: values.team,
|
|
12061
|
+
credentialTeamId: ctx.credentialTeamId
|
|
12023
12062
|
});
|
|
12024
12063
|
const stateDirs = ensureDaemonStateDirs(agentRootDir);
|
|
12025
12064
|
const result = await syncRuntimeSessions({
|
|
@@ -12204,7 +12243,7 @@ async function writeCache(cache) {
|
|
|
12204
12243
|
}
|
|
12205
12244
|
//#endregion
|
|
12206
12245
|
//#region src/version.ts
|
|
12207
|
-
var DAEMON_VERSION = "0.
|
|
12246
|
+
var DAEMON_VERSION = "0.60.0";
|
|
12208
12247
|
//#endregion
|
|
12209
12248
|
//#region src/cli.ts
|
|
12210
12249
|
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.60.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.",
|
|
@@ -85,10 +85,10 @@
|
|
|
85
85
|
"proper-lockfile": "4.1.2",
|
|
86
86
|
"reflect-metadata": "^0.2.2",
|
|
87
87
|
"typebox": "^1.2.8",
|
|
88
|
-
"@themoltnet/agent-runtime": "1.0
|
|
89
|
-
"@themoltnet/
|
|
90
|
-
"@themoltnet/sdk": "0.
|
|
91
|
-
"@themoltnet/
|
|
88
|
+
"@themoltnet/agent-runtime": "1.1.0",
|
|
89
|
+
"@themoltnet/os-keyring": "0.3.0",
|
|
90
|
+
"@themoltnet/sdk": "0.143.0",
|
|
91
|
+
"@themoltnet/pi-runtime": "0.18.1"
|
|
92
92
|
},
|
|
93
93
|
"devDependencies": {
|
|
94
94
|
"@fastify/swagger": "^9.6.1",
|
|
@@ -98,17 +98,17 @@
|
|
|
98
98
|
"vite": "^8.0.0",
|
|
99
99
|
"vite-plugin-dts": "^4.5.4",
|
|
100
100
|
"vitest": "^3.0.0",
|
|
101
|
-
"@moltnet/agent-eval": "0.1.0",
|
|
102
101
|
"@moltnet/api-client": "0.1.0",
|
|
103
|
-
"@moltnet/
|
|
104
|
-
"@moltnet/execution-plan": "0.1.0",
|
|
102
|
+
"@moltnet/agent-eval": "0.1.0",
|
|
105
103
|
"@moltnet/bootstrap": "0.1.0",
|
|
106
|
-
"@moltnet/models": "0.1.0",
|
|
107
|
-
"@moltnet/execution-integrations": "0.1.0",
|
|
108
104
|
"@moltnet/crypto-service": "0.1.0",
|
|
109
|
-
"@moltnet/
|
|
105
|
+
"@moltnet/execution-integrations": "0.1.0",
|
|
106
|
+
"@moltnet/execution-plan": "0.1.0",
|
|
107
|
+
"@moltnet/loopback-companion": "0.1.0",
|
|
110
108
|
"@moltnet/runtime-profiles": "0.1.0",
|
|
111
|
-
"@moltnet/
|
|
109
|
+
"@moltnet/observability": "0.1.0",
|
|
110
|
+
"@moltnet/tasks": "0.1.0",
|
|
111
|
+
"@moltnet/models": "0.1.0"
|
|
112
112
|
},
|
|
113
113
|
"nx": {
|
|
114
114
|
"projectType": "application",
|