@stratusagent/cli 0.11.2 → 0.11.4

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 (158) hide show
  1. package/README.md +14 -8
  2. package/dist/approvals.d.ts +93 -0
  3. package/dist/approvals.d.ts.map +1 -0
  4. package/dist/approvals.js +368 -0
  5. package/dist/approvals.js.map +1 -0
  6. package/dist/commands/agent-new.d.ts +22 -0
  7. package/dist/commands/agent-new.d.ts.map +1 -0
  8. package/dist/commands/agent-new.js +159 -0
  9. package/dist/commands/agent-new.js.map +1 -0
  10. package/dist/commands/agents.d.ts +4 -0
  11. package/dist/commands/agents.d.ts.map +1 -0
  12. package/dist/commands/agents.js +90 -0
  13. package/dist/commands/agents.js.map +1 -0
  14. package/dist/commands/chat.d.ts +4 -0
  15. package/dist/commands/chat.d.ts.map +1 -0
  16. package/dist/commands/chat.js +227 -0
  17. package/dist/commands/chat.js.map +1 -0
  18. package/dist/commands/credential.d.ts +22 -0
  19. package/dist/commands/credential.d.ts.map +1 -0
  20. package/dist/commands/credential.js +93 -0
  21. package/dist/commands/credential.js.map +1 -0
  22. package/dist/commands/dashboard.d.ts +14 -0
  23. package/dist/commands/dashboard.d.ts.map +1 -0
  24. package/dist/commands/dashboard.js +195 -0
  25. package/dist/commands/dashboard.js.map +1 -0
  26. package/dist/commands/doctor.d.ts +40 -0
  27. package/dist/commands/doctor.d.ts.map +1 -0
  28. package/dist/commands/doctor.js +376 -0
  29. package/dist/commands/doctor.js.map +1 -0
  30. package/dist/commands/grants.d.ts +19 -0
  31. package/dist/commands/grants.d.ts.map +1 -0
  32. package/dist/commands/grants.js +128 -0
  33. package/dist/commands/grants.js.map +1 -0
  34. package/dist/commands/logs.d.ts +9 -0
  35. package/dist/commands/logs.d.ts.map +1 -0
  36. package/dist/commands/logs.js +58 -0
  37. package/dist/commands/logs.js.map +1 -0
  38. package/dist/commands/memory.d.ts +20 -0
  39. package/dist/commands/memory.d.ts.map +1 -0
  40. package/dist/commands/memory.js +110 -0
  41. package/dist/commands/memory.js.map +1 -0
  42. package/dist/commands/plugins.d.ts +103 -0
  43. package/dist/commands/plugins.d.ts.map +1 -0
  44. package/dist/commands/plugins.js +386 -0
  45. package/dist/commands/plugins.js.map +1 -0
  46. package/dist/commands/restart.d.ts +4 -0
  47. package/dist/commands/restart.d.ts.map +1 -0
  48. package/dist/commands/restart.js +25 -0
  49. package/dist/commands/restart.js.map +1 -0
  50. package/dist/commands/schedules.d.ts +11 -0
  51. package/dist/commands/schedules.d.ts.map +1 -0
  52. package/dist/commands/schedules.js +50 -0
  53. package/dist/commands/schedules.js.map +1 -0
  54. package/dist/commands/serve.d.ts +4 -0
  55. package/dist/commands/serve.d.ts.map +1 -0
  56. package/dist/commands/serve.js +633 -0
  57. package/dist/commands/serve.js.map +1 -0
  58. package/dist/commands/service.d.ts +9 -0
  59. package/dist/commands/service.d.ts.map +1 -0
  60. package/dist/commands/service.js +97 -0
  61. package/dist/commands/service.js.map +1 -0
  62. package/dist/commands/session.d.ts +9 -0
  63. package/dist/commands/session.d.ts.map +1 -0
  64. package/dist/commands/session.js +25 -0
  65. package/dist/commands/session.js.map +1 -0
  66. package/dist/commands/setup.d.ts +5 -0
  67. package/dist/commands/setup.d.ts.map +1 -0
  68. package/dist/commands/setup.js +2467 -0
  69. package/dist/commands/setup.js.map +1 -0
  70. package/dist/commands/skills.d.ts +15 -0
  71. package/dist/commands/skills.d.ts.map +1 -0
  72. package/dist/commands/skills.js +272 -0
  73. package/dist/commands/skills.js.map +1 -0
  74. package/dist/commands/template.d.ts +4 -0
  75. package/dist/commands/template.d.ts.map +1 -0
  76. package/dist/commands/template.js +593 -0
  77. package/dist/commands/template.js.map +1 -0
  78. package/dist/commands/update.d.ts +21 -0
  79. package/dist/commands/update.d.ts.map +1 -0
  80. package/dist/commands/update.js +291 -0
  81. package/dist/commands/update.js.map +1 -0
  82. package/dist/daemon.d.ts +74 -0
  83. package/dist/daemon.d.ts.map +1 -0
  84. package/dist/daemon.js +192 -0
  85. package/dist/daemon.js.map +1 -0
  86. package/dist/environment.d.ts +114 -0
  87. package/dist/environment.d.ts.map +1 -0
  88. package/dist/environment.js +2 -0
  89. package/dist/environment.js.map +1 -0
  90. package/dist/events.d.ts +17 -0
  91. package/dist/events.d.ts.map +1 -0
  92. package/dist/events.js +87 -0
  93. package/dist/events.js.map +1 -0
  94. package/dist/help.d.ts +2 -0
  95. package/dist/help.d.ts.map +1 -0
  96. package/dist/help.js +272 -0
  97. package/dist/help.js.map +1 -0
  98. package/dist/index.d.ts +32 -624
  99. package/dist/index.d.ts.map +1 -1
  100. package/dist/index.js +39 -6981
  101. package/dist/index.js.map +1 -1
  102. package/dist/io.d.ts +16 -0
  103. package/dist/io.d.ts.map +1 -0
  104. package/dist/io.js +45 -0
  105. package/dist/io.js.map +1 -0
  106. package/dist/loaders.d.ts +31 -0
  107. package/dist/loaders.d.ts.map +1 -0
  108. package/dist/loaders.js +56 -0
  109. package/dist/loaders.js.map +1 -0
  110. package/dist/main.d.ts +8 -0
  111. package/dist/main.d.ts.map +1 -0
  112. package/dist/main.js +218 -0
  113. package/dist/main.js.map +1 -0
  114. package/dist/npm.d.ts +66 -0
  115. package/dist/npm.d.ts.map +1 -0
  116. package/dist/npm.js +201 -0
  117. package/dist/npm.js.map +1 -0
  118. package/dist/parse.d.ts +245 -0
  119. package/dist/parse.d.ts.map +1 -0
  120. package/dist/parse.js +1098 -0
  121. package/dist/parse.js.map +1 -0
  122. package/dist/plugin-catalog.d.ts +44 -0
  123. package/dist/plugin-catalog.d.ts.map +1 -0
  124. package/dist/plugin-catalog.js +70 -0
  125. package/dist/plugin-catalog.js.map +1 -0
  126. package/dist/preflight.d.ts +60 -0
  127. package/dist/preflight.d.ts.map +1 -0
  128. package/dist/preflight.js +102 -0
  129. package/dist/preflight.js.map +1 -0
  130. package/dist/prompter.d.ts +84 -0
  131. package/dist/prompter.d.ts.map +1 -0
  132. package/dist/prompter.js +347 -0
  133. package/dist/prompter.js.map +1 -0
  134. package/dist/roster.d.ts +57 -0
  135. package/dist/roster.d.ts.map +1 -0
  136. package/dist/roster.js +78 -0
  137. package/dist/roster.js.map +1 -0
  138. package/dist/runtime.d.ts +76 -0
  139. package/dist/runtime.d.ts.map +1 -0
  140. package/dist/runtime.js +372 -0
  141. package/dist/runtime.js.map +1 -0
  142. package/dist/slack.d.ts +26 -0
  143. package/dist/slack.d.ts.map +1 -0
  144. package/dist/slack.js +113 -0
  145. package/dist/slack.js.map +1 -0
  146. package/dist/source.d.ts +24 -0
  147. package/dist/source.d.ts.map +1 -0
  148. package/dist/source.js +80 -0
  149. package/dist/source.js.map +1 -0
  150. package/dist/supervisor.d.ts +69 -0
  151. package/dist/supervisor.d.ts.map +1 -0
  152. package/dist/supervisor.js +218 -0
  153. package/dist/supervisor.js.map +1 -0
  154. package/dist/trusted-config.d.ts +84 -0
  155. package/dist/trusted-config.d.ts.map +1 -0
  156. package/dist/trusted-config.js +159 -0
  157. package/dist/trusted-config.js.map +1 -0
  158. package/package.json +27 -23
