toga-ai 1.0.333 → 1.0.334
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.
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
| [ClickUp GitHub-tab Auto-linking & Ticket-id Branch Naming](features/clickup-github-autolink.md) | How ClickUp surfaces branches/PRs/commits in a ticket's **GitHub tab**, and the branch / PR-title naming convention that triggers it. | |
|
|
10
10
|
| [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 |
|
|
11
11
|
| [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 |
|
|
12
|
+
| [ClickUp Subtask Activity → Parent Opportunity/Epic Comments](features/clickup-subtask-activity.md) | Surfaces **subtask** progress, completion, and discussion on the top-level **Opportunity** or **Epic** it rolls up to, so a deal/project owner sees activity whe | worker2/Worker/Clickup/Subtask.php, worker2/Worker/Clickup.php, dbchanges2/Team/2026-07-14a - Add ClickupSubtaskActivity ledger.sql |
|
|
12
13
|
| [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 |
|
|
13
14
|
| [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 |
|
|
14
15
|
| [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,6 +15,7 @@ related:
|
|
|
15
15
|
- ../architecture.md
|
|
16
16
|
- ./creating-worker-actions.md
|
|
17
17
|
- ./clickup-connectivity-watchdog.md
|
|
18
|
+
- ./clickup-subtask-activity.md
|
|
18
19
|
---
|
|
19
20
|
|
|
20
21
|
## Summary
|
|
@@ -78,8 +79,11 @@ ids are discovered. Configure via the `DiscoverTaskTypes()` / `DiscoverIds()` ac
|
|
|
78
79
|
2. Each handler fetches task details via `_Worker_Clickup::getTaskDetails()` (a per-invocation
|
|
79
80
|
`static` cache shared across all handlers in one job), then guards on **space id** and
|
|
80
81
|
**top-level** (`parent` empty). The three routing handlers identify Epics/tasks
|
|
81
|
-
**structurally** (space + top-level).
|
|
82
|
-
|
|
82
|
+
**structurally** (space + top-level). The `enforceHubPlacement` guard was written to key on
|
|
83
|
+
`custom_item_id`, but the Task-Types ClickApp is **not reliably enabled** on the hub spaces
|
|
84
|
+
and `TASK_TYPE_EPIC_ID` / `TASK_TYPE_OPPORTUNITY_ID` remain `null` — so that guard is
|
|
85
|
+
effectively **inert** in practice; structural (space + top-level) identification is what
|
|
86
|
+
actually runs (see Gotchas, and `./clickup-subtask-activity.md`).
|
|
83
87
|
3. Custom fields are read by name via `extractCustomFields()` (one pass, last-non-null-wins,
|
|
84
88
|
trimmed) → mapped to a target list id → reconciled by `applyMultiListPlacement()`:
|
|
85
89
|
POST the target if absent, DELETE every other in-scope list (DELETE failures are caught
|
|
@@ -160,6 +164,7 @@ Read-only / setup actions, invoked via `curl -X POST https://worker.togahub.com/
|
|
|
160
164
|
`RESULT: PASS`), `RegisterOpportunityWebhook` and `RegisterHubGuardWebhooks` (one-time setup).
|
|
161
165
|
|
|
162
166
|
## Change history
|
|
167
|
+
- 2026-07-14 — Correction: `TASK_TYPE_EPIC_ID` / `TASK_TYPE_OPPORTUNITY_ID` are still `null` and the Task-Types ClickApp is not reliably enabled on the hub spaces, so `enforceHubPlacement`'s custom-Task-Type keying is inert; structural (space + top-level) identification is what actually runs. Cross-linked the new subtask-activity feature (same hub spaces/pipeline). (ajean)
|
|
163
168
|
- 2026-06-23 — Added the hub-placement guard (`enforceHubPlacement` on `taskCreated`/`taskMoved`): re-anchors misplaced Epics/Opportunities to their hub by custom Task Type + home space, backfills Business Unit/Project Category, and notifies. Added `DiscoverTaskTypes`, `RegisterHubGuardWebhooks`, `TestHubGuardMappings`. Corrected the earlier "Task Types not enabled" note. (ajean)
|
|
164
169
|
- 2026-06-09 — Documented ClickUp secondary multi-list routing (hybrid native-automation + worker design, three space handlers, self-trigger guard). (jcardinal)
|
|
165
170
|
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: ClickUp Subtask Activity → Parent Opportunity/Epic Comments
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: worker2
|
|
5
|
+
project: Worker
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: draft
|
|
9
|
+
updated: 2026-07-14
|
|
10
|
+
owners: [ajean]
|
|
11
|
+
files:
|
|
12
|
+
- worker2/Worker/Clickup/Subtask.php
|
|
13
|
+
- worker2/Worker/Clickup.php
|
|
14
|
+
- dbchanges2/Team/2026-07-14a - Add ClickupSubtaskActivity ledger.sql
|
|
15
|
+
related:
|
|
16
|
+
- ./clickup-project-routing.md
|
|
17
|
+
- ../architecture.md
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Summary
|
|
21
|
+
|
|
22
|
+
Surfaces **subtask** progress, completion, and discussion on the top-level **Opportunity**
|
|
23
|
+
or **Epic** it rolls up to, so a deal/project owner sees activity where they actually look.
|
|
24
|
+
On a `clickup` webhook for a subtask (a task with a non-empty `parent`) of event
|
|
25
|
+
`taskStatusUpdated` or `taskCommentPosted`, the worker walks the parent chain to the
|
|
26
|
+
top-level ancestor and, if that ancestor is an Opportunity (Opportunity/RFP Hub space
|
|
27
|
+
`90113928591`) or an Epic (Project Hub space `90113939341`), posts **one** comment on the
|
|
28
|
+
ancestor. Implemented in `_Worker_Clickup_Subtask` (extends `_Worker_Clickup`), fanned out
|
|
29
|
+
from a pre-switch block in `_Worker_Clickup::Webhook()`.
|
|
30
|
+
|
|
31
|
+
> **Status: not yet deployed / not live-verified.** PRs open: worker2 #110, dbchanges2 #427.
|
|
32
|
+
> The dbchanges2 migration must run **before or with** the worker deploy (the worker reserves
|
|
33
|
+
> a ledger row before every post).
|
|
34
|
+
|
|
35
|
+
## Key files / entry points
|
|
36
|
+
|
|
37
|
+
- `worker2/Worker/Clickup/Subtask.php` — `_Worker_Clickup_Subtask` (NEW), all subtask
|
|
38
|
+
fan-out / ancestor-walk / dedup / post logic.
|
|
39
|
+
- `worker2/Worker/Clickup.php` — a **pre-switch fan-out block** in `_Worker_Clickup::Webhook()`
|
|
40
|
+
detects a subtask event and dispatches `_Worker::runTask('Clickup/Subtask/Process',
|
|
41
|
+
['taskId', 'event', 'data' => history_items])`. The block is wrapped in a best-effort
|
|
42
|
+
`try/catch (Throwable)` and **deliberately does not `return`** — it falls through to the
|
|
43
|
+
existing `taskStatusUpdated` / `taskCommentPosted` switch, so sprint / work-type handling is
|
|
44
|
+
unaffected.
|
|
45
|
+
- `dbchanges2/Team/2026-07-14a - Add ClickupSubtaskActivity ledger.sql` (NEW) — creates the
|
|
46
|
+
`ClickUpSubtaskActivity` dedup ledger table in the **Team** DB.
|
|
47
|
+
|
|
48
|
+
## How it works
|
|
49
|
+
|
|
50
|
+
1. Webhook arrives → `_Worker_Clickup::Webhook()`. The pre-switch block short-circuits **early
|
|
51
|
+
on the subtask's own space** before any parent walk (a subtask lives in the same space as its
|
|
52
|
+
ancestor, so a non-hub space is discarded before spending API calls).
|
|
53
|
+
2. `Clickup/Subtask/Process` walks up the `parent` chain to the top-level (no-parent) ancestor
|
|
54
|
+
and confirms it is an Opportunity or Epic **structurally** — by home space id + top-level —
|
|
55
|
+
mirroring `handleEpicUpdate` / `handleOpportunityUpdate`. **Not** by custom Task Type: the
|
|
56
|
+
Task-Types ClickApp is not reliably enabled on the hub spaces (see Gotchas).
|
|
57
|
+
3. Triggers:
|
|
58
|
+
- **Status** — post when the subtask status moves into `in progress`, `roadblocked`, or
|
|
59
|
+
`on hold` (matched by **lowercase status name**, consistent with existing status
|
|
60
|
+
comparisons in `Worker/Clickup.php`), OR into any completed status (matched by **status
|
|
61
|
+
TYPE** in `done`/`closed` via the inherited `COMPLETE_STATUS_TYPES`).
|
|
62
|
+
- **Comment** — any user comment posted on the subtask is mirrored to the ancestor as
|
|
63
|
+
`"💬 <author> commented on subtask …"`.
|
|
64
|
+
4. Every post carries a trailing `(automated)` marker. Comments that already carry that marker
|
|
65
|
+
are **never** mirrored (loop guard).
|
|
66
|
+
5. Posts use `_Component_Api_Clickup::send('POST', '/task/{id}/comment',
|
|
67
|
+
['comment_text' => …, 'notify_all' => false])`; task lookups reuse
|
|
68
|
+
`_Worker_Clickup::getTaskDetails()` (in-process static cache);
|
|
69
|
+
`_Worker_Clickup_Project::RATE_LIMIT_DELAY_US` paces the parent walk.
|
|
70
|
+
|
|
71
|
+
### Dedup ledger (the authoritative guard)
|
|
72
|
+
|
|
73
|
+
Before posting, the worker **reserves a ledger row** (`INSERT` into `ClickUpSubtaskActivity`)
|
|
74
|
+
so the `UNIQUE(subtaskId, activityKey)` constraint — not a check-then-act window — is the
|
|
75
|
+
authoritative dedup guard against re-delivered webhooks. If the comment POST fails, the row is
|
|
76
|
+
**released** (`DELETE`) so a retry can re-attempt. The `activityKey` is:
|
|
77
|
+
- `status:<name>` for a watched named status,
|
|
78
|
+
- `status:completed` for a completion,
|
|
79
|
+
- `comment:<clickupCommentId>` for a mirrored comment — falling back to the history-item id,
|
|
80
|
+
then a per-event date, then `md5(text)`, so identical comment texts stay distinct.
|
|
81
|
+
|
|
82
|
+
Ledger access follows the `ClickUpHealthState` pattern from `_Worker_Clickup_Health`: raw
|
|
83
|
+
`_Query` on `_underscore::DB_TEAM` with `_Database::escape()`.
|
|
84
|
+
|
|
85
|
+
## Data model
|
|
86
|
+
|
|
87
|
+
`ClickUpSubtaskActivity` in the **Team** DB (dbchanges2 migration `2026-07-14a`):
|
|
88
|
+
`id`, `uuid`, `subtaskId VARCHAR(32)`, `activityKey VARCHAR(255)`, `dtPosted`, with
|
|
89
|
+
`UNIQUE(subtaskId, activityKey)`. All other state lives in ClickUp.
|
|
90
|
+
|
|
91
|
+
## Client variations
|
|
92
|
+
|
|
93
|
+
None — internal automation for Agilant's ClickUp workspace, uniform across clients.
|
|
94
|
+
|
|
95
|
+
## Gotchas / known issues
|
|
96
|
+
|
|
97
|
+
- **Ancestor identification is STRUCTURAL, not by Task Type.** The Task-Types ClickApp is not
|
|
98
|
+
reliably enabled on the hub spaces, and `_Worker_Clickup_Project`'s `TASK_TYPE_EPIC_ID` /
|
|
99
|
+
`TASK_TYPE_OPPORTUNITY_ID` remain `null` (inert). Identify Opportunities/Epics by home space
|
|
100
|
+
id + top-level, exactly like the routing handlers — do not rely on `custom_item_id` here.
|
|
101
|
+
- **Completion matches by status TYPE (`done`/`closed`), not name** — it survives label
|
|
102
|
+
changes. The other watched statuses match by lowercase **name** (`in progress`,
|
|
103
|
+
`roadblocked`, `on hold`).
|
|
104
|
+
- **Loop guard is the `(automated)` marker** — every automated post carries it, and any comment
|
|
105
|
+
carrying it is skipped on mirror. Removing/altering the marker text re-opens the mirror loop.
|
|
106
|
+
- **Fan-out never returns** — it falls through to the existing switch on purpose; keep it that
|
|
107
|
+
way or existing sprint/work-type handling breaks.
|
|
108
|
+
- **Migration ordering** — deploy the dbchanges2 `2026-07-14a` migration before/with the worker;
|
|
109
|
+
the worker reserves a ledger row on every post and will error without the table.
|
|
110
|
+
|
|
111
|
+
## Change history
|
|
112
|
+
- 2026-07-14 — Created (draft, not yet deployed): subtask status/completion/comment mirroring to the parent Opportunity/Epic via `_Worker_Clickup_Subtask`, guarded by the `ClickUpSubtaskActivity` UNIQUE-key dedup ledger. PRs worker2 #110 / dbchanges2 #427. (ajean)
|
|
113
|
+
|
|
114
|
+
## Related docs
|
|
115
|
+
|
|
116
|
+
- [ClickUp Project & Opportunity Multi-List Routing](./clickup-project-routing.md) — same hub spaces, same webhook pipeline, same structural identification.
|
|
117
|
+
- [Worker (worker2) Architecture](../architecture.md) — always-HTTP-200, commit-before-SQS worker contract.
|
package/knowledge/INDEX.md
CHANGED
|
@@ -18,7 +18,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
18
18
|
## 2.0 framework
|
|
19
19
|
|
|
20
20
|
- **_underscore** (_Underscore) _(framework core)_ — 31 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
|
|
21
|
-
- **worker2** (Worker) —
|
|
21
|
+
- **worker2** (Worker) — 29 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
|
|
22
22
|
- **api2** (API) — 10 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
|
|
23
23
|
- **dbchanges2** (Database Changes) _(framework core)_ — 3 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
|
|
24
24
|
- **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