toga-ai 1.0.219 → 1.0.221
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.
- package/knowledge/2.0/apps/_underscore/INDEX.md +1 -1
- package/knowledge/2.0/apps/_underscore/features/forecast-sale-import.md +50 -8
- package/knowledge/2.0/apps/worker2/INDEX.md +3 -0
- package/knowledge/2.0/apps/worker2/features/clickup-richtext-api.md +133 -0
- package/knowledge/2.0/apps/worker2/features/talos-meeting-notes-integration.md +97 -0
- package/knowledge/2.0/apps/worker2/workflows/ticket-to-pseudocode-planning.md +60 -0
- package/knowledge/INDEX.md +1 -1
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
| [Assortment Name Translation (AssortmentTranslations sidecar)](features/assortment-name-translation.md) | Serves Assortment (product-grouping) **names** in multiple languages by adding a per-language **sidecar** table `AssortmentTranslations`, reusing the platform's | _underscore/Model/Client/AssortmentTranslation.php, dbchanges2/Client/2026-06-26a - AssortmentTranslations.sql, dbchanges2/Core/2026-06-26a - AssortmentTranslationsRecord.sql, dbchanges2/Client/2026-06-26b - AssortmentTranslationsAcl.sql |
|
|
8
8
|
| [Carrier Shipping Labels (UPS/FedEx) & NetSuite Item Fulfillment](features/carrier-shipping-labels.md) | Backend mechanics behind TOGa Supply's Fulfill & Ship: buying a carrier label (UPS/FedEx), persisting it, and creating the NetSuite Item Fulfillment with tracki | _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Client/ItemFulfillments/TrackingNumber.php, _underscore/Component/Library/LabelPdf/LabelPdf.php, _underscore/Component/Library/Carriers/Ups/Ups.php, _underscore/Component/Library/Carriers/Fedex/Fedex.php, _underscore/Trait/Netsuite/ItemFulfillment.php, _underscore/Trait/Netsuite/SalesOrder.php, _underscore/Component/Library/NetSuite/NetSuite.php, _underscore/Model/Client/TrackingNumber.php, _underscore/Model/Client/ShippingMethod.php, _underscore/Model.php, _underscore/Cloud.php |
|
|
9
9
|
| [Client Email Template Sending](features/email-template-sending.md) | `_Model_Client_EmailTemplate` sends a stored, client-defined email template by UUID. | _underscore/Model/Client/EmailTemplate.php, _underscore/Model/Client/EmailTemplateOutgoingEmailAddress.php, _underscore/Email.php |
|
|
10
|
-
| [Forecast.Sales NetSuite import engine (real-time webhook)](features/forecast-sale-import.md) | Real-time importer that takes a NetSuite **sale** record and writes its lines into `Forecast.Sales` (the Forecast2 revenue table). | _underscore/Component/Forecast/SaleImport/SaleImport.php, _underscore/Component/Forecast/Db/Db.php, _underscore/Component/Api/Netsuite/Netsuite.php, worker2/Worker/Netsuite/Invoice.php, worker2/Worker/Netsuite/CashSale.php, worker2/Worker/Netsuite/CreditMemo.php, worker2/Worker/Netsuite/CashRefund.php, worker2/Worker/Netsuite/JournalEntry.php, worker2/Worker/Netsuite/Opportunity.php, worker2/Worker/Netsuite/SalesOrder.php, dbchanges2/Forecast/2026-06-26a - Add journalEntry to Sales transaction type enum.sql, test/@dave/test_invoice_lifecycle.php, test/@dave/test_je_lifecycle.php, test/@dave/test_creditmemo_lifecycle.php, test/@dave/test_cashsale_lifecycle.php, test/@dave/test_cashrefund_lifecycle.php, test/@dave/test_fetchrecord_routes.php, test/@dave/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js, test/@dave/NetSuite/api-message-queue/dev_ue_api_msg_queue_enqueue.js |
|
|
10
|
+
| [Forecast.Sales NetSuite import engine (real-time webhook)](features/forecast-sale-import.md) | Real-time importer that takes a NetSuite **sale** record and writes its lines into `Forecast.Sales` (the Forecast2 revenue table). | _underscore/Component/Forecast/SaleImport/SaleImport.php, _underscore/Component/Forecast/Db/Db.php, _underscore/Component/Api/Netsuite/Netsuite.php, worker2/Worker/Netsuite/Invoice.php, worker2/Worker/Netsuite/CashSale.php, worker2/Worker/Netsuite/CreditMemo.php, worker2/Worker/Netsuite/CashRefund.php, worker2/Worker/Netsuite/JournalEntry.php, worker2/Worker/Netsuite/Opportunity.php, worker2/Worker/Netsuite/SalesOrder.php, dbchanges2/Forecast/2026-06-26a - Add journalEntry to Sales transaction type enum.sql, test/@dave/test_invoice_lifecycle.php, test/@dave/test_je_lifecycle.php, test/@dave/test_creditmemo_lifecycle.php, test/@dave/test_cashsale_lifecycle.php, test/@dave/test_cashrefund_lifecycle.php, test/@dave/test_fetchrecord_routes.php, test/@dave/verify_je_classification.php, test/@dave/probe_je_accounts.php, test/@dave/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js, test/@dave/NetSuite/api-message-queue/dev_ue_api_msg_queue_enqueue.js |
|
|
11
11
|
| [_Model magic-field access (__get without __isset)](features/model-magic-field-access.md) | `_Model` exposes DB columns as "magic" properties via `__get()`, but it defines **no** `__isset()`. | _underscore/Model/Core/Model.php |
|
|
12
12
|
| [NetSuite REST Client (_Component_Api_Netsuite) — record writes & SuiteQL](features/netsuite-rest-client.md) | `_Component_Api_Netsuite` is the **2.0 `_underscore` NetSuite REST client** — the shared primitive every worker2/api2 NetSuite caller uses for record GETs, Suit | _underscore/Component/Api/Netsuite/Netsuite.php |
|
|
13
13
|
| [Per-Client Database Connections & the Local Logs Trap](features/per-client-database-connections.md) | When `_underscore` serves a request for a client it opens **three distinct per-client database connections**, not one. | _underscore/Database.php, _underscore/ApiRequest.php, _underscore/Model/Client/Logs/Api.php |
|
|
@@ -26,6 +26,8 @@ files:
|
|
|
26
26
|
- test/@dave/test_cashsale_lifecycle.php
|
|
27
27
|
- test/@dave/test_cashrefund_lifecycle.php
|
|
28
28
|
- test/@dave/test_fetchrecord_routes.php
|
|
29
|
+
- test/@dave/verify_je_classification.php
|
|
30
|
+
- test/@dave/probe_je_accounts.php
|
|
29
31
|
- test/@dave/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js
|
|
30
32
|
- test/@dave/NetSuite/api-message-queue/dev_ue_api_msg_queue_enqueue.js
|
|
31
33
|
related:
|
|
@@ -167,11 +169,25 @@ item-line upsert/reconcile machinery (`syncLines`/`guardedInsert`/`deleteRows`).
|
|
|
167
169
|
- Lines hitting a **revenue** or **cost** GL account are grouped by **(salesRep, item)**:
|
|
168
170
|
`revenue = Σ(credit − debit)` over revenue lines, `cost = Σ(debit − credit)` over cost
|
|
169
171
|
lines, `profit = revenue − cost`.
|
|
170
|
-
- **Account classification is
|
|
171
|
-
|
|
172
|
-
`
|
|
173
|
-
`
|
|
174
|
-
|
|
172
|
+
- **Account classification is an EXACT account-NUMBER allowlist** (sales-team-defined; this
|
|
173
|
+
*replaced* the old provisional `accttype`-bucket rule). Constants in
|
|
174
|
+
`_Component_Forecast_SaleImport`: `JE_REVENUE_ACCOUNT_NUMBERS = ['41100','41300','41500']`,
|
|
175
|
+
`JE_COST_ACCOUNT_NUMBERS = ['51100','51200']`. Any line whose account number is in neither
|
|
176
|
+
list is **ignored**. The helper `accountNumbers(accountIds)` (which **replaced**
|
|
177
|
+
`accountTypes()`) resolves each line account's `acctnumber` via SuiteQL
|
|
178
|
+
`SELECT id, acctnumber FROM account WHERE id IN (...)`, cached per run; matching is
|
|
179
|
+
**string membership of `acctnumber`**, not `accttype`.
|
|
180
|
+
- **Sub-accounts are EXCLUDED by design — exact match, never prefix.** This chart of accounts
|
|
181
|
+
has real traps that both a `accttype` rule AND a prefix match would mis-bucket: under **41300**
|
|
182
|
+
"Service Revenue" sit `41300.01` (Service Revenue-Agent, `accttype` Income), `41300.02`/`.03`
|
|
183
|
+
(Service Cost-Agent, `accttype` **Bank**), and `41300.04` (Service **Costs**-Agent, `accttype`
|
|
184
|
+
**Income** despite the cost name); **51200** has `51200.01` (COGS Service-Agent). Exact-number
|
|
185
|
+
matching sidesteps all of them.
|
|
186
|
+
- **Resolved account map** (number → internalId / name): `41100→245` Product Revenue,
|
|
187
|
+
`41300→339` Service Revenue, `41500→355` Pre-Sales Consulting; `51100→248` COGS Product,
|
|
188
|
+
`51200→249` COGS Service. Read-only resolver tool: `test/@dave/probe_je_accounts.php`.
|
|
189
|
+
- **Likely future one-line tweak:** if the Agent sub-accounts should count, add `41300.01` to
|
|
190
|
+
the revenue list and `51200.01` to the cost list.
|
|
175
191
|
- Group key uses `json_encode([salesRep, item])` (not a string-join) to stay collision-safe
|
|
176
192
|
once the fields go live — an empty-string separator would let `"5|" + null` collide.
|
|
177
193
|
|
|
@@ -281,6 +297,12 @@ live `_Component_Api_Netsuite::createRecord()` `#`-delimiter bug (still unfixed;
|
|
|
281
297
|
5. **delete** the record.
|
|
282
298
|
6. **verify** the local row is gone (`removeAll`).
|
|
283
299
|
|
|
300
|
+
> **Scope of the update step (what it does and does NOT cover):** the update step changes
|
|
301
|
+
> **only `tranDate`** — a tracked column, so it forces a real change-detection UPDATE while
|
|
302
|
+
> the amount stays unchanged. It does **NOT** exercise updates to revenue / customer /
|
|
303
|
+
> salesRep / item, nor line add/remove. A future tester verifying those must extend the
|
|
304
|
+
> harness; the current update-step assertion only proves the tracked-column UPDATE path.
|
|
305
|
+
|
|
284
306
|
Poll the **local** Forecast DB between steps:
|
|
285
307
|
`C:\xampp8\mysql\bin\mysql.exe -u root` → db **`forecast`**, table **`Sales`** (filter on the
|
|
286
308
|
test internalId, or customerId `2` / itemId `25`).
|
|
@@ -301,9 +323,16 @@ test internalId, or customerId `2` / itemId `25`).
|
|
|
301
323
|
- Item **103741** ("Test Other Charge for Sale") → local `forecast.Items` id **25**.
|
|
302
324
|
- Location **5** ("Main") — required as a **header-level** field on creditMemo/cashSale/cashRefund
|
|
303
325
|
and **line-level** on invoice.
|
|
304
|
-
- **JE fixture:** subsidiary **1**; accounts **245** (Product Revenue
|
|
305
|
-
(COGS Product
|
|
306
|
-
`reversalDate` so NetSuite auto-creates the reversal.
|
|
326
|
+
- **JE fixture:** subsidiary **1**; accounts **245** (Product Revenue, acctnumber **41100**),
|
|
327
|
+
**248** (COGS Product, **51100**), **215** (Clearing - CSS — ignored, neither revenue nor
|
|
328
|
+
cost); set a `reversalDate` so NetSuite auto-creates the reversal. Classification is now by
|
|
329
|
+
**account number** (allowlist), so any clearing/balance line lands outside both lists and is
|
|
330
|
+
ignored automatically.
|
|
331
|
+
- **Discriminating classification test — `test/@dave/verify_je_classification.php`:** posts one
|
|
332
|
+
balanced JE `+10/+20/+30` to `41100/41300/41500`, `−5/−7` to `51100/51200`, **plus `+100` to
|
|
333
|
+
the excluded sub-account `41300.01`**, plus a clearing balance line → local `Forecast.Sales`
|
|
334
|
+
aggregates to **revenue=60.00, profit=48.00** (NOT 160 — proving `41300.01` is excluded and the
|
|
335
|
+
exact-number rule is in force).
|
|
307
336
|
|
|
308
337
|
### Expected local revenue signs / row outcomes
|
|
309
338
|
- **invoice** → `+` (a **$0** invoice still writes **one** row, revenue `0.00` — the engine does
|
|
@@ -382,6 +411,19 @@ record is deleted in NetSuite.)
|
|
|
382
411
|
- The cron's sign handling is not portable here — see Sign convention.
|
|
383
412
|
|
|
384
413
|
## Change history
|
|
414
|
+
- 2026-06-26 — **Changed JE revenue/cost classification from `accttype` buckets to an EXACT
|
|
415
|
+
account-NUMBER allowlist** (sales-team-defined): `JE_REVENUE_ACCOUNT_NUMBERS =
|
|
416
|
+
{41100,41300,41500}`, `JE_COST_ACCOUNT_NUMBERS = {51100,51200}`; everything else ignored.
|
|
417
|
+
`accountNumbers()` (replaced `accountTypes()`) resolves each account's `acctnumber` via
|
|
418
|
+
SuiteQL, cached per run; match is exact-string, **never prefix** — sub-accounts are excluded
|
|
419
|
+
by design to dodge real chart-of-accounts traps (`41300.04` "Service Costs-Agent" is
|
|
420
|
+
`accttype` Income; `41300.02/.03` are `accttype` Bank). Resolved map: 41100→245, 41300→339,
|
|
421
|
+
41500→355, 51100→248, 51200→249. Verified with a discriminating balanced-JE test
|
|
422
|
+
(`test/@dave/verify_je_classification.php` → revenue=60.00/profit=48.00, proving `41300.01`
|
|
423
|
+
excluded); read-only resolver `test/@dave/probe_je_accounts.php`. Future one-liner: add
|
|
424
|
+
`41300.01`/`51200.01` if Agent sub-accounts should count. Also clarified the e2e harness
|
|
425
|
+
update step exercises **only `tranDate`** (a tracked column) — not revenue/customer/salesRep/
|
|
426
|
+
item or line add/remove. (dfranks)
|
|
385
427
|
- 2026-06-26 — **Documented the full e2e testing playbook + harness inventory to run cold**
|
|
386
428
|
(6-step create→verify→update→verify→delete→verify lifecycle; per-type harness action sets;
|
|
387
429
|
the two execution modes — WEBHOOK vs LOCAL-ONLY `sync` — fixtures, expected signs, prod
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
| [Worker (worker2) Architecture](architecture.md) | Worker (repo `worker2`) is an AWS Elastic Beanstalk **Worker Tier** application that processes background jobs. | worker2/Controller/Index.php, worker2/Worker/, worker2/LambdaFunctions/, _underscore/Worker.php |
|
|
6
6
|
| [ClickUp Connectivity Watchdog](features/clickup-connectivity-watchdog.md) | A cron watchdog that emails when the ClickUp integration looks disconnected during business hours. | worker2/Worker/Clickup/Health.php, worker2/Database/ClickupHealthWatchdog.sql |
|
|
7
7
|
| [ClickUp Project & Opportunity Multi-List Routing](features/clickup-project-routing.md) | Routes ClickUp tasks into the correct **secondary multi-list memberships** based on their custom-field values, via the `clickup` webhook. | worker2/Worker/Clickup/Project.php, worker2/Worker/Clickup.php |
|
|
8
|
+
| [ClickUp Rich-Text Custom Fields via Quill Delta (API)](features/clickup-richtext-api.md) | ClickUp custom text fields (type `text` and long-text) support rich formatting only through a **Quill Delta** written to the undocumented `value_richtext` key o | test/@dave/clickup_md2delta.js, .claude/skills/plan-ticket/scripts/clickup.js |
|
|
8
9
|
| [ClickUp Work Type Automation (Committed / Conditional / Stretch)](features/clickup-work-type-automation.md) | The ClickUp webhook handler (`_Worker_Clickup`) automatically maintains each task's **Work Type** custom field — `Committed`, `Conditional`, or `Stretch` — base | worker2/Worker/Clickup.php, worker2/Tests/Worker/ClickupWorkTypeTest.php |
|
|
9
10
|
| [Creating Worker Actions](features/creating-worker-actions.md) | How to add a new callable Worker action — a PHP class whose `public static` methods are invoked as background jobs (via webhook, cron, or `_Worker::runTask()`). | worker2/Worker/, worker2/Controller/Index.php, _underscore/Worker.php |
|
|
10
11
|
| [Elite Freshservice Sync (worker2)](features/elite-freshservice-sync.md) | `_Worker_Elite` processes Freshservice webhook events and syncs them into TOGA 2. | worker2/Worker/Elite.php, worker2/Config/dev-kmaramreddy-laptop.ini |
|
|
@@ -15,7 +16,9 @@
|
|
|
15
16
|
| [NetSuite → Forecast Open-Orders Sync (salesOrder webhook → OpenOrderItems)](features/netsuite-salesorder-open-orders-sync.md) | Webhook-driven, single-record port of the legacy open-orders importer (TRUE-79142). | worker2/Worker/Netsuite/SalesOrder.php, worker2/Worker/Netsuite.php, test/@dave/probe_salesorder_rest_shape.php, test/@dave/probe_open_order_lines.php, test/@dave/check_so_status.php, test/@dave/check_so_history.php, test/@dave/probe_so_rest_lines.php, test/@dave/probe_missing_oo_timing.php, test/@dave/probe_missing_oo_createdby.php, test/@dave/probe_drift_so_dates.php, test/@dave/probe_open_order_gating.php, worker/crons/toga2/forecast2/import_open_orders.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
|
|
16
17
|
| [DB-Driven Notification (Internal) Email](features/notification-email.md) | Internal/notification emails (merge-conflict alerts, ops notices — anything system-generated, not client-facing transactional mail) are sent through one worker | worker2/Worker/Notification/Email.php, _underscore/Model/Client/EmailTemplate.php, dbchanges2/Client/2026-06-23a - EmailTemplateWrapper.sql, dbchanges2/Client_True/2026-06-23a - EmailTemplateWrapper.sql |
|
|
17
18
|
| [Startech Webhook Handler (worker2)](features/startech-webhook-handler.md) | Receives inbound webhook events from Startech (Easeedesk) and creates or updates the corresponding ticket in TOGA 2.0. | worker2/Worker/Startech.php |
|
|
19
|
+
| [Talos (TOGa IQ) Meeting-Notes Integration & Token Auto-Refresh (consumer)](features/talos-meeting-notes-integration.md) | How a **dev tool / agent consumes Talos (TOGa IQ)** to query the team meeting-notes corpus programmatically. | .claude/skills/plan-ticket/scripts/talos.js |
|
|
18
20
|
| [Team Sprint Management & Reporting](features/team-sprint-management.md) | `_Worker_Team_Sprint` (file `Worker/Team/Sprint.php`) is the engine behind TOGA's internal **development-sprint process and reporting**. | worker2/Worker/Team/Sprint.php |
|
|
19
21
|
| [Teams Meeting Transcript Export](features/teams-transcript-export.md) | `_Worker_Team_Transcripts` (action `Team/Transcripts/Export`) polls Microsoft Graph for Teams meeting transcripts produced by a set of organizers, classifies ea | worker2/Worker/Team/Transcripts.php, worker2/Config/production.ini, worker2/Database/TeamsTranscriptExports.sql, dbchanges2/Core/2026-06-18a - Teams Transcript Export schedule.sql |
|
|
20
22
|
| [VAPI Webhook Handler (worker2 — AI-BDR end-of-call processing)](features/vapi-webhook-handler.md) | `_Worker_Vapi` ([worker2/Worker/Vapi.php](worker2/Worker/Vapi.php)) is the **PHP side of the AI-BDR call loop** — the webhook that receives VAPI's end-of-call r | worker2/Worker/Vapi.php, worker2/Worker/Ai/Bdr/Vapi.php |
|
|
21
23
|
| [WJE Freshservice Sync (worker2)](features/wje-freshservice-sync.md) | WJE ("WJE IT", helpdesk `wje.freshservice.com`) is a **Freshservice**-based help-desk client whose tickets, contacts, assets, groups, categories, and canned res | worker2/Worker/Wje.php, _underscore/Component/Api/Wje/Wje.php, _underscore/Model/Wje/Ticket.php, _underscore/Model/Wje/TicketNote.php, _underscore/Model/Wje/Contact.php, _underscore/Model/Wje/Unit.php, _underscore/Model/Wje/TicketTeam.php, _underscore/Model/Wje/TicketCategory.php, _underscore/Model/Wje/AssetType.php, _underscore/Model/Wje/PredefinedReply.php, library/app/api/wje.php, worker/crons/toga2/wje/import_supporting_records.php, worker/crons/toga2/wje/sync_togasupply_wje.php, worker/crons/notifications/reports/wje/wje_common.php, library/app/systemmonitor/wje.php, dbchanges2/Client_Wje/2024-10-04 - WjeOnboarding.sql |
|
|
24
|
+
| [Ticket → ClickUp Pseudocode Planning (Talos-grounded)](workflows/ticket-to-pseudocode-planning.md) | A repeatable procedure for turning a ClickUp ticket into a reviewed, formatted implementation plan posted back to the ticket's `📝 Pseudocode` custom field. | test/@dave/clickup_md2delta.js |
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: ClickUp Rich-Text Custom Fields via Quill Delta (API)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: worker2
|
|
5
|
+
project: Worker
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-26
|
|
10
|
+
owners: [dfranks]
|
|
11
|
+
files:
|
|
12
|
+
- test/@dave/clickup_md2delta.js
|
|
13
|
+
- .claude/skills/plan-ticket/scripts/clickup.js
|
|
14
|
+
related:
|
|
15
|
+
- ./netsuite-opportunity-sync.md
|
|
16
|
+
- ./clickup-project-routing.md
|
|
17
|
+
- ../workflows/ticket-to-pseudocode-planning.md
|
|
18
|
+
- ./talos-meeting-notes-integration.md
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Summary
|
|
22
|
+
|
|
23
|
+
ClickUp custom text fields (type `text` and long-text) support rich formatting only through a
|
|
24
|
+
**Quill Delta** written to the undocumented `value_richtext` key on the Set-Custom-Field-Value
|
|
25
|
+
endpoint. Markdown or HTML written to the documented `value` key renders as literal characters.
|
|
26
|
+
A companion `value` (plain-text fallback) must accompany every richtext write.
|
|
27
|
+
|
|
28
|
+
## How it works
|
|
29
|
+
|
|
30
|
+
### Endpoint
|
|
31
|
+
|
|
32
|
+
`POST https://api.clickup.com/api/v2/task/<taskId>/field/<fieldId>?custom_task_ids=true&team_id=<CLICKUP_TEAM_ID>`
|
|
33
|
+
|
|
34
|
+
Header: `Authorization: <CLICKUP_API_KEY>` — no `Bearer` prefix.
|
|
35
|
+
|
|
36
|
+
### Request body
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"value": "<plain-text fallback>",
|
|
41
|
+
"value_richtext": "{\"ops\":[...]}"
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`value_richtext` must be the Delta JSON **serialized to a string** — not a nested JSON object.
|
|
46
|
+
The outer JSON body is a normal JSON object; only `value_richtext`'s value is a JSON-encoded string.
|
|
47
|
+
|
|
48
|
+
### Quill Delta format rules
|
|
49
|
+
|
|
50
|
+
A Delta is `{"ops":[...]}`. Each op is `{"insert": "...", "attributes": {...}}`.
|
|
51
|
+
|
|
52
|
+
**Block-level formats** attach to the `\n` op that **terminates the block**; they apply back
|
|
53
|
+
to the previous newline. Block attribute keys:
|
|
54
|
+
|
|
55
|
+
| Format | Key | Values |
|
|
56
|
+
|--------|-----|--------|
|
|
57
|
+
| Heading | `header` | `1`, `2`, `3` |
|
|
58
|
+
| Bullet list | `list` | `"bullet"` |
|
|
59
|
+
| Ordered list | `list` | `"ordered"` |
|
|
60
|
+
| List indent | `indent` | `1`, `2`, … |
|
|
61
|
+
| Code block | `code-block` | `true` |
|
|
62
|
+
| Blockquote | `blockquote` | `true` |
|
|
63
|
+
|
|
64
|
+
**Inline formats** wrap the text `insert` directly:
|
|
65
|
+
|
|
66
|
+
| Format | Key | Value |
|
|
67
|
+
|--------|-----|-------|
|
|
68
|
+
| Bold | `bold` | `true` |
|
|
69
|
+
| Italic | `italic` | `true` |
|
|
70
|
+
| Inline code | `code` | `true` |
|
|
71
|
+
| Link | `link` | `"https://..."` |
|
|
72
|
+
|
|
73
|
+
**No native table op.** Convert markdown tables to bullet lists. Convert horizontal rules
|
|
74
|
+
(`---`) to a blank line — there is no `hr` op in ClickUp's Delta renderer.
|
|
75
|
+
|
|
76
|
+
Every Delta must end with a bare `{"insert": "\n"}` op.
|
|
77
|
+
|
|
78
|
+
### Minimal example — a heading followed by a bullet list
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"ops": [
|
|
83
|
+
{ "insert": "Overview" },
|
|
84
|
+
{ "insert": "\n", "attributes": { "header": 1 } },
|
|
85
|
+
{ "insert": "First item" },
|
|
86
|
+
{ "insert": "\n", "attributes": { "list": "bullet" } },
|
|
87
|
+
{ "insert": "Second item" },
|
|
88
|
+
{ "insert": "\n", "attributes": { "list": "bullet" } },
|
|
89
|
+
{ "insert": "\n" }
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Reusable converter
|
|
95
|
+
|
|
96
|
+
`test/@dave/clickup_md2delta.js` — converts a Markdown file to a Quill Delta and posts both
|
|
97
|
+
`value` and `value_richtext` to a ClickUp custom field.
|
|
98
|
+
|
|
99
|
+
```sh
|
|
100
|
+
node clickup_md2delta.js <md-path> <TASK_ID> <FIELD_UUID> [--dry]
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`--dry` prints the computed Delta without posting. Reads `CLICKUP_API_KEY` and
|
|
104
|
+
`CLICKUP_TEAM_ID` from the environment.
|
|
105
|
+
|
|
106
|
+
The converter was used to push a full implementation plan into the `📝 Pseudocode` custom
|
|
107
|
+
field (`ad0e4fc8-ff03-4e5f-a187-7d2cd6d0bc65`) on ticket TRUE-79868. The same mechanism is
|
|
108
|
+
reused by the `clickup.js` helper in the local `plan-ticket` skill — see the
|
|
109
|
+
[ticket-to-pseudocode planning workflow](../workflows/ticket-to-pseudocode-planning.md).
|
|
110
|
+
|
|
111
|
+
## API documentation status
|
|
112
|
+
|
|
113
|
+
ClickUp's official v2 API docs document only a plain-string `value` for text fields and list
|
|
114
|
+
markdown support as an unfulfilled feature request. The `value_richtext` write path is
|
|
115
|
+
**undocumented but empirically verified** (2026-06-26). It works for both `text` and
|
|
116
|
+
long-text field types.
|
|
117
|
+
|
|
118
|
+
## Tooling gotcha — PowerShell ConvertTo-Json
|
|
119
|
+
|
|
120
|
+
PowerShell 5.1 `ConvertTo-Json` pathologically inflates nested strings. A 21 KB
|
|
121
|
+
`value_richtext` string was inflated ~190x (to ~3.8 MB) due to recursive escape-doubling
|
|
122
|
+
of the inner JSON. Avoid `ConvertTo-Json` for ClickUp richtext payloads. Use one of:
|
|
123
|
+
|
|
124
|
+
- **Node `fetch`** — serialize the body with `JSON.stringify()` directly.
|
|
125
|
+
- **PowerShell + file** — write the JSON body to a UTF-8 file, then pass it to
|
|
126
|
+
`Invoke-RestMethod -InFile <path> -ContentType "application/json"`.
|
|
127
|
+
- **`System.Web.Script.Serialization.JavaScriptSerializer`** — correctly serializes a
|
|
128
|
+
pre-built string without re-escaping it.
|
|
129
|
+
|
|
130
|
+
## Change history
|
|
131
|
+
|
|
132
|
+
- 2026-06-26 — Cross-linked to the Talos meeting-notes integration and the ticket-to-pseudocode planning workflow; noted the `plan-ticket` skill reuses this richtext write path (dfranks)
|
|
133
|
+
- 2026-06-26 — Initial doc: Quill Delta richtext write path verified; converter tool documented; PowerShell inflation gotcha added (dfranks)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Talos (TOGa IQ) Meeting-Notes Integration & Token Auto-Refresh (consumer)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: worker2
|
|
5
|
+
project: Worker
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-26
|
|
10
|
+
owners: [dfranks]
|
|
11
|
+
files:
|
|
12
|
+
- .claude/skills/plan-ticket/scripts/talos.js
|
|
13
|
+
related:
|
|
14
|
+
- ../../talos/architecture.md
|
|
15
|
+
- ./clickup-richtext-api.md
|
|
16
|
+
- ../workflows/ticket-to-pseudocode-planning.md
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Summary
|
|
20
|
+
|
|
21
|
+
How a **dev tool / agent consumes Talos (TOGa IQ)** to query the team meeting-notes corpus
|
|
22
|
+
programmatically. Talos itself (the LangGraph Agent Protocol server) is documented under
|
|
23
|
+
[`2.0/apps/talos/`](../../talos/architecture.md); this doc is the **consumer side** — the
|
|
24
|
+
endpoints, assistant, and auth-refresh flow a worker2-side dev script needs to ground its
|
|
25
|
+
output in meeting notes.
|
|
26
|
+
|
|
27
|
+
**Key distinction:** the `[talos]` key in worker2 config only reaches the **stateless**
|
|
28
|
+
`/api/ai/generate` and `/api/ai/chat` endpoints, which have **no meeting-notes access**.
|
|
29
|
+
Meeting-notes access comes only from **assistants wired to knowledge bases**, reached through
|
|
30
|
+
the Agent Protocol `/threads` + `/threads/{id}/runs/wait` surface with a **user Bearer JWT**.
|
|
31
|
+
|
|
32
|
+
## How it works
|
|
33
|
+
|
|
34
|
+
### Server
|
|
35
|
+
|
|
36
|
+
- Agent Protocol (LangGraph) server: `https://api.togaiq.com`
|
|
37
|
+
- Web app (login / token source): `https://talos.togaiq.com`
|
|
38
|
+
|
|
39
|
+
### Assistants
|
|
40
|
+
|
|
41
|
+
Meeting-notes / knowledge-base queries go to a knowledge-wired **assistant**, not the
|
|
42
|
+
stateless generate endpoint.
|
|
43
|
+
|
|
44
|
+
| Assistant | ID | Use |
|
|
45
|
+
|-----------|-----|-----|
|
|
46
|
+
| **DevCore** | `a5b1833d-b47c-5cb3-8cda-963f1cc74a33` | dev + meeting notes (plan-and-execute agent) |
|
|
47
|
+
| Talos One | — | general |
|
|
48
|
+
| Talos Sales | — | sales |
|
|
49
|
+
| Talos HR | — | HR |
|
|
50
|
+
|
|
51
|
+
### Querying DevCore
|
|
52
|
+
|
|
53
|
+
1. `POST /threads` → returns a thread id.
|
|
54
|
+
2. `POST /threads/{id}/runs/wait` with the assistant id and the user message.
|
|
55
|
+
3. DevCore is a **plan-and-execute** agent that **interrupts for plan approval**
|
|
56
|
+
(agent-inbox schema): the response carries `__interrupt__` with `action: plan_approval`.
|
|
57
|
+
4. **Resume** the run with `command: { resume: [ { "type": "accept", "args": null } ] }`.
|
|
58
|
+
5. The **final answer** is the last item in `messages[]` whose `type` is `"ai"`.
|
|
59
|
+
|
|
60
|
+
### Authentication & token auto-refresh (verified)
|
|
61
|
+
|
|
62
|
+
JWTs are issued by `api-writer.togahub.com` with audience `talos.togaiq.com`. This is the
|
|
63
|
+
standard **TOGA 2.0 `/v2/auth/*` flow** (mirrors `api2/Component/Api/V2/V2.php`).
|
|
64
|
+
|
|
65
|
+
- **Access token** — 1 hour TTL.
|
|
66
|
+
- **Refresh token** — 30 day TTL and **rolls forward** on each refresh (a new refresh token
|
|
67
|
+
may be returned and must replace the stored one).
|
|
68
|
+
- **Refresh call:**
|
|
69
|
+
`POST https://api-writer.togahub.com/v2/auth/refresh?transactionId=<unique-uuid>`
|
|
70
|
+
Header: `Authorization: Bearer <refresh token>`
|
|
71
|
+
Response: `{ isSuccess, data: { tokens: { access [, refresh] } } }`
|
|
72
|
+
|
|
73
|
+
**Gotchas (both verified empirically 2026-06-26):**
|
|
74
|
+
|
|
75
|
+
- The **`/v2` path prefix is REQUIRED** — omitting it returns error **EV-2 "invalid version"**.
|
|
76
|
+
- `transactionId` must be **globally unique per call** — reusing one returns error **EV-5**.
|
|
77
|
+
|
|
78
|
+
The browser obtains the initial token pair via the `talos.togaiq.com` login. A developer can
|
|
79
|
+
copy `accessToken` + `refreshToken` out of the browser session to seed a local tool, after
|
|
80
|
+
which the tool refreshes on its own.
|
|
81
|
+
|
|
82
|
+
## Credential storage (location only)
|
|
83
|
+
|
|
84
|
+
The consumer tool stores its token pair at **`~/.talos/credentials.json`** (user home,
|
|
85
|
+
**outside any repo**). Never commit, echo, or paste the token values anywhere — they are
|
|
86
|
+
secrets. Document only this location.
|
|
87
|
+
|
|
88
|
+
## Consumer helper
|
|
89
|
+
|
|
90
|
+
`.claude/skills/plan-ticket/scripts/talos.js` — performs the auth refresh and runs a DevCore
|
|
91
|
+
query (create thread → run → accept plan interrupt → extract final `ai` message). It is part
|
|
92
|
+
of the **local** `plan-ticket` skill, not the team repo; its source is intentionally not
|
|
93
|
+
mirrored into the knowledge base.
|
|
94
|
+
|
|
95
|
+
## Change history
|
|
96
|
+
|
|
97
|
+
- 2026-06-26 — Initial doc: consumer-side Talos meeting-notes access via DevCore assistant + the `/v2/auth/refresh` token-rotation flow; stateless `[talos]` config vs. assistant distinction; EV-2/EV-5 gotchas; credential location `~/.talos/credentials.json` (dfranks)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Ticket → ClickUp Pseudocode Planning (Talos-grounded)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: worker2
|
|
5
|
+
project: Worker
|
|
6
|
+
client: shared
|
|
7
|
+
type: workflow
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-26
|
|
10
|
+
owners: [dfranks]
|
|
11
|
+
files:
|
|
12
|
+
- test/@dave/clickup_md2delta.js
|
|
13
|
+
related:
|
|
14
|
+
- ../features/clickup-richtext-api.md
|
|
15
|
+
- ../features/talos-meeting-notes-integration.md
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Summary
|
|
19
|
+
|
|
20
|
+
A repeatable procedure for turning a ClickUp ticket into a reviewed, formatted implementation
|
|
21
|
+
plan posted back to the ticket's `📝 Pseudocode` custom field. It spans three systems —
|
|
22
|
+
ClickUp (read ticket / write field), the codebase (investigation), and Talos DevCore
|
|
23
|
+
(meeting-notes grounding) — so it is a workflow, not a single feature.
|
|
24
|
+
|
|
25
|
+
It is implemented as a **local Claude skill** (`.claude/skills/plan-ticket/`) that is **not**
|
|
26
|
+
in the team repo. This doc records the *shape of the procedure* and the durable integration
|
|
27
|
+
mechanics; it does not duplicate the skill source. The two underlying integrations have their
|
|
28
|
+
own feature docs:
|
|
29
|
+
|
|
30
|
+
- [ClickUp rich-text custom fields via Quill Delta](../features/clickup-richtext-api.md) — how the plan reaches the `📝 Pseudocode` field.
|
|
31
|
+
- [Talos meeting-notes integration](../features/talos-meeting-notes-integration.md) — how the plan is grounded in meeting notes.
|
|
32
|
+
|
|
33
|
+
## Steps
|
|
34
|
+
|
|
35
|
+
1. **Fetch the ticket** by id from ClickUp (`custom_task_ids=true`, `team_id` from env).
|
|
36
|
+
2. **Investigate the codebase FIRST** — establish what already exists before asking Talos, so
|
|
37
|
+
the Talos query is code-informed rather than speculative.
|
|
38
|
+
3. **Query Talos DevCore** (assistant `a5b1833d-b47c-5cb3-8cda-963f1cc74a33`) for
|
|
39
|
+
meeting-notes context, passing the codebase findings as grounding. Accept the
|
|
40
|
+
plan-approval interrupt to get the final answer.
|
|
41
|
+
4. **Synthesize a ≤150-line phased plan**, saved to `test/@dave/approach/<TICKET>.md`.
|
|
42
|
+
5. **Preview & approval gate** — show the plan; iterate until the developer approves.
|
|
43
|
+
6. **Push to the ticket's `📝 Pseudocode` field** as a Quill Delta via the richtext write
|
|
44
|
+
path. The push is **overwrite-guarded**: it refuses to clobber an already-populated field
|
|
45
|
+
unless run with `--force`.
|
|
46
|
+
|
|
47
|
+
## Plan document format
|
|
48
|
+
|
|
49
|
+
`test/@dave/approach/<TICKET>.md` (see also the team convention to store approach plans
|
|
50
|
+
under `test/@dave/approach/`):
|
|
51
|
+
|
|
52
|
+
- Title heading.
|
|
53
|
+
- Bold header line: **Repos / Framework / Client / Sibling**.
|
|
54
|
+
- Sections, in order: **Summary** · **Meeting-notes context** · **What already exists** ·
|
|
55
|
+
**Architecture decision** · **Phases** (each with file paths + pseudocode) · **Testing** ·
|
|
56
|
+
**Risks** · **Owners / open questions** · **Key files**.
|
|
57
|
+
|
|
58
|
+
## Change history
|
|
59
|
+
|
|
60
|
+
- 2026-06-26 — Initial doc: ticket→pseudocode planning procedure (ClickUp read → codebase investigation → Talos DevCore grounding → phased plan → approval gate → guarded richtext push); records the shape, not the local skill source (dfranks)
|
package/knowledge/INDEX.md
CHANGED
|
@@ -17,7 +17,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
17
17
|
## 2.0 framework
|
|
18
18
|
|
|
19
19
|
- **_underscore** (_Underscore) _(framework core)_ — 17 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
|
|
20
|
-
- **worker2** (Worker) —
|
|
20
|
+
- **worker2** (Worker) — 20 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
|
|
21
21
|
- **api2** (API) — 7 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
|
|
22
22
|
- **dbchanges2** (Database Changes) _(framework core)_ — 3 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
|
|
23
23
|
- **toga2-supply** (TOGa Supply) — 3 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
|
package/package.json
CHANGED