botmux 3.9.0 → 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 (112) hide show
  1. package/dist/.runtime-build-id +1 -1
  2. package/dist/adapters/backend/tmux-pipe-backend.d.ts +7 -0
  3. package/dist/adapters/backend/tmux-pipe-backend.d.ts.map +1 -1
  4. package/dist/adapters/backend/tmux-pipe-backend.js +20 -0
  5. package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
  6. package/dist/adapters/backend/types.d.ts +12 -0
  7. package/dist/adapters/backend/types.d.ts.map +1 -1
  8. package/dist/adapters/backend/types.js.map +1 -1
  9. package/dist/adapters/cli/codex.d.ts.map +1 -1
  10. package/dist/adapters/cli/codex.js +40 -7
  11. package/dist/adapters/cli/codex.js.map +1 -1
  12. package/dist/adapters/cli/traex.d.ts.map +1 -1
  13. package/dist/adapters/cli/traex.js +87 -87
  14. package/dist/adapters/cli/traex.js.map +1 -1
  15. package/dist/cli/deferred-topic-send.d.ts +11 -0
  16. package/dist/cli/deferred-topic-send.d.ts.map +1 -1
  17. package/dist/cli/deferred-topic-send.js +21 -4
  18. package/dist/cli/deferred-topic-send.js.map +1 -1
  19. package/dist/cli.d.ts.map +1 -1
  20. package/dist/cli.js +177 -38
  21. package/dist/cli.js.map +1 -1
  22. package/dist/core/command-handler.d.ts.map +1 -1
  23. package/dist/core/command-handler.js +40 -1
  24. package/dist/core/command-handler.js.map +1 -1
  25. package/dist/core/dispatch.d.ts +43 -5
  26. package/dist/core/dispatch.d.ts.map +1 -1
  27. package/dist/core/dispatch.js +104 -6
  28. package/dist/core/dispatch.js.map +1 -1
  29. package/dist/core/passthrough-commands.d.ts.map +1 -1
  30. package/dist/core/passthrough-commands.js +1 -1
  31. package/dist/core/passthrough-commands.js.map +1 -1
  32. package/dist/daemon.d.ts +11 -1
  33. package/dist/daemon.d.ts.map +1 -1
  34. package/dist/daemon.js +32 -3
  35. package/dist/daemon.js.map +1 -1
  36. package/dist/i18n/en.d.ts.map +1 -1
  37. package/dist/i18n/en.js +2 -0
  38. package/dist/i18n/en.js.map +1 -1
  39. package/dist/i18n/zh.d.ts.map +1 -1
  40. package/dist/i18n/zh.js +2 -0
  41. package/dist/i18n/zh.js.map +1 -1
  42. package/dist/im/lark/card-handler.d.ts.map +1 -1
  43. package/dist/im/lark/card-handler.js +9 -0
  44. package/dist/im/lark/card-handler.js.map +1 -1
  45. package/dist/im/lark/issue-card.d.ts +223 -0
  46. package/dist/im/lark/issue-card.d.ts.map +1 -0
  47. package/dist/im/lark/issue-card.js +542 -0
  48. package/dist/im/lark/issue-card.js.map +1 -0
  49. package/dist/im/lark/issue-command-deps.d.ts +41 -0
  50. package/dist/im/lark/issue-command-deps.d.ts.map +1 -0
  51. package/dist/im/lark/issue-command-deps.js +195 -0
  52. package/dist/im/lark/issue-command-deps.js.map +1 -0
  53. package/dist/im/lark/issue-command.d.ts +137 -0
  54. package/dist/im/lark/issue-command.d.ts.map +1 -0
  55. package/dist/im/lark/issue-command.js +335 -0
  56. package/dist/im/lark/issue-command.js.map +1 -0
  57. package/dist/platform/issue-client.d.ts +201 -0
  58. package/dist/platform/issue-client.d.ts.map +1 -0
  59. package/dist/platform/issue-client.js +142 -0
  60. package/dist/platform/issue-client.js.map +1 -0
  61. package/dist/platform/platform-http.d.ts +4 -0
  62. package/dist/platform/platform-http.d.ts.map +1 -1
  63. package/dist/platform/platform-http.js +38 -2
  64. package/dist/platform/platform-http.js.map +1 -1
  65. package/dist/services/codex-composer-state.d.ts +27 -0
  66. package/dist/services/codex-composer-state.d.ts.map +1 -0
  67. package/dist/services/codex-composer-state.js +53 -0
  68. package/dist/services/codex-composer-state.js.map +1 -0
  69. package/dist/services/codex-transcript.d.ts +33 -12
  70. package/dist/services/codex-transcript.d.ts.map +1 -1
  71. package/dist/services/codex-transcript.js +82 -20
  72. package/dist/services/codex-transcript.js.map +1 -1
  73. package/dist/services/issue-board-store.d.ts +214 -0
  74. package/dist/services/issue-board-store.d.ts.map +1 -0
  75. package/dist/services/issue-board-store.js +472 -0
  76. package/dist/services/issue-board-store.js.map +1 -0
  77. package/dist/services/issue-claim-flow.d.ts +160 -0
  78. package/dist/services/issue-claim-flow.d.ts.map +1 -0
  79. package/dist/services/issue-claim-flow.js +222 -0
  80. package/dist/services/issue-claim-flow.js.map +1 -0
  81. package/dist/services/issue-outbox-pump.d.ts +25 -0
  82. package/dist/services/issue-outbox-pump.d.ts.map +1 -0
  83. package/dist/services/issue-outbox-pump.js +102 -0
  84. package/dist/services/issue-outbox-pump.js.map +1 -0
  85. package/dist/services/issue-release.d.ts +78 -0
  86. package/dist/services/issue-release.d.ts.map +1 -0
  87. package/dist/services/issue-release.js +89 -0
  88. package/dist/services/issue-release.js.map +1 -0
  89. package/dist/services/issue-report.d.ts +72 -0
  90. package/dist/services/issue-report.d.ts.map +1 -0
  91. package/dist/services/issue-report.js +84 -0
  92. package/dist/services/issue-report.js.map +1 -0
  93. package/dist/services/issue-status-view.d.ts +52 -0
  94. package/dist/services/issue-status-view.d.ts.map +1 -0
  95. package/dist/services/issue-status-view.js +42 -0
  96. package/dist/services/issue-status-view.js.map +1 -0
  97. package/dist/services/issue-status-writer.d.ts +98 -0
  98. package/dist/services/issue-status-writer.d.ts.map +1 -0
  99. package/dist/services/issue-status-writer.js +123 -0
  100. package/dist/services/issue-status-writer.js.map +1 -0
  101. package/dist/services/traex-paths.d.ts +14 -2
  102. package/dist/services/traex-paths.d.ts.map +1 -1
  103. package/dist/services/traex-paths.js +16 -2
  104. package/dist/services/traex-paths.js.map +1 -1
  105. package/dist/services/traex-transcript.d.ts +33 -0
  106. package/dist/services/traex-transcript.d.ts.map +1 -1
  107. package/dist/services/traex-transcript.js +135 -28
  108. package/dist/services/traex-transcript.js.map +1 -1
  109. package/dist/setup/lark-scopes.json +0 -2
  110. package/dist/worker.js +269 -7
  111. package/dist/worker.js.map +1 -1
  112. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issue-report.d.ts","sourceRoot":"","sources":["../../src/services/issue-report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAiC,KAAK,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,EAAiB,KAAK,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAEhF,gEAAgE;AAChE,MAAM,MAAM,UAAU,GAAG,gBAAgB,CAAC;AAE1C,MAAM,MAAM,iBAAiB,GACzB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,OAAO,CAAA;CAAE,GAC9E;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,GACnC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE;AAC5D;+CAC+C;GAC7C;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE;AAC1D,8DAA8D;GAC5D;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,YAAY,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAElG;;;;;GAKG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GACnD,YAAY,GAAG,IAAI,CAYrB;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAgCxG"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * issue 交付:agent 在领取群里跑完后执行 `botmux report`,把平台 issue 推到 `in_review`(待验收)。
