@ouro.bot/cli 0.1.0-alpha.205 → 0.1.0-alpha.207
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/config-registry.js +305 -0
- package/dist/heart/core.js +4 -2
- package/dist/heart/start-of-turn-packet.js +103 -5
- package/dist/nerves/coverage/file-completeness.js +38 -5
- package/dist/repertoire/tools-base.js +28 -1792
- package/dist/repertoire/tools-bridge.js +141 -0
- package/dist/repertoire/tools-config.js +266 -0
- package/dist/repertoire/tools-continuity.js +248 -0
- package/dist/repertoire/tools-files.js +342 -0
- package/dist/repertoire/tools-flow.js +62 -0
- package/dist/repertoire/tools-memory.js +347 -0
- package/dist/repertoire/tools-session.js +682 -0
- package/dist/repertoire/tools-shell.js +120 -0
- package/dist/repertoire/tools-surface.js +228 -0
- package/dist/repertoire/tools.js +14 -225
- package/dist/senses/pipeline.js +4 -0
- package/package.json +1 -1
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.bridgeToolDefinitions = void 0;
|
|
4
|
+
const config_1 = require("../heart/config");
|
|
5
|
+
const manager_1 = require("../heart/bridges/manager");
|
|
6
|
+
const runtime_1 = require("../nerves/runtime");
|
|
7
|
+
const session_recall_1 = require("../heart/session-recall");
|
|
8
|
+
const NO_SESSION_FOUND_MESSAGE = "no session found for that friend/channel/key combination.";
|
|
9
|
+
const EMPTY_SESSION_MESSAGE = "session exists but has no non-system messages.";
|
|
10
|
+
async function recallSessionSafely(options) {
|
|
11
|
+
try {
|
|
12
|
+
return await (0, session_recall_1.recallSession)(options);
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
if (options.summarize) {
|
|
16
|
+
(0, runtime_1.emitNervesEvent)({
|
|
17
|
+
component: "daemon",
|
|
18
|
+
event: "daemon.session_recall_summary_fallback",
|
|
19
|
+
message: "session recall summarization failed; using raw transcript",
|
|
20
|
+
meta: {
|
|
21
|
+
friendId: options.friendId,
|
|
22
|
+
channel: options.channel,
|
|
23
|
+
key: options.key,
|
|
24
|
+
/* v8 ignore next -- branch: String(error) fallback only for non-Error throws @preserve */
|
|
25
|
+
error: error instanceof Error ? error.message : String(error),
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
try {
|
|
29
|
+
return await (0, session_recall_1.recallSession)({
|
|
30
|
+
...options,
|
|
31
|
+
summarize: undefined,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return { kind: "missing" };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return { kind: "missing" };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.bridgeToolDefinitions = [
|
|
42
|
+
// -- cross-session awareness --
|
|
43
|
+
{
|
|
44
|
+
tool: {
|
|
45
|
+
type: "function",
|
|
46
|
+
function: {
|
|
47
|
+
name: "bridge_manage",
|
|
48
|
+
description: "create and manage shared live-work bridges across already-active sessions.",
|
|
49
|
+
parameters: {
|
|
50
|
+
type: "object",
|
|
51
|
+
properties: {
|
|
52
|
+
action: {
|
|
53
|
+
type: "string",
|
|
54
|
+
enum: ["begin", "attach", "status", "promote_task", "complete", "cancel"],
|
|
55
|
+
},
|
|
56
|
+
bridgeId: { type: "string", description: "bridge id for all actions except begin" },
|
|
57
|
+
objective: { type: "string", description: "objective for begin" },
|
|
58
|
+
summary: { type: "string", description: "optional concise shared-work summary" },
|
|
59
|
+
friendId: { type: "string", description: "target friend id for attach" },
|
|
60
|
+
channel: { type: "string", description: "target channel for attach" },
|
|
61
|
+
key: { type: "string", description: "target session key for attach (defaults to 'session')" },
|
|
62
|
+
title: { type: "string", description: "task title override for promote_task" },
|
|
63
|
+
category: { type: "string", description: "task category override for promote_task" },
|
|
64
|
+
body: { type: "string", description: "task body override for promote_task" },
|
|
65
|
+
},
|
|
66
|
+
required: ["action"],
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
handler: async (args, ctx) => {
|
|
71
|
+
const manager = (0, manager_1.createBridgeManager)();
|
|
72
|
+
const action = (args.action || "").trim();
|
|
73
|
+
if (action === "begin") {
|
|
74
|
+
if (!ctx?.currentSession) {
|
|
75
|
+
return "bridge_manage begin requires an active session context.";
|
|
76
|
+
}
|
|
77
|
+
const objective = (args.objective || "").trim();
|
|
78
|
+
if (!objective)
|
|
79
|
+
return "objective is required for bridge begin.";
|
|
80
|
+
return (0, manager_1.formatBridgeStatus)(manager.beginBridge({
|
|
81
|
+
objective,
|
|
82
|
+
summary: (args.summary || objective).trim(),
|
|
83
|
+
session: ctx.currentSession,
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
const bridgeId = (args.bridgeId || "").trim();
|
|
87
|
+
if (!bridgeId) {
|
|
88
|
+
return "bridgeId is required for this bridge action.";
|
|
89
|
+
}
|
|
90
|
+
if (action === "attach") {
|
|
91
|
+
const friendId = (args.friendId || "").trim();
|
|
92
|
+
const channel = (args.channel || "").trim();
|
|
93
|
+
const key = (args.key || "session").trim();
|
|
94
|
+
if (!friendId || !channel) {
|
|
95
|
+
return "friendId and channel are required for bridge attach.";
|
|
96
|
+
}
|
|
97
|
+
const sessionPath = (0, config_1.resolveSessionPath)(friendId, channel, key);
|
|
98
|
+
const recall = await recallSessionSafely({
|
|
99
|
+
sessionPath,
|
|
100
|
+
friendId,
|
|
101
|
+
channel,
|
|
102
|
+
key,
|
|
103
|
+
messageCount: 20,
|
|
104
|
+
trustLevel: ctx?.context?.friend?.trustLevel,
|
|
105
|
+
summarize: ctx?.summarize,
|
|
106
|
+
});
|
|
107
|
+
if (recall.kind === "missing") {
|
|
108
|
+
return NO_SESSION_FOUND_MESSAGE;
|
|
109
|
+
}
|
|
110
|
+
return (0, manager_1.formatBridgeStatus)(manager.attachSession(bridgeId, {
|
|
111
|
+
friendId,
|
|
112
|
+
channel,
|
|
113
|
+
key,
|
|
114
|
+
sessionPath,
|
|
115
|
+
snapshot: recall.kind === "ok" ? recall.snapshot : EMPTY_SESSION_MESSAGE,
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
if (action === "status") {
|
|
119
|
+
const bridge = manager.getBridge(bridgeId);
|
|
120
|
+
if (!bridge)
|
|
121
|
+
return `bridge not found: ${bridgeId}`;
|
|
122
|
+
return (0, manager_1.formatBridgeStatus)(bridge);
|
|
123
|
+
}
|
|
124
|
+
if (action === "promote_task") {
|
|
125
|
+
return (0, manager_1.formatBridgeStatus)(manager.promoteBridgeToTask(bridgeId, {
|
|
126
|
+
title: args.title,
|
|
127
|
+
category: args.category,
|
|
128
|
+
body: args.body,
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
if (action === "complete") {
|
|
132
|
+
return (0, manager_1.formatBridgeStatus)(manager.completeBridge(bridgeId));
|
|
133
|
+
}
|
|
134
|
+
if (action === "cancel") {
|
|
135
|
+
return (0, manager_1.formatBridgeStatus)(manager.cancelBridge(bridgeId));
|
|
136
|
+
}
|
|
137
|
+
return `unknown bridge action: ${action}`;
|
|
138
|
+
},
|
|
139
|
+
summaryKeys: ["action", "bridgeId", "objective", "friendId", "channel", "key"],
|
|
140
|
+
},
|
|
141
|
+
];
|
|
@@ -0,0 +1,266 @@
|
|
|
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
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.configToolDefinitions = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const runtime_1 = require("../nerves/runtime");
|
|
40
|
+
const identity_1 = require("../heart/identity");
|
|
41
|
+
const config_registry_1 = require("../heart/config-registry");
|
|
42
|
+
exports.configToolDefinitions = [
|
|
43
|
+
{
|
|
44
|
+
tool: {
|
|
45
|
+
type: "function",
|
|
46
|
+
function: {
|
|
47
|
+
name: "read_config",
|
|
48
|
+
description: "Read current agent configuration with tier annotations, descriptions, defaults, and effects. Optionally filter by topic to see only related settings.",
|
|
49
|
+
parameters: {
|
|
50
|
+
type: "object",
|
|
51
|
+
properties: {
|
|
52
|
+
related_to: {
|
|
53
|
+
type: "string",
|
|
54
|
+
description: "Optional topic to filter results (e.g., 'model', 'logging', 'senses'). Case-insensitive partial match.",
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
handler: (a) => {
|
|
61
|
+
const entries = a.related_to
|
|
62
|
+
? (0, config_registry_1.getRegistryEntriesByTopic)(a.related_to)
|
|
63
|
+
: (0, config_registry_1.getRegistryEntries)();
|
|
64
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
65
|
+
const configPath = path.join(agentRoot, "agent.json");
|
|
66
|
+
let rawConfig = {};
|
|
67
|
+
try {
|
|
68
|
+
rawConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
/* v8 ignore next -- defensive: agent.json read failure in read_config @preserve */
|
|
72
|
+
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.read_config_error", message: "failed to read agent.json", meta: { path: configPath } });
|
|
73
|
+
}
|
|
74
|
+
const result = entries.map((entry) => {
|
|
75
|
+
const parts = entry.path.split(".");
|
|
76
|
+
let current = rawConfig;
|
|
77
|
+
for (const part of parts) {
|
|
78
|
+
if (current && typeof current === "object" && !Array.isArray(current)) {
|
|
79
|
+
current = current[part];
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
current = undefined;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const isExplicit = current !== undefined;
|
|
87
|
+
let source;
|
|
88
|
+
let currentValue;
|
|
89
|
+
if (isExplicit) {
|
|
90
|
+
source = "explicit";
|
|
91
|
+
currentValue = current;
|
|
92
|
+
}
|
|
93
|
+
else if (entry.default !== undefined) {
|
|
94
|
+
source = "default";
|
|
95
|
+
currentValue = entry.default;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
source = "unset";
|
|
99
|
+
currentValue = null;
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
path: entry.path,
|
|
103
|
+
currentValue,
|
|
104
|
+
source,
|
|
105
|
+
tier: entry.tier,
|
|
106
|
+
description: entry.description,
|
|
107
|
+
default: entry.default !== undefined ? entry.default : null,
|
|
108
|
+
effects: entry.effects,
|
|
109
|
+
topics: entry.topics,
|
|
110
|
+
};
|
|
111
|
+
});
|
|
112
|
+
(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 } });
|
|
113
|
+
return JSON.stringify({ entries: result }, null, 2);
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
tool: {
|
|
118
|
+
type: "function",
|
|
119
|
+
function: {
|
|
120
|
+
name: "update_config",
|
|
121
|
+
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.",
|
|
122
|
+
parameters: {
|
|
123
|
+
type: "object",
|
|
124
|
+
properties: {
|
|
125
|
+
path: { type: "string", description: "Config key in dot-notation (e.g., 'context.contextMargin', 'logging.level')" },
|
|
126
|
+
value: { type: "string", description: "New value as JSON (e.g., '25', '\"debug\"', '[\"terminal\", \"ndjson\"]')" },
|
|
127
|
+
},
|
|
128
|
+
required: ["path", "value"],
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
handler: (a) => {
|
|
133
|
+
const entry = (0, config_registry_1.getRegistryEntry)(a.path);
|
|
134
|
+
if (!entry) {
|
|
135
|
+
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.update_config_error", message: `unknown config path: ${a.path}`, meta: { path: a.path } });
|
|
136
|
+
return `Error: unknown config path "${a.path}". Use read_config to see available paths.`;
|
|
137
|
+
}
|
|
138
|
+
let parsedValue;
|
|
139
|
+
try {
|
|
140
|
+
parsedValue = JSON.parse(a.value);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
(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 } });
|
|
144
|
+
return `Error: invalid JSON value. Provide value as valid JSON (e.g., 25, "debug", ["terminal"]).`;
|
|
145
|
+
}
|
|
146
|
+
// Validate value against registry entry's validator
|
|
147
|
+
/* v8 ignore next -- all current entries have validators; guard kept for future entries without one @preserve */
|
|
148
|
+
if (entry.validate) {
|
|
149
|
+
const validationError = entry.validate(parsedValue);
|
|
150
|
+
if (validationError) {
|
|
151
|
+
(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 } });
|
|
152
|
+
return `Error: validation failed for "${a.path}": ${validationError}`;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Tier 3: refuse
|
|
156
|
+
if (entry.tier === 3) {
|
|
157
|
+
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.update_config_refused", message: `refused T3 change to ${a.path}`, meta: { path: a.path, tier: 3 } });
|
|
158
|
+
return `Refused: "${a.path}" is an operator-only (Tier 3) key. ${entry.description} Only the operator can change this value directly in agent.json.`;
|
|
159
|
+
}
|
|
160
|
+
// Tier 2: refuse — must use propose_config
|
|
161
|
+
if (entry.tier === 2) {
|
|
162
|
+
(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 } });
|
|
163
|
+
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.`;
|
|
164
|
+
}
|
|
165
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
166
|
+
const configPath = path.join(agentRoot, "agent.json");
|
|
167
|
+
let rawConfig;
|
|
168
|
+
try {
|
|
169
|
+
rawConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
/* v8 ignore next -- defensive: agent.json read failure in update_config @preserve */
|
|
173
|
+
return `Error: failed to read agent.json at ${configPath}`;
|
|
174
|
+
}
|
|
175
|
+
// Apply T1 change immediately
|
|
176
|
+
const parts = entry.path.split(".");
|
|
177
|
+
let target = rawConfig;
|
|
178
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
179
|
+
if (!(parts[i] in target) || typeof target[parts[i]] !== "object" || target[parts[i]] === null) {
|
|
180
|
+
target[parts[i]] = {};
|
|
181
|
+
}
|
|
182
|
+
target = target[parts[i]];
|
|
183
|
+
}
|
|
184
|
+
target[parts[parts.length - 1]] = parsedValue;
|
|
185
|
+
fs.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + "\n", "utf-8");
|
|
186
|
+
(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 } });
|
|
187
|
+
return `Success: "${a.path}" updated to ${JSON.stringify(parsedValue)}. Change applied immediately.`;
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
tool: {
|
|
192
|
+
type: "function",
|
|
193
|
+
function: {
|
|
194
|
+
name: "propose_config",
|
|
195
|
+
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.",
|
|
196
|
+
parameters: {
|
|
197
|
+
type: "object",
|
|
198
|
+
properties: {
|
|
199
|
+
path: { type: "string", description: "Config key in dot-notation (e.g., 'context.maxTokens', 'humanFacing.model')" },
|
|
200
|
+
value: { type: "string", description: "New value as JSON (e.g., '120000', '\"azure\"', '{\"enabled\": true}')" },
|
|
201
|
+
},
|
|
202
|
+
required: ["path", "value"],
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
confirmationRequired: true,
|
|
207
|
+
confirmationAlwaysRequired: true,
|
|
208
|
+
handler: (a) => {
|
|
209
|
+
const entry = (0, config_registry_1.getRegistryEntry)(a.path);
|
|
210
|
+
if (!entry) {
|
|
211
|
+
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.propose_config_error", message: `unknown config path: ${a.path}`, meta: { path: a.path } });
|
|
212
|
+
return `Error: unknown config path "${a.path}". Use read_config to see available paths.`;
|
|
213
|
+
}
|
|
214
|
+
let parsedValue;
|
|
215
|
+
try {
|
|
216
|
+
parsedValue = JSON.parse(a.value);
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
(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 } });
|
|
220
|
+
return `Error: invalid JSON value. Provide value as valid JSON (e.g., 120000, "azure", {"enabled": true}).`;
|
|
221
|
+
}
|
|
222
|
+
// Validate value against registry entry's validator
|
|
223
|
+
/* v8 ignore next -- all current entries have validators; guard kept for future entries without one @preserve */
|
|
224
|
+
if (entry.validate) {
|
|
225
|
+
const validationError = entry.validate(parsedValue);
|
|
226
|
+
if (validationError) {
|
|
227
|
+
(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 } });
|
|
228
|
+
return `Error: validation failed for "${a.path}": ${validationError}`;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// Tier 3: refuse
|
|
232
|
+
if (entry.tier === 3) {
|
|
233
|
+
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.propose_config_refused", message: `refused T3 change to ${a.path}`, meta: { path: a.path, tier: 3 } });
|
|
234
|
+
return `Refused: "${a.path}" is an operator-only (Tier 3) key. ${entry.description} Only the operator can change this value directly in agent.json.`;
|
|
235
|
+
}
|
|
236
|
+
// Tier 1: refuse — use update_config for self-service keys
|
|
237
|
+
if (entry.tier === 1) {
|
|
238
|
+
(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 } });
|
|
239
|
+
return `Refused: "${a.path}" is a Tier 1 (self-service) key. Use update_config instead — no operator approval needed.`;
|
|
240
|
+
}
|
|
241
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
242
|
+
const configPath = path.join(agentRoot, "agent.json");
|
|
243
|
+
let rawConfig;
|
|
244
|
+
try {
|
|
245
|
+
rawConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
/* v8 ignore next -- defensive: agent.json read failure in propose_config @preserve */
|
|
249
|
+
return `Error: failed to read agent.json at ${configPath}`;
|
|
250
|
+
}
|
|
251
|
+
// Apply T2 change (operator confirmation already obtained via confirmationAlwaysRequired)
|
|
252
|
+
const parts = entry.path.split(".");
|
|
253
|
+
let target = rawConfig;
|
|
254
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
255
|
+
if (!(parts[i] in target) || typeof target[parts[i]] !== "object" || target[parts[i]] === null) {
|
|
256
|
+
target[parts[i]] = {};
|
|
257
|
+
}
|
|
258
|
+
target = target[parts[i]];
|
|
259
|
+
}
|
|
260
|
+
target[parts[parts.length - 1]] = parsedValue;
|
|
261
|
+
fs.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + "\n", "utf-8");
|
|
262
|
+
(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 } });
|
|
263
|
+
return `Success: "${a.path}" updated to ${JSON.stringify(parsedValue)}. Change approved and applied.`;
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
];
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.continuityToolDefinitions = void 0;
|
|
4
|
+
const identity_1 = require("../heart/identity");
|
|
5
|
+
const runtime_1 = require("../nerves/runtime");
|
|
6
|
+
const episodes_1 = require("../mind/episodes");
|
|
7
|
+
const cares_1 = require("../heart/cares");
|
|
8
|
+
const presence_1 = require("../heart/presence");
|
|
9
|
+
const intentions_1 = require("../heart/intentions");
|
|
10
|
+
exports.continuityToolDefinitions = [
|
|
11
|
+
// ── Continuity tools ──────────────────────────────────────────────
|
|
12
|
+
{
|
|
13
|
+
tool: {
|
|
14
|
+
type: "function",
|
|
15
|
+
function: {
|
|
16
|
+
name: "query_episodes",
|
|
17
|
+
description: "Query recent episodes from my continuity memory. Returns timestamped records of significant events (obligation shifts, coding milestones, bridge events, care events, turning points).",
|
|
18
|
+
parameters: {
|
|
19
|
+
type: "object",
|
|
20
|
+
properties: {
|
|
21
|
+
limit: { type: "number", description: "Maximum episodes to return (default 20)" },
|
|
22
|
+
kind: { type: "string", description: "Filter by episode kind: obligation_shift, coding_milestone, bridge_event, care_event, tempo_shift, turning_point" },
|
|
23
|
+
since: { type: "string", description: "ISO timestamp — only return episodes after this time" },
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
handler: (a) => {
|
|
29
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
30
|
+
const options = {};
|
|
31
|
+
if (a.limit)
|
|
32
|
+
options.limit = parseInt(a.limit, 10);
|
|
33
|
+
if (a.kind)
|
|
34
|
+
options.kinds = [a.kind];
|
|
35
|
+
if (a.since)
|
|
36
|
+
options.since = a.since;
|
|
37
|
+
const episodes = (0, episodes_1.readRecentEpisodes)(agentRoot, options);
|
|
38
|
+
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.query_episodes", message: `queried ${episodes.length} episodes`, meta: { count: episodes.length } });
|
|
39
|
+
return JSON.stringify(episodes, null, 2);
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
tool: {
|
|
44
|
+
type: "function",
|
|
45
|
+
function: {
|
|
46
|
+
name: "capture_episode",
|
|
47
|
+
description: "Record a turning point or significant moment. This is my tool for saying 'that was important — keep it.' Nearly frictionless: only summary and whyItMattered required.",
|
|
48
|
+
parameters: {
|
|
49
|
+
type: "object",
|
|
50
|
+
properties: {
|
|
51
|
+
summary: { type: "string", description: "What happened" },
|
|
52
|
+
whyItMattered: { type: "string", description: "Why this was significant" },
|
|
53
|
+
kind: { type: "string", description: "Episode kind (default: turning_point)" },
|
|
54
|
+
salience: { type: "string", description: "low, medium, high, or critical (default: medium)" },
|
|
55
|
+
},
|
|
56
|
+
required: ["summary", "whyItMattered"],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
handler: (a) => {
|
|
61
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
62
|
+
const episode = (0, episodes_1.emitEpisode)(agentRoot, {
|
|
63
|
+
kind: a.kind ?? "turning_point",
|
|
64
|
+
summary: a.summary,
|
|
65
|
+
whyItMattered: a.whyItMattered,
|
|
66
|
+
relatedEntities: [],
|
|
67
|
+
salience: a.salience ?? "medium",
|
|
68
|
+
});
|
|
69
|
+
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.capture_episode", message: `captured episode ${episode.id}`, meta: { id: episode.id } });
|
|
70
|
+
return JSON.stringify(episode, null, 2);
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
tool: {
|
|
75
|
+
type: "function",
|
|
76
|
+
function: {
|
|
77
|
+
name: "query_presence",
|
|
78
|
+
description: "Check who's around — my own availability/lane and known peer agents.",
|
|
79
|
+
parameters: { type: "object", properties: {} },
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
handler: () => {
|
|
83
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
84
|
+
const agentName = (0, identity_1.getAgentName)();
|
|
85
|
+
const self = (0, presence_1.readPresence)(agentRoot, agentName);
|
|
86
|
+
const peers = (0, presence_1.readPeerPresence)(agentRoot);
|
|
87
|
+
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.query_presence", message: `presence: self + ${peers.length} peers`, meta: { peerCount: peers.length } });
|
|
88
|
+
return JSON.stringify({ self, peers }, null, 2);
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
tool: {
|
|
93
|
+
type: "function",
|
|
94
|
+
function: {
|
|
95
|
+
name: "query_cares",
|
|
96
|
+
description: "Query things I care about — ongoing concerns, watched situations, projects, people.",
|
|
97
|
+
parameters: {
|
|
98
|
+
type: "object",
|
|
99
|
+
properties: {
|
|
100
|
+
status: { type: "string", description: "Filter by status: 'active', 'watching', 'resolved', 'dormant', or 'all' (default: active cares only)" },
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
handler: (a) => {
|
|
106
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
107
|
+
const cares = a.status === "all" ? (0, cares_1.readCares)(agentRoot) : (0, cares_1.readActiveCares)(agentRoot);
|
|
108
|
+
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.query_cares", message: `queried ${cares.length} cares`, meta: { count: cares.length } });
|
|
109
|
+
return JSON.stringify(cares, null, 2);
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
tool: {
|
|
114
|
+
type: "function",
|
|
115
|
+
function: {
|
|
116
|
+
name: "care_manage",
|
|
117
|
+
description: "Create, update, or resolve a care. Cares are things I watch over — people, projects, missions, system health.",
|
|
118
|
+
parameters: {
|
|
119
|
+
type: "object",
|
|
120
|
+
properties: {
|
|
121
|
+
action: { type: "string", enum: ["create", "update", "resolve"], description: "What to do" },
|
|
122
|
+
id: { type: "string", description: "Care ID (required for update/resolve)" },
|
|
123
|
+
label: { type: "string", description: "Short label for the care" },
|
|
124
|
+
why: { type: "string", description: "Why this matters" },
|
|
125
|
+
salience: { type: "string", description: "low, medium, high, or critical" },
|
|
126
|
+
kind: { type: "string", description: "person, agent, project, mission, or system" },
|
|
127
|
+
stewardship: { type: "string", description: "mine, shared, or delegated" },
|
|
128
|
+
},
|
|
129
|
+
required: ["action"],
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
handler: (a) => {
|
|
134
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
135
|
+
let result;
|
|
136
|
+
if (a.action === "create") {
|
|
137
|
+
result = (0, cares_1.createCare)(agentRoot, {
|
|
138
|
+
label: a.label ?? "untitled",
|
|
139
|
+
why: a.why ?? "",
|
|
140
|
+
kind: a.kind ?? "project",
|
|
141
|
+
status: "active",
|
|
142
|
+
salience: a.salience ?? "medium",
|
|
143
|
+
steward: a.stewardship ?? "mine",
|
|
144
|
+
relatedFriendIds: [],
|
|
145
|
+
relatedAgentIds: [],
|
|
146
|
+
relatedObligationIds: [],
|
|
147
|
+
relatedEpisodeIds: [],
|
|
148
|
+
currentRisk: null,
|
|
149
|
+
nextCheckAt: null,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
else if (a.action === "update") {
|
|
153
|
+
const updates = {};
|
|
154
|
+
if (a.label)
|
|
155
|
+
updates.label = a.label;
|
|
156
|
+
if (a.why)
|
|
157
|
+
updates.why = a.why;
|
|
158
|
+
if (a.salience)
|
|
159
|
+
updates.salience = a.salience;
|
|
160
|
+
result = (0, cares_1.updateCare)(agentRoot, a.id, updates);
|
|
161
|
+
}
|
|
162
|
+
else if (a.action === "resolve") {
|
|
163
|
+
result = (0, cares_1.resolveCare)(agentRoot, a.id);
|
|
164
|
+
}
|
|
165
|
+
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.care_manage", message: `care ${a.action}`, meta: { action: a.action, id: a.id } });
|
|
166
|
+
return JSON.stringify(result, null, 2);
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
tool: {
|
|
171
|
+
type: "function",
|
|
172
|
+
function: {
|
|
173
|
+
name: "query_relationships",
|
|
174
|
+
description: "Query known agent relationships — familiarity, trust, shared missions, interaction history.",
|
|
175
|
+
parameters: {
|
|
176
|
+
type: "object",
|
|
177
|
+
properties: {
|
|
178
|
+
agentName: { type: "string", description: "Specific agent name to query (omit for all)" },
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
handler: async (a, ctx) => {
|
|
184
|
+
const allFriends = ctx?.friendStore?.listAll ? await ctx.friendStore.listAll() : [];
|
|
185
|
+
let agents = allFriends.filter((f) => f.kind === "agent");
|
|
186
|
+
if (a.agentName) {
|
|
187
|
+
const needle = a.agentName.toLowerCase();
|
|
188
|
+
agents = agents.filter((f) => f.name?.toLowerCase() === needle);
|
|
189
|
+
}
|
|
190
|
+
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.query_relationships", message: `queried relationships`, meta: { agentName: a.agentName ?? "all" } });
|
|
191
|
+
return JSON.stringify(agents, null, 2);
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
tool: {
|
|
196
|
+
type: "function",
|
|
197
|
+
function: {
|
|
198
|
+
name: "intention_capture",
|
|
199
|
+
description: "File a lightweight mental note — something I want to do or check later, below the ceremony threshold of tasks or cares. Cheap to create, easy to close.",
|
|
200
|
+
parameters: {
|
|
201
|
+
type: "object",
|
|
202
|
+
properties: {
|
|
203
|
+
content: { type: "string", description: "What I want to remember to do" },
|
|
204
|
+
salience: { type: "string", description: "low, medium, or high (default: low)" },
|
|
205
|
+
nudgeAfter: { type: "string", description: "ISO timestamp — nudge me after this time" },
|
|
206
|
+
},
|
|
207
|
+
required: ["content"],
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
handler: (a) => {
|
|
212
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
213
|
+
const intention = (0, intentions_1.captureIntention)(agentRoot, {
|
|
214
|
+
content: a.content,
|
|
215
|
+
salience: a.salience ?? "low",
|
|
216
|
+
source: "tool",
|
|
217
|
+
...(a.nudgeAfter ? { nudgeAfter: a.nudgeAfter } : {}),
|
|
218
|
+
});
|
|
219
|
+
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.intention_capture", message: `captured intention ${intention.id}`, meta: { id: intention.id } });
|
|
220
|
+
return JSON.stringify(intention, null, 2);
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
tool: {
|
|
225
|
+
type: "function",
|
|
226
|
+
function: {
|
|
227
|
+
name: "intention_manage",
|
|
228
|
+
description: "Resolve or dismiss an intention. Resolve = done. Dismiss = no longer relevant. Both remove it from active list.",
|
|
229
|
+
parameters: {
|
|
230
|
+
type: "object",
|
|
231
|
+
properties: {
|
|
232
|
+
action: { type: "string", enum: ["resolve", "dismiss"], description: "What to do" },
|
|
233
|
+
id: { type: "string", description: "Intention ID" },
|
|
234
|
+
},
|
|
235
|
+
required: ["action", "id"],
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
handler: (a) => {
|
|
240
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
241
|
+
const result = a.action === "resolve"
|
|
242
|
+
? (0, intentions_1.resolveIntention)(agentRoot, a.id)
|
|
243
|
+
: (0, intentions_1.dismissIntention)(agentRoot, a.id);
|
|
244
|
+
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.intention_manage", message: `intention ${a.action}: ${a.id}`, meta: { action: a.action, id: a.id } });
|
|
245
|
+
return JSON.stringify(result, null, 2);
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
];
|