@jungjaehoon/mama-os 0.76.0 → 0.78.0

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 (190) hide show
  1. package/CHANGELOG.md +182 -0
  2. package/README.md +25 -10
  3. package/dist/api/attachment-actions.d.ts.map +1 -1
  4. package/dist/api/attachment-actions.js +8 -7
  5. package/dist/api/attachment-actions.js.map +1 -1
  6. package/dist/api/code-act-actions.d.ts.map +1 -1
  7. package/dist/api/code-act-actions.js +5 -2
  8. package/dist/api/code-act-actions.js.map +1 -1
  9. package/dist/api/drive-actions.d.ts +4 -0
  10. package/dist/api/drive-actions.d.ts.map +1 -1
  11. package/dist/api/drive-actions.js +15 -7
  12. package/dist/api/drive-actions.js.map +1 -1
  13. package/dist/api/drive-delivery.d.ts.map +1 -1
  14. package/dist/api/drive-delivery.js +8 -3
  15. package/dist/api/drive-delivery.js.map +1 -1
  16. package/dist/api/member-enrollment.d.ts +2 -0
  17. package/dist/api/member-enrollment.d.ts.map +1 -1
  18. package/dist/api/member-enrollment.js +1 -1
  19. package/dist/api/member-enrollment.js.map +1 -1
  20. package/dist/api/member-lifecycle.d.ts +2 -0
  21. package/dist/api/member-lifecycle.d.ts.map +1 -1
  22. package/dist/api/member-lifecycle.js +1 -1
  23. package/dist/api/member-lifecycle.js.map +1 -1
  24. package/dist/api/owner-timezone-actions.d.ts +2 -0
  25. package/dist/api/owner-timezone-actions.d.ts.map +1 -1
  26. package/dist/api/owner-timezone-actions.js.map +1 -1
  27. package/dist/api/security-events.d.ts.map +1 -1
  28. package/dist/api/security-events.js +10 -1
  29. package/dist/api/security-events.js.map +1 -1
  30. package/dist/api/viewer-server.d.ts +1 -0
  31. package/dist/api/viewer-server.d.ts.map +1 -1
  32. package/dist/api/viewer-server.js +34 -0
  33. package/dist/api/viewer-server.js.map +1 -1
  34. package/dist/api/work-actions.d.ts.map +1 -1
  35. package/dist/api/work-actions.js +27 -2
  36. package/dist/api/work-actions.js.map +1 -1
  37. package/dist/cli/commands/backfill.d.ts.map +1 -1
  38. package/dist/cli/commands/backfill.js +1 -0
  39. package/dist/cli/commands/backfill.js.map +1 -1
  40. package/dist/cli/commands/backup.d.ts +13 -0
  41. package/dist/cli/commands/backup.d.ts.map +1 -0
  42. package/dist/cli/commands/backup.js +137 -0
  43. package/dist/cli/commands/backup.js.map +1 -0
  44. package/dist/cli/commands/control.d.ts +26 -0
  45. package/dist/cli/commands/control.d.ts.map +1 -0
  46. package/dist/cli/commands/control.js +193 -0
  47. package/dist/cli/commands/control.js.map +1 -0
  48. package/dist/cli/commands/daemon.d.ts +49 -3
  49. package/dist/cli/commands/daemon.d.ts.map +1 -1
  50. package/dist/cli/commands/daemon.js +253 -75
  51. package/dist/cli/commands/daemon.js.map +1 -1
  52. package/dist/cli/commands/doctor.d.ts +13 -0
  53. package/dist/cli/commands/doctor.d.ts.map +1 -0
  54. package/dist/cli/commands/doctor.js +216 -0
  55. package/dist/cli/commands/doctor.js.map +1 -0
  56. package/dist/cli/commands/init.d.ts.map +1 -1
  57. package/dist/cli/commands/init.js +3 -2
  58. package/dist/cli/commands/init.js.map +1 -1
  59. package/dist/cli/commands/logs.d.ts +9 -0
  60. package/dist/cli/commands/logs.d.ts.map +1 -0
  61. package/dist/cli/commands/logs.js +109 -0
  62. package/dist/cli/commands/logs.js.map +1 -0
  63. package/dist/cli/commands/register-owner.d.ts.map +1 -1
  64. package/dist/cli/commands/register-owner.js +12 -0
  65. package/dist/cli/commands/register-owner.js.map +1 -1
  66. package/dist/cli/commands/restore.d.ts +14 -0
  67. package/dist/cli/commands/restore.d.ts.map +1 -0
  68. package/dist/cli/commands/restore.js +130 -0
  69. package/dist/cli/commands/restore.js.map +1 -0
  70. package/dist/cli/commands/service.d.ts +28 -0
  71. package/dist/cli/commands/service.d.ts.map +1 -0
  72. package/dist/cli/commands/service.js +278 -0
  73. package/dist/cli/commands/service.js.map +1 -0
  74. package/dist/cli/commands/uninstall.d.ts +7 -0
  75. package/dist/cli/commands/uninstall.d.ts.map +1 -0
  76. package/dist/cli/commands/uninstall.js +262 -0
  77. package/dist/cli/commands/uninstall.js.map +1 -0
  78. package/dist/cli/index.js +67 -20
  79. package/dist/cli/index.js.map +1 -1
  80. package/dist/cli/launch-files.d.ts +83 -0
  81. package/dist/cli/launch-files.d.ts.map +1 -1
  82. package/dist/cli/launch-files.js +231 -15
  83. package/dist/cli/launch-files.js.map +1 -1
  84. package/dist/cli/runtime/backend-probe.d.ts +34 -0
  85. package/dist/cli/runtime/backend-probe.d.ts.map +1 -0
  86. package/dist/cli/runtime/backend-probe.js +83 -0
  87. package/dist/cli/runtime/backend-probe.js.map +1 -0
  88. package/dist/cli/runtime/backup.d.ts +114 -0
  89. package/dist/cli/runtime/backup.d.ts.map +1 -0
  90. package/dist/cli/runtime/backup.js +564 -0
  91. package/dist/cli/runtime/backup.js.map +1 -0
  92. package/dist/cli/runtime/claude-caller-config.d.ts +3 -1
  93. package/dist/cli/runtime/claude-caller-config.d.ts.map +1 -1
  94. package/dist/cli/runtime/claude-caller-config.js +14 -2
  95. package/dist/cli/runtime/claude-caller-config.js.map +1 -1
  96. package/dist/cli/runtime/daemon-control.d.ts +84 -0
  97. package/dist/cli/runtime/daemon-control.d.ts.map +1 -0
  98. package/dist/cli/runtime/daemon-control.js +266 -0
  99. package/dist/cli/runtime/daemon-control.js.map +1 -0
  100. package/dist/cli/runtime/diagnostics.d.ts +43 -0
  101. package/dist/cli/runtime/diagnostics.d.ts.map +1 -0
  102. package/dist/cli/runtime/diagnostics.js +953 -0
  103. package/dist/cli/runtime/diagnostics.js.map +1 -0
  104. package/dist/cli/runtime/restore.d.ts +98 -0
  105. package/dist/cli/runtime/restore.d.ts.map +1 -0
  106. package/dist/cli/runtime/restore.js +756 -0
  107. package/dist/cli/runtime/restore.js.map +1 -0
  108. package/dist/cli/runtime/systemd-service.d.ts +13 -0
  109. package/dist/cli/runtime/systemd-service.d.ts.map +1 -0
  110. package/dist/cli/runtime/systemd-service.js +32 -0
  111. package/dist/cli/runtime/systemd-service.js.map +1 -0
  112. package/dist/connectors/chatwork/index.js +1 -1
  113. package/dist/connectors/chatwork/index.js.map +1 -1
  114. package/dist/connectors/config-loader.d.ts +2 -1
  115. package/dist/connectors/config-loader.d.ts.map +1 -1
  116. package/dist/connectors/config-loader.js +3 -2
  117. package/dist/connectors/config-loader.js.map +1 -1
  118. package/dist/connectors/framework/polling-scheduler.d.ts.map +1 -1
  119. package/dist/connectors/framework/polling-scheduler.js +12 -11
  120. package/dist/connectors/framework/polling-scheduler.js.map +1 -1
  121. package/dist/connectors/imessage/index.d.ts.map +1 -1
  122. package/dist/connectors/imessage/index.js +2 -1
  123. package/dist/connectors/imessage/index.js.map +1 -1
  124. package/dist/connectors/slack/index.js +1 -1
  125. package/dist/connectors/slack/index.js.map +1 -1
  126. package/dist/runtime/action-surface.d.ts.map +1 -1
  127. package/dist/runtime/action-surface.js +6 -0
  128. package/dist/runtime/action-surface.js.map +1 -1
  129. package/dist/runtime/backend-security.d.ts +3 -0
  130. package/dist/runtime/backend-security.d.ts.map +1 -1
  131. package/dist/runtime/backend-security.js +13 -5
  132. package/dist/runtime/backend-security.js.map +1 -1
  133. package/dist/runtime/claude-caller-hook.d.ts +14 -0
  134. package/dist/runtime/claude-caller-hook.d.ts.map +1 -1
  135. package/dist/runtime/claude-caller-hook.js +21 -1
  136. package/dist/runtime/claude-caller-hook.js.map +1 -1
  137. package/dist/runtime/config.d.ts +8 -0
  138. package/dist/runtime/config.d.ts.map +1 -1
  139. package/dist/runtime/config.js +17 -4
  140. package/dist/runtime/config.js.map +1 -1
  141. package/dist/runtime/connectors.d.ts +21 -1
  142. package/dist/runtime/connectors.d.ts.map +1 -1
  143. package/dist/runtime/connectors.js +28 -17
  144. package/dist/runtime/connectors.js.map +1 -1
  145. package/dist/runtime/core-db.d.ts.map +1 -1
  146. package/dist/runtime/core-db.js +2 -0
  147. package/dist/runtime/core-db.js.map +1 -1
  148. package/dist/runtime/member-session.d.ts.map +1 -1
  149. package/dist/runtime/member-session.js +11 -3
  150. package/dist/runtime/member-session.js.map +1 -1
  151. package/dist/runtime/member-system-prompt.d.ts +2 -0
  152. package/dist/runtime/member-system-prompt.d.ts.map +1 -1
  153. package/dist/runtime/member-system-prompt.js +11 -3
  154. package/dist/runtime/member-system-prompt.js.map +1 -1
  155. package/dist/runtime/native-session.d.ts +2 -0
  156. package/dist/runtime/native-session.d.ts.map +1 -1
  157. package/dist/runtime/native-session.js +25 -4
  158. package/dist/runtime/native-session.js.map +1 -1
  159. package/dist/runtime/owner-authority.d.ts +2 -0
  160. package/dist/runtime/owner-authority.d.ts.map +1 -1
  161. package/dist/runtime/owner-authority.js +4 -0
  162. package/dist/runtime/owner-authority.js.map +1 -1
  163. package/dist/runtime/owner-runtime.d.ts +2 -0
  164. package/dist/runtime/owner-runtime.d.ts.map +1 -1
  165. package/dist/runtime/owner-runtime.js +3 -0
  166. package/dist/runtime/owner-runtime.js.map +1 -1
  167. package/dist/runtime/owner-system-prompt.d.ts.map +1 -1
  168. package/dist/runtime/owner-system-prompt.js +9 -7
  169. package/dist/runtime/owner-system-prompt.js.map +1 -1
  170. package/dist/runtime/principal-access.d.ts.map +1 -1
  171. package/dist/runtime/principal-access.js +1 -0
  172. package/dist/runtime/principal-access.js.map +1 -1
  173. package/dist/runtime/rotating-log.d.ts +44 -0
  174. package/dist/runtime/rotating-log.d.ts.map +1 -0
  175. package/dist/runtime/rotating-log.js +124 -0
  176. package/dist/runtime/rotating-log.js.map +1 -0
  177. package/dist/runtime/web-fetch-scope.d.ts +20 -0
  178. package/dist/runtime/web-fetch-scope.d.ts.map +1 -0
  179. package/dist/runtime/web-fetch-scope.js +68 -0
  180. package/dist/runtime/web-fetch-scope.js.map +1 -0
  181. package/dist/tools/document-toolkit.d.ts +2 -0
  182. package/dist/tools/document-toolkit.d.ts.map +1 -1
  183. package/dist/tools/document-toolkit.js +3 -1
  184. package/dist/tools/document-toolkit.js.map +1 -1
  185. package/dist/tools/office-render.d.ts.map +1 -1
  186. package/dist/tools/office-render.js +3 -0
  187. package/dist/tools/office-render.js.map +1 -1
  188. package/package.json +4 -3
  189. package/toolkit/requirements.in +11 -0
  190. package/toolkit/requirements.lock +984 -5
