@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/README.md CHANGED
@@ -7,7 +7,7 @@ put them in Slack — from one command.
7
7
  <!-- Pinned to the commit that added the asset so it renders on any ref
8
8
  (including this branch pre-merge) and on npm, which needs an absolute
9
9
  URL. Replacing the banner means updating this pin. -->
10
- ![Stratus Labs](https://raw.githubusercontent.com/stratuslabs/agent/265bc175e60fd901b339095215439505606755bc/docs/assets/stratus-banner.png)
10
+ ![Stratus Labs](https://raw.githubusercontent.com/stratuslabs/agent/main/docs/assets/stratus-banner.png)
11
11
 
12
12
  ## Install
13
13
 
@@ -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,18 @@ 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
52
56
  stratus memory list ava # every live fact, with the trust label it carries
53
57
  stratus memory reassert ava --trust user --all-unknown # vouch for the facts with no recorded origin
54
58
  stratus session rollover <session-id> # archive a conversation's transcript and start the same id over
@@ -75,12 +79,14 @@ Full reference with every subcommand:
75
79
  | `--no-log-file` | `serve`: do not write `~/.stratus/logs/stratusd.jsonl` |
76
80
  | `--no-api` | `serve`: do not serve the control API |
77
81
  | `--api-host`, `--api-port` | `serve`: control API bind (default `127.0.0.1:4123`) |
78
- | `--gateway <url>`, `--token` | `agents`, `skill reload`, `restart`, `session rollover`: 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 |
79
84
  | `--trust <level>` | `memory list`: show only that label; `memory reassert`: the label to record (`user`, `agent`, `unknown`, `external`) |
80
85
  | `--all-unknown` | `memory reassert`: every live fact with no recorded origin |
81
86
  | `--agent <id>` | `credential set`/`remove`: that agent's own key rather than the fleet's shared one |
82
87
  | `--port`, `--host`, `--no-open` | `dashboard`: where a daemon it starts should bind; skip opening the browser |
83
88
  | `--no-login` | `service install`: install without the start-at-login trigger |
89
+ | `--version`, `-v` | Print this build's version and exit |
84
90
  | `-f`, `-n`, `--agent`, `--session` | `logs`: follow, backlog size, and filters |
85
91
 
86
92
  Precedence: flags → `STRATUS_*` env vars → soul file hints → config file.
@@ -0,0 +1,93 @@
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>,
17
+ /**
18
+ * The mode was defaulted rather than chosen. `always` chosen with a
19
+ * flag needs no commentary; `always` reached because nobody could be
20
+ * asked gets one line, the first time a call that would have asked runs.
21
+ */
22
+ defaulted?: boolean) => ApprovalPolicy;
23
+ /** One line saying whose messages arrive as the operator's, per Slack agent. */
24
+ export declare const describePrincipals: (principals: PrincipalsConfig, agentIds: string[]) => string;
25
+ export declare const describeApprovers: (approvals: ApprovalsConfig, agentIds: string[]) => string;
26
+ /**
27
+ * What a gated call would actually meet on this machine.
28
+ *
29
+ * The mode is not the answer on its own, in both directions. `headless`
30
+ * refuses a gated call *last*: the engine checks the standing tool grants,
31
+ * the approved command scopes, and the approved sites first, so an agent
32
+ * that was ever told "always allow" runs that tool unattended for good
33
+ * (`stratus grants <agent>` is what lists them). And `remote` only asks if
34
+ * somebody can be asked — with no channel to render the request a gated
35
+ * call waits out the timeout, and with no approver configured it is denied
36
+ * on arrival, which is `headless` by another name.
37
+ *
38
+ * Stating either as "gated calls are refused" or "gated calls are asked in
39
+ * Slack" would be wrong in exactly the configurations an operator runs this
40
+ * command to understand.
41
+ */
42
+ /**
43
+ * Who can actually be asked, and what is missing for the rest. Split out of
44
+ * the sentence below because two surfaces need the *answer*: `stratus
45
+ * plugins` renders it as a paragraph, setup's Approvals row as a menu
46
+ * summary, and a second hand-rolled copy of "who is askable" drifted from
47
+ * this one within three PRs — stored tokens read as reachable agents when
48
+ * the package was absent, when the agent had left the roster, and when the
49
+ * only route left was the control API.
50
+ */
51
+ export interface ApprovalReach {
52
+ /** Tokens stored, package installed, agent still served. */
53
+ askable: string[];
54
+ /** Of those, the ones an approver is named for — the rest are denied on arrival. */
55
+ covered: string[];
56
+ /** Of those, the ones with no conversation to ask in for a turn that did not start in Slack. */
57
+ noFallback: string[];
58
+ }
59
+ export declare const classifyApprovalReach: (approvals: ApprovalsConfig, channels: ChannelCredentials, servedAgentIds: readonly string[] | undefined, env: CliEnvironment) => ApprovalReach;
60
+ export declare const describeUnattendedReach: (mode: "headless" | "remote", approvals: ApprovalsConfig, env: CliEnvironment,
61
+ /**
62
+ * The agents actually being served, or undefined when the roster did not
63
+ * load. Stored Slack tokens outlive the agent they were stored for, and
64
+ * the adapter skips an id the gateway is not serving — so a token with no
65
+ * agent behind it must not read as somebody who can be asked.
66
+ *
67
+ * `runServe` prints its own line without this intersection, and is right
68
+ * to: at startup the roster has not loaded yet. It reports the reverse
69
+ * direction separately once it has one, warning about served agents no
70
+ * channel can ask for. Here both are in view from the start.
71
+ */
72
+ servedAgentIds: readonly string[] | undefined,
73
+ /**
74
+ * Whether the control API would be serving. It is a second way to answer
75
+ * a parked call — `GET /api/v1/approvals` lists them, `POST` settles one
76
+ * — so an agent no Slack channel can ask for is not necessarily an agent
77
+ * nobody can ask.
78
+ */
79
+ apiReachable: boolean) => Promise<string>;
80
+ /**
81
+ * The clauses above, unjoined and without reading the filesystem, so a
82
+ * caller holding unsaved state can render the same verdict. The first
83
+ * element is always the verdict itself; the rest qualify it.
84
+ *
85
+ * Split out because setup's Approvals row wrote its own version of this
86
+ * sentence, and every review round found it short a different clause the
87
+ * daemon actually applies — the headless exceptions, an explicit
88
+ * `timeoutMs: 0`, the control API on a mixed roster. There is one renderer
89
+ * now; a menu that wants a shorter line takes fewer clauses, never
90
+ * different words.
91
+ */
92
+ export declare const unattendedReachParts: (mode: "headless" | "remote", approvals: ApprovalsConfig, channels: ChannelCredentials, servedAgentIds: readonly string[] | undefined, apiReachable: boolean, env: CliEnvironment) => string[];
93
+ //# 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;AACzC;;;;GAIG;AACH,mBAAiB,KAChB,cA2EF,CAAC;AAEF,gFAAgF;AAChF,eAAO,MAAM,kBAAkB,GAAI,YAAY,gBAAgB,EAAE,UAAU,MAAM,EAAE,KAAG,MA+BrF,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,368 @@
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
+ /**
57
+ * The mode was defaulted rather than chosen. `always` chosen with a
58
+ * flag needs no commentary; `always` reached because nobody could be
59
+ * asked gets one line, the first time a call that would have asked runs.
60
+ */
61
+ defaulted = false) => {
62
+ if (mode === 'always') {
63
+ if (!defaulted) {
64
+ return new AllowAllApprovalPolicy();
65
+ }
66
+ let noted = false;
67
+ return {
68
+ async approve(context) {
69
+ if (!noted && context.risk !== 'safe') {
70
+ noted = true;
71
+ writeLine(streams.stderr, `Note: running ${context.call.toolName} without asking — stdin is not a terminal and --approvals was not given. `
72
+ + 'Pass --approvals never to refuse gated tools.');
73
+ }
74
+ return true;
75
+ },
76
+ };
77
+ }
78
+ if (mode === 'never') {
79
+ return {
80
+ async approve() {
81
+ return false;
82
+ },
83
+ };
84
+ }
85
+ // A caller that already owns stdin (chat's readline) supplies its own
86
+ // asker — two readers on one stream would race for the same bytes.
87
+ // `gated` is `ask` for the calls that matter: a `safe` tool is what the
88
+ // daemon runs unattended, and asking a person to confirm a memory read
89
+ // is how they learn to type `y` without reading the question. `ask`
90
+ // itself still asks about everything — the word says so.
91
+ if (mode === 'gated') {
92
+ const asking = createApprovalPolicy('ask', streams, env, ask);
93
+ return {
94
+ async approve(context) {
95
+ return context.risk === 'safe' ? true : asking.approve(context);
96
+ },
97
+ };
98
+ }
99
+ if (ask) {
100
+ return {
101
+ async approve(context) {
102
+ const approved = approvalOrigin(context);
103
+ const answer = await ask(`Approve tool call ${describeApprovalCall(context)}? [y/N] `);
104
+ return /^y(es)?$/i.test(answer.trim()) && stillOnApprovedPage(context, approved, streams);
105
+ },
106
+ };
107
+ }
108
+ return {
109
+ async approve(context) {
110
+ const input = env.approvalInput ?? process.stdin;
111
+ const readline = createInterface({ input, terminal: false });
112
+ const approved = approvalOrigin(context);
113
+ // Prompt on stderr so stdout stays parseable (e.g. --format json).
114
+ streams.stderr.write(`Approve tool call ${describeApprovalCall(context)}? [y/N] `);
115
+ try {
116
+ const answer = await new Promise((resolve) => {
117
+ readline.once('line', resolve);
118
+ readline.once('close', () => resolve(''));
119
+ });
120
+ writeLine(streams.stderr);
121
+ return /^y(es)?$/i.test(answer.trim()) && stillOnApprovedPage(context, approved, streams);
122
+ }
123
+ finally {
124
+ readline.close();
125
+ }
126
+ },
127
+ };
128
+ };
129
+ /** One line saying whose messages arrive as the operator's, per Slack agent. */
130
+ export const describePrincipals = (principals, agentIds) => {
131
+ const covered = agentIds.filter((agentId) => (resolveAgentPrincipals(principals, agentId).slackUsers ?? []).length > 0);
132
+ // Which agents refuse the unlisted outright, so an operator reading the
133
+ // startup line knows whether the list is a label or a door. Worked out
134
+ // before the no-list case: a closed agent with nobody listed refuses
135
+ // everyone, which is a valid configuration and the opposite of "every
136
+ // sender is unknown".
137
+ const closed = agentIds.filter((agentId) => resolveAgentPrincipals(principals, agentId).admit === 'principals');
138
+ const door = closed.length === 0
139
+ ? '; every agent still admits unlisted senders as unknown (principals.admit: "principals" refuses them)'
140
+ : closed.length === agentIds.length
141
+ ? '; unlisted senders are refused'
142
+ : `; unlisted senders are refused by ${closed.join(', ')} and admitted as unknown by the rest`;
143
+ if (covered.length === 0) {
144
+ return closed.length === 0
145
+ ? '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'
146
+ : `no principals listed, so every Slack sender is unknown${door} — nobody at all can talk to ${closed.join(', ')} until principals.slackUsers names someone`;
147
+ }
148
+ // An uncovered agent's senders are all unknown only if it admits them: a
149
+ // closed agent with nobody listed refuses everyone, and saying its
150
+ // senders are "all unknown" and then "refused" in one line is a summary
151
+ // that contradicts itself about the one thing it exists to say.
152
+ const uncovered = agentIds.filter((agentId) => !covered.includes(agentId));
153
+ const uncoveredOpen = uncovered.filter((agentId) => !closed.includes(agentId));
154
+ const uncoveredClosed = uncovered.filter((agentId) => closed.includes(agentId));
155
+ return `principals set for ${covered.join(', ')}`
156
+ + (uncoveredOpen.length > 0 ? `; none for ${uncoveredOpen.join(', ')}, whose Slack senders are all unknown` : '')
157
+ + (uncoveredClosed.length > 0
158
+ ? `; none for ${uncoveredClosed.join(', ')}, who refuse every sender until principals.slackUsers names someone`
159
+ : '')
160
+ + door;
161
+ };
162
+ /**
163
+ * Which of these agents the Slack adapter would actually ask about, and
164
+ * which it would decline for. Separated from the sentence below because
165
+ * two callers need the *answer* and only one needs it as prose: a summary
166
+ * that says a call "asks in Slack" must not say it about an agent the
167
+ * adapter denies on arrival.
168
+ */
169
+ const classifyApprovers = (approvals, agentIds) => {
170
+ const covered = agentIds.filter((agentId) => (resolveAgentApprovals(approvals, agentId).slackApprovers ?? []).length > 0);
171
+ return { covered, uncovered: agentIds.filter((agentId) => !covered.includes(agentId)) };
172
+ };
173
+ export const describeApprovers = (approvals, agentIds) => {
174
+ if (agentIds.length === 0) {
175
+ return 'but no channel is running to ask through, so gated calls will wait out the approval timeout and then be denied';
176
+ }
177
+ const { covered, uncovered } = classifyApprovers(approvals, agentIds);
178
+ if (covered.length === 0) {
179
+ return 'but no approvers are configured, so every gated call is denied on arrival';
180
+ }
181
+ return uncovered.length === 0
182
+ ? `approvers set for ${covered.join(', ')}`
183
+ : `approvers set for ${covered.join(', ')}; none for ${uncovered.join(', ')}, whose calls are denied on arrival`;
184
+ };
185
+ /**
186
+ * The ways a gated call is already authorized before either mode's decision
187
+ * is reached — the standing tool grants, the approved command scopes, the
188
+ * approved sites, and a schedule's pre-authorized destination, in the order
189
+ * `createPermissionPolicy` checks them.
190
+ *
191
+ * Written once because it is read twice: enumerating it separately per mode
192
+ * is what left the destination path out of one of them and out of the other
193
+ * entirely. It is prose about a rule `@stratusagent/permissions` owns, so
194
+ * when that engine gains a path this string is what has to follow it.
195
+ */
196
+ const ALREADY_AUTHORIZED = 'standing grants, approved command scopes and sites (stratus grants <agent>), '
197
+ + 'and destinations pre-authorized with a schedule (stratus schedules)';
198
+ export const classifyApprovalReach = (approvals, channels, servedAgentIds, env) => {
199
+ // An adapter that is not installed renders nothing, so its stored tokens
200
+ // are not a route — `runServe` starts without the Slack channel and says
201
+ // so. Undefined served ids means the roster did not load, which is not
202
+ // evidence that any token is orphaned.
203
+ const stored = packageInstalled('@stratusagent/channel-slack', env)
204
+ ? Object.keys(channels.slack ?? {})
205
+ : [];
206
+ const askable = servedAgentIds === undefined
207
+ ? stored
208
+ : stored.filter((agentId) => servedAgentIds.includes(agentId));
209
+ const { covered } = classifyApprovers(approvals, askable);
210
+ const noFallback = covered.filter((agentId) => resolveAgentApprovals(approvals, agentId).slackChannel === undefined);
211
+ return { askable, covered, noFallback };
212
+ };
213
+ export const describeUnattendedReach = async (mode, approvals, env,
214
+ /**
215
+ * The agents actually being served, or undefined when the roster did not
216
+ * load. Stored Slack tokens outlive the agent they were stored for, and
217
+ * the adapter skips an id the gateway is not serving — so a token with no
218
+ * agent behind it must not read as somebody who can be asked.
219
+ *
220
+ * `runServe` prints its own line without this intersection, and is right
221
+ * to: at startup the roster has not loaded yet. It reports the reverse
222
+ * direction separately once it has one, warning about served agents no
223
+ * channel can ask for. Here both are in view from the start.
224
+ */
225
+ servedAgentIds,
226
+ /**
227
+ * Whether the control API would be serving. It is a second way to answer
228
+ * a parked call — `GET /api/v1/approvals` lists them, `POST` settles one
229
+ * — so an agent no Slack channel can ask for is not necessarily an agent
230
+ * nobody can ask.
231
+ */
232
+ apiReachable) => {
233
+ const channels = await loadChannelCredentials(env);
234
+ return unattendedReachParts(mode, approvals, channels, servedAgentIds, apiReachable, env).join('; ');
235
+ };
236
+ /**
237
+ * The clauses above, unjoined and without reading the filesystem, so a
238
+ * caller holding unsaved state can render the same verdict. The first
239
+ * element is always the verdict itself; the rest qualify it.
240
+ *
241
+ * Split out because setup's Approvals row wrote its own version of this
242
+ * sentence, and every review round found it short a different clause the
243
+ * daemon actually applies — the headless exceptions, an explicit
244
+ * `timeoutMs: 0`, the control API on a mixed roster. There is one renderer
245
+ * now; a menu that wants a shorter line takes fewer clauses, never
246
+ * different words.
247
+ */
248
+ export const unattendedReachParts = (mode, approvals, channels, servedAgentIds, apiReachable, env) => {
249
+ if (mode === 'headless') {
250
+ return [`headless — an uncovered gated call is refused. Already-authorized ones still run: ${ALREADY_AUTHORIZED}`];
251
+ }
252
+ // The same condition `runServe` reports at startup, through the same
253
+ // helper: an agent is askable when its tokens are stored and something is
254
+ // installed to render the request.
255
+ const { askable, covered, noFallback } = classifyApprovalReach(approvals, channels, servedAgentIds, env);
256
+ // Qualified the same way the headless line is: the engine allows an
257
+ // already-authorized call before it asks anyone, so an unqualified "asks
258
+ // in Slack" hides unattended capability in precisely the configuration
259
+ // where Slack is set up correctly.
260
+ //
261
+ // The *verdict* is composed with the control API in view rather than
262
+ // corrected afterwards. `describeApprovers` answers a Slack question and
263
+ // is right to — `runServe` asks it before anything else is known — but
264
+ // its no-channel and no-approver answers both end in "denied", which is
265
+ // false wherever `POST /api/v1/approvals` can settle the call. Appending
266
+ // the API as a later clause left the two halves contradicting each other.
267
+ const slack = describeApprovers(approvals, askable);
268
+ // An explicit `timeoutMs: 0` is documented as "wait indefinitely", and
269
+ // the gateway arms no timer for it — so a call nobody answers is not
270
+ // eventually denied, it is parked for the life of the daemon. Promising
271
+ // a denial understates that, and holding a turn open forever is the more
272
+ // alarming outcome to leave unsaid.
273
+ const expires = approvals.timeoutMs !== 0;
274
+ const unanswered = expires ? 'before the timeout denies it' : 'and nothing else will — this daemon\'s approval timeout is 0, so it parks indefinitely';
275
+ // Three verdicts, selected by what can actually receive the request.
276
+ //
277
+ // With nobody askable there is no Slack adapter in the picture at all,
278
+ // so neither of the first two may say the call "asks in Slack" —
279
+ // appending `describeApprovers` to that phrasing produced a sentence
280
+ // that asked Slack and then said no Slack was running.
281
+ //
282
+ // The control API is offered only where Slack leaves a request parked.
283
+ // The Slack adapter handles the same event synchronously and *denies*
284
+ // when an agent has no approvers or no conversation to ask in, so for an
285
+ // agent it covers there is nothing left for an API client to answer.
286
+ // That is only true of agents it covers: one with no tokens at all
287
+ // reaches no adapter, and its request stays parked.
288
+ const verdict = () => {
289
+ if (askable.length === 0) {
290
+ return apiReachable
291
+ ? `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}`
292
+ : 'remote — an uncovered gated call parks with no channel to ask through and no control API to answer it, so it '
293
+ + (expires
294
+ ? 'waits out the approval timeout and is denied'
295
+ : 'is never answered: this daemon\'s approval timeout is 0, so it parks indefinitely');
296
+ }
297
+ // "Parks and asks" is false for an agent the adapter declines: with no
298
+ // approvers configured it calls `resolveApproval(deny)` synchronously
299
+ // (channel-slack decline()), so nothing parks and nobody is asked.
300
+ // Same defect as the branch above, one case over — found by auditing
301
+ // the rest of this function after that one, not by review.
302
+ if (covered.length === 0) {
303
+ return 'remote — an uncovered gated call reaches Slack and is denied on arrival, because no approvers are configured';
304
+ }
305
+ return `remote — an uncovered gated call parks and asks in Slack, ${slack}`;
306
+ };
307
+ const parts = [verdict()];
308
+ // The reverse of a stale token, and the failure that actually bites: an
309
+ // agent the daemon serves that no channel can ask for parks its gated
310
+ // calls until the timeout denies them. `runServe` warns about exactly
311
+ // this once its roster loads; the difference here is only that both
312
+ // halves are in view from the start.
313
+ //
314
+ // Named only when *some* agent is askable: with none, the verdict above
315
+ // has already said no Slack channel is running at all, and listing every
316
+ // served agent under it repeats that in more words.
317
+ const unreachable = askable.length === 0
318
+ ? []
319
+ : (servedAgentIds ?? []).filter((agentId) => !askable.includes(agentId));
320
+ if (unreachable.length > 0) {
321
+ // Slack is not the only way to answer. `GET /api/v1/approvals` lists
322
+ // what is parked and `POST` settles it, so with the control API up
323
+ // these calls wait for a client rather than for the timeout — a very
324
+ // different thing to tell an operator.
325
+ parts.push(apiReachable
326
+ ? `no Slack channel can ask for ${unreachable.join(', ')}, so their gated calls park until the control `
327
+ + `API answers them${expires ? ' or the timeout denies them' : ' — with a timeout of 0, nothing else ever will'}`
328
+ : `no channel can ask for ${unreachable.join(', ')}, so their gated calls `
329
+ + (expires ? 'wait out the timeout and are denied' : 'park indefinitely: this daemon\'s approval timeout is 0'));
330
+ }
331
+ // Stored tokens are a *configured* route, not a live one. The adapter
332
+ // pushes a connection only after `auth.test()` and `socket.start()` both
333
+ // succeed, and `renderApprovalRequest` denies undeliverable for a
334
+ // configured agent with no live connection — so a revoked token or a
335
+ // dead app token turns "asks in Slack" into "denies on arrival". This
336
+ // command reads config and manifests by design and starts no daemon, so
337
+ // it cannot know which; saying so is the only honest option, and the
338
+ // daemon log is where the answer actually is (`warn` writes there, so
339
+ // `slack: could not connect <agent>` is in `stratus logs`).
340
+ if (covered.length > 0) {
341
+ parts.push('whether those apps are connected is not something this command can see — it reads config, '
342
+ + 'and one whose token no longer authenticates denies its gated calls instead of asking; '
343
+ + '`stratus logs` shows which came up');
344
+ }
345
+ // Approvers with nowhere to be asked outside their own thread. A turn
346
+ // that did not start in Slack — the API, the dashboard, a delegation —
347
+ // reaches the adapter with no destination and is denied undeliverable,
348
+ // so "approvers set" is only half an answer without a fallback channel.
349
+ if (noFallback.length > 0) {
350
+ parts.push(`${noFallback.join(', ')} ${noFallback.length === 1 ? 'has' : 'have'} no slackChannel, `
351
+ + 'so only turns already in Slack can be asked');
352
+ }
353
+ // What an "always allow" answer persists depends on what the call names,
354
+ // and mostly it is not the session: `createPermissionPolicy` maps an
355
+ // unscoped gated tool to a standing grant that outlives every restart,
356
+ // a command to a scope, a click to a site, and only a schedule's
357
+ // destination to the session. Saying "for the rest of its session" flat
358
+ // understated durable unattended access, which is the wrong direction to
359
+ // be wrong about approvals in.
360
+ // The session case is a call scoped by *destination*, which an ordinary
361
+ // outbound `message.send` is — not only a scheduled one. Naming the
362
+ // schedule alone read as though the everyday case were durable.
363
+ parts.push('an "always allow" answer persists — a standing grant for an unscoped tool, a command scope, '
364
+ + 'or a site, all until revoked; only a call scoped by destination, such as message.send, '
365
+ + 'lasts just the session');
366
+ return parts;
367
+ };
368
+ //# 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,EAWL,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;CAyBjD,CAAC;AAEF,eAAO,MAAM,WAAW,GACtB,SAAS,qBAAqB,EAC9B,SAAS,UAAU,EACnB,MAAK,cAAmB,KACvB,OAAO,CAAC,MAAM,CAoIhB,CAAC"}