test(slack): align prerelease contracts (#105214)

* test(slack): align prerelease contracts

* docs(agents): note review artifact enums

* style(slack): format prerelease test
This commit is contained in:
Peter Steinberger
2026-07-12 10:23:47 +01:00
committed by GitHub
parent b4a0fc5c57
commit b02d276bbc
3 changed files with 11 additions and 51 deletions
+2
View File
@@ -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.
@@ -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"}}
+8 -50
View File
@@ -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<string, (args: unknown) => Promise<void>>();
const actions = new Map<string | RegExp, (args: unknown) => Promise<void>>();
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<void>) => {
commands.set(name, handler);
},
action: (id: string | RegExp, handler: (args: unknown) => Promise<void>) => {
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({