@@ -1,13 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.OWNER_PRINCIPAL_ID = void 0;
3
+ exports.MESSENGER_SECRETS = exports.BOOT_STAGES = exports.StopDuringBootError = exports.OWNER_PRINCIPAL_ID = void 0;
4
+ exports.pathsOverlap = pathsOverlap;
4
5
  exports.validateDeliveryRoutes = validateDeliveryRoutes;
6
+ exports.pathsFor = pathsFor;
5
7
  exports.ensureDaemonIsolation = ensureDaemonIsolation;
6
8
  exports.loggedOwnerIntake = loggedOwnerIntake;
7
9
  exports.bootDaemon = bootDaemon;
8
10
  exports.runDaemon = runDaemon;
9
- exports.daemonStatus = daemonStatus;
10
- exports.requestDaemonStop = requestDaemonStop;
11
+ exports.installControlPaths = installControlPaths;
12
+ exports.askDaemon = askDaemon;
13
+ exports.launchdService = launchdService;
14
+ exports.bootoutLaunchdService = bootoutLaunchdService;
11
15
  const mama_core_1 = require("@jungjaehoon/mama-core");
12
16
  const document_toolkit_js_1 = require("../../tools/document-toolkit.js");
13
17
  const node_fs_1 = require("node:fs");
@@ -24,6 +28,7 @@ const action_surface_js_1 = require("../../runtime/action-surface.js");
24
28
  const connectors_js_1 = require("../../runtime/connectors.js");
