@a1hvdy/cc-openclaw 0.27.6 → 0.27.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.
@@ -103,6 +103,19 @@ export declare function handleStatus(ctx: CommandContext): CommandResult;
103
103
  export declare function handleCompact(ctx: CommandContext): CommandResult;
104
104
  export declare function handleCost(ctx: CommandContext): CommandResult;
105
105
  export declare function handleRewind(ctx: CommandContext): CommandResult;
106
+ /**
107
+ * v0.27.7 — /clear claims the command so it stops leaking to the LLM as plain
108
+ * text (the bug: before this, /clear wasn't in COMMAND_HANDLERS, so the inbound
109
+ * router forwarded it verbatim to Claude instead of handling it).
110
+ *
111
+ * It does NOT fake an in-place context reset. The running session's lifecycle +
112
+ * resume linkage live in the command-router (activeSessions, meta.claudeSessionId)
113
+ * and the engine — not this bookkeeping registry — so a pure mirror handler has
114
+ * no honest lever to wipe context (the same D-6 constraint behind /compact and
115
+ * /rewind being CLI-only). So /clear points to the paths that actually work: a
116
+ * fresh session via /new <slug>, or an in-place wipe via /clear in the CLI.
117
+ */
118
+ export declare function handleClear(ctx: CommandContext): CommandResult;
106
119
  /**
107
120
  * Open a compose session for the chat. M7 — drafts append until /send.
108
121
  * Returns a force_reply prompt so Telegram nudges the user into reply mode.
@@ -231,6 +231,30 @@ export function handleRewind(ctx) {
231
231
  ],
232
232
  };
233
233
  }
234
+ // ── /clear ───────────────────────────────────────────────────────────────
235
+ /**
236
+ * v0.27.7 — /clear claims the command so it stops leaking to the LLM as plain
237
+ * text (the bug: before this, /clear wasn't in COMMAND_HANDLERS, so the inbound
238
+ * router forwarded it verbatim to Claude instead of handling it).
239
+ *
240
+ * It does NOT fake an in-place context reset. The running session's lifecycle +
241
+ * resume linkage live in the command-router (activeSessions, meta.claudeSessionId)
242
+ * and the engine — not this bookkeeping registry — so a pure mirror handler has
243
+ * no honest lever to wipe context (the same D-6 constraint behind /compact and
244
+ * /rewind being CLI-only). So /clear points to the paths that actually work: a
245
+ * fresh session via /new <slug>, or an in-place wipe via /clear in the CLI.
246
+ */
247
+ export function handleClear(ctx) {
248
+ return {
249
+ actions: [
250
+ {
251
+ type: 'sendMessage',
252
+ chat_id: ctx.chatId,
253
+ text: "⚠️ /clear can't reset a running session from Telegram (no session-control primitive — same as /compact and /rewind). For a fresh start use <b>/new &lt;slug&gt;</b>; for an in-place context wipe run <b>/clear</b> in the Claude Code CLI.",
254
+ },
255
+ ],
256
+ };
257
+ }
234
258
  // ── /compose ─────────────────────────────────────────────────────────────
235
259
  /**
236
260
  * Open a compose session for the chat. M7 — drafts append until /send.
@@ -372,6 +396,7 @@ export const ALL_COMMANDS = [
372
396
  { command: 'cost', description: 'Show Max 20x usage + weekly burn' },
373
397
  { command: 'compact', description: 'Compact context (CLI-only — see note)' },
374
398
  { command: 'rewind', description: 'Rewind a session (CLI-only — see note)' },
399
+ { command: 'clear', description: 'Fresh start — /new <slug> (in-place reset is CLI-only)' },
375
400
  { command: 'compose', description: 'Start a multi-message draft — finish with /send' },
376
401
  { command: 'send', description: 'Send the composed draft to Claude' },
377
402
  { command: 'cancel', description: 'Cancel the active compose draft' },
@@ -398,6 +423,7 @@ export const COMMAND_HANDLERS = {
398
423
  compact: handleCompact,
399
424
  cost: handleCost,
400
425
  rewind: handleRewind,
426
+ clear: handleClear,
401
427
  compose: handleCompose,
402
428
  send: handleSend,
403
429
  cancel: handleCancel,
@@ -17,8 +17,10 @@
17
17
  * Risks monitored: R-7 (parallel maintenance burden — re-evaluate at soak start).
18
18
  */
19
19
  import { defaultRegisterGuard } from '../../lib/register-guard.js';
20
- import { initBotTokenFromConfig } from '../../lib/telegram-bot-api.js';
20
+ import { initBotTokenFromConfig, telegramApi } from '../../lib/telegram-bot-api.js';
21
21
  import { registerInboundHandler } from './inbound-handler.js';
