@stratusagent/cli 0.11.1 → 0.11.3

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 +18 -7
  2. package/dist/approvals.d.ts +87 -0
  3. package/dist/approvals.d.ts.map +1 -0
  4. package/dist/approvals.js +315 -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 +154 -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 +224 -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 +357 -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 +91 -0
  43. package/dist/commands/plugins.d.ts.map +1 -0
  44. package/dist/commands/plugins.js +359 -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 +612 -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 +2412 -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 +575 -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 +108 -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 +266 -0
  97. package/dist/help.js.map +1 -0
  98. package/dist/index.d.ts +31 -582
  99. package/dist/index.d.ts.map +1 -1
  100. package/dist/index.js +38 -6646
  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 +214 -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 +231 -0
  119. package/dist/parse.d.ts.map +1 -0
  120. package/dist/parse.js +1082 -0
  121. package/dist/parse.js.map +1 -0
  122. package/dist/plugin-catalog.d.ts +35 -0
  123. package/dist/plugin-catalog.d.ts.map +1 -0
  124. package/dist/plugin-catalog.js +58 -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 +59 -0
  139. package/dist/runtime.d.ts.map +1 -0
  140. package/dist/runtime.js +266 -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 +71 -0
  155. package/dist/trusted-config.d.ts.map +1 -0
  156. package/dist/trusted-config.js +118 -0
  157. package/dist/trusted-config.js.map +1 -0
  158. package/package.json +25 -23
package/README.md CHANGED
@@ -24,16 +24,16 @@ says so plainly if yours is too old. Details:
24
24
 
