plugin-syndicate 0.1.0 → 0.1.1

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/README.md CHANGED
@@ -63,6 +63,13 @@ session (games expire after 7 idle days server-side).
63
63
  state names them in `power.bestSoloAgentId`. Sending a weaker member into a
64
64
  "beatable" fight loses. Crew death is permanent.
65
65
 
66
+ ## Context provider
67
+
68
+ `syndicate-game-state` narrates the current game (day, capital, heat,
69
+ territory, stash) into the agent's context on every message — from the cache
70
+ written by the actions, with **zero** extra API calls. Your character always
71
+ knows where the game stands without polling.
72
+
66
73
  ## Related
67
74
 
68
75
  - [`plugin-undesirables`](https://github.com/sailorpepe/plugin-undesirables) —
package/dist/index.js CHANGED
@@ -2,29 +2,58 @@
2
2
  var GAME_API = "https://play.the-undesirables.com/api/game";
3
3
  var SCORES_API = "https://play.the-undesirables.com/api/scores";
4
4
  var SKILL_URL = "https://play.the-undesirables.com/SKILL.md";
5
- var PLUGIN_VERSION = "0.1.0";
5
+ var PLUGIN_VERSION = "0.1.1";
6
+ var FETCH_TIMEOUT_MS = 15e3;
6
7
  var sessions = /* @__PURE__ */ new Map();
8
+ var lastViews = /* @__PURE__ */ new Map();
7
9
  function sessionFor(runtime) {
8
10
  const configured = runtime.getSetting?.("SYNDICATE_SESSION_ID");
9
11
  if (configured && typeof configured === "string") return configured;
10
12
  return sessions.get(String(runtime.agentId));
11
13
  }
14
+ var GameError = class extends Error {
15
+ constructor(message, expired = false) {
16
+ super(message);
17
+ this.expired = expired;
18
+ }
19
+ expired;
20
+ };
21
+ function toGameError(status, serverMsg) {
22
+ if (status === 404) return new GameError("Session unknown or expired (games expire after 7 idle days).", true);
23
+ if (status === 429) return new GameError("The city is rate-limited (30 calls/min/IP). Wait a minute, then try again.");
24
+ if (status === 409) return new GameError(serverMsg || "Game over \u2014 start a new syndicate with SYNDICATE_NEW.");
25
+ return new GameError(serverMsg || `HTTP ${status}`);
26
+ }
12
27
  async function api(body) {
13
28
  const res = await fetch(GAME_API, {
14
29
  method: "POST",
15
30
  headers: { "content-type": "application/json" },
16
- body: JSON.stringify(body)
31
+ body: JSON.stringify(body),
32
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
17
33
  });
18
- const data = await res.json();
19
- if (!res.ok) throw new Error(String(data.error || `HTTP ${res.status}`));
34
+ const data = await res.json().catch(() => ({}));
35
+ if (!res.ok) throw toGameError(res.status, String(data.error || ""));
20
36
  return data;
21
37
  }
22
38
  async function apiView(sessionId) {
23
- const res = await fetch(`${GAME_API}?id=${encodeURIComponent(sessionId)}`);
24
- const data = await res.json();
25
- if (!res.ok) throw new Error(String(data.error || `HTTP ${res.status}`));
39
+ const res = await fetch(`${GAME_API}?id=${encodeURIComponent(sessionId)}`, {
40
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
41
+ });
42
+ const data = await res.json().catch(() => ({}));
43
+ if (!res.ok) throw toGameError(res.status, String(data.error || ""));
26
44
  return data;
27
45
  }
46
+ function handleGameError(runtime, e) {
47
+ if (e instanceof GameError && e.expired) {
48
+ sessions.delete(String(runtime.agentId));
49
+ lastViews.delete(String(runtime.agentId));
50
+ return `${e.message} Start fresh with SYNDICATE_NEW.`;
51
+ }
52
+ if (e instanceof Error && e.name === "TimeoutError") {
53
+ return "The game server did not answer within 15s \u2014 it may be waking up. Try again shortly.";
54
+ }
55
+ return e instanceof Error ? e.message : String(e);
56
+ }
28
57
  function summarizeView(v) {
29
58
  const events = Array.isArray(v.events) ? v.events.slice(0, 12) : [];
30
59
  const crew = Array.isArray(v.crew) ? v.crew : [];
@@ -43,8 +72,12 @@ function summarizeView(v) {
43
72
  ];
44
73
  if (events.length) lines.push("", "What happened:", ...events.map((e) => ` ${e}`));
45
74
  if (v.gameOver) lines.push("", "GAME OVER \u2014 start a new syndicate with SYNDICATE_NEW.");
75
+ if (v.sessionId) lines.push("", `(session ${v.sessionId} \u2014 set SYNDICATE_SESSION_ID to resume it after a restart)`);
46
76
  return lines.join("\n");
47
77
  }
78
+ function remember(runtime, view) {
79
+ lastViews.set(String(runtime.agentId), view);
80
+ }
48
81
  var newGameAction = {
49
82
  name: "SYNDICATE_NEW",
50
83
  description: "Start a new game of The Syndicate. Creates a fresh crime family in a deterministic 80\xD780 city. One session per agent; the old session is abandoned.",
@@ -69,13 +102,14 @@ var newGameAction = {
69
102
  try {
70
103
  const view = await api({ action: "new", name: name.slice(0, 32), model: `elizaos/${runtime.character?.name || "agent"}` });
71
104
  sessions.set(String(runtime.agentId), String(view.sessionId));
105
+ remember(runtime, view);
72
106
  const text = `New syndicate founded. Read the rules once: ${SKILL_URL}
73
107
 
74
108
  ${summarizeView(view)}`;
75
109
  if (callback) await callback({ text });
76
110
  return { success: true, text, data: { sessionId: String(view.sessionId), view } };
77
111
  } catch (e) {
78
- const text = `Could not start a game: ${e instanceof Error ? e.message : String(e)}`;
112
+ const text = `Could not start a game: ${handleGameError(runtime, e)}`;
79
113
  if (callback) await callback({ text });
80
114
  return { success: false, text };
81
115
  }
@@ -102,11 +136,12 @@ var stateAction = {
102
136
  }
103
137
  try {
104
138
  const view = await apiView(sessionId);
139
+ remember(runtime, view);
105
140
  const text = summarizeView(view);
106
141
  if (callback) await callback({ text });
107
142
  return { success: true, text, data: { view } };
108
143
  } catch (e) {
109
- const text = `Could not read the game: ${e instanceof Error ? e.message : String(e)}`;
144
+ const text = `Could not read the game: ${handleGameError(runtime, e)}`;
110
145
  if (callback) await callback({ text });
111
146
  return { success: false, text };
112
147
  }
@@ -150,11 +185,12 @@ var moveAction = {
150
185
  }
151
186
  try {
152
187
  const view = await api({ action: "orders", sessionId, orders });
188
+ remember(runtime, view);
153
189
  const text = summarizeView(view);
154
190
  if (callback) await callback({ text });
155
191
  return { success: true, text, data: { view } };
156
192
  } catch (e) {
157
- const text = `The engine rejected the orders: ${e instanceof Error ? e.message : String(e)}`;
193
+ const text = `The engine rejected the orders: ${handleGameError(runtime, e)}`;
158
194
  if (callback) await callback({ text });
159
195
  return { success: false, text };
160
196
  }
@@ -174,7 +210,7 @@ var leaderboardAction = {
174
210
  validate: async () => true,
175
211
  handler: async (_runtime, _message, _state, _options, callback) => {
176
212
  try {
177
- const res = await fetch(SCORES_API);
213
+ const res = await fetch(SCORES_API, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
178
214
  const data = await res.json();
179
215
  const rows = (data.scores || []).slice(0, 10);
180
216
  const text = rows.length ? rows.map(
@@ -189,11 +225,25 @@ var leaderboardAction = {
189
225
  }
190
226
  }
191
227
  };
228
+ var syndicateProvider = {
229
+ name: "syndicate-game-state",
230
+ description: "Current state of the agent's Syndicate game (cached from the last action; no network calls)",
231
+ get: async (runtime, _message, _state) => {
232
+ const view = lastViews.get(String(runtime.agentId));
233
+ if (!view || !sessionFor(runtime)) return { text: "" };
234
+ const territory = view.territory || {};
235
+ const stash = view.stash || {};
236
+ return {
237
+ text: `[The Syndicate \u2014 day ${view.day}: capital $${view.capital}, heat ${view.heat}, ${territory.ownedTiles}/${territory.tilesToWin} tiles, stash $${stash.fenceValue}. Use SYNDICATE_STATE for detail, SYNDICATE_MOVE to play the day.]`,
238
+ data: { view }
239
+ };
240
+ }
241
+ };
192
242
  var syndicatePlugin = {
193
243
  name: "plugin-syndicate",
194
244
  description: `The Syndicate v${PLUGIN_VERSION} \u2014 agent-playable crime-city strategy with oracle-priced loot. Rules: ${SKILL_URL}`,
195
245
  actions: [newGameAction, stateAction, moveAction, leaderboardAction],
196
- providers: [],
246
+ providers: [syndicateProvider],
197
247
  evaluators: []
198
248
  };
199
249
  var index_default = syndicatePlugin;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * The Syndicate — ElizaOS Plugin v0.1\n * ====================================\n * Lets any elizaOS agent run a crime family in The Syndicate: a deterministic,\n * turn-based 80×80 city where the loot is REAL trading cards priced by a live\n * on-chain TCG oracle. Humans and agents share one world and one leaderboard.\n *\n * The game is server-authoritative — this plugin submits order INTENTS to the\n * public API and narrates what the engine resolved. There is no client-side\n * simulation and nothing to cheat.\n *\n * SYNDICATE_NEW start a game (one session per agent, kept in memory)\n * SYNDICATE_STATE read the current day without spending it\n * SYNDICATE_MOVE submit a day of orders and get the resolved events\n * SYNDICATE_LEADERBOARD the shared human+agent board\n *\n * Rules for models: https://play.the-undesirables.com/SKILL.md\n *\n * @see https://play.the-undesirables.com\n * @see https://github.com/sailorpepe/plugin-syndicate\n */\n\nimport type {\n Plugin,\n Action,\n IAgentRuntime,\n Memory,\n State,\n HandlerCallback,\n ActionExample,\n ActionResult,\n} from \"@elizaos/core\";\n\nconst GAME_API = \"https://play.the-undesirables.com/api/game\";\nconst SCORES_API = \"https://play.the-undesirables.com/api/scores\";\nconst SKILL_URL = \"https://play.the-undesirables.com/SKILL.md\";\nconst PLUGIN_VERSION = \"0.1.0\";\n\n// ============================================================\n// Session bookkeeping — one live game per agent, in-process.\n// The server holds all state; losing this map only means the\n// agent starts a fresh syndicate (sessions expire in 7 idle\n// days server-side anyway).\n// ============================================================\n\nconst sessions = new Map<string, string>(); // agentId -> sessionId\n\nfunction sessionFor(runtime: IAgentRuntime): string | undefined {\n const configured = runtime.getSetting?.(\"SYNDICATE_SESSION_ID\");\n if (configured && typeof configured === \"string\") return configured;\n return sessions.get(String(runtime.agentId));\n}\n\nasync function api(body: Record<string, unknown>): Promise<Record<string, unknown>> {\n const res = await fetch(GAME_API, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n const data = (await res.json()) as Record<string, unknown>;\n if (!res.ok) throw new Error(String((data as { error?: string }).error || `HTTP ${res.status}`));\n return data;\n}\n\nasync function apiView(sessionId: string): Promise<Record<string, unknown>> {\n const res = await fetch(`${GAME_API}?id=${encodeURIComponent(sessionId)}`);\n const data = (await res.json()) as Record<string, unknown>;\n if (!res.ok) throw new Error(String((data as { error?: string }).error || `HTTP ${res.status}`));\n return data;\n}\n\n// Compact narration so the model sees the day without drowning in JSON.\nfunction summarizeView(v: Record<string, unknown>): string {\n const events = Array.isArray(v.events) ? (v.events as string[]).slice(0, 12) : [];\n const crew = Array.isArray(v.crew) ? (v.crew as Record<string, unknown>[]) : [];\n const power = (v.power || {}) as Record<string, unknown>;\n const territory = (v.territory || {}) as Record<string, unknown>;\n const stash = (v.stash || {}) as Record<string, unknown>;\n const targets = Array.isArray(v.targets) ? (v.targets as Record<string, unknown>[]) : [];\n const beatable = targets.filter((t) => t.beatable !== \"no\").slice(0, 8);\n const lines = [\n `Day ${v.day} — capital $${v.capital}, heat ${v.heat}, wanted ${v.wantedLevel}`,\n `Territory: ${territory.ownedTiles}/${territory.tilesToWin} tiles toward the Kingpin win`,\n `Stash: ${stash.cards} card(s), fence value $${stash.fenceValue}`,\n `Crew: ${crew.map((c) => `${c.name} (hp ${c.hp}${(c as { unavailable?: string }).unavailable ? `, ${(c as { unavailable?: string }).unavailable}` : \"\"})`).join(\" · \")}`,\n `Power: best solo ${power.bestSolo} (${power.bestSoloAgentName}), full squad ${power.fullSquad}`,\n beatable.length\n ? `Beatable now: ${beatable.map((t) => `#${t.tileId} ${t.type} vault $${t.vault} (${t.beatable})`).join(\", \")}`\n : `Nothing is beatable right now — hire muscle or squad up.`,\n ];\n if (events.length) lines.push(\"\", \"What happened:\", ...events.map((e) => ` ${e}`));\n if (v.gameOver) lines.push(\"\", \"GAME OVER — start a new syndicate with SYNDICATE_NEW.\");\n return lines.join(\"\\n\");\n}\n\n// ============================================================\n// Actions\n// ============================================================\n\nconst newGameAction: Action = {\n name: \"SYNDICATE_NEW\",\n description:\n \"Start a new game of The Syndicate. Creates a fresh crime family in a deterministic 80×80 city. One session per agent; the old session is abandoned.\",\n similes: [\"START_SYNDICATE\", \"NEW_CRIME_FAMILY\", \"PLAY_SYNDICATE\"],\n parameters: [\n {\n name: \"syndicate_name\",\n description: \"Name for your crime family (max 32 chars)\",\n required: false,\n schema: { type: \"string\" },\n },\n ],\n examples: [\n [\n { name: \"{{user1}}\", content: { text: \"Start a syndicate called The Night Owls\" } } as ActionExample,\n { name: \"{{agentName}}\", content: { text: \"Founding The Night Owls...\", action: \"SYNDICATE_NEW\" } } as ActionExample,\n ],\n ],\n validate: async () => true,\n handler: async (\n runtime: IAgentRuntime,\n message: Memory,\n _state?: State,\n options?: Record<string, unknown>,\n callback?: HandlerCallback\n ): Promise<ActionResult | undefined> => {\n const name =\n (options?.syndicate_name as string) ||\n message.content.text?.match(/called\\s+(.{2,32}?)(?:\\.|$)/i)?.[1] ||\n `${runtime.character?.name || \"Agent\"} Syndicate`;\n try {\n const view = await api({ action: \"new\", name: name.slice(0, 32), model: `elizaos/${runtime.character?.name || \"agent\"}` });\n sessions.set(String(runtime.agentId), String(view.sessionId));\n const text = `New syndicate founded. Read the rules once: ${SKILL_URL}\\n\\n${summarizeView(view)}`;\n if (callback) await callback({ text });\n return { success: true, text, data: { sessionId: String(view.sessionId), view } };\n } catch (e) {\n const text = `Could not start a game: ${e instanceof Error ? e.message : String(e)}`;\n if (callback) await callback({ text });\n return { success: false, text };\n }\n },\n};\n\nconst stateAction: Action = {\n name: \"SYNDICATE_STATE\",\n description:\n \"Read the current state of your Syndicate game WITHOUT spending a day: crew, capital, heat, stash value, beatable targets, and progress toward the Kingpin win.\",\n similes: [\"SYNDICATE_STATUS\", \"CHECK_SYNDICATE\", \"GAME_STATE\"],\n parameters: [],\n examples: [\n [\n { name: \"{{user1}}\", content: { text: \"How is the syndicate doing?\" } } as ActionExample,\n { name: \"{{agentName}}\", content: { text: \"Checking the city...\", action: \"SYNDICATE_STATE\" } } as ActionExample,\n ],\n ],\n validate: async (runtime: IAgentRuntime) => sessionFor(runtime) !== undefined,\n handler: async (\n runtime: IAgentRuntime,\n _message: Memory,\n _state?: State,\n _options?: Record<string, unknown>,\n callback?: HandlerCallback\n ): Promise<ActionResult | undefined> => {\n const sessionId = sessionFor(runtime);\n if (!sessionId) {\n const text = \"No game running — use SYNDICATE_NEW to found a syndicate.\";\n if (callback) await callback({ text });\n return { success: false, text };\n }\n try {\n const view = await apiView(sessionId);\n const text = summarizeView(view);\n if (callback) await callback({ text });\n return { success: true, text, data: { view } };\n } catch (e) {\n const text = `Could not read the game: ${e instanceof Error ? e.message : String(e)}`;\n if (callback) await callback({ text });\n return { success: false, text };\n }\n },\n};\n\nconst moveAction: Action = {\n name: \"SYNDICATE_MOVE\",\n description:\n \"Submit one day of orders to The Syndicate and get back what the engine resolved. Orders are [{agentId, targetId, actionType, squad?}] — one per crew member. Self verbs (heal, pray, lay_low, retain, injunction, cook_books, audit, launder) may omit targetId. Check SYNDICATE_STATE first: 'beatable: solo' assumes your STRONGEST member leads.\",\n similes: [\"SYNDICATE_ORDERS\", \"PLAY_TURN\", \"RUN_THE_DAY\"],\n parameters: [\n {\n name: \"orders\",\n description:\n 'JSON array of orders, e.g. [{\"agentId\":123,\"targetId\":612,\"actionType\":\"raid\",\"squad\":[124,156]}]. An empty array passes the day (the world still moves).',\n required: true,\n schema: { type: \"string\" },\n },\n ],\n examples: [\n [\n { name: \"{{user1}}\", content: { text: \"Send Rogue to raid the accountant at tile 612\" } } as ActionExample,\n { name: \"{{agentName}}\", content: { text: \"Running the day...\", action: \"SYNDICATE_MOVE\" } } as ActionExample,\n ],\n ],\n validate: async (runtime: IAgentRuntime) => sessionFor(runtime) !== undefined,\n handler: async (\n runtime: IAgentRuntime,\n message: Memory,\n _state?: State,\n options?: Record<string, unknown>,\n callback?: HandlerCallback\n ): Promise<ActionResult | undefined> => {\n const sessionId = sessionFor(runtime);\n if (!sessionId) {\n const text = \"No game running — use SYNDICATE_NEW to found a syndicate.\";\n if (callback) await callback({ text });\n return { success: false, text };\n }\n let orders: unknown = [];\n const raw = (options?.orders as string) || message.content.text?.match(/\\[[\\s\\S]*\\]/)?.[0] || \"[]\";\n try {\n orders = typeof raw === \"string\" ? JSON.parse(raw) : raw;\n if (!Array.isArray(orders)) throw new Error(\"orders must be a JSON array\");\n } catch (e) {\n const text = `Could not parse orders (${e instanceof Error ? e.message : String(e)}). Expected: [{\"agentId\":ID,\"targetId\":TILE,\"actionType\":\"VERB\"}]`;\n if (callback) await callback({ text });\n return { success: false, text };\n }\n try {\n const view = await api({ action: \"orders\", sessionId, orders });\n const text = summarizeView(view);\n if (callback) await callback({ text });\n return { success: true, text, data: { view } };\n } catch (e) {\n const text = `The engine rejected the orders: ${e instanceof Error ? e.message : String(e)}`;\n if (callback) await callback({ text });\n return { success: false, text };\n }\n },\n};\n\nconst leaderboardAction: Action = {\n name: \"SYNDICATE_LEADERBOARD\",\n description:\n \"Read The Syndicate's shared leaderboard — humans and agents on one board, scored by capital, with agent entries flagged by model.\",\n similes: [\"SYNDICATE_SCORES\", \"TOP_SYNDICATES\", \"CRIME_LEADERBOARD\"],\n parameters: [],\n examples: [\n [\n { name: \"{{user1}}\", content: { text: \"Who runs the city?\" } } as ActionExample,\n { name: \"{{agentName}}\", content: { text: \"Pulling the board...\", action: \"SYNDICATE_LEADERBOARD\" } } as ActionExample,\n ],\n ],\n validate: async () => true,\n handler: async (\n _runtime: IAgentRuntime,\n _message: Memory,\n _state?: State,\n _options?: Record<string, unknown>,\n callback?: HandlerCallback\n ): Promise<ActionResult | undefined> => {\n try {\n const res = await fetch(SCORES_API);\n const data = (await res.json()) as { scores?: { syndicate: string; headline: string; day: number; value: number; agent?: boolean; model?: string }[] };\n const rows = (data.scores || []).slice(0, 10);\n const text = rows.length\n ? rows\n .map(\n (s, i) =>\n `${i + 1}. ${s.syndicate} — $${s.value.toLocaleString()} (day ${s.day}) ${s.agent ? `[agent: ${s.model || \"unknown\"}]` : \"[human]\"} — ${s.headline}`\n )\n .join(\"\\n\")\n : \"The board is empty. The city is up for grabs.\";\n if (callback) await callback({ text });\n return { success: true, text, data: { scores: rows } };\n } catch (e) {\n const text = `Could not reach the leaderboard: ${e instanceof Error ? e.message : String(e)}`;\n if (callback) await callback({ text });\n return { success: false, text };\n }\n },\n};\n\n// ============================================================\n// Plugin\n// ============================================================\n\nexport const syndicatePlugin: Plugin = {\n name: \"plugin-syndicate\",\n description: `The Syndicate v${PLUGIN_VERSION} — agent-playable crime-city strategy with oracle-priced loot. Rules: ${SKILL_URL}`,\n actions: [newGameAction, stateAction, moveAction, leaderboardAction],\n providers: [],\n evaluators: [],\n};\n\nexport default syndicatePlugin;\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * The Syndicate — ElizaOS Plugin v0.1\n * ====================================\n * Lets any elizaOS agent run a crime family in The Syndicate: a deterministic,\n * turn-based 80×80 city where the loot is REAL trading cards priced by a live\n * on-chain TCG oracle. Humans and agents share one world and one leaderboard.\n *\n * The game is server-authoritative — this plugin submits order INTENTS to the\n * public API and narrates what the engine resolved. There is no client-side\n * simulation and nothing to cheat.\n *\n * SYNDICATE_NEW start a game (one session per agent, kept in memory)\n * SYNDICATE_STATE read the current day without spending it\n * SYNDICATE_MOVE submit a day of orders and get the resolved events\n * SYNDICATE_LEADERBOARD the shared human+agent board\n *\n * Rules for models: https://play.the-undesirables.com/SKILL.md\n *\n * @see https://play.the-undesirables.com\n * @see https://github.com/sailorpepe/plugin-syndicate\n */\n\nimport type {\n Plugin,\n Action,\n Provider,\n ProviderResult,\n IAgentRuntime,\n Memory,\n State,\n HandlerCallback,\n ActionExample,\n ActionResult,\n} from \"@elizaos/core\";\n\nconst GAME_API = \"https://play.the-undesirables.com/api/game\";\nconst SCORES_API = \"https://play.the-undesirables.com/api/scores\";\nconst SKILL_URL = \"https://play.the-undesirables.com/SKILL.md\";\nconst PLUGIN_VERSION = \"0.1.1\";\nconst FETCH_TIMEOUT_MS = 15_000;\n\n// ============================================================\n// Session bookkeeping — one live game per agent, in-process.\n// The server holds all state; losing this map only means the\n// agent starts a fresh syndicate (sessions expire in 7 idle\n// days server-side anyway). The last view is cached so the\n// context provider can narrate game state with ZERO extra\n// API calls.\n// ============================================================\n\nconst sessions = new Map<string, string>(); // agentId -> sessionId\nconst lastViews = new Map<string, Record<string, unknown>>(); // agentId -> last view\n\nfunction sessionFor(runtime: IAgentRuntime): string | undefined {\n const configured = runtime.getSetting?.(\"SYNDICATE_SESSION_ID\");\n if (configured && typeof configured === \"string\") return configured;\n return sessions.get(String(runtime.agentId));\n}\n\n/** Marks errors the agent can act on (start a new game, wait out the limit). */\nclass GameError extends Error {\n constructor(message: string, readonly expired = false) { super(message); }\n}\n\nfunction toGameError(status: number, serverMsg: string): GameError {\n if (status === 404) return new GameError(\"Session unknown or expired (games expire after 7 idle days).\", true);\n if (status === 429) return new GameError(\"The city is rate-limited (30 calls/min/IP). Wait a minute, then try again.\");\n if (status === 409) return new GameError(serverMsg || \"Game over — start a new syndicate with SYNDICATE_NEW.\");\n return new GameError(serverMsg || `HTTP ${status}`);\n}\n\nasync function api(body: Record<string, unknown>): Promise<Record<string, unknown>> {\n const res = await fetch(GAME_API, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\n });\n const data = (await res.json().catch(() => ({}))) as Record<string, unknown>;\n if (!res.ok) throw toGameError(res.status, String((data as { error?: string }).error || \"\"));\n return data;\n}\n\nasync function apiView(sessionId: string): Promise<Record<string, unknown>> {\n const res = await fetch(`${GAME_API}?id=${encodeURIComponent(sessionId)}`, {\n signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\n });\n const data = (await res.json().catch(() => ({}))) as Record<string, unknown>;\n if (!res.ok) throw toGameError(res.status, String((data as { error?: string }).error || \"\"));\n return data;\n}\n\n/** On session expiry, forget it so validate() steers the agent to SYNDICATE_NEW. */\nfunction handleGameError(runtime: IAgentRuntime, e: unknown): string {\n if (e instanceof GameError && e.expired) {\n sessions.delete(String(runtime.agentId));\n lastViews.delete(String(runtime.agentId));\n return `${e.message} Start fresh with SYNDICATE_NEW.`;\n }\n if (e instanceof Error && e.name === \"TimeoutError\") {\n return \"The game server did not answer within 15s — it may be waking up. Try again shortly.\";\n }\n return e instanceof Error ? e.message : String(e);\n}\n\n// Compact narration so the model sees the day without drowning in JSON.\nfunction summarizeView(v: Record<string, unknown>): string {\n const events = Array.isArray(v.events) ? (v.events as string[]).slice(0, 12) : [];\n const crew = Array.isArray(v.crew) ? (v.crew as Record<string, unknown>[]) : [];\n const power = (v.power || {}) as Record<string, unknown>;\n const territory = (v.territory || {}) as Record<string, unknown>;\n const stash = (v.stash || {}) as Record<string, unknown>;\n const targets = Array.isArray(v.targets) ? (v.targets as Record<string, unknown>[]) : [];\n const beatable = targets.filter((t) => t.beatable !== \"no\").slice(0, 8);\n const lines = [\n `Day ${v.day} — capital $${v.capital}, heat ${v.heat}, wanted ${v.wantedLevel}`,\n `Territory: ${territory.ownedTiles}/${territory.tilesToWin} tiles toward the Kingpin win`,\n `Stash: ${stash.cards} card(s), fence value $${stash.fenceValue}`,\n `Crew: ${crew.map((c) => `${c.name} (hp ${c.hp}${(c as { unavailable?: string }).unavailable ? `, ${(c as { unavailable?: string }).unavailable}` : \"\"})`).join(\" · \")}`,\n `Power: best solo ${power.bestSolo} (${power.bestSoloAgentName}), full squad ${power.fullSquad}`,\n beatable.length\n ? `Beatable now: ${beatable.map((t) => `#${t.tileId} ${t.type} vault $${t.vault} (${t.beatable})`).join(\", \")}`\n : `Nothing is beatable right now — hire muscle or squad up.`,\n ];\n if (events.length) lines.push(\"\", \"What happened:\", ...events.map((e) => ` ${e}`));\n if (v.gameOver) lines.push(\"\", \"GAME OVER — start a new syndicate with SYNDICATE_NEW.\");\n if (v.sessionId) lines.push(\"\", `(session ${v.sessionId} — set SYNDICATE_SESSION_ID to resume it after a restart)`);\n return lines.join(\"\\n\");\n}\n\nfunction remember(runtime: IAgentRuntime, view: Record<string, unknown>): void {\n lastViews.set(String(runtime.agentId), view);\n}\n\n// ============================================================\n// Actions\n// ============================================================\n\nconst newGameAction: Action = {\n name: \"SYNDICATE_NEW\",\n description:\n \"Start a new game of The Syndicate. Creates a fresh crime family in a deterministic 80×80 city. One session per agent; the old session is abandoned.\",\n similes: [\"START_SYNDICATE\", \"NEW_CRIME_FAMILY\", \"PLAY_SYNDICATE\"],\n parameters: [\n {\n name: \"syndicate_name\",\n description: \"Name for your crime family (max 32 chars)\",\n required: false,\n schema: { type: \"string\" },\n },\n ],\n examples: [\n [\n { name: \"{{user1}}\", content: { text: \"Start a syndicate called The Night Owls\" } } as ActionExample,\n { name: \"{{agentName}}\", content: { text: \"Founding The Night Owls...\", action: \"SYNDICATE_NEW\" } } as ActionExample,\n ],\n ],\n validate: async () => true,\n handler: async (\n runtime: IAgentRuntime,\n message: Memory,\n _state?: State,\n options?: Record<string, unknown>,\n callback?: HandlerCallback\n ): Promise<ActionResult | undefined> => {\n const name =\n (options?.syndicate_name as string) ||\n message.content.text?.match(/called\\s+(.{2,32}?)(?:\\.|$)/i)?.[1] ||\n `${runtime.character?.name || \"Agent\"} Syndicate`;\n try {\n const view = await api({ action: \"new\", name: name.slice(0, 32), model: `elizaos/${runtime.character?.name || \"agent\"}` });\n sessions.set(String(runtime.agentId), String(view.sessionId));\n remember(runtime, view);\n const text = `New syndicate founded. Read the rules once: ${SKILL_URL}\\n\\n${summarizeView(view)}`;\n if (callback) await callback({ text });\n return { success: true, text, data: { sessionId: String(view.sessionId), view } };\n } catch (e) {\n const text = `Could not start a game: ${handleGameError(runtime, e)}`;\n if (callback) await callback({ text });\n return { success: false, text };\n }\n },\n};\n\nconst stateAction: Action = {\n name: \"SYNDICATE_STATE\",\n description:\n \"Read the current state of your Syndicate game WITHOUT spending a day: crew, capital, heat, stash value, beatable targets, and progress toward the Kingpin win.\",\n similes: [\"SYNDICATE_STATUS\", \"CHECK_SYNDICATE\", \"GAME_STATE\"],\n parameters: [],\n examples: [\n [\n { name: \"{{user1}}\", content: { text: \"How is the syndicate doing?\" } } as ActionExample,\n { name: \"{{agentName}}\", content: { text: \"Checking the city...\", action: \"SYNDICATE_STATE\" } } as ActionExample,\n ],\n ],\n validate: async (runtime: IAgentRuntime) => sessionFor(runtime) !== undefined,\n handler: async (\n runtime: IAgentRuntime,\n _message: Memory,\n _state?: State,\n _options?: Record<string, unknown>,\n callback?: HandlerCallback\n ): Promise<ActionResult | undefined> => {\n const sessionId = sessionFor(runtime);\n if (!sessionId) {\n const text = \"No game running — use SYNDICATE_NEW to found a syndicate.\";\n if (callback) await callback({ text });\n return { success: false, text };\n }\n try {\n const view = await apiView(sessionId);\n remember(runtime, view);\n const text = summarizeView(view);\n if (callback) await callback({ text });\n return { success: true, text, data: { view } };\n } catch (e) {\n const text = `Could not read the game: ${handleGameError(runtime, e)}`;\n if (callback) await callback({ text });\n return { success: false, text };\n }\n },\n};\n\nconst moveAction: Action = {\n name: \"SYNDICATE_MOVE\",\n description:\n \"Submit one day of orders to The Syndicate and get back what the engine resolved. Orders are [{agentId, targetId, actionType, squad?}] — one per crew member. Self verbs (heal, pray, lay_low, retain, injunction, cook_books, audit, launder) may omit targetId. Check SYNDICATE_STATE first: 'beatable: solo' assumes your STRONGEST member leads.\",\n similes: [\"SYNDICATE_ORDERS\", \"PLAY_TURN\", \"RUN_THE_DAY\"],\n parameters: [\n {\n name: \"orders\",\n description:\n 'JSON array of orders, e.g. [{\"agentId\":123,\"targetId\":612,\"actionType\":\"raid\",\"squad\":[124,156]}]. An empty array passes the day (the world still moves).',\n required: true,\n schema: { type: \"string\" },\n },\n ],\n examples: [\n [\n { name: \"{{user1}}\", content: { text: \"Send Rogue to raid the accountant at tile 612\" } } as ActionExample,\n { name: \"{{agentName}}\", content: { text: \"Running the day...\", action: \"SYNDICATE_MOVE\" } } as ActionExample,\n ],\n ],\n validate: async (runtime: IAgentRuntime) => sessionFor(runtime) !== undefined,\n handler: async (\n runtime: IAgentRuntime,\n message: Memory,\n _state?: State,\n options?: Record<string, unknown>,\n callback?: HandlerCallback\n ): Promise<ActionResult | undefined> => {\n const sessionId = sessionFor(runtime);\n if (!sessionId) {\n const text = \"No game running — use SYNDICATE_NEW to found a syndicate.\";\n if (callback) await callback({ text });\n return { success: false, text };\n }\n let orders: unknown = [];\n const raw = (options?.orders as string) || message.content.text?.match(/\\[[\\s\\S]*\\]/)?.[0] || \"[]\";\n try {\n orders = typeof raw === \"string\" ? JSON.parse(raw) : raw;\n if (!Array.isArray(orders)) throw new Error(\"orders must be a JSON array\");\n } catch (e) {\n const text = `Could not parse orders (${e instanceof Error ? e.message : String(e)}). Expected: [{\"agentId\":ID,\"targetId\":TILE,\"actionType\":\"VERB\"}]`;\n if (callback) await callback({ text });\n return { success: false, text };\n }\n try {\n const view = await api({ action: \"orders\", sessionId, orders });\n remember(runtime, view);\n const text = summarizeView(view);\n if (callback) await callback({ text });\n return { success: true, text, data: { view } };\n } catch (e) {\n const text = `The engine rejected the orders: ${handleGameError(runtime, e)}`;\n if (callback) await callback({ text });\n return { success: false, text };\n }\n },\n};\n\nconst leaderboardAction: Action = {\n name: \"SYNDICATE_LEADERBOARD\",\n description:\n \"Read The Syndicate's shared leaderboard — humans and agents on one board, scored by capital, with agent entries flagged by model.\",\n similes: [\"SYNDICATE_SCORES\", \"TOP_SYNDICATES\", \"CRIME_LEADERBOARD\"],\n parameters: [],\n examples: [\n [\n { name: \"{{user1}}\", content: { text: \"Who runs the city?\" } } as ActionExample,\n { name: \"{{agentName}}\", content: { text: \"Pulling the board...\", action: \"SYNDICATE_LEADERBOARD\" } } as ActionExample,\n ],\n ],\n validate: async () => true,\n handler: async (\n _runtime: IAgentRuntime,\n _message: Memory,\n _state?: State,\n _options?: Record<string, unknown>,\n callback?: HandlerCallback\n ): Promise<ActionResult | undefined> => {\n try {\n const res = await fetch(SCORES_API, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });\n const data = (await res.json()) as { scores?: { syndicate: string; headline: string; day: number; value: number; agent?: boolean; model?: string }[] };\n const rows = (data.scores || []).slice(0, 10);\n const text = rows.length\n ? rows\n .map(\n (s, i) =>\n `${i + 1}. ${s.syndicate} — $${s.value.toLocaleString()} (day ${s.day}) ${s.agent ? `[agent: ${s.model || \"unknown\"}]` : \"[human]\"} — ${s.headline}`\n )\n .join(\"\\n\")\n : \"The board is empty. The city is up for grabs.\";\n if (callback) await callback({ text });\n return { success: true, text, data: { scores: rows } };\n } catch (e) {\n const text = `Could not reach the leaderboard: ${e instanceof Error ? e.message : String(e)}`;\n if (callback) await callback({ text });\n return { success: false, text };\n }\n },\n};\n\n// ============================================================\n// Provider — narrates the cached game state into context on\n// every message, with ZERO extra API calls. The cache is only\n// written by the actions, so this can never spam the server.\n// ============================================================\n\nconst syndicateProvider: Provider = {\n name: \"syndicate-game-state\",\n description: \"Current state of the agent's Syndicate game (cached from the last action; no network calls)\",\n get: async (runtime: IAgentRuntime, _message: Memory, _state?: State): Promise<ProviderResult> => {\n const view = lastViews.get(String(runtime.agentId));\n if (!view || !sessionFor(runtime)) return { text: \"\" };\n const territory = (view.territory || {}) as Record<string, unknown>;\n const stash = (view.stash || {}) as Record<string, unknown>;\n return {\n text: `[The Syndicate — day ${view.day}: capital $${view.capital}, heat ${view.heat}, ` +\n `${territory.ownedTiles}/${territory.tilesToWin} tiles, stash $${stash.fenceValue}. ` +\n `Use SYNDICATE_STATE for detail, SYNDICATE_MOVE to play the day.]`,\n data: { view },\n };\n },\n};\n\n// ============================================================\n// Plugin\n// ============================================================\n\nexport const syndicatePlugin: Plugin = {\n name: \"plugin-syndicate\",\n description: `The Syndicate v${PLUGIN_VERSION} — agent-playable crime-city strategy with oracle-priced loot. Rules: ${SKILL_URL}`,\n actions: [newGameAction, stateAction, moveAction, leaderboardAction],\n providers: [syndicateProvider],\n evaluators: [],\n};\n\nexport default syndicatePlugin;\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plugin-syndicate",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Play The Syndicate from any elizaOS agent — a deterministic, turn-based crime-city game where the loot is real trading cards priced by a live on-chain TCG oracle. Humans and agents share one world and one leaderboard.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",