Skip to content

A08b: eject to project - write the agent directory into the workspace - #1160

Closed
LinuxDevil wants to merge 19 commits into
fb-w16-docs-studiofrom
fb-a08b-eject-to-project
Closed

LinuxDevil wants to merge 19 commits into
fb-w16-docs-studiofrom
fb-a08b-eject-to-project

Conversation

@LinuxDevil

Copy link
Copy Markdown
Owner

Summary

  • POST /agents/:id/eject (server/ejectRoute.ts): renders the spec with specToAgentDir and writes the agent-directory files under agents/<id>/ in the request's workspace (workspaceOf seam), atomically via writeFileAtomic inside withAgentLock.
  • Contract per ticket: 200 { dir, files }, 404 unknown agent, 409 LOUSHO_EJECT_EXISTS with existing details.files, { overwrite: true } to replace, 400 LOUSHO_EJECT_INVALID when generation fails or a path would escape the target (absolute / drive / NUL / .. rejected, plus a resolved-path containment check).
  • Code tab toolbar gains Eject to project (FolderOutput) behind a primary ConfirmDialog; on 409 it shows the server message and an "Overwrite existing files" checkbox (ConfirmDialog gains a children slot) that resends with overwrite: true; success toasts Ejected to agents/<id>/.
  • workspace/specLookup.ts extracts the shared read-spec-or-404 lookup (also adopted by the portability route); evals harness collector thunk adapted to CollectorSource.
  • Read-only gate applies automatically: every non-GET under /agents/ is refused with LOUSHO_WORKSPACE_READ_ONLY when the workspace SDK can't host runs.

Test plan

  • server/__tests__/ejectRoute.test.ts — 5 tests: 200 write, 409 + overwrite, 404, ../evil.ts → 400 nothing written, generator throw → 400
  • CodeTab.test.tsx — 2 new tests: dialog copy + confirm posts ejectAgent(id, false) + toast; 409 → error + checkbox → resends overwrite: true
  • typecheck + typecheck:server, eslint clean, fallow dead-code/dupes clean
  • Full client suite 4238 green; server suite green except the two known Windows flakes (hostSelector EPERM temp-dir race, idleVersions debounce)

Generated with Devin

LinuxDevil and others added 19 commits November 3, 2026 21:41
…ow spans

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Every guardrail check (input, runInParallel input, output, tool) emits a
`guardrail <name>` span: input/output checks nest under the run's
invoke_agent span, tool checks under that call's execute_tool span. The
span carries only ids and the outcome (lousho.guardrail.name, .kind,
.outcome, .parallel for parallel input checks, gen_ai.tool.name for tool
checks) - never the checked text or the trip reason. With no exporter or
no parent span the check runs directly, as before.

Spec-built agents stamp lousho.node.id = guard:<id>: NodeIdIndex gains
guardrailByName (entry id -> guard:<id>; S11 names each compiled
guardrail by its spec entry id) and the exporter maps 'guardrail ' spans
through it. Unknown names (code-built, inherited) stamp nothing.

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Add an `approval` flow node (ApprovalStepNode in RuntimeStep) so a human
gate can be its own step: the context's approve callback decides a
flow_approval request carrying { message, stepId }, or a durable run
pauses with status 'awaiting-approval' until FlowExecutor.resume()
brings the decision - exactly as a toolCall that needs approval does.

- flowToolGate: requireApproval renamed to exported requireFlowApproval,
  taking { toolName, toolCallId, args, subject }; toolCall error
  messages unchanged via subject "Tool '<name>'".
- FlowExecutor: approval handler + executeApprovalStep; outputVariable
  receives true once approved; refusal fails LOUSHO_FLOW_TOOL_DENIED.