3
+ *
4
+ * 这是 kickoff 正文里那句「完成后执行 botmux report 交付」的落地实现——见
5
+ * [[issue-command-deps]] 的 buildKickoffPrompt。平台生命周期:
6
+ * claimed → in_progress → **in_review** → done
7
+ * (`/status` 允许正向追赶,所以 claimed 也可直接到 in_review。)
8
+ *
9
+ * ## 为什么不复用 dispatch 的 report 路径
10
+ *
11
+ * `botmux report` 原本只服务「多话题协作」:把回报发回主编排会话。领取群是 chat-scope
12
+ * 会话,没有 creatorOpenId / dispatch 注册表,那条路会硬失败。issue 交付的「验收人」
13
+ * 在平台看板,不在飞书主编排话题,所以这里走 binding → outbox → `/status`,与
14
+ * [[issue-release]] 同一套发件箱。
15
+ *
16
+ * ## 平台先行,本地 lastSynced 后写
17
+ *
18
+ * 与 release 相同:先让平台确认 `in_review`,再 settle outbox。中间崩溃最多多发一次
19
+ * (sourceSeq 幂等),不会出现「本机以为已交付、平台还停在 claimed」。
20
+ *
21
+ * 与 release 的差异:交付**不**改 bindState——claim 仍归本机,等人在平台上 accept/reject。
22
+ */
23
+ import { getBinding, isActiveBindState } from './issue-board-store.js';
24
+ import { projectStatus } from './issue-status-writer.js';
25
+ /**
26
+ * 从会话候选锚点里找活跃 issue binding。
27
+ *
28
+ * 拉群领取的 binding 主键是 chatId;话题领取(若将来支持)主键是 rootMessageId。
29
+ * 两个候选都查一遍,先命中活跃 binding 的为准。
30
+ */
31
+ export function findActiveBindingForSession(dataDir, session) {
32
+ const candidates = [];
33
+ if (session.rootMessageId)
34
+ candidates.push(session.rootMessageId);
35
+ if (session.chatId && session.chatId !== session.rootMessageId)
36
+ candidates.push(session.chatId);
37
+ const seen = new Set();
38
+ for (const anchorId of candidates) {
39
+ if (seen.has(anchorId))
40
+ continue;
41
+ seen.add(anchorId);
42
+ const binding = getBinding(dataDir, anchorId);
43
+ if (binding && isActiveBindState(binding.bindState))
44
+ return binding;
45
+ }
46
+ return null;
47
+ }
48
+ /**
49
+ * 把 `anchorId` 上绑定的 issue 推到平台 `in_review`。
50
+ *
51
+ * 走 outbox:`enqueueDesiredStatus` 是 sourceSeq 唯一分配入口。发送失败时行留在
52
+ * pending,可重试同一 `botmux report`。
53
+ */
54
+ export async function reportIssueInReview(deps, anchorId) {
55
+ const binding = getBinding(deps.dataDir, anchorId);
56
+ if (!binding)
57
+ return { ok: false, reason: 'no_binding' };
58
+ if (!isActiveBindState(binding.bindState))
59
+ return { ok: false, reason: 'not_active', binding };
60
+ const r = await projectStatus(deps, anchorId, 'in_review');
61
+ // 409 对账后发现 claim 已易主:交付**没有**落地。这和 release 相反——释放时"claim 已不在
62
+ // 本机"正好就是想要的结果,交付时它意味着这次汇报根本没写上去,报成功就是骗人。
63
+ if (r.ok && !r.applied)
64
+ return { ok: false, reason: 'detached', binding };
65
+ // `idle` = 没有待发行且已同步到位 —— 重复交付(同一次 report 跑了两遍),不是失败。
66
+ const alreadyInReview = !r.ok && r.reason === 'idle';
67
+ if (!r.ok && !alreadyInReview) {
68
+ const detail = r.reason === 'busy'
69
+ ? '上一条回写还在发送中,稍后重试同一条 botmux report'
70
+ : r.reason === 'platform'
71
+ ? r.detail
72
+ : r.reason;
73
+ const permanent = !r.ok && r.reason === 'platform' && r.permanent === true;
74
+ return { ok: false, reason: 'platform', detail, binding, ...(permanent ? { permanent } : {}) };
75
+ }
76
+ // 交付**不**改 bindState:claim 仍归本机,等人在平台上 accept/reject。
77
+ return {
78
+ ok: true,
79
+ binding: getBinding(deps.dataDir, anchorId) ?? binding,
80
+ issueId: binding.issueId,
81
+ alreadyInReview,
82
+ };
83
+ }
84
+ //# sourceMappingURL=issue-report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issue-report.js","sourceRoot":"","sources":["../../src/services/issue-report.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * `/issue status`:把「这个群到底绑着哪条任务、现在什么状态」摊开给人看。
3
+ *
4
+ * 纯只读——不排队、不回写、不改 binding。之所以单独一个服务而不是让命令层直接读 store:
5
+ * 它要同时看**本机**和**平台**两边,而这两边不一致恰恰是最需要被看见的东西:
6
+ *
7
+ * - 本机 `lastSyncedStatus` 与平台 `status` 不同 → 有回写没发出去(发件箱里躺着)
8
+ * - 平台 `claim` 已经不是本机的 → 领取被回收/被别人领走了,这个群里干的活白干
9
+ * - 平台 `needs_attention` → 任务掉进了那扇单向门,得去平台处理
10
+ *
11
+ * 这三种状况在出问题时都是静默的,命令的价值就是把它们变成一眼可见。
12
+ */
13
+ import { type IssueBinding } from './issue-board-store.js';
14
+ import type { PlatformIssue } from '../platform/issue-client.js';
15
+ export interface IssueStatusDeps {
16
+ dataDir: string;
17
+ /** 拉平台当前状态。返回 null 表示**拉不到**(网络/已归档),不是"任务不存在"。 */
18
+ fetchIssue: (teamId: string, issueId: string) => Promise<PlatformIssue | null>;
19
+ }
20
+ export interface IssueStatusView {
21
+ binding: IssueBinding;
22
+ /** 平台侧当前的 issue;null = 没拉到(下游必须说"拉不到",不能说"没有这条任务")。 */
23
+ issue: PlatformIssue | null;
24
+ /** 发件箱里还没发出去的回写条数。>0 说明本机与平台之间存在滞后。 */
25
+ pendingWrites: number;
26
+ /**
27
+ * 已被判死、不再重投的回写条数(平台 401/403/400/404,见 `isPermanentFailure`)。
28
+ *
29
+ * 必须单独报出来:这些行不在 `pendingWrites` 里,界面上一声不吭就等于"已经同步好了",
30
+ * 而实际上那次状态变更**永远不会**到平台。给了 fatal 就得给能看见它的地方。
31
+ */
32
+ failedWrites: number;
33
+ /** 最后一条判死行的错误,直接给人看,省得去翻日志。 */
34
+ lastFailure?: string;
35
+ /** 平台上这条 claim 是否还归本机。issue 拉不到时为 undefined(无法判定,别猜)。 */
36
+ claimMine?: boolean;
37
+ }
38
+ export type IssueStatusResult = {
39
+ ok: true;
40
+ view: IssueStatusView;
41
+ } | {
42
+ ok: false;
43
+ reason: 'no_binding';
44
+ };
45
+ /**
46
+ * 查 `anchorId` 上绑定的 issue 现状。
47
+ *
48
+ * 与 release/report 不同,**终态 binding 也照查**:`/issue status` 的用处正是在"这个群是不是
49
+ * 已经结束了"存疑的时候回答它,此时把它当成"没有绑定"是把唯一的答案藏起来。
50
+ */
51
+ export declare function describeIssue(deps: IssueStatusDeps, anchorId: string): Promise<IssueStatusResult>;
52
+ //# sourceMappingURL=issue-status-view.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issue-status-view.d.ts","sourceRoot":"","sources":["../../src/services/issue-status-view.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAA0B,KAAK,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACnF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAEjE,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;CAChF;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,YAAY,CAAC;IACtB,uDAAuD;IACvD,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IAC5B,uCAAuC;IACvC,aAAa,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB,+BAA+B;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yDAAyD;IACzD,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,MAAM,iBAAiB,GACzB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,eAAe,CAAA;CAAE,GACnC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,CAAC;AAExC;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAsBvG"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * `/issue status`:把「这个群到底绑着哪条任务、现在什么状态」摊开给人看。
3
+ *
4
+ * 纯只读——不排队、不回写、不改 binding。之所以单独一个服务而不是让命令层直接读 store:
5
+ * 它要同时看**本机**和**平台**两边,而这两边不一致恰恰是最需要被看见的东西:
6
+ *
7
+ * - 本机 `lastSyncedStatus` 与平台 `status` 不同 → 有回写没发出去(发件箱里躺着)
8
+ * - 平台 `claim` 已经不是本机的 → 领取被回收/被别人领走了,这个群里干的活白干
9
+ * - 平台 `needs_attention` → 任务掉进了那扇单向门,得去平台处理
10
+ *
11
+ * 这三种状况在出问题时都是静默的,命令的价值就是把它们变成一眼可见。
12
+ */
13
+ import { getBinding, listOutbox } from './issue-board-store.js';
14
+ /**
15
+ * 查 `anchorId` 上绑定的 issue 现状。
16
+ *
17
+ * 与 release/report 不同,**终态 binding 也照查**:`/issue status` 的用处正是在"这个群是不是
18
+ * 已经结束了"存疑的时候回答它,此时把它当成"没有绑定"是把唯一的答案藏起来。
19
+ */
20
+ export async function describeIssue(deps, anchorId) {
21
+ const binding = getBinding(deps.dataDir, anchorId);
22
+ if (!binding)
23
+ return { ok: false, reason: 'no_binding' };
24
+ const issue = await deps.fetchIssue(binding.teamId, binding.issueId);
25
+ const rows = listOutbox(deps.dataDir, anchorId);
26
+ const pendingWrites = rows.filter((r) => r.state === 'pending' || r.state === 'inflight').length;
27
+ const failed = rows.filter((r) => r.state === 'failed');
28
+ const lastFailure = failed[failed.length - 1]?.lastError;
29
+ return {
30
+ ok: true,
31
+ view: {
32
+ binding,
33
+ issue,
34
+ pendingWrites,
35
+ failedWrites: failed.length,
36
+ ...(lastFailure ? { lastFailure } : {}),
37
+ // claim 字段被平台清掉(done/open/reopened)时也是"不归本机",与被别人领走同样处理。
38
+ ...(issue ? { claimMine: issue.claim?.claimId === binding.claimId } : {}),
39
+ },
40
+ };
41
+ }
42
+ //# sourceMappingURL=issue-status-view.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issue-status-view.js","sourceRoot":"","sources":["../../src/services/issue-status-view.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAqB,MAAM,wBAAwB,CAAC;AAgCnF;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAqB,EAAE,QAAgB;IACzE,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACnD,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;IAEzD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IACrE,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAChD,MAAM,aAAa,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,IAAI,CAAC,CAAC,KAAK,KAAK,UAAU,CAAC,CAAC,MAAM,CAAC;IACjG,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC;IACxD,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,SAAS,CAAC;IAEzD,OAAO;QACL,EAAE,EAAE,IAAI;QACR,IAAI,EAAE;YACJ,OAAO;YACP,KAAK;YACL,aAAa;YACb,YAAY,EAAE,MAAM,CAAC,MAAM;YAC3B,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACvC,yDAAyD;YACzD,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC1E;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * 状态回写:把「本会话应该处于什么状态」推到平台。
3
+ *
4
+ * 这是 [[issue-board-store]] 那个发件箱的**唯一**发送端。所有想改 issue 状态的调用方
5
+ * (开工、交付、验收、释放)都走 `projectStatus`,谁都不许直接调 `writeIssueStatus`——
6
+ * sourceSeq 的分配、串行、退避、409 对账全都只在这里实现一次。
7
+ *
8
+ * ## 为什么必须有它(不是架构洁癖)
9
+ *
10
+ * 平台在 bind 之后给一个 **5 分钟的 activation 租约**:到期时 issue 还停在 `claimed`,
11
+ * sweeper 就把它翻成 `needs_attention/claim_activate_timeout`(platform `src/store/issues.ts`
12
+ * 的 sweepExpiredLeases,每 60s 扫一次)。证明「我真的在跑」的唯一方式就是回写
13
+ * `in_progress`。
14
+ *
15
+ * 在这一刀之前 botmux 一个状态都不写,于是那条本该罕见的保护路径变成了**必经之路**:
16
+ * 每个领取的任务 5 分钟后必然掉进需要关注,而且**回不来**——平台只放行
17
+ * `task_blocked` 的 needs_attention 恢复成 in_progress,`claim_activate_timeout` 只能
18
+ * open/reopened(都清 claim),那个群的工作就废了。所以「开工即写 in_progress」不是
19
+ * 锦上添花,是拆引信。
20
+ *
21
+ * ## 409 的两种含义
22
+ *
23
+ * 与 [[issue-release]] 同一套判据:撞 409 先问「平台还认不认我这个 claim」——
24
+ * - **stateRev 过期** → claim 还是我的,拿新基线重发**同一条行**(它上次没被应用,
25
+ * 复用 sourceSeq 是安全的:平台只在 `<= lastSourceSeq` 时才当重复丢弃)
26
+ * - **claim 已不归本机** → 平台侧这条早就结束了(force-detach/租约过期/被别人领走),
27
+ * 再打多少次都一样,直接判定"已结算"让调用方收尾
28
+ */
29
+ import { type AttentionReason, type IssueStatus } from './issue-board-store.js';
30
+ import type { IssueClientResult, PlatformIssue } from '../platform/issue-client.js';
31
+ export interface StatusWriterDeps {
32
+ dataDir: string;
33
+ writeStatus: (issueId: string, args: {
34
+ claimId: string;
35
+ claimEpoch: number;
36
+ sourceSeq: number;
37
+ status: IssueStatus;
38
+ attentionReason?: AttentionReason;
39
+ expectedStateRev: number;
40
+ }) => Promise<IssueClientResult<{
41
+ issue: PlatformIssue;
42
+ }>>;
43
+ /** 撞 409 时用来判断「平台还认不认这个 claim」。拿不到就当无法判定,不猜。 */
44
+ fetchIssue: (teamId: string, issueId: string) => Promise<PlatformIssue | null>;
45
+ now?: () => number;
46
+ }
47
+ export type FlushOutcome =
48
+ /** 平台接受了这次回写。 */
49
+ {
50
+ ok: true;
51
+ applied: true;
52
+ issue: PlatformIssue;
53
+ }
54
+ /** 平台侧这条 claim 已经不归本机了 —— 无需再发,调用方按"已结算"收尾。 */
55
+ | {
56
+ ok: true;
57
+ applied: false;
58
+ detached: true;
59
+ }
60
+ /** 没有待发行(已经同步到位,或压根没排队)。 */
61
+ | {
62
+ ok: false;
63
+ reason: 'idle';
64
+ }
65
+ /** 同一 binding 已有 inflight —— 串行约束,稍后再来。 */
66
+ | {
67
+ ok: false;
68
+ reason: 'busy';
69
+ }
70
+ /** binding 不存在或已是终态。 */
71
+ | {
72
+ ok: false;
73
+ reason: 'no_binding';
74
+ }
75
+ /** `permanent` = 平台明确拒绝且重试无意义,行已标 fatal 不再重投(见 isPermanentFailure)。 */
76
+ | {
77
+ ok: false;
78
+ reason: 'platform';
79
+ detail: string;
80
+ permanent?: boolean;
81
+ };
82
+ /**
83
+ * 把发件箱里该 binding 的下一条待发行发出去。
84
+ *
85
+ * 不排队、只发送——用于后台 pump 重投那些失败退避过的行。
86
+ */
87
+ export declare function flushNextStatus(deps: StatusWriterDeps, anchorId: string): Promise<FlushOutcome>;
88
+ /**
89
+ * 投影一个目标状态并立刻尝试发送。
90
+ *
91
+ * 排队交给 `enqueueDesiredStatus`(sourceSeq 的唯一分配入口 + 去重合并),发送交给
92
+ * `flushNextStatus`。发失败时行留在发件箱里退避,后台 pump 会接着重投——所以调用方
93
+ * 拿到 `platform` 失败也不代表这次投影丢了。
94
+ */
95
+ export declare function projectStatus(deps: StatusWriterDeps, anchorId: string, desired: IssueStatus, opts?: {
96
+ attentionReason?: AttentionReason;
97
+ }): Promise<FlushOutcome>;
98
+ //# sourceMappingURL=issue-status-writer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issue-status-writer.d.ts","sourceRoot":"","sources":["../../src/services/issue-status-writer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,EAQL,KAAK,eAAe,EACpB,KAAK,WAAW,EACjB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAEpF,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,CACX,OAAO,EAAE,MAAM,EACf,IAAI,EAAE;QACJ,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,WAAW,CAAC;QACpB,eAAe,CAAC,EAAE,eAAe,CAAC;QAClC,gBAAgB,EAAE,MAAM,CAAC;KAC1B,KACE,OAAO,CAAC,iBAAiB,CAAC;QAAE,KAAK,EAAE,aAAa,CAAA;KAAE,CAAC,CAAC,CAAC;IAC1D,gDAAgD;IAChD,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IAC/E,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,YAAY;AACtB,iBAAiB;AACf;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,aAAa,CAAA;CAAE;AACnD,+CAA+C;GAC7C;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,IAAI,CAAA;CAAE;AAC9C,4BAA4B;GAC1B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AAC/B,2CAA2C;GACzC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AAC/B,wBAAwB;GACtB;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE;AACrC,uEAAuE;GACrE;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAE3E;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAqErG;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,gBAAgB,EACtB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,WAAW,EACpB,IAAI,GAAE;IAAE,eAAe,CAAC,EAAE,eAAe,CAAA;CAAO,GAC/C,OAAO,CAAC,YAAY,CAAC,CAgBvB"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * 状态回写:把「本会话应该处于什么状态」推到平台。
3
+ *
4
+ * 这是 [[issue-board-store]] 那个发件箱的**唯一**发送端。所有想改 issue 状态的调用方
5
+ * (开工、交付、验收、释放)都走 `projectStatus`,谁都不许直接调 `writeIssueStatus`——
6
+ * sourceSeq 的分配、串行、退避、409 对账全都只在这里实现一次。
7
+ *
8
+ * ## 为什么必须有它(不是架构洁癖)
9
+ *
10
+ * 平台在 bind 之后给一个 **5 分钟的 activation 租约**:到期时 issue 还停在 `claimed`,
11
+ * sweeper 就把它翻成 `needs_attention/claim_activate_timeout`(platform `src/store/issues.ts`
12
+ * 的 sweepExpiredLeases,每 60s 扫一次)。证明「我真的在跑」的唯一方式就是回写
13
+ * `in_progress`。
14
+ *
15
+ * 在这一刀之前 botmux 一个状态都不写,于是那条本该罕见的保护路径变成了**必经之路**:
16
+ * 每个领取的任务 5 分钟后必然掉进需要关注,而且**回不来**——平台只放行
17
+ * `task_blocked` 的 needs_attention 恢复成 in_progress,`claim_activate_timeout` 只能
18
+ * open/reopened(都清 claim),那个群的工作就废了。所以「开工即写 in_progress」不是
19
+ * 锦上添花,是拆引信。
20
+ *
21
+ * ## 409 的两种含义
22
+ *
23
+ * 与 [[issue-release]] 同一套判据:撞 409 先问「平台还认不认我这个 claim」——
24
+ * - **stateRev 过期** → claim 还是我的,拿新基线重发**同一条行**(它上次没被应用,
25
+ * 复用 sourceSeq 是安全的:平台只在 `<= lastSourceSeq` 时才当重复丢弃)
26
+ * - **claim 已不归本机** → 平台侧这条早就结束了(force-detach/租约过期/被别人领走),
27
+ * 再打多少次都一样,直接判定"已结算"让调用方收尾
28
+ */
29
+ import { claimNextOutboxRow, listOutbox, enqueueDesiredStatus, failOutboxRow, getBinding, isActiveBindState, settleOutboxRow, } from './issue-board-store.js';
30
+ import { isPermanentFailure } from '../platform/issue-client.js';
31
+ /**
32
+ * 把发件箱里该 binding 的下一条待发行发出去。
33
+ *
34
+ * 不排队、只发送——用于后台 pump 重投那些失败退避过的行。
35
+ */
36
+ export async function flushNextStatus(deps, anchorId) {
37
+ const now = deps.now ?? Date.now;
38
+ const binding = getBinding(deps.dataDir, anchorId);
39
+ if (!binding)
40
+ return { ok: false, reason: 'no_binding' };
41
+ const row = claimNextOutboxRow(deps.dataDir, anchorId, now());
42
+ if (!row) {
43
+ // claimNextOutboxRow 返回 null 有两种截然不同的原因,不能都当 idle:**有 inflight 挡着**
44
+ // (串行约束,稍后能发)vs **确实没有待发行**(已同步到位)。调用方要据此区分"发不出去"
45
+ // 和"已经是这个状态了"——释放就靠这个判断是补完本地还是报错让人重试。
46
+ const mine = listOutbox(deps.dataDir, anchorId);
47
+ return mine.some((r) => r.state === 'inflight')
48
+ ? { ok: false, reason: 'busy' }
49
+ : { ok: false, reason: 'idle' };
50
+ }
51
+ let expectedStateRev = row.expectedStateRev ?? binding.platformStateRev ?? 0;
52
+ let res = await deps.writeStatus(binding.issueId, {
53
+ claimId: row.claimId,
54
+ claimEpoch: row.claimEpoch,
55
+ sourceSeq: row.sourceSeq,
56
+ status: row.targetStatus,
57
+ ...(row.attentionReason ? { attentionReason: row.attentionReason } : {}),
58
+ expectedStateRev,
59
+ });
60
+ if (!res.ok && res.reason === 'conflict') {
61
+ const fresh = await deps.fetchIssue(binding.teamId, binding.issueId);
62
+ if (fresh && fresh.claim?.claimId !== binding.claimId) {
63
+ // `applied: false` 是关键:这条状态平台**没有**采纳。记成已同步的话,下一次投影同一
64
+ // 状态会走幂等分支直接回成功,而平台那边纹丝不动——一次静默的谎报。
65
+ settleOutboxRow(deps.dataDir, row.writeId, { platformStateRev: fresh.stateRev, applied: false }, now());
66
+ return { ok: true, applied: false, detached: true };
67
+ }
68
+ if (fresh) {
69
+ expectedStateRev = fresh.stateRev;
70
+ res = await deps.writeStatus(binding.issueId, {
71
+ claimId: row.claimId,
72
+ claimEpoch: row.claimEpoch,
73
+ sourceSeq: row.sourceSeq,
74
+ status: row.targetStatus,
75
+ ...(row.attentionReason ? { attentionReason: row.attentionReason } : {}),
76
+ expectedStateRev,
77
+ });
78
+ }
79
+ }
80
+ if (!res.ok) {
81
+ const detail = 'error' in res ? `${res.reason}: ${res.error}` : res.reason;
82
+ // 分清「等会儿再试」和「再试也没用」。401/403(凭证失效、machine_mismatch、claim 被
83
+ // revoke)与 400/404(参数错、issue 被删或归档)是后者:不标 fatal 的话这条行会每 ≤5
84
+ // 分钟朝一个永远不会变的死端点打一发,而且 `/issue status` 上那句「N 条回写没发出去」
85
+ // 永不清零,验收的人会一直以为"过会儿就好"。判据见 [[issue-client]] 的
86
+ // isPermanentFailure——它刻意不等于 !isRetriable(409 与 unbound 都不该判死)。
87
+ const fatal = isPermanentFailure(res);
88
+ failOutboxRow(deps.dataDir, row.writeId, detail, fatal ? { fatal: true } : {}, now());
89
+ return { ok: false, reason: 'platform', detail, ...(fatal ? { permanent: true } : {}) };
90
+ }
91
+ settleOutboxRow(deps.dataDir, row.writeId, {
92
+ platformStateRev: res.value.issue.stateRev,
93
+ ...(res.value.issue.claim ? { platformLastSourceSeq: res.value.issue.claim.lastSourceSeq } : {}),
94
+ }, now());
95
+ return { ok: true, applied: true, issue: res.value.issue };
96
+ }
97
+ /**
98
+ * 投影一个目标状态并立刻尝试发送。
99
+ *
100
+ * 排队交给 `enqueueDesiredStatus`(sourceSeq 的唯一分配入口 + 去重合并),发送交给
101
+ * `flushNextStatus`。发失败时行留在发件箱里退避,后台 pump 会接着重投——所以调用方
102
+ * 拿到 `platform` 失败也不代表这次投影丢了。
103
+ */
104
+ export async function projectStatus(deps, anchorId, desired, opts = {}) {
105
+ const now = deps.now ?? Date.now;
106
+ const binding = getBinding(deps.dataDir, anchorId);
107
+ if (!binding)
108
+ return { ok: false, reason: 'no_binding' };
109
+ const queued = enqueueDesiredStatus(deps.dataDir, anchorId, desired, opts, now());
110
+ if (!queued) {
111
+ // enqueue 返回 null 有三种情况,要分开:终态 binding(no_binding 语义)、已经同步
112
+ // 到位(idle)、有 inflight 挡着(busy)。混成一个"失败"会让调用方分不清
113
+ // "已经是这个状态了" 和 "发不出去"。
114
+ if (!isActiveBindState(binding.bindState))
115
+ return { ok: false, reason: 'no_binding' };
116
+ if (binding.lastSyncedStatus === desired)
117
+ return { ok: false, reason: 'idle' };
118
+ // 已经在追同一个目标:那条行还没发出去,直接发它,不要另起一条。
119
+ return flushNextStatus(deps, anchorId);
120
+ }
121
+ return flushNextStatus(deps, anchorId);
122
+ }
123
+ //# sourceMappingURL=issue-status-writer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issue-status-writer.js","sourceRoot":"","sources":["../../src/services/issue-status-writer.ts"],"names":[],"mappings":"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"}
@@ -4,9 +4,21 @@
4
4
  * still resolve correctly. */