package/dist/source.js ADDED
@@ -0,0 +1,80 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { realpath, stat } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { readWorkingDirectory } from '@stratusagent/state';
5
+ /**
6
+ * What a `skill add` source string means, in order: a directory on this
7
+ * machine, a GitHub `owner/repo` shorthand, or a URL git can clone. The
8
+ * shorthand is the form skills.sh and its CLI print, so a skill published
9
+ * there installs by the name its listing shows.
10
+ */
11
+ export const resolveSource = async (source, env, what) => {
12
+ const localPath = path.resolve(readWorkingDirectory(env), source);
13
+ try {
14
+ if ((await stat(localPath)).isDirectory()) {
15
+ // The real directory, not the path as typed: a source that is
16
+ // itself a symlink would otherwise be copied as that link — an
17
+ // installed entry pointing back at the source tree, which the
18
+ // loader ignores as not-a-directory.
19
+ return { kind: 'local', directory: await realpath(localPath) };
20
+ }
21
+ }
22
+ catch {
23
+ // Not a local directory; read it as a remote source.
24
+ }
25
+ if (/^[\w.-]+\/[\w.-]+$/.test(source)) {
26
+ return { kind: 'git', url: `https://github.com/${source}` };
27
+ }
28
+ if (/^(https?|git|ssh|file):\/\//.test(source) || /^git@[\w.-]+:/.test(source)) {
29
+ return { kind: 'git', url: source };
30
+ }
31
+ throw new Error(`Cannot read ${JSON.stringify(source)} as a ${what} source. Pass a GitHub owner/repo, a git URL, or a local path.`);
32
+ };
33
+ /**
34
+ * A source URL safe to print: userinfo stripped. A token travels in exactly
35
+ * this position (`https://user:token@host/repo.git`), stdout is commonly
36
+ * retained in CI logs, and git's own stderr already redacts — only the
37
+ * unredacted URL is handed to git itself.
38
+ */
39
+ export const redactedSourceUrl = (url) => {
40
+ try {
41
+ const parsed = new URL(url);
42
+ if (parsed.username || parsed.password) {
43
+ parsed.username = '***';
44
+ parsed.password = '';
45
+ }
46
+ return parsed.toString();
47
+ }
48
+ catch {
49
+ // scp-style (git@host:path) or anything else URL() refuses: keep the
50
+ // shape, hide whatever sits before the @.
51
+ return url.replace(/^[^@/]+@/, '***@');
52
+ }
53
+ };
54
+ /** Shallow-clone a source repository. Git owns every transport we would otherwise re-implement. */
55
+ export const cloneSource = async (url, destination) => {
56
+ const display = redactedSourceUrl(url);
57
+ await new Promise((resolve, reject) => {
58
+ const child = spawn('git', ['clone', '--depth', '1', '--quiet', url, destination], {
59
+ stdio: ['ignore', 'ignore', 'pipe'],
60
+ });
61
+ let stderr = '';
62
+ child.stderr?.on('data', (chunk) => {
63
+ stderr += String(chunk);
64
+ });
65
+ child.on('error', (error) => {
66
+ reject(new Error(`Could not run git to fetch ${display}: ${error.message}`));
67
+ });
68
+ child.on('close', (code) => {
69
+ if (code === 0) {
70
+ resolve();
71
+ }
72
+ else {
73
+ // git redacts credentials in its own messages; the URL we echo is
74
+ // ours to redact.
75
+ reject(new Error(`git clone failed for ${display}: ${stderr.trim() || `exit code ${code}`}`));
76
+ }
77
+ });
78
+ });
79
+ };
80
+ //# sourceMappingURL=source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source.js","sourceRoot":"","sources":["../src/source.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAG3D;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,EAChC,MAAc,EACd,GAAmB,EACnB,IAA0B,EACoD,EAAE;IAChF,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,oBAAoB,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;IAClE,IAAI,CAAC;QACH,IAAI,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;YAC1C,8DAA8D;YAC9D,+DAA+D;YAC/D,8DAA8D;YAC9D,qCAAqC;YACrC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QACjE,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,qDAAqD;IACvD,CAAC;IACD,IAAI,oBAAoB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACtC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,sBAAsB,MAAM,EAAE,EAAE,CAAC;IAC9D,CAAC;IACD,IAAI,6BAA6B,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/E,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;IACtC,CAAC;IACD,MAAM,IAAI,KAAK,CACb,eAAe,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,SAAS,IAAI,gEAAgE,CACnH,CAAC;AACJ,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,GAAW,EAAU,EAAE;IACvD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;YACvC,MAAM,CAAC,QAAQ,GAAG,KAAK,CAAC;YACxB,MAAM,CAAC,QAAQ,GAAG,EAAE,CAAC;QACvB,CAAC;QACD,OAAO,MAAM,CAAC,QAAQ,EAAE,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,qEAAqE;QACrE,0CAA0C;QAC1C,OAAO,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IACzC,CAAC;AACH,CAAC,CAAC;AAEF,mGAAmG;AACnG,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,EAAE,GAAW,EAAE,WAAmB,EAAiB,EAAE;IACnF,MAAM,OAAO,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;IACvC,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,WAAW,CAAC,EAAE;YACjF,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC;SACpC,CAAC,CAAC;QACH,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YACzC,MAAM,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YAC1B,MAAM,CAAC,IAAI,KAAK,CAAC,8BAA8B,OAAO,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAC/E,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;YACzB,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;gBACf,OAAO,EAAE,CAAC;YACZ,CAAC;iBAAM,CAAC;gBACN,kEAAkE;gBAClE,kBAAkB;gBAClB,MAAM,CAAC,IAAI,KAAK,CAAC,wBAAwB,OAAO,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,aAAa,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;YAChG,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC"}
@@ -0,0 +1,69 @@
1
+ import type { CliStreams, CliEnvironment, SupervisorLink, RestartHandoff } from './environment.ts';
2
+ import type { ParsedServeCommand } from './parse.ts';
3
+ /**
4
+ * `stratus serve` — run the gateway (stratusd) in the foreground: load the
5
+ * roster, accept dispatches, print events, and drain cleanly on SIGTERM,
6
+ * SIGINT, or the injected shutdown signal.
7
+ */
8
+ /**
9
+ * What a daemon exits with to ask for a fresh process. Only ever seen by the
10
+ * supervisor below, which is the only thing that starts a daemon with the
11
+ * environment marker that makes it exit this way instead of supervising.
12
+ */
13
+ export declare const RESTART_EXIT_CODE = 75;
14
+ /**
15
+ * What a daemon exits with when a restart could not drain — a turn that
16
+ * ignored its abort, a plugin or channel that did not let go. Distinct from
17
+ * the restart status on purpose: the supervisor answers this one by
18
+ * exiting with it too, so the process the service manager started ends
19
+ * and the manager restarts the whole unit — under systemd, cleaning the
20
+ * cgroup of whatever the plugin left running. A supervisor that started
21
+ * another daemon instead would keep that cgroup, and its leaks, alive.
22
+ */
23
+ export declare const UNDRAINED_RESTART_EXIT_CODE = 76;
24
+ /** Set in a daemon the supervisor started, so its own restart is an exit, not a second supervisor. */
25
+ export declare const SUPERVISED_ENV = "STRATUS_SERVE_SUPERVISED";
26
+ /**
27
+ * The control API port the last daemon bound, handed to its replacement by
28
+ * the supervisor. Honoured only where the replacement's own request — the
29
+ * flag, else the config it reads afresh — is still for any free port: a
30
+ * daemon that asked for port 0 comes back on the port it had, so a
31
+ * dashboard page reconnects to it, while a config since edited to a fixed
32
+ * port takes effect on that restart, as an `api` block change is meant to.
33
+ * A hint, never a pin: passing the port back as `--api-port` would outrank
34
+ * the config for every restart after.
35
+ */
36
+ export declare const BOUND_API_PORT_ENV = "STRATUS_SERVE_BOUND_API_PORT";
37
+ /**
38
+ * The IPC channel a supervisor opens when it spawns a daemon: what a
39
+ * supervised daemon says up it (the port it bound, so the supervisor
40
+ * carries *that* port to the next daemon rather than the first one's; the
41
+ * dashboard sessions it hands on) and what comes down it. Where there is
42
+ * no channel — the first daemon, or one a service manager started — there
43
+ * is no one to say it to, and nothing is said.
44
+ */
45
+ export declare const defaultSupervisorLink: SupervisorLink;
46
+ /**
47
+ * The parsed serve command as arguments again — what the fresh daemon is
48
+ * started with. From the command, not from `process.argv`: `stratus
49
+ * dashboard` runs the daemon through `runServe` with a command it built,
50
+ * and the process's own arguments would start a second dashboard.
51
+ */
52
+ export declare const serveArgv: (command: ParsedServeCommand) => string[];
53
+ /**
54
+ * The `bin` beside the module at `moduleUrl`, with that module's own
55
+ * extension: `dist/supervisor.js` respawns `dist/bin.js`, and a source checkout
56
+ * running `src/supervisor.ts` under type stripping respawns `src/bin.ts` — the
57
+ * flags that made that possible travel in `process.execArgv`. Assuming
58
+ * compiled output would hand a source checkout a file that does not exist,
59
+ * and a daemon that never comes back.
60
+ */
61
+ export declare const restartEntrypoint: (moduleUrl: string) => string;
62
+ /**
63
+ * Keep starting daemons for as long as they exit asking for another; the
64
+ * first exit that does not is this process's own — an undrained restart's
65
+ * status included, which ends the supervisor rather than starting a daemon
66
+ * beside what the last one could not release.
67
+ */
68
+ export declare const superviseRestarts: (command: ParsedServeCommand, first: RestartHandoff, streams: CliStreams, env: CliEnvironment) => Promise<number>;
69
+ //# sourceMappingURL=supervisor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supervisor.d.ts","sourceRoot":"","sources":["../src/supervisor.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,UAAU,EACV,cAAc,EAGd,cAAc,EACd,cAAc,EAEf,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD;;;;GAIG;AACH;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC;;;;;;;;GAQG;AACH,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAE9C,sGAAsG;AACtG,eAAO,MAAM,cAAc,6BAA6B,CAAC;AAEzD;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,iCAAiC,CAAC;AAuBjE;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,EAAE,cAoBnC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,SAAS,GAAI,SAAS,kBAAkB,KAAG,MAAM,EAU7D,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,GAAI,WAAW,MAAM,KAAG,MAGrD,CAAC;AA+EF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAC5B,SAAS,kBAAkB,EAC3B,OAAO,cAAc,EACrB,SAAS,UAAU,EACnB,KAAK,cAAc,KAClB,OAAO,CAAC,MAAM,CAoBhB,CAAC"}
@@ -0,0 +1,218 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { fileURLToPath } from 'node:url';
3
+ import path from 'node:path';
4
+ import { writeLine } from "./io.js";
5
+ /**
6
+ * `stratus serve` — run the gateway (stratusd) in the foreground: load the
7
+ * roster, accept dispatches, print events, and drain cleanly on SIGTERM,
8
+ * SIGINT, or the injected shutdown signal.
9
+ */
10
+ /**
11
+ * What a daemon exits with to ask for a fresh process. Only ever seen by the
12
+ * supervisor below, which is the only thing that starts a daemon with the
13
+ * environment marker that makes it exit this way instead of supervising.
14
+ */
15
+ export const RESTART_EXIT_CODE = 75;
16
+ /**
17
+ * What a daemon exits with when a restart could not drain — a turn that
18
+ * ignored its abort, a plugin or channel that did not let go. Distinct from
19
+ * the restart status on purpose: the supervisor answers this one by
20
+ * exiting with it too, so the process the service manager started ends
21
+ * and the manager restarts the whole unit — under systemd, cleaning the
22
+ * cgroup of whatever the plugin left running. A supervisor that started
23
+ * another daemon instead would keep that cgroup, and its leaks, alive.
24
+ */
25
+ export const UNDRAINED_RESTART_EXIT_CODE = 76;
26
+ /** Set in a daemon the supervisor started, so its own restart is an exit, not a second supervisor. */
27
+ export const SUPERVISED_ENV = 'STRATUS_SERVE_SUPERVISED';
28
+ /**
29
+ * The control API port the last daemon bound, handed to its replacement by
30
+ * the supervisor. Honoured only where the replacement's own request — the
31
+ * flag, else the config it reads afresh — is still for any free port: a
32
+ * daemon that asked for port 0 comes back on the port it had, so a
33
+ * dashboard page reconnects to it, while a config since edited to a fixed
34
+ * port takes effect on that restart, as an `api` block change is meant to.
35
+ * A hint, never a pin: passing the port back as `--api-port` would outrank
36
+ * the config for every restart after.
37
+ */
38
+ export const BOUND_API_PORT_ENV = 'STRATUS_SERVE_BOUND_API_PORT';
39
+ const isDashboardSession = (value) => typeof value === 'object' && value !== null
40
+ && typeof value.id === 'string'
41
+ && typeof value.expiresAt === 'number'
42
+ && typeof value.vouchedBy === 'string';
43
+ /** Shape-checked, because the other end of an IPC channel is still another process. */
44
+ const isSupervisorMessage = (value) => {
45
+ if (typeof value !== 'object' || value === null) {
46
+ return false;
47
+ }
48
+ const message = value;
49
+ if (message.type === 'stratusd.bound-api-port') {
50
+ return typeof message.port === 'number' && Number.isInteger(message.port) && message.port > 0;
51
+ }
52
+ if (message.type === 'stratusd.sessions') {
53
+ return Array.isArray(message.sessions) && message.sessions.every(isDashboardSession);
54
+ }
55
+ return false;
56
+ };
57
+ /**
58
+ * The IPC channel a supervisor opens when it spawns a daemon: what a
59
+ * supervised daemon says up it (the port it bound, so the supervisor
60
+ * carries *that* port to the next daemon rather than the first one's; the
61
+ * dashboard sessions it hands on) and what comes down it. Where there is
62
+ * no channel — the first daemon, or one a service manager started — there
63
+ * is no one to say it to, and nothing is said.
64
+ */
65
+ export const defaultSupervisorLink = {
66
+ send(message) {
67
+ if (typeof process.send === 'function' && process.channel) {
68
+ process.send(message);
69
+ }
70
+ },
71
+ receive(handler) {
72
+ const channel = process.channel;
73
+ if (!channel) {
74
+ return;
75
+ }
76
+ process.on('message', (message) => {
77
+ if (isSupervisorMessage(message)) {
78
+ handler(message);
79
+ }
80
+ });
81
+ // A 'message' listener refs the channel, which would keep this process
82
+ // alive after its drain; the daemon's own work is what holds it open.
83
+ channel.unref?.();
84
+ },
85
+ };
86
+ /**
87
+ * The parsed serve command as arguments again — what the fresh daemon is
88
+ * started with. From the command, not from `process.argv`: `stratus
89
+ * dashboard` runs the daemon through `runServe` with a command it built,
90
+ * and the process's own arguments would start a second dashboard.
91
+ */
92
+ export const serveArgv = (command) => [
93
+ 'serve',
94
+ ...(command.configPath !== undefined ? ['--config', command.configPath] : []),
95
+ ...(command.idleTimeoutMs !== undefined ? ['--idle-timeout', String(command.idleTimeoutMs / 1000)] : []),
96
+ ...(command.approvals !== undefined ? ['--approvals', command.approvals] : []),
97
+ ...(command.events ? [] : ['--no-events']),
98
+ ...(command.logToFile === false ? ['--no-log-file'] : []),
99
+ ...(command.api === false ? ['--no-api'] : command.api === true ? ['--api'] : []),
100
+ ...(command.apiPort !== undefined ? ['--api-port', String(command.apiPort)] : []),
101
+ ...(command.apiHost !== undefined ? ['--api-host', command.apiHost] : []),
102
+ ];
103
+ /**
104
+ * The `bin` beside the module at `moduleUrl`, with that module's own
105
+ * extension: `dist/supervisor.js` respawns `dist/bin.js`, and a source checkout
106
+ * running `src/supervisor.ts` under type stripping respawns `src/bin.ts` — the
107
+ * flags that made that possible travel in `process.execArgv`. Assuming
108
+ * compiled output would hand a source checkout a file that does not exist,
109
+ * and a daemon that never comes back.
110
+ */
111
+ export const restartEntrypoint = (moduleUrl) => {
112
+ const modulePath = fileURLToPath(moduleUrl);
113
+ return path.join(path.dirname(modulePath), `bin${path.extname(modulePath)}`);
114
+ };
115
+ /**
116
+ * Run this CLI's own entrypoint as a child with the daemon's streams and
117
+ * exit code, forwarding the signals a supervisor would otherwise swallow.
118
+ *
119
+ * A child, and the parent waits, rather than a detached process and an
120
+ * exit: under systemd and launchd the process the manager started is the
121
+ * service, and its exit ends the job — cgroup and all, on Linux — so the
122
+ * daemon that received the restart has to stay the manager's process and
123
+ * become the supervisor of the next one. That is what makes the same path
124
+ * hold in the foreground and under `--no-login`, where nothing else would
125
+ * bring a clean exit back.
126
+ */
127
+ const defaultServeRespawn = (env) => (argv, handoff) => new Promise((resolve, reject) => {
128
+ const entrypoint = restartEntrypoint(import.meta.url);
129
+ // The daemon's own streams, plus an IPC channel for what it and its
130
+ // supervisor have to tell each other. See SupervisorLink.
131
+ const child = spawn(process.execPath, [...process.execArgv, entrypoint, ...argv], {
132
+ stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
133
+ env: {
134
+ ...process.env,
135
+ [SUPERVISED_ENV]: '1',
136
+ ...(handoff.boundApiPort !== undefined ? { [BOUND_API_PORT_ENV]: String(handoff.boundApiPort) } : {}),
137
+ },
138
+ });
139
+ if (handoff.sessions.length > 0 && child.connected) {
140
+ // Queued in the channel until the daemon listens, which it does
141
+ // before its API is up; the API holds them until it can adopt them.
142
+ // A send that fails (the daemon died before reading) is not this
143
+ // supervisor's failure: the exit below reports what happened, and a
144
+ // daemon that never took them simply comes up signed out.
145
+ const sessions = { type: 'stratusd.sessions', sessions: handoff.sessions };
146
+ child.send(sessions, () => undefined);
147
+ }
148
+ const forward = (signal) => () => {
149
+ child.kill(signal);
150
+ };
151
+ const onTerm = forward('SIGTERM');
152
+ const onInt = forward('SIGINT');
153
+ const onAbort = () => {
154
+ child.kill('SIGTERM');
155
+ };
156
+ process.on('SIGTERM', onTerm);
157
+ process.on('SIGINT', onInt);
158
+ env.shutdownSignal?.addEventListener('abort', onAbort, { once: true });
159
+ const done = () => {
160
+ process.off('SIGTERM', onTerm);
161
+ process.off('SIGINT', onInt);
162
+ env.shutdownSignal?.removeEventListener('abort', onAbort);
163
+ };
164
+ let reportedPort;
165
+ let handedBack;
166
+ child.on('message', (message) => {
167
+ if (!isSupervisorMessage(message)) {
168
+ return;
169
+ }
170
+ if (message.type === 'stratusd.bound-api-port') {
171
+ reportedPort = message.port;
172
+ }
173
+ else {
174
+ handedBack = message.sessions;
175
+ }
176
+ });
177
+ child.once('error', (error) => {
178
+ done();
179
+ reject(error);
180
+ });
181
+ child.once('exit', (code, signal) => {
182
+ done();
183
+ // Killed by a signal is a stop, not a request to come back.
184
+ resolve({
185
+ code: code ?? (signal ? 1 : 0),
186
+ ...(reportedPort !== undefined ? { boundApiPort: reportedPort } : {}),
187
+ ...(handedBack !== undefined ? { sessions: handedBack } : {}),
188
+ });
189
+ });
190
+ });
191
+ /**
192
+ * Keep starting daemons for as long as they exit asking for another; the
193
+ * first exit that does not is this process's own — an undrained restart's
194
+ * status included, which ends the supervisor rather than starting a daemon
195
+ * beside what the last one could not release.
196
+ */
197
+ export const superviseRestarts = async (command, first, streams, env) => {
198
+ const respawn = env.serveRespawn ?? defaultServeRespawn(env);
199
+ // The hand-off follows the daemons: the port each one said it bound is
200
+ // what the next one is told (so a config edited to a fixed port and
201
+ // back hands on the last address, not the first), and the sessions are
202
+ // whatever the last one handed back — a daemon that handed nothing
203
+ // hands nothing on.
204
+ let handoff = first;
205
+ let result;
206
+ do {
207
+ writeLine(streams.stdout, 'Restarting stratusd.');
208
+ result = await respawn(serveArgv(command), handoff);
209
+ handoff = {
210
+ ...(result.boundApiPort !== undefined || handoff.boundApiPort !== undefined
211
+ ? { boundApiPort: result.boundApiPort ?? handoff.boundApiPort }
212
+ : {}),
213
+ sessions: result.sessions ?? [],
214
+ };
215
+ } while (result.code === RESTART_EXIT_CODE);
216
+ return result.code;
217
+ };
218
+ //# sourceMappingURL=supervisor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supervisor.js","sourceRoot":"","sources":["../src/supervisor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,84 @@
1
+ import { type ApiConfig, type ApprovalsConfig, type PrincipalsConfig, type PluginsConfig } from '@stratusagent/state';
2
+ import type { CliEnvironment } from './environment.ts';
3
+ /**
4
+ * A credential that exists only in this shell cannot reach the daemon: a
5
+ * service manager starts with its own environment and never sources a
6
+ * profile, so the unit would come up unauthenticated while setup had just
7
+ * reported everything ready. Returns the variable to name, if so.
8
+ */
9
+ /**
10
+ * The daemon's `approvals` block, from the config file the daemon itself
11
+ * would load. Discovery goes through the shared resolver rather than
12
+ * reading ~/.stratus/config.json directly: `--config` and STRATUS_CONFIG
13
+ * both move it, and a second copy of that precedence would resolve the
14
+ * approver set from a file the gateway is not running on.
15
+ *
16
+ * **Only from a trusted location.** An auto-discovered project-local
17
+ * `stratus.config.json` outranks the global one and can be checked into any
18
+ * repository — which is why stored credentials are already never combined
19
+ * with an endpoint it selects. This block is the same kind of boundary and
20
+ * a sharper one: it names the people who may authorize an agent's gated
21
+ * tool calls, and it would do so through Slack tokens the user configured
22
+ * globally. A cloned repo must not be able to appoint its own approver, so
23
+ * an untrusted config's approvals are ignored — loudly, since silently
24
+ * dropping the block someone is looking at is its own kind of wrong.
25
+ *
26
+ * An unreadable config degrades to headless with a warning, matching how
27
+ * every other consumer treats one: refusing to start would take the whole
28
+ * fleet down over a policy block that may not even be present.
29
+ */
30
+ export declare const loadServeApprovals: (env: CliEnvironment, configPath: string | undefined, warn: (line: string) => void) => Promise<ApprovalsConfig>;
31
+ /**
32
+ * One line naming who can actually answer, so a remote daemon does not look
33
+ * configured when it is not. Two ways it can be hollow, and they fail
34
+ * differently, so they read differently:
35
+ *
36
+ * - No channel running at all (no Slack tokens, or the optional package is
37
+ * not installed) — nothing renders the request, so the turn waits out the
38
+ * whole timeout before being denied. That is the bad one, and the only
39
+ * place it is visible is here, at startup.
40
+ * - A channel is running but an agent has no approvers — the adapter denies
41
+ * that agent's requests on arrival, which is at least prompt.
42
+ *
43
+ * `agentIds` is therefore the agents that can actually be *asked*, not
44
+ * every agent with tokens on disk.
45
+ */
46
+ /**
47
+ * The `principals` block, read under the same trust rule as `approvals` and
48
+ * for a sharper reason: it names the people whose messages an agent takes
49
+ * as its operator's, which is the trust root everything downstream lowers
50
+ * from. A cloned repo appointing itself the principal would arrive as
51
+ * `user` in every Slack thread the daemon serves.
52
+ */
53
+ export declare const loadServePrincipals: (env: CliEnvironment, configPath: string | undefined, warn: (line: string) => void) => Promise<PrincipalsConfig>;
54
+ /**
55
+ * The daemon's `api` block, from the config file the daemon itself would
56
+ * load — and only from a trusted location.
57
+ *
58
+ * An auto-discovered project-local `stratus.config.json` outranks the global
59
+ * one and can be checked into any repository. Which interface a daemon binds
60
+ * is exactly the kind of decision a cloned repo must not get to make, so an
61
+ * untrusted config's block is ignored loudly rather than obeyed.
62
+ */
63
+ export declare const loadServeApi: (env: CliEnvironment, configPath: string | undefined, warn: (line: string) => void) => Promise<ApiConfig>;
64
+ /**
65
+ * The daemon's `plugins` block — the same trust boundary as the three above,
66
+ * and the one it was written for. A plugin runs in-process with the daemon,
67
+ * so a list of them is a list of code; a `stratus.config.json` that ships
68
+ * in a cloned repository must not be able to write it.
69
+ */
70
+ export declare const loadServePlugins: (env: CliEnvironment, configPath: string | undefined, warn: (line: string) => void) => Promise<PluginsConfig>;
71
+ /**
72
+ * The daemon's `executor` or `memoryStore` selection — a name a plugin
73
+ * registered — under the trust rule `plugins` has, and for the same
74
+ * reason one step on: the plugins block decides which code runs, and
75
+ * these decide which of that code an agent's commands run in and where its
76
+ * memories are written. A cloned repository swapping a sandbox executor
77
+ * for the host's is the downgrade this refuses.
78
+ *
79
+ * Absent, or ignored, means the built-in — the caller substitutes it. A
80
+ * project config that says nothing leaves the global file's choice in
81
+ * force, as every trusted block does.
82
+ */
83
+ export declare const loadServeRuntimeSelection: (key: "executor" | "memoryStore", env: CliEnvironment, configPath: string | undefined, warn: (line: string) => void) => Promise<string | undefined>;
84
+ //# sourceMappingURL=trusted-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trusted-config.d.ts","sourceRoot":"","sources":["../src/trusted-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EACnB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD;;;;;GAKG;AACH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,kBAAkB,GAC7B,KAAK,cAAc,EACnB,YAAY,MAAM,GAAG,SAAS,EAC9B,MAAM,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,KAC3B,OAAO,CAAC,eAAe,CAczB,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,GAC9B,KAAK,cAAc,EACnB,YAAY,MAAM,GAAG,SAAS,EAC9B,MAAM,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,KAC3B,OAAO,CAAC,gBAAgB,CA0B1B,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,GACvB,KAAK,cAAc,EACnB,YAAY,MAAM,GAAG,SAAS,EAC9B,MAAM,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,KAC3B,OAAO,CAAC,SAAS,CAcnB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAC3B,KAAK,cAAc,EACnB,YAAY,MAAM,GAAG,SAAS,EAC9B,MAAM,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,KAC3B,OAAO,CAAC,aAAa,CAcvB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,yBAAyB,GACpC,KAAK,UAAU,GAAG,aAAa,EAC/B,KAAK,cAAc,EACnB,YAAY,MAAM,GAAG,SAAS,EAC9B,MAAM,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,KAC3B,OAAO,CAAC,MAAM,GAAG,SAAS,CAoB5B,CAAC"}
@@ -0,0 +1,159 @@
1
+ import { readGlobalConfigBlock, readTrustedConfigBlock, } from '@stratusagent/state';
2
+ /**
3
+ * A credential that exists only in this shell cannot reach the daemon: a
4
+ * service manager starts with its own environment and never sources a
5
+ * profile, so the unit would come up unauthenticated while setup had just
6
+ * reported everything ready. Returns the variable to name, if so.
7
+ */
8
+ /**
9
+ * The daemon's `approvals` block, from the config file the daemon itself
10
+ * would load. Discovery goes through the shared resolver rather than
11
+ * reading ~/.stratus/config.json directly: `--config` and STRATUS_CONFIG
12
+ * both move it, and a second copy of that precedence would resolve the
13
+ * approver set from a file the gateway is not running on.
14
+ *
15
+ * **Only from a trusted location.** An auto-discovered project-local
16
+ * `stratus.config.json` outranks the global one and can be checked into any
17
+ * repository — which is why stored credentials are already never combined
18
+ * with an endpoint it selects. This block is the same kind of boundary and
19
+ * a sharper one: it names the people who may authorize an agent's gated
20
+ * tool calls, and it would do so through Slack tokens the user configured
21
+ * globally. A cloned repo must not be able to appoint its own approver, so
22
+ * an untrusted config's approvals are ignored — loudly, since silently
23
+ * dropping the block someone is looking at is its own kind of wrong.
24
+ *
25
+ * An unreadable config degrades to headless with a warning, matching how
26
+ * every other consumer treats one: refusing to start would take the whole
27
+ * fleet down over a policy block that may not even be present.
28
+ */
29
+ export const loadServeApprovals = async (env, configPath, warn) => {
30
+ const block = await readTrustedConfigBlock('approvals', env, configPath);
31
+ if (block.status === 'untrusted') {
32
+ warn(`ignoring the approvals config in ${block.path}: a project-local config cannot decide who may approve `
33
+ + 'this daemon\'s tool calls. Move it to ~/.stratus/config.json, or pass it with --config.');
34
+ return {};
35
+ }
36
+ if (block.status === 'unreadable') {
37
+ warn(`ignoring the approvals config (${block.error instanceof Error ? block.error.message : String(block.error)}); refusing gated calls`);
38
+ return {};
39
+ }
40
+ return block.status === 'present' ? block.value : {};
41
+ };
42
+ /**
43
+ * One line naming who can actually answer, so a remote daemon does not look
44
+ * configured when it is not. Two ways it can be hollow, and they fail
45
+ * differently, so they read differently:
46
+ *
47
+ * - No channel running at all (no Slack tokens, or the optional package is
48
+ * not installed) — nothing renders the request, so the turn waits out the
49
+ * whole timeout before being denied. That is the bad one, and the only
50
+ * place it is visible is here, at startup.
51
+ * - A channel is running but an agent has no approvers — the adapter denies
52
+ * that agent's requests on arrival, which is at least prompt.
53
+ *
54
+ * `agentIds` is therefore the agents that can actually be *asked*, not
55
+ * every agent with tokens on disk.
56
+ */
57
+ /**
58
+ * The `principals` block, read under the same trust rule as `approvals` and
59
+ * for a sharper reason: it names the people whose messages an agent takes
60
+ * as its operator's, which is the trust root everything downstream lowers
61
+ * from. A cloned repo appointing itself the principal would arrive as
62
+ * `user` in every Slack thread the daemon serves.
63
+ */
64
+ export const loadServePrincipals = async (env, configPath, warn) => {
65
+ let block = await readTrustedConfigBlock('principals', env, configPath);
66
+ if (block.status === 'untrusted') {
67
+ // An auto-discovered project config shadows the global one in
68
+ // discovery, and a daemon started inside a cloned repository would
69
+ // otherwise run with no principals policy at all — a cloned repo that
70
+ // cannot define the policy must not be able to suppress it either. The
71
+ // global file is the trusted answer; it is read instead.
72
+ warn(`ignoring the principals config in ${block.path}: a project-local config cannot decide whose messages `
73
+ + 'this daemon\'s agents treat as their operator\'s. Using ~/.stratus/config.json instead.');
74
+ block = await readGlobalConfigBlock('principals', env);
75
+ }
76
+ if (block.status === 'unreadable') {
77
+ // Closed, not open: the one thing in this block that can fail to
78
+ // parse is `admit`, whose misspelling would otherwise mean `anyone` —
79
+ // the setting exists so that a typo does not open the door, and a
80
+ // warning that opens it anyway is the door with a note on it.
81
+ warn(`the principals config could not be read (${block.error instanceof Error ? block.error.message : String(block.error)}); `
82
+ + 'refusing every Slack sender until it is fixed');
83
+ return { admit: 'principals' };
84
+ }
85
+ return block.status === 'present' ? block.value : {};
86
+ };
87
+ /**
88
+ * The daemon's `api` block, from the config file the daemon itself would
89
+ * load — and only from a trusted location.
90
+ *
91
+ * An auto-discovered project-local `stratus.config.json` outranks the global
92
+ * one and can be checked into any repository. Which interface a daemon binds
93
+ * is exactly the kind of decision a cloned repo must not get to make, so an
94
+ * untrusted config's block is ignored loudly rather than obeyed.
95
+ */
96
+ export const loadServeApi = async (env, configPath, warn) => {
97
+ const block = await readTrustedConfigBlock('api', env, configPath);
98
+ if (block.status === 'untrusted') {
99
+ warn(`ignoring the api config in ${block.path}: a project-local config cannot decide which interface this `
100
+ + 'daemon binds. Move it to ~/.stratus/config.json, or pass it with --config.');
101
+ return {};
102
+ }
103
+ if (block.status === 'unreadable') {
104
+ warn(`ignoring the api config (${block.error instanceof Error ? block.error.message : String(block.error)}); using the defaults`);
105
+ return {};
106
+ }
107
+ return block.status === 'present' ? block.value : {};
108
+ };
109
+ /**
110
+ * The daemon's `plugins` block — the same trust boundary as the three above,
111
+ * and the one it was written for. A plugin runs in-process with the daemon,
112
+ * so a list of them is a list of code; a `stratus.config.json` that ships
113
+ * in a cloned repository must not be able to write it.
114
+ */
115
+ export const loadServePlugins = async (env, configPath, warn) => {
116
+ const block = await readTrustedConfigBlock('plugins', env, configPath);
117
+ if (block.status === 'untrusted') {
118
+ warn(`ignoring the plugins config in ${block.path}: a project-local config cannot decide which code runs inside `
119
+ + 'this daemon. Move it to ~/.stratus/config.json, or pass it with --config.');
120
+ return {};
121
+ }
122
+ if (block.status === 'unreadable') {
123
+ warn(`ignoring the plugins config (${block.error instanceof Error ? block.error.message : String(block.error)}); loading no plugins`);
124
+ return {};
125
+ }
126
+ return block.status === 'present' ? block.value : {};
127
+ };
128
+ /**
129
+ * The daemon's `executor` or `memoryStore` selection — a name a plugin
130
+ * registered — under the trust rule `plugins` has, and for the same
131
+ * reason one step on: the plugins block decides which code runs, and
132
+ * these decide which of that code an agent's commands run in and where its
133
+ * memories are written. A cloned repository swapping a sandbox executor
134
+ * for the host's is the downgrade this refuses.
135
+ *
136
+ * Absent, or ignored, means the built-in — the caller substitutes it. A
137
+ * project config that says nothing leaves the global file's choice in
138
+ * force, as every trusted block does.
139
+ */
140
+ export const loadServeRuntimeSelection = async (key, env, configPath, warn) => {
141
+ let block = await readTrustedConfigBlock(key, env, configPath);
142
+ if (block.status === 'untrusted') {
143
+ // The global file's choice, not the built-in: a clone that cannot
144
+ // select an executor must not be able to deselect one either — a
145
+ // repository shipping `"executor": "local"` would otherwise swap the
146
+ // operator's sandbox for the host, which is the exact downgrade this
147
+ // block refuses. The same fall-through `principals` uses.
148
+ warn(`ignoring ${key} in ${block.path}: a project-local config cannot decide `
149
+ + (key === 'executor' ? 'where this daemon\'s agents run their commands' : 'where this daemon\'s agents keep their memories')
150
+ + '. Using ~/.stratus/config.json instead.');
151
+ block = await readGlobalConfigBlock(key, env);
152
+ }
153
+ if (block.status === 'unreadable') {
154
+ warn(`ignoring ${key} (${block.error instanceof Error ? block.error.message : String(block.error)}); using the built-in`);
155
+ return undefined;
156
+ }
157
+ return block.status === 'present' ? block.value : undefined;
158
+ };
159
+ //# sourceMappingURL=trusted-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trusted-config.js","sourceRoot":"","sources":["../src/trusted-config.ts"],"names":[],"mappings":"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"}