simmer-automaton 0.6.5 → 0.6.7

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.
package/dist/api.d.ts CHANGED
@@ -46,8 +46,31 @@ export interface BriefingPosition {
46
46
  resolves_at: string | null;
47
47
  source: string | null;
48
48
  }
49
+ export interface BriefingVenue {
50
+ currency: string;
51
+ balance: number | null;
52
+ pnl: number;
53
+ positions_count: number;
54
+ redeemable_count?: number;
55
+ positions_needing_attention: BriefingPosition[];
56
+ actions: string[];
57
+ by_skill?: Record<string, {
58
+ count: number;
59
+ pnl: number;
60
+ }>;
61
+ }
49
62
  export interface BriefingResponse {
50
- portfolio: {
63
+ venues: {
64
+ simmer: BriefingVenue | null;
65
+ polymarket: BriefingVenue | null;
66
+ kalshi: BriefingVenue | null;
67
+ };
68
+ opportunities: {
69
+ new_markets: Array<Record<string, unknown>>;
70
+ recommended_skills: Array<Record<string, unknown>>;
71
+ };
72
+ risk_alerts: string[];
73
+ performance: {
51
74
  total_pnl: number;
52
75
  pnl_percent: number;
53
76
  win_rate: number;
@@ -55,16 +78,12 @@ export interface BriefingResponse {
55
78
  total_agents: number;
56
79
  settled_pnl: number;
57
80
  };
58
- positions: BriefingPosition[];
59
- recent_trades: Array<{
60
- market_question: string;
61
- action: string;
62
- side: string;
63
- shares: number;
64
- cost: number;
65
- source: string | null;
66
- created_at: string;
67
- }>;
81
+ checked_at: string;
82
+ sdk_update: {
83
+ current: string;
84
+ latest: string;
85
+ message: string;
86
+ } | null;
68
87
  }
69
88
  export interface SkillOutcome {
70
89
  skill_slug: string;
@@ -122,6 +141,9 @@ export declare class SimmerApi {
122
141
  since: string;
123
142
  }>;
124
143
  getBriefing(): Promise<BriefingResponse>;
144
+ getPositions(): Promise<{
145
+ positions: BriefingPosition[];
146
+ }>;
125
147
  postCycle(data: {
126
148
  active_skills: string[];
127
149
  cycle_number: number;
package/dist/api.js CHANGED
@@ -73,6 +73,9 @@ export class SimmerApi {
73
73
  async getBriefing() {
74
74
  return this.request("/api/sdk/briefing");
75
75
  }
76
+ async getPositions() {
77
+ return this.request("/api/sdk/positions");
78
+ }
76
79
  async postCycle(data) {
77
80
  return this.request("/api/sdk/automaton/cycle", {
78
81
  method: "POST",
package/dist/index.js CHANGED
@@ -85,8 +85,7 @@ async function refreshState(logger) {
85
85
  if (cachedState.venue) {
86
86
  config.venue = cachedState.venue;
87
87
  }
88
- // Compute tier (totalPnl = 0 for now, will be enriched when P&L tracking is added)
89
- currentTier = computeTier(cachedState, 0);
88
+ currentTier = computeTier(cachedState, cachedPortfolio?.totalPnl ?? 0);
90
89
  // Sync banditState from fetched skills — preserve memory for existing, seed new ones
91
90
  const existingBySlug = new Map(banditState.map((s) => [s.slug, s]));
92
91
  banditState = cachedSkills.map((skill) => {
@@ -178,15 +177,15 @@ function buildPromptContext() {
178
177
  lines.push("");
179
178
  lines.push(`**Portfolio:** ${cachedPortfolio.positionCount} positions | P&L: ${fmtCurrency(cachedPortfolio.totalPnl)} | Recent trades: ${cachedPortfolio.recentTradeCount}`);
180
179
  if (cachedPortfolio.positions.length > 0) {
181
- // Show top 3 by absolute PnL
182
- const sorted = [...cachedPortfolio.positions].sort((a, b) => Math.abs(b.pnl) - Math.abs(a.pnl));
183
- const top = sorted.slice(0, 3);
180
+ // Show positions needing attention (significant moves or nearing expiry)
181
+ lines.push(" Needing attention:");
182
+ const top = cachedPortfolio.positions.slice(0, 3);
184
183
  for (const p of top) {
185
184
  const pnlStr = p.pnl >= 0 ? `+${fmtCurrency(p.pnl)}` : fmtCurrency(p.pnl);
186
185
  lines.push(` - ${p.question.slice(0, 60)} | ${p.side} ${p.shares} shares @ ${p.avg_entry.toFixed(2)} → ${p.current_price.toFixed(2)} | ${pnlStr}`);
187
186
  }
188
- if (sorted.length > 3) {
189
- lines.push(` - ...and ${sorted.length - 3} more (use /simmer portfolio for full list)`);
187
+ if (cachedPortfolio.positions.length > 3) {
188
+ lines.push(` - ...and ${cachedPortfolio.positions.length - 3} more needing attention`);
190
189
  }
191
190
  }
192
191
  }
@@ -298,9 +297,11 @@ async function executeSkills(selectedSlugs, workspaceDir, logger) {
298
297
  // =============================================================================
299
298
  export default function register(pluginApi) {
300
299
  loadConfig(pluginApi.pluginConfig);
301
- // Fall back to SIMMER_API_KEY env var if no plugin config apiKey
302
- if (!config.apiKey) {
303
- config.apiKey = process.env.SIMMER_API_KEY || "";
300
+ // Env var takes priority; fall back to plugin config only if env var is absent.
301
+ // This prevents placeholder values in openclaw.json from shadowing the real key.
302
+ const envKey = process.env.SIMMER_API_KEY || "";
303
+ if (envKey) {
304
+ config.apiKey = envKey;
304
305
  }
305
306
  if (!config.apiKey) {
306
307
  pluginApi.logger.error("[simmer] No apiKey in plugin config or SIMMER_API_KEY env var — plugin disabled");
@@ -423,12 +424,13 @@ export default function register(pluginApi) {
423
424
  // Fetch portfolio snapshot for prompt context
424
425
  try {
425
426
  const briefing = await api.getBriefing();
426
- const positions = briefing.positions || [];
427
+ const venue = briefing.venues?.simmer;
428
+ const attentionPositions = venue?.positions_needing_attention || [];
427
429
  cachedPortfolio = {
428
- totalPnl: briefing.portfolio?.total_pnl ?? 0,
429
- positionCount: positions.length,
430
- positions,
431
- recentTradeCount: briefing.recent_trades?.length ?? 0,
430
+ totalPnl: briefing.performance?.total_pnl ?? 0,
431
+ positionCount: venue?.positions_count ?? 0,
432
+ positions: attentionPositions,
433
+ recentTradeCount: 0,
432
434
  };
433
435
  }
434
436
  catch (e) {
@@ -621,8 +623,8 @@ export default function register(pluginApi) {
621
623
  }
622
624
  if (subcommand === "portfolio") {
623
625
  try {
624
- const briefing = await api.getBriefing();
625
- const positions = briefing.positions || [];
626
+ const res = await api.getPositions();
627
+ const positions = res.positions || [];
626
628
  if (positions.length === 0) {
627
629
  return { text: "No open positions." };
628
630
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simmer-automaton",
3
- "version": "0.6.5",
3
+ "version": "0.6.7",
4
4
  "description": "Simmer Automaton plugin for OpenClaw — autonomous trading skill orchestration",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/api.ts CHANGED
@@ -51,8 +51,29 @@ export interface BriefingPosition {
51
51
  source: string | null;
52
52
  }
53
53
 
54
+ export interface BriefingVenue {
55
+ currency: string;
56
+ balance: number | null;
57
+ pnl: number;
58
+ positions_count: number;
59
+ redeemable_count?: number;
60
+ positions_needing_attention: BriefingPosition[];
61
+ actions: string[];
62
+ by_skill?: Record<string, { count: number; pnl: number }>;
63
+ }
64
+
54
65
  export interface BriefingResponse {
55
- portfolio: {
66
+ venues: {
67
+ simmer: BriefingVenue | null;
68
+ polymarket: BriefingVenue | null;
69
+ kalshi: BriefingVenue | null;
70
+ };
71
+ opportunities: {
72
+ new_markets: Array<Record<string, unknown>>;
73
+ recommended_skills: Array<Record<string, unknown>>;
74
+ };
75
+ risk_alerts: string[];
76
+ performance: {
56
77
  total_pnl: number;
57
78
  pnl_percent: number;
58
79
  win_rate: number;
@@ -60,16 +81,8 @@ export interface BriefingResponse {
60
81
  total_agents: number;
61
82
  settled_pnl: number;
62
83
  };
63
- positions: BriefingPosition[];
64
- recent_trades: Array<{
65
- market_question: string;
66
- action: string;
67
- side: string;
68
- shares: number;
69
- cost: number;
70
- source: string | null;
71
- created_at: string;
72
- }>;
84
+ checked_at: string;
85
+ sdk_update: { current: string; latest: string; message: string } | null;
73
86
  }
74
87
 
75
88
  export interface SkillOutcome {
@@ -176,6 +189,10 @@ export class SimmerApi {
176
189
  return this.request("/api/sdk/briefing");
177
190
  }
178
191
 
192
+ async getPositions(): Promise<{ positions: BriefingPosition[] }> {
193
+ return this.request("/api/sdk/positions");
194
+ }
195
+
179
196
  async postCycle(data: {
180
197
  active_skills: string[];
181
198
  cycle_number: number;
package/src/index.ts CHANGED
@@ -117,8 +117,7 @@ async function refreshState(logger: { info: (m: string) => void; error: (m: stri
117
117
  if (cachedState.venue) {
118
118
  config.venue = cachedState.venue;
119
119
  }
120
- // Compute tier (totalPnl = 0 for now, will be enriched when P&L tracking is added)
121
- currentTier = computeTier(cachedState, 0);
120
+ currentTier = computeTier(cachedState, cachedPortfolio?.totalPnl ?? 0);
122
121
 
123
122
  // Sync banditState from fetched skills — preserve memory for existing, seed new ones
124
123
  const existingBySlug = new Map(banditState.map((s) => [s.slug, s]));
@@ -219,15 +218,15 @@ function buildPromptContext(): string {
219
218
  lines.push("");
220
219
  lines.push(`**Portfolio:** ${cachedPortfolio.positionCount} positions | P&L: ${fmtCurrency(cachedPortfolio.totalPnl)} | Recent trades: ${cachedPortfolio.recentTradeCount}`);
221
220
  if (cachedPortfolio.positions.length > 0) {
222
- // Show top 3 by absolute PnL
223
- const sorted = [...cachedPortfolio.positions].sort((a, b) => Math.abs(b.pnl) - Math.abs(a.pnl));
224
- const top = sorted.slice(0, 3);
221
+ // Show positions needing attention (significant moves or nearing expiry)
222
+ lines.push(" Needing attention:");
223
+ const top = cachedPortfolio.positions.slice(0, 3);
225
224
  for (const p of top) {
226
225
  const pnlStr = p.pnl >= 0 ? `+${fmtCurrency(p.pnl)}` : fmtCurrency(p.pnl);
227
226
  lines.push(` - ${p.question.slice(0, 60)} | ${p.side} ${p.shares} shares @ ${p.avg_entry.toFixed(2)} → ${p.current_price.toFixed(2)} | ${pnlStr}`);
228
227
  }
229
- if (sorted.length > 3) {
230
- lines.push(` - ...and ${sorted.length - 3} more (use /simmer portfolio for full list)`);
228
+ if (cachedPortfolio.positions.length > 3) {
229
+ lines.push(` - ...and ${cachedPortfolio.positions.length - 3} more needing attention`);
231
230
  }
232
231
  }
233
232
  }
@@ -358,9 +357,11 @@ async function executeSkills(
358
357
  export default function register(pluginApi: PluginApi) {
359
358
  loadConfig(pluginApi.pluginConfig);
360
359
 
361
- // Fall back to SIMMER_API_KEY env var if no plugin config apiKey
362
- if (!config.apiKey) {
363
- config.apiKey = process.env.SIMMER_API_KEY || "";
360
+ // Env var takes priority; fall back to plugin config only if env var is absent.
361
+ // This prevents placeholder values in openclaw.json from shadowing the real key.
362
+ const envKey = process.env.SIMMER_API_KEY || "";
363
+ if (envKey) {
364
+ config.apiKey = envKey;
364
365
  }
365
366
 
366
367
  if (!config.apiKey) {
@@ -495,12 +496,13 @@ export default function register(pluginApi: PluginApi) {
495
496
  // Fetch portfolio snapshot for prompt context
496
497
  try {
497
498
  const briefing = await api.getBriefing();
498
- const positions = briefing.positions || [];
499
+ const venue = briefing.venues?.simmer;
500
+ const attentionPositions = venue?.positions_needing_attention || [];
499
501
  cachedPortfolio = {
500
- totalPnl: briefing.portfolio?.total_pnl ?? 0,
501
- positionCount: positions.length,
502
- positions,
503
- recentTradeCount: briefing.recent_trades?.length ?? 0,
502
+ totalPnl: briefing.performance?.total_pnl ?? 0,
503
+ positionCount: venue?.positions_count ?? 0,
504
+ positions: attentionPositions,
505
+ recentTradeCount: 0,
504
506
  };
505
507
  } catch (e) {
506
508
  ctx.logger.warn(`[simmer] Failed to fetch briefing: ${e}`);
@@ -701,8 +703,8 @@ export default function register(pluginApi: PluginApi) {
701
703
 
702
704
  if (subcommand === "portfolio") {
703
705
  try {
704
- const briefing = await api.getBriefing();
705
- const positions = briefing.positions || [];
706
+ const res = await api.getPositions();
707
+ const positions = res.positions || [];
706
708
  if (positions.length === 0) {
707
709
  return { text: "No open positions." };
708
710
  }