import type { CreateGroupOpts, CreateGroupResult } from './group-creator.js'; import type { IssueClientResult, PlatformIssue } from '../platform/issue-client.js'; import { type IssueBinding, type IssueStatus } from './issue-board-store.js'; /** * 群名里带的领取标记。对账时靠它把「平台上有 claim、本地没有 binding」的孤儿群认回来 * ——本地意图是一条腿,这个标记是另一条腿,少哪条都会留下认不出的群。 * * 只取前 8 位十六进制(32 bit):群名是给人看的,不该被一串 32 字符的随机数占满; * 同时在手可数的候选群里,32 bit 足以唯一定位。 */ export declare function claimMarker(claimId: string): string; export declare function matchesClaimMarker(groupName: string, claimId: string): boolean; /** * 拼领取群名:`<标题> #`,**标题被截、标记一定完整**。 * * 不截的话飞书自己会截,而它从尾部截——先吃掉的正是尾部那个 `#claimId` 标记,也就是双通道 * 反查的一条腿。那条腿断了不会报错:对账时只是"这个群不像是任何一次领取的",然后被当成 * 无主群跳过。宁可标题短一点。 * * 标记放尾部而不是挪到前面:群列表里先看到的应该是任务在说什么,一串十六进制不配占开头。 * 长度自己控住了,位置就不再是风险。 */ export declare function claimGroupName(title: string, claimId: string): string; /** * 平台上这条 issue 的详情深链。 * * 路由形状来自平台前端:tab 在 hash(`#issues`)、详情在 query(`?issue=`),见 * platform 仓 `frontend/src/App.tsx` 的 Tabs 与 `IssueBoard.tsx` 的 `openDetail`。 * 拼不出来(平台地址为空/非法)就返回 undefined,卡片少一个按钮而已,不该因此报错。 */ export declare function issueDetailUrl(platformBaseUrl: string, issueId: string): string | undefined; export interface ClaimFlowDeps { dataDir: string; platformBaseUrl: string; claim: (issueId: string, args: { claimId: string; agent?: string; repoLabel?: string; expectedStateRev: number; }) => Promise>; bind: (issueId: string, args: { claimId: string; localTaskRef: string; expectedStateRev: number; localTaskLabel?: string; localTaskUrl?: string; }) => Promise>; createGroup: (opts: CreateGroupOpts) => Promise; /** * 往新群里发开工播报([[issue-card]] 的 `buildIssueKickoffCard`)。 * * **best-effort**:失败只记日志,绝不让领取失败——它是给人看的,不是协议的一环。 * 不传就不播报(单测里默认不传)。 */ announce?: (chatId: string, card: string) => Promise; /** 播报失败时的旁路(生产接 logger;不注入就静默)。 */ onAnnounceError?: (reason: string) => void; /** in_progress 回写失败时的旁路(同上)。失败不推翻领取,但必须留下痕迹。 */ onStatusError?: (reason: string) => void; /** 发 kickoff(@ 目标 bot)把会话激活。返回 messageId。 */ activate: (chatId: string, botLarkAppId: string, prompt: string) => Promise; /** * 激活成功后把平台状态推到 `in_progress`。 * * **必填,不是可选**:平台的 activation lease 只扫 `status=claimed`(5 分钟),不回写 * in_progress 的话 sweeper 会把任务打成 `needs_attention(claim_activate_timeout)`,而 * 那个状态**回不去**——平台只放行 `task_blocked` 恢复成 in_progress,超时的只能 * open/reopened(都清 claim),群里的活就废了。做成可选依赖的话,漏传不会有任何报错, * 引信就这么静默装回去了;宁可编译期逼调用方给。 * * 与 fetchIssue 一起构成 [[issue-status-writer]] 的 `StatusWriterDeps`,回写走那边的 * `projectStatus`——sourceSeq 分配、串行、409 对账、退避只实现一次。 */ writeStatus: (issueId: string, args: { claimId: string; claimEpoch: number; sourceSeq: number; status: IssueStatus; expectedStateRev: number; }) => Promise>; /** 撞 409 时判断「平台还认不认这个 claim」。见 issue-status-writer。 */ fetchIssue: (teamId: string, issueId: string) => Promise; newClaimId?: () => string; now?: () => number; } export interface ClaimFlowArgs { /** `body` 只进开工播报(平台侧允许为空),所以这里放宽成可选。 */ issue: Pick & { body?: string; }; teamId: string; /** 由哪个 bot 承接这个 issue —— 决定 localTaskRef 的 appId 段与 kickoff 目标。 */ larkAppId: string; /** * 上送平台 `claim.agent` 的展示名。平台把这个值原样渲染在 issue 详情里,所以必须是**人能 * 认出来的名字**(`claude-loopy`),不是 `cli_xxx` ——早先直接传 larkAppId,看板上只能看到 * 一串 appId,谁在干这活完全看不出来。缺省回落到 larkAppId,保证总有值。 */ agentLabel?: string; /** 建群者(通常是 dashboard 所属的那个 bot)。 */ creatorLarkAppId: string; /** 一并拉进群的其它 bot。 */ peerLarkAppIds?: string[]; /** 把人按 union_id 拉进群(open_id 是 app-scoped 的,跨 bot 不通用)。 */ ownerUnionIds?: string[]; /** * agent 干活的目录。会作为 `bindWorkingDir` 绑到新群上(oncall 绑定),**必须传**—— * 不传的话人在卡片里选的仓库根本不会生效,会话会起在 bot 的默认目录里, * 等于让 agent 在错误的仓库动手,而且没有任何报错。 */ workingDir: string; kickoffPrompt: string; } export type ClaimFlowResult = { ok: true; binding: IssueBinding; chatId: string; kickoffMessageId: string; } /** 领取本身没成功,本地没有留下任何痕迹,重试是安全的。 */ | { ok: false; stage: 'claim'; reason: string; alreadyLocal?: IssueBinding; } /** 已 claim 但群没建成:意图在盘上,对账会接手(释放 claim 或补建)。 */ | { ok: false; stage: 'group'; reason: string; claimId: string; } /** 群建好了、binding 也落盘了,但 bind 被平台拒 → binding 置 void,群留给人处理。 */ | { ok: false; stage: 'bind'; reason: string; binding: IssueBinding; chatId: string; } /** 前面全成,只差 kickoff。binding 已 bound,重试 activate 即可,不必重建群。 */ | { ok: false; stage: 'activate'; reason: string; binding: IssueBinding; chatId: string; }; export declare function claimIssueIntoGroup(deps: ClaimFlowDeps, args: ClaimFlowArgs): Promise; //# sourceMappingURL=issue-claim-flow.d.ts.map