Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Slack broker API usage for this session:
- Register a broker-managed background job with: {{register_job_command}}
- Inspect the current session's co-author status with: {{coauthor_status_command}}
- Configure the current session's co-authors/mappings with: {{coauthor_configure_command}}
- The co-author configure endpoint accepts current-session contributors by Slack user id, @mention, display name, real name, username, or email, plus optional GitHub author mappings.
- The co-author configure endpoint accepts current-session contributors by Slack user id, @mention, display name, real name, username, or email; direct `slack_user_id` mapping entries may target any Slack user id or @mention.
- Prefer absolute file_path values when uploading local artifacts.
- Registered background jobs receive environment variables including BROKER_JOB_ID, BROKER_JOB_TOKEN, BROKER_API_BASE, BROKER_JOB_HELPER, SLACK_CHANNEL_ID, SLACK_THREAD_TS, SESSION_KEY, SESSION_WORKSPACE, and REPOS_ROOT.
- Inside a background job script, prefer `node "$BROKER_JOB_HELPER" ...` for heartbeat/event/complete/fail/cancel callbacks instead of hand-writing nested curl JSON payloads.
Expand Down
4 changes: 4 additions & 0 deletions src/services/codex/slack-thread-base-instructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ export async function buildSlackThreadBaseInstructions(
{
slack_user: "Alice Example",
github_author: "Alice Example <alice@example.com>"
},
{
slack_user_id: "<@U12345678>",
github_author: "Bot Reviewer <bot@example.com>"
}
]
});
Expand Down
24 changes: 19 additions & 5 deletions src/services/slack/slack-coauthor-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -668,12 +668,8 @@ export class SlackCoauthorService {
readonly githubAuthor: string;
}> {
return (mappings ?? []).map((entry) => {
const directUserId = entry.slackUserId?.trim();
const directUserId = normalizeDirectSlackUserId(entry.slackUserId);
if (directUserId) {
if (!status.candidates.some((candidate) => candidate.userId === directUserId)) {
throw new Error(`Unknown co-author candidate: ${entry.slackUserId}`);
}

return {
slackUserId: directUserId,
githubAuthor: entry.githubAuthor
Expand Down Expand Up @@ -858,3 +854,21 @@ function normalizeUserReference(value: string | undefined): string | undefined {
const normalized = value?.trim().toLowerCase();
return normalized || undefined;
}

function normalizeDirectSlackUserId(value: string | undefined): string | undefined {
const trimmed = value?.trim();
if (!trimmed) {
return undefined;
}

const mention = trimmed.match(/^<@([UW][A-Z0-9]+)(?:\|[^>]+)?>$/i);
if (mention?.[1]) {
return mention[1];
}

if (/^[UW][A-Z0-9]+$/i.test(trimmed)) {
return trimmed;
}

return undefined;
}
48 changes: 48 additions & 0 deletions test/slack-coauthor-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -454,4 +454,52 @@ describe("SlackCoauthorService", () => {
expect(mappings.getMapping("U1")).toBeUndefined();
expect(mappings.getMapping("U2")).toBeUndefined();
});

it("allows direct Slack user ids or mentions in configure-session mappings outside the co-author candidates", async () => {
const stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "slack-coauthor-state-"));
const sessionsRoot = await fs.mkdtemp(path.join(os.tmpdir(), "slack-coauthor-sessions-"));
tempDirs.push(stateDir, sessionsRoot);

const sessions = new SessionManager({
stateStore: new StateStore(stateDir, sessionsRoot),
sessionsRoot
});
await sessions.load();
const session = await sessions.ensureSession("C777", "222.333");
const mappings = new GitHubAuthorMappingService({ stateDir });
await mappings.load();

const service = new SlackCoauthorService({
sessions,
mappings,
slackApi: {
getUserIdentity: vi.fn(async (userId: string) => ({
userId,
mention: `<@${userId}>`,
realName: userId === "U3720" ? "codex-3720" : userId
})),
postEphemeral: vi.fn(),
openView: vi.fn()
} as never
});

await service.configureSessionCoauthors({
cwd: session.workspacePath,
mappings: [
{
slackUserId: "<@U3720>",
githubAuthor: "3720 Bot <bot@example.com>"
}
]
});

expect(mappings.getMapping("U3720")).toMatchObject({
slackUserId: "U3720",
githubAuthor: "3720 Bot <bot@example.com>",
source: "manual",
slackIdentity: {
realName: "codex-3720"
}
});
});
});
Loading