Skip to content

feat(console): add optional Agent workflow demo - #287

Merged
DavidHLP merged 7 commits into
mainfrom
codex/agent-demo-ui
Oct 12, 2026
Merged

DavidHLP merged 7 commits into
mainfrom
codex/agent-demo-ui

Conversation

@DavidHLP

@DavidHLP DavidHLP commented Oct 11, 2026 •

Copy link
Copy Markdown
Owner

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.

Copy link
Copy Markdown
Owner Author

Cross-system status correction — 2026-10-12 02:39 UTC

Linear 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 1afd1e1826e153dba40f3560bc9cb59408556b65; no live Agent/model demo or formal review approval is recorded. Its current check readback contains only a successful GitGuardian check.

The project is not ready for full manual acceptance; see the current Notion project snapshot.

@DavidHLP
DavidHLP marked this pull request as ready for review October 12, 2026 03:58
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-12T04:24:28.763962Z be4c577 New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread docs/DEVELOPMENT.md
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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`, {});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Comment on lines +111 to +113
<Button type="submit" :disabled="workflow.busy.value">
{{ t("agent.start") }}
</Button>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment on lines +193 to +194
:disabled="workflow.busy.value || workflow.analyzing.value"
@click="workflow.analyze"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment on lines +53 to +54
const canConfirm = computed(() => workflow.thread.value?.status === "awaiting_confirmation"
&& !draftDirty.value && Boolean(workflow.generatedText.value));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment on lines +159 to +161
<p v-if="workflow.thread.value.receipt.failureReason" class="text-sm text-destructive">
{{ workflow.thread.value.receipt.failureReason }}
</p>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment on lines +254 to +255
v-for="citation in workflow.thread.value.draft.citations"
:key="citation.chunk_id"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@DavidHLP
DavidHLP changed the base branch from codex/mcp-readonly-server-client to main October 12, 2026 04:15
# Conflicts:
#	services/agent/data/repository_corpus_manifest.json

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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 });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment on lines +229 to +230
} catch {
if (token === runGeneration) error.value = "requestFailed";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment on lines +99 to +100
recover(threadId: string) {
return apiPost<AgentThread>(`${threadPath(threadId)}/recover`, { retry: false });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment on lines +136 to +137
:disabled="workflow.loading.value || workflow.saving.value"
@click="loadFromUrl"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment on lines +183 to +188
<Button
v-if="workflow.thread.value.status === 'awaiting_confirmation'"
type="button"
variant="outline"
:disabled="workflow.busy.value || !draftDirty"
@click="workflow.updateDraft"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment on lines +148 to +149
applyThread(value);
if (value.status === "analyzing" || value.status === "saving") connectStream(value, token);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment on lines +129 to +132
if (isCurrent(token, threadId, runId)) {
applyThread(value, true);
if (value.runId !== runId && (value.status === "analyzing" || value.status === "saving")) {
connectStream(value, token);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@DavidHLP
DavidHLP merged commit 080e6f8 into main Oct 12, 2026
63 of 65 checks passed
@DavidHLP
DavidHLP deleted the codex/agent-demo-ui branch October 12, 2026 04:35
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.

1 participant