@ouro.bot/cli 0.1.0-alpha.8 → 0.1.0-alpha.81

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 (127) 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 +147 -205
  5. package/assets/ouroboros.png +0 -0
  6. package/changelog.json +468 -0
  7. package/dist/heart/active-work.js +218 -0
  8. package/dist/heart/bridges/manager.js +358 -0
  9. package/dist/heart/bridges/state-machine.js +135 -0
  10. package/dist/heart/bridges/store.js +123 -0
  11. package/dist/heart/commitments.js +89 -0
  12. package/dist/heart/config.js +68 -23
  13. package/dist/heart/core.js +452 -93
  14. package/dist/heart/cross-chat-delivery.js +146 -0
  15. package/dist/heart/daemon/agent-discovery.js +81 -0
  16. package/dist/heart/daemon/auth-flow.js +430 -0
  17. package/dist/heart/daemon/daemon-cli.js +1779 -247
  18. package/dist/heart/daemon/daemon-entry.js +55 -6
  19. package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
  20. package/dist/heart/daemon/daemon.js +216 -10
  21. package/dist/heart/daemon/hatch-animation.js +10 -3
  22. package/dist/heart/daemon/hatch-flow.js +7 -82
  23. package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
  24. package/dist/heart/daemon/launchd.js +159 -0
  25. package/dist/heart/daemon/log-tailer.js +4 -3
  26. package/dist/heart/daemon/message-router.js +17 -8
  27. package/dist/heart/daemon/ouro-bot-entry.js +0 -0
  28. package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
  29. package/dist/heart/daemon/ouro-entry.js +0 -0
  30. package/dist/heart/daemon/ouro-path-installer.js +260 -0
  31. package/dist/heart/daemon/ouro-uti.js +11 -2
  32. package/dist/heart/daemon/ouro-version-manager.js +164 -0
  33. package/dist/heart/daemon/process-manager.js +14 -1
  34. package/dist/heart/daemon/run-hooks.js +37 -0
  35. package/dist/heart/daemon/runtime-logging.js +58 -15
  36. package/dist/heart/daemon/runtime-metadata.js +219 -0
  37. package/dist/heart/daemon/runtime-mode.js +67 -0
  38. package/dist/heart/daemon/sense-manager.js +307 -0
  39. package/dist/heart/daemon/skill-management-installer.js +94 -0
  40. package/dist/heart/daemon/socket-client.js +202 -0
  41. package/dist/heart/daemon/specialist-orchestrator.js +53 -84
  42. package/dist/heart/daemon/specialist-prompt.js +63 -11
  43. package/dist/heart/daemon/specialist-tools.js +211 -60
  44. package/dist/heart/daemon/staged-restart.js +114 -0
  45. package/dist/heart/daemon/thoughts.js +507 -0
  46. package/dist/heart/daemon/update-checker.js +111 -0
  47. package/dist/heart/daemon/update-hooks.js +138 -0
  48. package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
  49. package/dist/heart/delegation.js +62 -0
  50. package/dist/heart/identity.js +126 -21
  51. package/dist/heart/kicks.js +1 -19
  52. package/dist/heart/model-capabilities.js +48 -0
  53. package/dist/heart/obligations.js +141 -0
  54. package/dist/heart/progress-story.js +42 -0
  55. package/dist/heart/providers/anthropic.js +74 -9
  56. package/dist/heart/providers/azure.js +86 -7
  57. package/dist/heart/providers/github-copilot.js +149 -0
  58. package/dist/heart/providers/minimax.js +4 -0
  59. package/dist/heart/providers/openai-codex.js +12 -3
  60. package/dist/heart/safe-workspace.js +228 -0
  61. package/dist/heart/sense-truth.js +61 -0
  62. package/dist/heart/session-activity.js +169 -0
  63. package/dist/heart/session-recall.js +116 -0
  64. package/dist/heart/streaming.js +100 -22
  65. package/dist/heart/target-resolution.js +123 -0
  66. package/dist/heart/turn-coordinator.js +28 -0
  67. package/dist/mind/associative-recall.js +14 -2
  68. package/dist/mind/bundle-manifest.js +70 -0
  69. package/dist/mind/context.js +27 -11
  70. package/dist/mind/first-impressions.js +16 -2
  71. package/dist/mind/friends/channel.js +35 -0
  72. package/dist/mind/friends/group-context.js +144 -0
  73. package/dist/mind/friends/store-file.js +19 -0
  74. package/dist/mind/friends/trust-explanation.js +74 -0
  75. package/dist/mind/friends/types.js +8 -0
  76. package/dist/mind/memory.js +27 -26
  77. package/dist/mind/pending.js +76 -9
  78. package/dist/mind/phrases.js +1 -0
  79. package/dist/mind/prompt.js +445 -77
  80. package/dist/mind/token-estimate.js +8 -12
  81. package/dist/nerves/cli-logging.js +15 -2
  82. package/dist/nerves/coverage/run-artifacts.js +1 -1
  83. package/dist/nerves/index.js +12 -0
  84. package/dist/repertoire/ado-client.js +4 -2
  85. package/dist/repertoire/coding/feedback.js +134 -0
  86. package/dist/repertoire/coding/index.js +4 -1
  87. package/dist/repertoire/coding/manager.js +62 -4
  88. package/dist/repertoire/coding/spawner.js +3 -3
  89. package/dist/repertoire/coding/tools.js +41 -2
  90. package/dist/repertoire/data/ado-endpoints.json +188 -0
  91. package/dist/repertoire/guardrails.js +290 -0
  92. package/dist/repertoire/mcp-client.js +254 -0
  93. package/dist/repertoire/mcp-manager.js +195 -0
  94. package/dist/repertoire/skills.js +3 -26
  95. package/dist/repertoire/tasks/board.js +12 -0
  96. package/dist/repertoire/tasks/index.js +23 -9
  97. package/dist/repertoire/tasks/transitions.js +1 -2
  98. package/dist/repertoire/tools-base.js +686 -251
  99. package/dist/repertoire/tools-bluebubbles.js +93 -0
  100. package/dist/repertoire/tools-teams.js +58 -25
  101. package/dist/repertoire/tools.js +95 -53
  102. package/dist/senses/bluebubbles-client.js +210 -5
  103. package/dist/senses/bluebubbles-entry.js +2 -0
  104. package/dist/senses/bluebubbles-inbound-log.js +109 -0
  105. package/dist/senses/bluebubbles-media.js +339 -0
  106. package/dist/senses/bluebubbles-model.js +12 -4
  107. package/dist/senses/bluebubbles-mutation-log.js +45 -5
  108. package/dist/senses/bluebubbles-runtime-state.js +109 -0
  109. package/dist/senses/bluebubbles-session-cleanup.js +72 -0
  110. package/dist/senses/bluebubbles.js +894 -45
  111. package/dist/senses/cli-layout.js +187 -0
  112. package/dist/senses/cli.js +405 -156
  113. package/dist/senses/continuity.js +94 -0
  114. package/dist/senses/debug-activity.js +154 -0
  115. package/dist/senses/inner-dialog-worker.js +47 -18
  116. package/dist/senses/inner-dialog.js +377 -83
  117. package/dist/senses/pipeline.js +307 -0
  118. package/dist/senses/teams.js +573 -129
  119. package/dist/senses/trust-gate.js +112 -2
  120. package/package.json +14 -3
  121. package/subagents/README.md +4 -70
  122. package/dist/heart/daemon/specialist-session.js +0 -142
  123. package/dist/heart/daemon/subagent-installer.js +0 -125
  124. package/dist/inner-worker-entry.js +0 -4
  125. package/subagents/work-doer.md +0 -233
  126. package/subagents/work-merger.md +0 -624
  127. package/subagents/work-planner.md +0 -373
