AppForge-3 — the Agent learns propose-code-file (staged CodeFile/v1 drafts) - #365
Merged
Merged
Conversation
…ct kind
The agent can now DRAFT a brainstorm/CodeFile/v1 (filename + language +
code) as a staged tray card the user approves — same propose → approve →
one-entities.create contract as the other seven kinds. Approval mirrors
the Code editor's own new-file create ({path, content, language} +
sizeBytes/lineCount), so the created file opens cleanly in Code
(real-shell verified end-to-end with the demo provider).
- propose-code-file.ts: pure staging — path sanitized+clamped like the
editor's rename (sanitizeInlineText), content REFUSED beyond 256 KiB,
language validated against CodeLanguage or inferred (extension/shebang,
PlainText fallback), fail-closed offer gate on
entities.write:brainstorm/CodeFile/v1 (canProposeCodeFiles).
- three-tier ceiling unchanged: offer gate + intersectAgentTools
(intents.dispatch:propose-code-file) + makeDispatchTool interception —
model output never reaches entities.create.
- tray card: editable filename, language chip, read-only scroll-capped
keyboard-focusable code preview; untrusted content renders as React
text nodes only (injection test); Shiki via the shared
@brainstorm-os/sdk/code-highlight tokenizer, dynamic-imported so shiki
core stays out of the agent's initial chunk. No new highlighter dep.
- sdk: LanguageKey→shiki-id map extracted to language-detect
(shikiIdForLanguage) at copy two; code-editor delegates to it.
- created-objects chip label falls back to `path` so approved code files
link back (open intent) with a real name.
- manifest: + intents.dispatch:propose-code-file,
+ entities.write:brainstorm/CodeFile/v1 (install-consented).
- demo provider: BRAINSTORM_DEMO_AGENT=appforge selects a capture-only
script proposing hello-app/manifest.json + index.html (the video act);
any other value keeps the existing reel.
- i18n: 4 new keys across all six catalogs (en/es/de/fr/it/pt).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
AppForge-3 — the Agent learns
propose-code-fileThe agent's curated tool set gains an eighth artifact kind: it can DRAFT a
brainstorm/CodeFile/v1(filename + language + code) as a staged card the user approves — the same propose → approve → one-entities.createcontract as the other seven kinds. This unblocks the agent act of the "build an app inside Brainstorm" video (env-gated demo script included) and is genuinely useful on its own: the agent can draft a script/snippet file straight into the vault, opened by the Code editor.What's in here
apps/agent/src/logic/propose-code-file.ts(new) — the pure staging module:PROPOSE_CODE_FILE_VERB,buildCodeFileProposal(path sanitized + clamped via the SDKsanitizeInlineText, exactly like the Code editor's rename validation; content refused beyond 256 KiB, never truncated; language validated against theCodeLanguageenum or inferred from extension/shebang via@brainstorm-os/sdk/language-detect, degrading to PlainText), the honest "staged, NOT saved" ack, and the fail-closed offer gatecanProposeCodeFiles(requiresentities.write:brainstorm/CodeFile/v1in the conversation's frozen set).curatedAgentTools(offer gated on the write cap, mirroring the row tool'shasDatabasesgate) →intersectAgentTools(requiresintents.dispatch:propose-code-file) →makeDispatchToolinterception (stages a draft; never dispatches, never writes).entities.create("brainstorm/CodeFile/v1", { path, content, language, sizeBytes, lineCount, createdAt, updatedAt }), mirroring the Code editor's own new-file create ({path, content, language}; the editor seeds its Y.Doc buffer from thecontentproperty on first open — real-shell verified below). Provenance-stamped with the conversation id like every approved proposal; the created-object chip (its label now falls back topath) opens the file via the cap-checkedopenintent.@brainstorm-os/sdk/code-highlightShiki tokenizer (dynamic-imported so shiki core stays out of the agent's initial chunk; plain-text fallback on any failure; skipped above 20 k chars). No new highlighter dependency. TheLanguageKey → shiki idmap was extracted from the code-editor to@brainstorm-os/sdk/language-detect(shikiIdForLanguage) at copy two; the code-editor now delegates to it.BRAINSTORM_DEMO_AGENT=appforgeselects a scripted sequence proposinghello-app/manifest.json+hello-app/index.htmlthen finalizing; any other value keeps the existing follow-up reel. Never registered outside the env gate.propose.codeFile.label,propose.kind.codeFile,propose.field.path,propose.codeFile.preview) across all six catalogs (en/es/de/fr/it/pt).createT{name}interpolation only, no ICU.Security posture (prompt-injection)
The staged-draft contract stays airtight: model output NEVER reaches
entities.create— apropose-code-filecall only stages a bounded draft; the write happens exclusively on the human approve gesture. Specifically tested:contentrenders as inert text in the preview (React text nodes only; test asserts no<img>/<script>element is created and no handler runs);pathis a vault-resident organizing string — sanitized (control/zero-width/bidi strip + 200-char clamp), never handed to any filesystem API; traversal-shaped input stays inert text.Real-shell verification (launch-orchestrator bug-class check)
Drove the worktree-built shell (packaged main, real broker/ledger/worker pipeline,
BRAINSTORM_DEMO_AGENT=appforge) with Playwright:propose-code-filecards staged in the tray (manifest.json → JSON chip, index.html → HTML chip, both with highlighted previews) — proving the tool is actually offered and dispatched in a real conversation, not just in unit tests;CodeFile/v1entities created, created-object chips listed both paths;index.htmlopen with the exact proposed content, HTML highlighting live, "No problems" — the acceptance test (agent-approved file opens cleanly in Code) passes.Also verified in the capture: the assistant message carries two "Used propose-code-file" chips, the approval notice reads Added "hello-app/index.html" to your vault, and the "Created in this chat" chips list both paths (the
pathlabel fallback) and open via the cap-checkedopenintent.Four-path evidence (tray card)
appforge3-shots/).tabIndex=0, scrollable with arrow keys — WCAG scrollable-region-focusable) → "Add to vault" → "Discard". Approve is disabled when the filename is cleared (never a blank write).role="group"labelled "Proposed Code file: "; the filename input is label-associated ("Filename"); the preview is arole="region"named "Code preview for "; approval confirmation is arole="status"live region ("Added "" to your vault").Tests
apps/agent/src/logic/propose-code-file.test.ts(new, 15 tests) — validation, bounds, sanitization, language inference, ack contract, cap gate.propose-persist.test.ts— create shape mirrors the code editor's; exactly one create; injection content persists as plain data.turn.test.ts— staged never dispatched; refusal acks; unknown-tool fail-closed.proposal-tray.test.tsx— inert rendering of injection content; single editable line; focusable preview; language chip.agent-tools.test.ts/manifest.test.ts— offer gating; manifest covers the curated footprint.demo-agent-provider.test.ts— mode selection; script shape; clamping.bun run typecheck(packages + apps) andbun run lint(incl. css-tokens, app-i18n, native-select gates) clean.🤖 Generated with Claude Code