@ouro.bot/cli 0.1.0-alpha.4 → 0.1.0-alpha.41

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.
Files changed (84) hide show
  1. package/AdoptionSpecialist.ouro/agent.json +70 -9
  2. package/AdoptionSpecialist.ouro/psyche/SOUL.md +5 -2
  3. package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
  4. package/README.md +117 -188
  5. package/assets/ouroboros.png +0 -0
  6. package/changelog.json +170 -0
  7. package/dist/heart/config.js +81 -8
  8. package/dist/heart/core.js +78 -45
  9. package/dist/heart/daemon/agent-discovery.js +81 -0
  10. package/dist/heart/daemon/daemon-cli.js +987 -77
  11. package/dist/heart/daemon/daemon-entry.js +14 -5
  12. package/dist/heart/daemon/daemon-runtime-sync.js +90 -0
  13. package/dist/heart/daemon/daemon.js +177 -9
  14. package/dist/heart/daemon/hatch-animation.js +35 -0
  15. package/dist/heart/daemon/hatch-flow.js +4 -20
  16. package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
  17. package/dist/heart/daemon/launchd.js +134 -0
  18. package/dist/heart/daemon/message-router.js +15 -6
  19. package/dist/heart/daemon/ouro-bot-entry.js +0 -0
  20. package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
  21. package/dist/heart/daemon/ouro-entry.js +0 -0
  22. package/dist/heart/daemon/ouro-path-installer.js +178 -0
  23. package/dist/heart/daemon/ouro-uti.js +11 -2
  24. package/dist/heart/daemon/process-manager.js +1 -1
  25. package/dist/heart/daemon/run-hooks.js +37 -0
  26. package/dist/heart/daemon/runtime-metadata.js +118 -0
  27. package/dist/heart/daemon/sense-manager.js +266 -0
  28. package/dist/heart/daemon/specialist-orchestrator.js +129 -0
  29. package/dist/heart/daemon/specialist-prompt.js +99 -0
  30. package/dist/heart/daemon/specialist-tools.js +283 -0
  31. package/dist/heart/daemon/staged-restart.js +114 -0
  32. package/dist/heart/daemon/subagent-installer.js +10 -1
  33. package/dist/heart/daemon/update-checker.js +111 -0
  34. package/dist/heart/daemon/update-hooks.js +138 -0
  35. package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
  36. package/dist/heart/identity.js +96 -4
  37. package/dist/heart/kicks.js +1 -19
  38. package/dist/heart/providers/anthropic.js +16 -2
  39. package/dist/heart/sense-truth.js +61 -0
  40. package/dist/heart/streaming.js +96 -21
  41. package/dist/mind/bundle-manifest.js +70 -0
  42. package/dist/mind/context.js +7 -7
  43. package/dist/mind/first-impressions.js +2 -1
  44. package/dist/mind/friends/channel.js +43 -0
  45. package/dist/mind/friends/store-file.js +19 -0
  46. package/dist/mind/friends/types.js +9 -1
  47. package/dist/mind/memory.js +10 -3
  48. package/dist/mind/pending.js +10 -2
  49. package/dist/mind/phrases.js +1 -0
  50. package/dist/mind/prompt.js +222 -7
  51. package/dist/mind/token-estimate.js +8 -12
  52. package/dist/nerves/cli-logging.js +15 -2
  53. package/dist/repertoire/ado-client.js +4 -2
  54. package/dist/repertoire/coding/feedback.js +134 -0
  55. package/dist/repertoire/coding/index.js +4 -1
  56. package/dist/repertoire/coding/manager.js +62 -4
  57. package/dist/repertoire/coding/spawner.js +3 -3
  58. package/dist/repertoire/coding/tools.js +41 -2
  59. package/dist/repertoire/data/ado-endpoints.json +188 -0
  60. package/dist/repertoire/tasks/index.js +2 -9
  61. package/dist/repertoire/tasks/transitions.js +1 -2
  62. package/dist/repertoire/tools-base.js +202 -219
  63. package/dist/repertoire/tools-bluebubbles.js +93 -0
  64. package/dist/repertoire/tools-teams.js +58 -25
  65. package/dist/repertoire/tools.js +55 -35
  66. package/dist/senses/bluebubbles-client.js +434 -0
  67. package/dist/senses/bluebubbles-entry.js +11 -0
  68. package/dist/senses/bluebubbles-media.js +338 -0
  69. package/dist/senses/bluebubbles-model.js +261 -0
  70. package/dist/senses/bluebubbles-mutation-log.js +74 -0
  71. package/dist/senses/bluebubbles-session-cleanup.js +72 -0
  72. package/dist/senses/bluebubbles.js +832 -0
  73. package/dist/senses/cli.js +327 -138
  74. package/dist/senses/debug-activity.js +127 -0
  75. package/dist/senses/inner-dialog.js +103 -55
  76. package/dist/senses/pipeline.js +124 -0
  77. package/dist/senses/teams.js +427 -112
  78. package/dist/senses/trust-gate.js +112 -2
  79. package/package.json +14 -3
  80. package/subagents/README.md +40 -53
  81. package/subagents/work-doer.md +26 -24
  82. package/subagents/work-merger.md +24 -30
  83. package/subagents/work-planner.md +34 -25
  84. package/dist/inner-worker-entry.js +0 -4
