diff --git a/AGENTS.md b/AGENTS.md index c7b09e92850a..6cec74953147 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -131,6 +131,8 @@ Skills own workflows; root owns hard policy and routing. - Use `$openclaw-testing` for test/CI choice and `$crabbox` for remote/full/E2E proof. - At task start, if code changes, tests, builds, typechecks, lint fan-out, Docker, packaging, E2E, or live proof are likely, classify source trust and immediately pre-warm the safe Crabbox backend in a background command session. Trusted maintainer code defaults to Blacksmith Testbox; untrusted contributor/fork code uses secretless fork CI or sanitized direct AWS Crabbox under the rule above. Continue inspection/editing while it hydrates; sync the current checkout for every run, reuse the lease, then stop it before handoff. +- Warm Testbox from the task checkout; lease ownership is checkout-path scoped. +- PR review artifacts: keep template enum values; put evidence detail in summaries. - Crabbox request means real scenario proof: install/update/call/repro user path; not just copy tests and run them remotely. - Visual proof: use Crabbox, set up like a user, then screenshot-verify. No harness/bypass/shortcut unless explicitly asked. - Local agent work is limited to lightweight non-test checks such as `git diff --check`, targeted formatting, and cheap static probes. Tests and computationally intensive work default to the selected remote box. diff --git a/extensions/slack/src/__traces__/final-blocks-and-text.trace.jsonl b/extensions/slack/src/__traces__/final-blocks-and-text.trace.jsonl index 829701aee655..f742a6534d27 100644 --- a/extensions/slack/src/__traces__/final-blocks-and-text.trace.jsonl +++ b/extensions/slack/src/__traces__/final-blocks-and-text.trace.jsonl @@ -1,6 +1,6 @@ {"seq":1,"at":0,"dir":"in","kind":"reply-start"} {"seq":2,"at":0,"dir":"out","kind":"assistant.threads.setStatus","data":{"payload":{"channel_id":"C0TRACE","loading_messages":["Reading the thread...","Checking context...","Working through the request...","Putting it all together..."],"status":"is typing...","thread_ts":"ts#1"},"result":{"ok":true},"target":"C0TRACE/ts#1"}} {"seq":3,"at":0,"dir":"in","kind":"final","data":{"text":"Release 2026.1.0 is ready to ship."}} -{"seq":4,"at":0,"dir":"out","kind":"chat.postMessage","data":{"payload":{"blocks":[{"text":{"text":"Release 2026.1.0 is ready to ship.","type":"mrkdwn","verbatim":true},"type":"section"},{"block_id":"openclaw_reply_buttons_1","elements":[{"action_id":"openclaw:reply_button:1:1","text":{"emoji":true,"text":"Approve release","type":"plain_text"},"type":"button","value":"approve-release"},{"action_id":"openclaw:reply_link:1:2","text":{"emoji":true,"text":"Release notes","type":"plain_text"},"type":"button","url":"https://docs.openclaw.ai/release"}],"type":"actions"}],"channel":"C0TRACE","mrkdwn":false,"text":"Release 2026.1.0 is ready to ship.\n\nApprove release\nRelease notes","thread_ts":"ts#1","unfurl_links":false},"result":{"ts":"ts#2"},"target":"C0TRACE"}} +{"seq":4,"at":0,"dir":"out","kind":"chat.postMessage","data":{"payload":{"blocks":[{"text":{"text":"Release 2026.1.0 is ready to ship.","type":"mrkdwn","verbatim":true},"type":"section"},{"block_id":"openclaw_reply_buttons_1","elements":[{"action_id":"openclaw:callback_button:1:1","text":{"emoji":true,"text":"Approve release","type":"plain_text"},"type":"button","value":"approve-release"},{"action_id":"openclaw:reply_link:1:2","text":{"emoji":true,"text":"Release notes","type":"plain_text"},"type":"button","url":"https://docs.openclaw.ai/release"}],"type":"actions"}],"channel":"C0TRACE","mrkdwn":false,"text":"Release 2026.1.0 is ready to ship.\n\nApprove release\nRelease notes","thread_ts":"ts#1","unfurl_links":false},"result":{"ts":"ts#2"},"target":"C0TRACE"}} {"seq":5,"at":0,"dir":"in","kind":"idle"} {"seq":6,"at":0,"dir":"out","kind":"assistant.threads.setStatus","data":{"payload":{"channel_id":"C0TRACE","status":"","thread_ts":"ts#1"},"result":{"ok":true},"target":"C0TRACE/ts#1"}} diff --git a/extensions/slack/src/monitor/slash.test.ts b/extensions/slack/src/monitor/slash.test.ts index 2ce08fd67d4e..0cb7bcbe0d52 100644 --- a/extensions/slack/src/monitor/slash.test.ts +++ b/extensions/slack/src/monitor/slash.test.ts @@ -718,58 +718,16 @@ describe("Slack native command argument menus", () => { ); it("falls back to static menus when app.options() throws during registration", async () => { - const commands = new Map Promise>(); - const actions = new Map Promise>(); - const postEphemeral = vi.fn().mockResolvedValue({ ok: true }); + const testHarness = createArgMenusHarness(); const runtimeLog = vi.fn(); - const app = { - client: { chat: { postEphemeral } }, - command: (name: string, handler: (args: unknown) => Promise) => { - commands.set(name, handler); - }, - action: (id: string | RegExp, handler: (args: unknown) => Promise) => { - actions.set(id, handler); - }, - // Simulate Bolt throwing during options registration (e.g. receiver not initialized) - options: () => { - throw new Error("Cannot read properties of undefined (reading 'listeners')"); - }, + (testHarness.ctx as { runtime: { log: typeof runtimeLog } }).runtime = { log: runtimeLog }; + testHarness.app.options = () => { + throw new Error("Cannot read properties of undefined (reading 'listeners')"); }; - const ctx = { - cfg: { commands: { native: true, nativeSkills: false } }, - runtime: { log: runtimeLog }, - botToken: "bot-token", - botUserId: "bot", - teamId: "T1", - allowFrom: ["*"], - dmEnabled: true, - dmPolicy: "open", - groupDmEnabled: false, - groupDmChannels: [], - defaultRequireMention: true, - groupPolicy: "open", - useAccessGroups: false, - channelsConfig: undefined, - slashCommand: { - enabled: true, - name: "openclaw", - ephemeral: true, - sessionPrefix: "slack:slash", - }, - textLimit: 4000, - app, - isChannelAllowed: () => true, - resolveChannelName: async () => ({ name: "dm", type: "im" }), - resolveUserName: async () => ({ name: "Ada" }), - } as unknown; - const account = { - accountId: "acct", - config: { commands: { native: true, nativeSkills: false } }, - } as unknown; // Registration should not throw despite app.options() throwing - await registerCommands(ctx, account); - expect(commands.size).toBeGreaterThan(0); + await registerCommands(testHarness.ctx, testHarness.account); + expect(testHarness.commands.size).toBeGreaterThan(0); expect(runtimeLog).toHaveBeenCalledTimes(1); expect(runtimeLog).toHaveBeenCalledWith( expect.stringContaining( @@ -777,14 +735,14 @@ describe("Slack native command argument menus", () => { ), ); expect( - Array.from(actions.keys()).some( + Array.from(testHarness.actions.keys()).some( (key) => key instanceof RegExp && String(key) === String(/^openclaw_cmdarg/), ), ).toBe(true); // The /reportexternal command (140 choices) should fall back to static_select // instead of external_select since options registration failed - const handler = requireHandler(commands, "/reportexternal", "/reportexternal"); + const handler = requireHandler(testHarness.commands, "/reportexternal", "/reportexternal"); const respond = vi.fn().mockResolvedValue(undefined); const ack = vi.fn().mockResolvedValue(undefined); await handler({