25
25
  `stratus setup` is the whole onboarding, as one small menu: sign in (Claude
26
26
  Pro/Max subscription or API key, ChatGPT/Codex, or any OpenAI-compatible
27
- service), pick default and fallback models, create your first agent,
28
- connect Slack, and
29
- install the always-on service — no config files to edit, no env vars to
30
- export. Walkthrough:
27
+ service), pick default and fallback models, create your first agent, enable
28
+ the plugins it may use, connect Slack, choose who approves gated calls while
29
+ nobody is watching, and install the always-on service — no config files to
30
+ edit, no env vars to export. Walkthrough:
31
31
  [Setup](https://github.com/stratuslabs/agent/blob/main/docs/start/setup.md).
32
32
 
33
33
  ## Commands
34
34
 
35
35
  ```bash
36
- stratus setup # onboarding menu: providers, models, agent, channels
36
+ stratus setup # onboarding menu: providers, models, agent, plugins, channels, approvals
37
37
  stratus chat # talk — the conversation persists
38
38
  stratus run "say hello" # one-shot run (works offline on the demo provider)
39
39
  stratus serve # stratusd: the whole roster, always on
@@ -41,14 +41,21 @@ stratus serve --approvals remote # ask a human in Slack instead of refusin
41
41
  stratus service install # keep stratusd running under launchd/systemd
42
42
  stratus logs -f # what the daemon has been doing
43
43
  stratus doctor # what a run would use right now, and why
44
- stratus update # stop → upgrade → migrate → repair unit → restart
44
+ stratus update # stop → upgrade (CLI + its companions) → migrate → repair unit → restart
45
+ stratus --version # which build this is
45
46
  stratus agent new # create an agent (guided on a terminal)
46
47
  stratus agents # who's on the team: souls, models, memory
48
+ stratus template add owner/repo # install an agent, its skills, and the plugins behind them — prints what it will add, then asks
47
49
  stratus skill add owner/repo # install skills from GitHub (validated against the Agent Skills spec) — a running daemon picks them up, no restart
48
50
  stratus skill validate ./my-skill # check a skill against the spec without installing it
51
+ stratus plugins # installed → enabled → granted → what approvals does with it
49
52
  stratus restart # announced restart: refuse, drain, come back — what a plugin change needs
50
53
  stratus credentials # stored credential names (never values)
51
54
  stratus schedules # what the fleet has scheduled, and where it reports
55
+ stratus grants ava # what ava may do unattended, and `grants revoke` to take one back
56
+ stratus memory list ava # every live fact, with the trust label it carries
57
+ stratus memory reassert ava --trust user --all-unknown # vouch for the facts with no recorded origin
58
+ stratus session rollover <session-id> # archive a conversation's transcript and start the same id over
52
59
  stratus dashboard # web dashboard, signed in via a one-time link
53
60
  ```
54
61
 
@@ -72,10 +79,14 @@ Full reference with every subcommand:
72
79
  | `--no-log-file` | `serve`: do not write `~/.stratus/logs/stratusd.jsonl` |
73
80
  | `--no-api` | `serve`: do not serve the control API |
74
81
  | `--api-host`, `--api-port` | `serve`: control API bind (default `127.0.0.1:4123`) |
75
- | `--gateway <url>`, `--token` | `agents`: read the roster from a running daemon's control API |
82
+ | `--gateway <url>`, `--token` | `agents`, `skill reload`, `restart`, `session rollover`, `grants`: a running daemon's control API |
83
+ | `--tool`, `--scope`, `--origin` | `grants revoke`: which grant goes — exactly one |
84
+ | `--trust <level>` | `memory list`: show only that label; `memory reassert`: the label to record (`user`, `agent`, `unknown`, `external`) |
85
+ | `--all-unknown` | `memory reassert`: every live fact with no recorded origin |
76
86
  | `--agent <id>` | `credential set`/`remove`: that agent's own key rather than the fleet's shared one |
77
87
  | `--port`, `--host`, `--no-open` | `dashboard`: where a daemon it starts should bind; skip opening the browser |
78
88
  | `--no-login` | `service install`: install without the start-at-login trigger |
89
+ | `--version`, `-v` | Print this build's version and exit |
79
90
  | `-f`, `-n`, `--agent`, `--session` | `logs`: follow, backlog size, and filters |
80
91
 
81
92
  Precedence: flags → `STRATUS_*` env vars → soul file hints → config file.
@@ -0,0 +1,87 @@
1
+ import { type ApprovalContext, type ApprovalPolicy } from '@stratusagent/core';
2
+ import { type ApprovalsConfig, type PrincipalsConfig, type ChannelCredentials } from '@stratusagent/state';
3
+ import type { CliStreams, CliEnvironment } from './environment.ts';
4
+ import type { CliApprovalMode } from './parse.ts';
5
+ /**
6
+ * What a local prompt asks about: the tool, where it would act when that is
7
+ * a question at all, and the arguments.
8
+ *
9
+ * The origin is not in the arguments and cannot be — a `browser.act` call
10
+ * carries a CSS selector, and `#submit` is equally "load more results" and
11
+ * "confirm purchase". A prompt that showed only the selector would be
12
+ * asking someone to authorize an effect whose location it withheld, which
13
+ * is the same reason the daemon's prompt and the Slack request name it.
14
+ */
15
+ export declare const describeApprovalCall: (context: ApprovalContext) => string;
16
+ export declare const createApprovalPolicy: (mode: CliApprovalMode, streams: CliStreams, env: CliEnvironment, ask?: (prompt: string) => Promise<string>) => ApprovalPolicy;
17
+ /** One line saying whose messages arrive as the operator's, per Slack agent. */
18
+ export declare const describePrincipals: (principals: PrincipalsConfig, agentIds: string[]) => string;
19
+ export declare const describeApprovers: (approvals: ApprovalsConfig, agentIds: string[]) => string;
20
+ /**
21
+ * What a gated call would actually meet on this machine.
22
+ *
23
+ * The mode is not the answer on its own, in both directions. `headless`
24
+ * refuses a gated call *last*: the engine checks the standing tool grants,
25
+ * the approved command scopes, and the approved sites first, so an agent
26
+ * that was ever told "always allow" runs that tool unattended for good
27
+ * (`stratus grants <agent>` is what lists them). And `remote` only asks if
28
+ * somebody can be asked — with no channel to render the request a gated
29
+ * call waits out the timeout, and with no approver configured it is denied
30
+ * on arrival, which is `headless` by another name.
31
+ *
32
+ * Stating either as "gated calls are refused" or "gated calls are asked in
33
+ * Slack" would be wrong in exactly the configurations an operator runs this
34
+ * command to understand.
35
+ */
36
+ /**
37
+ * Who can actually be asked, and what is missing for the rest. Split out of
38
+ * the sentence below because two surfaces need the *answer*: `stratus
39
+ * plugins` renders it as a paragraph, setup's Approvals row as a menu
40
+ * summary, and a second hand-rolled copy of "who is askable" drifted from
41
+ * this one within three PRs — stored tokens read as reachable agents when
42
+ * the package was absent, when the agent had left the roster, and when the
43
+ * only route left was the control API.
44
+ */
45
+ export interface ApprovalReach {
46
+ /** Tokens stored, package installed, agent still served. */
47
+ askable: string[];
48
+ /** Of those, the ones an approver is named for — the rest are denied on arrival. */
49
+ covered: string[];
50
+ /** Of those, the ones with no conversation to ask in for a turn that did not start in Slack. */
51
+ noFallback: string[];
52
+ }
53
+ export declare const classifyApprovalReach: (approvals: ApprovalsConfig, channels: ChannelCredentials, servedAgentIds: readonly string[] | undefined, env: CliEnvironment) => ApprovalReach;
54
+ export declare const describeUnattendedReach: (mode: "headless" | "remote", approvals: ApprovalsConfig, env: CliEnvironment,
55
+ /**
56
+ * The agents actually being served, or undefined when the roster did not
57
+ * load. Stored Slack tokens outlive the agent they were stored for, and
58
+ * the adapter skips an id the gateway is not serving — so a token with no
59
+ * agent behind it must not read as somebody who can be asked.
60
+ *
61
+ * `runServe` prints its own line without this intersection, and is right
62
+ * to: at startup the roster has not loaded yet. It reports the reverse
63
+ * direction separately once it has one, warning about served agents no
64
+ * channel can ask for. Here both are in view from the start.
65
+ */
66
+ servedAgentIds: readonly string[] | undefined,
67
+ /**
68
+ * Whether the control API would be serving. It is a second way to answer
69
+ * a parked call — `GET /api/v1/approvals` lists them, `POST` settles one
70
+ * — so an agent no Slack channel can ask for is not necessarily an agent
71
+ * nobody can ask.
72
+ */
73
+ apiReachable: boolean) => Promise<string>;
74
+ /**
75
+ * The clauses above, unjoined and without reading the filesystem, so a
76
+ * caller holding unsaved state can render the same verdict. The first
77
+ * element is always the verdict itself; the rest qualify it.
78
+ *
79
+ * Split out because setup's Approvals row wrote its own version of this
80
+ * sentence, and every review round found it short a different clause the
81
+ * daemon actually applies — the headless exceptions, an explicit
82
+ * `timeoutMs: 0`, the control API on a mixed roster. There is one renderer
83
+ * now; a menu that wants a shorter line takes fewer clauses, never
84
+ * different words.
85
+ */
86
+ export declare const unattendedReachParts: (mode: "headless" | "remote", approvals: ApprovalsConfig, channels: ChannelCredentials, servedAgentIds: readonly string[] | undefined, apiReachable: boolean, env: CliEnvironment) => string[];
87
+ //# sourceMappingURL=approvals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approvals.d.ts","sourceRoot":"","sources":["../src/approvals.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,cAAc,EACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAIL,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACxB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAWlD;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,GAAI,SAAS,eAAe,KAAG,MAI/D,CAAC;AAoCF,eAAO,MAAM,oBAAoB,GAC/B,MAAM,eAAe,EACrB,SAAS,UAAU,EACnB,KAAK,cAAc,EACnB,MAAM,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,KACxC,cA8CF,CAAC;AAEF,gFAAgF;AAChF,eAAO,MAAM,kBAAkB,GAAI,YAAY,gBAAgB,EAAE,UAAU,MAAM,EAAE,KAAG,MASrF,CAAC;AAiBF,eAAO,MAAM,iBAAiB,GAAI,WAAW,eAAe,EAAE,UAAU,MAAM,EAAE,KAAG,MAWlF,CAAC;AAgBF;;;;;;;;;;;;;;;GAeG;AACH;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B,4DAA4D;IAC5D,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,oFAAoF;IACpF,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,gGAAgG;IAChG,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,eAAO,MAAM,qBAAqB,GAChC,WAAW,eAAe,EAC1B,UAAU,kBAAkB,EAC5B,gBAAgB,SAAS,MAAM,EAAE,GAAG,SAAS,EAC7C,KAAK,cAAc,KAClB,aAcF,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAClC,MAAM,UAAU,GAAG,QAAQ,EAC3B,WAAW,eAAe,EAC1B,KAAK,cAAc;AACnB;;;;;;;;;;GAUG;AACH,gBAAgB,SAAS,MAAM,EAAE,GAAG,SAAS;AAC7C;;;;;GAKG;AACH,cAAc,OAAO,KACpB,OAAO,CAAC,MAAM,CAGhB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB,GAC/B,MAAM,UAAU,GAAG,QAAQ,EAC3B,WAAW,eAAe,EAC1B,UAAU,kBAAkB,EAC5B,gBAAgB,SAAS,MAAM,EAAE,GAAG,SAAS,EAC7C,cAAc,OAAO,EACrB,KAAK,cAAc,KAClB,MAAM,EAuHR,CAAC"}
@@ -0,0 +1,315 @@
1
+ import { createInterface } from 'node:readline';
2
+ import { AllowAllApprovalPolicy, originOf, } from '@stratusagent/core';
3
+ import { loadChannelCredentials, resolveAgentApprovals, resolveAgentPrincipals, } from '@stratusagent/state';
4
+ import { writeLine } from "./io.js";
5
+ import { packageInstalled } from "./loaders.js";
6
+ /**
7
+ * Where a call would act, for a tool that answers that — read through
8
+ * `originOf` so this and the daemon's engine compare the same thing.
9
+ */
10
+ const approvalOrigin = (context) => {
11
+ const reported = context.tool.originFor?.(context.session);
12
+ return reported === undefined ? undefined : originOf(reported);
13
+ };
14
+ /**
15
+ * What a local prompt asks about: the tool, where it would act when that is
16
+ * a question at all, and the arguments.
17
+ *
18
+ * The origin is not in the arguments and cannot be — a `browser.act` call
19
+ * carries a CSS selector, and `#submit` is equally "load more results" and
20
+ * "confirm purchase". A prompt that showed only the selector would be
21
+ * asking someone to authorize an effect whose location it withheld, which
22
+ * is the same reason the daemon's prompt and the Slack request name it.
23
+ */
24
+ export const describeApprovalCall = (context) => {
25
+ const origin = approvalOrigin(context);
26
+ return `${context.call.toolName}${origin ? ` on ${origin}` : ''}`
27
+ + ` with input ${JSON.stringify(context.call.input)}`;
28
+ };
29
+ /**
30
+ * Whether the conversation is still on the page the prompt named.
31
+ *
32
+ * A y/N at a terminal takes as long as it takes, and the page is live for
33
+ * all of it: a redirect between the question and the answer would have a
34
+ * yes given for one site click on another, with the prompt still showing
35
+ * the first. So the origin is read again and a page that moved refuses —
36
+ * the same check the daemon's engine makes after its own approval wait.
37
+ * This local policy needs its own because it is a separate policy sharing
38
+ * none of that code: `--approvals ask` judges the call, not a scope.
39
+ *
40
+ * Said out loud rather than failing quietly: somebody typed `y` and is owed
41
+ * an explanation for why nothing happened.
42
+ */
43
+ const stillOnApprovedPage = (context, approved, streams) => {
44
+ if (context.tool.originFor === undefined) {
45
+ return true;
46
+ }
47
+ const now = approvalOrigin(context);
48
+ if (now === approved) {
49
+ return true;
50
+ }
51
+ writeLine(streams.stderr, `Not running ${context.call.toolName}: it was approved on ${approved ?? 'a page with no origin'}, `
52
+ + `and the conversation ${now === undefined ? 'no longer has that page open' : `is on ${now} now`}.`);
53
+ return false;
54
+ };
55
+ export const createApprovalPolicy = (mode, streams, env, ask) => {
56
+ if (mode === 'always') {
57
+ return new AllowAllApprovalPolicy();
58
+ }
59
+ if (mode === 'never') {
60
+ return {
61
+ async approve() {
62
+ return false;
63
+ },
64
+ };
65
+ }
66
+ // A caller that already owns stdin (chat's readline) supplies its own
67
+ // asker — two readers on one stream would race for the same bytes.
68
+ if (ask) {
69
+ return {
70
+ async approve(context) {
71
+ const approved = approvalOrigin(context);
72
+ const answer = await ask(`Approve tool call ${describeApprovalCall(context)}? [y/N] `);
73
+ return /^y(es)?$/i.test(answer.trim()) && stillOnApprovedPage(context, approved, streams);
74
+ },
75
+ };
76
+ }
77
+ return {
78
+ async approve(context) {
79
+ const input = env.approvalInput ?? process.stdin;
80
+ const readline = createInterface({ input, terminal: false });
81
+ const approved = approvalOrigin(context);
82
+ // Prompt on stderr so stdout stays parseable (e.g. --format json).
83
+ streams.stderr.write(`Approve tool call ${describeApprovalCall(context)}? [y/N] `);
84
+ try {
85
+ const answer = await new Promise((resolve) => {
86
+ readline.once('line', resolve);
87
+ readline.once('close', () => resolve(''));
88
+ });
89
+ writeLine(streams.stderr);
90
+ return /^y(es)?$/i.test(answer.trim()) && stillOnApprovedPage(context, approved, streams);
91
+ }
92
+ finally {
93
+ readline.close();
94
+ }
95
+ },
96
+ };
97
+ };
98
+ /** One line saying whose messages arrive as the operator's, per Slack agent. */
99
+ export const describePrincipals = (principals, agentIds) => {
100
+ const covered = agentIds.filter((agentId) => (resolveAgentPrincipals(principals, agentId).slackUsers ?? []).length > 0);
101
+ if (covered.length === 0) {
102
+ return 'no principals configured, so every Slack sender is unknown and every fact written in Slack carries that label — set principals.slackUsers in ~/.stratus/config.json';
103
+ }
104
+ const uncovered = agentIds.filter((agentId) => !covered.includes(agentId));
105
+ return uncovered.length === 0
106
+ ? `principals set for ${covered.join(', ')}`
107
+ : `principals set for ${covered.join(', ')}; none for ${uncovered.join(', ')}, whose Slack senders are all unknown`;
108
+ };
109
+ /**
110
+ * Which of these agents the Slack adapter would actually ask about, and
111
+ * which it would decline for. Separated from the sentence below because
112
+ * two callers need the *answer* and only one needs it as prose: a summary
113
+ * that says a call "asks in Slack" must not say it about an agent the
114
+ * adapter denies on arrival.
115
+ */
116
+ const classifyApprovers = (approvals, agentIds) => {
117
+ const covered = agentIds.filter((agentId) => (resolveAgentApprovals(approvals, agentId).slackApprovers ?? []).length > 0);
118
+ return { covered, uncovered: agentIds.filter((agentId) => !covered.includes(agentId)) };
119
+ };
120
+ export const describeApprovers = (approvals, agentIds) => {
121
+ if (agentIds.length === 0) {
122
+ return 'but no channel is running to ask through, so gated calls will wait out the approval timeout and then be denied';
123
+ }
124
+ const { covered, uncovered } = classifyApprovers(approvals, agentIds);
125
+ if (covered.length === 0) {
126
+ return 'but no approvers are configured, so every gated call is denied on arrival';
127
+ }
128
+ return uncovered.length === 0
129
+ ? `approvers set for ${covered.join(', ')}`
130
+ : `approvers set for ${covered.join(', ')}; none for ${uncovered.join(', ')}, whose calls are denied on arrival`;
131
+ };
132
+ /**
133
+ * The ways a gated call is already authorized before either mode's decision
134
+ * is reached — the standing tool grants, the approved command scopes, the
135
+ * approved sites, and a schedule's pre-authorized destination, in the order
136
+ * `createPermissionPolicy` checks them.
137
+ *
138
+ * Written once because it is read twice: enumerating it separately per mode
139
+ * is what left the destination path out of one of them and out of the other
140
+ * entirely. It is prose about a rule `@stratusagent/permissions` owns, so
141
+ * when that engine gains a path this string is what has to follow it.
142
+ */
143
+ const ALREADY_AUTHORIZED = 'standing grants, approved command scopes and sites (stratus grants <agent>), '
144
+ + 'and destinations pre-authorized with a schedule (stratus schedules)';
145
+ export const classifyApprovalReach = (approvals, channels, servedAgentIds, env) => {
146
+ // An adapter that is not installed renders nothing, so its stored tokens
147
+ // are not a route — `runServe` starts without the Slack channel and says
148
+ // so. Undefined served ids means the roster did not load, which is not
149
+ // evidence that any token is orphaned.
150
+ const stored = packageInstalled('@stratusagent/channel-slack', env)
151
+ ? Object.keys(channels.slack ?? {})
152
+ : [];
153
+ const askable = servedAgentIds === undefined
154
+ ? stored
155
+ : stored.filter((agentId) => servedAgentIds.includes(agentId));
156
+ const { covered } = classifyApprovers(approvals, askable);
157
+ const noFallback = covered.filter((agentId) => resolveAgentApprovals(approvals, agentId).slackChannel === undefined);
158
+ return { askable, covered, noFallback };
159
+ };
160
+ export const describeUnattendedReach = async (mode, approvals, env,
161
+ /**
162
+ * The agents actually being served, or undefined when the roster did not
163
+ * load. Stored Slack tokens outlive the agent they were stored for, and
164
+ * the adapter skips an id the gateway is not serving — so a token with no
165
+ * agent behind it must not read as somebody who can be asked.
166
+ *
167
+ * `runServe` prints its own line without this intersection, and is right
168
+ * to: at startup the roster has not loaded yet. It reports the reverse
169
+ * direction separately once it has one, warning about served agents no
170
+ * channel can ask for. Here both are in view from the start.
171
+ */
172
+ servedAgentIds,
173
+ /**
174
+ * Whether the control API would be serving. It is a second way to answer
175
+ * a parked call — `GET /api/v1/approvals` lists them, `POST` settles one
176
+ * — so an agent no Slack channel can ask for is not necessarily an agent
177
+ * nobody can ask.
178
+ */
179
+ apiReachable) => {
180
+ const channels = await loadChannelCredentials(env);
181
+ return unattendedReachParts(mode, approvals, channels, servedAgentIds, apiReachable, env).join('; ');
182
+ };
183
+ /**
184
+ * The clauses above, unjoined and without reading the filesystem, so a
185
+ * caller holding unsaved state can render the same verdict. The first
186
+ * element is always the verdict itself; the rest qualify it.
187
+ *
188
+ * Split out because setup's Approvals row wrote its own version of this
189
+ * sentence, and every review round found it short a different clause the
190
+ * daemon actually applies — the headless exceptions, an explicit
191
+ * `timeoutMs: 0`, the control API on a mixed roster. There is one renderer
192
+ * now; a menu that wants a shorter line takes fewer clauses, never
193
+ * different words.
194
+ */
195
+ export const unattendedReachParts = (mode, approvals, channels, servedAgentIds, apiReachable, env) => {
196
+ if (mode === 'headless') {
197
+ return [`headless — an uncovered gated call is refused. Already-authorized ones still run: ${ALREADY_AUTHORIZED}`];
198
+ }
199
+ // The same condition `runServe` reports at startup, through the same
200
+ // helper: an agent is askable when its tokens are stored and something is
201
+ // installed to render the request.
202
+ const { askable, covered, noFallback } = classifyApprovalReach(approvals, channels, servedAgentIds, env);
203
+ // Qualified the same way the headless line is: the engine allows an
204
+ // already-authorized call before it asks anyone, so an unqualified "asks
205
+ // in Slack" hides unattended capability in precisely the configuration
206
+ // where Slack is set up correctly.
207
+ //
208
+ // The *verdict* is composed with the control API in view rather than
209
+ // corrected afterwards. `describeApprovers` answers a Slack question and
210
+ // is right to — `runServe` asks it before anything else is known — but
211
+ // its no-channel and no-approver answers both end in "denied", which is
212
+ // false wherever `POST /api/v1/approvals` can settle the call. Appending
213
+ // the API as a later clause left the two halves contradicting each other.
214
+ const slack = describeApprovers(approvals, askable);
215
+ // An explicit `timeoutMs: 0` is documented as "wait indefinitely", and
216
+ // the gateway arms no timer for it — so a call nobody answers is not
217
+ // eventually denied, it is parked for the life of the daemon. Promising
218
+ // a denial understates that, and holding a turn open forever is the more
219
+ // alarming outcome to leave unsaid.
220
+ const expires = approvals.timeoutMs !== 0;
221
+ const unanswered = expires ? 'before the timeout denies it' : 'and nothing else will — this daemon\'s approval timeout is 0, so it parks indefinitely';
222
+ // Three verdicts, selected by what can actually receive the request.
223
+ //
224
+ // With nobody askable there is no Slack adapter in the picture at all,
225
+ // so neither of the first two may say the call "asks in Slack" —
226
+ // appending `describeApprovers` to that phrasing produced a sentence
227
+ // that asked Slack and then said no Slack was running.
228
+ //
229
+ // The control API is offered only where Slack leaves a request parked.
230
+ // The Slack adapter handles the same event synchronously and *denies*
231
+ // when an agent has no approvers or no conversation to ask in, so for an
232
+ // agent it covers there is nothing left for an API client to answer.
233
+ // That is only true of agents it covers: one with no tokens at all
234
+ // reaches no adapter, and its request stays parked.
235
+ const verdict = () => {
236
+ if (askable.length === 0) {
237
+ return apiReachable
238
+ ? `remote — an uncovered gated call parks with no Slack channel to ask through, so the control API is the only way to answer it ${unanswered}`
239
+ : 'remote — an uncovered gated call parks with no channel to ask through and no control API to answer it, so it '
240
+ + (expires
241
+ ? 'waits out the approval timeout and is denied'
242
+ : 'is never answered: this daemon\'s approval timeout is 0, so it parks indefinitely');
243
+ }
244
+ // "Parks and asks" is false for an agent the adapter declines: with no
245
+ // approvers configured it calls `resolveApproval(deny)` synchronously
246
+ // (channel-slack decline()), so nothing parks and nobody is asked.
247
+ // Same defect as the branch above, one case over — found by auditing
248
+ // the rest of this function after that one, not by review.
249
+ if (covered.length === 0) {
250
+ return 'remote — an uncovered gated call reaches Slack and is denied on arrival, because no approvers are configured';
251
+ }
252
+ return `remote — an uncovered gated call parks and asks in Slack, ${slack}`;
253
+ };
254
+ const parts = [verdict()];
255
+ // The reverse of a stale token, and the failure that actually bites: an
256
+ // agent the daemon serves that no channel can ask for parks its gated
257
+ // calls until the timeout denies them. `runServe` warns about exactly
258
+ // this once its roster loads; the difference here is only that both
259
+ // halves are in view from the start.
260
+ //
261
+ // Named only when *some* agent is askable: with none, the verdict above
262
+ // has already said no Slack channel is running at all, and listing every
263
+ // served agent under it repeats that in more words.
264
+ const unreachable = askable.length === 0
265
+ ? []
266
+ : (servedAgentIds ?? []).filter((agentId) => !askable.includes(agentId));
267
+ if (unreachable.length > 0) {
268
+ // Slack is not the only way to answer. `GET /api/v1/approvals` lists
269
+ // what is parked and `POST` settles it, so with the control API up
270
+ // these calls wait for a client rather than for the timeout — a very
271
+ // different thing to tell an operator.
272
+ parts.push(apiReachable
273
+ ? `no Slack channel can ask for ${unreachable.join(', ')}, so their gated calls park until the control `
274
+ + `API answers them${expires ? ' or the timeout denies them' : ' — with a timeout of 0, nothing else ever will'}`
275
+ : `no channel can ask for ${unreachable.join(', ')}, so their gated calls `
276
+ + (expires ? 'wait out the timeout and are denied' : 'park indefinitely: this daemon\'s approval timeout is 0'));
277
+ }
278
+ // Stored tokens are a *configured* route, not a live one. The adapter
279
+ // pushes a connection only after `auth.test()` and `socket.start()` both
280
+ // succeed, and `renderApprovalRequest` denies undeliverable for a
281
+ // configured agent with no live connection — so a revoked token or a
282
+ // dead app token turns "asks in Slack" into "denies on arrival". This
283
+ // command reads config and manifests by design and starts no daemon, so
284
+ // it cannot know which; saying so is the only honest option, and the
285
+ // daemon log is where the answer actually is (`warn` writes there, so
286
+ // `slack: could not connect <agent>` is in `stratus logs`).
287
+ if (covered.length > 0) {
288
+ parts.push('whether those apps are connected is not something this command can see — it reads config, '
289
+ + 'and one whose token no longer authenticates denies its gated calls instead of asking; '
290
+ + '`stratus logs` shows which came up');
291
+ }
292
+ // Approvers with nowhere to be asked outside their own thread. A turn
293
+ // that did not start in Slack — the API, the dashboard, a delegation —
294
+ // reaches the adapter with no destination and is denied undeliverable,
295
+ // so "approvers set" is only half an answer without a fallback channel.
296
+ if (noFallback.length > 0) {
297
+ parts.push(`${noFallback.join(', ')} ${noFallback.length === 1 ? 'has' : 'have'} no slackChannel, `
298
+ + 'so only turns already in Slack can be asked');
299
+ }
300
+ // What an "always allow" answer persists depends on what the call names,
301
+ // and mostly it is not the session: `createPermissionPolicy` maps an
302
+ // unscoped gated tool to a standing grant that outlives every restart,
303
+ // a command to a scope, a click to a site, and only a schedule's
304
+ // destination to the session. Saying "for the rest of its session" flat
305
+ // understated durable unattended access, which is the wrong direction to
306
+ // be wrong about approvals in.
307
+ // The session case is a call scoped by *destination*, which an ordinary
308
+ // outbound `message.send` is — not only a scheduled one. Naming the
309
+ // schedule alone read as though the everyday case were durable.
310
+ parts.push('an "always allow" answer persists — a standing grant for an unscoped tool, a command scope, '
311
+ + 'or a site, all until revoked; only a call scoped by destination, such as message.send, '
312
+ + 'lasts just the session');
313
+ return parts;
314
+ };
315
+ //# sourceMappingURL=approvals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approvals.js","sourceRoot":"","sources":["../src/approvals.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import { type StratusConfigFile, type StratusProviderName } from '@stratusagent/state';
2
+ import type { CliStreams, CliEnvironment } from '../environment.ts';
3
+ import type { ParsedAgentNewCommand } from '../parse.ts';
4
+ /**
5
+ * The provider/model a newly created soul pins. Frontmatter pins what a run
6
+ * from this directory would actually use: env vars outrank the active
7
+ * config file (project-local or explicit first, global otherwise), and no
8
+ * provider anywhere falls back to demo — the exact precedence of stratus
9
+ * run. Demo produces no model at all: the soul keeps following the
10
+ * machine's configuration instead of demanding credentials nothing has
11
+ * signed in for. The active config's model was written for the provider
12
+ * named in that config, so it only travels into the soul when they still
13
+ * match; otherwise the selected provider's own default model stands in.
14
+ *
15
+ * Exported for tests: the interactive path that consumes it needs a TTY.
16
+ */
17
+ export declare const soulPinForNewAgent: (activeConfig: StratusConfigFile, processEnv: NodeJS.ProcessEnv) => {
18
+ provider: StratusProviderName;
19
+ model?: string;
20
+ };
21
+ export declare const runAgentNew: (command: ParsedAgentNewCommand, streams: CliStreams, env?: CliEnvironment) => Promise<number>;
22
+ //# sourceMappingURL=agent-new.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-new.d.ts","sourceRoot":"","sources":["../../src/commands/agent-new.ts"],"names":[],"mappings":"AAGA,OAAO,EAUL,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACzB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAiB,MAAM,mBAAmB,CAAC;AAEnF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAIzD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,kBAAkB,GAC7B,cAAc,iBAAiB,EAC/B,YAAY,MAAM,CAAC,UAAU,KAC5B;IAAE,QAAQ,EAAE,mBAAmB,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAoBjD,CAAC;AAEF,eAAO,MAAM,WAAW,GACtB,SAAS,qBAAqB,EAC9B,SAAS,UAAU,EACnB,MAAK,cAAmB,KACvB,OAAO,CAAC,MAAM,CAoIhB,CAAC"}