@formthefog/stratus 2026.2.24 → 2026.3.20

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 (75) hide show
  1. package/.github/sentinel/action.yml +100 -0
  2. package/.github/sentinel/dist/codebase.d.ts +3 -0
  3. package/.github/sentinel/dist/codebase.d.ts.map +1 -0
  4. package/.github/sentinel/dist/context.d.ts +6 -0
  5. package/.github/sentinel/dist/context.d.ts.map +1 -0
  6. package/.github/sentinel/dist/fixer.d.ts +6 -0
  7. package/.github/sentinel/dist/fixer.d.ts.map +1 -0
  8. package/.github/sentinel/dist/index.d.ts +1 -0
  9. package/.github/sentinel/dist/index.d.ts.map +1 -0
  10. package/.github/sentinel/dist/index.js +68808 -0
  11. package/.github/sentinel/dist/index.js.map +1 -0
  12. package/.github/sentinel/dist/licenses.txt +1152 -0
  13. package/.github/sentinel/dist/models/anthropic.d.ts +26 -0
  14. package/.github/sentinel/dist/models/anthropic.d.ts.map +1 -0
  15. package/.github/sentinel/dist/models/openai.d.ts +26 -0
  16. package/.github/sentinel/dist/models/openai.d.ts.map +1 -0
  17. package/.github/sentinel/dist/models/openrouter.d.ts +31 -0
  18. package/.github/sentinel/dist/models/openrouter.d.ts.map +1 -0
  19. package/.github/sentinel/dist/models/types.d.ts +37 -0
  20. package/.github/sentinel/dist/models/types.d.ts.map +1 -0
  21. package/.github/sentinel/dist/orchestrator.d.ts +3 -0
  22. package/.github/sentinel/dist/orchestrator.d.ts.map +1 -0
  23. package/.github/sentinel/dist/policy.d.ts +15 -0
  24. package/.github/sentinel/dist/policy.d.ts.map +1 -0
  25. package/.github/sentinel/dist/reporter.d.ts +8 -0
  26. package/.github/sentinel/dist/reporter.d.ts.map +1 -0
  27. package/.github/sentinel/dist/responder.d.ts +6 -0
  28. package/.github/sentinel/dist/responder.d.ts.map +1 -0
  29. package/.github/sentinel/dist/router.d.ts +2 -0
  30. package/.github/sentinel/dist/router.d.ts.map +1 -0
  31. package/.github/sentinel/dist/schemas/config.d.ts +195 -0
  32. package/.github/sentinel/dist/schemas/config.d.ts.map +1 -0
  33. package/.github/sentinel/dist/schemas/fix.d.ts +130 -0
  34. package/.github/sentinel/dist/schemas/fix.d.ts.map +1 -0
  35. package/.github/sentinel/dist/schemas/review.d.ts +275 -0
  36. package/.github/sentinel/dist/schemas/review.d.ts.map +1 -0
  37. package/.github/sentinel/dist/sourcemap-register.js +1 -0
  38. package/.github/sentinel/dist/subway.d.ts +31 -0
  39. package/.github/sentinel/dist/subway.d.ts.map +1 -0
  40. package/.github/sentinel/dist/types.d.ts +210 -0
  41. package/.github/sentinel/dist/types.d.ts.map +1 -0
  42. package/.github/sentinel/package-lock.json +2389 -0
  43. package/.github/sentinel/package.json +29 -0
  44. package/.github/sentinel/src/codebase.ts +265 -0
  45. package/.github/sentinel/src/context.ts +182 -0
  46. package/.github/sentinel/src/fixer.ts +353 -0
  47. package/.github/sentinel/src/index.ts +263 -0
  48. package/.github/sentinel/src/models/anthropic.ts +244 -0
  49. package/.github/sentinel/src/models/openai.ts +242 -0
  50. package/.github/sentinel/src/models/openrouter.ts +319 -0
  51. package/.github/sentinel/src/models/types.ts +35 -0
  52. package/.github/sentinel/src/orchestrator.ts +287 -0
  53. package/.github/sentinel/src/policy.ts +133 -0
  54. package/.github/sentinel/src/reporter.ts +666 -0
  55. package/.github/sentinel/src/responder.ts +156 -0
  56. package/.github/sentinel/src/router.ts +308 -0
  57. package/.github/sentinel/src/schemas/config.ts +84 -0
  58. package/.github/sentinel/src/schemas/fix.ts +44 -0
  59. package/.github/sentinel/src/schemas/review.ts +73 -0
  60. package/.github/sentinel/src/subway.ts +250 -0
  61. package/.github/sentinel/src/types.ts +234 -0
  62. package/.github/sentinel/tsconfig.json +19 -0
  63. package/.github/sentinel.yml +34 -0
  64. package/.github/workflows/sentinel.yml +55 -0
  65. package/README.md +88 -102
  66. package/SECURITY.md +21 -10
  67. package/TROUBLESHOOTING.md +2 -2
  68. package/index.ts +255 -114
  69. package/openclaw.plugin.json +50 -26
  70. package/package.json +1 -1
  71. package/skills/stratus-info/SKILL.md +70 -10
  72. package/src/client.ts +78 -18
  73. package/src/config.ts +29 -8
  74. package/src/setup.ts +82 -61
  75. package/src/types.ts +11 -0
