@ouro.bot/cli 0.1.0-alpha.595 → 0.1.0-alpha.597
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/awaiting/await-alert.js +146 -0
- package/dist/heart/awaiting/await-expiry.js +108 -0
- package/dist/heart/awaiting/await-loader.js +91 -0
- package/dist/heart/awaiting/await-parser.js +141 -0
- package/dist/heart/awaiting/await-runtime-state.js +97 -0
- package/dist/heart/awaiting/await-scheduler.js +377 -0
- package/dist/heart/commitments.js +35 -4
- package/dist/heart/daemon/cli-parse.js +8 -1
- package/dist/heart/daemon/daemon-entry.js +98 -0
- package/dist/heart/daemon/daemon.js +7 -0
- package/dist/mind/prompt.js +13 -2
- package/dist/repertoire/tools-awaiting.js +360 -0
- package/dist/repertoire/tools-base.js +4 -0
- package/dist/repertoire/tools-obligations.js +142 -0
- package/dist/senses/await-turn-message.js +58 -0
- package/dist/senses/inner-dialog-worker.js +13 -3
- package/dist/senses/inner-dialog.js +42 -0
- package/package.json +1 -1
|
@@ -0,0 +1,360 @@
|
|
|
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.awaitingToolDefinitions = void 0;
|
|
37
|
+
exports.setAwaitToolDeps = setAwaitToolDeps;
|
|
38
|
+
exports.resetAwaitToolDeps = resetAwaitToolDeps;
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const identity_1 = require("../heart/identity");
|
|
42
|
+
const runtime_1 = require("../nerves/runtime");
|
|
43
|
+
const await_parser_1 = require("../heart/awaiting/await-parser");
|
|
44
|
+
const await_alert_1 = require("../heart/awaiting/await-alert");
|
|
45
|
+
const pending_1 = require("../mind/pending");
|
|
46
|
+
/**
|
|
47
|
+
* Bundle-root-relative locations.
|
|
48
|
+
* - `awaiting/<name>.md` — active awaits (status: pending)
|
|
49
|
+
* - `awaiting/.done/<name>.md` — terminal awaits (resolved/expired/canceled)
|
|
50
|
+
*/
|
|
51
|
+
function awaitingDir(agentRoot) {
|
|
52
|
+
return path.join(agentRoot, "awaiting");
|
|
53
|
+
}
|
|
54
|
+
function awaitingDoneDir(agentRoot) {
|
|
55
|
+
return path.join(awaitingDir(agentRoot), ".done");
|
|
56
|
+
}
|
|
57
|
+
function awaitFilePath(agentRoot, name) {
|
|
58
|
+
return path.join(awaitingDir(agentRoot), `${name}.md`);
|
|
59
|
+
}
|
|
60
|
+
function awaitDoneFilePath(agentRoot, name) {
|
|
61
|
+
return path.join(awaitingDoneDir(agentRoot), `${name}.md`);
|
|
62
|
+
}
|
|
63
|
+
const VALID_NAME = /^[A-Za-z0-9_-]+$/;
|
|
64
|
+
function validateName(name) {
|
|
65
|
+
if (!name)
|
|
66
|
+
return "name is required";
|
|
67
|
+
if (!VALID_NAME.test(name))
|
|
68
|
+
return "name must be alphanumeric, underscores, or hyphens";
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
function readAwaitDefinition(agentRoot, name) {
|
|
72
|
+
const filePath = awaitFilePath(agentRoot, name);
|
|
73
|
+
try {
|
|
74
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
75
|
+
return (0, await_parser_1.parseAwaitFile)(content, filePath);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Default delivery deps for the await alert path used from the tool.
|
|
83
|
+
* Mirrors the proactive-outreach pattern: queue to the inner-dialog pending
|
|
84
|
+
* dir when no live deliverer is registered.
|
|
85
|
+
*/
|
|
86
|
+
function defaultDeliveryDeps(agentName) {
|
|
87
|
+
const pendingDir = (0, pending_1.getInnerDialogPendingDir)(agentName);
|
|
88
|
+
return {
|
|
89
|
+
agentName,
|
|
90
|
+
queuePending: (message) => {
|
|
91
|
+
// Mirror the write-as-pending convention from tools-session.
|
|
92
|
+
fs.mkdirSync(pendingDir, { recursive: true });
|
|
93
|
+
const filename = `${message.timestamp}-${Math.random().toString(36).slice(2, 10)}.json`;
|
|
94
|
+
fs.writeFileSync(path.join(pendingDir, filename), JSON.stringify(message, null, 2), "utf-8");
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
let injected = {};
|
|
99
|
+
function setAwaitToolDeps(deps) {
|
|
100
|
+
injected = deps;
|
|
101
|
+
}
|
|
102
|
+
function resetAwaitToolDeps() {
|
|
103
|
+
injected = {};
|
|
104
|
+
}
|
|
105
|
+
function resolveDeliveryDeps(agentName) {
|
|
106
|
+
if (injected.buildDeliveryDeps)
|
|
107
|
+
return injected.buildDeliveryDeps(agentName);
|
|
108
|
+
return defaultDeliveryDeps(agentName);
|
|
109
|
+
}
|
|
110
|
+
function fileAwait(args, agentRoot, agentName, sessionFriendId, sessionChannel) {
|
|
111
|
+
const nameError = validateName(args.name);
|
|
112
|
+
if (nameError)
|
|
113
|
+
return JSON.stringify({ error: nameError });
|
|
114
|
+
if (!args.condition || !args.condition.trim()) {
|
|
115
|
+
return JSON.stringify({ error: "condition is required" });
|
|
116
|
+
}
|
|
117
|
+
if (!args.cadence || !args.cadence.trim()) {
|
|
118
|
+
return JSON.stringify({ error: "cadence is required" });
|
|
119
|
+
}
|
|
120
|
+
const filePath = awaitFilePath(agentRoot, args.name);
|
|
121
|
+
if (fs.existsSync(filePath)) {
|
|
122
|
+
return JSON.stringify({ error: `await "${args.name}" already exists` });
|
|
123
|
+
}
|
|
124
|
+
const mode = args.mode === "quick" ? "quick" : "full";
|
|
125
|
+
const alert = args.alert ?? sessionChannel ?? null;
|
|
126
|
+
const frontmatter = {
|
|
127
|
+
condition: args.condition.trim(),
|
|
128
|
+
cadence: args.cadence.trim(),
|
|
129
|
+
alert,
|
|
130
|
+
mode,
|
|
131
|
+
max_age: args.max_age ?? null,
|
|
132
|
+
status: "pending",
|
|
133
|
+
created_at: new Date().toISOString(),
|
|
134
|
+
filed_from: sessionChannel ?? "unknown",
|
|
135
|
+
filed_for_friend_id: sessionFriendId ?? null,
|
|
136
|
+
};
|
|
137
|
+
const rendered = (0, await_parser_1.renderAwaitFile)(frontmatter, args.body ?? "");
|
|
138
|
+
fs.mkdirSync(awaitingDir(agentRoot), { recursive: true });
|
|
139
|
+
fs.writeFileSync(filePath, rendered, "utf-8");
|
|
140
|
+
(0, runtime_1.emitNervesEvent)({
|
|
141
|
+
component: "repertoire",
|
|
142
|
+
event: "repertoire.await_filed",
|
|
143
|
+
message: "filed new await",
|
|
144
|
+
meta: { agent: agentName, name: args.name, cadence: args.cadence, alert },
|
|
145
|
+
});
|
|
146
|
+
return JSON.stringify({ filed: args.name, path: filePath });
|
|
147
|
+
}
|
|
148
|
+
function archiveAwait(agentRoot, name, updates) {
|
|
149
|
+
const source = awaitFilePath(agentRoot, name);
|
|
150
|
+
/* v8 ignore start -- defensive: callers (resolve/cancel) already verify the file exists via readAwaitDefinition; this guards the file-disappears-between-calls race @preserve */
|
|
151
|
+
if (!fs.existsSync(source)) {
|
|
152
|
+
return { ok: false, error: `await "${name}" not found in awaiting/` };
|
|
153
|
+
}
|
|
154
|
+
/* v8 ignore stop */
|
|
155
|
+
const content = fs.readFileSync(source, "utf-8");
|
|
156
|
+
const current = (0, await_parser_1.parseAwaitFile)(content, source);
|
|
157
|
+
// merge frontmatter from the parsed file with updates
|
|
158
|
+
const merged = {
|
|
159
|
+
condition: current.condition,
|
|
160
|
+
cadence: current.cadence,
|
|
161
|
+
alert: current.alert,
|
|
162
|
+
mode: current.mode,
|
|
163
|
+
max_age: current.max_age,
|
|
164
|
+
status: current.status,
|
|
165
|
+
created_at: current.created_at,
|
|
166
|
+
filed_from: current.filed_from,
|
|
167
|
+
filed_for_friend_id: current.filed_for_friend_id,
|
|
168
|
+
...updates,
|
|
169
|
+
};
|
|
170
|
+
const rendered = (0, await_parser_1.renderAwaitFile)(merged, current.body);
|
|
171
|
+
fs.mkdirSync(awaitingDoneDir(agentRoot), { recursive: true });
|
|
172
|
+
fs.writeFileSync(awaitDoneFilePath(agentRoot, name), rendered, "utf-8");
|
|
173
|
+
fs.unlinkSync(source);
|
|
174
|
+
// re-parse the archived file so callers see merged fields (e.g. resolution_observation)
|
|
175
|
+
const archivedContent = fs.readFileSync(awaitDoneFilePath(agentRoot, name), "utf-8");
|
|
176
|
+
const archived = (0, await_parser_1.parseAwaitFile)(archivedContent, awaitDoneFilePath(agentRoot, name));
|
|
177
|
+
return { ok: true, file: archived };
|
|
178
|
+
}
|
|
179
|
+
async function resolveAwaitTool(name, verdict, observation, agentRoot, agentName) {
|
|
180
|
+
const nameError = validateName(name);
|
|
181
|
+
if (nameError)
|
|
182
|
+
return JSON.stringify({ error: nameError });
|
|
183
|
+
const existing = readAwaitDefinition(agentRoot, name);
|
|
184
|
+
if (!existing) {
|
|
185
|
+
return JSON.stringify({ error: `await "${name}" not found in awaiting/` });
|
|
186
|
+
}
|
|
187
|
+
if (existing.status !== "pending") {
|
|
188
|
+
return JSON.stringify({ error: `await "${name}" is not pending (status: ${existing.status})` });
|
|
189
|
+
}
|
|
190
|
+
if (verdict !== "yes" && verdict !== "no") {
|
|
191
|
+
return JSON.stringify({ error: "verdict must be 'yes' or 'no'" });
|
|
192
|
+
}
|
|
193
|
+
if (!observation || !observation.trim()) {
|
|
194
|
+
return JSON.stringify({ error: "observation is required" });
|
|
195
|
+
}
|
|
196
|
+
if (verdict === "no") {
|
|
197
|
+
// Update runtime state via recordAwaitCheck-style write
|
|
198
|
+
const { recordAwaitCheck } = await Promise.resolve().then(() => __importStar(require("../heart/awaiting/await-runtime-state")));
|
|
199
|
+
recordAwaitCheck(agentRoot, name, observation.trim(), new Date().toISOString());
|
|
200
|
+
(0, runtime_1.emitNervesEvent)({
|
|
201
|
+
component: "repertoire",
|
|
202
|
+
event: "repertoire.await_check_no",
|
|
203
|
+
message: "await checked, not yet ready",
|
|
204
|
+
meta: { agent: agentName, name },
|
|
205
|
+
});
|
|
206
|
+
return JSON.stringify({ verdict: "no", recorded: true });
|
|
207
|
+
}
|
|
208
|
+
// verdict === "yes" — archive and alert
|
|
209
|
+
const archive = archiveAwait(agentRoot, name, {
|
|
210
|
+
status: "resolved",
|
|
211
|
+
resolved_at: new Date().toISOString(),
|
|
212
|
+
resolution_observation: observation.trim(),
|
|
213
|
+
});
|
|
214
|
+
/* v8 ignore next -- defensive: archiveAwait only fails on the file-disappears-mid-call race already covered by v8 ignore inside archiveAwait @preserve */
|
|
215
|
+
if (!archive.ok)
|
|
216
|
+
return JSON.stringify({ error: archive.error });
|
|
217
|
+
(0, runtime_1.emitNervesEvent)({
|
|
218
|
+
component: "repertoire",
|
|
219
|
+
event: "repertoire.await_resolved",
|
|
220
|
+
message: "await resolved",
|
|
221
|
+
meta: { agent: agentName, name },
|
|
222
|
+
});
|
|
223
|
+
let alert = null;
|
|
224
|
+
try {
|
|
225
|
+
alert = await (0, await_alert_1.deliverAwaitAlert)({
|
|
226
|
+
awaitFile: archive.file,
|
|
227
|
+
reason: "resolved",
|
|
228
|
+
observation: observation.trim(),
|
|
229
|
+
agentRoot,
|
|
230
|
+
agentName,
|
|
231
|
+
deliveryDeps: resolveDeliveryDeps(agentName),
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
(0, runtime_1.emitNervesEvent)({
|
|
236
|
+
level: "error",
|
|
237
|
+
component: "repertoire",
|
|
238
|
+
event: "repertoire.await_alert_error",
|
|
239
|
+
message: "await alert delivery threw",
|
|
240
|
+
meta: { agent: agentName, name, error: error instanceof Error ? error.message : String(error) },
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
return JSON.stringify({
|
|
244
|
+
verdict: "yes",
|
|
245
|
+
archived: awaitDoneFilePath(agentRoot, name),
|
|
246
|
+
alert: alert ? { attempted: alert.attempted, status: alert.delivery?.status ?? null, skipped: alert.skipped ?? null } : null,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
function cancelAwaitTool(name, reason, agentRoot, agentName) {
|
|
250
|
+
const nameError = validateName(name);
|
|
251
|
+
if (nameError)
|
|
252
|
+
return JSON.stringify({ error: nameError });
|
|
253
|
+
const existing = readAwaitDefinition(agentRoot, name);
|
|
254
|
+
if (!existing) {
|
|
255
|
+
return JSON.stringify({ error: `await "${name}" not found in awaiting/` });
|
|
256
|
+
}
|
|
257
|
+
if (existing.status !== "pending") {
|
|
258
|
+
return JSON.stringify({ error: `await "${name}" is not pending (status: ${existing.status})` });
|
|
259
|
+
}
|
|
260
|
+
const updates = {
|
|
261
|
+
status: "canceled",
|
|
262
|
+
canceled_at: new Date().toISOString(),
|
|
263
|
+
};
|
|
264
|
+
if (reason && reason.trim()) {
|
|
265
|
+
updates.cancel_reason = reason.trim();
|
|
266
|
+
}
|
|
267
|
+
const archive = archiveAwait(agentRoot, name, updates);
|
|
268
|
+
/* v8 ignore next -- defensive: archiveAwait only fails on the file-disappears-mid-call race already covered by v8 ignore inside archiveAwait @preserve */
|
|
269
|
+
if (!archive.ok)
|
|
270
|
+
return JSON.stringify({ error: archive.error });
|
|
271
|
+
(0, runtime_1.emitNervesEvent)({
|
|
272
|
+
component: "repertoire",
|
|
273
|
+
event: "repertoire.await_canceled",
|
|
274
|
+
message: "await canceled",
|
|
275
|
+
meta: { agent: agentName, name },
|
|
276
|
+
});
|
|
277
|
+
return JSON.stringify({ canceled: name, archived: awaitDoneFilePath(agentRoot, name) });
|
|
278
|
+
}
|
|
279
|
+
exports.awaitingToolDefinitions = [
|
|
280
|
+
{
|
|
281
|
+
tool: {
|
|
282
|
+
type: "function",
|
|
283
|
+
function: {
|
|
284
|
+
name: "await_condition",
|
|
285
|
+
description: "File a one-shot waiting condition. The daemon polls on cadence; on each tick I evaluate the condition and call resolve_await. When the condition becomes true, an alert fires via my outward channel.",
|
|
286
|
+
parameters: {
|
|
287
|
+
type: "object",
|
|
288
|
+
properties: {
|
|
289
|
+
name: { type: "string", description: "Filename stem (alphanumeric/underscore/hyphen). Must be unique." },
|
|
290
|
+
condition: { type: "string", description: "Natural-language condition to watch for." },
|
|
291
|
+
cadence: { type: "string", description: "Polling cadence (e.g. '5m', '1h')." },
|
|
292
|
+
alert: { type: "string", description: "Channel to alert on (e.g. 'bluebubbles', 'teams'). Defaults to filing session's channel." },
|
|
293
|
+
mode: { type: "string", description: "'full' or 'quick'. Defaults 'full'." },
|
|
294
|
+
max_age: { type: "string", description: "Optional auto-expiry (e.g. '24h')." },
|
|
295
|
+
body: { type: "string", description: "Optional notes: why I filed this, what 'ready' looks like." },
|
|
296
|
+
},
|
|
297
|
+
required: ["name", "condition", "cadence"],
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
handler: (a, ctx) => {
|
|
302
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
303
|
+
const agentName = (0, identity_1.getAgentName)();
|
|
304
|
+
return fileAwait({
|
|
305
|
+
name: a.name,
|
|
306
|
+
condition: a.condition,
|
|
307
|
+
cadence: a.cadence,
|
|
308
|
+
alert: a.alert,
|
|
309
|
+
mode: a.mode,
|
|
310
|
+
max_age: a.max_age,
|
|
311
|
+
body: a.body,
|
|
312
|
+
}, agentRoot, agentName, ctx?.currentSession?.friendId ?? null, ctx?.currentSession?.channel ?? null);
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
tool: {
|
|
317
|
+
type: "function",
|
|
318
|
+
function: {
|
|
319
|
+
name: "resolve_await",
|
|
320
|
+
description: "Resolve a pending await with a verdict. verdict='yes' archives and fires the alert. verdict='no' records the observation and continues polling.",
|
|
321
|
+
parameters: {
|
|
322
|
+
type: "object",
|
|
323
|
+
properties: {
|
|
324
|
+
name: { type: "string", description: "Await name (filename stem)." },
|
|
325
|
+
verdict: { type: "string", description: "'yes' if the condition is met, 'no' otherwise." },
|
|
326
|
+
observation: { type: "string", description: "One-line summary of what I saw this tick." },
|
|
327
|
+
},
|
|
328
|
+
required: ["name", "verdict", "observation"],
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
handler: async (a) => {
|
|
333
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
334
|
+
const agentName = (0, identity_1.getAgentName)();
|
|
335
|
+
return resolveAwaitTool(a.name, a.verdict, a.observation, agentRoot, agentName);
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
tool: {
|
|
340
|
+
type: "function",
|
|
341
|
+
function: {
|
|
342
|
+
name: "cancel_await",
|
|
343
|
+
description: "Cancel a pending await without alerting. Archives with status: canceled.",
|
|
344
|
+
parameters: {
|
|
345
|
+
type: "object",
|
|
346
|
+
properties: {
|
|
347
|
+
name: { type: "string", description: "Await name (filename stem)." },
|
|
348
|
+
reason: { type: "string", description: "Optional cancel reason." },
|
|
349
|
+
},
|
|
350
|
+
required: ["name"],
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
handler: (a) => {
|
|
355
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
356
|
+
const agentName = (0, identity_1.getAgentName)();
|
|
357
|
+
return cancelAwaitTool(a.name, a.reason, agentRoot, agentName);
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
];
|
|
@@ -18,6 +18,8 @@ const tools_flight_1 = require("./tools-flight");
|
|
|
18
18
|
const tools_attachments_1 = require("./tools-attachments");
|
|
19
19
|
const tools_mail_1 = require("./tools-mail");
|
|
20
20
|
const tools_trip_1 = require("./tools-trip");
|
|
21
|
+
const tools_awaiting_1 = require("./tools-awaiting");
|
|
22
|
+
const tools_obligations_1 = require("./tools-obligations");
|
|
21
23
|
// Re-export flow tools for consumers that import them from tools-base
|
|
22
24
|
var tools_flow_1 = require("./tools-flow");
|
|
23
25
|
Object.defineProperty(exports, "ponderTool", { enumerable: true, get: function () { return tools_flow_1.ponderTool; } });
|
|
@@ -51,6 +53,8 @@ exports.baseToolDefinitions = [
|
|
|
51
53
|
...tools_attachments_1.attachmentToolDefinitions,
|
|
52
54
|
...tools_mail_1.mailToolDefinitions,
|
|
53
55
|
...tools_trip_1.tripToolDefinitions,
|
|
56
|
+
...tools_awaiting_1.awaitingToolDefinitions,
|
|
57
|
+
...tools_obligations_1.obligationToolDefinitions,
|
|
54
58
|
];
|
|
55
59
|
// Convenience array of just the tool schemas (no handler/integration metadata).
|
|
56
60
|
// Used by consumers that need the OpenAI function-tool format.
|
|
@@ -0,0 +1,142 @@
|
|
|
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.obligationToolDefinitions = void 0;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const identity_1 = require("../heart/identity");
|
|
39
|
+
const runtime_1 = require("../nerves/runtime");
|
|
40
|
+
const json_store_1 = require("../arc/json-store");
|
|
41
|
+
const obligations_1 = require("../arc/obligations");
|
|
42
|
+
/**
|
|
43
|
+
* `let_go` lets the agent release a held work item from its attention loop.
|
|
44
|
+
*
|
|
45
|
+
* Two stores need release semantics:
|
|
46
|
+
* 1. ReturnObligations at `arc/obligations/inner/<id>.json` (status: queued|running|returned|deferred)
|
|
47
|
+
* — these are what surface in the "held work items" section of the prompt.
|
|
48
|
+
* 2. Outer Obligations at `arc/obligations/<id>.json` (status: pending|...|fulfilled)
|
|
49
|
+
* — these surface as "i owe <friend>: ..." in the commitments section.
|
|
50
|
+
*
|
|
51
|
+
* The pre-existing path to terminal state for both is the `surface` tool, which fulfills
|
|
52
|
+
* an obligation as a side-effect of delivering a response. When work is resolved EXTERNALLY
|
|
53
|
+
* (e.g. a PR merged fixed the underlying issue), there is no response to surface — the
|
|
54
|
+
* agent has no way to clear the item, so it cycles in the prompt every turn.
|
|
55
|
+
*
|
|
56
|
+
* `let_go` is the missing primitive: dismissal WITHOUT delivery.
|
|
57
|
+
*/
|
|
58
|
+
function outerObligationsDir(agentRoot) {
|
|
59
|
+
return path.join(agentRoot, "arc", "obligations");
|
|
60
|
+
}
|
|
61
|
+
function readOuterObligation(agentRoot, id) {
|
|
62
|
+
return (0, json_store_1.readJsonFile)(outerObligationsDir(agentRoot), id) ?? null;
|
|
63
|
+
}
|
|
64
|
+
function letGo(args, agentRoot, agentName) {
|
|
65
|
+
if (typeof args.id !== "string" || args.id.trim().length === 0) {
|
|
66
|
+
return JSON.stringify({ error: "id is required" });
|
|
67
|
+
}
|
|
68
|
+
const id = args.id.trim();
|
|
69
|
+
const reason = typeof args.reason === "string" && args.reason.trim().length > 0
|
|
70
|
+
? args.reason.trim()
|
|
71
|
+
: null;
|
|
72
|
+
// 1. ReturnObligation (inner) first — these are what slugger sees in "held work items".
|
|
73
|
+
const ret = (0, obligations_1.readReturnObligation)(agentName, id);
|
|
74
|
+
if (ret) {
|
|
75
|
+
if (ret.status === "returned" || ret.status === "deferred") {
|
|
76
|
+
return JSON.stringify({ kind: "return_obligation", id, already: ret.status });
|
|
77
|
+
}
|
|
78
|
+
(0, obligations_1.advanceReturnObligation)(agentName, id, {
|
|
79
|
+
status: "returned",
|
|
80
|
+
returnedAt: Date.now(),
|
|
81
|
+
returnTarget: "surface",
|
|
82
|
+
});
|
|
83
|
+
(0, runtime_1.emitNervesEvent)({
|
|
84
|
+
component: "repertoire",
|
|
85
|
+
event: "repertoire.obligation_let_go",
|
|
86
|
+
message: "agent let go of return obligation",
|
|
87
|
+
meta: { kind: "return_obligation", id, reason: reason ?? null },
|
|
88
|
+
});
|
|
89
|
+
return JSON.stringify({ kind: "return_obligation", let_go: id, reason });
|
|
90
|
+
}
|
|
91
|
+
// 2. Outer Obligation — these surface as "i owe ..." in commitments.
|
|
92
|
+
const outer = readOuterObligation(agentRoot, id);
|
|
93
|
+
if (outer) {
|
|
94
|
+
if (outer.status === "fulfilled") {
|
|
95
|
+
return JSON.stringify({ kind: "obligation", id, already: "fulfilled" });
|
|
96
|
+
}
|
|
97
|
+
(0, obligations_1.fulfillObligation)(agentRoot, id);
|
|
98
|
+
if (reason !== null) {
|
|
99
|
+
// Persist the dismissal reason as the obligation's latestNote so future-me
|
|
100
|
+
// can read why this was released (the nerves event also captures it).
|
|
101
|
+
(0, obligations_1.advanceObligation)(agentRoot, id, { latestNote: reason });
|
|
102
|
+
}
|
|
103
|
+
(0, runtime_1.emitNervesEvent)({
|
|
104
|
+
component: "repertoire",
|
|
105
|
+
event: "repertoire.obligation_let_go",
|
|
106
|
+
message: "agent let go of outer obligation",
|
|
107
|
+
meta: { kind: "obligation", id, reason: reason ?? null },
|
|
108
|
+
});
|
|
109
|
+
return JSON.stringify({ kind: "obligation", let_go: id, reason });
|
|
110
|
+
}
|
|
111
|
+
return JSON.stringify({ error: `no obligation found with id "${id}"` });
|
|
112
|
+
}
|
|
113
|
+
exports.obligationToolDefinitions = [
|
|
114
|
+
{
|
|
115
|
+
tool: {
|
|
116
|
+
type: "function",
|
|
117
|
+
function: {
|
|
118
|
+
name: "let_go",
|
|
119
|
+
description: "release a held work item from my attention. for items that have been resolved externally, no longer apply, or are stale and just cycling in my prompt with nothing to act on. takes the id i see in the 'held work items' section (or any obligation id from arc/obligations/). optional reason is recorded for future me. idempotent — calling on an already-released item returns the existing status, not an error.",
|
|
120
|
+
parameters: {
|
|
121
|
+
type: "object",
|
|
122
|
+
properties: {
|
|
123
|
+
id: {
|
|
124
|
+
type: "string",
|
|
125
|
+
description: "the obligation id — the bracketed id in 'held work items' (e.g. '1775976317954-s5pno43r'), or any obligation file's id from arc/obligations/.",
|
|
126
|
+
},
|
|
127
|
+
reason: {
|
|
128
|
+
type: "string",
|
|
129
|
+
description: "optional one-line reason i'm letting go (e.g. 'resolved externally by PR #701').",
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
required: ["id"],
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
handler: (args) => {
|
|
137
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
138
|
+
const agentName = (0, identity_1.getAgentName)();
|
|
139
|
+
return letGo({ id: args.id, reason: args.reason }, agentRoot, agentName);
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
];
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildAwaitTurnMessage = buildAwaitTurnMessage;
|
|
4
|
+
const runtime_1 = require("../nerves/runtime");
|
|
5
|
+
function formatElapsed(ms) {
|
|
6
|
+
if (ms < 60_000)
|
|
7
|
+
return "<1m ago";
|
|
8
|
+
const minutes = Math.floor(ms / 60_000);
|
|
9
|
+
if (minutes < 60)
|
|
10
|
+
return `${minutes}m ago`;
|
|
11
|
+
const hours = Math.floor(minutes / 60);
|
|
12
|
+
if (hours < 24)
|
|
13
|
+
return `${hours}h ago`;
|
|
14
|
+
const days = Math.floor(hours / 24);
|
|
15
|
+
return `${days}d ago`;
|
|
16
|
+
}
|
|
17
|
+
function relativeAge(lastCheckedAt, now) {
|
|
18
|
+
if (!lastCheckedAt)
|
|
19
|
+
return null;
|
|
20
|
+
const lastMs = new Date(lastCheckedAt).getTime();
|
|
21
|
+
if (!Number.isFinite(lastMs))
|
|
22
|
+
return null;
|
|
23
|
+
return formatElapsed(now().getTime() - lastMs);
|
|
24
|
+
}
|
|
25
|
+
function buildAwaitTurnMessage(options) {
|
|
26
|
+
(0, runtime_1.emitNervesEvent)({
|
|
27
|
+
component: "senses",
|
|
28
|
+
event: "senses.await_turn_message_built",
|
|
29
|
+
message: "built await tick message",
|
|
30
|
+
meta: { awaitName: options.awaitName, checkedCount: options.checkedCount },
|
|
31
|
+
});
|
|
32
|
+
const lines = [];
|
|
33
|
+
lines.push(`await tick: ${options.awaitName} — ${options.condition}`);
|
|
34
|
+
if (options.body && options.body.trim().length > 0) {
|
|
35
|
+
lines.push("");
|
|
36
|
+
lines.push("what would count as ready:");
|
|
37
|
+
lines.push(options.body.trim());
|
|
38
|
+
}
|
|
39
|
+
const age = relativeAge(options.lastCheckedAt, options.now);
|
|
40
|
+
const obs = options.lastObservation && options.lastObservation.trim().length > 0
|
|
41
|
+
? `last observation: "${options.lastObservation.trim()}"`
|
|
42
|
+
: "last observation: (none yet)";
|
|
43
|
+
if (options.checkedCount === 0) {
|
|
44
|
+
lines.push("");
|
|
45
|
+
lines.push("history: never checked. this is my first look.");
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
lines.push("");
|
|
49
|
+
lines.push(`history: checked ${options.checkedCount}x so far. last checked ${age ?? "(unknown)"}. ${obs}.`);
|
|
50
|
+
}
|
|
51
|
+
if (options.checkpoint) {
|
|
52
|
+
lines.push("");
|
|
53
|
+
lines.push(`last checkpoint: ${options.checkpoint}`);
|
|
54
|
+
}
|
|
55
|
+
lines.push("");
|
|
56
|
+
lines.push("look around and decide. if the condition is met, call resolve_await with verdict='yes' and a one-line observation. otherwise call resolve_await with verdict='no' and a one-line observation of what i saw this tick.");
|
|
57
|
+
return lines.join("\n");
|
|
58
|
+
}
|
|
@@ -119,9 +119,9 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
119
119
|
});
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
|
-
async function run(reason, taskId, habitName) {
|
|
122
|
+
async function run(reason, taskId, habitName, awaitName) {
|
|
123
123
|
if (running) {
|
|
124
|
-
queue.push({ reason, taskId, habitName });
|
|
124
|
+
queue.push({ reason, taskId, habitName, awaitName });
|
|
125
125
|
return;
|
|
126
126
|
}
|
|
127
127
|
running = true;
|
|
@@ -129,10 +129,11 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
129
129
|
let nextReason = reason;
|
|
130
130
|
let nextTaskId = taskId;
|
|
131
131
|
let nextHabitName = habitName;
|
|
132
|
+
let nextAwaitName = awaitName;
|
|
132
133
|
let consecutiveInstinctTurns = reason === "instinct" ? 1 : 0;
|
|
133
134
|
do {
|
|
134
135
|
try {
|
|
135
|
-
await runTurn({ reason: nextReason, taskId: nextTaskId, habitName: nextHabitName });
|
|
136
|
+
await runTurn({ reason: nextReason, taskId: nextTaskId, habitName: nextHabitName, awaitName: nextAwaitName });
|
|
136
137
|
}
|
|
137
138
|
catch (error) {
|
|
138
139
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -165,6 +166,7 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
165
166
|
nextReason = next.reason;
|
|
166
167
|
nextTaskId = next.taskId;
|
|
167
168
|
nextHabitName = next.habitName;
|
|
169
|
+
nextAwaitName = next.awaitName;
|
|
168
170
|
consecutiveInstinctTurns = nextReason === "instinct" ? consecutiveInstinctTurns + 1 : 0;
|
|
169
171
|
continue;
|
|
170
172
|
}
|
|
@@ -191,6 +193,7 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
191
193
|
nextReason = "instinct";
|
|
192
194
|
nextTaskId = undefined;
|
|
193
195
|
nextHabitName = undefined;
|
|
196
|
+
nextAwaitName = undefined;
|
|
194
197
|
continue;
|
|
195
198
|
}
|
|
196
199
|
break;
|
|
@@ -211,6 +214,13 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
211
214
|
await run("habit", undefined, maybeMessage.habitName);
|
|
212
215
|
return;
|
|
213
216
|
}
|
|
217
|
+
if (maybeMessage.type === "await") {
|
|
218
|
+
/* v8 ignore next -- defensive fallback: live await dispatch always sets awaitName @preserve */
|
|
219
|
+
const awaitName = maybeMessage.awaitName ?? "(unnamed)";
|
|
220
|
+
recordHabitFireForRecursion(`await:${awaitName}`);
|
|
221
|
+
await run("await", undefined, undefined, maybeMessage.awaitName);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
214
224
|
if (maybeMessage.type === "heartbeat") {
|
|
215
225
|
// Backward compatibility: heartbeat -> habit/heartbeat
|
|
216
226
|
recordHabitFireForRecursion("heartbeat");
|