@@ -34,99 +34,250 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.getSpecialistTools = getSpecialistTools;
37
- exports.execSpecialistTool = execSpecialistTool;
37
+ exports.createSpecialistExecTool = createSpecialistExecTool;
38
+ const crypto = __importStar(require("crypto"));
38
39
  const fs = __importStar(require("fs"));
40
+ const path = __importStar(require("path"));
39
41
  const tools_base_1 = require("../../repertoire/tools-base");
40
42
  const hatch_flow_1 = require("./hatch-flow");
41
43
  const hatch_animation_1 = require("./hatch-animation");
44
+ const bundle_manifest_1 = require("../../mind/bundle-manifest");
42
45
  const runtime_1 = require("../../nerves/runtime");
43
- const hatchAgentTool = {
46
+ const completeAdoptionTool = {
44
47
  type: "function",
45
48
  function: {
46
- name: "hatch_agent",
47
- description: "create a new agent bundle with the given name. call this when you have gathered enough information from the human to hatch their agent.",
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.",
48
51
  parameters: {
49
52
  type: "object",
50
53
  properties: {
51
54
  name: {
52
55
  type: "string",
53
- description: "the name for the new agent (PascalCase, e.g. 'Slugger')",
56
+ description: "the PascalCase name for the new agent (e.g. 'Slugger')",
54
57
  },
55
- humanName: {
58
+ handoff_message: {
56
59
  type: "string",
57
- description: "the human's preferred name, as they told you during conversation",
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)",
58
69
  },
59
70
  },
60
- required: ["name", "humanName"],
71
+ required: ["name", "handoff_message"],
61
72
  },
62
73
  },
63
74
  };
64
75
  const readFileTool = tools_base_1.baseToolDefinitions.find((d) => d.tool.function.name === "read_file");
65
- const listDirTool = tools_base_1.baseToolDefinitions.find((d) => d.tool.function.name === "list_directory");
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
+ };
66
89
  /**
67
90
  * Returns the specialist's tool schema array.
68
91
  */
