@ouro.bot/cli 0.1.0-alpha.204 → 0.1.0-alpha.206

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.206",
6
+ "changes": [
7
+ "feat: capability discovery and tiered self-configuration — config registry with tier-aware metadata (T1 self-service, T2 proposal, T3 operator-only), read_config tool with topic-filtered discovery, update_config tool for T1 immediate changes, propose_config tool for T2 operator-approval flow, version-change surfacing in start-of-turn packet via buildCapabilitiesSection"
8
+ ]
9
+ },
10
+ {
11
+ "version": "0.1.0-alpha.205",
12
+ "changes": [
13
+ "refactor: enforce subsystem boundaries — eliminate all heart/ and nerves/ static imports from senses/, move AttentionItem type to heart/, surfaceToolDef to repertoire/, SteeringFollowUpEffect to heart/turn-coordinator, inline BlueBubbles runtime state reader in daemon"
14
+ ]
15
+ },
4
16
  {
5
17
  "version": "0.1.0-alpha.204",
6
18
  "changes": [
@@ -1,4 +1,37 @@
1
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
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  // Unified agent runtime entrypoint.
4
37
  // Requires --agent before importing runtime modules that rely on identity.
@@ -7,10 +40,12 @@ if (!process.argv.includes("--agent")) {
7
40
  console.error("Missing required --agent <name> argument.\nUsage: node dist/heart/agent-entry.js --agent ouroboros");
8
41
  process.exit(1);
9
42
  }
10
- const inner_dialog_worker_1 = require("../senses/inner-dialog-worker");
11
43
  const cli_logging_1 = require("../nerves/cli-logging");
12
44
  (0, cli_logging_1.configureCliRuntimeLogger)("self");
13
- (0, inner_dialog_worker_1.startInnerDialogWorker)().catch((error) => {
45
+ // Dynamic import: agent-entry is boot-time wiring that starts a sense process.
46
+ // Using dynamic import avoids a static heart/ -> senses/ dependency.
47
+ Promise.resolve().then(() => __importStar(require("../senses/inner-dialog-worker"))).then(({ startInnerDialogWorker }) => startInnerDialogWorker())
48
+ .catch((error) => {
14
49
  // eslint-disable-next-line no-console -- fatal startup guard for worker process
15
50
  console.error(error instanceof Error ? error.message : String(error));
16
51
  process.exit(1);
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ /**
3
+ * Shared attention queue types.
4
+ *
5
+ * Canonical home for the AttentionItem interface — consumed by heart/, repertoire/, nerves/, and senses/.
6
+ * Placed in heart/ because attention is a turn-coordination concern.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,305 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CONFIG_REGISTRY = void 0;
4
+ exports.getRegistryEntries = getRegistryEntries;
5
+ exports.getRegistryEntriesByTier = getRegistryEntriesByTier;
6
+ exports.getRegistryEntriesByTopic = getRegistryEntriesByTopic;
7
+ exports.getRegistryEntry = getRegistryEntry;
8
+ const runtime_1 = require("../nerves/runtime");
9
+ // --- Validation helpers ---
10
+ const KNOWN_PROVIDERS = ["anthropic", "azure", "minimax", "openai-codex", "github-copilot"];
11
+ function validateNumber(value) {
12
+ if (typeof value !== "number")
13
+ return `expected number, got ${typeof value}`;
14
+ return undefined;
15
+ }
16
+ function validateBoolean(value) {
17
+ if (typeof value !== "boolean")
18
+ return `expected boolean, got ${typeof value}`;
19
+ return undefined;
20
+ }
21
+ function validateString(value) {
22
+ if (typeof value !== "string")
23
+ return `expected string, got ${typeof value}`;
24
+ return undefined;
25
+ }
26
+ function validateStringEnum(allowed) {
27
+ return (value) => {
28
+ if (typeof value !== "string")
29
+ return `expected string, got ${typeof value}`;
30
+ if (!allowed.includes(value))
31
+ return `expected one of [${allowed.join(", ")}], got "${value}"`;
32
+ return undefined;
33
+ };
34
+ }
35
+ function validateInteger(min, max) {
36
+ return (value) => {
37
+ if (typeof value !== "number")
38
+ return `expected number, got ${typeof value}`;
39
+ if (!Number.isInteger(value))
40
+ return `expected integer, got ${value}`;
41
+ if (value < min || value > max)
42
+ return `expected integer between ${min} and ${max}, got ${value}`;
43
+ return undefined;
44
+ };
45
+ }
46
+ function validateStringArray(value) {
47
+ if (!Array.isArray(value))
48
+ return `expected array, got ${typeof value}`;
49
+ for (let i = 0; i < value.length; i++) {
50
+ if (typeof value[i] !== "string")
51
+ return `expected string at index ${i}, got ${typeof value[i]}`;
52
+ }
53
+ return undefined;
54
+ }
55
+ function validateStringEnumArray(allowed) {
56
+ return (value) => {
57
+ if (!Array.isArray(value))
58
+ return `expected array, got ${typeof value}`;
59
+ for (let i = 0; i < value.length; i++) {
60
+ if (typeof value[i] !== "string")
61
+ return `expected string at index ${i}, got ${typeof value[i]}`;
62
+ if (!allowed.includes(value[i]))
63
+ return `expected one of [${allowed.join(", ")}] at index ${i}, got "${value[i]}"`;
64
+ }
65
+ return undefined;
66
+ };
67
+ }
68
+ function validateObject(requiredFields) {
69
+ return (value) => {
70
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
71
+ return `expected object, got ${Array.isArray(value) ? "array" : typeof value}`;
72
+ }
73
+ const obj = value;
74
+ for (const [field, validator] of Object.entries(requiredFields)) {
75
+ if (!(field in obj))
76
+ return `missing required field "${field}"`;
77
+ const err = validator(obj[field]);
78
+ if (err)
79
+ return `field "${field}": ${err}`;
80
+ }
81
+ return undefined;
82
+ };
83
+ }
84
+ const registryData = [
85
+ // --- Tier 3: Operator-only ---
86
+ {
87
+ path: "version",
88
+ tier: 3,
89
+ description: "Agent config schema version. Must be an integer >= 1.",
90
+ default: 2,
91
+ effects: "Controls which config migrations apply on load. Changing incorrectly can corrupt config.",
92
+ topics: ["schema", "migration"],
93
+ validate: validateNumber,
94
+ },
95
+ {
96
+ path: "enabled",
97
+ tier: 3,
98
+ description: "Whether the agent is enabled. When false, the agent refuses to start.",
99
+ default: true,
100
+ effects: "Disables all agent functionality when set to false.",
101
+ topics: ["lifecycle", "activation"],
102
+ validate: validateBoolean,
103
+ },
104
+ {
105
+ path: "mcpServers",
106
+ tier: 3,
107
+ description: "MCP server configurations. Maps server name to command, args, and env.",
108
+ default: undefined,
109
+ effects: "Adds or removes MCP tool servers. Incorrect config can expose the agent to untrusted tool sources.",
110
+ topics: ["tools", "mcp", "servers", "extensions"],
111
+ },
112
+ // --- Tier 2: Proposal ---
113
+ {
114
+ path: "humanFacing.provider",
115
+ tier: 2,
116
+ description: "Provider for human-facing interactions (CLI, Teams, BlueBubbles).",
117
+ default: "anthropic",
118
+ effects: "Changes the LLM provider used for all human-facing conversations. Affects quality, latency, and cost.",
119
+ topics: ["model", "provider", "llm", "human"],
120
+ validate: validateStringEnum(KNOWN_PROVIDERS),
121
+ },
122
+ {
123
+ path: "humanFacing.model",
124
+ tier: 2,
125
+ description: "Model name for human-facing interactions.",
126
+ default: "claude-opus-4-6",
127
+ effects: "Changes the specific model used for human-facing conversations.",
128
+ topics: ["model", "provider", "llm", "human"],
129
+ validate: validateString,
130
+ },
131
+ {
132
+ path: "agentFacing.provider",
133
+ tier: 2,
134
+ description: "Provider for agent-facing interactions (inner dialog, delegation).",
135
+ default: "anthropic",
136
+ effects: "Changes the LLM provider used for inner dialog and agent-to-agent communication.",
137
+ topics: ["model", "provider", "llm", "agent", "inner-dialog"],
138
+ validate: validateStringEnum(KNOWN_PROVIDERS),
139
+ },
140
+ {
141
+ path: "agentFacing.model",
142
+ tier: 2,
143
+ description: "Model name for agent-facing interactions.",
144
+ default: "claude-opus-4-6",
145
+ effects: "Changes the specific model used for inner dialog and agent-to-agent communication.",
146
+ topics: ["model", "provider", "llm", "agent", "inner-dialog"],
147
+ validate: validateString,
148
+ },
149
+ {
150
+ path: "context.maxTokens",
151
+ tier: 2,
152
+ description: "Maximum context window size in tokens.",
153
+ default: 80000,
154
+ effects: "Larger values allow more context but increase cost and latency. Must match model capability.",
155
+ topics: ["context", "tokens", "memory", "performance"],
156
+ validate: validateInteger(1000, 1000000),
157
+ },
158
+ {
159
+ path: "senses.cli",
160
+ tier: 2,
161
+ description: "CLI sense configuration. Controls whether the CLI interface is enabled.",
162
+ default: { enabled: true },
163
+ effects: "Enables or disables the CLI (terminal) interaction channel.",
164
+ topics: ["senses", "cli", "channels", "interface"],
165
+ validate: validateObject({ enabled: validateBoolean }),
166
+ },
167
+ {
168
+ path: "senses.teams",
169
+ tier: 2,
170
+ description: "Teams sense configuration. Controls whether the Teams interface is enabled.",
171
+ default: { enabled: false },
172
+ effects: "Enables or disables the Microsoft Teams interaction channel.",
173
+ topics: ["senses", "teams", "channels", "interface"],
174
+ validate: validateObject({ enabled: validateBoolean }),
175
+ },
176
+ {
177
+ path: "senses.bluebubbles",
178
+ tier: 2,
179
+ description: "BlueBubbles sense configuration. Controls whether the iMessage interface is enabled.",
180
+ default: { enabled: false },
181
+ effects: "Enables or disables the BlueBubbles (iMessage) interaction channel.",
182
+ topics: ["senses", "bluebubbles", "imessage", "channels", "interface"],
183
+ validate: validateObject({ enabled: validateBoolean }),
184
+ },
185
+ {
186
+ path: "sync.enabled",
187
+ tier: 2,
188
+ description: "Whether git-based bundle sync is enabled.",
189
+ default: false,
190
+ effects: "Enables automatic synchronization of agent state via git. Requires sync.remote to be configured.",
191
+ topics: ["sync", "git", "state", "backup"],
192
+ validate: validateBoolean,
193
+ },
194
+ {
195
+ path: "sync.remote",
196
+ tier: 2,
197
+ description: "Git remote name used for bundle sync.",
198
+ default: "origin",
199
+ effects: "Controls which git remote is used when sync is enabled.",
200
+ topics: ["sync", "git", "remote"],
201
+ validate: validateString,
202
+ },
203
+ // --- Tier 1: Self-service ---
204
+ {
205
+ path: "context.contextMargin",
206
+ tier: 1,
207
+ description: "Percentage of context window reserved as margin before compaction triggers.",
208
+ default: 20,
209
+ effects: "Higher values trigger compaction earlier, preserving more headroom. Lower values use more context.",
210
+ topics: ["context", "compaction", "memory", "performance"],
211
+ validate: validateInteger(0, 100),
212
+ },
213
+ {
214
+ path: "phrases.thinking",
215
+ tier: 1,
216
+ description: "Array of phrases displayed while the agent is thinking.",
217
+ default: ["working"],
218
+ effects: "Changes the thinking indicator text shown to users. Purely cosmetic.",
219
+ topics: ["phrases", "ux", "display", "personality"],
220
+ validate: validateStringArray,
221
+ },
222
+ {
223
+ path: "phrases.tool",
224
+ tier: 1,
225
+ description: "Array of phrases displayed while the agent is running a tool.",
226
+ default: ["running tool"],
227
+ effects: "Changes the tool-use indicator text shown to users. Purely cosmetic.",
228
+ topics: ["phrases", "ux", "display", "personality"],
229
+ validate: validateStringArray,
230
+ },
231
+ {
232
+ path: "phrases.followup",
233
+ tier: 1,
234
+ description: "Array of phrases displayed during follow-up processing.",
235
+ default: ["processing"],
236
+ effects: "Changes the follow-up indicator text shown to users. Purely cosmetic.",
237
+ topics: ["phrases", "ux", "display", "personality"],
238
+ validate: validateStringArray,
239
+ },
240
+ {
241
+ path: "shell.defaultTimeout",
242
+ tier: 1,
243
+ description: "Default timeout in milliseconds for shell command execution.",
244
+ default: undefined,
245
+ effects: "Controls how long shell commands run before timing out. Undefined uses system default.",
246
+ topics: ["shell", "timeout", "execution", "tools"],
247
+ validate: validateInteger(1000, 600000),
248
+ },
249
+ {
250
+ path: "logging.level",
251
+ tier: 1,
252
+ description: "Minimum log level: debug, info, warn, or error.",
253
+ default: undefined,
254
+ effects: "Controls verbosity of runtime logging. Lower levels produce more output.",
255
+ topics: ["logging", "debug", "diagnostics"],
256
+ validate: validateStringEnum(["debug", "info", "warn", "error"]),
257
+ },
258
+ {
259
+ path: "logging.sinks",
260
+ tier: 1,
261
+ description: "Array of log sink types: 'terminal' and/or 'ndjson'.",
262
+ default: undefined,
263
+ effects: "Controls where log output is directed. Terminal shows in console, ndjson writes structured logs.",
264
+ topics: ["logging", "output", "diagnostics"],
265
+ validate: validateStringEnumArray(["terminal", "ndjson"]),
266
+ },
267
+ ];
268
+ exports.CONFIG_REGISTRY = new Map(registryData.map((entry) => [entry.path, entry]));
269
+ function getRegistryEntries() {
270
+ (0, runtime_1.emitNervesEvent)({
271
+ component: "heart",
272
+ event: "config_registry.access",
273
+ message: "listing all registry entries",
274
+ meta: { count: exports.CONFIG_REGISTRY.size },
275
+ });
276
+ return [...exports.CONFIG_REGISTRY.values()];
277
+ }
278
+ function getRegistryEntriesByTier(tier) {
279
+ (0, runtime_1.emitNervesEvent)({
280
+ component: "heart",
281
+ event: "config_registry.access",
282
+ message: `filtering registry entries by tier ${tier}`,
283
+ meta: { tier },
284
+ });
285
+ return [...exports.CONFIG_REGISTRY.values()].filter((entry) => entry.tier === tier);
286
+ }
287
+ function getRegistryEntriesByTopic(topic) {
288
+ const needle = topic.toLowerCase();
289
+ (0, runtime_1.emitNervesEvent)({
290
+ component: "heart",
291
+ event: "config_registry.access",
292
+ message: `filtering registry entries by topic "${topic}"`,
293
+ meta: { topic },
294
+ });
295
+ return [...exports.CONFIG_REGISTRY.values()].filter((entry) => entry.topics.some((t) => t.toLowerCase().includes(needle)));
296
+ }
297
+ function getRegistryEntry(path) {
298
+ (0, runtime_1.emitNervesEvent)({
299
+ component: "heart",
300
+ event: "config_registry.access",
301
+ message: `looking up registry entry for "${path}"`,
302
+ meta: { path },
303
+ });
304
+ return exports.CONFIG_REGISTRY.get(path);
305
+ }
@@ -18,7 +18,7 @@ const config_1 = require("./config");
18
18
  const identity_1 = require("./identity");
19
19
  const tools_1 = require("../repertoire/tools");
20
20
  const channel_1 = require("../mind/friends/channel");
21
- const surface_tool_1 = require("../senses/surface-tool");
21
+ const tools_2 = require("../repertoire/tools");
22
22
  const runtime_1 = require("../nerves/runtime");
23
23
  const context_1 = require("../mind/context");
24
24
  const prompt_1 = require("../mind/prompt");
@@ -148,11 +148,11 @@ function getProviderDisplayLabel(facing = "human") {
148
148
  return providerLabelBuilders[provider]();
149
149
  }
150
150
  // Re-export tools, execTool, summarizeArgs from ./tools for backward compat
151
- var tools_2 = require("../repertoire/tools");
152
- Object.defineProperty(exports, "tools", { enumerable: true, get: function () { return tools_2.tools; } });
153
- Object.defineProperty(exports, "execTool", { enumerable: true, get: function () { return tools_2.execTool; } });
154
- Object.defineProperty(exports, "summarizeArgs", { enumerable: true, get: function () { return tools_2.summarizeArgs; } });
155
- Object.defineProperty(exports, "getToolsForChannel", { enumerable: true, get: function () { return tools_2.getToolsForChannel; } });
151
+ var tools_3 = require("../repertoire/tools");
152
+ Object.defineProperty(exports, "tools", { enumerable: true, get: function () { return tools_3.tools; } });
153
+ Object.defineProperty(exports, "execTool", { enumerable: true, get: function () { return tools_3.execTool; } });
154
+ Object.defineProperty(exports, "summarizeArgs", { enumerable: true, get: function () { return tools_3.summarizeArgs; } });
155
+ Object.defineProperty(exports, "getToolsForChannel", { enumerable: true, get: function () { return tools_3.getToolsForChannel; } });
156
156
  // Re-export streaming functions for backward compat
157
157
  var streaming_1 = require("./streaming");
158
158
  Object.defineProperty(exports, "streamChatCompletion", { enumerable: true, get: function () { return streaming_1.streamChatCompletion; } });
@@ -530,7 +530,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
530
530
  const activeTools = [
531
531
  ...filteredBaseTools,
532
532
  tools_1.ponderTool,
533
- ...(isInnerDialog ? [surface_tool_1.surfaceToolDef, tools_1.restTool] : []),
533
+ ...(isInnerDialog ? [tools_2.surfaceToolDef, tools_1.restTool] : []),
534
534
  ...((currentContext?.isGroupChat || options?.isReactionSignal) && !isInnerDialog ? [tools_1.observeTool] : []),
535
535
  ...(!isInnerDialog ? [tools_1.settleTool] : []),
536
536
  ];
@@ -924,8 +924,10 @@ async function runAgent(messages, callbacks, channel, signal, options) {
924
924
  providerRuntime.appendToolOutput(tc.id, rejection);
925
925
  continue;
926
926
  }
927
- // Confirmation check for mutate tools
928
- if ((0, tools_1.isConfirmationRequired)(tc.name) && !options?.skipConfirmation) {
927
+ // Confirmation check for mutate tools.
928
+ // confirmationAlwaysRequired tools cannot be bypassed by skipConfirmation.
929
+ const needsConfirmation = (0, tools_1.isConfirmationRequired)(tc.name) && (!options?.skipConfirmation || (0, tools_1.isConfirmationAlwaysRequired)(tc.name));
930
+ if (needsConfirmation) {
929
931
  let decision = "denied";
930
932
  if (callbacks.onConfirmAction) {
931
933
  decision = await callbacks.onConfirmAction(tc.name, args);
@@ -38,7 +38,6 @@ const fs = __importStar(require("fs"));
38
38
  const os = __importStar(require("os"));
39
39
  const path = __importStar(require("path"));
40
40
  const runtime_1 = require("../../nerves/runtime");
41
- const bluebubbles_runtime_state_1 = require("../../senses/bluebubbles-runtime-state");
42
41
  const identity_1 = require("../identity");
43
42
  const sense_truth_1 = require("../sense-truth");
44
43
  const process_manager_1 = require("./process-manager");
@@ -187,13 +186,35 @@ function blueBubblesRuntimeStateIsFresh(lastCheckedAt, now = Date.now()) {
187
186
  }
188
187
  return checkedAt >= now - BLUEBUBBLES_RUNTIME_FRESHNESS_WINDOW_MS;
189
188
  }
189
+ function readBlueBubblesRuntimeJson(runtimePath) {
190
+ try {
191
+ const raw = fs.readFileSync(runtimePath, "utf-8");
192
+ const parsed = JSON.parse(raw);
193
+ /* v8 ignore start -- branches: ternary fallbacks for missing/malformed BB runtime fields @preserve */
194
+ return {
195
+ upstreamStatus: parsed.upstreamStatus === "ok" || parsed.upstreamStatus === "error"
196
+ ? parsed.upstreamStatus
197
+ : "unknown",
198
+ detail: typeof parsed.detail === "string" && parsed.detail.trim()
199
+ ? parsed.detail
200
+ : "startup health probe pending",
201
+ lastCheckedAt: typeof parsed.lastCheckedAt === "string" ? parsed.lastCheckedAt : undefined,
202
+ };
203
+ /* v8 ignore stop */
204
+ /* v8 ignore start -- defensive: catch for missing/corrupt BB runtime state file @preserve */
205
+ }
206
+ catch {
207
+ return { upstreamStatus: "unknown", detail: "startup health probe pending" };
208
+ }
209
+ /* v8 ignore stop */
210
+ }
190
211
  function readBlueBubblesRuntimeFacts(agent, bundlesRoot, snapshot) {
191
212
  const agentRoot = path.join(bundlesRoot, `${agent}.ouro`);
192
213
  const runtimePath = path.join(agentRoot, "state", "senses", "bluebubbles", "runtime.json");
193
214
  if (!fs.existsSync(runtimePath)) {
194
215
  return { runtime: snapshot?.runtime };
195
216
  }
196
- const state = (0, bluebubbles_runtime_state_1.readBlueBubblesRuntimeState)(agent, agentRoot);
217
+ const state = readBlueBubblesRuntimeJson(runtimePath);
197
218
  if (!blueBubblesRuntimeStateIsFresh(state.lastCheckedAt)) {
198
219
  return { runtime: snapshot?.runtime };
199
220
  }
@@ -1,8 +1,44 @@
1
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
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.buildCapabilitiesSection = buildCapabilitiesSection;
3
37
  exports.buildStartOfTurnPacket = buildStartOfTurnPacket;
4
38
  exports.renderStartOfTurnPacket = renderStartOfTurnPacket;
5
39
  exports.renderCompactStartOfTurnPacket = renderCompactStartOfTurnPacket;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
6
42
  const runtime_1 = require("../nerves/runtime");
7
43
  const tempo_1 = require("./tempo");
8
44
  function estimateTokens(text) {
@@ -87,6 +123,64 @@ function buildResumeHint(view, obligations) {
87
123
  return "";
88
124
  return hints.join("; ");
89
125
  }
126
+ /**
127
+ * Build a compact summary of capability changes between runtime versions.
128
+ * Reads bundle-meta.json for version info and changelog.json for change descriptions.
129
+ * Returns undefined if no version change detected or files are missing/malformed.
130
+ */
131
+ function buildCapabilitiesSection(bundleRoot) {
132
+ let meta;
133
+ try {
134
+ const raw = fs.readFileSync(path.join(bundleRoot, "bundle-meta.json"), "utf-8");
135
+ meta = JSON.parse(raw);
136
+ }
137
+ catch {
138
+ (0, runtime_1.emitNervesEvent)({
139
+ component: "heart",
140
+ event: "heart.capabilities_check",
141
+ message: "bundle-meta.json missing or malformed",
142
+ meta: { bundleRoot },
143
+ });
144
+ return undefined;
145
+ }
146
+ const currentVersion = meta.runtimeVersion;
147
+ const previousVersion = meta.previousRuntimeVersion;
148
+ if (typeof currentVersion !== "string" || typeof previousVersion !== "string") {
149
+ return undefined;
150
+ }
151
+ if (currentVersion === previousVersion) {
152
+ return undefined;
153
+ }
154
+ (0, runtime_1.emitNervesEvent)({
155
+ component: "heart",
156
+ event: "heart.capabilities_version_change",
157
+ message: `runtime version changed from ${previousVersion} to ${currentVersion}`,
158
+ meta: { previousVersion, currentVersion },
159
+ });
160
+ let changelog;
161
+ try {
162
+ const raw = fs.readFileSync(path.join(bundleRoot, "changelog.json"), "utf-8");
163
+ changelog = JSON.parse(raw);
164
+ }
165
+ catch {
166
+ // changelog missing -- still report the version change
167
+ }
168
+ const parts = [`Updated from ${previousVersion} to ${currentVersion}`];
169
+ if (changelog && Array.isArray(changelog)) {
170
+ const relevantChanges = [];
171
+ for (const entry of changelog) {
172
+ if (entry.version === previousVersion)
173
+ break;
174
+ if (Array.isArray(entry.changes)) {
175
+ relevantChanges.push(...entry.changes);
176
+ }
177
+ }
178
+ if (relevantChanges.length > 0) {
179
+ parts.push(`: ${relevantChanges.join("; ")}`);
180
+ }
181
+ }
182
+ return parts.join("");
183
+ }
90
184
  function buildStartOfTurnPacket(view, opts) {
91
185
  const tempo = view.tempo;
92
186
  const tokenBudget = tempo_1.TEMPO_BUDGETS[tempo];
@@ -128,11 +222,12 @@ function renderStartOfTurnPacket(packet) {
128
222
  // Assemble sections in priority order (highest priority first for budget allocation)
129
223
  // Each section is { label, content, priority } where lower priority number = truncated first
130
224
  const sections = [
131
- { label: "syncFailure", content: packet.syncFailure ?? "", priority: 6 },
132
- { label: "resume", content: packet.resumeHint, priority: 5 },
133
- { label: "obligations", content: packet.obligations, priority: 4 },
134
- { label: "cares", content: packet.cares, priority: 3 },
135
- { label: "plot", content: packet.plotLine, priority: 2 },
225
+ { label: "syncFailure", content: packet.syncFailure ?? "", priority: 7 },
226
+ { label: "resume", content: packet.resumeHint, priority: 6 },
227
+ { label: "obligations", content: packet.obligations, priority: 5 },
228
+ { label: "cares", content: packet.cares, priority: 4 },
229
+ { label: "plot", content: packet.plotLine, priority: 3 },
230
+ { label: "capabilities", content: packet.capabilities ?? "", priority: 2 },
136
231
  { label: "presence", content: packet.presence, priority: 1 },
137
232
  ].filter((s) => s.content.length > 0);
138
233
  if (sections.length === 0) {
@@ -193,6 +288,9 @@ function formatSections(sections) {
193
288
  case "presence":
194
289
  parts.push(`**Peers:**\n${section.content}`);
195
290
  break;
291
+ case "capabilities":
292
+ parts.push(`**Capabilities:** ${section.content}`);
293
+ break;
196
294
  case "syncFailure":
197
295
  parts.push(`**Sync warning:** ${section.content}`);
198
296
  break;
@@ -47,6 +47,7 @@ const file_state_1 = require("../mind/file-state");
47
47
  const scrutiny_1 = require("../mind/scrutiny");
48
48
  const identity_1 = require("../heart/identity");
49
49
  const identity_2 = require("../heart/identity");
50
+ const config_registry_1 = require("../heart/config-registry");
50
51
  const socket_client_1 = require("../heart/daemon/socket-client");
51
52
  const thoughts_1 = require("../heart/daemon/thoughts");
52
53
  const manager_1 = require("../heart/bridges/manager");
@@ -1738,6 +1739,230 @@ exports.baseToolDefinitions = [
1738
1739
  return JSON.stringify(result, null, 2);
1739
1740
  },
1740
1741
  },
1742
+ // --- Config discovery tools ---
1743
+ {
1744
+ tool: {
1745
+ type: "function",
1746
+ function: {
1747
+ name: "read_config",
1748
+ description: "Read current agent configuration with tier annotations, descriptions, defaults, and effects. Optionally filter by topic to see only related settings.",
1749
+ parameters: {
1750
+ type: "object",
1751
+ properties: {
1752
+ related_to: {
1753
+ type: "string",
1754
+ description: "Optional topic to filter results (e.g., 'model', 'logging', 'senses'). Case-insensitive partial match.",
1755
+ },
1756
+ },
1757
+ },
1758
+ },
1759
+ },
1760
+ handler: (a) => {
1761
+ const entries = a.related_to
1762
+ ? (0, config_registry_1.getRegistryEntriesByTopic)(a.related_to)
1763
+ : (0, config_registry_1.getRegistryEntries)();
1764
+ const agentRoot = (0, identity_1.getAgentRoot)();
1765
+ const configPath = path.join(agentRoot, "agent.json");
1766
+ let rawConfig = {};
1767
+ try {
1768
+ rawConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
1769
+ }
1770
+ catch {
1771
+ /* v8 ignore next -- defensive: agent.json read failure in read_config @preserve */
1772
+ (0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.read_config_error", message: "failed to read agent.json", meta: { path: configPath } });
1773
+ }
1774
+ const result = entries.map((entry) => {
1775
+ const parts = entry.path.split(".");
1776
+ let current = rawConfig;
1777
+ for (const part of parts) {
1778
+ if (current && typeof current === "object" && !Array.isArray(current)) {
1779
+ current = current[part];
1780
+ }
1781
+ else {
1782
+ current = undefined;
1783
+ break;
1784
+ }
1785
+ }
1786
+ const isExplicit = current !== undefined;
1787
+ let source;
1788
+ let currentValue;
1789
+ if (isExplicit) {
1790
+ source = "explicit";
1791
+ currentValue = current;
1792
+ }
1793
+ else if (entry.default !== undefined) {
1794
+ source = "default";
1795
+ currentValue = entry.default;
1796
+ }
1797
+ else {
1798
+ source = "unset";
1799
+ currentValue = null;
1800
+ }
1801
+ return {
1802
+ path: entry.path,
1803
+ currentValue,
1804
+ source,
1805
+ tier: entry.tier,
1806
+ description: entry.description,
1807
+ default: entry.default !== undefined ? entry.default : null,
1808
+ effects: entry.effects,
1809
+ topics: entry.topics,
1810
+ };
1811
+ });
1812
+ (0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.read_config", message: `read_config returned ${result.length} entries`, meta: { count: result.length, topic: a.related_to ?? null } });
1813
+ return JSON.stringify({ entries: result }, null, 2);
1814
+ },
1815
+ },
1816
+ {
1817
+ tool: {
1818
+ type: "function",
1819
+ function: {
1820
+ name: "update_config",
1821
+ description: "Update a Tier 1 (self-service) agent configuration value immediately. For Tier 2 keys that require operator approval, use propose_config instead. Tier 3 (operator-only) keys are refused.",
1822
+ parameters: {
1823
+ type: "object",
1824
+ properties: {
1825
+ path: { type: "string", description: "Config key in dot-notation (e.g., 'context.contextMargin', 'logging.level')" },
1826
+ value: { type: "string", description: "New value as JSON (e.g., '25', '\"debug\"', '[\"terminal\", \"ndjson\"]')" },
1827
+ },
1828
+ required: ["path", "value"],
1829
+ },
1830
+ },
1831
+ },
1832
+ handler: (a) => {
1833
+ const entry = (0, config_registry_1.getRegistryEntry)(a.path);
1834
+ if (!entry) {
1835
+ (0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.update_config_error", message: `unknown config path: ${a.path}`, meta: { path: a.path } });
1836
+ return `Error: unknown config path "${a.path}". Use read_config to see available paths.`;
1837
+ }
1838
+ let parsedValue;
1839
+ try {
1840
+ parsedValue = JSON.parse(a.value);
1841
+ }
1842
+ catch {
1843
+ (0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.update_config_error", message: `invalid JSON value for ${a.path}`, meta: { path: a.path, value: a.value } });
1844
+ return `Error: invalid JSON value. Provide value as valid JSON (e.g., 25, "debug", ["terminal"]).`;
1845
+ }
1846
+ // Validate value against registry entry's validator
1847
+ /* v8 ignore next -- all current entries have validators; guard kept for future entries without one @preserve */
1848
+ if (entry.validate) {
1849
+ const validationError = entry.validate(parsedValue);
1850
+ if (validationError) {
1851
+ (0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.update_config_error", message: `validation failed for ${a.path}: ${validationError}`, meta: { path: a.path, value: a.value, validationError } });
1852
+ return `Error: validation failed for "${a.path}": ${validationError}`;
1853
+ }
1854
+ }
1855
+ // Tier 3: refuse
1856
+ if (entry.tier === 3) {
1857
+ (0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.update_config_refused", message: `refused T3 change to ${a.path}`, meta: { path: a.path, tier: 3 } });
1858
+ return `Refused: "${a.path}" is an operator-only (Tier 3) key. ${entry.description} Only the operator can change this value directly in agent.json.`;
1859
+ }
1860
+ // Tier 2: refuse — must use propose_config
1861
+ if (entry.tier === 2) {
1862
+ (0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.update_config_refused", message: `refused T2 change to ${a.path} — use propose_config`, meta: { path: a.path, tier: 2 } });
1863
+ return `Refused: "${a.path}" is a Tier 2 (proposal) key that requires operator approval. Use propose_config instead of update_config to propose this change.`;
1864
+ }
1865
+ const agentRoot = (0, identity_1.getAgentRoot)();
1866
+ const configPath = path.join(agentRoot, "agent.json");
1867
+ let rawConfig;
1868
+ try {
1869
+ rawConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
1870
+ }
1871
+ catch {
1872
+ /* v8 ignore next -- defensive: agent.json read failure in update_config @preserve */
1873
+ return `Error: failed to read agent.json at ${configPath}`;
1874
+ }
1875
+ // Apply T1 change immediately
1876
+ const parts = entry.path.split(".");
1877
+ let target = rawConfig;
1878
+ for (let i = 0; i < parts.length - 1; i++) {
1879
+ if (!(parts[i] in target) || typeof target[parts[i]] !== "object" || target[parts[i]] === null) {
1880
+ target[parts[i]] = {};
1881
+ }
1882
+ target = target[parts[i]];
1883
+ }
1884
+ target[parts[parts.length - 1]] = parsedValue;
1885
+ fs.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + "\n", "utf-8");
1886
+ (0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.update_config_applied", message: `applied config change to ${a.path}`, meta: { path: a.path, tier: entry.tier, value: parsedValue } });
1887
+ return `Success: "${a.path}" updated to ${JSON.stringify(parsedValue)}. Change applied immediately.`;
1888
+ },
1889
+ },
1890
+ {
1891
+ tool: {
1892
+ type: "function",
1893
+ function: {
1894
+ name: "propose_config",
1895
+ description: "Propose a change to a Tier 2 (proposal) agent configuration value. Requires operator approval before the change is applied. For Tier 1 self-service keys, use update_config instead.",
1896
+ parameters: {
1897
+ type: "object",
1898
+ properties: {
1899
+ path: { type: "string", description: "Config key in dot-notation (e.g., 'context.maxTokens', 'humanFacing.model')" },
1900
+ value: { type: "string", description: "New value as JSON (e.g., '120000', '\"azure\"', '{\"enabled\": true}')" },
1901
+ },
1902
+ required: ["path", "value"],
1903
+ },
1904
+ },
1905
+ },
1906
+ confirmationRequired: true,
1907
+ confirmationAlwaysRequired: true,
1908
+ handler: (a) => {
1909
+ const entry = (0, config_registry_1.getRegistryEntry)(a.path);
1910
+ if (!entry) {
1911
+ (0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.propose_config_error", message: `unknown config path: ${a.path}`, meta: { path: a.path } });
1912
+ return `Error: unknown config path "${a.path}". Use read_config to see available paths.`;
1913
+ }
1914
+ let parsedValue;
1915
+ try {
1916
+ parsedValue = JSON.parse(a.value);
1917
+ }
1918
+ catch {
1919
+ (0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.propose_config_error", message: `invalid JSON value for ${a.path}`, meta: { path: a.path, value: a.value } });
1920
+ return `Error: invalid JSON value. Provide value as valid JSON (e.g., 120000, "azure", {"enabled": true}).`;
1921
+ }
1922
+ // Validate value against registry entry's validator
1923
+ /* v8 ignore next -- all current entries have validators; guard kept for future entries without one @preserve */
1924
+ if (entry.validate) {
1925
+ const validationError = entry.validate(parsedValue);
1926
+ if (validationError) {
1927
+ (0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.propose_config_error", message: `validation failed for ${a.path}: ${validationError}`, meta: { path: a.path, value: a.value, validationError } });
1928
+ return `Error: validation failed for "${a.path}": ${validationError}`;
1929
+ }
1930
+ }
1931
+ // Tier 3: refuse
1932
+ if (entry.tier === 3) {
1933
+ (0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.propose_config_refused", message: `refused T3 change to ${a.path}`, meta: { path: a.path, tier: 3 } });
1934
+ return `Refused: "${a.path}" is an operator-only (Tier 3) key. ${entry.description} Only the operator can change this value directly in agent.json.`;
1935
+ }
1936
+ // Tier 1: refuse — use update_config for self-service keys
1937
+ if (entry.tier === 1) {
1938
+ (0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.propose_config_refused", message: `refused T1 change to ${a.path} — use update_config`, meta: { path: a.path, tier: 1 } });
1939
+ return `Refused: "${a.path}" is a Tier 1 (self-service) key. Use update_config instead — no operator approval needed.`;
1940
+ }
1941
+ const agentRoot = (0, identity_1.getAgentRoot)();
1942
+ const configPath = path.join(agentRoot, "agent.json");
1943
+ let rawConfig;
1944
+ try {
1945
+ rawConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
1946
+ }
1947
+ catch {
1948
+ /* v8 ignore next -- defensive: agent.json read failure in propose_config @preserve */
1949
+ return `Error: failed to read agent.json at ${configPath}`;
1950
+ }
1951
+ // Apply T2 change (operator confirmation already obtained via confirmationAlwaysRequired)
1952
+ const parts = entry.path.split(".");
1953
+ let target = rawConfig;
1954
+ for (let i = 0; i < parts.length - 1; i++) {
1955
+ if (!(parts[i] in target) || typeof target[parts[i]] !== "object" || target[parts[i]] === null) {
1956
+ target[parts[i]] = {};
1957
+ }
1958
+ target = target[parts[i]];
1959
+ }
1960
+ target[parts[parts.length - 1]] = parsedValue;
1961
+ fs.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + "\n", "utf-8");
1962
+ (0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.propose_config_applied", message: `applied approved config change to ${a.path}`, meta: { path: a.path, tier: entry.tier, value: parsedValue } });
1963
+ return `Success: "${a.path}" updated to ${JSON.stringify(parsedValue)}. Change approved and applied.`;
1964
+ },
1965
+ },
1741
1966
  ...tools_1.codingToolDefinitions,
1742
1967
  ];
1743
1968
  exports.tools = exports.baseToolDefinitions.map((d) => d.tool);
@@ -33,9 +33,10 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.restTool = exports.ponderTool = exports.observeTool = exports.settleTool = exports.tools = void 0;
36
+ exports.surfaceToolDef = exports.restTool = exports.ponderTool = exports.observeTool = exports.settleTool = exports.tools = void 0;
37
37
  exports.getToolsForChannel = getToolsForChannel;
38
38
  exports.isConfirmationRequired = isConfirmationRequired;
39
+ exports.isConfirmationAlwaysRequired = isConfirmationAlwaysRequired;
39
40
  exports.execTool = execTool;
40
41
  exports.summarizeArgs = summarizeArgs;
41
42
  exports.buildToolResultSummary = buildToolResultSummary;
@@ -66,10 +67,36 @@ Object.defineProperty(exports, "settleTool", { enumerable: true, get: function (
66
67
  Object.defineProperty(exports, "observeTool", { enumerable: true, get: function () { return tools_base_2.observeTool; } });
67
68
  Object.defineProperty(exports, "ponderTool", { enumerable: true, get: function () { return tools_base_2.ponderTool; } });
68
69
  Object.defineProperty(exports, "restTool", { enumerable: true, get: function () { return tools_base_2.restTool; } });
70
+ // Surface tool schema — canonical home. Handler lives in senses/surface-tool.ts.
71
+ exports.surfaceToolDef = {
72
+ type: "function",
73
+ function: {
74
+ name: "surface",
75
+ description: "share a thought outward — deliver an answer, ask a follow-up, or surface progress to whoever needs to hear it. pass delegationId to address a held thought (see your attention queue above), or friendId for spontaneous outreach. does not end your turn.",
76
+ parameters: {
77
+ type: "object",
78
+ properties: {
79
+ content: {
80
+ type: "string",
81
+ description: "the message to deliver",
82
+ },
83
+ delegationId: {
84
+ type: "string",
85
+ description: "ID from your attention queue — addresses a specific held thought",
86
+ },
87
+ friendId: {
88
+ type: "string",
89
+ description: "friend to reach out to spontaneously (when not addressing a held thought)",
90
+ },
91
+ },
92
+ required: ["content"],
93
+ },
94
+ },
95
+ };
69
96
  // Surface tool handler: routes content to friend's freshest session
70
97
  /* v8 ignore start -- surface handler wiring: core logic tested via surface-tool.test.ts; this wires identity/routing deps @preserve */
71
98
  const surfaceToolDefinition = {
72
- tool: surface_tool_1.surfaceToolDef,
99
+ tool: exports.surfaceToolDef,
73
100
  handler: async (args, ctx) => {
74
101
  const queue = ctx?.delegatedOrigins ?? [];
75
102
  const agentName = (() => { try {
@@ -299,6 +326,12 @@ function isConfirmationRequired(toolName) {
299
326
  const def = allDefinitions.find((d) => d.tool.function.name === toolName);
300
327
  return def?.confirmationRequired === true;
301
328
  }
329
+ // Check whether a tool's confirmation is non-bypassable (cannot be skipped by skipConfirmation).
330
+ // Used for T2 config proposals that must always require operator approval.
331
+ function isConfirmationAlwaysRequired(toolName) {
332
+ const def = allDefinitions.find((d) => d.tool.function.name === toolName);
333
+ return def?.confirmationAlwaysRequired === true;
334
+ }
302
335
  function normalizeGuardArgs(_name, args) {
303
336
  return args;
304
337
  }
@@ -390,6 +390,10 @@ async function handleInboundTurn(input) {
390
390
  if (syncFailure) {
391
391
  startOfTurnPacket.syncFailure = syncFailure;
392
392
  }
393
+ const capabilities = (0, start_of_turn_packet_1.buildCapabilitiesSection)(agentRoot);
394
+ if (capabilities) {
395
+ startOfTurnPacket.capabilities = capabilities;
396
+ }
393
397
  renderedStartOfTurnPacket = (0, start_of_turn_packet_1.renderStartOfTurnPacket)(startOfTurnPacket);
394
398
  if (!renderedStartOfTurnPacket)
395
399
  renderedStartOfTurnPacket = undefined;
@@ -1,35 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.surfaceToolDef = void 0;
4
3
  exports.handleSurface = handleSurface;
5
4
  const attention_queue_1 = require("./attention-queue");
6
5
  const runtime_1 = require("../nerves/runtime");
7
- // ── Tool definition ──────────────────────────────────────────────
8
- exports.surfaceToolDef = {
9
- type: "function",
10
- function: {
11
- name: "surface",
12
- description: "share a thought outward — deliver an answer, ask a follow-up, or surface progress to whoever needs to hear it. pass delegationId to address a held thought (see your attention queue above), or friendId for spontaneous outreach. does not end your turn.",
13
- parameters: {
14
- type: "object",
15
- properties: {
16
- content: {
17
- type: "string",
18
- description: "the message to deliver",
19
- },
20
- delegationId: {
21
- type: "string",
22
- description: "ID from your attention queue — addresses a specific held thought",
23
- },
24
- friendId: {
25
- type: "string",
26
- description: "friend to reach out to spontaneously (when not addressing a held thought)",
27
- },
28
- },
29
- required: ["content"],
30
- },
31
- },
32
- };
33
6
  async function handleSurface(input) {
34
7
  const { content, delegationId, friendId, queue, routeToFriend, advanceObligation, fulfillHeartObligation } = input;
35
8
  // Resolve target friend
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.204",
3
+ "version": "0.1.0-alpha.206",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",