22
+ import { syncMyCommands } from './sync-commands.js';
23
+ import { TOP_7_COMMANDS } from './commands.js';
22
24
  /**
23
25
  * Mirror channel register — idempotent via defaultRegisterGuard.
24
26
  *
@@ -82,6 +84,47 @@ export function register(api) {
82
84
  ? fullApi.enqueueNextTurnInjection.bind(api)
83
85
  : undefined,
84
86
  });
87
+ // v0.27.7 — wire the command-menu sync. It was DEAD CODE (defined in
88
+ // sync-commands.ts but never called from boot), so cc-openclaw's native
89
+ // commands never reached Telegram's command menu — only OpenClaw's
90
+ // nativeSkills auto-sync did, which is why /stop /new /status were
91
+ // invisible in the menu. We MERGE (getMyCommands → union) so the sync
92
+ // AUGMENTS the menu instead of clobbering the skill commands, and DELAY
93
+ // it so it lands after OpenClaw's own boot sync (tunable via
94
+ // CC_OPENCLAW_CMD_SYNC_DELAY_MS; default 8s). Gated out of the test
95
+ // runner — register() fires repeatedly in unit tests and must not make
96
+ // real Telegram network calls (eager-work-at-register safety, mirrors
97
+ // OPENCLAW_PLUGIN_TEST_MODE rationale).
98
+ const isTestRunner = process.env.VITEST !== undefined ||
99
+ process.env.NODE_ENV === 'test' ||
100
+ process.env.OPENCLAW_PLUGIN_TEST_MODE === '1';
101
+ if (!isTestRunner) {
102
+ const cmdSyncDelayMs = Number(process.env.CC_OPENCLAW_CMD_SYNC_DELAY_MS) || 8000;
103
+ const timer = setTimeout(() => {
104
+ void syncMyCommands({
105
+ setMyCommands: (payload) => telegramApi('setMyCommands', { commands: payload.commands }),
106
+ getMyCommands: async () => {
107
+ const res = await telegramApi('getMyCommands', {});
108
+ const result = res.result;
109
+ return { commands: result ?? [] };
110
+ },
111
+ nativeCommands: [
112
+ ...TOP_7_COMMANDS,
113
+ {
114
+ command: 'clear',
115
+ description: 'Fresh start — /new <slug> (in-place reset is CLI-only)',
116
+ },
117
+ ],
118
+ logger: {
119
+ info: (msg) => process.stderr.write(`${msg}\n`),
120
+ warn: (msg) => process.stderr.write(`${msg}\n`),
121
+ },
122
+ }).catch((err) => process.stderr.write(`[cc-openclaw/telegram-mirror] command-menu sync failed: ${err instanceof Error ? err.message : String(err)}\n`));
123
+ }, cmdSyncDelayMs);
124
+ // Don't keep the event loop alive solely for this timer.
125
+ if (typeof timer.unref === 'function')
126
+ timer.unref();
127
+ }
85
128
  process.stderr.write('[cc-openclaw/telegram-mirror] guard body completed (v0.25.2).\n');
86
129
  }
87
130
  catch (err) {
@@ -39,6 +39,32 @@ export interface SyncOptions {
39
39
  * Tests pass the G-3 mock; production wires a fetch-based caller.
40
40
  */
41
41
  setMyCommands: (payload: SetMyCommandsPayload) => Promise<unknown>;
42
+ /**
43
+ * v0.27.7 — optional getMyCommands dispatcher. When supplied, the CURRENTLY
44
+ * registered commands (e.g. OpenClaw's nativeSkills auto-synced /search,
45
+ * /plan, …) are fetched and MERGED with the native list so the sync augments
46
+ * the Telegram menu instead of replacing it. setMyCommands is a full-replace
47
+ * API, so without this merge a native-only sync would wipe the skill
48
+ * commands (and vice-versa — which is the bug that left native commands
49
+ * invisible: the dead sync never ran, and OpenClaw's skill-sync owned the
50
+ * menu alone). Omit it for the legacy native-only replace.
51
+ */
52
+ getMyCommands?: () => Promise<{
53
+ commands?: ReadonlyArray<{
54
+ command: string;
55
+ description: string;
56
+ }>;
57
+ }>;
58
+ /**
59
+ * v0.27.7 — native command set to advertise. Defaults to TOP_7_COMMANDS so
60
+ * the legacy callers (and the M5 boot-only test) keep the exact top-7 list.
61
+ * The boot wiring passes top-7 + /clear so the menu reflects the full native
62
+ * surface.
63
+ */
64
+ nativeCommands?: ReadonlyArray<{
65
+ command: string;
66
+ description: string;
67
+ }>;
42
68
  logger?: SyncLogger;
43
69
  }
44
70
  export interface SyncResult {
@@ -28,7 +28,24 @@ export async function syncMyCommands(opts) {
28
28
  return { alreadySynced: true, commandCount: 0 };
29
29
  }
30
30
  synced = true;
31
- const payload = { commands: TOP_7_COMMANDS.slice() };
31
+ const native = opts.nativeCommands ?? TOP_7_COMMANDS;
32
+ let commands = native.map((c) => ({ ...c }));
33
+ // v0.27.7 — merge in existing (skill) commands so the sync augments rather
34
+ // than replaces the menu. Native wins on a name collision. If the fetch
35
+ // fails, fall back to native-only (never wipe the menu on a transient error).
36
+ if (opts.getMyCommands) {
37
+ try {
38
+ const existing = await opts.getMyCommands();
39
+ const nativeNames = new Set(native.map((c) => c.command));
40
+ const preserved = (existing?.commands ?? []).filter((c) => !nativeNames.has(c.command));
41
+ commands = [...commands, ...preserved.map((c) => ({ ...c }))];
42
+ }
43
+ catch (err) {
44
+ const msg = err instanceof Error ? err.message : String(err);
45
+ opts.logger?.warn(`[cc-openclaw/telegram-mirror] getMyCommands failed — syncing native-only: ${msg}`);
46
+ }
47
+ }
48
+ const payload = { commands };
32
49
  try {
33
50
  await opts.setMyCommands(payload);
34
51
  opts.logger?.info(`[cc-openclaw/telegram-mirror] setMyCommands synced — ${payload.commands.length} commands.`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a1hvdy/cc-openclaw",
3
- "version": "0.27.6",
3
+ "version": "0.27.7",
4
4
  "description": "A1xAI's Anthropic CLI bridge plugin for OpenClaw",
5
5
  "author": "@a1cy",
6
6
  "license": "MIT",