5
5
  export declare function traeHome(): string;
6
6
  /** SQLite database holding the `threads` table (one row per interactive
7
- * session). Used as the submit-verification source of truth because traex
8
- * (unlike codex) does not write a flat history.jsonl. */
7
+ * session). Used to reverse-map a botmux session id to a TRAE-native session
8
+ * UUID (buildArgs/buildResumeCommand) and as the session/path index for
9
+ * rollout-based transcript bridging. */
9
10
  export declare function traeStateDbPath(): string;
11
+ /** Global submit log — TRAE appends one JSON line (`{session_id, ts, text}`,
12
+ * byte-identical to Codex's format) here on every successful user submit
13
+ * across all sessions, AT SUBMIT TIME. This is the authoritative submit-
14
+ * confirmation source: unlike the per-session rollout JSONL, a message parked
15
+ * by TRAE's type-ahead queue while a turn is running is written here
16
+ * immediately, whereas the rollout only records it once the running turn
17
+ * dequeues it (which can exceed the worker's submit-confirmation deadline and
18
+ * fire a false "submission couldn't be confirmed" warning). Shared by every
19
+ * TRAE pane under one TRAE_HOME, so a match needs a pid-ownership filter to
20
+ * avoid attributing a sibling pane's identical text — mirrors codexHistoryPath. */
21
+ export declare function traeHistoryPath(): string;
10
22
  /** Per-session rollout JSONL files live under dates here, e.g.
11
23
  * sessions/2026/06/04/rollout-<timestamp>-<uuid>.jsonl. The threads table
12
24
  * stores the absolute path per session. */
