Skip to content

feat(app): add goal indicator and todo dock - #56417

Closed
Te-River wants to merge 11 commits into
anomalyco:devfrom
Te-River:goal-indicator
Closed

Te-River wants to merge 11 commits into
anomalyco:devfrom
Te-River:goal-indicator

Conversation

@Te-River

@Te-River Te-River commented Oct 17, 2026 •

Copy link
Copy Markdown

Issue for this PR

Implements the native session goal request in #27167.

Part of the goal/todo series. Stacked on #55744 (the endpoints) and #54799 (the durable contracts) — review those first; this PR's diff shrinks to the dock once they merge.

Type of change

  • New feature
  • Bug fix
  • Refactor / code improvement
  • Documentation

What does this PR do?

Adds the goal indicator and the todo dock to the composer region.

  • A session.goal store in the client, fed by the four durable goal/todo events over SSE.
  • SessionGoalDock (shell + state branch), SessionGoalProgress (objective + elapsed) and SessionGoalActions (buttons), rendered above the websearch dock.
  • The state table: no goal → nothing; active → objective + elapsed (plus a stopped marker when armed is false) + pause; paused → resume/edit/clear; blocked → blockedReason.message + resume/clear; complete → clear.
  • pause/play icons, session.goal.* English source strings, and theme tokens chosen by semantic role.

The dock reads the Goal.State read model only. armed is process-local, so a goal that is active but not armed in this process shows stopped rather than pretending to run.

Notes

  • edit is an inline input in the dock (the endpoint needs an objective).
  • The dock does not pass expectedRevision yet — updates are last-write-wins.
  • The state table does not render the rounds/todo counts; those land with the round driver.

Visual

<svg width="240" height="72" viewBox="0 0 240 72" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Goal dock above the composer: the dock grows in, its spinner turns, and the objective line fades in">
  <style>
    .dock { transform-origin: 120px 52px; animation: grow 4.5s cubic-bezier(.2,.8,.2,1) infinite; }
    .objective { animation: appear 4.5s ease-in-out infinite; }
    .todos { animation: appear 4.5s ease-in-out infinite; animation-delay: .35s; }
    .spin { transform-origin: 206px 24px; animation: turn 1.4s linear infinite; }
    .caret { animation: blink 1s steps(2, start) infinite; }
    @keyframes grow { 0%,12% { transform: scaleY(.35); opacity:.25 } 34%,82% { transform: scaleY(1); opacity:1 } 96%,100% { transform: scaleY(.35); opacity:.25 } }
    @keyframes appear { 0%,22% { opacity:0 } 42%,82% { opacity:1 } 96%,100% { opacity:0 } }
    @keyframes turn { to { transform: rotate(360deg) } }
    @keyframes blink { 50% { opacity: 0 } }
    @media (prefers-reduced-motion: reduce) { .dock,.objective,.todos,.spin,.caret { animation: none } }
  </style>

  <rect x="8" y="54" width="224" height="14" rx="7" fill="currentColor" opacity=".14"/>
  <rect x="18" y="59" width="70" height="4" rx="2" fill="currentColor" opacity=".38"/>
  <rect x="92" y="58.5" width="2" height="5" rx="1" fill="currentColor" opacity=".55" class="caret"/>

  <g class="dock">
    <rect x="16" y="14" width="208" height="34" rx="9" fill="currentColor" fill-opacity=".06" stroke="currentColor" stroke-opacity=".22"/>
    <circle cx="32" cy="24" r="4" fill="currentColor" opacity=".85"/>
    <rect x="42" y="21" width="92" height="6" rx="3" fill="currentColor" opacity=".72" class="objective"/>
    <rect x="24" y="35" width="10" height="4" rx="2" fill="currentColor" opacity=".45" class="todos"/>
    <rect x="38" y="35" width="58" height="4" rx="2" fill="currentColor" opacity=".3" class="todos"/>
    <rect x="100" y="35" width="10" height="4" rx="2" fill="currentColor" opacity=".45" class="todos"/>
    <rect x="114" y="35" width="44" height="4" rx="2" fill="currentColor" opacity=".3" class="todos"/>
    <g class="spin" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" opacity=".6">
      <circle cx="206" cy="24" r="5" stroke-opacity=".18"/>
      <path d="M206 19a5 5 0 0 1 5 5" stroke-opacity=".9"/>
    </g>
  </g>