package/index.ts CHANGED
@@ -4,6 +4,9 @@ import type { StratusPluginConfig } from "./src/types.js";
4
4
  import { createStratusClient } from "./src/client.js";
5
5
  import { StratusConfigSchema } from "./src/config.js";
6
6
  import { setupStratus } from "./src/setup.js";
7
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
8
+ import { join } from "path";
9
+ import { homedir } from "os";
7
10
 
8
11
  const PROVIDER_ID = "stratus";
9
12
  const PROVIDER_LABEL = "Stratus";
@@ -26,59 +29,108 @@ function buildModelDefinition(params: {
26
29
  };
27
30
  }
28
31
 
29
- const STRATUS_SIZES = ["small", "base", "large", "xl", "huge"] as const;
30
-
31
- const OPENAI_MODELS = [
32
- { id: "gpt-4o", name: "GPT-4o", context: 128000, tokens: 8192 },
33
- { id: "gpt-4o-mini", name: "GPT-4o Mini", context: 128000, tokens: 8192 },
34
- { id: "gpt-4-turbo", name: "GPT-4 Turbo", context: 128000, tokens: 4096 },
35
- { id: "gpt-4", name: "GPT-4", context: 8192, tokens: 4096 },
36
- { id: "gpt-3.5-turbo", name: "GPT-3.5 Turbo", context: 16385, tokens: 4096 },
37
- ] as const;
38
-
39
- const ANTHROPIC_MODELS = [
40
- { id: "claude-sonnet-4-5", name: "Claude 4.5 Sonnet", context: 200000, tokens: 8192 },
41
- { id: "claude-opus-4-5", name: "Claude 4.5 Opus", context: 200000, tokens: 8192 },
42
- { id: "claude-sonnet-4", name: "Claude 4 Sonnet", context: 200000, tokens: 8192 },
43
- { id: "claude-opus-4", name: "Claude 4 Opus", context: 200000, tokens: 8192 },
44
- { id: "claude-haiku-4-5", name: "Claude 4.5 Haiku", context: 200000, tokens: 8192 },
45
- { id: "claude-3-7-sonnet", name: "Claude 3.7 Sonnet", context: 200000, tokens: 8192 },
46
- { id: "claude-3-5-sonnet", name: "Claude 3.5 Sonnet", context: 200000, tokens: 8192 },
47
- { id: "claude-3-opus", name: "Claude 3 Opus", context: 200000, tokens: 4096 },
48
- { id: "claude-3-sonnet", name: "Claude 3 Sonnet", context: 200000, tokens: 4096 },
49
- { id: "claude-3-haiku", name: "Claude 3 Haiku", context: 200000, tokens: 4096 },
50
- ] as const;
51
-
52
- function generateAllModels() {
53
- const models = [];
54
-
55
- for (const size of STRATUS_SIZES) {
56
- const sizeLabel = size.charAt(0).toUpperCase() + size.slice(1);
57
-
58
- for (const llm of OPENAI_MODELS) {
59
- models.push(
60
- buildModelDefinition({
61
- id: `stratus-x1ac-${size}-${llm.id}`,
62
- name: `Stratus X1AC ${sizeLabel} (${llm.name})`,
63
- contextWindow: llm.context,
64
- maxTokens: llm.tokens,
65
- }),
66
- );
67
- }
32
+ // Model metadata for naming/context/tokens (fallback if API doesn't provide details)
33
+ const MODEL_METADATA: Record<string, { name: string; context: number; tokens: number }> = {
34
+ "gpt-4o": { name: "GPT-4o", context: 128000, tokens: 8192 },
35
+ "gpt-4o-mini": { name: "GPT-4o Mini", context: 128000, tokens: 8192 },
36
+ "gpt-4-turbo": { name: "GPT-4 Turbo", context: 128000, tokens: 4096 },
37
+ "gpt-4": { name: "GPT-4", context: 8192, tokens: 4096 },
38
+ "gpt-3.5-turbo": { name: "GPT-3.5 Turbo", context: 16385, tokens: 4096 },
39
+ "claude-sonnet-4-6": { name: "Claude 4.6 Sonnet", context: 200000, tokens: 8192 },
40
+ "claude-opus-4-6": { name: "Claude 4.6 Opus", context: 200000, tokens: 8192 },
41
+ "claude-sonnet-4-5": { name: "Claude 4.5 Sonnet", context: 200000, tokens: 8192 },
42
+ "claude-opus-4-5": { name: "Claude 4.5 Opus", context: 200000, tokens: 8192 },
43
+ "claude-haiku-4-5": { name: "Claude 4.5 Haiku", context: 200000, tokens: 8192 },
44
+ "claude-sonnet-4": { name: "Claude 4 Sonnet", context: 200000, tokens: 8192 },
45
+ "claude-opus-4-1": { name: "Claude 4.1 Opus", context: 200000, tokens: 8192 },
46
+ "claude-opus-4": { name: "Claude 4 Opus", context: 200000, tokens: 8192 },
47
+ "claude-3-7-sonnet": { name: "Claude 3.7 Sonnet", context: 200000, tokens: 8192 },
48
+ "claude-3-5-sonnet": { name: "Claude 3.5 Sonnet", context: 200000, tokens: 8192 },
49
+ "claude-3-opus": { name: "Claude 3 Opus", context: 200000, tokens: 4096 },
50
+ "claude-3-sonnet": { name: "Claude 3 Sonnet", context: 200000, tokens: 4096 },
51
+ "claude-3-haiku": { name: "Claude 3 Haiku", context: 200000, tokens: 4096 },
52
+ "gemini-2.0-flash": { name: "Gemini 2.0 Flash", context: 1048576, tokens: 8192 },
53
+ "gemini-1.5-pro": { name: "Gemini 1.5 Pro", context: 2097152, tokens: 8192 },
54
+ "gemini-1.5-flash": { name: "Gemini 1.5 Flash", context: 1048576, tokens: 8192 },
55
+ "gemini-pro": { name: "Gemini Pro", context: 32768, tokens: 8192 },
56
+ };
68
57
 
69
- for (const llm of ANTHROPIC_MODELS) {
70
- models.push(
71
- buildModelDefinition({
72
- id: `stratus-x1ac-${size}-${llm.id}`,
73
- name: `Stratus X1AC ${sizeLabel} (${llm.name})`,
74
- contextWindow: llm.context,
75
- maxTokens: llm.tokens,
76
- }),
77
- );
78
- }
58
+ function generateModelName(modelId: string): string {
59
+ // Parse model ID like "stratus-x1ac-base-claude-sonnet-4-5"
60
+ const parts = modelId.split("-");
61
+
62
+ // Extract size (small/base/large/xl/huge)
63
+ let size = "";
64
+ let baseModelId = "";
65
+
66
+ if (parts.length >= 4 && parts[0] === "stratus" && parts[1] === "x1ac") {
67
+ size = parts[2];
68
+ baseModelId = parts.slice(3).join("-");
69
+ } else {
70
+ // Fallback for non-standard format
71
+ return modelId;
72
+ }
73
+
74
+ const sizeLabel = size.charAt(0).toUpperCase() + size.slice(1);
75
+ const metadata = MODEL_METADATA[baseModelId];
76
+
77
+ if (metadata) {
78
+ return `Stratus X1AC ${sizeLabel} (${metadata.name})`;
79
79
  }
80
+
81
+ // Fallback: capitalize base model
82
+ return `Stratus X1AC ${sizeLabel} (${baseModelId})`;
83
+ }
80
84
 
81
- return models;
85
+ async function generateAllModels(apiKey?: string, baseUrl?: string) {
86
+ const root = (baseUrl || DEFAULT_BASE_URL).replace(/\/v\d+\/?$/, "");
87
+ try {
88
+ const url = `${root}/v1/models`;
89
+ const headers: Record<string, string> = {};
90
+ if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
91
+
92
+ const response = await fetch(url, { headers, signal: AbortSignal.timeout(5000) });
93
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
94
+ const data = await response.json() as { data: Array<{ id: string }> };
95
+
96
+ return data.data.map((model) => {
97
+ const modelId = model.id;
98
+ const baseModelId = modelId.replace(/^stratus-x1ac-(small|base|large|xl|huge)-/, "");
99
+ const metadata = MODEL_METADATA[baseModelId];
100
+ return buildModelDefinition({
101
+ id: modelId,
102
+ name: generateModelName(modelId),
103
+ contextWindow: metadata?.context || 128000,
104
+ maxTokens: metadata?.tokens || 8192,
105
+ });
106
+ });
107
+ } catch (error) {
108
+ console.error("[stratus] Failed to fetch models from API:", error);
109
+ return [
110
+ buildModelDefinition({
111
+ id: "stratus-x1ac-base-claude-sonnet-4-5",
112
+ name: "Stratus X1AC Base (Claude 4.5 Sonnet)",
113
+ contextWindow: 200000,
114
+ maxTokens: 8192,
115
+ }),
116
+ ];
117
+ }
118
+ }
119
+
120
+ async function updateStoredModels(models: ReturnType<typeof buildModelDefinition>[]) {
121
+ const configPath = join(homedir(), ".openclaw", "openclaw.json");
122
+ if (!existsSync(configPath)) return;
123
+
124
+ try {
125
+ const raw = readFileSync(configPath, "utf-8");
126
+ const config = JSON.parse(raw);
127
+ if (!config?.models?.providers?.stratus) return;
128
+
129
+ config.models.providers.stratus.models = models;
130
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
131
+ } catch {
132
+ // Silent fail — stale models are better than a crash
133
+ }
82
134
  }
83
135
 
84
136
  const stratusPlugin = {
@@ -98,15 +150,13 @@ const stratusPlugin = {
98
150
  auth: [
99
151
  {
100
152
  id: "api-key",
101
- label: "API Key",
153
+ label: "API Key (optional — Formation pool available)",
102
154
  kind: "api_key",
103
155
  run: async (ctx) => {
104
156
  const apiKey = await ctx.prompter.text({
105
- message: "Enter your Stratus API key:",
157
+ message: "Enter your Stratus API key (leave blank for Formation pool — zero-config):",
106
158
  validate: (val: string) => {
107
- if (!val.trim()) {
108
- return "API key is required";
109
- }
159
+ if (!val.trim()) return undefined;
110
160
  if (!val.startsWith("stratus_sk_")) {
111
161
  return "API key must start with 'stratus_sk_'";
112
162
  }
@@ -114,36 +164,30 @@ const stratusPlugin = {
114
164
  },
115
165
  });
116
166
 
117
- const profileId = `${PROVIDER_ID}:default`;
167
+ const usingPool = !apiKey?.trim();
168
+ const resolvedKey = usingPool ? undefined : apiKey;
169
+ const profileId = `${PROVIDER_ID}:${usingPool ? "formation-pool" : "default"}`;
170
+
171
+ const models = await generateAllModels(resolvedKey, DEFAULT_BASE_URL);
172
+
173
+ const credential = usingPool
174
+ ? { type: "formation_pool" as const, provider: PROVIDER_ID }
175
+ : { type: "api_key" as const, provider: PROVIDER_ID, key: resolvedKey! };
176
+
177
+ const authMode = usingPool
178
+ ? "Formation pool (zero-config, 25% markup)"
179
+ : "BYOK (no markup)";
118
180
 
119
181
  return {
120
- profiles: [
121
- {
122
- profileId,
123
- credential: {
124
- type: "api_key",
125
- provider: PROVIDER_ID,
126
- key: apiKey,
127
- },
128
- },
129
- ],
182
+ profiles: [{ profileId, credential }],
130
183
  configPatch: {
131
184
  models: {
132
185
  providers: {
133
186
  [PROVIDER_ID]: {
134
187
  baseUrl: DEFAULT_BASE_URL,
135
- apiKey: apiKey,
188
+ ...(resolvedKey ? { apiKey: resolvedKey } : {}),
136
189
  api: "openai-completions",
137
- models: generateAllModels(),
138
- },
139
- },
140
- },
141
- agents: {
142
- defaults: {
143
- models: {
144
- [`${PROVIDER_ID}/stratus-x1ac-base-claude-sonnet-4-5`]: {
145
- alias: "stratus",
146
- },
190
+ models: models,
147
191
  },
148
192
  },
149
193
  },
@@ -152,20 +196,26 @@ const stratusPlugin = {
152
196
  notes: [
153
197
  "🌊 Stratus X1-AC: World Model + Collective Intelligence System",
154
198
  "",
199
+ `🔒 Auth: ${authMode}`,
200
+ "",
155
201
  "NOT just an LLM wrapper — this is predictive planning:",
156
202
  " Traditional agents: see → think → act → see again",
157
203
  " Stratus agents: see → simulate outcomes → pick best → act",
158
204
  "",
159
205
  "Architecture:",
160
206
  " • World Model (X1): Predicts future states from actions (JEPA-based)",
161
- " • Policy Head (X1-AC): Selects optimal actions toward goals",
207
+ " • Policy Head v3 (X1-AC): 94.4% accuracy, brain-guided action sequencing",
162
208
  " • Collective Network: Agents learn from each other in real-time",
163
209
  "",
210
+ `Loaded ${models.length} models dynamically from Stratus API`,
211
+ " 2050+ models via OpenRouter — OpenAI, Anthropic, Google, and more",
212
+ " BYOK support: pass your own provider keys to bypass Formation pool",
213
+ "",
164
214
  "Available Tools:",
165
215
  " • stratus_embeddings() - 768-dim semantic state embeddings",
166
- " • stratus_rollout() - Multi-step action sequence planning",
216
+ " • stratus_rollout() - Multi-step action sequence planning (Policy Head v3)",
167
217
  "",
168
- "Learn more: /stratus-info (bundled skill)",
218
+ "Commands: /stratus setup | /stratus verify | /stratus models",
169
219
  "Technical docs: https://stratus.run/docs/technical-overview",
170
220
  ],
171
221
  };
@@ -174,6 +224,42 @@ const stratusPlugin = {
174
224
  ],
175
225
  });
176
226
 
227
+ api.on("gateway_start", async () => {
228
+ const apiKey = pluginConfig.apiKey || process.env.STRATUS_API_KEY;
229
+ try {
230
+ const models = await generateAllModels(apiKey, pluginConfig.baseUrl);
231
+ await updateStoredModels(models);
232
+ } catch {
233
+ // Silent fail — don't disrupt gateway startup
234
+ }
235
+
236
+ if (apiKey) {
237
+ try {
238
+ const authPath = join(homedir(), ".openclaw", "agents", "main", "agent", "auth-profiles.json");
239
+ let authConfig: any = { version: 1, profiles: {}, lastGood: {}, usageStats: {} };
240
+ if (existsSync(authPath)) {
241
+ authConfig = JSON.parse(readFileSync(authPath, "utf-8"));
242
+ }
243
+ const existingProfile = authConfig.profiles["stratus:default"];
244
+ if (!existingProfile || existingProfile.key !== apiKey) {
245
+ authConfig.profiles["stratus:default"] = {
246
+ type: "api_key",
247
+ provider: "stratus",
248
+ key: apiKey,
249
+ };
250
+ authConfig.lastGood.stratus = "stratus:default";
251
+ const authDir = join(homedir(), ".openclaw", "agents", "main", "agent");
252
+ if (!existsSync(authDir)) {
253
+ mkdirSync(authDir, { recursive: true });
254
+ }
255
+ writeFileSync(authPath, JSON.stringify(authConfig, null, 2));
256
+ }
257
+ } catch {
258
+ // Silent fail — don't disrupt gateway startup
259
+ }
260
+ }
261
+ });
262
+
177
263
  if (!pluginConfig.tools?.embeddings?.enabled && !pluginConfig.tools?.rollout?.enabled) {
178
264
  return;
179
265
  }
@@ -285,11 +371,24 @@ const stratusPlugin = {
285
371
  ? `Successfully planned ${steps.length} steps to reach goal`
286
372
  : `Planning incomplete (${steps.length} steps generated)`;
287
373
 
374
+ const meta = response.metadata;
375
+ const metaLines: string[] = [];
376
+ if (meta?.brain_confidence != null) {
377
+ metaLines.push(`Brain confidence: ${(meta.brain_confidence * 100).toFixed(1)}%`);
378
+ }
379
+ if (meta?.brain_goal_proximity != null) {
380
+ metaLines.push(`Goal proximity: ${(meta.brain_goal_proximity * 100).toFixed(1)}%`);
381
+ }
382
+ if (meta?.key_source) {
383
+ metaLines.push(`Key source: ${meta.key_source}${meta.formation_markup_applied ? ` (${meta.formation_markup_applied * 100}% markup)` : ""}`);
384
+ }
385
+ const metaText = metaLines.length > 0 ? `\n\n${metaLines.join("\n")}` : "";
386
+
288
387
  return {
289
388
  content: [
290
389
  {
291
390
  type: "text",
292
- text: `${summary}\nGoal: ${response.goal}\n\nSteps:\n${stepText}`,
391
+ text: `${summary}\nGoal: ${response.goal}\n\nSteps:\n${stepText}${metaText}`,
293
392
  },
294
393
  ],
295
394
  details: response,
@@ -312,8 +411,10 @@ const stratusPlugin = {
312
411
  const subcommand = (tokens[0] || "help").toLowerCase();
313
412
 
314
413
  if (subcommand === "setup") {
315
- // Run setup
316
- const result = await setupStratus(ctx.prompter);
414
+ const keyArg = tokens[1];
415
+ const result = keyArg
416
+ ? await setupStratus({ apiKey: keyArg, silent: true })
417
+ : await setupStratus(ctx.prompter);
317
418
 
318
419
  if (result.success) {
319
420
  console.log(`\n✅ ${result.message}\n`);
@@ -327,52 +428,92 @@ const stratusPlugin = {
327
428
  }
328
429
  process.exit(1);
329
430
  }
330
- return { text: "" }; // Return empty to avoid double output
431
+ return { text: "" };
432
+ } else if (subcommand === "models") {
433
+ const apiKey = pluginConfig.apiKey || process.env.STRATUS_API_KEY;
434
+ const models = await generateAllModels(apiKey, pluginConfig.baseUrl);
435
+
436
+ await updateStoredModels(models);
437
+
438
+ const sizes = ["small", "base", "large", "xl", "huge"];
439
+ const grouped = sizes
440
+ .map((size) => {
441
+ const sizeModels = models.filter(
442
+ (m) => m.id.includes(`-x1ac-${size}-`) || m.id.endsWith(`-x1ac-${size}`)
443
+ );
444
+ if (sizeModels.length === 0) return null;
445
+ return ` ${size.toUpperCase()} (${sizeModels.length})\n` +
446
+ sizeModels.map((m) => ` • ${m.id}`).join("\n");
447
+ })
448
+ .filter(Boolean)
449
+ .join("\n\n");
450
+
451
+ return {
452
+ text:
453
+ `Stratus Models (${models.length} total)\n` +
454
+ `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n` +
455
+ grouped +
456
+ `\n\nConfig synced. Run /models to see the full list.`,
457
+ };
331
458
  } else if (subcommand === "verify") {
332
- // Run verify
333
459
  console.log("🔍 Verifying Stratus configuration...\n");
334
460
 
335
- let errors = 0;
461
+ const hasKey = !!(pluginConfig.apiKey || process.env.STRATUS_API_KEY);
336
462
 
337
- // Check 1: Environment variable
338
- console.log("1️⃣ Checking STRATUS_API_KEY...");
339
- if (process.env.STRATUS_API_KEY) {
340
- console.log(" ✓ STRATUS_API_KEY is set");
463
+ console.log("1️⃣ Auth mode:");
464
+ if (hasKey) {
465
+ console.log(" ✓ BYOK — using STRATUS_API_KEY (no markup)");
341
466
  } else {
342
- console.log(" STRATUS_API_KEY not found");
343
- errors++;
467
+ console.log(" Formation pool — zero-config (25% markup)");
468
+ console.log(" 💡 Set STRATUS_API_KEY to remove markup");
344
469
  }
345
470
 
346
- // Check 2: Config exists
347
- console.log("\n2️⃣ Checking plugin configuration...");
348
- if (pluginConfig.apiKey || process.env.STRATUS_API_KEY) {
349
- console.log(" ✓ API key configured");
350
- } else {
351
- console.log(" ❌ API key not configured");
352
- errors++;
471
+ console.log("\n2️⃣ API connectivity...");
472
+ try {
473
+ const models = await generateAllModels(
474
+ hasKey ? (pluginConfig.apiKey || process.env.STRATUS_API_KEY) : undefined,
475
+ pluginConfig.baseUrl,
476
+ );
477
+ console.log(` ✓ Connected — ${models.length} models available`);
478
+ } catch {
479
+ console.log(" ⚠ Could not reach API (may still work at runtime)");
353
480
  }
354
481
 
355
- console.log("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
356
- if (errors === 0) {
357
- console.log(" All checks passed!");
358
- console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
359
- console.log("🎯 Try it out:");
360
- console.log(" openclaw agent 'Hello Stratus!' --model stratus\n");
361
- } else {
362
- console.log(`❌ ${errors} issue(s) found`);
363
- console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
364
- console.log("💡 Run: openclaw stratus setup\n");
365
- process.exit(1);
482
+ console.log("\n3️⃣ Inline BYOK keys:");
483
+ const envKeys = [
484
+ ["OPENAI_API_KEY", process.env.OPENAI_API_KEY],
485
+ ["ANTHROPIC_API_KEY", process.env.ANTHROPIC_API_KEY],
486
+ ["GOOGLE_API_KEY", process.env.GOOGLE_API_KEY],
487
+ ] as const;
488
+ let anyInline = false;
489
+ for (const [name, val] of envKeys) {
490
+ if (val) {
491
+ console.log(` ✓ ${name} detected (BYOK passthrough)`);
492
+ anyInline = true;
493
+ }
366
494
  }
367
- return { text: "" }; // Return empty to avoid double output
495
+ if (!anyInline) {
496
+ console.log(" – None set (optional — Formation pool covers all providers)");
497
+ }
498
+
499
+ console.log("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
500
+ console.log("✅ Stratus is ready!");
501
+ console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
502
+ console.log("🎯 Try it out:");
503
+ console.log(" openclaw agent 'Hello Stratus!' --model stratus\n");
504
+ return { text: "" };
368
505
  } else {
369
- // Show help
370
506
  return {
371
507
  text:
372
508
  "🌊 Stratus X1-AC Plugin\n\n" +
373
509
  "Commands:\n" +
374
- " /stratus setup - Interactive setup wizard\n" +
375
- " /stratus verify - Verify configuration\n\n" +
510
+ " /stratus setup - Interactive setup wizard (Formation pool)\n" +
511
+ " /stratus setup <stratus_sk_...> - Non-interactive setup with API key\n" +
512
+ " /stratus verify - Verify configuration & connectivity\n" +
513
+ " /stratus models - List all available models (live from API)\n\n" +
514
+ "Agent/programmatic setup:\n" +
515
+ " /stratus setup stratus_sk_your_key_here\n" +
516
+ " # Or: set STRATUS_API_KEY env var → gateway auto-configures on start\n\n" +
376
517
  "Get your API key: https://stratus.run\n" +
377
518
  "Docs: https://stratus.run/docs\n" +
378
519
  "Issues: https://github.com/formthefog/openclaw-stratus-x1-plugin/issues",
@@ -12,46 +12,70 @@
12
12
  "default": true,
13
13
  "description": "Enable Stratus plugin"
14
14
  },
15
- "apiKey": {
16
- "type": "string",
17
- "description": "Stratus API key (or use STRATUS_API_KEY env var)"
18
- },
19
- "baseUrl": {
20
- "type": "string",
21
- "default": "https://api.stratus.run",
22
- "description": "Stratus API base URL"
23
- },
24
- "provider": {
15
+ "config": {
25
16
  "type": "object",
17
+ "description": "Stratus plugin configuration",
26
18
  "properties": {
27
- "enabled": {
28
- "type": "boolean",
29
- "default": true
19
+ "apiKey": {
20
+ "type": "string",
21
+ "description": "Stratus API key (optional — Formation pool used as fallback)"
30
22
  },
31
- "defaultModel": {
23
+ "baseUrl": {
32
24
  "type": "string",
33
- "default": "stratus-x1ac-base-claude-sonnet-4-5"
34
- }
35
- }
36
- },
37
- "tools": {
38
- "type": "object",
39
- "properties": {
40
- "embeddings": {
25
+ "default": "https://api.stratus.run",
26
+ "description": "Stratus API base URL"
27
+ },
28
+ "inlineKeys": {
41
29
  "type": "object",
30
+ "description": "Optional BYOK keys passed per-request to bypass Formation pool",
42
31
  "properties": {
43
- "enabled": {
44
- "type": "boolean",
45
- "default": true
32
+ "openai_key": {
33
+ "type": "string",
34
+ "description": "OpenAI API key for BYOK passthrough"
35
+ },
36
+ "anthropic_key": {
37
+ "type": "string",
38
+ "description": "Anthropic API key for BYOK passthrough"
39
+ },
40
+ "gemini_key": {
41
+ "type": "string",
42
+ "description": "Google Gemini API key for BYOK passthrough (also sent as X-Google-Key header)"
46
43
  }
47
44
  }
48
45
  },
49
- "rollout": {
46
+ "provider": {
50
47
  "type": "object",
51
48
  "properties": {
52
49
  "enabled": {
53
50
  "type": "boolean",
54
51
  "default": true
52
+ },
53
+ "defaultModel": {
54
+ "type": "string",
55
+ "default": "stratus-x1ac-base-claude-sonnet-4-5"
56
+ }
57
+ }
58
+ },
59
+ "tools": {
60
+ "type": "object",
61
+ "properties": {
62
+ "embeddings": {
63
+ "type": "object",
64
+ "properties": {
65
+ "enabled": {
66
+ "type": "boolean",
67
+ "default": true
68
+ }
69
+ }
70
+ },
71
+ "rollout": {
72
+ "type": "object",
73
+ "properties": {
74
+ "enabled": {
75
+ "type": "boolean",
76
+ "default": true
77
+ }
78
+ }
55
79
  }
56
80
  }
57
81
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@formthefog/stratus",
3
- "version": "2026.2.24",
3
+ "version": "2026.3.20",
4
4
  "description": "Stratus API integration for OpenClaw - action-conditioned JEPA for autonomous agent planning",
5
5
  "keywords": [
6
6
  "agent",