@ouro.bot/cli 0.1.0-alpha.684 → 0.1.0-alpha.686
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
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.686",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Queue external-event payloads into private-runtime attention before dispatch."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"version": "0.1.0-alpha.685",
|
|
12
|
+
"changes": [
|
|
13
|
+
"Prevent scaffold-only bundles from being managed as live agents, and keep hatch/adoption bundles disabled until credentials are stored."
|
|
14
|
+
]
|
|
15
|
+
},
|
|
4
16
|
{
|
|
5
17
|
"version": "0.1.0-alpha.684",
|
|
6
18
|
"changes": [
|
|
@@ -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
|
}
|
|
@@ -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 {
|
|
@@ -1221,7 +1222,39 @@ class OuroDaemon {
|
|
|
1221
1222
|
}
|
|
1222
1223
|
return { type: "message", privateTurnDecision: decision };
|
|
1223
1224
|
}
|
|
1224
|
-
|
|
1225
|
+
queueExternalEventForPrivateRuntime(record) {
|
|
1226
|
+
const pendingDir = path.join(this.bundlesRoot, `${record.agent}.ouro`, "state", "pending", pending_1.PRIVATE_RUNTIME_PENDING.friendId, pending_1.PRIVATE_RUNTIME_PENDING.channel, pending_1.PRIVATE_RUNTIME_PENDING.key);
|
|
1227
|
+
const originKey = `${record.source}:${record.eventId}`;
|
|
1228
|
+
const parsedReceivedAt = Date.parse(record.receivedAt);
|
|
1229
|
+
(0, pending_1.queuePendingMessage)(pendingDir, {
|
|
1230
|
+
from: "ouro-external-event",
|
|
1231
|
+
friendId: "ouro-external-event",
|
|
1232
|
+
channel: "external-event",
|
|
1233
|
+
key: originKey,
|
|
1234
|
+
content: (0, router_1.buildExternalEventMessage)(record),
|
|
1235
|
+
timestamp: Number.isFinite(parsedReceivedAt) ? parsedReceivedAt : Date.now(),
|
|
1236
|
+
delegatedFrom: {
|
|
1237
|
+
friendId: "ouro-external-event",
|
|
1238
|
+
channel: "external-event",
|
|
1239
|
+
key: originKey,
|
|
1240
|
+
},
|
|
1241
|
+
obligationStatus: "pending",
|
|
1242
|
+
mode: "relay",
|
|
1243
|
+
});
|
|
1244
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1245
|
+
component: "daemon",
|
|
1246
|
+
event: "daemon.external_event_private_runtime_queued",
|
|
1247
|
+
message: "queued external event for private-runtime attention",
|
|
1248
|
+
meta: {
|
|
1249
|
+
agent: record.agent,
|
|
1250
|
+
source: record.source,
|
|
1251
|
+
eventType: record.eventType,
|
|
1252
|
+
eventId: record.eventId,
|
|
1253
|
+
pendingDir,
|
|
1254
|
+
},
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
async handlePrivateRuntimeWake(command, beforeDispatch) {
|
|
1225
1258
|
if (!this.hasManagedPrivateRuntime(command.agent)) {
|
|
1226
1259
|
return {
|
|
1227
1260
|
ok: false,
|
|
@@ -1239,6 +1272,7 @@ class OuroDaemon {
|
|
|
1239
1272
|
data: { decision },
|
|
1240
1273
|
};
|
|
1241
1274
|
}
|
|
1275
|
+
await beforeDispatch?.(decision);
|
|
1242
1276
|
await this.processManager.startAgent(command.agent);
|
|
1243
1277
|
this.processManager.sendToAgent?.(command.agent, this.buildPrivateRuntimeWorkerWakeMessage(command, decision));
|
|
1244
1278
|
return {
|
|
@@ -1474,7 +1508,7 @@ class OuroDaemon {
|
|
|
1474
1508
|
});
|
|
1475
1509
|
let wake = null;
|
|
1476
1510
|
if (command.wake !== false) {
|
|
1477
|
-
wake = await this.handlePrivateRuntimeWake(this.buildExternalEventPrivateWakeCommand(command, receipt.id));
|
|
1511
|
+
wake = await this.handlePrivateRuntimeWake(this.buildExternalEventPrivateWakeCommand(command, receipt.id), () => this.queueExternalEventForPrivateRuntime(record));
|
|
1478
1512
|
}
|
|
1479
1513
|
return {
|
|
1480
1514
|
ok: true,
|
|
@@ -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
|