69
92
  function getSpecialistTools() {
70
- return [hatchAgentTool, tools_base_1.finalAnswerTool, readFileTool.tool, listDirTool.tool];
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 });
71
227
  }
72
228
  /**
73
- * Execute a specialist tool call.
74
- * Returns the tool result string.
229
+ * Create a specialist tool executor with the given dependencies captured in closure.
75
230
  */
76
- async function execSpecialistTool(name, args, deps) {
231
+ function createSpecialistExecTool(deps) {
77
232
  (0, runtime_1.emitNervesEvent)({
78
233
  component: "daemon",
79
- event: "daemon.specialist_tool_exec",
80
- message: "executing specialist tool",
81
- meta: { tool: name },
234
+ event: "daemon.specialist_exec_tool_created",
235
+ message: "specialist exec tool created",
236
+ meta: { tempDir: deps.tempDir },
82
237
  });
83
- if (name === "hatch_agent") {
84
- const agentName = args.name;
85
- if (!agentName) {
86
- return "error: missing required 'name' parameter for hatch_agent";
87
- }
88
- const input = {
89
- agentName,
90
- humanName: args.humanName || deps.humanName,
91
- provider: deps.provider,
92
- credentials: deps.credentials,
93
- };
94
- // Pass identity dirs to prevent hatch flow from syncing to ~/AgentBundles/AdoptionSpecialist.ouro/
95
- // or cwd/AdoptionSpecialist.ouro/. The specialist already picked its identity; the hatch flow
96
- // just needs a valid source dir to pick from for the hatchling's LORE.md seed.
97
- const identitiesDir = deps.specialistIdentitiesDir;
98
- const result = await (0, hatch_flow_1.runHatchFlow)(input, {
99
- bundlesRoot: deps.bundlesRoot,
100
- secretsRoot: deps.secretsRoot,
101
- ...(identitiesDir ? { specialistIdentitySourceDir: identitiesDir, specialistIdentityTargetDir: identitiesDir } : {}),
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 },
102
244
  });
103
- await (0, hatch_animation_1.playHatchAnimation)(agentName, deps.animationWriter);
104
- return [
105
- `hatched ${agentName} successfully.`,
106
- `bundle path: ${result.bundleRoot}`,
107
- `identity seed: ${result.selectedIdentity}`,
108
- `specialist secrets: ${result.specialistSecretsPath}`,
109
- `hatchling secrets: ${result.hatchlingSecretsPath}`,
110
- ].join("\n");
111
- }
112
- if (name === "read_file") {
113
- try {
114
- return fs.readFileSync(args.path, "utf-8");
245
+ if (name === "complete_adoption") {
246
+ return execCompleteAdoption(args, deps);
115
247
  }
116
- catch (e) {
117
- return `error: ${e instanceof Error ? e.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(e)}`;
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
+ }
118
255
  }
119
- }
120
- if (name === "list_directory") {
121
- try {
122
- return fs
123
- .readdirSync(args.path, { withFileTypes: true })
124
- .map((e) => `${e.isDirectory() ? "d" : "-"} ${e.name}`)
125
- .join("\n");
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
+ }
126
269
  }
127
- catch (e) {
128
- return `error: ${e instanceof Error ? e.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(e)}`;
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
+ }
129
280
  }
130
- }
131
- return `error: unknown tool '${name}'`;
281
+ return `error: unknown tool '${name}'`;
282
+ };
132
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
+ }