Skip to content

AA10b: saveAgent/saveAgentUpdate tools, shared writeProposal, Apply save route - #860

Merged
LinuxDevil merged 1 commit into
mainfrom
fb-aa10b-save-tool
Nov 3, 2026
Merged

LinuxDevil merged 1 commit into
mainfrom
fb-aa10b-save-tool

Conversation

@LinuxDevil

Copy link
Copy Markdown
Owner

What changed

  • server/author/proposal/save.ts (new): writeProposal — the single write path for model saves and the card's Apply button. It re-validates the stored card's spec with today's agentSpecV2Schema (INVALID_STORED_PROPOSAL), writes exactly that spec inside withAgentLock, captures a source: 'author' version with requestText/authorSessionId, saves new agents disabled with createdFrom: { kind: 'author', ref: sessionId }, links created ids into session.createdAgentIds, publishes agent.changed/version on the workspace bus, and maps thrown errors to WRITE_FAILED. Also requestTextFor — the last non-empty, non-uiEvent user text before the card, cut to 400 chars.
  • server/author/proposal/failures.ts: the four new refusal builders (INVALID_STORED_PROPOSAL, BASE_CHANGED, AGENT_GONE, WRITE_FAILED) with the contract's model/person copy.
  • server/author/tools/saveTools.ts (new): saveAgent / saveAgentUpdate tools — AA10a blocking gate → stored-card resolution → writeProposal over deps.workspace() (an unresolvable workspace is AGENT_GONE); a success marks the identity saved in proposalTurnState. Registered in AUTHOR_TOOL_FACTORIES (17 tools now; AuthorToolDeps gains sessions).
  • POST /assistant/sessions/:id/save in server/author/routes.ts: body { proposalId }, 404/409-BUSY (running reply or save in flight), UNKNOWN_PROPOSAL/STALE_PROPOSAL/ALREADY_SAVED/BASE_CHANGED/AGENT_GONE as 400s (WRITE_FAILED → 500, details.latestProposalId carried), then appends the uiEvent: { kind: 'save' } user message and answers { ok, agentId, version, name, enabled, message }. The workspace resolves from the stored session.workspaceId via deps.workspaceResolver, not the request header.
  • RuntimeClient.saveProposal(sessionId, proposalId) returning AuthorSaveResponse; RuntimeApiError now keeps the server's code and details.

How it was tested

  • npm run typecheck:server, npm run test:server — 85 files / 1121 tests green (incl. new save.test.ts 11, saveFlow.test.ts 4, authorSave.test.ts 14)
  • Studio npm run typecheck, npm test -- --run — 88 files / 968 tests green
  • Root npx tsc --noEmit, npm run lint, npm run test:coverage, npm run fallow (0 dead exports, maintainability 89.2), npm run docs:ratchet (at baseline), npm run api:check, npm run docs:llms:check, npm run size-check — all green

Acceptance criteria

  • Turn 1 proposes, turn 2 calls saveAgent → agent file exists, meta.enabled === false, createdFrom.kind === 'author', version 1 has source: 'author' and requestText = turn 1's user text
  • Propose and save in the same turn → UNANSWERED, nothing written
  • Apply route on the newest card → 200, transcript ends with the uiEvent message; a second Apply → 400 ALREADY_SAVED
  • Apply on an older card → 400 STALE_PROPOSAL with details.latestProposalId
  • Update save after a canvas edit → BASE_CHANGED; nothing written
  • The written spec deep-equals the card's spec (no re-sent copy, no model input)
  • While a turn runs, Apply → 409 BUSY

Generated with Devin

…osal, Apply save route

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@LinuxDevil
LinuxDevil merged commit e5672e0 into main Nov 3, 2026
4 checks passed
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