@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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.js","sourceRoot":"","sources":["../../src/commands/logs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { CliStreams, CliEnvironment } from '../environment.ts';
2
+ import type { ParsedMemoryCommand } from '../parse.ts';
3
+ /**
4
+ * `stratus memory list|reassert` — the operator's half of provenance.
5
+ *
6
+ * `list` shows what an agent's store holds with the label each entry
7
+ * carries, because an operator cannot re-assert what they cannot see.
8
+ * `reassert` is the one way a label ever rises: it appends a re-assertion
9
+ * record to the JSONL (never rewrites a line), so a running daemon — which
10
+ * re-reads the file on every read — sees it at once. Operator only, on
11
+ * purpose: no tool exposes this, because an agent re-labelling its own
12
+ * memory as trusted is the attack writing its own permission slip.
13
+ *
14
+ * `--all-unknown` exists for the upgrade: every entry written before labels
15
+ * existed reads `unknown`, and the injected slice of such a store makes
16
+ * every session `unknown` on its first turn. Re-asserting the entries the
17
+ * agent actually surfaces, once, is the bounded work that drains it.
18
+ */
19
+ export declare const runMemory: (command: ParsedMemoryCommand, streams: CliStreams, env?: CliEnvironment) => Promise<number>;
20
+ //# sourceMappingURL=memory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/commands/memory.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEpE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,SAAS,GACpB,SAAS,mBAAmB,EAC5B,SAAS,UAAU,EACnB,MAAK,cAAmB,KACvB,OAAO,CAAC,MAAM,CAkGhB,CAAC"}
@@ -0,0 +1,110 @@
1
+ import { escapeControlCharacters, memoryEntryTrust } from '@stratusagent/core';
2
+ import { createFileMemoryStore, memoryFilePath, migrateLegacyMemory, withLegacyDefaultMemories, } from '@stratusagent/state';
3
+ import { writeLine } from "../io.js";
4
+ /**
5
+ * `stratus memory list|reassert` — the operator's half of provenance.
6
+ *
7
+ * `list` shows what an agent's store holds with the label each entry
8
+ * carries, because an operator cannot re-assert what they cannot see.
9
+ * `reassert` is the one way a label ever rises: it appends a re-assertion
10
+ * record to the JSONL (never rewrites a line), so a running daemon — which
11
+ * re-reads the file on every read — sees it at once. Operator only, on
12
+ * purpose: no tool exposes this, because an agent re-labelling its own
13
+ * memory as trusted is the attack writing its own permission slip.
14
+ *
15
+ * `--all-unknown` exists for the upgrade: every entry written before labels
16
+ * existed reads `unknown`, and the injected slice of such a store makes
17
+ * every session `unknown` on its first turn. Re-asserting the entries the
18
+ * agent actually surfaces, once, is the bounded work that drains it.
19
+ */
20
+ export const runMemory = async (command, streams, env = {}) => {
21
+ // Only the writer folds a legacy per-directory store in: `list` is the
22
+ // read-only command a downgraded build is allowed to run against newer
23
+ // state, and a migration is a write.
24
+ if (command.action === 'reassert') {
25
+ await migrateLegacyMemory(env);
26
+ }
27
+ const store = withLegacyDefaultMemories(createFileMemoryStore(memoryFilePath(env)));
28
+ // Two ways an entry reads `unknown`, and only one is the upgrade case:
29
+ // no label at all (written before labels existed, or added by hand), or a
30
+ // recorded `unknown` — a session that heard from a sender nobody vouched
31
+ // for, or ran a shell command. The bulk re-assertion is for the first;
32
+ // the second may hold a stranger's text and is named by id, once read.
33
+ const unlabelled = (entry) => entry.trust === undefined;
34
+ const describeEntry = (entry) => ({
35
+ id: entry.id,
36
+ trust: memoryEntryTrust(entry),
37
+ content: entry.content,
38
+ createdAt: entry.createdAt,
39
+ ...(entry.origin ? { origin: entry.origin } : {}),
40
+ });
41
+ if (command.action === 'list') {
42
+ const live = (await store.list(command.agentId)).entries
43
+ .filter((entry) => command.trust === undefined || memoryEntryTrust(entry) === command.trust);
44
+ if (command.format === 'json') {
45
+ writeLine(streams.stdout, JSON.stringify({ agentId: command.agentId, entries: live.map(describeEntry) }, null, 2));
46
+ return 0;
47
+ }
48
+ if (live.length === 0) {
49
+ writeLine(streams.stdout, command.trust === undefined
50
+ ? `${command.agentId} has no live memory entries in ${memoryFilePath(env)}.`
51
+ : `${command.agentId} has no live memory entries at ${command.trust}.`);
52
+ return 0;
53
+ }
54
+ for (const entry of live) {
55
+ const taintedBy = entry.origin?.taintedBy ? ` (tainted by ${entry.origin.taintedBy})` : '';
56
+ const unrecorded = unlabelled(entry) ? ' (no recorded origin)' : '';
57
+ writeLine(streams.stdout, `${entry.id} [${memoryEntryTrust(entry)}]${taintedBy}${unrecorded}`);
58
+ writeLine(streams.stdout, ` ${escapeControlCharacters(entry.content)}`);
59
+ }
60
+ if (command.trust === undefined) {
61
+ const unlabelledCount = live.filter(unlabelled).length;
62
+ const recordedUnknown = live.filter((entry) => !unlabelled(entry) && memoryEntryTrust(entry) === 'unknown').length;
63
+ if (unlabelledCount > 0) {
64
+ writeLine(streams.stdout, '');
65
+ writeLine(streams.stdout, `${unlabelledCount} entr${unlabelledCount === 1 ? 'y has' : 'ies have'} no recorded origin, so any session that reads one writes unknown. `
66
+ + `Review them, then: stratus memory reassert ${command.agentId} --trust user --all-unknown (or name ids).`);
67
+ }
68
+ if (recordedUnknown > 0) {
69
+ writeLine(streams.stdout, '');
70
+ writeLine(streams.stdout, `${recordedUnknown} entr${recordedUnknown === 1 ? 'y was' : 'ies were'} recorded unknown — written after a message from someone not configured as a principal, or after a shell command — `
71
+ + 'and may repeat what a stranger said. --all-unknown leaves these alone; re-assert one by id once you have read it.');
72
+ }
73
+ }
74
+ return 0;
75
+ }
76
+ if (!store.reassertTrust) {
77
+ writeLine(streams.stderr, 'Error: this memory store cannot re-assert trust.');
78
+ return 1;
79
+ }
80
+ const trust = command.trust ?? 'user';
81
+ const live = (await store.list(command.agentId)).entries;
82
+ const targets = command.allUnknown
83
+ ? [...new Set([...live.filter(unlabelled).map((entry) => entry.id), ...command.ids])]
84
+ : command.ids;
85
+ if (targets.length === 0) {
86
+ writeLine(streams.stdout, `${command.agentId} has no live entries with no recorded origin; nothing to re-assert. An entry recorded unknown is named by id.`);
87
+ return 0;
88
+ }
89
+ const missing = [];
90
+ let changed = 0;
91
+ for (const id of targets) {
92
+ if (await store.reassertTrust(command.agentId, id, trust)) {
93
+ changed += 1;
94
+ }
95
+ else {
96
+ missing.push(id);
97
+ }
98
+ }
99
+ if (command.format === 'json') {
100
+ writeLine(streams.stdout, JSON.stringify({ agentId: command.agentId, trust, reasserted: changed, missing }, null, 2));
101
+ }
102
+ else {
103
+ writeLine(streams.stdout, `Re-asserted ${changed} entr${changed === 1 ? 'y' : 'ies'} of ${command.agentId} as ${trust}. A running daemon reads the change on its next turn.`);
104
+ }
105
+ for (const id of missing) {
106
+ writeLine(streams.stderr, `No live memory entry with id ${id} belongs to ${command.agentId}; nothing was re-asserted for it.`);
107
+ }
108
+ return missing.length === 0 ? 0 : 1;
109
+ };
110
+ //# sourceMappingURL=memory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/commands/memory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,103 @@
1
+ import { type ToolRisk } from '@stratusagent/core';
2
+ import type { CliStreams, CliEnvironment } from '../environment.ts';
3
+ import type { ParsedPluginsCommand } from '../parse.ts';
4
+ /** One tool a plugin's manifest declares, as this machine would have it. */
5
+ export interface PluginToolReport {
6
+ name: string;
7
+ /**
8
+ * Declared as a namespace rather than named, so the tools under it arrive
9
+ * when the server connects. `mcp.*` is the case: a name that does not
10
+ * exist yet is not a name that does not exist.
11
+ */
12
+ discovered: boolean;
13
+ /**
14
+ * What `ManifestBoundToolRegistry` would settle on, minus the claim only a
15
+ * running daemon has. An operator's `toolRisks` override *replaces* the
16
+ * manifest's declaration and is bounded only by the package's floor —
17
+ * lowering one is the whole point of the key — and without an override it
18
+ * is the riskier of the declaration and that floor. A registered object
19
+ * may then raise itself further, so this is a floor on what a call will
20
+ * face rather than the last word on it.
21
+ */
22
+ risk: ToolRisk;
23
+ /**
24
+ * Agent ids whose `tools:` allowlist selects this name. Empty is the
25
+ * finding, not the absence of one: installing a plugin grants nothing.
26
+ */
27
+ grantedTo: string[];
28
+ }
29
+ export interface PluginReport {
30
+ package: string;
31
+ /** Resolvable from this process. */
32
+ installed: boolean;
33
+ /** Named in the trusted config's plugins block, whatever its `enabled`. */
34
+ configured: boolean;
35
+ /** Configured and not switched off. */
36
+ enabled: boolean;
37
+ tools: PluginToolReport[];
38
+ /**
39
+ * What the manifest declares besides tools and skills: the provider,
40
+ * channel, memory-store, and executor names its `setup()` may register.
41
+ * Declarations, read without loading — whether `setup()` registers each
42
+ * is the daemon's to report (`GET /catalog/tools`), not a manifest's.
43
+ */
44
+ contributes?: {
45
+ providers: string[];
46
+ channels: string[];
47
+ memory: string[];
48
+ executors: string[];
49
+ };
50
+ /**
51
+ * Why a daemon would register nothing for this plugin, when it would —
52
+ * an unreadable manifest, or settings its own schema rejects. Enabled and
53
+ * loadable are different questions, and a report that ran them together
54
+ * would call `plugin-mcp` with no `servers` ready to use.
55
+ */
56
+ problem?: string;
57
+ /**
58
+ * What might go wrong that a manifest cannot settle. A name two packages
59
+ * both declare collides only if both *register* it, and registration is
60
+ * `setup()`'s business — so this says "if it does" rather than reporting
61
+ * a failure that may never happen.
62
+ */
63
+ warnings?: string[];
64
+ }
65
+ export interface PluginsReport {
66
+ approvals: 'headless' | 'remote';
67
+ /**
68
+ * What this machine would really do with a gated call — the mode alone
69
+ * does not say. `headless` still runs one a standing grant, an approved
70
+ * command scope, or an approved site already covers (the engine checks
71
+ * all three before it refuses), and `remote` with no reachable approver
72
+ * denies on arrival rather than asking anybody.
73
+ */
74
+ approvalsSummary: string;
75
+ /**
76
+ * Set when the roster did not load, in which case every `grantedTo` is
77
+ * withheld rather than reported empty — the same rule `stratus skills`
78
+ * follows, and for the same reason: unreadable enablement must not print
79
+ * as "granted to nobody".
80
+ */
81
+ rosterUnreadable: boolean;
82
+ plugins: PluginReport[];
83
+ }
84
+ /**
85
+ * What this machine's plugins are, and where the chain from installed to
86
+ * callable breaks.
87
+ *
88
+ * Four things have to be true before an agent can call a plugin's tool —
89
+ * the package is installed, a trusted config enables it, the agent's
90
+ * `tools:` names it, and the approval policy lets the call through — and
91
+ * every one of them fails silently on its own. A listing of installed
92
+ * packages answers the first and reads as an answer to all four, which is
93
+ * how an agent ends up with a persona describing tools it never had.
94
+ *
95
+ * Manifests rather than a load: `readPluginManifest` imports nothing, so
96
+ * this never runs a plugin's `setup` — which for the MCP bridge would spawn
97
+ * every configured server's subprocess to answer a question about a
98
+ * daemon that is not running.
99
+ */
100
+ export declare const collectPluginsReport: (command: ParsedPluginsCommand, env: CliEnvironment, warn: (line: string) => void) => Promise<PluginsReport>;
101
+ /** `stratus plugins` — the chain from installed to callable, per plugin. */
102
+ export declare const runPlugins: (command: ParsedPluginsCommand, streams: CliStreams, env?: CliEnvironment) => Promise<number>;
103
+ //# sourceMappingURL=plugins.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugins.d.ts","sourceRoot":"","sources":["../../src/commands/plugins.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,QAAQ,EACd,MAAM,oBAAoB,CAAC;AAW5B,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGpE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAUxD,4EAA4E;AAC5E,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,UAAU,EAAE,OAAO,CAAC;IACpB;;;;;;;;OAQG;IACH,IAAI,EAAE,QAAQ,CAAC;IACf;;;OAGG;IACH,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,SAAS,EAAE,OAAO,CAAC;IACnB,2EAA2E;IAC3E,UAAU,EAAE,OAAO,CAAC;IACpB,uCAAuC;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B;;;;;OAKG;IACH,WAAW,CAAC,EAAE;QACZ,SAAS,EAAE,MAAM,EAAE,CAAC;QACpB,QAAQ,EAAE,MAAM,EAAE,CAAC;QACnB,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,SAAS,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;IACF;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,UAAU,GAAG,QAAQ,CAAC;IACjC;;;;;;OAMG;IACH,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,gBAAgB,EAAE,OAAO,CAAC;IAC1B,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,oBAAoB,GAC/B,SAAS,oBAAoB,EAC7B,KAAK,cAAc,EACnB,MAAM,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,KAC3B,OAAO,CAAC,aAAa,CAgSvB,CAAC;AAEF,4EAA4E;AAC5E,eAAO,MAAM,UAAU,GACrB,SAAS,oBAAoB,EAC7B,SAAS,UAAU,EACnB,MAAK,cAAmB,KACvB,OAAO,CAAC,MAAM,CAmFhB,CAAC"}
@@ -0,0 +1,386 @@
1
+ import { matchesToolAllowlist, raiseRiskTo, toolScopesOverlap, } from '@stratusagent/core';
2
+ import { declaredRiskFor, isFirstPartyPackage, parseToolRiskOverrides, preflightPlugin, readPluginManifest, riskFloorFor, } from '@stratusagent/plugins';
3
+ import { workspacesDirPath } from '@stratusagent/state';
4
+ import { describeUnattendedReach } from "../approvals.js";
5
+ import { writeLine } from "../io.js";
6
+ import { packageInstalled } from "../loaders.js";
7
+ import { PLUGIN_MARKETPLACE_URL, FIRST_PARTY_CAPABILITY_PACKAGES, FIRST_PARTY_CONTRIBUTION_PACKAGES, KERNEL_TOOL_NAMES, } from "../plugin-catalog.js";
8
+ import { soulGrantsTool, rosterSoulsWithConfigured } from "../roster.js";
9
+ import { loadServeApprovals, loadServeApi, loadServePlugins } from "../trusted-config.js";
10
+ /**
11
+ * What this machine's plugins are, and where the chain from installed to
12
+ * callable breaks.
13
+ *
14
+ * Four things have to be true before an agent can call a plugin's tool —
15
+ * the package is installed, a trusted config enables it, the agent's
16
+ * `tools:` names it, and the approval policy lets the call through — and
17
+ * every one of them fails silently on its own. A listing of installed
18
+ * packages answers the first and reads as an answer to all four, which is
19
+ * how an agent ends up with a persona describing tools it never had.
20
+ *
21
+ * Manifests rather than a load: `readPluginManifest` imports nothing, so
22
+ * this never runs a plugin's `setup` — which for the MCP bridge would spawn
23
+ * every configured server's subprocess to answer a question about a
24
+ * daemon that is not running.
25
+ */
26
+ export const collectPluginsReport = async (command, env, warn) => {
27
+ const pluginsConfig = await loadServePlugins(env, command.configPath, warn);
28
+ const approvals = await loadServeApprovals(env, command.configPath, warn);
29
+ // What the loader would fold in, so the validation below is against the
30
+ // object a daemon on this machine would build.
31
+ const workspaceRoot = workspacesDirPath(env);
32
+ // Read the same way the daemon reads it: installed, and not switched off
33
+ // by the trusted config's `api` block.
34
+ const api = await loadServeApi(env, command.configPath, warn);
35
+ const apiReachable = packageInstalled('@stratusagent/control-api', env) && api.enabled !== false;
36
+ // Who grants what, from the roster a dispatch actually serves — the same
37
+ // resolution `stratus skills` uses, so the two commands cannot disagree
38
+ // about which souls are live.
39
+ let roster = [];
40
+ let rosterUnreadable = false;
41
+ try {
42
+ const resolved = await rosterSoulsWithConfigured(env, warn, {
43
+ ...(command.configPath !== undefined ? { configPath: command.configPath } : {}),
44
+ includeBuiltIn: true,
45
+ });
46
+ roster = resolved.entries;
47
+ rosterUnreadable = !resolved.complete;
48
+ }
49
+ catch (error) {
50
+ rosterUnreadable = true;
51
+ warn(`cannot say who is granted what — the roster did not load: ${error instanceof Error ? error.message : String(error)}`);
52
+ }
53
+ /**
54
+ * Which agents a declared name reaches. A soul with no `tools:` key
55
+ * grants every registered tool, which is the opposite of an empty list —
56
+ * so an absent allowlist matches everything here too, or this would
57
+ * report the most permissive agents as the least.
58
+ */
59
+ const grantedTo = (name, discovered) => roster
60
+ .filter((entry) => soulGrantsTool(entry.soul.agent.tools, name, discovered))
61
+ .map((entry) => entry.soul.agent.id);
62
+ // Configured first, in the operator's own order, then the first-party
63
+ // packages they have not configured — the second group is why an install
64
+ // that granted nothing is visible at all.
65
+ const configured = Object.keys(pluginsConfig);
66
+ const packages = [
67
+ ...configured,
68
+ ...[...FIRST_PARTY_CAPABILITY_PACKAGES, ...FIRST_PARTY_CONTRIBUTION_PACKAGES].filter((name) => !configured.includes(name)),
69
+ ];
70
+ const plugins = [];
71
+ // Which package claimed each literal tool name, across the whole loop.
72
+ // `loadPlugins` keeps the same map and rejects the *later* plugin whole
73
+ // when a registration collides, so a per-plugin check would report two
74
+ // packages ready to serve one name that only one of them will get.
75
+ //
76
+ // Literal declarations only: the registry claims names as they register,
77
+ // and a namespace has none until its server connects.
78
+ // Qualified skill ids, tracked the same way and for the same reason.
79
+ const claimedSkills = new Map();
80
+ /**
81
+ * Every name or namespace some plugin may register, in config order.
82
+ *
83
+ * One list rather than a map of literals, because a collision is an
84
+ * *overlap* and overlap has no preferred direction: a later literal falls
85
+ * under an earlier namespace exactly as a later namespace covers an
86
+ * earlier literal. Two one-way passes is what this was, and it missed
87
+ * whichever direction was written second.
88
+ *
89
+ * The daemon's own tools are *registered*, unconditionally, before any
90
+ * plugin loads; a plugin's entry is only a declaration, which is why the
91
+ * two produce different warnings.
92
+ */
93
+ const claims = KERNEL_TOOL_NAMES
94
+ .map((name) => ({ pattern: name, owner: 'the daemon itself', registered: true }));
95
+ for (const specifier of packages) {
96
+ const block = pluginsConfig[specifier] ?? {};
97
+ const isConfigured = configured.includes(specifier);
98
+ const base = {
99
+ package: specifier,
100
+ installed: packageInstalled(specifier, env),
101
+ configured: isConfigured,
102
+ enabled: isConfigured && block.enabled !== false,
103
+ tools: [],
104
+ };
105
+ // Nothing below runs for a plugin the loader would skip. It skips an
106
+ // absent or switched-off block before it reads a manifest, parses an
107
+ // override, or validates anything — so doing any of that here invents a
108
+ // failure for a plugin that has none, and the renderer shows no tools
109
+ // for one either way. Gating the whole block rather than each call is
110
+ // deliberate: gating them one at a time is what left the override parse
111
+ // unconditional after the preflight moved.
112
+ if (!base.installed || !base.enabled) {
113
+ plugins.push(base);
114
+ continue;
115
+ }
116
+ try {
117
+ const { manifest, directory } = await readPluginManifest(specifier, {
118
+ resolve: (target) => import.meta.resolve(target),
119
+ });
120
+ const floor = riskFloorFor(isFirstPartyPackage(manifest.packageName));
121
+ const overrides = parseToolRiskOverrides(manifest, block);
122
+ // Everything the loader checks before importing, through the loader's
123
+ // own function — a plugin whose settings or skill files it rejects
124
+ // registers nothing, and reading here as enabled is the false clean
125
+ // bill this command exists to stop giving.
126
+ //
127
+ // Only for a plugin the loader would actually reach, though: it skips
128
+ // an absent or switched-off block before validating anything, so
129
+ // preflighting one would report `plugin-mcp` that nobody configured as
130
+ // broken settings instead of as the install to enable.
131
+ if (base.enabled) {
132
+ await preflightPlugin(manifest, directory, block, workspaceRoot);
133
+ }
134
+ // One row per tool the report will name, tracked as they are emitted.
135
+ // A concrete name can be reached more than one way — declared outright
136
+ // *and* covered by a namespace, or covered by two nested namespaces
137
+ // like `mcp.*` and `mcp.linear.*` — and every one of those is a single
138
+ // runtime tool. Filtering each source against the others is what
139
+ // produced two rounds of duplicate rows; one set, checked as rows are
140
+ // added, cannot miss a path.
141
+ const seen = new Set();
142
+ const declared = [];
143
+ const emit = (row) => {
144
+ if (seen.has(row.name)) {
145
+ return;
146
+ }
147
+ seen.add(row.name);
148
+ declared.push(row);
149
+ };
150
+ // Literal declarations first: a name the manifest states outright is
151
+ // described best by its own entry, not by a namespace that covers it.
152
+ // Risk through `declaredRiskFor`, never the declaration in hand: with
153
+ // overlapping namespaces (`mcp.*` and `mcp.linear.*`) it is the *first*
154
+ // match that registration uses, so reading each entry's own risk would
155
+ // show a narrower namespace at a risk none of its tools will have.
156
+ const riskOf = (name, fallback) => declaredRiskFor(manifest, name) ?? fallback;
157
+ for (const tool of manifest.contributes.tools) {
158
+ emit({ name: tool.name, discovered: false, namespace: false, declared: riskOf(tool.name, tool.risk) });
159
+ }
160
+ for (const entry of manifest.contributes.toolsDiscovered) {
161
+ emit({
162
+ name: entry.namespace,
163
+ discovered: true,
164
+ namespace: true,
165
+ declared: riskOf(entry.namespace, entry.risk),
166
+ });
167
+ // An override under a declared namespace names a concrete tool
168
+ // (`mcp.linear.get_issue`), which is the whole point of the key for
169
+ // a bridge — and it can never equal the namespace, so the namespace
170
+ // row alone would report the default risk for a tool the operator
171
+ // has deliberately re-rated. Listed beside it rather than folded in:
172
+ // they are different risks, and which tools carry the override is
173
+ // the thing worth seeing.
174
+ for (const name of overrides.keys()) {
175
+ // Any wildcard key, not merely the declared namespace itself: the
176
+ // registry applies an override by each concrete *registered*
177
+ // name, so a nested `mcp.linear.*` is exactly as inert as
178
+ // `mcp.*`. Excluding only the equal case left the nested one
179
+ // advertising a re-rating no call gets.
180
+ if (name.endsWith('.*')) {
181
+ continue;
182
+ }
183
+ if (matchesToolAllowlist(name, [entry.namespace])) {
184
+ emit({ name, discovered: true, namespace: false, declared: riskOf(name, entry.risk) });
185
+ }
186
+ }
187
+ }
188
+ // A warning, not a failure. Ownership is claimed at registration —
189
+ // `view.commit(owners)` records what `setup()` actually registered —
190
+ // so a name two manifests both declare collides only if both plugins
191
+ // go on to register it, which nothing here can know. Reporting it as
192
+ // a load failure would condemn a plugin that loads perfectly well
193
+ // because its tool is optional.
194
+ // Keyed by config entry, never by package name: the same package
195
+ // configured through two specifiers is two entries the loader treats
196
+ // as two plugins, and exempting them for sharing a `packageName`
197
+ // would hide the collision an operator is likeliest to create by
198
+ // accident.
199
+ //
200
+ // A plugin never collides with itself: everything it declares is
201
+ // gathered first, checked against what came before, and only then
202
+ // added. That ordering is what makes a manifest declaring both
203
+ // `mcp.ping` and `mcp.*` silent — it registers that name once — and
204
+ // it is load-bearing, so adding claims inside the loop below would
205
+ // reintroduce a warning telling operators to fix a working config.
206
+ const declaredHere = [
207
+ ...manifest.contributes.tools.map((tool) => tool.name),
208
+ ...manifest.contributes.toolsDiscovered.map((entry) => entry.namespace),
209
+ ];
210
+ for (const pattern of declaredHere) {
211
+ for (const claim of claims) {
212
+ if (!toolScopesOverlap(pattern, claim.pattern)) {
213
+ continue;
214
+ }
215
+ const subject = pattern === claim.pattern
216
+ ? `${pattern} is`
217
+ : `${pattern} overlaps ${claim.pattern}, which is`;
218
+ // One sentence for every collision, because a manifest cannot
219
+ // tell when a name registers and the cost turns entirely on
220
+ // that. `ManifestBoundToolRegistry` stages while `owners` is
221
+ // unset and registers live once `commit` sets it, so a clash
222
+ // before the plugin commits rolls it back whole and a clash
223
+ // after refuses that one registration. Neither side of the
224
+ // declaration says which: a bridge's first connect happens
225
+ // inside `setup()` when its server is up and on a reconnect
226
+ // when it is not, and a plugin may hold `context.tools` and
227
+ // register a plainly-named tool from a timer long after.
228
+ //
229
+ // Three rounds of review went into splitting this by
230
+ // declaration kind, then by which side of the pair held the
231
+ // namespace. Both splits claimed knowledge the manifest does
232
+ // not have. Do not reintroduce one.
233
+ base.warnings = [
234
+ ...(base.warnings ?? []),
235
+ `${subject} ${claim.registered ? 'already registered by' : 'also declared by'} ${claim.owner}; `
236
+ + 'if both register that name, a daemon keeps the first and refuses the second registration — '
237
+ + 'the whole plugin, tools and skills together, if it happens before that plugin finishes loading, '
238
+ + 'or just that one tool if it happens after',
239
+ ];
240
+ }
241
+ }
242
+ for (const pattern of declaredHere) {
243
+ claims.push({ pattern, owner: specifier, registered: false });
244
+ }
245
+ // Skills collide on the qualified `packageName:id`, so a clash means
246
+ // one package configured twice — the two-specifier case again. Firmer
247
+ // than the tool warning and worded that way: the loader stages skills
248
+ // from the manifest and refuses the second entry outright rather than
249
+ // waiting to see what `setup()` does. Still "if it loads", since a
250
+ // plugin that fails to import never reaches the check.
251
+ for (const skill of manifest.contributes.skills) {
252
+ const qualified = `${manifest.packageName}:${skill.id}`;
253
+ const owner = claimedSkills.get(qualified);
254
+ if (owner !== undefined) {
255
+ base.warnings = [
256
+ ...(base.warnings ?? []),
257
+ `skill ${qualified} is already declared by ${owner}; if both load, a daemon keeps the first `
258
+ + 'and refuses this one whole, tools and skills together',
259
+ ];
260
+ }
261
+ else {
262
+ claimedSkills.set(qualified, specifier);
263
+ }
264
+ }
265
+ const named = {
266
+ providers: manifest.contributes.providers.map((entry) => entry.name),
267
+ channels: manifest.contributes.channels.map((entry) => entry.name),
268
+ memory: manifest.contributes.memory.map((entry) => entry.name),
269
+ executors: manifest.contributes.executors.map((entry) => entry.name),
270
+ };
271
+ if (Object.values(named).some((names) => names.length > 0)) {
272
+ base.contributes = named;
273
+ }
274
+ base.tools = declared.map((tool) => {
275
+ // Never on the namespace row. `parseToolRiskOverrides` accepts a
276
+ // namespace-shaped key, but the registry looks an override up by
277
+ // each concrete *registered* name — so `toolRisks: { "mcp.*": … }`
278
+ // changes no call, and showing the row at that risk would advertise
279
+ // a re-rating the daemon will not honour.
280
+ const override = tool.namespace ? undefined : overrides.get(tool.name);
281
+ return {
282
+ name: tool.name,
283
+ discovered: tool.discovered,
284
+ risk: override !== undefined
285
+ ? raiseRiskTo(override, floor)
286
+ : raiseRiskTo(tool.declared, floor),
287
+ grantedTo: grantedTo(tool.name, tool.discovered),
288
+ };
289
+ });
290
+ }
291
+ catch (error) {
292
+ // Reported per plugin rather than thrown: one package with a broken
293
+ // manifest must not take down the listing that would have shown it.
294
+ base.problem = error instanceof Error ? error.message : String(error);
295
+ }
296
+ plugins.push(base);
297
+ }
298
+ const mode = approvals.mode ?? 'headless';
299
+ return {
300
+ approvals: mode,
301
+ approvalsSummary: await describeUnattendedReach(mode, approvals, env, rosterUnreadable ? undefined : roster.map((entry) => entry.soul.agent.id), apiReachable),
302
+ rosterUnreadable,
303
+ plugins,
304
+ };
305
+ };
306
+ /** `stratus plugins` — the chain from installed to callable, per plugin. */
307
+ export const runPlugins = async (command, streams, env = {}) => {
308
+ const report = await collectPluginsReport(command, env, (line) => {
309
+ writeLine(streams.stderr, `Warning: ${line}`);
310
+ });
311
+ if (command.format === 'json') {
312
+ writeLine(streams.stdout, JSON.stringify(report, null, 2));
313
+ return 0;
314
+ }
315
+ writeLine(streams.stdout, `approvals: ${report.approvalsSummary}`);
316
+ writeLine(streams.stdout);
317
+ for (const plugin of report.plugins) {
318
+ const state = !plugin.installed
319
+ ? 'not installed'
320
+ : !plugin.configured
321
+ ? 'installed, not enabled'
322
+ : !plugin.enabled
323
+ ? 'installed, switched off'
324
+ // Enabled and loadable are separate: a plugin whose settings its
325
+ // own schema rejects is enabled and registers nothing, and saying
326
+ // only "enabled" here is the false clean bill this command exists
327
+ // to stop giving.
328
+ : plugin.problem !== undefined ? 'installed, enabled, will not load' : 'installed, enabled';
329
+ // Padded to a column, but never run together: a package name longer
330
+ // than the column would otherwise touch its own status.
331
+ writeLine(streams.stdout, `${plugin.package.padEnd(29)} ${state}`);
332
+ if (plugin.problem !== undefined) {
333
+ writeLine(streams.stdout, ` a daemon would register nothing for it: ${plugin.problem}`);
334
+ continue;
335
+ }
336
+ if (!plugin.installed) {
337
+ writeLine(streams.stdout, ` install it: npm install -g ${plugin.package}`);
338
+ continue;
339
+ }
340
+ // Nothing is registered for a plugin that will not load, so its tools
341
+ // are not listed: a grant column beside a name no agent can call reads
342
+ // as capability this machine has.
343
+ if (!plugin.configured) {
344
+ writeLine(streams.stdout, ` installing granted nothing — add "${plugin.package}" to the plugins block of a trusted config to load it`);
345
+ continue;
346
+ }
347
+ if (!plugin.enabled) {
348
+ writeLine(streams.stdout, ' switched off — remove "enabled": false to load it');
349
+ continue;
350
+ }
351
+ for (const warning of plugin.warnings ?? []) {
352
+ writeLine(streams.stdout, ` warning: ${warning}`);
353
+ }
354
+ for (const [kind, names] of Object.entries(plugin.contributes ?? {})) {
355
+ if (names.length === 0) {
356
+ continue;
357
+ }
358
+ const label = kind === 'providers'
359
+ ? 'provider'
360
+ : kind === 'channels'
361
+ ? 'channel'
362
+ : kind === 'memory'
363
+ ? 'memory store'
364
+ : 'executor';
365
+ const how = kind === 'providers'
366
+ ? 'a soul selects it with provider:'
367
+ : kind === 'channels'
368
+ ? 'starts for the agents with tokens under channels.<kind> in credentials.json'
369
+ : `a trusted config selects it with ${kind === 'memory' ? 'memoryStore' : 'executor'}:`;
370
+ writeLine(streams.stdout, ` ${`${label} ${names.join(', ')}`.padEnd(28)}${how}`);
371
+ }
372
+ for (const tool of plugin.tools) {
373
+ const granted = report.rosterUnreadable
374
+ ? ''
375
+ : tool.grantedTo.length > 0
376
+ ? ` → ${tool.grantedTo.join(', ')}`
377
+ : ' → nobody, until a soul’s tools: list names it';
378
+ const shape = tool.discovered ? ' (names arrive at connect)' : '';
379
+ writeLine(streams.stdout, ` ${tool.name.padEnd(28)}${tool.risk}${shape}${granted}`);
380
+ }
381
+ }
382
+ writeLine(streams.stdout);
383
+ writeLine(streams.stdout, `More plugins — ${PLUGIN_MARKETPLACE_URL}`);
384
+ return 0;
385
+ };
386
+ //# sourceMappingURL=plugins.js.map