@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 listAllBundleAgents(options = {}) {
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 listAllBundleAgents(options)
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
- return { name, enabled };
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 dot = row.enabled ? green("●") : dim("○");
441
- const stateText = row.enabled ? "enabled " : "disabled";
442
- lines.push(` ${name} ${dot} ${stateText}`);
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 providers = agents.flatMap((agent) => (0, provider_visibility_1.providerVisibilityStatusRows)((0, provider_visibility_1.buildAgentProviderVisibility)({
619
- agentName: agent.name,
620
- agentRoot: path.join(this.bundlesRoot, `${agent.name}.ouro`),
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
- async handlePrivateRuntimeWake(command) {
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.enabled = true;
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.684",
3
+ "version": "0.1.0-alpha.686",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",