@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 +6 -2
- package/package.json +1 -1
- package/src/decide.ts +17 -6
- package/src/watcher.ts +31 -21
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 (
|
|
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: `${
|
|
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.
|
|
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
|
-
|
|
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":
|
|
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
|
|
66
|
-
`"press"/"cover" may add zone
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
110
|
-
//
|
|
111
|
-
// (host concern,
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
128
|
-
`${rendered}\n\n` +
|
|
129
|
-
|
|
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: (
|
|
156
|
+
deliver: (p: DeliverPrompt) => void | Promise<void>,
|
|
147
157
|
options: WatcherOptions = {},
|
|
148
158
|
): Promise<void> {
|
|
149
159
|
const { signal, logger } = options;
|