@ouro.bot/cli 0.1.0-alpha.146 → 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 +27 -0
- package/dist/heart/core.js +2 -2
- 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 +44 -5
- package/dist/repertoire/tasks/parser.js +0 -2
- package/dist/repertoire/tasks/transitions.js +0 -18
- package/dist/repertoire/tools-base.js +1 -1
- package/dist/senses/bluebubbles.js +22 -1
- 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/dist/senses/teams.js +190 -13
- package/package.json +1 -1
- package/dist/heart/daemon/heartbeat-timer.js +0 -132
|
@@ -0,0 +1,119 @@
|
|
|
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.parseHabitFile = parseHabitFile;
|
|
37
|
+
exports.renderHabitFile = renderHabitFile;
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const parser_1 = require("../../repertoire/tasks/parser");
|
|
40
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
41
|
+
function isHabitStatus(value) {
|
|
42
|
+
return value === "active" || value === "paused";
|
|
43
|
+
}
|
|
44
|
+
function extractFrontmatterAndBody(content) {
|
|
45
|
+
const lines = content.split(/\r?\n/);
|
|
46
|
+
if (lines[0]?.trim() !== "---") {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const closing = lines.findIndex((line, index) => index > 0 && line.trim() === "---");
|
|
50
|
+
if (closing === -1) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const rawFrontmatter = lines.slice(1, closing).join("\n");
|
|
54
|
+
const body = lines.slice(closing + 1).join("\n").trim();
|
|
55
|
+
return { frontmatter: (0, parser_1.parseFrontmatter)(rawFrontmatter), body };
|
|
56
|
+
}
|
|
57
|
+
function parseHabitFile(content, filePath) {
|
|
58
|
+
(0, runtime_1.emitNervesEvent)({
|
|
59
|
+
event: "daemon.habit_parse",
|
|
60
|
+
component: "daemon",
|
|
61
|
+
message: "parsing habit file",
|
|
62
|
+
meta: { filePath },
|
|
63
|
+
});
|
|
64
|
+
const stem = path.basename(filePath, ".md");
|
|
65
|
+
const parsed = extractFrontmatterAndBody(content);
|
|
66
|
+
if (!parsed) {
|
|
67
|
+
return {
|
|
68
|
+
name: stem,
|
|
69
|
+
title: stem,
|
|
70
|
+
cadence: null,
|
|
71
|
+
status: "active",
|
|
72
|
+
lastRun: null,
|
|
73
|
+
created: null,
|
|
74
|
+
body: content.trim(),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const { frontmatter, body } = parsed;
|
|
78
|
+
const rawTitle = frontmatter.title;
|
|
79
|
+
const title = typeof rawTitle === "string" && rawTitle.length > 0 ? rawTitle : stem;
|
|
80
|
+
const rawCadence = frontmatter.cadence;
|
|
81
|
+
const cadence = typeof rawCadence === "string" && rawCadence.length > 0 ? rawCadence : null;
|
|
82
|
+
const rawStatus = frontmatter.status;
|
|
83
|
+
const status = typeof rawStatus === "string" && isHabitStatus(rawStatus) ? rawStatus : "active";
|
|
84
|
+
const rawLastRun = frontmatter.lastRun;
|
|
85
|
+
const lastRun = typeof rawLastRun === "string" && rawLastRun.length > 0 ? rawLastRun : null;
|
|
86
|
+
const rawCreated = frontmatter.created;
|
|
87
|
+
const created = typeof rawCreated === "string" && rawCreated.length > 0 ? rawCreated : null;
|
|
88
|
+
return {
|
|
89
|
+
name: stem,
|
|
90
|
+
title,
|
|
91
|
+
cadence,
|
|
92
|
+
status,
|
|
93
|
+
lastRun,
|
|
94
|
+
created,
|
|
95
|
+
body,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function formatFrontmatterValue(value) {
|
|
99
|
+
if (value === null || value === undefined)
|
|
100
|
+
return "null";
|
|
101
|
+
return String(value);
|
|
102
|
+
}
|
|
103
|
+
function renderHabitFile(frontmatter, body) {
|
|
104
|
+
(0, runtime_1.emitNervesEvent)({
|
|
105
|
+
event: "daemon.habit_render",
|
|
106
|
+
component: "daemon",
|
|
107
|
+
message: "rendering habit file",
|
|
108
|
+
meta: {},
|
|
109
|
+
});
|
|
110
|
+
const lines = ["---"];
|
|
111
|
+
for (const key of Object.keys(frontmatter)) {
|
|
112
|
+
lines.push(`${key}: ${formatFrontmatterValue(frontmatter[key])}`);
|
|
113
|
+
}
|
|
114
|
+
lines.push("---");
|
|
115
|
+
lines.push("");
|
|
116
|
+
lines.push(body.trim());
|
|
117
|
+
lines.push("");
|
|
118
|
+
return lines.join("\n");
|
|
119
|
+
}
|
|
@@ -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, {
|