25
29
  const config_js_1 = require("../../runtime/config.js");
26
30
  const model_cache_js_1 = require("../../runtime/model-cache.js");
31
+ const backend_security_js_1 = require("../../runtime/backend-security.js");
27
32
  const session_credential_js_1 = require("../../runtime/session-credential.js");
28
33
  const action_mcp_config_js_1 = require("../runtime/action-mcp-config.js");
29
34
  const index_js_1 = require("../../connectors/index.js");
@@ -38,6 +43,7 @@ const wiki_read_js_1 = require("../../wiki/wiki-read.js");
38
43
  const timezone_js_1 = require("../../runtime/timezone.js");
39
44
  const security_events_js_1 = require("../../api/security-events.js");
40
45
  const egress_proxy_js_1 = require("../../runtime/egress-proxy.js");
46
+ const daemon_control_js_1 = require("../runtime/daemon-control.js");
41
47
  exports.OWNER_PRINCIPAL_ID = 'owner';
42
48
  const OWNER_AGENT_ID = 'owner-agent';
43
49
  const OWNER_MEMORY_SCOPES = (0, action_surface_js_1.ownerMemoryScopes)(exports.OWNER_PRINCIPAL_ID, index_js_1.LOADABLE_CONNECTORS);
@@ -55,9 +61,49 @@ function errorName(error) {
55
61
  const name = error.name.trim() !== '' ? error.name : 'Error';
56
62
  return `${name}: ${error.message}`;
57
63
  }
