@dungle-scrubs/tallow 0.8.21 → 0.8.23

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 (217) hide show
  1. package/dist/cli.js +35 -4
  2. package/dist/cli.js.map +1 -1
  3. package/dist/config.d.ts +1 -1
  4. package/dist/config.js +1 -1
  5. package/dist/interactive-mode-patch.d.ts +2 -0
  6. package/dist/interactive-mode-patch.d.ts.map +1 -1
  7. package/dist/interactive-mode-patch.js +82 -0
  8. package/dist/interactive-mode-patch.js.map +1 -1
  9. package/dist/sdk.d.ts +17 -0
  10. package/dist/sdk.d.ts.map +1 -1
  11. package/dist/sdk.js +68 -1
  12. package/dist/sdk.js.map +1 -1
  13. package/dist/workspace-transition-relay.d.ts +40 -7
  14. package/dist/workspace-transition-relay.d.ts.map +1 -1
  15. package/dist/workspace-transition-relay.js +81 -16
  16. package/dist/workspace-transition-relay.js.map +1 -1
  17. package/extensions/__integration__/background-task-widget-ownership.test.ts +216 -0
  18. package/extensions/__integration__/claude-hooks-compat.test.ts +156 -0
  19. package/extensions/__integration__/slash-command-bridge.test.ts +169 -23
  20. package/extensions/_shared/atomic-write.ts +1 -1
  21. package/extensions/_shared/bordered-box.ts +102 -0
  22. package/extensions/_shared/interop-events.ts +5 -0
  23. package/extensions/_shared/pid-registry.ts +1 -1
  24. package/extensions/agent-commands-tool/index.ts +4 -1
  25. package/extensions/background-task-tool/__tests__/lifecycle.test.ts +50 -25
  26. package/extensions/background-task-tool/index.ts +139 -221
  27. package/extensions/bash-tool-enhanced/index.ts +1 -75
  28. package/extensions/cd-tool/index.ts +2 -2
  29. package/extensions/context-fork/spawn.ts +4 -1
  30. package/extensions/health/index.ts +6 -6
  31. package/extensions/hooks/__tests__/claude-compat.test.ts +35 -0
  32. package/extensions/hooks/__tests__/subprocess-hardening.test.ts +73 -0
  33. package/extensions/hooks/index.ts +27 -4
  34. package/extensions/loop/__tests__/loop.test.ts +168 -4
  35. package/extensions/loop/extension.json +6 -5
  36. package/extensions/loop/index.ts +242 -31
  37. package/extensions/plan-mode-tool/__tests__/agent-end-execution.test.ts +373 -0
  38. package/extensions/plan-mode-tool/index.ts +103 -41
  39. package/extensions/prompt-suggestions/__tests__/editor-compatibility.test.ts +42 -0
  40. package/extensions/prompt-suggestions/index.ts +41 -6
  41. package/extensions/slash-command-bridge/__tests__/slash-command-bridge.test.ts +267 -671
  42. package/extensions/slash-command-bridge/extension.json +1 -1
  43. package/extensions/slash-command-bridge/index.ts +230 -116
  44. package/extensions/subagent-tool/index.ts +2 -2
  45. package/extensions/subagent-tool/process.ts +4 -5
  46. package/extensions/tasks/commands/register-tasks-extension.ts +41 -0
  47. package/extensions/teams-tool/__tests__/peer-messaging.test.ts +29 -24
  48. package/extensions/teams-tool/dashboard.ts +3 -5
  49. package/extensions/teams-tool/dispatch/auto-dispatch.ts +18 -1
  50. package/extensions/teams-tool/tools/teammate-tools.ts +9 -6
  51. package/extensions/wezterm-pane-control/__tests__/index.test.ts +88 -4
  52. package/extensions/wezterm-pane-control/index.ts +113 -8
  53. package/package.json +6 -4
  54. package/packages/tallow-tui/README.md +51 -0
  55. package/packages/tallow-tui/dist/autocomplete.d.ts +48 -0
  56. package/packages/tallow-tui/dist/autocomplete.d.ts.map +1 -0
  57. package/packages/tallow-tui/dist/autocomplete.js +564 -0
  58. package/packages/tallow-tui/dist/autocomplete.js.map +1 -0
  59. package/packages/tallow-tui/dist/border-styles.d.ts +32 -0
  60. package/packages/tallow-tui/dist/border-styles.d.ts.map +1 -0
  61. package/packages/tallow-tui/dist/border-styles.js +46 -0
  62. package/packages/tallow-tui/dist/border-styles.js.map +1 -0
  63. package/packages/tallow-tui/dist/components/bordered-box.d.ts +52 -0
  64. package/packages/tallow-tui/dist/components/bordered-box.d.ts.map +1 -0
  65. package/packages/tallow-tui/dist/components/bordered-box.js +89 -0
  66. package/packages/tallow-tui/dist/components/bordered-box.js.map +1 -0
  67. package/packages/tallow-tui/dist/components/box.d.ts +22 -0
  68. package/packages/tallow-tui/dist/components/box.d.ts.map +1 -0
  69. package/packages/tallow-tui/dist/components/box.js +104 -0
  70. package/packages/tallow-tui/dist/components/box.js.map +1 -0
  71. package/packages/tallow-tui/dist/components/cancellable-loader.d.ts +22 -0
  72. package/packages/tallow-tui/dist/components/cancellable-loader.d.ts.map +1 -0
  73. package/packages/tallow-tui/dist/components/cancellable-loader.js +35 -0
  74. package/packages/tallow-tui/dist/components/cancellable-loader.js.map +1 -0
  75. package/packages/tallow-tui/dist/components/editor.d.ts +240 -0
  76. package/packages/tallow-tui/dist/components/editor.d.ts.map +1 -0
  77. package/packages/tallow-tui/dist/components/editor.js +1766 -0
  78. package/packages/tallow-tui/dist/components/editor.js.map +1 -0
  79. package/packages/tallow-tui/dist/components/image.d.ts +126 -0
  80. package/packages/tallow-tui/dist/components/image.d.ts.map +1 -0
  81. package/packages/tallow-tui/dist/components/image.js +245 -0
  82. package/packages/tallow-tui/dist/components/image.js.map +1 -0
  83. package/packages/tallow-tui/dist/components/input.d.ts +37 -0
  84. package/packages/tallow-tui/dist/components/input.d.ts.map +1 -0
  85. package/packages/tallow-tui/dist/components/input.js +439 -0
  86. package/packages/tallow-tui/dist/components/input.js.map +1 -0
  87. package/packages/tallow-tui/dist/components/loader.d.ts +88 -0
  88. package/packages/tallow-tui/dist/components/loader.d.ts.map +1 -0
  89. package/packages/tallow-tui/dist/components/loader.js +146 -0
  90. package/packages/tallow-tui/dist/components/loader.js.map +1 -0
  91. package/packages/tallow-tui/dist/components/markdown.d.ts +95 -0
  92. package/packages/tallow-tui/dist/components/markdown.d.ts.map +1 -0
  93. package/packages/tallow-tui/dist/components/markdown.js +633 -0
  94. package/packages/tallow-tui/dist/components/markdown.js.map +1 -0
  95. package/packages/tallow-tui/dist/components/select-list.d.ts +32 -0
  96. package/packages/tallow-tui/dist/components/select-list.d.ts.map +1 -0
  97. package/packages/tallow-tui/dist/components/select-list.js +156 -0
  98. package/packages/tallow-tui/dist/components/select-list.js.map +1 -0
  99. package/packages/tallow-tui/dist/components/settings-list.d.ts +50 -0
  100. package/packages/tallow-tui/dist/components/settings-list.d.ts.map +1 -0
  101. package/packages/tallow-tui/dist/components/settings-list.js +189 -0
  102. package/packages/tallow-tui/dist/components/settings-list.js.map +1 -0
  103. package/packages/tallow-tui/dist/components/spacer.d.ts +12 -0
  104. package/packages/tallow-tui/dist/components/spacer.d.ts.map +1 -0
  105. package/packages/tallow-tui/dist/components/spacer.js +23 -0
  106. package/packages/tallow-tui/dist/components/spacer.js.map +1 -0
  107. package/packages/tallow-tui/dist/components/text.d.ts +19 -0
  108. package/packages/tallow-tui/dist/components/text.d.ts.map +1 -0
  109. package/packages/tallow-tui/dist/components/text.js +91 -0
  110. package/packages/tallow-tui/dist/components/text.js.map +1 -0
  111. package/packages/tallow-tui/dist/components/truncated-text.d.ts +13 -0
  112. package/packages/tallow-tui/dist/components/truncated-text.d.ts.map +1 -0
  113. package/packages/tallow-tui/dist/components/truncated-text.js +51 -0
  114. package/packages/tallow-tui/dist/components/truncated-text.js.map +1 -0
  115. package/packages/tallow-tui/dist/editor-component.d.ts +50 -0
  116. package/packages/tallow-tui/dist/editor-component.d.ts.map +1 -0
  117. package/packages/tallow-tui/dist/editor-component.js +2 -0
  118. package/packages/tallow-tui/dist/editor-component.js.map +1 -0
  119. package/packages/tallow-tui/dist/fuzzy.d.ts +16 -0
  120. package/packages/tallow-tui/dist/fuzzy.d.ts.map +1 -0
  121. package/packages/tallow-tui/dist/fuzzy.js +107 -0
  122. package/packages/tallow-tui/dist/fuzzy.js.map +1 -0
  123. package/packages/tallow-tui/dist/index.d.ts +25 -0
  124. package/packages/tallow-tui/dist/index.d.ts.map +1 -0
  125. package/packages/tallow-tui/dist/index.js +35 -0
  126. package/packages/tallow-tui/dist/index.js.map +1 -0
  127. package/packages/tallow-tui/dist/keybindings.d.ts +39 -0
  128. package/packages/tallow-tui/dist/keybindings.d.ts.map +1 -0
  129. package/packages/tallow-tui/dist/keybindings.js +114 -0
  130. package/packages/tallow-tui/dist/keybindings.js.map +1 -0
  131. package/packages/tallow-tui/dist/keys.d.ts +168 -0
  132. package/packages/tallow-tui/dist/keys.d.ts.map +1 -0
  133. package/packages/tallow-tui/dist/keys.js +971 -0
  134. package/packages/tallow-tui/dist/keys.js.map +1 -0
  135. package/packages/tallow-tui/dist/kill-ring.d.ts +28 -0
  136. package/packages/tallow-tui/dist/kill-ring.d.ts.map +1 -0
  137. package/packages/tallow-tui/dist/kill-ring.js +44 -0
  138. package/packages/tallow-tui/dist/kill-ring.js.map +1 -0
  139. package/packages/tallow-tui/dist/stdin-buffer.d.ts +48 -0
  140. package/packages/tallow-tui/dist/stdin-buffer.d.ts.map +1 -0
  141. package/packages/tallow-tui/dist/stdin-buffer.js +317 -0
  142. package/packages/tallow-tui/dist/stdin-buffer.js.map +1 -0
  143. package/packages/tallow-tui/dist/terminal-image.d.ts +161 -0
  144. package/packages/tallow-tui/dist/terminal-image.d.ts.map +1 -0
  145. package/packages/tallow-tui/dist/terminal-image.js +460 -0
  146. package/packages/tallow-tui/dist/terminal-image.js.map +1 -0
  147. package/packages/tallow-tui/dist/terminal.d.ts +102 -0
  148. package/packages/tallow-tui/dist/terminal.d.ts.map +1 -0
  149. package/packages/tallow-tui/dist/terminal.js +263 -0
  150. package/packages/tallow-tui/dist/terminal.js.map +1 -0
  151. package/packages/tallow-tui/dist/test-utils/capability-env.d.ts +14 -0
  152. package/packages/tallow-tui/dist/test-utils/capability-env.d.ts.map +1 -0
  153. package/packages/tallow-tui/dist/test-utils/capability-env.js +55 -0
  154. package/packages/tallow-tui/dist/test-utils/capability-env.js.map +1 -0
  155. package/packages/tallow-tui/dist/tui.d.ts +239 -0
  156. package/packages/tallow-tui/dist/tui.d.ts.map +1 -0
  157. package/packages/tallow-tui/dist/tui.js +1058 -0
  158. package/packages/tallow-tui/dist/tui.js.map +1 -0
  159. package/packages/tallow-tui/dist/undo-stack.d.ts +17 -0
  160. package/packages/tallow-tui/dist/undo-stack.d.ts.map +1 -0
  161. package/packages/tallow-tui/dist/undo-stack.js +25 -0
  162. package/packages/tallow-tui/dist/undo-stack.js.map +1 -0
  163. package/packages/tallow-tui/dist/utils.d.ts +96 -0
  164. package/packages/tallow-tui/dist/utils.d.ts.map +1 -0
  165. package/packages/tallow-tui/dist/utils.js +843 -0
  166. package/packages/tallow-tui/dist/utils.js.map +1 -0
  167. package/packages/tallow-tui/package.json +24 -0
  168. package/packages/tallow-tui/src/__tests__/__snapshots__/render.test.ts.snap +121 -0
  169. package/packages/tallow-tui/src/__tests__/editor-border.test.ts +72 -0
  170. package/packages/tallow-tui/src/__tests__/editor-change-listener.test.ts +121 -0
  171. package/packages/tallow-tui/src/__tests__/editor-ghost-text.test.ts +112 -0
  172. package/packages/tallow-tui/src/__tests__/fuzzy.test.ts +91 -0
  173. package/packages/tallow-tui/src/__tests__/image-component.test.ts +113 -0
  174. package/packages/tallow-tui/src/__tests__/keys.test.ts +141 -0
  175. package/packages/tallow-tui/src/__tests__/render.test.ts +179 -0
  176. package/packages/tallow-tui/src/__tests__/stdin-buffer.test.ts +82 -0
  177. package/packages/tallow-tui/src/__tests__/terminal-image.test.ts +363 -0
  178. package/packages/tallow-tui/src/__tests__/tui-diff-regression.test.ts +454 -0
  179. package/packages/tallow-tui/src/__tests__/tui-render-scheduling.test.ts +256 -0
  180. package/packages/tallow-tui/src/__tests__/utils.test.ts +259 -0
  181. package/packages/tallow-tui/src/autocomplete.ts +716 -0
  182. package/packages/tallow-tui/src/border-styles.ts +60 -0
  183. package/packages/tallow-tui/src/components/bordered-box.ts +113 -0
  184. package/packages/tallow-tui/src/components/box.ts +137 -0
  185. package/packages/tallow-tui/src/components/cancellable-loader.ts +40 -0
  186. package/packages/tallow-tui/src/components/editor.ts +2143 -0
  187. package/packages/tallow-tui/src/components/image.ts +315 -0
  188. package/packages/tallow-tui/src/components/input.ts +522 -0
  189. package/packages/tallow-tui/src/components/loader.ts +187 -0
  190. package/packages/tallow-tui/src/components/markdown.ts +780 -0
  191. package/packages/tallow-tui/src/components/select-list.ts +197 -0
  192. package/packages/tallow-tui/src/components/settings-list.ts +264 -0
  193. package/packages/tallow-tui/src/components/spacer.ts +28 -0
  194. package/packages/tallow-tui/src/components/text.ts +113 -0
  195. package/packages/tallow-tui/src/components/truncated-text.ts +65 -0
  196. package/packages/tallow-tui/src/editor-component.ts +92 -0
  197. package/packages/tallow-tui/src/fuzzy.ts +133 -0
  198. package/packages/tallow-tui/src/index.ts +118 -0
  199. package/packages/tallow-tui/src/keybindings.ts +183 -0
  200. package/packages/tallow-tui/src/keys.ts +1189 -0
  201. package/packages/tallow-tui/src/kill-ring.ts +46 -0
  202. package/packages/tallow-tui/src/stdin-buffer.ts +386 -0
  203. package/packages/tallow-tui/src/terminal-image.ts +619 -0
  204. package/packages/tallow-tui/src/terminal.ts +350 -0
  205. package/packages/tallow-tui/src/test-utils/capability-env.ts +56 -0
  206. package/packages/tallow-tui/src/tui.ts +1336 -0
  207. package/packages/tallow-tui/src/undo-stack.ts +28 -0
  208. package/packages/tallow-tui/src/utils.ts +948 -0
  209. package/packages/tallow-tui/tsconfig.build.json +21 -0
  210. package/runtime/agent-runner.ts +20 -0
  211. package/runtime/atomic-write.ts +8 -0
  212. package/runtime/otel.ts +12 -0
  213. package/runtime/resolve-module.ts +23 -0
  214. package/runtime/runtime-path-provider.ts +12 -0
  215. package/runtime/runtime-provenance.ts +17 -0
  216. package/runtime/workspace-transition-relay.ts +21 -0
  217. package/runtime/workspace-transition.ts +29 -0