@@ -1 +1 @@
1
- {"version":3,"file":"traex-paths.d.ts","sourceRoot":"","sources":["../../src/services/traex-paths.ts"],"names":[],"mappings":"AAOA;;;+BAG+B;AAC/B,wBAAgB,QAAQ,IAAI,MAAM,CAGjC;AAED;;0DAE0D;AAC1D,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED;;4CAE4C;AAC5C,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC"}
1
+ {"version":3,"file":"traex-paths.d.ts","sourceRoot":"","sources":["../../src/services/traex-paths.ts"],"names":[],"mappings":"AAOA;;;+BAG+B;AAC/B,wBAAgB,QAAQ,IAAI,MAAM,CAGjC;AAED;;;yCAGyC;AACzC,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED;;;;;;;;;oFASoF;AACpF,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED;;4CAE4C;AAC5C,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC"}
@@ -12,11 +12,25 @@ export function traeHome() {
12
12
  return configured ? expandHome(configured) : join(homedir(), '.trae');
13
13
  }
14
14
  /** SQLite database holding the `threads` table (one row per interactive
15
- * session). Used as the submit-verification source of truth because traex
16
- * (unlike codex) does not write a flat history.jsonl. */
15
+ * session). Used to reverse-map a botmux session id to a TRAE-native session
16
+ * UUID (buildArgs/buildResumeCommand) and as the session/path index for
17
+ * rollout-based transcript bridging. */
17
18
  export function traeStateDbPath() {
18
19
  return join(traeHome(), 'cli', 'state_5.sqlite');
19
20
  }
