@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 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 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
  }
@@ -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 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 {
@@ -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.683",
3
+ "version": "0.1.0-alpha.685",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",