</svg>

How did you verify your code works?

  • bun typecheck in packages/app and packages/client: exit 0.
  • bun run test:components -- component-tests/session-goal-dock.spec.ts: 6 passed.
  • bun test ./test/solid-goal.test.ts in packages/client: 2 pass.
  • bun run test:unit in packages/app: 760 pass.

Screenshots / recordings

n/a

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Added by the opencode bot (AI-generated).

Related: #27167, #55744, #54799

Public API: client: adds data.session.goal list/sync/invalidate; the stacked commits add goal/todo HTTP endpoints, four durable events and a /goal command (all additions, backwards compatible)

@github-actions

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dock commit looks sound: it reads the goal read model, re-reads it on the four goal/todo events once a reader has loaded it, and clears the goal on session delete. The dock rendering follows the state table you describe. The client test and the packages/client and packages/app type checks pass. I could not run the Playwright component tests here (most likely no browser is installed) or open the web app, so before/after screenshots or a short recording of the dock in each phase would help.

Public API: this commit only adds data.session.goal.{list, sync, invalidate} to @opencode/client/solid. The stacked commits from #55744/#54799 add GET /api/session/:sessionID/goal (session.goal.get), POST /api/session/:sessionID/goal (session.goal.update), PUT /api/session/:sessionID/todo (session.todo.replace), the durable events session.goal.updated, session.goal.cleared, session.todo.updated and session.todo.verified, a built-in /goal command, the matching generated client methods and types, and a database migration. All of these are additions and backwards compatible. They should be judged on those PRs.

Concerns:

  • The elapsed time keeps counting while a goal is paused, blocked or complete (see inline).
  • The title says "todo dock", but nothing in this PR shows todos. The store only re-reads on todo events. Either render the todo list or retitle so it isn't mistaken for the todo UI.

onCleanup(() => clearInterval(timer))
})

const elapsed = createMemo(() => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The elapsed time is now - createdAt from a 1s interval, whatever the phase. A paused or complete goal keeps showing a growing time: a goal finished yesterday reads "1440m …". Only show it (or only tick) while the phase is active, or freeze it at the last update time. Also, after an hour it formats as minutes only (e.g. 95m 3s).

@Te-River

Copy link
Copy Markdown
Author

Both addressed in 1166720a20.

1. Elapsed. It now ticks only while the phase is active. When the phase leaves active the interval stops and the last computed value is kept, so a paused or complete goal shows the time it had when it left active instead of a growing one. Resuming recomputes from createdAt, so the number still means "since the goal was created" and includes the paused span. >= 1h now formats as 1h 35m via a new ui.message.duration.hoursMinutes source string.

2. Todos. The dock now renders the todo list under the objective: one row per item with a status icon (circle / circle-dot / circle-check / circle-x), the content truncated to one line, and an aria-label per row. It caps at five rows and shows +N more beyond that; an empty list renders nothing.

Tests: the component spec is 10/10. The four new cases (freeze while paused, the hour format, the list, the five-row cap) fail against the previous session-goal-dock.tsx and pass now.

@Te-River
Te-River force-pushed the goal-indicator branch 3 times, most recently from 89774bc to df83fa2 Compare October 22, 2026 00:58

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The elapsed time no longer ticks outside the active phase, and an hour or more now shows as hours and minutes, so that part of the earlier thread is addressed. The value a paused or complete goal starts from is still now - createdAt, though, so a reload still shows a stale, growing time (see inline). The todo list looks fine. The new commit doesn't change the public API. I could not run the component tests or open the web app here, so before/after screenshots of the dock (active with todos, paused, complete) would still help.

timer = undefined
}