@@ -79,11 +79,12 @@ describe("peer-to-peer teammate messaging", () => {
79
79
  // Alice sends a message to bob
80
80
  const result = await messageTool.execute("call-1", { to: "bob", content: "I found 42 files" });
81
81
 
82
- // Message was stored
83
- const unread = getUnread(team, "bob");
84
- expect(unread.length).toBe(1);
85
- expect(unread[0].from).toBe("alice");
86
- expect(unread[0].content).toBe("I found 42 files");
82
+ // Message was stored and marked as read (forwarded via wakeTeammate)
83
+ expect(team.messages.length).toBe(1);
84
+ expect(team.messages[0].from).toBe("alice");
85
+ expect(team.messages[0].content).toBe("I found 42 files");
86
+ expect(team.messages[0].readBy.has("bob")).toBe(true);
87
+ expect(getUnread(team, "bob").length).toBe(0);
87
88
 
88
89
  // Bob was auto-woken: wakeTeammate called prompt() which resolves
89
90
  // instantly with our mock, so status cycles working → idle.
@@ -132,24 +133,25 @@ describe("peer-to-peer teammate messaging", () => {
132
133
 
133
134
  await messageTool.execute("call-4", { to: "all", content: "Step 1 done" });
134
135
 
135
- // Both bob and carol receive the message
136
- expect(getUnread(team, "bob").length).toBe(1);
137
- expect(getUnread(team, "carol").length).toBe(1);
138
-
139
- // Both were woken (they were idle)
136
+ // Both bob and carol were woken (they were idle)
140
137
  expect(bobPrompts.length).toBe(1);
141
138
  expect(carolPrompts.length).toBe(1);
142
139
 
143
- // Alice doesn't get her own broadcast in unread (she sent it)
144
- // Note: the broadcast IS stored with to="all", but alice wouldn't
145
- // read her own messages in practice
146
- expect(getUnread(team, "alice").length).toBe(1); // to="all" includes sender
140
+ // Messages marked as read for recipients who were forwarded the content
141
+ expect(getUnread(team, "bob").length).toBe(0);
142
+ expect(getUnread(team, "carol").length).toBe(0);
143
+
144
+ // Alice doesn't get her own broadcast forwarded (she sent it),
145
+ // but to="all" includes sender in the store
146
+ expect(getUnread(team, "alice").length).toBe(1);
147
147
  });
148
148
 
149
- it("message to working teammate is stored but doesn't re-prompt", async () => {
149
+ it("message to working teammate is queued as followUp", async () => {
150
150
  const team = freshTeam();
151
151
  const { mate: alice } = mockTeammate("alice");
152
152
  const { mate: bob, prompts: bobPrompts } = mockTeammate("bob", "working");
153
+ // Mark bob's session as streaming so wakeTeammate queues a followUp
154
+ (bob.session as unknown as { isStreaming: boolean }).isStreaming = true;
153
155
  team.teammates.set("alice", alice);
154
156
  team.teammates.set("bob", bob);
155
157
 
@@ -158,12 +160,13 @@ describe("peer-to-peer teammate messaging", () => {
158
160
 
159
161
  await messageTool.execute("call-5", { to: "bob", content: "update for you" });
160
162
 
161
- // Message stored
162
- expect(getUnread(team, "bob").length).toBe(1);
163
+ // Message stored and marked as read (forwarded via followUp)
164
+ expect(team.messages.length).toBe(1);
165
+ expect(team.messages[0].readBy.has("bob")).toBe(true);
163
166
 
164
- // Bob not re-woken (already working, wakeTeammate skips idle check in tool)
165
- // The tool only wakes idle recipients
166
- expect(bobPrompts.length).toBe(0);
167
+ // Bob received the message as a followUp (mock records both prompt and followUp)
168
+ expect(bobPrompts.length).toBe(1);
169
+ expect(bobPrompts[0]).toContain("update for you");
167
170
  });
168
171
 
169
172
  it("message to nonexistent teammate still stores message", async () => {
@@ -194,15 +197,17 @@ describe("peer-to-peer teammate messaging", () => {
194
197
  const aliceMsg = findTool(aliceTools, "team_message");
195
198
  const bobMsg = findTool(bobTools, "team_message");
196
199
 
197
- // Alice → Bob
200
+ // Alice → Bob (bob is idle, gets woken)
198
201
  await aliceMsg.execute("c1", { to: "bob", content: "Found 42 files" });
199
202
  expect(bobPrompts.length).toBe(1);
200
203
 
201
- // Simulate bob processing and responding
202
- bob.status = "working"; // bob is now working from the wake
204
+ // After wakeTeammate resolves (mock is instant), bob goes idle.
205
+ // Simulate bob being in working state for the next message.
206
+ bob.status = "working";
207
+ (bob.session as unknown as { isStreaming: boolean }).isStreaming = true;
203
208
  alice.status = "idle"; // alice finished her task
204
209
 
205
- // Bob → Alice
210
+ // Bob → Alice (alice is idle, gets woken)
206
211
  await bobMsg.execute("c2", { to: "alice", content: "Thanks, I need the list" });
207
212
  expect(alicePrompts.length).toBe(1);
208
213
 
@@ -1,7 +1,6 @@
1
1
  import type { KeybindingsManager, Theme } from "@mariozechner/pi-coding-agent";
2
2
  import { CustomEditor } from "@mariozechner/pi-coding-agent";
3
3
  import {
4
- BorderedBox,
5
4
  type EditorTheme,
6
5
  Key,
7
6
  matchesKey,
@@ -10,6 +9,7 @@ import {
10
9
  visibleWidth,
11
10
  } from "@mariozechner/pi-tui";
12
11
  import { getIcon, getSpinner } from "../_icons/index.js";
12
+ import { renderBorderedBox } from "../_shared/bordered-box.js";
13
13
  import {
14
14
  formatIdentityText,
15
15
  formatPresentationText,
@@ -927,11 +927,10 @@ export class TeamDashboardEditor extends CustomEditor {
927
927
  ): string[] {
928
928
  if (width <= 0) return [];
929
929
  if (team.teammates.length === 0) {
930
- const empty = new BorderedBox(["No teammates yet."], {
930
+ return renderBorderedBox(["No teammates yet."], width, {
931
931
  borderColorFn: (str) => this.colorTheme.fg("borderMuted", str),
932
932
  title: "empty",
933
933
  });
934
- return empty.render(width);
935
934
  }
936
935
 
937
936
  const columns = calculateDashboardGridColumns(width);
@@ -1013,14 +1012,13 @@ export class TeamDashboardEditor extends CustomEditor {
1013
1012
  ...outputLines.map((line) => ` ${formatDashboardRole(t, "process_output", line)}`),
1014
1013
  ];
1015
1014
 
1016
- const box = new BorderedBox(body, {
1015
+ return renderBorderedBox(body, width, {
1017
1016
  borderColorFn: selected
1018
1017
  ? (str) => colorMemberText(str, teammate.name, true)
1019
1018
  : (str) => t.fg("borderMuted", str),
1020
1019
  title: `@${teammate.name}`,
1021
1020
  titleColorFn: (str) => colorMemberText(str, teammate.name, selected),
1022
1021
  });
1023
- return box.render(width);
1024
1022
  }
1025
1023
 
1026
1024
  /**
@@ -12,7 +12,7 @@ import {
12
12
  } from "../dashboard/state.js";
13
13
  import { getRuntimeTeam } from "../state/team-view.js";
14
14
  import type { Teammate } from "../state/types.js";
15
- import { getReadyTasks, getTeammatesByStatus, type Team } from "../store.js";
15
+ import { getReadyTasks, getTeammatesByStatus, getUnread, markRead, type Team } from "../store.js";
16
16
 
17
17
  /**
18
18
  * Check for ready (unblocked, unclaimed) tasks and idle teammates,
@@ -106,6 +106,23 @@ export function wakeTeammate(
106
106
  if (team) {
107
107
  refreshTeamView(team);
108
108
  autoDispatch(team, piEvents);
109
+
110
+ // Drain unread messages that arrived while working.
111
+ // team_message now queues follow-ups for working teammates,
112
+ // but messages can still slip through during status transitions.
113
+ if (mate.status === "idle") {
114
+ const unread = getUnread(team, mate.name);
115
+ if (unread.length > 0) {
116
+ markRead(team, mate.name);
117
+ const digest = unread.map((m) => `[${m.from}] ${m.content}`).join("\n\n");
118
+ wakeTeammate(
119
+ mate,
120
+ `You have ${unread.length} unread message(s) that arrived while you were busy:\n\n${digest}`,
121
+ teamName,
122
+ piEvents
123
+ );
124
+ }
125
+ }
109
126
  }
110
127
  }
111
128
  })
@@ -158,14 +158,18 @@ export function createTeammateTools(
158
158
  }),
159
159
  // biome-ignore lint/suspicious/noExplicitAny: ToolDefinition params inferred from TypeBox schema
160
160
  execute: async (_toolCallId: string, params: any) => {
161
- addTeamMessage(team, myName, params.to, params.content);
161
+ const msg = addTeamMessage(team, myName, params.to, params.content);
162
162
  appendDashboardFeedEvent(team.name, myName, params.to, params.content);
163
163
 
164
- // Auto-wake idle recipients
164
+ // Wake recipients — wakeTeammate handles all states:
165
+ // idle → prompt(), working/streaming → followUp(), shutdown/error → no-op
166
+ // Mark as read after forwarding: content was delivered via prompt/followUp,
167
+ // so the idle-transition drain won't re-deliver it.
165
168
  if (params.to === "all") {
166
169
  for (const [name, mate] of team.teammates) {
167
- if (name !== myName && mate.status === "idle") {
170
+ if (name !== myName) {
168
171
  wakeTeammate(mate, `Broadcast from ${myName}: ${params.content}`, team.name, piEvents);
172
+ msg.readBy.add(name);
169
173
  }
170
174
  }
171
175
  } else {
@@ -182,9 +186,8 @@ export function createTeammateTools(
182
186
  details: {},
183
187
  };
184
188
  }
185
- if (recipient.status === "idle") {
186
- wakeTeammate(recipient, `Message from ${myName}: ${params.content}`, team.name, piEvents);
187
- }
189
+ wakeTeammate(recipient, `Message from ${myName}: ${params.content}`, team.name, piEvents);
190
+ msg.readBy.add(params.to);
188
191
  }
189
192
 
190
193
  refreshTeamView(team as Team<Teammate>);
@@ -5,6 +5,9 @@ import weztermPaneControl, {
5
5
  buildWeztermPaneGuidance,
6
6
  executeWeztermAction,
7
7
  filterPanesToCurrentTab,
8
+ hasExplicitPaneRequest,
9
+ hasSensitiveOutputMention,
10
+ isPaneCreatingAction,
8
11
  unescapeText,
9
12
  type WeztermCliResult,
10
13
  type WeztermPaneInfo,
@@ -92,16 +95,45 @@ describe("wezterm-pane-control registration", () => {
92
95
  });
93
96
 
94
97
  describe("buildWeztermPaneGuidance", () => {
95
- it("includes pane usage, sending, and privacy guidance", () => {
98
+ it("includes bg_bash-first default, pane exceptions, sending, and privacy guidance", () => {
96
99
  const guidance = buildWeztermPaneGuidance(116);
97
100
  expect(guidance).toContain("WezTerm pane 116");
98
- expect(guidance).toContain("Interactive TTY");
99
- expect(guidance).toContain("just create the pane and run it");
100
- expect(guidance).toContain("ask_user_question");
101
+ // bg_bash-first default
102
+ expect(guidance).toContain("use bg_bash");
103
+ expect(guidance).toContain("bg_bash");
104
+ // Pane exceptions — exactly 2
105
+ expect(guidance).toContain("User explicitly requests it");
106
+ expect(guidance).toContain("Sensitive output");
107
+ // Anti-speculative clause
108
+ expect(guidance).toContain("Never open a pane speculatively");
109
+ // bg_bash handles everything
110
+ expect(guidance).toContain(
111
+ "bg_bash\nhandles dev servers, builds, watchers, TUI apps, progress bars"
112
+ );
113
+ // Sending commands
101
114
  expect(guidance).toContain("appending \\n");
115
+ // Privacy
102
116
  expect(guidance).toContain("Do NOT call read_text on that pane");
103
117
  expect(guidance).toContain("LLM must not consume secrets");
104
118
  });
119
+
120
+ it("lists exactly 2 numbered exceptions", () => {
121
+ const guidance = buildWeztermPaneGuidance(116);
122
+ const numberedLines = guidance.split("\n").filter((l) => /^\d+\.\s/.test(l));
123
+ expect(numberedLines.length).toBe(2);
124
+ });
125
+
126
+ it("does not contain removed over-permissive guidance", () => {
127
+ const guidance = buildWeztermPaneGuidance(116);
128
+ expect(guidance).not.toContain("Long-running process the user wants to visually monitor");
129
+ expect(guidance).not.toContain("Process that needs a proper shell environment");
130
+ expect(guidance).not.toContain("ask_user_question");
131
+ expect(guidance).not.toContain("just create the pane and run it");
132
+ // Removed TTY exception
133
+ expect(guidance).not.toContain("Interactive TTY required");
134
+ expect(guidance).not.toContain("curses/TUI interface");
135
+ expect(guidance).not.toContain("progress bars that require a real terminal");
136
+ });
105
137
  });
106
138
 
107
139
  describe("filterPanesToCurrentTab", () => {
@@ -378,3 +410,55 @@ describe("unescapeText", () => {
378
410
  expect(unescapeText("no escapes here")).toBe("no escapes here");
379
411
  });
380
412
  });
413
+
414
+ describe("isPaneCreatingAction", () => {
415
+ it("returns true for pane-creating actions", () => {
416
+ expect(isPaneCreatingAction("split")).toBe(true);
417
+ expect(isPaneCreatingAction("spawn_tab")).toBe(true);
418
+ expect(isPaneCreatingAction("move_to_tab")).toBe(true);
419
+ });
420
+
421
+ it("returns false for non-creating actions", () => {
422
+ expect(isPaneCreatingAction("list")).toBe(false);
423
+ expect(isPaneCreatingAction("read_text")).toBe(false);
424
+ expect(isPaneCreatingAction("send_text")).toBe(false);
425
+ expect(isPaneCreatingAction("close")).toBe(false);
426
+ expect(isPaneCreatingAction("focus")).toBe(false);
427
+ expect(isPaneCreatingAction("zoom")).toBe(false);
428
+ expect(isPaneCreatingAction("resize")).toBe(false);
429
+ });
430
+ });
431
+
432
+ describe("hasExplicitPaneRequest", () => {
433
+ it("returns true for explicit pane/tab requests", () => {
434
+ expect(hasExplicitPaneRequest("open a pane for the server")).toBe(true);
435
+ expect(hasExplicitPaneRequest("new tab please")).toBe(true);
436
+ expect(hasExplicitPaneRequest("split the terminal")).toBe(true);
437
+ expect(hasExplicitPaneRequest("use wezterm to show logs")).toBe(true);
438
+ expect(hasExplicitPaneRequest("spawn a tab for this")).toBe(true);
439
+ });
440
+
441
+ it("returns false for non-pane prompts", () => {
442
+ expect(hasExplicitPaneRequest("start the dev server")).toBe(false);
443
+ expect(hasExplicitPaneRequest("run the build")).toBe(false);
444
+ expect(hasExplicitPaneRequest("left align the text")).toBe(false);
445
+ expect(hasExplicitPaneRequest("watch for file changes")).toBe(false);
446
+ expect(hasExplicitPaneRequest("open the browser")).toBe(false);
447
+ });
448
+ });
449
+
450
+ describe("hasSensitiveOutputMention", () => {
451
+ it("returns true for sensitive output mentions", () => {
452
+ expect(hasSensitiveOutputMention("show me the api key")).toBe(true);
453
+ expect(hasSensitiveOutputMention("generate a token")).toBe(true);
454
+ expect(hasSensitiveOutputMention("display the password")).toBe(true);
455
+ expect(hasSensitiveOutputMention("fetch my credentials")).toBe(true);
456
+ expect(hasSensitiveOutputMention("read the secret from 1password")).toBe(true);
457
+ });
458
+
459
+ it("returns false for non-sensitive prompts", () => {
460
+ expect(hasSensitiveOutputMention("start the dev server")).toBe(false);
461
+ expect(hasSensitiveOutputMention("run the tests")).toBe(false);
462
+ expect(hasSensitiveOutputMention("build the project")).toBe(false);
463
+ });
464
+ });
@@ -84,6 +84,71 @@ interface ToolResult {
84
84
  isError?: boolean;
85
85
  }
86
86
 
87
+ /**
88
+ * WezTerm pane actions that create new panes or tabs.
89
+ * These are gated by the guardrail hook — blocked unless the user's prompt
90
+ * explicitly requests pane/tab creation.
91
+ */
92
+ export const PANE_CREATING_ACTIONS: ReadonlySet<string> = new Set([
93
+ "split",
94
+ "spawn_tab",
95
+ "move_to_tab",
96
+ ]);
97
+
98
+ /**
99
+ * Patterns that indicate the user explicitly wants pane/tab creation.
100
+ * Only unambiguous pane vocabulary — no generic words like "window", "left", "right".
101
+ */
102
+ export const EXPLICIT_PANE_REQUEST_PATTERNS: readonly RegExp[] = [
103
+ /\bwezterm\b/i,
104
+ /\bpane(?:s)?\b/i,
105
+ /\btab(?:s)?\b/i,
106
+ /\bsplit\b/i,
107
+ /\bspawn\b/i,
108
+ ];
109
+
110
+ /**
111
+ * Patterns that indicate sensitive output the LLM should not see.
112
+ * Pane creation is allowed for these so the user can view secrets directly.
113
+ */
114
+ export const SENSITIVE_OUTPUT_PATTERNS: readonly RegExp[] = [
115
+ /\bsecret(?:s)?\b/i,
116
+ /\btoken(?:s)?\b/i,
117
+ /\bpassword(?:s)?\b/i,
118
+ /\bapi[- ]?key(?:s)?\b/i,
119
+ /\bcredential(?:s)?\b/i,
120
+ ];
121
+
122
+ /**
123
+ * Check whether a wezterm_pane action creates a new pane or tab.
124
+ *
125
+ * @param action - The action string from tool parameters
126
+ * @returns True when the action would create a pane/tab
127
+ */
128
+ export function isPaneCreatingAction(action: string): boolean {
129
+ return PANE_CREATING_ACTIONS.has(action);
130
+ }
131
+
132
+ /**
133
+ * Check whether user prompt text contains an explicit pane/tab request.
134
+ *
135
+ * @param prompt - The user's prompt text
136
+ * @returns True when the prompt explicitly mentions panes/tabs
137
+ */
138
+ export function hasExplicitPaneRequest(prompt: string): boolean {
139
+ return EXPLICIT_PANE_REQUEST_PATTERNS.some((pattern) => pattern.test(prompt));
140
+ }
141
+
142
+ /**
143
+ * Check whether user prompt text mentions sensitive output.
144
+ *
145
+ * @param prompt - The user's prompt text
146
+ * @returns True when the prompt mentions secrets/tokens/passwords
147
+ */
148
+ export function hasSensitiveOutputMention(prompt: string): boolean {
149
+ return SENSITIVE_OUTPUT_PATTERNS.some((pattern) => pattern.test(prompt));
150
+ }
151
+
87
152
  const ACTIONS: readonly WeztermAction[] = [
88
153
  "list",
89
154
  "split",
@@ -424,6 +489,11 @@ function runOrThrow(runCli: WeztermCliRunner, args: readonly string[]): string {
424
489
  /**
425
490
  * Build system-prompt guidance for WezTerm pane control behavior.
426
491
  *
492
+ * Encodes a bg_bash-first policy: panes are the exception, not the default.
493
+ * Long-running processes (dev servers, watchers, builds) should use bg_bash.
494
+ * Panes are reserved for explicit user requests and sensitive output that the
495
+ * LLM must not see.
496
+ *
427
497
  * @param currentPaneId - Current WezTerm pane ID
428
498
  * @returns Guidance block appended to the system prompt
429
499
  */
@@ -435,16 +505,24 @@ export function buildWeztermPaneGuidance(currentPaneId: number): string {
435
505
  "Use the wezterm_pane tool to manage panes: split, close, focus, zoom, resize, send/read text, or spawn new tabs.",
436
506
  'Use action "list" to see panes in the current tab.',
437
507
  "",
438
- "## When to use a pane",
508
+ "## Default: use bg_bash, not panes",
509
+ "",
510
+ "For long-running processes (dev servers, watchers, builds, tests, compilers),",
511
+ "use `bg_bash` with `background: true`. Monitor output with `task_output`.",
512
+ "This is the default — do NOT open a pane unless one of the exceptions below applies.",
513
+ "",
514
+ "## When to open a pane (exceptions only)",
515
+ "",
516
+ "Open a pane or tab ONLY when one of these is true:",
439
517
  "",
440
- "Spawn a pane or tab when a command needs something bash/bg_bash cannot provide:",
441
- "- Interactive TTY (Rich progress bars, TUI apps, curses UIs, interactive prompts)",
442
- "- Long-running process the user wants to visually monitor in a separate pane",
443
- "- Process that needs a proper shell environment",
518
+ '1. **User explicitly requests it** they said "open a pane", "use a pane",',
519
+ ' "split", "new tab", or similar. Without an explicit request, do NOT open a pane.',
520
+ "2. **Sensitive output** secrets, tokens, passwords the LLM must not see.",
521
+ " Spawn the pane, send the command, do NOT read_text the output.",
444
522
  "",
445
- "When it is clear a command needs a TTY, just create the pane and run it do not ask.",
446
- "When it is ambiguous whether a pane is needed, use ask_user_question to let the user decide.",
447
- "For simple commands that work fine in bash/bg_bash, prefer those — do not over-use panes.",
523
+ "If uncertain, ALWAYS use bg_bash. Never open a pane speculativelybg_bash",
524
+ "handles dev servers, builds, watchers, TUI apps, progress bars, and",
525
+ "interactive prompts fine.",
448
526
  "",
449
527
  "## Sending commands",
450
528
  "",
@@ -713,4 +791,31 @@ export default function weztermPaneControl(pi: ExtensionAPI): void {
713
791
  systemPrompt: `${event.systemPrompt}\n\n${buildWeztermPaneGuidance(currentPaneId)}`,
714
792
  };
715
793
  });
794
+
795
+ // Track the current turn's user prompt for the guardrail hook.
796
+ // Updated via "input" event which fires before agent processing.
797
+ let currentTurnPrompt = "";
798
+
799
+ pi.on("input", async (event) => {
800
+ currentTurnPrompt = event.text ?? "";
801
+ });
802
+
803
+ // Guardrail: block pane-creating actions unless the user explicitly asked for panes
804
+ // or mentioned sensitive output. This is the enforcement mechanism — prompt guidance
805
+ // alone has failed repeatedly (see plan 186 history).
806
+ pi.on("tool_call", async (event) => {
807
+ if (event.toolName !== "wezterm_pane") return;
808
+
809
+ const params = event.input as { action?: string } | undefined;
810
+ if (!params?.action || !isPaneCreatingAction(params.action)) return;
811
+
812
+ if (hasExplicitPaneRequest(currentTurnPrompt)) return;
813
+ if (hasSensitiveOutputMention(currentTurnPrompt)) return;
814
+
815
+ return {
816
+ block: true,
817
+ reason:
818
+ "Pane creation blocked — no explicit pane/tab request detected in this turn. Use bg_bash for long-running processes. The user must say 'pane', 'tab', 'split', etc. to open a WezTerm pane.",
819
+ };
820
+ });
716
821
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dungle-scrubs/tallow",
3
- "version": "0.8.21",
3
+ "version": "0.8.23",
4
4
  "description": "An opinionated coding agent. Built on pi.",
5
5
  "piConfig": {
6
6
  "name": "tallow",
@@ -25,6 +25,8 @@
25
25
  "files": [
26
26
  "dist",
27
27
  "extensions",
28
+ "packages/tallow-tui",
29
+ "runtime",
28
30
  "schemas",
29
31
  "skills",
30
32
  "templates",
@@ -70,7 +72,7 @@
70
72
  "dependencies": {
71
73
  "@clack/prompts": "^1.0.0",
72
74
  "@dungle-scrubs/synapse": "0.1.6",
73
- "@mariozechner/pi-coding-agent": "^0.56.3",
75
+ "@mariozechner/pi-coding-agent": "^0.58.1",
74
76
  "@opentelemetry/api": "^1.9.0",
75
77
  "@sinclair/typebox": "0.34.48",
76
78
  "ai": "^6.0.86",
@@ -80,8 +82,8 @@
80
82
  },
81
83
  "devDependencies": {
82
84
  "@biomejs/biome": "2.4.2",
83
- "@mariozechner/pi-agent-core": "^0.56.3",
84
- "@mariozechner/pi-ai": "^0.56.3",
85
+ "@mariozechner/pi-agent-core": "^0.58.1",
86
+ "@mariozechner/pi-ai": "^0.58.1",
85
87
  "@mariozechner/pi-tui": "workspace:*",
86
88
  "@types/node": "25.2.3",
87
89
  "husky": "^9.1.7",
@@ -0,0 +1,51 @@
1
+ # @mariozechner/pi-tui (Tallow fork)
2
+
3
+ Tallow's fork of `@mariozechner/pi-tui` (v0.52.9). Adds customizable
4
+ loader, border styles, and input middleware — changes that require
5
+ modifying pi-tui internals.
6
+
7
+ ## What's different from upstream
8
+
9
+ ### Configurable Loader
10
+
11
+ `Loader` accepts optional `frames` and `intervalMs` via constructor
12
+ options. Static `Loader.defaultFrames` / `Loader.defaultIntervalMs`
13
+ let extensions set global defaults at session start.
14
+
15
+ ### Border styles
16
+
17
+ New `BorderStyle` interface with three presets: `SHARP` (┌┐└┘),
18
+ `ROUNDED` (╭╮╰╯), `FLAT` (horizontal rules only).
19
+ `BorderedBox` component wraps content in a full border with optional
20
+ title, padding, and color functions.
21
+
22
+ ### Input middleware
23
+
24
+ `TUI.addInputMiddleware(fn)` inserts a hook before `handleInput`
25
+ forwards to the focused component. Middleware returns `true` to
26
+ consume input. Used by the which-key overlay extension.
27
+
28
+ ## Upstream sync
29
+
30
+ Source extracted from `@mariozechner/pi-tui@0.52.9` source maps.
31
+
32
+ ### Modified files (conflict surface on sync)
33
+
34
+ | File | Change |
35
+ |------|--------|
36
+ | `components/loader.ts` | Constructor options, static defaults |
37
+ | `tui.ts` | Input middleware array + add/remove methods |
38
+
39
+ ### Added files (zero conflict)
40
+
41
+ | File | Purpose |
42
+ |------|---------|
43
+ | `border-styles.ts` | BorderStyle interface + presets |
44
+ | `components/bordered-box.ts` | Box component with configurable borders |
45
+
46
+ ### How to sync
47
+
48
+ 1. Extract new upstream source from source maps (same script)
49
+ 2. Diff against `src/` — conflicts only in modified files above
50
+ 3. Apply upstream changes, keep our additions
51
+ 4. Rebuild: `cd packages/tallow-tui && npm run build`
@@ -0,0 +1,48 @@
1
+ export interface AutocompleteItem {
2
+ value: string;
3
+ label: string;
4
+ description?: string;
5
+ }
6
+ export interface SlashCommand {
7
+ name: string;
8
+ description?: string;
9
+ getArgumentCompletions?(argumentPrefix: string): AutocompleteItem[] | null;
10
+ }
11
+ export interface AutocompleteProvider {
12
+ getSuggestions(lines: string[], cursorLine: number, cursorCol: number): {
13
+ items: AutocompleteItem[];
14
+ prefix: string;
15
+ } | null;
16
+ applyCompletion(lines: string[], cursorLine: number, cursorCol: number, item: AutocompleteItem, prefix: string): {
17
+ lines: string[];
18
+ cursorLine: number;
19
+ cursorCol: number;
20
+ };
21
+ }
22
+ export declare class CombinedAutocompleteProvider implements AutocompleteProvider {
23
+ private commands;
24
+ private basePath;
25
+ private fdPath;
26
+ constructor(commands?: (SlashCommand | AutocompleteItem)[], basePath?: string, fdPath?: string | null);
27
+ getSuggestions(lines: string[], cursorLine: number, cursorCol: number): {
28
+ items: AutocompleteItem[];
29
+ prefix: string;
30
+ } | null;
31
+ applyCompletion(lines: string[], cursorLine: number, cursorCol: number, item: AutocompleteItem, prefix: string): {
32
+ lines: string[];
33
+ cursorLine: number;
34
+ cursorCol: number;
35
+ };
36
+ private extractAtPrefix;
37
+ private extractPathPrefix;
38
+ private expandHomePath;
39
+ private getFileSuggestions;
40
+ private scoreEntry;
41
+ private getFuzzyFileSuggestions;
42
+ getForceFileSuggestions(lines: string[], cursorLine: number, cursorCol: number): {
43
+ items: AutocompleteItem[];
44
+ prefix: string;
45
+ } | null;
46
+ shouldTriggerFileCompletion(lines: string[], cursorLine: number, cursorCol: number): boolean;
47
+ }
48
+ //# sourceMappingURL=autocomplete.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autocomplete.d.ts","sourceRoot":"","sources":["../src/autocomplete.ts"],"names":[],"mappings":"AA2JA,MAAM,WAAW,gBAAgB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IAGrB,sBAAsB,CAAC,CAAC,cAAc,EAAE,MAAM,GAAG,gBAAgB,EAAE,GAAG,IAAI,CAAC;CAC3E;AAED,MAAM,WAAW,oBAAoB;IAGpC,cAAc,CACb,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GACf;QACF,KAAK,EAAE,gBAAgB,EAAE,CAAC;QAC1B,MAAM,EAAE,MAAM,CAAC;KACf,GAAG,IAAI,CAAC;IAIT,eAAe,CACd,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,gBAAgB,EACtB,MAAM,EAAE,MAAM,GACZ;QACF,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;KAClB,CAAC;CACF;AAGD,qBAAa,4BAA6B,YAAW,oBAAoB;IACxE,OAAO,CAAC,QAAQ,CAAsC;IACtD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAgB;gBAG7B,QAAQ,GAAE,CAAC,YAAY,GAAG,gBAAgB,CAAC,EAAO,EAClD,QAAQ,GAAE,MAAsB,EAChC,MAAM,GAAE,MAAM,GAAG,IAAW;IAO7B,cAAc,CACb,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GACf;QAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAqGvD,eAAe,CACd,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,gBAAgB,EACtB,MAAM,EAAE,MAAM,GACZ;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE;IAwF7D,OAAO,CAAC,eAAe;IAiBvB,OAAO,CAAC,iBAAiB;IA8BzB,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,kBAAkB;IAoI1B,OAAO,CAAC,UAAU;IAuBlB,OAAO,CAAC,uBAAuB;IAkD/B,uBAAuB,CACtB,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GACf;QAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAyBvD,2BAA2B,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;CAW5F"}