@ouro.bot/cli 0.1.0-alpha.147 → 0.1.0-alpha.148
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 +15 -0
- package/dist/heart/daemon/cadence.js +70 -0
- package/dist/heart/daemon/daemon-cli.js +104 -6
- package/dist/heart/daemon/daemon-entry.js +36 -15
- package/dist/heart/daemon/daemon.js +7 -0
- package/dist/heart/daemon/habit-migration.js +181 -0
- package/dist/heart/daemon/habit-parser.js +119 -0
- package/dist/heart/daemon/habit-scheduler.js +244 -0
- package/dist/heart/daemon/hatch-flow.js +13 -31
- package/dist/heart/daemon/os-cron-deps.js +134 -0
- package/dist/heart/daemon/specialist-tools.js +1 -1
- package/dist/heart/daemon/task-scheduler.js +3 -25
- package/dist/mind/prompt.js +9 -5
- package/dist/repertoire/tasks/parser.js +0 -2
- package/dist/repertoire/tasks/transitions.js +0 -18
- package/dist/senses/contextual-heartbeat.js +5 -1
- package/dist/senses/inner-dialog-worker.js +84 -20
- package/dist/senses/inner-dialog.js +145 -39
- package/package.json +1 -1
- package/dist/heart/daemon/heartbeat-timer.js +0 -132
|
@@ -0,0 +1,244 @@
|
|
|
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.HabitScheduler = void 0;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
39
|
+
const habit_parser_1 = require("./habit-parser");
|
|
40
|
+
const cadence_1 = require("./cadence");
|
|
41
|
+
const WATCH_DEBOUNCE_MS = 200;
|
|
42
|
+
class HabitScheduler {
|
|
43
|
+
agent;
|
|
44
|
+
habitsDir;
|
|
45
|
+
osCronManager;
|
|
46
|
+
onHabitFire;
|
|
47
|
+
deps;
|
|
48
|
+
watcher = null;
|
|
49
|
+
debounceTimer = null;
|
|
50
|
+
parseErrors = [];
|
|
51
|
+
constructor(options) {
|
|
52
|
+
this.agent = options.agent;
|
|
53
|
+
this.habitsDir = options.habitsDir;
|
|
54
|
+
this.osCronManager = options.osCronManager;
|
|
55
|
+
this.onHabitFire = options.onHabitFire;
|
|
56
|
+
this.deps = options.deps;
|
|
57
|
+
}
|
|
58
|
+
start() {
|
|
59
|
+
(0, runtime_1.emitNervesEvent)({
|
|
60
|
+
component: "daemon",
|
|
61
|
+
event: "daemon.habit_scheduler_start",
|
|
62
|
+
message: "habit scheduler starting",
|
|
63
|
+
meta: { agent: this.agent, habitsDir: this.habitsDir },
|
|
64
|
+
});
|
|
65
|
+
const habits = this.scanHabits();
|
|
66
|
+
const jobs = this.buildJobs(habits);
|
|
67
|
+
this.osCronManager.sync(jobs);
|
|
68
|
+
// Fire overdue habits immediately
|
|
69
|
+
for (const habit of habits) {
|
|
70
|
+
if (habit.status !== "active")
|
|
71
|
+
continue;
|
|
72
|
+
if (!habit.cadence)
|
|
73
|
+
continue;
|
|
74
|
+
const cadenceMs = (0, cadence_1.parseCadenceToMs)(habit.cadence);
|
|
75
|
+
if (cadenceMs === null)
|
|
76
|
+
continue;
|
|
77
|
+
const nowMs = this.deps.now();
|
|
78
|
+
if (habit.lastRun === null) {
|
|
79
|
+
(0, runtime_1.emitNervesEvent)({
|
|
80
|
+
component: "daemon",
|
|
81
|
+
event: "daemon.habit_fire",
|
|
82
|
+
message: "firing overdue habit (never run)",
|
|
83
|
+
meta: { habitName: habit.name, agent: this.agent },
|
|
84
|
+
});
|
|
85
|
+
this.onHabitFire(habit.name);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const lastRunMs = new Date(habit.lastRun).getTime();
|
|
89
|
+
const elapsed = nowMs - lastRunMs;
|
|
90
|
+
if (elapsed >= cadenceMs) {
|
|
91
|
+
(0, runtime_1.emitNervesEvent)({
|
|
92
|
+
component: "daemon",
|
|
93
|
+
event: "daemon.habit_fire",
|
|
94
|
+
message: "firing overdue habit",
|
|
95
|
+
meta: { habitName: habit.name, agent: this.agent, elapsedMs: elapsed },
|
|
96
|
+
});
|
|
97
|
+
this.onHabitFire(habit.name);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
reconcile() {
|
|
102
|
+
(0, runtime_1.emitNervesEvent)({
|
|
103
|
+
component: "daemon",
|
|
104
|
+
event: "daemon.habit_scheduler_reconcile",
|
|
105
|
+
message: "habit scheduler reconciling",
|
|
106
|
+
meta: { agent: this.agent },
|
|
107
|
+
});
|
|
108
|
+
const habits = this.scanHabits();
|
|
109
|
+
const jobs = this.buildJobs(habits);
|
|
110
|
+
this.osCronManager.sync(jobs);
|
|
111
|
+
}
|
|
112
|
+
stop() {
|
|
113
|
+
(0, runtime_1.emitNervesEvent)({
|
|
114
|
+
component: "daemon",
|
|
115
|
+
event: "daemon.habit_scheduler_stop",
|
|
116
|
+
message: "habit scheduler stopping",
|
|
117
|
+
meta: { agent: this.agent },
|
|
118
|
+
});
|
|
119
|
+
this.osCronManager.removeAll();
|
|
120
|
+
}
|
|
121
|
+
listOverdueHabits() {
|
|
122
|
+
const habits = this.scanHabits();
|
|
123
|
+
const nowMs = this.deps.now();
|
|
124
|
+
const overdue = [];
|
|
125
|
+
for (const habit of habits) {
|
|
126
|
+
if (habit.status !== "active")
|
|
127
|
+
continue;
|
|
128
|
+
if (!habit.cadence)
|
|
129
|
+
continue;
|
|
130
|
+
const cadenceMs = (0, cadence_1.parseCadenceToMs)(habit.cadence);
|
|
131
|
+
if (cadenceMs === null)
|
|
132
|
+
continue;
|
|
133
|
+
if (habit.lastRun === null) {
|
|
134
|
+
overdue.push({ name: habit.name, elapsedMs: Infinity });
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const lastRunMs = new Date(habit.lastRun).getTime();
|
|
138
|
+
const elapsed = nowMs - lastRunMs;
|
|
139
|
+
if (elapsed >= cadenceMs) {
|
|
140
|
+
overdue.push({ name: habit.name, elapsedMs: elapsed });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return overdue;
|
|
144
|
+
}
|
|
145
|
+
getParseErrors() {
|
|
146
|
+
return [...this.parseErrors];
|
|
147
|
+
}
|
|
148
|
+
getHabitFile(name) {
|
|
149
|
+
const filePath = path.join(this.habitsDir, `${name}.md`);
|
|
150
|
+
try {
|
|
151
|
+
const content = this.deps.readFile(filePath, "utf-8");
|
|
152
|
+
return (0, habit_parser_1.parseHabitFile)(content, filePath);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
watchForChanges() {
|
|
159
|
+
const watchFn = this.deps.watch;
|
|
160
|
+
if (!watchFn)
|
|
161
|
+
return;
|
|
162
|
+
this.watcher = watchFn(this.habitsDir, (_event, _filename) => {
|
|
163
|
+
if (this.debounceTimer !== null) {
|
|
164
|
+
clearTimeout(this.debounceTimer);
|
|
165
|
+
}
|
|
166
|
+
this.debounceTimer = setTimeout(() => {
|
|
167
|
+
this.debounceTimer = null;
|
|
168
|
+
this.reconcile();
|
|
169
|
+
}, WATCH_DEBOUNCE_MS);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
stopWatching() {
|
|
173
|
+
if (this.debounceTimer !== null) {
|
|
174
|
+
clearTimeout(this.debounceTimer);
|
|
175
|
+
this.debounceTimer = null;
|
|
176
|
+
}
|
|
177
|
+
if (this.watcher !== null) {
|
|
178
|
+
this.watcher.close();
|
|
179
|
+
this.watcher = null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
scanHabits() {
|
|
183
|
+
let files;
|
|
184
|
+
try {
|
|
185
|
+
files = this.deps.readdir(this.habitsDir);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
this.parseErrors = [];
|
|
189
|
+
return [];
|
|
190
|
+
}
|
|
191
|
+
const habits = [];
|
|
192
|
+
const errors = [];
|
|
193
|
+
for (const file of files) {
|
|
194
|
+
if (!file.endsWith(".md"))
|
|
195
|
+
continue;
|
|
196
|
+
const filePath = path.join(this.habitsDir, file);
|
|
197
|
+
try {
|
|
198
|
+
const content = this.deps.readFile(filePath, "utf-8");
|
|
199
|
+
const habit = (0, habit_parser_1.parseHabitFile)(content, filePath);
|
|
200
|
+
habits.push(habit);
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
204
|
+
errors.push({ file, error: errorMessage });
|
|
205
|
+
(0, runtime_1.emitNervesEvent)({
|
|
206
|
+
level: "error",
|
|
207
|
+
component: "daemon",
|
|
208
|
+
event: "daemon.habit_parse_error",
|
|
209
|
+
message: "failed to parse habit file",
|
|
210
|
+
meta: {
|
|
211
|
+
file,
|
|
212
|
+
error: errorMessage,
|
|
213
|
+
agent: this.agent,
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
this.parseErrors = errors;
|
|
219
|
+
return habits;
|
|
220
|
+
}
|
|
221
|
+
buildJobs(habits) {
|
|
222
|
+
const jobs = [];
|
|
223
|
+
for (const habit of habits) {
|
|
224
|
+
if (habit.status !== "active")
|
|
225
|
+
continue;
|
|
226
|
+
if (!habit.cadence)
|
|
227
|
+
continue;
|
|
228
|
+
const cronSchedule = (0, cadence_1.parseCadenceToCron)(habit.cadence);
|
|
229
|
+
if (cronSchedule === null)
|
|
230
|
+
continue;
|
|
231
|
+
jobs.push({
|
|
232
|
+
id: `${this.agent}:${habit.name}:cadence`,
|
|
233
|
+
agent: this.agent,
|
|
234
|
+
taskId: habit.name,
|
|
235
|
+
schedule: cronSchedule,
|
|
236
|
+
lastRun: habit.lastRun,
|
|
237
|
+
command: `${this.deps.ouroPath} poke ${this.agent} --habit ${habit.name}`,
|
|
238
|
+
taskPath: path.join(this.habitsDir, `${habit.name}.md`),
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
return jobs;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
exports.HabitScheduler = HabitScheduler;
|
|
@@ -42,6 +42,7 @@ const identity_1 = require("../identity");
|
|
|
42
42
|
const config_1 = require("../config");
|
|
43
43
|
const runtime_1 = require("../../nerves/runtime");
|
|
44
44
|
const auth_flow_1 = require("./auth-flow");
|
|
45
|
+
const habit_parser_1 = require("./habit-parser");
|
|
45
46
|
const hatch_specialist_1 = require("./hatch-specialist");
|
|
46
47
|
function requiredCredentialKeys(provider) {
|
|
47
48
|
if (provider === "anthropic")
|
|
@@ -81,36 +82,17 @@ function writeReadme(dir, purpose) {
|
|
|
81
82
|
fs.writeFileSync(readmePath, `# ${path.basename(dir)}\n\n${purpose}\n`, "utf-8");
|
|
82
83
|
}
|
|
83
84
|
}
|
|
84
|
-
function
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
function formatTaskStem(now) {
|
|
88
|
-
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}`;
|
|
89
|
-
}
|
|
90
|
-
function writeHeartbeatTask(bundleRoot, now) {
|
|
91
|
-
const habitsDir = path.join(bundleRoot, "tasks", "habits");
|
|
85
|
+
function writeHeartbeatHabit(bundleRoot, now) {
|
|
86
|
+
const habitsDir = path.join(bundleRoot, "habits");
|
|
92
87
|
fs.mkdirSync(habitsDir, { recursive: true });
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
"
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
"status: processing",
|
|
102
|
-
`created: ${iso}`,
|
|
103
|
-
`updated: ${iso}`,
|
|
104
|
-
"requester: system",
|
|
105
|
-
"validator: null",
|
|
106
|
-
"cadence: \"30m\"",
|
|
107
|
-
"scheduledAt: null",
|
|
108
|
-
"lastRun: null",
|
|
109
|
-
"---",
|
|
110
|
-
"",
|
|
111
|
-
"Run a lightweight heartbeat cycle. Review task board and inbox.",
|
|
112
|
-
"",
|
|
113
|
-
].join("\n");
|
|
88
|
+
const filePath = path.join(habitsDir, "heartbeat.md");
|
|
89
|
+
const content = (0, habit_parser_1.renderHabitFile)({
|
|
90
|
+
title: "Heartbeat check-in",
|
|
91
|
+
cadence: "30m",
|
|
92
|
+
status: "active",
|
|
93
|
+
lastRun: "null",
|
|
94
|
+
created: now.toISOString(),
|
|
95
|
+
}, "Run a lightweight heartbeat cycle. Review task board and inbox.\nCheck on pending obligations. Journal anything important.");
|
|
114
96
|
fs.writeFileSync(filePath, content, "utf-8");
|
|
115
97
|
}
|
|
116
98
|
function writeFriendImprint(bundleRoot, humanName, now) {
|
|
@@ -186,16 +168,16 @@ async function runHatchFlow(input, deps = {}) {
|
|
|
186
168
|
writeReadme(path.join(bundleRoot, "diary"), "Persistent diary — things I've learned and remember.");
|
|
187
169
|
writeReadme(path.join(bundleRoot, "friends"), "Known friend records.");
|
|
188
170
|
writeReadme(path.join(bundleRoot, "tasks"), "Task files.");
|
|
189
|
-
writeReadme(path.join(bundleRoot, "tasks", "habits"), "Recurring tasks.");
|
|
190
171
|
writeReadme(path.join(bundleRoot, "tasks", "one-shots"), "One-shot tasks.");
|
|
191
172
|
writeReadme(path.join(bundleRoot, "tasks", "ongoing"), "Ongoing tasks.");
|
|
173
|
+
writeReadme(path.join(bundleRoot, "habits"), "Recurring habits and autonomous rhythms.");
|
|
192
174
|
writeReadme(path.join(bundleRoot, "skills"), "Local skill files.");
|
|
193
175
|
writeReadme(path.join(bundleRoot, "senses"), "Sense-specific config.");
|
|
194
176
|
writeReadme(path.join(bundleRoot, "senses", "teams"), "Teams sense config.");
|
|
195
177
|
writeHatchlingAgentConfig(bundleRoot, input);
|
|
196
178
|
writeDiaryScaffold(bundleRoot);
|
|
197
179
|
writeFriendImprint(bundleRoot, input.humanName, now);
|
|
198
|
-
|
|
180
|
+
writeHeartbeatHabit(bundleRoot, now);
|
|
199
181
|
(0, runtime_1.emitNervesEvent)({
|
|
200
182
|
component: "daemon",
|
|
201
183
|
event: "daemon.hatch_flow_end",
|
|
@@ -0,0 +1,134 @@
|
|
|
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.createRealOsCronDeps = createRealOsCronDeps;
|
|
37
|
+
exports.createRealCrontabDeps = createRealCrontabDeps;
|
|
38
|
+
exports.resolveOuroBinaryPath = resolveOuroBinaryPath;
|
|
39
|
+
const child_process_1 = require("child_process");
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const os = __importStar(require("os"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
44
|
+
function createRealOsCronDeps() {
|
|
45
|
+
(0, runtime_1.emitNervesEvent)({
|
|
46
|
+
component: "daemon",
|
|
47
|
+
event: "daemon.os_cron_deps_created",
|
|
48
|
+
message: "created real OS cron deps",
|
|
49
|
+
meta: { platform: process.platform },
|
|
50
|
+
});
|
|
51
|
+
return {
|
|
52
|
+
exec: (cmd) => {
|
|
53
|
+
try {
|
|
54
|
+
(0, child_process_1.execSync)(cmd, { stdio: "ignore" });
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
/* best effort */
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
writeFile: (p, c) => fs.writeFileSync(p, c, "utf-8"),
|
|
61
|
+
removeFile: (p) => {
|
|
62
|
+
try {
|
|
63
|
+
fs.unlinkSync(p);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
/* best effort — file may already be gone */
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
existsFile: (p) => fs.existsSync(p),
|
|
70
|
+
listDir: (dir) => {
|
|
71
|
+
try {
|
|
72
|
+
return fs.readdirSync(dir);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
mkdirp: (dir) => fs.mkdirSync(dir, { recursive: true }),
|
|
79
|
+
homeDir: os.homedir(),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function createRealCrontabDeps() {
|
|
83
|
+
(0, runtime_1.emitNervesEvent)({
|
|
84
|
+
component: "daemon",
|
|
85
|
+
event: "daemon.crontab_deps_created",
|
|
86
|
+
message: "created real crontab deps",
|
|
87
|
+
meta: {},
|
|
88
|
+
});
|
|
89
|
+
/* v8 ignore start -- crontab exec closures: calling these in tests would modify the real system crontab @preserve */
|
|
90
|
+
return {
|
|
91
|
+
execOutput: (cmd) => (0, child_process_1.execSync)(cmd, { encoding: "utf-8" }),
|
|
92
|
+
execWrite: (cmd, stdin) => {
|
|
93
|
+
(0, child_process_1.execSync)(cmd, { input: stdin, stdio: ["pipe", "ignore", "ignore"] });
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
/* v8 ignore stop */
|
|
97
|
+
}
|
|
98
|
+
/* v8 ignore start -- ouro path resolution: probes process.argv, filesystem layout, and PATH; branches depend on install method and runtime environment @preserve */
|
|
99
|
+
function resolveOuroBinaryPath() {
|
|
100
|
+
// Try to resolve from process.argv[1] — the script being run
|
|
101
|
+
const scriptPath = process.argv[1];
|
|
102
|
+
if (scriptPath) {
|
|
103
|
+
// If running via node dist/heart/daemon/daemon-entry.js, resolve the ouro wrapper
|
|
104
|
+
// The ouro binary is typically at the package root's bin
|
|
105
|
+
const distDir = path.resolve(path.dirname(scriptPath));
|
|
106
|
+
const packageBin = path.resolve(distDir, "..", "..", "..", "node_modules", ".bin", "ouro");
|
|
107
|
+
if (fs.existsSync(packageBin)) {
|
|
108
|
+
return packageBin;
|
|
109
|
+
}
|
|
110
|
+
// Try the repo-local scripts/ouro.sh
|
|
111
|
+
const repoOuro = path.resolve(distDir, "..", "..", "..", "scripts", "ouro.sh");
|
|
112
|
+
if (fs.existsSync(repoOuro)) {
|
|
113
|
+
return repoOuro;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Try which ouro
|
|
117
|
+
try {
|
|
118
|
+
const result = (0, child_process_1.execSync)("which ouro", { encoding: "utf-8" }).trim();
|
|
119
|
+
if (result.length > 0)
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
/* not on PATH */
|
|
124
|
+
}
|
|
125
|
+
// Fallback: use "ouro" and rely on PATH
|
|
126
|
+
(0, runtime_1.emitNervesEvent)({
|
|
127
|
+
component: "daemon",
|
|
128
|
+
event: "daemon.ouro_path_fallback",
|
|
129
|
+
message: "could not resolve full ouro binary path, falling back to 'ouro'",
|
|
130
|
+
meta: {},
|
|
131
|
+
});
|
|
132
|
+
return "ouro";
|
|
133
|
+
}
|
|
134
|
+
/* v8 ignore stop */
|
|
@@ -110,8 +110,8 @@ function scaffoldBundle(bundleRoot) {
|
|
|
110
110
|
writeReadme(path.join(bundleRoot, "memory", "archive"), "Archived memory.");
|
|
111
111
|
writeReadme(path.join(bundleRoot, "friends"), "Known friend records.");
|
|
112
112
|
writeReadme(path.join(bundleRoot, "tasks"), "Task files.");
|
|
113
|
-
writeReadme(path.join(bundleRoot, "tasks", "habits"), "Recurring tasks.");
|
|
114
113
|
writeReadme(path.join(bundleRoot, "tasks", "one-shots"), "One-shot tasks.");
|
|
114
|
+
writeReadme(path.join(bundleRoot, "habits"), "Recurring habits and autonomous rhythms.");
|
|
115
115
|
writeReadme(path.join(bundleRoot, "tasks", "ongoing"), "Ongoing tasks.");
|
|
116
116
|
writeReadme(path.join(bundleRoot, "skills"), "Local skill files.");
|
|
117
117
|
writeReadme(path.join(bundleRoot, "senses"), "Sense-specific config.");
|
|
@@ -39,6 +39,7 @@ const path = __importStar(require("path"));
|
|
|
39
39
|
const identity_1 = require("../identity");
|
|
40
40
|
const runtime_1 = require("../../nerves/runtime");
|
|
41
41
|
const parser_1 = require("../../repertoire/tasks/parser");
|
|
42
|
+
const cadence_1 = require("./cadence");
|
|
42
43
|
function walkMarkdownFiles(root, readdirSync, existsSync, files) {
|
|
43
44
|
if (!existsSync(root))
|
|
44
45
|
return;
|
|
@@ -53,29 +54,6 @@ function walkMarkdownFiles(root, readdirSync, existsSync, files) {
|
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
|
-
function parseCadence(raw) {
|
|
57
|
-
if (typeof raw !== "string")
|
|
58
|
-
return null;
|
|
59
|
-
const value = raw.trim();
|
|
60
|
-
if (!value)
|
|
61
|
-
return null;
|
|
62
|
-
// Cron format (minute hour day month weekday)
|
|
63
|
-
if (/^\S+\s+\S+\s+\S+\s+\S+\s+\S+$/.test(value)) {
|
|
64
|
-
return value;
|
|
65
|
-
}
|
|
66
|
-
const cadenceMatch = /^(\d+)(m|h|d)$/.exec(value);
|
|
67
|
-
if (!cadenceMatch)
|
|
68
|
-
return null;
|
|
69
|
-
const interval = Number.parseInt(cadenceMatch[1], 10);
|
|
70
|
-
if (!Number.isFinite(interval) || interval <= 0)
|
|
71
|
-
return null;
|
|
72
|
-
const unit = cadenceMatch[2];
|
|
73
|
-
if (unit === "m")
|
|
74
|
-
return `*/${interval} * * * *`;
|
|
75
|
-
if (unit === "h")
|
|
76
|
-
return `0 */${interval} * * *`;
|
|
77
|
-
return `0 0 */${interval} * *`;
|
|
78
|
-
}
|
|
79
57
|
function parseScheduledAt(raw) {
|
|
80
58
|
if (typeof raw !== "string")
|
|
81
59
|
return null;
|
|
@@ -140,7 +118,7 @@ class TaskDrivenScheduler {
|
|
|
140
118
|
const nextTaskPaths = new Map();
|
|
141
119
|
for (const agent of this.agents) {
|
|
142
120
|
const taskRoot = path.join(this.bundlesRoot, `${agent}.ouro`, "tasks");
|
|
143
|
-
const collections = ["one-shots", "ongoing"
|
|
121
|
+
const collections = ["one-shots", "ongoing"];
|
|
144
122
|
const files = [];
|
|
145
123
|
for (const collection of collections) {
|
|
146
124
|
walkMarkdownFiles(path.join(taskRoot, collection), this.readdirSync, this.existsSync, files);
|
|
@@ -157,7 +135,7 @@ class TaskDrivenScheduler {
|
|
|
157
135
|
nextTaskPaths.set(`${agent}:${taskId}`, filePath);
|
|
158
136
|
if (task.status === "done")
|
|
159
137
|
continue;
|
|
160
|
-
const cadence =
|
|
138
|
+
const cadence = (0, cadence_1.parseCadenceToCron)(task.frontmatter.cadence);
|
|
161
139
|
if (cadence) {
|
|
162
140
|
const id = `${agent}:${taskId}:cadence`;
|
|
163
141
|
nextJobs.set(id, {
|
package/dist/mind/prompt.js
CHANGED
|
@@ -721,11 +721,15 @@ when a thought is ready to share, i surface it outward.
|
|
|
721
721
|
when i need to go deeper, i ponder.
|
|
722
722
|
when i'm done thinking and the attention queue is clear, i rest.
|
|
723
723
|
|
|
724
|
-
my
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
724
|
+
my habits live at habits/ — they're my autonomous rhythms. heartbeat
|
|
725
|
+
is my breathing, other habits are patterns i choose. i can read, create,
|
|
726
|
+
and modify them with read_file/write_file. the format is simple
|
|
727
|
+
frontmatter (title, cadence, status, lastRun, created) plus a body
|
|
728
|
+
that says what i do when the rhythm fires.
|
|
729
|
+
|
|
730
|
+
\`ouro habit list\` shows my current habits. \`ouro habit create\` makes
|
|
731
|
+
a new one. the cadence is personal — how often do i want each rhythm
|
|
732
|
+
to turn? that's mine to shape.
|
|
729
733
|
|
|
730
734
|
same for my diary — if i'm still on psyche/memory/ instead of diary/,
|
|
731
735
|
i can migrate myself. and if journal/ doesn't exist yet, i create it
|
|
@@ -96,8 +96,6 @@ function collectionFromPath(taskPath, type) {
|
|
|
96
96
|
return "one-shots";
|
|
97
97
|
if (parts.includes("ongoing"))
|
|
98
98
|
return "ongoing";
|
|
99
|
-
if (parts.includes("habits"))
|
|
100
|
-
return "habits";
|
|
101
99
|
return (0, transitions_1.canonicalCollectionForTaskType)(type);
|
|
102
100
|
}
|
|
103
101
|
function parseTaskFile(content, filePath) {
|
|
@@ -29,17 +29,14 @@ exports.TASK_STATUS_TRANSITIONS = {
|
|
|
29
29
|
exports.TASK_CANONICAL_TYPES = [
|
|
30
30
|
"one-shot",
|
|
31
31
|
"ongoing",
|
|
32
|
-
"habit",
|
|
33
32
|
];
|
|
34
33
|
exports.TASK_CANONICAL_COLLECTIONS = [
|
|
35
34
|
"one-shots",
|
|
36
35
|
"ongoing",
|
|
37
|
-
"habits",
|
|
38
36
|
];
|
|
39
37
|
exports.TASK_TYPE_TO_COLLECTION = {
|
|
40
38
|
"one-shot": "one-shots",
|
|
41
39
|
ongoing: "ongoing",
|
|
42
|
-
habit: "habits",
|
|
43
40
|
};
|
|
44
41
|
exports.TASK_RESERVED_DIRECTORIES = ["templates", ".trash", "archive"];
|
|
45
42
|
exports.TASK_FILENAME_PATTERN = /^\d{4}-\d{2}-\d{2}-\d{4}-[a-z0-9][a-z0-9-]*\.md$/;
|
|
@@ -76,21 +73,6 @@ exports.TASK_REQUIRED_TEMPLATE_FIELDS = {
|
|
|
76
73
|
"child_tasks",
|
|
77
74
|
"artifacts",
|
|
78
75
|
],
|
|
79
|
-
habit: [
|
|
80
|
-
"type",
|
|
81
|
-
"category",
|
|
82
|
-
"title",
|
|
83
|
-
"status",
|
|
84
|
-
"validator",
|
|
85
|
-
"requester",
|
|
86
|
-
"cadence",
|
|
87
|
-
"scheduledAt",
|
|
88
|
-
"lastRun",
|
|
89
|
-
"created",
|
|
90
|
-
"updated",
|
|
91
|
-
"child_tasks",
|
|
92
|
-
"artifacts",
|
|
93
|
-
],
|
|
94
76
|
};
|
|
95
77
|
function canonicalCollectionForTaskType(type) {
|
|
96
78
|
return exports.TASK_TYPE_TO_COLLECTION[type];
|
|
@@ -26,7 +26,7 @@ function formatRelativeTime(nowMs, mtimeMs) {
|
|
|
26
26
|
return `${days} ${days === 1 ? "day" : "days"} ago`;
|
|
27
27
|
}
|
|
28
28
|
function buildContextualHeartbeat(options) {
|
|
29
|
-
const { lastCompletedAt, pendingObligations, lastSurfaceAt, checkpoint, now, readJournalDir, } = options;
|
|
29
|
+
const { lastCompletedAt, pendingObligations, lastSurfaceAt, checkpoint, now, readJournalDir, habitBody, } = options;
|
|
30
30
|
const nowDate = now();
|
|
31
31
|
const nowMs = nowDate.getTime();
|
|
32
32
|
const journalFiles = readJournalDir();
|
|
@@ -51,6 +51,10 @@ function buildContextualHeartbeat(options) {
|
|
|
51
51
|
const elapsed = nowMs - lastMs;
|
|
52
52
|
sections.push(`it's been ${formatElapsed(elapsed)} since your last turn.`);
|
|
53
53
|
}
|
|
54
|
+
// Habit body (instructions from habit file)
|
|
55
|
+
if (habitBody) {
|
|
56
|
+
sections.push(habitBody);
|
|
57
|
+
}
|
|
54
58
|
// Pending attention count
|
|
55
59
|
if (pendingObligations.length > 0) {
|
|
56
60
|
const count = pendingObligations.length;
|