@@ -0,0 +1,283 @@
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.getSpecialistTools = getSpecialistTools;
37
+ exports.createSpecialistExecTool = createSpecialistExecTool;
38
+ const crypto = __importStar(require("crypto"));
39
+ const fs = __importStar(require("fs"));
40
+ const path = __importStar(require("path"));
41
+ const tools_base_1 = require("../../repertoire/tools-base");
42
+ const hatch_flow_1 = require("./hatch-flow");
43
+ const hatch_animation_1 = require("./hatch-animation");
44
+ const bundle_manifest_1 = require("../../mind/bundle-manifest");
45
+ const runtime_1 = require("../../nerves/runtime");
46
+ const completeAdoptionTool = {
47
+ type: "function",
48
+ function: {
49
+ name: "complete_adoption",
50
+ description: "finalize the agent bundle and hatch the new agent. call this only when you have written all 5 psyche files and agent.json to the temp directory, and the human has approved the bundle.",
51
+ parameters: {
52
+ type: "object",
53
+ properties: {
54
+ name: {
55
+ type: "string",
56
+ description: "the PascalCase name for the new agent (e.g. 'Slugger')",
57
+ },
58
+ handoff_message: {
59
+ type: "string",
60
+ description: "a warm handoff message to display to the human after the agent is hatched",
61
+ },
62
+ phone: {
63
+ type: "string",
64
+ description: "the human's phone number (optional, for iMessage contact recognition)",
65
+ },
66
+ teams_handle: {
67
+ type: "string",
68
+ description: "the human's Teams email/handle (optional, for Teams contact recognition)",
69
+ },
70
+ },
71
+ required: ["name", "handoff_message"],
72
+ },
73
+ },
74
+ };
75
+ const readFileTool = tools_base_1.baseToolDefinitions.find((d) => d.tool.function.name === "read_file");
76
+ const writeFileTool = tools_base_1.baseToolDefinitions.find((d) => d.tool.function.name === "write_file");
77
+ const listDirToolSchema = {
78
+ type: "function",
79
+ function: {
80
+ name: "list_directory",
81
+ description: "list directory contents",
82
+ parameters: {
83
+ type: "object",
84
+ properties: { path: { type: "string" } },
85
+ required: ["path"],
86
+ },
87
+ },
88
+ };
89
+ /**
90
+ * Returns the specialist's tool schema array.
91
+ */
92
+ function getSpecialistTools() {
93
+ return [completeAdoptionTool, tools_base_1.finalAnswerTool, readFileTool.tool, writeFileTool.tool, listDirToolSchema];
94
+ }
95
+ const PSYCHE_FILES = ["SOUL.md", "IDENTITY.md", "LORE.md", "TACIT.md", "ASPIRATIONS.md"];
96
+ function isPascalCase(name) {
97
+ return /^[A-Z][a-zA-Z0-9]*$/.test(name);
98
+ }
99
+ function writeReadme(dir, purpose) {
100
+ fs.mkdirSync(dir, { recursive: true });
101
+ const readmePath = path.join(dir, "README.md");
102
+ /* v8 ignore next -- defensive: guard against re-scaffold on existing bundle @preserve */
103
+ if (!fs.existsSync(readmePath)) {
104
+ fs.writeFileSync(readmePath, `# ${path.basename(dir)}\n\n${purpose}\n`, "utf-8");
105
+ }
106
+ }
107
+ function scaffoldBundle(bundleRoot) {
108
+ writeReadme(path.join(bundleRoot, "memory"), "Persistent memory store.");
109
+ writeReadme(path.join(bundleRoot, "memory", "daily"), "Daily memory entries.");
110
+ writeReadme(path.join(bundleRoot, "memory", "archive"), "Archived memory.");
111
+ writeReadme(path.join(bundleRoot, "friends"), "Known friend records.");
112
+ writeReadme(path.join(bundleRoot, "tasks"), "Task files.");
113
+ writeReadme(path.join(bundleRoot, "tasks", "habits"), "Recurring tasks.");
114
+ writeReadme(path.join(bundleRoot, "tasks", "one-shots"), "One-shot tasks.");
115
+ writeReadme(path.join(bundleRoot, "tasks", "ongoing"), "Ongoing tasks.");
116
+ writeReadme(path.join(bundleRoot, "skills"), "Local skill files.");
117
+ writeReadme(path.join(bundleRoot, "senses"), "Sense-specific config.");
118
+ writeReadme(path.join(bundleRoot, "senses", "teams"), "Teams sense config.");
119
+ // Memory scaffold files
120
+ const memoryRoot = path.join(bundleRoot, "memory");
121
+ const factsPath = path.join(memoryRoot, "facts.jsonl");
122
+ const entitiesPath = path.join(memoryRoot, "entities.json");
123
+ /* v8 ignore next -- defensive: guard against re-scaffold on existing bundle @preserve */
124
+ if (!fs.existsSync(factsPath))
125
+ fs.writeFileSync(factsPath, "", "utf-8");
126
+ /* v8 ignore next -- defensive: guard against re-scaffold on existing bundle @preserve */
127
+ if (!fs.existsSync(entitiesPath))
128
+ fs.writeFileSync(entitiesPath, "{}\n", "utf-8");
129
+ // bundle-meta.json
130
+ const meta = (0, bundle_manifest_1.createBundleMeta)();
131
+ fs.writeFileSync(path.join(bundleRoot, "bundle-meta.json"), JSON.stringify(meta, null, 2) + "\n", "utf-8");
132
+ }
133
+ function moveDir(src, dest) {
134
+ try {
135
+ fs.renameSync(src, dest);
136
+ }
137
+ catch {
138
+ /* v8 ignore start -- cross-device fallback: only triggers on EXDEV (e.g. /tmp → different mount), untestable in CI @preserve */
139
+ fs.cpSync(src, dest, { recursive: true });
140
+ fs.rmSync(src, { recursive: true, force: true });
141
+ /* v8 ignore stop */
142
+ }
143
+ }
144
+ async function execCompleteAdoption(args, deps) {
145
+ const name = args.name;
146
+ const handoffMessage = args.handoff_message;
147
+ if (!name) {
148
+ return "error: missing required 'name' parameter";
149
+ }
150
+ if (!isPascalCase(name)) {
151
+ return `error: name '${name}' must be PascalCase (e.g. 'Slugger', 'MyAgent')`;
152
+ }
153
+ // Validate psyche files exist
154
+ const psycheDir = path.join(deps.tempDir, "psyche");
155
+ const missingPsyche = PSYCHE_FILES.filter((f) => !fs.existsSync(path.join(psycheDir, f)));
156
+ if (missingPsyche.length > 0) {
157
+ return `error: missing psyche files in temp directory: ${missingPsyche.join(", ")}. write them first using write_file.`;
158
+ }
159
+ // Validate agent.json exists
160
+ const agentJsonPath = path.join(deps.tempDir, "agent.json");
161
+ if (!fs.existsSync(agentJsonPath)) {
162
+ return "error: agent.json not found in temp directory. write it first using write_file.";
163
+ }
164
+ // Validate target doesn't exist
165
+ const targetBundle = path.join(deps.bundlesRoot, `${name}.ouro`);
166
+ if (fs.existsSync(targetBundle)) {
167
+ return `error: bundle '${name}.ouro' already exists at ${deps.bundlesRoot}. choose a different name.`;
168
+ }
169
+ // Scaffold structural dirs into tempDir
170
+ scaffoldBundle(deps.tempDir);
171
+ // Move tempDir -> final bundle location
172
+ moveDir(deps.tempDir, targetBundle);
173
+ // Write secrets
174
+ try {
175
+ (0, hatch_flow_1.writeSecretsFile)(name, deps.provider, deps.credentials, deps.secretsRoot);
176
+ }
177
+ catch (e) {
178
+ // Rollback: remove the moved bundle
179
+ try {
180
+ fs.rmSync(targetBundle, { recursive: true, force: true });
181
+ }
182
+ catch {
183
+ // Best effort cleanup
184
+ }
185
+ return `error: failed to write secrets: ${e instanceof Error ? e.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(e)}`;
186
+ }
187
+ // Create initial friend record if contact info provided
188
+ const phone = args.phone;
189
+ const teamsHandle = args.teams_handle;
190
+ if (phone || teamsHandle) {
191
+ const friendId = crypto.randomUUID();
192
+ const now = new Date().toISOString();
193
+ const externalIds = [];
194
+ if (phone)
195
+ externalIds.push({ provider: "imessage-handle", externalId: phone, linkedAt: now });
196
+ if (teamsHandle)
197
+ externalIds.push({ provider: "aad", externalId: teamsHandle, linkedAt: now });
198
+ const friendRecord = {
199
+ id: friendId,
200
+ name: deps.humanName ?? "primary",
201
+ trustLevel: "family",
202
+ externalIds,
203
+ tenantMemberships: [],
204
+ toolPreferences: {},
205
+ notes: {},
206
+ createdAt: now,
207
+ updatedAt: now,
208
+ schemaVersion: 1,
209
+ };
210
+ const friendPath = path.join(targetBundle, "friends", `${friendId}.json`);
211
+ fs.writeFileSync(friendPath, JSON.stringify(friendRecord, null, 2), "utf-8");
212
+ }
213
+ // Play hatch animation
214
+ await (0, hatch_animation_1.playHatchAnimation)(name, deps.animationWriter);
215
+ // Display handoff message
216
+ /* v8 ignore next -- UI-only: handoff message display, covered by integration @preserve */
217
+ if (handoffMessage && deps.animationWriter) {
218
+ deps.animationWriter(`\n${handoffMessage}\n`);
219
+ }
220
+ (0, runtime_1.emitNervesEvent)({
221
+ component: "daemon",
222
+ event: "daemon.adoption_complete",
223
+ message: "adoption completed successfully",
224
+ meta: { agentName: name, bundlePath: targetBundle },
225
+ });
226
+ return JSON.stringify({ success: true, agentName: name, bundlePath: targetBundle });
227
+ }
228
+ /**
229
+ * Create a specialist tool executor with the given dependencies captured in closure.
230
+ */
231
+ function createSpecialistExecTool(deps) {
232
+ (0, runtime_1.emitNervesEvent)({
233
+ component: "daemon",
234
+ event: "daemon.specialist_exec_tool_created",
235
+ message: "specialist exec tool created",
236
+ meta: { tempDir: deps.tempDir },
237
+ });
238
+ return async (name, args) => {
239
+ (0, runtime_1.emitNervesEvent)({
240
+ component: "daemon",
241
+ event: "daemon.specialist_tool_exec",
242
+ message: "executing specialist tool",
243
+ meta: { tool: name },
244
+ });
245
+ if (name === "complete_adoption") {
246
+ return execCompleteAdoption(args, deps);
247
+ }
248
+ if (name === "read_file") {
249
+ try {
250
+ return fs.readFileSync(args.path, "utf-8");
251
+ }
252
+ catch (e) {
253
+ return `error: ${e instanceof Error ? e.message : /* v8 ignore next -- defensive @preserve */ String(e)}`;
254
+ }
255
+ }
256
+ if (name === "write_file") {
257
+ try {
258
+ const dir = path.dirname(args.path);
259
+ fs.mkdirSync(dir, { recursive: true });
260
+ const content = typeof args.content === "string"
261
+ ? args.content
262
+ : JSON.stringify(args.content, null, 2);
263
+ fs.writeFileSync(args.path, content, "utf-8");
264
+ return `wrote ${args.path}`;
265
+ }
266
+ catch (e) {
267
+ return `error: ${e instanceof Error ? e.message : /* v8 ignore next -- defensive @preserve */ String(e)}`;
268
+ }
269
+ }
270
+ if (name === "list_directory") {
271
+ try {
272
+ return fs
273
+ .readdirSync(args.path, { withFileTypes: true })
274
+ .map((e) => `${e.isDirectory() ? "d" : "-"} ${e.name}`)
275
+ .join("\n");
276
+ }
277
+ catch (e) {
278
+ return `error: ${e instanceof Error ? e.message : /* v8 ignore next -- defensive @preserve */ String(e)}`;
279
+ }
280
+ }
281
+ return `error: unknown tool '${name}'`;
282
+ };
283
+ }
@@ -0,0 +1,114 @@
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.performStagedRestart = performStagedRestart;
37
+ const path = __importStar(require("path"));
38
+ const runtime_1 = require("../../nerves/runtime");
39
+ async function performStagedRestart(version, deps) {
40
+ (0, runtime_1.emitNervesEvent)({
41
+ component: "daemon",
42
+ event: "daemon.staged_restart_start",
43
+ message: "starting staged restart",
44
+ meta: { version },
45
+ });
46
+ // Step 1: Install new version
47
+ try {
48
+ deps.execSync(`npm install -g @ouro.bot/cli@${version}`);
49
+ }
50
+ catch (err) {
51
+ const errorMessage = err instanceof Error ? err.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(err);
52
+ (0, runtime_1.emitNervesEvent)({
53
+ component: "daemon",
54
+ event: "daemon.staged_restart_install_failed",
55
+ message: "npm install failed",
56
+ meta: { version, error: errorMessage },
57
+ });
58
+ return { ok: false, error: errorMessage };
59
+ }
60
+ // Step 2: Resolve new code path
61
+ const newCodePath = deps.resolveNewCodePath(version);
62
+ if (!newCodePath) {
63
+ (0, runtime_1.emitNervesEvent)({
64
+ component: "daemon",
65
+ event: "daemon.staged_restart_path_failed",
66
+ message: "could not resolve new code path",
67
+ meta: { version },
68
+ });
69
+ return { ok: false, error: "could not resolve new code path after install" };
70
+ }
71
+ // Step 3: Spawn hook runner on NEW code
72
+ const hookRunnerPath = path.join(newCodePath, "dist", "heart", "daemon", "run-hooks.js");
73
+ const spawnResult = deps.spawnSync(deps.nodePath, [hookRunnerPath, "--bundles-root", deps.bundlesRoot], { stdio: "inherit" });
74
+ if (spawnResult.error) {
75
+ const errorMessage = spawnResult.error.message;
76
+ (0, runtime_1.emitNervesEvent)({
77
+ component: "daemon",
78
+ event: "daemon.staged_restart_spawn_failed",
79
+ message: "hook runner spawn failed",
80
+ meta: { version, error: errorMessage },
81
+ });
82
+ return { ok: false, error: errorMessage };
83
+ }
84
+ if (spawnResult.status !== 0) {
85
+ (0, runtime_1.emitNervesEvent)({
86
+ component: "daemon",
87
+ event: "daemon.staged_restart_hooks_failed",
88
+ message: "hook runner exited with non-zero status",
89
+ meta: { version, exitCode: spawnResult.status },
90
+ });
91
+ return { ok: false, error: `hook runner exited with code ${spawnResult.status}` };
92
+ }
93
+ // Step 4: Graceful shutdown (launchd will restart with new code)
94
+ (0, runtime_1.emitNervesEvent)({
95
+ component: "daemon",
96
+ event: "daemon.staged_restart_hooks_passed",
97
+ message: "hooks passed, shutting down for restart",
98
+ meta: { version },
99
+ });
100
+ try {
101
+ await deps.gracefulShutdown();
102
+ }
103
+ catch (err) {
104
+ const shutdownError = err instanceof Error ? err.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(err);
105
+ (0, runtime_1.emitNervesEvent)({
106
+ component: "daemon",
107
+ event: "daemon.staged_restart_shutdown_error",
108
+ message: "graceful shutdown encountered error",
109
+ meta: { version, error: shutdownError },
110
+ });
111
+ return { ok: true, shutdownError };
112
+ }
113
+ return { ok: true };
114
+ }
@@ -55,9 +55,18 @@ function listSubagentSources(subagentsDir) {
55
55
  .map((name) => path.join(subagentsDir, name))
56
56
  .sort((a, b) => a.localeCompare(b));
57
57
  }