- docs/flows.md: approval node row + Approval steps section.
- Regenerated api/*.api.md and llms-full.txt.

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…-s23-spec-subpath

# Conflicts:
#	docs/observability.md
#	llms-full.txt
#	src/spec/v2/nodeIdExporter.test.ts
#	src/spec/v2/nodeIdExporter.ts
Add a browser-safe lousho/spec subpath that re-exports the spec v1/v2
schemas, their spec types and the pure helpers Studio needs (catalogs,
upgradeSpec, agentSpecV2JsonSchema, specToCode, specNodeIds +
nodeIdExporter, flowProblems + flowSpecSchema, triggerSpecSchema +
listTriggerTypes) with no node:* imports anywhere in its graph.
listBuiltinTools is not exported: its catalog builds the real tool
factories, which pull node:net via web_fetch — it stays on the root.

Two leaf extractions keep the provider/executor stack out of the
browser bundle: approvalTtl.ts (approvalTtlMs/ttlMs helpers, re-exported
by execution/permissions.ts) and guardrailLists.ts (PII_TYPES and
MODERATION_CATEGORIES, consumed by spec/guardrailOptions.ts).

Studio's importExport.ts drops its deep ../../../../src/spec/schema
import for lousho/spec. Also: api/spec.api.md, typedoc/tsconfig/
fallow wiring, a packed-size budget bump in pack-smoke (+~33 KiB for
dist/spec/), JSDoc on the spec section types the entry re-exports
(ratchet baseline 1735 -> 1724), and the leftover merge-conflict
markers in nodeIdExporter.test.ts resolved keeping both tests.

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
# Conflicts:
#	api/executor.api.md
#	api/flows.api.md
#	api/index.api.md
#	api/kv.api.md
#	api/sqlite.api.md
#	api/types.api.md
#	api/utils.api.md
#	api/worker.api.md
#	scripts/pack-smoke.ts
#	src/spec/v2/flow.ts
- src/observe/index.ts re-exports the daemon surface Studio needs
  (startObserveDaemon, probeHealth, isObserveDaemon, defaultDbPath,
  retention/session/port defaults, ObserveDaemon(+Options), HealthProbe);
  wired through package.json exports/typesVersions, tsup, typedoc,
  tsconfig paths and api/observe.api.md.
- apps/agent-forge gains eslintRules/noSdkSource.mjs + a dedicated
  eslint.imports.config.mjs (`lint:imports`, CI-gated) that rejects
  SDK src/dist deep imports and relative escapes while allowing public
  subpaths; covers lousho and the future @lousho/build-ai-agent name.
- Studio deep imports migrated to public exports: specTypes.ts now
  re-exports from lousho/spec, judge.test.ts drives the daemon over its
  HTTP API via lousho/observe, checkpointStore.test.ts keeps its
  intentional SDK-repo fixture import behind a scoped disable.
- S23-base repairs the branch needs to build: drop committed merge
  markers from eight api reports, dedupe the author routes secrets dep,
  restore the draft schema field filter, seed the stateTools secrets stub,
  and wrap direct Topbar renders in ToastProvider.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
One CollectorManager (server/collector.ts) decides which observe daemon
the /observe proxy, run reporting, the judge loop and /settings/collector
all follow: env LOUSHO_OBSERVE_URL > settings collector.url > loopback
default; token = env LOUSHO_OBSERVE_TOKEN > the named secret. On boot
Studio attaches to a running daemon or starts one in-process on a
loopback URL (never with pidPath, closed on shutdown); a remote URL is
probe-only and a plain-HTTP port squatter reports down with the port
message. Down collectors re-probe every 30s.

GET/PUT /settings/collector expose a sanitized CollectorStatus
(hasToken, never the token); PUT validates the URL (400), honours the
env lock (409) and applies token set/keep/remove semantics. Settings →
Collector card edits the URL and token with per-mode status copy, the
locked state and Save/Check busy states. LOUSHO_STUDIO_COLLECTOR=0 and
startStudioServer({ collector: false }) keep the probe-only e2e path.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…udio

W13a: the SDK package moves from the unscoped 'lousho' name to the
scoped @linuxdevil/lousho so npm publishes land under the owner's scope.
Every import specifier, package reference, template, generated registry
item, deploy virtual-specifier matcher, dual-package test layout and
CLI message follows the new name; scripts/publish-github-packages.mjs
drops its now-obsolete alias rewrite since npm and GitHub Packages
names coincide.

Studio becomes independently publishable: apps/agent-forge is
@linuxdevil/studio with a 'lousho-studio' bin, files/engines/license/
repository/publishConfig metadata, and CJS tsup bundles (dist-server).
server/cli.ts parses --workspace/--port/--host/--help/--version,
validates them (exit 2 on bad input) and boots Studio with cwd as the
launch workspace; sdkRoot.ts resolves the SDK in both monorepo and
installed node_modules layouts; scripts/pin-sdk-range.mjs rewrites the
file:../.. dependency to a ^<version> range at prepack and restores it
at postpack. Runtime deps (@ai-sdk/*, ai, yaml, zod@3) move out of
devDependencies so the packed server runs standalone; zod stays v3 to
match Studio's schema style (the SDK peer accepts ^3.25.76 || ^4.0.0).
README/LICENSE added for the package.

Verified: SDK build + typecheck, Studio client/server typecheck,
lint + lint:imports (new no-sdk-source rule uses the registered
plugin id), SDK suite (7421 tests), Studio client suite (1238, one
perf-wall-clock flake that passes in isolation), Studio server suite
(1253), create-lousho unit + template e2e, api:check, docs:ratchet,
registry checks, npm pack (9-file tarball with pinned semver dep,
package.json restored), CLI smoke (--help/--version/port 0/health/
SIGTERM).

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…cher

The @linuxdevil/lousho package no longer contains apps/agent-forge:
files drops the Studio entries, prepublishOnly is just the SDK build,
prepack-check loses its Agent-Forge warning, and pack-smoke no longer
builds Studio, requires its paths, or allows anything under apps/.
Re-measured budgets: 1016 entries / 15,289,389 B unpacked / 4,393,121 B
packed (~10% headroom); MAX_ENTRIES stays at 1040 since the count still
fits. size-budget.json regenerated (bundleBytes also dropped ~30% vs
the stale S16b budget - drift, not this change).

src/cli/studio.ts is rewritten around findStudio(): the SDK checkout's
apps/agent-forge (package name @linuxdevil/studio) keeps the dev/prod
contributor modes - prod now spawns dist-server/cli.cjs with
--workspace/--port/--host and lets the Studio CLI mint its own token -
while an installed @linuxdevil/studio is found via createRequire from
cwd, then the SDK package root, then npm's global root, and launched
the same way. --prod/--dev against an installed target is a usage
error (exit 2); no target prints 'Studio is a separate install: npx
@linuxdevil/studio' and exits 1. spawn is injectable for tests.

Verified: tsc, lint, test:coverage (7427), build, pack-smoke (all
checks incl. strict-libs zod3/zod4), size-check, fallow (no new
findings), npm pack --dry-run shows zero apps/ paths, live
'lousho studio --help' and the missing-Studio exit-1 path.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
scripts/pack-smoke-studio.ts packs the SDK and @linuxdevil/studio,
checks the Studio tarball (required/forbidden paths, budgets, pinned
semver SDK dep, package.json restored by prepack/postpack), dry-run
publishes it, installs both tarballs into a fresh project, and proves
`lousho-studio` and the `lousho studio` launcher serve the UI.

smokeKit gains shared dryRunPublish/freePort; pack-smoke.ts uses them.
ci.yml moves all Studio checks out of the check/forge jobs into a
dedicated studio job that ends with pack-smoke:studio --skip-build.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
… compat suite

Brings the workspace/bridge/host-status line (W11a-c, passes, author,
canvas revamp) onto the publishable-package line (W12-W15a). Resolution
keeps both feature sets: CollectorManager alongside the workspace global
services, CollectorSource unified for run reporting with the selector's
env thunk derived from it, W14's public subpath imports and lint rule
re-applied over theirs' new 'lousho' specifiers (repo-wide rename sweep
to @linuxdevil/lousho), and theirs' newer tarball budgets.

W15b: bridge.compat.test.ts drives all seven protocol cases through
WorkspaceHostSelector + BridgeAgentHost against LOUSHO_BRIDGE_SDK_DIR
(default: this checkout); scriptedProvider.mjs learns the same env var so
the preload registers in the SDK under test. vitest.compat.config.ts +
test:bridge-compat script; studio CI job runs it against this commit's
SDK and a new bridge-compat-min job runs it against MIN_BRIDGE_SDK_VERSION
once published (skips with a notice until then). HostSelection records
the hello's protocol/specVersions for the assertions.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…ocumented deep seams

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- host banners and test fixtures now name @linuxdevil/lousho (the
  @lousho/build-ai-agent strings predated the W13a rename; the legacy
  name stays supported in workspaceSdk discovery only).
- specTypes stops re-exporting SecretRef so shared/wireTypes stays the
  single Studio-side export (fallow duplicate-exports); the three canvas
  consumers and types.test.ts import it from wireTypes, the same source
  the other fields already use.
- pack-smoke and pack-smoke-studio share packTarball/smokeDirs/
  smokeSetup/checkTarballContents/cleanSmokeDir/reportSmoke via
  smokeKit (fallow clone groups); mustRun goes private now that
  smokeSetup wraps it.
- drop unused dagre devDependency (elkjs replaced it in C17).

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
… collector

docs/agent-forge.md is retitled Lousho Studio and reorganized: install and
start (npx @linuxdevil/studio / lousho-studio / lousho studio launcher),
workspaces and the ~/.lousho storage split, the three kinds of agent,
per-workspace SDK selection with each host banner verbatim, the collector
lifecycle, reporting deployed agents via LOUSHO_OBSERVE_*, and independent
updates. installation/cli/observability/upgrading/README/CHANGELOG updated;
llms.txt regenerated.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
POST /agents/:id/eject renders the spec with specToAgentDir and writes the
files under agents/<id>/ in the request's workspace (workspaceOf seam),
atomically and under withAgentLock. 409 LOUSHO_EJECT_EXISTS carries the
existing file list; { overwrite: true } replaces. Generated paths are
re-validated before touching disk (absolute, drive-letter, NUL, '..'
segments all 400) and each resolved target must stay inside the eject
directory.

The Code tab toolbar gains "Eject to project" behind a primary
ConfirmDialog; on 409 it shows the server message plus an "Overwrite
existing files" checkbox that resends with overwrite: true. ConfirmDialog
gains a children slot for that checkbox. The shared readSpecOr404 lookup
is extracted to workspace/specLookup (also used by the portability route)
and the evals harness's collector thunk is adapted to CollectorSource.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@LinuxDevil

Copy link
Copy Markdown
Owner Author

Consolidated into #1155 — the full W-chain + A08b stack now merges as one PR to run a single CI cycle. This branch's commits are included there.

@LinuxDevil LinuxDevil closed this Nov 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant