mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-06 23:55:12 +00:00
fix(line): require wildcard for open dm policy
This commit is contained in:
@@ -68,6 +68,22 @@ Minimal config:
|
||||
}
|
||||
```
|
||||
|
||||
Public DM config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
line: {
|
||||
enabled: true,
|
||||
channelAccessToken: "LINE_CHANNEL_ACCESS_TOKEN",
|
||||
channelSecret: "LINE_CHANNEL_SECRET",
|
||||
dmPolicy: "open",
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Env vars (default account only):
|
||||
|
||||
- `LINE_CHANNEL_ACCESS_TOKEN`
|
||||
@@ -119,7 +135,7 @@ openclaw pairing approve line <CODE>
|
||||
Allowlists and policies:
|
||||
|
||||
- `channels.line.dmPolicy`: `pairing | allowlist | open | disabled`
|
||||
- `channels.line.allowFrom`: allowlisted LINE user IDs for DMs
|
||||
- `channels.line.allowFrom`: allowlisted LINE user IDs for DMs; `dmPolicy: "open"` requires `["*"]`
|
||||
- `channels.line.groupPolicy`: `allowlist | open | disabled`
|
||||
- `channels.line.groupAllowFrom`: allowlisted LINE user IDs for groups
|
||||
- Per-group overrides: `channels.line.groups.<groupId>.allowFrom`
|
||||
|
||||
51
extensions/line/src/config-schema.test.ts
Normal file
51
extensions/line/src/config-schema.test.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { LineConfigSchema } from "./config-schema.js";
|
||||
|
||||
describe("LineConfigSchema", () => {
|
||||
it('rejects dmPolicy="open" without wildcard allowFrom', () => {
|
||||
const result = LineConfigSchema.safeParse({
|
||||
channelAccessToken: "token",
|
||||
channelSecret: "secret",
|
||||
dmPolicy: "open",
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error.issues).toEqual([
|
||||
expect.objectContaining({
|
||||
path: ["allowFrom"],
|
||||
message: 'channels.line.dmPolicy="open" requires channels.line.allowFrom to include "*"',
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('accepts dmPolicy="open" with wildcard allowFrom', () => {
|
||||
const result = LineConfigSchema.safeParse({
|
||||
channelAccessToken: "token",
|
||||
channelSecret: "secret",
|
||||
dmPolicy: "open",
|
||||
allowFrom: ["*"],
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects account dmPolicy="open" without wildcard allowFrom', () => {
|
||||
const result = LineConfigSchema.safeParse({
|
||||
accounts: {
|
||||
work: {
|
||||
channelAccessToken: "token",
|
||||
channelSecret: "secret",
|
||||
dmPolicy: "open",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error.issues).toEqual([
|
||||
expect.objectContaining({
|
||||
path: ["accounts", "work", "allowFrom"],
|
||||
message: 'channels.line.dmPolicy="open" requires channels.line.allowFrom to include "*"',
|
||||
}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,8 @@
|
||||
import { buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";
|
||||
import {
|
||||
buildChannelConfigSchema,
|
||||
requireOpenAllowFrom,
|
||||
} from "openclaw/plugin-sdk/channel-config-schema";
|
||||
import { requireChannelOpenAllowFrom } from "openclaw/plugin-sdk/extension-shared";
|
||||
import { z } from "openclaw/plugin-sdk/zod";
|
||||
|
||||
const DmPolicySchema = z.enum(["open", "allowlist", "pairing", "disabled"]);
|
||||
@@ -15,7 +19,7 @@ const ThreadBindingsSchema = z
|
||||
})
|
||||
.strict();
|
||||
|
||||
const LineCommonConfigSchema = z.object({
|
||||
const LineCommonConfigSchemaBase = z.object({
|
||||
enabled: z.boolean().optional(),
|
||||
channelAccessToken: z.string().optional(),
|
||||
channelSecret: z.string().optional(),
|
||||
@@ -42,15 +46,35 @@ const LineGroupConfigSchema = z
|
||||
})
|
||||
.strict();
|
||||
|
||||
const LineAccountConfigSchema = LineCommonConfigSchema.extend({
|
||||
const LineAccountConfigSchema = LineCommonConfigSchemaBase.extend({
|
||||
groups: z.record(z.string(), LineGroupConfigSchema.optional()).optional(),
|
||||
}).strict();
|
||||
})
|
||||
.strict()
|
||||
.superRefine((value, ctx) => {
|
||||
requireChannelOpenAllowFrom({
|
||||
channel: "line",
|
||||
policy: value.dmPolicy,
|
||||
allowFrom: value.allowFrom,
|
||||
ctx,
|
||||
requireOpenAllowFrom,
|
||||
});
|
||||
});
|
||||
|
||||
export const LineConfigSchema = LineCommonConfigSchema.extend({
|
||||
export const LineConfigSchema = LineCommonConfigSchemaBase.extend({
|
||||
accounts: z.record(z.string(), LineAccountConfigSchema.optional()).optional(),
|
||||
defaultAccount: z.string().optional(),
|
||||
groups: z.record(z.string(), LineGroupConfigSchema.optional()).optional(),
|
||||
}).strict();
|
||||
})
|
||||
.strict()
|
||||
.superRefine((value, ctx) => {
|
||||
requireChannelOpenAllowFrom({
|
||||
channel: "line",
|
||||
policy: value.dmPolicy,
|
||||
allowFrom: value.allowFrom,
|
||||
ctx,
|
||||
requireOpenAllowFrom,
|
||||
});
|
||||
});
|
||||
|
||||
export const LineChannelConfigSchema = buildChannelConfigSchema(LineConfigSchema);
|
||||
|
||||
|
||||
@@ -2907,41 +2907,6 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
expect(statusReactionController.setTool).toHaveBeenCalledWith("exec");
|
||||
});
|
||||
|
||||
it("keeps non-command Telegram progress draft lines across post-tool assistant boundaries", async () => {
|
||||
const draftStream = createSequencedDraftStream(2001);
|
||||
createTelegramDraftStream.mockReturnValue(draftStream);
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
|
||||
async ({ dispatcherOptions, replyOptions }) => {
|
||||
await replyOptions?.onReplyStart?.();
|
||||
await replyOptions?.onAssistantMessageStart?.();
|
||||
await replyOptions?.onItemEvent?.({ kind: "search", progressText: "docs lookup" });
|
||||
await replyOptions?.onItemEvent?.({ progressText: "tests passed" });
|
||||
await replyOptions?.onAssistantMessageStart?.();
|
||||
await dispatcherOptions.deliver({ text: "Final after tool" }, { kind: "final" });
|
||||
return { queuedFinal: true };
|
||||
},
|
||||
);
|
||||
|
||||
await dispatchWithContext({
|
||||
context: createContext(),
|
||||
streamMode: "progress",
|
||||
telegramCfg: { streaming: { mode: "progress", progress: { label: "Shelling" } } },
|
||||
});
|
||||
|
||||
expect(draftStream.update).toHaveBeenCalledWith(
|
||||
expect.stringMatching(/^Shelling\n`🔎 Web Search: docs lookup`\n• `tests passed`$/),
|
||||
);
|
||||
expect(draftStream.forceNewMessage).not.toHaveBeenCalled();
|
||||
expect(draftStream.materialize).not.toHaveBeenCalled();
|
||||
expect(draftStream.clear).toHaveBeenCalledTimes(1);
|
||||
expect(deliverReplies).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
replies: [expect.objectContaining({ text: "Final after tool" })],
|
||||
}),
|
||||
);
|
||||
expect(editMessageTelegram).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps DM reasoning block updates in preview flow without sending duplicates", async () => {
|
||||
const answerDraftStream = createDraftStream(999);
|
||||
let previewRevision = 0;
|
||||
|
||||
Reference in New Issue
Block a user