Skip to content

Release/1.11.1 - #727

Merged
philmerrell merged 1751 commits into
mainfrom
release/1.11.1
Jul 24, 2026
Merged

Release/1.11.1#727
philmerrell merged 1751 commits into
mainfrom
release/1.11.1

Conversation

@philmerrell

Copy link
Copy Markdown
Contributor

Patch release 1.11.1.

What's in it

A single bug fix landed on develop since 1.11.0:

  • 🐛 fix: download markdown artifacts as .md source, not the HTML wrapper (fix: download markdown artifacts as .md source, not the HTML wrapper #726) — downloading a Markdown artifact previously saved a .html file of the render scaffolding instead of the authored .md. The download path now recovers the embedded raw Markdown and serves it as text/markdown with a .md extension. Works for existing artifacts (source already embedded, no re-storage); falls back to the wrapper .html if the embed marker is absent. Preview-panel rendering unchanged.

Version

VERSION 1.11.0 → 1.11.1; manifests + lockfiles synced via scripts/common/sync-version.sh (--check passes).

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 backmerge maindevelop (merge commit, per release workflow).

🤖 Generated with Claude Code

philmerrell and others added 30 commits July 7, 2026 16:22
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>
romanmeredith-rgb and others added 26 commits July 20, 2026 09:39
_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>
@philmerrell
philmerrell requested a review from a team July 24, 2026 16:17
@philmerrell
philmerrell merged commit d5a8360 into main Jul 24, 2026
11 checks passed
@philmerrell
philmerrell deleted the release/1.11.1 branch July 24, 2026 16:43
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.

5 participants