const tick = () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tick() runs once on mount and on each phase change, and always computes Date.now() - createdAt. The value only freezes while this component stays mounted. After a reload or reopening the session, a goal paused or completed yesterday shows ~24h, and more each time. To truly freeze it, the read model needs an end time (e.g. the goal row's last update or phase-change time) to measure to when the phase isn't active. Otherwise, hide the elapsed time outside active.

@bkhim

bkhim commented Oct 22, 2026

Copy link
Copy Markdown

Browser-first user here. I upgraded to 2.1.3 and enabled experimental.todo; the TUI has the timeline/sidebar, but the web session still has no todo surface. The 2.1.3 web bundle still drops todowrite from the timeline (only permission-label strings remain) and there is no /api/todo.

This dock is the missing piece. Happy to test this branch or a preview build and report behavior across session switching, compaction, cleared lists, completed lists, and revert, with screenshots. Is there a build I can try, or should I run the branch locally?

@Te-River

Copy link
Copy Markdown
Author

Thanks — this is the report that matters for this feature: the web session has no todo surface, so the dock is what a browser-first user is missing.

Right now the branch is goal-indicator on my fork, and it is behind dev, so it will not apply cleanly. I am rebasing it onto current dev now; once it is pushed I will reply here with the exact branch head, and that is the thing to run locally (bun dev serve for the backend, bun dev -- --port 4444 in packages/app). A preview build is not something I can produce from here.

Two things from your report I want to make sure this PR actually covers:

  • experimental.todo is not this feature. todowrite and this dock are separate: todowrite is the model's stateless scratch list, while this dock reads the durable session_todo rows. So it should show up without that flag — if it does not, that is a bug in this PR and I would rather hear it.
  • /api/todo — the endpoints in this series are GET/POST /api/session/:sessionID/goal and PUT /api/session/:sessionID/todo, not a top-level /api/todo. If you were looking for the latter, that is my naming, not a missing surface.

Your test list — session switching, compaction, cleared, completed, revert — is exactly the set I could not exercise from here (no browser in the sandbox). Screenshots of those five would be genuinely useful, and I will fold whatever they show into the PR.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rebase broke the generated client: packages/client/src/promise/generated/types.ts was not regenerated against the new dev. It no longer exports McpClientTokenInput/McpClientTokenOutput and DebugHeapDumpInput/DebugHeapDumpOutput, which the generated client.ts still imports, so tsc fails in packages/client and packages/app. It also drops other types dev has, such as PtyTicketConnectToken and SessionMessageAgentSelected. Please regenerate the client from the rebased protocol instead of keeping the conflict-resolved file. This comes from the stacked commits, so #55744 needs the same fix.

The goal dock code is unchanged since the last review, so the elapsed-time threads are still open. The new push doesn't change the public API beyond what was noted before, apart from the accidental type removals above. The client goal test passes. I could not run the component tests or the web app here.

@Te-River

Copy link
Copy Markdown
Author

Both addressed in 6e314bec33.

Elapsed. The value now freezes at the time the phase last changed, and that time survives a reload. Goal.State has no phase timestamp, so the client records one from the session.goal.updated event's created — server time, not arrival — only when the phase actually changes, so an edit or a new round does not move it. It persists under session-goal-phase, keyed by session + goal + phase.

I did not add time_updated to Goal.State: that needs a schema change and bun run generate is broken here (#57981). If you would rather have the server own this, that is the right shape and it replaces the client record cleanly.

Still-active hint. Shown when the goal is active, not armed, and the last idle outcome is interrupted, with an inline dismiss that resets when the condition stops holding. Copy is the one from the earlier discussion, with /goal pause as plain text.

One caveat on this branch: disarm-on-human-message lives in the driver PR, so here "not armed" means "this process has not armed it" rather than "a human message disarmed it". The hint is accurate but will be rarer than it will be once both land; the interrupted condition is what stops it firing on a plain restart.

Tests: session-goal-dock.spec.ts 13 pass; solid-goal + solid-data 33 pass. Both new cases fail before the change — freeze showed 1m 5s where it should be frozen, and the hint slot count was 0.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thread 1 is addressed: the elapsed time only ticks while the goal is active, and long runs show hours and minutes. The reload case (thread 2) is only partly fixed: the frozen time comes from one browser-local entry, so it is right only for the last session this browser saw change phase live (see inline).

Also:

  • packages/app no longer type-checks: the stub goal model in src/session/story-model.tsx lacks the new phaseChangedAt and interrupted fields (TS2739).
  • The generated types.ts regression from the last review is still there, so packages/client fails to type-check too.
  • The "still active" notice after an interruption is a new feature in a fix-up commit, and would be easier to review on its own.

The new commit doesn't change the public API: data.session.goal.phaseChange is an addition to the in-app solid data layer. The client goal test passes. I could not run the component tests or the web app here.

Comment thread packages/app/src/session/goal/model.ts Outdated
api: ServerApi
}) {
const language = useLanguage()
const [phaseChange, setPhaseChange] = persisted(Persist.global("session-goal-phase"), PhaseChange, {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is one global browser-storage entry (session-goal-phase), written only when this client sees a session.goal.updated event live. Opening another session that has a live record overwrites it. A goal paused in another browser or device, by /goal pause from the TUI, or while the app was closed has no record, and falls back to clock.ended = Date.now() at mount. That is the same reload drift as before. The phase-change time is durable server state: please add it to the goal row and SessionGoalState (e.g. phaseChangedAt, set in the projector when phase changes) and measure to it, instead of reconstructing it per browser.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new commit only touches the core projection: a todo update without a goal row, or one that doesn't advance the goal revision, is now dropped. That matches how replaceTodo bumps the revision, and the core goal tests pass. It changes no event shape or endpoint.

The dock code is unchanged, so the elapsed-time threads stay open. The build is still broken:

  • The generated packages/client/src/promise/generated/types.ts is still missing McpClientTokenInput/McpClientTokenOutput and DebugHeapDumpInput/DebugHeapDumpOutput, which client.ts imports, so packages/client and packages/app fail to type-check. The commit message leaves regeneration to maintainers, but a PR that changes the protocol needs to ship a consistent generated client: please regenerate it from the rebased protocol (in #55744 too).
  • The stub goal model in packages/app/src/session/story-model.tsx still lacks phaseChangedAt and interrupted (TS2739).

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generated client is consistent again: packages/client type-checks.

packages/app still doesn't. The stub goal model in src/session/story-model.tsx (around lines 205 and 239) lacks the new phaseChangedAt and interrupted fields, so tsc -b fails with TS2739 and a knock-on TS2322 at line 286. The dock code is unchanged, so the elapsed-time threads stay open. This push doesn't change the public API beyond the earlier review.

@Te-River

Copy link
Copy Markdown
Author

Fixed in the current head: the four declarations are back in packages/client/src/promise/generated/types.ts — McpClientTokenInput/McpClientTokenOutput and DebugHeapDumpInput/DebugHeapDumpOutput. The rebase had taken this branch's older copy of that file, which predates them.

bun run typecheck:tsc in packages/client is clean now.

I could not run bun run generate to produce it: this is Windows and the generator throws GenerationException: Referenced Promise types are not implemented on a clean dev here (filed as #57981). So the four declarations were restored by hand at their generated positions, and the file still needs a clean bun install + bun run generate on a working host before merge — which matches what you said about the ~50-declaration ordering churn.

@Te-River

Copy link
Copy Markdown
Author

The story stub is fixed: packages/app/src/session/story-model.tsx now supplies phaseChangedAt: () => undefined and interrupted: () => false, matching the signatures in session/goal/model.ts — the same shape as the neighbouring state: () => undefined, not a fake timestamp.

tsc -b in packages/app is clean at this head. The @pierre/highlights and custom-elements.d.ts errors I had been reporting were a stale node_modules in my working copy, not the branch: after a frozen install those are gone too.

The elapsed-time threads are addressed in the commit below the stub fix — the frozen value is keyed to the phase-change timestamp recorded from session.goal.updated's created, persisted so a reload shows the same number, and the still-active hint appears only when the goal is active, unarmed, and the last idle outcome was interrupted. session-goal-dock.spec.ts is 13 pass, including a freeze case that reads 1m 5s where the old code read 10m 0s.

Screenshots: I can render the dock states locally but cannot attach files through the API. If you want them on this PR, say so and I will post the images.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The story stub now has phaseChangedAt and interrupted, and packages/app type-checks again. The rebuilt stack's session-event.ts matches dev apart from the goal/todo events. The schema manifest test and the client goal test pass. The public API is unchanged since the last review. The dock code is unchanged, so the elapsed-time threads stay open.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean rebase onto the latest dev: the goal code is unchanged and the generated client stays consistent with dev. packages/client and packages/app type-check, and the client, core and server goal tests pass. The public API is unchanged since the last review. The dock code is unchanged, so the elapsed-time threads stay open.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The elapsed time is now shown only while the goal is active, and the browser-local phase record is gone, so paused, blocked or complete goals no longer show a time that drifts across reloads. This addresses the remaining elapsed-time threads. packages/client and packages/app type-check and the client goal test passes. The public API is unchanged: the commit only removes an in-app data-layer addition from this PR.

@Te-River

Copy link
Copy Markdown
Author

Took your fallback rather than the phaseChangedAt field: the elapsed time is now rendered only while the goal is active, and paused / blocked / complete show the phase without a duration.

The session-goal-phase browser record is gone entirely — the store field, the phaseChange() accessor, the Persist.global wrapper and the model's phaseChangedAt() all removed. That was your objection and it was right: a per-browser record could not describe a goal paused from the TUI or while the app was closed, so it just moved the drift.

git grep for phaseChange|goalPhase|session-goal-phase is empty.

Why not the durable field: phaseChangedAt reaches SessionGoal.Snapshot, which is generated client surface, and bun run generate is broken on this machine (#57981) — I would be hand-editing the generated types.ts again, which is the churn you flagged on the contracts PR. If the duration matters enough to keep, that field is the right shape and it can land as its own change on a host where generate works.

The still-active hint is untouched, since it was a separate decision: interrupted() and the dismissable notice both still work.

Tests: session-goal-dock.spec.ts 12 pass, including a new case asserting the elapsed slot is absent for paused, blocked and complete and present for active; solid-goal + solid-data 32 pass; packages/app tsc -b clean.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This push only rewords a commit message; the code is identical to the previous review. The current code addresses all three elapsed-time threads: the elapsed time is shown and ticks only while the goal is active, uses hours and minutes past an hour, and no longer relies on a browser-local phase record. The public API is unchanged.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebase onto the latest dev: the dock code is unchanged, and the goal, migration and database tests pass with packages/core, packages/client and packages/app type-checking. The public API is unchanged from earlier reviews.

One cleanup in the stacked core commit: packages/core/schema.json was regenerated without the formatter, so all single-line arrays (["id"] and so on) are expanded, adding ~180 lines of unrelated churn. Please run the formatter on it. Optionally, also regenerate the migration with a fresh timestamp: 20261012112921_modern_red_wolf now chains after 20261024002554_session_allowance. It works because migrations are applied by id, but the order is confusing.

@Te-River

Copy link
Copy Markdown
Author

Formatted. packages/core/schema.json is now serialized the way the generator writes it — short primitive arrays stay on one line, arrays of objects expand — so the ~180 lines of churn are gone and the diff carries only the goal tables.

I verified the format rather than eyeballing it: the same serializer applied to the previous snapshot reproduces the committed dev file byte for byte, so this is the generator's shape and not a new style. The file goes from 2413 to 2311 lines, and upstream/dev is 2078, which leaves the ~230 lines for session_goal and session_todo and nothing else. The whole stack carries one identical blob for it.

On your optional point: I left 20261012112921_modern_red_wolf where it is. Re-timestamping means renaming the migration and regenerating the chain, for a file whose id, not its position, decides whether it runs — the ordering only reads oddly next to 20261024002554_session_allowance. If you would rather have the timestamp agree with the merge order, say so and I will redo it, but I did not want to churn the migration name for a cosmetic reason in this PR.

packages/core migration and goal tests pass on the new head.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

packages/core/schema.json is formatted like the generator output again, so its diff against dev is just the goal/todo tables. Nothing else changed, and the core database, migration and goal tests pass. The public API is unchanged.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rebase brought back a stale column in the stacked core commit: packages/core/schema.json includes session_v2.allowance again, which dev dropped in 20261024175301_drop_session_allowance. As a result, the core test declared schema has no ungenerated migrations fails here. It passes on dev and on the previous head. Please regenerate the snapshot on top of current dev (bun script/migration.ts in packages/core) so it only adds the goal/todo tables (see inline).

Everything else checks out: the generated client and migration.gen.ts are additions only, packages/client and packages/app type-check, and the client, server and core goal tests pass. The public API is unchanged.

Comment thread packages/core/schema.json Outdated
"autoincrement": false,
"default": "0",
"generated": null,
"name": "allowance",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This re-adds session_v2.allowance, which dev dropped in 20261024175301_drop_session_allowance. Nothing in the schema code defines it anymore, so script/migration.ts --check reports ungenerated migrations, and test/database-migration.test.ts › "declared schema has no ungenerated migrations" fails. Regenerate the snapshot on top of current dev so only session_goal and session_todo are added.

The durable half of a session goal: a schema contract, session_goal / session_todo
tables, four durable events, their projections and the read model. No endpoints, no
UI, no round driver.

The projections trust the event log's order: an update upserts the row wholesale, and
a different goal id clears the previous todo list instead of being skipped. A clear
deletes by (session_id, goal_id). The todo list is replaced wholesale in one
transaction, and a verification applies verdicts by position, rejecting a verdict
whose revision moved since it was taken.

Round counting and the revision compare-and-set belong to the endpoints and the driver
that use them.
A session.todo.updated event carries the goal revision a verification is judged against, so an event that does not advance it could rewind the goal onto a number an already-stale verdict carries; an event with no goal row wrote a list no verdict could ever match. Both are dropped now.

Also drops the unrelated httpapi-codegen change (stripBrand and the Import artifact filter). The generated client stays as committed; regenerating from a clean install is left to the maintainer.
Add the session goal and todo endpoints, the /goal command, and the user's cancel authority. pause commits the durable phase and the process-local disarm separately, then interrupts the running turn with reason user. The round driver, verifier and model tools land in a follow-up.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The schema snapshot is regenerated on current dev: it only adds the goal/todo tables, and the core migration check passes again. The rebase onto dev's new composer region looks right: the dock sits above the composer, and the composer-docks story has the goal stub. Core, server and client goal tests pass, and packages/client and packages/app type-check. The public API is unchanged.

The description's SVG is a mock-up rather than the real UI. Since I can't run the web app here, real screenshots or a short recording of the dock (active with todos, paused, blocked, complete) would still help.

@Te-River

Copy link
Copy Markdown
Author

Closing: #59437 landed a /goal implementation on 29 Oct (plugin-based, client-side completion check). This one added the dock that rendered that state. That PR owns the /goal command, so this branch cannot be reconciled with it; the design is recorded here as reference rather than kept open.

@Te-River Te-River closed this Oct 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants