@agentmessier/openclaw-agent-messier 0.3.10 → 0.3.11

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/index.ts CHANGED
@@ -79,7 +79,7 @@ export default function register(api: OpenClawPluginApi) {
79
79
  // Fire-and-forget: the watcher runs until aborted or the gateway stops.
80
80
  void startObserveWatcher(
81
81
  { serverUrl: cfg.serverUrl, matchId: seat.id!, agentId, mode: cfg.mode, strategyFile: cfg.strategyFile, actTool, label },
82
- async (msg) => {
82
+ async ({ system, user }) => {
83
83
  if (!sessionKey) {
84
84
  ctx.logger.warn(`[${label}] no sessionKey configured; cannot deliver move prompts.`);
85
85
  return;
@@ -101,8 +101,12 @@ export default function register(api: OpenClawPluginApi) {
101
101
  runtime: api.runtime,
102
102
  sessionKey: `${sessionKey}:${turn}`,
103
103
  idempotencyKey,
104
+ // The static rulebook (spec.instructions.system) rides the SYSTEM
105
+ // channel; the per-tick board is the user message. '' on the
106
+ // fallback path → no extra system prompt.
107
+ extraSystemPrompt: system || undefined,
104
108
  // Steer the agent to reply with ONLY the moves JSON (no tool call).
105
- message: `${msg}${STRICT_JSON_DIRECTIVE}`,
109
+ message: `${user}${STRICT_JSON_DIRECTIVE}`,
106
110
  // 45s ceiling, matching the watcher's per-delivery watchdog backstop:
107
111
  // a run that hasn't produced a decision by then is treated as stalled
108
112
  // (was 300s, which let one hung run silence the team for 5 min — m171).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentmessier/openclaw-agent-messier",
3
- "version": "0.3.10",
3
+ "version": "0.3.11",
4
4
  "description": "Agent Messier multi-venue client for OpenClaw \u2014 play games and work tasks on the AgentNet platform (soccer today; venues discovered from the marketplace registry)",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/decide.ts CHANGED
@@ -35,7 +35,9 @@ export type Move = {
35
35
  type: string;
36
36
  dir?: Vec2;
37
37
  power?: number;
38
- zone?: number;
38
+ /** Zone is now a NAME (e.g. "att-left"); an integer is accepted for back-compat
39
+ * and forwarded as-is (the server normalizes name→id). */
40
+ zone?: string | number;
39
41
  say?: string;
40
42
  };
41
43
 
@@ -60,10 +62,11 @@ export class DecideError extends Error {
60
62
  export const STRICT_JSON_DIRECTIVE =
61
63
  `\n\nDo NOT call any tool. Reply with ONLY a JSON object — no prose, no markdown ` +
62
64
  `fences — in exactly this shape, one entry per player you control:\n` +
63
- `{"moves":[{"playerId":"<id>","type":"<action>","dir":{"x":1,"y":0},"power":0.8,"zone":11,"say":"optional"}]}\n` +
65
+ `{"moves":[{"playerId":"<id>","type":"<action>","dir":{"x":1,"y":0},"power":0.8,"zone":"att-left","say":"optional"}]}\n` +
64
66
  `Valid action types: ${ACTION_TYPES.join(", ")}. ` +
65
- `"run"/"kick" need dir {x,y}; "kick" also needs power 0..1; "push" needs zone 1..12; ` +
66
- `"press"/"cover" may add zone 1..12. Omit fields an action does not need. Return the moves JSON now.`;
67
+ `"run"/"kick" need dir {x,y}; "kick" also needs power 0..1; "push" needs a zone NAME; ` +
68
+ `"press"/"cover" may add a zone NAME. Use the zone names from the board. ` +
69
+ `Omit fields an action does not need. Return the moves JSON now.`;
67
70
 
68
71
  // ── tolerant JSON extraction (ported from driver.ts) ─────────────────────────
69
72
 
@@ -167,7 +170,11 @@ export function parseMoves(text: string): Move[] {
167
170
  const dir = coerceDir(r.dir);
168
171
  if (dir) move.dir = dir;
169
172
  if (typeof r.power === "number" && Number.isFinite(r.power)) move.power = r.power;
170
- if (Number.isInteger(r.zone) && (r.zone as number) >= 1 && (r.zone as number) <= 12) move.zone = r.zone as number;
173
+ // Zones are now NAMES (e.g. "att-left"); accept a non-empty string and pass
174
+ // it through verbatim (the server is the authority — no client-side name
175
+ // validation). Still accept an integer for back-compat. Anything else drops.
176
+ if (typeof r.zone === "string" && r.zone.trim() !== "") move.zone = r.zone;
177
+ else if (Number.isInteger(r.zone)) move.zone = r.zone as number;
171
178
  if (typeof r.say === "string") move.say = r.say;
172
179
  moves.push(move);
173
180
  }
@@ -269,8 +276,11 @@ export type AutoplayTurnDeps = {
269
276
  runtime: PluginRuntime;
270
277
  sessionKey: string;
271
278
  idempotencyKey: string;
272
- /** The strict-JSON move prompt to deliver to the agent. */
279
+ /** The strict-JSON move prompt (the per-tick board) to deliver to the agent. */
273
280
  message: string;
281
+ /** The static game rulebook (spec.instructions.system), delivered on the run's
282
+ * SYSTEM channel rather than concatenated into the per-tick board. */
283
+ extraSystemPrompt?: string;
274
284
  /** Backstop ceiling for waitForRun (the watcher watchdog is the latch backstop). */
275
285
  timeoutMs: number;
276
286
  matchId: string;
@@ -301,6 +311,7 @@ export async function runAutoplayTurn(deps: AutoplayTurnDeps): Promise<AutoplayT
301
311
  const { runId } = await runtime.subagent.run({
302
312
  sessionKey: deps.sessionKey,
303
313
  message: deps.message,
314
+ ...(deps.extraSystemPrompt ? { extraSystemPrompt: deps.extraSystemPrompt } : {}),
304
315
  deliver: false,
305
316
  idempotencyKey: deps.idempotencyKey,
306
317
  });
package/src/watcher.ts CHANGED
@@ -100,36 +100,46 @@ export function parseSseBlock(block: string): { event?: string; data?: string }
100
100
  return event !== undefined ? { event, ...(data !== undefined ? { data } : {}) } : (data !== undefined ? { data } : {});
101
101
  }
102
102
 
103
- export function prompt(v: TeamView & { summary?: string }, mode: "easy" | "advanced" | "both", strategyFile?: string, spec?: GameSpec | null, actTool = "soccer_play"): string {
103
+ /** A delivered turn, split so the rulebook can ride the model's SYSTEM channel.
104
+ * - `system`: the STATIC game rules (spec.instructions.system) — passed as
105
+ * subagent.run's extraSystemPrompt. Empty on the fallback path (no spec).
106
+ * - `user`: the PER-TICK board — strategy + rendered situation + play guidance
107
+ * + the act/JSON directive. This is what changes every tick. */
108
+ export type DeliverPrompt = { system: string; user: string };
109
+
110
+ const actDirective = (actTool: string) =>
111
+ `Decide and act NOW: make ONE ${actTool} call with a move for every player you control — ` +
112
+ `every time you are prompted, even if the plan is unchanged. The order holds until you change it, ` +
113
+ `so if you go quiet your team freezes on stale orders. Never reply without acting.`;
114
+
115
+ export function prompt(v: TeamView & { summary?: string }, mode: "easy" | "advanced" | "both", strategyFile?: string, spec?: GameSpec | null, actTool = "soccer_play"): DeliverPrompt {
104
116
  const standing = strategyText(strategyFile);
105
117
  const stratBlock = standing ? `## Your manager's standing instructions\n${standing}\n\n` : "";
106
118
  const ins = spec?.instructions;
107
119
  if (ins && ins.system && ins.play && v.summary) {
108
120
  // Generic GSP path: the server authored the instructions AND rendered the
109
- // situation the plugin only concatenates. Tool-calling host, so the
110
- // direct-JSON `output` contract is replaced by a generic tool-act line
111
- // (host concern, not game knowledge).
112
- return (
113
- `${ins.system}\n\n` +
114
- stratBlock +
115
- `${v.summary}\n\n` +
116
- `${ins.play}\n\n` +
117
- `Decide and act NOW: make ONE ${actTool} call with a move for every player you control — ` +
118
- `every time you are prompted, even if the plan is unchanged. The order holds until you change it, ` +
119
- `so if you go quiet your team freezes on stale orders. Never reply without acting.`
120
- );
121
+ // situation. The rulebook (static) rides the SYSTEM channel; the board
122
+ // (per-tick) is the user message. Tool-calling host, so the direct-JSON
123
+ // `output` contract is replaced by a generic tool-act line (host concern,
124
+ // not game knowledge).
125
+ return {
126
+ system: ins.system,
127
+ user:
128
+ stratBlock +
129
+ `${v.summary}\n\n` +
130
+ `${ins.play}\n\n` +
131
+ actDirective(actTool),
132
+ };
121
133
  }
122
134
  // Fallback (pre-envelope server or handshake not yet arrived). describeTeam is
123
135
  // the soccer-specific renderer — only valid for a TeamView; for any other
124
136
  // venue with no server instructions, dump the raw view rather than crash.
137
+ // No server-authored rulebook here, so the system channel is empty.
125
138
  const rendered = Array.isArray(v.mine) ? describeTeam(v, mode) : JSON.stringify(v);
126
- return (
127
- stratBlock +
128
- `${rendered}\n\n` +
129
- `Decide and act NOW: make ONE ${actTool} call with a move for every player you control — ` +
130
- `every time you are prompted, even if the plan is unchanged. The order holds until you change it, ` +
131
- `so if you go quiet your team freezes on stale orders. Never reply without acting.`
132
- );
139
+ return {
140
+ system: "",
141
+ user: stratBlock + `${rendered}\n\n` + actDirective(actTool),
142
+ };
133
143
  }
134
144
 
135
145
  /** The observe path for a venue, from spec.routes ({matchId}/{did} substituted),
@@ -143,7 +153,7 @@ export function observeUrl(spec: GameSpec | null, matchId: string, did: string):
143
153
 
144
154
  export async function startObserveWatcher(
145
155
  cfg: WatcherCfg,
146
- deliver: (msg: string) => void | Promise<void>,
156
+ deliver: (p: DeliverPrompt) => void | Promise<void>,
147
157
  options: WatcherOptions = {},
148
158
  ): Promise<void> {
149
159
  const { signal, logger } = options;