feat(discord): add autoArchiveDuration config option (#35065)

* feat(discord): add autoArchiveDuration config option

Add config option to control auto-archive duration for auto-created threads:

- autoArchiveDuration: 60 (default), 1440, 4320, or 10080
  - Sets archive duration in minutes (1hr/1day/3days/1week)
  - Accepts both string and numeric values
  - Discord's default was 60 minutes (hardcoded)

Example config:
```yaml
channels:
  discord:
    guilds:
      GUILD_ID:
        channels:
          CHANNEL_ID:
            autoThread: true
            autoArchiveDuration: 10080  # 1 week
```

* feat(discord): add autoArchiveDuration changelog entry (#35065) (thanks @davidguttman)

---------

Co-authored-by: Onur <onur@textcortex.com>
This commit is contained in:
David Guttman
2026-03-10 15:13:24 -07:00
committed by GitHub
parent a76e810193
commit b517dc089a
5 changed files with 93 additions and 2 deletions

View File

@@ -12,6 +12,7 @@ Docs: https://docs.openclaw.ai
- Exec/child commands: mark child command environments with `OPENCLAW_CLI` so subprocesses can detect when they were launched from the OpenClaw CLI. (#41411) Thanks @vincentkoc.
- iOS/Home canvas: add a bundled welcome screen with a live agent overview that refreshes on connect, reconnect, and foreground return, and move the compact connection pill off the top-left canvas overlay. (#42456) Thanks @ngutman.
- iOS/Home canvas: replace floating controls with a docked toolbar, make the bundled home scaffold adapt to smaller phones, and open chat in the resolved main session instead of a synthetic `ios` session. (#42456) Thanks @ngutman.
- Discord/auto threads: add `autoArchiveDuration` channel config for auto-created threads so Discord thread archiving can stay at 1 hour, 1 day, 3 days, or 1 week instead of always using the 1-hour default. (#35065) Thanks @davidguttman.
### Breaking

View File

@@ -384,6 +384,16 @@ export const DiscordGuildChannelSchema = z
systemPrompt: z.string().optional(),
includeThreadStarter: z.boolean().optional(),
autoThread: z.boolean().optional(),
/** Archive duration for auto-created threads in minutes. Discord supports 60, 1440 (1 day), 4320 (3 days), 10080 (1 week). Default: 60. */
autoArchiveDuration: z
.union([
z.enum(["60", "1440", "4320", "10080"]),
z.literal(60),
z.literal(1440),
z.literal(4320),
z.literal(10080),
])
.optional(),
})
.strict();

View File

@@ -40,6 +40,7 @@ export type DiscordGuildEntryResolved = {
systemPrompt?: string;
includeThreadStarter?: boolean;
autoThread?: boolean;
autoArchiveDuration?: "60" | "1440" | "4320" | "10080" | 60 | 1440 | 4320 | 10080;
}
>;
};
@@ -55,6 +56,7 @@ export type DiscordChannelConfigResolved = {
systemPrompt?: string;
includeThreadStarter?: boolean;
autoThread?: boolean;
autoArchiveDuration?: "60" | "1440" | "4320" | "10080" | 60 | 1440 | 4320 | 10080;
matchKey?: string;
matchSource?: ChannelMatchSource;
};
@@ -401,6 +403,7 @@ function resolveDiscordChannelConfigEntry(
systemPrompt: entry.systemPrompt,
includeThreadStarter: entry.includeThreadStarter,
autoThread: entry.autoThread,
autoArchiveDuration: entry.autoArchiveDuration,
};
return resolved;
}

View File

@@ -1,5 +1,5 @@
import { ChannelType } from "@buape/carbon";
import { describe, it, expect, vi } from "vitest";
import { describe, it, expect, vi, beforeEach } from "vitest";
import { maybeCreateDiscordAutoThread } from "./threading.js";
describe("maybeCreateDiscordAutoThread", () => {
@@ -89,3 +89,74 @@ describe("maybeCreateDiscordAutoThread", () => {
expect(postMock).toHaveBeenCalled();
});
});
describe("maybeCreateDiscordAutoThread autoArchiveDuration", () => {
const postMock = vi.fn();
const getMock = vi.fn();
const mockClient = {
rest: { post: postMock, get: getMock },
} as unknown as Parameters<typeof maybeCreateDiscordAutoThread>[0]["client"];
const mockMessage = {
id: "msg1",
timestamp: "123",
} as unknown as Parameters<typeof maybeCreateDiscordAutoThread>[0]["message"];
beforeEach(() => {
postMock.mockReset();
getMock.mockReset();
});
it("uses configured autoArchiveDuration", async () => {
postMock.mockResolvedValueOnce({ id: "thread1" });
await maybeCreateDiscordAutoThread({
client: mockClient,
message: mockMessage,
messageChannelId: "text1",
isGuildMessage: true,
channelConfig: { allowed: true, autoThread: true, autoArchiveDuration: "10080" },
channelType: ChannelType.GuildText,
baseText: "test",
combinedBody: "test",
});
expect(postMock).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({ body: expect.objectContaining({ auto_archive_duration: 10080 }) }),
);
});
it("accepts numeric autoArchiveDuration", async () => {
postMock.mockResolvedValueOnce({ id: "thread1" });
await maybeCreateDiscordAutoThread({
client: mockClient,
message: mockMessage,
messageChannelId: "text1",
isGuildMessage: true,
channelConfig: { allowed: true, autoThread: true, autoArchiveDuration: 4320 },
channelType: ChannelType.GuildText,
baseText: "test",
combinedBody: "test",
});
expect(postMock).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({ body: expect.objectContaining({ auto_archive_duration: 4320 }) }),
);
});
it("defaults to 60 when autoArchiveDuration not set", async () => {
postMock.mockResolvedValueOnce({ id: "thread1" });
await maybeCreateDiscordAutoThread({
client: mockClient,
message: mockMessage,
messageChannelId: "text1",
isGuildMessage: true,
channelConfig: { allowed: true, autoThread: true },
channelType: ChannelType.GuildText,
baseText: "test",
combinedBody: "test",
});
expect(postMock).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({ body: expect.objectContaining({ auto_archive_duration: 60 }) }),
);
});
});

View File

@@ -397,12 +397,18 @@ export async function maybeCreateDiscordAutoThread(params: {
params.baseText || params.combinedBody || "Thread",
params.message.id,
);
// Parse archive duration from config, default to 60 minutes
const archiveDuration = params.channelConfig?.autoArchiveDuration
? Number(params.channelConfig.autoArchiveDuration)
: 60;
const created = (await params.client.rest.post(
`${Routes.channelMessage(messageChannelId, params.message.id)}/threads`,
{
body: {
name: threadName,
auto_archive_duration: 60,
auto_archive_duration: archiveDuration,
},
},
)) as { id?: string };