botmux 3.9.1 → 3.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/dist/.runtime-build-id +1 -1
  2. package/dist/adapters/cli/traex.d.ts.map +1 -1
  3. package/dist/adapters/cli/traex.js +87 -87
  4. package/dist/adapters/cli/traex.js.map +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +94 -16
  7. package/dist/cli.js.map +1 -1
  8. package/dist/core/command-handler.d.ts.map +1 -1
  9. package/dist/core/command-handler.js +40 -1
  10. package/dist/core/command-handler.js.map +1 -1
  11. package/dist/core/passthrough-commands.d.ts.map +1 -1
  12. package/dist/core/passthrough-commands.js +1 -1
  13. package/dist/core/passthrough-commands.js.map +1 -1
  14. package/dist/daemon.d.ts +11 -1
  15. package/dist/daemon.d.ts.map +1 -1
  16. package/dist/daemon.js +32 -3
  17. package/dist/daemon.js.map +1 -1
  18. package/dist/i18n/en.d.ts.map +1 -1
  19. package/dist/i18n/en.js +1 -0
  20. package/dist/i18n/en.js.map +1 -1
  21. package/dist/i18n/zh.d.ts.map +1 -1
  22. package/dist/i18n/zh.js +1 -0
  23. package/dist/i18n/zh.js.map +1 -1
  24. package/dist/im/lark/card-handler.d.ts.map +1 -1
  25. package/dist/im/lark/card-handler.js +9 -0
  26. package/dist/im/lark/card-handler.js.map +1 -1
  27. package/dist/im/lark/issue-card.d.ts +223 -0
  28. package/dist/im/lark/issue-card.d.ts.map +1 -0
  29. package/dist/im/lark/issue-card.js +542 -0
  30. package/dist/im/lark/issue-card.js.map +1 -0
  31. package/dist/im/lark/issue-command-deps.d.ts +41 -0
  32. package/dist/im/lark/issue-command-deps.d.ts.map +1 -0
  33. package/dist/im/lark/issue-command-deps.js +195 -0
  34. package/dist/im/lark/issue-command-deps.js.map +1 -0
  35. package/dist/im/lark/issue-command.d.ts +137 -0
  36. package/dist/im/lark/issue-command.d.ts.map +1 -0
  37. package/dist/im/lark/issue-command.js +335 -0
  38. package/dist/im/lark/issue-command.js.map +1 -0
  39. package/dist/platform/issue-client.d.ts +201 -0
  40. package/dist/platform/issue-client.d.ts.map +1 -0
  41. package/dist/platform/issue-client.js +142 -0
  42. package/dist/platform/issue-client.js.map +1 -0
  43. package/dist/platform/platform-http.d.ts +4 -0
  44. package/dist/platform/platform-http.d.ts.map +1 -1
  45. package/dist/platform/platform-http.js +38 -2
  46. package/dist/platform/platform-http.js.map +1 -1
  47. package/dist/services/issue-board-store.d.ts +214 -0
  48. package/dist/services/issue-board-store.d.ts.map +1 -0
  49. package/dist/services/issue-board-store.js +472 -0
  50. package/dist/services/issue-board-store.js.map +1 -0
  51. package/dist/services/issue-claim-flow.d.ts +160 -0
  52. package/dist/services/issue-claim-flow.d.ts.map +1 -0
  53. package/dist/services/issue-claim-flow.js +222 -0
  54. package/dist/services/issue-claim-flow.js.map +1 -0
  55. package/dist/services/issue-outbox-pump.d.ts +25 -0
  56. package/dist/services/issue-outbox-pump.d.ts.map +1 -0
  57. package/dist/services/issue-outbox-pump.js +102 -0
  58. package/dist/services/issue-outbox-pump.js.map +1 -0
  59. package/dist/services/issue-release.d.ts +78 -0
  60. package/dist/services/issue-release.d.ts.map +1 -0
  61. package/dist/services/issue-release.js +89 -0
  62. package/dist/services/issue-release.js.map +1 -0
  63. package/dist/services/issue-report.d.ts +72 -0
  64. package/dist/services/issue-report.d.ts.map +1 -0
  65. package/dist/services/issue-report.js +84 -0
  66. package/dist/services/issue-report.js.map +1 -0
  67. package/dist/services/issue-status-view.d.ts +52 -0
  68. package/dist/services/issue-status-view.d.ts.map +1 -0
  69. package/dist/services/issue-status-view.js +42 -0
  70. package/dist/services/issue-status-view.js.map +1 -0
  71. package/dist/services/issue-status-writer.d.ts +98 -0
  72. package/dist/services/issue-status-writer.d.ts.map +1 -0
  73. package/dist/services/issue-status-writer.js +123 -0
  74. package/dist/services/issue-status-writer.js.map +1 -0
  75. package/dist/services/traex-paths.d.ts +14 -2
  76. package/dist/services/traex-paths.d.ts.map +1 -1
  77. package/dist/services/traex-paths.js +16 -2
  78. package/dist/services/traex-paths.js.map +1 -1
  79. package/dist/services/traex-transcript.d.ts +33 -0
  80. package/dist/services/traex-transcript.d.ts.map +1 -1
  81. package/dist/services/traex-transcript.js +135 -28
  82. package/dist/services/traex-transcript.js.map +1 -1
  83. package/dist/setup/lark-scopes.json +0 -2
  84. package/dist/worker.js +143 -17
  85. package/dist/worker.js.map +1 -1
  86. package/package.json +1 -1
@@ -0,0 +1,142 @@
1
+ /**
2
+ * 平台 Issue Board 的 machine-auth 客户端(打 `/v1/machine/*`,Bearer = machineToken)。
3
+ *
4
+ * 平台侧入口见 platform 仓 `src/server/machine-issues-http.ts` / DESIGN-issue-board.md §三之二。
5
+ * 只有五个端点:teams / issues(list) / claim / bind / status。创建、编辑、force-detach、
6
+ * 归档是**人的决策**,只在平台 Web 上做,这里没有也不该有。
7
+ *
8
+ * 鉴权材料全部来自 `~/.botmux/platform.json`([[platform/binding]]):未绑定平台时所有调用
9
+ * 直接返回 `{ ok:false, reason:'unbound' }`,调用方据此把整个 issue board 功能关掉。
10
+ *
11
+ * 错误分型是这层的重点——上层 outbox pump 要据此决定「退避重试」还是「就此打住」:
12
+ * - `network` :连不上/超时 → **必须重试**(daemon 离线期间状态照样变,回来要补上)
13
+ * - `conflict` :平台返回 409(state_rev_conflict / stale_epoch / claim_expired…)
14
+ * → 重新投影后再发,不是简单重试
15
+ * - `forbidden`:401/403(token 失效、解绑、machine_mismatch)→ **停手**,重试无意义
16
+ * - `server` :5xx → 退避重试(含平台 ByteDoc 降级的 503)
17
+ */
18
+ import { getJson, postJson } from './platform-http.js';
19
+ import { readPlatformBinding } from './binding.js';
20
+ /**
21
+ * 该失败是否值得退避后重投。
22
+ *
23
+ * 只有 `network` 与**真正的 5xx** 值得:4xx 全都是「再发一次结果不变」——401/403 是凭证/
24
+ * 归属问题,409 要先重新投影再发(不是盲重试),400/404 是请求本身错了。把它们混进可重试
25
+ * 一类,pump 会对着一个永远不会成功的请求指数退避到天荒地老,日志里还只看得到"在重试"。
26
+ */
27
+ export function isRetriable(f) {
28
+ return f.reason === 'network' || (f.reason === 'server' && f.status >= 500);
29
+ }
30
+ /**
31
+ * 这次失败是否**永远**不会成功——发件箱据此把行标 fatal,彻底停止重投。
32
+ *
33
+ * 注意它**不是** `!isRetriable`,那两个的补集差着两类,混用会把好行判死:
34
+ * - `conflict`(409):是竞争不是错误。上层先重新对账再重发,对账本身可能因为网络问题
35
+ * 没做成,此时判死等于把一条完全正常的回写扔了。留给下一轮。
36
+ * - `unbound`:本机当前没绑平台。绑回来之后这条行照样该发,判死就再也发不出去了。
37
+ *
38
+ * 剩下的两类才是真死:`forbidden`(401/403,凭证失效 / machine_mismatch / claim 被 revoke)
39
+ * 与 `client`(400 参数错 / 404 issue 被删或归档)。对它们退避重投,就是每 5 分钟朝一个
40
+ * 永远不会变的死端点打一发,同时 `/issue status` 上那句「N 条回写没发出去」永不清零。
41
+ */
42
+ export function isPermanentFailure(f) {
43
+ return f.reason === 'forbidden' || f.reason === 'client';
44
+ }
45
+ function resolveBinding(opts) {
46
+ if (opts.binding !== undefined)
47
+ return opts.binding;
48
+ const b = readPlatformBinding();
49
+ return b ? { platformUrl: b.platformUrl, machineToken: b.machineToken, machineId: b.machineId } : null;
50
+ }
51
+ function classify(status, json) {
52
+ const error = typeof json?.error === 'string' ? String(json.error) : `http_${status}`;
53
+ if (status === 401 || status === 403)
54
+ return { ok: false, reason: 'forbidden', status, error };
55
+ if (status === 409)
56
+ return { ok: false, reason: 'conflict', status, error };
57
+ // 其余 4xx 单列一类:既不是鉴权问题(别误导排查方向),也绝不该被重试。
58
+ if (status >= 400 && status < 500)
59
+ return { ok: false, reason: 'client', status, error };
60
+ return { ok: false, reason: 'server', status, error };
61
+ }
62
+ async function call(opts, method, path, body, pick = (j) => j) {
63
+ const binding = resolveBinding(opts);
64
+ if (!binding)
65
+ return { ok: false, reason: 'unbound' };
66
+ const http = opts.http ?? { get: getJson, post: postJson };
67
+ const url = `${binding.platformUrl.replace(/\/+$/, '')}${path}`;
68
+ const reqOpts = {
69
+ headers: { authorization: `Bearer ${binding.machineToken}` },
70
+ ...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),
71
+ };
72
+ let res;
73
+ try {
74
+ res = method === 'GET' ? await http.get(url, reqOpts) : await http.post(url, body ?? {}, reqOpts);
75
+ }
76
+ catch (e) {
77
+ return { ok: false, reason: 'network', error: String(e?.message ?? e) };
78
+ }
79
+ // 2xx 之外一律走分型;平台的成功响应恒为 200。
80
+ if (res.status < 200 || res.status >= 300)
81
+ return classify(res.status, res.json);
82
+ return { ok: true, value: pick(res.json) };
83
+ }
84
+ /** 本机 owner 所属的平台团队。注意:日常判断「本机在不在团队里」应读本地
85
+ * `platform-team-sync.json`([[platform-team-store]]),这个接口是不依赖 team-sync
86
+ * 是否已收敛的权威兜底。 */
87
+ export function fetchTeams(opts = {}) {
88
+ return call(opts, 'GET', '/v1/machine/teams', undefined, (j) => (Array.isArray(j?.teams) ? j.teams : []));
89
+ }
90
+ export function fetchIssues(teamId, opts = {}) {
91
+ return call(opts, 'GET', `/v1/machine/issues?teamId=${encodeURIComponent(teamId)}`, undefined, (j) => (j?.sections ?? { needsAttention: [], todo: [], inProgress: [], inReview: [], done: [] }));
92
+ }
93
+ /**
94
+ * 按 id 单查一条 issue。
95
+ *
96
+ * 平台**没有**「按 id 单查」的机器接口,只能从分段列表里捞——这是唯一的实现方式,所以收口
97
+ * 在这里,别让每个调用方各抄一遍(回写的 409 对账、领取确认、交付都要用它)。
98
+ *
99
+ * 返回 null 有两种含义且**无法区分**:拉列表失败,或 issue 不在活跃分段里(已归档)。
100
+ * 调用方一律按「无法判定」处理,不要把 null 当成「平台上没有/不是我的」——那会让
101
+ * 409 对账做出相反的决定。
102
+ */
103
+ export async function findIssueById(teamId, issueId, opts = {}) {
104
+ const list = await fetchIssues(teamId, opts);
105
+ if (!list.ok)
106
+ return null;
107
+ for (const section of Object.values(list.value)) {
108
+ const hit = section.find((i) => i._id === issueId);
109
+ if (hit)
110
+ return hit;
111
+ }
112
+ return null;
113
+ }
114
+ /**
115
+ * 领取。`claimId` 必须是**高熵随机**(`crypto.randomBytes(16).toString('hex')`),
116
+ * 绝不能由 issueId 派生——平台的幂等分支现在会连 machineId/unionId 一起校验,派生键在
117
+ * 多机场景下会直接撞 409 `already_claimed`(早失败,好过静默把别人的 claim 当成自己的)。
118
+ *
119
+ * `machineId` 不用传:平台以 token 为准(传了且不一致会 403 machine_mismatch)。
120
+ */
121
+ export function claimIssue(issueId, args, opts = {}) {
122
+ return call(opts, 'POST', `/v1/machine/issues/${encodeURIComponent(issueId)}/claim`, args);
123
+ }
124
+ /**
125
+ * 绑定本地任务。`localTaskRef` = `<anchorId>::<larkAppId>`(见 [[issue-board-store]] 的
126
+ * `buildLocalTaskRef`)。
127
+ *
128
+ * `localTaskLabel` / `localTaskUrl` 是**纯展示**的可选项:前者给人看(群名),后者是点进去
129
+ * 的深链。平台只接受 https 深链,其余静默丢弃且**不会**让 bind 失败——展示字段不该有能力
130
+ * 弄砸一次真实绑定。
131
+ */
132
+ export function bindIssue(issueId, args, opts = {}) {
133
+ return call(opts, 'POST', `/v1/machine/issues/${encodeURIComponent(issueId)}/bind`, args);
134
+ }
135
+ /**
136
+ * 状态回写。幂等只认 `sourceSeq`(每 binding 单调、串行发送),所以同一条 outbox 行重发
137
+ * 是安全的:平台见到 `sourceSeq <= lastSourceSeq` 直接回当前态、零副作用。
138
+ */
139
+ export function writeIssueStatus(issueId, args, opts = {}) {
140
+ return call(opts, 'POST', `/v1/machine/issues/${encodeURIComponent(issueId)}/status`, args);
141
+ }
142
+ //# sourceMappingURL=issue-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issue-client.js","sourceRoot":"","sources":["../../src/platform/issue-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AA+DnD;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,CAAqB;IAC/C,OAAO,CAAC,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,CAAC,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC;AAC9E,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB,CAAC,CAAqB;IACtD,OAAO,CAAC,CAAC,MAAM,KAAK,WAAW,IAAI,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC;AAC3D,CAAC;AAaD,SAAS,cAAc,CAAC,IAAwB;IAC9C,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC,OAAO,CAAC;IACpD,MAAM,CAAC,GAAG,mBAAmB,EAAE,CAAC;IAChC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,YAAY,EAAE,CAAC,CAAC,YAAY,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACzG,CAAC;AAED,SAAS,QAAQ,CAAC,MAAc,EAAE,IAAa;IAC7C,MAAM,KAAK,GAAG,OAAQ,IAA4B,EAAE,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAE,IAA0B,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,MAAM,EAAE,CAAC;IACtI,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAC/F,IAAI,MAAM,KAAK,GAAG;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAC5E,wCAAwC;IACxC,IAAI,MAAM,IAAI,GAAG,IAAI,MAAM,GAAG,GAAG;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IACzF,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AACxD,CAAC;AAED,KAAK,UAAU,IAAI,CACjB,IAAwB,EACxB,MAAsB,EACtB,IAAY,EACZ,IAAc,EACd,OAAyB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAM;IAEtC,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACrC,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IACtD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAC3D,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC;IAChE,MAAM,OAAO,GAAG;QACd,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,OAAO,CAAC,YAAY,EAAE,EAAE;QAC5D,GAAG,CAAC,IAAI,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACvE,CAAC;IACF,IAAI,GAAsC,CAAC;IAC3C,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC;IACpG,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,CAAE,CAAW,EAAE,OAAO,IAAI,CAAC,CAAC,EAAE,CAAC;IACrF,CAAC;IACD,6BAA6B;IAC7B,IAAI,GAAG,CAAC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG;QAAE,OAAO,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;IACjF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;AAC7C,CAAC;AAED;;kBAEkB;AAClB,MAAM,UAAU,UAAU,CACxB,OAA2B,EAAE;IAE7B,OAAO,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,mBAAmB,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC5G,CAAC;AAED,MAAM,UAAU,WAAW,CACzB,MAAc,EACd,OAA2B,EAAE;IAE7B,OAAO,IAAI,CACT,IAAI,EACJ,KAAK,EACL,6BAA6B,kBAAkB,CAAC,MAAM,CAAC,EAAE,EACzD,SAAS,EACT,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,IAAI,EAAE,cAAc,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAA0B,CAC1H,CAAC;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,MAAc,EACd,OAAe,EACf,OAA2B,EAAE;IAE7B,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC7C,IAAI,CAAC,IAAI,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC;IAC1B,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAChD,MAAM,GAAG,GAAI,OAA2B,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,OAAO,CAAC,CAAC;QACxE,IAAI,GAAG;YAAE,OAAO,GAAG,CAAC;IACtB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CACxB,OAAe,EACf,IAAuF,EACvF,OAA2B,EAAE;IAE7B,OAAO,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,sBAAsB,kBAAkB,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;AAC7F,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CACvB,OAAe,EACf,IAMC,EACD,OAA2B,EAAE;IAE7B,OAAO,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,sBAAsB,kBAAkB,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AAC5F,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAC9B,OAAe,EACf,IAOC,EACD,OAA2B,EAAE;IAE7B,OAAO,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,sBAAsB,kBAAkB,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;AAC9F,CAAC"}
@@ -6,6 +6,10 @@ export interface PostJsonOptions {
6
6
  /** 强制 IP 协议族:4 / 6;缺省跟随系统默认解析(含 net 层 happy-eyeballs)。 */
7
7
  family?: 4 | 6;
8
8
  timeoutMs?: number;
9
+ /** 附加请求头(如 `Authorization: Bearer <machineToken>`)。不能覆盖 content-* 两个。 */
10
+ headers?: Record<string, string>;
9
11
  }
10
12
  export declare function postJson(url: string, body: unknown, opts?: PostJsonOptions): Promise<PostJsonResult>;
13
+ /** 同 {@link postJson} 的连接口径(含 family 钉死),只是不带请求体的 GET。 */
14
+ export declare function getJson(url: string, opts?: PostJsonOptions): Promise<PostJsonResult>;
11
15
  //# sourceMappingURL=platform-http.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"platform-http.d.ts","sourceRoot":"","sources":["../../src/platform/platform-http.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,0DAA0D;IAC1D,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,GAAE,eAAoB,GAAG,OAAO,CAAC,cAAc,CAAC,CAmCxG"}
1
+ {"version":3,"file":"platform-http.d.ts","sourceRoot":"","sources":["../../src/platform/platform-http.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,0DAA0D;IAC1D,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,GAAE,eAAoB,GAAG,OAAO,CAAC,cAAc,CAAC,CAqCxG;AAED,0DAA0D;AAC1D,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,eAAoB,GAAG,OAAO,CAAC,cAAc,CAAC,CAkCxF"}
@@ -5,7 +5,7 @@
5
5
  import { request as httpsRequest } from 'node:https';
6
6
  import { request as httpRequest } from 'node:http';
7
7
  export function postJson(url, body, opts = {}) {
8
- const { family, timeoutMs = 10_000 } = opts;
8
+ const { family, timeoutMs = 10_000, headers } = opts;
9
9
  const u = new URL(url);
10
10
  const doRequest = u.protocol === 'https:' ? httpsRequest : httpRequest;
11
11
  const payload = Buffer.from(JSON.stringify(body), 'utf8');
@@ -17,7 +17,9 @@ export function postJson(url, body, opts = {}) {
17
17
  method: 'POST',
18
18
  family,
19
19
  timeout: timeoutMs,
20
- headers: { 'content-type': 'application/json', 'content-length': payload.length },
20
+ // content-* 放在扩展头之后:调用方传错 content-length 会让请求体被截断/挂死,
21
+ // 这两个必须由本函数说了算。
22
+ headers: { ...headers, 'content-type': 'application/json', 'content-length': payload.length },
21
23
  }, (res) => {
22
24
  const chunks = [];
23
25
  res.on('data', (c) => chunks.push(c));
@@ -38,4 +40,38 @@ export function postJson(url, body, opts = {}) {
38
40
  req.end(payload);
39
41
  });
40
42
  }
43
+ /** 同 {@link postJson} 的连接口径(含 family 钉死),只是不带请求体的 GET。 */
44
+ export function getJson(url, opts = {}) {
45
+ const { family, timeoutMs = 10_000, headers } = opts;
46
+ const u = new URL(url);
47
+ const doRequest = u.protocol === 'https:' ? httpsRequest : httpRequest;
48
+ return new Promise((resolve, reject) => {
49
+ const req = doRequest({
50
+ host: u.hostname,
51
+ port: u.port || (u.protocol === 'https:' ? 443 : 80),
52
+ path: `${u.pathname}${u.search}`,
53
+ method: 'GET',
54
+ family,
55
+ timeout: timeoutMs,
56
+ headers: { ...headers, accept: 'application/json' },
57
+ }, (res) => {
58
+ const chunks = [];
59
+ res.on('data', (c) => chunks.push(c));
60
+ res.on('end', () => {
61
+ let json = {};
62
+ try {
63
+ json = JSON.parse(Buffer.concat(chunks).toString('utf8'));
64
+ }
65
+ catch {
66
+ /* 非 JSON 响应按空对象处理,调用方看 status */
67
+ }
68
+ resolve({ status: res.statusCode || 0, json });
69
+ });
70
+ res.on('error', reject);
71
+ });
72
+ req.on('timeout', () => req.destroy(new Error(`连接超时(${timeoutMs}ms)`)));
73
+ req.on('error', reject);
74
+ req.end();
75
+ });
76
+ }
41
77
  //# sourceMappingURL=platform-http.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"platform-http.js","sourceRoot":"","sources":["../../src/platform/platform-http.ts"],"names":[],"mappings":"AAAA,mDAAmD;AACnD,wDAAwD;AACxD,2DAA2D;AAC3D,gDAAgD;AAChD,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,WAAW,CAAC;AAanD,MAAM,UAAU,QAAQ,CAAC,GAAW,EAAE,IAAa,EAAE,OAAwB,EAAE;IAC7E,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG,MAAM,EAAE,GAAG,IAAI,CAAC;IAC5C,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACvB,MAAM,SAAS,GAAG,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,WAAW,CAAC;IACvE,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;IAC1D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,GAAG,GAAG,SAAS,CACnB;YACE,IAAI,EAAE,CAAC,CAAC,QAAQ;YAChB,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YACpD,IAAI,EAAE,GAAG,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,MAAM,EAAE;YAChC,MAAM,EAAE,MAAM;YACd,MAAM;YACN,OAAO,EAAE,SAAS;YAClB,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,OAAO,CAAC,MAAM,EAAE;SAClF,EACD,CAAC,GAAG,EAAE,EAAE;YACN,MAAM,MAAM,GAAa,EAAE,CAAC;YAC5B,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YAC9C,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;gBACjB,IAAI,IAAI,GAAY,EAAE,CAAC;gBACvB,IAAI,CAAC;oBACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;gBAC5D,CAAC;gBAAC,MAAM,CAAC;oBACP,iCAAiC;gBACnC,CAAC;gBACD,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,UAAU,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;YACjD,CAAC,CAAC,CAAC;YACH,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC1B,CAAC,CACF,CAAC;QACF,GAAG,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,QAAQ,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC;QACxE,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACxB,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACnB,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"platform-http.js","sourceRoot":"","sources":["../../src/platform/platform-http.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,214 @@
1
+ /**
2
+ * localTaskRef 的**唯一**构造入口,且 `createBinding` 会自己调它——`CreateBindingInput`
3
+ * 里没有 localTaskRef 字段,调用方**没有**手拼的机会。
4
+ *
5
+ * 之所以做成内生而不是「约定调用方用这个函数」:拼反了顺序不会当场报错,要等平台 bind
6
+ * 记下的 localTaskRef 与崩溃恢复时的重确认对不上,那条 held 会话被判 claim_mismatch 作废
7
+ * 才暴露——离出错点太远。直接复用 botmux 自己的 `sessionKey`,与 activeSessions 键同源。
8
+ */
9
+ export declare function buildLocalTaskRef(anchorId: string, larkAppId: string): string;
10
+ /** 平台 IssueStatus 的线上取值(回写目标只用得到其中一部分,但解析要认全)。 */
11
+ export type IssueStatus = 'open' | 'claimed' | 'in_progress' | 'in_review' | 'done' | 'cancelling' | 'needs_attention' | 'reopened';
12
+ export type AttentionReason = 'cancel_delivery_timeout' | 'claim_activate_timeout' | 'task_blocked' | 'run_timeout';
13
+ /**
14
+ * 一条 issue ↔ 本机会话的绑定。
15
+ *
16
+ * `bindState` 对应 §六 的崩溃恢复分支,外加一个人为终态:
17
+ * - `pending` :claim 成功、binding 已落盘,但还没向平台 bind(群可能还没建出来)
18
+ * - `bound` :平台已接受 bind,本地会话可以 activate(发 kickoff @ bot)
19
+ * - `void` :补 bind 被平台拒(issue 被回收 / 别人领走 / 代次过期),这条作废
20
+ * - `released`:人主动释放([[issue-release]]),issue 已退回平台待领取池
21
+ * - `done` :人验收通过(`/issue done`),平台已终结这条 issue
22
+ *
23
+ * 三个终态都不再回写,但**不能合并**:`void` 是"平台不认这次领取",对账见到它要考虑群是不是
24
+ * 白建了;`released` 是"领取本来是好的,人不做了",群里有正经的工作记录,对账不该去动它;
25
+ * `done` 是"活干完并且验收了"。区分开也让日志能说清一个群到底是怎么结束的。
26
+ *
27
+ * 加终态时只改 `isActiveBindState` 一处——散落的 `!== 'void'` 式判断漏改一处,就会出现
28
+ * 「已结束的 issue 仍被当成本机持有」,而且全程不报错。
29
+ */
30
+ export interface IssueBinding {
31
+ /**
32
+ * **路由锚点**——主键,也是平台契约里的「本地任务」标识。
33
+ *
34
+ * 这里必须是 anchor 而**不是** botmux 的 session id:协议要求 bind(带 localTaskRef)
35
+ * 发生在 activate 之前,而 botmux 的会话是 kickoff 消息把 bot @ 起来之后才由 daemon
36
+ * 创建的——bind 那一刻根本还没有 session id。anchor 则在 kickoff 之前就存在:
37
+ * - 拉群模式 → `chatId`(chat-scope 会话的路由键,建群即得)
38
+ * - 话题模式 → `rootMessageId`(先发无 @ 的 seed 拿到,再发 threaded kickoff)
39
+ * 见 core/types.ts 的 `sessionAnchorId` / `sessionKey`。
40
+ */
41
+ anchorId: string;
42
+ /** 平台契约的 localTaskRef,取值就是 botmux 自己的 `sessionKey(anchorId, larkAppId)`
43
+ * = `<anchorId>::<larkAppId>`。复用它而不是另造一套:它本来就是本仓跨 bot 的规范会话
44
+ * 身份,且 `om_`(消息)与 `oc_`(群)两个地址空间不会碰撞。 */
45
+ localTaskRef: string;
46
+ larkAppId: string;
47
+ issueId: string;
48
+ teamId: string;
49
+ /** 绑到哪个平台(换绑/多平台时区分该往哪回写)。 */
50
+ platformBaseUrl: string;
51
+ /** 领取幂等键,必须是高熵随机(禁止由 issueId 派生)。全表唯一。 */
52
+ claimId: string;
53
+ /** 领取代次快照——回写栅栏,旧代次的迟到回写会被平台按 stale_epoch 丢弃。 */
54
+ claimEpoch: number;
55
+ bindState: 'pending' | 'bound' | 'void' | 'released' | 'done';
56
+ /** 会话所在的群。拉群模式下 === anchorId;话题模式下是承载话题的那个群。
57
+ * 崩溃恢复据此复用已建的群而不是再建一个。 */
58
+ chatId?: string;
59
+ /** 会话作用域:拉群 → 'chat'(一群一 issue 一会话);话题 → 'thread'。 */
60
+ scope: 'chat' | 'thread';
61
+ /** 本 binding 的 sourceSeq 分配计数器:单调、每 binding 唯一,平台的线上幂等只认它。 */
62
+ nextSourceSeq: number;
63
+ /** 已成功回写的状态(离线期间不动——去重合并要拿它和「最新未完成目标」一起比)。 */
64
+ lastSyncedStatus?: IssueStatus;
65
+ /** 上次回写后平台返回的 issue.stateRev,下次状态 CAS 用。 */
66
+ platformStateRev?: number;
67
+ createdAt: number;
68
+ updatedAt: number;
69
+ }
70
+ export interface IssueOutboxRow {
71
+ /** 仅本地行主键(去重发送用),不上送平台——平台幂等只认 sourceSeq。 */
72
+ writeId: string;
73
+ anchorId: string;
74
+ sourceSeq: number;
75
+ targetStatus: IssueStatus;
76
+ attentionReason?: AttentionReason;
77
+ claimId: string;
78
+ claimEpoch: number;
79
+ expectedStateRev?: number;
80
+ state: 'pending' | 'inflight' | 'done' | 'failed';
81
+ attempts: number;
82
+ nextRetryAt?: number;
83
+ lastError?: string;
84
+ createdAt: number;
85
+ }
86
+ export declare function listBindings(dataDir: string): IssueBinding[];
87
+ export declare function getBinding(dataDir: string, anchorId: string): IssueBinding | null;
88
+ /**
89
+ * 这条 binding 是否还代表「本机正持有这个 issue」。
90
+ *
91
+ * 抽成一个谓词而不是到处写 `!== 'void'`:终态从一个变成两个(`void` / `released`)时,
92
+ * 散落各处的判断只要漏改一处,就会出现「已释放的 issue 仍被当成已领取」——重领被拦、
93
+ * 回写继续发,而且全程不报错。加状态就改这一个地方。
94
+ */
95
+ export declare function isActiveBindState(state: IssueBinding['bindState']): boolean;
96
+ /** 按 claimId 反查——崩溃恢复的入口(§六:不依赖平台「按 claimId 查 issue」的接口,那个接口不存在)。 */
97
+ export declare function findBindingByClaimId(dataDir: string, claimId: string): IssueBinding | null;
98
+ /** 某 issue 当前在本机的活跃 binding(作废/已释放的不算)。用于「这个 issue 是不是我已经领了」。 */
99
+ export declare function findActiveBindingByIssue(dataDir: string, issueId: string): IssueBinding | null;
100
+ export interface IssueClaimIntent {
101
+ /** 主键。高熵随机,与平台 claim 用的是同一个值。 */
102
+ claimId: string;
103
+ issueId: string;
104
+ teamId: string;
105
+ claimEpoch: number;
106
+ platformBaseUrl: string;
107
+ /** 平台 claim 返回的 stateRev,后续 bind 的 CAS 基线。 */
108
+ platformStateRev: number;
109
+ /** 这个 issue 交给哪个 bot 跑(决定 localTaskRef 的 appId 段)。 */
110
+ larkAppId: string;
111
+ scope: 'chat' | 'thread';
112
+ /**
113
+ * 建群/发 seed 拿到 anchor 后回填。它出现 = 群已存在,对账时**不要再建一个**。
114
+ * 注意这份回填本身也可能崩在中间,所以对账不能只信它,还要按 claimId 去群里反查。
115
+ */
116
+ anchorId?: string;
117
+ chatId?: string;
118
+ createdAt: number;
119
+ updatedAt: number;
120
+ }
121
+ export declare function listClaimIntents(dataDir: string): IssueClaimIntent[];
122
+ export declare function getClaimIntent(dataDir: string, claimId: string): IssueClaimIntent | null;
123
+ /**
124
+ * 记下一次领取意图。**必须在 platform claim 返回成功后立刻调用,早于任何建群动作**。
125
+ *
126
+ * 同 claimId 重入返回既有记录(claim 本身幂等,重试不该产生第二条意图)。
127
+ */
128
+ export declare function recordClaimIntent(dataDir: string, input: Omit<IssueClaimIntent, 'createdAt' | 'updatedAt'>, now?: number): IssueClaimIntent;
129
+ /** 回填 anchorId / chatId(建群成功后)。 */
130
+ export declare function updateClaimIntent(dataDir: string, claimId: string, patch: Partial<Omit<IssueClaimIntent, 'claimId' | 'createdAt'>>, now?: number): IssueClaimIntent | null;
131
+ /**
132
+ * 领取流程走完(binding 已 bound)后清掉意图——此后 binding 自己就是完整证据。
133
+ *
134
+ * ⚠️ 清除必须**晚于** binding 落盘。反过来先清意图再写 binding,中间崩溃就同时丢掉了两份
135
+ * 证据,那个群彻底认不回来——这正是意图存在的意义。
136
+ */
137
+ export declare function clearClaimIntent(dataDir: string, claimId: string): boolean;
138
+ /** 对账分类:意图还在、但没有对应 binding 的,就是需要人/流程接手的悬空领取。 */
139
+ export declare function listDanglingClaimIntents(dataDir: string): IssueClaimIntent[];
140
+ /** 注意没有 `localTaskRef`:它由 `buildLocalTaskRef(anchorId, larkAppId)` 内生,见上。 */
141
+ export type CreateBindingInput = Omit<IssueBinding, 'nextSourceSeq' | 'createdAt' | 'updatedAt' | 'bindState' | 'localTaskRef'> & {
142
+ bindState?: IssueBinding['bindState'];
143
+ };
144
+ /**
145
+ * 写入一条 pending binding。**在建群/发 seed 拿到 anchorId 之后调用**——主键就是那个
146
+ * anchor,之前不可能有。放在 `createGroupWithBots` 的 `onChatCreated` 同步钩子里写,
147
+ * 让它成为「群已存在」到「群已归属某个 issue」之间的 durable boundary(见文件头时序)。
148
+ *
149
+ * 两条不变式都在这层强制,不指望调用方自觉:
150
+ * - **claimId 唯一**:同 claimId 重入直接返回既有行(本地幂等,对应 Desktop 侧的
151
+ * `UNIQUE(claim_id)`)——领取重试不会产生第二条 binding、也不会重复建群。
152
+ * - **一 issue 一活跃 binding**:同 issue 已有非 void 的 binding 却拿着不同 claimId 进来,
153
+ * 是调用方漏查(比如没先 `findActiveBindingByIssue`)。此时**抛错**而不是照写——写下去
154
+ * 就是同一个 issue 两个群、两个 agent 同时开工,而平台侧只认最后一次 claim,另一个群
155
+ * 会变成谁也不管的孤儿。抛在这里离原因最近;释放后重领是安全的(旧 binding 已置 void)。
156
+ */
157
+ export declare function createBinding(dataDir: string, input: CreateBindingInput, now?: number): IssueBinding;
158
+ /** 局部更新一条 binding(read-modify-write,保住并发写入的其它字段)。 */
159
+ export declare function updateBinding(dataDir: string, anchorId: string, patch: Partial<Omit<IssueBinding, 'anchorId' | 'createdAt'>>, now?: number): IssueBinding | null;
160
+ /** 删除一条 binding(会话彻底结束且 issue 已终态后清理)。返回是否删掉。 */
161
+ export declare function removeBinding(dataDir: string, anchorId: string): boolean;
162
+ export declare function listOutbox(dataDir: string, anchorId?: string): IssueOutboxRow[];
163
+ /**
164
+ * 投影一次「本会话应达的 issue 状态」到发件箱(§七 去重合并)。
165
+ *
166
+ * 合并规则(**与最新未完成目标比,不是与 lastSyncedStatus 比**):
167
+ * - 该 binding 已有 `pending` 行 → **就地覆盖**它的 targetStatus 并重分配 sourceSeq,不新增行;
168
+ * - 否则仅当 desired 与「最新目标 ∪ lastSyncedStatus」都不同才排一条新行。
169
+ *
170
+ * 为什么必须这样:离线期间 30s 一次的 tick 会反复投影,若每次都追加,恢复后会把一长串
171
+ * 早已过时的中间态依次发给平台——既刷无效自迁移,又让**最新**状态排在队尾迟迟到不了。
172
+ *
173
+ * 返回排出的行;无需回写时返回 null。
174
+ */
175
+ export declare function enqueueDesiredStatus(dataDir: string, anchorId: string, desired: IssueStatus, opts?: {
176
+ attentionReason?: AttentionReason;
177
+ expectedStateRev?: number;
178
+ }, now?: number): IssueOutboxRow | null;
179
+ /**
180
+ * 领取该 binding 的下一条待发行(`pending → inflight` 的 CAS)。
181
+ *
182
+ * **串行**:同一 binding 已有 inflight 行时返回 null——平台侧要求同一 issue 的 sourceSeq
183
+ * 单调到达,并发发送会让顺序乱掉。`nextRetryAt` 未到的行也不返回(退避)。
184
+ */
185
+ export declare function claimNextOutboxRow(dataDir: string, anchorId: string, now?: number): IssueOutboxRow | null;
186
+ /**
187
+ * 发送成功:标 done,并把平台返回的 stateRev / 已同步状态写回 binding。
188
+ *
189
+ * `applied: false` 用于「这条行不必再发了,但平台**没有**采纳它」——目前只有一种情况:
190
+ * 409 对账发现 claim 已易主。此时**绝不能**写 `lastSyncedStatus`:写了本地就以为
191
+ * "已经是 in_review 了",下一次交付会走幂等分支直接回成功,而平台上那条压根没变。
192
+ */
193
+ export declare function settleOutboxRow(dataDir: string, writeId: string, result: {
194
+ platformStateRev?: number;
195
+ platformLastSourceSeq?: number;
196
+ applied?: boolean;
197
+ }, now?: number): void;
198
+ /** 发送失败:退回 pending + 指数退避(上限 5min)。`fatal` 用于平台明确拒绝、重试无意义的情况。 */
199
+ export declare function failOutboxRow(dataDir: string, writeId: string, error: string, opts?: {
200
+ fatal?: boolean;
201
+ }, now?: number): void;
202
+ /**
203
+ * 启动对账:把本机所有 `inflight` 退回 `pending`。
204
+ *
205
+ * 进程在「标 inflight 后、记录响应前」崩溃时,那一行会永远停在 inflight 并**堵死该
206
+ * binding 的串行 pump**(claimNextOutboxRow 见到 inflight 就返回 null)。平台侧的
207
+ * sourceSeq 单调 + 终态幂等保证重复投递是安全的,所以无脑退回即可,不需要发送租约。
208
+ *
209
+ * 返回被退回的行数。
210
+ */
211
+ export declare function resetInflightToPending(dataDir: string): number;
212
+ /** 清理已完成的发件箱行(保留最近 `keep` 条便于排查)。返回清掉的行数。 */
213
+ export declare function pruneOutbox(dataDir: string, keep?: number): number;
214
+ //# sourceMappingURL=issue-board-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issue-board-store.d.ts","sourceRoot":"","sources":["../../src/services/issue-board-store.ts"],"names":[],"mappings":"AA6EA;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED,kDAAkD;AAClD,MAAM,MAAM,WAAW,GACnB,MAAM,GACN,SAAS,GACT,aAAa,GACb,WAAW,GACX,MAAM,GACN,YAAY,GACZ,iBAAiB,GACjB,UAAU,CAAC;AAEf,MAAM,MAAM,eAAe,GACvB,yBAAyB,GACzB,wBAAwB,GACxB,cAAc,GACd,aAAa,CAAC;AAElB;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;;;;OASG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;8CAE0C;IAC1C,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,8BAA8B;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;IAC9D;+BAC2B;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,KAAK,EAAE,MAAM,GAAG,QAAQ,CAAC;IACzB,8DAA8D;IAC9D,aAAa,EAAE,MAAM,CAAC;IACtB,8CAA8C;IAC9C,gBAAgB,CAAC,EAAE,WAAW,CAAC;IAC/B,4CAA4C;IAC5C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,WAAW,CAAC;IAC1B,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,KAAK,EAAE,SAAS,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,CAAC;IAClD,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAuBD,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,EAAE,CAE5D;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAEjF;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,YAAY,CAAC,WAAW,CAAC,GAAG,OAAO,CAE3E;AAED,qEAAqE;AACrE,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAE1F;AAED,iEAAiE;AACjE,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAE9F;AAiBD,MAAM,WAAW,gBAAgB;IAC/B,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,8CAA8C;IAC9C,gBAAgB,EAAE,MAAM,CAAC;IACzB,sDAAsD;IACtD,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,GAAG,QAAQ,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAUD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAEpE;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAExF;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,IAAI,CAAC,gBAAgB,EAAE,WAAW,GAAG,WAAW,CAAC,EACxD,GAAG,GAAE,MAAmB,GACvB,gBAAgB,CAUlB;AAED,mCAAmC;AACnC,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,WAAW,CAAC,CAAC,EAC/D,GAAG,GAAE,MAAmB,GACvB,gBAAgB,GAAG,IAAI,CAUzB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAQ1E;AAED,iDAAiD;AACjD,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAG5E;AAED,6EAA6E;AAC7E,MAAM,MAAM,kBAAkB,GAAG,IAAI,CACnC,YAAY,EACZ,eAAe,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,cAAc,CAC3E,GAAG;IAAE,SAAS,CAAC,EAAE,YAAY,CAAC,WAAW,CAAC,CAAA;CAAE,CAAC;AAE9C;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,kBAAkB,EACzB,GAAG,GAAE,MAAmB,GACvB,YAAY,CA0Bd;AAED,qDAAqD;AACrD,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,UAAU,GAAG,WAAW,CAAC,CAAC,EAC5D,GAAG,GAAE,MAAmB,GACvB,YAAY,GAAG,IAAI,CAErB;AAkBD,iDAAiD;AACjD,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAQxE;AAaD,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,cAAc,EAAE,CAG/E;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,WAAW,EACpB,IAAI,GAAE;IAAE,eAAe,CAAC,EAAE,eAAe,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAO,EAC3E,GAAG,GAAE,MAAmB,GACvB,cAAc,GAAG,IAAI,CAqEvB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,GAAG,GAAE,MAAmB,GACvB,cAAc,GAAG,IAAI,CAkBvB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,MAAM,EAAE;IAAE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,EACxF,GAAG,GAAE,MAAmB,GACvB,IAAI,CAgCN;AAED,iEAAiE;AACjE,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,EAC9B,GAAG,GAAE,MAAmB,GACvB,IAAI,CAeN;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAY9D;AAED,6CAA6C;AAC7C,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,SAAK,GAAG,MAAM,CAW9D"}