## Summary
Prior PR removed the open-gen-ui feature but left several loose ends.
This PR completes the scrub:
1. **Source YAML** — removed `open` profile + `open-gen-ui` entries from
`showcase/shared/constraints.yaml` (was missed before; would have
re-introduced `open-gen-ui` on next generator run)
2. **Schema enum** — dropped `"open"` from `generative_ui` enum in
`showcase/shared/manifest.schema.json`
3. **Test fixture** — `invalid-genui-manifest.yaml` now uses
`[unknown-profile]` instead of `[open]`; tests still pass (validator
rejects unknown profiles)
4. **Manifest descriptions** — all 17
`showcase/packages/*/manifest.yaml` files: "5 GenUI rendering
strategies" → "4" (open-gen-ui was the 5th; now removed)
5. **Derived JSON regen** — `registry.json` regenerated cleanly via
`generate-registry.ts`
6. **Smoke-test filter fix** — `integration-smoke.spec.ts:383` now reads
top-level `i.deployed` instead of stale `i.starter?.deployed`. The old
filter returned 0 starters post-regen (silent CI skip every 6h); new
filter correctly gates on canonical top-level field.
## Context
- `showcase/shell/src/data/demo-content.json` is also regenerated but
NOT committed because it exceeds the lefthook 1MB binary-size cap
(pre-existing repo condition, separate from this PR).
- 3 starters (mastra, crewai-crews, claude-sdk-typescript) that had
manually-patched `starter.deployed: false` are now covered by smoke —
verified all 3 starter URLs respond HTTP 200 live.
## Test plan
- [ ] CI green
- [ ] Post-merge `starter-smoke` workflow picks up all 17 starters (not
zero, as was the silent broken state)
- [ ] No `open-gen-ui` references remain anywhere in showcase/
## Summary
- **CI workflow consolidation**: Merged commitlint into static_quality,
added concurrency groups to test workflows, added path filters to
publish-commit, fixed static_danger matrix bug and python-sdk
concurrency
- **Showcase deploy refactor**: Replaced 23 duplicate jobs with a
dynamic matrix strategy (~1000 lines removed)
- **Preview capture → MP4**: Switched from GIF to optimized MP4 (H.264,
CRF 28, faststart), uploads to GitHub Release `showcase-previews`
instead of committing blobs to the repo. DemoCard uses `<video autoplay
muted loop>` for hover previews.
## Test plan
- [x] Local MP4 capture test passed (langgraph-python, 14KB, 8s, H.264
400x300 @ 10fps)
- [ ] CI workflows pass on this branch
- [ ] Capture workflow produces and uploads MP4 to release
- [ ] DemoCard hover shows video preview when URL is populated
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
Fixes#2296
The Markdown component had hardcoded remark/rehype plugins with no way
for consumers to add their own. This adds optional `remarkPlugins` and
`rehypePlugins` props that are spread into the existing plugin arrays,
enabling custom markdown processing.
## Test plan
- [ ] Verify custom remarkPlugins are applied alongside built-in plugins
- [ ] Verify custom rehypePlugins are applied alongside built-in plugins
- [ ] Verify Markdown renders correctly without custom plugins (no
regression)
Closes#3217
When `getLanguageModel()` returns null and provider/model are undefined
(as with LangChainAdapter), the code constructed `"undefined/undefined"`
as a model string, causing a cryptic "Unknown provider" error.
Now checks each source of model info explicitly and throws a clear error
message directing users to provide an explicit agents config when using
adapters that don't expose model metadata.
Split from #3838.
## Summary
- Widens the python dependency from `>=3.10,<3.13` to `>=3.10,<4`
- Adds Python 3.13 classifier to enable installation on newer Python
versions
Closes#3156
## Summary
- `convertJsonSchemaToParameter` now handles null-union types
(`["string", "null"]`) by filtering out `"null"` and recursing with the
resolved scalar type
Closes#2651
## Test plan
- [x] `@copilotkit/shared` tests pass
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
- Added `py.typed` marker file to the Python SDK for PEP 561 compliance,
enabling type checker support
Closes#2921
## Test plan
- [x] Python SDK builds successfully with marker included
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The scrub and #4084 touched the same surface: #4084 re-added an `open:`
generative_ui profile listing `open-gen-ui`/`open-gen-ui-advanced`, and
re-added both features to `constrained-explicit.allowed`. Extending the
branch's scrub to both re-additions keeps the semantic consistent with
the schema (which already dropped `open` from the approaches enum).
- `showcase/shared/constraints.yaml`: drop `open-gen-ui` +
`open-gen-ui-advanced` from `constrained-explicit.allowed`; drop main's
re-added `open:` profile entirely.
- `showcase/packages/langgraph-python/manifest.yaml`: drop the now-orphan
`open-gen-ui` + `open-gen-ui-advanced` feature and demo entries
(validator confirmed they had no allowed approach left).
- Regenerated `showcase/shell/src/data/registry.json` + sibling
`shell-docs`/`shell-dojo` registries and `constraints.json` via
`pnpm --dir showcase/scripts generate-registry`. All 17 integrations
validate.
`feature-registry.json` intentionally still defines both features — the
original scrub commits (2b996c54d, 27f886e59) left it untouched, so the
demo source files on disk also stay. Follow-up deletion if desired is
out of scope for this merge.
## CI hygiene fixes
Four direct-fix items surfaced during the 2026-04-16 QA/E2E blitz,
bundled as one PR with one commit per fix:
1. **`test_unit.yml` paths-ignore** — add `showcase/**` +
`sdk-python/**` (prevents spurious TS unit matrix runs on showcase-only
or sdk-python-only PRs). `sdk-python-old/**` was mentioned in the plan
but doesn't exist on main; only the two existing dirs are added.
2. **`test_doc-examples.yml`** — scope PR trigger to `branches: [main]`
(matches convention of other workflows).
3. **`e2e_dojo.yml`** — symmetric `.changeset` path filter on push+PR
(was asymmetric — already in the `dorny/paths-filter` step's `ts:` list,
so this aligns the trigger).
4. **`starter-smoke.yml`** — rename internal job id (`starter-smoke` →
`smoke-starter`) and artifact name pattern (cosmetic; matches the new
`test_<layer>-<target>` / `smoke-<layer>` naming convention; no external
consumers).
### Fix skipped
**`showcase_smoke-monitor.yml` slug normalization** (item #10 in the
Notion page) — inspecting the file, it already uses the
`showcase/packages/` slug convention (`ms-agent-python`,
`ms-agent-dotnet`, `strands`). The apparent inconsistency is actually in
`starter-smoke.yml`, whose matrix keys must stay as
`ms-agent-framework-*` / `strands-python` because they are literal
directory names in `examples/integrations/` (used as `working-directory:
examples/integrations/${{ matrix.starter }}`). So there is no actionable
change here — the two files use different slug conventions *by
necessity*, because they target different directories (deployed
`showcase/packages/*` vs. local `examples/integrations/*`). Flagging for
the author of the blitz notes in case the actual concern was something
else.
Refs: [Bugs Found During
Blitz](https://www.notion.so/3443aa381852812fb595c5118dd68818) items #3,
#8, #9, #12.
Addresses PR feedback on #3822.
Instead of plumbing remarkPlugins and rehypePlugins through the component
one prop at a time, MarkdownProps now extends Omit<Options, "children">
and spreads the remainder into the underlying ReactMarkdown. Consumers
can now pass any react-markdown Options field (urlTransform, allowedElements,
skipHtml, etc.) without further plumbing.
The three fields that need library-specific defaults (components,
remarkPlugins, rehypePlugins) are still destructured, merged, and memoized
internally so the built-in behavior is preserved and merged values keep
stable references across renders.
Dropping the custom memo comparator — now that arbitrary props flow
through, a fixed 4-key check would silently skip re-renders when
consumer-provided props change. React's default shallow-compare, combined
with the internal useMemo on merged values, gives correct behavior without
regressing the memoization for the common case.
Covers the three branches added by the fix:
- getLanguageModel() returns a LanguageModel -> wired into BuiltInAgent
- provider + model strings present -> composed as "provider/model"
- neither present (e.g. LangChainAdapter) -> throws CopilotKitMisuseError
with adapter name in the message, instead of silently producing
"undefined/undefined" and failing downstream.
Also guards the partial-info case where only one of provider/model is set
to ensure the regression doesn't re-emerge via a half-populated adapter.
## Summary
- `copilotkit_interrupt` now handles string and dict resume values from
LangGraph 1.x's `interrupt()`
- Previously crashed with `AttributeError: 'str' object has no attribute
'content'` or `KeyError: -1`
- Type-checks response: str returned directly, dict JSON-serialized,
list uses existing `[-1].content` path
## Test plan
- [x] Red-green test: string resume value returns without crash
- [x] Red-green test: dict resume value returns JSON string
- [x] Test: list resume value still works (existing behavior)
- [x] Full test suite passes (15/15)
Closes#3096
## Summary
- `LangGraphAGUIAgent.langgraph_default_merge_state` now calls
`model_dump()` on Pydantic Context objects before storing in copilotkit
state
- Previously stored raw Pydantic objects, causing JSON serialization
failures downstream
- Handles mixed types: Pydantic objects get `model_dump()`, plain dicts
pass through unchanged
- Matches the existing pattern already used in `CopilotKitMiddleware`
## Test plan
- [x] Red-green test: AG-UI Context objects stored as plain dicts, not
Pydantic
- [x] Red-green test: mixed Pydantic + dict context items all
serializable
- [x] Full test suite passes (15/15)
Closes#3690
## What does this PR do?
Adds a new `"pin-to-send"` value to `CopilotChatView`'s `autoScroll`
prop, matching ChatGPT's scroll behavior: when the user sends a message,
that message scrolls to the top of the viewport and stays there while
the assistant's response streams in below. The user reads at their own
pace — the viewport does not chase the bottom.
### New public API
```ts
autoScroll?: "pin-to-bottom" | "pin-to-send" | "none" | boolean
```
- `"pin-to-bottom"` — current behavior. Chases the bottom as content
streams (still the default).
- `"pin-to-send"` — **new.** Scrolls the latest user message to ~16px
from the top, maintains a dynamic bottom spacer so the message can
actually reach the top, and does not chase the bottom.
- `"none"` — no auto-scroll.
- Boolean back-compat: `true` → `"pin-to-bottom"`, `false` → `"none"`.
Existing consumers unchanged.
`AutoScrollMode` type is exported from the package barrel.
### Example usage
```tsx
<CopilotChat autoScroll="pin-to-send" />
```
### How it works
1. `CopilotChat` computes the latest user message ID from `messages` and
publishes `{ id, sendNonce }` via a new `LastUserMessageContext`. The
nonce increments on each new send so message edits also retrigger.
2. A new `usePinToSend` hook reads the context and, on each new send:
measures viewport + user message heights, sets a spacer `<div>`'s
height, then does a single smooth `scrollTo` to the user message's
offset.
3. A shrink-only `ResizeObserver` on content collapses the spacer as the
assistant's response fills space below, so there's no wasted empty
space.
4. `ScrollView` branches on the normalized mode: existing
`<StickToBottom>` path for `"pin-to-bottom"`, existing plain-div path
for `"none"`, new `PinToSendScrollContainer` for `"pin-to-send"`.
### Edge cases handled
- Thread restore with existing messages doesn't trigger a spurious
scroll.
- `scrollTo` target computed via `getBoundingClientRect` arithmetic —
robust across any CSS positioning.
- Virtualization: the spacer lives outside the virtualized list.
- Back-compat: `autoScroll={true|false}` produces byte-for-byte
identical DOM to pre-change.
### Tests
- 7 unit tests for `normalizeAutoScroll`.
- 4 TDD behavior tests for `usePinToSend` (spacer sizing, scroll target,
shrink-only ResizeObserver, cleanup).
- 5 integration tests for `CopilotChatView` covering all three modes +
boolean back-compat.
- Full `@copilotkit/react-core` suite: **85 files / 1120 tests pass,
zero regressions.**
- publint + attw green across all 13 packages.
- Existing `CopilotChatView.slots.e2e.test.tsx` (43 tests) unchanged.
## Related PRs and Issues
- (none — new feature)
## Checklist
- [x] I have read the Contribution Guide
- [ ] Docs site update to follow in a separate PR
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
- Split `server.mjs` into `liveness.mjs` (new entry) + `server.mjs`
(actual langgraph-api server).
- `liveness.mjs` binds `/ok` on :8124 first, then dynamic-imports
`server.mjs` to kick off the real langgraph bootstrap.
- `src/agent/package.json` `start` script now runs `node --import tsx
liveness.mjs` as the entry.
## Why
#4140 put the probe inside `server.mjs` under the belief it would bind
"synchronously before any await." ES module semantics say otherwise —
all top-level `import` declarations resolve before any module-body code
runs. Cold start of `@langchain/langgraph-api` + graph transpile takes
~4m30s on Railway, but watchdog grace (180s) + strikes (90s) = 270s
kills before the probe ever opens. Dynamic import defers the heavy load
until after liveness is already serving 200.
## Test plan
- [x] Local Docker build green (amd64)
- [x] Container serves /ok within <2s of launch (measured: 1.4s)
- [ ] Railway deploy green + smoke passes sustained
PR #4140 placed the liveness probe inside server.mjs under the belief it
would bind "synchronously before any await." That is not how ES modules
work — all top-level `import` statements resolve before any module-body
code runs. `@langchain/langgraph-api/server` takes ~4m30s to cold-import
on Railway (graph eval + tsx transpile), but the watchdog's 180s grace +
3x30s strikes = 270s kills the container ~4s before the probe would have
bound. Kill-loop forever.
Split into:
- `src/agent/liveness.mjs` (new entry): binds :8124/ok immediately using
only `node:http`, then dynamic-imports server.mjs so the heavy langgraph
bootstrap runs AFTER :8124 is already serving 200.
- `src/agent/server.mjs`: removed the now-dead sibling-probe code; comment
block explains why.
- `src/agent/package.json`: `start` script now runs `node --import tsx
liveness.mjs` instead of `server.mjs`.
- `entrypoint.sh`: comments updated to describe the new ordering.
Local smoke (docker build + run on linux/amd64): /ok returns 200 within
1.4s of container launch vs the previous ~4m34s.
## Summary
- Rewrite starter Dockerfile templates (`typescript`, `python`, `java`,
`dotnet`) as true multi-stage: builder has dev tools, runner is minimal.
No `pip`/`npm`/`pnpm`/`tsx`/`*-cli dev` in runtime stages.
- Encode prod-mode behaviors for `claude-sdk-typescript`, `mastra`,
`langgraph-typescript` directly in the TS template + generator
conditionals (folds in the pattern from #4132 for langgraph-ts; adds
parallel migration for claude-sdk-ts + mastra).
- Regenerate all 17 starter Dockerfiles.
- Rewrite all 17 package Dockerfiles to match the template shape
(multi-stage, venv builder for Python, 3-stage Python runtime, `USER
app`, etc.).
- Cold-start instrumentation on `claude-sdk-typescript` package
(absorbed from closed PR #4133).
- Pin `platforms: linux/amd64` in the showcase deploy workflow
(`docker/build-push-action` step) to match what Railway + GHCR require.
- Template `README.md` documenting multi-stage conventions, size target,
platform mandate, and the `dockerfile_hygiene` probe.
- `generate-starters.ts` strips the `langgraph-typescript` starter's
dead `server.mjs` + its `start` script (both reference deps only needed
in the package's prod-mode path).
## Why
Railway cold starts exceed the 180s watchdog grace when the runtime
stage boots via `langgraph-cli dev` / `npx mastra dev` / `npx tsx` — the
dev-mode work (TS compile, Rollup bundle, native libsql init) blocks the
health probe. #4132 proved the fix is to move this to image build time
for langgraph-ts. This PR generalizes that shape to all TS starters that
need it and reshapes every Dockerfile in the tree so runners no longer
carry dev toolchains.
Secondary outcomes:
- Smaller runtime images (pip/npm toolchain moved to builder only).
- Consistent `--platform linux/amd64` pinning end-to-end.
- No `pnpm install --frozen-lockfile` in the TS starter template
(starters are no-lockfile by design).
- `validate-pins` ratchet re-baselined to match the regenerated spec
strings.
## Test plan
- [ ] `docker build --platform linux/amd64` green for every starter (17)
and package (17) locally
- [ ] `pnpm -C showcase/scripts` vitest: 1085/1085 pass
- [ ] `tsc --noEmit -p showcase/scripts/tsconfig.json` clean
- [ ] `oxfmt --check` + `oxlint` clean on touched TS files
- [ ] validate-pins: count 110, hash matches updated baseline
- [ ] CI Showcase Validate green on PR HEAD
- [ ] Post-merge: showcase deploy for mastra + claude-sdk-typescript
goes green (currently flapping)
## Why
Adds an interim GitHub Actions cron that curls `/api/health` on each of
the 17 showcase starter services every 5 minutes. The goal is to keep
the agent's in-memory state warm — JIT tiers, module cache, connection
pools. Railway Pro tier doesn't sleep containers, but warm-state still
decays over idle periods.
This is a stopgap until the `showcase-ops` service (see Notion proposal
§2a) lands. Its smoke probe runs at the same cadence and will subsume
this workflow entirely.
## What
New file: `.github/workflows/showcase_keep-alive.yml`
- Schedule: `*/5 * * * *` (every 5 minutes) + manual `workflow_dispatch`
- Matrix over all 17 starter slugs (`ag2`, `agno`, `claude-sdk-python`,
`claude-sdk-typescript`, `crewai-crews`, `google-adk`,
`langgraph-fastapi`, `langgraph-python`, `langgraph-typescript`,
`langroid`, `llamaindex`, `mastra`, `ms-agent-dotnet`,
`ms-agent-python`, `pydantic-ai`, `spring-ai`, `strands`)
- Each cell: `curl -fsS --max-time 15
https://showcase-<slug>-production.up.railway.app/api/health`
- `fail-fast: false` + `continue-on-error: true` so one starter being
down doesn't cancel the others
- No Slack notification — `showcase_smoke-monitor.yml` already covers
alerting. This workflow is strictly keep-alive, not observability.
- Header comment marks it INTERIM and references the showcase-ops
cutover
## Interim marker
Remove this workflow once `showcase-ops` ships its smoke probe at the
same 5-min cadence (tracked in the Notion showcase-ops proposal).
## Test plan
- [ ] Verify workflow parses in the Actions UI after merge
- [ ] Trigger `workflow_dispatch` once manually; confirm all 17 cells
run and pass for healthy starters
- [ ] Confirm no Slack noise generated by this workflow
- [ ] Remove when showcase-ops cutover lands
## Summary
- Root cause: #4132 migrated langgraph-typescript from `langgraph-cli
dev` (which exposed `/ok` immediately) to `@langchain/langgraph-api`
`startServer`. That server DOES register `/ok` via its meta routes, but
only binds the port AFTER `registerFromEnv` imports the graph and spawns
the N workers. On Railway that window has been exceeding the watchdog's
180s startup grace + 90s strike budget, producing an infinite restart
loop (deploy `ddc88ef9` is flapping ~every 4m30s).
- Fix: move liveness off the heavy Hono path entirely. `server.mjs` now
starts a bare `node:http` server on port 8124 (configurable via
`HEALTH_PORT`) synchronously before any `await`, answering `GET /ok`
with `{"status":"ok"}`. `entrypoint.sh` watchdog now probes
`127.0.0.1:8124/ok` instead of `:8123/ok`.
## Why
The service has been 502-ing all day. Railway deploy `ddc88ef9` never
serves a single `/api/health` 200 despite `[server] LangGraph API
listening on 0.0.0.0:8123` landing in the logs — the `listening`
callback fires after the watchdog has already killed PID 4 and triggered
a restart.
Liveness on a sibling port is independent of whether :8123 has finished
its graph import, so the probe reflects what the watchdog actually cares
about: is the Node process alive and its event loop responsive. A true
hang (event loop pinned) still fails the probe and triggers container
restart, so hang-detection is preserved.
## Test plan
- [x] Local Docker build green (`--platform linux/amd64`, depot)
- [x] Local container `curl http://127.0.0.1:8124/ok` returns `HTTP=200
{"status":"ok"}` within 20s of boot
- [x] `[server] Liveness probe listening on 0.0.0.0:8124` emitted before
`[server] LangGraph API listening on 0.0.0.0:8123`
- [ ] Railway redeploy recovers `/api/health` + `/api/smoke`
## Summary
- Correct RELEASING.md prose to reflect the metadata-sync automation:
contributors only edit CHANGELOG.md; the `VS Code Extension — Metadata
Sync` workflow auto-bumps `package.json.version`/`.description` and
commits `chore: release vX.Y.Z` on the PR branch. The old copy still
said maintainers hand-edit `package.json`.
- Fix the Rollback section step 2 to match the new flow (CHANGELOG-only,
sync workflow handles `package.json`).
- Move all one-time bootstrap setup (GH environment variables/secrets +
Open VSX namespace claim) to a `One-time setup (historical reference)`
appendix at the bottom of the doc, under a horizontal rule. The front of
the doc now flows maintainer-first: Cutting a release → CI publish flow
→ Transient failures → Verifying → Rollback → Rollback guidance → Notes.
One-time setup was completed during 2026-04-21 pipeline bootstrap and
shouldn't need redoing; keeping it for disaster recovery only.
## Why
Two readers in a row tripped on the "hand-edit package.json" wording
because the automation now does that for them. And the one-time setup
was the first thing a maintainer saw on every release, even though
they'd never need to run any of it again.
## Test plan
- [x] `oxfmt --write` clean on RELEASING.md (no re-format on second
run).
- [x] H2 order matches the maintainer-first flow: Cutting a release, CI
publish flow, Transient registry failures, Verifying, Rollback, Rollback
guidance, Notes and future work, One-time setup (historical reference).
- [x] No content edits inside the moved sections — only relocation + a
one-line historical-reference intro above them.
The aimock-parity paragraph and the Rollback section still described
contributors hand-editing both package.json and CHANGELOG.md. That was
stale — the vscode-extension-changelog-sync.yml workflow auto-syncs
package.json.version (and optionally package.json.description) from
the CHANGELOG entry (and README first paragraph) when the PR is opened
and commits 'chore: release vX.Y.Z' on the PR branch.
Rewrite both passages so the documented flow matches reality: contributor
edits CHANGELOG.md only (optionally README.md), sync workflow handles the
package.json bump automatically, publish workflow self-gates via vsce show
and ships on merge to main.
Template actually pins node:22-slim for both builder and runner stages,
and eclipse-temurin:21-jre (not -jre-alpine) for spring-ai runner. README
called out node:24 + temurin:21-jre-alpine. Align docs with reality.
Node 24 bump is a separate decision not made in this body of work.
Completes the NODE_ENV scope cleanup started in the prior Python
packages commit. Image-scope ENV NODE_ENV=production leaks into every
child process (agent subprocesses, healthchecks, shell scripts) —
several frameworks change behavior under NODE_ENV and should NOT see
'production' when running as an agent backend.
- mastra: Dockerfile drops image ENV; entrypoint.sh now invokes
'env NODE_ENV=production npx next start' (was relying on image ENV).
- spring-ai: Dockerfile drops image ENV; entrypoint.sh now scopes
NODE_ENV=production to the Next.js child only (Java agent never
interpreted NODE_ENV but the scoping matches every other package).
- langgraph-python entrypoint: 'npx next start' now inherits
NODE_ENV=production via 'env' wrapper (Dockerfile already removed
image ENV in prior commit).
All 11 Python runners previously executed as root. Bring them into parity
with langroid (the one Python package that already had the pattern):
- Create uid/gid 1001 'app' user/group before any COPY
- Add --chown=app:app to runner-stage COPY directives
- USER app before EXPOSE/CMD
- langgraph-python: add the missing COPY --from=frontend /app/public line
(every other Python package has it) plus an empty public/.gitkeep so
the copy resolves
Packages touched: ag2, agno, claude-sdk-python, crewai-crews, google-adk,
langgraph-fastapi, langgraph-python, llamaindex, ms-agent-python,
pydantic-ai, strands.
- entrypoint.template.sh: cleanup trap + final kill now tolerate no-match
(|| true) so trap doesn't abort shell when one of the three PIDs has
already exited. Package-level entrypoint.sh already had this shape.
- generate-starters.ts: claude-sdk-typescript tsc now uses --rootDir .
so emitted artifact is /app/dist/agent/index.js (matches entrypoint
expectation + runner-stage COPY). tsc was inferring agent/ as rootDir
and stripping the prefix.
- generate-starters.ts: drop server.mjs from langgraph-typescript starter
output. The starter uses 'npx @langchain/langgraph-cli dev' and never
invokes server.mjs; the file imports @langchain/langgraph-api/server
which is not in the starter's extraDependencies (resolution relied on
transitive hoist via @langchain/langgraph-cli). The file stays in the
PACKAGE where prod mode does use it.
- generate-starters.ts: assert agentDir non-empty / non-absolute before
Dockerfile substitution so the 'rm -f {{AGENT_DIR}}/package*.json' line
can never degrade into 'rm -f /package*.json' across /app.
Verify Marketplace credential expects an active Azure session via
azure/login, but login was gated only on published==false. When
dry_run=true and version is already published, the verify step ran
without a session and failed. Widening the guard to include dry_run.
Add platforms: linux/amd64 to the depot/build-push-action invocation in
showcase_deploy.yml. Railway and GHCR serve x86 hosts, so an arm64-only
image crashes on pull. Matches the platform requirement enforced for
local docker build invocations (documented in
showcase/starters/template/README.md).
Add showcase/starters/template/README.md covering the multi-stage build
convention (builder + runtime split), the >=40% image-size reduction
target, the linux/amd64 platform requirement for local builds, and the
dockerfile_hygiene probe rules (>=2 FROM stages, no dev-dep installs in
the runtime stage).
Expand the header comment on showcase/scripts/generate-starters.ts to
document the >=40% size target, the getAgentBuildSteps/getAgentBuildCopy
handoff in more detail, and the linux/amd64 platform pin in the deploy
workflow.
Package-level service claims to listen on :8000 but Railway health probes
fail. Narrow the cold-start window by timestamping each phase from
entrypoint exec to first /health-serving moment:
- entrypoint.sh: print `[entrypoint] pre-node <ISO>` immediately before
`node /app/agent_server.js`, pairing with the agent_server.ts prints
below to chain timestamps across the exec boundary.
- agent_server.ts: print `[agent_server] module loaded <ISO>` before any
side-effect imports (so import-time hangs surface distinctly from
init-time hangs), `[agent_server] pre-Anthropic <ISO>` right before
`new Anthropic(...)`, and include an ISO timestamp in the existing
app.listen() callback so we can read the delta directly.
No behavior change — only logs added. Next deploy cycle should reveal
which phase consumes the budget and whether the failure mode is
listen-but-unreachable vs. listen-never-reached.