@astram/frontend-core
Platform-agnostic auth, identity, agent and proposal services over an injected HttpClient
(no React, DOM, fetch or storage built in). Each create…Service(http) factory takes the app's
HTTP client, receives the bearer token per call and resolves to { ok: true, … } or
{ ok: false, status, error } with fixed text; it never throws for an HTTP error.
Proposals (createProposalService)
Client for the governed proposal routes /identity/proposals… (agent-proposals SPEC §3, §5 H):
list, get, submit, select, approve, reject, withdraw, apply. A failure also
carries code (one of PROPOSAL_ERROR_CODES, or null) and, where the server named them,
itemIndex / itemIndexes. (SPEC v8) screened (422: the kind's screen refused the draft;
screenCode is the kind's declared code or screen_failed, for your own wording) and
submit_app_only (403: the kind accepts submits from app code only).
Rendering contract
- Values are text, never HTML. Render
title,rationale,metadata.assumptions,metadata.uncertainty.note, evidencenotes,impact.summary, item issue messages and every label as text nodes only (noinnerHTML,dangerouslySetInnerHTML,v-htmlor markdown). previewcells are HTML-escaped by the server. Pass each cell throughpreviewCellText(cell)and render the result as text. Never insert a cell as HTML.- Ids are for linking, never displayed.
uuid,provenance.taskId/callId, itemtargetId/resultId, evidence ids and applyresults[].idmay be used in routes and API calls only. Show a resolved label instead (kindLabel,requester.label,decided.byLabel,history[].actorLabel, or a label the app resolves for a target record); show a neutral fallback when no label resolves. A review page must have a test that no uuid-shaped text renders. - Provenance is server-set.
submitsends onlykind,payload,title,rationale?andmetadata?; the server refusesorigin,taskIdorcallIdin a body. - Show
error(fixed text), never a server message.
Proposals created during an agent task (AgentTaskState.approvals)
When an agent propose capability creates a proposal, the agent stream sends approval.required
{ proposalId, kind, title }. agentReducer keeps these in AgentTaskState.approvals
(readonly AgentApproval[], [] in INITIAL_AGENT_STATE): text fields only (anything else in the
payload is dropped; a payload missing one of the three is ignored), one entry per proposalId (a
replayed event adds nothing), in arrival order, kept across the task's runs. Render kind and
title as text and use proposalId only to link to the review page (never display it); fetch the
details with createProposalService(http).get(accessToken, proposalId).
Agent plan and workers (AgentTaskState.plan, .workers)
The agent stream may send the agent's todo list and its helpers (docs/features/agent-plan-line/SPEC.md):
plan⇒AgentTaskState.plan(readonly AgentPlanItem[] | null): each event replaces the list after a shape check (1–12 items, a stringidandtitle,statuspending|in_progress|done); a malformed event is ignored. Render titles as text only (never HTML or markdown): they are model-written.idis a rendering key, never displayed.worker.started/worker.finished⇒AgentTaskState.workers, keyed by the app-assigned worker number as a string ('1','2', …) with{ profile, status, code }; a request the app denied has no number and is keyedd1,d2, … in arrival order (never merged with a numbered worker).profileisreaderoranalyst: map it to your own label.tool.startedwith a worker number ⇒AgentToolState.worker, so a worker's calls can be grouped under it.task.startedresetsplantonullandworkersto{}(each run starts empty).getTaskreturns the task view's lastplan(ornull).