Repository navigation
feat(console): add optional Agent workflow demo - #287
Conversation
…' into codex/agent-demo-ui
…' into codex/agent-demo-ui
Cross-system status correction — 2026-10-12 02:39 UTCLinear DAV-34 is now Canceled because the old planning queue was retired; the previous body text saying “In Progress” is superseded. This cancellation does not mark the optional demo accepted or complete. PR #287 remains Open/Draft at The project is not ready for full manual acceptance; see the current Notion project snapshot. |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1afd1e1826
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| ULTICODE_APP_BASE=http://127.0.0.1:9103 \ | ||
| ULTICODE_AUTH_BASE=http://127.0.0.1:9101 \ | ||
| ULTICODE_AGENT_ORIGIN=http://localhost:9002 \ | ||
| uv run uvicorn --factory agent_service.app:create_app --app-dir src --host 127.0.0.1 --port 8000 |
There was a problem hiding this comment.
Start the demo with its required workflow gates
When a developer follows this command, Uvicorn invokes create_app() without arguments, leaving gate unset. Consequently /analyze fails through _require_current_gate(), while /confirm, /save, and /recover are not registered because they are defined only under if gate is not None; the documented demo therefore cannot complete its advertised workflow. Provide a configured factory or launcher that supplies the gate and model-budget dependencies, or document the actual supported entry point.
AGENTS.md reference: AGENTS.md:L141-L142
Useful? React with 👍 / 👎.
| return apiGet<AgentThread>(threadPath(threadId)); | ||
| }, | ||
| analyze(threadId: string) { | ||
| return apiPost<AgentThread>(`${threadPath(threadId)}/analyze`, {}); |
There was a problem hiding this comment.
Disable retries for the stateful analysis request
If analysis completes server-side but its response is lost or hits the shared 30-second client timeout, this call is automatically retried up to twice by packages/http-client/src/index.ts:399-428 because no { retry: 0 } is supplied. The endpoint accepts the resulting awaiting_confirmation state and starts a new run, potentially repeating billed model calls and appending another analysis to the draft; disable transport retries here and rely on the existing canonical refresh/polling recovery.
Useful? React with 👍 / 👎.
| <Button type="submit" :disabled="workflow.busy.value"> | ||
| {{ t("agent.start") }} | ||
| </Button> |
There was a problem hiding this comment.
Block thread creation while a restored save is pending
When a thread is reopened with canonical status saving, workflow.saving and workflow.busy are both false because no local save request is active, so this submit button remains enabled. Submitting immediately closes the old event stream, resets its state, and replaces the URL with a new thread even though the previous write is unresolved; this bypasses the canonical-status guard already used by the adjacent “New thread” action and can make the user lose track of the save result.
Useful? React with 👍 / 👎.
| :disabled="workflow.busy.value || workflow.analyzing.value" | ||
| @click="workflow.analyze" |
There was a problem hiding this comment.
Preserve draft edits before starting analysis
When the user changes either draft field but has not clicked “Update draft,” this button remains enabled despite draftDirty being true. analyze() sends only the thread ID, so the server analyzes its previous canonical draft, and the next poll or response calls applyThread() and overwrites the user's unsaved title and content; disable analysis while the draft is dirty or persist the edits before starting it.
Useful? React with 👍 / 👎.
| const canConfirm = computed(() => workflow.thread.value?.status === "awaiting_confirmation" | ||
| && !draftDirty.value && Boolean(workflow.generatedText.value)); |
There was a problem hiding this comment.
Allow expired confirmations to be renewed
If a confirmed thread is reopened or left idle beyond the server's 30-minute expiresAt, Save returns confirmation_expired without changing the canonical confirmed status. This condition prevents the confirmation button from appearing for that status even though the backend explicitly accepts confirmation from confirmed and can issue a fresh token, leaving the UI with a Save button that will fail repeatedly and Cancel as the only exit.
Useful? React with 👍 / 👎.
| const value = await agentApi.getThread(threadId); | ||
| if (token !== runGeneration) return null; | ||
| applyThread(value); | ||
| if (value.status === "analyzing" || value.status === "saving") connectStream(value, token); |
There was a problem hiding this comment.
Recover threads stranded in analyzing after a restart
If the Agent process stops after persisting analyzing but before completing the model call, reopening the URL loads that stale status and connects a stream even though no work exists to finish it. The stream times out and refresh() performs another plain GET, which still returns analyzing because only action endpoints call the backend's _reconcile; the view consequently hides Analyze and Update and leaves cancellation as the only option. Provide a retry/reconciliation path for a timed-out restored run.
Useful? React with 👍 / 👎.
| <p v-if="workflow.thread.value.receipt.failureReason" class="text-sm text-destructive"> | ||
| {{ workflow.thread.value.receipt.failureReason }} | ||
| </p> |
There was a problem hiding this comment.
Hide stale failure reasons after a successful retry
When an analysis attempt fails and a subsequent attempt succeeds, the backend's analysis_completed transition does not clear the prior failure_reason, so the returned receipt can still contain analysis_failed while the thread is awaiting confirmation or even saved. Rendering every nonempty reason unconditionally therefore leaves a red failure message on a successful workflow; clear it on successful transitions or display it only for statuses where the reason is current.
Useful? React with 👍 / 👎.
| v-for="citation in workflow.thread.value.draft.citations" | ||
| :key="citation.chunk_id" |
There was a problem hiding this comment.
Use a unique key for repeated source citations
The Agent parser allows multiple citations to the same chunk_id when they carry different claims, but this list uses only that chunk ID as its Vue key. Such a valid response produces duplicate keys, so subsequent thread refreshes or analyses can reuse the wrong DOM node and show a mismatched or missing claim/source entry; include the claim or list index in the key.
Useful? React with 👍 / 👎.
# Conflicts: # services/agent/data/repository_corpus_manifest.json
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: be4c57760a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| export const agentApi = { | ||
| createThread(sourceSubmissionId: string, question: string) { | ||
| return apiPost<AgentThread>("/agent/threads", { sourceSubmissionId, question }); |
There was a problem hiding this comment.
Disable retries when creating a thread
When the server commits the create but its response is lost or replaced by a 5xx, the shared HTTP client automatically retries this POST up to twice. The create endpoint generates a fresh thread ID and business key on every invocation and does not deduplicate by request ID, so one click can leave multiple owner threads while the UI tracks only the response from the last successful attempt. Pass { retry: 0 } for creation or add a server-recognized idempotency key.
Useful? React with 👍 / 👎.
| } catch { | ||
| if (token === runGeneration) error.value = "requestFailed"; |
There was a problem hiding this comment.
Reconcile after an ambiguous draft update
If the draft update commits but its response is lost, the automatic retry sends the old draftVersion and receives a conflict because the server already incremented it. This catch then leaves thread.value on the old version, so subsequent Update attempts keep conflicting even though the submitted text is already canonical on the server; refresh the thread here just as confirmation and saving do after uncertain outcomes.
Useful? React with 👍 / 👎.
| recover(threadId: string) { | ||
| return apiPost<AgentThread>(`${threadPath(threadId)}/recover`, { retry: false }); |
There was a problem hiding this comment.
Provide an explicit retry path after reconciliation
When a save reaches unknown and the safe readback confirms that no Java record exists, this method always sends retry: false, so the thread remains unknown; the only visible recovery button invokes the same check again. The backend contract supports a later explicit retry: true under the original idempotency key and confirmation guards, but this API shape makes that recovery path unreachable from the demo even after a transient pre-dispatch failure has cleared.
AGENTS.md reference: AGENTS.md:L39-L39
Useful? React with 👍 / 👎.
| :disabled="workflow.loading.value || workflow.saving.value" | ||
| @click="loadFromUrl" |
There was a problem hiding this comment.
Disable reload while a mutation is in flight
During draft update or confirmation, busy is true but Reload remains enabled. Clicking it calls loadThread(), which increments runGeneration; if its GET returns before the mutation commits, it applies the old state and the later mutation response is ignored as stale. For draft updates this also leaves the client on the previous version, causing the next edit to conflict until another reload, so reload should be disabled while busy or sequenced after the active mutation.
Useful? React with 👍 / 👎.
| <Button | ||
| v-if="workflow.thread.value.status === 'awaiting_confirmation'" | ||
| type="button" | ||
| variant="outline" | ||
| :disabled="workflow.busy.value || !draftDirty" | ||
| @click="workflow.updateDraft" |
There was a problem hiding this comment.
Allow confirmed drafts to be revised
Once a thread reaches confirmed, this condition hides the draft-update action while the input fields are also disabled, leaving Save or Cancel as the only choices if the user notices a mistake after confirming. The backend deliberately accepts PUT /draft from both awaiting_confirmation and confirmed and invalidates the old confirmation, so the frontend should expose that supported correction path before the irreversible save step.
AGENTS.md reference: AGENTS.md:L39-L39
Useful? React with 👍 / 👎.
| applyThread(value); | ||
| if (value.status === "analyzing" || value.status === "saving") connectStream(value, token); |
There was a problem hiding this comment.
Restore tool activity for completed threads
When a completed, failed, or cancelled thread is reopened from its URL, reset() clears all tool events and this condition connects the replayable event stream only for analyzing or saving. Consequently the Tool activity section disappears after a page refresh even though the backend retains the workflow events and the thread response also includes its analysis trace; load the stored events or reconstruct the tool summary when restoring a terminal thread.
Useful? React with 👍 / 👎.
| if (isCurrent(token, threadId, runId)) { | ||
| applyThread(value, true); | ||
| if (value.runId !== runId && (value.status === "analyzing" || value.status === "saving")) { | ||
| connectStream(value, token); |
There was a problem hiding this comment.
Clear prior run output when switching streams
If another tab starts a new analysis for the same thread, the old stream reports run_replaced and this refresh applies the new runId with preserveAnswer=true without clearing generatedText or events. While the replacement run is still analyzing, the page therefore presents the previous run's answer and tool activity as though they belonged to the current run; reset run-scoped output when the fetched runId differs before connecting the replacement stream.
Useful? React with 👍 / 👎.
Adds an authenticated, single-screen Console demo for the standalone Agent workflow. It reuses the existing API/auth/CSRF path, displays generation and tool events plus source citations, separates confirmation from saving, and preserves canonical thread state across cancellation, refresh, and late events. Duplicate confirmation and saving requests are suppressed; uncertain confirmation, cancellation, or recovery outcomes reload canonical thread state before retrying. Disconnects and stream timeouts show errors and reconcile final status.
Validation on the exact source tree represented by head
1afd1e1826e153dba40f3560bc9cb59408556b65: Console tests (79 files, 635 passed), lint, type-check, i18n validation, and production build passed. The build reports the repository's existing large-chunk and ineffective dynamic-import warnings. No live Agent service or model was used. GitHub has no workflow runs or status checks for this head because the PR is stacked on a feature branch.Stacked on PR #286 at
7e44463a01c68d77ce27b8220f8631745156ad5b. DAV-34 remains In Progress pending its required recording and actual personal hours. DAV-20 is Done; DAV-31 describes post-U04 learning/calibration as a non-delivery gate, so neither is a current implementation blocker.