58
+ function pathExists(target) {
59
+ try {
60
+ fs.lstatSync(target);
61
+ return true;
62
+ }
63
+ catch {
64
+ return false;
65
+ }
66
+ }
58
67
  function ensureSymlink(source, target) {
59
68
  fs.mkdirSync(path.dirname(target), { recursive: true });
60
- if (fs.existsSync(target)) {
69
+ if (pathExists(target)) {
61
70
  const stats = fs.lstatSync(target);
62
71
  if (stats.isSymbolicLink()) {
63
72
  const linkedPath = fs.readlinkSync(target);
@@ -0,0 +1,111 @@
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.checkForUpdate = checkForUpdate;
37
+ exports.startUpdateChecker = startUpdateChecker;
38
+ exports.stopUpdateChecker = stopUpdateChecker;
39
+ const semver = __importStar(require("semver"));
40
+ const runtime_1 = require("../../nerves/runtime");
41
+ const DEFAULT_INTERVAL_MS = 30 * 60 * 1000; // 30 minutes
42
+ async function checkForUpdate(currentVersion, deps) {
43
+ (0, runtime_1.emitNervesEvent)({
44
+ component: "daemon",
45
+ event: "daemon.update_check",
46
+ message: "checking for update",
47
+ meta: { currentVersion, distTag: deps.distTag },
48
+ });
49
+ let registryData;
50
+ try {
51
+ registryData = (await deps.fetchRegistryJson());
52
+ }
53
+ catch (err) {
54
+ const errorMessage = err instanceof Error ? err.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(err);
55
+ return { available: false, error: errorMessage };
56
+ }
57
+ const distTags = registryData?.["dist-tags"];
58
+ if (!distTags) {
59
+ return { available: false, error: "registry response missing dist-tags" };
60
+ }
61
+ const latestVersion = distTags[deps.distTag];
62
+ if (!latestVersion) {
63
+ return { available: false, error: `dist-tag "${deps.distTag}" not found in registry` };
64
+ }
65
+ const available = semver.gt(latestVersion, currentVersion);
66
+ (0, runtime_1.emitNervesEvent)({
67
+ component: "daemon",
68
+ event: "daemon.update_check_result",
69
+ message: available ? "update available" : "no update available",
70
+ meta: { currentVersion, latestVersion, available },
71
+ });
72
+ return { available, latestVersion };
73
+ }
74
+ let _intervalId = null;
75
+ function startUpdateChecker(options) {
76
+ const intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;
77
+ (0, runtime_1.emitNervesEvent)({
78
+ component: "daemon",
79
+ event: "daemon.update_checker_start",
80
+ message: "starting update checker",
81
+ meta: { intervalMs, currentVersion: options.currentVersion },
82
+ });
83
+ _intervalId = setInterval(() => {
84
+ void (async () => {
85
+ const result = await checkForUpdate(options.currentVersion, options.deps);
86
+ if (result.available && options.onUpdate) {
87
+ await options.onUpdate(result);
88
+ }
89
+ })().catch((err) => {
90
+ (0, runtime_1.emitNervesEvent)({
91
+ component: "daemon",
92
+ event: "daemon.update_checker_error",
93
+ level: "warn",
94
+ message: "update checker tick failed",
95
+ meta: { reason: err instanceof Error ? err.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(err) },
96
+ });
97
+ });
98
+ }, intervalMs);
99
+ }
100
+ function stopUpdateChecker() {
101
+ if (_intervalId) {
102
+ clearInterval(_intervalId);
103
+ _intervalId = null;
104
+ }
105
+ (0, runtime_1.emitNervesEvent)({
106
+ component: "daemon",
107
+ event: "daemon.update_checker_stop",
108
+ message: "stopping update checker",
109
+ meta: {},
110
+ });
111
+ }
@@ -0,0 +1,138 @@
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.registerUpdateHook = registerUpdateHook;
37
+ exports.getRegisteredHooks = getRegisteredHooks;
38
+ exports.clearRegisteredHooks = clearRegisteredHooks;
39
+ exports.applyPendingUpdates = applyPendingUpdates;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const semver = __importStar(require("semver"));
43
+ const runtime_1 = require("../../nerves/runtime");
44
+ const _hooks = [];
45
+ function registerUpdateHook(hook) {
46
+ _hooks.push(hook);
47
+ (0, runtime_1.emitNervesEvent)({
48
+ component: "daemon",
49
+ event: "daemon.update_hook_registered",
50
+ message: "registered update hook",
51
+ meta: {},
52
+ });
53
+ }
54
+ function getRegisteredHooks() {
55
+ return _hooks;
56
+ }
57
+ function clearRegisteredHooks() {
58
+ _hooks.length = 0;
59
+ }
60
+ async function applyPendingUpdates(bundlesRoot, currentVersion) {
61
+ const summary = { updated: [] };
62
+ (0, runtime_1.emitNervesEvent)({
63
+ component: "daemon",
64
+ event: "daemon.apply_pending_updates_start",
65
+ message: "applying pending updates",
66
+ meta: { bundlesRoot, currentVersion },
67
+ });
68
+ if (!fs.existsSync(bundlesRoot)) {
69
+ return summary;
70
+ }
71
+ let entries;
72
+ try {
73
+ entries = fs.readdirSync(bundlesRoot, { withFileTypes: true });
74
+ }
75
+ catch {
76
+ return summary;
77
+ }
78
+ for (const entry of entries) {
79
+ if (!entry.isDirectory() || !entry.name.endsWith(".ouro"))
80
+ continue;
81
+ const agentRoot = path.join(bundlesRoot, entry.name);
82
+ let previousVersion;
83
+ const metaPath = path.join(agentRoot, "bundle-meta.json");
84
+ try {
85
+ if (fs.existsSync(metaPath)) {
86
+ const raw = fs.readFileSync(metaPath, "utf-8");
87
+ const meta = JSON.parse(raw);
88
+ previousVersion = meta.runtimeVersion;
89
+ if (previousVersion === currentVersion) {
90
+ continue;
91
+ }
92
+ // Skip downgrades — only update forward
93
+ if (semver.valid(previousVersion) && semver.valid(currentVersion) && semver.gte(previousVersion, currentVersion)) {
94
+ (0, runtime_1.emitNervesEvent)({
95
+ component: "daemon",
96
+ event: "daemon.update_hook_skip_downgrade",
97
+ message: "skipping downgrade",
98
+ meta: { agentRoot, previousVersion, currentVersion },
99
+ });
100
+ continue;
101
+ }
102
+ }
103
+ }
104
+ catch {
105
+ // Malformed or unreadable bundle-meta.json -- treat as needing update
106
+ previousVersion = undefined;
107
+ }
108
+ const ctx = { agentRoot, currentVersion, previousVersion };
109
+ for (const hook of _hooks) {
110
+ try {
111
+ await hook(ctx);
112
+ }
113
+ catch (err) {
114
+ (0, runtime_1.emitNervesEvent)({
115
+ component: "daemon",
116
+ event: "daemon.update_hook_error",
117
+ message: "update hook threw",
118
+ meta: {
119
+ agentRoot,
120
+ error: err instanceof Error ? err.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(err),
121
+ },
122
+ });
123
+ }
124
+ }
125
+ summary.updated.push({
126
+ agent: entry.name.replace(/\.ouro$/, ""),
127
+ from: previousVersion,
128
+ to: currentVersion,
129
+ });
130
+ }
131
+ (0, runtime_1.emitNervesEvent)({
132
+ component: "daemon",
133
+ event: "daemon.apply_pending_updates_end",
134
+ message: "pending updates applied",
135
+ meta: { bundlesRoot },
136
+ });
137
+ return summary;
138
+ }