@ouro.bot/cli 0.1.0-alpha.683 → 0.1.0-alpha.685
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 +12 -0
- package/dist/heart/agent-entry.js +5 -0
- package/dist/heart/daemon/agent-discovery.js +54 -4
- package/dist/heart/daemon/cli-render.js +10 -4
- package/dist/heart/daemon/daemon-entry.js +25 -0
- package/dist/heart/daemon/daemon.js +4 -3
- package/dist/heart/hatch/hatch-flow.js +9 -1
- package/dist/heart/hatch/specialist-tools.js +8 -0
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
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.685",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Prevent scaffold-only bundles from being managed as live agents, and keep hatch/adoption bundles disabled until credentials are stored."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"version": "0.1.0-alpha.684",
|
|
12
|
+
"changes": [
|
|
13
|
+
"Bootstrap private-runtime provider credentials before queued work is flushed."
|
|
14
|
+
]
|
|
15
|
+
},
|
|
4
16
|
{
|
|
5
17
|
"version": "0.1.0-alpha.683",
|
|
6
18
|
"changes": [
|
|
@@ -122,6 +122,7 @@ if (!ipcState.installed) {
|
|
|
122
122
|
// Dynamic import: agent-entry is process-start wiring that starts a sense process.
|
|
123
123
|
// Using dynamic import avoids a static heart/ -> senses/ dependency.
|
|
124
124
|
Promise.resolve().then(() => __importStar(require("./runtime-credentials"))).then(async ({ readMachineRuntimeCredentialConfig, readRuntimeCredentialConfig, refreshMachineRuntimeCredentialConfig, refreshRuntimeCredentialConfig, applyRuntimeCredentialBootstrapMessage, waitForRuntimeCredentialBootstrap, }) => {
|
|
125
|
+
const { readProviderCredentialPool, refreshProviderCredentialPool } = await Promise.resolve().then(() => __importStar(require("./provider-credentials")));
|
|
125
126
|
const bootstrappedBeforeWait = drainBufferedRuntimeCredentialBootstrap(applyRuntimeCredentialBootstrapMessage);
|
|
126
127
|
const bootstrappedDuringWait = bootstrappedBeforeWait ? true : await waitForRuntimeCredentialBootstrap(agentName);
|
|
127
128
|
if (bootstrappedDuringWait) {
|
|
@@ -133,6 +134,10 @@ Promise.resolve().then(() => __importStar(require("./runtime-credentials"))).the
|
|
|
133
134
|
if (!readRuntimeCredentialConfig(agentName).ok) {
|
|
134
135
|
void refreshRuntimeCredentialConfig(agentName, { preserveCachedOnFailure: true }).catch(() => undefined);
|
|
135
136
|
}
|
|
137
|
+
const providerPool = readProviderCredentialPool(agentName);
|
|
138
|
+
if (!providerPool.ok || Object.keys(providerPool.pool.providers).length === 0) {
|
|
139
|
+
await refreshProviderCredentialPool(agentName, { preserveCachedOnFailure: true }).catch(() => undefined);
|
|
140
|
+
}
|
|
136
141
|
/* v8 ignore next 7 -- process-start best-effort machine credential refresh runs in a child entrypoint and is covered operationally by daemon startup tests @preserve */
|
|
137
142
|
if (!readMachineRuntimeCredentialConfig(agentName).ok) {
|
|
138
143
|
void Promise.resolve().then(() => __importStar(require("./machine-identity"))).then(({ loadOrCreateMachineIdentity }) => {
|
|
@@ -51,6 +51,46 @@ const runtime_1 = require("../../nerves/runtime");
|
|
|
51
51
|
function isLibraryKind(kind) {
|
|
52
52
|
return kind === "library";
|
|
53
53
|
}
|
|
54
|
+
function isRecord(value) {
|
|
55
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
56
|
+
}
|
|
57
|
+
function hasConfiguredVault(parsed) {
|
|
58
|
+
const vault = parsed.vault;
|
|
59
|
+
if (!isRecord(vault))
|
|
60
|
+
return false;
|
|
61
|
+
return typeof vault.email === "string" && vault.email.trim().length > 0
|
|
62
|
+
&& typeof vault.serverUrl === "string" && vault.serverUrl.trim().length > 0;
|
|
63
|
+
}
|
|
64
|
+
function hasEnabledSync(parsed) {
|
|
65
|
+
const sync = parsed.sync;
|
|
66
|
+
return isRecord(sync) && sync.enabled === true;
|
|
67
|
+
}
|
|
68
|
+
function hasEnabledExternalSense(parsed) {
|
|
69
|
+
const senses = parsed.senses;
|
|
70
|
+
if (!isRecord(senses))
|
|
71
|
+
return false;
|
|
72
|
+
for (const [name, value] of Object.entries(senses)) {
|
|
73
|
+
if (name === "cli")
|
|
74
|
+
continue;
|
|
75
|
+
if (isRecord(value) && value.enabled === true)
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
function isManageableAgentConfig(parsed) {
|
|
81
|
+
return hasConfiguredVault(parsed)
|
|
82
|
+
|| hasEnabledSync(parsed)
|
|
83
|
+
|| hasEnabledExternalSense(parsed);
|
|
84
|
+
}
|
|
85
|
+
function stripDiscoveryMetadata(row) {
|
|
86
|
+
const publicRow = { name: row.name, enabled: row.enabled };
|
|
87
|
+
if (row.kind !== undefined)
|
|
88
|
+
publicRow.kind = row.kind;
|
|
89
|
+
if (row.managementBlockedReason !== undefined) {
|
|
90
|
+
publicRow.managementBlockedReason = row.managementBlockedReason;
|
|
91
|
+
}
|
|
92
|
+
return publicRow;
|
|
93
|
+
}
|
|
54
94
|
/**
|
|
55
95
|
* Walk the bundles root and return one row per `<name>.ouro` directory whose
|
|
56
96
|
* `agent.json` is readable and parseable. Includes both enabled and disabled
|
|
@@ -62,7 +102,7 @@ function isLibraryKind(kind) {
|
|
|
62
102
|
*
|
|
63
103
|
* Sorted alphabetically by name for stable display.
|
|
64
104
|
*/
|
|
65
|
-
function
|
|
105
|
+
function discoverBundleAgents(options = {}) {
|
|
66
106
|
const bundlesRoot = options.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
|
|
67
107
|
const readdirSync = options.readdirSync ?? fs.readdirSync;
|
|
68
108
|
const readFileSync = options.readFileSync ?? fs.readFileSync;
|
|
@@ -88,34 +128,44 @@ function listAllBundleAgents(options = {}) {
|
|
|
88
128
|
const configPath = path.join(bundlesRoot, entry.name, "agent.json");
|
|
89
129
|
let enabled = true;
|
|
90
130
|
let kind;
|
|
131
|
+
let manageable = false;
|
|
91
132
|
try {
|
|
92
133
|
const raw = readFileSync(configPath, "utf-8");
|
|
93
134
|
const parsed = JSON.parse(raw);
|
|
135
|
+
if (!isRecord(parsed))
|
|
136
|
+
continue;
|
|
94
137
|
if (typeof parsed.enabled === "boolean") {
|
|
95
138
|
enabled = parsed.enabled;
|
|
96
139
|
}
|
|
97
140
|
if (typeof parsed.kind === "string") {
|
|
98
141
|
kind = parsed.kind;
|
|
99
142
|
}
|
|
143
|
+
manageable = isManageableAgentConfig(parsed);
|
|
100
144
|
}
|
|
101
145
|
catch {
|
|
102
146
|
continue;
|
|
103
147
|
}
|
|
104
|
-
const row = { name: agentName, enabled };
|
|
148
|
+
const row = { name: agentName, enabled, manageable };
|
|
105
149
|
if (kind !== undefined)
|
|
106
150
|
row.kind = kind;
|
|
151
|
+
if (enabled && !isLibraryKind(kind) && !manageable) {
|
|
152
|
+
row.managementBlockedReason = "inactive scaffold: no vault locator, sync, or enabled external sense";
|
|
153
|
+
}
|
|
107
154
|
discovered.push(row);
|
|
108
155
|
}
|
|
109
156
|
return discovered.sort((left, right) => left.name.localeCompare(right.name));
|
|
110
157
|
}
|
|
158
|
+
function listAllBundleAgents(options = {}) {
|
|
159
|
+
return discoverBundleAgents(options).map(stripDiscoveryMetadata);
|
|
160
|
+
}
|
|
111
161
|
/**
|
|
112
162
|
* Real agents only — excludes both disabled bundles and library-kind bundles.
|
|
113
163
|
* Library bundles (SerpentGuide, RepairGuide, …) are content-only and must
|
|
114
164
|
* never appear in spawn lists, status rollups, or sync rows.
|
|
115
165
|
*/
|
|
116
166
|
function listEnabledBundleAgents(options = {}) {
|
|
117
|
-
return
|
|
118
|
-
.filter((row) => row.enabled && !isLibraryKind(row.kind))
|
|
167
|
+
return discoverBundleAgents(options)
|
|
168
|
+
.filter((row) => row.enabled && !isLibraryKind(row.kind) && row.manageable)
|
|
119
169
|
.map((row) => row.name);
|
|
120
170
|
}
|
|
121
171
|
function readPrivateRuntimeConfig(agent, options = {}) {
|
|
@@ -276,7 +276,11 @@ function parseStatusPayload(data) {
|
|
|
276
276
|
const enabled = booleanField(row.enabled);
|
|
277
277
|
if (!name || enabled === null)
|
|
278
278
|
return null;
|
|
279
|
-
|
|
279
|
+
const parsed = { name, enabled };
|
|
280
|
+
const managementBlockedReason = stringField(row.managementBlockedReason);
|
|
281
|
+
if (managementBlockedReason !== null)
|
|
282
|
+
parsed.managementBlockedReason = managementBlockedReason;
|
|
283
|
+
return parsed;
|
|
280
284
|
});
|
|
281
285
|
const parsedProviders = (providers ?? []).map((entry) => {
|
|
282
286
|
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
@@ -437,9 +441,11 @@ function formatDaemonStatusOutput(response, fallback) {
|
|
|
437
441
|
const agentNameWidth = Math.max(12, ...payload.agents.map((r) => r.name.length));
|
|
438
442
|
for (const row of payload.agents) {
|
|
439
443
|
const name = row.name.padEnd(agentNameWidth);
|
|
440
|
-
const
|
|
441
|
-
const
|
|
442
|
-
|
|
444
|
+
const inactive = row.enabled && !!row.managementBlockedReason;
|
|
445
|
+
const dot = inactive ? yellow("●") : row.enabled ? green("●") : dim("○");
|
|
446
|
+
const stateText = inactive ? "inactive" : row.enabled ? "enabled " : "disabled";
|
|
447
|
+
const detail = inactive ? ` ${dim(row.managementBlockedReason)}` : "";
|
|
448
|
+
lines.push(` ${name} ${dot} ${stateText}${detail}`);
|
|
443
449
|
}
|
|
444
450
|
lines.push("");
|
|
445
451
|
}
|
|
@@ -66,6 +66,8 @@ const bundle_manifest_1 = require("../../mind/bundle-manifest");
|
|
|
66
66
|
const mcp_canary_1 = require("./mcp-canary");
|
|
67
67
|
const context_loss_sentinel_1 = require("../context-loss-sentinel");
|
|
68
68
|
const provider_credentials_1 = require("../provider-credentials");
|
|
69
|
+
const runtime_credentials_1 = require("../runtime-credentials");
|
|
70
|
+
const machine_identity_1 = require("../machine-identity");
|
|
69
71
|
function parseSocketPath(argv) {
|
|
70
72
|
const socketIndex = argv.indexOf("--socket");
|
|
71
73
|
if (socketIndex >= 0) {
|
|
@@ -100,6 +102,28 @@ const managedPrivateRuntimes = managedAgents.map((agent) => ({
|
|
|
100
102
|
agent,
|
|
101
103
|
config: (0, agent_discovery_1.readPrivateRuntimeConfig)(agent),
|
|
102
104
|
}));
|
|
105
|
+
function currentMachineId() {
|
|
106
|
+
return (0, machine_identity_1.loadOrCreateMachineIdentity)().machineId;
|
|
107
|
+
}
|
|
108
|
+
function privateRuntimeCredentialBootstrapFor(agent) {
|
|
109
|
+
const machineId = currentMachineId();
|
|
110
|
+
const runtime = (0, runtime_credentials_1.readRuntimeCredentialConfig)(agent);
|
|
111
|
+
const machine = (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(agent);
|
|
112
|
+
const providerPool = (0, provider_credentials_1.readProviderCredentialPool)(agent);
|
|
113
|
+
const providerCredentialRecords = providerPool.ok
|
|
114
|
+
? Object.values(providerPool.pool.providers).filter((record) => !!record)
|
|
115
|
+
: [];
|
|
116
|
+
const bootstrap = {
|
|
117
|
+
agentName: agent,
|
|
118
|
+
runtimeConfig: runtime.ok ? runtime.config : undefined,
|
|
119
|
+
machineRuntimeConfig: machine.ok ? machine.config : undefined,
|
|
120
|
+
machineId,
|
|
121
|
+
providerCredentialRecords: providerCredentialRecords.length > 0 ? providerCredentialRecords : undefined,
|
|
122
|
+
};
|
|
123
|
+
if (!bootstrap.runtimeConfig && !bootstrap.machineRuntimeConfig && !bootstrap.providerCredentialRecords)
|
|
124
|
+
return null;
|
|
125
|
+
return bootstrap;
|
|
126
|
+
}
|
|
103
127
|
function sentinelHealthStatus(receipt) {
|
|
104
128
|
if (receipt.verdict === "ready")
|
|
105
129
|
return "ok";
|
|
@@ -178,6 +202,7 @@ const processManager = new process_manager_1.DaemonProcessManager({
|
|
|
178
202
|
entry: "heart/agent-entry.js",
|
|
179
203
|
channel: "private-runtime",
|
|
180
204
|
autoStart: config.autoStart,
|
|
205
|
+
getRuntimeCredentialBootstrap: () => privateRuntimeCredentialBootstrapFor(agent),
|
|
181
206
|
})),
|
|
182
207
|
existsSync: fs.existsSync,
|
|
183
208
|
/* v8 ignore next 4 -- wiring: delegates to checkAgentConfig which has full unit tests @preserve */
|
|
@@ -615,9 +615,10 @@ class OuroDaemon {
|
|
|
615
615
|
const repoRoot = (0, identity_1.getRepoRoot)();
|
|
616
616
|
const sync = (0, agent_discovery_1.listBundleSyncRows)({ bundlesRoot: this.bundlesRoot });
|
|
617
617
|
const agents = (0, agent_discovery_1.listAllBundleAgents)({ bundlesRoot: this.bundlesRoot });
|
|
618
|
-
const
|
|
619
|
-
|
|
620
|
-
|
|
618
|
+
const providerAgents = (0, agent_discovery_1.listEnabledBundleAgents)({ bundlesRoot: this.bundlesRoot });
|
|
619
|
+
const providers = providerAgents.flatMap((agentName) => (0, provider_visibility_1.providerVisibilityStatusRows)((0, provider_visibility_1.buildAgentProviderVisibility)({
|
|
620
|
+
agentName,
|
|
621
|
+
agentRoot: path.join(this.bundlesRoot, `${agentName}.ouro`),
|
|
621
622
|
})));
|
|
622
623
|
const mailboxUrl = this.mailboxServer?.origin ?? "http://127.0.0.1:0";
|
|
623
624
|
return {
|
|
@@ -135,9 +135,16 @@ function writeHatchlingAgentConfig(bundleRoot, input) {
|
|
|
135
135
|
template.provider = input.provider;
|
|
136
136
|
template.humanFacing = { provider: input.provider, model };
|
|
137
137
|
template.agentFacing = { provider: input.provider, model };
|
|
138
|
-
template.
|
|
138
|
+
template.vault = (0, identity_1.resolveVaultConfig)(input.agentName);
|
|
139
|
+
template.enabled = false;
|
|
139
140
|
fs.writeFileSync(path.join(bundleRoot, "agent.json"), `${JSON.stringify(template, null, 2)}\n`, "utf-8");
|
|
140
141
|
}
|
|
142
|
+
function setHatchlingEnabled(bundleRoot, enabled) {
|
|
143
|
+
const agentJsonPath = path.join(bundleRoot, "agent.json");
|
|
144
|
+
const parsed = JSON.parse(fs.readFileSync(agentJsonPath, "utf-8"));
|
|
145
|
+
parsed.enabled = enabled;
|
|
146
|
+
fs.writeFileSync(agentJsonPath, `${JSON.stringify(parsed, null, 2)}\n`, "utf-8");
|
|
147
|
+
}
|
|
141
148
|
async function runHatchFlow(input, deps = {}) {
|
|
142
149
|
(0, runtime_1.emitNervesEvent)({
|
|
143
150
|
component: "daemon",
|
|
@@ -180,6 +187,7 @@ async function runHatchFlow(input, deps = {}) {
|
|
|
180
187
|
writeRecordScaffold(bundleRoot);
|
|
181
188
|
writeFriendImprint(bundleRoot, input.humanName, now);
|
|
182
189
|
writeHeartbeatHabit(bundleRoot, now);
|
|
190
|
+
setHatchlingEnabled(bundleRoot, true);
|
|
183
191
|
(0, runtime_1.emitNervesEvent)({
|
|
184
192
|
component: "daemon",
|
|
185
193
|
event: "daemon.hatch_flow_end",
|
|
@@ -147,6 +147,11 @@ function moveDir(src, dest) {
|
|
|
147
147
|
/* v8 ignore stop */
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
|
+
function updateAgentConfig(bundleRoot, updates) {
|
|
151
|
+
const agentJsonPath = path.join(bundleRoot, "agent.json");
|
|
152
|
+
const parsed = JSON.parse(fs.readFileSync(agentJsonPath, "utf-8"));
|
|
153
|
+
fs.writeFileSync(agentJsonPath, `${JSON.stringify({ ...parsed, ...updates }, null, 2)}\n`, "utf-8");
|
|
154
|
+
}
|
|
150
155
|
async function execCompleteAdoption(args, deps) {
|
|
151
156
|
const name = args.name;
|
|
152
157
|
const handoffMessage = args.handoff_message;
|
|
@@ -188,6 +193,8 @@ async function execCompleteAdoption(args, deps) {
|
|
|
188
193
|
catch (error) {
|
|
189
194
|
return `error: failed to read hatchling vault unlock secret: ${error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error)}`;
|
|
190
195
|
}
|
|
196
|
+
// Keep the bundle inert until vault/provider credentials are durably stored.
|
|
197
|
+
updateAgentConfig(deps.tempDir, { enabled: false, vault });
|
|
191
198
|
// Scaffold structural dirs into tempDir
|
|
192
199
|
scaffoldBundle(deps.tempDir);
|
|
193
200
|
// Move tempDir -> final bundle location
|
|
@@ -237,6 +244,7 @@ async function execCompleteAdoption(args, deps) {
|
|
|
237
244
|
const friendPath = path.join(targetBundle, "friends", `${friendId}.json`);
|
|
238
245
|
fs.writeFileSync(friendPath, JSON.stringify(friendRecord, null, 2), "utf-8");
|
|
239
246
|
}
|
|
247
|
+
updateAgentConfig(targetBundle, { enabled: true });
|
|
240
248
|
// Play hatch animation
|
|
241
249
|
await (0, hatch_animation_1.playHatchAnimation)(name, deps.animationWriter);
|
|
242
250
|
// Display handoff message
|