Release/1.11.1 - #727
Merged
Merged
Conversation
The safe, non-LLM slice of Workstream A6. `MemorySpaceService.consolidate`
(editor+) + `POST /memory/spaces/{id}/consolidate` → a `ConsolidationReport`.
Auto-fixes only storage hygiene: orphaned content-addressed objects — keys
under a space's prefix that no manifest entry or the index pointer references
(leaks from crashed/raced writes) — are GC'd (new `MemorySpaceStore.list_keys`
drives it). Everything that needs a judgment call is *reported, not mutated*:
- duplicate content across slugs (same content hash) — which slug survives is
semantic, so it's flagged, never auto-merged;
- dead `[[slug]]` wikilinks in MEMORY.md — reported; opt-in `stripDeadLinks`
unlinks them (they point nowhere) while preserving the surrounding prose;
- over-cap entry counts (`MEMORY_SPACE_INDEX_CAP`, default 200) — flagged, never
auto-evicted.
This deliberately does not merge/evict/rewrite durable memory — that's deferred
to the LLM consolidation pass (Workstream B era), which extends this exact
`consolidate()` seam once agentic writes create real duplication/staleness to
act on. On-demand only for now; scheduler/threshold auto-run and SPA surfacing
are follow-ups.
Tests: 8 service (healthy report, orphan GC + skip, dup-report-no-merge,
dead-link report + strip-keeps-prose, over-cap flag, editor gate) + 4 route
(report shape, no-body, viewer 403, flag-off 404) + 3 store (list_keys prefix
scoping / empty / disabled). 104 memory + boundary tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…6-consolidation feat(memory): deterministic consolidation health pass (A6)
Captures the strategy for the "Agent Designer" (Agent Harness Editor): a new authoring surface that composes an Agent from RBAC-governed primitives (instructions, model, KBs, tools, skills, Memory Spaces, + future), replacing the term/feature "Assistant." Locks the load-bearing decisions: own a primitive-agnostic Agent contract and federate AgentCore Registry later rather than build on it (adopt-with-boundary precedent); evolve the assistant store in place (no parallel table); a uniform bindings[] model with the model as a governed single-select; RBAC = compose the five existing per-primitive access checks (incl. ModelAccessService), not a new system; design-time filter + run-time re-resolution per invoker with block-on- missing v1; ship memory-consumption as a thin vertical slice before the full Designer. Phasing 0–5 + later AWS federation; supersedes the memory spec's "extend the Assistant" §B1 framing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…signer-spec docs(agent): Agent Designer spec — unified primitive-binding surface
…dels Phase 1 (PR-1) of the Agent Designer. Pure library, zero behavior change: legacy Assistants read unchanged and no caller passes the new fields yet. - AgentModelConfig (D3 governed single-select; field is model_settings/ alias modelConfig to dodge pydantic's reserved model_config — R3) - AgentBinding (open kind on read, KNOWN_BINDING_KINDS for request validation) - optional model_settings + bindings on Assistant (additive) - compat.effective_bindings/to_agent_view (D2): absent bindings synthesize a knowledge_base binding reffing the assistant id (KB's only stable identity, F4 deferred — R4); absent model maps to None, never fabricated (R1) - Decimal-safe serialization for modelConfig.params floats Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 1 (PR-2). The Agent fields now round-trip through the rag-assistants store and are validated at write time by composing existing RBAC checks (D4). Legacy clients are unaffected: the SPA sends none of the new fields and the AssistantResponse surface is unchanged. - service.create_assistant/update_assistant thread bindings + model_settings; to_ddb_safe on write / from_ddb on read so modelConfig.params floats survive DynamoDB (Decimal); explicit [] replaces bindings, absent leaves them untouched - app_api/agents/services/binding_validation.py composes model access (ModelAccessService), memory resolve_permission (viewer+/editor+), the implicit-KB rejection, and inert shape-only checks for tool/skill (D4/D5) - assistants POST/PUT validate then pass through; validation raises 4xx outside the create handler's generic except so it isn't masked as 500 - tests: validation matrix (incl. inert no-RBAC guarantee), persistence round trip, legacy no-field read Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…signer-phase1 feat(agents): Agent Designer Phase 1 — contract + binding persistence (foundation)
Phase 1 (PR-3). A governed Agent read/write surface over the evolved assistant store: same shared service functions and identity-based access gates as /assistants, but returning the Agent shape (compat.to_agent_view -> AgentResponse) so callers see modelConfig + bindings. Legacy ids valid unchanged. - feature_flags.agents_enabled(): AGENTS_API_ENABLED, default OFF (memory-spaces pattern) — surface 404s while off, ships incrementally, /assistants unaffected - app_api/agents/routes.py: require_agents_enabled 404-gate; draft/create/list/ get/update/delete + 4 shares endpoints, delegating to apis.shared.assistants service and reprojecting via to_agent_view; create/update run binding_validation - AgentResponse/AgentsListResponse/AgentSharesResponse (agentId == assistantId) - main.py mounts the router - test-chat + document sub-routes deliberately excluded (would force a 2nd architecture import-boundary exception); list is owner+shared (public/pagination parity deferred to the Phase-4 Designer) - tests: 404-gate, agentId/bindings projection, CRUD permission gating, shares Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…signer-agents-router feat(agents): Agent Designer Phase 1 — /agents surface (dark)
Phase 1 (PR-4). Completes Phase 1 deployability: the /agents surface can now
be turned on per environment. No new AWS resources — the flag only gates
whether the routes 404 (the assistant store it reads is always present).
- config.ts: AgentsConfig { enabled }; CDK_AGENTS_API_ENABLED (default off,
empty-string-safe) or an `agents.enabled` cdk.json context, mirroring
memorySpaces exactly
- app-api-environment.ts: AGENTS_API_ENABLED env on app-api
- infra tests: default-off / opt-on assertion; mock-config default
- docs: agent-designer.md Phase-1 status (+ the two refinements and the
Oliver-dogfood-gated-on-Phase-3 note); CHANGELOG [Unreleased]
The live Oliver dogfood (D6) is deliberately NOT included: it needs Phase 3
harness resolution + Memory Spaces deployed to the target env before a
memory_space binding resolves at invocation. Tracked as the Phase 3 payoff.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…signer-flag-plumbing feat(agents): Agent Designer Phase 1 — AGENTS_API_ENABLED flag plumbing + docs
…se 3 PR-0) Phase 3 harness resolution runs inside inference-api, so the runtime needs the same flag the app-api surface got in #593. Default off, mirrors the app-api wiring; without it the harness ignores Agent bindings entirely (today's behavior). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…hase 3 PR-A) The Harness now re-resolves an Agent's governed modelConfig against the INVOKING user (D5) and applies it to model selection. Absent modelConfig ⇒ the model resolves exactly as today; gated on AGENTS_API_ENABLED (off in all envs still). - agent_binding_resolver.py: resolve_agent_invocation() checks the pinned model against AppRoleService.can_access_model for the invoker (R2 — same gate the harness uses elsewhere), returns a model_override or raises AgentBindingBlockedError. inference-api imports apis.shared only (boundary-safe) - routes.py /invocations: resolve after assistant load, before the KB search; on block, stream a conversational stream_error via stream_conversational_message (D5 block-with-message, no silent downgrade). Override wins at model resolution; agent params sit beneath request params, still flowing through admin bounds/locks - tests: allowed→override, denied→block (checked vs invoker), no-modelConfig→ empty plan (no RBAC call); 57 existing inference/chat tests unchanged Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…se 3 PR-B) Shared, sync helper that resolves a memory_space binding's alwaysLoad specs into injectable text fragments — the read side of Workstream B. - resolve_always_load(): MEMORY.md → index; latest:<type>/<prefix> → most-recent matching manifest entry (defines that scheme, which had no resolver); bare slug → entry. Missing entries skipped (never fails a turn). Byte-budgeted with a truncation marker pointing at memory_read (MEMORY_INJECTION_MAX_BYTES, ~24KB) - render_memory_block(): delimited system-prompt block; empty for a fresh space - reads go through MemorySpaceService (re-checks viewer+ internally) — no leak - 11 unit tests against a fake service Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…(Phase 3 PR-C) The Harness now resolves an Agent's memory_space binding against the invoking user and injects the space's alwaysLoad content (read-only) into the system prompt — the first half of the Workstream B / Oliver payoff. - agent_binding_resolver: _resolve_memory() checks the invoker's grant via MemorySpaceService.resolve_permission (D4); blocks (D5) when the flag is off, the space is gone, or a readwrite binding meets a below-editor invoker (no silent read-only downgrade). Returns ResolvedMemoryBinding (v1: first binding) - routes.py: after prompt assembly, hydrate via resolve_always_load (asyncio.to_ thread; MemorySpaceService re-checks viewer+) and append render_memory_block; best-effort — a memory-read hiccup never fails the turn - tests: memory grant matrix (none/flag-off/missing/read-viewer/readwrite-viewer- block/readwrite-editor/invoker-identity); 78 inference+compat tests green Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…signer-harness-resolution feat(agents): Agent Designer Phase 3 — harness resolution (model + read-only memory)
…el agents run-app-api.sh launches app-api with `cd src/apis/app_api && python main.py`, putting that directory on sys.path[0]. The new apis/app_api/agents/ package (Agent Designer surface, #591/#592) then shadowed the top-level `agents` package, so `admin/quota/routes.py`'s `from agents.main_agent...` resolved into it and crashed startup with `ModuleNotFoundError: No module named 'agents.main_agent'`. Tests never caught it — pytest runs from backend/ where `agents` resolves correctly. Production is unaffected (the container runs `uvicorn apis.app_api.main:app` from WORKDIR /app, so sys.path[0] is /app). Rename the package apis/app_api/agents → apis/app_api/agent_designer (and its test dir) so its name can't collide with the top-level `agents` package. Pure rename: the /agents URL surface, router, and behavior are unchanged. Verified: `import agents.main_agent.quota.repository` and the full app-api module now load from src/apis/app_api; 34 agent/boundary tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ge-name-collision fix(agents): rename app_api.agents package to unbreak local app-api startup
…(Phase 3) Completes the Workstream B write side: an Agent with a memory_space binding now gets memory_list / memory_read (always) and memory_write (readwrite bindings only) at invocation — Oliver can read AND write his space. - agents/builtin_tools/memory_spaces/: closure-scoped factories capturing the binding's space id + invoker identity, MemorySpaceService via asyncio.to_thread (artifact-tools pattern). Every call re-checks the grant inside the service (viewer+ read / editor+ write), so a revoked grant becomes an error tool-result mid-session, never a leak - routes.py _build_memory_tools(): appended to the extra_tools seam only when a memory binding resolved; write tool gated on access==readwrite. extra_tools agents are never cached → tools closed over user A can't be served to user B - not gated on enabled_tools: the governing capability is the Agent's binding, not the user's tool picker (same reasoning as artifact tools) - tests: tool success/permission-error/not-found matrix + seam counts (none=0, read=2, readwrite=3); 84 inference+tool tests green Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Memory Spaces is a complete feature (CRUD + SPA panel + agent binding), so it should ship enabled for every deployer/forker — opt-out, not opt-in — matching the kbSync / scheduledRuns convention. The table + bucket are already provisioned unconditionally in PlatformStack, so this only flips the runtime MEMORY_SPACES_ENABLED env var; no new infra footprint. - config.ts: memorySpaces.enabled default true (empty/unset workflow var = on, only literal "false" disables); interface + block docs updated - platform.yml: forward CDK_MEMORY_SPACES_ENABLED (kill switch) and CDK_AGENTS_API_ENABLED (per-env enable for the still-default-off /agents surface, so dev can turn it on for dogfooding without a code change) - app-api-environment.ts: comment reflects default-on - config.test.ts: 5 tests locking default-on + kill-switch + context override Agent Designer (AGENTS_API_ENABLED) stays default OFF until the Phase-4 Designer UI ships — a headless /agents surface helps no forker. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…mory-tools feat(agents): Agent Designer Phase 3 — memory_* tools (write side)
…paces-default-on chore(memory): default Memory Spaces ON with a kill switch
Ships the Agent Designer authoring surface (Phase 4) and its Phase-2
precursor, the bindable-primitives catalog. The pickers can't exist
without the catalog, so both land together.
Backend (Phase 2 catalog):
- GET /agents/bindable?kind=model|tool|skill|knowledge_base|memory_space
returns an RBAC-filtered palette, composing the 5 existing per-primitive
access services (D4); no new RBAC invented. Uniform BindableItem shape so
every picker consumes one contract. Route declared before /{agent_id} so
the literal path isn't captured. knowledge_base → empty (welded/synthesized);
skill/memory_space → empty when their feature flag is off. Behind
AGENTS_API_ENABLED.
- Fix binding_validation._validate_model: it resolved models via
get_managed_model() — a primary-key lookup on the internal UUID — but
modelConfig.modelId is the Bedrock model_id that the runtime resolver, RBAC
(permissions.models) and invocation all key on. A valid model would have
been rejected 400 on save the moment the picker set one. Now matches by
model_id, consistent with the whole chain.
Frontend (Phase 4 UI):
- New agents/ feature dir (separate from the Assistants editor): Agent +
Binding + BindableItem TS contracts, a thin AgentApiService, and an
AgentService signal facade with the accessible$ 404-probe idiom + a
per-kind bindable cache.
- Agents list page (model + binding-count badges) and an agent-form page:
persona/emoji/tags/starters, a required single-select model picker (D3),
tool/skill multi-select chips, and a memory-space picker with access
(read/read+write — write disabled unless editor+ on the space, per D5) and
an alwaysLoad MEMORY.md toggle. KB shown read-only. Sharing reuses the
assistants share dialog (agentId == assistantId).
- Routes agents / agents/new / agents/:id/edit, plus a sidenav "Agents"
entry gated on the accessible$ probe.
Tests: backend 1552 pass (9 new catalog + 4 new route + 3 updated
model-validation); SPA build + tsc clean, ng test 7 AgentService + 11
sidenav specs pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The sidenav constructor now probes agent accessibility (void agentService.loadAgents()), but sidenav.spec.ts didn't provide a mock AgentService, so the real service fired an unstubbed HTTP GET /agents that rejected with status 0 — vitest fails the run on unhandled errors even though all assertions passed. Provide a mock AgentService (accessible$ signal + no-op loadAgents) mirroring the schedule/memory stubs, plus parity tests for the probe + showAgents gate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…igner-phase-4-ui-666b5e feat(agent-designer): Phase 4 — Agent Designer UI + bindable catalog API
The catalog lists models via ModelAccessService.filter_accessible_models, but design-time write validation used can_access_model — and the two disagree. filter_accessible_models grants access whenever the model id is in the user's AppRole permissions.models; can_access_model only honors that membership when the model record ALSO carries a non-empty allowed_app_roles. So a model granted purely via the user's AppRole (empty allowed_app_roles) was listed by the picker but rejected on save with a 403 — and the runtime resolver (membership-based) would actually have allowed it. Validate the model with the same filter_accessible_models predicate the catalog uses, so 'if the palette offers it, the write accepts it' holds by construction. Adds a regression test for the empty-allowed_app_roles grant. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…igner-phase-4-ui-666b5e fix(agent-designer): model write-check rejects models the picker lists (403)
…ew badges Match the Scheduled Runs treatment for the two other preview surfaces: Memory Spaces and Agents now also require the system_admin AppRole (showX() && isAdmin()) in addition to their accessibility probe. Adds a small amber 'Preview' badge to all three nav entries (Agents, Memory Spaces, Scheduled Runs) so their preview status is visible. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…av-admin-gate feat(sidenav): gate Memory Spaces + Agents to system-admin, add Preview badges
…per-invoker RBAC) An Agent's `tool` bindings were stored by the Designer but inert at run time — the free-select tool picker fully drove the toolset regardless of what the Agent bound. This resolves them, mirroring the shipped `modelConfig` override: - Run-time (inference-api): `resolve_agent_invocation` now returns `plan.tools` (`ResolvedTools`). When an Agent binds tools they *replace* the request's `enabled_tools` for the turn; each bound tool is re-checked against the INVOKING user via `AppRoleService.can_access_tool` (the same AppRole gate the harness uses for model, R2) and a missing tool blocks the turn with a message (D5). No tool binding ⇒ `plan.tools is None` ⇒ the request drives the toolset exactly as today. Wired at the existing `extra_tools`/`get_agent` seam via `effective_enabled_tools` (also feeds the spreadsheet/artifact tool gates + attachment guidance/inventory). - Design-time (app-api): `tool` dropped from `_INERT_KINDS`; a bound tool must be in the author's palette (`ToolCatalogService.get_user_accessible_tools`, the same source the picker fetches — "if the palette offers it, the write accepts it", cf. the model check). The palette is resolved once per write. `skill` bindings stay inert here (their run-time fold interacts with agent_type/skill resolution — a follow-up slice). Tests: 6 resolver cases (override, dedupe, block-on-missing, per-invoker, none→passthrough) + 5 validation cases (accessible/inaccessible/empty-ref/fetch-once/lazy). Full backend suite green (4621 passed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
_user_files_bucket() previously defaulted to a literal 'user-files' bucket when S3_USER_FILES_BUCKET_NAME was unset, which surfaced a missing runtime env var as a confusing S3 PermanentRedirect/AccessDenied. It now raises _StorageNotConfiguredError, and create/modify/read short-circuit before the Code Interpreter run with a clear 'storage is not configured' message.
…storage-config Fail loudly when Word doc storage bucket is unconfigured
…nto-develop-1.9.0 Backmerge main into develop after 1.9.0
- Add excel_spreadsheet_tool.py with create/modify/list/read tools for .xlsx files - Create office/_storage.py module with shared Code Interpreter and S3 storage utilities for Word and Excel - Implement Excel toolset integration in inference_api chat routes with tool injection - Add "Excel Spreadsheets" catalog entry to DEFAULT_TOOLS in seed_bootstrap_data.py - Replace word-document-renderer with generic file-download-renderer for all generated office documents - Extend Word document tool to use shared office storage module - Update test fixtures and chat routes to support Excel tool provisioning - Generated Excel files are persisted to S3_USER_FILES_BUCKET_NAME and appear in chat Files panel
…dsheet-tools feat(office-tools): add Excel spreadsheet toolset
Add powerpoint_presentation_tool.py with create/modify/list/read tools for .pptx files, mirroring the Word/Excel toolsets and reusing the shared office/_storage.py module (Code Interpreter + user-files storage). python-pptx runs in the sandbox; generated decks persist to S3_USER_FILES_BUCKET_NAME and render inline via the shared file_download card. - Gate key create_powerpoint_presentation injects the full toolset via _build_powerpoint_presentation_tools in inference_api chat routes - Seed 'PowerPoint Presentations' into DEFAULT_TOOLS; update seed test counts (7->8) and assertions - Add pptx MIME/extension to ALLOWED_MIME_TYPES/ALLOWED_EXTENSIONS - Frontend: file-download-renderer picks an orange presentation icon for .pptx/.ppt - Fold slide-design guidance (palettes, typography, layout variety, anti-patterns) into the create docstring in lieu of porting AWS's JS/Deno skills framework
…ction create_powerpoint_presentation gains an optional template_name: when given, the deck is built on top of that .pptx (resolved from this chat's files) so it inherits the template's slide masters, layouts, theme colors, fonts, and master/layout branding. The template's example slides are stripped first (removing slide-id refs leaves masters/layouts intact), so it starts themed-but-empty. Add list_powerpoint_layouts tool (equivalent of the AWS sample's get_presentation_layouts): reports a template's layout indices, names, and placeholders so the model targets the right layout/placeholders when building on it. Provisioned by the same create_powerpoint_presentation gate key (no new catalog entry). Wired the new tool into _build_powerpoint_presentation_tools in inference_api chat routes.
The SPA distribution's CloudFront ResponseHeadersPolicy only ever opened
frame-src to 'self' and the artifacts origin, so every domained deploy
CSP-blocked the MCP App sandbox iframe (mcp-sandbox.{domain}) — the
sandbox side's frame-ancestors was locked to the SPA origin from the
start, but the SPA side was never extended. Localhost dev bypasses
CloudFront's response headers entirely, which masked the gap through
live verification.
Thread the already-computed mcpSandboxProxyOrigin from PlatformStack
into SpaDistributionConstruct and append it to frame-src. Add a
regression test that synths a domained PlatformStack and asserts both
iframe origins are present in the frontend headers policy.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…c-mcp-sandbox fix(infra): allow the mcp-sandbox origin in the SPA frame-src CSP
Generic agent file surface over the existing user-files store, per
docs/specs/session-workspace-tools.md:
- apis/shared/files/workspace.py: DynamoDB-metadata-first list (session +
user scope), bounded ranged text reads (48KB/call with offset
continuation), binary by presigned-URL reference only (never base64),
and a write path mirroring word_document_tool._store_document (READY
FileMetadata row, source="agent", quota check + increment). Missing
identity raises — no default-user fallback.
- FileMetadata gains an additive display-only `source` field
(default "upload").
- agents/builtin_tools/workspace_tools.py: make_workspace_{list,read,
write}_tool closure factories; workspace_write returns the inline
download-card contract (ui_type "workspace_file").
- Wiring: _build_workspace_tools in inference chat routes behind the
single "workspace_files" catalog gate key (seeded, enabledByDefault
false) and the WORKSPACE_TOOLS_ENABLED default-on kill switch
(apis/shared/feature_flags.py).
- SPA: workspace_file ui_type routes to the existing download-card
renderer.
- 48 new backend tests; seed tests updated for the 7th catalog entry.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nto-develop-1.10.0 Backmerge main into develop (1.10.0)
…presentation-tools feat(office-tools): add PowerPoint presentation toolset
…rkspace-tools # Conflicts: # backend/scripts/seed_bootstrap_data.py # backend/src/apis/inference_api/chat/routes.py # backend/tests/test_seed_system_admin_jwt.py # frontend/ai.client/src/app/session/components/message-list/components/inline-visual/renderers/word-document-renderer.component.ts
…workspace-tools feat: session workspace tools (workspace_list/read/write)
create_artifact's content_type defaults to text/html, so a request like "create a markdown recipe artifact" produced raw Markdown stored verbatim as HTML — the iframe then showed run-together `#`/`**` source and the card badge read "HTML" instead of rendering the document. Add a server-side safety net: HTML-typed content that lacks a standalone HTML document shell (`<!doctype html>` / `<html>`) is reclassified as text/markdown before storage, so the writer wraps it into a proper render document and the DynamoDB row (badge + render-Lambda mapping) stays truthful. Real HTML documents, Markdown, and other MIME types pass through untouched. Applied in both create_artifact_record and update_artifact_record (the update path covers the inherited-type case where there is no argument for the model to get right). Also steer the model up front: the create_artifact docstring now says to author prose (reports, docs, recipes, mostly-text) as Markdown and to honor an explicit "markdown" request, reducing how often the backstop fires. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-markdown-coercion fix: render markdown artifacts authored under the default HTML type
Sonnet 5 sometimes persists a reasoningContent block with empty reasoningText.text and no redactedContent (a signature-only thinking block). The signature must stay in the message because Bedrock requires it on follow-up calls, but the block has nothing to display. The live stream parser already guards on reasoningText, but the history- rehydration path bypasses it and reaches displayBlocks, which pushed a Thinking display block for any truthy reasoningContent object. The reasoning-content component then renders its "Thinking" header unconditionally, so an empty collapsible appeared on reload. Guard the render decision with hasRenderableReasoning() so a reasoningContent block is only painted when it has reasoning text or redacted content -- mirroring the component's own visibility logic and the live parser's guard. Display-only: the block is left untouched in the persisted message to preserve the signature/prompt-cache contract. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…mpty-thinking-blocks-9f8bda fix: don't render empty "Thinking" blocks from signature-only reasoning
Markdown artifact records keep content_type=text/markdown but S3 holds the writer's HTML render wrapper (raw Markdown base64-embedded in a <script id="md-src"> block). The download path served those wrapper bytes verbatim and mapped text/markdown -> html, so saving a Markdown artifact produced a .html file of the render scaffolding instead of the .md the user authored. On download (?download=1) of a Markdown record, recover the embedded raw Markdown source and serve it as text/markdown with a .md extension. Works for existing artifacts (source is already embedded — no re-storage). If the embed marker is ever absent (older render / template drift), fall back to the wrapper bytes as .html so the download never fails. Rendering in the preview panel is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nto-develop-1.11.0 Backmerge: main into develop (1.11.0)
…download-format-d759c8 fix: download markdown artifacts as .md source, not the HTML wrapper
Patch release fixing Markdown artifact downloads that saved the HTML render wrapper instead of the authored .md source. - fix: download markdown artifacts as .md source, not the HTML wrapper (#726) Backend-only (artifact-render Lambda); no dependency changes, no CDK deploy. Existing artifacts fixed at download time with no re-storage. Co-Authored-By: Claude Opus 4.8 <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.
Patch release 1.11.1.
What's in it
A single bug fix landed on
developsince 1.11.0:.htmlfile of the render scaffolding instead of the authored.md. The download path now recovers the embedded raw Markdown and serves it astext/markdownwith a.mdextension. Works for existing artifacts (source already embedded, no re-storage); falls back to the wrapper.htmlif the embed marker is absent. Preview-panel rendering unchanged.Version
VERSION1.11.0 → 1.11.1; manifests + lockfiles synced viascripts/common/sync-version.sh(--checkpasses).Deployment
Backend-only — no new AWS resources, no dependency changes, no CDK deploy. Ship the artifact-render Lambda via
backend.yml. No per-environment action; existing Markdown artifacts are corrected at download time.After merge
Squash-merge into
main, then backmergemain→develop(merge commit, per release workflow).🤖 Generated with Claude Code