Files
fusion/plugins/fusion-plugin-even-realities-glasses
gsxdsm dca20496f4 consolidate/u7: plugins to zero + 8 executor rebound guards + resume lanes (supersedes #2607, #2635, #2640) (#2644)
Consolidation branch for U7, per the new one-branch working mode.
**Supersedes #2607, #2635, #2640** — the three of my PRs that were stuck
on review threads. My other seven (#2602, #2605, #2606, #2611, #2621,
#2628, #2633) are green with **zero unresolved threads** and are
deliberately left alone for the merge sweep.

## What is in here, file by file

| file | change | guards before → after |
|---|---|---|
| `plugins/…/glasses/src/agent-actions.ts` | gates, destinations and
degraded-resolution refusal all resolve from the task's own workflow | 2
→ 0 |
| `plugins/…/glasses/src/quick-capture.ts` | accepted capture columns
come from the board; default no longer names the deleted column | 1 → 0
|
| `plugins/…/glasses/src/settings.ts` | quick-capture default was
`triage`, the column #2515 removed | (assignment, uncounted) |
| `plugins/…/dependency-graph/src/GraphTaskNode.tsx` | redundant column
condition deleted | 1 → 0 |
| `packages/engine/src/executor.ts` | 8 rebound guards compare the
resolved column; 4 resume-eligibility literals share one resolver | 151
→ 143 (+4 off-bar) |
| `packages/engine/src/__tests__/` | 4 new suites, 26 cases | — |

`plugins/` reaches **zero** column guards with this branch.

## The three threads it closes

**#2607 — five findings, all mine, all the same rule.** I kept
*qualifying* a legacy-id fallback instead of removing it:

| attempt | rule | hole review found |
|---|---|---|
| 1 | fall back to `todo` when the role is missing | moved cards to
phantom columns |
| 2 | …only if the workflow **declares** `todo` | aliased **review**
lane named `todo` |
| 3 | …and only if no other role is assigned to it | **traitless**
parking column named `todo` |

The qualifications were the mistake. Once `resolveLanes` returns a lane
set the workflow *has* a column vocabulary, so "no column carries the
hold trait" is a complete answer — refuse. `destination()` is two lines
now, with no aliasing surface left to qualify.

Plus a sixth, which is a genuinely different state: **degraded
resolution is indistinguishable from the default board.**
`resolveWorkflowIrForTask` is total by design — a missing definition
silently returns the *default* coding IR — so a card on a custom board
whose definition could not be read resolved to `todo`/`in-progress`.
`undefined` lanes cannot express that (it means "no workflow at all",
where the legacy ids *are* the answer). The actions now refuse with 409.
#2618 would replace this check with resolver provenance; it is not
merged, so this does not depend on it.

**#2635 — "seven rebound sites remain untested."** Fair; my "same shape"
note was an assertion, not coverage. Seven of the eight need a live
graph run to reach, so the *shape* is pinned instead: a static check
that no guard in front of a rebound move compares against a column
literal, with a vacuity case (the same detection run against the
original shape) and a match-count floor (≥8), because a guard reporting
success on zero matches is worse than no guard.

**#2640 — duplicate workflow resolution.** Framed as I/O; it is also a
correctness bug. Eligibility and re-entry are two halves of one decision
and resolved the workflow separately, so a workflow edit landing between
them has the halves reading *different boards*. Now one caller-owned
memo per decision — caller-owned because a process-lifetime cache would
have to guess when a mid-flight workflow edit invalidates it.

## Behavioural findings, not tidying

- **The last-resort recovery for completed-but-stranded work did not
exist off the default lineage.** `promotedFromPlannerColumn` was false
on a renamed board, so finished work resting in planning was never
promoted; the code fell through to a review handoff that role adjacency
rejects, and the card stayed stuck with its work complete.
- **Rebound guards could not see the column their own move targeted.**
U5b converted the move target; the eight `column !== "todo"` checks in
front of it were left literal, so on a renamed board the engine moved a
card into the column it was already in — and `moveTaskInternal` runs
reset-on-entry on every real move, so at the `preserveProgress: false`
site it reset step progress a second time.
- **The FN-1404 `task:move` audit row was lying**, recording `to:
"todo"` while the move target was resolved. A run-audit trail that
disagrees with the move it describes is worse than none. Not a
comparison, so no census counts it.
- **A task interrupted by an engine pause never resumed on a renamed
board** (off-bar, `in-review`/`in-progress` literals): four comparisons
decided one question and had to agree; two of them disagreed on a
renamed board, so re-entry silently never fired.

## Revert proofs, isolated per site

| reverted | result |
|---|---|
| `destination()` back to attempt 3 | 3 of 38 fail |
| degraded-resolution refusals removed | 2 of 42 fail |
| capture set back to the legacy five | 2 of 3 fail (renamed-board
suite) |
| forward exclusions → literals | 1 of 14 fails |
| missing-wip refusal removed | 2 of 14 fail |
| `promotedFromPlannerColumn` → literals | 3 of 7 fail |
| promotion target → `"in-progress"` | 3 of 7 fail |
| one rebound guard → `!== "todo"` | 1 of 3 fails (static shape) |
| resume lanes → legacy trio | 1 of 5 fails |

Every conversion is paired with a negative — a forward move, a
not-a-planner-lane card, a default-lineage card, an unresolvable
workflow — so neither "always fire" nor "never fire" can pass for
"resolve the role".

## Commit discipline

Twelve commits, each one thing: the code move (`resolvePlannerLanes` out
of `triage.ts`) is separate from every behavior change, and each review
fix is its own commit with its own revert proof.

## Verification

- `pnpm test:gate` **71/71**
- 162/162 across the glasses plugin's 19 files; 26/26 across the four
new engine suites
- engine + glasses typecheck clean; `pnpm lint` clean

🤖 Generated with [Claude Code](https://claude.com/claude-code)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Engine recovery and retries now work correctly with renamed or
customized workflow columns.
  * Tasks in manual-intake columns are no longer automatically planned.
* Agent actions and quick capture now respect each board’s declared
columns and lifecycle stages.
* Awaiting-approval tasks are recognized regardless of their current
column.
* Command Center SDLC funnel stages now accurately reflect customized
workflows.

* **Documentation**
* Added guidance for safely changing workflow-column logic and
interpreting lifecycle-column checks.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 00:52:55 -07:00
..
2026-07-26 18:11:47 -07:00

Even Realities Glasses Plugin (Fusion)

@fusion-plugin-examples/even-realities-glasses is a standalone Fusion plugin that provides a task-centric card workflow for Even Realities glasses.

Scope (v1)

  • Quick capture text into new tasks
  • Polling-based task transition notifications
  • Selected task workflow actions from glasses

Out of scope in v1: missions, roadmaps, search, multi-project routing, cloud/remote deployment orchestration.

Install (workspace local)

From repo root:

pnpm install
pnpm --filter @fusion-plugin-examples/even-realities-glasses build
pnpm --filter @fusion-plugin-examples/even-realities-glasses test

Required settings

  • fusionApiBaseUrl (default http://localhost:4040)
  • fusionApiToken (required Bearer token)
  • apiKey (required for plugin routes)
  • glassesDeviceId (optional identifier)
  • companionWebhookUrl (optional companion endpoint base URL for card push, e.g. https://companion.example)
  • pollingIntervalSeconds (default 30, min 5)
  • notifyOnColumns (default ["in-review"])
  • quickCaptureDefaultColumn (default triage)
  • enableAgentActions (default true)

Quick capture

Use POST /quick-capture for one-gesture glasses capture (POST /tasks is still the general-purpose route).

Pipeline:

  1. Strip leading wake phrase (hey fusion, fusion, ok fusion, note, task, capture)
  2. Strip filler tokens (um, uh, er, like, you know) and transcript punctuation noise
  3. Split first sentence into title + description (title capped at 80 chars; overflow moved into description)
  4. Resolve column from request column or plugin setting quickCaptureDefaultColumn (fallback triage)

Example:

curl -X POST http://localhost:4040/api/plugins/fusion-plugin-even-realities-glasses/quick-capture \
  -H "Authorization: Bearer <apiKey>" \
  -H "Content-Type: application/json" \
  -d '{"text":"hey fusion, file a bug about the merge gate"}'

Agent actions

All action routes require Authorization: Bearer <apiKey> and enableAgentActions=true.

Endpoints

Method Path
POST /actions/start-work
POST /actions/request-review
POST /actions/approve-plan
POST /actions/accept-review
POST /actions/return-to-agent
POST /actions/retry

Request body:

{ "taskId": "FN-123" }

Success response:

{
  "task": { "id": "FN-123" },
  "card": { "kind": "task", "taskId": "FN-123" }
}

Error envelope:

{ "error": "message" }

Status mapping:

  • 401: missing/wrong API key
  • 403: enableAgentActions disabled
  • 400: invalid input (for example empty taskId)
  • 404: task not found
  • 409: action not allowed for current column/status
  • 500: unexpected internal error

Preconditions and mutations:

Action Allowed preconditions Mutation
start-work column ∈ {triage, todo} and status not in {planning, needs-replan, awaiting-approval, awaiting-user-review} moveTask(id, "in-progress")
request-review column === "in-progress" moveTask(id, "in-review")
approve-plan column === "triage" and status === "awaiting-approval" moveTask(id, "todo") then updateTask(id, { status: undefined })
accept-review column === "in-review" updateTask(id, { status: null, assigneeUserId: null })
return-to-agent column === "in-review" updateTask(id, { assigneeUserId: null, status: null, assignedAgentId: null }) then moveTask(id, "todo")
retry (in-review branch) column === "in-review" and status ∈ {failed, stuck-killed} updateTask(id, { status: null, error: null, stuckKillCount: 0, mergeRetries: 0 })
retry (triage/planning branch) column === "triage" and (status ∈ {failed, planning, needs-replan} or (stuckKillCount ?? 0) > 0) updateTask(id, { status: "needs-replan", error: null, worktree: null, branch: null, baseBranch: null, baseCommitSha: null, stuckKillCount: 0, recoveryRetryCount: null, nextRecoveryAt: null })
retry (general failed branch) status ∈ {failed, stuck-killed} and not covered by branches above updateTask(id, { status: null, error: null, worktree: null, branch: null, baseBranch: null, baseCommitSha: null, stuckKillCount: 0, recoveryRetryCount: null, nextRecoveryAt: null }) then moveTask(id, "todo")

Example:

curl -X POST http://localhost:4040/api/plugins/fusion-plugin-even-realities-glasses/actions/start-work \
  -H "Authorization: Bearer <apiKey>" \
  -H "Content-Type: application/json" \
  -d '{"taskId":"FN-123"}'

Known limitations (v1)

  • startWork does not allocate a worktree directly. Tasks can enter in-progress with worktree: null; executor createWorktree flow allocates on first dispatch.
  • approvePlan performs move-then-clear; re-fetched responses may present task.status == null.
  • retry triage/planning branch does not delete on-disk PROMPT.md and does not run dashboard retry step-reset / branch-inspection logic.

Notifications

Notifications are produced by polling taskStore.listTasks({ includeArchived: false }) on pollingIntervalSeconds and diffing against persisted snapshot rows in even_realities_seen_tasks.

Diff reasons:

  • new-task (task first seen in a watched column)
  • entered-column (task moved into a watched column)
  • left-column (task moved out of a watched column)
  • completed (supported by diff engine; currently disabled in notifier v1)

Snapshot rows survive plugin restarts, so previously-seen tasks are not re-notified after reload.

Notification endpoints

Method Path Description
GET /notifications Read pending events (limit, optional drain=true) with rendered cards
POST /notifications/ack Ack events by taskIds
POST /notifications/poll-now Force immediate poll and return emitted events

Example:

curl -X GET "http://localhost:4040/api/plugins/fusion-plugin-even-realities-glasses/notifications?limit=25" \
  -H "Authorization: Bearer <apiKey>"

Security notes

  • Uses Authorization: Bearer <token> for all API requests.
  • Prefer local/self-hosted Fusion instances and avoid exposing dashboard APIs to public networks.
  • Treat fusionApiToken as secret material and rotate regularly.

Board/task card endpoints (merged canonical surface)

All companion-facing routes use Authorization: Bearer <apiKey> and are hosted under:

/api/plugins/fusion-plugin-even-realities-glasses

Method Path Description
GET /board/cards Card deck projection for selected columns (columns, max)
GET /board Board summary counts and status
GET /tasks/:id/cards Single-task card deck

Transport path (implemented)

Production transport uses WebhookGlassesTransport.

  • pushCard() POSTs cards to ${companionWebhookUrl}/cards
  • /status reports connected, transport mode, webhook config presence, lastPushAt, lastActionAt, and lastError
  • POST /reconnect forces disconnect/connect on the transport state machine
  • POST /transport/actions ingests companion actions (start-work, request-review, quick-capture) into plugin action handlers

If companionWebhookUrl is unset, the plugin still serves authenticated routes and polling notifications, but card push remains degraded with connected=false and a status error until configured.