@creezio/tasks 0.22.0

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 (106) hide show
  1. package/README.md +279 -0
  2. package/dist/.creezio-src-hash.json +4 -0
  3. package/dist/ai-task-agent.d.ts +47 -0
  4. package/dist/ai-task-agent.d.ts.map +1 -0
  5. package/dist/ai-task-agent.js +558 -0
  6. package/dist/ai-task-agent.js.map +1 -0
  7. package/dist/ai-task-runner.d.ts +78 -0
  8. package/dist/ai-task-runner.d.ts.map +1 -0
  9. package/dist/ai-task-runner.js +645 -0
  10. package/dist/ai-task-runner.js.map +1 -0
  11. package/dist/api-mount.d.ts +4 -0
  12. package/dist/api-mount.d.ts.map +1 -0
  13. package/dist/api-mount.js +91 -0
  14. package/dist/api-mount.js.map +1 -0
  15. package/dist/assistant-adapter.d.ts +7 -0
  16. package/dist/assistant-adapter.d.ts.map +1 -0
  17. package/dist/assistant-adapter.js +96 -0
  18. package/dist/assistant-adapter.js.map +1 -0
  19. package/dist/brand/config.d.ts +165 -0
  20. package/dist/brand/config.d.ts.map +1 -0
  21. package/dist/brand/config.js +39 -0
  22. package/dist/brand/config.js.map +1 -0
  23. package/dist/env-bridge.d.ts +12 -0
  24. package/dist/env-bridge.d.ts.map +1 -0
  25. package/dist/env-bridge.js +33 -0
  26. package/dist/env-bridge.js.map +1 -0
  27. package/dist/hono-routes.d.ts +8 -0
  28. package/dist/hono-routes.d.ts.map +1 -0
  29. package/dist/hono-routes.js +563 -0
  30. package/dist/hono-routes.js.map +1 -0
  31. package/dist/index.d.ts +29 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +21 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/kanban-service.d.ts +136 -0
  36. package/dist/kanban-service.d.ts.map +1 -0
  37. package/dist/kanban-service.js +580 -0
  38. package/dist/kanban-service.js.map +1 -0
  39. package/dist/mcp-host-tools.d.ts +59 -0
  40. package/dist/mcp-host-tools.d.ts.map +1 -0
  41. package/dist/mcp-host-tools.js +326 -0
  42. package/dist/mcp-host-tools.js.map +1 -0
  43. package/dist/mcp-workspace-tools.d.ts +15 -0
  44. package/dist/mcp-workspace-tools.d.ts.map +1 -0
  45. package/dist/mcp-workspace-tools.js +338 -0
  46. package/dist/mcp-workspace-tools.js.map +1 -0
  47. package/dist/memory-store.d.ts +3 -0
  48. package/dist/memory-store.d.ts.map +1 -0
  49. package/dist/memory-store.js +75 -0
  50. package/dist/memory-store.js.map +1 -0
  51. package/dist/sqlite-driver.d.ts +13 -0
  52. package/dist/sqlite-driver.d.ts.map +1 -0
  53. package/dist/sqlite-driver.js +13 -0
  54. package/dist/sqlite-driver.js.map +1 -0
  55. package/dist/sqlite-store.d.ts +15 -0
  56. package/dist/sqlite-store.d.ts.map +1 -0
  57. package/dist/sqlite-store.js +113 -0
  58. package/dist/sqlite-store.js.map +1 -0
  59. package/dist/task-runs.d.ts +108 -0
  60. package/dist/task-runs.d.ts.map +1 -0
  61. package/dist/task-runs.js +427 -0
  62. package/dist/task-runs.js.map +1 -0
  63. package/dist/types.d.ts +35 -0
  64. package/dist/types.d.ts.map +1 -0
  65. package/dist/types.js +15 -0
  66. package/dist/types.js.map +1 -0
  67. package/dist-cjs/ai-task-agent.js +569 -0
  68. package/dist-cjs/ai-task-agent.js.map +1 -0
  69. package/dist-cjs/ai-task-runner.js +660 -0
  70. package/dist-cjs/ai-task-runner.js.map +1 -0
  71. package/dist-cjs/api-mount.js +94 -0
  72. package/dist-cjs/api-mount.js.map +1 -0
  73. package/dist-cjs/assistant-adapter.js +99 -0
  74. package/dist-cjs/assistant-adapter.js.map +1 -0
  75. package/dist-cjs/brand/config.js +47 -0
  76. package/dist-cjs/brand/config.js.map +1 -0
  77. package/dist-cjs/env-bridge.js +36 -0
  78. package/dist-cjs/env-bridge.js.map +1 -0
  79. package/dist-cjs/hono-routes.js +566 -0
  80. package/dist-cjs/hono-routes.js.map +1 -0
  81. package/dist-cjs/index.js +117 -0
  82. package/dist-cjs/index.js.map +1 -0
  83. package/dist-cjs/kanban-service.js +601 -0
  84. package/dist-cjs/kanban-service.js.map +1 -0
  85. package/dist-cjs/mcp-host-tools.js +333 -0
  86. package/dist-cjs/mcp-host-tools.js.map +1 -0
  87. package/dist-cjs/mcp-workspace-tools.js +342 -0
  88. package/dist-cjs/mcp-workspace-tools.js.map +1 -0
  89. package/dist-cjs/memory-store.js +81 -0
  90. package/dist-cjs/memory-store.js.map +1 -0
  91. package/dist-cjs/package.json +3 -0
  92. package/dist-cjs/sqlite-driver.js +16 -0
  93. package/dist-cjs/sqlite-driver.js.map +1 -0
  94. package/dist-cjs/sqlite-store.js +119 -0
  95. package/dist-cjs/sqlite-store.js.map +1 -0
  96. package/dist-cjs/task-runs.js +457 -0
  97. package/dist-cjs/task-runs.js.map +1 -0
  98. package/dist-cjs/types.js +18 -0
  99. package/dist-cjs/types.js.map +1 -0
  100. package/package.json +79 -0
  101. package/scripts/port-from-tempoflow.mjs +405 -0
  102. package/ui/ai-activity-panel.tsx +559 -0
  103. package/ui/index.ts +7 -0
  104. package/ui/task-detail-sheet.tsx +668 -0
  105. package/ui/tasks-kanban-client.tsx +647 -0
  106. package/ui/tasks-types.ts +120 -0
package/README.md ADDED
@@ -0,0 +1,279 @@
1
+ # @creezio/tasks
2
+
3
+ ## Rôle
4
+
5
+ `@creezio/tasks` fournit les tâches plateforme Creezio :
6
+
7
+ - store mince `PlatformTasksStore` pour `core.db` / `api-kernel` ;
8
+ - kanban unifié `human` / `ai` / `hermes` ;
9
+ - synchronisation Hermes kanban ;
10
+ - runs IA (`task_runs`) avec logs, HITL, quotas et runner host ;
11
+ - adapter assistant pour `create_task` / `list_tasks` ;
12
+ - outils MCP host-only pour piloter les collaborateurs IA ;
13
+ - UI kanban (`TasksKanbanClient`, `TaskDetailSheet`, `AiActivityPanel`).
14
+
15
+ Ce package est distinct des `PluginTaskRecord` du Product Hub.
16
+
17
+ ## Périmètre kit vs marque
18
+
19
+ **Kit**
20
+
21
+ - Définit les statuts, exécutants, invariants d'assignation et opérations kanban.
22
+ - Gère les runs IA, logs agent, SSE, relance, annulation et human-in-the-loop.
23
+ - Fournit `createTasksHonoRoutes()` et `createTasksApiMount()`.
24
+ - Fournit les tools MCP host `list_ai_collaborators`, `create_ai_task`, `get_ai_task`, `get_ai_run_logs`, `answer_ai_question`.
25
+ - Fournit les tools MCP **workspace** (`mcp-workspace-tools.ts`, câblés prod
26
+ via `@creezio/app-runtime` — architecture « Hermes cerveau / task runner
27
+ mains ») : `workspace.open_tab`, `workspace.list_tabs`,
28
+ `workspace.web_list_targets|click|type|scroll|read|screenshot`
29
+ (`ai_user_id` obligatoire — l'action s'exécute dans le workspace du
30
+ collaborateur IA, garde allowlist `*_WEB_ALLOWED_HOSTS` en UX + défense
31
+ en profondeur host `web_host_not_allowed`), et le HITL asynchrone
32
+ `platform.ask_human` / `platform.get_human_answer` (tâche kanban + run
33
+ HITL détaché).
34
+ - Fournit l'adapter assistant `createAssistantTasksAdapter()`.
35
+
36
+ **Marque**
37
+
38
+ - Appelle `configureTasksBrand()` au boot.
39
+ - Fournit DB, users, présence desktop, workspace Electron, navigation/permissions, external tabs, screencast et auth.
40
+ - Monte les routes Hono sous `/api/v1/tasks`.
41
+ - Monte le store `ApiMount` si elle expose la surface platform-core.
42
+ - Configure les variables d'environnement préfixées pour le runner IA.
43
+ - Branche les tools MCP dans son serveur host.
44
+
45
+ ## Installation/build
46
+
47
+ ```bash
48
+ npm run build -w @creezio/tasks
49
+ npm run typecheck -w @creezio/tasks
50
+ ```
51
+
52
+ Exports :
53
+
54
+ - `@creezio/tasks` : store, config, kanban, runner, Hono, MCP.
55
+ - `@creezio/tasks/ui` : kanban, sheet détail et activité IA.
56
+
57
+ ## Configuration détaillée
58
+
59
+ ### `configureTasksBrand`
60
+
61
+ ```ts
62
+ import { configureTasksBrand } from "@creezio/tasks";
63
+
64
+ configureTasksBrand({
65
+ productName: "Ma Marque",
66
+ productDomain: "CRM métier",
67
+ hermesSourceLabel: "Ma Marque CRM",
68
+ hermesSkill: "brand-crm",
69
+ envPrefix: "BRAND_AI",
70
+ idempotencyPrefix: "crm",
71
+ assistantIdempotencyPrefix: "asst",
72
+ taskHref: "/taches",
73
+ examplePaths: ["/clients", "/dashboard"],
74
+ db,
75
+ users,
76
+ presence,
77
+ workspace,
78
+ navigation,
79
+ externalTabs,
80
+ screencast,
81
+ auth,
82
+ });
83
+ ```
84
+
85
+ Bindings requis :
86
+
87
+ - `db` : `getWriteDb`, `queryAll`, `queryOne`, `tableExists`.
88
+ - `users` : `getById`, `list`, `getOwner`, `ready`.
89
+ - `presence` : état desktop et bridges en ligne.
90
+ - `workspace` : création/navigation workspace IA, tabs externes, web actions, screencast.
91
+ - `navigation` : permission requise par chemin et vérification ACL.
92
+ - `externalTabs` : résolution d'URL/site externe vers workspace params.
93
+ - `screencast` : viewer count et subscription frames.
94
+ - `auth` : session depuis Hono et helpers owner/impersonation.
95
+
96
+ ### Env runner IA
97
+
98
+ Le préfixe vient de `envPrefix`. Pour `envPrefix: "BRAND_AI"`, le package lit notamment :
99
+
100
+ - `BRAND_AI_RUNNER_ENABLED=0|false|no` pour désactiver le runner ;
101
+ - `BRAND_AI_MAX_RUNS_PER_DAY` ;
102
+ - `BRAND_AI_MAX_TOKENS_PER_DAY` ;
103
+ - `BRAND_AI_HITL=1|true` pour forcer le human-in-the-loop ;
104
+ - variables modèle/limites utilisées par `ai-task-agent` via `tasksEnv` / `tasksEnvNumber`.
105
+
106
+ Les valeurs non configurées utilisent les defaults du kit ou restent illimitées selon le cas.
107
+
108
+ ### Hermes
109
+
110
+ Le kanban Hermes utilise les fonctions de `@creezio/assistant` (`hermesKanbanConfigured`, create/list/patch/dispatch/cron). Les tâches `executorKind: "hermes"` n'ont pas d'assigné local et peuvent être dispatchées immédiatement.
111
+
112
+ ### Assistant brand binding
113
+
114
+ ```ts
115
+ import { configureAssistantBrand } from "@creezio/assistant";
116
+ import { createAssistantTasksAdapter } from "@creezio/tasks";
117
+
118
+ configureAssistantBrand({
119
+ // autres bindings...
120
+ tasks: createAssistantTasksAdapter(),
121
+ });
122
+ ```
123
+
124
+ ## API publique avec exemples
125
+
126
+ ### Kanban service
127
+
128
+ ```ts
129
+ import {
130
+ createTask,
131
+ listTasks,
132
+ tasksByColumn,
133
+ updateTask,
134
+ } from "@creezio/tasks";
135
+
136
+ const { task } = await createTask({
137
+ title: "Préparer la relance client",
138
+ body: "Analyser les échanges et proposer un message.",
139
+ executorKind: "ai",
140
+ assigneeUserId: "user_ai_1",
141
+ createdBy: "owner_1",
142
+ source: "ui",
143
+ });
144
+
145
+ await updateTask(task.id, { status: "in_progress" });
146
+
147
+ const board = tasksByColumn(listTasks({ includeCancelled: false }));
148
+ ```
149
+
150
+ ### Routes Hono
151
+
152
+ ```ts
153
+ import { Hono } from "hono";
154
+ import { createTasksHonoRoutes } from "@creezio/tasks";
155
+
156
+ const api = new Hono();
157
+ api.route("/tasks", createTasksHonoRoutes());
158
+ ```
159
+
160
+ Endpoints sous `/tasks` :
161
+
162
+ - `GET /meta`
163
+ - `GET /`
164
+ - `POST /sync`
165
+ - `GET /:id`
166
+ - `POST /`
167
+ - `PATCH /:id`
168
+ - `DELETE /:id`
169
+ - `POST /:id/launch`
170
+ - `GET /runs/:runId`, `/runs/:runId/logs`, `/runs/:runId/stream`
171
+ - `POST /runs/:runId/retry`, `/resume`, `/cancel`
172
+ - `GET /activity/:userId`, `/activity/:userId/stream`
173
+ - `GET /screencast/:aiUserId/stream`
174
+ - `POST /runner/tick`
175
+ - `POST /logs/purge`
176
+
177
+ ### `ApiMount` platform-core
178
+
179
+ ```ts
180
+ import { createTasksApiMount, createSqliteTasksStore } from "@creezio/tasks";
181
+
182
+ const store = createSqliteTasksStore({ db });
183
+ const mount = createTasksApiMount(store);
184
+ ```
185
+
186
+ Surface mince :
187
+
188
+ - `GET /list` ou `GET /`
189
+ - `POST /create` ou `POST /`
190
+ - `GET|PATCH|DELETE /:uuid`
191
+
192
+ L'acteur vient de `x-creezio-user-id` ou `body.userId`.
193
+
194
+ ### Runner IA
195
+
196
+ ```ts
197
+ import {
198
+ enqueueAiRunForTask,
199
+ ensureAiRunnerLoop,
200
+ processAiTaskQueue,
201
+ } from "@creezio/tasks";
202
+
203
+ const run = enqueueAiRunForTask("task_id");
204
+ ensureAiRunnerLoop();
205
+ void processAiTaskQueue();
206
+ ```
207
+
208
+ ### MCP host tools
209
+
210
+ ```ts
211
+ import { createAiTaskHostMcpTools } from "@creezio/tasks";
212
+
213
+ const registered = createAiTaskHostMcpTools({
214
+ registerTool: server.registerTool.bind(server),
215
+ getActorUserId: () => currentApiKey.user_id,
216
+ includeListTasks: true,
217
+ });
218
+ ```
219
+
220
+ ### UI
221
+
222
+ ```tsx
223
+ import { TasksKanbanClient } from "@creezio/tasks/ui";
224
+
225
+ export default function TasksPage() {
226
+ return <TasksKanbanClient />;
227
+ }
228
+ ```
229
+
230
+ ## Flux
231
+
232
+ ### Création tâche
233
+
234
+ 1. L'UI, l'assistant ou MCP appelle `createTask`.
235
+ 2. Le kit valide l'exécutant :
236
+ - `human` : assigné humain si fourni ;
237
+ - `ai` : assigné IA actif obligatoire ;
238
+ - `hermes` : pas d'assigné local.
239
+ 3. La tâche est écrite dans le schéma local et upsertée dans le store plateforme.
240
+ 4. Si Hermes est choisi, le kit crée/synchronise la tâche Hermes.
241
+ 5. Si IA est choisie et lancée, un run est enfilé.
242
+
243
+ ### Runner IA
244
+
245
+ 1. `ensureAiRunnerLoop` démarre le poller.
246
+ 2. `claimNextQueuedRun` sélectionne un run.
247
+ 3. Le runner vérifie quotas, modèle LLM et bridge desktop host.
248
+ 4. Il ouvre/assure le workspace de l'IA avec les ACL du collaborateur.
249
+ 5. `runAiTaskAgent` exécute les étapes et écrit les logs.
250
+ 6. Le run finit `succeeded`, `failed`, `cancelled` ou attend HITL.
251
+
252
+ ### MCP host
253
+
254
+ 1. La marque enregistre les tools avec `createAiTaskHostMcpTools`.
255
+ 2. L'actor MCP doit être owner pour créer des tâches IA.
256
+ 3. `create_ai_task` crée et lance la tâche si demandé.
257
+ 4. `get_ai_task` / `get_ai_run_logs` servent au polling externe.
258
+ 5. `answer_ai_question` reprend un run HITL.
259
+
260
+ ## Intégration marques
261
+
262
+ - Appeler `configureTasksBrand` avant toute route, UI ou runner.
263
+ - Monter `createTasksHonoRoutes()` sous `/api/v1/tasks`.
264
+ - Brancher `createAssistantTasksAdapter()` dans `configureAssistantBrand`.
265
+ - Démarrer le runner uniquement sur le host serveur/desktop adéquat.
266
+ - Garder les permissions dans l'adapter `navigation`, pas dans le prompt.
267
+ - Exposer les tools MCP host seulement côté host, avec actor owner.
268
+ - Prévoir les migrations SQL `tasks` et `task_runs` avant d'ouvrir l'UI.
269
+
270
+ ## Dépendances
271
+
272
+ - Runtime : `@creezio/api-kernel`, `@creezio/assistant`, `@creezio/auth`, `@creezio/platform-core`, `@creezio/shell-ui`, `hono`, `zod`.
273
+ - Peer UI : `react`, `lucide-react`, `date-fns`, `sonner`.
274
+ - Intégrations : Hermes via `@creezio/assistant`, desktop workspace via bindings marque.
275
+
276
+ ## Voir aussi
277
+
278
+ - [AGENTS.md](./AGENTS.md)
279
+ - [docs/FILES.md](./docs/FILES.md)
@@ -0,0 +1,4 @@
1
+ {
2
+ "srcHash": "4bc58cadfa45713166a20fc7001cdda8cf2b57393e1642485a309bdebe68c357",
3
+ "generatedAt": "2026-08-31T18:14:50.895Z"
4
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Agent LLM d'un collaborateur IA — exécute une tâche du kanban dans SON
3
+ * workspace Electron (partition dédiée, fake-cursor visible via « Voir
4
+ * comme IA »), avec les ACL de SON persona (jamais celles de l'owner).
5
+ *
6
+ * Outils : navigation CRM, inspection/action UI (UiDriver), onglets web,
7
+ * délégation Hermes (sous-tâche), HITL, fin de tâche explicite (D4 :
8
+ * `done` seulement sur finish_task(success=true)).
9
+ */
10
+ import { type AgentLoopResult, type AgentModelCaller, type AgentStepEvent, type AgentTool } from "@creezio/assistant";
11
+ import { type TasksUser } from "./brand/config.js";
12
+ import { type Task } from "./kanban-service.js";
13
+ type PublicUser = TasksUser;
14
+ export declare function setAiTaskModelCaller(fn: AgentModelCaller | null): void;
15
+ /** Un modèle est disponible (BYOK OpenAI ou stub de test). */
16
+ export declare function hasAiAgentModel(): boolean;
17
+ export declare function aiTaskModel(): string;
18
+ export declare function aiTaskMaxSteps(): number;
19
+ export declare function aiTaskTimeoutMs(): number;
20
+ export declare function aiTaskMaxTokens(): number;
21
+ export declare function aiWebHostAllowed(url: string): {
22
+ ok: boolean;
23
+ error?: string;
24
+ };
25
+ export type AskHumanFn = (question: string) => Promise<{
26
+ outcome: "resumed" | "cancelled" | "timeout";
27
+ response?: string;
28
+ }>;
29
+ export type AiTaskAgentContext = {
30
+ task: Task;
31
+ assignee: PublicUser;
32
+ hostUserId: string;
33
+ runId: string;
34
+ askHuman: AskHumanFn;
35
+ onStep?: (ev: AgentStepEvent) => void;
36
+ signal?: AbortSignal;
37
+ };
38
+ export declare function buildAiTaskTools(ctx: AiTaskAgentContext): AgentTool[];
39
+ export type AiTaskAgentOutcome = {
40
+ loop: AgentLoopResult;
41
+ success: boolean;
42
+ summary: string;
43
+ };
44
+ /** Déroule la boucle LLM pour un run. Ne lève pas (statuts explicites). */
45
+ export declare function runAiTaskAgent(ctx: AiTaskAgentContext): Promise<AiTaskAgentOutcome>;
46
+ export {};
47
+ //# sourceMappingURL=ai-task-agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-task-agent.d.ts","sourceRoot":"","sources":["../src/ai-task-agent.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,SAAS,EACf,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAc,KAAK,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAE5D,KAAK,UAAU,GAAG,SAAS,CAAC;AAiE5B,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,gBAAgB,GAAG,IAAI,GAAG,IAAI,CAEtE;AAED,8DAA8D;AAC9D,wBAAgB,eAAe,IAAI,OAAO,CAGzC;AAED,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED,wBAAgB,cAAc,IAAI,MAAM,CAGvC;AAED,wBAAgB,eAAe,IAAI,MAAM,CAGxC;AAED,wBAAgB,eAAe,IAAI,MAAM,CAGxC;AAgBD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAgB7E;AAED,MAAM,MAAM,UAAU,GAAG,CACvB,QAAQ,EAAE,MAAM,KACb,OAAO,CAAC;IAAE,OAAO,EAAE,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAElF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,EAAE,UAAU,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,UAAU,CAAC;IACrB,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,cAAc,KAAK,IAAI,CAAC;IACtC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC;AA2FF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,kBAAkB,GAAG,SAAS,EAAE,CA4arE;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,eAAe,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,2EAA2E;AAC3E,wBAAsB,cAAc,CAClC,GAAG,EAAE,kBAAkB,GACtB,OAAO,CAAC,kBAAkB,CAAC,CAsB7B"}