21
+ /** Global submit log — TRAE appends one JSON line (`{session_id, ts, text}`,
22
+ * byte-identical to Codex's format) here on every successful user submit
23
+ * across all sessions, AT SUBMIT TIME. This is the authoritative submit-
24
+ * confirmation source: unlike the per-session rollout JSONL, a message parked
25
+ * by TRAE's type-ahead queue while a turn is running is written here
26
+ * immediately, whereas the rollout only records it once the running turn
27
+ * dequeues it (which can exceed the worker's submit-confirmation deadline and
28
+ * fire a false "submission couldn't be confirmed" warning). Shared by every
29
+ * TRAE pane under one TRAE_HOME, so a match needs a pid-ownership filter to
30
+ * avoid attributing a sibling pane's identical text — mirrors codexHistoryPath. */
31
+ export function traeHistoryPath() {
32
+ return join(traeHome(), 'cli', 'history.jsonl');
33
+ }
20
34
  /** Per-session rollout JSONL files live under dates here, e.g.
21
35
  * sessions/2026/06/04/rollout-<timestamp>-<uuid>.jsonl. The threads table
22
36
  * stores the absolute path per session. */
@@ -1 +1 @@
1
- {"version":3,"file":"traex-paths.js","sourceRoot":"","sources":["../../src/services/traex-paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACtE,CAAC;AAED;;;+BAG+B;AAC/B,MAAM,UAAU,QAAQ;IACtB,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC;IACjD,OAAO,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC;AACxE,CAAC;AAED;;0DAE0D;AAC1D,MAAM,UAAU,eAAe;IAC7B,OAAO,IAAI,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,gBAAgB,CAAC,CAAC;AACnD,CAAC;AAED;;4CAE4C;AAC5C,MAAM,UAAU,gBAAgB;IAC9B,OAAO,IAAI,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;AAC7C,CAAC"}
1
+ {"version":3,"file":"traex-paths.js","sourceRoot":"","sources":["../../src/services/traex-paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACtE,CAAC;AAED;;;+BAG+B;AAC/B,MAAM,UAAU,QAAQ;IACtB,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC;IACjD,OAAO,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC;AACxE,CAAC;AAED;;;yCAGyC;AACzC,MAAM,UAAU,eAAe;IAC7B,OAAO,IAAI,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,gBAAgB,CAAC,CAAC;AACnD,CAAC;AAED;;;;;;;;;oFASoF;AACpF,MAAM,UAAU,eAAe;IAC7B,OAAO,IAAI,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,eAAe,CAAC,CAAC;AAClD,CAAC;AAED;;4CAE4C;AAC5C,MAAM,UAAU,gBAAgB;IAC9B,OAAO,IAAI,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;AAC7C,CAAC"}
@@ -12,6 +12,23 @@ export declare function drainTraexRollout(path: string, fromOffset: number): Cod
12
12
  /** Authoritative submit confirmation used by the adapter. Only a complete
13
13
  * role=user rollout record appended after `fromOffset` can match. */
14
14
  export declare function traexRolloutHasUserInputSince(path: string, fromOffset: number, expectedText: string): boolean;
15
+ export interface TraexHistoryMatch {
16
+ found: boolean;
17
+ cliSessionId?: string;
18
+ }
19
+ /** Optional ownership filter for a shared-history match: accept a same-text
20
+ * line only when its session id is one the owning pid actually holds open.
21
+ * Re-evaluated on every call so a lazily-opened owned rollout fd that appears
22
+ * AFTER its history line can still be accepted on a later poll. */
23
+ export type TraexHistorySidFilter = (cliSessionId: string | undefined) => boolean;
24
+ /** Scan the byte delta appended to history.jsonl since `fromByte` for a line
25
+ * whose decoded `text` exactly matches `expectedText` (newline-normalised).
26
+ * Never parses a non-newline-terminated tail, so a half-written line can't
27
+ * manufacture a false match — a later poll sees the completed entry. */
28
+ export declare function traexHistoryMatchDelta(path: string, fromByte: number, expectedText: string, acceptSid?: TraexHistorySidFilter): TraexHistoryMatch;
29
+ /** Current byte size of history.jsonl (0 when absent), captured before a paste
30
+ * so the confirmation scan only considers lines this submit appends. */
31
+ export declare function traexHistorySize(path: string): number;
15
32
  /** Find the rollout file an externally-running TRAE process has open.
16
33
  * Same /proc/<pid>/fd strategy as findCodexRolloutByPid, but with a
17
34
  * TRAE-specific path matcher so we never bind to a sibling Codex pane. */
@@ -19,6 +36,22 @@ export declare function findTraexRolloutByPid(pid: number): {
19
36
  path: string;
20
37
  cliSessionId: string;
21
38
  } | undefined;
39
+ /** Lowercased set of TRAE session ids whose rollout the pid holds open. The
40
+ * ownership gate for a shared-history.jsonl submit match: only a sid this pid
41
+ * actually owns is safe to accept, so a concurrent sibling pane's identical
42
+ * text can't hand back a foreign session id. Empty Set = pid holds no TRAE
43
+ * rollout; undefined = fd enumeration unavailable (callers must treat undefined
44
+ * as "cannot prove ownership" — fail closed, do not bind). Mirrors
45
+ * findCodexRolloutSetByPid. */
46
+ export declare function findTraexRolloutSetByPid(pid: number): Set<string> | undefined;
47
+ /** Pure ownership decision: is `cliSessionId` one of the rollouts the observed
48
+ * pid holds open? `ownedRollouts` is the lowercased sid set from
49
+ * findTraexRolloutSetByPid (undefined when fd enumeration was unavailable).
50
+ * FAIL CLOSED — a missing set or a non-member id returns false so the caller
51
+ * never binds the bridge (or persists a resume id) it can't prove the pid owns.
52
+ * Extracted so the exact predicate the worker's persist/attach gates use is
53
+ * unit-testable without a live pid. Mirrors codexHistorySidIsOwned. */
54
+ export declare function traexHistorySidIsOwned(cliSessionId: string, ownedRollouts: Set<string> | undefined): boolean;
22
55
  /** Locate the rollout file for a given TRAE session UUID. Filename shape is
23
56
  * identical to Codex: `rollout-<ts>-<sid>.jsonl`, so a suffix match over the
24
57
  * TRAE sessions tree is unambiguous. */
@@ -1 +1 @@
1
- {"version":3,"file":"traex-transcript.d.ts","sourceRoot":"","sources":["../../src/services/traex-transcript.ts"],"names":[],"mappings":"AA6BA,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EAEtB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EAAE,wBAAwB,IAAI,wBAAwB,EAAE,CAAC;AAChE,OAAO,EAAE,oBAAoB,IAAI,oBAAoB,EAAE,CAAC;AACxD,YAAY,EAAE,gBAAgB,IAAI,gBAAgB,EAAE,gBAAgB,IAAI,gBAAgB,EAAE,CAAC;AA8B3F;;;;;6EAK6E;AAC7E,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,gBAAgB,CAwEpF;AAMD;qEACqE;AACrE,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,GACnB,OAAO,CAKT;AAiBD;;2EAE2E;AAC3E,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAgCrG;AAED;;yCAEyC;AACzC,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAqBpF"}
1
+ {"version":3,"file":"traex-transcript.d.ts","sourceRoot":"","sources":["../../src/services/traex-transcript.ts"],"names":[],"mappings":"AA6BA,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EAEtB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EAAE,wBAAwB,IAAI,wBAAwB,EAAE,CAAC;AAChE,OAAO,EAAE,oBAAoB,IAAI,oBAAoB,EAAE,CAAC;AACxD,YAAY,EAAE,gBAAgB,IAAI,gBAAgB,EAAE,gBAAgB,IAAI,gBAAgB,EAAE,CAAC;AA8B3F;;;;;6EAK6E;AAC7E,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,gBAAgB,CAwEpF;AAMD;qEACqE;AACrE,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,GACnB,OAAO,CAKT;AAeD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,OAAO,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;oEAGoE;AACpE,MAAM,MAAM,qBAAqB,GAAG,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,KAAK,OAAO,CAAC;AAQlF;;;yEAGyE;AACzE,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,SAAS,CAAC,EAAE,qBAAqB,GAChC,iBAAiB,CA2BnB;AAED;yEACyE;AACzE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGrD;AA+CD;;2EAE2E;AAC3E,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAQrG;AAED;;;;;;gCAMgC;AAChC,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,SAAS,CAS7E;AAED;;;;;;wEAMwE;AACxE,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,SAAS,GACrC,OAAO,CAGT;AAGD;;yCAEyC;AACzC,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAqBpF"}