58
- function stage(logger, name) {
64
+ class StopDuringBootError extends Error {
65
+ constructor() {
66
+ super('Stopped during boot');
67
+ this.name = 'StopDuringBootError';
68
+ }
69
+ }
70
+ exports.StopDuringBootError = StopDuringBootError;
71
+ /** Every stage boot logs as `boot stage=<name>`; `mama doctor` reports only these names. */
72
+ exports.BOOT_STAGES = [
73
+ 'config',
74
+ 'isolation',
75
+ 'owner_runtime',
76
+ 'viewer',
77
+ 'replay',
78
+ 'connectors',
79
+ 'messengers',
80
+ 'telegram',
81
+ 'telegram:disabled',
82
+ 'discord',
83
+ 'slack',
84
+ 'report_scheduler',
85
+ 'report_scheduler:route_unavailable',
86
+ 'personal_job_scheduler',
87
+ ];
88
+ function stage(logger, name, signal) {
89
+ if (signal?.aborted)
90
+ throw new StopDuringBootError();
59
91
  logger.info(`boot stage=${name}`);
60
92
  }
93
+ /** The auth.env names each messenger needs before it starts; `mama doctor` checks the same. */
94
+ exports.MESSENGER_SECRETS = {
95
+ telegram: ['MAMA_TELEGRAM_TOKEN'],
96
+ discord: ['MAMA_DISCORD_TOKEN'],
97
+ slack: ['MAMA_SLACK_TOKEN', 'MAMA_SLACK_APP_TOKEN'],
98
+ };
99
+ function messengerSecrets(messenger) {
100
+ return exports.MESSENGER_SECRETS[messenger].map((name) => {
101
+ const value = process.env[name];
102
+ if (!value?.trim())
103
+ throw new Error(`${name} is required`);
104
+ return value;
105
+ });
106
+ }
61
107
  function stageFailed(logger, name, error) {
62
108
  logger.error(`boot stage=${name} failed error=${errorName(error)}`);
63
109
  }
@@ -70,9 +116,10 @@ function stimulusFailed(logger, kind, id, error, modelRunId = null) {
70
116
  function stimulusDelivered(logger, row, modelRunId) {
71
117
  logger.info(`stimulus delivered kind=${row.kind ?? 'unknown'} id=${row.stimulusId} model_run_id=${modelRunId}`);
72
118
  }
119
+ /** Physical paths: a symlinked workspace must not reach the directories it is kept apart from. */
73
120
  function pathsOverlap(left, right) {
74
121
  const inside = (parent, child) => {
75
- const local = (0, node_path_1.relative)((0, node_path_1.resolve)(parent), (0, node_path_1.resolve)(child));
122
+ const local = (0, node_path_1.relative)((0, backend_security_js_1.physicalReadPath)(parent), (0, backend_security_js_1.physicalReadPath)(child));
76
123
  return local === '' || (local !== '..' && !local.startsWith(`..${node_path_1.sep}`) && !(0, node_path_1.isAbsolute)(local));
77
124
  };
78
125
  return inside(left, right) || inside(right, left);
@@ -122,6 +169,16 @@ function pathsFor(configPath, config) {
122
169
  if (pathsOverlap(workspaceDir, connectorsRoot)) {
123
170
  throw new Error(`agent.codex_cwd must not contain or sit inside ${connectorsRoot}`);
124
171
  }
172
+ // The runtime directory holds the session credential and the running turn's web fetch mode;
173
+ // an agent that could write there could open web fetch for a turn with no person in it.
174
+ if (pathsOverlap(workspaceDir, runtimeRoot)) {
175
+ throw new Error(`agent.codex_cwd must not contain or sit inside ${runtimeRoot}`);
176
+ }
177
+ // Backups are denied to the agents; a workspace there could not be read by its own agent.
178
+ const backupsRoot = (0, node_path_1.join)(mamaRoot, 'backups');
179
+ if (pathsOverlap(workspaceDir, backupsRoot)) {
180
+ throw new Error(`agent.codex_cwd must not contain or sit inside ${backupsRoot}`);
181
+ }
125
182
  return {
126
183
  mamaRoot,
127
184
  runtimeRoot,
@@ -189,7 +246,15 @@ async function stopOne(logger, name, stop, errors) {
189
246
  }
190
247
  /** Open the W1 owner subject, then activate producers in their dependency order. */
191
248
  async function bootDaemon(options = {}) {
192
- const logger = options.logger ?? defaultLogger;
249
+ const baseLogger = options.logger ?? defaultLogger;
250
+ let lastError = null;
251
+ const logger = {
252
+ info: (line) => baseLogger.info(line),
253
+ error: (line) => {
254
+ lastError = { at: Date.now(), line: line.slice(0, 500) };
255
+ baseLogger.error(line);
256
+ },
257
+ };
193
258
  const dependencies = options.dependencies ?? {};
194
259
  const configPath = options.configPath ?? (0, config_js_1.defaultConfigPath)(options.home ?? (0, node_os_1.homedir)());
195
260
  let config;
@@ -206,10 +271,49 @@ async function bootDaemon(options = {}) {
206
271
  let deliveryReady = options.mode === 'replay';
207
272
  const startedAt = Date.now();
208
273
  let currentStage = 'config';
209
- const stopResources = async () => {
274
+ let booted = false;
275
+ const command = options.command ?? (options.mode === 'replay' ? 'replay' : 'daemon');
276
+ // The install's one process: taken before anything opens the database or rewrites settings.
277
+ const control = (0, daemon_control_js_1.daemonControlPaths)((0, node_path_1.dirname)(configPath));
278
+ const lock = (0, daemon_control_js_1.acquireInstallLock)(control, command);
279
+ let controlServer = null;
280
+ const statusReport = async () => {
281
+ const report = {
282
+ pid: process.pid,
283
+ command,
284
+ version: (0, package_version_js_1.resolvePackageVersion)(),
285
+ startedAt,
286
+ stage: stopped ? 'stopping' : booted ? 'running' : currentStage,
287
+ lastError,
288
+ };
289
+ // Stores close during a stop; what it reports then is that it is stopping.
210
290
  if (stopped)
211
- return;
212
- stopped = true;
291
+ return report;
292
+ if (owner) {
293
+ const adapter = owner.database.adapter;
294
+ report.runningTurns = adapter
295
+ .prepare(`SELECT COALESCE(json_extract(input_refs_json, '$.sessionKey'), 'unknown') AS session,
296
+ COUNT(*) AS count
297
+ FROM model_runs WHERE status = 'running' AND created_at >= ? GROUP BY 1 ORDER BY 1`)
298
+ .all(startedAt);
299
+ report.inputs = adapter
300
+ .prepare(`SELECT status, COUNT(*) AS count FROM mailbox_inputs
301
+ WHERE status IN ('pending', 'claimed') GROUP BY status ORDER BY status`)
302
+ .all();
303
+ }
304
+ if (connectors) {
305
+ const connectorRuntime = connectors;
306
+ const health = await connectorRuntime.registry.healthCheckAll();
307
+ report.connectors = connectorRuntime.enabledConnectorNames.map((name) => ({
308
+ name,
309
+ healthy: health[name]?.healthy === true,
310
+ lastPoll: health[name]?.lastPollTime?.toISOString() ?? null,
311
+ error: health[name]?.error ?? null,
312
+ }));
313
+ }
314
+ return report;
315
+ };
316
+ const stopOwned = async () => {
213
317
  const errors = [];
214
318
  owner?.beginStop?.();
215
319
  // Quiesce every producer before awaiting any one of them. Transports and the database
@@ -236,22 +340,50 @@ async function bootDaemon(options = {}) {
236
340
  if (errors.length > 0)
237
341
  throw new AggregateError(errors, 'Daemon shutdown failed');
238
342
  };
343
+ const stopResources = async () => {
344
+ if (stopped)
345
+ return;
346
+ stopped = true;
347
+ let closed = false;
348
+ try {
349
+ await stopOwned();
350
+ closed = true;
351
+ }
352
+ finally {
353
+ if (controlServer) {
354
+ await controlServer
355
+ .close()
356
+ .catch((error) => logger.error(`shutdown stage=control failed error=${errorName(error)}`));
357
+ }
358
+ // Released only once every store closed: after a failed stop something may still run, so
359
+ // the install stays held until this process ends and the operating system lets it go.
360
+ if (closed)
361
+ lock.release();
362
+ }
363
+ };
239
364
  try {
240
365
  currentStage = 'config';
241
- stage(logger, 'config');
366
+ // A half-switched install is neither the old one nor the restored one.
367
+ (0, daemon_control_js_1.refuseWhileRestoring)((0, node_path_1.dirname)(configPath));
368
+ controlServer = await (0, daemon_control_js_1.startDaemonControl)(control, {
369
+ status: statusReport,
370
+ stop: () => {
371
+ if (!options.requestStop)
372
+ throw new Error(`${command} stops when it finishes`);
373
+ options.requestStop();
374
+ },
375
+ });
376
+ if (options.signal?.aborted)
377
+ throw new StopDuringBootError();
242
378
  config = options.config ?? (0, config_js_1.loadConfig)({ path: configPath, home: options.home });
379
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(config.logging.file), { recursive: true });
380
+ // The service manager redirects only what comes before this point, to the crash log. From here
381
+ // on the daemon's own log holds everything, from the first boot stage on.
382
+ options.captureOutput?.(config.logging.file);
383
+ stage(logger, 'config', options.signal);
243
384
  logger.info(`document toolkit: ${(0, document_toolkit_js_1.readDocumentToolkit)(config.tools_root, (error) => logger.error(error.message)) ? 'present' : 'absent (run mama tools install)'}`);
244
385
  const timeZone = (0, timezone_js_1.createTimeZoneSetting)(config.timezone);
245
386
  validateDeliveryRoutes(config);
246
- // launchd may have created the redirected log already; preserve its contents and tighten it.
247
- (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(config.logging.file), { recursive: true });
248
- const logDescriptor = (0, node_fs_1.openSync)(config.logging.file, 'a', 0o600);
249
- try {
250
- (0, node_fs_1.fchmodSync)(logDescriptor, 0o600);
251
- }
252
- finally {
253
- (0, node_fs_1.closeSync)(logDescriptor);
254
- }
255
387
  (0, model_cache_js_1.declareModelCache)();
256
388
  paths = pathsFor(configPath, config);
257
389
  currentStage = 'isolation';
@@ -261,7 +393,7 @@ async function bootDaemon(options = {}) {
261
393
  paths,
262
394
  ...(options.mcpServerPath ? { mcpServerPath: options.mcpServerPath } : {}),
263
395
  });
264
- stage(logger, 'isolation');
396
+ stage(logger, 'isolation', options.signal);
265
397
  currentStage = 'owner_runtime';
266
398
  const ownerPolicyProvider = (0, owner_policy_js_1.createOwnerPolicyProvider)(paths.mamaRoot);
267
399
  const ownerPolicy = ownerPolicyProvider();
@@ -381,6 +513,7 @@ async function bootDaemon(options = {}) {
381
513
  connectors: index_js_1.LOADABLE_CONNECTORS,
382
514
  rawPath: paths.connectorsRoot,
383
515
  effort: config.agent.effort,
516
+ autonomousWebFetch: config.agent.autonomous_web_fetch ?? [],
384
517
  maxTurns: config.agent.max_turns,
385
518
  timeout: config.agent.timeout,
386
519
  maxTurnMs: config.agent.max_turn_ms,
@@ -486,7 +619,7 @@ async function bootDaemon(options = {}) {
486
619
  onStimulusDelivered: (row, modelRunId) => stimulusDelivered(logger, row, modelRunId),
487
620
  onStimulusFailed: (row, reason, modelRunId) => stimulusFailed(logger, row.kind ?? 'unknown', row.stimulusId, reason, modelRunId),
488
621
  });
489
- stage(logger, 'owner_runtime');
622
+ stage(logger, 'owner_runtime', options.signal);
490
623
  currentStage = 'viewer';
491
624
  const viewerFactory = dependencies.createViewerServer ?? viewer_server_js_1.createViewerServer;
492
625
  viewer = viewerFactory({
@@ -537,14 +670,14 @@ async function bootDaemon(options = {}) {
537
670
  });
538
671
  await viewer.start();
539
672
  logger.info(`viewer server listening on port=${String(viewer.port)}`);
540
- stage(logger, 'viewer');
673
+ stage(logger, 'viewer', options.signal);
541
674
  if (options.mode === 'replay') {
542
675
  logger.info('replay collectors: disabled');
543
676
  currentStage = 'replay';
544
677
  if (!options.replay)
545
678
  throw new Error('Replay mode requires a replay feeder');
546
679
  await options.replay({ config, paths, owner, logger, timeZone });
547
- stage(logger, 'replay');
680
+ stage(logger, 'replay', options.signal);
548
681
  return {
549
682
  config,
550
683
  paths,
@@ -581,16 +714,14 @@ async function bootDaemon(options = {}) {
581
714
  coreAdapter: owner.database?.adapter,
582
715
  acceptSourceDelta,
583
716
  });
584
- stage(logger, 'connectors');
717
+ stage(logger, 'connectors', options.signal);
585
718
  currentStage = 'messengers';
586
719
  const intake = loggedOwnerIntake(owner.intake, logger);
587
720
  const ledgerPath = paths.ownerMessageLedgerPath;
588
721
  messageLedger = new telegram_message_ledger_js_1.OwnerMessageLedger(ledgerPath, { log: (line) => logger.info(line) });
589
722
  const filesRoot = (0, node_path_1.join)(paths.workspaceDir, 'files');
590
723
  if (config.telegram.enabled) {
591
- const token = process.env.MAMA_TELEGRAM_TOKEN;
592
- if (!token?.trim())
593
- throw new Error('MAMA_TELEGRAM_TOKEN is required');
724
+ const [token] = messengerSecrets('telegram');
594
725
  const factory = dependencies.createTelegramGateway ?? ((options) => new telegram_js_1.TelegramGateway(options));
595
726
  gateway = factory({
596
727
  token,
@@ -625,14 +756,12 @@ async function bootDaemon(options = {}) {
625
756
  });
626
757
  gateways.set('telegram', gateway);
627
758
  await gateway.start();
628
- stage(logger, 'telegram');
759
+ stage(logger, 'telegram', options.signal);
629
760
  }
630
761
  else
631
- stage(logger, 'telegram:disabled');
762
+ stage(logger, 'telegram:disabled', options.signal);
632
763
  if (config.discord?.enabled) {
633
- const token = process.env.MAMA_DISCORD_TOKEN;
634
- if (!token?.trim())
635
- throw new Error('MAMA_DISCORD_TOKEN is required');
764
+ const [token] = messengerSecrets('discord');
636
765
  const factory = dependencies.createDiscordGateway ?? ((options) => new discord_js_1.DiscordGateway(options));
637
766
  const active = factory({
638
767
  token,
@@ -654,15 +783,10 @@ async function bootDaemon(options = {}) {
654
783
  });
655
784
  gateways.set('discord', active);
656
785
  await active.start();
657
- stage(logger, 'discord');
786
+ stage(logger, 'discord', options.signal);
658
787
  }
659
788
  if (config.slack?.enabled) {
660
- const token = process.env.MAMA_SLACK_TOKEN;
661
- const appToken = process.env.MAMA_SLACK_APP_TOKEN;
662
- if (!token?.trim())
663
- throw new Error('MAMA_SLACK_TOKEN is required');
664
- if (!appToken?.trim())
665
- throw new Error('MAMA_SLACK_APP_TOKEN is required');
789
+ const [token, appToken] = messengerSecrets('slack');
666
790
  const factory = dependencies.createSlackGateway ?? ((options) => new slack_js_1.SlackGateway(options));
667
791
  const active = factory({
668
792
  token,
@@ -685,7 +809,7 @@ async function bootDaemon(options = {}) {
685
809
  });
686
810
  gateways.set('slack', active);
687
811
  await active.start();
688
- stage(logger, 'slack');
812
+ stage(logger, 'slack', options.signal);
689
813
  }
690
814
  const reportRoute = config.delivery?.reports ?? 'telegram';
691
815
  if (gateways.has(reportRoute)) {
@@ -716,18 +840,19 @@ async function bootDaemon(options = {}) {
716
840
  onError: (error) => logger.error(`report scheduler failed reason=${(0, stimulus_delivery_js_1.stimulusFailureReason)(error)}`),
717
841
  });
718
842
  reportScheduler.start();
719
- stage(logger, 'report_scheduler');
843
+ stage(logger, 'report_scheduler', options.signal);
720
844
  }
721
845
  else
722
- stage(logger, 'report_scheduler:route_unavailable');
846
+ stage(logger, 'report_scheduler:route_unavailable', options.signal);
723
847
  if (gateway?.sendMemberText) {
724
848
  owner.personalJobs.start();
725
849
  await owner.personalJobs
726
850
  .tick()
727
851
  .catch((error) => logger.error(`personal job scheduler failed reason=${(0, stimulus_delivery_js_1.stimulusFailureReason)(error)}`));
728
- stage(logger, 'personal_job_scheduler');
852
+ stage(logger, 'personal_job_scheduler', options.signal);
729
853
  }
730
854
  deliveryReady = true;
855
+ booted = true;
731
856
  return {
732
857
  config,
733
858
  paths,
@@ -740,7 +865,16 @@ async function bootDaemon(options = {}) {
740
865
  };
741
866
  }
742
867
  catch (error) {
743
- stageFailed(logger, currentStage, error);
868
+ if (error instanceof StopDuringBootError) {
869
+ logger.info(`boot stopped before stage=${currentStage}`);
870
+ }
871
+ else {
872
+ stageFailed(logger, currentStage, error);
873
+ // A frozen error keeps its own shape; the stage is still in the log line above.
874
+ if (error !== null && typeof error === 'object' && Object.isExtensible(error)) {
875
+ Object.defineProperty(error, 'bootStage', { value: currentStage, enumerable: false });
876
+ }
877
+ }
744
878
  try {
745
879
  await stopResources();
746
880
  }
@@ -750,48 +884,92 @@ async function bootDaemon(options = {}) {
750
884
  throw error;
751
885
  }
752
886
  }
753
- /** Foreground launchd entry: wait until SIGTERM/SIGINT, then close reverse-order stages. */
887
+ /**
888
+ * Foreground entry for launchd, systemd, Docker or a terminal. Signals are taken before boot, so a
889
+ * stop during boot unwinds what has started; `mama stop` asks through the control socket.
890
+ */
754
891
  async function runDaemon(options = {}) {
755
- const daemon = await bootDaemon(options);
756
- await new Promise((resolve, reject) => {
757
- let finished = false;
758
- const finish = async () => {
759
- if (finished)
892
+ const controller = new AbortController();
893
+ // A stop asked for through `mama stop` exits 0; one by a signal exits 128 + its number, as an
894
+ // unhandled signal would, so a service that restarts only after a failure brings back a daemon
895
+ // that was killed (a service manager's own stop does not restart it whatever the code).
896
+ let signalExit;
897
+ const requestStop = () => controller.abort();
898
+ const onSigterm = () => {
899
+ signalExit = 143;
900
+ controller.abort();
901
+ };
902
+ const onSigint = () => {
903
+ signalExit = 130;
904
+ controller.abort();
905
+ };
906
+ // Once each: a second signal falls to Node's default and ends the process at once, a force stop;
907
+ // the operating system releases the install lock with it.
908
+ process.once('SIGTERM', onSigterm);
909
+ process.once('SIGINT', onSigint);
910
+ try {
911
+ let daemon;
912
+ try {
913
+ daemon = await bootDaemon({ ...options, signal: controller.signal, requestStop });
914
+ }
915
+ catch (error) {
916
+ if (error instanceof StopDuringBootError) {
917
+ if (signalExit !== undefined)
918
+ process.exitCode = signalExit;
760
919
  return;
761
- finished = true;
762
- try {
763
- await daemon.stop();
764
- resolve();
765
- }
766
- catch (error) {
767
- reject(error);
768
920
  }
769
- };
770
- process.once('SIGTERM', () => void finish());
771
- process.once('SIGINT', () => void finish());
772
- });
921
+ throw error;
922
+ }
923
+ if (!controller.signal.aborted) {
924
+ await new Promise((resolve) => controller.signal.addEventListener('abort', () => resolve(), { once: true }));
925
+ }
926
+ await daemon.stop();
927
+ if (signalExit !== undefined)
928
+ process.exitCode = signalExit;
929
+ }
930
+ finally {
931
+ process.off('SIGTERM', onSigterm);
932
+ process.off('SIGINT', onSigint);
933
+ }
934
+ }
935
+ /** The install's control paths from its config path, as boot derives them. */
936
+ function installControlPaths(options = {}) {
937
+ const configPath = options.configPath ?? (0, config_js_1.defaultConfigPath)(options.home ?? (0, node_os_1.homedir)());
938
+ return (0, daemon_control_js_1.daemonControlPaths)((0, node_path_1.dirname)(configPath));
939
+ }
940
+ async function askDaemon(op, options = {}) {
941
+ return await (0, daemon_control_js_1.requestDaemonControl)(installControlPaths(options), op);
773
942
  }
774
- function launchdService(command) {
775
- const result = (0, node_child_process_1.spawnSync)('launchctl', [command, `gui/${process.getuid()}/com.mama.server`], {
943
+ /** macOS only: this user's LaunchAgent (`launchctl print` exits 113 when it is not loaded). */
944
+ function launchdService() {
945
+ if (process.platform !== 'darwin')
946
+ return { loaded: false, pid: null };
947
+ const result = (0, node_child_process_1.spawnSync)('launchctl', ['print', `gui/${process.getuid()}/com.mama.server`], {
776
948
  encoding: 'utf8',
777
949
  });
778
950
  if (result.error)
779
951
  throw result.error;
780
- // launchctl print exits 113 when this user's service is not registered.
781
- if (result.status !== 0 && !(command === 'print' && result.status === 113)) {
782
- throw Object.assign(new Error(`launchctl ${command} failed`), {
952
+ if (result.status === 113)
953
+ return { loaded: false, pid: null };
954
+ if (result.status !== 0) {
955
+ throw Object.assign(new Error('launchctl print failed'), {
783
956
  code: `LAUNCHCTL_${result.status ?? result.signal ?? 'UNKNOWN'}`,
784
957
  });
785
958
  }
786
- return result;
959
+ const pid = /^\s*pid = (\d+)\s*$/m.exec(result.stdout)?.[1];
960
+ return { loaded: true, pid: pid === undefined ? null : Number(pid) };
787
961
  }
788
- function daemonStatus() {
789
- const result = launchdService('print');
790
- return result.status === 0 && /^\s*state = running\s*$/m.test(result.stdout)
791
- ? 'running'
792
- : 'stopped';
793
- }
794
- function requestDaemonStop() {
795
- launchdService('bootout');
962
+ /** Unload the LaunchAgent so KeepAlive cannot restart it; launchd sends the stop signal. */
963
+ function bootoutLaunchdService() {
964
+ const result = (0, node_child_process_1.spawnSync)('launchctl', ['bootout', `gui/${process.getuid()}/com.mama.server`], {
965
+ encoding: 'utf8',
966
+ });
967
+ if (result.error)
968
+ throw result.error;
969
+ if (result.status !== 0) {
970
+ throw Object.assign(new Error('launchctl bootout failed'), {
971
+ code: `LAUNCHCTL_${result.status ?? result.signal ?? 'UNKNOWN'}`,
972
+ });
973
+ }
796
974
  }
797
975
  //# sourceMappingURL=daemon.js.map