@ouro.bot/cli 0.1.0-alpha.343 → 0.1.0-alpha.345
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/changelog.json +13 -0
- package/dist/heart/machine-identity.js +161 -0
- package/dist/heart/provider-credential-pool.js +386 -0
- package/dist/heart/provider-state.js +208 -0
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.345",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Machine-wide provider credentials now have a local pool at `~/.agentsecrets/providers.json` with one active record per provider, generated credential revisions, safe provenance, redacted summaries, and explicit migration from legacy per-agent `~/.agentsecrets/<agent>/secrets.json` files."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"version": "0.1.0-alpha.344",
|
|
12
|
+
"changes": [
|
|
13
|
+
"Provider readiness groundwork now has stable machine identity stored at `~/.ouro-cli/machine.json`, preserving random machine IDs across hostname changes while recording aliases for diagnostics.",
|
|
14
|
+
"Per-agent provider bindings and readiness can now be read, validated, written, and bootstrapped in `<agent>.ouro/state/providers.json` using explicit `outward` and `inner` lanes without storing credentials in bundle state."
|
|
15
|
+
]
|
|
16
|
+
},
|
|
4
17
|
{
|
|
5
18
|
"version": "0.1.0-alpha.343",
|
|
6
19
|
"changes": [
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.getMachineIdentityPath = getMachineIdentityPath;
|
|
37
|
+
exports.loadOrCreateMachineIdentity = loadOrCreateMachineIdentity;
|
|
38
|
+
const crypto = __importStar(require("crypto"));
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const os = __importStar(require("os"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const runtime_1 = require("../nerves/runtime");
|
|
43
|
+
function nowIso(deps) {
|
|
44
|
+
return (deps.now?.() ?? new Date()).toISOString();
|
|
45
|
+
}
|
|
46
|
+
function currentHostname(deps) {
|
|
47
|
+
return (deps.hostname?.() ?? os.hostname()).trim();
|
|
48
|
+
}
|
|
49
|
+
function defaultRandomId() {
|
|
50
|
+
return `machine_${crypto.randomUUID()}`;
|
|
51
|
+
}
|
|
52
|
+
function normalizeAliases(value) {
|
|
53
|
+
if (!Array.isArray(value))
|
|
54
|
+
return [];
|
|
55
|
+
const aliases = [];
|
|
56
|
+
for (const alias of value) {
|
|
57
|
+
if (typeof alias !== "string")
|
|
58
|
+
continue;
|
|
59
|
+
const trimmed = alias.trim();
|
|
60
|
+
if (trimmed && !aliases.includes(trimmed))
|
|
61
|
+
aliases.push(trimmed);
|
|
62
|
+
}
|
|
63
|
+
return aliases;
|
|
64
|
+
}
|
|
65
|
+
function parseMachineIdentity(value) {
|
|
66
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
67
|
+
return null;
|
|
68
|
+
const record = value;
|
|
69
|
+
const aliases = normalizeAliases(record.hostnameAliases);
|
|
70
|
+
if (record.schemaVersion !== 1 ||
|
|
71
|
+
typeof record.machineId !== "string" ||
|
|
72
|
+
record.machineId.trim().length === 0 ||
|
|
73
|
+
typeof record.createdAt !== "string" ||
|
|
74
|
+
record.createdAt.trim().length === 0 ||
|
|
75
|
+
typeof record.updatedAt !== "string" ||
|
|
76
|
+
record.updatedAt.trim().length === 0) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
schemaVersion: 1,
|
|
81
|
+
machineId: record.machineId,
|
|
82
|
+
createdAt: record.createdAt,
|
|
83
|
+
updatedAt: record.updatedAt,
|
|
84
|
+
hostnameAliases: aliases,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function writeMachineIdentity(machinePath, identity) {
|
|
88
|
+
fs.mkdirSync(path.dirname(machinePath), { recursive: true, mode: 0o700 });
|
|
89
|
+
fs.writeFileSync(machinePath, `${JSON.stringify(identity, null, 2)}\n`, { encoding: "utf-8", mode: 0o600 });
|
|
90
|
+
}
|
|
91
|
+
function getMachineIdentityPath(homeDir = os.homedir()) {
|
|
92
|
+
return path.join(homeDir, ".ouro-cli", "machine.json");
|
|
93
|
+
}
|
|
94
|
+
function loadOrCreateMachineIdentity(deps = {}) {
|
|
95
|
+
const homeDir = deps.homeDir ?? os.homedir();
|
|
96
|
+
const machinePath = getMachineIdentityPath(homeDir);
|
|
97
|
+
const hostname = currentHostname(deps);
|
|
98
|
+
let existing = null;
|
|
99
|
+
let hadInvalidFile = false;
|
|
100
|
+
try {
|
|
101
|
+
existing = parseMachineIdentity(JSON.parse(fs.readFileSync(machinePath, "utf-8")));
|
|
102
|
+
if (!existing)
|
|
103
|
+
hadInvalidFile = true;
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
const code = error && typeof error === "object" && "code" in error
|
|
107
|
+
? error.code
|
|
108
|
+
: undefined;
|
|
109
|
+
if (code !== "ENOENT")
|
|
110
|
+
hadInvalidFile = true;
|
|
111
|
+
}
|
|
112
|
+
if (hadInvalidFile) {
|
|
113
|
+
(0, runtime_1.emitNervesEvent)({
|
|
114
|
+
level: "warn",
|
|
115
|
+
component: "config/identity",
|
|
116
|
+
event: "config.machine_identity_invalid",
|
|
117
|
+
message: "machine identity file is invalid; replacing it",
|
|
118
|
+
meta: { path: machinePath },
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
if (existing) {
|
|
122
|
+
if (hostname && !existing.hostnameAliases.includes(hostname)) {
|
|
123
|
+
const updated = {
|
|
124
|
+
...existing,
|
|
125
|
+
updatedAt: nowIso(deps),
|
|
126
|
+
hostnameAliases: [...existing.hostnameAliases, hostname],
|
|
127
|
+
};
|
|
128
|
+
writeMachineIdentity(machinePath, updated);
|
|
129
|
+
(0, runtime_1.emitNervesEvent)({
|
|
130
|
+
component: "config/identity",
|
|
131
|
+
event: "config.machine_identity_alias_added",
|
|
132
|
+
message: "recorded hostname alias for stable machine identity",
|
|
133
|
+
meta: { machineId: updated.machineId, hostname },
|
|
134
|
+
});
|
|
135
|
+
return updated;
|
|
136
|
+
}
|
|
137
|
+
(0, runtime_1.emitNervesEvent)({
|
|
138
|
+
component: "config/identity",
|
|
139
|
+
event: "config.machine_identity_loaded",
|
|
140
|
+
message: "loaded stable machine identity",
|
|
141
|
+
meta: { machineId: existing.machineId },
|
|
142
|
+
});
|
|
143
|
+
return existing;
|
|
144
|
+
}
|
|
145
|
+
const timestamp = nowIso(deps);
|
|
146
|
+
const identity = {
|
|
147
|
+
schemaVersion: 1,
|
|
148
|
+
machineId: (deps.randomId?.() ?? defaultRandomId()).trim() || defaultRandomId(),
|
|
149
|
+
createdAt: timestamp,
|
|
150
|
+
updatedAt: timestamp,
|
|
151
|
+
hostnameAliases: hostname ? [hostname] : [],
|
|
152
|
+
};
|
|
153
|
+
writeMachineIdentity(machinePath, identity);
|
|
154
|
+
(0, runtime_1.emitNervesEvent)({
|
|
155
|
+
component: "config/identity",
|
|
156
|
+
event: "config.machine_identity_created",
|
|
157
|
+
message: "created stable machine identity",
|
|
158
|
+
meta: { machineId: identity.machineId },
|
|
159
|
+
});
|
|
160
|
+
return identity;
|
|
161
|
+
}
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.getProviderCredentialPoolPath = getProviderCredentialPoolPath;
|
|
37
|
+
exports.validateProviderCredentialPool = validateProviderCredentialPool;
|
|
38
|
+
exports.readProviderCredentialPool = readProviderCredentialPool;
|
|
39
|
+
exports.writeProviderCredentialPool = writeProviderCredentialPool;
|
|
40
|
+
exports.upsertProviderCredential = upsertProviderCredential;
|
|
41
|
+
exports.redactProviderCredentialPool = redactProviderCredentialPool;
|
|
42
|
+
exports.summarizeProviderCredentialPool = summarizeProviderCredentialPool;
|
|
43
|
+
exports.readLegacyAgentProviderCredentials = readLegacyAgentProviderCredentials;
|
|
44
|
+
exports.migrateLegacyAgentProviderCredentials = migrateLegacyAgentProviderCredentials;
|
|
45
|
+
const crypto = __importStar(require("crypto"));
|
|
46
|
+
const fs = __importStar(require("fs"));
|
|
47
|
+
const os = __importStar(require("os"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
const runtime_1 = require("../nerves/runtime");
|
|
50
|
+
const VALID_PROVIDERS = ["azure", "minimax", "anthropic", "openai-codex", "github-copilot"];
|
|
51
|
+
const VALID_PROVENANCE_SOURCES = ["auth-flow", "legacy-agent-secrets", "manual"];
|
|
52
|
+
const PROVIDER_FIELD_SPLITS = {
|
|
53
|
+
anthropic: {
|
|
54
|
+
credentials: ["setupToken", "refreshToken", "expiresAt"],
|
|
55
|
+
config: [],
|
|
56
|
+
meaningfulCredentials: ["setupToken", "refreshToken"],
|
|
57
|
+
},
|
|
58
|
+
"openai-codex": {
|
|
59
|
+
credentials: ["oauthAccessToken"],
|
|
60
|
+
config: [],
|
|
61
|
+
meaningfulCredentials: ["oauthAccessToken"],
|
|
62
|
+
},
|
|
63
|
+
azure: {
|
|
64
|
+
credentials: ["apiKey"],
|
|
65
|
+
config: ["endpoint", "deployment", "apiVersion", "managedIdentityClientId"],
|
|
66
|
+
meaningfulCredentials: ["apiKey", "managedIdentityClientId"],
|
|
67
|
+
},
|
|
68
|
+
minimax: {
|
|
69
|
+
credentials: ["apiKey"],
|
|
70
|
+
config: [],
|
|
71
|
+
meaningfulCredentials: ["apiKey"],
|
|
72
|
+
},
|
|
73
|
+
"github-copilot": {
|
|
74
|
+
credentials: ["githubToken"],
|
|
75
|
+
config: ["baseUrl"],
|
|
76
|
+
meaningfulCredentials: ["githubToken"],
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
function isRecord(value) {
|
|
80
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
81
|
+
}
|
|
82
|
+
function isAgentProvider(value) {
|
|
83
|
+
return VALID_PROVIDERS.includes(value);
|
|
84
|
+
}
|
|
85
|
+
function isProvenanceSource(value) {
|
|
86
|
+
return typeof value === "string" && VALID_PROVENANCE_SOURCES.includes(value);
|
|
87
|
+
}
|
|
88
|
+
function isCredentialValue(value) {
|
|
89
|
+
return typeof value === "string" || typeof value === "number";
|
|
90
|
+
}
|
|
91
|
+
function isPresentCredentialValue(value) {
|
|
92
|
+
if (!isCredentialValue(value))
|
|
93
|
+
return false;
|
|
94
|
+
if (typeof value === "string")
|
|
95
|
+
return value.trim().length > 0;
|
|
96
|
+
return Number.isFinite(value) && value !== 0;
|
|
97
|
+
}
|
|
98
|
+
function copyKnownFields(source, fields) {
|
|
99
|
+
const result = {};
|
|
100
|
+
for (const field of fields) {
|
|
101
|
+
const value = source[field];
|
|
102
|
+
if (isPresentCredentialValue(value)) {
|
|
103
|
+
result[field] = value;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
function splitLegacyProviderConfig(provider, rawConfig) {
|
|
109
|
+
const fields = PROVIDER_FIELD_SPLITS[provider];
|
|
110
|
+
return {
|
|
111
|
+
credentials: copyKnownFields(rawConfig, fields.credentials),
|
|
112
|
+
config: copyKnownFields(rawConfig, fields.config),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function legacyProviderHasCredentialData(provider, rawConfig) {
|
|
116
|
+
const fields = PROVIDER_FIELD_SPLITS[provider];
|
|
117
|
+
return fields.meaningfulCredentials.some((field) => isPresentCredentialValue(rawConfig[field]));
|
|
118
|
+
}
|
|
119
|
+
function makeEmptyProviderCredentialPool(now) {
|
|
120
|
+
return {
|
|
121
|
+
schemaVersion: 1,
|
|
122
|
+
updatedAt: now.toISOString(),
|
|
123
|
+
providers: {},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function makeRevision() {
|
|
127
|
+
return `cred_${crypto.randomUUID()}`;
|
|
128
|
+
}
|
|
129
|
+
function getProviderCredentialPoolPath(homeDir = os.homedir()) {
|
|
130
|
+
return path.join(homeDir, ".agentsecrets", "providers.json");
|
|
131
|
+
}
|
|
132
|
+
function validateProviderCredentialPool(value) {
|
|
133
|
+
if (!isRecord(value)) {
|
|
134
|
+
throw new Error("provider credential pool must be an object");
|
|
135
|
+
}
|
|
136
|
+
if (value.schemaVersion !== 1) {
|
|
137
|
+
throw new Error("provider credential pool schemaVersion must be 1");
|
|
138
|
+
}
|
|
139
|
+
if (typeof value.updatedAt !== "string" || value.updatedAt.length === 0) {
|
|
140
|
+
throw new Error("provider credential pool updatedAt must be a non-empty string");
|
|
141
|
+
}
|
|
142
|
+
if (!isRecord(value.providers)) {
|
|
143
|
+
throw new Error("provider credential pool providers must be an object");
|
|
144
|
+
}
|
|
145
|
+
const providers = value.providers;
|
|
146
|
+
for (const [providerKey, rawRecord] of Object.entries(providers)) {
|
|
147
|
+
if (!isAgentProvider(providerKey)) {
|
|
148
|
+
throw new Error(`provider credential pool has unsupported provider ${providerKey}`);
|
|
149
|
+
}
|
|
150
|
+
if (!isRecord(rawRecord)) {
|
|
151
|
+
throw new Error(`${providerKey} credential record must be an object`);
|
|
152
|
+
}
|
|
153
|
+
if (rawRecord.provider !== providerKey) {
|
|
154
|
+
throw new Error(`${providerKey}.provider must match its provider key`);
|
|
155
|
+
}
|
|
156
|
+
if (typeof rawRecord.revision !== "string" || rawRecord.revision.length === 0) {
|
|
157
|
+
throw new Error(`${providerKey}.revision must be a non-empty string`);
|
|
158
|
+
}
|
|
159
|
+
if (typeof rawRecord.updatedAt !== "string" || rawRecord.updatedAt.length === 0) {
|
|
160
|
+
throw new Error(`${providerKey}.updatedAt must be a non-empty string`);
|
|
161
|
+
}
|
|
162
|
+
if (!isRecord(rawRecord.credentials)) {
|
|
163
|
+
throw new Error(`${providerKey}.credentials must be an object`);
|
|
164
|
+
}
|
|
165
|
+
if (!isRecord(rawRecord.config)) {
|
|
166
|
+
throw new Error(`${providerKey}.config must be an object`);
|
|
167
|
+
}
|
|
168
|
+
for (const [field, fieldValue] of Object.entries(rawRecord.credentials)) {
|
|
169
|
+
if (!isCredentialValue(fieldValue)) {
|
|
170
|
+
throw new Error(`${providerKey}.credentials.${field} must be a string or number`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
for (const [field, fieldValue] of Object.entries(rawRecord.config)) {
|
|
174
|
+
if (!isCredentialValue(fieldValue)) {
|
|
175
|
+
throw new Error(`${providerKey}.config.${field} must be a string or number`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (!isRecord(rawRecord.provenance)) {
|
|
179
|
+
throw new Error(`${providerKey}.provenance must be an object`);
|
|
180
|
+
}
|
|
181
|
+
if (!isProvenanceSource(rawRecord.provenance.source)) {
|
|
182
|
+
throw new Error(`${providerKey}.provenance.source must be auth-flow, legacy-agent-secrets, or manual`);
|
|
183
|
+
}
|
|
184
|
+
const contributedByAgent = rawRecord.provenance.contributedByAgent;
|
|
185
|
+
if (contributedByAgent !== undefined && typeof contributedByAgent !== "string") {
|
|
186
|
+
throw new Error(`${providerKey}.provenance.contributedByAgent must be a string when present`);
|
|
187
|
+
}
|
|
188
|
+
if (typeof rawRecord.provenance.updatedAt !== "string" || rawRecord.provenance.updatedAt.length === 0) {
|
|
189
|
+
throw new Error(`${providerKey}.provenance.updatedAt must be a non-empty string`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return value;
|
|
193
|
+
}
|
|
194
|
+
function readProviderCredentialPool(homeDir = os.homedir()) {
|
|
195
|
+
const poolPath = getProviderCredentialPoolPath(homeDir);
|
|
196
|
+
try {
|
|
197
|
+
const raw = fs.readFileSync(poolPath, "utf-8");
|
|
198
|
+
const parsed = JSON.parse(raw);
|
|
199
|
+
const pool = validateProviderCredentialPool(parsed);
|
|
200
|
+
(0, runtime_1.emitNervesEvent)({
|
|
201
|
+
component: "config/identity",
|
|
202
|
+
event: "config.provider_credential_pool_read",
|
|
203
|
+
message: "read provider credential pool",
|
|
204
|
+
meta: { poolPath },
|
|
205
|
+
});
|
|
206
|
+
return { ok: true, poolPath, pool };
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
if (error.code === "ENOENT") {
|
|
210
|
+
(0, runtime_1.emitNervesEvent)({
|
|
211
|
+
component: "config/identity",
|
|
212
|
+
event: "config.provider_credential_pool_missing",
|
|
213
|
+
message: "provider credential pool not found",
|
|
214
|
+
meta: { poolPath },
|
|
215
|
+
});
|
|
216
|
+
return {
|
|
217
|
+
ok: false,
|
|
218
|
+
reason: "missing",
|
|
219
|
+
poolPath,
|
|
220
|
+
error: "provider credential pool not found",
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
(0, runtime_1.emitNervesEvent)({
|
|
224
|
+
level: "error",
|
|
225
|
+
component: "config/identity",
|
|
226
|
+
event: "config.provider_credential_pool_invalid",
|
|
227
|
+
message: "provider credential pool invalid",
|
|
228
|
+
meta: { poolPath, reason: String(error) },
|
|
229
|
+
});
|
|
230
|
+
return {
|
|
231
|
+
ok: false,
|
|
232
|
+
reason: "invalid",
|
|
233
|
+
poolPath,
|
|
234
|
+
error: String(error),
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function writeProviderCredentialPool(homeDir, pool) {
|
|
239
|
+
const validated = validateProviderCredentialPool(pool);
|
|
240
|
+
const poolPath = getProviderCredentialPoolPath(homeDir);
|
|
241
|
+
fs.mkdirSync(path.dirname(poolPath), { recursive: true });
|
|
242
|
+
fs.writeFileSync(poolPath, `${JSON.stringify(validated, null, 2)}\n`, "utf-8");
|
|
243
|
+
(0, runtime_1.emitNervesEvent)({
|
|
244
|
+
component: "config/identity",
|
|
245
|
+
event: "config.provider_credential_pool_written",
|
|
246
|
+
message: "wrote provider credential pool",
|
|
247
|
+
meta: { poolPath, providerCount: Object.keys(validated.providers).length },
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
function upsertProviderCredential(input) {
|
|
251
|
+
const homeDir = input.homeDir ?? os.homedir();
|
|
252
|
+
const now = input.now ?? new Date();
|
|
253
|
+
const updatedAt = now.toISOString();
|
|
254
|
+
const readResult = readProviderCredentialPool(homeDir);
|
|
255
|
+
const pool = readResult.ok ? readResult.pool : makeEmptyProviderCredentialPool(now);
|
|
256
|
+
if (!readResult.ok && readResult.reason === "invalid") {
|
|
257
|
+
throw new Error(`Cannot update invalid provider credential pool at ${readResult.poolPath}: ${readResult.error}`);
|
|
258
|
+
}
|
|
259
|
+
const record = {
|
|
260
|
+
provider: input.provider,
|
|
261
|
+
revision: (input.makeRevision ?? makeRevision)(),
|
|
262
|
+
updatedAt,
|
|
263
|
+
credentials: { ...input.credentials },
|
|
264
|
+
config: { ...input.config },
|
|
265
|
+
provenance: {
|
|
266
|
+
...input.provenance,
|
|
267
|
+
updatedAt,
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
pool.updatedAt = updatedAt;
|
|
271
|
+
pool.providers[input.provider] = record;
|
|
272
|
+
writeProviderCredentialPool(homeDir, pool);
|
|
273
|
+
(0, runtime_1.emitNervesEvent)({
|
|
274
|
+
component: "config/identity",
|
|
275
|
+
event: "config.provider_credential_upserted",
|
|
276
|
+
message: "upserted provider credential record",
|
|
277
|
+
meta: { provider: input.provider, revision: record.revision, source: record.provenance.source },
|
|
278
|
+
});
|
|
279
|
+
return record;
|
|
280
|
+
}
|
|
281
|
+
function redactProviderCredentialPool(pool) {
|
|
282
|
+
const validated = validateProviderCredentialPool(pool);
|
|
283
|
+
const providers = {};
|
|
284
|
+
for (const [provider, record] of Object.entries(validated.providers)) {
|
|
285
|
+
const redactedCredentials = {};
|
|
286
|
+
for (const key of Object.keys(record.credentials)) {
|
|
287
|
+
redactedCredentials[key] = "[redacted]";
|
|
288
|
+
}
|
|
289
|
+
providers[provider] = {
|
|
290
|
+
...record,
|
|
291
|
+
credentials: redactedCredentials,
|
|
292
|
+
config: { ...record.config },
|
|
293
|
+
provenance: { ...record.provenance },
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
...validated,
|
|
298
|
+
providers,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
function summarizeProviderCredentialPool(pool) {
|
|
302
|
+
const validated = validateProviderCredentialPool(pool);
|
|
303
|
+
return {
|
|
304
|
+
providers: Object.entries(validated.providers).map(([, record]) => ({
|
|
305
|
+
provider: record.provider,
|
|
306
|
+
revision: record.revision,
|
|
307
|
+
source: record.provenance.source,
|
|
308
|
+
contributedByAgent: record.provenance.contributedByAgent,
|
|
309
|
+
updatedAt: record.updatedAt,
|
|
310
|
+
credentialFields: Object.keys(record.credentials).sort(),
|
|
311
|
+
configFields: Object.keys(record.config).sort(),
|
|
312
|
+
})),
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
function readLegacyAgentProviderCredentials(input) {
|
|
316
|
+
const homeDir = input.homeDir ?? os.homedir();
|
|
317
|
+
const secretsPath = path.join(homeDir, ".agentsecrets", input.agentName, "secrets.json");
|
|
318
|
+
let parsed;
|
|
319
|
+
try {
|
|
320
|
+
parsed = JSON.parse(fs.readFileSync(secretsPath, "utf-8"));
|
|
321
|
+
}
|
|
322
|
+
catch (error) {
|
|
323
|
+
if (error.code === "ENOENT") {
|
|
324
|
+
return [];
|
|
325
|
+
}
|
|
326
|
+
throw new Error(`Failed to read legacy provider credentials for ${input.agentName}: ${String(error)}`);
|
|
327
|
+
}
|
|
328
|
+
if (!isRecord(parsed) || !isRecord(parsed.providers)) {
|
|
329
|
+
return [];
|
|
330
|
+
}
|
|
331
|
+
const candidates = [];
|
|
332
|
+
for (const [providerKey, rawProviderConfig] of Object.entries(parsed.providers)) {
|
|
333
|
+
if (!isAgentProvider(providerKey) || !isRecord(rawProviderConfig)) {
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
if (!legacyProviderHasCredentialData(providerKey, rawProviderConfig)) {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
const { credentials, config } = splitLegacyProviderConfig(providerKey, rawProviderConfig);
|
|
340
|
+
candidates.push({
|
|
341
|
+
provider: providerKey,
|
|
342
|
+
credentials,
|
|
343
|
+
config,
|
|
344
|
+
provenance: {
|
|
345
|
+
source: "legacy-agent-secrets",
|
|
346
|
+
contributedByAgent: input.agentName,
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
(0, runtime_1.emitNervesEvent)({
|
|
351
|
+
component: "config/identity",
|
|
352
|
+
event: "config.provider_credential_legacy_read",
|
|
353
|
+
message: "read legacy provider credential candidates",
|
|
354
|
+
meta: { agentName: input.agentName, providerCount: candidates.length },
|
|
355
|
+
});
|
|
356
|
+
return candidates;
|
|
357
|
+
}
|
|
358
|
+
function migrateLegacyAgentProviderCredentials(input) {
|
|
359
|
+
const homeDir = input.homeDir ?? os.homedir();
|
|
360
|
+
const migrated = [];
|
|
361
|
+
for (const agentName of input.agentNames) {
|
|
362
|
+
const candidates = readLegacyAgentProviderCredentials({ homeDir, agentName });
|
|
363
|
+
for (const candidate of candidates) {
|
|
364
|
+
const record = upsertProviderCredential({
|
|
365
|
+
homeDir,
|
|
366
|
+
provider: candidate.provider,
|
|
367
|
+
credentials: candidate.credentials,
|
|
368
|
+
config: candidate.config,
|
|
369
|
+
provenance: candidate.provenance,
|
|
370
|
+
now: input.now,
|
|
371
|
+
makeRevision: input.makeRevision,
|
|
372
|
+
});
|
|
373
|
+
migrated.push({ agentName, provider: candidate.provider, revision: record.revision });
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
(0, runtime_1.emitNervesEvent)({
|
|
377
|
+
component: "config/identity",
|
|
378
|
+
event: "config.provider_credential_legacy_migrated",
|
|
379
|
+
message: "migrated legacy provider credentials",
|
|
380
|
+
meta: { migratedCount: migrated.length },
|
|
381
|
+
});
|
|
382
|
+
return {
|
|
383
|
+
poolPath: getProviderCredentialPoolPath(homeDir),
|
|
384
|
+
migrated,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.validateProviderState = validateProviderState;
|
|
37
|
+
exports.getProviderStatePath = getProviderStatePath;
|
|
38
|
+
exports.readProviderState = readProviderState;
|
|
39
|
+
exports.writeProviderState = writeProviderState;
|
|
40
|
+
exports.bootstrapProviderStateFromAgentConfig = bootstrapProviderStateFromAgentConfig;
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const runtime_1 = require("../nerves/runtime");
|
|
44
|
+
const identity_1 = require("./identity");
|
|
45
|
+
const LANES = ["outward", "inner"];
|
|
46
|
+
const VALID_SOURCES = new Set(["bootstrap", "local"]);
|
|
47
|
+
const VALID_READINESS = new Set(["ready", "failed", "stale", "unknown"]);
|
|
48
|
+
function isProvider(value) {
|
|
49
|
+
return typeof value === "string" && Object.prototype.hasOwnProperty.call(identity_1.PROVIDER_CREDENTIALS, value);
|
|
50
|
+
}
|
|
51
|
+
function isNonEmptyString(value) {
|
|
52
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
53
|
+
}
|
|
54
|
+
function validateBinding(value, label) {
|
|
55
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
56
|
+
throw new Error(`${label} must be an object`);
|
|
57
|
+
}
|
|
58
|
+
const record = value;
|
|
59
|
+
if (!isProvider(record.provider))
|
|
60
|
+
throw new Error(`${label}.provider must be a valid provider`);
|
|
61
|
+
if (!isNonEmptyString(record.model))
|
|
62
|
+
throw new Error(`${label}.model must be a non-empty string`);
|
|
63
|
+
if (!VALID_SOURCES.has(record.source)) {
|
|
64
|
+
throw new Error(`${label}.source must be bootstrap or local`);
|
|
65
|
+
}
|
|
66
|
+
if (!isNonEmptyString(record.updatedAt))
|
|
67
|
+
throw new Error(`${label}.updatedAt must be a non-empty string`);
|
|
68
|
+
return {
|
|
69
|
+
provider: record.provider,
|
|
70
|
+
model: record.model,
|
|
71
|
+
source: record.source,
|
|
72
|
+
updatedAt: record.updatedAt,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function validateReadiness(value, label) {
|
|
76
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
77
|
+
throw new Error(`${label} must be an object`);
|
|
78
|
+
}
|
|
79
|
+
const record = value;
|
|
80
|
+
if (!VALID_READINESS.has(record.status)) {
|
|
81
|
+
throw new Error(`${label}.status must be ready, failed, stale, or unknown`);
|
|
82
|
+
}
|
|
83
|
+
if (!isProvider(record.provider))
|
|
84
|
+
throw new Error(`${label}.provider must be a valid provider`);
|
|
85
|
+
if (!isNonEmptyString(record.model))
|
|
86
|
+
throw new Error(`${label}.model must be a non-empty string`);
|
|
87
|
+
if (record.checkedAt !== undefined && typeof record.checkedAt !== "string") {
|
|
88
|
+
throw new Error(`${label}.checkedAt must be a string when present`);
|
|
89
|
+
}
|
|
90
|
+
if (record.credentialRevision !== undefined && typeof record.credentialRevision !== "string") {
|
|
91
|
+
throw new Error(`${label}.credentialRevision must be a string when present`);
|
|
92
|
+
}
|
|
93
|
+
if (record.error !== undefined && typeof record.error !== "string") {
|
|
94
|
+
throw new Error(`${label}.error must be a string when present`);
|
|
95
|
+
}
|
|
96
|
+
if (record.attempts !== undefined && typeof record.attempts !== "number") {
|
|
97
|
+
throw new Error(`${label}.attempts must be a number when present`);
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
status: record.status,
|
|
101
|
+
provider: record.provider,
|
|
102
|
+
model: record.model,
|
|
103
|
+
...(record.checkedAt !== undefined ? { checkedAt: record.checkedAt } : {}),
|
|
104
|
+
...(record.credentialRevision !== undefined ? { credentialRevision: record.credentialRevision } : {}),
|
|
105
|
+
...(record.error !== undefined ? { error: record.error } : {}),
|
|
106
|
+
...(record.attempts !== undefined ? { attempts: record.attempts } : {}),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function validateProviderState(value) {
|
|
110
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
111
|
+
throw new Error("provider state must be an object");
|
|
112
|
+
}
|
|
113
|
+
const record = value;
|
|
114
|
+
if (record.schemaVersion !== 1)
|
|
115
|
+
throw new Error("schemaVersion must be 1");
|
|
116
|
+
if (!isNonEmptyString(record.machineId))
|
|
117
|
+
throw new Error("machineId must be a non-empty string");
|
|
118
|
+
if (!isNonEmptyString(record.updatedAt))
|
|
119
|
+
throw new Error("updatedAt must be a non-empty string");
|
|
120
|
+
if (!record.lanes || typeof record.lanes !== "object" || Array.isArray(record.lanes)) {
|
|
121
|
+
throw new Error("lanes must be an object");
|
|
122
|
+
}
|
|
123
|
+
const rawLanes = record.lanes;
|
|
124
|
+
const lanes = {
|
|
125
|
+
outward: validateBinding(rawLanes.outward, "outward"),
|
|
126
|
+
inner: validateBinding(rawLanes.inner, "inner"),
|
|
127
|
+
};
|
|
128
|
+
if (!record.readiness || typeof record.readiness !== "object" || Array.isArray(record.readiness)) {
|
|
129
|
+
throw new Error("readiness must be an object");
|
|
130
|
+
}
|
|
131
|
+
const rawReadiness = record.readiness;
|
|
132
|
+
const readiness = {};
|
|
133
|
+
for (const lane of LANES) {
|
|
134
|
+
if (rawReadiness[lane] !== undefined) {
|
|
135
|
+
readiness[lane] = validateReadiness(rawReadiness[lane], `${lane}.readiness`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
schemaVersion: 1,
|
|
140
|
+
machineId: record.machineId,
|
|
141
|
+
updatedAt: record.updatedAt,
|
|
142
|
+
lanes,
|
|
143
|
+
readiness,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function getProviderStatePath(agentRoot) {
|
|
147
|
+
return path.join(agentRoot, "state", "providers.json");
|
|
148
|
+
}
|
|
149
|
+
function readProviderState(agentRoot) {
|
|
150
|
+
const statePath = getProviderStatePath(agentRoot);
|
|
151
|
+
let raw;
|
|
152
|
+
try {
|
|
153
|
+
raw = fs.readFileSync(statePath, "utf-8");
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
const code = error.code;
|
|
157
|
+
if (code === "ENOENT") {
|
|
158
|
+
return { ok: false, reason: "missing", statePath, error: "provider state not found" };
|
|
159
|
+
}
|
|
160
|
+
return { ok: false, reason: "invalid", statePath, error: String(error) };
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
const state = validateProviderState(JSON.parse(raw));
|
|
164
|
+
(0, runtime_1.emitNervesEvent)({
|
|
165
|
+
component: "config/identity",
|
|
166
|
+
event: "config.provider_state_read",
|
|
167
|
+
message: "read provider state",
|
|
168
|
+
meta: { statePath, machineId: state.machineId },
|
|
169
|
+
});
|
|
170
|
+
return { ok: true, statePath, state };
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
return { ok: false, reason: "invalid", statePath, error: String(error) };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function writeProviderState(agentRoot, state) {
|
|
177
|
+
const statePath = getProviderStatePath(agentRoot);
|
|
178
|
+
const validated = validateProviderState(state);
|
|
179
|
+
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
|
180
|
+
fs.writeFileSync(statePath, `${JSON.stringify(validated, null, 2)}\n`, "utf-8");
|
|
181
|
+
(0, runtime_1.emitNervesEvent)({
|
|
182
|
+
component: "config/identity",
|
|
183
|
+
event: "config.provider_state_written",
|
|
184
|
+
message: "wrote provider state",
|
|
185
|
+
meta: { statePath, machineId: validated.machineId },
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
function binding(provider, model, updatedAt) {
|
|
189
|
+
return {
|
|
190
|
+
provider,
|
|
191
|
+
model,
|
|
192
|
+
source: "bootstrap",
|
|
193
|
+
updatedAt,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function bootstrapProviderStateFromAgentConfig(input) {
|
|
197
|
+
const updatedAt = input.now.toISOString();
|
|
198
|
+
return {
|
|
199
|
+
schemaVersion: 1,
|
|
200
|
+
machineId: input.machineId,
|
|
201
|
+
updatedAt,
|
|
202
|
+
lanes: {
|
|
203
|
+
outward: binding(input.agentConfig.humanFacing.provider, input.agentConfig.humanFacing.model, updatedAt),
|
|
204
|
+
inner: binding(input.agentConfig.agentFacing.provider, input.agentConfig.agentFacing.model, updatedAt),
|
|
205
|
+
},
|
|
206
|
+
readiness: {},
|
|
207
|
+
};
|
|
208
|
+
}
|